工程化踩坑:死循环、状态丢失与成本控制

踩坑题是面试的“照妖镜”——没做过的人编不出来。面试官问这类题不是要标准答案,而是看你有没有在生产环境里摔过跤、摔完有没有系统性地解决。答得出具体的坑和对应的工程方案,比答十道理论题都管用。


踩坑与经验总结

Q:Agent 的成本怎么控制?线上烧钱太快怎么办?

来源:Agent 岗面试高频题

新手答:“用更便宜的模型。”

高手答:

换模型是最后的手段,换了可能效果也打折。成本控制要从架构层面系统性地做:

  1. 分级调度:不是所有步骤都需要最强模型。意图识别、参数校验这种简单任务用小模型(7B / 14B),只有核心推理环节才用大模型。一个任务里可能 80% 的调用走小模型,只有 20% 走大模型
  2. Token 预算制:每个任务有 token 预算上限,执行过程中实时统计。接近预算时自动触发“省钱模式”——压缩上下文、跳过非必要步骤、简化输出
  3. 缓存复用:相似请求的中间结果做缓存。比如“查北京天气”这种高频工具调用,5 分钟内的结果直接复用,不重复调用 API 也不重复让模型处理
  4. 超预算熔断:单用户单日消费超过阈值,自动限流或降级。这不只是省钱,也是防止恶意用户用 prompt injection 让 Agent 无限循环烧钱

差距在哪:新手的”换模型”是一维思考——只在模型选择上做文章。高手的方案是四维的:调度策略(谁用什么模型)、预算控制(花多少停)、缓存复用(重复的不花)、熔断保护(异常的不让花)。面试官考的是”你有没有成本意识和系统设计能力”——这和后端系统的限流、降级、熔断是同一套思路。

追问:开源 Agent 框架(如 LobeChat)为什么特别容易烧 token?

来源:字节实习二面

LobeChat 等开源 Agent 框架烧 token 的原因不是框架本身”浪费”,而是默认配置追求通用性而非经济性:

  1. 全量对话历史注入:每次请求把完整对话历史发给模型,10 轮对话后上下文可能已经 8000+ token,但其中大部分和当前问题无关。没有做摘要压缩或滑动窗口
  2. System Prompt 膨胀:支持多种场景的通用 System Prompt 往往很长(2000+ token),每次请求都重复发送
  3. 插件/工具描述全量加载:即使用户只需要搜索功能,所有已安装插件的 schema 都会写进 tools 参数,每次调用都消耗 token
  4. 缺少缓存层:相似问题每次都重新调模型,没有语义缓存。尤其高频场景(”今天天气””最新新闻”)可以直接返回缓存结果
  5. 模型选择不分级:所有任务都走同一个大模型,没有根据任务复杂度做路由——简单的意图识别和复杂的推理任务用同样贵的模型

优化方向就是上面四条策略的具体化:压缩历史、动态加载工具 schema、加语义缓存、做模型路由。


Q:为什么很多 Agent Demo 很惊艳,但一上线就不稳定?

来源:腾讯大模型应用开发二面

新手答:“因为线上环境比 Demo 复杂。”

高手答:

Demo 往往是在理想条件下演示的——输入干净、工具有限、单次任务、短上下文。模型只要看起来会做事就行了。但线上环境完全不一样:

Demo 环境:理想输入、有限工具、单次任务、短上下文
线上环境:输入脏、任务长、工具多、状态复杂、异常频繁、权限安全约束

Demo 能跑通,只能说明“这个方向可能”。线上稳定,说明的是你把模型的不确定性关进了工程笼子里。

真正难的是做治理,不是做演示。很多团队一开始觉得问题在模型不够强,后来才发现大量问题其实来自状态管理、工具设计、上下文污染和缺少容错机制。

差距在哪:新手只说了“环境复杂”——这是现象不是分析。高手指出了 Demo 和生产环境的具体差异(输入质量、任务长度、状态管理、异常处理),且点出了核心观点:“稳定不靠模型强,靠工程治理”。面试官考的是你有没有把 Agent 从 Demo 推到生产的实战经验。


Q:开发 Agent 时踩过什么坑?

来源:Agent 岗面试高频题

新手答:“模型有时候不听话,输出格式不对。”

高手答:

坑太多了,挑几个最痛的:

坑一:死循环——模型反复调同一个工具

模型调了一个工具拿到错误结果,然后用一模一样的参数再调一次,无限循环。

解法:① 加失败计数器,同一工具连续失败 2 次直接终止该路径;② 每次重试前让模型先分析上次失败原因,强制改变参数或策略;③ 超过全局步数上限(比如 15 步)直接结束,输出当前最优结果。

坑二:状态丢失——多轮对话里关键信息不翼而飞

用户在第 3 轮说了“预算 5000 以内”,到第 10 轮模型推荐了个 8000 的方案。不是模型故意忽略,是上下文太长被截断了,或者关键信息被淹没。

解法:① 关键参数实时抽取,写入独立的状态存储(Redis / 数据库),不依赖上下文承载;② 每次调用模型时,把当前任务的关键约束作为 System Prompt 的一部分强制注入,而不是埋在历史消息里;③ 定期用模型做自检——“当前任务的约束条件有哪些?”,发现遗漏立刻补回来。

坑三:JSON 解析翻车——模型输出“差一点点”的 JSON

模型返回的 JSON 格式上“几乎正确”——多了个逗号、少了个引号、在 JSON 前面加了句“好的,以下是结果:”。用 json.loads() 直接崩。

解法:① 用正则先提取 JSON 块(匹配 {...} 或 [...]),再解析;② 用 json-repair 之类的库做容错解析;③ 最根本的解法:用模型原生的 function calling / structured output,不要让模型自己拼 JSON 字符串。

坑四:成本失控——一个任务烧掉几十块

复杂任务里模型思考了 30 步,每步都带完整上下文,token 量指数级增长。一个用户的一次请求可能烧掉几十块钱。

解法:① 限制每轮 token 数上限,每个 Agent 有独立的 token 预算;② 实时监控单任务累计成本,超过阈值自动告警并降级(比如切换到更便宜的模型);③ 上下文管理做好——中间结果落库、历史消息压缩,从源头减少 token 消耗。

差距在哪:新手只能说出一个笼统的“输出格式不对”。高手每个坑都有三层结构:① 具体的故障现象(什么情况下出的);② 根因分析(为什么会这样);③ 系统性的解法(不是临时 patch,而是工程化方案)。面试官考的是“你踩过坑之后有没有复盘和系统性修复的习惯”。


AI 工具与框架

Q:用过哪些 Code Agent?有什么优缺点?

来源:腾讯 AI 应用开发 【淘天Agent开发追问:AI辅助编程工作流拆解 + 提示词策略与人工审核介入点】【字节火山引擎 Managed Agent 一面追问:如何用 Coding Agent 完成项目开发】

新手答:“用过 Copilot,挺好用的。”

高手答:

目前 Code Agent 工具分两类:IDE 集成型和终端独立型,工程定位不同。

类型 代表工具 优点 缺点
IDE 集成型 Cursor / Windsurf / Copilot 感知项目上下文(目录、类型、导入),补全即时、改完直接看 diff 对大规模跨文件重构支持有限
终端独立型 Claude Code / Codex CLI / Aider 能执行 shell 验证代码、跑测试、做 git 操作;全局视角 没有实时补全体验,需要更多上下文描述

实际使用策略——两类互补而非互斥:

  • 写新功能、局部修改 → IDE 集成型(实时补全 + 即时预览)
  • 跨文件重构、项目搭建、调试排查 → 终端型(能执行验证 + 全局操作)
  • 两类工具都受益于项目级规则约束(CLAUDE.md / .cursorrules),定义编码规范和技术栈约束后输出质量显著提升

核心认知:Code Agent 的瓶颈通常不是模型能力,而是上下文不足和约束不够——给足项目规则和相关代码上下文,输出质量有质的飞跃。

日常 AI Coding 的实战工作流:

“平时怎么用 AI Coding”是近期高频面试题,面试官想听的不是”用 Cursor 写代码”,而是你的工作流和方法论:

高效使用的三层模式:

模式 场景 具体做法 效率提升
自动驾驶 重复性任务(CRUD、测试用例、类型定义) 给 Agent 明确指令 + 格式要求,让它一次性生成 5-10x
副驾模式 中等复杂度任务(重构、Bug 修复) 自己做设计决策,让 AI 执行具体代码变更 2-3x
导航模式 探索性任务(学新框架、理解陌生代码) 让 AI 解释代码、提供方案对比,自己做最终判断 1.5-2x

关键实践原则:

  1. 先给上下文再给指令:不是直接说”帮我写个排序”,而是先让 AI 读懂项目结构、了解编码规范,再给具体任务
  2. 小步验证:不要让 AI 一次改 20 个文件,而是分步执行、每步验证。一次大改出错了很难定位
  3. AI 生成 → 人类审查 → AI 修正:不盲信 AI 输出,但也不逐行手改——用 AI 的速度生成,用人的判断审核
  4. 知道什么时候不用 AI:涉及核心业务逻辑的架构决策、安全敏感代码、需要深度理解上下文的 Debug——这些场景人工效率更高

差距在哪:新手只评价了”好不好用”。高手按工具类型做了系统对比,且给出了互补使用策略和提升质量的方法论。面试官考的是你对 AI Coding 工具的实际使用深度。

追问:公司里有几十个应用,改一个需求要动多个系统,AI Coding 怎么处理?

来源:蚂蚁 Agent 开发一面

这道题考的是当前 AI Coding 的能力边界——大部分候选人只在单 repo 里用过 AI Coding,面试官想知道你对企业级多系统场景的认知。

核心矛盾:当前 AI Coding 工具以单项目为粒度工作,但企业需求往往跨多个仓库(前端、BFF、后端、基础设施)。一个需求改三个系统,AI 只能看到一个。

分层解决思路:

层级 方案 适用场景
人工拆解 + AI 执行 人工把跨系统需求拆成每个系统的独立任务,每个任务用 AI 完成 当前最实际的方案
共享接口契约 把接口定义(OpenAPI/Proto/GraphQL Schema)作为上下文注入到每个系统的 AI 会话中 保证跨系统一致性
多 Worktree / 多 Session 每个系统开独立 session,通过 CLAUDE.md 注入全局需求背景和接口约定 Claude Code 已原生支持 worktree
编排层协调 用一个 orchestrator Agent 分解需求、分发子任务、校验接口一致性 未来方向,当前不成熟

当前最佳实践:

1. 人工做需求拆解和接口设计(AI 最弱的环节)
2. 把接口契约写成文档,作为每个系统 AI 会话的上下文
3. 每个系统用 AI 独立实现,实现完跑集成测试
4. 接口变更时手动同步到其他系统的 AI 上下文

诚实回答:这个场景是当前 AI Coding 的硬伤——跨系统状态同步、接口一致性保障、变更顺序依赖这些问题,AI 暂时没法自主解决。但人工拆解 + AI 分系统执行 + 契约共享,已经比纯人工效率高很多。


Q:平时写的代码有多少是 AI 生成的?怎么保证质量?

来源:腾讯 Agent 应用开发一面

新手答:“大概 50%,然后自己改改。”

高手答:

比例因任务类型而异:

  • 样板代码(CRUD、配置、测试骨架):80%+ 由 AI 生成,人工只做审查和微调
  • 核心业务逻辑(算法、状态机、并发控制):AI 生成初版,人工大幅修改,实际保留 30-40%
  • 架构设计和技术选型:AI 提供选项和分析,但决策完全由人做

保证质量的方法不是“自己改改”,而是流程化的质量门禁:

  1. 生成前约束:给足上下文(类型定义、接口规范、项目规则文件),让 AI 一次生成质量更高的代码
  2. 生成后审查:AI 生成的代码必须过 Code Review,重点看边界条件、安全漏洞、和已有代码的一致性
  3. 自动化验证:类型检查(TypeScript)、lint、单元测试必须通过。AI 容易写出“看起来对但类型不安全”的代码

核心原则:AI 生成的比例不重要,重要的是每行代码都经过了验证。

怎么回答“占比多少”这类问题:

面试官问这道题不是想听一个数字,而是考察你对 AI 辅助开发的认知成熟度。回答时避免两个极端:说“90% AI 写的”会让面试官觉得你不审代码;说“很少用”会让面试官觉得你效率低。正确的回答结构是:按任务类型分层 → 每层给一个合理比例 → 重点讲质量保障流程。数字本身不重要,展示你对“人机协作边界”的清晰认知才重要。

差距在哪:新手只给了一个比例。高手按任务类型分了不同比例,且给出了系统化的质量保障方法。面试官考的是你对 AI 辅助开发的成熟度。


Q:平时用过哪些 AI Agent 工具?

来源:腾讯 Agent 应用开发一面

新手答:“用过 ChatGPT。”

高手答:

AI Agent 工具按用途分三类:

类别 代表工具 特点
通用对话型 ChatGPT、Claude、Kimi 单轮/多轮问答,不主动执行操作
开发框架型 LangChain、LangGraph、Dify、Coze 提供编排、工具调用、记忆等基础设施,开发者搭建 Agent
AI Coding 型 Cursor、Claude Code、GitHub Copilot 直接辅助写代码,有项目上下文感知

关键认知:这三类工具解决的问题不同——通用型解决“问答”,框架型解决“编排”,Coding 型解决“开发效率”。实际做 Agent 开发时,框架型和 Coding 型通常同时使用:用 Coding 工具写代码,用框架搭 Agent 流程。

选工具的标准不是“哪个最热”,而是你的任务需要什么级别的控制力:简单任务用 Dify/Coze 拖拽搞定;需要自定义编排的用 LangGraph;需要最大灵活性的直接用 SDK + 自建。

差距在哪:新手只列了名字。高手按用途做了分类,且说清了选型标准。面试官考的是你对 Agent 工具生态的全局认知。


Q:你熟悉的 Agent 框架,在架构设计上有什么优势?

来源:腾讯 Agent 应用开发一面

新手答:“它用了 LLM,很智能。”

高手答:

以 OpenClaw 为例,它的架构优势体现在三个层面:

  1. 编排层和能力层分离:编排层(状态机/DAG)负责“做什么、按什么顺序做”,能力层(LLM 调用、工具执行)负责“具体怎么做”。分离后编排逻辑可以独立测试和复用,不和模型调用耦合
  2. 工具即插件:工具通过标准化接口(类似 MCP 的 tool schema)注册,新增工具不需要改编排代码。这让工具生态可以独立演化
  3. 上下文管理可配置:不是硬编码“保留最近 N 轮”,而是提供多种上下文策略(滚动窗口、摘要压缩、按需召回),开发者根据场景组合

这三个优势的共性是关注点分离——编排、能力、工具、上下文各自独立演化,互不干扰。这和微服务架构的设计哲学是一致的。

差距在哪:新手用“智能”概括一切。高手从编排/能力分离、工具插件化、上下文可配置三个架构决策分析优势。面试官考的是你对框架的理解是“会用”还是“理解设计”。


Q:自己做 Agent 时,踩过最大的坑是什么?

来源:腾讯 Agent 应用开发一面

新手答:“模型输出不稳定。”

高手答:

最大的坑是过度信任模型的“理解力”,导致状态管理全靠上下文承载。

早期做 Agent 时,觉得模型能“记住”之前的对话,所以把关键参数(用户偏好、任务进度、中间结果)全放在对话历史里。结果上下文一长,模型就开始丢信息、乱推理——用户在第 3 轮说的约束条件,到第 10 轮完全被忽略。

系统性修复:把“模型记住”改成“系统记住”——

  1. 关键参数实时提取到独立的状态存储(Redis / 数据库),不依赖上下文
  2. 每轮调用前把当前约束条件作为 System Prompt 的固定部分强制注入
  3. 中间结果落库,上下文里只留一句摘要

这个坑的本质是:Agent 的上下文窗口是“工作台”,不是“仓库”。工作台上只放当前步骤需要的东西,其他全放仓库里按需取。

差距在哪:新手说“不稳定”是表象。高手指出了根因(过度依赖上下文承载状态)和系统性解法(状态外置 + 强制注入 + 落库)。面试官考的是你踩坑后有没有做过根因分析和系统性修复。


代码质量与测试

Q:如何解决大模型 API 服务的响应延迟问题?

来源:字节 Agent 实习一面 【小红书数据库智能化二面追问:产品层面的等待体验优化】【小舒一面追问:异步调用与线程阻塞】

新手答:“用更快的模型。”

高手答:

API 服务延迟分两部分:首 token 延迟(TTFT) 和生成吞吐(TPS),优化思路不同。

1. 流式输出(Streaming)——最直接的体感优化:

  • 不等全部生成完再返回,逐 token 流式输出。用户感知延迟从“等 10 秒”变成“0.5 秒就开始看到内容”
  • SSE(Server-Sent Events)或 WebSocket 实现

2. 模型层面:

  • 模型分级调度:简单请求(分类、提取)用小模型(7B/14B),复杂请求(推理、创作)用大模型。80% 的请求可以用小模型处理,延迟降 5-10 倍
  • 量化部署:INT4/INT8 量化减少计算量和显存占用,推理速度提升 2-3 倍
  • 推测解码:小模型快速生成草稿,大模型批量验证,整体速度提升 2-3 倍

