From 64d2ac53a8eb4473b62b1186f850a5c66e358850 Mon Sep 17 00:00:00 2001 From: Dhanji Prasanna Date: Mon, 22 Sep 2025 14:25:04 +1000 Subject: [PATCH] tweaks for streaming toolcalls --- crates/g3-core/src/lib.rs | 5 ++++- crates/g3-providers/src/anthropic.rs | 30 +++++++++++++++++++++------- 2 files changed, 27 insertions(+), 8 deletions(-) diff --git a/crates/g3-core/src/lib.rs b/crates/g3-core/src/lib.rs index 2cf7453..74538be 100644 --- a/crates/g3-core/src/lib.rs +++ b/crates/g3-core/src/lib.rs @@ -580,6 +580,7 @@ IMPORTANT: You must call tools to complete tasks. When you receive a request: For shell commands: Use the shell tool with the exact command needed. Avoid commands that produce a large amount of output, and consider piping those outputs to files. Example: If asked to list files, immediately call the shell tool with command parameter \"ls\". For task completion: Use the final_output tool with a summary. +IMPORTANT: If the user asks you to just respond with text (like \"just say hello\" or \"tell me about X\"), do NOT use tools. Simply respond with the requested text directly. Only use tools when you need to execute commands or complete tasks that require action. Do not explain what you're going to do - just do it by calling the tools.".to_string() } else { @@ -1068,8 +1069,10 @@ The tool will execute immediately and you'll receive the result (success or erro .replace("<>", ""); if !clean_content.is_empty() { + debug!("Printing clean content: '{}'", clean_content); print!("{}", clean_content); - io::stdout().flush()?; + let _ = io::stdout().flush(); // Force immediate output + debug!("Flushed {} characters to stdout", clean_content.len()); current_response.push_str(&clean_content); } } diff --git a/crates/g3-providers/src/anthropic.rs b/crates/g3-providers/src/anthropic.rs index 5fc9be7..b1b5b75 100644 --- a/crates/g3-providers/src/anthropic.rs +++ b/crates/g3-providers/src/anthropic.rs @@ -338,19 +338,34 @@ impl AnthropicProvider { match content_block { AnthropicContent::ToolUse { id, name, input } => { debug!("Found tool use in content_block_start: id={}, name={}, input={:?}", id, name, input); - debug!("Input JSON string: {}", serde_json::to_string(&input).unwrap_or_else(|_| "failed to serialize".to_string())); - // Create initial tool call - we'll update the args later from streaming JSON + // For native tool calls, create the tool call immediately if we have complete args + // If args are empty, we'll wait for partial_json to accumulate them let tool_call = ToolCall { id: id.clone(), tool: name.clone(), - args: input, // This might be empty initially + args: input.clone(), }; - debug!("Created initial tool call: {:?}", tool_call); - current_tool_calls.push(tool_call); - // Reset partial JSON accumulator for this tool - partial_tool_json.clear(); + // Check if we already have complete arguments + if !input.is_null() && input != serde_json::Value::Object(serde_json::Map::new()) { + // We have complete arguments, send the tool call immediately + debug!("Tool call has complete args, sending immediately: {:?}", tool_call); + let chunk = CompletionChunk { + content: String::new(), + finished: false, + tool_calls: Some(vec![tool_call]), + }; + if tx.send(Ok(chunk)).await.is_err() { + debug!("Receiver dropped, stopping stream"); + return; + } + } else { + // Arguments are empty, we'll accumulate them from partial_json + debug!("Tool call has empty args, will accumulate from partial_json"); + current_tool_calls.push(tool_call); + partial_tool_json.clear(); + } } _ => { debug!("Non-tool content block: {:?}", content_block); @@ -361,6 +376,7 @@ impl AnthropicProvider { "content_block_delta" => { if let Some(delta) = event.delta { if let Some(text) = delta.text { + debug!("Sending text chunk of length {}: '{}'", text.len(), text); let chunk = CompletionChunk { content: text, finished: false,