mirror of
https://github.com/crewAIInc/crewAI.git
synced 2026-02-03 12:38:14 +00:00
Compare commits
14 Commits
feat/impro
...
feat/ibm-m
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5cd79b7345 | ||
|
|
377793af42 | ||
|
|
56cea8fb93 | ||
|
|
9933d8f880 | ||
|
|
12b0cf6100 | ||
|
|
66698503b8 | ||
|
|
ec2967c362 | ||
|
|
4ae07468f3 | ||
|
|
6193eb13fa | ||
|
|
55cd15bfc6 | ||
|
|
5f46ff8836 | ||
|
|
cdfbd5f62b | ||
|
|
b43f3987ec | ||
|
|
240527d06c |
19
.github/security.md
vendored
Normal file
19
.github/security.md
vendored
Normal file
@@ -0,0 +1,19 @@
|
||||
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.
|
||||
|
||||
## 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
|
||||
|
||||
- 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
|
||||
|
||||
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.
|
||||
|
||||
At this time, we are not offering a bug bounty program. Any rewards will be at our discretion.
|
||||
@@ -560,6 +560,40 @@ uv run kickoff
|
||||
|
||||
The flow will execute, and you should see the output in the console.
|
||||
|
||||
|
||||
### Adding Additional Crews Using the CLI
|
||||
|
||||
Once you have created your initial flow, you can easily add additional crews to your project using the CLI. This allows you to expand your flow's capabilities by integrating new crews without starting from scratch.
|
||||
|
||||
To add a new crew to your existing flow, use the following command:
|
||||
|
||||
```bash
|
||||
crewai flow add-crew <crew_name>
|
||||
```
|
||||
|
||||
This command will create a new directory for your crew within the `crews` folder of your flow project. It will include the necessary configuration files and a crew definition file, similar to the initial setup.
|
||||
|
||||
#### Folder Structure
|
||||
|
||||
After adding a new crew, your folder structure will look like this:
|
||||
|
||||
name_of_flow/
|
||||
├── crews/
|
||||
│ ├── poem_crew/
|
||||
│ │ ├── config/
|
||||
│ │ │ ├── agents.yaml
|
||||
│ │ │ └── tasks.yaml
|
||||
│ │ └── poem_crew.py
|
||||
│ └── name_of_crew/
|
||||
│ ├── config/
|
||||
│ │ ├── agents.yaml
|
||||
│ │ └── tasks.yaml
|
||||
│ └── name_of_crew.py
|
||||
|
||||
You can then customize the `agents.yaml` and `tasks.yaml` files to define the agents and tasks for your new crew. The `name_of_crew.py` file will contain the crew's logic, which you can modify to suit your needs.
|
||||
|
||||
By using the CLI to add additional crews, you can efficiently build complex AI workflows that leverage multiple crews working together.
|
||||
|
||||
## Plot Flows
|
||||
|
||||
Visualizing your AI workflows can provide valuable insights into the structure and execution paths of your flows. CrewAI offers a powerful visualization tool that allows you to generate interactive plots of your flows, making it easier to understand and optimize your AI workflows.
|
||||
|
||||
@@ -25,52 +25,55 @@ By default, CrewAI uses the `gpt-4o-mini` model. It uses environment variables i
|
||||
- `OPENAI_API_BASE`
|
||||
- `OPENAI_API_KEY`
|
||||
|
||||
### 2. String Identifier
|
||||
|
||||
```python Code
|
||||
agent = Agent(llm="gpt-4o", ...)
|
||||
```
|
||||
|
||||
### 3. LLM Instance
|
||||
|
||||
List of [more providers](https://docs.litellm.ai/docs/providers).
|
||||
|
||||
```python Code
|
||||
from crewai import LLM
|
||||
|
||||
llm = LLM(model="gpt-4", temperature=0.7)
|
||||
agent = Agent(llm=llm, ...)
|
||||
```
|
||||
|
||||
### 4. Custom LLM Objects
|
||||
### 2. Custom LLM Objects
|
||||
|
||||
Pass a custom LLM implementation or object from another library.
|
||||
|
||||
See below for examples.
|
||||
|
||||
<Tabs>
|
||||
<Tab title="String Identifier">
|
||||
```python Code
|
||||
agent = Agent(llm="gpt-4o", ...)
|
||||
```
|
||||
</Tab>
|
||||
|
||||
<Tab title="LLM Instance">
|
||||
```python Code
|
||||
from crewai import LLM
|
||||
|
||||
llm = LLM(model="gpt-4", temperature=0.7)
|
||||
agent = Agent(llm=llm, ...)
|
||||
```
|
||||
</Tab>
|
||||
</Tabs>
|
||||
|
||||
## Connecting to OpenAI-Compatible LLMs
|
||||
|
||||
You can connect to OpenAI-compatible LLMs using either environment variables or by setting specific attributes on the LLM class:
|
||||
|
||||
1. Using environment variables:
|
||||
<Tabs>
|
||||
<Tab title="Using Environment Variables">
|
||||
```python Code
|
||||
import os
|
||||
|
||||
```python Code
|
||||
import os
|
||||
os.environ["OPENAI_API_KEY"] = "your-api-key"
|
||||
os.environ["OPENAI_API_BASE"] = "https://api.your-provider.com/v1"
|
||||
```
|
||||
</Tab>
|
||||
<Tab title="Using LLM Class Attributes">
|
||||
```python Code
|
||||
from crewai import LLM
|
||||
|
||||
os.environ["OPENAI_API_KEY"] = "your-api-key"
|
||||
os.environ["OPENAI_API_BASE"] = "https://api.your-provider.com/v1"
|
||||
```
|
||||
|
||||
2. Using LLM class attributes:
|
||||
|
||||
```python Code
|
||||
from crewai import LLM
|
||||
|
||||
llm = LLM(
|
||||
model="custom-model-name",
|
||||
api_key="your-api-key",
|
||||
base_url="https://api.your-provider.com/v1"
|
||||
)
|
||||
agent = Agent(llm=llm, ...)
|
||||
```
|
||||
llm = LLM(
|
||||
model="custom-model-name",
|
||||
api_key="your-api-key",
|
||||
base_url="https://api.your-provider.com/v1"
|
||||
)
|
||||
agent = Agent(llm=llm, ...)
|
||||
```
|
||||
</Tab>
|
||||
</Tabs>
|
||||
|
||||
## LLM Configuration Options
|
||||
|
||||
@@ -97,55 +100,149 @@ When configuring an LLM for your agent, you have access to a wide range of param
|
||||
| **api_key** | `str` | Your API key for authentication. |
|
||||
|
||||
|
||||
## OpenAI Example Configuration
|
||||
These are examples of how to configure LLMs for your agent.
|
||||
|
||||
```python Code
|
||||
from crewai import LLM
|
||||
<AccordionGroup>
|
||||
<Accordion title="OpenAI">
|
||||
|
||||
llm = LLM(
|
||||
model="gpt-4",
|
||||
temperature=0.8,
|
||||
max_tokens=150,
|
||||
top_p=0.9,
|
||||
frequency_penalty=0.1,
|
||||
presence_penalty=0.1,
|
||||
stop=["END"],
|
||||
seed=42,
|
||||
base_url="https://api.openai.com/v1",
|
||||
api_key="your-api-key-here"
|
||||
)
|
||||
agent = Agent(llm=llm, ...)
|
||||
```
|
||||
```python Code
|
||||
from crewai import LLM
|
||||
|
||||
## Cerebras Example Configuration
|
||||
llm = LLM(
|
||||
model="gpt-4",
|
||||
temperature=0.8,
|
||||
max_tokens=150,
|
||||
top_p=0.9,
|
||||
frequency_penalty=0.1,
|
||||
presence_penalty=0.1,
|
||||
stop=["END"],
|
||||
seed=42,
|
||||
base_url="https://api.openai.com/v1",
|
||||
api_key="your-api-key-here"
|
||||
)
|
||||
agent = Agent(llm=llm, ...)
|
||||
```
|
||||
</Accordion>
|
||||
|
||||
```python Code
|
||||
from crewai import LLM
|
||||
<Accordion title="Cerebras">
|
||||
|
||||
llm = LLM(
|
||||
model="cerebras/llama-3.1-70b",
|
||||
base_url="https://api.cerebras.ai/v1",
|
||||
api_key="your-api-key-here"
|
||||
)
|
||||
agent = Agent(llm=llm, ...)
|
||||
```
|
||||
```python Code
|
||||
from crewai import LLM
|
||||
|
||||
## Using Ollama (Local LLMs)
|
||||
llm = LLM(
|
||||
model="cerebras/llama-3.1-70b",
|
||||
base_url="https://api.cerebras.ai/v1",
|
||||
api_key="your-api-key-here"
|
||||
)
|
||||
agent = Agent(llm=llm, ...)
|
||||
```
|
||||
</Accordion>
|
||||
|
||||
<Accordion title="Ollama (Local LLMs)">
|
||||
|
||||
CrewAI supports using Ollama for running open-source models locally:
|
||||
CrewAI supports using Ollama for running open-source models locally:
|
||||
|
||||
1. Install Ollama: [ollama.ai](https://ollama.ai/)
|
||||
2. Run a model: `ollama run llama2`
|
||||
3. Configure agent:
|
||||
1. Install Ollama: [ollama.ai](https://ollama.ai/)
|
||||
2. Run a model: `ollama run llama2`
|
||||
3. Configure agent:
|
||||
|
||||
```python Code
|
||||
from crewai import LLM
|
||||
```python Code
|
||||
from crewai import LLM
|
||||
|
||||
agent = Agent(
|
||||
llm=LLM(model="ollama/llama3.1", base_url="http://localhost:11434"),
|
||||
...
|
||||
)
|
||||
```
|
||||
agent = Agent(
|
||||
llm=LLM(
|
||||
model="ollama/llama3.1",
|
||||
base_url="http://localhost:11434"
|
||||
),
|
||||
...
|
||||
)
|
||||
```
|
||||
</Accordion>
|
||||
|
||||
<Accordion title="Groq">
|
||||
|
||||
```python Code
|
||||
from crewai import LLM
|
||||
|
||||
llm = LLM(
|
||||
model="groq/llama3-8b-8192",
|
||||
base_url="https://api.groq.com/openai/v1",
|
||||
api_key="your-api-key-here"
|
||||
)
|
||||
agent = Agent(llm=llm, ...)
|
||||
```
|
||||
</Accordion>
|
||||
|
||||
<Accordion title="Anthropic">
|
||||
|
||||
```python Code
|
||||
from crewai import LLM
|
||||
|
||||
llm = LLM(
|
||||
model="anthropic/claude-3-5-sonnet-20241022",
|
||||
base_url="https://api.anthropic.com/v1",
|
||||
api_key="your-api-key-here"
|
||||
)
|
||||
agent = Agent(llm=llm, ...)
|
||||
```
|
||||
</Accordion>
|
||||
|
||||
<Accordion title="Fireworks">
|
||||
|
||||
```python Code
|
||||
from crewai import LLM
|
||||
|
||||
llm = LLM(
|
||||
model="fireworks/meta-llama-3.1-8b-instruct",
|
||||
base_url="https://api.fireworks.ai/inference/v1",
|
||||
api_key="your-api-key-here"
|
||||
)
|
||||
agent = Agent(llm=llm, ...)
|
||||
```
|
||||
</Accordion>
|
||||
|
||||
<Accordion title="Gemini">
|
||||
|
||||
```python Code
|
||||
from crewai import LLM
|
||||
|
||||
llm = LLM(
|
||||
model="gemini/gemini-1.5-flash",
|
||||
base_url="https://api.gemini.google.com/v1",
|
||||
api_key="your-api-key-here"
|
||||
)
|
||||
agent = Agent(llm=llm, ...)
|
||||
```
|
||||
</Accordion>
|
||||
|
||||
<Accordion title="Perplexity AI (pplx-api)">
|
||||
|
||||
```python Code
|
||||
from crewai import LLM
|
||||
|
||||
llm = LLM(
|
||||
model="perplexity/mistral-7b-instruct",
|
||||
base_url="https://api.perplexity.ai/v1",
|
||||
api_key="your-api-key-here"
|
||||
)
|
||||
agent = Agent(llm=llm, ...)
|
||||
```
|
||||
</Accordion>
|
||||
|
||||
<Accordion title="IBM watsonx.ai">
|
||||
|
||||
```python Code
|
||||
from crewai import LLM
|
||||
|
||||
llm = LLM(
|
||||
model="watsonx/ibm/granite-13b-chat-v2",
|
||||
base_url="https://api.watsonx.ai/v1",
|
||||
api_key="your-api-key-here"
|
||||
)
|
||||
agent = Agent(llm=llm, ...)
|
||||
```
|
||||
</Accordion>
|
||||
</AccordionGroup>
|
||||
|
||||
## Changing the Base API URL
|
||||
|
||||
|
||||
@@ -254,6 +254,31 @@ my_crew = Crew(
|
||||
)
|
||||
```
|
||||
|
||||
### Using Watson embeddings
|
||||
|
||||
```python Code
|
||||
from crewai import Crew, Agent, Task, Process
|
||||
|
||||
# Note: Ensure you have installed and imported `ibm_watsonx_ai` for Watson embeddings to work.
|
||||
|
||||
my_crew = Crew(
|
||||
agents=[...],
|
||||
tasks=[...],
|
||||
process=Process.sequential,
|
||||
memory=True,
|
||||
verbose=True,
|
||||
embedder={
|
||||
"provider": "watson",
|
||||
"config": {
|
||||
"model": "<model_name>",
|
||||
"api_url": "<api_url>",
|
||||
"api_key": "<YOUR_API_KEY>",
|
||||
"project_id": "<YOUR_PROJECT_ID>",
|
||||
}
|
||||
}
|
||||
)
|
||||
```
|
||||
|
||||
### Resetting Memory
|
||||
|
||||
```shell
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[project]
|
||||
name = "crewai"
|
||||
version = "0.76.2"
|
||||
version = "0.76.9"
|
||||
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"
|
||||
@@ -16,7 +16,7 @@ dependencies = [
|
||||
"opentelemetry-exporter-otlp-proto-http>=1.22.0",
|
||||
"instructor>=1.3.3",
|
||||
"regex>=2024.9.11",
|
||||
"crewai-tools>=0.13.2",
|
||||
"crewai-tools>=0.13.4",
|
||||
"click>=8.1.7",
|
||||
"python-dotenv>=1.0.0",
|
||||
"appdirs>=1.4.4",
|
||||
@@ -37,7 +37,7 @@ Documentation = "https://docs.crewai.com"
|
||||
Repository = "https://github.com/crewAIInc/crewAI"
|
||||
|
||||
[project.optional-dependencies]
|
||||
tools = ["crewai-tools>=0.13.2"]
|
||||
tools = ["crewai-tools>=0.13.4"]
|
||||
agentops = ["agentops>=0.3.0"]
|
||||
|
||||
[tool.uv]
|
||||
@@ -52,7 +52,7 @@ dev-dependencies = [
|
||||
"mkdocs-material-extensions>=1.3.1",
|
||||
"pillow>=10.2.0",
|
||||
"cairosvg>=2.7.1",
|
||||
"crewai-tools>=0.13.2",
|
||||
"crewai-tools>=0.13.4",
|
||||
"pytest>=8.0.0",
|
||||
"pytest-vcr>=1.0.2",
|
||||
"python-dotenv>=1.0.0",
|
||||
|
||||
@@ -14,5 +14,5 @@ warnings.filterwarnings(
|
||||
category=UserWarning,
|
||||
module="pydantic.main",
|
||||
)
|
||||
__version__ = "0.76.2"
|
||||
__version__ = "0.76.9"
|
||||
__all__ = ["Agent", "Crew", "Process", "Task", "Pipeline", "Router", "LLM", "Flow"]
|
||||
|
||||
70
src/crewai/cli/add_crew_to_flow.py
Normal file
70
src/crewai/cli/add_crew_to_flow.py
Normal file
@@ -0,0 +1,70 @@
|
||||
from pathlib import Path
|
||||
|
||||
import click
|
||||
|
||||
from crewai.cli.utils import copy_template
|
||||
|
||||
|
||||
def add_crew_to_flow(crew_name: str) -> None:
|
||||
"""Add a new crew to the current flow."""
|
||||
# Check if pyproject.toml exists in the current directory
|
||||
if not Path("pyproject.toml").exists():
|
||||
print("This command must be run from the root of a flow project.")
|
||||
raise click.ClickException(
|
||||
"This command must be run from the root of a flow project."
|
||||
)
|
||||
|
||||
# Determine the flow folder based on the current directory
|
||||
flow_folder = Path.cwd()
|
||||
crews_folder = flow_folder / "src" / flow_folder.name / "crews"
|
||||
|
||||
if not crews_folder.exists():
|
||||
print("Crews folder does not exist in the current flow.")
|
||||
raise click.ClickException("Crews folder does not exist in the current flow.")
|
||||
|
||||
# Create the crew within the flow's crews directory
|
||||
create_embedded_crew(crew_name, parent_folder=crews_folder)
|
||||
|
||||
click.echo(
|
||||
f"Crew {crew_name} added to the current flow successfully!",
|
||||
)
|
||||
|
||||
|
||||
def create_embedded_crew(crew_name: str, parent_folder: Path) -> None:
|
||||
"""Create a new crew within an existing flow project."""
|
||||
folder_name = crew_name.replace(" ", "_").replace("-", "_").lower()
|
||||
class_name = crew_name.replace("_", " ").replace("-", " ").title().replace(" ", "")
|
||||
|
||||
crew_folder = parent_folder / folder_name
|
||||
|
||||
if crew_folder.exists():
|
||||
if not click.confirm(
|
||||
f"Crew {folder_name} already exists. Do you want to override it?"
|
||||
):
|
||||
click.secho("Operation cancelled.", fg="yellow")
|
||||
return
|
||||
click.secho(f"Overriding crew {folder_name}...", fg="green", bold=True)
|
||||
else:
|
||||
click.secho(f"Creating crew {folder_name}...", fg="green", bold=True)
|
||||
crew_folder.mkdir(parents=True)
|
||||
|
||||
# Create config and crew.py files
|
||||
config_folder = crew_folder / "config"
|
||||
config_folder.mkdir(exist_ok=True)
|
||||
|
||||
templates_dir = Path(__file__).parent / "templates" / "crew"
|
||||
config_template_files = ["agents.yaml", "tasks.yaml"]
|
||||
crew_template_file = f"{folder_name}_crew.py" # Updated file name
|
||||
|
||||
for file_name in config_template_files:
|
||||
src_file = templates_dir / "config" / file_name
|
||||
dst_file = config_folder / file_name
|
||||
copy_template(src_file, dst_file, crew_name, class_name, folder_name)
|
||||
|
||||
src_file = templates_dir / "crew.py"
|
||||
dst_file = crew_folder / crew_template_file
|
||||
copy_template(src_file, dst_file, crew_name, class_name, folder_name)
|
||||
|
||||
click.secho(
|
||||
f"Crew {crew_name} added to the flow successfully!", fg="green", bold=True
|
||||
)
|
||||
@@ -3,6 +3,7 @@ from typing import Optional
|
||||
import click
|
||||
import pkg_resources
|
||||
|
||||
from crewai.cli.add_crew_to_flow import add_crew_to_flow
|
||||
from crewai.cli.create_crew import create_crew
|
||||
from crewai.cli.create_flow import create_flow
|
||||
from crewai.cli.create_pipeline import create_pipeline
|
||||
@@ -178,10 +179,12 @@ def test(n_iterations: int, model: str):
|
||||
evaluate_crew(n_iterations, model)
|
||||
|
||||
|
||||
@crewai.command(context_settings=dict(
|
||||
ignore_unknown_options=True,
|
||||
allow_extra_args=True,
|
||||
))
|
||||
@crewai.command(
|
||||
context_settings=dict(
|
||||
ignore_unknown_options=True,
|
||||
allow_extra_args=True,
|
||||
)
|
||||
)
|
||||
@click.pass_context
|
||||
def install(context):
|
||||
"""Install the Crew."""
|
||||
@@ -324,5 +327,13 @@ def flow_plot():
|
||||
plot_flow()
|
||||
|
||||
|
||||
@flow.command(name="add-crew")
|
||||
@click.argument("crew_name")
|
||||
def flow_add_crew(crew_name):
|
||||
"""Add a crew to an existing flow."""
|
||||
click.echo(f"Adding crew {crew_name} to the flow")
|
||||
add_crew_to_flow(crew_name)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
crewai()
|
||||
|
||||
38
src/crewai/cli/config.py
Normal file
38
src/crewai/cli/config.py
Normal file
@@ -0,0 +1,38 @@
|
||||
import json
|
||||
from pathlib import Path
|
||||
from pydantic import BaseModel, Field
|
||||
from typing import Optional
|
||||
|
||||
DEFAULT_CONFIG_PATH = Path.home() / ".config" / "crewai" / "settings.json"
|
||||
|
||||
class Settings(BaseModel):
|
||||
tool_repository_username: Optional[str] = Field(None, description="Username for interacting with the Tool Repository")
|
||||
tool_repository_password: Optional[str] = Field(None, description="Password for interacting with the Tool Repository")
|
||||
config_path: Path = Field(default=DEFAULT_CONFIG_PATH, exclude=True)
|
||||
|
||||
def __init__(self, config_path: Path = DEFAULT_CONFIG_PATH, **data):
|
||||
"""Load Settings from config path"""
|
||||
config_path.parent.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
file_data = {}
|
||||
if config_path.is_file():
|
||||
try:
|
||||
with config_path.open("r") as f:
|
||||
file_data = json.load(f)
|
||||
except json.JSONDecodeError:
|
||||
file_data = {}
|
||||
|
||||
merged_data = {**file_data, **data}
|
||||
super().__init__(config_path=config_path, **merged_data)
|
||||
|
||||
def dump(self) -> None:
|
||||
"""Save current settings to settings.json"""
|
||||
if self.config_path.is_file():
|
||||
with self.config_path.open("r") as f:
|
||||
existing_data = json.load(f)
|
||||
else:
|
||||
existing_data = {}
|
||||
|
||||
updated_data = {**existing_data, **self.model_dump(exclude_unset=True)}
|
||||
with self.config_path.open("w") as f:
|
||||
json.dump(updated_data, f, indent=4)
|
||||
@@ -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.76.2,<1.0.0"
|
||||
"crewai[tools]>=0.76.9,<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.76.2,<1.0.0",
|
||||
"crewai[tools]>=0.76.9,<1.0.0",
|
||||
]
|
||||
|
||||
[project.scripts]
|
||||
|
||||
@@ -6,7 +6,7 @@ authors = ["Your Name <you@example.com>"]
|
||||
|
||||
[tool.poetry.dependencies]
|
||||
python = ">=3.10,<=3.13"
|
||||
crewai = { extras = ["tools"], version = ">=0.76.2,<1.0.0" }
|
||||
crewai = { extras = ["tools"], version = ">=0.76.9,<1.0.0" }
|
||||
asyncio = "*"
|
||||
|
||||
[tool.poetry.scripts]
|
||||
|
||||
@@ -5,7 +5,7 @@ description = "{{name}} using crewAI"
|
||||
authors = ["Your Name <you@example.com>"]
|
||||
requires-python = ">=3.10,<=3.13"
|
||||
dependencies = [
|
||||
"crewai[tools]>=0.76.2,<1.0.0"
|
||||
"crewai[tools]>=0.76.9,<1.0.0"
|
||||
]
|
||||
|
||||
[project.scripts]
|
||||
|
||||
@@ -5,6 +5,6 @@ description = "Power up your crews with {{folder_name}}"
|
||||
readme = "README.md"
|
||||
requires-python = ">=3.10,<=3.13"
|
||||
dependencies = [
|
||||
"crewai[tools]>=0.76.2"
|
||||
"crewai[tools]>=0.76.9"
|
||||
]
|
||||
|
||||
|
||||
@@ -1,17 +1,15 @@
|
||||
import base64
|
||||
import os
|
||||
import platform
|
||||
import subprocess
|
||||
import tempfile
|
||||
from pathlib import Path
|
||||
from netrc import netrc
|
||||
import stat
|
||||
|
||||
import click
|
||||
from rich.console import Console
|
||||
|
||||
from crewai.cli import git
|
||||
from crewai.cli.command import BaseCommand, PlusAPIMixin
|
||||
from crewai.cli.config import Settings
|
||||
from crewai.cli.utils import (
|
||||
get_project_description,
|
||||
get_project_name,
|
||||
@@ -153,26 +151,16 @@ class ToolCommand(BaseCommand, PlusAPIMixin):
|
||||
raise SystemExit
|
||||
|
||||
login_response_json = login_response.json()
|
||||
self._set_netrc_credentials(login_response_json["credential"])
|
||||
|
||||
settings = Settings()
|
||||
settings.tool_repository_username = login_response_json["credential"]["username"]
|
||||
settings.tool_repository_password = login_response_json["credential"]["password"]
|
||||
settings.dump()
|
||||
|
||||
console.print(
|
||||
"Successfully authenticated to the tool repository.", style="bold green"
|
||||
)
|
||||
|
||||
def _set_netrc_credentials(self, credentials, netrc_path=None):
|
||||
if not netrc_path:
|
||||
netrc_filename = "_netrc" if platform.system() == "Windows" else ".netrc"
|
||||
netrc_path = Path.home() / netrc_filename
|
||||
netrc_path.touch(mode=stat.S_IRUSR | stat.S_IWUSR, exist_ok=True)
|
||||
|
||||
netrc_instance = netrc(file=netrc_path)
|
||||
netrc_instance.hosts["app.crewai.com"] = (credentials["username"], "", credentials["password"])
|
||||
|
||||
with open(netrc_path, 'w') as file:
|
||||
file.write(str(netrc_instance))
|
||||
|
||||
console.print(f"Added credentials to {netrc_path}", style="bold green")
|
||||
|
||||
def _add_package(self, tool_details):
|
||||
tool_handle = tool_details["handle"]
|
||||
repository_handle = tool_details["repository"]["handle"]
|
||||
@@ -187,7 +175,11 @@ class ToolCommand(BaseCommand, PlusAPIMixin):
|
||||
tool_handle,
|
||||
]
|
||||
add_package_result = subprocess.run(
|
||||
add_package_command, capture_output=False, text=True, check=True
|
||||
add_package_command,
|
||||
capture_output=False,
|
||||
env=self._build_env_with_credentials(repository_handle),
|
||||
text=True,
|
||||
check=True
|
||||
)
|
||||
|
||||
if add_package_result.stderr:
|
||||
@@ -206,3 +198,13 @@ class ToolCommand(BaseCommand, PlusAPIMixin):
|
||||
"[bold yellow]Tip:[/bold yellow] Navigate to a different directory and try again."
|
||||
)
|
||||
raise SystemExit
|
||||
|
||||
def _build_env_with_credentials(self, repository_handle: str):
|
||||
repository_handle = repository_handle.upper().replace("-", "_")
|
||||
settings = Settings()
|
||||
|
||||
env = os.environ.copy()
|
||||
env[f"UV_INDEX_{repository_handle}_USERNAME"] = str(settings.tool_repository_username or "")
|
||||
env[f"UV_INDEX_{repository_handle}_PASSWORD"] = str(settings.tool_repository_password or "")
|
||||
|
||||
return env
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
# flow.py
|
||||
|
||||
import asyncio
|
||||
import inspect
|
||||
from typing import Any, Callable, Dict, Generic, List, Set, Type, TypeVar, Union
|
||||
@@ -120,6 +118,8 @@ class FlowMeta(type):
|
||||
methods = attr_value.__trigger_methods__
|
||||
condition_type = getattr(attr_value, "__condition_type__", "OR")
|
||||
listeners[attr_name] = (condition_type, methods)
|
||||
|
||||
# TODO: should we add a check for __condition_type__ 'AND'?
|
||||
elif hasattr(attr_value, "__is_router__"):
|
||||
routers[attr_value.__router_for__] = attr_name
|
||||
possible_returns = get_possible_return_constants(attr_value)
|
||||
@@ -159,7 +159,8 @@ class Flow(Generic[T], metaclass=FlowMeta):
|
||||
def __init__(self) -> None:
|
||||
self._methods: Dict[str, Callable] = {}
|
||||
self._state: T = self._create_initial_state()
|
||||
self._completed_methods: Set[str] = set()
|
||||
self._executed_methods: Set[str] = set()
|
||||
self._scheduled_tasks: Set[str] = set()
|
||||
self._pending_and_listeners: Dict[str, Set[str]] = {}
|
||||
self._method_outputs: List[Any] = [] # List to store all method outputs
|
||||
|
||||
@@ -216,17 +217,24 @@ class Flow(Generic[T], metaclass=FlowMeta):
|
||||
else:
|
||||
return None # Or raise an exception if no methods were executed
|
||||
|
||||
async def _execute_start_method(self, start_method: str) -> None:
|
||||
result = await self._execute_method(self._methods[start_method])
|
||||
await self._execute_listeners(start_method, result)
|
||||
async def _execute_start_method(self, start_method_name: str) -> None:
|
||||
result = await self._execute_method(
|
||||
start_method_name, self._methods[start_method_name]
|
||||
)
|
||||
await self._execute_listeners(start_method_name, result)
|
||||
|
||||
async def _execute_method(self, method: Callable, *args: Any, **kwargs: Any) -> Any:
|
||||
async def _execute_method(
|
||||
self, method_name: str, method: Callable, *args: Any, **kwargs: Any
|
||||
) -> Any:
|
||||
result = (
|
||||
await method(*args, **kwargs)
|
||||
if asyncio.iscoroutinefunction(method)
|
||||
else method(*args, **kwargs)
|
||||
)
|
||||
self._method_outputs.append(result) # Store the output
|
||||
|
||||
self._executed_methods.add(method_name)
|
||||
|
||||
return result
|
||||
|
||||
async def _execute_listeners(self, trigger_method: str, result: Any) -> None:
|
||||
@@ -234,32 +242,40 @@ class Flow(Generic[T], metaclass=FlowMeta):
|
||||
|
||||
if trigger_method in self._routers:
|
||||
router_method = self._methods[self._routers[trigger_method]]
|
||||
path = await self._execute_method(router_method)
|
||||
path = await self._execute_method(
|
||||
trigger_method, router_method
|
||||
) # TODO: Change or not?
|
||||
# Use the path as the new trigger method
|
||||
trigger_method = path
|
||||
|
||||
for listener, (condition_type, methods) in self._listeners.items():
|
||||
for listener_name, (condition_type, methods) in self._listeners.items():
|
||||
if condition_type == "OR":
|
||||
if trigger_method in methods:
|
||||
listener_tasks.append(
|
||||
self._execute_single_listener(listener, result)
|
||||
)
|
||||
if (
|
||||
listener_name not in self._executed_methods
|
||||
and listener_name not in self._scheduled_tasks
|
||||
):
|
||||
self._scheduled_tasks.add(listener_name)
|
||||
listener_tasks.append(
|
||||
self._execute_single_listener(listener_name, result)
|
||||
)
|
||||
elif condition_type == "AND":
|
||||
if listener not in self._pending_and_listeners:
|
||||
self._pending_and_listeners[listener] = set()
|
||||
self._pending_and_listeners[listener].add(trigger_method)
|
||||
if set(methods) == self._pending_and_listeners[listener]:
|
||||
listener_tasks.append(
|
||||
self._execute_single_listener(listener, result)
|
||||
)
|
||||
del self._pending_and_listeners[listener]
|
||||
if all(method in self._executed_methods for method in methods):
|
||||
if (
|
||||
listener_name not in self._executed_methods
|
||||
and listener_name not in self._scheduled_tasks
|
||||
):
|
||||
self._scheduled_tasks.add(listener_name)
|
||||
listener_tasks.append(
|
||||
self._execute_single_listener(listener_name, result)
|
||||
)
|
||||
|
||||
# Run all listener tasks concurrently and wait for them to complete
|
||||
await asyncio.gather(*listener_tasks)
|
||||
|
||||
async def _execute_single_listener(self, listener: str, result: Any) -> None:
|
||||
async def _execute_single_listener(self, listener_name: str, result: Any) -> None:
|
||||
try:
|
||||
method = self._methods[listener]
|
||||
method = self._methods[listener_name]
|
||||
sig = inspect.signature(method)
|
||||
params = list(sig.parameters.values())
|
||||
|
||||
@@ -268,15 +284,22 @@ class Flow(Generic[T], metaclass=FlowMeta):
|
||||
|
||||
if method_params:
|
||||
# If listener expects parameters, pass the result
|
||||
listener_result = await self._execute_method(method, result)
|
||||
listener_result = await self._execute_method(
|
||||
listener_name, method, result
|
||||
)
|
||||
else:
|
||||
# If listener does not expect parameters, call without arguments
|
||||
listener_result = await self._execute_method(method)
|
||||
listener_result = await self._execute_method(listener_name, method)
|
||||
|
||||
# Remove from scheduled tasks after execution
|
||||
self._scheduled_tasks.discard(listener_name)
|
||||
|
||||
# Execute listeners of this listener
|
||||
await self._execute_listeners(listener, listener_result)
|
||||
await self._execute_listeners(listener_name, listener_result)
|
||||
except Exception as e:
|
||||
print(f"[Flow._execute_single_listener] Error in method {listener}: {e}")
|
||||
print(
|
||||
f"[Flow._execute_single_listener] Error in method {listener_name}: {e}"
|
||||
)
|
||||
import traceback
|
||||
|
||||
traceback.print_exc()
|
||||
|
||||
@@ -34,6 +34,7 @@ class ContextualMemory:
|
||||
formatted_results = "\n".join(
|
||||
[f"- {result['context']}" for result in stm_results]
|
||||
)
|
||||
print("formatted_results stm", formatted_results)
|
||||
return f"Recent Insights:\n{formatted_results}" if stm_results else ""
|
||||
|
||||
def _fetch_ltm_context(self, task) -> Optional[str]:
|
||||
@@ -53,6 +54,8 @@ class ContextualMemory:
|
||||
formatted_results = list(dict.fromkeys(formatted_results))
|
||||
formatted_results = "\n".join([f"- {result}" for result in formatted_results]) # type: ignore # Incompatible types in assignment (expression has type "str", variable has type "list[str]")
|
||||
|
||||
print("formatted_results ltm", formatted_results)
|
||||
|
||||
return f"Historical Data:\n{formatted_results}" if ltm_results else ""
|
||||
|
||||
def _fetch_entity_context(self, query) -> str:
|
||||
@@ -64,4 +67,5 @@ class ContextualMemory:
|
||||
formatted_results = "\n".join(
|
||||
[f"- {result['context']}" for result in em_results] # type: ignore # Invalid index type "str" for "str"; expected type "SupportsIndex | slice"
|
||||
)
|
||||
print("formatted_results em", formatted_results)
|
||||
return f"Entities:\n{formatted_results}" if em_results else ""
|
||||
|
||||
@@ -70,7 +70,7 @@ class KickoffTaskOutputsSQLiteStorage:
|
||||
task.expected_output,
|
||||
json.dumps(output, cls=CrewJSONEncoder),
|
||||
task_index,
|
||||
json.dumps(inputs),
|
||||
json.dumps(inputs, cls=CrewJSONEncoder),
|
||||
was_replayed,
|
||||
),
|
||||
)
|
||||
|
||||
@@ -4,13 +4,13 @@ import logging
|
||||
import os
|
||||
import shutil
|
||||
import uuid
|
||||
from typing import Any, Dict, List, Optional
|
||||
from crewai.memory.storage.base_rag_storage import BaseRAGStorage
|
||||
from crewai.utilities.paths import db_storage_path
|
||||
from typing import Any, Dict, List, Optional, cast
|
||||
|
||||
from chromadb import Documents, EmbeddingFunction, Embeddings
|
||||
from chromadb.api import ClientAPI
|
||||
from chromadb.api.types import validate_embedding_function
|
||||
from chromadb import Documents, EmbeddingFunction, Embeddings
|
||||
from typing import cast
|
||||
from crewai.memory.storage.base_rag_storage import BaseRAGStorage
|
||||
from crewai.utilities.paths import db_storage_path
|
||||
|
||||
|
||||
@contextlib.contextmanager
|
||||
@@ -21,9 +21,11 @@ def suppress_logging(
|
||||
logger = logging.getLogger(logger_name)
|
||||
original_level = logger.getEffectiveLevel()
|
||||
logger.setLevel(level)
|
||||
with contextlib.redirect_stdout(io.StringIO()), contextlib.redirect_stderr(
|
||||
io.StringIO()
|
||||
), contextlib.suppress(UserWarning):
|
||||
with (
|
||||
contextlib.redirect_stdout(io.StringIO()),
|
||||
contextlib.redirect_stderr(io.StringIO()),
|
||||
contextlib.suppress(UserWarning),
|
||||
):
|
||||
yield
|
||||
logger.setLevel(original_level)
|
||||
|
||||
@@ -113,12 +115,52 @@ class RAGStorage(BaseRAGStorage):
|
||||
self.embedder_config = embedding_functions.HuggingFaceEmbeddingServer(
|
||||
url=config.get("api_url"),
|
||||
)
|
||||
elif provider == "watson":
|
||||
try:
|
||||
import ibm_watsonx_ai.foundation_models as watson_models
|
||||
from ibm_watsonx_ai import Credentials
|
||||
from ibm_watsonx_ai.metanames import (
|
||||
EmbedTextParamsMetaNames as EmbedParams,
|
||||
)
|
||||
except ImportError as e:
|
||||
raise ImportError(
|
||||
"IBM Watson dependencies are not installed. Please install them to use Watson embedding."
|
||||
) from e
|
||||
|
||||
class WatsonEmbeddingFunction(EmbeddingFunction):
|
||||
def __call__(self, input: Documents) -> Embeddings:
|
||||
if isinstance(input, str):
|
||||
input = [input]
|
||||
|
||||
embed_params = {
|
||||
EmbedParams.TRUNCATE_INPUT_TOKENS: 3,
|
||||
EmbedParams.RETURN_OPTIONS: {"input_text": True},
|
||||
}
|
||||
|
||||
embedding = watson_models.Embeddings(
|
||||
model_id=config.get("model"),
|
||||
params=embed_params,
|
||||
credentials=Credentials(
|
||||
api_key=config.get("api_key"), url=config.get("api_url")
|
||||
),
|
||||
project_id=config.get("project_id"),
|
||||
)
|
||||
|
||||
try:
|
||||
embeddings = embedding.embed_documents(input)
|
||||
return cast(Embeddings, embeddings)
|
||||
|
||||
except Exception as e:
|
||||
print("Error during Watson embedding:", e)
|
||||
raise e
|
||||
|
||||
self.embedder_config = WatsonEmbeddingFunction()
|
||||
else:
|
||||
raise Exception(
|
||||
f"Unsupported embedding provider: {provider}, supported providers: [openai, azure, ollama, vertexai, google, cohere, huggingface]"
|
||||
f"Unsupported embedding provider: {provider}, supported providers: [openai, azure, ollama, vertexai, google, cohere, huggingface, watson]"
|
||||
)
|
||||
else:
|
||||
validate_embedding_function(self.embedder_config) # type: ignore # used for validating embedder_config if defined a embedding function/class
|
||||
validate_embedding_function(self.embedder_config)
|
||||
self.embedder_config = self.embedder_config
|
||||
|
||||
def _initialize_app(self):
|
||||
|
||||
@@ -21,7 +21,7 @@ with suppress_warnings():
|
||||
|
||||
|
||||
from opentelemetry import trace # noqa: E402
|
||||
from opentelemetry.exporter.otlp.proto.http.trace_exporter import OTLPSpanExporter # noqa: E402
|
||||
from opentelemetry.exporter.otlp.proto.http.trace_exporter import OTLPSpanExporter # noqa: E402
|
||||
from opentelemetry.sdk.resources import SERVICE_NAME, Resource # noqa: E402
|
||||
from opentelemetry.sdk.trace import TracerProvider # noqa: E402
|
||||
from opentelemetry.sdk.trace.export import BatchSpanProcessor # noqa: E402
|
||||
@@ -48,6 +48,10 @@ class Telemetry:
|
||||
def __init__(self):
|
||||
self.ready = False
|
||||
self.trace_set = False
|
||||
|
||||
if os.getenv("OTEL_SDK_DISABLED", "false").lower() == "true":
|
||||
return
|
||||
|
||||
try:
|
||||
telemetry_endpoint = "https://telemetry.crewai.com:4319"
|
||||
self.resource = Resource(
|
||||
|
||||
@@ -2,13 +2,14 @@ from datetime import datetime, date
|
||||
import json
|
||||
from uuid import UUID
|
||||
from pydantic import BaseModel
|
||||
from decimal import Decimal
|
||||
|
||||
|
||||
class CrewJSONEncoder(json.JSONEncoder):
|
||||
def default(self, obj):
|
||||
if isinstance(obj, BaseModel):
|
||||
return self._handle_pydantic_model(obj)
|
||||
elif isinstance(obj, UUID):
|
||||
elif isinstance(obj, UUID) or isinstance(obj, Decimal):
|
||||
return str(obj)
|
||||
|
||||
elif isinstance(obj, datetime) or isinstance(obj, date):
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
from pathlib import Path
|
||||
from unittest import mock
|
||||
|
||||
import pytest
|
||||
from click.testing import CliRunner
|
||||
|
||||
from crewai.cli.cli import (
|
||||
deploy_create,
|
||||
deploy_list,
|
||||
@@ -9,6 +11,7 @@ from crewai.cli.cli import (
|
||||
deploy_push,
|
||||
deploy_remove,
|
||||
deply_status,
|
||||
flow_add_crew,
|
||||
reset_memories,
|
||||
signup,
|
||||
test,
|
||||
@@ -277,3 +280,42 @@ def test_deploy_remove_no_uuid(command, runner):
|
||||
|
||||
assert result.exit_code == 0
|
||||
mock_deploy.remove_crew.assert_called_once_with(uuid=None)
|
||||
|
||||
|
||||
@mock.patch("crewai.cli.add_crew_to_flow.create_embedded_crew")
|
||||
@mock.patch("pathlib.Path.exists", return_value=True) # Mock the existence check
|
||||
def test_flow_add_crew(mock_path_exists, mock_create_embedded_crew, runner):
|
||||
crew_name = "new_crew"
|
||||
result = runner.invoke(flow_add_crew, [crew_name])
|
||||
|
||||
# Log the output for debugging
|
||||
print(result.output)
|
||||
|
||||
assert result.exit_code == 0, f"Command failed with output: {result.output}"
|
||||
assert f"Adding crew {crew_name} to the flow" in result.output
|
||||
|
||||
# Verify that create_embedded_crew was called with the correct arguments
|
||||
mock_create_embedded_crew.assert_called_once()
|
||||
call_args, call_kwargs = mock_create_embedded_crew.call_args
|
||||
assert call_args[0] == crew_name
|
||||
assert "parent_folder" in call_kwargs
|
||||
assert isinstance(call_kwargs["parent_folder"], Path)
|
||||
|
||||
|
||||
def test_add_crew_to_flow_not_in_root(runner):
|
||||
# Simulate not being in the root of a flow project
|
||||
with mock.patch("pathlib.Path.exists", autospec=True) as mock_exists:
|
||||
# Mock Path.exists to return False when checking for pyproject.toml
|
||||
def exists_side_effect(self):
|
||||
if self.name == "pyproject.toml":
|
||||
return False # Simulate that pyproject.toml does not exist
|
||||
return True # All other paths exist
|
||||
|
||||
mock_exists.side_effect = exists_side_effect
|
||||
|
||||
result = runner.invoke(flow_add_crew, ["new_crew"])
|
||||
|
||||
assert result.exit_code != 0
|
||||
assert "This command must be run from the root of a flow project." in str(
|
||||
result.output
|
||||
)
|
||||
|
||||
109
tests/cli/config_test.py
Normal file
109
tests/cli/config_test.py
Normal file
@@ -0,0 +1,109 @@
|
||||
import unittest
|
||||
import json
|
||||
import tempfile
|
||||
import shutil
|
||||
from pathlib import Path
|
||||
from crewai.cli.config import Settings
|
||||
|
||||
class TestSettings(unittest.TestCase):
|
||||
def setUp(self):
|
||||
self.test_dir = Path(tempfile.mkdtemp())
|
||||
self.config_path = self.test_dir / "settings.json"
|
||||
|
||||
def tearDown(self):
|
||||
shutil.rmtree(self.test_dir)
|
||||
|
||||
def test_empty_initialization(self):
|
||||
settings = Settings(config_path=self.config_path)
|
||||
self.assertIsNone(settings.tool_repository_username)
|
||||
self.assertIsNone(settings.tool_repository_password)
|
||||
|
||||
def test_initialization_with_data(self):
|
||||
settings = Settings(
|
||||
config_path=self.config_path,
|
||||
tool_repository_username="user1"
|
||||
)
|
||||
self.assertEqual(settings.tool_repository_username, "user1")
|
||||
self.assertIsNone(settings.tool_repository_password)
|
||||
|
||||
def test_initialization_with_existing_file(self):
|
||||
self.config_path.parent.mkdir(parents=True, exist_ok=True)
|
||||
with self.config_path.open("w") as f:
|
||||
json.dump({"tool_repository_username": "file_user"}, f)
|
||||
|
||||
settings = Settings(config_path=self.config_path)
|
||||
self.assertEqual(settings.tool_repository_username, "file_user")
|
||||
|
||||
def test_merge_file_and_input_data(self):
|
||||
self.config_path.parent.mkdir(parents=True, exist_ok=True)
|
||||
with self.config_path.open("w") as f:
|
||||
json.dump({
|
||||
"tool_repository_username": "file_user",
|
||||
"tool_repository_password": "file_pass"
|
||||
}, f)
|
||||
|
||||
settings = Settings(
|
||||
config_path=self.config_path,
|
||||
tool_repository_username="new_user"
|
||||
)
|
||||
self.assertEqual(settings.tool_repository_username, "new_user")
|
||||
self.assertEqual(settings.tool_repository_password, "file_pass")
|
||||
|
||||
def test_dump_new_settings(self):
|
||||
settings = Settings(
|
||||
config_path=self.config_path,
|
||||
tool_repository_username="user1"
|
||||
)
|
||||
settings.dump()
|
||||
|
||||
with self.config_path.open("r") as f:
|
||||
saved_data = json.load(f)
|
||||
|
||||
self.assertEqual(saved_data["tool_repository_username"], "user1")
|
||||
|
||||
def test_update_existing_settings(self):
|
||||
self.config_path.parent.mkdir(parents=True, exist_ok=True)
|
||||
with self.config_path.open("w") as f:
|
||||
json.dump({"existing_setting": "value"}, f)
|
||||
|
||||
settings = Settings(
|
||||
config_path=self.config_path,
|
||||
tool_repository_username="user1"
|
||||
)
|
||||
settings.dump()
|
||||
|
||||
with self.config_path.open("r") as f:
|
||||
saved_data = json.load(f)
|
||||
|
||||
self.assertEqual(saved_data["existing_setting"], "value")
|
||||
self.assertEqual(saved_data["tool_repository_username"], "user1")
|
||||
|
||||
def test_none_values(self):
|
||||
settings = Settings(
|
||||
config_path=self.config_path,
|
||||
tool_repository_username=None
|
||||
)
|
||||
settings.dump()
|
||||
|
||||
with self.config_path.open("r") as f:
|
||||
saved_data = json.load(f)
|
||||
|
||||
self.assertIsNone(saved_data.get("tool_repository_username"))
|
||||
|
||||
def test_invalid_json_in_config(self):
|
||||
self.config_path.parent.mkdir(parents=True, exist_ok=True)
|
||||
with self.config_path.open("w") as f:
|
||||
f.write("invalid json")
|
||||
|
||||
try:
|
||||
settings = Settings(config_path=self.config_path)
|
||||
self.assertIsNone(settings.tool_repository_username)
|
||||
except json.JSONDecodeError:
|
||||
self.fail("Settings initialization should handle invalid JSON")
|
||||
|
||||
def test_empty_config_file(self):
|
||||
self.config_path.parent.mkdir(parents=True, exist_ok=True)
|
||||
self.config_path.touch()
|
||||
|
||||
settings = Settings(config_path=self.config_path)
|
||||
self.assertIsNone(settings.tool_repository_username)
|
||||
@@ -82,6 +82,7 @@ def test_install_success(mock_get, mock_subprocess_run):
|
||||
capture_output=False,
|
||||
text=True,
|
||||
check=True,
|
||||
env=unittest.mock.ANY
|
||||
)
|
||||
|
||||
assert "Succesfully installed sample-tool" in output
|
||||
|
||||
14
uv.lock
generated
14
uv.lock
generated
@@ -604,7 +604,7 @@ wheels = [
|
||||
|
||||
[[package]]
|
||||
name = "crewai"
|
||||
version = "0.76.2"
|
||||
version = "0.76.9"
|
||||
source = { editable = "." }
|
||||
dependencies = [
|
||||
{ name = "appdirs" },
|
||||
@@ -665,8 +665,8 @@ requires-dist = [
|
||||
{ name = "auth0-python", specifier = ">=4.7.1" },
|
||||
{ name = "chromadb", specifier = ">=0.4.24" },
|
||||
{ name = "click", specifier = ">=8.1.7" },
|
||||
{ name = "crewai-tools", specifier = ">=0.13.2" },
|
||||
{ name = "crewai-tools", marker = "extra == 'tools'", specifier = ">=0.13.2" },
|
||||
{ name = "crewai-tools", specifier = ">=0.13.4" },
|
||||
{ name = "crewai-tools", marker = "extra == 'tools'", specifier = ">=0.13.4" },
|
||||
{ name = "instructor", specifier = ">=1.3.3" },
|
||||
{ name = "json-repair", specifier = ">=0.25.2" },
|
||||
{ name = "jsonref", specifier = ">=1.1.0" },
|
||||
@@ -688,7 +688,7 @@ requires-dist = [
|
||||
[package.metadata.requires-dev]
|
||||
dev = [
|
||||
{ name = "cairosvg", specifier = ">=2.7.1" },
|
||||
{ name = "crewai-tools", specifier = ">=0.13.2" },
|
||||
{ name = "crewai-tools", specifier = ">=0.13.4" },
|
||||
{ name = "mkdocs", specifier = ">=1.4.3" },
|
||||
{ name = "mkdocs-material", specifier = ">=9.5.7" },
|
||||
{ name = "mkdocs-material-extensions", specifier = ">=1.3.1" },
|
||||
@@ -707,7 +707,7 @@ dev = [
|
||||
|
||||
[[package]]
|
||||
name = "crewai-tools"
|
||||
version = "0.13.2"
|
||||
version = "0.13.4"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "beautifulsoup4" },
|
||||
@@ -725,9 +725,9 @@ dependencies = [
|
||||
{ name = "requests" },
|
||||
{ name = "selenium" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/96/02/136f42ed8a7bd706a85663714c615bdcb684e43e95e4719c892aa0ce3d53/crewai_tools-0.13.2.tar.gz", hash = "sha256:c6782f2e868c0e96b25891f1b40fb8c90c01e920bab2fd1388f89ef1d7a4b99b", size = 816250 }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/64/bd/eff7b633a0b28ff4ed115adde1499e3dcc683e4f0b5c378a4c6f5c0c1bf6/crewai_tools-0.13.4.tar.gz", hash = "sha256:b6ac527633b7018471d892c21ac96bc961a86b6626d996b1ed7d53cd481d4505", size = 816588 }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/28/30/df215173b6193b2cfb1902a339443be73056eae89579805b853c6f359761/crewai_tools-0.13.2-py3-none-any.whl", hash = "sha256:8c7583c9559fb625f594349c6553a5251ebd7b21918735ad6fbe8bab7ec3db50", size = 463444 },
|
||||
{ url = "https://files.pythonhosted.org/packages/6c/40/93cd347d854059cf5e54a81b70f896deea7ad1f03e9c024549eb323c4da5/crewai_tools-0.13.4-py3-none-any.whl", hash = "sha256:eda78fe3c4df57676259d8dd6b2610fa31f89b90909512f15893adb57fb9e825", size = 463703 },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
||||
Reference in New Issue
Block a user