Merge branch 'main' into sr/better-interrupts

This commit is contained in:
Sydney Runkle
2025-04-22 13:48:03 -07:00
committed by GitHub
64 changed files with 408 additions and 460 deletions
+1 -1
View File
@@ -78,7 +78,7 @@ test_watch_all:
PYTHON_FILES=.
MYPY_CACHE=.mypy_cache
lint format: PYTHON_FILES=.
lint_diff format_diff: PYTHON_FILES=$(shell git diff --name-only --relative --diff-filter=d main . | grep -E '\.py$$|\.ipynb$$')
lint_diff format_diff: PYTHON_FILES=$(shell git diff --name-only --relative --diff-filter=d main . | grep -E r'\.py$$|\.ipynb$$')
lint_package: PYTHON_FILES=langgraph
lint_tests: PYTHON_FILES=tests
lint_tests: MYPY_CACHE=.mypy_cache_test
+2 -1
View File
@@ -1,7 +1,8 @@
import operator
from collections.abc import Sequence
from functools import partial
from random import choice
from typing import Annotated, Optional, Sequence
from typing import Annotated, Optional
from pydantic import BaseModel, Field, field_validator
+2 -1
View File
@@ -1,7 +1,8 @@
import operator
from collections.abc import Sequence
from functools import partial
from random import choice
from typing import Annotated, Optional, Sequence
from typing import Annotated, Optional
from typing_extensions import TypedDict
+2 -1
View File
@@ -1,8 +1,9 @@
import operator
from collections.abc import Sequence
from dataclasses import dataclass, field
from functools import partial
from random import choice
from typing import Annotated, Optional, Sequence
from typing import Annotated, Optional
from langgraph.constants import END, START
from langgraph.graph.state import StateGraph
+2 -2
View File
@@ -1,6 +1,6 @@
import functools
import warnings
from typing import Any, Callable, Type, TypeVar, Union, cast
from typing import Any, Callable, TypeVar, Union, cast
class LangGraphDeprecationWarning(DeprecationWarning):
@@ -8,7 +8,7 @@ class LangGraphDeprecationWarning(DeprecationWarning):
F = TypeVar("F", bound=Callable[..., Any])
C = TypeVar("C", bound=Type[Any])
C = TypeVar("C", bound=type[Any])
def deprecated(
@@ -1,4 +1,5 @@
from typing import Any, Generic, Sequence, Type
from collections.abc import Sequence
from typing import Any, Generic
from typing_extensions import Self
@@ -21,12 +22,12 @@ class AnyValue(Generic[Value], BaseChannel[Value, Value, Value]):
return isinstance(value, AnyValue)
@property
def ValueType(self) -> Type[Value]:
def ValueType(self) -> type[Value]:
"""The type of the value stored in the channel."""
return self.typ
@property
def UpdateType(self) -> Type[Value]:
def UpdateType(self) -> type[Value]:
"""The type of the update received by the channel."""
return self.typ
+2 -1
View File
@@ -1,5 +1,6 @@
from abc import ABC, abstractmethod
from typing import Any, Generic, Sequence, TypeVar
from collections.abc import Sequence
from typing import Any, Generic, TypeVar
from typing_extensions import Self
+5 -4
View File
@@ -1,5 +1,6 @@
import collections.abc
from typing import Callable, Generic, Sequence, Type
from collections.abc import Sequence
from typing import Callable, Generic
from typing_extensions import NotRequired, Required, Self
@@ -31,7 +32,7 @@ class BinaryOperatorAggregate(Generic[Value], BaseChannel[Value, Value, Value]):
__slots__ = ("value", "operator")
def __init__(self, typ: Type[Value], operator: Callable[[Value, Value], Value]):
def __init__(self, typ: type[Value], operator: Callable[[Value, Value], Value]):
super().__init__(typ)
self.operator = operator
# special forms from typing or collections.abc are not instantiable
@@ -57,12 +58,12 @@ class BinaryOperatorAggregate(Generic[Value], BaseChannel[Value, Value, Value]):
)
@property
def ValueType(self) -> Type[Value]:
def ValueType(self) -> type[Value]:
"""The type of the value stored in the channel."""
return self.typ
@property
def UpdateType(self) -> Type[Value]:
def UpdateType(self) -> type[Value]:
"""The type of the update received by the channel."""
return self.typ
@@ -1,5 +1,5 @@
from collections.abc import Set
from typing import Any, Generic, NamedTuple, Optional, Sequence, Type, Union
from collections.abc import Sequence, Set
from typing import Any, Generic, NamedTuple, Optional, Union
from typing_extensions import Self
@@ -13,7 +13,7 @@ class WaitForNames(NamedTuple):
class DynamicBarrierValue(
Generic[Value], BaseChannel[Value, Union[Value, WaitForNames], set[Value]]
Generic[Value], BaseChannel[Value, Union[Value, WaitForNames], Set[Value]]
):
"""A channel that switches between two states
@@ -29,7 +29,7 @@ class DynamicBarrierValue(
names: Optional[Set[Value]]
seen: set[Value]
def __init__(self, typ: Type[Value]) -> None:
def __init__(self, typ: type[Value]) -> None:
super().__init__(typ)
self.names = None
self.seen = set()
@@ -38,12 +38,12 @@ class DynamicBarrierValue(
return isinstance(value, DynamicBarrierValue) and value.names == self.names
@property
def ValueType(self) -> Type[Value]:
def ValueType(self) -> type[Value]:
"""The type of the value stored in the channel."""
return self.typ
@property
def UpdateType(self) -> Type[Value]:
def UpdateType(self) -> type[Value]:
"""The type of the update received by the channel."""
return self.typ
@@ -1,4 +1,5 @@
from typing import Any, Generic, Sequence, Type
from collections.abc import Sequence
from typing import Any, Generic
from typing_extensions import Self
@@ -21,12 +22,12 @@ class EphemeralValue(Generic[Value], BaseChannel[Value, Value, Value]):
return isinstance(value, EphemeralValue) and value.guard == self.guard
@property
def ValueType(self) -> Type[Value]:
def ValueType(self) -> type[Value]:
"""The type of the value stored in the channel."""
return self.typ
@property
def UpdateType(self) -> Type[Value]:
def UpdateType(self) -> type[Value]:
"""The type of the update received by the channel."""
return self.typ
@@ -1,4 +1,5 @@
from typing import Any, Generic, Sequence, Type
from collections.abc import Sequence
from typing import Any, Generic
from typing_extensions import Self
@@ -25,12 +26,12 @@ class LastValue(Generic[Value], BaseChannel[Value, Value, Value]):
return isinstance(value, LastValue)
@property
def ValueType(self) -> Type[Value]:
def ValueType(self) -> type[Value]:
"""The type of the value stored in the channel."""
return self.typ
@property
def UpdateType(self) -> Type[Value]:
def UpdateType(self) -> type[Value]:
"""The type of the update received by the channel."""
return self.typ
@@ -1,4 +1,5 @@
from typing import Generic, Sequence, Type
from collections.abc import Sequence
from typing import Generic
from typing_extensions import Self
@@ -15,7 +16,7 @@ class NamedBarrierValue(Generic[Value], BaseChannel[Value, Value, set[Value]]):
names: set[Value]
seen: set[Value]
def __init__(self, typ: Type[Value], names: set[Value]) -> None:
def __init__(self, typ: type[Value], names: set[Value]) -> None:
super().__init__(typ)
self.names = names
self.seen: set[str] = set()
@@ -24,12 +25,12 @@ class NamedBarrierValue(Generic[Value], BaseChannel[Value, Value, set[Value]]):
return isinstance(value, NamedBarrierValue) and value.names == self.names
@property
def ValueType(self) -> Type[Value]:
def ValueType(self) -> type[Value]:
"""The type of the value stored in the channel."""
return self.typ
@property
def UpdateType(self) -> Type[Value]:
def UpdateType(self) -> type[Value]:
"""The type of the update received by the channel."""
return self.typ
+3 -2
View File
@@ -1,4 +1,5 @@
from typing import Any, Generic, Iterator, Sequence, Type, Union
from collections.abc import Iterator, Sequence
from typing import Any, Generic, Union
from typing_extensions import Self
@@ -28,7 +29,7 @@ class Topic(
__slots__ = ("values", "accumulate")
def __init__(self, typ: Type[Value], accumulate: bool = False) -> None:
def __init__(self, typ: type[Value], accumulate: bool = False) -> None:
super().__init__(typ)
# attrs
self.accumulate = accumulate
@@ -1,4 +1,5 @@
from typing import Generic, Sequence, Type
from collections.abc import Sequence
from typing import Generic
from typing_extensions import Self
@@ -12,7 +13,7 @@ class UntrackedValue(Generic[Value], BaseChannel[Value, Value, Value]):
__slots__ = ("value", "guard")
def __init__(self, typ: Type[Value], guard: bool = True) -> None:
def __init__(self, typ: type[Value], guard: bool = True) -> None:
super().__init__(typ)
self.guard = guard
self.value = MISSING
@@ -21,12 +22,12 @@ class UntrackedValue(Generic[Value], BaseChannel[Value, Value, Value]):
return isinstance(value, UntrackedValue) and value.guard == self.guard
@property
def ValueType(self) -> Type[Value]:
def ValueType(self) -> type[Value]:
"""The type of the value stored in the channel."""
return self.typ
@property
def UpdateType(self) -> Type[Value]:
def UpdateType(self) -> type[Value]:
"""The type of the update received by the channel."""
return self.typ
+2 -1
View File
@@ -1,6 +1,7 @@
import sys
from collections.abc import Mapping
from types import MappingProxyType
from typing import Any, Literal, Mapping, cast
from typing import Any, Literal, cast
from langgraph.types import Interrupt, Send # noqa: F401
+2 -1
View File
@@ -1,5 +1,6 @@
from collections.abc import Sequence
from enum import Enum
from typing import Any, Sequence
from typing import Any
from langgraph.checkpoint.base import EmptyChannelError # noqa: F401
from langgraph.types import Command, Interrupt
+1 -2
View File
@@ -2,14 +2,13 @@ import asyncio
import concurrent.futures
import functools
import inspect
from collections.abc import Awaitable, Sequence
from dataclasses import dataclass
from typing import (
Any,
Awaitable,
Callable,
Generic,
Optional,
Sequence,
TypeVar,
Union,
get_args,
+3 -6
View File
@@ -1,3 +1,4 @@
from collections.abc import Awaitable, Hashable, Sequence
from inspect import (
isfunction,
ismethod,
@@ -7,14 +8,10 @@ from itertools import zip_longest
from types import FunctionType
from typing import (
Any,
Awaitable,
Callable,
Hashable,
Literal,
NamedTuple,
Optional,
Sequence,
Type,
Union,
cast,
get_args,
@@ -48,7 +45,7 @@ def _get_branch_path_input_schema(
Callable[..., Awaitable[Union[Hashable, list[Hashable]]]],
Runnable[Any, Union[Hashable, list[Hashable]]],
],
) -> Optional[Type[Any]]:
) -> Optional[type[Any]]:
input = None
# detect input schema annotation in the branch callable
try:
@@ -91,7 +88,7 @@ class Branch(NamedTuple):
path: Runnable[Any, Union[Hashable, list[Hashable]]]
ends: Optional[dict[Hashable, str]]
then: Optional[str] = None
input_schema: Optional[Type[Any]] = None
input_schema: Optional[type[Any]] = None
@classmethod
def from_path(
+1 -3
View File
@@ -1,13 +1,11 @@
import logging
from collections import defaultdict
from collections.abc import Awaitable, Hashable, Sequence
from typing import (
Any,
Awaitable,
Callable,
Hashable,
NamedTuple,
Optional,
Sequence,
Union,
cast,
overload,
+1 -1
View File
@@ -1,5 +1,6 @@
import uuid
import warnings
from collections.abc import Sequence
from functools import partial
from typing import (
Annotated,
@@ -7,7 +8,6 @@ from typing import (
Callable,
Literal,
Optional,
Sequence,
Union,
cast,
)
@@ -3,10 +3,10 @@ import logging
import weakref
from inspect import isclass
from typing import (
Annotated,
Any,
Callable,
Optional,
Type,
Union,
get_args,
get_origin,
@@ -15,14 +15,13 @@ from typing import (
from pydantic import BaseModel
from pydantic.v1 import BaseModel as BaseModelV1
from typing_extensions import Annotated
__all__ = ["SchemaCoercionMapper"]
logger = logging.getLogger(__name__)
_cache: weakref.WeakKeyDictionary[Type[Any], dict[int, "SchemaCoercionMapper"]] = (
_cache: weakref.WeakKeyDictionary[type[Any], dict[int, "SchemaCoercionMapper"]] = (
weakref.WeakKeyDictionary()
)
@@ -32,7 +31,7 @@ class SchemaCoercionMapper:
def __new__(
cls,
schema: Type[Any],
schema: type[Any],
type_hints: Optional[dict[str, Any]] = None,
*,
max_depth: int = 12,
@@ -46,7 +45,7 @@ class SchemaCoercionMapper:
def __init__(
self,
schema: Type[Any],
schema: type[Any],
type_hints: Optional[dict[str, Any]] = None,
*,
max_depth: int = 12,
@@ -187,7 +186,7 @@ class SchemaCoercionMapper:
def dict_coercer(v: Any, d: Any) -> Any:
if not isinstance(v, dict):
if throw:
raise TypeError("Expected dict, got %s" % type(v))
raise TypeError(f"Expected dict, got {type(v)}")
return v
return dict_coercer
@@ -197,7 +196,7 @@ class SchemaCoercionMapper:
def dict_coercer(v: Any, d: Any) -> Any:
if not isinstance(v, dict):
if throw:
raise TypeError("Expected dict, got %s" % type(v))
raise TypeError(f"Expected dict, got {type(v)}")
return v
return {k_sub(k, d - 1): v_sub(val, d - 1) for k, val in v.items()}
+22 -25
View File
@@ -3,19 +3,16 @@ import logging
import typing
import warnings
from collections import defaultdict
from collections.abc import Awaitable, Hashable, Sequence
from functools import partial
from inspect import isclass, isfunction, ismethod, signature
from types import FunctionType
from typing import (
Any,
Awaitable,
Callable,
Hashable,
Literal,
NamedTuple,
Optional,
Sequence,
Type,
Union,
cast,
get_args,
@@ -84,7 +81,7 @@ from langgraph.utils.runnable import RunnableLike, coerce_to_runnable
logger = logging.getLogger(__name__)
def _warn_invalid_state_schema(schema: Union[Type[Any], Any]) -> None:
def _warn_invalid_state_schema(schema: Union[type[Any], Any]) -> None:
if isinstance(schema, type):
return
if typing.get_args(schema):
@@ -108,7 +105,7 @@ def _get_node_name(node: RunnableLike) -> str:
class StateNodeSpec(NamedTuple):
runnable: Runnable
metadata: Optional[dict[str, Any]]
input: Type[Any]
input: type[Any]
retry_policy: Optional[Union[RetryPolicy, Sequence[RetryPolicy]]]
ends: Optional[Union[tuple[str, ...], dict[str, str]]] = EMPTY_SEQ
@@ -166,15 +163,15 @@ class StateGraph(Graph):
nodes: dict[str, StateNodeSpec] # type: ignore[assignment]
channels: dict[str, BaseChannel]
managed: dict[str, ManagedValueSpec]
schemas: dict[Type[Any], dict[str, Union[BaseChannel, ManagedValueSpec]]]
schemas: dict[type[Any], dict[str, Union[BaseChannel, ManagedValueSpec]]]
def __init__(
self,
state_schema: Optional[Type[Any]] = None,
config_schema: Optional[Type[Any]] = None,
state_schema: Optional[type[Any]] = None,
config_schema: Optional[type[Any]] = None,
*,
input: Optional[Type[Any]] = None,
output: Optional[Type[Any]] = None,
input: Optional[type[Any]] = None,
output: Optional[type[Any]] = None,
) -> None:
super().__init__()
if state_schema is None:
@@ -195,7 +192,7 @@ class StateGraph(Graph):
self.schemas = {}
self.channels = {}
self.managed = {}
self.type_hints: dict[Type[Any], dict[str, Any]] = {}
self.type_hints: dict[type[Any], dict[str, Any]] = {}
self.schema = state_schema
self.input = input
self.output = output
@@ -211,7 +208,7 @@ class StateGraph(Graph):
(start, end) for starts, end in self.waiting_edges for start in starts
}
def _add_schema(self, schema: Type[Any], /, allow_managed: bool = True) -> None:
def _add_schema(self, schema: type[Any], /, allow_managed: bool = True) -> None:
if schema not in self.schemas:
_warn_invalid_state_schema(schema)
channels, managed, type_hints = _get_channels(schema)
@@ -250,7 +247,7 @@ class StateGraph(Graph):
node: RunnableLike,
*,
metadata: Optional[dict[str, Any]] = None,
input: Optional[Type[Any]] = None,
input: Optional[type[Any]] = None,
retry: Optional[Union[RetryPolicy, Sequence[RetryPolicy]]] = None,
destinations: Optional[Union[dict[str, str], tuple[str, ...]]] = None,
) -> Self:
@@ -275,7 +272,7 @@ class StateGraph(Graph):
action: RunnableLike,
*,
metadata: Optional[dict[str, Any]] = None,
input: Optional[Type[Any]] = None,
input: Optional[type[Any]] = None,
retry: Optional[Union[RetryPolicy, Sequence[RetryPolicy]]] = None,
destinations: Optional[Union[dict[str, str], tuple[str, ...]]] = None,
) -> Self:
@@ -299,7 +296,7 @@ class StateGraph(Graph):
action: Optional[RunnableLike] = None,
*,
metadata: Optional[dict[str, Any]] = None,
input: Optional[Type[Any]] = None,
input: Optional[type[Any]] = None,
retry: Optional[Union[RetryPolicy, Sequence[RetryPolicy]]] = None,
destinations: Optional[Union[dict[str, str], tuple[str, ...]]] = None,
) -> Self:
@@ -686,12 +683,12 @@ class StateGraph(Graph):
class CompiledStateGraph(CompiledGraph):
builder: StateGraph
schema_to_mapper: dict[Type[Any], Optional[Callable[[Any], Any]]]
schema_to_mapper: dict[type[Any], Optional[Callable[[Any], Any]]]
def __init__(
self,
*,
schema_to_mapper: dict[Type[Any], Optional[Callable[[Any], Any]]],
schema_to_mapper: dict[type[Any], Optional[Callable[[Any], Any]]],
**kwargs: Any,
) -> None:
super().__init__(**kwargs)
@@ -1022,7 +1019,7 @@ class CompiledStateGraph(CompiledGraph):
def _pick_mapper(
state_keys: Sequence[str], schema: Type[Any], type_hints: Optional[dict[str, Any]]
state_keys: Sequence[str], schema: type[Any], type_hints: Optional[dict[str, Any]]
) -> Optional[Callable[[Any], Any]]:
if state_keys == ["__root__"]:
return None
@@ -1034,7 +1031,7 @@ def _pick_mapper(
return partial(_coerce_state, schema)
def _coerce_state(schema: Type[Any], input: dict[str, Any]) -> dict[str, Any]:
def _coerce_state(schema: type[Any], input: dict[str, Any]) -> dict[str, Any]:
return schema(**input)
@@ -1103,7 +1100,7 @@ def _get_root(input: Any) -> Optional[Sequence[tuple[str, Any]]]:
def _get_channels(
schema: Type[dict],
schema: type[dict],
) -> tuple[dict[str, BaseChannel], dict[str, ManagedValueSpec], dict[str, Any]]:
if not hasattr(schema, "__annotations__"):
return (
@@ -1157,7 +1154,7 @@ def _get_channel(
return fallback
def _is_field_channel(typ: Type[Any]) -> Optional[BaseChannel]:
def _is_field_channel(typ: type[Any]) -> Optional[BaseChannel]:
if hasattr(typ, "__metadata__"):
meta = typ.__metadata__
if len(meta) >= 1 and isinstance(meta[-1], BaseChannel):
@@ -1167,7 +1164,7 @@ def _is_field_channel(typ: Type[Any]) -> Optional[BaseChannel]:
return None
def _is_field_binop(typ: Type[Any]) -> Optional[BinaryOperatorAggregate]:
def _is_field_binop(typ: type[Any]) -> Optional[BinaryOperatorAggregate]:
if hasattr(typ, "__metadata__"):
meta = typ.__metadata__
if len(meta) >= 1 and callable(meta[-1]):
@@ -1188,7 +1185,7 @@ def _is_field_binop(typ: Type[Any]) -> Optional[BinaryOperatorAggregate]:
return None
def _is_field_managed_value(name: str, typ: Type[Any]) -> Optional[ManagedValueSpec]:
def _is_field_managed_value(name: str, typ: type[Any]) -> Optional[ManagedValueSpec]:
if hasattr(typ, "__metadata__"):
meta = typ.__metadata__
if len(meta) >= 1:
@@ -1206,7 +1203,7 @@ def _is_field_managed_value(name: str, typ: Type[Any]) -> Optional[ManagedValueS
def _get_schema(
typ: Type,
typ: type,
schemas: dict,
channels: dict,
name: str,
+5 -8
View File
@@ -1,14 +1,11 @@
from abc import ABC, abstractmethod
from collections.abc import AsyncIterator, Iterator, Sequence
from contextlib import asynccontextmanager, contextmanager
from inspect import isclass
from typing import (
Any,
AsyncIterator,
Generic,
Iterator,
NamedTuple,
Sequence,
Type,
TypeVar,
Union,
)
@@ -66,11 +63,11 @@ class WritableManagedValue(Generic[V, U], ManagedValue[V], ABC):
class ConfiguredManagedValue(NamedTuple):
cls: Type[ManagedValue]
cls: type[ManagedValue]
kwargs: dict[str, Any]
ManagedValueSpec = Union[Type[ManagedValue], ConfiguredManagedValue]
ManagedValueSpec = Union[type[ManagedValue], ConfiguredManagedValue]
def is_managed_value(value: Any) -> TypeGuard[ManagedValueSpec]:
@@ -79,7 +76,7 @@ def is_managed_value(value: Any) -> TypeGuard[ManagedValueSpec]:
)
def is_readonly_managed_value(value: Any) -> TypeGuard[Type[ManagedValue]]:
def is_readonly_managed_value(value: Any) -> TypeGuard[type[ManagedValue]]:
return (
isclass(value)
and issubclass(value, ManagedValue)
@@ -90,7 +87,7 @@ def is_readonly_managed_value(value: Any) -> TypeGuard[Type[ManagedValue]]:
)
def is_writable_managed_value(value: Any) -> TypeGuard[Type[WritableManagedValue]]:
def is_writable_managed_value(value: Any) -> TypeGuard[type[WritableManagedValue]]:
return (isclass(value) and issubclass(value, WritableManagedValue)) or (
isinstance(value, ConfiguredManagedValue)
and issubclass(value.cls, WritableManagedValue)
+17 -14
View File
@@ -1,15 +1,16 @@
from contextlib import asynccontextmanager, contextmanager
from collections.abc import AsyncIterator, Iterator
from contextlib import (
AbstractAsyncContextManager,
AbstractContextManager,
asynccontextmanager,
contextmanager,
)
from inspect import signature
from typing import (
Any,
AsyncContextManager,
AsyncIterator,
Callable,
ContextManager,
Generic,
Iterator,
Optional,
Type,
Union,
)
@@ -28,15 +29,15 @@ class Context(ManagedValue[V], Generic[V]):
def of(
ctx: Union[
None,
Callable[..., ContextManager[V]],
Type[ContextManager[V]],
Callable[..., AsyncContextManager[V]],
Type[AsyncContextManager[V]],
Callable[..., AbstractContextManager[V]],
type[AbstractContextManager[V]],
Callable[..., AbstractAsyncContextManager[V]],
type[AbstractAsyncContextManager[V]],
] = None,
actx: Optional[
Union[
Callable[..., AsyncContextManager[V]],
Type[AsyncContextManager[V]],
Callable[..., AbstractAsyncContextManager[V]],
type[AbstractAsyncContextManager[V]],
]
] = None,
) -> ConfiguredManagedValue:
@@ -98,8 +99,10 @@ class Context(ManagedValue[V], Generic[V]):
self,
loop: LoopProtocol,
*,
ctx: Union[None, Type[ContextManager[V]], Type[AsyncContextManager[V]]] = None,
actx: Optional[Type[AsyncContextManager[V]]] = None,
ctx: Union[
None, type[AbstractContextManager[V]], type[AbstractAsyncContextManager[V]]
] = None,
actx: Optional[type[AbstractAsyncContextManager[V]]] = None,
) -> None:
self.ctx = ctx
self.actx = actx
@@ -1,12 +1,9 @@
import collections.abc
from collections.abc import AsyncIterator, Iterator, Sequence
from contextlib import asynccontextmanager, contextmanager
from typing import (
Any,
AsyncIterator,
Iterator,
Optional,
Sequence,
Type,
)
from typing_extensions import NotRequired, Required, Self
@@ -71,7 +68,7 @@ class SharedValue(WritableManagedValue[Value, Update]):
yield value
def __init__(
self, loop: LoopProtocol, *, typ: Type[Any], scope: str, key: str
self, loop: LoopProtocol, *, typ: type[Any], scope: str, key: str
) -> None:
super().__init__(loop)
if typ := _strip_extras(typ):
+122 -140
View File
@@ -6,17 +6,11 @@ import concurrent.futures
import queue
import weakref
from collections import defaultdict, deque
from collections.abc import AsyncIterator, Iterator, Mapping, Sequence
from functools import partial
from typing import (
Any,
AsyncIterator,
Callable,
Dict,
Iterator,
Mapping,
Optional,
Sequence,
Type,
Union,
cast,
get_type_hints,
@@ -143,8 +137,8 @@ class Channel:
cls,
channels: str,
*,
key: Optional[str] = None,
tags: Optional[list[str]] = None,
key: str | None = None,
tags: list[str] | None = None,
) -> PregelNode: ...
@overload
@@ -154,16 +148,16 @@ class Channel:
channels: Sequence[str],
*,
key: None = None,
tags: Optional[list[str]] = None,
tags: list[str] | None = None,
) -> PregelNode: ...
@classmethod
def subscribe_to(
cls,
channels: Union[str, Sequence[str]],
channels: str | Sequence[str],
*,
key: Optional[str] = None,
tags: Optional[list[str]] = None,
key: str | None = None,
tags: list[str] | None = None,
) -> PregelNode:
"""Runs process.invoke() each time channels are updated,
with a dict of the channel values as input."""
@@ -469,7 +463,7 @@ class Pregel(PregelProtocol):
nodes: dict[str, PregelNode]
channels: dict[str, Union[BaseChannel, ManagedValueSpec]]
channels: dict[str, BaseChannel | ManagedValueSpec]
stream_mode: StreamMode = "values"
"""Mode to stream output, defaults to 'values'."""
@@ -478,18 +472,18 @@ class Pregel(PregelProtocol):
"""Whether to force emitting stream events eagerly, automatically turned on
for stream_mode "messages" and "custom"."""
output_channels: Union[str, Sequence[str]]
output_channels: str | Sequence[str]
stream_channels: Optional[Union[str, Sequence[str]]] = None
stream_channels: str | Sequence[str] | None = None
"""Channels to stream, defaults to all channels not in reserved channels"""
interrupt_after_nodes: Union[All, Sequence[str]]
interrupt_after_nodes: All | Sequence[str]
interrupt_before_nodes: Union[All, Sequence[str]]
interrupt_before_nodes: All | Sequence[str]
input_channels: Union[str, Sequence[str]]
input_channels: str | Sequence[str]
step_timeout: Optional[float] = None
step_timeout: float | None = None
"""Maximum time to wait for a step to complete, in seconds. Defaults to None."""
debug: bool
@@ -498,44 +492,44 @@ class Pregel(PregelProtocol):
checkpointer: Checkpointer = None
"""Checkpointer used to save and load graph state. Defaults to None."""
store: Optional[BaseStore] = None
store: BaseStore | None = None
"""Memory store to use for SharedValues. Defaults to None."""
retry_policy: Optional[Sequence[RetryPolicy]] = None
retry_policy: Sequence[RetryPolicy] | None = None
"""Retry policies to use when running tasks. Set to None to disable."""
config_type: Optional[Type[Any]] = None
config_type: type[Any] | None = None
input_model: Optional[Type[BaseModel]] = None
input_model: type[BaseModel] | None = None
config: Optional[RunnableConfig] = None
config: RunnableConfig | None = None
name: str = "LangGraph"
trigger_to_nodes: Optional[Mapping[str, Sequence[str]]] = None
trigger_to_nodes: Mapping[str, Sequence[str]] | None = None
def __init__(
self,
*,
nodes: dict[str, PregelNode],
channels: Optional[dict[str, Union[BaseChannel, ManagedValueSpec]]],
channels: dict[str, BaseChannel | ManagedValueSpec] | None,
auto_validate: bool = True,
stream_mode: StreamMode = "values",
stream_eager: bool = False,
output_channels: Union[str, Sequence[str]],
stream_channels: Optional[Union[str, Sequence[str]]] = None,
interrupt_after_nodes: Union[All, Sequence[str]] = (),
interrupt_before_nodes: Union[All, Sequence[str]] = (),
input_channels: Union[str, Sequence[str]],
step_timeout: Optional[float] = None,
debug: Optional[bool] = None,
checkpointer: Optional[BaseCheckpointSaver] = None,
store: Optional[BaseStore] = None,
retry_policy: Optional[Union[RetryPolicy, Sequence[RetryPolicy]]] = None,
config_type: Optional[Type[Any]] = None,
input_model: Optional[Type[BaseModel]] = None,
config: Optional[RunnableConfig] = None,
trigger_to_nodes: Optional[Mapping[str, Sequence[str]]] = None,
output_channels: str | Sequence[str],
stream_channels: str | Sequence[str] | None = None,
interrupt_after_nodes: All | Sequence[str] = (),
interrupt_before_nodes: All | Sequence[str] = (),
input_channels: str | Sequence[str],
step_timeout: float | None = None,
debug: bool | None = None,
checkpointer: BaseCheckpointSaver | None = None,
store: BaseStore | None = None,
retry_policy: RetryPolicy | Sequence[RetryPolicy] | None = None,
config_type: type[Any] | None = None,
input_model: type[BaseModel] | None = None,
config: RunnableConfig | None = None,
trigger_to_nodes: Mapping[str, Sequence[str]] | None = None,
name: str = "LangGraph",
) -> None:
self.nodes = nodes
@@ -564,10 +558,7 @@ class Pregel(PregelProtocol):
self.validate()
def get_graph(
self,
config: Optional[RunnableConfig] = None,
*,
xray: Union[int, bool] = False,
self, config: RunnableConfig | None = None, *, xray: int | bool = False
) -> Graph:
"""Returns a drawable representation of the computation graph."""
# gather subgraphs
@@ -595,10 +586,7 @@ class Pregel(PregelProtocol):
)
async def aget_graph(
self,
config: Optional[RunnableConfig] = None,
*,
xray: Union[int, bool] = False,
self, config: RunnableConfig | None = None, *, xray: int | bool = False
) -> Graph:
"""Returns a drawable representation of the computation graph."""
@@ -646,13 +634,11 @@ class Pregel(PregelProtocol):
"image/png": self.get_graph().draw_mermaid_png(),
}
def copy(self, update: Optional[dict[str, Any]] = None) -> Self:
def copy(self, update: dict[str, Any] | None = None) -> Self:
attrs = {**self.__dict__, **(update or {})}
return self.__class__(**attrs)
def with_config(
self, config: Optional[RunnableConfig] = None, **kwargs: Any
) -> Self:
def with_config(self, config: RunnableConfig | None = None, **kwargs: Any) -> Self:
return self.copy(
{"config": merge_configs(self.config, config, cast(RunnableConfig, kwargs))}
)
@@ -707,9 +693,7 @@ class Pregel(PregelProtocol):
]
]
def config_schema(
self, *, include: Optional[Sequence[str]] = None
) -> Type[BaseModel]:
def config_schema(self, *, include: Sequence[str] | None = None) -> type[BaseModel]:
# If the config type is not set explicitly, we will try to infer it.
# If the config type is provided, but isn't directly supported by pydantic
# (e.g., vanilla python class), we will also delegate to the parent class,
@@ -729,8 +713,8 @@ class Pregel(PregelProtocol):
return create_model(self.get_name("Config"), field_definitions=fields)
def get_config_jsonschema(
self, *, include: Optional[Sequence[str]] = None
) -> Dict[str, Any]:
self, *, include: Sequence[str] | None = None
) -> dict[str, Any]:
schema = self.config_schema(include=include)
if hasattr(schema, "model_json_schema"):
return schema.model_json_schema()
@@ -744,9 +728,7 @@ class Pregel(PregelProtocol):
if isinstance(channel, BaseChannel):
return channel.UpdateType
def get_input_schema(
self, config: Optional[RunnableConfig] = None
) -> Type[BaseModel]:
def get_input_schema(self, config: RunnableConfig | None = None) -> type[BaseModel]:
if self.input_model is not None:
return self.input_model
config = merge_configs(self.config, config)
@@ -763,8 +745,8 @@ class Pregel(PregelProtocol):
)
def get_input_jsonschema(
self, config: Optional[RunnableConfig] = None
) -> Dict[str, Any]:
self, config: RunnableConfig | None = None
) -> dict[str, Any]:
schema = self.get_input_schema(config)
if hasattr(schema, "model_json_schema"):
return schema.model_json_schema()
@@ -779,8 +761,8 @@ class Pregel(PregelProtocol):
return channel.ValueType
def get_output_schema(
self, config: Optional[RunnableConfig] = None
) -> Type[BaseModel]:
self, config: RunnableConfig | None = None
) -> type[BaseModel]:
config = merge_configs(self.config, config)
if isinstance(self.output_channels, str):
return super().get_output_schema(config)
@@ -795,8 +777,8 @@ class Pregel(PregelProtocol):
)
def get_output_jsonschema(
self, config: Optional[RunnableConfig] = None
) -> Dict[str, Any]:
self, config: RunnableConfig | None = None
) -> dict[str, Any]:
schema = self.get_output_schema(config)
if hasattr(schema, "model_json_schema"):
return schema.model_json_schema()
@@ -811,13 +793,13 @@ class Pregel(PregelProtocol):
)
@property
def stream_channels_asis(self) -> Union[str, Sequence[str]]:
def stream_channels_asis(self) -> str | Sequence[str]:
return self.stream_channels or [
k for k in self.channels if isinstance(self.channels[k], BaseChannel)
]
def get_subgraphs(
self, *, namespace: Optional[str] = None, recurse: bool = False
self, *, namespace: str | None = None, recurse: bool = False
) -> Iterator[tuple[str, PregelProtocol]]:
for name, node in self.nodes.items():
# filter by prefix
@@ -846,7 +828,7 @@ class Pregel(PregelProtocol):
)
async def aget_subgraphs(
self, *, namespace: Optional[str] = None, recurse: bool = False
self, *, namespace: str | None = None, recurse: bool = False
) -> AsyncIterator[tuple[str, PregelProtocol]]:
for name, node in self.get_subgraphs(namespace=namespace, recurse=recurse):
yield name, node
@@ -858,8 +840,8 @@ class Pregel(PregelProtocol):
def _prepare_state_snapshot(
self,
config: RunnableConfig,
saved: Optional[CheckpointTuple],
recurse: Optional[BaseCheckpointSaver] = None,
saved: CheckpointTuple | None,
recurse: BaseCheckpointSaver | None = None,
apply_pending_writes: bool = False,
) -> StateSnapshot:
if not saved:
@@ -907,7 +889,7 @@ class Pregel(PregelProtocol):
# get the subgraphs
subgraphs = dict(self.get_subgraphs())
parent_ns = saved.config[CONF].get(CONFIG_KEY_CHECKPOINT_NS, "")
task_states: dict[str, Union[RunnableConfig, StateSnapshot]] = {}
task_states: dict[str, RunnableConfig | StateSnapshot] = {}
for task in next_tasks.values():
if task.name not in subgraphs:
continue
@@ -974,8 +956,8 @@ class Pregel(PregelProtocol):
async def _aprepare_state_snapshot(
self,
config: RunnableConfig,
saved: Optional[CheckpointTuple],
recurse: Optional[BaseCheckpointSaver] = None,
saved: CheckpointTuple | None,
recurse: BaseCheckpointSaver | None = None,
apply_pending_writes: bool = False,
) -> StateSnapshot:
if not saved:
@@ -1026,7 +1008,7 @@ class Pregel(PregelProtocol):
# get the subgraphs
subgraphs = {n: g async for n, g in self.aget_subgraphs()}
parent_ns = saved.config[CONF].get(CONFIG_KEY_CHECKPOINT_NS, "")
task_states: dict[str, Union[RunnableConfig, StateSnapshot]] = {}
task_states: dict[str, RunnableConfig | StateSnapshot] = {}
for task in next_tasks.values():
if task.name not in subgraphs:
continue
@@ -1094,7 +1076,7 @@ class Pregel(PregelProtocol):
self, config: RunnableConfig, *, subgraphs: bool = False
) -> StateSnapshot:
"""Get the current state of the graph."""
checkpointer: Optional[BaseCheckpointSaver] = ensure_config(config)[CONF].get(
checkpointer: BaseCheckpointSaver | None = ensure_config(config)[CONF].get(
CONFIG_KEY_CHECKPOINTER, self.checkpointer
)
if not checkpointer:
@@ -1136,7 +1118,7 @@ class Pregel(PregelProtocol):
self, config: RunnableConfig, *, subgraphs: bool = False
) -> StateSnapshot:
"""Get the current state of the graph."""
checkpointer: Optional[BaseCheckpointSaver] = ensure_config(config)[CONF].get(
checkpointer: BaseCheckpointSaver | None = ensure_config(config)[CONF].get(
CONFIG_KEY_CHECKPOINTER, self.checkpointer
)
if not checkpointer:
@@ -1178,13 +1160,13 @@ class Pregel(PregelProtocol):
self,
config: RunnableConfig,
*,
filter: Optional[Dict[str, Any]] = None,
before: Optional[RunnableConfig] = None,
limit: Optional[int] = None,
filter: dict[str, Any] | None = None,
before: RunnableConfig | None = None,
limit: int | None = None,
) -> Iterator[StateSnapshot]:
config = ensure_config(config)
"""Get the history of the state of the graph."""
checkpointer: Optional[BaseCheckpointSaver] = ensure_config(config)[CONF].get(
checkpointer: BaseCheckpointSaver | None = ensure_config(config)[CONF].get(
CONFIG_KEY_CHECKPOINTER, self.checkpointer
)
if not checkpointer:
@@ -1229,13 +1211,13 @@ class Pregel(PregelProtocol):
self,
config: RunnableConfig,
*,
filter: Optional[Dict[str, Any]] = None,
before: Optional[RunnableConfig] = None,
limit: Optional[int] = None,
filter: dict[str, Any] | None = None,
before: RunnableConfig | None = None,
limit: int | None = None,
) -> AsyncIterator[StateSnapshot]:
config = ensure_config(config)
"""Get the history of the state of the graph."""
checkpointer: Optional[BaseCheckpointSaver] = ensure_config(config)[CONF].get(
checkpointer: BaseCheckpointSaver | None = ensure_config(config)[CONF].get(
CONFIG_KEY_CHECKPOINTER, self.checkpointer
)
if not checkpointer:
@@ -1300,7 +1282,7 @@ class Pregel(PregelProtocol):
RunnableConfig: The updated config.
"""
checkpointer: Optional[BaseCheckpointSaver] = ensure_config(config)[CONF].get(
checkpointer: BaseCheckpointSaver | None = ensure_config(config)[CONF].get(
CONFIG_KEY_CHECKPOINTER, self.checkpointer
)
if not checkpointer:
@@ -1575,7 +1557,7 @@ class Pregel(PregelProtocol):
next_tasks[tid].writes.append((k, v))
if tasks := [t for t in next_tasks.values() if t.writes]:
apply_writes(checkpoint, channels, tasks, None)
valid_updates: list[tuple[str, Optional[dict[str, Any]]]] = []
valid_updates: list[tuple[str, dict[str, Any] | None]] = []
if len(updates) == 1:
values, as_node = updates[0]
# find last node that updated the state, if not provided
@@ -1714,7 +1696,7 @@ class Pregel(PregelProtocol):
RunnableConfig: The updated config.
"""
checkpointer: Optional[BaseCheckpointSaver] = ensure_config(config)[CONF].get(
checkpointer: BaseCheckpointSaver | None = ensure_config(config)[CONF].get(
CONFIG_KEY_CHECKPOINTER, self.checkpointer
)
if not checkpointer:
@@ -1989,7 +1971,7 @@ class Pregel(PregelProtocol):
next_tasks[tid].writes.append((k, v))
if tasks := [t for t in next_tasks.values() if t.writes]:
apply_writes(checkpoint, channels, tasks, None)
valid_updates: list[tuple[str, Optional[dict[str, Any]]]] = []
valid_updates: list[tuple[str, dict[str, Any] | None]] = []
if len(updates) == 1:
values, as_node = updates[0]
# find last node that updated the state, if not provided
@@ -2109,8 +2091,8 @@ class Pregel(PregelProtocol):
def update_state(
self,
config: RunnableConfig,
values: Optional[Union[dict[str, Any], Any]],
as_node: Optional[str] = None,
values: dict[str, Any] | Any | None,
as_node: str | None = None,
) -> RunnableConfig:
"""Update the state of the graph with the given values, as if they came from
node `as_node`. If `as_node` is not provided, it will be set to the last node
@@ -2122,7 +2104,7 @@ class Pregel(PregelProtocol):
self,
config: RunnableConfig,
values: dict[str, Any] | Any,
as_node: Optional[str] = None,
as_node: str | None = None,
) -> RunnableConfig:
"""Update the state of the graph asynchronously with the given values, as if they came from
node `as_node`. If `as_node` is not provided, it will be set to the last node
@@ -2134,19 +2116,19 @@ class Pregel(PregelProtocol):
self,
config: RunnableConfig,
*,
stream_mode: Optional[Union[StreamMode, list[StreamMode]]],
output_keys: Optional[Union[str, Sequence[str]]],
interrupt_before: Optional[Union[All, Sequence[str]]],
interrupt_after: Optional[Union[All, Sequence[str]]],
debug: Optional[bool],
stream_mode: StreamMode | list[StreamMode] | None,
output_keys: str | Sequence[str] | None,
interrupt_before: All | Sequence[str] | None,
interrupt_after: All | Sequence[str] | None,
debug: bool | None,
) -> tuple[
bool,
set[StreamMode],
Union[str, Sequence[str]],
Union[All, Sequence[str]],
Union[All, Sequence[str]],
Optional[BaseCheckpointSaver],
Optional[BaseStore],
str | Sequence[str],
All | Sequence[str],
All | Sequence[str],
BaseCheckpointSaver | None,
BaseStore | None,
]:
if config["recursion_limit"] < 1:
raise ValueError("recursion_limit must be at least 1")
@@ -2164,7 +2146,7 @@ class Pregel(PregelProtocol):
# if being called as a node in another graph, always use values mode
stream_mode = ["values"]
if self.checkpointer is False:
checkpointer: Optional[BaseCheckpointSaver] = None
checkpointer: BaseCheckpointSaver | None = None
elif CONFIG_KEY_CHECKPOINTER in config.get(CONF, {}):
checkpointer = config[CONF][CONFIG_KEY_CHECKPOINTER]
elif self.checkpointer is True:
@@ -2176,7 +2158,7 @@ class Pregel(PregelProtocol):
f"Checkpointer requires one or more of the following 'configurable' keys: {[s.id for s in checkpointer.config_specs]}"
)
if CONFIG_KEY_STORE in config.get(CONF, {}):
store: Optional[BaseStore] = config[CONF][CONFIG_KEY_STORE]
store: BaseStore | None = config[CONF][CONFIG_KEY_STORE]
else:
store = self.store
return (
@@ -2191,17 +2173,17 @@ class Pregel(PregelProtocol):
def stream(
self,
input: Union[dict[str, Any], Any],
config: Optional[RunnableConfig] = None,
input: dict[str, Any] | Any,
config: RunnableConfig | None = None,
*,
stream_mode: Optional[Union[StreamMode, list[StreamMode]]] = None,
output_keys: Optional[Union[str, Sequence[str]]] = None,
interrupt_before: Optional[Union[All, Sequence[str]]] = None,
interrupt_after: Optional[Union[All, Sequence[str]]] = None,
checkpoint_during: Optional[bool] = None,
debug: Optional[bool] = None,
stream_mode: StreamMode | list[StreamMode] | None = None,
output_keys: str | Sequence[str] | None = None,
interrupt_before: All | Sequence[str] | None = None,
interrupt_after: All | Sequence[str] | None = None,
checkpoint_during: bool | None = None,
debug: bool | None = None,
subgraphs: bool = False,
) -> Iterator[Union[dict[str, Any], Any]]:
) -> Iterator[dict[str, Any] | Any]:
"""Stream graph steps for a single input.
Args:
@@ -2427,7 +2409,7 @@ class Pregel(PregelProtocol):
):
# we are careful to have a single waiter live at any one time
# because on exit we increment semaphore count by exactly 1
waiter: Optional[concurrent.futures.Future] = None
waiter: concurrent.futures.Future | None = None
# because sync futures cannot be cancelled, we instead
# release the stream semaphore on exit, which will cause
# a pending waiter to return immediately
@@ -2478,17 +2460,17 @@ class Pregel(PregelProtocol):
async def astream(
self,
input: Union[dict[str, Any], Any],
config: Optional[RunnableConfig] = None,
input: dict[str, Any] | Any,
config: RunnableConfig | None = None,
*,
stream_mode: Optional[Union[StreamMode, list[StreamMode]]] = None,
output_keys: Optional[Union[str, Sequence[str]]] = None,
interrupt_before: Optional[Union[All, Sequence[str]]] = None,
interrupt_after: Optional[Union[All, Sequence[str]]] = None,
checkpoint_during: Optional[bool] = None,
debug: Optional[bool] = None,
stream_mode: StreamMode | list[StreamMode] | None = None,
output_keys: str | Sequence[str] | None = None,
interrupt_before: All | Sequence[str] | None = None,
interrupt_after: All | Sequence[str] | None = None,
checkpoint_during: bool | None = None,
debug: bool | None = None,
subgraphs: bool = False,
) -> AsyncIterator[Union[dict[str, Any], Any]]:
) -> AsyncIterator[dict[str, Any] | Any]:
"""Stream graph steps for a single input.
Args:
@@ -2779,17 +2761,17 @@ class Pregel(PregelProtocol):
def invoke(
self,
input: Union[dict[str, Any], Any],
config: Optional[RunnableConfig] = None,
input: dict[str, Any] | Any,
config: RunnableConfig | None = None,
*,
stream_mode: StreamMode = "values",
output_keys: Optional[Union[str, Sequence[str]]] = None,
interrupt_before: Optional[Union[All, Sequence[str]]] = None,
interrupt_after: Optional[Union[All, Sequence[str]]] = None,
checkpoint_during: Optional[bool] = None,
debug: Optional[bool] = None,
output_keys: str | Sequence[str] | None = None,
interrupt_before: All | Sequence[str] | None = None,
interrupt_after: All | Sequence[str] | None = None,
checkpoint_during: bool | None = None,
debug: bool | None = None,
**kwargs: Any,
) -> Union[dict[str, Any], Any]:
) -> dict[str, Any] | Any:
"""Run the graph with a single input and config.
Args:
@@ -2843,17 +2825,17 @@ class Pregel(PregelProtocol):
async def ainvoke(
self,
input: Union[dict[str, Any], Any],
config: Optional[RunnableConfig] = None,
input: dict[str, Any] | Any,
config: RunnableConfig | None = None,
*,
stream_mode: StreamMode = "values",
output_keys: Optional[Union[str, Sequence[str]]] = None,
interrupt_before: Optional[Union[All, Sequence[str]]] = None,
interrupt_after: Optional[Union[All, Sequence[str]]] = None,
checkpoint_during: Optional[bool] = None,
debug: Optional[bool] = None,
output_keys: str | Sequence[str] | None = None,
interrupt_before: All | Sequence[str] | None = None,
interrupt_after: All | Sequence[str] | None = None,
checkpoint_during: bool | None = None,
debug: bool | None = None,
**kwargs: Any,
) -> Union[dict[str, Any], Any]:
) -> dict[str, Any] | Any:
"""Asynchronously invoke the graph on a single input.
Args:
+1 -3
View File
@@ -3,19 +3,17 @@ import itertools
import sys
import threading
from collections import defaultdict, deque
from collections.abc import Iterable, Mapping, Sequence
from copy import copy
from functools import partial
from hashlib import sha1
from typing import (
Any,
Callable,
Iterable,
Literal,
Mapping,
NamedTuple,
Optional,
Protocol,
Sequence,
Union,
cast,
overload,
+4 -7
View File
@@ -5,7 +5,8 @@ import functools
import inspect
import sys
import types
from typing import Any, Callable, Generator, Generic, Optional, Sequence, TypeVar, cast
from collections.abc import Generator, Sequence
from typing import Any, Callable, Generic, Optional, TypeVar, cast
from langchain_core.runnables import Runnable
from typing_extensions import ParamSpec
@@ -29,16 +30,12 @@ from langgraph.utils.runnable import (
def _getattribute(obj: Any, name: str) -> Any:
for subpath in name.split("."):
if subpath == "<locals>":
raise AttributeError(
"Can't get local attribute {!r} on {!r}".format(name, obj)
)
raise AttributeError(f"Can't get local attribute {name!r} on {obj!r}")
try:
parent = obj
obj = getattr(obj, subpath)
except AttributeError:
raise AttributeError(
"Can't get attribute {!r} on {!r}".format(name, obj)
) from None
raise AttributeError(f"Can't get attribute {name!r} on {obj!r}") from None
return obj, parent
@@ -1,5 +1,6 @@
from collections.abc import Mapping
from datetime import datetime, timezone
from typing import Mapping, Optional
from typing import Optional
from langgraph.channels.base import BaseChannel
from langgraph.checkpoint.base import Checkpoint
+1 -4
View File
@@ -1,15 +1,12 @@
from collections import defaultdict
from collections.abc import Iterable, Iterator, Mapping, Sequence
from dataclasses import asdict
from datetime import datetime, timezone
from pprint import pformat
from typing import (
Any,
Iterable,
Iterator,
Literal,
Mapping,
Optional,
Sequence,
Union,
)
from uuid import UUID
+2 -1
View File
@@ -1,5 +1,6 @@
from collections import defaultdict
from typing import Any, Mapping, Optional, Sequence, Union, cast
from collections.abc import Mapping, Sequence
from typing import Any, Optional, Union, cast
from langchain_core.runnables.config import RunnableConfig
from langchain_core.runnables.graph import Graph, Node
+4 -7
View File
@@ -1,15 +1,12 @@
import asyncio
import concurrent.futures
import time
from contextlib import ExitStack
from collections.abc import Awaitable, Coroutine
from contextlib import AbstractAsyncContextManager, AbstractContextManager, ExitStack
from contextvars import copy_context
from types import TracebackType
from typing import (
AsyncContextManager,
Awaitable,
Callable,
ContextManager,
Coroutine,
Optional,
Protocol,
TypeVar,
@@ -40,7 +37,7 @@ class Submit(Protocol[P, T]):
) -> concurrent.futures.Future[T]: ...
class BackgroundExecutor(ContextManager):
class BackgroundExecutor(AbstractContextManager):
"""A context manager that runs sync tasks in the background.
Uses a thread pool executor to delegate tasks to separate threads.
On exit,
@@ -122,7 +119,7 @@ class BackgroundExecutor(ContextManager):
pass
class AsyncBackgroundExecutor(AsyncContextManager):
class AsyncBackgroundExecutor(AbstractAsyncContextManager):
"""A context manager that runs async tasks in the background.
Uses the current event loop to delegate tasks to asyncio tasks.
On exit,
+2 -1
View File
@@ -1,5 +1,6 @@
from collections import Counter
from typing import Any, Iterator, Literal, Mapping, Optional, Sequence, TypeVar, Union
from collections.abc import Iterator, Mapping, Sequence
from typing import Any, Literal, Optional, TypeVar, Union
from uuid import UUID
from langchain_core.runnables.utils import AddableDict
+18 -19
View File
@@ -3,21 +3,20 @@ import binascii
import concurrent.futures
import dataclasses
from collections import defaultdict, deque
from contextlib import AsyncExitStack, ExitStack
from collections.abc import Iterator, Mapping, Sequence
from contextlib import (
AbstractAsyncContextManager,
AbstractContextManager,
AsyncExitStack,
ExitStack,
)
from inspect import signature
from types import TracebackType
from typing import (
Any,
AsyncContextManager,
Callable,
ContextManager,
Iterator,
List,
Literal,
Mapping,
Optional,
Sequence,
Type,
TypeVar,
Union,
cast,
@@ -146,7 +145,7 @@ def DuplexStream(*streams: StreamProtocol) -> StreamProtocol:
class PregelLoop(LoopProtocol):
input: Optional[Any]
input_model: Optional[Type[BaseModel]]
input_model: Optional[type[BaseModel]]
checkpointer: Optional[BaseCheckpointSaver]
nodes: Mapping[str, PregelNode]
specs: Mapping[str, Union[BaseChannel, ManagedValueSpec]]
@@ -186,7 +185,7 @@ class PregelLoop(LoopProtocol):
checkpoint_ns: tuple[str, ...]
checkpoint_config: RunnableConfig
checkpoint_metadata: CheckpointMetadata
checkpoint_pending_writes: List[PendingWrite]
checkpoint_pending_writes: list[PendingWrite]
checkpoint_previous_versions: dict[str, Union[str, float, int]]
prev_checkpoint_config: Optional[RunnableConfig]
@@ -214,7 +213,7 @@ class PregelLoop(LoopProtocol):
interrupt_after: Union[All, Sequence[str]] = EMPTY_SEQ,
interrupt_before: Union[All, Sequence[str]] = EMPTY_SEQ,
manager: Union[None, AsyncParentRunManager, ParentRunManager] = None,
input_model: Optional[Type[BaseModel]] = None,
input_model: Optional[type[BaseModel]] = None,
debug: bool = False,
migrate_checkpoint: Optional[Callable[[Checkpoint], None]] = None,
trigger_to_nodes: Optional[Mapping[str, Sequence[str]]] = None,
@@ -491,7 +490,7 @@ class PregelLoop(LoopProtocol):
if self.input is INPUT_SHOULD_VALIDATE:
self.input = INPUT_DONE
# validate
cast(Type[BaseModel], self.input_model)(
cast(type[BaseModel], self.input_model)(
**read_channels(self.channels, self.stream_keys)
)
# produce values output
@@ -839,7 +838,7 @@ class PregelLoop(LoopProtocol):
def _suppress_interrupt(
self,
exc_type: Optional[Type[BaseException]],
exc_type: Optional[type[BaseException]],
exc_value: Optional[BaseException],
traceback: Optional[TracebackType],
) -> Optional[bool]:
@@ -943,7 +942,7 @@ class PregelLoop(LoopProtocol):
)
class SyncPregelLoop(PregelLoop, ContextManager):
class SyncPregelLoop(PregelLoop, AbstractContextManager):
def __init__(
self,
input: Optional[Any],
@@ -959,7 +958,7 @@ class SyncPregelLoop(PregelLoop, ContextManager):
interrupt_before: Union[All, Sequence[str]] = EMPTY_SEQ,
output_keys: Union[str, Sequence[str]] = EMPTY_SEQ,
stream_keys: Union[str, Sequence[str]] = EMPTY_SEQ,
input_model: Optional[Type[BaseModel]] = None,
input_model: Optional[type[BaseModel]] = None,
debug: bool = False,
migrate_checkpoint: Optional[Callable[[Checkpoint], None]] = None,
trigger_to_nodes: Optional[Mapping[str, Sequence[str]]] = None,
@@ -1085,7 +1084,7 @@ class SyncPregelLoop(PregelLoop, ContextManager):
def __exit__(
self,
exc_type: Optional[Type[BaseException]],
exc_type: Optional[type[BaseException]],
exc_value: Optional[BaseException],
traceback: Optional[TracebackType],
) -> Optional[bool]:
@@ -1093,7 +1092,7 @@ class SyncPregelLoop(PregelLoop, ContextManager):
return self.stack.__exit__(exc_type, exc_value, traceback)
class AsyncPregelLoop(PregelLoop, AsyncContextManager):
class AsyncPregelLoop(PregelLoop, AbstractAsyncContextManager):
def __init__(
self,
input: Optional[Any],
@@ -1109,7 +1108,7 @@ class AsyncPregelLoop(PregelLoop, AsyncContextManager):
manager: Union[None, AsyncParentRunManager, ParentRunManager] = None,
output_keys: Union[str, Sequence[str]] = EMPTY_SEQ,
stream_keys: Union[str, Sequence[str]] = EMPTY_SEQ,
input_model: Optional[Type[BaseModel]] = None,
input_model: Optional[type[BaseModel]] = None,
debug: bool = False,
migrate_checkpoint: Optional[Callable[[Checkpoint], None]] = None,
trigger_to_nodes: Optional[Mapping[str, Sequence[str]]] = None,
@@ -1238,7 +1237,7 @@ class AsyncPregelLoop(PregelLoop, AsyncContextManager):
async def __aexit__(
self,
exc_type: Optional[Type[BaseException]],
exc_type: Optional[type[BaseException]],
exc_value: Optional[BaseException],
traceback: Optional[TracebackType],
) -> Optional[bool]:
+2 -1
View File
@@ -1,6 +1,7 @@
import asyncio
from collections.abc import AsyncIterator, Iterator, Mapping
from contextlib import AsyncExitStack, ExitStack, asynccontextmanager, contextmanager
from typing import AsyncIterator, Iterator, Mapping, Union
from typing import Union
from langgraph.channels.base import BaseChannel
from langgraph.checkpoint.base import Checkpoint
+5 -9
View File
@@ -1,12 +1,8 @@
from collections.abc import AsyncIterator, Iterator, Sequence
from typing import (
Any,
AsyncIterator,
Callable,
Dict,
Iterator,
List,
Optional,
Sequence,
TypeVar,
Union,
cast,
@@ -115,13 +111,13 @@ class StreamMessagesHandler(BaseCallbackHandler, _StreamingCallbackHandler):
def on_chain_start(
self,
serialized: Dict[str, Any],
inputs: Dict[str, Any],
serialized: dict[str, Any],
inputs: dict[str, Any],
*,
run_id: UUID,
parent_run_id: Optional[UUID] = None,
tags: Optional[List[str]] = None,
metadata: Optional[Dict[str, Any]] = None,
tags: Optional[list[str]] = None,
metadata: Optional[dict[str, Any]] = None,
**kwargs: Any,
) -> Any:
if (
+1 -3
View File
@@ -1,10 +1,8 @@
from abc import ABC, abstractmethod
from collections.abc import AsyncIterator, Iterator, Sequence
from typing import (
Any,
AsyncIterator,
Iterator,
Optional,
Sequence,
Union,
)
+38 -48
View File
@@ -1,14 +1,10 @@
from __future__ import annotations
from collections.abc import AsyncIterator, Iterator, Mapping, Sequence
from functools import cached_property
from typing import (
Any,
AsyncIterator,
Callable,
Iterator,
Mapping,
Optional,
Sequence,
Union,
)
@@ -37,11 +33,11 @@ class ChannelRead(RunnableCallable):
"""Implements the logic for reading state from CONFIG_KEY_READ.
Usable both as a runnable as well as a static method to call imperatively."""
channel: Union[str, list[str]]
channel: str | list[str]
fresh: bool = False
mapper: Optional[Callable[[Any], Any]] = None
mapper: Callable[[Any], Any] | None = None
@property
def config_specs(self) -> list[ConfigurableFieldSpec]:
@@ -57,11 +53,11 @@ class ChannelRead(RunnableCallable):
def __init__(
self,
channel: Union[str, list[str]],
channel: str | list[str],
*,
fresh: bool = False,
mapper: Optional[Callable[[Any], Any]] = None,
tags: Optional[list[str]] = None,
mapper: Callable[[Any], Any] | None = None,
tags: list[str] | None = None,
) -> None:
super().__init__(
func=self._read,
@@ -75,9 +71,7 @@ class ChannelRead(RunnableCallable):
self.mapper = mapper
self.channel = channel
def get_name(
self, suffix: Optional[str] = None, *, name: Optional[str] = None
) -> str:
def get_name(self, suffix: str | None = None, *, name: str | None = None) -> str:
if name:
pass
elif isinstance(self.channel, str):
@@ -100,9 +94,9 @@ class ChannelRead(RunnableCallable):
def do_read(
config: RunnableConfig,
*,
select: Union[str, list[str]],
select: str | list[str],
fresh: bool = False,
mapper: Optional[Callable[[Any], Any]] = None,
mapper: Callable[[Any], Any] | None = None,
) -> Any:
try:
read: READ_TYPE = config[CONF][CONFIG_KEY_READ]
@@ -125,7 +119,7 @@ class PregelNode(Runnable):
itself, but instead acts as a container for the components necessary to make
a PregelExecutableTask for a node."""
channels: Union[list[str], Mapping[str, str]]
channels: list[str] | Mapping[str, str]
"""The channels that will be passed as input to `bound`.
If a list, the node will be invoked with the first of that isn't empty.
If a dict, the keys are the names of the channels, and the values are the keys
@@ -135,7 +129,7 @@ class PregelNode(Runnable):
"""If any of these channels is written to, this node will be triggered in
the next step."""
mapper: Optional[Callable[[Any], Any]]
mapper: Callable[[Any], Any] | None
"""A function to transform the input before passing it to `bound`."""
writers: list[Runnable]
@@ -146,13 +140,13 @@ class PregelNode(Runnable):
"""The main logic of the node. This will be invoked with the input from
`channels`."""
retry_policy: Optional[Sequence[RetryPolicy]]
retry_policy: Sequence[RetryPolicy] | None
"""The retry policies to use when invoking the node."""
tags: Optional[Sequence[str]]
tags: Sequence[str] | None
"""Tags to attach to the node for tracing."""
metadata: Optional[Mapping[str, Any]]
metadata: Mapping[str, Any] | None
"""Metadata to attach to the node for tracing."""
subgraphs: Sequence[PregelProtocol]
@@ -161,15 +155,15 @@ class PregelNode(Runnable):
def __init__(
self,
*,
channels: Union[list[str], Mapping[str, str]],
channels: list[str] | Mapping[str, str],
triggers: Sequence[str],
mapper: Optional[Callable[[Any], Any]] = None,
writers: Optional[list[Runnable]] = None,
tags: Optional[list[str]] = None,
metadata: Optional[Mapping[str, Any]] = None,
bound: Optional[Runnable[Any, Any]] = None,
retry_policy: Optional[Union[RetryPolicy, Sequence[RetryPolicy]]] = None,
subgraphs: Optional[Sequence[PregelProtocol]] = None,
mapper: Callable[[Any], Any] | None = None,
writers: list[Runnable] | None = None,
tags: list[str] | None = None,
metadata: Mapping[str, Any] | None = None,
bound: Runnable[Any, Any] | None = None,
retry_policy: RetryPolicy | Sequence[RetryPolicy] | None = None,
subgraphs: Sequence[PregelProtocol] | None = None,
) -> None:
self.channels = channels
self.triggers = list(triggers)
@@ -219,7 +213,7 @@ class PregelNode(Runnable):
return writers
@cached_property
def node(self) -> Optional[Runnable[Any, Any]]:
def node(self) -> Runnable[Any, Any] | None:
"""Get a runnable that combines `bound` and `writers`."""
writers = self.flat_writers
if self.bound is DEFAULT_BOUND and not writers:
@@ -262,11 +256,9 @@ class PregelNode(Runnable):
def __or__(
self,
other: Union[
Runnable[Any, Other],
Callable[[Any], Other],
Mapping[str, Runnable[Any, Other] | Callable[[Any], Other]],
],
other: Runnable[Any, Other]
| Callable[[Any], Other]
| Mapping[str, Runnable[Any, Other] | Callable[[Any], Other]],
) -> PregelNode:
if isinstance(other, Runnable) and ChannelWrite.is_writer(other):
return self.copy(update=dict(writers=[*self.writers, other]))
@@ -278,7 +270,7 @@ class PregelNode(Runnable):
def pipe(
self,
*others: Runnable[Any, Other] | Callable[[Any], Other],
name: Optional[str] = None,
name: str | None = None,
) -> RunnableSerializable[Any, Other]:
for other in others:
self = self | other
@@ -286,19 +278,17 @@ class PregelNode(Runnable):
def __ror__(
self,
other: Union[
Runnable[Other, Any],
Callable[[Any], Other],
Mapping[str, Union[Runnable[Other, Any], Callable[[Other], Any]]],
],
other: Runnable[Other, Any]
| Callable[[Any], Other]
| Mapping[str, Runnable[Other, Any] | Callable[[Other], Any]],
) -> RunnableSerializable:
raise NotImplementedError()
def invoke(
self,
input: Input,
config: Optional[RunnableConfig] = None,
**kwargs: Optional[Any],
config: RunnableConfig | None = None,
**kwargs: Any | None,
) -> Any:
return self.bound.invoke(
input,
@@ -309,8 +299,8 @@ class PregelNode(Runnable):
async def ainvoke(
self,
input: Input,
config: Optional[RunnableConfig] = None,
**kwargs: Optional[Any],
config: RunnableConfig | None = None,
**kwargs: Any | None,
) -> Any:
return await self.bound.ainvoke(
input,
@@ -321,8 +311,8 @@ class PregelNode(Runnable):
def stream(
self,
input: Input,
config: Optional[RunnableConfig] = None,
**kwargs: Optional[Any],
config: RunnableConfig | None = None,
**kwargs: Any | None,
) -> Iterator[Any]:
yield from self.bound.stream(
input,
@@ -333,8 +323,8 @@ class PregelNode(Runnable):
async def astream(
self,
input: Input,
config: Optional[RunnableConfig] = None,
**kwargs: Optional[Any],
config: RunnableConfig | None = None,
**kwargs: Any | None,
) -> AsyncIterator[Any]:
async for item in self.bound.astream(
input,
+1 -3
View File
@@ -1,11 +1,9 @@
from collections.abc import AsyncIterator, Iterator, Sequence
from dataclasses import asdict
from typing import (
Any,
AsyncIterator,
Iterator,
Literal,
Optional,
Sequence,
Union,
cast,
)
+2 -1
View File
@@ -3,8 +3,9 @@ import logging
import random
import sys
import time
from collections.abc import Sequence
from dataclasses import replace
from typing import Any, Optional, Sequence
from typing import Any, Optional
from langgraph.constants import (
CONF,
+3 -8
View File
@@ -3,18 +3,13 @@ import concurrent.futures
import threading
import time
import weakref
from collections.abc import AsyncIterator, Awaitable, Iterable, Iterator, Sequence
from functools import partial
from typing import (
Any,
AsyncIterator,
Awaitable,
Callable,
Generic,
Iterable,
Iterator,
Optional,
Sequence,
Type,
TypeVar,
Union,
cast,
@@ -72,7 +67,7 @@ class FuturesDict(Generic[F, E], dict[F, Optional[PregelExecutableTask]]):
callback: weakref.ref[
Callable[[PregelExecutableTask, Optional[BaseException]], None]
],
future_type: Type[F],
future_type: type[F],
# used for generic typing, newer py supports FutureDict[...](...)
) -> None:
super().__init__()
@@ -475,7 +470,7 @@ def _exception(
def _panic_or_proceed(
futs: Union[set[concurrent.futures.Future], set[asyncio.Future]],
*,
timeout_exc_cls: Type[Exception] = TimeoutError,
timeout_exc_cls: type[Exception] = TimeoutError,
panic: bool = True,
) -> None:
"""Cancel remaining tasks if any failed, re-raise exception if panic is True."""
+2 -1
View File
@@ -1,4 +1,5 @@
from typing import Any, Mapping, Optional, Sequence, Union
from collections.abc import Mapping, Sequence
from typing import Any, Optional, Union
from langgraph.channels.base import BaseChannel
from langgraph.constants import RESERVED
+10 -12
View File
@@ -1,11 +1,11 @@
from __future__ import annotations
from collections.abc import Sequence
from typing import (
Any,
Callable,
NamedTuple,
Optional,
Sequence,
TypeVar,
Union,
cast,
@@ -32,12 +32,12 @@ class ChannelWriteEntry(NamedTuple):
"""Value to write, or PASSTHROUGH to use the input."""
skip_none: bool = False
"""Whether to skip writing if the value is None."""
mapper: Optional[Callable] = None
mapper: Callable | None = None
"""Function to transform the value before writing."""
class ChannelWriteTupleEntry(NamedTuple):
mapper: Callable[[Any], Optional[Sequence[tuple[str, Any]]]]
mapper: Callable[[Any], Sequence[tuple[str, Any]] | None]
"""Function to extract tuples from value."""
value: Any = PASSTHROUGH
"""Value to write, or PASSTHROUGH to use the input."""
@@ -49,15 +49,15 @@ class ChannelWrite(RunnableCallable):
"""Implements the logic for sending writes to CONFIG_KEY_SEND.
Can be used as a runnable or as a static method to call imperatively."""
writes: list[Union[ChannelWriteEntry, ChannelWriteTupleEntry, Send]]
writes: list[ChannelWriteEntry | ChannelWriteTupleEntry | Send]
"""Sequence of write entries or Send objects to write."""
def __init__(
self,
writes: Sequence[Union[ChannelWriteEntry, ChannelWriteTupleEntry, Send]],
writes: Sequence[ChannelWriteEntry | ChannelWriteTupleEntry | Send],
*,
tags: Optional[Sequence[str]] = None, # ignored
require_at_least_one_of: Optional[Sequence[str]] = None, # ignored
tags: Sequence[str] | None = None, # ignored
require_at_least_one_of: Sequence[str] | None = None, # ignored
):
super().__init__(
func=self._write,
@@ -70,9 +70,7 @@ class ChannelWrite(RunnableCallable):
list[Union[ChannelWriteEntry, ChannelWriteTupleEntry, Send]], writes
)
def get_name(
self, suffix: Optional[str] = None, *, name: Optional[str] = None
) -> str:
def get_name(self, suffix: str | None = None, *, name: str | None = None) -> str:
if not name:
name = f"ChannelWrite<{','.join(w.channel if isinstance(w, ChannelWriteEntry) else '...' if isinstance(w, ChannelWriteTupleEntry) else w.node for w in self.writes)}>"
return super().get_name(suffix, name=name)
@@ -122,9 +120,9 @@ class ChannelWrite(RunnableCallable):
@staticmethod
def do_write(
config: RunnableConfig,
writes: Sequence[Union[ChannelWriteEntry, ChannelWriteTupleEntry, Send]],
writes: Sequence[ChannelWriteEntry | ChannelWriteTupleEntry | Send],
allow_passthrough: bool = True,
require_at_least_one_of: Optional[Sequence[str]] = None, # ignored
require_at_least_one_of: Sequence[str] | None = None, # ignored
) -> None:
# validate
for w in writes:
+2 -4
View File
@@ -3,18 +3,16 @@ import hashlib
import sys
import uuid
from collections import deque
from collections.abc import Hashable, Sequence
from typing import (
TYPE_CHECKING,
Any,
Callable,
ClassVar,
Generic,
Hashable,
Literal,
NamedTuple,
Optional,
Sequence,
Type,
TypeVar,
Union,
cast,
@@ -120,7 +118,7 @@ class RetryPolicy(NamedTuple):
jitter: bool = True
"""Whether to add random jitter to the interval between retries."""
retry_on: Union[
Type[Exception], Sequence[Type[Exception]], Callable[[Exception], bool]
type[Exception], Sequence[type[Exception]], Callable[[Exception], bool]
] = default_retry_on
"""List of exception classes that should trigger a retry, or a callable that returns True for exceptions that should trigger a retry."""
+2 -1
View File
@@ -1,6 +1,7 @@
from collections import ChainMap
from collections.abc import Sequence
from os import getenv
from typing import Any, Optional, Sequence, cast
from typing import Any, Optional, cast
from langchain_core.callbacks import (
AsyncCallbackManager,
+5 -4
View File
@@ -1,9 +1,10 @@
import dataclasses
from typing import Any, Generator, Optional, Sequence, Type, Union, get_type_hints
from collections.abc import Generator, Sequence
from typing import Annotated, Any, Optional, Union, get_type_hints
from pydantic import BaseModel
from pydantic.v1 import BaseModel as BaseModelV1
from typing_extensions import Annotated, NotRequired, ReadOnly, Required, get_origin
from typing_extensions import NotRequired, ReadOnly, Required, get_origin
# NOTE: this is redefined here separately from langgraph.constants
# to avoid a circular import
@@ -68,7 +69,7 @@ def _is_readonly_type(type_: Any) -> bool:
_DEFAULT_KEYS: frozenset[str] = frozenset()
def get_field_default(name: str, type_: Any, schema: Type[Any]) -> Any:
def get_field_default(name: str, type_: Any, schema: type[Any]) -> Any:
"""Determine the default value for a field in a state schema.
This is based on:
@@ -115,7 +116,7 @@ def get_field_default(name: str, type_: Any, schema: Type[Any]) -> Any:
def get_enhanced_type_hints(
type: Type[Any],
type: type[Any],
) -> Generator[tuple[str, Any, Any, Optional[str]], None, None]:
"""Attempt to extract default values and descriptions from provided type, used for config schema."""
for name, typ in get_type_hints(type).items():
+2 -1
View File
@@ -4,7 +4,8 @@ import contextvars
import inspect
import sys
import types
from typing import Awaitable, Coroutine, Generator, Optional, TypeVar, Union, cast
from collections.abc import Awaitable, Coroutine, Generator
from typing import Optional, TypeVar, Union, cast
T = TypeVar("T")
AnyFuture = Union[asyncio.Future, concurrent.futures.Future]
+2 -2
View File
@@ -1,7 +1,7 @@
import sys
import typing
from dataclasses import is_dataclass
from typing import Any, Dict, Optional, Union
from typing import Any, Optional, Union
import typing_extensions
from pydantic import BaseModel
@@ -11,7 +11,7 @@ from pydantic.v1 import BaseModel as BaseModelV1
def create_model(
model_name: str,
*,
field_definitions: Optional[Dict[str, Any]] = None,
field_definitions: Optional[dict[str, Any]] = None,
root: Optional[Any] = None,
) -> Union[BaseModel, BaseModelV1]:
"""Create a pydantic model with the given field definitions.
+9 -8
View File
@@ -2,21 +2,22 @@ import asyncio
import enum
import inspect
import sys
from collections.abc import (
AsyncIterator,
Awaitable,
Coroutine,
Generator,
Iterator,
Sequence,
)
from contextlib import AsyncExitStack, contextmanager
from contextvars import Context, Token, copy_context
from functools import partial, wraps
from typing import (
Any,
AsyncIterator,
Awaitable,
Callable,
Coroutine,
Generator,
Iterator,
Optional,
Protocol,
Sequence,
Tuple,
Union,
cast,
)
@@ -278,7 +279,7 @@ class RunnableCallable(Runnable):
if func_accepts_config is not None:
self.func_accepts_config = func_accepts_config
self.func_accepts: dict[str, Tuple[str, Any]] = {}
self.func_accepts: dict[str, tuple[str, Any]] = {}
else:
params = inspect.signature(cast(Callable, func or afunc)).parameters
+3 -2
View File
@@ -41,11 +41,12 @@ types-requests = "^2.32.0.20240914"
pycryptodome = "^3.21.0"
[tool.ruff]
lint.select = [ "E", "F", "I", "TID251" ]
lint.ignore = [ "E501" ]
lint.select = [ "E", "F", "I", "TID251", "UP" ]
lint.ignore = [ "E501", "UP007" ]
line-length = 88
indent-width = 4
extend-include = ["*.ipynb"]
target-version = "py39"
[tool.ruff.format]
quote-style = "double"
+2 -1
View File
@@ -1,5 +1,6 @@
import re
from typing import Any, Sequence, Union
from collections.abc import Sequence
from typing import Any, Union
from typing_extensions import Self
+2 -1
View File
@@ -1,6 +1,7 @@
import sys
from collections.abc import AsyncIterator
from contextlib import asynccontextmanager
from typing import AsyncIterator, Optional
from typing import Optional
from uuid import UUID, uuid4
import pytest
+8 -7
View File
@@ -1,5 +1,6 @@
import re
from typing import Any, AsyncIterator, Iterator, List, Optional, cast
from collections.abc import AsyncIterator, Iterator
from typing import Any, Optional, cast
from langchain_core.callbacks import (
AsyncCallbackManagerForLLMRun,
@@ -20,8 +21,8 @@ class FakeChatModel(GenericFakeChatModel):
def _generate(
self,
messages: List[BaseMessage],
stop: Optional[List[str]] = None,
messages: list[BaseMessage],
stop: Optional[list[str]] = None,
run_manager: Optional[CallbackManagerForLLMRun] = None,
**kwargs: Any,
) -> ChatResult:
@@ -42,8 +43,8 @@ class FakeChatModel(GenericFakeChatModel):
def _stream(
self,
messages: List[BaseMessage],
stop: Optional[List[str]] = None,
messages: list[BaseMessage],
stop: Optional[list[str]] = None,
run_manager: Optional[CallbackManagerForLLMRun] = None,
**kwargs: Any,
) -> Iterator[ChatGenerationChunk]:
@@ -90,8 +91,8 @@ class FakeChatModel(GenericFakeChatModel):
async def _astream(
self,
messages: List[BaseMessage],
stop: Optional[List[str]] = None,
messages: list[BaseMessage],
stop: Optional[list[str]] = None,
run_manager: Optional[AsyncCallbackManagerForLLMRun] = None,
**kwargs: Any,
) -> AsyncIterator[ChatGenerationChunk]:
+4 -3
View File
@@ -1,5 +1,6 @@
import operator
from typing import Sequence, Union
from collections.abc import Sequence
from typing import Union
import pytest
@@ -33,7 +34,7 @@ def test_last_value() -> None:
def test_topic() -> None:
channel = Topic(str).from_checkpoint(MISSING)
assert channel.ValueType is Sequence[str]
assert channel.ValueType == Sequence[str]
assert channel.UpdateType is Union[str, list[str]]
assert channel.update(["a", "b"])
@@ -57,7 +58,7 @@ def test_topic() -> None:
def test_topic_accumulate() -> None:
channel = Topic(str, accumulate=True).from_checkpoint(MISSING)
assert channel.ValueType is Sequence[str]
assert channel.ValueType == Sequence[str]
assert channel.UpdateType is Union[str, list[str]]
assert channel.update(["a", "b"])
@@ -1575,7 +1575,7 @@ def test_migrate_checkpoints(source: str, target: str) -> None:
# check that the migrated checkpoint matches the target checkpoint
assert (
migrated == target_checkpoint.checkpoint
), "Checkpoint mismatch at index {}".format(idx)
), f"Checkpoint mismatch at index {idx}"
@NEEDS_CONTEXTVARS
+1 -1
View File
@@ -1,4 +1,4 @@
from typing import Iterator
from collections.abc import Iterator
from langgraph.pregel.io import single
+2 -1
View File
@@ -2,9 +2,10 @@ import json
import operator
import re
import time
from collections.abc import Iterator
from contextlib import contextmanager
from dataclasses import replace
from typing import Annotated, Any, Iterator, Literal, Optional, Union, cast
from typing import Annotated, Any, Literal, Optional, Union, cast
import httpx
import pytest
@@ -2,11 +2,11 @@ import asyncio
import operator
import re
import sys
from collections.abc import AsyncIterator
from contextlib import asynccontextmanager
from typing import (
Annotated,
Any,
AsyncIterator,
Literal,
Optional,
Union,
+14 -19
View File
@@ -9,6 +9,7 @@ import time
import uuid
import warnings
from collections import Counter, deque
from collections.abc import Generator, Iterator, Sequence
from concurrent.futures import ThreadPoolExecutor
from contextlib import contextmanager
from dataclasses import dataclass, field
@@ -16,14 +17,8 @@ from random import randrange
from typing import (
Annotated,
Any,
Dict,
Generator,
Iterator,
List,
Literal,
Optional,
Sequence,
Tuple,
Union,
get_type_hints,
)
@@ -243,7 +238,7 @@ def test_checkpoint_errors() -> None:
class FaultyPutWritesCheckpointer(InMemorySaver):
def put_writes(
self, config: RunnableConfig, writes: List[Tuple[str, Any]], task_id: str
self, config: RunnableConfig, writes: list[tuple[str, Any]], task_id: str
) -> RunnableConfig:
raise ValueError("Faulty put_writes")
@@ -448,7 +443,7 @@ def test_reducer_before_first_node() -> None:
class State(TypedDict):
hello: str
messages: Annotated[List[str], add_messages]
messages: Annotated[list[str], add_messages]
def node_a(state: State) -> State:
assert state == {
@@ -1131,7 +1126,7 @@ def test_pending_writes_resume(
value: Annotated[int, operator.add]
class AwhileMaker:
def __init__(self, sleep: float, rtn: Union[Dict, Exception]) -> None:
def __init__(self, sleep: float, rtn: Union[dict, Exception]) -> None:
self.sleep = sleep
self.rtn = rtn
self.reset()
@@ -4429,7 +4424,7 @@ def test_xray_lance(snapshot: SnapshotAssertion):
return f"Name: {self.name}\nRole: {self.role}\nAffiliation: {self.affiliation}\nDescription: {self.description}\n"
class Perspectives(BaseModel):
analysts: List[Analyst] = Field(
analysts: list[Analyst] = Field(
description="Comprehensive list of investment analysts with their roles and affiliations.",
)
@@ -4448,15 +4443,15 @@ def test_xray_lance(snapshot: SnapshotAssertion):
)
class InterviewState(TypedDict):
messages: Annotated[List[AnyMessage], add_messages]
messages: Annotated[list[AnyMessage], add_messages]
analyst: Analyst
section: Section
class ResearchGraphState(TypedDict):
analysts: List[Analyst]
analysts: list[Analyst]
topic: str
max_analysts: int
sections: List[Section]
sections: list[Section]
interviews: Annotated[list, operator.add]
# Conditional edge
@@ -7435,7 +7430,7 @@ def test_parallel_interrupts(
class ChildState(BaseModel):
prompt: str = Field(..., description="What is going to be asked to the user?")
human_input: Optional[str] = Field(None, description="What the human said")
human_inputs: Annotated[List[str], operator.add] = Field(
human_inputs: Annotated[list[str], operator.add] = Field(
default_factory=list, description="All of my messages"
)
@@ -7456,10 +7451,10 @@ def test_parallel_interrupts(
# --- PARENT GRAPH ---
class ParentState(BaseModel):
prompts: List[str] = Field(
prompts: list[str] = Field(
..., description="What is going to be asked to the user?"
)
human_inputs: Annotated[List[str], operator.add] = Field(
human_inputs: Annotated[list[str], operator.add] = Field(
default_factory=list, description="All of my messages"
)
@@ -7612,7 +7607,7 @@ def test_parallel_interrupts_double(
class ChildState(BaseModel):
prompt: str = Field(..., description="What is going to be asked to the user?")
human_input: Optional[str] = Field(None, description="What the human said")
human_inputs: Annotated[List[str], operator.add] = Field(
human_inputs: Annotated[list[str], operator.add] = Field(
default_factory=list, description="All of my messages"
)
@@ -7640,10 +7635,10 @@ def test_parallel_interrupts_double(
# --- PARENT GRAPH ---
class ParentState(BaseModel):
prompts: List[str] = Field(
prompts: list[str] = Field(
..., description="What is going to be asked to the user?"
)
human_inputs: Annotated[List[str], operator.add] = Field(
human_inputs: Annotated[list[str], operator.add] = Field(
default_factory=list, description="All of my messages"
)
+6 -11
View File
@@ -8,20 +8,15 @@ import random
import sys
import uuid
from collections import Counter, deque
from collections.abc import AsyncGenerator, AsyncIterator, Generator
from contextlib import asynccontextmanager, contextmanager
from dataclasses import replace
from time import perf_counter
from typing import (
Annotated,
Any,
AsyncGenerator,
AsyncIterator,
Dict,
Generator,
List,
Literal,
Optional,
Tuple,
Union,
)
from uuid import UUID
@@ -113,7 +108,7 @@ async def test_checkpoint_errors() -> None:
class FaultyPutWritesCheckpointer(InMemorySaver):
async def aput_writes(
self, config: RunnableConfig, writes: List[Tuple[str, Any]], task_id: str
self, config: RunnableConfig, writes: list[tuple[str, Any]], task_id: str
) -> RunnableConfig:
raise ValueError("Faulty put_writes")
@@ -1959,7 +1954,7 @@ async def test_pending_writes_resume(
value: Annotated[int, operator.add]
class AwhileMaker:
def __init__(self, sleep: float, rtn: Union[Dict, Exception]) -> None:
def __init__(self, sleep: float, rtn: Union[dict, Exception]) -> None:
self.sleep = sleep
self.rtn = rtn
self.reset()
@@ -6249,7 +6244,7 @@ async def test_store_injected_async(checkpointer_name: str, store_name: str) ->
assert result == {"count": N + 1}
returned_doc = (await the_store.aget(namespace, doc_id)).value
assert returned_doc == {**doc, "from_thread": thread_1, "some_val": 0}
assert len((await the_store.asearch(namespace))) == 1
assert len(await the_store.asearch(namespace)) == 1
# Check results after another turn of the same thread
result = await graph.ainvoke(
@@ -6258,7 +6253,7 @@ async def test_store_injected_async(checkpointer_name: str, store_name: str) ->
assert result == {"count": (N + 1) * 2}
returned_doc = (await the_store.aget(namespace, doc_id)).value
assert returned_doc == {**doc, "from_thread": thread_1, "some_val": N + 1}
assert len((await the_store.asearch(namespace))) == 1
assert len(await the_store.asearch(namespace)) == 1
# Test with a different thread
result = await graph.ainvoke(
@@ -6272,7 +6267,7 @@ async def test_store_injected_async(checkpointer_name: str, store_name: str) ->
"some_val": 0,
} # Overwrites the whole doc
assert (
len((await the_store.asearch(namespace))) == 1
len(await the_store.asearch(namespace)) == 1
) # still overwriting the same one
+2 -2
View File
@@ -6,7 +6,7 @@ import re
import sys
import uuid
from enum import Enum
from typing import Annotated, List, Literal, Optional, Union
from typing import Annotated, Literal, Optional, Union
import pytest
@@ -183,7 +183,7 @@ def test_nested_pydantic_models(version: str) -> None:
validated_age: Annotated[int, Field(gt=0, lt=120)]
# Generic containers with validators
decimal_list: List[decimal.Decimal]
decimal_list: list[decimal.Decimal]
id_tuple: tuple[uuid.UUID, uuid.UUID]
inputs = {
+2 -2
View File
@@ -2,13 +2,13 @@ import inspect
import operator
import warnings
from dataclasses import dataclass, field
from typing import Annotated, Any, Optional
from typing import Annotated as Annotated2
from typing import Any, Optional
import pytest
from langchain_core.runnables import RunnableConfig, RunnableLambda
from pydantic.v1 import BaseModel
from typing_extensions import Annotated, NotRequired, Required, TypedDict
from typing_extensions import NotRequired, Required, TypedDict
from langgraph.graph.state import StateGraph, _get_node_name, _warn_invalid_state_schema
from langgraph.managed.shared_value import SharedValue
@@ -1,7 +1,7 @@
import json
import sys
import time
from typing import Any, Callable, Tuple, TypeVar
from typing import Any, Callable, TypeVar
from unittest.mock import MagicMock
import langsmith as ls
@@ -35,7 +35,7 @@ T = TypeVar("T")
def wait_for(
condition: Callable[[], Tuple[T, bool]],
condition: Callable[[], tuple[T, bool]],
max_sleep_time: int = 10,
sleep_time: int = 3,
) -> T:
+7 -8
View File
@@ -2,11 +2,10 @@ import functools
import sys
import uuid
from typing import (
Annotated,
Any,
Callable,
Dict,
ForwardRef,
List,
Literal,
Optional,
TypeVar,
@@ -16,7 +15,7 @@ from unittest.mock import patch
import langsmith
import pytest
from typing_extensions import Annotated, NotRequired, Required, TypedDict
from typing_extensions import NotRequired, Required, TypedDict
from langgraph.graph import END, StateGraph
from langgraph.graph.graph import CompiledGraph
@@ -150,9 +149,9 @@ def test_is_optional_type():
assert not _is_optional_type(int)
assert _is_optional_type(Optional[Literal[1, 2, 3]])
assert not _is_optional_type(Literal[1, 2, 3])
assert _is_optional_type(Optional[List[int]])
assert _is_optional_type(Optional[Dict[str, int]])
assert not _is_optional_type(List[Optional[int]])
assert _is_optional_type(Optional[list[int]])
assert _is_optional_type(Optional[dict[str, int]])
assert not _is_optional_type(list[Optional[int]])
assert _is_optional_type(Union[Optional[str], Optional[int]])
assert _is_optional_type(
Union[
@@ -177,8 +176,8 @@ def test_is_optional_type():
assert _is_optional_type(Optional[ForwardRef("MyClass")])
assert not _is_optional_type(ForwardRef("MyClass"))
assert _is_optional_type(Optional[Union[List[int], Dict[str, Optional[int]]]])
assert not _is_optional_type(Union[List[int], Dict[str, Optional[int]]])
assert _is_optional_type(Optional[Union[list[int], dict[str, Optional[int]]]])
assert not _is_optional_type(Union[list[int], dict[str, Optional[int]]])
assert _is_optional_type(Optional[Callable[[int], str]])
assert not _is_optional_type(Callable[[int], Optional[str]])