From 514c9c58fcc91933348d2188ed8c82bbe98132f2 Mon Sep 17 00:00:00 2001 From: Fahad Date: Thu, 11 Dec 2025 20:08:17 +0000 Subject: [PATCH] feat: grok-4.1 support https://github.com/BeehiveInnovations/pal-mcp-server/issues/339 --- conf/openrouter_models.json | 18 +++ conf/xai_models.json | 49 ++---- docs/advanced-usage.md | 6 +- docs/configuration.md | 6 +- providers/xai.py | 39 +++-- simulator_tests/test_xai_models.py | 67 +++----- tests/test_auto_mode_comprehensive.py | 6 +- tests/test_auto_mode_provider_selection.py | 2 +- tests/test_model_enumeration.py | 2 +- tests/test_supported_models_aliases.py | 17 +- tests/test_xai_provider.py | 175 ++++++++------------- utils/model_restrictions.py | 2 +- 12 files changed, 157 insertions(+), 232 deletions(-) diff --git a/conf/openrouter_models.json b/conf/openrouter_models.json index 92645e4..46fabd2 100644 --- a/conf/openrouter_models.json +++ b/conf/openrouter_models.json @@ -473,6 +473,24 @@ "temperature_constraint": "range", "description": "xAI's Grok 4 via OpenRouter with vision and advanced reasoning", "intelligence_score": 15 + }, + { + "model_name": "x-ai/grok-4.1-fast", + "aliases": [ + "grok-4.1-fast-openrouter", + "grok-4.1-openrouter" + ], + "context_window": 2000000, + "max_output_tokens": 2000000, + "supports_extended_thinking": true, + "supports_json_mode": true, + "supports_function_calling": true, + "supports_images": true, + "max_image_size_mb": 20.0, + "supports_temperature": true, + "temperature_constraint": "range", + "description": "xAI's Grok 4.1 Fast Reasoning via OpenRouter (2M context) with vision and advanced reasoning", + "intelligence_score": 15 } ] } diff --git a/conf/xai_models.json b/conf/xai_models.json index 243ea72..a48f769 100644 --- a/conf/xai_models.json +++ b/conf/xai_models.json @@ -5,7 +5,7 @@ "usage": "Models listed here are exposed directly through the X.AI provider. Aliases are case-insensitive.", "field_notes": "Matches providers/shared/model_capabilities.py.", "field_descriptions": { - "model_name": "The model identifier (e.g., 'grok-4', 'grok-3-fast')", + "model_name": "The model identifier (e.g., 'grok-4', 'grok-4.1-fast')", "aliases": "Array of short names users can type instead of the full model name", "context_window": "Total number of tokens the model can process (input + output combined)", "max_output_tokens": "Maximum number of tokens the model can generate in a single response", @@ -46,42 +46,27 @@ "max_image_size_mb": 20.0 }, { - "model_name": "grok-3", - "friendly_name": "X.AI (Grok 3)", + "model_name": "grok-4-1-fast-reasoning", + "friendly_name": "X.AI (Grok 4.1 Fast Reasoning)", "aliases": [ - "grok3" + "grok-4.1", + "grok-4-1", + "grok-4.1-fast-reasoning", + "grok-4.1-fast-reasoning-latest", + "grok-4.1-fast" ], - "intelligence_score": 13, - "description": "GROK-3 (131K context) - Advanced reasoning model from X.AI, excellent for complex analysis", - "context_window": 131072, - "max_output_tokens": 131072, - "supports_extended_thinking": false, + "intelligence_score": 15, + "description": "GROK-4.1 Fast Reasoning (2M context) - High-performance multimodal reasoning model with function calling", + "context_window": 2000000, + "max_output_tokens": 2000000, + "supports_extended_thinking": true, "supports_system_prompts": true, "supports_streaming": true, "supports_function_calling": true, - "supports_json_mode": false, - "supports_images": false, - "supports_temperature": true - }, - { - "model_name": "grok-3-fast", - "friendly_name": "X.AI (Grok 3 Fast)", - "aliases": [ - "grok3fast", - "grokfast", - "grok3-fast" - ], - "intelligence_score": 12, - "description": "GROK-3 Fast (131K context) - Higher performance variant, faster processing but more expensive", - "context_window": 131072, - "max_output_tokens": 131072, - "supports_extended_thinking": false, - "supports_system_prompts": true, - "supports_streaming": true, - "supports_function_calling": true, - "supports_json_mode": false, - "supports_images": false, - "supports_temperature": true + "supports_json_mode": true, + "supports_images": true, + "supports_temperature": true, + "max_image_size_mb": 20.0 } ] } diff --git a/docs/advanced-usage.md b/docs/advanced-usage.md index 30a171c..ab42134 100644 --- a/docs/advanced-usage.md +++ b/docs/advanced-usage.md @@ -48,8 +48,7 @@ Regardless of your default configuration, you can specify models per request: | **`gpt5-mini`** (GPT-5 Mini) | OpenAI | 400K tokens | Efficient variant with reasoning | Balanced performance and capability | | **`gpt5-nano`** (GPT-5 Nano) | OpenAI | 400K tokens | Fastest, cheapest GPT-5 variant | Summarization and classification tasks | | **`grok-4`** | X.AI | 256K tokens | Latest flagship Grok model with reasoning, vision | Complex analysis, reasoning tasks | -| **`grok-3`** | X.AI | 131K tokens | Advanced reasoning model | Deep analysis, complex problems | -| **`grok-3-fast`** | X.AI | 131K tokens | Higher performance variant | Fast responses with reasoning | +| **`grok-4.1-fast-reasoning`** | X.AI | 2M tokens | High-performance Grok 4.1 Fast Reasoning with vision | Fast responses and light reasoning | | **`llama`** (Llama 3.2) | Custom/Local | 128K tokens | Local inference, privacy | On-device analysis, cost-free processing | | **Any model** | OpenRouter | Varies | Access to GPT-4, Claude, Llama, etc. | User-specified or based on task requirements | @@ -72,8 +71,7 @@ cloud models (expensive/powerful) AND local models (free/private) in the same co - **GPT-5**: Full-featured with reasoning support and vision - **GPT-5 Mini**: Balanced efficiency and capability - **GPT-5 Nano**: Optimized for fast, low-cost tasks -- **Grok-4**: Extended thinking support, vision capabilities, 256K context -- **Grok-3 Models**: Advanced reasoning, 131K context +- **Grok-4 / Grok-4.1-fast-reasoning**: Extended thinking support, vision capabilities (256K / 2M context) ## Model Usage Restrictions diff --git a/docs/configuration.md b/docs/configuration.md index 0f4ecea..d084f2b 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -83,7 +83,7 @@ DEFAULT_MODEL=auto # Claude picks best model for each task (recommended) |----------|-----------------|-----------------| | OpenAI | `gpt-5.2`, `gpt-5.1-codex`, `gpt-5.1-codex-mini`, `gpt-5`, `gpt-5.2-pro`, `gpt-5-mini`, `gpt-5-nano`, `gpt-5-codex`, `gpt-4.1`, `o3`, `o3-mini`, `o3-pro`, `o4-mini` | `gpt5.2`, `gpt-5.2`, `5.2`, `gpt5.1-codex`, `codex-5.1`, `codex-mini`, `gpt5`, `gpt5pro`, `mini`, `nano`, `codex`, `o3mini`, `o3pro`, `o4mini` | | Gemini | `gemini-2.5-pro`, `gemini-2.5-flash`, `gemini-2.0-flash`, `gemini-2.0-flash-lite` | `pro`, `gemini-pro`, `flash`, `flash-2.0`, `flashlite` | - | X.AI | `grok-4`, `grok-3`, `grok-3-fast` | `grok`, `grok4`, `grok3`, `grok3fast`, `grokfast` | + | X.AI | `grok-4`, `grok-4.1-fast` | `grok`, `grok4`, `grok-4.1-fast-reasoning` | | OpenRouter | See `conf/openrouter_models.json` for the continually evolving catalogue | e.g., `opus`, `sonnet`, `flash`, `pro`, `mistral` | | Custom | User-managed entries such as `llama3.2` | Define your own aliases per entry | @@ -179,7 +179,7 @@ OPENAI_ALLOWED_MODELS=gpt-5.1-codex-mini,gpt-5-mini,o3-mini,o4-mini,mini GOOGLE_ALLOWED_MODELS=flash,pro # X.AI GROK model restrictions -XAI_ALLOWED_MODELS=grok-3,grok-3-fast,grok-4 +XAI_ALLOWED_MODELS=grok-4,grok-4.1-fast-reasoning # OpenRouter model restrictions (affects models via custom provider) OPENROUTER_ALLOWED_MODELS=opus,sonnet,mistral @@ -208,7 +208,7 @@ GOOGLE_ALLOWED_MODELS=pro # Balanced selection GOOGLE_ALLOWED_MODELS=flash,pro OPENAI_ALLOWED_MODELS=gpt-5.1-codex-mini,gpt-5-mini,o4-mini -XAI_ALLOWED_MODELS=grok,grok-3-fast +XAI_ALLOWED_MODELS=grok,grok-4.1-fast-reasoning ``` ### Advanced Configuration diff --git a/providers/xai.py b/providers/xai.py index a24d425..82536da 100644 --- a/providers/xai.py +++ b/providers/xai.py @@ -26,6 +26,10 @@ class XAIModelProvider(RegistryBackedProviderMixin, OpenAICompatibleProvider): REGISTRY_CLASS = XAIModelRegistry MODEL_CAPABILITIES: ClassVar[dict[str, ModelCapabilities]] = {} + # Canonical model identifiers used for category routing. + PRIMARY_MODEL = "grok-4-1-fast-reasoning" + FALLBACK_MODEL = "grok-4" + def __init__(self, api_key: str, **kwargs): """Initialize X.AI provider with API key.""" # Set X.AI base URL @@ -54,32 +58,27 @@ class XAIModelProvider(RegistryBackedProviderMixin, OpenAICompatibleProvider): return None if category == ToolModelCategory.EXTENDED_REASONING: - # Prefer GROK-4 for advanced reasoning with thinking mode - if "grok-4" in allowed_models: - return "grok-4" - elif "grok-3" in allowed_models: - return "grok-3" - # Fall back to any available model + # Prefer Grok 4.1 Fast Reasoning for advanced tasks + if self.PRIMARY_MODEL in allowed_models: + return self.PRIMARY_MODEL + if self.FALLBACK_MODEL in allowed_models: + return self.FALLBACK_MODEL return allowed_models[0] elif category == ToolModelCategory.FAST_RESPONSE: - # Prefer GROK-3-Fast for speed, then GROK-4 - if "grok-3-fast" in allowed_models: - return "grok-3-fast" - elif "grok-4" in allowed_models: - return "grok-4" - # Fall back to any available model + # Prefer Grok 4.1 Fast Reasoning for speed as well (latest fast SKU). + if self.PRIMARY_MODEL in allowed_models: + return self.PRIMARY_MODEL + if self.FALLBACK_MODEL in allowed_models: + return self.FALLBACK_MODEL return allowed_models[0] else: # BALANCED or default - # Prefer GROK-4 for balanced use (best overall capabilities) - if "grok-4" in allowed_models: - return "grok-4" - elif "grok-3" in allowed_models: - return "grok-3" - elif "grok-3-fast" in allowed_models: - return "grok-3-fast" - # Fall back to any available model + # Prefer Grok 4.1 Fast Reasoning for balanced use. + if self.PRIMARY_MODEL in allowed_models: + return self.PRIMARY_MODEL + if self.FALLBACK_MODEL in allowed_models: + return self.FALLBACK_MODEL return allowed_models[0] diff --git a/simulator_tests/test_xai_models.py b/simulator_tests/test_xai_models.py index df48932..41c57e3 100644 --- a/simulator_tests/test_xai_models.py +++ b/simulator_tests/test_xai_models.py @@ -3,8 +3,8 @@ X.AI GROK Model Tests Tests that verify X.AI GROK functionality including: -- Model alias resolution (grok, grok3, grokfast map to actual GROK models) -- GROK-3 and GROK-3-fast models work correctly +- Model alias resolution (grok maps to Grok 4) +- GROK-4 and GROK-4.1 Fast Reasoning models work correctly - Conversation continuity works with GROK models - API integration and response validation """ @@ -63,75 +63,44 @@ class XAIModelsTest(BaseSimulatorTest): if continuation_id: self.logger.info(f" ✅ Got continuation_id: {continuation_id}") - # Test 2: Direct grok-3 model name - self.logger.info(" 2: Testing direct model name (grok-3)") + # Test 2: Direct grok-4.1-fast model name + self.logger.info(" 2: Testing direct model name (grok-4.1-fast)") response2, _ = self.call_mcp_tool( "chat", { - "prompt": "Say 'Hello from GROK-3!' and nothing else.", - "model": "grok-3", + "prompt": "Say 'Hello from GROK-4.1 Fast!' and nothing else.", + "model": "grok-4.1-fast", "temperature": 0.1, }, ) if not response2: - self.logger.error(" ❌ Direct GROK-3 model test failed") + self.logger.error(" ❌ Direct GROK-4.1-fast model test failed") return False - self.logger.info(" ✅ Direct GROK-3 model call completed") + self.logger.info(" ✅ Direct GROK-4.1-fast model call completed") - # Test 3: grok-3-fast model - self.logger.info(" 3: Testing GROK-3-fast model") + # Test 3: grok-4.1-fast-reasoning alias + self.logger.info(" 3: Testing 'grok-4.1-fast-reasoning' alias") response3, _ = self.call_mcp_tool( "chat", { - "prompt": "Say 'Hello from GROK-3-fast!' and nothing else.", - "model": "grok-3-fast", + "prompt": "Say 'Hello from GROK-4.1 Fast Reasoning alias!' and nothing else.", + "model": "grok-4.1-fast-reasoning", "temperature": 0.1, }, ) if not response3: - self.logger.error(" ❌ GROK-3-fast model test failed") + self.logger.error(" ❌ GROK-4.1-fast-reasoning alias test failed") return False - self.logger.info(" ✅ GROK-3-fast model call completed") + self.logger.info(" ✅ GROK-4.1-fast-reasoning alias call completed") - # Test 4: Shorthand aliases - self.logger.info(" 4: Testing shorthand aliases (grok3, grokfast)") - - response4, _ = self.call_mcp_tool( - "chat", - { - "prompt": "Say 'Hello from grok3 alias!' and nothing else.", - "model": "grok3", - "temperature": 0.1, - }, - ) - - if not response4: - self.logger.error(" ❌ grok3 alias test failed") - return False - - response5, _ = self.call_mcp_tool( - "chat", - { - "prompt": "Say 'Hello from grokfast alias!' and nothing else.", - "model": "grokfast", - "temperature": 0.1, - }, - ) - - if not response5: - self.logger.error(" ❌ grokfast alias test failed") - return False - - self.logger.info(" ✅ Shorthand aliases work correctly") - - # Test 5: Conversation continuity with GROK models - self.logger.info(" 5: Testing conversation continuity with GROK") + # Test 4: Conversation continuity with GROK models + self.logger.info(" 4: Testing conversation continuity with GROK") response6, new_continuation_id = self.call_mcp_tool( "chat", @@ -167,8 +136,8 @@ class XAIModelsTest(BaseSimulatorTest): else: self.logger.warning(" ⚠️ Model may not have remembered the number") - # Test 6: Validate X.AI API usage from logs - self.logger.info(" 6: Validating X.AI API usage in logs") + # Test 5: Validate X.AI API usage from logs + self.logger.info(" 5: Validating X.AI API usage in logs") logs = self.get_recent_server_logs() # Check for X.AI API calls diff --git a/tests/test_auto_mode_comprehensive.py b/tests/test_auto_mode_comprehensive.py index 523d864..c06afba 100644 --- a/tests/test_auto_mode_comprehensive.py +++ b/tests/test_auto_mode_comprehensive.py @@ -108,9 +108,9 @@ class TestAutoModeComprehensive: "OPENROUTER_API_KEY": None, }, { - "EXTENDED_REASONING": "grok-4", # GROK-4 for reasoning (now preferred) - "FAST_RESPONSE": "grok-3-fast", # GROK-3-fast for speed - "BALANCED": "grok-4", # GROK-4 as balanced (now preferred) + "EXTENDED_REASONING": "grok-4-1-fast-reasoning", # Latest Grok 4.1 Fast Reasoning + "FAST_RESPONSE": "grok-4-1-fast-reasoning", # Latest fast SKU + "BALANCED": "grok-4-1-fast-reasoning", # Latest balanced default }, ), # Both Gemini and OpenAI available - Google comes first in priority diff --git a/tests/test_auto_mode_provider_selection.py b/tests/test_auto_mode_provider_selection.py index 9268489..fc2c8d2 100644 --- a/tests/test_auto_mode_provider_selection.py +++ b/tests/test_auto_mode_provider_selection.py @@ -321,7 +321,7 @@ class TestAutoModeProviderSelection: ("mini", ProviderType.OPENAI, "gpt-5-mini"), # "mini" now resolves to gpt-5-mini ("o3mini", ProviderType.OPENAI, "o3-mini"), ("grok", ProviderType.XAI, "grok-4"), - ("grokfast", ProviderType.XAI, "grok-3-fast"), + ("grok-4.1-fast-reasoning", ProviderType.XAI, "grok-4-1-fast-reasoning"), ] for alias, expected_provider_type, expected_resolved_name in test_cases: diff --git a/tests/test_model_enumeration.py b/tests/test_model_enumeration.py index 790387a..f4f902c 100644 --- a/tests/test_model_enumeration.py +++ b/tests/test_model_enumeration.py @@ -161,7 +161,7 @@ class TestModelEnumeration: ("grok", False), # X.AI - not available without API key ("gemini-2.5-flash", False), # Full Gemini name - not available without API key ("o4-mini", False), # OpenAI variant - not available without API key - ("grok-3-fast", False), # X.AI variant - not available without API key + ("grok-4.1-fast", False), # X.AI variant - not available without API key ], ) def test_specific_native_models_only_with_api_keys(self, model_name, should_exist): diff --git a/tests/test_supported_models_aliases.py b/tests/test_supported_models_aliases.py index 8b4dd84..ee23f16 100644 --- a/tests/test_supported_models_aliases.py +++ b/tests/test_supported_models_aliases.py @@ -86,20 +86,17 @@ class TestSupportedModelsAliases: # Test specific aliases assert "grok" in provider.MODEL_CAPABILITIES["grok-4"].aliases assert "grok4" in provider.MODEL_CAPABILITIES["grok-4"].aliases - assert "grok3" in provider.MODEL_CAPABILITIES["grok-3"].aliases - assert "grok3fast" in provider.MODEL_CAPABILITIES["grok-3-fast"].aliases - assert "grokfast" in provider.MODEL_CAPABILITIES["grok-3-fast"].aliases + assert "grok-4.1-fast-reasoning" in provider.MODEL_CAPABILITIES["grok-4-1-fast-reasoning"].aliases # Test alias resolution assert provider._resolve_model_name("grok") == "grok-4" assert provider._resolve_model_name("grok4") == "grok-4" - assert provider._resolve_model_name("grok3") == "grok-3" - assert provider._resolve_model_name("grok3fast") == "grok-3-fast" - assert provider._resolve_model_name("grokfast") == "grok-3-fast" + assert provider._resolve_model_name("grok-4.1-fast-reasoning") == "grok-4-1-fast-reasoning" + assert provider._resolve_model_name("grok-4.1-fast-reasoning-latest") == "grok-4-1-fast-reasoning" # Test case insensitive resolution assert provider._resolve_model_name("Grok") == "grok-4" - assert provider._resolve_model_name("GROKFAST") == "grok-3-fast" + assert provider._resolve_model_name("GROK-4.1-FAST-REASONING") == "grok-4-1-fast-reasoning" def test_dial_provider_aliases(self): """Test DIAL provider's alias structure.""" @@ -148,10 +145,10 @@ class TestSupportedModelsAliases: # Test XAI xai_provider = XAIModelProvider("test-key") xai_models = xai_provider.list_models(respect_restrictions=False) - assert "grok-3" in xai_models + assert "grok-4" in xai_models assert "grok" in xai_models - assert "grok-3-fast" in xai_models - assert "grokfast" in xai_models + assert "grok-4.1-fast" in xai_models + assert "grok-4.1-fast-reasoning" in xai_models # Test DIAL dial_provider = DIALModelProvider("test-key") diff --git a/tests/test_xai_provider.py b/tests/test_xai_provider.py index b9cf06c..1d1d1aa 100644 --- a/tests/test_xai_provider.py +++ b/tests/test_xai_provider.py @@ -47,17 +47,21 @@ class TestXAIProvider: # Test valid models assert provider.validate_model_name("grok-4") is True assert provider.validate_model_name("grok4") is True - assert provider.validate_model_name("grok-3") is True - assert provider.validate_model_name("grok-3-fast") is True assert provider.validate_model_name("grok") is True - assert provider.validate_model_name("grok3") is True - assert provider.validate_model_name("grokfast") is True - assert provider.validate_model_name("grok3fast") is True + assert provider.validate_model_name("grok-4.1-fast") is True + assert provider.validate_model_name("grok-4.1-fast-reasoning") is True + assert provider.validate_model_name("grok-4.1-fast-reasoning-latest") is True + assert provider.validate_model_name("grok-4.1-fast") is True + assert provider.validate_model_name("grok-4.1-fast-reasoning") is True + assert provider.validate_model_name("grok-4.1-fast-reasoning-latest") is True # Test invalid model assert provider.validate_model_name("invalid-model") is False assert provider.validate_model_name("gpt-4") is False assert provider.validate_model_name("gemini-pro") is False + assert provider.validate_model_name("grok-3") is False + assert provider.validate_model_name("grok-3-fast") is False + assert provider.validate_model_name("grokfast") is False def test_resolve_model_name(self): """Test model name resolution.""" @@ -66,33 +70,12 @@ class TestXAIProvider: # Test shorthand resolution assert provider._resolve_model_name("grok") == "grok-4" assert provider._resolve_model_name("grok4") == "grok-4" - assert provider._resolve_model_name("grok3") == "grok-3" - assert provider._resolve_model_name("grokfast") == "grok-3-fast" - assert provider._resolve_model_name("grok3fast") == "grok-3-fast" + assert provider._resolve_model_name("grok-4.1-fast-reasoning") == "grok-4-1-fast-reasoning" + assert provider._resolve_model_name("grok-4.1-fast-reasoning-latest") == "grok-4-1-fast-reasoning" # Test full name passthrough assert provider._resolve_model_name("grok-4") == "grok-4" - assert provider._resolve_model_name("grok-3") == "grok-3" - assert provider._resolve_model_name("grok-3-fast") == "grok-3-fast" - - def test_get_capabilities_grok3(self): - """Test getting model capabilities for GROK-3.""" - provider = XAIModelProvider("test-key") - - capabilities = provider.get_capabilities("grok-3") - assert capabilities.model_name == "grok-3" - assert capabilities.friendly_name == "X.AI (Grok 3)" - assert capabilities.context_window == 131_072 - assert capabilities.provider == ProviderType.XAI - assert not capabilities.supports_extended_thinking - assert capabilities.supports_system_prompts is True - assert capabilities.supports_streaming is True - assert capabilities.supports_function_calling is True - - # Test temperature range - assert capabilities.temperature_constraint.min_temp == 0.0 - assert capabilities.temperature_constraint.max_temp == 2.0 - assert capabilities.temperature_constraint.default_temp == 0.3 + assert provider._resolve_model_name("grok-4.1-fast") == "grok-4-1-fast-reasoning" def test_get_capabilities_grok4(self): """Test getting model capabilities for GROK-4.""" @@ -115,16 +98,19 @@ class TestXAIProvider: assert capabilities.temperature_constraint.max_temp == 2.0 assert capabilities.temperature_constraint.default_temp == 0.3 - def test_get_capabilities_grok3_fast(self): - """Test getting model capabilities for GROK-3 Fast.""" + def test_get_capabilities_grok4_1_fast(self): + """Test getting model capabilities for GROK-4.1 Fast Reasoning.""" provider = XAIModelProvider("test-key") - capabilities = provider.get_capabilities("grok-3-fast") - assert capabilities.model_name == "grok-3-fast" - assert capabilities.friendly_name == "X.AI (Grok 3 Fast)" - assert capabilities.context_window == 131_072 + capabilities = provider.get_capabilities("grok-4.1-fast") + assert capabilities.model_name == "grok-4-1-fast-reasoning" + assert capabilities.friendly_name == "X.AI (Grok 4.1 Fast Reasoning)" + assert capabilities.context_window == 2_000_000 assert capabilities.provider == ProviderType.XAI - assert not capabilities.supports_extended_thinking + assert capabilities.supports_extended_thinking is True + assert capabilities.supports_function_calling is True + assert capabilities.supports_json_mode is True + assert capabilities.supports_images is True def test_get_capabilities_with_shorthand(self): """Test getting model capabilities with shorthand.""" @@ -134,8 +120,8 @@ class TestXAIProvider: assert capabilities.model_name == "grok-4" # Should resolve to full name assert capabilities.context_window == 256_000 - capabilities_fast = provider.get_capabilities("grokfast") - assert capabilities_fast.model_name == "grok-3-fast" # Should resolve to full name + capabilities_fast = provider.get_capabilities("grok-4.1-fast-reasoning") + assert capabilities_fast.model_name == "grok-4-1-fast-reasoning" # Should resolve to full name def test_unsupported_model_capabilities(self): """Test error handling for unsupported models.""" @@ -148,20 +134,23 @@ class TestXAIProvider: """X.AI capabilities should expose extended thinking support correctly.""" provider = XAIModelProvider("test-key") - thinking_aliases = ["grok-4", "grok", "grok4"] + thinking_aliases = [ + "grok-4", + "grok", + "grok4", + "grok-4.1-fast", + "grok-4.1-fast-reasoning", + "grok-4.1-fast-reasoning-latest", + ] for alias in thinking_aliases: assert provider.get_capabilities(alias).supports_extended_thinking is True - non_thinking_aliases = ["grok-3", "grok-3-fast", "grokfast"] - for alias in non_thinking_aliases: - assert provider.get_capabilities(alias).supports_extended_thinking is False - def test_provider_type(self): """Test provider type identification.""" provider = XAIModelProvider("test-key") assert provider.get_provider_type() == ProviderType.XAI - @patch.dict(os.environ, {"XAI_ALLOWED_MODELS": "grok-3"}) + @patch.dict(os.environ, {"XAI_ALLOWED_MODELS": "grok-4"}) def test_model_restrictions(self): """Test model restrictions functionality.""" # Clear cached restriction service @@ -173,20 +162,17 @@ class TestXAIProvider: provider = XAIModelProvider("test-key") - # grok-3 should be allowed - assert provider.validate_model_name("grok-3") is True - assert provider.validate_model_name("grok3") is True # Shorthand for grok-3 + # grok-4 should be allowed (including alias) + assert provider.validate_model_name("grok-4") is True + assert provider.validate_model_name("grok") is True - # grok should be blocked (resolves to grok-4 which is not allowed) - assert provider.validate_model_name("grok") is False + # grok-4.1-fast should be blocked by restrictions + assert provider.validate_model_name("grok-4.1-fast") is False + assert provider.validate_model_name("grok-4.1-fast-reasoning") is False - # grok-3-fast should be blocked by restrictions - assert provider.validate_model_name("grok-3-fast") is False - assert provider.validate_model_name("grokfast") is False - - @patch.dict(os.environ, {"XAI_ALLOWED_MODELS": "grok,grok-3-fast"}) + @patch.dict(os.environ, {"XAI_ALLOWED_MODELS": "grok-4.1-fast-reasoning"}) def test_multiple_model_restrictions(self): - """Test multiple models in restrictions.""" + """Restrictions should allow aliases for Grok 4.1 Fast.""" # Clear cached restriction service import utils.model_restrictions from providers.registry import ModelProviderRegistry @@ -196,24 +182,18 @@ class TestXAIProvider: provider = XAIModelProvider("test-key") - # Shorthand "grok" should be allowed (resolves to grok-4) - assert provider.validate_model_name("grok") is True + # Alias should be allowed (resolves to grok-4.1-fast) + assert provider.validate_model_name("grok-4.1-fast-reasoning") is True - # Full name "grok-4" should NOT be allowed (only shorthand "grok" is in restriction list) + # Canonical name is not allowed unless explicitly listed + assert provider.validate_model_name("grok-4.1-fast") is False + + # grok-4 should NOT be allowed assert provider.validate_model_name("grok-4") is False - # "grok-3" should NOT be allowed (not in restriction list) - assert provider.validate_model_name("grok-3") is False - - # "grok-3-fast" should be allowed (explicitly listed) - assert provider.validate_model_name("grok-3-fast") is True - - # Shorthand "grokfast" should be allowed (resolves to grok-3-fast) - assert provider.validate_model_name("grokfast") is True - - @patch.dict(os.environ, {"XAI_ALLOWED_MODELS": "grok,grok-3,grok-4"}) + @patch.dict(os.environ, {"XAI_ALLOWED_MODELS": "grok,grok-4.1-fast"}) def test_both_shorthand_and_full_name_allowed(self): - """Test that both shorthand and full name can be allowed.""" + """Test that aliases and canonical names can be allowed together.""" # Clear cached restriction service import utils.model_restrictions @@ -223,12 +203,8 @@ class TestXAIProvider: # Both shorthand and full name should be allowed assert provider.validate_model_name("grok") is True # Resolves to grok-4 - assert provider.validate_model_name("grok-3") is True assert provider.validate_model_name("grok-4") is True - - # Other models should not be allowed - assert provider.validate_model_name("grok-3-fast") is False - assert provider.validate_model_name("grokfast") is False + assert provider.validate_model_name("grok-4.1-fast") is True @patch.dict(os.environ, {"XAI_ALLOWED_MODELS": ""}) def test_empty_restrictions_allows_all(self): @@ -241,10 +217,9 @@ class TestXAIProvider: provider = XAIModelProvider("test-key") assert provider.validate_model_name("grok-4") is True - assert provider.validate_model_name("grok-3") is True - assert provider.validate_model_name("grok-3-fast") is True + assert provider.validate_model_name("grok-4.1-fast") is True + assert provider.validate_model_name("grok-4.1-fast-reasoning") is True assert provider.validate_model_name("grok") is True - assert provider.validate_model_name("grokfast") is True assert provider.validate_model_name("grok4") is True def test_friendly_name(self): @@ -252,8 +227,8 @@ class TestXAIProvider: provider = XAIModelProvider("test-key") assert provider.FRIENDLY_NAME == "X.AI" - capabilities = provider.get_capabilities("grok-3") - assert capabilities.friendly_name == "X.AI (Grok 3)" + capabilities = provider.get_capabilities("grok-4") + assert capabilities.friendly_name == "X.AI (Grok 4)" def test_supported_models_structure(self): """Test that MODEL_CAPABILITIES has the correct structure.""" @@ -261,8 +236,7 @@ class TestXAIProvider: # Check that all expected base models are present assert "grok-4" in provider.MODEL_CAPABILITIES - assert "grok-3" in provider.MODEL_CAPABILITIES - assert "grok-3-fast" in provider.MODEL_CAPABILITIES + assert "grok-4-1-fast-reasoning" in provider.MODEL_CAPABILITIES # Check model configs have required fields from providers.shared import ModelCapabilities @@ -280,20 +254,11 @@ class TestXAIProvider: assert "grok-4" in grok4_config.aliases assert "grok4" in grok4_config.aliases - grok3_config = provider.MODEL_CAPABILITIES["grok-3"] - assert grok3_config.context_window == 131_072 - assert grok3_config.supports_extended_thinking is False - # Check aliases are correctly structured - assert "grok3" in grok3_config.aliases # grok3 resolves to grok-3 - - # Check grok-4 aliases - grok4_config = provider.MODEL_CAPABILITIES["grok-4"] - assert "grok" in grok4_config.aliases # grok resolves to grok-4 - assert "grok4" in grok4_config.aliases - - grok3fast_config = provider.MODEL_CAPABILITIES["grok-3-fast"] - assert "grok3fast" in grok3fast_config.aliases - assert "grokfast" in grok3fast_config.aliases + grok41fast_config = provider.MODEL_CAPABILITIES["grok-4-1-fast-reasoning"] + assert grok41fast_config.context_window == 2_000_000 + assert grok41fast_config.supports_extended_thinking is True + assert "grok-4.1-fast" in grok41fast_config.aliases + assert "grok-4.1-fast-reasoning" in grok41fast_config.aliases @patch("providers.openai_compatible.OpenAI") def test_generate_content_resolves_alias_before_api_call(self, mock_openai_class): @@ -376,19 +341,13 @@ class TestXAIProvider: call_kwargs = mock_client.chat.completions.create.call_args[1] assert call_kwargs["model"] == "grok-4" - # Test grok3 -> grok-3 - mock_response.model = "grok-3" - provider.generate_content(prompt="Test", model_name="grok3", temperature=0.7) + # Test grok-4.1-fast-reasoning -> grok-4-1-fast-reasoning + mock_response.model = "grok-4-1-fast-reasoning" + provider.generate_content(prompt="Test", model_name="grok-4.1-fast-reasoning", temperature=0.7) call_kwargs = mock_client.chat.completions.create.call_args[1] - assert call_kwargs["model"] == "grok-3" + assert call_kwargs["model"] == "grok-4-1-fast-reasoning" - # Test grokfast -> grok-3-fast - mock_response.model = "grok-3-fast" - provider.generate_content(prompt="Test", model_name="grokfast", temperature=0.7) + # Test grok-4.1-fast -> grok-4-1-fast-reasoning + provider.generate_content(prompt="Test", model_name="grok-4.1-fast", temperature=0.7) call_kwargs = mock_client.chat.completions.create.call_args[1] - assert call_kwargs["model"] == "grok-3-fast" - - # Test grok3fast -> grok-3-fast - provider.generate_content(prompt="Test", model_name="grok3fast", temperature=0.7) - call_kwargs = mock_client.chat.completions.create.call_args[1] - assert call_kwargs["model"] == "grok-3-fast" + assert call_kwargs["model"] == "grok-4-1-fast-reasoning" diff --git a/utils/model_restrictions.py b/utils/model_restrictions.py index d0d3ca1..c068391 100644 --- a/utils/model_restrictions.py +++ b/utils/model_restrictions.py @@ -16,7 +16,7 @@ Environment Variables: Example: OPENAI_ALLOWED_MODELS=o3-mini,o4-mini GOOGLE_ALLOWED_MODELS=flash - XAI_ALLOWED_MODELS=grok-3,grok-3-fast + XAI_ALLOWED_MODELS=grok-4,grok-4.1-fast-reasoning OPENROUTER_ALLOWED_MODELS=opus,sonnet,mistral """