mirror of
https://github.com/langchain-ai/deepagents.git
synced 2026-07-22 01:35:28 -04:00
fix(sdk): assign UUIDs to ID-less messages in _messages_delta_reducer (#3513)
## Summary - `_messages_delta_reducer` now assigns `str(uuid.uuid4())` to messages with `id=None` inside the existing write-loop iteration (no extra pass), matching the behaviour of `add_messages` - Version bumped `0.6.2 → 0.6.3` - `uv lock` refreshed across all `libs/` and `examples/` packages ## Test plan - [ ] Confirm messages written without an explicit ID arrive in state with a stable UUID - [ ] Confirm deduplication still works for messages that already carry an ID - [ ] Confirm `RemoveMessage` tombstoning still works correctly
This commit is contained in:
@@ -10,6 +10,7 @@ side — so we skip the per-message coercion.
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import uuid
|
||||
from typing import Any, cast
|
||||
|
||||
from langchain_core.messages import (
|
||||
@@ -21,16 +22,14 @@ from langchain_core.messages import (
|
||||
from langgraph.graph.message import REMOVE_ALL_MESSAGES
|
||||
|
||||
|
||||
def _messages_delta_reducer( # noqa: C901
|
||||
def _messages_delta_reducer( # noqa: C901, PLR0912
|
||||
state: list[AnyMessage], writes: list[list[AnyMessage]]
|
||||
) -> list[AnyMessage]:
|
||||
"""Batch reducer for use with `DeltaChannel` on the messages key.
|
||||
|
||||
Dedups by ID, tombstones via `RemoveMessage`, resets on
|
||||
`REMOVE_ALL_MESSAGES`. ID-less messages are appended without ID
|
||||
assignment — checkpointers serialize pending writes before
|
||||
`update()` runs, so IDs assigned inside the reducer never reach
|
||||
stored writes and would differ on replay, defeating deduplication.
|
||||
`REMOVE_ALL_MESSAGES`. ID-less messages are assigned a UUID before
|
||||
being appended, matching the behaviour of `add_messages`.
|
||||
|
||||
Raw dict / string / tuple inputs are coerced to typed `BaseMessage` so
|
||||
HTTP-driven graphs work without a separate coercion step.
|
||||
@@ -60,11 +59,19 @@ def _messages_delta_reducer( # noqa: C901
|
||||
state_msgs = []
|
||||
msgs = msgs[remove_all_idx + 1 :]
|
||||
|
||||
index: dict[str, int] = {m.id: i for i, m in enumerate(state_msgs) if m.id is not None}
|
||||
result: list[AnyMessage | None] = list(state_msgs)
|
||||
result: list[AnyMessage | None] = []
|
||||
index: dict[str, int] = {}
|
||||
for m in state_msgs:
|
||||
if m.id is None:
|
||||
m.id = str(uuid.uuid4())
|
||||
index[m.id] = len(result)
|
||||
result.append(m)
|
||||
for msg in msgs:
|
||||
mid = msg.id
|
||||
if mid is None:
|
||||
msg.id = str(uuid.uuid4())
|
||||
mid = msg.id
|
||||
index[mid] = len(result)
|
||||
result.append(msg)
|
||||
elif isinstance(msg, RemoveMessage):
|
||||
if mid in index:
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
"""Unit tests for _messages_delta_reducer."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from langchain_core.messages import AIMessage, HumanMessage
|
||||
|
||||
from deepagents._messages_reducer import _messages_delta_reducer
|
||||
|
||||
|
||||
def test_idless_message_gets_id_and_order_preserved() -> None:
|
||||
existing = [AIMessage(content="hello", id="existing-1")]
|
||||
new_msg = HumanMessage(content="follow-up") # no id
|
||||
|
||||
result = _messages_delta_reducer(existing, [[new_msg]])
|
||||
|
||||
assert len(result) == 2
|
||||
assert result[0].id == "existing-1"
|
||||
assert result[1] is new_msg
|
||||
assert result[1].id is not None
|
||||
Reference in New Issue
Block a user