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

Aperta
#2,178 2 commenti 0 reazioni 0 assegnatari Vedi su GitHub
bug Integration
Lingua principale
Python
Stelle
15.9k
Fork
1.4k
Merge medio
1g 17h
PR unite (30g)
163

Descrizione

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

Guida per i contributori

Apri la guida per i contributori

Valutazione

Questa issue non è ancora stata valutata.

Ricevi le nuove issue nella tua casella

Un breve riepilogo di issue GitHub adatte ai principianti.