はじめに
社内データとLLMを組み合わせて、
「先月のA支店の売上は?」
「前年同月と比べてどう?」
「3拠点を比較して」
のような質問に自然言語で答えられる仕組みを作りたい。
こうした要求は、一見すると「社内データをRAGに入れてLLMに答えさせればよい」ように見えます。
しかし、実際に定量データを扱い始めると、一般的な「検索+LLM」だけではかなり厳しいことが分かってきます。
LLMは非常に優秀な言語理解・推論エンジンですが、
- 似た指標を取り違える
- 対象期間を補完する
- 足りない条件を推測する
- 取得した数字から別の数字を計算する
- データがない場合にも、もっともらしい回答を生成する
といった挙動を完全には排除できません。
文章生成では便利なこの性質も、
30%を50%と答えたら困る
という世界では、そのまま使うわけにはいきません。
そこで私が採用した設計方針は非常に単純です。
LLMには考えさせる。だが、決めさせない。
文章は作らせる。だが、数字は作らせない。
本記事では、定量データを安全に扱うために、LLMを「権威」から降ろし、決定論的なプログラムを中心に据えたアーキテクチャについて紹介します。
1. RAGが悪いのではない。「何のAuthorityにするか」が問題
最初に明確にしておきたいのは、
「定量データにRAGは使えない」わけではありません。
問題なのは、RAGで取得した情報とLLMの推論結果を、そのまま**数値の最終Authority(権威)**にしてしまうことです。
定性的な用途
例えば、
- 社内規程
- マニュアル
- 障害対応手順
- 過去の問い合わせ
- 技術文書
- ノウハウ
などについて、
「このケースではどう対応するべきか?」
と質問する用途では、ベクトル検索+LLMは非常に強力です。
多少曖昧な表現で質問しても関連文書を探し、複数の文章をまとめ、人間が読みやすい形に整理できます。
定量的な用途
一方、
- 売上
- 利益率
- CVR
- 稼働率
- 不良率
- 全社KPI
- 拠点別実績
- 前年比較
などでは事情が変わります。
例えば、
「8月のA支店のCVRは?」
という質問に対して必要なのは、文章としての「それっぽさ」ではありません。
必要なのは、
指標 = CVR
対象 = A支店
期間 = 2026年8月
分子 = 正式に定義されたConversion数
分母 = 正式に定義されたVisit数
除外条件 = 会社が承認した条件
データソース = 承認済みデータセット
が毎回同じルールで解釈されることです。
したがって、私の設計では、
RAGはMeaningやEvidenceを探すためには使える。
しかし、数値を確定するAuthorityにはしない。
という境界を置いています。
2. 「KB+LLM」で数値を扱ったときに起きる問題
2.1 似た指標が増えるほど検索が難しくなる
例えば社内に、
売上
月次売上
累計売上
予算売上
前年差
前年同月売上
店舗売上
EC売上
が存在するとします。
人間から見れば明確に違う指標でも、Embedding上では非常に近い位置に配置されます。
この状態で、
「A店の今月の売上は?」
と検索すると、似た文書や似た数字が複数取得されます。
その後の最終判断をLLMに任せると、
検索
↓
候補1
候補2
候補3
↓
LLMが文脈から選択
という構造になります。
私はこれを内部的に**「意味空間の椅子取りゲーム」**と考えています。
データやKPIが増えるほど、この問題は悪化します。
2.2 LLMによる計算は「保証」が難しい
LLMは簡単な算術をかなり正確に処理できます。
したがって、
「LLMは計算ができない」
という表現は正確ではありません。
問題はそこではありません。
問題は、
同じ条件なら必ず同じ計算規則を適用する
という業務システムとしての保証を持たせにくいことです。
例えば、
CVR = 成約数 / 来店数
という指標でも、
- キャンセルを含めるのか
- NULLをどう扱うのか
- 重複レコードを除外するのか
- 期間境界は営業日か暦日か
- 分母0を0%とするのかNULLとするのか
によって結果は変わります。
ここをLLMの推論に任せるべきではありません。
2.3 「分からない」の種類を区別しにくい
実際の業務では、
データが0件
データソースが存在しない
指標自体が未定義
対象店舗が不明
期間が曖昧
計算方法が未承認
システム障害
はすべて別の状態です。
ところがLLMだけに処理させると、これらをまとめて、
「おそらく○○です」
と補完してしまうことがあります。
エンタープライズ用途では、答えることよりも、
なぜ答えられないのかを正しく分類すること
の方が重要な場合があります。
2.4 会話履歴が「ゾンビ化」する
チャットシステムでは、過去の会話文脈も注意が必要です。
例えば、
User: A支店の8月は?
AI: ...
User: ではB支店は?
なら、8月を引き継ぐのは自然です。
しかし、
User: A支店の8月は?
AI: ...
User: C事業部の年間実績を教えて
という明示的な新しい条件が来たにもかかわらず、以前の「A支店」「8月」が一部残存すると危険です。
そのため、会話状態についても、
現在の明示的な指示 > 過去の会話状態
というルールをLLMではなくプログラム側で管理する必要があります。
3. 基本思想:「Proposal」と「Decision」を分離する
この設計の中心にある考え方は、
LLM Proposal != System Decision
です。
LLMは、
「この質問は、おそらくA支店・2026年8月・CVRについて聞いている」
という提案はできます。
しかし、その提案をそのままSQLへ渡してはいけません。
User
↓
LLM
↓
Proposal
↓
Deterministic Validator
↓
Decision
とします。
つまり、
LLMは候補を出す。
プログラムが採用するかどうかを決める。
この境界を設けるだけでも、システムの性質はかなり変わります。
4. 「真実」を1本のTrust Orderで考えない
当初私は、
DBの生データ
>
プログラム
>
LLM
のような単純なTrust Orderを考えていました。
しかし、これは少し不正確です。
なぜなら、
DBに数字が入っているだけでは、その数字の意味は決まらない
からです。
例えば、
value = 42
が正しくDBに保存されていても、
- 何の42なのか
- 単位は何か
- どの粒度か
- どの期間か
- どの母集団か
が分からなければ、業務上の「真実」にはなりません。
そこで、Authorityを役割ごとに分離します。
| Authority | 役割 |
|---|---|
| Meaning Authority | そのKPIが何を意味するか |
| Capability Authority | システムが安全に回答できるか |
| State Authority | 今回の質問条件は何か |
| Execution Authority | どの計算・データ取得方法を使うか |
| Data Authority | 実際に取得されたデータ |
| Evidence Authority | 最終的に回答根拠として確定したもの |
| Presentation Authority | Evidenceをどう人間向けに表現するか |
ポイントは、
LLMをこれらのAuthorityの中心に置かない
ことです。
5. 全体アーキテクチャ:8つの論理レイヤー
最終的には、質問を次のような流れで処理します。
User
↓
1. LLM Advisor
↓
2. Request Resolver / Orchestrator
↓
3. Semantic Catalog
↓
4. Capability Registry
↓
5. Execution Contract
↓
6. Typed Executor
↓
7. Canonical Evidence Package
↓
8. Answer Permission / Evidence-only Renderer
↓
User
順番に説明します。
5.1 LLM Advisor
ここではLLMを自然言語の通訳として利用します。
例えば、
「先月のA支店の新規獲得率どうだった?」
を、
{
"metric_candidate": "customer_acquisition_rate",
"entity_candidate": "branch_a",
"period_candidate": "previous_month"
}
のような構造化された候補へ変換します。
重要なのは、これはまだProposalだということです。
LLMには、
- SQL
- テーブル名の決定
- KPI計算式
- WHERE条件
- 集計方法
を決める権限を持たせません。
5.2 Request Resolver / Orchestrator
次に、LLMのProposalと現在の会話状態を検証し、
Resolved Request
を作ります。
例えば、
{
"metric": "customer_acquisition_rate",
"entity": "branch_a",
"period": "2026-08"
}
です。
ここでは、
現在の明示指定
>
今回安全に引き継げる文脈
>
過去の文脈
という優先順位を決定論的に適用します。
曖昧なら勝手に補完せず、
CLARIFY
へ送ります。
5.3 Semantic Catalog
Semantic Catalogは会社における意味の正本です。
例えば、
customer_acquisition_rate
について、
正式名称
説明
単位
粒度
期間の意味
対象母集団
同義語
関連指標
などを管理します。
例えば、
metric_id: customer_acquisition_rate
display_name: 新規顧客獲得率
unit: percent
grain:
- branch
- month
のようなイメージです。
ここで重要なのは、
KPI名とSQLを直接結び付けない
ことです。
意味と実装を分離します。
5.4 Capability Registry
Semantic Catalogに指標が存在していても、
システムがその指標を安全に計算できる
とは限りません。
そこでCapability Registryを別に持ちます。
例えば、
DEFINED
SOURCE_EXISTS
SEMANTICS_CONFIRMED
EXECUTION_APPROVED
UNSUPPORTED
のように、
この質問にシステムとしてどこまで責任を持てるのか
を管理します。
これにより、
KPIの意味は知っている
しかし実績値を正確に算出する経路は未承認
という状態を表現できます。
これは非常に重要です。
5.5 Execution Contract
ここが定量システムの中核です。
Execution Contractには、
このKPIをどう計算するか
を機械可読な形で固定します。
概念的には、例えば次のようになります。
{
"metric_id": "customer_acquisition_rate",
"source_id": "monthly_branch_metrics",
"grain": [
"branch",
"month"
],
"formula": {
"type": "ratio",
"numerator": "new_customers",
"denominator": "visitors"
},
"filters": [
{
"field": "record_status",
"operator": "=",
"value": "valid"
}
],
"zero_denominator_policy": "null"
}
ポイントは、
Formula
WHERE条件
JOIN条件
粒度
NULL処理
0除算処理
をLLMのPromptの中に置かないことです。
それらは、
- Version管理
- Review
- Test
- 承認
可能なExecution Contractとして管理します。
JSON/YAMLにするか、型付きPythonコードにするかは実装上の選択ですが、
LLMではなく、レビュー可能な正本がAuthorityである
ことが重要です。
5.6 Typed Executor
Execution Contractを実行するのがTyped Executorです。
一般的なText-to-SQLでは、
User
↓
LLM
↓
自由記述SQL
↓
DB
となります。
この設計では、
Resolved Request
+
Execution Contract
↓
Typed Compiler
↓
Parameterized Query
↓
DB
とします。
例えば、
WHERE branch_id = :branch_id
AND target_month = :target_month
のように、ユーザーから変化する部分は値のパラメータだけにします。
LLMが、
WHERE ...
GROUP BY ...
JOIN ...
を自由に発明する余地をできるだけ排除します。
5.7 Canonical Evidence Package
DBから結果が返ってきても、そのままLLMへ渡しません。
一度、回答に利用可能な事実をCanonical Evidence Packageとして固定します。
例えば、
{
"authority": {
"metric_id": "customer_acquisition_rate",
"execution_contract": "customer_acquisition_rate_v3",
"grain": "branch_month"
},
"request": {
"branch": "branch_a",
"period": "2026-08"
},
"evidence": {
"actual": 31.4,
"unit": "%",
"row_count": 1
},
"provenance": {
"source": "monthly_branch_metrics",
"executor": "ratio_metric_executor"
},
"render_policy": {
"allowed_claims": [
"actual_value",
"branch",
"period",
"unit"
]
}
}
この瞬間、
何を回答してよいか
を確定します。
これがLLMとのTruth Boundaryになります。
5.8 Answer Permission / Evidence-only Renderer
最後にLLMをもう一度使います。
ただし、このLLMは極端に権限の弱いLLMです。
入力されたEvidence Packageを、
「A支店の2026年8月の新規顧客獲得率は31.4%です。」
という自然な文章へ変換するだけです。
禁止事項は明確です。
Evidenceにない数字を追加しない
勝手に平均との差を計算しない
前年比を計算しない
目標値を補完しない
原因を断定しない
つまりLLMはアナウンサーです。
分析官でも、DBAでも、会計士でもありません。
6. 「Rendererまで縛る」理由
SQLだけ安全にすれば十分だと思われるかもしれません。
しかし、実際にはそうではありません。
例えばDBから、
{
"actual": 31.4
}
が返ってきたあと、LLMが、
「31.4%で、全社平均より5ポイント高い好調な結果です」
と回答したらどうでしょうか。
SQLは一切間違っていません。
それでも、
全社平均
5ポイント
好調
という3つの新しい主張が生成されています。
そのため、
ExecutorだけでなくRendererにも契約が必要
です。
Evidence Packageに、
"allowed_claims": [
"actual_value",
"entity_scope",
"period_scope"
]
のような制約を持たせる理由がここにあります。
7. 「Deterministic」は「正しい」と同義ではない
ここはかなり重要です。
決定論的なシステムを作ったからといって、
必ず正しい数字が出る
わけではありません。
間違った計算式をExecution Contractに登録すれば、
毎回同じ間違った数字
を返すだけです。
Deterministicが保証するのは、
同じContractと同じ入力なら、同じルールで実行される
という再現性です。
したがって、本当に必要なのは、
Semantic Definition
+
Execution Contract
+
Test
+
Human Review
+
Data Provenance
です。
これはLLMの問題というより、従来のデータ基盤やBIでも同じ話です。
LLMを導入したからこそ、今まで暗黙だったKPI定義を機械可読なContractへ落とし込む必要が出てきた、とも言えます。
8. 目指すのは「回答率100%」ではない
この設計で重要視している指標の1つが、
何でも答えられること
ではありません。
むしろ、
答えられない質問に対して、正しく答えられないと言えること
です。
例えば、
ANSWER
CLARIFY
UNSUPPORTED
INSUFFICIENT_EVIDENCE
ERROR
のように、質問のDisposition(処置結果)を明確にします。
例えば、
「この指標の目標値は登録されていますが、実績値を安全に算出するExecution Contractは現在ありません。」
という回答は、一見すると能力不足です。
しかし業務システムとしては、
適当な数字を答える
よりはるかに価値があります。
私はこれを、
Answer CoverageではなくDisposition Correctnessを上げる
と考えています。
全社KPIを対象にするシステムでの「万能性」は、
全質問に数字を返すこと
ではなく、
どんな質問に対しても、ANSWER / CLARIFY / UNSUPPORTEDを正しく選べること
ではないでしょうか。
9. Agentic AIを否定しているわけではない
ここまで読むと、
「ではAgentic AIは使わないのか?」
と思うかもしれません。
そうではありません。
むしろ私は、
探索にはAgentic、確定にはDeterministic
という分離が重要だと考えています。
例えばWeb検索では、
検索
↓
証拠不足
↓
検索語を書き換える
↓
再検索
↓
十分なら回答
といったAgenticな動きが有効です。
これは最初から正解のデータソースが決まっていない探索問題だからです。
一方、
「8月のA支店の売上はいくらか」
は探索問題ではありません。
会社が正式な数字として扱うのであれば、
どのデータソース
どの定義
どの期間
どの粒度
どの集計方法
を事前に確定しておくべきです。
したがって、
Web Research
↓
Bounded Agentic Executor
KPI Execution
↓
Deterministic Typed Executor
のように、用途ごとに権限を分離します。
大事なのは、
Agentic AIをシステム全体の最高権限にしない
ことです。
10. 既存システムを一気に作り直さない
実際のエンタープライズシステムでは、
旧SQL
旧KPI
既存API
既存ダッシュボード
既存チャット
が既に大量に存在します。
そのため、私は一括置換よりも段階移行の方が安全だと考えています。
例えば、
既存経路
│
├─────────────→ Production
│
└→ 新ExecutorをShadow実行
↓
結果比較
↓
Dry Run
↓
Limited Runtime
↓
Production
という進め方です。
特に数字を扱うシステムでは、
「新しい設計の方が綺麗だから置き換える」
ではなく、
同じ質問に同じ答えが返ることをEvidenceで確認してから権限を移す
方が安全です。
11. この設計のメリット
この構造にすると、LLMとデータ基盤のライフサイクルを分離できます。
LLMが高性能になれば、
曖昧な質問の理解
同義語理解
会話UX
自然な文章生成
は改善します。
しかし、
KPI定義
Execution Contract
Typed Executor
Evidence Package
はそのまま利用できます。
つまり、
LLMを交換しても「会社における数字の意味」を作り直す必要がない
という構造になります。
逆に、データソースが変更された場合も、Execution ContractやExecutor側を更新すればよく、LLMのPromptに業務ロジックを大量に埋め込む必要がありません。
12. このアーキテクチャで最も重要だったこと
開発を通じて最も重要だと感じたのは、
LLMをどう賢くするか
ではありませんでした。
重要だったのは、
LLMがどこまで間違えてよいかを、システム構造として定義すること
でした。
自然言語の解釈を間違えることはあります。
その場合はClarifyすればよい。
文章表現が多少不自然になることもあります。
それは改善できます。
しかし、
存在しない売上を作る
分母を勝手に変更する
期間を無言で補完する
別店舗の数字を混ぜる
ことは許容できません。
だからこそ、
LLM Proposal
↓
Deterministic Decision
↓
Typed Execution
↓
Canonical Evidence
↓
Evidence-only Generation
という境界を作りました。
まとめ
LLMは非常に優秀です。
だからこそ、何でも任せたくなります。
しかし、エンタープライズの定量データでは、
「できること」と「やらせてよいこと」は別
です。
RAGもAgentic AIもText-to-SQLも、それ自体が悪いわけではありません。
問題は、
どこにAuthorityを置くか
です。
私が採用した原則をまとめると、次のようになります。
LLMはProposalを作れる。
しかしDecisionは作れない。
LLMは質問を理解できる。
しかしKPI定義は変更できない。
LLMは検索を提案できる。
しかしExecution Contractは変更できない。
LLMは文章を生成できる。
しかしEvidenceにない数字は生成できない。
「LLMをもっと賢くすれば、いつか間違えなくなる」という前提でシステムを作るのではなく、
LLMが間違えても、会社の事実までは曲げられない
構造にする。
そして、Agenticな探索能力とDeterministicなAuthorityを分離する。
これが、長期間運用できるエンタープライズ向けデータチャットを作るうえで、一つの有効なアプローチだと考えています。