ag-ui-protocol / ag-ui-protocol/ag-ui
langgraph (python): streamed_tool_call_ids rebuilt per HTTP request re-emits TOOL_CALL_ARGS after an interrupt resume, corrupting stored arguments
- 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
`ag_ui_langgraph` (Python) rebuilds `active_run["streamed_tool_call_ids"]` as an empty set on every HTTP request. That reset is only safe while one logical turn fits in one request. A LangGraph `interrupt()` (human-in-the-loop) splits a turn across two requests: on the resume request the set is empty, the `OnToolEnd` re-emit fallback sees `already_streamed == False`, and `TOOL_CALL_START` / `TOOL_CALL_ARGS` / `TOOL_CALL_END` are re-emitted for a tool call the client already received during the first request.
`TOOL_CALL_ARGS` is a delta protocol keyed by tool-call id, so a persisting client (e.g. CopilotKit Cloud) **appends** rather than replaces — the stored arguments become two concatenated JSON documents (`{...}{...}`), which no longer parse. Because thread history replays on every later turn, the thread stays broken permanently (`json.loads` → `Extra data`) rather than failing once.
This is the **Python-adapter twin of #2014** (`@ag-ui/langgraph`'s `emittedToolCallStartIds` is per-run, not durable). The open fix PR #2166 touches only `integrations/langgraph/typescript/` — the Python adapter needs the same treatment.
## Steps to Reproduce
1. Python LangGraph agent with a HITL `interrupt()` inside a tool flow, fronted by `ag_ui_langgraph`, with a client that persists thread history.
2. Trigger the tool call; answer the interrupt so the run resumes in a second HTTP request.
3. Observe the resume request re-emit Start/Args/End for the already-delivered tool call; the persisted arguments are now two concatenated JSON documents.
## Expected Behavior
A resumed turn does not re-announce tool calls the client already holds. A tool call present in the inbound `RunAgentInput.messages` is by definition one the client already received — seeding `streamed_tool_call_ids` from the inbound history fixes this. We (Sameday AI) run exactly that in production as a subclass patch (seed the set in `prepare_stream`) and can contribute it.
## Environment
- ag-ui-langgraph 0.0.42 and 0.0.43 (latest — verified in 0.0.43's `agent.py`: the set is created empty per run and the `OnToolEnd` fallback gates re-emission only on it)
- langgraph 1.2.x, Python 3.14
Hướng dẫn đóng góp
Đánh giá
Issue này chưa được đánh giá.