diff --git a/src/client/bedrock.rs b/src/client/bedrock.rs index 2f15dcb..73671a7 100644 --- a/src/client/bedrock.rs +++ b/src/client/bedrock.rs @@ -397,6 +397,11 @@ fn build_chat_completions_body(data: ChatCompletionsData, model: &Model) -> Resu })) } for tool_result in tool_results { + if let Some(round_text) = &tool_result.text { + assistant_parts.push(json!({ + "text": round_text, + })) + } assistant_parts.push(json!({ "toolUse": { "toolUseId": tool_result.call.id, diff --git a/src/client/claude.rs b/src/client/claude.rs index e20726e..06fa603 100644 --- a/src/client/claude.rs +++ b/src/client/claude.rs @@ -320,6 +320,12 @@ pub fn claude_build_chat_completions_body( })) } for tool_result in tool_results { + if let Some(round_text) = &tool_result.text { + assistant_parts.push(json!({ + "type": "text", + "text": round_text, + })) + } assistant_parts.push(json!({ "type": "tool_use", "id": tool_result.call.id, diff --git a/src/client/message.rs b/src/client/message.rs index d8d24bf..3e4074c 100644 --- a/src/client/message.rs +++ b/src/client/message.rs @@ -118,6 +118,9 @@ impl MessageContent { lines.push(text.clone()) } for tool_result in tool_results { + if let Some(round_text) = &tool_result.text { + lines.push(round_text.clone()) + } let mut parts = vec!["Call".to_string()]; if let Some((agent_name, functions)) = agent_info && functions.contains(&tool_result.call.name) @@ -201,9 +204,13 @@ impl MessageContentToolCalls { } } - pub fn merge(&mut self, tool_results: Vec, _text: String) { + pub fn merge(&mut self, mut tool_results: Vec, text: String) { + if !text.is_empty() + && let Some(first) = tool_results.first_mut() + { + first.text = Some(text); + } self.tool_results.extend(tool_results); - self.text.clear(); self.sequence = true; } } diff --git a/src/client/openai.rs b/src/client/openai.rs index f49937f..e5c3dd8 100644 --- a/src/client/openai.rs +++ b/src/client/openai.rs @@ -370,7 +370,7 @@ pub fn openai_build_chat_completions_body(data: ChatCompletionsData, model: &Mod match content { MessageContent::ToolCalls(MessageContentToolCalls { tool_results, - text: _, + text, sequence, }) => { if !sequence { @@ -387,9 +387,12 @@ pub fn openai_build_chat_completions_body(data: ChatCompletionsData, model: &Mod }) }) .collect(); - let mut messages = vec![ - json!({ "role": MessageRole::Assistant, "tool_calls": tool_calls }), - ]; + let mut assistant_message = + json!({ "role": MessageRole::Assistant, "tool_calls": tool_calls }); + if !text.is_empty() { + assistant_message["content"] = strip_think_tag(&text).into(); + } + let mut messages = vec![assistant_message]; for tool_result in tool_results { messages.push(json!({ "role": "tool", @@ -399,21 +402,30 @@ pub fn openai_build_chat_completions_body(data: ChatCompletionsData, model: &Mod } messages } else { - tool_results.into_iter().flat_map(|tool_result| { + tool_results.into_iter().enumerate().flat_map(|(index, tool_result)| { + let round_text = if index == 0 && !text.is_empty() { + Some(text.clone()) + } else { + tool_result.text.clone() + }; + let mut assistant_message = json!({ + "role": MessageRole::Assistant, + "tool_calls": [ + { + "id": tool_result.call.id, + "type": "function", + "function": { + "name": tool_result.call.name, + "arguments": tool_result.call.arguments.to_string(), + }, + } + ] + }); + if let Some(round_text) = round_text { + assistant_message["content"] = strip_think_tag(&round_text).into(); + } vec![ - json!({ - "role": MessageRole::Assistant, - "tool_calls": [ - { - "id": tool_result.call.id, - "type": "function", - "function": { - "name": tool_result.call.name, - "arguments": tool_result.call.arguments.to_string(), - }, - } - ] - }), + assistant_message, json!({ "role": "tool", "content": tool_result.output.to_string(), @@ -552,24 +564,36 @@ pub fn openai_build_responses_body(data: ChatCompletionsData, model: &Model) -> match content { MessageContent::ToolCalls(MessageContentToolCalls { tool_results, - text: _, + text, sequence: _, }) => tool_results .into_iter() - .flat_map(|tool_result| { - vec![ - json!({ - "type": "function_call", - "call_id": tool_result.call.id, - "name": tool_result.call.name, - "arguments": tool_result.call.arguments.to_string(), - }), - json!({ - "type": "function_call_output", - "call_id": tool_result.call.id, - "output": tool_result.output.to_string(), - }), - ] + .enumerate() + .flat_map(|(index, tool_result)| { + let round_text = if index == 0 && !text.is_empty() { + Some(text.clone()) + } else { + tool_result.text.clone() + }; + let mut items = vec![]; + if let Some(round_text) = round_text { + items.push(json!({ + "role": MessageRole::Assistant, + "content": strip_think_tag(&round_text), + })); + } + items.push(json!({ + "type": "function_call", + "call_id": tool_result.call.id, + "name": tool_result.call.name, + "arguments": tool_result.call.arguments.to_string(), + })); + items.push(json!({ + "type": "function_call_output", + "call_id": tool_result.call.id, + "output": tool_result.output.to_string(), + })); + items }) .collect(), MessageContent::Text(text) if role.is_assistant() && i != messages_len - 1 => { diff --git a/src/client/vertexai.rs b/src/client/vertexai.rs index d779c1e..ae0cc73 100644 --- a/src/client/vertexai.rs +++ b/src/client/vertexai.rs @@ -372,8 +372,15 @@ pub fn gemini_build_chat_completions_body( .collect(); vec![json!({ "role": role, "parts": parts })] }, - MessageContent::ToolCalls(MessageContentToolCalls { tool_results, .. }) => { - let model_parts: Vec = tool_results.iter().map(|tool_result| { + MessageContent::ToolCalls(MessageContentToolCalls { tool_results, text, .. }) => { + let mut model_parts: Vec = vec![]; + if !text.is_empty() { + model_parts.push(json!({ "text": text })); + } + for tool_result in tool_results.iter() { + if let Some(round_text) = &tool_result.text { + model_parts.push(json!({ "text": round_text })); + } let mut part = json!({ "functionCall": { "name": tool_result.call.name, @@ -383,8 +390,8 @@ pub fn gemini_build_chat_completions_body( if let Some(sig) = &tool_result.call.thought_signature { part["thoughtSignature"] = json!(sig); } - part - }).collect(); + model_parts.push(part); + } let function_parts: Vec = tool_results.into_iter().map(|tool_result| { json!({ "functionResponse": { diff --git a/src/function/mod.rs b/src/function/mod.rs index f26655d..4d1a23f 100644 --- a/src/function/mod.rs +++ b/src/function/mod.rs @@ -203,11 +203,17 @@ fn normalize_tool_result(result: Value) -> Value { pub struct ToolResult { pub call: ToolCall, pub output: Value, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub text: Option, } impl ToolResult { pub fn new(call: ToolCall, output: Value) -> Self { - Self { call, output } + Self { + call, + output, + text: None, + } } }