字节跳动 Agent 开发面试:Harness 设计、上下文管理与长任务排查
- 时间
- 2026-10
- 来源
- 牛客网
《面试题目》
Agent 架构与设计
- 你们 Agent 项目的整体架构是怎么设计的?
- Harness 和 LangChain 的本质区别是什么?
- Harness 里面怎么管理状态?
- Agent Loop 是怎么实现的?Plan / Executor / Checker 怎么分工?
- ReAct 和 Plan 模式有什么区别?分别适合什么场景?
- Context 和长期 Memory 有什么区别?
- Agent 运行中 message 分几类?
上下文与记忆
- 上下文超过模型限制怎么处理?
- 大量工具描述导致 Prompt 过长怎么办?
- 记忆管理怎么做的?超出上下文怎么处理?
- 短期 Memory 和长期 Memory 分别怎么实现?
长任务、容错与多 Agent 协作
- Agent 出现路径震荡怎么优化?
- 多个 Agent 同时修改文件怎么避免冲突?
- Agent 长任务从 5 分钟变成 20 分钟,怎么排查?
- 工具调用参数错误、超时、重试怎么设计?
- LLM 返回 tool call 格式不合法怎么容错?
- 工具返回 50MB 数据,上下文炸了怎么办?
- 多 Agent 通信为什么不用网络通信?怎么交互?
- Agent 怎么判断任务已完成并停止?
- 长程任务怎么防止行为漂移?
- 用户中途取消任务怎么实现?
工程与基础
- 模型是自己部署的吗?
- HTTPS 为什么能实现加密通信?
- 根证书有什么用?
- 进程和线程的区别?进程间怎么通信?
- ThreadLocal 线程安全吗?有什么内存泄漏风险?
- BeanFactory 和 ApplicationContext 有什么区别?
- MySQL 去重语句有哪些?ROW_NUMBER() 怎么用?
- Linux 怎么查看进程和端口?
- 哈希表怎么解决哈希冲突?
手撕
- 给定两个嵌套结构的 JSON,实现合并函数:字典递归合并相同 key,只在一个字典里出现的 key 直接保留,列表做拼接。
项目追问
- 面试评分、简历评分的内容如何结构化输出?从哪几个维度评分?
- 项目里的 RAG 用了哪些知识?离线的上传环节和在线问答环节分别怎么进行?
《参考解析》
Harness 与 LangChain 的本质区别
LangChain 是一套通用零件库:Prompt 模板、Chain、Tool、Retriever、Memory,解决的是「把一次模型调用拼起来」。Harness 是包在模型外面的运行外壳——一次任务从接受到结束,循环怎么转、状态存在哪、工具怎么注册和执行、上下文怎么装配和压缩、失败怎么重试、预算怎么卡、全过程怎么观测,这些策略的总和才是 Harness。
真正的差别在控制权:用框架时执行器是框架的(比如 AgentExecutor 的循环固定,能改的主要是 prompt 和回调);自研 Harness 意味着每一步的判断、终止条件、错误恢复和状态落盘都由自己定义,可以针对业务做上下文压缩策略、工具路由、检查点恢复这些框架不提供的动作。回答时最好给出具体的取舍——为什么这个业务不能直接套框架、自研多付出了多少维护成本。
状态管理与 Agent Loop 的分工
状态不是只有消息列表。一次任务里至少要落盘四类东西:对话与工具调用轨迹、计划与当前进度(plan、TODO、已完成步骤及结论)、中间产物(大结果外置后的文件路径或变量引用)、预算与计数(步数、token、耗时、重试次数)。状态按 thread 隔离,每一步结束打检查点,进程挂了可以从最后一个检查点续跑;并发写同一会话时用版本号或乐观锁,冲突就重读重做。
Agent Loop 就是「组装上下文 → 调模型 → 解析输出 → 执行工具 → 把结果作为观测回灌 → 判断是否继续」的循环。Plan / Executor / Checker 的分工价值在于把判断和事实核验分开:Plan 负责把目标拆成有依赖关系、有可验收产物的步骤;Executor 一次只做一步,把大结果外置、只回摘要和状态;Checker 独立验证产物(跑测试、schema 校验、规则检查),失败就带着可复现的失败证据回到 Plan 重规划。执行者不能自证正确,这是这套结构存在的理由。
ReAct 与 Plan 模式的适用场景
ReAct 是边想边做:每一步根据最新观测决定下一步,灵活、对反馈敏感,但路径短、容易被局部信息带偏,适合探索性任务和需要频繁与环境交互的场景(排查问题、调用外部 API 试探)。Plan 模式先把全局拆好再执行,步骤之间的依赖和顺序提前固定,适合步骤长、依赖多、需要先看清全貌的任务(跨多文件重构、多阶段数据处理)。实践中常混合:先粗粒度 Plan 保证方向,每个步骤内部用 ReAct 处理细节,执行中发现计划不成立再触发重规划。
上下文超过模型限制怎么处理
按「先无损、后有损、最后换路子」的顺序做。无损节约:工具结果外置到文件或对象存储,上下文里只留路径加摘要;折叠历史里已经完成的工具调用原文;去掉重复的示例和冗余系统提示。有损压缩:把较早的对话压成摘要(保留数字、文件名、结论这类硬信息),滑窗只留最近若干轮,或者直接丢弃最旧的工具原文。最后才是换长上下文模型、分段 map-reduce、或先检索再生成。
Context 和长期 Memory 的区别要分清:Context 是本次推理窗口里模型看得到的东西,任务结束就没了;Memory 是跨会话持久化的存储,需要主动读写。长期记忆的工程重点是写入过滤(不是所有对话都值得记)、冲突覆盖(用户改了口径,旧记忆要失效而不是并存)、召回限额与时效标注(检索回来的条目要有数量和 token 上限,并标明来源与时间)。
Agent 运行中的 message 一般分四类:system(规则与工具说明,压缩时优先级最高)、user、assistant(含 tool call)、tool result(可外置、可摘要,压缩优先级最低);工程实现里还会有内部注入的状态类消息(计划、检查结果、预算提醒),这类不进对话历史,只在需要时拼进当轮上下文。
工具描述过长与工具调用容错
工具多到 prompt 装不下时,不要把所有工具一股脑塞进去:先按任务做一次工具检索或路由,只挂当前子任务相关的工具子集;工具描述写短,参数用 schema 严格约束并给示例;把细碎工具合并成一个带 mode 参数的工具;文档外置成按需加载的资源,让模型需要时先读文档再调用。
工具调用这一层的容错要分情况设计:参数错误——用 JSON schema 校验,把校验错误原文回灌让模型自己改,而不是抛异常;同一工具同参数短时间内重复调用要做去重(幂等键)。超时——每个工具单独设超时,读类工具可以重试,写类工具必须带幂等键,超时后先查询真实状态再决定是否重试,避免重复副作用。格式不合法——解析失败不要直接崩,先做宽松解析(截取 JSON 子串、修掉尾随逗号等常见畸形),仍失败就把原文和错误一起回灌要求重出;连续失败降级到结构化输出接口或换模型重试一次。
工具返回 50MB 数据时,绝不能把原文放进上下文:落盘或写对象存储,上下文里只留指针加摘要加少量采样片段,模型需要细节时再用工具按需读取局部(按行分页、grep 关键片段)。
长任务变慢、路径震荡与行为漂移
「5 分钟变 20 分钟」这类问题要分层定位,而不是猜:模型侧看 token 数趋势、模型是否被切换、工具调用轮数是否变多、前缀缓存是否失效;工具侧看外部依赖耗时、单次返回体积、重试次数;Harness 侧看上下文装配与压缩耗时、状态序列化与落盘、锁竞争和串行化点;环境侧看网络与并发资源。做法是把每一步的耗时和 token 落成趋势数据再归因。
路径震荡(在两个方案之间反复横跳)通常是因为判断依据没有沉淀。收敛办法:把试过的方案、结论和失败原因写进结构化状态,要求模型重复尝试前先引用失败记录;给 Checker 明确的通过标准,让「没通过」有客观依据;限制重规划次数,超限就升级处理(换策略或交给人),而不是继续绕圈。
行为漂移是长任务里原始目标被后来的上下文淹没。对策是把目标、约束和验收标准放在每轮都可见、且不参与压缩的固定位置,阶段性地回头做一次目标对齐自查,产物偏离立即纠偏。
任务完成的判断不能只靠模型说「我完成了」:优先用显式的验收条件(测试通过、schema 校验、目标产物存在且非空),再补兜底停止条件(模型不再产生工具调用、连续若干轮没有新信息、达到步数与 token 预算上限、同类错误反复出现)。
中途取消要能把信号传到执行层(abort signal、子进程 kill),已经产生的副作用要能回滚或至少有检查点可恢复,取消后的状态要标记为「用户取消」而不是失败,避免污染指标和重试逻辑。
多 Agent 协作与文件冲突
同一台机器上的多 Agent 本质是同一批进程和同一份文件系统,通信走消息队列、共享内存或直接读写共享工作区就够了——延迟低、不用管端口和鉴权,也不引入网络分区这类不一致。网络通信的价值在跨主机、跨语言、隔离与权限边界,单机协作这些需求都不存在。
多个 Agent 同时改文件,最有效的是单写者原则:按目录或文件切分所有权,一个文件同时只有一个 Agent 可写;必须共享的文件用锁加版本号,写入前校验版本、冲突就重读重做(乐观锁);结构上更好的做法是让每个 Agent 产出各自的文件,由一个整合者负责合并。原子改名(先写临时文件再 rename)能避免读到半个文件。
模型部署与选型
自部署(开源权重加 vLLM / SGLang 这类推理框架)换来的是数据不出域、能做量化与 KV Cache 复用、高并发下单位 token 成本更低;走 API 换来的是免运维、前沿模型能力和弹性扩容。合理的回答是按任务分层:涉及敏感数据的链路自部署,需要最强推理或用量波动大的链路走 API,并结合自己的量级说明延迟与成本怎么权衡、切换成本有多大。
HTTPS 与根证书
HTTPS 是 HTTP 加 TLS。握手阶段用非对称算法(ECDHE 这类)协商出会话密钥,同时用服务器证书里的公钥验证对方身份;之后的业务数据用对称加密(AES-GCM、ChaCha20-Poly1305)加完整性校验——非对称解决密钥交换和身份认证,对称解决性能,这是整套设计能成立的原因。
根证书是信任链的起点,预置在操作系统和浏览器的信任库里。服务器证书由中间 CA 签发,中间 CA 由根 CA 签发,客户端逐级验证签名直到一个受信任的根;如果根证书不被信任或已过期,整条链上的站点都会验签失败。证书本身校验的是域名(SAN)、有效期、签发链和吊销状态(CRL / OCSP)。
进程线程、ThreadLocal 与 Spring 容器
进程是资源分配单位(独立地址空间、文件描述符表),线程是调度单位,同进程的线程共享地址空间,切换更便宜,但一个线程崩溃会带走整个进程。进程间通信有管道与 FIFO、消息队列、共享内存(最快,需要自己配合同步)、信号、socket 和文件。
ThreadLocal 给每个线程一份独立副本,实现上数据存在线程自己的 ThreadLocalMap 里、以 ThreadLocal 的弱引用为 key,所以它本身不共享、是线程安全的。风险在线程池复用:线程不销毁,值会一直挂着造成脏数据和内存泄漏(key 被回收后 value 仍被线程强引用),必须在 try / finally 里显式 remove。它解决的是「按线程隔离」,不解决多线程共享数据的同步问题。
BeanFactory 是最基础的 IoC 容器,负责 bean 定义、实例化和依赖注入,默认延迟初始化;ApplicationContext 在它之上增加了企业级能力——国际化、事件发布、资源加载、AOP 集成、注解驱动的自动装配,并在启动时预实例化单例,所以业务里基本都用 ApplicationContext。
MySQL 去重、Linux 排障与哈希冲突
MySQL 去重常见三类写法:DISTINCT(整行去重,无法按业务键保留某一条)、GROUP BY(配合聚合取值)、以及窗口函数 ROW_NUMBER() OVER (PARTITION BY 业务键 ORDER BY 时间 DESC) 配子查询或 CTE 取 rn = 1。窗口函数的优势是能指定「保留最新一条」,也能顺手带出该行的其它字段,适合「同一手机号只留最新记录」这类需求;再老一点的写法是用自连接或 NOT EXISTS 保留最小主键。
Linux 查看进程用 ps aux / ps -ef(配 grep 或 pgrep 按名找 PID)、top / htop 看资源占用;查看端口用 ss -lntp(新系统)或 netstat -lntp,定位某个端口被谁占用用 lsof -i:端口,不加 sudo 通常看不到别人的进程名。
哈希冲突有两大类解法:链地址法(每个桶挂链表,长度超阈值转红黑树,Java 的 HashMap 就是这样)和开放寻址(线性探测、二次探测、双重哈希,删除需要墓碑标记,装填因子要控制住)。工程实现里都会配合扩容 rehash,装填因子越高冲突越频繁、平均查找长度越长。
手撕:合并嵌套 JSON
规则很清楚:两边都是字典时按 key 并集递归合并,只在一侧出现的 key 直接取那一侧;两边都是列表时返回拼接结果;类型不一致时以第二个为准(这一点要先和面试官确认规则)。
实现上有几个细节值得主动说:不要原地修改输入,用新对象返回,避免调用方数据被悄悄改掉;递归时先判断类型再递归,遇到深层嵌套要考虑 Python 的递归深度限制,必要时改写成显式栈的迭代版本;字典的 key 顺序在 Python 3.7+ 保持插入顺序,合并后「原有的在前、新增的在后」符合直觉。若列表也需要按索引合并或去重,一定要先问清楚,别默认按自己的理解写。
项目追问:评分结构化输出与 RAG 两条链路
评分类需求的关键是把主观判断约束成结构化数据:预先定义维度(如技术正确性、表达清晰度、结构完整性、岗位匹配度)、每个维度的分档标准与分值范围,用 function calling 或 JSON schema 强制模型按这个结构返回,服务端再做一次 schema 校验,缺字段或越界就重试或按默认值补齐;要求每个分数带上原文证据片段,既方便解释也方便排查打分不一致。
RAG 要分清两条链路。离线上传:解析文档(去除页眉页脚与噪声)、按语义切分、加元数据(来源、版本、时间)、embedding 入向量库,同时保留原文与 chunk 的对应关系。在线问答:query 改写、混合召回(向量加关键词)、rerank、拼上下文、生成并带引用。两条链路要能对账——线上答不出来时,要能回到离线数据里确认目标 chunk 到底进没进库、切分是否把答案切断了,这是排查召回问题最快的一条路。