AI Agent 上下文窗口:精简臃肿的 API 响应

API 冗余响应会挤爆 AI Agent 上下文窗口,导致运行崩溃。本指南提供三大优化策略:利用字段选择过滤冗余、严格限制列表长度,以及在工具层裁剪第三方数据,帮你的 Agent 瘦身,显著降本增效!

用 Apifox,节省研发团队的每一分钟

AI Agent 上下文窗口:精简臃肿的 API 响应

免费使用 Apifox

相关推荐

最新文章

API

一体化协作平台

API 设计

API 文档

API 调试

自动化测试

API Mock

API Hub

立即体验 Apifox
目录

Agent 请求获取一条客户记录。你的 API 返回了该客户,以及他们最近的 200 笔订单、这些订单中的每一项商品、三种格式的时间戳,还有针对每一项的 _links 块。4 万个 token 塞满了上下文窗口。而 Agent 其实只需要邮箱地址。

在一次运行中重复此操作四次,Agent 就会把大部分预算花在读取它并没有请求的 JSON 数据上。随后,各种离奇的失败就会接踵而至:它会忘记最初的指令,开始概括任务而不是去完成它,单次运行的成本不断攀升,而质量却持续下降。

这是一个 API 层面的设计问题,而不是 prompt(提示词)的问题。Agent 是通过固定窗口来消费响应的,你返回的每个字段都会与指令、对话和规划争夺空间。本指南将涵盖数据臃肿的来源、解决该问题的字段选择和分页模式、在无法控制 API 时如何在工具层进行精简,以及如何衡量精简前后的差异。我们关于“AI agent 为什么会在生产环境中崩溃”的核心文章将上下文耗尽列为了核心失效模式之一,而本文则是针对该问题的实操部分。

Apifox 可以帮助进行测量:在 Agent 调用之前,你就可以看到每个接口的真实响应大小,并在 API 团队交付之前 mock 出你期望的精简后的数据结构。

AI Coding 交流群

如果你也在用 AI 写代码,或者正在研究 Cursor、Claude Code 这些工具,欢迎加入以下交流群。群里平时会聊一些 AI 编程的实际用法、开发工作流,还有各种新工具和新玩法。

Token 都消耗在哪里了

为浏览器和仪表板(dashboard)设计的响应往往携带了大量的冗余信息,这会消耗 Agent 的实际资金成本。

  • 冗长的信封(envelopes)。包裹在仅有五个字段的对象外的 datametalinksincluded 包装器会使有效载荷(payload)翻倍。超媒体链接(Hypermedia links)对于会追踪它们的客户端很有用,但 Agent 几乎从不这样做,而每个 URL 都会消耗 token。
  • 重复的键。JSON 会在每个数组元素中重复每个字段名。一个包含 15 个字段、共 200 项的列表需要为 3,000 个键字符串买单。这就是为什么列表接口在上下文使用量中占主导地位的原因。
  • 默认嵌套展开。在 Agent 访问之前,内联相关资源的接口确实很方便。但一个客户加上他们的订单和商品就构成了一棵树,而树的增长速度是极快的。
  • 冗余格式。同一个对象上同时存在 created_atcreated_at_unixcreated_at_human,意味着要为一个值支付三倍的成本。
  • 空值和未设置的值。许多序列化器即使在字段未设置时也会输出它们。每条记录 20 个 null 是纯粹的浪费。

一个直观的理解方式是:token 成本取决于序列化文本的大小,而不是记录的数量。200 条各含 5 个字段的记录可能比一个深度嵌套的对象更便宜。

原则一:返回字段,而非资源

最具价值的改变就是让调用者自己请求所需要的内容。

GET /v1/customers/8812?fields=id,email,plan,status
{ "id": "8812", "email": "dana@example.com", "plan": "pro", "status": "active" }

对于大多数 API 而言,这比返回完整记录减少了 90% 的数据量,且只需一下午的时间即可完成添加。如果你想要一个有先例可循的版本,Google 的 API设计指南中记录了 field-mask 模式,而 GraphQL 则通过强制进行字段选择来解决同一个问题。

