
ローカルLLM(Gemma3)にRAGを組み合わせた記事生成パイプラインを用いて、RAGあり・RAGなしの出力を比較するためのレポートです。
実装の説明や前回からの差分も記載します。
※出力した内容は必ずしも事実であるとは限りません。
今回の記事生成パイプラインは以下のステップで動作します。
生成した内容をChatGPTに評価させ、精度の違いを検証します。
ChatGPTへの指示は「下記の①と②の文章のファクトチェックをした上で回答精度の比較評価をしてださい」です。
モデル: gemma3:4b
wikipediaダンプ: jawiki-latest-pages-articles.xml.bz2 04-Mar-2026 01:54 4592085011
リポジトリ: https://github.com/YKZ-J/ja-wikipedia-rag
- どこを直したか
- mcp_server.py に検索クエリ正規化と再ランキングのガードを追加・変更。
- 主な実装内容(技術的要点)
- 「根拠トークン」導入
- ユーザー質問本文とルールベースのシードから質問に根拠のあるトークン集合を抽出する関数
_collect_grounding_tokens()を追加。- グラウンディング判定とサニタイズ
- 候補語が質問に十分アンカーされているか判定する
_is_grounded_term()、複合語を質問根拠ベースで間引く_sanitize_grounded_phrase()を追加。これにより Gemma が出力する幻覚的固有名詞(例: シャルル・ジョゼフ・ジダン)の混入を排除します。- Gemma 出力の安全化とルールフォールバック併用
_extract_search_queries_with_gemma()内で、Gemma 生成候補を grounding トークンでフィルタリングし、不十分な候補は捨てる/ルールベース候補を後から補完する処理を組み込みました。- ベクトル/タイトル候補の扱い改善
- vector/title の候補生成時に上記サニタイズ判定を通過した語のみ採用。最後にルールベースの候補を追加入力して取りこぼしを減らす仕組みを追加しました。
- 正規表現・閾値の追加
_RE_GROUNDED_TOKENなどの正規表現と、部分一致での採用割合閾値(部分一致率 >= 0.5)などのヒューリスティックを導入。- 期待される効果
- Gemma が出力する「質問に根拠の無い固有名詞」を検索クエリ候補に含めないので、無関係記事(例: ジダンダ!)が上位に来る誤動作を抑制。
- それでも Gemma が有効な候補を出さない場合は、安定したルールベース候補で補完するため、汎用性と堅牢性が改善します。
MCPサーバー構築実践ガイド
このドキュメントは、Bun(TypeScript) クライアントから Python FastMCP サーバーを呼び出す MCP 構成を、ゼロから作れるレベルで解説します。
対象構成:
クライアント: Bun + TypeScript
サーバー: Python + FastMCP
LLM: Ollama(ローカル)
検索基盤: Supabase(PostgreSQL + pgvector)
1. MCPとは何か(このプロジェクトでの役割)
MCP は「LLMに渡すツール実行インターフェース」を標準化するためのプロトコルです。
このプロジェクトでは次の役割を持ちます。
TypeScript から Python を安全に呼ぶ
LLM処理(生成・要約・RAG)を tool 単位で分離する
ツール入出力を構造化し、CLI から再利用しやすくする
実行フロー:
kbコマンド実行TypeScript クライアントが Python FastMCP を stdio 起動
callToolでgenerate_doc/summarize/rag_askを実行Python が Ollama / DB を呼び出して結果を返す
2. 使うライブラリ
2.1 TypeScript側
@modelcontextprotocol/sdkMCP クライアント本体
使う主なクラス:
Client,StdioClientTransport
zodHTTP 経由の入力バリデーション
gray-matterMarkdown frontmatter の読み書き
unified,remark-parse,unist-util-visitMarkdown AST の解析
インストール例:
bun add @modelcontextprotocol/sdk zod gray-matter unified remark-parse unist-util-visit2.2 Python側
mcp(FastMCP)Python MCP サーバーを実装
FastMCP(...),@mcp.tool()を利用
llama-cpp-python
.ggufモデルをローカル推論
aiohttpOllama API 呼び出し(
/api/chat,/api/generate,/api/embed)
asyncpgPostgreSQL 非同期アクセス
python-dotenv
.env.localから設定ロードインストール例:
source .venv/bin/activate pip install mcp llama-cpp-python aiohttp asyncpg python-dotenv3. 最小MCPサーバーを作る(Python)
ファイル例:
python/mcp_server.pyfrom mcp.server.fastmcp import FastMCP mcp = FastMCP("kb-llm-server") @mcp.tool() def summarize(prompt: str) -> str: # 実際はここで LLM を呼ぶ return f"要約結果: {prompt[:80]}" if __name__ == "__main__": mcp.run()ポイント:
FastMCP("server-name")でサーバー名を定義
@mcp.tool()を付けた関数が公開ツールになる引数・戻り値はシンプルな型(
str,int,boolなど)を基本にする4. Bunクライアントを作る(TypeScript)
ファイル例:
src/infrastructure/llm/llama-bridge.tsimport { Client } from "@modelcontextprotocol/sdk/client/index.js"; import { StdioClientTransport } from "@modelcontextprotocol/sdk/client/stdio.js"; const transport = new StdioClientTransport({ command: "python3", args: ["python/mcp_server.py"], env: process.env as Record<string, string>, }); const client = new Client({ name: "kb-llm-client", version: "1.0.0" }); await client.connect(transport); const result = await client.callTool({ name: "summarize", arguments: { prompt: "テスト入力" }, });ポイント:
Python を stdio で起動するため運用が簡単
Clientは毎回作らずシングルトン化すると安定複数同時
callToolはタイムアウト要因になるため、キュー直列化が安全5. 実運用向けに必要な設計
5.1 ツール分割
このリポジトリでは下記の3ツールに分割しています。
generate_doc: 長文生成 + Markdown保存
summarize: 要約・単発応答
rag_ask: 検索 + 回答生成分割する理由:
タイムアウト設定を用途別に最適化できる
障害点を切り分けやすい
テストしやすい
5.2 タイムアウト管理
TypeScript 側で tool ごとにタイムアウトを分けます。
例:
KB_RAG_ASK_TIMEOUT_MS=300000
KB_SUMMARIZE_TIMEOUT_MS=300000
KB_GENERATE_DOC_TIMEOUT_MS=1800005.3 タイムゾーン
日付出力は
Asia/Tokyo固定で統一します。from datetime import datetime from zoneinfo import ZoneInfo today = datetime.now(ZoneInfo("Asia/Tokyo")).strftime("%Y-%m-%d")6. RAGツールの作り方(実装手順)
rag_askを実装する基本手順:
ユーザー質問から検索クエリ候補を抽出
埋め込みモデルでベクトル化(Ollama embed API)
PostgreSQL の
search_documentsRPC で候補取得タイトル一致検索を補助シグナルとして追加
再ランキングして上位記事を決定
参照本文をコンテキスト化して LLM に投入
回答・検索クエリ・ランキングを Markdown 保存
検索品質の実装注意:
主軸検索クエリはユーザー原文を先頭固定
質問にない固有名詞が抽出された場合は破棄
タイトル一致は「境界一致」を優先し、部分一致ノイズを減点
7. 設定ファイルと環境変数
必須:
MODEL_PATH:.ggufファイルパス
DATABASE_URL: Supabase/PostgreSQL 接続文字列
VAULT_PATH: 出力ディレクトリ推奨:
RAG_DB_MAX_CONCURRENCY
RAG_DB_QUERY_TIMEOUT_SEC
RAG_MAX_CONTEXT_CHARS
RAG_MIN_CONTEXT_CHARS
RAG_QUERY_NORMALIZATION_NUM_PREDICT8. 具体的な起動手順
bun install source .venv/bin/activate pip install -r requirements.txt # Supabase / DB 起動 docker compose up -d supabase start # TypeScript MCP サーバー起動 bun run src/interface/http/mcp-server.ts確認:
kb search "テスト"が動く
kb ask-wiki "テスト"が動く
vault/に生成ファイルが保存される9. よくある失敗と対策
9.1
Unable to connect原因:
Python MCP が起動していない
Ollama が停止している
環境変数不足
対策:
Ollama の待受確認
MODEL_PATH/DATABASE_URLの確認Python プロセス再起動
9.2 検索精度が悪い
原因:
抽出クエリへの幻覚語混入
タイトル部分一致ノイズ
RPC 側 LIMIT 不足
対策:
クエリ抽出に「質問語アンカー」検証を入れる
タイトル境界一致を優先する
search_documentsの LIMIT / インデックス設計を見直す9.3 生成が途中で切れる
原因:
num_predict不足対策:
done_reason=lengthを検知
num_predictを増やして1回だけ再試行10. 実装完了条件
実装変更後は必ず次を実行します。
bun run lint bun run test bun run build3つすべて成功したら完了です。
総論
結論から言うと、①・②ともに重大な事実誤認が多く、質問「ジダン(一般に ジネディーヌ・ジダン)について」に正確に答えられていません。
ただし誤りの種類が異なり、①は「対象の取り違え」、②は「事実の捏造・誤記」が中心です。
①のファクトチェック
❌ 主な誤り
対象が間違っている
- 質問は「ジダン本人」なのに、回答は息子(ルカ、テオ)について説明している
名前の誤り
- 「ルカ・ジネディーヌ・ジダン・フェルナンデス」という表記は不正確(通常はルカ・ジダン)
家族構成の誤り
- 「エンツォ・フェルナンデス」は誤り
→ 正しくは「エンツォ・ジダン」プレースタイルの遺伝的説明
- 「遺伝的に受け継がれている」という表現は科学的に不正確で誇張
細かい経歴
所属クラブや移籍の一部は概ね近いが、時系列や正確性に不安あり
✔ 一部正しい点
ルカ・ジダンやテオ・ジダンがプロ選手であること
レアル・マドリード下部組織出身という点
評価
質問への適合性:ほぼゼロ
事実正確性:中程度(部分的に正しいが混乱)
②のファクトチェック
❌ 主な誤り(かなり重大)
出生地の誤り
「パリ」→ ❌
正しくはマルセイユ
クラブ名の誤り
「モナン・バス」→ ❌(存在しない)
正しくはカンヌ(AS Cannes)
移籍経歴の誤り
レアル・マドリード加入は2001年
1992年ではない
ボルドー表記の誤り
- 「ordeaux=Bordeaux」→表記崩壊
チャンピオンズリーグ決勝の誤り
対戦相手はユヴェントス(1998年)
ACミランではない
ゴール内容の誤り
- 延長戦ではなく、ボレーシュート(有名な左足ボレー)
プレースタイルの誤り
- 左サイドのドリブラーではなく、主にトップ下の司令塔
ワールドカップMVP
- 1998年は受賞していない(受賞はロナウド)
ドーピング・禁固刑
完全な事実無根(重大な誤情報)
✔ 一部正しい点
フランス代表でW杯・EURO優勝に貢献した点
評価
質問への適合性:高い(本人について書いている)
事実正確性:非常に低い(致命的な誤り多数)
比較評価
観点 ① ② 質問への適合性 ❌(別人物) ⭕(本人) 事実正確性 △(部分的に正しい) ❌(重大な誤り多数) 信頼性 低い 非常に低い 致命的問題 主題の取り違え 事実の捏造レベルの誤り
最終結論
①の方がまだマシ(事実の破綻は少ないが、質問に答えていない)
②は危険なレベルで不正確(誤情報が多く、学習用途に不適)
補足(正しい方向性)
本来説明すべき内容は以下です:
フランス代表の司令塔
1998年W杯優勝(決勝で2得点)
2000年EURO優勝
クラブではユヴェントス → レアル・マドリード
2002年CL決勝のボレーゴール
2006年W杯決勝の頭突き事件
必要なら「正しい1000文字解説」も作れます。
質問
サッカー選手のジダンについて1000文字程度で説明して
回答
サッカー選手のジダンは、ルカ・ジネディーヌ・ジダン・フェルナンデスとテオ・ジダン・フェルナンデスという2人の兄弟として知られる、ジネディーヌ・ジダン氏の血を引く選手たちです。それぞれの選手について、その経歴、プレースタイル、そして家族背景を紐解き、サッカー界における彼らの存在意義を明らかにします。 ルカ・ジダンについてルカ・ジダンは、1998年5月13日生まれのゴールキーパーとして、ラ・リーガ2部・グラナダに所属しています。父親であるジネディーヌ・ジダン氏のプレッシャーを背負いながら、レアル・マドリードのユースアカデミーに入団。当初は母方の姓であるフェルナンデスと登録されましたが、2019年3月31日のSDウエスカ戦で父・ジネディーヌ氏が監督としてチームを率いる中、先発としてデビューを果たし、トップチーム初勝利に貢献しました。その後、ラシン・サンタンデールへのレンタル移籍を経て、ラージョ・バジェカーノ、セグンダ・ディビシオンのラシン・サンタンデールでプレーし、2022年9月1日にはSDエイバルに加入。2024年7月5日には、3年契約でグラナダに移籍しました。ルカ・ジダンは、父親の影に隠れることなく、自身のキャリアを築き上げていく過程が注目されています。 テオ・ジダンについてテオ・ジダンは、2002年5月18日生まれのミッドフィールダーとして、コルドバCFに所属しています。マルセイユ出身のジネディーヌ・ジダン氏の三男であり、エンツォ・フェルナンデス、ルカ・ジダン、エルヤズ・ジダンという3人の兄弟と共に、レアル・マドリードの下部組織で育ちました。2021年2月、トップチームに昇格し、父親が監督を務めるチームでトレーニングを経験。そのプレースタイルは、父親のジネディーヌ・ジダン氏と同じく、ボックス内で様々なレパートリーを持つ素晴らしいフィニッシャー、ゴールスコアラー、スピードを兼ね備えていると評されています。2020-21シーズンからセグンダ・ディビシオンBでプレーし、2021-22シーズンにはプリメーラ・ディビシオンRFEFで活躍。2023-24シーズンにはプリメーラ・フェデラシオンでもプレーしています。テオ・ジダンは、父親のレガシーを受け継ぎながら、自身の才能を最大限に発揮しようと努力する若手選手として、今後の活躍が期待されています。 家族背景と共通点ジダン家は、サッカー界において非常に特別な存在です。ジネディーヌ・ジダン氏の卓越したプレースタイルは、その息子たちにも遺伝的に受け継がれています。エンツォ、ルカ、テオの3兄弟は、全員がプロサッカー選手として活躍しており、その才能は世代を超えて受け継がれています。特に、テオとルカは、父親のプレッシャーを背負いながらも、自身のキャリアを築き上げていく過程が注目されています。また、マルセイユ出身であること、そしてレアル・マドリードの下部組織で育ったことなど、共通点も多く、ジダン家はサッカー界において、その存在感を増していくことでしょう。 これらの情報を総合的に見ると、ジダン家は、サッカー界において、その才能と血統によって、特別な存在であると言えます。ルカとテオの今後の活躍が、ジダン家のさらなる成功に繋がることを期待するとともに、サッカー界における新たなスターの誕生を見守る必要があります。
検索クエリ(実際に使用)
サッカー選手のジダンについて1000文字程度で説明して参照元 Wikipedia 一覧
- 【ルカ・ジダン】
- 【テオ・ジダン】
質問
サッカー選手のジダンについて1000文字程度で説明して
回答
- 略語は避ける * 選手としてのキャリア * プレースタイルと特徴 * 代表での活躍 * オフフィールドでの出来事 回答: サッカー界における偉大な選手の一人、ジダンは、フランスのパリで生まれ育ちました。彼の選手としてのキャリアは、1989年に地元のクラブであるモナン・バスに加入し、その後、ordeaux=Bordeauxに移籍しました。そして、1992年にはレアル・マドリードへ移籍し、その後のキャリアにおいて、サッカー史に残る伝説的な存在となりました。 選手としてのキャリア レアル・マドリードでは、1994年から2006年までプレーし、クラブの歴史上でも特別な選手として記憶されています。特に、1997-98シーズンのチャンピオンズリーグにおいては、決勝でACミランと対戦し、延長戦で劇的な決勝ゴールを決め、チームを優勝に導きました。このゴールは、サッカー史に残る名場面の一つとして語り継がれています。 プレースタイルと特徴 ジダンの最大の魅力は、彼の独特なプレースタイルでした。彼は、左サイドからのドリブル突破を得意とし、相手ディフェンダーを翻弄するその技術は、多くのファンを魅了しました。また、彼は、正確なパスで味方にチャンスを作り出す能力も持ち合わせており、チームの攻撃において重要な役割を果たしていました。さらに、彼の卓越したテクニックに加え、高い運動量と戦術眼も兼ね備えており、多岐にわたる能力でチームを勝利に導きました。 代表での活躍 フランス代表としても、ジダンは長年にわたり活躍しました。1998年のワールドカップでは、大会MVPを受賞し、フランス代表を優勝に導く立役者の一人となりました。また、2000年のヨーロッパ選手権でも、フランス代表を優勝に導きました。彼の存在は、フランスサッカーの発展に大きく貢献しました。 オフフィールドでの出来事 しかし、ジダン選手は、選手としてのキャリアを通じて、いくつかの困難な時期も経験しました。2014年に、ドーピング問題で禁固刑を受け、サッカー界に大きな衝撃を与えました。この事件後、彼は、自身の過ちを深く反省し、社会奉仕活動に積極的に参加するなど、新たな一歩を踏み出しました。 ジダンは、サッカー選手としてだけでなく、人間としても多くの人々に感銘を与えた存在です。彼の才能と努力、そして困難を乗り越える姿は、多くの人々の心に深く刻まれています。
事実性が高いのは①(RAGあり)の方ですが質問対する回答として不適切な内容になりました。 RAGによって情報ソースを参照できるため正確性と具体性が向上しますが適切な情報ソースを取得しないと文脈に沿った回答にならない。