""" Tests for GigaChat LiteLLM Provider Tests message transformation, parameter handling, and response transformation. Run with: pytest tests/llm_translation/test_gigachat.py -v """ import pytest class TestGigaChatMessageTransformation: """Tests for message transformation (OpenAI -> GigaChat format)""" @pytest.fixture def config(self): from litellm.llms.gigachat.chat.transformation import GigaChatConfig return GigaChatConfig() def test_simple_user_message(self, config): """Basic user message should pass through""" messages = [{"role": "user", "content": "Hello"}] result = config._transform_messages(messages) assert len(result) == 1 assert result[0]["role"] == "user" assert result[0]["content"] == "Hello" def test_developer_role_to_system(self, config): """Developer role should be converted to system""" messages = [{"role": "developer", "content": "You are helpful"}] result = config._transform_messages(messages) assert result[0]["role"] == "system" def test_system_after_first_becomes_user(self, config): """System message after first position should become user""" messages = [ {"role": "assistant", "content": "Response"}, {"role": "system", "content": "Additional instruction"}, ] result = config._transform_messages(messages) assert result[0]["role"] == "assistant" assert result[1]["role"] == "user" # system after first becomes user def test_tool_role_to_function(self, config): """Tool role should be converted to function""" messages = [{"role": "tool", "content": "result data"}] result = config._transform_messages(messages) assert result[0]["role"] == "function" def test_tool_content_convertation_non_string_value(self, config): """Non string tool content should be serialized""" messages = [{"role": "tool", "content": {"output": 42}}] result = config._transform_messages(messages) assert result[0]["content"] == '{"output": 42}' def test_tool_content_convertation_json_string_value(self, config): """JSON string tool content left unchanged""" valid_json = '{"output": "red car"}' messages = [{"role": "tool", "content": valid_json}] result = config._transform_messages(messages) assert result[0]["content"] == valid_json def test_tool_content_convertation_random_string_value(self, config): """Non JSON tool content should be serialized""" messages = [{"role": "tool", "content": "random string"}] result = config._transform_messages(messages) assert result[0]["content"] == '"random string"' def test_tool_calls_to_function_call(self, config): """tool_calls should be converted to function_call""" messages = [ { "role": "assistant", "content": "", "tool_calls": [ { "id": "call_123", "type": "function", "function": { "name": "get_weather", "arguments": '{"city": "Moscow"}', }, } ], } ] result = config._transform_messages(messages) assert "function_call" in result[0] assert result[0]["function_call"]["name"] == "get_weather" assert result[0]["function_call"]["arguments"] == {"city": "Moscow"} assert "tool_calls" not in result[0] def test_none_content_becomes_empty_string(self, config): """None content should become empty string""" messages = [{"role": "assistant", "content": None}] result = config._transform_messages(messages) assert result[0]["content"] == "" def test_name_field_removed(self, config): """name field should be removed (not supported by GigaChat)""" messages = [{"role": "user", "content": "Hi", "name": "John"}] result = config._transform_messages(messages) assert "name" not in result[0] class TestGigaChatCollapseUserMessages: """Tests for collapsing consecutive user messages""" @pytest.fixture def config(self): from litellm.llms.gigachat.chat.transformation import GigaChatConfig return GigaChatConfig() class TestGigaChatToolsTransformation: """Tests for tools -> functions conversion""" @pytest.fixture def config(self): from litellm.llms.gigachat.chat.transformation import GigaChatConfig return GigaChatConfig() def test_single_tool_conversion(self, config): """Single tool should be converted correctly""" tools = [ { "type": "function", "function": { "name": "get_weather", "description": "Get weather for a city", "parameters": { "type": "object", "properties": {"city": {"type": "string"}}, }, }, } ] result = config._convert_tools_to_functions(tools) assert len(result) == 1 assert result[0]["name"] == "get_weather" assert result[0]["description"] == "Get weather for a city" def test_multiple_tools_conversion(self, config): """Multiple tools should all be converted""" tools = [ { "type": "function", "function": { "name": "func1", "description": "First", "parameters": {"type": "object", "properties": {}}, }, }, { "type": "function", "function": { "name": "func2", "description": "Second", "parameters": {"type": "object", "properties": {}}, }, }, ] result = config._convert_tools_to_functions(tools) assert len(result) == 2 assert result[0]["name"] == "func1" assert result[1]["name"] == "func2" class TestGigaChatParamsTransformation: """Tests for parameter transformation""" @pytest.fixture def config(self): from litellm.llms.gigachat.chat.transformation import GigaChatConfig return GigaChatConfig() def test_temperature_zero_becomes_top_p_zero(self, config): """temperature=0 should become top_p=0""" params = {"temperature": 0} result = config.map_openai_params( non_default_params=params, optional_params={}, model="GigaChat", drop_params=False, ) assert "top_p" in result assert result["top_p"] == 0 assert "temperature" not in result def test_temperature_nonzero_preserved(self, config): """Non-zero temperature should be preserved""" params = {"temperature": 0.7} result = config.map_openai_params( non_default_params=params, optional_params={}, model="GigaChat", drop_params=False, ) assert result["temperature"] == 0.7 def test_max_completion_tokens_to_max_tokens(self, config): """max_completion_tokens should become max_tokens""" params = {"max_completion_tokens": 100} result = config.map_openai_params( non_default_params=params, optional_params={}, model="GigaChat", drop_params=False, ) assert result["max_tokens"] == 100 def test_structured_output_via_json_schema(self, config): """json_schema response_format should trigger structured output mode""" params = { "response_format": { "type": "json_schema", "json_schema": { "name": "person", "schema": { "type": "object", "properties": { "name": {"type": "string"}, "age": {"type": "integer"}, }, }, }, } } result = config.map_openai_params( non_default_params=params, optional_params={}, model="GigaChat", drop_params=False, ) assert "_structured_output" in result assert result["_structured_output"] is True assert "function_call" in result assert result["function_call"]["name"] == "person" class TestGigaChatProviderRegistration: """Tests for provider registration in LiteLLM""" def test_gigachat_in_provider_list(self): """GigaChat should be in provider list""" from litellm.types.utils import LlmProviders assert hasattr(LlmProviders, "GIGACHAT") assert LlmProviders.GIGACHAT.value == "gigachat" def test_gigachat_in_chat_providers(self): """GigaChat should be in LITELLM_CHAT_PROVIDERS""" from litellm.constants import LITELLM_CHAT_PROVIDERS assert "gigachat" in LITELLM_CHAT_PROVIDERS def test_gigachat_key_exists(self): """gigachat_key should be available""" import litellm assert hasattr(litellm, "gigachat_key") def test_gigachat_config_exists(self): """GigaChatConfig should be available""" import litellm assert hasattr(litellm, "GigaChatConfig") class TestGigaChatTransformRequest: """Tests for request transformation""" @pytest.fixture def config(self): from litellm.llms.gigachat.chat.transformation import GigaChatConfig return GigaChatConfig() def test_basic_request(self, config): """Basic request should be transformed correctly""" messages = [{"role": "user", "content": "Hello"}] result = config.transform_request( model="gigachat/GigaChat", messages=messages, optional_params={}, litellm_params={}, headers={}, ) assert result["model"] == "GigaChat" assert len(result["messages"]) == 1 assert result["messages"][0]["role"] == "user" def test_request_with_temperature(self, config): """Request with temperature should include it""" messages = [{"role": "user", "content": "Hello"}] result = config.transform_request( model="gigachat/GigaChat", messages=messages, optional_params={"temperature": 0.7}, litellm_params={}, headers={}, ) assert result["temperature"] == 0.7 def test_request_with_functions(self, config): """Request with functions should include them""" messages = [{"role": "user", "content": "Hello"}] functions = [{"name": "test", "description": "Test", "parameters": {}}] result = config.transform_request( model="gigachat/GigaChat", messages=messages, optional_params={"functions": functions}, litellm_params={}, headers={}, ) assert "functions" in result assert len(result["functions"]) == 1 class TestGigaChatSupportedParams: """Tests for supported parameters""" @pytest.fixture def config(self): from litellm.llms.gigachat.chat.transformation import GigaChatConfig return GigaChatConfig() def test_supported_params(self, config): """Check supported parameters list""" supported = config.get_supported_openai_params("GigaChat") assert "temperature" in supported assert "max_tokens" in supported assert "max_completion_tokens" in supported assert "tools" in supported assert "response_format" in supported assert "stream" in supported class TestGigaChatToolChoiceMapping: """Tests for tool_choice -> function_call mapping""" @pytest.fixture def config(self): from litellm.llms.gigachat.chat.transformation import GigaChatConfig return GigaChatConfig() def test_tool_choice_none(self, config): """tool_choice='none' should map to function_call='none'""" result = config._map_tool_choice("none") assert result == "none" def test_tool_choice_auto(self, config): """tool_choice='auto' should map to function_call='auto'""" result = config._map_tool_choice("auto") assert result == "auto" def test_tool_choice_required(self, config): """tool_choice='required' should map to function_call='auto' (closest equivalent)""" result = config._map_tool_choice("required") assert result == "auto" def test_tool_choice_forced_function(self, config): """tool_choice with forced function should map to function_call with name""" tool_choice = {"type": "function", "function": {"name": "get_weather"}} result = config._map_tool_choice(tool_choice) assert result == {"name": "get_weather"} def test_tool_choice_forced_function_full(self, config): """tool_choice with full function details should extract only name""" tool_choice = { "type": "function", "function": { "name": "weather_forecast", "description": "Get weather forecast", }, } result = config._map_tool_choice(tool_choice) assert result == {"name": "weather_forecast"} def test_tool_choice_invalid_dict(self, config): """tool_choice with invalid dict should return None""" tool_choice = {"type": "tool"} # Missing function result = config._map_tool_choice(tool_choice) assert result is None def test_tool_choice_in_map_openai_params_auto(self, config): """tool_choice='auto' should be mapped in map_openai_params""" params = {"tool_choice": "auto"} result = config.map_openai_params( non_default_params=params, optional_params={}, model="GigaChat", drop_params=False, ) assert result["function_call"] == "auto" def test_tool_choice_in_map_openai_params_none(self, config): """tool_choice='none' should be mapped in map_openai_params""" params = {"tool_choice": "none"} result = config.map_openai_params( non_default_params=params, optional_params={}, model="GigaChat", drop_params=False, ) assert result["function_call"] == "none" def test_tool_choice_in_map_openai_params_required(self, config): """tool_choice='required' should be mapped to 'auto' in map_openai_params""" params = {"tool_choice": "required"} result = config.map_openai_params( non_default_params=params, optional_params={}, model="GigaChat", drop_params=False, ) assert result["function_call"] == "auto" def test_tool_choice_in_map_openai_params_forced(self, config): """tool_choice with forced function should be mapped in map_openai_params""" params = { "tool_choice": { "type": "function", "function": {"name": "weather_forecast"}, } } result = config.map_openai_params( non_default_params=params, optional_params={}, model="GigaChat", drop_params=False, ) assert result["function_call"] == {"name": "weather_forecast"} def test_tool_choice_with_tools(self, config): """tool_choice should work together with tools parameter""" params = { "tools": [ { "type": "function", "function": { "name": "get_weather", "description": "Get weather", "parameters": {"type": "object", "properties": {}}, }, } ], "tool_choice": {"type": "function", "function": {"name": "get_weather"}}, } result = config.map_openai_params( non_default_params=params, optional_params={}, model="GigaChat", drop_params=False, ) assert "functions" in result assert result["function_call"] == {"name": "get_weather"} def test_transform_request_with_tool_choice(self, config): """Full transform_request should include function_call from tool_choice""" messages = [{"role": "user", "content": "What's the weather?"}] optional_params = { "functions": [ { "name": "get_weather", "description": "Get weather", "parameters": {"type": "object", "properties": {}}, } ], "function_call": {"name": "get_weather"}, } result = config.transform_request( model="gigachat/GigaChat", messages=messages, optional_params=optional_params, litellm_params={}, headers={}, ) assert "function_call" in result assert result["function_call"] == {"name": "get_weather"}