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

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

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

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions