目录
[1. 企业级RAG真正要解决的是"可信"](#1. 企业级RAG真正要解决的是“可信”)
[2. 检索结果相关,不代表它可以使用](#2. 检索结果相关,不代表它可以使用)
[3. 生产级RAG不要只做向量检索](#3. 生产级RAG不要只做向量检索)
[4. 回答必须和Evidence绑定](#4. 回答必须和Evidence绑定)
[5. Citation不是在答案后面加个1](#5. Citation不是在答案后面加个[1])
[6. 企业RAG必须敢于拒答](#6. 企业RAG必须敢于拒答)
[7. 文档上传成功,也不等于知识可以使用](#7. 文档上传成功,也不等于知识可以使用)
[8. 企业知识源也不应该只有"上传文件"](#8. 企业知识源也不应该只有“上传文件”)
[9. 回答错误之后,还需要Quality Loop](#9. 回答错误之后,还需要Quality Loop)
[10. 我现在更推荐的生产级RAG架构](#10. 我现在更推荐的生产级RAG架构)
[11. Spring AI 2.0负责什么,业务系统又该负责什么?](#11. Spring AI 2.0负责什么,业务系统又该负责什么?)
[12. 开源项目:Spring AI Business Copilot](#12. 开源项目:Spring AI Business Copilot)
[13. 总结](#13. 总结)
本文所有架构思路、工程设计和核心代码,都来自我正在持续维护的开源项目 Spring AI Business Copilot。
⭐ GitHub:
项目基于 Java 21 + Spring Boot 4.1 + Spring AI 2.0 + PostgreSQL + pgvector,目前已经包含 Knowledge、Data、Support、Report、HR 等多个企业 AI Copilot。
本文讲到的 混合检索、引用校验、无依据拒答、知识版本、权限过滤、质量复核 都可以直接在项目中找到实现并运行测试。
如果这套企业 AI 工程实践对你有帮助,建议先给项目点个 Star ⭐。
后续这个系列还会继续拆 Text-to-SQL、Guardrails、Human-in-the-loop、Agent Evaluation 等生产级实现。
前言
很多人第一次做 RAG,流程基本都是:
文档上传
↓
文本解析
↓
Chunk
↓
Embedding
↓
Vector Store
↓
TopK
↓
Prompt
↓
LLM
这条链路当然没有问题。
我之前的文章里也已经详细写过企业 RAG 的整体设计,包括文档解析、Chunk、向量检索、权限过滤、Rerank 等内容。
所以这篇不再重新讲一遍 Chunk 怎么切、Embedding 怎么调、TopK 怎么选。
这次继续往生产环境走一步。
因为真正把 RAG 接进企业系统以后,很快会遇到另一个问题:
检索到了,不代表这个答案就可以相信。
比如员工问:
北京出差住宿标准是多少?
知识库召回了一份《员工差旅制度》:
一线城市住宿标准最高 500 元/晚。
模型回答:
根据公司差旅制度,北京住宿标准最高为 500 元/晚。
答案流畅、召回正常,甚至还能给你挂一个引用。
但问题是:
这是一份两年前的制度。
半年前,公司已经更新到 V3:
一线城市住宿标准最高 600 元/晚。
这时候问题已经不再是:
RAG 能不能找到内容?
而是:
RAG 怎么保证找到的是当前有效、用户有权访问,而且真正能够支撑答案的内容?
这就是 Demo RAG 和生产级 RAG 之间非常关键的一道分界线。
1. 企业级RAG真正要解决的是"可信"
一个简单 RAG 主要解决:
Question
↓
Retrieval
↓
Context
↓
LLM
↓
Answer
但企业真正关心的问题会多很多:
- 这句话依据哪份文档?
- 引用的 Chunk 真的存在吗?
- 文档是不是当前版本?
- 文档有没有过期?
- 用户有没有权限查看?
- 两份制度冲突怎么办?
- 没有证据时模型会不会继续编?
- AI 回答错了以后能不能追查?
- 人工发现问题后怎么形成治理闭环?
所以我现在更倾向于把生产级 RAG 的可信能力拆成三层:
检索可信
Retrieval Trust
↓
回答可信
Answer Trust
↓
知识可信
Knowledge Trust
第一层保证:
找到的知识可以使用。
第二层保证:
模型的回答能够被证据支撑。
第三层保证:
进入 RAG 的知识本身处于正确生命周期。
这时候 RAG 才不只是:
Vector Search + LLM
而逐渐变成一套完整的:
Knowledge Governance
+
Retrieval
+
Evidence
+
Generation
+
Guardrails
+
Audit

2. 检索结果相关,不代表它可以使用
RAG 优化时我们经常关注:
- TopK
- Similarity
- Hybrid Search
- Rerank
- Recall
- Precision
这些都重要。
但生产环境还要加一个前提:
相关性只是知识进入上下文的条件之一。
例如知识库里存在三份文档:
员工差旅制度 V1
状态:历史版本
员工差旅制度 V3
状态:当前版本
财务特殊报销政策
权限:Finance
员工询问:
北京出差住宿标准是多少?
从语义相关度来看,这三份文档都可能被命中。
甚至 V1 与 V3 的文本高度相似。
如果检索逻辑只有:
ORDER BY similarity DESC
LIMIT 10
就可能把:
- 历史版本
- 已经过期文档
- 当前用户无权访问的文档
一起交给 LLM。
这也是为什么我在 Knowledge Copilot 中没有单纯依赖向量相似度,而是把知识生命周期直接放进检索条件。
项目中的文本检索核心 SQL,实际会先做这些过滤:
java
private static final String TEXT_SEARCH_SQL = """
SELECT c.id AS chunk_id,
ts_rank_cd(
to_tsvector('simple', c.content),
websearch_to_tsquery('simple', ?)
) AS rank
FROM knowledge_chunks c
JOIN knowledge_documents d ON d.id = c.document_id
WHERE d.enabled = TRUE
AND d.current_version = TRUE
AND d.index_status = 'INDEXED'
AND (d.expires_at IS NULL OR d.expires_at > now())
AND d.conflict_status = 'NONE'
AND (
d.visibility_scope = 'ALL'
OR (d.visibility_scope = 'HR_REVIEWER' AND ?)
OR (d.visibility_scope = 'ADMIN' AND ?)
)
AND (?::text IS NULL OR d.category = ?::text)
AND to_tsvector('simple', c.content)
@@ websearch_to_tsquery('simple', ?)
ORDER BY rank DESC, c.id
LIMIT ?
""";
这里真正值得关注的不是全文检索 SQL 本身,而是:
enabled = true
current_version = true
index_status = INDEXED
未过期
无冲突
ACL匹配
业务分类匹配
只有通过这些条件的知识,才有资格进入后续检索。
这意味着:
权限、版本和生命周期控制应该发生在 Retrieval 层,而不是 Prompt 层。
千万不要先把敏感文档塞进 Context,再告诉模型:
如果用户没有权限,请不要回答。
因为这个时候数据已经进入模型上下文了。
3. 生产级RAG不要只做向量检索
另一个常见误区是:
RAG = Vector Search。
但实际企业知识里,大量查询并不完全适合纯向量检索。
例如:
A10086退款规范
2026差旅管理制度
P1故障处理流程
采购审批三级权限
这种包含:
- 编号
- 专有名词
- 制度名称
- 业务关键词
的问题,Keyword / Full Text Search 往往也非常有价值。
所以 Knowledge Copilot 当前采用的是:
PostgreSQL Text Search
+
Keyword Search
+
pgvector Vector Search
↓
Fusion
↓
TopK
核心逻辑类似这样:
java
List<KnowledgeChunkRepository.TextSearchResult> textResults =
chunkRepository.findByTextSearch(question, topK * 2);
List<KnowledgeChunkRepository.TextSearchResult> keywordResults =
chunkRepository.findByKeywordSearch(
KnowledgeQueryTerms.extract(question), topK * 2);
float[] questionVector =
aiEmbeddingService.embed(
"knowledge.question-retrieval", question);
List<KnowledgeEmbeddingRepository.SimilaritySearchResult> vectorResults =
embeddingRepository.findSimilarChunks(
questionVector,
embeddingModel,
topK * 2,
minSimilarity);
然后把不同通道的排序结果进行融合。
这里需要强调一点:
Hybrid Retrieval 解决的是"怎么找到更好的候选"。
但是它依然不能替代:
Document Version
ACL
Expiry
Conflict
Index Status
Rerank 也是同理。
Rerank 能告诉你谁更相关,却不能告诉你谁有资格被使用。
4. 回答必须和Evidence绑定
传统 RAG 通常会这样构建 Prompt:
java
请根据以下知识回答问题。
Context:
{retrievedChunks}
Question:
{question}
相比直接让模型裸答,这已经安全很多。
但是:
LLM 拿到 Context,不代表它一定只使用 Context。
它仍然可能:
- 使用自己的参数知识;
- 补全缺失的信息;
- 把两个 Chunk 拼出一个不存在的结论;
- 生成证据中没有出现的数字;
- 编造一个不存在的引用。
因此企业知识助手最好不要只返回:
java
{
"answer": "北京住宿标准为600元/晚"
}
更合理的是返回:
java
{
"status": "ANSWERED",
"answer": "根据当前差旅制度,北京住宿标准最高为600元/晚。",
"citations": [
{
"chunkId": 1028,
"excerpt": "一线城市住宿标准最高600元/晚"
}
]
}
此时答案和证据形成:
Answer
↓
Claim
↓
Citation
↓
Evidence
也就是说:
重要结论必须能够回到本次真实检索到的证据。
5. Citation不是在答案后面加个1
现在很多 RAG 系统已经开始支持引用:
北京住宿标准最高600元/晚。[1]
[1] 员工差旅制度.pdf
看起来已经能够溯源。
但如果这个 [1] 完全由 LLM 自己生成,它仍然可能出现幻觉。
比如模型返回:
java
{
"chunkId": 99999
}
但 99999 根本没有在这次检索结果里出现。
所以 Citation 不能直接信模型。
我在项目里单独做了一个 CitationGuardrailService。
核心代码并不复杂:
java
public CitationValidationResult validate(
List<KnowledgeCitation> citations,
List<RetrievedKnowledgeChunk> retrievedChunks) {
List<String> violations = new ArrayList<>();
if (citations == null || citations.isEmpty()) {
violations.add("ANSWERED 状态至少需要一条引用,但模型未返回引用");
return new CitationValidationResult(false, violations);
}
Set<Long> validChunkIds = retrievedChunks.stream()
.map(r -> r.chunk().id())
.collect(Collectors.toSet());
for (KnowledgeCitation citation : citations) {
if (citation.chunkId() == null) {
violations.add("引用中的 chunkId 不能为空");
continue;
}
if (!validChunkIds.contains(citation.chunkId())) {
violations.add(
"引用的 chunkId=" + citation.chunkId()
+ " 不在本次召回结果中");
}
}
return new CitationValidationResult(
violations.isEmpty(), violations);
}
思路其实很简单:
LLM说自己引用了什么
↓
系统拿本次真实Retrieved Chunks
↓
做集合校验
↓
引用真实存在?
↙ ↘
YES NO
↓ ↓
Continue Reject
在项目里我甚至没有直接相信模型返回的引用摘录。
Citation 校验完成以后,excerpt 会重新从服务端真实 Chunk 中生成。
因为模型完全可能把原文:
原则上不超过600元
改写成:
统一标准为600元
虽然看起来只改了一点点,但语义已经发生变化。
所以:
Citation ID 可以让模型选择,但 Citation Evidence 最好由系统重新绑定。

6. 企业RAG必须敢于拒答
企业 AI 最危险的一种能力其实是:
什么都敢回答。
比如员工问:
公司对海外长期驻场的住房补贴是多少?
系统检索后:
没有找到任何当前有效知识。
这个时候最危险的处理是:
LLM根据常识继续生成。
更加可靠的处理应该是:
NO_EVIDENCE
直接告诉用户:
当前知识库没有找到能够支持该问题的有效资料。
在 Knowledge Copilot 里,这个逻辑不是单纯写一句 Prompt:
不知道的时候请说不知道。
而是直接进入系统状态。
例如当前 KnowledgeAnswerService 的核心逻辑:
java
if (retrievedChunks == null || retrievedChunks.isEmpty()) {
log.info("未召回知识分片,返回无依据状态");
return result(
new KnowledgeAnswerResponse(
KnowledgeAnswerStatus.NO_EVIDENCE,
null,
List.of(),
List.of(),
aiChatService.modelName()
),
null,
null,
"NO_RETRIEVED_EVIDENCE"
);
}
注意这里一个很重要的细节:
没有 Evidence 时,连 LLM 都不调用。
这样不仅避免模型幻觉,还能:
- 减少 Token 消耗;
- 降低模型调用延迟;
- 让拒答状态可以统计;
- 让监控系统区分"知识不足"和"模型失败"。
而即使模型返回了 ANSWERED,后面依然还会做 Citation Guardrail:
java
CitationValidationResult validation =
citationGuardrailService.validate(
citations, retrievedChunks);
if (!validation.valid()) {
return result(
new KnowledgeAnswerResponse(
KnowledgeAnswerStatus.REJECTED,
null,
List.of(),
validation.violations(),
modelName
),
prompt.metadata(),
aiMetadata,
"CITATION_VALIDATION_FAILED"
);
}
所以完整思路并不是:
LLM说可以回答
↓
直接展示
而是:
Retrieval
↓
Evidence Gate
↓
LLM
↓
Structured Output
↓
Citation Guardrail
↓
Final Answer
这里我认为有一句话特别重要:
有召回,不等于有依据。
7. 文档上传成功,也不等于知识可以使用
企业知识还有一个很容易被忽略的问题:
生命周期。
普通 Demo 里文档可能只有:
上传
↓
切分
↓
入库
但生产环境需要考虑:
旧版本怎么办?
Embedding失败怎么办?
索引做到一半应用重启怎么办?
外部知识已经删除怎么办?
文档已经过期怎么办?
两份制度发生冲突怎么办?
因此文档本身需要状态:
Version 1
SUPERSEDED
Version 2
SUPERSEDED
Version 3
CURRENT
索引任务也需要状态:
PENDING
↓
PROCESSING
↓
SUCCESS
或
FAILED
只有同时满足:
enabled
current_version
INDEXED
not expired
no conflict
ACL passed
才允许进入检索。
这也是为什么我不建议企业知识库采用:
上传文件 → 直接标记成功
而应该把:
文档状态
和:
索引任务状态
分开管理。
甚至还要处理一种比较隐蔽的情况:
任务A开始索引V1
↓
任务A执行很慢
↓
用户已经上传V2
↓
任务B完成V2索引
↓
任务A此时才执行完成
如果没有版本和任务状态保护,旧任务就可能反过来覆盖新数据。

8. 企业知识源也不应该只有"上传文件"
企业知识往往并不只来自上传 PDF。
还可能来自:
MinIO / S3
SharePoint
Confluence
Notion
内部知识平台
挂载目录
这时候新的问题又来了:
每次同步是不是重新全量向量化?
显然不应该。
更加合理的是:
Source
↓
Cursor
↓
Incremental Sync
↓
Content Hash
↓
Changed?
↙ ↘
YES NO
↓ ↓
Reindex Renew
除此之外,还必须处理:
- 源端删除;
- ACL发生变化;
- 同步失败;
- Cursor异常;
- 长期没有成功同步;
- 外部文档过期;
- 内容冲突。
因此做到后面会发现:
企业 Knowledge Copilot 已经不只是一个 RAG 页面。
它逐渐变成一个:
知识接入 + 知识生命周期 + 检索 + AI问答 + 质量治理系统。
这也是生产级 RAG 真正复杂的地方。
9. 回答错误之后,还需要Quality Loop
做到前面这些,是不是就不会回答错了?
当然不是。
LLM 永远不可能保证 100% 正确。
所以系统还需要解决最后一个问题:
错了以后怎么办?
简单一点的产品通常只有:
👍 / 👎
但这对于真正定位问题还不够。
一次错误回答可能来自完全不同的原因:
Retrieval Error
检索错了
Evidence Error
证据不足
Citation Error
引用错误
Answer Error
模型理解错误
Knowledge Error
知识本身过期
Policy Error
规则设计问题
因此在 Knowledge Copilot 中,我把用户反馈继续进入 Quality Queue。
整体链路是:
Answer
↓
Feedback
↓
Quality Queue
↓
Reviewer
↓
Evidence Assessment
+
Answer Assessment
+
Remediation
+
Disposition
↓
Closed
这样人工审核结果才能反过来指导系统优化。
例如:
召回错误
→ 调整Retrieval
引用错误
→ 修复Citation Guardrail
知识过期
→ 修复Source / Lifecycle
模型过度推断
→ 调整Prompt / Evidence Gate
资料本身错误
→ 回到Knowledge Governance
这时候"用户点了一个差评"才真正形成工程闭环。
10. 我现在更推荐的生产级RAG架构
把前面的能力串起来以后,一套企业 Knowledge Copilot 大致会变成:
Knowledge Sources
↓
Document Processing
↓
Version / Index Job
↓
Lifecycle + ACL Filter
↓
┌──────────────┼──────────────┐
↓ ↓ ↓
Text Search Keyword Search Vector Search
└──────────────┼──────────────┘
↓
Result Fusion
↓
Evidence Set
↓
Evidence Gate
↙ ↘
NO_EVIDENCE SUPPORTED
↓ ↓
Refuse LLM
↓
Structured Answer
↓
Citation Guardrail
↓
Final Response
↓
Audit / Feedback
↓
Quality Review
这时候企业 RAG 已经从:
Retrieval + LLM
升级成:
Knowledge Lifecycle
+
Hybrid Retrieval
+
Evidence
+
Generation
+
Citation Guardrail
+
Audit
+
Quality Loop
11. Spring AI 2.0负责什么,业务系统又该负责什么?
这里也很容易产生一个误区:
使用 Spring AI 做了 RAG,就等于企业级 RAG 已经完成。
实际上不是。
Spring AI 2.0 可以帮我们提供很多 AI 应用基础设施,例如:
Chat Model
Embedding Model
Vector Store
Prompt
Structured Output
Tool Calling
Advisor
这些能力能大幅降低 Java 开发 AI 应用的成本。
但:
Document Version
ACL
Knowledge Lifecycle
Index Job
Citation Validation
NO_EVIDENCE
Conflict Management
Quality Review
Audit
这些仍然需要你的业务系统负责。
这和我们以前做 Spring Boot 项目其实非常像。
Spring Boot 帮我们解决:
IOC
MVC
Transaction
AOP
但是:
订单状态
审批规则
业务权限
库存一致性
依然要自己设计。
AI Framework 也是同样的道理。
框架负责提供能力,企业系统负责守住业务边界。
12. 开源项目:Spring AI Business Copilot
本文上面讲到的这些能力,并不是我单独整理的一套概念。
目前都在持续落到我的开源项目:
Spring AI Business Copilot
⭐ GitHub:
当前项目已经包含:
Knowledge Copilot
企业知识助手
Data Copilot
Text-to-SQL / 数据分析
Support Copilot
客户服务
Report Copilot
企业报告
HR Copilot
招聘与员工服务
其中本文拆解的 Knowledge Copilot 已经覆盖:
- 文档版本管理
- 持久化索引任务
- PostgreSQL Text Search
- Keyword Search
- pgvector Vector Search
- 混合检索
- Role / Category权限过滤
- 无依据拒答
- Citation Guardrail
- 过期与冲突知识过滤
- Answer Feedback
- Quality Review Queue
项目可以直接 Clone 后运行。
如果你正在学习:
Spring AI 2.0
RAG
Agent
企业AI架构
AI工程治理
也可以直接拿它做学习和二次开发参考。
如果看到这里还没有 Star,可以顺手支持一下。⭐
后续我也会继续基于这个项目拆:
Text-to-SQL 真正进入生产为什么不能直接执行 SQL?
Guardrails 和 Human-in-the-loop 到底应该放在哪一层?
Agent Evaluation 怎么做离线评测、Readiness 和上线门禁?
13. 总结
回到本文最开始的问题:
企业级 RAG 真正难在哪里?
现在答案应该比较清楚了。
难的并不是:
把PDF转成向量。
而是:
知识是不是最新的?
用户有没有权限?
索引是否真的完成?
检索结果能不能作为证据?
引用是不是真的?
没有证据时AI敢不敢拒答?
知识更新后旧版本怎么处理?
AI回答错了以后怎么追查?
所以我现在更愿意把企业 RAG 理解成:
一个围绕"可信知识"建立起来的 AI 应用系统,而不仅仅是向量检索。
Vector Search 只是其中一个组件。
真正决定它能不能进入企业生产环境的,是:
版本、权限、生命周期、证据、引用、拒答、审计和质量闭环。
最终我们真正需要完成的是:
AI能回答
到:
AI为什么这样回答,我能够证明。
的升级。
👨💻 关于作者|QCoding
专注 AI应用开发与Java技术实践。
持续分享 Spring AI、RAG、 Agentic、Agent Evaluation、企业AI架构、工程治理与AI转型实践