金融特化型 RAG & 引用一致率監査エージェント システム詳細設計書
System Design Document (SDD) | 日本経済新聞社・法人向け金融情報サービス技術標準準拠
1. システム概要と設計思想
1.1 目的
本システム(financial-rag-agent)は、日本経済新聞社(Nikkei)等の大手経済メディアおよび金融機関における法人向け情報サービス(日経テレコン、日経Smart Work、Nikkei Compass 等)の次世代 AI コア基盤を想定して設計された、エンタープライズ品質の金融分析・引用監査エージェントである。
一般的な LLM アプリケーションが「プロンプトによる単発生成」にとどまるのに対し、本システムは以下のエンタープライズ要件を本番品質(Production Quality)で満たすことを設計思想としている:
- ゼロ・ハルシネーション(幻覚の完全排除): 金融取引や経営判断に直結する数値(売上高、営業利益、PER、ROE、金利等)に対する徹底した根拠帰属。
- 文単位の引用一致率監査(Sentence-level Citation Attribution): 生成された回答のすべての主張に対して根拠文献の Chunk ID を付与し、事後監査器で数理的一致度を検証。
- 標準プロトコルの採用(LangGraph & MCP): 独自のブラックボックス実装を排し、業界標準のステートマシン(LangGraph)およびツール連携プロトコル(Model Context Protocol)に完全準拠。
- ハイブリッド検索(Hybrid Search with RRF): 日本語特有の専門用語・財務数値を捉える BM25 と意味ベクトル検索を融合(Reciprocal Rank Fusion, $k=60$)。
- リアルタイムストリーミングとバッチ処理の双対性: 低遅延 SSE による対話型 UI と、決算集中時の夜間大量処理(非同期パイプライン)の両立。
2. 全体アーキテクチャ設計
2.1 システム構成図
システムは、クライアント利用層、FastAPI/Caddy ゲートウェイ層、LangGraph オーケストレーション層、検索・データ層、MCP ツール連携層、および Datadog APM 互換の可観測性層から構成される。
flowchart TD
subgraph Client_Layer ["クライアント・利用層"]
Browser["金融ターミナル風 Web UI\n(Dark / Light Theme, SSE Stream)"]
BatchTrigger["深夜適時開示バッチ\n(Nightly Disclosure Batch)"]
ExternalAPI["外部連携クライアント (REST / SSE)"]
end
subgraph Gateway_Layer ["ゲートウェイ・API層 (FastAPI / Caddy)"]
Caddy["Caddy Reverse Proxy\n(f.0101.click / HTTPS / No-Buffer SSE)"]
FastAPIApp["FastAPI Core App (ポート 8800)"]
Routes["/api/v1/chat/stream (SSE)\n/api/v1/chat/sync (REST)\n/api/v1/settings (Hot-Reload)"]
end
subgraph Orchestration_Layer ["LangGraph オーケストレーション層"]
StateGraph["StateGraph (AgentState)"]
Checkpointer["MemorySaver / State Persistence"]
Node_QR["1. Query Rewriter & Intent Classifier"]
Node_HS["2. Hybrid Search Engine"]
Node_MCP["3. MCP Tool Executor"]
Node_SYN["4. Grounded Synthesizer"]
Node_VER["5. Citation Verifier & Auditor"]
Edge_Retry{"引用一致率 < 70%\nかつ 再試行 < 2回 ?"}
end
subgraph Retrieval_Layer ["検索・データ層"]
CorpusDB[("日経新聞報道・JPX適時開示\nサンプルコーパス (data/)")]
BM25Engine["BM25 Okapi (CJK Bi-gram + 数値Token)"]
DenseEngine["Dense Vector Engine (384-dim 余弦類似度)"]
RRF["Reciprocal Rank Fusion (k=60)"]
ESAdapter["Elasticsearch 8.x Kuromoji\nマッピングアダプタ (拡張用)"]
DiscoveryAdapter["GCP Discovery Engine\n(Vertex AI Search) アダプタ (拡張用)"]
end
subgraph LLM_Layer ["LLM Provider フォールバック層"]
CLIProvider["Local AI CLI\n(claude / antigravity / opencode)\n既定・最優先・API Key不要"]
CloudProvider["Azure OpenAI / Vertex AI\n(opt-in 兜底)"]
TemplateFallback["テンプレート最終フォールバック"]
end
subgraph Tool_Layer ["MCP ツール連携層 (Model Context Protocol)"]
MCPServer["MCP Financial Server (FastMCP)"]
DualSource{"データソース切替\n(Snapshot vs Live API)"}
SnapDB[("2025/2026 決算・財務DB\n(PER/PBR/ROE/TSR/Trap)")]
LiveAPI["yfinance API クライアント\n(東証 .T 行情・金利・為替)"]
end
subgraph Observability_Layer ["可観測性・監査層"]
Telemetry["W3C TraceContext & JSON Telemetry"]
DatadogLogs["Datadog APM / OpenSearch 構造化ログ"]
AuditReport["引用監査レポート\n(適合率・再現率・一致率)"]
end
Browser --> Caddy
BatchTrigger --> FastAPIApp
ExternalAPI --> Caddy
Caddy --> FastAPIApp
FastAPIApp --> Routes
Routes --> StateGraph
StateGraph --> Node_QR
Node_QR --> Node_HS
Node_HS --> Node_MCP
Node_MCP --> Node_SYN
Node_SYN --> Node_VER
Node_VER --> Edge_Retry
Edge_Retry -- "Yes (再試行)" --> Node_SYN
Edge_Retry -- "No (PASS / 上限到達)" --> Checkpointer
Node_HS <--> BM25Engine
Node_HS <--> DenseEngine
BM25Engine & DenseEngine --> RRF
BM25Engine <--> CorpusDB
DenseEngine <--> CorpusDB
BM25Engine -.-> ESAdapter
RRF -.-> DiscoveryAdapter
Node_MCP <--> MCPServer
MCPServer --> DualSource
DualSource -- "snapshot" --> SnapDB
DualSource -- "live_api" --> LiveAPI
Node_SYN <--> CLIProvider
CLIProvider -. "CLI失敗 かつ 設定時のみ" .-> CloudProvider
CLIProvider -. "全Provider失敗時" .-> TemplateFallback
CloudProvider -. "失敗時" .-> TemplateFallback
Node_VER --> AuditReport
StateGraph -.-> Telemetry
Telemetry -.-> DatadogLogs
3. コアコンポーネント詳細設計
3.1 LangGraph マルチステップ状態遷移エンジン
配置: src/graph/workflow.py, src/graph/edges.py, src/graph/nodes/
データ契約 (AgentState): src/models/state.py
状態スキーマ (AgentState)
class AgentState(TypedDict):
query: str # ユーザー元入力
rewritten_query: str # 検索最適化済みクエリ
ticker: Optional[str] # 銘柄コード (4桁数字、例: "7203")
intent: str # クエリ意図 (company_analysis, macro, etc.)
data_source_mode: str # "snapshot" または "live_api"
retrieved_chunks: List[Dict] # RRF 統合検索結果 (上位K件)
mcp_tools_output: Dict[str, Any] # MCP 財務ツール実行結果
answer: str # 生成された回答(引用タグ含む)
citation_report: Dict[str, Any] # 引用一致率・適合率・再現率監査結果
retry_count: int # 幻覚検知時の再試行カウンタ (最大2回)
intermediate_steps: List[str] # 実行ログ・トレース用
各ノード(Node)の責務と内部処理
| ノード名 | 配置ファイル | 責務とアルゴリズム |
|---|---|---|
| query_rewriter | nodes/query_rewriter.py |
東証銘柄コード(例:「トヨタ」→「7203」)の抽出と検索意図(決算・マクロ等)の分類、最適化クエリの生成。 |
| hybrid_search | nodes/hybrid_search.py |
BM25 スパース検索と Dense 意味ベクトル検索を並行実行し、RRF (k=60) で順位を統合して上位 K 件を抽出。 |
| mcp_tool_runner | nodes/mcp_tool_runner.py |
抽出コードに基づき MCP ツール(PER、PBR、ROE、TSR、バリュートラップ判定)を呼び出し、状態に確定数値を注入。 |
| synthesizer | nodes/synthesizer.py |
検索文献 Chunk と財務指標を統合し、文単位で [doc_X] または [mcp_val_Y] 引用タグを付与した回答を生成。 |
| citation_verifier | nodes/citation_verifier.py |
文境界で分解し、引用先テキスト内に該当事実・数値が含まれているかを検証。一致率・適合率・再現率を算定。 |
条件付きエッジと自己修正ループ (src/graph/edges.py)
def should_retry(state: AgentState) -> str:
report = state.get("citation_report", {})
consistency_rate = report.get("consistency_rate", 1.0)
retry_count = state.get("retry_count", 0)
# 引用一致率が 70% 未満で、再試行回数が 2 回以内の場合は自己修正ループへ
if consistency_rate < 0.70 and retry_count < 2:
return "retry_synthesis"
return "finalize"
3.2 Model Context Protocol (MCP) アーキテクチャ
配置: src/mcp_server/server.py, src/mcp_server/client.py, src/mcp_server/tools/
目的: LLM エージェントと外部財務データベース/リアルタイム市場 API の接続を標準化し、強結合を防止。
| ツール名 | 引数 | 戻り値 | 概要 |
|---|---|---|---|
get_stock_valuation |
ticker: str, mode: str |
StockValuation |
指定銘柄の PER, PBR, ROE, TSR, 時価総額, 配当利回りを取得 |
check_value_trap |
ticker: str, mode: str |
ValueTrapCheck |
低PBR放置企業の「バリュートラップ」リスクを財務規律から自動判定 |
get_macro_indicators |
mode: str |
MacroIndicators |
日経平均、ドル円為替、日銀政策金利の最新ステータスを取得 |
get_recent_disclosures |
ticker: str |
List[Dict] |
JPX 適時開示・決算短信のサマリー一覧を取得 |
3.3 ハイブリッド検索 & RRF 融合エンジン
配置: src/retrieval/hybrid_retriever.py, src/retrieval/reranker.py
本番移行用アダプタ(同一インターフェース形状、ランタイム未接続の仕様定義層): src/retrieval/es_adapter.py(Elasticsearch 8.x Kuromoji + KNN), src/retrieval/discovery_engine_adapter.py(GCP Discovery Engine / Vertex AI Search のハイブリッド検索リクエスト構築・レスポンス正規化)。
日本語・金融数値特化型 BM25
一般的な形態素解析器では分断されやすい「5兆3529億円」「0.25%」「350万台」といった英数字・単位の複合トークンを正規表現で最優先抽出し、漢字・カタカナ部分を CJK Bi-gram(2文字スライディングウィンドウ)でインデックス化。
Reciprocal Rank Fusion (RRF) 数理モデル
RRF_Score(d) = ∑ [ 1 / (60 + rank_m(d)) ] (m ∈ {BM25, Dense})
平滑化定数 $k = 60$(業界標準)。BM25 の対数オッズ比とベクトルのコサイン類似度という異次元のスコアを順位空間へ写像し、頑健に統合。
3.4 引用一致率(Citation Attribution)監査エンジン
配置: src/citation/extractor.py, src/citation/verifier.py
生成文を形態素・文境界単位で分解し、引用タグに対応する参照先 Chunk 内に主張内容の数値・固有名詞が存在するか検証。
flowchart TD
RawAnswer["生成された回答テキスト"] --> SentenceSplit["句点(。)・改行による文分割"]
SentenceSplit --> ForEach["各文のループ処理"]
ForEach --> ExtractTag["文中の引用タグ ([doc_X], [mcp_val_Y]) を抽出"]
ExtractTag --> HasTag{"引用タグが存在するか?"}
HasTag -- No --> CheckFacts{"文中に重要な金融数値・事実主張が含まれるか?"}
CheckFacts -- Yes --> UncitedPenalty["未引用事実として Recall ペナルティ加算"]
CheckFacts -- No --> Neutral["一般的な接続詞・要約文として除外"]
HasTag -- Yes --> FetchDoc["対応する Chunk ID または MCP データを取得"]
FetchDoc --> TokenMatch["文中の数値・重要トークンが引用先テキストに包含されているか検証"]
TokenMatch --> IsConsistent{"数値・事実が完全一致するか?"}
IsConsistent -- Yes --> PassSentence["一致文 (Consistent) としてカウント"]
IsConsistent -- No --> FailSentence["不一致文 (Inconsistent / 幻覚) としてマーク"]
PassSentence & FailSentence & UncitedPenalty --> CalcReport["CitationAuditReport (一致率・適合率・再現率) を算出"]
3.5 配信層・ストリーミング設計 (FastAPI + SSE)
配置: src/api/app.py, src/api/routes.py
イベント種別 (event:) |
ペイロード (data:) |
クライアント側処理 |
|---|---|---|
status |
{"step": "rewriting_query", "message": "..."} |
画面上部のステートマシン進行インジケーターを点灯 |
intermediate |
{"retrieved_chunks": [...], "mcp_data": {...}} |
参照元カードおよび財務バッジをリアルタイム描画 |
token |
{"token": "日"} ... {"token": "本"} |
打字機(Typewriter)風に回答本文をストリーミング描画 |
citation_report |
{"consistency_rate": 0.933, "status": "PASS"} |
引用一致率スコアボードおよび合否バッジを確定表示 |
done |
{"total_duration_ms": 28.5} |
処理完了通知とレスポンス確定 |
3.6 夜間非同期バッチパイプライン
配置: src/batch/nightly_disclosure_batch.py
東証大引け(15:00)後の適時開示ラッシュを想定し、asyncio.Semaphore(max_concurrency=4) による並行度制御を行い、全銘柄の決算短信を一括分析して引用監査付きレポートを出力。
3.7 可観測性 (Observability) & 構造化ログ
配置: src/monitoring/telemetry.py
W3C TraceContext (traceparent) および Datadog APM / OpenSearch 構造化 JSON ログ規格に準拠。
{
"timestamp": "2026-09-15T14:00:00.000Z",
"level": "INFO",
"service": "financial-rag-agent",
"trace_id": "8f3b2a1c...",
"span_id": "4d9e0f...",
"event": "workflow_completed",
"duration_ms": 24.2,
"metrics": {
"retrieved_count": 5,
"mcp_tools_invoked": 1,
"consistency_rate": 0.933,
"audit_status": "PASS"
}
}
3.8 LLM Provider フォールバック戦略
配置: src/llm/cli_provider.py(既定・最優先), src/llm/cloud_provider.py(opt-in 兜底), src/graph/nodes/synthesizer.py(呼び出し順序の統括)
回答生成(Synthesizer ノード)の LLM 呼び出しは、コスト・レイテンシ・API Key 依存を最小化するため以下の 3 段フォールバック順で実行される。ローカル CLI が常に最優先であり、クラウド Provider は明示的に設定した場合にのみ 2 段目として関与する。
| 優先順位 | Provider | 実装 | 発動条件 |
|---|---|---|---|
| 1(既定・必須) | ローカル AI CLI(claude / antigravity / opencode) |
invoke_local_cli() |
常時。サブプロセス呼び出しのみで API Key 不要。local_cli_provider_order の順に試行。 |
| 2(opt-in 兜底) | Azure OpenAI / Google Vertex AI | invoke_cloud_llm() |
全 CLI 失敗 かつ llm_cloud_fallback_order(既定は空リスト)に Provider 名と対応する認証情報が設定されている場合のみ。Azure は openai SDK の AzureOpenAI Client、Vertex は google-genai(vertexai=True、ADC 認証)。両 SDK とも関数内遅延 import。 |
| 3(最終フォールバック) | テンプレート合成 | synthesizer.py 内蔵ロジック |
CLI・クラウドの両方が利用不可/未設定/失敗の場合。検索結果と MCP 指標から機械的にテンプレート文を組み立て、無停止性を担保。 |
llm_cloud_fallback_order は既定で空リストのため、AZURE_OPENAI_* / VERTEX_* 環境変数を設定しない限りクラウド Provider には一切到達しない。既存デプロイの挙動(ローカル CLI → テンプレート の 2 段構成)は完全に維持される。
4. データモデル・スキーマ定義
StockValuation(src/models/finance.py): 銘柄コード、会社名、株価、時価総額、PER、PBR、ROE、配当利回り、TSR、データソース種別。RetrievedChunk(src/models/document.py): Chunk ID (doc_xxx)、タイトル、本文、BM25/Dense/RRF スコア、出所。CitationAuditReport(src/models/document.py): 引用一致率、適合率、再現率、総合判定 (PASS/WARN/FAIL)、文単位検証ログ。
5. 非機能要件設計
| 項目 | 目標値・仕様 | 担保メカニズム |
|---|---|---|
| 耐障害性 | 外部 API 障害時の無停止継続 | yfinance 障害検知時にスナップショット DB へ自動フォールバック |
| 低レイテンシ | P95 < 50ms (Snapshot) | インメモリ BM25 + ベクトルコサイン計算 + 300s TTL キャッシュ |
| 引用監査性 | Consistency Rate ≥ 90% | 70% 未満時の自動再生成ループ + 厳密トークン照合 |
| セキュリティ | 全リクエストの監査追跡可能性 | UUID trace_id を付与した Datadog 構造化ログ |