{"record":{"id":"0afd4ca1d1a19718","repo":"langchain-ai/langchain","slug":"expected-serializable-got-type-obj","errorCode":null,"errorMessage":"Expected Serializable, got {type(obj)}","messagePattern":"Expected Serializable, got (.+?)","errorType":"exception","errorClass":"TypeError","httpStatus":null,"severity":"error","filePath":"libs/core/langchain_core/load/_validation.py","lineNumber":148,"sourceCode":"        obj: The `Serializable` object to serialize.\n\n    Returns:\n        The serialized dict with user data in kwargs escaped as needed.\n\n    Note:\n        Kwargs values are processed with `_serialize_value` to escape user data\n        (like metadata) that contains `'lc'` keys. Secret fields are identified\n        by the class's declared `lc_secrets` and skipped because `to_json()`\n        already converted their values to secret markers.\n\n        The check is key-based rather than shape-based. A shape-based check\n        (\"this dict looks like a secret marker\") can be forged by user data,\n        letting attacker-controlled free-form dicts bypass escaping and reach\n        the Reviver.\n    \"\"\"\n    if not isinstance(obj, Serializable):\n        msg = f\"Expected Serializable, got {type(obj)}\"\n        raise TypeError(msg)\n\n    serialized: dict[str, Any] = dict(obj.to_json())\n\n    # Process kwargs to escape user data that could be confused with LC objects.\n    # Skip kwargs declared as secrets - `to_json()` already replaced their\n    # values with secret markers via `_replace_secrets`.\n    if serialized.get(\"type\") == \"constructor\" and \"kwargs\" in serialized:\n        secret_keys = _get_secret_keys(obj)\n        serialized[\"kwargs\"] = {\n            k: v if k in secret_keys else _serialize_value(v)\n            for k, v in serialized[\"kwargs\"].items()\n        }\n\n    return serialized\n\n\ndef _unescape_value(obj: Any) -> Any:\n    \"\"\"Unescape a value, processing escape markers in dict values and lists.","sourceCodeStart":130,"sourceCodeEnd":166,"githubUrl":"https://github.com/langchain-ai/langchain/blob/e32fa9a52eab3b61ad7a45399bfde59b3e580fc4/libs/core/langchain_core/load/_validation.py#L130-L166","documentation":"Raised by langchain_core.load._validation when attempting to serialize an object that is not an instance of Serializable. Only Serializable objects (which implement to_json) can be dumped by dumps/dumpd; arbitrary Python objects, plain dicts of non-serializable values, or pydantic models outside the Serializable hierarchy are rejected before reaching the JSON encoder.","triggerScenarios":"dumps({'model': SomePydanticModel(...)}) where the model does not extend Serializable; dumps(some_custom_object); nesting a plain object inside otherwise-serializable kwargs. Note pydantic models are first converted by _dump_pydantic_models, so the failure is specific to non-pydantic, non-Serializable objects.","commonSituations":"Trying to serialize a third-party object (e.g. an SDK client or tokenizer) stored on a chain; mixing LangChain serialization (dumps) with generic JSON dumping expectations; version upgrades where a class stopped inheriting Serializable.","solutions":["Pass only Serializable/JSON-native values to dumps; store non-serializable objects outside serialized fields or as serializable identifiers","If the object is pydantic but not Serializable, convert it to a dict first (e.g. obj.model_dump())","For truly custom classes, subclass langchain_core.load.serializable.Serializable and implement to_json"],"exampleFix":"# before\nfrom langchain_core.load import dumps\ndumps({'retriever': custom_retriever_obj})\n# after\nfrom langchain_core.load import dumps\ndumps({'retriever_name': custom_retriever_obj.name})","handlingStrategy":"type-guard","validationCode":"from langchain_core.load.serializable import Serializable\ndef assert_serializable_tree(o: object) -> None:\n    if isinstance(o, dict):\n        for v in o.values(): assert_serializable_tree(v)\n    elif isinstance(o, (list, tuple)):\n        for v in o: assert_serializable_tree(v)\n    elif o is not None and not isinstance(o, (str, int, float, bool, Serializable)):\n        raise TypeError(f'not serializable: {type(o)!r}')","typeGuard":"from langchain_core.load.serializable import Serializable\ndef is_serializable(o: object) -> bool:\n    return o is None or isinstance(o, (str, int, float, bool, Serializable)) or hasattr(o, 'to_json')","tryCatchPattern":"try:\n    dumps(obj)\nexcept TypeError as e:\n    if 'Expected Serializable' in str(e):\n        obj = strip_or_convert(obj)  # replace offending values with dicts/primitives\n        dumps(obj)\n    else:\n        raise","preventionTips":["Never store live clients, tokenizers, or arbitrary SDK objects in fields that get dumped","Convert pydantic models with .model_dump() before nesting them into dumps inputs","Run dumps() in a smoke test for every object type you persist"],"tags":["serialization","dump","type-error"],"backgroundTag":null,"analyzedSha":"e32fa9a52eab3b61ad7a45399bfde59b3e580fc4","analyzedAt":"2026-08-14T18:42:09.092Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}