Upgrade to Pro — share decks privately, control downloads, hide ads and more …

決定論と非決定論を組み合わせた AI Agentの構築と改善

決定論と非決定論を組み合わせた AI Agentの構築と改善

Avatar for zakky / Rintaro Yamazaki

zakky / Rintaro Yamazaki

July 28, 2026

Transcript

  1. 導入 題材は FP&A 向けの社内 AI エージェント Slack とスプレッドシートから自然言語で使え、管理会計データや議事録を横断して回答する LLM は

    Bedrock 上の Claude、トレーシングはセルフホストの Langfuse ※ 本資料の金額・事例は説明用に加工した架空の値です(黒いターミナル画面の出力のみ実物・一部マスク) 3
  2. 1. 診断 掘れるかどうかを最初に決める観測基盤 財務データを外に出せないので Langfuse をセルフホストし、全ノード・全ツール実行を記録している LangGraph の全ノード、LLM 呼び出し、ツール実行、 検証リトライまでがトレースに残る。指摘の調査はトレ

    ース ID の特定から始まるので、これが無いと何も掘れな い。 巨大なトレースは取得 API ごと落ちるので、 observations API を名前フィルタとページネーション で掘る実装にしておく。 本番トレースへの実際の調査出力(抜粋・一部マスク) 10
  3. 1. 診断 まず疑うのは LLM ではなく、データとプロンプト トレースをデータ取得、プロンプト構築、LLM 推論の順に掘る。この指摘の原因は B 層だった A

    データ取得(決定論) う疑に順らか A ず必 fetch ノード・SQL・API。取れた⽣値は正しいか B プロンプト構築(決定論) LLM に渡るまでに情報が消えたり歪んだりしていないか C LLM 推論(⾮決定論) 正しい⼊⼒なのに出⼒が違う。制約追加や few-shot で対処 同じトレースをグラフのノードだけに絞った出力。生成ノードが 39.6 秒中 37.96 秒 データも数値も正しかった。評価の強さに関する規約がプロンプトのどこにも無い、それが原因だった 下流は上流の影響を受けるため、⾮決定論を疑うのは決定論を潰してから 11
  4. 1. 診断 診断を属人化させない道具と記録 手順は Skill に、知見は patterns.md に残し、誰が対応しても同じ型で回るようにする 道具は2本のスクリプト。 patterns.md

    からの抜粋 がフィードバック文面か 小さい金額が「 百万円」と書かれる らトレースを特定し、 がノード を段階的に掘る。手順そのものは Claude Code の Skill(6フェーズの手順書)にまとめた。 診断で分かったことは に追記してい 大きく上回ったのに「予算達成」と書く く。似た指摘が来たとき、調査がゼロからにならな い。 find_feedback_traces.py inspect_trace.py ## C-1 0 仮説: プロンプト構築の問題。丸めると0になる値が LLM に「0」としか伝わっていない → 元の値を併記する形にフォーマッタを修正済み patterns.md ## C-2 仮説: プロンプト構築の問題。評価の強さを決める 規約がプロンプトのどこにも無い → 強度ラベルを導入し validator で検査する形に対応済み 12
  5. 2. 修正 表現の強さまでコードで決める設計 強度の判断はコードが行い、LLM は言葉にするだけにする 生成コメントの変化(架空の文例) def classify_variance_strength(variance, budget): ratio

    = variance / abs(budget) * 100 if ratio >= 30: return "大幅上回り" if ratio >= 5: return "上回り" if ratio >= -5: return "達成" ... # 実装イメージ(抜粋・簡略化) 対応前 売上高は予算達成となった 対応後 ▼ 売上高は予算を大幅に上回って着地した この関数をプロンプト構築と validator の両方が参照する。指示と検査はズレようがない。 14
  6. 2. 修正 数値を LLM に書かせない設計 金額の単位変換ミスを機に、数値は参照キーで運ぶ構造へ変えた 起きたこと 単位変換を LLM に任せていたら「+2,345,678円」が

    「+235百万円」と書かれた(正しくは +2百万円)。長 い対話では別科目との数値の取り違えも起きた 打った手 ツールの結果は result_store に置き、LLM が本文や 表に書けるのは のような参照のみ。数値リテラル の直書きはスキーマ検証がはじく $v1 計算ツールの docstring にも同じ契約を書いてある 数値の算術演算を実行します。DB由来の値は参照で渡し、手入力しないでください。 - 数値リテラル: what-if係数・%・しきい値などデータ由来でない定数のみ。 17
  7. 2. 修正 プロンプトではなくコードで守る出力の契約 禁止表現は正規表現で検出し、書き直しだけを LLM にやり直させる 禁止表現の契約(実コードから抜粋) _BANNED_PATTERNS = {

    "赤字・黒字": r"赤字|赤字幅|黒字|黒字化|黒字転換", "ですます調": r"です(?![a-zA-Z])|ます(?![a-zA-Z])|でしょう", "不適切な用語": r"役員報酬|未計上", } 違反時にプロンプトへ注入している文面 【重要】前回の生成で以下の禁止ワードが検出されました: {検出語} これらのワードを絶対に使用しないでください。代替表現を使用してく ださい。 このリトライの様子は Langfuse のトレースにそのまま残る 生成(LLM) generate_comment ▼ 検証(決定論) 正規表現・citations・強度整合 ▼ 合格したものだけ完成 違反時は検出語を注入して再生成(最大2回) 18
  8. 2. 修正 外部管理に置いてはいけない「不変条件」 会計年度の定義が1年ズレる事故を機に、必ず効かせたいルールはコード注入へ移した 不変条件 = コードから常時注⼊(決定論) 実際に踏んだ事故 会計年度の定義・現在⽇時・出⼒の安全規約 どのプロンプト版が本番でも「必ず届く」ことをコードが保証

    チューニング領域 = Langfuse レジストリ 表現ルール・出⼒例など → UI編集で1〜2分で本番反映 障害時はコード内フォールバックで必ず動く(SWRキャッシュ+起動時prefetch) システム プロンプト 会計年度の定義をレジストリ側に 置いていた → 改版で消えても 誰も気付けない エラーは出ず、もっともらしい 「1年ズレた期間」の回答に → FY定義はコード注⼊側へ移動 19
  9. 3. 資産化 フィードバック1件の正体は、JSON 1ファイル 凍結した入力と、人間が直した正解と、ケース固有の検査条件をまとめて Git で管理する 実際のスキーマに沿った例(値は架空)。あの指摘は must_not_include の「予算達成」として永続化されている

    { "meta": { "case_id": "fb20260601-007", // フィードバック1件 = ケース1件 "source": { "trace_id": "9f3a1c…" }, // 発生時のトレースに紐づく "status": "active" // quarantine → audit → active }, "input": { "department_external_code": "D-1024", "period_start": "2026-05", … }, "frozen_state": { // 決定論7ノードの出力をそのまま凍結 "performance_data": { "unit": "百万円", "rows": [ /* 発生時の予実データ */ ] }, "qualitative_documents": [ /* 発生時の議事録検索結果 */ ] }, "expectations": { "must_not_include": ["予算達成"], // このケース固有の合格条件 "must_include": ["大幅に上回って着地"], "numeric_tolerance_million": 1.0, // 校正で実測に合わせた許容誤差 "reference_output": "売上高は予算を大幅に上回って着地した。…" } } // 人間が直した正解 23
  10. 3. 資産化 回帰ケース作りで直面した想定外 金額は本番と同じ取得ノードを再実行して凍結する。この回り道を正規手順にした トレース上の金額情報はマスクしているため、部署や期間はト quarantine(隔離) レースから取り、金額は取得ノードの再実行で埋めることにし 登録直後はまだゲートに使わない た。 ▼

    audit(健全性チェック) 達成率の単位も取り違えていた。サーバーは「108.5」のよう 人間が直した正解自体が検査を通ることを先に確認 な%値を返すのに、評価側は比率とみなして100倍していた。 ▼ 合成テストも同じ誤解で作られていて、テストがバグを隠して active(ゲート対象) 以後すべてのリプレイで検証され続ける いた。 24
  11. 3. 資産化 LLM の非決定性を CI に持ち込まない評価設計 合否は決定論チェックだけで判定し、LLM の採点は傾向の観測に使う Tier1 決定論チェック

    Tier2 LLM-as-a-Judge 役割 ハードゲート(違反 = fail) 傾向の観測(ゲートに使わない) 内容 数値整合・禁止語・citations・ケース固有の assertion 5軸ルーブリックで1〜5点 特性 再現可能・高速・LLM 不使用 揺らぎあり・LLM 1呼び出し/ケース uv run python -m evals.run --mode replay # Tier1 ゲート判定(違反があれば exit 1) uv run python -m evals.run --mode replay --judge --report r.json uv run python -m evals.run --mode replay --prompt-label dev # Judge 採点とベースライン比較 # 昇格前プロンプトを本番反映前に評価 あの指摘は今、ケース固有の assertion として全リプレイで検証され続けている 25
  12. 3. 資産化 評価基盤そのものの校正 初回リプレイの数値違反は幻覚ではなくモデルの丸め癖。基準は緩めず、許容誤差を実測に合わせた 違反ケースを凍結データと1件ずつ突き合わせて 確認した(123.9 を 123、1,980 を「約 2,000」と書く癖だった)

    許容誤差は絶対値と相対値の大きい方で判定す る形に設計し直した 校正を経て、登録済みケースの正解は全件が決 定論チェックを通っている 登壇準備時点の実際の実行結果 偽陽性を放置するとゲートへの信頼が崩れ、本物の違反まで無視されるようになる 26
  13. 学び 運用の中で動き続ける境界線 1件の指摘への対応が積み重なって、自律性を広げては畳み、ガードを締めては緩めた1年近くになった 2025-11〜12 2025-09 プロトタイプ LangGraph化 ガードレール3本 検証ループ・単位変換の決定論化・流量制御 2025-10

    LLMの⾃律性を広げて 翌⽇ Revert 2026-02 2026-04 2026-07 決定的パイプラインを新基盤へ移植 evals 回帰ハーネス (移植の過程で複数の潜在バグを発⾒) Quality Gate(数値突合) プロンプト外部管理 ベースライン確⽴ 2026-06 フィードバック起点の改善が続く agentic経路を廃⽌(約1,300⾏削除) ⾃律性を広げては畳み、ガードを締めては緩めてきた 27