From b9629cfb8ff9845d8c4ab992eb213a83c6113240 Mon Sep 17 00:00:00 2001 From: mairaarshad19 Date: Thu, 17 Sep 2026 20:59:36 +0500 Subject: [PATCH] feat: add native Gemini 3.8 Flash support (#7284) * feat(llm): add Gemini 3.8 Flash support - Register Gemini 3.8 Flash in CrewAI and CLI model catalogs - Add 1,048,576-token context window mapping - Add native Gemini provider support - Add Gemini 3.8 Flash to the CLI model picker - Add native model detection coverage for gemini/ and google/ formats - Add context window regression coverage Closes #7241 * test: add VCR cassette for gemini-3.8-flash * chore: stop tracking .env.test * Bringing back .env.test --------- Co-authored-by: Vidit Ostwal <110953813+Vidit-Ostwal@users.noreply.github.com> Co-authored-by: ViditOstwal --- lib/cli/src/crewai_cli/constants.py | 1 + lib/cli/src/crewai_cli/create_json_crew.py | 1 + lib/crewai/src/crewai/constants.py | 1 + lib/crewai/src/crewai/llm.py | 1 + lib/crewai/src/crewai/llms/constants.py | 2 + .../llms/providers/gemini/completion.py | 1 + ...emini_models[gemini-gemini-3.8-flash].yaml | 77 +++++++++++++++++++ lib/crewai/tests/llms/google/test_google.py | 50 +++++++++--- lib/crewai/tests/test_llm.py | 1 + 9 files changed, 123 insertions(+), 12 deletions(-) create mode 100644 lib/crewai/tests/cassettes/test_gemini_models[gemini-gemini-3.8-flash].yaml diff --git a/lib/cli/src/crewai_cli/constants.py b/lib/cli/src/crewai_cli/constants.py index c587147b3..c7b902b2a 100644 --- a/lib/cli/src/crewai_cli/constants.py +++ b/lib/cli/src/crewai_cli/constants.py @@ -177,6 +177,7 @@ MODELS: dict[str, list[str]] = { "claude-haiku-4-5-20251001", ], "gemini": [ + "gemini/gemini-3.8-flash", "gemini/gemini-3-pro-preview", "gemini/gemini-1.5-flash", "gemini/gemini-1.5-pro", diff --git a/lib/cli/src/crewai_cli/create_json_crew.py b/lib/cli/src/crewai_cli/create_json_crew.py index 3c7a0dba4..1586ba52a 100644 --- a/lib/cli/src/crewai_cli/create_json_crew.py +++ b/lib/cli/src/crewai_cli/create_json_crew.py @@ -75,6 +75,7 @@ _PROVIDER_MODELS: dict[str, list[tuple[str, str]]] = { ("claude-sonnet-4-6", "Claude Sonnet 4.6"), ], "gemini": [ + ("gemini-3.8-flash", "Gemini 3.8 Flash"), ("gemini-3.5-flash", "Gemini 3.5 Flash"), ("gemini-3.1-pro-preview", "Gemini 3.1 Pro (preview)"), ("gemini-3-flash-preview", "Gemini 3 Flash (preview)"), diff --git a/lib/crewai/src/crewai/constants.py b/lib/crewai/src/crewai/constants.py index 237a54dbc..84a10642d 100644 --- a/lib/crewai/src/crewai/constants.py +++ b/lib/crewai/src/crewai/constants.py @@ -173,6 +173,7 @@ MODELS: dict[str, list[str]] = { "claude-haiku-4-5-20251001", ], "gemini": [ + "gemini/gemini-3.8-flash", "gemini/gemini-3-pro-preview", "gemini/gemini-1.5-flash", "gemini/gemini-1.5-pro", diff --git a/lib/crewai/src/crewai/llm.py b/lib/crewai/src/crewai/llm.py index 0c95e3ddd..64d86403e 100644 --- a/lib/crewai/src/crewai/llm.py +++ b/lib/crewai/src/crewai/llm.py @@ -181,6 +181,7 @@ LLM_CONTEXT_WINDOW_SIZES: Final[dict[str, int]] = { "o1-mini": 128000, "o3-mini": 200000, "o4-mini": 200000, + "gemini-3.8-flash": 1048576, "gemini-3-pro-preview": 1048576, "gemini-2.0-flash": 1048576, "gemini-2.0-flash-thinking-exp-01-21": 32768, diff --git a/lib/crewai/src/crewai/llms/constants.py b/lib/crewai/src/crewai/llms/constants.py index 20697f6a5..b26a9e3e2 100644 --- a/lib/crewai/src/crewai/llms/constants.py +++ b/lib/crewai/src/crewai/llms/constants.py @@ -213,6 +213,7 @@ ANTHROPIC_MODELS: list[AnthropicModels] = [ ] GeminiModels: TypeAlias = Literal[ + "gemini-3.8-flash", "gemini-3-pro-preview", "gemini-3-flash-preview", "gemini-2.5-pro", @@ -268,6 +269,7 @@ GeminiModels: TypeAlias = Literal[ "learnlm-2.0-flash-experimental", ] GEMINI_MODELS: list[GeminiModels] = [ + "gemini-3.8-flash", "gemini-3-pro-preview", "gemini-3-flash-preview", "gemini-2.5-pro", diff --git a/lib/crewai/src/crewai/llms/providers/gemini/completion.py b/lib/crewai/src/crewai/llms/providers/gemini/completion.py index 68fc9d4d7..6ca6da229 100644 --- a/lib/crewai/src/crewai/llms/providers/gemini/completion.py +++ b/lib/crewai/src/crewai/llms/providers/gemini/completion.py @@ -1394,6 +1394,7 @@ class GeminiCompletion(BaseLLM): ) context_windows = { + "gemini-3.8-flash": 1048576, # 1M tokens "gemini-3-pro-preview": 1048576, # 1M tokens "gemini-2.0-flash": 1048576, # 1M tokens "gemini-2.0-flash-thinking": 32768, diff --git a/lib/crewai/tests/cassettes/test_gemini_models[gemini-gemini-3.8-flash].yaml b/lib/crewai/tests/cassettes/test_gemini_models[gemini-gemini-3.8-flash].yaml new file mode 100644 index 000000000..fc4ccecfb --- /dev/null +++ b/lib/crewai/tests/cassettes/test_gemini_models[gemini-gemini-3.8-flash].yaml @@ -0,0 +1,77 @@ +interactions: +- request: + body: '{"contents": [{"parts": [{"text": "What is the capital of France?"}], "role": + "user"}], "generationConfig": {"thinkingConfig": {"include_thoughts": true}}}' + headers: + User-Agent: + - X-USER-AGENT-XXX + accept: + - '*/*' + accept-encoding: + - ACCEPT-ENCODING-XXX + connection: + - keep-alive + content-length: + - '155' + content-type: + - application/json + host: + - generativelanguage.googleapis.com + x-goog-api-client: + - google-genai-sdk/1.65.0 gl-python/3.13.15 + x-goog-api-key: + - X-GOOG-API-KEY-XXX + method: POST + uri: https://generativelanguage.googleapis.com/v1beta/models/gemini-3.8-flash:generateContent + response: + body: + string: "{\n \"candidates\": [\n {\n \"content\": {\n \"parts\": + [\n {\n \"text\": \"**My Thought Process: Providing a + Direct Answer**\\n\\nOkay, so the user wants to know the capital of France. + Right, straightforward enough. My first step is to *immediately* identify + the central question: \\\"Capital of France?\\\" That's the core. No need + for fluff or ambiguity; clarity is key here, especially for someone who, presumably, + knows what they're looking for.\\n\\nNext, I need to retrieve the relevant + knowledge. Capital of France... Paris. Simple fact. Solidly established. No + debate.\\n\\nNow, how do I formulate the response? The user is likely very + well-versed in the specifics of cities and government, so I'll keep it concise + and accurate. A direct answer is all that's required. \\\"The capital of France + is Paris.\\\" That's it. Succinct, complete, and correct. Done.\\n\\n\\n\",\n + \ \"thought\": true\n },\n {\n \"text\": + \"The capital of France is **Paris**.\",\n \"thoughtSignature\": + \"EvUCCvICARFNMg8/4vvgyvFJnrzbczsj7uopma+hWpxZcfOjV0xuUc1aITo6NeNf3RNqb5f2TSPIbgZk4wempJOb2ZXI10ZGI9LmUi8uPKNHFKA0k4cbcUET6Y8eU18ymoOFAcBlIN7saWJm01yINKBkQSend2K9uoR1T94P8Q65JbGkcxKzOOoWe3c5Q66vonBxWmu++FxsWKmk2U8Spg1spMnZarzZJsl/mN0L3FY3fkejV5+i8EywJ8sU282s+Er6PnIfwV1Dmi08Ut3JsboqsiT4CFyp5uwBUqqo3soWa66tA0iV0I/S3nR2SSjWGb2XvLB1v/M1TxPWYkTuCSViRurPtT6MELSTYP+NwJ3UXqzj0amQhIPjXpXqozgSZZUDhPo4gMGr0b8sHp7uaqKOTt/PISw4211XDk4QLEcvZklozAoDggoqwXPUWsQUQqNUQxMP0rJZrNU7Zd04HKjVkFIM9BTvEaNhjMwrRCRrPMrXWYz1Pg==\"\n + \ }\n ],\n \"role\": \"model\"\n },\n \"finishReason\": + \"STOP\",\n \"index\": 0\n }\n ],\n \"usageMetadata\": {\n \"promptTokenCount\": + 8,\n \"candidatesTokenCount\": 8,\n \"totalTokenCount\": 80,\n \"promptTokensDetails\": + [\n {\n \"modality\": \"TEXT\",\n \"tokenCount\": 8\n }\n + \ ],\n \"thoughtsTokenCount\": 64,\n \"serviceTier\": \"standard\"\n + \ },\n \"modelVersion\": \"gemini-3.8-flash\",\n \"responseId\": \"WK2mauioB9nbxs0PnK3_8AM\"\n}\n" + headers: + alt-svc: + - h3=":443"; ma=2592000,h3-29=":443"; ma=2592000 + content-type: + - application/json; charset=UTF-8 + date: + - Sun, 13 Sep 2026 14:04:11 GMT + server: + - scaffolding on HTTPServer2 + server-timing: + - gfet4t7; dur=3461 + transfer-encoding: + - chunked + vary: + - Origin + - X-Origin + - Referer + x-content-type-options: + - X-CONTENT-TYPE-XXX + x-frame-options: + - X-FRAME-OPTIONS-XXX + x-gemini-service-tier: + - standard + x-xss-protection: + - X-XSS-PROTECTION-XXX + status: + code: 200 + message: OK +version: 1 diff --git a/lib/crewai/tests/llms/google/test_google.py b/lib/crewai/tests/llms/google/test_google.py index 17b0b2700..31fe5f8fa 100644 --- a/lib/crewai/tests/llms/google/test_google.py +++ b/lib/crewai/tests/llms/google/test_google.py @@ -20,27 +20,44 @@ def mock_google_api_key(): yield -def test_gemini_completion_is_used_when_google_provider(): +@pytest.mark.parametrize( + "model, expected_model", + [ + ("google/gemini-2.0-flash-001", "gemini-2.0-flash-001"), + ("google/gemini-3.8-flash", "gemini-3.8-flash"), + ], +) +def test_gemini_completion_is_used_when_google_provider(model, expected_model): """ - Test that GeminiCompletion from completion.py is used when LLM uses provider 'google' + Test that GeminiCompletion is used when LLM uses provider 'google' """ - llm = LLM(model="google/gemini-2.0-flash-001") + llm = LLM(model=model) assert llm.__class__.__name__ == "GeminiCompletion" assert llm.provider == "gemini" - assert llm.model == "gemini-2.0-flash-001" + assert llm.model == expected_model -def test_gemini_completion_is_used_when_gemini_provider(): + +@pytest.mark.parametrize( + "model, expected_model", + [ + ("gemini/gemini-2.0-flash-001", "gemini-2.0-flash-001"), + ("gemini/gemini-3.8-flash", "gemini-3.8-flash"), + ], +) +def test_gemini_completion_is_used_when_gemini_provider(model, expected_model): """ Test that GeminiCompletion is used when provider is 'gemini' """ - llm = LLM(model="gemini/gemini-2.0-flash-001") - from crewai.llms.providers.gemini.completion import GeminiCompletion + + llm = LLM(model=model) + assert isinstance(llm, GeminiCompletion) assert llm.provider == "gemini" - assert llm.model == "gemini-2.0-flash-001" + assert llm.model == expected_model + def test_gemini_completion_module_is_imported(): """ @@ -330,7 +347,7 @@ def test_gemini_completion_with_tools(): def test_gemini_raises_error_when_model_not_supported(): - """Test that GeminiCompletion raises ValueError when model not supported""" + """Test that GeminiCompletion raises an API error for an unsupported model.""" with patch('crewai.llms.providers.gemini.completion.genai') as mock_genai: mock_client = MagicMock() @@ -342,7 +359,7 @@ def test_gemini_raises_error_when_model_not_supported(): mock_response.body_segments = [{ 'error': { 'code': 404, - 'message': 'models/model-doesnt-exist is not found for API version v1beta, or is not supported for generateContent.', + 'message': 'models/gemini-model-doesnt-exist is not found for API version v1beta, or is not supported for generateContent.', 'status': 'NOT_FOUND' } }] @@ -350,11 +367,13 @@ def test_gemini_raises_error_when_model_not_supported(): mock_client.models.generate_content.side_effect = ClientError(404, mock_response) - llm = LLM(model="google/model-doesnt-exist") + llm = LLM(model="google/gemini-model-doesnt-exist") - with pytest.raises(Exception): # Should raise some error for unsupported model + with pytest.raises(ClientError, match="404"): llm.call("Hello") + mock_client.models.generate_content.assert_called_once() + def test_gemini_vertex_ai_setup(): """ @@ -440,6 +459,8 @@ def test_gemini_model_detection(): """ # Test Gemini model naming patterns that actually work with provider detection gemini_test_cases = [ + "gemini/gemini-3.8-flash", + "google/gemini-3.8-flash", "google/gemini-2.0-flash-001", "gemini/gemini-2.0-flash-001", "google/gemini-1.5-pro", @@ -469,6 +490,11 @@ def test_gemini_context_window_size(): context_size_2_0 = llm_2_0.get_context_window_size() assert context_size_2_0 > 500000 + # Test Gemini 3.8 Flash + llm_3_8 = LLM(model="google/gemini-3.8-flash") + context_size_3_8 = llm_3_8.get_context_window_size() + assert context_size_3_8 == 891289 + # Test Gemini 1.5 Pro llm_1_5 = LLM(model="google/gemini-1.5-pro") context_size_1_5 = llm_1_5.get_context_window_size() diff --git a/lib/crewai/tests/test_llm.py b/lib/crewai/tests/test_llm.py index ec15e8a3c..dde38fb9d 100644 --- a/lib/crewai/tests/test_llm.py +++ b/lib/crewai/tests/test_llm.py @@ -254,6 +254,7 @@ def test_validate_call_params_no_response_format(): @pytest.mark.parametrize( "model", [ + "gemini/gemini-3.8-flash", "gemini/gemini-3-pro-preview", "gemini/gemini-2.0-flash-thinking-exp-01-21", "gemini/gemini-2.0-flash-001",