面灵AI→

字节跳动 Agent 开发面试:Harness 设计、上下文管理与长任务排查

时间
2026-10
来源
牛客网

《面试题目》

Agent 架构与设计

  1. 你们 Agent 项目的整体架构是怎么设计的?
  2. Harness 和 LangChain 的本质区别是什么?
  3. Harness 里面怎么管理状态?
  4. Agent Loop 是怎么实现的?Plan / Executor / Checker 怎么分工?
  5. ReAct 和 Plan 模式有什么区别?分别适合什么场景?
  6. Context 和长期 Memory 有什么区别?
  7. Agent 运行中 message 分几类?

上下文与记忆

  1. 上下文超过模型限制怎么处理?
  2. 大量工具描述导致 Prompt 过长怎么办?
  3. 记忆管理怎么做的?超出上下文怎么处理?
  4. 短期 Memory 和长期 Memory 分别怎么实现?

长任务、容错与多 Agent 协作

  1. Agent 出现路径震荡怎么优化?
  2. 多个 Agent 同时修改文件怎么避免冲突?
  3. Agent 长任务从 5 分钟变成 20 分钟,怎么排查?
  4. 工具调用参数错误、超时、重试怎么设计?
  5. LLM 返回 tool call 格式不合法怎么容错?
  6. 工具返回 50MB 数据,上下文炸了怎么办?
  7. 多 Agent 通信为什么不用网络通信?怎么交互?
  8. Agent 怎么判断任务已完成并停止?
  9. 长程任务怎么防止行为漂移?
  10. 用户中途取消任务怎么实现?

工程与基础

  1. 模型是自己部署的吗?
  2. HTTPS 为什么能实现加密通信?
  3. 根证书有什么用?
  4. 进程和线程的区别?进程间怎么通信?
  5. ThreadLocal 线程安全吗?有什么内存泄漏风险?
  6. BeanFactory 和 ApplicationContext 有什么区别?
  7. MySQL 去重语句有哪些?ROW_NUMBER() 怎么用?
  8. Linux 怎么查看进程和端口?
  9. 哈希表怎么解决哈希冲突?

手撕

  1. 给定两个嵌套结构的 JSON,实现合并函数:字典递归合并相同 key,只在一个字典里出现的 key 直接保留,列表做拼接。

项目追问

  1. 面试评分、简历评分的内容如何结构化输出?从哪几个维度评分?
  2. 项目里的 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 到底进没进库、切分是否把答案切断了,这是排查召回问题最快的一条路。