Initial commit

This commit is contained in:
Zhongwei Li
2025-11-29 18:16:40 +08:00
commit f125e90b9f
370 changed files with 67769 additions and 0 deletions

View File

@@ -0,0 +1,125 @@
# Phase 1: Tool Schema Design
**Objective**: Design validated tool schemas for your agent
## Steps
### 1. Identify Required Tools
Ask the user:
- "What actions should the agent be able to perform?"
- "What external systems will the agent interact with?"
- "What parameters does each tool need?"
**Example agent**: Travel booking
- `search_flights` - Find available flights
- `book_flight` - Reserve a flight
- `search_hotels` - Find hotels
- `book_hotel` - Reserve accommodation
### 2. Design Tool Schema with `strict: true`
**Template:**
```python
{
"name": "tool_name",
"description": "Clear description of what this tool does",
"strict": True, # ← Enables strict mode
"input_schema": {
"type": "object",
"properties": {
"param_name": {
"type": "string",
"description": "Clear parameter description"
}
},
"required": ["param_name"],
"additionalProperties": False # ← Required
}
}
```
**Example: Flight Search Tool**
```python
{
"name": "search_flights",
"description": "Search for available flights between two cities",
"strict": True,
"input_schema": {
"type": "object",
"properties": {
"origin": {
"type": "string",
"description": "Departure city (e.g., 'San Francisco, CA')"
},
"destination": {
"type": "string",
"description": "Arrival city (e.g., 'Paris, France')"
},
"departure_date": {
"type": "string",
"format": "date",
"description": "Departure date in YYYY-MM-DD format"
},
"return_date": {
"type": "string",
"format": "date",
"description": "Return date in YYYY-MM-DD format (optional)"
},
"travelers": {
"type": "integer",
"enum": [1, 2, 3, 4, 5, 6],
"description": "Number of travelers"
},
"class": {
"type": "string",
"enum": ["economy", "premium", "business", "first"],
"description": "Flight class preference"
}
},
"required": ["origin", "destination", "departure_date", "travelers"],
"additionalProperties": False
}
}
```
### 3. Apply JSON Schema Limitations
**✅ Supported:**
- All basic types (object, array, string, integer, number, boolean)
- `enum` for constrained values
- `format` for strings (date, email, uri, uuid, etc.)
- Nested objects and arrays
- `required` fields
- `additionalProperties: false` (required!)
**❌ NOT Supported:**
- Recursive schemas
- Numerical constraints (minimum, maximum)
- String length constraints
- Complex regex patterns
### 4. Add Clear Descriptions
Good descriptions help Claude:
- Understand when to call the tool
- Know what values to provide
- Format parameters correctly
```python
# ✅ Good: Clear and specific
"origin": {
"type": "string",
"description": "Departure city and state/country (e.g., 'San Francisco, CA')"
}
# ❌ Vague: Not helpful
"origin": {
"type": "string",
"description": "Origin"
}
```
## Output
Well-designed tool schemas ready for implementation.

View File

