12 · 面试话术卡
按被问到的概率排序。每张卡的结构是:先给一句能立住的判断,再给结构化展开,最后给一个能证明你真读过源码的细节。
别把这些当稿子背。面试官真正在测的是「你有没有自己的判断」,所以每张卡的第一句话(判断)才是核心,后面的展开只是支撑。
如果你只能记住一样东西,记住第一句。
Q1 · 说说你理解的智能体架构
判断:智能体系统的复杂度不在「循环」本身,而在循环之外的四件事:上下文治理、工具调度、权限边界、错误恢复。
展开:标准的六层分层是 —— 入口层 / 会话层 / 智能体循环层 / 工具执行层 / 供应商适配层 / 持久化层。
关键是循环层和工具执行层必须分开:循环层是有状态的状态机(它要跨轮次记住「我压缩过几次」「我切换过备用模型没有」),工具执行层是无状态的调度器(给一批工具调用,还一批执行结果,做完就忘)。
揉在一起的直接后果是:错误恢复逻辑没法单独测试 —— 你想测「压缩失败后会不会正确重试」,就必须先造一堆假工具。这是很多自建智能体项目后期极难维护的根本原因。
细节:我对比读过 Claude Code 和 Hermes 的源码。Hermes 的代码量是 Claude Code 的 3.7 倍(191 万行对 51 万行),但两者的核心循环体量几乎相同。那 3.7 倍的差距全在外围 —— Hermes 的 22 个聊天平台适配器、十几种模型供应商适配、插件系统。这说明智能体的能力密度集中在很小的一块代码里,其余都是集成工程量。
Q2 · 上下文满了怎么办
判断:摘要压缩是最后一级,前面还有三级更便宜的。上来就答摘要,说明没做过。
展开:一条成本递增的阶梯 ——
- 不让它进来:单条工具结果超限就写到磁盘,只回预览和文件路径。成本 0。
- 删掉确定没用的:旧的文件读取和搜索结果,按工具调用 id 精确删除。成本 0。
- 结构化折叠:折成可展开的摘要,保留粒度和可重放性。成本低。
- 整段摘要:一次完整的模型调用,有损、不可逆。成本高。
顺序很重要:如果第 3 级已经降到阈值以下,第 4 级就直接跳过 —— 从而保住细粒度上下文,而不是把它变成一坨摘要。Claude Code 源码里有一句注释就是讲这个:「keep granular context instead of a single summary」。
细节(这个是杀手锏):缓存冷热应该成为压缩策略的输入。
提示词缓存是前缀匹配的 —— 改动上下文中段,从那里往后的缓存全部失效。所以缓存热的时候,改本地数据会让整段缓存作废,省下的 token 还不如重建缓存贵;Claude Code 的做法是发一条 cache_edits 指令让服务端在缓存内部删除,本地一个字不改。
反过来,如果距上次响应已经超过缓存有效期、缓存已经凉了,那前缀反正要全部重新处理 —— 这时候正是大刀阔斧清理旧内容的最佳时机。
同一个目标,两条完全相反的路。大多数自建智能体根本没有「缓存现在是热还是凉」这个概念,所以策略只有一套,在两种场景下各错一半。而判断冷热只需要一个时间戳,成本为零。
Q3 · 怎么防止智能体执行危险命令
判断:最有效的防护不是「拦住危险命令」,而是「那个危险工具根本不在模型能看到的清单里」。
展开:四层,从外到内 ——
- 工具面收窄。按信任边界配置工具集:交互式会话给全量,后台任务给只读,公开 webhook 只给联网搜索。Hermes 的注释写得很直白:webhook 事件可能来自不可信的第三方内容(公开代码仓库的合并请求标题、评论),所以默认工具集刻意收窄,避免提示词注入触发本地文件读写或命令执行。模型无法调用一个它不知道存在的工具。
- 规则级联 + 不可绕过的底座。允许用户关掉烦人的确认(否则自动化就没价值),但保留一层他们关不掉的检查。Claude Code 的权限判定链里,「工具自己拒绝」「需要人在场」「用户显式配的规则」「敏感路径」这四类排在 bypassPermissions 检查之前 —— 开了
--dangerously-skip-permissions也照样拦得住。顺序就是设计。 - 语义判定。规则匹配不了意图,需要模型看完整对话记录来判断。但分类器很贵(每次一个额外 API 请求),前面必须挡快速通道。
- 执行隔离。前三层都是软的,真正的边界是容器、沙箱、独立用户账号。
细节:「命令」和「数据」必须区分。
朴素的 if "rm -rf /" in cmd 会拦掉 git commit -m "fix: block rm -rf / spellings" —— 这是 Hermes 真实修过的 bug,用户没法提交一条说明文字里含这段字符的代码提交。
正确做法是只在命令位置匹配(行首 / ;&&|| 分隔符后 / $( 子 shell 后 / sudo 包装后),并对引号内容做遮蔽。但双引号里的 $(...) 不能遮蔽,因为 shell 真的会执行它;而遇到 sh -c / eval 这类 shell 载体时,遮蔽整个失效,因为那里引号内容就是代码。源码里一句话总结:「quoting is not a bypass」。
最后补一句判断力:误报率过高的安全措施等于没有安全措施 —— 用户会直接把它整个关掉。
Q4 · 多工具并行怎么保证不出竞态
判断:并发安全性应该由工具自己声明,调度器不认识任何具体工具。
展开:
is_concurrency_safe(参数)必须是接收参数的 —— 同一个 Bash 工具,跑ls安全、跑rm不安全。安全性取决于这次要做什么,不取决于工具类型。所以它是一个方法而不是一个静态标记。- 贪心分区,不是全排序。把相邻的安全工具合并成一个并行批,遇到不安全的就切断并单独串行。既拿到并行的速度收益,又完整保留了模型隐含的顺序语义(模型可能依赖「先改文件再读回来验证」的顺序)。
- 失败时倒向保守。参数格式解析失败、安全判定函数自己抛异常 —— 全部当作不安全。Claude Code 专门为此写了 try/catch,注释是「Bash 命令的引号解析失败时保守处理」。
细节:两级中止作用域。
一批并行的 Bash 命令里有一个失败了(比如编译报错),其他几个跑完毫无意义 —— 但如果用同一个全局中止开关去停它们,整个轮次就结束了,模型收不到错误信息,也就没法重试。
Claude Code 的做法是创建一个父控制器的子控制器:批内失败时中止子控制器,兄弟子进程立刻死掉省资源;父控制器不动,所以本轮不结束,模型正常收到错误并重试。
任何有「批内失败」概念的并发执行器,都应该有一个可以独立触发的子作用域。
Q5 · 智能体循环怎么防止无限循环
判断:真正的无限循环几乎都不来自主流程,而来自两条恢复逻辑互相触发。
展开:
- 硬闸(最大轮次 / 最大美元花费 / 最长运行时间)—— 这是兜底,正常情况不该碰到它。
- 每条恢复路径独立限次,而且只在正常推进时重置。这是核心。
- 软着陆 —— 预算快耗尽时给一次「宽限调用」让模型收尾,比硬切断的体验好得多。这是 Hermes 的做法。
细节:Claude Code 源码里留了一条事故记录。有人在「结束钩子」分支里把 hasAttemptedReactiveCompact(是否已尝试反应式压缩)这个幂等锁重置成了 false,结果形成死循环:压缩 → 还是超 → 报错 → 结束钩子判定不合格要求重试 → 又去压缩 → …… 烧掉了几千次 API 调用。
请注意这个循环的形状:它不是一条路径自己转圈,而是两条恢复路径互相触发。压缩路径和钩子路径各自看起来都有终止条件,但组合起来就成了死循环。
还有一个相关的:上下文超长导致的失败明确不走结束钩子。因为那个钩子会往上下文里注入反馈内容,越注入越超 —— 源码里管这叫 death spiral(死亡螺旋)。失败路径必须能识别「这一类失败不该触发常规的质量重试机制」。
Q6 · 什么时候该用子智能体
判断:子智能体的第一性原理是上下文隔离,不是并行。并行只是副产品。
展开:要读 20 个文件才能得出一个结论时,自己读会让那 20 个文件的内容永久占用主上下文、每一轮都重发一遍;派子智能体去读,它的上下文用完即弃,主智能体只收到一句结论。40,000 token 换成 15 token。
- 该用:探索型任务(结论远小于探索过程)、需要不同工具面或不同权限的任务、可并行且互不依赖的任务。
- 不该用:需要主智能体完整上下文才能做对的任务(分叉模式除外,因为它本来就继承全部上下文)、需要和用户交互的任务(子智能体通常被禁用「向用户提问」工具)、单步就能完成的任务(派生开销大于收益)。
细节:扇出时的缓存策略。
N 个子智能体如果能让请求前缀达到字节级一致,后 N−1 个全是缓存命中,输入成本降到 1/3 以下。Claude Code 为此付出了四个看起来不优雅的代价:
· 给子智能体一堆用不到的工具(因为工具定义是前缀的一部分)
· 传父智能体已渲染好的系统提示词字节,而不是重新生成(重新生成可能因为特性开关的冷热变化而产生不同字节)
· 用完全相同的占位内容填充所有工具结果
· 把递归分叉的守卫从接口层下移到调用层(因为从工具池里移除会改变工具定义,破坏缓存)
只让最后一个文本块携带各自不同的指令。这是很具体、可量化的架构决策。
Q7 · 智能体的长期记忆怎么做
判断:先分清三种记忆,别一股脑塞进向量数据库。
- 指令性记忆(偏好、约定、项目规范)—— 不该用检索。应该是人类可读、可审阅、可以用 git 管理的纯文本,每次全量加载。用户改了要立刻生效,而且必须能看到自己写了什么。Claude Code 的
CLAUDE.md就是这个定位。 - 事实性记忆 —— 需要检索。词法检索 + 向量检索混合优于纯向量,因为事实里全是专有名词,而向量检索恰好对专有名词不敏感。
- 过程性记忆 —— 本质是会话历史检索,存对话记录 + 全文索引。
两个容易漏的工程点:
- 信任衰减。Hermes 用
trust_score加record_feedback(fact_id, helpful):有用的上浮,误导过人的沉底。没有这个机制,一条早期的错误记忆会永久污染后续所有会话 —— 比如智能体误以为「这个项目用 npm」,实际用的是 pnpm,之后每次都会先执行错的命令。 - 注入去重。模型自己刚 Read 过的文件不要再作为记忆注入一遍。Claude Code 用跨迭代累积的已读文件状态过滤 —— 注意是跨迭代累积,只看本次迭代会漏掉早期读过的。
细节:Hermes 的全息记忆用 SHA-256 确定性生成原子向量,而不是用神经网络生成 embedding。
好处是:同一个词在任何机器、任何 Python 版本上编码结果完全一致 —— 彻底避开了「换了 embedding 模型就要重算全库」这个运维噩梦,而且向量可以直接存进数据库的二进制字段跨机器同步。
代价是:它是词袋级的符号组合,没有语义泛化能力("docker" 和 "container" 相似度接近 0),所以必须配合词法检索使用,而不是替代它。这个权衡本身就很值得讨论。
Q8 · 你怎么优化智能体的成本和延迟
判断:智能体的成本大头是输入 token,不是输出。因为每一轮都要重发整个历史。所以优化的核心是提示词缓存的命中率。
四个手段:
- 保护缓存前缀。任何会改动历史前缀的操作都要重新评估。举个极端例子:Claude Code 里内建工具和外部 MCP 工具是分别排序后拼接的,不是合并排序 —— 因为服务端在「最后一个内建工具」之后放缓存分界点,统一排序会让外部工具插进内建工具中间,把区间劈开。结果是用户每装一个 MCP 服务,所有人的系统提示词缓存就全崩。
- 渐进式披露。工具的参数说明、技能正文、MCP 工具、记忆内容 —— 全部「目录常驻 + 内容按需展开」。Claude Code 甚至有一个函数专门估算所有技能头部元数据的常驻成本。
- 流式执行。工具在模型流式返回的过程中就开始跑,不等整个响应结束。
- 把慢操作藏进流式窗口。模型流式输出要 5 到 30 秒,这段时间 CPU 闲着。Claude Code 在这个窗口里塞了记忆预取、技能发现预取、上一批工具的小模型摘要生成 —— 实测技能预取的完成率大于 98%。关键是消费点必须设计成「好了就用,没好就跳过」,绝不阻塞。一旦开始等,这个优化就变成负优化。
细节:还有一个很小但很实在的例子 —— backfillObservableInput(回填可观测输入)。
工具有时需要给日志、钩子、开发工具包补一些派生字段,但绝不能改那个要发回 API 的原始参数对象,因为字节变了缓存就没了。Claude Code 的做法是只修改一个克隆副本,而且只有当补充操作真的新增了字段时才产生克隆 —— 如果只是覆写了已有字段,连克隆都不做,因为那会改变对话记录的序列化结果、破坏测试固件的哈希。
这个级别的克制程度,能说明「保护缓存」在这个系统里是一等公民约束。
Q9 · 反问环节可以问的问题
这些问题能同时展示你的深度,也能帮你判断这家公司值不值得去:
- 「你们的智能体是绑定单一模型供应商,还是做了多供应商抽象?这个选择当时是怎么权衡的?」
直接切到第 11.6 节第 1 条决策。对方的回答质量能立刻告诉你团队的技术深度 —— 如果对方能讲出「我们为了拿到某个私有能力接受了绑定」或者「我们为了合规必须支持私有部署所以做了抽象」,说明是真的想过。 - 「上下文压缩是自己实现的还是用框架的?摘要之前有没有更便宜的层?」
如果答案是「直接调框架的压缩接口」,说明这块还很早期。 - 「工具执行有沙箱吗?是容器级还是进程级?」
- 「线上遇到过智能体死循环吗?最后定位到是什么原因?」
这个问题几乎一定能问出真实故事,而故事最能反映团队的工程成熟度。 - 「提示词缓存的命中率现在是多少?有专门监控它吗?」
如果对方没有监控这个指标,说明成本优化还很早期 —— 对你是机会,也是风险。
最后:这份文档的正确用法
你手上现在有两套真实系统的完整源代码。
面试里最强的表达不是「我读过 Claude Code 的源码」,而是「我遇到 X 问题时参考了它的 Y 做法,因为它的约束和我们的类似 / 不类似」。
所以最好的下一步是:挑一个你自己的小项目,把第 11 章的骨架实际写一遍。哪怕只实现主循环 + 工具并发分区 + 两级上下文治理,你在面试里能讲的东西会立刻变得具体十倍 —— 因为你会有自己踩坑的故事,而故事比知识更有说服力。
文档里任何看不懂或想深挖的地方,选中那段文字点「提问」就行。术语忘了含义就翻下一章的术语表。