paint-brush
Creación de una CLI de respuesta a preguntas con Dewy y LangChain.jspor@kerinin
464 lecturas
464 lecturas

Creación de una CLI de respuesta a preguntas con Dewy y LangChain.js

por Ryan11m2024/02/17
Read on Terminal Reader

Demasiado Largo; Para Leer

En este tutorial, nos centramos en cómo crear una herramienta CLI de respuesta a preguntas utilizando Dewy y LangChain.js.
featured image - Creación de una CLI de respuesta a preguntas con Dewy y LangChain.js
Ryan HackerNoon profile picture
0-item
1-item
2-item

En este tutorial, nos centramos en cómo crear una herramienta CLI de respuesta a preguntas utilizando Dewy y LangChain.js. Dewy es una base de conocimientos de código abierto que ayuda a los desarrolladores a organizar y recuperar información de manera eficiente. LangChain.js es un marco que simplifica la integración de grandes modelos de lenguaje (LLM) en aplicaciones. Al combinar las capacidades de Dewy para gestionar el conocimiento con la integración LLM de LangChain.js, puede crear herramientas que respondan consultas complejas con información precisa y relevante.


Esta guía lo guiará a través de la configuración de su entorno, la carga de documentos en Dewy y el uso de un LLM a través de LangChain.js para responder preguntas basadas en los datos almacenados. Está diseñado para ingenieros que buscan mejorar sus proyectos con funcionalidades avanzadas de respuesta a preguntas.

¿Por qué Dewy y LangChain.js?

Dewy es una base de conocimientos de OSS diseñada para optimizar la forma en que los desarrolladores almacenan, organizan y recuperan información. Su flexibilidad y facilidad de uso lo convierten en una excelente opción para los desarrolladores que desean crear aplicaciones basadas en el conocimiento.


LangChain.js , por otro lado, es un marco poderoso que permite a los desarrolladores integrar LLM en sus aplicaciones sin problemas. Al combinar la gestión del conocimiento estructurado de Dewy con las capacidades LLM de LangChain.js, los desarrolladores pueden crear sofisticados sistemas de respuesta a preguntas que pueden comprender y procesar consultas complejas, ofreciendo respuestas precisas y contextualmente relevantes.

La meta

Nuestro objetivo es crear un script CLI de respuesta a preguntas simple pero potente. Este script permitirá a los usuarios cargar documentos en la base de conocimientos de Dewy y luego utilizar un LLM, a través de LangChain.js, para responder preguntas basadas en la información almacenada en Dewy. Este tutorial lo guiará a través del proceso, desde la configuración de su entorno hasta la implementación del script CLI.


Aprenderá cómo utilizar LangChain para crear una aplicación sencilla de respuesta a preguntas y cómo integrar Dewy como fuente de conocimiento, permitiendo que su aplicación responda preguntas basadas en documentos específicos que usted le proporcione.

Requisitos previos

Antes de sumergirse en el tutorial, asegúrese de tener cubiertos los siguientes requisitos previos:

  • Conocimientos básicos de programación mecanografiada.
  • Familiaridad con el desarrollo de herramientas CLI.
  • Una copia de Dewy ejecutándose en su máquina local (consulte las instrucciones de instalación de Dewy si necesita ayuda aquí).

Paso 1: configura tu proyecto

El código final para este ejemplo está disponible en el repositorio de Dewy si desea avanzar.

Primero, cree un directorio para el proyecto CLI de TypeScript y cámbielo al directorio

 mkdir dewy_qa cd dewy_qa

Con el directorio configurado, puede instalar TypeScript e inicializar el proyecto:

 npm init -y npm i typescript --save-dev npx tsc --init

Dependiendo de su entorno, es posible que deba realizar algunos cambios en su configuración de TypeScript. Asegúrese de que su tsconfig.json se parezca a lo siguiente:

 { "compilerOptions": { "target": "ES6", "module": "CommonJS", "moduleResolution": "node", "declaration": true, "outDir": "./dist", "esModuleInterop": true, "strict": true, }

Ahora está listo para crear la aplicación CLI. Para evitar que el código se ensucie demasiado, organícelo en varios directorios, con el siguiente diseño

 dewy_qa/ ├── commands/ │ └── ... ├── utils/ │ └── ... ├── index.ts ├── package.json └── tsconfig.ts

Cada comando se implementará en el directorio commands y el código compartido irá al directorio utils . El punto de entrada a la aplicación CLI es el archivo index.ts .

Comience con una versión simple de "hola mundo" de index.ts ; comenzará a completarla en la siguiente sección

 #!/usr/bin/env ts-node-script console.log("hello world");

Para verificar que el entorno esté configurado correctamente, intente ejecutar el siguiente comando; debería ver "hola mundo" impreso en la consola:

 npx ts-node index.ts

En lugar de escribir este comando muy largo cada vez, creemos una entrada en package.json para el comando. Esto nos ayudará a recordar cómo invocar la CLI y facilitará la instalación como un comando:

 { ... "bin": { "dewy_qa": "./index.ts" } ... }

Ahora puede ejecutar su script con npm exec dewy_qa o npm link el paquete y ejecutarlo simplemente como dewy_qa

Paso 2: implementar la carga de documentos

Cargue documentos configurando el cliente Dewy. El primer paso es agregar algunas dependencias al proyecto. El primero es dewy-ts , la biblioteca cliente de Dewy. El segundo es commander , que nos ayudará a crear una aplicación CLI con análisis de argumentos, subcomandos y más. Finalmente, chalk para que las indicaciones sean más coloridas.

 npm install dewy-ts commander chalk

A continuación, implemente la lógica del comando de carga. Hará esto en un archivo separado llamado commands/load.ts . Este archivo implementa una función llamada load , que espera una URL y algunas opciones adicionales; esto se conectará con la CLI en una sección posterior.


Dewy hace que la carga de documentos sea súper simple: simplemente configure el cliente y llame a addDocument con la URL del archivo que desea cargar. Dewy se encarga de extraer el contenido del PDF, dividirlo en fragmentos del tamaño adecuado para enviarlos a un LLM e indexarlos para una búsqueda semántica.

 import { Dewy } from 'dewy-ts'; import { success, error } from '../utils/colors'; export async function load(url: string, options: { collection: string, dewy_endpoint: string }): Promise<void> { console.log(success(`Loading ${url} into collection: ${options.collection}`)); try { const dewy = new Dewy({ BASE: options.dewy_endpoint }) const result = await dewy.kb.addDocument({ collection: options.collection, url }); console.log(success(`File loaded successfully`)); console.log(JSON.stringify(result, null, 2)); } catch (err: any) { console.error(error(`Failed to load file: ${err.message}`)); } }

Es posible que hayas notado que algunas funciones se importaron desde ../utils/colors . Este archivo simplemente configura algunos asistentes para colorear la salida de la consola; colóquelo en utils para que pueda usarse en otros lugares:

 import chalk from 'chalk'; export const success = (message: string) => chalk.green(message); export const info = (message: string) => chalk.blue(message); export const error = (message: string) => chalk.red(message);

Paso 3: implementar la respuesta a preguntas

Con la capacidad de cargar documentos en Dewy, es hora de integrar LangChain.js para utilizar LLM para responder preguntas. Este paso implica configurar LangChain.js para consultar la base de conocimientos de Dewy y procesar los resultados utilizando un LLM para generar respuestas.


Para comenzar, instale algunos paquetes adicionales: langchain y openai para usar la API de OpenAI como LLM:

 npm install dewy-langchain langchain @langchain/openai openai

Este comando es algo largo, por lo que repasaremos varias partes antes de combinarlas al final.

Cree clientes para OpenAI y Dewy

Lo primero que hay que configurar es Dewy (como antes) y un LLM. Una diferencia con respecto a antes es que dewy se usa para construir un DewyRetriever : este es un tipo especial usado por LangChain para recuperar información como parte de una cadena. Verás cómo se utiliza el retriever en tan sólo un minuto.

 const model = new ChatOpenAI({ openAIApiKey: options.openai_api_key, }); const dewy = new Dewy({ BASE: options.dewy_endpoint }) const retriever = new DewyRetriever({ dewy, collection });

Crear un mensaje LangChain

Esta es una plantilla de cadena que indica al LLM cómo debe comportarse, con marcadores de posición para contexto adicional que se proporcionarán cuando se cree la "cadena". En este caso, el LLM debe responder la pregunta, pero únicamente utilizando la información que se le proporciona. Esto reduce la tendencia del modelo a "alucinar" o inventar una respuesta que sea plausible pero incorrecta. Los valores de context y question se proporcionan en el siguiente paso:

 const prompt = PromptTemplate.fromTemplate(`Answer the question based only on the following context: {context} Question: {question}`);

Construye la cadena

LangChain funciona construyendo "cadenas" de comportamiento que controlan cómo consultar el LLM y otras fuentes de datos. Este ejemplo utiliza LCEL , que proporciona una experiencia de programación más flexible que algunas de las interfaces originales de LangChain.


Utilice RunnableSequence para crear una cadena LCEL. Esta cadena describe cómo generar el context y los valores question : el contexto se genera utilizando el recuperador creado anteriormente y la pregunta se genera pasando por la entrada del paso. Los resultados que recupera Dewy se formatean como una cadena canalizándolos a la función formatDocumentsAsString .


Esta cadena hace lo siguiente:

  1. Recupera documentos utilizando DewyRetriever , los asigna al context y asigna el valor de entrada de la cadena a question .
  2. Da formato a la cadena de solicitud utilizando las variables context y question .
  3. Pasa el mensaje formateado al LLM para generar una respuesta.
  4. Formatea la respuesta del LLM como una cadena.
 const chain = RunnableSequence.from([ { context: retriever.pipe(formatDocumentsAsString), question: new RunnablePassthrough(), }, prompt, model, new StringOutputParser(), ]);

Ejecutar la cadena

Ahora que se ha construido la cadena, ejecútela y envíe los resultados a la consola. Como verá, question es un argumento de entrada proporcionado por quien llama a la función.


La ejecución de la cadena usando chain.streamLog() le permite ver cada fragmento de respuesta tal como lo devuelve el LLM. El bucle del controlador de transmisión es algo feo, pero solo filtra los resultados de la transmisión apropiados y los escribe en STDOUT (usando console.log habría agregado nuevas líneas después de cada fragmento).

 const stream = await chain.streamLog(question); // Write chunks of the response to STDOUT as they're received console.log("Answer:"); for await (const chunk of stream) { if (chunk.ops?.length > 0 && chunk.ops[0].op === "add") { const addOp = chunk.ops[0]; if ( addOp.path.startsWith("/logs/ChatOpenAI") && typeof addOp.value === "string" && addOp.value.length ) { process.stdout.write(addOp.value); } } }

Júntelo todo como un comando

Ahora que ha visto todas las piezas, está listo para crear el comando query . Esto debería ser similar al comando load anterior, con algunas importaciones adicionales.

 import { StringOutputParser } from "@langchain/core/output_parsers"; import { PromptTemplate } from "@langchain/core/prompts"; import { formatDocumentsAsString } from "langchain/util/document"; import { RunnablePassthrough, RunnableSequence } from "@langchain/core/runnables"; import { ChatOpenAI } from "@langchain/openai"; import { Dewy } from 'dewy-ts'; import { DewyRetriever } from 'dewy-langchain'; import { success, error } from '../utils/colors'; export async function query(question: string, options: { collection: string, dewy_endpoint: string, openai_api_key: string }): Promise<void> { console.log(success(`Querying ${options.collection} collection for: "${question}"`)); try { const model = new ChatOpenAI({ openAIApiKey: options.openai_api_key, }); const dewy = new Dewy({ BASE: options.dewy_endpoint }) const retriever = new DewyRetriever({ dewy, collection: options.collection }); const prompt = PromptTemplate.fromTemplate(`Answer the question based only on the following context: {context} Question: {question}`); const chain = RunnableSequence.from([ { context: retriever.pipe(formatDocumentsAsString), question: new RunnablePassthrough(), }, prompt, model, new StringOutputParser(), ]); const stream = await chain.streamLog(question); // Write chunks of the response to STDOUT as they're received console.log("Answer:"); for await (const chunk of stream) { if (chunk.ops?.length > 0 && chunk.ops[0].op === "add") { const addOp = chunk.ops[0]; if ( addOp.path.startsWith("/logs/ChatOpenAI") && typeof addOp.value === "string" && addOp.value.length ) { process.stdout.write(addOp.value); } } } } catch (err: any) { console.error(error(`Failed to query: ${err.message}`)); } }

Paso 4: construir la CLI

Con Dewy y LangChain.js integrados, el siguiente paso es crear la interfaz CLI. Utilice una biblioteca como commander para crear una interfaz de línea de comandos fácil de usar que admita comandos para cargar documentos en Dewy y consultar la base de conocimientos utilizando LangChain.js.


Primero, reescriba index.ts para crear los subcomandos load y query . El argumento --collection determina en qué colección de Dewy se debe cargar el documento (Dewy le permite organizar documentos en diferentes colecciones, similar a las carpetas de archivos). El argumento --dewy-endpoint le permite especificar cómo conectarse a Dewy; de forma predeterminada, se asume una instancia que se ejecuta localmente en el puerto 8000 . Finalmente, el argumento --openai_api_key (que por defecto es una variable de entorno) configura la API de OpenAI:

 #!/usr/bin/env ts-node-script import { Command } from 'commander'; import { load } from './commands/load'; import { query } from './commands/query'; const program = new Command(); program.name('dewy-qa').description('CLI tool for interacting with a knowledge base API').version('1.0.0'); const defaultOpenAIKey = process.env.OPENAI_API_KEY; program .command('load') .description("Load documents into Dewy from a URL") .option('--collection <collection>', 'Specify the collection name', 'main') .option('--dewy-endpoint <endpoint>', 'Specify the collection name', 'http://localhost:8000') .argument('<url>', 'URL to load into the knowledge base') .action(load); program .command('query') .description('Ask questions using an LLM and the loaded documents for answers') .option('--collection <collection>', 'Specify the collection name', 'main') .option('--dewy-endpoint <endpoint>', 'Specify the collection name', 'http://localhost:8000') .option('--openai-api-key <key>', 'Specify the collection name', defaultOpenAIKey) .argument('<question>', 'Question to ask the knowledge base') .action(query); program.parse(process.argv);

Bien, ya está todo hecho. ¿No fue tan fácil? Puedes probarlo ejecutando el comando:

 dewy_qa load https://arxiv.org/pdf/2009.08553.pdf

Deberías ver algo como

 Loading https://arxiv.org/pdf/2009.08553.pdf into collection: main File loaded successfully { "id": 18, "collection": "main", "extracted_text": null, "url": "https://arxiv.org/pdf/2009.08553.pdf", "ingest_state": "pending", "ingest_error": null }

Extraer el contenido de un PDF grande puede llevar uno o dos minutos, por lo que a menudo verá "ingest_state": "pending" cuando cargue un documento nuevo por primera vez.

A continuación, intente hacer algunas preguntas:

 dewy_qa query "tell me about RAG

Deberías ver algo como

 Querying main collection for: "tell me about RAG" Answer: Based on the given context, RAG refers to the RAG proteins, which are involved in DNA binding and V(D)J recombination. The RAG1 and RAG2 proteins work together to bind specific DNA sequences known as RSS (recombination signal sequences) and facilitate the cutting and rearrangement of DNA segments during the process of V(D)J recombination...

Conclusión

Siguiendo esta guía, ha aprendido a crear una CLI que utiliza Dewy para gestionar el conocimiento y LangChain.js para procesar preguntas y generar respuestas. Esta herramienta demuestra la aplicación práctica de combinar una base de conocimientos estructurada con el poder analítico de los LLM, lo que permite a los desarrolladores crear aplicaciones más inteligentes y receptivas.

Lecturas y recursos adicionales