第一部分:RAG(检索增强生成)【阿里必问,背熟】

发布于
第一部分:RAG(检索增强生成)【阿里必问,背熟】

好,那我们换打法。

下面这份内容是**「逐字稿级」背诵材料**,不是提纲,而是可以直接说出来、面试官听了会觉得你真做过、而且讲得很顺的那种。

我按「高频问题 → 完整答案(可直接背诵)→ 埋点(方便你后面接项目)」来写。

你现在不需要理解,只需要像背课文一样背下来。等你项目研究透了,这些“埋点”就是你展示深度的出口。


第一部分:RAG(检索增强生成)【阿里必问,背熟】

Q1:请你详细讲一下什么是 RAG,为什么要做 RAG,它的核心价值是什么?

【完整背诵答案】

RAG 是 Retrieval-Augmented Generation 的缩写,中文叫检索增强生成。它是一种将信息检索和大语言模型生成结合在一起的技术架构。

它的核心工作流程可以分为三个阶段:

第一阶段是索引阶段,我们会把私域知识库里的文档进行清洗、分块,然后通过 Embedding 模型转换成向量,存入向量数据库中,同时保留原始文本;

第二阶段是检索阶段,当用户发起 Query 时,系统会把用户的 Query 也向量化,然后在向量数据库中进行相似性检索,召回与 Query 最相关的若干个文本块;

第三阶段是生成阶段,系统会将召回的文本块作为上下文,和用户 Query 一起拼接到 Prompt 中,发送给大语言模型,让模型基于给定的上下文生成最终答案。

之所以要做 RAG,主要是为了解决大语言模型的三个天然缺陷:

第一是知识陈旧问题,大模型预训练的数据是有截止日期的,无法直接回答训练数据之后发生的事件或最新的业务数据;

第二是幻觉问题,当模型遇到训练数据中不存在的知识时,很容易编造答案,这在企业级应用中是不可接受的;

第三是私有数据无法访问的问题,企业的核心知识库、客服话术、技术文档等私有数据通常不会公开,模型在预训练阶段并没有见过这些内容。

RAG 的核心价值在于,它让大模型在生成答案时能够基于真实的、可追溯的外部知识,既保证了答案的准确性,又避免了频繁微调模型带来的高昂成本。此外,RAG 还能通过返回参考来源,提升系统的可解释性。

**【埋点:后面可以接】

“在我之前的项目中,我们主要落地的是面向客服的 RAG 系统,当时面临的最大挑战其实是召回精度问题……”**


Q2:RAG 里文档切分(Chunking)是怎么做的?Chunk Size 怎么选?为什么不能太大或太小?

【完整背诵答案】

文档切分是 RAG 中非常关键的一环,直接影响召回质量和生成效果。我们常用的切分策略主要有三种:

第一种是固定长度切分,就是按照固定的 Token 数量,比如 256 个或 512 个 Token 进行切分,这种方式实现简单,但容易把一个完整的语义单元切断;

第二种是基于结构的切分,比如针对 Markdown 文档,按照标题层级进行切分,针对代码,按照函数或类进行切分,这种方式能很好地保留语义完整性;

第三种是滑动窗口切分,就是在固定长度的基础上,让相邻的 Chunk 之间有一定的重叠,比如重叠 10% 到 20%,这样可以避免关键信息刚好出现在两个 Chunk 的边界上而被切断。

关于 Chunk Size 的选择,这是一个典型的 Trade-off。

如果 Chunk Size 太小,会导致上下文信息不足,模型在生成时可能看不到完整的背景信息,从而影响答案的准确性;同时,过小的 Chunk 会增加检索阶段的噪音,也会提高存储和检索的成本。

如果 Chunk Size 太大,首先会引入大量无关信息,干扰模型判断,也就是常说的“Lost in the Middle”问题;其次会占用过多的 Prompt 窗口,浪费 Token,增加推理成本,甚至超过模型的最大上下文长度限制。

