Skip to content

[Bug] Tool function names become empty strings in streaming responses #6757

@huangqz71

Description

@huangqz71

Description

When using Agno Agent with tools in streaming mode, particularly with Claude models via LiteLLM, tool function names are being overwritten with empty strings, causing LLM API validation errors.

In streaming responses, the parse_tool_calls method incorrectly handles tool metadata that spans multiple stream chunks. When tool information is fragmented across chunks (name in early chunk, arguments in later chunks), the method overwrites the valid tool name with an empty string from a later chunk.

Steps to Reproduce

from agno.agent import Agent
from agno.models.litellm import LiteLLM
from agno.tools import tool

@tool(name="add", description="Add two numbers")
def add_func(a: int, b: int) -> str:
    return f"{a} + {b} = {a+b}"

llm = LiteLLM(id="litellm_proxy/claude-sonnet-4-5", api_key="your-key")
agent = Agent(model=llm, tools=[add_func])

# Run in streaming mode
for event in agent.run("What is 5 + 3?", stream=True):
    pass

Agent Configuration (if applicable)

No response

Expected Behavior

The agent should successfully invoke the add tool and return the correct result.

Actual Behavior

ERROR: litellm.BadRequestError: Litellm_proxyException - 2 validation errors detected:
Value at 'messages.2.member.content.1.member.toolUse.name' failed to satisfy constraint: 
Member must have length greater than or equal to 1

Screenshots or Logs (if applicable)

No response

Environment

- **Agno version:** 2.5.5 (confirmed with bug)
- **LiteLLM version:** Latest
- **Python version:** 3.12
- **Models affected:** Claude (all versions through LiteLLM)

Possible Solutions (optional)

Root Cause Analysis

Stream Chunks Structure

LiteLLM splits Claude's tool call response into multiple chunks:

Chunk 1: {index: 1, id: "tooluse_...", type: "function", function: {name: "add", arguments: ""}}
Chunk 2: {index: 1, id: None, type: "function", function: {name: "", arguments: "{\"a\": 5"}}
Chunk 3: {index: 1, id: None, type: "function", function: {name: "", arguments: ", \"b\": 3}"}}

Bug in parse_tool_calls

In agno/models/litellm.py, the parse_tool_calls method (around line 420-450):

# Current problematic code:
if function_data.get("name") is not None:
    name = function_data.get("name", "")
    tool_calls_by_index[index]["function"]["name"] = name

The Problem:

  • When function_data.get("name") returns an empty string "", the condition "" is not None evaluates to True
  • This causes the previously captured valid name (e.g., "add") to be overwritten with the empty string
  • The final result has name: "" instead of name: "add"

Execution Flow:

  1. Chunk 1: name="add" → condition True → sets name="add"
  2. Chunk 2: name="" → condition True (because "" is not None) → overwrites to name=""
  3. Chunk 3: name="" → condition True → keeps name=""

Solution

Change the condition to check for truthiness instead of None:

# Fixed code:
if function_data.get("name"):  # Only update if name is non-empty
    name = function_data.get("name", "")
    tool_calls_by_index[index]["function"]["name"] = name

This way:

  • Chunk 1: name="add" → truthy → sets name="add"
  • Chunk 2: name="" → falsy → skips update, preserves "add"
  • Chunk 3: name="" → falsy → keeps "add"

Additional Context

Affected Models

  • Primary: Claude (via LiteLLM's Claude provider)
  • All providers using Claude: AWS Bedrock, Anthropic API, etc.
  • Not affected: Gemini, other models (they handle tool metadata differently)

Impact Level

  • Severity: High (breaks all tool calling functionality with Claude in streaming mode)
  • Scope: Affects anyone using Agno Agent with Claude models and tools in streaming mode
  • Workaround: Available (override parse_tool_calls in subclass)

Proposed Fix

File: agno/models/litellm.py
Method: parse_tool_calls (static method)
Line: Change if function_data.get("name") is not None: to if function_data.get("name"):

Testing

Add test case for streaming tool calls with fragmented metadata:

def test_parse_tool_calls_with_empty_strings():
    """Test that empty strings don't overwrite valid tool names in streaming chunks"""
    # Simulates chunks received from Claude API via LiteLLM
    chunks = [
        {"index": 0, "id": "id_1", "function": {"name": "add", "arguments": ""}},
        {"index": 0, "id": None, "function": {"name": "", "arguments": "{\"a\""}},
        {"index": 0, "id": None, "function": {"name": "", "arguments": ": 5}"}},
    ]
    
    result = LiteLLM.parse_tool_calls(chunks)
    
    # Current bug: name becomes ""
    # Expected: name should remain "add"
    assert result[0]["function"]["name"] == "add", f"Got empty string, expected 'add'"
    assert result[0]["function"]["arguments"] == '{"a": 5}'

Reproducibility

  • Status: Consistently reproducible in Agno 2.5.5
  • Impact: 100% failure rate when calling Claude tools in streaming mode with Agent
  • Non-streaming mode: Not affected (tool calls work correctly)

Version History

This bug appears to be introduced in Agno 2.5.x based on the streaming response handling implementation. Earlier versions may not have this issue if they used non-streaming or had different tool call parsing logic.

References

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions