refactor(channels): DeltaChannel batch reducer interface + _messages_delta_reducer

Renames `operator` → `reducer` and flips arg order to `(reducer, typ=None)`,
matching the new batch contract: `reducer(state, list[writes]) -> state`. The
reducer receives all writes for a step in one call instead of being folded
pairwise, enabling single-pass implementations that avoid O(N²) reprocessing.

`typ` is now optional — `_is_field_channel` in `graph/state.py` always
overwrites it from the `Annotated[T, ...]` outer type, so users can write
`DeltaChannel(my_reducer)` rather than `DeltaChannel(list, my_reducer)`.

Adds `_messages_delta_reducer` to `langgraph.graph.message` (experimental):
a single-pass bulk reducer for message lists that deduplicates by ID and
handles `RemoveMessage` tombstoning without calling `add_messages`, avoiding
repeated dedup passes that `add_messages` would incur in a fold.

Also fixes the `_delta_write_futs` mypy error in `AsyncPregelLoop` by moving
the type annotation to the class body, and unignores `new_pr_desc.md` from
the repo via `.gitignore`.

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Sydney Runkle
2026-04-29 12:58:49 -04:00
co-authored by Claude Sonnet 4.6
parent 959c8c8618
commit 0e5d61692e
10 changed files with 211 additions and 124 deletions
+2 -2
View File
@@ -385,11 +385,11 @@ async def test_delta_channel_chain_reconstruction(saver_name: str) -> None:
from langchain_core.messages import AIMessage, HumanMessage
from langgraph.channels.delta import DeltaChannel
from langgraph.graph import START, StateGraph
from langgraph.graph.message import add_messages
from langgraph.graph.message import _messages_delta_reducer
from typing_extensions import TypedDict
class State(TypedDict):
messages: Annotated[list, DeltaChannel(list, add_messages)]
messages: Annotated[list, DeltaChannel(_messages_delta_reducer)]
def respond(state: State) -> dict:
n = len(state["messages"])