资料助手刚查到一份报告,用户只让它打开详情。列表里返回的报告ID是 9007199254740993,详情工具收到的却成了 9007199254740992。模型甚至还没来得及“抄错”,中间的一次普通JSON解析就可能改掉最后一位。
这是本文的虚构教学情节,不是本站或某个产品的真实事故。它要解释的问题是:工具参数明明还是整数,JSON也能解析,为什么Agent却可能指向另一个对象?
AI原创概念插画:雨窗前,蓝绒托盘上的两把象牙色钥匙有不同的齿端,后方磨砂圆屏模糊了细节,右侧玻璃罩保留清楚的钥匙轮廓。它比喻转换过程中的身份细节损失,不是数据传输实验或真实产品照片。
一、先分清:数字的写法、内存里的值、业务上的身份
JSON里的数字是一段十进制文本。解析器必须把它变成某种程序内部的数值,而不同语言、库和配置,不一定采用同一种表示。
RFC 8259第6节允许实现对数值范围与精度设置限制,并指出:使用常见的IEEE 754 binary64表示时,处于 [-(2**53)+1, (2**53)-1] 范围内的整数具有精确数值互操作性。[1] 这不是“JSON语法禁止更大的数”,而是跨实现交换时应该留意的精度边界。
JavaScript的普通 Number 采用这种双精度浮点表示。它能表示很大的数,却不能在足够大的范围内继续逐个区分相邻整数。能存下一个大致数值,和能保住ID的每一位,是两回事。
报告ID本来就不是“有多少份报告”。我们通常不会把两个报告ID相加,再用结果打开第三份报告。它的业务作用是精确指认对象,少一个前导零、改掉一个末位,都可能变成别的名字。
因此应分别核验:
| 层次 | 真正检查什么 | 没有证明什么 |
|---|---|---|
| JSON语法 | 这段文本是不是合法JSON | 每个解析器是否保留原数值 |
| 字段类型 | 解析后的值是不是符合契约的整数 | 它是不是原来那个ID |
| 数值表示 | 这条链能否准确表达目标整数范围 | 这个ID是否存在、可访问 |
| 业务身份 | 当前请求是否仍指向被选中的对象 | 用户是否授权后续动作 |
JSON Schema官方数值类型文档把 integer 定义为整数值类型,并提供 minimum、maximum 等范围约束;字符串不因“看上去都是数字”就成为整数。[3] 但类型定义不是具体运行时的任意精度实现承诺。不要从抽象Schema能描述一个整数,推导出某个SDK已经无损接收了它。
二、一个实际执行的Node.js小实验
下面的代码可以保存成 .js 文件,用Node.js运行。输入是人工指定的JSON,不是模型输出,也不是伪造的API响应。本文在Node.js v26.7.0上实际执行了这段代码;它只验证本地解析、类型判断和重新序列化。
const raw = '{"report_id":9007199254740993}';
const parsed = JSON.parse(raw);
const saved = JSON.stringify(parsed);
const strings = JSON.parse('{"report_id":"9007199254740993"}');
console.log('parsed:', parsed.report_id);
console.log('integer:', Number.isInteger(parsed.report_id));
console.log('safe:', Number.isSafeInteger(parsed.report_id));
console.log('roundtrip:', saved);
console.log('late string:', String(parsed.report_id));
console.log('string first:', strings.report_id);实际输出为:
parsed: 9007199254740992
integer: true
safe: false
roundtrip: {"report_id":9007199254740992}
late string: 9007199254740992
string first: 9007199254740993最容易忽略的是第二行:值已经变了,Number.isInteger仍然返回真。 它回答的是“当前这个值是不是整数”,没有回查原始JSON里的十进制字符。
Number.isSafeInteger在这里可以拒绝这条不安全的数值输入;它也不是修复工具,不能把丢失的那一位找回来。安全整数范围之外并非每个整数都无法精确表示:实验还验证了 9007199254740992本身可以表示,但它不属于安全整数范围,附近另一个整数会与它发生碰撞。不能把“这次打印没变”当作全范围安全。
完整断言另外覆盖了安全边界内的整数、两个不同原始数字解析后相等、字符串ID保留前导零,以及解析后再转 BigInt仍保留错误值。它们均通过,但没有运行Agent、数据库、远端工具或模型,也没有测量线上误选率。
三、为什么“最后再转字符串”已经晚了
MDN的 JSON.parse()文档明确提醒:普通数值在reviver处理前就已经被解析,可能已经损失精度;一种无损传输方法是先序列化为字符串,再按需要恢复为任意精度类型。[2]
这意味着下面两条路线不是一回事:
- 原始ID → JSON字符串字段 → 字符串解析 → 原样传入下一工具:只要中途没有额外改写,可以保住这些字符。
- 原始ID → JSON数字字段 → 有损数值解析 → 再调用
String():得到的是已经变化的值的文本。
第二条路线像是先把钥匙齿磨平,再给它装进更结实的盒子。盒子能保护现状,却不会长回丢掉的那一齿。
BigInt也不能凭空恢复已经损失的位数;直接把已解析的 Number转成 BigInt,只是精确保存那个错误整数。并且JavaScript默认 JSON.stringify不能直接序列化 BigInt,完整实验对这一点也做了异常断言。若应用要使用它,需要明确序列化协议,而不是悄悄换类型。
MDN当前页面还介绍了通过reviver的 context.source取得原始数值文本的方式。这可用于支持该能力的运行时,但必须核对部署兼容性、字段选择与下一跳编码;本文没有执行这条恢复路线,也不把它当作所有客户端都具备的救援机制。 它更无法挽回在更早一跳就被改写的原始内容。
四、把ID保真放在Harness边界,而不是靠模型小心
回到虚构的报告助手。假设你能够设计列表与详情两端的工具契约,一个可采用的方案是:让 report_id从列表输出开始就是字符串,并在详情输入中继续要求字符串。
下面只是固定为“正好16位十进制ID”的教学契约片段,不是通用ID标准,也不是本站接口:
{
"type": "object",
"properties": {
"report_id": {
"type": "string",
"minLength": 16,
"maxLength": 16,
"pattern": "^[0-9]{16}$"
}
},
"required": ["report_id"],
"additionalProperties": false
}真实系统应按真实ID契约定义长度、字符集、大小写与前导零规则。不要把示例正则复制给UUID、带前缀ID或其他标识符;也不要为了通过格式要求,自动补零、删字符或“猜最后一位”。
更重要的是端到端:
- 工具输出端就返回准确的字符串;若上游只能给数字,适配层应在首次有损解析之前取得可保真的表示。
- 中间层不能为了“规范化”把纯数字字符串转成数值。缓存、队列、日志工件、SDK映射和恢复文件都算中间层。
- 工具输入端按当前契约拒绝不符合要求的类型,不把数字默默转成字符串以掩盖来源问题。
- 执行前把目标与可信候选或已确认任务绑定。字段长得合法不等于对象存在,更不等于当前调用者有权访问。
- 执行后回读实际对象与结果,不能只凭调用成功就宣称打开了用户选定的那份报告。
这是一份应用层工程建议,不是RFC或某个Agent框架规定的唯一实现。严格结构化生成可以约束输出形状,但它不能让已经丢位的工具结果重新正确;模型若从错误列表里原样复制,也可能稳定地复制错误对象。
如果工具契约确实要求数值,不应擅自改成字符串发给它。可选择在完整业务允许范围内限制为安全整数,或使用双方明确支持的无损解析与编码链;后一种方案必须验证每一跳。不能证明原始值的情况下,应停止该动作、重新取得可信ID,而不是“先试试看能不能查到”。
五、一份能复用的验收清单
上线或升级工具适配层时,可以从这些条件开始:
- 区分计数、金额、测量值和对象ID,不因字符全是数字就统一使用浮点数。
- 列出经过的语言、解析器与SDK配置,确认是否存在转换为JavaScript
Number或其他有限精度类型的一跳。 - 把真实协议允许范围内的边界值与相邻值放入往返测试,断言对象标识一致,不只断言“可以解析”。
- 字符串ID经过完整链路仍逐字符一致;前导零、大小写等是否有意义,以字段契约为准,不做通用数字清洗。
- 对要求安全整数的输入,在执行前明确拒绝不安全值;拒绝不等于修复,也不能据此证明上游从未失真。
- 测试恢复与缓存:暂停前保存的标识,恢复后不能经过另一次隐式数字转换。
- 如果既有数据可能已丢位,回查可信原件并做对账;不要对近似值批量补引号后宣布修复完成。
- 身份核验、对象授权、用户确认与副作用验收仍各自成立,格式正确不增加权限。
这些是建议测试,不表示本文已经搭建并验收了完整工具链。本文实际执行的范围仅为前述Node.js确定性解析实验与断言。
Agent工程里有一类错误,不来自模型“不够聪明”,而来自模型收到的世界已经被悄悄改写。在要求AI原样使用一个ID之前,先保证你的程序真的把那个ID原样交给了它。
来源、日期与实验范围
本文由AI创作。报告助手、ID语境与契约片段均为教学设计;本地实际执行使用人工JSON夹具,不涉及真实报告、账号操作或模型推理。配图为AI原创概念插画。
检索截止:2026年10月24日14:50 UTC。基础标准与动态参考的适用范围分别如下:
- RFC 8259:JSON Data Interchange Format,第6节Numbers,2017年12月发布。实际补读公开纯文本第6节的表示限制与精确整数互操作区间;引用的是格式与互操作指导,不是任何语言实现的完整能力表。
- MDN:JSON.parse(),本轮直接页面标注最后修改于2026-10-18,动态文档、未锁定版本。实际阅读reviver、精度损失与原始source相关正文;新增能力须另查目标运行时兼容性,本文未验证全部浏览器。
- JSON Schema:Numeric types,动态说明页,未核实独立发布日期。实际阅读页面提取中的integer、number与Range相关正文;直接HTTP补读返回403,未将其算作阅读全文。该页用于解释类型和范围关键字,不作为具体校验器无损实现或模型效果的证明。

留下你的想法 0
欢迎提问、补充和不同观点。评论通过审核后会公开显示。