Skip to content

工具调用

工具调用(Function Calling)让 LLM 能够主动决定何时调用外部函数,将模型的知识与程序的能力结合起来。用户问"今天天气如何",LLM 识别出需要天气信息,返回一个函数调用请求,程序执行 get_weather() 函数后将结果反馈给模型,模型生成自然语言回答。这种机制让 AI 从"被动响应"进化为"主动行动"。

工作原理

工具调用的核心是让 LLM 学会"什么时候调用什么函数,传什么参数"。实现方式不是真的让模型执行代码,而是让模型输出结构化的函数调用指令,程序解析后执行对应函数,再将结果返回模型续写。

典型的对话流程:用户提问 → LLM 分析是否需要工具 → 返回 {"name": "search", "arguments": "{\"query\": \"AI 最新进展\"}"} → 程序执行 search 函数 → 获取搜索结果 → 将结果作为系统消息发送给 LLM → LLM 基于搜索结果生成答案。

关键点在于 LLM 输出的是"应该调用什么",而非直接执行。这保证了安全性,LLM 无法直接操作数据库或发送邮件,只能建议程序去做这些事,程序可以校验参数、检查权限、审计日志。

OpenAI 函数调用标准

OpenAI 的函数调用 API 已成为事实标准。请求时在 tools 参数中定义可用函数,每个函数包含 namedescriptionparameters(JSON Schema)。模型判断是否需要调用函数,返回 tool_calls 数组,包含函数名和参数。

python
import openai

tools = [{
    "type": "function",
    "function": {
        "name": "get_weather",
        "description": "获取指定城市的天气信息",
        "parameters": {
            "type": "object",
            "properties": {
                "city": {
                    "type": "string",
                    "description": "城市名称,如 北京、上海"
                },
                "unit": {
                    "type": "string",
                    "enum": ["celsius", "fahrenheit"],
                    "description": "温度单位"
                }
            },
            "required": ["city"]
        }
    }
}]

response = client.chat.completions.create(
    model="gpt-4o",
    messages=[{"role": "user", "content": "北京今天多少度"}],
    tools=tools
)

tool_call = response.choices[0].message.tool_calls[0]
function_name = tool_call.function.name
arguments = json.loads(tool_call.function.arguments)

# 执行函数
result = get_weather(arguments["city"], arguments.get("unit", "celsius"))

JSON Schema 设计

工具定义的质量直接影响调用成功率。description 字段是模型理解函数用途的唯一途径,需要清晰说明功能、适用场景、参数含义。"搜索网络"太模糊,"根据关键词搜索互联网信息,返回相关文章标题和链接"更具体。

参数定义要详细。type 指定数据类型(string、number、boolean、array、object),description 解释参数含义和约束,enum 限制可选值,required 标记必填参数。对于复杂参数,可以嵌套定义 properties

json
{
    "name": "book_flight",
    "description": "预订机票,查询价格并下单",
    "parameters": {
        "type": "object",
        "properties": {
            "origin": {
                "type": "string",
                "description": "出发城市的三字机场代码,如 PEK(北京首都)、SHA(上海虹桥)"
            },
            "destination": {
                "type": "string",
                "description": "目的城市的三字机场代码"
            },
            "date": {
                "type": "string",
                "description": "出发日期,格式 YYYY-MM-DD,必须是今天或之后的日期"
            },
            "passengers": {
                "type": "integer",
                "description": "乘客人数,最少 1 人,最多 9 人",
                "minimum": 1,
                "maximum": 9
            }
        },
        "required": ["origin", "destination", "date"]
    }
}

工具注册与管理

随着应用复杂度增加,工具数量会从几个增长到几十个。如何让模型在众多工具中选择正确的?一种方法是工具分类,将工具按功能分组(搜索、计算、数据库),先让模型确定需要哪类工具,再在该类中选择。另一种方法是智能路由,根据用户问题或上下文,只暴露相关的工具子集,减少模型的选择负担。

