【Bug已解决】Deprecated classic chat agents leak empty ValueError on save
【Bug已解决】Deprecated classic chat agents leak empty ValueError on save一、现象长什么样当你对一个已废弃deprecated的经典 chat agent比如老式的ConversationalAgent/ 旧的AgentExecutor包装调用.save()想把它的配置落盘时代码抛出一个空的ValueError——也就是ValueError()不带任何 messageTraceback (most recent call last): ... File .../agent/agent.py, line N, in save raise ValueError() ValueError最坑的地方在于空你只看到ValueError五个字母没有任何上下文说为什么失败、是哪个字段不支持、该怎么做。开发者只能去翻源码发现save()里有一堆if not supported: raise ValueError()的裸抛废弃路径上某个分支触发了它但信息为零。更要命的是这个 agent 已经 deprecated官方建议迁移到新 API但在迁移前你只是想先保存一下当前配置却被一个毫无信息的异常挡住。二、背景LangChain 早期有经典 agentAgentAction/AgentFinishAgentExecutor 各种*Agent类后来统一到 LCEL /create_agent体系旧类被标deprecated。但旧类的.save()方法把 agent 的tools/llm/prompt序列化成一个AgentExecutor配置 dict 再写 JSON还在且内部对某些组合不支持保存的处理是直接raise ValueError()没带消息。随着代码演进废弃类里越来越多字段/分支走到了不支持保存的路径而每个raise ValueError()都没补 message于是用户在任何不支持的组合上.save()拿到的都是空错误。三、根因根因两点裸raise ValueError()无 message所有不支持分支都抛空异常信息缺失用户无法定位。废弃代码的保存路径未维护deprecated 类本就少维护.save()对新字段/新分支没跟上触发不支持路径却没有友好提示也没有引导到新 API。本质错误处理用了占位式抛异常先 raise 个 ValueError 占坑日后补 message但日后没补导致废弃路径上的错误完全不可诊断。四、最小可运行复现下面缩略逻辑复现空 ValueErrorclass DeprecatedChatAgent: def __init__(self, tools, llm): self.tools tools self.llm llm def save(self, path): # 废弃路径某些组合不支持保存 if self.llm is None: raise ValueError() # 空 message用户一脸懵 if not self._supports_persist(): raise ValueError() # 又是空 message _write(path, {...}) agent DeprecatedChatAgent(tools[], llmNone) agent.save(agent.json) # ValueError 空修复给每个分支补上有意义的 message并引导迁移。def save(self, path): if self.llm is None: raise ValueError(cannot save agent: llm is None) if not self._supports_persist(): raise ValueError( this deprecated agent type no longer supports save(); migrate to create_agent(...) instead )五、解决方案第一层最小直接修复最小修法把所有裸raise ValueError()补上明确 message说明原因与迁移建议并对确定不支持的情况直接给出 deprecation 引导而非含糊拒绝。def save(self, path): if self.llm is None: raise ValueError(save() failed: llm is required but was None) if not self._supports_persist(): raise NotImplementedError( save() is not supported for this deprecated agent; please migrate to langchain.agents.create_agent ) _write(path, self._to_config())这一层让错误自解释用户立刻知道原因和下一步。六、解决方案第二层结构化改进把废弃 agent 保存策略固化成策略对象作为单一事实来源集中管理哪些情况可保存、不可保存时说什么。from dataclasses import dataclass, field from typing import List dataclass(frozenTrue) class LangChainClassicAgentSavePolicy: 废弃经典 chat agent 保存策略的单一事实来源。 supported_configs: List[str] field(default_factorylambda: [toolsllmprompt]) require_llm: bool True on_unsupported: str raise_with_migration_hint migration_hint: str migrate to langchain.agents.create_agent def decide(self, llm, supports_persist: bool) - str: if self.require_llm and llm is None: return fsave() failed: llm is required if not supports_persist: if self.on_unsupported raise_with_migration_hint: return fsave() unsupported; {self.migration_hint} return save() unsupported return ok def validate(self) - None: if self.on_unsupported silent and self.require_llm: raise AssertionError(must not silently fail on required field).save()用policy.decide(...)生成错误信息所有分支的 message 集中、可测、含迁移提示。七、解决方案第三层断言 / CI 守护用 pytest 锁死错误处理质量import pytest from policy import LangChainClassicAgentSavePolicy as P def test_none_llm_message_clear(): p P() msg p.decide(None, True) assert llm is required in msg def test_unsupported_gives_hint(): p P() msg p.decide(object(), False) assert create_agent in msg def test_no_silent_failure(): with pytest.raises(AssertionError): P(on_unsupportedsilent).validate() def test_ok_path(): p P() assert p.decide(object(), True) okCI 加一条grep 废弃类的save()禁止出现裸raise ValueError()无参数强制带 message。八、排查清单.save()抛空ValueError没原因→ 废弃路径裸raise ValueError()需补 message。是否提示如何迁移→ 不可保存时应引导到create_agent。是否有占坑式异常→ 所有raise ValueError()必须带说明。llm is None是否被明确报出→ 缺必填字段要有名有姓。CI 是否禁止裸 raise→ 扫描raise ValueError()无参调用。是否已废弃→ 优先引导迁移而非修旧实现。九、小结已废弃的经典 chat agent 的.save()在多个不支持分支抛空ValueError用户得不到任何诊断信息且无法据此迁移。根因是错误处理用了占坑式裸抛且废弃代码长期未维护。第一层给每个分支补上明确 message 与迁移引导第二层用LangChainClassicAgentSavePolicy把保存策略与错误信息固化成单一事实来源第三层用 pytest CI 扫描禁止裸raise ValueError()。错误处理的通用原则异常必须自解释原因下一步决不能用占位式空异常。