agent面经1
模块一:Agent 核心架构与设计 (Architecture & Planning)
1. Agent 的“规划-执行-反思 (Plan-Act-Reflect)”闭环,具体在工程中要怎么落地实现?
【通俗讲解】
Agent 和普通 ChatGPT 对话最大的区别,就是它能自己“动动手、动动脑”。
- 规划 (Plan):接到任务后,思考第一步干啥,第二步干啥。
- 执行 (Act):调用外部工具(查天气、读数据库等)去干活。
- 反思 (Reflect):干完之后看看结果对不对。不对的话,换个思路重新干。
【工程落地实现方案】
在实际代码中,千万不能只靠一个死循环让大模型自己玩,那样很容易导致 API 费用爆炸或死循环。我们需要用到**状态机(State Machine)或有向无环图(DAG)**的思想(比如使用 LangGraph 框架):
- 状态管理 (State):定义一个全局的上下文对象,里面包含“用户问题、当前步骤、历史工具调用结果、错误信息”。
- Plan 节点:将状态喂给大模型,Prompt 要求它输出下一步的行动指令(比如输出 JSON 格式的工具名和参数)。
- Act 节点:代码层拦截大模型的输出,判断如果是工具调用,就在本地执行 Python 函数/API,并将结果存回“状态”中。
- Reflect 节点:把工具的返回结果(比如报错了,或者查到了数据)再次发给大模型。你可以设计一个专门的
Evaluator Prompt(评估提示词):“请检查当前结果是否解决了用户问题,如果解决了请输出最终答案,如果报错了请分析原因并给出新的工具调用”。 - 跳出机制(极度重要):必须在工程里写死一个
max_iterations(最大迭代次数,比如 5 次),超过次数强行中止,返回兜底话术,防止无限循环。
2. 生产级 AI Agent 系统架构怎么设计?画出核心组件及数据流转。
【通俗讲解】
你在本地跑一个 Python 脚本叫 Demo,但在公司里供成千上万人并发使用的叫生产级系统。生产级系统必须考虑并发、记忆存储、安全和监控。
【核心组件设计】
你可以向面试官描述这样一个典型的企业级架构图:
- 接入网关层 (API Gateway):负责鉴权、限流(令牌桶/滑动窗口算法)、协议转换(将后端的流式数据转成前端需要的 SSE 流)。
- 编排引擎 (Orchestrator/Agent Core):系统的大脑,负责管理工作流流转(基于 LangGraph 或自研状态机)。
- 大模型路由与调用层 (LLM Router & Client):管理对 OpenAI/Qwen/DeepSeek 等底层模型的 API 调用,包含 Prompt 模板库。
- 记忆/上下文模块 (Memory & Context):
- 短期记忆:存放在 Redis 中,管理当前多轮对话的 Session 上下文。
- 长期记忆:存放在向量数据库(如 Milvus/Chroma)中,用于 RAG 检索用户的历史偏好或企业知识库。
- 工具执行引擎 (Tools / MCP Server):封装各类内部 API、数据库查询、代码执行器(Sandbox)。
- 监控与观测系统 (Observability):链路追踪(Trace),记录大模型的每一次输入输出耗时,用于排查“大模型到底是在哪一步胡说八道的”。
【数据流转逻辑】
用户发送 Query → 网关鉴权拦截 → 编排引擎去 Redis 捞出历史对话上下文 → 组装 Prompt 发给大模型 → 大模型决定调用工具 A → 引擎暂停 LLM 响应,去请求工具 A → 拿到结果更新上下文 → 再次请求大模型生成最终结果 → 通过网关 SSE 流式推给前端。
3. 单智能体 vs 多智能体 (Single vs. Multi-Agent)
① 你的系统是单 Agent 还是多 Agent?为什么这么设计?
- 回答建议:“在实习初期/项目早期,我采用的是单 Agent,因为它实现简单,且能覆盖 80% 的通用闲聊和单次查询任务。但随着业务变复杂(比如又要做数据分析,又要做文案润色),单 Agent 的 Prompt 变得极其臃肿,导致模型容易遗忘和产生幻觉。因此我重构成了多 Agent 架构(比如主管-员工模式 Supervisor-Worker),将不同领域的工具和职责解耦给专业的子 Agent,不仅降低了单次调用的延迟,还提高了意图识别的准确率。”
② 由 LLM 做 Router 分发任务 vs 由固定规则分发,各有什么优劣?
- LLM Router(大模型路由):
- 优势:泛化能力极强,能理解用户模糊、口语化的意图,直接免去了人工写正则的烦恼。
- 劣势:极高延迟(每次都要等大模型推理),存在幻觉概率(分发错人),成本高。
- 规则分发(如正则、关键字、传统分类小模型):
- 优势:速度极快(毫秒级),结果稳定、可解释、资源消耗低。
- 劣势:死板,用户换个说法就匹配不上了,维护成本高(需要不断加规则)。
- 最佳工程实践:混合路由。先过一遍轻量级的规则引擎/意图识别小模型,如果没命中,再兜底交给 LLM Router 去分析。
4. 状态与工作流 (Workflow)
① 工作流 vs Agent 怎么选?
- 工作流 (Workflow):执行路径是确定的。比如“帮我点一杯星巴克”,步骤永远是:查菜单 -> 选配料 -> 扣款 -> 下单。只要是 SOP(标准作业程序)明确的场景,坚决用 Workflow,稳定且快速。
- Agent:执行路径是未知的。比如“帮我调研一下竞品并写个报告”。不知道要搜几次网页,不知道要总结什么。需要大模型根据上一步结果动态决定下一步时,才用 Agent。
② 复杂任务怎么拆解?拆分粒度是怎么决定的?
- 拆分粒度应该对齐工具的原子能力。粒度太粗,大模型一头雾水(比如工具只有“查天气”,任务是“安排旅游计划”,一步做不到);粒度太细,会导致频繁串行调用 LLM,系统延迟爆炸(Wait time 激增)。
③ Think-Execute 循环机制的 prompt 是怎么设计的?
你需要强制大模型先输出“思考过程”,再输出“执行动作”。(这其实就是 ReAct 论文的核心精髓)。
- Prompt 示例:讲解:先让大模型生成
1
2
3
4你必须严格按照以下格式输出:
Thought: 思考我接下来应该做什么以解决用户问题。
Action: 需要调用的工具名称(必须从 [tool1, tool2] 中选择)。
Action_Input: 工具的输入参数,JSON格式。Thought,相当于给了它一个“打草稿”的空间(Token 空间),这能极大地提升模型后续选择Action的逻辑准确度。
5. 异常与兜底:Agent 长任务中如何做中断恢复和状态持久化?
【通俗讲解】
假设 Agent 在帮用户跑一个极其复杂的数据分析,跑了 3 分钟,突然服务器发版重启了,或者遇到一个需要用户确认“是否继续扣费”的步骤。这时候怎么办?
- 状态持久化 (Checkpointing):不能把对话状态只存在内存变量里。每次 Agent 走到一个节点(比如规划完、调用完工具),都要把当前的
DAG Graph State序列化,并打上thread_id存入数据库(如 PostgreSQL/Redis)。这样就算系统崩溃,根据thread_id捞出状态就能无缝接着跑。 - 中断恢复 (Human-in-the-loop, HITL):在工作流中设置“断点”。跑到支付节点时,Agent 挂起当前线程,持久化状态。用户在前端点击“确认支付”后,触发 API,带上原状态恢复执行上下文。
- 兜底 (Fallback) 策略:
- 重试兜底:工具调用超时,自动重试 3 次。
- 模型降维兜底:如果复杂大模型(如 GPT-4)频繁超时,降级切到稍笨但更快的模型(如 GPT-3.5/Qwen-Turbo)给出一个抱歉的话术。
- 人工转交兜底:当 Agent 发现自己兜圈子 3 次都没解决问题,触发异常状态,将历史上下文直接打包转交给人工客服人工坐席。
6. 行业前沿探索:如何不用多智能体方案,让 1000 个 tools 正常工作?
【通俗讲解】
如果你的系统有 1000 个工具,你把这 1000 个工具的描述都写进 Prompt 里发给大模型,首先会超出上下文窗口限制(Token 爆炸,钱烧没),其次大模型的注意力机制会“迷失”,根本选不准工具。多 Agent 方案是把 1000 个工具分给 10 个子 Agent,但这里面试官限制了“不用多 Agent”。
【标准答案:工具检索 / RAG for Tools】
借鉴 RAG(检索增强生成)的思路:
- 向量化工具库:将这 1000 个工具的名字、描述(Description)、参数要求全部做成 Embedding 向量,存入向量数据库中。
- 动态召回:当用户输入 Query 时(比如“帮我查一下昨天上海的降雨量”),先去向量数据库里做一次相似度检索(Retrieval)。
- Prompt 组装:通过检索,可能只召回了 Top 5 最相关的工具(如“天气查询工具”、“水文查询工具”等)。我们将这 5 个工具作为
tools参数传递给大模型。 - 这样,大模型每次面临的选择题就从 1000 选 1,变成了 5 选 1。既避免了 Token 爆炸,又极大提高了选择准确率。
模块二:上下文与记忆机制 (Context & Memory)
1. 记忆分级:短期记忆与长期记忆的区别及压缩策略
【通俗讲解】
- 短期记忆 (Short-term Memory, STM):就像电脑的内存(RAM)。记住你这次对话上下文正在聊什么。比如你刚说“我明天要去北京”,下一句问“那边的天气怎么样”,Agent 得知道“那边”是指北京。
- 长期记忆 (Long-term Memory, LTM):就像电脑的硬盘。记住你几个月前聊过的信息、你的个人偏好(比如“不吃香菜”)。哪怕你关了网页,明天重新登录,它还能认出你。
【工程设计与本质区别】
- 本质区别:
- 短期记忆是**“时间线性”**的,严格按照对话的先后顺序存储(List结构),生命周期仅限于当前 Session。
- 长期记忆是**“语义空间”**的,不需要按顺序,而是把关键信息提取成知识点(Knowledge/Fact),存放在向量数据库(Vector DB)或图数据库(Graph DB)中,跨 Session 共享。
【对应的缓存与压缩策略】
- 短期记忆策略:通常存在 Redis 中。
- 滑动窗口 (Sliding Window):最简单的策略,只保留最近的 N 轮对话(比如只保留近 5 轮)。
- Token 截断:按 Token 数量限制,超过比如 4000 token,就把最早的一轮对话删掉。
- 长期记忆策略:通常存在 Milvus / Qdrant 等向量数据库中。
- 提取与压缩:不能直接把大段聊天记录扔进数据库。必须在后台跑一个“记忆提取 Agent”,把闲聊转化为结构化标签(如:
{"user_preference": "不吃香菜", "location": "北京"}),然后再做向量化存储。
- 提取与压缩:不能直接把大段聊天记录扔进数据库。必须在后台跑一个“记忆提取 Agent”,把闲聊转化为结构化标签(如:
2. 上下文污染与截断策略
【通俗讲解】
- 上下文污染:Agent 执行任务时,常常需要调用工具。比如查了一次机票,工具返回了长达两万字的极其复杂的 JSON(包含了经纬度、航班号、甚至毫无意义的广告代码)。把这堆东西全塞进 Prompt,大模型就“蒙圈”了,直接忘记了用户最初问的问题是什么,这就是上下文污染。
【具体解决方案】
① 多 Agent / 多异步任务下,如何防止上下文污染?
- 状态隔离:每个子 Agent 只能看到与自己任务相关的上下文。比如“查天气 Agent”只接收地点和时间,不要把用户前面聊的购物需求也传给它。
- 工具输出清洗:工具返回结果后,绝对不能直接塞给大模型。必须加一层代码解析(或用一个小模型),把两万字的 JSON 提取成只包含核心字段的极简文本(如:“查到3个航班,分别是XXX”),再放入上下文。
② 如果上下文窗口爆炸(不够用),你优先保留哪些信息?为什么?
优先级从高到低必须是这样设计的:
- 最高优先级:System Prompt (系统指令)。Agent 的人设和核心规则,丢了就彻底崩盘。
- 次高优先级:最新的 User Query (用户当前提问)。必须知道现在要干嘛。
- 中等优先级:关键的长期记忆 / RAG 检索结果。解决当前问题所需的外部知识。
- 低优先级:最近的 2-3 轮历史对话。
- 最低优先级(优先丢弃):Agent 历史的 Thought(思考过程)和冗长的工具返回结果。
③ 除了直接截断,了解哪些更高效的上下文压缩方法?
- LLM 总结压缩 (Summarization):当对话达到一定长度时,触发一个后台任务,让模型把前 10 轮对话总结成一段百字以内的话:“用户之前询问了北京旅游攻略并预定了酒店”,用这段总结替换掉前面冗长的对话记录。
- Prompt 压缩模型 (如 LLMLingua):利用专门的轻量级模型,把一段话里的介词、不重要的修饰词剔除,保留核心语义(信息熵最高的词),可以无损压缩 50% 左右的 Token。
3. 数据存储设计与向量库去重
【通俗讲解】
如果一个应用跑了一年,每个用户的记忆和聊天记录多达几十万条,每次都要去里面搜索,速度肯定慢,而且会有很多废话和重复内容。
【具体优化方案】
① 历史记录量非常大,怎么优化查询效率?
- 元数据过滤 (Metadata Filtering):这是面试必答点!不要只做全局向量检索,一定要结合传统数据库的思维。在存入向量数据库时,打上
user_id、session_id、time_range(时间范围)等标量标签。检索时,先用标量过滤(例如只查这个 user_id 且是最近一个月的数据),再去算向量相似度,速度能提升百倍。 - 混合检索 (Hybrid Search):向量检索容易产生“语义相似但毫不相干”的误召回,应采用
向量检索 (如 Embedding) + 关键词检索 (如 BM25)融合的双路召回策略。
② 向量记忆库是如何做去重的?如果用户反复说同一件事,你会重复存储还是进行语义合并?
- 坚决不能重复存储,否则知识库会极度冗余,且导致大模型在检索时提取到多条矛盾或重复的信息。
- 正确做法:语义合并 (Semantic Update / Upsert)。
- 流程:当提取出一条新记忆(如:“用户说他喜欢红苹果”)准备存入前,先拿这个信息去向量库里搜一下。
- 合并:如果搜到库里已经有一条高度相似的记忆(如:“用户偏好:爱吃绿苹果”,相似度 > 0.85),我们就在后台调用一次 LLM,让 LLM 做判断——这两个信息是同类项吗?如果是,LLM 输出合并后的更新版本(“用户偏好:爱吃各类苹果,如红苹果和绿苹果”),覆盖(Update)旧的数据,而不是新增(Insert)。
4. 并发与一致性保障
【通俗讲解】
在单机跑 Demo 时,你怎么点都没事。但在大厂,可能同一秒钟有 100 个用户在跟你开发的 Agent 说话;甚至有个急躁的用户,手机卡了,疯狂连按了 3 次“发送”。如果不做并发控制,Agent 的脑子就“精分”了,上下文全部错乱。
【技术落地细节】
① 多轮对话上下文状态管理是如何做的?
- 不建议只把状态放在单机内存中(分布式部署下,请求可能落到不同实例)。
- 常见做法是使用 Redis 等分布式存储。Key 设为
tenant_id:user_id:session_id,Value 为当前对话上下文 JSON,并设置合理过期时间(TTL,如 24 小时)。
② 如何在多租户、高并发场景下隔离并发并保证一致性?
- 多租户隔离:在所有数据(无论 Redis 还是向量数据库)的 Key 或 Metadata 中,建议带上
tenant_id(企业ID)和user_id,避免数据越权串调(A 公司的 Agent 回答了 B 公司的机密)。 - 并发一致性(分布式锁):针对“用户连按 3 次发送”问题,常见做法是引入**分布式锁(如 Redis Redisson)**或队列串行化。
- 当收到
session_id_1的请求时,给这个 session 加锁。 - 在此请求还在等大模型返回的期间,如果又来了一个同一个 session 的请求,发现锁被占用,网关应该直接拒绝该请求(提示:“正在思考中,请勿重复发送”),或者将其放入消息队列等待。这能有效防止上下文读写发生脏数据(Dirty Write)。
- 当收到
模块三:工具调用与外部交互 (Tool Calling / Function Calling / MCP)
1. 工具描述(Description)怎么写,模型才能准确识别并调用?
【通俗讲解】
大模型是个“瞎子”,它完全依赖你写的文字说明来决定用哪个工具。如果你写得模棱两可(比如两个工具都写着“可以查询商品信息”),大模型就会瞎猜,甚至编造不存在的参数。
【大厂高分工程实践】
写 Tool Description 不是写简单的注释,而是在写“给机器看的 Prompt”。优秀的描述必须包含以下三要素:
- 明确边界(做且仅做什么):不仅要写它能干什么,还要写**“什么时候绝对不要用它”**。
- 反面教材:“用于查询天气。”
- 正面案例:“用于查询未来 3 天内的国内城市天气。注意:如果用户查询历史天气,或者国外城市,请勿使用此工具。”
- 强约束的参数格式(Schema):利用 JSON Schema 或 Pydantic 强制约束参数类型。
- 如果参数是日期,不要只写
date,要写明格式Format: YYYY-MM-DD。 - 如果参数有限定范围,必须使用枚举项(
Enum: ["北京", "上海", "广州"])。
- 如果参数是日期,不要只写
- 注入 Few-shot(少样本)示例:在 description 的末尾,直接给出一个正确的调用例子。
- 示例:“例如:当用户问’明天上海热吗’时,参数应为
{"city": "上海", "date": "2026-03-23"}”。
- 示例:“例如:当用户问’明天上海热吗’时,参数应为
2. 可靠性保障与“死循环”破局
【通俗讲解】
实战中,Function Calling 极度不可靠。大模型经常输出不合法的 JSON(少个引号)、瞎编参数,或者外部工具本身挂了(比如查不到数据返回 500)。如果不做干预,大模型就会卡在错误里,不断重复调用同一个错误的工具,陷入“死循环”(Death Loop),导致你的 API 费用一秒钟烧掉几十块钱。
【工程解决方案】
① Function Calling 的可靠性如何保障?
- 代码层:必须引入强类型校验框架(如 Python 的 Pydantic)。大模型输出 JSON 后,先过 Pydantic 校验,校验失败直接拦截,绝不能发给真实的后端 API。
- 模型层:强制要求模型输出严格的 JSON 模式(如 OpenAI 的
json_mode或Structured Outputs结构化输出能力),从底层概率上杜绝格式错误。
② 模型反复调用同一个错误工具,如何设计 Prompt 引导重试?
- 错误信息回传(Error Feedback):当工具报错时,不要把系统级的崩溃代码(如 Java 的一长串 Exception)直接丢给模型,模型会懵。
- 重试 Prompt 设计:你需要拦截错误,并将一段“纠错 Prompt”连同精简后的错误信息发给模型:
“系统提示:你刚才调用的工具
get_weather失败了。错误原因是:‘城市名称不合法,不支持拼音输入’。请反思错误原因,不要使用完全相同的参数进行重试。如果重试 3 次仍然失败,请直接向用户致歉并说明原因。” - 工程熔断机制(极度重要):千万不要完全信任大模型的“反思”。代码里必须写死一个
max_retries = 3的计数器。只要同一个工具连续报错 3 次,代码层面强制break,退出循环,返回兜底话术,彻底斩断死循环。
3. 性能优化:串行工具导致响应延迟过高,怎么优化?
【通俗讲解】
假设 Agent 要回答“对比一下阿里和腾讯昨天的股价”。
传统的串行逻辑是:LLM 思考 -> 调阿里股价工具 -> LLM 拿到结果再思考 -> 调腾讯股价工具 -> LLM 汇总输出。这里面大模型推理了 3 次,网络往返了 N 次,用户等得花儿都谢了。
【优化角度】
- 并发调用 (Parallel Function Calling):
- 这是目前最主流的做法。优秀的基座模型(如 GPT-4o, DeepSeek-V3)已经支持一次性输出多个工具调用指令。
- 工程实现:大模型一次性返回
[call:get_stock(阿里), call:get_stock(腾讯)]。代码端使用 Python 的asyncio.gather或者 Go 的 Goroutine 并发请求这两个外部 API,等两个结果都回来了,再统一喂给大模型。时间从 缩短到了 。
- 工具聚合 (Macro-Tools / 粗粒度工具):
- 别让模型像搭积木一样调用零碎的接口。如果业务上经常需要“先查用户 ID,再查订单”,就把这两个接口在后端封装成一个
get_user_orders_by_name的聚合大工具。减少 LLM 的推理轮次。
- 别让模型像搭积木一样调用零碎的接口。如果业务上经常需要“先查用户 ID,再查订单”,就把这两个接口在后端封装成一个
- 语义缓存 (Semantic Cache):
- 接入 Redis 缓存。如果有用户问了同样或相似的问题,直接命中缓存,不走 LLM 推理,也不调外部工具。
- 流式输出状态 (Streaming Thoughts):
- 虽然总时间长,但在等待工具响应时,前端不要转圈圈。通过 SSE 协议给用户推送“正在查询阿里股价…”、“查询成功,正在对比…”等执行状态。这属于体验层面的性能优化。
4. 最前沿必考:MCP 协议 (Model Context Protocol)
【通俗讲解】
这是近两年 Agent 工程里很重要的标准化方向。
以前,你想让 Agent 能查数据库、能读 GitHub、能看本地文件,你得在自己的 Agent 代码里给每一个工具写对接逻辑、配鉴权、写 JSON Schema,代码耦合度极高。
Anthropic 推动了 MCP (Model Context Protocol)。它可以类比 AI 时代的“统一接口标准”:不同模型与外部系统按同一协议对接,集成和迁移成本会更低。
【面试核心答法】
① MCP 的工作原理和交互流程是怎样的?
- MCP 采用经典的 Client-Server (C/S) 架构。
- MCP Server(服务端):封装了具体的工具或数据源(比如一个对接了本地 MySQL 的 MCP Server)。它向上暴露三种能力:Resources(静态数据)、Prompts(提示词模板)、Tools(可执行工具)。
- MCP Client(客户端/Agent层):我们的 Agent 内部运行一个 Client。
- 交互流程:Agent 启动 -> Client 连接 Server -> Client 问 Server“你有什么工具?” -> Server 甩过来标准化的 JSON Schema -> Agent 发给大模型 -> 大模型决定调用 -> Client 把请求发给 Server 执行 -> Server 返回结果。
② Agent 如何与 MCP Server 连接通信?
有两种标准的传输方式:
- stdio (标准输入输出):用于本地通信。Agent 启动一个本地子进程,通过控制台的输入输出流进行 JSON-RPC 格式的通信(极度安全,无网络延迟)。
- HTTP(含流式传输):用于远程通信。常见是基于 HTTP 的请求-响应与流式返回;需要注意 SSE 本身是服务端到客户端的单向推送通道。
③ MCP 协议与传统的 Function Calling 有什么区别?
- 解耦与标准化:传统方式是紧耦合,工具代码和大模型业务代码全写在一个工程里。MCP 是完全解耦的,数据源和工具成为了独立的服务(Server),支持跨语言、跨框架的即插即用(Plug & Play)。
- 能力维度:Function Calling 只有“执行动作”的能力;而 MCP 包含了读数据 (Resources)、执行动作 (Tools) 和上下文构建 (Prompts) 三位一体的能力。
④ 使用 MCP 接入多个测评工具时,回答格式不统一怎么处理?
- 痛点:工具 A 返回
{"score": 90},工具 B 返回{"result": "A", "confidence": 0.9},全丢给大模型容易把模型搞晕。 - 解决方案(Adapter/适配器模式):
在 Agent 这一侧(MCP Client 获取到 Server 结果之后,提交给大模型之前),设计一层 中间件/适配器 (Middleware/Adapter)。
利用 Pydantic 定义一个你业务上需要的最标准的UnifiedResponseSchema。如果 MCP Server 传回来的格式千奇百怪,我们要么写一段简单的 Python 逻辑做字段映射,要么(如果是复杂非结构化数据)先过一个小参数模型,将其格式化对齐后,再喂给主 Agent 进行最终的多步推理。
模块四:RAG 技术与应用 (Retrieval-Augmented Generation)
1. 流程与基础选型
【通俗讲解】
大模型是个“万事通”,但它不知道你们公司的内部机密(比如昨天的销售报表)。RAG 的本质就是给大模型配一个“开卷考试”的参考书。
① RAG 的工作流程 vs 传统检索
- 传统检索(如 Elasticsearch):更偏向关键字匹配(TF-IDF / BM25),字面不一致时容易漏召回(例如搜“苹果”未必稳定命中“iPhone”)。然后通常返回匹配文档片段给用户。
- RAG 流程:
- 数据准备:解析文档 -> 切分 (Chunking) -> 变成向量 (Embedding) -> 存入向量库。
- 检索生成:用户提问 -> 转成向量 -> 去库里搜出最相似的文本 -> 把提问和文本一起拼成 Prompt -> 喂给 LLM 生成人话。
- 核心区别:RAG 是语义检索(搜“苹果”能召回“iPhone”),并且最终由 LLM 综合总结,而不是生硬地返回原始文档碎片。
② 向量数据库选型对比(Milvus / Pinecone / Chroma)
- Chroma:
- 特点:轻量级,基于本地文件系统(SQLite/Parquet)。
- 场景:适合做本地 Demo、开源项目的 PoC(概念验证),完全不适合生产环境,因为不支持分布式和高并发。
- Pinecone:
- 特点:SaaS 托管服务,易用、免运维。
- 场景:适合快速上线;是否采用通常取决于数据合规、网络地域、成本预算与供应商策略。
- Milvus:
- 特点:开源,云原生架构,支持存储计算分离,支持极大规模(十亿级向量)的分布式高并发。
- 场景:适合追求私有化部署、可控运维和大规模检索的生产系统;与 Weaviate、Qdrant 等方案可按团队能力与生态做选型。
2. 文档处理与切分 (Chunking)
【通俗讲解】
一本书太长,大模型一口吃不下,必须切成“块”(Chunk)。切得太大,找不准;切得太小,上下句连不上(语义丢失)。
① Chunk 大小怎么定?不同场景策略差异
没有固定标准,主要取决于你的 Embedding 模型的限制(如 bge-m3 最多 8192 token)和 LLM 的上下文窗口。
- 纯文本/新闻:采用固定长度+滑动窗口(Overlap)。比如每块 500 字,保留 50 字的重叠,防止一句话被硬生生切断。
- 法律/规章制度:采用按规则切分(Rule-based)。绝对不能按字数切!必须按“第几条、第几款”切分,保证法律条款的完整性。
- 代码仓库:采用AST(抽象语法树)切分。按“函数 (Function)”或“类 (Class)”为单位切,否则代码逻辑会断裂。
② 复杂文档(带图片、图表)的解析流程?
这是目前最难的工程问题(异构数据解析)。
- 痛点:PDF 里的表格直接用传统方法抽成纯文本,行列就错乱了;图片直接丢弃,关键信息就没了。
- 大厂解法:使用 VLM(多模态大模型,如 Qwen-VL 或 GPT-4o) 或者专门的版面分析工具(如 Marker / 阿里 DocMind)。
- 表格:识别出表格边界,调用模型将其转换成 Markdown 表格格式或 HTML,这两种格式大模型理解得最好。
- 图片:用 VLM 对图片进行 Image Captioning(图片描述生成),比如生成“图为 2025 年 Q1 销量折线图,呈上升趋势,峰值在 3 月”。把这段描述变成文本向量存入库中,同时保留原图片的 URL/URI。检索到这段文本时,把原图一起交给最终的 Agent 生成答案。
3. 检索与召回优化(重点考察)
① 为什么引入父子索引(Parent-Document Retrieval)?
- 痛点:Chunk 越小,向量检索越精准;但 Chunk 越小,大模型拿到的上下文越少,回答越干瘪。
- 解法:在存数据时,把文档切成大块(父,如 2000字),再把大块切成小块(子,如 200字)。向量库里只存“子块”的向量。当用户提问命中某个“子块”时,系统顺藤摸瓜找到它的“父块”,把整个“父块”喂给大模型。完美兼顾了“检索精准度”和“上下文丰富度”。
② 为什么引入 BM25?向量与 BM25 融合比例?
- 痛点:向量检索擅长“语义”(搜“猫”能匹配“喵星人”),但极度不擅长精准字面匹配(比如搜特定商品型号
iPhone 16 Pro Max 256G,向量可能会给你推一堆其他型号的手机,因为它们语义相似)。 - 解法:引入 BM25(传统的词频-逆文档频率算法)。
- 融合流程 (Hybrid Search):同时并行发起向量检索(召回 100 条)和 BM25 检索(召回 100 条),然后使用 RRF(倒数秩融合算法 Reciprocal Rank Fusion) 或者设定比例(如 向量 0.7 : BM25 0.3)将两路结果合并打分。
③ Rerank (重排) 及 Top-K 截断怎么做?
- 流程:双路召回出来的比如 50 个结果,准确度参差不齐。我们引入一个专门的 Reranker 模型(Cross-Encoder 架构,如
bge-reranker),把用户的 Query 和这 50 个结果一一配对,重新打分排序。 - Top-K 截断:不是死板地取前 5 个。大厂通常做动态截断:设定一个阈值(比如相似度分数 > 0.6),只要大于 0.6 的全留,如果最高分都没过 0.6,直接判定“知识库中没有相关信息”,防止大模型胡说八道。
4. 高级检索场景
① 多模态 Embedding 相似度权重平衡
- 解法:当用户搜“一张外观像苹果的零件图纸”时,既包含语义(零件),又包含视觉特征(像苹果)。可以计算文本 Embedding 的相似度 和图像 Embedding(如 CLIP 模型)的相似度 。
- 权重平衡:动态权重。用一个小模型判断用户的 Query 意图,如果是询问参数,文本权重调高(0.8);如果是询问外观,图像权重调高(0.8)。
② 知识库更新频繁,增量向量更新怎么设计?
- 大忌:千万不能每次文档一改,就把整个向量库清空重算,GPU 算力费直接破产。
- 解法:给每一篇文档生成一个 哈希值(Hash/MD5) 作为文档指纹,存在 MySQL 中。定时任务扫描时,比对指纹,只有指纹变了的文档,才重新切分、Embedding。对向量库执行
Upsert(存在则更新,不存在则插入)或者标记旧向量为 Soft Delete(软删除)。
③ 金融等特定行业术语歧义,如何消歧?
- 问题:金融里的“杠杆”和物理上的“杠杆”完全不同,向量模型容易混淆。
- 解法:引入 知识图谱 (Knowledge Graph) 或行业词典。在检索前做 Query 改写(Query Rewriting)。用 NER(命名实体识别)查到“杠杆”,从图谱中调出它的金融定义,将 Query 改写为:“什么是杠杆(指通过借钱放大投资收益的金融工具)?” 然后再去向量库检索,准确率大幅提升。
5. RAG 评估(Ragas 等评测框架)
【通俗讲解】
老板问你:“你这套 RAG 系统到底准不准?” 你不能回答“感觉挺准的”,必须甩出量化指标。面试中提到 Ragas Framework 会极大加分。
- 召回率 (Context Recall):
- 定义:标准答案所需要的所有信息,有没有被向量检索全部捞出来?(考察检索阶段的能力)。
- 计算:分子是“检索到的有用信息量”,分母是“回答该问题实际所需的总信息量”。
- 上下文精确度 (Context Precision):
- 定义:检索出来的东西有没有排在前面?(考察 Rerank 排序的能力)。如果正确的片段排在第 10 名,精确度就很低。
- 忠实度 (Faithfulness):
- 定义:大模型生成的回答,是不是 100% 来源于检索到的上下文?(考察防止幻觉的能力)。
- 怎么算:用另一个大模型(LLM-as-a-Judge)把生成的答案拆成一个个短句,去上下文中对比。如果有一句话在上下文中找不到出处,说明模型“自己编造”了,忠实度扣分。
模块五:大模型基础与微调 (LLM & Fine-tuning)
1. Transformer 基础(大模型的心脏)
【通俗讲解】
Transformer 的核心就是 Attention(注意力机制)。就像你在看一句话“这只苹果真好吃”,你的大脑会自动把注意力放在“好吃”上,知道这里的“苹果”是水果,而不是手机。
① 为什么拆成 Q、K、V?
- 本质:Attention 的本质是一个**“软寻址”的字典查询过程**。
- Q (Query/查询):当前字“想寻找什么”(比如“苹果”想找个形容词)。
- K (Key/键):其他字“能提供什么特征”(比如“好吃”带有一种食物属性的 Key)。
- V (Value/值):其他字“实际包含的信息内容”。
- 面试金句:“如果只用一个矩阵,模型很难表达非对称关系(比如‘我’高度关注‘吃’,但‘吃’不一定关注‘我’)。拆成 QKV 可以让查询和被查询的过程解耦,表达更丰富的语义映射。”
② 为什么用多头注意力 (Multi-head)?
- 就像盲人摸象,一个头只能关注一个维度的信息。比如头 A 专门盯“语法结构”(主谓宾),头 B 专门盯“情感色彩”(褒义贬义)。多头机制让大模型能同时在多个不同的子空间提取特征,防止顾此失彼。
③ 为什么 Attention 可以建模长距离关系?太长怎么优化?
- 传统 RNN 是一步步往后传,句子一长,前面的记忆更容易衰减。Attention 通过全局两两交互,任意两位置都能直接建立依赖(路径长度可视作常数级),但计算与显存复杂度通常是 。
- 太长怎么优化?
- 工程手段:引入 FlashAttention(通过针对 GPU 的底层 I/O 显存读写优化,极大降低长文本的显存占用和耗时)。
- 算法手段:使用 **RoPE(旋转位置编码)**的动态外推技术(如 YaRN),把 8K 窗口强行拉长到 128K。
④ 什么是位置编码?绝对 vs 相对的区别?
- Attention 本身是个“词袋子”,你把词打乱,它算出来的结果一模一样(不包含词序信息)。所以必须加上位置编码(告诉它谁是第一个词,谁是第二个)。
- 绝对位置编码:简单粗暴,直接给第 个位置加上固定的数值(类似给每个人发个固定的座位号)。
- 相对位置编码:不关心你坐在哪,只关心“你坐在我左边第 3 个位置”(关注词与词之间的相对距离)。目前多数主流开源大模型(如 LLaMA、Qwen、DeepSeek)采用 RoPE(旋转位置编码),它能较好表达相对位置信息并支持长上下文扩展。
2. 模型幻觉 (Hallucination) 与复读机
【通俗讲解】
大模型不是数据库,它是一个**“概率接龙”引擎**。它不知道什么是真理,它只知道接“这个词”的概率比较大。
① 产生幻觉 / 复读机的原因是什么?
- 复读机:通常是因为在解码(Decoding)时,温度参数 (Temperature) 太低 或者 重复惩罚 (Repetition Penalty) 没设置好,导致模型死死咬住某几个高频词不放,陷入局部最优的贪婪搜索中。
- 幻觉:
- 预训练数据有毒:网上本来就有很多假新闻,模型学杂了。
- 知识盲区:用户问了一个偏门问题,模型不敢说“我不知道”,为了迎合用户,只能强行按概率“编造”事实。
② 工业界降低/规避幻觉的方案?
这是 Agent 工程师最拿手的:
- 架构层(最有效):接入 RAG(检索增强生成),把大模型的开卷考试资料直接塞进 Prompt,限制它只能“基于给定上下文回答”。
- Prompt 工程层:强化人设约束(System Prompt 注入:“你是一个严谨的助手,如果你在上下文中找不到答案,请直接回复‘不知道’,严禁胡编乱造。”)。
- 后处理层 (Post-processing):也就是评测里提到的“忠实度校验”。在把答案返回给用户前,让另一个轻量级小模型快速对齐检查一次,发现是幻觉直接拦截。
3. 微调技术 (SFT / LoRA)
【通俗讲解】
预训练就像让大模型读完了世界上所有的书(它有了智商);**SFT(指令微调)**则是教大模型怎么和人类一问一答(教它懂礼貌、听指令)。
① SFT 核心流程与灾难性遗忘
- 流程:构建“指令 (Instruction)”+“输入 (Input)”+“高质量人类回答 (Output)”的数据对,送入模型计算交叉熵损失进行梯度下降。
- 灾难性遗忘:大模型学了新的垂直领域知识(比如学了怎么看医疗化验单),结果把以前学过的基础常识(比如写 Python 代码)给全忘了。
- 大厂解法:绝对不能只用纯医疗数据微调!必须在数据集中混入 10%-20% 的通用高质量闲聊/逻辑推理数据(这叫 Replay 策略),以此来“锚定”它原本的通用能力。
② LoRA 高效参数微调的原理
- 全量微调一个 70B 模型需要几十张显卡,一般人玩不起。
- LoRA 原理:把原本庞大的权重矩阵锁死(Freeze/不训练),在旁边挂一个**“旁路”**。这个旁路是由两个极小的低秩矩阵()相乘构成的。训练时只更新这两个小矩阵,参数量瞬间下降 99%,单张消费级显卡(如 4090)就能跑。
- 为什么要“逐层解冻”?:如果一开始就把所有层放开,剧烈的梯度震荡会破坏底层好不容易学到的基础特征。先调顶层(靠近输出的抽象层),等稳定了再慢慢解冻底层,收敛更平滑。
③ QLoRA 核心思想与权重 Merge
- QLoRA:更绝!连原本锁死的原始大矩阵,也给它量化压缩到 4-bit(极大省显存),然后在这个 4-bit 模型上跑 16-bit 的 LoRA 训练。
- 权重 Merge (合并):在推理(线上服务)时,为了避免每次都要算
原矩阵 + 旁路矩阵的额外耗时,我们会在部署前,执行一段脚本:新矩阵 = 原矩阵 + A × B。物理上把权重融为一体,彻底消除延迟。
4. 对齐技术(RLHF / PPO / DPO / GRPO)
【通俗讲解】
模型能回答问题了,但有时候说话很难听,或者帮罪犯写黑客代码。对齐技术就是给模型树立**“价值观”**,让它回答得符合人类喜好(Helpful, Honest, Harmless)。
① PPO / DPO / GRPO 的区别(重点,面试极爱问)
- PPO(传统 RLHF):经典路线(早期对齐系统常见)。训练链路较复杂,通常涉及策略模型、奖励模型与参考模型,工程成本较高。
- DPO(直接偏好优化):目前开源界的主流(Qwen, Llama 都在用)。去掉了庞大的“奖励模型 (Reward Model)”。它通过数学公式推导,直接把人类的偏好数据映射为语言模型本身的损失函数。简单、稳定、省显存。
- GRPO(DeepSeek 引爆的最新技术):放弃了传统强化学习里最吃显存的 Critic(评论家)模型。它通过对同一个 Prompt 生成一批答案(比如 8 个),直接在这一组内部算相对得分(均值和方差),极大节省计算资源,是大规模强化学习的未来方向。
② 为什么 DPO 不需要像 PPO 那样在线采样?数据格式是啥?
- PPO 是“在线”的,模型得实时生成一个回答,然后裁判(奖励模型)实时给它打分,它再改进。
- DPO 从数学理论上证明了,奖励可以被隐含表达。只要准备好离线的数据对即可。
- 常见数据格式:
{ "prompt": "天空是什么颜色?", "chosen": "蓝色的", "rejected": "红色的" }(一个用户提示,一个好回答,一个坏回答)。
5. 推理加速(vLLM 与 KV-cache)
【通俗讲解】
在生产环境中,直接用 HuggingFace pipeline 往往难以支撑高并发和高吞吐场景,所以通常会选用专门的推理服务框架。当前社区里 vLLM 是非常主流的方案之一。
① vLLM 是怎么做加速的?
- 核心技术叫做 PagedAttention(分页注意力)。
- 过去,大模型生成句子的长短不可预测,系统只能按最大可能(比如直接预留 4000 长度的显存)给它分配连续的显存,导致大量显存碎片和浪费(就像去饭店吃饭,你不知道来几个人,直接包下一个大厅,别人就进不去了)。
- vLLM 借鉴了操作系统的**“虚拟内存分页”机制**,把显存切成一个个小块(Block),用多少分多少。这使得 GPU 显存利用率从 20% 暴涨到 90%,可以同时处理几十倍的并发请求。
② KV-cache 是什么?如何防止被污染?
- KV-cache:大模型生成文本是“一个字一个字蹦”的。当它生成第 100 个字时,前面的 99 个字的 K 和 V 矩阵其实早就计算过了。KV-cache 就是把过去的计算结果缓存起来,避免重复计算,是用“显存空间换取生成时间”。
- 被污染怎么办?:在多轮对话或 Agent 调用工具时,如果把工具返回的“两万字垃圾代码”塞进了上下文,这些垃圾就会一直驻留在 KV-cache 里(不仅占显存,还让大模型后续回答变蠢)。
- 大厂解法:工程上做 Context Isolation(上下文隔离)。工作流分支里产生的临时工具输出,一旦当前节点执行完毕,立马从对话 History 中剔除(或替换为一句简短的总结)。这样底层推理引擎在算下一次生成时,就不会把这些废话放进 KV-cache 造成污染。
模块六:工程落地与系统运维 (Engineering & Production)
1. 全链路追踪 (Trace) 与监控告警
【通俗讲解】
用户抱怨一句:“你的 Agent 刚才卡了 30 秒才回复,而且一直在胡说八道!”
如果你没有做追踪和监控,你根本不知道这 30 秒是网卡了、数据库挂了、大模型处理慢了、还是外部 API 超时了。
① 如何追踪 Agent 的单次请求完整链路?跨服务 Trace 设计思路
- 核心机制:Trace ID 与 Span ID。
- 设计思路:
- 当用户在前端点击发送时,API 网关 (Gateway) 拦截请求,生成一个全局唯一的
Trace ID(比如12345)。 - 这个
Trace ID会随着 HTTP Header(如X-B3-TraceId)往下游的所有服务透传。 - Agent 内部的每一个动作(比如:思考、调外部工具、查向量库),都会生成一个局部的
Span ID。 - 在大厂,我们会接入 LangSmith、Phoenix 或开源的 OpenTelemetry。把每次大模型调用的 Prompt(输入文本)、Token 消耗、耗时、外部工具的入参出参,全部绑定在这个
Trace ID下,统一收集到日志中心(如 ELK)。 - 排查时,只要根据
Trace ID,就能画出一棵完整的“调用树(瀑布图)”,哪里耗时长、哪里报错,一目了然。
- 当用户在前端点击发送时,API 网关 (Gateway) 拦截请求,生成一个全局唯一的
② Agent 系统的核心监控指标与告警策略?
- 核心监控指标 (Metrics):
- TTFT (Time To First Token):首字响应时间。如果超过 2 秒,用户就会觉得卡。
- Token 消耗速率与成本:监控每天/每小时烧了多少钱,防止被人恶意刷接口。
- 工具调用成功率:Agent 调用的 10 个外部 API 中,如果有哪个持续报 500 错误。
- 429 Rate Limit 次数:底层大模型接口是否被并发打满。
- 合理的告警策略:
- 切忌单点报错就告警(AI 偶尔调用失败很正常,会有重试机制)。
- 正确做法是“基于阈值和 P99 告警”:比如“过去 5 分钟内,TTFT 的 P99 耗时超过 5 秒则飞书/钉钉告警”;或者“某一工具连续 10 次调用失败则触发严重告警”。
2. 前后端交互:流式响应 (Streaming) 与 SSE
【通俗讲解】
大模型生成答案是一个字一个字往外蹦的。如果不做流式响应,用户问一个复杂问题,前端界面可能要白屏死机 20 秒,然后突然弹出一大段话,体验极差。为了实现“打字机效果”,我们必须用流式传输。
① 流式响应是怎么实现的?
- 底层主要是基于 HTTP/1.1 的 Chunked Transfer Encoding(分块传输编码) 或 SSE (Server-Sent Events) 技术。服务器不需要等所有数据准备好才响应,而是一点一点把 Buffer(缓存)推给客户端。
② SSE 在前后端如何交互?
- 交互过程:前端发起一个普通的 HTTP 请求,但后端的响应头里会带上
Content-Type: text/event-stream。这条 HTTP 连接就不会挂断,变成了一条“单向通道(Server 到 Client)”,后端可以源源不断地把 Token 推给前端。
③ 发生“工具调用”时,SSE 推送的事件结构包含哪些字段?
- 面试陷阱:只传文本 Token 是不够的。当 Agent 决定去“查天气”时,虽然大模型不吐字了,但前端需要显示“🔍 正在查询天气…”。
- 工程设计:SSE 推送的数据必须是结构化的 JSON 事件。通常包含以下字段:当前端收到
1
2
3
4
5
6
7
8
9{
"id": "msg_001",
"event": "tool_call_start", // 事件类型:文本生成、工具开始、工具结束、报错
"data": {
"tool_name": "get_weather",
"status_msg": "正在查询北京的天气...", // 给前端展示用的友好提示
"arguments": "{\"city\": \"北京\"}"
}
}tool_call_start事件,就在界面上渲染一个“转圈圈的加载组件”;当收到message事件,就把字拼接到对话框里。
3. 性能与并发:高并发场景下的弹性伸缩策略
【通俗讲解】
双十一的时候,淘天的客服 Agent 可能平时只有 100 个人问,晚上 8 点突然涌入 10000 个人。此时怎么分配 GPU 算力和服务器资源?
【大厂弹性伸缩设计 (Auto-scaling)】
- 绝不能基于 CPU/内存 来扩容 LLM:传统后端 CPU 满了就加机器。但 LLM 节点把一个 70B 的大模型加载到 GPU 显存里需要几分钟!等你扩容完,用户的请求早超时了。
- 基于指标驱动的扩容 (KEDA):
- 网关请求队列长度:实时监控排队等待大模型处理的请求数。只要队伍超过 50 个人,立马触发后台异步拉起新的 GPU 实例。
- vLLM 的 KV-Cache 利用率:如果发现当前推理引擎的显存池已经占用了 85%,立刻停止向该节点派发新请求,并触发扩容。
- 柔性降级(极度重要的工程思维):
- 当并发实在太高、算力耗尽时。我们实施模型降级策略:把那些非核心的闲聊请求,自动路由给便宜、小巧、速度快的模型(如 Qwen-7B 或 DeepSeek-V3);把极少量高价值的复杂推理留给大模型。
- 同时在网关层执行令牌桶算法限流,超出系统承载极限的请求直接返回:“当前排队人数较多,请稍后再试”,防止把数据库和整个集群打挂。
4. 框架理解:LangChain vs LangGraph
【通俗讲解】
这道题主要考察你对框架抽象层级和工程可控性的理解。近两年业界在复杂 Agent 场景中更偏向 LangGraph 或自研编排,而不是仅依赖高封装链式框架。
① LangChain
- 本质:一个大杂烩“工具箱”。它封装了各种 Prompt 模板、PDF 解析器、向量库对接。
- 优势:做个玩具 Demo 极快,20 行代码就能跑通一个 RAG。
* 劣势(痛点):默认抽象较高,复杂场景下可观测性、状态可控性和定制化能力可能不够,需要额外工程补齐。
② LangGraph
- 本质:它是基于状态机(State Machine)和有向无环图(DAG)的工作流编排框架。
- 优势与核心区别:
- 支持循环 (Cycles):LangChain 是线性的,跑完拉倒。而 LangGraph 支持图结构的循环。这对于 Agent 的“思考-执行-反思(不满意重新执行)”闭环至关重要!
- 状态的细粒度控制 (State Management):LangGraph 要求你显式定义一个
State类。数据在不同的节点(Node,比如大模型节点、工具节点)之间清晰地流转。你能百分之百掌控上下文。 - 原生支持中断与恢复 (Human-in-the-loop):自带 Checkpointer 机制,执行到某个需要人工审批的节点,整个图可以挂起,把状态存入数据库,等用户点击确认后,无缝恢复执行。(这在传统 LangChain 中几乎无法实现)。
- 适用场景:适合一切生产环境下的多智能体系统 (Multi-Agent)、极其复杂的定制化业务流。