Skip to content

Commit 535ba43

Browse files
jacoblee93mdrxy
andauthored
feat(core): add an option to make deserialization more permissive (#32054)
## Description Currently when deserializing objects that contain non-deserializable values, we throw an error. However, there are cases (e.g. proxies that return response fields containing extra fields like Python datetimes), where these values are not important and we just want to drop them. Twitter handle: @Hacubu --------- Co-authored-by: Mason Daugherty <[email protected]>
1 parent 3628dcc commit 535ba43

File tree

2 files changed

+67
-2
lines changed

2 files changed

+67
-2
lines changed

libs/core/langchain_core/load/load.py

Lines changed: 23 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,8 @@ def __init__(
5656
additional_import_mappings: Optional[
5757
dict[tuple[str, ...], tuple[str, ...]]
5858
] = None,
59+
*,
60+
ignore_unserializable_fields: bool = False,
5961
) -> None:
6062
"""Initialize the reviver.
6163
@@ -70,6 +72,8 @@ def __init__(
7072
additional_import_mappings: A dictionary of additional namespace mappings
7173
You can use this to override default mappings or add new mappings.
7274
Defaults to None.
75+
ignore_unserializable_fields: Whether to ignore unserializable fields.
76+
Defaults to False.
7377
"""
7478
self.secrets_from_env = secrets_from_env
7579
self.secrets_map = secrets_map or {}
@@ -88,6 +92,7 @@ def __init__(
8892
if self.additional_import_mappings
8993
else ALL_SERIALIZABLE_MAPPINGS
9094
)
95+
self.ignore_unserializable_fields = ignore_unserializable_fields
9196

9297
def __call__(self, value: dict[str, Any]) -> Any:
9398
"""Revive the value."""
@@ -108,6 +113,8 @@ def __call__(self, value: dict[str, Any]) -> Any:
108113
and value.get("type") == "not_implemented"
109114
and value.get("id") is not None
110115
):
116+
if self.ignore_unserializable_fields:
117+
return None
111118
msg = (
112119
"Trying to load an object that doesn't implement "
113120
f"serialization: {value}"
@@ -170,6 +177,7 @@ def loads(
170177
valid_namespaces: Optional[list[str]] = None,
171178
secrets_from_env: bool = True,
172179
additional_import_mappings: Optional[dict[tuple[str, ...], tuple[str, ...]]] = None,
180+
ignore_unserializable_fields: bool = False,
173181
) -> Any:
174182
"""Revive a LangChain class from a JSON string.
175183
@@ -187,14 +195,20 @@ def loads(
187195
additional_import_mappings: A dictionary of additional namespace mappings
188196
You can use this to override default mappings or add new mappings.
189197
Defaults to None.
198+
ignore_unserializable_fields: Whether to ignore unserializable fields.
199+
Defaults to False.
190200
191201
Returns:
192202
Revived LangChain objects.
193203
"""
194204
return json.loads(
195205
text,
196206
object_hook=Reviver(
197-
secrets_map, valid_namespaces, secrets_from_env, additional_import_mappings
207+
secrets_map,
208+
valid_namespaces,
209+
secrets_from_env,
210+
additional_import_mappings,
211+
ignore_unserializable_fields=ignore_unserializable_fields,
198212
),
199213
)
200214

@@ -207,6 +221,7 @@ def load(
207221
valid_namespaces: Optional[list[str]] = None,
208222
secrets_from_env: bool = True,
209223
additional_import_mappings: Optional[dict[tuple[str, ...], tuple[str, ...]]] = None,
224+
ignore_unserializable_fields: bool = False,
210225
) -> Any:
211226
"""Revive a LangChain class from a JSON object.
212227
@@ -225,12 +240,18 @@ def load(
225240
additional_import_mappings: A dictionary of additional namespace mappings
226241
You can use this to override default mappings or add new mappings.
227242
Defaults to None.
243+
ignore_unserializable_fields: Whether to ignore unserializable fields.
244+
Defaults to False.
228245
229246
Returns:
230247
Revived LangChain objects.
231248
"""
232249
reviver = Reviver(
233-
secrets_map, valid_namespaces, secrets_from_env, additional_import_mappings
250+
secrets_map,
251+
valid_namespaces,
252+
secrets_from_env,
253+
additional_import_mappings,
254+
ignore_unserializable_fields=ignore_unserializable_fields,
234255
)
235256

236257
def _load(obj: Any) -> Any:

libs/core/tests/unit_tests/load/test_serializable.py

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -232,3 +232,47 @@ class MyModel(BaseModel):
232232
def test_serialization_with_generation() -> None:
233233
generation = Generation(text="hello-world")
234234
assert dumpd(generation)["kwargs"] == {"text": "hello-world", "type": "Generation"}
235+
236+
237+
def test_serialization_with_ignore_unserializable_fields() -> None:
238+
data = {
239+
"messages": [
240+
[
241+
{
242+
"lc": 1,
243+
"type": "constructor",
244+
"id": ["langchain", "schema", "messages", "AIMessage"],
245+
"kwargs": {
246+
"content": "Call tools to get entity details",
247+
"response_metadata": {
248+
"other_field": "foo",
249+
"create_date": {
250+
"lc": 1,
251+
"type": "not_implemented",
252+
"id": ["datetime", "datetime"],
253+
"repr": "datetime.datetime(2025, 7, 15, 13, 14, 0, 000000, tzinfo=datetime.timezone.utc)", # noqa: E501
254+
},
255+
},
256+
"type": "ai",
257+
"id": "00000000-0000-0000-0000-000000000000",
258+
},
259+
},
260+
]
261+
]
262+
}
263+
ser = dumpd(data)
264+
deser = load(ser, ignore_unserializable_fields=True)
265+
assert deser == {
266+
"messages": [
267+
[
268+
AIMessage(
269+
id="00000000-0000-0000-0000-000000000000",
270+
content="Call tools to get entity details",
271+
response_metadata={
272+
"other_field": "foo",
273+
"create_date": None,
274+
},
275+
)
276+
]
277+
]
278+
}

0 commit comments

Comments
 (0)