AI工具调用五步落地:从定义到自愈
【免费下载链接】coursesAnthropic's educational courses项目地址: https://gitcode.com/GitHub_Trending/cours/courses
你让模型"帮我查下订单",它回了一句"我无法访问数据库"——问题不在模型,在你没把工具交出去。一套能跑的 AI工具调用 系统只有三个角色:你的应用、模型、工具函数,数据按 App→Model→Tool→Model 闭环流转。这篇文章用"查用户→查订单→取消订单"这条客服链路,把定义到自愈的完整过程走一遍。
1. 拆开黑盒:一句"查订单"的数据链路
场景:用户发一句"帮我查 ORD-24601 的物流状态"。
第 1 步,你的应用把两样东西发给模型:用户这句话,加上一组工具定义(name、description、input_schema)。第 2 步,模型不直接回答,而是返回一个tool_use块,声明"我要调 get_order_by_id,入参是 24601"。第 3 步,真正执行函数的是你的代码,拿到结果后写成tool_result消息塞回对话。第 4 步,模型基于工具数据生成自然语言回复。
这里有个容易被忽略的事实:模型从头到尾没有"执行"过工具,它只负责声明想调用什么;每一次真实执行都发生在你的进程里。你试着想想:用户只说了"帮我查下订单"但没给订单号,此时模型下一步该做什么?答案不在这篇文章里,06_chatbot_with_multiple_tools.ipynb 的系统提示词里藏得很清楚。
2. 动手前的三把锁:工具定义规范
🛠️ 工具定义是模型和你之间签的合同,合同写得含糊,后面所有的错都在你这边。
名称锁。名字要唯一、语义明确,动词+宾语直接说明动作:
# 反例:模型看不出它什么时候该用这个工具 bad = {"name": "helper", "description": "does some data thing"} # 正例:名字即用途,description 写清返回内容 good = {"name": "get_order_by_id", "description": "根据订单ID查询订单详情,返回订单号、商品、数量、价格、状态"}名字是模型选工具的索引,动词+宾语让它不用猜。
参数锁。类型、格式、必填项一个都不能少:
# 反例:无类型、无必填,模型只能凭感觉传参 bad = {"name": "calculator", "input_schema": { "type": "object", "properties": {"op": {}, "a": {}, "b": {}}}} # 正例:enum 限定合法值,required 保证必填 good = {"name": "calculator", "input_schema": { "type": "object", "properties": { "operation": {"type": "string", "enum": ["add", "subtract", "multiply", "divide"]}, "operand1": {"type": "number"}, "operand2": {"type": "number"}}, "required": ["operation", "operand1", "operand2"]}}enum 把模型的搜索空间收窄到 4 个合法值,required 告诉它哪几个字段不能省,错传的概率直接掉下去。
边界锁。工具之间职责不重叠:
# 反例:两个工具职责模糊,模型只能靠掷骰子 tools_bad = [ {"name": "get_user_info", "description": "fetch user data"}, {"name": "get_customer_info", "description": "fetch customer data"} ] # 正例:一个工具管一件事 tools_good = [ {"name": "get_user", "description": "按 email/phone/username 查找用户"}, {"name": "get_customer_orders", "description": "按 customer_id 列出该用户全部订单"} ]职责不重叠时,模型的活儿只剩"名字+描述匹配",选错工具的代价被锁死了。
3. 让输出变成管道燃料:免解析的结构化返回
核心论点只有一句:工具与模型之间传递的必须是机器可直接消费的 JSON,而不是"看起来像答案的文本"。以情感分析为例,schema 片段:
{ "type": "object", "properties": { "positive_score": {"type": "number"}, "negative_score": {"type": "number"}, "neutral_score": {"type": "number"} }, "required": ["positive_score", "negative_score", "neutral_score"] }模型返回的实际内容就一行,拿起来就能用:
{"positive_score": 0.0, "negative_score": 0.9, "neutral_score": 0.1}反过来推演一下:如果这里返回的是"这条推文明显是负面的,负面情绪较强",下游怎么办?要么写正则去抠数字,要么再付一轮推理让另一个模型把文本解析成数字——两条路都给流水线增加一个会坏的环节,而且失败时你很难定位是谁说错了话。JSON 字段则可以直接进 if/else、直接写库、直接当下一环工具的入参。
4. 多工具编排的三种拓扑
💡 单一工具解决不了真实业务,三个工具的编排无非三种拓扑,先认脸再选图:
链式。用户报邮箱,get_user查出 customer_id,get_customer_orders拿这个 id 查订单。前者的输出就是后者的输入,顺序不能跳。
扇出-扇入。用户问"总结我最近三笔订单",并行调 3 次get_order_by_id,结果全部汇合后再交给模型生成一份总结。并行省时间,汇合保证总结不缺数据。
条件分叉。查到订单后,如果存在 "Processing" 状态就可以继续调cancel_order;如果全是 "Delivered",直接拒绝并解释。分支条件由中间结果决定,而不是用户输入决定。
一个把三种拓扑压在一起的简化 DAG:
workflow: steps: - id: find_user tool: get_user input: {key: email, value: "{{user.email}}"} - id: list_orders # 链式:消费上一步输出 tool: get_customer_orders input: {customer_id: "{{find_user.output.id}}"} - id: cancel # 条件分叉:有可取消订单才走 tool: cancel_order when: "{{list_orders.output.has_pending}}" input: {order_id: "{{list_orders.output.first_pending}}"}{{step.output}}是依赖占位符,执行引擎按拓扑序做替换;when为假则跳过该分支。
5. 它会坏,而且会经常坏
上线第一周,你的工具调用失败率不会是零。把错误分四类,各配一个固定动作:
| 错误类型 | 典型表现 | 应对动作 |
|---|---|---|
| 参数错误 | 模型传错字段名或格式 | 返回具体错误信息,让模型自纠后重试 |
| 超时 | 工具慢或依赖服务挂了 | 限时重试,超限走 fallback |
| 逻辑错误 | 返回空数据或与预期不符 | 当作"无结果",提示模型换工具或向用户追问 |
| 权限错误 | 工具调用无授权 | 不重试,立即转人工 |
每个工具调用都应该有一份这样的规格:
call_spec = { "tool": "get_order_by_id", "parameters": {"order_id": "24601"}, "timeout": 5, # 秒,超过即记失败 "max_retries": 2, # 临时错误最多重试两次 "fallback": "ask_user_for_order_id" # 仍失败,降级为向用户要信息 }边界线划在这里:凡是向外写数据的操作——发邮件、取消订单、退款——都不允许自动重试,自愈机制只对"读"操作有管辖权;写操作失败必须等人。
6. 一个能跑的最小实例:三工具客服
把前面所有要点压进一个可复现场景:TechNova 线上客服,查用户→查订单→取消订单。完整代码在 06_chatbot_with_multiple_tools.ipynb,三步走查:
Step 1:定义工具并建分发表。4 个工具(get_user、get_order_by_id、get_customer_orders、cancel_order)各带 name/description/input_schema,再写一个按名字分发的函数:
def process_tool_call(tool_name, tool_input): if tool_name == "get_user": return db.get_user(tool_input["key"], tool_input["value"]) if tool_name == "get_order_by_id": return db.get_order_by_id(tool_input["order_id"])Step 2:发请求并判断停因。带上tools调 API,检查response.stop_reason:是"tool_use"说明模型要调工具,否则就是普通回复,直接结束本轮。
Step 3:回传结果,让模型基于数据作答。执行工具、把结果写成tool_result追加进 messages,再发回模型:
if response.stop_reason == "tool_use": tool_result = process_tool_call(tool_use.name, tool_use.input) messages.append({"role": "user", "content": [{ "type": "tool_result", "tool_use_id": tool_use.id, "content": json.dumps(tool_result) }]})一轮对话跑完,messages 列表正好长成下面的四段结构:初始提示→tool_use→tool_result→最终回答。
系统提示词里还有两个值得抄的细节:信息不足时模型必须追问而不是瞎调工具;用户可见的回复统一包在<reply></reply>标签里,方便解析。
7. 下一步往哪走
- 结构化输出深入 —— 把任意文本产出变成可直接消费的 JSON,是工作流自动化的地基 —— 03_structured_outputs.ipynb
- 工具选择策略 —— 吃透 auto / any / tool 三种模式,让模型该用工具时不用漏、不该用时不乱调 —— 05_tool_choice.ipynb
- 完整工作流复盘 —— 把 tool_use → tool_result 的消息循环从头到尾再走一遍,补齐边角细节 —— 04_complete_workflow.ipynb
今晚就挑一个你手头真实的重复任务——查个数、填个模板、拉一笔订单——把它写成工具定义,让模型学着用上它。
【免费下载链接】coursesAnthropic's educational courses项目地址: https://gitcode.com/GitHub_Trending/cours/courses
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考