You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
172 lines
6.4 KiB
172 lines
6.4 KiB
# 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 |