Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 4 additions & 2 deletions haystack/core/super_component/super_component.py
Original file line number Diff line number Diff line change
Expand Up @@ -119,7 +119,8 @@ def run(self, **kwargs: Any) -> dict[str, Any]:
:returns:
Dictionary containing the SuperComponent's output values
"""
filtered_inputs = {param: value for param, value in kwargs.items() if value != _delegate_default}
# `is not`, not `!=`: numpy/pandas/torch override `__ne__` element-wise and would crash here.
filtered_inputs = {param: value for param, value in kwargs.items() if value is not _delegate_default}
pipeline_inputs = self._map_explicit_inputs(input_mapping=self.input_mapping, inputs=filtered_inputs)
include_outputs_from = self._get_include_outputs_from()
pipeline_outputs = self.pipeline.run(data=pipeline_inputs, include_outputs_from=include_outputs_from)
Expand Down Expand Up @@ -147,7 +148,8 @@ async def run_async(self, **kwargs: Any) -> dict[str, Any]:
if not isinstance(self.pipeline, AsyncPipeline):
raise TypeError("Pipeline is not an AsyncPipeline. run_async is not supported.")

filtered_inputs = {param: value for param, value in kwargs.items() if value != _delegate_default}
# `is not`, not `!=`: numpy/pandas/torch override `__ne__` element-wise and would crash here.
filtered_inputs = {param: value for param, value in kwargs.items() if value is not _delegate_default}
pipeline_inputs = self._map_explicit_inputs(input_mapping=self.input_mapping, inputs=filtered_inputs)
pipeline_outputs = await self.pipeline.run_async(data=pipeline_inputs)
return self._map_explicit_outputs(pipeline_outputs, self.output_mapping)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
---
fixes:
- |
Fixed ``SuperComponent.run`` and ``SuperComponent.run_async`` raising
``ValueError: The truth value of ... is ambiguous`` whenever a non-scalar
input (numpy arrays, pandas DataFrames/Series, torch tensors, or any
object whose ``__ne__`` returns a non-scalar) was passed. The internal
sentinel filter is now an identity check (``is not _delegate_default``)
instead of value equality (``!= _delegate_default``), so it no longer
invokes ``__ne__`` on user-provided values.
50 changes: 50 additions & 0 deletions test/core/super_component/test_super_component.py
Original file line number Diff line number Diff line change
Expand Up @@ -469,3 +469,53 @@ async def run_async(self):

result = await deserialized_super_component.run_async()
assert result == {"output": "Hello world"}


@component
class _CaptureValue:
"""Captures whatever value reaches the inner pipeline, for both sync and async runs."""

def __init__(self) -> None:
self.captured: dict[str, Any] = {}

@component.output_types(seen=bool)
def run(self, value: Any = None) -> dict[str, bool]:
self.captured["value"] = value
return {"seen": True}

@component.output_types(seen=bool)
async def run_async(self, value: Any = None) -> dict[str, bool]:
self.captured["value"] = value
return {"seen": True}


class TestSuperComponentDelegateDefaultFiltering:
"""
Regression for the `_delegate_default` filter: must be `is not`, not `!=`, otherwise
numpy / pandas / torch inputs raise `ValueError: The truth value of ... is ambiguous`.
"""

def test_run_accepts_numpy_ndarray_input(self):
np = pytest.importorskip("numpy")

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We have numpy in our test deps so we shouldn't need this and can just import numpy as normal.


inner = _CaptureValue()
pipe = Pipeline()
pipe.add_component("capture", inner)

result = SuperComponent(pipe).run(value=np.array([1, 2, 3]))

assert result == {"seen": True}
assert np.array_equal(inner.captured["value"], np.array([1, 2, 3]))

@pytest.mark.asyncio
async def test_run_async_accepts_numpy_ndarray_input(self):
np = pytest.importorskip("numpy")

inner = _CaptureValue()
pipe = AsyncPipeline()
pipe.add_component("capture", inner)

result = await SuperComponent(pipe).run_async(value=np.array([4, 5, 6]))

assert result == {"seen": True}
assert np.array_equal(inner.captured["value"], np.array([4, 5, 6]))
Loading