100日チャレンジ day24 (簡易RAGシステム)

昨日
100日チャレンジに感化されたので、アレンジして自分でもやってみます。
やりたいこと
- 世の中のさまざまなドメインの簡易実装をつくり、バックエンドの実装に慣れる(dbスキーマ設計や、関数の分割、使いやすいインターフェイスの切り方に慣れる
- 設計力(これはシステムのオーバービューを先に自分で作ってaiに依頼できるようにする
- 生成aiをつかったバイブコーティングになれる
- 実際にやったことはzennのスクラップにまとめ、成果はzennのブログにまとめる(アプリ自体の公開は必須ではないかコードはgithubにおく)
できたもの

RAGを作ってみる

アプリケーション仕様:
- 目的: 特定のWikipediaページ(名探偵コナン関連)を知識ソースとして、ユーザーの質問に関連する情報を検索し、その情報に基づいて簡易的な応答を生成するRAG (Retrieval-Augmented Generation) システムを構築する。
-
知識ソース:
- 名探偵コナン (https://ja.wikipedia.org/wiki/名探偵コナン)
- 名探偵コナンの漫画エピソード一覧 (https://ja.wikipedia.org/wiki/名探偵コナンの漫画エピソード一覧)
- 上記ページのテキストデータを抽出し、SQLiteデータベース (
prisma/dev.db
) のDocument
テーブルに初期データ (seed) として格納する。
-
主要機能:
- RAG質問インターフェース: ユーザーが自然言語で質問を入力できるUIを提供する。
-
関連ドキュメント検索: 入力された質問からキーワードを抽出し、SQLiteデータベース内の
Document
テーブルのcontent
フィールドを検索する。- 検索ロジック: 質問文をスペースで分割し、各単語で
content
をcontains
検索 (OR
条件)。最も多くのキーワードにマッチしたドキュメントを最優先とする (簡易実装)。
- 検索ロジック: 質問文をスペースで分割し、各単語で
-
応答生成 (シミュレーション): 検索で見つかったドキュメントの情報を使って、固定テンプレートに基づいた応答文を生成する。実際のLLMは使用しない。
- 応答テンプレート例 (見つかった場合):
「${ドキュメントタイトル}」の情報によると、あなたの質問「${質問文}」に関連すると思われる記述は以下の通りです。「${ドキュメント本文の冒頭200文字}...」
- 応答テンプレート例 (見つからない場合):
申し訳ありませんが、「${質問文}」に関連する明確な情報は知識ベースの中に見つかりませんでした。
- 応答テンプレート例 (見つかった場合):
-
データモデル:
model Document { id Int @id @default(autoincrement()) title String // 例: "名探偵コナン (Wikipedia)" content String @db.Text // Wikipediaページのテキスト本文 sourceUrl String // 例: "https://ja.wikipedia.org/wiki/名探偵コナン" createdAt DateTime @default(now()) updatedAt DateTime @updatedAt }
-
APIエンドポイント:
-
POST /api/rag
- リクエスト:
{ "query": "ユーザーの質問文字列" }
- レスポンス:
{ "response": "生成された応答文字列", "sources": [{ "id": number, "title": string, "url": string }] }
(応答の根拠となったドキュメント情報のリスト)
- リクエスト:
-
-
UI:
- 質問入力用のテキストエリア
- 実行ボタン
- 生成された応答と、根拠となったドキュメントへのリンク(タイトルとWikipedia URL)を表示するエリア

はい、承知いたしました。day24「簡易RAGシステム」の実装に向けた作業計画は以下の通りです。
-
プロジェクト設定:
- 作業ディレクトリ
day24_simple_rag
に移動します。 -
day24_simple_rag/package.json
のname
フィールドをday24-simple-rag
に変更します。 -
day24_simple_rag/package.json
のscripts
にdb:seed
を追加します (prisma db seed
)。 seed スクリプトは TypeScript で記述するため、実行用にtsconfig.seed.json
も作成し、scripts.db:seed
をprisma db seed -- --tsconfig tsconfig.seed.json
に修正します。
- 作業ディレクトリ
-
データベース設定:
-
day24_simple_rag/prisma/schema.prisma
にDocument
モデル (id, title, content, sourceUrl, createdAt, updatedAt) を定義します。 -
npx prisma migrate deploy --schema=./day24_simple_rag/prisma/schema.prisma
を実行してデータベーススキーマを適用します。
-
-
初期データ準備 (Seed):
-
day24_simple_rag/prisma/seed.ts
を作成します。 -
seed.ts
内に、指定された2つのWikipediaページのコンテンツを取得し(※取得処理は簡易的に、直接テキストを埋め込むか、別途取得したテキストファイルを読み込む形とします。Webスクレイピングは実装しません)、Document
テーブルに登録する処理を実装します。 -
npx prisma db seed -- --tsconfig day24_simple_rag/tsconfig.seed.json
を実行して初期データを投入します。
-
-
API 実装:
-
day24_simple_rag/app/api/rag/route.ts
を作成します。 -
POST
リクエストハンドラを実装します。- リクエストボディから
query
を受け取ります。 -
query
を単語に分割します。 - Prisma Client を使用して、
Document
テーブルのcontent
を検索します (分割した単語でcontains
OR 検索)。 - 検索結果から関連性の高いドキュメントを選択します (今回は最初に見つかった1件とします)。
- 選択されたドキュメント情報(タイトル、本文冒頭、URL)と質問文を使って、テンプレートに基づいた応答文字列 (
response
) を生成します。 - 応答文字列と、根拠となったドキュメントの情報 (
sources
) をJSON形式で返します。
- リクエストボディから
-
-
UI 実装:
-
day24_simple_rag/app/page.tsx
を作成 (または編集) します。 -
useState
を用いて、質問入力 (query
)、ローディング状態 (isLoading
)、APIからの応答 (response
)、参照元 (sources
) を管理します。 - 質問入力用の
<textarea>
と送信ボタンを配置します。 - ボタンクリック時に
/api/rag
へ POST リクエストを送信する関数を実装します (fetch
を使用)。 - ローディング状態に応じて表示を切り替えます。
- APIからの応答 (
response
とsources
) を画面に表示します。sources
はリンク付きで表示します。 - Tailwind CSS で基本的なスタイリングを適用します。
-
-
動作確認:
-
npm run dev
で開発サーバーを起動します。 - ブラウザで
http://localhost:3001
にアクセスします。 - 名探偵コナンに関する様々な質問を入力し、期待される応答(関連情報を含む応答 or 情報が見つからない旨のメッセージ)が表示されるか確認します。
-
-
ドキュメント整備:
-
day24_simple_rag/README.md
の内容を最終確認し、必要に応じて追記・修正します。 -
.cursor/rules/knowledge.md
に Day 24 のエントリを追加します。
-
この計画に沿って、まずはプロジェクト設定から進めていきます。

んーーー、vertor db 使って欲しい。そうじゃないとただ単語ぶんかつしてるだけでは?
ご指摘ありがとうございます。確かに、単なるキーワード検索では RAG の「賢い検索」部分が十分に表現できませんね。ベクトル検索の導入は良いアイデアです。
ただし、本格的なベクトルデータベース (Pinecone, Weaviate, Qdrant 等) を導入するのは、今回の1日チャレンジの範囲と制約 (SQLite利用) を考えると難しいです。セットアップや外部サービス依存が発生してしまいます。
そこで、より RAG のコンセプトに近づけるための代替案として、以下を提案します。
提案: transformers.js
を利用したインメモリでの簡易ベクトル検索
-
ライブラリ導入:
-
@xenova/transformers
パッケージを導入します。これは、JavaScript (Node.js やブラウザ) で Hugging Face の Transformer モデル(テキストのベクトル化/Embedding を含む)を利用可能にするライブラリです。
-
-
ベクトル化 (Embedding) 処理:
-
知識ソースのベクトル化: アプリケーション起動時、または最初の API リクエスト時に、SQLite から全ての
Document
(id
,content
) を読み込みます。 -
transformers.js
の Embedding モデル (例:Xenova/multilingual-e5-small
) を使用して、各ドキュメントのcontent
をベクトル(数値の配列)に変換します。 - これらのドキュメントIDとベクトルのペアをメモリ上にキャッシュします。(DBにはベクトル自体は保存しません)
-
知識ソースのベクトル化: アプリケーション起動時、または最初の API リクエスト時に、SQLite から全ての
-
質問のベクトル化と類似度検索:
- ユーザーからの質問 (
query
) も、同じ Embedding モデルを使ってベクトル化します。 - メモリ上にキャッシュされたドキュメントベクトル群と、質問ベクトルのコサイン類似度を計算します。
- 最も類似度が高いドキュメントを検索結果として特定します。
- ユーザーからの質問 (
-
応答生成:
- 特定されたドキュメントの情報を使って、従来通りテンプレートベースで応答を生成します。
このアプローチのメリット:
- セマンティック検索: 単なるキーワードマッチではなく、意味的に類似したドキュメントを検索できます。RAG の「賢い検索」に近づきます。
-
SQLite 利用の維持: ベクトルデータ自体はメモリ上に持つため、SQLite のスキーマは変更不要(
Document
モデルはそのまま)で、Prisma でのメタデータ管理も継続できます。 - ローカル完結: 外部のベクトルDBサービスは不要です。
注意点・デメリット:
- メモリ消費: 全ドキュメントのベクトルをメモリ上に保持するため、ドキュメント数や内容によってはメモリを消費します。(今回の Wikipedia 2ページ程度なら問題ない範囲と考えられます)
- 初回処理時間: 初めて Embedding モデルを使う際や、アプリケーション起動時に全ドキュメントをベクトル化する際に、モデルのダウンロードと計算処理が発生し、時間がかかる可能性があります。
-
transformers.js
の学習: このライブラリの使い方を学ぶ必要があります。 - 検索精度: 使用する Embedding モデルに依存します。
作業計画の変更点:
-
package.json
に@xenova/transformers
を追加。 - API (
/api/rag
) または共通ライブラリ (lib/
) に、ベクトル化と類似度検索を行うロジックを追加。 - アプリケーション起動時 or 初回アクセス時にドキュメントベクトルを計算し、メモリにキャッシュする仕組みを実装。 (簡易的には、
api/rag
が最初に呼ばれた時にキャッシュを生成するのでも良いかもしれません)
この transformers.js
を利用したインメモリベクトル検索 のアプローチで進めるのはいかがでしょうか? より挑戦的で RAG らしい実装になるかと思います。

承知いたしました。transformers.js
を利用したインメモリベクトル検索を導入する方向で、修正した作業計画をお伝えします。
修正版 作業計画:
-
プロジェクト設定:
- 作業ディレクトリ
day24_simple_rag
に移動します。 (cd day24_simple_rag
) -
package.json
のname
フィールドをday24-simple-rag
に変更します。 -
package.json
のdependencies
に@xenova/transformers
を追加します (npm install @xenova/transformers
)。 -
package.json
のscripts
にdb:seed
を追加します ("db:seed": "npx prisma db seed -- --tsconfig tsconfig.seed.json"
など)。 - Seed スクリプト用の
tsconfig.seed.json
を作成します。
- 作業ディレクトリ
-
データベース設定:
-
prisma/schema.prisma
にDocument
モデル (id, title, content, sourceUrl, createdAt, updatedAt) を定義します。(これは変更ありません) -
npx prisma migrate deploy --schema=./day24_simple_rag/prisma/schema.prisma
を実行してデータベーススキーマを適用します。
-
-
初期データ準備 (Seed):
-
prisma/seed.ts
を作成します。 -
seed.ts
内に、指定されたWikipediaページのテキストデータをDocument
テーブルに登録する処理を実装します。(テキストデータは直接埋め込むかファイル読み込みとします) -
npx prisma db seed -- --tsconfig day24_simple_rag/tsconfig.seed.json
を実行して初期データを投入します。
-
-
ベクトル化・検索ロジック実装:
-
day24_simple_rag/lib/vectorStore.ts
(または類似のパス) を作成します。 - このファイルで以下の機能を実装します:
-
Embeddingモデルの準備:
transformers.js
のpipeline
関数を使って、Embeddingモデル (例:Xenova/multilingual-e5-small
) のインスタンスを作成する関数を定義します。(モデルロードは非同期なので注意) -
インメモリキャッシュ: ドキュメントIDと対応するベクトルを格納するキャッシュ(例:
Map<number, number[]>
)をモジュールスコープで定義します。キャッシュが初期化されたかどうかのフラグも用意します。 -
キャッシュ初期化関数 (
initializeVectorCache
):- キャッシュが未初期化の場合のみ実行します。
- Prisma Client を使ってDBから全ドキュメント (
id
,content
) を取得します。 - 取得した各
content
を準備したEmbeddingモデルでベクトル化します。 -
{ documentId: vector }
の形式でキャッシュに格納します。 - 初期化フラグを
true
にします。
-
類似度計算関数 (
cosineSimilarity
): 2つのベクトル間のコサイン類似度を計算するヘルパー関数。 -
検索関数 (
findMostSimilarDocument
):- 入力された質問文字列 (
query
) をEmbeddingモデルでベクトル化します。 - キャッシュ内の全てのドキュメントベクトルと質問ベクトルのコサイン類似度を計算します。
- 最も類似度が高いドキュメントの
id
を返します。
- 入力された質問文字列 (
-
Embeddingモデルの準備:
-
-
API 実装 (
app/api/rag/route.ts
):-
POST
リクエストハンドラを実装します。 - 最初に
initializeVectorCache
をawait
付きで呼び出し、キャッシュが準備されるのを待ちます。(一度初期化されれば、以降の呼び出しはすぐ完了する想定) - リクエストボディから
query
を受け取ります。 -
findMostSimilarDocument
関数を呼び出して、最も関連性の高いドキュメントIDを取得します。 - 取得したIDを使って、Prismaで該当ドキュメントの詳細 (
id
,title
,content
,sourceUrl
) をDBから取得します。 - 見つかったドキュメント情報に基づいて、テンプレート応答文 (
response
) を生成します。 - 応答 (
response
) とソース情報 (sources
) をJSON形式で返します。
-
-
UI 実装 (
app/page.tsx
):- (基本的な構成は変更なし)質問入力、実行ボタン、結果表示エリアを設けます。
- API (
/api/rag
) 呼び出し処理を実装します。初回API呼び出し時にモデルのロード等で時間がかかる可能性があるため、ローディング表示を適切に行います。 - APIからの応答を表示します。
- Tailwind CSS でスタイリングします。
-
動作確認:
-
npm run dev
で起動し、ブラウザで動作確認します。 - 初回アクセス時に時間がかかること、2回目以降は比較的速いことを確認します。
- 様々な質問(キーワードが直接一致しないものも含む)で、意味的に関連すると思われるドキュメントが検索され、応答が生成されるかを確認します。
-
-
ドキュメント整備:
-
README.md
にtransformers.js
の利用、インメモリベクトル検索、使用モデル、注意点などを追記・修正します。 -
.cursor/rules/knowledge.md
を更新します。
-
この計画で進めていきます。まずはプロジェクト設定のステップ1から開始します。

これを使うと web で transform できて vector db 使わなくてもいいとか

こういうざっくりDocumentDBをつくって対応している
そしてtransfer.js が一番一致率が高いものを返している
Searching for query: "黒の組織は?"
Similarity with doc 3: 0.7822222866720302
Similarity with doc 4: 0.8467446724048368
Similarity with doc 5: 0.7907871183658612
Similarity with doc 6: 0.78245901433795
Similarity with doc 7: 0.836542908913885
Similarity with doc 8: 0.7712599015458526
Similarity with doc 9: 0.7996219390536028
Similarity with doc 10: 0.7844713683406721
Similarity with doc 11: 0.7812669525229684
Similarity with doc 12: 0.817501108462392
Best match: doc 4 with similarity 0.8467446724048368
Found most similar document ID: 4
Sending response.

本来のデータの取り方
Wikipedia のデータをベクトルデータベース (Vector DB) に入れて RAG システムで活用するための、より一般的で本格的な手順は以下のようになります。
-
データ取得 (Data Acquisition):
- 目標: 必要な Wikipedia ページのコンテンツを取得します。
-
方法:
-
Wikipedia API / ライブラリ: Python の
wikipedia
ライブラリや、MediaWiki API を直接叩くなどして、指定したページのコンテンツ(通常は HTML または Wiki マークアップ形式)を取得します。これにより、手動コピーよりも効率的かつ網羅的にデータを取得できます。 -
Web スクレイピング: 上記が難しい場合、
requests
やBeautifulSoup
(Python) などのライブラリで直接ページをスクレイピングしますが、Wikipedia の利用規約や負荷に配慮が必要です。
-
Wikipedia API / ライブラリ: Python の
-
データ抽出・整形 (Data Extraction & Cleaning):
- 目標: 取得した生データ(HTML や Wiki マークアップ)から、RAG に必要な本文テキストだけを抽出し、不要な情報(ナビゲーションメニュー、サイドバー、情報ボックス、編集リンク、参考文献リスト、カテゴリなど)を除去します。
-
方法:
- HTML パーサー (
BeautifulSoup
,lxml
など) を使って HTML 構造を解析し、本文が含まれる主要なタグ (<p>
,<h1>
,<h2>
など) の内容を抽出します。 - 正規表現などを用いて、不要な定型句やマークアップの残骸をクリーニングします。
- Wiki マークアップの場合は、専用のパーサーライブラリを利用することもあります。
- HTML パーサー (
-
チャンキング (Chunking):
- 目標: 整形された長い本文テキストを、ベクトル化に適した、意味的にまとまりのある短い単位(チャンク)に分割します。
-
方法:
-
単純な分割: 固定文字数、固定トークン数、文(
.
や。
)ごとなどで分割します。シンプルですが、意味的な区切りとずれる可能性があります。 -
再帰的文字分割 (Recursive Character Text Splitting):
LangChain
などのフレームワークでよく使われる手法。まず大きな区切り文字(例:\n\n
段落)で分割し、チャンクが長すぎる場合は次に小さな区切り文字(例:\n
改行)、さらに文 (。
,.
)、単語 ( - 意味的チャンキング (Semantic Chunking): Embedding モデルを使って文や段落間の意味的な類似度を計算し、意味が変わる箇所で分割する高度な手法もあります。
-
単純な分割: 固定文字数、固定トークン数、文(
- 考慮点: チャンクのサイズ(大きすぎると情報が薄まり、小さすぎると文脈が失われる)や、チャンク間のオーバーラップ(文脈維持のためにチャンクの境界を少し重ねる)を調整します。
-
ベクトル化 (Embedding):
- 目標: 分割した各チャンクを、意味情報を保持した数値ベクトル(Embedding)に変換します。
-
方法:
- Hugging Face Hub で公開されているような事前学習済みの Embedding モデル(例:
sentence-transformers/all-MiniLM-L6-v2
(英語)、intfloat/multilingual-e5-large
(多言語)、cl-tohoku/bert-base-japanese-whole-word-masking
(日本語、ファインチューニングが必要な場合も) など)を使用します。 -
transformers.js
(今回使用)、sentence-transformers
(Python)、transformers
(Python) などのライブラリを使って、各チャンクテキストを入力し、ベクトル出力を得ます。
- Hugging Face Hub で公開されているような事前学習済みの Embedding モデル(例:
-
ベクトルデータベースへの格納 (Storing in Vector DB):
- 目標: 生成したベクトルと、それに対応するチャンクテキスト、およびメタデータをベクトルデータベースに効率的に格納し、高速な類似度検索を可能にします。
-
方法:
- Vector DB の選択: 用途や規模に応じて適切なベクトルデータベースを選びます (例: Pinecone, Weaviate, Qdrant, Milvus, Chroma DB など)。ローカルで試せるものから、クラウドサービスまで様々です。
-
データの登録: 各 Vector DB が提供するクライアントライブラリや API を使用して、以下の情報をセットで登録します。
- ベクトル: Embedding モデルから得られた数値配列。
- コンテンツ (ペイロード): 対応するチャンクの本文テキスト。検索後に内容を確認したり、LLM に渡したりするために必要です。
- メタデータ: 元のドキュメント名、URL、チャンク番号、タイトル、カテゴリなど、検索結果のフィルタリングや情報源の提示に役立つ情報。
- インデックス作成: Vector DB は登録されたベクトルに対して、効率的な近似最近傍探索 (ANN) を行うためのインデックスを内部で構築します。
この一連のパイプラインを経ることで、Wikipedia のような大規模な非構造化テキストデータから、質問応答に適した形式で情報を抽出し、ベクトルデータベースに格納することができます。今回の実装では、特にチャンキングと Vector DB への格納部分を大幅に簡略化(ドキュメント単位、インメモリキャッシュ)した形になります。

はい、ご指摘の通り Redis と PostgreSQL にもベクトル検索機能を追加するための拡張機能やモジュールが存在します。これらを利用して今回のデータを保存・検索する方法の概要は以下のようになります。
1. PostgreSQL と pgvector
を使う場合:
-
準備:
- PostgreSQL サーバーを用意し、
pgvector
拡張機能をインストール・有効化します (CREATE EXTENSION vector;
)。 -
pgvector
に対応した Node.js クライアント (例:pg
) や、Prisma のプレビュー機能などを利用します。(Prisma のpgvector
ネイティブサポートはまだ進化途上かもしれません)
- PostgreSQL サーバーを用意し、
-
テーブル定義:
- ドキュメントチャンクとベクトルを格納するテーブルを作成します。スキーマ例:
CREATE TABLE wiki_docs ( id SERIAL PRIMARY KEY, chunk_text TEXT, doc_title TEXT, doc_url TEXT, chunk_order INTEGER, embedding vector(384) -- ベクトルの次元数を指定 (e.g., multilingual-e5-small は 384次元) );
- ドキュメントチャンクとベクトルを格納するテーブルを作成します。スキーマ例:
-
データ投入:
- Wikipedia データを取得し、意味のある単位にチャンキングします (今回の手動分割よりさらに細かく、自動化するのが理想)。
- 各チャンクテキストを
transformers.js
でベクトル化します。 - PostgreSQL クライアントを使って、チャンクテキスト、メタデータ (タイトル、URL、順序など)、そして計算したベクトルを
wiki_docs
テーブルにINSERT
します。ベクトルはpgvector
が期待する形式(例:'[0.1, 0.2, ...]'
という文字列や配列)で渡します。
-
インデックス作成 (性能向上のため推奨):
- ベクトル列に対して
pgvector
が提供するインデックスを作成します (例:HNSW
やIVFFlat
)。距離計算方法 (コサイン類似度など) も指定します。
CREATE INDEX ON wiki_docs USING hnsw (embedding vector_cosine_ops);
- ベクトル列に対して
-
検索:
- ユーザーの質問を同じモデルでベクトル化します。
- SQL クエリで
pgvector
の類似度検索演算子 (<=>
はコサイン距離) を使って検索します。
これにより、質問ベクトルに最も近いチャンクが取得できます。SELECT id, chunk_text, doc_title, embedding <=> '[query_vector]'::vector AS distance FROM wiki_docs ORDER BY distance ASC -- コサイン距離なので昇順 (類似度は 1 - distance) LIMIT 5;
2. Redis と RediSearch
(Vector Search) を使う場合:
-
準備:
- Redis サーバーを用意し、
RediSearch
モジュールをロードします。 - Node.js 用の Redis クライアント (例:
redis
) を利用します。
- Redis サーバーを用意し、
-
インデックス定義:
-
FT.CREATE
コマンドでインデックススキーマを定義します。メタデータ用のTEXT
やTAG
フィールド、そしてベクトル用のVECTOR
フィールドを指定します。VECTOR
フィールドには、アルゴリズム (FLAT, HNSW)、次元数、データ型 (FLOAT32)、距離計量 (COSINE) を指定します。
-
-
データ投入:
- データをチャンキングし、各チャンクをベクトル化します。
- 各チャンクについて、Redis の Hash データ構造を使ってメタデータ、チャンクテキスト、そしてベクトルデータを格納します。ベクトルデータは
Buffer
(Float32Arrayなどから変換) として格納する必要があります。 -
HSET
コマンドなどでデータを登録します。
-
検索:
- 質問をベクトル化します。
-
FT.SEARCH
コマンドとKNN
(K-Nearest Neighbors) クエリを使って類似ベクトルを検索します。
これにより、指定したベクトル (FT.SEARCH idx:wiki_docs "*=>[KNN 5 @embedding $query_vec AS score]" PARAMS 2 query_vec <query_vector_buffer> RETURN 3 chunk_text doc_title score
$query_vec
) に最も近い5つのドキュメント(のチャンクテキストやタイトル、スコア)が返されます。
現在の実装との主な違い:
-
専用機能の利用:
pgvector
やRediSearch
といった専用の拡張/モジュールが必要です。 - データ構造: メモリ上の Map ではなく、PostgreSQL のテーブルや Redis の Hash に、ベクトルデータを含めて永続化します。
- インデックス: パフォーマンスのために専用のベクトルインデックスを利用します。
- クエリ方法: SQL の特殊な演算子や Redis の専用コマンドを使います。
- 複雑性: SQLite とインメモリキャッシュに比べて、セットアップや管理の複雑性が増します。
今回のプロジェクトではこれらの導入はスコープ外ですが、より本格的な RAG システムを構築する際には、このようなベクトルデータベース機能を持つシステムを利用するのが一般的です。