message_as_output=True unconditionally set, destroying structured event.output for single_turn agents with output_schema
- 主要言語
- Python
- スター
- 21.5k
- フォーク
- 4k
- 平均マージ
- 1日 14時間
- マージ済み PR(30日)
- 37
説明
## 🔴 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:
```python
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
1. Create an `LlmAgent` with:
* `mode="single_turn"` (or use it as a workflow node)
* an `output_schema` based on a Pydantic model
2. Execute the agent through a runner instance:
```python
runner = InMemoryRunner(agent=agent)
events = await runner.run_async(...)
```
or:
```python
runner = Runner(...)
events = await runner.run_async(...)
```
3. Inspect the emitted events.
4. Observe that:
```python
event.output
```
is always `None`, even when the model successfully produces valid structured output.
### Expected Behavior
When an `output_schema` is configured and validation succeeds:
```python
event.output
```
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`:
```python
event.output
```
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:
```python
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:
```python
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:
```python
event.output = output
if not agent.output_schema:
event.node_info.message_as_output = True
```
### Minimal Reproduction
```python
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%)**
コントリビューションガイド
評価
この issue はまだ評価されていません。