两个实现注意事项:首先,根据数据模型校验字段列表并拒绝未知名称,这样幻觉产生的字段就会触发明确的错误,而不是无声无息地截断对象。其次,对于没有传递任何内容的调用者,只保留一个较小的默认字段集,而不是默认返回所有字段。

然后,在工具描述中向模型暴露该 parameter,并明确写出可选的字段:

{
  "name": "getCustomer",
  "description": "Fetch a customer by ID. Always pass `fields` with only what you need. Available: id, email, name, plan, status, created_at, billing_address, order_count.",
  "input_schema": {
    "type": "object",
    "required": ["customerId", "fields"],
    "properties": {
      "customerId": { "type": "string" },
      "fields": {
        "type": "array",
        "items": { "type": "string" },
        "description": "Field names to return. Keep this list minimal."
      }
    }
  }
}

描述是模型学习这些规则的唯一地方,无论是 OpenAI 函数调用指南 还是 Anthropic 工具使用文档 都对此给予了同样的重视。将 fields 设为必填(required)是关键所在。可选的 parameter 往往会被跳过,而必填的 parameter 则会强迫模型去思考它实际上需要哪些数据。

规则二:始终限制列表长度

未作限制的列表接口是导致数据膨胀的第二大原因。智能体(Agent)请求“最近的订单”,结果却获取到了自 2019 年以来的所有数据。

在服务端设置一个硬性的最大值限制,而不仅仅是默认值。如果智能体发送了 limit=5000,则返回 100 条数据并告知对方。我们关于 REST API 分页和为数百万条记录设计分页的指南中介绍了其实现机制;而针对智能体的特定规则要更为聚焦:

  • 限制每页数据量在模型可读取的范围内,对于典型记录,通常在 20 到 50 条之间。
  • 返回总条数(total count),以便智能体无需通过逐页拉取就能知道是否已获取全部数据。
  • 使用游标分页(cursor pagination)。在运行过程中数据发生变化时,偏移量(offset)会发生漂移,翻页较慢的智能体很容易遇到这个问题。
  • 在响应中包含明确的声明,例如 "truncated": true,以便模型知道还有更多数据。仅凭数组长度,模型很难准确判断数据是否完整。

另外,提供一种让智能体完全无需分页的方法。一个 count 接口、带窄窗口的过滤搜索,或者一个摘要对象,通常可以在不返回任何记录的情况下回答问题。最省成本的响应就是不包含数据的响应。

规则三:当 API 不归你所有时,在工具层进行裁剪

第三方 API 不会因为你的要求而添加字段选择功能。因此,应将数据裁剪放在你的执行器中处理,介于 HTTP 响应和模型之间。

KEEP = {
    "getCustomer": ["id", "email", "plan", "status"],
    "listOrders": ["id", "total", "status", "created_at"],
}

def project(tool_name, payload):
    keep = KEEP.get(tool_name)
    if keep is None:
        return payload
    if isinstance(payload, list):
        return [{k: item.get(k) for k in keep if k in item} for item in payload]
    return {k: payload.get(k) for k in keep if k in payload}

在实际应用中,可以通过以下三点改进来使该方案更加稳健:

存储完整响应,仅将投影数据传给模型。 在运行日志中保留未裁剪的 payload,以便仍能进行调试。我们关于追踪智能体工具调用的文章中介绍了需要记录的内容。

告知模型你移除了哪些内容。 诸如 "_omitted": ["billing_address", "notes", "metadata"] 这样的一行信息,可以让模型在确实需要完整记录时主动请求,而不是直接断定该数据不存在。

将列表转换为紧凑格式。 对于表格数据,CSV 或 markdown 表格所消耗的 token 远少于 JSON,因为字段名只出现一次,而不是每一行都重复出现。模型对这两者都能很好地理解。

id,total,status,created_at
ord_91,4900,paid,2026-08-21
ord_92,1200,refunded,2026-08-22

规则四:针对重度场景在服务端进行总结

有些问题根本不需要具体记录。例如,“该客户本月是否有支付失败的记录?”只是一个布尔值。返回 40 个支付对象来让模型自行计算,是一种成本极高的解答方式。