在实际项目中,我们一般会以 300 到 500 个 Token 作为一个基准区间,然后通过离线评估 + 线上 AB 测试来确定最优值。比如我们会构造一批标准 QA 对,分别用不同的 Chunk Size 进行召回测试,看哪个尺寸的召回率和最终答案准确率最高。

另外,针对不同类型的文档,Chunk Size 也会动态调整。比如对于 FAQ 类的短文本,Chunk Size 可以设得小一些;对于技术文档或合同,则需要更大的 Chunk Size 来保证语义完整。

**【埋点】

“我们在做技术文档问答时,还专门对代码块做了特殊处理,比如一个函数如果超过 Chunk Size,我们会强制按函数边界切分,而不是硬截断……”**


Q3:向量检索的原理是什么?HNSW 为什么快?为什么不用暴力检索?

【完整背诵答案】

向量检索的本质是在高维向量空间中,找到与查询向量最相似的若干个向量。衡量相似度的常用指标有欧氏距离和余弦相似度,在实际应用中,我们通常会先对所有向量做归一化处理,这样内积、余弦相似度和欧氏距离在排序上是等价的。

如果不考虑性能,最直观的方法是暴力检索,也就是把查询向量和库中的所有向量逐一计算相似度,然后取 Top-K。这种方法优点是准确率 100%,召回率是 1.0,但缺点是时间复杂度是 O(N),当数据量达到百万、千万级别时,单次检索的延迟会非常高,完全无法满足线上实时性要求。

所以在工业界,我们通常使用近似最近邻搜索,也就是 ANN。其中 HNSW 是目前应用最广泛的一种算法。

HNSW 全称是 Hierarchical Navigable Small World,中文叫分层可导航小世界。它的核心思想借鉴了小世界网络和跳表。

它构建了一个多层图结构:

最上层节点少、边长长,适合快速跳转,可以迅速缩小搜索范围;

最下层节点多、边长短,适合精细搜索。

在检索时,算法会从最上层的一个入口点开始,在每一层找到局部最近的节点,然后进入下一层,直到最底层,最终得到最近邻结果。

HNSW 之所以快,是因为它将搜索复杂度从暴力检索的 O(N) 降低到了 O(log N) 级别。同时,它通过图的连接关系,保证了在大规模数据集上依然能维持较高的召回率。

当然,HNSW 也有代价,比如建图阶段比较耗时,内存占用较高,而且对新数据的插入不如倒排索引灵活。但在 RAG 场景下,知识库的更新频率通常不是极高,因此 HNSW 是一个非常理想的权衡选择。

**【埋点】

“在我们的生产环境中,使用的是 Milvus 配合 HNSW 索引,当时我们还专门调优过 efConstruction 和 efSearch 这两个参数,efConstruction 设得高一点,索引质量会更好,但建图慢;efSearch 设得高一点,召回率会提升,但查询延迟会增加……”**


Q4:什么是 Rerank?为什么有了向量检索还要做 Rerank?Bi-Encoder 和 Cross-Encoder 的区别是什么?

【完整背诵答案】

Rerank,也就是重排序,是 RAG 链路中在初步召回之后、送入大模型之前的一个关键环节。

虽然向量检索速度很快,但它存在一个天然的缺陷:向量检索是一个“粗排”过程,它只能捕捉语义的整体相似性,而无法精确建模 Query 和 Document 之间的细粒度交互。举个例子,Query 是“如何退款”,向量检索可能会召回所有包含“退款”这个词的文章,但这些文章可能讲的是“退款政策”“退款时效”“退款失败处理”,相关性其实参差不齐。

这时候就需要 Rerank 模型来做精细化排序。Rerank 模型通常采用的是 Cross-Encoder 结构。它会将 Query 和每一个候选 Document 拼接在一起,作为一个整体输入到 Transformer 模型中,让模型直接计算这两者之间的相关性得分。因为 Query 和 Document 的 Token 在 Transformer 的每一层都可以相互 Attention,所以 Cross-Encoder 能捕捉到非常细微的语义匹配信号,排序精度远高于向量检索。

