このチュートリアルでは、Dewy と LangChain.js を使用して質問応答 CLI ツールを構築する方法に焦点を当てています。 Dewy は、開発者が情報を効率的に整理して取得できるようにするオープンソースのナレッジ ベースです。 LangChain.js は、大規模言語モデル (LLM) のアプリケーションへの統合を簡素化するフレームワークです。 Dewy のナレッジ管理機能と LangChain.js の LLM 統合を組み合わせることで、複雑なクエリに正確で関連性の高い情報を返すツールを作成できます。
このガイドでは、環境のセットアップ、Dewy へのドキュメントの読み込み、LangChain.js を介して LLM を使用して保存されたデータに基づいて質問に答える方法について説明します。高度な質問応答機能を使用してプロジェクトを強化したいと考えているエンジニア向けに設計されています。
Dewy は、開発者が情報を保存、整理、取得する方法を合理化するように設計された OSS ナレッジ ベースです。その柔軟性と使いやすさにより、知識主導型アプリケーションの構築を目指す開発者にとって優れた選択肢となります。
一方、 LangChain.jsは、開発者が LLM をアプリケーションにシームレスに統合できるようにする強力なフレームワークです。 Dewy の構造化ナレッジ管理と LangChain.js の LLM 機能を組み合わせることで、開発者は複雑なクエリを理解して処理し、正確で文脈に応じた回答を提供できる高度な質問応答システムを作成できます。
私たちの目的は、シンプルかつ強力な質問応答 CLI スクリプトを構築することです。このスクリプトを使用すると、ユーザーはドキュメントを Dewy ナレッジ ベースにロードし、LangChain.js を通じて LLM を使用して、Dewy に保存されている情報に基づいて質問に答えることができます。このチュートリアルでは、環境のセットアップから CLI スクリプトの実装までのプロセスを説明します。
LangChain を使用して簡単な質問応答アプリケーションを構築する方法と、Dewy を知識のソースとして統合して、アプリケーションが提供された特定のドキュメントに基づいて質問に回答できるようにする方法を学びます。
チュートリアルに入る前に、次の前提条件が満たされていることを確認してください。
この例の最終コードは、先に進みたい場合はDewy リポジトリで入手できます。
まず、TypeScript CLI プロジェクト用のディレクトリを作成し、そのディレクトリに移動します。
mkdir dewy_qa cd dewy_qa
ディレクトリを設定したら、TypeScript をインストールしてプロジェクトを初期化できます。
npm init -y npm i typescript --save-dev npx tsc --init
環境によっては、TypeScript 構成にいくつかの変更を加える必要がある場合があります。 tsconfig.json
が次のようになっていることを確認してください。
{ "compilerOptions": { "target": "ES6", "module": "CommonJS", "moduleResolution": "node", "declaration": true, "outDir": "./dist", "esModuleInterop": true, "strict": true, }
これで、CLI アプリケーションを作成する準備が整いました。コードが乱雑になりすぎないように、次のレイアウトでコードを複数のディレクトリに整理します。
dewy_qa/ ├── commands/ │ └── ... ├── utils/ │ └── ... ├── index.ts ├── package.json └── tsconfig.ts
各コマンドはcommands
ディレクトリに実装され、共有コードはutils
ディレクトリに配置されます。 CLI アプリケーションへのエントリポイントは、ファイルindex.ts
です。
シンプルな「hello world」バージョンのindex.ts
から始めます。次のセクションで入力を開始します。
#!/usr/bin/env ts-node-script console.log("hello world");
環境が正しく設定されていることを確認するには、次のコマンドを実行してみてください。コンソールに「hello world」が表示されるはずです。
npx ts-node index.ts
この非常に長いコマンドを毎回入力するのではなく、 package.json
にコマンド用のエントリを作成しましょう。これは、CLI の呼び出し方法を覚えておくのに役立ち、コマンドとしてインストールするのが簡単になります。
{ ... "bin": { "dewy_qa": "./index.ts" } ... }
これで、 npm exec dewy_qa
使用してスクリプトを実行するか、パッケージをnpm link
て単にdewy_qa
として実行できます。
Dewy クライアントをセットアップしてドキュメントをロードします。最初のステップは、プロジェクトに依存関係を追加することです。 1 つ目はdewy-ts
、Dewy のクライアント ライブラリです。 2 つ目はcommander
で、引数の解析やサブコマンドなどを備えた CLI アプリケーションを構築するのに役立ちます。最後に、 chalk
プロンプトをよりカラフルにします。
npm install dewy-ts commander chalk
次に、load コマンドのロジックを実装します。これは、 commands/load.ts
という名前の別のファイルで行います。このファイルは、 URL といくつかの追加オプションを必要とする、 load
という名前の関数を実装します。これは、後のセクションで CLI と関連付けられます。
Dewy を使用すると、ドキュメントの読み込みが非常に簡単になります。クライアントをセットアップし、読み込みたいファイルの URL を指定してaddDocument
を呼び出すだけです。 Dewy は、PDF のコンテンツを抽出し、LLM に送信するのにちょうどよいサイズのチャンクに分割し、セマンティック検索のためにインデックスを作成します。
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}`)); } }
いくつかの関数が../utils/colors
からインポートされたことに気づいたかもしれません。このファイルは、コンソール出力に色を付けるためのいくつかのヘルパーを設定するだけです。他の場所でも使用できるように、 utils
に配置します。
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);
Dewy にドキュメントをロードできるようになったので、LangChain.js を統合して、質問に答えるために LLM を利用します。この手順には、Dewy ナレッジ ベースにクエリを実行し、LLM を使用して結果を処理して回答を生成するように LangChain.js を設定することが含まれます。
まず、追加のパッケージ ( langchain
とopenai
をインストールして、OpenAI API を LLM として使用します。
npm install dewy-langchain langchain @langchain/openai openai
このコマンドはかなり長いので、最後に結合する前にいくつかの部分を見ていきます。
最初にセットアップするのは Dewy (以前と同様) と LLM です。以前との違いの 1 つは、 dewy
を使用してDewyRetriever
を構築することです。これは、チェーンの一部として情報を取得するために LangChain によって使用される特別なタイプです。レトリーバーがどのように使用されるかは、ほんの 1 分でわかります。
const model = new ChatOpenAI({ openAIApiKey: options.openai_api_key, }); const dewy = new Dewy({ BASE: options.dewy_endpoint }) const retriever = new DewyRetriever({ dewy, collection });
これは、「チェーン」の作成時に提供される追加コンテキストのプレースホルダーを使用して、LLM がどのように動作するかを指示する文字列テンプレートです。この場合、LLM は質問に答えるように指示されますが、提供された情報のみを使用します。これにより、モデルが「幻覚」を起こしたり、もっともらしいが間違った答えをでっちあげたりする傾向が軽減されます。 context
とquestion
の値は、次のステップで提供されます。
const prompt = PromptTemplate.fromTemplate(`Answer the question based only on the following context: {context} Question: {question}`);
LangChain は、LLM およびその他のデータ ソースへのクエリ方法を制御する動作の「チェーン」を構築することによって機能します。この例では、LangChain のオリジナル インターフェイスの一部よりも柔軟なプログラミング エクスペリエンスを提供するLCELを使用します。
RunnableSequence
を使用して LCEL チェーンを作成します。このチェーンでは、 context
とquestion
値を生成する方法を説明します。コンテキストは前に作成した取得プログラムを使用して生成され、質問はステップの入力を通過することによって生成されます。 Dewy が取得した結果は、 formatDocumentsAsString
関数にパイプすることによって文字列としてフォーマットされます。
このチェーンは次のことを行います。
DewyRetriever
を使用してドキュメントを取得し、それらをcontext
に割り当て、チェーンの入力値をquestion
に割り当てます。context
変数とquestion
変数を使用してプロンプト文字列をフォーマットします。 const chain = RunnableSequence.from([ { context: retriever.pipe(formatDocumentsAsString), question: new RunnablePassthrough(), }, prompt, model, new StringOutputParser(), ]);
チェーンが構築されたので、それを実行し、結果をコンソールに出力します。ご覧のとおり、 question
は関数の呼び出し元によって提供される入力引数です。
chain.streamLog()
を使用してチェーンを実行すると、LLM から返された各応答チャンクを確認できます。ストリーム ハンドラー ループはある意味醜いですが、適切なストリーム結果をフィルター処理してSTDOUT
に書き込むだけです ( console.log
を使用すると、各チャンクの後に改行が追加されます)。
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); } } }
すべての部分を確認したので、 query
コマンドを作成する準備が整いました。これは、インポートがいくつか追加されているものの、前のload
コマンドと似ているはずです。
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}`)); } }
Dewy と LangChain.js が統合されたので、次のステップは CLI インターフェイスを構築することです。コマンダーのようなcommander
を使用して、Dewy にドキュメントをロードし、LangChain.js を使用してナレッジ ベースをクエリするためのコマンドをサポートする、使いやすいコマンド ライン インターフェイスを作成します。
まず、 index.ts
を書き換えて、サブコマンドload
とquery
を作成します。 --collection
引数は、ドキュメントをどの Dewy コレクションにロードするかを決定します (Dewy を使用すると、ファイル フォルダーと同様に、ドキュメントをさまざまなコレクションに整理できます)。 --dewy-endpoint
引数を使用すると、Dewy への接続方法を指定できます。デフォルトでは、ポート8000
でローカルに実行されているインスタンスが想定されます。最後に、 --openai_api_key
引数 (デフォルトは環境変数) で 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、すべて完了しました - それは簡単ではありませんでしたか?次のコマンドを実行して試すことができます。
dewy_qa load https://arxiv.org/pdf/2009.08553.pdf
次のようなものが表示されるはずです
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 }
大きな PDF のコンテンツの抽出には 1 ~ 2 分かかる場合があるため、新しいドキュメントを最初に読み込むときに"ingest_state": "pending"
が表示されることがよくあります。
次に、いくつか質問してみてください。
dewy_qa query "tell me about RAG
次のようなものが表示されるはずです
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...
このガイドに従うことで、Dewy を使用してナレッジを管理し、LangChain.js を使用して質問を処理し、回答を生成する CLI を作成する方法を学習しました。このツールは、構造化されたナレッジ ベースと LLM の分析能力を組み合わせた実際の応用例を示し、開発者がよりインテリジェントで応答性の高いアプリケーションを構築できるようにします。