mirror of
https://github.com/langchain-ai/langgraph.git
synced 2026-08-26 01:22:24 +02:00
In Binop channels unwrap types before trying to instantiate
- this makes List/Sequence annotations behave same as list
This commit is contained in:
@@ -1,5 +1,15 @@
|
||||
import collections.abc
|
||||
from contextlib import contextmanager
|
||||
from typing import Callable, Generator, Generic, Optional, Sequence, Type
|
||||
from typing import (
|
||||
Callable,
|
||||
Generator,
|
||||
Generic,
|
||||
NotRequired,
|
||||
Optional,
|
||||
Required,
|
||||
Sequence,
|
||||
Type,
|
||||
)
|
||||
|
||||
from typing_extensions import Self
|
||||
|
||||
@@ -7,6 +17,17 @@ from langgraph.channels.base import BaseChannel, Value
|
||||
from langgraph.errors import EmptyChannelError
|
||||
|
||||
|
||||
# Adapted from typing_extensions
|
||||
def _strip_extras(t):
|
||||
"""Strips Annotated, Required and NotRequired from a given type."""
|
||||
if hasattr(t, "__origin__"):
|
||||
return _strip_extras(t.__origin__)
|
||||
if hasattr(t, "__origin__") and t.__origin__ in (Required, NotRequired):
|
||||
return _strip_extras(t.__args__[0])
|
||||
|
||||
return t
|
||||
|
||||
|
||||
class BinaryOperatorAggregate(Generic[Value], BaseChannel[Value, Value, Value]):
|
||||
"""Stores the result of applying a binary operator to the current value and each new value.
|
||||
|
||||
@@ -18,8 +39,18 @@ class BinaryOperatorAggregate(Generic[Value], BaseChannel[Value, Value, Value]):
|
||||
"""
|
||||
|
||||
def __init__(self, typ: Type[Value], operator: Callable[[Value, Value], Value]):
|
||||
self.typ = typ
|
||||
self.operator = operator
|
||||
# keep the type exposed by ValueType/UpdateType as-is
|
||||
self.typ = typ
|
||||
# special forms from typing or collections.abc are not instantiable
|
||||
# so we need to replace them with their concrete counterparts
|
||||
typ = _strip_extras(typ)
|
||||
if typ in (collections.abc.Sequence, collections.abc.MutableSequence):
|
||||
typ = list
|
||||
if typ in (collections.abc.Set, collections.abc.MutableSet):
|
||||
typ = set
|
||||
if typ in (collections.abc.Mapping, collections.abc.MutableMapping):
|
||||
typ = dict
|
||||
try:
|
||||
self.value = typ()
|
||||
except Exception:
|
||||
|
||||
@@ -10,6 +10,7 @@ from typing import (
|
||||
Any,
|
||||
Dict,
|
||||
Generator,
|
||||
List,
|
||||
Literal,
|
||||
Optional,
|
||||
Sequence,
|
||||
@@ -170,6 +171,70 @@ def test_graph_validation() -> None:
|
||||
graph.compile()
|
||||
|
||||
|
||||
def test_reducer_before_first_node() -> None:
|
||||
from langchain_core.messages import HumanMessage
|
||||
|
||||
class State(TypedDict):
|
||||
hello: str
|
||||
messages: Annotated[list[str], add_messages]
|
||||
|
||||
def node_a(state: State) -> State:
|
||||
assert state == {
|
||||
"hello": "there",
|
||||
"messages": [HumanMessage(content="hello", id=AnyStr())],
|
||||
}
|
||||
|
||||
builder = StateGraph(State)
|
||||
builder.add_node("a", node_a)
|
||||
builder.set_entry_point("a")
|
||||
builder.set_finish_point("a")
|
||||
graph = builder.compile()
|
||||
assert graph.invoke({"hello": "there", "messages": "hello"}) == {
|
||||
"hello": "there",
|
||||
"messages": [HumanMessage(content="hello", id=AnyStr())],
|
||||
}
|
||||
|
||||
class State(TypedDict):
|
||||
hello: str
|
||||
messages: Annotated[List[str], add_messages]
|
||||
|
||||
def node_a(state: State) -> State:
|
||||
assert state == {
|
||||
"hello": "there",
|
||||
"messages": [HumanMessage(content="hello", id=AnyStr())],
|
||||
}
|
||||
|
||||
builder = StateGraph(State)
|
||||
builder.add_node("a", node_a)
|
||||
builder.set_entry_point("a")
|
||||
builder.set_finish_point("a")
|
||||
graph = builder.compile()
|
||||
assert graph.invoke({"hello": "there", "messages": "hello"}) == {
|
||||
"hello": "there",
|
||||
"messages": [HumanMessage(content="hello", id=AnyStr())],
|
||||
}
|
||||
|
||||
class State(TypedDict):
|
||||
hello: str
|
||||
messages: Annotated[Sequence[str], add_messages]
|
||||
|
||||
def node_a(state: State) -> State:
|
||||
assert state == {
|
||||
"hello": "there",
|
||||
"messages": [HumanMessage(content="hello", id=AnyStr())],
|
||||
}
|
||||
|
||||
builder = StateGraph(State)
|
||||
builder.add_node("a", node_a)
|
||||
builder.set_entry_point("a")
|
||||
builder.set_finish_point("a")
|
||||
graph = builder.compile()
|
||||
assert graph.invoke({"hello": "there", "messages": "hello"}) == {
|
||||
"hello": "there",
|
||||
"messages": [HumanMessage(content="hello", id=AnyStr())],
|
||||
}
|
||||
|
||||
|
||||
def test_invoke_single_process_in_out(mocker: MockerFixture) -> None:
|
||||
add_one = mocker.Mock(side_effect=lambda x: x + 1)
|
||||
chain = Channel.subscribe_to("input") | add_one | Channel.write_to("output")
|
||||
|
||||
Reference in New Issue
Block a user