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

Đang mở
#2,515 0 bình luận 0 reaction 0 người được giao Xem trên GitHub
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

Mở hướng dẫn đóng góp

Đánh giá

Issue này chưa được đánh giá.

Nhận issue mới trong hộp thư của bạn

Bản tóm tắt ngắn những issue GitHub phù hợp với người mới.