面经
面试题库(全量合并版)
更新时间:2026-06-29 覆盖岗位:字节 Coze AI Agent 开发工程师 / 零一万物 AI 全栈开发工程师 来源:
questions.md+coze_interview_answers.md+me.md+mock_log.md+last_2_hours_review.md
目录(按技术专题)
共 62 题,分 10 个技术专题。点击跳转。
一、面试通用
- 1. 自我介绍 + 为什么适合这个岗位
- 2. FuserMate 是什么,你负责了什么
- 3. 传统 Web 应用和 AI Agent 应用有什么本质不同
- 4. 你怎么理解"产品思维"在 Agent 开发中的体现
二、上下文工程与产品设计
三、Multi-Agent 架构与协作
- 9. 多 Agent 协作模式:Orchestrator-Worker vs A2A vs Debate vs Swarm
- 10. 主 Agent 和子 Agent 的通信链路怎么设计
- 11. A2A 协议 vs MCP 是什么关系
- 12. Tool 调用 vs 多 Agent 架构本质区别
- 13. 多 Agent 协作中"记忆污染"怎么处理
- 14. 两个 Agent 产生不可调和的分歧时怎么处理
- 15. 如何防止多 Agent 死循环?循环检测与熔断
- 16. 多 Agent 的评估体系怎么搭建?
- 17. 多 Agent 的分布式 Tracing 与成本归因
- 18. 多 Agent 系统的上下文窗口管理
- 19. 什么情况下「不应该」上多 Agent
四、工具系统与协议
- 20. MCP vs Function Calling 深度对比:原理与架构
- 21. MCP 协议在 Agent 系统中起什么作用
- 22. Skill 和 Tool 的区别?怎么设计 Skill 体系
- 23. 工具调用失败了怎么办?重试策略
- 24. 工具太多时怎么管理?工具路由
- 25. 工具执行的可观测性怎么做
五、Agent 运行时深挖
- 26. AgentLoop 怎么设计(源码级全链路)
- 27. FuserMate Agent 全链路(Mailbox/Hook/Cron)
- 28. 为什么 FuserMate 自研 AgentLoop,FuserLab 用 LangGraph
- 29. 工具安全怎么防 Prompt Injection
- 30. FuserLab 的 Trace 和评测怎么做
- 31. 你怎么优化过 LangGraph 语音控制延迟
- 32. RAG 你实际做到了哪一步
六、LangGraph 与编排框架
- 33. LangChain 和 LangGraph 的核心区别
- 34. StateGraph 三要素:State / Node / Edge
- 35. Checkpoint 机制:状态持久化与断点恢复
- 36. Human-in-the-Loop 怎么设计?三种插入范式
- 37. State Schema 设计:TypedDict vs Pydantic
- 38. Reducer 机制与状态合并策略
- 39. Node 粒度与 Conditional Edge 控制
- 40. LangGraph 生产环境的可观测性与失败处理
- 41. 从 LC Agent 迁移到 LangGraph 的思维转变
- 42. 什么情况下「不应该」使用 LangGraph
七、工具执行引擎
- 43. 工具串行 vs 并行怎么设计?Claude Code / Codex 对比
- 44. 工具中断怎么恢复?防止重复执行
- 45. 工具错误分类与区别处理
- 46. 工具幂等性设计:Key / Lease / 意图分离
- 47. 长时间运行的工具怎么管理
八、推理引擎与基础设施
- 48. 高并发下模型推理延迟(vLLM 调优)
- 49. 如何降低推理成本?效率 vs 准确性
- 50. 自建推理(华为 910B + vLLM-Ascend)
- 51. 私有化交付你做过哪些
- 52. 多租户和配额怎么设计
- 53. 完整 Helm chart 和离线交付
- 54. 企业 SSO/LDAP
- 55. Agent 系统"千人千面"个性化
- 56. Agent 执行链路长任务正确性保证
九、行业视野与场景设计
- 57. LangGraph vs. Eino vs. 自研 AgentLoop
- 58. Claude Code / Codex / FuserMate 三方对比
- 59. Coze 的不足与改进方向
- 60. 对 Manus、DeepSeek 等新产品的看法
- 61. 场景设计:全自动化 Agent 创作
- 62. 场景设计:旅行攻略 Agent + 测评
十、反问 + 速记
一、面试通用
A1. 自我介绍 + 为什么适合这个岗位
面试官意图:看你经历是否和岗位匹配,能否把产品、全栈、AI 平台负责人串成一个稳定画像。
推荐回答:
面试官您好,我是郭鹏,目前在中电信数智做 AI 研发工程师,主要负责部门 AI 平台和 Agent 产品的研发工作。我的经历比较复合,既做过产品经理,也回到研发主导 AI 项目落地,所以我比较擅长从业务目标出发,拆到产品方案、技术架构、前后端实现和上线验证。
我最近最核心的两段项目经历是 FuserMate 和 FuserLab。
FuserMate 是一个类 Claude Code 的通用智能体平台,对内作为数字员工,对外承接政企场景 Agent。我在里面担任 AI 全栈和小组负责人,从 0 到 1 主导产品定义、UI/UX、技术架构和核心代码,重点做了 AgentLoop、工具体系、MCP、Skills、HITL、中断恢复、上下文压缩和工具安全。
FuserLab 是部门级 AI 中台,核心是把 Prompt 编排、版本发布、Trace 观测、评测实验和知识库能力平台化。我在里面负责 Prompt/评测/观测工具链、LangGraph 业务 Agent,以及 pgvector + unstructured 知识库能力。
我理解这个岗位需要的是上下文工程、MultiAgent 架构、工具 Skill 生态、高并发低延迟和高可用系统能力。我的这两段项目正好覆盖这些方向,而且我不是只做某个局部功能,而是从产品价值、平台边界到代码落地都实际负责过,所以我认为和岗位匹配度比较高。
Coze 版微调:结尾强调"上下文工程、MultiAgent 协作、Skill 生态、高并发推理",对应 Coze JD 四大方向。 零一万物版微调:结尾强调"B 端 Agent 平台、工具工程化、RAG、可观测评测和全栈交付能力"。
可能追问:
- 你从产品转研发后最大的优势是什么?→ 能从业务价值出发做技术取舍,而不是为了技术而技术
- 你说效率提升 200%,怎么衡量?→ 200% 是团队交付效率的内部估算,主要来自需求拆解、代码生成、测试补齐的周期缩短。具体例子:以前一个设置页需要多人多天,现在 AI 协作可以压缩到一到两天
表达注意:
- "类 Claude Code"和"类 Claw"根据面试官熟悉度选一个,不要混用
- 不要用"deepagent"这种没有业界定义的自造词
- 不要说"内部使用风险小所以敢自研"→ 说"需要精细控制平台运行时所以自研"
A2. FuserMate 是什么,你负责了什么
面试官意图:确认你是否真的主导平台,而不是只做页面或零散功能。
推荐回答:
FuserMate 是一个通用智能体平台,对内作为数字员工,对外作为可嵌入对讲/IM 业务流程的场景化 Agent。我的角色是 AI 全栈和小组负责人,负责从产品定义、交互设计、技术架构到核心编码的闭环。
技术上我主导了 AgentLoop、工具体系、MCP、Skills、HITL、中断恢复、上下文压缩、运行事件和工具安全。架构上把 Agent、Workspace、Session、AgentRun 分开——Agent 管身份和记忆,Workspace 管路径和能力,Session 管对话,AgentRun 管执行快照。这样平台既能支持实时对话,也能支持回放、审计、Cron、任务和后续多 Agent 协作。
可展开项目证据:
backend/src/agent/core/agent_loop.pybackend/src/agent/core/tools/registry.pybackend/src/agent/core/tools/executor.pydocs/plan/detail/03-core-model.mddocs/plan/detail/README-command-safety-policy.md
可能追问:
- AgentRun 为什么要单独建模?→ 冻结执行上下文,支撑回放、审计、崩溃恢复
- Workspace 为什么管 tools/skills,不放在 Agent 上?→ Agent 是身份,Workspace 是能力边界,解耦后同一 Agent 可以在不同场景有不同能力
A3. 为什么 FuserMate 自研 AgentLoop,FuserLab 用 LangGraph
面试官意图:考察你对框架取舍的理解。
推荐回答:
两个项目的复杂度方向不一样。
FuserLab 的语音控制是一个业务上已经比较明确的状态机:agent 调用工具 → 工具返回结果 → agent 继续推理 → 多候选时走 ask_user 分支。这个流程用 LangGraph 的 StateGraph + ToolNode + checkpointer 刚好能表达,开发成本低,而且 LangGraph 的 Postgres checkpointer 天然解决了状态持久化的问题,不需要自己再造一遍。
FuserMate 的主要复杂度不在固定的图拓扑,而在平台运行时。我需要精细控制模型流式输出的每一步拆分——thinking delta、message delta、tool call 的识别——然后把这些语义事件通过 WebSocket 实时推给前端。工具执行也不是简单的 ToolNode 概念,而是有 started、completed、failed 的完整生命周期事件,需要配合 cancel 中断、结果截断、上下文压缩和快照冻结。这些平台级能力如果用 LangGraph 硬套,反而会被它的抽象层束缚住,不如自研轻量 AgentLoop 来得直接。
但自研不是没有代价——我需要自己处理迭代上限、异常兜底、流式中断、事件序等基础设施问题,LangGraph 在社区和稳定性上有优势。所以我的判断是:业务状态机明确的场景用 LangGraph,需要掌控平台运行时的场景自研。如果 FuserMate 后续要做复杂 DAG,我的演进思路是在上层引入图编排,底层工具执行和事件系统仍然复用自研的 AgentLoop,不是全量迁移到 LangGraph。
可能追问:
- 自研 AgentLoop 的风险是什么?→ 社区支持弱、需要自行维护基础设施、新人上手成本高
- LangGraph checkpointer 带来了什么问题?→ 连接创建进了热路径,通过池化优化解决
不会时怎么说:如果问到 LangGraph 内核执行细节:我没有深入改过 LangGraph 内核,项目里主要使用 StateGraph、ToolNode 和 Postgres checkpointer。我更熟的是如何把它封装进业务 Agent,并通过 Trace 和 stage timing 定位性能问题。
A4. 传统 Web 应用和 AI Agent 应用有什么本质不同
面试官意图:考察对 Agent 应用范式的根本理解。
推荐回答:
本质区别在三个层面:
确定性 vs. 概率性。传统 Web 应用的逻辑是确定性的——同一个请求走同一条代码路径,得到同一个结果。Agent 应用的每一步都是概率性的——模型可能选择工具 A 也可能选择工具 B,推理链可能正确也可能偏航。这意味着 Agent 的调试方式完全不同:传统应用看日志找哪行代码逻辑错了;Agent 需要 trace 贯穿模型推理、工具调用、上下文状态的完整链路。
请求-响应 vs. 持续执行。传统 Web 应用是一次 HTTP 请求-响应。Agent 的一次"请求"可能包含 10 轮模型调用 + 5 次工具执行 + 2 次人工确认,持续数分钟。这需要完全不同的架构:异步执行、流式推送、中断恢复、状态持久化。FuserMate 的 WebSocket 推流 + AgentRun 快照 + reconcile 崩溃恢复,都是为这种"持续执行"模式设计的。
代码即逻辑 vs. Prompt 即逻辑。传统应用的业务逻辑在代码里,修改逻辑 = 改代码 + 部署。Agent 应用的一部分逻辑在 Prompt 里,修改 Prompt 可能改变 Agent 的行为——但没有编译器和类型系统帮你检查 Prompt 的正确性。所以 Prompt 需要版本管理、评测回归、灰度发布——和代码一样的工程化流程。
一句话总结:Agent 应用不是"传统应用 + AI",而是一种新的应用范式,其不可靠性需要新的工程体系和产品设计来管理。
A5. 你怎么理解"产品思维"在 Agent 开发中的体现
面试官意图:JD 第一条就强调产品思维,这是区别于纯工程岗位的关键。
推荐回答:
我有一段特殊经历——从研发转到产品经理,做了两年产品,再回到研发主导 AI 平台。所以我对"产品思维"的理解不是抽象概念,而是实际的工作方式。
产品思维在 Agent 开发中体现在三个方面:
第一,定义 Agent 能做什么、不能做什么。技术人容易陷入"技术上能做到就去实现",但产品思维要先问:用户真的需要这个吗?Agent 全自动处理和半自动辅助的边界在哪里?FuserMate 做工具安全策略时,我没有把权限做得极细(每个工具都弹窗确认),因为那会把 Agent 变成"需要人盯着每一步的自动机器"——失去了 Agent 的核心价值。
第二,定义失败时的用户体验。Agent 出错了——比如工具调用失败、推理跑偏——用户看到的不能是 traceback 或技术错误码,而是"Agent 在做什么、为什么没做成、你现在可以怎么做"。FuserMate 的 27 种运行时事件和 cancel 中断的即时反馈,都是围绕这个目标设计的。
第三,用评测数据而非"感觉"来判断质量。产品思维不是"我觉得这个 prompt 效果不错",而是"这个 prompt 在 200 个 case 的评测集上,intent 准确率从 87% 提升到了 92%,但 slot 完整率下降了 1%——我们看看是哪些 case 在退化"。
总结:Agent 产品经理和传统产品经理最大的区别是——你需要理解"模型的不可靠性"是系统的一部分,而不是 bug。设计产品时要假设模型会出错,然后让系统在这个前提下仍然可用、可恢复。
二、上下文工程与产品设计
B1. 上下文工程的核心挑战是什么?你做过哪些上下文管理方案
面试官意图:考察你是否理解上下文不只是"塞进 prompt",而是系统工程问题。
推荐回答:
上下文工程的核心挑战不是"窗口够不够大",而是信息密度和时效性的平衡。你把什么放进上下文、什么时候放、放进去之后什么时候清理——这三个问题比窗口大小重要得多。
我在 FuserMate 里做了两层压缩来解决这个问题。
第一层是 MicroCompactor,规则驱动的轻量压缩,在每轮模型调用前自动执行。逻辑很简单但有效:保留最近 5 个工具结果原文,其余替换为哨兵字符串 "[Old tool result content cleared]"。短结果(<200 字符)永不压缩。这样模型知道有工具执行过,但不消耗 token 记录完整历史输出。关键是这个压缩是零延迟的,不用调模型。
第二层是 CompactionService,模型驱动的深度压缩。当上下文接近 128K token 时自动触发。它不是简单地让模型自由摘要,而是用结构化的 summary prompt 要求保留五类信息:当前目标、重要决策、暂停约束、未决问题、文件路径和接口约定。压缩后的摘要作为 system message 注入到消息列表开头,模型看到的是"摘要 + 最近未压缩的 turns"。
这里有一个我特别想强调的设计点:上下文的核心是"状态"而非"聊天记录"。很多方案只保留滑动窗口的最近对话,但 Agent 跨越几十轮执行后,真正重要的是"当前目标是什么、哪些决策已经做了、哪些约束还在生效",而不是三小时前用户说了什么。MicroCompactor 保的是最近的操作语义,CompactionService 保的是全局任务状态——两层互补。
可能追问:
- 为什么 MicroCompactor 不用模型做摘要?→ 跑在每次模型调用前,如果在热路径上再加 LLM 调用会显著增加延迟。规则级压缩零延迟,只在真正逼近阈值时才触发模型级压缩
- 128K 阈值怎么定的?→ 200K 窗口模型,128K 触发留 70K buffer 给当前轮次输出和工具结果
B2. 多 Agent 架构下主 Agent 和子 Agent 的通信链路怎么设计
面试官意图:考察多 Agent 协作的工程落地经验。
推荐回答:
我实际做过的是 FuserMate 的 Mailbox 异步通知机制,它虽然不是完整的多 Agent 商业平台,但解决了多执行单元之间通信的核心问题。
Mailbox 的设计是:后台任务、Cron、子 Agent 完成后,写入 mailbox 消息,每条消息包含 source(来源)、wake_policy(是否唤醒主 Agent)、content(结构化内容)。消息消费后标记 consumed_at,如果运行被取消则回滚为未消费,保证不丢消息。这个机制可以自然扩展到多 Agent 场景——每个子 Agent 就是一个消息生产者,主 Agent 在下一轮模型调用前消费 mailbox。
除此之外,我研究过 Claude Code 和 Codex 的多 Agent 方案:
Claude Code 用的是 Master-Worker 模式。Coordinator 只保留 AgentTool + TaskStop 等少量工具,Worker 通过 runForkedAgent() 启动,共享 prompt cache(CacheSafeParams)。上下文传递比较"重"——Worker 直接拿到完整的 fork 上下文,适合编程场景的单次深度任务。
Codex 用的是 Actor Model,更灵活但更复杂。Agent 之间通过 Mailbox + TriggerTurn + SendMessage 通信,有 AgentRegistry 管理生命周期,agent_max_depth 限制防止递归爆炸。上下文传递有两种模式:FullHistory(完整历史)适合需要全局上下文的任务,LastNTurns(N) 适合独立子任务。
如果让我给 Coze 设计多 Agent 通信,我会融合这三家的经验:用 Mailbox 做异步消息总线(参考 FuserMate/Codex),用 AgentTool 做工具化调用(参考 Claude Code),上下文传递按任务类型灵活选择——独立子任务只传目标和约束,协作任务共享关键状态摘要。底层用 agent_max_depth + 超时 + 结果校验三层防护,防止级联故障。
可能追问:
- 子 Agent 执行失败了怎么处理?→ 可重试的指数退避 2 次;参数错误返回 error+Schema 让主 Agent 修正;不可恢复的返回清晰错误
- 如何防止子 Agent 的幻觉污染主 Agent?→ 子 Agent 返回结果经过验证层:结构化输出校验、关键字段非空检查、结果长度阈值
B3. 多 Agent 协作中"记忆污染"怎么处理
面试官意图:考察你对多 Agent 系统级风险的认知深度。
推荐回答:
记忆污染在单 Agent 场景只是单点问题——一个错误的工具结果或推理结论影响后续决策。但在多 Agent 系统中,污染会通过共享上下文或协调消息传播,变成系统性风险。
最危险的场景是:A Agent 产生了一个错误但"看起来合理"的判断,通过共享状态传给 B Agent,B 基于这个错误判断继续推理并产生新结论,C Agent 再基于 B 的结论行动——形成围绕错误信念的共识,比单一错误更难检测。
我的应对思路有三层:
第一层是独立验证路径。关键的中间结论不依赖单一 Agent 的判断,而是让另一个独立 Agent(或规则引擎)做交叉验证。比如 FuserMate 的工具安全策略就是这种思路——模型输出不可信,由运行时独立做策略评估。
第二层是信息的可信度标记。不同来源的信息标注不同的信任级别:用户直接输入 > 工具确定性结果 > Agent 推理结论 > Agent 推测。高风险的决策不能仅依赖低可信度信息。
第三层是状态的可回滚性。FuserMate 的 AgentRun 快照思路可以扩展——每个关键决策点前保存 checkpoint,如果后续发现有污染,可以回滚到安全点重新执行。
可能追问:怎么发现记忆污染?→ 通过 trace 和评测。离线评测集设计"对抗样本"——故意插入错误中间结果,验证系统能否抵抗污染。
B4. 两个 Agent 产生不可调和的分歧时怎么处理
面试官意图:考察 Multi-Agent 架构中的冲突解决设计。
推荐回答:
我的核心观点是:分歧不是错误,是不确定性的信号。处理方式取决于分歧发生在什么类型的操作上。
对于不可逆或高风险操作——比如发消息给用户、修改数据库、调用外部付费 API——不应该由某个 Agent "赢"来裁决,而应该触发升级(escalation)。把分歧信息、各 Agent 的理由、影响范围打包,交给人类决策或更高层的协调者 Agent。这类似于分布式共识系统中的原则:安全性优先于活性(Safety > Liveness),在不确定时,宁可不决策也不要错误决策。
对于低风险、可回滚操作,可以让其中一个 Agent 先执行,同时记录分歧和回滚预案。执行后收集反馈,如果结果有问题就回滚。
协调者 Agent 的职责不是"比 Worker 更聪明地决策",而是执行决策纪律——要求每个 Agent 明确自己的假设,拒绝欠规范的方案,在不确定性超过阈值时延迟执行。
具体实现上分三步:
- 识别分歧来源:是假设差异、约束差异、还是目标理解差异
- 判断操作可逆性:不可逆 → 升级;可逆 → 先执行并收集反馈
- 记录并学习:分歧本身就是评测信号,持续出现分歧的环节说明系统设计或 prompt 有问题
B5. Agent 的上下文漂移怎么解决
面试官意图:考察长任务中 Agent 稳定性的工程经验。
推荐回答:
上下文漂移本质上是 Agent 在长任务执行中逐渐偏离原始目标。核心原因是模型在每次推理时注意力的分布会变化——早期的约束和子目标在长上下文中被"稀释"了。
我在 FuserMate 中通过三个机制来对抗漂移:
第一,CompactionService 的结构化摘要。摘要 prompt 明确要求保留"当前目标"和"暂停约束"——比如"用户要求代码必须兼容 Python 3.10"这种约束,如果只在第一轮提过一次,几十轮后模型很容易遗忘。摘要让关键约束始终在模型注意力范围内。
第二,AgentRun 快照的冻结机制。每次运行启动时,把当前的目标、workspace 配置、启用的工具集、memory 都冻结成快照。这样即使上下文被压缩,外层的运行时状态不会漂移——Agent 能做什么、不能做什么、目标是什么,这些不在 LLM 的上下文里,而在系统层控制。
第三,关键约束的 Pin 机制。用户标记为"重要"的指令或约束,不参与 MicroCompactor 的清理,始终保留在上下文中。不是所有消息都平等,有些约束必须"钉"在上下文里。
还有一个设计原则:Agent 永远不应从自身意图推断执行状态。Agent 不能说"我认为我做了 X,所以 X 已经完成了"——这就是漂移的根源。工具交互必须外部可验证、幂等、可恢复。
B6. Prompt 工程的稳定性如何保障
面试官意图:考察 Prompt Engineering 的工程化思维,而非调参经验。
推荐回答:
换个说法效果差十倍,根本原因是 LLM 在语义空间中对不同表述的敏感度差异很大。对同一个指令,"请你做 X"和"你的任务是 X"在模型的 embedding 空间里激活的路径可能不同,尤其当 X 涉及多步推理或工具选择时,措辞的微小差异会被后续链式推理放大。
解决问题的思路不是"找到一个 magic prompt",而是把 prompt 当作代码来管理。我在 FuserLab 做的 Prompt 编排系统就是基于这个思路:
- 版本管理:Prompt 用 Jinja2 模板定义,每次修改有版本记录,支持回滚。改 prompt 和改代码一样走版本流程。
- 评测闭环:改 prompt 之前,先跑离线 benchmark(RexUniNLU),看 intent、slots、complexity 各维度是否有回归。没有评测就改 prompt 等于蒙着眼睛开车。
- Badcase 回灌:线上 trace 发现的错误 case,直接写进离线评测集变成固定 regress case。这样每次改 prompt 都能看到会不会引入老问题。这个闭环是保证质量不退化的关键。
- LFU 缓存:编译后的 prompt template 用 LFU 策略缓存,避免每次请求都重新渲染。
另外,System Prompt 和 User Prompt 的职责要严格分离。System Prompt 放"规则"——工具使用规范、安全约束、输出格式要求;User Prompt 放"任务"——具体要做什么。规则层面的措辞要经过充分测试,任务层面允许灵活变化。
B7. Coze 的拖拽式编排 vs. Agent 化编排
面试官意图:考察对 Coze 产品本身的理解,以及编排模式的选择判断力。
推荐回答:
Coze 的两个版本代表了两种 Agent 编排哲学:
拖拽式编排(旧版)适合流程稳定、路径清晰的场景。比如固定的审批流程、FAQ 问答、标准信息抽取——输入明确、步骤确定、输出结构定义好。拖拽式的优势是可控——每一步做什么、分支条件是什么,开发者心里有数,出问题容易定位。
Agent 化编排(新版)适合目标不稳定、路径需要动态决策的场景。比如多轮任务执行、跨工具协作、用户意图不明确的开放式对话。Agent 化的优势是灵活——模型根据中间结果自行判断下一步做什么,不需要人工穷举所有分支。
但 Agent 化的代价是不可预测——你没法穷举模型会走什么路径,出了问题也难以复现。
我判断用哪个的标准是:任务的不确定性有多大。不确定性低用拖拽式(稳定可控),不确定性高用 Agent 化(灵活应变)。实际项目中两者经常混合——整体流程用拖拽式保证主干稳定,局部决策节点用 Agent 化处理模糊输入。
还有一个设计原则:节点设计不要按功能菜单拆,要按状态边界和失败恢复边界拆。每个节点应该做到"输入清晰、输出结构化、失败可恢复",这样不管用哪种编排方式,系统都是健壮的。
B8. Coze 有哪些不足?如果你是工程师会改进什么
面试官意图:这是必问题——考察你对产品的理解和改进思路。
推荐回答:
我从开发者的视角来讲几个方向:
第一,上下文工程的可配置性。Coze 目前的知识库 + 变量管理解决了"静态知识"的注入,但对"动态上下文"的管理——跨轮次的记忆压缩、关键约束的长期保持、工具结果的智能裁剪——还比较基础。开发者需要更细粒度的控制:哪些记忆应该持久化、什么时候触发压缩、压缩保留什么信息。
第二,工具生态的质量保障。Coze 的插件市场很丰富,但插件质量参差不齐。缺少标准化的测试框架——开发者在发布插件前没法自动验证插件的 Schema 正确性、异常处理、性能基线。建议引入插件评测机制(类似 App Store 的审核)+ 开发者的沙盒测试环境。
第三,可观测性和调试体验。Agent 出问题时,开发者需要知道"模型在哪一步想错了、工具为什么没调对、上下文是什么状态"。目前 Coze 的 Trace 更多是调用链展示,缺少根因分析——比如自动标记"这次失败可能是因为工具 X 的 Schema 描述不够清晰,模型填充参数时漏了必填字段"。
第四,多 Agent 协作的深度。目前 Coze 的多 Agent 模式还比较线性——一个主 Bot 调用子 Bot。更复杂的模式(并行 Agent + 投票、Agent 辩论、层级式 Agent 树)还支持不够。尤其缺少 Agent 间冲突检测和协调机制。
如果让我选一个最有价值的方向先做,我会选可观测性和调试体验——因为这是 Agent 从"demo 能跑"到"生产可用"之间最大的鸿沟。
三、Multi-Agent 架构与协作
本章基于网上 MultiAgent 面试真题 + 学术 Benchmark 研究 + 你的 FuserMate Mailbox/Claude Code Coordinator/Codex Actor 研究经验编写。 多 Agent 架构是 Coze JD 的核心关键词之一,也是字节面试的深度追问方向。
M1. 多 Agent 协作模式有哪些?Orchestrator-Worker vs A2A 怎么选
面试官意图:考察你对多 Agent 拓扑结构的系统认知。
推荐回答:
多 Agent 协作有六种主流拓扑,按控制集中度从高到低排列:
| 拓扑 | 控制方式 | 优势 | 劣势 | 适用场景 |
|---|---|---|---|---|
| Supervisor(Orchestrator-Worker) | 中心化 | 易审计、任务分配清晰 | 单点瓶颈、Coordinator token 开销大 | 结构化任务拆解(退款处理、巡检流程) |
| Hierarchical | 层级化 | 分层管理、局部自治 | 层间延迟、Manager prompt 复杂 | 大型 Agent 团队、跨域协作 |
| Pipeline/Sequential | 链式 | 协调开销最低 | 无并行、上游阻塞下游 | 线性任务(提取→转换→验证) |
| Peer-to-Peer(Debate) | 去中心 | 多视角、无单点 | 难以收敛、token 爆炸 | 验证、创意头脑风暴 |
| Swarm/Handoff | 交接式 | 上下文自然传递 | 难以回滚、交接条件苛刻 | 客服升级(通用→专业→人工) |
| Graph/Mesh | 网状 | 灵活、分布式决策 | 协调复杂度高 | 研究任务、复杂问题求解 |
Orchestrator-Worker(星型拓扑)vs A2A(网状拓扑)的选择标准:
| 维度 | Orchestrator-Worker | A2A |
|---|---|---|
| 任务可预分解 | ✅ 任务结构事先已知 | ❌ 需要 Agent 自行发现协作方式 |
| 控制需求 | 强审计、单点可控 | 去中心、弹性优先 |
| 通信模式 | Worker → Coordinator 单向汇报 | Agent ↔ Agent 双向对等通信 |
| 上下文传递 | Coordinator 统一管理 | 各 Agent 自行维护 |
| 典型协议 | Supervisor 分配任务 | A2A Agent Card + Task 生命周期 |
我的实践:FuserMate 的 Mailbox 机制更接近 Orchestrator-Worker——主 Agent 通过 Mailbox 收集后台任务/Cron/子 Agent 的结果。Claude Code 的 Coordinator Mode 也是 SuperVisor 模式——Coordinator 只保留 AgentTool + TaskStop。Codex 走的是 Actor Model(网状)——AgentRegistry + Mailbox + TriggerTurn。
选择建议:任务可预分解 → Supervisor;需要 Agent 自行协商 → A2A。实际生产中 Supervisor 模式最常见,因为大多数多 Agent 场景本质上是"分解得好就能做好",不需要 Agent 自由协商。
M2. Tool 调用 vs 多 Agent 架构,本质区别在哪?什么时候必须拆 Agent
面试官意图:这是高频陷阱题——面试官想看你能不能讲清楚"拆分 Agent 的真正理由"。
推荐回答:
本质区别有六个维度:
| 维度 | Tool 调用 | 多 Agent |
|---|---|---|
| 上下文隔离 | 工具结果直接拼进主 Agent prompt | 子 Agent 有独立上下文,不受主 Agent 历史污染 |
| 独立规划 | LLM 在主上下文中规划,工具参与执行 | 子 Agent 独立做子任务内的规划 |
| 并行性 | 单个 Agent 串行调用多个 tool | 多个 Agent 可并行执行独立子任务 |
| 容错粒度 | 一个 tool 失败,Agent 需要自行处理 | 子 Agent 崩溃可被 Coordinator 独立恢复 |
| System Prompt | 共享同一个 system prompt | 每个 Agent 有独立的 system prompt + 工具集 |
| 成本 | 低(单上下文) | 高(多个上下文 + 协调开销),通常 4-5 倍 |
什么时候必须拆 Agent 而不是加 Tool?三条判断标准:
子任务需要独立的 System Prompt 和工具集。比如"代码安全审计 Agent"需要安全领域的 system prompt + 专用的漏洞扫描工具,和主 Agent 的"帮用户写代码"是完全不同的角色。
子任务的上下文会和主任务互相干扰。比如主 Agent 正在排查一个数据库问题,同时需要分析一段日志——如果把日志分析的结果全部拼进主 Agent 上下文,会把排查思路污染掉。拆成子 Agent,返回结构化的分析结论,不污染主上下文。
子任务可以并行且有时间收益。比如同时审查 3 个微服务的代码,3 个 Agent 并行跑 vs 1 个 Agent 串行跑 3 次,有明显的延迟收益。
反模式:多 Agent 不是银弹。如果只是为了"看起来高级"而拆分,引入的协调开销(Coordinator 的 token 消耗、通信延迟、失败处理复杂度)可能远超收益。
一句话:Tool 解决"能做更多事",多 Agent 解决"能以不同角色思考和决策"。
M3. 多 Agent 通信:A2A 协议 vs MCP 是什么关系
面试官意图:考察对 Agent 协议生态的理解,2025-2026 年热点。
推荐回答:
A2A(Agent-to-Agent)和 MCP(Model Context Protocol)是互补关系,不是竞争关系。
| 维度 | MCP | A2A |
|---|---|---|
| 解决的问题 | Agent ↔ Tool/Data 标准接口 | Agent ↔ Agent 互操作协议 |
| 类比 | USB 接口(连接外设) | HTTP/gRPC(服务间调用) |
| 状态 | 无状态(每次调用独立) | 有状态(Task 生命周期管理) |
| 关系 | 主从(Agent 控制 Tool) | 对等(Agent 间协商) |
| 核心概念 | Tool、Resource、Prompt | Agent Card、Task、Artifact |
A2A 的核心概念:
- Agent Card:每个 Agent 的"名片",JSON 描述能力、输入输出 Schema、认证方式
- Task:原子工作单元,有完整的生命周期(submitted → processing → completed/failed)
- Streaming(SSE):实时推送中间结果
- Push Notification:长任务完成后的异步回调
- Artifact:Agent 产出的文件/数据,可直接传给下游 Agent
消息头的三层结构:
- 路由层:目标 Agent ID、源地址、全局唯一 Message ID(幂等去重)
- 语义层:消息类型(任务派发、结果回传、状态更新、错误报告、心跳)
- 安全层:源签名、认证 Token、加密标记
工程实践:用 A2A 做 Agent 间任务分发,用 MCP 做每个 Agent 内部调用工具。两者在同一系统里分层协作。
M4. 如何防止多 Agent 死循环?循环检测与熔断机制怎么设计
面试官意图:考察生产级多 Agent 系统的稳定性设计。
推荐回答:
多 Agent 死循环的典型场景:Agent A 把任务委派给 Agent B,Agent B 不理解又回给 Agent A → 无限循环。解决方案分三层:
第一层:循环检测(DFS on Call Graph)
维护全局调用图。每次 Agent A 要调用 Agent B 时,检查 B 是否已经在当前调用链中:
def detect_cycle(callee_id: str, call_chain: list, max_depth: int) -> bool:
if callee_id in call_chain:
return True # 检测到环
if len(call_chain) >= max_depth:
return True # 深度超限,视为环
return False
调用链在每次跨 Agent 调用时追加当前 Agent ID,传递时带 call_chain + max_depth。
第二层:深度限制
设置 agent_max_depth(参考 Codex)。任何 Agent 不能调用超过 N 层子 Agent(通常 N=3)。超过深度直接返回错误,不继续。这个机制简单但极有效——阻止了 90% 的级联调用失控。
第三层:三级恢复策略
| 级别 | 策略 | 触发条件 |
|---|---|---|
| 降级(Degrade) | 返回缓存/已知结果,不继续下游调用 | 单次循环检测触发 |
| 降频(Rate Limit) | 降低调用频率,加等待队列 | 短时间内多次循环检测 |
| 熔断(Circuit Break) | 彻底切断此调用链,标记子任务为"不可用" | 连续 3 次循环 → 熔断 |
收敛保证:max_rounds + 进度检测——连续 2 轮没有实质性进展(State 无变化),强制终止并返回"无法完成"。
我的实践:FuserMate 的 AgentLoop 有 max_iterations=200 上限。Codex 有 agent_max_depth 防止递归爆炸。Claude Code 有 Terminal 枚举(11 种终止原因)——max_turns、prompt_too_long 等,这些都是收敛保证机制。在实际系统中,简单粗暴的上限比复杂的启发式更可靠。
M5. 多 Agent 系统的上下文窗口管理怎么做
面试官意图:考察大规模多 Agent 系统的上下文工程。
推荐回答:
多 Agent 的上下文窗口管理比单 Agent 复杂得多——Coordinator 需要同时关注多个 Worker 的结果,token 爆炸是常见故障。
Coordinator 的五条设计原则:
Single Writer:只有 Coordinator 写
goal/plan/completed_steps。Worker 只 appendobservation。避免状态写入冲突。Structured Communication:Worker 返回固定 JSON Schema,不是自由文本。自由文本会导致 Coordinator 的上下文被低信息密度的内容占满。
Summary Compression:Coordinator 只读最近 N 轮 + 全局 completed_list。当上下文 > 80K token,用渐进式摘要把前半段对话压缩成 500 字摘要。
Minimal Information Delivery:Worker 只收自己的任务上下文,不收全局 plan。不给 Worker 不需要的信息,减少 token 消耗。
Instruction Bumping:关键规则追加到消息列表末尾(对抗 Recency Bias)——LLM 天然更关注消息末尾的内容。
渐进式摘要 vs. 结构化状态:
- 渐进式摘要(自然语言):保留语义、但 token 效率低
- 结构化状态(JSON):
{"completed": [...], "failed": [...], "pending": [...]},比自然语言省 70-80% token - 建议:生产环境优先用结构化状态 + 关键信息用自然语言补充
M6. 多 Agent 的评估体系怎么搭建?如何证明多 Agent 比单 Agent 好
面试官意图:考察评估工程的系统思维——"你不能管理你无法测量的东西"。
推荐回答:
评估分四层:
| 层 | 评估什么 | 核心指标 |
|---|---|---|
| L1: 单 Agent 质量 | 每个 Worker 的任务完成能力 | 准确率、延迟、tool_call_success_rate |
| L2: 协调质量 | Coordinator 的拆解和分配能力 | Plan precision/recall、routing accuracy |
| L3: 端到端质量 | 用户目标达成 | Completion rate、用户满意度 |
| L4: 成本效率 | 同等质量下的 token 消耗 | Cost per completion、avg rounds |
多 Agent 准入 Gate(核心):
Stage 1 → Stage 2 准入条件:
✅ 单 Agent eval ≥ 80%
✅ Checkpoint + Trace + 离线 case ≥ 30
Stage 2 留存条件:
✅ 多 Agent eval ≥ 单 Agent + 5%(效果有显著提升)
✅ 多 Agent 成本 ≤ 单 Agent × 2(成本可控)
✅ avg_rounds ≤ 5(不会无限循环)
✅ timeout_rate ≤ 5%(大部分任务能完成)
失败 → 连续 3 天 eval 低于阈值 → 降低流量 → 回滚到单 Agent
关键原则:单 Agent baseline 没做到 80% 之前,不要上多 Agent。多 Agent 是在 baseline 之上解决"一个 Agent 搞不定"的问题,不是用来掩盖单 Agent 质量差的。多 Agent 的成本通常是单 Agent 的 4-5 倍,completion 提升至少 ≥15% 才值得。
学术 Benchmark 的启示:
- **CooperBench(ICLR 2026)**发现:多 Agent 协作比单 Agent 独立完成成功率下降 30%——"协调的诅咒"。说明多 Agent 不是天然更好,设计不好反而更差
- SILO-BENCH 发现:"Communication-Reasoning Gap"——Agent 自发形成了正确的通信拓扑,但在推理整合阶段系统性失败。通信是成功的,但综合判断是失败的
- GEMMAS 发现:两个系统准确率只差 2.1%,但过程指标(信息多样性、冗余路径比)差 12.8% 和 80%。只看结果指标会漏掉关键问题
我的实践:FuserLab 的评测体系——离线 benchmark(RexUniNLU)+ case-level failure 分析 + badcase 回灌闭环。这套方法论可以直接扩展到多 Agent 评估。
M7. 多 Agent 的分布式 Tracing 与成本归因怎么做
面试官意图:考察生产环境可观测性。
推荐回答:
多 Agent 的 Tracing 核心挑战是:一次用户请求可能被 Coordinator 拆成 N 个子任务,每个子任务被不同的 Worker 执行,Worker 之间可能还有嵌套调用。追踪链必须完整覆盖这个"一对多 + 嵌套"的拓扑。
Trace 链路设计:
User Request (root span)
└─ Coordinator.plan (span, parent=root)
├─ Worker_A.execute (span, parent=plan, agent_id="A")
│ ├─ Worker_A.llm_call (span)
│ └─ Worker_A.tool_call (span)
├─ Worker_B.execute (span, parent=plan, agent_id="B")
└─ Coordinator.synthesize (span, parent=root)
每个 span 除了常规的 trace_id/span_id/parent_id/duration/token/ttft/error,还要加:
agent_id:哪个 Agent 产生的agent_role:Coordinator / Worker / Reviewertask_id:属于哪个子任务round_number:第几轮协调
成本归因:token 消耗按 agent_id 分组,可以看到每个 Agent 的 token 占比。如果 Coordinator 的 token 占到了 50% 以上——说明任务拆得太碎、协调开销过大,该合并一些 Agent。
关键指标看板:
- 每个 Agent 的 P50/P99 延迟
- 每个 Agent 的 token 占比
- Agent 间调用次数分布(识别热点通信路径)
- 失败率按 Agent 分组(识别"问题 Agent")
- Loop detection 触发次数
我的实践:FuserLab 的 TraceCallbackHandler 把 trace_id 贯穿全链路,span 之间通过 parent_id 串联。扩展到多 Agent 时,只需要在 span 上加 agent_id 和 task_id 两个字段——核心的 trace 基础设施不需要推翻重建。
M8. 什么情况下「不应该」上多 Agent
面试官意图:考察工程判断力——"知道什么时候不做"比"知道怎么做"更难。
推荐回答:
多 Agent 只在三个条件同时满足时才有意义:
- ✅ 子任务可并行且上下文可隔离
- ✅ 角色技能差异显著(需要不同的 system prompt + 工具集)
- ✅ 单 Agent 完成率 < 目标(baseline ≥ 80% 后再考虑)
不应该上多 Agent 的六种情况:
- 任务简单、步骤固定:一个 Chain 就能搞定的事,引入多 Agent 是杀鸡用牛刀
- 单 Agent 效果还没做扎实:baseline 不到 80%,先优化 prompt、工具、RAG,别急着加 Agent
- 上下文强耦合:子任务之间有大量共享状态,拆开反而需要花更多 token 来传递上下文
- 延迟敏感的场景:多 Agent 的协调开销(Coordinator 推理 + 消息传递 + 结果汇总)会增加端到端延迟
- 团队没有多 Agent 运维经验:多 Agent 的出问题路径是指数级的——2 个 Agent 有 A→B 和 B→A 两条错误路径,5 个 Agent 有 20 条
- Tool 调用就能解决:如果给模型更好的 tool description 和更多 tool 就能搞定,加 Agent 是过度设计
一句话总结:多 Agent 解决的是"角色维度"的问题——不同的 Agent 以不同的身份思考。如果你的场景不需要不同的身份,给一个 Agent 加更多 tool 通常是更优解。
四、工具系统与协议
C0. MCP vs Function Calling 深度对比:原理、架构与选择
面试官意图:这是字节面试的深度追问——不仅要知道"区别是什么",还要讲清楚两者的底层原理。
推荐回答:
一、一句话定性
Function Calling 是模型厂商的"内置特性",MCP 是跨模型的"开放协议"。 Function Calling 解决"模型怎么调用工具"的问题,MCP 解决"工具怎么被 Agent 生态系统发现和管理"的问题。它们处于不同抽象层,互补而非竞争。
二、Function Calling 的底层原理
核心:LLM 通过特殊 Token 被训练成"工具调用状态机"。
① 训练层面:模型不是靠 prompt 学会调用工具的,而是通过微调(SFT + RL)在学习阶段就内化了工具调用模式。
模型微调时使用特殊 Token 标记不同阶段:
| Token 类型 | 示例 | 作用 |
|---|---|---|
| 工具声明 | <|im_system|>tool_declare | 包裹 function schema 注入 system prompt |
| 工具调用 | <start_function_call> / [TOOL_CALLS] | 标记模型要输出一个结构化调用 |
| 工具结果 | [TOOL_RESULTS] / <|tool_result|> | 包裹外部执行结果,回传给模型 |
| 质量条件 | <|high_reward|> | RL 训练时用于区分优/劣轨迹 |
② 推理层面:Thought → Act → Observe 三段循环
用户输入 → 模型内部推理(Thought:需要调什么工具?)
↓
模型输出 <start_function_call> + 结构化 JSON(Act:精确的工具名+参数)
↓
系统执行工具,拿到真实结果
↓
结果注入回上下文 [TOOL_RESULTS](Observe:模型理解结果)
↓
模型判断:继续调工具 or 输出最终回答?
↓ 需要更多信息
回到 Thought,循环
③ tool_choice 参数控制:
| 值 | 含义 |
|---|---|
auto | 模型自行判断是否需要调工具 |
none | 禁止调工具,纯文本回复 |
required | 必须调工具 |
{"type": "function", "function": {"name": "xxx"}} | 强制调指定工具 |
④ 底层数据结构:工具定义以 JSON Schema 形式注入 messages 数组的 system/tool 消息。模型输出的 tool_calls 数组包含 id、function.name、function.arguments(JSON 字符串)。下一个 turn 以 role: "tool" + tool_call_id 回传结果。
⑤ 关键局限:每个模型厂商有自己的 function calling 格式——OpenAI 用 parameters + strict 模式,Anthropic 用 input_schema,Google 用 OpenAPI 子集。工具定义不能跨模型复用,切换模型 = 重写工具定义。
三、MCP 的底层原理
核心:基于 JSON-RPC 2.0 的三层 Client-Host-Server 架构。
① 三层架构:
Host(AI 应用) — 管理者
├── 管理安全策略、用户同意
├── 协调 LLM 集成
└── 创建多个 Client 连接不同的 Server
Client(连接器) — 1:1 连接一个 Server
├── 每个请求附带协议版本 + 能力声明
└── 与 Server 建立双向 JSON-RPC 通道
Server(工具提供方) — 暴露 Tools / Resources / Prompts
├── 独立部署、独立版本、独立生命周期
└── 不知道对话上下文(Host 隔离)
② JSON-RPC 2.0 消息格式:
请求(有 id → 需要响应):
{
"jsonrpc": "2.0",
"id": 1,
"method": "tools/call",
"params": {"name": "weather", "arguments": {"city": "Beijing"}}
}
响应(匹配请求 id):
{
"jsonrpc": "2.0",
"id": 1,
"result": {"content": [{"type": "text", "text": "Beijing: 22°C"}]}
}
通知(无 id → 不需要响应):
{
"jsonrpc": "2.0",
"method": "notifications/progress",
"params": {"progress": 50}
}
③ 连接生命周期:
1. Initialize → Client 发 capabilities + protocol version
2. Server 回传 capabilities(有哪些 tools/resources/prompts)
3. Client 发 initialized 通知
4. 双向通信(工具调用 / 资源读取 / 提示词获取)
5. Shutdown(graceful disconnect)
④ 传输层:
| 传输方式 | 适用场景 | 原理 |
|---|---|---|
| stdio | 本地进程 | 子进程 stdin/stdout 走 JSON-RPC,延退最低 |
| Streamable HTTP(推荐) | 远程服务 | 单一 HTTP endpoint 支持双向流式 JSON-RPC |
| HTTP + SSE | ⚠️弃用 | 双通道模式,被 Streamable HTTP 取代 |
⑤ 核心设计原则:
- 无状态:每个请求自包含(协议版本 + 客户端身份 + 能力声明),Server 不推断历史状态
- 动态发现:Client 通过
tools/list实时查询 Server 有哪些工具,而非硬编码 - Server 看不到对话上下文:Host 只发送必要的参数给 Server,对话历史不泄露到工具提供方
四、核心差异对比表
| 维度 | Function Calling | MCP |
|---|---|---|
| 本质 | LLM 推理能力的一环(特殊 Token + 微调) | 工具生命周期管理协议(JSON-RPC 2.0) |
| 抽象层 | 模型层:"模型怎么输出工具调用" | 架构层:"工具怎么被注册、发现、调用、管理" |
| 工具发现 | 静态:每次请求显式传入 tools 数组 | 动态:tools/list 实时查询 Server 能力 |
| 跨模型 | ❌ 每家的 FC 格式不同 | ✅ 同一 MCP Server 对接任何兼容的 Agent |
| 状态 | 完全无状态,每次请求独立 | 协议无状态,但支持持久连接 |
| 热插拔 | ❌ 改工具 = 改代码部署 | ✅ Server 独立部署,动态注册/卸载 |
| 安全模型 | 依赖模型层的 policy | 协议层:OAuth 2.1 / RBAC / 审计 |
| 工具定义 | JSON Schema(内联在 API 请求中) | JSON Schema(在 Server 侧定义,tools/list 返回) |
| M×N 问题 | M 个应用 × N 个数据源 = M×N 次集成 | M 个应用 + N 个 MCP Server = M+N 次集成 |
| 延迟 | 零额外开销(模型直出) | 多一层 JSON-RPC 转发,额外 1-5ms |
五、协作模式:分层而非对立
两者在实际架构中处于不同层次,可以且经常同时使用:
┌─────────────────────────────────────────┐
│ LLM 推理层 │
│ Function Calling: 模型输出 tool_call │
│ "我需要调用 weather 工具,城市=北京" │
└──────────────────┬──────────────────────┘
↓ tool_call JSON
┌─────────────────────────────────────────┐
│ MCP 协议层 │
│ Client → tools/call → Server │
│ 标准化传输、认证、审计 │
└──────────────────┬──────────────────────┘
↓ JSON-RPC
┌─────────────────────────────────────────┐
│ 工具执行层 │
│ weather("Beijing") → {"temp": 22} │
└─────────────────────────────────────────┘
一句话:Function Calling 是"模型的嘴",MCP 是"工具的插座"——嘴说想用什么工具,插座保证工具能插上。
六、选型指南
| 场景 | 方案 |
|---|---|
| 快速原型、单一模型 | 直接用 Function Calling,简单快速 |
| 多模型切换、跨平台 | MCP 统一工具接口,一次集成到处能用 |
| 企业级多工具系统 | MCP + Function Calling 分层:MCP 管工具生命周期,FC 管意图理解 |
| 本地工具(文件系统、数据库) | MCP stdio transport,最低延迟 |
| 外部 API(天气、搜索、支付) | MCP Streamable HTTP + OAuth 2.1 |
| 需要严格审计合规 | MCP 的 OAuth + RBAC + 审计日志 |
C1. MCP 协议在 Agent 系统中起什么作用
面试官意图:考察对行业标准的理解和实际集成经验。
推荐回答:
MCP(Model Context Protocol)解决的是 Agent 工具生态的标准化问题。在 MCP 之前,每个 Agent 平台都要自己定义工具的注册、发现、调用协议——Coze 有 Coze 的插件协议,LangChain 有 LangChain 的 Tool 抽象,各自不互通。MCP 的价值在于让工具提供方只实现一次,所有兼容 MCP 的 Agent 平台都能用。
我在 FuserMate 中直接集成了 MCP server。MCP 工具和内置工具通过 ToolRegistry 统一管理,模型调用时无差别使用。
但 MCP 目前也有不成熟的地方:工具的 input_schema 描述质量参差不齐,直接影响模型正确调用率;没有标准化的工具测试和评测机制;安全模型还不完善。对比 Coze 的插件生态,Coze 的优势是插件经过了平台审核和质量把控,但代价是只能在 Coze 内使用。MCP 是开放标准,生态更广但质量不可控。长期看,MCP 应该会成为 Agent 工具的标准协议。
可能追问:MCP 和 Function Calling 是什么关系?→ MCP 是工具发现和生命周期管理协议,Function Calling 是模型调用工具的具体接口。它们是不同抽象层的东西,互补而非竞争。
C2. Skill 和 Tool 的区别是什么?怎么设计 Skill 体系
面试官意图:考察对 Agent 能力封装的抽象能力。
推荐回答:
Tool 是原子能力——做一件事,比如读文件、执行 bash、调 API。Skill 是可复用的提示词 + 工具组合——封装了一个领域知识或工作流。
举个例子:一个"代码审查"Skill,内部包含 System Prompt(审查标准和关注点)、工具白名单(Read、Grep、Glob)、输出格式要求(问题分级、修复建议)。用户说"/review",Skill 被加载,注入到上下文,模型在这个 Skill 框架下工作。
FuserMate 的 Skill 系统参考了 Claude Code 的 /skill 机制。Skill 放在 ~/.claude/skills/ 目录下,每个 Skill 是一个 Markdown 文件,包含触发关键词、system prompt 扩展、推荐工具集。
Skill 体系的设计要点:
- 触发方式:命令式(用户手动
/skill-name)和自动匹配式(根据用户意图自动加载) - 作用域:全局 Skill 和项目 Skill
- 权限控制:Skill 声明需要的工具,但实际能否调用仍受安全策略控制
- 组合性:Skill 之间不冲突,可以同时加载多个
理想情况下,Skill 底层可以调用 MCP 工具,形成"Skill → Tool (MCP) → 执行"的层次。
C3. 工具调用失败了怎么办?重试策略怎么设计
面试官意图:考察 Agent 工程化的容错设计。
推荐回答:
FuserMate 的 ToolExecutor 对工具失败做了三层处理:
第一层:可重试错误。 比如网络超时、临时资源不可用。策略是指数退避重试 2 次(1s → 2s),失败后进入第二层。
第二层:参数错误。 比如模型传了错误的参数类型、缺少必填字段。不会重试——因为重试同样的参数大概率还是失败。而是把 error message + 工具的 input_schema 一起返回给模型,让模型理解"哪里错了、正确格式是什么",然后修正参数重新调用。
第三层:不可恢复错误。 比如权限拒绝、资源不存在、系统硬拒绝。返回清晰的 error,附带建议替代方案——比如"文件不存在,可能是路径错误?试试用 Glob 搜索类似文件名"。
这里有一个重要设计:错误信息要结构化返回。工具异常统一抓取,转成 {"error": "...", "suggestion": "...", "retryable": false} 格式。模型更容易理解结构化错误,而不是自由文本的 traceback。
还有一个预防性保护:工具结果截断。大结果(bash/read/grep 等)默认截断到 50,000 字符,保留 truncated: true 标记——防止一个 cat 大文件 撑爆上下文窗口。
可能追问:怎么防止模型死循环重试同一个工具?→ max_iterations=200 上限 + 连续调用同一工具且连续失败超过 3 次,主动插入系统提示让模型换策略。
C4. 工具太多时怎么管理?工具路由怎么做
面试官意图:考察大规模工具体系的架构能力。
推荐回答:
工具太多时,核心问题不是"存不下"——模型上下文窗口越来越大——而是模型在选择工具时的注意力分散。30 个工具的定义塞进 system prompt,模型容易选错工具,而且 token 消耗大幅增加。
FuserMate 的做法是按维度做工具分层注入:
- 按 toolset 分组:每个工具标记所属的 toolset(如
file_ops、web、database),Agent 配置时按场景选择注入哪些 toolset - 按 risk_level 过滤:高风险工具(如 bash、write)在特定场景可以不注入
- 按 enabled_by_default 控制:只有通用工具默认启用,领域专用工具需要显式开启
如果要做得更细,可以引入工具路由器——一个轻量级分类模型在调用前判断用户意图属于哪个领域,然后只注入该领域的工具集。这个分类器的延迟必须很低(<100ms),否则得不偿失。
Coze 在这方面的做法是工作流节点级别的工具绑定——每个节点只暴露需要的工具,天然避免了工具爆炸。但代价是灵活性下降。所以这是一个经典的"灵活 vs. 可控"的权衡。
C5. 你们怎么处理高并发下的模型推理延迟
面试官意图:考察大规模 AI 系统的性能优化实战经验。这是你的最强差异化武器。
推荐回答:
我实际部署和调优过 vLLM-Ascend 推理集群,用的是华为 910B 8 卡 NPU,部署了 4 个千问系列模型。
有一次线上报 sequence length exceeds max_model_len——集成了 RAG 知识库后,system prompt 膨胀,加上工具定义数量增加,单次输入超过 5000 token,超过了当时的 max-model-len 5500。
调整分三步:
第一步,拉 max-model-len 到 12000,同时把 gpu-memory-utilization 从 0.9 降到 0.85。因为长上下文的 KV cache 需求翻倍了,不降显存利用率会 OOM。
第二步,调并发参数。max-num-seqs 从 64 降到 32——单个请求的显存占用变大,并发要收敛。
第三步,确认 chunked-prefill 和 prefix-caching 开着。chunked-prefill 防止长 prompt 一次性 prefill 时显存峰值过高;prefix-caching 复用 system prompt 和工具定义的 KV cache,长上下文场景收益非常明显。
还有一个重要参数是 block-size。vLLM 默认 16,我们改成了 128。长上下文场景下 block_size=16 会切出上千个块,block manager 调度开销很大。128 把块数降到 1/8,910B NPU 对大块连续内存也更友好。代价是内部碎片——短回复会有一些浪费,但在 8k-12k 输入场景下这点碎片可以忽略。
网关层并发控制:模型前面还有一层 OpenResty + Redis 网关。Qwen3-30B-A3B 设了全局并发上限 64、生产 40、测试 24,max_wait 60 秒。模型层面的参数调整和网关层面的并发控制要一起看,不能只调一边。
另外在 FuserLab 的语音控制中,我定位过一个非模型延迟问题。通过 stage timing 把链路拆开计时后发现,延迟不在 LLM 推理,而在 LangGraph Postgres checkpointer 的连接创建。我把 AsyncConnectionPool 初始化移到应用启动阶段,请求时直接复用,延迟明显下降。Agent 系统的延迟瓶颈经常不在模型本身,而在基础设施的初始化开销。
参数速查:
- 报
sequence length exceeds→ max-model-len 不够 - 报 OOM/device memory failed → gpu-memory-utilization 太高
- 请求排队超时 → 并发数太高
可能追问:
- block-size 为什么是 128 不是 256?→ 256 碎片率太高,128 是长上下文和碎片之间的平衡点
- max-model-len 为什么不直接设 32K?→ 显存有限,设越大并发越低。12K 够覆盖绝大部分请求。参数调优不追求极值,追求当前负载下的最优平衡
C6. Agent 系统的"千人千面"个性化怎么实现
面试官意图:考察个性化架构设计能力。
推荐回答:
"千人千面"在 Agent 系统里本质是三个维度的个性化:
第一个维度是配置级个性化。FuserMate 的核心模型拆分——Agent(身份+memory)、Workspace(路径+tools+skills)、Session(对话)——天然支持个性化。每个用户的 Agent 有不同的 system prompt、memory、启用的工具集和 skills。这层是个性化的基础。
第二个维度是记忆级个性化。用户的偏好、习惯、历史决策通过 memory 系统持久化。不是把原始对话存下来,而是结构化提取:用户偏好哪些工具、喜欢简短还是详细回复、有哪些重复性任务模式。下次对话时按相关性检索注入上下文。
第三个维度是行为级个性化。这层最复杂。比如同一个"帮我分析数据"的请求,数据科学家希望直接跑 Python 脚本,产品经理希望在 Excel 里呈现,管理者希望一个高层摘要。系统需要根据用户的角色和历史行为模式,自动选择不同的执行策略和输出格式。
技术实现上,三层递进:配置是最确定的(用户显式设置),记忆是半结构化的(从对话历史中提取),行为是最模糊的(需要模式识别)。明确的归系统、模糊的归模型——这是个性化架构的核心原则。
C7. Agent 执行链路如何保证长任务的正确性
面试官意图:考察长任务可靠性的工程经验。
推荐回答:
长任务的核心挑战是:执行可能跨分钟甚至小时,期间进程可能重启,工具可能有副作用,模型可能遗忘早期目标。
FuserMate 通过以下机制保证长任务正确性:
执行快照(AgentRun):每次运行启动时,冻结当前的 Agent 配置、Workspace 路径、启用的 tools/skills、memory 状态为一个不可变快照。运行过程中即使外部配置发生了变化,当前运行不受影响。
崩溃恢复(reconcile_startup):服务启动时扫描所有 session,所有 status="running" 的 AgentRun 全部标记为 "failed",记录错误"服务重启或运行进程丢失"。这保证了不会出现"僵尸运行"。
增量持久化:Bash 后台任务每 1000ms 持久化一次输出快照。即使进程崩溃,已经产生的输出不丢失。
Mailbox 消息去重:Mailbox 消息消费后标记 consumed_at。如果运行被取消但后台任务实际已完成,consumed_at 回滚为 None,下次运行时重新消费。
如果要做 K8s 多副本高可用,我的设计方案(docs/agent_checkpoint_interrupt_ha_design.md)是将运行状态拆为 runs、checkpoints、writes、events 四层持久化到 Postgres,用 lease + fencing token 防止多 worker 同时执行同一个 run。
设计原则:Agent 永远不能从自身意图推断执行状态。工具交互必须外部可验证、幂等、可恢复。
C8. 如何降低推理成本?效率 vs 准确性的权衡
面试官意图:考察成本意识和工程权衡能力。
推荐回答:
推理成本优化我从三个层面来做:
缓存层面:vLLM 的 prefix-caching 是性价比最高的优化。System prompt 和工具定义在大多数请求中完全一致,这些 token 的 KV cache 可以直接复用,节省 prefill 阶段的计算。在语音控制场景,这部分能节省约 20-30% 的 prefill 时间。
模型路由层面:不是所有请求都需要最大的模型。简单意图分类、关键词提取用 Qwen3-4B 完全够用,复杂推理才路由到 Qwen3-30B。
上下文压缩层面:FuserMate 的两层压缩同时也是成本优化。MicroCompactor 减少了每轮模型调用的输入 token,CompactionService 让长对话不会无限增长。
效率 vs 准确性的权衡:我的原则是——关键路径上不省。意图识别、工具选择、安全校验这些环节如果出错,后续全部白费。但辅助环节可以省:历史消息的摘要可以用小模型做、非关键检索可以降级到更简单的策略。就像系统工程里的"瓶颈在约束上"——非约束环节的优化空间远大于约束环节。
五、Agent 运行时深挖(AgentLoop / 安全 / Trace / RAG)
D1. AgentLoop 怎么设计(源码级全链路)
面试官意图:考察 Agent 运行时、工具循环和事件模型。
推荐回答(基于源码 backend/src/agent/core/agent_loop.py):
AgentLoop 的入口是 stream(messages),核心是一个 for 循环包裹的迭代过程,默认 max_iterations=200。
每轮迭代做了四件事:
第一步:准备消息。 _prepare_iteration_messages 先调用 context_update_provider 注入动态上下文(比如 system prompt、memory 刷新),然后走 micro_compactor 做上下文压缩——消息太多时自动裁剪,防止超出模型窗口。
第二步:流式调用模型。 _stream_model_response 调用 model_client.stream(messages, tools=tool_definitions),用 _handle_model_chunk 逐 chunk 分类处理:
kind="message"→ 发message_delta事件,累积 assistant_partskind="thinking"→ 发thinking_delta事件,累积 reasoningkind="tool_call"→ 收集到 tool_calls 列表
每个 chunk 之后都检查 cancel_event,一旦被外部 set 就立即中断并发送 run_cancelling → run_cancelled。
第三步:判断结果。 流结束后如果没有 tool_calls,走 message_display hook 处理显示内容,发 message_done,外层收到后发 run_completed 结束。如果有 tool_calls,走第四步。
第四步:执行工具。 先把 assistant 消息(含 tool_calls)append 到消息历史,发 assistant_tool_call_done。然后逐个执行工具,_execute_one_tool 做了五层处理:
- 执行前检查 cancel
- 走
pre_tool_usehook,hook 可以 deny(直接返回失败)或修改工具参数 - 发
tool_call_started - 通过 ToolExecutor 调用
execute_async——异步 handler 直接 await,同步 handler 用asyncio.to_thread放到线程池,避免阻塞事件循环 - 结果发
tool_call_completed或tool_call_failed,走post_tool_usehook
工具结果作为 role="tool" 消息 append 回 current_messages,进入下一轮模型调用。
保护机制有五层:
- max_iterations:默认 200,超出发
run_failed并返回 - cancel_event:在迭代前、每个 chunk 后、工具执行前/后都检查,支持用户随时中断
- 工具结果截断:ToolExecutor 对 bash/read/grep/glob 等大结果默认截断到 50,000 字符,保留 truncated 标记
- 错误 JSON 化:工具异常和 handler 异常统一抓取,转成
{"error": "..."}格式返回给模型,不让异常直接炸掉 loop - 顶层 try/except:任何漏网异常被 catch,脱敏后发
run_failed,不会让 Agent 处在不可知状态
这里有一个重要的设计决策:AgentLoop 本身不做持久化,只生产语义事件。持久化和快照交给外层的 runtime adapter。这样 AgentLoop 保持纯粹,方便测试和替换。
D2. FuserMate Agent 全链路技术细节
面试官意图:深入考察 Agent 平台架构——不只是讲单个模块,而是全链路串联。
整体架构层次:
API (FastAPI) → RuntimeManager → SessionRunner → AgentLoop
↓
CompactionService / MicroCompactor
HookDispatcher / CronService
Mailbox / PostDispatch / Task
1. 用户输入:HTTP POST 或 WebSocket(支持 input/control/ping/resume 四种帧)。请求到达 SessionRunnerService,进入并发控制逻辑。
2. SessionRunner 并发模型:每次运行是独立 asyncio.Task,受 asyncio.Lock 保护。同时间只有一个活跃运行,优先级链:用户在后台运行期间发消息 → 抢占取消后台 → 入队项 FIFO → 后台唤醒。
3. AgentLoop 主循环:max_iterations=200 的 for 循环,每轮四步(详见 D1)。
4. 中断与恢复:中断由 asyncio.Event 实现。服务器崩溃恢复:reconcile_startup() 启动时扫描所有 session,把所有 running 状态标记为 failed。WebSocket 断线恢复:客户端发 resume + cursor,服务端重放事件。
5. 上下文压缩(两层):MicroCompactor 规则级(每轮自动)+ CompactionService 模型驱动(128K 触发)。
6. Mailbox 异步通知机制:后台任务/cron/subagent 完成后写入 mailbox,根据 wake_policy 决定是否唤醒 Agent。消息消费后标记 consumed_at,运行取消则回滚。
7. Hook 系统:基于 RunHookDispatcher 的观察者模式,8 个活跃 hook 事件。merge_hook_decisions() 合并多观察者输出:block 最先、deny 次之、allow/ask 继续。
8. Runtime 事件系统:27 种事件类型,写入 JSONL 文件(每个 session 一个),自动分配递增 cursor。
9. Task / Cron / Background:CronService(croniter + IANA 时区)、PostDispatchService(产物分发)、Background task(bash 子进程 + 1s 快照持久化)。
可能追问:
- AgentLoop 为什么不做持久化?→ AgentLoop 只负责生产语义事件,持久化交给外层 runtime adapter,保持 AgentLoop 纯粹、可测试、可替换
- MicroCompactor 为什么是规则级?→ 跑在每次模型调用前,如果用模型做摘要会增加延迟。规则级零延迟
- Mailbox 怎么避免消息丢失?→ 持久化在 FileRuntimeStore,写完后才标记投递。崩溃时回滚未完成运行状态
D3. 工具安全怎么防 Prompt Injection
面试官意图:考察 Agent 安全意识,JD 明确要求。
推荐回答:
我的核心原则:模型输出不可信,工具执行必须由运行时兜底。
FuserMate 的安全策略没有写在 prompt 里(因为 prompt injection 可以绕过 prompt 约束),而是实现在工具执行入口。具体分四层:
第一层 system_hard_deny:系统级硬拒绝。比如 Bash 命令解析后,识别 git reset --hard、git clean -fdx、curl | bash、rm -rf / 等危险命令,直接拦截。用户 allow 不能覆盖这一层。
第二层 user_deny:用户配置的黑名单。
第三层 user_ask:需要用户确认。中等风险的工具调用触发 pre_tool_use hook,弹窗让用户确认。
第四层 user_allow:默认允许的低风险操作(读文件、搜索等)。
Bash 命令解析是安全的关键。我不是简单做字符串匹配,而是把 Bash 输入解析成 AST:识别 simple command、pipeline(|)、command substitution($(...))、shell -c 等结构,然后逐条命令匹配安全策略。路径工具也会校验:workspace 边界、symlink 追踪、敏感路径限制。
可能追问:
- 为什么 user_allow 不能覆盖 system_hard_deny?→ 因为 system_hard_deny 是对系统完整性的保护,用户可能被社会工程学攻击诱导点击 allow,但系统必须兜底
- 如果模型把危险命令拆成多段怎么办?→ 更彻底的方案是沙盒执行(参考 Codex 的 bwrap/landlock)。FuserMate 选了策略评估而非沙盒,是因为私有化部署不能引入系统依赖——场景驱动的取舍
- 工具调用审计怎么做?→ 全部记录在 runtime events 里,JSONL 持久化,按 session 组织,可以完整回溯
D4. FuserLab 的 Trace 和评测怎么做
面试官意图:考察可观测和评测平台经验。
推荐回答:
Trace 这边,我们没有从零造轮子,而是利用 LangChain/LangGraph 的 callback 机制。我写了一个 TraceCallbackHandler,继承 BaseCallbackHandler,在模型调用的关键节点把运行过程转成 span。每个 span 里记录了 trace_id、span_id、parent_id、span_type、span_name、start_time、duration、app_id、input、output、model、token、ttft 和 error_message。trace_id 在请求入口生成或透传,同一次请求的所有 span 共享一个 trace_id。这样一次用户请求的链路从 root 到 LLM 到 tool 到 prompt 全部串起来,慢在哪里、错在哪一步直接定位。
评测这边,我实际做过语音控制 RexUniNLU 的 benchmark。评测集是按 case 组织的,每个 case 标记了 complexity——简单还是复杂——以及预期的 intent、slots。评估的时候不会只看总分,而是看 case-level failure:intent 有没有漂移、slot 有没有漏抽、复杂指令有没有被简单 schema 误吞。我特别看重的一点是线上 badcase 的回灌——线上 trace 里发现的错误,把它写进离线评测集变成固定 regress case,这样每次改 prompt 或切模型,跑一遍 benchmark 就能看到会不会引入新问题。这个闭环是保证质量不退化的关键。
D5. 你怎么优化过 LangGraph 语音控制延迟
面试官意图:考察端到端性能优化能力。
推荐回答:
整体链路是 ASR → LLM → TTS 三段。ASR 用了流式识别减少等待,但流式的准确度会下降,所以在关键搜索接口上用了 AC 自动机对输入文本做关键词标注,直接告诉大模型"这是一个实体词",减少模型误判。TTS 也是实时的,LLM 边输出 TTS 边播,减少用户体感延迟。
LLM 部分,对于多候选反问用户这种固定场景,没有让模型去驱动产生文本,而是用规则直接生成候选展示文本,然后把上下文手动拼回对话,省掉一次 LLM 调用。
除了端到端链路的优化,我还专门定位过 LangGraph 自身的热路径问题。最初拿到慢请求,我没有直接猜是 LLM 慢了,而是给链路加了 stage timing,把 prompt build、LLM prepare、LLM stream、tool node、after_tools route、checkpointer get、workflow compile、graph invoke 全部拆开计时。结果发现不是 LLM 慢,而是 Postgres checkpointer 的连接创建和 setup 进入了请求热路径——每次请求都要建连接、初始化 saver。我把 AsyncConnectionPool 和 AsyncPostgresSaver 的初始化移到了应用启动阶段,请求时通过 get_langgraph_checkpointer() 直接复用连接池,关闭时统一 cleanup。这样基础设施开销被移出了用户请求路径。
之所以用 Postgres 而不用内存 checkpointer,是因为语音控制需要状态持久化——用户可能中断对话后回来继续,内存会丢状态。
D6. RAG 你实际做到了哪一步
面试官意图:确认 RAG 落地深度,区分实做和方案。
推荐回答:
我实际落地更熟的是 FuserLab 的 pgvector + unstructured 知识库中台。链路上包括文档解析、切分、向量化入库、按 dataset_id/query/top_k/score_threshold 检索,返回内容、文档名和页码命中,用于智能客服、热词匹配等业务。
对于 JD 里 Milvus + ES 的混合检索,我不把它说成已深度落地。我理解的方案是向量召回和全文召回并行,候选合并后用权重或 rerank 排序;引用溯源要保留 document、page、chunk;权限过滤尽量前置到 dataset/document 范围,最终返回前再强校验。
为什么选 pgvector 不用 Milvus+ES?
- 业务评估两年内知识库规模不大,pgvector 够用
- 团队人少,天翼云有 pgvector 托管服务可直接采,Milvus 需要自建维护
- ES 尝试过 pgvector 插件分词但版本不兼容,BM25 预留接口但优先级后移
- 检索客户端已抽象,将来切 Milvus 不改业务代码
可能追问:
- pgvector 和 Milvus 怎么选?→ 规模 < 千万级用 pgvector(免运维),规模更大用 Milvus(性能更优)
- chunk 怎么切?→ 默认递归分割,高精度用语义分割,表格/代码单独处理
- 权限过滤放检索前还是检索后?→ 尽量前置到 candidate set 生成阶段,最终返回前再做强校验
六、LangGraph 与编排框架
本章基于网上 LangGraph 面试真题 + 你的 FuserLab LangGraph 实战经验编写。 LangGraph 是字节 Coze JD 明确提到的框架,也是面试中最高频的技术深挖方向。
L1. LangChain 和 LangGraph 的核心区别是什么
面试官意图:这是字节面试最高频的问题。面试官想区分"用过 Demo"和"真正落地过生产项目"的候选人。
推荐回答:
一句话总结:LangChain 是零件库(组件),LangGraph 是装配线(编排)。LangChain 做简单线性流程,LangGraph 做复杂有状态的图编排。
| 维度 | LangChain | LangGraph |
|---|---|---|
| 核心抽象 | Chain(链式调用) | StateGraph(有向状态图) |
| 流程类型 | 线性 / 顺序执行 | 支持循环、分支、条件跳转 |
| 状态管理 | 隐式(黑盒) | 显式 State 管理,每个节点都能读写 |
| 可控性 | 低——AgentExecutor 是黑盒 | 高——每个 Node/Edge 由你定义 |
| 失败处理 | 不可定制 | 完全可定制重试/回滚/中断 |
| 可观测性 | 难以追溯内部决策 | 节点级日志 + 状态快照 |
| 适合场景 | 简单文档问答/直线流程 | 复杂 Agent/多步骤推理/生产级系统 |
我两个项目正好体现了这两种模式的适用场景:
FuserLab 的语音控制用 LangGraph,因为业务流程是 agent → tools → agent → ask_user,有明确的状态流转和循环,用 StateGraph + ToolNode + checkpointer 刚好匹配。
但我没有在 FuserMate 用 LangGraph——因为 FuserMate 的核心复杂度不在图拓扑,而在平台运行时:精细控制流式事件拆分(thinking/message/tool_call 的语义拆分)、工具生命周期管理、WebSocket 推流、Hook 系统。LangGraph 的 ToolNode 是一个黑盒,不会给你中间的事件粒度。所以自研 AgentLoop 更合适。
加分表达:
"LangGraph 的核心价值不是让流程更复杂,而是让失败路径变得可设计。状态不可见的 Agent,本质上是不可治理的。80% 的价值体现在失败路径上,而不是成功路径。"
可能追问:
- 两者能结合使用吗?→ 完全可以。LangChain 提供组件(ChatModel、Tool、PromptTemplate),LangGraph 负责编排这些组件。FuserLab 就是这样用的——LLM 调用走 LangChain 的 ChatOpenAI,流程编排走 LangGraph 的 StateGraph
- 什么时候只用 LangChain 就够了?→ 简单 QA、单次文档总结、不需要循环和分支的直线流程
L2. StateGraph 三要素:State / Node / Edge 怎么设计
面试官意图:考察对 LangGraph 核心抽象的深入理解。
推荐回答:
State:共享状态容器,是 Graph 的"记忆"。每个 Node 读取当前 State,返回部分更新,LangGraph 自动合并。State 的定义可以用 TypedDict 或 Pydantic BaseModel。关键设计原则:State 是所有节点的唯一通信渠道——节点之间不能直接调用,只能通过 State 传递信息。
Node:执行单元。接收当前 State,返回 {"key": "new_value"} 格式的部分更新。职责必须单一——一个 Node 失败时,失败原因必须是单一可解释的。我在 FuserLab 语音控制里拆了 agent node(LLM 推理)、tools node(工具执行)、ask_user node(多候选时人工确认),每个 Node 只做一件事。
Edge:控制流载体。普通边(固定 A→B)和条件边(根据 State 的某个字段动态路由)。条件边的价值在于把模型的决策范围限制在结构化选项里——模型产出的是结构化结果(比如 next: "tools" or next: "ask_user"),规则路由做判断,不是让模型自由决定下一步做什么。这样可以防止 Agent 跑偏。
这三者的关系类比:State 是共享内存,Node 是 CPU 指令,Edge 是程序计数器——决定了执行什么、以什么顺序、在什么条件下跳转。
面试易错点:
- 不要把 Node 说成"就是函数封装"——Node 的核心是 State 读写契约
- Edge 不能只说"连接节点"——必须提条件路由和循环控制
- 不要忽略 State 的不可变性约束——Node 返回的是更新而非直接修改
L3. Checkpoint 机制:状态持久化与断点恢复怎么实现
面试官意图:Checkpoint 是 LangGraph 最核心的机制,面试必追问。
推荐回答:
Checkpoint 的本质是 State 在时间维度上的版本快照机制。每个 superstep(一个或多个 Node 执行完成)后,Graph Engine 自动触发一次 Checkpoint 写入,将当前 State 序列化后存入持久化层。
触发时机:每个 step 边界触发(不是每个 Node——一个 step 可以包含多个 Node 的并行执行)。这意味着即使进程崩溃,也能恢复到最近的安全点。
持久化后端选型:
| 后端 | 写入延迟 | 进程重启保留 | 并发支持 | 适用场景 |
|---|---|---|---|---|
| MemorySaver | <1ms | ❌ | 单进程 | 本地调试/PoC |
| Redis | 1-3ms | ✅ | 高并发 | 生产高并发 |
| PostgreSQL | 5-15ms | ✅ | 高并发 | 企业多租户/合规审计 |
| SQLite | 2-5ms | ✅ | 单进程 | 边缘部署 |
我在 FuserLab 语音控制里选择了 PostgreSQL checkpointer(AsyncPostgresSaver)。原因有两个:第一,语音控制需要状态持久化——用户可能中断对话后回来继续,内存会丢状态;第二,Postgres 是我们已有的基础设施,不需要引入新的存储组件。
恢复机制:通过 thread_id 定位会话,checkpoint_id 指定恢复到哪个版本。checkpoint 之间通过 parent_checkpoint_id 串成历史链,支持回溯到任意历史版本——类似 Git commit。
踩过的坑:最初 Postgres checkpointer 的连接创建和 setup 进入了请求热路径——每次请求都要建连接、初始化 saver,拖慢了整体延迟。优化方式是把 AsyncConnectionPool 和 AsyncPostgresSaver 的初始化移到应用启动阶段,请求时通过 get_langgraph_checkpointer() 直接复用连接池。
可能追问:
- 最大快照数有限制吗?→ 理论上无限,取决于存储成本。生产环境建议按 TTL 清理旧 checkpoint
- 并发写入冲突怎么解决?→ LangGraph 内部通过
thread_id+checkpoint_id的乐观锁机制,写入前检查版本号 - Redis 不可用怎么降级?→ 降级到 MemorySaver 并打告警,但会牺牲持久性——只适用于短期容错
L4. Human-in-the-Loop 怎么设计?三种插入范式
面试官意图:考察 HITL 的工程落地能力。
推荐回答:
HITL 的核心是在 Agent 自主执行的关键节点插入人工决策,而不是每一步都让人点确认。LangGraph 支持三种 HITL 插入范式:
范式一:条件边中断(Conditional Edges) 在 Edge 层面做路由。比如 Node 输出 confidence_score,条件边判断:score > 0.9 → 继续执行;score < 0.9 → 路由到人工审核节点。适合低风险、需要"软把关"的场景。
范式二:工具节点拦截(interrupt_before / interrupt_after) 在 Node 执行前后暂停。interrupt_before=["tools"] 会在执行工具节点前暂停,等人工审批后再执行。适合中等风险的工具调用(发消息、修改数据库)。我在 FuserMate 里通过 pre_tool_use hook 实现了类似效果——hook 可以 deny 单个工具调用。
范式三:状态机断点(Checkpoint + interrupt) 在 Graph 的任意节点通过 interrupt() 函数主动暂停,整个 State 被持久化到 Checkpoint。等人工操作完成后,通过 Command(resume=...) 恢复执行。适合长周期异步审核——审核可能跨小时甚至跨天,进程可以重启,状态不丢。
设计原则:
- 异步优先:审核不阻塞主流程,不要让 P99 被审核拖爆
- 按操作风险分级:低风险自动、中风险审批、高风险升级
- 审核超时兜底:超过 N 小时未审批,自动拒绝或降级处理
面试大坑:不要把 HITL 等同"加个确认按钮"。HITL 是架构级设计——什么操作需要人介入、以什么粒度介入、超时怎么处理、审核队列积压怎么办——这些都要系统性考虑。
L5. State Schema 设计:TypedDict vs Pydantic 怎么选
面试官意图:考察生产级 State 设计经验。
推荐回答:
| 维度 | TypedDict | Pydantic BaseModel |
|---|---|---|
| 校验能力 | 无运行时校验 | 完整运行时类型校验 |
| 默认值 | 不支持 | Field(default=...) |
| 可观测性 | 无 description | Field(description="...") 出现在 LangSmith Trace |
| 依赖 | Python 标准库 | 需安装 pydantic |
| 性能 | 极轻量 | 有微小序列化开销 |
| 适用场景 | 原型 / ≤5 字段 | 生产 / 多 Agent / 复杂嵌套 |
选型建议:原型阶段用 TypedDict 快速验证 → 进入生产或多人协作迁移到 Pydantic。迁移成本不高——主要是加 Field(description=...) 做可观测性增强。
State 设计的三层拆分(我在 FuserLab 的实际做法):
- 业务输入:
user_query、context_id、session_id——用户请求直接映射 - 过程状态:
messages(对话历史)、tool_results(工具结果)、intermediate_steps——Agent 执行中的中间产物 - 决策结果:
next_action(下一步路由)、confidence_score(置信度)、final_response——用于 Edge 路由和最终输出
这个拆分的价值在于:不同层有不同的更新策略。业务输入基本不变(默认覆盖即可),过程状态需要追加(messages 用 append Reducer),决策结果是单值覆盖。
L6. Reducer 机制与状态合并策略
面试官意图:考察对 LangGraph 状态合并机制的深入理解。
推荐回答:
Reducer 决定了多个 Node 或并行分支对同一个 State key 写入时,最终值怎么合并。
默认 Reducer:last-write-wins(后写入覆盖先写入)。适合单值字段——next_action、final_response、confidence_score。
自定义 Reducer 的三种模式:
append(列表追加):
messages字段最常用。每个 Node 返回新的消息片段,Reducer 自动追加到消息列表末尾,而不是覆盖整个列表。这样 Agent 的多轮对话逐步累积。deep merge(深度合并):适合嵌套 dict 结构。比如
agent_state = {"plan": {"step1": "done"}, "context": {...}},不同 Node 更新 plan 的不同 key,Reducer 自动合并而非覆盖。自定义聚合:比如多个并行分支对同一个
score字段投票,Reducer 取平均值或最大值。
并发写入问题:多个并行分支同时写同一个 key 时,默认 Reducer 的执行顺序不确定。解决方案:① 用条件边分流,保证同一时刻只有一个分支写;② 自定义 Reducer 做确定性合并(如 timestamp merge)。
实际踩坑:LangGraph 的 State 默认是覆盖式更新。如果你在 Node A 里设了 state["context"] = {...},Node B 如果不显式返回 context,State 里的 context 会保持 Node A 的值——但如果你在 Node B 里返回了 {"context": None},它会覆盖掉。这就是为什么每个 Node 的返回值要精确——只返回你真正想改的字段。
L7. Node 粒度如何控制?Conditional Edge 如何限制 Agent 决策
面试官意图:考察 LangGraph 流程设计的工程判断力。
推荐回答:
Node 粒度原则:一个 Node 失败时,失败原因必须是单一可解释的。如果你问"这个 Node 为什么失败了",答案不能是"可能是 LLM 调用超时,也可能是工具 Schema 解析错误,也可能是业务逻辑 bug"。这意味着你要把 LLM 调用、工具执行、校验逻辑拆成不同的 Node。
反模式:把整个 Agent 逻辑塞进一个巨大的 Node(类似 LangChain 的 AgentExecutor)。这样做确实能跑,但出了问题时你完全不知道是哪一步出了问题。
Conditional Edge 的核心价值:限制模型的决策范围。不是让模型自由说"接下来做什么",而是让模型产出结构化结果,然后规则路由做判断:
# 模型产出结构化决策
def agent_node(state):
response = model.invoke(state["messages"])
return {"next": response.next_action} # 只能是 "tools" / "ask_user" / "finish"
# 规则路由做判断——不是模型说了算
graph.add_conditional_edges("agent", lambda s: s["next"], {
"tools": "tools_node",
"ask_user": "ask_user_node",
"finish": END
})
避免无限循环:在 State 里显式记录执行次数(loop_count),条件边强制校验——超过阈值直接路由到 END 或 error handler。不要依赖模型自己判断"是不是该停了"——模型可能会一直说"还需要再检查一下"。
L8. LangGraph 生产环境的可观测性与失败处理
面试官意图:考察生产级 LangGraph 运维经验。
推荐回答:
可观测性三层设计:
节点级日志:每个 Node 记录输入 State、输出 State、耗时、异常。这不是靠 print 做的——LangGraph 的 callback 机制(
BaseCallbackHandler)可以拦截每个 Node 的开始/结束事件。我在 FuserLab 的TraceCallbackHandler就是基于这个机制,把每个 Node 的执行转成 span,记录 trace_id/span_id/parent_id/duration/token/ttft/error。Checkpoint 快照:每次出问题时,不仅看日志,还要能回放——拿到当时的 State 快照,用相同的输入重跑,看能不能复现。LangGraph 的 checkpoint 天然支持这个。
LangSmith / 自建 Trace 平台:LangSmith 是 LangGraph 的原生可观测平台,但我实际用的是自建 Trace 系统——因为部门有自己的数据合规要求,不能把 trace 数据发到外部服务。
失败处理的分级策略:
- 工具级失败(网络超时、API 限流)→ 重试 2 次,指数退避
- 模型级异常(输出格式错误、JSON 解析失败)→ 校验后重跑一次,带上 format instruction
- 业务级风险(置信度低、权限不足)→ 中断 + 升级人工处理
- 基础设施故障(DB 连接断开、Redis 不可用)→ 降级 + 告警
生产环境常驻问题:
- 长流程 Agent 的 State 会越来越大 → 定期 checkpoint 清理 + 摘要压缩
- 并发场景下同一个 thread_id 被多次触发 → 用
config["configurable"]["thread_id"]的幂等性保证 - P99 延迟被 checkpoint 写入拖慢 → checkpoint 异步写入(队列 + 批量 flush)
L9. 从 LangChain Agent 迁移到 LangGraph 最大的思维转变
面试官意图:这是字节 Agent 平台组的高频题——考察你是否理解两种框架的根本差异。
推荐回答:
最大的思维转变:从「黑盒循环」到「显式流程」。
LangChain 的 AgentExecutor 是一个黑盒——Think → Act → Observe 循环被封装死。你不知道工具被调用了几次、在哪一步失败的、Agent 为什么选了工具 A 而不是工具 B。出了问题时只能猜。
LangGraph 把这一切打开了:
- 每个 Node 做什么一目了然
- 每条 Edge 的跳转条件由你定义
- 可以在任意节点插入日志、审核、人工审批
- State 是所有决策的完整记录——事后可以从 State 快照完整复现一次执行
- 故障定位从 30 分钟降到 5 分钟
我再补充一个核心观点:LangChain Agent 的上下文和决策是隐式堆在 prompt 里的。流程一复杂(多步分析 + 工具调用 + 校验节点),失败路径根本兜不住。一旦某一步返回异常,Agent 要么继续胡编,要么直接走偏,而且你很难知道是在哪个决策点开始出问题的。
LangGraph 把"模型怎么想"和"系统怎么管"分开了——模型负责局部推理(在 Node 里),系统负责全局约束(在 Edge 和 State 里)。这就是我在 FuserLab 选 LangGraph 做语音控制的核心原因——状态机的确定性 + 模型的灵活性,两者各司其职。
L10. 什么情况下「不应该」使用 LangGraph
面试官意图:考察框架选型的判断力,而不是盲目吹捧。
推荐回答:
LangGraph 不是银弹。以下场景不应该用它:
简单 RAG / 一次性问答:用户问一个问题,检索文档,生成答案,结束。没有循环、没有分支、没有状态管理需求。这种场景 LangChain 的 Chain 或者直接调 LLM SDK 就行。引入 LangGraph 反而增加了代码量和维护成本。
无失败兜底需求的场景:如果你的业务能接受"偶尔出错就重试整个流程",不需要精细的失败恢复——那 AgentExecutor 够用。
团队 LangGraph 经验不足且时间紧:LangGraph 的学习曲线比 LangChain 陡——你需要理解 State、Node、Edge、Reducer、Checkpoint、Conditional Edge 等一系列概念。如果团队不熟悉且项目时间紧,用 LangGraph 的风险是被抽象层困住。
需要极致低延迟的实时场景:每次 Node 执行后 LangGraph 都会触发 checkpoint 写入。对于毫秒级延迟敏感的实时场景,这个开销可能不可接受。虽然可以调优(异步写入、Redis 后端),但本质上 checkpoint 是有代价的。
模型自主决策比流程可控更重要的场景:有些场景下 Agent 的创造力比可控性更重要——比如开放式创作、头脑风暴。LangGraph 的显式状态机反而会限制模型的灵活性。
总结一句话:LangGraph 解决的是"可控性"问题,如果你的场景不需要精细的可控性,引入 LangGraph 是过度设计。
七、工具执行引擎(串行/并行/中断/恢复/去重)
本章参考 Claude Code 的
runTools+partitionToolCalls、Codex 的ToolOrchestrator+ sandbox escalation、以及 2025 年学术界 SHIELDA/ALAS 等错误处理框架。 工具执行引擎是 Agent 运行时最核心的工程问题——串并行调度、中断恢复、幂等去重——面试官深挖这个方向时,考察的是你对 Agent "操作系统级"能力的理解。
T1. 工具执行串行 vs 并行怎么设计?Claude Code 和 Codex 是怎么做的
面试官意图:考察 Agent 运行时调度能力——"不是调个 function 就完了"。
推荐回答:
Claude Code 和 Codex 在这个问题上选择了不同的安全策略,但核心分叉逻辑一致。
Claude Code:贪心分区 + 并发上限 10
核心函数 partitionToolCalls() 做贪心分区:
模型输出 N 个 tool_use blocks
↓
partitionToolCalls() 扫描:
连续并发安全工具 → 合并到同一批次
遇到不安全工具 → 切断,独占一个批次
↓
并发批次 → runToolsConcurrently(max=10)
串行批次 → runToolsSerially()
关键设计决策:
- 并发安全判定:
isConcurrencySafe——Read/Glob/Grep 只读类是true;Edit/Write/Bash 修改状态类是false - 并发上限默认 10:通过环境变量
CLAUDE_CODE_MAX_TOOL_USE_CONCURRENCY可覆盖 - 延迟上下文修改(关键!):并发执行期间,每个工具的上下文修改先收集到队列,等整批结束后按原始顺序统一应用——"先记流水,后统一入账",防止竞态
- 串行立即提交:每个工具执行完成后立即提交上下文修改,保证下一个工具看到更新后的状态
Codex:ToolOrchestrator 统一编排 + OS 沙箱
ToolOrchestrator
├── Compute ExecApprovalRequirement(策略 + 权限 + 沙箱上下文)
├── Check cached approvals
├── Guardian 子 Agent 自动审批(可选,低风险)
├── Fallback 到交互式用户审批
├── Select sandbox(Seatbelt/Bubblewrap/Restricted Token)
├── Execute via concrete runtime(ShellRuntime / UnifiedExecRuntime)
└── Retry with relaxed sandboxing on denial
Codex 的并行策略更保守——所有 mutating 工具串行,只有读操作可以并行。sandbox 类型决定权限边界(ReadOnly → WorkspaceWrite → DangerFullAccess),retry 时可以 escalation 提权。
我的实践(FuserMate):工具是逐个执行的(for 循环),没有做并行批处理。这是有意为之——FuserMate 的部署目标是私有化 Docker 单镜像,不需要处理 10 个工具并发的复杂场景。但如果要做并行优化,我会参考 Claude Code 的 isConcurrencySafe 标记 + 延迟上下文提交方案——这是工程上最清晰的做法。
核心原则:
- 只读工具 → 并行(
join_all) - 修改状态工具 → 串行
- 并行时延迟提交上下文修改,串行时立即提交
- 并发上限防止资源耗尽(默认 10)
T2. 工具中断怎么恢复?如何防止中断导致重复执行
面试官意图:考察中断恢复——Agent 系统最容易被忽视的工程难题。
推荐回答:
工具中断的典型故障场景(来自 LiveKit Agents 的真实 bug):
1. 用户通过语音中断(VAD 触发)
2. 工具实际上已成功执行(比如创建了一条数据库记录)
3. 但中断导致 tool result 未被保存到 chat history
4. 下一轮 LLM 推理时,模型不知道工具已执行
5. Agent 重新执行同一个工具 → 重复创建资源
这个 bug 暴露了三个核心问题:① 中断时机;② 中间状态持久化;③ 幂等性。
中断恢复策略分三层:
第一层:中断检查点的粒度
在哪里检查中断,决定了能恢复到什么程度。Claude Code 在以下节点检查 cancel/abort:
每轮迭代前 → 每个 message/thinking/tool_call chunk 后 → 工具执行前 → 工具执行后
FuserMate 的 cancel_event(asyncio.Event)也是同样的粒度。检查点越密集,中断响应越快,但恢复越复杂。
第二层:已完成步骤的持久化(Journaling)
关键原则:工具结果必须在中断发生前持久化,而不是在"一切正常"的流程末尾。
Codex 的做法:每个 tool call 完成后,ToolOrchestrator 立即将其结果写入 journal,不等到整批结束。Claude Code 的做法:_execute_one_tool 完成后立即 append 到 current_messages。
FuserMate 的做法:
- 工具结果通过
RunEventBroker.emit_event()实时写入 JSONL(每个 session 一个文件) - 每个事件自动分配递增 cursor
- 即使 run 被 cancel,已经执行的工具结果已持久化,恢复时可通过 cursor 重放
第三层:恢复时的去重
恢复时(比如 WebSocket 断线重连,或进程崩溃后重启),需要判断哪些工具已经执行过了:
- Cursor 重放:FuserMate 的 WebSocket resume 帧传入 cursor,服务端从该 cursor 重放事件。客户端已经收到的事件不会重复处理
- 幂等 Key:对于有副作用的工具(write、bash、数据库操作),每次调用带
idempotency_key(= tool_call_id),服务端 TTL 缓存(15 分钟),重复 key 返回缓存结果 - Reconcile 扫描:启动时
reconcile_startup()扫描所有status="running"的 run,全部标记为"failed",不会继续执行未完成的工具
设计原则:先持久化,再返回结果。永远不要让"持久化"和"返回结果"之间存在中断风险窗口。
T3. 工具错误如何分类?不同错误类型怎么区别处理
面试官意图:考察你是否能对不同错误做精细化的恢复策略,而非"失败就重试"。
推荐回答:
学术界 SHIELDA 框架定义了 36 种异常类型,落地到工程实践我归纳为四类:
| 错误类型 | 典型场景 | 能否重试 | 处理策略 |
|---|---|---|---|
| 瞬态错误(Transient) | 网络超时、API 限流、临时不可用 | ✅ 可以 | 指数退避重试 2 次(1s → 2s) |
| 参数错误(Parameter) | 模型传了错误类型、缺少必填字段 | ❌ 不应重试 | 返回 error + Schema,让模型修正参数 |
| 持久错误(Permanent) | 权限拒绝、资源不存在、系统硬拒绝 | ❌ 不应重试 | 返回清晰 error + suggestion,建议替代方案 |
| 状态冲突(State Conflict) | 并发写入冲突、版本不匹配 | ⚠️ 条件重试 | 重新读取当前状态 → 重新计算 → 重试 1 次 |
Codex 的错误升级策略(参考):
Tool execution fails
↓
Sandbox denial? → retry with relaxed sandboxing
↓
Still denied? → Guardian 子 Agent 自动审批(低风险操作)
↓
Still denied? → fallback to 用户交互审批
↓
连续 3 次拒绝 → Circuit Breaker 熔断,强制 Agent 换方案
错误信息的结构化返回:
FuserMate 的 ToolExecutor 不对模型返回原始 traceback,而是返回结构化错误:
{
"error": "File not found: /path/to/file",
"suggestion": "检查路径是否正确,或使用 Glob 搜索类似文件名",
"retryable": false,
"error_type": "permanent"
}
为什么结构化?因为 LLM 理解结构化 JSON 比理解自由文本 traceback 更准确——后者容易让模型"误解"错误原因并做出错误的重试决策。
防止同一工具死循环:连续调用同一工具且连续失败 ≥3 次 → 系统层自动注入 "You have called tool X multiple times without success. Consider a different approach." 到上下文。
T4. 工具幂等性怎么设计?如何防止工具重复执行
面试官意图:考察对分布式系统中"恰好执行一次"语义的理解。
推荐回答:
Agent 系统中工具重复执行的根因有三种:
- 中断恢复导致:工具已执行但结果未保存 → 恢复后重新执行
- LLM 重复调用:模型不记得已经调用过,又调了一次
- 并发冲突:多个 Agent 或 run 同时执行同一个有副作用的操作
方案一:幂等 Key(Idempotency Key)
每个有副作用的工具调用带 idempotency_key(= tool_call_id)。服务端维护 TTL 缓存(15 分钟):
async def execute(self, tool_name, params, idempotency_key):
cached = await idempotency_cache.get(idempotency_key)
if cached:
return {**cached, "cached": True} # 不重复执行
result = await actual_execute(tool_name, params)
await idempotency_cache.set(idempotency_key, result, ttl=900)
return result
适用工具:write、edit、bash、数据库操作等有副作用的工具。只读工具(read/glob/grep)不需要幂等 key。
方案二:Lease + Version Token
对于需要并发安全的有状态操作(如修改同一文件),用乐观锁:
async def edit_file(path, content, expected_version):
current = await get_version(path)
if current != expected_version:
raise StateConflictError(f"Version mismatch: expected {expected_version}, got {current}")
await write_file(path, content)
await bump_version(path)
Codex 的 sandbox 隔离天然提供了文件系统级别的并发保护——每个 Agent 在自己的 workspace 内操作,不会冲突。
方案三:意图-执行分离
一个更激进的架构:Agent 不直接执行工具,而是"提议"执行,由 Engine 层统一做幂等/去重/重试:
Agent → 发出 Intent(含 correlation_id + preconditions + compensation plan)
↓
Execution Engine → 检查幂等 key → 执行 → 记录 journal
↓
失败 → Engine 自动重试或触发 compensation
这个方案的好处是 Agent 侧完全不需要关心中断恢复——Engine 层提供 durable execution 保证。缺点是架构复杂度上升。
LLM 层面的防护:除了系统层机制,还可以在上下文里做轻量防护——记录最近 N 次的 tool_call,MicroCompactor 对 COMPACTABLE_TOOLS 只保留最近 5 个结果的哨兵字符串。这样至少让模型知道"我用过这些工具"。
T5. 长时间运行的工具怎么管理?后台任务 + 快照持久化
面试官意图:考察长任务(几分钟到几小时)的工程管理能力。
推荐回答:
FuserMate 的后台任务系统专为长时间运行的工具设计:
架构:
BackgroundTaskService
├── Bash → asyncio.create_subprocess_shell(流式捕获输出)
│ └── 每 1000ms 持久化输出快照到 FileRuntimeStore
├── Read/Grep/Glob → ThreadPoolExecutor
└── 完成时 → 写入 Mailbox + 可选唤醒 Agent
关键设计点:
增量快照持久化:Bash 后台任务每 1 秒持久化一次 stdout/stderr。即使进程崩溃,已经产生的输出不丢失,恢复时可以从最后快照继续。
Mailbox 异步通知:后台任务完成后写入 Mailbox,
wake_policy决定是否唤醒 Agent。Agent 在下一轮模型调用前消费 Mailbox 消息。如果 run 被取消,Mailbox 消息的consumed_at回滚为 None,下次 run 重新消费。Agent 不阻塞等待:Agent 不轮询后台任务状态。后台任务完成后通过 Mailbox 主动通知——事件驱动而非轮询驱动。
Codex 的对比:Codex 用 UnifiedExecProcessManager 管理交互式进程。长任务在 sandbox 内运行,ToolOrchestrator 支持 retry with escalation。Codex 更侧重 sandbox 隔离——每个长任务在 OS 级 sandbox 里执行,主进程不直接接触。
Claude Code 的对比:Claude Code 的 bash 子进程有 subprocessTracking 机制——即使主 Agent 被 cancel,已启动的 bash 子进程可以被追踪和恢复。
设计原则:
- 长任务一定异步执行(不阻塞 Agent Loop)
- 增量持久化(不等到结束才保存)
- 完成后主动通知(不轮询)
- 崩溃后可以恢复或标记失败(不留僵尸进程)
T6. 工具执行的可观测性怎么做
面试官意图:考察工具执行的监控和调试能力。
推荐回答:
工具执行的可观测性需要回答四个问题:什么被调了 / 结果是什么 / 花了多久 / 为什么失败。
工具执行的 Trace Span 设计:
{
"span_type": "tool_execution",
"tool_name": "bash",
"tool_call_id": "toolu_xxx",
"input": {"command": "ls -la"},
"output": {"stdout": "...", "stderr": "", "exit_code": 0},
"output_truncated": false,
"output_size": 1234,
"duration_ms": 450,
"permission_decision": "user_ask",
"permission_duration_ms": 1200,
"error": null,
"retry_count": 0
}
关键字段:tool_call_id(与 LLM 输出的 tool_use id 对应)、permission_decision(是 allow/ask/deny/hard_deny)、permission_duration_ms(人工审批花了多久——这是 HITL 的性能瓶颈指标)、output_truncated(结果是否被截断)、retry_count(第几次重试)。
FuserMate 的 27 种运行时事件覆盖了工具的完整生命周期:tool_call_started → tool_call_completed / tool_call_failed。每个事件写入 JSONL 文件(按 session 组织),带递增 cursor,支持按时间回放。
告警指标:
tool_failure_rate按工具分组(识别"问题工具")tool_permission_deny_rate(用户拒绝频率异常可能表示安全策略过严或模型滥用)tool_retry_rate(重试率过高表示工具稳定性差)tool_output_truncation_rate(大结果比例高可能需要调整截断阈值)tool_duration_p99(P99 延迟突然恶化)
八、推理引擎与基础设施
E1. 私有化交付你做过哪些
面试官意图:考察交付能力。注意:不要说"没做私有化",你的 Docker 部署就是私有化雏形。
推荐回答:
我实际做过的是 Docker 级别的交付,Helm/K8s/离线包不说成已深度落地。
FuserMate 有两种形态:Electron 桌面端前后端一起打包;Docker 形态一个镜像含 React 静态站点 + FastAPI,Nginx 反代 /api/* 和 WebSocket,后端不直接暴露端口。运行时配置和数据挂载到宿主机 /data/fuser,API Key 不入镜像。部署后通过前端访问、/api/v1/mates、/api/v1/models 做 smoke test。
FuserLab 是前端 Nginx Alpine + 后端 Python Slim 两个镜像分开部署。后端实际可拆 8 个微服务,但线上 3 个 Pod 没遇到性能瓶颈,优先把研发时间放在业务功能上。
K8s 这边我们日常用的是天翼云 CCSE 容器服务,底层是 K8s 但日常操作不需要裸写 kubectl,主要用云平台管理,命令行只会用到 log 查看。Helm chart 如果要做,思路和 Docker Compose 一脉相承:values、Secret、ConfigMap、PVC、init migration job、readiness/liveness 和 rollback。
E2. 多租户和配额怎么设计
面试官意图:考察 B 端平台能力。
推荐回答:
我不把它包装成完整大规模 SaaS 多租户落地经验。
实际做过的:FuserMate 因为是数字员工,多租户就是多容器多部署,每个实例独立隔离。FuserLab 当时考虑过两个方案:一是全部加 tenant_id 索引(类似 Coze 的做法),二是直接用数据库隔离。我们选了后者,因为 toB/toG 客户天然偏好物理隔离,独立数据库更符合他们的安全和合规预期。公有云 SaaS 场景用 tenant_id 索引更经济,但 Lab 的客户场景决定了数据库隔离是更合适的方案。
设计方案:完整多租户体系的话,tenant_id 要贯穿用户、workspace、agent、tool_policy、model_config、trace、artifact 和 quota,在 repository 和 API 层强制注入。配额按模型 token、工具调用次数、并发任务做维度,Redis 令牌桶限流,超限记录审计日志。
E3. 自建推理你了解多少
面试官意图:这是强项,充分展示。
推荐回答:
我实际做过。用的是华为 910B 8 卡(DaVinci NPU),部署了 4 个千问系列模型:
| 模型 | NPU | 关键参数 |
|---|---|---|
| Qwen3-32B | 0-3 | tensor-parallel-size 4 |
| Qwen3-30B-A3B | 4-5 | TP=2, enable_expert_parallel(MoE) |
| Qwen3-14B | 6 | 单卡 |
| Qwen3-4B | 7 | 单卡 |
推理引擎是 vLLM-Ascend,Ascend 量化,所有模型通过 OpenAI 兼容 API 暴露。
关键参数:--tensor-parallel-size 2、--enable_expert_parallel(MoE)、--max-model-len 5500(后调至 12000)、--max-num-seqs 64(后调至 32)、--enable-chunked-prefill、--enable-prefix-caching、--gpu-memory-utilization 0.9(后调至 0.85)、--block-size 128、--async-scheduling、--tool-call-parser hermes。
Ascend 特有配置:HCCL_OP_EXPANSION_MODE=AIV(集合通信优化)、VLLM_ASCEND_ENABLE_PREFETCH_MLP=1(MLP 预取)、VLLM_ASCEND_ENABLE_FLASHCOMM1=1(Flash 通信)、pa_shape_list: [48,64,72,80](形状预分配减少动态编译开销)。
模型前面是一层 OpenResty + Redis 网关,做 OpenAI 兼容路由和并发控制。Qwen3-30B-A3B 全局并发上限 64,生产环境 40,测试环境 24,max_wait 60 秒。
向量模型也部署了 BGE-M3 做 embedding、BGE-Reranker-v2-m3 做重排,用的是华为 Ascend 优化版的 MIS-TEI 镜像。
E4. 你们有完整 Helm chart 和离线交付吗
状态:高风险(如实承认边界)
推荐回答:
我目前能确定的实做是 FuserMate 的 Docker Compose 私有化形态,包含镜像构建、Nginx 反代、配置挂载和 smoke test。Helm chart 和完整离线交付我不会说已经主导落地。如果要做,我会把 Docker Compose 的经验迁移到 K8s:应用镜像、values、Secret、ConfigMap、PVC、init/migration job、健康检查、离线镜像仓库、依赖包和回滚预案。
E5. 你做过企业 SSO/LDAP 吗
状态:高风险(如实承认)
推荐回答:
没有做过。FuserLab 有基础的 auth/role/permission/key 权限模块,但完整的 SSO/LDAP/AD 集成不是我的实战经验。如果要做,思路是把外部认证作为接入层,统一映射到内部的 user/role/permission 体系,再结合审计日志和租户隔离控制资源访问。
九、行业视野与场景设计
F1. 你怎么看 LangGraph vs. Eino vs. 自研 AgentLoop
面试官意图:这是 Coze 面试的核心题——考察对 Agent 框架的深度理解和选型判断力。
推荐回答:
这三个代表了 Agent 框架的三种路线,我两个项目正好覆盖了其中两种。
LangGraph 的核心是状态机 + 图编排。用 StateGraph 定义节点和边,每个节点是纯函数,状态通过 checkpoint 持久化。优势是:社区成熟、checkpoint 机制完善、适合业务状态机明确的场景。我在 FuserLab 的语音控制里用了 LangGraph——agent、tools、ask_user 三个节点的状态流转非常清晰。
Eino 是字节开源的 Go 语言 Agent 框架,设计思想和 LangGraph 类似——用 Graph 编排组件。差异化在于:Go 语言(更适合字节技术栈)、更轻量的抽象(不像 LangChain 那么重)、字节内部已经在生产环境大规模验证。如果团队技术栈是 Go,Eino 是比 LangGraph 更自然的选择。但我没用过,这是基于文档和社区反馈的判断。
自研 AgentLoop 是我在 FuserMate 上的选择。原因很明确:FuserMate 的核心复杂度不在固定的图拓扑,而在平台运行时——需要精细控制流式事件的每一步拆分,工具执行有完整的生命周期事件,需要配合 cancel 中断、结果截断、上下文压缩和快照冻结。LangGraph 的 ToolNode 是一个黑盒——工具调了就调了,不会给你中间的事件粒度。
我的判断:
- 业务状态机明确的场景(工作流、审批、语音对话)→ LangGraph / Eino
- 需要掌控平台运行时的场景(通用 Agent 工作台)→ 自研 AgentLoop
- 不存在"最好"的框架,只有"最适合当前复杂度"的选择
如果 FuserMate 后续要做复杂 DAG,我的演进思路是在上层引入图编排,底层工具执行和事件系统仍然复用自研的 AgentLoop,不是全量迁移到 LangGraph。
F2. Claude Code / Codex / FuserMate 三方技术对比
面试官意图:考察对行业主流 Agent 架构的理解深度和设计判断力。
一句话定位:
| Claude Code | Codex | FuserMate | |
|---|---|---|---|
| 定位 | 通用编程助手 | 企业级 Agent 平台 | 通用智能体工作台 |
| 语言 | TypeScript (Bun) | Rust | Python (FastAPI) |
| 核心特色 | 深度 OS 集成 + IDE | 沙盒安全 + 多 Agent | 平台运行时 + 全栈 |
Agent Loop 对比:
| Claude Code | Codex | FuserMate | |
|---|---|---|---|
| 实现 | while(true) | 双层循环:Session Task + Turn | for max_iterations=200 |
| 状态机 | Terminal(11种)/Continue(7种) 枚举 | 指数退避重试 | 四步迭代:prepare→stream→tools→loop |
| 工具循环 | 并发安全工具并行(max 10) | approval→sandbox→retry | 逐个执行,结果 append → 下一轮 |
| 中断恢复 | stopHooks + abort controller + snapshot 回滚 | CancellationToken + Thread fork | asyncio.Event + AgentRun 快照 + reconcile_startup |
| 并发 | Coordinator Mode | Actor Model | asyncio.Lock 排队 |
工具系统对比:
| Claude Code | Codex | FuserMate | |
|---|---|---|---|
| 安全模型 | Permission deny rules | Sandbox 兜底(bwrap/landlock) | 运行时策略 system_hard_deny > user_deny > user_ask > user_allow |
| MCP | assembleToolPool(built-in + MCP 去重) | codex-mcp crate | 直接集成 MCP server |
上下文压缩对比:三家都做了两层压缩。FuserMate 的 CompactionService 在 prompt 设计上更结构化——明确要求保留当前目标、重要决策、未决问题、文件路径和接口约定。
多 Agent 对比:
- Claude Code:Master-Worker (Coordinator Mode),Coordinator 仅保留 AgentTool + TaskStop
- Codex:Actor Model,Mailbox + AgentRegistry + agent_max_depth 限制
- FuserMate:单 Agent 为主,AgentRun 快照预留 team/subagent 扩展空间
一句话总结:
Claude Code 是我做 FuserMate 的直接参考——它的 AgentLoop、Hook、MCP、Skills、HITL 模式都影响了 FuserMate 的设计。但 Claude Code 是编程场景专用,FuserMate 把它抽象成了通用 Agent 运行时,在核心模型拆分(Agent/Workspace/Session/AgentRun)和安全策略(运行时强制)上做了差异化的设计决策。
Codex 的沙盒方案是它最大的技术亮点,但部署复杂度也高。FuserMate 选择不依赖沙盒而是用策略兜底,是因为我们的部署目标是私有化 Docker 单镜像,不能引入 bwrap/landlock 这些系统依赖。这是场景驱动的取舍。
F3. 对 Manus、DeepSeek 这些新产品怎么看
面试官意图:考察前沿视野和技术判断力。
推荐回答:
Manus 代表了通用 AI Agent 产品化的方向。它的核心价值主张是"给 AI 一个目标,它自己完成所有步骤"——这和 Coze 的 Agent 化编排是一个方向。但 Manus 面临的核心挑战是可靠性——开放域任务的完成率很难做到用户期望的水平。Agent 产品最大的敌人不是竞品,是用户的"失望一次就不再信任"。
DeepSeek 改变了行业两个认知。第一,证明了顶尖模型能力不需要天文数字的投入;第二,开源模型和闭源模型的差距在加速缩小。这对 Agent 平台是利好——推理成本在持续下降,意味着我们可以给 Agent 更多"思考预算"。但 DeepSeek 的 Function Calling 和工具使用能力相比 GPT/Claude 还有差距,这是 Agent 场景的关键能力。
我的整体判断:模型层在快速收敛,差异化会越来越小。Agent 平台的价值不在"用哪个模型",而在上下文工程、工具生态、评测体系、安全控制这些平台级能力——这些才是护城河。
F4. 设计一个全自动化的 Agent 进行创作(场景设计题)
面试官意图:考察架构设计和任务拆解能力。
推荐回答(以 AI 漫剧创作为例):
第一步:任务拆解 → 子 Agent 划分。不是按功能菜单拆,而是按独立决策边界拆:
- 世界观设定 Agent
- 剧情规划 Agent
- 角色 Agent
- 分镜 Agent
- 评审 Agent(独立验证角色一致性、剧情连贯性、对白质量)
第二步:定义输入/输出 Schema。每个子 Agent 的输入输出必须是结构化的。
第三步:协作流程设计。串行流程:世界观 → 剧情大纲 → 逐章生成(角色+分镜并行) → 评审 → 修改 → 定稿。评审不通过时回退到对应 Agent 重新生成,最多 3 轮。
第四步:一致性保障——最难的部分。不能只靠 prompt 里说"保持角色一致"。需要:
- 角色拆为不可变属性(年龄、身份、说话风格底色)、弱可变属性(好恶、互动方式)、动态状态(当前情绪、目标、已知信息)
- 每轮生成前注入角色状态,每轮生成后评审 Agent 做一致性校验
- 维护全局 Story State:角色关系图、时间线、关键伏笔、已用设定、未回收线索
第五步:评测标准:角色一致性、剧情连贯性、对白自然度、创作效率。
F5. 设计旅行攻略 Agent,怎么测评(场景设计题)
面试官意图:考察 Agent 评测体系的完整思维。
推荐回答:
Agent 架构:意图解析 → 信息检索 Agent → 行程编排 Agent → 输出格式化 Agent
最大的三个问题:
- 幻觉——推荐不存在的景点、餐厅。这是最致命的,因为用户会实际去执行
- 信息过时——API 数据可能是旧的
- 个性化不足——攻略"大路货"
评测体系:
- 离线评测集覆盖不同维度(目的地类型、天数、预算、偏好)
- 评测维度:事实准确性(权重最高)、行程合理性、个性化程度、信息完整性
- 评测方式:规则校验 + LLM-as-judge(多次打分取平均,与人工标注校准)+ 人工抽检 10%
持续改进闭环:线上用户反馈 → badcase 回灌到离线评测集 → 每次改 prompt 或切数据源跑回归评测。
十、反问 + 速记
反问面试官
Coze 版
- Coze 目前的 Agent 上下文工程,更多是开发者自己管理还是平台提供自动化能力?未来方向是什么?
- 多 Agent 协作目前在 Coze 上的主要使用场景是什么?最常遇到的工程挑战有哪些?
- Coze 的工具/Skill 生态,目前是更偏平台自建插件还是鼓励开发者通过 MCP 接入?
- 团队目前在 AI Agent 架构上最大的技术投入方向是什么?MultiAgent 协作、上下文工程、还是推理性能优化?
- 这个岗位日常工作中,和产品、前端、模型团队的协作模式是怎样的?
零一万物版
- 这个岗位当前更需要从 0 到 1 搭 Agent 平台,还是在已有平台上做工程化增强?
- 现在 Agent 工具体系是已经有 registry/schema/审计,还是还在 prompt function call 阶段?
- RAG 当前主要问题是召回质量、权限过滤、引用溯源,还是数据接入和增量更新?
- 私有化交付目前是 Docker Compose 为主,还是已经要求 Helm/K8s 和离线包?
- 评测体系现在有没有固定离线集和线上 badcase 回归机制?
考前速记卡
核心画像(必背)
我是能从产品价值出发,把 Agent 平台从 0 到 1 落地的 AI 全栈负责人。优势不是只会某个框架,而是能把产品目标拆成平台边界、运行时、工具链、观测评测、前端交互和交付闭环。
两个项目一句话
FuserMate:通用智能体平台,类 Claude Code。我主导产品/架构/核心代码——AgentLoop、工具体系、MCP、Skills、HITL、上下文压缩、工具安全。
FuserLab:部门级 AI 中台。Prompt 编排、Trace 观测、评测实验、LangGraph Agent、pgvector 知识库。
核心模型拆分
Agent(身份/模型/prompt/memory)→ Workspace(路径/skills/tools/规则)→ Session(对话历史)→ AgentRun(执行快照)
AgentLoop 流程
stream(messages) → for 0..max_iterations:
① _prepare_iteration_messages(注入上下文 + micro_compactor)
② _stream_model_response → 逐 chunk: message_delta / thinking_delta / tool_call
↔ 每个 chunk 后检查 cancel_event
③ 无 tool_call → message_done → run_completed
④ 有 tool_call → _execute_one_tool → 结果 append → 下一轮
五层保护:max_iterations(200) / cancel_event / 结果截断(50k) / 错误 JSON 化 / 顶层 try-except
安全优先级
system_hard_deny > user_deny > user_ask > user_allow 原则:模型输出不可信,运行时兜底
vLLM 关键参数
block-size 128 / max-model-len 12000 / gpu-memory-utilization 0.85 / max-num-seqs 32 / chunked-prefill / prefix-caching
高风险边界
- ⚠️ Milvus+ES:实做 pgvector,混合检索讲方案
- ⚠️ Helm/K8s 离线包:Docker 实做,Helm 讲方案
- ❌ SSO/LDAP:没用过,如实承认
- ✅ vLLM-Ascend:强项,全链路有代码
- ✅ 多租户:数据库隔离理由真实
常见口误提醒
- "项目经理" → "项目经历"
- "类 Claw"和"类 Claude Code"选一个,不要混用
- 不要用"deepagent"这种没有业界定义的自造词
- 不要说"内部使用风险小所以敢自研" → 说"需要精细控制平台运行时所以自研"
- 不要把"理解方案"包装成"落地经验"——Milvus/ES/Helm/SSO 永远先说实做边界
- 不要先说"我都没做私有化交付" → 你的 Docker 部署就是私有化雏形
回答优先级速查
| 优先级 | 题目 | 核心策略 |
|---|---|---|
| S级 | A1, A3, B1, C5, D1, D3, F1, F2 | 自我介绍 + 框架选型 + vLLM 调优 + 安全 + AgentLoop 是杀手组合 |
| A级 | A2, B2, B4, B6, C3, C7, D4, D5 | 高频追问,项目证据充分 |
| B级 | A4, A5, B3, B5, B7, B8, C1, C2, C4, C6, C8, F3 | 展示广度和产品思维 |
| 场景题 | F4, F5 | 结构化拆解框架 |
| 风险题 | E4, E5 | 如实承认边界,讲设计思路 |