工具版本管理是生产环境的挑战。函数签名可能变更(新增参数、修改类型),老版本的工具调用可能兼容失败。解决方案包括:工具命名带版本号(search_v1search_v2),维护多版本并存;渐进式迁移,新调用用新版本,老调用兼容老版本;灰度发布,小流量测试新版本后全量。

工具注册表是集中管理的地方。可以用数据库存储工具定义,支持动态增删改查,无需重新部署应用。启动时从数据库加载工具定义,运行时监听变更热更新。工具注册表还应该记录调用统计(成功率、延迟、错误率),用于优化工具描述和参数设计。

参数校验与错误处理

LLM 生成的参数可能不符合预期。类型错误(传入字符串而非数字)、格式错误(日期不是 YYYY-MM-DD)、值域错误(乘客人数为负数)都需要校验。校验失败时,应该给模型明确的错误提示,"参数 city 不能为空"比"参数错误"更有帮助,模型可以根据提示重新生成正确参数。

超时和重试策略必不可少。工具调用可能因网络延迟、服务故障而超时,需要设置合理超时时间(一般 5-30 秒)和重试机制(指数退避)。对于幂等操作(查询),可以安全重试;对于非幂等操作(下单),需要去重或事务补偿。

工具调用的错误应该转换为模型能理解的反馈。网络错误返回"服务暂时不可用,请稍后重试",参数错误返回"乘客人数必须在 1-9 之间",业务错误返回"该航班已售罄"。这些反馈让模型能够向用户解释失败原因,并建议替代方案。

多步调用与链式推理

复杂任务可能需要多个工具协作。"订一张去北京的机票,然后查一下那里的天气"需要先调用 book_flight,再调用 get_weather。模型需要理解任务顺序,将前一个工具的输出作为后一个工具的输入。实现方式是在每轮工具调用后,将函数结果附加到对话历史,让模型基于最新结果决定下一步。

并行调用能提升效率。查询多个城市的天气、搜索多个关键词的信息,这些操作相互独立,可以同时发起。OpenAI API 支持在单次响应中返回多个 tool_calls,程序并行执行后汇总结果。判断是否可以并行需要模型理解任务依赖关系,这是更高级的能力。

循环调用用于迭代优化。"搜索价格低于 5000 元的机票"可能第一次搜索结果都高于预算,模型可以调用筛选工具过滤结果,或者调整搜索参数重新调用。循环需要设置终止条件(最大迭代次数、结果满意度),避免无限循环。

安全与权限

工具调用打开了从 LLM 到外部世界的通道,必须考虑安全性。参数注入攻击通过精心构造的输入,让工具执行非预期操作(SQL 注入、命令注入)。防御方法包括严格的参数校验、使用参数化查询、限制可执行的操作范围。

权限控制确保模型只能调用授权的工具。用户级别的权限(普通用户不能调用管理员函数)、资源级别的权限(用户只能查询自己的订单)、操作级别的权限(只读工具 vs 读写工具)。权限校验应该在工具执行前进行,失败时返回明确的权限错误。

审计日志记录所有工具调用,包括调用者、时间、函数名、参数、结果。这不仅是安全要求,也是问题排查和优化的数据来源。日志应该脱敏敏感信息(密码、身份证号),并保留合理时长(一般 30-90 天)。

框架支持

LangChain 的 @tool 装饰器简化了工具定义,自动生成 JSON Schema 和文档字符串。StructuredTool.from_function() 可以将现有函数转换为工具。LangChain 还支持工具的并行调用、错误处理、输入验证。

LlamaIndex 的 FunctionCallingAgent 专注于检索增强场景,工具可以是查询向量数据库、调用搜索引擎。它的特色是自动将检索结果注入上下文,减少工具调用轮次。

Vercel AI SDK 的 useChat hook 前端工具调用,让前端也能响应模型的工具请求。这对于需要前端操作的场景(切换主题、导航页面)很有用,但也需要谨慎处理安全边界。