多言語RAGパイプラインの構築・活用を直接実習するために構築したトイプロジェクトを逆分析した手法ドキュメント。
四柱推命(四柱八字)を主題に選定した理由は、四柱推命が統計に基づいた分析 + 韓国語・中国語・英語の原文が自然に混在しており、多言語チャンキング/埋め込み/検索のすべての問題を一つのプロジェクトで経験できるからである。
すべての実験と運営はローカル環境(M1 Mac)で遂行され、外部サービスへの依存なしに完結したパイプラインである。
このドキュメントは概要と4つの詳細ドキュメントで構成される。
| ドキュメント | 内容 | 核心的な問い |
|---|---|---|
| 本ドキュメント (概要) | 用語解説、背景、アーキテクチャ全体像、設計原則 | RAGとは何か、なぜこのように作ったのか? |
| Part 1: データ収集・精製・チャンキング | データソース階層、精製ルール、チャンキング戦略、埋め込みモデルのバグ発見 | 原本データをどのように検索可能な単位にするのか? |
| Part 2: 埋め込み・インデックス・検索 | Dense/BM25二重インデックス、ハイブリッド検索、適応型重み、多言語クエリ拡張 | インデックスをどのように作り、検索はどのように動作するのか? |
| Part 3: コーパス管理・自動強化・エージェント | コーパス劣化の類型、最新化戦略、自動強化サイクル、LLMエージェント活用 | コーパスをどのように最新状態に維持し、LLMがどのように活用するのか? |
| Part 4: 拡張・Production・IT適用 | 10K→1M+ スケーリング、段階別構築オプション、IT企業のKnowledge Base適用 | これを実際のサービスにどのように適用するのか? |
このドキュメントで繰り返し登場する核心用語をまず整理する。RAGや埋め込みの概念に慣れている場合は読み飛ばしてもよい。
LLM(大規模言語モデル)が回答を生成する前に、関連ドキュメントをまず検索してその内容を参考にさせる手法。LLM単独では学習データにない、あるいは不正確な知識を「ハルシネーション(幻覚)」として作り出してしまう問題がある。RAGは「検索した原文を根拠に答えよ」と強制することで、この問題を緩和する。
一般のLLM: ユーザーの質問 → LLM → 回答 (学習データに依存、ハルシネーションのリスク)
RAG: ユーザーの質問 → [検索] → 関連ドキュメント発見 → LLM + ドキュメント → 回答 (根拠あり)
RAGが検索対象として使用するドキュメントの集合全体を指す。このプロジェクトでは、四柱推命関連のPDF、マークダウン、テキストファイル593個がコーパスを構成する。コーパスの品質と範囲がRAG性能の上限を決定する。いくら検索アルゴリズムが良くても、コーパスに該当する情報がなければ見つけることはできない。
長いドキュメントを検索に適した**小さな単位(チャンク)**に分割する過程。本一冊(数万文字)を丸ごと検索すると、どの部分が関連しているか分からなくなるため、300〜500文字程度の意味単位で区切って、それぞれを独立した検索対象にする。大きく切りすぎると検索精度が落ち、小さく切りすぎると文脈が失われる。
テキストを**数値ベクトル(数百個の小数点数値の配列)**に変換すること。「甲木の性格は真っ直ぐで強い」という文章が [0.12, -0.34, 0.56, ...] のような384個の数値の配列になる。意味が似ている文章は似たベクトルを持つようになるため、ベクトル間の距離を計算すれば意味的な類似度を数値で比較できる。これが「意味検索」の原理である。
伝統的なキーワードベースの検索アルゴリズム。ドキュメントにクエリ単語が何回出てくるか(TF)、その単語がどれほど稀少か(IDF)を組み合わせてスコアを付ける。「甲木」を検索すると、正確に「甲木」という文字があるドキュメントを探す。埋め込みが「意味」で探すものなら、BM25は「文字」で探すものである。両者を合わせれば(ハイブリッド検索)、それぞれの弱点を補完できる。
BM25(キーワードマッチング)とDense Embedding(意味検索)の結果を合算して最終順位を付ける方式。例えば「甲木の性格」を検索すると:
1次検索で候補を絞り込んだ後、より精密なモデルで再度順位を付ける2段階検索。1次(BM25+Dense)は高速だが大まかであり、2次(Cross-Encoder)は低速だが精密である。クエリとドキュメントを同時に入力して直接関連性を判断するため、1次検索よりもはるかに正確な順位を作る。ただし、コーパス全体に適用すると遅すぎるため、1次で20〜40個の候補を絞り込んだ後、それらにのみ適用する。
埋め込みベクトル間の距離(類似度)を計算して、最も近いベクトルを探すこと。このプロジェクトでは、9,586個のチャンクのベクトルとクエリベクトルの内積(dot product)を計算する。規模が大きくなれば、FAISSやPineconeのような専用のベクトルデータベースを使用して、近似最近傍(ANN)検索で速度を確保する。
検索結果から上位K個だけを返すという設定値。top-k 5であれば、最も関連性の高い5つのチャンクだけを持ってくる。Kが小さすぎると関連情報を見逃し、大きすぎると関連のないノイズが混ざってLLMが混乱する。このプロジェクトでは、CLI単一検索時に top-k 5、RAGバッチ検索時に top-k 3を基本として使用する。
テキストを検索可能なトークン(単語/形態素)単位に分離するツール。韓国語は「사주명리학을」を「사주/명리/학」に分離する形態素解析が必要であり、中国語は分かち書きがないため「四柱八字」を「四柱/八字」に分節しなければならず、英語は空白で分ければほとんど解決する。各言語に合ったトークナイザーを使用することでBM25の検索品質が向上する。
LLMは四柱推命のように体系的だが非主流な専門知識においてハルシネーションが激しい。「甲木(甲木)の日干が寅月(寅月)に生まれた時の用神は?」と聞くと、それらしいが間違った答えを自信満々に出してくる。古典原文(滴天髄、窮通宝鑑)に基づいた正確な解釈が必要な領域において、LLM単独では信頼できない。
これは四柱推命だけの問題ではない。サービス企画書、特定のフレームワークの最新API、法律の判例、医療ガイドラインなど、LLMの学習データに十分に反映されていないすべてのドメインで同様の問題が発生する。
四柱推命は本質的に多言語ドメインである:
| 言語 | 役割 | 例示 |
|---|---|---|
| 中国語 | 原典・古典テキスト | 滴天髄, 窮通宝鑑, 子平真詮 |
| 韓国語 | 現代の解釈・教材・実戦事例 | 格局論, 用神論, 実戦100構文 |
| 英語 | 学術論文・ML研究 | BaZi career prediction, AI character simulation |
一つのクエリ「甲木 寅月 用神」が韓国語の教材、中国語の原典『窮通宝鑑』、英語の論文を同時に検索して初めて完全な回答を構成できる。これはIT企業において、英語の技術文書 + 韓国語の企画書 + 日本語のパートナーAPIドキュメントを統合検索しなければならない状況と構造的に同一である。
┌─────────────────────────────────────────────────────────┐
│ 0段階: データ収集・精製 │
│ 原本PDF/MD/TXT → 構造化マークアップ (--- 区切り線, 出典表記) │
│ 重複検査 → 出典検証 → books/ ディレクトリに配置 │
│ → 詳細: Part 1 │
└───────────────┬─────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────┐
│ 1段階: チャンキング (chunk.py) │
│ PDF→PyMuPDF / TXT→直接読み込み │
│ 言語検知 → 段落境界を尊重 → 512文字チャンキング (64文字 overlap) │
│ 出力: chunks.jsonl (9,586個のチャンク, 4.16MB) │
│ → 詳細: Part 1 │
└───────────────┬─────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────┐
│ 2段階: インデックス作成 (embed.py) │
│ │
│ ┌──────────────────┐ ┌──────────────────────┐ │
│ │ Dense Embedding │ │ BM25 Sparse Index │ │
│ │ MiniLM-L12-v2 │ │ 韓国語: Kiwi POS │ │
│ │ 384次元, L2 正規化 │ │ 中国語: jieba 分節 │ │
│ │ → embeddings.npy │ │ 英語: regex+stopwords │ │
│ │ (14MB) │ │ → bm25_index.pkl │ │
│ └──────────────────┘ └──────────────────────┘ │
│ → 詳細: Part 2 │
└───────────────┬─────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────┐
│ 3段階: ハイブリッド検索 (search.py) │
│ │
│ クエリ → 言語検知 → 多言語トークン拡張(韓↔漢 143個のキー) │
│ → BM25 top-K ∪ Dense top-K 候補 (K=max(20,top_k×4)) │
│ → (オプション) bge-reranker-v2-m3 リランキング │
│ → ソース多様性ペナルティ → top-K 返却 │
│ → 詳細: Part 2 │
└───────────────┬─────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────┐
│ 4段階: LLMエージェント活用 + コーパス管理 │
│ │
│ Claude Code Skill オーケストレーター │
│ → 5つのサブエージェントを並列実行 │
│ → 各エージェントが search.py で RAG 検索 │
│ → 原文引用に基づいた分析レポート生成 │
│ → 自動ギャップ分析 → 収集 → リビルドサイクル │
│ → 詳細: Part 3 │
└─────────────────────────────────────────────────────────┘
| 項目 | 数値 |
|---|---|
| ソースドキュメント | 593個 (PDF + MD + TXT) |
| 総チャンク数 | 9,586個 |
| 総テキスト容量 | 約416万文字 (chunks.jsonl ファイルサイズ 10.4MB) |
| 平均チャンクサイズ | 434文字 |
| Dense 埋め込み | 9,586 × 384 float32 = 14MB |
| BM25 インデックス | 14.7MB (bm25_index.pkl 7.1MB + tokenized_corpus.pkl 7.6MB) |
| 言語分布 | 韓国語 80.5% / 中国語 14.2% / 英語 5.3% |
このプロジェクトで検証された、ドメインや規模に関わらず適用できる原則を整理する。
専門ドメインではBM25(キーワード) + Dense(意味)を必ず併用する。固有名詞、略語、コード名、APIエンドポイントなど、正確な文字マッチングが必要な用語があるドメインにおいて、Dense単独は危険である。Denseは「意味的に似ている」的外れな結果を持ってくることがあり、BM25は正確なキーワードがなければ何も見つけられない。両者を合わせる必要がある。
類義語、略語、多言語対応テーブルを構築すれば、検索再現率(recall)が劇的に向上する。このプロジェクトの143個のキー(209ペア)の韓↔漢マッピングがその例である。法律ドメインなら「不法行為 ↔ 違法行為 ↔ tort」、ITなら「K8s ↔ Kubernetes ↔ クーバネティス」、医療なら「糖尿病 ↔ diabetes mellitus ↔ DM」のようなテーブルを構築する。
固定サイズでのチャンキングは簡単だが、意味単位が切れると検索品質が落ちる。--- 区切り線やマークダウンの見出し(##)のようなドキュメント構造を atomic boundary として活用するのが効果的である。そしてこれが動作するためには、データ収集段階ですでに構造化されていなければならない。 チャンキングの品質はデータ精製の品質の反映である。
Stage 1でBM25+Denseで候補を広く取り、Stage 2でCross-Encoderで精密な順位を付ける2-stageパイプラインは検索品質を大きく向上させる。候補40個をリランキングするコストは微々たるものだが、精度の向上は体感できる。
静的なコーパスというものは存在しない。「ギャップ検知 → 資料収集/更新 → リビルド → 検証」を自動化してこそ、時間が経つにつれて品質が維持、あるいは向上する。手動管理は規模が大きくなれば必ず失敗する。
LLMに検索結果を渡す際、要約せずに原文をそのまま渡せ。要約の過程で情報が損失したり歪曲されたりする。LLMが原文を直接見て判断するようにしてこそ、引用の正確性が保証される。このプロジェクトでサブエージェントが「原文の全文引用」を強制する理由である。
| 構成要素 | このプロジェクトの実装 | Production 拡張オプション | 詳細ドキュメント |
|---|---|---|---|
| データ収集 | 手動 + saju-research-collector エージェント | Confluence API, Git ウェブフック, S3 イベント, Unstructured.io | Part 1 |
| データ精製 | --- 区切り線, 出典必須, AI生成禁止 |
HTML→MD 変換, PII マスキング, バージョン管理 | Part 1 |
| チャンキング | 512文字, 64文字 overlap, --- 境界尊重 |
LangChain TextSplitter, Semantic Chunking | Part 1 |
| 埋め込み | MiniLM-L12-v2 (384d, ローカル) | bge-m3, OpenAI embedding API, Cohere embed | Part 2 |
| BM25 | rank-bm25 (in-memory pickle) | Elasticsearch, OpenSearch, Weaviate 内蔵 | Part 2 |
| ベクトル検索 | numpy 内積 (14MB npy) | FAISS, Pinecone, Qdrant, pgvector | Part 2 |
| リランカー | bge-reranker-v2-m3 (ローカル) | Cohere Rerank, Jina Reranker, LLM リランキング | Part 2 |
| クエリ拡張 | 143個のキー(209ペア) 韓↔漢 辞書 | WordNet, ドメインオントロジー, LLM ベースの拡張 | Part 2 |
| コーパス管理 | 自動ギャップ→収集→リビルドサイクル | 増分同期, TTL 万了, ユーザーフィードバックループ | Part 3 |
| エージェント | Claude Code Skills (5+5 並列) | LangChain Agent, LlamaIndex Agent | Part 3 |
| スケーリング | numpy + pickle (9.6K チャンク) | FAISS → Qdrant → Pinecone | Part 4 |
| IT 適用 | — | 企画書 RAG, KB ボット, 障害対応 | Part 4 |
このシステムの核心的な価値は、「小さいが完全な」RAGパイプラインであるということだ。593個のドキュメント、9,586個のチャンク、4つのPythonスクリプト(chunk.py, embed.py, search.py, dedup.py)で構成されたこのシステムが、多言語ハイブリッド検索、リランキング、適応型重み、自動コーパス強化までをすべて含んでいる。
規模を拡大する際は、上の表の各構成要素を Production オプションに交換すればよい。設計原則とパイプライン構造は規模に関わらず同一に維持される。 変わるのはインフラであり、アーキテクチャではない。