主题
高频面试题
AI Agent 如何处理工具调用返回超大结果的问题?
这道题看起来是在问一个小技巧,实际是在考你有没有真的做过 Agent 工程:很多时候,把上下文撑爆的不是用户问题,而是工具结果。
面试官角度分析,想考什么
超大工具结果会带来什么问题?
考你是否知道 observation 是上下文膨胀主源,会推高成本、延迟和质量风险。你会怎么治理返回内容?
考方案分层:工具协议做摘要和分页,执行层截断或落盘成句柄,模型按需二次拉取。怎么避免截断丢信息和成本失控?
考取舍:显式截断标记、回看路径、二次拉取上限,以及用 trace 看截断比例和失败率。
可直接抄走的 30 秒参考答案
text
工具结果是 Agent 上下文膨胀的主要来源,我不会把它原样塞回模型。我的做法分三层:第一,工具协议上默认返回摘要、总数、前 N 条和游标,支持 `limit`、`fields` 这类参数;第二,执行层做兜底,超长结果要么头尾保留并明确标记截断,要么落盘成 artifact,只把摘要和句柄放进上下文;第三,多轮任务里把旧工具结果蒸馏成结论,保留证据 id,避免历史结果把当前目标淹没。最后用 trace 看截断比例、二次拉取次数和任务失败率,判断这个策略是不是真的有效。面试回答详解,知其所以然
这道题的核心不是“加个 maxLength 截断”,而是把工具结果当成一种要治理的上下文资源:先在工具协议层控制产出,再在执行层兜底,最后在上下文层消化。三层配合,才像生产答案。
1. 为什么工具结果是上下文膨胀的第一大来源
Agent 每轮的行动指令(tool call)通常只有几十个 token,但工具的观察结果(observation)动辄几千到几十万 token,两者天然不对称。
容易超大的工具类型:
- 读文件、读代码库:整个文件或目录结构一次性返回。
- 搜索引擎、网页抓取:整页 HTML 或几十条搜索结果全文。
- 数据库查询:SELECT 结果上千行。
- 命令执行:构建日志、测试输出、stack trace。
- 内部系统 API:订单列表、监控指标、日志流。
一个搜索工具一次返回 10 条结果全文,就可能相当于几十轮对话的 token 量。如果不治理,几轮之后 Agent 的上下文里大部分都是工具结果,用户目标被淹没,成本和延迟同步恶化,还会触发 Lost in the Middle:关键结论被埋在大块结果中间,模型视而不见。
2. 第一层:工具协议层,从源头控制结果大小
最好的方案是工具一开始就别返回那么多。设计工具返回 schema 时遵循"摘要优先、细节按需":
json
{
"status": "ok",
"summary": "共找到 128 个订单,其中 12 个待处理",
"total": 128,
"items": [
{ "id": "a103", "title": "订单标题", "status": "pending" }
],
"item_count": 10,
"cursor": "next_page_token",
"truncated": false
}关键设计点:
- 稳定 schema:字段固定、类型固定,模型能学会怎么用,而不是每次猜格式。
- 摘要先行:
summary让模型不翻页也能拿到结论性信息。 - 默认限量:默认只返回前 N 条(例如 10 条),
total告诉模型一共有多少。 - 分页游标:
cursor或next_page支持继续拉取,页大小可由模型指定。 - 参数化输出:工具入参支持
limit、offset、fields、format,让调用方控制粒度。 - 结构化提取:返回解析后的字段,而不是原始 HTML、原始 JSON dump 或整段日志。
- 结果分级:提供
detail_level: summary | standard | full,默认 standard。
典型例子是主流搜索类工具的做法:返回标题、URL、摘要片段,不返回全文,模型判断哪条相关后再用读取工具取正文。
3. 第二层:执行层,兜底截断与落盘
协议再好,也总有工具不受你控制(第三方 API、MCP 服务、shell 输出)。Agent 框架需要在执行层兜底:
截断策略
- 设 token 预算:为每类工具结果设上限,例如单条 observation 不超过 4K token。
- 头尾保留:日志类内容头部有上下文、尾部有错误结论,保留两头折叠中间。
- 显式标记:截断后必须注明,例如
[结果已截断,显示前 200 行 / 共 3521 行,可用 read_file(path, offset=200) 查看后续]。不标记的截断等于骗模型。 - 结构化提取替代文本截断:与其硬截 JSON,不如提取关键字段重新组一个精简对象。
落盘与句柄引用
- 超过阈值的结果写入外部存储(本地文件、artifact store、对象存储),上下文里只放引用:文件路径、行号范围、资源 id 或 URL。
- 配套提供读取工具:
read_file(path, offset, limit)、get_artifact(id, section),模型按需取片段。 - 引用要可验证:保留内容摘要或校验值,让模型和人都确认引用没失效。
text
工具执行 -> 结果超过阈值?
-> 否: 结构化摘要直接进入上下文
-> 是: 完整结果落盘 + 上下文只放 引用句柄 + 摘要 + 截断标记4. 第三层:上下文层,消化已经进来的结果
即使控制得很好,多轮积累的工具结果仍会占据大量窗口,需要上下文治理配合:
- 及时蒸馏:早期轮次的工具结果,只保留结论("测试 T1/T2 通过,T3 失败原因 X"),原始输出可以在后续轮次清理。
- 位置布局:大块证据放中间,提炼后的结论和当前目标放末尾附近,对抗 Lost in the Middle。
- 工具结果去重:同一资源反复读取时,用引用替换旧的大块内容。
- 与历史压缩联动:触发上下文压缩时,工具结果是优先压缩对象,但结论和证据 id 必须保留。
5. 生产细节与失败模式
方案落地时要防这些坑:
- 翻页死循环:每页太小或 cursor 语义不清,模型反复拉取同一页。要给分页参数设默认值和上限,并在 trace 里监控连续同页拉取。
- 模型不知道能要更多:截断标记里必须写清楚"怎么看剩下的",否则模型会把截断结果当完整事实。
- 错误结果污染上下文:工具失败要返回结构化错误(error code、message、修复建议),不要把原始 stack trace 当 observation 塞回去。
- 缓存失效:工具结果位于 prompt 尾部,频繁变化会破坏 prompt cache 命中,长会话要注意稳定前缀和变化后缀的分层。
- 评估闭环:统计每轮工具结果的原始大小、截断比例、二次拉取次数、翻页深度,以及"因信息不全导致重试或失败"的比例,用数据调页大小和阈值。
一句话收束:好方案的标准不是"塞得下",而是模型任何一轮都能低成本拿到"刚好够用"的信息,并且知道更多信息去哪里拿。
面试官追问3个问题
追问一:截断把模型最需要的那部分截掉了怎么办?
- 考察点:是否理解截断是有损操作,需要设计补偿机制。
- 回答方向:第一,截断必须显式标记并给出取回路径;第二,按内容类型选策略——日志保留尾部错误、列表保留前 N 条加总数、JSON 做结构化提取而不是硬切;第三,更根本的解法是先返回索引层(标题、行号、摘要),让模型自己定位再取细节,把"猜哪段重要"变成模型的主动选择。
追问二:模型为了细节反复二次拉取,轮次变多不是更贵更慢吗?
- 考察点:是否有成本权衡意识,而不是背方案。
- 回答方向:承认 trade-off 存在。权衡点是二次拉取增加的是便宜的输入 token 和一轮往返,而全量塞入每轮都重复付费、还放大延迟;通常首层信息密度设计得好,一到两次拉取就够。可以通过 trace 统计平均拉取次数来调页大小:拉取次数高说明首页信息不够,死循环说明 cursor 或标记设计有问题。
追问三:大结果落盘后,怎么保证模型后续引用是准确的?
- 考察点:是否考虑可验证性和状态一致性。
- 回答方向:落盘时生成稳定 id 和内容摘要;读取工具按 id 加行号或 section 精确取;文件被修改后让读取工具返回变化提示或版本号,避免模型基于过期内容继续推理;重要结论引用时要求带上证据 id,方便事后审计回放。
扩展知识
主流 Agent 的实际做法
- 编程类 Agent 普遍用带行号的内容读取 + offset/limit 分段读大文件,搜索只返回匹配行片段而不是全文。
- 搜索和浏览工具普遍采用"索引层 + 详情层"两级结构:先返回标题、链接、snippet,模型选中后再抓正文。
- 多数框架(LangGraph、OpenAI Agents SDK 等)不强制工具结果大小,需要自己在工具实现或中间层做预算控制,这是很多人落地时漏掉的一环。
- MCP 生态里工具结果有内容长度上限(约 25K token),超限会被服务端拒绝,所以 MCP 工具设计必须内置分页或摘要。
和上下文压缩的分工
- 工具结果治理:发生在结果进入上下文之前,控制单条 observation 的形态:摘要、分页、落盘、句柄。
- 上下文压缩:发生在结果进入上下文之后,处理多轮积累:历史摘要、滚动压缩、旧结果蒸馏。
- 长期记忆:处理跨会话复用:把工具产出的高价值结论写入外部 memory,按需召回。
- prompt caching:只降低重复前缀的成本延迟,不减少窗口占用,不解决噪声和位置偏置。
四者互补:治理入口、压缩存量、记忆沉淀、缓存降本,缺一层都会在长任务里暴露问题。
设计原则的一句话版本
把工具想成一个 API 设计问题,而不是一个函数调用:默认给"列表页",别给"全量导出";给游标,给摘要,给错误语义;让模型像一个精明的调用方一样按需翻页。