字节跳动后端一面面经(Agent 与 MCP 方向)
- 轮次
- 一面
- 时间
- 2026-09
- 来源
- 牛客网
《面试题目》
- 先讲一下你在实习做的第一个项目,整体的框架是怎样的,用来做什么的?
- 你负责的部分 Agent 是怎么实现的,有没有底层的设计优化?
- 你怎么看待 JEV,和 BERT 有什么区别?你认为 JEV 对你的 Agent 有什么优化点?
- RPC 和 MCP 的接口区别是什么?
- 开发 MCP 最重要的是什么?
- MCP 定义出参入参,实际过程中有没有出现调用 MCP 入参和出参出错的?怎么防止?
- Skill、workflow、Agent 三者的联系和区别是什么?
- 你的项目中有没有用到 RSI,怎么做的?
- 你的项目为什么要用一个 Agent,而不是直接写一个 Skill 嵌入到里面?
- 你们的评审准确率等一些指标是怎么计算的?
- 聊一下你的后端项目,简单介绍一下
- Redis 通过 Lua 预扣减为什么还要乐观锁兜底?
- 如果假设投递消息是 exactly once、不丢不重复,还有必要乐观锁兜底吗?
- 强一致和最终一致性
- Redis 是强一致还是最终一致性?
- Redis 怎么做主从同步的?
- master 向 slave 传递数据,这里的数据都是什么?
手撕(共 20 分钟)
- K 个一组的链表反转
- 两数相加 II(不能用额外空间、输出链表)
《参考解析》
-
RPC 与 MCP 的区别:RPC 是「调用远端的一个方法」,接口是给程序员看的——服务、方法、参数类型都写死在双方约定的 IDL 里,改接口要改两端。MCP 是「把能力暴露给模型」,接口首先是给模型看的——重点在工具的名称、描述和 JSON Schema,模型靠自然语言描述来决定要不要调、怎么填参数。所以 RPC 的设计目标是类型安全和性能,MCP 的设计目标是可被正确选择。这个差别直接决定了两边的工程重点不同:RPC 追求契约稳定,MCP 追求描述清晰、参数可校验、失败可自修正。
-
开发 MCP 最重要的是什么:把「什么时候该用这个工具」写清楚,而不是把实现写得多漂亮。具体三件事——描述里包含触发条件和适用边界(以及不该用的场景),避免多个工具之间语义重叠导致模型选错;参数用 schema 约束类型和取值范围,并在服务端做校验,不要相信模型一定会填对;返回结果要结构化且信息量够,错误信息要能让模型看懂并自我修正(区分「参数填错了可以重试」和「这个操作本来就不允许」)。此外工具如有副作用必须幂等,否则模型重试会重复写入。
-
入参出参出错怎么防:入参侧用 schema 校验 + 服务端二次校验,把校验失败的原文回灌给模型让它改,同时设重试上限;枚举值在描述里列全,别让模型自由发挥。出参侧统一包装(成功/失败、错误码、可读消息),避免把异常堆栈直接丢回去;大结果要截断并说明被截断了,否则模型会以为数据就这么多。再往上,把高频出错的 case 固化成评测样例,改一次描述跑一遍。
-
Skill、workflow、Agent 的联系与区别:Skill 是一段被固化下来的能力(输入输出明确、执行路径基本确定),Workflow 是按固定顺序编排多个步骤的流程,两者都是确定性的;Agent 是让模型自己决定下一步做什么,是非确定性的。三者的关系不是替代而是包含——Agent 会调用 Skill 和 Workflow 作为手脚,能用确定性流程解决的就不该交给 Agent,因为 Agent 更贵、更慢、更不稳定。判据是:路径事先能写清楚的走 Workflow/Skill,路径依赖中间结果、需要临场判断的才上 Agent。
-
为什么用 Agent 而不是写个 Skill:Skill 的前提是「步骤固定、分支可穷举」。如果实际任务里下一步做什么取决于上一步拿到的内容(比如先看文档结构才决定读哪一节、评估结果不合格就要换方案),穷举所有分支会变成一棵爆炸的组合树,维护成本比给模型自主权更高。这时 Agent 的价值是把「决策」交给模型。反过来,如果这条路径其实很稳定,那用 Agent 只是把一个 Workflow 包了一层不确定性,是错误的选型。
-
评审准确率这类指标怎么算:要有评测集、有判定标准、有分母定义。三个必须说清的点:样本从哪来(真实流量采样还是构造)、单条怎么判对错(人工标注 / 规则 / 另一个模型当裁判,各自偏差不同)、分母是什么(是所有请求,还是模型给出了结论的那些——把「拒答」排除在分母外会虚高)。工程上要做成可回归的:每次改 prompt 或换模型都跑同一套集,看指标变化,而不是只报一个当下数字。
-
Redis 用 Lua 预扣减后为什么还要乐观锁兜底:Lua 保证的是单次 Redis 操作的原子性,不是整个业务流程的原子性。典型场景是「先扣库存、再落库/发消息」——Lua 扣减成功之后,如果后续步骤失败(进程崩了、网络断了、DB 写不进去),库存已经扣掉但业务没完成,这就需要补偿;反过来,如果业务侧有并发更新同一份数据的情况(比如读出来算一下再写回),Lua 管不到这一步,得靠版本号做 CAS。所以两者解决的是不同层次的并发:Lua 解决 Redis 内部的竞态,乐观锁解决「跨越 Redis 与其它系统」的竞态。
-
消息 exactly once 下还要不要乐观锁:要看 exactly once 是在哪一层保证的。如果消息系统真的保证「不丢不重」,那「重复消费导致重复扣减」这一类问题消失了,但并发问题还在——两个不同请求同时修改同一条业务数据,exactly once 完全不管这个,仍然需要乐观锁或数据库行锁。另外「exactly once」在工程上通常指端到端的幂等语义,实现上依然依赖消费端去重,所以消费侧写库时保留幂等键仍然是必要的。
-
Redis 的主从同步与强一致性:Redis 默认是最终一致——主节点处理写请求后异步把命令转发给从节点,主从之间存在延迟窗口;主节点挂了如果还没同步完,从节点提升后可能丢数据。异步复制的第一阶段是全量同步(主节点 fork 出子进程生成 RDB 快照发给从节点,期间的新写命令缓存进 replication buffer,快照发完再补发缓冲);之后进入命令传播阶段,主节点把每条写命令转发给从节点。传递的数据就是这些写命令流(以及全量阶段的 RDB 快照和 offset/runid 这类同步元信息),不是磁盘文件也不是查询结果。要更强的保证可以用
WAIT命令等指定数量的从节点确认,但代价是延迟和可用性。