From e3b3f12777f554cdc7be551166d88398d493303e Mon Sep 17 00:00:00 2001 From: Elon Azoulay Date: Tue, 21 Apr 2026 20:20:10 -0700 Subject: [PATCH] fix(fireworks): add glm-5p1 metadata and parallel_tool_calls (#26069) --- .../llms/fireworks_ai/chat/transformation.py | 56 +++++++++---------- ...odel_prices_and_context_window_backup.json | 30 ++++++++++ model_prices_and_context_window.json | 30 ++++++++++ .../test_fireworks_ai_chat_transformation.py | 54 ++++++++++++++++-- 4 files changed, 135 insertions(+), 35 deletions(-) diff --git a/litellm/llms/fireworks_ai/chat/transformation.py b/litellm/llms/fireworks_ai/chat/transformation.py index ed6d167a118..5499447c52e 100644 --- a/litellm/llms/fireworks_ai/chat/transformation.py +++ b/litellm/llms/fireworks_ai/chat/transformation.py @@ -116,6 +116,7 @@ class FireworksAIConfig(OpenAIGPTConfig): # Only add tool_choice for models that explicitly support it if supports_tool_choice(model=model, custom_llm_provider="fireworks_ai"): supported_params.append("tool_choice") + supported_params.append("parallel_tool_calls") # Only add reasoning_effort for models that support it if supports_reasoning(model=model, custom_llm_provider="fireworks_ai"): @@ -248,34 +249,24 @@ class FireworksAIConfig(OpenAIGPTConfig): return messages + def _get_model_cost_capability(self, model: str, capability: str) -> Optional[bool]: + candidate_keys = [model] + if not model.startswith("fireworks_ai/"): + candidate_keys.append(f"fireworks_ai/{model}") + + for candidate_key in candidate_keys: + model_info = litellm.model_cost.get(candidate_key) + if model_info is not None and model_info.get(capability) is not None: + return cast(Optional[bool], model_info.get(capability)) + + return None + def get_provider_info(self, model: str) -> ProviderSpecificModelInfo: - # Models that support reasoning_effort - reasoning_supported_models = [ - "qwen3-8b", - "qwen3-32b", - "qwen3-coder-480b-a35b-instruct", - "deepseek-v3p1", - "deepseek-v3p2", - "glm-4p5", - "glm-4p5-air", - "glm-4p6", - "gpt-oss-120b", - "gpt-oss-20b", - ] - - # Normalize model name - remove prefix if present - normalized_model = model - if model.startswith("fireworks_ai/"): - normalized_model = model.replace("fireworks_ai/", "") - if normalized_model.startswith("accounts/fireworks/models/"): - normalized_model = normalized_model.replace( - "accounts/fireworks/models/", "" - ) - - # Check if model supports reasoning - supports_reasoning_value = any( - reasoning_model in normalized_model - for reasoning_model in reasoning_supported_models + supports_function_calling_value = self._get_model_cost_capability( + model=model, capability="supports_function_calling" + ) + supports_reasoning_value = self._get_model_cost_capability( + model=model, capability="supports_reasoning" ) provider_specific_model_info: ProviderSpecificModelInfo = { @@ -285,9 +276,16 @@ class FireworksAIConfig(OpenAIGPTConfig): "supports_vision": True, # via document inlining } + if supports_function_calling_value is not None: + provider_specific_model_info["supports_function_calling"] = ( + supports_function_calling_value + ) + # Only include supports_reasoning if True - if supports_reasoning_value: - provider_specific_model_info["supports_reasoning"] = True + if supports_reasoning_value is not None: + provider_specific_model_info["supports_reasoning"] = ( + supports_reasoning_value + ) return provider_specific_model_info diff --git a/litellm/model_prices_and_context_window_backup.json b/litellm/model_prices_and_context_window_backup.json index 04b68b8f4ec..b8e1edc1ece 100644 --- a/litellm/model_prices_and_context_window_backup.json +++ b/litellm/model_prices_and_context_window_backup.json @@ -13219,6 +13219,21 @@ "supports_response_schema": true, "supports_tool_choice": true }, + "fireworks_ai/accounts/fireworks/models/glm-5p1": { + "cache_read_input_token_cost": 2.6e-07, + "input_cost_per_token": 1.4e-06, + "litellm_provider": "fireworks_ai", + "max_input_tokens": 202800, + "max_output_tokens": 202800, + "max_tokens": 202800, + "mode": "chat", + "output_cost_per_token": 4.4e-06, + "source": "https://fireworks.ai/models/fireworks/glm-5p1", + "supports_function_calling": false, + "supports_reasoning": true, + "supports_response_schema": false, + "supports_tool_choice": false + }, "fireworks_ai/accounts/fireworks/models/gpt-oss-120b": { "input_cost_per_token": 1.5e-07, "litellm_provider": "fireworks_ai", @@ -13485,6 +13500,21 @@ "supports_response_schema": true, "supports_tool_choice": true }, + "fireworks_ai/glm-5p1": { + "cache_read_input_token_cost": 2.6e-07, + "input_cost_per_token": 1.4e-06, + "litellm_provider": "fireworks_ai", + "max_input_tokens": 202800, + "max_output_tokens": 202800, + "max_tokens": 202800, + "mode": "chat", + "output_cost_per_token": 4.4e-06, + "source": "https://fireworks.ai/models/fireworks/glm-5p1", + "supports_function_calling": false, + "supports_reasoning": true, + "supports_response_schema": false, + "supports_tool_choice": false + }, "fireworks_ai/kimi-k2p5": { "cache_read_input_token_cost": 1e-07, "input_cost_per_token": 6e-07, diff --git a/model_prices_and_context_window.json b/model_prices_and_context_window.json index 04b68b8f4ec..b8e1edc1ece 100644 --- a/model_prices_and_context_window.json +++ b/model_prices_and_context_window.json @@ -13219,6 +13219,21 @@ "supports_response_schema": true, "supports_tool_choice": true }, + "fireworks_ai/accounts/fireworks/models/glm-5p1": { + "cache_read_input_token_cost": 2.6e-07, + "input_cost_per_token": 1.4e-06, + "litellm_provider": "fireworks_ai", + "max_input_tokens": 202800, + "max_output_tokens": 202800, + "max_tokens": 202800, + "mode": "chat", + "output_cost_per_token": 4.4e-06, + "source": "https://fireworks.ai/models/fireworks/glm-5p1", + "supports_function_calling": false, + "supports_reasoning": true, + "supports_response_schema": false, + "supports_tool_choice": false + }, "fireworks_ai/accounts/fireworks/models/gpt-oss-120b": { "input_cost_per_token": 1.5e-07, "litellm_provider": "fireworks_ai", @@ -13485,6 +13500,21 @@ "supports_response_schema": true, "supports_tool_choice": true }, + "fireworks_ai/glm-5p1": { + "cache_read_input_token_cost": 2.6e-07, + "input_cost_per_token": 1.4e-06, + "litellm_provider": "fireworks_ai", + "max_input_tokens": 202800, + "max_output_tokens": 202800, + "max_tokens": 202800, + "mode": "chat", + "output_cost_per_token": 4.4e-06, + "source": "https://fireworks.ai/models/fireworks/glm-5p1", + "supports_function_calling": false, + "supports_reasoning": true, + "supports_response_schema": false, + "supports_tool_choice": false + }, "fireworks_ai/kimi-k2p5": { "cache_read_input_token_cost": 1e-07, "input_cost_per_token": 6e-07, diff --git a/tests/test_litellm/llms/fireworks_ai/chat/test_fireworks_ai_chat_transformation.py b/tests/test_litellm/llms/fireworks_ai/chat/test_fireworks_ai_chat_transformation.py index 323443b2e15..07aeb44d6e5 100644 --- a/tests/test_litellm/llms/fireworks_ai/chat/test_fireworks_ai_chat_transformation.py +++ b/tests/test_litellm/llms/fireworks_ai/chat/test_fireworks_ai_chat_transformation.py @@ -10,12 +10,23 @@ sys.path.insert( 0, os.path.abspath("../../../../..") ) # Adds the parent directory to the system path -from litellm import supports_reasoning +from litellm import get_model_info, supports_reasoning from litellm.llms.fireworks_ai.chat.transformation import FireworksAIConfig from litellm.types.llms.openai import ChatCompletionToolCallFunctionChunk from litellm.types.utils import ChatCompletionMessageToolCall, Function, Message +@pytest.fixture(autouse=True) +def force_local_model_cost(monkeypatch): + """Force local model cost map usage for all tests in this file.""" + monkeypatch.setenv("LITELLM_LOCAL_MODEL_COST_MAP", "True") + # Refresh model_cost from local map + import litellm + from litellm.litellm_core_utils.get_model_cost_map import get_model_cost_map + + litellm.model_cost = get_model_cost_map(url=litellm.model_cost_map_url) + + def test_handle_message_content_with_tool_calls(): config = FireworksAIConfig() message = Message( @@ -62,7 +73,6 @@ def test_handle_message_content_with_tool_calls(): def test_supports_reasoning_effort(): """Test that reasoning_effort is only supported for specific Fireworks AI models.""" - # Models that support reasoning_effort supported_models = [ "fireworks_ai/accounts/fireworks/models/qwen3-8b", "fireworks_ai/accounts/fireworks/models/qwen3-32b", @@ -72,11 +82,13 @@ def test_supports_reasoning_effort(): "fireworks_ai/accounts/fireworks/models/glm-4p5", "fireworks_ai/accounts/fireworks/models/glm-4p5-air", "fireworks_ai/accounts/fireworks/models/glm-4p6", + "fireworks_ai/accounts/fireworks/models/glm-4p7", + "fireworks_ai/accounts/fireworks/models/glm-5p1", "fireworks_ai/accounts/fireworks/models/gpt-oss-120b", "fireworks_ai/accounts/fireworks/models/gpt-oss-20b", + "fireworks_ai/glm-5p1", ] - # Models that don't support reasoning_effort unsupported_models = [ "fireworks_ai/accounts/fireworks/models/llama-v3-70b-instruct", "fireworks_ai/accounts/fireworks/models/mixtral-8x7b-instruct", @@ -97,19 +109,41 @@ def test_get_supported_openai_params_reasoning_effort(): """Test that reasoning_effort is only included in supported params for models that support it.""" config = FireworksAIConfig() - # Model that supports reasoning_effort supported_params = config.get_supported_openai_params( - "fireworks_ai/accounts/fireworks/models/qwen3-8b" + "fireworks_ai/accounts/fireworks/models/glm-5p1" ) assert "reasoning_effort" in supported_params - # Model that doesn't support reasoning_effort unsupported_params = config.get_supported_openai_params( "fireworks_ai/accounts/fireworks/models/llama-v3-70b-instruct" ) assert "reasoning_effort" not in unsupported_params +def test_get_supported_openai_params_parallel_tool_calls(): + """Test that parallel_tool_calls is included for models that support tool_choice.""" + config = FireworksAIConfig() + + supported_params = config.get_supported_openai_params( + "fireworks_ai/accounts/fireworks/models/glm-4p6" + ) + assert "parallel_tool_calls" in supported_params + + unsupported_params = config.get_supported_openai_params( + "fireworks_ai/accounts/fireworks/models/glm-5p1" + ) + assert "parallel_tool_calls" not in unsupported_params + + +def test_get_model_info_respects_explicit_fireworks_capabilities(): + """Test that get_model_info preserves explicit capability flags from the model map.""" + model_info = get_model_info("fireworks_ai/accounts/fireworks/models/glm-5p1") + + assert model_info["supports_function_calling"] is False + assert model_info["supports_reasoning"] is True + assert model_info["supports_tool_choice"] is False + + def test_add_transform_inline_image_block_skips_data_urls(): """ data: URLs must not have #transform=inline appended — doing so corrupts the @@ -232,3 +266,11 @@ def test_transform_messages_helper_removes_provider_specific_fields(): ) for msg in out: assert "provider_specific_fields" not in msg + + +def test_unmapped_model_fallback_function_calling(): + """Test that a model not in model_cost still defaults to supporting function calling for Fireworks.""" + config = FireworksAIConfig() + model = "fireworks_ai/unmapped-future-model" + info = config.get_provider_info(model) + assert info["supports_function_calling"] is True