ADR 0017 — 会話履歴は生成専用のコンテキストである
過去のターンをインテント解析、検索、セキュリティ判断、エビデンスに入り込ませることなく、第一級の履歴機能を追加する。
ステータス: accepted
日付: 2026-07-14
不変の記録。ドリフト追跡の対象外(
coversなし)。編集ではなく、上書き(supersede)で置き換えること。
本決定は、エンジン ADR 0005(バイト同一のデフォルトパスと並置する新機能)およびエンジン ADR 0010(モデル向けコンテキストから独立した決定的なインテント/セキュリティ)によって制約される。spinestudio ADR 0006 で顕在化したマルチターンの製品面のギャップに応えるものである。
ADR 番号はリポジトリローカルである。この原本の記録における参照は RAGSpine エンジンの ADR を指しており、 ウェブサイトの過去の ADR 0012 は別個の、ファミリーレベルの記録である。
コンテキスト
answer_question(question, store, provider, ...) は単発かつステートレスであり、履歴パラメータを
持っていなかった。製品レイヤーがマルチターンチャットを追加したとき、モデルに過去のコンテキストを
与える唯一の方法は、履歴を question 文字列に継ぎ足すことだった。
これは決定的なインテント解析を汚染した。1320 のような過去の回答の数値が期間 FY1320 として
読み取られる可能性があり、単一メトリクスの照会が誤った比較へと静かに変わり、決定的ルーティングと
捏造防止の正しさを壊してしまう。そのため製品レイヤーは、解析入力と生成コンテキストを構造的に
分離する第一級のパラメータをエンジンが公開するのを待つ間、履歴の注入をデフォルトオフに
しておいた。
決定
answer_question にオプションのキーワード引数
history: Sequence[tuple[str, str]] | None = None を追加する。各ターンは (role, text) であり、想定されるロールは user または assistant である。同じ形状とセマンティクスを、サービスの /v1/ask と /v1/ask/stream に AskRequest.history として貫通させる。
デフォルトの None はバイト同一
_history_messages(None) == []。履歴がない場合、ツールループとナラティブのメッセージシーケンスは
変わらない。tests/agent/test_history.py::test_default_none_is_byte_identical
のパラメータ化された回帰テストが、構造化チャネルの発見あり・発見なし・複数サブタスク・ナラティブの各挙動をカバーする。
履歴は決定的なインテント解析に決して入らない
パーサーが見るのは現在の question のみである: intent = parser.parse(question, …)。セキュリティ
ゲートも同様にその生の質問を再スクリーニングする。_history_messages は過去のターンを OpenAI 形式の
メッセージに変換し、_run_tool_loop と _run_narrative においてシステムプロンプトと現在のユーザーターンの間に挿入する。現在の質問は最後のユーザーメッセージのままであるため、MockProvider の
最終ユーザーターン解析は汚染されない。
公開される履歴の形状はフラットな (role, text) タプルである。system/tool ロールを持ち込むことも、
tool_calls を密輸することもできない。未知のロールは user に正規化される。この解析入力と
生成コンテキストの構造的な分離が、製品側のペインポイントを解決する。
捏造防止は履歴があっても維持される
構造化チャネルは引き続き、発見された回答をツールのファクトから決定的にレンダリングするか、
ミスを not-found へ書き換える。履歴はエビデンスを生成しない。出典追跡は依然として実際のツール/検索
ヒットのみを指す。ネガティブテストでは、捏造されたファクト——Shanghai FY2099 REVENUE = 999——を履歴に配置し、
ナレッジベースのミスが拒否回答のままであること、決して 999 を引用しないこと、sources が空であることをアサートする。
ナラティブの検索クエリは現在の質問のみのままである。履歴は検索に決して入らない。
RESTRICTED の二重出口分離は変わらない
履歴が追加するのは生成コンテキストのメッセージのみである。検索には一切触れないため、link/ と
rerank/ の出口は引き続き RESTRICTED コンテンツを除去する。tests/conformance/test_history_isolation.py
が両方の履歴形態を拘束し、空虚でないリーキーリトリーバーによる逆証明を含む。
シグネチャはエンジンのオンボーディング複雑性予算の範囲内に留まる。history はデフォルト付きの
キーワード専用引数であるため、最初の回答を得るまでのオンボーディングコストは変わらない。対応するエンジン側の記録は
docs/adr/0012-onboarding-complexity-budget.md
であり、ウェブサイトの同番号のファミリー ADR ではない。
検討した代替案(却下)
- 履歴を
questionに継ぎ足し続ける: これこそがまさに当該の欠陥であり、決定的解析と 捏造防止を汚染する。 - 任意の OpenAI
list[dict]メッセージを受け入れる: 呼び出し側がsystem、tool_calls、または 捏造されたツール結果を注入できてしまう。フラットな(role, text)タプルは、意図的にその形状を表現できないようにしてある。 - 照応・省略解決のためにインテントパーサーへ履歴を渡す: これは汚染経路を再び開くことになる。
決定的解析は現在の質問のみに依存する。オプションの
ConversationSessionによるスロットの 引き継ぎは別物のままである。 - 履歴をナラティブ検索に供給する: 履歴が引用されないエビデンスソースになってしまう。 検索クエリは現在の質問のみのままとする。
帰結
- エンジンは第一級のマルチターンコンテキストパラメータを持つ。製品は決定的ルーティングや 捏造防止を壊すことなく履歴を有効化できる。
- バイト同一のデフォルト挙動、解析/コンテキスト分離、捏造防止、および RESTRICTED 分離はテストによって拘束される。
- 新たな成果物は
answer_question(history=)、_history_messages、両方の ask ルートにおけるAskRequest.history、tests/agent/test_history.py、tests/conformance/test_history_isolation.py、 およびtests/service/api/test_api_ask_history.pyである。 ConversationSessionには手を触れていない。これは直交する存在であり、将来的には記憶された ターンから履歴リストを構築することもできる。