chore: import upstream snapshot with attribution
CodeQL / Analyze (python) (push) Has been cancelled
Update Platform Components Table / update (push) Has been cancelled
Docker image release / Build base image (push) Has been cancelled
Sync docs with Docusaurus / sync (push) Has been cancelled
Tests / Check if changed (push) Has been cancelled
Tests / format (push) Has been cancelled
Tests / check-imports (push) Has been cancelled
Tests / Unit / macos-latest (push) Has been cancelled
Tests / Unit / ubuntu-latest (push) Has been cancelled
Tests / Unit / windows-latest (push) Has been cancelled
Tests / mypy (push) Has been cancelled
Tests / Integration / ubuntu-latest (push) Has been cancelled
Tests / Integration / macos-latest (push) Has been cancelled
Tests / Integration / windows-latest (push) Has been cancelled
Tests / notify-slack-on-failure (push) Has been cancelled
Tests / Mark tests as completed (push) Has been cancelled

This commit is contained in:
wehub-resource-sync
2026-07-13 13:22:28 +08:00
commit c56bef871b
9296 changed files with 1854228 additions and 0 deletions
+3
View File
@@ -0,0 +1,3 @@
# SPDX-FileCopyrightText: 2022-present deepset GmbH <info@deepset.ai>
#
# SPDX-License-Identifier: Apache-2.0
@@ -0,0 +1,130 @@
# SPDX-FileCopyrightText: 2022-present deepset GmbH <info@deepset.ai>
#
# SPDX-License-Identifier: Apache-2.0
import pytest
from haystack import Document
from haystack.components.joiners.answer_joiner import AnswerJoiner, JoinMode
from haystack.dataclasses.answer import ExtractedAnswer, GeneratedAnswer
class TestAnswerJoiner:
def test_init(self):
joiner = AnswerJoiner()
assert joiner.join_mode == JoinMode.CONCATENATE
assert joiner.top_k is None
assert joiner.sort_by_score is False
def test_init_with_custom_parameters(self):
joiner = AnswerJoiner(join_mode="concatenate", top_k=5, sort_by_score=True)
assert joiner.join_mode == JoinMode.CONCATENATE
assert joiner.top_k == 5
assert joiner.sort_by_score is True
def test_to_dict(self):
joiner = AnswerJoiner()
data = joiner.to_dict()
assert data == {
"type": "haystack.components.joiners.answer_joiner.AnswerJoiner",
"init_parameters": {"join_mode": "concatenate", "top_k": None, "sort_by_score": False},
}
def test_to_from_dict_custom_parameters(self):
joiner = AnswerJoiner("concatenate", top_k=5, sort_by_score=True)
data = joiner.to_dict()
assert data == {
"type": "haystack.components.joiners.answer_joiner.AnswerJoiner",
"init_parameters": {"join_mode": "concatenate", "top_k": 5, "sort_by_score": True},
}
deserialized_joiner = AnswerJoiner.from_dict(data)
assert deserialized_joiner.join_mode == JoinMode.CONCATENATE
assert deserialized_joiner.top_k == 5
assert deserialized_joiner.sort_by_score is True
def test_from_dict(self):
data = {"type": "haystack.components.joiners.answer_joiner.AnswerJoiner", "init_parameters": {}}
answer_joiner = AnswerJoiner.from_dict(data)
assert answer_joiner.join_mode == JoinMode.CONCATENATE
assert answer_joiner.top_k is None
assert answer_joiner.sort_by_score is False
def test_from_dict_customs_parameters(self):
data = {
"type": "haystack.components.joiners.answer_joiner.AnswerJoiner",
"init_parameters": {"join_mode": "concatenate", "top_k": 5, "sort_by_score": True},
}
answer_joiner = AnswerJoiner.from_dict(data)
assert answer_joiner.join_mode == JoinMode.CONCATENATE
assert answer_joiner.top_k == 5
assert answer_joiner.sort_by_score is True
def test_empty_list(self):
joiner = AnswerJoiner()
result = joiner.run([])
assert result == {"answers": []}
def test_list_of_empty_lists(self):
joiner = AnswerJoiner()
result = joiner.run([[], []])
assert result == {"answers": []}
def test_list_of_single_answer(self):
joiner = AnswerJoiner()
answers = [
GeneratedAnswer(query="a", data="a", meta={}, documents=[Document(content="a")]),
GeneratedAnswer(query="b", data="b", meta={}, documents=[Document(content="b")]),
GeneratedAnswer(query="c", data="c", meta={}, documents=[Document(content="c")]),
]
result = joiner.run([answers])
assert result == {"answers": answers}
def test_two_lists_of_generated_answers(self):
joiner = AnswerJoiner()
answers1 = [GeneratedAnswer(query="a", data="a", meta={}, documents=[Document(content="a")])]
answers2 = [GeneratedAnswer(query="d", data="d", meta={}, documents=[Document(content="d")])]
result = joiner.run([answers1, answers2])
assert result == {"answers": answers1 + answers2}
def test_multiple_lists_of_mixed_answers(self):
joiner = AnswerJoiner()
answers1 = [GeneratedAnswer(query="a", data="a", meta={}, documents=[Document(content="a")])]
answers2 = [ExtractedAnswer(query="d", score=0.9, meta={}, document=Document(content="d"))]
answers3 = [GeneratedAnswer(query="f", data="f", meta={}, documents=[Document(content="f")])]
all_answers = answers1 + answers2 + answers3 # type: ignore
result = joiner.run([answers1, answers2, answers3])
assert result == {"answers": all_answers}
def test_unsupported_join_mode(self):
unsupported_mode = "unsupported_mode"
with pytest.raises(ValueError):
AnswerJoiner(join_mode=unsupported_mode)
def test_sort_by_score(self):
joiner = AnswerJoiner(sort_by_score=True)
answers1 = [ExtractedAnswer(query="a", score=0.3, meta={}, document=Document(content="a"))]
answers2 = [ExtractedAnswer(query="b", score=0.9, meta={}, document=Document(content="b"))]
result = joiner.run([answers1, answers2])
scores = [answer.score for answer in result["answers"]]
assert scores == [0.9, 0.3]
def test_sort_by_score_with_none_score(self):
# The docstring promises that an answer with no score is handled as if its score is -infinity.
# ExtractedAnswer with score=None must not raise a TypeError during sorting and must be sorted last.
joiner = AnswerJoiner(sort_by_score=True)
answers1 = [ExtractedAnswer(query="a", score=0.5, meta={}, document=Document(content="a"))]
answers2 = [ExtractedAnswer(query="b", score=None, meta={}, document=Document(content="b"))] # type: ignore[arg-type]
result = joiner.run([answers1, answers2])
assert [answer.data for answer in result["answers"]] == [None, None]
assert [answer.score for answer in result["answers"]] == [0.5, None]
def test_sort_by_score_with_answers_missing_score_attribute(self):
# GeneratedAnswer has no score attribute at all; it must be handled as -infinity and sorted last.
joiner = AnswerJoiner(sort_by_score=True)
answers1 = [GeneratedAnswer(query="a", data="a", meta={}, documents=[Document(content="a")])]
answers2 = [ExtractedAnswer(query="b", score=0.9, meta={}, document=Document(content="b"))]
result = joiner.run([answers1, answers2])
# The ExtractedAnswer (score 0.9) comes first, the GeneratedAnswer (no score) comes last.
assert isinstance(result["answers"][0], ExtractedAnswer)
assert isinstance(result["answers"][1], GeneratedAnswer)
@@ -0,0 +1,36 @@
# SPDX-FileCopyrightText: 2022-present deepset GmbH <info@deepset.ai>
#
# SPDX-License-Identifier: Apache-2.0
import pytest
from haystack.components.joiners import BranchJoiner
class TestBranchJoiner:
def test_one_value(self):
joiner = BranchJoiner(int)
output = joiner.run(value=[2])
assert output == {"value": 2}
def test_one_value_of_wrong_type(self):
# BranchJoiner does not type check the input
joiner = BranchJoiner(int)
output = joiner.run(value=["hello"])
assert output == {"value": "hello"}
def test_one_value_of_none_type(self):
# BranchJoiner does not type check the input
joiner = BranchJoiner(int)
output = joiner.run(value=[None])
assert output == {"value": None}
def test_more_values_of_expected_type(self):
joiner = BranchJoiner(int)
with pytest.raises(ValueError, match="BranchJoiner expects only one input, but 3 were received."):
joiner.run(value=[2, 3, 4])
def test_no_values(self):
joiner = BranchJoiner(int)
with pytest.raises(ValueError, match="BranchJoiner expects only one input, but 0 were received."):
joiner.run(value=[])
@@ -0,0 +1,310 @@
# SPDX-FileCopyrightText: 2022-present deepset GmbH <info@deepset.ai>
#
# SPDX-License-Identifier: Apache-2.0
import logging
import re
import pytest
from haystack import Document
from haystack.components.joiners.document_joiner import DocumentJoiner, JoinMode
class TestDocumentJoiner:
def test_init(self):
joiner = DocumentJoiner()
assert joiner.join_mode == JoinMode.CONCATENATE
assert joiner.weights is None
assert joiner.top_k is None
assert joiner.sort_by_score
def test_init_with_custom_parameters(self):
joiner = DocumentJoiner(join_mode="merge", weights=[0.4, 0.6], top_k=5, sort_by_score=False)
assert joiner.join_mode == JoinMode.MERGE
assert joiner.weights == [0.4, 0.6]
assert joiner.top_k == 5
assert not joiner.sort_by_score
def test_init_with_zero_sum_weights_raises(self):
# weights that sum to zero would divide by zero during normalization
with pytest.raises(ValueError, match="must not sum to zero"):
DocumentJoiner(join_mode="merge", weights=[0.0, 0.0, 0.0])
def test_to_dict(self):
joiner = DocumentJoiner()
data = joiner.to_dict()
assert data == {
"type": "haystack.components.joiners.document_joiner.DocumentJoiner",
"init_parameters": {"join_mode": "concatenate", "sort_by_score": True, "top_k": None, "weights": None},
}
def test_to_dict_custom_parameters(self):
joiner = DocumentJoiner("merge", weights=[0.4, 0.6], top_k=4, sort_by_score=False)
data = joiner.to_dict()
assert data == {
"type": "haystack.components.joiners.document_joiner.DocumentJoiner",
"init_parameters": {"join_mode": "merge", "weights": [0.4, 0.6], "top_k": 4, "sort_by_score": False},
}
def test_from_dict(self):
data = {"type": "haystack.components.joiners.document_joiner.DocumentJoiner", "init_parameters": {}}
document_joiner = DocumentJoiner.from_dict(data)
assert document_joiner.join_mode == JoinMode.CONCATENATE
assert document_joiner.weights is None
assert document_joiner.top_k is None
assert document_joiner.sort_by_score
def test_from_dict_customs_parameters(self):
data = {
"type": "haystack.components.joiners.document_joiner.DocumentJoiner",
"init_parameters": {"join_mode": "merge", "weights": [0.5, 0.6], "top_k": 6, "sort_by_score": False},
}
document_joiner = DocumentJoiner.from_dict(data)
assert document_joiner.join_mode == JoinMode.MERGE
assert document_joiner.weights == pytest.approx([0.5, 0.6], rel=0.1)
assert document_joiner.top_k == 6
assert not document_joiner.sort_by_score
@pytest.mark.parametrize(
"join_mode",
[
JoinMode.CONCATENATE,
JoinMode.MERGE,
JoinMode.RECIPROCAL_RANK_FUSION,
JoinMode.DISTRIBUTION_BASED_RANK_FUSION,
],
)
def test_empty_list(self, join_mode: JoinMode):
joiner = DocumentJoiner(join_mode=join_mode)
result = joiner.run([])
assert result == {"documents": []}
@pytest.mark.parametrize(
"join_mode",
[
JoinMode.CONCATENATE,
JoinMode.MERGE,
JoinMode.RECIPROCAL_RANK_FUSION,
JoinMode.DISTRIBUTION_BASED_RANK_FUSION,
],
)
def test_list_of_empty_lists(self, join_mode: JoinMode):
joiner = DocumentJoiner(join_mode=join_mode)
result = joiner.run([[], []])
assert result == {"documents": []}
@pytest.mark.parametrize(
"join_mode",
[
JoinMode.CONCATENATE,
JoinMode.MERGE,
JoinMode.RECIPROCAL_RANK_FUSION,
JoinMode.DISTRIBUTION_BASED_RANK_FUSION,
],
)
def test_list_with_one_empty_list(self, join_mode: JoinMode):
joiner = DocumentJoiner(join_mode=join_mode)
documents = [Document(content="a"), Document(content="b"), Document(content="c")]
result = joiner.run([[], documents])
# Verify the same documents are returned (scoring functions assign scores to the results;
# compare by ID to avoid relying on in-place score mutation of the input list).
result_ids = {doc.id for doc in result["documents"]}
expected_ids = {doc.id for doc in documents}
assert result_ids == expected_ids
def test_unsupported_join_mode(self):
unsupported_mode = "unsupported_mode"
expected_error_pattern = (
re.escape(f"Unknown join mode '{unsupported_mode}'") + r".*Supported modes in DocumentJoiner are: \[.*\]"
)
with pytest.raises(ValueError, match=expected_error_pattern):
DocumentJoiner(join_mode=unsupported_mode)
def test_run_with_concatenate_join_mode_and_top_k(self):
joiner = DocumentJoiner(top_k=6)
documents_1 = [Document(content="a"), Document(content="b"), Document(content="c")]
documents_2 = [
Document(content="d"),
Document(content="e"),
Document(content="f", meta={"key": "value"}),
Document(content="g"),
]
output = joiner.run([documents_1, documents_2])
assert len(output["documents"]) == 6
assert sorted(documents_1 + documents_2[:-1], key=lambda d: d.id) == sorted(
output["documents"], key=lambda d: d.id
)
def test_run_with_concatenate_join_mode_and_duplicate_documents(self):
joiner = DocumentJoiner()
documents_1 = [Document(content="a", score=0.3), Document(content="b"), Document(content="c")]
documents_2 = [
Document(content="a", score=0.2),
Document(content="a"),
Document(content="f", meta={"key": "value"}),
]
output = joiner.run([documents_1, documents_2])
assert len(output["documents"]) == 4
assert sorted(documents_1 + [documents_2[-1]], key=lambda d: d.id) == sorted(
output["documents"], key=lambda d: d.id
)
def test_run_with_concatenate_join_mode_keeps_zero_score_over_negative_duplicate(self):
joiner = DocumentJoiner(sort_by_score=False)
documents_1 = [Document(content="a", score=0.0)]
documents_2 = [Document(content="a", score=-0.5)]
output = joiner.run([documents_1, documents_2])
assert len(output["documents"]) == 1
assert output["documents"][0].score == 0.0
def test_run_with_concatenate_join_mode_keeps_zero_score_over_none_duplicate(self):
joiner = DocumentJoiner(sort_by_score=False)
documents_1 = [Document(content="a", score=0.0)]
documents_2 = [Document(content="a")]
output = joiner.run([documents_1, documents_2])
assert len(output["documents"]) == 1
assert output["documents"][0].score == 0.0
def test_run_with_merge_join_mode_handles_zero_score(self):
joiner = DocumentJoiner(join_mode="merge", weights=[0.5, 0.5])
documents_1 = [Document(content="a", score=0.0)]
documents_2 = [Document(content="a", score=0.0)]
output = joiner.run([documents_1, documents_2])
assert output["documents"][0].score == 0.0
def test_run_with_merge_join_mode(self):
joiner = DocumentJoiner(join_mode="merge", weights=[1.5, 0.5])
documents_1 = [Document(content="a", score=1.0), Document(content="b", score=2.0)]
documents_2 = [
Document(content="a", score=0.5),
Document(content="b", score=3.0),
Document(content="f", score=4.0, meta={"key": "value"}),
]
output = joiner.run([documents_1, documents_2])
assert len(output["documents"]) == 3
expected_document_ids = [
doc.id
for doc in [
Document(content="a", score=1.25),
Document(content="b", score=2.25),
Document(content="f", score=4.0, meta={"key": "value"}),
]
]
assert all(doc.id in expected_document_ids for doc in output["documents"])
def test_run_with_reciprocal_rank_fusion_join_mode(self):
joiner = DocumentJoiner(join_mode="reciprocal_rank_fusion")
documents_1 = [Document(content="a"), Document(content="b"), Document(content="c")]
documents_2 = [
Document(content="b", score=1000.0),
Document(content="c"),
Document(content="a"),
Document(content="f", meta={"key": "value"}),
]
output = joiner.run([documents_1, documents_2])
assert len(output["documents"]) == 4
expected_document_ids = [
doc.id
for doc in [
Document(content="b"),
Document(content="a"),
Document(content="c"),
Document(content="f", meta={"key": "value"}),
]
]
assert all(doc.id in expected_document_ids for doc in output["documents"])
def test_run_with_distribution_based_rank_fusion_join_mode(self):
joiner = DocumentJoiner(join_mode="distribution_based_rank_fusion")
documents_1 = [
Document(content="a", score=0.6),
Document(content="b", score=0.2),
Document(content="c", score=0.5),
]
documents_2 = [
Document(content="d", score=0.5),
Document(content="e", score=0.8),
Document(content="f", score=1.1, meta={"key": "value"}),
Document(content="g", score=0.3),
Document(content="a", score=0.3),
]
output = joiner.run([documents_1, documents_2])
assert len(output["documents"]) == 7
expected_document_ids = [
doc.id
for doc in [
Document(content="a", score=0.66),
Document(content="b", score=0.27),
Document(content="c", score=0.56),
Document(content="d", score=0.44),
Document(content="e", score=0.60),
Document(content="f", score=0.76, meta={"key": "value"}),
Document(content="g", score=0.33),
]
]
assert all(doc.id in expected_document_ids for doc in output["documents"])
def test_run_with_distribution_based_rank_fusion_join_mode_same_scores(self):
joiner = DocumentJoiner(join_mode="distribution_based_rank_fusion")
documents_1 = [
Document(content="a", score=0.2),
Document(content="b", score=0.2),
Document(content="c", score=0.2),
]
documents_2 = [
Document(content="d", score=0.5),
Document(content="e", score=0.8),
Document(content="f", score=1.1, meta={"key": "value"}),
Document(content="g", score=0.3),
Document(content="a", score=0.3),
]
output = joiner.run([documents_1, documents_2])
assert len(output["documents"]) == 7
expected_document_ids = [
doc.id
for doc in [
Document(content="a", score=0),
Document(content="b", score=0),
Document(content="c", score=0),
Document(content="d", score=0.44),
Document(content="e", score=0.60),
Document(content="f", score=0.76, meta={"key": "value"}),
Document(content="g", score=0.33),
]
]
assert all(doc.id in expected_document_ids for doc in output["documents"])
def test_run_with_distribution_based_rank_fusion_join_mode_with_none_score(self):
# Documents with score=None (e.g. from a non-scoring source) must not crash DBSF;
# a missing score is treated as 0, consistent with how the statistics are computed.
joiner = DocumentJoiner(join_mode="distribution_based_rank_fusion")
documents_1 = [Document(content="a", score=0.6), Document(content="b", score=None)]
documents_2 = [Document(content="c", score=0.5), Document(content="d", score=0.3)]
output = joiner.run([documents_1, documents_2])
assert len(output["documents"]) == 4
assert all(doc.score is not None for doc in output["documents"])
def test_run_with_top_k_in_run_method(self):
joiner = DocumentJoiner()
documents_1 = [Document(content="a"), Document(content="b"), Document(content="c")]
documents_2 = [Document(content="d"), Document(content="e"), Document(content="f")]
top_k = 4
output = joiner.run([documents_1, documents_2], top_k=top_k)
assert len(output["documents"]) == top_k
def test_sort_by_score_without_scores(self, caplog):
joiner = DocumentJoiner()
with caplog.at_level(logging.INFO):
documents = [Document(content="a"), Document(content="b", score=0.5)]
output = joiner.run([documents])
assert "those with score=None were sorted as if they had a score of -infinity" in caplog.text
assert output["documents"] == documents[::-1]
def test_output_documents_not_sorted_by_score(self):
joiner = DocumentJoiner(sort_by_score=False)
documents_1 = [Document(content="a", score=0.1)]
documents_2 = [Document(content="d", score=0.2)]
output = joiner.run([documents_1, documents_2])
assert output["documents"] == documents_1 + documents_2
+172
View File
@@ -0,0 +1,172 @@
# SPDX-FileCopyrightText: 2022-present deepset GmbH <info@deepset.ai>
#
# SPDX-License-Identifier: Apache-2.0
from typing import List
import pytest
from haystack import Document, Pipeline
from haystack.components.builders import AnswerBuilder, ChatPromptBuilder
from haystack.components.embedders import OpenAITextEmbedder
from haystack.components.generators.chat.openai import OpenAIChatGenerator
from haystack.components.joiners.list_joiner import ListJoiner
from haystack.core.errors import PipelineConnectError
from haystack.dataclasses import ChatMessage
from haystack.dataclasses.answer import GeneratedAnswer
from haystack.utils.auth import Secret
class TestListJoiner:
def test_init(self):
joiner = ListJoiner(list[ChatMessage])
assert isinstance(joiner, ListJoiner)
assert joiner.list_type_ == list[ChatMessage]
def test_init_typing_list(self):
joiner = ListJoiner(List[ChatMessage])
assert isinstance(joiner, ListJoiner)
assert joiner.list_type_ == List[ChatMessage]
def test_to_dict_defaults(self):
joiner = ListJoiner()
data = joiner.to_dict()
assert data == {
"type": "haystack.components.joiners.list_joiner.ListJoiner",
"init_parameters": {"list_type_": None},
}
def test_to_dict_non_default(self):
joiner = ListJoiner(list[ChatMessage])
data = joiner.to_dict()
assert data == {
"type": "haystack.components.joiners.list_joiner.ListJoiner",
"init_parameters": {"list_type_": "list[haystack.dataclasses.chat_message.ChatMessage]"},
}
def test_to_dict_non_default_typing_list(self):
joiner = ListJoiner(List[ChatMessage])
data = joiner.to_dict()
assert data == {
"type": "haystack.components.joiners.list_joiner.ListJoiner",
"init_parameters": {"list_type_": "typing.List[haystack.dataclasses.chat_message.ChatMessage]"},
}
def test_from_dict_default(self):
data = {"type": "haystack.components.joiners.list_joiner.ListJoiner", "init_parameters": {"list_type_": None}}
list_joiner = ListJoiner.from_dict(data)
assert isinstance(list_joiner, ListJoiner)
assert list_joiner.list_type_ is None
def test_from_dict_non_default(self):
data = {
"type": "haystack.components.joiners.list_joiner.ListJoiner",
"init_parameters": {"list_type_": "list[haystack.dataclasses.chat_message.ChatMessage]"},
}
list_joiner = ListJoiner.from_dict(data)
assert isinstance(list_joiner, ListJoiner)
assert list_joiner.list_type_ == list[ChatMessage]
def test_from_dict_non_default_typing_list(self):
data = {
"type": "haystack.components.joiners.list_joiner.ListJoiner",
"init_parameters": {"list_type_": "typing.List[haystack.dataclasses.chat_message.ChatMessage]"},
}
list_joiner = ListJoiner.from_dict(data)
assert isinstance(list_joiner, ListJoiner)
assert list_joiner.list_type_ == List[ChatMessage]
def test_empty_list(self):
joiner = ListJoiner(list[ChatMessage])
result = joiner.run([])
assert result == {"values": []}
def test_list_of_empty_lists(self):
joiner = ListJoiner(list[ChatMessage])
result = joiner.run([[], []])
assert result == {"values": []}
def test_single_list_of_chat_messages(self):
joiner = ListJoiner(list[ChatMessage])
messages = [ChatMessage.from_user("Hello"), ChatMessage.from_assistant("Hi there")]
result = joiner.run([messages])
assert result == {"values": messages}
def test_multiple_lists_of_chat_messages(self):
joiner = ListJoiner(list[ChatMessage])
messages1 = [ChatMessage.from_user("Hello")]
messages2 = [ChatMessage.from_assistant("Hi there")]
messages3 = [ChatMessage.from_system("System message")]
result = joiner.run([messages1, messages2, messages3])
assert result == {"values": messages1 + messages2 + messages3}
def test_list_of_generated_answers(self):
joiner = ListJoiner(list[GeneratedAnswer])
answers1 = [GeneratedAnswer(query="q1", data="a1", meta={}, documents=[Document(content="d1")])]
answers2 = [GeneratedAnswer(query="q2", data="a2", meta={}, documents=[Document(content="d2")])]
result = joiner.run([answers1, answers2])
assert result == {"values": answers1 + answers2}
def test_list_two_different_types(self):
joiner = ListJoiner()
result = joiner.run([["a", "b"], [1, 2]])
assert result == {"values": ["a", "b", 1, 2]}
def test_mixed_empty_and_non_empty_lists(self):
joiner = ListJoiner(list[ChatMessage])
messages = [ChatMessage.from_user("Hello")]
result = joiner.run([messages, [], messages])
assert result == {"values": messages + messages}
def test_pipeline_connection_validation(self):
joiner = ListJoiner()
llm = OpenAIChatGenerator(api_key=Secret.from_token("test-api-key"))
pipe = Pipeline()
pipe.add_component("joiner", joiner)
pipe.add_component("llm", llm)
with pytest.raises(PipelineConnectError):
pipe.connect("joiner.values", "llm.messages")
assert pipe is not None
def test_pipeline_connection_validation_list_chatmessage(self):
joiner = ListJoiner(list[ChatMessage])
llm = OpenAIChatGenerator(api_key=Secret.from_token("test-api-key"))
pipe = Pipeline()
pipe.add_component("joiner", joiner)
pipe.add_component("llm", llm)
pipe.connect("joiner", "llm.messages")
assert pipe is not None
def test_pipeline_bad_connection(self):
with pytest.raises(PipelineConnectError):
joiner = ListJoiner()
query_embedder = OpenAITextEmbedder(api_key=Secret.from_token("test-api-key"))
pipe = Pipeline()
pipe.add_component("joiner", joiner)
pipe.add_component("query_embedder", query_embedder)
pipe.connect("joiner.values", "query_embedder.text")
def test_pipeline_bad_connection_different_list_types(self):
with pytest.raises(PipelineConnectError):
joiner = ListJoiner(list[int])
llm = OpenAIChatGenerator(api_key=Secret.from_token("test-api-key"))
pipe = Pipeline()
pipe.add_component("joiner", joiner)
pipe.add_component("llm", llm)
pipe.connect("joiner.values", "llm.messages")
def test_result_two_different_types(self):
pipe = Pipeline()
pipe.add_component("answer_builder", AnswerBuilder())
pipe.add_component("chat_prompt_builder", ChatPromptBuilder())
pipe.add_component("joiner", ListJoiner())
pipe.connect("answer_builder", "joiner.values")
pipe.connect("chat_prompt_builder", "joiner.values")
result = pipe.run(
data={
"answer_builder": {"query": "What is nuclear physics?", "replies": ["This is an answer."]},
"chat_prompt_builder": {"template": [ChatMessage.from_user("Hello")]},
}
)
assert isinstance(result["joiner"]["values"][0], GeneratedAnswer)
assert isinstance(result["joiner"]["values"][1], ChatMessage)
@@ -0,0 +1,37 @@
# SPDX-FileCopyrightText: 2022-present deepset GmbH <info@deepset.ai>
#
# SPDX-License-Identifier: Apache-2.0
from haystack.components.joiners.string_joiner import StringJoiner
from haystack.core.serialization import component_from_dict, component_to_dict
class TestStringJoiner:
def test_init(self):
joiner = StringJoiner()
assert isinstance(joiner, StringJoiner)
def test_to_dict(self):
joiner = StringJoiner()
data = component_to_dict(joiner, name="string_joiner")
assert data == {"type": "haystack.components.joiners.string_joiner.StringJoiner", "init_parameters": {}}
def test_from_dict(self):
data = {"type": "haystack.components.joiners.string_joiner.StringJoiner", "init_parameters": {}}
string_joiner = component_from_dict(StringJoiner, data=data, name="string_joiner")
assert isinstance(string_joiner, StringJoiner)
def test_empty_list(self):
joiner = StringJoiner()
result = joiner.run([])
assert result == {"strings": []}
def test_single_string(self):
joiner = StringJoiner()
result = joiner.run("a")
assert result == {"strings": ["a"]}
def test_two_strings(self):
joiner = StringJoiner()
result = joiner.run(["a", "b"])
assert result == {"strings": ["a", "b"]}