针对经常重复出现的问题,可以直接添加一个用于回答该问题的接口。比如账户健康度摘要、状态汇总或小型聚合数据。这看起来就像普通的 API 设计工作,事实也确实如此,而且这是上述所有方法中最具价值的一种:你无需去裁剪庞大的响应,而是直接避免了它的产生。

这里有两个保障措施:保持总结的数据结构稳定,以便智能体能够依赖它们;对其进行版本控制,因为智能体的提示词是基于特定的数据结构编写的,任何无声的变更都会破坏它。我们关于“当底层 API 发生变更时智能体会发生什么”的文章讨论了这一风险,而“最佳 API 版本控制策略”则介绍了具体机制。

测量前后对比

盲目地做这些工作毫无意义。以下三个指标可以帮你指出问题所在:

每个接口、每次响应的字节数。 向智能体可以调用的每个工具发送一次真实的请求,并记录其 payload 大小。任何超过几 KB 的响应都是需要优化的备选对象。在 Apifox 中,你可以运行每个接口一次,直接从响应中读取大小,然后保存该请求,以便在 API 发生变更时重复进行检测。

每次工具调用的 Token 数。字节数只是近似指标,Token 才是实际账单。将 Payload 传入服务商的分词器(例如针对 OpenAI 模型的 tiktoken),并对接口进行排序。排序结果通常会严重倾斜,往往一两个接口就贡献了大部分的成本。

单次运行消耗的上下文。记录整个 Agent 任务运行过程中的累计 Token 总数。如果一个任务在接近上限时结束,对数据进行裁剪带来的不仅是成本的降低,更是能让运行顺利完成。

然后设计你需要的响应结构,并在 API 团队开发之前对其进行 mock。通过一个返回裁剪后响应的 mock 服务端,你可以评估优化效果,并验证 Agent 在数据减少的情况下是否仍能成功运行——这才是最核心的问题。我们关于针对 mock(而非生产环境)运行 Agent 的文章详细介绍了该工作流。

优秀的设计范式

一个对 Agent 友好的响应应该是体积小、结构扁平,并且清晰地说明省略了哪些内容:

{
  "customer": { "id": "8812", "email": "dana@example.com", "plan": "pro" },
  "recent_orders": [
    { "id": "ord_91", "total_cents": 4900, "status": "paid" },
    { "id": "ord_92", "total_cents": 1200, "status": "refunded" }
  ],
  "recent_orders_total": 47,
  "truncated": true,
  "_omitted": ["billing_address", "metadata", "order_line_items"]
}

体积低于 200 个 Token。它回答了常见问题,指明共有 47 个订单(而不是让人误以为只有两个),并告诉模型接下来可以请求获取什么。

首先从开销最大的接口入手。对其进行测量,添加字段选择,限制列表数量,然后再次运行 Agent。这两次测量数值之间的差距通常足够大,足以证明后续优化的价值。如果你希望在同一个项目中进行测量和 mock,可以 下载 Apifox

这种模式适用的三个场景

工单分类与分流(Support triage)。 Agent 读取工单,获取客户信息,并决定是否进行升级处理。最原始的方案会获取完整的客户 object 以及最近的 50 个工单,在读到真正的投诉内容之前就白白烧掉了 30,000 个 Token。而优化后的方案则是调用一个摘要接口,仅返回订阅计划、状态、未结工单数和最后联系日期。这样大约只需 80 个 Token,而且由于关键事实没有被海量数据淹没,升级决策的效果反而更好。

内部运维 Agent。 一个部署 Agent 需要检查 40 个服务的运行状况。完整的状态 object 在检查到第 12 个服务时就会撑爆上下文窗口。而采用汇总方式,每个服务仅返回一行数据(名称 + 状态 + 错误率),这样就能把所有 40 个服务的信息压缩在几百个 Token 内,让 Agent 能够对全局进行推理,而不会遗忘前半部分的内容。

数据录入与对账。 Agent 将发票与付款进行匹配。如果返回完整的发票文档,在处理几十条记录后就会失败。而如果以 CSV 格式返回 idamount_centsdatereference,则可以一次性处理数百条记录,因为对账比对实际上只需要这四个字段。

