Dans ce didacticiel, nous nous concentrons sur la façon de créer un outil CLI de réponse aux questions à l'aide de Dewy et LangChain.js. Dewy est une base de connaissances open source qui aide les développeurs à organiser et à récupérer efficacement les informations. LangChain.js est un framework qui simplifie l'intégration de grands modèles de langage (LLM) dans les applications. En combinant les capacités de gestion des connaissances de Dewy avec l'intégration LLM de LangChain.js, vous pouvez créer des outils qui répondent à des requêtes complexes avec des informations précises et pertinentes.
Ce guide vous guide dans la configuration de votre environnement, le chargement de documents dans Dewy et l'utilisation d'un LLM via LangChain.js pour répondre aux questions basées sur les données stockées. Il est conçu pour les ingénieurs qui cherchent à améliorer leurs projets avec des fonctionnalités avancées de réponse aux questions.
Dewy est une base de connaissances OSS conçue pour rationaliser la façon dont les développeurs stockent, organisent et récupèrent les informations. Sa flexibilité et sa facilité d'utilisation en font un excellent choix pour les développeurs souhaitant créer des applications basées sur la connaissance.
LangChain.js , quant à lui, est un framework puissant qui permet aux développeurs d'intégrer de manière transparente les LLM dans leurs applications. En combinant la gestion structurée des connaissances de Dewy avec les capacités LLM de LangChain.js, les développeurs peuvent créer des systèmes de questions-réponses sophistiqués capables de comprendre et de traiter des requêtes complexes, offrant des réponses précises et contextuellement pertinentes.
Notre objectif est de créer un script CLI de réponse aux questions simple mais puissant. Ce script permettra aux utilisateurs de charger des documents dans la base de connaissances Dewy, puis d'utiliser un LLM, via LangChain.js, pour répondre à des questions basées sur les informations stockées dans Dewy. Ce didacticiel vous guidera tout au long du processus, depuis la configuration de votre environnement jusqu'à la mise en œuvre du script CLI.
Vous apprendrez comment utiliser LangChain pour créer une application simple de réponse aux questions et comment intégrer Dewy en tant que source de connaissances, permettant à votre application de répondre aux questions basées sur les documents spécifiques que vous lui fournissez.
Avant de plonger dans le didacticiel, assurez-vous que les conditions préalables suivantes sont couvertes :
Le code final de cet exemple est disponible dans le dépôt Dewy si vous souhaitez aller de l'avant.
Tout d’abord, créez un répertoire pour le projet TypeScript CLI et accédez au répertoire
mkdir dewy_qa cd dewy_qa
Une fois le répertoire configuré, vous pouvez installer TypeScript et initialiser le projet :
npm init -y npm i typescript --save-dev npx tsc --init
En fonction de votre environnement, vous devrez peut-être apporter quelques modifications à votre configuration TypeScript. Assurez-vous que votre tsconfig.json
ressemble à ceci :
{ "compilerOptions": { "target": "ES6", "module": "CommonJS", "moduleResolution": "node", "declaration": true, "outDir": "./dist", "esModuleInterop": true, "strict": true, }
Vous êtes maintenant prêt à créer l'application CLI. Pour éviter que le code ne devienne trop compliqué, organisez-le dans plusieurs répertoires, avec la disposition suivante
dewy_qa/ ├── commands/ │ └── ... ├── utils/ │ └── ... ├── index.ts ├── package.json └── tsconfig.ts
Chaque commande sera implémentée dans le répertoire commands
et le code partagé ira dans le répertoire utils
. Le point d'entrée de l'application CLI est le fichier index.ts
.
Commencez par une simple version "hello world" d' index.ts
- vous commencerez à la remplir dans la section suivante
#!/usr/bin/env ts-node-script console.log("hello world");
Pour vérifier que l'environnement est correctement configuré, essayez d'exécuter la commande suivante : vous devriez voir "hello world" imprimé dans la console :
npx ts-node index.ts
Plutôt que de taper cette très longue commande à chaque fois, créons une entrée dans package.json
pour la commande. Cela nous aidera à nous rappeler comment appeler la CLI et facilitera son installation en tant que commande :
{ ... "bin": { "dewy_qa": "./index.ts" } ... }
Vous pouvez maintenant exécuter votre script avec npm exec dewy_qa
ou npm link
le package et l'exécuter simplement comme dewy_qa
Chargez des documents en configurant le client Dewy. La première étape consiste à ajouter quelques dépendances au projet. La première est dewy-ts
, la bibliothèque client de Dewy. Le second est commander
, qui nous aidera à créer une application CLI avec une analyse des arguments, des sous-commandes, etc. Enfin, chalk
rend les invites plus colorées.
npm install dewy-ts commander chalk
Ensuite, implémentez la logique de la commande de chargement. Vous ferez cela dans un fichier séparé nommé commands/load.ts
. Ce fichier implémente une fonction nommée load
, qui attend une URL et quelques options supplémentaires - cela sera connecté à la CLI dans une section ultérieure.
Dewy rend le chargement des documents très simple : il suffit de configurer le client et d'appeler addDocument
avec l'URL du fichier que vous souhaitez charger. Dewy s'occupe d'extraire le contenu du PDF, de le diviser en morceaux de la bonne taille pour l'envoyer à un LLM et de les indexer pour une recherche sémantique.
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}`)); } }
Vous avez peut-être remarqué que certaines fonctions ont été importées depuis ../utils/colors
. Ce fichier configure simplement quelques assistants pour colorer la sortie de la console - placez-le dans utils
afin qu'il puisse être utilisé ailleurs :
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);
Avec la possibilité de charger des documents dans Dewy, il est temps d'intégrer LangChain.js pour utiliser les LLM pour répondre aux questions. Cette étape implique la configuration de LangChain.js pour interroger la base de connaissances Dewy et traiter les résultats à l'aide d'un LLM pour générer des réponses.
Pour commencer, installez quelques packages supplémentaires - langchain
et openai
pour utiliser l'API OpenAI comme LLM :
npm install dewy-langchain langchain @langchain/openai openai
Cette commande est assez longue, nous allons donc en parcourir plusieurs éléments avant de les combiner à la fin.
La première chose à configurer est Dewy (comme avant) et un LLM. Une différence par rapport à avant est que dewy
est utilisé pour construire un DewyRetriever
: il s'agit d'un type spécial utilisé par LangChain pour récupérer des informations dans le cadre d'une chaîne. Vous verrez comment le retriever est utilisé dans une minute.
const model = new ChatOpenAI({ openAIApiKey: options.openai_api_key, }); const dewy = new Dewy({ BASE: options.dewy_endpoint }) const retriever = new DewyRetriever({ dewy, collection });
Il s'agit d'un modèle de chaîne qui indique au LLM comment il doit se comporter, avec des espaces réservés pour un contexte supplémentaire qui seront fournis lors de la création de la « chaîne ». Dans ce cas, le LLM est chargé de répondre à la question, mais uniquement en utilisant les informations fournies. Cela réduit la tendance du modèle à « halluciner » ou à inventer une réponse plausible mais fausse. Les valeurs du context
et question
sont fournies à l'étape suivante :
const prompt = PromptTemplate.fromTemplate(`Answer the question based only on the following context: {context} Question: {question}`);
LangChain fonctionne en créant des « chaînes » de comportement qui contrôlent la manière d'interroger le LLM et d'autres sources de données. Cet exemple utilise LCEL , qui offre une expérience de programmation plus flexible que certaines des interfaces originales de LangChain.
Utilisez un RunnableSequence
pour créer une chaîne LCEL. Cette chaîne décrit comment générer les valeurs context
et question
: le contexte est généré à l'aide du récupérateur créé précédemment et la question est générée en passant par l'entrée de l'étape. Les résultats récupérés par Dewy sont formatés sous forme de chaîne en les redirigeant vers la fonction formatDocumentsAsString
.
Cette chaîne effectue les opérations suivantes :
DewyRetriever
, les affecte au context
et attribue la valeur d'entrée de la chaîne à question
.context
et question
. const chain = RunnableSequence.from([ { context: retriever.pipe(formatDocumentsAsString), question: new RunnablePassthrough(), }, prompt, model, new StringOutputParser(), ]);
Maintenant que la chaîne a été construite, exécutez-la et affichez les résultats sur la console. Comme vous le verrez, question
est un argument d'entrée fourni par l'appelant de la fonction.
L'exécution de la chaîne à l'aide chain.streamLog()
vous permet de voir chaque morceau de réponse tel qu'il est renvoyé par le LLM. La boucle du gestionnaire de flux est en quelque sorte moche, mais elle filtre simplement les résultats du flux appropriés et les écrit dans STDOUT
(en utilisant console.log
, elle aurait ajouté des nouvelles lignes après chaque morceau).
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); } } }
Maintenant que vous avez vu tous les éléments, vous êtes prêt à créer la commande query
. Cela devrait ressembler à la commande load
d'avant, avec quelques importations supplémentaires.
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}`)); } }
Avec Dewy et LangChain.js intégrés, l'étape suivante consiste à créer l'interface CLI. Utilisez une bibliothèque telle que commander
pour créer une interface de ligne de commande conviviale qui prend en charge les commandes permettant de charger des documents dans Dewy et d'interroger la base de connaissances à l'aide de LangChain.js.
Tout d’abord, réécrivez index.ts
pour créer les sous-commandes load
et query
. L'argument --collection
détermine dans quelle collection Dewy le document doit être chargé (Dewy vous permet d'organiser les documents en différentes collections, similaires aux dossiers de fichiers). L'argument --dewy-endpoint
vous permet de spécifier comment vous connecter à Dewy - par défaut, une instance exécutée localement sur le port 8000
est supposée. Enfin, l'argument --openai_api_key
(qui est par défaut une variable d'environnement) configure l'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, tout est terminé – n'était-ce pas facile ? Vous pouvez l'essayer en exécutant la commande :
dewy_qa load https://arxiv.org/pdf/2009.08553.pdf
Vous devriez voir quelque chose comme
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 }
L'extraction du contenu d'un PDF volumineux peut prendre une minute ou deux, c'est pourquoi vous verrez souvent "ingest_state": "pending"
lorsque vous chargez pour la première fois un nouveau document.
Ensuite, essayez de poser quelques questions :
dewy_qa query "tell me about RAG
Vous devriez voir quelque chose comme
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...
En suivant ce guide, vous avez appris à créer une CLI qui utilise Dewy pour gérer les connaissances et LangChain.js pour traiter les questions et générer des réponses. Cet outil démontre l'application pratique de la combinaison d'une base de connaissances structurée avec la puissance analytique des LLM, permettant aux développeurs de créer des applications plus intelligentes et réactives.