ag-ui-protocol / ag-ui-protocol/ag-ui

[Bug]: ToolMessage.name=None on OnToolEnd causes downstream validation errors in MessagesSnapshot

Aperta
#1,742 0 commenti 0 reazioni 0 assegnatari Vedi su GitHub
bug Integration
Lingua principale
Python
Stelle
15.9k
Fork
1.4k
Merge medio
2g 41m
PR unite (30g)
165

Descrizione

### Pre-flight Checklist

- [x] I have searched [existing issues](https://github.com/ag-ui-protocol/ag-ui/issues) and this hasn't been reported yet.
- [x] I am using the **latest** version AG-UI.

### Describe the Bug

When a LangGraph tool returns a `ToolMessage` with `name=None` (which is a valid default — LangChain's `ToolMessage` has `name: Optional[str] = None`), the `OnToolEnd` handler in `agent.py` correctly falls back to `event.get("name", "")` when emitting `ToolCallStartEvent` (line 1243). However, **the `ToolMessage` object itself retains `name=None`** in the LangGraph checkpoint.

This causes two downstream problems:

1. **`langchain_messages_to_agui`** converts these messages for the `MessagesSnapshotEvent`. The AG-UI `ToolMessage` type expects a non-None `name` field, and `None` produces an invalid snapshot or a Pydantic `ValidationError` depending on the consumer.

2. **`Command` objects** — when a tool returns a `Command(update={"messages": [ToolMessage(tool_call_id=..., content=..., name=None)]})`, the `name=None` propagates through the same path.

The root cause is that the `OnToolEnd` handler only compensates for `None` at the *event emission* layer (`ToolCallStartEvent`) but does not fix the underlying `ToolMessage` object that gets committed to the checkpoint and later fed to `langchain_messages_to_agui`.

### Steps to Reproduce

1. Create a LangGraph tool that returns a `ToolMessage` without setting `name`:

```python
from langchain_core.messages import ToolMessage

def my_tool(query: str) -> ToolMessage:
return ToolMessage(content="result", tool_call_id="tc-123")
# name defaults to None
```

2. Run the agent through `ag-ui-langgraph`.
3. After the tool completes, the `MessagesSnapshotEvent` includes a `ToolMessage` with `name=None`.
4. Depending on the AG-UI client, this causes a validation error or renders incorrectly.

### Expected Behavior

The `OnToolEnd` handler should patch `ToolMessage.name` on the actual message object (not just on the event), so that downstream consumers (especially `langchain_messages_to_agui` for `MessagesSnapshotEvent`) see a valid name.

Suggested fix — patch the `ToolMessage` before processing:

```python
elif event_type == LangGraphEventTypes.OnToolEnd:
tool_call_output = event["data"]["output"]
tool_name = event.get("name") or "unknown"

# Patch ToolMessage.name when None to avoid downstream validation errors
if isinstance(tool_call_output, ToolMessage) and tool_call_output.name is None:
tool_call_output.name = tool_name
elif isinstance(tool_call_output, Command) and tool_call_output.update:
for msg in tool_call_output.update.get("messages", []):
if isinstance(msg, ToolMessage) and msg.name is None:
msg.name = tool_name
# ... rest of handler
```

### Environment

```text
ag-ui-langgraph: 0.0.35
ag-ui-protocol: 0.1.18
langgraph: 0.4.7
langchain-core: 0.3.59
Python: 3.13
```

### Additional Context

Workaround: override `_handle_single_event` and patch `ToolMessage.name` before delegating to the base class.

Happy to send a PR with the fix and a regression test if assigned.

Guida per i contributori

Apri la guida per i contributori

Valutazione

Questa issue non è ancora stata valutata.

Ricevi le nuove issue nella tua casella

Un breve riepilogo di issue GitHub adatte ai principianti.