这里就要说到 Bi-Encoder 和 Cross-Encoder 的区别:

Bi-Encoder 就是我们做向量检索时用的模型,它分别将 Query 和 Document 编码成两个独立的向量,相似度通过向量点积或余弦计算。它的优点是速度快,可以离线计算 Document 的向量,线上只需要一次向量检索,适合大规模召回。

Cross-Encoder 则不产生中间向量,而是直接输出一个相关性分数。它的优点是精度高,缺点是无法离线处理 Document,每次检索都需要将 Query 和每个候选 Document 实时计算一遍,延迟很高,计算成本大。

因此,在工业界的最佳实践通常是级联结构:先用 Bi-Encoder(向量检索)快速从海量数据中召回 Top-100 或 Top-200 的候选集,再用 Cross-Encoder(Rerank)对这个小规模候选集进行精细化重排序,取 Top-5 或 Top-10 送入大模型。这样既保证了召回的效率,又保证了最终输入上下文的相关性。

**【埋点】

“我们在线上落地时,Rerank 模型用的是 BGE-Reranker,一开始我们是把 Rerank 放在在线链路里的,后来发现 P95 延迟有点高,于是我们做了一个优化:对于高频 Query,我们把 Rerank 的结果缓存了起来……”**


第二部分:Agent(智能体)【阿里近两年超爱问】

Q5:什么是 Agent?Agent 和普通的 Chatbot 有什么区别?ReAct 范式讲一下。

【完整背诵答案】

Agent,中文叫智能体,是一种具备自主规划、工具调用、记忆和反思能力的 AI 系统。它不仅仅是被动地回答问题,而是能够主动理解复杂目标,并将其拆解为一系列可执行的步骤。

和普通的 Chatbot 相比,Chatbot 更像是一个“问答机器”,它的行为模式是:用户输入 Prompt,模型直接生成回复,整个过程是单轮的、无状态的,模型无法主动获取外部信息,也无法执行实际操作。

而 Agent 则完全不同,它具有以下几个核心特征:

第一是规划能力,它能将一个复杂任务拆解成多个子任务,比如用户问“帮我分析一下上个月北京地区的销售数据并生成图表”,Agent 会先拆解为:查询数据库 → 数据清洗 → 数据分析 → 绘图;

第二是工具调用能力,Agent 可以调用外部 API、搜索引擎、计算器、代码解释器等工具来获取信息或执行操作;

第三是记忆能力,包括短期记忆(上下文窗口)和长期记忆(外部向量存储),能够积累历史交互信息;

第四是反思能力,Agent 能够根据工具执行的结果调整后续计划,甚至从错误中学习。

目前最主流的 Agent 范式是 ReAct,也就是 Reason + Act。

ReAct 的核心思想是让大语言模型在生成过程中,交替输出思考(Thought)和行动(Action)。

具体来说,面对一个任务,模型首先会输出 Thought,分析当前局面、制定下一步计划;然后输出 Action,决定调用哪个工具、传入什么参数;系统执行 Action 后得到 Observation(观察结果),再将 Observation 反馈给模型;模型基于 Observation 继续输出 Thought 和 Action,如此循环,直到得出最终答案。

相比于单纯的 Chain-of-Thought(思维链),ReAct 引入了外部工具的反馈,打破了模型知识的时空限制,能够处理更加动态和复杂的任务。相比于单纯的 Action 执行,ReAct 增加了 Reasoning 过程,使得决策更加透明和可解释。

**【埋点】

“在我们内部的 Agent 平台中,除了 ReAct,我们还尝试过 Plan-and-Execute 的模式,也就是先一次性生成完整计划,再分步执行,这种模式在任务路径清晰的场景下效率更高,但容错率较低……”**


Q6:Agent 调用工具失败了怎么办?如何防止 Agent 陷入死循环?

