diff --git a/langgraph/graph/graph.py b/langgraph/graph/graph.py index 7291b4e15..7580eb797 100644 --- a/langgraph/graph/graph.py +++ b/langgraph/graph/graph.py @@ -5,11 +5,15 @@ from typing import ( Awaitable, Callable, Dict, + Literal, NamedTuple, Optional, Sequence, Union, cast, + get_args, + get_origin, + get_type_hints, ) from langchain_core.runnables import Runnable @@ -142,7 +146,7 @@ class Graph: Callable[..., Awaitable[Union[str, list[str]]]], Runnable[Any, Union[str, list[str]]], ], - path_map: Optional[dict[str, str]] = None, + path_map: Optional[Union[dict[str, str], list[str]]] = None, then: Optional[str] = None, ) -> None: """Add a conditional edge from the starting node to any number of destination nodes. @@ -166,6 +170,14 @@ class Graph: "Adding an edge to a graph that has already been compiled. This will " "not be reflected in the compiled graph." ) + # coerce path_map to a dictionary + if isinstance(path_map, dict): + pass + elif isinstance(path_map, list): + path_map = {name: name for name in path_map} + elif rtn_type := get_type_hints(path).get("return"): + if get_origin(rtn_type) is Literal: + path_map = {name: name for name in get_args(rtn_type)} # find a name for the condition path = coerce_to_runnable(path, name=None, trace=True) name = path.name or "condition" diff --git a/tests/__snapshots__/test_pregel.ambr b/tests/__snapshots__/test_pregel.ambr index baef3d081..18ded1bc0 100644 --- a/tests/__snapshots__/test_pregel.ambr +++ b/tests/__snapshots__/test_pregel.ambr @@ -1541,76 +1541,76 @@ # --- # name: test_in_one_fan_out_state_graph_waiting_edge_via_branch[end_of_run] ''' - +-----------+ - | __start__ | - +-----------+ - * - * - * - +---------------+ - | rewrite_query | - +---------------+ - *** *** - * * - ** ** - +--------------+ +-----------+ - | analyzer_one | | condition | - +--------------+ +-----------+ - * . - * . - * . - +---------------+ +---------------+ - | retriever_one | | retriever_two | - +---------------+ +---------------+ - *** *** - * * - ** ** - +----+ - | qa | - +----+ - * - * - * - +---------+ - | __end__ | - +---------+ + +-----------+ + | __start__ | + +-----------+ + * + * + * + +---------------+ + | rewrite_query | + +---------------+ + *** *** + * * + ** ** + +--------------+ +--------------------+ + | analyzer_one | | rewrite_query_then | + +--------------+ +--------------------+ + * . + * . + * . + +---------------+ +---------------+ + | retriever_one | | retriever_two | + +---------------+ +---------------+ + *** *** + * * + ** ** + +----+ + | qa | + +----+ + * + * + * + +---------+ + | __end__ | + +---------+ ''' # --- # name: test_in_one_fan_out_state_graph_waiting_edge_via_branch[end_of_step] ''' - +-----------+ - | __start__ | - +-----------+ - * - * - * - +---------------+ - | rewrite_query | - +---------------+ - *** *** - * * - ** ** - +--------------+ +-----------+ - | analyzer_one | | condition | - +--------------+ +-----------+ - * . - * . - * . - +---------------+ +---------------+ - | retriever_one | | retriever_two | - +---------------+ +---------------+ - *** *** - * * - ** ** - +----+ - | qa | - +----+ - * - * - * - +---------+ - | __end__ | - +---------+ + +-----------+ + | __start__ | + +-----------+ + * + * + * + +---------------+ + | rewrite_query | + +---------------+ + *** *** + * * + ** ** + +--------------+ +--------------------+ + | analyzer_one | | rewrite_query_then | + +--------------+ +--------------------+ + * . + * . + * . + +---------------+ +---------------+ + | retriever_one | | retriever_two | + +---------------+ +---------------+ + *** *** + * * + ** ** + +----+ + | qa | + +----+ + * + * + * + +---------+ + | __end__ | + +---------+ ''' # --- # name: test_message_graph[end_of_run] diff --git a/tests/test_pregel.py b/tests/test_pregel.py index 631f666b6..550dcea64 100644 --- a/tests/test_pregel.py +++ b/tests/test_pregel.py @@ -4,7 +4,7 @@ import time import warnings from concurrent.futures import ThreadPoolExecutor from contextlib import contextmanager -from typing import Annotated, Any, Generator, Optional, TypedDict, Union +from typing import Annotated, Any, Generator, Literal, Optional, TypedDict, Union import pytest from langchain_core.runnables import RunnableLambda, RunnablePassthrough @@ -3115,6 +3115,7 @@ def test_branch_then(snapshot: SnapshotAssertion, checkpoint_at: CheckpointAt) - invalid_graph.add_conditional_edges( source="prepare", path=lambda s: "tool_two_slow" if s["market"] == "DE" else "tool_two_fast", + path_map=["tool_two_slow", "tool_two_fast"], ) invalid_graph.add_node("prepare", lambda s: {"my_key": " prepared"}) invalid_graph.add_node("tool_two_slow", lambda s: {"my_key": " slow"}) @@ -3387,6 +3388,9 @@ def test_in_one_fan_out_state_graph_waiting_edge_via_branch( def qa(data: State) -> State: return {"answer": ",".join(data["docs"])} + def rewrite_query_then(data: State) -> Literal["retriever_two"]: + return "retriever_two" + workflow = StateGraph(State) workflow.add_node("rewrite_query", rewrite_query) @@ -3398,9 +3402,7 @@ def test_in_one_fan_out_state_graph_waiting_edge_via_branch( workflow.set_entry_point("rewrite_query") workflow.add_edge("rewrite_query", "analyzer_one") workflow.add_edge("analyzer_one", "retriever_one") - workflow.add_conditional_edges( - "rewrite_query", lambda _: "retriever_two", {"retriever_two": "retriever_two"} - ) + workflow.add_conditional_edges("rewrite_query", rewrite_query_then) workflow.add_edge(["retriever_one", "retriever_two"], "qa") workflow.set_finish_point("qa")