3. 服务架构层面:

  • KV Cache 复用:多轮对话中,前几轮的 KV Cache 不需要重算,缓存后复用,TTFT 大幅缩短
  • Prefix Caching:共享相同 System Prompt 的请求复用前缀计算结果
  • 连续批处理:vLLM/SGLang 的 continuous batching,请求到达即处理,不等凑满一批
  • 负载均衡:多实例部署 + 按当前负载智能路由,避免单实例过载

4. 缓存层面:

  • 语义缓存:对高频相似请求做语义匹配,命中缓存直接返回,不调模型。适合 FAQ 类场景
  • 工具结果缓存:Agent 场景中,工具返回值(天气、股价等)设 TTL 缓存,避免重复调用

5. 产品交互层面:

  • 尽早通过 SSE 返回首个可见事件,把“正在检索、已找到 N 条证据、正在生成”作为结构化进度,而不是伪造模型思考过程
  • 长任务提供取消、超时后后台继续和结果通知,让用户能在方向错误时尽早终止,减少无效 token 消耗
  • 产品层优化不降低真实总耗时,但能降低无反馈等待;必须和后端 TTFT、P95/P99 指标分开评估

6. 调用与任务执行模型:

  • 不要让 Web 请求线程用同步 SDK 阻塞等待几十秒的 LLM 调用。在线短请求使用异步客户端、连接池和有界并发,避免请求量上升后线程、连接和内存一起耗尽
  • 长任务进入队列或 Job 系统,请求先返回 task_id,结果通过 SSE 推送或轮询获取;生成过程与客户端连接解耦
  • 为整条请求和每个模型/工具步骤设置 deadline、超时、取消传播和有限重试,并用幂等键避免重试造成重复副作用

async 不会缩短单次模型推理时间,它解决的是等待期间不占住请求线程,从而保住并发吞吐和取消能力。真正的总耗时仍要靠模型、上下文、并行路径和缓存优化。

差距在哪:新手只想到换模型。高手从流式输出、模型分级、服务架构、缓存、产品交互和任务执行六个层面做了系统优化,且区分了 TTFT、TPS、并发吞吐与用户感知等待。面试官考的是你对大模型服务工程化的理解——延迟优化不只是“模型更快”,而是一个全栈工程问题。


Q:AI Coding 产品怎么测试?从 Demo 到生产可交付还差什么?

来源:蚂蚁集团智能体与大模型应用二面

新手答:“写几个 case 测一下就行。”

高手答:

AI Coding 产品的测试和传统软件测试有本质区别——输出是概率性的,同样的输入不一定得到同样的输出,所以不能只靠“写几个 case 通过了就行”。

测试方法论:

1. 构造评估集,不是测几个 case:

  • 设计覆盖不同代码复杂度(纯函数 / 有依赖 / 异步 / 并发)和任务类型(生成 / 修复 / 重构 / 解释)的评估集
  • 每个 case 要有明确的验收标准:不是“看起来对”,而是能编译、能通过测试、符合代码规范
  • 评估集要包含边界和对抗 case:超长文件、语法错误的输入、故意误导的 Prompt

2. 评估维度不只是“对不对”:

维度 指标 说明
正确性 Pass@K、编译通过率 K 次生成中至少一次正确的概率
一致性 同一输入多次生成的方差 输出是否稳定
安全性 有无注入漏洞、敏感信息泄露 生成的代码是否安全
效率 Token 消耗、端到端延迟 成本和速度

从 Demo 到生产可交付,还差什么:

  1. 鉴权和权限控制:Demo 不需要考虑谁能用、能操作哪些文件。生产环境必须有用户身份验证和操作权限隔离
  2. 错误恢复和回滚:Demo 出错了重来就行。生产环境需要操作日志、变更回滚(类似 git revert)、异常告警
  3. 并发和多用户:Demo 是单用户。生产环境要支持多用户同时使用,状态隔离,资源限制
  4. 监控和可观测性:生成质量监控、成本监控、异常检测、用户反馈收集——没有这些,线上问题完全是黑盒
  5. 安全审计:AI 生成的代码可能引入安全漏洞,生产环境需要自动化安全扫描(SAST/DAST)
  6. CI/CD 集成:生成的代码要能自动跑 lint、测试、构建,不能只靠人工 review

差距在哪:新手的“测几个 case”只是验证“能不能跑”。高手从评估集设计、多维度指标、生产化六大能力(鉴权、回滚、并发、监控、安全、CI/CD)展开。面试官考的不是“会不会测试”,而是你对“把 AI 能力做成产品”这件事的完整认知。


Q:什么是 SDD(Spec-Driven Development)?它和 Skills 有什么区别?

来源:蚂蚁集团智能体与大模型应用二面

新手答:“SDD 就是先写规格再写代码,Skills 是预定义能力。”

高手答:

SDD(Spec-Driven Development,规格驱动开发)是 AI Coding 领域的一种工作流方法——先让 AI 生成详细的技术规格说明(Spec),人工审核确认后,再让 AI 按照 Spec 生成代码。

SDD 的核心流程:

为什么要这么做:直接让 AI 写代码的问题是——需求理解错了,生成的代码再完美也没用。SDD 把“理解需求”和“写代码”拆开,在理解阶段就做人工确认,避免代码写完才发现方向错了。

SDD vs Skills 的对比:

维度 SDD Skills
本质 开发工作流方法论 Agent 的可复用能力单元
解决的问题 “怎么让 AI 写出正确的代码” “怎么让 Agent 处理特定类型的任务”
关注点 需求→规格→代码的转化质量 触发匹配→Prompt→工具→输出的执行流程
人工参与 核心环节(审核 Spec) 可选(配置后自动执行)
适用场景 代码生成、架构设计 任何 Agent 能力扩展
输出物 Spec 文档 + 代码 任务执行结果

两者的关系:不互斥,可以结合。一个“代码生成 Skill”可以内部采用 SDD 流程——Skill 定义了“什么时候触发、用什么工具”,SDD 定义了“触发后按什么步骤生成代码”。

Skills 是能力的封装方式,SDD 是代码生成的质量保障方法。前者回答“Agent 能做什么”,后者回答“怎么做得对”。

差距在哪:新手把两者简单对立。高手看到了它们在不同维度上的定位——SDD 是方法论层面的流程设计,Skills 是系统架构层面的能力封装,两者可以组合使用。面试官考的是你能不能区分“工作流方法”和“系统架构”这两个层次的抽象。


Q:如何保证 AI 代码生成的质量与掌控性?

来源:蚂蚁集团 Agent 开发一面

新手答:“生成后人工 review 一下。”

高手答:

人工 review 是最后一道线,但不能是唯一一道。保证质量和掌控性要在生成前、生成中、生成后三个阶段都做控制:

生成前——约束输入,减少出错空间:

  1. 上下文精准投喂:给模型完整的类型定义、接口规范、相关测试用例,而不是只给一句需求描述。上下文越精确,生成质量越高
  2. 项目规则注入:通过 CLAUDE.md / .cursorrules 定义编码规范(命名风格、错误处理模式、禁用的 API),让模型生成时就遵守项目约定
  3. Spec 先行(SDD):对复杂功能,先让 AI 生成技术规格说明,人工确认后再生成代码。在理解阶段就拦住方向性错误

生成中——约束输出,缩小自由度:

  1. Structured Output:函数签名、返回类型、错误码这些关键接口用 schema 约束,不让模型自由发挥
  2. 增量生成:不让模型一次生成 500 行。拆成小步——先生成函数骨架、再填充逻辑、再补异常处理,每步可验证
  3. Diff 模式:修改已有代码时,要求模型输出精确的 diff 而非整文件重写,减少意外修改

生成后——验证结果,兜住底线:

  1. 自动化门禁:生成的代码必须通过类型检查(TypeScript/mypy)、lint、现有测试套件。不过门禁不允许合入
  2. AI 自验证:让模型同时生成代码和对应的测试用例,跑通测试才算完成。这不完美,但能抓住明显的逻辑错误
  3. 安全扫描:自动检测常见漏洞模式(SQL 注入、XSS、硬编码密钥),AI 生成的代码更容易引入这类问题

掌控性的核心原则:生成的每一步都要有人类可审查的检查点。不是“生成完看一眼”,而是“每个阶段都有明确的验收标准”。

差距在哪:新手只有事后 review 一道防线。高手在生成前(约束输入)、生成中(约束输出)、生成后(自动验证)三个阶段构建了九道防线。面试官考的是你对 AI 代码生成的质量体系有没有工程化的设计。


基础设施与算法

Q:LangGraph 定义的搜索节点做不到并发执行吗?怎么做?

来源:AI 工程师面试

新手答:“LangGraph 是顺序执行的,做不了并发。”

高手答:

LangGraph 完全支持并发执行,有三种方式,适用场景不同:

方式一:Fan-out / Fan-in(静态并行)

在 graph 定义时,让多个节点从同一个上游节点出发,形成并行分支,最后汇聚到一个下游节点:

graph.add_edge("plan_node", "search_node_1")
graph.add_edge("plan_node", "search_node_2")
graph.add_edge("plan_node", "search_node_3")
graph.add_edge("search_node_1", "merge_node")
graph.add_edge("search_node_2", "merge_node")
graph.add_edge("search_node_3", "merge_node")

三个 search 节点会并发执行,全部完成后进入 merge_node。适合编译时就知道要并行几路的场景。

方式二:Send API(动态并行)

并行数量在运行时才确定时,用 Send API 动态创建并行分支:

from langgraph.types import Send

def plan_node(state):
    queries = state["search_queries"]  # 运行时才知道有几个查询
    return [Send("search_node", {"query": q}) for q in queries]

graph.add_conditional_edges("plan_node", plan_node)

每个 Send 创建一个独立的并发执行分支,适合搜索数量取决于规划结果的场景。

方式三:节点内部 asyncio 并发

如果只是一个节点内部需要并发调用多个 API:

import asyncio

async def search_node(state):
    queries = state["search_queries"]
    results = await asyncio.gather(*[
        search_api(q) for q in queries
    ])
    return {"search_results": results}

状态合并是关键问题:

并发节点同时写同一个 state 字段会冲突。LangGraph 用 Reducer 函数解决:

from typing import Annotated
import operator

class State(TypedDict):
    # operator.add 表示多个并发节点的结果做列表拼接
    search_results: Annotated[list, operator.add]

每个并发分支返回的 search_results 会被自动拼接成一个大列表,而不是互相覆盖。

差距在哪:新手以为 LangGraph 不支持并发——说明没深入用过。高手给出了三种并发方式(静态 Fan-out、动态 Send、节点内 asyncio),且指出了并发状态合并(Reducer)这个关键问题。面试官考的是你对 LangGraph 并发执行机制的实际掌握程度。


Q:PostgreSQL 的索引结构是什么?索引如何优化查询速度?在 Checkpoint 场景下如何用索引加速?

来源:AI 工程师面试

新手答:“就是 B+ 树,查询快。”

高手答:

PostgreSQL 支持多种索引类型,不同索引适用不同查询模式:

索引类型 底层结构 适用查询 典型场景
B-Tree(默认) 平衡多叉树 等值查询、范围查询、排序 绝大多数场景的首选
Hash 哈希表 纯等值查询 精确匹配,如 session_id = ‘xxx’
GIN 倒排索引 包含查询、全文搜索 JSONB 字段查询、数组包含
GiST 通用搜索树 范围重叠、最近邻 地理空间、区间查询
BRIN 块范围索引 大表上的范围查询 时间序列、日志表(数据物理有序时)

B-Tree 索引如何加速查询:

B-Tree 是一棵平衡多叉排序树,每个节点存多个键值和指向子节点的指针。查询时从根节点开始,每一层通过比较快速定位到目标区间,时间复杂度 O(log N):

无索引:全表扫描 100 万行 → 逐行比较 → O(N)
有 B-Tree 索引:树高约 3-4 层 → 3-4 次磁盘 IO → O(log N)

关键优化手段:

  1. 覆盖索引(Covering Index):把查询需要的列都放进索引,查询直接从索引返回,不需要回表
  2. 复合索引:CREATE INDEX ON checkpoints (thread_id, checkpoint_id DESC) 一个索引同时支持按 thread_id 过滤 + 按 checkpoint_id 排序
  3. 部分索引(Partial Index):CREATE INDEX ON checkpoints (thread_id) WHERE is_active = true 只索引活跃数据,索引体积更小
  4. Index-Only Scan:当查询的所有列都在索引中时,PostgreSQL 直接扫描索引,完全跳过堆表

Checkpoint 场景下的索引设计:

Checkpoint 表的核心查询模式是:给定 session_id(thread_id),找到最新的 Checkpoint。

-- 最常见的查询:加载最新 checkpoint
SELECT * FROM checkpoints
WHERE thread_id = 'abc-123'
ORDER BY checkpoint_id DESC
LIMIT 1;

最优索引:

-- 复合索引:thread_id 精确匹配 + checkpoint_id 倒序排列
CREATE INDEX idx_checkpoints_thread_latest
ON checkpoints (thread_id, checkpoint_id DESC);

这个索引让查询一次 B-Tree 查找就能定位到目标行——先按 thread_id 定位到该会话的所有 checkpoint,B-Tree 内部已按 checkpoint_id 倒序排列,直接取第一条就是最新的。

如果 Checkpoint 表还有 JSONB 类型的 metadata 字段需要查询(如按节点名过滤):

-- GIN 索引支持 JSONB 内部字段查询
CREATE INDEX idx_checkpoints_metadata ON checkpoints USING GIN (metadata);

-- 查询:找所有在 search_node 节点暂停的 checkpoint
SELECT * FROM checkpoints WHERE metadata @> '{"node": "search_node"}';

差距在哪:新手只知道 B+ 树一个名字。高手区分了五种索引类型及适用场景,详细说明了 B-Tree 的查询加速原理,且结合 Checkpoint 实际场景给出了复合索引 + GIN 索引的具体设计。面试官考的是你能不能把数据库基础知识应用到 Agent 系统的实际存储设计中。


Q:用 Claude Code 做一个比较长的任务,如果遇到单次 session 跑不完、中间断网怎么办?

来源:AI 工程师面试

新手答:“重新开一个 session 从头做。”

高手答:

长任务中断是 AI Coding 工具的常见问题。解决方案分预防和恢复两个层面:

预防——降低中断的影响:

  1. 任务拆分:长任务不要一口气交给 Agent。先用 Plan Mode(/plan)生成整体方案,再按模块逐步执行。每完成一个模块就 commit 一次,确保进度不丢
  2. CLAUDE.md 持久化上下文:把项目的关键信息、架构约定、已完成的进度写在 CLAUDE.md 文件中。新 session 启动时会自动读取,相当于给 Agent 一个“接班备忘录”
  3. TodoList 跟踪进度:用 Claude Code 的任务管理功能(或直接在 CLAUDE.md 中维护 checklist),标记每个子任务的完成状态。中断后可以精确知道“做到哪了”
## 当前任务进度
- [x] 数据库 schema 设计
- [x] API 路由层实现
- [ ] 业务逻辑层实现  ← 断点
- [ ] 单元测试
- [ ] 集成测试
  1. 高频 git commit:每完成一个有意义的改动就 commit,不要攒到最后。即使 session 崩了,代码改动不会丢

恢复——中断后快速接续:

  1. --continue 继续上次对话:Claude Code 支持 claude --continue 直接恢复上一次 session 的对话上下文,从断点继续
  2. /resume 恢复任务:在新 session 中使用 /resume 命令,可以加载之前的对话历史和任务状态
  3. Git diff 定位进度:新 session 中让 Claude Code 执行 git diff 和 git log,快速了解已完成的改动,从断点处继续

最佳实践流程:

核心认知:长任务的可靠性不靠 session 稳定性保证,而靠“频繁保存 + 上下文持久化 + 快速恢复”的工程习惯。这和写代码要频繁 commit 是同一个道理——不要把所有筹码押在“一次跑通”上。

差距在哪:新手遇到中断只会重来。高手从预防(任务拆分 + CLAUDE.md + TodoList + 高频 commit)和恢复(–continue + /resume + git diff)两个层面给出了完整方案。面试官考的是你用 AI Coding 工具做大型任务时的工程化习惯。


Q:分布式限流算法——令牌桶、漏桶、滑动窗口的区别和实现?

来源:快手 AI Agent 开发一面

新手答:“用令牌桶限流,每秒放几个令牌。”

高手答:

三种限流算法各有适用场景,核心差异在于对突发流量的处理方式:

算法 核心思想 允许突发 流量整形 适用场景
令牌桶 桶以固定速率填充令牌,请求消耗令牌 ✅ 桶满时可突发 ❌ API 限流(允许短时突发)
漏桶 请求先入桶,桶以固定速率漏出处理 ❌ 强制匀速 ✅ 流量整形(保护下游)
滑动窗口 统计滑动时间窗口内的请求数 ❌ 窗口内硬上限 ❌ 精确的请求频率控制

令牌桶算法:

参数:桶容量 capacity、填充速率 rate(个/秒)
状态:当前令牌数 tokens、上次填充时间 last_refill

