mirror of
https://github.com/hwchase17/langchain.git
synced 2026-07-11 09:26:29 +00:00
fix(langchain-classic): fix Chain.save() regression from dict-to-model_dump migration (#35667)
Fixes #35665 --- Fixes a regression introduced in #33035 where `Chain.save()` stopped working for chain types that support saving, including `LLMChain`. `Chain.save()` now calls `self.model_dump()`, but the `_type` injection still lived in the deprecated `dict()` override. As a result, serialized chains no longer included `_type`, and `save()` always raised the "does not support saving" error. This moves the override to `model_dump()` so saved chain output includes `_type` again. Pydantic v2's `dict()` method delegates to `model_dump()`, so existing `dict()` callers continue to get the same serialized shape.
This commit is contained in:
committed by
GitHub
parent
b08c3913fb
commit
2d8100c4fa
@@ -732,15 +732,15 @@ class Chain(RunnableSerializable[dict[str, Any], dict[str, Any]], ABC):
|
||||
)
|
||||
raise ValueError(msg)
|
||||
|
||||
def dict(self, **kwargs: Any) -> dict:
|
||||
def model_dump(self, **kwargs: Any) -> dict:
|
||||
"""Dictionary representation of chain.
|
||||
|
||||
Expects `Chain._chain_type` property to be implemented and for memory to be
|
||||
null.
|
||||
|
||||
Args:
|
||||
**kwargs: Keyword arguments passed to default `pydantic.BaseModel.dict`
|
||||
method.
|
||||
**kwargs: Keyword arguments passed to default
|
||||
`pydantic.BaseModel.model_dump` method.
|
||||
|
||||
Returns:
|
||||
A dictionary representation of the chain.
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
import re
|
||||
import uuid
|
||||
from pathlib import Path
|
||||
from typing import Any
|
||||
|
||||
import pytest
|
||||
@@ -66,6 +67,14 @@ class FakeChain(Chain):
|
||||
return {"baz": "bar"}
|
||||
|
||||
|
||||
class FakeSavableChain(FakeChain):
|
||||
"""Fake chain that supports saving via _chain_type."""
|
||||
|
||||
@property
|
||||
def _chain_type(self) -> str:
|
||||
return "fake_savable"
|
||||
|
||||
|
||||
def test_bad_inputs() -> None:
|
||||
"""Test errors are raised if input keys are not found."""
|
||||
chain = FakeChain()
|
||||
@@ -243,3 +252,51 @@ def test_run_with_callback_and_output_error() -> None:
|
||||
assert handler.starts == 1
|
||||
assert handler.ends == 0
|
||||
assert handler.errors == 1
|
||||
|
||||
|
||||
def test_model_dump_includes_type() -> None:
|
||||
"""Test that model_dump includes _type when _chain_type is implemented."""
|
||||
chain = FakeSavableChain()
|
||||
dumped = chain.model_dump()
|
||||
assert "_type" in dumped
|
||||
assert dumped["_type"] == "fake_savable"
|
||||
|
||||
|
||||
def test_model_dump_excludes_type_when_not_implemented() -> None:
|
||||
"""Test that model_dump omits _type when _chain_type raises."""
|
||||
chain = FakeChain()
|
||||
dumped = chain.model_dump()
|
||||
assert "_type" not in dumped
|
||||
|
||||
|
||||
def test_save_yaml(tmp_path: Path) -> None:
|
||||
"""Test that save() works for a chain that implements _chain_type."""
|
||||
chain = FakeSavableChain()
|
||||
file_path = tmp_path / "chain.yaml"
|
||||
chain.save(str(file_path))
|
||||
assert file_path.exists()
|
||||
import yaml
|
||||
|
||||
with file_path.open() as f:
|
||||
data = yaml.safe_load(f)
|
||||
assert data["_type"] == "fake_savable"
|
||||
|
||||
|
||||
def test_save_json(tmp_path: Path) -> None:
|
||||
"""Test that save() works with JSON format."""
|
||||
chain = FakeSavableChain()
|
||||
file_path = tmp_path / "chain.json"
|
||||
chain.save(str(file_path))
|
||||
assert file_path.exists()
|
||||
import json
|
||||
|
||||
with file_path.open() as f:
|
||||
data = json.load(f)
|
||||
assert data["_type"] == "fake_savable"
|
||||
|
||||
|
||||
def test_save_raises_when_chain_type_not_implemented(tmp_path: Path) -> None:
|
||||
"""Test that save() raises NotImplementedError for unsavable chains."""
|
||||
chain = FakeChain()
|
||||
with pytest.raises(NotImplementedError, match="does not support saving"):
|
||||
chain.save(str(tmp_path / "chain.yaml"))
|
||||
|
||||
Reference in New Issue
Block a user