【完整背诵答案】

Agent 调用工具失败是一个非常常见的线上问题,我们在设计系统时必须有完善的容错机制。

针对工具调用失败,我们通常会从以下几个层面进行处理:

第一是重试机制。对于网络抖动、服务超时等临时性错误,我们会采用指数退避策略进行重试,比如第一次等待 1 秒,第二次等待 2 秒,第三次等待 4 秒,最多重试 3 次。

第二是Fallback 机制,也就是降级。如果一个关键工具调用失败,我们会尝试调用备用工具。比如天气查询接口挂了,可以尝试调用另一个天气数据源;如果所有天气接口都不可用,可以返回一个缓存的历史数据,或者直接告知用户当前服务不可用。

第三是异常处理与兜底回复。如果工具调用彻底失败,且无法降级,Agent 应该捕获异常,并在 Thought 中分析原因,然后生成一个友好的兜底回复,比如“抱歉,查询订单信息时出现系统错误,请您稍后再试或联系人工客服”,而不是直接崩溃或胡言乱语。

第四是人工接管机制。对于高价值的业务流程,当 Agent 多次尝试仍失败时,应该能够主动识别并转人工处理。

关于防止 Agent 陷入死循环,这是 Agent 稳定性设计的重中之重。我们通常采用以下几种策略:

第一是设置最大步数(Max Steps)。严格限制 Agent 在一次任务中允许执行的 Action 次数,比如上限设为 10 步或 15 步,一旦超过立即强制终止,并返回超时或失败信息。

第二是循环检测。系统会维护一个 Action-Observation 的历史序列,如果发现 Agent 连续多次执行相同或高度相似的 Action,并且得到的 Observation 也没有实质性变化,就判定为死循环,立即中断。

第三是反思与自我纠正(Reflection)。在 Prompt 中引导 Agent,如果发现某个工具连续调用失败,应该停下来反思是否是参数错误、工具选择错误或任务本身不可行,并尝试更换策略。

第四是超时控制。为每个 Action 设置合理的超时时间,避免因为某个工具阻塞而导致整个 Agent 进程挂起。

通过这些机制的组合,我们可以有效提升 Agent 在复杂环境下的鲁棒性和用户体验。

**【埋点】

“我们有一个真实的线上案例:Agent 在处理用户退款请求时,因为订单状态同步有延迟,导致查询订单工具反复返回‘处理中’,Agent 就一直在重试。后来我们加了一个规则:如果同一个工具返回相同状态超过 3 次,就强制进入人工审核流程……”**


第三部分:工程与 Java 后端【阿里社招硬指标】

Q7:SSE 和 WebSocket 的区别是什么?在 AI 大模型流式返回场景下,为什么更倾向于用 SSE?

【完整背诵答案】

SSE(Server-Sent Events)和 WebSocket 都是实现服务端向客户端推送数据的技术,但它们在设计理念、通信模式和适用场景上有很大区别。

首先从通信模式上看,SSE 是基于 HTTP 的单向通信,只允许服务端向客户端发送数据,客户端只能通过传统的 HTTP 请求向服务端发送数据;而 WebSocket 是全双工的双向通信,一旦连接建立,客户端和服务端都可以在任何时候主动向对方发送数据。

其次从协议层面看,SSE 本质上是 HTTP 协议的一个扩展,它复用了 HTTP 的连接机制,握手过程就是一次普通的 HTTP GET 请求,服务端返回的 Content-Type 是 text/event-stream;而 WebSocket 在建立连接时需要一次 HTTP 协议的 Upgrade 握手,成功后才切换到 WebSocket 专有协议。

再从复杂度来看,SSE 的实现非常简单,浏览器原生支持 EventSource API,服务端只需要按照特定格式输出文本流即可;WebSocket 的实现相对复杂,需要处理连接管理、心跳检测、断线重连等复杂逻辑。

