iCloud官方网页版入口 iCloud电脑端网页版官网入口
2026-11-02
2026-11-06 0
先限定模型适配器、版本和要读的字段,再对照构造与调用配置。本次 ChatAnthropic 1.6.0 默认开启且实例值优先,所以单次 false 不能覆盖;应看实际块的 usage_metadata,而非只看参数打印或任意 usage 对象。
本例检查 ChatAnthropic 的 model.stream 输出,环境为 Node.js 22.23.4、@langchain/anthropic 1.6.0、@langchain/core 1.3.4 与 @anthropic-ai/sdk 0.129.0。streamUsage 在这里影响标准化用量元数据的流式呈现,不代表关闭文本生成,也不能根据元数据缺失推断 Token 消耗为0或计费停止。其他提供商的同名字段不能直接套用本次结果。
安装 @langchain/[email protected] 与 @langchain/[email protected],将下面保存为 fixture.mjs。它让真实适配器和SDK连接本地模拟 SSE,每次都提供相同的 ok 文本与模拟用量9入、2出、缓存0;没有调用线上模型,也没有真实计费。collect 合并消息块,统计携带 usage_metadata 的块数,并分别读取标准化结果和原始响应元数据。
import { createServer } from 'node:http';
import { ChatAnthropic } from '@langchain/anthropic';
export async function fixture() {
const requests = [];
const server = createServer(async (req, res) => {
let body = ''; for await (const part of req) body += part;
const parsed = JSON.parse(body);
requests.push({stream:parsed.stream, hasStreamUsage:'streamUsage' in parsed});
res.writeHead(200, {'Content-Type':'text/event-stream'});
const event = data => res.write(`event: ${data.type}\ndata: ${JSON.stringify(data)}\n\n`);
event({type:'message_start',message:{id:'msg_local',type:'message',role:'assistant',
model:'local-model',content:[],stop_reason:null,stop_sequence:null,
usage:{input_tokens:9,output_tokens:0,cache_read_input_tokens:0,cache_creation_input_tokens:0}}});
event({type:'content_block_start',index:0,content_block:{type:'text',text:''}});
event({type:'content_block_delta',index:0,delta:{type:'text_delta',text:'ok'}});
event({type:'content_block_stop',index:0});
event({type:'message_delta',delta:{stop_reason:'end_turn',stop_sequence:null},
usage:{output_tokens:2}});
event({type:'message_stop'}); res.end();
});
await new Promise(resolve => server.listen(0,'127.0.0.1',resolve));
return {requests,
model(fields = {}) { return new ChatAnthropic({model:'local-model',apiKey:'local-test-key',
maxTokens:16,maxRetries:0,
clientOptions:{baseURL:`http://127.0.0.1:${server.address().port}`},...fields}); },
close:() => new Promise(resolve => server.close(resolve))
};
}
export async function collect(model, options = {}) {
let final; const chunks = [];
for await (const chunk of await model.stream('local diagnostic', options)) {
chunks.push(chunk); final = final ? final.concat(chunk) : chunk;
}
return {instance:model.streamUsage, usageChunks:chunks.filter(c=>c.usage_metadata).length,
finalUsage:final?.usage_metadata ?? null, rawUsage:final?.response_metadata?.usage ?? null,
text:final?.content};
}省略构造参数后,实例里的 streamUsage 仍已初始化成 true。将下列保存为 01-matrix.mjs 并执行 node 01-matrix.mjs,以相同文本和SSE事件分别改变构造值、第二调用参数顶层的值,核对完整矩阵。
import {fixture, collect} from './fixture.mjs';
const f = await fixture();
try {
for (const ctor of [undefined,true,false]) {
const model = f.model(ctor === undefined ? {} : {streamUsage:ctor});
for (const call of [undefined,true,false]) {
const result = await collect(model, call === undefined ? {} : {streamUsage:call});
console.log(JSON.stringify({ctor:ctor ?? 'omitted',call:call ?? 'omitted',...result}));
}
}
console.log('requests', JSON.stringify(f.requests));
} finally { await f.close(); }| 构造传值 | 单次传值 | 实际实例值 | 含标准化用量的块数 | 合并正文 |
|---|---|---|---|---|
| 省略 | 省略 | true | 2 | ok |
| 省略 | true | true | 2 | ok |
| 省略 | false | true | 2 | ok |
| true | 省略 | true | 2 | ok |
| true | true | true | 2 | ok |
| true | false | true | 2 | ok |
| false | 省略 | false | 0 | ok |
| false | true | false | 0 | ok |
| false | false | false | 0 | ok |
本次前六组都保留2个用量块,最后三组都为0,正文始终是 ok。反向条件同样重要:构造 false 时,单次 true 仍然无法开启。这说明决定结果的是当前实例优先规则,不能简单解释成“false 在 ?? 下被当成空值”;false 本身不会触发空值回退。
开启的合并 usage_metadata 为9入、2出、总计11;关闭时这个标准化字段缺失,collect 才将其记录成 null。与此同时,本例 response_metadata.usage 仍保留两个为0的缓存字段,所以“还看见 usage 对象”不等于标准化开关失败。捕获的9个请求仍为 stream:true,未包含名为 streamUsage 的请求体字段;这里是适配器结果呈现的开关,不能泛化为所有服务端都会停止提供用量事件。
本次安装包的流式路径使用 this.streamUsage ?? options.streamUsage;只要实例值不是 null 或 undefined,就先用实例值,包括 false。若目标约定是单次值优先,应先取 options,再回退实例;下面仅做表达式语义对照,不声称相关修复已经发布。
const instanceValue = true;
const perCall = false;
console.log(instanceValue ?? perCall); // true:实例在前
console.log(perCall ?? instanceValue); // false:单次在前,保留显式false
console.log(perCall || instanceValue); // true:错误地丢掉false
console.log(undefined ?? instanceValue); // true:省略时回退
console.log(null ?? instanceValue); // true:仅演示JS空值行为,不是受支持的布尔配置
console.log(false ?? true); // false
六行输出依次为 true、false、true、true、true、false。把顺序调整后又换成 || 仍然不对,因为显式 false 会被逻辑或回退成实例 true;undefined 才是本例省略值的常见表示。null 只用于说明 JavaScript 运算规则,不表示该API类型支持用它关闭或清除配置。
在没有经过验证的修复版本时,可以将开启与关闭分别固化到实例,再按本请求的布尔值选择实例。保存为 02-isolated.mjs,使用同一个 fixture 执行;入口以 ?? 保留显式 false,并拒绝字符串 false,不在并发请求间临时修改同一实例的属性。
import {fixture, collect} from './fixture.mjs';
const f = await fixture();
try {
// 固定策略实例复用;不在请求中临时改共享实例的streamUsage。
const instances = new Map([[true,f.model({streamUsage:true})],
[false,f.model({streamUsage:false})]]);
const defaultUsage = true;
async function run(options = {}) {
const enabled = options.streamUsage ?? defaultUsage;
if (typeof enabled !== 'boolean') throw new TypeError('streamUsage必须是布尔值');
return collect(instances.get(enabled));
}
const results = await Promise.all([run({streamUsage:false}),run(),run({streamUsage:true})]);
console.log(JSON.stringify(results)); // 依次关闭、默认开启、显式开启
try { await run({streamUsage:'false'}); }
catch (error) { console.log(error.name); } // TypeError:字符串不是布尔false
} finally { await f.close(); }三次并发结果依次为关闭0块、默认开启2块、显式开启2块,正文都是 ok;传字符串 false 的额外调用抛 TypeError。这里是业务入口选择不同固定策略,不是底层单次优先级已经修复。真实项目须让两实例的模型、超时和其他配置保持预期一致,并管理客户端连接及实例数量;不要通过把共享实例临时改成 false 再恢复来模拟单次配置。
截至2026年11月5日本次核查,相关修复提案仍未合并,已安装1.6.0也复现了上述矩阵。后续更新依赖,应重新核对锁文件、部署产物和实际适配器,并至少验证默认开启加单次false、固定关闭加单次true、省略调用值三个边界;不要把提出的修改当作已发布行为。
本篇实际验证范围是直接 model.stream 与本地兼容SSE,没有证明线上计费、其他适配器、普通 invoke 或所有 Agent 事件投影都有同样结果。若字段已经按预期变化,而日志仍显示用量,应继续确认读取的是 usage_metadata、response_metadata 还是原始服务端事件,以及是否经过额外聚合层。
固定关闭可在构造时设 false 并核对标准化字段;按请求切换则在未修复版本中选固定策略实例;正式修复后仍以同矩阵验收单次优先和省略回退。分清布尔、默认与字段层次,避免把不同 usage 混在一起。