langchain-ai/langchain#38779 · ChatAnthropic.bind_tools 静默变异调用方 tool_choice 字典:一次调用,永久污染 · 2026-07-25
ChatAnthropic.bind_tools()
在 parallel_tool_calls=False 时,
把调用方传入的 tool_choice 字典
按引用直接存入 kwargs,随后在同一对象上原地写入
disable_parallel_tool_use=True。
结果是:调用方自己的字典被框架偷偷改写。
如果该字典是模块级常量、配置对象或在多个模型间复用——污染将随共享引用扩散到所有后续调用。
任何在 dict 形式下使用 tool_choice + parallel_tool_calls=False 的 Anthropic 用户都会触发。强制工具调用(forced tool use)是结构化输出的主流姿势。
变异发生在框架内部,无警告、无返回值差异。调用方只有在下一次复用同一字典时才可能发现行为异常——而那时距离变异点可能已隔了任意长的代码路径。
同族bug已被证实存在于 ChatPerplexity(#38840:extra_body 原地变异导致"每次调用的参数泄漏到实例,污染所有后续请求")。这是一个模式级缺陷,不是孤立笔误。
复现(3行即可确认):
根因是Python共享引用语义与"输入参数应视为不可变"契约的冲突——框架把调用方对象当成了自己的私有可写状态。
至少7个修复PR(#38778/38792/38795/38819/38864/38923/39006)全部因"未分配issue"被自动关闭——与 #38892 相同的分配机制死锁再次上演。
@Suneel-DK、@umi008、@saitejabandaru-in、@gingeekrishna、@vishnup102002 五人独立研究,全部收敛到同一行修复:
issue 挂 open 15 天、7 个 PR 被关、修复只需一行——这是"维护带宽瓶颈"的教科书样本,也是生产环境用户必须自行加防护层的理由。
场景:多Agent共享工具配置
团队把 TOOL_CHOICE_EXTRACT = {"type":"tool","name":"Extract"} 定义为模块级常量,供 Agent-A(禁并行)和 Agent-B(允许并行)共用。
→ Agent-A 先初始化,常量被写入 disable_parallel_tool_use=True
→ Agent-B 随后绑定同一"常量",静默继承了禁并行语义——其并行工具调用能力消失,吞吐下降,且无任何报错。
问题本质
这是与 #38840(Perplexity extra_body)同型的"配置即状态"污染:一次调用的私有意图被写进共享对象,变成所有后续调用的全局默认。行为漂移与变异点在时间和代码位置上完全解耦,几乎无法用常规调试定位。
① OutputValidator — 配置不变式校验
② IdempotencyGuard — 以内容哈希暴露配置漂移
③ OTel Bridge — 配置变异事件全链路留痕
配合 Langfuse/Jaeger,可精确回放"哪一次 bind 污染了哪个配置对象"。
#38779 的杀伤力不在这一个bug,而在它揭示的模式。框架把调用方对象当私有状态原地改写,同型缺陷已在 Perplexity 集成(#38840)中再次出现——"配置即状态"污染是集成层的系统性风险。
变异无信号、症状与根因在时间上解耦、污染沿共享引用扩散——这三点叠加,使它成为生产Agent中最难调试的一类行为漂移。
7个PR被自动关闭、issue开放15天、修复只需一行——再次验证:上游修复速度不可依赖,可靠性防线必须建在自己这一侧。
ARK的配置不变式校验+幂等哈希+OTel留痕组合,把"静默污染"转化为显式失败、可观测指标与可回放证据链——这正是 Trust Layer 存在的意义。