工具调用机制
问题
LLM 的 Function Calling / Tool Use 是如何工作的?如何设计好的工具定义?
答案
工具调用(Function Calling / Tool Use)是 Agent 与外部世界交互的核心机制。LLM 不直接执行工具,而是生成结构化的调用请求,由应用层执行后返回结果。
一、工作流程
关键理解
LLM 不执行工具——它只是决定调用什么工具、传什么参数。实际执行由应用层完成。
二、工具定义
OpenAI 格式
tools = [
{
"type": "function",
"function": {
"name": "get_weather",
"description": "获取指定城市的天气信息。当用户问天气相关问题时调用。",
"parameters": {
"type": "object",
"properties": {
"city": {
"type": "string",
"description": "城市名称,如'北京'、'上海'"
},
"date": {
"type": "string",
"description": "日期,格式 YYYY-MM-DD,默认今天"
}
},
"required": ["city"]
}
}
}
]
Anthropic 格式
tools = [
{
"name": "get_weather",
"description": "获取指定城市的天气信息",
"input_schema": {
"type": "object",
"properties": {
"city": {"type": "string", "description": "城市名称"},
},
"required": ["city"]
}
}
]
三、工具设计最佳实践
| 原则 | 正确做法 | 错误做法 |
|---|---|---|
| 描述清晰 | "获取指定城市的实时天气" | "天气工具" |
| 参数明确 | 标注类型、格式、枚举值 | 只写参数名 |
| 单一职责 | 一个工具做一件事 | 一个工具做很多事 |
| 何时调用 | 描述中说明触发条件 | 不说明使用时机 |
| 错误返回 | 返回结构化错误信息 | 抛异常或空返回 |
# ✅ 好的工具描述
{
"name": "search_products",
"description": "在商品库中搜索商品。当用户询问商品信息、价格、库存时调用。返回匹配的商品列表。",
"parameters": {
"type": "object",
"properties": {
"query": {"type": "string", "description": "搜索关键词"},
"category": {
"type": "string",
"enum": ["electronics", "clothing", "food"],
"description": "商品类别(可选)"
},
"max_results": {
"type": "integer",
"description": "最大返回数量,默认5",
"default": 5
}
},
"required": ["query"]
}
}
四、并行工具调用
LLM 可以在一次响应中发起多个并行的工具调用:
# LLM 一次返回多个 tool_calls
response.choices[0].message.tool_calls = [
ToolCall(id="call_1", function=Function(name="get_weather", arguments='{"city":"北京"}')),
ToolCall(id="call_2", function=Function(name="get_weather", arguments='{"city":"上海"}')),
]
# 并行执行,分别返回结果
for tool_call in message.tool_calls:
result = execute_tool(tool_call)
messages.append({
"role": "tool",
"tool_call_id": tool_call.id,
"content": result
})
五、工具调用安全
| 风险 | 说明 | 防御 |
|---|---|---|
| 越权调用 | Agent 调用了不该调用的工具 | 白名单限制可用工具 |
| 参数注入 | 恶意输入导致危险参数 | 参数校验、沙箱执行 |
| 数据泄露 | 工具返回敏感信息 | 输出过滤、权限控制 |
| 无限循环 | Agent 反复调用同一工具 | 次数限制 |
# 工具执行层必须有安全防护
def safe_execute_tool(name: str, arguments: dict, user_permissions: list):
# 1. 检查权限
if name not in user_permissions:
return {"error": "无权限调用此工具"}
# 2. 参数校验
validated_args = validate_arguments(name, arguments)
# 3. 沙箱执行
result = sandbox_execute(name, validated_args)
# 4. 输出过滤(脱敏)
return sanitize_output(result)
常见面试问题
Q1: Function Calling 的参数是 LLM 生成的,如何保证准确性?
答案:
- 清晰的参数描述:description 中给出格式和示例
- enum 约束:可枚举的参数用 enum 限定范围
- Structured Output:OpenAI 的 Structured Output 可以保证 JSON Schema 合规
- 应用层兜底:对参数做类型校验和范围检查
Q2: 工具太多(>20个)时 LLM 会不会选错?
答案:
- 工具数量多确实会降低选择准确率
- 优化方式:
- 分组:根据意图先分类,再暴露该类工具
- 动态工具:用 Embedding 检索最相关的 5-10 个工具
- 描述优化:清晰的 description 比工具数量更重要