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
11 changes: 10 additions & 1 deletion haystack/tools/component_tool.py
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,7 @@ def __init__(
:param description: Optional description (defaults to component's docstring).
:param parameters:
A JSON schema defining the parameters expected by the Tool.
Top-level property names must match the component's input socket names.
Will fall back to the parameters defined in the component's run method signature if not provided.
:param outputs_to_string:
Optional dictionary defining how tool outputs should be converted into string(s) or results.
Expand Down Expand Up @@ -159,7 +160,9 @@ def __init__(
}
```
:raises TypeError: If the object passed is not a Haystack Component instance.
:raises ValueError: If the component has already been added to a pipeline, or if schema generation fails.
:raises ValueError:
If the component has already been added to a pipeline, schema generation fails,
or custom parameters declare unknown component inputs.
"""
if not isinstance(component, Component):
message = (
Expand Down Expand Up @@ -250,6 +253,12 @@ async def async_component_invoker(**kwargs: Any) -> dict[str, Any]:
outputs_to_string=outputs_to_string,
)

if parameters:
unknown = set(parameters.get("properties", {})) - self._get_valid_inputs()
if unknown:
names = ", ".join(sorted(unknown))
raise ValueError(f"Custom parameters contain unknown component input parameter(s): {names}.")

def _get_valid_inputs(self) -> set[str]:
"""
Return valid input parameter names from the component's input sockets.
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
upgrade:
- |
``ComponentTool`` now raises ``ValueError`` during construction when a custom
``parameters`` schema declares top-level properties that are not component
inputs. Previously these names could cause a ``KeyError`` during invocation.
Rename or remove unknown properties to match the component input sockets.
21 changes: 21 additions & 0 deletions test/tools/test_component_tool.py
Original file line number Diff line number Diff line change
Expand Up @@ -173,6 +173,27 @@ def test_from_component_basic(self):
assert "reply" in result
assert result["reply"] == "Hello, world!"

def test_custom_parameters_reject_unknown_component_input(self):
parameters = {"type": "object", "properties": {"unknown": {"type": "string"}}}

with pytest.raises(ValueError, match="unknown component input parameter.*unknown"):
ComponentTool(component=SimpleComponent(), parameters=parameters)

@pytest.mark.parametrize("parameters", [{}, {"type": "object"}, {"type": "object", "properties": {}}])
def test_custom_parameters_allow_implicit_inputs(self, parameters):
tool = ComponentTool(component=SimpleComponent(), parameters=parameters)

assert tool.invoke(text="world") == {"reply": "Hello, world!"}

def test_custom_parameters_allow_mandatory_input_from_state(self):
parameters = {"type": "object", "properties": {}}

tool = ComponentTool(
component=SimpleComponent(), parameters=parameters, inputs_from_state={"user_text": "text"}
)

assert tool.parameters == parameters

def test_from_component_long_description(self):
tool = ComponentTool(component=SimpleComponent(), description="".join(["A"] * 1024))
assert len(tool.description) == 1024
Expand Down