# Fix for Issue #936: Agent Tool Usage with Streaming Enabled ## Problem Summary - Agent tool usage callable doesn't work with streaming enabled - Works without streaming well - Tool execution logging disappeared ## Root Cause Analysis When streaming is enabled, the LLM response chunks are collected as plain text strings, losing the structured API response format that contains tool call metadata. The `tool_struct.execute_function_calls_from_api_response()` method expects structured responses with `tool_calls` attributes, but streaming responses only contained concatenated text content. ## Solution Implementation ### 1. Created StreamingToolResponse Class **File**: `swarms/structs/agent.py` (lines 91-104) ```python class StreamingToolResponse: """ Response wrapper that preserves both content and tool calls from streaming responses. This enables tool execution when streaming is enabled. """ def __init__(self, content: str, tool_calls: List[Any] = None): self.content = content self.tool_calls = tool_calls or [] def __str__(self): return self.content def __repr__(self): return f"StreamingToolResponse(content='{self.content[:50]}...', tool_calls={len(self.tool_calls)} calls)" ``` **Purpose**: Replace dynamic type creation with a proper class that preserves both content and tool calls from streaming responses. ### 2. Enhanced Streaming Chunk Processing **File**: `swarms/structs/agent.py` (lines 2600-2690) **Modified all three streaming paths in `call_llm` method**: #### A. Streaming with Callback (lines 2615-2625) ```python # Preserve tool calls from streaming chunks try: if (hasattr(chunk, "choices") and len(chunk.choices) > 0 and hasattr(chunk.choices[0], "delta") and hasattr(chunk.choices[0].delta, "tool_calls") and chunk.choices[0].delta.tool_calls): tool_calls.extend(chunk.choices[0].delta.tool_calls) except (AttributeError, IndexError) as e: logger.debug(f"Could not extract tool calls from chunk: {e}") ``` #### B. Silent Streaming (lines 2636-2646) - Same tool call preservation logic as above - Maintains streaming behavior while capturing tool calls #### C. Streaming Panel (lines 2658-2688) ```python # Create a tool-aware streaming processor to preserve tool calls def tool_aware_streaming(stream_response): for chunk in stream_response: # Preserve tool calls from streaming chunks with error handling try: if (hasattr(chunk, "choices") and len(chunk.choices) > 0 and hasattr(chunk.choices[0], "delta") and hasattr(chunk.choices[0].delta, "tool_calls") and chunk.choices[0].delta.tool_calls): tool_calls.extend(chunk.choices[0].delta.tool_calls) except (AttributeError, IndexError) as e: logger.debug(f"Could not extract tool calls from chunk: {e}") yield chunk ``` **Purpose**: Prevents iterator consumption bug while preserving tool calls. ### 3. Enhanced Tool Execution Logging **File**: `swarms/structs/agent.py` (lines 3109-3133) ```python # Add tool execution logging logger.info(f"Starting tool execution for agent '{self.agent_name}' in loop {loop_count}") # Enhanced retry logic with logging try: output = self.tool_struct.execute_function_calls_from_api_response(response) except Exception as e: logger.warning(f"First attempt at tool execution failed: {e}. Retrying...") try: output = self.tool_struct.execute_function_calls_from_api_response(response) except Exception as retry_error: logger.error(f"Tool execution failed after retry: {retry_error}") if output is None: raise retry_error # Log successful tool execution if output is not None: logger.info(f"Tool execution successful for agent '{self.agent_name}' in loop {loop_count}. Output length: {len(str(output)) if output else 0}") else: logger.warning(f"Tool execution completed but returned None output for agent '{self.agent_name}' in loop {loop_count}") ``` **Purpose**: Restore missing tool execution logging with comprehensive status reporting. ## Key Improvements ### 1. **Robust Error Handling** - Added try-catch blocks around tool call extraction - Graceful handling of malformed chunks - Protection against `AttributeError` and `IndexError` ### 2. **Iterator Safety** - Fixed streaming iterator consumption bug - Proper generator pattern to avoid iterator exhaustion ### 3. **Comprehensive Logging** - Tool execution start/success/failure logging - Retry attempt logging - Debug-level logging for chunk processing errors ### 4. **Backward Compatibility** - No changes to existing non-streaming behavior - Maintains all existing API contracts - Falls back gracefully when no tool calls present ## Testing Created two test files: ### 1. `test_streaming_tools.py` - Tests streaming behavior with and without tools - Validates tool execution occurs with streaming enabled - Checks memory history for tool execution evidence ### 2. `test_original_issue.py` - Reproduces exact code from GitHub issue #936 - Uses original function signatures and agent configuration - Validates the specific use case reported in the issue ## Files Modified 1. **`swarms/structs/agent.py`** - Added `StreamingToolResponse` class - Enhanced streaming chunk processing in `call_llm` method - Improved tool execution logging in `execute_tools` method 2. **Created Test Files** - `test_streaming_tools.py` - Comprehensive streaming + tool tests - `test_original_issue.py` - Reproduction of original issue scenario ## Verification The solution addresses both reported issues: ✅ **Tool Usage with Streaming**: Tool calls are now preserved and executed when streaming is enabled ✅ **Tool Execution Logging**: Comprehensive logging is now present throughout the tool execution process ## Edge Cases Handled 1. **Malformed Chunks**: Graceful error handling prevents crashes 2. **Empty Tool Calls**: Proper validation before processing 3. **Iterator Consumption**: Safe streaming processing without iterator exhaustion 4. **Mixed Content**: Handles chunks with both content and tool calls 5. **Multiple Tool Calls**: Supports multiple tool calls in single or multiple chunks ## Performance Impact - **Minimal**: Only additional memory for tool call arrays during streaming - **Efficient**: Tool call extraction only occurs when chunks contain them - **Scalable**: Handles multiple concurrent streaming agents safely