Skip to content

Commit 4238930

Browse files
stebbinsclaude
andcommitted
fix: ensure reasoning_content on all Kimi assistant+tool_calls messages
Add a safety-net pass in create_request() that ensures every assistant message with tool_calls has a reasoning_content field for Kimi thinking models. This handles edge cases where thinking content was stripped by compaction, tool-pair summarization, or not returned by the model. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> Signed-off-by: Harrison <hcstebbins@gmail.com>
1 parent 2a223c3 commit 4238930

File tree

1 file changed

+64
-1
lines changed

1 file changed

+64
-1
lines changed

crates/goose/src/providers/formats/openai.rs

Lines changed: 64 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -836,11 +836,28 @@ pub fn create_request(
836836
"content": system
837837
});
838838

839-
let messages_spec = format_messages(messages, image_format);
839+
let mut messages_spec = format_messages(messages, image_format);
840840
let mut tools_spec = format_tools(tools)?;
841841

842842
validate_tool_schemas(&mut tools_spec);
843843

844+
// Safety net: Kimi thinking models require reasoning_content on every
845+
// assistant message with tool_calls. Add empty reasoning_content if missing,
846+
// which handles edge cases where thinking content was stripped by compaction,
847+
// tool-pair summarization, or simply not returned by the model.
848+
if is_kimi_thinking {
849+
for msg in &mut messages_spec {
850+
let is_assistant = msg.get("role").and_then(|v| v.as_str()) == Some("assistant");
851+
let has_tool_calls = msg.get("tool_calls").is_some();
852+
let has_reasoning = msg.get("reasoning_content").is_some();
853+
if is_assistant && has_tool_calls && !has_reasoning {
854+
msg.as_object_mut()
855+
.unwrap()
856+
.insert("reasoning_content".to_string(), json!(""));
857+
}
858+
}
859+
}
860+
844861
let mut messages_array = vec![system_message];
845862
messages_array.extend(messages_spec);
846863

@@ -2115,4 +2132,50 @@ data: [DONE]
21152132

21162133
Ok(())
21172134
}
2135+
2136+
#[test]
2137+
fn test_create_request_kimi_ensures_reasoning_content() -> anyhow::Result<()> {
2138+
let model_config = ModelConfig {
2139+
model_name: "kimi-k2.5".to_string(),
2140+
context_limit: Some(131072),
2141+
temperature: None,
2142+
max_tokens: None,
2143+
toolshim: false,
2144+
toolshim_model: None,
2145+
fast_model: None,
2146+
request_params: None,
2147+
};
2148+
2149+
// Create an assistant message with a tool call but NO thinking content.
2150+
// This simulates edge cases where thinking was stripped (compaction,
2151+
// tool-pair summarization) or never returned by the model.
2152+
let msg = Message::assistant().with_tool_request(
2153+
"call_1",
2154+
Ok(CallToolRequestParams {
2155+
meta: None,
2156+
task: None,
2157+
name: "test_tool".into(),
2158+
arguments: Some(object!({"key": "value"})),
2159+
}),
2160+
);
2161+
2162+
let request = create_request(
2163+
&model_config,
2164+
"system",
2165+
&[Message::user().with_text("hello"), msg],
2166+
&[],
2167+
&ImageFormat::OpenAi,
2168+
false,
2169+
)?;
2170+
2171+
let messages = request["messages"].as_array().unwrap();
2172+
// messages[0] = system, messages[1] = user, messages[2] = assistant with tool_calls
2173+
let assistant_msg = &messages[2];
2174+
assert_eq!(assistant_msg["role"], "assistant");
2175+
assert!(assistant_msg.get("tool_calls").is_some());
2176+
// Safety net should have added reasoning_content
2177+
assert_eq!(assistant_msg.get("reasoning_content").unwrap(), &json!(""));
2178+
2179+
Ok(())
2180+
}
21182181
}

0 commit comments

Comments
 (0)