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

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

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

工具结果拼进上下文,类型信息先消失

2026-10-25 / 0 评论 / 3 阅读

工具结果拼进上下文,类型信息先消失

工具明明查到了值,拼进下一段上下文后却只剩 [object Object]。看起来像是内容丢了,实际丢在类型转换那一步:JavaScript 把普通对象转成字符串时没有替它生成 JSON。把字符串、普通对象或内容块数组接到同一段文本后面,保留下来的信息并不相同。

固定值搭了一个纯 Node 小实验,只测本地序列化和拼接,不调用模型,也没有接入 Agent 框架。输出如下,脚本在 tools/verify-agent-tool-result-shape.mjs:

STRING CONCAT: 工具返回:余额:12
OBJECT CONCAT: 工具返回:[object Object]
CONTENT CONCAT: 工具返回:[object Object],[object Object]
OBJECT JSON: {"ok":true,"count":12}
CONTENT JSON: [{"type":"text","text":"已查询余额"},{"type":"image","mimeType":"image/png","data":"AAECAw=="}]
UNDEFINED OBJECT FIELD: {"text":"已查到"}
UNDEFINED ARRAY SLOT: ["已查到",null]
NONFINITE NUMBER: {"count":null}
CLIPPED JSON: {"text":"准备执行","a
CLIPPED JSON PARSE: SyntaxError
TEXT-ONLY FLATTEN: 已查询余额
CONTENT PART COUNT: 2
ASSERTIONS: all passed

字符串碰巧适合直接展示。对象则需要明确序列化;结构化内容即使被 JSON 完整保存,也仍然是数组,每个元素的 type、文本或媒体字段各有含义。把它们和一句提示语用 + 连起来,数组会先转字符串,对象元素又各自退化成 [object Object]。这不是「上下文压缩」的神秘现象,是普通的 JavaScript 类型转换。

一句文本和一份记录不是同一种结果

工具接口要把返回类型写清楚。字符串可以作为文本处理。对象要保留字段边界,内容块数组要保留顺序和块类型。把它们都塞进一个泛化的 result 字段,再让后续代码猜它是什么,问题迟早出在拼接、日志或持久化的边界上。

JSON.stringify 能把普通对象编码成文本,但它不是无损容器。脚本里的 undefined 对象字段在序列化后消失,数组里的 undefined 槽位变成 null,Infinity 也变成 null。如果这些值有业务意义,序列化前就要决定用什么显式表示,不能指望恢复时猜回来。对于需要跨进程传递的结果,限制可接受的数据类型并在边界检查,比把任意运行时对象交给 JSON 稳妥。

内容块还有另一类损失:只选 type === 'text' 的元素再 join(''),脚本得到 已查询余额,原来两个块中的 image 信息就不在结果里了。只提取文本可能是合理的展示策略,但它不是完整保存。工具结果契约应该说明哪些内容是可选的、未知类型怎么处理,以及文本摘要是否只是原结果的一个视图。

截断发生在编码之后

上下文预算不足时,系统可能需要压缩或截断内容。这里能直接验证的只是字符串操作:对 JSON 文本调用 slice(0, 17) 会留下半截对象,JSON.parse 随即抛出 SyntaxError。脚本输出显示截断结果是 {"text":"准备执行","a,它不是一个可继续解析的记录。

如果先把结构化数据转成一句摘要再裁字数,字段名、调用标识或不同内容块之间的界线也可能被删掉。若只从头部保留文本,末尾的错误原因可能消失;若只保留尾部,起始指令或标识可能不见。具体会丢什么,取决于实际采用的压缩规则,不能从 slice 的例子推出某个 Agent 一定怎样做。可在本机复现的结论止于此:字符串截断不理解 JSON 语法,也不理解记录的业务重要性。

更可控的做法是把「结果数据」和「给上下文看的摘要」分开。原始结果按约定保留字段或内容块,摘要则明确标注为摘要;超限时按字段优先级裁剪,而不是从整段 JSON 任意截字符。需要保留完整对象时,先解析后按结构截取,再重新序列化,并对输出做解析检查。若结果包含大段媒体数据,摘要可以提及媒体类型和引用标识,不必把二进制内容硬转成一段长文本。

模型对工具结果的读取方式和上下文压缩后的信息保留情况没有实测。现有日志只证明 Node 对固定 JavaScript 值的拼接、JSON 序列化与截断结果,不证明模型行为或某个 SDK 的结果格式。接入时应先看实际接口的返回类型,再检查框架在哪一步做转换。

我的取舍是让每种工具结果都有明确形状:面向文本的结果就返回字符串;需要字段的结果保留对象;多模态或分块内容保留带类型的数组。要给上下文一个便于阅读的版本,就另做摘要,不把摘要冒充原结果。只有边界清楚,后续的拼接和压缩才知道哪些信息可以丢,哪些必须留下。

    🤞 分享