2026/8/15 2:54:43

【Bug已解决】fix create_agent fixed settings when invoke dashscope models with customed response format …

【Bug已解决】fix create_agent fixed settings when invoke dashscope models with customed response format … 【Bug已解决】fix create_agent fixed settings when invoke dashscope models with customed response format 解决方案一、现象长什么样用 LangChain/LangGraph 的create_agent创建一个智能体底层接DashScope阿里云百炼模型并给模型传了一个自定义的response_format比如要求模型按某个 JSON schema 输出。结果智能体创建后你设置的那些模型参数温度、response_format 本身等被“固定/覆盖”了# 期望模型用我给的 response_format 和 temperature agent create_agent(model_with_custom_format, tools...) # 实际response_format 没生效或被改成了默认值temperature 也被重置 # DashScope 返回的不再是自定义结构而是普通文本具体表现只有在使用DashScope 模型 自定义response_format时出问题其它模型或不用自定义格式时正常。create_agent内部在“套用 DashScope”时会重新生成/固定一份模型配置把你传入的response_format、部分参数给覆盖或丢弃。不是报错而是“配置被悄悄改了”导致模型输出格式不符合预期下游解析 JSON 失败。调试时发现传入的model对象上的response_format还在但真正发给 DashScope 请求里的格式变成了默认的或被抹掉。关键特征create_agent在适配 DashScope 自定义 response_format 时错误地“固定fix”了模型设置覆盖/丢失了用户显式传入的自定义格式与参数。二、背景create_agent是 LangChain/LangGraph 里用来“把模型 工具 提示”组装成一个可运行智能体”的工厂函数。它会对传入的model一个BaseChatModel做一些处理确保智能体能正确调用工具、处理结构化输出等。DashScope阿里云的通义千问等模型服务支持结构化输出你可以通过response_format参数告诉模型“按这个 JSON schema 返回”。这是很多 agent 场景的刚需比如让模型返回标准化的工具调用参数或固定结构答案。问题在于create_agent为了“适配”不同模型 provider可能会根据 provider 类型重建或覆盖模型配置。当 provider 是 DashScope 且用户已经设了response_format时这段代码走的“固定设置”逻辑没有把用户的response_format接住反而用了一套内部默认/被清空的设置把它覆盖了。换句话说工厂函数假设“是我来定格式”于是把用户定的格式抹掉了。这种“配置被静默覆盖”的 bug 特别隐蔽没有异常只是行为不对而且只在“DashScope 自定义格式”这个组合触发很容易让人以为是 DashScope 本身不支持。三、根因根因是create_agent在适配 DashScope 模型时无条件“固定/重置”了模型设置没有保留用户已显式提供的response_format与参数导致自定义格式被覆盖覆盖而非合并适配 DashScope 的分支直接model SomeModel(..., response_formatDEFAULT)用新的构造覆盖了原model的response_format没有“如果用户已设就用用户的”这一判断。fixed语义错位代码里所谓“fixed settings”本意是“补全缺失的必需设置”却实现成了“重置成固定值”把用户显式传入的部分也清掉了。只针对 DashScope 自定义格式触发其它 provider 或不用自定义格式时走的是另一条不覆盖的路径所以问题被局限在这个组合更具迷惑性。无校验/告警覆盖后没有检查“用户原来设的格式丢了没”导致静默错误。一句话create_agent的 DashScope 适配把“补全设置”做成了“覆盖设置”用户显式传入的response_format被固定值抹掉自定义格式失效。四、最小可运行复现下面用 Python 模拟“工厂函数覆盖 vs 合并用户设置”的机理from dataclasses import dataclass, field from typing import Optional, Dict dataclass class ModelSettings: provider: str temperature: float 0.7 response_format: Optional[Dict] None def create_agent_buggy(model: ModelSettings, tools) - ModelSettings: 错误适配 DashScope 时无条件覆盖 response_format。 if model.provider dashscope: # “固定设置”用默认值覆盖丢了用户自定义的 response_format model ModelSettings( providerdashscope, temperaturemodel.temperature, response_format{type: text}, # 覆盖用户格式丢失 ) return model def create_agent_fixed(model: ModelSettings, tools) - ModelSettings: 修复只在用户没设时才补全已有则保留。 if model.provider dashscope: if model.response_format is None: model ModelSettings( providerdashscope, temperaturemodel.temperature, response_format{type: text}, ) # 用户已设 - 原样保留不覆盖 return model user_model ModelSettings( providerdashscope, temperature0.2, response_format{type: json_schema, schema: {name: Answer}}) print(buggy :, create_agent_buggy(user_model, []).response_format) # {type: text} - 用户自定义格式被覆盖 print(fixed :, create_agent_fixed(user_model, []).response_format) # {type: json_schema, schema: {name: Answer}} - 保留buggy把用户的json_schema格式覆盖成textfixed正确保留——正是该修的点。五、解决方案第一层最小直接修复最小修复是在create_agent适配 DashScope 时把“固定设置”改为“按需补全”仅当用户未显式提供response_format等时才用默认已提供则原样保留# agent_factory.py修复片段 def _adapt_dashscope(model: BaseChatModel, tools) - BaseChatModel: # 读取用户已设的 response_format不假设一定没有 user_rf getattr(model, response_format, None) if user_rf is not None: # 用户已显式设置 - 原样保留不覆盖 return model # 用户没设 - 才补全默认或按工具调用需要设置 return model.copy(update{response_format: _default_format(tools)}) def create_agent(model, tools, **kwargs): if _is_dashscope(model): model _adapt_dashscope(model, tools) return _build_agent(model, tools, **kwargs)这一层让 DashScope 自定义response_format的设置被完整保留智能体按用户预期的结构化格式工作。六、解决方案第二层结构性改进把“create_agent如何合并/补全模型设置尤其 DashScope”收口成唯一的配置对象LangChainAgentSettingPolicy工厂函数读它from dataclasses import dataclass from typing import Optional, Tuple, Dict dataclass(frozenTrue) class LangChainAgentSettingPolicy: create_agent 模型设置合并的单一事实来源。 # 用户显式设置的字段必须保留工厂只补全缺失项 preserve_user_settings: bool True # “固定设置”语义 补全merge不是覆盖overwrite merge_not_overwrite: bool True # DashScope 仅当用户未设 response_format 时才补全 dashscope_complete_only_if_absent: bool True # 禁止静默丢弃用户 response_format forbid_drop_user_response_format: bool True # 代码评审卡点 forbidden_patterns: Tuple[str, ...] ( model Model(response_formatDEFAULT) ignoring user, overwrite user response_format on dashscope, ) def adapt(self, provider: str, user_rf: Optional[Dict], default_rf: Dict) - Optional[Dict]: if not self.preserve_user_settings: return default_rf if user_rf is not None: return user_rf # 保留用户 if provider dashscope and self.dashscope_complete_only_if_absent: return default_rf # 仅补全 return user_rf if user_rf is not None else default_rf def describe(self) - str: return create_agent 保留用户设置、仅补全缺失、不覆盖 response_format POLICY LangChainAgentSettingPolicy() def plan_agent_settings(provider: str, user_rf: Optional[Dict], default_rf: Dict, policy: LangChainAgentSettingPolicy POLICY) - Optional[Dict]: return policy.adapt(provider, user_rf, default_rf)所有create_agent的 provider 适配都读POLICY设置合并语义被固化自定义格式不会再被“固定”掉。七、解决方案第三层断言 / CI 守护把“保留用户设置、仅补全、不覆盖”做成断言。下面用 pytest 守护import pytest def test_preserve_user_response_format(policy): user {type: json_schema, schema: {name: A}} out policy.adapt(dashscope, user, {type: text}) assert out user # 用户格式保留 def test_complete_when_absent(policy): out policy.adapt(dashscope, None, {type: text}) assert out {type: text} # 缺失才补全 def test_no_overwrite(policy): assert policy.merge_not_overwrite is True assert overwrite user response_format on dashscope in policy.forbidden_patterns def test_forbid_drop(policy): assert policy.forbid_drop_user_response_format is True def test_non_dashscope_passthrough(policy): # 非 dashscope 也遵循保留语义 user {type: json_schema} assert policy.adapt(openai, user, {type: text}) user这五组断言锁住(1) 保留用户格式(2) 缺失才补全(3) 不覆盖(4) 禁止丢弃(5) 非 DashScope 同样保留。CI 跑通即代表create_agent不会再悄悄改掉用户设置。八、排查清单遇到create_agent DashScope 自定义 response_format 失效看是不是配置被覆盖没报错但格式不对 → 工厂函数动了设置本题。确认组合是否 DashScope 自定义response_format才触发。查适配分支create_agent里 DashScope 分支是不是无条件重置了response_format。改补全不覆盖用户已设就保留只在缺失时补全默认。统一到LangChainAgentSettingPolicyCI 断言禁止覆盖用户设置。加日志/断言适配后检查“用户原格式是否还在”丢了就报错。端到端智能体发出的请求里response_format与用户设定一致。九、小结fix create_agent fixed settings when invoke dashscope models with customed response format题目保留customed原始拼写的根因是create_agent在适配 DashScope 模型时把“补全缺失设置”实现成了“重置成固定值”无条件覆盖了用户显式传入的response_format及部分参数导致自定义结构化输出格式被抹掉、模型返回普通文本下游解析失败——且全程无报错只在“DashScope 自定义格式”组合下触发。最小修复是把适配逻辑从“覆盖”改为“合并/补全”用户已设response_format就原样保留仅在缺失时补全默认结构性改进是用唯一的LangChainAgentSettingPolicy固化设置合并语义CI 用五组断言守护“保留用户设置、仅补全、不覆盖”。记住工厂函数对用户输入应是“补全者”而非“独裁者”任何“固定设置”都必须以“不覆盖用户显式值”为前提。