mirror of
https://github.com/crewAIInc/crewAI.git
synced 2026-04-30 14:52:36 +00:00
Compare commits
12 Commits
devin/1746
...
lorenze/ag
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4789484523 | ||
|
|
b67f2a2c9b | ||
|
|
a30246503d | ||
|
|
55ed9b366f | ||
|
|
b9af25f434 | ||
|
|
1c2238066f | ||
|
|
bce6865b43 | ||
|
|
bd61c52371 | ||
|
|
eb9ae78158 | ||
|
|
5fa8cdbb48 | ||
|
|
a9decb54c8 | ||
|
|
e54a470493 |
38
.github/security.md
vendored
38
.github/security.md
vendored
@@ -1,27 +1,19 @@
|
||||
## CrewAI Security Vulnerability Reporting Policy
|
||||
CrewAI takes the security of our software products and services seriously, which includes all source code repositories managed through our GitHub organization.
|
||||
If you believe you have found a security vulnerability in any CrewAI product or service, please report it to us as described below.
|
||||
|
||||
CrewAI prioritizes the security of our software products, services, and GitHub repositories. To promptly address vulnerabilities, follow these steps for reporting security issues:
|
||||
## Reporting a Vulnerability
|
||||
Please do not report security vulnerabilities through public GitHub issues.
|
||||
To report a vulnerability, please email us at security@crewai.com.
|
||||
Please include the requested information listed below so that we can triage your report more quickly
|
||||
|
||||
### Reporting Process
|
||||
Do **not** report vulnerabilities via public GitHub issues.
|
||||
- Type of issue (e.g. SQL injection, cross-site scripting, etc.)
|
||||
- Full paths of source file(s) related to the manifestation of the issue
|
||||
- The location of the affected source code (tag/branch/commit or direct URL)
|
||||
- Any special configuration required to reproduce the issue
|
||||
- Step-by-step instructions to reproduce the issue (please include screenshots if needed)
|
||||
- Proof-of-concept or exploit code (if possible)
|
||||
- Impact of the issue, including how an attacker might exploit the issue
|
||||
|
||||
Email all vulnerability reports directly to:
|
||||
**security@crewai.com**
|
||||
Once we have received your report, we will respond to you at the email address you provide. If the issue is confirmed, we will release a patch as soon as possible depending on the complexity of the issue.
|
||||
|
||||
### Required Information
|
||||
To help us quickly validate and remediate the issue, your report must include:
|
||||
|
||||
- **Vulnerability Type:** Clearly state the vulnerability type (e.g., SQL injection, XSS, privilege escalation).
|
||||
- **Affected Source Code:** Provide full file paths and direct URLs (branch, tag, or commit).
|
||||
- **Reproduction Steps:** Include detailed, step-by-step instructions. Screenshots are recommended.
|
||||
- **Special Configuration:** Document any special settings or configurations required to reproduce.
|
||||
- **Proof-of-Concept (PoC):** Provide exploit or PoC code (if available).
|
||||
- **Impact Assessment:** Clearly explain the severity and potential exploitation scenarios.
|
||||
|
||||
### Our Response
|
||||
- We will acknowledge receipt of your report promptly via your provided email.
|
||||
- Confirmed vulnerabilities will receive priority remediation based on severity.
|
||||
- Patches will be released as swiftly as possible following verification.
|
||||
|
||||
### Reward Notice
|
||||
Currently, we do not offer a bug bounty program. Rewards, if issued, are discretionary.
|
||||
At this time, we are not offering a bug bounty program. Any rewards will be at our discretion.
|
||||
@@ -169,55 +169,19 @@ In this section, you'll find detailed examples that help you select, configure,
|
||||
```
|
||||
</Accordion>
|
||||
|
||||
<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).
|
||||
<Accordion title="Google">
|
||||
Set the following environment variables in your `.env` file:
|
||||
|
||||
```toml .env
|
||||
```toml Code
|
||||
# Option 1: Gemini accessed with an API key.
|
||||
# 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
|
||||
```
|
||||
|
||||
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:
|
||||
Get credentials from your Google Cloud Console and save it to a JSON file with the following code:
|
||||
```python Code
|
||||
import json
|
||||
|
||||
@@ -241,18 +205,14 @@ 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.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 |
|
||||
| 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 |
|
||||
</Accordion>
|
||||
|
||||
<Accordion title="Azure">
|
||||
|
||||
@@ -68,13 +68,7 @@ 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
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[project]
|
||||
name = "crewai"
|
||||
version = "0.119.0"
|
||||
version = "0.118.0"
|
||||
description = "Cutting-edge framework for orchestrating role-playing, autonomous AI agents. By fostering collaborative intelligence, CrewAI empowers agents to work together seamlessly, tackling complex tasks."
|
||||
readme = "README.md"
|
||||
requires-python = ">=3.10,<3.13"
|
||||
@@ -45,7 +45,7 @@ Documentation = "https://docs.crewai.com"
|
||||
Repository = "https://github.com/crewAIInc/crewAI"
|
||||
|
||||
[project.optional-dependencies]
|
||||
tools = ["crewai-tools~=0.44.0"]
|
||||
tools = ["crewai-tools~=0.42.2"]
|
||||
embeddings = [
|
||||
"tiktoken~=0.7.0"
|
||||
]
|
||||
|
||||
@@ -17,7 +17,7 @@ warnings.filterwarnings(
|
||||
category=UserWarning,
|
||||
module="pydantic.main",
|
||||
)
|
||||
__version__ = "0.119.0"
|
||||
__version__ = "0.118.0"
|
||||
__all__ = [
|
||||
"Agent",
|
||||
"Crew",
|
||||
|
||||
@@ -13,7 +13,7 @@ ENV_VARS = {
|
||||
],
|
||||
"gemini": [
|
||||
{
|
||||
"prompt": "Enter your GEMINI API key from https://ai.dev/apikey (press Enter to skip)",
|
||||
"prompt": "Enter your GEMINI API key (press Enter to skip)",
|
||||
"key_name": "GEMINI_API_KEY",
|
||||
}
|
||||
],
|
||||
|
||||
@@ -5,7 +5,7 @@ description = "{{name}} using crewAI"
|
||||
authors = [{ name = "Your Name", email = "you@example.com" }]
|
||||
requires-python = ">=3.10,<3.13"
|
||||
dependencies = [
|
||||
"crewai[tools]>=0.119.0,<1.0.0"
|
||||
"crewai[tools]>=0.118.0,<1.0.0"
|
||||
]
|
||||
|
||||
[project.scripts]
|
||||
|
||||
@@ -5,7 +5,7 @@ description = "{{name}} using crewAI"
|
||||
authors = [{ name = "Your Name", email = "you@example.com" }]
|
||||
requires-python = ">=3.10,<3.13"
|
||||
dependencies = [
|
||||
"crewai[tools]>=0.119.0,<1.0.0",
|
||||
"crewai[tools]>=0.118.0,<1.0.0",
|
||||
]
|
||||
|
||||
[project.scripts]
|
||||
|
||||
@@ -5,7 +5,7 @@ description = "Power up your crews with {{folder_name}}"
|
||||
readme = "README.md"
|
||||
requires-python = ">=3.10,<3.13"
|
||||
dependencies = [
|
||||
"crewai[tools]>=0.119.0"
|
||||
"crewai[tools]>=0.118.0"
|
||||
]
|
||||
|
||||
[tool.crewai]
|
||||
|
||||
@@ -246,9 +246,6 @@ class AccumulatedToolArgs(BaseModel):
|
||||
|
||||
|
||||
class LLM(BaseLLM):
|
||||
ANTHROPIC_PREFIXES = ("anthropic/", "claude-", "claude/")
|
||||
GEMINI_IDENTIFIERS = ("gemini", "gemma-")
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
model: str,
|
||||
@@ -322,55 +319,8 @@ class LLM(BaseLLM):
|
||||
Returns:
|
||||
bool: True if the model is from Anthropic, False otherwise.
|
||||
"""
|
||||
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
|
||||
ANTHROPIC_PREFIXES = ("anthropic/", "claude-", "claude/")
|
||||
return any(prefix in model.lower() for prefix in ANTHROPIC_PREFIXES)
|
||||
|
||||
def _prepare_completion_params(
|
||||
self,
|
||||
@@ -393,23 +343,9 @@ class LLM(BaseLLM):
|
||||
messages = [{"role": "user", "content": messages}]
|
||||
formatted_messages = self._format_messages_for_provider(messages)
|
||||
|
||||
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
|
||||
# --- 2) Prepare the parameters for the completion call
|
||||
params = {
|
||||
"model": model,
|
||||
"model": self.model,
|
||||
"messages": formatted_messages,
|
||||
"timeout": self.timeout,
|
||||
"temperature": self.temperature,
|
||||
|
||||
@@ -220,37 +220,6 @@ 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
|
||||
@@ -305,82 +274,6 @@ 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",
|
||||
|
||||
10
uv.lock
generated
10
uv.lock
generated
@@ -738,7 +738,7 @@ wheels = [
|
||||
|
||||
[[package]]
|
||||
name = "crewai"
|
||||
version = "0.119.0"
|
||||
version = "0.118.0"
|
||||
source = { editable = "." }
|
||||
dependencies = [
|
||||
{ name = "appdirs" },
|
||||
@@ -828,7 +828,7 @@ requires-dist = [
|
||||
{ name = "blinker", specifier = ">=1.9.0" },
|
||||
{ name = "chromadb", specifier = ">=0.5.23" },
|
||||
{ name = "click", specifier = ">=8.1.7" },
|
||||
{ name = "crewai-tools", marker = "extra == 'tools'", specifier = "~=0.44.0" },
|
||||
{ name = "crewai-tools", marker = "extra == 'tools'", specifier = "~=0.42.2" },
|
||||
{ name = "docling", marker = "extra == 'docling'", specifier = ">=2.12.0" },
|
||||
{ name = "fastembed", marker = "extra == 'fastembed'", specifier = ">=0.4.1" },
|
||||
{ name = "instructor", specifier = ">=1.3.3" },
|
||||
@@ -879,7 +879,7 @@ dev = [
|
||||
|
||||
[[package]]
|
||||
name = "crewai-tools"
|
||||
version = "0.44.0"
|
||||
version = "0.42.2"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "chromadb" },
|
||||
@@ -894,9 +894,9 @@ dependencies = [
|
||||
{ name = "pytube" },
|
||||
{ name = "requests" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/b8/1f/2977dc72628c1225bf5788ae22a65e5a53df384d19b197646d2c4760684e/crewai_tools-0.44.0.tar.gz", hash = "sha256:44e0c26079396503a326efdd9ff34bf369d410cbf95c362cc523db65b18f3c3a", size = 892004 }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/17/34/9e63e2db53d8f5c30353f271a3240687a48e55204bbd176a057c0b7658c8/crewai_tools-0.42.2.tar.gz", hash = "sha256:69365ffb168cccfea970e09b308905aa5007cfec60024d731ffac1362a0153c0", size = 754967 }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/ba/80/b91aa837d06edbb472445ea3c92d7619518894fd3049d480e5fffbf0c21b/crewai_tools-0.44.0-py3-none-any.whl", hash = "sha256:119e2365fe66ee16e18a5e8e222994b19f76bafcc8c1bb87f61609c1e39b2463", size = 583462 },
|
||||
{ url = "https://files.pythonhosted.org/packages/4e/43/0f70b95350084e5cb1e1d74e9acb9e18a89ba675b1d579c787c2662baba7/crewai_tools-0.42.2-py3-none-any.whl", hash = "sha256:13727fb68f0efefd21edeb281be3d66ff2f5a3b5029d4e6adef388b11fd5846a", size = 583933 },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
||||
Reference in New Issue
Block a user