派聪明 RAG 面试题预测:31道高频 AI 面试八股,1.2 万字 50 张手绘图
本文目录 · 63 节
- 1.请详细描述完整的RAG系统架构,包括主要组件和数据流向?
- 2.在设计RAG系统时,如何选择合适的向量数据库?Elasticsearch、Pinecone等有什么区别?
- 3.RAG系统中的混合检索是什么?如何实现?
- 4.解释向量embedding的维度选择对系统性能的影响?为什么我们选择2048维而不是384维?
- 5.如何解决向量检索中的"语义漂移"问题?
- 6.在多租户RAG系统中,如何设计权限控制和数据隔离?
- 7.当RAG检索到的知识与LLM预训练知识冲突时,你会如何处理?请提供具体的解决方案。
- 8.评估RAG系统检索质量的关键指标有哪些?
- 9.什么是RAG中的"幻觉"问题?如何预防?
- 10.如何设计置信度评分机制来判断检索结果的可靠性?
- 11.比较固定长度分块和语义分块的优缺点?
- 12.如何处理跨chunk的信息完整性问题?
- 23.对RAG技术的未来发展有什么看法?
- 24.如果RAG系统返回0个检索结果,你会如何排查问题?
- 25.如何处理不同API服务(豆包 embedding、DeepSeek)的调用失败?
- 26.请说说你AIGC、RAG、Agent 的理解?
- 27.那什么是 function call 模型?
- 28.什么是 MCP?
- 29.了解 A2A 吗?
- 30.了解Transformer 吗?
- 31.在做检索时,你是否尝试过或了解过其他的重排(Re-ranking)方法?
- 1.请详细描述完整的RAG系统架构,包括主要组件和数据流向?
- 2.在设计RAG系统时,如何选择合适的向量数据库?Elasticsearch、Pinecone等有什么区别?
- 3.RAG系统中的混合检索是什么?如何实现?
- 4.解释向量embedding的维度选择对系统性能的影响?为什么我们选择2048维而不是384维?
- 5.如何解决向量检索中的"语义漂移"问题?
- 6.在多租户RAG系统中,如何设计权限控制和数据隔离?
- 7.当RAG检索到的知识与LLM预训练知识冲突时,你会如何处理?请提供具体的解决方案。
- 8.评估RAG系统检索质量的关键指标有哪些?
- 9.什么是RAG中的"幻觉"问题?如何预防?
- 10.如何设计置信度评分机制来判断检索结果的可靠性?
- 11.比较固定长度分块和语义分块的优缺点?
- 12.如何处理跨chunk的信息完整性问题?
- 23.对RAG技术的未来发展有什么看法?
- 24.如果RAG系统返回0个检索结果,你会如何排查问题?
- 25.如何处理不同API服务(豆包 embedding、DeepSeek)的调用失败?
- 26.请说说你AIGC、RAG、Agent 的理解?
- 27.那什么是 function call 模型?
- 28.什么是 MCP?
- 29.了解 A2A 吗?
- 30.了解Transformer 吗?
- 31.在做检索时,你是否尝试过或了解过其他的重排(Re-ranking)方法?
- 1.请详细描述完整的RAG系统架构,包括主要组件和数据流向?
- 2.在设计RAG系统时,如何选择合适的向量数据库?Elasticsearch、Pinecone等有什么区别?
- 3.RAG系统中的混合检索是什么?如何实现?
- 4.解释向量embedding的维度选择对系统性能的影响?为什么我们选择2048维而不是384维?
- 5.如何解决向量检索中的"语义漂移"问题?
- 6.在多租户RAG系统中,如何设计权限控制和数据隔离?
- 7.当RAG检索到的知识与LLM预训练知识冲突时,你会如何处理?请提供具体的解决方案。
- 8.评估RAG系统检索质量的关键指标有哪些?
- 9.什么是RAG中的"幻觉"问题?如何预防?
- 10.如何设计置信度评分机制来判断检索结果的可靠性?
- 11.比较固定长度分块和语义分块的优缺点?
- 12.如何处理跨chunk的信息完整性问题?
- 23.对RAG技术的未来发展有什么看法?
- 24.如果RAG系统返回0个检索结果,你会如何排查问题?
- 25.如何处理不同API服务(豆包 embedding、DeepSeek)的调用失败?
- 26.请说说你AIGC、RAG、Agent 的理解?
- 27.那什么是 function call 模型?
- 28.什么是 MCP?
- 29.了解 A2A 吗?
- 30.了解Transformer 吗?
- 31.在做检索时,你是否尝试过或了解过其他的重排(Re-ranking)方法?
星球:Java进阶之路&二哥编程星球 | 专栏:派聪明RAG知识库 | 作者:沉默王二 | 发布:2025-10-08
派聪明 RAG 面试题预测:31道高频 AI 面试八股,1.2 万字 50 张手绘图
派聪明 RAG 面试题预测:31道高频 AI 面试八股,1.2 万字 50 张手绘图
技术派阅读体验会比星球更好: https://paicoding.com/column/10/19 支持微信扫码直接登录了。

1.请详细描述完整的RAG系统架构,包括主要组件和数据流向?
RAG 系统本质上要解决一个问题:如何让 AI 能够基于企业内部的知识库来回答用户问题。所以整个架构设计围绕着"文件上传-文件存储-向量生成-答案生成"这条主线来展开。
当用户上传文档后,我们首先通过 Upload 接口来处理上传文件,并支持分片上传避免大文件传输问题。然后很关键的一点是,我们没有选择同步处理,而是把文件处理任务丢到 Kafka 的消息队列里,这样用户上传完就能立即得到响应,不用等待漫长的处理过程。

接下来是文档解析环节, FileProcessingConsumer 作为 Kafka 消费者会异步处理这些任务。我们使用 Apache Tika 来解析各种格式的文档,比如 PDF、Word、Excel 等,然后通过 ParseService 把文档内容切分成小段,这样做的好处是既能保持语义的完整性,又能控制向量化的粒度。
向量化这块是整个 RAG 系统的核心, VectorizationService 会调用豆包的 Embedding API 把文本转换成向量表示。我们选择把这些向量存储在 Elasticsearch 中,主要是因为 ES 在向量检索方面的性能比较好,而且支持混合检索。
说到检索,这是 RAG 系统能否准确回答问题的关键。我们实现了混合检索策略,既有基于向量相似度的语义检索,也有传统的关键词检索,这样能够在不同场景下都有比较好的召回效果。特别重要的是,我们在检索时加入了权限控制,确保用户只能检索到自己有权限访问的文档。
生成这块,我们集成了 DeepSeek 大语言模型,通过 DeepSeekClient 来调用 API。这里有个技术细节就是我们支持流式响应,用户不用等到整个回答生成完才能看到结果,而是可以实时看到 AI 的回答过程,体验会好很多。

整个对话流程是通过 ChatHandler 来协调的,它会先调用检索服务找到相关文档,然后把这些文档作为上下文传给大语言模型,最后把生成的回答通过 WebSocket 实时推送给用户。我们还在 Redis 中维护了对话历史,这样 AI 能够理解上下文,进行多轮对话。
权限控制方面,我们实现了基于组织标签的多租户架构。通过 OrgTagAuthorizationFilter 确保用户只能访问自己组织内的文档,实现数据的安全隔离。
总的来说,这套 RAG 架构的设计理念就是要在保证准确性的前提下,尽可能提升用户体验和系统性能,同时确保企业级的安全性。
2.在设计RAG系统时,如何选择合适的向量数据库?Elasticsearch、Pinecone等有什么区别?
首先说说我们为什么在派聪明中选择了 Elasticsearch。第一个原因是我们团队对 ES 比较熟悉,第二个原因是 ES 的混合检索能力很强,既支持传统的全文检索,也支持向量检索,这对 RAG 系统来说是个很大的优势。

但是说实话,ES 在纯向量检索性能上并不是最优的选择。如果系统主要是向量相似度搜索,专门的向量数据库会更合适。比如 Pinecone,它是专门为向量检索设计的云服务,性能确实很不错,而且使用起来很简单,基本上开箱即用。但是有个问题就是成本,特别是数据量大的时候,费用会比较高。而且作为云服务,数据安全和合规性可能是一些企业需要考虑的问题。

就我个人的体验来说,可以先用 ES 这样的通用方案快速验证业务价值,等业务稳定后再根据性能瓶颈考虑迁移到专门的向量数据库。这样既能快速上线,又能控制技术风险。毕竟 RAG 系统的核心价值还是在业务逻辑和数据质量上,选择合适的就行,不一定非要追求最新最炫的技术。
3.RAG系统中的混合检索是什么?如何实现?
混合检索简单来说就是把不同的检索方法结合起来,取长补短,提高检索的准确性和召回率。

在派聪明项目中,混合检索主要是结合了两种检索方式:语义检索和关键词检索。语义检索就是基于向量相似度的,它能够理解查询的语义含义,即使用词不完全匹配也能找到相关内容。比如用户问"如何提升工作效率",它能找到包含"提高生产力"、"优化流程"这样语义相关的文档。而关键词检索就是传统的全文检索,它对精确匹配很有效,特别是一些专业术语、人名、地名这种。

在技术实现上,我们是这样做的。首先对用户查询同时执行向量检索和全文检索,然后把两个结果集合并。这里有个关键问题就是如何合并和排序。我们采用的是加权融合的方式,给语义检索和关键词检索分别设置权重,然后计算综合得分。
第一阶段:KNN 向量召回
// KNN 向量召回阶段
s.knn(kn -> kn
.field("vector")
.queryVector(queryVector) // 查询向量
.k(recallK) // 召回数量(topK * 30)
.numCandidates(recallK) // 候选数量
);
第二阶段:关键词过滤
// 必须命中关键词 + 权限过滤
s.query(q -> q.bool(b -> b
.must(mst -> mst.match(m -> m.field("textContent").query(query))) // 关键词匹配
.filter(f -> f.bool(bf -> bf
.should(s1 -> s1.term(t -> t.field("userId").value(userDbId))) // 用户权限
.should(s2 -> s2.term(t -> t.field("public").value(true))) // 公开文档
.should(s3 -> /* 组织权限 */) // 组织权限
))
));
第三个阶段:BM25 重排序
// BM25 rescore 重排序
s.rescore(r -> r
.windowSize(recallK)
.query(rq -> rq
.queryWeight(0.2d) // KNN分数权重20%
.rescoreQueryWeight(1.0d) // BM25分数权重100%
.query(rqq -> rqq.match(m -> m
.field("textContent")
.query(query)
.operator(Operator.And) // 严格关键词匹配
))
)
);
具体的算法是这样的:假设一个文档在语义检索中的得分是 0.8,在关键词检索中的得分是 0.6,我们可以设置语义检索权重为 0.7,关键词检索权重为 0.3,那么最终得分就是 0.8*0.7 + 0.6*0.3 = 0.74。当然这个权重可以根据实际效果来调整。
在此基础上,我们还加入了权限过滤的逻辑。用户只能检索到自己有权限访问的文档,这个过滤是在检索结果合并之后进行的。
// 三种权限访问模式
.should(s1 -> s1.term(t -> t.field("userId").value(userDbId))) // 自己的文档
.should(s2 -> s2.term(t -> t.field("public").value(true))) // 公开文档
.should(s3 -> /* 组织层级权限 */) // 组织文档
4.解释向量embedding的维度选择对系统性能的影响?为什么我们选择2048维而不是384维?
首先,从模型能力角度来看,我们使用的是火山引擎的 doubao-embedding-text-240515 模型。 根据官方文档,这个模型的最高维度向量是 2048 维,支持 512、1024 降维使用。我们选择 2048 维实际上是在使用这个模型的原生最高维度输出,这样能够最大程度地保留模型训练时学到的语义信息。

