6.7 KiB
6.7 KiB
Command API
An advanced API that integrates state updates and control flow.
Overview
The Command API is a feature that allows nodes to specify state updates and control flow simultaneously.
Basic Usage
from langgraph.types import Command
def decision_node(state: State) -> Command:
"""Update state and specify the next node"""
result = analyze(state["data"])
if result["confidence"] > 0.8:
return Command(
update={"result": result, "confident": True},
goto="finalize"
)
else:
return Command(
update={"result": result, "confident": False},
goto="review"
)
Command Object Parameters
Command(
update: dict, # Updates to state
goto: str | list[str], # Next node(s) (single or multiple)
graph: str | None = None # For subgraph navigation
)
vs Traditional State Updates
Traditional Method
def node(state: State) -> dict:
return {"result": "value"}
# Control flow in edges
def route(state: State) -> str:
if state["result"] == "value":
return "next_node"
return "other_node"
builder.add_conditional_edges("node", route, {...})
Command API
def node(state: State) -> Command:
return Command(
update={"result": "value"},
goto="next_node" # Specify control flow as well
)
# No edges needed (Command controls flow)
Advanced Patterns
Pattern 1: Conditional Branching
def validator(state: State) -> Command:
"""Validate and determine next node"""
is_valid = validate(state["data"])
if is_valid:
return Command(
update={"valid": True},
goto="process"
)
else:
return Command(
update={"valid": False, "errors": get_errors(state["data"])},
goto="error_handler"
)
Pattern 2: Parallel Execution
def fan_out_node(state: State) -> Command:
"""Branch to multiple nodes in parallel"""
return Command(
update={"started": True},
goto=["worker_a", "worker_b", "worker_c"] # Parallel execution
)
Pattern 3: Loop Control
def iterator_node(state: State) -> Command:
"""Iterative processing"""
iteration = state.get("iteration", 0) + 1
result = process_iteration(state["data"], iteration)
if iteration < state["max_iterations"] and not result["done"]:
return Command(
update={"iteration": iteration, "result": result},
goto="iterator_node" # Loop back to self
)
else:
return Command(
update={"final_result": result},
goto=END
)
Pattern 4: Subgraph Navigation
def sub_node(state: State) -> Command:
"""Navigate from subgraph to parent graph"""
result = process(state["data"])
if need_parent_intervention(result):
return Command(
update={"sub_result": result},
goto="parent_handler",
graph=Command.PARENT # Navigate to parent graph
)
return {"sub_result": result}
Integration with Tools
Control After Tool Execution
def tool_node_with_command(state: MessagesState) -> Command:
"""Determine next action after tool execution"""
last_message = state["messages"][-1]
tool_results = []
for tool_call in last_message.tool_calls:
tool = tool_map[tool_call["name"]]
result = tool.invoke(tool_call["args"])
tool_results.append(
ToolMessage(
content=str(result),
tool_call_id=tool_call["id"]
)
)
# Determine next node based on results
if any("error" in r.content.lower() for r in tool_results):
return Command(
update={"messages": tool_results},
goto="error_handler"
)
else:
return Command(
update={"messages": tool_results},
goto="agent"
)
Command from Within Tools
from langgraph.types import interrupt
@tool
def send_email(to: str, subject: str, body: str) -> str:
"""Send email (with approval)"""
# Request approval
approved = interrupt({
"action": "send_email",
"to": to,
"subject": subject,
"message": "Approve sending this email?"
})
if approved:
result = actually_send_email(to, subject, body)
return f"Email sent to {to}"
else:
return "Email cancelled by user"
Dynamic Routing
def dynamic_router(state: State) -> Command:
"""Dynamically select route based on state"""
score = evaluate(state["data"])
# Select route based on score
if score > 0.9:
route = "expert_handler"
elif score > 0.7:
route = "standard_handler"
else:
route = "basic_handler"
return Command(
update={"confidence_score": score},
goto=route
)
Error Recovery
def processor_with_fallback(state: State) -> Command:
"""Fallback on error"""
try:
result = risky_operation(state["data"])
return Command(
update={"result": result, "error": None},
goto="success_handler"
)
except Exception as e:
return Command(
update={"error": str(e)},
goto="fallback_handler"
)
State Machine Implementation
def state_machine_node(state: State) -> Command:
"""State machine"""
current_state = state.get("state", "initial")
transitions = {
"initial": ("validate", {"state": "validating"}),
"validating": ("process" if state.get("valid") else "error", {"state": "processing"}),
"processing": ("finalize", {"state": "finalizing"}),
"finalizing": (END, {"state": "done"})
}
next_node, update = transitions[current_state]
return Command(
update=update,
goto=next_node
)
Benefits
✅ Conciseness: Define state updates and control flow in one place ✅ Readability: Node intent is clear ✅ Flexibility: Dynamic routing is easier ✅ Debugging: Control flow is easier to track
Considerations
⚠️ Complexity: Avoid overly complex conditional branching ⚠️ Testing: All branches need to be tested ⚠️ Parallel Execution: Order of parallel nodes is non-deterministic
Summary
The Command API integrates state updates and control flow, enabling more flexible and readable graph construction.
Related Pages
- 01_core_concepts_node.md - Node basics
- 01_core_concepts_edge.md - Comparison with edges
- 02_graph_architecture_subgraph.md - Subgraph navigation