最后从断线重连来看,SSE 规范内置了自动重连机制,浏览器在连接断开后会自动尝试重新连接,并且可以携带上次连接的 Last-Event-ID,方便服务端续传;WebSocket 的重连逻辑需要开发者自行实现。

在 AI 大模型流式返回场景下,我们更倾向于使用 SSE,主要原因有以下几点:

第一,场景匹配。大模型生成文本是一个典型的“服务端持续推送、客户端被动接收”的场景,客户端不需要频繁向服务端发送数据,单向通信的 SSE 完全够用,没有必要引入复杂的全双工通信。

第二,实现简单,维护成本低。SSE 基于 HTTP,可以无缝集成现有的 Web 基础设施,比如负载均衡、反向代理(Nginx 对 SSE 支持良好)、监控体系等。而 WebSocket 对这些基础设施有特殊要求,运维成本更高。

第三,更好的错误处理和重连机制。AI 生成过程可能较长,网络波动容易导致连接中断。SSE 的自动重连特性可以保证用户体验的连续性。

第四,资源占用更低。SSE 连接在空闲时占用的资源更少,更适合高并发场景。

当然,如果应用场景需要客户端和服务端频繁交互,比如多人在线协作编辑、实时游戏等,那么 WebSocket 会是更合适的选择。但对于大模型 Chat 或 Copilot 这类应用,SSE 是事实上的最佳实践。

**【埋点】

“我们在网关层还专门做了适配,比如 Nginx 的 proxy_buffering 要关掉,否则会导致流式输出被缓冲,用户感知到的延迟会变高。另外,我们还在 SSE 流里自定义了 [DONE] 事件,用来标识生成结束……”**


Q8:线程池的核心参数有哪些?如何根据业务场景设置?IO 密集型和 CPU 密集型有什么区别?

【完整背诵答案】

Java 线程池的核心参数主要在 ThreadPoolExecutor 的构造函数中定义,一共有 7 个参数,其中最核心的 5 个是:

corePoolSize(核心线程数):线程池中常驻的线程数量,即使它们处于空闲状态也不会被回收(除非设置了 allowCoreThreadTimeOut)。

maximumPoolSize(最大线程数):线程池允许创建的最大线程数量。当工作队列满了之后,线程池可以临时扩容到这个数量。

keepAliveTime(空闲线程存活时间):当线程数超过 corePoolSize 时,多余的空闲线程在终止前等待新任务的最长时间。

unit(时间单位):keepAliveTime 的单位,比如秒、毫秒。

workQueue(工作队列):用于存放等待执行的任务的阻塞队列。常见的有 ArrayBlockingQueue(有界数组队列)、LinkedBlockingQueue(链表队列,默认 Integer.MAX_VALUE,容易 OOM)、SynchronousQueue(同步队列,不存储元素,直接交付)。

threadFactory:用于创建线程的工厂,可以用来给线程命名、设置守护线程等。

RejectedExecutionHandler(拒绝策略):当线程池和工作队列都满了之后,如何处理新提交的任务。常见的有 AbortPolicy(默认,抛异常)、CallerRunsPolicy(调用者线程自己运行)、DiscardPolicy(直接丢弃)、DiscardOldestPolicy(丢弃队列最老任务)。

关于线程池参数的设置,核心是要区分业务场景是 CPU 密集型还是 IO 密集型。

CPU 密集型任务是指任务主要消耗 CPU 资源,比如复杂的计算、视频编码、加密解密等。这类任务的特点是 CPU 利用率很高,线程数过多会导致频繁的上下文切换,反而降低性能。因此,CPU 密集型任务的线程数通常设置为 CPU 核数 + 1。多出来的这一个线程是为了防止偶尔的缺页中断或任务暂停时,CPU 出现闲置。

