diff --git a/crates/llm-worker/src/llm_client/scheme/openai_responses/request.rs b/crates/llm-worker/src/llm_client/scheme/openai_responses/request.rs index acce55c3..27ad62b7 100644 --- a/crates/llm-worker/src/llm_client/scheme/openai_responses/request.rs +++ b/crates/llm-worker/src/llm_client/scheme/openai_responses/request.rs @@ -62,8 +62,8 @@ pub(crate) struct ResponsesRequest { pub(crate) struct ReasoningConfig { #[serde(skip_serializing_if = "Option::is_none")] pub effort: Option, - /// Reasoning encrypted_content は同一 user turn 内だけ再利用する。 - /// 古い turn の reasoning item は request input から除外する。 + /// API 側の reasoning retention policy。Insomnia はこの値を送るが、 + /// persisted reasoning item の client-side filtering はしない。 pub context: &'static str, /// summary の出力制御。`"auto"` 固定で summary_text を受け取る。 pub summary: &'static str, @@ -240,9 +240,8 @@ impl OpenAIResponsesScheme { /// `Item` 列を `input[]` に変換する。 fn convert_items_to_input(items: &[Item]) -> Vec { - let current_turn_start = current_turn_start_index(items); let mut out = Vec::with_capacity(items.len()); - for (idx, item) in items.iter().enumerate() { + for item in items { match item { Item::Message { role, content, .. } => { let (role_str, text_variant): (&'static str, fn(String) -> InputContent) = @@ -299,9 +298,6 @@ fn convert_items_to_input(items: &[Item]) -> Vec { encrypted_content, .. } => { - if idx < current_turn_start { - continue; - } let summary_parts = summary .iter() .filter(|s| !s.is_empty()) @@ -324,26 +320,6 @@ fn convert_items_to_input(items: &[Item]) -> Vec { out } -/// Responses の `reasoning.context = "current_turn"` に合わせ、直近の -/// user message 以降だけを current turn とみなす。ToolResult は Responses -/// wire 上では user 側 item だが、新しい人間/外部入力ではなく function-call -/// chain の継続なので turn reset には使わない。System/developer notes も -/// 同一 turn 内の補助入力になり得るため reset しない。 -fn current_turn_start_index(items: &[Item]) -> usize { - items - .iter() - .rposition(|item| { - matches!( - item, - Item::Message { - role: Role::User, - .. - } - ) - }) - .unwrap_or(0) -} - fn convert_tool(tool: &ToolDefinition) -> ResponseTool { ResponseTool { r#type: "function", @@ -506,7 +482,7 @@ mod tests { } #[test] - fn old_turn_reasoning_items_are_omitted_for_current_turn_context() { + fn persisted_reasoning_items_are_preserved_across_user_turns() { let scheme = OpenAIResponsesScheme::new(); let old_reasoning = Item::reasoning("old").with_encrypted_content("OLD_ENC"); let current_reasoning = Item::reasoning("current").with_encrypted_content("CURRENT_ENC"); @@ -527,7 +503,7 @@ mod tests { _ => None, }) .collect(); - assert_eq!(encrypted, vec!["CURRENT_ENC"]); + assert_eq!(encrypted, vec!["OLD_ENC", "CURRENT_ENC"]); } #[test] diff --git a/docs/ref/model-reasoning-context.md b/docs/ref/model-reasoning-context.md index f44e3b66..ca9ec64d 100644 --- a/docs/ref/model-reasoning-context.md +++ b/docs/ref/model-reasoning-context.md @@ -85,9 +85,9 @@ reasoning トークンは各ターンの後に破棄される。次ターンに 1. `previous_response_id` パラメータで過去のレスポンスを参照 2. `response.output` の全アイテムを次の `input` に手動で渡す -ステートレス利用(`store=false`、ZDR組織)の場合は `include=["reasoning.encrypted_content"]` を指定すれば暗号化された推論コンテンツを受け取り、次リクエストに渡すことで推論を引き継げる。ただし Insomnia では Responses リクエストに `reasoning.context="current_turn"` を明示し、直近の user message 以降の同一ターン内 reasoning item だけを `input` に残す。過去ターンの persisted `encrypted_content` は、履歴に残っていても次ターンへ盲目的には再送しない。 +ステートレス利用(`store=false`、ZDR組織)の場合は `include=["reasoning.encrypted_content"]` を指定すれば暗号化された推論コンテンツを受け取り、次リクエストに渡すことで推論を引き継げる。Insomnia は Responses リクエストに `reasoning.context="current_turn"` を明示するが、このパラメータの正確な履歴境界 semantics は provider 側の責務として扱い、履歴から復元した reasoning item を client 側でターン境界に基づいて削除しない。 -同一ターン内の function-call loop では、`reasoning item → function_call → function_call_output → 次の Responses request` の連続性を保つため、直近 user message 以降の reasoning item は保持する。ToolResult は wire 上で user 側 item に見えるが、新しい user turn ではなく function-call chain の継続なので reasoning reset の境界にはしない。 +同一ターン内の function-call loop でも、`reasoning item → function_call → function_call_output → 次の Responses request` の連続性を保つため、履歴上の reasoning item は通常の API message として保持する。ToolResult は wire 上で user 側 item に見えるが、reasoning item の削除境界としては扱わない。 #### モデル世代差 @@ -187,7 +187,7 @@ Ollamaはローカル実行プラットフォームで、モデルごとに思 **ChatGPT を使うとき** - 新規実装は **Responses API** を選ぶ(Chat Completions は推論引き継ぎが弱い) -- ZDR組織でも `reasoning.encrypted_content` で推論を引き継げるが、Insomnia では `reasoning.context="current_turn"` に合わせて同一 user turn / function-call loop 内だけ再送する +- ZDR組織でも `reasoning.encrypted_content` で推論を引き継げる。Insomnia は `reasoning.context="current_turn"` を送るが、履歴上の reasoning item は通常の API message として扱い、独自の turn-boundary filtering はしない - raw reasoning の抽出を試みない(規約違反の可能性) **Ollama を使うとき**