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

[Bug]: ag-ui-langgraph omits TOOL_CALL_ARGS when tool arguments are returned atomically

Ouverte
#2,463 0 commentaires 1 réaction 0 personnes assignées Voir sur GitHub
bug
Langage dominant
Python
Étoiles
15.9k
Forks
1.4k
Merge moyen
1 j 17 h
PR mergées (30 j)
163

Description

### 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

## Title

[Bug]: ag-ui-langgraph omits TOOL_CALL_ARGS when tool arguments are returned atomically

### Pre-flight Checklist

* [x] I have searched existing issues and this exact case has not been reported.
* [x] I am using the latest version of `ag-ui-langgraph`.

### Describe the Bug

Some models/providers do not stream tool-call arguments through multiple incremental `tool_call_chunks`.

Instead, the complete arguments become available atomically when the tool call is finalized or when LangGraph emits the tool execution event.

In this case, `ag-ui-langgraph` emits:

```text
TOOL_CALL_START
TOOL_CALL_END
```

but does not emit any `TOOL_CALL_ARGS` event.

Example SSE output:

```text
data: {"type":"RUN_STARTED","threadId":"thread-001","runId":"171621bb-6c80-4e8c-a031-e34f33a03913"}

data: {"type":"STEP_STARTED","stepName":"model"}

data: {"type":"TOOL_CALL_START","toolCallId":"call_52f5621cc5fd4f5d893024e2","toolCallName":"get_weather","parentMessageId":"lc_run--01a01982-bf07-71e0-a620-868b71649f57"}

data: {"type":"TOOL_CALL_END","toolCallId":"call_52f5621cc5fd4f5d893024e2"}
```

The tool is executed with valid arguments, and the complete input is available from the corresponding LangGraph tool event, for example:

```python
event["event"] == "on_tool_start"
event["data"]["input"] == {
"location": "Chengdu",
"date": "tomorrow",
}
```

However, those arguments are not translated into an AG-UI `TOOL_CALL_ARGS` event.

As a result, an AG-UI client receives the tool name and tool-call ID but cannot reconstruct the arguments used for the invocation.

This is especially problematic for:

* Frontend tool-call rendering
* Human-in-the-loop approval interfaces
* Persisting complete assistant tool calls
* Replaying tool calls
* Client-side tool execution

According to the AG-UI tool-call lifecycle, tool calls should be represented as:

```text
TOOL_CALL_START → TOOL_CALL_ARGS (one or more) → TOOL_CALL_END
```

The arguments do not need to arrive incrementally. If a provider exposes the complete arguments atomically, the adapter can emit the complete serialized JSON value as a single `TOOL_CALL_ARGS.delta`.

Reference:

https://docs.ag-ui.com/concepts/tools

### Steps to Reproduce

1. Create a LangChain/LangGraph agent with a tool:

```python
from langchain.tools import tool

@tool
def get_weather(location: str, date: str) -> str:
"""Get the weather for a location and date."""
return "Sunny"
```

2. Use a model/provider that returns complete tool arguments atomically instead of emitting incremental argument chunks.

3. Wrap the graph with `LangGraphAgent`.

4. Send a message that triggers the tool:

```text
What will the weather be in Chengdu tomorrow?
```

5. Inspect the AG-UI SSE stream.

6. Observe that `TOOL_CALL_START` and `TOOL_CALL_END` are emitted, but no `TOOL_CALL_ARGS` event is emitted.

### Actual Behavior

```text
TOOL_CALL_START
TOOL_CALL_END
```

The frontend has no access to the invocation arguments.

### Expected Behavior

Even when the underlying model does not stream argument chunks, `ag-ui-langgraph` should emit one `TOOL_CALL_ARGS` event containing the complete serialized arguments:

```text
data: {"type":"TOOL_CALL_START","toolCallId":"call_52f5621cc5fd4f5d893024e2","toolCallName":"get_weather","parentMessageId":"lc_run--01a01982-bf07-71e0-a620-868b71649f57"}

data: {"type":"TOOL_CALL_ARGS","toolCallId":"call_52f5621cc5fd4f5d893024e2","delta":"{\"location\":\"Chengdu\",\"date\":\"tomorrow\"}"}

data: {"type":"TOOL_CALL_END","toolCallId":"call_52f5621cc5fd4f5d893024e2"}
```

This would normalize the different streaming behaviors of model providers into the standard AG-UI event lifecycle.

### Possible Cause

The current implementation may treat the presence of `TOOL_CALL_START` as evidence that the complete tool call, including its arguments, has already been streamed.

