让 AI 调用外部工具,该怎么设计?
一个"只会说不会做"的 AI
你做了一个 AI 助手,用户问它:"今天北京天气怎么样?"
AI 回答:"作为 AI,我无法获取实时天气信息。建议您查看天气预报网站或使用手机天气应用。"
用户又问:"我的订单到哪了?"
AI 回答:"我无法查询您的订单信息。请联系客服或登录官网查看。"
两次对话,两次"我不能"。
用户失望地离开了。你做了个只能聊天的 AI,不能查天气、不能查订单、不能调 API、不能做任何"实事"。
这不是 AI 的能力问题。大语言模型本身确实不能访问外部数据——它的知识来自训练数据,它不能上网、不能查数据库、不能调接口。
但你可以教它"用工具"。
这就是今天要聊的主题——Tool Use(工具调用),也叫 Function Calling。
Tool Use 是什么?
一句话:让 AI 决定"调用哪个工具"和"传什么参数",然后由你的代码去执行。
流程是这样的:
用户: "北京今天天气怎么样?"↓AI: 我需要调用 get_weather 工具,参数是 city="北京"↓你的代码: 调用天气 API,拿到结果 {"temp": 28, "weather": "晴"}↓AI: 北京今天天气晴朗,气温 28°C。
AI 不直接调用工具。 AI 只负责"决定"调用哪个工具、传什么参数。真正的执行由你的代码完成。AI 的角色是"决策者",你的代码是"执行者"。
这个设计非常优雅——AI 不需要有执行能力,只需要有"判断力"。
怎么告诉 AI 有哪些工具?
你需要给 AI 提供一份"工具说明书",告诉它每个工具的名称、用途和参数。
这份说明书的格式叫Tool Description,是一个 JSON Schema。
tools = [{"type": "function","function": {"name": "get_weather","description": "查询指定城市的当前天气信息,包括温度、天气状况、湿度","parameters": {"type": "object","properties": {"city": {"type": "string","description": "城市名称,如'北京'、'上海'"}},"required": ["city"]}}},{"type": "function","function": {"name": "get_order_status","description": "查询用户的订单状态,返回物流信息和预计送达时间","parameters": {"type": "object","properties": {"order_id": {"type": "string","description": "订单号,如 ORD-20240101-001"}},"required": ["order_id"]}}}]
关键要素:
name:工具名称,要简洁明确。 get_weather比weather_tool好。description:工具用途,这是 AI 决定是否调用的依据。写清楚"什么时候该用这个工具"。 parameters:参数的 JSON Schema,包括类型、描述、是否必填。
description 写得好不好,直接影响 AI 的工具选择准确率。 下面展开说。
Tool Description 怎么写才好?
这是 Tool Use 最容易被忽视、也最影响效果的环节。
原则一:描述"什么时候用",而不是"怎么实现"
# 差:只描述了实现细节"description": "调用天气 API 获取数据"# 好:描述了使用场景"description": "当用户询问某个城市的天气、气温、是否下雨等问题时调用。只支持中国大陆城市。"
原则二:参数描述要包含格式和约束
# 差:参数描述太简单"order_id": {"type": "string", "description": "订单号"}# 好:包含格式说明"order_id": {"type": "string", "description": "订单号,格式为 ORD-YYYYMMDD-XXX,如 ORD-20240101-001"}
原则三:用 enum 限制选项
# 用 enum 限制 AI 的选择范围"unit": {"type": "string","enum": ["celsius", "fahrenheit"],"description": "温度单位"}
原则四:在 description 中写明"不适用"的情况
"description": "查询订单物流状态。不支持查询退款进度,退款请用 get_refund_status 工具。"这能有效防止 AI 调错工具。
完整实战:多工具 AI 助手
来看一个完整的实现——一个能查天气、查订单、查汇率的 AI 助手。
"""多工具 AI 助手pip install openai"""import jsonfrom openai import OpenAIclient = OpenAI()# ===== 第一步:定义工具 =====tools = [{"type": "function","function": {"name": "get_weather","description": "查询指定城市的当前天气。当用户问天气、气温、是否下雨时调用。只支持中国大陆城市。","parameters": {"type": "object","properties": {"city": {"type": "string", "description": "城市名称,如'北京'、'上海'"}},"required": ["city"]}}},{"type": "function","function": {"name": "get_order_status","description": "查询订单的物流状态和预计送达时间。需要用户提供订单号。","parameters": {"type": "object","properties": {"order_id": {"type": "string", "description": "订单号,格式 ORD-YYYYMMDD-XXX"}},"required": ["order_id"]}}},{"type": "function","function": {"name": "convert_currency","description": "货币汇率转换。支持 USD、EUR、CNY、JPY、GBP。","parameters": {"type": "object","properties": {"amount": {"type": "number", "description": "金额"},"from_currency": {"type": "string", "enum": ["USD", "EUR", "CNY", "JPY", "GBP"], "description": "源货币"},"to_currency": {"type": "string", "enum": ["USD", "EUR", "CNY", "JPY", "GBP"], "description": "目标货币"}},"required": ["amount", "from_currency", "to_currency"]}}}]# ===== 第二步:实现工具函数 =====def get_weather(city):"""模拟天气 API"""mock_data = {"北京": {"temp": 28, "weather": "晴", "humidity": 45},"上海": {"temp": 31, "weather": "多云", "humidity": 72},}return mock_data.get(city, {"error": f"暂不支持 {city} 的天气查询"})def get_order_status(order_id):"""模拟订单 API"""return {"order_id": order_id,"status": "配送中","carrier": "顺丰速运","eta": "2024-01-15"}def convert_currency(amount, from_currency, to_currency):"""模拟汇率 API"""rates = {"USD": 1, "CNY": 7.2, "EUR": 0.92, "JPY": 148, "GBP": 0.79}result = amount / rates[from_currency] * rates[to_currency]return {"amount": round(result, 2), "from": from_currency, "to": to_currency}# 工具映射表tool_map = {"get_weather": get_weather,"get_order_status": get_order_status,"convert_currency": convert_currency,}# ===== 第三步:对话循环 =====def chat(user_input, history=None):if history is None:history = [{"role": "system", "content": "你是一个贴心的 AI 助手,可以帮用户查天气、查订单、算汇率。"}]history.append({"role": "user", "content": user_input})# 第一轮:让 AI 决定是否调用工具response = client.chat.completions.create(model="gpt-4o-mini",messages=history,tools=tools,tool_choice="auto" # 让 AI 自动决定是否调用)msg = response.choices[0].message# 如果 AI 决定调用工具if msg.tool_calls:history.append(msg)for tool_call in msg.tool_calls:func_name = tool_call.function.namefunc_args = json.loads(tool_call.function.arguments)# 执行工具result = tool_map[func_name](**func_args)# 把工具结果返回给 AIhistory.append({"role": "tool","tool_call_id": tool_call.id,"content": json.dumps(result, ensure_ascii=False)})# 第二轮:AI 基于工具结果生成最终回答final_response = client.chat.completions.create(model="gpt-4o-mini",messages=history)answer = final_response.choices[0].message.contentelse:# AI 不需要调用工具,直接回答answer = msg.contenthistory.append({"role": "assistant", "content": answer})return answer# ===== 测试 =====print(chat("北京今天天气怎么样?"))# 输出:北京今天天气晴朗,气温 28°C,湿度 45%,适合出行。print(chat("帮我查一下订单 ORD-20240101-001 到哪了"))# 输出:您的订单 ORD-20240101-001 目前正在配送中,由顺丰速运承运,预计 1 月 15 日送达。print(chat("100 美元换成人民币是多少?"))# 输出:100 美元约合 720 人民币。
整个流程分两步:
AI 根据用户问题 + 工具列表,决定调用哪个工具、传什么参数 你的代码执行工具,把结果返回给 AI,AI 生成最终回答
工具太多反而效果差?
一个常见的误区:给 AI 注册越多工具越好。
事实是:工具数量超过 10-15 个时,AI 的工具选择准确率会明显下降。
原因很简单——AI 需要在所有工具中"挑选"最合适的那个。工具越多,干扰越多,选错的概率越大。
解决方案一:分层工具
把工具分成"类别",先选类别,再选具体工具。
# 第一层:AI 先选类别categories = [{"name": "weather", "description": "天气相关查询"},{"name": "order", "description": "订单和物流查询"},{"name": "finance", "description": "金融和汇率查询"},]# 第二层:根据类别加载对应工具def get_tools_for_category(category):tool_sets = {"weather": [get_weather_tool, get_forecast_tool],"order": [get_order_status_tool, get_order_detail_tool, cancel_order_tool],"finance": [convert_currency_tool, get_stock_price_tool],}return tool_sets.get(category, [])
解决方案二:动态工具加载
根据用户的意图,动态决定加载哪些工具。
def detect_intent(user_input):"""简单的意图检测"""if any(w in user_input for w in ["天气", "气温", "下雨"]):return "weather"elif any(w in user_input for w in ["订单", "物流", "快递"]):return "order"elif any(w in user_input for w in ["汇率", "换算", "美元"]):return "finance"return "general"# 根据意图加载工具intent = detect_intent(user_input)tools = get_tools_for_category(intent)
解决方案三:工具描述优化
确保每个工具的 description 足够精确,让 AI 能快速判断是否适用。模糊的描述是工具选择错误的主要原因。
生产环境的注意事项
1. 工具执行要有超时和重试。 外部 API 可能慢或挂,不能让 AI 一直等。
import httpxdef call_api_with_timeout(url, timeout=5):try:resp = httpx.get(url, timeout=timeout)return resp.json()except httpx.TimeoutException:return {"error": "请求超时,请稍后重试"}except Exception as e:return {"error": f"请求失败: {str(e)}"}
2. 工具结果要做格式校验。 AI 传来的参数可能不符合预期,要验证后再执行。
3. 敏感操作要加确认。 比如"取消订单"这种操作,不能让 AI 直接执行,要让用户确认。
4. 记录工具调用日志。 每次调用什么工具、传了什么参数、返回了什么结果,都要记录。出了问题好排查。
行动清单
从 3 个工具开始:先选最核心的 3 个工具,跑通流程后再逐步扩展。
把 description 当产品文档写:给每个工具写清楚"什么时候用"和"什么时候不用",这是影响准确率的第一因素。
加参数校验:AI 传来的参数不一定合法,执行前要验证。
监控工具调用成功率:记录每次调用是成功还是失败,失败原因是什么。这是优化工具描述的数据基础。
下篇预告
Tool Use 让 AI 能"做事"了。但你可能注意到一个问题——AI 的输出是自然语言,格式不稳定。
你让它返回 JSON,它有时候返回 JSON,有时候返回一段解释文字,有时候返回一个带注释的代码块。你写了解析逻辑,三天两头报错。
下一篇,我们来解决这个痛点——《怎么让 AI 稳定输出 JSON 并直接入库?》
夜雨聆风