loading

Loading

探索与发现

首页 Agent 工程

Agent 工具返回值的分页游标怎么设计才不坑

分类:Agent 工程
字数: (2172)
阅读: (2)
0
摘要:Agent 调分页取数据的坑,多半不在接口本身,而在游标语义、字段互斥和上下文压缩三处。

工具调用里最容易被低估的一类接口,是返回结果带分页的工具。单个接口里加个 cursor 字段看起来简单,但一旦把它交给 Agent 自主循环调用,分页游标就从「协议细节」变成了「状态机设计」问题。我最近在做代码检索和工单查询这块时,踩了几个反复出现的坑。

先分清游标是什么语义

Cursor 官方 changelog 在 1.4 版本里提到,List 工具「现在可以在一次调用中遍历完整目录树,并返回诸如文件数量和类型等元数据」。这类改动的本质是把「多次翻页」合并成「一次拿全」,因为对于目录树这种小结果集,翻页反而是负担。但把同样的逻辑搬到提交历史、日志检索、工单列表这种可能返回几万条的结果上,就变成了新的问题。

所以在写工具之前,必须先定死游标的语义。常见的有三种:偏移量、快照游标、游标 + 完成标志。偏移量在静态数据上用起来最省事,但在数据会变化的列表里必然重复或漏项。快照游标(服务端记下查询时刻的锚点)稳定得多,代价是服务端要持有会话状态。第三种,也就是游标和 has_more 一起返回、游标为空表示到底,是最适合 Agent 的形式,因为它只要求模型记住一个字符串,不需要数学计算。

我倾向于把工具输出固定成这样的形状:

{
  "items": [...],
  "next_cursor": "eyJ0cyI6MTc...",
  "has_more": true,
  "total_hint": 1280,
  "truncated_fields": ["body"]
}

关键字段是 next_cursor 和 has_more 必须成对出现,total_hint 只是给模型一个量级感,不能当可靠计数用。truncated_fields 是我额外加的:当单条记录体积太大、只返回摘要时,必须告诉调用方被截断的是哪个字段,否则模型会以为拿到的正文就是全部。

两参数互斥是常见错误

看公开资料里 Cursor 的 fetch_pull_request 工具定义,它要求传 PR 编号、提交哈希或 git 引用,其中特别注明「不允许使用 HEAD」。这是一个有用的先例:当工具接受多种定位方式时,提示词必须明确约束边界,否则模型容易传一个它并不理解的值。

分页游标有同样的问题。很多工具有 page 和 cursor 两个参数,如果同时出现或者自相矛盾,服务端要么报错、要么静默取其中一个、要么算出空结果。Agent 收到空结果不会判断「我参数传错了」,而会认为「没有更多数据」,然后停止探索。这类失败安静得可怕。

教训是:同一个分页功能只允许一种游标体系,另一个参数从 schema 层删掉,比在运行时宽松兼容更加省事。

模型在哪一步丢掉游标

再简单的工具也会被复杂上下文吞掉关键字段。跑多轮 agent 时,我看到过几种典型的游标丢失方式。

第一种是上下文压缩把「游标字符串」当噪声行省掉。Agent 上下文吃紧之后,历史信息会被压缩成摘要,而压缩策略通常按信息密度挑选,一大串 base64 通常躲不过。第二次要翻页时,模型根本拿不出现实存在的游标,只能重新从头查。

第二种是并行调用发起多路分页,回包乱序之后把不同查询的游标复合。这需要做法是让每次工具调用的输出 id 与 cursor 强绑定,或者干脆不允许同一个查询的一次执行分发多个翻页请求。

第三种是子工具包装出一层抽象后丢掉了 next_cursor 字段。常见于 MCP Server 里包了一层通用响应包装,内层工具返回 5 条时就丢掉 has_more,模型就因此只取了第一段。

这些问题的对策中我比较满意的是把游标结构做得足够短、足够可日志化。游标内容用服务端可判定的集合(例如记录 id 或时间戳),与业务空间解耦。服务端记录之后,出错时可以直接核对日志里传进来的游标当时是否在返回序列里。

控制总调用次数与取值顺序

游标能翻多页并不意味着应该无限翻页。按经验地给出上限,并在工具描述里写清楚,比如「本次单轮最多返回 10 条;若 has_more 为 true,请说明是否需要继续,并在描述开头列出总条数提示」。这个约束对模型行为有直接影响:它跟你的工具描述同样重要。

另一个取舍是先聚合还是先看例子。如果列表结果一旦查就要翻 5 页才能看全,可以加一个总是单页返回的参数,让模型自己做选择。Cursor 数据里的做法是对于目录树这种小结果直接拿全,这里没有通用的规则,把 2MB 跟 10 页这类分界经经验做成阈值,是相对容易追溯的取法。

在评估数据上要把游标纳入

市面上讨论工具调用的文章大多在讲 ReAct 框架的结构,以及检索类工具的能力;但关于工具返回边界的工程约定却写不下来。一个 Agent 评测里如果只检查最后一轮答案对错,大量翻页引发的重复、遗漏、过早停止通常都抓不到。

我手动跟踪路径时至少加上三列特征:每次工具调用时返回的 has_more 是否与返回条数一致;next_cursor 是否有任何一次在后续调用中被真正使用;整个任务下来对同一信源实际触达的总条数。这些字段记录下来再放大到整个任务切面,就足够看清模型的翻页行为。结果可能会像输出截断一样——单个 case 看起来都没有问题,放大到整体才知道用不用游标。

转载请注明出处: 皮哈皮阿

本文的链接地址: https://blog.phpat.com/agent-engineering/645.html

手机扫码阅读

微信或手机浏览器扫一扫,随时随地随心阅读与分享

您可能对以下文章感兴趣
1 / 1
预览图