deepset-ai/haystack · error · DeserializationError
Could not find attribute '{part}' in {container}
Error message
Could not find attribute '{part}' in {container} What it means
deserialize_callable walks the dotted path of a serialized callable and raised DeserializationError because an intermediate attribute 'part' could not be found via getattr on the current container. The serialized handle points to an attribute path that no longer exists on the target module/object.
Source
Thrown at haystack/utils/callable_serialization.py:119
try:
mod: Any = thread_safe_import(module_name)
except Exception:
# keep reducing i until we find a valid module import
continue
attr_value = mod
for part in parts[i:]:
# A handle legitimately walks `module.Class.method`, never into an object's internals.
# Refuse dunder/frame attributes (`__globals__`, `__dict__`, `__class__`, ...) before the
# getattr: `<func>.__globals__` yields a live module namespace (a gateway to the allowlist
# state and to `__builtins__`/`eval`) even though the traversal never leaves an allowlisted
# module, so neither the module allowlist nor the resolved-object checks below would catch it.
_check_traversable_attribute(part, callable_handle)
try:
attr_value = getattr(attr_value, part)
except AttributeError as e:
container = getattr(attr_value, "__name__", type(attr_value).__name__)
raise DeserializationError(f"Could not find attribute '{part}' in {container}") from e
# A crafted handle can walk through an object re-exported from an unallowlisted module and then reach a
# final callable whose own module is allowlisted. For example, an allowlisted Haystack module re-exports
# `rich.console.Console`; walking through that class to `Console._environ.update` ends at
# `collections.abc.MutableMapping.update`, hiding the unallowlisted `rich` hop from the final check below.
# Validate every object reached during traversal so no intermediate hop can escape the allowlist.
_check_resolved_module_allowed(attr_value, declared_module=module_name)
# when the attribute is a classmethod, we need the underlying function
if isinstance(attr_value, (classmethod, staticmethod)):
attr_value = attr_value.__func__
# Handle the case where @tool decorator replaced the function with a Tool object
if isinstance(attr_value, Tool):
attr_value = attr_value.function or attr_value.async_function
# Handle the case where @hook decorator replaced the function with a FunctionHook object
if isinstance(attr_value, FunctionHook):
attr_value = attr_value.function or attr_value.async_functionView on GitHub (pinned to e318778c9b)
Solutions
- Verify the dotted path exists in the installed version (python -c 'import ...' and getattr chain)
- Update the serialized pipeline's callable reference to the new path after a rename
- Pin the library version matching the serialized pipeline
- Check for typos in module and attribute names in the stored handle
Example fix
// before "splitting_function": "haystack.components.preprocessors.old_split" // after "splitting_function": "haystack.components.preprocessors.split" # updated to current path
Defensive patterns
Strategy: validation
Validate before calling
import importlib
def handle_resolves(handle: str) -> bool:
parts = handle.split(".")
obj = importlib.import_module(parts[0])
for part in parts[1:]:
if not hasattr(obj, part):
return False
obj = getattr(obj, part)
return True Type guard
import importlib
from collections.abc import Callable
def resolves_to_callable(handle: str) -> bool:
try:
parts = handle.split(".")
obj = importlib.import_module(parts[0])
for part in parts[1:]:
obj = getattr(obj, part)
except (ImportError, AttributeError):
return False
return callable(obj) Try / catch
try:
pipeline = Pipeline.loads(yaml_str)
except DeserializationError as e:
logger.error("callable path unresolvable: %s", e)
raise Prevention
- Check dotted paths still exist after upgrading haystack or custom packages
- Pin library versions for stored pipeline files or migrate handles on upgrade
- Validate callable handles in pipeline YAML in CI
When it happens
Trigger: Calling deserialize_callable or from_dict with a stored callable path like 'mypkg.utils.func' where 'utils' or 'func' was renamed/removed, or where the string was malformed.
Common situations: Library version upgrade renamed the function; pipeline YAML written with an older haystack version; typo in the stored callable path.
Related errors
- The final attribute is not callable: {attr_value}
- Refusing to deserialize an OutputAdapter with unsafe=True wh
- Refusing to deserialize an OutputAdapter with custom filters
- Missing 'type' in serialization data
- Failed to deserialize data '{payload}' into Pydantic model '
AI-assisted analysis of deepset-ai/haystack@e318778c9b (2026-08-30).
Data as JSON: /api/errors/3cbdf763e5286c9b.
Report an issue: GitHub.