langchain-ai/langchain#38840 · ChatPerplexity 在构建 Responses 载荷时原地篡改调用方的 extra_body 字典:单次调用参数泄漏进实例,污染后续所有请求 · 2026-07-26
ChatPerplexity._to_responses_payload
(langchain-perplexity)在构建 Responses API 请求体时,直接写入调用方传入的 extra_body 字典,而非拷贝副本。
后果有二:① 调用方自己持有的 extra_body dict 被就地改写;
② 由于 _generate 走
{**self._default_params, **kwargs} 浅合并,
单次调用的临时参数(如 search_mode)会永久沉淀进实例的 model_kwargs,泄漏到之后每一次请求。
无异常、无报错——损坏本身就是 bug。
只要同一个 ChatPerplexity 实例被复用、且某次调用传了 per-call 参数,污染即发生——而复用实例正是生产的默认用法。
没有异常、没有堆栈、没有红灯。第 2 次调用没有传 search_mode,载荷里却带着上一次的 search_mode="academic"。行为漂移是唯一症状。
检索模式(academic/web)、地域(country)等参数跨请求串味,会导致本应普通检索的请求被静默切成学术检索,或用户 A 的地域设置泄漏到用户 B——在多租户 Agent 网关中即数据/合规越界。
根因:载荷构建把 extra_body 引用拿来直接 [key] = value 写入,未做防御性拷贝。叠加 _generate 的浅合并,per-call 参数被写回实例级 model_kwargs。
这是一个教科书级的"共享可变默认/引用未拷贝"缺陷——与 ARK 已诊断的 #38779(ChatAnthropic.bind_tools 篡改 tool_choice)、#38659(ChatGroq 原地改 token_usage)同一 bug 族。
报告者提供了零依赖、可直接运行的最小复现(无需 API Key、无需联网),并主动关联了同类 issue #38779 与 #38659,明确指出这是一个"跨集成反复出现的可变状态篡改模式"。截至诊断时 issue 仍 OPEN(5 条讨论)。
修复本身极简(入口浅拷贝),但价值在于:这是第三次以同样形态出现的 bug——说明缺的不是某处补丁,而是一道"禁止原地篡改调用方入参"的系统性防线。
当同一缺陷族在 Anthropic / Groq / Perplexity 三个集成里各自复发,逐个打补丁只是治标;真正的解法是把"入参不可变"变成可在运行时被强制校验的契约。
场景:多租户检索网关复用单一 LLM 实例
网关为省资源全局复用一个 ChatPerplexity。用户 A 的请求带 search_mode="academic"、country="US"——
→ 参数沉淀进实例 model_kwargs
→ 用户 B 本该做普通全网检索,却被静默套上 A 的学术模式与美国地域,返回错误的检索结果,且日志里 B 的请求参数看起来完全正常。
问题本质
这是"静默失败"的经典形态:函数越权修改了调用方拥有的数据,破坏了"传入的参数在调用后保持不变"这一隐性契约。没有崩溃、没有告警,只有随调用次数累积的行为漂移——最难复现、最难归因的一类生产事故。
① InputGuard — 入参不可变契约(运行时冻结/校验)
② IdempotencyGuard — 隔离 per-call 参数,杜绝跨请求泄漏
③ OTel Bridge — 参数漂移全链路留痕
在生产真实流量上捕获"未传却出现"的参数,把最难归因的行为漂移变成一条可检索的告警。
#38840 的真正信号不是"又一个原地篡改 bug",而是这已经是同一缺陷族第三次复发(#38779 / #38659 / #38840)。逐个打补丁治标不治本——缺的是一道"禁止越权修改调用方入参"的系统性防线。
被污染的是检索模式、地域这类会直接改变输出语义的参数;在多租户网关里,一次调用的参数会静默泄漏到后续所有请求,甚至跨用户串味——无异常、无红灯,只有随调用次数累积的行为漂移。
修复一处很简单(入口浅拷贝),但可靠性不能靠"记得每个集成都别忘了拷贝"。防线必须建在自己这一侧,并且可被机器强制执行。
ARK 的入参不可变契约 + per-call 参数隔离 + OTel 漂移留痕,把"跨集成反复复发的可变状态篡改"从靠人自觉的编码规范,升级为运行时可校验、可观测、可告警的信任基础设施。