paint-brush
Dewy と LangChain.js を使用した質問応答 CLI の作成by@kerinin
486
486

Dewy と LangChain.js を使用した質問応答 CLI の作成

Ryan11m2024/02/17
Read on Terminal Reader

このチュートリアルでは、Dewy と LangChain.js を使用して質問応答 CLI ツールを構築する方法に焦点を当てています。
featured image - Dewy と LangChain.js を使用した質問応答 CLI の作成
Ryan HackerNoon profile picture
0-item
1-item
2-item

このチュートリアルでは、Dewy と LangChain.js を使用して質問応答 CLI ツールを構築する方法に焦点を当てています。 Dewy は、開発者が情報を効率的に整理して取得できるようにするオープンソースのナレッジ ベースです。 LangChain.js は、大規模言語モデル (LLM) のアプリケーションへの統合を簡素化するフレームワークです。 Dewy のナレッジ管理機能と LangChain.js の LLM 統合を組み合わせることで、複雑なクエリに正確で関連性の高い情報を返すツールを作成できます。


このガイドでは、環境のセットアップ、Dewy へのドキュメントの読み込み、LangChain.js を介して LLM を使用して保存されたデータに基づいて質問に答える方法について説明します。高度な質問応答機能を使用してプロジェクトを強化したいと考えているエンジニア向けに設計されています。

Dewy と LangChain.js を使用する理由

Dewy は、開発者が情報を保存、整理、取得する方法を合理化するように設計された OSS ナレッジ ベースです。その柔軟性と使いやすさにより、知識主導型アプリケーションの構築を目指す開発者にとって優れた選択肢となります。


一方、 LangChain.jsは、開発者が LLM をアプリケーションにシームレスに統合できるようにする強力なフレームワークです。 Dewy の構造化ナレッジ管理と LangChain.js の LLM 機能を組み合わせることで、開発者は複雑なクエリを理解して処理し、正確で文脈に応じた回答を提供できる高度な質問応答システムを作成できます。

目標

私たちの目的は、シンプルかつ強力な質問応答 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

各コマンドは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として実行できます。

ステップ 2: ドキュメントのロードを実装する

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

ステップ 3: 質問応答を実装する

Dewy にドキュメントをロードできるようになったので、LangChain.js を統合して、質問に答えるために LLM を利用します。この手順には、Dewy ナレッジ ベースにクエリを実行し、LLM を使用して結果を処理して回答を生成するように LangChain.js を設定することが含まれます。


まず、追加のパッケージ ( langchainopenaiをインストールして、OpenAI API を LLM として使用します。

 npm install dewy-langchain langchain @langchain/openai openai

このコマンドはかなり長いので、最後に結合する前にいくつかの部分を見ていきます。

OpenAI と Dewy のクライアントを作成する

最初にセットアップするのは 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 });

LangChain プロンプトを作成する

これは、「チェーン」の作成時に提供される追加コンテキストのプレースホルダーを使用して、LLM がどのように動作するかを指示する文字列テンプレートです。この場合、LLM は質問に答えるように指示されますが、提供された情報のみを使用します。これにより、モデルが「幻覚」を起こしたり、もっともらしいが間違った答えをでっちあげたりする傾向が軽減されます。 contextquestionの値は、次のステップで提供されます。

 const prompt = PromptTemplate.fromTemplate(`Answer the question based only on the following context: {context} Question: {question}`);

チェーンを構築する

LangChain は、LLM およびその他のデータ ソースへのクエリ方法を制御する動作の「チェーン」を構築することによって機能します。この例では、LangChain のオリジナル インターフェイスの一部よりも柔軟なプログラミング エクスペリエンスを提供するLCELを使用します。


RunnableSequenceを使用して LCEL チェーンを作成します。このチェーンでは、 contextquestion値を生成する方法を説明します。コンテキストは前に作成した取得プログラムを使用して生成され、質問はステップの入力を通過することによって生成されます。 Dewy が取得した結果は、 formatDocumentsAsString関数にパイプすることによって文字列としてフォーマットされます。


このチェーンは次のことを行います。

  1. DewyRetrieverを使用してドキュメントを取得し、それらをcontextに割り当て、チェーンの入力値をquestionに割り当てます。
  2. context変数とquestion変数を使用してプロンプト文字列をフォーマットします。
  3. フォーマットされたプロンプトを LLM に渡して応答を生成します。
  4. LLM の応答を文字列としてフォーマットします。
 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}`)); } }

ステップ 4: CLI の構築

Dewy と LangChain.js が統合されたので、次のステップは CLI インターフェイスを構築することです。コマンダーのようなcommanderを使用して、Dewy にドキュメントをロードし、LangChain.js を使用してナレッジ ベースをクエリするためのコマンドをサポートする、使いやすいコマンド ライン インターフェイスを作成します。


まず、 index.tsを書き換えて、サブコマンドloadqueryを作成します。 --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 の分析能力を組み合わせた実際の応用例を示し、開発者がよりインテリジェントで応答性の高いアプリケーションを構築できるようにします。

さらに詳しい資料とリソース