Skip to content
Closed
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
22 changes: 22 additions & 0 deletions litellm/llms/groq/chat/transformation.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,8 @@

from ...openai_like.chat.transformation import OpenAILikeChatConfig

GROQ_COMPOUND_MODELS = frozenset({"compound", "compound-mini"})

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Hardcoded model list violates team rule on model-specific flags

GROQ_COMPOUND_MODELS is a hardcoded frozenset in the codebase. The team's explicit rule is to put model-specific flags in model_prices_and_context_window.json and read them via get_model_info, so that adding a new model (e.g. a future groq/compound-pro) works without a LiteLLM code update. The JSON entries for these models are already added in this PR, so a custom field like "requires_groq_prefix": true could be added there and checked via litellm.get_model_info(f"groq/{model}") in _get_groq_model_name. The current approach means any new Groq compound variant requires both a JSON entry and a code change to stay correct.

Rule Used: What: Do not hardcode model-specific flags in the ... (source)

Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!



class GroqChatConfig(OpenAILikeChatConfig):
frequency_penalty: Optional[int] = None
Expand Down Expand Up @@ -84,6 +86,26 @@ def custom_llm_provider(self) -> Optional[str]:
def get_config(cls):
return super().get_config()

@staticmethod
def _get_groq_model_name(model: str) -> str:
return f"groq/{model}" if model in GROQ_COMPOUND_MODELS else model

def transform_request(
self,
model: str,
messages: List[AllMessageValues],
optional_params: dict,
litellm_params: dict,
headers: dict,
) -> dict:
return super().transform_request(
model=self._get_groq_model_name(model),
messages=messages,
optional_params=optional_params,
litellm_params=litellm_params,
headers=headers,
)

def get_model_response_iterator(
self,
streaming_response: Union[Iterator[str], AsyncIterator[str], ModelResponse],
Expand Down
22 changes: 22 additions & 0 deletions litellm/model_prices_and_context_window_backup.json
Original file line number Diff line number Diff line change
Expand Up @@ -24104,6 +24104,28 @@
"supports_response_schema": false,
"supports_tool_choice": true
},
"groq/compound": {
"input_cost_per_token": 0.0,
"litellm_provider": "groq",
"max_input_tokens": 131072,
"max_output_tokens": 8192,
"max_tokens": 8192,
"mode": "chat",
"output_cost_per_token": 0.0,
"supports_tool_choice": true,
"supports_web_search": true
},
"groq/compound-mini": {
"input_cost_per_token": 0.0,
"litellm_provider": "groq",
"max_input_tokens": 131072,
"max_output_tokens": 8192,
"max_tokens": 8192,
"mode": "chat",
"output_cost_per_token": 0.0,
"supports_tool_choice": true,
"supports_web_search": true
},
"groq/whisper-large-v3": {
"input_cost_per_second": 3.083e-05,
"litellm_provider": "groq",
Expand Down
22 changes: 22 additions & 0 deletions model_prices_and_context_window.json
Original file line number Diff line number Diff line change
Expand Up @@ -24262,6 +24262,28 @@
"supports_response_schema": false,
"supports_tool_choice": true
},
"groq/compound": {
"input_cost_per_token": 0.0,
"litellm_provider": "groq",
"max_input_tokens": 131072,
"max_output_tokens": 8192,
"max_tokens": 8192,
"mode": "chat",
"output_cost_per_token": 0.0,
"supports_tool_choice": true,
"supports_web_search": true
},
"groq/compound-mini": {
"input_cost_per_token": 0.0,
"litellm_provider": "groq",
"max_input_tokens": 131072,
"max_output_tokens": 8192,
"max_tokens": 8192,
"mode": "chat",
"output_cost_per_token": 0.0,
"supports_tool_choice": true,
"supports_web_search": true
},
"groq/whisper-large-v3": {
"input_cost_per_second": 3.083e-05,
"litellm_provider": "groq",
Expand Down
92 changes: 92 additions & 0 deletions tests/test_litellm/llms/groq/chat/test_groq_chat_transformation.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
import json
from pathlib import Path
from unittest.mock import patch

import httpx
import pytest

import litellm
from litellm.llms.custom_httpx.http_handler import HTTPHandler
from litellm.llms.groq.chat.transformation import GroqChatConfig


@pytest.mark.parametrize(
"model, expected",
[
("compound", "groq/compound"),
("compound-mini", "groq/compound-mini"),
("llama-3.3-70b-versatile", "llama-3.3-70b-versatile"),
("openai/gpt-oss-120b", "openai/gpt-oss-120b"),
],
)
def test_get_groq_model_name(model, expected):
assert GroqChatConfig._get_groq_model_name(model) == expected


@pytest.mark.parametrize("model", ["groq/compound", "groq/compound-mini"])
def test_compound_models_in_backup_cost_map(model):
"""
Regression test for https://github.com/BerriAI/litellm/issues/32467

The Groq compound systems must be registered so they route through the groq
provider and expose metadata.
"""
json_path = Path(__file__).parents[5] / "litellm" / "model_prices_and_context_window_backup.json"
with open(json_path) as f:
model_cost = json.load(f)

info = model_cost.get(model)
assert info is not None, f"{model} missing from backup JSON"
assert info["litellm_provider"] == "groq"
assert info["mode"] == "chat"
assert info["max_input_tokens"] == 131072
assert info["max_output_tokens"] == 8192


@pytest.mark.parametrize(
"requested_model, expected_api_model",
[
("groq/compound", "groq/compound"),
("groq/compound-mini", "groq/compound-mini"),
("groq/llama-3.3-70b-versatile", "llama-3.3-70b-versatile"),
],
)
def test_compound_model_name_sent_to_groq(requested_model, expected_api_model):
"""
Regression test for https://github.com/BerriAI/litellm/issues/32467

Groq exposes the compound systems with the "groq/" prefix as part of the
actual model id, so LiteLLM must send "groq/compound(-mini)" instead of the
prefix-stripped "compound(-mini)", which Groq rejects with model_not_found.
"""
client = HTTPHandler()
fake_response = httpx.Response(
status_code=200,
json={
"id": "chatcmpl-1",
"object": "chat.completion",
"created": 1,
"model": expected_api_model,
"service_tier": "auto",
"choices": [
{
"index": 0,
"message": {"role": "assistant", "content": "hi"},
"finish_reason": "stop",
}
],
"usage": {"prompt_tokens": 1, "completion_tokens": 1, "total_tokens": 2},
},
request=httpx.Request("POST", "https://api.groq.com/openai/v1/chat/completions"),
)

with patch.object(client, "post", return_value=fake_response) as mock_post:
litellm.completion(
model=requested_model,
messages=[{"role": "user", "content": "hi"}],
api_key="fake-key",
client=client,
)

sent_body = json.loads(mock_post.call_args.kwargs["data"])
assert sent_body["model"] == expected_api_model
Loading