IO 密集型任务是指任务需要频繁等待 IO 操作,比如网络请求、数据库查询、文件读写等。在大模型应用开发中,调用 LLM API、查询向量数据库、访问 Redis 都属于 IO 密集型。这类任务的特点是线程经常处于阻塞状态,CPU 利用率不高。为了充分利用 CPU,线程数应该设置得更大。经验公式是:线程数 = CPU 核数 × (1 + 平均等待时间 / 平均计算时间)。在实际工程中,如果没有精确的监控数据,通常设置为 CPU 核数 × 2 或更高,比如 16 核的机器,可以设置为 32 或 48。

具体到我们的 AI 应用,比如在处理用户请求的线程池中,由于涉及到大量的 RPC 调用(调用模型服务、检索服务等),属于典型的 IO 密集型,我们会将 corePoolSize 设置为 CPU 核数的 2 倍,maximumPoolSize 设置为 4 倍,并使用 LinkedBlockingQueue 或 ArrayBlockingQueue 作为工作队列,同时配合 CallerRunsPolicy 作为拒绝策略,避免在流量洪峰时直接拒绝用户请求,而是让提交任务的线程暂时参与执行,起到一定的限流作用。

**【埋点】

“在我们的 RAG 服务中,有一个专门的线程池用于处理 Embedding 请求,因为 Embedding 模型推理虽然是 CPU/GPU 密集型,但我们是通过 RPC 调用的,所以对本地服务来说仍然是 IO 密集型。我们还针对不同的模型服务配置了不同的线程池,实现资源隔离……”**


第四部分:大模型基础(八股文)

Q9:Transformer 的结构是怎样的?Self-Attention 的计算过程是什么?为什么要有多头注意力?

【完整背诵答案】

Transformer 是 Google 在 2017 年提出的经典模型架构,目前几乎所有的大语言模型都是基于 Transformer 或其变体构建的。

Transformer 整体上是一个 Encoder-Decoder 结构。

Encoder(编码器) 由 N 个相同的层堆叠而成,每一层包含两个子层:一个是 Multi-Head Self-Attention 机制,另一个是 Position-wise Feed-Forward Network(前馈神经网络)。每个子层周围都使用了残差连接和 Layer Normalization。

Decoder(解码器) 同样由 N 个相同的层堆叠而成,但与 Encoder 不同的是,每一层包含三个子层:第一个是 Masked Multi-Head Self-Attention,它加入了掩码机制,确保位置 i 的预测只能依赖于位置小于 i 的已知输出;第二个是 Multi-Head Cross-Attention,它接收 Encoder 的输出作为 Key 和 Value,接收 Decoder 的第一个子层的输出作为 Query,从而实现编解码之间的信息交互;第三个是 Feed-Forward Network。同样,每个子层也使用了残差连接和 Layer Normalization。

Self-Attention(自注意力机制) 是 Transformer 的核心。它的计算过程可以分为以下几步:

第一步,线性变换。输入序列 X 会通过三个不同的权重矩阵 W_Q、W_K、W_V 分别线性变换为 Query(查询)、Key(键)、Value(值)三个矩阵。

第二步,计算注意力分数。将 Query 和 Key 的转置相乘,得到注意力分数矩阵,这个分数代表了序列中每个位置对其他位置的关注程度。

第三步,缩放。为了防止点积结果过大导致 softmax 梯度消失,会将分数除以 Key 维度的平方根(√d_k)。

第四步,掩码(可选)。在 Decoder 中,会对未来的位置进行掩码,设置为负无穷。

第五步,Softmax 归一化。将分数转换为概率分布,即注意力权重。

第六步,加权求和。用注意力权重对 Value 矩阵进行加权求和,得到最终的自注意力输出。

公式表达为:

Attention(Q, K, V) = softmax(QK^T / √d_k)V

多头注意力(Multi-Head Attention) 是指不进行单次注意力计算,而是将 Q、K、V 通过不同的线性投影映射到 h 个不同的子空间(头),在每个子空间中并行地进行自注意力计算,得到 h 个输出结果。然后将这 h 个结果拼接起来,再通过一个线性层进行融合。

为什么要使用多头注意力呢?主要有两个原因:

