Skip to content

No answer received when streaming reasoning + enforcing output schema + using tools #3599

Description

@ramcav

Describe the bug
When using output_schema with streaming mode (StreamingMode.SSE), function calls from set_model_response are being lost or processed incorrectly. This means that the final response contains the complete set of streamed thoughts but an empty string for the answer, causing:

  1. Function calls not being registered: Despite the raw GenAI API response containing function calls, they do not appear in the final Event objects
  2. Early stream termination: The streaming loop breaks early before receiving the function call response
  3. Parsing errors: When trying to parse the final response into the expected Pydantic model, the missing function calls makes it so an empty string tries to get parsed (resulting in a ValidationError)

To Reproduce

Steps to reproduce the behavior:

  1. Install google-adk latest version

  2. Create an agent with output_schema and enable streaming:

    from pydantic import BaseModel, Field
    from google.adk.agents import LlmAgent
    from google.adk.runners import Runner
    from google.adk.agents.run_config import RunConfig, StreamingMode
    from google.adk.sessions import InMemorySessionService
    from google.adk.planners import BuiltInPlanner
    from google.genai.types import GenerateContentConfig, ThinkingConfig, Content, Part
    
    # Define output schema
    class ShoppingItem(BaseModel):
        name: str
        quantity: int
        price: float
    
    class ShoppingList(BaseModel):
        items: list[ShoppingItem]
        total_price: float
        complexity: str = Field(description="The complexity of the shopping list, either 'easy', 'medium', or 'hard'")
    
    # Create agent with output_schema
    planner = BuiltInPlanner(
        thinking_config=ThinkingConfig(include_thoughts=True, thinking_budget=-1),
    )
    
    agent = LlmAgent(
        name="shopper",
        model="gemini-2.0-flash-exp",  # or any model that supports function calling and will
        description="AI shopping list assistant",
        instruction="You are a helpful shopping list assistant.",
        generate_content_config=GenerateContentConfig(temperature=0.2, seed=42),
        output_schema=ShoppingList,
        planner=planner,
        tools=[tool1, tool2, ...]
    )
    
    # Create runner and stream
    session_service = InMemorySessionService()
    await session_service.create_session(
        app_name="test",
        user_id="test",
        session_id="test",
    )
    
    runner = Runner(
        agent=agent,
        session_service=session_service,
        app_name="test",
    )
    
    event_stream = runner.run_async(
        user_id="test",
        session_id="test",
        new_message=Content(role="user", parts=[Part(text="I need you to give me shopping list for reina pepiada")]),
        run_config=RunConfig(streaming_mode=StreamingMode.SSE),
    )
  3. Iterate through the event stream and check for function calls:

    event_count = 0
    function_call_count = 0
    
    async for event in event_stream:
        event_count += 1
        function_calls = event.get_function_calls()
        
        if function_calls:
            function_call_count += 1
            print(f"✅ Event {event_count}: Found {len(function_calls)} function call(s)")
        else:
            # Check for thoughts or text
            if event.content and event.content.parts:
                for part in event.content.parts:
                    if hasattr(part, 'text') and part.text:
                        is_thought = getattr(part, 'thought', False)
                        print(f"📝 Event {event_count}: {'Thought' if is_thought else 'Text'}")
    
    print(f"Total events: {event_count}")
    print(f"Events with function_calls: {function_call_count}")
    # Output: Events with function_calls: 0
    # Expected: Events with function_calls: 1 (with set_model_response)
  4. Observed behavior: The stream completes with 0 function calls detected, even though the raw GenAI API response contains a set_model_response function call with the structured JSON output.

  5. Error or stacktrace: A Pydantic Validation error should arise when trying to parse the response

Expected behavior

When using output_schema with streaming:

  • Function calls should be properly detected and yielded in the event stream
  • The streaming loop should not terminate prematurely before receiving the function call
  • Function call responses should not be duplicated
  • The final structured response from set_model_response should be accessible via event.get_function_calls() or event.content.parts with function_response

Metadata

Metadata

Labels

core[Component] This issue is related to the core interface and implementationneeds review[Status] The PR/issue is awaiting review from the maintainer

Type

Fields

No fields configured for Bug.

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions