ag-ui-protocol / ag-ui-protocol/ag-ui
[Bug]: ToolMessage.name=None on OnToolEnd causes downstream validation errors in MessagesSnapshot
- Ngôn ngữ chính
- Python
- Star
- 15.9k
- Fork
- 1.4k
- Merge trung bình
- 1 ngày 17 giờ
- Pull request đã merge (30 ngày)
- 163
Mô tả
### 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.
Hướng dẫn đóng góp
Đánh giá
Issue này chưa được đánh giá.