fix(langgraph): missing conditional edge on get_graph() (#4458)

Co-authored-by: Nuno Campos <nuno@langchain.dev>
This commit is contained in:
David Duong
2025-04-29 19:49:48 +00:00
committed by GitHub
co-authored by Nuno Campos
parent dbceb3c2e6
commit d86d0a9311
4 changed files with 135 additions and 38 deletions
+10 -3
View File
@@ -847,7 +847,9 @@ class CompiledStateGraph(CompiledGraph):
) -> Sequence[Union[ChannelWriteEntry, Send]]:
writes = [
(
ChannelWriteEntry(CHANNEL_BRANCH_TO.format(p), None)
ChannelWriteEntry(
p if p == END else CHANNEL_BRANCH_TO.format(p), None
)
if not isinstance(p, Send)
else p
)
@@ -1067,9 +1069,14 @@ def _control_static(
ends: Union[tuple[str, ...], dict[str, str]],
) -> Sequence[tuple[str, Any, Optional[str]]]:
if isinstance(ends, dict):
return [(CHANNEL_BRANCH_TO.format(k), None, label) for k, label in ends.items()]
return [
(k if k == END else CHANNEL_BRANCH_TO.format(k), None, label)
for k, label in ends.items()
]
else:
return [(CHANNEL_BRANCH_TO.format(e), None, None) for e in ends]
return [
(e if e == END else CHANNEL_BRANCH_TO.format(e), None, None) for e in ends
]
def _get_root(input: Any) -> Optional[Sequence[tuple[str, Any]]]:
@@ -198,6 +198,12 @@
"source": "__start__",
"target": "agent"
},
{
"source": "agent",
"target": "__end__",
"data": "exit",
"conditional": true
},
{
"source": "agent",
"target": "tools",
@@ -207,11 +213,6 @@
{
"source": "tools",
"target": "agent"
},
{
"source": "agent",
"target": "__end__",
"conditional": true
}
]
}
@@ -221,9 +222,9 @@
'''
graph TD;
__start__ --> agent;
agent -. &nbsp;exit&nbsp; .-> __end__;
agent -. &nbsp;continue&nbsp; .-> tools;
tools --> agent;
agent -.-> __end__;
'''
# ---
@@ -284,6 +285,12 @@
"source": "__start__",
"target": "agent"
},
{
"source": "agent",
"target": "__end__",
"data": "end",
"conditional": true
},
{
"source": "agent",
"target": "tools",
@@ -293,11 +300,6 @@
{
"source": "tools",
"target": "agent"
},
{
"source": "agent",
"target": "__end__",
"conditional": true
}
]
}
@@ -307,9 +309,9 @@
'''
graph TD;
__start__ --> agent;
agent -. &nbsp;end&nbsp; .-> __end__;
agent -. &nbsp;continue&nbsp; .-> tools;
tools --> agent;
agent -.-> __end__;
'''
# ---
@@ -371,6 +373,11 @@
"source": "__start__",
"target": "agent"
},
{
"source": "agent",
"target": "__end__",
"conditional": true
},
{
"source": "agent",
"target": "tools",
@@ -379,11 +386,6 @@
{
"source": "tools",
"target": "agent"
},
{
"source": "agent",
"target": "__end__",
"conditional": true
}
]
}
@@ -393,9 +395,9 @@
'''
graph TD;
__start__ --> agent;
agent -.-> __end__;
agent -.-> tools;
tools --> agent;
agent -.-> __end__;
'''
# ---
@@ -156,7 +156,8 @@
},
{
"source": "left",
"target": "__end__"
"target": "__end__",
"conditional": true
},
{
"source": "right",
@@ -171,7 +172,7 @@
graph TD;
__start__ -. &nbsp;go-left&nbsp; .-> left;
__start__ -. &nbsp;go-right&nbsp; .-> right;
left --> __end__;
left -.-> __end__;
right --> __end__;
'''
@@ -736,8 +737,10 @@
Call_Tool -.-> Researcher;
Chart_Generator -. &nbsp;call_tool&nbsp; .-> Call_Tool;
Chart_Generator -. &nbsp;continue&nbsp; .-> Researcher;
Chart_Generator -. &nbsp;end&nbsp; .-> __end__;
Researcher -. &nbsp;call_tool&nbsp; .-> Call_Tool;
Researcher -. &nbsp;continue&nbsp; .-> Chart_Generator;
Researcher -. &nbsp;end&nbsp; .-> __end__;
__start__ --> Researcher;
Researcher -. &nbsp;redo&nbsp; .-> Researcher;
@@ -777,26 +780,26 @@
gp_one(gp_one)
__end__([<p>__end__</p>]):::last
__start__ --> gp_one;
gp_one -. &nbsp;1&nbsp; .-> __end__;
gp_one -. &nbsp;0&nbsp; .-> gp_two___start__;
gp_two___end__ --> gp_one;
gp_one -.-> __end__;
subgraph gp_two
gp_two___start__(<p>__start__</p>)
gp_two_p_one(p_one)
gp_two___end__(<p>__end__</p>)
gp_two___start__ --> gp_two_p_one;
gp_two_p_one -. &nbsp;1&nbsp; .-> gp_two___end__;
gp_two_p_one -. &nbsp;0&nbsp; .-> gp_two_p_two___start__;
gp_two_p_two___end__ --> gp_two_p_one;
gp_two_p_one -.-> gp_two___end__;
subgraph p_two
gp_two_p_two___start__(<p>__start__</p>)
gp_two_p_two_c_one(c_one)
gp_two_p_two_c_two(c_two)
gp_two_p_two___end__(<p>__end__</p>)
gp_two_p_two___start__ --> gp_two_p_two_c_one;
gp_two_p_two_c_one -. &nbsp;1&nbsp; .-> gp_two_p_two___end__;
gp_two_p_two_c_one -. &nbsp;0&nbsp; .-> gp_two_p_two_c_two;
gp_two_p_two_c_two --> gp_two_p_two_c_one;
gp_two_p_two_c_one -.-> gp_two_p_two___end__;
end
end
classDef default fill:#f2f0ff,line-height:1.2
@@ -817,18 +820,18 @@
p_one(p_one)
__end__([<p>__end__</p>]):::last
__start__ --> p_one;
p_one -. &nbsp;1&nbsp; .-> __end__;
p_one -. &nbsp;0&nbsp; .-> p_two___start__;
p_two___end__ --> p_one;
p_one -.-> __end__;
subgraph p_two
p_two___start__(<p>__start__</p>)
p_two_c_one(c_one)
p_two_c_two(c_two)
p_two___end__(<p>__end__</p>)
p_two___start__ --> p_two_c_one;
p_two_c_one -. &nbsp;1&nbsp; .-> p_two___end__;
p_two_c_one -. &nbsp;0&nbsp; .-> p_two_c_two;
p_two_c_two --> p_two_c_one;
p_two_c_one -.-> p_two___end__;
end
classDef default fill:#f2f0ff,line-height:1.2
classDef first fill-opacity:0
@@ -843,6 +846,11 @@
'source': '__start__',
'target': 'ask_question',
}),
dict({
'conditional': True,
'source': 'answer_question',
'target': '__end__',
}),
dict({
'conditional': True,
'source': 'answer_question',
@@ -852,11 +860,6 @@
'source': 'ask_question',
'target': 'answer_question',
}),
dict({
'conditional': True,
'source': 'answer_question',
'target': '__end__',
}),
]),
'nodes': list([
dict({
@@ -1008,6 +1011,11 @@
'source': 'conduct_interview:__start__',
'target': 'conduct_interview:ask_question',
}),
dict({
'conditional': True,
'source': 'conduct_interview:answer_question',
'target': 'conduct_interview:__end__',
}),
dict({
'conditional': True,
'source': 'conduct_interview:answer_question',
@@ -1017,11 +1025,6 @@
'source': 'conduct_interview:ask_question',
'target': 'conduct_interview:answer_question',
}),
dict({
'conditional': True,
'source': 'conduct_interview:answer_question',
'target': 'conduct_interview:__end__',
}),
]),
'nodes': list([
dict({
+85
View File
@@ -8703,3 +8703,88 @@ async def test_batch_update_as_input(checkpointer_name: str) -> None:
]
assert new_history == history
async def test_draw_invalid():
from langchain_core.messages import BaseMessage
class AgentState(TypedDict):
messages: Annotated[list[BaseMessage], add_messages]
workflow = StateGraph(AgentState)
async def call_model(state: AgentState) -> AgentState:
return state
async def call_tool(state: AgentState) -> AgentState:
return state
async def do_nothing(state: AgentState) -> AgentState:
return state
def should_continue(state):
messages = state["messages"]
last_message = messages[-1]
if last_message.content.startswith("end"):
return END
else:
return [Send("tool", last_message), Send("nothing", last_message)]
workflow.add_node("agent", call_model)
workflow.add_node("tool", call_tool)
workflow.add_node("nothing", do_nothing)
workflow.set_entry_point("agent")
workflow.add_conditional_edges(
"agent",
should_continue,
path_map=["tool", "nothing", END],
)
workflow.add_edge("tool", "agent")
graph = workflow.compile()
assert graph.get_graph().to_json() == {
"nodes": [
{
"id": "__start__",
"type": "runnable",
"data": {
"id": ["langchain", "schema", "runnable", "RunnablePassthrough"],
"name": "__start__",
},
},
{
"id": "agent",
"type": "runnable",
"data": {
"id": ["langgraph", "utils", "runnable", "RunnableCallable"],
"name": "agent",
},
},
{
"id": "tool",
"type": "runnable",
"data": {
"id": ["langgraph", "utils", "runnable", "RunnableCallable"],
"name": "tool",
},
},
{
"id": "nothing",
"type": "runnable",
"data": {
"id": ["langgraph", "utils", "runnable", "RunnableCallable"],
"name": "nothing",
},
},
{"id": "__end__"},
],
"edges": [
{"source": "__start__", "target": "agent"},
{"source": "agent", "target": "__end__", "conditional": True},
{"source": "agent", "target": "nothing", "conditional": True},
{"source": "agent", "target": "tool", "conditional": True},
{"source": "tool", "target": "agent"},
{"source": "nothing", "target": "__end__"},
],
}