🔴 Required Information
Describe the Bug
When a single_turn LlmAgent is configured with an output_schema, the validated structured output is lost before it reaches downstream consumers.
The issue occurs because process_llm_agent_output() in _llm_agent_wrapper.py unconditionally sets:
event.node_info.message_as_output = True
even when the agent is producing structured output through output_schema.
Later, _consume_event_queue() in runners.py interprets message_as_output=True as a signal that the message content itself represents the node output and clears event.output to avoid duplication.
As a result, the structured output stored in event.output is overwritten with None.
The implementation also appears to contradict the documented intent in _consume_event_queue(), whose comments explicitly describe message_as_output as applying only to the "no output_schema" case.
Steps to Reproduce
-
Create an LlmAgent with:
mode="single_turn" (or use it as a workflow node)
- an
output_schema based on a Pydantic model
-
Execute the agent through a runner instance:
runner = InMemoryRunner(agent=agent)
events = await runner.run_async(...)
or:
runner = Runner(...)
events = await runner.run_async(...)
-
Inspect the emitted events.
-
Observe that:
is always None, even when the model successfully produces valid structured output.
Expected Behavior
When an output_schema is configured and validation succeeds:
should contain the validated structured result.
Downstream consumers should be able to access the structured output through the event stream.
Observed Behavior
For single_turn agents using output_schema:
is always None.
The structured output is generated correctly but is later removed by _consume_event_queue() because message_as_output was set unconditionally.
Environment Details
- ADK Library Version: Latest
main branch
- Desktop OS: N/A (identified via static code analysis)
- Python Version: N/A
Model Information
- Using LiteLLM: No
- Model: Any model used with
output_schema
🟡 Optional Information
Additional Context
The issue involves two locations.
1. src/google/adk/workflow/_llm_agent_wrapper.py
process_llm_agent_output() sets:
event.output = output
event.node_info.message_as_output = True
The flag is enabled regardless of whether the output is plain text or a validated structured result.
2. src/google/adk/runners.py
_consume_event_queue() contains logic similar to:
if not event.partial:
if event.node_info.message_as_output and event.content is not None:
event = event.model_copy()
event.output = None
This removes the structured output before the event is returned.
Documentation / Implementation Mismatch
The comment in _consume_event_queue() (approximately lines 788–794) states:
When an LlmAgent node uses message_as_output (no output_schema), the wrapper sets both event.content and event.output to the same text. event.output is cleared here to avoid rendering the same value twice.
This comment explicitly limits the behavior to the no output_schema case.
However, the current implementation sets message_as_output = True regardless of whether an output_schema exists.
Suggested Fix
Only mark the message as the output when no structured output schema is configured:
event.output = output
if not agent.output_schema:
event.node_info.message_as_output = True
Minimal Reproduction
from pydantic import BaseModel
from google.adk.agents import LlmAgent
from google.adk.runners import InMemoryRunner
class WeatherOutput(BaseModel):
temperature: float
condition: str
agent = LlmAgent(
name="weather_agent",
model="gemini-2.0-flash",
output_schema=WeatherOutput,
instruction="Return weather data as structured output.",
)
runner = InMemoryRunner(agent=agent)
events = await runner.run_async(
user_id="test",
session_id="test",
new_message=types.Content(
parts=[types.Part(text="What is the weather in Tokyo?")]
),
)
for event in events:
print(event.output) # Always None
Frequency
- Always reproducible (100%)
🔴 Required Information
Describe the Bug
When a
single_turnLlmAgentis configured with anoutput_schema, the validated structured output is lost before it reaches downstream consumers.The issue occurs because
process_llm_agent_output()in_llm_agent_wrapper.pyunconditionally sets:even when the agent is producing structured output through
output_schema.Later,
_consume_event_queue()inrunners.pyinterpretsmessage_as_output=Trueas a signal that the message content itself represents the node output and clearsevent.outputto avoid duplication.As a result, the structured output stored in
event.outputis overwritten withNone.The implementation also appears to contradict the documented intent in
_consume_event_queue(), whose comments explicitly describemessage_as_outputas applying only to the "no output_schema" case.Steps to Reproduce
Create an
LlmAgentwith:mode="single_turn"(or use it as a workflow node)output_schemabased on a Pydantic modelExecute the agent through a runner instance:
or:
Inspect the emitted events.
Observe that:
is always
None, even when the model successfully produces valid structured output.Expected Behavior
When an
output_schemais configured and validation succeeds:should contain the validated structured result.
Downstream consumers should be able to access the structured output through the event stream.
Observed Behavior
For
single_turnagents usingoutput_schema:is always
None.The structured output is generated correctly but is later removed by
_consume_event_queue()becausemessage_as_outputwas set unconditionally.Environment Details
mainbranchModel Information
output_schema🟡 Optional Information
Additional Context
The issue involves two locations.
1.
src/google/adk/workflow/_llm_agent_wrapper.pyprocess_llm_agent_output()sets:The flag is enabled regardless of whether the output is plain text or a validated structured result.
2.
src/google/adk/runners.py_consume_event_queue()contains logic similar to:This removes the structured output before the event is returned.
Documentation / Implementation Mismatch
The comment in
_consume_event_queue()(approximately lines 788–794) states:This comment explicitly limits the behavior to the no output_schema case.
However, the current implementation sets
message_as_output = Trueregardless of whether anoutput_schemaexists.Suggested Fix
Only mark the message as the output when no structured output schema is configured:
Minimal Reproduction
Frequency