LangChainJS、Milvus、StrapiでRAGを構築する

AIアプリケーションを構築する際、ユーザーからの問い合わせに対して正確かつ適切な回答を提供することは非常に重要です。自然な会話は可能だが、従来のチャットボットやAIモデルは、特定の最新情報にアクセスできないことが多い。それらの回答は、古い、または専門的なニーズには一般的すぎるかもしれない学習データから得られます。
Retrieval Augmented Generation(RAG)は、AIモデルの生成能力と、Milvusのようなベクトルデータベースによるカスタム知識ベースを組み合わせることで、この制限に対処する。事前に訓練された知識だけに頼るのではなく、RAGは応答を生成する前に、関連する情報を見つけるために積極的にコンテンツを検索します。このアプローチにより、回答は正確かつ文脈に適したものとなります。
RAGとベクトル検索を理解する
RAGは、検索メカニズムを生成プロセスに統合することで、一般的なAIの応答と専門的な知識との間のギャップを埋めます。事前に訓練された知識のみに依存する従来の言語モデルとは異なり、RAGはまずカスタム知識ベースから関連情報を検索し、AIの応答を補足します。
例えば、AIアシスタントが特定のポリシーや手順について質問された場合、記憶だけに頼るのではなく、回答を提供する前に関連文書を検索する。この検索プロセスは、埋め込みモデルとベクトルデータベースによって支えられている。
では、標準的なRAGの仕組みを見てみよう。
1.テキスト埋め込み:テキストは、埋め込みモデルによって、その意味的な意味を捉える数値表現、つまりベクトルに変換される。例えば、How do I return an item?とWhat's the refund process?というフレーズは異なる単語を使っているかもしれないが、似たような意味を共有しており、ベクトルの類似性に反映されている。
2.ベクトル検索**:ユーザーが質問をすると、そのクエリもベクトルに変換される。このベクトルは、Milvusのようなベクトルデータベースに格納されている他のベクトルと比較され、最も意味的に類似したエントリを見つける。
3.回答生成:検索された情報は、ユーザーのクエリとともに大規模言語モデルに渡される。このモデルは、検索されたコンテンツに基づいた回答を生成し、回答が適切かつ正確であることを保証する。
この検索と生成の組み合わせにより、AIの回答は文脈に適したものとなり、知識ベースから最も関連性の高いデータに基づいたものとなる。
以下のセクションでは、Strapiで管理された知識ベースを使ってMilvusに関する質問に答えることができるRAGを搭載したFAQシステムを作成します。このシステムは、ベクトル検索のためのMilvus、ワークフロー調整のためのLangChain.js、正確で文脈に沿った回答を提供するためのOpenAIの言語モデルを統合します。コンテンツは、オープンソースのNode.jsヘッドレスCMSであるStrapiで整理され、リアルタイムでクエリされ、カスタムデータでAIの回答の根拠となります。
ざっとご覧になりたい方は、フルコードとStrapiバックエンドをどうぞ。
開発環境のセットアップ
コードを書く前に、上で説明した3つの主要コンポーネントをセットアップする必要があります:ベクター保存用のMilvus、コンテンツ管理用のStrapi、そしてエンベッディングとレスポンスを生成するためのOpenAI APIキーです。それぞれを設定しましょう。
Milvusのインストール
まずはベクターデータベースであるMilvusのセットアップから。設定済みのDocker Composeファイルを手動でダウンロード](https://github.com/milvus-io/milvus/releases/download/v2.0.2/milvus-standalone-docker-compose.yml)するか、wgetを使用します。ターミナルで
wget https://github.com/milvus-io/milvus/releases/download/v2.0.2/milvus-standalone-docker-compose.yml -O docker-compose.yml
設定ファイルを配置したら、Dockerを使ってMilvusを起動する:
sudo docker-compose up -d
V1 ではなく Docker Compose V2 を使用している場合は、docker-compose ではなく docker compose を使用してください。docker compose version`を実行することでバージョンを確認できる。
ストラピのインストール
ナレッジ・ベース・コンテンツを管理するためにStrapiをセットアップしましょう。以下のコマンドを使用して、新しいStrapiプロジェクトを作成します:
npx create-strapi-app@latest my-project
プロンプトが表示されたら、データベースとしてSQLiteを選択してください。インストール後、Strapiがブラウザで開き、管理者アカウントを作成します。
Strapi管理パネルで、MILVUS-KNOWLEDGEBASEという新しいコレクション・タイプを作成します。Textタイプを使用するTitleフィールドと、Rich textタイプを使用するContentフィールドです。
図-ストラピ・コレクションにフィールドを追加する](https://assets.zilliz.com/Figure_Adding_fields_to_a_Strapi_collection_fa2b6f3b2b.png)
図:ストラピ・コレクションにフィールドを追加する_。
この構造は、ナレッジ・ベースのエントリを効果的に整理するのに役立ちます。
コレクションがセットアップされたら、Content Managerにアクセスしてサンプルコンテンツを追加してください。
図- Strapiコレクションにデータを入力したところ](https://assets.zilliz.com/Figure_Strapi_collection_populated_with_data_542ff07cf7.png)
図:ストラピ・コレクションにデータが入力された_。
エントリー作成後、必ずパブリッシュしてください。パブリッシュされていないエントリーは、我々のアプリケーションからアクセスできません。
エントリをパブリッシュした後、パーミッションを設定することによって、コンテンツをRAGシステムで利用できるようにする必要があります。Settings、次にRolesに移動し、Publicロールを選択します。MILVUS-KNOWLEDGEBASEコレクションを見つけ、findおよびfindOneパーミッションを有効にします。
OpenAI API キーの取得
最後に必要なのは、エンベッディングとレスポンスを生成するためのOpenAI APIキーです。OpenAIプラットフォーム](https://platform.openai.com)にアクセスし、サインアップするかログインします。APIキーのセクションに移動し、新しいシークレットキーを作成します。このキーは後で必要になりますし、一度しか見ることができないので、コピーして安全な場所に保存してください。
Reactアプリケーションの作成
コアサービスの準備ができたので、RAGシステムをホストするReactアプリケーションをセットアップしよう。create-react-app`ツールを使って新しいReactプロジェクトを作成し、必要な依存関係をインストールする。
まず、新しいReactアプリケーションを作成します:
npx create-react-app rag-app
cd rag-app
このコマンドは、必要なビルドツールと設定をすべて備えた完全なReactプロジェクトをセットアップする。 次に、RAGシステムに必要なパッケージをインストールします:
npm install @langchain/community @langchain/openai @zilliz/milvus2-sdk-node axios cors express langchain react-markdown
各パッケージが我々のアプリケーションで何をするかは以下の通りだ:
langchain/community`:様々なツールやサービスとの統合へのアクセスを提供します。Milvusベクターストアとの統合に使います。
langchain/openai`:OpenAIのモデルとのインタラクションを扱う:OpenAIのモデルとのインタラクションを処理します。GPT-3.5を使ってエンベッディングを作成し、レスポンスを生成します。
zilliz/milvus2-sdk-node`:公式のNode.js用Milvus SDKです。ベクトル操作のためにMilvusインスタンスと直接通信することができます。
axios`:HTTPクライアント。Strapi APIからコンテンツを取得し、フロントエンドとバックエンド間でリクエストを行うために使用します。
cors`:クロスオリジンリソース共有を可能にするExpress用ミドルウェア。これにより、フロントエンドはバックエンドサーバーと安全に通信することができます。
express: Node.js 用の Web フレームワーク:Node.js 用の Web フレームワーク。フロントエンド、Milvus、OpenAIの間で連携するバックエンドAPIの構築に使用します。langchain`:コアとなるLangChainライブラリで、RAGワークフローのオーケストレーションを支援し、コンテンツの取得からレスポンスの生成までのフローを管理する。
react-markdown`:ReactコンポーネントでMarkdownコンテンツをレンダリングする。AIアシスタントからのフォーマットされたレスポンスを表示するために使用する。
バックエンドサーバーの構築
開発環境のセットアップが完了したので、RAGシステムを動かすサーバーの構築を始めよう。Strapiからコンテンツを取得し、テキストをvector embeddingsに変換し、Milvusに保存し、RAGワークフローを調整してレスポンスを生成する。
プロジェクトルートに server.mjs という名前のファイルと .env という名前のファイルを作成する。.env`ファイルに、OpenAIのキーを以下のように貼り付けます:
OPENAI_API_KEY= your-api-key
これが機密情報を保存するためのファイルです。それではサーバーファイルを開いて、コーディングを始めましょう。
1.サーバーと環境のセットアップ
システムが安全に動作するように、必要なライブラリをインポートし、環境変数を設定することから始める。
express" から express をインポートする;
import axios from "axios";
dotenv" から dotenv をインポートする;
import cors from "cors";
import { ChatOpenAI } from "@langchain/openai";
import { createStuffDocumentsChain } from "langchain/chains/combine_documents";
import { ChatPromptTemplate } from "@langchain/core/prompts";
import { RecursiveCharacterTextSplitter } from "langchain/text_splitter";
import { OpenAIEmbeddings } from "@langchain/openai";
import { Milvus } from "@langchain/community/vectorstores/milvus";
import { Document } from "langchain/document";
import { HumanMessage, AIMessage } from "@langchain/core/messages";
dotenv.config();
環境にインストールしたライブラリをコードにインポートすることで、コード内でライブラリの関数やメソッドを呼び出して使用できるようになります。dotenv.config()`関数はAPIキーをロードし、コード内で直接公開することなく、アプリケーション全体でアクセスできるようにする。
2.アプリケーションの設定
次に、コンフィギュレーションオブジェクトを使ってアプリケーションの設定を一元化します。このステップはすべての主要なパラメーターを1つの場所に保持し、必要に応じて管理し調整することを容易にします。
const CONFIG = {
PORT: parseInt(process.env.PORT || "30080", 10)、
STRAPI: {
URL:"http://localhost:1337/api/milvus-knowledgebases"、
TIMEOUT: 5000
},
MILVUS:{
URL:「localhost:19530"、
COLLECTION: "rag_collection"、
PRIMARY_FIELD: "pk"、
VECTOR_FIELD: "vector"、
TEXT_FIELD: "text"、
text_max_length: 4096、
search_params: {
nprobe:16,
オフセット:0
},
バッチサイズ: 100
},
CHUNKING: {
SIZE: 2000、
オーバーラップ:200
},
TOP_K: 3
};
この設定オブジェクトは、アプリケーションの重要なパラメータを定義する。PORT設定はサーバがリッスンするポートを決定します。STRAPI セクションには、コンテンツ管理システムの URL と、データを取得する際の長い待ち時間を避けるためのタイムアウト値を指定します。
MILVUSセクションはベクトルデータベースを設定し、コレクション名、テキストの長さの制限、検索パラメータなどの詳細を指定します。 nprobe は Milvus が検査するクラスタの数を調整することで、ベクトル検索の精度を制御する。最後に CHUNKING セクションでは、処理中に文脈が失われないように、チャンクサイズやオーバーラップなど、ドキュメントがどのように小さな部分に分割されるかを概説する。
3.サーバーとAIサービスの初期化
設定が完了したら、次はExpressサーバーを初期化し、AIサービスをセットアップする。これには、応答を生成するための大規模な言語モデルと、テキストをベクトルに変換するための埋め込みサービスが含まれる。
const app = express();
app.use(express.json({ limit: '1mb' }));
app.use(cors());
モデル = new ChatOpenAI({
modelName:"gpt-3.5-turbo"、
temperature:0.7,
openAIApiKey: process.env.OPENAI_API_KEY、
maxRetries:3,
timeout:30000,
});
const embeddings = new OpenAIEmbeddings({ 次のようにします。
openAIApiKey: process.env.OPENAI_API_KEY、
maxRetries:3,
});
const splitter = new RecursiveCharacterTextSplitter({
chunkSize:config.chunking.size、
chunkOverlap:config.chunking.overlap、
separators:セパレータ:["˶n", "˶n", "", ""].
});
app` オブジェクトは Express を使用して作成され、ミドルウェアが追加されて、受信する JSON リクエストを解析し、CORS を使用してクロスオリジンリクエストを処理します。これにより、サーバーが異なるドメインからのペイロードを受け入れることができるようになります。
次に、レスポンスを生成するためにOpenAIのGPT-3.5モデルにシステムを接続するために使用する ChatOpenAI インスタンスを作成します。パラメータ temperature はレスポンスの創造性をコントロールし、低い値はより予測可能な出力を、高い値はより多様で創造的なものを生成します。OpenAIEmbeddingsインスタンスはテキストをベクトルに変換し、セマンティック検索を可能にする。最後に、RecursiveCharacterTextSplitter`インスタンスが、設定で定義されたサイズと重なりに基づいてテキストをチャンクに分割する。
4.処理するドキュメントの準備
Milvusを使ってドキュメントを保存したり検索したりする前に、生のコンテンツをクリーニングし、一貫性のある形式に構造化する必要があります。これにより、ワークフローの後半でデータを埋め込み、効率的に検索する準備が整います。
let milvusStore = null;
let lastDataHash = null;
const processDocument = (content) => { もし (!content?.Content || !content.Title)
if (!content?.Content || !content.Title) return null;
const processedContent = content.Content
.map(セクション => {
if (section.type === 'paragraph' && section.children) { if (section.type === 'paragraph' && section.children)
return section.children
.map(child => child.text)
.join(' ')
.trim();
}
return null;
})
.filter(Boolean)
.join('\n');
return processedContent ?
pageContent: processedContent、
metadata:{
source: 'milvus_content'、
id: content.id、
title: content.Title、
documentId: content.documentId
}
}) : null;
};
上記のコードでは、let milvusStore 変数と let lastDataHash 変数がグローバル参照として動作する。milvusStoreはMilvusコレクションへの初期化された接続を保持し、lastDataHash`は基礎となるドキュメントデータが最後の更新以降に変更されたかどうかを追跡する。
processDocument関数はStrapiから生のデータを受け取り、構造化されたDocumentオブジェクトにフォーマットするのに役立つ。まず、contentフィールドとtitleフィールドが存在するかどうかをチェックする。有効であれば、コンテンツのセクションをマッピングし、段落からテキストを抽出し、不要なスペースを削除してきれいにします。これらのセクションは、テキストのまとまったブロックに結合されます。この関数は、処理したテキストをDocument` オブジェクトにラップして返し、ドキュメントのタイトル、ID、ソースなどのメタデータを含む。この形式により、MilvusやLangChainとの互換性が確保され、さらなる処理が可能となる。
5.Milvusへの接続と初期化
ドキュメントの準備ができたので、次はMilvusと統合します。既存のMilvusコレクションへの接続を確立するか、存在しない場合は作成し、準備された文書を入力します。
非同期関数 getMilvusStore() {
if (milvusStore) {
return milvusStore;
}
try {
// 最初に既存のコレクションへの接続を試みる
try {
console.log('Attempting to connect to existing collection');
milvusStore = await Milvus.fromExistingCollection(
embeddings、
{
url:config.milvus.url、
collectionName:config.milvus.collection、
primaryField:config.milvus.primary_field、
vectorField:config.milvus.vector_field、
textField:config.milvus.text_field、
textFieldMaxLength:config.milvus.text_max_length、
}
);
return milvusStore;
} catch (error) {
console.log('Collection does not exist, will create new one');
// コレクションが存在しない場合は、データを取得して作成します。
const response = await axios.get(CONFIG.STRAPI.URL, {
timeout:config.strapi.timeout
});
const processPromises = response.data.data.map(processDocument);
const processedDocs = await Promise.all(processPromises);
const uniqueDocs = new Map();
processedDocs
.filter(Boolean)
.forEach(doc => uniqueDocs.set(doc.metadata.documentId, doc));
const docs = Array.from(uniqueDocs.values());
// 文書を分割する
const splitPromises = docs.map(doc => splitter.splitDocuments([doc]));
const splitDocs = await Promise.all(splitPromises);
const allSplitDocs = splitDocs.flat();
console.log(`Creating new collection with ${allSplitDocs.length} documents`);
milvusStore = await Milvus.fromDocuments(
allSplitDocs、
embeddings、
{
url:config.milvus.url、
collectionName:config.milvus.collection、
primaryField:config.milvus.primary_field、
vectorField:config.milvus.vector_field、
textField:config.milvus.text_field、
textFieldMaxLength:config.milvus.text_max_length、
}
);
lastDataHash = Buffer.from(JSON.stringify(response.data)).toString('base64');
milvusStore を返します;
}
} catch (error) {
console.error('Failed to initialize Milvus:', error);
エラーをスローします;
}
}`
この関数はMilvusコレクションへの接続を管理する。変数 milvusStore が既にアクティブな接続を保持している場合、不要な操作を避けるために、この関数は直ちにその接続を返す。そうでない場合、 fromExistingCollection メソッドを使用して Milvus の既存のコレクションへの接続を試みる。コレクションが存在しない場合、関数は新しいコレクションを作成する。まず、Strapiからデータを取得し、 processDocument 関数を使用してドキュメントを処理し、documentId を Map のキーとして使用して各ドキュメントが一意であることを確認する。次に、 splitter を用いてドキュメントを小さな塊に分割する。これにより、Milvus に埋め込んだりインデックスを作成したりする際にドキュメントが大きすぎないようにすることができる。処理されたチャンクは fromDocuments メソッドを用いて Milvus コレクションに格納される。lastDataHash`はStrapiデータの現在の状態を反映するように更新され、変更を確実に追跡する。
6.Milvusを最新に保つ
MilvusはStrapiと常に同期し、ベクターデータベースが最新のデータを含むようにしなければなりません。Strapiのコンテンツの変更を検出し、更新または新しいドキュメントを識別し、それに応じてMilvusコレクションを更新するプロセスを実装してみましょう。
// 必要に応じてMilvusデータを更新する。
非同期関数 updateMilvusData() { // Milvusデータを更新する。
try {
const response = await axios.get(CONFIG.STRAPI.URL, {
timeout:config.strapi.timeout
});
const currentHash = Buffer.from(JSON.stringify(response.data)).toString('base64');
if (currentHash === lastDataHash) { 以下のようにします。
console.log('Data unchanged, skip update');
return false;
}
console.log('Content changed, updating Milvus collection');
const processPromises = response.data.data.map(processDocument);
const processedDocs = await Promise.all(processPromises);
const uniqueDocs = new Map();
processedDocs
.filter(Boolean)
.forEach(doc => uniqueDocs.set(doc.metadata.documentId, doc));
const docs = Array.from(uniqueDocs.values());
// 文書を分割する
const splitPromises = docs.map(doc => splitter.splitDocuments([doc]));
const splitDocs = await Promise.all(splitPromises);
const allSplitDocs = splitDocs.flat();
// 新しいドキュメントを追加する前に、既存のドキュメントをすべて削除する
await milvusStore.delete({});
// 新規文書を一括追加
for (let i = 0; i < allSplitDocs.length; i += CONFIG.MILVUS.BATCH_SIZE) { // 新しいドキュメントをバッチで追加する。
const batch = allSplitDocs.slice(i, i + CONFIG.MILVUS.BATCH_SIZE);
await milvusStore.addDocuments(batch);
console.log(`Added batch ${Math.floor(i / CONFIG.MILVUS.BATCH_SIZE) + 1} of ${Math.ceil(allSplitDocs.length / CONFIG.MILVUS.BATCH_SIZE)}`);
}
lastDataHash = currentHash;
console.log(`${allSplitDocs.length} documents`でMilvusを更新`);
trueを返す;
キャッチ (エラー) {
console.error("Error updating Milvus data:", error);
エラーをスローします;
}
}
この関数はStrapiから最新のデータをフェッチし、そのハッシュを計算することから始める。ハッシュが lastDataHash と一致する場合、データは変更されていないので、更新の必要はない。そうでない場合、この関数は更新されたドキュメントを処理し、deleteメソッドを使用してMilvusの既存のコレクションをクリアします。その後、新しいドキュメントのチャンクがバッチで追加され、効率的なストレージが確保されると同時に、進捗追跡のためのロギングが維持される。最後に lastDataHash を更新し、今後のチェックでデータの変更を正確に検出できるようにする。
7.ユーザークエリの処理
Milvusに我々のデータを入力した後、次のステップはユーザのクエリを処理することである。システムはユーザーのクエリとの意味的類似性に基づいて、Milvusから最も関連性の高い文書を取得する。これらの文書は、OpenAIのGPT-3.5大規模言語モデルを使用して、コンテキストを考慮した応答を生成するために使用されます。
async関数 handleQuery(chatHistory, input) { { { { { store = await getMilkery(chatHistory, input)
const store = await getMilvusStore();
try {
await updateMilvusData();
} catch (error) {
console.warn("Failed to check for updates:", error);
}
const results = await store.similaritySearchWithScore(
input、
CONFIG.TOP_K
);
console.log(' \n=== Milvusから文書を取得 ===');
results.forEach(([doc, score], index) => { {{document ${index + 1
console.log(`nDocument ${index + 1} (score: ${score}):`);
console.log('Title:', doc.metadata.title);
console.log('Content:', doc.pageContent.substring(0, 150) + '...');
});
const chain = await createStuffDocumentsChain({
llm: model、
prompt:ChatPromptTemplate.fromMessages([
[
"system"、
あなたはMilvusとZilliz、ベクトルデータベース技術に特化したAIアシスタントです。あなたのゴールは、提供されたコンテキストに基づいて正確で役立つ答えを提供することです。
**ガイドライン
1.**Milvus/Zilliz-Relatedクエリ:**。
- クエリがMilvusまたはZillizに関するもので、コンテキストに関連情報が含まれている場合、詳細かつ構造化された回答を提供してください。
- 適切なMarkdownフォーマットを使用してください:
- コードブロックの場合
- Bold** for emphasis.
- リストには箇条書き
- セクションの見出し
- 関連する情報が文脈に存在しない場合は、こう答えてください:*その情報はまだ持っていません。
2.**関連性のない問い合わせ
- ミルヴァスやジリズに関する問い合わせでない場合、以下のように返答する:
*このトピックは私の専門外です。私はMilvusとZillizを専門としています。これらの技術に関連した質問をしてください。
**コンテキスト:**
{コンテキスト}
**ユーザークエリ:**
{入力}`
],
... chatHistory、
["user", "{input}" ]。
]),
documentPrompt:ChatPromptTemplate.fromTemplate("Content: {page_content}n")
});
コンストラスト response = await chain.invoke({
input、
context: results.map(([doc]) => doc)
});
return {
answer: レスポンス、
コンテキスト: results.map(([doc]) => ({
content: doc.pageContent、
title: doc.metadata.title、
id: doc.metadata.id、
documentId: doc.metadata.documentId
}))
};
}
この関数はまず、Milvus ベクトルストアが最新であることを確認する。updateMilvusData関数はtry-catchブロック内で呼び出され、クエリ処理を中断することなく潜在的なエラーを処理する。クエリは次に Milvus のsimilaritySearchWithScoreメソッドに渡され、データベースから上位k` 件の最も類似した文書を検索する。検索された文書はログに記録される。各文書にはタイトルやIDなどのメタデータと、その内容を切り詰めたプレビューが含まれる。これにより、RAGパイプラインが正しい文書を取得しているかどうかを知ることができる。
次に、ドキュメントはLangChainの createStuffDocumentsChain に送り込まれ、提供されたコンテキストのみに基づいて回答するようモデルに指示するプロンプトを使用する。これにより、OpenAIのGPT-3.5は、外部の知識に依存することなく、検索されたコンテンツに基づいた応答を生成し、正確性と関連性を維持します。最後に、この関数は、生成されたレスポンスと検索されたドキュメントのコンテキストの両方を返します。
8.APIエンドポイントの設定
バックエンドがユーザクエリを処理し、Milvusと対話できるようになったので、次のステップはAPIエンドポイントを通してこの機能を公開することです。これらのエンドポイントは、バックエンドとフロントエンド(または他のクライアントアプリケーション)との橋渡しの役割を果たします。
app.post("/chat"), async (req, res) => {
try {
const { chatHistory, input } = req.body; { try {= req.body;
if (!input?.trim()) {
return res.status(400).json({
error:「無効な入力です、
message:"入力がありません"
});
}
const formattedHistory = Array.isArray(chatHistory)
?チャット履歴.map(msg =>)
msg.role === "user"
? new HumanMessage(msg.content)
: new AIMessage(msg.content)
)
: [];
const response = await handleQuery(formattedHistory, input);
res.json(response);
} catch (error) {
console.error("Chat request error:", error);
res.status(500).json({
error:"内部サーバーエラー"、
message: process.env.NODE_ENV === 'production' ?
?"予期しないエラーが発生しました"
: error.message
});
}
});
app.get("/health", 非同期 (req, res) => { {)
try {
const store = await getMilvusStore();
res.json({
status:"ok"、
timestamp: new Date().toISOString()、
milvusInitialized: !!store、
config:コンフィグ
});
} catch (error) {
res.status(500).json({
status:"error"、
error: error.message
});
}
});
chatエンドポイントは、ユーザのクエリとチャット履歴を含む POST リクエストを処理します。チャット履歴はコンテキストを保持するためにHumanMessageオブジェクトとAIMessageオブジェクトにフォーマットされる。クエリが無効な場合 (例: 空)、エンドポイントは400 Bad Requestステータスと適切なエラーメッセージで応答する。クエリが有効な場合、handleQuery関数を呼び出して入力を処理し、Milvus から関連する文書を取得してレスポンスを生成する。結果はJSONオブジェクトとして返される。エラーが発生した場合はログに記録され、サーバーは500 Internal Server Error` で応答する。
一方、 /health エンドポイントはサーバの状態を監視する方法を提供する。これは、Milvusストアが初期化されているかどうかをチェックし、サーバの設定と現在のタイムスタンプとともに、okまたはerrorのステータスを応答します。
9.サーバの起動
最後のステップはサーバーを起動し、リクエストを処理できるようにすることです。
app.listen(CONFIG.PORT, () => {
console.log(`Server running on http://localhost:${CONFIG.PORT}`);
console.log('Configuration:', CONFIG);
});
app.listen`メソッドは、コンフィギュレーションで指定されたポート、この場合はポート 30080 でサーバーを起動します。サーバーが起動すると、アクセス可能な URL と現在の設定を示すメッセージがログに記録されます。これにより、バックエンドがリクエストを処理する準備ができたことを確認できます。
図- RAGシステムのバックエンドサーバが現在の設定をログに記録しながら動作している様子](https://assets.zilliz.com/Figure_RAG_system_backend_server_running_while_logging_the_current_configurations_4a827917fb.png)
図:RAGシステムバックエンドサーバが現在のコンフィギュレーションをロギングしながら動作している_RAGシステムバックエンドサーバが現在のコンフィギュレーションをロギングしながら動作している。
サーバーを実行するには、ターミナルに進み、以下のコマンドを実行します。
ノード . \server.mjs
これで RAG システムのバックエンドの作成は完了です。
RAGシステムのフロントエンドを作成する
RAGシステムのバックエンドが完全に機能したので、次のステップは、ユーザーがシステムと対話できるようにするためのフロントエンドを作成することです。フロントエンドは、バックエンドに接続するチャットボットのインターフェースとなり、ユーザーがクエリーを入力し、AIが生成した応答を受け取ることができるようにする。
ユーザーインターフェースの作成
まず src ディレクトリに ChatbotUI.js というファイルを作成する。このファイルでチャットボットコンポーネントを定義し、ユーザーからの問い合わせとバックエンドからの応答のフローを管理します。
import React, { useState, useEffect, useRef } from 'react';
import ReactMarkdown from 'react-markdown';
import axios from 'axios';
import './ChatbotUI.css';
constチャットボットUI = () => {
const [chatHistory, setChatHistory] = useState([]);
const [userInput, setUserInput] = useState('');
const [isLoading, setIsLoading] = useState(false);
const [error, setError] = useState(null);
const chatContainerRef = useRef(null);
useEffect(() => { // 最新のメッセージにスクロールする。
// チャット履歴が更新されたら、最新のメッセージにスクロールする
chatContainerRef.current.scrollTop = chatContainerRef.current.scrollHeight;
}, [chatHistory]);
const handleUserInput = (e) => setUserInput(e.target.value);
const handleSendMessage = 非同期() => { {.
if (userInput.trim() !== '') { }.
const newMessage = { role: 'user', content: userInput };
const updatedChatHistory = [...chatHistory, newMessage];
setChatHistory(updatedChatHistory);
setUserInput('');
setIsLoading(true);
try { 以下のようにします。
const response = await axios.post('http://localhost:30080/chat', { )
chatHistory: updatedChatHistory、
input: userInput、
});
ボットメッセージ = {
role: 'assistant'、
content: response.data.answer || 'すみません、よくわかりませんでした、
};
setTimeout(() => { {」とします。
setChatHistory((prevMessages) => [...prevMessages, botMessage]);
}, 1000); // タイピング遅延をシミュレートする
} catch (err) {
console.error('Error:', err);
setError('サーバーに接続できません。');
} finally {
setIsLoading(false);
}
};
return (
<div className="chatbot-ui">
{ヘッダー */}
<div className="chat-header">
<img src="/milvus_logo.png" alt="アシスタント" className="assistant-logo" />
<h2>チャットアシスタント</h2>
</div>
{チャットメッセージ */}
<div className="chat-body" ref={chatContainerRef}>
{chatHistory.map((message, index) => (
<div
key={index}
className={`message ${message.role === 'user' ?'user-message' : 'bot-message'}`}となります。
>
<ReactMarkdown>{メッセージの内容}</ReactMarkdown
</div>
))}
{isLoading && (
<div className="bot-message typing-indicator">のようにします。
<span></span>
<span></span
<span></span
</div>
)}
{エラー && <div className="エラーメッセージ">{エラー}</div>}。
</div>
{フッター */}
<div className="チャットフッター"></div
<input
type="text"
placeholder="何でも聞いてください..."
value={userInput}
onChange={handleUserInput}
onKeyPress={(e)=>{」となります。
if (e.key === 'Enter') handleSendMessage();
}}
disabled={isLoading}
/>
<button onClick={handleSendMessage}disabled={isLoading}>をクリックしてください。
<span className="send-icon">✈</span> </span
</button
</div>
</div>
);
};
export default ChatbotUI;
このコンポーネントは React の useState を使用して、チャット履歴やユーザー入力などの状態を管理します。useEffectフックは、チャットコンテナが自動的に最新のメッセージまでスクロールするようにします。ユーザーがクエリを送信すると、axios` POST リクエストを介して http://localhost:30080/chat バックエンド API エンドポイントに送信されます。バックエンドのレスポンスはチャットインターフェイスに表示されます。
チャットボットのスタイリング
次に、src ディレクトリに ChatbotUI.css というファイルを作成します。このファイルがチャットボットのインターフェイスをスタイリングします。
/* フルスクリーンのグラデーション背景 */
.chatbot-ui {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
width: 100%;
height: 100vh;
background: linear-gradient(135deg, #8358ff, #00a6ff); /* Milvusカラーパレット */
font-family: 'Roboto', sans-serif;
color:color: #fff;
}
/* チャットヘッダー */
.chat-header {
display: flex;
align-items: center;
justify-content: center;
background: linear-gradient(90deg, #1e1e2d, #28293e);
width: 100%;
padding:15px;
border-radius: 15px 15px 0 0;
box-shadow: 0 4px 10px rgba(0, 0, 0, 0.2);
}
アシスタントのロゴ
width: 50px;
height: 50px;
margin-right: 15px;
border-radius: 50%;
background: radial-gradient(circle, #8358ff, #00a6ff); /* ダイナミックなロゴの輝き */
animation: pulse 2s infinite;
}
.chat-header h2 {
font-size: 1.8rem;
font-weight: bold;
color:color: #ffffff;
}
.chat-header p {
font-size: 0.9rem;
color:#a0a0b1;
margin-top: 5px;
}
/* チャット本文 */
.chat-body {
flex: 1;
width: 100%;
padding:15px;
overflow-y: auto;
display: flex;
flex-direction: column;
gap: 12px;
background:background: #1e1e2d;
color:color: #ffffff;
border-radius: 0 0 15px 15px;
}
.message {
max-width: 80%;
padding:12px 15px;
border-radius: 10px;
font-size: 1rem;
line-height: 1.5;
animation: fadeIn 0.3s ease-in-out;
}
.user-message {
align-self: flex-end;
background: linear-gradient(90deg, #8358ff, #00a6ff);
color:color: #ffffff;
box-shadow: 0 4px 10px rgba(0, 0, 0, 0.2);
}
.bot-message {
align-self: flex-start;
background:#29293f;
color:color: #d4d4e5;
box-shadow: 0 4px 10px rgba(0, 0, 0, 0.2);
}
/* タイピング・インジケーター */
.typing-indicator {
display: flex;
justify-content: flex-start;
gap: 5px;
}
.typing-indicator span {
width: 8px;
height: 8px;
background-color:background-color: #00a6ff;
border-radius: 50%;
animation: blink 1.2s infinite;
}
.typing-indicator span:nth-child(2){の場合
animation-delay:0.2s;
}
.typing-indicatorのspan:nth-child(3) { { アニメーション遅延: 0.2s; }.
animation-delay:0.4s;
}
/* フッター */
.chat-footer {
display: flex;
align-items: center;
width: 100%;
padding:10px 15px;
背景background: #1e1e2d;
border-top: 1px solid #28293e;
border-radius: 0 0 15px 15px;
box-shadow: 0 -4px 10px rgba(0, 0, 0, 0.2);
}
.chat-footer input {
flex: 1;
padding:12px 15px;
border-radius:30px
border:1px solid #8358ff;
font-size: 1rem;
outline: none;
背景#29293f;
color:color: #ffffff;
transition: border-color 0.3s ease;
}
.chat-footer input:focus {
border-color:#00a6ff;
}
.chat-footer button {
margin-left: 10px;
padding:12px 20px;
border: none;
border-radius:30px;
background: linear-gradient(90deg, #8358ff, #00a6ff);
color: white;
font-size: 1.2rem;
cursor: pointer;
box-shadow: 0 4px 10px rgba(0, 0, 0, 0.2);
transition: transform 0.3s ease, box-shadow 0.3s ease;
}
.チャット・フッター・ボタン:hover {
transform: scale(1.1);
box-shadow: 0 6px 15px rgba(0, 0, 0, 0.4);
}
.chat-footerボタン:disabled {
背景#29293f;
color:color: #a0a0b1;
cursor: not-allowed;
}
キーフレーム blink { {キーフレーム
0%, 80%, 100% {
opacity: 0;
}
40% {
opacity: 1;
}
}
キーフレーム fadeIn {
から
opacity: 0;
transform: translateY(10px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
keyframesパルス { {キーフレーム
0%, 100% {
transform: scale(1);
box-shadow: 0 0 10px rgba(131, 88, 255, 0.5);
}
50% {
transform: scale(1.1);
box-shadow: 0 0 20px rgba(0, 166, 255, 0.7);
}
CSSスタイルは、Milvusのカラーパレットにインスパイアされたチャットボットのインターフェイスを作成します。背景はMilvusのブランディングを反映し、深い紫とシアンのグラデーションを使用しています。チャットのヘッダーは暗いグラデーションスタイルで、ユーザーメッセージはMilvusの原色にマッチした明るいグラデーションで強調されています。ボットメッセージは、ダークで落ち着いたテーマでスタイリングされています。このデザインは、統一された機能的なユーザーインターフェースのためにMilvusの色を活用しています。
図- RAGシステム完成イメージ](https://assets.zilliz.com/Figure_RAG_system_completed_user_interface_cd5c621102.png)
図:RAGシステム完成ユーザーインターフェース
最終的なUIはこんな感じです。
チャットボットをアプリケーションに組み込む
最後に、src ディレクトリの App.js ファイルを更新して、ChatbotUI コンポーネントをレンダリングします。
import React from 'react';
import ChatbotUI from './ChatbotUI';
const アプリ = () => {
return (
<div
<ChatbotUI
</div>
);
};
export default App;
これにより、アプリケーションの起動時にチャットボットがメインコンポーネントとしてレンダリングされるようになります。App` コンポーネントは React アプリケーションのエントリポイントとして機能します。
RAGシステムのテスト
バックエンドとフロントエンドのセットアップが完了したら、RAGパイプライン全体が期待通りに動作するかテストしましょう。進め方は以下の通りです:
**バックエンドの開始
ターミナルを開き、プロジェクトディレクトリに移動し、バックエンドサーバーを起動します:
node server.mjs
バックエンドが http://localhost:30080 で動作していることを確認する。ログをチェックして、Milvusが初期化され、クエリの準備が整っていることを確認する。
**フロントエンドの実行
別のターミナルを開き、rag-appディレクトリに移動し、Reactフロントエンドを起動する:
npm start
これで、デフォルトのブラウザで http://localhost:3000 からアプリケーションが起動する。
**チャットボットのインターフェイスをテストする。
チャットボットのインターフェースで、MilvusやZillizに関する質問を入力してください。また、これらのトピック以外の質問を入力してみて、システムが適切なメッセージで応答するかどうかを確認してください。最後に、Strapiのナレッジベース外の質問を入力してみてください。以下にサンプル結果を示します:
図- ミルバスに関連するクエリのRAGシステム結果](https://assets.zilliz.com/Figure_RAG_system_result_of_a_query_related_to_Milvus_b9531f1880.png)
図:Milvusに関連するクエリのRAGシステム結果_()
図- RAGシステムのテスト結果](https://assets.zilliz.com/Figure_RAG_system_testing_results_4d191167b6.png)
図RAGシステムテスト結果
上記のスクリーンショットは、我々のRAGシステムが期待通りに動作し、正しい結果を表示していることを示している。また、我々のナレッジベースを使用しているため、Strapiに存在しないMilvusの創設者の情報を知ることができない。
ナレッジベースにコンテンツを追加し、システムをテストしてください。
結論
LangChain、Milvus、Strapiを統合して検索拡張生成(RAG)システムを構築することで、AIが実際の最新の知識に基づいた正確でドメインに特化した応答をどのように提供できるかを示している。このアーキテクチャは、カスタマーサポート、ナレッジマネジメント、教育ツールなどのアプリケーションに理想的である。アーキテクチャを明確に理解し、このステップバイステップのガイドを読めば、特定のニーズに合わせたRAGシステムを作成できるようになります。
関連リソース
フルコード](https://github.com/FINCH285/RAG-with-LangChain-Milvus-and-Strapi-) と ストラピバックエンド
Milvus | 🦜️🔗 Langchain](https://js.langchain.com/docs/integrations/vectorstores/milvus/)
ストラピ開発者ドキュメントへようこそ!|ストラピ5ドキュメント](https://docs.strapi.io/dev-docs/intro)
Milvusベクトルデータベース・ドキュメント](https://milvus.io/docs)
読み続けて

How to Improve Retrieval Quality for Japanese Text with Sudachi, Milvus/Zilliz, and AWS Bedrock
Learn how Sudachi normalization and Milvus/Zilliz hybrid search improve Japanese RAG accuracy with BM25 + vector fusion, AWS Bedrock embeddings, and practical code examples.

The Great AI Agent Protocol Race: Function Calling vs. MCP vs. A2A
Compare Function Calling, MCP, and A2A protocols for AI agents. Learn which standard best fits your development needs and future-proof your applications.

VidTok: Rethinking Video Processing with Compact Tokenization
VidTok tokenizes videos to reduce redundancy while preserving spatial and temporal details for efficient processing.
