diff --git a/haystack/components/generators/chat/hugging_face_api.py b/haystack/components/generators/chat/hugging_face_api.py index 5c2947787..d48509637 100644 --- a/haystack/components/generators/chat/hugging_face_api.py +++ b/haystack/components/generators/chat/hugging_face_api.py @@ -80,6 +80,26 @@ def _convert_hfapi_tool_calls(hfapi_tool_calls: Optional[List["ChatCompletionOut return tool_calls +def _convert_tools_to_hfapi_tools( + tools: Optional[Union[List[Tool], Toolset]], +) -> Optional[List["ChatCompletionInputTool"]]: + if not tools: + return None + + # huggingface_hub<0.31.0 uses "arguments", huggingface_hub>=0.31.0 uses "parameters" + parameters_name = "arguments" if hasattr(ChatCompletionInputFunctionDefinition, "arguments") else "parameters" + + hf_tools = [] + for tool in tools: + hf_tools_args = {"name": tool.name, "description": tool.description, parameters_name: tool.parameters} + + hf_tools.append( + ChatCompletionInputTool(function=ChatCompletionInputFunctionDefinition(**hf_tools_args), type="function") + ) + + return hf_tools + + @component class HuggingFaceAPIChatGenerator: """ @@ -313,19 +333,11 @@ class HuggingFaceAPIChatGenerator: if streaming_callback: return self._run_streaming(formatted_messages, generation_kwargs, streaming_callback) - hf_tools = None - if tools: - if isinstance(tools, Toolset): - tools = list(tools) - hf_tools = [ - ChatCompletionInputTool( - function=ChatCompletionInputFunctionDefinition( - name=tool.name, description=tool.description, arguments=tool.parameters - ), - type="function", - ) - for tool in tools - ] + if tools and isinstance(tools, Toolset): + tools = list(tools) + + hf_tools = _convert_tools_to_hfapi_tools(tools) + return self._run_non_streaming(formatted_messages, generation_kwargs, hf_tools) @component.output_types(replies=List[ChatMessage]) @@ -373,19 +385,11 @@ class HuggingFaceAPIChatGenerator: if streaming_callback: return await self._run_streaming_async(formatted_messages, generation_kwargs, streaming_callback) - hf_tools = None - if tools: - if isinstance(tools, Toolset): - tools = list(tools) - hf_tools = [ - ChatCompletionInputTool( - function=ChatCompletionInputFunctionDefinition( - name=tool.name, description=tool.description, arguments=tool.parameters - ), - type="function", - ) - for tool in tools - ] + if tools and isinstance(tools, Toolset): + tools = list(tools) + + hf_tools = _convert_tools_to_hfapi_tools(tools) + return await self._run_non_streaming_async(formatted_messages, generation_kwargs, hf_tools) def _run_streaming( diff --git a/releasenotes/notes/hf-tool-definition-0.31.0-c8403da8769fff1d.yaml b/releasenotes/notes/hf-tool-definition-0.31.0-c8403da8769fff1d.yaml new file mode 100644 index 000000000..774a5b61c --- /dev/null +++ b/releasenotes/notes/hf-tool-definition-0.31.0-c8403da8769fff1d.yaml @@ -0,0 +1,7 @@ +--- +fixes: + - | + Make internal tool conversion in the HuggingFaceAPICompatibleChatGenerator compatible with huggingface_hub>=0.31.0. + In the huggingface_hub library, `arguments` attribute of `ChatCompletionInputFunctionDefinition` has been renamed to + `parameters`. + Our implementation is compatible with both the legacy version and the new one. diff --git a/test/components/generators/chat/test_hugging_face_api.py b/test/components/generators/chat/test_hugging_face_api.py index 0bf01a775..64d66c0f9 100644 --- a/test/components/generators/chat/test_hugging_face_api.py +++ b/test/components/generators/chat/test_hugging_face_api.py @@ -10,6 +10,7 @@ from haystack import Pipeline from haystack.dataclasses import StreamingChunk from haystack.utils.auth import Secret from haystack.utils.hf import HFGenerationAPIType + from huggingface_hub import ( ChatCompletionOutput, ChatCompletionOutputComplete, @@ -21,9 +22,14 @@ from huggingface_hub import ( ChatCompletionStreamOutputChoice, ChatCompletionStreamOutputDelta, ) -from huggingface_hub.utils import RepositoryNotFoundError +from huggingface_hub.errors import RepositoryNotFoundError + +from haystack.components.generators.chat.hugging_face_api import ( + HuggingFaceAPIChatGenerator, + _convert_hfapi_tool_calls, + _convert_tools_to_hfapi_tools, +) -from haystack.components.generators.chat.hugging_face_api import HuggingFaceAPIChatGenerator, _convert_hfapi_tool_calls from haystack.tools import Tool from haystack.dataclasses import ChatMessage, ToolCall from haystack.tools.toolset import Toolset @@ -980,3 +986,41 @@ class TestHuggingFaceAPIChatGenerator: }, } assert data["init_parameters"]["tools"] == expected_tools_data + + def test_convert_tools_to_hfapi_tools(self): + assert _convert_tools_to_hfapi_tools(None) is None + assert _convert_tools_to_hfapi_tools([]) is None + + tool = Tool( + name="weather", + description="useful to determine the weather in a given location", + parameters={"city": {"type": "string"}}, + function=get_weather, + ) + hf_tools = _convert_tools_to_hfapi_tools([tool]) + assert len(hf_tools) == 1 + assert hf_tools[0].type == "function" + assert hf_tools[0].function.name == "weather" + assert hf_tools[0].function.description == "useful to determine the weather in a given location" + assert hf_tools[0].function.parameters == {"city": {"type": "string"}} + + def test_convert_tools_to_hfapi_tools_legacy(self): + # this satisfies the check hasattr(ChatCompletionInputFunctionDefinition, "arguments") + mock_class = MagicMock() + + with patch( + "haystack.components.generators.chat.hugging_face_api.ChatCompletionInputFunctionDefinition", mock_class + ): + tool = Tool( + name="weather", + description="useful to determine the weather in a given location", + parameters={"city": {"type": "string"}}, + function=get_weather, + ) + _convert_tools_to_hfapi_tools([tool]) + + mock_class.assert_called_once_with( + name="weather", + arguments={"city": {"type": "string"}}, + description="useful to determine the weather in a given location", + )