Initial commit
This commit is contained in:
280
skills/langgraph-master/04_tool_integration_command_api.md
Normal file
280
skills/langgraph-master/04_tool_integration_command_api.md
Normal file
@@ -0,0 +1,280 @@
|
||||
# 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
|
||||
Reference in New Issue
Block a user