deepset-ai / deepset-ai/haystack

DocumentJoiner positional weights follow Variadic arrival order, not connect() order

Open
#12,646 2 comments 0 reactions 1 assignee View on GitHub

@anakin87 is already working on this.

Since Sep 7, 2026.

Dominant language
Python
Stars
26.6k
Forks
3.2k
Avg merge
1d 3h
Merged PRs (30d)
194

Description

Describe the bug

DocumentJoiner weights=[w0, w1, …] are zipped positionally onto the Variadic documents list. That list order is not Pipeline.connect() order:

  • sync run: independent senders run in alphabetical component-name order, so weights bind to alphabetical arrival
  • run_async: completion/arrival order (nondeterministic)

Users reasonably assume connect(sparse); connect(dense) + weights=[sparse_w, dense_w] maps weights to those senders. The same graph + weights can therefore produce different hybrid scores.

Related: if a predecessor never runs (e.g. a ConditionalRouter branch is skipped), len(document_lists) < len(weights) and zip(..., strict=True) raises ValueError / PipelineRuntimeError.

To Reproduce

from haystack import Pipeline, Document, component
from haystack.components.joiners import DocumentJoiner

@component
class EmitDocs:
    def __init__(self, docs):
        self.docs = docs
    @component.output_types(documents=list[Document])
    def run(self):
        return {documents: self.docs}

p = Pipeline()
# alphabetical: dense < sparse  — opposite of connect order below
p.add_component(dense, EmitDocs([Document(content=shared, score=10.0)]))
p.add_component(sparse, EmitDocs([Document(content=shared, score=1.0)]))
p.add_component(joiner, DocumentJoiner(join_mode=merge, weights=[0.9, 0.1], sort_by_score=False))
p.connect(sparse, joiner)  # intended weight 0.9
p.connect(dense, joiner)   # intended weight 0.1

score = p.run({})[joiner][documents][0].score
print(score)
# actual today: 9.1  (dense*0.9 + sparse*0.1)
# expected if connect order: 1.9  (sparse*0.9 + dense*0.1)

Skipped-branch crash:

from haystack.components.routers import ConditionalRouter

# router -> branch_a / branch_b -> DocumentJoiner(weights=[0.5, 0.5])
# run with only branch_a selected -> zip length mismatch

Expected behavior

Weights follow connect() / InputSocket.senders order. Missing senders pad as empty lists so weighted merge/RRF does not crash.

Related

  • Closed discussion of alphabetical auto-variadic ordering: #10979 (auto-promoted list sockets; this issue is about explicit Variadic + DocumentJoiner weights)

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.