Files
gh-hiroshi75-protografico-p…/skills/langgraph-master/02_graph_architecture_agent.md
2025-11-29 18:45:58 +08:00

339 lines
8.9 KiB
Markdown

# Agent (Autonomous Tool Usage)
A pattern where the LLM dynamically determines tool selection to handle unpredictable problem-solving.
## Overview
The Agent pattern follows **ReAct** (Reasoning + Acting), where the LLM dynamically selects and executes tools to solve problems.
## ReAct Pattern
**ReAct** = Reasoning + Acting
1. **Reasoning**: Think "What should I do next?"
2. **Acting**: Take action using tools
3. **Observing**: Observe the results
4. **Repeat steps 1-3** until reaching a final answer
## Implementation Example: Basic Agent
```python
from langgraph.graph import StateGraph, START, END, MessagesState
from langgraph.prebuilt import ToolNode
from typing import Literal
# Tool definitions
@tool
def search(query: str) -> str:
"""Execute web search"""
return perform_search(query)
@tool
def calculator(expression: str) -> float:
"""Execute calculation"""
return eval(expression)
tools = [search, calculator]
# Agent node
def agent_node(state: MessagesState):
"""LLM determines tool usage"""
messages = state["messages"]
# Invoke LLM with tools
response = llm_with_tools.invoke(messages)
return {"messages": [response]}
# Continue decision
def should_continue(state: MessagesState) -> Literal["tools", "end"]:
"""Check if there are tool calls"""
last_message = state["messages"][-1]
# Continue if there are tool calls
if last_message.tool_calls:
return "tools"
# End if no tool calls (final answer)
return "end"
# Build graph
builder = StateGraph(MessagesState)
builder.add_node("agent", agent_node)
builder.add_node("tools", ToolNode(tools))
builder.add_edge(START, "agent")
# ReAct loop
builder.add_conditional_edges(
"agent",
should_continue,
{
"tools": "tools",
"end": END
}
)
# Return to agent after tool execution
builder.add_edge("tools", "agent")
graph = builder.compile()
```
## Tool Definitions
### Basic Tools
```python
from langchain_core.tools import tool
@tool
def get_weather(location: str) -> str:
"""Get weather for the specified location.
Args:
location: City name (e.g., "Tokyo", "New York")
"""
return fetch_weather_data(location)
@tool
def send_email(to: str, subject: str, body: str) -> str:
"""Send an email.
Args:
to: Recipient email address
subject: Email subject
body: Email body
"""
return send_email_api(to, subject, body)
```
### Structured Output Tools
```python
from pydantic import BaseModel, Field
class WeatherResponse(BaseModel):
location: str
temperature: float
condition: str
humidity: int
@tool(response_format="content_and_artifact")
def get_detailed_weather(location: str) -> tuple[str, WeatherResponse]:
"""Get detailed weather information"""
data = fetch_weather_data(location)
weather = WeatherResponse(
location=location,
temperature=data["temp"],
condition=data["condition"],
humidity=data["humidity"]
)
message = f"Weather in {location}: {weather.condition}, {weather.temperature}°C"
return message, weather
```
## Advanced Patterns
### Pattern 1: Multi-Agent Collaboration
```python
# Specialist agents
def research_agent(state: State):
"""Research specialist agent"""
response = research_llm_with_tools.invoke(state["messages"])
return {"messages": [response]}
def coding_agent(state: State):
"""Coding specialist agent"""
response = coding_llm_with_tools.invoke(state["messages"])
return {"messages": [response]}
# Router
def route_to_specialist(state: State) -> Literal["research", "coding"]:
"""Select specialist based on task"""
last_message = state["messages"][-1]
if "research" in last_message.content or "search" in last_message.content:
return "research"
elif "code" in last_message.content or "implement" in last_message.content:
return "coding"
return "research" # Default
```
### Pattern 2: Agent with Memory
```python
from langgraph.checkpoint.memory import MemorySaver
class AgentState(TypedDict):
messages: Annotated[list, add_messages]
context: dict # Long-term memory
def agent_with_memory(state: AgentState):
"""Agent utilizing context"""
messages = state["messages"]
context = state.get("context", {})
# Add context to prompt
system_message = f"Context: {context}"
response = llm_with_tools.invoke([
{"role": "system", "content": system_message},
*messages
])
return {"messages": [response]}
# Compile with checkpointer
checkpointer = MemorySaver()
graph = builder.compile(checkpointer=checkpointer)
```
### Pattern 3: Human-in-the-Loop Agent
```python
from langgraph.types import interrupt
def careful_agent(state: State):
"""Confirm with human before important actions"""
response = llm_with_tools.invoke(state["messages"])
# Request confirmation for important tool calls
if response.tool_calls:
for tool_call in response.tool_calls:
if tool_call["name"] in ["send_email", "delete_data"]:
# Wait for human approval
approved = interrupt({
"action": tool_call["name"],
"args": tool_call["args"],
"message": "Approve this action?"
})
if not approved:
return {
"messages": [
{"role": "assistant", "content": "Action cancelled by user"}
]
}
return {"messages": [response]}
```
### Pattern 4: Error Handling and Retry
```python
class RobustAgentState(TypedDict):
messages: Annotated[list, add_messages]
retry_count: int
errors: list[str]
def robust_tool_node(state: RobustAgentState):
"""Tool execution with error handling"""
last_message = state["messages"][-1]
tool_results = []
for tool_call in last_message.tool_calls:
try:
result = execute_tool(tool_call)
tool_results.append(result)
except Exception as e:
error_msg = f"Tool {tool_call['name']} failed: {str(e)}"
# Check if retry is possible
if state.get("retry_count", 0) < 3:
tool_results.append({
"tool_call_id": tool_call["id"],
"error": error_msg,
"retry": True
})
else:
tool_results.append({
"tool_call_id": tool_call["id"],
"error": "Max retries exceeded",
"retry": False
})
return {
"messages": tool_results,
"retry_count": state.get("retry_count", 0) + 1
}
```
## Advanced Tool Features
### Dynamic Tool Generation
```python
def create_tool_for_api(api_spec: dict):
"""Dynamically generate tool from API specification"""
@tool
def dynamic_api_tool(**kwargs) -> str:
f"""
{api_spec['description']}
Args: {api_spec['parameters']}
"""
return call_api(api_spec['endpoint'], kwargs)
return dynamic_api_tool
```
### Conditional Tool Usage
```python
def conditional_agent(state: State):
"""Change toolset based on situation"""
context = state.get("context", {})
# Basic tools only for beginners
if context.get("user_level") == "beginner":
tools = [basic_search, simple_calculator]
# Advanced tools for advanced users
else:
tools = [advanced_search, scientific_calculator, code_executor]
llm_with_selected_tools = llm.bind_tools(tools)
response = llm_with_selected_tools.invoke(state["messages"])
return {"messages": [response]}
```
## Benefits
**Flexibility**: Dynamically responds to unpredictable problems
**Autonomy**: LLM selects optimal tools and strategies
**Extensibility**: Extend functionality by simply adding tools
**Adaptability**: Solves complex multi-step tasks
## Considerations
⚠️ **Unpredictability**: May behave differently with same input
⚠️ **Cost**: Multiple LLM calls occur
⚠️ **Infinite Loops**: Proper termination conditions required
⚠️ **Tool Misuse**: LLM may use tools incorrectly
## Best Practices
1. **Clear Tool Descriptions**: Write detailed tool docstrings
2. **Maximum Iterations**: Set upper limit for loops
3. **Error Handling**: Handle tool execution errors appropriately
4. **Logging**: Make agent behavior traceable
## Summary
The Agent pattern is optimal for **dynamic and uncertain problem-solving**. It autonomously solves problems using tools through the ReAct loop.
## Related Pages
- [02_graph_architecture_workflow_vs_agent.md](02_graph_architecture_workflow_vs_agent.md) - Differences between Workflow and Agent
- [04_tool_integration_overview.md](04_tool_integration_overview.md) - Tool details
- [05_advanced_features_human_in_the_loop.md](05_advanced_features_human_in_the_loop.md) - Human intervention