google / google/adk-python

message_as_output=True unconditionally set, destroying structured event.output for single_turn agents with output_schema

Đang mở
#6,089 2 bình luận 0 reaction 1 người được giao Được @sanketpatil06 nhận Xem trên GitHub
core needs review
Ngôn ngữ chính
Python
Star
21.5k
Fork
4k
Merge trung bình
1 ngày 14 giờ
Pull request đã merge (30 ngày)
37

Mô tả

## 🔴 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%)**

Hướng dẫn đóng góp

Mở hướng dẫn đóng góp

Đánh giá

Issue này chưa được đánh giá.

Nhận issue mới trong hộp thư của bạn

Bản tóm tắt ngắn những issue GitHub phù hợp với người mới.