GPT-6 Astra 是怎么找到问题所在的?结构分析与反馈定位过程
2026-11-06
2026-11-06 0
profile 能帮助 LangChain JS 在本地选择策略,但其上限仍需与当前模型端点核对。遇到长对话提前超限,可以依次比较版本核查、显式配置、摘要阈值和输入预算,先找到错误发生的层次。
原理:先打印运行进程实际使用的模型名与 profile,再对照该提供商、端点、模型版本和权限对应的当前窗口。本次使用 Node.js 22.23.4、langchain 1.6.1、@langchain/core 1.3.4、@langchain/anthropic 1.6.0;示例保存为 .mjs 后用 node 执行,除特别说明外都不访问云模型。
import { ChatAnthropic } from '@langchain/anthropic';
for (const model of ['claude-sonnet-4-5','claude-sonnet-4-5-20250929','claude-sonnet-4-6']) {
const llm = new ChatAnthropic({model,apiKey:'local-test-key'});
console.log(model, llm.profile.maxInputTokens);
}
// 本次1.6.0:依次为200000、200000、1000000。只读取本地元数据,不访问服务端。当前版本输出依次是 200000、200000、1000000。另在隔离目录安装 @langchain/anthropic 1.5.5,实测前两个 Sonnet 4.5 名称都返回 1000000;因此历史偏差确实能在旧包中读到,但本次 1.6.0 已不再返回该错误值。当前 Claude 文档将 Sonnet 4.5 标为已弃用、窗口为 200k;这里保留它是为了复核历史问题,不是新项目的模型选择建议,也没有实测账户能否继续调用。
优点:先查版本与端点,可以避免把历史偏差套用到新包。限制:集成包中的 profile 并非服务端的实时确认;请保留错误类型、消息和请求编号,分清应用未发请求时的预检、服务端 prompt is too long 和请求字节过大的 413。旧上限偏高会推迟摘要,使请求在未达到标称值时被服务端拒绝;本地阈值偏低则可能提前挡住请求。
原理:使用 initChatModel 的 profile 选项,为实际使用该元数据的模型设置经过核对的值。下面把当前 200000 再保守调整到 180000,以展示覆盖是否生效;它是示范策略值,不是宣称该模型真实窗口只有 180000。fakeFetch 完全截获请求并返回本地假响应,只用于比对序列化结果。
import { initChatModel } from 'langchain/ch@t_models/universal';
const requests = [];
const fakeFetch = async (_url, init) => {
requests.push(JSON.parse(init.body)); // 本地截获,不发网络请求
return new Response(JSON.stringify({id:'msg_local',type:'message',role:'assistant',
model:'claude-sonnet-4-5-20250929',content:[{type:'text',text:'ok'}],
stop_reason:'end_turn',stop_sequence:null,usage:{input_tokens:1,output_tokens:1}}),
{status:200,headers:{'content-type':'application/json'}});
};
const options = {modelProvider:'anthropic',apiKey:'local-test-key',maxTokens:16,
maxRetries:0,clientOptions:{fetch:fakeFetch}};
const normal = await initChatModel('claude-sonnet-4-5-20250929', options);
const corrected = await initChatModel('claude-sonnet-4-5-20250929', {
...options, profile:{...normal.profile,maxInputTokens:180000}
});
console.log(normal.profile.maxInputTokens, corrected.profile.maxInputTokens);
await normal.invoke('hi');
await corrected.invoke('hi');
console.log(JSON.stringify(requests[0]) === JSON.stringify(requests[1]));
console.log('maxInputTokens' in requests[1]);
// 200000 180000 / true / false:本地元数据改变,请求体并未扩容。优点:保留其他 profile 字段,同时明确覆盖 maxInputTokens;输出为 200000 180000、true、false,说明两个实例的本地元数据不同,但发出的请求体相同,且没有 maxInputTokens 字段。限制:覆盖不会开通长上下文权限,也不会更改服务端模型;不要直接给只读 getter 赋值,或假定每个集成的构造参数都接受同样的覆盖方式。变更后要检查最终传给预检或中间件的实例,而不是只打印另一个未被使用的模型。
原理:百分比阈值依赖它所读取的 profile,绝对 Token 阈值则能脱离这个比例基数。在本次 langchain 1.6.1 的 summarizationMiddleware 中,fraction 读取的是传入该中间件的摘要模型 profile;它不会自动读取另一个主 Agent 模型的窗口。下面直接调用真实中间件钩子,用本地摘要模型和人为计数演示分支,不是真实长文本推理。
import {summarizationMiddleware} from 'langchain';
import {HumanMessage,AIMessage} from '@langchain/core/messages';
for (const [label,limit,trigger] of [
['stale-profile',1000000,{fraction:0.85}],
['corrected-profile',200000,{fraction:0.85}],
['absolute-tokens',1000000,{tokens:170000}]
]) {
let summaryCalls = 0;
const summaryModel = {profile:{maxInputTokens:limit},
async invoke() { summaryCalls++; return new AIMessage('local summary'); }};
// 模拟每条消息52500,仅验证阈值分支,不是真实Token计数。
const middleware = summarizationMiddleware({model:summaryModel,trigger,
keep:{messages:2},tokenCounter:messages=>messages.length*52500,
trimTokensToSummarize:500000});
const messages = [new HumanMessage('a'),new AIMessage('b'),
new HumanMessage('c'),new AIMessage('d')];
// 仅离线测试钩子;业务应将中间件交给createAgent执行。
const update = await middleware.beforeModel({messages},{context:{}});
console.log(label, summaryCalls, Boolean(update));
}
// stale-profile 0 false;其余两组1 true。四条消息被人为计成 210000 Tokens:1000000 × 0.85 的触发点是 850000,第一组不摘要,输出 0 false;改为 200000 后触发点是 170000,第二组输出 1 true;第三组直接设置 tokens:170000,同样输出 1 true。优点:能用小样本确认“摘要为什么还没触发”,无需构造几十万字或付费请求。
限制:示例 tokenCounter 与模型 invoke 都是测试替身,不能拿结果证明线上计数或摘要质量。业务中应把中间件交给 createAgent 执行,使用适合目标模型的计数方法,并为主模型与摘要模型各自留余量;keep 的数量、摘要长度、工具调用配对与摘要请求自身能否容纳也要复核。如果两个模型窗口不同,可按较小的可用预算设置绝对阈值,并在摘要更新后重新计数,不能只把 fraction 改小就认为一定安全。
原理:在已核实的窗口内,应用可以保守地预留输出和计数误差,再决定是否摘要、裁剪检索片段或拒绝发送。这是应用策略示例,不是所有提供商统一采用的服务端拒绝公式。
// 这是保守应用预算,不是假定所有提供商都用同一拒绝公式。
const verifiedContext = 200000;
const outputReserve = 8000;
const safetyMargin = 4000;
const inputBudget = verifiedContext - outputReserve - safetyMargin;
const estimatedInput = 190000; // 应来自与最终消息、系统、工具和附件匹配的计数
console.log(inputBudget, estimatedInput <= inputBudget); // 188000 false
console.log(Math.floor(1000000 * 0.85), Math.floor(200000 * 0.85)); // 850000 170000本例输入预算为 188000,估算输入 190000 时不放行。优点:预算由业务明确控制,方便复查。限制:不能只数用户可见的聊天文字;系统提示、历史、工具定义与结果、图片附件等都应按目标端点规则计入最终输入。提示缓存不会让缓存前缀退出上下文,thinking 块的保留与计数也要按具体模型核对。
还要区分“预留不足”和“输入本身已超限”。当前 Claude 文档规定输入单独超过窗口会报 400;对于 Claude 4.5 及更新模型,输入与 max_tokens 相加超过窗口时可能先接受请求,再以 model_context_window_exceeded 停止生成。因此不要仅凭输入加输出预算超过上限,就断言所有模型都会在请求前被拒绝;诊断时同时看服务端错误与 stop_reason。本篇没有发出超长云请求,服务端行为以当前官方规则为依据。
| 做法 | 适用场景 | 关键限制 |
|---|---|---|
| 打印版本、模型与profile | 怀疑旧资料、别名或端点不一致 | 本地元数据不代表服务端承诺 |
| initChatModel显式profile | 已核实容量,需修正本地预检基数 | 不扩容模型;要改实际被消费的实例 |
| 绝对阈值或校准fraction | 摘要一直没有按预期触发 | 要核对摘要模型的窗口与最终输入计数 |
| 完整输入预算与预留 | 避免历史、工具或输出占满窗口 | 应用保守策略不等于统一API拒绝规则 |
仍用旧集成包时,先在部署环境读取真正运行的版本与 profile,然后核对当前服务端窗口。若这些都正确,接着查完整输入、摘要条件和错误所在层;最终用真实端点响应复验,不通过增大本地数字来假装扩容。