Skip to content

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

@uesleilima

Description

@uesleilima

Pre-flight Checklist

  • I have searched existing issues and this hasn't been reported yet.
  • 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:
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
  1. Run the agent through ag-ui-langgraph.
  2. After the tool completes, the MessagesSnapshotEvent includes a ToolMessage with name=None.
  3. 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:

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

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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions