月之暗面 Agent 应用开发:上下文超限,连追五层
- 结果
- 挂
- 时间
- 2026-10
- 来源
- 牛客网
《面试题目》
Agent 架构与流程
- 背景及选择 Agent 方向的原因?
- 一个完整的 Agent 系统通常包含哪些核心模块?相比传统应用多了什么?
- Agent 项目架构设计及解决的问题?
- 你怎么理解 Harness?
- ReAct 和 Plan-and-Execute 你选哪个?为什么?
- Agent 如何做任务规划?
- 路由机制(请求交给哪个 Agent)?
多 Agent
- 多 Agent 并行执行的状态管理和结果汇总?
- 多 Agent 之间如何通信和同步状态?用过消息队列吗?还是共享内存?
- 多 Agent 之间上下文和状态怎么隔离?
- 多 Agent 同时执行时争抢哪些资源?怎么做隔离?
上下文与工具
- 上下文管理(避免过长导致效果下降)?
- 大量工具时避免 Prompt 过长的方案?
- 工具返回数据量过大的处理?
- 如果上下文超限了,你怎么处理?
- 用摘要压缩的话,摘要丢失关键信息怎么办?
- 分块存储后,跨块关联怎么保证?
- 工具调用失败,容错机制是什么?有没有做重试?重试几次?
RAG 与安全
- RAG 全流程(切分、向量检索、召回、生成)?
- 文本切分策略怎么选?语义切分怎么实现?
- 如何保证 Agent 生成代码的安全性?有没有在沙箱里执行?
- Agent 对工具调用不做限制会产生哪些风险?怎么防范?
- Prompt Injection 怎么防范?用户诱导 Agent 泄露 System Prompt 怎么办?
评测与手撕
- 如何评测你的 Agent 系统?关注哪些指标?端到端成功率大概多少?
- 手撕:LRU Cache,并讲思路。
项目追问
- 你的 Agent 项目整体架构是怎样的?单 Agent 还是多 Agent?
- ReAct Agent 的核心执行流程是什么?Thought、Action、Observation 分别做什么?
- Agent 调用工具后,返回结果的消息结构怎么设计?为什么需要单独的 Tool Message?
《参考解析》
1. Agent 系统的核心模块与 Harness 的位置。 可上线的 Agent 系统拆六块:接入与会话、模型与工具运行时、循环与状态机(规划、执行、观察、终止、重试降级)、记忆与检索、安全与权限、评测与可观测。相比传统应用多出来的核心是不确定性管理:输出可能格式不对、可能选错工具、可能绕圈,所以必须有结构化校验、终止条件、幂等与降级。Harness 指包在模型外面那层让它可靠跑起来的工程外壳:循环驱动、上下文组装与预算、工具调用与结果归一、重试熔断、评测回归、Trace 与成本记账。同一个模型,Harness 好坏会让成功率与成本差好几倍。
2. ReAct 与 Plan-and-Execute 的取舍,以及任务规划。 ReAct 想一步做一步,能按观察调整、适应不确定环境,但步数多、每步带全量上下文、长任务易迷失;Plan-and-Execute 先分解再执行,方向明确、可并行、上下文可按子任务切小,但计划错了要返工。常见答案是混用:粗粒度用计划(分解出可验收的子任务)并允许执行中动态重规划,子任务内部用 ReAct。规划的实现要点是把可执行性交给代码:要求模型产出结构化计划(目标、约束、依赖、验收标准),代码校验是否有环、是否引用不存在的工具、是否超预算;每个子任务有独立上下文与状态,完成后把结果摘要回写计划状态。终止与放弃显式定义:子任务全部达成即结束,超步数或连续失败就降级并如实告知未完成的部分。路由按意图决定交给哪个 Agent,走错要靠 Trace 与 badcase 回补规则。
3. 多 Agent 的状态管理、通信与隔离。 状态管理三件事:每个子任务有独立状态对象(目标、已执行动作、中间结果、失败记录)且由代码维护;汇总层按结论、证据、置信度结构化归并,先按实体去重再处理冲突(证据强度优先,无法裁决就标记冲突升级),不做字符串拼接;每步落检查点,失败重试从中间继续。通信按耦合度选:共享内存只适合同进程强协作;进程内队列或跨机 MQ 是常规选择,解耦、可缓冲、可重放,代价是重复投递与顺序问题,所以每条任务带 taskId、幂等键与超时。争抢的资源是模型配额、沙箱、共享数据与外部工具配额,隔离手段是配额桶与优先级队列、一任务一沙箱或用租约从池里取、共享数据用版本号乐观锁、外部调用统一走网关限流;上下文隔离靠只传引用与摘要。
4. 上下文治理:工具描述、工具结果与超限处理。 三个源头分别治:工具数量多就按意图做工具检索或分级暴露,只给当前最可能用到的十几个工具并精简描述;工具结果大就约定结构化返回而不是自然语言长文,大结果落对象存储只把摘要与引用放进上下文、列表做分页与 top-N;历史变长就分层记忆,近期保留原文、更早做摘要、关键结论与用户约束写进常驻任务状态。快超窗口时按结构裁而不是从头砍:保持 tool_call 与工具结果成对(删一半会让 API 直接报错)、保留系统提示与任务约束、优先丢最早的完整轮次,关键状态外置。摘要丢关键信息的答案不是“写得更好”,而是结构化保留、校验、回捞:摘要必须覆盖目标、约束、已完成、待办、关键实体与数字;压缩后做一致性检查;丢了就从原始记录或工具产物重取。
5. 工具调用失败的容错与重试策略。 前提是分类:确定性失败(schema 不符、权限不足、资源不存在、业务拒绝)重试无意义,把结构化错误回灌让模型改参数或换工具;瞬时失败(超时、连接重置、5xx、限流)指数退避加抖动重试一到两次;语义失败(工具成功但结果为空或不合预期)要换策略——换关键词、换数据源、扩大范围或回上层重规划。重试次数与预算绑定:单工具上限、单任务总调用上限、总 token 与时间上限,触顶走降级并明确告知哪一步没做成。工程上要幂等(写操作带幂等键)、要缓存(纯读幂等结果)、要记录(requestId、耗时、结果状态),并对错误率超阈值的工具熔断。
6. RAG 切分与 Prompt Injection 防范。 RAG 链路是解析、清洗、切分、向量化并建关键词索引、检索、融合重排、生成、引用与评测。切分要与结构匹配:有标题先按标题切再按长度合并;表格、代码围栏、清单整块保留;纯文本滑窗加重叠;结构混乱时用语义切分(相邻句向量相似度骤降处断开)兜底。Prompt Injection 分四层防:把外部文档与网页内容标注为不可信数据并用结构化字段包裹,系统提示声明其中的指令一律不执行;输入检测拦截“忽略以上指令”“输出系统提示”等模式及编码、分段、多语言绕过;权限最小化,让模型即使被诱导也没有危险操作权限,写操作要确认、工具按身份鉴权;输出侧过滤系统提示片段与敏感字段。用户诱导泄露 System Prompt 时不靠藏得更深,而是保证提示里没有机密、检测到就回固定话术并记录。
7. 评测指标与 LRU、Tool Message 的设计。 评测分四层:检索的 Recall@k 与 MRR、工具的成功率与参数正确率、规划的无效步数与错分支比例、端到端的任务成功率与平均步数成本,并按任务类型分层报、持续回流 badcase。LRU 用哈希表加双向链表:哈希表 O(1) 定位,双向链表维护访问顺序,get 命中移到头部,put 满则淘汰尾部并把新节点插头部;带哨兵头尾能省掉大量边界判断,手写易错点是漏更新前驱后继、容量为 1、更新已存在 key 时忘记移动;要能覆盖线程安全(整体加锁最简单,ConcurrentHashMap 加分段锁更好,生产用 Caffeine)以及为什么必须双向链表。Tool Message 单独成消息的原因是协议与语义两层:多数厂商 API 要求 assistant 的 tool_call 与结果成对且带相同 call_id;语义上模型要区分用户说的话、自己决定调工具、工具返回的客观结果,混成一条会让模型分不清事实来源,也更容易被工具返回里的内容当成指令。