Files

5.1 KiB
Raw Permalink Blame History

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
function-calling
Function Calling
Tool Calling
JSON Schema
调用循环
参数校验
intermediate
python.python.typing-errors-testing
3
ai.mcp.index
ai.langgraph.state-graph
ai.function-calling.interview
10
1

工具调用原理:工具定义、调用循环、参数校验、并行调用和错误回传

各家模型的工具调用格式大同小异,本课以 OpenAI Chat Completions 的格式为例,字段以各家官方文档为准:https://platform.openai.com/docs/guides/function-calling

解释说明

模型本身不会执行任何代码。 工具调用(Function Calling / Tool Calling)的流程是:

  1. 你把工具的名字、描述、参数的 JSON Schema 和对话一起发给模型;
  2. 模型判断需要工具时,返回 tool_calls(工具名 + JSON 字符串参数),而不是文本答案;
  3. 你的代码校验参数、检查权限、执行工具;
  4. 把结果作为 role: "tool" 的消息(带 tool_call_id)加回对话,再次请求模型;
  5. 重复,直到模型返回普通文本答案,或者达到最大步数。

这就是所有 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 的完整流程?模型在其中做了什么、没做什么?
  • 模型返回的参数不合法怎么办?
  • 有副作用的工具怎么保证安全?