Neste tutorial, estamos nos concentrando em como construir uma ferramenta CLI de resposta a perguntas usando Dewy e LangChain.js. Dewy é uma base de conhecimento de código aberto que ajuda os desenvolvedores a organizar e recuperar informações com eficiência. LangChain.js é uma estrutura que simplifica a integração de grandes modelos de linguagem (LLMs) em aplicativos. Ao combinar os recursos do Dewy para gerenciamento de conhecimento com a integração LLM do LangChain.js, você pode criar ferramentas que respondem a consultas complexas com informações precisas e relevantes.
Este guia orienta você na configuração do seu ambiente, no carregamento de documentos no Dewy e no uso de um LLM por meio do LangChain.js para responder perguntas com base nos dados armazenados. Ele foi projetado para engenheiros que buscam aprimorar seus projetos com funcionalidades avançadas de resposta a perguntas.
Dewy é uma base de conhecimento OSS projetada para agilizar a maneira como os desenvolvedores armazenam, organizam e recuperam informações. Sua flexibilidade e facilidade de uso o tornam uma excelente escolha para desenvolvedores que desejam construir aplicativos orientados ao conhecimento.
LangChain.js , por outro lado, é uma estrutura poderosa que permite aos desenvolvedores integrar LLMs em seus aplicativos de maneira transparente. Ao combinar o gerenciamento de conhecimento estruturado do Dewy com os recursos LLM do LangChain.js, os desenvolvedores podem criar sistemas sofisticados de resposta a perguntas que podem compreender e processar consultas complexas, oferecendo respostas precisas e contextualmente relevantes.
Nosso objetivo é construir um script CLI de resposta a perguntas simples, mas poderoso. Este script permitirá aos usuários carregar documentos na base de conhecimento Dewy e então usar um LLM, por meio de LangChain.js, para responder perguntas com base nas informações armazenadas em Dewy. Este tutorial irá guiá-lo através do processo, desde a configuração do seu ambiente até a implementação do script CLI.
Você aprenderá como usar o LangChain para construir um aplicativo simples de resposta a perguntas e como integrar o Dewy como uma fonte de conhecimento, permitindo que seu aplicativo responda perguntas com base em documentos específicos que você fornecer.
Antes de mergulhar no tutorial, certifique-se de atender aos seguintes pré-requisitos:
O código final deste exemplo está disponível no repositório Dewy se você quiser avançar.
Primeiro, crie um diretório para o projeto TypeScript CLI e mude para o diretório
mkdir dewy_qa cd dewy_qa
Com o diretório configurado, você pode instalar o TypeScript e inicializar o projeto:
npm init -y npm i typescript --save-dev npx tsc --init
Dependendo do seu ambiente, pode ser necessário fazer algumas alterações na configuração do TypeScript. Certifique-se de que seu tsconfig.json
seja semelhante ao seguinte:
{ "compilerOptions": { "target": "ES6", "module": "CommonJS", "moduleResolution": "node", "declaration": true, "outDir": "./dist", "esModuleInterop": true, "strict": true, }
Agora você está pronto para criar o aplicativo CLI. Para evitar que o código fique muito confuso, organize-o em vários diretórios, com o seguinte layout
dewy_qa/ ├── commands/ │ └── ... ├── utils/ │ └── ... ├── index.ts ├── package.json └── tsconfig.ts
Cada comando será implementado no diretório commands
e o código compartilhado irá para o diretório de utils
. O ponto de entrada para o aplicativo CLI é o arquivo index.ts
.
Comece com uma versão simples "olá mundo" de index.ts
- você começará a preenchê-la na próxima seção
#!/usr/bin/env ts-node-script console.log("hello world");
Para verificar se o ambiente está configurado corretamente, tente executar o seguinte comando - você deverá ver "hello world" impresso no console:
npx ts-node index.ts
Em vez de digitar esse comando muito longo todas as vezes, vamos criar uma entrada em package.json
para o comando. Isso nos ajudará a lembrar como invocar a CLI e facilitará a instalação como um comando:
{ ... "bin": { "dewy_qa": "./index.ts" } ... }
Agora você pode executar seu script com npm exec dewy_qa
ou npm link
the package e executá-lo apenas como dewy_qa
Carregue documentos configurando o cliente Dewy. O primeiro passo é adicionar algumas dependências ao projeto. A primeira é dewy-ts
, a biblioteca cliente do Dewy. O segundo é commander
, que nos ajudará a construir um aplicativo CLI com análise de argumentos, subcomandos e muito mais. Por fim, chalk
para deixar as instruções mais coloridas.
npm install dewy-ts commander chalk
A seguir, implemente a lógica do comando load. Você fará isso em um arquivo separado chamado commands/load.ts
. Este arquivo implementa uma função chamada load
, que espera uma URL e algumas opções adicionais - isso será conectado à CLI em uma seção posterior.
O Dewy torna o carregamento de documentos muito simples - basta configurar o cliente e chamar addDocument
com a URL do arquivo que você deseja carregar. Dewy se encarrega de extrair o conteúdo do PDF, dividindo-o em pedaços do tamanho certo para enviar a um LLM e indexando-os para pesquisa 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}`)); } }
Você deve ter notado que algumas funções foram importadas de ../utils/colors
. Este arquivo apenas configura alguns auxiliares para colorir a saída do console - coloque-o em utils
para que possa ser usado em outro lugar:
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);
Com a capacidade de carregar documentos no Dewy, é hora de integrar LangChain.js para utilizar LLMs para responder perguntas. Esta etapa envolve a configuração do LangChain.js para consultar a base de conhecimento Dewy e processar os resultados usando um LLM para gerar respostas.
Para começar, instale alguns pacotes adicionais - langchain
e openai
para usar a API OpenAI como LLM:
npm install dewy-langchain langchain @langchain/openai openai
Este comando é meio longo, então vamos percorrer várias partes dele antes de combiná-las no final
A primeira coisa a configurar é o Dewy (como antes) e um LLM. Uma diferença de antes é que dewy
é usado para construir um DewyRetriever
: este é um tipo especial usado por LangChain para recuperar informações como parte de uma cadeia. Você verá como o recuperador é usado em apenas um minuto.
const model = new ChatOpenAI({ openAIApiKey: options.openai_api_key, }); const dewy = new Dewy({ BASE: options.dewy_endpoint }) const retriever = new DewyRetriever({ dewy, collection });
Este é um modelo de string que instrui o LLM como ele deve se comportar, com espaços reservados para contexto adicional que será fornecido quando a "cadeia" for criada. Nesse caso, o LLM é instruído a responder à pergunta, mas apenas utilizando as informações fornecidas. Isso reduz a tendência do modelo de "alucinar" ou de inventar uma resposta plausível, mas errada. Os valores de context
e question
são fornecidos na próxima etapa:
const prompt = PromptTemplate.fromTemplate(`Answer the question based only on the following context: {context} Question: {question}`);
LangChain funciona construindo “cadeias” de comportamento que controlam como consultar o LLM e outras fontes de dados. Este exemplo usa LCEL , que fornece uma experiência de programação mais flexível do que algumas das interfaces originais do LangChain.
Use um RunnableSequence
para criar uma cadeia LCEL. Esta cadeia descreve como gerar o context
e os valores question
: o contexto é gerado usando o recuperador criado anteriormente e a pergunta é gerada passando pela entrada da etapa. Os resultados recuperados pelo Dewy são formatados como uma string, canalizando-os para a função formatDocumentsAsString
.
Esta cadeia faz o seguinte:
DewyRetriever
e os atribui ao context
e atribui o valor de entrada da cadeia a question
.context
e question
. const chain = RunnableSequence.from([ { context: retriever.pipe(formatDocumentsAsString), question: new RunnablePassthrough(), }, prompt, model, new StringOutputParser(), ]);
Agora que a cadeia foi construída, execute-a e envie os resultados para o console. Como você verá, question
é um argumento de entrada fornecido pelo chamador da função.
Executar a cadeia usando chain.streamLog()
permite que você veja cada pedaço de resposta conforme ele é retornado do LLM. O loop do manipulador de stream é meio feio, mas está apenas filtrando os resultados de stream apropriados e gravando-os em STDOUT
(usando console.log
, ele teria adicionado novas linhas após cada pedaço).
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); } } }
Agora que você viu todas as peças, está pronto para criar o comando query
. Deve ser semelhante ao comando load
anterior, com algumas importações adicionais.
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}`)); } }
Com Dewy e LangChain.js integrados, a próxima etapa é construir a interface CLI. Use uma biblioteca como commander
para criar uma interface de linha de comando amigável que suporte comandos para carregar documentos no Dewy e consultar a base de conhecimento usando LangChain.js.
Primeiro, reescreva index.ts
para criar os subcomandos load
e query
. O argumento --collection
determina em qual coleção Dewy o documento deve ser carregado (Dewy permite organizar documentos em coleções diferentes, semelhantes a pastas de arquivos). O argumento --dewy-endpoint
permite especificar como se conectar ao Dewy - por padrão, uma instância executada localmente na porta 8000
é assumida. Finalmente, o argumento --openai_api_key
(cujo padrão é uma variável de ambiente) configura a API 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);
OK, tudo pronto - não foi tão fácil? Você pode experimentar executando o comando:
dewy_qa load https://arxiv.org/pdf/2009.08553.pdf
Você deveria 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 }
A extração do conteúdo de um PDF grande pode levar um ou dois minutos, então você verá frequentemente "ingest_state": "pending"
ao carregar um novo documento pela primeira vez.
Em seguida, tente fazer algumas perguntas:
dewy_qa query "tell me about RAG
Você deveria 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...
Seguindo este guia, você aprendeu como criar uma CLI que usa Dewy para gerenciar conhecimento e LangChain.js para processar perguntas e gerar respostas. Esta ferramenta demonstra a aplicação prática de combinar uma base de conhecimento estruturada com o poder analítico dos LLMs, permitindo aos desenvolvedores construir aplicações mais inteligentes e responsivas.