@@ -0,0 +1,222 @@
# Phase 2: Multi-Tool Agent Implementation
**Objective**: Implement agent with multiple validated tools
## Python Implementation
```python
from anthropic import Anthropic
from typing import Dict, Any, List
client = Anthropic()
# Define tools
TOOLS = [
{
"name": "search_flights",
"description": "Search for available flights",
"strict": True,
"input_schema": {
"type": "object",
"properties": {
"origin": {"type": "string", "description": "Departure city"},
"destination": {"type": "string", "description": "Arrival city"},
"departure_date": {"type": "string", "format": "date"},
"travelers": {"type": "integer", "enum": [1, 2, 3, 4, 5, 6]}
},
"required": ["origin", "destination", "departure_date", "travelers"],
"additionalProperties": False
}
},
{
"name": "book_flight",
"description": "Book a selected flight",
"strict": True,
"input_schema": {
"type": "object",
"properties": {
"flight_id": {"type": "string", "description": "Flight identifier"},
"passengers": {
"type": "array",
"items": {
"type": "object",
"properties": {
"name": {"type": "string"},
"passport": {"type": "string"}
},
"required": ["name", "passport"],
"additionalProperties": False
}
}
},
"required": ["flight_id", "passengers"],
"additionalProperties": False
}
},
{
"name": "search_hotels",
"description": "Search for hotels in a city",
"strict": True,
"input_schema": {
"type": "object",
"properties": {
"city": {"type": "string", "description": "City name"},
"check_in": {"type": "string", "format": "date"},
"check_out": {"type": "string", "format": "date"},
"guests": {"type": "integer", "enum": [1, 2, 3, 4]}
},
"required": ["city", "check_in", "check_out", "guests"],
"additionalProperties": False
}
}
]
# Tool execution functions
def search_flights(origin: str, destination: str, departure_date: str, travelers: int) -> Dict:
"""Execute flight search - calls actual API."""
# Implementation here
return {"flights": [...]}
def book_flight(flight_id: str, passengers: List[Dict]) -> Dict:
"""Book the flight - calls actual API."""
# Implementation here
return {"confirmation": "ABC123", "status": "confirmed"}
def search_hotels(city: str, check_in: str, check_out: str, guests: int) -> Dict:
"""Search hotels - calls actual API."""
# Implementation here
return {"hotels": [...]}
# Tool registry
TOOL_FUNCTIONS = {
"search_flights": search_flights,
"book_flight": book_flight,
"search_hotels": search_hotels,
}
# Agent loop
def run_agent(user_request: str, max_turns: int = 10):
"""Run agent with tool validation."""
messages = [{"role": "user", "content": user_request}]
for turn in range(max_turns):
response = client.beta.messages.create(
model="claude-sonnet-4-5",
max_tokens=2048,
betas=["structured-outputs-2025-11-13"],
messages=messages,
tools=TOOLS,
)
# Process response
if response.stop_reason == "end_turn":
# Agent finished
return extract_final_answer(response)
if response.stop_reason == "tool_use":
# Execute tools
tool_results = []
for block in response.content:
if block.type == "tool_use":
# Tool input is GUARANTEED to match schema
tool_name = block.name
tool_input = block.input # Already validated!
# Execute tool
tool_function = TOOL_FUNCTIONS[tool_name]
result = tool_function(**tool_input) # Type-safe!
tool_results.append({
"type": "tool_result",
"tool_use_id": block.id,
"content": str(result)
})
# Add assistant response and tool results to conversation
messages.append({"role": "assistant", "content": response.content})
messages.append({"role": "user", "content": tool_results})
else:
raise Exception(f"Unexpected stop reason: {response.stop_reason}")
raise Exception("Max turns reached")
# Usage
result = run_agent("Book a flight from SF to Paris for 2 people, departing May 15")
print(result)
```
## TypeScript Implementation
```typescript
import Anthropic from '@anthropic-ai/sdk';
const client = new Anthropic({ apiKey: process.env.ANTHROPIC_API_KEY });
const TOOLS: Anthropic.Tool[] = [
{
name: "search_flights",
description: "Search for available flights",
strict: true,
input_schema: {
type: "object",
properties: {
origin: { type: "string", description: "Departure city" },
destination: { type: "string", description: "Arrival city" },
departure_date: { type: "string", format: "date" },
travelers: { type: "integer", enum: [1, 2, 3, 4, 5, 6] }
},
required: ["origin", "destination", "departure_date", "travelers"],
additionalProperties: false
}
},
// ... other tools
];
async function runAgent(userRequest: string, maxTurns: number = 10) {
const messages: Anthropic.MessageParam[] = [
{ role: "user", content: userRequest }
];
for (let turn = 0; turn < maxTurns; turn++) {
const response = await client.beta.messages.create({
model: "claude-sonnet-4-5",
max_tokens: 2048,
betas: ["structured-outputs-2025-11-13"],
messages,
tools: TOOLS,
});
if (response.stop_reason === "end_turn") {
return extractFinalAnswer(response);
}
if (response.stop_reason === "tool_use") {
const toolResults: Anthropic.ToolResultBlockParam[] = [];
for (const block of response.content) {
if (block.type === "tool_use") {
// Input guaranteed to match schema!
const result = await executeTool(block.name, block.input);
toolResults.push({
type: "tool_result",
tool_use_id: block.id,
content: JSON.stringify(result)
});
}
}
messages.push({ role: "assistant", content: response.content });
messages.push({ role: "user", content: toolResults });
}
}
throw new Error("Max turns reached");
}
```
## Output
Working multi-tool agent with validated tool schemas.

View File

@@ -0,0 +1,58 @@
# Phase 3: Error Handling & Validation
**Objective**: Handle errors and edge cases in agent workflows
## Key Error Scenarios
### 1. Tool Execution Failures
```python
def execute_tool_safely(tool_name: str, tool_input: Dict) -> Dict:
"""Execute tool with error handling."""
try:
tool_function = TOOL_FUNCTIONS[tool_name]
result = tool_function(**tool_input)
return {"success": True, "data": result}
except Exception as e:
logger.error(f"Tool {tool_name} failed: {e}")
return {
"success": False,
"error": str(e),
"message": "Tool execution failed. Please try again."
}
```
### 2. Safety Refusals
```python
if response.stop_reason == "refusal":
logger.warning("Agent refused request")
# Don't retry - respect safety boundaries
return {"error": "Request cannot be completed"}
```
### 3. Max Turns Exceeded
```python
if turn >= max_turns:
logger.warning("Agent exceeded max turns")
return {
"error": "Task too complex",
"partial_progress": extract_progress(messages)
}
```
### 4. Invalid Tool Name
```python
# With strict mode, tool names are guaranteed valid
# But external factors can cause issues
if tool_name not in TOOL_FUNCTIONS:
logger.error(f"Unknown tool: {tool_name}")
return {"error": f"Tool {tool_name} not implemented"}
```
## Output
Robust error handling for production agent workflows.

View File

