面灵AI→

字节 AI Agent 开发一面 30 题全记录

轮次
一面
时间
2026-09
来源
牛客网

《面试题目》

项目深挖

  1. 讲一个你做过的 Agent 项目,它在真实业务里解决了什么问题?
  2. 那套系统整体架构长啥样,模块之间怎么通信?
  3. 项目里最让你头疼的一个坑是什么,怎么填上的?
  4. 为什么选 Agent 而不是写一堆规则脚本,收益到底在哪?
  5. 如果让你重做一遍,哪块你会直接换方案?

Agent / RAG 核心

  1. 给对话 Agent 加长期记忆,你打算怎么设计存储和读取?
  2. 当轮上下文和长期记忆怎么分工,什么值得落库?
  3. 上下文超出窗口了,你有几种压缩手段,各自会丢什么信息?
  4. 摘要压缩时你保留哪些关键信息,怎么判断哪些能扔?
  5. 多个 Agent 一起干活,怎么防止它们互相打架、卡死循环?
  6. 单 Agent、多 Agent、Workflow 编排,你什么场景选哪种?
  7. 走一步看一步和先列计划再执行,你更倾向哪种,为什么?
  8. Agent 调工具,工具挂了你怎么兜底,失败信息怎么回传给模型?
  9. RAG 为什么要用父子切块,小块检索大块返回解决了啥?
  10. 向量检索和关键词检索怎么结合,重排放在哪一步?
  11. 你用过的向量库,增删数据后索引怎么更新,缓存怎么失效?

八股基础

  1. HTTP 和 RPC 在内部调用上你怎么选,为什么?
  2. HTTPS 怎么保证传输加密,根证书起什么作用?
  3. 进程和线程的区别,Go 的协程你理解到哪一层?
  4. 垃圾回收你了解哪几种策略,分别适合什么负载?
  5. 缓存击穿、穿透、雪崩,你线上遇到过哪个,怎么处理的?

手撕

  1. 【手撕】给一串工具调用的日志,统计每个工具的成功率,并找出失败次数最多的那个工具名,要求时间复杂度尽量低。

工程与场景

  1. 你做的 Agent 上线后怎么评测效果,有没有离线评测集?
  2. 模型推理成本高,你从哪些维度去压?
  3. 你们模型是自己部署还是调 API,部署踩过什么坑?
  4. Agent 输出怎么防止跑偏、产生幻觉,你有哪几道闸?
  5. 需求方说要快上线但评测还没过,你怎么权衡?

反问与软素质

  1. 反问环节你会问什么,为什么这么问?
  2. 你觉得自己做 Agent 最大的短板在哪?
  3. 入职后第一个月只让你做工具调用稳定性,你会从哪入手?

《参考解析》

长期记忆分三层存,读写路径分开:工作记忆是当前会话的原始上下文,不落库;情节记忆是历史会话的摘要或关键片段,走向量库做模糊召回;语义记忆是用户画像和稳定事实(偏好、常驻城市、技术栈),走关系库做成 user_id + key + value + 置信度 + 更新时间 的结构化表,方便精确读取与覆盖更新。读取时先按 user_id 把画像拼进 system prompt(这部分不参与检索),再按当前 query 召回 top-k 情节片段,最后按 token 预算裁剪。写入是关键:让模型显式输出「新增 / 更新 / 作废」三种记忆动作而不是自由文本,冲突时以更新时间和置信度裁决,避免同一事实越攒越矛盾。

上下文压缩四种手段,丢的东西各不相同:滑动窗口截断最便宜,丢的是最早的约束和已确立的结论,长任务上会突然「忘掉需求」;摘要压缩保留目标和决策,丢数值细节和原始措辞(所以关键数值要单独存进事实表,不能只靠摘要);结构化抽取把历史转成任务状态和事实表,丢的是对话语气和推理过程;检索式回填按需召回历史片段,丢的是没被召回的那部分,风险是召回漏了却没人知道。工程上一般组合用:固定 system prompt + 近期 N 轮原文 + 更早历史摘要 + 一份关键事实表。

多 Agent 防打架靠所有权和预算闸,不靠提示词:每个任务有唯一 owner,其他 Agent 只能提议不能改共享状态;共享状态放中心化黑板/消息总线,带版本号做乐观并发,写完发现版本被人动过就重试。防死循环靠硬预算:最大轮数、最大 token、最大墙钟时间,再加上「无进展检测」——连续 N 轮共享状态哈希不变就强制终止。终止条件必须写成显式谓词(比如「测试全绿」),而不是让模型自己判断「差不多了」。

父子切块解决的是检索粒度和上下文完整性的矛盾:切得小(200~400 token),向量语义集中、命中率高,但返回给模型的信息太碎、缺上下文;切得大(1000+ token),信息完整但向量被稀释、检索命中率掉。典型做法是用子块建索引、命中后返回它所属的父块(或把同一父块下的兄弟块一起带上),两头的好处都拿到。要注意父块过大时按 token 预算截断,并保留父块内的段落顺序。

混合检索与重排的顺序不能乱:关键词/倒排负责精确匹配(型号、错误码、专有名词),向量负责语义泛化,两路各自召回 top-50 左右,用 RRF 之类的融合算法合并;合并之后、生成之前接 cross-encoder 重排(如 bge-reranker),对候选逐条打分,取 top-3~5 进 prompt。顺序错了(先重排后召回,或对全库重排)延迟会直接爆掉,cross-encoder 的复杂度是候选数乘以文本长度,只能在小候选集上用。

缓存三兄弟的解法各不同:击穿是单个热点 key 过期瞬间大量请求打到 DB,用单飞(同一 key 只放一个请求去重建)或逻辑过期(值里带过期时间,过期后先返回旧值、后台异步刷新);穿透是查根本不存在的数据,用布隆过滤器挡在前面,再对空结果做短 TTL 的空值缓存;雪崩是大量 key 同时过期或缓存整体故障,解法是 TTL 加随机抖动打散、做本地 + 分布式多级缓存、再配限流和降级兜底。

手撕题一次遍历就够:日志按行给,用哈希表统计,key 是工具名,value 维护 [总数, 失败数],一次 O(n) 遍历即可,空间 O(k)(k 是工具种类数);遍历时顺手维护失败次数最多的工具名和次数,避免第二次扫描。伪代码:

for line in logs:
    name, ok = parse(line)          // ok 为 bool
    slot = table.get_or_create(name)
    slot.total += 1
    if not ok: slot.fail += 1
    if slot.fail > best.fail: best = (name, slot.fail)
for name, slot in table: print(name, slot.fail / slot.total)

复杂度 O(n) 时间、O(k) 空间,这已经是下界(必须读完每条日志)。如果日志存在磁盘且超大,就流式按行读、不要一次 readlines();如果同一个工具名会被并发统计,注意分片后合并而不是加锁。