paint-brush
Criando uma CLI de resposta a perguntas com Dewy e LangChain.jspor@kerinin
494 leituras
494 leituras

Criando uma CLI de resposta a perguntas com Dewy e LangChain.js

por Ryan11m2024/02/17
Read on Terminal Reader

Muito longo; Para ler

Neste tutorial, estamos nos concentrando em como construir uma ferramenta CLI de resposta a perguntas usando Dewy e LangChain.js.
featured image - Criando uma CLI de resposta a perguntas com Dewy e LangChain.js
Ryan HackerNoon profile picture
0-item
1-item
2-item

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.

Por que Dewy e LangChain.js?

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.

O objetivo

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.

Pré-requisitos

Antes de mergulhar no tutorial, certifique-se de atender aos seguintes pré-requisitos:

  • Conhecimento básico de programação Typescript
  • Familiaridade com desenvolvimento de ferramentas CLI
  • Uma cópia do Dewy em execução em sua máquina local (consulte as instruções de instalação do Dewy se precisar de ajuda aqui).

Etapa 1: configure seu projeto

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

Etapa 2: implementar o carregamento de documentos

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);

Etapa 3: implementar respostas a perguntas

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

Crie clientes para OpenAI e Dewy

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 });

Crie um prompt LangChain

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}`);

Construa a corrente

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:

  1. Ele recupera documentos usando o DewyRetriever e os atribui ao context e atribui o valor de entrada da cadeia a question .
  2. Ele formata a string do prompt usando as variáveis context e question .
  3. Ele passa o prompt formatado para o LLM gerar uma resposta.
  4. Ele formata a resposta do LLM como uma string.
 const chain = RunnableSequence.from([ { context: retriever.pipe(formatDocumentsAsString), question: new RunnablePassthrough(), }, prompt, model, new StringOutputParser(), ]);

Execute a cadeia

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); } } }

Junte tudo como um comando

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}`)); } }

Etapa 4: Construindo a CLI

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...

Conclusão

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.

Leitura adicional e recursos