google / google/adk-python

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

Offen
#6,089 2 Kommentare 0 Reaktionen 1 zugewiesene Person Beansprucht von @sanketpatil06 Auf GitHub ansehen
core needs review
Vorherrschende Sprache
Python
Sterne
21.5k
Forks
4k
Ø Merge
1 T. 14 Std.
Gemergte PRs (30 T.)
37

Beschreibung

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

Beitragsleitfaden

Beitragsleitfaden öffnen

Bewertung

Dieses Issue wurde noch nicht bewertet.

Neue Issues direkt in Ihr Postfach

Eine kurze Übersicht über anfängerfreundliche GitHub-Issues.