LangChain JS 的单次 guardrailConfig 没覆盖默认值,应该查哪一层?
先限定为 @langchain/aws 的 ChatBedrockConverse,再检查选项解析和构造出的 SDK 命令。本次1.5.2实测 guardrailConfig、additionalModelRequestFields 都先用构造值;不能只看回答内容就判断单次配置或护栏已经生效。
1. 三组输入:同时设置、只用默认、只用单次值
环境为 Node.js 22.23.4、@langchain/aws 1.5.2、@langchain/core 1.3.4,底层 @aws-sdk/client-bedrock-runtime 3.1162.0。安装前两个指定版本后,将公共代码保存为 fixture.mjs,再将每节代码分别保存为 01-resolve.mjs 等文件,用 Node.js 执行;每组独立运行。这里注入本地 SDK 替身,不读取真实 AWS 凭证,也不发送AWS请求。模型和护栏标识都是模拟占位,top_k 只用于区分两个 JSON 值,不代表任意模型都支持该参数。
import { ChatBedrockConverse } from '@langchain/aws';
export const defaults = {
guardrailConfig:{guardrailIdentifier:'guardbase',guardrailVersion:'1',trace:'disabled'},
additionalModelRequestFields:{top_k:10}
};
export const perCall = {
guardrailConfig:{guardrailIdentifier:'guardcall',guardrailVersion:'2',trace:'enabled'},
additionalModelRequestFields:{top_k:20}
};
// 本地SDK边界替身:捕获真实适配器构造的命令,不向AWS发送请求。
export function createProbe() {
const seen = [];
const client = { async send(command) {
seen.push(structuredClone({kind:command.constructor.name,
guardrailConfig:command.input.guardrailConfig,
additionalModelRequestFields:command.input.additionalModelRequestFields}));
if (command.constructor.name === 'ConverseStreamCommand') {
return {stream:(async function* () {
yield {messageStart:{role:'assistant'}};
yield {contentBlockDelta:{contentBlockIndex:0,delta:{text:'ok'}}};
yield {messageStop:{stopReason:'end_turn'}};
})()};
}
return {output:{message:{role:'assistant',content:[{text:'ok'}]}},stopReason:'end_turn'};
}};
return {seen, client,
model(fields = {}) { return new ChatBedrockConverse({
model:'local-model',region:'us-east-1',client,maxRetries:0,...fields
}); }
};
}先直接检查本版本的 invocationParams,它可用于本地诊断解析,不应当成跨版本稳定的业务扩展接口。构造值是 guardbase、版本1、top_k:10;单次值是 guardcall、版本2、top_k:20。下面保持相同配置,只改变值放在哪一层。
import {createProbe, defaults, perCall} from './fixture.mjs';
const probe = createProbe();
const configured = probe.model(defaults);
for (const [label, model, options] of [
['both', configured, perCall],
['fallback', configured, {}],
['call-only', probe.model(), perCall]
]) {
const p = model.invocationParams(options); // 仅作本地解析诊断
console.log(label, JSON.stringify({guardrailConfig:p.guardrailConfig,
additionalModelRequestFields:p.additionalModelRequestFields}));
}
// 1.5.2中:both仍为guardbase/10;call-only为guardcall/20。both 和 fallback 都得到 guardbase、版本1、top_k:10,call-only 才得到 guardcall、版本2、top_k:20。说明单次字段并非完全没有传进来,而是存在构造值时被它挡住;如果三组全都一样,还要检查上层封装是否丢弃了选项、字段是否放在 invoke 的第二参数顶层,以及运行时实际加载的包版本。
2. 不只看中间参数:核对 invoke 和 stream 的命令输入
继续使用相同 fixture。这次实际执行适配器的非流式和流式路径,在 client.send 入口捕获它构造出的 ConverseCommand 与 ConverseStreamCommand;替身返回的 ok 只是让流程完成,不能证明任何真实护栏策略生效。
import {createProbe, defaults, perCall} from './fixture.mjs';
const probe = createProbe();
const model = probe.model(defaults);
await model.invoke('diagnostic', perCall);
for await (const chunk of await model.stream('diagnostic', perCall)) {
// 消费完整流,使流式路径也走到本地SDK替身。
}
console.log(JSON.stringify(probe.seen));
// 两种命令的guardrailConfig仍为guardbase,top_k仍为10。两种命令都携带 guardbase、版本1、trace:disabled 与 top_k:10,覆盖问题已经在进入 SDK 传输前出现。本次验证止于命令输入,没有验证 HTTP 序列化、签名、权限、模型支持或 AWS 服务端执行;如果命令已经正确,才继续沿真实测试环境检查后面的链路。trace 是追踪配置,不能只凭启用追踪就判断覆盖顺序已修复。
3. 当前版本的临时做法:先选完整配置,再创建独立实例
在没有经过验证的修复版本、又暂不维护受控补丁时,可以在请求入口用业务规则选定整份配置,再放入本请求自己的实例。下面两次调用并发使用不同实例,共享同一个本地客户端,但不修改共享 model 的属性;真实项目也可按批准的固定策略复用独立实例,注意实例管理和缓存成本。
import {createProbe, defaults, perCall} from './fixture.mjs';
const probe = createProbe();
function requestModel(options = {}) {
// 先决定整份配置,再放入本请求独立的模型实例。
return probe.model({
guardrailConfig:structuredClone(options.guardrailConfig ?? defaults.guardrailConfig),
additionalModelRequestFields:structuredClone(
options.additionalModelRequestFields ?? defaults.additionalModelRequestFields)
});
}
await Promise.all([
requestModel(perCall).invoke('override'),
requestModel().invoke('fallback')
]);
console.log(JSON.stringify(probe.seen));
// override为guardcall/20,fallback为guardbase/10,不改共享model属性。override 得到 guardcall、版本2、top_k:20,fallback 保留 guardbase、版本1、top_k:10。配置在进入有问题的解析层前已经选定,因此没有构造值与单次值竞争。另测了不设置这两项构造默认、每次显式传入完整已解析值的入口,也能传入单次配置;采用这种方式时,应统一封装所有调用,不能让裸调用遗漏应有默认配置。
4. ?? 的顺序决定优先级,但它不会深合并对象
本版本源码对这两个字段使用构造值在左、调用值在右的空值回退,其他部分字段则采用相反顺序。下面用最小表达式对照根因与目标顺序,不将它伪装成已经发布的补丁;本节的 null、空对象只用于解释 JavaScript 行为,不保证它们是该服务接受的调用配置。
import {defaults, perCall} from './fixture.mjs';
// 根因与目标优先级的最小对照,不是已发布版本的补丁。
const ctor = defaults.guardrailConfig;
const call = perCall.guardrailConfig;
console.log((ctor ?? call).guardrailIdentifier); // guardbase
console.log((call ?? ctor).guardrailIdentifier); // guardcall
console.log(JSON.stringify({trace:'enabled'} ?? ctor)); // 只有trace,没有自动合并id和version
console.log(JSON.stringify({} ?? ctor)); // {}:空对象仍优先
console.log((null ?? ctor).guardrailIdentifier); // guardbase
console.log((undefined ?? ctor).guardrailIdentifier); // guardbase交换两侧后,非空的单次值才优先;但只含 trace 的对象仍只是自己的字段,并不会自动继承标识和版本,空对象也仍是一个有值的对象。null 与 undefined 在 ?? 下都会回退,因此不能当作可靠的清除或禁用开关。需要继承时应显式构造并验证完整配置,保持策略标识与版本的配对,不要随意深合并不同模型的请求 JSON。
5. 固定版本复验,再判断问题落在哪一层
截至2026年11月5日本次核查,对应修复提案仍未合并,已安装的1.5.2包也实际复现了构造值优先。不要将提交、开放PR或修改源码的建议直接写成正式版本已修复;未来更新依赖后,重新运行同一矩阵,并核对锁文件与部署产物,确认实际执行的是哪份实现。
| 核对项 | 本次观察或应有判据 |
|---|---|
| 构造+单次都设置 | 1.5.2中两个字段仍取构造值 |
| 没有单次配置 | 保留明确的构造默认 |
| 没有构造配置 | 本次单次完整配置进入解析及命令 |
| invoke / stream | 两条路径都要看对应命令输入 |
| 目标顺序为单次优先 | 仍须验证整对象替换、空值回退与并发隔离 |
| 真实服务端 | 命令正确后再查策略标识、版本、地区、权限及既定验证用例 |
先对照三组输入的解析,再核对 invoke、stream 命令。临时方案在入口选定完整配置或使用独立策略实例;不要并发修改共享 model.guardrailConfig 后再恢复,也不能用本地 mock 成功证明云端护栏已运行。
-
- 微信支付联调如何识别仿真与生产请求?
- 11.06
-
-
-
-
-
-
下载
- |
-
-
下载
- 《行尸走肉第一章》免安装中文汉化硬盘版下载
- 单机|436 MB
- 一款以动作冒险为主题的游戏
-
-
下载
- 《街头霸王X铁拳》免安装中文汉化硬盘版下载
- 单机|111MB
- 一款非常好玩的格斗游戏
-
-
下载
- |
-
-
下载
- 《暗黑破坏神3》免安装繁体中文正式版下载
- 单机|7630 MB
- 一款以角色扮演为主题的游戏
-
-
下载
- 《马克思佩恩3》免安装硬盘版下载
- 单机|27033 MB
- 一款以第三人称射击为主题的游戏