mirror of
https://github.com/langchain-ai/langgraph.git
synced 2026-08-26 09:32:25 +02:00
## Summary Refactors the four (now five) sdk-py streaming projections into reusable, transport-agnostic `Decoder` classes and adds a new `interleave_projections(channels)` method to `AsyncThreadStream` and `SyncThreadStream` that drives multiple decoders from one shared subscription, yielding `(channel_name, item)` tuples in arrival order (the SDK analog of local `GraphRunStream.interleave`). - **New `langgraph_sdk/stream/decoders.py`**: pure `feed(event) -> Iterable[item]` state machines — `ValuesDecoder`, `MessagesDecoder`, `ToolCallsDecoder`, `SubgraphsDecoder`, `ExtensionsDecoder` — behind a `Decoder` Protocol. No subscription/queue/thread access. - **Projection migration (async + sync)**: the five existing projections now delegate their per-event logic to the decoders. Behavior-preserving — the existing test suite is the regression net. Thread-coupled side effects (active-stream registration, terminal-error-on-close, root-inbox forwarding) stay in the projection wrappers; sync messages/tool_calls keep their pre-dispatch contract (handle/stream resolved on yield) via a FIFO-head buffer. - **`interleave_projections`**: flat-namespace channel list (built-ins + extension names), `tool_calls`↔`tools` wire mapping, subgraphs fed every event, extensions keyed by bare name. ### Notable - Fixes a latent sync bug surfaced by the refactor: two tool calls / messages whose events interleave previously dropped the second; both now surface. Locked in with a regression test. - `Decoder.feed` takes `Mapping[str, Any]` (read-only), so the Protocol is load-bearing in both stream files. ### Deferred (not in this PR) - Wiring `RemoteGraph._RemoteGraphRunStream.interleave` to `interleave_projections` (gated on #7927). - Migrating the handle-scoped projections (`_Handle*Projection`) to the decoders — hence the small, verified-identical helper duplication between `decoders.py` and `_async/stream.py`. - `interleave_projections` handles aren't registered for thread-close cleanup (additive follow-up). ## Test Plan - [x] `make test` in `libs/sdk-py` — 464 passed, 0 failures - [x] `make format` / `make lint` (ruff + ty) clean - [x] Per-decoder unit tests in `tests/streaming/test_decoders.py` - [x] `interleave_projections` tests (single-channel, multi-channel arrival order, builtin+extension mix, tool_calls public-name, subgraphs discovery) async + sync - [x] Existing projection suites pass unchanged (regression net for the migration)