mirror of
https://github.com/langchain-ai/langgraph.git
synced 2026-10-24 17:01:45 +02:00
Compare commits
3
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4c52e129eb | ||
|
|
6c4ceb1332 | ||
|
|
4897c51365 |
No files matched your search
@@ -29,7 +29,19 @@ from langgraph._internal._constants import (
|
||||
NS_SEP,
|
||||
)
|
||||
|
||||
DEFAULT_RECURSION_LIMIT = int(getenv("LANGGRAPH_DEFAULT_RECURSION_LIMIT", "10007"))
|
||||
|
||||
class _DefaultRecursionLimit(int):
|
||||
"""The limit `ensure_config` fills in when none is given.
|
||||
|
||||
`merge_configs` skips values of this type rather than any equal value, so an
|
||||
explicit limit that happens to equal the default still applies. It checks the
|
||||
type, not identity, because copying or pickling a config rebuilds the value.
|
||||
"""
|
||||
|
||||
|
||||
DEFAULT_RECURSION_LIMIT = _DefaultRecursionLimit(
|
||||
getenv("LANGGRAPH_DEFAULT_RECURSION_LIMIT", "10007")
|
||||
)
|
||||
DELTA_MAX_SUPERSTEPS_SINCE_SNAPSHOT = int(
|
||||
getenv("LANGGRAPH_DELTA_MAX_SUPERSTEPS_SINCE_SNAPSHOT", "5000")
|
||||
)
|
||||
@@ -182,7 +194,7 @@ def merge_configs(*configs: RunnableConfig | None) -> RunnableConfig:
|
||||
base.get("callbacks"), cast(Callbacks, value)
|
||||
)
|
||||
elif key == "recursion_limit":
|
||||
if config["recursion_limit"] != DEFAULT_RECURSION_LIMIT:
|
||||
if not isinstance(config["recursion_limit"], _DefaultRecursionLimit):
|
||||
base["recursion_limit"] = config["recursion_limit"]
|
||||
else:
|
||||
base[key] = config[key] # type: ignore[literal-required]
|
||||
|
||||
@@ -861,11 +861,6 @@ class RemoteGraph(PregelProtocol):
|
||||
)
|
||||
yield {"type": mode, "ns": ns, "data": chunk.data, "interrupts": ints}
|
||||
elif subgraphs:
|
||||
if NS_SEP in chunk.event:
|
||||
mode, ns_ = chunk.event.split(NS_SEP, 1)
|
||||
ns = tuple(ns_.split(NS_SEP))
|
||||
else:
|
||||
mode, ns = chunk.event, ()
|
||||
if req_single:
|
||||
yield ns, chunk.data
|
||||
else:
|
||||
@@ -1016,11 +1011,6 @@ class RemoteGraph(PregelProtocol):
|
||||
)
|
||||
yield {"type": mode, "ns": ns, "data": chunk.data, "interrupts": ints}
|
||||
elif subgraphs:
|
||||
if NS_SEP in chunk.event:
|
||||
mode, ns_ = chunk.event.split(NS_SEP, 1)
|
||||
ns = tuple(ns_.split(NS_SEP))
|
||||
else:
|
||||
mode, ns = chunk.event, ()
|
||||
if req_single:
|
||||
yield ns, chunk.data
|
||||
else:
|
||||
|
||||
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
|
||||
|
||||
[project]
|
||||
name = "langgraph"
|
||||
version = "1.2.16"
|
||||
version = "1.2.17"
|
||||
description = "Building stateful, multi-actor applications with LLMs"
|
||||
authors = []
|
||||
requires-python = ">=3.10"
|
||||
|
||||
@@ -1,11 +1,19 @@
|
||||
from collections.abc import Callable
|
||||
|
||||
import pytest
|
||||
from langchain_core.callbacks import AsyncCallbackManager, BaseCallbackHandler
|
||||
|
||||
from langgraph._internal._config import get_async_callback_manager_for_config
|
||||
from langgraph._internal._config import (
|
||||
DEFAULT_RECURSION_LIMIT,
|
||||
get_async_callback_manager_for_config,
|
||||
)
|
||||
from langgraph.graph import StateGraph
|
||||
from langgraph.pregel import Pregel
|
||||
|
||||
pytestmark = pytest.mark.anyio
|
||||
|
||||
EXPLICIT_DEFAULT_LIMIT = int(DEFAULT_RECURSION_LIMIT)
|
||||
|
||||
|
||||
def test_new_async_manager_includes_tags() -> None:
|
||||
config = {"callbacks": None}
|
||||
@@ -114,3 +122,27 @@ async def test_with_config_tags_preserved_on_invoke() -> None:
|
||||
await graph.ainvoke({}, {"tags": ["invoke"]})
|
||||
assert "bound" in captured, "bound tag was dropped by ensure_config overwrite"
|
||||
assert "invoke" in captured, "invoke-time tag not present"
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"rebind",
|
||||
[
|
||||
lambda g: g.with_config(recursion_limit=EXPLICIT_DEFAULT_LIMIT),
|
||||
lambda g: g.with_config({"recursion_limit": EXPLICIT_DEFAULT_LIMIT}),
|
||||
lambda g: g.with_config(
|
||||
{"recursion_limit": 1}, recursion_limit=EXPLICIT_DEFAULT_LIMIT
|
||||
),
|
||||
lambda g: g.with_config(recursion_limit=EXPLICIT_DEFAULT_LIMIT).with_config(
|
||||
tags=["t"]
|
||||
),
|
||||
],
|
||||
ids=["kwarg", "config", "kwarg_over_config", "kept_by_later_with_config"],
|
||||
)
|
||||
def test_with_config_applies_recursion_limit_equal_to_default(
|
||||
rebind: Callable[[Pregel], Pregel],
|
||||
) -> None:
|
||||
builder = StateGraph(dict)
|
||||
builder.add_node("node", lambda state: state)
|
||||
builder.add_edge("__start__", "node")
|
||||
graph = builder.compile().with_config(recursion_limit=EXPLICIT_DEFAULT_LIMIT + 1)
|
||||
assert rebind(graph).config["recursion_limit"] == EXPLICIT_DEFAULT_LIMIT
|
||||
@@ -819,6 +819,50 @@ async def test_astream():
|
||||
]
|
||||
|
||||
|
||||
_CALLER_CONFIG: RunnableConfig = {
|
||||
"configurable": {"thread_id": "thread_1", "checkpoint_ns": "outer:123"}
|
||||
}
|
||||
_SUBGRAPH_PARTS = [
|
||||
StreamPart(event="values|sub:abc", data={"chunk": "inner"}),
|
||||
StreamPart(event="values", data={"chunk": "root"}),
|
||||
]
|
||||
_CALLER_PREFIXED = [
|
||||
(("outer:123", "sub:abc"), {"chunk": "inner"}),
|
||||
(("outer:123",), {"chunk": "root"}),
|
||||
]
|
||||
|
||||
|
||||
def test_stream_subgraphs_keeps_caller_namespace():
|
||||
mock_sync_client = MagicMock()
|
||||
mock_sync_client.runs.stream.return_value = _SUBGRAPH_PARTS
|
||||
remote_pregel = RemoteGraph("test_graph_id", sync_client=mock_sync_client)
|
||||
|
||||
stream_parts = list(
|
||||
remote_pregel.stream(
|
||||
{"input": "data"}, _CALLER_CONFIG, stream_mode="values", subgraphs=True
|
||||
)
|
||||
)
|
||||
|
||||
assert stream_parts == _CALLER_PREFIXED
|
||||
|
||||
|
||||
async def test_astream_subgraphs_keeps_caller_namespace():
|
||||
mock_async_client = MagicMock()
|
||||
async_iter = MagicMock()
|
||||
async_iter.__aiter__.return_value = _SUBGRAPH_PARTS
|
||||
mock_async_client.runs.stream.return_value = async_iter
|
||||
remote_pregel = RemoteGraph("test_graph_id", client=mock_async_client)
|
||||
|
||||
stream_parts = [
|
||||
part
|
||||
async for part in remote_pregel.astream(
|
||||
{"input": "data"}, _CALLER_CONFIG, stream_mode="values", subgraphs=True
|
||||
)
|
||||
]
|
||||
|
||||
assert stream_parts == _CALLER_PREFIXED
|
||||
|
||||
|
||||
def test_invoke():
|
||||
# set up test
|
||||
mock_sync_client = MagicMock()
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
import copy
|
||||
import functools
|
||||
import pickle
|
||||
import sys
|
||||
import uuid
|
||||
from collections.abc import Callable
|
||||
@@ -25,6 +27,7 @@ from pydantic import BaseModel, Field
|
||||
from typing_extensions import NotRequired, Required, TypedDict
|
||||
|
||||
from langgraph._internal._config import (
|
||||
DEFAULT_RECURSION_LIMIT,
|
||||
_is_not_empty,
|
||||
_merge_callbacks,
|
||||
ensure_config,
|
||||
@@ -620,6 +623,30 @@ def test_merge_configs_merges_metadata_lc_versions() -> None:
|
||||
assert merged["metadata"]["lc_agent_name"] == "agent"
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"rebuild",
|
||||
[
|
||||
lambda config: config,
|
||||
copy.deepcopy,
|
||||
lambda config: pickle.loads(pickle.dumps(config)),
|
||||
],
|
||||
ids=["as_is", "deepcopy", "pickle"],
|
||||
)
|
||||
def test_merge_configs_skips_the_injected_default_recursion_limit(
|
||||
rebuild: Callable[[RunnableConfig], RunnableConfig],
|
||||
) -> None:
|
||||
merged = merge_configs({"recursion_limit": 50}, rebuild(ensure_config()))
|
||||
assert merged["recursion_limit"] == 50
|
||||
|
||||
|
||||
def test_merge_configs_applies_an_explicit_recursion_limit_equal_to_default() -> None:
|
||||
explicit_default = int(DEFAULT_RECURSION_LIMIT)
|
||||
merged = merge_configs(
|
||||
{"recursion_limit": 50}, {"recursion_limit": explicit_default}
|
||||
)
|
||||
assert merged["recursion_limit"] == explicit_default
|
||||
|
||||
|
||||
def test_ensure_config_merges_metadata_lc_versions() -> None:
|
||||
a = {
|
||||
"metadata": {
|
||||
|
||||
Generated
+1
-1
@@ -1451,7 +1451,7 @@ wheels = [
|
||||
|
||||
[[package]]
|
||||
name = "langgraph"
|
||||
version = "1.2.16"
|
||||
version = "1.2.17"
|
||||
source = { editable = "." }
|
||||
dependencies = [
|
||||
{ name = "langchain-core" },
|
||||
|
||||
Generated
+1
-1
@@ -286,7 +286,7 @@ wheels = [
|
||||
|
||||
[[package]]
|
||||
name = "langgraph"
|
||||
version = "1.2.16"
|
||||
version = "1.2.17"
|
||||
source = { editable = "../langgraph" }
|
||||
dependencies = [
|
||||
{ name = "langchain-core" },
|
||||
|
||||
Generated
+1
-1
@@ -301,7 +301,7 @@ wheels = [
|
||||
|
||||
[[package]]
|
||||
name = "langgraph"
|
||||
version = "1.2.16"
|
||||
version = "1.2.17"
|
||||
source = { editable = "../langgraph" }
|
||||
dependencies = [
|
||||
{ name = "langchain-core" },
|
||||
|
||||
Reference in new issue
Block a user