Function Call 让模型从聊天到干活
作者:程序员马丁
Ragent AI —— 从 0 到 1 纯手工打造企业级 Agentic RAG,拒绝 Demo 玩具!AI 时代,助你拿个offer。
上一篇讲完了 RAG 的生成策略,你已经能搭建一个完整的 RAG 系统:用户问知识库里的问题,系统检索相关文档,生成答案,还能标注引用来源。看起来很完美,但实际跑起来你会发现——用户的需求不只是查文档。
假设你在一家公司做企业知识库助手,用户问“公司的年假制度是什么”,RAG 系统检索到《员工手册》里的年假政策,回答得很好。但用户接着问“我还剩几天年假”,系统就懵了:知识库里有年假制度,但没 有这个用户的具体年假数据。这需要查 HR 系统,而 RAG 系统只会检索文档,不会调接口。
再比如用户问“帮我查订单 #12345 的物流”,RAG 系统检索到物流查询的操作指南,但查不到这个订单的实时物流信息——这需要调物流系统的 API。
这就是 RAG 的能力边界:它能回答知识库里的问题,但干不了活。接下来要讲的 Function Call(函数调用),就是让 RAG 系统从只能查知识库升级到能查数据、能调接口、能干活。
从查知识库到干活:RAG 的能力边界
1. RAG 只能查知识库的局限
回顾一下 RAG 的工作流程:用户提问 → 向量检索 → 召回相关文档 → 生成答案。整个流程的数据来源是知识库(向量数据库里存的文档 chunk),所以 RAG 只能回答“知识库里有什么”。
但企业场景下,用户的需求远不止查文档:
- 查实时数据:我还剩几天年假?我这个月的考勤记录?我的销售业绩排名?
- 查业务状态:订单 #12345 的物流到哪了?工单 #678 处理到哪一步了?
- 执行操作:帮我请 3 天年假、帮我提交报销申请、帮我发一条通知给项目组
这些需求的共同点是:答案不在知识库里,需要调用业务系统的接口或数据库查询。
2. 传统方案的问题
面对这种需求,传统的做法有两种:
2.1 方案一:在 Prompt 里写死兜底回复
如果用户问年假余额,请回复:"您可以访问 HR 系统查询年假余额,地址:https://hr.company.com"
如果用户问订单物流,请回复:"您可以在订单详情页查看物流信息"
这种方案太死板,用户体验差。用户问“我还剩几天年假”,系统回复“请访问 HR 系统查询”,用户心里想:你不就是个智能助手吗,为什么不能直接告诉我?
2.2 方案二:用规则匹配用户意图,然后调接口
if (userQuestion.contains("年假")) {
int days = hrSystem.getAnnualLeave(userId);
return "您还剩 " + days + " 天年假";
}
if (userQuestion.contains("订单") && userQuestion.contains("物流")) {
String status = logisticsSystem.getOrderStatus(orderId);
return "订单物流状态:" + status;
}
这种方案的问题是:规则写不完,维护成本高。用户可能问“我的假期余额”、“还有多少天假”、“年假还剩多少”,你要为每种表达都写一条规则吗?而且用户问题里可能没有明确的关键词,比如“我想请假,但不知道还有没有额度”,这种问题用规则很难匹配。
更关键的是,这种方案把判断用户意图的逻辑硬编码在代码里,每次新增一个工具(比如新增查考勤记录的功能),你都要改代码、加规则、重新部署。
我们需要一种更灵活的方案:让模型自己判断什么时候该调工具、该调哪个工具、该传什么参数。这就是 Function Call。
Function Call 是什么
1. Function Call 的本质:模型输出调用意图
先说结论:模型不是真的调函数,它只是输出一个 JSON,告诉你“我觉得应该调某个函数,参数是这些”。真正执行函数的是你的代码。
打个比方:你去餐厅吃饭,服务员(模型)拿着菜单(工具列表)问你想吃什么(用户问题)。你说“我想吃点清淡的”,服务员根据菜单推荐“那来一份清蒸鲈鱼吧”(输出调用意图)。但服务员不会做菜,做菜的是厨房(你的代码)。服务员只是把你的需求翻译成厨房能理解的订单(JSON 格式的函数调用)。
具体来说,Function Call 的流程是这样的:
- 你定义一些工具(函数),每个工具有名字、描述、参数定义
- 把工具列表和用户问题一起发给模型
- 模型判断需要调用哪个工具,输出一个 JSON(叫
tool_calls),里面包含函数名和参数 - 你解析这个 JSON,执行对应的函数,拿到结果
- 把函数执行结果返回给模型
- 模型基于结果生成最终答案
注意第 3 步和第 4 步的区别:模型只是输出“应该调 getUserAnnualLeave 函数,参数是 userId=12345”,但不会真的去调这个函数。你的代码拿到这个 JSON 后,才去执行 getUserAnnualLeave(12345),拿到结果 (比如:剩余 5 天),然后把结果返回给模型。
2. Function Call 的完整流程
用一个完整的例子说明:
场景:用户问“我还剩几天年假”
第一轮:发送工具列表和用户问题
你的代码构建一个请求,包含:
- 用户问题:我还剩几天年假
- 工具列表:
getUserAnnualLeave(查询用户年假余额)
发送给模型。
第一轮响应:模型输出调用意图
模型分析用户问题,发现需要查年假余额,于是输出:
{
"tool_calls": [
{
"id": "call_abc123",
"type": "function",
"function": {
"name": "getUserAnnualLeave",
"arguments": "{\"userId\": \"12345\"}"
}
}
]
}
注意模型并没有生成最终答案,而是告诉你需要调用 getUserAnnualLeave 函数。
你的代码执行函数
你解析 tool_calls,发现要调 getUserAnnualLeave,参数是 userId=12345。你执行这个函数(可能是查数据库、调 HR 系统 API),拿到结果:
{
"remainingDays": 5,
"totalDays": 10,
"usedDays": 5
}
第二轮:把结果返回给模型
你构建第二轮请求,包含:
- 第一轮的用户问题
- 第一轮的模型响应(带
tool_calls) - 函数执行结果(上面的 JSON)
发送给模型。
第二轮响应:模型生成最终答案
模型基于函数执行结果,生成最终答案:
您还剩 5 天年假(总共 10 天,已使用 5 天)。
整个流程是一个多轮对话:第一轮模型输出调用意图,你执行函数,第二轮模型基于结果生成答案。
下面用时序图展示完整流程:

3. 为什么需要 Function Call
对比一下没有 Function Call 的方案:
方案一:在 Prompt 里写死规则
- 问题:不灵活,用户体验差
- Function Call 的优势:模型根据用户问题动态判断是否需要调工具,用户无感知
方案二:用传统 NLP 做意图识别
- 问题:规则写不完,维护成本高,难以处理复杂表达
- Function Call 的优势:模型理解自然语言,能处理各种表达方式("我还剩几天年假"、"我的假期余额"、"年假还有多少"都能识别)
方案三:让模型在回答里生成函数调用代码
- 问题:模型可能生成错误的代码,不安全,难以解析
- Function Call 的优势:输出格式标准化(JSON),易于解析,参数类型有校验
Function Call 的核心优势是:把判断什么时候该调工具的逻辑交给模型,把执行工具的逻辑留给项目代码。模型擅长理解自然语言和意图识别,你的代码擅长执行具体的业务逻辑,各司其职。
下面用流程图展示模型如何判断是否调用工具:
