# 快手 Agent 开发一面:为什么不用多 Agent,指标又是怎么测出来的?
面经时间:2026年9月28日
前面那篇阿里淘天 Agent 研发岗面经,重点是代码 Agent 从读仓库、改文件到运行验证的执行过程。
这次看快手 Agent 开发一面,追问进一步落到了技术选型和验证依据。

为什么不用多 Agent?为什么代码知识库不用图?QPS 提升到底怎么测?
简历上写了一个方案,现场就要解释它解决了什么问题、付出了什么代价。 后半场还串起了 KV Cache、序列化、Kafka 和零拷贝。
下面统一用“面向 Java 仓库的代码问答与小范围修改 Agent”举例:工具层支持本地命令与 MCP,检索方案以 Elasticsearch 的关键词和向量混合召回为例,任务状态保存在 MySQL,Kafka 用于异步索引更新。
本次面经问题:
- 一、说一下你的实习主要做了什么内容
- 二、第一个 Agent 为什么不用多 Agent 去做?
- 三、MCP 调用和本地 CLI 调用有什么区别,各有什么优缺点?
- 四、Agent 的可观测性怎么做,指标是怎么得出的?
- 五、怎么看待 Human as a Judge 和 LLM as a Judge?
- 七、为什么代码知识库不用图,而用现在的技术栈,优点在哪?
- 八、KV Cache 的原理是什么,工程侧怎样提升复用?
- 九、Protobuf 和 JSON 有什么区别?
- 十、后端项目通过什么方法提升 QPS,怎么测的?
- 十一、为什么选用 Kafka?
- 十二、零拷贝的原理是什么?
- 十三、能否转正?
- 十四、手撕:最小覆盖子串
# 一、说一下你的实习主要做了什么内容
以这个示例项目为例,我主要围绕代码 Agent 的检索、工具执行和任务验收做工作。用户输入代码问题或一个小需求后,系统定位相关文件,把必要上下文交给模型,再根据模型提出的工具请求读取文件、应用补丁和运行测试。
我会把自己负责的部分说具体:比如代码按方法和类切分,保留仓库版本、路径和行号;检索后回读当前文件;执行器对工具参数、工作目录、超时和输出大小做限制。模型提出动作,执行器真正执行,再把结果交回模型继续判断。
最值得展开的难点是“找对代码”和“证明完成”。前者需要看召回证据是否足够、索引是否过期;后者需要结合 Diff、测试和任务约束验收,不能只看最终回复是否流畅。
面试时我会明确哪些模块由我实现、哪些是团队已有能力,再用一个实际失败案例说明修改过程。有实测记录就报基线、样本和结果;没有就说明验证到哪一步。不要把项目完整架构全部说成自己的贡献。
# 二、第一个 Agent 为什么不用多 Agent 去做?
我的判断依据是任务能否拆成相对独立、可分别验收的部分。这个示例主要处理同一仓库里的局部问题,搜索结果、修改位置和测试反馈高度相关,用一个 Agent 配合多个工具,更容易保持上下文和任务状态一致。
比如新增参数校验,需要先看接口约定,再修改实现,随后根据测试报错调整。这些步骤有明显依赖。拆成“检索 Agent、编码 Agent、测试 Agent”,还要额外处理上下文传递、证据丢失、共享文件冲突和结果合并,不会因为角色多就自动提速。
单 Agent 也可以由执行器并行运行相互独立的只读工具。 搜多个目录、查多个文档,不一定要为每个动作再建一个 Agent。多 Agent 的额外模型调用、重复上下文和协调成本,需要由实际收益覆盖。
如果任务变成多个模块的独立调查,或者确实需要不同权限的执行角色,我会考虑拆分,并先定义交付格式、依赖和冲突处理。验证时用同一组任务比较验收成功率、端到端耗时、Token 成本和人工介入,决定是否值得引入,不能只拿一个演示任务下结论。

图里的模型与工具循环已经能承载多步任务。要不要拆成多个 Agent,可以继续对照Multi-Agent Harness 面试详解看协调和隔离成本。
# 三、MCP 调用和本地 CLI 调用有什么区别,各有什么优缺点?
我先区分层次:MCP 是接入协议,CLI 是程序的命令行入口,两者可以配合使用。 MCP 服务既能通过 stdio 在本地启动,也能通过 Streamable HTTP 提供服务,所以不能把 MCP 等同于远程调用。接入方式可以结合本站的 MCP 协议详解理解。
以搜代码为例,执行器可以直接启动 rg,也可以调用 MCP 暴露的 search_code,由服务端在内部运行 rg。模型提出调用请求后,都需要宿主侧校验并执行,模型不会因为看见工具定义就获得操作系统权限。
| 比较点 | MCP 接入 | 本地 CLI 接入 |
|---|---|---|
| 接口契约 | 有工具发现、参数 schema 和协议消息约定,具体返回质量仍取决于实现 | 依赖参数、退出码和输出约定,有些工具支持 JSON,有些需要解析文本 |
| 复用方式 | 便于让多个兼容客户端接同一服务 | 便于复用现有开发工具和脚本 |
| 接入成本 | 需要维护协议适配、服务生命周期和可能的网络认证 | 需要维护安装版本、环境变量、工作目录和进程管理 |
| 调用代价 | 包含协议编解码及进程或网络开销,远端也可能复用长驻服务 | 可能包含进程启动和文本处理开销,性能要按实际路径测 |
本地仓库里的 Git、编译和测试,我会优先复用成熟 CLI;需要统一暴露企业检索、接多个客户端时,MCP 更方便管理契约。工具数量、描述和返回大小都会影响上下文成本,不能认为换成 MCP 就天然省 Token。
两种方式都要限制路径、权限、时间和输出。调用 CLI 时尽量使用程序名与参数数组,避免把模型字符串直接拼进 shell;MCP 的 schema 能约束参数形状,也不能替代业务权限校验。
# 四、Agent 的可观测性怎么做,指标是怎么得出的?
我会把一次任务关联成一条 Trace,模型请求、检索、工具执行和验收分别记录 Span,再关联日志与指标。跨进程或队列传递 Trace 上下文,才能从“最终失败”追到具体哪一步;Trace 的记录与回放可以结合本站的Agent Harness 可观测性面试详解理解。
工具层记录调用 ID、工具名、脱敏后的参数、开始结束时间、退出码、超时和重试;模型层记录模型与 Prompt 版本、usage、错误和流式响应时间;任务层记录基线 commit、检索到的路径、补丁和最终验收状态。长文本可以保存受控引用,没必要把所有原始内容塞进指标标签,也不依赖模型内部思维过程。
| 指标 | 数据来源与计算口径 |
|---|---|
| 任务端到端延迟 | 从服务接收任务到任务进入终态,包含约定范围内的排队与重试;按任务统计 P50、P95 |
| 首 Token 延迟 | 模型调用开始到首个约定类型的生成 Token 到达,区分供应商口径和客户端观测值 |
| 工具执行成功率 | 成功执行次数 / 总执行尝试次数;超时、参数错误、取消等状态分别记录 |
| 任务验收成功率 | 达到预设验收条件的任务数 / 纳入评测的任务数;超时和未完成任务不能悄悄剔除 |
| Token 与估算费用 | 以接口 usage 为主,按模型、计价版本和缓存类别计算;本地 tokenizer 只能作为估算 |
| 检索 Recall@K | 在有相关片段标注的评测集上,看 Top-K 覆盖了多少应召回证据,不能直接从线上日志推断 |
比如 rg 正常退出只证明搜索过程完成,不证明找到了正确实现;测试命令返回成功,也不证明覆盖了全部需求。工具成功、测试通过和任务完成,是三种不同的状态。
指标还要交代统计窗口、样本来源、重试口径和版本。全量计数器用于算总体比例,采样 Trace 用于分析个案;如果 Trace 特意保留更多失败样本,就不能直接用它计算总体失败率。更完整的记录字段和回放方法,可以看Agent Harness 可观测性面试详解。

