阶段 2:工具调用与函数式 Agent
📖 本阶段目标
- 理解 Function Calling 的工作原理
- 学会将业务 API、本地模块包装为 LLM 可调用的工具
- 掌握工具调用的错误处理和重试机制
- 实践构建多工具聚合的 Agent
🔧 Function Calling 核心概念
什么是 Function Calling?
Function Calling 允许 LLM 在需要时调用外部函数/API,从而: - 获取实时数据(天气、股票、新闻等) - 执行具体操作(发送邮件、创建任务等) - 访问数据库或内部系统 - 扩展 LLM 的能力边界
工作流程
用户输入:"北京明天的天气怎么样?"
↓
LLM 理解意图,决定调用工具
↓
返回 Function Call 请求:
{
"name": "get_weather",
"arguments": {
"city": "北京",
"date": "明天"
}
}
↓
系统执行实际的 API 调用
↓
将结果返回给 LLM
↓
LLM 生成自然语言回复:
"北京明天晴,最高温度25°C,最低温度15°C..."
🛠️ 实现 Function Calling
1. 基础示例:天气查询
# examples/stage2/weather_agent.py
import json
from openai import OpenAI
client = OpenAI()
# 定义工具函数
def get_current_weather(location, unit="celsius"):
"""获取指定地点的天气(模拟)"""
# 实际应用中这里会调用真实的天气API
weather_data = {
"北京": {"temperature": 22, "condition": "晴", "humidity": 45},
"上海": {"temperature": 26, "condition": "多云", "humidity": 60},
"深圳": {"temperature": 28, "condition": "阴", "humidity": 75},
}
weather = weather_data.get(location, {
"temperature": 20,
"condition": "未知",
"humidity": 50
})
return json.dumps({
"location": location,
"temperature": weather["temperature"],
"unit": unit,
"condition": weather["condition"],
"humidity": weather["humidity"]
}, ensure_ascii=False)
# 工具定义(告诉 LLM 有哪些工具可用)
tools = [
{
"type": "function",
"function": {
"name": "get_current_weather",
"description": "获取指定城市的当前天气信息",
"parameters": {
"type": "object",
"properties": {
"location": {
"type": "string",
"description": "城市名称,例如:北京、上海"
},
"unit": {
"type": "string",
"enum": ["celsius", "fahrenheit"],
"description": "温度单位"
}
},
"required": ["location"]
}
}
}
]
def run_conversation(user_message):
"""运行对话,处理函数调用"""
messages = [
{"role": "user", "content": user_message}
]
# 第一次调用:让 LLM 决定是否需要调用函数
response = client.chat.completions.create(
model="gpt-4-turbo-preview",
messages=messages,
tools=tools,
tool_choice="auto" # 让模型自动决定
)
response_message = response.choices[0].message
tool_calls = response_message.tool_calls
# 如果 LLM 决定调用函数
if tool_calls:
messages.append(response_message)
# 执行所有函数调用
for tool_call in tool_calls:
function_name = tool_call.function.name
function_args = json.loads(tool_call.function.arguments)
# 调用实际的函数
if function_name == "get_current_weather":
function_response = get_current_weather(**function_args)
# 将函数结果添加到消息历史
messages.append({
"tool_call_id": tool_call.id,
"role": "tool",
"name": function_name,
"content": function_response
})
# 第二次调用:让 LLM 根据函数结果生成回复
second_response = client.chat.completions.create(
model="gpt-4-turbo-preview",
messages=messages
)
return second_response.choices[0].message.content
# 如果不需要调用函数,直接返回
return response_message.content
# 测试
if __name__ == "__main__":
result = run_conversation("北京现在的天气怎么样?")
print(result)
2. 多工具集成:待办事项管理
# examples/stage2/todo_agent.py
import json
from datetime import datetime
from openai import OpenAI
client = OpenAI()
# 简单的内存存储(实际应用应使用数据库)
todo_list = []
def add_todo(task, priority="medium", due_date=None):
"""添加待办事项"""
todo_item = {
"id": len(todo_list) + 1,
"task": task,
"priority": priority,
"due_date": due_date,
"completed": False,
"created_at": datetime.now().isoformat()
}
todo_list.append(todo_item)
return json.dumps({"success": True, "item": todo_item}, ensure_ascii=False)
def list_todos(filter_by=None):
"""列出待办事项"""
if filter_by == "completed":
items = [t for t in todo_list if t["completed"]]
elif filter_by == "pending":
items = [t for t in todo_list if not t["completed"]]
else:
items = todo_list
return json.dumps({"count": len(items), "items": items}, ensure_ascii=False)
def complete_todo(task_id):
"""标记待办事项为完成"""
for item in todo_list:
if item["id"] == task_id:
item["completed"] = True
return json.dumps({"success": True, "item": item}, ensure_ascii=False)
return json.dumps({"success": False, "error": "任务不存在"}, ensure_ascii=False)
def delete_todo(task_id):
"""删除待办事项"""
global todo_list
original_len = len(todo_list)
todo_list = [t for t in todo_list if t["id"] != task_id]
success = len(todo_list) < original_len
return json.dumps({"success": success}, ensure_ascii=False)
# 定义所有工具
tools = [
{
"type": "function",
"function": {
"name": "add_todo",
"description": "添加新的待办事项",
"parameters": {
"type": "object",
"properties": {
"task": {
"type": "string",
"description": "待办任务的描述"
},
"priority": {
"type": "string",
"enum": ["low", "medium", "high"],
"description": "任务优先级"
},
"due_date": {
"type": "string",
"description": "截止日期,格式:YYYY-MM-DD"
}
},
"required": ["task"]
}
}
},
{
"type": "function",
"function": {
"name": "list_todos",
"description": "列出待办事项",
"parameters": {
"type": "object",
"properties": {
"filter_by": {
"type": "string",
"enum": ["all", "completed", "pending"],
"description": "筛选条件"
}
}
}
}
},
{
"type": "function",
"function": {
"name": "complete_todo",
"description": "标记待办事项为已完成",
"parameters": {
"type": "object",
"properties": {
"task_id": {
"type": "integer",
"description": "任务ID"
}
},
"required": ["task_id"]
}
}
},
{
"type": "function",
"function": {
"name": "delete_todo",
"description": "删除待办事项",
"parameters": {
"type": "object",
"properties": {
"task_id": {
"type": "integer",
"description": "任务ID"
}
},
"required": ["task_id"]
}
}
}
]
# 函数映射
available_functions = {
"add_todo": add_todo,
"list_todos": list_todos,
"complete_todo": complete_todo,
"delete_todo": delete_todo
}
def run_agent(user_input):
"""运行待办事项 Agent"""
messages = [
{
"role": "system",
"content": "你是一个待办事项管理助手,帮助用户管理他们的任务。"
},
{"role": "user", "content": user_input}
]
response = client.chat.completions.create(
model="gpt-4-turbo-preview",
messages=messages,
tools=tools,
tool_choice="auto"
)
response_message = response.choices[0].message
tool_calls = response_message.tool_calls
if tool_calls:
messages.append(response_message)
for tool_call in tool_calls:
function_name = tool_call.function.name
function_args = json.loads(tool_call.function.arguments)
# 调用对应的函数
function_to_call = available_functions[function_name]
function_response = function_to_call(**function_args)
messages.append({
"tool_call_id": tool_call.id,
"role": "tool",
"name": function_name,
"content": function_response
})
second_response = client.chat.completions.create(
model="gpt-4-turbo-preview",
messages=messages
)
return second_response.choices[0].message.content
return response_message.content
# 测试
if __name__ == "__main__":
# 添加任务
print(run_agent("帮我添加一个高优先级的任务:完成项目报告,截止日期是2024-12-31"))
# 列出任务
print(run_agent("给我看看所有待办任务"))
# 完成任务
print(run_agent("把第1个任务标记为完成"))
3. 综合项目:多工具聚合 Agent
# examples/stage2/multi_tool_agent.py
import json
import requests
from datetime import datetime
from openai import OpenAI
client = OpenAI()
class MultiToolAgent:
"""多工具聚合 Agent"""
def __init__(self):
self.conversation_history = []
self.tools = self._define_tools()
self.functions = {
"get_weather": self.get_weather,
"get_exchange_rate": self.get_exchange_rate,
"calculate": self.calculate,
"get_current_time": self.get_current_time
}
def get_weather(self, city):
"""获取天气(使用真实API或模拟)"""
# 这里应该调用真实的天气API,如 OpenWeatherMap
# 为了演示,使用模拟数据
mock_data = {
"北京": {"temp": 22, "condition": "晴"},
"上海": {"temp": 26, "condition": "多云"},
}
data = mock_data.get(city, {"temp": 20, "condition": "未知"})
return json.dumps(data, ensure_ascii=False)
def get_exchange_rate(self, from_currency, to_currency):
"""获取汇率"""
# 实际应该调用汇率API
# 模拟数据
rates = {
("USD", "CNY"): 7.2,
("CNY", "USD"): 0.14,
("EUR", "CNY"): 7.8,
}
rate = rates.get((from_currency, to_currency), 1.0)
return json.dumps({"from": from_currency, "to": to_currency, "rate": rate})
def calculate(self, expression):
"""计算数学表达式"""
try:
# 注意:eval 有安全风险,生产环境应使用专门的数学解析库
result = eval(expression, {"__builtins__": {}}, {})
return json.dumps({"expression": expression, "result": result})
except Exception as e:
return json.dumps({"error": str(e)})
def get_current_time(self, timezone="Asia/Shanghai"):
"""获取当前时间"""
now = datetime.now()
return json.dumps({
"timezone": timezone,
"datetime": now.isoformat(),
"formatted": now.strftime("%Y-%m-%d %H:%M:%S")
})
def _define_tools(self):
"""定义所有可用工具"""
return [
{
"type": "function",
"function": {
"name": "get_weather",
"description": "获取指定城市的天气信息",
"parameters": {
"type": "object",
"properties": {
"city": {"type": "string", "description": "城市名称"}
},
"required": ["city"]
}
}
},
{
"type": "function",
"function": {
"name": "get_exchange_rate",
"description": "获取货币汇率",
"parameters": {
"type": "object",
"properties": {
"from_currency": {"type": "string", "description": "源货币代码,如USD"},
"to_currency": {"type": "string", "description": "目标货币代码,如CNY"}
},
"required": ["from_currency", "to_currency"]
}
}
},
{
"type": "function",
"function": {
"name": "calculate",
"description": "计算数学表达式,支持加减乘除和括号",
"parameters": {
"type": "object",
"properties": {
"expression": {"type": "string", "description": "数学表达式,如 '(10 + 5) * 2'"}
},
"required": ["expression"]
}
}
},
{
"type": "function",
"function": {
"name": "get_current_time",
"description": "获取当前时间",
"parameters": {
"type": "object",
"properties": {
"timezone": {"type": "string", "description": "时区,默认Asia/Shanghai"}
}
}
}
}
]
def chat(self, user_input):
"""处理用户输入"""
self.conversation_history.append({"role": "user", "content": user_input})
response = client.chat.completions.create(
model="gpt-4-turbo-preview",
messages=[
{"role": "system", "content": "你是一个多功能助手,可以查天气、查汇率、计算和查时间。"},
*self.conversation_history
],
tools=self.tools,
tool_choice="auto"
)
response_message = response.choices[0].message
tool_calls = response_message.tool_calls
if tool_calls:
self.conversation_history.append(response_message)
for tool_call in tool_calls:
function_name = tool_call.function.name
function_args = json.loads(tool_call.function.arguments)
# 调用对应函数
function_response = self.functions[function_name](**function_args)
self.conversation_history.append({
"tool_call_id": tool_call.id,
"role": "tool",
"name": function_name,
"content": function_response
})
# 获取最终回复
final_response = client.chat.completions.create(
model="gpt-4-turbo-preview",
messages=[
{"role": "system", "content": "你是一个多功能助手。"},
*self.conversation_history
]
)
assistant_message = final_response.choices[0].message.content
else:
assistant_message = response_message.content
self.conversation_history.append({"role": "assistant", "content": assistant_message})
return assistant_message
# 测试
if __name__ == "__main__":
agent = MultiToolAgent()
print("=== 多工具 Agent 测试 ===\n")
# 测试1:天气查询
print("用户:北京今天天气怎么样?")
print("助手:", agent.chat("北京今天天气怎么样?"))
print()
# 测试2:汇率查询
print("用户:100美元等于多少人民币?")
print("助手:", agent.chat("100美元等于多少人民币?"))
print()
# 测试3:计算
print("用户:帮我算一下 (123 + 456) * 2")
print("助手:", agent.chat("帮我算一下 (123 + 456) * 2"))
print()
# 测试4:复杂查询(可能需要多个工具)
print("用户:现在几点了?北京天气如何?")
print("助手:", agent.chat("现在几点了?北京天气如何?"))
📋 工具设计最佳实践
1. 工具定义原则
✅ DO(应该): - 清晰的函数名称(使用动词开头) - 详细的描述说明 - 明确的参数类型和说明 - 合理的必填/可选参数设置
❌ DON'T(不应该): - 模糊的函数名 - 缺少参数描述 - 参数类型不明确 - 过于复杂的参数结构
2. 错误处理
def safe_tool_call(func, **kwargs):
"""安全的工具调用包装器"""
try:
result = func(**kwargs)
return {
"success": True,
"data": result
}
except Exception as e:
return {
"success": False,
"error": str(e),
"error_type": type(e).__name__
}
3. 重试机制
import time
from functools import wraps
def retry_on_failure(max_retries=3, delay=1):
"""重试装饰器"""
def decorator(func):
@wraps(func)
def wrapper(*args, **kwargs):
for attempt in range(max_retries):
try:
return func(*args, **kwargs)
except Exception as e:
if attempt == max_retries - 1:
raise
time.sleep(delay * (attempt + 1))
return None
return wrapper
return decorator
@retry_on_failure(max_retries=3)
def call_external_api(url):
"""调用外部API(带重试)"""
response = requests.get(url, timeout=5)
response.raise_for_status()
return response.json()
✅ 阶段完成检查清单
- [ ] 理解 Function Calling 的工作原理和流程
- [ ] 能够定义标准的工具函数描述
- [ ] 掌握单工具和多工具的集成方法
- [ ] 实现了至少一个实际的工具调用项目
- [ ] 了解工具调用的错误处理机制
- [ ] 能够处理多轮对话中的工具调用
- [ ] 理解工具调用的最佳实践
🎯 下一步
完成本阶段后,进入 阶段 3:任务分解与多步推理 Agent,学习如何让 Agent 自主进行多步推理和任务分解。
💡 小贴士:工具调用是 Agent 的"手和脚",好的工具设计能让 Agent 更强大。记住:保持工具的单一职责,让每个工具只做好一件事。