每次请求:
  1. 计算距上次的时间差,补充令牌:tokens += elapsed × rate
  2. tokens = min(tokens, capacity)  // 不超过桶容量
  3. if tokens >= 1: tokens -= 1, 放行
     else: 拒绝

漏桶算法:

请求入桶(队列),桶以固定速率出队处理。
桶满时新请求直接丢弃或排队等待。
效果:无论入口流量多不均匀,出口始终匀速。

滑动窗口算法:

参数:窗口大小 window_ms、最大请求数 max_count

结构体字段:
  window_start: int64    // 当前窗口起始时间戳
  request_count: int      // 窗口内已有请求数
  window_size_ms: int     // 窗口大小(毫秒)
  max_requests: int       // 窗口内允许的最大请求数
  // 精确版还需要:request_timestamps: list  // 每个请求的时间戳

每次请求:
  1. 清除窗口外的过期记录
  2. 统计窗口内请求数
  3. if count < max_count: 记录时间戳, 放行
     else: 拒绝

令牌桶 vs 滑动窗口:

令牌桶:允许突发(桶满时一次性用完所有令牌),适合"平均速率限制"
滑动窗口:任何时刻窗口内请求数不超限,适合"严格频率限制"

例:限制 100 请求/分钟
  令牌桶:可能在第 1 秒就用完 100 个令牌(突发)
  滑动窗口:任意 60 秒窗口内最多 100 个请求(平滑)

Redis 实现:

算法 Redis 数据结构 实现思路
令牌桶 String + EXPIRY 存剩余令牌数,用 Lua 脚本原子操作(补充 + 扣减)
滑动窗口 Sorted Set(ZSET) 每个请求以时间戳为 score 加入 ZSET,用 ZREMRANGEBYSCORE 清除窗口外记录,ZCARD 统计窗口内数量
固定窗口 String + INCR + EXPIRY key 为“窗口起始时间”,INCR 计数,EXPIRY 到窗口结束

分布式场景下,所有操作必须用 Lua 脚本保证原子性,避免并发竞态。

差距在哪:新手只知道令牌桶一种。高手把三种算法的核心差异(是否允许突发、是否整形)讲清楚了,且给出了滑动窗口的结构体设计和 Redis 数据结构选型。面试官考的是你对限流这个经典系统设计问题的工程化理解。


Q:布隆过滤器的原理?会出现什么问题?如何控制误判率?

来源:快手 AI Agent 开发一面

新手答:“用来判断一个元素是否在集合中。”

高手答:

布隆过滤器是一个空间效率极高的概率型数据结构,用于快速判断“某元素是否可能在集合中”。

原理:

数据结构:一个 m 位的 bit 数组(初始全 0)+ k 个独立哈希函数

添加元素:对元素做 k 次哈希,将对应的 k 个位置置 1
查询元素:对元素做 k 次哈希,检查 k 个位置
  - 全部为 1 → "可能存在"(可能是误判)
  - 任一为 0 → "一定不存在"(绝无漏判)
示例:m=10, k=3

添加 "apple":hash1→2, hash2→5, hash3→8
  bit: [0,0,1,0,0,1,0,0,1,0]

添加 "banana":hash1→1, hash2→5, hash3→9
  bit: [0,1,1,0,0,1,0,0,1,1]

查询 "cherry":hash1→2, hash2→5, hash3→9 → 全部为 1 → "可能存在"(误判!)
查询 "grape":hash1→3, hash2→7, hash3→9 → 位置 3 为 0 → "一定不存在"

会出现什么问题:

  1. 假阳性(False Positive):说“在集合中”但实际不在。多个元素的哈希位重叠导致
  2. 不支持删除:清除某个位可能影响其他元素(它们的哈希位重叠)。变体 Counting Bloom Filter 用计数器替代 bit 可支持删除
  3. 无假阴性:说“不在”就一定不在——这是布隆过滤器的核心保证

如何控制误判率:

误判率公式:FPR ≈ (1 - e^(-kn/m))^k

三个可调参数:
  m:bit 数组大小 → 越大误判率越低
  k:哈希函数数量 → 存在最优值
  n:已插入元素数量 → 固定的业务参数

最优 k = (m/n) × ln2 ≈ 0.693 × (m/n)

实际估算:
  n = 100 万元素,目标 FPR = 1%
  m ≈ -n × ln(FPR) / (ln2)^2 ≈ 958 万 bit ≈ 1.2 MB
  k = 7

  n = 100 万元素,目标 FPR = 0.1%
  m ≈ 1437 万 bit ≈ 1.8 MB
  k = 10

典型应用场景:缓存穿透防护(快速判断 key 是否存在)、爬虫 URL 去重、垃圾邮件过滤、大规模数据去重。

差距在哪:新手只知道“判断是否存在”。高手讲清了原理(bit 数组 + k 次哈希)、两个核心问题(假阳性 + 不可删除)、误判率控制公式及实际估算。面试官考的是你对概率数据结构的工程化理解——不只是“知道这个东西”,还能算出实际场景需要多大空间。


Q:数据库索引失效的常见场景?LIKE 查询会不会导致索引失效?

来源:快手 AI Agent 开发一面

新手答:“LIKE 查询会导致索引失效。”

高手答:

索引失效不是 LIKE 的专利——有很多常见写法会让优化器放弃使用索引:

索引失效的常见场景:

场景 示例 失效原因
对索引列使用函数 WHERE YEAR(create_time) = 2024 函数运算破坏了索引的有序性
隐式类型转换 WHERE varchar_col = 123 varchar 列和 int 比较,触发隐式转换
LIKE 前缀通配 WHERE name LIKE '%张三' 无法利用 B+ 树的有序性
OR 混合非索引列 WHERE indexed_col = 1 OR non_indexed = 2 优化器可能放弃索引走全表扫描
违反最左前缀原则 联合索引 (a,b,c),查 WHERE b=1 跳过了最左列 a
索引选择性太低 WHERE gender = '男'(50% 数据命中) 优化器判断全表扫描更快
NOT / != / <> WHERE status != 'deleted' 部分情况下优化器放弃索引
IS NULL / IS NOT NULL WHERE col IS NOT NULL 取决于数据分布和数据库版本

LIKE 查询的索引命中情况:

✅ 会走索引:
  WHERE name LIKE '张三%'    -- 前缀匹配,B+ 树可以范围扫描
  WHERE name LIKE '张三%王'  -- 前缀固定,仍可利用索引

❌ 不走索引:
  WHERE name LIKE '%张三'    -- 前缀通配,无法确定扫描起点
  WHERE name LIKE '%张三%'   -- 前后通配,只能全表扫描

本质原因:B+ 树索引是按键值有序排列的。前缀匹配相当于范围查询(“张三”开头的所有记录在一个连续范围内),可以利用有序性。但前缀通配意味着匹配位置不确定,B+ 树无法定位起点,只能全表扫描。

需要模糊匹配怎么办:如果业务必须支持 %关键词% 查询,应该用全文索引(MySQL FULLTEXT)或搜索引擎(Elasticsearch),而不是在关系数据库上硬撑。

差距在哪:新手笼统地说“LIKE 会失效”——其实前缀匹配不会。高手区分了八种索引失效场景,且说清了 LIKE 的前缀匹配 vs 通配的本质区别(B+ 树的有序性)。面试官考的是你对数据库索引原理的理解深度,而不是背条目。


Q:大规模数据处理场景设计——从千条到百万级如何扩展?

来源:快手 AI Agent 开发一面

新手答:“用循环累加就行。”

高手答:

千条数据:单机单线程直接处理,没必要引入复杂架构。一个循环累加,时间复杂度 O(n),毫秒级完成。

百万级数据:核心挑战从计算变成了内存、IO、和故障恢复:

分治并行(MapReduce 思想):

  1. Map 阶段:将数据按分片键(如 ID 范围)拆分为 N 个子集,每个 Worker 独立计算局部和
  2. Reduce 阶段:汇总节点收集所有局部和,做最终聚合
  3. 总耗时 ≈ 单个分片处理时间 + 网络传输时间,而非 N 倍

保证效率和稳定性:

挑战 解决方案
内存不够 流式处理:逐批读取数据,不一次性加载全量到内存
Worker 失败 检查点(Checkpoint):每处理一批保存中间结果,失败后从断点恢复而非从头开始
数据倾斜 均匀分片:避免某个 Worker 分到的数据远多于其他
结果正确性 幂等设计:重试不会导致重复计算(去重或幂等累加)
监控 进度追踪:每个 Worker 上报处理进度,整体进度可视化

技术选型:

百万级:多线程/多进程 + 内存流式处理即可
千万级:单机多核(Python multiprocessing / Go goroutine)
亿级以上:分布式计算框架(Spark / Flink)

差距在哪:新手用循环就结束了——千条数据确实够用,但百万级完全不同。高手用分治并行处理,且考虑了内存、故障恢复、数据倾斜、幂等性四个生产环境必须面对的问题。面试官考的是你从”能跑”到”能在生产环境稳定跑”的工程化思维跃迁。


性能与延迟优化

Q:针对包含 3 个以上工具调用且高频请求的任务,通过什么方式可以压低系统整体的端到端延迟?

来源:淘天 AI Agent 一面

新手答:”用更快的模型。”

高手答:

模型推理速度只是延迟的一部分。Agent 任务的端到端延迟是一条串行链路的总和——模型推理 + N 次工具调用 + 网络传输 + 最终合成。要压延迟,必须拆开这条链路,逐段优化。

延迟拆解:

一次典型的多工具 Agent 任务延迟组成:

模型推理(工具选择)    ~500ms
├─ 工具调用 1(API 请求)  ~300ms
├─ 工具调用 2(数据库查询) ~200ms
├─ 工具调用 3(网页抓取)   ~800ms
模型推理(结果合成)    ~1000ms
─────────────────────────────
串行总延迟              ~2800ms

核心策略:从串行到并行:

六大优化策略:

1. 工具并行调用:识别无依赖关系的工具调用,用 asyncio.gather 并行执行。总耗时 = max(各工具耗时),而非 sum。

# 依赖分析示例
用户问:”北京明天天气怎么样,顺便帮我查一下机票价格”
  - 查天气 API ← 独立
  - 查机票 API ← 独立
  → 可并行,耗时从 300+400=700ms 降到 max(300,400)=400ms

2. 投机执行(Speculative Execution):在当前工具还没返回时,预测下一步可能需要的工具调用并提前发起。如果预测正确,省掉一轮等待;如果错误,丢弃结果即可(前提是工具调用是只读的)。

3. 结果缓存:高频且时效性要求不高的工具结果做 TTL 缓存。

工具类型 缓存 TTL 示例
天气查询 10 分钟 同一城市短时间内多次查询
汇率查询 5 分钟 同一币种对
知识库检索 1 小时 同一 query 的 RAG 结果
数据库聚合 按业务需求 日报、周报类统计

4. 流式生成:不等所有工具结果全部返回再开始合成,而是边收结果边生成。第一个工具结果返回后立即开始流式输出,后续结果到达时追加合成。用户感知到的首 token 延迟大幅降低。

5. 工具调用批处理:多次调用同一服务时,合并为一次批量请求。比如需要查 5 个用户的信息,合成一次 SELECT ... WHERE id IN (...) 而非 5 次单独查询。

6. 模型分级:工具选择和参数提取用小模型(7B),速度快且准确率够用;只有最终结果合成才用大模型保证质量。

综合效果对比:

优化手段 延迟降低 实现复杂度 适用条件
工具并行调用 40-60% 低 工具之间无依赖
结果缓存 30-80%(命中时) 低 工具结果有时效性容忍
流式生成 首 token 延迟降 50%+ 中 客户端支持流式接收
工具调用批处理 20-40% 中 多次调用同一服务
投机执行 10-30% 高 工具调用只读且可预测
模型分级 20-40% 高 有合适的小模型可用

实际工程组合:

第一优先级(低成本高收益):
  ├─ 工具并行调用:改一下调度逻辑即可
  └─ 结果缓存:加一层 Redis 缓存

第二优先级(中等投入):
  ├─ 流式生成:需要改前后端交互协议
  └─ 批处理合并:需要识别可合并的调用

第三优先级(高投入):
  ├─ 模型分级:需要评估小模型的工具选择准确率
  └─ 投机执行:需要建立工具调用预测模型

差距在哪:新手只盯着模型速度——这只是链路中的一环。高手把端到端延迟拆成多段,逐段分析瓶颈,从并行化、缓存、流式、批处理、模型分级五个维度给出方案,并按投入产出比排了优先级。面试官考的是你对分布式系统延迟优化的工程直觉——Agent 系统本质上就是一个”模型 + 多服务调用”的分布式系统,优化思路和后端微服务架构是相通的。


Q:任务执行远大于单次 Token 限制时,如何设计以支持断点继续生成?

来源:淘天 AI Agent 一面

新手答:”让模型一次生成完。”

高手答:

现实中很多 Agent 任务的规模远超单次上下文窗口——比如分析一份 200 页的合同、生成一套完整的测试用例、或执行一个需要 50+ 步的复杂工作流。不可能一次塞进去,也不能指望一次调用就跑完。必须设计断点续传机制,像分布式任务调度器一样管理 Agent 的执行状态。

三层架构设计:

第一层:任务分解

把大任务拆成多个子任务,每个子任务的输入输出在单次上下文窗口内可完成:

示例:分析 200 页合同
  → 子任务 1:提取合同基本信息(甲乙方、日期、金额)
  → 子任务 2:分析第 1-20 页的条款风险
  → 子任务 3:分析第 21-40 页的条款风险
  → ...
  → 子任务 N:汇总所有风险点,生成报告

每个子任务独立可执行,中间结果持久化到数据库

第二层:检查点机制

每完成一个关键步骤,保存结构化的检查点状态:

Checkpoint 数据结构:
{
  “task_id”: “contract-analysis-001”,
  “total_subtasks”: 12,
  “completed”: [1, 2, 3, 4, 5],
  “current_subtask”: 6,
  “current_progress”: “已分析到第 108 页”,
  “intermediate_results”: {
    “基本信息”: { ... },
    “风险点”: [ ... ],
    “待确认项”: [ ... ]
  },
  “constraints”: {
    “输出格式”: “结构化 JSON”,
    “关注重点”: “知识产权条款”
  },
  “created_at”: “2026-04-23T10:30:00Z”,
  “last_updated”: “2026-04-23T10:45:00Z”
}

第三层:状态恢复与上下文重建

恢复时不需要重放完整历史,而是构建一个”恢复提示词”:

恢复提示词 = 
  系统指令(角色 + 约束)
  + 任务概述(原始目标的一句话摘要)
  + 已完成进度(完成了什么、关键结论)
  + 当前子任务(从哪里继续、还剩什么)
  + 核心约束(必须遵循的格式和规则)

关键原则:恢复提示词的 token 数应远小于上下文窗口
  完整历史可能有 50K+ token
  恢复提示词只需要 2K-4K token

关键工程细节:

问题 解决方案
幂等性 工具调用必须幂等,或用执行日志去重。恢复后不能重复执行已完成的操作(比如重复发邮件、重复写数据库)
检查点粒度 太粗(每个子任务一次)→ 恢复代价大;太细(每步一次)→ 存储开销大。实践中按”关键里程碑”保存
上下文一致性 恢复时外部状态可能已变化(比如数据库数据更新了),需要校验检查点中引用的数据是否仍然有效
超时与重试 单个子任务设超时时间,超时后保存当前进度、切换到下一个子任务或进入等待队列
人工介入点 在关键决策节点设置人工确认,避免长时间无人监控的自动执行走偏

与传统分布式任务框架的对比:

特性 Agent 断点续传 Temporal / Airflow
状态管理 自然语言摘要 + 结构化 JSON 严格的状态机
恢复策略 上下文重建(可能有信息损失) 精确重放(无损)
任务拆分 动态的,模型自行判断 静态的 DAG 定义
错误处理 模型可自适应调整策略 按预定义规则重试
适用场景 开放式、探索性任务 确定性流程

实际上,最成熟的方案是两者结合:用 Temporal 等工作流引擎管理子任务编排和检查点持久化,用 Agent 负责每个子任务内部的智能推理。工作流引擎保证可靠性,Agent 保证灵活性。

差距在哪:新手以为模型的上下文窗口是无限的——“一次生成完”在 4K token 的小任务上能凑合,但面对真实的复杂任务完全行不通。高手设计了任务分解 → 检查点 → 状态恢复的三层架构,并考虑了幂等性、粒度、一致性等生产级问题,还能类比到传统分布式任务框架。面试官考的是你对长时间运行任务的工程化设计能力——Agent 不只是”调一次 API”,而是一个需要持久化状态、支持故障恢复的分布式系统。


Q:开发 Agent 的时候,你用的是什么开发流程?为什么选这个流程?

来源:字节 Agent 开发实习一面

新手答:“想到什么做什么,边做边调。”

高手答:

Agent 开发和传统软件开发有本质区别——行为是概率性的、测试更难、需求往往要通过实验才能明确。所以不能用传统的“写需求→写代码→上线”流程,需要一套以评测驱动为核心的开发方法论。

六阶段 Agent 开发流程