@@ -0,0 +1,81 @@
# Phase 4: Testing Agent Workflows
**Objective**: Validate agent behavior with realistic scenarios
## Test Strategy
```python
import pytest
from unittest.mock import Mock, patch
@pytest.fixture
def mock_tool_functions():
"""Mock external tool functions."""
return {
"search_flights": Mock(return_value={"flights": [{"id": "F1", "price": 500}]}),
"book_flight": Mock(return_value={"confirmation": "ABC123"}),
"search_hotels": Mock(return_value={"hotels": [{"id": "H1", "price": 150}]}),
}
def test_simple_flight_search(mock_tool_functions):
"""Test agent handles simple flight search."""
with patch.dict('agent.TOOL_FUNCTIONS', mock_tool_functions):
result = run_agent("Find flights from SF to LA on May 15 for 2 people")
# Verify search_flights was called
mock_tool_functions["search_flights"].assert_called_once()
call_args = mock_tool_functions["search_flights"].call_args[1]
# Strict mode guarantees these match schema
assert call_args["origin"] == "San Francisco, CA" # or similar
assert call_args["destination"] == "Los Angeles, CA"
assert call_args["travelers"] == 2
assert "2024-05-15" in call_args["departure_date"]
def test_multi_step_booking(mock_tool_functions):
"""Test agent completes multi-step booking."""
with patch.dict('agent.TOOL_FUNCTIONS', mock_tool_functions):
result = run_agent(
"Book a round trip from SF to Paris for 2 people, "
"May 15-22, and find a hotel"
)
# Verify correct tool sequence
assert mock_tool_functions["search_flights"].called
assert mock_tool_functions["book_flight"].called
assert mock_tool_functions["search_hotels"].called
def test_tool_failure_handling(mock_tool_functions):
"""Test agent handles tool failures gracefully."""
mock_tool_functions["search_flights"].side_effect = Exception("API down")
with patch.dict('agent.TOOL_FUNCTIONS', mock_tool_functions):
result = run_agent("Find flights to Paris")
# Should handle error gracefully
assert "error" in result or "failed" in str(result).lower()
def test_parameter_validation():
"""Test that strict mode guarantees valid parameters."""
# With strict mode, parameters are guaranteed to match schema
# This test verifies the guarantee holds
response = client.beta.messages.create(
model="claude-sonnet-4-5",
betas=["structured-outputs-2025-11-13"],
messages=[{"role": "user", "content": "Search flights for 2 people"}],
tools=TOOLS,
)
for block in response.content:
if block.type == "tool_use":
# These assertions should NEVER fail with strict mode
assert isinstance(block.input, dict)
assert "travelers" in block.input
assert isinstance(block.input["travelers"], int)
assert block.input["travelers"] in [1, 2, 3, 4, 5, 6]
```
## Output
Comprehensive test coverage for agent workflows.

View File

@@ -0,0 +1,98 @@
# Phase 5: Production Agent Patterns
**Objective**: Production-ready agent architectures
## Pattern 1: Stateful Agent with Memory
```python
class StatefulTravelAgent:
"""Agent that maintains state across interactions."""
def __init__(self):
self.conversation_history: List[Dict] = []
self.booking_state: Dict[str, Any] = {}
def chat(self, user_message: str) -> str:
"""Process user message and return response."""
self.conversation_history.append({
"role": "user",
"content": user_message
})
response = client.beta.messages.create(
model="claude-sonnet-4-5",
betas=["structured-outputs-2025-11-13"],
max_tokens=2048,
messages=self.conversation_history,
tools=TOOLS,
)
# Process tools and update state
final_response = self._process_response(response)
self.conversation_history.append({
"role": "assistant",
"content": final_response
})
return final_response
def _process_response(self, response) -> str:
"""Process tool calls and maintain state."""
# Implementation...
pass
# Usage
agent = StatefulTravelAgent()
print(agent.chat("I want to go to Paris"))
print(agent.chat("For 2 people")) # Remembers context
print(agent.chat("May 15 to May 22")) # Continues booking
```
## Pattern 2: Tool Retry Logic
```python
def execute_tool_with_retry(
tool_name: str,
tool_input: Dict,
max_retries: int = 3
) -> Dict:
"""Execute tool with exponential backoff retry."""
import time
for attempt in range(max_retries):
try:
tool_func = TOOL_FUNCTIONS[tool_name]
result = tool_func(**tool_input)
return {"success": True, "data": result}
except Exception as e:
if attempt == max_retries - 1:
return {"success": False, "error": str(e)}
wait_time = 2 ** attempt # Exponential backoff
logger.warning(f"Tool {tool_name} failed, retrying in {wait_time}s")
time.sleep(wait_time)
```
## Pattern 3: Tool Result Validation
```python
def validate_tool_result(tool_name: str, result: Any) -> bool:
"""Validate tool execution result."""
validators = {
"search_flights": lambda r: "flights" in r and len(r["flights"]) > 0,
"book_flight": lambda r: "confirmation" in r,
"search_hotels": lambda r: "hotels" in r,
}
validator = validators.get(tool_name)
if validator:
return validator(result)
return True # No validator = assume valid
```
## Output
Production-ready agent patterns with state management, retry logic, and validation.