5.1 KiB
5.1 KiB
id, title, type, domain, stack, tags, level, prerequisites, est_hours, related, week, order
| id | title | type | domain | stack | tags | level | prerequisites | est_hours | related | week | order | |||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| ai.function-calling.tool-loop | 工具调用原理:工具定义、调用循环、参数校验、并行调用和错误回传 | lesson | ai |
|
|
intermediate |
|
3 |
|
|
1 |
工具调用原理:工具定义、调用循环、参数校验、并行调用和错误回传
各家模型的工具调用格式大同小异,本课以 OpenAI Chat Completions 的格式为例,字段以各家官方文档为准:https://platform.openai.com/docs/guides/function-calling
解释说明
模型本身不会执行任何代码。 工具调用(Function Calling / Tool Calling)的流程是:
- 你把工具的名字、描述、参数的 JSON Schema 和对话一起发给模型;
- 模型判断需要工具时,返回
tool_calls(工具名 + JSON 字符串参数),而不是文本答案; - 你的代码校验参数、检查权限、执行工具;
- 把结果作为
role: "tool"的消息(带tool_call_id)加回对话,再次请求模型; - 重复,直到模型返回普通文本答案,或者达到最大步数。
这就是所有 Agent 框架的核心循环(ReAct 的工程实现)。LangChain 的 create_agent、LangGraph 的工具节点、MCP 的工具,最后都落到这个循环。
工程上要处理的事
| 问题 | 做法 |
|---|---|
| 参数不可信(格式错、越权、注入) | pydantic 校验;权限在工具内部按当前登录用户校验,不能信任模型传的用户 ID |
| 一次返回多个调用(并行调用) | 逐个(或并发)执行,每个结果带对应的 tool_call_id 回传 |
| 工具执行失败 | 把错误信息作为工具结果回传,让模型修正;不要直接抛异常中断 |
| 有副作用的操作(退款、发邮件、删数据) | 用户确认 / 人工审批;幂等键;审计日志 |
| 无限循环 | 最大步数,超过后转人工 |
| 工具太多 | 工具描述占用上下文、模型选错;按场景动态选择工具子集(见 Agent 设计) |
工具描述怎么写
- 名字动词开头、含义明确:
get_order、create_refund。 - 描述写清楚什么时候用、什么时候不用、返回什么。
- 参数加
description、枚举、格式约束(pattern、minimum)。模型会读这些。
案例
售后助手:用户说“订单 1001 我要退款”。模型第一次传了 order_id: "1001",校验失败(格式应为 A1001),错误回传后模型改成 A1001 查询成功;随后调用退款工具,因为是有副作用的操作,先请求用户确认;最后回复用户。
代码示例
完整可运行版本(带 5 个测试):examples/tool-loop。核心循环:
for step in range(max_steps):
msg = model.chat(messages, tool_schemas)
messages.append(msg)
if not msg.get("tool_calls"):
return msg["content"] # 模型给出最终答案
for c in msg["tool_calls"]:
tool = TOOLS.get(c["function"]["name"])
try:
args = tool.args_model.model_validate_json(c["function"]["arguments"])
if tool.needs_confirm and not confirm(tool.name, args.model_dump()):
result = {"error": "用户拒绝执行该操作"}
else:
result = tool.fn(args, ctx) # ctx 里是当前登录用户,不来自模型
except Exception as e:
result = {"error": f"参数错误:{e}"} # 错误回传给模型
messages.append({"role": "tool", "tool_call_id": c["id"],
"content": json.dumps(result, ensure_ascii=False)})
return "步骤过多,已转人工处理。"
运行输出:
调用 get_order({"order_id": "1001"}) -> {'error': "参数错误:1 validation error for GetOrderArgs ..."}
调用 get_order({"order_id": "A1001"}) -> {'order_id': 'A1001', 'user': 'u1', 'paid': 199.0, 'status': 'delivered'}
调用 refund({"order_id": "A1001", "amount": 199}) -> {'ok': True, 'refunded': 199.0}
回答: 已为订单 A1001 退款 199 元。 步数: 4
易错点
- 以为模型会执行工具:执行、权限、错误处理全是你的代码。
arguments是 JSON 字符串,要解析和校验,可能不是合法 JSON。- 回传结果时漏了
tool_call_id,或者顺序和数量对不上,接口报错。 - 工具结果太长(整张表、整篇文档)塞满上下文:只返回需要的字段,必要时分页。
- 工具结果里包含的文本也可能是提示词注入(例如网页内容),要当作数据而不是指令。
- 让模型决定“是否需要确认”:确认规则应写在代码里。
面试常问
- Function Calling 的完整流程?模型在其中做了什么、没做什么?
- 模型返回的参数不合法怎么办?
- 有副作用的工具怎么保证安全?