Compare commits

..

6 Commits

Author SHA1 Message Date
Devin AI
5b03d0e0db Address PR review feedback: move constants to class level, add error handling, enhance logging
Co-Authored-By: Joe Moura <joao@crewai.com>
2025-05-10 11:00:51 +00:00
Devin AI
ee308ed322 Fix Gemini model integration issues (#2803)
Co-Authored-By: Joe Moura <joao@crewai.com>
2025-05-10 10:51:46 +00:00
Orce MARINKOVSKI
cb1a98cabf Update arize-phoenix-observability.mdx (#2595)
Some checks failed
Notify Downstream / notify-downstream (push) Has been cancelled
Mark stale issues and pull requests / stale (push) Has been cancelled
missing code to kickoff the monitoring for the crew

Co-authored-by: Lorenze Jay <63378463+lorenzejay@users.noreply.github.com>
Co-authored-by: Tony Kipkemboi <iamtonykipkemboi@gmail.com>
2025-05-08 13:25:10 -04:00
Mark McDonald
369e6d109c Adds link to AI Studio when entering Gemini key (#2780)
I used ai.dev as the alternate URL as it takes up less space but if this
is likely to confuse users we can use the long form.

Co-authored-by: Tony Kipkemboi <iamtonykipkemboi@gmail.com>
2025-05-08 13:00:03 -04:00
Mark McDonald
2c011631f9 Clean up the Google setup section (#2785)
The Gemini & Vertex sections were conflated and a little hard to
distingush, so I have put them in separate sections.

Also added the latest 2.5 and 2.0 flash-lite models, and added a note
that Gemma models work too.

Co-authored-by: Tony Kipkemboi <iamtonykipkemboi@gmail.com>
2025-05-08 12:24:38 -04:00
Rip&Tear
d3fc2b4477 Update security.md (#2779)
update policy for better readability
2025-05-08 09:00:41 -04:00
5 changed files with 236 additions and 19 deletions

View File

@@ -169,19 +169,55 @@ In this section, you'll find detailed examples that help you select, configure,
```
</Accordion>
<Accordion title="Google">
Set the following environment variables in your `.env` file:
<Accordion title="Google (Gemini API)">
Set your API key in your `.env` file. If you need a key, or need to find an
existing key, check [AI Studio](https://aistudio.google.com/apikey).
```toml Code
# Option 1: Gemini accessed with an API key.
```toml .env
# https://ai.google.dev/gemini-api/docs/api-key
GEMINI_API_KEY=<your-api-key>
# Option 2: Vertex AI IAM credentials for Gemini, Anthropic, and Model Garden.
# https://cloud.google.com/vertex-ai/generative-ai/docs/overview
```
Get credentials from your Google Cloud Console and save it to a JSON file with the following code:
Example usage in your CrewAI project:
```python Code
from crewai import LLM
llm = LLM(
model="gemini/gemini-2.0-flash",
temperature=0.7,
)
```
### Gemini models
Google offers a range of powerful models optimized for different use cases.
| Model | Context Window | Best For |
|--------------------------------|----------------|-------------------------------------------------------------------|
| gemini-2.5-flash-preview-04-17 | 1M tokens | Adaptive thinking, cost efficiency |
| gemini-2.5-pro-preview-05-06 | 1M tokens | Enhanced thinking and reasoning, multimodal understanding, advanced coding, and more |
| gemini-2.0-flash | 1M tokens | Next generation features, speed, thinking, and realtime streaming |
| gemini-2.0-flash-lite | 1M tokens | Cost efficiency and low latency |
| gemini-1.5-flash | 1M tokens | Balanced multimodal model, good for most tasks |
| gemini-1.5-flash-8B | 1M tokens | Fastest, most cost-efficient, good for high-frequency tasks |
| gemini-1.5-pro | 2M tokens | Best performing, wide variety of reasoning tasks including logical reasoning, coding, and creative collaboration |
The full list of models is available in the [Gemini model docs](https://ai.google.dev/gemini-api/docs/models).
### Gemma
The Gemini API also allows you to use your API key to access [Gemma models](https://ai.google.dev/gemma/docs) hosted on Google infrastructure.
| Model | Context Window |
|----------------|----------------|
| gemma-3-1b-it | 32k tokens |
| gemma-3-4b-it | 32k tokens |
| gemma-3-12b-it | 32k tokens |
| gemma-3-27b-it | 128k tokens |
</Accordion>
<Accordion title="Google (Vertex AI)">
Get credentials from your Google Cloud Console and save it to a JSON file, then load it with the following code:
```python Code
import json
@@ -205,14 +241,18 @@ In this section, you'll find detailed examples that help you select, configure,
vertex_credentials=vertex_credentials_json
)
```
Google offers a range of powerful models optimized for different use cases:
| Model | Context Window | Best For |
|-----------------------|----------------|------------------------------------------------------------------|
| gemini-2.0-flash-exp | 1M tokens | Higher quality at faster speed, multimodal model, good for most tasks |
| gemini-1.5-flash | 1M tokens | Balanced multimodal model, good for most tasks |
| gemini-1.5-flash-8B | 1M tokens | Fastest, most cost-efficient, good for high-frequency tasks |
| gemini-1.5-pro | 2M tokens | Best performing, wide variety of reasoning tasks including logical reasoning, coding, and creative collaboration |
| Model | Context Window | Best For |
|--------------------------------|----------------|-------------------------------------------------------------------|
| gemini-2.5-flash-preview-04-17 | 1M tokens | Adaptive thinking, cost efficiency |
| gemini-2.5-pro-preview-05-06 | 1M tokens | Enhanced thinking and reasoning, multimodal understanding, advanced coding, and more |
| gemini-2.0-flash | 1M tokens | Next generation features, speed, thinking, and realtime streaming |
| gemini-2.0-flash-lite | 1M tokens | Cost efficiency and low latency |
| gemini-1.5-flash | 1M tokens | Balanced multimodal model, good for most tasks |
| gemini-1.5-flash-8B | 1M tokens | Fastest, most cost-efficient, good for high-frequency tasks |
| gemini-1.5-pro | 2M tokens | Best performing, wide variety of reasoning tasks including logical reasoning, coding, and creative collaboration |
</Accordion>
<Accordion title="Azure">

View File

@@ -68,7 +68,13 @@ We'll create a CrewAI application where two agents collaborate to research and w
```python
from crewai import Agent, Crew, Process, Task
from crewai_tools import SerperDevTool
from openinference.instrumentation.crewai import CrewAIInstrumentor
from phoenix.otel import register
# setup monitoring for your crew
tracer_provider = register(
endpoint="http://localhost:6006/v1/traces")
CrewAIInstrumentor().instrument(skip_dep_check=True, tracer_provider=tracer_provider)
search_tool = SerperDevTool()
# Define your agents with roles and goals

View File

@@ -13,7 +13,7 @@ ENV_VARS = {
],
"gemini": [
{
"prompt": "Enter your GEMINI API key (press Enter to skip)",
"prompt": "Enter your GEMINI API key from https://ai.dev/apikey (press Enter to skip)",
"key_name": "GEMINI_API_KEY",
}
],

View File

@@ -246,6 +246,9 @@ class AccumulatedToolArgs(BaseModel):
class LLM(BaseLLM):
ANTHROPIC_PREFIXES = ("anthropic/", "claude-", "claude/")
GEMINI_IDENTIFIERS = ("gemini", "gemma-")
def __init__(
self,
model: str,
@@ -319,8 +322,55 @@ class LLM(BaseLLM):
Returns:
bool: True if the model is from Anthropic, False otherwise.
"""
ANTHROPIC_PREFIXES = ("anthropic/", "claude-", "claude/")
return any(prefix in model.lower() for prefix in ANTHROPIC_PREFIXES)
if not isinstance(model, str):
return False
return any(prefix in model.lower() for prefix in self.ANTHROPIC_PREFIXES)
def _is_gemini_model(self, model: str) -> bool:
"""Determine if the model is from Google Gemini provider.
Args:
model: The model identifier string.
Returns:
bool: True if the model is from Gemini, False otherwise.
"""
if not isinstance(model, str):
return False
return any(identifier in model.lower() for identifier in self.GEMINI_IDENTIFIERS)
def _normalize_gemini_model(self, model: str) -> str:
"""Normalize Gemini model name to the format expected by LiteLLM.
Handles formats like "models/gemini-pro" or "gemini-pro" and converts
them to "gemini/gemini-pro" format.
Args:
model: The model identifier string.
Returns:
str: Normalized model name.
Raises:
ValueError: If model is not a string or is empty.
"""
if not isinstance(model, str):
raise ValueError(f"Model must be a string, got {type(model)}")
if not model.strip():
raise ValueError("Model name cannot be empty")
if model.startswith("gemini/"):
return model
if model.startswith("models/"):
model_name = model.split("/", 1)[1]
return f"gemini/{model_name}"
if self._is_gemini_model(model) and "/" not in model:
return f"gemini/{model}"
return model
def _prepare_completion_params(
self,
@@ -343,9 +393,23 @@ class LLM(BaseLLM):
messages = [{"role": "user", "content": messages}]
formatted_messages = self._format_messages_for_provider(messages)
# --- 2) Prepare the parameters for the completion call
model = self.model
if self._is_gemini_model(model):
try:
model = self._normalize_gemini_model(model)
logging.info(f"Normalized Gemini model name from '{self.model}' to '{model}'")
# --- 2.1) Map GOOGLE_API_KEY to GEMINI_API_KEY if needed
if not os.environ.get("GEMINI_API_KEY") and os.environ.get("GOOGLE_API_KEY"):
os.environ["GEMINI_API_KEY"] = os.environ["GOOGLE_API_KEY"]
logging.info("Mapped GOOGLE_API_KEY to GEMINI_API_KEY for Gemini model")
except ValueError as e:
logging.error(f"Error normalizing Gemini model: {str(e)}")
model = self.model
# --- 3) Prepare the parameters for the completion call
params = {
"model": self.model,
"model": model,
"messages": formatted_messages,
"timeout": self.timeout,
"temperature": self.temperature,

View File

@@ -220,6 +220,37 @@ def test_get_custom_llm_provider_gemini():
assert llm._get_custom_llm_provider() == "gemini"
def test_is_gemini_model():
"""Test the _is_gemini_model method with various model names."""
llm = LLM(model="gpt-4") # Model doesn't matter for this test
assert llm._is_gemini_model("gemini-pro") == True
assert llm._is_gemini_model("gemini/gemini-1.5-pro") == True
assert llm._is_gemini_model("models/gemini-pro") == True
assert llm._is_gemini_model("gemma-7b") == True
# Should not identify as Gemini models
assert llm._is_gemini_model("gpt-4") == False
assert llm._is_gemini_model("claude-3") == False
assert llm._is_gemini_model("mistral-7b") == False
def test_normalize_gemini_model():
"""Test the _normalize_gemini_model method with various model formats."""
llm = LLM(model="gpt-4") # Model doesn't matter for this test
assert llm._normalize_gemini_model("gemini/gemini-1.5-pro") == "gemini/gemini-1.5-pro"
assert llm._normalize_gemini_model("models/gemini-pro") == "gemini/gemini-pro"
assert llm._normalize_gemini_model("models/gemini-1.5-flash") == "gemini/gemini-1.5-flash"
assert llm._normalize_gemini_model("gemini-pro") == "gemini/gemini-pro"
assert llm._normalize_gemini_model("gemini-1.5-flash") == "gemini/gemini-1.5-flash"
assert llm._normalize_gemini_model("gpt-4") == "gpt-4"
assert llm._normalize_gemini_model("claude-3") == "claude-3"
def test_get_custom_llm_provider_openai():
llm = LLM(model="gpt-4")
assert llm._get_custom_llm_provider() == None
@@ -274,6 +305,82 @@ def test_gemini_models(model):
assert "Paris" in result
@pytest.mark.vcr(filter_headers=["authorization"], filter_query_parameters=["key"])
@pytest.mark.parametrize(
"model",
[
"models/gemini-pro", # Format from issue #2803
"gemini-pro", # Format without provider prefix
],
)
def test_gemini_model_normalization(model):
"""Test that different Gemini model formats are normalized correctly."""
llm = LLM(model=model)
with patch("litellm.completion") as mock_completion:
# Create mocks for response structure
mock_message = MagicMock()
mock_message.content = "Paris"
mock_choice = MagicMock()
mock_choice.message = mock_message
mock_response = MagicMock()
mock_response.choices = [mock_choice]
# Set up the mocked completion to return the mock response
mock_completion.return_value = mock_response
llm.call("What is the capital of France?")
# Check that the model was normalized correctly in the call to litellm
args, kwargs = mock_completion.call_args
assert kwargs["model"].startswith("gemini/")
assert "gemini-pro" in kwargs["model"]
@pytest.mark.vcr(filter_headers=["authorization"], filter_query_parameters=["key"])
def test_gemini_api_key_mapping():
"""Test that GOOGLE_API_KEY is mapped to GEMINI_API_KEY for Gemini models."""
original_google_api_key = os.environ.get("GOOGLE_API_KEY")
original_gemini_api_key = os.environ.get("GEMINI_API_KEY")
try:
# Set up test environment
test_api_key = "test_google_api_key"
os.environ["GOOGLE_API_KEY"] = test_api_key
if "GEMINI_API_KEY" in os.environ:
del os.environ["GEMINI_API_KEY"]
llm = LLM(model="gemini-pro")
with patch("litellm.completion") as mock_completion:
# Create mocks for response structure
mock_message = MagicMock()
mock_message.content = "Paris"
mock_choice = MagicMock()
mock_choice.message = mock_message
mock_response = MagicMock()
mock_response.choices = [mock_choice]
# Set up the mocked completion to return the mock response
mock_completion.return_value = mock_response
llm.call("What is the capital of France?")
# Check that GEMINI_API_KEY was set from GOOGLE_API_KEY
assert os.environ.get("GEMINI_API_KEY") == test_api_key
finally:
if original_google_api_key is not None:
os.environ["GOOGLE_API_KEY"] = original_google_api_key
else:
os.environ.pop("GOOGLE_API_KEY", None)
if original_gemini_api_key is not None:
os.environ["GEMINI_API_KEY"] = original_gemini_api_key
else:
os.environ.pop("GEMINI_API_KEY", None)
@pytest.mark.vcr(filter_headers=["authorization"], filter_query_parameters=["key"])
@pytest.mark.parametrize(
"model",