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",
"
__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", *range(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", + *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( @@ -2650,6 +3567,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) @@ -2699,6 +3617,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) @@ -2823,6 +3742,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) @@ -2889,6 +3809,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) @@ -2938,6 +3859,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) @@ -3062,6 +3984,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) @@ -3128,6 +4051,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) @@ -3548,6 +4472,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) @@ -3594,6 +4519,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) @@ -3674,6 +4600,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) @@ -3733,6 +4660,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) @@ -3778,6 +4706,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) @@ -3856,6 +4785,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) @@ -4478,6 +5408,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"], @@ -4550,6 +5482,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) @@ -4603,6 +5536,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) @@ -4712,6 +5646,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) @@ -4761,6 +5696,303 @@ 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) + ][-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": "2", + }, + 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": "2", + }, + 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": "2", + }, + 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": "2", }, parent_config=[ c async for c in app_w_interrupt.checkpointer.alist(config, limit=2) @@ -5021,6 +6253,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) @@ -5070,6 +6303,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) @@ -5155,6 +6389,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) @@ -5198,6 +6433,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) @@ -5500,12 +6736,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( @@ -5516,7 +6756,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, @@ -5539,6 +6786,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) @@ -5559,7 +6808,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, @@ -5582,6 +6838,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) @@ -5602,7 +6860,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, @@ -5622,6 +6887,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) @@ -5645,6 +6912,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) @@ -5713,6 +6982,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__"], @@ -5751,6 +7021,7 @@ async def test_branch_then(checkpointer_name: str) -> None: "source": "loop", "step": 0, "writes": None, + "thread_id": "10", }, "parent_config": { "tags": [], @@ -5822,6 +7093,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": [], @@ -5893,6 +7165,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": [], @@ -5964,6 +7237,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": [], @@ -6021,6 +7295,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__"], @@ -6059,6 +7334,7 @@ async def test_branch_then(checkpointer_name: str) -> None: "source": "loop", "step": 0, "writes": None, + "thread_id": "11", }, "parent_config": { "tags": [], @@ -6130,6 +7406,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": [], @@ -6167,6 +7444,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) @@ -6190,6 +7468,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) @@ -6215,6 +7494,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) @@ -6238,6 +7518,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) @@ -6271,6 +7552,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) @@ -6294,6 +7576,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) @@ -6319,6 +7602,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) @@ -6342,6 +7626,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) @@ -6365,6 +7650,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, ) @@ -6387,6 +7673,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, ) @@ -6408,6 +7695,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) @@ -6759,6 +8047,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={ @@ -7724,6 +9013,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={ @@ -7750,9 +9040,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",), @@ -7778,6 +9067,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={ @@ -7806,6 +9102,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={ @@ -7847,6 +9144,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={ @@ -7880,6 +9178,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={ @@ -7913,6 +9212,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, @@ -7932,7 +9232,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()} ), } }, @@ -7946,6 +9246,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={ @@ -7954,13 +9261,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"}, @@ -7971,7 +9276,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()} ), } }, @@ -7980,6 +9285,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={ @@ -7988,15 +9300,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", @@ -8013,7 +9325,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()} ), } }, @@ -8022,14 +9334,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"}, ), ), @@ -8057,6 +9376,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={ @@ -8090,6 +9410,7 @@ async def test_nested_graph_state(checkpointer_name: str) -> None: } }, "step": 3, + "thread_id": "1", }, created_at=AnyStr(), parent_config={ @@ -8123,6 +9444,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={ @@ -8162,6 +9484,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={ @@ -8195,6 +9518,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={ @@ -8228,6 +9552,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, @@ -8333,6 +9658,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={ @@ -8367,9 +9693,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={ @@ -8377,6 +9700,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={ @@ -8384,9 +9708,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] @@ -8426,6 +9747,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={ @@ -8498,6 +9826,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={ @@ -8535,6 +9873,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={ @@ -8563,6 +9908,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={ @@ -8610,6 +9956,7 @@ async def test_doubly_nested_graph_state(checkpointer_name: str) -> None: } }, "step": 3, + "thread_id": "1", }, created_at=AnyStr(), parent_config={ @@ -8646,6 +9993,7 @@ async def test_doubly_nested_graph_state(checkpointer_name: str) -> None: } }, "step": 3, + "thread_id": "1", }, created_at=AnyStr(), parent_config={ @@ -8671,6 +10019,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={ @@ -8714,6 +10063,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={ @@ -8739,6 +10089,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={ @@ -8769,6 +10120,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, @@ -8803,6 +10155,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={ @@ -8835,6 +10194,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={ @@ -8880,6 +10246,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, @@ -8927,6 +10300,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={ @@ -8972,6 +10355,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={ @@ -9024,6 +10417,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={ @@ -9076,6 +10479,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, @@ -9190,7 +10603,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": { @@ -9237,6 +10656,7 @@ async def test_send_to_nested_graphs(checkpointer_name: str) -> None: ] }, "step": 1, + "thread_id": "1", }, created_at=AnyStr(), parent_config={ @@ -9279,6 +10699,7 @@ async def test_send_to_nested_graphs(checkpointer_name: str) -> None: ] }, "step": 1, + "thread_id": "1", }, created_at=AnyStr(), parent_config={ @@ -9325,7 +10746,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": { @@ -9358,6 +10785,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, @@ -9535,6 +10963,7 @@ async def test_weather_subgraph( "writes": {"router_node": {"route": "weather"}}, "step": 1, "parents": {}, + "thread_id": "1", }, created_at=AnyStr(), parent_config={ @@ -9626,6 +11055,7 @@ async def test_weather_subgraph( "writes": {"router_node": {"route": "weather"}}, "step": 1, "parents": {}, + "thread_id": "14", }, created_at=AnyStr(), parent_config={ @@ -9666,6 +11096,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={ @@ -9716,6 +11155,7 @@ async def test_weather_subgraph( "writes": {"router_node": {"route": "weather"}}, "step": 1, "parents": {}, + "thread_id": "14", }, created_at=AnyStr(), parent_config={ @@ -9753,8 +11193,8 @@ async def test_weather_subgraph( } }, metadata={ - "source": "update", "step": 2, + "source": "update", "writes": { "weather_node": { "messages": [ @@ -9763,6 +11203,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 83e5f913b..70857ed61 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,15 @@ 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="__end__", data="__end__", metadata=None ), - "__end__": DrawableNode(id="__end__", name="", 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 +103,7 @@ async def test_aget_graph(): "type": "runnable", "data": { "id": ["langgraph", "utils", "RunnableCallable"], - "name": "agent", + "name": "agent_1", }, }, ], @@ -118,13 +120,15 @@ 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="__end__", data="__end__", metadata=None ), - "__end__": DrawableNode(id="__end__", name="", 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, ), } 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", diff --git a/libs/sdk-js/src/client.ts b/libs/sdk-js/src/client.ts index 04b6537e0..e8ce15f44 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, @@ -935,17 +936,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