langgraph 1.2.7__tar.gz → 1.2.8__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {langgraph-1.2.7 → langgraph-1.2.8}/PKG-INFO +1 -1
- {langgraph-1.2.7 → langgraph-1.2.8}/langgraph/pregel/_checkpoint.py +5 -0
- {langgraph-1.2.7 → langgraph-1.2.8}/langgraph/pregel/main.py +32 -47
- {langgraph-1.2.7 → langgraph-1.2.8}/pyproject.toml +1 -1
- {langgraph-1.2.7 → langgraph-1.2.8}/tests/test_delta_channel_update_state.py +20 -30
- {langgraph-1.2.7 → langgraph-1.2.8}/uv.lock +281 -272
- {langgraph-1.2.7 → langgraph-1.2.8}/.gitignore +0 -0
- {langgraph-1.2.7 → langgraph-1.2.8}/LICENSE +0 -0
- {langgraph-1.2.7 → langgraph-1.2.8}/Makefile +0 -0
- {langgraph-1.2.7 → langgraph-1.2.8}/README.md +0 -0
- {langgraph-1.2.7 → langgraph-1.2.8}/bench/__init__.py +0 -0
- {langgraph-1.2.7 → langgraph-1.2.8}/bench/__main__.py +0 -0
- {langgraph-1.2.7 → langgraph-1.2.8}/bench/fanout_to_subgraph.py +0 -0
- {langgraph-1.2.7 → langgraph-1.2.8}/bench/pydantic_state.py +0 -0
- {langgraph-1.2.7 → langgraph-1.2.8}/bench/react_agent.py +0 -0
- {langgraph-1.2.7 → langgraph-1.2.8}/bench/sequential.py +0 -0
- {langgraph-1.2.7 → langgraph-1.2.8}/bench/serde_allowlist.py +0 -0
- {langgraph-1.2.7 → langgraph-1.2.8}/bench/wide_dict.py +0 -0
- {langgraph-1.2.7 → langgraph-1.2.8}/bench/wide_state.py +0 -0
- {langgraph-1.2.7 → langgraph-1.2.8}/langgraph/_internal/__init__.py +0 -0
- {langgraph-1.2.7 → langgraph-1.2.8}/langgraph/_internal/_cache.py +0 -0
- {langgraph-1.2.7 → langgraph-1.2.8}/langgraph/_internal/_config.py +0 -0
- {langgraph-1.2.7 → langgraph-1.2.8}/langgraph/_internal/_constants.py +0 -0
- {langgraph-1.2.7 → langgraph-1.2.8}/langgraph/_internal/_fields.py +0 -0
- {langgraph-1.2.7 → langgraph-1.2.8}/langgraph/_internal/_future.py +0 -0
- {langgraph-1.2.7 → langgraph-1.2.8}/langgraph/_internal/_pydantic.py +0 -0
- {langgraph-1.2.7 → langgraph-1.2.8}/langgraph/_internal/_queue.py +0 -0
- {langgraph-1.2.7 → langgraph-1.2.8}/langgraph/_internal/_replay.py +0 -0
- {langgraph-1.2.7 → langgraph-1.2.8}/langgraph/_internal/_retry.py +0 -0
- {langgraph-1.2.7 → langgraph-1.2.8}/langgraph/_internal/_runnable.py +0 -0
- {langgraph-1.2.7 → langgraph-1.2.8}/langgraph/_internal/_scratchpad.py +0 -0
- {langgraph-1.2.7 → langgraph-1.2.8}/langgraph/_internal/_serde.py +0 -0
- {langgraph-1.2.7 → langgraph-1.2.8}/langgraph/_internal/_timeout.py +0 -0
- {langgraph-1.2.7 → langgraph-1.2.8}/langgraph/_internal/_typing.py +0 -0
- {langgraph-1.2.7 → langgraph-1.2.8}/langgraph/callbacks.py +0 -0
- {langgraph-1.2.7 → langgraph-1.2.8}/langgraph/channels/__init__.py +0 -0
- {langgraph-1.2.7 → langgraph-1.2.8}/langgraph/channels/any_value.py +0 -0
- {langgraph-1.2.7 → langgraph-1.2.8}/langgraph/channels/base.py +0 -0
- {langgraph-1.2.7 → langgraph-1.2.8}/langgraph/channels/binop.py +0 -0
- {langgraph-1.2.7 → langgraph-1.2.8}/langgraph/channels/delta.py +0 -0
- {langgraph-1.2.7 → langgraph-1.2.8}/langgraph/channels/ephemeral_value.py +0 -0
- {langgraph-1.2.7 → langgraph-1.2.8}/langgraph/channels/last_value.py +0 -0
- {langgraph-1.2.7 → langgraph-1.2.8}/langgraph/channels/named_barrier_value.py +0 -0
- {langgraph-1.2.7 → langgraph-1.2.8}/langgraph/channels/topic.py +0 -0
- {langgraph-1.2.7 → langgraph-1.2.8}/langgraph/channels/untracked_value.py +0 -0
- {langgraph-1.2.7 → langgraph-1.2.8}/langgraph/config.py +0 -0
- {langgraph-1.2.7 → langgraph-1.2.8}/langgraph/constants.py +0 -0
- {langgraph-1.2.7 → langgraph-1.2.8}/langgraph/errors.py +0 -0
- {langgraph-1.2.7 → langgraph-1.2.8}/langgraph/func/__init__.py +0 -0
- {langgraph-1.2.7 → langgraph-1.2.8}/langgraph/graph/__init__.py +0 -0
- {langgraph-1.2.7 → langgraph-1.2.8}/langgraph/graph/_branch.py +0 -0
- {langgraph-1.2.7 → langgraph-1.2.8}/langgraph/graph/_node.py +0 -0
- {langgraph-1.2.7 → langgraph-1.2.8}/langgraph/graph/message.py +0 -0
- {langgraph-1.2.7 → langgraph-1.2.8}/langgraph/graph/state.py +0 -0
- {langgraph-1.2.7 → langgraph-1.2.8}/langgraph/graph/ui.py +0 -0
- {langgraph-1.2.7 → langgraph-1.2.8}/langgraph/managed/__init__.py +0 -0
- {langgraph-1.2.7 → langgraph-1.2.8}/langgraph/managed/base.py +0 -0
- {langgraph-1.2.7 → langgraph-1.2.8}/langgraph/managed/is_last_step.py +0 -0
- {langgraph-1.2.7 → langgraph-1.2.8}/langgraph/pregel/__init__.py +0 -0
- {langgraph-1.2.7 → langgraph-1.2.8}/langgraph/pregel/_algo.py +0 -0
- {langgraph-1.2.7 → langgraph-1.2.8}/langgraph/pregel/_call.py +0 -0
- {langgraph-1.2.7 → langgraph-1.2.8}/langgraph/pregel/_config.py +0 -0
- {langgraph-1.2.7 → langgraph-1.2.8}/langgraph/pregel/_draw.py +0 -0
- {langgraph-1.2.7 → langgraph-1.2.8}/langgraph/pregel/_executor.py +0 -0
- {langgraph-1.2.7 → langgraph-1.2.8}/langgraph/pregel/_io.py +0 -0
- {langgraph-1.2.7 → langgraph-1.2.8}/langgraph/pregel/_log.py +0 -0
- {langgraph-1.2.7 → langgraph-1.2.8}/langgraph/pregel/_loop.py +0 -0
- {langgraph-1.2.7 → langgraph-1.2.8}/langgraph/pregel/_messages.py +0 -0
- {langgraph-1.2.7 → langgraph-1.2.8}/langgraph/pregel/_read.py +0 -0
- {langgraph-1.2.7 → langgraph-1.2.8}/langgraph/pregel/_remote_run_stream.py +0 -0
- {langgraph-1.2.7 → langgraph-1.2.8}/langgraph/pregel/_retry.py +0 -0
- {langgraph-1.2.7 → langgraph-1.2.8}/langgraph/pregel/_runner.py +0 -0
- {langgraph-1.2.7 → langgraph-1.2.8}/langgraph/pregel/_tools.py +0 -0
- {langgraph-1.2.7 → langgraph-1.2.8}/langgraph/pregel/_utils.py +0 -0
- {langgraph-1.2.7 → langgraph-1.2.8}/langgraph/pregel/_validate.py +0 -0
- {langgraph-1.2.7 → langgraph-1.2.8}/langgraph/pregel/_write.py +0 -0
- {langgraph-1.2.7 → langgraph-1.2.8}/langgraph/pregel/debug.py +0 -0
- {langgraph-1.2.7 → langgraph-1.2.8}/langgraph/pregel/protocol.py +0 -0
- {langgraph-1.2.7 → langgraph-1.2.8}/langgraph/pregel/remote.py +0 -0
- {langgraph-1.2.7 → langgraph-1.2.8}/langgraph/pregel/types.py +0 -0
- {langgraph-1.2.7 → langgraph-1.2.8}/langgraph/py.typed +0 -0
- {langgraph-1.2.7 → langgraph-1.2.8}/langgraph/runtime.py +0 -0
- {langgraph-1.2.7 → langgraph-1.2.8}/langgraph/stream/__init__.py +0 -0
- {langgraph-1.2.7 → langgraph-1.2.8}/langgraph/stream/_convert.py +0 -0
- {langgraph-1.2.7 → langgraph-1.2.8}/langgraph/stream/_mux.py +0 -0
- {langgraph-1.2.7 → langgraph-1.2.8}/langgraph/stream/_types.py +0 -0
- {langgraph-1.2.7 → langgraph-1.2.8}/langgraph/stream/run_stream.py +0 -0
- {langgraph-1.2.7 → langgraph-1.2.8}/langgraph/stream/stream_channel.py +0 -0
- {langgraph-1.2.7 → langgraph-1.2.8}/langgraph/stream/transformers.py +0 -0
- {langgraph-1.2.7 → langgraph-1.2.8}/langgraph/types.py +0 -0
- {langgraph-1.2.7 → langgraph-1.2.8}/langgraph/typing.py +0 -0
- {langgraph-1.2.7 → langgraph-1.2.8}/langgraph/utils/__init__.py +0 -0
- {langgraph-1.2.7 → langgraph-1.2.8}/langgraph/utils/config.py +0 -0
- {langgraph-1.2.7 → langgraph-1.2.8}/langgraph/utils/runnable.py +0 -0
- {langgraph-1.2.7 → langgraph-1.2.8}/langgraph/version.py +0 -0
- {langgraph-1.2.7 → langgraph-1.2.8}/langgraph/warnings.py +0 -0
- {langgraph-1.2.7 → langgraph-1.2.8}/tests/__init__.py +0 -0
- {langgraph-1.2.7 → langgraph-1.2.8}/tests/__snapshots__/test_large_cases.ambr +0 -0
- {langgraph-1.2.7 → langgraph-1.2.8}/tests/__snapshots__/test_pregel.ambr +0 -0
- {langgraph-1.2.7 → langgraph-1.2.8}/tests/__snapshots__/test_pregel_async.ambr +0 -0
- {langgraph-1.2.7 → langgraph-1.2.8}/tests/agents.py +0 -0
- {langgraph-1.2.7 → langgraph-1.2.8}/tests/any_int.py +0 -0
- {langgraph-1.2.7 → langgraph-1.2.8}/tests/any_str.py +0 -0
- {langgraph-1.2.7 → langgraph-1.2.8}/tests/compose-postgres.yml +0 -0
- {langgraph-1.2.7 → langgraph-1.2.8}/tests/compose-redis.yml +0 -0
- {langgraph-1.2.7 → langgraph-1.2.8}/tests/conftest.py +0 -0
- {langgraph-1.2.7 → langgraph-1.2.8}/tests/conftest_checkpointer.py +0 -0
- {langgraph-1.2.7 → langgraph-1.2.8}/tests/conftest_store.py +0 -0
- {langgraph-1.2.7 → langgraph-1.2.8}/tests/example_app/example_graph.py +0 -0
- {langgraph-1.2.7 → langgraph-1.2.8}/tests/example_app/langgraph.json +0 -0
- {langgraph-1.2.7 → langgraph-1.2.8}/tests/example_app/requirements.txt +0 -0
- {langgraph-1.2.7 → langgraph-1.2.8}/tests/fake_chat.py +0 -0
- {langgraph-1.2.7 → langgraph-1.2.8}/tests/fake_tracer.py +0 -0
- {langgraph-1.2.7 → langgraph-1.2.8}/tests/memory_assert.py +0 -0
- {langgraph-1.2.7 → langgraph-1.2.8}/tests/messages.py +0 -0
- {langgraph-1.2.7 → langgraph-1.2.8}/tests/test_algo.py +0 -0
- {langgraph-1.2.7 → langgraph-1.2.8}/tests/test_channels.py +0 -0
- {langgraph-1.2.7 → langgraph-1.2.8}/tests/test_checkpoint_migration.py +0 -0
- {langgraph-1.2.7 → langgraph-1.2.8}/tests/test_config_async.py +0 -0
- {langgraph-1.2.7 → langgraph-1.2.8}/tests/test_delta_channel_benchmark.py +0 -0
- {langgraph-1.2.7 → langgraph-1.2.8}/tests/test_delta_channel_exit_mode.py +0 -0
- {langgraph-1.2.7 → langgraph-1.2.8}/tests/test_delta_channel_id_stability.py +0 -0
- {langgraph-1.2.7 → langgraph-1.2.8}/tests/test_delta_channel_migration.py +0 -0
- {langgraph-1.2.7 → langgraph-1.2.8}/tests/test_delta_channel_supersteps_bound.py +0 -0
- {langgraph-1.2.7 → langgraph-1.2.8}/tests/test_deprecation.py +0 -0
- {langgraph-1.2.7 → langgraph-1.2.8}/tests/test_graph_callbacks.py +0 -0
- {langgraph-1.2.7 → langgraph-1.2.8}/tests/test_interleave_arrival_order.py +0 -0
- {langgraph-1.2.7 → langgraph-1.2.8}/tests/test_interrupt_migration.py +0 -0
- {langgraph-1.2.7 → langgraph-1.2.8}/tests/test_interruption.py +0 -0
- {langgraph-1.2.7 → langgraph-1.2.8}/tests/test_large_cases.py +0 -0
- {langgraph-1.2.7 → langgraph-1.2.8}/tests/test_large_cases_async.py +0 -0
- {langgraph-1.2.7 → langgraph-1.2.8}/tests/test_managed_values.py +0 -0
- {langgraph-1.2.7 → langgraph-1.2.8}/tests/test_messages_state.py +0 -0
- {langgraph-1.2.7 → langgraph-1.2.8}/tests/test_parent_command.py +0 -0
- {langgraph-1.2.7 → langgraph-1.2.8}/tests/test_parent_command_async.py +0 -0
- {langgraph-1.2.7 → langgraph-1.2.8}/tests/test_pregel.py +0 -0
- {langgraph-1.2.7 → langgraph-1.2.8}/tests/test_pregel_async.py +0 -0
- {langgraph-1.2.7 → langgraph-1.2.8}/tests/test_pregel_debug.py +0 -0
- {langgraph-1.2.7 → langgraph-1.2.8}/tests/test_pregel_stream_events_v3.py +0 -0
- {langgraph-1.2.7 → langgraph-1.2.8}/tests/test_pydantic.py +0 -0
- {langgraph-1.2.7 → langgraph-1.2.8}/tests/test_remote_graph.py +0 -0
- {langgraph-1.2.7 → langgraph-1.2.8}/tests/test_remote_graph_v3.py +0 -0
- {langgraph-1.2.7 → langgraph-1.2.8}/tests/test_retry.py +0 -0
- {langgraph-1.2.7 → langgraph-1.2.8}/tests/test_runnable.py +0 -0
- {langgraph-1.2.7 → langgraph-1.2.8}/tests/test_runtime.py +0 -0
- {langgraph-1.2.7 → langgraph-1.2.8}/tests/test_serde_allowlist.py +0 -0
- {langgraph-1.2.7 → langgraph-1.2.8}/tests/test_state.py +0 -0
- {langgraph-1.2.7 → langgraph-1.2.8}/tests/test_stream_before_builtins.py +0 -0
- {langgraph-1.2.7 → langgraph-1.2.8}/tests/test_stream_data_transformers.py +0 -0
- {langgraph-1.2.7 → langgraph-1.2.8}/tests/test_stream_events_v3.py +0 -0
- {langgraph-1.2.7 → langgraph-1.2.8}/tests/test_stream_events_v3_e2e.py +0 -0
- {langgraph-1.2.7 → langgraph-1.2.8}/tests/test_stream_events_v3_kwarg_forwarding.py +0 -0
- {langgraph-1.2.7 → langgraph-1.2.8}/tests/test_stream_lifecycle_transformer.py +0 -0
- {langgraph-1.2.7 → langgraph-1.2.8}/tests/test_stream_messages_transformer.py +0 -0
- {langgraph-1.2.7 → langgraph-1.2.8}/tests/test_stream_subgraph_transformer.py +0 -0
- {langgraph-1.2.7 → langgraph-1.2.8}/tests/test_subgraph_persistence.py +0 -0
- {langgraph-1.2.7 → langgraph-1.2.8}/tests/test_subgraph_persistence_async.py +0 -0
- {langgraph-1.2.7 → langgraph-1.2.8}/tests/test_time_travel.py +0 -0
- {langgraph-1.2.7 → langgraph-1.2.8}/tests/test_time_travel_async.py +0 -0
- {langgraph-1.2.7 → langgraph-1.2.8}/tests/test_tool_stream_handler.py +0 -0
- {langgraph-1.2.7 → langgraph-1.2.8}/tests/test_tracing_interops.py +0 -0
- {langgraph-1.2.7 → langgraph-1.2.8}/tests/test_type_checking.py +0 -0
- {langgraph-1.2.7 → langgraph-1.2.8}/tests/test_utils.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: langgraph
|
|
3
|
-
Version: 1.2.
|
|
3
|
+
Version: 1.2.8
|
|
4
4
|
Summary: Building stateful, multi-actor applications with LLMs
|
|
5
5
|
Project-URL: Homepage, https://docs.langchain.com/oss/python/langgraph/overview
|
|
6
6
|
Project-URL: Documentation, https://reference.langchain.com/python/langgraph/
|
|
@@ -102,6 +102,11 @@ def create_checkpoint(
|
|
|
102
102
|
continue
|
|
103
103
|
ch = channels[k]
|
|
104
104
|
if k in channels_to_snapshot:
|
|
105
|
+
# Callers force a full snapshot blob here: exit mode when a
|
|
106
|
+
# delta channel reaches its snapshot cadence, and update_state
|
|
107
|
+
# on a fresh thread (no ancestor to replay writes from). The
|
|
108
|
+
# manual version-bump below only applies to the exit-mode case.
|
|
109
|
+
#
|
|
105
110
|
# In exit mode, the snapshot decision is deferred to exit
|
|
106
111
|
# time (intermediate steps have do_checkpoint=False). The
|
|
107
112
|
# channel's count may have reached snapshot_frequency over
|
|
@@ -1997,33 +1997,11 @@ class Pregel(
|
|
|
1997
1997
|
),
|
|
1998
1998
|
)
|
|
1999
1999
|
# save task writes
|
|
2000
|
-
has_delta_writes = any(
|
|
2001
|
-
isinstance(channels.get(c), DeltaChannel)
|
|
2002
|
-
for task in run_tasks
|
|
2003
|
-
for c, _ in task.writes
|
|
2004
|
-
)
|
|
2005
|
-
should_put_writes = saved is not None or has_delta_writes
|
|
2006
|
-
|
|
2007
|
-
if saved is None and has_delta_writes:
|
|
2008
|
-
# If there is no previous checkpoint, we need to create a stub checkpoint
|
|
2009
|
-
# so the first delta writes has a parent to anchor under.
|
|
2010
|
-
# This is the model of DeltaChannel.
|
|
2011
|
-
stub = empty_checkpoint()
|
|
2012
|
-
checkpoint_config = checkpointer.put(
|
|
2013
|
-
patch_configurable(
|
|
2014
|
-
checkpoint_config, {CONFIG_KEY_CHECKPOINT_ID: None}
|
|
2015
|
-
),
|
|
2016
|
-
stub,
|
|
2017
|
-
{"source": "update", "step": -1, "parents": {}},
|
|
2018
|
-
{},
|
|
2019
|
-
)
|
|
2020
|
-
|
|
2021
2000
|
for task_id, task in zip(run_task_ids, run_tasks):
|
|
2022
2001
|
# channel writes are saved to current checkpoint
|
|
2023
2002
|
channel_writes = [w for w in task.writes if w[0] != PUSH]
|
|
2024
|
-
if
|
|
2003
|
+
if saved and channel_writes:
|
|
2025
2004
|
checkpointer.put_writes(checkpoint_config, channel_writes, task_id)
|
|
2026
|
-
|
|
2027
2005
|
# apply to checkpoint and save
|
|
2028
2006
|
apply_writes(
|
|
2029
2007
|
checkpoint,
|
|
@@ -2032,7 +2010,21 @@ class Pregel(
|
|
|
2032
2010
|
checkpointer.get_next_version,
|
|
2033
2011
|
self.trigger_to_nodes,
|
|
2034
2012
|
)
|
|
2035
|
-
|
|
2013
|
+
# On a fresh thread there is no ancestor to replay DeltaChannel
|
|
2014
|
+
# writes from, so force a self-contained snapshot in the first
|
|
2015
|
+
# checkpoint instead of relying on ancestor write-replay.
|
|
2016
|
+
delta_snapshot = (
|
|
2017
|
+
{
|
|
2018
|
+
k
|
|
2019
|
+
for k, ch in channels.items()
|
|
2020
|
+
if isinstance(ch, DeltaChannel) and ch.is_available()
|
|
2021
|
+
}
|
|
2022
|
+
if saved is None
|
|
2023
|
+
else None
|
|
2024
|
+
)
|
|
2025
|
+
checkpoint = create_checkpoint(
|
|
2026
|
+
checkpoint, channels, step + 1, channels_to_snapshot=delta_snapshot
|
|
2027
|
+
)
|
|
2036
2028
|
next_config = checkpointer.put(
|
|
2037
2029
|
checkpoint_config,
|
|
2038
2030
|
checkpoint,
|
|
@@ -2464,31 +2456,10 @@ class Pregel(
|
|
|
2464
2456
|
),
|
|
2465
2457
|
)
|
|
2466
2458
|
# save task writes
|
|
2467
|
-
has_delta_writes = any(
|
|
2468
|
-
isinstance(channels.get(c), DeltaChannel)
|
|
2469
|
-
for task in run_tasks
|
|
2470
|
-
for c, _ in task.writes
|
|
2471
|
-
)
|
|
2472
|
-
should_put_writes = saved is not None or has_delta_writes
|
|
2473
|
-
|
|
2474
|
-
if saved is None and has_delta_writes:
|
|
2475
|
-
# If there is no previous checkpoint, we need to create a stub checkpoint
|
|
2476
|
-
# so the first delta writes has a parent to anchor under.
|
|
2477
|
-
# This is the model of DeltaChannel.
|
|
2478
|
-
stub = empty_checkpoint()
|
|
2479
|
-
checkpoint_config = await checkpointer.aput(
|
|
2480
|
-
patch_configurable(
|
|
2481
|
-
checkpoint_config, {CONFIG_KEY_CHECKPOINT_ID: None}
|
|
2482
|
-
),
|
|
2483
|
-
stub,
|
|
2484
|
-
{"source": "update", "step": -1, "parents": {}},
|
|
2485
|
-
{},
|
|
2486
|
-
)
|
|
2487
|
-
|
|
2488
2459
|
for task_id, task in zip(run_task_ids, run_tasks):
|
|
2489
2460
|
# channel writes are saved to current checkpoint
|
|
2490
2461
|
channel_writes = [w for w in task.writes if w[0] != PUSH]
|
|
2491
|
-
if
|
|
2462
|
+
if saved and channel_writes:
|
|
2492
2463
|
await checkpointer.aput_writes(
|
|
2493
2464
|
checkpoint_config, channel_writes, task_id
|
|
2494
2465
|
)
|
|
@@ -2500,7 +2471,21 @@ class Pregel(
|
|
|
2500
2471
|
checkpointer.get_next_version,
|
|
2501
2472
|
self.trigger_to_nodes,
|
|
2502
2473
|
)
|
|
2503
|
-
|
|
2474
|
+
# On a fresh thread there is no ancestor to replay DeltaChannel
|
|
2475
|
+
# writes from, so force a self-contained snapshot in the first
|
|
2476
|
+
# checkpoint instead of relying on ancestor write-replay.
|
|
2477
|
+
delta_snapshot = (
|
|
2478
|
+
{
|
|
2479
|
+
k
|
|
2480
|
+
for k, ch in channels.items()
|
|
2481
|
+
if isinstance(ch, DeltaChannel) and ch.is_available()
|
|
2482
|
+
}
|
|
2483
|
+
if saved is None
|
|
2484
|
+
else None
|
|
2485
|
+
)
|
|
2486
|
+
checkpoint = create_checkpoint(
|
|
2487
|
+
checkpoint, channels, step + 1, channels_to_snapshot=delta_snapshot
|
|
2488
|
+
)
|
|
2504
2489
|
# save checkpoint, after applying writes
|
|
2505
2490
|
next_config = await checkpointer.aput(
|
|
2506
2491
|
checkpoint_config,
|
|
@@ -2,10 +2,13 @@
|
|
|
2
2
|
|
|
3
3
|
Originally a regression suite for deepagents#3774 — `update_state` on a *fresh*
|
|
4
4
|
thread silently dropped the first write to a `DeltaChannel`-backed channel
|
|
5
|
-
because channel writes were only persisted when a previous checkpoint existed
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
5
|
+
because channel writes were only persisted when a previous checkpoint existed
|
|
6
|
+
and no snapshot was written either, so the checkpoint reconstructed to empty.
|
|
7
|
+
|
|
8
|
+
Fixed by forcing a self-contained `_DeltaSnapshot` blob into the first
|
|
9
|
+
checkpoint on a fresh thread (`saved is None`), so the value is stored inline
|
|
10
|
+
and no ancestor write-replay is required. This keeps the read/replay path
|
|
11
|
+
untouched.
|
|
9
12
|
|
|
10
13
|
Coverage:
|
|
11
14
|
|
|
@@ -13,8 +16,8 @@ Coverage:
|
|
|
13
16
|
* non-fresh thread: `update_state` after `invoke`, after another `update_state`,
|
|
14
17
|
and `bulk_update_state` with multiple per-superstep updates
|
|
15
18
|
* update-by-id end-to-end via `update_state` (DeltaChannel reducer semantics)
|
|
16
|
-
* state-history chain shape on a fresh thread (
|
|
17
|
-
with
|
|
19
|
+
* state-history chain shape on a fresh thread (single self-contained update
|
|
20
|
+
checkpoint with the snapshot inline and no parent)
|
|
18
21
|
"""
|
|
19
22
|
|
|
20
23
|
from typing import Annotated, Any
|
|
@@ -94,7 +97,7 @@ async def test_aupdate_state_fresh_thread_delta_channel() -> None:
|
|
|
94
97
|
|
|
95
98
|
def test_update_state_after_invoke_delta_channel() -> None:
|
|
96
99
|
"""The non-fresh-thread path was already working before the fix; pin it
|
|
97
|
-
down so the
|
|
100
|
+
down so the forced-snapshot change for fresh threads doesn't regress it."""
|
|
98
101
|
saver = InMemorySaver()
|
|
99
102
|
graph = _build_graph(saver)
|
|
100
103
|
config = {"configurable": {"thread_id": "after-invoke-sync"}}
|
|
@@ -133,9 +136,9 @@ async def test_aupdate_state_after_invoke_delta_channel() -> None:
|
|
|
133
136
|
|
|
134
137
|
|
|
135
138
|
def test_consecutive_update_states_delta_channel() -> None:
|
|
136
|
-
"""First update_state
|
|
137
|
-
parent (`saved is not None`) and
|
|
138
|
-
messages must round-trip in chronological order."""
|
|
139
|
+
"""First update_state forces a self-contained snapshot seed; the second
|
|
140
|
+
sees a real parent (`saved is not None`) and anchors its writes under that
|
|
141
|
+
seed. Both messages must round-trip in chronological order."""
|
|
139
142
|
saver = InMemorySaver()
|
|
140
143
|
graph = _build_graph(saver)
|
|
141
144
|
config = {"configurable": {"thread_id": "consecutive-sync"}}
|
|
@@ -252,14 +255,14 @@ def test_bulk_update_state_multi_task_per_superstep_delta_channel() -> None:
|
|
|
252
255
|
|
|
253
256
|
|
|
254
257
|
# ---------------------------------------------------------------------------
|
|
255
|
-
# Public-API observation of the
|
|
258
|
+
# Public-API observation of the forced-snapshot mechanism
|
|
256
259
|
# ---------------------------------------------------------------------------
|
|
257
260
|
|
|
258
261
|
|
|
259
262
|
def test_state_history_chain_after_fresh_update_state_delta_channel() -> None:
|
|
260
|
-
"""A fresh-thread `update_state` should produce
|
|
261
|
-
via `get_state_history`:
|
|
262
|
-
|
|
263
|
+
"""A fresh-thread `update_state` should produce a single self-contained
|
|
264
|
+
checkpoint visible via `get_state_history`: step=0, `source='update'`,
|
|
265
|
+
no parent, with the DeltaChannel value snapshotted inline."""
|
|
263
266
|
saver = InMemorySaver()
|
|
264
267
|
graph = _build_graph(saver)
|
|
265
268
|
config = {"configurable": {"thread_id": "history-chain"}}
|
|
@@ -270,25 +273,12 @@ def test_state_history_chain_after_fresh_update_state_delta_channel() -> None:
|
|
|
270
273
|
as_node="model",
|
|
271
274
|
)
|
|
272
275
|
|
|
273
|
-
# Newest first per `get_state_history` ordering.
|
|
274
276
|
history = list(graph.get_state_history(config))
|
|
275
|
-
assert len(history) ==
|
|
276
|
-
|
|
277
|
-
update_snapshot, stub_snapshot = history
|
|
277
|
+
assert len(history) == 1
|
|
278
278
|
|
|
279
|
+
(update_snapshot,) = history
|
|
279
280
|
assert update_snapshot.metadata is not None
|
|
280
281
|
assert update_snapshot.metadata["source"] == "update"
|
|
281
282
|
assert update_snapshot.metadata["step"] == 0
|
|
283
|
+
assert update_snapshot.parent_config is None
|
|
282
284
|
assert [m.content for m in update_snapshot.values["messages"]] == ["hello"]
|
|
283
|
-
|
|
284
|
-
assert stub_snapshot.metadata is not None
|
|
285
|
-
assert stub_snapshot.metadata["source"] == "update"
|
|
286
|
-
assert stub_snapshot.metadata["step"] == -1
|
|
287
|
-
assert stub_snapshot.parent_config is None
|
|
288
|
-
|
|
289
|
-
# The update checkpoint's parent is the stub.
|
|
290
|
-
assert update_snapshot.parent_config is not None
|
|
291
|
-
assert (
|
|
292
|
-
update_snapshot.parent_config["configurable"]["checkpoint_id"]
|
|
293
|
-
== stub_snapshot.config["configurable"]["checkpoint_id"]
|
|
294
|
-
)
|