1. 是什么
Function Calling 是大模型 API 的原生能力(OpenAI/豆包等)。开发者在代码里定义函数名、参数 JSON Schema 注入 Prompt,大模型理解意图后决定是否调用并输出符合 Schema 的参数 JSON。是模型连接物理世界的"手和脚"。
2. 工作机制
- 定义工具 Schema(名称/描述/参数类型)喂给模型。
- 模型判断当前是否需要调工具。
- 需要则输出
{函数名, 参数}的 JSON。 - 框架解析 JSON 在本地执行对应函数。
- 结果返还模型,模型据此生成最终回复。
3. vs MCP
Function Calling 是"动作执行的格式规范"(模型侧能力),MCP 是"工具接入的标准化基建"(工具侧协议)。
- FC:与项目代码耦合,切模型要重写对接逻辑。
- MCP:Client-Server 架构,解耦、可迁移,一处编写到处运行。
- 项目实践:简单工具用 FC;多项目共用服务(TTS/OCR/ASR)封装 MCP Server。
详见 → MCP协议
4. vs RAG
FC 是模型的"手和脚"(调工具执行动作、查动态数据);RAG 是"回答前翻私有字典"(检索增强、答静态知识)。两者可结合:RAG 答文档问题,FC 查工单库等动态数据。
5. 结构化输出关系
Function Calling / Tool Use 本身就是一种结构化输出方式——模型输出符合特定 Schema 的参数,可用来强制约束输出格式,减少自由文本幻觉。
6. 高频追问速答
❓ 常见追问 · 模型不支持 FC 怎么办? → 退化为 Prompt 里描述工具 + 要求输出固定 JSON,后端解析(不如原生 FC 稳定)。
❓ 常见追问 · FC 调用失败怎么处理? → 指数退避重试(3~5 次)、参数校验、备用模型降级。
❓ 常见追问 · 怎么防止乱调工具? → 工具描述清晰 + 白名单 + 对输出的 Tool Call JSON 做正则校验和权限拦截(防 Prompt Injection 越权)。
深度拆解:容易踩坑的点
维度一:工程实现
⚠️ 模型可能编造不存在的参数或给错类型(参数幻觉),返回的 JSON 甚至可能不合法,怎么做校验和容错?
分层防御:①解析层用 JSON Schema(如 jsonschema/Pydantic)强校验字段名、类型、必填项,不合法直接打回;对"编造字段"在 Schema 里设 additionalProperties: false 直接拒绝未定义参数;②校验失败不要直接终止或报错给用户,而是把错误信息(哪个字段、期望什么类型)作为 tool 角色消息回传给模型,让模型自己纠正重试(通常1~2轮内收敛);③设最大重试次数,超过则降级为让模型用自然语言向用户追问缺失信息,而不是硬凑参数糊弄过去;④如果模型支持 Structured Outputs/strict 模式(如 OpenAI 的 strict:true),优先用其约束采样,从生成层面减少幻觉,而不是纯靠后校验补救。
工具调用失败或超时,模型是重试、换工具还是硬编答案?失败怎么反馈给模型?
先区分失败类型:网络超时/限流是"可重试",参数错误/权限拒绝是"不可重试需改参数",工具不存在或下线是"不可恢复需换路径"。对可重试错误做指数退避重试(2~3 次带上限);对不可恢复错误直接构造一条 role=tool 的错误结果消息(含错误类型和简要原因)回传模型,让模型基于错误信息决定换工具、向用户澄清,还是承认无法完成——绝不能因为工具挂了就让模型凭"常识"硬编一个看似合理的答案伪装成执行结果,这比不调用更危险。同时设总轮次/总耗时熔断,防止模型陷入"调用失败→重试→再失败"的死循环。
维度二:安全防护
⚠️ 模型自主决定调哪个工具,如果它调了删除/转账类高危工具怎么办?工具权限和确认怎么设计?
核心原则:模型只有"建议权",没有"执行权"。具体:①工具分级,只读查询类可自动执行,写类/不可逆类(删除、转账、发送)必须走 Human-in-the-loop 二次确认——模型输出的只是"调用意图",由后端拦截、展示给用户确认后才真正执行;②工具粒度设计上避免暴露 execute_sql、delete_by_id(id) 这类过宽的原子操作,而是设计业务语义明确、参数受限的动作(如 cancel_order(order_id));③后端要有独立于模型的权限校验层——不能假设模型的判断等于授权,同一会话对应的权限/资产范围要在执行层再校验一遍(如转账金额上限、只能操作自己名下的订单),不能把模型输出直接当成"已授权的操作";④对高危操作留审计日志,记录调用的上下文依据,出问题可追溯。
工具入参来自模型输出,等于把不可信输入传给后端,怎么防注入/越权?
把模型输出的参数当成和"用户直接提交的表单"同等不可信的外部输入对待,走标准后端安全防线:①参数进入业务逻辑前统一做 Schema 校验+白名单枚举(如 status 只能是预定义值,不能是模型编的任意字符串);②凡是要拼接到 SQL/Shell/文件路径的参数必须走参数化查询/白名单转义,不能因为"参数来自可信模型的 JSON"就跳过防注入检查,因为模型的输入源头可能被间接提示注入污染(如工具返回的外部文档内容反过来操纵下一轮调用参数);③执行层做越权校验(这个 order_id 是否属于当前会话用户),不能信任模型给出的 user_id/tenant_id 这类可被诱导篡改的身份字段,这类参数应由后端从会话上下文注入,而不是让模型自己填。
维度三:性能优化
多轮 FC(调工具→看结果→再调)串行延迟高,怎么并行/减少轮次?
①并行调用:如果模型一次响应里给出多个互不依赖的 tool_call(多数新模型原生支持 parallel tool calls),框架应并发执行这些工具而非顺序执行,全部返回后一次性喂回模型;②减少轮次:通过更完整的工具描述和示例让模型倾向"一次想清楚需要哪些信息再并发调用",而非一步步试探;对确定性强的多步流程用工作流编排(提前定义好的 DAG/Chain)替代让模型每步都决策,只在有分支判断的地方才交给模型选择;③对幂等的只读工具(查天气、查字典)做结果缓存,命中则跳过真实调用和网络往返;④流式提前触发:不必等模型输出完整文字才解析 tool_call,检测到 JSON 片段完整即可提前执行工具,与模型输出其余文本并行;⑤设轮次上限,超过强制让模型基于已有信息收敛输出而不是无限调用。
维度四:架构设计
FC 和 MCP 的边界——什么时候用原生 FC、什么时候封装成 MCP Server?滥用 FC 会有什么维护问题?
判断标准看"复用范围"和"变更频率":单一项目内、工具逻辑简单、不需要跨项目/跨模型复用(比如某 Agent 专属的一个内部计算函数),直接用原生 FC 足够,引入 MCP 反而增加一层 Client-Server 通信开销和部署复杂度;如果工具是被多个项目/多个 Agent 共用的能力(如 TTS、OCR、企业统一工单查询),或未来可能换模型厂商、需要工具与业务解耦独立部署/独立鉴权,就该封装成 MCP Server,一处实现多处接入,还能借助现成的 MCP Server 生态而不必重复造轮子。滥用原生 FC 的典型维护问题:①工具定义和执行逻辑散落在各项目代码里,同一工具在不同项目重复实现、行为不一致;②换模型厂商时,各家 FC 的 JSON Schema 细节、多轮 tool 消息格式有差异,所有工具定义要跟着重写、回归测试成本高;③工具数量增长后全堆在一个 Prompt 里做函数选择,会导致 Prompt 过长、模型选错工具概率上升,而原生 FC 没有 MCP 那样的服务发现/动态加载机制来缓解。
评论