281 lines
6.7 KiB
Markdown
281 lines
6.7 KiB
Markdown
# 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
|
|
|
|
```python
|
|
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
|
|
|
|
```python
|
|
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
|
|
|
|
```python
|
|
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
|
|
|
|
```python
|
|
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
|
|
|
|
```python
|
|
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
|
|
|
|
```python
|
|
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
|
|
|
|
```python
|
|
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
|
|
|
|
```python
|
|
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
|
|
|
|
```python
|
|
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
|
|
|
|
```python
|
|
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
|
|
|
|
```python
|
|
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
|
|
|
|
```python
|
|
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
|
|
|
|
```python
|
|
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](01_core_concepts_node.md) - Node basics
|
|
- [01_core_concepts_edge.md](01_core_concepts_edge.md) - Comparison with edges
|
|
- [02_graph_architecture_subgraph.md](02_graph_architecture_subgraph.md) - Subgraph navigation
|