【Node.js】ChatGPTに自分のデータに対して質問させる

前回の記事ではPythonで同じことを行いました。今回はNode.jsを使ってChatGPTに自分で準備したテキストファイルを読ませてそれに対して質問させるアプリのデモを作成します。

目的

ユーザーがChatGPTに質問をするとOpenAIが持っているデータベースを参照して回答してくれます。しかしこのデータは2021年までしか保存されていないため、それ以降のデータは質問しても回答してくれません。しかし自分で準備しておいたデータ(大量のファイルも可能)を読ませておくことで自分の好きな著者と会話をしたり、最新のデータから必要なデータを検索させるシステムを作成することが可能になります。

プログラムの完成イメージ

このように通常ChatGPTが知らないデータでも事前に教えておくことでチャット形式でデータを参照することができるようになります。

使用するテクノロジー

  • Node.js
  • Lang Chain
  • OpenAI API

今回はOpenAIのAPIキー(有料、カード登録が必要)が必要になります。先に下記の記事を読んで各フレームワークの理解とキーの取得をしておきましょう。

リポジトリのクローン

ではこちらのGitHubのリポからコードをクローンしましょう。

git clone https://github.com/DanNakatoshi/Node-ChatGPT-LangChain.git
cd Node-ChatGPT-LangChain
npm i


ファイルの実行は下記のコマンドでできます。(APIキーを登録しないとエラーになります。)

node index.js

下準備

テキストファイルの準備

では、ChatGPTに読み込ませたいファイルを準備しましょう。ファイル名はconst txtFilenameに格納しておきます。

APIキーを環境変数に登録

OpenAIのAPIキーを.envファイルに保存します。

コードの説明

ではindex.jsを見てください。

// 1. 必要なモジュールとライブラリをインポートする
import { OpenAI } from 'langchain/llms';
import { RetrievalQAChain } from 'langchain/chains';
import { HNSWLib } from 'langchain/vectorstores';
import { OpenAIEmbeddings } from 'langchain/embeddings';
import { RecursiveCharacterTextSplitter } from 'langchain/text_splitter';
import * as fs from 'fs';
import * as dotenv from 'dotenv';
import readline from 'readline';

// 2. 環境変数を読み込む
dotenv.config();

// 3. 入力データとパスを設定する
const txtFilename = "LangChain";
const txtPath = `./${txtFilename}.txt`;
const VECTOR_STORE_PATH = `${txtFilename}.index`;

// 4. メインの関数 runWithEmbeddings を定義する
export const runWithEmbeddings = async () => {
  // 5. 空の設定オブジェクトで OpenAI モデルを初期化する
  const model = new OpenAI({});

  // 6. ベクトルストアファイルが存在するかどうかをチェックする
  let vectorStore;
  if (fs.existsSync(VECTOR_STORE_PATH)) {
    // 6.1. ベクトルストアファイルが存在する場合は、メモリに読み込む
    console.log('ベクトルストアが存在します..');
    vectorStore = await HNSWLib.load(VECTOR_STORE_PATH, new OpenAIEmbeddings());
  } else {
    // 6.2. ベクトルストアファイルが存在しない場合は、作成する
    // 6.2.1. 入力テキストファイルを読み込む
    const text = fs.readFileSync(txtPath, 'utf8');
    // 6.2.2. 指定されたチャンクサイズで RecursiveCharacterTextSplitter を作成する
    const textSplitter = new RecursiveCharacterTextSplitter({ chunkSize: 1000 });
    // 6.2.3. 入力テキストをドキュメントに分割する
    const docs = await textSplitter.createDocuments([text]);
    // 6.2.4. OpenAIEmbeddings を使用してドキュメントから新しいベクトルストアを作成する
    vectorStore = await HNSWLib.fromDocuments(docs, new OpenAIEmbeddings());
    // 6.2.5. ベクトルストアをファイルに保存する
    await vectorStore.save(VECTOR_STORE_PATH);
  }

  // 7. 初期化された OpenAI モデルとベクトルストアリトリーバーを渡して RetrievalQAChain を作成する
  const chain = RetrievalQAChain.fromLLM(model, vectorStore.asRetriever());

  // 8. ユーザーに質問を入力してもらう
  const rl = readline.createInterface({
    input: process.stdin,
    output: process.stdout,
  });

  rl.question('質問を入力してください:', async (userQuestion) => {
    // 9. 入力の質問で RetrievalQAChain を呼び出し、結果を 'res' 変数に格納する
    const res = await chain.call({
      query: userQuestion,
    });

    // 10. 結果をコンソールに出力する
    console.log('結果:', res);

    // 11. readline インターフェースを閉じる
    rl.close();
  });
};

// 12. メインの関数 runWithEmbeddings を実行する
runWithEmbeddings();

このコードは、Node.jsを使用してOpenAIのRetrieval-based Question Answering(質問応答)モデルを実装するものです。以下に、コードの主な機能を説明します。

  1. 必要なモジュールとライブラリをインポートします。
    • OpenAI:OpenAIのモデルを使用するためのモジュール。
    • RetrievalQAChain:質問応答のためのチェーンを作成するためのモジュール。
    • HNSWLib:ベクトルストアの読み込みと作成を行うためのモジュール。
    • OpenAIEmbeddings:OpenAIの埋め込みを使用するためのモジュール。
    • RecursiveCharacterTextSplitter:テキストを指定されたチャンクサイズで分割するためのモジュール。
    • fs:ファイルシステムへのアクセスを提供するモジュール。
    • dotenv.envファイルから環境変数を読み込むためのモジュール。
    • readline:ユーザーからの入力を受け取るためのモジュール。
  2. 環境変数を読み込みます。
  3. 入力データとパスを設定します。txtFilenameはテキストファイルの名前を、txtPathはテキストファイルのパスを表します。VECTOR_STORE_PATHはベクトルストアのパスを表します。
  4. メインの関数 runWithEmbeddings を定義します。
  5. 空の設定オブジェクトで OpenAI モデルを初期化します。
  6. ベクトルストアファイルが存在するかどうかをチェックします。ファイルが存在する場合は、ベクトルストアをメモリに読み込みます。存在しない場合は、テキストファイルを読み込み、指定されたチャンクサイズでテキストを分割し、新しいベクトルストアを作成します。作成されたベクトルストアはファイルに保存されます。
  7. 初期化された OpenAI モデルとベクトルストアリトリーバーを使用して RetrievalQAChain を作成します。
  8. readlineを使用して、ユーザーに質問を入力してもらいます。
  9. 入力の質問で RetrievalQAChain を呼び出し、結果を res 変数に格納します。
  10. 結果をコンソールに出力します。
  11. readline インターフェースを閉じます。
  12. メインの関数 runWithEmbeddings を実行します。

このコードは、OpenAIのモデルとベクトルストアを使用してユーザーの質問に応答するシンプルな質問応答システムを作成しています。ユーザーが質問を入力すると、モデルが応答を生成し、それがコンソールに表示されます。

OpenAIモジュール

OpenAIモジュールは、OpenAIの自然言語処理モデルを利用するためのモジュールです。このモジュールは、OpenAIのAPIとの通信やテキスト生成タスクの実行を簡素化するための機能を提供します。

OpenAIモジュールを使用すると、以下のようなことが可能です:

  1. 初期化(Initialization): OpenAIモデルを使用するために、APIキーの設定やモデルの選択などの初期化を行います。これにより、OpenAIのモデルを利用できる状態になります。
  2. テキスト生成(Text Generation): OpenAIのモデルを使用して、テキスト生成タスクを実行します。例えば、与えられたテキストの続きを生成したり、文章を要約したり、質問に回答したりすることができます。
  3. APIとの通信(API Communication): OpenAIモジュールは、OpenAIのAPIとの通信を簡単に行うためのメソッドや関数を提供します。APIに対してリクエストを送信し、応答を受け取ることができます。

OpenAIモジュールは、様々な自然言語処理タスクに対して利用されます。具体的には、文章生成、文章の意味理解、文章の分類、質問応答などのタスクに使用されます。

コードの例では、OpenAIモジュールが使用されています。OpenAIクラスをインスタンス化し、そのインスタンスを利用してOpenAIのモデルを初期化しています。このモジュールを使用することで、OpenAIのモデルを簡単に利用できます。

RetrievalQAChainモジュールとは

RetrievalQAChainは、質問応答(Question Answering)タスクを処理するためのモジュールです。このモジュールは、与えられた質問に対して適切な回答を見つけるために使用されます。

具体的には、RetrievalQAChainは以下のような機能を提供します:

  1. 初期化(Initialization): RetrievalQAChainを使用する前に、OpenAIモデルとベクトルストアリトリーバーを指定して初期化します。これにより、質問と回答の検索が可能になります。
  2. 質問の呼び出し(Calling the Question): 質問を受け取り、RetrievalQAChainを呼び出して回答を取得します。質問はテキスト形式で提供されます。
  3. 回答の取得(Getting the Answer): RetrievalQAChainは、与えられた質問に対して適切な回答を検索します。回答はテキスト形式で返されます。

RetrievalQAChainは、ベクトルストアを使用して質問と回答の類似性を計算し、最も適切な回答を見つけるための機能を提供します。このモジュールは、大規模なテキストデータセットから効率的に質問に対する回答を見つけることができます。

コードの例では、RetrievalQAChainモジュールがOpenAIのモデルとベクトルストアリトリーバーを使用して初期化され、質問が与えられると回答が取得されます。質問と回答の検索は、ベクトルストアの中から最も類似した文書を見つけることによって行われます。

HNSWLibモジュール

HNSWLibモジュールは、高次元のベクトルデータの効率的なインデックス構築と検索を可能にするためのモジュールです。HNSWは、ハイブリッドネットワークスワップ(Hierarchical Navigable Small World)と呼ばれるアルゴリズムを使用して、高次元ベクトルの類似性検索を高速に実行します。

HNSWLibモジュールは、LangChainという自然言語処理ライブラリの一部として提供されています。このモジュールは、ベクトルストアの作成と操作を行うためのインターフェースを提供します。

具体的には、HNSWLibモジュールを使用すると、以下のようなことが可能です:

  1. ベクトルストアの作成(Vector Store Creation): HNSWLibを使用して、与えられたドキュメント集合からベクトルストアを作成することができます。ベクトルストアは、高次元のベクトルを効率的に格納し、検索を高速化するためのデータ構造です。
  2. ベクトルストアへのベクトルの追加(Adding Vectors to Vector Store): HNSWLibを使用して、ベクトルストアに新しいベクトルを追加することができます。追加されたベクトルは、類似性検索の対象となります。
  3. ベクトルの類似性検索(Vector Similarity Search): HNSWLibを使用して、指定されたクエリベクトルに対してベクトルストア内の類似なベクトルを検索することができます。類似性検索は、与えられたベクトルに最も近いベクトルを見つけるために使用されます。

HNSWLibは、高次元ベクトルの類似性検索において効率的でスケーラブルな解決策を提供するため、情報検索、機械学習、自然言語処理などの分野で広く利用されています。上記のコード例では、HNSWLibモジュールが使用されており、ベクトルストアのロードや作成に利用されています。

OpenAIEmbeddingsモジュールとは

OpenAIEmbeddingsモジュールは、LangChainという自然言語処理ライブラリの一部として提供されているモジュールです。このモジュールは、テキストデータや文章をベクトル表現に変換するための機能を提供します。

具体的には、OpenAIEmbeddingsモジュールを使用すると、以下のようなことが可能です:

  1. テキストの埋め込み(Text Embedding): OpenAIEmbeddingsを使用して、与えられたテキストデータや文章をベクトル表現に変換することができます。埋め込みは、テキストの意味や特徴を捉えたベクトル表現として表現されます。
  2. ベクトル間の類似度計算(Vector Similarity Calculation): OpenAIEmbeddingsを使用して、2つのベクトル間の類似度を計算することができます。類似度計算は、2つのテキストや文章の意味的な類似度を測るために使用されます。
  3. ベクトルの操作(Vector Manipulation): OpenAIEmbeddingsモジュールには、ベクトルの操作(加算、減算、正規化など)を行うための機能も提供されています。これにより、ベクトルの意味的な変換や操作を行うことができます。

OpenAIEmbeddingsモジュールは、自然言語処理タスクにおいてテキストデータの表現や解析に広く活用されます。テキストデータをベクトルに変換することで、機械学習モデルの入力や比較、検索などのさまざまなタスクを実行することができます。上記のコード例では、OpenAIEmbeddingsモジュールが使用されており、ドキュメントからベクトルストアを作成する際に利用されています。

ベクトルストアファイル

ベクトルストアファイル(Vector Store file)は、テキストや文書の埋め込み(ベクトル化)を保存して管理するためのファイルです。ベクトルストアは、テキストデータを高次元の数値ベクトル空間にマッピングし、各文書をベクトルとして表現します。これにより、類似性の計算や検索などの情報検索タスクを効率的に実行できます。

ベクトルストアファイルは、一般的にデータベースや検索エンジンのバックエンドとして使用されます。ファイルには、テキストデータの埋め込みベクトルや関連するメタデータが格納されます。これにより、大規模な文書コレクションを効率的に管理し、特定の質問や検索クエリに対して迅速な検索結果を提供することができます。

ベクトルストアファイルは、一度作成されると、後続の検索や質問に対して再利用できます。これにより、テキストデータの埋め込み処理を繰り返し行う必要がなくなり、処理時間やリソースの節約につながります。

コードの例では、ベクトルストアファイルを使用してテキストデータを保存し、ユーザーの質問に対して迅速に応答するために利用しています。ファイルが存在しない場合は、テキストデータを埋め込み処理してベクトルストアファイルを作成し、次回以降の検索に使用します。ファイルが存在する場合は、ベクトルストアファイルをメモリに読み込んで再利用します。

