AI 智能体与函数调用:为 LLM 构建工具
你正在构建一个需要获取实时数据、执行计算或与外部 API 交互的 AI 智能体。但如何弥合 LLM 文本生成与实际代码执行之间的鸿沟?函数调用(也称为工具使用)就是答案。它让 LLM 请求特定操作,由你的代码执行这些操作。本指南将带你了解如何为 AI 智能体设计、实现和调试函数调用。
什么是函数调用?
函数调用是一种机制,LLM 可以输出结构化请求来调用你定义的函数。模型不再生成自由文本,而是返回包含函数名和参数的 JSON 对象。然后你的应用程序执行该函数,并将结果反馈给模型。这使智能体能够执行文本生成之外的操作,例如查询数据库、发送邮件或调用 API。
OpenAI、Anthropic 和 Google 等主要 LLM 提供商都支持函数调用。核心思路一致:你描述可用的工具,模型决定何时使用它们,你负责处理执行。
为 LLM 设计工具
设计良好的工具对于可靠的智能体行为至关重要。遵循以下原则:
- 清晰的名称和描述:使用描述性的函数名称和详细的描述。LLM 依赖这些信息来选择正确的工具。
- 简单的参数:保持参数最少,并使用标准类型(string、number、boolean、array、object)。尽可能避免复杂的嵌套结构。
- 幂等性:尽可能将工具设计为幂等的(可安全重试),以便优雅地处理失败。
- 错误处理:返回信息丰富的错误消息,以便 LLM 调整其方法。
示例:天气工具
以下是一个简单的 JSON schema 格式工具定义,常用于 OpenAI 的 API:
{
"name": "get_weather",
"description": "Get the current weather for a given city",
"parameters": {
"type": "object",
"properties": {
"city": {
"type": "string",
"description": "The city name, e.g., San Francisco"
},
"unit": {
"type": "string",
"enum": ["celsius", "fahrenheit"],
"description": "Temperature unit"
}
},
"required": ["city"]
}
}
实现函数调用:分步指南
让我们使用 OpenAI 的 API 构建一个最小的智能体循环(该模式也适用于其他提供商)。
1. 定义你的工具
创建工具 schema 列表以及函数名称到实际 Python 函数的映射。
import json
import openai
# Tool schemas
tools = [
{
"type": "function",
"function": {
"name": "get_weather",
"description": "Get current weather for a city",
"parameters": {
"type": "object",
"properties": {
"city": {"type": "string"},
"unit": {"type": "string", "enum": ["celsius", "fahrenheit"]}
},
"required": ["city"]
}
}
}
]
# Actual functions
def get_weather(city: str, unit: str = "celsius") -> dict:
# In reality, call a weather API
return {"city": city, "temperature": 22, "unit": unit, "condition": "sunny"}
# Map names to functions
function_map = {
"get_weather": get_weather
}
2. 创建智能体循环
智能体循环向 LLM 发送消息,检查工具调用,执行它们,并重复直到模型返回最终答案。
def run_agent(user_message: str):
messages = [{"role": "user", "content": user_message}]
while True:
response = openai.ChatCompletion.create(
model="gpt-4",
messages=messages,
tools=tools,
tool_choice="auto"
)
message = response.choices[0].message
messages.append(message)
# If no tool calls, return the content
if not message.get("tool_calls"):
return message["content"]
# Execute each tool call
for tool_call in message.tool_calls:
function_name = tool_call.function.name
arguments = json.loads(tool_call.function.arguments)
if function_name in function_map:
result = function_map[function_name](**arguments)
else:
result = {"error": f"Unknown function: {function_name}"}
# Append tool result to messages
messages.append({
"role": "tool",
"tool_call_id": tool_call.id,
"content": json.dumps(result)
})
此循环持续进行,直到 LLM 生成不带工具调用的响应,表明它已获得足够的信息。
3. 处理错误和边缘情况
真实世界的智能体必须处理:
- 无效参数:在解析参数或调用函数时捕获异常。
- 未知工具:向 LLM 返回错误消息,以便它自我纠正。
- 超时:为外部 API 调用设置超时,避免挂起。
- 速率限制:实现带指数退避的重试。
可靠智能体的最佳实践
- 限制工具数量:太多工具会混淆模型。将相关函数分组或使用路由器。
- 验证输入:在执行前对所有参数进行清理和验证。
- 记录一切:记录工具调用和结果,用于调试和审计。
- 用多样化提示测试:确保智能体在不同场景下选择正确的工具。
- 提供回退方案:如果工具失败,智能体应尝试替代方案或请求澄清。
函数调用支持对比
| 提供商 | 功能名称 | 格式 |
|---|---|---|
| OpenAI | Function Calling | JSON Schema |
| Anthropic | Tool Use | JSON Schema |
| Function Calling | OpenAPI Schema |
高级模式
随着智能体的成长,考虑以下模式:
- 并行工具调用:某些模型可以一次请求多个工具。并发执行以提高速度。
- 人在回路中:对于敏感操作(例如汇款),在执行前需要人工批准。
- 记忆:存储对话历史和工具结果,以便在长时间会话中提供上下文。
- 工具路由:在调用主 LLM 之前,使用轻量级分类器选择相关工具。
调试函数调用
当出现问题时,检查:
- 工具描述是否清晰明确?
- 参数名称和类型是否正确?
- 模型是否有足够的上下文来选择正确的工具?
- 是否正确处理工具结果(例如 JSON 序列化)?
使用日志记录完整的消息历史和工具调用。通常,问题在于预期参数与实际参数不匹配。
常见问题
函数调用和工具使用有什么区别?
它们指的是同一个概念。OpenAI 称之为“函数调用”,而 Anthropic 使用“工具使用”。两者都允许 LLM 请求执行外部函数。
我可以将函数调用与开源模型一起使用吗?
可以,一些开源模型如 Llama 3.1 支持函数调用,LangChain 等框架提供了抽象。然而,支持程度各异,你可能需要微调或使用特定的提示格式。
如何防止 LLM 调用危险函数?
切勿直接暴露危险函数。使用允许列表、验证输入并实施权限检查。对于敏感操作,在执行前需要人工确认。
准备好构建你自己的 AI 智能体了吗?从定义一个简单的工具并测试智能体循环开始。如需更多开发者工具,请查看我们的 JSON Formatter 来调试工具调用负载。