从语义表达能力来说,维度越高,向量能够表达的语义信息就越丰富。2048 维相比 384 维有着显著的优势。高维向量能够在语义空间中更精确地区分不同概念之间的细微差别,这对于 RAG 系统来说至关重要。
当然,高维度意味着成本更高。
从技术实现角度,我们在 ES 的 knowledge_base.json 中也配置了向量字段为 2048 维,使用 cosine 相似度计算。这个配置与豆包的 embedding 模型完全匹配。

5.如何解决向量检索中的"语义漂移"问题?
语义漂移是 RAG 系统中一个非常关键的问题,简单来说就是随着时间推移,向量表示的语义可能会发生偏移,导致检索效果下降。

语义漂移
在派聪明项目中,我们采用了多层次的解决策略。第一个层面是模型版本管理。我们在 EsDocument 中专门设计了 modelVersion 字段来记录每个向量是由哪个版本模型生成的。这样当我们升级向量模型时,可以识别出哪些向量需要重新生成。

第二个层面是增量更新策略。我们不是一次性替换所有历史向量,而是采用渐进式的方法。当检测到某些文档的检索效果明显下降时,我们会优先对这些文档进行重新向量化。这个过程可以通过用户反馈和检索点击率来触发。
第三个层面是混合检索的优势。我们结合了语义检索和关键词检索。即使语义向量出现漂移,关键词检索仍然能够提供稳定的基准效果。
6.在多租户RAG系统中,如何设计权限控制和数据隔离?
首先,我们采用了多层级的权限控制架构。在用户层面,系统支持普通用户和管理员两种角色,管理员拥有全局访问权限,可以管理所有用户的组织标签分配。在组织层面,我们设计了灵活的组织标签系统,每个用户可以属于多个组织,并且有一个主组织标签。特别值得一提的是,系统为每个用户自动创建私人组织标签(PRIVATE_用户名),确保用户有独立的私人空间。

在数据存储层面,我们设计了三个关键字段来实现数据隔离:userId 标识文档所有者,orgTag 标识文档所属组织,isPublic 标识是否为公开资源。
在文件上传时,系统会根据用户的主组织标签自动为文档分配组织标签,确保数据从源头就有正确的权限标识。同时,用户可以选择将文档设置为公开,这样其他用户也能访问。

我们还对请求加了过滤器,在每个请求到达控制器之前就进行权限验证:首先检查资源是否为公开资源,如果是则直接放行;然后验证用户是否为资源所有者,所有者拥有完全访问权限;接着检查用户是否为管理员,管理员拥有全局权限;最后进行组织标签匹配,只有用户的组织标签包含资源的组织标签时才允许访问。

在 RAG 系统的核心功能——混合检索中,我们还实现了权限感知的搜索。系统会根据用户的有效组织标签构建 Elasticsearch 查询条件,确保用户只能检索到有权限访问的文档。

