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

ag-ui-langgraph: multi-interrupt resume (__agui_resume_map__) rejected by LangGraph 1.x id-keyed resume requirement

オープン
#2,178 コメント 2 件 リアクション 0 件 担当者 0 名 GitHub で見る
bug Integration
主要言語
Python
スター
15.9k
フォーク
1.4k
平均マージ
1日 17時間
マージ済み PR(30日)
163

説明

## Summary

`ag-ui-langgraph`'s multi-interrupt resume path is incompatible with LangGraph 1.x. When a thread has **more than one pending interrupt** (e.g. an `AIMessage` with several parallel tool calls that each call `interrupt()`), resuming via `RunAgentInput.resume = [ResumeEntry, ...]` raises before any `interrupt()` handler runs:

```
RuntimeError: When there are multiple pending interrupts, you must specify the interrupt id when resuming.
```

The single-interrupt path works; only the N>1 path is affected.

## Root cause

`LangGraphAgent._build_command_from_agui_resume` (`integrations/langgraph/python/ag_ui_langgraph/agent.py`, ~L1181-1195) folds N>1 resume entries into:

```python
Command(resume={"__agui_resume_map__": {interrupt_id: {"status": ..., "payload": ...}, ...}})
```

That value is passed straight to the graph (`agent.py` ~L626) with no unwrap. But LangGraph 1.x requires, when multiple interrupts are pending, that the resume dict be **keyed by interrupt id** (native keyed resume: `Command(resume={interrupt_id: value})`); a non-id key like `"__agui_resume_map__"` is rejected. So the documented behavior — README "Resuming via AG-UI standard `resume[]`" states that for *multiple entries* each `interrupt()` returns `{"__agui_resume_map__": {...}}` and the handler self-selects — can never be reached on LangGraph 1.x: the run errors before the handler executes.

## Reproduce

- A LangGraph `create_react_agent` (or any graph) whose turn fans out **two** tool calls, each calling `interrupt()`.
- Resume with `RunAgentInput.resume = [ResumeEntry(interrupt_id=, status="resolved", payload=...), ResumeEntry(interrupt_id=, status="resolved", payload=...)]`.
- Result: `RuntimeError: When there are multiple pending interrupts, you must specify the interrupt id when resuming.`

Confirmed on **langgraph 1.0.7** with `ag-ui-langgraph` at commit `d2049deb` (its `pyproject` declares `langgraph>=0.6.0,<2`). I did not pin the exact langgraph version at which the id-keyed requirement was introduced, so I can't say precisely which point in the declared `<2` range it starts failing — but it fails across langgraph 1.x. Note also `test_interrupt_handling.py` observes *"Without a real LangGraph id we can't round-trip a resume answer"*, i.e. the N>1 resume round-trip isn't covered by the existing tests, which is likely why this went unnoticed.

## Suggested fix

Have `_build_command_from_agui_resume` build the **framework-native id-keyed** resume for the N>1 case (and, uniformly, for the single case):

```python
Command(resume={entry.interrupt_id: for entry in resume_entries})
```

so LangGraph routes each pending `interrupt()` its own value directly.

**Behavior-change note:** this changes the documented multi-interrupt handler contract — with the native-keyed form, each `interrupt()` receives *its own* payload directly, rather than the full `__agui_resume_map__` that handlers currently self-select from. That is arguably a simplification (no handler-side map lookup), and it appears to be the only option compatible with LangGraph's id-keyed multi-interrupt resume — but it's a contract change worth a deliberate call + a README update.

## Context

Found while adopting #1945's canonical `RunFinished.outcome` + `RunAgentInput.resume[]` protocol in a downstream LangGraph agent. We currently work around it by subclassing `LangGraphAgent` and overriding `_build_command_from_agui_resume` to emit the native id-keyed form; happy to open a PR against the base method if the behavior change above is acceptable.

コントリビューションガイド

コントリビューションガイドを開く

評価

この issue はまだ評価されていません。

新しい issue をメールで受け取る

初心者向けの GitHub issue を短くまとめたダイジェスト。