_deepcopy_with_exceptions turns dict subclasses (defaultdict, OrderedDict, Counter) into plain dict, so pipeline inputs lose their type
Maintainers usually reply within 1 day
@anakin87 is already working on this.
Since Oct 3, 2026.
Assessment
- Difficulty
- 2/5
- Estimated time
- 1-3 hours
- Newbie friendliness
- 78/100
Research direction
Start in haystack/core/pipeline/utils.py at _deepcopy_with_exceptions, then inspect the input-copying calls in pipeline.py and base.py. Add focused coverage for OrderedDict, defaultdict, and Counter, including defaultdict missing-key behavior, and run the relevant pipeline and utility tests. Done means dict subclasses retain their type and state when passed through Pipeline.run.
Written by the indexing model from the issue text.
Description
Describe the bug
_deepcopy_with_exceptions in haystack/core/pipeline/utils.py rebuilds every dict as a plain dict:
if isinstance(obj, dict):
return {k: _deepcopy_with_exceptions(v) for k, v in obj.items()}
For list, tuple and set the function keeps the original type (type(obj)(...)), and #11981 added the same care for namedtuples. Dict subclasses do not get that treatment. A defaultdict, OrderedDict or Counter comes back as a plain dict, and a defaultdict loses its default_factory.
Pipeline.run copies its inputs with this function (pipeline.py and base.py call it on the input data), so a component receives a different type than the caller passed in.
Error message and reproduction
Direct call:
from collections import OrderedDict, defaultdict
from haystack.core.pipeline.utils import _deepcopy_with_exceptions as d
print(type(d(OrderedDict(a=1))).__name__) # dict
print(type(d(defaultdict(list, a=[1]))).__name__) # dict
d(defaultdict(list))["missing"] # KeyError: 'missing'
Through a pipeline:
from collections import defaultdict
from haystack import Pipeline, component
@component
class Lookup:
@component.output_types(v=int)
def run(self, d: dict):
return {"v": len(d["absent"])}
p = Pipeline()
p.add_component("d", Lookup())
p.run({"d": {"d": defaultdict(list)}})
# PipelineRuntimeError ... Error: 'absent'
Calling Lookup().run(d=defaultdict(list)) directly returns {"v": 0}. Only the pipeline run fails, because the input was converted to a plain dict on the way in.
I ran both snippets twice and got identical output each time.
Expected behavior
The copy keeps the type of a dict subclass, the same way it already does for lists, tuples, sets and namedtuples.
Possible fix
For dict subclasses, shallow copy the container so its type and state are kept, then fill it with copied values:
if isinstance(obj, dict):
if type(obj) is dict:
return {k: _deepcopy_with_exceptions(v) for k, v in obj.items()}
try:
new = copy.copy(obj)
new.clear()
for k, v in obj.items():
new[k] = _deepcopy_with_exceptions(v)
return new
except Exception:
return {k: _deepcopy_with_exceptions(v) for k, v in obj.items()}
I tried this on a local copy. OrderedDict, defaultdict and Counter all keep their type, compare equal to the original, and the defaultdict still creates missing keys. I did not run the full test suite. Given the current Haystack contribution state I am leaving this as an issue and am happy to send a PR if a maintainer wants one.
Additional context
Checked against main at 87dba65; haystack/core/pipeline/utils.py there is identical to the file I tested. Python 3.14 locally, Haystack from main. I searched open and closed issues and PRs for this function and for dict subclass copying and found only #11981 (namedtuples), which this is the sibling of.
Disclosure: I used an AI coding assistant to help find and write this up. I ran the reproductions myself and checked the output.
- Dominant language
- Python
- Stars
- 26.8k
- Forks
- 3.3k
- Avg merge
- 1d 10h
- Merged PRs (30d)
- 260
Getting set up
- No Dockerfile or Docker Compose file
- Has a pull request template
- Read the contributing guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
More from deepset-ai/haystack
-
CSVToDocument accepts an unsupported conversion_mode and silently converts in row modePossibly taken @Lesereingrape claimed this 17 days ago. OpenP3
Difficulty 2/5 1-3 hours Newbie friendliness 88/100
deepset-ai/haystack#13070 ·
Maintainers usually reply within 1 day
-
PPTXToDocument drops soft line breaks when link_format is markdown or plainPossibly taken @bogdankostic claimed this 7 days ago. OpenP3
Difficulty 2/5 1-3 hours Newbie friendliness 78/100
deepset-ai/haystack#13043 · 1 comment · 1 assignee ·
Maintainers usually reply within 1 day
-
InMemoryDocumentStore: count_unique_metadata_by_filter and get_metadata_field_unique_values disagree on int/float/bool valuesPossibly taken @julian-risch claimed this 17 days ago. OpenP3
Difficulty 2/5 1-3 hours Newbie friendliness 78/100
deepset-ai/haystack#13040 · 1 comment · 1 assignee ·
Maintainers usually reply within 1 day
-
AutoMergingRetriever raises ValueError when a matched document has no parent (for example the root document)Possibly taken @julian-risch claimed this 19 days ago. Open
Difficulty 2/5 1-3 hours Newbie friendliness 82/100
deepset-ai/haystack#13029 · 2 comments · 1 assignee ·
Maintainers usually reply within 1 day
-
CSVToDocument row mode fails on CR-separated CSV inputPossibly taken @julian-risch claimed this 18 days ago. OpenP3
Difficulty 2/5 1-3 hours Newbie friendliness 86/100
deepset-ai/haystack#13022 · 1 comment · 1 assignee ·
Maintainers usually reply within 1 day
All issues in deepset-ai/haystack
Similar issues
-
bug
Difficulty 2/5 1-3 hours Newbie friendliness 79/100
Maintainers usually reply within 1 day
-
Difficulty 2/5 1-3 hours Newbie friendliness 78/100
mohitmishra786/readprism#322 ·
Maintainers usually reply within 1 day
-
good first issue
Difficulty 2/5 1-3 hours Newbie friendliness 76/100
-
Difficulty 1/5 Under an hour Newbie friendliness 75/100
Maintainers usually reply within 1 day
-
API-key credentials accept a blank key: empty via the API, spaces-only via the builder's Add API key dialogPossibly taken A pull request linked to this issue is open or already merged. Open
Difficulty 2/5 1-3 hours Newbie friendliness 77/100
Significant-Gravitas/AutoGPT#15727 ·
Maintainers usually reply within 1 day