7.当RAG检索到的知识与LLM预训练知识冲突时,你会如何处理?请提供具体的解决方案。
可以修改提示词,比如说设置优先级规则,让检索到的参考信息优先级高于预训练知识,在遇到冲突时优先采用检索信息,无法确定时明确告知“存在信息冲突,建议核实”。
ai:
prompt:
rules: |
你是派聪明知识助手,处理知识冲突时须遵守:
【优先级规则】
1. 检索到的参考信息优先级 > 你的预训练知识
2. 最新时间的信息 > 较旧的信息
3. 企业内部规定 > 通用规则
4. 具体场景规则 > 一般性原则
【冲突处理】
1. 发现冲突时,优先采用检索信息,并明确说明"根据最新检索信息"
2. 如检索信息不完整,可补充预训练知识,但需标注"补充:基于通用知识"
3. 存在明显矛盾时,同时呈现两种观点并标注来源
4. 无法确定时,明确告知"存在信息冲突,建议核实"
【回答格式】
- 检索信息:(来源#编号)
- 补充信息:(基于通用知识)
- 冲突提示:(⚠️信息冲突)
8.评估RAG系统检索质量的关键指标有哪些?
准确性指标 是最核心的。首先是召回率(Recall),也就是相关文档中有多少被成功检索出来了。比如用户问一个技术问题,实际上知识库中有 10 个相关文档,系统检索出了 7 个,那召回率就是 70%。

然后是精确率(Precision),检索出来的文档中有多少是真正相关的。如果我们检索出了 10 个文档,但只有 7 个是相关的,精确率就是 70%。
// 在 HybridSearchService 中添加评估逻辑
public EvaluationMetrics evaluateSearch(String query, List relevantDocIds, int
topK) {
List results = searchWithPermission(query, userId, topK);
// 计算 Precision@K
long relevantCount = results.stream()
.limit(topK)
.mapToLong(result -> relevantDocIds.contains(result.getFileMd5()) ? 1 : 0)
.sum();
double precision = (double) relevantCount / Math.min(topK, results.size());
// 计算 Recall@K
double recall = (double) relevantCount / relevantDocIds.size();
// 计算 MRR
double mrr = calculateMRR(results, relevantDocIds);
return new EvaluationMetrics(precision, recall, mrr);
}
9.什么是RAG中的"幻觉"问题?如何预防?
RAG 中的"幻觉"是指大语言模型生成的内容与检索到的真实信息不符,或者模型编造了不存在的信息。在派聪明项目中,我们采用了多种策略来预防幻觉问题。
首先是提示词,我们明确要求模型严格基于检索到的文档内容回答,不要添加文档中没有的信息。
:::color1
你的回答必须依据参考文献,若参考文献无法回答问题,则回复“无法回答”
:::
其次是检索质量,我们采用了最高维度的豆包向量模型,确保检索到的文档真正相关。同时,我还使用了混合检索策略,通过提高检索精度来减少无关信息的干扰。并通过设置相似度阈值,过滤掉相关性低的检索结果,确保只有高质量的上下文信息被传递给模型。

另外,我们为每个检索结果都添加了明确的来源标识和置信度信息,让模型清楚地知道信息的可靠性。
10.如何设计置信度评分机制来判断检索结果的可靠性?
置信度是 RAG 系统用来保证检索质量的一个重要指标。所以要综合多个维度来考虑。

第一个关键维度我认为是向量相似度。当用户搜索时,我们利用 Elasticsearch 的 KNN 算法计算查询向量和文档向量的余弦相似度,这个分数能反映语义层面的相关性——分数越高,说明文档和查询在语义上越接近。

第二个维度是文本关键词匹配度。我们会利用 ES 默认的 BM25 算法对关键词在文档中的出现频率、重要性进行打分。

然后综合计算出最后的得分 最终分数 = KNN分数 × 0.2 + BM25分数 × 1.0。

11.比较固定长度分块和语义分块的优缺点?
固定长度分块和语义分块是 RAG 系统中两种主要的文档分割策略,**固定长度分块**是最简单直接的方式,派聪明采用的就是这种方式,每 512 个字符为一块,虽然可能会把语义完整的内容强行切断,但实现起来非常容易,对于计算资源有限的我们来说,是一个非常实用的选择。

语义分块 会基于文档的语义结构来分割,比如按照段落、章节、主题来切分。这样能确保每个 chunk 包含相对完整的语义信息。我们打算在下一个版本中增加语义分块,对于结构化程度高的文档,比如技术手册、政策文件等,采用语义分块,充分利用文档的结构信息。对于结构化程度低的文档,比如聊天记录、邮件等,采用固定长度分块,确保处理的稳定性。
/**
\* 智能文本分割,保持语义完整性
*/
private List splitTextIntoChunks(String text, int chunkSize) {
List chunks = new ArrayList<>();
// 按段落分割
String[] paragraphs = text.split("\n\n+");
StringBuilder currentChunk = new StringBuilder();
for (String paragraph : paragraphs) {
// 如果单个段落超过chunk大小,需要进一步分割
if (paragraph.length() > chunkSize) {
// 先保存当前chunk
if (currentChunk.length() > 0) {
chunks.add(currentChunk.toString().trim());
currentChunk = new StringBuilder();
}
// 按句子分割长段落
List sentenceChunks = splitLongParagraph(paragraph, chunkSize);
chunks.addAll(sentenceChunks);
}
// 如果添加这个段落会超过chunk大小
else if (currentChunk.length() + paragraph.length() > chunkSize) {
// 保存当前chunk
if (currentChunk.length() > 0) {
chunks.add(currentChunk.toString().trim());
}
// 开始新chunk
currentChunk = new StringBuilder(paragraph);
}
// 可以添加到当前chunk
else {
if (currentChunk.length() > 0) {
currentChunk.append("\n\n");
}
currentChunk.append(paragraph);
}
}
// 添加最后一个chunk
if (currentChunk.length() > 0) {
chunks.add(currentChunk.toString().trim());
}
return chunks;
}
/**
\* 分割长段落,按句子边界
*/
private List splitLongParagraph(String paragraph, int chunkSize) {
List chunks = new ArrayList<>();
// 按句子分割
String[] sentences = paragraph.split("(? chunkSize) {
if (currentChunk.length() > 0) {
chunks.add(currentChunk.toString().trim());
currentChunk = new StringBuilder();
}
// 如果单个句子太长,按词分割
if (sentence.length() > chunkSize) {
chunks.addAll(splitLongSentence(sentence, chunkSize));
} else {
currentChunk.append(sentence);
}
} else {
currentChunk.append(sentence);
}
}
if (currentChunk.length() > 0) {
chunks.add(currentChunk.toString().trim());
}
return chunks;
}
/**
\* 分割超长句子,按词边界
*/
private List splitLongSentence(String sentence, int chunkSize) {
List chunks = new ArrayList<>();
String[] words = sentence.split("\\s+");
StringBuilder currentChunk = new StringBuilder();
for (String word : words) {
if (currentChunk.length() + word.length() + 1 > chunkSize) {
if (currentChunk.length() > 0) {
chunks.add(currentChunk.toString().trim());
currentChunk = new StringBuilder();
}
}
if (currentChunk.length() > 0) {
currentChunk.append(" ");
}
currentChunk.append(word);
}
if (currentChunk.length() > 0) {
chunks.add(currentChunk.toString().trim());
}
return chunks;
}
12.如何处理跨chunk的信息完整性问题?
最直接有效的解决方案是引入滑动窗口机制,在相邻 chunk 之间保持一定的重叠区域。具体实现上,可以优化派聪明的分块逻辑,设置 20-30% 的重叠率。例如,如果 chunk 大小为 512 字符,则每次移动 350-400 字符,保留 100-150 字符的重叠。这样能够确保被切断的信息在相邻 chunk 中得到保留,提高信息检索的完整性。重叠策略虽然会增加存储空间,但能显著改善跨边界信息的连续性。
/**
\* 使用重叠窗口分割文本
*/
private List splitTextWithOverlap(String text, int chunkSize, int overlapSize) {
List chunks = new ArrayList<>();
// 首先进行语义分割
List semanticChunks = splitTextIntoChunks(text, chunkSize);
// 添加重叠内容
for (int i = 0; i 0) {
String prevChunk = semanticChunks.get(i - 1);
String prevOverlap = getLastNChars(prevChunk, overlapSize / 2);
chunkWithOverlap.append(prevOverlap).append(" ");
}
// 添加当前chunk
chunkWithOverlap.append(semanticChunks.get(i));
// 添加下一个chunk的开头部分作为重叠
if (i (实体:牛逼) 这样的结构。这就把零散的知识点编织成了一张巨大的、相互连接的知识网络。

在检索阶段,传统的 RAG 是基于语义相似度的。就像我们在派聪明中做的那样,将用户问题向量化,然后在向量数据库中寻找与之最相似的文本块。
GraphRAG 的检索变成了在知识图谱上的图遍历或子图查询 。当用户问“沉默王二吹的牛逼是啥?”时,GraphRAG 可以:
- 第一跳 :在图谱中定位到“沉默王二”这个节点。
- 第二跳 :沿着“吹了”这条边,找到“牛逼”节点。
- 第三跳 :再从“牛逼”节点出发,找到“ 26 万订阅号读者”、“GitHub 14000+ star”等内容。
23.对RAG技术的未来发展有什么看法?
未来的 RAG 将是多模态的。图片、音频、视频、代码、表格、API……任何信息形态都可以被索引和检索。到时候,我们的提问可以是“这张图里穿蓝色衣服的人在做什么?”
未来的 RAG 将会向智能体方向发展,比如模型在检索后会先判断“我找到的知识足够回答问题吗?”如果不够,它可能会主动向用户追问,或者调用 function call 去外部查询,再根据前面的检索结果,生成新的、更精确的查询。

24.如果RAG系统返回0个检索结果,你会如何排查问题?
首先,我要确定这是特殊情况还是普遍现象。如果所有的提问都无法检索到结果,那很可能是系统级的故障,比如向量数据库连接失败了、索引被误删了、或者向量 API 服务宕机了。如果只是特殊情况,那么问题很可能出在数据处理、查询理解或召回策略上。比如,用户问了一个知识库里完全没有涉及的领域,或者查询的关键词过于生僻。
接着,我会检查召回的候选集数量 k 是否设置得过小,导致过滤条件叠加后没有结果。
权限 也需要排查。可能是用户的权限不足,无法访问相关文档。
25.如何处理不同API服务(豆包 embedding、DeepSeek)的调用失败?
在调用豆包向量 API 失败时,我们会自动回退到纯文本搜索,实现服务降级,确保检索服务可用。
// 在 HybridSearchService 中添加更精细的错误处理
public List> searchWithPermission(String query, String userId, int size) {
try {
// 尝试混合搜索
return performHybridSearch(query, userId, size);
} catch (EmbeddingServiceException e) {
logger.warn("向量服务不可用,降级到文本搜索: {}", e.getMessage());
return textOnlySearch(query, userId, size);
} catch (ElasticsearchException e) {
logger.error("搜索服务异常: {}", e.getMessage());
return Collections.emptyList(); // 或返回缓存结果
}
}
并且在调用豆包向量 API 时,我们采用了 Reactor 的重试机制,支持固定延迟重试 3 次,并设置了 30 秒的超时保护。
// 在 EmbeddingClient 中添加熔断器和更智能的重试
@Component
public class EmbeddingClient {
private final CircuitBreaker circuitBreaker;
private String callApiOnce(List batch) {
return circuitBreaker.executeSupplier(() -> {
return webClient.post()
.uri("/embeddings")
.bodyValue(requestBody)
.retrieve()
.bodyToMono(String.class)
.retryWhen(Retry.backoff(3, Duration.ofSeconds(1))
.maxBackoff(Duration.ofSeconds(10))
.filter(this::isRetryableException))
.timeout(Duration.ofSeconds(30))
.block();
});
}
private boolean isRetryableException(Throwable ex) {
return ex instanceof WebClientResponseException &&
((WebClientResponseException) ex).getStatusCode().is5xxServerError();
}
}
下个版本中,我们打算接入更多的大模型 API,当 DeepSeek 不可用的时候,能够自动切换到豆包、文心一言、腾讯混元、阿里通义千问等。派聪明目前已经支持快速切换到本地的模型服务。

26.请说说你AIGC、RAG、Agent 的理解?
AIGC,全称为 AI Generated Content,意为“人工智能生成内容”。它指的是利用人工智能技术自动生成文本、图片、音频、视频等多种内容的过程。2022 年 11 月 30 日,OpenAI 基于 GPT-3.5 的 ChatGPT 正式上线,引爆了 AIGC 热潮。

RAG,是一种将信息检索(IR) 与大型语言模型(LLM) 的文本生成能力相结合的技术。其核心思想是:当 LLM 需要回答一个问题或生成文本时,不是仅依赖其内部训练时学到的知识,而是先从一个外部知识库中检索出相关的信息片段,然后将这些检索到的信息与原始问题/指令一起提供给 LLM,让 LLM 基于这些最新、最相关的上下文信息来生成更准确、更可靠、更少幻觉的答案。

Agent,也就是“智能体”,在计算机科学和人工智能领域指的是 一个能够感知环境、自主决策并采取行动以实现特定目标的实体或系统。 它可以是软件程序、机器人硬件,甚至是生物实体(如人类或动物),但在 AI 领域通常指 软件智能体。
Agent 和 AIGC 最大的区别:
- AIGC 主要以生成式任务为主,而 Agent 是可以通过自主决策能力完成更多通用任务的智能系统。
- 常见的 AIGC 系统(文生文,文生图)的核心就是一个 生成模型,而 Agent 是一个集 Function Call 模型、软件工程于一体的复杂系统,需要处理模型和外界的信息交互。
- Agent 可以集成 AIGC 能力完成某些特定的任务,也就是 AIGC 可以是 Agent 系统里面的一个子模块。
也就是说,Agent 最大的特点是,借助 Function Call 模型,可以自主决策使用外接的一些工具来完成特定的任务。
27.那什么是 function call 模型?
Function Calling,也就是函数调用, 是大型语言模型的关键技术。 RAG 技术 是为了解决模型无法和外接数据交互的问题,但是 RAG 的局限在于只赋予了模型检索数据的能力,而 Function Calling 允许模型理解用户请求中的潜在意图,并自动生成结构化参数来调用外部 任何 函数/工具,从而突破纯文本生成的限制,实现与真实世界的交互,比如可以调用查天气、发邮件、数学计算等工具。
Function Call 模型最早由 OpenAI 在 2023 年 6 月 13 日提出并发布,首次在 GPT-4 模型上实现了 Function Calling 能力。

Function Call 需要先定义函数,向 LLM 描述函数的用途、输入参数格式(JSON Schema):
{
"name": "get_current_weather",
"description": "获取指定城市的天气",
"parameters": {
"type": "object",
"properties": {
"city": {
"type": "string",
"description": "城市名称"
},
"unit": {
"enum": ["celsius", "fahrenheit"]
}
},
"required": ["city"]
}
}
当用户提问“北京今天需要带伞吗?”
→ LLM 识别到意图需要调用 get_current_weather
→ 并生成结构化参数:{"city": "北京", "unit": "celsius"}
然后执行 get_current_weather 函数调用天气 API,获取真实数据:{"temp": 25, "rain_prob": 30%},然后将结果交回 LLM,生成最终回复:“北京今天 25°C,降水概率 30%,建议带伞。”
那也就是 OpenAI 发布 Function Call 模型后,Agent 才开始迅速发展。Agent 真正进入到公众视野,被大家广泛关注的事件是 2025 年 4 月 Manus 发布的通用智能体产品,引入了 Computer Use 和 Browser Use,首次展现出智能体的强大能力。

28.什么是 MCP?
MCP,是 Model Context Protocol 的缩写,也就是模型上下文协议,由人工智能公司 **Anthropic **于 2024 年 11 月 24 日 正式发布并开源。
MCP 协议旨在解决大型语言模型(LLM)与外部数据源、工具间的集成难题,被比喻为“AI 应用的 USB-C 接口“。通过标准化通信协议,将传统的“M×N 集成问题”(即多个模型与多个数据源的点对点连接)转化为“M+N 模式”,大幅降低开发成本。

MCP 自 2024 年 11 月 24 日 发布以来, OpenAI、Google、微软、腾讯、阿里、百度等头部企业纷纷接入 MCP,推动其成为 事实性行业标准。
29.了解 A2A 吗?
A2A ,即 Agent-to-Agent ,指的是一种系统架构,其包含多个独立的、专门的 Agent 进行协同工作 ,以完成比单个 Agent 能处理的更复杂的任务。
单个 Agent 就像是一个全能的“通才”,他什么都懂一点,但可能没有哪个领域是顶尖的。
A2A 就像一个专家团队,有项目经理、数据分析师、文案专家、软件工程师等。项目经理负责拆解任务,然后分发给最合适的专家去执行。

一个 Agent 要能解决问题,首先需要获取准确的信息。RAG 可以作为这个 Agent 获取和理解信息的核心工具之一。

A2A 架构的优势在于每个 Agent 都可以专注于一个特定领域(如代码执行、数据库查询、API 调用、文案写作),使得开发、测试和维护更加简单。
30.了解Transformer 吗?
Transformer 是近年来深度学习领域,尤其是自然语言处理(NLP)中,最具革命性的模型架构。它奠定了所有现代大型语言模型(LLM),包括 GPT、BERT 等的基础。
Transformer 最初是为机器翻译任务设计的,所以它有一个经典的编码器-解码器(Encoder-Decoder)结构。

GPT 本质上就是把 Transformer 的 Decoder 部分拿出来,进行大量的预训练。
在 Transformer 出现之前,处理序列数据(如文本)的主流模型是 RNN,它的工作方式像人阅读一样,一个词一个词地顺序处理,并试图通过一个“记忆单元”来记住前面的信息。
RNN 的问题是必须处理完前一个词才能处理后一个词,这在硬件(GPU/TPU)飞速发展的当下阶段,极大地限制了训练速度。并且当句子很长时,RNN 很难记住最开始的信息,会出现“遗忘”现象
Transformer 完全抛弃了 RNN 的循环结构,提出了自注意力机制。
对于一句话中的每一个词,自注意力机制都会计算这句话中所有其他词对这个词的“重要性”或“相关性”得分。然后根据这个得分,将所有词的信息加权融合,生成这个词在当前上下文中的新表示。
比如说在“派聪明是一个企业级的 RAG 知识库,它是由沉默王二的团队研发的”这个句子中,自注意力机制能够识别出“它”指的是派聪明,而不是沉默王二。
Transformer 通过位置编码感知单词在句子中的位置顺序。位置编码是一个与词向量维度相同的向量,通过数学公式(正弦和余弦函数)生成,包含了单词在序列中的绝对或相对位置信息。在输入模型前,它会和词向量相加,让模型知道每个词的位置。

在 RAG 中,最后负责整合检索到的知识并生成答案的那个“生成”模块,通常就是一个基于 Transformer 的大型语言模型。而用于将文本块转换为向量的模型,也都是基于 Transformer 的 Encoder 结构训练出来的。
31.在做检索时,你是否尝试过或了解过其他的重排(Re-ranking)方法?
我们考虑过一种轻量级的重排方法—— 倒数排名融合(RRF)。它是 Milvus 混合搜索的一种重新排名策略,核心思想是,一个文档如果在多个不同的召回列表中都排名靠前,那么它应该更重要。

具体来说,我们会分别从向量检索和关键词检索拿到两个排好序的文档列表。对于任何一个文档,我们计算一个 RRF 分数,公式是 1 / (k + rank1) + 1 / (k + rank2),其中 rank1 和 rank2 是它在两个列表中的排名(如果不在某个列表中,则该项为 0), k 是一个小的平滑常数(比如 60)。最后,我们根据这个新的 RRF 分数对所有文档进行最终排序。
另外就是大模型重排,将召回的 Top N 个文档块的内容,连同原始查询一起,通过一个精心设计的提示词全部提交给 LLM。这个 Prompt 大致会是这样:
查询 :[用户的原始问题]
文档列表 :
[文档 1]:[文档 1 的文本内容]
[文档 2]:[文档 2 的文本内容]
...
任务 :请根据以上文档与查询的相关性,对文档进行重排,并以 JSON 格式输出排序后的文档索引列表。
然后,我们再解析 LLM 返回的 JSON 结果,得到最终的排序。
技术派阅读体验会比星球更好: https://paicoding.com/column/10/19 支持微信扫码直接登录了。

1.请详细描述完整的RAG系统架构,包括主要组件和数据流向?
RAG 系统本质上要解决一个问题:如何让 AI 能够基于企业内部的知识库来回答用户问题。所以整个架构设计围绕着"文件上传-文件存储-向量生成-答案生成"这条主线来展开。
当用户上传文档后,我们首先通过 Upload 接口来处理上传文件,并支持分片上传避免大文件传输问题。然后很关键的一点是,我们没有选择同步处理,而是把文件处理任务丢到 Kafka 的消息队列里,这样用户上传完就能立即得到响应,不用等待漫长的处理过程。

接下来是文档解析环节, FileProcessingConsumer 作为 Kafka 消费者会异步处理这些任务。我们使用 Apache Tika 来解析各种格式的文档,比如 PDF、Word、Excel 等,然后通过 ParseService 把文档内容切分成小段,这样做的好处是既能保持语义的完整性,又能控制向量化的粒度。
向量化这块是整个 RAG 系统的核心, VectorizationService 会调用豆包的 Embedding API 把文本转换成向量表示。我们选择把这些向量存储在 Elasticsearch 中,主要是因为 ES 在向量检索方面的性能比较好,而且支持混合检索。
说到检索,这是 RAG 系统能否准确回答问题的关键。我们实现了混合检索策略,既有基于向量相似度的语义检索,也有传统的关键词检索,这样能够在不同场景下都有比较好的召回效果。特别重要的是,我们在检索时加入了权限控制,确保用户只能检索到自己有权限访问的文档。
生成这块,我们集成了 DeepSeek 大语言模型,通过 DeepSeekClient 来调用 API。这里有个技术细节就是我们支持流式响应,用户不用等到整个回答生成完才能看到结果,而是可以实时看到 AI 的回答过程,体验会好很多。

整个对话流程是通过 ChatHandler 来协调的,它会先调用检索服务找到相关文档,然后把这些文档作为上下文传给大语言模型,最后把生成的回答通过 WebSocket 实时推送给用户。我们还在 Redis 中维护了对话历史,这样 AI 能够理解上下文,进行多轮对话。
权限控制方面,我们实现了基于组织标签的多租户架构。通过 OrgTagAuthorizationFilter 确保用户只能访问自己组织内的文档,实现数据的安全隔离。
总的来说,这套 RAG 架构的设计理念就是要在保证准确性的前提下,尽可能提升用户体验和系统性能,同时确保企业级的安全性。
2.在设计RAG系统时,如何选择合适的向量数据库?Elasticsearch、Pinecone等有什么区别?
首先说说我们为什么在派聪明中选择了 Elasticsearch。第一个原因是我们团队对 ES 比较熟悉,第二个原因是 ES 的混合检索能力很强,既支持传统的全文检索,也支持向量检索,这对 RAG 系统来说是个很大的优势。

但是说实话,ES 在纯向量检索性能上并不是最优的选择。如果系统主要是向量相似度搜索,专门的向量数据库会更合适。比如 Pinecone,它是专门为向量检索设计的云服务,性能确实很不错,而且使用起来很简单,基本上开箱即用。但是有个问题就是成本,特别是数据量大的时候,费用会比较高。而且作为云服务,数据安全和合规性可能是一些企业需要考虑的问题。

就我个人的体验来说,可以先用 ES 这样的通用方案快速验证业务价值,等业务稳定后再根据性能瓶颈考虑迁移到专门的向量数据库。这样既能快速上线,又能控制技术风险。毕竟 RAG 系统的核心价值还是在业务逻辑和数据质量上,选择合适的就行,不一定非要追求最新最炫的技术。
3.RAG系统中的混合检索是什么?如何实现?
混合检索简单来说就是把不同的检索方法结合起来,取长补短,提高检索的准确性和召回率。

在派聪明项目中,混合检索主要是结合了两种检索方式:语义检索和关键词检索。语义检索就是基于向量相似度的,它能够理解查询的语义含义,即使用词不完全匹配也能找到相关内容。比如用户问"如何提升工作效率",它能找到包含"提高生产力"、"优化流程"这样语义相关的文档。而关键词检索就是传统的全文检索,它对精确匹配很有效,特别是一些专业术语、人名、地名这种。

在技术实现上,我们是这样做的。首先对用户查询同时执行向量检索和全文检索,然后把两个结果集合并。这里有个关键问题就是如何合并和排序。我们采用的是加权融合的方式,给语义检索和关键词检索分别设置权重,然后计算综合得分。
第一阶段:KNN 向量召回
// KNN 向量召回阶段
s.knn(kn -> kn
.field("vector")
.queryVector(queryVector) // 查询向量
.k(recallK) // 召回数量(topK * 30)
.numCandidates(recallK) // 候选数量
);
第二阶段:关键词过滤
// 必须命中关键词 + 权限过滤
s.query(q -> q.bool(b -> b
.must(mst -> mst.match(m -> m.field("textContent").query(query))) // 关键词匹配
.filter(f -> f.bool(bf -> bf
.should(s1 -> s1.term(t -> t.field("userId").value(userDbId))) // 用户权限
.should(s2 -> s2.term(t -> t.field("public").value(true))) // 公开文档
.should(s3 -> /* 组织权限 */) // 组织权限
))
));
第三个阶段:BM25 重排序
// BM25 rescore 重排序
s.rescore(r -> r
.windowSize(recallK)
.query(rq -> rq
.queryWeight(0.2d) // KNN分数权重20%
.rescoreQueryWeight(1.0d) // BM25分数权重100%
.query(rqq -> rqq.match(m -> m
.field("textContent")
.query(query)
.operator(Operator.And) // 严格关键词匹配
))
)
);
具体的算法是这样的:假设一个文档在语义检索中的得分是 0.8,在关键词检索中的得分是 0.6,我们可以设置语义检索权重为 0.7,关键词检索权重为 0.3,那么最终得分就是 0.8*0.7 + 0.6*0.3 = 0.74。当然这个权重可以根据实际效果来调整。
在此基础上,我们还加入了权限过滤的逻辑。用户只能检索到自己有权限访问的文档,这个过滤是在检索结果合并之后进行的。
// 三种权限访问模式
.should(s1 -> s1.term(t -> t.field("userId").value(userDbId))) // 自己的文档
.should(s2 -> s2.term(t -> t.field("public").value(true))) // 公开文档
.should(s3 -> /* 组织层级权限 */) // 组织文档
4.解释向量embedding的维度选择对系统性能的影响?为什么我们选择2048维而不是384维?
首先,从模型能力角度来看,我们使用的是火山引擎的 doubao-embedding-text-240515 模型。 根据官方文档,这个模型的最高维度向量是 2048 维,支持 512、1024 降维使用。我们选择 2048 维实际上是在使用这个模型的原生最高维度输出,这样能够最大程度地保留模型训练时学到的语义信息。

从语义表达能力来说,维度越高,向量能够表达的语义信息就越丰富。2048 维相比 384 维有着显著的优势。高维向量能够在语义空间中更精确地区分不同概念之间的细微差别,这对于 RAG 系统来说至关重要。
当然,高维度意味着成本更高。
从技术实现角度,我们在 ES 的 knowledge_base.json 中也配置了向量字段为 2048 维,使用 cosine 相似度计算。这个配置与豆包的 embedding 模型完全匹配。

5.如何解决向量检索中的"语义漂移"问题?
语义漂移是 RAG 系统中一个非常关键的问题,简单来说就是随着时间推移,向量表示的语义可能会发生偏移,导致检索效果下降。

语义漂移
在派聪明项目中,我们采用了多层次的解决策略。第一个层面是模型版本管理。我们在 EsDocument 中专门设计了 modelVersion 字段来记录每个向量是由哪个版本模型生成的。这样当我们升级向量模型时,可以识别出哪些向量需要重新生成。

第二个层面是增量更新策略。我们不是一次性替换所有历史向量,而是采用渐进式的方法。当检测到某些文档的检索效果明显下降时,我们会优先对这些文档进行重新向量化。这个过程可以通过用户反馈和检索点击率来触发。
第三个层面是混合检索的优势。我们结合了语义检索和关键词检索。即使语义向量出现漂移,关键词检索仍然能够提供稳定的基准效果。
6.在多租户RAG系统中,如何设计权限控制和数据隔离?
首先,我们采用了多层级的权限控制架构。在用户层面,系统支持普通用户和管理员两种角色,管理员拥有全局访问权限,可以管理所有用户的组织标签分配。在组织层面,我们设计了灵活的组织标签系统,每个用户可以属于多个组织,并且有一个主组织标签。特别值得一提的是,系统为每个用户自动创建私人组织标签(PRIVATE_用户名),确保用户有独立的私人空间。

在数据存储层面,我们设计了三个关键字段来实现数据隔离:userId 标识文档所有者,orgTag 标识文档所属组织,isPublic 标识是否为公开资源。
在文件上传时,系统会根据用户的主组织标签自动为文档分配组织标签,确保数据从源头就有正确的权限标识。同时,用户可以选择将文档设置为公开,这样其他用户也能访问。

我们还对请求加了过滤器,在每个请求到达控制器之前就进行权限验证:首先检查资源是否为公开资源,如果是则直接放行;然后验证用户是否为资源所有者,所有者拥有完全访问权限;接着检查用户是否为管理员,管理员拥有全局权限;最后进行组织标签匹配,只有用户的组织标签包含资源的组织标签时才允许访问。

在 RAG 系统的核心功能——混合检索中,我们还实现了权限感知的搜索。系统会根据用户的有效组织标签构建 Elasticsearch 查询条件,确保用户只能检索到有权限访问的文档。

7.当RAG检索到的知识与LLM预训练知识冲突时,你会如何处理?请提供具体的解决方案。
可以修改提示词,比如说设置优先级规则,让检索到的参考信息优先级高于预训练知识,在遇到冲突时优先采用检索信息,无法确定时明确告知“存在信息冲突,建议核实”。
ai:
prompt:
rules: |
你是派聪明知识助手,处理知识冲突时须遵守:
【优先级规则】
1. 检索到的参考信息优先级 > 你的预训练知识
2. 最新时间的信息 > 较旧的信息
3. 企业内部规定 > 通用规则
4. 具体场景规则 > 一般性原则
【冲突处理】
1. 发现冲突时,优先采用检索信息,并明确说明"根据最新检索信息"
2. 如检索信息不完整,可补充预训练知识,但需标注"补充:基于通用知识"
3. 存在明显矛盾时,同时呈现两种观点并标注来源
4. 无法确定时,明确告知"存在信息冲突,建议核实"
【回答格式】
- 检索信息:(来源#编号)
- 补充信息:(基于通用知识)
- 冲突提示:(⚠️信息冲突)
8.评估RAG系统检索质量的关键指标有哪些?
准确性指标 是最核心的。首先是召回率(Recall),也就是相关文档中有多少被成功检索出来了。比如用户问一个技术问题,实际上知识库中有 10 个相关文档,系统检索出了 7 个,那召回率就是 70%。

然后是精确率(Precision),检索出来的文档中有多少是真正相关的。如果我们检索出了 10 个文档,但只有 7 个是相关的,精确率就是 70%。
// 在 HybridSearchService 中添加评估逻辑
public EvaluationMetrics evaluateSearch(String query, List relevantDocIds, int
topK) {
List results = searchWithPermission(query, userId, topK);
// 计算 Precision@K
long relevantCount = results.stream()
.limit(topK)
.mapToLong(result -> relevantDocIds.contains(result.getFileMd5()) ? 1 : 0)
.sum();
double precision = (double) relevantCount / Math.min(topK, results.size());
// 计算 Recall@K
double recall = (double) relevantCount / relevantDocIds.size();
// 计算 MRR
double mrr = calculateMRR(results, relevantDocIds);
return new EvaluationMetrics(precision, recall, mrr);
}
9.什么是RAG中的"幻觉"问题?如何预防?
RAG 中的"幻觉"是指大语言模型生成的内容与检索到的真实信息不符,或者模型编造了不存在的信息。在派聪明项目中,我们采用了多种策略来预防幻觉问题。
首先是提示词,我们明确要求模型严格基于检索到的文档内容回答,不要添加文档中没有的信息。
:::color1
你的回答必须依据参考文献,若参考文献无法回答问题,则回复“无法回答”
:::
其次是检索质量,我们采用了最高维度的豆包向量模型,确保检索到的文档真正相关。同时,我还使用了混合检索策略,通过提高检索精度来减少无关信息的干扰。并通过设置相似度阈值,过滤掉相关性低的检索结果,确保只有高质量的上下文信息被传递给模型。

另外,我们为每个检索结果都添加了明确的来源标识和置信度信息,让模型清楚地知道信息的可靠性。
10.如何设计置信度评分机制来判断检索结果的可靠性?
置信度是 RAG 系统用来保证检索质量的一个重要指标。所以要综合多个维度来考虑。

第一个关键维度我认为是向量相似度。当用户搜索时,我们利用 Elasticsearch 的 KNN 算法计算查询向量和文档向量的余弦相似度,这个分数能反映语义层面的相关性——分数越高,说明文档和查询在语义上越接近。

第二个维度是文本关键词匹配度。我们会利用 ES 默认的 BM25 算法对关键词在文档中的出现频率、重要性进行打分。

然后综合计算出最后的得分 最终分数 = KNN分数 × 0.2 + BM25分数 × 1.0。

11.比较固定长度分块和语义分块的优缺点?
固定长度分块和语义分块是 RAG 系统中两种主要的文档分割策略,**固定长度分块**是最简单直接的方式,派聪明采用的就是这种方式,每 512 个字符为一块,虽然可能会把语义完整的内容强行切断,但实现起来非常容易,对于计算资源有限的我们来说,是一个非常实用的选择。

语义分块 会基于文档的语义结构来分割,比如按照段落、章节、主题来切分。这样能确保每个 chunk 包含相对完整的语义信息。我们打算在下一个版本中增加语义分块,对于结构化程度高的文档,比如技术手册、政策文件等,采用语义分块,充分利用文档的结构信息。对于结构化程度低的文档,比如聊天记录、邮件等,采用固定长度分块,确保处理的稳定性。
/**
\* 智能文本分割,保持语义完整性
*/
private List splitTextIntoChunks(String text, int chunkSize) {
List chunks = new ArrayList<>();
// 按段落分割
String[] paragraphs = text.split("\n\n+");
StringBuilder currentChunk = new StringBuilder();
for (String paragraph : paragraphs) {
// 如果单个段落超过chunk大小,需要进一步分割
if (paragraph.length() > chunkSize) {
// 先保存当前chunk
if (currentChunk.length() > 0) {
chunks.add(currentChunk.toString().trim());
currentChunk = new StringBuilder();
}
// 按句子分割长段落
List sentenceChunks = splitLongParagraph(paragraph, chunkSize);
chunks.addAll(sentenceChunks);
}
// 如果添加这个段落会超过chunk大小
else if (currentChunk.length() + paragraph.length() > chunkSize) {
// 保存当前chunk
if (currentChunk.length() > 0) {
chunks.add(currentChunk.toString().trim());
}
// 开始新chunk
currentChunk = new StringBuilder(paragraph);
}
// 可以添加到当前chunk
else {
if (currentChunk.length() > 0) {
currentChunk.append("\n\n");
}
currentChunk.append(paragraph);
}
}
// 添加最后一个chunk
if (currentChunk.length() > 0) {
chunks.add(currentChunk.toString().trim());
}
return chunks;
}
/**
\* 分割长段落,按句子边界
*/
private List splitLongParagraph(String paragraph, int chunkSize) {
List chunks = new ArrayList<>();
// 按句子分割
String[] sentences = paragraph.split("(? chunkSize) {
if (currentChunk.length() > 0) {
chunks.add(currentChunk.toString().trim());
currentChunk = new StringBuilder();
}
// 如果单个句子太长,按词分割
if (sentence.length() > chunkSize) {
chunks.addAll(splitLongSentence(sentence, chunkSize));
} else {
currentChunk.append(sentence);
}
} else {
currentChunk.append(sentence);
}
}
if (currentChunk.length() > 0) {
chunks.add(currentChunk.toString().trim());
}
return chunks;
}
/**
\* 分割超长句子,按词边界
*/
private List splitLongSentence(String sentence, int chunkSize) {
List chunks = new ArrayList<>();
String[] words = sentence.split("\\s+");
StringBuilder currentChunk = new StringBuilder();
for (String word : words) {
if (currentChunk.length() + word.length() + 1 > chunkSize) {
if (currentChunk.length() > 0) {
chunks.add(currentChunk.toString().trim());
currentChunk = new StringBuilder();
}
}
if (currentChunk.length() > 0) {
currentChunk.append(" ");
}
currentChunk.append(word);
}
if (currentChunk.length() > 0) {
chunks.add(currentChunk.toString().trim());
}
return chunks;
}
12.如何处理跨chunk的信息完整性问题?
最直接有效的解决方案是引入滑动窗口机制,在相邻 chunk 之间保持一定的重叠区域。具体实现上,可以优化派聪明的分块逻辑,设置 20-30% 的重叠率。例如,如果 chunk 大小为 512 字符,则每次移动 350-400 字符,保留 100-150 字符的重叠。这样能够确保被切断的信息在相邻 chunk 中得到保留,提高信息检索的完整性。重叠策略虽然会增加存储空间,但能显著改善跨边界信息的连续性。
/**
\* 使用重叠窗口分割文本
*/
private List splitTextWithOverlap(String text, int chunkSize, int overlapSize) {
List chunks = new ArrayList<>();
// 首先进行语义分割
List semanticChunks = splitTextIntoChunks(text, chunkSize);
// 添加重叠内容
for (int i = 0; i 0) {
String prevChunk = semanticChunks.get(i - 1);
String prevOverlap = getLastNChars(prevChunk, overlapSize / 2);
chunkWithOverlap.append(prevOverlap).append(" ");
}
// 添加当前chunk
chunkWithOverlap.append(semanticChunks.get(i));
// 添加下一个chunk的开头部分作为重叠
if (i (实体:牛逼) 这样的结构。这就把零散的知识点编织成了一张巨大的、相互连接的知识网络。

在检索阶段,传统的 RAG 是基于语义相似度的。就像我们在派聪明中做的那样,将用户问题向量化,然后在向量数据库中寻找与之最相似的文本块。
GraphRAG 的检索变成了在知识图谱上的图遍历或子图查询 。当用户问“沉默王二吹的牛逼是啥?”时,GraphRAG 可以:
- 第一跳 :在图谱中定位到“沉默王二”这个节点。
- 第二跳 :沿着“吹了”这条边,找到“牛逼”节点。
- 第三跳 :再从“牛逼”节点出发,找到“ 26 万订阅号读者”、“GitHub 14000+ star”等内容。
23.对RAG技术的未来发展有什么看法?
未来的 RAG 将是多模态的。图片、音频、视频、代码、表格、API……任何信息形态都可以被索引和检索。到时候,我们的提问可以是“这张图里穿蓝色衣服的人在做什么?”
未来的 RAG 将会向智能体方向发展,比如模型在检索后会先判断“我找到的知识足够回答问题吗?”如果不够,它可能会主动向用户追问,或者调用 function call 去外部查询,再根据前面的检索结果,生成新的、更精确的查询。

24.如果RAG系统返回0个检索结果,你会如何排查问题?
首先,我要确定这是特殊情况还是普遍现象。如果所有的提问都无法检索到结果,那很可能是系统级的故障,比如向量数据库连接失败了、索引被误删了、或者向量 API 服务宕机了。如果只是特殊情况,那么问题很可能出在数据处理、查询理解或召回策略上。比如,用户问了一个知识库里完全没有涉及的领域,或者查询的关键词过于生僻。
接着,我会检查召回的候选集数量 k 是否设置得过小,导致过滤条件叠加后没有结果。
权限 也需要排查。可能是用户的权限不足,无法访问相关文档。
25.如何处理不同API服务(豆包 embedding、DeepSeek)的调用失败?
在调用豆包向量 API 失败时,我们会自动回退到纯文本搜索,实现服务降级,确保检索服务可用。
// 在 HybridSearchService 中添加更精细的错误处理
public List> searchWithPermission(String query, String userId, int size) {
try {
// 尝试混合搜索
return performHybridSearch(query, userId, size);
} catch (EmbeddingServiceException e) {
logger.warn("向量服务不可用,降级到文本搜索: {}", e.getMessage());
return textOnlySearch(query, userId, size);
} catch (ElasticsearchException e) {
logger.error("搜索服务异常: {}", e.getMessage());
return Collections.emptyList(); // 或返回缓存结果
}
}
并且在调用豆包向量 API 时,我们采用了 Reactor 的重试机制,支持固定延迟重试 3 次,并设置了 30 秒的超时保护。
// 在 EmbeddingClient 中添加熔断器和更智能的重试
@Component
public class EmbeddingClient {
private final CircuitBreaker circuitBreaker;
private String callApiOnce(List batch) {
return circuitBreaker.executeSupplier(() -> {
return webClient.post()
.uri("/embeddings")
.bodyValue(requestBody)
.retrieve()
.bodyToMono(String.class)
.retryWhen(Retry.backoff(3, Duration.ofSeconds(1))
.maxBackoff(Duration.ofSeconds(10))
.filter(this::isRetryableException))
.timeout(Duration.ofSeconds(30))
.block();
});
}
private boolean isRetryableException(Throwable ex) {
return ex instanceof WebClientResponseException &&
((WebClientResponseException) ex).getStatusCode().is5xxServerError();
}
}
下个版本中,我们打算接入更多的大模型 API,当 DeepSeek 不可用的时候,能够自动切换到豆包、文心一言、腾讯混元、阿里通义千问等。派聪明目前已经支持快速切换到本地的模型服务。

26.请说说你AIGC、RAG、Agent 的理解?
AIGC,全称为 AI Generated Content,意为“人工智能生成内容”。它指的是利用人工智能技术自动生成文本、图片、音频、视频等多种内容的过程。2022 年 11 月 30 日,OpenAI 基于 GPT-3.5 的 ChatGPT 正式上线,引爆了 AIGC 热潮。

RAG,是一种将信息检索(IR) 与大型语言模型(LLM) 的文本生成能力相结合的技术。其核心思想是:当 LLM 需要回答一个问题或生成文本时,不是仅依赖其内部训练时学到的知识,而是先从一个外部知识库中检索出相关的信息片段,然后将这些检索到的信息与原始问题/指令一起提供给 LLM,让 LLM 基于这些最新、最相关的上下文信息来生成更准确、更可靠、更少幻觉的答案。

Agent,也就是“智能体”,在计算机科学和人工智能领域指的是 一个能够感知环境、自主决策并采取行动以实现特定目标的实体或系统。 它可以是软件程序、机器人硬件,甚至是生物实体(如人类或动物),但在 AI 领域通常指 软件智能体。
Agent 和 AIGC 最大的区别:
- AIGC 主要以生成式任务为主,而 Agent 是可以通过自主决策能力完成更多通用任务的智能系统。
- 常见的 AIGC 系统(文生文,文生图)的核心就是一个 生成模型,而 Agent 是一个集 Function Call 模型、软件工程于一体的复杂系统,需要处理模型和外界的信息交互。
- Agent 可以集成 AIGC 能力完成某些特定的任务,也就是 AIGC 可以是 Agent 系统里面的一个子模块。
也就是说,Agent 最大的特点是,借助 Function Call 模型,可以自主决策使用外接的一些工具来完成特定的任务。
27.那什么是 function call 模型?
Function Calling,也就是函数调用, 是大型语言模型的关键技术。 RAG 技术 是为了解决模型无法和外接数据交互的问题,但是 RAG 的局限在于只赋予了模型检索数据的能力,而 Function Calling 允许模型理解用户请求中的潜在意图,并自动生成结构化参数来调用外部 任何 函数/工具,从而突破纯文本生成的限制,实现与真实世界的交互,比如可以调用查天气、发邮件、数学计算等工具。
Function Call 模型最早由 OpenAI 在 2023 年 6 月 13 日提出并发布,首次在 GPT-4 模型上实现了 Function Calling 能力。

Function Call 需要先定义函数,向 LLM 描述函数的用途、输入参数格式(JSON Schema):
{
"name": "get_current_weather",
"description": "获取指定城市的天气",
"parameters": {
"type": "object",
"properties": {
"city": {
"type": "string",
"description": "城市名称"
},
"unit": {
"enum": ["celsius", "fahrenheit"]
}
},
"required": ["city"]
}
}
当用户提问“北京今天需要带伞吗?”
→ LLM 识别到意图需要调用 get_current_weather
→ 并生成结构化参数:{"city": "北京", "unit": "celsius"}
然后执行 get_current_weather 函数调用天气 API,获取真实数据:{"temp": 25, "rain_prob": 30%},然后将结果交回 LLM,生成最终回复:“北京今天 25°C,降水概率 30%,建议带伞。”
那也就是 OpenAI 发布 Function Call 模型后,Agent 才开始迅速发展。Agent 真正进入到公众视野,被大家广泛关注的事件是 2025 年 4 月 Manus 发布的通用智能体产品,引入了 Computer Use 和 Browser Use,首次展现出智能体的强大能力。

28.什么是 MCP?
MCP,是 Model Context Protocol 的缩写,也就是模型上下文协议,由人工智能公司 **Anthropic **于 2024 年 11 月 24 日 正式发布并开源。
MCP 协议旨在解决大型语言模型(LLM)与外部数据源、工具间的集成难题,被比喻为“AI 应用的 USB-C 接口“。通过标准化通信协议,将传统的“M×N 集成问题”(即多个模型与多个数据源的点对点连接)转化为“M+N 模式”,大幅降低开发成本。

MCP 自 2024 年 11 月 24 日 发布以来, OpenAI、Google、微软、腾讯、阿里、百度等头部企业纷纷接入 MCP,推动其成为 事实性行业标准。
29.了解 A2A 吗?
A2A ,即 Agent-to-Agent ,指的是一种系统架构,其包含多个独立的、专门的 Agent 进行协同工作 ,以完成比单个 Agent 能处理的更复杂的任务。
单个 Agent 就像是一个全能的“通才”,他什么都懂一点,但可能没有哪个领域是顶尖的。
A2A 就像一个专家团队,有项目经理、数据分析师、文案专家、软件工程师等。项目经理负责拆解任务,然后分发给最合适的专家去执行。

一个 Agent 要能解决问题,首先需要获取准确的信息。RAG 可以作为这个 Agent 获取和理解信息的核心工具之一。

A2A 架构的优势在于每个 Agent 都可以专注于一个特定领域(如代码执行、数据库查询、API 调用、文案写作),使得开发、测试和维护更加简单。
30.了解Transformer 吗?
Transformer 是近年来深度学习领域,尤其是自然语言处理(NLP)中,最具革命性的模型架构。它奠定了所有现代大型语言模型(LLM),包括 GPT、BERT 等的基础。
Transformer 最初是为机器翻译任务设计的,所以它有一个经典的编码器-解码器(Encoder-Decoder)结构。

GPT 本质上就是把 Transformer 的 Decoder 部分拿出来,进行大量的预训练。
在 Transformer 出现之前,处理序列数据(如文本)的主流模型是 RNN,它的工作方式像人阅读一样,一个词一个词地顺序处理,并试图通过一个“记忆单元”来记住前面的信息。
RNN 的问题是必须处理完前一个词才能处理后一个词,这在硬件(GPU/TPU)飞速发展的当下阶段,极大地限制了训练速度。并且当句子很长时,RNN 很难记住最开始的信息,会出现“遗忘”现象
Transformer 完全抛弃了 RNN 的循环结构,提出了自注意力机制。
对于一句话中的每一个词,自注意力机制都会计算这句话中所有其他词对这个词的“重要性”或“相关性”得分。然后根据这个得分,将所有词的信息加权融合,生成这个词在当前上下文中的新表示。
比如说在“派聪明是一个企业级的 RAG 知识库,它是由沉默王二的团队研发的”这个句子中,自注意力机制能够识别出“它”指的是派聪明,而不是沉默王二。
Transformer 通过位置编码感知单词在句子中的位置顺序。位置编码是一个与词向量维度相同的向量,通过数学公式(正弦和余弦函数)生成,包含了单词在序列中的绝对或相对位置信息。在输入模型前,它会和词向量相加,让模型知道每个词的位置。

在 RAG 中,最后负责整合检索到的知识并生成答案的那个“生成”模块,通常就是一个基于 Transformer 的大型语言模型。而用于将文本块转换为向量的模型,也都是基于 Transformer 的 Encoder 结构训练出来的。
31.在做检索时,你是否尝试过或了解过其他的重排(Re-ranking)方法?
我们考虑过一种轻量级的重排方法—— 倒数排名融合(RRF)。它是 Milvus 混合搜索的一种重新排名策略,核心思想是,一个文档如果在多个不同的召回列表中都排名靠前,那么它应该更重要。

具体来说,我们会分别从向量检索和关键词检索拿到两个排好序的文档列表。对于任何一个文档,我们计算一个 RRF 分数,公式是 1 / (k + rank1) + 1 / (k + rank2),其中 rank1 和 rank2 是它在两个列表中的排名(如果不在某个列表中,则该项为 0), k 是一个小的平滑常数(比如 60)。最后,我们根据这个新的 RRF 分数对所有文档进行最终排序。
另外就是大模型重排,将召回的 Top N 个文档块的内容,连同原始查询一起,通过一个精心设计的提示词全部提交给 LLM。这个 Prompt 大致会是这样:
查询 :[用户的原始问题]
文档列表 :
[文档 1]:[文档 1 的文本内容]
[文档 2]:[文档 2 的文本内容]
...
任务 :请根据以上文档与查询的相关性,对文档进行重排,并以 JSON 格式输出排序后的文档索引列表。
然后,我们再解析 LLM 返回的 JSON 结果,得到最终的排序。
技术派阅读体验会比星球更好: https://paicoding.com/column/10/19 支持微信扫码直接登录了。

1.请详细描述完整的RAG系统架构,包括主要组件和数据流向?
RAG 系统本质上要解决一个问题:如何让 AI 能够基于企业内部的知识库来回答用户问题。所以整个架构设计围绕着"文件上传-文件存储-向量生成-答案生成"这条主线来展开。
当用户上传文档后,我们首先通过 Upload 接口来处理上传文件,并支持分片上传避免大文件传输问题。然后很关键的一点是,我们没有选择同步处理,而是把文件处理任务丢到 Kafka 的消息队列里,这样用户上传完就能立即得到响应,不用等待漫长的处理过程。

接下来是文档解析环节, FileProcessingConsumer 作为 Kafka 消费者会异步处理这些任务。我们使用 Apache Tika 来解析各种格式的文档,比如 PDF、Word、Excel 等,然后通过 ParseService 把文档内容切分成小段,这样做的好处是既能保持语义的完整性,又能控制向量化的粒度。
向量化这块是整个 RAG 系统的核心, VectorizationService 会调用豆包的 Embedding API 把文本转换成向量表示。我们选择把这些向量存储在 Elasticsearch 中,主要是因为 ES 在向量检索方面的性能比较好,而且支持混合检索。
说到检索,这是 RAG 系统能否准确回答问题的关键。我们实现了混合检索策略,既有基于向量相似度的语义检索,也有传统的关键词检索,这样能够在不同场景下都有比较好的召回效果。特别重要的是,我们在检索时加入了权限控制,确保用户只能检索到自己有权限访问的文档。
生成这块,我们集成了 DeepSeek 大语言模型,通过 DeepSeekClient 来调用 API。这里有个技术细节就是我们支持流式响应,用户不用等到整个回答生成完才能看到结果,而是可以实时看到 AI 的回答过程,体验会好很多。

整个对话流程是通过 ChatHandler 来协调的,它会先调用检索服务找到相关文档,然后把这些文档作为上下文传给大语言模型,最后把生成的回答通过 WebSocket 实时推送给用户。我们还在 Redis 中维护了对话历史,这样 AI 能够理解上下文,进行多轮对话。
权限控制方面,我们实现了基于组织标签的多租户架构。通过 OrgTagAuthorizationFilter 确保用户只能访问自己组织内的文档,实现数据的安全隔离。
总的来说,这套 RAG 架构的设计理念就是要在保证准确性的前提下,尽可能提升用户体验和系统性能,同时确保企业级的安全性。
2.在设计RAG系统时,如何选择合适的向量数据库?Elasticsearch、Pinecone等有什么区别?
首先说说我们为什么在派聪明中选择了 Elasticsearch。第一个原因是我们团队对 ES 比较熟悉,第二个原因是 ES 的混合检索能力很强,既支持传统的全文检索,也支持向量检索,这对 RAG 系统来说是个很大的优势。

但是说实话,ES 在纯向量检索性能上并不是最优的选择。如果系统主要是向量相似度搜索,专门的向量数据库会更合适。比如 Pinecone,它是专门为向量检索设计的云服务,性能确实很不错,而且使用起来很简单,基本上开箱即用。但是有个问题就是成本,特别是数据量大的时候,费用会比较高。而且作为云服务,数据安全和合规性可能是一些企业需要考虑的问题。

就我个人的体验来说,可以先用 ES 这样的通用方案快速验证业务价值,等业务稳定后再根据性能瓶颈考虑迁移到专门的向量数据库。这样既能快速上线,又能控制技术风险。毕竟 RAG 系统的核心价值还是在业务逻辑和数据质量上,选择合适的就行,不一定非要追求最新最炫的技术。
3.RAG系统中的混合检索是什么?如何实现?
混合检索简单来说就是把不同的检索方法结合起来,取长补短,提高检索的准确性和召回率。

在派聪明项目中,混合检索主要是结合了两种检索方式:语义检索和关键词检索。语义检索就是基于向量相似度的,它能够理解查询的语义含义,即使用词不完全匹配也能找到相关内容。比如用户问"如何提升工作效率",它能找到包含"提高生产力"、"优化流程"这样语义相关的文档。而关键词检索就是传统的全文检索,它对精确匹配很有效,特别是一些专业术语、人名、地名这种。

在技术实现上,我们是这样做的。首先对用户查询同时执行向量检索和全文检索,然后把两个结果集合并。这里有个关键问题就是如何合并和排序。我们采用的是加权融合的方式,给语义检索和关键词检索分别设置权重,然后计算综合得分。
第一阶段:KNN 向量召回
// KNN 向量召回阶段
s.knn(kn -> kn
.field("vector")
.queryVector(queryVector) // 查询向量
.k(recallK) // 召回数量(topK * 30)
.numCandidates(recallK) // 候选数量
);
第二阶段:关键词过滤
// 必须命中关键词 + 权限过滤
s.query(q -> q.bool(b -> b
.must(mst -> mst.match(m -> m.field("textContent").query(query))) // 关键词匹配
.filter(f -> f.bool(bf -> bf
.should(s1 -> s1.term(t -> t.field("userId").value(userDbId))) // 用户权限
.should(s2 -> s2.term(t -> t.field("public").value(true))) // 公开文档
.should(s3 -> /* 组织权限 */) // 组织权限
))
));
第三个阶段:BM25 重排序
// BM25 rescore 重排序
s.rescore(r -> r
.windowSize(recallK)
.query(rq -> rq
.queryWeight(0.2d) // KNN分数权重20%
.rescoreQueryWeight(1.0d) // BM25分数权重100%
.query(rqq -> rqq.match(m -> m
.field("textContent")
.query(query)
.operator(Operator.And) // 严格关键词匹配
))
)
);
具体的算法是这样的:假设一个文档在语义检索中的得分是 0.8,在关键词检索中的得分是 0.6,我们可以设置语义检索权重为 0.7,关键词检索权重为 0.3,那么最终得分就是 0.8*0.7 + 0.6*0.3 = 0.74。当然这个权重可以根据实际效果来调整。
在此基础上,我们还加入了权限过滤的逻辑。用户只能检索到自己有权限访问的文档,这个过滤是在检索结果合并之后进行的。
// 三种权限访问模式
.should(s1 -> s1.term(t -> t.field("userId").value(userDbId))) // 自己的文档
.should(s2 -> s2.term(t -> t.field("public").value(true))) // 公开文档
.should(s3 -> /* 组织层级权限 */) // 组织文档
4.解释向量embedding的维度选择对系统性能的影响?为什么我们选择2048维而不是384维?
首先,从模型能力角度来看,我们使用的是火山引擎的 doubao-embedding-text-240515 模型。 根据官方文档,这个模型的最高维度向量是 2048 维,支持 512、1024 降维使用。我们选择 2048 维实际上是在使用这个模型的原生最高维度输出,这样能够最大程度地保留模型训练时学到的语义信息。

从语义表达能力来说,维度越高,向量能够表达的语义信息就越丰富。2048 维相比 384 维有着显著的优势。高维向量能够在语义空间中更精确地区分不同概念之间的细微差别,这对于 RAG 系统来说至关重要。
当然,高维度意味着成本更高。
从技术实现角度,我们在 ES 的 knowledge_base.json 中也配置了向量字段为 2048 维,使用 cosine 相似度计算。这个配置与豆包的 embedding 模型完全匹配。

5.如何解决向量检索中的"语义漂移"问题?
语义漂移是 RAG 系统中一个非常关键的问题,简单来说就是随着时间推移,向量表示的语义可能会发生偏移,导致检索效果下降。

语义漂移
在派聪明项目中,我们采用了多层次的解决策略。第一个层面是模型版本管理。我们在 EsDocument 中专门设计了 modelVersion 字段来记录每个向量是由哪个版本模型生成的。这样当我们升级向量模型时,可以识别出哪些向量需要重新生成。

第二个层面是增量更新策略。我们不是一次性替换所有历史向量,而是采用渐进式的方法。当检测到某些文档的检索效果明显下降时,我们会优先对这些文档进行重新向量化。这个过程可以通过用户反馈和检索点击率来触发。
第三个层面是混合检索的优势。我们结合了语义检索和关键词检索。即使语义向量出现漂移,关键词检索仍然能够提供稳定的基准效果。
6.在多租户RAG系统中,如何设计权限控制和数据隔离?
首先,我们采用了多层级的权限控制架构。在用户层面,系统支持普通用户和管理员两种角色,管理员拥有全局访问权限,可以管理所有用户的组织标签分配。在组织层面,我们设计了灵活的组织标签系统,每个用户可以属于多个组织,并且有一个主组织标签。特别值得一提的是,系统为每个用户自动创建私人组织标签(PRIVATE_用户名),确保用户有独立的私人空间。

在数据存储层面,我们设计了三个关键字段来实现数据隔离:userId 标识文档所有者,orgTag 标识文档所属组织,isPublic 标识是否为公开资源。
在文件上传时,系统会根据用户的主组织标签自动为文档分配组织标签,确保数据从源头就有正确的权限标识。同时,用户可以选择将文档设置为公开,这样其他用户也能访问。

我们还对请求加了过滤器,在每个请求到达控制器之前就进行权限验证:首先检查资源是否为公开资源,如果是则直接放行;然后验证用户是否为资源所有者,所有者拥有完全访问权限;接着检查用户是否为管理员,管理员拥有全局权限;最后进行组织标签匹配,只有用户的组织标签包含资源的组织标签时才允许访问。

在 RAG 系统的核心功能——混合检索中,我们还实现了权限感知的搜索。系统会根据用户的有效组织标签构建 Elasticsearch 查询条件,确保用户只能检索到有权限访问的文档。

7.当RAG检索到的知识与LLM预训练知识冲突时,你会如何处理?请提供具体的解决方案。
可以修改提示词,比如说设置优先级规则,让检索到的参考信息优先级高于预训练知识,在遇到冲突时优先采用检索信息,无法确定时明确告知“存在信息冲突,建议核实”。
ai:
prompt:
rules: |
你是派聪明知识助手,处理知识冲突时须遵守:
【优先级规则】
1. 检索到的参考信息优先级 > 你的预训练知识
2. 最新时间的信息 > 较旧的信息
3. 企业内部规定 > 通用规则
4. 具体场景规则 > 一般性原则
【冲突处理】
1. 发现冲突时,优先采用检索信息,并明确说明"根据最新检索信息"
2. 如检索信息不完整,可补充预训练知识,但需标注"补充:基于通用知识"
3. 存在明显矛盾时,同时呈现两种观点并标注来源
4. 无法确定时,明确告知"存在信息冲突,建议核实"
【回答格式】
- 检索信息:(来源#编号)
- 补充信息:(基于通用知识)
- 冲突提示:(⚠️信息冲突)
8.评估RAG系统检索质量的关键指标有哪些?
准确性指标 是最核心的。首先是召回率(Recall),也就是相关文档中有多少被成功检索出来了。比如用户问一个技术问题,实际上知识库中有 10 个相关文档,系统检索出了 7 个,那召回率就是 70%。

然后是精确率(Precision),检索出来的文档中有多少是真正相关的。如果我们检索出了 10 个文档,但只有 7 个是相关的,精确率就是 70%。
// 在 HybridSearchService 中添加评估逻辑
public EvaluationMetrics evaluateSearch(String query, List relevantDocIds, int
topK) {
List results = searchWithPermission(query, userId, topK);
// 计算 Precision@K
long relevantCount = results.stream()
.limit(topK)
.mapToLong(result -> relevantDocIds.contains(result.getFileMd5()) ? 1 : 0)
.sum();
double precision = (double) relevantCount / Math.min(topK, results.size());
// 计算 Recall@K
double recall = (double) relevantCount / relevantDocIds.size();
// 计算 MRR
double mrr = calculateMRR(results, relevantDocIds);
return new EvaluationMetrics(precision, recall, mrr);
}
9.什么是RAG中的"幻觉"问题?如何预防?
RAG 中的"幻觉"是指大语言模型生成的内容与检索到的真实信息不符,或者模型编造了不存在的信息。在派聪明项目中,我们采用了多种策略来预防幻觉问题。
首先是提示词,我们明确要求模型严格基于检索到的文档内容回答,不要添加文档中没有的信息。
:::color1
你的回答必须依据参考文献,若参考文献无法回答问题,则回复“无法回答”
:::
其次是检索质量,我们采用了最高维度的豆包向量模型,确保检索到的文档真正相关。同时,我还使用了混合检索策略,通过提高检索精度来减少无关信息的干扰。并通过设置相似度阈值,过滤掉相关性低的检索结果,确保只有高质量的上下文信息被传递给模型。

另外,我们为每个检索结果都添加了明确的来源标识和置信度信息,让模型清楚地知道信息的可靠性。
10.如何设计置信度评分机制来判断检索结果的可靠性?
置信度是 RAG 系统用来保证检索质量的一个重要指标。所以要综合多个维度来考虑。

第一个关键维度我认为是向量相似度。当用户搜索时,我们利用 Elasticsearch 的 KNN 算法计算查询向量和文档向量的余弦相似度,这个分数能反映语义层面的相关性——分数越高,说明文档和查询在语义上越接近。

第二个维度是文本关键词匹配度。我们会利用 ES 默认的 BM25 算法对关键词在文档中的出现频率、重要性进行打分。

然后综合计算出最后的得分 最终分数 = KNN分数 × 0.2 + BM25分数 × 1.0。

11.比较固定长度分块和语义分块的优缺点?
固定长度分块和语义分块是 RAG 系统中两种主要的文档分割策略,**固定长度分块**是最简单直接的方式,派聪明采用的就是这种方式,每 512 个字符为一块,虽然可能会把语义完整的内容强行切断,但实现起来非常容易,对于计算资源有限的我们来说,是一个非常实用的选择。

语义分块 会基于文档的语义结构来分割,比如按照段落、章节、主题来切分。这样能确保每个 chunk 包含相对完整的语义信息。我们打算在下一个版本中增加语义分块,对于结构化程度高的文档,比如技术手册、政策文件等,采用语义分块,充分利用文档的结构信息。对于结构化程度低的文档,比如聊天记录、邮件等,采用固定长度分块,确保处理的稳定性。
/**
\* 智能文本分割,保持语义完整性
*/
private List splitTextIntoChunks(String text, int chunkSize) {
List chunks = new ArrayList<>();
// 按段落分割
String[] paragraphs = text.split("\n\n+");
StringBuilder currentChunk = new StringBuilder();
for (String paragraph : paragraphs) {
// 如果单个段落超过chunk大小,需要进一步分割
if (paragraph.length() > chunkSize) {
// 先保存当前chunk
if (currentChunk.length() > 0) {
chunks.add(currentChunk.toString().trim());
currentChunk = new StringBuilder();
}
// 按句子分割长段落
List sentenceChunks = splitLongParagraph(paragraph, chunkSize);
chunks.addAll(sentenceChunks);
}
// 如果添加这个段落会超过chunk大小
else if (currentChunk.length() + paragraph.length() > chunkSize) {
// 保存当前chunk
if (currentChunk.length() > 0) {
chunks.add(currentChunk.toString().trim());
}
// 开始新chunk
currentChunk = new StringBuilder(paragraph);
}
// 可以添加到当前chunk
else {
if (currentChunk.length() > 0) {
currentChunk.append("\n\n");
}
currentChunk.append(paragraph);
}
}
// 添加最后一个chunk
if (currentChunk.length() > 0) {
chunks.add(currentChunk.toString().trim());
}
return chunks;
}
/**
\* 分割长段落,按句子边界
*/
private List splitLongParagraph(String paragraph, int chunkSize) {
List chunks = new ArrayList<>();
// 按句子分割
String[] sentences = paragraph.split("(? chunkSize) {
if (currentChunk.length() > 0) {
chunks.add(currentChunk.toString().trim());
currentChunk = new StringBuilder();
}
// 如果单个句子太长,按词分割
if (sentence.length() > chunkSize) {
chunks.addAll(splitLongSentence(sentence, chunkSize));
} else {
currentChunk.append(sentence);
}
} else {
currentChunk.append(sentence);
}
}
if (currentChunk.length() > 0) {
chunks.add(currentChunk.toString().trim());
}
return chunks;
}
/**
\* 分割超长句子,按词边界
*/
private List splitLongSentence(String sentence, int chunkSize) {
List chunks = new ArrayList<>();
String[] words = sentence.split("\\s+");
StringBuilder currentChunk = new StringBuilder();
for (String word : words) {
if (currentChunk.length() + word.length() + 1 > chunkSize) {
if (currentChunk.length() > 0) {
chunks.add(currentChunk.toString().trim());
currentChunk = new StringBuilder();
}
}
if (currentChunk.length() > 0) {
currentChunk.append(" ");
}
currentChunk.append(word);
}
if (currentChunk.length() > 0) {
chunks.add(currentChunk.toString().trim());
}
return chunks;
}
12.如何处理跨chunk的信息完整性问题?
最直接有效的解决方案是引入滑动窗口机制,在相邻 chunk 之间保持一定的重叠区域。具体实现上,可以优化派聪明的分块逻辑,设置 20-30% 的重叠率。例如,如果 chunk 大小为 512 字符,则每次移动 350-400 字符,保留 100-150 字符的重叠。这样能够确保被切断的信息在相邻 chunk 中得到保留,提高信息检索的完整性。重叠策略虽然会增加存储空间,但能显著改善跨边界信息的连续性。
/**
\* 使用重叠窗口分割文本
*/
private List splitTextWithOverlap(String text, int chunkSize, int overlapSize) {
List chunks = new ArrayList<>();
// 首先进行语义分割
List semanticChunks = splitTextIntoChunks(text, chunkSize);
// 添加重叠内容
for (int i = 0; i 0) {
String prevChunk = semanticChunks.get(i - 1);
String prevOverlap = getLastNChars(prevChunk, overlapSize / 2);
chunkWithOverlap.append(prevOverlap).append(" ");
}
// 添加当前chunk
chunkWithOverlap.append(semanticChunks.get(i));
// 添加下一个chunk的开头部分作为重叠
if (i (实体:牛逼) 这样的结构。这就把零散的知识点编织成了一张巨大的、相互连接的知识网络。

在检索阶段,传统的 RAG 是基于语义相似度的。就像我们在派聪明中做的那样,将用户问题向量化,然后在向量数据库中寻找与之最相似的文本块。
GraphRAG 的检索变成了在知识图谱上的图遍历或子图查询 。当用户问“沉默王二吹的牛逼是啥?”时,GraphRAG 可以:
- 第一跳 :在图谱中定位到“沉默王二”这个节点。
- 第二跳 :沿着“吹了”这条边,找到“牛逼”节点。
- 第三跳 :再从“牛逼”节点出发,找到“ 26 万订阅号读者”、“GitHub 14000+ star”等内容。
23.对RAG技术的未来发展有什么看法?
未来的 RAG 将是多模态的。图片、音频、视频、代码、表格、API……任何信息形态都可以被索引和检索。到时候,我们的提问可以是“这张图里穿蓝色衣服的人在做什么?”
未来的 RAG 将会向智能体方向发展,比如模型在检索后会先判断“我找到的知识足够回答问题吗?”如果不够,它可能会主动向用户追问,或者调用 function call 去外部查询,再根据前面的检索结果,生成新的、更精确的查询。

24.如果RAG系统返回0个检索结果,你会如何排查问题?
首先,我要确定这是特殊情况还是普遍现象。如果所有的提问都无法检索到结果,那很可能是系统级的故障,比如向量数据库连接失败了、索引被误删了、或者向量 API 服务宕机了。如果只是特殊情况,那么问题很可能出在数据处理、查询理解或召回策略上。比如,用户问了一个知识库里完全没有涉及的领域,或者查询的关键词过于生僻。
接着,我会检查召回的候选集数量 k 是否设置得过小,导致过滤条件叠加后没有结果。
权限 也需要排查。可能是用户的权限不足,无法访问相关文档。
25.如何处理不同API服务(豆包 embedding、DeepSeek)的调用失败?
在调用豆包向量 API 失败时,我们会自动回退到纯文本搜索,实现服务降级,确保检索服务可用。
// 在 HybridSearchService 中添加更精细的错误处理
public List> searchWithPermission(String query, String userId, int size) {
try {
// 尝试混合搜索
return performHybridSearch(query, userId, size);
} catch (EmbeddingServiceException e) {
logger.warn("向量服务不可用,降级到文本搜索: {}", e.getMessage());
return textOnlySearch(query, userId, size);
} catch (ElasticsearchException e) {
logger.error("搜索服务异常: {}", e.getMessage());
return Collections.emptyList(); // 或返回缓存结果
}
}
并且在调用豆包向量 API 时,我们采用了 Reactor 的重试机制,支持固定延迟重试 3 次,并设置了 30 秒的超时保护。
// 在 EmbeddingClient 中添加熔断器和更智能的重试
@Component
public class EmbeddingClient {
private final CircuitBreaker circuitBreaker;
private String callApiOnce(List batch) {
return circuitBreaker.executeSupplier(() -> {
return webClient.post()
.uri("/embeddings")
.bodyValue(requestBody)
.retrieve()
.bodyToMono(String.class)
.retryWhen(Retry.backoff(3, Duration.ofSeconds(1))
.maxBackoff(Duration.ofSeconds(10))
.filter(this::isRetryableException))
.timeout(Duration.ofSeconds(30))
.block();
});
}
private boolean isRetryableException(Throwable ex) {
return ex instanceof WebClientResponseException &&
((WebClientResponseException) ex).getStatusCode().is5xxServerError();
}
}
下个版本中,我们打算接入更多的大模型 API,当 DeepSeek 不可用的时候,能够自动切换到豆包、文心一言、腾讯混元、阿里通义千问等。派聪明目前已经支持快速切换到本地的模型服务。

26.请说说你AIGC、RAG、Agent 的理解?
AIGC,全称为 AI Generated Content,意为“人工智能生成内容”。它指的是利用人工智能技术自动生成文本、图片、音频、视频等多种内容的过程。2022 年 11 月 30 日,OpenAI 基于 GPT-3.5 的 ChatGPT 正式上线,引爆了 AIGC 热潮。

RAG,是一种将信息检索(IR) 与大型语言模型(LLM) 的文本生成能力相结合的技术。其核心思想是:当 LLM 需要回答一个问题或生成文本时,不是仅依赖其内部训练时学到的知识,而是先从一个外部知识库中检索出相关的信息片段,然后将这些检索到的信息与原始问题/指令一起提供给 LLM,让 LLM 基于这些最新、最相关的上下文信息来生成更准确、更可靠、更少幻觉的答案。

Agent,也就是“智能体”,在计算机科学和人工智能领域指的是 一个能够感知环境、自主决策并采取行动以实现特定目标的实体或系统。 它可以是软件程序、机器人硬件,甚至是生物实体(如人类或动物),但在 AI 领域通常指 软件智能体。
Agent 和 AIGC 最大的区别:
- AIGC 主要以生成式任务为主,而 Agent 是可以通过自主决策能力完成更多通用任务的智能系统。
- 常见的 AIGC 系统(文生文,文生图)的核心就是一个 生成模型,而 Agent 是一个集 Function Call 模型、软件工程于一体的复杂系统,需要处理模型和外界的信息交互。
- Agent 可以集成 AIGC 能力完成某些特定的任务,也就是 AIGC 可以是 Agent 系统里面的一个子模块。
也就是说,Agent 最大的特点是,借助 Function Call 模型,可以自主决策使用外接的一些工具来完成特定的任务。
27.那什么是 function call 模型?
Function Calling,也就是函数调用, 是大型语言模型的关键技术。 RAG 技术 是为了解决模型无法和外接数据交互的问题,但是 RAG 的局限在于只赋予了模型检索数据的能力,而 Function Calling 允许模型理解用户请求中的潜在意图,并自动生成结构化参数来调用外部 任何 函数/工具,从而突破纯文本生成的限制,实现与真实世界的交互,比如可以调用查天气、发邮件、数学计算等工具。
Function Call 模型最早由 OpenAI 在 2023 年 6 月 13 日提出并发布,首次在 GPT-4 模型上实现了 Function Calling 能力。

Function Call 需要先定义函数,向 LLM 描述函数的用途、输入参数格式(JSON Schema):
{
"name": "get_current_weather",
"description": "获取指定城市的天气",
"parameters": {
"type": "object",
"properties": {
"city": {
"type": "string",
"description": "城市名称"
},
"unit": {
"enum": ["celsius", "fahrenheit"]
}
},
"required": ["city"]
}
}
当用户提问“北京今天需要带伞吗?”
→ LLM 识别到意图需要调用 get_current_weather
→ 并生成结构化参数:{"city": "北京", "unit": "celsius"}
然后执行 get_current_weather 函数调用天气 API,获取真实数据:{"temp": 25, "rain_prob": 30%},然后将结果交回 LLM,生成最终回复:“北京今天 25°C,降水概率 30%,建议带伞。”
那也就是 OpenAI 发布 Function Call 模型后,Agent 才开始迅速发展。Agent 真正进入到公众视野,被大家广泛关注的事件是 2025 年 4 月 Manus 发布的通用智能体产品,引入了 Computer Use 和 Browser Use,首次展现出智能体的强大能力。

28.什么是 MCP?
MCP,是 Model Context Protocol 的缩写,也就是模型上下文协议,由人工智能公司 **Anthropic **于 2024 年 11 月 24 日 正式发布并开源。
MCP 协议旨在解决大型语言模型(LLM)与外部数据源、工具间的集成难题,被比喻为“AI 应用的 USB-C 接口“。通过标准化通信协议,将传统的“M×N 集成问题”(即多个模型与多个数据源的点对点连接)转化为“M+N 模式”,大幅降低开发成本。

MCP 自 2024 年 11 月 24 日 发布以来, OpenAI、Google、微软、腾讯、阿里、百度等头部企业纷纷接入 MCP,推动其成为 事实性行业标准。
29.了解 A2A 吗?
A2A ,即 Agent-to-Agent ,指的是一种系统架构,其包含多个独立的、专门的 Agent 进行协同工作 ,以完成比单个 Agent 能处理的更复杂的任务。
单个 Agent 就像是一个全能的“通才”,他什么都懂一点,但可能没有哪个领域是顶尖的。
A2A 就像一个专家团队,有项目经理、数据分析师、文案专家、软件工程师等。项目经理负责拆解任务,然后分发给最合适的专家去执行。

一个 Agent 要能解决问题,首先需要获取准确的信息。RAG 可以作为这个 Agent 获取和理解信息的核心工具之一。

A2A 架构的优势在于每个 Agent 都可以专注于一个特定领域(如代码执行、数据库查询、API 调用、文案写作),使得开发、测试和维护更加简单。
30.了解Transformer 吗?
Transformer 是近年来深度学习领域,尤其是自然语言处理(NLP)中,最具革命性的模型架构。它奠定了所有现代大型语言模型(LLM),包括 GPT、BERT 等的基础。
Transformer 最初是为机器翻译任务设计的,所以它有一个经典的编码器-解码器(Encoder-Decoder)结构。

GPT 本质上就是把 Transformer 的 Decoder 部分拿出来,进行大量的预训练。
在 Transformer 出现之前,处理序列数据(如文本)的主流模型是 RNN,它的工作方式像人阅读一样,一个词一个词地顺序处理,并试图通过一个“记忆单元”来记住前面的信息。
RNN 的问题是必须处理完前一个词才能处理后一个词,这在硬件(GPU/TPU)飞速发展的当下阶段,极大地限制了训练速度。并且当句子很长时,RNN 很难记住最开始的信息,会出现“遗忘”现象
Transformer 完全抛弃了 RNN 的循环结构,提出了自注意力机制。
对于一句话中的每一个词,自注意力机制都会计算这句话中所有其他词对这个词的“重要性”或“相关性”得分。然后根据这个得分,将所有词的信息加权融合,生成这个词在当前上下文中的新表示。
比如说在“派聪明是一个企业级的 RAG 知识库,它是由沉默王二的团队研发的”这个句子中,自注意力机制能够识别出“它”指的是派聪明,而不是沉默王二。
Transformer 通过位置编码感知单词在句子中的位置顺序。位置编码是一个与词向量维度相同的向量,通过数学公式(正弦和余弦函数)生成,包含了单词在序列中的绝对或相对位置信息。在输入模型前,它会和词向量相加,让模型知道每个词的位置。

在 RAG 中,最后负责整合检索到的知识并生成答案的那个“生成”模块,通常就是一个基于 Transformer 的大型语言模型。而用于将文本块转换为向量的模型,也都是基于 Transformer 的 Encoder 结构训练出来的。
31.在做检索时,你是否尝试过或了解过其他的重排(Re-ranking)方法?
我们考虑过一种轻量级的重排方法—— 倒数排名融合(RRF)。它是 Milvus 混合搜索的一种重新排名策略,核心思想是,一个文档如果在多个不同的召回列表中都排名靠前,那么它应该更重要。

具体来说,我们会分别从向量检索和关键词检索拿到两个排好序的文档列表。对于任何一个文档,我们计算一个 RRF 分数,公式是 1 / (k + rank1) + 1 / (k + rank2),其中 rank1 和 rank2 是它在两个列表中的排名(如果不在某个列表中,则该项为 0), k 是一个小的平滑常数(比如 60)。最后,我们根据这个新的 RRF 分数对所有文档进行最终排序。
另外就是大模型重排,将召回的 Top N 个文档块的内容,连同原始查询一起,通过一个精心设计的提示词全部提交给 LLM。这个 Prompt 大致会是这样:
查询 :[用户的原始问题]
文档列表 :
[文档 1]:[文档 1 的文本内容]
[文档 2]:[文档 2 的文本内容]
...
任务 :请根据以上文档与查询的相关性,对文档进行重排,并以 JSON 格式输出排序后的文档索引列表。
然后,我们再解析 LLM 返回的 JSON 结果,得到最终的排序。