diff --git a/integrations/google_vertex/src/haystack_integrations/components/generators/google_vertex/chat/gemini.py b/integrations/google_vertex/src/haystack_integrations/components/generators/google_vertex/chat/gemini.py index ac4c93228..f09692daf 100644 --- a/integrations/google_vertex/src/haystack_integrations/components/generators/google_vertex/chat/gemini.py +++ b/integrations/google_vertex/src/haystack_integrations/components/generators/google_vertex/chat/gemini.py @@ -17,6 +17,7 @@ HarmCategory, Part, Tool, + ToolConfig, ) logger = logging.getLogger(__name__) @@ -54,6 +55,8 @@ def __init__( generation_config: Optional[Union[GenerationConfig, Dict[str, Any]]] = None, safety_settings: Optional[Dict[HarmCategory, HarmBlockThreshold]] = None, tools: Optional[List[Tool]] = None, + tool_config: Optional[ToolConfig] = None, + system_instruction: Optional[Union[str, ByteStream, Part]] = None, streaming_callback: Optional[Callable[[StreamingChunk], None]] = None, ): """ @@ -76,8 +79,11 @@ def __init__( :param tools: List of tools to use when generating content. See the documentation for [Tool](https://cloud.google.com/python/docs/reference/aiplatform/latest/vertexai.generative_models.Tool) the list of supported arguments. + :param tool_config: The tool config to use. See the documentation for [ToolConfig] + (https://cloud.google.com/vertex-ai/generative-ai/docs/reference/python/latest/vertexai.generative_models.ToolConfig) + :param system_instruction: Default system instruction to use for generating content. :param streaming_callback: A callback function that is called when a new token is received from - the stream. The callback function accepts StreamingChunk as an argument. + the stream. The callback function accepts StreamingChunk as an argument. """ @@ -87,13 +93,25 @@ def __init__( self._model_name = model self._project_id = project_id self._location = location - self._model = GenerativeModel(self._model_name) + # model parameters self._generation_config = generation_config self._safety_settings = safety_settings self._tools = tools + self._tool_config = tool_config + self._system_instruction = system_instruction self._streaming_callback = streaming_callback + # except streaming_callback, all other model parameters can be passed during initialization + self._model = GenerativeModel( + self._model_name, + generation_config=self._generation_config, + safety_settings=self._safety_settings, + tools=self._tools, + tool_config=self._tool_config, + system_instruction=self._system_instruction, + ) + def _generation_config_to_dict(self, config: Union[GenerationConfig, Dict[str, Any]]) -> Dict[str, Any]: if isinstance(config, dict): return config @@ -106,6 +124,17 @@ def _generation_config_to_dict(self, config: Union[GenerationConfig, Dict[str, A "stop_sequences": config._raw_generation_config.stop_sequences, } + def _tool_config_to_dict(self, tool_config: ToolConfig) -> Dict[str, Any]: + """Serializes the ToolConfig object into a dictionary.""" + mode = tool_config._gapic_tool_config.function_calling_config.mode + allowed_function_names = tool_config._gapic_tool_config.function_calling_config.allowed_function_names + config_dict = {"function_calling_config": {"mode": mode}} + + if allowed_function_names: + config_dict["function_calling_config"]["allowed_function_names"] = allowed_function_names + + return config_dict + def to_dict(self) -> Dict[str, Any]: """ Serializes the component to a dictionary. @@ -123,10 +152,14 @@ def to_dict(self) -> Dict[str, Any]: generation_config=self._generation_config, safety_settings=self._safety_settings, tools=self._tools, + tool_config=self._tool_config, + system_instruction=self._system_instruction, streaming_callback=callback_name, ) if (tools := data["init_parameters"].get("tools")) is not None: data["init_parameters"]["tools"] = [Tool.to_dict(t) for t in tools] + if (tool_config := data["init_parameters"].get("tool_config")) is not None: + data["init_parameters"]["tool_config"] = self._tool_config_to_dict(tool_config) if (generation_config := data["init_parameters"].get("generation_config")) is not None: data["init_parameters"]["generation_config"] = self._generation_config_to_dict(generation_config) return data @@ -141,10 +174,23 @@ def from_dict(cls, data: Dict[str, Any]) -> "VertexAIGeminiChatGenerator": :returns: Deserialized component. """ + + def _tool_config_from_dict(config_dict: Dict[str, Any]) -> ToolConfig: + """Deserializes the ToolConfig object from a dictionary.""" + function_calling_config = config_dict["function_calling_config"] + return ToolConfig( + function_calling_config=ToolConfig.FunctionCallingConfig( + mode=function_calling_config["mode"], + allowed_function_names=function_calling_config.get("allowed_function_names"), + ) + ) + if (tools := data["init_parameters"].get("tools")) is not None: data["init_parameters"]["tools"] = [Tool.from_dict(t) for t in tools] if (generation_config := data["init_parameters"].get("generation_config")) is not None: data["init_parameters"]["generation_config"] = GenerationConfig.from_dict(generation_config) + if (tool_config := data["init_parameters"].get("tool_config")) is not None: + data["init_parameters"]["tool_config"] = _tool_config_from_dict(tool_config) if (serialized_callback_handler := data["init_parameters"].get("streaming_callback")) is not None: data["init_parameters"]["streaming_callback"] = deserialize_callable(serialized_callback_handler) return default_from_dict(cls, data) @@ -212,9 +258,6 @@ def run( new_message = self._message_to_part(messages[-1]) res = session.send_message( content=new_message, - generation_config=self._generation_config, - safety_settings=self._safety_settings, - tools=self._tools, stream=streaming_callback is not None, ) diff --git a/integrations/google_vertex/src/haystack_integrations/components/generators/google_vertex/gemini.py b/integrations/google_vertex/src/haystack_integrations/components/generators/google_vertex/gemini.py index 7394211bf..2b1c1b477 100644 --- a/integrations/google_vertex/src/haystack_integrations/components/generators/google_vertex/gemini.py +++ b/integrations/google_vertex/src/haystack_integrations/components/generators/google_vertex/gemini.py @@ -16,6 +16,7 @@ HarmCategory, Part, Tool, + ToolConfig, ) logger = logging.getLogger(__name__) @@ -58,6 +59,8 @@ def __init__( generation_config: Optional[Union[GenerationConfig, Dict[str, Any]]] = None, safety_settings: Optional[Dict[HarmCategory, HarmBlockThreshold]] = None, tools: Optional[List[Tool]] = None, + tool_config: Optional[ToolConfig] = None, + system_instruction: Optional[Union[str, ByteStream, Part]] = None, streaming_callback: Optional[Callable[[StreamingChunk], None]] = None, ): """ @@ -86,6 +89,8 @@ def __init__( :param tools: List of tools to use when generating content. See the documentation for [Tool](https://cloud.google.com/python/docs/reference/aiplatform/latest/vertexai.generative_models.Tool) the list of supported arguments. + :param tool_config: The tool config to use. See the documentation for [ToolConfig](https://cloud.google.com/vertex-ai/generative-ai/docs/reference/python/latest/vertexai.generative_models.ToolConfig) + :param system_instruction: Default system instruction to use for generating content. :param streaming_callback: A callback function that is called when a new token is received from the stream. The callback function accepts StreamingChunk as an argument. """ @@ -96,13 +101,25 @@ def __init__( self._model_name = model self._project_id = project_id self._location = location - self._model = GenerativeModel(self._model_name) + # model parameters self._generation_config = generation_config self._safety_settings = safety_settings self._tools = tools + self._tool_config = tool_config + self._system_instruction = system_instruction self._streaming_callback = streaming_callback + # except streaming_callback, all other model parameters can be passed during initialization + self._model = GenerativeModel( + self._model_name, + generation_config=self._generation_config, + safety_settings=self._safety_settings, + tools=self._tools, + tool_config=self._tool_config, + system_instruction=self._system_instruction, + ) + def _generation_config_to_dict(self, config: Union[GenerationConfig, Dict[str, Any]]) -> Dict[str, Any]: if isinstance(config, dict): return config @@ -115,6 +132,18 @@ def _generation_config_to_dict(self, config: Union[GenerationConfig, Dict[str, A "stop_sequences": config._raw_generation_config.stop_sequences, } + def _tool_config_to_dict(self, tool_config: ToolConfig) -> Dict[str, Any]: + """Serializes the ToolConfig object into a dictionary.""" + + mode = tool_config._gapic_tool_config.function_calling_config.mode + allowed_function_names = tool_config._gapic_tool_config.function_calling_config.allowed_function_names + config_dict = {"function_calling_config": {"mode": mode}} + + if allowed_function_names: + config_dict["function_calling_config"]["allowed_function_names"] = allowed_function_names + + return config_dict + def to_dict(self) -> Dict[str, Any]: """ Serializes the component to a dictionary. @@ -132,10 +161,14 @@ def to_dict(self) -> Dict[str, Any]: generation_config=self._generation_config, safety_settings=self._safety_settings, tools=self._tools, + tool_config=self._tool_config, + system_instruction=self._system_instruction, streaming_callback=callback_name, ) if (tools := data["init_parameters"].get("tools")) is not None: data["init_parameters"]["tools"] = [Tool.to_dict(t) for t in tools] + if (tool_config := data["init_parameters"].get("tool_config")) is not None: + data["init_parameters"]["tool_config"] = self._tool_config_to_dict(tool_config) if (generation_config := data["init_parameters"].get("generation_config")) is not None: data["init_parameters"]["generation_config"] = self._generation_config_to_dict(generation_config) return data @@ -150,10 +183,23 @@ def from_dict(cls, data: Dict[str, Any]) -> "VertexAIGeminiGenerator": :returns: Deserialized component. """ + + def _tool_config_from_dict(config_dict: Dict[str, Any]) -> ToolConfig: + """Deserializes the ToolConfig object from a dictionary.""" + function_calling_config = config_dict["function_calling_config"] + return ToolConfig( + function_calling_config=ToolConfig.FunctionCallingConfig( + mode=function_calling_config["mode"], + allowed_function_names=function_calling_config.get("allowed_function_names"), + ) + ) + if (tools := data["init_parameters"].get("tools")) is not None: data["init_parameters"]["tools"] = [Tool.from_dict(t) for t in tools] if (generation_config := data["init_parameters"].get("generation_config")) is not None: data["init_parameters"]["generation_config"] = GenerationConfig.from_dict(generation_config) + if (tool_config := data["init_parameters"].get("tool_config")) is not None: + data["init_parameters"]["tool_config"] = _tool_config_from_dict(tool_config) if (serialized_callback_handler := data["init_parameters"].get("streaming_callback")) is not None: data["init_parameters"]["streaming_callback"] = deserialize_callable(serialized_callback_handler) return default_from_dict(cls, data) @@ -188,11 +234,9 @@ def run( converted_parts = [self._convert_part(p) for p in parts] contents = [Content(parts=converted_parts, role="user")] + res = self._model.generate_content( contents=contents, - generation_config=self._generation_config, - safety_settings=self._safety_settings, - tools=self._tools, stream=streaming_callback is not None, ) self._model.start_chat() diff --git a/integrations/google_vertex/tests/chat/test_gemini.py b/integrations/google_vertex/tests/chat/test_gemini.py index ab21008fb..87b43d66b 100644 --- a/integrations/google_vertex/tests/chat/test_gemini.py +++ b/integrations/google_vertex/tests/chat/test_gemini.py @@ -13,6 +13,7 @@ HarmCategory, Part, Tool, + ToolConfig, ) from haystack_integrations.components.generators.google_vertex import VertexAIGeminiChatGenerator @@ -60,6 +61,12 @@ def test_init(mock_vertexai_init, _mock_generative_model): safety_settings = {HarmCategory.HARM_CATEGORY_DANGEROUS_CONTENT: HarmBlockThreshold.BLOCK_ONLY_HIGH} tool = Tool(function_declarations=[GET_CURRENT_WEATHER_FUNC]) + tool_config = ToolConfig( + function_calling_config=ToolConfig.FunctionCallingConfig( + mode=ToolConfig.FunctionCallingConfig.Mode.ANY, + allowed_function_names=["get_current_weather_func"], + ) + ) gemini = VertexAIGeminiChatGenerator( project_id="TestID123", @@ -67,12 +74,16 @@ def test_init(mock_vertexai_init, _mock_generative_model): generation_config=generation_config, safety_settings=safety_settings, tools=[tool], + tool_config=tool_config, + system_instruction="Please provide brief answers.", ) mock_vertexai_init.assert_called() assert gemini._model_name == "gemini-1.5-flash" assert gemini._generation_config == generation_config assert gemini._safety_settings == safety_settings assert gemini._tools == [tool] + assert gemini._tool_config == tool_config + assert gemini._system_instruction == "Please provide brief answers." @patch("haystack_integrations.components.generators.google_vertex.chat.gemini.vertexai_init") @@ -92,6 +103,8 @@ def test_to_dict(_mock_vertexai_init, _mock_generative_model): "safety_settings": None, "streaming_callback": None, "tools": None, + "tool_config": None, + "system_instruction": None, }, } @@ -110,12 +123,20 @@ def test_to_dict_with_params(_mock_vertexai_init, _mock_generative_model): safety_settings = {HarmCategory.HARM_CATEGORY_DANGEROUS_CONTENT: HarmBlockThreshold.BLOCK_ONLY_HIGH} tool = Tool(function_declarations=[GET_CURRENT_WEATHER_FUNC]) + tool_config = ToolConfig( + function_calling_config=ToolConfig.FunctionCallingConfig( + mode=ToolConfig.FunctionCallingConfig.Mode.ANY, + allowed_function_names=["get_current_weather_func"], + ) + ) gemini = VertexAIGeminiChatGenerator( project_id="TestID123", generation_config=generation_config, safety_settings=safety_settings, tools=[tool], + tool_config=tool_config, + system_instruction="Please provide brief answers.", ) assert gemini.to_dict() == { @@ -155,6 +176,13 @@ def test_to_dict_with_params(_mock_vertexai_init, _mock_generative_model): ] } ], + "tool_config": { + "function_calling_config": { + "mode": ToolConfig.FunctionCallingConfig.Mode.ANY, + "allowed_function_names": ["get_current_weather_func"], + } + }, + "system_instruction": "Please provide brief answers.", }, } @@ -180,6 +208,8 @@ def test_from_dict(_mock_vertexai_init, _mock_generative_model): assert gemini._project_id == "TestID123" assert gemini._safety_settings is None assert gemini._tools is None + assert gemini._tool_config is None + assert gemini._system_instruction is None assert gemini._generation_config is None @@ -222,6 +252,13 @@ def test_from_dict_with_param(_mock_vertexai_init, _mock_generative_model): ] } ], + "tool_config": { + "function_calling_config": { + "mode": ToolConfig.FunctionCallingConfig.Mode.ANY, + "allowed_function_names": ["get_current_weather_func"], + } + }, + "system_instruction": "Please provide brief answers.", "streaming_callback": None, }, } @@ -231,7 +268,12 @@ def test_from_dict_with_param(_mock_vertexai_init, _mock_generative_model): assert gemini._project_id == "TestID123" assert gemini._safety_settings == {HarmCategory.HARM_CATEGORY_DANGEROUS_CONTENT: HarmBlockThreshold.BLOCK_ONLY_HIGH} assert repr(gemini._tools) == repr([Tool(function_declarations=[GET_CURRENT_WEATHER_FUNC])]) + assert isinstance(gemini._tool_config, ToolConfig) assert isinstance(gemini._generation_config, GenerationConfig) + assert gemini._system_instruction == "Please provide brief answers." + assert ( + gemini._tool_config._gapic_tool_config.function_calling_config.mode == ToolConfig.FunctionCallingConfig.Mode.ANY + ) @patch("haystack_integrations.components.generators.google_vertex.chat.gemini.GenerativeModel") diff --git a/integrations/google_vertex/tests/test_gemini.py b/integrations/google_vertex/tests/test_gemini.py index bb96ec409..1543f3ccf 100644 --- a/integrations/google_vertex/tests/test_gemini.py +++ b/integrations/google_vertex/tests/test_gemini.py @@ -9,6 +9,7 @@ HarmBlockThreshold, HarmCategory, Tool, + ToolConfig, ) from haystack_integrations.components.generators.google_vertex import VertexAIGeminiGenerator @@ -48,6 +49,12 @@ def test_init(mock_vertexai_init, _mock_generative_model): safety_settings = {HarmCategory.HARM_CATEGORY_DANGEROUS_CONTENT: HarmBlockThreshold.BLOCK_ONLY_HIGH} tool = Tool(function_declarations=[GET_CURRENT_WEATHER_FUNC]) + tool_config = ToolConfig( + function_calling_config=ToolConfig.FunctionCallingConfig( + mode=ToolConfig.FunctionCallingConfig.Mode.ANY, + allowed_function_names=["get_current_weather_func"], + ) + ) gemini = VertexAIGeminiGenerator( project_id="TestID123", @@ -55,12 +62,16 @@ def test_init(mock_vertexai_init, _mock_generative_model): generation_config=generation_config, safety_settings=safety_settings, tools=[tool], + tool_config=tool_config, + system_instruction="Please provide brief answers.", ) mock_vertexai_init.assert_called() assert gemini._model_name == "gemini-1.5-flash" assert gemini._generation_config == generation_config assert gemini._safety_settings == safety_settings assert gemini._tools == [tool] + assert gemini._tool_config == tool_config + assert gemini._system_instruction == "Please provide brief answers." @patch("haystack_integrations.components.generators.google_vertex.gemini.vertexai_init") @@ -80,6 +91,8 @@ def test_to_dict(_mock_vertexai_init, _mock_generative_model): "safety_settings": None, "streaming_callback": None, "tools": None, + "tool_config": None, + "system_instruction": None, }, } @@ -98,12 +111,20 @@ def test_to_dict_with_params(_mock_vertexai_init, _mock_generative_model): safety_settings = {HarmCategory.HARM_CATEGORY_DANGEROUS_CONTENT: HarmBlockThreshold.BLOCK_ONLY_HIGH} tool = Tool(function_declarations=[GET_CURRENT_WEATHER_FUNC]) + tool_config = ToolConfig( + function_calling_config=ToolConfig.FunctionCallingConfig( + mode=ToolConfig.FunctionCallingConfig.Mode.ANY, + allowed_function_names=["get_current_weather_func"], + ) + ) gemini = VertexAIGeminiGenerator( project_id="TestID123", generation_config=generation_config, safety_settings=safety_settings, tools=[tool], + tool_config=tool_config, + system_instruction="Please provide brief answers.", ) assert gemini.to_dict() == { "type": "haystack_integrations.components.generators.google_vertex.gemini.VertexAIGeminiGenerator", @@ -142,6 +163,13 @@ def test_to_dict_with_params(_mock_vertexai_init, _mock_generative_model): ] } ], + "tool_config": { + "function_calling_config": { + "mode": ToolConfig.FunctionCallingConfig.Mode.ANY, + "allowed_function_names": ["get_current_weather_func"], + } + }, + "system_instruction": "Please provide brief answers.", }, } @@ -159,6 +187,8 @@ def test_from_dict(_mock_vertexai_init, _mock_generative_model): "safety_settings": None, "tools": None, "streaming_callback": None, + "tool_config": None, + "system_instruction": None, }, } ) @@ -167,6 +197,8 @@ def test_from_dict(_mock_vertexai_init, _mock_generative_model): assert gemini._project_id == "TestID123" assert gemini._safety_settings is None assert gemini._tools is None + assert gemini._tool_config is None + assert gemini._system_instruction is None assert gemini._generation_config is None @@ -210,6 +242,13 @@ def test_from_dict_with_param(_mock_vertexai_init, _mock_generative_model): } ], "streaming_callback": None, + "tool_config": { + "function_calling_config": { + "mode": ToolConfig.FunctionCallingConfig.Mode.ANY, + "allowed_function_names": ["get_current_weather_func"], + } + }, + "system_instruction": "Please provide brief answers.", }, } ) @@ -219,6 +258,11 @@ def test_from_dict_with_param(_mock_vertexai_init, _mock_generative_model): assert gemini._safety_settings == {HarmCategory.HARM_CATEGORY_DANGEROUS_CONTENT: HarmBlockThreshold.BLOCK_ONLY_HIGH} assert repr(gemini._tools) == repr([Tool(function_declarations=[GET_CURRENT_WEATHER_FUNC])]) assert isinstance(gemini._generation_config, GenerationConfig) + assert isinstance(gemini._tool_config, ToolConfig) + assert gemini._system_instruction == "Please provide brief answers." + assert ( + gemini._tool_config._gapic_tool_config.function_calling_config.mode == ToolConfig.FunctionCallingConfig.Mode.ANY + ) @patch("haystack_integrations.components.generators.google_vertex.gemini.GenerativeModel") diff --git a/integrations/llama_cpp/pyproject.toml b/integrations/llama_cpp/pyproject.toml index acf42d958..673df575a 100644 --- a/integrations/llama_cpp/pyproject.toml +++ b/integrations/llama_cpp/pyproject.toml @@ -26,7 +26,7 @@ classifiers = [ "Programming Language :: Python :: Implementation :: CPython", "Programming Language :: Python :: Implementation :: PyPy", ] -dependencies = ["haystack-ai", "llama-cpp-python>=0.2.87"] +dependencies = ["haystack-ai", "llama-cpp-python>=0.2.87,<0.3.0"] [project.urls] Documentation = "https://github.com/deepset-ai/haystack-core-integrations/tree/main/integrations/llama_cpp#readme" diff --git a/integrations/ollama/CHANGELOG.md b/integrations/ollama/CHANGELOG.md index 8f51237e9..5da725e87 100644 --- a/integrations/ollama/CHANGELOG.md +++ b/integrations/ollama/CHANGELOG.md @@ -1,5 +1,11 @@ # Changelog +## [integrations/ollama-v1.0.1] - 2024-09-26 + +### 🐛 Bug Fixes + +- Ollama Chat Generator - add missing `to_dict` and `from_dict` methods (#1110) + ## [integrations/ollama-v1.0.0] - 2024-09-07 ### 🐛 Bug Fixes