OpenAIEmbeddingsモジュールとは

OpenAIEmbeddingsは、テキストデータの埋め込み(ベクトル化)を行うためのクラスです。このクラスのメソッドは、与えられたテキストデータをOpenAIの埋め込みモデルを使用してベクトルに変換します。

具体的には、OpenAIEmbeddingsクラスの主要なメソッドは次のとおりです:

  • embed(text: string): Promise<number[]>:与えられたテキストデータを埋め込みベクトルに変換します。このメソッドは非同期であり、Promiseを返します。埋め込みベクトルは数値の配列として返されます。

このメソッドを使用することで、テキストデータを高次元の数値ベクトルに変換することができます。埋め込みベクトルは、テキストの意味や表現を捉えた特徴ベクトルとなります。これにより、文書の類似性の計算や情報検索などのタスクを実行する際に使用することができます。

コードの例では、OpenAIEmbeddingsクラスがOpenAIの埋め込みモデルを使用してテキストデータをベクトル化しています。具体的には、テキストデータをドキュメントに分割した後、OpenAIEmbeddingsを使用して各ドキュメントのベクトルを作成し、それらをベクトルストアに保存しています。

RecursiveCharacterTextSplitterとは

OpenAIEmbeddingsモジュールは、LangChainという自然言語処理ライブラリの一部として提供されているモジュールです。このモジュールは、テキストデータや文章をベクトル表現に変換するための機能を提供します。

具体的には、OpenAIEmbeddingsモジュールを使用すると、以下のようなことが可能です:

  1. テキストの埋め込み(Text Embedding): OpenAIEmbeddingsを使用して、与えられたテキストデータや文章をベクトル表現に変換することができます。埋め込みは、テキストの意味や特徴を捉えたベクトル表現として表現されます。
  2. ベクトル間の類似度計算(Vector Similarity Calculation): OpenAIEmbeddingsを使用して、2つのベクトル間の類似度を計算することができます。類似度計算は、2つのテキストや文章の意味的な類似度を測るために使用されます。
  3. ベクトルの操作(Vector Manipulation): OpenAIEmbeddingsモジュールには、ベクトルの操作(加算、減算、正規化など)を行うための機能も提供されています。これにより、ベクトルの意味的な変換や操作を行うことができます。

OpenAIEmbeddingsモジュールは、自然言語処理タスクにおいてテキストデータの表現や解析に広く活用されます。テキストデータをベクトルに変換することで、機械学習モデルの入力や比較、検索などのさまざまなタスクを実行することができます。上記のコード例では、OpenAIEmbeddingsモジュールが使用されており、ドキュメントからベクトルストアを作成する際に利用されています。

Pythonで構築する方法と比較

前回の記事では同じことをPythonで行いました。

Node.jsを使用してLangChainとOpenAIを構築する際の強みは次のようになります。

  1. ノンブロッキングIOモデル:Node.jsは非同期イベント駆動のノンブロッキングIOモデルを持っています。これにより、リクエストの処理中に他のリクエストをブロックせず、高い並行性とスケーラビリティを実現できます。LangChainやOpenAIといった処理が重いタスクを非同期に処理することで、応答性とパフォーマンスが向上します。
  2. JavaScriptのエコシステム:Node.jsはJavaScriptを使用するため、JavaScriptの広範なエコシステムや豊富なライブラリにアクセスできます。これにより、LangChainやOpenAIの統合に必要な機能やツールを容易に見つけることができます。また、JavaScriptの熟練度が高い開発者は、Node.jsを使用してLangChainとOpenAIを構築することで、シームレスな開発フローを実現できます。
  3. フルスタック開発のサポート:Node.jsはフルスタック開発に適しており、バックエンドとフロントエンドの両方をカバーすることができます。これにより、LangChainやOpenAIと組み合わせて、エンドツーエンドのアプリケーションを効率的に構築することができます。一つの言語でサーバーサイドとクライアントサイドの開発を行うことで、開発者の生産性を向上させることができます。
  4. デプロイメントの容易さ:Node.jsは軽量であり、導入が容易です。また、Node.jsは多くのクラウドプラットフォームやホスティングサービスでサポートされており、デプロイメントが簡単です。LangChainやOpenAIをNode.jsベースの環境にデプロイすることで、スムーズな展開とスケーラビリティの確保が可能です。

以上のような要素から、Node.jsを使用してLangChainとOpenAIを構築する際には、高いパフォーマンス、エコシステムの利用、フルスタック開発のサポート、容易なデプロイメントなどの強みがあります。開発者はNode.jsの利点を活かし、効率的かつ柔軟な方法でLangChainとOpenAIを統合することができます。

今回のコードはこのリポジトリを参考にしました。