mirror of
https://github.com/tinyhumansai/openhuman.git
synced 2026-07-27 21:08:00 +00:00
fix(agent): use canonical assistant history format in subagent_runner (#606)
build_native_assistant_payload in subagent_runner.rs serialised the
assistant tool-call message using {"text": …, "tool_calls": [{
"type":"function","function":{…}}]} — two mismatches vs the format
parse::build_native_assistant_history produces and the backend
jinja template expects:
1. "text" instead of "content" — the downstream convert_messages
parser looks for "content" to detect assistant-with-tool-calls
messages; "text" is not recognised, so the message is treated
as a plain assistant message.
2. Nested {"type":"function","function":{"name":…,"arguments":…}}
wrappers instead of flat {"id":…,"name":…,"arguments":…}.
Result: every sub-agent that made a tool call and entered iteration
1+ hit a 400 from the backend: "jinja template rendering failed.
Message has tool role, but there was no previous assistant message
with a tool call!" — the backend saw a role=tool message without a
recognised assistant-with-tool-calls predecessor.
The fix replaces the broken inline copy with a direct call to
parse::build_native_assistant_history (the same serialiser
tool_loop.rs uses successfully for the orchestrator's tool calls).
The dead build_native_assistant_payload function is removed.
Introduced in PR #474 (6465f3d3, 2026-04-09). Latent since then
because the orchestrator self-heals by retrying via other agents.
E2E verified: skills_agent with gmail toolkit now progresses through
iterations 0→1→2 successfully (previously died at iteration 1 with
the 400 error every time).
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.6
parent
fd213d40a2
commit
a78506f6a3
@@ -728,8 +728,15 @@ async fn run_inner_loop(
|
||||
|
||||
// Persist assistant message with the original tool_calls payload so
|
||||
// subsequent role=tool messages can reference call ids correctly.
|
||||
// Uses the canonical serialiser from `parse` — the old inline
|
||||
// `build_native_assistant_payload` used `"text"` instead of
|
||||
// `"content"` and nested `{"type":"function","function":{…}}`
|
||||
// wrappers instead of flat `{id, name, arguments}`, which caused
|
||||
// 400 errors from the backend jinja template ("Message has tool
|
||||
// role, but there was no previous assistant message with a tool
|
||||
// call!").
|
||||
let assistant_history_content =
|
||||
build_native_assistant_payload(&response_text, &native_calls);
|
||||
super::parse::build_native_assistant_history(&response_text, &native_calls);
|
||||
history.push(ChatMessage::assistant(assistant_history_content));
|
||||
|
||||
// Execute each call, append role=tool messages.
|
||||
@@ -779,31 +786,6 @@ async fn run_inner_loop(
|
||||
Err(SubagentRunError::MaxIterationsExceeded(max_iterations))
|
||||
}
|
||||
|
||||
fn build_native_assistant_payload(text: &str, tool_calls: &[ToolCall]) -> String {
|
||||
// Mirror the existing native-tool-call serialisation pattern used by
|
||||
// `agent::loop_::parse::build_native_assistant_history`. We inline a
|
||||
// small subset here to avoid an inter-module dep cycle.
|
||||
let calls_json: Vec<serde_json::Value> = tool_calls
|
||||
.iter()
|
||||
.map(|call| {
|
||||
serde_json::json!({
|
||||
"id": call.id,
|
||||
"type": "function",
|
||||
"function": {
|
||||
"name": call.name,
|
||||
"arguments": call.arguments,
|
||||
},
|
||||
})
|
||||
})
|
||||
.collect();
|
||||
|
||||
let payload = serde_json::json!({
|
||||
"text": text,
|
||||
"tool_calls": calls_json,
|
||||
});
|
||||
payload.to_string()
|
||||
}
|
||||
|
||||
fn parse_tool_arguments(arguments: &str) -> serde_json::Value {
|
||||
serde_json::from_str(arguments)
|
||||
.unwrap_or_else(|_| serde_json::Value::Object(Default::default()))
|
||||
|
||||
Reference in New Issue
Block a user