侧边栏壁纸
博主头像
一笑痕

人生若只如初见,
是可喜亦或者是可悲?

  • 累计撰写 276 篇文章
  • 累计收到 7 条评论

工具结果里的 then 会接管 Promise 返回值

2026-11-14 / 0 评论 / 9 阅读

工具结果里的 then 会接管 Promise 返回值

工具把一份看起来正常的对象交给异步执行器,日志里却只剩下一个字符串。对象中有个字段叫 then,而且它是函数。JavaScript 的 Promise 会把这个对象当成 thenable,执行它的 then 方法,最后采用该方法交给 resolve 的值。对 Agent 来说,工具结果的形状可能在进入上下文之前就变了。

本机用 Node 写了一个固定桩,没有启动模型,也没有接外部工具。两个异步函数分别返回普通对象和带可调用 then 的对象,外层再用 await 接收:

ordinary then field: {"value":"report","then":"metadata"}
callable then field: "replacement"
ordinary has value: true
assimilated is string: true

脚本是 tools/verify-agent-thenable-result.mjs。第二个对象原本也有 value: "report",结果被 thenable 主动给出的 "replacement" 替代。这里观察的是 Node 中的原生 Promise 处理,不是某个模型 API 的特殊约定。

字段名本身不构成问题

普通 JSON 数据可以带 then 这个键。只要它的值不是函数,Promise 不会把它当作 thenable 方法调用。测试里的 then: "metadata" 仍作为普通字段保留下来;危险边界在于属性可调用。

这个差别容易漏掉,因为开发者看到的是一份对象,而 Promise 解析过程会额外检查它的 then 属性。工具可以返回自定义对象、代理对象,或由本地扩展代码构造的数据。若执行器统一用 return await tool(args)、Promise.resolve(tool(args)) 之类的方式接收,thenable 就会参与 Promise 的采用流程。即使工具作者没有打算实现异步接口,恰好同名的函数属性也足以触发它。

消失发生在结果进入历史之前

Agent 通常会把工具返回值标准化,再转换成对话消息。若标准化之前就发生 thenable 采用,后续拿到的可能已经是替代值,而非工具最初返回的对象。下游的类型检查或 JSON 序列化只能看到已经变化的结果,无法还原旧字段。

这和序列化时丢类型不一样。序列化问题发生在对象被转换成文本时;这里的变化发生得更早,甚至还没开始序列化。只在消息构造层检查字段,可能已经来不及。调试日志若在 await 后记录,也会只留下替代值,让人误以为工具本来就返回了字符串。

接口边界要收窄

如果工具约定返回 JSON 值,执行器应在接收边界拒绝函数、代理等非 JSON 数据,并尽早把有效结果转换成已验证的普通数据结构。仅靠浅拷贝并非通用解法:读取 then 属性本身可能触发 getter,代理也能自定义属性读取。对可疑对象,安全做法是把输入范围限定为信任的 JSON 文本,解析后再校验结构,而不是接收任意 JavaScript 对象。

另一种方案是让工具调用通过明确的消息通道传值,不把返回对象直接作为 Promise 的完成值。不过这会改变执行器接口,需要确认错误传播和取消逻辑,不适合只为避开一个属性就仓促改造。数据契约越窄,越容易审计。

这段本机测试证明了可调用 then 会影响 Promise 接收结果,没有覆盖具体 Agent 框架的适配器,也没有测试代理或 getter。工具结果若只来自标准 JSON 解析,then 会是数据字段而非函数;若允许插件直接返回任意对象,就应把边界检查放在 Promise 交付之后难以补救之前。我会把工具输出限制为经过校验的 JSON 形状,不接受任意对象混进执行器。

    🤞 分享