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