第一,增强模型的表达能力。不同的头可以关注到输入序列的不同方面。比如有的头可能关注语法结构,有的头可能关注语义关联,有的头可能关注长距离依赖。这相当于模型有了多个“视角”。

第二,类似于卷积神经网络中的多个卷积核。多头机制允许模型在不同的表示子空间中学习信息,比单头注意力具有更强的特征提取能力。

通过多头注意力,Transformer 能够同时捕捉到丰富的上下文信息,这也是它在自然语言处理任务中取得巨大成功的关键原因之一。


Q10:大模型幻觉是什么?如何解决幻觉问题?

【完整背诵答案】

大模型幻觉(Hallucination)是指大语言模型在生成内容时,输出了看似流畅合理、但实际上与事实不符、无中生有或逻辑错误的信息。这是目前大模型落地应用面临的最大挑战之一。

幻觉产生的原因比较复杂,主要包括以下几点:

第一,预训练数据的局限性。训练数据中可能存在错误、偏见或过时的信息,模型在学习过程中将这些错误信息内化。

第二,概率生成的本质。大模型是基于统计概率预测下一个 Token 的,当面对不确定的问题时,它倾向于生成一个“看起来合理”的答案,而不是承认不知道。

第三,知识截止日期。模型无法知晓训练数据之后发生的事件。

第四,对齐问题。在 RLHF 等对齐过程中,如果奖励模型存在偏差,也可能导致模型产生幻觉。

解决幻觉问题通常需要从数据、模型、推理和应用等多个层面入手:

数据层面:

 提高预训练数据的质量和准确性,清洗噪声数据。
 在微调阶段,使用高质量的、经过验证的指令数据进行监督微调(SFT)。

模型层面:

 改进模型架构,比如引入检索增强生成(RAG),让模型在生成时能够参考外部知识库。
 优化对齐策略,比如在 RLHF 中加强对抗幻觉的奖励信号。

推理层面:

 **Prompt 工程**:在 Prompt 中明确指示模型“如果不确定,就说不知道”,或者要求模型给出信息来源。
 **Chain-of-Thought(思维链)**:引导模型一步步推理,减少跳跃式错误。
 **Self-Consistency**:让模型对同一问题生成多个答案,通过投票选择最一致的结果。
 **Logit 抑制**:在解码阶段,对低概率或不确定的 Token 进行惩罚。

应用层面(RAG 是核心手段):

 **检索增强**:这是目前最有效的手段。通过 RAG 架构,让模型基于检索到的真实文档生成答案,从根本上约束模型的输出范围。
 **后处理校验**:对模型生成的答案进行事实核查,比如与知识库比对、检查逻辑一致性。
 **人机回环**:在关键场景中,引入人工审核环节。
 **置信度评估**:让模型输出答案的置信度,当置信度低于阈值时,触发人工介入或拒绝回答。

在我们的实际项目中,RAG 是解决幻觉问题的基石。除此之外,我们还会在 Prompt 中加入严格的约束条件,并对输出结果进行正则表达式匹配和关键词过滤,多重保障来降低幻觉风险。


第五部分:系统设计(背诵思路 + 话术)

Q11:如果要你设计一个高并发的 RAG 系统,你会怎么设计?需要考虑哪些关键点?

【完整背诵答案】

设计一个高并发的 RAG 系统,我会从整体架构、核心模块、性能优化、稳定性保障这四个维度来考虑。

高并发系统设计

整体架构上,我会采用分层解耦的设计,主要分为四层:

接入层:使用 Nginx 或 API Gateway 统一接入流量,负责负载均衡、SSL 卸载、身份认证和限流熔断。这里限流非常重要,防止突发流量打垮下游服务。

检索编排层:这是核心业务逻辑层,通常由 Java 或 Python 微服务组成。它负责接收用户 Query,进行意图识别(如果需要),调用 Embedding 服务,查询向量数据库,调用 Rerank 服务,拼接 Prompt,最后调用大模型服务。这一层要做到无状态,方便水平扩展。

模型服务层:包括 Embedding 模型服务和 LLM 服务。Embedding 模型通常部署在 GPU 或 CPU 服务器上,通过 TensorRT 或 vLLM 等推理框架加速。LLM 服务如果是调用第三方 API,则需要做好连接池管理;如果是自建模型,同样需要高性能推理框架支撑。

数据存储层:包括向量数据库(如 Milvus、Faiss)、关系型数据库(如 MySQL,存储元数据)、缓存(如 Redis,缓存热点 Query、Embedding 向量、高频问答对)、对象存储(如 OSS/S3,存储原始文档)。

核心模块设计上,有几个关键点:

Embedding 模块:为了提高性能,我们会将高频 Query 的 Embedding 向量缓存在 Redis 中。Embedding 模型服务独立部署,避免受 LLM 服务波动影响。

检索模块:向量数据库采用集群部署,支持读写分离。检索策略上,采用“向量检索 + 关键词检索”的混合检索方式,提高召回率。召回数量先大后小,比如先召回 Top-200,经过 Rerank 后再取 Top-5。

生成模块:调用 LLM 时采用流式返回(SSE),降低用户感知延迟。Prompt 模板化管理,支持动态插拔。

缓存设计:除了 Embedding 缓存,还可以缓存最终的问答对(Question-Answer Pair),对于完全相同的 Query,可以直接返回答案,大幅降低 Token 消耗和响应时间。

性能优化方面:

异步化:在检索编排层,向量检索、Rerank、LLM 调用等 IO 密集型操作全部采用异步非阻塞方式,提高吞吐量。

连接池:对向量数据库、Redis、LLM API 等建立连接池,复用连接,减少连接建立开销。

批处理:如果业务允许,可以将多个用户的 Embedding 请求合并成一个 Batch 发送给模型服务,提高 GPU 利用率。

稳定性保障方面:

降级熔断:当向量数据库或 LLM 服务不可用时,需要有降级策略。比如关闭 Rerank,或者直接返回“系统繁忙”;对于非核心场景,可以使用小参数模型作为兜底。

监控告警:全方位监控 QPS、P99/P95 延迟、Token 消耗量、错误率、向量数据库查询性能等指标,并设置合理的告警阈值。

幂等设计:确保用户重复提交同一请求时,系统不会重复扣费或产生副作用。

最后,在整个系统的迭代过程中,我们会建立一个Bad Case 分析闭环,定期收集线上错误的案例,分析原因(是检索问题、Rerank 问题还是生成问题),针对性地优化模型或策略。

**【埋点】

“在我们的实际落地中,缓存命中率做到了 35% 左右,P95 延迟控制在了 2 秒以内。另外,我们还做了一个小技巧:对于用户连续输入的追问,我们会把历史对话的 Embedding 进行加权平均,作为新的检索向量,这在一定程度上缓解了多轮对话的语义漂移问题……”**


使用说明

先背“黑体字”部分:那是主干,面试时先把这些流畅说出来。

“埋点”部分先别背:那是给你留的“后门”。等你研究项目时,把里面的例子换成你自己的真实经历。到时候面试,你说完主干,自然接一句“我们在项目中当时是这么处理的……”,面试官会直接认定你是老手。

每天背 2~3 个:不求多,求脱口而出。可以对着镜子练,或者录下来听。

遇到追问不慌:比如背了 HNSW,面试官问 efConstruction 和 efSearch 的区别,你暂时答不上来没关系,先承认“这块参数调优的细节我还需要再深入”,然后回去补。社招允许有盲区,但不能全是盲区。

这套内容背完,阿里面试的前两轮技术面,关于“知识广度”和“基础概念”的部分你就稳了。

等你背完这一轮,告诉我你项目里具体用了啥(比如:Milvus 还是 ES?有没有用 Agent?用的什么模型?),我给你补**「追问级」的深度内容**,让你能把背的东西和你项目无缝缝合。