度小满 Agent 开发一面:ReAct、多 Agent 与记忆系统
- 轮次
- 一面
- 时间
- 2026-09
- 来源
- 牛客网
《面试题目》
- 整个 ReAct 流程是怎样的?
- 如果工具调用被用户打断怎么处理?比如当前循环正在执行,用户打断怎么处理?再比如当前循环正在执行,用户又加了新的提示词内容(像 Codex、Claude 那样可以在运行过程中继续输入),你们有没有做?怎么做?
- 校准对话 History 与工具调用 ID 的关联是怎么做的?过程中有没有遇到过什么坑?怎么解决的?
- 多 Agent 怎么分工的?怎么实现的?子 subagent 之间是怎么传递消息的?
- 为什么要用多 Agent?
- 子 Agent 遇到问题、调用失败怎么办?
- 简历中提到的快照是什么?怎么实现的?详细说说。
- 子 Agent 怎么实现上下文隔离?子 Agent 之间有共用的部分吗?还是都不同?
- 记忆系统是怎么设计的?详细说说。
- 遇到过任务执行幻觉吗?就是 Agent 说他做了、实际没做。遇到过什么?怎么解决改进的?
- 压缩机制怎么保证不丢失关键信息?详细说说。
- 手撕:IP 地址解析。
《参考解析》
ReAct 主循环:重点在终止条件和消息协议的完整性:ReAct 不是「思考—行动—观察」三个词背一遍,而是一个由模型输出驱动的循环:把系统提示、历史消息和工具定义发给模型,模型要么输出最终答案、要么输出一个或多个工具调用;执行工具后把结果以 tool 消息的形式追加回历史,再次调用模型,直到出现终止条件。工程上真正要答的是三件事。第一是终止条件必须有多重:模型输出不含工具调用、达到最大步数或 token 预算、同一个工具用相同参数重复调用超过阈值、外部取消信号——只靠「模型自己说完了」迟早会失控。第二是消息协议必须严格配对:assistant 的 tool_calls 和随后的 tool 结果消息要按 id 一一对应且顺序正确,缺一条或 id 对不上,OpenAI 兼容接口会直接返回 400,这就是题目里问的「History 与工具调用 ID 的关联」,常见坑是中断/截断历史时把 assistant 的 tool_calls 和它的结果切散了,修法是按「消息组」而不是按单条消息做裁剪,并在落盘时校验配对完整性。第三是错误也要回填:工具抛异常时,把结构化的错误信息作为 tool 结果返回给模型,让它自己决定重试还是换路,而不是静默失败让循环空转。
工具调用被用户打断、或运行中追加新输入:这道题最有含金量,考察的是把一次用户轮次拆成可取消执行单元的能力。取消要贯穿两层:LLM 流式请求用可中止的连接(AbortController 这类机制),工具执行要响应取消令牌,循环每步开始前检查状态。但关键区分是「可取消」和「不可取消」的工具——纯读的查询可以随时中断,而已经发出去的写操作(下单、支付、写库、发消息)不能假装取消,必须靠幂等键 + 事后对账/补偿,UI 上如实显示「已提交,等待结果」。中断后要把状态落盘(任务目标、已完成步骤、已有的工具结果、当前假设),下一轮从断点续跑,而不是重放全部步骤,否则副作用会重复执行。运行中追加输入有两条路线,Codex 类产品两种都用过:排队(新输入作为下一条用户消息,在本次循环结束后的下一轮生效,实现简单、语义清晰)和抢占(立刻中断当前循环,把新输入并入上下文重新规划,响应快但要处理已产生的副作用与状态回滚)。选择标准是任务是否可分割、副作用是否可逆;同时给用户明确的反馈——「已打断,正在重新规划」比静默丢弃体验好得多。
多 Agent:为什么拆、怎么传消息、上下文怎么隔离:拆多 Agent 的理由通常是四条:单个上下文窗口装不下(长任务、大代码库)、一个 Agent 里角色和指令互相冲突(规划与校验混在一起容易自我背书)、需要并行提速、以及子 Agent 可以独立配置模型/工具/权限并单独测试。实现上一般是编排者模式:主 Agent 负责分解任务、决定调用谁、汇总结论;子 Agent 只拿到属于自己那部分的上下文——任务描述、必要的工具、以及只读的共享事实,这样天然形成上下文隔离,避免互相污染。消息传递一般不是两个 Agent 互相对话,而是通过共享工作区(文件系统、KV、消息总线)交换结构化结果:子 Agent 写产物,父 Agent 读产物和状态摘要,而不是去读子 Agent 的全部思维链——既省 token,也防止中间推理污染主上下文。共用的部分通常是任务目标、全局约束、共享事实和工具结果缓存,各自独有的是推理历史与临时状态。代价要说清楚:信息在边界处会丢失、错误会沿链条放大、调试变难(所以每一步都要有可回放的 trace 和明确的超时/重试/降级策略)。子 Agent 失败时的处理顺序是:带上下文重试一次 → 换策略或换工具 → 降级成更小的子任务 → 把失败原因和已完成部分回传给父 Agent,让它决定替代路径,绝不能让父 Agent 误以为子任务成功。
记忆系统与压缩:怎么做到不丢关键信息:记忆一般分三层——会话内的短期上下文、跨会话的长期事实、以及用户画像与偏好;写入策略建议抽取式,只把事实与结论落库(带来源、时间戳、置信度),不存整段原文,否则检索回来全是噪声。检索用向量加关键词的混合召回再重排,注入前按预算裁剪。面试官追问的「记忆里捞到之前的类似对话,Agent 该沿用旧方案还是换新方案」,正确做法是把方案连同适用条件和结果一起存:记「在什么前提下、用了什么做法、结果如何、有没有失败过」,而不是存一条无条件的结论;检索时把这几项一起给模型,让它判断当前条件是否匹配,条件不符就走新方案,匹配度低就用澄清追问确认,而不是默认复用。压缩上,自由摘要最容易丢字段,建议用固定结构(任务目标 / 当前进度 / 已确认的约束与事实 / 未完成待办 / 失败尝试与原因 / 关键引用 id)来压缩;顺序是先做确定性清理——去掉重复的工具日志、把大段原始输出截断但保留关键字段和可回查的 id——再做摘要;对超长任务用分层压缩,老的细节收敛成结论、最近的保持高保真。判断压缩是否合格的依据不是长度,而是「压缩后继续执行是否走偏」,所以要用可复现的长任务做回归评测。
执行幻觉(说自己做了实际没做)的治理:根因通常是模型在上下文里看到了「计划做某事」的文字,把它当成了「已经做过」,或者工具调用失败后模型自行脑补了成功结果。治理手段分三层:协议层要求每个「已完成」的结论都必须绑定一次真实的工具调用记录(有 id、有时间、有返回),没有记录就不允许出现在最终答复里;执行层把副作用操作做成幂等且可校验(写完回读一次、对账关键计数),让「实际没做」能在下一步被自动发现;展示层区分「计划中 / 执行中 / 已完成 / 失败」四种状态,不要让模型的自然语言描述直接决定 UI 状态。再加一条兜底:对关键产物做确定性的存在性检查(文件在不在、记录数对不对),失败就带着检查结果重新进入循环。