From 0c5c2e63705bd86df7e36a71c81729b545e62720 Mon Sep 17 00:00:00 2001 From: Nuno Campos Date: Sat, 19 Oct 2024 15:14:36 -0700 Subject: [PATCH 01/60] lib: Add support for graphs without edges - Return Control(update_state=, trigger=, send=) from your nodes instead - Annotate nodes with Control[Literal["destination"]] to see your graph connections drawn --- libs/langgraph/langgraph/constants.py | 2 + libs/langgraph/langgraph/graph/graph.py | 14 ++- libs/langgraph/langgraph/graph/state.py | 113 ++++++++++++++++------ libs/langgraph/langgraph/types.py | 18 ++++ libs/langgraph/tests/test_pregel.py | 2 +- libs/langgraph/tests/test_pregel_async.py | 61 +++++++++++- 6 files changed, 180 insertions(+), 30 deletions(-) diff --git a/libs/langgraph/langgraph/constants.py b/libs/langgraph/langgraph/constants.py index 36e375751..5d18b5262 100644 --- a/libs/langgraph/langgraph/constants.py +++ b/libs/langgraph/langgraph/constants.py @@ -20,6 +20,8 @@ START = sys.intern("__start__") """The first (maybe virtual) node in graph-style Pregel.""" END = sys.intern("__end__") """The last (maybe virtual) node in graph-style Pregel.""" +SELF = sys.intern("__self__") +"""The implicit branch that handles each node's Control values.""" # --- Reserved write keys --- INPUT = sys.intern("__input__") diff --git a/libs/langgraph/langgraph/graph/graph.py b/libs/langgraph/langgraph/graph/graph.py index e3d11c1eb..afe6604dd 100644 --- a/libs/langgraph/langgraph/graph/graph.py +++ b/libs/langgraph/langgraph/graph/graph.py @@ -27,6 +27,7 @@ from typing_extensions import Self from langgraph.channels.ephemeral_value import EphemeralValue from langgraph.constants import ( + EMPTY_SEQ, END, NS_END, NS_SEP, @@ -47,6 +48,7 @@ logger = logging.getLogger(__name__) class NodeSpec(NamedTuple): runnable: Runnable metadata: Optional[dict[str, Any]] = None + ends: Optional[tuple[str, ...]] = EMPTY_SEQ class Branch(NamedTuple): @@ -123,7 +125,7 @@ class Branch(NamedTuple): result: Any, config: RunnableConfig, ) -> Union[Runnable, Any]: - if not isinstance(result, list): + if not isinstance(result, (list, tuple)): result = [result] if self.ends: destinations: Sequence[Union[Send, str]] = [ @@ -364,6 +366,9 @@ class Graph: for node in self.nodes: if node != start and node != branch.then: all_sources.add(node) + for name, spec in self.nodes.items(): + if spec.ends: + all_sources.add(name) # validate sources for source in all_sources: if source not in self.nodes and source != START: @@ -387,6 +392,9 @@ class Graph: for node in self.nodes: if node != start and node != branch.then: all_targets.add(node) + for name, spec in self.nodes.items(): + if spec.ends: + all_targets.update(spec.ends) # validate targets for node in self.nodes: if node not in all_targets: @@ -620,5 +628,9 @@ class CompiledGraph(Pregel): ) if branch.then is not None: add_edge(end, branch.then) + for key, n in self.builder.nodes.items(): + if n.ends: + for end in n.ends: + add_edge(key, end, conditional=True) return graph diff --git a/libs/langgraph/langgraph/graph/state.py b/libs/langgraph/langgraph/graph/state.py index ad9a3edab..b64e54b21 100644 --- a/libs/langgraph/langgraph/graph/state.py +++ b/libs/langgraph/langgraph/graph/state.py @@ -14,6 +14,7 @@ from typing import ( Type, Union, cast, + get_args, get_origin, get_type_hints, overload, @@ -32,7 +33,7 @@ from langgraph.channels.dynamic_barrier_value import DynamicBarrierValue, WaitFo from langgraph.channels.ephemeral_value import EphemeralValue from langgraph.channels.last_value import LastValue from langgraph.channels.named_barrier_value import NamedBarrierValue -from langgraph.constants import NS_END, NS_SEP, TAG_HIDDEN +from langgraph.constants import EMPTY_SEQ, NS_END, NS_SEP, SELF, TAG_HIDDEN from langgraph.errors import ErrorCode, InvalidUpdateError, create_error_message from langgraph.graph.graph import END, START, Branch, CompiledGraph, Graph, Send from langgraph.managed.base import ( @@ -46,10 +47,10 @@ from langgraph.managed.base import ( from langgraph.pregel.read import ChannelRead, PregelNode from langgraph.pregel.write import SKIP_WRITE, ChannelWrite, ChannelWriteEntry from langgraph.store.base import BaseStore -from langgraph.types import All, Checkpointer, RetryPolicy +from langgraph.types import All, Checkpointer, Control, RetryPolicy from langgraph.utils.fields import get_field_default from langgraph.utils.pydantic import create_model -from langgraph.utils.runnable import coerce_to_runnable +from langgraph.utils.runnable import RunnableCallable, coerce_to_runnable logger = logging.getLogger(__name__) @@ -71,6 +72,7 @@ class StateNodeSpec(NamedTuple): metadata: Optional[dict[str, Any]] input: Type[Any] retry_policy: Optional[RetryPolicy] + ends: Optional[tuple[str, ...]] = EMPTY_SEQ class StateGraph(Graph): @@ -338,8 +340,29 @@ class StateGraph(Graph): f"'{character}' is a reserved character and is not allowed in the node names." ) - if input is None: - input = _get_input_schema_from_type_hint(action) + ends = EMPTY_SEQ + try: + if isfunction(action) and ( + hints := get_type_hints(getattr(action, "__call__")) + or get_type_hints(action) + ): + if input is None: + first_parameter_name = next( + iter(inspect.signature(action).parameters.keys()) + ) + if input_hint := hints.get(first_parameter_name): + if isinstance(input_hint, type) and get_type_hints(input_hint): + input = input_hint + if ( + (rtn := hints.get("return")) + and get_origin(rtn) is Control + and (rargs := get_args(rtn)) + and get_origin(rargs[0]) is Literal + and (vals := get_args(rargs[0])) + ): + ends = vals + except (TypeError, StopIteration): + pass if input is not None: self._add_schema(input) self.nodes[cast(str, node)] = StateNodeSpec( @@ -347,6 +370,7 @@ class StateGraph(Graph): metadata, input=input or self.schema, retry_policy=retry, + ends=ends, ) return self @@ -468,6 +492,9 @@ class StateGraph(Graph): for key, node in self.nodes.items(): compiled.attach_node(key, node) + for key, node in self.nodes.items(): + compiled.attach_branch(key, SELF, CONTROL_BRANCH, with_reader=False) + for start, end in self.edges: compiled.attach_edge(start, end) @@ -518,11 +545,23 @@ class CompiledStateGraph(CompiledGraph): if is_writable_managed_value(v) ] + def _get_root(input: Any) -> Any: + if isinstance(input, Control): + return input.update_state + else: + return input + def _get_state_key(input: Union[None, dict, Any], *, key: str) -> Any: if input is None: return SKIP_WRITE elif isinstance(input, dict): + if all(k not in output_keys for k in input): + raise InvalidUpdateError( + f"Expected node {key} to update at least one of {output_keys}, got {input}" + ) return input.get(key, SKIP_WRITE) + elif isinstance(input, Control): + return _get_state_key(input.update_state, key=key) elif get_type_hints(type(input)): value = getattr(input, key, SKIP_WRITE) return value if value is not None else SKIP_WRITE @@ -535,7 +574,7 @@ class CompiledStateGraph(CompiledGraph): # state updaters write_entries = ( - [ChannelWriteEntry("__root__", skip_none=True)] + [ChannelWriteEntry("__root__", skip_none=True, mapper=_get_root)] if output_keys == ["__root__"] else [ ChannelWriteEntry(key, mapper=partial(_get_state_key, key=key)) @@ -578,7 +617,6 @@ class CompiledStateGraph(CompiledGraph): ChannelWrite( [ChannelWriteEntry(key, key)] + write_entries, tags=[TAG_HIDDEN], - require_at_least_one_of=output_keys, ), ], metadata=node.metadata, @@ -615,7 +653,9 @@ class CompiledStateGraph(CompiledGraph): [ChannelWriteEntry(channel_name, start)], tags=[TAG_HIDDEN] ) - def attach_branch(self, start: str, name: str, branch: Branch) -> None: + def attach_branch( + self, start: str, name: str, branch: Branch, *, with_reader: bool = True + ) -> None: def branch_writer( packets: Sequence[Union[str, Send]], config: RunnableConfig ) -> None: @@ -648,7 +688,8 @@ class CompiledStateGraph(CompiledGraph): else self.builder.schema ) self.nodes[start] |= branch.run( - branch_writer, _get_state_reader(self.builder, schema) + branch_writer, + _get_state_reader(self.builder, schema) if with_reader else None, ) # attach branch subscribers @@ -697,6 +738,42 @@ def _coerce_state(schema: Type[Any], input: dict[str, Any]) -> dict[str, Any]: return schema(**input) +def _control_branch(value: Any) -> Sequence[Union[str, Send]]: + if not isinstance(value, Control): + return EMPTY_SEQ + rtn: list[Union[str, Send]] = [] + if isinstance(value.trigger, str): + rtn.append(value.trigger) + else: + rtn.extend(value.trigger) + if isinstance(value.send, Send): + rtn.append(value.send) + else: + rtn.extend(value.send) + return rtn + + +async def _acontrol_branch(value: Any) -> None: + if not isinstance(value, Control): + return EMPTY_SEQ + rtn: list[Union[str, Send]] = [] + if isinstance(value.trigger, str): + rtn.append(value.trigger) + else: + rtn.extend(value.trigger) + if isinstance(value.send, Send): + rtn.append(value.send) + else: + rtn.extend(value.send) + return rtn + + +CONTROL_BRANCH_PATH = RunnableCallable( + _control_branch, _acontrol_branch, tags=[TAG_HIDDEN], trace=False, recurse=False +) +CONTROL_BRANCH = Branch(CONTROL_BRANCH_PATH, None) + + def _get_channels( schema: Type[dict], ) -> tuple[dict[str, BaseChannel], dict[str, ManagedValueSpec]]: @@ -823,21 +900,3 @@ def _get_schema( if k in channels and isinstance(channels[k], BaseChannel) }, ) - - -def _get_input_schema_from_type_hint( - action: Optional[RunnableLike], -) -> Optional[Type[Any]]: - if not isfunction(action) and not ismethod(getattr(action, "__call__", None)): - return None - action = cast(Callable, action) - - try: - hints = get_type_hints(getattr(action, "__call__")) or get_type_hints(action) - first_parameter_name = next(iter(inspect.signature(action).parameters.keys())) - input_hint = hints.get(first_parameter_name) - if isinstance(input_hint, type) and get_type_hints(input_hint): - return input_hint - except (TypeError, StopIteration): - pass - return None diff --git a/libs/langgraph/langgraph/types.py b/libs/langgraph/langgraph/types.py index 279594dec..f7f330b83 100644 --- a/libs/langgraph/langgraph/types.py +++ b/libs/langgraph/langgraph/types.py @@ -4,11 +4,13 @@ from typing import ( TYPE_CHECKING, Any, Callable, + Generic, Literal, NamedTuple, Optional, Sequence, Type, + TypeVar, Union, cast, ) @@ -221,6 +223,22 @@ class Send: ) +N = TypeVar("N") + + +class Control(Generic[N]): + def __init__( + self, + *, + update_state: Optional[dict[str, Any]] = None, + trigger: Union[str, Sequence[str]] = (), + send: Union[Send, Sequence[Send]] = (), + ) -> None: + self.update_state = update_state + self.trigger = trigger + self.send = send + + StreamChunk = tuple[tuple[str, ...], str, Any] diff --git a/libs/langgraph/tests/test_pregel.py b/libs/langgraph/tests/test_pregel.py index 71b7f4a4f..13bc25236 100644 --- a/libs/langgraph/tests/test_pregel.py +++ b/libs/langgraph/tests/test_pregel.py @@ -6458,7 +6458,7 @@ def test_root_graph( ), AIMessage(content="answer", id="ai2"), AIMessage( - content="an extra message", id="00000000-0000-4000-8000-000000000091" + content="an extra message", id="00000000-0000-4000-8000-000000000092" ), HumanMessage(content="what is weather in la"), ], diff --git a/libs/langgraph/tests/test_pregel_async.py b/libs/langgraph/tests/test_pregel_async.py index 03b119d0d..fe4bee325 100644 --- a/libs/langgraph/tests/test_pregel_async.py +++ b/libs/langgraph/tests/test_pregel_async.py @@ -62,7 +62,7 @@ from langgraph.pregel import Channel, GraphRecursionError, Pregel, StateSnapshot from langgraph.pregel.retry import RetryPolicy from langgraph.store.base import BaseStore from langgraph.store.memory import InMemoryStore -from langgraph.types import Interrupt, PregelTask, Send, StreamWriter +from langgraph.types import Control, Interrupt, PregelTask, Send, StreamWriter from tests.any_str import AnyDict, AnyStr, AnyVersion, FloatBetween, UnsortedSequence from tests.conftest import ( ALL_CHECKPOINTERS_ASYNC, @@ -1969,6 +1969,65 @@ async def test_max_concurrency() -> None: assert node2.currently == 0 +async def test_max_concurrency_control() -> None: + async def node1(state) -> Control[Literal["2"]]: + return Control(update_state=["1"], send=[Send("2", state)] * 100) + + node2_currently = 0 + node2_max_currently = 0 + + async def node2(state) -> Control[Literal["3"]]: + nonlocal node2_currently, node2_max_currently + node2_currently += 1 + if node2_currently > node2_max_currently: + node2_max_currently = node2_currently + await asyncio.sleep(0.1) + node2_currently -= 1 + + return Control(update_state=["2"], trigger="3") + + async def node3(state) -> Literal["3"]: + return ["3"] + + builder = StateGraph(Annotated[list, operator.add]) + builder.add_node("1", node1) + builder.add_node("2", node2) + builder.add_node("3", node3) + builder.add_edge(START, "1") + graph = builder.compile() + + assert ( + graph.get_graph().draw_mermaid() + == """%%{init: {'flowchart': {'curve': 'linear'}}}%% +graph TD; + __start__([

__start__

]):::first + 1(1) + 2(2) + 3([3]):::last + __start__ --> 1; + 1 -.-> 2; + 2 -.-> 3; + classDef default fill:#f2f0ff,line-height:1.2 + classDef first fill-opacity:0 + classDef last fill:#bfb6fc +""" + ) + + assert await graph.ainvoke(["0"], debug=True) == ["0", "1", *(["2"] * 100), "3"] + assert node2_max_currently == 100 + assert node2_currently == 0 + node2_max_currently = 0 + + assert await graph.ainvoke(["0"], {"max_concurrency": 10}) == [ + "0", + "1", + *(["2"] * 100), + "3", + ] + assert node2_max_currently == 10 + assert node2_currently == 0 + + @pytest.mark.parametrize("checkpointer_name", ALL_CHECKPOINTERS_ASYNC) async def test_invoke_checkpoint_three( mocker: MockerFixture, checkpointer_name: str From 2624fc43dd73c106e7944b4e5a40579be389e4f9 Mon Sep 17 00:00:00 2001 From: Nuno Campos Date: Thu, 31 Oct 2024 09:40:24 -0700 Subject: [PATCH 02/60] Fix --- libs/langgraph/langgraph/graph/state.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libs/langgraph/langgraph/graph/state.py b/libs/langgraph/langgraph/graph/state.py index b64e54b21..1eba5f7de 100644 --- a/libs/langgraph/langgraph/graph/state.py +++ b/libs/langgraph/langgraph/graph/state.py @@ -342,7 +342,7 @@ class StateGraph(Graph): ends = EMPTY_SEQ try: - if isfunction(action) and ( + if (isfunction(action) or ismethod(getattr(action, "__call__", None))) and ( hints := get_type_hints(getattr(action, "__call__")) or get_type_hints(action) ): From e132dfc9553fc25801a95efa161de4940e01d53c Mon Sep 17 00:00:00 2001 From: Vadym Barda Date: Thu, 31 Oct 2024 13:43:08 -0400 Subject: [PATCH 03/60] docs: update FAQ (#2277) Sync with https://www.langchain.com/langgraph --- docs/docs/concepts/faq.md | 53 +++++++++++++++++++++++++++++++++++++-- 1 file changed, 51 insertions(+), 2 deletions(-) diff --git a/docs/docs/concepts/faq.md b/docs/docs/concepts/faq.md index 4ce51b98e..33457bdf8 100644 --- a/docs/docs/concepts/faq.md +++ b/docs/docs/concepts/faq.md @@ -2,9 +2,58 @@ Common questions and their answers! -## Do I need to use LangChain in order to use LangGraph? +## Do I need to use LangChain to use LangGraph? What’s the difference? -No! LangGraph is a general-purpose framework - the nodes and edges are nothing more than Python functions. You can use LangChain, raw HTTP requests, or even other frameworks inside these nodes and edges. +No. LangGraph is an orchestration framework for complex agentic systems and is more low-level and controllable than LangChain agents. LangChain provides a standard interface to interact with models and other components, useful for straight-forward chains and retrieval flows. + +## How is LangGraph different from other agent frameworks? + +Other agentic frameworks can work for simple, generic tasks but fall short for complex tasks bespoke to a company’s needs. LangGraph provides a more expressive framework to handle companies’ unique tasks without restricting users to a single black-box cognitive architecture. + +## Does LangGraph impact the performance of my app? + +LangGraph will not add any overhead to your code and is specifically designed with streaming workflows in mind. + +## Is LangGraph open source? Is it free? + +Yes. LangGraph is an MIT-licensed open-source library and is free to use. + +## How are LangGraph and LangGraph Platform different? + +LangGraph is a stateful, orchestration framework that brings added control to agent workflows. LangGraph Platform is a service for deploying and scaling LangGraph applications, with an opinionated API for building agent UXs, plus an integrated developer studio. + +| Features | LangGraph (open source) | LangGraph Platform | +|----------|------------------------|-------------------| +| Description | Stateful orchestration framework for agentic applications | Scalable infrastructure for deploying LangGraph applications | +| SDKs | Python and JavaScript | Python and JavaScript | +| HTTP APIs | None | Yes - useful for retrieving & updating state or long-term memory, or creating a configurable assistant | +| Streaming | Basic | Dedicated mode for token-by-token messages | +| Checkpointer | Community contributed | Supported out-of-the-box | +| Persistence Layer | Self-managed | Managed Postgres with efficient storage | +| Deployment | Self-managed | • Cloud SaaS
• Free self-hosted
• Enterprise (BYOC or paid self-hosted) | +| Scalability | Self-managed | Auto-scaling of task queues and servers | +| Fault-tolerance | Self-managed | Automated retries | +| Concurrency Control | Simple threading | Supports double-texting | +| Scheduling | None | Cron scheduling | +| Monitoring | None | Integrated with LangSmith for observability | +| IDE integration | LangGraph Studio for Desktop | LangGraph Studio for Desktop & Cloud | + +## What are my deployment options for LangGraph Platform? + +We currently have the following deployment options for LangGraph applications: + +- [‍Self-Hosted Lite](./deployment_options.md#self-hosted-lite): A free (up to 1M nodes executed), limited version of LangGraph Platform that you can run locally or in a self-hosted manner. This version requires a LangSmith API key and logs all usage to LangSmith. Fewer features are available than in paid plans. +- [Cloud SaaS](./deployment_options.md#cloud-saas): Fully managed and hosted as part of LangSmith, with automatic updates and zero maintenance. +- [‍Bring Your Own Cloud (BYOC)](./deployment_options.md#bring-your-own-cloud): Deploy LangGraph Platform within your VPC, provisioned and run as a service. Keep data in your environment while outsourcing the management of the service. +- [Self-Hosted Enterprise](./deployment_options.md#self-hosted-enterprise): Deploy LangGraph entirely on your own infrastructure. + +## Is LangGraph Platform open source? + +No. LangGraph Platform is proprietary software. + +There is a free, self-hosted version of LangGraph Platform with access to basic features. The Cloud SaaS deployment option is free while in beta, but will eventually be a paid service. We will always give ample notice before charging for a service and reward our early adopters with preferential pricing. The Bring Your Own Cloud (BYOC) and Self-Hosted Enterprise options are also paid services. [Contact our sales team](https://www.langchain.com/contact-sales) to learn more. + +For more information, see our [LangGraph Platform pricing page](https://www.langchain.com/pricing-langgraph-platform). ## Does LangGraph work with LLMs that don't support tool calling? From 4150abf18c359302082cbca4f8541c02a4c5fbd9 Mon Sep 17 00:00:00 2001 From: Eugene Yurtsev Date: Thu, 31 Oct 2024 14:23:27 -0400 Subject: [PATCH 04/60] docs: update reference layout (#2279) ![image](https://github.com/user-attachments/assets/62f1588c-d08d-474c-b512-a558759e7627) --- docs/mkdocs.yml | 20 +++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/docs/mkdocs.yml b/docs/mkdocs.yml index 2d760c1bb..757e02ce9 100644 --- a/docs/mkdocs.yml +++ b/docs/mkdocs.yml @@ -22,6 +22,7 @@ theme: - navigation.footer - navigation.indexes - navigation.instant + - navigation.sections - navigation.instant.prefetch - navigation.instant.progress - navigation.prune @@ -87,15 +88,16 @@ nav: - "How-to Guides": "how-tos/index.md" - Reference: - "reference/index.md" - - Graphs: reference/graphs.md - - RemoteGraph: reference/remote_graph.md - - Checkpointing: reference/checkpoints.md - - Storage: reference/store.md - - Prebuilt Components: reference/prebuilt.md - - Channels: reference/channels.md - - Errors: reference/errors.md - - Types: reference/types.md - - Constants: reference/constants.md + - Library: + - Graphs: reference/graphs.md + - RemoteGraph: reference/remote_graph.md + - Checkpointing: reference/checkpoints.md + - Storage: reference/store.md + - Prebuilt Components: reference/prebuilt.md + - Channels: reference/channels.md + - Errors: reference/errors.md + - Types: reference/types.md + - Constants: reference/constants.md - LangGraph Platform: - Server API: "cloud/reference/api/api_ref.md" - SDK (Python): "cloud/reference/sdk/python_sdk_ref.md" From ed91572ceb608bf5fe9fc13febb225173a33cb40 Mon Sep 17 00:00:00 2001 From: Vadym Barda Date: Thu, 31 Oct 2024 14:34:16 -0400 Subject: [PATCH 05/60] docs: expose how-to on rebuilding graph at runtime (#2281) --- docs/docs/how-tos/index.md | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/docs/docs/how-tos/index.md b/docs/docs/how-tos/index.md index 3b9ccbcf5..c19007b2c 100644 --- a/docs/docs/how-tos/index.md +++ b/docs/docs/how-tos/index.md @@ -90,9 +90,9 @@ These how-to guides show common patterns for tool calling with LangGraph: ### State Management -- [Use Pydantic model as state](state-model.ipynb) -- [Have a separate input and output schema](input_output_schema.ipynb) -- [Pass private state between nodes inside the graph](pass_private_state.ipynb) +- [How to use Pydantic model as state](state-model.ipynb) +- [How to define input/output schema for your graph](input_output_schema.ipynb) +- [How to pass private state between nodes inside the graph](pass_private_state.ipynb) ### Other @@ -141,7 +141,8 @@ Learn how to set up your app for deployment to LangGraph Platform: - [How to set up app for deployment (JavaScript)](../cloud/deployment/setup_javascript.md) - [How to customize Dockerfile](../cloud/deployment/custom_docker.md) - [How to test locally](../cloud/deployment/test_locally.md) - +- [How to rebuild graph at runtime](../cloud/deployment/graph_rebuild.md) + ### Deployment LangGraph applications can be deployed using LangGraph Cloud, which provides a range of services to help you deploy, manage, and scale your applications. From 8b36013a030f6534bb24a7a73e09f29d769d64da Mon Sep 17 00:00:00 2001 From: Eugene Yurtsev Date: Thu, 31 Oct 2024 14:48:21 -0400 Subject: [PATCH 06/60] Fix bad tab in how-to guide (#2278) --- docs/docs/cloud/deployment/test_locally.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/docs/cloud/deployment/test_locally.md b/docs/docs/cloud/deployment/test_locally.md index 3277f8314..b68caaa29 100644 --- a/docs/docs/cloud/deployment/test_locally.md +++ b/docs/docs/cloud/deployment/test_locally.md @@ -13,7 +13,7 @@ Install the proper packages: ```bash pip install -U langgraph-cli ``` -=== "Homebrew" (macOS only) +=== "Homebrew (macOS only)" ```bash brew install langgraph-cli ``` @@ -189,4 +189,4 @@ Now we can invoke our graph to ensure it is working. Make sure to change the inp ' ``` -If your graph works correctly, you should see your graph output displayed in the console. Of course, there are many more ways you might need to test your graph, for a full list of commands you can send with the SDK, see the [Python](https://langchain-ai.github.io/langgraph/cloud/reference/sdk/python_sdk_ref/) and [JS/TS](https://langchain-ai.github.io/langgraph/cloud/reference/sdk/js_ts_sdk_ref/) references. \ No newline at end of file +If your graph works correctly, you should see your graph output displayed in the console. Of course, there are many more ways you might need to test your graph, for a full list of commands you can send with the SDK, see the [Python](https://langchain-ai.github.io/langgraph/cloud/reference/sdk/python_sdk_ref/) and [JS/TS](https://langchain-ai.github.io/langgraph/cloud/reference/sdk/js_ts_sdk_ref/) references. From 4b1d30906ebe25331bba96fb65d192642743baa1 Mon Sep 17 00:00:00 2001 From: Eugene Yurtsev Date: Thu, 31 Oct 2024 14:48:32 -0400 Subject: [PATCH 07/60] ci: update deploy docs workflow to check external links on langchain.com (#2280) Reverts temporary change that was made yesterday to accommodate a link that was only available on the staging version of langchain.com --- .github/workflows/deploy_docs.yml | 2 -- 1 file changed, 2 deletions(-) diff --git a/.github/workflows/deploy_docs.yml b/.github/workflows/deploy_docs.yml index 426cdaa3c..1852d9896 100644 --- a/.github/workflows/deploy_docs.yml +++ b/.github/workflows/deploy_docs.yml @@ -82,7 +82,6 @@ jobs: --check-links-ignore "https://github\.com/.*" \ --check-links-ignore "/.*\.(ipynb|html)$" \ --check-links-ignore "https://python\.langchain\.com/.*" \ - --check-links-ignore "https://www.langchain\.com/.*" \ --check-links-ignore "https://openai.com/index/memory-and-new-controls-for-chatgpt/" \ --check-links $(find docs/site -name "index.html" | grep -v 'storm/index.html') @@ -97,7 +96,6 @@ jobs: poetry run pytest -v \ --check-links-ignore "https://(api|web|docs)\.smith\.langchain\.com/.*" \ --check-links-ignore "https://x.com/.*" \ - --check-links-ignore "https://www\.langchain\.com/.*" \ --check-links-ignore "https://github\.com/.*" \ --check-links-ignore "/.*\.(ipynb|html)$" \ --check-links ${CHANGED_FILES} \ From 18e71469e1d33115d1a21fe3954ce5e6b4562be2 Mon Sep 17 00:00:00 2001 From: Nuno Campos Date: Thu, 31 Oct 2024 12:57:00 -0700 Subject: [PATCH 08/60] Lint --- libs/langgraph/langgraph/graph/state.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/libs/langgraph/langgraph/graph/state.py b/libs/langgraph/langgraph/graph/state.py index 1eba5f7de..fc429cb60 100644 --- a/libs/langgraph/langgraph/graph/state.py +++ b/libs/langgraph/langgraph/graph/state.py @@ -4,6 +4,7 @@ import typing import warnings from functools import partial from inspect import isclass, isfunction, ismethod, signature +from types import FunctionType from typing import ( Any, Callable, @@ -348,7 +349,11 @@ class StateGraph(Graph): ): if input is None: first_parameter_name = next( - iter(inspect.signature(action).parameters.keys()) + iter( + inspect.signature( + cast(FunctionType, action) + ).parameters.keys() + ) ) if input_hint := hints.get(first_parameter_name): if isinstance(input_hint, type) and get_type_hints(input_hint): From ea012fb7dc7800adbdf4f50ffce87b21294780ff Mon Sep 17 00:00:00 2001 From: Eugene Yurtsev Date: Thu, 31 Oct 2024 16:25:58 -0400 Subject: [PATCH 09/60] docs: reorder reference (#2282) --- docs/mkdocs.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/mkdocs.yml b/docs/mkdocs.yml index 757e02ce9..e2813b141 100644 --- a/docs/mkdocs.yml +++ b/docs/mkdocs.yml @@ -90,7 +90,6 @@ nav: - "reference/index.md" - Library: - Graphs: reference/graphs.md - - RemoteGraph: reference/remote_graph.md - Checkpointing: reference/checkpoints.md - Storage: reference/store.md - Prebuilt Components: reference/prebuilt.md @@ -100,9 +99,10 @@ nav: - Constants: reference/constants.md - LangGraph Platform: - Server API: "cloud/reference/api/api_ref.md" + - CLI: "cloud/reference/cli.md" - SDK (Python): "cloud/reference/sdk/python_sdk_ref.md" - SDK (JS/TS): "cloud/reference/sdk/js_ts_sdk_ref.md" - - CLI: "cloud/reference/cli.md" + - RemoteGraph: reference/remote_graph.md - Environment Variables: "cloud/reference/env_var.md" markdown_extensions: From c28dc26356aa8ad9ce0b47a28543cc59e9604759 Mon Sep 17 00:00:00 2001 From: Vadym Barda Date: Thu, 31 Oct 2024 16:27:07 -0400 Subject: [PATCH 10/60] docs: fix typo (#2283) --- docs/docs/how-tos/deploy-self-hosted.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/docs/how-tos/deploy-self-hosted.md b/docs/docs/how-tos/deploy-self-hosted.md index c1f71b97f..995dc6787 100644 --- a/docs/docs/how-tos/deploy-self-hosted.md +++ b/docs/docs/how-tos/deploy-self-hosted.md @@ -14,7 +14,7 @@ With the self-hosted deployment option, you are responsible for managing the inf You will need to do the following: 1. Deploy Redis and Postgres instances on your own infrastructure. -2. Build a docker image with the [LangGraph Sever](../concepts/langgraph_server.md) using the [LangGraph CLI](../concepts/langgraph_cli.md). +2. Build a docker image with the [LangGraph Server](../concepts/langgraph_server.md) using the [LangGraph CLI](../concepts/langgraph_cli.md). 3. Deploy a web server that will run the docker image and pass in the necessary environment variables. ## Environment Variables From e2de59c6fbd1a045e5d6da074c477dd69395af70 Mon Sep 17 00:00:00 2001 From: Bagatur <22008038+baskaryan@users.noreply.github.com> Date: Thu, 31 Oct 2024 14:53:03 -0700 Subject: [PATCH 11/60] langgraph[patch]: bump core ^0.3.15 || ^ 0.2.43 (#2285) --- libs/langgraph/poetry.lock | 216 +++++++++++++++++----------------- libs/langgraph/pyproject.toml | 2 +- 2 files changed, 109 insertions(+), 109 deletions(-) diff --git a/libs/langgraph/poetry.lock b/libs/langgraph/poetry.lock index cc8797279..db2b60544 100644 --- a/libs/langgraph/poetry.lock +++ b/libs/langgraph/poetry.lock @@ -1336,13 +1336,13 @@ files = [ [[package]] name = "langchain-core" -version = "0.3.14" +version = "0.3.15" description = "Building applications with LLMs through composability" optional = false python-versions = "<4.0,>=3.9" files = [ - {file = "langchain_core-0.3.14-py3-none-any.whl", hash = "sha256:3b1e031820ef60af7134ffb7fd15393c5be87031b4288cc1978130f4f468dca8"}, - {file = "langchain_core-0.3.14.tar.gz", hash = "sha256:cef68958d59d0970a89c778004dfa7d4559a89139abbf8b0e348e18511792e84"}, + {file = "langchain_core-0.3.15-py3-none-any.whl", hash = "sha256:3d4ca6dbb8ed396a6ee061063832a2451b0ce8c345570f7b086ffa7288e4fa29"}, + {file = "langchain_core-0.3.15.tar.gz", hash = "sha256:b1a29787a4ffb7ec2103b4e97d435287201da7809b369740dd1e32f176325aba"}, ] [package.dependencies] @@ -2829,114 +2829,114 @@ files = [ [[package]] name = "rpds-py" -version = "0.20.0" +version = "0.20.1" description = "Python bindings to Rust's persistent data structures (rpds)" optional = false python-versions = ">=3.8" files = [ - {file = "rpds_py-0.20.0-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:3ad0fda1635f8439cde85c700f964b23ed5fc2d28016b32b9ee5fe30da5c84e2"}, - {file = "rpds_py-0.20.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:9bb4a0d90fdb03437c109a17eade42dfbf6190408f29b2744114d11586611d6f"}, - {file = "rpds_py-0.20.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c6377e647bbfd0a0b159fe557f2c6c602c159fc752fa316572f012fc0bf67150"}, - {file = "rpds_py-0.20.0-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:eb851b7df9dda52dc1415ebee12362047ce771fc36914586b2e9fcbd7d293b3e"}, - {file = "rpds_py-0.20.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1e0f80b739e5a8f54837be5d5c924483996b603d5502bfff79bf33da06164ee2"}, - {file = "rpds_py-0.20.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5a8c94dad2e45324fc74dce25e1645d4d14df9a4e54a30fa0ae8bad9a63928e3"}, - {file = "rpds_py-0.20.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f8e604fe73ba048c06085beaf51147eaec7df856824bfe7b98657cf436623daf"}, - {file = "rpds_py-0.20.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:df3de6b7726b52966edf29663e57306b23ef775faf0ac01a3e9f4012a24a4140"}, - {file = "rpds_py-0.20.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:cf258ede5bc22a45c8e726b29835b9303c285ab46fc7c3a4cc770736b5304c9f"}, - {file = "rpds_py-0.20.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:55fea87029cded5df854ca7e192ec7bdb7ecd1d9a3f63d5c4eb09148acf4a7ce"}, - {file = "rpds_py-0.20.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:ae94bd0b2f02c28e199e9bc51485d0c5601f58780636185660f86bf80c89af94"}, - {file = "rpds_py-0.20.0-cp310-none-win32.whl", hash = "sha256:28527c685f237c05445efec62426d285e47a58fb05ba0090a4340b73ecda6dee"}, - {file = "rpds_py-0.20.0-cp310-none-win_amd64.whl", hash = "sha256:238a2d5b1cad28cdc6ed15faf93a998336eb041c4e440dd7f902528b8891b399"}, - {file = "rpds_py-0.20.0-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:ac2f4f7a98934c2ed6505aead07b979e6f999389f16b714448fb39bbaa86a489"}, - {file = "rpds_py-0.20.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:220002c1b846db9afd83371d08d239fdc865e8f8c5795bbaec20916a76db3318"}, - {file = "rpds_py-0.20.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8d7919548df3f25374a1f5d01fbcd38dacab338ef5f33e044744b5c36729c8db"}, - {file = "rpds_py-0.20.0-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:758406267907b3781beee0f0edfe4a179fbd97c0be2e9b1154d7f0a1279cf8e5"}, - {file = "rpds_py-0.20.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3d61339e9f84a3f0767b1995adfb171a0d00a1185192718a17af6e124728e0f5"}, - {file = "rpds_py-0.20.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1259c7b3705ac0a0bd38197565a5d603218591d3f6cee6e614e380b6ba61c6f6"}, - {file = "rpds_py-0.20.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5c1dc0f53856b9cc9a0ccca0a7cc61d3d20a7088201c0937f3f4048c1718a209"}, - {file = "rpds_py-0.20.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:7e60cb630f674a31f0368ed32b2a6b4331b8350d67de53c0359992444b116dd3"}, - {file = "rpds_py-0.20.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:dbe982f38565bb50cb7fb061ebf762c2f254ca3d8c20d4006878766e84266272"}, - {file = "rpds_py-0.20.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:514b3293b64187172bc77c8fb0cdae26981618021053b30d8371c3a902d4d5ad"}, - {file = "rpds_py-0.20.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:d0a26ffe9d4dd35e4dfdd1e71f46401cff0181c75ac174711ccff0459135fa58"}, - {file = "rpds_py-0.20.0-cp311-none-win32.whl", hash = "sha256:89c19a494bf3ad08c1da49445cc5d13d8fefc265f48ee7e7556839acdacf69d0"}, - {file = "rpds_py-0.20.0-cp311-none-win_amd64.whl", hash = "sha256:c638144ce971df84650d3ed0096e2ae7af8e62ecbbb7b201c8935c370df00a2c"}, - {file = "rpds_py-0.20.0-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:a84ab91cbe7aab97f7446652d0ed37d35b68a465aeef8fc41932a9d7eee2c1a6"}, - {file = "rpds_py-0.20.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:56e27147a5a4c2c21633ff8475d185734c0e4befd1c989b5b95a5d0db699b21b"}, - {file = "rpds_py-0.20.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2580b0c34583b85efec8c5c5ec9edf2dfe817330cc882ee972ae650e7b5ef739"}, - {file = "rpds_py-0.20.0-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:b80d4a7900cf6b66bb9cee5c352b2d708e29e5a37fe9bf784fa97fc11504bf6c"}, - {file = "rpds_py-0.20.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:50eccbf054e62a7b2209b28dc7a22d6254860209d6753e6b78cfaeb0075d7bee"}, - {file = "rpds_py-0.20.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:49a8063ea4296b3a7e81a5dfb8f7b2d73f0b1c20c2af401fb0cdf22e14711a96"}, - {file = "rpds_py-0.20.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ea438162a9fcbee3ecf36c23e6c68237479f89f962f82dae83dc15feeceb37e4"}, - {file = "rpds_py-0.20.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:18d7585c463087bddcfa74c2ba267339f14f2515158ac4db30b1f9cbdb62c8ef"}, - {file = "rpds_py-0.20.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:d4c7d1a051eeb39f5c9547e82ea27cbcc28338482242e3e0b7768033cb083821"}, - {file = "rpds_py-0.20.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:e4df1e3b3bec320790f699890d41c59d250f6beda159ea3c44c3f5bac1976940"}, - {file = "rpds_py-0.20.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:2cf126d33a91ee6eedc7f3197b53e87a2acdac63602c0f03a02dd69e4b138174"}, - {file = "rpds_py-0.20.0-cp312-none-win32.whl", hash = "sha256:8bc7690f7caee50b04a79bf017a8d020c1f48c2a1077ffe172abec59870f1139"}, - {file = "rpds_py-0.20.0-cp312-none-win_amd64.whl", hash = "sha256:0e13e6952ef264c40587d510ad676a988df19adea20444c2b295e536457bc585"}, - {file = "rpds_py-0.20.0-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:aa9a0521aeca7d4941499a73ad7d4f8ffa3d1affc50b9ea11d992cd7eff18a29"}, - {file = "rpds_py-0.20.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:4a1f1d51eccb7e6c32ae89243cb352389228ea62f89cd80823ea7dd1b98e0b91"}, - {file = "rpds_py-0.20.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8a86a9b96070674fc88b6f9f71a97d2c1d3e5165574615d1f9168ecba4cecb24"}, - {file = "rpds_py-0.20.0-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:6c8ef2ebf76df43f5750b46851ed1cdf8f109d7787ca40035fe19fbdc1acc5a7"}, - {file = "rpds_py-0.20.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b74b25f024b421d5859d156750ea9a65651793d51b76a2e9238c05c9d5f203a9"}, - {file = "rpds_py-0.20.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:57eb94a8c16ab08fef6404301c38318e2c5a32216bf5de453e2714c964c125c8"}, - {file = "rpds_py-0.20.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e1940dae14e715e2e02dfd5b0f64a52e8374a517a1e531ad9412319dc3ac7879"}, - {file = "rpds_py-0.20.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:d20277fd62e1b992a50c43f13fbe13277a31f8c9f70d59759c88f644d66c619f"}, - {file = "rpds_py-0.20.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:06db23d43f26478303e954c34c75182356ca9aa7797d22c5345b16871ab9c45c"}, - {file = "rpds_py-0.20.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:b2a5db5397d82fa847e4c624b0c98fe59d2d9b7cf0ce6de09e4d2e80f8f5b3f2"}, - {file = "rpds_py-0.20.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:5a35df9f5548fd79cb2f52d27182108c3e6641a4feb0f39067911bf2adaa3e57"}, - {file = "rpds_py-0.20.0-cp313-none-win32.whl", hash = "sha256:fd2d84f40633bc475ef2d5490b9c19543fbf18596dcb1b291e3a12ea5d722f7a"}, - {file = "rpds_py-0.20.0-cp313-none-win_amd64.whl", hash = "sha256:9bc2d153989e3216b0559251b0c260cfd168ec78b1fac33dd485750a228db5a2"}, - {file = "rpds_py-0.20.0-cp38-cp38-macosx_10_12_x86_64.whl", hash = "sha256:f2fbf7db2012d4876fb0d66b5b9ba6591197b0f165db8d99371d976546472a24"}, - {file = "rpds_py-0.20.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:1e5f3cd7397c8f86c8cc72d5a791071431c108edd79872cdd96e00abd8497d29"}, - {file = "rpds_py-0.20.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ce9845054c13696f7af7f2b353e6b4f676dab1b4b215d7fe5e05c6f8bb06f965"}, - {file = "rpds_py-0.20.0-cp38-cp38-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:c3e130fd0ec56cb76eb49ef52faead8ff09d13f4527e9b0c400307ff72b408e1"}, - {file = "rpds_py-0.20.0-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4b16aa0107ecb512b568244ef461f27697164d9a68d8b35090e9b0c1c8b27752"}, - {file = "rpds_py-0.20.0-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:aa7f429242aae2947246587d2964fad750b79e8c233a2367f71b554e9447949c"}, - {file = "rpds_py-0.20.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:af0fc424a5842a11e28956e69395fbbeab2c97c42253169d87e90aac2886d751"}, - {file = "rpds_py-0.20.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:b8c00a3b1e70c1d3891f0db1b05292747f0dbcfb49c43f9244d04c70fbc40eb8"}, - {file = "rpds_py-0.20.0-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:40ce74fc86ee4645d0a225498d091d8bc61f39b709ebef8204cb8b5a464d3c0e"}, - {file = "rpds_py-0.20.0-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:4fe84294c7019456e56d93e8ababdad5a329cd25975be749c3f5f558abb48253"}, - {file = "rpds_py-0.20.0-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:338ca4539aad4ce70a656e5187a3a31c5204f261aef9f6ab50e50bcdffaf050a"}, - {file = "rpds_py-0.20.0-cp38-none-win32.whl", hash = "sha256:54b43a2b07db18314669092bb2de584524d1ef414588780261e31e85846c26a5"}, - {file = "rpds_py-0.20.0-cp38-none-win_amd64.whl", hash = "sha256:a1862d2d7ce1674cffa6d186d53ca95c6e17ed2b06b3f4c476173565c862d232"}, - {file = "rpds_py-0.20.0-cp39-cp39-macosx_10_12_x86_64.whl", hash = "sha256:3fde368e9140312b6e8b6c09fb9f8c8c2f00999d1823403ae90cc00480221b22"}, - {file = "rpds_py-0.20.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:9824fb430c9cf9af743cf7aaf6707bf14323fb51ee74425c380f4c846ea70789"}, - {file = "rpds_py-0.20.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:11ef6ce74616342888b69878d45e9f779b95d4bd48b382a229fe624a409b72c5"}, - {file = "rpds_py-0.20.0-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:c52d3f2f82b763a24ef52f5d24358553e8403ce05f893b5347098014f2d9eff2"}, - {file = "rpds_py-0.20.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9d35cef91e59ebbeaa45214861874bc6f19eb35de96db73e467a8358d701a96c"}, - {file = "rpds_py-0.20.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d72278a30111e5b5525c1dd96120d9e958464316f55adb030433ea905866f4de"}, - {file = "rpds_py-0.20.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b4c29cbbba378759ac5786730d1c3cb4ec6f8ababf5c42a9ce303dc4b3d08cda"}, - {file = "rpds_py-0.20.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:6632f2d04f15d1bd6fe0eedd3b86d9061b836ddca4c03d5cf5c7e9e6b7c14580"}, - {file = "rpds_py-0.20.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:d0b67d87bb45ed1cd020e8fbf2307d449b68abc45402fe1a4ac9e46c3c8b192b"}, - {file = "rpds_py-0.20.0-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:ec31a99ca63bf3cd7f1a5ac9fe95c5e2d060d3c768a09bc1d16e235840861420"}, - {file = "rpds_py-0.20.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:22e6c9976e38f4d8c4a63bd8a8edac5307dffd3ee7e6026d97f3cc3a2dc02a0b"}, - {file = "rpds_py-0.20.0-cp39-none-win32.whl", hash = "sha256:569b3ea770c2717b730b61998b6c54996adee3cef69fc28d444f3e7920313cf7"}, - {file = "rpds_py-0.20.0-cp39-none-win_amd64.whl", hash = "sha256:e6900ecdd50ce0facf703f7a00df12374b74bbc8ad9fe0f6559947fb20f82364"}, - {file = "rpds_py-0.20.0-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:617c7357272c67696fd052811e352ac54ed1d9b49ab370261a80d3b6ce385045"}, - {file = "rpds_py-0.20.0-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:9426133526f69fcaba6e42146b4e12d6bc6c839b8b555097020e2b78ce908dcc"}, - {file = "rpds_py-0.20.0-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:deb62214c42a261cb3eb04d474f7155279c1a8a8c30ac89b7dcb1721d92c3c02"}, - {file = "rpds_py-0.20.0-pp310-pypy310_pp73-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:fcaeb7b57f1a1e071ebd748984359fef83ecb026325b9d4ca847c95bc7311c92"}, - {file = "rpds_py-0.20.0-pp310-pypy310_pp73-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d454b8749b4bd70dd0a79f428731ee263fa6995f83ccb8bada706e8d1d3ff89d"}, - {file = "rpds_py-0.20.0-pp310-pypy310_pp73-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d807dc2051abe041b6649681dce568f8e10668e3c1c6543ebae58f2d7e617855"}, - {file = "rpds_py-0.20.0-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c3c20f0ddeb6e29126d45f89206b8291352b8c5b44384e78a6499d68b52ae511"}, - {file = "rpds_py-0.20.0-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:b7f19250ceef892adf27f0399b9e5afad019288e9be756d6919cb58892129f51"}, - {file = "rpds_py-0.20.0-pp310-pypy310_pp73-musllinux_1_2_aarch64.whl", hash = "sha256:4f1ed4749a08379555cebf4650453f14452eaa9c43d0a95c49db50c18b7da075"}, - {file = "rpds_py-0.20.0-pp310-pypy310_pp73-musllinux_1_2_i686.whl", hash = "sha256:dcedf0b42bcb4cfff4101d7771a10532415a6106062f005ab97d1d0ab5681c60"}, - {file = "rpds_py-0.20.0-pp310-pypy310_pp73-musllinux_1_2_x86_64.whl", hash = "sha256:39ed0d010457a78f54090fafb5d108501b5aa5604cc22408fc1c0c77eac14344"}, - {file = "rpds_py-0.20.0-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:bb273176be34a746bdac0b0d7e4e2c467323d13640b736c4c477881a3220a989"}, - {file = "rpds_py-0.20.0-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:f918a1a130a6dfe1d7fe0f105064141342e7dd1611f2e6a21cd2f5c8cb1cfb3e"}, - {file = "rpds_py-0.20.0-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:f60012a73aa396be721558caa3a6fd49b3dd0033d1675c6d59c4502e870fcf0c"}, - {file = "rpds_py-0.20.0-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3d2b1ad682a3dfda2a4e8ad8572f3100f95fad98cb99faf37ff0ddfe9cbf9d03"}, - {file = "rpds_py-0.20.0-pp39-pypy39_pp73-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:614fdafe9f5f19c63ea02817fa4861c606a59a604a77c8cdef5aa01d28b97921"}, - {file = "rpds_py-0.20.0-pp39-pypy39_pp73-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:fa518bcd7600c584bf42e6617ee8132869e877db2f76bcdc281ec6a4113a53ab"}, - {file = "rpds_py-0.20.0-pp39-pypy39_pp73-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f0475242f447cc6cb8a9dd486d68b2ef7fbee84427124c232bff5f63b1fe11e5"}, - {file = "rpds_py-0.20.0-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f90a4cd061914a60bd51c68bcb4357086991bd0bb93d8aa66a6da7701370708f"}, - {file = "rpds_py-0.20.0-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:def7400461c3a3f26e49078302e1c1b38f6752342c77e3cf72ce91ca69fb1bc1"}, - {file = "rpds_py-0.20.0-pp39-pypy39_pp73-musllinux_1_2_aarch64.whl", hash = "sha256:65794e4048ee837494aea3c21a28ad5fc080994dfba5b036cf84de37f7ad5074"}, - {file = "rpds_py-0.20.0-pp39-pypy39_pp73-musllinux_1_2_i686.whl", hash = "sha256:faefcc78f53a88f3076b7f8be0a8f8d35133a3ecf7f3770895c25f8813460f08"}, - {file = "rpds_py-0.20.0-pp39-pypy39_pp73-musllinux_1_2_x86_64.whl", hash = "sha256:5b4f105deeffa28bbcdff6c49b34e74903139afa690e35d2d9e3c2c2fba18cec"}, - {file = "rpds_py-0.20.0-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:fdfc3a892927458d98f3d55428ae46b921d1f7543b89382fdb483f5640daaec8"}, - {file = "rpds_py-0.20.0.tar.gz", hash = "sha256:d72a210824facfdaf8768cf2d7ca25a042c30320b3020de2fa04640920d4e121"}, + {file = "rpds_py-0.20.1-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:a649dfd735fff086e8a9d0503a9f0c7d01b7912a333c7ae77e1515c08c146dad"}, + {file = "rpds_py-0.20.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:f16bc1334853e91ddaaa1217045dd7be166170beec337576818461268a3de67f"}, + {file = "rpds_py-0.20.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:14511a539afee6f9ab492b543060c7491c99924314977a55c98bfa2ee29ce78c"}, + {file = "rpds_py-0.20.1-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:3ccb8ac2d3c71cda472b75af42818981bdacf48d2e21c36331b50b4f16930163"}, + {file = "rpds_py-0.20.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c142b88039b92e7e0cb2552e8967077e3179b22359e945574f5e2764c3953dcf"}, + {file = "rpds_py-0.20.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f19169781dddae7478a32301b499b2858bc52fc45a112955e798ee307e294977"}, + {file = "rpds_py-0.20.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:13c56de6518e14b9bf6edde23c4c39dac5b48dcf04160ea7bce8fca8397cdf86"}, + {file = "rpds_py-0.20.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:925d176a549f4832c6f69fa6026071294ab5910e82a0fe6c6228fce17b0706bd"}, + {file = "rpds_py-0.20.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:78f0b6877bfce7a3d1ff150391354a410c55d3cdce386f862926a4958ad5ab7e"}, + {file = "rpds_py-0.20.1-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:3dd645e2b0dcb0fd05bf58e2e54c13875847687d0b71941ad2e757e5d89d4356"}, + {file = "rpds_py-0.20.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:4f676e21db2f8c72ff0936f895271e7a700aa1f8d31b40e4e43442ba94973899"}, + {file = "rpds_py-0.20.1-cp310-none-win32.whl", hash = "sha256:648386ddd1e19b4a6abab69139b002bc49ebf065b596119f8f37c38e9ecee8ff"}, + {file = "rpds_py-0.20.1-cp310-none-win_amd64.whl", hash = "sha256:d9ecb51120de61e4604650666d1f2b68444d46ae18fd492245a08f53ad2b7711"}, + {file = "rpds_py-0.20.1-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:762703bdd2b30983c1d9e62b4c88664df4a8a4d5ec0e9253b0231171f18f6d75"}, + {file = "rpds_py-0.20.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:0b581f47257a9fce535c4567782a8976002d6b8afa2c39ff616edf87cbeff712"}, + {file = "rpds_py-0.20.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:842c19a6ce894493563c3bd00d81d5100e8e57d70209e84d5491940fdb8b9e3a"}, + {file = "rpds_py-0.20.1-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:42cbde7789f5c0bcd6816cb29808e36c01b960fb5d29f11e052215aa85497c93"}, + {file = "rpds_py-0.20.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:6c8e9340ce5a52f95fa7d3b552b35c7e8f3874d74a03a8a69279fd5fca5dc751"}, + {file = "rpds_py-0.20.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8ba6f89cac95c0900d932c9efb7f0fb6ca47f6687feec41abcb1bd5e2bd45535"}, + {file = "rpds_py-0.20.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4a916087371afd9648e1962e67403c53f9c49ca47b9680adbeef79da3a7811b0"}, + {file = "rpds_py-0.20.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:200a23239781f46149e6a415f1e870c5ef1e712939fe8fa63035cd053ac2638e"}, + {file = "rpds_py-0.20.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:58b1d5dd591973d426cbb2da5e27ba0339209832b2f3315928c9790e13f159e8"}, + {file = "rpds_py-0.20.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:6b73c67850ca7cae0f6c56f71e356d7e9fa25958d3e18a64927c2d930859b8e4"}, + {file = "rpds_py-0.20.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:d8761c3c891cc51e90bc9926d6d2f59b27beaf86c74622c8979380a29cc23ac3"}, + {file = "rpds_py-0.20.1-cp311-none-win32.whl", hash = "sha256:cd945871335a639275eee904caef90041568ce3b42f402c6959b460d25ae8732"}, + {file = "rpds_py-0.20.1-cp311-none-win_amd64.whl", hash = "sha256:7e21b7031e17c6b0e445f42ccc77f79a97e2687023c5746bfb7a9e45e0921b84"}, + {file = "rpds_py-0.20.1-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:36785be22066966a27348444b40389f8444671630063edfb1a2eb04318721e17"}, + {file = "rpds_py-0.20.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:142c0a5124d9bd0e2976089484af5c74f47bd3298f2ed651ef54ea728d2ea42c"}, + {file = "rpds_py-0.20.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:dbddc10776ca7ebf2a299c41a4dde8ea0d8e3547bfd731cb87af2e8f5bf8962d"}, + {file = "rpds_py-0.20.1-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:15a842bb369e00295392e7ce192de9dcbf136954614124a667f9f9f17d6a216f"}, + {file = "rpds_py-0.20.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:be5ef2f1fc586a7372bfc355986226484e06d1dc4f9402539872c8bb99e34b01"}, + {file = "rpds_py-0.20.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:dbcf360c9e3399b056a238523146ea77eeb2a596ce263b8814c900263e46031a"}, + {file = "rpds_py-0.20.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ecd27a66740ffd621d20b9a2f2b5ee4129a56e27bfb9458a3bcc2e45794c96cb"}, + {file = "rpds_py-0.20.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:d0b937b2a1988f184a3e9e577adaa8aede21ec0b38320d6009e02bd026db04fa"}, + {file = "rpds_py-0.20.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:6889469bfdc1eddf489729b471303739bf04555bb151fe8875931f8564309afc"}, + {file = "rpds_py-0.20.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:19b73643c802f4eaf13d97f7855d0fb527fbc92ab7013c4ad0e13a6ae0ed23bd"}, + {file = "rpds_py-0.20.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:3c6afcf2338e7f374e8edc765c79fbcb4061d02b15dd5f8f314a4af2bdc7feb5"}, + {file = "rpds_py-0.20.1-cp312-none-win32.whl", hash = "sha256:dc73505153798c6f74854aba69cc75953888cf9866465196889c7cdd351e720c"}, + {file = "rpds_py-0.20.1-cp312-none-win_amd64.whl", hash = "sha256:8bbe951244a838a51289ee53a6bae3a07f26d4e179b96fc7ddd3301caf0518eb"}, + {file = "rpds_py-0.20.1-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:6ca91093a4a8da4afae7fe6a222c3b53ee4eef433ebfee4d54978a103435159e"}, + {file = "rpds_py-0.20.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:b9c2fe36d1f758b28121bef29ed1dee9b7a2453e997528e7d1ac99b94892527c"}, + {file = "rpds_py-0.20.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f009c69bc8c53db5dfab72ac760895dc1f2bc1b62ab7408b253c8d1ec52459fc"}, + {file = "rpds_py-0.20.1-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:6740a3e8d43a32629bb9b009017ea5b9e713b7210ba48ac8d4cb6d99d86c8ee8"}, + {file = "rpds_py-0.20.1-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:32b922e13d4c0080d03e7b62991ad7f5007d9cd74e239c4b16bc85ae8b70252d"}, + {file = "rpds_py-0.20.1-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:fe00a9057d100e69b4ae4a094203a708d65b0f345ed546fdef86498bf5390982"}, + {file = "rpds_py-0.20.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:49fe9b04b6fa685bd39237d45fad89ba19e9163a1ccaa16611a812e682913496"}, + {file = "rpds_py-0.20.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:aa7ac11e294304e615b43f8c441fee5d40094275ed7311f3420d805fde9b07b4"}, + {file = "rpds_py-0.20.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:6aa97af1558a9bef4025f8f5d8c60d712e0a3b13a2fe875511defc6ee77a1ab7"}, + {file = "rpds_py-0.20.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:483b29f6f7ffa6af845107d4efe2e3fa8fb2693de8657bc1849f674296ff6a5a"}, + {file = "rpds_py-0.20.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:37fe0f12aebb6a0e3e17bb4cd356b1286d2d18d2e93b2d39fe647138458b4bcb"}, + {file = "rpds_py-0.20.1-cp313-none-win32.whl", hash = "sha256:a624cc00ef2158e04188df5e3016385b9353638139a06fb77057b3498f794782"}, + {file = "rpds_py-0.20.1-cp313-none-win_amd64.whl", hash = "sha256:b71b8666eeea69d6363248822078c075bac6ed135faa9216aa85f295ff009b1e"}, + {file = "rpds_py-0.20.1-cp38-cp38-macosx_10_12_x86_64.whl", hash = "sha256:5b48e790e0355865197ad0aca8cde3d8ede347831e1959e158369eb3493d2191"}, + {file = "rpds_py-0.20.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:3e310838a5801795207c66c73ea903deda321e6146d6f282e85fa7e3e4854804"}, + {file = "rpds_py-0.20.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2249280b870e6a42c0d972339e9cc22ee98730a99cd7f2f727549af80dd5a963"}, + {file = "rpds_py-0.20.1-cp38-cp38-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:e79059d67bea28b53d255c1437b25391653263f0e69cd7dec170d778fdbca95e"}, + {file = "rpds_py-0.20.1-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:2b431c777c9653e569986ecf69ff4a5dba281cded16043d348bf9ba505486f36"}, + {file = "rpds_py-0.20.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:da584ff96ec95e97925174eb8237e32f626e7a1a97888cdd27ee2f1f24dd0ad8"}, + {file = "rpds_py-0.20.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:02a0629ec053fc013808a85178524e3cb63a61dbc35b22499870194a63578fb9"}, + {file = "rpds_py-0.20.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:fbf15aff64a163db29a91ed0868af181d6f68ec1a3a7d5afcfe4501252840bad"}, + {file = "rpds_py-0.20.1-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:07924c1b938798797d60c6308fa8ad3b3f0201802f82e4a2c41bb3fafb44cc28"}, + {file = "rpds_py-0.20.1-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:4a5a844f68776a7715ecb30843b453f07ac89bad393431efbf7accca3ef599c1"}, + {file = "rpds_py-0.20.1-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:518d2ca43c358929bf08f9079b617f1c2ca6e8848f83c1225c88caeac46e6cbc"}, + {file = "rpds_py-0.20.1-cp38-none-win32.whl", hash = "sha256:3aea7eed3e55119635a74bbeb80b35e776bafccb70d97e8ff838816c124539f1"}, + {file = "rpds_py-0.20.1-cp38-none-win_amd64.whl", hash = "sha256:7dca7081e9a0c3b6490a145593f6fe3173a94197f2cb9891183ef75e9d64c425"}, + {file = "rpds_py-0.20.1-cp39-cp39-macosx_10_12_x86_64.whl", hash = "sha256:b41b6321805c472f66990c2849e152aff7bc359eb92f781e3f606609eac877ad"}, + {file = "rpds_py-0.20.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:0a90c373ea2975519b58dece25853dbcb9779b05cc46b4819cb1917e3b3215b6"}, + {file = "rpds_py-0.20.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:16d4477bcb9fbbd7b5b0e4a5d9b493e42026c0bf1f06f723a9353f5153e75d30"}, + {file = "rpds_py-0.20.1-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:84b8382a90539910b53a6307f7c35697bc7e6ffb25d9c1d4e998a13e842a5e83"}, + {file = "rpds_py-0.20.1-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4888e117dd41b9d34194d9e31631af70d3d526efc363085e3089ab1a62c32ed1"}, + {file = "rpds_py-0.20.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5265505b3d61a0f56618c9b941dc54dc334dc6e660f1592d112cd103d914a6db"}, + {file = "rpds_py-0.20.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e75ba609dba23f2c95b776efb9dd3f0b78a76a151e96f96cc5b6b1b0004de66f"}, + {file = "rpds_py-0.20.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:1791ff70bc975b098fe6ecf04356a10e9e2bd7dc21fa7351c1742fdeb9b4966f"}, + {file = "rpds_py-0.20.1-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:d126b52e4a473d40232ec2052a8b232270ed1f8c9571aaf33f73a14cc298c24f"}, + {file = "rpds_py-0.20.1-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:c14937af98c4cc362a1d4374806204dd51b1e12dded1ae30645c298e5a5c4cb1"}, + {file = "rpds_py-0.20.1-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:3d089d0b88996df627693639d123c8158cff41c0651f646cd8fd292c7da90eaf"}, + {file = "rpds_py-0.20.1-cp39-none-win32.whl", hash = "sha256:653647b8838cf83b2e7e6a0364f49af96deec64d2a6578324db58380cff82aca"}, + {file = "rpds_py-0.20.1-cp39-none-win_amd64.whl", hash = "sha256:fa41a64ac5b08b292906e248549ab48b69c5428f3987b09689ab2441f267d04d"}, + {file = "rpds_py-0.20.1-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:7a07ced2b22f0cf0b55a6a510078174c31b6d8544f3bc00c2bcee52b3d613f74"}, + {file = "rpds_py-0.20.1-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:68cb0a499f2c4a088fd2f521453e22ed3527154136a855c62e148b7883b99f9a"}, + {file = "rpds_py-0.20.1-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fa3060d885657abc549b2a0f8e1b79699290e5d83845141717c6c90c2df38311"}, + {file = "rpds_py-0.20.1-pp310-pypy310_pp73-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:95f3b65d2392e1c5cec27cff08fdc0080270d5a1a4b2ea1d51d5f4a2620ff08d"}, + {file = "rpds_py-0.20.1-pp310-pypy310_pp73-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:2cc3712a4b0b76a1d45a9302dd2f53ff339614b1c29603a911318f2357b04dd2"}, + {file = "rpds_py-0.20.1-pp310-pypy310_pp73-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5d4eea0761e37485c9b81400437adb11c40e13ef513375bbd6973e34100aeb06"}, + {file = "rpds_py-0.20.1-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7f5179583d7a6cdb981151dd349786cbc318bab54963a192692d945dd3f6435d"}, + {file = "rpds_py-0.20.1-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:2fbb0ffc754490aff6dabbf28064be47f0f9ca0b9755976f945214965b3ace7e"}, + {file = "rpds_py-0.20.1-pp310-pypy310_pp73-musllinux_1_2_aarch64.whl", hash = "sha256:a94e52537a0e0a85429eda9e49f272ada715506d3b2431f64b8a3e34eb5f3e75"}, + {file = "rpds_py-0.20.1-pp310-pypy310_pp73-musllinux_1_2_i686.whl", hash = "sha256:92b68b79c0da2a980b1c4197e56ac3dd0c8a149b4603747c4378914a68706979"}, + {file = "rpds_py-0.20.1-pp310-pypy310_pp73-musllinux_1_2_x86_64.whl", hash = "sha256:93da1d3db08a827eda74356f9f58884adb254e59b6664f64cc04cdff2cc19b0d"}, + {file = "rpds_py-0.20.1-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:754bbed1a4ca48479e9d4182a561d001bbf81543876cdded6f695ec3d465846b"}, + {file = "rpds_py-0.20.1-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:ca449520e7484534a2a44faf629362cae62b660601432d04c482283c47eaebab"}, + {file = "rpds_py-0.20.1-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:9c4cb04a16b0f199a8c9bf807269b2f63b7b5b11425e4a6bd44bd6961d28282c"}, + {file = "rpds_py-0.20.1-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bb63804105143c7e24cee7db89e37cb3f3941f8e80c4379a0b355c52a52b6780"}, + {file = "rpds_py-0.20.1-pp39-pypy39_pp73-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:55cd1fa4ecfa6d9f14fbd97ac24803e6f73e897c738f771a9fe038f2f11ff07c"}, + {file = "rpds_py-0.20.1-pp39-pypy39_pp73-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:0f8f741b6292c86059ed175d80eefa80997125b7c478fb8769fd9ac8943a16c0"}, + {file = "rpds_py-0.20.1-pp39-pypy39_pp73-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0fc212779bf8411667234b3cdd34d53de6c2b8b8b958e1e12cb473a5f367c338"}, + {file = "rpds_py-0.20.1-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0ad56edabcdb428c2e33bbf24f255fe2b43253b7d13a2cdbf05de955217313e6"}, + {file = "rpds_py-0.20.1-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:0a3a1e9ee9728b2c1734f65d6a1d376c6f2f6fdcc13bb007a08cc4b1ff576dc5"}, + {file = "rpds_py-0.20.1-pp39-pypy39_pp73-musllinux_1_2_aarch64.whl", hash = "sha256:e13de156137b7095442b288e72f33503a469aa1980ed856b43c353ac86390519"}, + {file = "rpds_py-0.20.1-pp39-pypy39_pp73-musllinux_1_2_i686.whl", hash = "sha256:07f59760ef99f31422c49038964b31c4dfcfeb5d2384ebfc71058a7c9adae2d2"}, + {file = "rpds_py-0.20.1-pp39-pypy39_pp73-musllinux_1_2_x86_64.whl", hash = "sha256:59240685e7da61fb78f65a9f07f8108e36a83317c53f7b276b4175dc44151684"}, + {file = "rpds_py-0.20.1-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:83cba698cfb3c2c5a7c3c6bac12fe6c6a51aae69513726be6411076185a8b24a"}, + {file = "rpds_py-0.20.1.tar.gz", hash = "sha256:e1791c4aabd117653530dccd24108fa03cc6baf21f58b950d0a73c3b3b29a350"}, ] [[package]] @@ -3425,4 +3425,4 @@ type = ["pytest-mypy"] [metadata] lock-version = "2.0" python-versions = ">=3.9.0,<4.0" -content-hash = "6ee10fe34b2f65b33184c95d3713962afe4ca7923fbcca9b85fe6168555cff43" +content-hash = "f0b7800f90041227ba4d7b72482c70fabdbacc95e8a3a784fdebc88721728a91" diff --git a/libs/langgraph/pyproject.toml b/libs/langgraph/pyproject.toml index f1a5aef18..903efd6c0 100644 --- a/libs/langgraph/pyproject.toml +++ b/libs/langgraph/pyproject.toml @@ -9,7 +9,7 @@ repository = "https://www.github.com/langchain-ai/langgraph" [tool.poetry.dependencies] python = ">=3.9.0,<4.0" -langchain-core = ">=0.2.42,<0.4.0,!=0.3.0,!=0.3.1,!=0.3.2,!=0.3.3,!=0.3.4,!=0.3.5,!=0.3.6,!=0.3.7,!=0.3.8,!=0.3.9,!=0.3.10,!=0.3.11,!=0.3.12,!=0.3.13" +langchain-core = ">=0.2.43,<0.4.0,!=0.3.0,!=0.3.1,!=0.3.2,!=0.3.3,!=0.3.4,!=0.3.5,!=0.3.6,!=0.3.7,!=0.3.8,!=0.3.9,!=0.3.10,!=0.3.11,!=0.3.12,!=0.3.13,!=0.3.14" langgraph-checkpoint = "^2.0.0" langgraph-sdk = "^0.1.32" From 7bacb8c984ef08df9a12de8a92249fd289f71b73 Mon Sep 17 00:00:00 2001 From: Vadym Barda Date: Thu, 31 Oct 2024 17:57:21 -0400 Subject: [PATCH 12/60] langgraph: release 0.2.43 (#2286) --- libs/langgraph/pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libs/langgraph/pyproject.toml b/libs/langgraph/pyproject.toml index 903efd6c0..dd6d9c64a 100644 --- a/libs/langgraph/pyproject.toml +++ b/libs/langgraph/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "langgraph" -version = "0.2.42" +version = "0.2.43" description = "Building stateful, multi-actor applications with LLMs" authors = [] license = "MIT" From 66cf2e6779509e147b7e9ec449b19dac9336be1f Mon Sep 17 00:00:00 2001 From: William FH <13333726+hinthornw@users.noreply.github.com> Date: Thu, 31 Oct 2024 21:27:13 -0700 Subject: [PATCH 13/60] Add Tree of Thoughts (#1128) --- docs/docs/tutorials/index.md | 3 +- docs/docs/tutorials/tot/img/tot.png | Bin 0 -> 37886 bytes docs/docs/tutorials/tot/tot.ipynb | 527 ++++++++++++++++++++++++++++ 3 files changed, 529 insertions(+), 1 deletion(-) create mode 100644 docs/docs/tutorials/tot/img/tot.png create mode 100644 docs/docs/tutorials/tot/tot.ipynb diff --git a/docs/docs/tutorials/index.md b/docs/docs/tutorials/index.md index 0c5687fd4..887740e6a 100644 --- a/docs/docs/tutorials/index.md +++ b/docs/docs/tutorials/index.md @@ -56,7 +56,8 @@ Learn from example implementations of graphs designed for specific scenarios and - [Basic Reflection](reflection/reflection.ipynb): Prompt the agent to reflect on and revise its outputs - [Reflexion](reflexion/reflexion.ipynb): Critique missing and superfluous details to guide next steps -- [Language Agent Tree Search](lats/lats.ipynb): Use reflection and rewards to drive a tree search over agents +- [Tree of Thoughts](tot/tot.ipynb): Search over candidate solutions to a problem using a scored tree +- [Language Agent Tree Search](lats/lats.ipynb): Use reflection and rewards to drive a monte-carlo tree search over agents - [Self-Discover Agent](self-discover/self-discover.ipynb): Analyze an agent that learns about its own capabilities ### Evaluation diff --git a/docs/docs/tutorials/tot/img/tot.png b/docs/docs/tutorials/tot/img/tot.png new file mode 100644 index 0000000000000000000000000000000000000000..519937d11d1f49fca9cad960439f52ace4a99c92 GIT binary patch literal 37886 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 P00000NkvXXu0mjf@4XE( literal 0 HcmV?d00001 diff --git a/docs/docs/tutorials/tot/tot.ipynb b/docs/docs/tutorials/tot/tot.ipynb new file mode 100644 index 000000000..29fe435bc --- /dev/null +++ b/docs/docs/tutorials/tot/tot.ipynb @@ -0,0 +1,527 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Tree of Thoughts\n", + "\n", + "[Tree of Thoughts](https://arxiv.org/abs/2305.10601) (ToT), by Yao, et. al, is a general LLM agent search algorithm that combines reflection/evaluation and simple search (in this case BFS, though you can apply DFS or other algorithms if you'd like).\n", + "\n", + "![LATS diagram](./img/tot.png)\n", + "\n", + "It has three main steps:\n", + "\n", + "1. Expand: generate 1 or more candidate solutions to the problem.\n", + "2. Score: measure the quality of the responses.\n", + "3. Prune: retain the top K best candidates\n", + "\n", + "Then return to \"Expand\" if no solution is found (or if the solution is of insufficient quality).\n", + "\n", + "\n", + "## Prerequisites\n", + "\n", + "We'll install the tutorial's dependent packages and set our API key for the LLM provider of choice." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "%%capture --no-stderr\n", + "%pip install -U langgraph langchain-openai" + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "metadata": {}, + "outputs": [], + "source": [ + "import getpass\n", + "import os\n", + "\n", + "\n", + "def _set_env(var: str):\n", + " if not os.environ.get(var):\n", + " os.environ[var] = getpass.getpass(f\"{var}: \")\n", + "\n", + "\n", + "_set_env(\"OPENAI_API_KEY\")\n", + "# To visualize the algorithm\n", + "trace = True\n", + "if trace:\n", + " _set_env(\"LANGSMITH_API_KEY\")\n", + " os.environ[\"LANGSMITH_PROJECT\"] = \"ToT Tutorial\"" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Task Definition\n", + "\n", + "Our agent will try to play the \"Game of 24\". Given 4 numbers, it must generate a math equation that uses each of these numbers exactly one time to evaluate to a value of `24`." + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "metadata": {}, + "outputs": [], + "source": [ + "import operator\n", + "from typing import List, Literal, Union, NamedTuple, Optional\n", + "from pydantic import BaseModel, Field\n", + "\n", + "OperatorType = Literal[\"+\", \"-\", \"*\", \"/\"]\n", + "TokenType = Union[float, OperatorType]\n", + "\n", + "## We use these schemas to prompt the LLM to generate equations that evaluate to 24.\n", + "\n", + "\n", + "class Equation(BaseModel):\n", + " \"\"\"The formula combining the provided numbers to reach the target of 24.\"\"\"\n", + "\n", + " tokens: List[TokenType] = Field(\n", + " description=\"The stack of tokens and operators in reverse-polish notation. Example: [3, 4, '+', -1, '*'] would evaluate to (3 + 4) * -1 = -7.\",\n", + " )\n", + "\n", + " def compute(self) -> float:\n", + " op_funcs = {\n", + " \"+\": operator.add,\n", + " \"-\": operator.sub,\n", + " \"*\": operator.mul,\n", + " \"/\": operator.truediv,\n", + " }\n", + " stack = []\n", + " for token in self.tokens:\n", + " if isinstance(token, float):\n", + " stack.append(token)\n", + " else:\n", + " b, a = stack.pop(), stack.pop()\n", + " stack.append(op_funcs[token](a, b))\n", + "\n", + " return stack[0]\n", + "\n", + "\n", + "class GuessEquations(BaseModel):\n", + " \"\"\"Submit multiple equations as guesses.\"\"\"\n", + "\n", + " reasoning: str = Field(\n", + " description=\"The reasoning behind the submitted guesses. Explain how you arrived at these equations.\"\n", + " )\n", + "\n", + " equations: List[Equation] = Field(\n", + " description=\"The list of equations to submit as guesses.\"\n", + " )\n", + "\n", + "\n", + "## These objects will represent a single \"candidate\" (or scored candidate) within our agent's state.\n", + "# You can update the candidate object to match your own task.\n", + "\n", + "\n", + "class Candidate(NamedTuple):\n", + " candidate: Equation\n", + " score: Optional[float] = None\n", + " feedback: Optional[str] = None\n", + "\n", + " def __str__(self):\n", + " try:\n", + " computed = self.candidate.compute()\n", + " except Exception as e:\n", + " computed = f\"Invalid equation: {self.candidate.tokens}; Error: {repr(e)}\"\n", + "\n", + " return f\"Equation({self.candidate.tokens}) = {computed} (Reward: {self.score})\"\n", + "\n", + "\n", + "class ScoredCandidate(Candidate):\n", + " candidate: Equation\n", + " score: float\n", + " feedback: str" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "#### Fetch data\n", + "\n", + "We'll use an example from the [Game of 24](https://github.com/princeton-nlp/tree-of-thought-llm) dataset." + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Example puzzles: ['1 1 4 6', '1 1 11 11', '1 1 3 8']\n" + ] + } + ], + "source": [ + "import requests\n", + "import csv\n", + "\n", + "csv_data = requests.get(\n", + " \"https://storage.googleapis.com/benchmarks-artifacts/game-of-24/24.csv\"\n", + ").content.decode(\"utf-8\")\n", + "# Get just the Puzzles column (column index 1)\n", + "puzzles = [row[1].strip() for row in csv.reader(csv_data.splitlines()[1:])]\n", + "\n", + "print(f\"Example puzzles: {puzzles[:3]}\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Expander\n", + "\n", + "The \"tree of thoughts\" algorithm is relatively generic. The primary two task-specific components are the **expander** and the **scorer**.\n", + "The expander (the augmented LLM) tries to generate 1 or more solutions to the problem. On subsequent attempts, it is given a seed/candidate value from \n", + "the previous search.\n", + "\n", + "You can update this section to match your own task requirements. The expander can be arbitrarily complex. All that's required is that it accepts the problem and an optional previous attempt (or attempts) and returns a new result." + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "metadata": {}, + "outputs": [], + "source": [ + "from langchain_core.prompts import ChatPromptTemplate\n", + "from langchain_openai import ChatOpenAI\n", + "\n", + "\n", + "prompt = ChatPromptTemplate.from_messages(\n", + " [\n", + " (\n", + " \"system\",\n", + " \"You are playing the Game of 24. Using the provide numbers, create an equation that evaluates to 24.\\n\"\n", + " \"Submit exactly {k} guesses for this round.\",\n", + " ),\n", + " (\"user\", \"Solve the 24 game for these numbers: {problem}.{candidate}\"),\n", + " ],\n", + ").partial(candidate=\"\")\n", + "llm = ChatOpenAI(model=\"gpt-4o-mini\")\n", + "\n", + "bound_llm = llm.with_structured_output(GuessEquations)\n", + "solver = prompt | bound_llm" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Scorer\n", + "\n", + "In this game, the scorer is easy. We need to assert two things:\n", + "\n", + "1. The LLM has generated a valid equation using each number exactly one time.\n", + "2. The equation evaluates to 24.\n", + "\n", + "You can update this function to match your own task requirements." + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "metadata": {}, + "outputs": [], + "source": [ + "def compute_score(problem: str, candidate: Candidate) -> ScoredCandidate:\n", + " numbers = list(map(int, problem.split()))\n", + " # Check that the candidate equation uses all 4 numbers exactly once\n", + " used_numbers = [\n", + " token for token in candidate.candidate.tokens if isinstance(token, float)\n", + " ]\n", + " if sorted(used_numbers) != sorted(numbers):\n", + " score = 0\n", + " feedback = \"The equation must use all 4 numbers exactly once.\"\n", + " return ScoredCandidate(\n", + " candidate=candidate.candidate, score=score, feedback=feedback\n", + " )\n", + " try:\n", + " result = candidate.candidate.compute()\n", + " score = 1 / (1 + abs(24 - result))\n", + " feedback = f\"Result: {result}\"\n", + " except Exception as e:\n", + " score = 0\n", + " feedback = f\"Invalid equation. Error: {repr(e)}\"\n", + " return ScoredCandidate(\n", + " candidate=candidate.candidate, score=score, feedback=feedback\n", + " )" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Graph\n", + "\n", + "Now it's time to create our graph." + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "metadata": {}, + "outputs": [], + "source": [ + "import operator\n", + "from typing import Optional, Dict, Any\n", + "from typing_extensions import Annotated, TypedDict\n", + "from langgraph.graph import StateGraph\n", + "\n", + "from langchain_core.runnables import RunnableConfig\n", + "from langgraph.constants import Send\n", + "from langgraph.checkpoint.memory import MemorySaver\n", + "\n", + "\n", + "def update_candidates(\n", + " existing: Optional[list] = None,\n", + " updates: Optional[Union[list, Literal[\"clear\"]]] = None,\n", + ") -> List[str]:\n", + " if existing is None:\n", + " existing = []\n", + " if updates is None:\n", + " return existing\n", + " if updates == \"clear\":\n", + " return []\n", + " # Concatenate the lists\n", + " return existing + updates\n", + "\n", + "\n", + "class ToTState(TypedDict):\n", + " problem: str\n", + " candidates: Annotated[List[Candidate], update_candidates]\n", + " scored_candidates: Annotated[List[ScoredCandidate], update_candidates]\n", + " depth: Annotated[int, operator.add]\n", + "\n", + "\n", + "class Configuration(TypedDict, total=False):\n", + " max_depth: int\n", + " threshold: float\n", + " k: int\n", + " beam_size: int\n", + "\n", + "\n", + "def _ensure_configurable(config: RunnableConfig) -> Configuration:\n", + " \"\"\"Get params that configure the search algorithm.\"\"\"\n", + " configurable = config.get(\"configurable\", {})\n", + " return {\n", + " **configurable,\n", + " \"max_depth\": configurable.get(\"max_depth\", 10),\n", + " \"threshold\": config.get(\"threshold\", 0.9),\n", + " \"k\": configurable.get(\"k\", 5),\n", + " \"beam_size\": configurable.get(\"beam_size\", 3),\n", + " }\n", + "\n", + "\n", + "class ExpansionState(ToTState):\n", + " seed: Optional[Candidate]\n", + "\n", + "\n", + "def expand(state: ExpansionState, *, config: RunnableConfig) -> Dict[str, List[str]]:\n", + " \"\"\"Generate the next state.\"\"\"\n", + " configurable = _ensure_configurable(config)\n", + " if not state.get(\"seed\"):\n", + " candidate_str = \"\"\n", + " else:\n", + " candidate_str = \"\\n\\n\" + str(state[\"seed\"])\n", + " try:\n", + " equation_submission = solver.invoke(\n", + " {\n", + " \"problem\": state[\"problem\"],\n", + " \"candidate\": candidate_str,\n", + " \"k\": configurable[\"k\"],\n", + " },\n", + " config=config,\n", + " )\n", + " except Exception:\n", + " return {\"candidates\": []}\n", + " new_candidates = [\n", + " Candidate(candidate=equation) for equation in equation_submission.equations\n", + " ]\n", + " return {\"candidates\": new_candidates}\n", + "\n", + "\n", + "def score(state: ToTState) -> Dict[str, List[float]]:\n", + " \"\"\"Evaluate the candidate generations.\"\"\"\n", + " candidates = state[\"candidates\"]\n", + " scored = []\n", + " for candidate in candidates:\n", + " scored.append(compute_score(state[\"problem\"], candidate))\n", + " return {\"scored_candidates\": scored, \"candidates\": \"clear\"}\n", + "\n", + "\n", + "def prune(\n", + " state: ToTState, *, config: RunnableConfig\n", + ") -> Dict[str, List[Dict[str, Any]]]:\n", + " scored_candidates = state[\"scored_candidates\"]\n", + " beam_size = _ensure_configurable(config)[\"beam_size\"]\n", + " organized = sorted(\n", + " scored_candidates, key=lambda candidate: candidate[1], reverse=True\n", + " )\n", + " pruned = organized[:beam_size]\n", + " return {\n", + " # Update the starting point for the next iteration\n", + " \"candidates\": pruned,\n", + " # Clear the old memory\n", + " \"scored_candidates\": \"clear\",\n", + " # Increment the depth by 1\n", + " \"depth\": 1,\n", + " }\n", + "\n", + "\n", + "def should_terminate(\n", + " state: ToTState, config: RunnableConfig\n", + ") -> Union[Literal[\"__end__\"], Send]:\n", + " configurable = _ensure_configurable(config)\n", + " solved = state[\"candidates\"][0].score >= configurable[\"threshold\"]\n", + " if solved or state[\"depth\"] >= configurable[\"max_depth\"]:\n", + " return \"__end__\"\n", + " return [\n", + " Send(\"expand\", {**state, \"somevalseed\": candidate})\n", + " for candidate in state[\"candidates\"]\n", + " ]\n", + "\n", + "\n", + "# Create the graph\n", + "builder = StateGraph(state_schema=ToTState, config_schema=Configuration)\n", + "\n", + "# Add nodes\n", + "builder.add_node(expand)\n", + "builder.add_node(score)\n", + "builder.add_node(prune)\n", + "\n", + "# Add edges\n", + "builder.add_edge(\"expand\", \"score\")\n", + "builder.add_edge(\"score\", \"prune\")\n", + "builder.add_conditional_edges(\"prune\", should_terminate, path_map=[\"expand\", \"__end__\"])\n", + "\n", + "# Set entry point\n", + "builder.add_edge(\"__start__\", \"expand\")\n", + "\n", + "# Compile the graph\n", + "graph = builder.compile(checkpointer=MemorySaver())" + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "metadata": {}, + "outputs": [ + { + "data": { + "image/jpeg": "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", + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "from IPython.display import Image, display\n", + "\n", + "display(Image(graph.get_graph().draw_mermaid_png()))" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Run\n", + "\n", + "Now let's try it on one of the puzzles!" + ] + }, + { + "cell_type": "code", + "execution_count": 10, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "{'expand': {'candidates': [Candidate(candidate=Equation(tokens=[12.0, 5.0, '/', 7.0, '*']), score=None, feedback=None), Candidate(candidate=Equation(tokens=[12.0, 1.0, '+', 5.0, '*']), score=None, feedback=None), Candidate(candidate=Equation(tokens=[12.0, 7.0, '*', 1.0, '/']), score=None, feedback=None), Candidate(candidate=Equation(tokens=[5.0, 7.0, '*', 1.0, '*']), score=None, feedback=None), Candidate(candidate=Equation(tokens=[7.0, 5.0, '*', 12.0, '/']), score=None, feedback=None)]}}\n", + "{'score': {'candidates': 'clear', 'scored_candidates': [ScoredCandidate(candidate=Equation(tokens=[12.0, 5.0, '/', 7.0, '*']), score=0, feedback='The equation must use all 4 numbers exactly once.'), ScoredCandidate(candidate=Equation(tokens=[12.0, 1.0, '+', 5.0, '*']), score=0, feedback='The equation must use all 4 numbers exactly once.'), ScoredCandidate(candidate=Equation(tokens=[12.0, 7.0, '*', 1.0, '/']), score=0, feedback='The equation must use all 4 numbers exactly once.'), ScoredCandidate(candidate=Equation(tokens=[5.0, 7.0, '*', 1.0, '*']), score=0, feedback='The equation must use all 4 numbers exactly once.'), ScoredCandidate(candidate=Equation(tokens=[7.0, 5.0, '*', 12.0, '/']), score=0, feedback='The equation must use all 4 numbers exactly once.')]}}\n", + "{'prune': {'candidates': [ScoredCandidate(candidate=Equation(tokens=[12.0, 5.0, '/', 7.0, '*']), score=0, feedback='The equation must use all 4 numbers exactly once.'), ScoredCandidate(candidate=Equation(tokens=[12.0, 1.0, '+', 5.0, '*']), score=0, feedback='The equation must use all 4 numbers exactly once.'), ScoredCandidate(candidate=Equation(tokens=[12.0, 7.0, '*', 1.0, '/']), score=0, feedback='The equation must use all 4 numbers exactly once.')], 'scored_candidates': 'clear', 'depth': 1}}\n", + "{'expand': {'candidates': [Candidate(candidate=Equation(tokens=[12.0, 5.0, '-', 1.0, '*']), score=None, feedback=None), Candidate(candidate=Equation(tokens=[1.0, 7.0, '*', 5.0, '+']), score=None, feedback=None), Candidate(candidate=Equation(tokens=[12.0, 1.0, '+', 5.0, '*']), score=None, feedback=None), Candidate(candidate=Equation(tokens=[7.0, 5.0, '*', 1.0, '-']), score=None, feedback=None), Candidate(candidate=Equation(tokens=[12.0, 1.0, '*', 5.0, '-']), score=None, feedback=None)]}}\n", + "{'expand': {'candidates': []}}\n", + "{'expand': {'candidates': [Candidate(candidate=Equation(tokens=[5.0, 7.0, '*', 12.0, '-']), score=None, feedback=None), Candidate(candidate=Equation(tokens=[1.0, 5.0, 7.0, '*', 12.0, '-', '+']), score=None, feedback=None), Candidate(candidate=Equation(tokens=[12.0, 1.0, '*', 5.0, 7.0, '/']), score=None, feedback=None), Candidate(candidate=Equation(tokens=[12.0, 5.0, '*', 7.0, '/', 1.0, '-']), score=None, feedback=None), Candidate(candidate=Equation(tokens=[5.0, 7.0, '*', 1.0, '-', 12.0, '+']), score=None, feedback=None)]}}\n", + "{'score': {'candidates': 'clear', 'scored_candidates': [ScoredCandidate(candidate=Equation(tokens=[12.0, 5.0, '/', 7.0, '*']), score=0, feedback='The equation must use all 4 numbers exactly once.'), ScoredCandidate(candidate=Equation(tokens=[12.0, 1.0, '+', 5.0, '*']), score=0, feedback='The equation must use all 4 numbers exactly once.'), ScoredCandidate(candidate=Equation(tokens=[12.0, 7.0, '*', 1.0, '/']), score=0, feedback='The equation must use all 4 numbers exactly once.'), ScoredCandidate(candidate=Equation(tokens=[5.0, 7.0, '*', 12.0, '-']), score=0, feedback='The equation must use all 4 numbers exactly once.'), ScoredCandidate(candidate=Equation(tokens=[1.0, 5.0, 7.0, '*', 12.0, '-', '+']), score=1.0, feedback='Result: 24.0'), ScoredCandidate(candidate=Equation(tokens=[12.0, 1.0, '*', 5.0, 7.0, '/']), score=0.07692307692307693, feedback='Result: 12.0'), ScoredCandidate(candidate=Equation(tokens=[12.0, 5.0, '*', 7.0, '/', 1.0, '-']), score=0.05737704918032786, feedback='Result: 7.571428571428571'), ScoredCandidate(candidate=Equation(tokens=[5.0, 7.0, '*', 1.0, '-', 12.0, '+']), score=0.043478260869565216, feedback='Result: 46.0'), ScoredCandidate(candidate=Equation(tokens=[12.0, 5.0, '-', 1.0, '*']), score=0, feedback='The equation must use all 4 numbers exactly once.'), ScoredCandidate(candidate=Equation(tokens=[1.0, 7.0, '*', 5.0, '+']), score=0, feedback='The equation must use all 4 numbers exactly once.'), ScoredCandidate(candidate=Equation(tokens=[12.0, 1.0, '+', 5.0, '*']), score=0, feedback='The equation must use all 4 numbers exactly once.'), ScoredCandidate(candidate=Equation(tokens=[7.0, 5.0, '*', 1.0, '-']), score=0, feedback='The equation must use all 4 numbers exactly once.'), ScoredCandidate(candidate=Equation(tokens=[12.0, 1.0, '*', 5.0, '-']), score=0, feedback='The equation must use all 4 numbers exactly once.')]}}\n", + "{'prune': {'candidates': [ScoredCandidate(candidate=Equation(tokens=[1.0, 5.0, 7.0, '*', 12.0, '-', '+']), score=1.0, feedback='Result: 24.0'), ScoredCandidate(candidate=Equation(tokens=[12.0, 1.0, '*', 5.0, 7.0, '/']), score=0.07692307692307693, feedback='Result: 12.0'), ScoredCandidate(candidate=Equation(tokens=[12.0, 5.0, '*', 7.0, '/', 1.0, '-']), score=0.05737704918032786, feedback='Result: 7.571428571428571')], 'scored_candidates': 'clear', 'depth': 1}}\n" + ] + } + ], + "source": [ + "config = {\n", + " \"configurable\": {\n", + " \"thread_id\": \"test_1\",\n", + " \"depth\": 10,\n", + " }\n", + "}\n", + "for step in graph.stream({\"problem\": puzzles[42]}, config):\n", + " print(step)" + ] + }, + { + "cell_type": "code", + "execution_count": 16, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Found a winning solution in 2 steps: [Equation(tokens=[1.0, 5.0, 7.0, '*', 12.0, '-', '+']), 1.0, 'Result: 24.0']\n" + ] + } + ], + "source": [ + "final_state = graph.get_state(config)\n", + "winning_solution = final_state.values[\"candidates\"][0]\n", + "search_depth = final_state.values[\"depth\"]\n", + "if winning_solution[1] == 1:\n", + " print(f\"Found a winning solution in {search_depth} steps: {winning_solution}\")\n", + "else:\n", + " print(\n", + " f\"Failed to find a winning solution in {search_depth} steps. Best guess: {winning_solution}\"\n", + " )" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": ".venv", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.12.2" + } + }, + "nbformat": 4, + "nbformat_minor": 2 +} From c2cc1e2e5e58f6cff4fa051d2cbceaafc6a67071 Mon Sep 17 00:00:00 2001 From: Vadym Barda Date: Fri, 1 Nov 2024 09:27:06 -0400 Subject: [PATCH 14/60] docs: remove token-by-token streaming from stream events (#2292) --- docs/docs/cloud/how-tos/stream_events.md | 129 +---------------------- 1 file changed, 1 insertion(+), 128 deletions(-) diff --git a/docs/docs/cloud/how-tos/stream_events.md b/docs/docs/cloud/how-tos/stream_events.md index 385909376..a627f581b 100644 --- a/docs/docs/cloud/how-tos/stream_events.md +++ b/docs/docs/cloud/how-tos/stream_events.md @@ -292,131 +292,4 @@ Output: Receiving new event of type: end... - None - - -## Token-by-Token Streaming - -Token-by-token streaming can be implemented with the `events` streaming mode. The `on_chat_model_stream` event type should be processed to stream LLM responses token-by-token. - -=== "Python" - - ```python - llm_response = "" - - # stream token-by-token - async for chunk in client.runs.stream( - thread_id=thread["thread_id"], - assistant_id=assistant_id, - input=input, - stream_mode="events", - ): - if ( - chunk.event == "events" and - chunk.data["event"] == "on_chat_model_stream" and - len(chunk.data["data"]["chunk"]["content"]) > 0 and - 'text' in chunk.data["data"]["chunk"]["content"][0] - ): - llm_response += chunk.data["data"]["chunk"]["content"][0]['text'] - print(llm_response) - ``` - -=== "Javascript" - - ```js - const llmResponse = ""; - // stream events - const streamResponse = client.runs.stream( - thread["thread_id"], - assistantID, - { - input, - streamMode: "events" - } - ); - for await (const chunk of streamResponse) { - if (chunk.event === "events" && chunk.data.event === "on_chat_model_stream" && chunk.data.chunk.content.length > 0 && 'text' in chunk.data.chunk.content[0]) { - llmResponse += chunk.data.data.chunk.content[0].text; - console.log(llmResponse); - } - } - ``` - -=== "CURL" - - ```bash - curl --request POST \ - --url /threads//runs/stream \ - --header 'Content-Type: application/json' \ - --data "{ - \"assistant_id\": \"agent\", - \"input\": {\"messages\": [{\"role\": \"human\", \"content\": \"What's the weather in sf\"}]}, - \"stream_mode\": [ - \"events\" - ] - }" | sed 's/\r$//' | awk ' - /^event:/ { event = $2 } - /^data:/ { - json_data = substr($0, index($0, $2)) - - if (event == "events") { - print json_data - } - }' | jq -r ' - select(.event == "on_chat_model_stream") | - .data.chunk.content[] | .text // empty - ' | awk ' - BEGIN { llm_response="" } - $0 != "" && $0 != "null" { - llm_response = llm_response $0 - print llm_response - }' - ``` - -Output: - - The - The search - The search results provide - The search results provide the current weather conditions - The search results provide the current weather conditions in San Francisco. - The search results provide the current weather conditions in San Francisco. According - The search results provide the current weather conditions in San Francisco. According to the data, - The search results provide the current weather conditions in San Francisco. According to the data, as - The search results provide the current weather conditions in San Francisco. According to the data, as of 3 - The search results provide the current weather conditions in San Francisco. According to the data, as of 3:19 - The search results provide the current weather conditions in San Francisco. According to the data, as of 3:19 PM on August - The search results provide the current weather conditions in San Francisco. According to the data, as of 3:19 PM on August 12, - The search results provide the current weather conditions in San Francisco. According to the data, as of 3:19 PM on August 12, 2024, - The search results provide the current weather conditions in San Francisco. According to the data, as of 3:19 PM on August 12, 2024, the weather - The search results provide the current weather conditions in San Francisco. According to the data, as of 3:19 PM on August 12, 2024, the weather in - The search results provide the current weather conditions in San Francisco. According to the data, as of 3:19 PM on August 12, 2024, the weather in San Francisco is sunny - The search results provide the current weather conditions in San Francisco. According to the data, as of 3:19 PM on August 12, 2024, the weather in San Francisco is sunny with a temperature of - The search results provide the current weather conditions in San Francisco. According to the data, as of 3:19 PM on August 12, 2024, the weather in San Francisco is sunny with a temperature of 60 - The search results provide the current weather conditions in San Francisco. According to the data, as of 3:19 PM on August 12, 2024, the weather in San Francisco is sunny with a temperature of 60. - The search results provide the current weather conditions in San Francisco. According to the data, as of 3:19 PM on August 12, 2024, the weather in San Francisco is sunny with a temperature of 60.8°F ( - The search results provide the current weather conditions in San Francisco. According to the data, as of 3:19 PM on August 12, 2024, the weather in San Francisco is sunny with a temperature of 60.8°F (16° - The search results provide the current weather conditions in San Francisco. According to the data, as of 3:19 PM on August 12, 2024, the weather in San Francisco is sunny with a temperature of 60.8°F (16°C). - The search results provide the current weather conditions in San Francisco. According to the data, as of 3:19 PM on August 12, 2024, the weather in San Francisco is sunny with a temperature of 60.8°F (16°C). The win - The search results provide the current weather conditions in San Francisco. According to the data, as of 3:19 PM on August 12, 2024, the weather in San Francisco is sunny with a temperature of 60.8°F (16°C). The wind is bl - The search results provide the current weather conditions in San Francisco. According to the data, as of 3:19 PM on August 12, 2024, the weather in San Francisco is sunny with a temperature of 60.8°F (16°C). The wind is blowing from the west - The search results provide the current weather conditions in San Francisco. According to the data, as of 3:19 PM on August 12, 2024, the weather in San Francisco is sunny with a temperature of 60.8°F (16°C). The wind is blowing from the west- - The search results provide the current weather conditions in San Francisco. According to the data, as of 3:19 PM on August 12, 2024, the weather in San Francisco is sunny with a temperature of 60.8°F (16°C). The wind is blowing from the west-southwest at 13 - The search results provide the current weather conditions in San Francisco. According to the data, as of 3:19 PM on August 12, 2024, the weather in San Francisco is sunny with a temperature of 60.8°F (16°C). The wind is blowing from the west-southwest at 13.4 mph - The search results provide the current weather conditions in San Francisco. According to the data, as of 3:19 PM on August 12, 2024, the weather in San Francisco is sunny with a temperature of 60.8°F (16°C). The wind is blowing from the west-southwest at 13.4 mph (21.6 - The search results provide the current weather conditions in San Francisco. According to the data, as of 3:19 PM on August 12, 2024, the weather in San Francisco is sunny with a temperature of 60.8°F (16°C). The wind is blowing from the west-southwest at 13.4 mph (21.6 k - The search results provide the current weather conditions in San Francisco. According to the data, as of 3:19 PM on August 12, 2024, the weather in San Francisco is sunny with a temperature of 60.8°F (16°C). The wind is blowing from the west-southwest at 13.4 mph (21.6 kph). - The search results provide the current weather conditions in San Francisco. According to the data, as of 3:19 PM on August 12, 2024, the weather in San Francisco is sunny with a temperature of 60.8°F (16°C). The wind is blowing from the west-southwest at 13.4 mph (21.6 kph). The humidity is - The search results provide the current weather conditions in San Francisco. According to the data, as of 3:19 PM on August 12, 2024, the weather in San Francisco is sunny with a temperature of 60.8°F (16°C). The wind is blowing from the west-southwest at 13.4 mph (21.6 kph). The humidity is 70% - The search results provide the current weather conditions in San Francisco. According to the data, as of 3:19 PM on August 12, 2024, the weather in San Francisco is sunny with a temperature of 60.8°F (16°C). The wind is blowing from the west-southwest at 13.4 mph (21.6 kph). The humidity is 70% and visibility - The search results provide the current weather conditions in San Francisco. According to the data, as of 3:19 PM on August 12, 2024, the weather in San Francisco is sunny with a temperature of 60.8°F (16°C). The wind is blowing from the west-southwest at 13.4 mph (21.6 kph). The humidity is 70% and visibility is 6 - The search results provide the current weather conditions in San Francisco. According to the data, as of 3:19 PM on August 12, 2024, the weather in San Francisco is sunny with a temperature of 60.8°F (16°C). The wind is blowing from the west-southwest at 13.4 mph (21.6 kph). The humidity is 70% and visibility is 6 miles - The search results provide the current weather conditions in San Francisco. According to the data, as of 3:19 PM on August 12, 2024, the weather in San Francisco is sunny with a temperature of 60.8°F (16°C). The wind is blowing from the west-southwest at 13.4 mph (21.6 kph). The humidity is 70% and visibility is 6 miles (10 km - The search results provide the current weather conditions in San Francisco. According to the data, as of 3:19 PM on August 12, 2024, the weather in San Francisco is sunny with a temperature of 60.8°F (16°C). The wind is blowing from the west-southwest at 13.4 mph (21.6 kph). The humidity is 70% and visibility is 6 miles (10 km). - The search results provide the current weather conditions in San Francisco. According to the data, as of 3:19 PM on August 12, 2024, the weather in San Francisco is sunny with a temperature of 60.8°F (16°C). The wind is blowing from the west-southwest at 13.4 mph (21.6 kph). The humidity is 70% and visibility is 6 miles (10 km). Overall - The search results provide the current weather conditions in San Francisco. According to the data, as of 3:19 PM on August 12, 2024, the weather in San Francisco is sunny with a temperature of 60.8°F (16°C). The wind is blowing from the west-southwest at 13.4 mph (21.6 kph). The humidity is 70% and visibility is 6 miles (10 km). Overall, it appears - The search results provide the current weather conditions in San Francisco. According to the data, as of 3:19 PM on August 12, 2024, the weather in San Francisco is sunny with a temperature of 60.8°F (16°C). The wind is blowing from the west-southwest at 13.4 mph (21.6 kph). The humidity is 70% and visibility is 6 miles (10 km). Overall, it appears to be a nice - The search results provide the current weather conditions in San Francisco. According to the data, as of 3:19 PM on August 12, 2024, the weather in San Francisco is sunny with a temperature of 60.8°F (16°C). The wind is blowing from the west-southwest at 13.4 mph (21.6 kph). The humidity is 70% and visibility is 6 miles (10 km). Overall, it appears to be a nice sunny day in San - The search results provide the current weather conditions in San Francisco. According to the data, as of 3:19 PM on August 12, 2024, the weather in San Francisco is sunny with a temperature of 60.8°F (16°C). The wind is blowing from the west-southwest at 13.4 mph (21.6 kph). The humidity is 70% and visibility is 6 miles (10 km). Overall, it appears to be a nice sunny day in San Francisco. - - + None \ No newline at end of file From 4aebb461e99a562e588e5026ecc9c1478bf2f5a4 Mon Sep 17 00:00:00 2001 From: Vadym Barda Date: Fri, 1 Nov 2024 09:51:34 -0400 Subject: [PATCH 15/60] docs: update core in docs dependencies (#2291) --- poetry.lock | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/poetry.lock b/poetry.lock index aa041a3f7..80e87def8 100644 --- a/poetry.lock +++ b/poetry.lock @@ -2810,13 +2810,13 @@ tenacity = ">=8.1.0,<8.4.0 || >8.4.0,<9.0.0" [[package]] name = "langchain-core" -version = "0.3.8" +version = "0.3.15" description = "Building applications with LLMs through composability" optional = false python-versions = "<4.0,>=3.9" files = [ - {file = "langchain_core-0.3.8-py3-none-any.whl", hash = "sha256:07015f7b1d9f52eefe05130e8cafe4dcbdbbf72a8411c9edafe38422e4d11b5c"}, - {file = "langchain_core-0.3.8.tar.gz", hash = "sha256:7485904f7082f1df880d5ae470a488161616132f30d99f556a1877901fffd1cb"}, + {file = "langchain_core-0.3.15-py3-none-any.whl", hash = "sha256:3d4ca6dbb8ed396a6ee061063832a2451b0ce8c345570f7b086ffa7288e4fa29"}, + {file = "langchain_core-0.3.15.tar.gz", hash = "sha256:b1a29787a4ffb7ec2103b4e97d435287201da7809b369740dd1e32f176325aba"}, ] [package.dependencies] @@ -2828,7 +2828,7 @@ pydantic = [ {version = ">=2.7.4,<3.0.0", markers = "python_full_version >= \"3.12.4\""}, ] PyYAML = ">=5.3" -tenacity = ">=8.1.0,<8.4.0 || >8.4.0,<9.0.0" +tenacity = ">=8.1.0,<8.4.0 || >8.4.0,<10.0.0" typing-extensions = ">=4.7" [[package]] From d9743f684e81bba348c1ceb39fbd04787f620d45 Mon Sep 17 00:00:00 2001 From: Vadym Barda Date: Fri, 1 Nov 2024 09:57:16 -0400 Subject: [PATCH 16/60] docs: update annotation in tutorial (#2293) --- docs/docs/tutorials/reflexion/reflexion.ipynb | 13 +++---------- 1 file changed, 3 insertions(+), 10 deletions(-) diff --git a/docs/docs/tutorials/reflexion/reflexion.ipynb b/docs/docs/tutorials/reflexion/reflexion.ipynb index 102f48175..a9a1ea87d 100644 --- a/docs/docs/tutorials/reflexion/reflexion.ipynb +++ b/docs/docs/tutorials/reflexion/reflexion.ipynb @@ -46,8 +46,7 @@ "metadata": {}, "outputs": [], "source": [ - "%pip install -U --quiet langgraph langchain_anthropic\n", - "%pip install -U --quiet tavily-python" + "%pip install -U --quiet langgraph langchain_anthropic tavily-python" ] }, { @@ -189,7 +188,7 @@ " self.runnable = runnable\n", " self.validator = validator\n", "\n", - " def respond(self, state: list):\n", + " def respond(self, state: dict):\n", " response = []\n", " for attempt in range(3):\n", " response = self.runnable.invoke(\n", @@ -622,12 +621,6 @@ "2. The 'reflections' can be paired with additional external feedback (such as validators), to further guide the actor.\n", "3. In the paper, 1 environment (AlfWorld) uses external memory. It does this by storing summaries of the reflections to an external store and using them in subsequent trials/invocations." ] - }, - { - "cell_type": "markdown", - "id": "39e44dd6", - "metadata": {}, - "source": [] } ], "metadata": { @@ -646,7 +639,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.11.9" + "version": "3.12.3" } }, "nbformat": 4, From 52f095378623d830c61a5e00ebe8497fa94c82c9 Mon Sep 17 00:00:00 2001 From: Chester Curme Date: Fri, 1 Nov 2024 11:23:16 -0400 Subject: [PATCH 17/60] add detail --- docs/docs/cloud/how-tos/webhooks.md | 21 ++++++++++++++++----- 1 file changed, 16 insertions(+), 5 deletions(-) diff --git a/docs/docs/cloud/how-tos/webhooks.md b/docs/docs/cloud/how-tos/webhooks.md index 59a3ec049..8e3396923 100644 --- a/docs/docs/cloud/how-tos/webhooks.md +++ b/docs/docs/cloud/how-tos/webhooks.md @@ -76,7 +76,9 @@ Output: ## Use graph with a webhook -Now we can invoke a run with a webhook: +To invoke a run with a webhook, we specify the `webhook` parameter with the desired endpoint when creating a run. Webhook requests are triggered by the end of a run. + +For example, if we can receive requests at `https://my-server.app/my-webhook-endpoint`, we can pass this to `stream`: === "Python" @@ -89,7 +91,7 @@ Now we can invoke a run with a webhook: assistant_id=assistant_id, input=input, stream_mode="events", - webhook="your-webhook" + webhook="https://my-server.app/my-webhook-endpoint" ): # Do something with the stream output pass @@ -107,7 +109,7 @@ Now we can invoke a run with a webhook: assistantID, { input: input, - webhook: "your-webhook" + webhook: "https://my-server.app/my-webhook-endpoint" } ); for await (const chunk of streamResponse) { @@ -124,8 +126,17 @@ Now we can invoke a run with a webhook: --data '{ "assistant_id": , "input" : {"messages":[{"role": "user", "content": "Hello!"}]}, - "webhook": + "webhook": "https://my-server.app/my-webhook-endpoint" }' ``` -And that's it! Now you can trigger your custom webhooks whenever you want in your LangGraph applications! \ No newline at end of file +The schema for the payload sent to `my-webhook-endpoint` is that of a [run](../../concepts/langgraph_server.md/#runs). See [API Reference](https://langchain-ai.github.io/langgraph/cloud/reference/api/api_ref.html#model/run) for more detail. Note that the run input, configuration, etc. are included in the `kwargs` field. + +### Signing webhook requests + +To sign the webhook requests, we can specify a token parameter in the webhook URL, e.g., +``` +https://my-server.app/my-webhook-endpoint?token=... +``` + +The server should then extract the token from the request's parameters and validate it before processing the payload. From 84e023e10bc91b2abb894c95337114ce50ae3754 Mon Sep 17 00:00:00 2001 From: vbarda Date: Fri, 1 Nov 2024 12:43:59 -0400 Subject: [PATCH 18/60] langgraph: handle messages-tuple stream mode in RemoteGraph --- libs/langgraph/langgraph/pregel/remote.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/libs/langgraph/langgraph/pregel/remote.py b/libs/langgraph/langgraph/pregel/remote.py index 35eee1679..d9ee53e8c 100644 --- a/libs/langgraph/langgraph/pregel/remote.py +++ b/libs/langgraph/langgraph/pregel/remote.py @@ -538,6 +538,13 @@ class RemoteGraph(PregelProtocol): if "messages" in updated_stream_modes: updated_stream_modes.remove("messages") updated_stream_modes.append("messages-tuple") + + # if requested "messages-tuple", + # map to "messages" in requested_stream_modes + if "messages-tuple" in requested_stream_modes: + requested_stream_modes.remove("messages-tuple") + requested_stream_modes.append("messages") + # add 'updates' mode if not present if "updates" not in updated_stream_modes: updated_stream_modes.append("updates") From f4801ae9aec41de715befeb9ad82a0ee12c361ff Mon Sep 17 00:00:00 2001 From: Tat Dat Duong Date: Fri, 1 Nov 2024 18:47:35 +0100 Subject: [PATCH 19/60] chore(cli): rename Debugger to LangGraph Studio --- libs/cli/langgraph_cli/cli.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libs/cli/langgraph_cli/cli.py b/libs/cli/langgraph_cli/cli.py index a3bd18ddd..1e38e47c7 100644 --- a/libs/cli/langgraph_cli/cli.py +++ b/libs/cli/langgraph_cli/cli.py @@ -239,7 +239,7 @@ For production use, requires a license key in env var LANGGRAPH_CLOUD_LICENSE_KE f"""Ready! - API: http://localhost:{port} - Docs: http://localhost:{port}/docs -- Debugger: {debugger_origin}/studio/?baseUrl={debugger_base_url_query} +- LangGraph Studio: {debugger_origin}/studio/?baseUrl={debugger_base_url_query} """ ) sys.stdout.flush() From 6986a507112dc79fcf2d73cb6e95e59be0eb3730 Mon Sep 17 00:00:00 2001 From: Andrew Nguonly Date: Fri, 1 Nov 2024 11:38:12 -0700 Subject: [PATCH 20/60] Update LangGraph Server API docs (#2300) --- docs/docs/cloud/reference/api/openapi.json | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/docs/docs/cloud/reference/api/openapi.json b/docs/docs/cloud/reference/api/openapi.json index 02dc13814..2ad201052 100644 --- a/docs/docs/cloud/reference/api/openapi.json +++ b/docs/docs/cloud/reference/api/openapi.json @@ -304,10 +304,19 @@ "description": "The ID of the assistant.", "required": true, "schema": { - "type": "string", - "format": "uuid", - "title": "Assistant ID", - "description": "The ID of the assistant." + "anyOf": [ + { + "type": "string", + "format": "uuid", + "title": "Assistant ID", + "description": "The ID of the assistant." + }, + { + "type": "string", + "title": "Graph ID", + "description": "The ID of the graph." + } + ] }, "name": "assistant_id", "in": "path" From d261d43b396712513a3bb6f04bd7a3d43c1a446a Mon Sep 17 00:00:00 2001 From: Vadym Barda Date: Fri, 1 Nov 2024 14:38:41 -0400 Subject: [PATCH 21/60] docs: update the docs for streaming messages (#2299) --- docs/docs/cloud/concepts/api.md | 117 +---------- docs/docs/cloud/how-tos/stream_messages.md | 221 +++++++++++---------- docs/docs/concepts/streaming.md | 85 +------- docs/mkdocs.yml | 3 + 4 files changed, 128 insertions(+), 298 deletions(-) diff --git a/docs/docs/cloud/concepts/api.md b/docs/docs/cloud/concepts/api.md index f8e948285..7b2f56b2d 100644 --- a/docs/docs/cloud/concepts/api.md +++ b/docs/docs/cloud/concepts/api.md @@ -62,122 +62,7 @@ The LangGraph Cloud API offers several features to support complex agent archite ### Streaming -Streaming is critical for making LLM applications feel responsive to end users. When creating a streaming run, the streaming mode determines what data is streamed back to the API client. The LangGraph Cloud API supports five streaming modes. - -- `values`: Stream the full state of the graph after each [super-step](https://langchain-ai.github.io/langgraph/concepts/low_level/#graphs) is executed. See the [how-to guide](../how-tos/stream_values.md) for streaming values. -- `messages`: Stream complete messages (at the end of node execution) as well as tokens for any messages generated inside a node. This mode is primarily meant for powering chat applications. This is only an option if your graph contains a `messages` key. See the [how-to guide](../how-tos/stream_messages.md) for streaming messages. -- `updates`: Streams updates to the state of the graph after each node is executed. See the [how-to guide](../how-tos/stream_updates.md) for streaming updates. -- `events`: Stream all events (including the state of the graph) that occur during graph execution. See the [how-to guide](../how-tos/stream_events.md) for streaming events. This can be used to do token-by-token streaming for LLMs. -- `debug`: Stream debug events throughout graph execution. See the [how-to guide](../how-tos/stream_debug.md) for streaming debug events. - -You can also specify multiple streaming modes at the same time. See the [how-to guide](../how-tos/stream_multiple.md) for configuring multiple streaming modes at the same time. - -See the [API reference](../reference/api/api_ref.html#tag/runscreate/POST/threads/{thread_id}/runs/stream) for how to create streaming runs. - -Streaming modes `values`, `updates`, and `debug` are very similar to modes available in the LangGraph library - for a deeper conceptual explanation of those, you can see the LangGraph library documentation [here](../../concepts/low_level.md#streaming). - -Streaming mode `events` is the same as using `.astream_events` in the LangGraph library - for a deeper conceptual explanation of this, you can see the LangGraph library documentation [here](../../concepts/low_level.md#streaming). - -#### `mode="messages"` -Streaming mode `messages` is a new streaming mode, currently only available in the API. What does this mode enable? - -This mode is focused on streaming back messages. It currently assumes that you have a `messages` key in your graph that is a list of messages. Assuming we have a simple react agent deployed, what does this stream look like? - -All events emitted have two attributes: - -- `event`: This is the name of the event -- `data`: This is data associated with the event - -Let's run it on a question that should trigger a tool call: - -```python -thread = await client.threads.create() -input = {"messages": [{"role": "user", "content": "what's the weather in sf?"}]} - -events = [] -async for event in client.runs.stream( - thread["thread_id"], - assistant_id="agent", # This may need to change depending on the graph you deployed - input=input, - stream_mode="messages", -): - print(event.event) -``` -```shell -metadata -messages/complete -messages/metadata -messages/partial -... -messages/partial -messages/complete -messages/complete -messages/metadata -messages/partial -... -messages/partial -messages/complete -end -``` - -We first get some `metadata` - this is metadata about the run. - -```python -StreamPart(event='metadata', data={'run_id': '1ef657cf-ae55-6f65-97d4-f4ed1dbdabc6'}) -``` - -We then get a `messages/complete` event - this a fully formed message getting emitted. In this case, -this was the just the input message we sent in. - -```python -StreamPart(event='messages/complete', data=[{'content': 'hi!', 'additional_kwargs': {}, 'response_metadata': {}, 'type': 'human', 'name': None, 'id': '833c09a3-bb19-46c9-81d9-1e5954ec5f92', 'example': False}]) -``` - -We then get a `messages/metadata` - this is just letting us know that a new message is starting. - -```python -StreamPart(event='messages/metadata', data={'run-985c0f14-9f43-40d4-a505-4637fc58e333': {'metadata': {'created_by': 'system', 'run_id': '1ef657de-7594-66df-8eb2-31518e4a1ee2', 'graph_id': 'agent', 'thread_id': 'c178eab5-e293-423c-8e7d-1d113ffe7cd9', 'model_name': 'openai', 'assistant_id': 'fe096781-5601-53d2-b2f6-0d3403f7e9ca', 'langgraph_step': 1, 'langgraph_node': 'agent', 'langgraph_triggers': ['start:agent'], 'langgraph_task_idx': 0, 'ls_provider': 'openai', 'ls_model_name': 'gpt-4o', 'ls_model_type': 'chat', 'ls_temperature': 0.0}}}) -``` - -We then get a BUNCH of `messages/partial` events - these are the individual tokens from the LLM! In the case below, we can see the START of a tool call. - -```python -StreamPart(event='messages/partial', data=[{'content': '', 'additional_kwargs': {'tool_calls': [{'index': 0, 'id': 'call_w8Hr8dHGuZCPgRfd5FqRBArs', 'function': {'arguments': '', 'name': 'tavily_search_results_json'}, 'type': 'function'}]}, 'response_metadata': {}, 'type': 'ai', 'name': None, 'id': 'run-985c0f14-9f43-40d4-a505-4637fc58e333', 'example': False, 'tool_calls': [], 'invalid_tool_calls': [{'name': 'tavily_search_results_json', 'args': '', 'id': 'call_w8Hr8dHGuZCPgRfd5FqRBArs', 'error': None}], 'usage_metadata': None}]) -``` - -After that, we get a `messages/complete` event - this is the AIMessage finishing. It's now a complete tool call: - -```python -StreamPart(event='messages/complete', data=[{'content': '', 'additional_kwargs': {'tool_calls': [{'index': 0, 'id': 'call_w8Hr8dHGuZCPgRfd5FqRBArs', 'function': {'arguments': '{"query":"current weather in San Francisco"}', 'name': 'tavily_search_results_json'}, 'type': 'function'}]}, 'response_metadata': {'finish_reason': 'tool_calls', 'model_name': 'gpt-4o-2024-05-13', 'system_fingerprint': 'fp_157b3831f5'}, 'type': 'ai', 'name': None, 'id': 'run-985c0f14-9f43-40d4-a505-4637fc58e333', 'example': False, 'tool_calls': [{'name': 'tavily_search_results_json', 'args': {'query': 'current weather in San Francisco'}, 'id': 'call_w8Hr8dHGuZCPgRfd5FqRBArs'}], 'invalid_tool_calls': [], 'usage_metadata': None}]) -``` - -After that, we get ANOTHER `messages/complete` event. This is a tool message - our agent has called a tool, gotten a response, and now inserting it into the state in the form of a tool message. - -```python -StreamPart(event='messages/complete', data=[{'content': '[{"url": "https://www.weatherapi.com/", "content": "{\'location\': {\'name\': \'San Francisco\', \'region\': \'California\', \'country\': \'United States of America\', \'lat\': 37.78, \'lon\': -122.42, \'tz_id\': \'America/Los_Angeles\', \'localtime_epoch\': 1724877689, \'localtime\': \'2024-08-28 13:41\'}, \'current\': {\'last_updated_epoch\': 1724877000, \'last_updated\': \'2024-08-28 13:30\', \'temp_c\': 23.3, \'temp_f\': 73.9, \'is_day\': 1, \'condition\': {\'text\': \'Partly cloudy\', \'icon\': \'//cdn.weatherapi.com/weather/64x64/day/116.png\', \'code\': 1003}, \'wind_mph\': 15.0, \'wind_kph\': 24.1, \'wind_degree\': 310, \'wind_dir\': \'NW\', \'pressure_mb\': 1014.0, \'pressure_in\': 29.93, \'precip_mm\': 0.0, \'precip_in\': 0.0, \'humidity\': 57, \'cloud\': 25, \'feelslike_c\': 25.0, \'feelslike_f\': 77.1, \'windchill_c\': 20.9, \'windchill_f\': 69.6, \'heatindex_c\': 23.3, \'heatindex_f\': 74.0, \'dewpoint_c\': 12.9, \'dewpoint_f\': 55.2, \'vis_km\': 16.0, \'vis_miles\': 9.0, \'uv\': 6.0, \'gust_mph\': 19.5, \'gust_kph\': 31.3}}"}]', 'additional_kwargs': {}, 'response_metadata': {}, 'type': 'tool', 'name': 'tavily_search_results_json', 'id': '0112eba5-7660-4375-9f24-c7a1d6777b97', 'tool_call_id': 'call_w8Hr8dHGuZCPgRfd5FqRBArs'}]) -``` - -After that, we see the agent doing another LLM call and streaming back a response. We then get an `end` event: - -```python -StreamPart(event='end', data=None) -``` - -And that's it! This is more focused streaming mode specifically focused on streaming back messages. See this [how-to guide](../how-tos/stream_messages.md) for more information. - - -### Human-in-the-Loop - -There are many occasions where the graph cannot run completely autonomously. For instance, the user might need to input some additional arguments to a function call, or select the next edge for the graph to continue on. In these instances, we need to insert some human in the loop interaction, which you can learn about in the [human in the loop how-tos](../how-tos/index.md#human-in-the-loop). - -### Double Texting - -Many times users might interact with your graph in unintended ways. For instance, a user may send one message and before the graph has finished running send a second message. To solve this issue of "double-texting" (i.e. prompting the graph a second time before the first run has finished), LangGraph has provided four different solutions, all of which are covered in the [Double Texting how-tos](../how-tos/index.md#double-texting). These options are: - -- `reject`: This is the simplest option, this just rejects any follow up runs and does not allow double texting. See the [how-to guide](../how-tos/reject_concurrent.md) for configuring the reject double text option. -- `enqueue`: This is a relatively simple option which continues the first run until it completes the whole run, then sends the new input as a separate run. See the [how-to guide](../how-tos/enqueue_concurrent.md) for configuring the enqueue double text option. -- `interrupt`: This option interrupts the current execution but saves all the work done up until that point. It then inserts the user input and continues from there. If you enable this option, your graph should be able to handle weird edge cases that may arise. See the [how-to guide](../how-tos/interrupt_concurrent.md) for configuring the interrupt double text option. -- `rollback`: This option rolls back all work done up until that point. It then sends the user input in, basically as if it just followed the original run input. See the [how-to guide](../how-tos/rollback_concurrent.md) for configuring the rollback double text option. +[Streaming](../../concepts/streaming.md) is critical for making LLM applications feel responsive to end users. When creating a streaming run, the streaming mode determines what data is streamed back to the API client. LangGraph Platform supports five streaming modes: `values`, `updates`, `messages-tuple`, `events`, and `debug`. See these [how-to guides](../../how-tos/index.md#streaming_1) and the [API reference](../reference/api/api_ref.html#tag/thread-runs/POST/threads/%7Bthread_id%7D/runs/stream) for more details. ### Stateless Runs diff --git a/docs/docs/cloud/how-tos/stream_messages.md b/docs/docs/cloud/how-tos/stream_messages.md index d81d40a57..a359956a3 100644 --- a/docs/docs/cloud/how-tos/stream_messages.md +++ b/docs/docs/cloud/how-tos/stream_messages.md @@ -3,9 +3,7 @@ !!! info "Prerequisites" * [Streaming](../../concepts/streaming.md) -This guide covers how to stream messages from your graph. With `stream_mode="messages"`, messages from any chat model invocations inside your graph nodes will be streamed back. - -Read more about how the `messages` streaming mode works [here](https://langchain-ai.github.io/langgraph/cloud/concepts/api/#modemessages) +This guide covers how to stream messages from your graph. With `stream_mode="messages-tuple"`, messages (i.e. individual LLM tokens) from any chat model invocations inside your graph nodes will be streamed back. ## Setup @@ -60,7 +58,7 @@ Output: ## Stream graph in messages mode -Now we can stream by messages, which will return complete messages (at the end of node execution) as well as tokens for any messages generated inside a node: +Now we can stream LLM tokens for any messages generated inside a node in the form of tuples `(message, metadata)`. Metadata contains additional information that can be useful for filtering the streamed outputs to a specific node or LLM. === "Python" @@ -73,7 +71,7 @@ Now we can stream by messages, which will return complete messages (at the end o assistant_id=assistant_id, input=input, config=config, - stream_mode="messages", + stream_mode="messages-tuple", ): print(f"Receiving new event of type: {chunk.event}...") print(chunk.data) @@ -99,7 +97,7 @@ Now we can stream by messages, which will return complete messages (at the end o { input, config, - streamMode: "messages" + streamMode: "messages-tuple" } ); for await (const chunk of streamResponse) { @@ -119,7 +117,7 @@ Now we can stream by messages, which will return complete messages (at the end o \"assistant_id\": \"agent\", \"input\": {\"messages\": [{\"role\": \"human\", \"content\": \"what's the weather in la\"}]}, \"stream_mode\": [ - \"messages\" + \"messages-tuple\" ] }" | \ sed 's/\r$//' | \ @@ -150,117 +148,101 @@ Output: Receiving new event of type: metadata... {"run_id": "1ef971e0-9a84-6154-9047-247b4ce89c4d", "attempt": 1} - - - Receiving new event of type: messages/metadata... - { - "run-700157a5-df1a-4829-9e7c-1e07a1d934f7": { - "metadata": { - "graph_id": "agent", - "langgraph_node": "agent", - ... - } - } - } - ... - Receiving new event of type: messages/partial... + Receiving new event of type: messages... [ { + "type": "AIMessageChunk", "tool_calls": [ { "name": "tavily_search_results_json", "args": { - "query": "weather" + "query": "weat" }, - "id": "toolu_01RJGmVJtTxccoHHixGkGqaC", - "type": "tool_call" - } - ], - } - ] - - - - Receiving new event of type: messages/partial... - [ - { - "type": "ai", - "tool_calls": [ - { - "name": "tavily_search_results_json", - "args": { - "query": "weather in " - }, - "id": "toolu_01RJGmVJtTxccoHHixGkGqaC", + "id": "toolu_0114XKXdNtHQEa3ozmY1uDdM", "type": "tool_call" } ], ... - } - ] - - ... - - Receiving new event of type: messages/partial... - [ + }, { - "type": "ai", - "tool_calls": [ - { - "name": "tavily_search_results_json", - "args": { - "query": "weather in san francisco" - }, - "id": "toolu_01RJGmVJtTxccoHHixGkGqaC", - "type": "tool_call" - } - ], + "graph_id": "agent", + "langgraph_node": "agent", ... } ] - Receiving new event of type: messages/metadata... - { - "aa162b98-433d-4e3c-b204-0d41a6694156": { - "metadata": { - "graph_id": "agent", - "langgraph_node": "action", - ... - } - } - } - - - - Receiving new event of type: messages/complete... + Receiving new event of type: messages... [ { - "content": "[{\"url\": \"https://www.weatherapi.com/\", \"content\": \"{'location': {'name': 'San Francisco', 'region': 'California', 'country': 'United States of America', 'lat': 37.775, 'lon': -122.4183, 'tz_id': 'America/Los_Angeles', 'localtime_epoch': 1730334046, 'localtime': '2024-10-30 17:20'}, 'current': {'last_updated_epoch': 1730333700, 'last_updated': '2024-10-30 17:15', 'temp_c': 12.3, 'temp_f': 54.2, 'is_day': 1, 'condition': {'text': 'Partly Cloudy', 'icon': '//cdn.weatherapi.com/weather/64x64/day/116.png', 'code': 1003}, 'wind_mph': 9.6, 'wind_kph': 15.5, 'wind_degree': 238, 'wind_dir': 'WSW', 'pressure_mb': 1021.0, 'pressure_in': 30.15, 'precip_mm': 0.0, 'precip_in': 0.0, 'humidity': 93, 'cloud': 57, 'feelslike_c': 11.2, 'feelslike_f': 52.2, 'windchill_c': 11.2, 'windchill_f': 52.2, 'heatindex_c': 12.3, 'heatindex_f': 54.2, 'dewpoint_c': 11.2, 'dewpoint_f': 52.1, 'vis_km': 10.0, 'vis_miles': 6.0, 'uv': 0.5, 'gust_mph': 12.9, 'gust_kph': 20.8}}\"}]", + "type": "AIMessageChunk", + "tool_calls": [ + { + "name": "tavily_search_results_json", + "args": { + "query": "her in san " + }, + "id": "toolu_0114XKXdNtHQEa3ozmY1uDdM", + "type": "tool_call" + } + ], + ... + }, + { + "graph_id": "agent", + "langgraph_node": "agent", + ... + } + ] + + ... + + Receiving new event of type: messages... + [ + { + "type": "AIMessageChunk", + "tool_calls": [ + { + "name": "tavily_search_results_json", + "args": { + "query": "francisco" + }, + "id": "toolu_0114XKXdNtHQEa3ozmY1uDdM", + "type": "tool_call" + } + ], + ... + }, + { + "graph_id": "agent", + "langgraph_node": "agent", + ... + } + ] + + ... + + Receiving new event of type: messages... + [ + { + "content": "[{\"url\": \"https://www.weatherapi.com/\", \"content\": \"{'location': {'name': 'San Francisco', 'region': 'California', 'country': 'United States of America', 'lat': 37.775, 'lon': -122.4183, 'tz_id': 'America/Los_Angeles', 'localtime_epoch': 1730475777, 'localtime': '2024-11-01 08:42'}, 'current': {'last_updated_epoch': 1730475000, 'last_updated': '2024-11-01 08:30', 'temp_c': 11.1, 'temp_f': 52.0, 'is_day': 1, 'condition': {'text': 'Partly cloudy', 'icon': '//cdn.weatherapi.com/weather/64x64/day/116.png', 'code': 1003}, 'wind_mph': 2.2, 'wind_kph': 3.6, 'wind_degree': 192, 'wind_dir': 'SSW', 'pressure_mb': 1018.0, 'pressure_in': 30.07, 'precip_mm': 0.0, 'precip_in': 0.0, 'humidity': 89, 'cloud': 75, 'feelslike_c': 11.5, 'feelslike_f': 52.6, 'windchill_c': 10.0, 'windchill_f': 50.1, 'heatindex_c': 10.4, 'heatindex_f': 50.7, 'dewpoint_c': 9.1, 'dewpoint_f': 48.5, 'vis_km': 16.0, 'vis_miles': 9.0, 'uv': 3.0, 'gust_mph': 6.7, 'gust_kph': 10.8}}\"}]", "type": "tool", - "name": "tavily_search_results_json", - "tool_call_id": "toolu_01RJGmVJtTxccoHHixGkGqaC", + "tool_call_id": "toolu_0114XKXdNtHQEa3ozmY1uDdM", + ... + }, + { + "graph_id": "agent", + "langgraph_node": "action", + ... } ] + ... - Receiving new event of type: messages/metadata... - { - "run-f92646d2-6b13-4648-90c7-0280766bfaf2": { - "metadata": { - "graph_id": "agent", - "langgraph_node": "agent", - ... - } - } - } - - - - Receiving new event of type: messages/partial... + Receiving new event of type: messages... [ { "content": [ @@ -270,41 +252,80 @@ Output: "index": 0 } ], - "type": "ai", + "type": "AIMessageChunk", + ... + }, + { + "graph_id": "agent", + "langgraph_node": "agent", ... } ] - Receiving new event of type: messages/partial... + Receiving new event of type: messages... [ { "content": [ { - "text": "\n\nThe search results provide", + "text": " results provide", "type": "text", "index": 0 } ], - "type": "ai", + "type": "AIMessageChunk", + ... + }, + { + "graph_id": "agent", + "langgraph_node": "agent", ... } ] - ... - Receiving new event of type: messages/partial... + + Receiving new event of type: messages... [ { "content": [ { - "text": "\n\nThe search results provide the current weather conditions in San Francisco. According to the data, as of 5:20pm on October 30, 2024, the weather in San Francisco is partly cloudy with a temperature of 54\\u00b0F (12\\u00b0C). The wind is blowing from the west-southwest at around 10 mph (15 km/h). The humidity is high at 93% and visibility is 6 miles (10 km). Overall, it seems to be a cool, partly cloudy day with moderate winds in San Francisco.", + "text": " the current weather conditions", "type": "text", "index": 0 } ], - "type": "ai", + "type": "AIMessageChunk", + ... + }, + { + "graph_id": "agent", + "langgraph_node": "agent", ... } - ] \ No newline at end of file + ] + + + + Receiving new event of type: messages... + [ + { + "content": [ + { + "text": " in San Francisco.", + "type": "text", + "index": 0 + } + ], + "type": "AIMessageChunk", + ... + }, + { + "graph_id": "agent", + "langgraph_node": "agent", + ... + } + ] + + ... \ No newline at end of file diff --git a/docs/docs/concepts/streaming.md b/docs/docs/concepts/streaming.md index 052715a01..4cff01497 100644 --- a/docs/docs/concepts/streaming.md +++ b/docs/docs/concepts/streaming.md @@ -153,7 +153,7 @@ guide for that [here](../how-tos/streaming-tokens.ipynb). Streaming is critical for making LLM applications feel responsive to end users. When creating a streaming run, the streaming mode determines what data is streamed back to the API client. LangGraph Platform supports five streaming modes: - `values`: Stream the full state of the graph after each [super-step](https://langchain-ai.github.io/langgraph/concepts/low_level/#graphs) is executed. See the [how-to guide](../cloud/how-tos/stream_values.md) for streaming values. -- `messages`: Stream complete messages (at the end of node execution) as well as tokens for any messages generated inside a node. This mode is primarily meant for powering chat applications. This is only an option if your graph contains a `messages` key. See the [how-to guide](../cloud/how-tos/stream_messages.md) for streaming messages. +- `messages-tuple`: Stream LLM tokens for any messages generated inside a node. This mode is primarily meant for powering chat applications. See the [how-to guide](../cloud/how-tos/stream_messages.md) for streaming messages. - `updates`: Streams updates to the state of the graph after each node is executed. See the [how-to guide](../cloud/how-tos/stream_updates.md) for streaming updates. - `events`: Stream all events (including the state of the graph) that occur during graph execution. See the [how-to guide](../cloud/how-tos/stream_events.md) for streaming events. This can be used to do token-by-token streaming for LLMs. - `debug`: Stream debug events throughout graph execution. See the [how-to guide](../cloud/how-tos/stream_debug.md) for streaming debug events. @@ -162,90 +162,11 @@ You can also specify multiple streaming modes at the same time. See the [how-to See the [API reference](../cloud/reference/api/api_ref.html#tag/threads-runs/POST/threads/{thread_id}/runs/stream) for how to create streaming runs. -Streaming modes `values`, `updates`, and `debug` are very similar to modes available in the LangGraph library - for a deeper conceptual explanation of those, you can see the [previous section](#streaming-graph-outputs-stream-and-astream). +Streaming modes `values`, `updates`, `messages-tuple` and `debug` are very similar to modes available in the LangGraph library - for a deeper conceptual explanation of those, you can see the [previous section](#streaming-graph-outputs-stream-and-astream). Streaming mode `events` is the same as using `.astream_events` in the LangGraph library - for a deeper conceptual explanation of this, you can see the [previous section](#streaming-graph-outputs-stream-and-astream). -### `stream_mode="messages"` - -Streaming mode `messages` is for streaming back messages from the LLM. Assuming we have a simple [ReAct](./agentic_concepts.md#react-implementation)-style agent deployed, what does this stream look like? - All events emitted have two attributes: - `event`: This is the name of the event -- `data`: This is data associated with the event - -!!! note - Streaming mode `messages` is different from the one in the LangGraph library: - - - LangGraph Server streams event objects with messages in the `data` field, while LangGraph library streams tuples (`AIMessageChunk`, metadata). - - In LangGraph Server, metadata is streamed only once per message (`messages/metadata`), before the individual tokens are streamed (`messages/partial`), while in LangGraph library it's streamed with every `AIMessageChunk` (for each LLM token). - - LangGraph Server also streams additional events (`metadata`, `messages/complete`, see below for more details). - -Let's run it on a question that should trigger a tool call: - -```python -thread = await client.threads.create() -input = {"messages": [{"role": "user", "content": "what's the weather in sf?"}]} - -events = [] -async for event in client.runs.stream( - thread["thread_id"], - assistant_id="agent", # This may need to change depending on the graph you deployed - input=input, - stream_mode="messages", -): - print(event.event) -``` -```shell -metadata -messages/metadata -messages/partial -... -messages/partial -messages/metadata -messages/complete -messages/metadata -messages/partial -... -messages/partial -end -``` - -We first get some `metadata` - this is metadata about the run. - -```python -StreamPart(event='metadata', data={'run_id': '1ef657cf-ae55-6f65-97d4-f4ed1dbdabc6'}) -``` - -We then get a `messages/metadata` - this is letting us know that a new message is starting and provides additional information about the LLM as well as the node where the LLM is invoked. - -```python -StreamPart(event='messages/metadata', data={'run-985c0f14-9f43-40d4-a505-4637fc58e333': {'metadata': {'created_by': 'system', 'run_id': '1ef657de-7594-66df-8eb2-31518e4a1ee2', 'graph_id': 'agent', 'thread_id': 'c178eab5-e293-423c-8e7d-1d113ffe7cd9', 'model_name': 'openai', 'assistant_id': 'fe096781-5601-53d2-b2f6-0d3403f7e9ca', 'langgraph_step': 1, 'langgraph_node': 'agent', 'langgraph_triggers': ['start:agent'], 'langgraph_task_idx': 0, 'ls_provider': 'openai', 'ls_model_name': 'gpt-4o', 'ls_model_type': 'chat', 'ls_temperature': 0.0}}}) -``` - -We then get a BUNCH of `messages/partial` events - these are the individual tokens from the LLM! In the case below, we can see the START of a tool call. - -```python -StreamPart(event='messages/partial', data=[{'content': '', 'additional_kwargs': {'tool_calls': [{'index': 0, 'id': 'call_w8Hr8dHGuZCPgRfd5FqRBArs', 'function': {'arguments': '', 'name': 'tavily_search_results_json'}, 'type': 'function'}]}, 'response_metadata': {}, 'type': 'ai', 'name': None, 'id': 'run-985c0f14-9f43-40d4-a505-4637fc58e333', 'example': False, 'tool_calls': [], 'invalid_tool_calls': [{'name': 'tavily_search_results_json', 'args': '', 'id': 'call_w8Hr8dHGuZCPgRfd5FqRBArs', 'error': None}], 'usage_metadata': None}]) -``` - -The last `messages/partial` event for a given message will contain all of the tokens streamed for that message. In our case, it is now a complete tool call: - -```python -StreamPart(event='messages/partial', data=[{'content': '', 'additional_kwargs': {'tool_calls': [{'index': 0, 'id': 'call_w8Hr8dHGuZCPgRfd5FqRBArs', 'function': {'arguments': '{"query":"current weather in San Francisco"}', 'name': 'tavily_search_results_json'}, 'type': 'function'}]}, 'response_metadata': {'finish_reason': 'tool_calls', 'model_name': 'gpt-4o-2024-05-13', 'system_fingerprint': 'fp_157b3831f5'}, 'type': 'ai', 'name': None, 'id': 'run-985c0f14-9f43-40d4-a505-4637fc58e333', 'example': False, 'tool_calls': [{'name': 'tavily_search_results_json', 'args': {'query': 'current weather in San Francisco'}, 'id': 'call_w8Hr8dHGuZCPgRfd5FqRBArs'}], 'invalid_tool_calls': [], 'usage_metadata': None}]) -``` - -After that, we get another `messages/metadata`, now followed by a `messages/complete` event. This event is emitted for a tool message - our agent has called a tool, gotten a response, and now inserting it into the state in the form of a tool message. - -```python -StreamPart(event='messages/complete', data=[{'content': '[{"url": "https://www.weatherapi.com/", "content": "{\'location\': {\'name\': \'San Francisco\', \'region\': \'California\', \'country\': \'United States of America\', \'lat\': 37.78, \'lon\': -122.42, \'tz_id\': \'America/Los_Angeles\', \'localtime_epoch\': 1724877689, \'localtime\': \'2024-08-28 13:41\'}, \'current\': {\'last_updated_epoch\': 1724877000, \'last_updated\': \'2024-08-28 13:30\', \'temp_c\': 23.3, \'temp_f\': 73.9, \'is_day\': 1, \'condition\': {\'text\': \'Partly cloudy\', \'icon\': \'//cdn.weatherapi.com/weather/64x64/day/116.png\', \'code\': 1003}, \'wind_mph\': 15.0, \'wind_kph\': 24.1, \'wind_degree\': 310, \'wind_dir\': \'NW\', \'pressure_mb\': 1014.0, \'pressure_in\': 29.93, \'precip_mm\': 0.0, \'precip_in\': 0.0, \'humidity\': 57, \'cloud\': 25, \'feelslike_c\': 25.0, \'feelslike_f\': 77.1, \'windchill_c\': 20.9, \'windchill_f\': 69.6, \'heatindex_c\': 23.3, \'heatindex_f\': 74.0, \'dewpoint_c\': 12.9, \'dewpoint_f\': 55.2, \'vis_km\': 16.0, \'vis_miles\': 9.0, \'uv\': 6.0, \'gust_mph\': 19.5, \'gust_kph\': 31.3}}"}]', 'additional_kwargs': {}, 'response_metadata': {}, 'type': 'tool', 'name': 'tavily_search_results_json', 'id': '0112eba5-7660-4375-9f24-c7a1d6777b97', 'tool_call_id': 'call_w8Hr8dHGuZCPgRfd5FqRBArs'}]) -``` - -After that, we see the agent doing another LLM call and streaming back a response. We then get an `end` event: - -```python -StreamPart(event='end', data=None) -``` - -And that's it! This is more focused streaming mode specifically focused on streaming back messages. See this [how-to guide](../cloud/how-tos/stream_messages.md) for more information. \ No newline at end of file +- `data`: This is data associated with the event \ No newline at end of file diff --git a/docs/mkdocs.yml b/docs/mkdocs.yml index e2813b141..08f15d8bd 100644 --- a/docs/mkdocs.yml +++ b/docs/mkdocs.yml @@ -54,6 +54,9 @@ plugins: - search: separator: '[\s\u200b\-_,:!=\[\]()"`/]+|\.(?!\d)|&[lg]t;|(?!\b)(?=[A-Z][a-z])' - autorefs + - redirects: + redirect_maps: + 'cloud/concepts/api.md': 'concepts/index.md#langgraph-platform' - mkdocstrings: handlers: python: From dbb1958be5accac08783c78f2b23e11783ce2905 Mon Sep 17 00:00:00 2001 From: Vadym Barda Date: Fri, 1 Nov 2024 15:40:21 -0400 Subject: [PATCH 22/60] docs: add more redirects & remove unused cloud pages (#2301) --- docs/docs/cloud/concepts/api.md | 101 ------------------ .../docs/cloud/concepts/assistant_version.png | Bin 263115 -> 0 bytes docs/docs/cloud/concepts/cloud.md | 28 ----- .../concepts/langgraph_cloud_architecture.png | Bin 161117 -> 0 bytes docs/docs/cloud/faq/studio.md | 73 ------------- .../cloud/how-tos/assistant_versioning.md | 2 +- docs/docs/cloud/how-tos/copy_threads.md | 2 +- docs/docs/cloud/how-tos/enqueue_concurrent.md | 2 +- docs/docs/cloud/how-tos/index.md | 85 --------------- .../cloud/how-tos/interrupt_concurrent.md | 2 +- docs/docs/cloud/how-tos/reject_concurrent.md | 2 +- .../docs/cloud/how-tos/rollback_concurrent.md | 2 +- docs/docs/cloud/index.md | 44 -------- docs/docs/concepts/assistants.md | 2 +- docs/docs/concepts/high_level.md | 2 +- docs/mkdocs.yml | 6 +- 16 files changed, 13 insertions(+), 340 deletions(-) delete mode 100644 docs/docs/cloud/concepts/api.md delete mode 100644 docs/docs/cloud/concepts/assistant_version.png delete mode 100644 docs/docs/cloud/concepts/cloud.md delete mode 100644 docs/docs/cloud/concepts/langgraph_cloud_architecture.png delete mode 100644 docs/docs/cloud/faq/studio.md delete mode 100644 docs/docs/cloud/how-tos/index.md delete mode 100644 docs/docs/cloud/index.md diff --git a/docs/docs/cloud/concepts/api.md b/docs/docs/cloud/concepts/api.md deleted file mode 100644 index 7b2f56b2d..000000000 --- a/docs/docs/cloud/concepts/api.md +++ /dev/null @@ -1,101 +0,0 @@ -# API Concepts - -This page describes the high-level concepts of the LangGraph Cloud API. The conceptual guide of LangGraph (Python library) is [here](../../concepts/high_level.md). - -## Data Models - -The LangGraph Cloud API consists of a few core data models: [Assistants](#assistants), [Threads](#threads), [Runs](#runs), and [Cron Jobs](#cron-jobs). - -### Assistants - -When building agents, it is fairly common to make rapid changes that *do not* alter the graph logic. For example, simply changing prompts or the LLM selection can have significant impacts on the behavior of the agents. Assistants offer an easy way to make and save these types of changes to agent configuration. This can have at least two use-cases: - -* Assistants give developers a quick and easy way to modify and version graph version for experimentation. -* Assistants can be modified via LangGraph Studio, offering a no-code way to configure agents (e.g., for business users). - -#### Configuring Assistants - -In practice, an assistant is just an *instance* of a graph with a specific configuration. Because of this, multiple assistants can reference the same graph but can contain different configurations, such as prompts, models, and other graph configuration options. The LangGraph Cloud API provides several endpoints for creating and managing assistants. See the [API reference](../reference/api/api_ref.html#tag/assistantscreate) and [this how-to](../how-tos/configuration_cloud.md) for more details on how to create assistants. - -#### Versioning Assistants - -![assistant versions](./assistant_version.png) - -Once you've created an assistant, you can save and version it to track changes to the configuration over time. You can think about this at three levels: - -1) The graph lays out the general agent application logic -2) The agent configuration options represent parameters that can be changed -3) Assistant versions save and track specific settings of the agent configuration options - -For example, if you have an agent that helps for planning trips, you can create a new assistant *for each user* that passes specific user preferences (e.g., desired airline and car service). As each user interacts with their own assistant, assistant versions can be saved that track the specific desires of the user. Read [this how-to](../how-tos/assistant_versioning.md) to learn how you can use assistant versioning through both the [Studio](../how-tos/index.md/#langgraph-studio) and the SDK. - -### Threads - -A thread contains the accumulated state of a group of runs. If a run is executed on a thread, then the [state][state] of the underlying graph of the assistant will be persisted to the thread. A thread's current and historical state can be retrieved. To persist state, a thread must be created prior to executing a run. - -The state of a thread at a particular point in time is called a checkpoint. - -For more on threads and checkpoints, see this section of the [LangGraph conceptual guide](../../concepts/low_level.md#persistence). - -The LangGraph Cloud API provides several endpoints for creating and managing threads and thread state. See the [API reference](../reference/api/api_ref.html#tag/threadscreate) for more details. - -### Runs - -A run is an invocation of an assistant. Each run may have its own input, configuration, and metadata, which may affect execution and output of the underlying graph. A run can optionally be executed on a thread. - -The LangGraph Cloud API provides several endpoints for creating and managing runs. See the [API reference](../reference/api/api_ref.html#tag/runscreate) for more details. - -### Cron Jobs - -It's often useful to run graphs on some schedule. LangGraph Cloud supports cron jobs, which run on a user defined schedule. The user specifies a schedule, an assistant, and some input. After than, on the specified schedule LangGraph cloud will: - -- Create a new thread with the specified assistant -- Send the specified input to that thread - -Note that this sends the same input to the thread every time. See the [how-to guide](../how-tos/cron_jobs.md) for creating cron jobs. - -The LangGraph Cloud API provides several endpoints for creating and managing cron jobs. See the [API reference](../reference/api/api_ref.html#tag/runscreate/POST/threads/{thread_id}/runs/crons) for more details. - -## Features - -The LangGraph Cloud API offers several features to support complex agent architectures. - -### Streaming - -[Streaming](../../concepts/streaming.md) is critical for making LLM applications feel responsive to end users. When creating a streaming run, the streaming mode determines what data is streamed back to the API client. LangGraph Platform supports five streaming modes: `values`, `updates`, `messages-tuple`, `events`, and `debug`. See these [how-to guides](../../how-tos/index.md#streaming_1) and the [API reference](../reference/api/api_ref.html#tag/thread-runs/POST/threads/%7Bthread_id%7D/runs/stream) for more details. - -### Stateless Runs - -All runs use the built-in checkpointer to store checkpoints for runs. However, it can often be useful to just kick off a run without worrying about explicitly creating a thread and without wanting to keep those checkpointers around. Stateless runs allow you to do this by exposing an endpoint that: - -- Takes in user input -- Under the hood, creates a thread -- Runs the agent but skips all checkpointing steps -- Cleans up the thread afterwards - -Stateless runs are still retried as regular retries are per node, while everything still in memory, so doesn't use checkpoints. - -The only difference is in stateless background runs, if the task worker dies halfway (not because the run itself failed, for some external reason) then the whole run will be retried like any background run, but - -- whereas a stateful background run would retry from the last successful checkpoint -- a stateless background run would retry from the beginning - -See the [how-to guide](../how-tos/stateless_runs.md) for creating stateless runs. - -### Webhooks - -For all types of runs, langgraph cloud supports completion webhooks. When you create the run you can pass a webhook URL to be called when the completes (successfully or not). This is especially useful for background runs and cron jobs, as the webhook can give you an indication the run has completed and you can perform further actions for your appilcation. - -See this [how-to guide](../how-tos/webhooks.md) to learn about how to use webhooks with LangGraph Cloud. - -## Deployment - -The LangGraph Cloud offers several features to support secure and robost deployments. - -### Authentication - -LangGraph applications deployed to LangGraph Cloud are automatically configured with LangSmith authentication. In order to call the API, a valid LangSmith API key is required. - -### Local Testing - -Before deploying your app in production to LangGraph Cloud, you may wish to test out your graph locally in order to ensure that everything is running as expected. Luckily, LangGraph makes this easy for you through use of the LangGraph CLI. Read more in this [how-to guide](../deployment/test_locally.md) or look at the [CLI reference](../reference/cli.md) to learn more. diff --git a/docs/docs/cloud/concepts/assistant_version.png b/docs/docs/cloud/concepts/assistant_version.png deleted file mode 100644 index 3406673fc0ddedd95e925ba79636db68c7518ccb..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 263115 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

aY3LN)3=I$Ef_qc3#JfK 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

\n", "

Note

\n", "

\n", - " The first thing you do when you define a graph is define the State of the graph. The State consists of the schema of the graph as well as reducer functions which specify how to apply updates to the state. In our example State is a TypedDict with a single key: messages. The messages key is annotated with the add_messages reducer function, which tells LangGraph to append new messages to the existing list, rather than overwriting it. State keys without an annotation will be overwritten by each update, storing the most recent value. Check out this conceptual guide to learn more about state, reducers and other low-level concepts.\n", + " The first thing you do when you define a graph is define the State of the graph. The State consists of the schema of the graph as well as reducer functions which specify how to apply updates to the state. In our example State is a TypedDict with a single key: messages. The messages key is annotated with the add_messages reducer function, which tells LangGraph to append new messages to the existing list, rather than overwriting it. State keys without an annotation will be overwritten by each update, storing the most recent value. Check out this conceptual guide to learn more about state, reducers and other low-level concepts.\n", "

\n", "
" ] From eeebd44a8785aa10076c421de47023a7a0fe6e20 Mon Sep 17 00:00:00 2001 From: Vadym Barda Date: Fri, 1 Nov 2024 18:16:09 -0400 Subject: [PATCH 28/60] docs: add subgraph streaming example for remote graph (#2306) --- docs/docs/how-tos/use-remote-graph.md | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/docs/docs/how-tos/use-remote-graph.md b/docs/docs/how-tos/use-remote-graph.md index ecca665e2..819c46f4b 100644 --- a/docs/docs/how-tos/use-remote-graph.md +++ b/docs/docs/how-tos/use-remote-graph.md @@ -217,6 +217,12 @@ Since the `RemoteGraph` behaves the same way as a regular `CompiledGraph`, it ca "messages": [{"role": "user", "content": "what's the weather in sf"}] }) print(result) + + # stream outputs from both the parent graph and subgraph + for chunk in graph.stream({ + "messages": [{"role": "user", "content": "what's the weather in sf"}] + }, subgraphs=True): + print(chunk) ``` === "JavaScript" @@ -240,4 +246,11 @@ Since the `RemoteGraph` behaves the same way as a regular `CompiledGraph`, it ca messages: [{ role: "user", content: "what's the weather in sf" }] }); console.log(result); + + // stream outputs from both the parent graph and subgraph + for await (const chunk of await graph.stream({ + messages: [{ role: "user", content: "what's the weather in la" }] + }, { subgraphs: true })) { + console.log(chunk); + } ``` \ No newline at end of file From cdd789956426eb8ff16e9f74d6529bd7a2cc91d4 Mon Sep 17 00:00:00 2001 From: Andrew Nguonly Date: Fri, 1 Nov 2024 15:21:39 -0700 Subject: [PATCH 29/60] RemoteGraph: If node name is not present, fallback to node id as the node name (#2304) --- libs/langgraph/langgraph/pregel/remote.py | 14 ++++++++++++-- libs/langgraph/tests/test_remote_graph.py | 20 ++++++++++---------- 2 files changed, 22 insertions(+), 12 deletions(-) diff --git a/libs/langgraph/langgraph/pregel/remote.py b/libs/langgraph/langgraph/pregel/remote.py index d9ee53e8c..abe27eb28 100644 --- a/libs/langgraph/langgraph/pregel/remote.py +++ b/libs/langgraph/langgraph/pregel/remote.py @@ -133,10 +133,20 @@ class RemoteGraph(PregelProtocol): nodes = {} for node in graph["nodes"]: node_id = str(node["id"]) + node_data = node.get("data", {}) + + # Get node name from node_data if available. If not, use node_id. + node_name = node.get("name") + if node_name is None: + if isinstance(node_data, dict): + node_name = node_data.get("name", node_id) + else: + node_name = node_id + nodes[node_id] = DrawableNode( id=node_id, - name=node.get("name", ""), - data=node.get("data", {}), + name=node_name, + data=node_data, metadata=node.get("metadata"), ) return nodes diff --git a/libs/langgraph/tests/test_remote_graph.py b/libs/langgraph/tests/test_remote_graph.py index 83e5f913b..21fb8278f 100644 --- a/libs/langgraph/tests/test_remote_graph.py +++ b/libs/langgraph/tests/test_remote_graph.py @@ -54,7 +54,7 @@ def test_get_graph(): "type": "runnable", "data": { "id": ["langgraph", "utils", "RunnableCallable"], - "name": "agent", + "name": "agent_1", }, }, ], @@ -71,13 +71,13 @@ def test_get_graph(): assert drawable_graph.nodes == { "__start__": DrawableNode( - id="__start__", name="", data="__start__", metadata=None + id="__start__", name="__start__", data="__start__", metadata=None ), - "__end__": DrawableNode(id="__end__", name="", data="__end__", metadata=None), + "__end__": DrawableNode(id="__end__", name="__end__", data="__end__", metadata=None), "agent": DrawableNode( id="agent", - name="", - data={"id": ["langgraph", "utils", "RunnableCallable"], "name": "agent"}, + name="agent_1", + data={"id": ["langgraph", "utils", "RunnableCallable"], "name": "agent_1"}, metadata=None, ), } @@ -101,7 +101,7 @@ async def test_aget_graph(): "type": "runnable", "data": { "id": ["langgraph", "utils", "RunnableCallable"], - "name": "agent", + "name": "agent_1", }, }, ], @@ -118,13 +118,13 @@ async def test_aget_graph(): assert drawable_graph.nodes == { "__start__": DrawableNode( - id="__start__", name="", data="__start__", metadata=None + id="__start__", name="__start__", data="__start__", metadata=None ), - "__end__": DrawableNode(id="__end__", name="", data="__end__", metadata=None), + "__end__": DrawableNode(id="__end__", name="__end__", data="__end__", metadata=None), "agent": DrawableNode( id="agent", - name="", - data={"id": ["langgraph", "utils", "RunnableCallable"], "name": "agent"}, + name="agent_1", + data={"id": ["langgraph", "utils", "RunnableCallable"], "name": "agent_1"}, metadata=None, ), } From bf3098a0759f7328b2e3d6211bb602ad8a5b2fd2 Mon Sep 17 00:00:00 2001 From: Tat Dat Duong Date: Sat, 2 Nov 2024 00:44:08 +0100 Subject: [PATCH 30/60] feat(cli): add support for other JS package managers based off package lock --- libs/cli/langgraph_cli/config.py | 24 +++++++++++++++++++++++- 1 file changed, 23 insertions(+), 1 deletion(-) diff --git a/libs/cli/langgraph_cli/config.py b/libs/cli/langgraph_cli/config.py index 49c1102b4..9293d164e 100644 --- a/libs/cli/langgraph_cli/config.py +++ b/libs/cli/langgraph_cli/config.py @@ -289,13 +289,35 @@ ENV LANGSERVE_GRAPHS='{json.dumps(config["graphs"])}' def node_config_to_docker(config_path: pathlib.Path, config: Config, base_image: str): faux_path = f"/deps/{config_path.parent.name}" + def test_file(file_name): + full_path = config_path.parent / file_name + try: + return full_path.is_file() + except OSError: + return False + + npm, yarn, pnpm = [ + test_file("package-lock.json"), + test_file("yarn.lock"), + test_file("pnpm-lock.yaml"), + ] + + if yarn: + install_cmd = "yarn install --frozen-lockfile" + elif pnpm: + install_cmd = "pnpm i --frozen-lockfile" + elif npm: + install_cmd = "npm ci" + else: + install_cmd = "npm i" + return f"""FROM {base_image}:{config['node_version']} {os.linesep.join(config["dockerfile_lines"])} ADD . {faux_path} -RUN cd {faux_path} && yarn install --frozen-lockfile +RUN cd {faux_path} && {install_cmd} ENV LANGSERVE_GRAPHS='{json.dumps(config["graphs"])}' From 46ffa0e8d4cbb5130917389825458c4a75c38061 Mon Sep 17 00:00:00 2001 From: Vadym Barda Date: Fri, 1 Nov 2024 20:27:17 -0400 Subject: [PATCH 31/60] langgraph: release 0.2.44 (#2308) --- libs/langgraph/pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libs/langgraph/pyproject.toml b/libs/langgraph/pyproject.toml index dd6d9c64a..1f711bc64 100644 --- a/libs/langgraph/pyproject.toml +++ b/libs/langgraph/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "langgraph" -version = "0.2.43" +version = "0.2.44" description = "Building stateful, multi-actor applications with LLMs" authors = [] license = "MIT" From 18cea39090dac951cead52f45760b7ff500f5679 Mon Sep 17 00:00:00 2001 From: Allen <113149736+allenc97@users.noreply.github.com> Date: Fri, 1 Nov 2024 17:37:16 -0700 Subject: [PATCH 32/60] fix(docs): function call in partial (#2297) Docs use prompts with partial + function call, which would result in a static datetime as opposed to a dynamic datetime being rendered on use. --- .../customer-support/customer-support.ipynb | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/docs/docs/tutorials/customer-support/customer-support.ipynb b/docs/docs/tutorials/customer-support/customer-support.ipynb index 9cf3ac479..73ca73925 100644 --- a/docs/docs/tutorials/customer-support/customer-support.ipynb +++ b/docs/docs/tutorials/customer-support/customer-support.ipynb @@ -1077,7 +1077,7 @@ " ),\n", " (\"placeholder\", \"{messages}\"),\n", " ]\n", - ").partial(time=datetime.now())\n", + ").partial(time=datetime.now)\n", "\n", "part_1_tools = [\n", " TavilySearchResults(max_results=1),\n", @@ -1893,7 +1893,7 @@ " ),\n", " (\"placeholder\", \"{messages}\"),\n", " ]\n", - ").partial(time=datetime.now())\n", + ").partial(time=datetime.now)\n", "\n", "part_2_tools = [\n", " TavilySearchResults(max_results=1),\n", @@ -2472,7 +2472,7 @@ " ),\n", " (\"placeholder\", \"{messages}\"),\n", " ]\n", - ").partial(time=datetime.now())\n", + ").partial(time=datetime.now)\n", "\n", "\n", "# \"Read\"-only tools (such as retrievers) don't need a user confirmation to use\n", @@ -3183,7 +3183,7 @@ " ),\n", " (\"placeholder\", \"{messages}\"),\n", " ]\n", - ").partial(time=datetime.now())\n", + ").partial(time=datetime.now)\n", "\n", "update_flight_safe_tools = [search_flights]\n", "update_flight_sensitive_tools = [update_ticket_to_new_flight, cancel_ticket]\n", @@ -3215,7 +3215,7 @@ " ),\n", " (\"placeholder\", \"{messages}\"),\n", " ]\n", - ").partial(time=datetime.now())\n", + ").partial(time=datetime.now)\n", "\n", "book_hotel_safe_tools = [search_hotels]\n", "book_hotel_sensitive_tools = [book_hotel, update_hotel, cancel_hotel]\n", @@ -3247,7 +3247,7 @@ " ),\n", " (\"placeholder\", \"{messages}\"),\n", " ]\n", - ").partial(time=datetime.now())\n", + ").partial(time=datetime.now)\n", "\n", "book_car_rental_safe_tools = [search_car_rentals]\n", "book_car_rental_sensitive_tools = [\n", @@ -3282,7 +3282,7 @@ " ),\n", " (\"placeholder\", \"{messages}\"),\n", " ]\n", - ").partial(time=datetime.now())\n", + ").partial(time=datetime.now)\n", "\n", "book_excursion_safe_tools = [search_trip_recommendations]\n", "book_excursion_sensitive_tools = [book_excursion, update_excursion, cancel_excursion]\n", @@ -3389,7 +3389,7 @@ " ),\n", " (\"placeholder\", \"{messages}\"),\n", " ]\n", - ").partial(time=datetime.now())\n", + ").partial(time=datetime.now)\n", "primary_assistant_tools = [\n", " TavilySearchResults(max_results=1),\n", " search_flights,\n", From 7854a19dac71371fae745b7c1f72aa4abc6bd284 Mon Sep 17 00:00:00 2001 From: Tat Dat Duong Date: Sat, 2 Nov 2024 02:05:42 +0100 Subject: [PATCH 33/60] Update test --- libs/cli/tests/unit_tests/test_config.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libs/cli/tests/unit_tests/test_config.py b/libs/cli/tests/unit_tests/test_config.py index f53313986..e2afd214c 100644 --- a/libs/cli/tests/unit_tests/test_config.py +++ b/libs/cli/tests/unit_tests/test_config.py @@ -250,7 +250,7 @@ def test_config_to_docker_nodejs(): ARG meow ARG foo ADD . /deps/unit_tests -RUN cd /deps/unit_tests && yarn install --frozen-lockfile +RUN cd /deps/unit_tests && npm i ENV LANGSERVE_GRAPHS='{"agent": "./graphs/agent.js:graph"}' WORKDIR /deps/unit_tests""" From 39dcafcdeae9d3d9e5e572b6d944b6dda1eedfcf Mon Sep 17 00:00:00 2001 From: Tat Dat Duong Date: Sat, 2 Nov 2024 02:48:47 +0100 Subject: [PATCH 34/60] feat(sdk-js): improve types for drawable graph, interrupts and metadata --- libs/sdk-js/src/schema.ts | 31 ++++++++++++++++++++++++++++--- libs/sdk-js/src/types.ts | 4 ++-- 2 files changed, 30 insertions(+), 5 deletions(-) diff --git a/libs/sdk-js/src/schema.ts b/libs/sdk-js/src/schema.ts index 86a668d01..52908a47b 100644 --- a/libs/sdk-js/src/schema.ts +++ b/libs/sdk-js/src/schema.ts @@ -79,7 +79,17 @@ export interface GraphSchema { export type Subgraphs = Record; -export type Metadata = Optional>; +export type Metadata = Optional<{ + source?: "input" | "loop" | "update" | (string & {}); + + step?: number; + + writes?: Record | null; + + parents?: Record; + + [key: string]: unknown; +}>; export interface AssistantBase { /** The ID of the assistant. */ @@ -110,7 +120,22 @@ export interface Assistant extends AssistantBase { /** The name of the assistant */ name: string; } -export type AssistantGraph = Record>>; + +export interface AssistantGraph { + nodes: Array<{ + id: string | number; + name?: string; + // eslint-disable-next-line @typescript-eslint/no-explicit-any + data?: Record | string; + metadata?: unknown; + }>; + edges: Array<{ + source: string; + target: string; + data?: string; + conditional?: boolean; + }>; +} export interface Thread { /** The ID of the thread. */ @@ -184,7 +209,7 @@ export interface ThreadTask { id: string; name: string; error: Optional; - interrupts: Array>; + interrupts: Array<{ value: unknown; when: "during" }>; checkpoint: Optional; state: Optional; } diff --git a/libs/sdk-js/src/types.ts b/libs/sdk-js/src/types.ts index 3f4e90764..93b025fa4 100644 --- a/libs/sdk-js/src/types.ts +++ b/libs/sdk-js/src/types.ts @@ -45,12 +45,12 @@ interface RunsInvokePayload { /** * Interrupt execution before entering these nodes. */ - interruptBefore?: string[]; + interruptBefore?: "*" | string[]; /** * Interrupt execution after leaving these nodes. */ - interruptAfter?: string[]; + interruptAfter?: "*" | string[]; /** * Strategy to handle concurrent runs on the same thread. Only relevant if From 8455771eb964773ce313145d02a76bc320c3f32b Mon Sep 17 00:00:00 2001 From: Tat Dat Duong Date: Mon, 4 Nov 2024 13:20:36 +0100 Subject: [PATCH 35/60] Bump to 0.0.21 --- libs/sdk-js/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libs/sdk-js/package.json b/libs/sdk-js/package.json index 71a0976bb..8b9718872 100644 --- a/libs/sdk-js/package.json +++ b/libs/sdk-js/package.json @@ -1,6 +1,6 @@ { "name": "@langchain/langgraph-sdk", - "version": "0.0.20", + "version": "0.0.21", "description": "Client library for interacting with the LangGraph API", "type": "module", "packageManager": "yarn@1.22.19", From 30937d040613d12a7c2e444763f5f77579d1b79f Mon Sep 17 00:00:00 2001 From: Tat Dat Duong Date: Mon, 4 Nov 2024 13:50:41 +0100 Subject: [PATCH 36/60] feat(cli): add JS prebuild script --- libs/cli/langgraph_cli/config.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/libs/cli/langgraph_cli/config.py b/libs/cli/langgraph_cli/config.py index 9293d164e..202e160c5 100644 --- a/libs/cli/langgraph_cli/config.py +++ b/libs/cli/langgraph_cli/config.py @@ -321,7 +321,10 @@ RUN cd {faux_path} && {install_cmd} ENV LANGSERVE_GRAPHS='{json.dumps(config["graphs"])}' -WORKDIR {faux_path}""" +WORKDIR {faux_path} + +RUN (test ! -f /api/langgraph_api/js/build.mts && echo "Prebuild script not found, skipping") || tsx /api/langgraph_api/js/build.mts +""" def config_to_docker(config_path: pathlib.Path, config: Config, base_image: str): From 5deedfe5480d270f6ec945585ff4348cddbc1514 Mon Sep 17 00:00:00 2001 From: Tat Dat Duong Date: Mon, 4 Nov 2024 13:51:46 +0100 Subject: [PATCH 37/60] Bump to 0.1.53 --- libs/cli/pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libs/cli/pyproject.toml b/libs/cli/pyproject.toml index 20fe010d9..05ff3e506 100644 --- a/libs/cli/pyproject.toml +++ b/libs/cli/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "langgraph-cli" -version = "0.1.52" +version = "0.1.53" description = "CLI for interacting with LangGraph API" authors = [] license = "MIT" From e1467c27cb15990029de4299a197661d359e4702 Mon Sep 17 00:00:00 2001 From: Tat Dat Duong Date: Mon, 4 Nov 2024 13:52:40 +0100 Subject: [PATCH 38/60] Remove newline --- libs/cli/langgraph_cli/config.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/libs/cli/langgraph_cli/config.py b/libs/cli/langgraph_cli/config.py index 202e160c5..16473083f 100644 --- a/libs/cli/langgraph_cli/config.py +++ b/libs/cli/langgraph_cli/config.py @@ -323,8 +323,7 @@ ENV LANGSERVE_GRAPHS='{json.dumps(config["graphs"])}' WORKDIR {faux_path} -RUN (test ! -f /api/langgraph_api/js/build.mts && echo "Prebuild script not found, skipping") || tsx /api/langgraph_api/js/build.mts -""" +RUN (test ! -f /api/langgraph_api/js/build.mts && echo "Prebuild script not found, skipping") || tsx /api/langgraph_api/js/build.mts""" def config_to_docker(config_path: pathlib.Path, config: Config, base_image: str): From 44ff5f1a5a54b1eb74b1bfc62e3f35970f595b5c Mon Sep 17 00:00:00 2001 From: Tat Dat Duong Date: Mon, 4 Nov 2024 14:00:02 +0100 Subject: [PATCH 39/60] Update tests --- libs/cli/tests/unit_tests/test_config.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/libs/cli/tests/unit_tests/test_config.py b/libs/cli/tests/unit_tests/test_config.py index e2afd214c..cff16ead6 100644 --- a/libs/cli/tests/unit_tests/test_config.py +++ b/libs/cli/tests/unit_tests/test_config.py @@ -252,7 +252,8 @@ ARG foo ADD . /deps/unit_tests RUN cd /deps/unit_tests && npm i ENV LANGSERVE_GRAPHS='{"agent": "./graphs/agent.js:graph"}' -WORKDIR /deps/unit_tests""" +WORKDIR /deps/unit_tests +RUN (test ! -f /api/langgraph_api/js/build.mts && echo "Prebuild script not found, skipping") || tsx /api/langgraph_api/js/build.mts""" assert clean_empty_lines(actual_docker_stdin) == expected_docker_stdin From 014f8485a6127a6b258c66065a4fd45ca14be2ae Mon Sep 17 00:00:00 2001 From: Nuno Campos Date: Mon, 4 Nov 2024 11:23:53 -0800 Subject: [PATCH 40/60] lib: Unset skip_done_tasks after each tick of the loop --- libs/langgraph/langgraph/pregel/loop.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/libs/langgraph/langgraph/pregel/loop.py b/libs/langgraph/langgraph/pregel/loop.py index 2ba8af3a6..b58ee4da7 100644 --- a/libs/langgraph/langgraph/pregel/loop.py +++ b/libs/langgraph/langgraph/pregel/loop.py @@ -399,6 +399,9 @@ class PregelLoop(LoopProtocol): for task in self.tasks.values(): if task.writes: self._output_writes(task.id, task.writes, cached=True) + elif not self.skip_done_tasks: + # "not skip_done_tasks" only applies to first tick after resuming + self.skip_done_tasks = True # if all tasks have finished, re-tick if all(task.writes for task in self.tasks.values()): From 2e656d9145191c43627b8b91383cc093ede309cd Mon Sep 17 00:00:00 2001 From: Vadym Barda Date: Mon, 4 Nov 2024 14:26:34 -0500 Subject: [PATCH 41/60] langgraph: add config metadata to pregel loop (#2323) --- libs/langgraph/langgraph/pregel/__init__.py | 12 +- libs/langgraph/langgraph/pregel/loop.py | 2 + libs/langgraph/tests/test_prebuilt.py | 2 + libs/langgraph/tests/test_pregel.py | 392 ++++++++++++++++++-- libs/langgraph/tests/test_pregel_async.py | 338 +++++++++++++++-- libs/langgraph/tests/test_remote_graph.py | 8 +- 6 files changed, 695 insertions(+), 59 deletions(-) diff --git a/libs/langgraph/langgraph/pregel/__init__.py b/libs/langgraph/langgraph/pregel/__init__.py index e6a5638d2..2525081d1 100644 --- a/libs/langgraph/langgraph/pregel/__init__.py +++ b/libs/langgraph/langgraph/pregel/__init__.py @@ -814,7 +814,7 @@ class Pregel(PregelProtocol): raise ValueError(f"Subgraph {recast_checkpoint_ns} not found") # get last checkpoint - config = merge_configs(self.config, config) if self.config else config + config = ensure_config(self.config, config) saved = checkpointer.get_tuple(config) checkpoint = copy_checkpoint(saved.checkpoint) if saved else empty_checkpoint() checkpoint_previous_versions = ( @@ -826,14 +826,17 @@ class Pregel(PregelProtocol): config, {CONFIG_KEY_CHECKPOINT_NS: config[CONF].get(CONFIG_KEY_CHECKPOINT_NS, "")}, ) + checkpoint_metadata = config["metadata"] if saved: checkpoint_config = patch_configurable(config, saved.config[CONF]) + checkpoint_metadata = {**saved.metadata, **checkpoint_metadata} # find last node that updated the state, if not provided if values is None and as_node is None: next_config = checkpointer.put( checkpoint_config, create_checkpoint(checkpoint, None, step), { + **checkpoint_metadata, "source": "update", "step": step + 1, "writes": {}, @@ -922,6 +925,7 @@ class Pregel(PregelProtocol): checkpoint_config, checkpoint, { + **checkpoint_metadata, "source": "update", "step": step + 1, "writes": {as_node: values}, @@ -966,7 +970,7 @@ class Pregel(PregelProtocol): raise ValueError(f"Subgraph {recast_checkpoint_ns} not found") # get last checkpoint - config = merge_configs(self.config, config) if self.config else config + config = ensure_config(self.config, config) saved = await checkpointer.aget_tuple(config) checkpoint = copy_checkpoint(saved.checkpoint) if saved else empty_checkpoint() checkpoint_previous_versions = ( @@ -978,14 +982,17 @@ class Pregel(PregelProtocol): config, {CONFIG_KEY_CHECKPOINT_NS: config[CONF].get(CONFIG_KEY_CHECKPOINT_NS, "")}, ) + checkpoint_metadata = config["metadata"] if saved: checkpoint_config = patch_configurable(config, saved.config[CONF]) + checkpoint_metadata = {**saved.metadata, **checkpoint_metadata} # find last node that updated the state, if not provided if values is None and as_node is None: next_config = await checkpointer.aput( checkpoint_config, create_checkpoint(checkpoint, None, step), { + **checkpoint_metadata, "source": "update", "step": step + 1, "writes": {}, @@ -1072,6 +1079,7 @@ class Pregel(PregelProtocol): checkpoint_config, checkpoint, { + **checkpoint_metadata, "source": "update", "step": step + 1, "writes": {as_node: values}, diff --git a/libs/langgraph/langgraph/pregel/loop.py b/libs/langgraph/langgraph/pregel/loop.py index 2ba8af3a6..91c513ede 100644 --- a/libs/langgraph/langgraph/pregel/loop.py +++ b/libs/langgraph/langgraph/pregel/loop.py @@ -491,6 +491,8 @@ class PregelLoop(LoopProtocol): ) def _put_checkpoint(self, metadata: CheckpointMetadata) -> None: + for k, v in self.config["metadata"].items(): + metadata.setdefault(k, v) # type: ignore # assign step and parents metadata["step"] = self.step metadata["parents"] = self.config[CONF].get(CONFIG_KEY_CHECKPOINT_MAP, {}) diff --git a/libs/langgraph/tests/test_prebuilt.py b/libs/langgraph/tests/test_prebuilt.py index 1ea506381..a6655a451 100644 --- a/libs/langgraph/tests/test_prebuilt.py +++ b/libs/langgraph/tests/test_prebuilt.py @@ -158,6 +158,7 @@ def test_no_modifier(request: pytest.FixtureRequest, checkpointer_name: str) -> "source": "loop", "writes": {"agent": {"messages": [AIMessage(content="hi?", id="0")]}}, "step": 1, + "thread_id": "123", } assert saved.pending_writes == [] @@ -189,6 +190,7 @@ async def test_no_modifier_async(checkpointer_name: str) -> None: "source": "loop", "writes": {"agent": {"messages": [AIMessage(content="hi?", id="0")]}}, "step": 1, + "thread_id": "123", } assert saved.pending_writes == [] diff --git a/libs/langgraph/tests/test_pregel.py b/libs/langgraph/tests/test_pregel.py index f8a5b49a8..563ce295e 100644 --- a/libs/langgraph/tests/test_pregel.py +++ b/libs/langgraph/tests/test_pregel.py @@ -748,7 +748,13 @@ def test_invoke_two_processes_in_out_interrupt( "checkpoint_id": AnyStr(), } }, - metadata={"parents": {}, "source": "loop", "step": 6, "writes": {"two": 5}}, + metadata={ + "parents": {}, + "source": "loop", + "step": 6, + "writes": {"two": 5}, + "thread_id": "1", + }, created_at=AnyStr(), parent_config=history[1].config, ), @@ -768,6 +774,7 @@ def test_invoke_two_processes_in_out_interrupt( "source": "loop", "step": 5, "writes": {"one": None}, + "thread_id": "1", }, created_at=AnyStr(), parent_config=history[2].config, @@ -788,6 +795,7 @@ def test_invoke_two_processes_in_out_interrupt( "source": "input", "step": 4, "writes": {"input": 3}, + "thread_id": "1", }, created_at=AnyStr(), parent_config=history[3].config, @@ -808,6 +816,7 @@ def test_invoke_two_processes_in_out_interrupt( "source": "loop", "step": 3, "writes": {"one": None}, + "thread_id": "1", }, created_at=AnyStr(), parent_config=history[4].config, @@ -828,6 +837,7 @@ def test_invoke_two_processes_in_out_interrupt( "source": "input", "step": 2, "writes": {"input": 20}, + "thread_id": "1", }, created_at=AnyStr(), parent_config=history[5].config, @@ -843,7 +853,13 @@ def test_invoke_two_processes_in_out_interrupt( "checkpoint_id": AnyStr(), } }, - metadata={"parents": {}, "source": "loop", "step": 1, "writes": {"two": 4}}, + metadata={ + "parents": {}, + "source": "loop", + "step": 1, + "writes": {"two": 4}, + "thread_id": "1", + }, created_at=AnyStr(), parent_config=history[6].config, ), @@ -863,6 +879,7 @@ def test_invoke_two_processes_in_out_interrupt( "source": "loop", "step": 0, "writes": {"one": None}, + "thread_id": "1", }, created_at=AnyStr(), parent_config=history[7].config, @@ -883,6 +900,7 @@ def test_invoke_two_processes_in_out_interrupt( "source": "input", "step": -1, "writes": {"input": 2}, + "thread_id": "1", }, created_at=AnyStr(), parent_config=None, @@ -949,6 +967,7 @@ def test_fork_always_re_runs_nodes( "source": "loop", "step": 5, "writes": {"add_one": 1}, + "thread_id": "1", }, created_at=AnyStr(), parent_config=history[1].config, @@ -969,6 +988,7 @@ def test_fork_always_re_runs_nodes( "source": "loop", "step": 4, "writes": {"add_one": 1}, + "thread_id": "1", }, created_at=AnyStr(), parent_config=history[2].config, @@ -989,6 +1009,7 @@ def test_fork_always_re_runs_nodes( "source": "loop", "step": 3, "writes": {"add_one": 1}, + "thread_id": "1", }, created_at=AnyStr(), parent_config=history[3].config, @@ -1009,6 +1030,7 @@ def test_fork_always_re_runs_nodes( "source": "loop", "step": 2, "writes": {"add_one": 1}, + "thread_id": "1", }, created_at=AnyStr(), parent_config=history[4].config, @@ -1029,6 +1051,7 @@ def test_fork_always_re_runs_nodes( "source": "loop", "step": 1, "writes": {"add_one": 1}, + "thread_id": "1", }, created_at=AnyStr(), parent_config=history[5].config, @@ -1044,7 +1067,13 @@ def test_fork_always_re_runs_nodes( "checkpoint_id": AnyStr(), } }, - metadata={"parents": {}, "source": "loop", "step": 0, "writes": None}, + metadata={ + "parents": {}, + "source": "loop", + "step": 0, + "writes": None, + "thread_id": "1", + }, created_at=AnyStr(), parent_config=history[6].config, ), @@ -1064,6 +1093,7 @@ def test_fork_always_re_runs_nodes( "source": "input", "step": -1, "writes": {"__start__": 1}, + "thread_id": "1", }, created_at=AnyStr(), parent_config=None, @@ -1496,6 +1526,7 @@ def test_pending_writes_resume( "source": "loop", "step": 0, "writes": None, + "thread_id": "1", } # should contain pending write of "one" checkpoint = checkpointer.get_tuple(thread1) @@ -1584,6 +1615,7 @@ def test_pending_writes_resume( "step": 1, "source": "loop", "writes": {"one": {"value": 2}, "two": {"value": 3}}, + "thread_id": "1", }, parent_config={ "configurable": { @@ -1628,7 +1660,13 @@ def test_pending_writes_resume( "start:two": "__start__", }, }, - metadata={"parents": {}, "step": 0, "source": "loop", "writes": None}, + metadata={ + "parents": {}, + "step": 0, + "source": "loop", + "writes": None, + "thread_id": "1", + }, parent_config={ "configurable": { "thread_id": "1", @@ -1668,6 +1706,7 @@ def test_pending_writes_resume( "step": -1, "source": "input", "writes": {"__start__": {"value": 1}}, + "thread_id": "1", }, parent_config=None, pending_writes=UnsortedSequence( @@ -2117,7 +2156,9 @@ def test_conditional_graph( workflow.add_node("agent", agent) workflow.add_node( - "tools", execute_tools, metadata={"parents": {}, "version": 2, "variant": "b"} + "tools", + execute_tools, + metadata={"parents": {}, "version": 2, "variant": "b"}, ) workflow.set_entry_point("agent") @@ -2311,6 +2352,7 @@ def test_conditional_graph( } }, }, + "thread_id": "1", }, parent_config=[*app_w_interrupt.checkpointer.list(config, limit=2)][-1].config, ) @@ -2362,6 +2404,7 @@ def test_conditional_graph( "input": "what is weather in sf", }, }, + "thread_id": "1", }, parent_config=[*app_w_interrupt.checkpointer.list(config, limit=2)][-1].config, ) @@ -2482,6 +2525,7 @@ def test_conditional_graph( ), } }, + "thread_id": "1", }, parent_config=[*app_w_interrupt.checkpointer.list(config, limit=2)][-1].config, ) @@ -2537,6 +2581,7 @@ def test_conditional_graph( } } }, + "thread_id": "2", }, parent_config=[*app_w_interrupt.checkpointer.list(config, limit=2)][-1].config, ) @@ -2582,6 +2627,7 @@ def test_conditional_graph( "input": "what is weather in sf", } }, + "thread_id": "2", }, parent_config=[*app_w_interrupt.checkpointer.list(config, limit=2)][-1].config, ) @@ -2702,6 +2748,7 @@ def test_conditional_graph( ), } }, + "thread_id": "2", }, parent_config=[*app_w_interrupt.checkpointer.list(config, limit=2)][-1].config, ) @@ -2757,6 +2804,7 @@ def test_conditional_graph( } } }, + "thread_id": "3", }, parent_config=[*app_w_interrupt.checkpointer.list(config, limit=2)][-1].config, ) @@ -3230,6 +3278,7 @@ def test_conditional_state_graph( ), } }, + "thread_id": "1", }, parent_config=[*app_w_interrupt.checkpointer.list(config, limit=2)][-1].config, ) @@ -3272,6 +3321,7 @@ def test_conditional_state_graph( ) }, }, + "thread_id": "1", }, parent_config=[*app_w_interrupt.checkpointer.list(config, limit=2)][-1].config, ) @@ -3348,6 +3398,7 @@ def test_conditional_state_graph( ) } }, + "thread_id": "1", }, parent_config=[*app_w_interrupt.checkpointer.list(config, limit=2)][-1].config, ) @@ -3399,6 +3450,7 @@ def test_conditional_state_graph( ), } }, + "thread_id": "2", }, parent_config=[*app_w_interrupt.checkpointer.list(config, limit=2)][-1].config, ) @@ -3440,6 +3492,7 @@ def test_conditional_state_graph( ) } }, + "thread_id": "2", }, parent_config=[*app_w_interrupt.checkpointer.list(config, limit=2)][-1].config, ) @@ -3514,6 +3567,7 @@ def test_conditional_state_graph( ) } }, + "thread_id": "2", }, parent_config=[*app_w_interrupt.checkpointer.list(config, limit=2)][-1].config, ) @@ -3541,7 +3595,13 @@ def test_conditional_state_graph( next=("agent",), config=app_w_interrupt.checkpointer.get_tuple(config).config, created_at=app_w_interrupt.checkpointer.get_tuple(config).checkpoint["ts"], - metadata={"parents": {}, "source": "loop", "step": 0, "writes": None}, + metadata={ + "parents": {}, + "source": "loop", + "step": 0, + "writes": None, + "thread_id": "3", + }, parent_config=[*app_w_interrupt.checkpointer.list(config, limit=2)][-1].config, ) @@ -3580,6 +3640,7 @@ def test_conditional_state_graph( ), } }, + "thread_id": "3", }, parent_config=[*app_w_interrupt.checkpointer.list(config, limit=2)][-1].config, ) @@ -3640,6 +3701,7 @@ def test_conditional_state_graph( ], } }, + "thread_id": "3", }, parent_config=[*app_w_interrupt.checkpointer.list(config, limit=2)][-1].config, ) @@ -3702,6 +3764,7 @@ def test_conditional_state_graph( ), } }, + "thread_id": "4", }, parent_config=[*app_w_interrupt.checkpointer.list(config, limit=2)][-1].config, ) @@ -3762,6 +3825,7 @@ def test_conditional_state_graph( ], } }, + "thread_id": "4", }, parent_config=[*app_w_interrupt.checkpointer.list(config, limit=2)][-1].config, ) @@ -4103,7 +4167,7 @@ def test_prebuilt_tool_chat(snapshot: SnapshotAssertion) -> None: "langgraph_step": 1, "langgraph_node": "agent", "langgraph_triggers": ["start:agent"], - "langgraph_path": ("__pregel_pull", "agent"), + "langgraph_path": (PULL, "agent"), "langgraph_checkpoint_ns": AnyStr("agent:"), "checkpoint_ns": AnyStr("agent:"), "ls_provider": "fakechatmodel", @@ -4120,7 +4184,7 @@ def test_prebuilt_tool_chat(snapshot: SnapshotAssertion) -> None: "langgraph_step": 2, "langgraph_node": "tools", "langgraph_triggers": ["branch:agent:should_continue:tools"], - "langgraph_path": ("__pregel_pull", "tools"), + "langgraph_path": (PULL, "tools"), "langgraph_checkpoint_ns": AnyStr("tools:"), }, ), @@ -4162,7 +4226,7 @@ def test_prebuilt_tool_chat(snapshot: SnapshotAssertion) -> None: "langgraph_step": 3, "langgraph_node": "agent", "langgraph_triggers": ["tools"], - "langgraph_path": ("__pregel_pull", "agent"), + "langgraph_path": (PULL, "agent"), "langgraph_checkpoint_ns": AnyStr("agent:"), "checkpoint_ns": AnyStr("agent:"), "ls_provider": "fakechatmodel", @@ -4179,7 +4243,7 @@ def test_prebuilt_tool_chat(snapshot: SnapshotAssertion) -> None: "langgraph_step": 4, "langgraph_node": "tools", "langgraph_triggers": ["branch:agent:should_continue:tools"], - "langgraph_path": ("__pregel_pull", "tools"), + "langgraph_path": (PULL, "tools"), "langgraph_checkpoint_ns": AnyStr("tools:"), }, ), @@ -4193,7 +4257,7 @@ def test_prebuilt_tool_chat(snapshot: SnapshotAssertion) -> None: "langgraph_step": 4, "langgraph_node": "tools", "langgraph_triggers": ["branch:agent:should_continue:tools"], - "langgraph_path": ("__pregel_pull", "tools"), + "langgraph_path": (PULL, "tools"), "langgraph_checkpoint_ns": AnyStr("tools:"), }, ), @@ -4205,7 +4269,7 @@ def test_prebuilt_tool_chat(snapshot: SnapshotAssertion) -> None: "langgraph_step": 5, "langgraph_node": "agent", "langgraph_triggers": ["tools"], - "langgraph_path": ("__pregel_pull", "agent"), + "langgraph_path": (PULL, "agent"), "langgraph_checkpoint_ns": AnyStr("agent:"), "checkpoint_ns": AnyStr("agent:"), "ls_provider": "fakechatmodel", @@ -4678,6 +4742,7 @@ def test_state_graph_packets( ) } }, + "thread_id": "1", }, parent_config=[*app_w_interrupt.checkpointer.list(config, limit=2)][-1].config, ) @@ -4731,6 +4796,7 @@ def test_state_graph_packets( "something_extra": "hi there", } }, + "thread_id": "1", }, parent_config=[*app_w_interrupt.checkpointer.list(config, limit=2)][-1].config, ) @@ -4837,6 +4903,7 @@ def test_state_graph_packets( ) }, }, + "thread_id": "1", }, parent_config=[*app_w_interrupt.checkpointer.list(config, limit=2)][-1].config, ) @@ -4887,6 +4954,7 @@ def test_state_graph_packets( "something_extra": "hi there", } }, + "thread_id": "1", }, parent_config=[*app_w_interrupt.checkpointer.list(config, limit=2)][-1].config, ) @@ -5176,6 +5244,7 @@ def test_message_graph( id="ai1", ) }, + "thread_id": "1", }, parent_config=[*app_w_interrupt.checkpointer.list(config, limit=2)][-1].config, ) @@ -5222,6 +5291,7 @@ def test_message_graph( id="ai1", ) }, + "thread_id": "1", }, parent_config=[*app_w_interrupt.checkpointer.list(config, limit=2)][-1].config, ) @@ -5304,6 +5374,7 @@ def test_message_graph( id="ai2", ) }, + "thread_id": "1", }, parent_config=[*app_w_interrupt.checkpointer.list(config, limit=2)][-1].config, ) @@ -5344,6 +5415,7 @@ def test_message_graph( "source": "update", "step": 5, "writes": {"agent": AIMessage(content="answer", id="ai2")}, + "thread_id": "1", }, parent_config=[*app_w_interrupt.checkpointer.list(config, limit=2)][-1].config, ) @@ -5408,6 +5480,7 @@ def test_message_graph( id="ai1", ) }, + "thread_id": "2", }, parent_config=[*app_w_interrupt.checkpointer.list(config, limit=2)][-1].config, ) @@ -5454,6 +5527,7 @@ def test_message_graph( id="ai1", ) }, + "thread_id": "2", }, parent_config=[*app_w_interrupt.checkpointer.list(config, limit=2)][-1].config, ) @@ -5536,6 +5610,7 @@ def test_message_graph( id="ai2", ) }, + "thread_id": "2", }, parent_config=[*app_w_interrupt.checkpointer.list(config, limit=2)][-1].config, ) @@ -5577,6 +5652,7 @@ def test_message_graph( "source": "update", "step": 5, "writes": {"agent": AIMessage(content="answer", id="ai2")}, + "thread_id": "2", }, parent_config=[*app_w_interrupt.checkpointer.list(config, limit=2)][-1].config, ) @@ -5618,6 +5694,7 @@ def test_message_graph( "source": "update", "step": 6, "writes": {"tools": UnsortedSequence("ai", "an extra message")}, + "thread_id": "2", }, parent_config=[*app_w_interrupt.checkpointer.list(config, limit=2)][-1].config, ) @@ -5906,6 +5983,7 @@ def test_root_graph( id="ai1", ) }, + "thread_id": "1", }, parent_config=[*app_w_interrupt.checkpointer.list(config, limit=2)][-1].config, ) @@ -5952,6 +6030,7 @@ def test_root_graph( id="ai1", ) }, + "thread_id": "1", }, parent_config=[*app_w_interrupt.checkpointer.list(config, limit=2)][-1].config, ) @@ -6035,6 +6114,7 @@ def test_root_graph( id="ai2", ) }, + "thread_id": "1", }, parent_config=[*app_w_interrupt.checkpointer.list(config, limit=2)][-1].config, ) @@ -6076,6 +6156,7 @@ def test_root_graph( "source": "update", "step": 5, "writes": {"agent": AIMessage(content="answer", id="ai2")}, + "thread_id": "1", }, parent_config=[*app_w_interrupt.checkpointer.list(config, limit=2)][-1].config, ) @@ -6140,6 +6221,7 @@ def test_root_graph( id="ai1", ) }, + "thread_id": "2", }, parent_config=[*app_w_interrupt.checkpointer.list(config, limit=2)][-1].config, ) @@ -6186,6 +6268,7 @@ def test_root_graph( id="ai1", ) }, + "thread_id": "2", }, parent_config=[*app_w_interrupt.checkpointer.list(config, limit=2)][-1].config, ) @@ -6269,6 +6352,7 @@ def test_root_graph( id="ai2", ) }, + "thread_id": "2", }, parent_config=[*app_w_interrupt.checkpointer.list(config, limit=2)][-1].config, ) @@ -6309,6 +6393,7 @@ def test_root_graph( "source": "update", "step": 5, "writes": {"agent": AIMessage(content="answer", id="ai2")}, + "thread_id": "2", }, parent_config=[*app_w_interrupt.checkpointer.list(config, limit=2)][-1].config, ) @@ -6350,6 +6435,7 @@ def test_root_graph( "source": "update", "step": 6, "writes": {"tools": UnsortedSequence("ai", "an extra message")}, + "thread_id": "2", }, parent_config=[*app_w_interrupt.checkpointer.list(config, limit=2)][-1].config, ) @@ -6422,6 +6508,7 @@ def test_root_graph( "source": "update", "step": 6, "writes": {"tools": UnsortedSequence("ai", "an extra message")}, + "thread_id": "2", }, parent_config=[*app_w_interrupt.checkpointer.list(config, limit=2)][-1].config, ) @@ -6751,12 +6838,14 @@ def test_dynamic_interrupt( "source": "loop", "step": 0, "writes": None, + "thread_id": "1", }, { "parents": {}, "source": "input", "step": -1, "writes": {"__start__": {"my_key": "value ⛰️", "market": "DE"}}, + "thread_id": "1", }, ] assert tool_two.get_state(thread1) == StateSnapshot( @@ -6772,7 +6861,13 @@ def test_dynamic_interrupt( ), config=tool_two.checkpointer.get_tuple(thread1).config, created_at=tool_two.checkpointer.get_tuple(thread1).checkpoint["ts"], - metadata={"parents": {}, "source": "loop", "step": 0, "writes": None}, + metadata={ + "parents": {}, + "source": "loop", + "step": 0, + "writes": None, + "thread_id": "1", + }, parent_config=[*tool_two.checkpointer.list(thread1, limit=2)][-1].config, ) @@ -6849,12 +6944,16 @@ def test_start_branch_then( "source": "loop", "step": 0, "writes": None, + "assistant_id": "a", + "thread_id": "1", }, { "parents": {}, "source": "input", "step": -1, "writes": {"__start__": {"my_key": "value ⛰️", "market": "DE"}}, + "assistant_id": "a", + "thread_id": "1", }, ] assert tool_two.get_state(thread1) == StateSnapshot( @@ -6863,7 +6962,14 @@ def test_start_branch_then( next=("tool_two_slow",), config=tool_two.checkpointer.get_tuple(thread1).config, created_at=tool_two.checkpointer.get_tuple(thread1).checkpoint["ts"], - metadata={"parents": {}, "source": "loop", "step": 0, "writes": None}, + metadata={ + "parents": {}, + "source": "loop", + "step": 0, + "writes": None, + "assistant_id": "a", + "thread_id": "1", + }, parent_config=[*tool_two.checkpointer.list(thread1, limit=2)][-1].config, ) # resume, for same result as above @@ -6882,6 +6988,8 @@ def test_start_branch_then( "source": "loop", "step": 1, "writes": {"tool_two_slow": {"my_key": " slow"}}, + "assistant_id": "a", + "thread_id": "1", }, parent_config=[*tool_two.checkpointer.list(thread1, limit=2)][-1].config, ) @@ -6898,7 +7006,14 @@ def test_start_branch_then( next=("tool_two_fast",), config=tool_two.checkpointer.get_tuple(thread2).config, created_at=tool_two.checkpointer.get_tuple(thread2).checkpoint["ts"], - metadata={"parents": {}, "source": "loop", "step": 0, "writes": None}, + metadata={ + "parents": {}, + "source": "loop", + "step": 0, + "writes": None, + "assistant_id": "a", + "thread_id": "2", + }, parent_config=[*tool_two.checkpointer.list(thread2, limit=2)][-1].config, ) # resume, for same result as above @@ -6917,6 +7032,8 @@ def test_start_branch_then( "source": "loop", "step": 1, "writes": {"tool_two_fast": {"my_key": " fast"}}, + "assistant_id": "a", + "thread_id": "2", }, parent_config=[*tool_two.checkpointer.list(thread2, limit=2)][-1].config, ) @@ -6933,7 +7050,14 @@ def test_start_branch_then( next=("tool_two_fast",), config=tool_two.checkpointer.get_tuple(thread3).config, created_at=tool_two.checkpointer.get_tuple(thread3).checkpoint["ts"], - metadata={"parents": {}, "source": "loop", "step": 0, "writes": None}, + metadata={ + "parents": {}, + "source": "loop", + "step": 0, + "writes": None, + "assistant_id": "b", + "thread_id": "3", + }, parent_config=[*tool_two.checkpointer.list(thread3, limit=2)][-1].config, ) # update state @@ -6949,6 +7073,8 @@ def test_start_branch_then( "source": "update", "step": 1, "writes": {START: {"my_key": "key"}}, + "assistant_id": "b", + "thread_id": "3", }, parent_config=[*tool_two.checkpointer.list(thread3, limit=2)][-1].config, ) @@ -6968,6 +7094,8 @@ def test_start_branch_then( "source": "loop", "step": 2, "writes": {"tool_two_fast": {"my_key": " fast"}}, + "assistant_id": "b", + "thread_id": "3", }, parent_config=[*tool_two.checkpointer.list(thread3, limit=2)][-1].config, ) @@ -7041,6 +7169,7 @@ def test_branch_then( "source": "input", "step": -1, "writes": {"__start__": {"my_key": "value", "market": "DE"}}, + "thread_id": "10", }, "parent_config": None, "next": ["__start__"], @@ -7079,6 +7208,7 @@ def test_branch_then( "source": "loop", "step": 0, "writes": None, + "thread_id": "10", }, "parent_config": { "tags": [], @@ -7145,6 +7275,7 @@ def test_branch_then( "source": "loop", "step": 1, "writes": {"prepare": {"my_key": " prepared"}}, + "thread_id": "10", }, "parent_config": { "tags": [], @@ -7216,6 +7347,7 @@ def test_branch_then( "source": "loop", "step": 2, "writes": {"tool_two_slow": {"my_key": " slow"}}, + "thread_id": "10", }, "parent_config": { "tags": [], @@ -7282,6 +7414,7 @@ def test_branch_then( "source": "loop", "step": 3, "writes": {"finish": {"my_key": " finished"}}, + "thread_id": "10", }, "parent_config": { "tags": [], @@ -7325,6 +7458,7 @@ def test_branch_then( "source": "loop", "step": 1, "writes": {"prepare": {"my_key": " prepared"}}, + "thread_id": "1", }, parent_config=[*tool_two.checkpointer.list(thread1, limit=2)][-1].config, ) @@ -7344,6 +7478,7 @@ def test_branch_then( "source": "loop", "step": 3, "writes": {"finish": {"my_key": " finished"}}, + "thread_id": "1", }, parent_config=[*tool_two.checkpointer.list(thread1, limit=2)][-1].config, ) @@ -7365,6 +7500,7 @@ def test_branch_then( "source": "loop", "step": 1, "writes": {"prepare": {"my_key": " prepared"}}, + "thread_id": "2", }, parent_config=[*tool_two.checkpointer.list(thread2, limit=2)][-1].config, ) @@ -7384,6 +7520,7 @@ def test_branch_then( "source": "loop", "step": 3, "writes": {"finish": {"my_key": " finished"}}, + "thread_id": "2", }, parent_config=[*tool_two.checkpointer.list(thread2, limit=2)][-1].config, ) @@ -7413,6 +7550,7 @@ def test_branch_then( "source": "loop", "step": 2, "writes": {"tool_two_slow": {"my_key": " slow"}}, + "thread_id": "11", }, parent_config=[*tool_two.checkpointer.list(thread1, limit=2)][-1].config, ) @@ -7433,6 +7571,7 @@ def test_branch_then( "source": "update", "step": 3, "writes": {"tool_two_slow": {"my_key": "er"}}, + "thread_id": "11", }, parent_config=[*tool_two.checkpointer.list(thread1, limit=2)][-1].config, ) @@ -7462,6 +7601,7 @@ def test_branch_then( "source": "loop", "step": 1, "writes": {"prepare": {"my_key": " prepared"}}, + "thread_id": "21", }, parent_config=[*tool_two.checkpointer.list(thread1, limit=2)][-1].config, ) @@ -7481,6 +7621,7 @@ def test_branch_then( "source": "loop", "step": 3, "writes": {"finish": {"my_key": " finished"}}, + "thread_id": "21", }, parent_config=[*tool_two.checkpointer.list(thread1, limit=2)][-1].config, ) @@ -7502,6 +7643,7 @@ def test_branch_then( "source": "loop", "step": 1, "writes": {"prepare": {"my_key": " prepared"}}, + "thread_id": "22", }, parent_config=[*tool_two.checkpointer.list(thread2, limit=2)][-1].config, ) @@ -7521,6 +7663,7 @@ def test_branch_then( "source": "loop", "step": 3, "writes": {"finish": {"my_key": " finished"}}, + "thread_id": "22", }, parent_config=[*tool_two.checkpointer.list(thread2, limit=2)][-1].config, ) @@ -7540,6 +7683,7 @@ def test_branch_then( "source": "update", "step": 0, "writes": {START: {"my_key": "key", "market": "DE"}}, + "thread_id": "23", }, parent_config=None, ) @@ -7560,6 +7704,7 @@ def test_branch_then( "source": "loop", "step": 1, "writes": {"prepare": {"my_key": " prepared"}}, + "thread_id": "23", }, parent_config=uconfig, ) @@ -7579,6 +7724,7 @@ def test_branch_then( "source": "loop", "step": 3, "writes": {"finish": {"my_key": " finished"}}, + "thread_id": "23", }, parent_config=[*tool_two.checkpointer.list(thread3, limit=2)][-1].config, ) @@ -7706,6 +7852,7 @@ def test_in_one_fan_out_state_graph_waiting_edge( "source": "update", "step": 4, "writes": {"retriever_one": {"docs": ["doc5"]}}, + "thread_id": "2", }, parent_config=[*app_w_interrupt.checkpointer.list(config, limit=2)][-1].config, ) @@ -9032,6 +9179,7 @@ def test_nested_graph_state( "source": "loop", "writes": {"outer_1": {"my_key": "hi my value"}}, "step": 1, + "thread_id": "1", }, created_at=AnyStr(), parent_config={ @@ -9085,6 +9233,13 @@ def test_nested_graph_state( } }, "step": 1, + "thread_id": "1", + "checkpoint_ns": AnyStr("inner:"), + "langgraph_node": "inner", + "langgraph_path": [PULL, "inner"], + "langgraph_step": 2, + "langgraph_triggers": ["outer_1"], + "langgraph_checkpoint_ns": AnyStr("inner:"), }, created_at=AnyStr(), parent_config={ @@ -9113,6 +9268,7 @@ def test_nested_graph_state( "source": "loop", "writes": {"outer_1": {"my_key": "hi my value"}}, "step": 1, + "thread_id": "1", }, created_at=AnyStr(), parent_config={ @@ -9154,6 +9310,7 @@ def test_nested_graph_state( "source": "loop", "writes": {"outer_1": {"my_key": "hi my value"}}, "step": 1, + "thread_id": "1", }, created_at=AnyStr(), parent_config={ @@ -9182,7 +9339,13 @@ def test_nested_graph_state( "checkpoint_id": AnyStr(), } }, - metadata={"parents": {}, "source": "loop", "writes": None, "step": 0}, + metadata={ + "parents": {}, + "source": "loop", + "writes": None, + "step": 0, + "thread_id": "1", + }, created_at=AnyStr(), parent_config={ "configurable": { @@ -9215,6 +9378,7 @@ def test_nested_graph_state( "source": "input", "writes": {"__start__": {"my_key": "my value"}}, "step": -1, + "thread_id": "1", }, created_at=AnyStr(), parent_config=None, @@ -9246,6 +9410,13 @@ def test_nested_graph_state( }, "step": 1, "parents": {"": AnyStr()}, + "thread_id": "1", + "checkpoint_ns": AnyStr("inner:"), + "langgraph_node": "inner", + "langgraph_path": [PULL, "inner"], + "langgraph_step": 2, + "langgraph_triggers": ["outer_1"], + "langgraph_checkpoint_ns": AnyStr("inner:"), }, created_at=AnyStr(), parent_config={ @@ -9278,6 +9449,13 @@ def test_nested_graph_state( "writes": None, "step": 0, "parents": {"": AnyStr()}, + "thread_id": "1", + "checkpoint_ns": AnyStr("inner:"), + "langgraph_node": "inner", + "langgraph_path": [PULL, "inner"], + "langgraph_step": 2, + "langgraph_triggers": ["outer_1"], + "langgraph_checkpoint_ns": AnyStr("inner:"), }, created_at=AnyStr(), parent_config={ @@ -9320,6 +9498,13 @@ def test_nested_graph_state( "writes": {"__start__": {"my_key": "hi my value"}}, "step": -1, "parents": {"": AnyStr()}, + "thread_id": "1", + "checkpoint_ns": AnyStr("inner:"), + "langgraph_node": "inner", + "langgraph_path": [PULL, "inner"], + "langgraph_step": 2, + "langgraph_triggers": ["outer_1"], + "langgraph_checkpoint_ns": AnyStr("inner:"), }, created_at=AnyStr(), parent_config=None, @@ -9355,6 +9540,7 @@ def test_nested_graph_state( "outer_2": {"my_key": "hi my value here and there and back again"} }, "step": 3, + "thread_id": "1", }, created_at=AnyStr(), parent_config={ @@ -9386,6 +9572,7 @@ def test_nested_graph_state( "outer_2": {"my_key": "hi my value here and there and back again"} }, "step": 3, + "thread_id": "1", }, created_at=AnyStr(), parent_config={ @@ -9419,6 +9606,7 @@ def test_nested_graph_state( "source": "loop", "writes": {"inner": {"my_key": "hi my value here and there"}}, "step": 2, + "thread_id": "1", }, created_at=AnyStr(), parent_config={ @@ -9455,6 +9643,7 @@ def test_nested_graph_state( "source": "loop", "writes": {"outer_1": {"my_key": "hi my value"}}, "step": 1, + "thread_id": "1", }, created_at=AnyStr(), parent_config={ @@ -9483,7 +9672,13 @@ def test_nested_graph_state( "checkpoint_id": AnyStr(), } }, - metadata={"parents": {}, "source": "loop", "writes": None, "step": 0}, + metadata={ + "parents": {}, + "source": "loop", + "writes": None, + "step": 0, + "thread_id": "1", + }, created_at=AnyStr(), parent_config={ "configurable": { @@ -9516,6 +9711,7 @@ def test_nested_graph_state( "source": "input", "writes": {"__start__": {"my_key": "my value"}}, "step": -1, + "thread_id": "1", }, created_at=AnyStr(), parent_config=None, @@ -9622,6 +9818,7 @@ def test_doubly_nested_graph_state( "source": "loop", "writes": {"parent_1": {"my_key": "hi my value"}}, "step": 1, + "thread_id": "1", }, created_at=AnyStr(), parent_config={ @@ -9663,6 +9860,7 @@ def test_doubly_nested_graph_state( "source": "loop", "writes": None, "step": 0, + "thread_id": "1", }, created_at=AnyStr(), parent_config={ @@ -9709,6 +9907,13 @@ def test_doubly_nested_graph_state( "source": "loop", "writes": {"grandchild_1": {"my_key": "hi my value here"}}, "step": 1, + "thread_id": "1", + "checkpoint_ns": AnyStr("child:"), + "langgraph_checkpoint_ns": AnyStr("child:"), + "langgraph_node": "child_1", + "langgraph_path": [PULL, AnyStr("child_1")], + "langgraph_step": 1, + "langgraph_triggers": [AnyStr("start:child_1")], }, created_at=AnyStr(), parent_config={ @@ -9779,6 +9984,16 @@ def test_doubly_nested_graph_state( "grandchild_1": {"my_key": "hi my value here"} }, "step": 1, + "thread_id": "1", + "checkpoint_ns": AnyStr("child:"), + "langgraph_checkpoint_ns": AnyStr("child:"), + "langgraph_node": "child_1", + "langgraph_path": [ + PULL, + AnyStr("child_1"), + ], + "langgraph_step": 1, + "langgraph_triggers": [AnyStr("start:child_1")], }, created_at=AnyStr(), parent_config={ @@ -9816,6 +10031,13 @@ def test_doubly_nested_graph_state( "source": "loop", "writes": None, "step": 0, + "thread_id": "1", + "checkpoint_ns": AnyStr("child:"), + "langgraph_node": "child", + "langgraph_path": [PULL, AnyStr("child")], + "langgraph_step": 2, + "langgraph_triggers": [AnyStr("parent_1")], + "langgraph_checkpoint_ns": AnyStr("child:"), }, created_at=AnyStr(), parent_config={ @@ -9844,6 +10066,7 @@ def test_doubly_nested_graph_state( "source": "loop", "writes": {"parent_1": {"my_key": "hi my value"}}, "step": 1, + "thread_id": "1", }, created_at=AnyStr(), parent_config={ @@ -9854,7 +10077,7 @@ def test_doubly_nested_graph_state( } }, ) - # resume + # # resume assert [c for c in app.stream(None, config, subgraphs=True)] == [ ( (AnyStr("child:"), AnyStr("child_1:")), @@ -9886,6 +10109,7 @@ def test_doubly_nested_graph_state( "parent_2": {"my_key": "hi my value here and there and back again"} }, "step": 3, + "thread_id": "1", }, created_at=AnyStr(), parent_config={ @@ -9918,6 +10142,7 @@ def test_doubly_nested_graph_state( "parent_2": {"my_key": "hi my value here and there and back again"} }, "step": 3, + "thread_id": "1", }, created_at=AnyStr(), parent_config={ @@ -9943,6 +10168,7 @@ def test_doubly_nested_graph_state( "writes": {"child": {"my_key": "hi my value here and there"}}, "step": 2, "parents": {}, + "thread_id": "1", }, created_at=AnyStr(), parent_config={ @@ -9990,6 +10216,7 @@ def test_doubly_nested_graph_state( "source": "loop", "writes": {"parent_1": {"my_key": "hi my value"}}, "step": 1, + "thread_id": "1", }, created_at=AnyStr(), parent_config={ @@ -10010,7 +10237,13 @@ def test_doubly_nested_graph_state( "checkpoint_id": AnyStr(), } }, - metadata={"source": "loop", "writes": None, "step": 0, "parents": {}}, + metadata={ + "source": "loop", + "writes": None, + "step": 0, + "parents": {}, + "thread_id": "1", + }, created_at=AnyStr(), parent_config={ "configurable": { @@ -10043,6 +10276,7 @@ def test_doubly_nested_graph_state( "writes": {"__start__": {"my_key": "my value"}}, "step": -1, "parents": {}, + "thread_id": "1", }, created_at=AnyStr(), parent_config=None, @@ -10077,6 +10311,13 @@ def test_doubly_nested_graph_state( "writes": {"child_1": {"my_key": "hi my value here and there"}}, "step": 1, "parents": {"": AnyStr()}, + "thread_id": "1", + "checkpoint_ns": AnyStr("child:"), + "langgraph_node": "child", + "langgraph_path": [PULL, AnyStr("child")], + "langgraph_step": 2, + "langgraph_triggers": [AnyStr("parent_1")], + "langgraph_checkpoint_ns": AnyStr("child:"), }, created_at=AnyStr(), parent_config={ @@ -10109,6 +10350,13 @@ def test_doubly_nested_graph_state( "writes": None, "step": 0, "parents": {"": AnyStr()}, + "thread_id": "1", + "checkpoint_ns": AnyStr("child:"), + "langgraph_node": "child", + "langgraph_path": [PULL, AnyStr("child")], + "langgraph_step": 2, + "langgraph_triggers": [AnyStr("parent_1")], + "langgraph_checkpoint_ns": AnyStr("child:"), }, created_at=AnyStr(), parent_config={ @@ -10154,6 +10402,13 @@ def test_doubly_nested_graph_state( "writes": {"__start__": {"my_key": "hi my value"}}, "step": -1, "parents": {"": AnyStr()}, + "thread_id": "1", + "checkpoint_ns": AnyStr("child:"), + "langgraph_node": "child", + "langgraph_path": [PULL, AnyStr("child")], + "langgraph_step": 2, + "langgraph_triggers": [AnyStr("parent_1")], + "langgraph_checkpoint_ns": AnyStr("child:"), }, created_at=AnyStr(), parent_config=None, @@ -10197,6 +10452,16 @@ def test_doubly_nested_graph_state( AnyStr("child:"): AnyStr(), } ), + "thread_id": "1", + "checkpoint_ns": AnyStr("child:"), + "langgraph_checkpoint_ns": AnyStr("child:"), + "langgraph_node": "child_1", + "langgraph_path": [ + PULL, + AnyStr("child_1"), + ], + "langgraph_step": 1, + "langgraph_triggers": [AnyStr("start:child_1")], }, created_at=AnyStr(), parent_config={ @@ -10242,6 +10507,16 @@ def test_doubly_nested_graph_state( AnyStr("child:"): AnyStr(), } ), + "thread_id": "1", + "checkpoint_ns": AnyStr("child:"), + "langgraph_checkpoint_ns": AnyStr("child:"), + "langgraph_node": "child_1", + "langgraph_path": [ + PULL, + AnyStr("child_1"), + ], + "langgraph_step": 1, + "langgraph_triggers": [AnyStr("start:child_1")], }, created_at=AnyStr(), parent_config={ @@ -10294,6 +10569,16 @@ def test_doubly_nested_graph_state( AnyStr("child:"): AnyStr(), } ), + "thread_id": "1", + "checkpoint_ns": AnyStr("child:"), + "langgraph_checkpoint_ns": AnyStr("child:"), + "langgraph_node": "child_1", + "langgraph_path": [ + PULL, + AnyStr("child_1"), + ], + "langgraph_step": 1, + "langgraph_triggers": [AnyStr("start:child_1")], }, created_at=AnyStr(), parent_config={ @@ -10346,6 +10631,16 @@ def test_doubly_nested_graph_state( AnyStr("child:"): AnyStr(), } ), + "thread_id": "1", + "checkpoint_ns": AnyStr("child:"), + "langgraph_checkpoint_ns": AnyStr("child:"), + "langgraph_node": "child_1", + "langgraph_path": [ + PULL, + AnyStr("child_1"), + ], + "langgraph_step": 1, + "langgraph_triggers": [AnyStr("start:child_1")], }, created_at=AnyStr(), parent_config=None, @@ -10460,7 +10755,13 @@ def test_send_to_nested_graphs( "checkpoint_id": AnyStr(), } }, - metadata={"parents": {}, "source": "loop", "writes": None, "step": 0}, + metadata={ + "parents": {}, + "source": "loop", + "writes": None, + "step": 0, + "thread_id": "1", + }, created_at=AnyStr(), parent_config={ "configurable": { @@ -10492,6 +10793,13 @@ def test_send_to_nested_graphs( "source": "loop", "writes": {"edit": None}, "parents": {"": AnyStr()}, + "thread_id": "1", + "checkpoint_ns": AnyStr("generate_joke:"), + "langgraph_checkpoint_ns": AnyStr("generate_joke:"), + "langgraph_node": "generate_joke", + "langgraph_path": [PUSH, 0], + "langgraph_step": 1, + "langgraph_triggers": [PUSH], }, created_at=AnyStr(), parent_config={ @@ -10530,6 +10838,13 @@ def test_send_to_nested_graphs( "source": "loop", "writes": {"edit": None}, "parents": {"": AnyStr()}, + "thread_id": "1", + "checkpoint_ns": AnyStr("generate_joke:"), + "langgraph_checkpoint_ns": AnyStr("generate_joke:"), + "langgraph_node": "generate_joke", + "langgraph_path": [PUSH, 1], + "langgraph_step": 1, + "langgraph_triggers": [PUSH], }, created_at=AnyStr(), parent_config={ @@ -10583,6 +10898,7 @@ def test_send_to_nested_graphs( ] }, "step": 1, + "thread_id": "1", }, created_at=AnyStr(), parent_config={ @@ -10624,6 +10940,7 @@ def test_send_to_nested_graphs( ] }, "step": 1, + "thread_id": "1", }, created_at=AnyStr(), parent_config={ @@ -10670,7 +10987,13 @@ def test_send_to_nested_graphs( "checkpoint_id": AnyStr(), } }, - metadata={"parents": {}, "source": "loop", "writes": None, "step": 0}, + metadata={ + "parents": {}, + "source": "loop", + "writes": None, + "step": 0, + "thread_id": "1", + }, created_at=AnyStr(), parent_config={ "configurable": { @@ -10703,6 +11026,7 @@ def test_send_to_nested_graphs( "source": "input", "writes": {"__start__": {"subjects": ["cats", "dogs"]}}, "step": -1, + "thread_id": "1", }, created_at=AnyStr(), parent_config=None, @@ -10864,6 +11188,7 @@ def test_weather_subgraph( "writes": {"router_node": {"route": "weather"}}, "step": 1, "parents": {}, + "thread_id": "1", }, created_at=AnyStr(), parent_config={ @@ -10951,6 +11276,7 @@ def test_weather_subgraph( "writes": {"router_node": {"route": "weather"}}, "step": 1, "parents": {}, + "thread_id": "14", }, created_at=AnyStr(), parent_config={ @@ -10991,6 +11317,15 @@ def test_weather_subgraph( "writes": {"model_node": {"city": "San Francisco"}}, "step": 1, "parents": {"": AnyStr()}, + "thread_id": "14", + "checkpoint_ns": AnyStr("weather_graph:"), + "langgraph_node": "weather_graph", + "langgraph_path": [PULL, "weather_graph"], + "langgraph_step": 2, + "langgraph_triggers": [ + "branch:router_node:route_after_prediction:weather_graph" + ], + "langgraph_checkpoint_ns": AnyStr("weather_graph:"), }, created_at=AnyStr(), parent_config={ @@ -11041,6 +11376,7 @@ def test_weather_subgraph( "writes": {"router_node": {"route": "weather"}}, "step": 1, "parents": {}, + "thread_id": "14", }, created_at=AnyStr(), parent_config={ @@ -11078,14 +11414,24 @@ def test_weather_subgraph( } }, metadata={ - "source": "update", "step": 2, + "source": "update", "writes": { "weather_node": { "messages": [{"role": "assistant", "content": "rainy"}] } }, "parents": {"": AnyStr()}, + "thread_id": "14", + "checkpoint_id": AnyStr(), + "checkpoint_ns": AnyStr("weather_graph:"), + "langgraph_node": "weather_graph", + "langgraph_path": [PULL, "weather_graph"], + "langgraph_step": 2, + "langgraph_triggers": [ + "branch:router_node:route_after_prediction:weather_graph" + ], + "langgraph_checkpoint_ns": AnyStr("weather_graph:"), }, created_at=AnyStr(), parent_config={ diff --git a/libs/langgraph/tests/test_pregel_async.py b/libs/langgraph/tests/test_pregel_async.py index bedbcef4d..25072eeaf 100644 --- a/libs/langgraph/tests/test_pregel_async.py +++ b/libs/langgraph/tests/test_pregel_async.py @@ -306,12 +306,14 @@ async def test_dynamic_interrupt(checkpointer_name: str) -> None: "source": "loop", "step": 0, "writes": None, + "thread_id": "1", }, { "parents": {}, "source": "input", "step": -1, "writes": {"__start__": {"my_key": "value ⛰️", "market": "DE"}}, + "thread_id": "1", }, ] tup = await tool_two.checkpointer.aget_tuple(thread1) @@ -328,7 +330,13 @@ async def test_dynamic_interrupt(checkpointer_name: str) -> None: ), config=tup.config, created_at=tup.checkpoint["ts"], - metadata={"parents": {}, "source": "loop", "step": 0, "writes": None}, + metadata={ + "parents": {}, + "source": "loop", + "step": 0, + "writes": None, + "thread_id": "1", + }, parent_config=[ c async for c in tool_two.checkpointer.alist(thread1, limit=2) ][-1].config, @@ -487,6 +495,7 @@ async def test_cancel_graph_astream(checkpointer_name: str) -> None: "source": "loop", "step": 0, "writes": None, + "thread_id": "1", } @@ -563,6 +572,7 @@ async def test_cancel_graph_astream_events_v2(checkpointer_name: Optional[str]) "source": "loop", "step": 1, "writes": {"alittlewhile": {"value": 2}}, + "thread_id": "2", } @@ -946,6 +956,7 @@ async def test_invoke_two_processes_in_out_interrupt( "source": "loop", "step": 6, "writes": {"two": 5}, + "thread_id": "1", }, created_at=AnyStr(), parent_config=history[1].config, @@ -968,6 +979,7 @@ async def test_invoke_two_processes_in_out_interrupt( "source": "loop", "step": 5, "writes": {"one": None}, + "thread_id": "1", }, created_at=AnyStr(), parent_config=history[2].config, @@ -990,6 +1002,7 @@ async def test_invoke_two_processes_in_out_interrupt( "source": "input", "step": 4, "writes": {"input": 3}, + "thread_id": "1", }, created_at=AnyStr(), parent_config=history[3].config, @@ -1010,6 +1023,7 @@ async def test_invoke_two_processes_in_out_interrupt( "source": "loop", "step": 3, "writes": {"one": None}, + "thread_id": "1", }, created_at=AnyStr(), parent_config=history[4].config, @@ -1032,6 +1046,7 @@ async def test_invoke_two_processes_in_out_interrupt( "source": "input", "step": 2, "writes": {"input": 20}, + "thread_id": "1", }, created_at=AnyStr(), parent_config=history[5].config, @@ -1052,6 +1067,7 @@ async def test_invoke_two_processes_in_out_interrupt( "source": "loop", "step": 1, "writes": {"two": 4}, + "thread_id": "1", }, created_at=AnyStr(), parent_config=history[6].config, @@ -1074,6 +1090,7 @@ async def test_invoke_two_processes_in_out_interrupt( "source": "loop", "step": 0, "writes": {"one": None}, + "thread_id": "1", }, created_at=AnyStr(), parent_config=history[7].config, @@ -1096,6 +1113,7 @@ async def test_invoke_two_processes_in_out_interrupt( "source": "input", "step": -1, "writes": {"input": 2}, + "thread_id": "1", }, created_at=AnyStr(), parent_config=None, @@ -1171,6 +1189,7 @@ async def test_fork_always_re_runs_nodes( "source": "loop", "step": 5, "writes": {"add_one": 1}, + "thread_id": "1", }, created_at=AnyStr(), parent_config=history[1].config, @@ -1191,6 +1210,7 @@ async def test_fork_always_re_runs_nodes( "source": "loop", "step": 4, "writes": {"add_one": 1}, + "thread_id": "1", }, created_at=AnyStr(), parent_config=history[2].config, @@ -1211,6 +1231,7 @@ async def test_fork_always_re_runs_nodes( "source": "loop", "step": 3, "writes": {"add_one": 1}, + "thread_id": "1", }, created_at=AnyStr(), parent_config=history[3].config, @@ -1231,6 +1252,7 @@ async def test_fork_always_re_runs_nodes( "source": "loop", "step": 2, "writes": {"add_one": 1}, + "thread_id": "1", }, created_at=AnyStr(), parent_config=history[4].config, @@ -1251,6 +1273,7 @@ async def test_fork_always_re_runs_nodes( "source": "loop", "step": 1, "writes": {"add_one": 1}, + "thread_id": "1", }, created_at=AnyStr(), parent_config=history[5].config, @@ -1266,7 +1289,13 @@ async def test_fork_always_re_runs_nodes( "checkpoint_id": AnyStr(), } }, - metadata={"parents": {}, "source": "loop", "step": 0, "writes": None}, + metadata={ + "parents": {}, + "source": "loop", + "step": 0, + "writes": None, + "thread_id": "1", + }, created_at=AnyStr(), parent_config=history[6].config, ), @@ -1288,6 +1317,7 @@ async def test_fork_always_re_runs_nodes( "source": "input", "step": -1, "writes": {"__start__": 1}, + "thread_id": "1", }, created_at=AnyStr(), parent_config=None, @@ -1710,6 +1740,7 @@ async def test_pending_writes_resume( "source": "loop", "step": 0, "writes": None, + "thread_id": "1", } # should contain pending write of "one" checkpoint = await checkpointer.aget_tuple(thread1) @@ -1798,6 +1829,7 @@ async def test_pending_writes_resume( "step": 1, "source": "loop", "writes": {"one": {"value": 2}, "two": {"value": 3}}, + "thread_id": "1", }, parent_config={ "configurable": { @@ -1844,7 +1876,13 @@ async def test_pending_writes_resume( "start:two": "__start__", }, }, - metadata={"parents": {}, "step": 0, "source": "loop", "writes": None}, + metadata={ + "parents": {}, + "step": 0, + "source": "loop", + "writes": None, + "thread_id": "1", + }, parent_config={ "configurable": { "thread_id": "1", @@ -1886,6 +1924,7 @@ async def test_pending_writes_resume( "step": -1, "source": "input", "writes": {"__start__": {"value": 1}}, + "thread_id": "1", }, parent_config=None, pending_writes=UnsortedSequence( @@ -2732,6 +2771,7 @@ async def test_conditional_graph(checkpointer_name: str) -> None: } } }, + "thread_id": "1", }, parent_config=[ c async for c in app_w_interrupt.checkpointer.alist(config, limit=2) @@ -2781,6 +2821,7 @@ async def test_conditional_graph(checkpointer_name: str) -> None: "input": "what is weather in sf", } }, + "thread_id": "1", }, parent_config=[ c async for c in app_w_interrupt.checkpointer.alist(config, limit=2) @@ -2905,6 +2946,7 @@ async def test_conditional_graph(checkpointer_name: str) -> None: ), } }, + "thread_id": "1", }, parent_config=[ c async for c in app_w_interrupt.checkpointer.alist(config, limit=2) @@ -2971,6 +3013,7 @@ async def test_conditional_graph(checkpointer_name: str) -> None: } } }, + "thread_id": "2", }, parent_config=[ c async for c in app_w_interrupt.checkpointer.alist(config, limit=2) @@ -3020,6 +3063,7 @@ async def test_conditional_graph(checkpointer_name: str) -> None: "input": "what is weather in sf", } }, + "thread_id": "2", }, parent_config=[ c async for c in app_w_interrupt.checkpointer.alist(config, limit=2) @@ -3144,6 +3188,7 @@ async def test_conditional_graph(checkpointer_name: str) -> None: ), } }, + "thread_id": "2", }, parent_config=[ c async for c in app_w_interrupt.checkpointer.alist(config, limit=2) @@ -3210,6 +3255,7 @@ async def test_conditional_graph(checkpointer_name: str) -> None: } } }, + "thread_id": "3", }, parent_config=[ c async for c in app_w_interrupt.checkpointer.alist(config, limit=2) @@ -3630,6 +3676,7 @@ async def test_conditional_graph_state( ), } }, + "thread_id": "1", }, parent_config=[ c async for c in app_w_interrupt.checkpointer.alist(config, limit=2) @@ -3676,6 +3723,7 @@ async def test_conditional_graph_state( ) } }, + "thread_id": "1", }, parent_config=[ c async for c in app_w_interrupt.checkpointer.alist(config, limit=2) @@ -3756,6 +3804,7 @@ async def test_conditional_graph_state( ) } }, + "thread_id": "1", }, parent_config=[ c async for c in app_w_interrupt.checkpointer.alist(config, limit=2) @@ -3815,6 +3864,7 @@ async def test_conditional_graph_state( ), } }, + "thread_id": "2", }, parent_config=[ c async for c in app_w_interrupt.checkpointer.alist(config, limit=2) @@ -3860,6 +3910,7 @@ async def test_conditional_graph_state( ) } }, + "thread_id": "2", }, parent_config=[ c async for c in app_w_interrupt.checkpointer.alist(config, limit=2) @@ -3938,6 +3989,7 @@ async def test_conditional_graph_state( ) } }, + "thread_id": "2", }, parent_config=[ c async for c in app_w_interrupt.checkpointer.alist(config, limit=2) @@ -4632,6 +4684,7 @@ async def test_state_graph_packets(checkpointer_name: str) -> None: ) } }, + "thread_id": "1", }, parent_config=[ c async for c in app_w_interrupt.checkpointer.alist(config, limit=2) @@ -4685,6 +4738,7 @@ async def test_state_graph_packets(checkpointer_name: str) -> None: ) } }, + "thread_id": "1", }, parent_config=[ c async for c in app_w_interrupt.checkpointer.alist(config, limit=2) @@ -4794,6 +4848,7 @@ async def test_state_graph_packets(checkpointer_name: str) -> None: ) }, }, + "thread_id": "1", }, parent_config=[ c async for c in app_w_interrupt.checkpointer.alist(config, limit=2) @@ -4843,6 +4898,7 @@ async def test_state_graph_packets(checkpointer_name: str) -> None: "messages": AIMessage(content="answer", id="ai2"), } }, + "thread_id": "1", }, parent_config=[ c async for c in app_w_interrupt.checkpointer.alist(config, limit=2) @@ -5103,6 +5159,7 @@ async def test_message_graph(checkpointer_name: str) -> None: id="ai1", ) }, + "thread_id": "1", }, parent_config=[ c async for c in app_w_interrupt.checkpointer.alist(config, limit=2) @@ -5152,6 +5209,7 @@ async def test_message_graph(checkpointer_name: str) -> None: id="ai1", ) }, + "thread_id": "1", }, parent_config=[ c async for c in app_w_interrupt.checkpointer.alist(config, limit=2) @@ -5237,6 +5295,7 @@ async def test_message_graph(checkpointer_name: str) -> None: id="ai2", ) }, + "thread_id": "1", }, parent_config=[ c async for c in app_w_interrupt.checkpointer.alist(config, limit=2) @@ -5280,6 +5339,7 @@ async def test_message_graph(checkpointer_name: str) -> None: "source": "update", "step": 5, "writes": {"agent": AIMessage(content="answer", id="ai2")}, + "thread_id": "1", }, parent_config=[ c async for c in app_w_interrupt.checkpointer.alist(config, limit=2) @@ -5582,12 +5642,16 @@ async def test_start_branch_then(checkpointer_name: str) -> None: "source": "loop", "step": 0, "writes": None, + "assistant_id": "a", + "thread_id": "1", }, { "parents": {}, "source": "input", "step": -1, "writes": {"__start__": {"my_key": "value", "market": "DE"}}, + "assistant_id": "a", + "thread_id": "1", }, ] assert await tool_two.aget_state(thread1) == StateSnapshot( @@ -5598,7 +5662,14 @@ async def test_start_branch_then(checkpointer_name: str) -> None: created_at=(await tool_two.checkpointer.aget_tuple(thread1)).checkpoint[ "ts" ], - metadata={"parents": {}, "source": "loop", "step": 0, "writes": None}, + metadata={ + "parents": {}, + "source": "loop", + "step": 0, + "writes": None, + "assistant_id": "a", + "thread_id": "1", + }, parent_config=[ c async for c in tool_two.checkpointer.alist(thread1, limit=2) ][-1].config, @@ -5621,6 +5692,8 @@ async def test_start_branch_then(checkpointer_name: str) -> None: "source": "loop", "step": 1, "writes": {"tool_two_slow": {"my_key": " slow"}}, + "assistant_id": "a", + "thread_id": "1", }, parent_config=[ c async for c in tool_two.checkpointer.alist(thread1, limit=2) @@ -5641,7 +5714,14 @@ async def test_start_branch_then(checkpointer_name: str) -> None: created_at=(await tool_two.checkpointer.aget_tuple(thread2)).checkpoint[ "ts" ], - metadata={"parents": {}, "source": "loop", "step": 0, "writes": None}, + metadata={ + "parents": {}, + "source": "loop", + "step": 0, + "writes": None, + "assistant_id": "a", + "thread_id": "2", + }, parent_config=[ c async for c in tool_two.checkpointer.alist(thread2, limit=2) ][-1].config, @@ -5664,6 +5744,8 @@ async def test_start_branch_then(checkpointer_name: str) -> None: "source": "loop", "step": 1, "writes": {"tool_two_fast": {"my_key": " fast"}}, + "assistant_id": "a", + "thread_id": "2", }, parent_config=[ c async for c in tool_two.checkpointer.alist(thread2, limit=2) @@ -5684,7 +5766,14 @@ async def test_start_branch_then(checkpointer_name: str) -> None: created_at=(await tool_two.checkpointer.aget_tuple(thread3)).checkpoint[ "ts" ], - metadata={"parents": {}, "source": "loop", "step": 0, "writes": None}, + metadata={ + "parents": {}, + "source": "loop", + "step": 0, + "writes": None, + "assistant_id": "b", + "thread_id": "3", + }, parent_config=[ c async for c in tool_two.checkpointer.alist(thread3, limit=2) ][-1].config, @@ -5704,6 +5793,8 @@ async def test_start_branch_then(checkpointer_name: str) -> None: "source": "update", "step": 1, "writes": {START: {"my_key": "key"}}, + "assistant_id": "b", + "thread_id": "3", }, parent_config=[ c async for c in tool_two.checkpointer.alist(thread3, limit=2) @@ -5727,6 +5818,8 @@ async def test_start_branch_then(checkpointer_name: str) -> None: "source": "loop", "step": 2, "writes": {"tool_two_fast": {"my_key": " fast"}}, + "assistant_id": "b", + "thread_id": "3", }, parent_config=[ c async for c in tool_two.checkpointer.alist(thread3, limit=2) @@ -5795,6 +5888,7 @@ async def test_branch_then(checkpointer_name: str) -> None: "source": "input", "step": -1, "writes": {"__start__": {"my_key": "value", "market": "DE"}}, + "thread_id": "10", }, "parent_config": None, "next": ["__start__"], @@ -5833,6 +5927,7 @@ async def test_branch_then(checkpointer_name: str) -> None: "source": "loop", "step": 0, "writes": None, + "thread_id": "10", }, "parent_config": { "tags": [], @@ -5904,6 +5999,7 @@ async def test_branch_then(checkpointer_name: str) -> None: "source": "loop", "step": 1, "writes": {"prepare": {"my_key": " prepared"}}, + "thread_id": "10", }, "parent_config": { "tags": [], @@ -5975,6 +6071,7 @@ async def test_branch_then(checkpointer_name: str) -> None: "source": "loop", "step": 2, "writes": {"tool_two_slow": {"my_key": " slow"}}, + "thread_id": "10", }, "parent_config": { "tags": [], @@ -6046,6 +6143,7 @@ async def test_branch_then(checkpointer_name: str) -> None: "source": "loop", "step": 3, "writes": {"finish": {"my_key": " finished"}}, + "thread_id": "10", }, "parent_config": { "tags": [], @@ -6103,6 +6201,7 @@ async def test_branch_then(checkpointer_name: str) -> None: "source": "input", "step": -1, "writes": {"__start__": {"my_key": "value", "market": "DE"}}, + "thread_id": "11", }, "parent_config": None, "next": ["__start__"], @@ -6141,6 +6240,7 @@ async def test_branch_then(checkpointer_name: str) -> None: "source": "loop", "step": 0, "writes": None, + "thread_id": "11", }, "parent_config": { "tags": [], @@ -6212,6 +6312,7 @@ async def test_branch_then(checkpointer_name: str) -> None: "source": "loop", "step": 1, "writes": {"prepare": {"my_key": " prepared"}}, + "thread_id": "11", }, "parent_config": { "tags": [], @@ -6249,6 +6350,7 @@ async def test_branch_then(checkpointer_name: str) -> None: "source": "loop", "step": 1, "writes": {"prepare": {"my_key": " prepared"}}, + "thread_id": "11", }, parent_config=[ c async for c in tool_two.checkpointer.alist(thread1, limit=2) @@ -6272,6 +6374,7 @@ async def test_branch_then(checkpointer_name: str) -> None: "source": "loop", "step": 3, "writes": {"finish": {"my_key": " finished"}}, + "thread_id": "11", }, parent_config=[ c async for c in tool_two.checkpointer.alist(thread1, limit=2) @@ -6297,6 +6400,7 @@ async def test_branch_then(checkpointer_name: str) -> None: "source": "loop", "step": 1, "writes": {"prepare": {"my_key": " prepared"}}, + "thread_id": "12", }, parent_config=[ c async for c in tool_two.checkpointer.alist(thread2, limit=2) @@ -6320,6 +6424,7 @@ async def test_branch_then(checkpointer_name: str) -> None: "source": "loop", "step": 3, "writes": {"finish": {"my_key": " finished"}}, + "thread_id": "12", }, parent_config=[ c async for c in tool_two.checkpointer.alist(thread2, limit=2) @@ -6353,6 +6458,7 @@ async def test_branch_then(checkpointer_name: str) -> None: "source": "loop", "step": 1, "writes": {"prepare": {"my_key": " prepared"}}, + "thread_id": "21", }, parent_config=[ c async for c in tool_two.checkpointer.alist(thread1, limit=2) @@ -6376,6 +6482,7 @@ async def test_branch_then(checkpointer_name: str) -> None: "source": "loop", "step": 3, "writes": {"finish": {"my_key": " finished"}}, + "thread_id": "21", }, parent_config=[ c async for c in tool_two.checkpointer.alist(thread1, limit=2) @@ -6401,6 +6508,7 @@ async def test_branch_then(checkpointer_name: str) -> None: "source": "loop", "step": 1, "writes": {"prepare": {"my_key": " prepared"}}, + "thread_id": "22", }, parent_config=[ c async for c in tool_two.checkpointer.alist(thread2, limit=2) @@ -6424,6 +6532,7 @@ async def test_branch_then(checkpointer_name: str) -> None: "source": "loop", "step": 3, "writes": {"finish": {"my_key": " finished"}}, + "thread_id": "22", }, parent_config=[ c async for c in tool_two.checkpointer.alist(thread2, limit=2) @@ -6447,6 +6556,7 @@ async def test_branch_then(checkpointer_name: str) -> None: "source": "update", "step": 0, "writes": {START: {"my_key": "key", "market": "DE"}}, + "thread_id": "23", }, parent_config=None, ) @@ -6469,6 +6579,7 @@ async def test_branch_then(checkpointer_name: str) -> None: "source": "loop", "step": 1, "writes": {"prepare": {"my_key": " prepared"}}, + "thread_id": "23", }, parent_config=uconfig, ) @@ -6490,6 +6601,7 @@ async def test_branch_then(checkpointer_name: str) -> None: "source": "loop", "step": 3, "writes": {"finish": {"my_key": " finished"}}, + "thread_id": "23", }, parent_config=[ c async for c in tool_two.checkpointer.alist(thread3, limit=2) @@ -6841,6 +6953,7 @@ async def test_in_one_fan_out_state_graph_waiting_edge_custom_state_class( "source": "loop", "writes": {"qa": {"answer": "doc1,doc2,doc3,doc4"}}, "step": 4, + "thread_id": "1", }, created_at=AnyStr(), parent_config={ @@ -7806,6 +7919,7 @@ async def test_nested_graph_state(checkpointer_name: str) -> None: "source": "loop", "writes": {"outer_1": {"my_key": "hi my value"}}, "step": 1, + "thread_id": "1", }, created_at=AnyStr(), parent_config={ @@ -7832,9 +7946,8 @@ async def test_nested_graph_state(checkpointer_name: str) -> None: tasks=( PregelTask( AnyStr(), - name="inner_2", - path=(PULL, "inner_2"), - error=None, + "inner_2", + (PULL, "inner_2"), ), ), next=("inner_2",), @@ -7860,6 +7973,13 @@ async def test_nested_graph_state(checkpointer_name: str) -> None: } }, "step": 1, + "thread_id": "1", + "checkpoint_ns": AnyStr("inner:"), + "langgraph_node": "inner", + "langgraph_path": [PULL, "inner"], + "langgraph_step": 2, + "langgraph_triggers": ["outer_1"], + "langgraph_checkpoint_ns": AnyStr("inner:"), }, created_at=AnyStr(), parent_config={ @@ -7888,6 +8008,7 @@ async def test_nested_graph_state(checkpointer_name: str) -> None: "source": "loop", "writes": {"outer_1": {"my_key": "hi my value"}}, "step": 1, + "thread_id": "1", }, created_at=AnyStr(), parent_config={ @@ -7929,6 +8050,7 @@ async def test_nested_graph_state(checkpointer_name: str) -> None: "source": "loop", "writes": {"outer_1": {"my_key": "hi my value"}}, "step": 1, + "thread_id": "1", }, created_at=AnyStr(), parent_config={ @@ -7962,6 +8084,7 @@ async def test_nested_graph_state(checkpointer_name: str) -> None: "source": "loop", "writes": None, "step": 0, + "thread_id": "1", }, created_at=AnyStr(), parent_config={ @@ -7995,6 +8118,7 @@ async def test_nested_graph_state(checkpointer_name: str) -> None: "source": "input", "writes": {"__start__": {"my_key": "my value"}}, "step": -1, + "thread_id": "1", }, created_at=AnyStr(), parent_config=None, @@ -8014,7 +8138,7 @@ async def test_nested_graph_state(checkpointer_name: str) -> None: "checkpoint_ns": AnyStr("inner:"), "checkpoint_id": AnyStr(), "checkpoint_map": AnyDict( - {"": AnyStr(), AnyStr("inner:"): AnyStr()} + {"": AnyStr(), AnyStr("child:"): AnyStr()} ), } }, @@ -8028,6 +8152,13 @@ async def test_nested_graph_state(checkpointer_name: str) -> None: }, "step": 1, "parents": {"": AnyStr()}, + "thread_id": "1", + "checkpoint_ns": AnyStr("inner:"), + "langgraph_node": "inner", + "langgraph_path": [PULL, "inner"], + "langgraph_step": 2, + "langgraph_triggers": ["outer_1"], + "langgraph_checkpoint_ns": AnyStr("inner:"), }, created_at=AnyStr(), parent_config={ @@ -8036,13 +8167,11 @@ async def test_nested_graph_state(checkpointer_name: str) -> None: "checkpoint_ns": AnyStr("inner:"), "checkpoint_id": AnyStr(), "checkpoint_map": AnyDict( - {"": AnyStr(), AnyStr("inner:"): AnyStr()} + {"": AnyStr(), AnyStr("child:"): AnyStr()} ), } }, - tasks=( - PregelTask(id=AnyStr(), name="inner_2", path=(PULL, "inner_2")), - ), + tasks=(PregelTask(AnyStr(), "inner_2", (PULL, "inner_2")),), ), StateSnapshot( values={"my_key": "hi my value"}, @@ -8053,7 +8182,7 @@ async def test_nested_graph_state(checkpointer_name: str) -> None: "checkpoint_ns": AnyStr("inner:"), "checkpoint_id": AnyStr(), "checkpoint_map": AnyDict( - {"": AnyStr(), AnyStr("inner:"): AnyStr()} + {"": AnyStr(), AnyStr("child:"): AnyStr()} ), } }, @@ -8062,6 +8191,13 @@ async def test_nested_graph_state(checkpointer_name: str) -> None: "writes": None, "step": 0, "parents": {"": AnyStr()}, + "thread_id": "1", + "checkpoint_ns": AnyStr("inner:"), + "langgraph_node": "inner", + "langgraph_path": [PULL, "inner"], + "langgraph_step": 2, + "langgraph_triggers": ["outer_1"], + "langgraph_checkpoint_ns": AnyStr("inner:"), }, created_at=AnyStr(), parent_config={ @@ -8070,15 +8206,15 @@ async def test_nested_graph_state(checkpointer_name: str) -> None: "checkpoint_ns": AnyStr("inner:"), "checkpoint_id": AnyStr(), "checkpoint_map": AnyDict( - {"": AnyStr(), AnyStr("inner:"): AnyStr()} + {"": AnyStr(), AnyStr("child:"): AnyStr()} ), } }, tasks=( PregelTask( - id=AnyStr(), - name="inner_1", - path=(PULL, "inner_1"), + AnyStr(), + "inner_1", + (PULL, "inner_1"), result={ "my_key": "hi my value here", "my_other_key": "hi my value", @@ -8095,7 +8231,7 @@ async def test_nested_graph_state(checkpointer_name: str) -> None: "checkpoint_ns": AnyStr("inner:"), "checkpoint_id": AnyStr(), "checkpoint_map": AnyDict( - {"": AnyStr(), AnyStr("inner:"): AnyStr()} + {"": AnyStr(), AnyStr("child:"): AnyStr()} ), } }, @@ -8104,14 +8240,21 @@ async def test_nested_graph_state(checkpointer_name: str) -> None: "writes": {"__start__": {"my_key": "hi my value"}}, "step": -1, "parents": {"": AnyStr()}, + "thread_id": "1", + "checkpoint_ns": AnyStr("inner:"), + "langgraph_node": "inner", + "langgraph_path": [PULL, "inner"], + "langgraph_step": 2, + "langgraph_triggers": ["outer_1"], + "langgraph_checkpoint_ns": AnyStr("inner:"), }, created_at=AnyStr(), parent_config=None, tasks=( PregelTask( - id=AnyStr(), - name="__start__", - path=(PULL, "__start__"), + AnyStr(), + "__start__", + (PULL, "__start__"), result={"my_key": "hi my value"}, ), ), @@ -8139,6 +8282,7 @@ async def test_nested_graph_state(checkpointer_name: str) -> None: "outer_2": {"my_key": "hi my value here and there and back again"} }, "step": 3, + "thread_id": "1", }, created_at=AnyStr(), parent_config={ @@ -8172,6 +8316,7 @@ async def test_nested_graph_state(checkpointer_name: str) -> None: } }, "step": 3, + "thread_id": "1", }, created_at=AnyStr(), parent_config={ @@ -8205,6 +8350,7 @@ async def test_nested_graph_state(checkpointer_name: str) -> None: "source": "loop", "writes": {"inner": {"my_key": "hi my value here and there"}}, "step": 2, + "thread_id": "1", }, created_at=AnyStr(), parent_config={ @@ -8244,6 +8390,7 @@ async def test_nested_graph_state(checkpointer_name: str) -> None: "source": "loop", "writes": {"outer_1": {"my_key": "hi my value"}}, "step": 1, + "thread_id": "1", }, created_at=AnyStr(), parent_config={ @@ -8277,6 +8424,7 @@ async def test_nested_graph_state(checkpointer_name: str) -> None: "source": "loop", "writes": None, "step": 0, + "thread_id": "1", }, created_at=AnyStr(), parent_config={ @@ -8310,6 +8458,7 @@ async def test_nested_graph_state(checkpointer_name: str) -> None: "source": "input", "writes": {"__start__": {"my_key": "my value"}}, "step": -1, + "thread_id": "1", }, created_at=AnyStr(), parent_config=None, @@ -8415,6 +8564,7 @@ async def test_doubly_nested_graph_state(checkpointer_name: str) -> None: "source": "loop", "writes": {"parent_1": {"my_key": "hi my value"}}, "step": 1, + "thread_id": "1", }, created_at=AnyStr(), parent_config={ @@ -8449,9 +8599,6 @@ async def test_doubly_nested_graph_state(checkpointer_name: str) -> None: "thread_id": "1", "checkpoint_ns": AnyStr("child:"), "checkpoint_id": AnyStr(), - "checkpoint_map": AnyDict( - {"": AnyStr(), AnyStr("child:"): AnyStr()} - ), } }, metadata={ @@ -8459,6 +8606,7 @@ async def test_doubly_nested_graph_state(checkpointer_name: str) -> None: "source": "loop", "writes": None, "step": 0, + "thread_id": "1", }, created_at=AnyStr(), parent_config={ @@ -8466,9 +8614,6 @@ async def test_doubly_nested_graph_state(checkpointer_name: str) -> None: "thread_id": "1", "checkpoint_ns": AnyStr("child:"), "checkpoint_id": AnyStr(), - "checkpoint_map": AnyDict( - {"": AnyStr(), AnyStr("child:"): AnyStr()} - ), } }, ).tasks[0] @@ -8508,6 +8653,13 @@ async def test_doubly_nested_graph_state(checkpointer_name: str) -> None: "source": "loop", "writes": {"grandchild_1": {"my_key": "hi my value here"}}, "step": 1, + "thread_id": "1", + "checkpoint_ns": AnyStr("child:"), + "langgraph_checkpoint_ns": AnyStr("child:"), + "langgraph_node": "child_1", + "langgraph_path": [PULL, AnyStr("child_1")], + "langgraph_step": 1, + "langgraph_triggers": [AnyStr("start:child_1")], }, created_at=AnyStr(), parent_config={ @@ -8580,6 +8732,16 @@ async def test_doubly_nested_graph_state(checkpointer_name: str) -> None: } }, "step": 1, + "thread_id": "1", + "checkpoint_ns": AnyStr("child:"), + "langgraph_checkpoint_ns": AnyStr("child:"), + "langgraph_node": "child_1", + "langgraph_path": [ + PULL, + AnyStr("child_1"), + ], + "langgraph_step": 1, + "langgraph_triggers": [AnyStr("start:child_1")], }, created_at=AnyStr(), parent_config={ @@ -8617,6 +8779,13 @@ async def test_doubly_nested_graph_state(checkpointer_name: str) -> None: "source": "loop", "writes": None, "step": 0, + "thread_id": "1", + "checkpoint_ns": AnyStr("child:"), + "langgraph_node": "child", + "langgraph_path": [PULL, AnyStr("child")], + "langgraph_step": 2, + "langgraph_triggers": [AnyStr("parent_1")], + "langgraph_checkpoint_ns": AnyStr("child:"), }, created_at=AnyStr(), parent_config={ @@ -8645,6 +8814,7 @@ async def test_doubly_nested_graph_state(checkpointer_name: str) -> None: "source": "loop", "writes": {"parent_1": {"my_key": "hi my value"}}, "step": 1, + "thread_id": "1", }, created_at=AnyStr(), parent_config={ @@ -8692,6 +8862,7 @@ async def test_doubly_nested_graph_state(checkpointer_name: str) -> None: } }, "step": 3, + "thread_id": "1", }, created_at=AnyStr(), parent_config={ @@ -8728,6 +8899,7 @@ async def test_doubly_nested_graph_state(checkpointer_name: str) -> None: } }, "step": 3, + "thread_id": "1", }, created_at=AnyStr(), parent_config={ @@ -8753,6 +8925,7 @@ async def test_doubly_nested_graph_state(checkpointer_name: str) -> None: "source": "loop", "writes": {"child": {"my_key": "hi my value here and there"}}, "step": 2, + "thread_id": "1", }, created_at=AnyStr(), parent_config={ @@ -8796,6 +8969,7 @@ async def test_doubly_nested_graph_state(checkpointer_name: str) -> None: "source": "loop", "writes": {"parent_1": {"my_key": "hi my value"}}, "step": 1, + "thread_id": "1", }, created_at=AnyStr(), parent_config={ @@ -8821,6 +8995,7 @@ async def test_doubly_nested_graph_state(checkpointer_name: str) -> None: "source": "loop", "writes": None, "step": 0, + "thread_id": "1", }, created_at=AnyStr(), parent_config={ @@ -8851,6 +9026,7 @@ async def test_doubly_nested_graph_state(checkpointer_name: str) -> None: "source": "input", "writes": {"my_key": "my value"}, "step": -1, + "thread_id": "1", }, created_at=AnyStr(), parent_config=None, @@ -8885,6 +9061,13 @@ async def test_doubly_nested_graph_state(checkpointer_name: str) -> None: "writes": {"child_1": {"my_key": "hi my value here and there"}}, "step": 1, "parents": {"": AnyStr()}, + "thread_id": "1", + "checkpoint_ns": AnyStr("child:"), + "langgraph_node": "child", + "langgraph_path": [PULL, AnyStr("child")], + "langgraph_step": 2, + "langgraph_triggers": [AnyStr("parent_1")], + "langgraph_checkpoint_ns": AnyStr("child:"), }, created_at=AnyStr(), parent_config={ @@ -8917,6 +9100,13 @@ async def test_doubly_nested_graph_state(checkpointer_name: str) -> None: "writes": None, "step": 0, "parents": {"": AnyStr()}, + "thread_id": "1", + "checkpoint_ns": AnyStr("child:"), + "langgraph_node": "child", + "langgraph_path": [PULL, AnyStr("child")], + "langgraph_step": 2, + "langgraph_triggers": [AnyStr("parent_1")], + "langgraph_checkpoint_ns": AnyStr("child:"), }, created_at=AnyStr(), parent_config={ @@ -8962,6 +9152,13 @@ async def test_doubly_nested_graph_state(checkpointer_name: str) -> None: "writes": {"__start__": {"my_key": "hi my value"}}, "step": -1, "parents": {"": AnyStr()}, + "thread_id": "1", + "checkpoint_ns": AnyStr("child:"), + "langgraph_node": "child", + "langgraph_path": [PULL, AnyStr("child")], + "langgraph_step": 2, + "langgraph_triggers": [AnyStr("parent_1")], + "langgraph_checkpoint_ns": AnyStr("child:"), }, created_at=AnyStr(), parent_config=None, @@ -9009,6 +9206,16 @@ async def test_doubly_nested_graph_state(checkpointer_name: str) -> None: AnyStr("child:"): AnyStr(), } ), + "thread_id": "1", + "checkpoint_ns": AnyStr("child:"), + "langgraph_checkpoint_ns": AnyStr("child:"), + "langgraph_node": "child_1", + "langgraph_path": [ + PULL, + AnyStr("child_1"), + ], + "langgraph_step": 1, + "langgraph_triggers": [AnyStr("start:child_1")], }, created_at=AnyStr(), parent_config={ @@ -9054,6 +9261,16 @@ async def test_doubly_nested_graph_state(checkpointer_name: str) -> None: AnyStr("child:"): AnyStr(), } ), + "thread_id": "1", + "checkpoint_ns": AnyStr("child:"), + "langgraph_checkpoint_ns": AnyStr("child:"), + "langgraph_node": "child_1", + "langgraph_path": [ + PULL, + AnyStr("child_1"), + ], + "langgraph_step": 1, + "langgraph_triggers": [AnyStr("start:child_1")], }, created_at=AnyStr(), parent_config={ @@ -9106,6 +9323,16 @@ async def test_doubly_nested_graph_state(checkpointer_name: str) -> None: AnyStr("child:"): AnyStr(), } ), + "thread_id": "1", + "checkpoint_ns": AnyStr("child:"), + "langgraph_checkpoint_ns": AnyStr("child:"), + "langgraph_node": "child_1", + "langgraph_path": [ + PULL, + AnyStr("child_1"), + ], + "langgraph_step": 1, + "langgraph_triggers": [AnyStr("start:child_1")], }, created_at=AnyStr(), parent_config={ @@ -9158,6 +9385,16 @@ async def test_doubly_nested_graph_state(checkpointer_name: str) -> None: AnyStr("child:"): AnyStr(), } ), + "thread_id": "1", + "checkpoint_ns": AnyStr("child:"), + "langgraph_checkpoint_ns": AnyStr("child:"), + "langgraph_node": "child_1", + "langgraph_path": [ + PULL, + AnyStr("child_1"), + ], + "langgraph_step": 1, + "langgraph_triggers": [AnyStr("start:child_1")], }, created_at=AnyStr(), parent_config=None, @@ -9272,7 +9509,13 @@ async def test_send_to_nested_graphs(checkpointer_name: str) -> None: "checkpoint_id": AnyStr(), } }, - metadata={"parents": {}, "source": "loop", "writes": None, "step": 0}, + metadata={ + "parents": {}, + "source": "loop", + "writes": None, + "step": 0, + "thread_id": "1", + }, created_at=AnyStr(), parent_config={ "configurable": { @@ -9319,6 +9562,7 @@ async def test_send_to_nested_graphs(checkpointer_name: str) -> None: ] }, "step": 1, + "thread_id": "1", }, created_at=AnyStr(), parent_config={ @@ -9361,6 +9605,7 @@ async def test_send_to_nested_graphs(checkpointer_name: str) -> None: ] }, "step": 1, + "thread_id": "1", }, created_at=AnyStr(), parent_config={ @@ -9407,7 +9652,13 @@ async def test_send_to_nested_graphs(checkpointer_name: str) -> None: "checkpoint_id": AnyStr(), } }, - metadata={"parents": {}, "source": "loop", "writes": None, "step": 0}, + metadata={ + "parents": {}, + "source": "loop", + "writes": None, + "step": 0, + "thread_id": "1", + }, created_at=AnyStr(), parent_config={ "configurable": { @@ -9440,6 +9691,7 @@ async def test_send_to_nested_graphs(checkpointer_name: str) -> None: "source": "input", "writes": {"__start__": {"subjects": ["cats", "dogs"]}}, "step": -1, + "thread_id": "1", }, created_at=AnyStr(), parent_config=None, @@ -9617,6 +9869,7 @@ async def test_weather_subgraph( "writes": {"router_node": {"route": "weather"}}, "step": 1, "parents": {}, + "thread_id": "1", }, created_at=AnyStr(), parent_config={ @@ -9708,6 +9961,7 @@ async def test_weather_subgraph( "writes": {"router_node": {"route": "weather"}}, "step": 1, "parents": {}, + "thread_id": "14", }, created_at=AnyStr(), parent_config={ @@ -9748,6 +10002,15 @@ async def test_weather_subgraph( "writes": {"model_node": {"city": "San Francisco"}}, "step": 1, "parents": {"": AnyStr()}, + "thread_id": "14", + "checkpoint_ns": AnyStr("weather_graph:"), + "langgraph_node": "weather_graph", + "langgraph_path": [PULL, "weather_graph"], + "langgraph_step": 2, + "langgraph_triggers": [ + "branch:router_node:route_after_prediction:weather_graph" + ], + "langgraph_checkpoint_ns": AnyStr("weather_graph:"), }, created_at=AnyStr(), parent_config={ @@ -9798,6 +10061,7 @@ async def test_weather_subgraph( "writes": {"router_node": {"route": "weather"}}, "step": 1, "parents": {}, + "thread_id": "14", }, created_at=AnyStr(), parent_config={ @@ -9835,8 +10099,8 @@ async def test_weather_subgraph( } }, metadata={ - "source": "update", "step": 2, + "source": "update", "writes": { "weather_node": { "messages": [ @@ -9845,6 +10109,16 @@ async def test_weather_subgraph( } }, "parents": {"": AnyStr()}, + "thread_id": "14", + "checkpoint_id": AnyStr(), + "checkpoint_ns": AnyStr("weather_graph:"), + "langgraph_node": "weather_graph", + "langgraph_path": [PULL, "weather_graph"], + "langgraph_step": 2, + "langgraph_triggers": [ + "branch:router_node:route_after_prediction:weather_graph" + ], + "langgraph_checkpoint_ns": AnyStr("weather_graph:"), }, created_at=AnyStr(), parent_config={ diff --git a/libs/langgraph/tests/test_remote_graph.py b/libs/langgraph/tests/test_remote_graph.py index 21fb8278f..70857ed61 100644 --- a/libs/langgraph/tests/test_remote_graph.py +++ b/libs/langgraph/tests/test_remote_graph.py @@ -73,7 +73,9 @@ def test_get_graph(): "__start__": DrawableNode( id="__start__", name="__start__", data="__start__", metadata=None ), - "__end__": DrawableNode(id="__end__", name="__end__", data="__end__", metadata=None), + "__end__": DrawableNode( + id="__end__", name="__end__", data="__end__", metadata=None + ), "agent": DrawableNode( id="agent", name="agent_1", @@ -120,7 +122,9 @@ async def test_aget_graph(): "__start__": DrawableNode( id="__start__", name="__start__", data="__start__", metadata=None ), - "__end__": DrawableNode(id="__end__", name="__end__", data="__end__", metadata=None), + "__end__": DrawableNode( + id="__end__", name="__end__", data="__end__", metadata=None + ), "agent": DrawableNode( id="agent", name="agent_1", From 895079bbdc1e7419bdd1cca947d79d1aa8e8debd Mon Sep 17 00:00:00 2001 From: Vadym Barda Date: Mon, 4 Nov 2024 14:34:07 -0500 Subject: [PATCH 42/60] langgraph: release 0.2.45 (#2326) --- libs/langgraph/pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libs/langgraph/pyproject.toml b/libs/langgraph/pyproject.toml index 1f711bc64..05c4d1836 100644 --- a/libs/langgraph/pyproject.toml +++ b/libs/langgraph/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "langgraph" -version = "0.2.44" +version = "0.2.45" description = "Building stateful, multi-actor applications with LLMs" authors = [] license = "MIT" From 5606bef3dd52d7cefcb209cbe7fa76dcd2b3e73b Mon Sep 17 00:00:00 2001 From: Nuno Campos Date: Mon, 4 Nov 2024 11:52:39 -0800 Subject: [PATCH 43/60] lib: Add two more tests for Send --- libs/langgraph/langgraph/types.py | 6 ++ libs/langgraph/tests/test_pregel.py | 99 ++++++++++++++++++++++++++++- 2 files changed, 104 insertions(+), 1 deletion(-) diff --git a/libs/langgraph/langgraph/types.py b/libs/langgraph/langgraph/types.py index f7f330b83..605f06f60 100644 --- a/libs/langgraph/langgraph/types.py +++ b/libs/langgraph/langgraph/types.py @@ -238,6 +238,12 @@ class Control(Generic[N]): self.trigger = trigger self.send = send + def __repr__(self): + contents = ", ".join( + f"{key}={value!r}" for key, value in self.__dict__.items() if value + ) + return f"Control({contents})" + StreamChunk = tuple[tuple[str, ...], str, Any] diff --git a/libs/langgraph/tests/test_pregel.py b/libs/langgraph/tests/test_pregel.py index 563ce295e..8536a08c3 100644 --- a/libs/langgraph/tests/test_pregel.py +++ b/libs/langgraph/tests/test_pregel.py @@ -74,7 +74,7 @@ from langgraph.pregel import ( from langgraph.pregel.retry import RetryPolicy from langgraph.store.base import BaseStore from langgraph.store.memory import InMemoryStore -from langgraph.types import Interrupt, PregelTask, Send, StreamWriter +from langgraph.types import Control, Interrupt, PregelTask, Send, StreamWriter from tests.any_str import AnyDict, AnyStr, AnyVersion, FloatBetween, UnsortedSequence from tests.conftest import ( ALL_CHECKPOINTERS_SYNC, @@ -1743,6 +1743,103 @@ def test_cond_edge_after_send() -> None: assert graph.invoke(["0"]) == ["0", "1", "2", "2", "3"] +def test_concurrent_emit_sends() -> None: + class Node: + def __init__(self, name: str): + self.name = name + setattr(self, "__name__", name) + + def __call__(self, state): + return ( + [self.name] + if isinstance(state, list) + else ["|".join((self.name, str(state)))] + ) + + def send_for_fun(state): + return [Send("2", 1), Send("2", 2), "3.1"] + + def send_for_profit(state): + return [Send("2", 3), Send("2", 4)] + + def route_to_three(state) -> Literal["3"]: + return "3" + + builder = StateGraph(Annotated[list, operator.add]) + builder.add_node(Node("1")) + builder.add_node(Node("1.1")) + builder.add_node(Node("2")) + builder.add_node(Node("3")) + builder.add_node(Node("3.1")) + builder.add_edge(START, "1") + builder.add_edge(START, "1.1") + builder.add_conditional_edges("1", send_for_fun) + builder.add_conditional_edges("1.1", send_for_profit) + builder.add_conditional_edges("2", route_to_three) + graph = builder.compile() + assert graph.invoke(["0"]) == [ + "0", + "1", + "1.1", + "2|1", + "2|2", + "2|3", + "2|4", + "3.1", + "3", + ] + + +def test_send_sequences() -> None: + class Node: + def __init__(self, name: str): + self.name = name + setattr(self, "__name__", name) + + def __call__(self, state): + update = ( + [self.name] + if isinstance(state, list) # or isinstance(state, Control) + else ["|".join((self.name, str(state)))] + ) + if isinstance(state, Control): + state.update_state = update + return state + else: + return update + + def send_for_fun(state): + return [ + Send("2", Control(send=Send("2", 3))), + Send("2", Control(send=Send("2", 4))), + "3.1", + ] + + def route_to_three(state) -> Literal["3"]: + return "3" + + builder = StateGraph(Annotated[list, operator.add]) + builder.add_node(Node("1")) + builder.add_node(Node("2")) + builder.add_node(Node("3")) + builder.add_node(Node("3.1")) + builder.add_edge(START, "1") + builder.add_conditional_edges("1", send_for_fun) + builder.add_conditional_edges("2", route_to_three) + graph = builder.compile() + assert graph.invoke(["0"]) == [ + "0", + "1", + "2|Control(send=Send(node='2', arg=3))", + "2|Control(send=Send(node='2', arg=4))", + "3.1", + "2|3", + "2|4", + "3", + "3", + ] + + @pytest.mark.parametrize("checkpointer_name", ALL_CHECKPOINTERS_SYNC) def test_invoke_checkpoint_three( mocker: MockerFixture, request: pytest.FixtureRequest, checkpointer_name: str From 38332fd3c6a01c43bb61756f9b6934da53b1165c Mon Sep 17 00:00:00 2001 From: Nuno Campos Date: Mon, 4 Nov 2024 11:55:14 -0800 Subject: [PATCH 44/60] Lint --- libs/langgraph/langgraph/types.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libs/langgraph/langgraph/types.py b/libs/langgraph/langgraph/types.py index 605f06f60..1ed6e43ad 100644 --- a/libs/langgraph/langgraph/types.py +++ b/libs/langgraph/langgraph/types.py @@ -238,7 +238,7 @@ class Control(Generic[N]): self.trigger = trigger self.send = send - def __repr__(self): + def __repr__(self) -> str: contents = ", ".join( f"{key}={value!r}" for key, value in self.__dict__.items() if value ) From 8138c88b41f2823f582371f55cdf1476cb760253 Mon Sep 17 00:00:00 2001 From: Nuno Campos Date: Mon, 4 Nov 2024 11:56:31 -0800 Subject: [PATCH 45/60] Add async versions --- libs/langgraph/tests/test_pregel_async.py | 97 +++++++++++++++++++++++ 1 file changed, 97 insertions(+) diff --git a/libs/langgraph/tests/test_pregel_async.py b/libs/langgraph/tests/test_pregel_async.py index 25072eeaf..d8299c930 100644 --- a/libs/langgraph/tests/test_pregel_async.py +++ b/libs/langgraph/tests/test_pregel_async.py @@ -1962,6 +1962,103 @@ async def test_cond_edge_after_send() -> None: assert await graph.ainvoke(["0"]) == ["0", "1", "2", "2", "3"] +async def test_concurrent_emit_sends() -> None: + class Node: + def __init__(self, name: str): + self.name = name + setattr(self, "__name__", name) + + async def __call__(self, state): + return ( + [self.name] + if isinstance(state, list) + else ["|".join((self.name, str(state)))] + ) + + async def send_for_fun(state): + return [Send("2", 1), Send("2", 2), "3.1"] + + async def send_for_profit(state): + return [Send("2", 3), Send("2", 4)] + + async def route_to_three(state) -> Literal["3"]: + return "3" + + builder = StateGraph(Annotated[list, operator.add]) + builder.add_node(Node("1")) + builder.add_node(Node("1.1")) + builder.add_node(Node("2")) + builder.add_node(Node("3")) + builder.add_node(Node("3.1")) + builder.add_edge(START, "1") + builder.add_edge(START, "1.1") + builder.add_conditional_edges("1", send_for_fun) + builder.add_conditional_edges("1.1", send_for_profit) + builder.add_conditional_edges("2", route_to_three) + graph = builder.compile() + assert await graph.ainvoke(["0"]) == [ + "0", + "1", + "1.1", + "2|1", + "2|2", + "2|3", + "2|4", + "3.1", + "3", + ] + + +async def test_send_sequences() -> None: + class Node: + def __init__(self, name: str): + self.name = name + setattr(self, "__name__", name) + + async def __call__(self, state): + update = ( + [self.name] + if isinstance(state, list) # or isinstance(state, Control) + else ["|".join((self.name, str(state)))] + ) + if isinstance(state, Control): + state.update_state = update + return state + else: + return update + + async def send_for_fun(state): + return [ + Send("2", Control(send=Send("2", 3))), + Send("2", Control(send=Send("2", 4))), + "3.1", + ] + + async def route_to_three(state) -> Literal["3"]: + return "3" + + builder = StateGraph(Annotated[list, operator.add]) + builder.add_node(Node("1")) + builder.add_node(Node("2")) + builder.add_node(Node("3")) + builder.add_node(Node("3.1")) + builder.add_edge(START, "1") + builder.add_conditional_edges("1", send_for_fun) + builder.add_conditional_edges("2", route_to_three) + graph = builder.compile() + assert await graph.ainvoke(["0"]) == [ + "0", + "1", + "2|Control(send=Send(node='2', arg=3))", + "2|Control(send=Send(node='2', arg=4))", + "3.1", + "2|3", + "2|4", + "3", + "3", + ] + + @pytest.mark.parametrize("checkpointer_name", ALL_CHECKPOINTERS_ASYNC) async def test_max_concurrency(checkpointer_name: str) -> None: class Node: From f9409022ed968293f4354c2726a8e00653721886 Mon Sep 17 00:00:00 2001 From: Nuno Campos Date: Mon, 4 Nov 2024 15:10:12 -0800 Subject: [PATCH 46/60] lib: In calls to get_state apply pending writes - When calling get_state without a checkpoint id (ie to get the latest state) apply any pending writes for current checkpoint --- libs/langgraph/langgraph/pregel/__init__.py | 35 ++++++++++++++++++--- libs/langgraph/tests/test_pregel.py | 7 ++++- libs/langgraph/tests/test_pregel_async.py | 10 ++++-- 3 files changed, 45 insertions(+), 7 deletions(-) diff --git a/libs/langgraph/langgraph/pregel/__init__.py b/libs/langgraph/langgraph/pregel/__init__.py index 2525081d1..d146a9529 100644 --- a/libs/langgraph/langgraph/pregel/__init__.py +++ b/libs/langgraph/langgraph/pregel/__init__.py @@ -54,6 +54,7 @@ from langgraph.checkpoint.base import ( ) from langgraph.constants import ( CONF, + CONFIG_KEY_CHECKPOINT_ID, CONFIG_KEY_CHECKPOINT_NS, CONFIG_KEY_CHECKPOINTER, CONFIG_KEY_NODE_FINISHED, @@ -439,6 +440,7 @@ class Pregel(PregelProtocol): config: RunnableConfig, saved: Optional[CheckpointTuple], recurse: Optional[BaseCheckpointSaver] = None, + apply_pending_writes: bool = False, ) -> StateSnapshot: if not saved: return StateSnapshot( @@ -469,7 +471,10 @@ class Pregel(PregelProtocol): managed, saved.config, saved.metadata.get("step", -1) + 1, - for_execution=False, + for_execution=True, + store=self.store, + checkpointer=self.checkpointer or None, + manager=None, ) # get the subgraphs subgraphs = dict(self.get_subgraphs()) @@ -503,6 +508,12 @@ class Pregel(PregelProtocol): task_states[task.id] = subgraphs[task.name].get_state( config, subgraphs=True ) + # apply pending writes + if apply_pending_writes and saved.pending_writes: + for tid, *t in saved.pending_writes: + next_tasks[tid].writes.append(t) + if tasks := [t for t in next_tasks.values() if t.writes]: + apply_writes(saved.checkpoint, channels, tasks, None) # assemble the state snapshot return StateSnapshot( read_channels(channels, self.stream_channels_asis), @@ -524,6 +535,7 @@ class Pregel(PregelProtocol): config: RunnableConfig, saved: Optional[CheckpointTuple], recurse: Optional[BaseCheckpointSaver] = None, + apply_pending_writes: bool = False, ) -> StateSnapshot: if not saved: return StateSnapshot( @@ -557,7 +569,10 @@ class Pregel(PregelProtocol): managed, saved.config, saved.metadata.get("step", -1) + 1, - for_execution=False, + for_execution=True, + store=self.store, + checkpointer=self.checkpointer or None, + manager=None, ) # get the subgraphs subgraphs = {n: g async for n, g in self.aget_subgraphs()} @@ -591,6 +606,12 @@ class Pregel(PregelProtocol): task_states[task.id] = await subgraphs[task.name].aget_state( config, subgraphs=True ) + # apply pending writes + if apply_pending_writes and saved.pending_writes: + for tid, *t in saved.pending_writes: + next_tasks[tid].writes.append(t) + if tasks := [t for t in next_tasks.values() if t.writes]: + apply_writes(saved.checkpoint, channels, tasks, None) # assemble the state snapshot return StateSnapshot( read_channels(channels, self.stream_channels_asis), @@ -638,7 +659,10 @@ class Pregel(PregelProtocol): config = merge_configs(self.config, config) if self.config else config saved = checkpointer.get_tuple(config) return self._prepare_state_snapshot( - config, saved, recurse=checkpointer if subgraphs else None + config, + saved, + recurse=checkpointer if subgraphs else None, + apply_pending_writes=CONFIG_KEY_CHECKPOINT_ID not in config[CONF], ) async def aget_state( @@ -672,7 +696,10 @@ class Pregel(PregelProtocol): config = merge_configs(self.config, config) if self.config else config saved = await checkpointer.aget_tuple(config) return await self._aprepare_state_snapshot( - config, saved, recurse=checkpointer if subgraphs else None + config, + saved, + recurse=checkpointer if subgraphs else None, + apply_pending_writes=CONFIG_KEY_CHECKPOINT_ID not in config[CONF], ) def get_state_history( diff --git a/libs/langgraph/tests/test_pregel.py b/libs/langgraph/tests/test_pregel.py index 8536a08c3..a8a614408 100644 --- a/libs/langgraph/tests/test_pregel.py +++ b/libs/langgraph/tests/test_pregel.py @@ -1513,9 +1513,10 @@ def test_pending_writes_resume( assert two.calls == 2 # two attempts # latest checkpoint should be before nodes "one", "two" + # but we should have applied the write from "one" state = graph.get_state(thread1) assert state is not None - assert state.values == {"value": 1} + assert state.values == {"value": 3} assert state.next == ("one", "two") assert state.tasks == ( PregelTask(AnyStr(), "one", (PULL, "one"), result={"value": 2}), @@ -1528,6 +1529,10 @@ def test_pending_writes_resume( "writes": None, "thread_id": "1", } + # get_state with checkpoint_id should not apply any pending writes + state = graph.get_state(state.config) + assert state is not None + assert state.values == {"value": 1} # should contain pending write of "one" checkpoint = checkpointer.get_tuple(thread1) assert checkpoint is not None diff --git a/libs/langgraph/tests/test_pregel_async.py b/libs/langgraph/tests/test_pregel_async.py index d8299c930..ec4179c71 100644 --- a/libs/langgraph/tests/test_pregel_async.py +++ b/libs/langgraph/tests/test_pregel_async.py @@ -482,10 +482,11 @@ async def test_cancel_graph_astream(checkpointer_name: str) -> None: assert awhile.started is False # checkpoint with output of "alittlewhile" should not be saved + # but we should have applied pending writes if checkpointer is not None: state = await graph.aget_state(thread1) assert state is not None - assert state.values == {"value": 1} + assert state.values == {"value": 3} # 1 + 2 assert state.next == ( "aparallelwhile", "alittlewhile", @@ -1722,9 +1723,10 @@ async def test_pending_writes_resume( assert two.calls == 2 # latest checkpoint should be before nodes "one", "two" + # but we should have applied pending writes from "one" state = await graph.aget_state(thread1) assert state is not None - assert state.values == {"value": 1} + assert state.values == {"value": 3} assert state.next == ("one", "two") assert state.tasks == ( PregelTask(AnyStr(), "one", (PULL, "one"), result={"value": 2}), @@ -1742,6 +1744,10 @@ async def test_pending_writes_resume( "writes": None, "thread_id": "1", } + # get_state with checkpoint_id should not apply any pending writes + state = await graph.aget_state(state.config) + assert state is not None + assert state.values == {"value": 1} # should contain pending write of "one" checkpoint = await checkpointer.aget_tuple(thread1) assert checkpoint is not None From d28734f2875e7aee34f777fc16811c0ab9eddbc9 Mon Sep 17 00:00:00 2001 From: Nuno Campos Date: Mon, 4 Nov 2024 15:12:34 -0800 Subject: [PATCH 47/60] Lint --- libs/langgraph/langgraph/pregel/__init__.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/libs/langgraph/langgraph/pregel/__init__.py b/libs/langgraph/langgraph/pregel/__init__.py index d146a9529..9aa3bc915 100644 --- a/libs/langgraph/langgraph/pregel/__init__.py +++ b/libs/langgraph/langgraph/pregel/__init__.py @@ -511,7 +511,7 @@ class Pregel(PregelProtocol): # apply pending writes if apply_pending_writes and saved.pending_writes: for tid, *t in saved.pending_writes: - next_tasks[tid].writes.append(t) + next_tasks[tid].writes.append(t) # type: ignore[arg-type] if tasks := [t for t in next_tasks.values() if t.writes]: apply_writes(saved.checkpoint, channels, tasks, None) # assemble the state snapshot @@ -609,7 +609,7 @@ class Pregel(PregelProtocol): # apply pending writes if apply_pending_writes and saved.pending_writes: for tid, *t in saved.pending_writes: - next_tasks[tid].writes.append(t) + next_tasks[tid].writes.append(t) # type: ignore[arg-type] if tasks := [t for t in next_tasks.values() if t.writes]: apply_writes(saved.checkpoint, channels, tasks, None) # assemble the state snapshot From de3b654735104e460320afabf0d49f20c8d6d8f7 Mon Sep 17 00:00:00 2001 From: Nuno Campos Date: Mon, 4 Nov 2024 15:14:29 -0800 Subject: [PATCH 48/60] Add one more assertion --- libs/langgraph/tests/test_pregel_async.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/libs/langgraph/tests/test_pregel_async.py b/libs/langgraph/tests/test_pregel_async.py index ec4179c71..b9d7936db 100644 --- a/libs/langgraph/tests/test_pregel_async.py +++ b/libs/langgraph/tests/test_pregel_async.py @@ -2123,6 +2123,8 @@ async def test_max_concurrency(checkpointer_name: str) -> None: thread1 = {"max_concurrency": 10, "configurable": {"thread_id": "1"}} assert await graph.ainvoke(["0"], thread1) == ["0", "1"] + state = await graph.aget_state(thread1) + assert state.values == ["0", "1"] assert await graph.ainvoke(None, thread1) == ["0", "1", *range(100), "3"] From ae282e3ae10327307c889ddd2061a0400fb4d5e8 Mon Sep 17 00:00:00 2001 From: Nuno Campos Date: Mon, 4 Nov 2024 15:41:02 -0800 Subject: [PATCH 49/60] lib: Add test for react architecture using Send + interrupt_before - Both for cond edge and edgeless graphs --- libs/langgraph/tests/test_pregel.py | 502 +++++++++++++++++++++- libs/langgraph/tests/test_pregel_async.py | 496 ++++++++++++++++++++- 2 files changed, 996 insertions(+), 2 deletions(-) diff --git a/libs/langgraph/tests/test_pregel.py b/libs/langgraph/tests/test_pregel.py index a8a614408..c9de54ec8 100644 --- a/libs/langgraph/tests/test_pregel.py +++ b/libs/langgraph/tests/test_pregel.py @@ -58,7 +58,7 @@ from langgraph.constants import CONFIG_KEY_NODE_FINISHED, ERROR, PULL, PUSH from langgraph.errors import InvalidUpdateError, MultipleSubgraphsError, NodeInterrupt from langgraph.graph import END, Graph from langgraph.graph.graph import START -from langgraph.graph.message import MessageGraph, add_messages +from langgraph.graph.message import MessageGraph, MessagesState, add_messages from langgraph.graph.state import StateGraph from langgraph.managed.shared_value import SharedValue from langgraph.prebuilt.chat_agent_executor import ( @@ -1845,6 +1845,506 @@ def test_send_sequences() -> None: ] +@pytest.mark.parametrize("checkpointer_name", ALL_CHECKPOINTERS_SYNC) +def test_send_react_interrupt( + request: pytest.FixtureRequest, checkpointer_name: str +) -> None: + from langchain_core.messages import AIMessage, HumanMessage, ToolCall, ToolMessage + + checkpointer = request.getfixturevalue(f"checkpointer_{checkpointer_name}") + + ai_message = AIMessage( + "", + id="ai1", + tool_calls=[ToolCall(name="foo", args={"hi": [1, 2, 3]}, id=AnyStr())], + ) + + def agent(state): + return {"messages": ai_message} + + def route(state): + if isinstance(state["messages"][-1], AIMessage): + return [ + Send(call["name"], call) for call in state["messages"][-1].tool_calls + ] + + foo_called = 0 + + def foo(call: ToolCall): + nonlocal foo_called + foo_called += 1 + return {"messages": ToolMessage(str(call["args"]), tool_call_id=call["id"])} + + builder = StateGraph(MessagesState) + builder.add_node(agent) + builder.add_node(foo) + builder.add_edge(START, "agent") + builder.add_conditional_edges("agent", route) + graph = builder.compile() + + assert graph.invoke({"messages": [HumanMessage("hello")]}) == { + "messages": [ + _AnyIdHumanMessage(content="hello"), + _AnyIdAIMessage( + content="", + tool_calls=[ + { + "name": "foo", + "args": {"hi": [1, 2, 3]}, + "id": "", + "type": "tool_call", + } + ], + ), + _AnyIdToolMessage( + content="{'hi': [1, 2, 3]}", + tool_call_id=AnyStr(), + ), + ] + } + assert foo_called == 1 + + # simple interrupt-resume flow + foo_called = 0 + graph = builder.compile(checkpointer=checkpointer, interrupt_before=["foo"]) + thread1 = {"configurable": {"thread_id": "1"}} + assert graph.invoke({"messages": [HumanMessage("hello")]}, thread1) == { + "messages": [ + _AnyIdHumanMessage(content="hello"), + _AnyIdAIMessage( + content="", + tool_calls=[ + { + "name": "foo", + "args": {"hi": [1, 2, 3]}, + "id": "", + "type": "tool_call", + } + ], + ), + ] + } + assert foo_called == 0 + assert graph.invoke(None, thread1) == { + "messages": [ + _AnyIdHumanMessage(content="hello"), + _AnyIdAIMessage( + content="", + tool_calls=[ + { + "name": "foo", + "args": {"hi": [1, 2, 3]}, + "id": "", + "type": "tool_call", + } + ], + ), + _AnyIdToolMessage( + content="{'hi': [1, 2, 3]}", + tool_call_id=AnyStr(), + ), + ] + } + assert foo_called == 1 + + # interrupt-update-resume flow + foo_called = 0 + graph = builder.compile(checkpointer=checkpointer, interrupt_before=["foo"]) + thread1 = {"configurable": {"thread_id": "2"}} + assert graph.invoke({"messages": [HumanMessage("hello")]}, thread1) == { + "messages": [ + _AnyIdHumanMessage(content="hello"), + _AnyIdAIMessage( + content="", + tool_calls=[ + { + "name": "foo", + "args": {"hi": [1, 2, 3]}, + "id": "", + "type": "tool_call", + } + ], + ), + ] + } + assert foo_called == 0 + + # get state should show the pending task + state = graph.get_state(thread1) + assert state == StateSnapshot( + values={ + "messages": [ + _AnyIdHumanMessage(content="hello"), + _AnyIdAIMessage( + content="", + tool_calls=[ + { + "name": "foo", + "args": {"hi": [1, 2, 3]}, + "id": "", + "type": "tool_call", + } + ], + ), + ] + }, + next=("foo",), + config={ + "configurable": { + "thread_id": "2", + "checkpoint_ns": "", + "checkpoint_id": AnyStr(), + } + }, + metadata={ + "step": 1, + "source": "loop", + "writes": { + "agent": { + "messages": _AnyIdAIMessage( + content="", + tool_calls=[ + { + "name": "foo", + "args": {"hi": [1, 2, 3]}, + "id": "", + "type": "tool_call", + } + ], + ) + } + }, + "parents": {}, + "thread_id": "2", + }, + created_at=AnyStr(), + parent_config={ + "configurable": { + "thread_id": "2", + "checkpoint_ns": "", + "checkpoint_id": AnyStr(), + } + }, + tasks=( + PregelTask( + id=AnyStr(), + name="foo", + path=("__pregel_push", 0), + error=None, + interrupts=(), + state=None, + result=None, + ), + ), + ) + + # remove the tool call, clearing the pending task + graph.update_state( + thread1, {"messages": AIMessage("Bye now", id=ai_message.id, tool_calls=[])} + ) + + # tool call no longer in pending tasks + assert graph.get_state(thread1) == StateSnapshot( + values={ + "messages": [ + _AnyIdHumanMessage(content="hello"), + _AnyIdAIMessage( + content="Bye now", + tool_calls=[], + ), + ] + }, + next=(), + config={ + "configurable": { + "thread_id": "2", + "checkpoint_ns": "", + "checkpoint_id": AnyStr(), + } + }, + metadata={ + "step": 2, + "source": "update", + "writes": { + "agent": { + "messages": _AnyIdAIMessage( + content="Bye now", + tool_calls=[], + ) + } + }, + "parents": {}, + "thread_id": "2", + }, + created_at=AnyStr(), + parent_config={ + "configurable": { + "thread_id": "2", + "checkpoint_ns": "", + "checkpoint_id": AnyStr(), + } + }, + tasks=(), + ) + + # tool call not executed + assert graph.invoke(None, thread1) == { + "messages": [ + _AnyIdHumanMessage(content="hello"), + _AnyIdAIMessage(content="Bye now"), + ] + } + assert foo_called == 0 + + +@pytest.mark.parametrize("checkpointer_name", ALL_CHECKPOINTERS_SYNC) +def test_send_react_interrupt_control( + request: pytest.FixtureRequest, checkpointer_name: str +) -> None: + from langchain_core.messages import AIMessage, HumanMessage, ToolCall, ToolMessage + + checkpointer = request.getfixturevalue(f"checkpointer_{checkpointer_name}") + + ai_message = AIMessage( + "", + id="ai1", + tool_calls=[ToolCall(name="foo", args={"hi": [1, 2, 3]}, id=AnyStr())], + ) + + def agent(state) -> Control[Literal["foo"]]: + return Control( + update_state={"messages": ai_message}, + send=[Send(call["name"], call) for call in ai_message.tool_calls], + ) + + foo_called = 0 + + def foo(call: ToolCall): + nonlocal foo_called + foo_called += 1 + return {"messages": ToolMessage(str(call["args"]), tool_call_id=call["id"])} + + builder = StateGraph(MessagesState) + builder.add_node(agent) + builder.add_node(foo) + builder.add_edge(START, "agent") + graph = builder.compile() + + assert graph.invoke({"messages": [HumanMessage("hello")]}) == { + "messages": [ + _AnyIdHumanMessage(content="hello"), + _AnyIdAIMessage( + content="", + tool_calls=[ + { + "name": "foo", + "args": {"hi": [1, 2, 3]}, + "id": "", + "type": "tool_call", + } + ], + ), + _AnyIdToolMessage( + content="{'hi': [1, 2, 3]}", + tool_call_id=AnyStr(), + ), + ] + } + assert foo_called == 1 + + # simple interrupt-resume flow + foo_called = 0 + graph = builder.compile(checkpointer=checkpointer, interrupt_before=["foo"]) + thread1 = {"configurable": {"thread_id": "1"}} + assert graph.invoke({"messages": [HumanMessage("hello")]}, thread1) == { + "messages": [ + _AnyIdHumanMessage(content="hello"), + _AnyIdAIMessage( + content="", + tool_calls=[ + { + "name": "foo", + "args": {"hi": [1, 2, 3]}, + "id": "", + "type": "tool_call", + } + ], + ), + ] + } + assert foo_called == 0 + assert graph.invoke(None, thread1) == { + "messages": [ + _AnyIdHumanMessage(content="hello"), + _AnyIdAIMessage( + content="", + tool_calls=[ + { + "name": "foo", + "args": {"hi": [1, 2, 3]}, + "id": "", + "type": "tool_call", + } + ], + ), + _AnyIdToolMessage( + content="{'hi': [1, 2, 3]}", + tool_call_id=AnyStr(), + ), + ] + } + assert foo_called == 1 + + # interrupt-update-resume flow + foo_called = 0 + graph = builder.compile(checkpointer=checkpointer, interrupt_before=["foo"]) + thread1 = {"configurable": {"thread_id": "2"}} + assert graph.invoke({"messages": [HumanMessage("hello")]}, thread1) == { + "messages": [ + _AnyIdHumanMessage(content="hello"), + _AnyIdAIMessage( + content="", + tool_calls=[ + { + "name": "foo", + "args": {"hi": [1, 2, 3]}, + "id": "", + "type": "tool_call", + } + ], + ), + ] + } + assert foo_called == 0 + + # get state should show the pending task + state = graph.get_state(thread1) + assert state == StateSnapshot( + values={ + "messages": [ + _AnyIdHumanMessage(content="hello"), + _AnyIdAIMessage( + content="", + tool_calls=[ + { + "name": "foo", + "args": {"hi": [1, 2, 3]}, + "id": "", + "type": "tool_call", + } + ], + ), + ] + }, + next=("foo",), + config={ + "configurable": { + "thread_id": "2", + "checkpoint_ns": "", + "checkpoint_id": AnyStr(), + } + }, + metadata={ + "step": 1, + "source": "loop", + "writes": { + "agent": { + "messages": _AnyIdAIMessage( + content="", + tool_calls=[ + { + "name": "foo", + "args": {"hi": [1, 2, 3]}, + "id": "", + "type": "tool_call", + } + ], + ) + } + }, + "parents": {}, + "thread_id": "2", + }, + created_at=AnyStr(), + parent_config={ + "configurable": { + "thread_id": "2", + "checkpoint_ns": "", + "checkpoint_id": AnyStr(), + } + }, + tasks=( + PregelTask( + id=AnyStr(), + name="foo", + path=("__pregel_push", 0), + error=None, + interrupts=(), + state=None, + result=None, + ), + ), + ) + + # remove the tool call, clearing the pending task + graph.update_state( + thread1, {"messages": AIMessage("Bye now", id=ai_message.id, tool_calls=[])} + ) + + # tool call no longer in pending tasks + assert graph.get_state(thread1) == StateSnapshot( + values={ + "messages": [ + _AnyIdHumanMessage(content="hello"), + _AnyIdAIMessage( + content="Bye now", + tool_calls=[], + ), + ] + }, + next=(), + config={ + "configurable": { + "thread_id": "2", + "checkpoint_ns": "", + "checkpoint_id": AnyStr(), + } + }, + metadata={ + "step": 2, + "source": "update", + "writes": { + "agent": { + "messages": _AnyIdAIMessage( + content="Bye now", + tool_calls=[], + ) + } + }, + "parents": {}, + "thread_id": "2", + }, + created_at=AnyStr(), + parent_config={ + "configurable": { + "thread_id": "2", + "checkpoint_ns": "", + "checkpoint_id": AnyStr(), + } + }, + tasks=(), + ) + + # tool call not executed + assert graph.invoke(None, thread1) == { + "messages": [ + _AnyIdHumanMessage(content="hello"), + _AnyIdAIMessage(content="Bye now"), + ] + } + assert foo_called == 0 + + @pytest.mark.parametrize("checkpointer_name", ALL_CHECKPOINTERS_SYNC) def test_invoke_checkpoint_three( mocker: MockerFixture, request: pytest.FixtureRequest, checkpointer_name: str diff --git a/libs/langgraph/tests/test_pregel_async.py b/libs/langgraph/tests/test_pregel_async.py index b9d7936db..1235d5cc0 100644 --- a/libs/langgraph/tests/test_pregel_async.py +++ b/libs/langgraph/tests/test_pregel_async.py @@ -55,7 +55,7 @@ from langgraph.constants import CONFIG_KEY_NODE_FINISHED, ERROR, PULL, PUSH from langgraph.errors import InvalidUpdateError, MultipleSubgraphsError, NodeInterrupt from langgraph.graph import END, Graph, StateGraph from langgraph.graph.graph import START -from langgraph.graph.message import MessageGraph, add_messages +from langgraph.graph.message import MessageGraph, MessagesState, add_messages from langgraph.managed.shared_value import SharedValue from langgraph.prebuilt.chat_agent_executor import create_tool_calling_executor from langgraph.prebuilt.tool_node import ToolNode @@ -2065,6 +2065,500 @@ async def test_send_sequences() -> None: ] +@pytest.mark.parametrize("checkpointer_name", ALL_CHECKPOINTERS_ASYNC) +async def test_send_react_interrupt(checkpointer_name: str) -> None: + from langchain_core.messages import AIMessage, HumanMessage, ToolCall, ToolMessage + + ai_message = AIMessage( + "", + id="ai1", + tool_calls=[ToolCall(name="foo", args={"hi": [1, 2, 3]}, id=AnyStr())], + ) + + async def agent(state): + return {"messages": ai_message} + + def route(state): + if isinstance(state["messages"][-1], AIMessage): + return [ + Send(call["name"], call) for call in state["messages"][-1].tool_calls + ] + + foo_called = 0 + + async def foo(call: ToolCall): + nonlocal foo_called + foo_called += 1 + return {"messages": ToolMessage(str(call["args"]), tool_call_id=call["id"])} + + builder = StateGraph(MessagesState) + builder.add_node(agent) + builder.add_node(foo) + builder.add_edge(START, "agent") + builder.add_conditional_edges("agent", route) + graph = builder.compile() + + assert await graph.ainvoke({"messages": [HumanMessage("hello")]}) == { + "messages": [ + _AnyIdHumanMessage(content="hello"), + _AnyIdAIMessage( + content="", + tool_calls=[ + { + "name": "foo", + "args": {"hi": [1, 2, 3]}, + "id": "", + "type": "tool_call", + } + ], + ), + _AnyIdToolMessage( + content="{'hi': [1, 2, 3]}", + tool_call_id=AnyStr(), + ), + ] + } + assert foo_called == 1 + + async with awith_checkpointer(checkpointer_name) as checkpointer: + # simple interrupt-resume flow + foo_called = 0 + graph = builder.compile(checkpointer=checkpointer, interrupt_before=["foo"]) + thread1 = {"configurable": {"thread_id": "1"}} + assert await graph.ainvoke({"messages": [HumanMessage("hello")]}, thread1) == { + "messages": [ + _AnyIdHumanMessage(content="hello"), + _AnyIdAIMessage( + content="", + tool_calls=[ + { + "name": "foo", + "args": {"hi": [1, 2, 3]}, + "id": "", + "type": "tool_call", + } + ], + ), + ] + } + assert foo_called == 0 + assert await graph.ainvoke(None, thread1) == { + "messages": [ + _AnyIdHumanMessage(content="hello"), + _AnyIdAIMessage( + content="", + tool_calls=[ + { + "name": "foo", + "args": {"hi": [1, 2, 3]}, + "id": "", + "type": "tool_call", + } + ], + ), + _AnyIdToolMessage( + content="{'hi': [1, 2, 3]}", + tool_call_id=AnyStr(), + ), + ] + } + assert foo_called == 1 + + # interrupt-update-resume flow + foo_called = 0 + graph = builder.compile(checkpointer=checkpointer, interrupt_before=["foo"]) + thread1 = {"configurable": {"thread_id": "2"}} + assert await graph.ainvoke({"messages": [HumanMessage("hello")]}, thread1) == { + "messages": [ + _AnyIdHumanMessage(content="hello"), + _AnyIdAIMessage( + content="", + tool_calls=[ + { + "name": "foo", + "args": {"hi": [1, 2, 3]}, + "id": "", + "type": "tool_call", + } + ], + ), + ] + } + assert foo_called == 0 + + # get state should show the pending task + state = await graph.aget_state(thread1) + assert state == StateSnapshot( + values={ + "messages": [ + _AnyIdHumanMessage(content="hello"), + _AnyIdAIMessage( + content="", + tool_calls=[ + { + "name": "foo", + "args": {"hi": [1, 2, 3]}, + "id": "", + "type": "tool_call", + } + ], + ), + ] + }, + next=("foo",), + config={ + "configurable": { + "thread_id": "2", + "checkpoint_ns": "", + "checkpoint_id": AnyStr(), + } + }, + metadata={ + "step": 1, + "source": "loop", + "writes": { + "agent": { + "messages": _AnyIdAIMessage( + content="", + tool_calls=[ + { + "name": "foo", + "args": {"hi": [1, 2, 3]}, + "id": "", + "type": "tool_call", + } + ], + ) + } + }, + "parents": {}, + "thread_id": "2", + }, + created_at=AnyStr(), + parent_config={ + "configurable": { + "thread_id": "2", + "checkpoint_ns": "", + "checkpoint_id": AnyStr(), + } + }, + tasks=( + PregelTask( + id=AnyStr(), + name="foo", + path=("__pregel_push", 0), + error=None, + interrupts=(), + state=None, + result=None, + ), + ), + ) + + # remove the tool call, clearing the pending task + await graph.aupdate_state( + thread1, {"messages": AIMessage("Bye now", id=ai_message.id, tool_calls=[])} + ) + + # tool call no longer in pending tasks + assert await graph.aget_state(thread1) == StateSnapshot( + values={ + "messages": [ + _AnyIdHumanMessage(content="hello"), + _AnyIdAIMessage( + content="Bye now", + tool_calls=[], + ), + ] + }, + next=(), + config={ + "configurable": { + "thread_id": "2", + "checkpoint_ns": "", + "checkpoint_id": AnyStr(), + } + }, + metadata={ + "step": 2, + "source": "update", + "writes": { + "agent": { + "messages": _AnyIdAIMessage( + content="Bye now", + tool_calls=[], + ) + } + }, + "parents": {}, + "thread_id": "2", + }, + created_at=AnyStr(), + parent_config={ + "configurable": { + "thread_id": "2", + "checkpoint_ns": "", + "checkpoint_id": AnyStr(), + } + }, + tasks=(), + ) + + # tool call not executed + assert await graph.ainvoke(None, thread1) == { + "messages": [ + _AnyIdHumanMessage(content="hello"), + _AnyIdAIMessage(content="Bye now"), + ] + } + assert foo_called == 0 + + +@pytest.mark.parametrize("checkpointer_name", ALL_CHECKPOINTERS_ASYNC) +async def test_send_react_interrupt_control(checkpointer_name: str) -> None: + from langchain_core.messages import AIMessage, HumanMessage, ToolCall, ToolMessage + + ai_message = AIMessage( + "", + id="ai1", + tool_calls=[ToolCall(name="foo", args={"hi": [1, 2, 3]}, id=AnyStr())], + ) + + async def agent(state) -> Control[Literal["foo"]]: + return Control( + update_state={"messages": ai_message}, + send=[Send(call["name"], call) for call in ai_message.tool_calls], + ) + + foo_called = 0 + + async def foo(call: ToolCall): + nonlocal foo_called + foo_called += 1 + return {"messages": ToolMessage(str(call["args"]), tool_call_id=call["id"])} + + builder = StateGraph(MessagesState) + builder.add_node(agent) + builder.add_node(foo) + builder.add_edge(START, "agent") + graph = builder.compile() + + assert await graph.ainvoke({"messages": [HumanMessage("hello")]}) == { + "messages": [ + _AnyIdHumanMessage(content="hello"), + _AnyIdAIMessage( + content="", + tool_calls=[ + { + "name": "foo", + "args": {"hi": [1, 2, 3]}, + "id": "", + "type": "tool_call", + } + ], + ), + _AnyIdToolMessage( + content="{'hi': [1, 2, 3]}", + tool_call_id=AnyStr(), + ), + ] + } + assert foo_called == 1 + + async with awith_checkpointer(checkpointer_name) as checkpointer: + # simple interrupt-resume flow + foo_called = 0 + graph = builder.compile(checkpointer=checkpointer, interrupt_before=["foo"]) + thread1 = {"configurable": {"thread_id": "1"}} + assert await graph.ainvoke({"messages": [HumanMessage("hello")]}, thread1) == { + "messages": [ + _AnyIdHumanMessage(content="hello"), + _AnyIdAIMessage( + content="", + tool_calls=[ + { + "name": "foo", + "args": {"hi": [1, 2, 3]}, + "id": "", + "type": "tool_call", + } + ], + ), + ] + } + assert foo_called == 0 + assert await graph.ainvoke(None, thread1) == { + "messages": [ + _AnyIdHumanMessage(content="hello"), + _AnyIdAIMessage( + content="", + tool_calls=[ + { + "name": "foo", + "args": {"hi": [1, 2, 3]}, + "id": "", + "type": "tool_call", + } + ], + ), + _AnyIdToolMessage( + content="{'hi': [1, 2, 3]}", + tool_call_id=AnyStr(), + ), + ] + } + assert foo_called == 1 + + # interrupt-update-resume flow + foo_called = 0 + graph = builder.compile(checkpointer=checkpointer, interrupt_before=["foo"]) + thread1 = {"configurable": {"thread_id": "2"}} + assert await graph.ainvoke({"messages": [HumanMessage("hello")]}, thread1) == { + "messages": [ + _AnyIdHumanMessage(content="hello"), + _AnyIdAIMessage( + content="", + tool_calls=[ + { + "name": "foo", + "args": {"hi": [1, 2, 3]}, + "id": "", + "type": "tool_call", + } + ], + ), + ] + } + assert foo_called == 0 + + # get state should show the pending task + state = await graph.aget_state(thread1) + assert state == StateSnapshot( + values={ + "messages": [ + _AnyIdHumanMessage(content="hello"), + _AnyIdAIMessage( + content="", + tool_calls=[ + { + "name": "foo", + "args": {"hi": [1, 2, 3]}, + "id": "", + "type": "tool_call", + } + ], + ), + ] + }, + next=("foo",), + config={ + "configurable": { + "thread_id": "2", + "checkpoint_ns": "", + "checkpoint_id": AnyStr(), + } + }, + metadata={ + "step": 1, + "source": "loop", + "writes": { + "agent": { + "messages": _AnyIdAIMessage( + content="", + tool_calls=[ + { + "name": "foo", + "args": {"hi": [1, 2, 3]}, + "id": "", + "type": "tool_call", + } + ], + ) + } + }, + "parents": {}, + "thread_id": "2", + }, + created_at=AnyStr(), + parent_config={ + "configurable": { + "thread_id": "2", + "checkpoint_ns": "", + "checkpoint_id": AnyStr(), + } + }, + tasks=( + PregelTask( + id=AnyStr(), + name="foo", + path=("__pregel_push", 0), + error=None, + interrupts=(), + state=None, + result=None, + ), + ), + ) + + # remove the tool call, clearing the pending task + await graph.aupdate_state( + thread1, {"messages": AIMessage("Bye now", id=ai_message.id, tool_calls=[])} + ) + + # tool call no longer in pending tasks + assert await graph.aget_state(thread1) == StateSnapshot( + values={ + "messages": [ + _AnyIdHumanMessage(content="hello"), + _AnyIdAIMessage( + content="Bye now", + tool_calls=[], + ), + ] + }, + next=(), + config={ + "configurable": { + "thread_id": "2", + "checkpoint_ns": "", + "checkpoint_id": AnyStr(), + } + }, + metadata={ + "step": 2, + "source": "update", + "writes": { + "agent": { + "messages": _AnyIdAIMessage( + content="Bye now", + tool_calls=[], + ) + } + }, + "parents": {}, + "thread_id": "2", + }, + created_at=AnyStr(), + parent_config={ + "configurable": { + "thread_id": "2", + "checkpoint_ns": "", + "checkpoint_id": AnyStr(), + } + }, + tasks=(), + ) + + # tool call not executed + assert await graph.ainvoke(None, thread1) == { + "messages": [ + _AnyIdHumanMessage(content="hello"), + _AnyIdAIMessage(content="Bye now"), + ] + } + assert foo_called == 0 + + @pytest.mark.parametrize("checkpointer_name", ALL_CHECKPOINTERS_ASYNC) async def test_max_concurrency(checkpointer_name: str) -> None: class Node: From 7ba9a663010d607cef229884f37719c0759065a6 Mon Sep 17 00:00:00 2001 From: William FH <13333726+hinthornw@users.noreply.github.com> Date: Mon, 4 Nov 2024 15:43:00 -0800 Subject: [PATCH 50/60] [Docs] Clarify checkpointer options (#2328) --- docs/docs/concepts/langgraph_cloud.md | 8 +++----- libs/langgraph/langgraph/graph/state.py | 8 +++++--- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/docs/docs/concepts/langgraph_cloud.md b/docs/docs/concepts/langgraph_cloud.md index 11a26c184..24067f054 100644 --- a/docs/docs/concepts/langgraph_cloud.md +++ b/docs/docs/concepts/langgraph_cloud.md @@ -1,12 +1,10 @@ # Cloud SaaS -!!! info "Prerequisites" - - [LangGraph Platform](./langgraph_platform.md) - - [LangGraph Server](./langgraph_server.md) +!!! info "Prerequisites" - [LangGraph Platform](./langgraph_platform.md) - [LangGraph Server](./langgraph_server.md) ## Overview -LangGraph's Cloud SaaS is a managed service that provides a scalable and secure environment for deploying LangGraph APIs. It is designed to work seamlessly with your LangGraph API regardless of how it is defined, what tools it uses, or any dependencies. Cloud SaaS provides a simple way to deploy and manage your LangGraph API in the cloud. +LangGraph's Cloud SaaS is a managed service for deploying LangGraph APIs, regardless of its definition or dependencies. The service offers managed implementations of checkpointers and stores, allowing you to focus on building the right cognitive architecture for your use case. By handling scalable & secure infrastructure, LangGraph Cloud offers the fastest path to getting your LangGraph API deployed to production. ## Deployment @@ -35,4 +33,4 @@ A high-level diagram of a Cloud SaaS deployment. ## Related -- [Deployment Options](./deployment_options.md) \ No newline at end of file +- [Deployment Options](./deployment_options.md) diff --git a/libs/langgraph/langgraph/graph/state.py b/libs/langgraph/langgraph/graph/state.py index fc429cb60..89ee6ecca 100644 --- a/libs/langgraph/langgraph/graph/state.py +++ b/libs/langgraph/langgraph/graph/state.py @@ -430,9 +430,11 @@ class StateGraph(Graph): streamed, batched, and run asynchronously. Args: - checkpointer (Checkpointer): An optional checkpoint saver object. - This serves as a fully versioned "memory" for the graph, allowing - the graph to be paused and resumed, and replayed from any point. + checkpointer (Optional[Union[Checkpointer, Literal[False]]]): A checkpoint saver object or flag. + If provided, this Checkpointer serves as a fully versioned "short-term memory" for the graph, + allowing it to be paused, resumed, and replayed from any point. + If None, it may inherit the parent graph's checkpointer when used as a subgraph. + If False, it will not use or inherit any checkpointer. interrupt_before (Optional[Sequence[str]]): An optional list of node names to interrupt before. interrupt_after (Optional[Sequence[str]]): An optional list of node names to interrupt after. debug (bool): A flag indicating whether to enable debug mode. From b71fd5092b647a2fd4eb61ef2e52073dd719b300 Mon Sep 17 00:00:00 2001 From: Nuno Campos Date: Mon, 4 Nov 2024 16:03:34 -0800 Subject: [PATCH 51/60] lib: When getting latest state, alst make `next` reflect pending writes - ie. tasks already executed should not show up in `next` list --- libs/langgraph/langgraph/pregel/__init__.py | 18 ++++++++++++------ libs/langgraph/tests/test_pregel.py | 3 ++- libs/langgraph/tests/test_pregel_async.py | 8 +++----- 3 files changed, 17 insertions(+), 12 deletions(-) diff --git a/libs/langgraph/langgraph/pregel/__init__.py b/libs/langgraph/langgraph/pregel/__init__.py index 9aa3bc915..0755a4473 100644 --- a/libs/langgraph/langgraph/pregel/__init__.py +++ b/libs/langgraph/langgraph/pregel/__init__.py @@ -65,9 +65,11 @@ from langgraph.constants import ( CONFIG_KEY_STREAM, CONFIG_KEY_STREAM_WRITER, CONFIG_KEY_TASK_ID, + ERROR, INTERRUPT, NS_END, NS_SEP, + SCHEDULED, ) from langgraph.errors import ( ErrorCode, @@ -510,14 +512,16 @@ class Pregel(PregelProtocol): ) # apply pending writes if apply_pending_writes and saved.pending_writes: - for tid, *t in saved.pending_writes: - next_tasks[tid].writes.append(t) # type: ignore[arg-type] + for tid, k, v in saved.pending_writes: + if k in (ERROR, INTERRUPT, SCHEDULED): + continue + next_tasks[tid].writes.append((k, v)) if tasks := [t for t in next_tasks.values() if t.writes]: apply_writes(saved.checkpoint, channels, tasks, None) # assemble the state snapshot return StateSnapshot( read_channels(channels, self.stream_channels_asis), - tuple(t.name for t in next_tasks.values()), + tuple(t.name for t in next_tasks.values() if not t.writes), patch_checkpoint_map(saved.config, saved.metadata), saved.metadata, saved.checkpoint["ts"], @@ -608,14 +612,16 @@ class Pregel(PregelProtocol): ) # apply pending writes if apply_pending_writes and saved.pending_writes: - for tid, *t in saved.pending_writes: - next_tasks[tid].writes.append(t) # type: ignore[arg-type] + for tid, k, v in saved.pending_writes: + if k in (ERROR, INTERRUPT, SCHEDULED): + continue + next_tasks[tid].writes.append((k, v)) if tasks := [t for t in next_tasks.values() if t.writes]: apply_writes(saved.checkpoint, channels, tasks, None) # assemble the state snapshot return StateSnapshot( read_channels(channels, self.stream_channels_asis), - tuple(t.name for t in next_tasks.values()), + tuple(t.name for t in next_tasks.values() if not t.writes), patch_checkpoint_map(saved.config, saved.metadata), saved.metadata, saved.checkpoint["ts"], diff --git a/libs/langgraph/tests/test_pregel.py b/libs/langgraph/tests/test_pregel.py index c9de54ec8..dd3a9f714 100644 --- a/libs/langgraph/tests/test_pregel.py +++ b/libs/langgraph/tests/test_pregel.py @@ -1517,7 +1517,7 @@ def test_pending_writes_resume( state = graph.get_state(thread1) assert state is not None assert state.values == {"value": 3} - assert state.next == ("one", "two") + assert state.next == ("two",) assert state.tasks == ( PregelTask(AnyStr(), "one", (PULL, "one"), result={"value": 2}), PregelTask(AnyStr(), "two", (PULL, "two"), 'ConnectionError("I\'m not good")'), @@ -1533,6 +1533,7 @@ def test_pending_writes_resume( state = graph.get_state(state.config) assert state is not None assert state.values == {"value": 1} + assert state.next == ("one", "two") # should contain pending write of "one" checkpoint = checkpointer.get_tuple(thread1) assert checkpoint is not None diff --git a/libs/langgraph/tests/test_pregel_async.py b/libs/langgraph/tests/test_pregel_async.py index 1235d5cc0..87d2679e3 100644 --- a/libs/langgraph/tests/test_pregel_async.py +++ b/libs/langgraph/tests/test_pregel_async.py @@ -487,10 +487,7 @@ async def test_cancel_graph_astream(checkpointer_name: str) -> None: state = await graph.aget_state(thread1) assert state is not None assert state.values == {"value": 3} # 1 + 2 - assert state.next == ( - "aparallelwhile", - "alittlewhile", - ) + assert state.next == ("aparallelwhile",) assert state.metadata == { "parents": {}, "source": "loop", @@ -1727,7 +1724,7 @@ async def test_pending_writes_resume( state = await graph.aget_state(thread1) assert state is not None assert state.values == {"value": 3} - assert state.next == ("one", "two") + assert state.next == ("two",) assert state.tasks == ( PregelTask(AnyStr(), "one", (PULL, "one"), result={"value": 2}), PregelTask( @@ -1748,6 +1745,7 @@ async def test_pending_writes_resume( state = await graph.aget_state(state.config) assert state is not None assert state.values == {"value": 1} + assert state.next == ("one", "two") # should contain pending write of "one" checkpoint = await checkpointer.aget_tuple(thread1) assert checkpoint is not None From 9a2ba8c8cd6793f005512361318b3f93d42d98c7 Mon Sep 17 00:00:00 2001 From: Nuno Campos Date: Mon, 4 Nov 2024 16:20:21 -0800 Subject: [PATCH 52/60] lib: When updating state from latest, apply pending writes first - This picks a default value for as_node which matches the node which last acted, even if the step didnt finish (due to an interrupt) --- libs/langgraph/langgraph/pregel/__init__.py | 210 ++++++++++++-------- 1 file changed, 126 insertions(+), 84 deletions(-) diff --git a/libs/langgraph/langgraph/pregel/__init__.py b/libs/langgraph/langgraph/pregel/__init__.py index 0755a4473..d9ca7badf 100644 --- a/libs/langgraph/langgraph/pregel/__init__.py +++ b/libs/langgraph/langgraph/pregel/__init__.py @@ -863,55 +863,75 @@ class Pregel(PregelProtocol): if saved: checkpoint_config = patch_configurable(config, saved.config[CONF]) checkpoint_metadata = {**saved.metadata, **checkpoint_metadata} - # find last node that updated the state, if not provided - if values is None and as_node is None: - next_config = checkpointer.put( - checkpoint_config, - create_checkpoint(checkpoint, None, step), - { - **checkpoint_metadata, - "source": "update", - "step": step + 1, - "writes": {}, - "parents": saved.metadata.get("parents", {}) if saved else {}, - }, - {}, - ) - return patch_checkpoint_map(next_config, saved.metadata if saved else None) - elif as_node is None and not any( - v for vv in checkpoint["versions_seen"].values() for v in vv.values() - ): - if ( - isinstance(self.input_channels, str) - and self.input_channels in self.nodes - ): - as_node = self.input_channels - elif as_node is None: - last_seen_by_node = sorted( - (v, n) - for n, seen in checkpoint["versions_seen"].items() - if n in self.nodes - for v in seen.values() - ) - # if two nodes updated the state at the same time, it's ambiguous - if last_seen_by_node: - if len(last_seen_by_node) == 1: - as_node = last_seen_by_node[0][1] - elif last_seen_by_node[-1][0] != last_seen_by_node[-2][0]: - as_node = last_seen_by_node[-1][1] - if as_node is None: - raise InvalidUpdateError("Ambiguous update, specify as_node") - if as_node not in self.nodes: - raise InvalidUpdateError(f"Node {as_node} does not exist") - # update channels with ChannelsManager( self.channels, checkpoint, LoopProtocol(config=config, step=step + 1, stop=step + 2), - ) as ( - channels, - managed, - ): + ) as (channels, managed): + # apply pending writes, if not on specific checkpoint + if CONFIG_KEY_CHECKPOINT_ID not in config[CONF] and saved is not None: + # tasks for this checkpoint + next_tasks = prepare_next_tasks( + checkpoint, + self.nodes, + channels, + managed, + saved.config, + saved.metadata.get("step", -1) + 1, + for_execution=True, + store=self.store, + checkpointer=self.checkpointer or None, + manager=None, + ) + # apply writes + for tid, k, v in saved.pending_writes: + if k in (ERROR, INTERRUPT, SCHEDULED): + continue + 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) + # find last node tha`t updated the state, if not provided + if values is None and as_node is None: + next_config = checkpointer.put( + checkpoint_config, + create_checkpoint(checkpoint, None, step), + { + **checkpoint_metadata, + "source": "update", + "step": step + 1, + "writes": {}, + "parents": saved.metadata.get("parents", {}) if saved else {}, + }, + {}, + ) + return patch_checkpoint_map( + next_config, saved.metadata if saved else None + ) + elif as_node is None and not any( + v for vv in checkpoint["versions_seen"].values() for v in vv.values() + ): + if ( + isinstance(self.input_channels, str) + and self.input_channels in self.nodes + ): + as_node = self.input_channels + elif as_node is None: + last_seen_by_node = sorted( + (v, n) + for n, seen in checkpoint["versions_seen"].items() + if n in self.nodes + for v in seen.values() + ) + # if two nodes updated the state at the same time, it's ambiguous + if last_seen_by_node: + if len(last_seen_by_node) == 1: + as_node = last_seen_by_node[0][1] + elif last_seen_by_node[-1][0] != last_seen_by_node[-2][0]: + as_node = last_seen_by_node[-1][1] + if as_node is None: + raise InvalidUpdateError("Ambiguous update, specify as_node") + if as_node not in self.nodes: + raise InvalidUpdateError(f"Node {as_node} does not exist") # create task to run all writers of the chosen node writers = self.nodes[as_node].flat_writers if not writers: @@ -1019,45 +1039,6 @@ class Pregel(PregelProtocol): if saved: checkpoint_config = patch_configurable(config, saved.config[CONF]) checkpoint_metadata = {**saved.metadata, **checkpoint_metadata} - # find last node that updated the state, if not provided - if values is None and as_node is None: - next_config = await checkpointer.aput( - checkpoint_config, - create_checkpoint(checkpoint, None, step), - { - **checkpoint_metadata, - "source": "update", - "step": step + 1, - "writes": {}, - "parents": saved.metadata.get("parents", {}) if saved else {}, - }, - {}, - ) - return patch_checkpoint_map(next_config, saved.metadata if saved else None) - elif as_node is None and not saved: - if ( - isinstance(self.input_channels, str) - and self.input_channels in self.nodes - ): - as_node = self.input_channels - elif as_node is None: - last_seen_by_node = sorted( - (v, n) - for n, seen in checkpoint["versions_seen"].items() - if n in self.nodes - for v in seen.values() - ) - # if two nodes updated the state at the same time, it's ambiguous - if last_seen_by_node: - if len(last_seen_by_node) == 1: - as_node = last_seen_by_node[0][1] - elif last_seen_by_node[-1][0] != last_seen_by_node[-2][0]: - as_node = last_seen_by_node[-1][1] - if as_node is None: - raise InvalidUpdateError("Ambiguous update, specify as_node") - if as_node not in self.nodes: - raise InvalidUpdateError(f"Node {as_node} does not exist") - # update channels, acting as the chosen node async with AsyncChannelsManager( self.channels, checkpoint, @@ -1066,6 +1047,67 @@ class Pregel(PregelProtocol): channels, managed, ): + # apply pending writes, if not on specific checkpoint + if CONFIG_KEY_CHECKPOINT_ID not in config[CONF] and saved is not None: + # tasks for this checkpoint + next_tasks = prepare_next_tasks( + checkpoint, + self.nodes, + channels, + managed, + saved.config, + saved.metadata.get("step", -1) + 1, + for_execution=True, + store=self.store, + checkpointer=self.checkpointer or None, + manager=None, + ) + for tid, k, v in saved.pending_writes: + if k in (ERROR, INTERRUPT, SCHEDULED): + continue + 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) + # find last node that updated the state, if not provided + if values is None and as_node is None: + next_config = await checkpointer.aput( + checkpoint_config, + create_checkpoint(checkpoint, None, step), + { + **checkpoint_metadata, + "source": "update", + "step": step + 1, + "writes": {}, + "parents": saved.metadata.get("parents", {}) if saved else {}, + }, + {}, + ) + return patch_checkpoint_map( + next_config, saved.metadata if saved else None + ) + elif as_node is None and not saved: + if ( + isinstance(self.input_channels, str) + and self.input_channels in self.nodes + ): + as_node = self.input_channels + elif as_node is None: + last_seen_by_node = sorted( + (v, n) + for n, seen in checkpoint["versions_seen"].items() + if n in self.nodes + for v in seen.values() + ) + # if two nodes updated the state at the same time, it's ambiguous + if last_seen_by_node: + if len(last_seen_by_node) == 1: + as_node = last_seen_by_node[0][1] + elif last_seen_by_node[-1][0] != last_seen_by_node[-2][0]: + as_node = last_seen_by_node[-1][1] + if as_node is None: + raise InvalidUpdateError("Ambiguous update, specify as_node") + if as_node not in self.nodes: + raise InvalidUpdateError(f"Node {as_node} does not exist") # create task to run all writers of the chosen node writers = self.nodes[as_node].flat_writers if not writers: From 971d746061705f58f4d9d86a88c65ae531c48d1a Mon Sep 17 00:00:00 2001 From: Nuno Campos Date: Mon, 4 Nov 2024 16:33:20 -0800 Subject: [PATCH 53/60] Lint --- libs/langgraph/langgraph/pregel/__init__.py | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/libs/langgraph/langgraph/pregel/__init__.py b/libs/langgraph/langgraph/pregel/__init__.py index d9ca7badf..666560024 100644 --- a/libs/langgraph/langgraph/pregel/__init__.py +++ b/libs/langgraph/langgraph/pregel/__init__.py @@ -869,7 +869,11 @@ class Pregel(PregelProtocol): LoopProtocol(config=config, step=step + 1, stop=step + 2), ) as (channels, managed): # apply pending writes, if not on specific checkpoint - if CONFIG_KEY_CHECKPOINT_ID not in config[CONF] and saved is not None: + if ( + CONFIG_KEY_CHECKPOINT_ID not in config[CONF] + and saved is not None + and saved.pending_writes + ): # tasks for this checkpoint next_tasks = prepare_next_tasks( checkpoint, @@ -890,7 +894,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) - # find last node tha`t updated the state, if not provided + # find last node that updated the state, if not provided if values is None and as_node is None: next_config = checkpointer.put( checkpoint_config, @@ -1048,7 +1052,11 @@ class Pregel(PregelProtocol): managed, ): # apply pending writes, if not on specific checkpoint - if CONFIG_KEY_CHECKPOINT_ID not in config[CONF] and saved is not None: + if ( + CONFIG_KEY_CHECKPOINT_ID not in config[CONF] + and saved is not None + and saved.pending_writes + ): # tasks for this checkpoint next_tasks = prepare_next_tasks( checkpoint, From 04657408f8d510e8f406ade299b185ac2e99ff9c Mon Sep 17 00:00:00 2001 From: Nuno Campos Date: Mon, 4 Nov 2024 16:39:04 -0800 Subject: [PATCH 54/60] lib: Enforce write application order in apply_writes - Previously order was enforced in prepare_next_tasks, but that's not a good fit for future features - This changes order between PULL and PUSH tasks, updates from PUSH tasks will now be applied after updates from PULL tasks --- libs/langgraph/langgraph/pregel/__init__.py | 6 ++++-- libs/langgraph/langgraph/pregel/algo.py | 13 +++++++++++-- libs/langgraph/langgraph/pregel/loop.py | 5 ++++- libs/langgraph/tests/test_pregel.py | 6 +++--- libs/langgraph/tests/test_pregel_async.py | 6 +++--- 5 files changed, 25 insertions(+), 11 deletions(-) diff --git a/libs/langgraph/langgraph/pregel/__init__.py b/libs/langgraph/langgraph/pregel/__init__.py index 666560024..750a32fd1 100644 --- a/libs/langgraph/langgraph/pregel/__init__.py +++ b/libs/langgraph/langgraph/pregel/__init__.py @@ -877,6 +877,7 @@ class Pregel(PregelProtocol): # tasks for this checkpoint next_tasks = prepare_next_tasks( checkpoint, + saved.pending_writes or [], self.nodes, channels, managed, @@ -941,7 +942,7 @@ class Pregel(PregelProtocol): if not writers: raise InvalidUpdateError(f"Node {as_node} has no writers") writes: deque[tuple[str, Any]] = deque() - task = PregelTaskWrites(as_node, writes, [INTERRUPT]) + task = PregelTaskWrites((), as_node, writes, [INTERRUPT]) task_id = str(uuid5(UUID(checkpoint["id"]), INTERRUPT)) run = RunnableSequence(*writers) if len(writers) > 1 else writers[0] # execute task @@ -1060,6 +1061,7 @@ class Pregel(PregelProtocol): # tasks for this checkpoint next_tasks = prepare_next_tasks( checkpoint, + saved.pending_writes or [], self.nodes, channels, managed, @@ -1121,7 +1123,7 @@ class Pregel(PregelProtocol): if not writers: raise InvalidUpdateError(f"Node {as_node} has no writers") writes: deque[tuple[str, Any]] = deque() - task = PregelTaskWrites(as_node, writes, [INTERRUPT]) + task = PregelTaskWrites((), as_node, writes, [INTERRUPT]) task_id = str(uuid5(UUID(checkpoint["id"]), INTERRUPT)) run = RunnableSequence(*writers) if len(writers) > 1 else writers[0] # execute task diff --git a/libs/langgraph/langgraph/pregel/algo.py b/libs/langgraph/langgraph/pregel/algo.py index 4a2455aa1..af71294ae 100644 --- a/libs/langgraph/langgraph/pregel/algo.py +++ b/libs/langgraph/langgraph/pregel/algo.py @@ -67,6 +67,9 @@ class WritesProtocol(Protocol): """Protocol for objects containing writes to be applied to checkpoint. Implemented by PregelTaskWrites and PregelExecutableTask.""" + @property + def path(self) -> tuple[Union[str, int], ...]: ... + @property def name(self) -> str: ... @@ -81,6 +84,7 @@ class PregelTaskWrites(NamedTuple): """Simplest implementation of WritesProtocol, for usage with writes that don't originate from a runnable task, eg. graph input, update_state, etc.""" + path: tuple[Union[str, int], ...] name: str writes: Sequence[tuple[str, Any]] triggers: Sequence[str] @@ -190,6 +194,9 @@ def apply_writes( """Apply writes from a set of tasks (usually the tasks from a Pregel step) to the checkpoint and channels, and return managed values writes to be applied externally.""" + # sort tasks on path + tasks = sorted(tasks, key=lambda t: t.path) + # update seen versions for task in tasks: checkpoint["versions_seen"].setdefault(task.name, {}).update( @@ -444,7 +451,9 @@ def prepare_single_task( checkpoint, channels, managed, - PregelTaskWrites(packet.node, writes, triggers), + PregelTaskWrites( + task_path, packet.node, writes, triggers + ), config, ), CONFIG_KEY_STORE: ( @@ -552,7 +561,7 @@ def prepare_single_task( checkpoint, channels, managed, - PregelTaskWrites(name, writes, triggers), + PregelTaskWrites(task_path, name, writes, triggers), config, ), CONFIG_KEY_STORE: ( diff --git a/libs/langgraph/langgraph/pregel/loop.py b/libs/langgraph/langgraph/pregel/loop.py index 14254da75..4cfd550fc 100644 --- a/libs/langgraph/langgraph/pregel/loop.py +++ b/libs/langgraph/langgraph/pregel/loop.py @@ -477,7 +477,10 @@ class PregelLoop(LoopProtocol): mv_writes = apply_writes( self.checkpoint, self.channels, - [*discard_tasks.values(), PregelTaskWrites(INPUT, input_writes, [])], + [ + *discard_tasks.values(), + PregelTaskWrites((), INPUT, input_writes, []), + ], self.checkpointer_get_next_version, ) assert not mv_writes, "Can't write to SharedValues in graph input" diff --git a/libs/langgraph/tests/test_pregel.py b/libs/langgraph/tests/test_pregel.py index dd3a9f714..0ec87b881 100644 --- a/libs/langgraph/tests/test_pregel.py +++ b/libs/langgraph/tests/test_pregel.py @@ -1787,11 +1787,11 @@ def test_concurrent_emit_sends() -> None: "0", "1", "1.1", + "3.1", "2|1", "2|2", "2|3", "2|4", - "3.1", "3", ] @@ -1836,13 +1836,13 @@ def test_send_sequences() -> None: assert graph.invoke(["0"]) == [ "0", "1", + "3.1", "2|Control(send=Send(node='2', arg=3))", "2|Control(send=Send(node='2', arg=4))", - "3.1", + "3", "2|3", "2|4", "3", - "3", ] diff --git a/libs/langgraph/tests/test_pregel_async.py b/libs/langgraph/tests/test_pregel_async.py index 87d2679e3..b73f0a5ee 100644 --- a/libs/langgraph/tests/test_pregel_async.py +++ b/libs/langgraph/tests/test_pregel_async.py @@ -2004,11 +2004,11 @@ async def test_concurrent_emit_sends() -> None: "0", "1", "1.1", + "3.1", "2|1", "2|2", "2|3", "2|4", - "3.1", "3", ] @@ -2053,13 +2053,13 @@ async def test_send_sequences() -> None: assert await graph.ainvoke(["0"]) == [ "0", "1", + "3.1", "2|Control(send=Send(node='2', arg=3))", "2|Control(send=Send(node='2', arg=4))", - "3.1", + "3", "2|3", "2|4", "3", - "3", ] From dec0b7f439e2cb15040a0fe4604a46ffd5c499b1 Mon Sep 17 00:00:00 2001 From: Nuno Campos Date: Mon, 4 Nov 2024 16:43:07 -0800 Subject: [PATCH 55/60] Lint --- libs/langgraph/langgraph/pregel/__init__.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/libs/langgraph/langgraph/pregel/__init__.py b/libs/langgraph/langgraph/pregel/__init__.py index 750a32fd1..d69fbc75f 100644 --- a/libs/langgraph/langgraph/pregel/__init__.py +++ b/libs/langgraph/langgraph/pregel/__init__.py @@ -877,7 +877,6 @@ class Pregel(PregelProtocol): # tasks for this checkpoint next_tasks = prepare_next_tasks( checkpoint, - saved.pending_writes or [], self.nodes, channels, managed, @@ -1061,7 +1060,6 @@ class Pregel(PregelProtocol): # tasks for this checkpoint next_tasks = prepare_next_tasks( checkpoint, - saved.pending_writes or [], self.nodes, channels, managed, From 639501809c5f70eb81d778bc0d35d79335a37a8d Mon Sep 17 00:00:00 2001 From: Nuno Campos Date: Tue, 5 Nov 2024 09:15:46 -0800 Subject: [PATCH 56/60] api: Add one more test case for send + interrupt before - Testing same exact behavior as send + interrupt after --- libs/langgraph/tests/test_pregel.py | 292 +++++++++++++++++++++ libs/langgraph/tests/test_pregel_async.py | 298 ++++++++++++++++++++++ 2 files changed, 590 insertions(+) diff --git a/libs/langgraph/tests/test_pregel.py b/libs/langgraph/tests/test_pregel.py index dd3a9f714..92832c8d9 100644 --- a/libs/langgraph/tests/test_pregel.py +++ b/libs/langgraph/tests/test_pregel.py @@ -5275,6 +5275,8 @@ def test_state_graph_packets( {"agent": {"messages": AIMessage(content="answer", id="ai3")}}, ] + # interrupt after agent + app_w_interrupt = workflow.compile( checkpointer=checkpointer, interrupt_after=["agent"], @@ -5562,6 +5564,296 @@ def test_state_graph_packets( parent_config=[*app_w_interrupt.checkpointer.list(config, limit=2)][-1].config, ) + # interrupt before tools + + app_w_interrupt = workflow.compile( + checkpointer=checkpointer, + interrupt_before=["tools"], + ) + config = {"configurable": {"thread_id": "2"}} + model.i = 0 + + assert [ + c + for c in app_w_interrupt.stream( + {"messages": HumanMessage(content="what is weather in sf")}, config + ) + ] == [ + { + "agent": { + "messages": AIMessage( + id="ai1", + content="", + tool_calls=[ + { + "id": "tool_call123", + "name": "search_api", + "args": {"query": "query"}, + }, + ], + ) + } + }, + {"__interrupt__": ()}, + ] + + assert app_w_interrupt.get_state(config) == StateSnapshot( + values={ + "messages": [ + _AnyIdHumanMessage(content="what is weather in sf"), + AIMessage( + id="ai1", + content="", + tool_calls=[ + { + "id": "tool_call123", + "name": "search_api", + "args": {"query": "query"}, + }, + ], + ), + ] + }, + tasks=(PregelTask(AnyStr(), "tools", (PUSH, 0)),), + next=("tools",), + config=(app_w_interrupt.checkpointer.get_tuple(config)).config, + created_at=(app_w_interrupt.checkpointer.get_tuple(config)).checkpoint["ts"], + metadata={ + "parents": {}, + "source": "loop", + "step": 1, + "writes": { + "agent": { + "messages": AIMessage( + id="ai1", + content="", + tool_calls=[ + { + "id": "tool_call123", + "name": "search_api", + "args": {"query": "query"}, + }, + ], + ) + } + }, + "thread_id": "2", + }, + parent_config=[*app_w_interrupt.checkpointer.list(config, limit=2)][-1].config, + ) + + # modify ai message + last_message = (app_w_interrupt.get_state(config)).values["messages"][-1] + last_message.tool_calls[0]["args"]["query"] = "a different query" + app_w_interrupt.update_state( + config, {"messages": last_message, "something_extra": "hi there"} + ) + + # message was replaced instead of appended + assert app_w_interrupt.get_state(config) == StateSnapshot( + values={ + "messages": [ + _AnyIdHumanMessage(content="what is weather in sf"), + AIMessage( + id="ai1", + content="", + tool_calls=[ + { + "id": "tool_call123", + "name": "search_api", + "args": {"query": "a different query"}, + }, + ], + ), + ] + }, + tasks=(PregelTask(AnyStr(), "tools", (PUSH, 0)),), + next=("tools",), + config=app_w_interrupt.checkpointer.get_tuple(config).config, + created_at=(app_w_interrupt.checkpointer.get_tuple(config)).checkpoint["ts"], + metadata={ + "parents": {}, + "source": "update", + "step": 2, + "writes": { + "agent": { + "messages": AIMessage( + id="ai1", + content="", + tool_calls=[ + { + "id": "tool_call123", + "name": "search_api", + "args": {"query": "a different query"}, + }, + ], + ), + "something_extra": "hi there", + } + }, + "thread_id": "2", + }, + parent_config=[*app_w_interrupt.checkpointer.list(config, limit=2)][-1].config, + ) + + assert [c for c in app_w_interrupt.stream(None, config)] == [ + { + "tools": { + "messages": _AnyIdToolMessage( + content="result for a different query", + name="search_api", + tool_call_id="tool_call123", + ) + } + }, + { + "agent": { + "messages": AIMessage( + id="ai2", + content="", + tool_calls=[ + { + "id": "tool_call234", + "name": "search_api", + "args": {"query": "another", "idx": 0}, + }, + { + "id": "tool_call567", + "name": "search_api", + "args": {"query": "a third one", "idx": 1}, + }, + ], + ) + }, + }, + {"__interrupt__": ()}, + ] + + assert app_w_interrupt.get_state(config) == StateSnapshot( + values={ + "messages": [ + _AnyIdHumanMessage(content="what is weather in sf"), + AIMessage( + id="ai1", + content="", + tool_calls=[ + { + "id": "tool_call123", + "name": "search_api", + "args": {"query": "a different query"}, + }, + ], + ), + _AnyIdToolMessage( + content="result for a different query", + name="search_api", + tool_call_id="tool_call123", + ), + AIMessage( + id="ai2", + content="", + tool_calls=[ + { + "id": "tool_call234", + "name": "search_api", + "args": {"query": "another", "idx": 0}, + }, + { + "id": "tool_call567", + "name": "search_api", + "args": {"query": "a third one", "idx": 1}, + }, + ], + ), + ] + }, + tasks=( + PregelTask(AnyStr(), "tools", (PUSH, 0)), + PregelTask(AnyStr(), "tools", (PUSH, 1)), + ), + next=("tools", "tools"), + config=app_w_interrupt.checkpointer.get_tuple(config).config, + created_at=(app_w_interrupt.checkpointer.get_tuple(config)).checkpoint["ts"], + metadata={ + "parents": {}, + "source": "loop", + "step": 4, + "writes": { + "agent": { + "messages": AIMessage( + id="ai2", + content="", + tool_calls=[ + { + "id": "tool_call234", + "name": "search_api", + "args": {"query": "another", "idx": 0}, + }, + { + "id": "tool_call567", + "name": "search_api", + "args": {"query": "a third one", "idx": 1}, + }, + ], + ) + }, + }, + "thread_id": "2", + }, + parent_config=[*app_w_interrupt.checkpointer.list(config, limit=2)][-1].config, + ) + + app_w_interrupt.update_state( + config, + { + "messages": AIMessage(content="answer", id="ai2"), + "something_extra": "hi there", + }, + ) + + # replaces message even if object identity is different, as long as id is the same + assert app_w_interrupt.get_state(config) == StateSnapshot( + values={ + "messages": [ + _AnyIdHumanMessage(content="what is weather in sf"), + AIMessage( + id="ai1", + content="", + tool_calls=[ + { + "id": "tool_call123", + "name": "search_api", + "args": {"query": "a different query"}, + }, + ], + ), + _AnyIdToolMessage( + content="result for a different query", + name="search_api", + tool_call_id="tool_call123", + ), + AIMessage(content="answer", id="ai2"), + ] + }, + tasks=(), + next=(), + config=app_w_interrupt.checkpointer.get_tuple(config).config, + created_at=(app_w_interrupt.checkpointer.get_tuple(config)).checkpoint["ts"], + metadata={ + "parents": {}, + "source": "update", + "step": 5, + "writes": { + "agent": { + "messages": AIMessage(content="answer", id="ai2"), + "something_extra": "hi there", + } + }, + "thread_id": "2", + }, + parent_config=[*app_w_interrupt.checkpointer.list(config, limit=2)][-1].config, + ) + @pytest.mark.parametrize("checkpointer_name", ALL_CHECKPOINTERS_SYNC) def test_message_graph( diff --git a/libs/langgraph/tests/test_pregel_async.py b/libs/langgraph/tests/test_pregel_async.py index 87d2679e3..981c002f5 100644 --- a/libs/langgraph/tests/test_pregel_async.py +++ b/libs/langgraph/tests/test_pregel_async.py @@ -5209,6 +5209,8 @@ async def test_state_graph_packets(checkpointer_name: str) -> None: ] async with awith_checkpointer(checkpointer_name) as checkpointer: + # interrupt after agent + app_w_interrupt = workflow.compile( checkpointer=checkpointer, interrupt_after=["agent"], @@ -5502,6 +5504,302 @@ async def test_state_graph_packets(checkpointer_name: str) -> None: ][-1].config, ) + # interrupt before tools + + app_w_interrupt = workflow.compile( + checkpointer=checkpointer, + interrupt_before=["tools"], + ) + config = {"configurable": {"thread_id": "2"}} + model.i = 0 + + assert [ + c + async for c in app_w_interrupt.astream( + {"messages": HumanMessage(content="what is weather in sf")}, config + ) + ] == [ + { + "agent": { + "messages": AIMessage( + id="ai1", + content="", + tool_calls=[ + { + "id": "tool_call123", + "name": "search_api", + "args": {"query": "query"}, + }, + ], + ) + } + }, + {"__interrupt__": ()}, + ] + + assert await app_w_interrupt.aget_state(config) == StateSnapshot( + values={ + "messages": [ + _AnyIdHumanMessage(content="what is weather in sf"), + AIMessage( + id="ai1", + content="", + tool_calls=[ + { + "id": "tool_call123", + "name": "search_api", + "args": {"query": "query"}, + }, + ], + ), + ] + }, + tasks=(PregelTask(AnyStr(), "tools", (PUSH, 0)),), + next=("tools",), + config=(await app_w_interrupt.checkpointer.aget_tuple(config)).config, + created_at=( + await app_w_interrupt.checkpointer.aget_tuple(config) + ).checkpoint["ts"], + metadata={ + "parents": {}, + "source": "loop", + "step": 1, + "writes": { + "agent": { + "messages": AIMessage( + id="ai1", + content="", + tool_calls=[ + { + "id": "tool_call123", + "name": "search_api", + "args": {"query": "query"}, + }, + ], + ) + } + }, + "thread_id": "2a", + }, + parent_config=[ + c async for c in app_w_interrupt.checkpointer.alist(config, limit=2) + ][-1].config, + ) + + # modify ai message + last_message = (await app_w_interrupt.aget_state(config)).values["messages"][-1] + last_message.tool_calls[0]["args"]["query"] = "a different query" + await app_w_interrupt.aupdate_state(config, {"messages": last_message}) + + # message was replaced instead of appended + tup = await app_w_interrupt.checkpointer.aget_tuple(config) + assert await app_w_interrupt.aget_state(config) == StateSnapshot( + values={ + "messages": [ + _AnyIdHumanMessage(content="what is weather in sf"), + AIMessage( + id="ai1", + content="", + tool_calls=[ + { + "id": "tool_call123", + "name": "search_api", + "args": {"query": "a different query"}, + }, + ], + ), + ] + }, + tasks=(PregelTask(AnyStr(), "tools", (PUSH, 0)),), + next=("tools",), + config=tup.config, + created_at=tup.checkpoint["ts"], + metadata={ + "parents": {}, + "source": "update", + "step": 2, + "writes": { + "agent": { + "messages": AIMessage( + id="ai1", + content="", + tool_calls=[ + { + "id": "tool_call123", + "name": "search_api", + "args": {"query": "a different query"}, + }, + ], + ) + } + }, + "thread_id": "2a", + }, + parent_config=[ + c async for c in app_w_interrupt.checkpointer.alist(config, limit=2) + ][-1].config, + ) + + assert [c async for c in app_w_interrupt.astream(None, config)] == [ + { + "tools": { + "messages": _AnyIdToolMessage( + content="result for a different query", + name="search_api", + tool_call_id="tool_call123", + ) + } + }, + { + "agent": { + "messages": AIMessage( + id="ai2", + content="", + tool_calls=[ + { + "id": "tool_call234", + "name": "search_api", + "args": {"query": "another", "idx": 0}, + }, + { + "id": "tool_call567", + "name": "search_api", + "args": {"query": "a third one", "idx": 1}, + }, + ], + ) + }, + }, + {"__interrupt__": ()}, + ] + + tup = await app_w_interrupt.checkpointer.aget_tuple(config) + assert await app_w_interrupt.aget_state(config) == StateSnapshot( + values={ + "messages": [ + _AnyIdHumanMessage(content="what is weather in sf"), + AIMessage( + id="ai1", + content="", + tool_calls=[ + { + "id": "tool_call123", + "name": "search_api", + "args": {"query": "a different query"}, + }, + ], + ), + _AnyIdToolMessage( + content="result for a different query", + name="search_api", + tool_call_id="tool_call123", + ), + AIMessage( + id="ai2", + content="", + tool_calls=[ + { + "id": "tool_call234", + "name": "search_api", + "args": {"query": "another", "idx": 0}, + }, + { + "id": "tool_call567", + "name": "search_api", + "args": {"query": "a third one", "idx": 1}, + }, + ], + ), + ] + }, + tasks=( + PregelTask(AnyStr(), "tools", (PUSH, 0)), + PregelTask(AnyStr(), "tools", (PUSH, 1)), + ), + next=("tools", "tools"), + config=tup.config, + created_at=tup.checkpoint["ts"], + metadata={ + "parents": {}, + "source": "loop", + "step": 4, + "writes": { + "agent": { + "messages": AIMessage( + id="ai2", + content="", + tool_calls=[ + { + "id": "tool_call234", + "name": "search_api", + "args": {"query": "another", "idx": 0}, + }, + { + "id": "tool_call567", + "name": "search_api", + "args": {"query": "a third one", "idx": 1}, + }, + ], + ) + }, + }, + "thread_id": "2a", + }, + parent_config=[ + c async for c in app_w_interrupt.checkpointer.alist(config, limit=2) + ][-1].config, + ) + + await app_w_interrupt.aupdate_state( + config, + {"messages": AIMessage(content="answer", id="ai2")}, + ) + + # replaces message even if object identity is different, as long as id is the same + tup = await app_w_interrupt.checkpointer.aget_tuple(config) + assert await app_w_interrupt.aget_state(config) == StateSnapshot( + values={ + "messages": [ + _AnyIdHumanMessage(content="what is weather in sf"), + AIMessage( + id="ai1", + content="", + tool_calls=[ + { + "id": "tool_call123", + "name": "search_api", + "args": {"query": "a different query"}, + }, + ], + ), + _AnyIdToolMessage( + content="result for a different query", + name="search_api", + tool_call_id="tool_call123", + ), + AIMessage(content="answer", id="ai2"), + ] + }, + tasks=(), + next=(), + config=tup.config, + created_at=tup.checkpoint["ts"], + metadata={ + "parents": {}, + "source": "update", + "step": 5, + "writes": { + "agent": { + "messages": AIMessage(content="answer", id="ai2"), + } + }, + "thread_id": "2a", + }, + parent_config=[ + c async for c in app_w_interrupt.checkpointer.alist(config, limit=2) + ][-1].config, + ) + @pytest.mark.parametrize("checkpointer_name", ALL_CHECKPOINTERS_ASYNC) async def test_message_graph(checkpointer_name: str) -> None: From f283dac325f7a60f669891b0ba9df6a6f98c2e8e Mon Sep 17 00:00:00 2001 From: Nuno Campos Date: Tue, 5 Nov 2024 09:27:02 -0800 Subject: [PATCH 57/60] Add one more test for send-react-interrupt flow with replacing tool call --- libs/langgraph/tests/test_pregel.py | 199 +++++++++++++++++++++ libs/langgraph/tests/test_pregel_async.py | 207 +++++++++++++++++++++- 2 files changed, 402 insertions(+), 4 deletions(-) diff --git a/libs/langgraph/tests/test_pregel.py b/libs/langgraph/tests/test_pregel.py index 92832c8d9..4345c5689 100644 --- a/libs/langgraph/tests/test_pregel.py +++ b/libs/langgraph/tests/test_pregel.py @@ -2097,6 +2097,205 @@ def test_send_react_interrupt( } assert foo_called == 0 + # interrupt-update-resume flow, creating new Send in update call + foo_called = 0 + graph = builder.compile(checkpointer=checkpointer, interrupt_before=["foo"]) + thread1 = {"configurable": {"thread_id": "3"}} + assert graph.invoke({"messages": [HumanMessage("hello")]}, thread1) == { + "messages": [ + _AnyIdHumanMessage(content="hello"), + _AnyIdAIMessage( + content="", + tool_calls=[ + { + "name": "foo", + "args": {"hi": [1, 2, 3]}, + "id": "", + "type": "tool_call", + } + ], + ), + ] + } + assert foo_called == 0 + + # get state should show the pending task + state = graph.get_state(thread1) + assert state == StateSnapshot( + values={ + "messages": [ + _AnyIdHumanMessage(content="hello"), + _AnyIdAIMessage( + content="", + tool_calls=[ + { + "name": "foo", + "args": {"hi": [1, 2, 3]}, + "id": "", + "type": "tool_call", + } + ], + ), + ] + }, + next=("foo",), + config={ + "configurable": { + "thread_id": "3", + "checkpoint_ns": "", + "checkpoint_id": AnyStr(), + } + }, + metadata={ + "step": 1, + "source": "loop", + "writes": { + "agent": { + "messages": _AnyIdAIMessage( + content="", + tool_calls=[ + { + "name": "foo", + "args": {"hi": [1, 2, 3]}, + "id": "", + "type": "tool_call", + } + ], + ) + } + }, + "parents": {}, + "thread_id": "3", + }, + created_at=AnyStr(), + parent_config={ + "configurable": { + "thread_id": "3", + "checkpoint_ns": "", + "checkpoint_id": AnyStr(), + } + }, + tasks=( + PregelTask( + id=AnyStr(), + name="foo", + path=("__pregel_push", 0), + error=None, + interrupts=(), + state=None, + result=None, + ), + ), + ) + + # replace the tool call, should clear previous send, create new one + graph.update_state( + thread1, + { + "messages": AIMessage( + "", + id=ai_message.id, + tool_calls=[ + { + "name": "foo", + "args": {"hi": [4, 5, 6]}, + "id": "tool1", + "type": "tool_call", + } + ], + ) + }, + ) + + # prev tool call no longer in pending tasks, new tool call is + assert graph.get_state(thread1) == StateSnapshot( + values={ + "messages": [ + _AnyIdHumanMessage(content="hello"), + _AnyIdAIMessage( + content="", + tool_calls=[ + { + "name": "foo", + "args": {"hi": [4, 5, 6]}, + "id": "tool1", + "type": "tool_call", + } + ], + ), + ] + }, + next=("foo",), + config={ + "configurable": { + "thread_id": "3", + "checkpoint_ns": "", + "checkpoint_id": AnyStr(), + } + }, + metadata={ + "step": 2, + "source": "update", + "writes": { + "agent": { + "messages": _AnyIdAIMessage( + content="", + tool_calls=[ + { + "name": "foo", + "args": {"hi": [4, 5, 6]}, + "id": "tool1", + "type": "tool_call", + } + ], + ) + } + }, + "parents": {}, + "thread_id": "3", + }, + created_at=AnyStr(), + parent_config={ + "configurable": { + "thread_id": "3", + "checkpoint_ns": "", + "checkpoint_id": AnyStr(), + } + }, + tasks=( + PregelTask( + id=AnyStr(), + name="foo", + path=("__pregel_push", 0), + error=None, + interrupts=(), + state=None, + result=None, + ), + ), + ) + + # prev tool call not executed, new tool call is + assert graph.invoke(None, thread1) == { + "messages": [ + _AnyIdHumanMessage(content="hello"), + AIMessage( + "", + id="ai1", + tool_calls=[ + { + "name": "foo", + "args": {"hi": [4, 5, 6]}, + "id": "tool1", + "type": "tool_call", + } + ], + ), + _AnyIdToolMessage(content="{'hi': [4, 5, 6]}", tool_call_id="tool1"), + ] + } + assert foo_called == 1 + @pytest.mark.parametrize("checkpointer_name", ALL_CHECKPOINTERS_SYNC) def test_send_react_interrupt_control( diff --git a/libs/langgraph/tests/test_pregel_async.py b/libs/langgraph/tests/test_pregel_async.py index 981c002f5..83bd4789a 100644 --- a/libs/langgraph/tests/test_pregel_async.py +++ b/libs/langgraph/tests/test_pregel_async.py @@ -2311,6 +2311,205 @@ async def test_send_react_interrupt(checkpointer_name: str) -> None: } assert foo_called == 0 + # interrupt-update-resume flow, creating new Send in update call + foo_called = 0 + graph = builder.compile(checkpointer=checkpointer, interrupt_before=["foo"]) + thread1 = {"configurable": {"thread_id": "3"}} + assert await graph.ainvoke({"messages": [HumanMessage("hello")]}, thread1) == { + "messages": [ + _AnyIdHumanMessage(content="hello"), + _AnyIdAIMessage( + content="", + tool_calls=[ + { + "name": "foo", + "args": {"hi": [1, 2, 3]}, + "id": "", + "type": "tool_call", + } + ], + ), + ] + } + assert foo_called == 0 + + # get state should show the pending task + state = await graph.aget_state(thread1) + assert state == StateSnapshot( + values={ + "messages": [ + _AnyIdHumanMessage(content="hello"), + _AnyIdAIMessage( + content="", + tool_calls=[ + { + "name": "foo", + "args": {"hi": [1, 2, 3]}, + "id": "", + "type": "tool_call", + } + ], + ), + ] + }, + next=("foo",), + config={ + "configurable": { + "thread_id": "3", + "checkpoint_ns": "", + "checkpoint_id": AnyStr(), + } + }, + metadata={ + "step": 1, + "source": "loop", + "writes": { + "agent": { + "messages": _AnyIdAIMessage( + content="", + tool_calls=[ + { + "name": "foo", + "args": {"hi": [1, 2, 3]}, + "id": "", + "type": "tool_call", + } + ], + ) + } + }, + "parents": {}, + "thread_id": "3", + }, + created_at=AnyStr(), + parent_config={ + "configurable": { + "thread_id": "3", + "checkpoint_ns": "", + "checkpoint_id": AnyStr(), + } + }, + tasks=( + PregelTask( + id=AnyStr(), + name="foo", + path=("__pregel_push", 0), + error=None, + interrupts=(), + state=None, + result=None, + ), + ), + ) + + # replace the tool call, should clear previous send, create new one + await graph.aupdate_state( + thread1, + { + "messages": AIMessage( + "", + id=ai_message.id, + tool_calls=[ + { + "name": "foo", + "args": {"hi": [4, 5, 6]}, + "id": "tool1", + "type": "tool_call", + } + ], + ) + }, + ) + + # prev tool call no longer in pending tasks, new tool call is + assert await graph.aget_state(thread1) == StateSnapshot( + values={ + "messages": [ + _AnyIdHumanMessage(content="hello"), + _AnyIdAIMessage( + content="", + tool_calls=[ + { + "name": "foo", + "args": {"hi": [4, 5, 6]}, + "id": "tool1", + "type": "tool_call", + } + ], + ), + ] + }, + next=("foo",), + config={ + "configurable": { + "thread_id": "3", + "checkpoint_ns": "", + "checkpoint_id": AnyStr(), + } + }, + metadata={ + "step": 2, + "source": "update", + "writes": { + "agent": { + "messages": _AnyIdAIMessage( + content="", + tool_calls=[ + { + "name": "foo", + "args": {"hi": [4, 5, 6]}, + "id": "tool1", + "type": "tool_call", + } + ], + ) + } + }, + "parents": {}, + "thread_id": "3", + }, + created_at=AnyStr(), + parent_config={ + "configurable": { + "thread_id": "3", + "checkpoint_ns": "", + "checkpoint_id": AnyStr(), + } + }, + tasks=( + PregelTask( + id=AnyStr(), + name="foo", + path=("__pregel_push", 0), + error=None, + interrupts=(), + state=None, + result=None, + ), + ), + ) + + # prev tool call not executed, new tool call is + assert await graph.ainvoke(None, thread1) == { + "messages": [ + _AnyIdHumanMessage(content="hello"), + AIMessage( + "", + id="ai1", + tool_calls=[ + { + "name": "foo", + "args": {"hi": [4, 5, 6]}, + "id": "tool1", + "type": "tool_call", + } + ], + ), + _AnyIdToolMessage(content="{'hi': [4, 5, 6]}", tool_call_id="tool1"), + ] + } + assert foo_called == 1 + @pytest.mark.parametrize("checkpointer_name", ALL_CHECKPOINTERS_ASYNC) async def test_send_react_interrupt_control(checkpointer_name: str) -> None: @@ -5579,7 +5778,7 @@ async def test_state_graph_packets(checkpointer_name: str) -> None: ) } }, - "thread_id": "2a", + "thread_id": "2", }, parent_config=[ c async for c in app_w_interrupt.checkpointer.alist(config, limit=2) @@ -5633,7 +5832,7 @@ async def test_state_graph_packets(checkpointer_name: str) -> None: ) } }, - "thread_id": "2a", + "thread_id": "2", }, parent_config=[ c async for c in app_w_interrupt.checkpointer.alist(config, limit=2) @@ -5743,7 +5942,7 @@ async def test_state_graph_packets(checkpointer_name: str) -> None: ) }, }, - "thread_id": "2a", + "thread_id": "2", }, parent_config=[ c async for c in app_w_interrupt.checkpointer.alist(config, limit=2) @@ -5793,7 +5992,7 @@ async def test_state_graph_packets(checkpointer_name: str) -> None: "messages": AIMessage(content="answer", id="ai2"), } }, - "thread_id": "2a", + "thread_id": "2", }, parent_config=[ c async for c in app_w_interrupt.checkpointer.alist(config, limit=2) From de8e487ff16d62bd5ecaf28c0c90d08c452918f4 Mon Sep 17 00:00:00 2001 From: Nuno Campos Date: Tue, 5 Nov 2024 10:53:42 -0800 Subject: [PATCH 58/60] Add todo --- libs/langgraph/tests/test_pregel.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/libs/langgraph/tests/test_pregel.py b/libs/langgraph/tests/test_pregel.py index 87606ea29..0f0bbe461 100644 --- a/libs/langgraph/tests/test_pregel.py +++ b/libs/langgraph/tests/test_pregel.py @@ -2544,6 +2544,10 @@ def test_send_react_interrupt_control( } assert foo_called == 0 + # interrupt-update-resume flow, creating new Send in update call + + # TODO add here test with invoke(Control()) + @pytest.mark.parametrize("checkpointer_name", ALL_CHECKPOINTERS_SYNC) def test_invoke_checkpoint_three( From 18a3fa4a00fd09dd984303de7f725c92b49014e8 Mon Sep 17 00:00:00 2001 From: Nuno Campos Date: Tue, 5 Nov 2024 14:22:23 -0800 Subject: [PATCH 59/60] Ignore unknown tasks --- libs/langgraph/langgraph/pregel/__init__.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/libs/langgraph/langgraph/pregel/__init__.py b/libs/langgraph/langgraph/pregel/__init__.py index d69fbc75f..5ff3c69b8 100644 --- a/libs/langgraph/langgraph/pregel/__init__.py +++ b/libs/langgraph/langgraph/pregel/__init__.py @@ -515,6 +515,8 @@ class Pregel(PregelProtocol): for tid, k, v in saved.pending_writes: if k in (ERROR, INTERRUPT, SCHEDULED): continue + if tid not in next_tasks: + continue next_tasks[tid].writes.append((k, v)) if tasks := [t for t in next_tasks.values() if t.writes]: apply_writes(saved.checkpoint, channels, tasks, None) @@ -615,6 +617,8 @@ class Pregel(PregelProtocol): for tid, k, v in saved.pending_writes: if k in (ERROR, INTERRUPT, SCHEDULED): continue + if tid not in next_tasks: + continue next_tasks[tid].writes.append((k, v)) if tasks := [t for t in next_tasks.values() if t.writes]: apply_writes(saved.checkpoint, channels, tasks, None) @@ -891,6 +895,8 @@ class Pregel(PregelProtocol): for tid, k, v in saved.pending_writes: if k in (ERROR, INTERRUPT, SCHEDULED): continue + if tid not in next_tasks: + continue 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) @@ -1073,6 +1079,8 @@ class Pregel(PregelProtocol): for tid, k, v in saved.pending_writes: if k in (ERROR, INTERRUPT, SCHEDULED): continue + if tid not in next_tasks: + continue 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) From 010564cbb392d8107d8648b74bdbf447b78aee4a Mon Sep 17 00:00:00 2001 From: Nuno Campos Date: Tue, 5 Nov 2024 15:24:20 -0800 Subject: [PATCH 60/60] lib: Make Control object serializable --- .../langgraph/checkpoint/serde/jsonplus.py | 17 ++++++++++++++++- .../langgraph/checkpoint/serde/types.py | 11 +++++++++++ libs/langgraph/langgraph/types.py | 8 +++++++- 3 files changed, 34 insertions(+), 2 deletions(-) diff --git a/libs/checkpoint/langgraph/checkpoint/serde/jsonplus.py b/libs/checkpoint/langgraph/checkpoint/serde/jsonplus.py index f8d280b96..10908eb87 100644 --- a/libs/checkpoint/langgraph/checkpoint/serde/jsonplus.py +++ b/libs/checkpoint/langgraph/checkpoint/serde/jsonplus.py @@ -25,7 +25,7 @@ from langchain_core.load.serializable import Serializable from zoneinfo import ZoneInfo from langgraph.checkpoint.serde.base import SerializerProtocol -from langgraph.checkpoint.serde.types import SendProtocol +from langgraph.checkpoint.serde.types import ControlProtocol, SendProtocol from langgraph.store.base import Item LC_REVIVER = Reviver() @@ -402,6 +402,21 @@ def _msgpack_default(obj: Any) -> Union[str, msgpack.ExtType]: (obj.__class__.__module__, obj.__class__.__name__, (obj.node, obj.arg)), ), ) + elif isinstance(obj, ControlProtocol): + return msgpack.ExtType( + EXT_CONSTRUCTOR_KW_ARGS, + _msgpack_enc( + ( + obj.__class__.__module__, + obj.__class__.__name__, + { + "update_state": obj.update_state, + "trigger": obj.trigger, + "send": obj.send, + }, + ), + ), + ) elif dataclasses.is_dataclass(obj): # doesn't use dataclasses.asdict to avoid deepcopy and recursion return msgpack.ExtType( diff --git a/libs/checkpoint/langgraph/checkpoint/serde/types.py b/libs/checkpoint/langgraph/checkpoint/serde/types.py index 43a5bf878..862cbe83f 100644 --- a/libs/checkpoint/langgraph/checkpoint/serde/types.py +++ b/libs/checkpoint/langgraph/checkpoint/serde/types.py @@ -4,6 +4,7 @@ from typing import ( Protocol, Sequence, TypeVar, + Union, runtime_checkable, ) @@ -48,3 +49,13 @@ class SendProtocol(Protocol): def __repr__(self) -> str: ... def __eq__(self, value: object) -> bool: ... + + +@runtime_checkable +class ControlProtocol(Protocol): + # Mirrors langgraph.constants.Control + update_state: Optional[dict[str, Any]] + trigger: Union[str, Sequence[str]] + send: Union[Any, Sequence[Any]] + + def __repr__(self) -> str: ... diff --git a/libs/langgraph/langgraph/types.py b/libs/langgraph/langgraph/types.py index 1ed6e43ad..b1a40f1a1 100644 --- a/libs/langgraph/langgraph/types.py +++ b/libs/langgraph/langgraph/types.py @@ -227,6 +227,10 @@ N = TypeVar("N") class Control(Generic[N]): + """A control object to update the graph's state, trigger nodes, and send messages.""" + + __slots__ = ("update_state", "trigger", "send") + def __init__( self, *, @@ -240,7 +244,9 @@ class Control(Generic[N]): def __repr__(self) -> str: contents = ", ".join( - f"{key}={value!r}" for key, value in self.__dict__.items() if value + f"{key}={value!r}" + for key in self.__slots__ + if (value := getattr(self, key)) ) return f"Control({contents})"

|jtNM~j&xJd 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 diff --git a/docs/docs/cloud/concepts/cloud.md b/docs/docs/cloud/concepts/cloud.md deleted file mode 100644 index a6ddd3f92..000000000 --- a/docs/docs/cloud/concepts/cloud.md +++ /dev/null @@ -1,28 +0,0 @@ -# Cloud Concepts - -This page describes the high-level concepts of the LangGraph Cloud deployment. - -## Deployment - -A deployment is an instance of a LangGraph API. A single deployment can have many [revisions](#revision). When a deployment is created, all of the necessary infrastructure (e.g. database, containers, secrets store) are automatically provisioned. See the [architecture diagram](#architecture) below for more details. - -See the [how-to guide](../deployment/cloud.md#create-new-deployment) for creating a new deployment. - -## Revision - -A revision is an iteration of a [deployment](#deployment). When a new deployment is created, an initial revision is automatically created. To deploy new code changes or update environment variable configurations for a deployment, a new revision must be created. When a revision is created, a new container image is built automatically. - -See the [how-to guide](../deployment/cloud.md#create-new-revision) for creating a new revision. - -## Asynchronous Deployment - -Infrastructure for [deployments](#deployment) and [revisions](#revision) are provisioned and deployed asynchronously. They are not deployed immediately after submission. Currently, deployment can take up to several minutes. - -## Architecture - -!!! warning "Subject to Change" - The LangGraph Cloud deployment architecture may change in the future. - -A high-level diagram of a LangGraph Cloud deployment. - -![diagram](langgraph_cloud_architecture.png) diff --git a/docs/docs/cloud/concepts/langgraph_cloud_architecture.png b/docs/docs/cloud/concepts/langgraph_cloud_architecture.png deleted file mode 100644 index dc2d1b15a585e88bf134889111cc23b67362e48d..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 161117 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 diff --git a/docs/docs/cloud/faq/studio.md b/docs/docs/cloud/faq/studio.md deleted file mode 100644 index 232699315..000000000 --- a/docs/docs/cloud/faq/studio.md +++ /dev/null @@ -1,73 +0,0 @@ -# Studio FAQs - -## Why is my project failing to start? - -There are a few reasons that your project might fail to start, here are some of the most common ones. - -### Docker issues - -LangGraph Studio requires Docker Desktop version 4.24 or higher. Please make sure you have a version of Docker installed that satisfies that requirement and also make sure you have the Docker Desktop app up and running before trying to use LangGraph Studio. In addition, make sure you have docker-compose updated to version 2.22.0 or higher. - -### Configuration or environment issues - -Another reason your project might fail to start is because your configuration file is defined incorrectly, or you are missing required environment variables. - -## How does interrupt work? - -When you select the `Interrupts` dropdown and select a node to interrupt the graph will pause execution before and after (unless the node goes straight to `END`) that node has run. This means that you will be able to both edit the state before the node is ran and the state after the node has ran. This is intended to allow developers more fine-grained control over the behavior of a node and make it easier to observe how the node is behaving. You will not be able to edit the state after the node has ran if the node is the final node in the graph. - -## How do I reload the app? - -If you would like to reload the app, don't use Command+R as you might normally do. Instead, close and reopen the app for a full refresh. - -## How does automatic rebuilding work? - -One of the key features of LangGraph Studio is that it automatically rebuilds your image when you change the source code. This allows for a super fast development and testing cycle which makes it easy to iterate on your graph. There are two different ways that LangGraph rebuilds your image: either by editing the image or completely rebuilding it. - -### Rebuilds from source code changes - -If you modified the source code only (no configuration or dependency changes!) then the image does not require a full rebuild, and LangGraph Studio will only update the relevant parts. The UI status in the bottom left will switch from `Online` to `Stopping` temporarily while the image gets edited. The logs will be shown as this process is happening, and after the image has been edited the status will change back to `Online` and you will be able to run your graph with the modified code! - - -### Rebuilds from configuration or dependency changes - -If you edit your graph configuration file (`langgraph.json`) or the dependencies (either `pyproject.toml` or `requirements.txt`) then the entire image will be rebuilt. This will cause the UI to switch away from the graph view and start showing the logs of the new image building process. This can take a minute or two, and once it is done your updated image will be ready to use! - -## Why is my graph taking so long to startup? - -The LangGraph Studio interacts with a local LangGraph API server. To stay aligned with ongoing updates, the LangGraph API requires regular rebuilding. As a result, you may occasionally experience slight delays when starting up your project. - -## Why are extra edges showing up in my graph? - -If you don't define your conditional edges carefully, you might notice extra edges appearing in your graph. This is because without proper definition, LangGraph Studio assumes the conditional edge could access all other nodes. In order for this to not be the case, you need to be explicit about how you define the nodes the conditional edge routes to. There are two ways you can do this: - -### Solution 1: Include a path map - -The first way to solve this is to add path maps to your conditional edges. A path map is just a dictionary or array that maps the possible outputs of your router function with the names of the nodes that each output corresponds to. The path map is passed as the third argument to the `add_conditional_edges` function like so: - -=== "Python" - - ```python - graph.add_conditional_edges("node_a", routing_function, {True: "node_b", False: "node_c"}) - ``` - -=== "Javascript" - - ```ts - graph.addConditionalEdges("node_a", routingFunction, { true: "node_b", false: "node_c" }); - ``` - -In this case, the routing function returns either True or False, which map to `node_b` and `node_c` respectively. - -### Solution 2: Update the typing of the router (Python only) - -Instead of passing a path map, you can also be explicit about the typing of your routing function by specifying the nodes it can map to using the `Literal` python definition. Here is an example of how to define a routing function in that way: - -```python -def routing_function(state: GraphState) -> Literal["node_b","node_c"]: - if state['some_condition'] == True: - return "node_b" - else: - return "node_c" -``` - diff --git a/docs/docs/cloud/how-tos/assistant_versioning.md b/docs/docs/cloud/how-tos/assistant_versioning.md index 3fe4fc7a7..7793ef45c 100644 --- a/docs/docs/cloud/how-tos/assistant_versioning.md +++ b/docs/docs/cloud/how-tos/assistant_versioning.md @@ -1,6 +1,6 @@ # How to version assistants -In this how-to guide we will walk through how you can create and manage different assistant versions. If you haven't already, you can read [this](../concepts/api.md/#versioning-assistants) conceptual guide to gain a better understanding of what assistant versioning is. This how-to assumes you have a graph that is configurable, which means you have defined a config schema and passed it to your graph as follows: +In this how-to guide we will walk through how you can create and manage different assistant versions. If you haven't already, you can read [this](../../concepts/assistants.md#versioning-assistants) conceptual guide to gain a better understanding of what assistant versioning is. This how-to assumes you have a graph that is configurable, which means you have defined a config schema and passed it to your graph as follows: === "Python" diff --git a/docs/docs/cloud/how-tos/copy_threads.md b/docs/docs/cloud/how-tos/copy_threads.md index 22586ed4d..2e83ae0e4 100644 --- a/docs/docs/cloud/how-tos/copy_threads.md +++ b/docs/docs/cloud/how-tos/copy_threads.md @@ -4,7 +4,7 @@ You may wish to copy (i.e. "fork") an existing thread in order to keep the exist ## Setup -This code assumes you already have a thread to copy. You can read about what a thread is [here](https://langchain-ai.github.io/langgraph/cloud/concepts/api/#threads) and learn how to stream a run on a thread in [these how-to guides](https://langchain-ai.github.io/langgraph/cloud/how-tos/#streaming). +This code assumes you already have a thread to copy. You can read about what a thread is [here](../../concepts/langgraph_server.md#threads) and learn how to stream a run on a thread in [these how-to guides](../../how-tos/index.md#streaming_1). ### SDK initialization diff --git a/docs/docs/cloud/how-tos/enqueue_concurrent.md b/docs/docs/cloud/how-tos/enqueue_concurrent.md index a8cb04e4c..4f10436bf 100644 --- a/docs/docs/cloud/how-tos/enqueue_concurrent.md +++ b/docs/docs/cloud/how-tos/enqueue_concurrent.md @@ -1,6 +1,6 @@ # Enqueue -This guide assumes knowledge of what double-texting is, which you can learn about in the [double-texting conceptual guide](../concepts/api.md#double-texting). +This guide assumes knowledge of what double-texting is, which you can learn about in the [double-texting conceptual guide](../../concepts/double_texting.md). The guide covers the `enqueue` option for double texting, which adds the interruptions to a queue and executes them in the order they are received by the client. Below is a quick example of using the `enqueue` option. diff --git a/docs/docs/cloud/how-tos/index.md b/docs/docs/cloud/how-tos/index.md deleted file mode 100644 index b5ef870dd..000000000 --- a/docs/docs/cloud/how-tos/index.md +++ /dev/null @@ -1,85 +0,0 @@ ---- -hide: - - toc ---- - -# How-to Guides - -Welcome to the LangGraph Cloud how-to guides! These guides provide practical, step-by-step instructions for accomplishing key tasks in LangGraph Cloud. - -## Setup - -LangGraph Cloud gives you best in class observability, testing, and hosting services. Learn how to setup your app for deployment to LangGraph Cloud in these how-to guides - -- [How to set up app for deployment (requirements.txt)](../deployment/setup.md) -- [How to set up app for deployment (pyproject.toml)](../deployment/setup_pyproject.md) -- [How to set up app for deployment (JavaScript)](../deployment/setup_javascript.md) -- [How to customize Dockerfile](../deployment/custom_docker.md) -- [How to test locally](../deployment/test_locally.md) - -## Deploy - -Learn how to deploy your app to LangGraph Cloud in these how to guides: - -- [How to deploy to LangGraph cloud](../deployment/cloud.md) -- [How to interact with the deployment using RemoteGraph](../../how-tos/use-remote-graph.md) - - -## Streaming - -Streaming the results of your LLM application is vital for ensuring a good user experience, especially when your graph may call multiple models and take a long time to fully complete a run. Read about how to stream values from your graph in these how to guides: - -- [How to stream values](./stream_values.md) -- [How to stream updates](./stream_updates.md) -- [How to stream messages](./stream_messages.md) -- [How to stream events](./stream_events.md) -- [How to stream in debug mode](./stream_debug.md) -- [How to stream multiple modes](./stream_multiple.md) - -## Double-texting - -Graph execution can take a while, and sometimes users may change their mind about the input they wanted to send before their original input has finished running. For example, a user might notice a typo in their original request and will edit the prompt and resend it. Deciding what to do in these cases is important for ensuring a smooth user experience and preventing your graphs from behaving in unexpected ways. The following how-to guides provide information on the various options LangGraph Cloud gives you for dealing with double-texting: - -- [How to use the interrupt option](./interrupt_concurrent.md) -- [How to use the rollback option](./rollback_concurrent.md) -- [How to use the reject option](./reject_concurrent.md) -- [How to use the enqueue option](./enqueue_concurrent.md) - -## Human-in-the-loop - -When creating complex graphs, leaving every decision up to the LLM can be dangerous, especially when the decisions involve invoking certain tools or accessing specific documents. To remedy this, LangGraph allows you to insert human-in-the-loop behavior to ensure your graph does not have undesired outcomes. Read more about the different ways you can add human-in-the-loop capabilities to your LangGraph Cloud projects in these how-to guides: - -- [How to add a breakpoint](./human_in_the_loop_breakpoint.md) -- [How to wait for user input](./human_in_the_loop_user_input.md) -- [How to edit graph state](./human_in_the_loop_edit_state.md) -- [How to replay and branch from prior states](./human_in_the_loop_time_travel.md) -- [How to review tool calls](./human_in_the_loop_review_tool_calls.md) - -## LangGraph Studio - -LangGraph Studio is a built-in UI for visualizing, testing, and debugging your agents. - -- [How to enter LangGraph Studio](./test_deployment.md) -- [How to enter LangGraph Studio for local deployment](./test_local_deployment.md) -- [How to test your graph in LangGraph Studio](./invoke_studio.md) -- [Interact with threads in LangGraph Studio](./threads_studio.md) - -## Different Types of Runs: - -LangGraph Cloud supports multiple types of runs besides streaming runs. - -- [How to run an agent in the background](./background_run.md) -- [How to run multiple agents in the same thread](./same-thread.md) -- [How to create cron jobs](./cron_jobs.md) -- [How to create stateless runs](./stateless_runs.md) - -## Other - -Other guides that may prove helpful! - -- [How to configure agents](./configuration_cloud.md) -- [How to version assistants](./assistant_versioning.md) -- [How to convert LangGraph calls to LangGraph cloud calls](./langgraph_to_langgraph_cloud.ipynb) -- [How to integrate webhooks](./webhooks.md) -- [How to copy threads](./copy_threads.md) -- [How to check status of your threads](./check_thread_status.md) \ No newline at end of file diff --git a/docs/docs/cloud/how-tos/interrupt_concurrent.md b/docs/docs/cloud/how-tos/interrupt_concurrent.md index e1e35ca53..a45518c8c 100644 --- a/docs/docs/cloud/how-tos/interrupt_concurrent.md +++ b/docs/docs/cloud/how-tos/interrupt_concurrent.md @@ -1,6 +1,6 @@ # Interrupt -This guide assumes knowledge of what double-texting is, which you can learn about in the [double-texting conceptual guide](../concepts/api.md#double-texting). +This guide assumes knowledge of what double-texting is, which you can learn about in the [double-texting conceptual guide](../../concepts/double_texting.md). The guide covers the `interrupt` option for double texting, which interrupts the prior run of the graph and starts a new one with the double-text. This option does not delete the first run, but rather keeps it in the database but sets its status to `interrupted`. Below is a quick example of using the `interrupt` option. diff --git a/docs/docs/cloud/how-tos/reject_concurrent.md b/docs/docs/cloud/how-tos/reject_concurrent.md index cfc406545..c954c7da6 100644 --- a/docs/docs/cloud/how-tos/reject_concurrent.md +++ b/docs/docs/cloud/how-tos/reject_concurrent.md @@ -1,6 +1,6 @@ # Reject -This guide assumes knowledge of what double-texting is, which you can learn about in the [double-texting conceptual guide][double-texting]. +This guide assumes knowledge of what double-texting is, which you can learn about in the [double-texting conceptual guide](../../concepts/double_texting.md). The guide covers the `reject` option for double texting, which rejects the new run of the graph by throwing an error and continues with the original run until completion. Below is a quick example of using the `reject` option. diff --git a/docs/docs/cloud/how-tos/rollback_concurrent.md b/docs/docs/cloud/how-tos/rollback_concurrent.md index a23643685..9fb6582c7 100644 --- a/docs/docs/cloud/how-tos/rollback_concurrent.md +++ b/docs/docs/cloud/how-tos/rollback_concurrent.md @@ -1,6 +1,6 @@ # Rollback -This guide assumes knowledge of what double-texting is, which you can learn about in the [double-texting conceptual guide][double-texting]. +This guide assumes knowledge of what double-texting is, which you can learn about in the [double-texting conceptual guide](../../concepts/double_texting.md). The guide covers the `rollback` option for double texting, which interrupts the prior run of the graph and starts a new one with the double-text. This option is very similar to the `interrupt` option, but in this case the first run is completely deleted from the database and cannot be restarted. Below is a quick example of using the `rollback` option. diff --git a/docs/docs/cloud/index.md b/docs/docs/cloud/index.md deleted file mode 100644 index 73421da49..000000000 --- a/docs/docs/cloud/index.md +++ /dev/null @@ -1,44 +0,0 @@ -# LangGraph Cloud (beta) - -!!! tip - - LangGraph is an MIT-licensed open-source library, which we are committed to maintaining and growing for the community. - - LangGraph Cloud is an optional managed hosting service for LangGraph, which provides additional features geared towards production deployments. - - We are actively contributing improvements back to LangGraph informed by our work on LangGraph Cloud. - - You can always deploy LangGraph applications on your own infrastructure using the open-source LangGraph project. - -!!! warning "Under Construction" - LangGraph Cloud documentation is under construction. Contents may change until general availability. - - - - - -## Overview - -LangGraph Cloud is a managed service for deploying and hosting LangGraph applications. Deploying applications with LangGraph Cloud shortens the time-to-market for developers. With one click, deploy a production-ready API with built-in persistence for your LangGraph application. LangGraph Cloud APIs are horizontally scalable and deployed with durable storage. - -The LangGraph Cloud API exposes functionality of your LangGraph application through [Assistants](./concepts/api.md#assistants). An assistant abstracts the cognitive architecture of your graph. Invoke an assistant by calling the pre-built [API endpoints](./reference/api/api_ref.md). - -LangGraph Cloud is seamlessly integrated with [LangSmith](https://www.langchain.com/langsmith) and is accessible from within the LangSmith UI. - -LangGraph Cloud applications can be tested and debugged using the [LangGraph Studio Desktop](https://github.com/langchain-ai/langgraph-studio). - -## Key Features - -The LangGraph Cloud API supports key LangGraph features in addition to new functionality for enabling complex, agentic workflows. - -- **Assistants and Threads**: Assistants abstract the cognitive architecture of graphs and threads track the state/history of graphs. -- **Streaming**: API support for [LangGraph streaming modes](../concepts/low_level.md#streaming) including setting multiple streaming modes at the same time. -- **Human-in-the-Loop**: API support for [LangGraph human-in-the-loop features](../concepts/agentic_concepts.md#human-in-the-loop). -- **Double Texting**: Configure how assistants respond when new input is received while processing a previous input. Interrupt, rollback, reject, or enqueue. -- **Background Runs/Cron Jobs**: A built-in task queue enables background runs and scheduled cron jobs. -- **Stateless Runs**: For simpler use cases, invoke an assistant without needing to create a thread. - -## Documentation - -- [Tutorials](./quick_start.md): Learn to build and deploy applications for LangGraph Cloud. -- [How-to Guides](./how-tos/index.md): Learn how to set up a LangGraph application for deployment and implement features of the LangGraph Cloud API such as streaming tokens, configuring double texting, and creating cron jobs. Go here if you want to copy and run a specific code snippet. -- [Conceptual Guides](./concepts/api.md): In-depth explanations of the core data models (e.g. assistants), key features of the LangGraph Cloud API (e.g. double texting), and the architecture of a LangGraph Cloud deployment. -- [Reference](./reference/api/api_ref.md): References for the LangGraph Cloud API, the corresponding Python and JS/TS SDKs, the LangGraph CLI, and deployment environment variables. diff --git a/docs/docs/concepts/assistants.md b/docs/docs/concepts/assistants.md index 51f0e10f5..4e33fc694 100644 --- a/docs/docs/concepts/assistants.md +++ b/docs/docs/concepts/assistants.md @@ -25,7 +25,7 @@ Once you've created an assistant, you can save and version it to track changes t 2) The agent configuration options represent parameters that can be changed 3) Assistant versions save and track specific settings of the agent configuration options -For example, let's imagine you have a general writing agent. You have created a general graph architecture that works well for writing. However, there are different types of writing, e.g. blogs vs tweets. In order to get the best performance on each use case, you need to make some minor changes to the models and prompts used. In this setup, you could create an assistant for each use case - one for blog writing and one for tweeting. These would share the same graph structure, but they may use different models and different prompts. Read [this how-to](../cloud/how-tos/assistant_versioning.md) to learn how you can use assistant versioning through both the [Studio](../cloud/how-tos/index.md/#langgraph-studio) and the SDK. +For example, let's imagine you have a general writing agent. You have created a general graph architecture that works well for writing. However, there are different types of writing, e.g. blogs vs tweets. In order to get the best performance on each use case, you need to make some minor changes to the models and prompts used. In this setup, you could create an assistant for each use case - one for blog writing and one for tweeting. These would share the same graph structure, but they may use different models and different prompts. Read [this how-to](../cloud/how-tos/assistant_versioning.md) to learn how you can use assistant versioning through both the [Studio](../concepts/langgraph_studio.md) and the SDK. ![assistant versions](img/assistants.png) diff --git a/docs/docs/concepts/high_level.md b/docs/docs/concepts/high_level.md index 7146fed9c..a5696546b 100644 --- a/docs/docs/concepts/high_level.md +++ b/docs/docs/concepts/high_level.md @@ -55,4 +55,4 @@ Once you've built a graph, you often want to test and debug it. [LangGraph Studi ## Deployment -Once you have confidence in your LangGraph application, many developers want an easy path to deployment. [LangGraph Cloud](../cloud/index.md) is an opinionated, simple way to deploy LangGraph objects from the LangChain team. Of course, you can also use services like [FastAPI](https://fastapi.tiangolo.com/) and call your graph from inside the FastAPI server as you see fit. \ No newline at end of file +Once you have confidence in your LangGraph application, many developers want an easy path to deployment. [LangGraph Platform](../concepts/index.md#langgraph-platform) offers a range of options for deploying LangGraph graphs. \ No newline at end of file diff --git a/docs/mkdocs.yml b/docs/mkdocs.yml index 08f15d8bd..15b716744 100644 --- a/docs/mkdocs.yml +++ b/docs/mkdocs.yml @@ -56,7 +56,11 @@ plugins: - autorefs - redirects: redirect_maps: - 'cloud/concepts/api.md': 'concepts/index.md#langgraph-platform' + 'cloud/index.md': 'concepts/index.md#langgraph-platform' + 'cloud/how-tos/index.md': 'how-tos/index.md#langgraph-platform' + 'cloud/concepts/api.md': 'concepts/langgraph_server.md' + 'cloud/concepts/cloud.md': 'concepts/langgraph_cloud.md' + 'cloud/faq/studio.md': 'concepts/langgraph_studio.md#studio-faqs' - mkdocstrings: handlers: python: From 509291fc1a4b8ec8ea5ee948d772ea07ffb70619 Mon Sep 17 00:00:00 2001 From: William FH <13333726+hinthornw@users.noreply.github.com> Date: Fri, 1 Nov 2024 12:53:48 -0700 Subject: [PATCH 23/60] Bump CLI (#2302) --- libs/cli/pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libs/cli/pyproject.toml b/libs/cli/pyproject.toml index 20fe010d9..05ff3e506 100644 --- a/libs/cli/pyproject.toml +++ b/libs/cli/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "langgraph-cli" -version = "0.1.52" +version = "0.1.53" description = "CLI for interacting with LangGraph API" authors = [] license = "MIT" From 1e3953d1e05907e26a7de8680c58bbd59323c20a Mon Sep 17 00:00:00 2001 From: Nuno Campos Date: Fri, 1 Nov 2024 13:23:10 -0700 Subject: [PATCH 24/60] Test order of update application after Send - updates from inside Send tasks are applied in the order the Sends were created, if when you fan out, and have each task write results to a list with reducer, the final list is in the order you used when triggering --- .../langgraph/checkpoint/postgres/base.py | 2 +- libs/langgraph/tests/test_pregel.py | 6 +-- libs/langgraph/tests/test_pregel_async.py | 49 ++++++++++++++----- 3 files changed, 40 insertions(+), 17 deletions(-) diff --git a/libs/checkpoint-postgres/langgraph/checkpoint/postgres/base.py b/libs/checkpoint-postgres/langgraph/checkpoint/postgres/base.py index e4f930294..5f6a2ab1b 100644 --- a/libs/checkpoint-postgres/langgraph/checkpoint/postgres/base.py +++ b/libs/checkpoint-postgres/langgraph/checkpoint/postgres/base.py @@ -84,7 +84,7 @@ select and cw.checkpoint_id = checkpoints.checkpoint_id ) as pending_writes, ( - select array_agg(array[cw.type::bytea, cw.blob] order by cw.idx) + select array_agg(array[cw.type::bytea, cw.blob] order by cw.task_id, cw.idx) from checkpoint_writes cw where cw.thread_id = checkpoints.thread_id and cw.checkpoint_ns = checkpoints.checkpoint_ns diff --git a/libs/langgraph/tests/test_pregel.py b/libs/langgraph/tests/test_pregel.py index 13bc25236..f8a5b49a8 100644 --- a/libs/langgraph/tests/test_pregel.py +++ b/libs/langgraph/tests/test_pregel.py @@ -8689,14 +8689,14 @@ def test_stream_subgraphs_during_execution( ), (FloatBetween(0.2, 0.3), ((), {"outer_1": {"my_key": " and parallel"}})), ( - FloatBetween(0.5, 0.6), + FloatBetween(0.5, 0.8), ( (AnyStr("inner:"),), {"inner_2": {"my_key": " and there", "my_other_key": "got here"}}, ), ), - (FloatBetween(0.5, 0.6), ((), {"inner": {"my_key": "got here and there"}})), - (FloatBetween(0.5, 0.6), ((), {"outer_2": {"my_key": " and back again"}})), + (FloatBetween(0.5, 0.8), ((), {"inner": {"my_key": "got here and there"}})), + (FloatBetween(0.5, 0.8), ((), {"outer_2": {"my_key": " and back again"}})), ] diff --git a/libs/langgraph/tests/test_pregel_async.py b/libs/langgraph/tests/test_pregel_async.py index fe4bee325..bedbcef4d 100644 --- a/libs/langgraph/tests/test_pregel_async.py +++ b/libs/langgraph/tests/test_pregel_async.py @@ -1,5 +1,6 @@ import asyncio import operator +import random import re import sys import uuid @@ -1922,7 +1923,8 @@ async def test_cond_edge_after_send() -> None: assert await graph.ainvoke(["0"]) == ["0", "1", "2", "2", "3"] -async def test_max_concurrency() -> None: +@pytest.mark.parametrize("checkpointer_name", ALL_CHECKPOINTERS_ASYNC) +async def test_max_concurrency(checkpointer_name: str) -> None: class Node: def __init__(self, name: str): self.name = name @@ -1934,27 +1936,33 @@ async def test_max_concurrency() -> None: self.currently += 1 if self.currently > self.max_currently: self.max_currently = self.currently - await asyncio.sleep(0.1) + await asyncio.sleep(random.random() / 10) self.currently -= 1 - return [self.name] + return [state] + + def one(state): + return ["1"] + + def three(state): + return ["3"] async def send_to_many(state): - return [Send("2", state)] * 100 + return [Send("2", idx) for idx in range(100)] async def route_to_three(state) -> Literal["3"]: return "3" node2 = Node("2") builder = StateGraph(Annotated[list, operator.add]) - builder.add_node(Node("1")) + builder.add_node("1", one) builder.add_node(node2) - builder.add_node(Node("3")) + builder.add_node("3", three) builder.add_edge(START, "1") builder.add_conditional_edges("1", send_to_many) builder.add_conditional_edges("2", route_to_three) graph = builder.compile() - assert await graph.ainvoke(["0"]) == ["0", "1", *(["2"] * 100), "3"] + assert await graph.ainvoke(["0"]) == ["0", "1", *range(100), "3"] assert node2.max_currently == 100 assert node2.currently == 0 node2.max_currently = 0 @@ -1962,16 +1970,24 @@ async def test_max_concurrency() -> None: assert await graph.ainvoke(["0"], {"max_concurrency": 10}) == [ "0", "1", - *(["2"] * 100), + *range(100), "3", ] assert node2.max_currently == 10 assert node2.currently == 0 + async with awith_checkpointer(checkpointer_name) as checkpointer: + graph = builder.compile(checkpointer=checkpointer, interrupt_before=["2"]) + thread1 = {"max_concurrency": 10, "configurable": {"thread_id": "1"}} -async def test_max_concurrency_control() -> None: + assert await graph.ainvoke(["0"], thread1) == ["0", "1"] + assert await graph.ainvoke(None, thread1) == ["0", "1", *range(100), "3"] + + +@pytest.mark.parametrize("checkpointer_name", ALL_CHECKPOINTERS_ASYNC) +async def test_max_concurrency_control(checkpointer_name: str) -> None: async def node1(state) -> Control[Literal["2"]]: - return Control(update_state=["1"], send=[Send("2", state)] * 100) + return Control(update_state=["1"], send=[Send("2", idx) for idx in range(100)]) node2_currently = 0 node2_max_currently = 0 @@ -1984,7 +2000,7 @@ async def test_max_concurrency_control() -> None: await asyncio.sleep(0.1) node2_currently -= 1 - return Control(update_state=["2"], trigger="3") + return Control(update_state=[state], trigger="3") async def node3(state) -> Literal["3"]: return ["3"] @@ -2013,7 +2029,7 @@ graph TD; """ ) - assert await graph.ainvoke(["0"], debug=True) == ["0", "1", *(["2"] * 100), "3"] + assert await graph.ainvoke(["0"], debug=True) == ["0", "1", *range(100), "3"] assert node2_max_currently == 100 assert node2_currently == 0 node2_max_currently = 0 @@ -2021,12 +2037,19 @@ graph TD; assert await graph.ainvoke(["0"], {"max_concurrency": 10}) == [ "0", "1", - *(["2"] * 100), + *range(100), "3", ] assert node2_max_currently == 10 assert node2_currently == 0 + async with awith_checkpointer(checkpointer_name) as checkpointer: + graph = builder.compile(checkpointer=checkpointer, interrupt_before=["2"]) + thread1 = {"max_concurrency": 10, "configurable": {"thread_id": "1"}} + + assert await graph.ainvoke(["0"], thread1) == ["0", "1"] + assert await graph.ainvoke(None, thread1) == ["0", "1", *range(100), "3"] + @pytest.mark.parametrize("checkpointer_name", ALL_CHECKPOINTERS_ASYNC) async def test_invoke_checkpoint_three( From ecb584cbe0a8b4a15ed1b7649da58cc199cffd74 Mon Sep 17 00:00:00 2001 From: Andrew Nguonly Date: Fri, 1 Nov 2024 13:32:49 -0700 Subject: [PATCH 25/60] sdk: Add `action` query param to cancel Run methods (#2284) --- libs/sdk-js/src/client.ts | 4 ++++ libs/sdk-js/src/schema.ts | 2 ++ libs/sdk-py/langgraph_sdk/client.py | 33 +++++++++++++++++++++++------ libs/sdk-py/langgraph_sdk/schema.py | 7 ++++++ 4 files changed, 40 insertions(+), 6 deletions(-) diff --git a/libs/sdk-js/src/client.ts b/libs/sdk-js/src/client.ts index 32e75017b..6139e8c41 100644 --- a/libs/sdk-js/src/client.ts +++ b/libs/sdk-js/src/client.ts @@ -1,6 +1,7 @@ import { Assistant, AssistantGraph, + CancelAction, Config, DefaultValues, GraphSchema, @@ -928,17 +929,20 @@ export class RunsClient extends BaseClient { * @param threadId The ID of the thread. * @param runId The ID of the run. * @param wait Whether to block when canceling + * @param action Action to take when cancelling the run. Possible values are `interrupt` or `rollback`. Default is `interrupt`. * @returns */ async cancel( threadId: string, runId: string, wait: boolean = false, + action: CancelAction = "interrupt", ): Promise { return this.fetch(`/threads/${threadId}/runs/${runId}/cancel`, { method: "POST", params: { wait: wait ? "1" : "0", + action: action, }, }); } diff --git a/libs/sdk-js/src/schema.ts b/libs/sdk-js/src/schema.ts index c2de251f6..86a668d01 100644 --- a/libs/sdk-js/src/schema.ts +++ b/libs/sdk-js/src/schema.ts @@ -14,6 +14,8 @@ type ThreadStatus = "idle" | "busy" | "interrupted" | "error"; type MultitaskStrategy = "reject" | "interrupt" | "rollback" | "enqueue"; +export type CancelAction = "interrupt" | "rollback"; + export interface Config { /** * Tags for this call and any sub-calls (eg. a Chain calling an LLM). diff --git a/libs/sdk-py/langgraph_sdk/client.py b/libs/sdk-py/langgraph_sdk/client.py index 8118d6a5c..a057d452f 100644 --- a/libs/sdk-py/langgraph_sdk/client.py +++ b/libs/sdk-py/langgraph_sdk/client.py @@ -34,6 +34,7 @@ from langgraph_sdk.schema import ( All, Assistant, AssistantVersion, + CancelAction, Checkpoint, Config, Cron, @@ -1712,13 +1713,22 @@ class RunsClient: return await self.http.get(f"/threads/{thread_id}/runs/{run_id}") - async def cancel(self, thread_id: str, run_id: str, *, wait: bool = False) -> None: + async def cancel( + self, + thread_id: str, + run_id: str, + *, + wait: bool = False, + action: CancelAction = "interrupt", + ) -> None: """Get a run. Args: thread_id: The thread ID to cancel. run_id: The run ID to cancek. wait: Whether to wait until run has completed. + action: Action to take when cancelling the run. Possible values + are `interrupt` or `rollback`. Default is `interrupt`. Returns: None @@ -1728,12 +1738,13 @@ class RunsClient: await client.runs.cancel( thread_id="thread_id_to_cancel", run_id="run_id_to_cancel", - wait=True + wait=True, + action="interrupt" ) """ # noqa: E501 return await self.http.post( - f"/threads/{thread_id}/runs/{run_id}/cancel?wait={1 if wait else 0}", + f"/threads/{thread_id}/runs/{run_id}/cancel?wait={1 if wait else 0}&action={action}", json=None, ) @@ -3792,13 +3803,22 @@ class SyncRunsClient: return self.http.get(f"/threads/{thread_id}/runs/{run_id}") - def cancel(self, thread_id: str, run_id: str, *, wait: bool = False) -> None: + def cancel( + self, + thread_id: str, + run_id: str, + *, + wait: bool = False, + action: CancelAction = "interrupt", + ) -> None: """Get a run. Args: thread_id: The thread ID to cancel. run_id: The run ID to cancek. wait: Whether to wait until run has completed. + action: Action to take when cancelling the run. Possible values + are `interrupt` or `rollback`. Default is `interrupt`. Returns: None @@ -3808,12 +3828,13 @@ class SyncRunsClient: client.runs.cancel( thread_id="thread_id_to_cancel", run_id="run_id_to_cancel", - wait=True + wait=True, + action="interrupt" ) """ # noqa: E501 return self.http.post( - f"/threads/{thread_id}/runs/{run_id}/cancel?wait={1 if wait else 0}", + f"/threads/{thread_id}/runs/{run_id}/cancel?wait={1 if wait else 0}&action={action}", json=None, ) diff --git a/libs/sdk-py/langgraph_sdk/schema.py b/libs/sdk-py/langgraph_sdk/schema.py index 43e218669..9583a1c1b 100644 --- a/libs/sdk-py/langgraph_sdk/schema.py +++ b/libs/sdk-py/langgraph_sdk/schema.py @@ -78,6 +78,13 @@ Specifies behavior if the thread doesn't exist: - "reject": Reject the operation if the thread doesn't exist. """ +CancelAction = Literal["interrupt", "rollback"] +""" +Action to take when cancelling the run. +- "interrupt": Simply cancel the run. +- "rollback": Cancel the run. Then delete the run and associated checkpoints. +""" + class Config(TypedDict, total=False): """Configuration options for a call.""" From 782b9a79035fdb03fdc11acd7e3c7e2347e3543f Mon Sep 17 00:00:00 2001 From: Vadym Barda Date: Fri, 1 Nov 2024 17:40:53 -0400 Subject: [PATCH 26/60] langgraph: add message list validation to create_react_agent + a troubleshooting guide (#2182) --- docs/docs/how-tos/index.md | 3 +- .../errors/INVALID_CHAT_HISTORY.md | 30 +++++++++ docs/docs/troubleshooting/errors/index.md | 1 + libs/langgraph/langgraph/errors.py | 1 + .../langgraph/prebuilt/chat_agent_executor.py | 34 ++++++++++ libs/langgraph/tests/test_prebuilt.py | 66 +++++++++++++++++++ 6 files changed, 134 insertions(+), 1 deletion(-) create mode 100644 docs/docs/troubleshooting/errors/INVALID_CHAT_HISTORY.md diff --git a/docs/docs/how-tos/index.md b/docs/docs/how-tos/index.md index c19007b2c..4689ebb0a 100644 --- a/docs/docs/how-tos/index.md +++ b/docs/docs/how-tos/index.md @@ -220,11 +220,12 @@ LangGraph Studio is a built-in UI for visualizing, testing, and debugging your a ## Troubleshooting -The [Error Reference](../troubleshooting/errors/index.md) page contains guides around resolving common errors you may find while building with LangGraph. Errors referenced below will have an `lc_error_code` property corresponding to one of the below codes when they are thrown in code. +These are the guides for resolving common errors you may find while building with LangGraph. Errors referenced below will have an `lc_error_code` property corresponding to one of the below codes when they are thrown in code. - [GRAPH_RECURSION_LIMIT](../troubleshooting/errors/GRAPH_RECURSION_LIMIT.md) - [INVALID_CONCURRENT_GRAPH_UPDATE](../troubleshooting/errors/INVALID_CONCURRENT_GRAPH_UPDATE.md) - [INVALID_GRAPH_NODE_RETURN_VALUE](../troubleshooting/errors/INVALID_GRAPH_NODE_RETURN_VALUE.md) - [MULTIPLE_SUBGRAPHS](../troubleshooting/errors/MULTIPLE_SUBGRAPHS.md) +- [INVALID_CHAT_HISTORY](../troubleshooting/errors/INVALID_CHAT_HISTORY.md) diff --git a/docs/docs/troubleshooting/errors/INVALID_CHAT_HISTORY.md b/docs/docs/troubleshooting/errors/INVALID_CHAT_HISTORY.md new file mode 100644 index 000000000..121152ba0 --- /dev/null +++ b/docs/docs/troubleshooting/errors/INVALID_CHAT_HISTORY.md @@ -0,0 +1,30 @@ +# INVALID_CHAT_HISTORY + +This error is raised in the prebuilt [create_react_agent][langgraph.prebuilt.chat_agent_executor.create_react_agent] when the `call_model` graph node receives a malformed list of messages. Specifically, it is malformed when there are `AIMessages` with `tool_calls` (LLM requesting to call a tool) that do not have a corresponding `ToolMessage` (result of a tool invocation to return to the LLM). + +There could be a few reasons you're seeing this error: + +1. You manually passed a malformed list of messages when invoking the graph, e.g. `graph.invoke({'messages': [AIMessage(..., tool_calls=[...])]})` +2. The graph was interrupted before receiving updates from the `tools` node (i.e. a list of ToolMessages) +and you invoked it with a an input that is not None or a ToolMessage, +e.g. `graph.invoke({'messages': [HumanMessage(...)]}, config)`. + This interrupt could have been triggered in one of the following ways: + - You manually set `interrupt_before = ['tools']` in `create_react_agent` + - One of the tools raised an error that wasn't handled by the [ToolNode][langgraph.prebuilt.tool_node.ToolNode] (`"tools"`) + +## Troubleshooting + +To resolve this, you can do one of the following: + +1. Don't invoke the graph with a malformed list of messages +2. In case of an interrupt (manual or due to an error) you can: + + - provide ToolMessages that match existing tool calls and call `graph.invoke({'messages': [ToolMessage(...)]})`. + **NOTE**: this will append the messages to the history and run the graph from the START node. + - manually update the state and resume the graph from the interrupt: + + 1. get the list of most recent messages from the graph state with `graph.get_state(config)` + 2. modify the list of messages to either remove unanswered tool calls from AIMessages +or add ToolMessages with tool_call_ids that match unanswered tool calls + 3. call `graph.update_state(config, {'messages': ...})` with the modified list of messages + 4. resume the graph, e.g. call `graph.invoke(None, config)` diff --git a/docs/docs/troubleshooting/errors/index.md b/docs/docs/troubleshooting/errors/index.md index 9a0baab23..c8a21d5d5 100644 --- a/docs/docs/troubleshooting/errors/index.md +++ b/docs/docs/troubleshooting/errors/index.md @@ -7,3 +7,4 @@ Errors referenced below will have an `lc_error_code` property corresponding to o - [INVALID_CONCURRENT_GRAPH_UPDATE](./INVALID_CONCURRENT_GRAPH_UPDATE.md) - [INVALID_GRAPH_NODE_RETURN_VALUE](./INVALID_GRAPH_NODE_RETURN_VALUE.md) - [MULTIPLE_SUBGRAPHS](./MULTIPLE_SUBGRAPHS.md) +- [INVALID_CHAT_HISTORY](./INVALID_CHAT_HISTORY.md) diff --git a/libs/langgraph/langgraph/errors.py b/libs/langgraph/langgraph/errors.py index 114232514..2e3d13120 100644 --- a/libs/langgraph/langgraph/errors.py +++ b/libs/langgraph/langgraph/errors.py @@ -12,6 +12,7 @@ class ErrorCode(Enum): INVALID_CONCURRENT_GRAPH_UPDATE = "INVALID_CONCURRENT_GRAPH_UPDATE" INVALID_GRAPH_NODE_RETURN_VALUE = "INVALID_GRAPH_NODE_RETURN_VALUE" MULTIPLE_SUBGRAPHS = "MULTIPLE_SUBGRAPHS" + INVALID_CHAT_HISTORY = "INVALID_CHAT_HISTORY" def create_error_message(*, message: str, error_code: ErrorCode) -> str: diff --git a/libs/langgraph/langgraph/prebuilt/chat_agent_executor.py b/libs/langgraph/langgraph/prebuilt/chat_agent_executor.py index 5f739c0a6..fc812ccbc 100644 --- a/libs/langgraph/langgraph/prebuilt/chat_agent_executor.py +++ b/libs/langgraph/langgraph/prebuilt/chat_agent_executor.py @@ -11,6 +11,7 @@ from langchain_core.tools import BaseTool from typing_extensions import Annotated, TypedDict from langgraph._api.deprecation import deprecated_parameter +from langgraph.errors import ErrorCode, create_error_message from langgraph.graph import StateGraph from langgraph.graph.graph import CompiledGraph from langgraph.graph.message import add_messages @@ -161,6 +162,37 @@ def _should_bind_tools(model: LanguageModelLike, tools: Sequence[BaseTool]) -> b return False +def _validate_chat_history( + messages: Sequence[BaseMessage], +) -> None: + """Validate that all tool calls in AIMessages have a corresponding ToolMessage.""" + all_tool_calls = [ + tool_call + for message in messages + if isinstance(message, AIMessage) + for tool_call in message.tool_calls + ] + tool_call_ids_with_results = { + message.tool_call_id for message in messages if isinstance(message, ToolMessage) + } + tool_calls_without_results = [ + tool_call + for tool_call in all_tool_calls + if tool_call["id"] not in tool_call_ids_with_results + ] + if not tool_calls_without_results: + return + + error_message = create_error_message( + message="Found AIMessages with tool_calls that do not have a corresponding ToolMessage. " + f"Here are the first few of those tool calls: {tool_calls_without_results[:3]}.\n\n" + "Every tool call (LLM requesting to call a tool) in the message history MUST have a corresponding ToolMessage " + "(result of a tool invocation to return to the LLM) - this is required by most LLM providers.", + error_code=ErrorCode.INVALID_CHAT_HISTORY, + ) + raise ValueError(error_message) + + @deprecated_parameter("messages_modifier", "0.1.9", "state_modifier", removal="0.3.0") def create_react_agent( model: LanguageModelLike, @@ -530,6 +562,7 @@ def create_react_agent( # Define the function that calls the model def call_model(state: AgentState, config: RunnableConfig) -> AgentState: + _validate_chat_history(state["messages"]) response = model_runnable.invoke(state, config) has_tool_calls = isinstance(response, AIMessage) and response.tool_calls all_tools_return_direct = ( @@ -566,6 +599,7 @@ def create_react_agent( return {"messages": [response]} async def acall_model(state: AgentState, config: RunnableConfig) -> AgentState: + _validate_chat_history(state["messages"]) response = await model_runnable.ainvoke(state, config) has_tool_calls = isinstance(response, AIMessage) and response.tool_calls all_tools_return_direct = ( diff --git a/libs/langgraph/tests/test_prebuilt.py b/libs/langgraph/tests/test_prebuilt.py index c77a2cac4..1ea506381 100644 --- a/libs/langgraph/tests/test_prebuilt.py +++ b/libs/langgraph/tests/test_prebuilt.py @@ -46,6 +46,7 @@ from langgraph.prebuilt import ( create_react_agent, tools_condition, ) +from langgraph.prebuilt.chat_agent_executor import _validate_chat_history from langgraph.prebuilt.tool_node import ( TOOL_CALL_ERROR_TEMPLATE, InjectedState, @@ -378,6 +379,71 @@ def test_model_with_tools(tool_style: str): create_react_agent(model.bind_tools([tool1]), [tool2]) +def test__validate_messages(): + # empty input + _validate_chat_history([]) + + # single human message + _validate_chat_history( + [ + HumanMessage(content="What's the weather?"), + ] + ) + + # human + AI + _validate_chat_history( + [ + HumanMessage(content="What's the weather?"), + AIMessage(content="The weather is sunny and 75°F."), + ] + ) + + # Answered tool calls + _validate_chat_history( + [ + HumanMessage(content="What's the weather?"), + AIMessage( + content="Let me check that for you.", + tool_calls=[{"id": "call1", "name": "get_weather", "args": {}}], + ), + ToolMessage(content="Sunny, 75°F", tool_call_id="call1"), + AIMessage(content="The weather is sunny and 75°F."), + ] + ) + + # Unanswered tool calls + with pytest.raises(ValueError): + _validate_chat_history( + [ + AIMessage( + content="I'll check that for you.", + tool_calls=[ + {"id": "call1", "name": "get_weather", "args": {}}, + {"id": "call2", "name": "get_time", "args": {}}, + ], + ) + ] + ) + + with pytest.raises(ValueError): + _validate_chat_history( + [ + HumanMessage(content="What's the weather and time?"), + AIMessage( + content="I'll check that for you.", + tool_calls=[ + {"id": "call1", "name": "get_weather", "args": {}}, + {"id": "call2", "name": "get_time", "args": {}}, + ], + ), + ToolMessage(content="Sunny, 75°F", tool_call_id="call1"), + AIMessage( + content="The weather is sunny and 75°F. Let me check the time." + ), + ] + ) + + def test__infer_handled_types() -> None: def handle(e): # type: ignore return "" From 805f43753429161d3d61adfcf80aa666cd9eaec0 Mon Sep 17 00:00:00 2001 From: Vadym Barda Date: Fri, 1 Nov 2024 17:52:09 -0400 Subject: [PATCH 27/60] docs: fix link in the tutorial (#2305) --- docs/docs/tutorials/introduction.ipynb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/docs/tutorials/introduction.ipynb b/docs/docs/tutorials/introduction.ipynb index 7c95ec299..ed6510a82 100644 --- a/docs/docs/tutorials/introduction.ipynb +++ b/docs/docs/tutorials/introduction.ipynb @@ -127,7 +127,7 @@ "