langgraph 1.2.4__tar.gz → 1.2.5__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.4 → langgraph-1.2.5}/Makefile +2 -5
- {langgraph-1.2.4 → langgraph-1.2.5}/PKG-INFO +2 -2
- {langgraph-1.2.4 → langgraph-1.2.5}/langgraph/_internal/_config.py +46 -13
- {langgraph-1.2.4 → langgraph-1.2.5}/langgraph/pregel/main.py +46 -2
- {langgraph-1.2.4 → langgraph-1.2.5}/langgraph/types.py +1 -1
- {langgraph-1.2.4 → langgraph-1.2.5}/pyproject.toml +21 -12
- langgraph-1.2.5/tests/test_delta_channel_update_state.py +294 -0
- {langgraph-1.2.4 → langgraph-1.2.5}/tests/test_large_cases.py +7 -0
- {langgraph-1.2.4 → langgraph-1.2.5}/tests/test_large_cases_async.py +7 -0
- {langgraph-1.2.4 → langgraph-1.2.5}/tests/test_pregel.py +2 -0
- {langgraph-1.2.4 → langgraph-1.2.5}/tests/test_pregel_async.py +111 -272
- {langgraph-1.2.4 → langgraph-1.2.5}/tests/test_stream_events_v3.py +1 -1
- {langgraph-1.2.4 → langgraph-1.2.5}/tests/test_utils.py +154 -0
- {langgraph-1.2.4 → langgraph-1.2.5}/uv.lock +396 -524
- {langgraph-1.2.4 → langgraph-1.2.5}/.gitignore +0 -0
- {langgraph-1.2.4 → langgraph-1.2.5}/LICENSE +0 -0
- {langgraph-1.2.4 → langgraph-1.2.5}/README.md +0 -0
- {langgraph-1.2.4 → langgraph-1.2.5}/bench/__init__.py +0 -0
- {langgraph-1.2.4 → langgraph-1.2.5}/bench/__main__.py +0 -0
- {langgraph-1.2.4 → langgraph-1.2.5}/bench/fanout_to_subgraph.py +0 -0
- {langgraph-1.2.4 → langgraph-1.2.5}/bench/pydantic_state.py +0 -0
- {langgraph-1.2.4 → langgraph-1.2.5}/bench/react_agent.py +0 -0
- {langgraph-1.2.4 → langgraph-1.2.5}/bench/sequential.py +0 -0
- {langgraph-1.2.4 → langgraph-1.2.5}/bench/serde_allowlist.py +0 -0
- {langgraph-1.2.4 → langgraph-1.2.5}/bench/wide_dict.py +0 -0
- {langgraph-1.2.4 → langgraph-1.2.5}/bench/wide_state.py +0 -0
- {langgraph-1.2.4 → langgraph-1.2.5}/langgraph/_internal/__init__.py +0 -0
- {langgraph-1.2.4 → langgraph-1.2.5}/langgraph/_internal/_cache.py +0 -0
- {langgraph-1.2.4 → langgraph-1.2.5}/langgraph/_internal/_constants.py +0 -0
- {langgraph-1.2.4 → langgraph-1.2.5}/langgraph/_internal/_fields.py +0 -0
- {langgraph-1.2.4 → langgraph-1.2.5}/langgraph/_internal/_future.py +0 -0
- {langgraph-1.2.4 → langgraph-1.2.5}/langgraph/_internal/_pydantic.py +0 -0
- {langgraph-1.2.4 → langgraph-1.2.5}/langgraph/_internal/_queue.py +0 -0
- {langgraph-1.2.4 → langgraph-1.2.5}/langgraph/_internal/_replay.py +0 -0
- {langgraph-1.2.4 → langgraph-1.2.5}/langgraph/_internal/_retry.py +0 -0
- {langgraph-1.2.4 → langgraph-1.2.5}/langgraph/_internal/_runnable.py +0 -0
- {langgraph-1.2.4 → langgraph-1.2.5}/langgraph/_internal/_scratchpad.py +0 -0
- {langgraph-1.2.4 → langgraph-1.2.5}/langgraph/_internal/_serde.py +0 -0
- {langgraph-1.2.4 → langgraph-1.2.5}/langgraph/_internal/_timeout.py +0 -0
- {langgraph-1.2.4 → langgraph-1.2.5}/langgraph/_internal/_typing.py +0 -0
- {langgraph-1.2.4 → langgraph-1.2.5}/langgraph/callbacks.py +0 -0
- {langgraph-1.2.4 → langgraph-1.2.5}/langgraph/channels/__init__.py +0 -0
- {langgraph-1.2.4 → langgraph-1.2.5}/langgraph/channels/any_value.py +0 -0
- {langgraph-1.2.4 → langgraph-1.2.5}/langgraph/channels/base.py +0 -0
- {langgraph-1.2.4 → langgraph-1.2.5}/langgraph/channels/binop.py +0 -0
- {langgraph-1.2.4 → langgraph-1.2.5}/langgraph/channels/delta.py +0 -0
- {langgraph-1.2.4 → langgraph-1.2.5}/langgraph/channels/ephemeral_value.py +0 -0
- {langgraph-1.2.4 → langgraph-1.2.5}/langgraph/channels/last_value.py +0 -0
- {langgraph-1.2.4 → langgraph-1.2.5}/langgraph/channels/named_barrier_value.py +0 -0
- {langgraph-1.2.4 → langgraph-1.2.5}/langgraph/channels/topic.py +0 -0
- {langgraph-1.2.4 → langgraph-1.2.5}/langgraph/channels/untracked_value.py +0 -0
- {langgraph-1.2.4 → langgraph-1.2.5}/langgraph/config.py +0 -0
- {langgraph-1.2.4 → langgraph-1.2.5}/langgraph/constants.py +0 -0
- {langgraph-1.2.4 → langgraph-1.2.5}/langgraph/errors.py +0 -0
- {langgraph-1.2.4 → langgraph-1.2.5}/langgraph/func/__init__.py +0 -0
- {langgraph-1.2.4 → langgraph-1.2.5}/langgraph/graph/__init__.py +0 -0
- {langgraph-1.2.4 → langgraph-1.2.5}/langgraph/graph/_branch.py +0 -0
- {langgraph-1.2.4 → langgraph-1.2.5}/langgraph/graph/_node.py +0 -0
- {langgraph-1.2.4 → langgraph-1.2.5}/langgraph/graph/message.py +0 -0
- {langgraph-1.2.4 → langgraph-1.2.5}/langgraph/graph/state.py +0 -0
- {langgraph-1.2.4 → langgraph-1.2.5}/langgraph/graph/ui.py +0 -0
- {langgraph-1.2.4 → langgraph-1.2.5}/langgraph/managed/__init__.py +0 -0
- {langgraph-1.2.4 → langgraph-1.2.5}/langgraph/managed/base.py +0 -0
- {langgraph-1.2.4 → langgraph-1.2.5}/langgraph/managed/is_last_step.py +0 -0
- {langgraph-1.2.4 → langgraph-1.2.5}/langgraph/pregel/__init__.py +0 -0
- {langgraph-1.2.4 → langgraph-1.2.5}/langgraph/pregel/_algo.py +0 -0
- {langgraph-1.2.4 → langgraph-1.2.5}/langgraph/pregel/_call.py +0 -0
- {langgraph-1.2.4 → langgraph-1.2.5}/langgraph/pregel/_checkpoint.py +0 -0
- {langgraph-1.2.4 → langgraph-1.2.5}/langgraph/pregel/_config.py +0 -0
- {langgraph-1.2.4 → langgraph-1.2.5}/langgraph/pregel/_draw.py +0 -0
- {langgraph-1.2.4 → langgraph-1.2.5}/langgraph/pregel/_executor.py +0 -0
- {langgraph-1.2.4 → langgraph-1.2.5}/langgraph/pregel/_io.py +0 -0
- {langgraph-1.2.4 → langgraph-1.2.5}/langgraph/pregel/_log.py +0 -0
- {langgraph-1.2.4 → langgraph-1.2.5}/langgraph/pregel/_loop.py +0 -0
- {langgraph-1.2.4 → langgraph-1.2.5}/langgraph/pregel/_messages.py +0 -0
- {langgraph-1.2.4 → langgraph-1.2.5}/langgraph/pregel/_read.py +0 -0
- {langgraph-1.2.4 → langgraph-1.2.5}/langgraph/pregel/_remote_run_stream.py +0 -0
- {langgraph-1.2.4 → langgraph-1.2.5}/langgraph/pregel/_retry.py +0 -0
- {langgraph-1.2.4 → langgraph-1.2.5}/langgraph/pregel/_runner.py +0 -0
- {langgraph-1.2.4 → langgraph-1.2.5}/langgraph/pregel/_tools.py +0 -0
- {langgraph-1.2.4 → langgraph-1.2.5}/langgraph/pregel/_utils.py +0 -0
- {langgraph-1.2.4 → langgraph-1.2.5}/langgraph/pregel/_validate.py +0 -0
- {langgraph-1.2.4 → langgraph-1.2.5}/langgraph/pregel/_write.py +0 -0
- {langgraph-1.2.4 → langgraph-1.2.5}/langgraph/pregel/debug.py +0 -0
- {langgraph-1.2.4 → langgraph-1.2.5}/langgraph/pregel/protocol.py +0 -0
- {langgraph-1.2.4 → langgraph-1.2.5}/langgraph/pregel/remote.py +0 -0
- {langgraph-1.2.4 → langgraph-1.2.5}/langgraph/pregel/types.py +0 -0
- {langgraph-1.2.4 → langgraph-1.2.5}/langgraph/py.typed +0 -0
- {langgraph-1.2.4 → langgraph-1.2.5}/langgraph/runtime.py +0 -0
- {langgraph-1.2.4 → langgraph-1.2.5}/langgraph/stream/__init__.py +0 -0
- {langgraph-1.2.4 → langgraph-1.2.5}/langgraph/stream/_convert.py +0 -0
- {langgraph-1.2.4 → langgraph-1.2.5}/langgraph/stream/_mux.py +0 -0
- {langgraph-1.2.4 → langgraph-1.2.5}/langgraph/stream/_types.py +0 -0
- {langgraph-1.2.4 → langgraph-1.2.5}/langgraph/stream/run_stream.py +0 -0
- {langgraph-1.2.4 → langgraph-1.2.5}/langgraph/stream/stream_channel.py +0 -0
- {langgraph-1.2.4 → langgraph-1.2.5}/langgraph/stream/transformers.py +0 -0
- {langgraph-1.2.4 → langgraph-1.2.5}/langgraph/typing.py +0 -0
- {langgraph-1.2.4 → langgraph-1.2.5}/langgraph/utils/__init__.py +0 -0
- {langgraph-1.2.4 → langgraph-1.2.5}/langgraph/utils/config.py +0 -0
- {langgraph-1.2.4 → langgraph-1.2.5}/langgraph/utils/runnable.py +0 -0
- {langgraph-1.2.4 → langgraph-1.2.5}/langgraph/version.py +0 -0
- {langgraph-1.2.4 → langgraph-1.2.5}/langgraph/warnings.py +0 -0
- {langgraph-1.2.4 → langgraph-1.2.5}/tests/__init__.py +0 -0
- {langgraph-1.2.4 → langgraph-1.2.5}/tests/__snapshots__/test_large_cases.ambr +0 -0
- {langgraph-1.2.4 → langgraph-1.2.5}/tests/__snapshots__/test_pregel.ambr +0 -0
- {langgraph-1.2.4 → langgraph-1.2.5}/tests/__snapshots__/test_pregel_async.ambr +0 -0
- {langgraph-1.2.4 → langgraph-1.2.5}/tests/agents.py +0 -0
- {langgraph-1.2.4 → langgraph-1.2.5}/tests/any_int.py +0 -0
- {langgraph-1.2.4 → langgraph-1.2.5}/tests/any_str.py +0 -0
- {langgraph-1.2.4 → langgraph-1.2.5}/tests/compose-postgres.yml +0 -0
- {langgraph-1.2.4 → langgraph-1.2.5}/tests/compose-redis.yml +0 -0
- {langgraph-1.2.4 → langgraph-1.2.5}/tests/conftest.py +0 -0
- {langgraph-1.2.4 → langgraph-1.2.5}/tests/conftest_checkpointer.py +0 -0
- {langgraph-1.2.4 → langgraph-1.2.5}/tests/conftest_store.py +0 -0
- {langgraph-1.2.4 → langgraph-1.2.5}/tests/example_app/example_graph.py +0 -0
- {langgraph-1.2.4 → langgraph-1.2.5}/tests/example_app/langgraph.json +0 -0
- {langgraph-1.2.4 → langgraph-1.2.5}/tests/example_app/requirements.txt +0 -0
- {langgraph-1.2.4 → langgraph-1.2.5}/tests/fake_chat.py +0 -0
- {langgraph-1.2.4 → langgraph-1.2.5}/tests/fake_tracer.py +0 -0
- {langgraph-1.2.4 → langgraph-1.2.5}/tests/memory_assert.py +0 -0
- {langgraph-1.2.4 → langgraph-1.2.5}/tests/messages.py +0 -0
- {langgraph-1.2.4 → langgraph-1.2.5}/tests/test_algo.py +0 -0
- {langgraph-1.2.4 → langgraph-1.2.5}/tests/test_channels.py +0 -0
- {langgraph-1.2.4 → langgraph-1.2.5}/tests/test_checkpoint_migration.py +0 -0
- {langgraph-1.2.4 → langgraph-1.2.5}/tests/test_config_async.py +0 -0
- {langgraph-1.2.4 → langgraph-1.2.5}/tests/test_delta_channel_benchmark.py +0 -0
- {langgraph-1.2.4 → langgraph-1.2.5}/tests/test_delta_channel_exit_mode.py +0 -0
- {langgraph-1.2.4 → langgraph-1.2.5}/tests/test_delta_channel_id_stability.py +0 -0
- {langgraph-1.2.4 → langgraph-1.2.5}/tests/test_delta_channel_migration.py +0 -0
- {langgraph-1.2.4 → langgraph-1.2.5}/tests/test_delta_channel_supersteps_bound.py +0 -0
- {langgraph-1.2.4 → langgraph-1.2.5}/tests/test_deprecation.py +0 -0
- {langgraph-1.2.4 → langgraph-1.2.5}/tests/test_graph_callbacks.py +0 -0
- {langgraph-1.2.4 → langgraph-1.2.5}/tests/test_interleave_arrival_order.py +0 -0
- {langgraph-1.2.4 → langgraph-1.2.5}/tests/test_interrupt_migration.py +0 -0
- {langgraph-1.2.4 → langgraph-1.2.5}/tests/test_interruption.py +0 -0
- {langgraph-1.2.4 → langgraph-1.2.5}/tests/test_managed_values.py +0 -0
- {langgraph-1.2.4 → langgraph-1.2.5}/tests/test_messages_state.py +0 -0
- {langgraph-1.2.4 → langgraph-1.2.5}/tests/test_parent_command.py +0 -0
- {langgraph-1.2.4 → langgraph-1.2.5}/tests/test_parent_command_async.py +0 -0
- {langgraph-1.2.4 → langgraph-1.2.5}/tests/test_pregel_debug.py +0 -0
- {langgraph-1.2.4 → langgraph-1.2.5}/tests/test_pregel_stream_events_v3.py +0 -0
- {langgraph-1.2.4 → langgraph-1.2.5}/tests/test_pydantic.py +0 -0
- {langgraph-1.2.4 → langgraph-1.2.5}/tests/test_remote_graph.py +0 -0
- {langgraph-1.2.4 → langgraph-1.2.5}/tests/test_remote_graph_v3.py +0 -0
- {langgraph-1.2.4 → langgraph-1.2.5}/tests/test_retry.py +0 -0
- {langgraph-1.2.4 → langgraph-1.2.5}/tests/test_runnable.py +0 -0
- {langgraph-1.2.4 → langgraph-1.2.5}/tests/test_runtime.py +0 -0
- {langgraph-1.2.4 → langgraph-1.2.5}/tests/test_serde_allowlist.py +0 -0
- {langgraph-1.2.4 → langgraph-1.2.5}/tests/test_state.py +0 -0
- {langgraph-1.2.4 → langgraph-1.2.5}/tests/test_stream_before_builtins.py +0 -0
- {langgraph-1.2.4 → langgraph-1.2.5}/tests/test_stream_data_transformers.py +0 -0
- {langgraph-1.2.4 → langgraph-1.2.5}/tests/test_stream_events_v3_e2e.py +0 -0
- {langgraph-1.2.4 → langgraph-1.2.5}/tests/test_stream_events_v3_kwarg_forwarding.py +0 -0
- {langgraph-1.2.4 → langgraph-1.2.5}/tests/test_stream_lifecycle_transformer.py +0 -0
- {langgraph-1.2.4 → langgraph-1.2.5}/tests/test_stream_messages_transformer.py +0 -0
- {langgraph-1.2.4 → langgraph-1.2.5}/tests/test_stream_subgraph_transformer.py +0 -0
- {langgraph-1.2.4 → langgraph-1.2.5}/tests/test_subgraph_persistence.py +0 -0
- {langgraph-1.2.4 → langgraph-1.2.5}/tests/test_subgraph_persistence_async.py +0 -0
- {langgraph-1.2.4 → langgraph-1.2.5}/tests/test_time_travel.py +0 -0
- {langgraph-1.2.4 → langgraph-1.2.5}/tests/test_time_travel_async.py +0 -0
- {langgraph-1.2.4 → langgraph-1.2.5}/tests/test_tool_stream_handler.py +0 -0
- {langgraph-1.2.4 → langgraph-1.2.5}/tests/test_tracing_interops.py +0 -0
- {langgraph-1.2.4 → langgraph-1.2.5}/tests/test_type_checking.py +0 -0
|
@@ -111,22 +111,19 @@ test_watch_all:
|
|
|
111
111
|
|
|
112
112
|
# Define a variable for Python and notebook files.
|
|
113
113
|
PYTHON_FILES=.
|
|
114
|
-
MYPY_CACHE=.mypy_cache
|
|
115
114
|
lint format: PYTHON_FILES=.
|
|
116
115
|
lint_diff format_diff: PYTHON_FILES=$(shell git diff --name-only --relative --diff-filter=d main . | grep -E r'\.py$$|\.ipynb$$')
|
|
117
116
|
lint_package: PYTHON_FILES=langgraph
|
|
118
117
|
lint_tests: PYTHON_FILES=tests
|
|
119
|
-
lint_tests: MYPY_CACHE=.mypy_cache_test
|
|
120
118
|
|
|
121
119
|
lint lint_diff lint_package lint_tests:
|
|
122
120
|
uv run ruff check .
|
|
123
121
|
[ "$(PYTHON_FILES)" = "" ] || uv run ruff format $(PYTHON_FILES) --diff
|
|
124
122
|
[ "$(PYTHON_FILES)" = "" ] || uv run ruff check --select I $(PYTHON_FILES)
|
|
125
|
-
[ "$(PYTHON_FILES)" = "" ] ||
|
|
126
|
-
[ "$(PYTHON_FILES)" = "" ] || uv run mypy langgraph --cache-dir $(MYPY_CACHE)
|
|
123
|
+
[ "$(PYTHON_FILES)" = "" ] || uv run ty check langgraph
|
|
127
124
|
|
|
128
125
|
type:
|
|
129
|
-
|
|
126
|
+
uv run ty check langgraph
|
|
130
127
|
|
|
131
128
|
format format_diff:
|
|
132
129
|
uv run ruff format $(PYTHON_FILES)
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: langgraph
|
|
3
|
-
Version: 1.2.
|
|
3
|
+
Version: 1.2.5
|
|
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/
|
|
@@ -22,7 +22,7 @@ Classifier: Programming Language :: Python :: 3.13
|
|
|
22
22
|
Classifier: Programming Language :: Python :: Implementation :: CPython
|
|
23
23
|
Classifier: Programming Language :: Python :: Implementation :: PyPy
|
|
24
24
|
Requires-Python: >=3.10
|
|
25
|
-
Requires-Dist: langchain-core<2,>=1.4.
|
|
25
|
+
Requires-Dist: langchain-core<2,>=1.4.7
|
|
26
26
|
Requires-Dist: langgraph-checkpoint<5.0.0,>=4.1.0
|
|
27
27
|
Requires-Dist: langgraph-prebuilt<1.2.0,>=1.1.0
|
|
28
28
|
Requires-Dist: langgraph-sdk<0.5.0,>=0.4.2
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
3
|
from collections import ChainMap
|
|
4
|
-
from collections.abc import Sequence
|
|
4
|
+
from collections.abc import Mapping, Sequence
|
|
5
5
|
from os import getenv
|
|
6
6
|
from typing import Any, cast
|
|
7
7
|
|
|
@@ -79,6 +79,42 @@ def patch_checkpoint_map(
|
|
|
79
79
|
return config
|
|
80
80
|
|
|
81
81
|
|
|
82
|
+
def _copy_mapping_value(value: Any) -> Any:
|
|
83
|
+
return dict(value) if isinstance(value, Mapping) else value
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
def _merge_metadata(
|
|
87
|
+
base: Mapping[str, Any] | None, new: Mapping[str, Any] | None
|
|
88
|
+
) -> dict[str, Any]:
|
|
89
|
+
"""Merge metadata without mutating inputs.
|
|
90
|
+
|
|
91
|
+
Top-level keys merge with newer values winning. `lc_versions` is the only
|
|
92
|
+
mapping-valued key that merges one level deeper, so independent LangChain
|
|
93
|
+
packages can contribute package versions without changing generic metadata
|
|
94
|
+
semantics. Mapping values are copied one level, so deeper nested objects
|
|
95
|
+
remain shared. `None` inputs are treated as empty metadata.
|
|
96
|
+
|
|
97
|
+
Mirrors `langchain_core.runnables.config._merge_metadata_dicts` so configs
|
|
98
|
+
that pass through LangGraph's merge keep the same `lc_versions` semantics;
|
|
99
|
+
keep the two in sync. Unlike lc-core, this copies mapping values one level
|
|
100
|
+
(intentionally more defensive — do not "simplify" back to a shared ref).
|
|
101
|
+
"""
|
|
102
|
+
merged = {key: _copy_mapping_value(value) for key, value in (base or {}).items()}
|
|
103
|
+
for key, value in (new or {}).items():
|
|
104
|
+
if (
|
|
105
|
+
key == "lc_versions"
|
|
106
|
+
and isinstance(merged.get(key), Mapping)
|
|
107
|
+
and isinstance(value, Mapping)
|
|
108
|
+
):
|
|
109
|
+
merged[key] = {
|
|
110
|
+
**cast(Mapping[str, Any], merged[key]),
|
|
111
|
+
**value,
|
|
112
|
+
}
|
|
113
|
+
else:
|
|
114
|
+
merged[key] = _copy_mapping_value(value)
|
|
115
|
+
return merged
|
|
116
|
+
|
|
117
|
+
|
|
82
118
|
def _merge_callbacks(base: Callbacks, new: Callbacks) -> Callbacks:
|
|
83
119
|
"""Merge two callbacks values (None / list / BaseCallbackManager).
|
|
84
120
|
|
|
@@ -126,10 +162,10 @@ def merge_configs(*configs: RunnableConfig | None) -> RunnableConfig:
|
|
|
126
162
|
if not value:
|
|
127
163
|
continue
|
|
128
164
|
if key == "metadata":
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
165
|
+
base[key] = _merge_metadata(
|
|
166
|
+
cast(Mapping[str, Any] | None, base.get(key)),
|
|
167
|
+
cast(Mapping[str, Any], value),
|
|
168
|
+
)
|
|
133
169
|
elif key == "tags":
|
|
134
170
|
if base_value := base.get(key):
|
|
135
171
|
base[key] = [*base_value, *value] # type: ignore
|
|
@@ -327,14 +363,11 @@ def ensure_config(*configs: RunnableConfig | None) -> RunnableConfig:
|
|
|
327
363
|
empty.get("callbacks"), cast(Callbacks, v)
|
|
328
364
|
)
|
|
329
365
|
elif k == "metadata":
|
|
330
|
-
#
|
|
331
|
-
#
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
{**cast(dict, existing), **cast(dict, v)}
|
|
336
|
-
if existing
|
|
337
|
-
else cast(dict, v).copy()
|
|
366
|
+
# Matches merge_configs: top-level metadata keys merge, and
|
|
367
|
+
# only `lc_versions` merges one level deeper.
|
|
368
|
+
empty["metadata"] = _merge_metadata(
|
|
369
|
+
cast(Mapping[str, Any] | None, empty.get("metadata")),
|
|
370
|
+
cast(Mapping[str, Any], v),
|
|
338
371
|
)
|
|
339
372
|
elif k == "tags":
|
|
340
373
|
# Concatenate tags across configs so values bound via
|
|
@@ -108,6 +108,7 @@ from langgraph.callbacks import (
|
|
|
108
108
|
get_sync_graph_callback_manager_for_config,
|
|
109
109
|
)
|
|
110
110
|
from langgraph.channels.base import BaseChannel
|
|
111
|
+
from langgraph.channels.delta import DeltaChannel
|
|
111
112
|
from langgraph.channels.topic import Topic
|
|
112
113
|
from langgraph.config import get_config
|
|
113
114
|
from langgraph.constants import END
|
|
@@ -1996,11 +1997,33 @@ class Pregel(
|
|
|
1996
1997
|
),
|
|
1997
1998
|
)
|
|
1998
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
|
+
|
|
1999
2021
|
for task_id, task in zip(run_task_ids, run_tasks):
|
|
2000
2022
|
# channel writes are saved to current checkpoint
|
|
2001
2023
|
channel_writes = [w for w in task.writes if w[0] != PUSH]
|
|
2002
|
-
if
|
|
2024
|
+
if should_put_writes and channel_writes:
|
|
2003
2025
|
checkpointer.put_writes(checkpoint_config, channel_writes, task_id)
|
|
2026
|
+
|
|
2004
2027
|
# apply to checkpoint and save
|
|
2005
2028
|
apply_writes(
|
|
2006
2029
|
checkpoint,
|
|
@@ -2441,10 +2464,31 @@ class Pregel(
|
|
|
2441
2464
|
),
|
|
2442
2465
|
)
|
|
2443
2466
|
# 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
|
+
|
|
2444
2488
|
for task_id, task in zip(run_task_ids, run_tasks):
|
|
2445
2489
|
# channel writes are saved to current checkpoint
|
|
2446
2490
|
channel_writes = [w for w in task.writes if w[0] != PUSH]
|
|
2447
|
-
if
|
|
2491
|
+
if should_put_writes and channel_writes:
|
|
2448
2492
|
await checkpointer.aput_writes(
|
|
2449
2493
|
checkpoint_config, channel_writes, task_id
|
|
2450
2494
|
)
|
|
@@ -32,7 +32,7 @@ from langgraph.warnings import LangGraphDeprecatedSinceV10, LangGraphDeprecatedS
|
|
|
32
32
|
|
|
33
33
|
# Local TypeVars for generic stream TypedDicts.
|
|
34
34
|
# We use separate TypeVars here (rather than importing from langgraph.typing)
|
|
35
|
-
# because the typing module TypeVars have defaults that cause
|
|
35
|
+
# because the typing module TypeVars have defaults that cause type checker issues
|
|
36
36
|
# when used in standalone type aliases.
|
|
37
37
|
StateT = TypeVar("StateT")
|
|
38
38
|
OutputT = TypeVar("OutputT")
|
|
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "langgraph"
|
|
7
|
-
version = "1.2.
|
|
7
|
+
version = "1.2.5"
|
|
8
8
|
description = "Building stateful, multi-actor applications with LLMs"
|
|
9
9
|
authors = []
|
|
10
10
|
requires-python = ">=3.10"
|
|
@@ -24,7 +24,7 @@ classifiers = [
|
|
|
24
24
|
'Programming Language :: Python :: 3.13',
|
|
25
25
|
]
|
|
26
26
|
dependencies = [
|
|
27
|
-
"langchain-core>=1.4.
|
|
27
|
+
"langchain-core>=1.4.7,<2",
|
|
28
28
|
"langgraph-checkpoint>=4.1.0,<5.0.0",
|
|
29
29
|
"langgraph-sdk>=0.4.2,<0.5.0",
|
|
30
30
|
"langgraph-prebuilt>=1.1.0,<1.2.0",
|
|
@@ -69,8 +69,8 @@ test = [
|
|
|
69
69
|
"redis",
|
|
70
70
|
]
|
|
71
71
|
lint = [
|
|
72
|
-
"mypy",
|
|
73
72
|
"ruff",
|
|
73
|
+
"ty",
|
|
74
74
|
"types-requests",
|
|
75
75
|
]
|
|
76
76
|
dev = [
|
|
@@ -99,15 +99,24 @@ target-version = "py310"
|
|
|
99
99
|
[tool.ruff.lint.flake8-tidy-imports.banned-api]
|
|
100
100
|
"typing.TypedDict".msg = "Use typing_extensions.TypedDict instead."
|
|
101
101
|
|
|
102
|
-
[tool.
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
102
|
+
[tool.ty.rules]
|
|
103
|
+
call-non-callable = "ignore"
|
|
104
|
+
deprecated = "ignore"
|
|
105
|
+
invalid-argument-type = "ignore"
|
|
106
|
+
invalid-assignment = "ignore"
|
|
107
|
+
invalid-key = "ignore"
|
|
108
|
+
invalid-method-override = "ignore"
|
|
109
|
+
invalid-paramspec = "ignore"
|
|
110
|
+
invalid-return-type = "ignore"
|
|
111
|
+
invalid-type-form = "ignore"
|
|
112
|
+
invalid-yield = "ignore"
|
|
113
|
+
missing-typed-dict-key = "ignore"
|
|
114
|
+
no-matching-overload = "ignore"
|
|
115
|
+
not-iterable = "ignore"
|
|
116
|
+
unused-type-ignore-comment = "ignore"
|
|
117
|
+
unresolved-attribute = "ignore"
|
|
118
|
+
unsupported-base = "ignore"
|
|
119
|
+
unsupported-operator = "ignore"
|
|
111
120
|
|
|
112
121
|
[tool.coverage.run]
|
|
113
122
|
omit = ["tests/*"]
|
|
@@ -0,0 +1,294 @@
|
|
|
1
|
+
"""Tests for `update_state` / `aupdate_state` against `DeltaChannel`.
|
|
2
|
+
|
|
3
|
+
Originally a regression suite for deepagents#3774 — `update_state` on a *fresh*
|
|
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
|
+
Fixed by lazily persisting an empty stub checkpoint on a fresh thread so the
|
|
7
|
+
first write has a parent to anchor under (mirrors the exit-mode lazy-stub
|
|
8
|
+
pattern in `_loop._put_exit_delta_writes`).
|
|
9
|
+
|
|
10
|
+
Coverage:
|
|
11
|
+
|
|
12
|
+
* fresh-thread regression: single `update_state` writes a message and reads back
|
|
13
|
+
* non-fresh thread: `update_state` after `invoke`, after another `update_state`,
|
|
14
|
+
and `bulk_update_state` with multiple per-superstep updates
|
|
15
|
+
* update-by-id end-to-end via `update_state` (DeltaChannel reducer semantics)
|
|
16
|
+
* state-history chain shape on a fresh thread (lazy stub + update checkpoint
|
|
17
|
+
with correct parent linking)
|
|
18
|
+
"""
|
|
19
|
+
|
|
20
|
+
from typing import Annotated, Any
|
|
21
|
+
|
|
22
|
+
import pytest
|
|
23
|
+
from langchain_core.messages import HumanMessage
|
|
24
|
+
from langgraph.checkpoint.memory import InMemorySaver
|
|
25
|
+
from typing_extensions import TypedDict
|
|
26
|
+
|
|
27
|
+
from langgraph.channels.delta import DeltaChannel
|
|
28
|
+
from langgraph.graph import START, StateGraph
|
|
29
|
+
from langgraph.graph.message import _messages_delta_reducer
|
|
30
|
+
|
|
31
|
+
pytestmark = pytest.mark.anyio
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def _build_graph(checkpointer: InMemorySaver, *, two_nodes: bool = False) -> Any:
|
|
35
|
+
"""Compile a minimal DeltaChannel-backed `messages` graph.
|
|
36
|
+
|
|
37
|
+
`two_nodes=True` adds a second writer node so `bulk_update_state` can route
|
|
38
|
+
distinct updates to different `as_node` values within a single superstep.
|
|
39
|
+
"""
|
|
40
|
+
channel = DeltaChannel(_messages_delta_reducer)
|
|
41
|
+
State = TypedDict("State", {"messages": Annotated[list, channel]}) # type: ignore[call-overload] # noqa: UP013
|
|
42
|
+
|
|
43
|
+
def model(state: dict) -> dict:
|
|
44
|
+
return {}
|
|
45
|
+
|
|
46
|
+
def assistant(state: dict) -> dict:
|
|
47
|
+
return {}
|
|
48
|
+
|
|
49
|
+
builder = StateGraph(State)
|
|
50
|
+
builder.add_node("model", model)
|
|
51
|
+
builder.add_edge(START, "model")
|
|
52
|
+
if two_nodes:
|
|
53
|
+
builder.add_node("assistant", assistant)
|
|
54
|
+
builder.add_edge("model", "assistant")
|
|
55
|
+
builder.set_finish_point("assistant")
|
|
56
|
+
else:
|
|
57
|
+
builder.set_finish_point("model")
|
|
58
|
+
return builder.compile(checkpointer=checkpointer)
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
# ---------------------------------------------------------------------------
|
|
62
|
+
# Fresh-thread regression (deepagents#3774)
|
|
63
|
+
# ---------------------------------------------------------------------------
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
def test_update_state_fresh_thread_delta_channel() -> None:
|
|
67
|
+
saver = InMemorySaver()
|
|
68
|
+
graph = _build_graph(saver)
|
|
69
|
+
config = {"configurable": {"thread_id": "fresh-sync"}}
|
|
70
|
+
message = HumanMessage(content="hello", id="m1")
|
|
71
|
+
|
|
72
|
+
graph.update_state(config, {"messages": [message]}, as_node="model")
|
|
73
|
+
|
|
74
|
+
state = graph.get_state(config)
|
|
75
|
+
assert [m.content for m in state.values["messages"]] == ["hello"]
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
async def test_aupdate_state_fresh_thread_delta_channel() -> None:
|
|
79
|
+
saver = InMemorySaver()
|
|
80
|
+
graph = _build_graph(saver)
|
|
81
|
+
config = {"configurable": {"thread_id": "fresh-async"}}
|
|
82
|
+
message = HumanMessage(content="hello", id="m1")
|
|
83
|
+
|
|
84
|
+
await graph.aupdate_state(config, {"messages": [message]}, as_node="model")
|
|
85
|
+
|
|
86
|
+
state = await graph.aget_state(config)
|
|
87
|
+
assert [m.content for m in state.values["messages"]] == ["hello"]
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
# ---------------------------------------------------------------------------
|
|
91
|
+
# Non-fresh thread: update_state after invoke
|
|
92
|
+
# ---------------------------------------------------------------------------
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
def test_update_state_after_invoke_delta_channel() -> None:
|
|
96
|
+
"""The non-fresh-thread path was already working before the fix; pin it
|
|
97
|
+
down so the lazy-stub change for fresh threads doesn't regress it."""
|
|
98
|
+
saver = InMemorySaver()
|
|
99
|
+
graph = _build_graph(saver)
|
|
100
|
+
config = {"configurable": {"thread_id": "after-invoke-sync"}}
|
|
101
|
+
|
|
102
|
+
graph.invoke({"messages": [HumanMessage(content="seed", id="m1")]}, config)
|
|
103
|
+
graph.update_state(
|
|
104
|
+
config,
|
|
105
|
+
{"messages": [HumanMessage(content="appended", id="m2")]},
|
|
106
|
+
as_node="model",
|
|
107
|
+
)
|
|
108
|
+
|
|
109
|
+
state = graph.get_state(config)
|
|
110
|
+
assert [m.content for m in state.values["messages"]] == ["seed", "appended"]
|
|
111
|
+
assert [m.id for m in state.values["messages"]] == ["m1", "m2"]
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
async def test_aupdate_state_after_invoke_delta_channel() -> None:
|
|
115
|
+
saver = InMemorySaver()
|
|
116
|
+
graph = _build_graph(saver)
|
|
117
|
+
config = {"configurable": {"thread_id": "after-invoke-async"}}
|
|
118
|
+
|
|
119
|
+
await graph.ainvoke({"messages": [HumanMessage(content="seed", id="m1")]}, config)
|
|
120
|
+
await graph.aupdate_state(
|
|
121
|
+
config,
|
|
122
|
+
{"messages": [HumanMessage(content="appended", id="m2")]},
|
|
123
|
+
as_node="model",
|
|
124
|
+
)
|
|
125
|
+
|
|
126
|
+
state = await graph.aget_state(config)
|
|
127
|
+
assert [m.content for m in state.values["messages"]] == ["seed", "appended"]
|
|
128
|
+
|
|
129
|
+
|
|
130
|
+
# ---------------------------------------------------------------------------
|
|
131
|
+
# Non-fresh thread: consecutive update_state calls
|
|
132
|
+
# ---------------------------------------------------------------------------
|
|
133
|
+
|
|
134
|
+
|
|
135
|
+
def test_consecutive_update_states_delta_channel() -> None:
|
|
136
|
+
"""First update_state lazily persists a stub; the second sees a real
|
|
137
|
+
parent (`saved is not None`) and takes the original write path. Both
|
|
138
|
+
messages must round-trip in chronological order."""
|
|
139
|
+
saver = InMemorySaver()
|
|
140
|
+
graph = _build_graph(saver)
|
|
141
|
+
config = {"configurable": {"thread_id": "consecutive-sync"}}
|
|
142
|
+
|
|
143
|
+
graph.update_state(
|
|
144
|
+
config,
|
|
145
|
+
{"messages": [HumanMessage(content="first", id="m1")]},
|
|
146
|
+
as_node="model",
|
|
147
|
+
)
|
|
148
|
+
graph.update_state(
|
|
149
|
+
config,
|
|
150
|
+
{"messages": [HumanMessage(content="second", id="m2")]},
|
|
151
|
+
as_node="model",
|
|
152
|
+
)
|
|
153
|
+
|
|
154
|
+
state = graph.get_state(config)
|
|
155
|
+
assert [m.content for m in state.values["messages"]] == ["first", "second"]
|
|
156
|
+
assert [m.id for m in state.values["messages"]] == ["m1", "m2"]
|
|
157
|
+
|
|
158
|
+
|
|
159
|
+
async def test_aconsecutive_update_states_delta_channel() -> None:
|
|
160
|
+
saver = InMemorySaver()
|
|
161
|
+
graph = _build_graph(saver)
|
|
162
|
+
config = {"configurable": {"thread_id": "consecutive-async"}}
|
|
163
|
+
|
|
164
|
+
await graph.aupdate_state(
|
|
165
|
+
config,
|
|
166
|
+
{"messages": [HumanMessage(content="first", id="m1")]},
|
|
167
|
+
as_node="model",
|
|
168
|
+
)
|
|
169
|
+
await graph.aupdate_state(
|
|
170
|
+
config,
|
|
171
|
+
{"messages": [HumanMessage(content="second", id="m2")]},
|
|
172
|
+
as_node="model",
|
|
173
|
+
)
|
|
174
|
+
|
|
175
|
+
state = await graph.aget_state(config)
|
|
176
|
+
assert [m.content for m in state.values["messages"]] == ["first", "second"]
|
|
177
|
+
|
|
178
|
+
|
|
179
|
+
# ---------------------------------------------------------------------------
|
|
180
|
+
# Update-by-id semantics through the update_state path
|
|
181
|
+
# ---------------------------------------------------------------------------
|
|
182
|
+
|
|
183
|
+
|
|
184
|
+
def test_update_state_replaces_message_by_id_delta_channel() -> None:
|
|
185
|
+
"""`_messages_delta_reducer` dedups by `id` — re-issuing a write with the
|
|
186
|
+
same id replaces the existing entry rather than appending. Verify this
|
|
187
|
+
works through the `update_state` path (not just `invoke`)."""
|
|
188
|
+
saver = InMemorySaver()
|
|
189
|
+
graph = _build_graph(saver)
|
|
190
|
+
config = {"configurable": {"thread_id": "update-by-id"}}
|
|
191
|
+
|
|
192
|
+
graph.invoke({"messages": [HumanMessage(content="original", id="h1")]}, config)
|
|
193
|
+
graph.update_state(
|
|
194
|
+
config,
|
|
195
|
+
{"messages": [HumanMessage(content="updated", id="h1")]},
|
|
196
|
+
as_node="model",
|
|
197
|
+
)
|
|
198
|
+
|
|
199
|
+
state = graph.get_state(config)
|
|
200
|
+
msgs = state.values["messages"]
|
|
201
|
+
assert len(msgs) == 1
|
|
202
|
+
assert msgs[0].id == "h1"
|
|
203
|
+
assert msgs[0].content == "updated"
|
|
204
|
+
|
|
205
|
+
|
|
206
|
+
# ---------------------------------------------------------------------------
|
|
207
|
+
# bulk_update_state with multiple updates per superstep
|
|
208
|
+
# ---------------------------------------------------------------------------
|
|
209
|
+
|
|
210
|
+
|
|
211
|
+
def test_bulk_update_state_multi_task_per_superstep_delta_channel() -> None:
|
|
212
|
+
"""`bulk_update_state` with N updates in one superstep produces N tasks
|
|
213
|
+
that each call `put_writes`. Guards the regression where moving
|
|
214
|
+
`put_writes` outside the per-task loop would persist only the last
|
|
215
|
+
task's writes.
|
|
216
|
+
|
|
217
|
+
Explicit `task_id`s are required to disambiguate writes belonging to
|
|
218
|
+
different `StateUpdate`s targeting the same node — otherwise both share
|
|
219
|
+
the deterministic interrupt-derived id and collide in the saver.
|
|
220
|
+
"""
|
|
221
|
+
from langgraph.types import StateUpdate
|
|
222
|
+
|
|
223
|
+
saver = InMemorySaver()
|
|
224
|
+
graph = _build_graph(saver)
|
|
225
|
+
config = {"configurable": {"thread_id": "bulk-multi-task"}}
|
|
226
|
+
|
|
227
|
+
graph.bulk_update_state(
|
|
228
|
+
config,
|
|
229
|
+
[
|
|
230
|
+
[
|
|
231
|
+
StateUpdate(
|
|
232
|
+
values={"messages": [HumanMessage(content="first", id="m1")]},
|
|
233
|
+
as_node="model",
|
|
234
|
+
task_id="task-1",
|
|
235
|
+
),
|
|
236
|
+
StateUpdate(
|
|
237
|
+
values={"messages": [HumanMessage(content="second", id="m2")]},
|
|
238
|
+
as_node="model",
|
|
239
|
+
task_id="task-2",
|
|
240
|
+
),
|
|
241
|
+
]
|
|
242
|
+
],
|
|
243
|
+
)
|
|
244
|
+
|
|
245
|
+
state = graph.get_state(config)
|
|
246
|
+
contents = [m.content for m in state.values["messages"]]
|
|
247
|
+
ids = [m.id for m in state.values["messages"]]
|
|
248
|
+
assert sorted(contents) == ["first", "second"], (
|
|
249
|
+
f"both updates' writes must persist; got {contents}"
|
|
250
|
+
)
|
|
251
|
+
assert sorted(ids) == ["m1", "m2"]
|
|
252
|
+
|
|
253
|
+
|
|
254
|
+
# ---------------------------------------------------------------------------
|
|
255
|
+
# Public-API observation of the lazy-stub mechanism
|
|
256
|
+
# ---------------------------------------------------------------------------
|
|
257
|
+
|
|
258
|
+
|
|
259
|
+
def test_state_history_chain_after_fresh_update_state_delta_channel() -> None:
|
|
260
|
+
"""A fresh-thread `update_state` should produce two checkpoints visible
|
|
261
|
+
via `get_state_history`: a stub (step=-1, no parent) and the update
|
|
262
|
+
(step=0, parent=stub). Both attributed `source='update'`."""
|
|
263
|
+
saver = InMemorySaver()
|
|
264
|
+
graph = _build_graph(saver)
|
|
265
|
+
config = {"configurable": {"thread_id": "history-chain"}}
|
|
266
|
+
|
|
267
|
+
graph.update_state(
|
|
268
|
+
config,
|
|
269
|
+
{"messages": [HumanMessage(content="hello", id="m1")]},
|
|
270
|
+
as_node="model",
|
|
271
|
+
)
|
|
272
|
+
|
|
273
|
+
# Newest first per `get_state_history` ordering.
|
|
274
|
+
history = list(graph.get_state_history(config))
|
|
275
|
+
assert len(history) == 2
|
|
276
|
+
|
|
277
|
+
update_snapshot, stub_snapshot = history
|
|
278
|
+
|
|
279
|
+
assert update_snapshot.metadata is not None
|
|
280
|
+
assert update_snapshot.metadata["source"] == "update"
|
|
281
|
+
assert update_snapshot.metadata["step"] == 0
|
|
282
|
+
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
|
+
)
|
|
@@ -9,6 +9,7 @@ import pytest
|
|
|
9
9
|
from langchain_core.messages import AIMessage, AnyMessage, ToolCall
|
|
10
10
|
from langchain_core.runnables import RunnableConfig, RunnableMap, RunnablePick
|
|
11
11
|
from langchain_core.tools import tool
|
|
12
|
+
from langchain_core.version import VERSION as LANGCHAIN_CORE_VERSION
|
|
12
13
|
from langgraph.checkpoint.base import BaseCheckpointSaver
|
|
13
14
|
from langgraph.checkpoint.memory import InMemorySaver
|
|
14
15
|
from langgraph.prebuilt.chat_agent_executor import create_react_agent
|
|
@@ -1399,6 +1400,7 @@ def test_prebuilt_tool_chat(snapshot: SnapshotAssertion) -> None:
|
|
|
1399
1400
|
"ls_provider": "fakechatmodel",
|
|
1400
1401
|
"ls_model_type": "chat",
|
|
1401
1402
|
"ls_integration": "langchain_chat_model",
|
|
1403
|
+
"lc_versions": {"langchain-core": LANGCHAIN_CORE_VERSION},
|
|
1402
1404
|
},
|
|
1403
1405
|
),
|
|
1404
1406
|
(
|
|
@@ -1461,6 +1463,7 @@ def test_prebuilt_tool_chat(snapshot: SnapshotAssertion) -> None:
|
|
|
1461
1463
|
"ls_provider": "fakechatmodel",
|
|
1462
1464
|
"ls_model_type": "chat",
|
|
1463
1465
|
"ls_integration": "langchain_chat_model",
|
|
1466
|
+
"lc_versions": {"langchain-core": LANGCHAIN_CORE_VERSION},
|
|
1464
1467
|
},
|
|
1465
1468
|
),
|
|
1466
1469
|
]
|
|
@@ -1513,6 +1516,7 @@ def test_prebuilt_tool_chat(snapshot: SnapshotAssertion) -> None:
|
|
|
1513
1516
|
"ls_provider": "fakechatmodel",
|
|
1514
1517
|
"ls_model_type": "chat",
|
|
1515
1518
|
"ls_integration": "langchain_chat_model",
|
|
1519
|
+
"lc_versions": {"langchain-core": LANGCHAIN_CORE_VERSION},
|
|
1516
1520
|
},
|
|
1517
1521
|
),
|
|
1518
1522
|
]
|
|
@@ -6884,6 +6888,7 @@ def test_weather_subgraph(
|
|
|
6884
6888
|
"ls_provider": "fakemessageslistchatmodel",
|
|
6885
6889
|
"ls_model_type": "chat",
|
|
6886
6890
|
"ls_integration": "langchain_chat_model",
|
|
6891
|
+
"lc_versions": {"langchain-core": LANGCHAIN_CORE_VERSION},
|
|
6887
6892
|
},
|
|
6888
6893
|
),
|
|
6889
6894
|
),
|
|
@@ -6911,6 +6916,7 @@ def test_weather_subgraph(
|
|
|
6911
6916
|
"ls_provider": "fakemessageslistchatmodel",
|
|
6912
6917
|
"ls_model_type": "chat",
|
|
6913
6918
|
"ls_integration": "langchain_chat_model",
|
|
6919
|
+
"lc_versions": {"langchain-core": LANGCHAIN_CORE_VERSION},
|
|
6914
6920
|
},
|
|
6915
6921
|
),
|
|
6916
6922
|
),
|
|
@@ -6947,6 +6953,7 @@ def test_weather_subgraph(
|
|
|
6947
6953
|
"ls_provider": "fakemessageslistchatmodel",
|
|
6948
6954
|
"ls_model_type": "chat",
|
|
6949
6955
|
"ls_integration": "langchain_chat_model",
|
|
6956
|
+
"lc_versions": {"langchain-core": LANGCHAIN_CORE_VERSION},
|
|
6950
6957
|
},
|
|
6951
6958
|
),
|
|
6952
6959
|
]
|