这三个场景的共同规律是:Agent 真正需要的是一个“决策面”(decision surface),而 API 却给了它一整篇文档。

你需要运行请求历史来发现模式

单次运行只能告诉你响应很大。而具体的模式——哪个接口超出了预算以及发生频率如何——只有在多次运行中才会显现。

这意味着这些数据必须在会话结束后保留下来。对于你部署的服务,这就是你自己的遥测数据。对于执行分配任务的编码 Agent 而言,则是运行它们的平台:HiFox 会将每次运行的执行追踪和结果保留在其所属的 Task 上,因此多次运行之间的对比只需读取任务历史,而无需重建终端会话。无论哪种方式,在没有请求历史的情况下强制执行预算只能告诉你某些内容过大,但无法告诉你应该优先修复什么。

针对每个工具设置预算,而不仅仅是每次运行

大多数团队只会限制总上下文长度,然后就到此为止了。而针对每个工具设置预算会更有用,因为它将一个模糊的问题转变成了一个具体的问题。

为每个工具设定一个上限,例如 1,500 个 Token。当响应超出该限制时,执行器会将其裁剪至投影,附加省略字段标记,并记录溢出日志。现在你就有了一份经常超出预算的接口列表,并按照 Agent 调用它们的频率进行排序,这便是你的工作队列。

预算还能保护你免受那些“测试时很小,但对某个真实客户却大得惊人”的接口的影响。数据分布是有长尾的,那个拥有 4,000 个订单的账户就是会在凌晨 2 点导致运行崩溃的元凶。硬性上限能将这种情况转化为经过裁剪的响应,而不是失败的任务。

常见问题解答

如果 Agent 需要缺失的数据,截断响应是否有风险? 只有在隐藏截断行为时才会有风险。应包含一个明确的标记和省略字段的列表,以便模型可以请求它们。导致错误答案的是“静默截断”,而不是裁剪本身。

我是否应该改用 GraphQL 来服务 Agent? GraphQL 强制要求进行字段选择,这干净利落地解决了这个问题,但它把复杂度转移到了查询构建上,而且模型编写无效查询的频率往往高于它们误用字段列表的频率。在 REST 接口中添加 fields 参数通常是改动较小的方案。

工具响应应该多小? 目标是单条记录读取在 1,000 个 Token 以下,列表读取在 2,000 个 Token 以下。超过这个范围,就要问问 Agent 需要的是具体记录还是一个答案。

Prompt 缓存能解决这个问题吗? 它降低了重复上下文的成本,但并没有减少其占用的空间。一个缓存的 40,000 Token 响应仍然会填满窗口,因此缓存虽然能省钱,但可靠性问题依然存在。

二进制和文件响应怎么处理? 绝对不要将它们放入上下文中。存储文件,向 Agent 提供引用和简短描述,并给它一个单独的工具来仅提取它需要的内容。

裁剪应该放在哪里:API 还是工具包装器中? 如果 API 是你自己的,就放在 API 中,因为这样每个调用者都能受益,且数据字节根本不需要通过网络传输。如果不是你自己的,就放在包装器中。两者都做也是可以的。

开发必备:API 全流程管理神器 Apifox

介绍完上文的内容,我想额外介绍一个对开发者同样重要的效率工具 —— Apifox。作为一个集 API 文档、调试、设计、测试、Mock、自动化测试于一体的工具,Apifox 是目前提升研发效率的首选。

如果你正在开发项目,不妨试试其极其友好的界面设计,它完全兼容 Postman 和 Swagger 数据格式,导入数据非常方便,,即使是新手也能很快上手,点击这里即可注册使用

Apifox

值得一提的是,除了个人和常规团队使用,针对有高安全合规要求、或需要在内网环境协作的企业,Apifox 还提供了深度定制的私有化部署方案

获取专属报价与部署方案

icon 详细的私有化部署系统架构与安全白皮书
icon 针对您公司规模的专属报价单
icon 免费的 1v1 专属产品演示 (Demo) 机会
获取部署方案
* 提交后,我们的客户经理将在 1 个工作日内与您联系
林俊锋 企业微信
@Apifox 专属顾问
扫码备注: 私有化 + 公司名