这张图对应的是一次任务需要留下哪些证据。看板只能展示采集到的数据,不能凭空得出“回答正确率”。
# 五、怎么看待 Human as a Judge 和 LLM as a Judge?
我会把人工评委和模型评委结合起来,先明确评什么,再选择裁判。人工更适合业务语义、复杂失败和争议样本,但成本高,也会存在标准理解不一致;模型适合批量初筛和回归评测,但需要用人工标注校准。
代码任务里,能用编译、测试、静态检查和 Diff 规则判断的条件,我会先用这些客观检查。LLM 再看测试难覆盖的部分,比如解释有没有引用真实证据、修改有没有超出需求。编译失败不能靠一个“代码质量 9 分”抵消。
人工标注时先写清评分标准和失败示例,让多人独立标注一部分共同样本,分歧再讨论。模型评测固定评委模型、Prompt 和评分规则,要求给证据;做 A/B 比较时交换答案顺序、隐藏方案名称,并检查重复评测是否稳定。同时要检查评委是否偏好靠前的答案、更长的文字或与自身风格相近的输出。评委的使用边界,可以继续看本站的Agent 幻觉约束与输出校验。
我会在独立保留的人工标注集上,检查模型判断的一致率,以及把失败错判为成功的比例。调评委 Prompt 的样本和最终验收样本要分开。模型判高分但测试失败、两种评委结论冲突的任务,进入人工复核,评测分数也不直接等同于真实用户满意度。
# 七、为什么代码知识库不用图,而用现在的技术栈,优点在哪?
我会先说明主要查询是什么。在这个示例里,常见问题是“某接口在哪实现”“这段报错关联什么代码”,目标是定位少量证据。关键词检索擅长精确符号和错误字符串,向量检索补充自然语言描述,两路结果融合后重排,再按路径回读当前文件,能比较直接地满足需求。
代码按方法、类等结构切分,保留签名、路径、行号和版本。对超长方法继续拆分,并保留父级关系;命中后扩展读取必要的类定义或调用点。这个方案的优点是接入现有搜索设施方便,增量更新和召回问题也容易逐层排查,不需要先构建完整的跨语言关系图。
图更适合显式关系问题,例如“改动这个接口会影响哪些调用者”。但代码图需要解析符号、解析引用和更新边,还会遇到反射、动态调用、生成代码与跨服务关系不完整的问题。知识图谱式 GraphRAG 和基于 AST、调用关系构建的代码图,也不能混成同一种方案。
所以我不会说“图没有用”,而是用查询集判断收益。若主要失败来自自然语言召回,就优化切分、混合检索和重排;若主要失败来自调用链缺失,就加符号跳转或调用图扩展。图、关键词和向量也能组合,并非只能选一个。
对比时看标注集上的 Recall@K、证据完整性、最终回答正确性,以及索引更新延迟、查询耗时和维护成本。取舍可以结合代码 Agent 为什么需要 grep 和 RAG与GraphRAG 面试题继续理解,不能只用“图更复杂”代替分析。
# 八、KV Cache 的原理是什么,工程侧怎样提升复用?
在常见的自回归 Decoder Transformer 中,每层注意力会为输入位置计算 Q、K、V。生成新 Token 时,可以保留历史 Token 的 K、V,只计算新增位置的投影,再用当前 Q 与已有 K、V 做注意力,避免反复计算历史位置的 KV。历史 Q 通常不用为未来步骤保留。
输入阶段叫 prefill,后续逐个生成叫 decode。KV Cache 减少重复计算,但新 Token 仍要读取历史 KV 并进行注意力计算,也要经过其他网络层。 因此上下文变长,显存占用和读取开销仍会增加,不能说缓存后每步成本恒定。
按未分片、未量化、各层长度一致的常见情况,KV 数据量可近似写成:2 × 层数 × 缓存Token总数 × KV头数 × 每头维度 × 单元素字节数。这里的 2 代表 K 和 V;GQA、MQA 要用 KV 头数,不能直接套查询头数,实际分配还要考虑块和元数据开销。
工程侧提升复用,我会分成两层:
- 单次生成内部复用:推理引擎保留已有 KV,追加新位置,避免每次把整个序列重新前向计算。
- 跨请求前缀复用:引擎支持 Prefix Caching 时,共享的 Token 前缀可以复用已有 KV,主要节省重复 prefill。固定系统提示、工具定义和稳定上下文放前面,用户问题与变化数据放后面,避免在前缀里插入不断变化的时间戳、随机 ID,工具顺序也保持稳定。
前缀复用要求 Token 前缀相同,并满足引擎对模型权重、适配器、位置编码等配置的兼容要求;只是“意思差不多”不能直接复用。路由到保存缓存的实例有助于命中,但还要平衡排队和负载。缓存也会被淘汰,权限或租户隔离要求不能为了命中率被绕过。
PagedAttention 的分页管理有助于减少显存浪费和管理共享块,但不能等同于“自动获得更多相同前缀”。量化、卸载也属于容量或存储取舍,需要分别评估精度与搬运成本。缓存与显存管理的关系,可以继续看本站的 KV Cache 与 PagedAttention 详解。
我会同时看缓存命中 Token 占可缓存输入 Token 的比例、冷暖请求的首 Token 延迟、端到端延迟、显存和吞吐,记录请求长度与并发。不能只报命中率,也不能把答案缓存、会话记忆和 KV Cache 当成一个东西。
# 九、Protobuf 和 JSON 有什么区别?
JSON 是文本数据格式,通常会把字段名一起传输,可读性好,浏览器和调试工具支持方便。Protobuf 通常指基于 schema 的二进制序列化,使用字段编号和 wire type 编码,服务之间需要共享消息定义,通常由 .proto 生成读写代码。
比如同一个任务状态消息,JSON 会直接出现 taskId、status 等字段名;Protobuf 二进制使用字段 tag 表达这些信息,减少重复字段名的开销。数值还可能使用 varint 等编码,但不是每种数据都更小,也不能脱离库、消息形状和压缩条件断言一定更快。字段编号和 wire type 用于识别字段及其编码类别,具体字段含义仍要结合消息定义理解。
兼容性上,Protobuf 可以通过新增字段等兼容方式演进,删除字段要保留编号,不能把旧编号重新分配给不同含义。JSON 也可以通过明确的接口契约兼容演进;它不强制内置 schema,不代表不能做类型校验。JavaScript 对大整数的表示还需要接口额外约定。
在这个示例里,外部 HTTP 接口和方便排查的事件可以先用 JSON;若内部消息量、跨语言契约和带宽开销成为实际问题,再评估 Protobuf,保留人能读懂的诊断出口。Protobuf 是序列化方案,gRPC 是 RPC 框架,不能画等号。
# 十、后端项目通过什么方法提升 QPS,怎么测的?
我会先说清是哪种接口:提交任务、查询状态,还是等 Agent 执行完才返回的接口。它们耗时和瓶颈不同,不能拿快速返回任务 ID 的接口,和完整模型任务的吞吐直接比较。
我会先做基线压测,再结合 Trace、数据库慢查询、连接池等待、CPU 和 GC 判断瓶颈。比如任务列表慢,检查 SQL 和执行计划,优化索引、查询字段或分页方式;状态查询有热点,再评估缓存及失效策略;索引更新占用请求线程,则在业务允许异步完成时改成可靠投递。阻塞在外部模型上时,盲目加线程可能只会放大限流和排队。
压测时固定机器资源、实例数、数据量、模型与依赖配置,说明是否预热、是否使用模拟依赖,以及请求类型和长度分布。用阶梯负载逐步提高到达速率或并发,观察稳定窗口,记录成功 QPS、P95/P99、错误和超时率、资源占用,并确认压测机本身没有先到瓶颈。
我把“可用 QPS”定义为在约定延迟与错误率范围内,单位时间完成的有效请求数。超时、被拒绝和业务失败要单独报出;用闭环固定并发测试时,还要说明服务变慢会降低实际发压速率,不能把配置的并发数当成 QPS。
每次改变一个主要因素,或清楚列出组合变更,再用相同负载做前后对照。异步方案除了接收 QPS,还必须看队列积压、消费吞吐和任务最终完成时间。没有测量记录,我会讲方案和验证方法,不临时报“提升了三倍”。
# 十一、为什么选用 Kafka?
在这个示例里,我考虑 Kafka,是因为代码索引更新属于可异步处理的变更事件,而且检索索引、审计等下游可能独立消费;索引规则变化后,还可能需要重放保留期内的事件。事件里记录仓库、目标版本和事件 ID,比只发送一句“有更新”更方便定位和补偿。
Kafka 的分区日志、保留和消费位点适合这类场景,不同消费者组可以独立推进。需要同一仓库更新保持顺序时,可以按仓库 ID 分区;但只能得到分区内的日志顺序,还要控制消费端并行方式,不能宣称所有消息全局有序。
可靠性需要单独设计。MySQL 写入和 Kafka 发布之间可以用事务 Outbox 等方式衔接,事件持久化后再转发;消费者按事件 ID 或版本做幂等,成功处理后再提交位点,并设置重试、失败隔离和积压告警。Kafka 生产者幂等或事务,不能直接保证写外部 Elasticsearch 也恰好一次。
如果任务量小、只是单一后台作业,数据库任务表可能更省事;如果主要诉求是复杂消息路由和任务分发,也应比较其他消息系统。选 Kafka 要能解释持久化、重放、消费模型和运维成本,不能只背“高吞吐”。
评论
验证登录状态...