从 RAG 检索到 Function Call:理解大模型应用的完整问答链路
前言
最近在学习 RAG 和 AI Agent 相关内容时,我逐渐意识到一个问题:
很多人一开始理解 RAG,只会停留在一句话上:
RAG = 检索增强生成,也就是先查资料,再让大模型回答。
这句话没错,但还不够。
一个真正可用的 RAG 系统,至少要解决三个关键问题:
-
怎么把相关资料准确检索出来?
-
检索出来之后,怎么让大模型基于资料回答,而不是乱编?
-
如果用户的问题不在知识库里,而是要查实时数据或调用业务接口,该怎么办?
这三件事分别对应:
RAG 检索策略
RAG 生成策略
Function Call 工具调用
本文就按照这个顺序,把 RAG 从"查资料"到"生成答案",再到"调用工具"的完整链路梳理一遍。
一、RAG 的整体流程
先看一个完整 RAG 系统的大致流程。
1. 离线建库阶段
离线阶段的目标是:把原始资料处理成可以被检索的知识库。
流程大致是:
原始文档
→ 文本提取
→ 分 chunk
→ 给 chunk 添加元数据
→ 向量化
→ 存入向量数据库
这里每一步都有自己的作用:
-
文本提取:从 PDF、Word、网页等文件中提取纯文本。
-
分 chunk:把长文档切成适合检索的小段。
-
元数据:给每个 chunk 加上来源、标题、时间、权限等信息。
-
向量化:把文本转换成向量,方便做语义检索。
-
向量数据库:存储向量和对应 chunk,支持后续相似度搜索。
简单说,离线阶段就是:
把资料切好、标好、向量化,然后放进知识库。
2. 在线问答阶段
用户真正提问时,进入在线阶段。
流程大致是:
用户提问
→ 问题向量化
→ 检索相关 chunk
→ 混合检索召回
→ Reranker 重排序
→ 取 Top-K chunk
→ 组装 Prompt
→ 调用大模型生成答案
→ 返回答案和引用来源
这就是 RAG 问答的核心链路。
但要注意,RAG 不是简单地:
检索到资料 → 扔给大模型 → 自动得到正确答案
真正的生产级 RAG 要同时关注:
检索质量
生成质量
答案可追溯性
下面分别展开。
二、RAG 检索策略:不能只靠向量检索
很多人刚学 RAG,会以为有了向量数据库就够了。
但实际上,生产环境里的 RAG 不能只依赖向量检索。
因为向量检索擅长语义理解,但不擅长精准关键词匹配。
1. 向量检索:擅长语义理解
向量检索的核心思路是:
把文本转成向量,然后找语义上相近的内容。
它适合处理:
-
同义表达
-
模糊问题
-
自然语言提问
-
语义相似内容
比如用户问:
买了一周的东西还能退吗?
向量检索可能匹配到:
七天无理由退货政策
这就是向量检索的优势:它不要求用户和文档使用完全一样的词,只要语义接近,就能召回。
但是向量检索也有短板。
它对下面这类信息不够敏感:
订单号
商品型号
年份
缩写
专有名词
比如:
订单号 2026012345 的物流状态是什么?
这个问题的关键不是"物流状态"这几个字,而是具体的订单号 2026012345。
如果只靠向量检索,系统可能返回:
物流配送时效说明
订单物流查询入口
物流异常处理流程
这些内容看起来相关,但没有真正命中具体订单。
2. BM25:擅长关键词精准匹配
BM25 是一种经典关键词检索算法,它主要根据关键词匹配程度来判断文档相关性。
它关注三个核心因素:
词频 TF
词的稀有度 IDF
文档长度归一化
可以简单理解为:
-
一个词出现得越多,文档可能越相关。
-
一个词越稀有,区分度越高。
-
长文档不能因为字多就天然占便宜。
BM25 的优势是:精准命中关键词。
比如:
订单号 2026012345
iPhone 16 Pro Max
RMA
2026 年春节
这些词如果出现在某个 chunk 里,很可能就是用户真正需要的内容。
但 BM25 不擅长理解语义。
比如用户问:
买了一周的东西还能退吗?
如果文档里写的是:
签收后 7 天内支持无理由退货
BM25 可能因为关键词不完全一致而匹配不好。
3. 混合检索:向量检索 + BM25
所以在 RAG 中,真正更稳的方案是混合检索:
向量检索负责语义召回
BM25 负责关键词精准匹配
流程大致是:
用户问题
→ 向量检索召回一批 chunk
→ BM25 检索召回一批 chunk
→ 融合两路结果
→ 得到候选 Top-K
两者是互补关系:
| 检索方式 | 擅长 | 短板 |
|---|---|---|
| 向量检索 | 语义理解、同义表达、模糊问题 | 容易丢数字、编号、型号、专有名词 |
| BM25 | 精准关键词、订单号、缩写、型号 | 不理解语义,同义词容易匹配不上 |
所以实际项目里不应该纠结:
到底用向量检索还是 BM25?
而应该考虑:
怎么把向量检索和关键词检索结合起来?
4. RRF:融合两路检索结果
混合检索会产生一个问题:
向量检索和 BM25 的分数不是一个尺度,不能直接相加。
比如:
向量相似度:0.84
BM25 分数:3.8
这两个分数没有可比性。
所以常见做法是使用 RRF,也就是基于排名的融合方法。
RRF 的思想很简单:
一个 chunk 如果在向量检索和 BM25 中都排得靠前,那它大概率更相关。
它不直接比较分数,而是比较排名,因此更适合工程落地。
5. Rerank:对候选结果做精排
混合检索解决的是"召回"问题,但召回出来的结果不一定顺序最优。
所以通常还会加 Reranker。
流程是:
混合检索召回 Top-20 / Top-50
→ Reranker 判断 query 和 chunk 的相关性
→ 重新排序
→ 取 Top-3 / Top-5 给大模型
这里有一个关键点:
LLM 最终只能看到放进上下文的那几个 chunk,所以 Top-K 的质量非常重要。
如果最相关的 chunk 没有排到前面,大模型后面再强也很难回答好。
所以调优顺序应该是:
先保证关键 chunk 能被召回
再优化排序质量
最后看业务效果
如果召回阶段已经漏掉了关键内容,后面的 Rerank 也救不回来。
三、RAG 生成策略:检索到不代表答得对
很多人会有一个误区:
只要检索到正确 chunk,大模型自然会回答正确。
这是不一定的。
即使 Top-K chunk 很准,大模型仍然可能出现问题:
用自己的通用知识覆盖知识库内容
编造 chunk 中没有的数字、日期、金额
把多个 chunk 的信息混在一起
回答相关内容,但没有真正回答用户问题
不给引用来源,用户无法验证
所以 RAG 的最后一公里不是检索,而是生成。
1. RAG Prompt 的三段式结构
一个标准 RAG Prompt 通常包含三部分:
System Prompt
Retrieved Context
User Query
可以这样理解:
System Prompt:规定模型行为
Retrieved Context:提供事实依据
User Query:明确用户问题
一句话总结:
System Prompt 管行为,Context 管事实,User Query 管目标。
2. System Prompt 的作用
System Prompt 不是简单写一句:
你是一个客服助手,请回答用户问题。
这种 Prompt 约束太弱,大模型很容易自由发挥。
在 RAG 场景下,System Prompt 最核心的任务是:
限制模型只能基于参考资料回答。
常见规则包括:
只基于参考资料回答
资料不足就明确说不知道
不要编造资料中没有的数字、日期、金额
回答时引用资料编号
多条资料冲突时指出冲突
控制回答语气和长度
其中最关键的是前三条:
只基于资料回答
不知道就说不知道
不要编造资料中没有的信息
这三条是抑制幻觉的底线。
3. 检索上下文要带编号和来源
推荐的上下文格式是:
【参考资料】
[1] 来源:退货政策文档 | 更新时间:2026-01-15
iPhone 16 Pro Max 拆封后不支持七天无理由退货。
[2] 来源:通用退货规则 | 更新时间:2026-01-10
标准商品在签收后 7 天内可申请无理由退货。
这样设计有三个好处:
-
编号 :方便模型在回答中引用
[1]、[2]。 -
来源:方便用户追溯答案依据。
-
更新时间:当资料冲突时,可以判断哪条资料更新。
这也是元数据在 RAG 系统中的重要价值。
4. 不要塞太多 chunk
Top-K 不是越多越好。
chunk 太多会带来几个问题:
关键信息被稀释
模型容易被无关内容干扰
中间信息容易被忽略
成本和延迟增加
一般经验是:
简单事实问答:3~5 个 chunk
复杂对比问题:5~8 个 chunk
总结类问题:8~10 个 chunk
如果已经用了 Reranker,通常 Top 3 到 Top 5 个高质量 chunk 就够了。
宁可给少量高质量资料,也不要塞一堆低质量资料。
四、幻觉抑制:让模型别乱编
RAG 生成阶段最怕的就是幻觉。
这里的幻觉不是指模型胡言乱语,而是:
模型生成了看起来合理、读起来流畅,但实际上不符合资料内容的答案。
RAG 场景下常见幻觉有三类。
1. 篡改事实
chunk 里说 A,模型回答 B。
比如:
chunk:iPhone 16 Pro Max 拆封后不支持七天无理由退货。
模型:iPhone 16 Pro Max 支持七天无理由退货。
这是最严重的幻觉。
它说明模型没有严格服从知识库,而是用自己的通用知识覆盖了资料。
2. 凭空捏造
chunk 里没有的信息,模型自己补出来。
比如:
chunk:质量问题退货,运费由商家承担。
模型:质量问题退货,运费由商家承担,退款将在 3-5 个工作日内到账。
这里的"3-5 个工作日"就是模型编出来的。
这类幻觉在数字、日期、金额上特别常见。
3. 张冠李戴
模型把不同 chunk 的信息混在一起。
比如:
chunk1:AirPods Pro 默认保修 1 年。
chunk2:AppleCare+ 可将保修期延长至 2 年。
模型:AirPods Pro 的保修期是 2 年。
正确说法应该是:
AirPods Pro 默认保修 1 年;
如果购买 AppleCare+,可以延长至 2 年。
4. 抑制幻觉的核心方法
常用方法有:
明确限定知识来源
加"不知道就说不知道"的兜底指令
禁止编造具体细节
要求回答必须带引用
降低 Temperature
RAG 场景下,模型参数也要偏保守:
Temperature:0 ~ 0.3
Top-P:0.9 ~ 0.95
因为 RAG 追求的是准确性,不是创造力。
所以可以记住一句话:
RAG 场景下,要把模型自由发挥的空间压到最低。
五、引用对齐:让答案可追溯
企业级 RAG 中,答案不能只是"看起来对",还要能追溯来源。
引用对齐的核心是:
模型回答中的关键结论,都能对应到具体 chunk。
实现方式一般是:
每个 chunk 带编号
Prompt 要求模型引用编号
后端解析回答中的 [1][2]
前端把引用渲染成可点击来源
比如模型回答:
iPhone 16 Pro Max 拆封后不支持七天无理由退货 [1]。
用户点击 [1],可以看到对应的退货政策文档。
引用对齐的价值有三个:
用户能验证答案
出错后能定位问题来源
金融、医疗、法律等场景方便合规审计
所以生产级 RAG 不是只返回一个 answer,而是最好返回:
answer
citations
source metadata
六、Function Call:让 RAG 从"查资料"升级到"调工具"
前面的 RAG 解决了一个问题:
用户问知识库里的问题,系统可以检索资料并生成答案。
但现实业务里,用户不一定只问静态知识。
比如:
公司的年假制度是什么?
这个问题可以查知识库。
但如果用户问:
我还剩几天年假?
这个答案就不在知识库里,而在 HR 系统里。
再比如:
订单 #12345 到哪了?
这个答案也不在知识库里,而在订单系统或物流系统里。
这就是 RAG 的能力边界:
RAG 能查资料,但不能天然查实时数据、调业务接口、执行业务动作。
Function Call 就是为了解决这个问题。
1. Function Call 的本质
Function Call 的本质不是让大模型真的执行函数,而是:
让大模型输出一个结构化的函数调用意图。
比如模型输出:
{
"name": "getUserAnnualLeave",
"arguments": {
"userId": "user_12345"
}
}
这表示:
模型判断这个问题需要调用 getUserAnnualLeave 工具,
参数是 userId = user_12345。
然后真正执行的是后端代码:
getUserAnnualLeave("user_12345");
所以角色分工是:
大模型:理解意图,选择工具,生成参数
后端代码:执行函数,查数据库 / 调接口 / 查知识库
大模型:根据工具结果生成自然语言回答
一句话总结:
模型负责选工具和填参数,后端负责真正执行工具。
2. Function Call 的完整流程
标准流程是:
1. 后端定义工具列表
2. 把工具列表和用户问题一起发给大模型
3. 大模型判断是否需要调用工具
4. 如果需要,返回 tool_calls
5. 后端解析 tool_calls
6. 后端执行对应函数
7. 把函数执行结果返回给大模型
8. 大模型生成最终答案
也就是说,Function Call 通常是两轮模型调用:
第一轮:模型选择工具
第二轮:模型根据工具结果生成答案
第一轮模型可能不会直接回答用户,而是返回:
{
"tool_calls": [
{
"function": {
"name": "getUserAnnualLeave",
"arguments": "{\"userId\":\"user_12345\"}"
}
}
]
}
后端执行后得到:
{
"remainingDays": 5,
"totalDays": 10,
"usedDays": 5
}
第二轮再把这个结果发给模型,模型最终回答:
你还剩 5 天年假,总共 10 天,已使用 5 天。
3. 工具定义:name、description、parameters
一个工具定义通常包括:
name:工具名
description:工具描述
parameters:参数定义
示例:
{
"type": "function",
"function": {
"name": "getUserAnnualLeave",
"description": "查询用户的年假余额,包括总天数、已使用天数、剩余天数",
"parameters": {
"type": "object",
"properties": {
"userId": {
"type": "string",
"description": "用户 ID"
}
},
"required": ["userId"]
}
}
}
其中最重要的是 description。
因为模型主要根据 description 判断:
这个工具能干什么?
当前用户问题该不该调用它?
如果 description 写得太模糊,模型就可能选错工具。
4. parameters 是参数说明书
parameters 使用 JSON Schema 描述函数参数。
比如:
{
"type": "object",
"properties": {
"userId": {
"type": "string",
"description": "用户 ID"
},
"year": {
"type": "integer",
"description": "查询年份"
}
},
"required": ["userId"]
}
它表示:
参数整体是一个对象
对象里有 userId 和 year 两个字段
userId 是字符串,必填
year 是整数,可选
可以类比 Java 方法:
queryLeave(String userId, Integer year)
七、Function Call 在 RAG 中的作用:意图路由
Function Call 可以作为 RAG 系统里的意图路由机制。
比如定义三个工具:
searchKnowledgeBase:查询知识库
getUserAnnualLeave:查询用户年假余额
getOrderStatus:查询订单状态
用户问:
年假制度是什么?
模型应该选择:
searchKnowledgeBase
用户问:
我还剩几天年假?
模型应该选择:
getUserAnnualLeave
用户问:
订单 #12345 到哪了?
模型应该选择:
getOrderStatus
更复杂的情况还可以组合使用。
比如用户问:
我的订单 #12345 能退货吗?
这个问题既要查订单状态,也要查退货政策。
可能流程是:
1. 调 getOrderStatus 查询订单是否签收、是否拆封、购买时间
2. 调 searchKnowledgeBase 查询退货政策
3. 大模型综合订单状态和退货规则生成答案
这就实现了:
知识检索 + 工具调用
也就是说:
RAG 让系统能查资料,Function Call 让系统能选工具,后端接口让系统真正干活。
八、Function Call 的局限性
Function Call 很有用,但不是万能的。
它本身只是一个协议,真正落地还要处理很多工程问题。
主要包括:
工具定义维护成本高
跨语言、跨系统集成复杂
权限和安全控制要后端自己做
工具调用日志和链路追踪要自己实现
尤其是权限问题,不能交给模型。
比如用户 A 问:
帮我查用户 B 的年假余额
模型可能真的生成:
{
"name": "getUserAnnualLeave",
"arguments": {
"userId": "user_B"
}
}
但用户 A 有没有权限查用户 B,这是后端必须校验的。
所以一定要记住:
模型可以决定"想调用什么",但后端必须决定"能不能调用"。
实际项目里,Function Call 后端至少要做:
工具白名单
参数校验
权限校验
异常处理
日志记录
九、完整链路总结
把三部分串起来,一个更完整的大模型应用链路大概是:
原始文档
→ 文本提取
→ 分 chunk
→ 添加元数据
→ 向量化
→ 存入向量数据库
用户提问
→ 模型判断意图
→ 如果是知识库问题:走 RAG 检索
→ 如果是实时数据问题:走 Function Call 调接口
→ 如果是复杂问题:知识库检索 + 工具调用组合
→ 获取相关资料或工具结果
→ 组装 Prompt
→ 大模型生成最终答案
→ 返回答案和引用来源
可以进一步压缩成一句话:
RAG 负责查静态知识,Function Call 负责调动态工具,Prompt 负责约束模型生成可靠答案。
十、面试表达版
如果在面试中被问到 RAG 检索、生成和 Function Call,可以这样回答:
RAG 系统一般分为离线建库和在线问答两个阶段。离线阶段会对原始文档进行文本提取、分 chunk、添加元数据、向量化,并存入向量数据库。在线阶段用户提问后,会先进行检索召回,生产环境中通常不会只依赖向量检索,而是采用向量检索和 BM25 的混合检索,再通过 RRF 做融合排序,必要时用 Reranker 对候选 chunk 进行精排,最后选出 Top-K chunk 交给大模型。
在生成阶段,RAG 不能简单地把 chunk 丢给模型,因为模型可能会用自身知识覆盖资料、编造细节或者混淆不同 chunk 的信息。因此一般会使用三段式 Prompt,也就是 System Prompt、Retrieved Context 和 User Query。System Prompt 用来限制模型只能基于参考资料回答;Retrieved Context 会带上 chunk 编号、来源、更新时间;User Query 是用户原始问题。同时要求模型引用资料编号,并在后端解析引用,实现答案可追溯。
但 RAG 本身主要解决静态知识问答,如果用户问的是实时数据,比如年假余额、订单状态、考勤记录,就需要 Function Call。Function Call 的本质不是让模型真的执行函数,而是让模型根据工具描述输出 tool_calls,包括函数名和参数。后端解析 tool_calls 后真正执行接口或数据库查询,再把结果返回给模型,由模型生成最终答案。实际项目中,还必须在后端做好参数校验、权限控制、工具白名单和日志追踪。
十一、最终记忆版
最后可以用这几句话记住整套逻辑:
RAG 检索不能只靠向量。
向量检索懂语义,但容易丢精准词;
BM25 不懂语义,但擅长订单号、型号、年份、缩写等精准匹配;
生产级 RAG 通常是向量检索 + BM25 + RRF 融合 + Rerank 精排。
RAG 生成不能只把 chunk 丢给大模型。
要用 System Prompt 限制模型只基于资料回答,
资料不足就说不知道,
不要编造数字、日期、金额,
并通过引用编号实现答案可追溯。
Function Call 不是模型真的执行函数。
模型只负责理解用户意图、选择工具、生成参数;
后端负责真正执行接口、查数据库、查知识库,并做权限校验和参数校验。
最核心的一句话:
生产级大模型问答系统 = 混合检索保证找得准,Prompt 约束保证答得稳,Function Call 扩展系统能力边界。