阶段一:需求定义 + 边界划定

定义 Agent 应该做什么,更重要的是定义它不应该做什么。边界划定是 Agent 项目成败的关键——范围无限膨胀的 Agent 项目必然失败。

阶段二:Prompt 原型

用一个简单的 Prompt + 手动工具描述快速验证核心假设:模型能不能理解这个任务?这个阶段只需要几个小时,不要花几天搭架构。

阶段三:工具集成 + 编排

接入真实工具(MCP/原生函数调用),搭建执行循环。先跑通 Happy Path,不要在这个阶段处理所有边界情况。

阶段四:评测集构建

这是整个流程中最关键的一步——在优化之前构建评测集。评测集的组成:

类别 占比 作用
基础用例 60% 验证核心功能
边界用例 20% 覆盖异常输入和极端情况
对抗用例 10% 测试 Prompt 注入、恶意输入
回归用例 10% 防止优化过程中旧功能退化

没有评测集就去优化 Prompt,等于闭着眼睛调参数——不知道改好了还是改坏了。

阶段五:迭代优化

跑评测 → 找失败模式 → 修复(调整 Prompt / 增加工具 / 改善上下文) → 重新跑评测。每一轮迭代都是数据驱动的,不是凭感觉调。

阶段六:上线 + 监控

部署时加上日志记录、行为监控、高风险操作的人工审批。Agent 上线不是结束,是持续监控和迭代的开始。

为什么选这个流程?

核心理念是评测驱动开发(Eval-Driven Development),类似于传统软件的 TDD。评测集就是 Agent 开发的“测试套件”——每次改动都必须提升评测指标,否则就是退化。

Agent 开发 vs 传统软件开发

维度 传统软件开发 Agent 开发
需求定义 确定性的功能规格 概率性的行为边界
测试方式 单元测试 + 集成测试 评测集 + 人工审查
调试方式 堆栈追踪 + 日志 执行链分析 + Prompt 考古
迭代节奏 写代码 → 跑测试 → 部署 调 Prompt → 跑评测 → 观察 → 调整
发布策略 功能开关 + 回滚 灰度发布 + 人工兜底

什么做法是错的

  • 不要一上来就搭复杂的多 Agent 架构——先用单 Agent 验证核心假设
  • 不要没有评测集就反复调 Prompt——这是盲人摸象
  • 不要不测边界情况就上功能——Agent 的失败模式远比传统软件多

差距在哪:新手没有流程,想到什么做什么,遇到问题靠感觉调。高手有一套六阶段的系统化流程,核心是评测驱动开发——用数据说话而不是凭直觉。面试官考的是你有没有把 Agent 开发当成一个工程问题来系统性地解决,而不是当成”调 Prompt”的手艺活。


Q:AI 应用的前端资源缓存怎么配的?

来源:百度实习 AI 应用开发一面

新手答:”用浏览器缓存就行。”

高手答:

AI 应用的前端缓存策略和普通 Web 应用一样,核心是按资源变更频率分层——频繁变化的资源不缓存或短缓存,稳定的资源强缓存。

分层缓存策略:

资源类型 缓存策略 HTTP 头配置 原因
JS/CSS(带 hash) 强缓存一年 Cache-Control: max-age=31536000, immutable 文件名含 hash,内容变则 URL 变,无需重新验证
HTML 入口文件 协商缓存 Cache-Control: no-cache + ETag 每次请求都验证是否更新,保证用户拿到最新版本
图片/字体 强缓存 + CDN Cache-Control: max-age=2592000 静态资源很少变,CDN 边缘节点分发
API 响应 不缓存或短缓存 Cache-Control: no-store 或 max-age=60 AI 生成结果通常不可复用

关键机制:

  1. 内容哈希命名:构建工具(Vite/Webpack)给 JS/CSS 文件名加 hash(如 app.3a7f2b.js),内容变则文件名变。这样可以对带 hash 的文件设超长缓存,部署新版本时旧缓存自动失效
  2. 协商缓存:HTML 文件用 ETag(内容指纹)或 Last-Modified。浏览器每次请求时带 If-None-Match,服务器内容没变则返回 304,省带宽
  3. CDN 配置:静态资源上 CDN,配置 CDN 缓存 TTL + 部署时主动刷新 CDN 缓存
  4. Service Worker:PWA 场景下可用 Service Worker 做离线缓存,但 AI 应用通常依赖后端实时响应,离线场景有限

AI 应用的特殊点:

  • SSE/WebSocket 连接不走缓存:流式响应是实时的,缓存无意义
  • 对话历史可以缓存在客户端:用 IndexedDB 或 localStorage 存已完成的对话,减少重复请求
  • 模型响应缓存:相同 query 的模型响应可以在服务端做 KV 缓存(语义缓存),但这是后端策略,不是前端配置

差距在哪:新手只说”浏览器缓存”——太笼统。高手按资源类型分层配置,说清了强缓存 vs 协商缓存的选择逻辑、内容哈希命名的原理、以及 AI 应用的特殊缓存考量。面试官考的是你对前端工程化的基本功。


Q:AI 应用中 SSE 流式数据怎么处理?数据格式是什么?

来源:百度实习 AI 应用开发一面

新手答:”就是后端一直发数据,前端一直收。”

高手答:

SSE(Server-Sent Events)是 AI 应用做流式输出的主流方案——模型逐 token 生成,后端逐 token 推送,用户不需要等全部生成完就能看到内容。

SSE 数据格式:

SSE 是纯文本协议,每条消息由字段组成,消息之间用空行分隔:

event: message
data: {“content”: “你”, “role”: “assistant”}

event: message
data: {“content”: “好”, “role”: “assistant”}

event: done
data: [DONE]
字段 作用 示例
data: 消息体(必须),多行 data 会自动拼接 data: {“token”: “hello”}
event: 事件类型(可选),客户端按类型监听 event: tool_call
id: 消息 ID(可选),断线重连时用 id: 42
retry: 重连间隔毫秒(可选) retry: 3000

Content-Type 必须是 text/event-stream,连接是单向的(服务端 → 客户端)。

前端处理:

核心 API:EventSource(原生)或 fetch + ReadableStream(更灵活)

EventSource 方式:
  - 自动重连、自动解析 SSE 格式
  - 缺点:只支持 GET,不能带自定义 header
  
fetch 方式(主流):
  - fetch(url, {method: 'POST', body: ...})
  - 拿到 response.body(ReadableStream)
  - 用 TextDecoder 逐 chunk 解码
  - 手动按 \n\n 分割 SSE 消息
  - 优点:支持 POST、自定义 header、更灵活的错误处理

工程上要处理的边界情况:

  1. 断线重连:网络波动导致连接断开,需要用 Last-Event-ID 从断点续传,而非从头开始
  2. 消息拼接:一个 SSE chunk 可能包含不完整的消息(TCP 拆包),需要在客户端做缓冲区拼接
  3. 背压处理:模型生成速度可能超过前端渲染速度,需要控制 DOM 更新频率(requestAnimationFrame 或节流)
  4. 超时处理:长时间无数据推送时,客户端需要区分”模型还在思考”和”连接已断”
  5. 优雅关闭:用户中途取消生成时,前端 abort 请求,后端需要同步停止推理以节省算力

SSE vs WebSocket:

维度 SSE WebSocket
方向 单向(服务端→客户端) 双向
协议 HTTP 独立协议(ws://)
重连 内置自动重连 需手动实现
适用场景 LLM 流式输出、通知推送 实时聊天、协同编辑
基础设施兼容性 好(走 HTTP,CDN/代理友好) 需要额外配置代理

AI 应用中,模型输出是单向流,SSE 足够且更简单。只有需要客户端实时发送大量数据(如语音流)时才需要 WebSocket。

差距在哪:新手对 SSE 的理解停留在”后端发前端收”。高手说清了 SSE 的数据格式规范、两种前端处理方式的取舍、五个工程边界情况、以及和 WebSocket 的选型对比。面试官考的是你对流式通信的工程化理解——AI 应用开发绕不开 SSE。


扩展与排查

Q:数据量和 QPS 增大后,Agent 架构怎么改进?需要什么硬件?RT 要求高时怎么部署?

来源:阿里国际 AI 应用研发二面

新手答:“加机器,用更好的 GPU。”

高手答:

按 QPS 量级分层的架构改进

QPS 量级 架构策略 硬件配置
< 10 单机部署,vLLM/Ollama 单卡 A100/H100,或 4090 开发机
10-100 多实例 + 负载均衡,连续批处理 2-4 卡 A100/H100,CPU 用于编排层
100-1000 微服务拆分(编排/推理/检索分离),推理集群 GPU 集群 + 独立向量 DB 节点 + Redis 缓存
1000+ 模型分级(大小模型混合),全链路异步,缓存命中率优化 混合 GPU 集群(H100 给大模型 + A10 给小模型)

RT(响应时间)优化的关键手段

  1. 推理加速:量化(INT4/INT8/FP8)、推测解码、KV Cache 复用、Prefix Caching
  2. 模型分级调度:简单请求走 7B/14B 小模型(延迟 < 200ms),复杂请求走大模型
  3. 服务架构:推理和编排分离部署,编排层用 CPU 即可;推理层用 vLLM/TensorRT-LLM/SGLang,continuous batching
  4. 缓存策略:语义缓存(相似 query 命中率可达 30-50%),工具结果缓存,KV Cache 跨请求复用

GPU 选型参考

GPU 显存 适用场景 性价比
A100 80G 80G 70B 模型推理/微调,主力生产 GPU 中
H100 80G 高吞吐推理,支持 FP8 高(新项目首选)
A10/L4 24G 小模型推理(7B-14B),性价比高 高
4090 24G 开发测试,不建议生产(缺 ECC、NVLink) 开发最优

Agent 特有的扩容挑战

Agent 和普通 LLM 推理不同——单次请求可能触发 5-15 次模型调用(规划 + 多次工具调用 + 总结),所以 Agent 的 QPS 要乘以平均调用次数来估算推理集群的真实负载。

差距在哪:新手只想到“加机器加 GPU”。高手按 QPS 量级分层给出了架构策略和硬件配置,且区分了推理加速和服务架构两个优化层面,并指出 Agent 的 QPS 需要乘以平均调用次数来估算真实负载。面试官考的是你对 Agent 系统从开发到生产扩容的全链路工程认知。


Q:使用 LangGraph 开发 Agent,遇到最大的困难是什么?

来源:bilibili AI研发实习一面

新手答:“文档看不太懂。”

高手答:

LangGraph 的核心价值是把 Agent 的执行逻辑建模为有状态的图(Graph),但这也带来了几个实际开发中非常痛的问题:

困难一:状态设计是最大的架构决策

LangGraph 中所有节点共享一个 State 对象,State 的字段设计直接决定了整个 Agent 的行为。设计不好的 State 会导致:

  • 字段太少 → 节点之间信息传递不够,每个节点都要重新推理
  • 字段太多 → 状态膨胀,调试困难,且不相关的字段可能干扰模型判断
  • 类型不对 → 并发节点写同一个字段时覆盖(忘了用 Reducer)
# 常见的 State 设计错误
class BadState(TypedDict):
    messages: list  # ❌ 并发节点同时 append 会互相覆盖

class GoodState(TypedDict):
    messages: Annotated[list, operator.add]  # ✅ Reducer 自动合并

困难二:调试极其困难

传统代码用断点就能调试。LangGraph 的执行是“图遍历 + 模型推理”的混合,出了问题很难定位:

  • 不知道是模型推理错了,还是图的边条件判断错了,还是 State 传递有问题
  • 节点之间的数据流是隐式的(通过 State),不像函数调用那样有明确的参数传递
  • 解法:用 LangSmith 做执行链追踪,每个节点的输入输出都可视化;对关键节点加日志断点

困难三:并发和状态合并

LangGraph 支持 Fan-out 并行,但并发节点写回同一个 State 字段时必须用 Reducer 函数处理合并。这个机制初学时很容易忽略——结果是多个并发节点的输出互相覆盖,只剩最后一个。

困难四:图的复杂度失控

项目初期图很简单(3-5 个节点),但随着功能增加,边和条件判断快速膨胀。超过 10 个节点的图已经很难在脑子里追踪执行路径了。

  • 解法:用 subgraph 做模块化——把相关节点封装成子图,主图只编排子图之间的关系
  • 给每个条件边起有意义的名字,画出来能读懂

困难五:Checkpoint 和恢复

LangGraph 的 checkpointer 机制允许在任意节点暂停和恢复,但实际使用中 checkpoint 的序列化和反序列化经常出问题——特别是 State 中包含不可序列化的对象(如数据库连接、模型实例)时。

困难 根因 解法
状态设计难 State 是全局共享的,字段设计影响所有节点 先画数据流图,再定义 State;并发字段必须加 Reducer
调试难 执行链路是图遍历+模型推理的混合 用 LangSmith 可视化执行链;关键节点加日志
并发合并 Reducer 机制不直观 任何可能被并发写入的字段都显式定义 Reducer
复杂度失控 节点和边增长后图不可读 用 subgraph 模块化;控制单图节点数 < 10
Checkpoint 序列化 State 包含不可序列化对象 State 中只放可序列化数据,非序列化对象放工厂函数

差距在哪:新手只说”文档看不懂”——这是学习阶段的问题不是工程问题。高手点出了状态设计、调试、并发合并、复杂度控制、Checkpoint 五个实际工程难点,每个都有具体的根因和解法。面试官考的是你对 Agent 开发框架的实战理解深度——踩过的坑越具体,越说明你真正用过。


Q:AI Coding 检查错误的时间比自己写还长,怎么提效?

来源:字节实习二面

新手答:”那就不用 AI 写了,自己写更快。”

高手答:

这个”悖论”很常见,但问题不在 AI Coding 本身,而在使用方式。检查时间长,通常是因为三个原因:

1. 生成粒度太大

一次让 AI 生成整个模块(几百行),review 时完全不知道它的意图和设计决策,只能逐行检查。

解法:小步生成,逐步确认。每次只让 AI 生成一个函数或一个逻辑块(20-50 行),确认正确后再继续。Claude Code 的 plan 模式就是这个思路——先生成计划,确认后再执行。

2. 对生成结果没有结构化验证

只用肉眼 review 是最慢的方式。

解法:让机器帮你检查。配置好 TypeScript 类型检查、Lint 规则、已有测试套件,AI 生成后自动跑一遍。通过了静态检查和测试的代码,人工只需要 review 业务逻辑正确性,工作量减少 70%。

3. 上下文给得不够精确

AI 不了解项目上下文,生成的代码风格不一致、用了错误的 API、不符合项目规范。这些”小问题”累积起来,review 成本极高。

解法:投入上下文工程。把项目规范、API 文档、代码风格写进 CLAUDE.md 或 Rules;用 @file 引入相关代码作为参考。上下文给得越精确,生成质量越高,检查成本越低。

效率公式:

AI Coding 的实际效率 = 生成速度 × 首次通过率
                    = 生成速度 × f(上下文质量, 任务粒度, 自动化验证)

检查时间长说明”首次通过率”太低,根因是上下文质量差、任务粒度大、没有自动化验证——不是 AI Coding 不行,是使用姿势需要优化。

差距在哪:新手直接放弃 AI Coding——这是把工具的学习曲线当成工具的上限。高手分析了检查成本高的三个根因(粒度大、无自动化验证、上下文不足),且给出了对应的提效方案。面试官考的是你对 AI Coding 工作流有没有深入思考——不是”用没用过”,而是”怎么用才高效”。


Q:Agent 系统的缓存选型——本地缓存 vs Redis,怎么选?

来源:字节实习二面

新手答:”数据量大用 Redis,小用本地缓存。”

高手答:

数据量只是一个维度。选型的核心是根据一致性要求、访问模式和部署架构综合判断:

维度 本地缓存(Guava/Caffeine) Redis
访问延迟 纳秒级(堆内存) 毫秒级(网络 IO)
一致性 单进程内一致 跨进程/跨机器一致
容量 受 JVM 堆限制(通常 < 1GB) 独立内存,可扩展到 TB
故障影响 进程重启即丢失 持久化可恢复
适合场景 热点数据、不常变更、可容忍短暂不一致 共享状态、需要跨实例一致、数据量大

Agent 系统中的典型选型:

  • 工具 schema 缓存 → 本地缓存。schema 很少变更,每台机器缓存一份即可,不需要跨实例一致
  • 用户会话状态 → Redis。多个 Agent 实例需要读取同一个用户的会话上下文
  • LLM 响应缓存(语义缓存) → Redis。相同语义的请求可以跨用户复用
  • Token 消耗计数 → Redis。需要实时跨实例汇总,做预算熔断

二级缓存架构:

很多生产系统同时使用两者——本地缓存做 L1(减少网络 IO),Redis 做 L2(保证一致性):

请求 → L1 本地缓存(命中?) → L2 Redis(命中?) → 数据源
                ↑ 回填                    ↑ 回填

关键工程问题:

  1. 热 Key:某个 Agent 工具被高频调用,对应的 Redis Key 请求量暴涨。解法:本地缓存兜底 + Redis 读副本分散压力
  2. 大 Key:长对话的会话状态可能达到几 MB。解法:拆分存储(按轮次分 key)或压缩后存储
  3. 缓存命中率计算:命中率 = 缓存命中次数 / 总请求次数。L1 命中率低于 60% 说明缓存策略需要调整(TTL 太短或缓存容量不足)
  4. 迁移注意点:从本地缓存迁到 Redis 后,JVM 堆内存压力减小(GC 频率降低),但网络延迟增加。需要在延迟和一致性之间找平衡

差距在哪:新手只按数据量做判断——这是最表面的维度。高手从一致性、延迟、容量、故障影响四个维度对比,且给出了 Agent 场景下的具体选型建议和二级缓存架构。面试官考的是你对缓存体系的工程化理解——不只是”知道 Redis”,而是知道什么场景用什么缓存、为什么。


Q:模型离线 AUC 很高但上线后效果暴跌,怎么排查?

来源:淘宝闪购 Agent 一面(AI Coding 压轴题)

新手答:“可能是数据有问题,重新训练一下。”

高手答:

离线 AUC 92%、测试 89%、上线跌至 62%——这是工业界最经典的 Train-Serving Skew 问题。排查需要从数据、标签、Serving 三个维度系统展开,按影响幅度排序:

数据层(影响最大,优先排查)

排序 原因 影响幅度 排查方法
1 Train-Serving 特征分布偏移 10-30% AUC 下降 对比线上/线下特征分布(KL 散度、PSI)
2 线上样本分布与训练集不一致 5-20% 对比线上流量的类别分布 vs 训练集
3 特征泄露导致离线 AUC 虚高 可达 20%+ 检查是否用到了只有离线才有的“未来特征”

标签层

排序 原因 影响幅度 排查方法
4 标签定义口径不一致 5-15% 比对离线标签定义 vs 线上业务指标定义
5 Label Delay 问题 3-10% 检查标签回收窗口是否覆盖完整转化周期
6 标注噪声线上放大 3-8% 抽样人工复审标注质量

Serving 层

排序 原因 影响幅度 排查方法
7 模型版本与 Feature Schema 未对齐 可致严重错误 比对模型期望的特征列表 vs 线上实际提供的
8 在线推理精度损失/缺失填充策略不一致 1-5% 对比离线 batch 推理 vs 在线实时推理的输出

排序依据:数据层 skew 对 AUC 的影响幅度最大(可达 10-30%),且在工业界出现频率最高,因此排在前三。标签问题通常灰度期可被发现。Serving 层问题通过日志比对可快速定位,实际频率相对低。

排查方法论:

差距在哪:新手只能模糊地说“数据有问题”。高手从数据、标签、Serving 三个维度列出 8+ 具体原因,按影响幅度排序,并给出每个原因对应的排查方法。面试官考的不只是你知不知道这些原因,更是你能不能按优先级系统排查——工业界的 debug 不是靠灵感,是靠方法论。


Q:高并发场景下同时调 10 个 Embedding 接口,asyncio.gather 相比多线程有什么资源优势?

来源:阿里淘天 AI应用开发一面

新手答:“asyncio 更快。”

高手答:

“更快”不准确——asyncio 和多线程在 IO 密集场景下的完成时间几乎一样,真正的差距在于资源消耗。

核心区别:

维度 多线程(threading) 协程(asyncio)
调度层级 OS 内核态调度 用户态调度(Python 解释器内部)
内存开销 每线程 ~8MB 栈内存(默认) 每协程 ~KB 级(几百字节到几 KB)
上下文切换 内核态切换,涉及寄存器保存/恢复、TLB 刷新 用户态切换,只需保存/恢复 Python 帧对象
创建销毁成本 高(系统调用) 极低(普通 Python 对象)
GIL 影响 受 GIL 限制,同一时刻只有一个线程执行 Python 代码 单线程内运行,天然不受 GIL 影响

10 个并发 Embedding 调用的场景分析:

瓶颈在网络 IO(等 API 返回),不在 CPU 计算。IO 密集型任务是协程的最佳场景——协程在 await 时让出执行权,单线程就能管理上千并发连接。

资源对比:

多线程方案(10 个线程):
  内存:10 × 8MB = 80MB 额外栈内存
  线程池管理开销:线程创建/销毁/调度
  OS 资源:10 个内核线程对象

asyncio 方案(10 个协程):
  内存:10 × ~几KB = ~几十 KB
  无线程池,单线程 event loop 调度
  OS 资源:1 个线程

差距:内存开销差 1000 倍+

asyncio 方案实现:

import asyncio
import aiohttp

async def embed_one(session, text):
    async with session.post(EMBED_URL, json={"input": text}) as resp:
        return await resp.json()

async def embed_batch(texts):
    async with aiohttp.ClientSession() as session:
        tasks = [embed_one(session, t) for t in texts]
        results = await asyncio.gather(*tasks)
    return results

# 10 个并发请求,单线程完成
results = asyncio.run(embed_batch(texts[:10]))

asyncio 的局限——什么时候不适用:

如果 Embedding 是本地 CPU 计算(比如用 sentence-transformers 在本地跑模型),asyncio 无法并行——因为 Python 的 GIL 限制,单线程内 CPU 密集计算无法真正并行执行。这时需要 ProcessPoolExecutor:

from concurrent.futures import ProcessPoolExecutor
import asyncio

async def embed_batch_cpu(texts):
    loop = asyncio.get_event_loop()
    with ProcessPoolExecutor(max_workers=4) as pool:
        # CPU 密集任务交给进程池
        results = await loop.run_in_executor(pool, local_embed, texts)
    return results

实际工程中的混合方案:

asyncio + aiohttp      → 网络并发(调远程 Embedding API)
ProcessPoolExecutor     → CPU 密集(本地模型推理)
两者结合               → 最优资源利用

典型场景:
  10 个文本需要 Embedding
  ├─ 远程 API(OpenAI / BGE API)→ asyncio.gather + aiohttp
  └─ 本地模型(sentence-transformers)→ ProcessPoolExecutor + batch inference
场景 推荐方案 原因
远程 API 调用(IO 密集) asyncio + aiohttp 内存占用极低,单线程管理上千连接
本地模型推理(CPU 密集) ProcessPoolExecutor 绕过 GIL,真正多核并行
本地 GPU 推理 batch inference(一次送入) GPU 本身并行计算,不需要多线程/多进程
混合场景 asyncio 编排 + executor 处理 CPU 部分 各取所长

差距在哪:新手只说“更快”。高手区分了 IO 密集 vs CPU 密集场景,给出了具体的内存开销对比,且知道什么时候协程不适用。面试官考的是你对 Python 并发模型的工程理解。


Q:Agent 异步任务管线中引入消息中间件(如 Kafka),会不会反而变慢或成为瓶颈?扫表和消息驱动各有什么优缺点?

来源:淘天 Agent 开发

新手答:“Kafka 是异步的,肯定更快。”

高手答:

消息中间件不是万能提速器——引入 Kafka 后系统延迟可能反而增加,关键看任务类型:

什么时候 Kafka 反而变慢?

场景 直接调用延迟 Kafka 延迟 结论
单次简单任务(<100ms) ~50ms ~50ms + 入队 + 消费延迟 ≈ 200ms Kafka 更慢
多步串行任务 步骤间直接调用 每步都经 Kafka 中转,延迟倍增 严重更慢
高并发异步任务 线程池打满,排队阻塞 削峰填谷,稳定吞吐 Kafka 优势明显
长时任务(分钟级) 占线程等待,资源浪费 发消息后释放线程,异步回调 Kafka 优势明显

核心认知:Kafka 的价值不是「更快」,而是「更稳」——通过解耦生产者和消费者,让系统在流量突增时不崩溃。延迟可能增加几十毫秒,但换来的是吞吐稳定性和可靠性。

流量变大后 Kafka 本身会不会成为瓶颈?

会,但可控。常见的瓶颈点和解法:

瓶颈 表现 解法
分区数不足 消费速度跟不上生产速度 增加 partition 数 + 消费者组扩容
单条消息过大 序列化/网络传输耗时 消息体只传 ID/引用,大数据走对象存储
消费者处理慢 消息堆积、lag 持续增长 业务侧限流(令牌桶) + 结合业务频次限制

扫表 vs 消息驱动管理长时任务:

维度 扫表(定时轮询) 消息驱动(如 Kafka 双 Topic)
实时性 取决于扫描间隔(秒级延迟) 近实时(毫秒级)
资源开销 持续查库,空转浪费 事件驱动,无空转
可靠性 数据库是天然持久化,断电不丢 需配置持久化和消费确认
复杂度 简单直接,一条 SQL 搞定 需维护 Topic、消费者组、死信队列
适用规模 小规模(<1000 任务/分钟) 大规模(10000+ 任务/分钟)
状态可查 数据库直接查,运维友好 需额外建状态表或监控面板

生产中的最佳实践:小规模用扫表(简单可靠),中大规模用消息驱动 + 状态表(消息负责流转,数据库负责状态持久化和查询),两者结合取长补短。

差距在哪:新手认为异步就等于更快。高手区分了 Kafka 适用和不适用的场景,且给出了扫表和消息驱动在不同规模下的选型依据。面试官考的是你对异步架构 trade-off 的理解——不是「用不用」,而是「什么时候用」。


Q:用 AI Coding 工具写代码达不到预期怎么办?

来源:CVTE/AI应用工程师一面

新手答:“多试几次,换个写法描述需求,或者自己手写。”

高手答:

“达不到预期”要先定位是哪层的问题,然后针对性解决:

1. 意图传达问题(最常见)

AI 不理解你要什么。表现为:生成的代码逻辑方向完全错误。

解法:不要一句话描述需求,而是给结构化上下文——输入/输出示例、约束条件、已有代码片段、类似功能的参考实现。本质上你在做上下文工程,和给 Agent 写 Prompt 一样的方法论。

2. 知识边界问题

AI 对你用的框架/库版本不熟。表现为:API 不存在、用法过时。

解法:把相关源码、类型定义、文档片段喂给它。Claude Code 的 @file 引用、Cursor 的 @codebase 都是这个思路——扩充 AI 的工作上下文。

3. 架构一致性问题

AI 生成的代码能跑但不符合项目风格/架构。表现为:命名混乱、模式不统一。

解法:用 CLAUDE.md / .cursorrules 定义项目规范,或者用 Skill 封装项目特有的代码模式,让 AI 每次生成都对齐架构约束。

4. 复杂度超限问题

任务太复杂,超出单次生成能力。表现为:前半段对后半段错。

解法:拆任务。把一个大需求拆成 3-5 个小步骤,每步验证后再进行下一步。用 Plan 模式先对齐方案,再逐步实现。

差距在哪:新手的“多试几次”是盲目重试。高手先分类问题(意图/知识/架构/复杂度),每类有不同的解法策略。面试官考的是你对 AI 辅助开发的方法论——不是“用不用 AI”,而是“AI 不好使时怎么系统性解决”。


Q:LangGraph 的 State Snapshot(状态快照)机制是怎么实现的?解决什么问题?

来源:字节后端开发 Agent 一面

新手答:“就是保存一下当前状态,方便恢复。”

高手答:

LangGraph 的 State Snapshot 机制本质是基于 Checkpoint 的确定性状态管理——让 Agent 的执行过程可追溯、可回退、可分叉。

解决的核心问题:

Agent 执行多步任务时,中间任何一步可能失败或产生分支。没有快照机制,失败后只能从头重跑;有了快照,可以从任意中间状态恢复或分叉。

实现机制:

组件 职责
State 图执行的全局状态对象(TypedDict),包含所有节点共享的数据
Checkpointer 在每个节点执行后序列化并持久化当前 State
Thread 一次完整的图执行实例,通过 thread_id 标识
Checkpoint ID 每个快照的唯一标识,支持精确回退到任意历史状态

Checkpointer 的存储实现:

存储后端 适用场景
MemorySaver 开发调试,内存存储,进程结束丢失
SqliteSaver 单机持久化,轻量部署
PostgresSaver 生产环境,多实例共享状态

快照解决的三个工程问题:

  1. Human-in-the-loop:节点执行到需要人工确认时,保存快照暂停;人工审批后从快照恢复继续执行
  2. 错误恢复:工具调用失败时回退到上一个快照,换策略重试,而不是从头重跑整个流程
  3. 状态分叉:同一个快照可以沿不同路径继续执行(类似 Git 分支),用于 A/B 测试不同的 Agent 决策路径

和 Git 的类比:

Checkpoint ≈ Git Commit(记录某一刻的完整状态)
Thread ≈ Git Branch(一条执行路径)
回退 ≈ Git Reset(回到历史状态)
分叉 ≈ Git Branch(从某个 Commit 开出新分支)

差距在哪:新手只理解”保存状态”——这和普通变量序列化没区别。高手理解 Checkpoint 是 LangGraph 实现确定性执行、人工干预、错误恢复的基础设施,且能讲清具体的存储实现和应用场景。面试官考的是你对 Agent 框架状态管理的理解深度——不是”用过 LangGraph”,而是理解它为什么要这样设计。


Q:Agent 系统可观测性设计——怎样的结构才能更好地追踪整个 Trace?

来源:美团Agent开发(智能客服方向)二面 【懂车帝 Agent 开发一面追问:Trace、日志、指标和配置版本联合归因】

新手答:”每一步打个日志就行。”

高手答:

Agent 的可观测性和传统微服务 tracing 有本质区别——它是非确定性的。核心设计:

  1. Trace 结构:每次 Agent 调用生成唯一 trace_id,内部用 span 表示 LLM 调用、工具调用、记忆检索等步骤,形成树状结构
  2. 结构化日志:每个 span 记录输入输出摘要、token、延迟、重试、缓存命中、错误码和业务主键;敏感原文按权限脱敏或不落盘
  3. 决策依据:记录“选了哪个动作、基于哪些可公开的证据和规则”,不要依赖不可审计的原始思维链
  4. 跨 Session 关联:用 user_id + session_id + trace_id 三级关联,支持用户维度的行为分析
  5. 异常检测:设置 token 消耗异常、循环调用、工具连续失败等自动告警规则
  6. 版本指纹:在根 span 和关键子 span 记录 Prompt、模型、Tool Schema、Skill/路由规则、知识库索引和运行配置版本

定位问题时要联合四类证据:Metrics 先确认影响范围和异常时段,Trace 找到慢或错的首个 span,结构化日志还原该节点的输入、重试和错误上下文,版本指纹判断是否由发布变更引起。只看其中一种,容易把下游等待误判成模型慢,或把路由变化误判成数据波动。

日志本身也可能制造假象:把真正失败记成 INFO/DEBUG、捕获异常后返回空结果、只记录最终 fallback 成功,都会让错误率看似正常。异常被降级时仍应设置 span error status,保留原始错误分类和 fallback 链路;日志级别、指标计数与用户看到的结果要使用同一套错误语义。

差距在哪:面试官要看你是否理解 Agent 的 Trace 不是 request→response 那么简单,需要记录决策过程才能 debug。


Q:SSE 流式输出中断后如何保证之前的输出不丢失?

来源:某教育agent开发

新手答:”断了就重新生成呗。”

高手答:

SSE 流式断连是 Agent 应用的常见问题(用户关闭浏览器、网络波动等),解决方案分层:

  1. 服务端持久化:每个 SSE 事件带递增 event_id,服务端将已发送内容写入 Redis/DB(key = task_id)
  2. 断点续传:客户端重连时携带 Last-Event-ID,服务端从该位置继续推送
  3. 异步任务分离:将 LLM 生成与 SSE 推送解耦——生成结果写入消息队列,SSE 连接只负责消费和推送。即使连接断开,生成不中断
  4. 轮询兜底:如果 SSE 重连失败,降级为轮询接口拉取 task_id 对应的已生成内容
  5. 前端状态恢复:客户端本地缓存已收到的 chunk,重连后只接收增量

差距在哪:关键是”生成过程和推送过程解耦”的架构思想,不要让连接状态影响计算状态。


Q:产品的用户量、每日 token 消耗和底层模型选型怎么估算?

来源:快手AI应用开发一面

新手答:”看产品有多少用户,乘以每次对话的 token 数就行了。”

高手答:

这类问题不能只报一个数字,要展示估算方法论。核心公式:

日 token 消耗 = DAU × AI 触发率 × 平均轮数 × 每轮 token 量

以理赔场景为例:DAU 5 万,20% 触发 AI 审核/解释(1 万次 AI 会话),每次平均 3 轮,每轮输入上下文 1500 token + 输出 500 token:

1万会话 × 3轮 × (1500 + 500) = 6000万 token/天

底层模型不会只用一个,需要分层路由:

模型层 用途 选型考虑
小模型 意图识别、JSON 修复 低延迟、低成本
大模型 多证据综合、争议解释 准确性优先
Embedding 知识库向量化 维度/召回率
Rerank 重排精排 精度/延迟

延迟要求严格时还要做模型路由和降级:大模型 P95 超时后返回模板化解释或进入人工审核队列。

成本估算要覆盖:模型调用费、Embedding 索引存储、向量检索 QPS、GPU 推理实例数。线上的实际消耗 = 估算值 × 1.3(缓存未命中、重试、上下文膨胀的安全系数)。

差距在哪:新手只能报一个”大概多少 token”。高手能展示完整估算链路:DAU → 触发率 → 轮数 → token → 分层模型路由 → 降级策略 → 成本。面试官考的不是精确数字,而是你的工程化容量规划能力——能否从业务指标推导到资源需求。


Q:Agent 如何做版本管理与灰度?

来源:Agent面经八股系列

新手答:”把 Prompt 存到数据库,每次改了就更新版本号。”

高手答:

Agent 的版本管理涉及三层:Prompt/Tool Schema/Model 各自独立版本号管理。灰度方案:

  1. 影子模式(Shadow Mode):新版 Agent 并行执行但只记录建议不真实执行,对比老版结果
  2. 金丝雀发布:先对 1% 用户群开放,观察成功率、成本、违规数等关键指标
  3. 快速回滚:保留前 N 个版本快照,任何指标异常一键回滚
  4. AB 对比:同一批请求同时发给新旧版本,diff 输出差异自动告警

差距在哪:面试官要看你是否理解”Agent 不是一个函数而是一个系统”——Prompt 改了、工具改了、模型换了都算版本变更,需要联动管理。


Q:怎么设计一个大模型网关系统?

来源:猎豹移动Agent全栈开发

新手答:”做个 API 代理,转发请求到不同模型就行。”

高手答:

大模型网关是 Agent 系统的基础设施层,核心职责:

  1. 统一接口:屏蔽不同模型厂商 API 差异(OpenAI/Anthropic/本地模型),对上层提供一致的 chat/completion 接口
  2. 路由策略:根据任务类型/成本/延迟要求,智能选择模型(复杂推理→大模型,简单分类→小模型)
  3. 限流与配额:按 team/user/app 维度的 token 预算控制、QPS 限流、优先级队列
  4. 缓存层:语义缓存(相似 prompt 复用历史结果)+ 精确缓存(相同 prompt hash)
  5. 可观测性:记录每次调用的 token 消耗、延迟、模型版本、是否降级
  6. 降级与容灾:主模型超时/报错时自动 fallback 到备用模型,保证服务可用性
  7. 安全审计:敏感词过滤、PII 脱敏、Prompt 注入检测

差距在哪:面试官期望你把网关当成一个完整的中间件来设计,而不是简单的 HTTP 代理。


Q:Claude Code 用久了感觉响应越来越慢,这是什么原因?怎么解决?

来源:字节TikTok AI应用开发一面

新手答:”上下文太长了,清空一下就好。”

高手答: “用久了变慢”是上下文膨胀的典型症状,但根因不只是”上下文长”,还有几个容易被忽视的层次:

根因分析:

  1. 上下文窗口累积:每轮对话都把完整历史附加进去,随着对话轮数增加,每次 API 调用的 token 数线性增长,推理时间也随之增长(注意力机制是 O(n^2))
  2. 工具调用结果堆积:Claude Code 每次读文件、运行命令的输出都会附在上下文里,长任务后上下文中可能有几十次工具调用的完整结果
  3. Prompt Cache Miss:Anthropic 的 Prompt Caching 对 system prompt 前缀做缓存,但如果每轮消息的前缀都在变化(比如动态插入时间戳),缓存命中率下降,实际计费 token 增多
  4. 并发请求排队:高负载时段 API 端侧本身有排队延迟,跟本地上下文无关

解决方案:

  1. 主动 /clear 或分段任务:长任务拆成多个独立 session,每个 session 只传必要上下文
  2. 利用 CLAUDE.md 做持久化:把跨 session 的关键约束、项目背景写入 CLAUDE.md,而不是每次在对话里重复说明——这样可以利用 system prompt 前缀缓存
  3. 精简工具调用输出:对大文件读取结果做截断,只保留关键段落,避免整个文件内容堆在上下文里
  4. 区分模型:快速的迭代性任务(改一行代码、解释报错)用 Haiku/Sonnet,复杂规划任务才用 Opus——响应速度差异在 3-10x
  5. 本地缓存复用:重复查询相同文件的场景,可以在 session 开头一次性读取并引用,避免多次触发工具调用

差距在哪:面试官考的不只是”怎么解决变慢”,而是你对 Claude Code 运行机制的理解——上下文管理、Prompt Caching、工具调用成本。高手会把”变慢”拆解成几个独立的根因,而不是只给一个”清空上下文”的答案。


Q:高并发场景下,如何设计 Agent 服务的弹性伸缩策略?

来源:阿里淘天智能体开发

新手答:“加机器、加副本就行。”

高手答:

Agent 服务的伸缩和普通 Web 服务有本质不同——单次请求可能持续数十秒、消耗大量 token、调用多个外部工具:

  1. 请求粒度拆分:将 Agent 执行拆为“调度层”和“执行层”。调度层轻量可快速水平扩展;执行层(LLM 调用 + 工具执行)按资源消耗独立伸缩
  2. 异步队列削峰:高并发时不直接打到 LLM,而是入消息队列(Kafka/SQS),按 LLM 供应商的 RPM/TPM 限制控制消费速率
  3. 模型分级降级:流量高峰时自动将非关键任务降级到小模型(如 Haiku),保障核心任务用大模型(如 Opus)
  4. 长连接管理:Agent 的 SSE 长连接占用资源,需要独立的连接池管理和超时回收策略
  5. 成本感知伸缩:不只看 CPU/内存,还要基于 token 消耗速率和 API 配额做伸缩决策——token burn rate 超阈值时触发限流而非加机器
  6. 预热与冷启动:Agent 服务依赖模型连接池、向量数据库连接等,冷启动慢,需要保留最小实例数 + 预热机制

差距在哪:面试官要看你是否理解“Agent 的瓶颈不在 CPU 而在外部依赖(LLM API 限流、工具响应)”——传统的 HPA 按 CPU 扩容在 Agent 场景下几乎无效。


Q:如何设计 Agent 的流式输出以提升用户体验,特别是包含工具调用和多次大模型交互时?

来源:Agent开发八股合集(南京大学)

新手答:“用 SSE 把模型输出一个字一个字推给前端就行了。”

高手答:

简单的 LLM 流式输出只需要 SSE token-by-token 推送,但 Agent 场景复杂得多——一次用户请求可能包含多次模型调用、工具执行、结果整合,用户等待时间远超纯生成场景。UX 设计的核心原则是:让用户始终知道系统在做什么,而不是面对空白等待。

Agent 流式输出的分层设计:

具体工程实现:

  1. 事件类型分层:SSE 不能只有 data: 一种类型,需要区分:
    • thinking:模型推理中间过程(可选展示)
    • tool_call:工具调用开始,附带工具名和参数摘要
    • tool_result:工具返回结果摘要
    • content:最终回答的流式 token
    • status:状态更新(“正在搜索”、“已找到 3 条结果”)
  2. 工具调用期间的“占位体验”:
    • 工具执行时前端展示进度指示器 + 当前步骤文案
    • 并行工具调用时展示多个进度条,完成一个标绿一个
    • 工具超时时给用户可操作的提示(“查询耗时较长,是否继续等待?”)
  3. 部分结果渐进展示:
    • 多步任务不必等全部完成才输出——检索到的中间结果可以先展示为“草稿”,最终整合后替换
    • 类似 Deep Research 的“研究进展”面板:实时展示已完成步骤和发现
  4. 异常与中断处理:
    • 工具失败时立即推送错误状态,不让用户干等
    • 支持用户中途取消(发送 cancel 事件),后端优雅中止当前工具链
    • SSE 连接断开后支持断点续传(通过 event ID 重连恢复)
  5. 延迟隐藏技巧:
    • 模型还在 plan 时就开始展示“理解您的需求:xxx”
    • 第一个工具调用结果返回后立即开始生成部分回答,不等全部工具完成
    • 预测性 UI:根据 plan 预先渲染结果骨架(skeleton),数据到了填充

差距在哪:面试官考的是你对“Agent 不是 ChatBot”的 UX 理解。纯 LLM 只需 token 流,Agent 需要多阶段状态反馈、并行任务进度、异常即时通知。能设计出分层事件协议 + 渐进展示策略,说明你做过面向用户的 Agent 产品而非只写后端。


流式返回中的非文本事件

Q:流式返回时,如何插入非文本事件(工具调用标记、思考过程、错误提示、分段标识),且不影响前端渲染?

来源:牛客Agent面经汇总(含答题思路)

新手答:“把工具调用信息也当文本发回去就行了。”

高手答:

核心思路是结构化事件流——不是所有内容都是文本,SSE 的 event 字段天然支持事件分类。

协议设计:

event: text
data: {"content": "让我帮你查一下..."}

event: tool_call
data: {"tool": "search", "params": {"query": "xxx"}, "status": "start"}

event: thinking
data: {"content": "用户想要对比两个方案..."}

event: tool_result
data: {"tool": "search", "status": "done", "summary": "找到3条结果"}

event: text
data: {"content": "根据搜索结果,..."}

event: error
data: {"code": "TOOL_TIMEOUT", "message": "搜索服务超时,已使用缓存结果"}

前端渲染策略:

事件类型 渲染方式
text 追加到主回答区,支持 Markdown 流式渲染
thinking 折叠区域,默认收起,用户可展开
tool_call 状态卡片(加载动画 → 完成打勾)
error 内联 toast 或黄色提示条,不打断主流
separator 分段线,标记逻辑段落边界

工程细节:

  1. 每个事件带序号(id 字段):断线重连时从上次 ID 续传
  2. 工具调用用 start/done 配对:前端据此展示“正在调用 XX”的 loading 态
  3. 文本和非文本事件交错:前端维护一个渲染队列,按序号严格顺序渲染
  4. 错误事件分级:warning 级不中断流,error 级中断当前段落

差距在哪:面试官考的是“你做过面向用户的 Agent 产品吗”。只会拼接文本字符串说明你没处理过工具调用的中间态展示。能设计出分层事件协议 + 前端分类渲染,说明你理解 Agent 和纯 ChatBot 在交互设计上的本质区别。


SSE、WebSocket 与单次调用

Q:SSE 和 WebSocket、单次调用的区别是什么?Agent 场景该怎么选?

来源:成都agent面试(社招)

新手答:“WebSocket 双向通信,SSE 单向推送,单次就是普通 HTTP。”

高手答:

三种方式的核心差异在于通信模型和适用场景:

维度 单次调用 SSE WebSocket
方向 请求-响应,一次性 服务端→客户端单向流 双向全双工
连接 短连接,用完即断 长连接,服务端持续推送 长连接,双方随时发
协议 HTTP HTTP(text/event-stream) 独立协议(ws://)
断线恢复 无需(每次独立) 内建 Last-Event-ID 续传 需自行实现
适用 同步工具调用、简单 QA 流式生成、Agent 执行过程 实时协作、多人编辑

Agent 场景选型:

  1. 纯问答/单步工具调用:单次 HTTP 即可。延迟低、实现简单、无状态
  2. 流式生成 + 工具调用进度:SSE 最优。90% 的 Agent 产品用这个——模型 token 流式输出 + 工具状态推送,前端只需要“被动接收”
  3. 人机协作/多 Agent 对话:WebSocket。用户需要在 Agent 执行中途插话、发送取消指令、上传追加文件

为什么大多数 Agent 产品选 SSE 而非 WebSocket?

  • Agent 交互本质是“用户发一条 → 系统执行一大段”,单向流足够
  • SSE 基于 HTTP,天然兼容 CDN、负载均衡、API 网关,运维成本低
  • WebSocket 需要维护长连接状态,服务端扩缩容更复杂

差距在哪:面试官考的不是定义背诵,而是“你在做 Agent 产品时选了什么、为什么”。能说出 SSE 在 Agent 场景的优势(HTTP 兼容 + 内建断线续传 + 单向够用)以及 WebSocket 的使用边界(需要客户端主动发消息时),说明你做过技术选型。


AgentState vs 全局变量

Q:AgentState 的作用是什么?为什么不使用全局变量?

来源:字节Agent开发一面(某大厂)

新手答:“用全局变量也能存状态吧,AgentState 就是框架的封装。”

高手答:

AgentState 解决的核心问题是状态的可追踪、可回溯、可序列化——这三点是全局变量做不到的。

全局变量的致命问题:

  1. 不可追踪:不知道谁在什么时候改了什么值,调试多步 Agent 像猜谜
  2. 不可回溯:Agent 执行到第 5 步发现走错了,没法回到第 3 步的状态
  3. 不可序列化:没法做 checkpoint 持久化,进程挂了状态全丢
  4. 并发不安全:多个节点并行执行时,全局变量竞争写入导致数据覆盖
  5. 默认覆盖语义:Python dict 赋值是覆盖,但 Agent 很多场景需要累加(如 messages 列表追加)

AgentState 的设计价值:

能力 实现
状态版本化 每个节点执行后生成新版本的 State snapshot
Checkpoint 序列化到 SQLite/Redis,支持断点续跑
Reducer 语义 字段可以定义 add(累加)而非 replace(覆盖)
类型安全 TypedDict 定义 schema,节点间契约明确
调试追踪 每步状态变化有记录,出错时精确定位哪个节点改坏了什么

LangGraph 的经典踩坑:默认 State 是覆盖式更新。如果你在 State 里放了一个 messages: list,节点 A 返回 {"messages": [msg1]},节点 B 返回 {"messages": [msg2]}——结果是 B 覆盖 A,而不是追加。必须用 Annotated[list, operator.add] 声明累加语义。

差距在哪:面试官考的是你对“状态管理”的工程理解。只把 AgentState 当成“框架的 dict 封装”说明你没用它做过多步任务。能说出 checkpoint、reducer 语义、状态回溯这些设计意图,说明你真的在 LangGraph 上踩过坑。


基础工程能力


Q:从原始诉求到可执行 PRD/Spec,谁负责清洗?如何判断需求完备,质量门禁放在哪层?

来源:电商库存一面 / 小得盈满一面

新手答:“产品经理把需求写清楚,研发发现缺信息再问,最后让 Agent 按 PRD 开发。”

高手答:

需求清洗不是把一段口语润色成文档,而是把业务意图变成可验证的执行契约。业务 Owner/产品经理对目标和语义负责,需求接入层可以用 Agent 做抽取、归一化和缺口提示,架构或研发 Owner 判断技术可执行性;模型只能辅助整理,不能替人确认业务事实与风险。

结构化输入契约至少包含:目标与成功指标、目标用户和场景、范围内/范围外、业务事实与数据来源、前置条件、权限与依赖、接口和数据约束、明确的验收案例、性能/可用性/安全/隐私等非功能要求、风险与回滚方案,以及负责人和时间约束。每个关键结论要能回指原始诉求、会议记录或确认人。

判断完备可用 Definition of Ready:关键字段齐全;同一术语、优先级和约束之间没有未解决冲突;关键假设已标注;异常、边界和失败路径有处理方式;验收标准能写成测试或人工检查项;高风险操作有权限、审批和回滚。缺信息时输出“已知、未知、冲突、需谁确认”,而不是让 Agent 自行补全。

质量门禁应分层放置:

  1. 接入层:schema、必填项、术语和来源校验,拦截结构性缺失。
  2. 规划前:做冲突、依赖、可行性、风险和非功能要求检查;未达到 Ready 状态不进入执行计划。
  3. 执行前:对写库、发版、付费、外发信息等不可逆或高风险动作设置人工批准,并绑定批准范围。
  4. 交付前:按验收案例、非功能指标和回滚演练做发布门禁。

PRD/Spec 必须版本化和可追溯:记录需求 ID、版本、变更原因、来源、Owner、审批人、验收用例及关联任务;每次规划和执行都绑定具体 Spec 版本。需求变化后生成 diff 并重新评估受影响的计划与测试,不能静默覆盖旧文档后继续跑。

差距在哪:新手把需求质量寄托在“有人写清楚”。高手明确语义责任人、结构化契约、完备性判据、分层门禁和版本追溯,让不完整需求在执行前暴露,并把高风险决策留给人。


Q:如何记录 Agent 的非确定性边界,实现可重复的故障回放?

来源:腾讯互娱全栈开发(AI)二面(2026-08-13)【字节火山引擎 Managed Agent 一面追问:记录耗时、Token、结果和失败路线】

新手答:“保存完整日志,回放时设置相同 temperature。”

高手答:确定性回放不要求模型再次生成相同文本,而是记录每个非确定性边界的请求哈希和结果:模型响应、检索快照、工具返回、随机种子、时间、配置和运行时版本。每轮还应写入 run_id/step_id/parent_step_id、开始结束时间、输入输出 Token、费用、状态、失败类型和下一条路线,让系统能重建时间线、成本和分支因果关系。回放默认注入历史结果,只重跑指定节点;有副作用的工具使用模拟器或只读影子环境。若请求哈希变化,应明确标记无法复用,而不是静默加载旧结果。

差距在哪:新手依赖随机种子,高手把外部世界和模型输出都变成可替换的事件记录。


Q:系统里多租户隔离是怎么实现的?

来源:视频面经汇总

新手答:“每个租户一个数据库。”

高手答:

多租户隔离的方案取决于隔离强度和运维成本的权衡:

方案 隔离强度 成本 适用场景
独立数据库 最强 最高 金融/医疗等强合规
共享库+独立 Schema 中等 中等 SaaS 中型客户
共享表+tenant_id 字段 最弱 最低 通用 SaaS 平台

在 Agent/RAG 系统中,我们用的是逻辑隔离方案:

  1. 数据层:向量数据库用 collection_id 或 metadata 中的 tenant_id 做过滤,检索时强制注入 tenant 条件,SQL 层面 WHERE 子句必加
  2. 模型层:System Prompt 注入租户专属规则和知识范围边界,不同租户可配置不同的 Skill 集合和工具权限
  3. 接口层:API Gateway 做认证 + 租户路由,token 解析后注入 tenant context 到请求上下文,下游服务无感
  4. 存储层:向量库索引按租户分 partition(Milvus 的 partition_key),文件存储用 /{tenant_id}/ 前缀隔离

防泄漏关键点:

  • 检索时 tenant_id 过滤放在向量数据库的 filter 条件中(非应用层过滤),避免先召回再过滤导致的信息泄漏
  • 日志脱敏:跨租户的 trace 信息不能出现其他租户数据
  • 压测:专门做跨租户渗透测试(tenant A 是否能检索到 tenant B 的文档)

差距在哪:新手只想到“分库”——这是最暴力也最贵的方案。高手按数据层/模型层/接口层/存储层分层设计,说明理解了 Agent 系统中多租户不只是数据库的问题,还涉及向量检索、Prompt 注入和权限管控。


Q:了解 Kubernetes 吗?在 Agent 项目里有没有实际用到?

来源:视频面经汇总

新手答:“了解一些,用过 Docker 部署。”

高手答:

K8s 在 Agent 项目中主要解决三个问题:

1. 弹性伸缩:Agent 请求是典型的“长连接 + 高 token 消耗”——一个复杂任务可能执行 30s+。用 HPA 基于自定义指标(如当前活跃 Agent 会话数)做 Pod 扩缩容,而不是简单的 CPU/内存。

2. 服务编排:一个完整 Agent 系统拆成多个服务——Gateway、Planner、Tool Executor、RAG Service、Memory Service。K8s 的 Service + Deployment 天然适合管理这种微服务拓扑,配合 ConfigMap 做各环境配置隔离。

3. GPU 调度:如果自部署 Embedding 或 Rerank 模型,K8s 的 nvidia.com/gpu 资源类型 + Node Affinity 可以精确调度 GPU Pod,避免抢占。

实际使用中的踩坑:

  • Agent 长任务 Pod 被 HPA 缩容杀掉 → 设置 terminationGracePeriodSeconds + 任务 checkpoint,被 kill 前保存状态
  • SSE 长连接通过 Ingress 时被 nginx 超时断开 → 调整 proxy_read_timeout 到 300s+
  • 向量数据库(Milvus)的 StatefulSet 扩容后需要 rebalance segment → 用 CronJob 定期触发

差距在哪:面试官不是要考你 K8s 八股,而是看你有没有在 AI 应用场景下用过。能说出 Agent 长任务的优雅终止、SSE 超时配置、GPU 调度,说明你真正在生产环境部署过 Agent 系统。


Q:进程、线程、协程有什么区别?什么场景下协程更有优势?

来源:视频面经汇总

新手答:“进程是资源分配单位,线程是执行单位,协程更轻量。”

高手答:

维度 进程 线程 协程
调度者 OS 内核 OS 内核 用户态(runtime)
切换成本 ~ms 级(TLB 刷新) ~μs 级(寄存器保存) ~ns 级(仅保存少量寄存器)
内存开销 MB 级(独立地址空间) ~1MB(默认栈大小) ~KB(可按需增长)
并行能力 真并行(多核) 真并行(多核) 并发但不并行(单线程内)
通信方式 IPC(管道/共享内存/Socket) 共享内存+锁 直接 yield/resume

协程的优势场景——Agent 系统中的 IO 密集任务:

Agent 系统的典型操作都是 IO 密集型:调大模型 API(网络 IO)、查向量数据库(网络 IO)、读文件(磁盘 IO)。这些操作等待时间远大于计算时间,用线程会导致大量线程阻塞在 IO 上白白占内存。

协程的解法:asyncio.gather 同时发起 10 个 Embedding 请求,底层只用 1 个线程,IO 等待时自动切换到其他协程执行——并发量上去了,内存开销几乎不变。

协程不适合的场景:CPU 密集计算(如大规模矩阵运算)——协程无法利用多核,此时需要多进程。

差距在哪:新手背定义但不理解本质区别。高手从调度者、切换成本、并行能力三个维度做结构化对比,并能绑定到 Agent 系统中“调 API 是 IO 密集型”这个具体场景,解释为什么 Python asyncio 是 Agent 开发的主流并发模型。


Q:子 Agent 和工具调用的 Token 用量统计缺失,怎么做容错补偿?(用户断连、子 Agent 延迟退出场景)

来源:深信服 AI 全栈开发二面(开源 Agent 项目贡献)

新手答:“每次调用完记一下 token 数就行了。”

高手答:

Token 用量统计看似简单,但在多 Agent 架构中,有几个场景会导致统计缺失:

问题场景分析:

容错补偿的三层设计:

第一层:预扣费(Pessimistic Accounting)

在子 Agent 启动前,基于历史数据预估本次任务的 token 消耗上限,先从用户配额中预扣。任务正常完成后,用实际消耗替换预扣值;异常退出时,预扣值作为近似统计保留。

预扣策略:
  子 Agent 启动 → 预扣 estimated_max_tokens(基于任务类型 P95 消耗)
  正常完成 → 预扣释放,记录实际值
  异常退出 → 预扣值按衰减系数保留(如 0.7 × estimated_max)

第二层:异步上报 + 本地缓冲

子 Agent 和工具调用的 token 消耗不依赖同步回传,而是写入本地缓冲队列,异步批量上报:

  1. 本地 WAL(Write-Ahead Log):每次模型调用完成后,先写本地日志文件(追加写,不丢失),再异步上报到统计服务
  2. 重试队列:上报失败的记录进入重试队列,指数退避重试
  3. 进程退出前 flush:子 Agent 收到终止信号(SIGTERM)时,先 flush 缓冲区再退出。设置 graceful shutdown 超时(如 5s)

第三层:事后对账(Reconciliation)

对于极端情况(进程被 kill -9、机器宕机),用定期对账补偿:

对账方式 实现 适用场景
模型供应商账单核对 每小时拉取 API 使用量,和本地统计做 diff 使用云端 API 时
请求日志反推 从网关/负载均衡器的请求日志中提取 token 信息 自部署推理服务
估算补偿 按“同类任务的平均 token 消耗”补充缺失记录 日志完全丢失时的兜底

用户断连场景的特殊处理:

用户断连后,子 Agent 可能还在运行(尤其是异步任务)。这时需要:

  1. 子 Agent 的 token 统计独立于用户连接状态——即使用户断了,统计仍然正常记录
  2. 任务级别的 token 预算熔断——用户断连后,子 Agent 继续消耗 token 超过预算上限时自动终止
  3. 断连恢复后,客户端拉取任务级别的 token 汇总,补齐前端展示

差距在哪:新手只想到“调用完记一下”——这是理想路径。高手考虑了用户断连、子 Agent 延迟退出、网络丢失三类异常场景,用预扣费 + 异步缓冲 + 事后对账三层容错保证统计完整性。面试官考的是你对分布式系统中“数据完整性”问题的工程化思考——token 统计本质上是一个分布式计数器的可靠性问题。


Q:多个子 Agent 延迟退出,同时更新同一对话的 Token 统计数据,线程竞争怎么解决?

来源:深信服 AI 全栈开发二面

新手答:“加个锁就行了。”

高手答:

“加锁”方向对,但具体怎么加、在哪个层面加、锁的粒度是什么——这些细节决定了方案是否适用于生产环境。

问题本质:

多个子 Agent 并行执行,各自消耗 token,最终需要汇总到同一个 session 的 token 统计字段。如果多个子 Agent 同时完成并尝试更新同一条记录:

子 Agent A 读取当前 total_tokens = 1000
子 Agent B 读取当前 total_tokens = 1000
子 Agent A 写入 total_tokens = 1000 + 500 = 1500
子 Agent B 写入 total_tokens = 1000 + 300 = 1300  ← 覆盖了 A 的更新!

经典的“读-改-写”竞态条件(Lost Update)。

四种解决方案对比:

方案 实现 优点 缺点 适用场景
原子增量操作 INCR / UPDATE SET x = x + delta 最简单、性能最好 只适合简单累加 token 计数(首选)
分布式锁 Redis SETNX + 过期时间 强一致 锁等待增加延迟 需要读-改-写的复杂更新
乐观锁(CAS) 带版本号更新,失败重试 无锁等待 高并发时重试多 中等竞争场景
归并队列 各子 Agent 写消息队列,单消费者汇总 完全无竞争 延迟略高 高并发 + 复杂统计

最佳方案:原子增量操作

对于 token 统计这种“只需要累加”的场景,根本不需要锁——用数据库的原子自增操作:

Redis 方案:
  HINCRBY session:{id}:tokens input_tokens 500
  HINCRBY session:{id}:tokens output_tokens 300
  → 原子操作,无竞态,O(1)

SQL 方案:
  UPDATE session_stats 
  SET total_tokens = total_tokens + 500,
      updated_at = NOW()
  WHERE session_id = 'xxx';
  → 数据库内部保证原子性

为什么不用分布式锁:

分布式锁(如 Redis SETNX)虽然能解决问题,但引入了不必要的复杂度——锁超时设多少?获取锁失败怎么重试?死锁怎么处理?对于简单的计数累加,原子操作是最优解。

需要锁的场景——复杂统计:

如果统计逻辑不只是累加(比如“如果总 token 超过预算则拒绝,否则累加”),就需要原子的读-判断-写操作:

Redis Lua 脚本(原子执行):
  local current = tonumber(redis.call('HGET', key, 'total'))
  if current + delta > budget then
    return -1  -- 超预算,拒绝
  end
  redis.call('HINCRBY', key, 'total', delta)
  return current + delta

Lua 脚本在 Redis 中原子执行,不需要外部锁。

归并队列方案——高并发场景:

当并发子 Agent 数量极大(如 50+ 并发),且统计逻辑复杂时,用消息队列解耦:

各子 Agent 只负责发消息(无阻塞),单消费者串行处理所有更新——彻底消除竞争,代价是统计延迟增加几十毫秒。

差距在哪:新手只说“加锁”——这是最暴力且通常不是最优的方案。高手根据业务特征(简单累加 vs 复杂判断)选择不同方案:简单累加用原子操作(零锁开销),复杂逻辑用 Lua 脚本或归并队列。面试官考的是你对并发控制的方案选型能力——不是“会不会用锁”,而是“什么时候不需要锁”。


Q:Agent 框架如何实现流式并行?了解 Claude Code 的流式并行是怎么做的吗?

来源:广州某小厂 Agent 后端开发二面

新手答:“用多线程同时跑多个 Agent 就行了。”

高手答:

流式并行(Streaming Parallelism)是指 Agent 在执行多步任务时,边流式输出边并行执行子任务——用户不需要等所有子任务完成才看到结果,而是每完成一个就实时推送一部分。

和普通并行的区别:

维度 普通并行 流式并行
用户体验 等所有任务完成才返回 每完成一个子任务就推送部分结果
输出模式 一次性返回完整结果 流式递增返回
适用场景 后台批处理 面向用户的实时交互
难点 并发控制 并发控制 + 流式输出顺序 + 部分结果合并

流式并行的核心架构:

实现的三个关键组件:

1. 子任务并行调度器

用 asyncio.gather 或类似机制同时启动多个子 Agent,每个子 Agent 独立执行并产出流式输出:

async def parallel_execute(subtasks):
    streams = [spawn_sub_agent(task) for task in subtasks]
    async for event in merge_streams(streams):
        yield event  # 实时推送每个子 Agent 的输出片段

2. 流式合并器(Stream Multiplexer)

多个子 Agent 同时产出 token 流,需要一个合并器决定“先推送谁的输出”:

  • 按完成顺序:谁先产出就先推送谁(最低延迟,但输出顺序不固定)
  • 按逻辑顺序:按子任务的编号顺序推送(有序但可能有等待)
  • 分区域推送:前端为每个子任务分配独立的渲染区域,各自独立流式显示(并行但有序)

3. 部分结果与最终合并

子 Agent 各自完成后,主 Agent 可能需要基于所有子结果做最终合成(如总结、去重)。这时有两种策略:

  • 增量合成:每个子结果到达后立即更新总结(延迟低但质量可能变)
  • 两阶段输出:先流式推送各子结果,全部完成后再推送一个“综合总结”

Claude Code 的流式并行实现思路:

Claude Code 的 Agent 工具支持并行启动多个子 Agent(run_in_background),每个子 Agent 独立执行并产出结果。其流式并行的设计哲学:

  1. 主 Agent 不阻塞:启动子 Agent 后立即继续处理其他任务或响应用户
  2. 子 Agent 独立上下文:每个子 Agent 有自己的上下文窗口,互不干扰
  3. 结果异步汇总:子 Agent 完成后通知主 Agent,主 Agent 按需合并结果
  4. 隔离性:子 Agent 可以在独立的 worktree 中工作,避免文件系统冲突

工程实现的难点:

难点 解决方案
子 Agent 输出交错导致用户困惑 前端按子任务分区渲染,每个区域独立流式
某个子 Agent 卡住拖慢整体 设置单任务超时,超时后用已有结果继续
子 Agent 之间有依赖关系 DAG 调度——无依赖的并行,有依赖的串行
流式输出中途某个子 Agent 失败 推送错误事件,其他子 Agent 继续执行
token 预算分配 总预算按子任务数均分,某个子任务省下的配额可以转移

差距在哪:新手把“流式并行”等同于“多线程”。高手理解这是一个涉及并行调度、流式合并、部分结果推送的完整架构问题,且能结合 Claude Code 的实际设计说明“非阻塞启动 + 独立上下文 + 异步汇总”的实现思路。面试官考的是你对 Agent 系统中“并行 + 流式”这两个维度交叉时的工程化设计能力。


Q:LangGraph 图状态机里,怎么捕获每个节点的执行结果并实时推前端?

来源:淘天/AI Agent一面

新手答:“用回调函数。”

高手答:

问题本质:LangGraph 的节点是异步执行的,但前端需要实时看到“当前执行到哪一步”和“每步结果”。解决方案分三层:

第一层:LangGraph 层——流式获取节点 state diff

使用 stream_mode="updates" 流式获取每个节点的状态变更,每个节点执行完会 yield 一个 {node_name: state_update} 对象:

async for event in graph.astream(input, config, stream_mode="updates"):
    node_name = list(event.keys())[0]
    state_update = event[node_name]
    # 每个节点完成时都会产出一个事件

第二层:后端层(FastAPI + SSE)——包装为事件流

将 LangGraph 的 async generator 包装为 SSE EventSource,每个节点完成时发送一个 event:

event: node_end
data: {"node": "search_node", "result": "找到5条相关文档", "timestamp": "..."}

event: node_start
data: {"node": "generate_node", "status": "正在生成回答..."}

event: token_stream
data: {"content": "根据检索结果,..."}

第三层:前端层——按事件类型更新 UI

监听 SSE 事件流,按 node_name 更新 UI 组件状态:

"正在检索..." → "检索完成,找到5条" → "正在生成回答..." → 流式文本输出

关键细节:

事件类型 用途 处理方式
node_start 节点开始执行 前端展示 loading 动画
node_end 节点执行完成 前端展示节点结果摘要
tool_call 工具调用中间结果 展示搜索结果等中间信息
token_stream LLM 逐 token 输出 流式文本渲染
error 节点执行异常 展示“该步骤失败,正在重试”

完整架构:

对比 LangChain 的 callback 方案:callback 是同步拦截机制,不适合异步图执行;LangGraph 的 stream 是原生支持的一等公民,直接 yield 节点级别的状态变更,更适合图状态机的执行模型。

差距在哪:新手只知道 callback 这一种方式。高手给出了 LangGraph 层(stream_mode)+ 后端层(SSE)+ 前端层(事件驱动 UI)的三层方案,且区分了五种事件类型的不同处理方式。面试官考的是对 LangGraph streaming API 和前后端实时通信的实战经验——不是理论概念,而是你真的用 LangGraph 做过面向用户的产品。


Q:多模型如何动态路由?根据视频特征、任务特征、成本、延迟和效果选模型?

来源:商汤/大模型算法应用实习二面

新手答:“根据任务类型写 if-else 选模型。”

高手答:

动态路由本质是一个在线决策问题,需要平衡多个目标。if-else 只能处理最简单的情况,真正的路由系统要做到多维度决策 + 数据驱动调整。

路由决策维度:

维度 信号 权重策略
任务复杂度 token数、步骤数预估 复杂任务→大模型
延迟要求 用户场景SLA 实时→小模型/规则
成本约束 月度budget剩余 预算紧→小模型优先
质量要求 任务类型(创意/事实/代码) 高精度→大模型
历史表现 该类任务各模型的成功率 数据驱动选型

三层实现方案:

第一层:规则层(快速初筛)

用硬性特征规则排除不合格的候选模型:

  • 视频 >1080p 且 >5min → 只保留支持长视频的模型
  • 需要代码生成 → 排除纯文本模型
  • 延迟要求 <500ms → 排除大模型

第二层:打分层(精确选型)

对候选模型按综合分数排序:

score = α × quality + β × (1/latency) + γ × (1/cost)

其中:
  quality = 该模型在该任务类型上的历史成功率
  latency = 该模型的 P95 响应时间
  cost = 该模型的千 token 单价
  α, β, γ = 根据场景动态调整的权重

第三层:反馈层(持续优化)

用 Bandit 算法(如 UCB/Thompson Sampling)根据实际效果动态调整权重——不需要离线训练,在线即可学习“哪个模型在哪类任务上表现更好”。

故障兜底:首选模型超时 → 自动 fallback 到次选模型,不中断用户体验。实现方式:并发发起请求但只用首选结果,超时后切换到次选的已有结果。

关键洞察:路由本身不能太耗时——路由决策应在 <10ms 完成,否则还不如直接用默认模型。所以规则层和打分层都是纯内存计算,不涉及网络调用。

差距在哪:新手的 if-else 只能处理静态规则,无法适应模型表现的动态变化。高手设计了规则层(快速排除)+ 打分层(精确选型)+ 反馈层(持续学习)的三层架构,且强调路由决策本身的延迟约束。面试官考的是对“模型即资源”的调度思维——像调度微服务一样调度模型,需要考虑成本、延迟、质量的多目标优化。


Q:Redis 在 Agent 系统中适合承担哪些职责,哪些数据不应只放 Redis?

来源:点点互动/Agent开发秋招一面

新手答:“Redis 可以缓存对话和工具结果,提高速度。”

高手答:

Redis 适合高频、短生命周期、允许重建的数据:会话热状态、短期记忆窗口、工具结果缓存、幂等键、限流计数器、分布式锁、任务队列和流式事件。设计时必须为 key 加租户与会话命名空间,设置 TTL,并明确缓存穿透、击穿和热 key 的保护策略。

不应只放 Redis 的数据包括:审计日志、长期用户事实、不可丢的工作流 checkpoint、训练反馈和计费记录。这些需要持久数据库或对象存储作为事实源,Redis 只做热缓存或加速层。否则淘汰、过期、故障切换都可能造成状态不可恢复。

典型分层是:Redis(热状态/协调) + PostgreSQL(事实与事务) + 向量库(语义记忆) + 对象存储(大结果)。关键不是“用了 Redis”,而是每类状态的持久性、一致性和恢复目标是否匹配。

差距在哪:新手把 Redis 当万能高速数据库,高手能按生命周期、一致性和可恢复性划分职责。面试官考的是 Agent 状态工程,而不是 Redis 数据类型背诵。


Q:什么是死锁?死锁产生的条件、检测和解决方法是什么?

来源:小红书 Agent 岗一面

新手答:“两个线程互相等锁,所以程序卡住了。”

高手答:

死锁是多个执行单元永久等待彼此持有的资源,导致都无法继续。经典四个必要条件是:互斥、占有且等待、不可抢占、循环等待;四者同时成立才可能死锁。

工程上有四类处理方法:

  1. 预防:统一锁顺序,破坏循环等待;一次申请全部资源,破坏占有且等待。
  2. 避免:已知最大资源需求时用银行家算法,只进入安全状态。
  3. 检测与恢复:构建等待图检测环;发现后超时回滚、取消任务或重启受影响执行单元。
  4. 降低风险:缩小临界区、使用超时锁和 try/finally、避免持锁做网络 I/O,能用消息传递或不可变数据时少共享锁。

在 Agent 系统中也会出现逻辑死锁:主 Agent 等子 Agent,子 Agent 又等待主 Agent 提供信息;两个任务分别持有文件锁和数据库锁并交叉申请。除了线程 dump,还要用 task_id、锁持有者、等待边和超时事件构建可观测的等待图。

差距在哪:新手只描述现象;高手能说出四条件、破坏哪个条件,以及如何检测和恢复。


Q:编译器从源代码到可执行程序经历哪些阶段?

来源:小红书 Agent 岗一面

新手答:“编译原理就是把高级语言翻译成机器语言。”

高手答:

编译原理研究的是如何把源语言程序转换成目标程序,并在转换过程中保证语义、发现错误和进行优化。典型编译管线包括:

  1. 词法分析:字符流变成 Token,如关键字、标识符、数字和运算符。
  2. 语法分析:根据文法把 Token 组织成 AST,并报告结构错误。
  3. 语义分析:做作用域、名称绑定、类型检查和控制流合法性检查。
  4. 中间代码生成:把 AST 降低成便于分析和跨平台处理的 IR,如 SSA。
  5. 优化:常量折叠、死代码删除、公共子表达式消除、循环优化等;必须保持可观察语义。
  6. 目标代码生成:指令选择、寄存器分配和指令调度,生成汇编或目标文件。
  7. 汇编与链接:生成机器码,解析符号和重定位,组合库得到可执行文件。

解释器、JIT 和 AOT 不是完全割裂的三套理论:都可能复用前端和 IR,只是代码生成时机与运行时参与程度不同。

差距在哪:高手不止背阶段名称,还能说明每层输入输出、错误类型以及 IR 为什么是前后端解耦的关键。


Q:在浏览器输入一个 URL 到页面显示,完整经历了哪些过程?

来源:小红书 Agent 岗一面

新手答:“DNS 找到服务器,HTTP 请求页面,然后浏览器渲染。”

高手答:

可以沿着“解析地址—建立连接—服务端处理—浏览器渲染”讲完整链路:

  1. 浏览器解析 URL,检查 HSTS、Service Worker、HTTP 缓存和本地资源策略。
  2. DNS 依次查询浏览器/系统缓存、递归解析器和权威服务器,得到 IP;CDN 可能按位置调度边缘节点。
  3. 客户端通过 ARP/NDP 找到下一跳,经路由与 NAT 发包。HTTP/1.1、HTTP/2 通常先建 TCP;HTTPS 再进行 TLS 握手、证书校验和密钥协商。HTTP/3 则基于 QUIC/UDP。
  4. 浏览器发送 HTTP 请求。请求可能经过 CDN、WAF、负载均衡、网关和反向代理,再到应用服务;服务端访问缓存或数据库后返回状态码、响应头和正文,可使用压缩、分块或流式传输。
  5. 浏览器处理缓存、重定向、Cookie 和安全头,HTML 解析为 DOM,CSS 解析为 CSSOM;预加载扫描器并行请求 CSS、JS、字体和图片。
  6. DOM 与 CSSOM 形成渲染树,随后 style、layout、paint、composite。脚本可能阻塞解析或修改 DOM,引发重新布局与重绘。
  7. 页面可见不代表完成:异步请求、懒加载、事件循环任务和性能指标仍会继续;排障时可按 DNS、连接、TLS、TTFB、资源下载和渲染阶段定位。

差距在哪:高手能说明协议分支、缓存与代理层,以及网络完成后浏览器主线程如何把字节变成像素。


Q:云端 Agent 的沙盒应该常驻还是按任务创建?如何优化启动和通信开销?

来源:顺极 Agent 开发二面(2026-08-23)

新手答:“常驻更快,按任务创建更安全,看业务选择。”

高手答:

默认采用“按任务隔离、池化复用”的混合方案:高风险或跨租户任务创建一次性沙盒,任务结束后销毁;同一租户的低风险连续任务可在短 TTL 内复用预热实例,但必须清理工作区、凭据、网络连接和进程树。不能为了冷启动把不同租户放进同一可写环境。

启动优化包括预拉镜像、分层快照、预热池和惰性挂载;通信只传结构化事件和对象存储引用,避免反复复制大文件。调度器要设置租户配额、并发预算和背压,沙盒用租约与心跳防止泄漏。验收同时看 P95 启动时间、任务成功率、隔离逃逸、空闲成本和销毁完整率。

差距在哪:新手把问题当成速度二选一,高手同时处理隔离边界、冷启动、资源池、数据清理和成本。面试官考的是能否把执行沙盒做成可运营的多租户基础设施。


Q:如何设计同时兼顾吞吐、首 Token 延迟和租户公平性的推理调度器?

来源:智象未来 AI Infra 一面(2026-08-20)

新手答:“做动态批处理,批次越大吞吐越高。”

高手答:

调度器要显式承认三个目标冲突:大批次提高 GPU 吞吐,却会增加排队和 TTFT;长生成任务持续占用 KV Cache,又可能饿死短请求。入口先按租户设置并发与 Token 配额,队列按等待时间、请求长度、SLO 和租户权重做加权公平调度;Prefill 与 Decode 可分离或使用 chunked prefill,连续批处理只吸收不会突破延迟预算的请求。

运行时持续监控队列等待、TTFT、TPOT、完成延迟、有效 Token/s、KV Cache 占用和抢占次数。内存压力下优先暂停或换出可恢复请求,而不是随机失败;过载时用背压和明确的 429/retry-after 保护系统。验收必须同时看总体吞吐和每个租户、长度分桶的 P95/P99。

差距在哪:新手只优化平均吞吐,高手把延迟预算、KV 资源、长短请求和多租户公平放进同一个调度问题。面试官考的是是否理解线上推理不是单纯的 batching。


Q:Agent 的中间与最终交付物应该如何版本化、校验和交接?

来源:福田 FDE 线下面试(2026-08-09)

新手答:“把生成文件保存到对象存储,最后给用户下载。”

高手答:每个交付物绑定 run_id、任务步骤、输入快照、模型/Prompt/Tool 版本、内容哈希、创建者和状态;草稿、已验证、已批准、已发布不可混用。结构化结果做 schema/业务校验,代码和文档运行对应测试,引用型报告保存证据映射。大文件进对象存储,状态与元数据进数据库;交接时提供产物、验证报告、未决风险和可复现命令,发布后仍保留回滚版本。

差距在哪:新手只保存文件,高手把交付物当可审计、可验收的发布制品。


Q:多模型供应商如何抽象统一 Provider,而不丢失差异能力?

来源:成都晓多科技 Agent 开发岗二面(2026-08-12)

新手答:“定义统一的 chat 和 stream 接口,再写适配器。”

高手答:先定义最小公共协议:消息、工具调用、流式事件、usage、错误分类和取消;供应商特性通过 capability negotiation 和显式扩展字段暴露,不能硬塞进最低公分母。适配器负责角色、schema、finish reason、错误码和流事件转换;网关再做路由、限流、重试、观测与版本兼容。契约测试同时验证普通文本、并行工具、截断、流式中断和安全拒绝。

差距在哪:新手统一函数签名,高手统一语义并保留能力发现与兼容测试。


Q:如何可靠采集 Coding Agent 轨迹,避免崩溃或异步退出时丢数据?

来源:MiniMax 平台研发一面(2026-08-20)

新手答:“每一步写日志,任务结束后上传。”

高手答:轨迹以 append-only 事件实时写入,事件带 run/step/event ID、因果关系和版本;本地 WAL 或持久队列先确认,再异步汇聚。模型、工具、文件 diff、测试和人工操作分别记录引用,敏感内容分级脱敏。任务结束只是写终止事件,不是唯一上传时机;采集器崩溃可按 offset 重放,服务端按 event ID 幂等去重,并监控缺口和迟到事件。

差距在哪:新手依赖正常结束,高手把轨迹采集做成可恢复的数据管道。


Q:自动回滚阈值如何设置,避免固定阈值误杀或放过回归?

来源:深信服 Agent 三面(2026-08-23)

新手答:“成功率下降超过 5% 就回滚。”

高手答:按任务、风险和流量切片建立历史基线,核心安全/副作用指标使用硬阈值,成功率、延迟、成本使用相对变化和置信区间。小流量阶段要求足够样本或序贯检验,严重事件可一票否决;同时设置告警、停止扩量和自动回滚三级动作。阈值与版本、评测集和回滚目标绑定,定期根据季节性和业务变化校准。

差距在哪:新手背固定百分比,高手处理样本量、分层风险和指标波动。


Q:如何设计类似 LangFlow 的 Agent 工作流可视化编排画布?

来源:商汤 AI Agent 开发面经(2026-03-05)

新手答:“前端用节点编辑器画流程,后端把节点按连线顺序执行。”

高手答:

先把画布定位为版本化工作流中间表示的可视化投影,而不是一张靠坐标驱动执行的图。组件注册表定义节点类型、配置 schema、输入输出端口、权限、超时、重试和版本;画布文档只保存稳定的 node_id、组件版本、配置、边、视图坐标和子图引用。端口使用 typed schema,连线时就检查类型、必填输入、单/多输入基数和作用域,避免等到运行时才发现两个节点不能连接。

前端按“节点库 + 画布 + 属性面板 + 运行面板”拆分。拖拽、连线、撤销重做和批量移动都转成可重放 command;自动保存采用文档版本与乐观锁,服务端拒绝基于旧版本的覆盖。大图只渲染可视区域,搜索、分组、折叠子图和键盘操作保证可用性。UI 状态与业务图状态分离,缩放和选中状态不能污染发布制品。

发布时由编译器完成四层门禁:

阶段 核心检查
静态校验 孤立节点、不可达节点、端口类型、必填配置、非法环和未绑定秘密
语义校验 条件分支是否完备、循环是否有退出条件、并行汇聚 reducer 是否确定
安全校验 节点权限、数据分级、跨租户引用和高风险工具审批
执行计划 拓扑/状态机编译、并发组、超时预算、checkpoint 和补偿边界

运行时只消费不可变的已发布版本,草稿修改不能影响在途任务。每个节点事件通过 run_id + node_id + attempt 回传,画布展示排队、运行、成功、失败、耗时和输入输出摘要,并能从失败节点基于 checkpoint 重放。组件升级要做 schema migration 和兼容检查,旧工作流仍绑定旧版本;验收既测编译正确性,也测大图交互性能、协同冲突、断线恢复和运行轨迹与节点高亮的一致性。

差距在哪:新手把画布当拖拽 UI,高手把它设计成“组件元数据 → typed Graph IR → 编译门禁 → 可观测 Runtime”的完整产品,并处理版本、迁移、协作和安全边界。面试官考的是能否让低代码编排既好用又可执行、可治理。


Q:接入多个外部 Agent 时,如何用 Adapter 统一异构事件、工具调用和生命周期协议?

来源:北京 B 端 AI 小厂面经(2026-07)

新手答:“为每个 Agent 写一个 Adapter,把它们的 JSON 转成统一格式。”

高手答:

核心是建立一层 anti-corruption layer:内部先定义稳定的 canonical protocol,外部 A2A、厂商 SDK、Webhook 或私有流式协议各自实现 Adapter。统一事件信封至少包含 tenant_id、run_id、task_id、event_id、sequence、type、timestamp、trace_id、causation_id、deadline、schema_version;type 用判别联合表达 message / tool_request / tool_result / status / artifact / error,厂商特性放显式 extension,不能为了最低公分母静默丢失语义。

Adapter 不只是改字段名,还要完成三类语义映射:

  1. 能力与工具:握手时发现模态、流式、取消、回调和工具能力;工具加命名空间,参数按内部 schema 校验,凭证由网关注入,外部 Agent 不能继承宿主的全部权限
  2. 状态机:把各家的 queued/running/waiting/completed/failed/cancelled 映射到受控状态迁移表;无法精确映射时保留原状态并标记 unknown,不能猜成成功
  3. 流式与制品:文本增量、工具事件、进度和 artifact 分通道处理;大对象只传受权引用与摘要,内部顺序由 sequence 和 causation 关系确定

可靠性边界由接入层兜住:按 event_id 幂等去重,对乱序事件做有界缓冲并检测序号缺口;取消和 deadline 向下传播,迟到事件必须携带当前 lifecycle_epoch,旧 epoch 的回调不能覆盖新任务状态。有副作用的工具使用幂等键、状态查询和人工审批,重连后从持久化 offset 恢复,而不是把整个任务盲目重跑。

每个 Adapter 都跑同一套 conformance suite,覆盖能力协商、正常/异常工具调用、流式中断、取消、重复与乱序事件、版本升级和鉴权失败;生产 trace 同时保留原始协议摘要与 canonical event,badcase 可离线回放。协议升级采用双读、单写和按 Agent 灰度,确认指标稳定后再退役旧映射。

差距在哪:新手做 DTO 转换,高手统一的是事件语义、状态所有权、权限和恢复行为,同时保留扩展能力与原始证据。面试官考的是异构 Agent 接入能否长期演进,而不只是第一次联调成功。


这类题的答题模式

踩坑题的核心是真实 + 系统性:

1. 每个坑要有三层:故障现象 → 根因分析 → 工程化解法
2. 解法不能是"下次注意"——要是代码层面、架构层面的系统性方案
3. 成本控制不只是换模型——分级调度、预算制、缓存、熔断四管齐下
4. 踩坑经验要能泛化——"这个坑的本质是什么,其他场景会不会也遇到"

面试官听到“模型不听话”就知道你只在 Notebook 里跑过。听到死循环计数器、状态外置存储、JSON 容错解析、token 预算熔断,才会觉得你真的在生产环境里摔过跤,并且摔完站起来做了系统性修复。


下一篇建议继续看: