In diesem Tutorial konzentrieren wir uns darauf, wie man mit Dewy und LangChain.js ein CLI-Tool zur Beantwortung von Fragen erstellt. Dewy ist eine Open-Source-Wissensdatenbank, die Entwicklern hilft, Informationen effizient zu organisieren und abzurufen. LangChain.js ist ein Framework, das die Integration großer Sprachmodelle (LLMs) in Anwendungen vereinfacht. Durch die Kombination der Fähigkeiten von Dewy zur Wissensverwaltung mit der LLM-Integration von LangChain.js können Sie Tools erstellen, die komplexe Anfragen mit präzisen und relevanten Informationen beantworten.
Dieser Leitfaden führt Sie durch die Einrichtung Ihrer Umgebung, das Laden von Dokumenten in Dewy und die Verwendung eines LLM über LangChain.js, um Fragen basierend auf den gespeicherten Daten zu beantworten. Es richtet sich an Ingenieure, die ihre Projekte mit erweiterten Frage-Antwort-Funktionen erweitern möchten.
Dewy ist eine OSS-Wissensdatenbank, die entwickelt wurde, um die Art und Weise zu optimieren, wie Entwickler Informationen speichern, organisieren und abrufen. Seine Flexibilität und Benutzerfreundlichkeit machen es zu einer ausgezeichneten Wahl für Entwickler, die wissensbasierte Anwendungen erstellen möchten.
LangChain.js hingegen ist ein leistungsstarkes Framework, das es Entwicklern ermöglicht, LLMs nahtlos in ihre Anwendungen zu integrieren. Durch die Kombination des strukturierten Wissensmanagements von Dewy mit den LLM-Funktionen von LangChain.js können Entwickler anspruchsvolle Frage-Antwort-Systeme erstellen, die komplexe Abfragen verstehen und verarbeiten können und präzise und kontextrelevante Antworten bieten.
Unser Ziel ist es, ein einfaches, aber leistungsstarkes CLI-Skript zur Beantwortung von Fragen zu erstellen. Dieses Skript ermöglicht es Benutzern, Dokumente in die Dewy-Wissensdatenbank zu laden und dann über LangChain.js ein LLM zu verwenden, um Fragen basierend auf den in Dewy gespeicherten Informationen zu beantworten. Dieses Tutorial führt Sie durch den Prozess, von der Einrichtung Ihrer Umgebung bis zur Implementierung des CLI-Skripts.
Sie erfahren, wie Sie mit LangChain eine einfache Anwendung zur Beantwortung von Fragen erstellen und wie Sie Dewy als Wissensquelle integrieren, sodass Ihre Anwendung Fragen auf der Grundlage spezifischer Dokumente beantworten kann, die Sie ihr bereitstellen.
Bevor Sie mit dem Tutorial beginnen, stellen Sie sicher, dass die folgenden Voraussetzungen erfüllt sind:
Der endgültige Code für dieses Beispiel ist im Dewy-Repo verfügbar, wenn Sie weitermachen möchten.
Erstellen Sie zunächst ein Verzeichnis für das TypeScript-CLI-Projekt und wechseln Sie in das Verzeichnis
mkdir dewy_qa cd dewy_qa
Wenn das Verzeichnis eingerichtet ist, können Sie TypeScript installieren und das Projekt initialisieren:
npm init -y npm i typescript --save-dev npx tsc --init
Abhängig von Ihrer Umgebung müssen Sie möglicherweise einige Änderungen an Ihrer TypeScript-Konfiguration vornehmen. Stellen Sie sicher, dass Ihre tsconfig.json
etwa wie folgt aussieht:
{ "compilerOptions": { "target": "ES6", "module": "CommonJS", "moduleResolution": "node", "declaration": true, "outDir": "./dist", "esModuleInterop": true, "strict": true, }
Jetzt können Sie die CLI-Anwendung erstellen. Damit der Code nicht zu unübersichtlich wird, organisieren Sie ihn in mehreren Verzeichnissen mit dem folgenden Layout
dewy_qa/ ├── commands/ │ └── ... ├── utils/ │ └── ... ├── index.ts ├── package.json └── tsconfig.ts
Jeder Befehl wird im commands
implementiert und gemeinsam genutzter Code wird im utils
Verzeichnis abgelegt. Der Einstiegspunkt zur CLI-Anwendung ist die Datei index.ts
.
Beginnen Sie mit einer einfachen „Hallo Welt“-Version von index.ts
– mit dem Ausfüllen beginnen Sie im nächsten Abschnitt
#!/usr/bin/env ts-node-script console.log("hello world");
Um zu überprüfen, ob die Umgebung korrekt eingerichtet ist, versuchen Sie, den folgenden Befehl auszuführen. In der Konsole sollte „Hallo Welt“ angezeigt werden:
npx ts-node index.ts
Anstatt diesen sehr langen Befehl jedes Mal einzugeben, erstellen wir einen Eintrag in package.json
für den Befehl. Dies hilft uns, uns daran zu erinnern, wie die CLI aufgerufen wird, und erleichtert die Installation als Befehl:
{ ... "bin": { "dewy_qa": "./index.ts" } ... }
Jetzt können Sie Ihr Skript mit npm exec dewy_qa
ausführen oder das Paket npm link
und es einfach als dewy_qa
ausführen
Laden Sie Dokumente, indem Sie den Dewy-Client einrichten. Der erste Schritt besteht darin, dem Projekt einige Abhängigkeiten hinzuzufügen. Die erste ist dewy-ts
, die Client-Bibliothek für Dewy. Der zweite ist commander
, der uns beim Erstellen einer CLI-Anwendung mit Argumentanalyse, Unterbefehlen und mehr hilft. Abschließend können chalk
die Aufforderungen bunter gestalten.
npm install dewy-ts commander chalk
Als nächstes implementieren Sie die Logik des Ladebefehls. Sie tun dies in einer separaten Datei mit dem Namen commands/load.ts
. Diese Datei implementiert eine Funktion namens „ load
, die eine URL und einige zusätzliche Optionen erwartet – dies wird in einem späteren Abschnitt mit der CLI verknüpft.
Dewy macht das Laden von Dokumenten ganz einfach – richten Sie einfach den Client ein und rufen Sie addDocument
mit der URL der Datei auf, die Sie laden möchten. Dewy kümmert sich darum, den Inhalt der PDF-Datei zu extrahieren, sie in Stücke aufzuteilen, die genau die richtige Größe für den Versand an ein LLM haben, und sie für die semantische Suche zu indizieren.
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}`)); } }
Möglicherweise ist Ihnen aufgefallen, dass einige Funktionen aus ../utils/colors
importiert wurden. Diese Datei richtet lediglich einige Hilfsprogramme zum Färben der Konsolenausgabe ein. Fügen Sie sie in utils
ein, damit sie an anderer Stelle verwendet werden kann:
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);
Mit der Möglichkeit, Dokumente in Dewy zu laden, ist es an der Zeit, LangChain.js zu integrieren, um LLMs zur Beantwortung von Fragen zu nutzen. In diesem Schritt wird LangChain.js eingerichtet, um die Dewy-Wissensdatenbank abzufragen und die Ergebnisse mithilfe eines LLM zu verarbeiten, um Antworten zu generieren.
Installieren Sie zunächst einige zusätzliche Pakete – langchain
und openai
, um die OpenAI-API als LLM zu verwenden:
npm install dewy-langchain langchain @langchain/openai openai
Dieser Befehl ist ziemlich lang, daher werden wir mehrere Teile davon durchgehen, bevor wir sie am Ende kombinieren
Das erste, was eingerichtet werden muss, ist Dewy (wie zuvor) und ein LLM. Ein Unterschied zu zuvor besteht darin, dass dewy
zum Erstellen eines DewyRetriever
verwendet wird: Dies ist ein spezieller Typ, der von LangChain zum Abrufen von Informationen als Teil einer Kette verwendet wird. Sie werden in nur einer Minute sehen, wie der Retriever verwendet wird.
const model = new ChatOpenAI({ openAIApiKey: options.openai_api_key, }); const dewy = new Dewy({ BASE: options.dewy_endpoint }) const retriever = new DewyRetriever({ dewy, collection });
Dabei handelt es sich um eine String-Vorlage, die dem LLM anweist, wie er sich verhalten soll, mit Platzhaltern für zusätzlichen Kontext, der beim Erstellen der „Kette“ bereitgestellt wird. In diesem Fall wird das LLM angewiesen, die Frage zu beantworten, jedoch nur unter Verwendung der von ihm bereitgestellten Informationen. Dies verringert die Tendenz des Modells, zu „halluzinieren“ oder eine Antwort zu finden, die plausibel, aber falsch ist. Die Werte context
und question
werden im nächsten Schritt bereitgestellt:
const prompt = PromptTemplate.fromTemplate(`Answer the question based only on the following context: {context} Question: {question}`);
LangChain funktioniert durch den Aufbau von Verhaltensketten, die steuern, wie das LLM und andere Datenquellen abgefragt werden. In diesem Beispiel wird LCEL verwendet, das ein flexibleres Programmiererlebnis bietet als einige der ursprünglichen Schnittstellen von LangChain.
Verwenden Sie eine RunnableSequence
, um eine LCEL-Kette zu erstellen. In dieser Kette wird beschrieben, wie die context
und question
generiert werden: Der Kontext wird mithilfe des zuvor erstellten Retrievers generiert, und die Frage wird durch Durchlaufen der Schritteingabe generiert. Die von Dewy abgerufenen Ergebnisse werden als Zeichenfolge formatiert, indem sie an die Funktion formatDocumentsAsString
weitergeleitet werden.
Diese Kette bewirkt Folgendes:
DewyRetriever
ab, ordnet sie dem context
zu und weist den Eingabewert der Kette question
zu.context
und question
. const chain = RunnableSequence.from([ { context: retriever.pipe(formatDocumentsAsString), question: new RunnablePassthrough(), }, prompt, model, new StringOutputParser(), ]);
Nachdem die Kette nun erstellt wurde, führen Sie sie aus und geben Sie die Ergebnisse an die Konsole aus. Wie Sie sehen werden, ist question
ein Eingabeargument, das vom Aufrufer der Funktion bereitgestellt wird.
Wenn Sie die Kette mit chain.streamLog()
ausführen, können Sie jeden Antwortblock sehen, der vom LLM zurückgegeben wird. Die Stream-Handler-Schleife ist irgendwie hässlich, aber sie filtert nur nach geeigneten Stream-Ergebnissen und schreibt sie nach STDOUT
(mit console.log
wären nach jedem Block neue Zeilen eingefügt worden).
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); } } }
Nachdem Sie nun alle Teile gesehen haben, können Sie den query
erstellen. Dies sollte ähnlich wie der load
von zuvor aussehen, mit einigen zusätzlichen Importen.
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}`)); } }
Nachdem Dewy und LangChain.js integriert sind, besteht der nächste Schritt darin, die CLI-Schnittstelle zu erstellen. Verwenden Sie eine Bibliothek wie commander
, um eine benutzerfreundliche Befehlszeilenschnittstelle zu erstellen, die Befehle zum Laden von Dokumenten in Dewy und zum Abfragen der Wissensdatenbank mithilfe von LangChain.js unterstützt.
Schreiben Sie zunächst index.ts
um, um die Unterbefehle load
und query
zu erstellen. Das Argument --collection
bestimmt, in welche Dewy-Sammlung das Dokument geladen werden soll (mit Dewy können Sie Dokumente in verschiedenen Sammlungen organisieren, ähnlich wie Dateiordner). Mit dem Argument --dewy-endpoint
können Sie angeben, wie eine Verbindung zu Dewy hergestellt wird. Standardmäßig wird davon ausgegangen, dass eine Instanz lokal auf Port 8000
ausgeführt wird. Schließlich konfiguriert das Argument --openai_api_key
(das standardmäßig eine Umgebungsvariable ist) die OpenAI-API:
#!/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, alles erledigt – war das nicht einfach? Sie können es ausprobieren, indem Sie den folgenden Befehl ausführen:
dewy_qa load https://arxiv.org/pdf/2009.08553.pdf
Sie sollten so etwas sehen wie
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 }
Das Extrahieren des Inhalts einer großen PDF-Datei kann ein oder zwei Minuten dauern. Daher wird häufig "ingest_state": "pending"
angezeigt, wenn Sie zum ersten Mal ein neues Dokument laden.
Versuchen Sie als Nächstes, einige Fragen zu stellen:
dewy_qa query "tell me about RAG
Sie sollten so etwas sehen wie
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...
Durch Befolgen dieser Anleitung haben Sie gelernt, wie Sie eine CLI erstellen, die Dewy zum Verwalten von Wissen und LangChain.js zum Verarbeiten von Fragen und Generieren von Antworten verwendet. Dieses Tool demonstriert die praktische Anwendung der Kombination einer strukturierten Wissensbasis mit der Analyseleistung von LLMs und ermöglicht es Entwicklern, intelligentere und reaktionsfähigere Anwendungen zu erstellen.