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.
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.
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.
Antes de sumergirse en el tutorial, asegúrese de tener cubiertos los siguientes requisitos previos:
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
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);
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.
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 });
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}`);
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:
DewyRetriever
, los asigna al context
y asigna el valor de entrada de la cadena a question
.context
y question
. const chain = RunnableSequence.from([ { context: retriever.pipe(formatDocumentsAsString), question: new RunnablePassthrough(), }, prompt, model, new StringOutputParser(), ]);
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); } } }
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}`)); } }
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...
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.