For providers that emit the tool name and ID first but expose the arguments only when the invocation is finalized, this may cause the fallback path to skip emitting `TOOL_CALL_ARGS`.

### Suggested Fix

Track argument emission per `toolCallId`.

If no `TOOL_CALL_ARGS` event has been emitted when the complete tool input becomes available, for example in `on_tool_start` or `on_tool_end`, emit a single fallback event before `TOOL_CALL_END`:

```python
ToolCallArgsEvent(
type=EventType.TOOL_CALL_ARGS,
tool_call_id=tool_call_id,
delta=json.dumps(tool_input),
)
```

The fallback should only run when no streamed argument deltas have already been emitted for the same `toolCallId`, to avoid duplicate arguments.

### Environment

```text
ag-ui-langgraph: 0.0.43
ag-ui-protocol:
langchain:
langchain-core:
langgraph:
langchain-openai:
Python:

Model:
Provider/API endpoint:
```

### Related Issues

* #1678 addressed a different case where the complete `TOOL_CALL_START` / `TOOL_CALL_ARGS` / `TOOL_CALL_END` sequence was dropped during a text-to-tool transition.
* This issue is specifically about streams where `TOOL_CALL_START` and `TOOL_CALL_END` are present, but `TOOL_CALL_ARGS` alone is missing.

I would be happy to add a regression test and submit a PR if this behavior is confirmed as a bug.

### Steps to Reproduce

1. Define a LangChain tool that accepts structured arguments:

```python
@tool
def get_weather(location: str, date: str) -> str:
"""Get the weather for a location and date."""
return "Sunny"
```

2. Create a LangChain agent using GLM-5.3 through the Z.AI OpenAI-compatible API.

3. Wrap the agent graph with `ag_ui_langgraph.LangGraphAgent` and expose it through a FastAPI `/agent` endpoint.

4. Send a user message that triggers the tool:

```text
What will the weather be in Chengdu tomorrow?
```

5. Inspect the SSE events returned by the AG-UI endpoint.

6. The tool is successfully invoked with complete arguments, but the emitted events contain only:

```text
data: {"type":"TOOL_CALL_START","toolCallId":"call_52f5621cc5fd4f5d893024e2","toolCallName":"get_weather","parentMessageId":"lc_run--01a01982-bf07-71e0-a620-868b71649f57"}

data: {"type":"TOOL_CALL_END","toolCallId":"call_52f5621cc5fd4f5d893024e2"}
```

No `TOOL_CALL_ARGS` event is emitted between `TOOL_CALL_START` and `TOOL_CALL_END`.

The model/provider returns the complete tool arguments atomically rather than streaming them as incremental argument chunks.

### Expected Behavior

`ag-ui-langgraph` should always emit the tool arguments through at least one `TOOL_CALL_ARGS` event.

When the model/provider returns the complete arguments atomically instead of streaming incremental argument chunks, the adapter should serialize the complete arguments as a single `TOOL_CALL_ARGS.delta`.

Expected event sequence:

```text
data: {"type":"TOOL_CALL_START","toolCallId":"call_52f5621cc5fd4f5d893024e2","toolCallName":"get_weather","parentMessageId":"lc_run--01a01982-bf07-71e0-a620-868b71649f57"}

data: {"type":"TOOL_CALL_ARGS","toolCallId":"call_52f5621cc5fd4f5d893024e2","delta":"{\"location\":\"Chengdu\",\"date\":\"tomorrow\"}"}

data: {"type":"TOOL_CALL_END","toolCallId":"call_52f5621cc5fd4f5d893024e2"}
```

This allows AG-UI clients to reconstruct the complete tool call regardless of whether the underlying model streams the arguments incrementally or returns them all at once.

### Environment

```text
AG-UI package(s) & version(s):
- ag-ui-langgraph: 0.0.43
Runtime:
- Python: >=3.12
- FastAPI: >=0.115.12

Agent framework:
- LangChain: >=1.3.15
- langchain-core: >=1.5.6
- langchain-openai: >=1.5.2

Model:
- GLM-5.3

Provider:
- Z.AI OpenAI-compatible API

Server:
- FastAPI
- Uvicorn: >=0.52.4

Operating system:
- macOS
```

### Screenshots

_No response_

### Logs & Errors

```shell

```

### Additional Context

_No response_

Guide de contribution

Ouvrir le guide de contribution

Évaluation

Cette issue n'a pas encore été évaluée.

Recevez les nouvelles issues par e-mail

Un résumé court des issues GitHub adaptées aux débutants.