mirror of
https://github.com/crewAIInc/crewAI.git
synced 2025-12-20 14:28:28 +00:00
Compare commits
10 Commits
sec_docs
...
feat/ibm-m
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5cd79b7345 | ||
|
|
377793af42 | ||
|
|
56cea8fb93 | ||
|
|
9933d8f880 | ||
|
|
12b0cf6100 | ||
|
|
66698503b8 | ||
|
|
ec2967c362 | ||
|
|
4ae07468f3 | ||
|
|
6193eb13fa | ||
|
|
55cd15bfc6 |
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.
|
||||||
@@ -25,52 +25,55 @@ By default, CrewAI uses the `gpt-4o-mini` model. It uses environment variables i
|
|||||||
- `OPENAI_API_BASE`
|
- `OPENAI_API_BASE`
|
||||||
- `OPENAI_API_KEY`
|
- `OPENAI_API_KEY`
|
||||||
|
|
||||||
### 2. String Identifier
|
### 2. Custom LLM Objects
|
||||||
|
|
||||||
```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
|
|
||||||
|
|
||||||
Pass a custom LLM implementation or object from another library.
|
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
|
## 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:
|
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
|
os.environ["OPENAI_API_KEY"] = "your-api-key"
|
||||||
import os
|
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"
|
llm = LLM(
|
||||||
os.environ["OPENAI_API_BASE"] = "https://api.your-provider.com/v1"
|
model="custom-model-name",
|
||||||
```
|
api_key="your-api-key",
|
||||||
|
base_url="https://api.your-provider.com/v1"
|
||||||
2. Using LLM class attributes:
|
)
|
||||||
|
agent = Agent(llm=llm, ...)
|
||||||
```python Code
|
```
|
||||||
from crewai import LLM
|
</Tab>
|
||||||
|
</Tabs>
|
||||||
llm = LLM(
|
|
||||||
model="custom-model-name",
|
|
||||||
api_key="your-api-key",
|
|
||||||
base_url="https://api.your-provider.com/v1"
|
|
||||||
)
|
|
||||||
agent = Agent(llm=llm, ...)
|
|
||||||
```
|
|
||||||
|
|
||||||
## LLM Configuration Options
|
## 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. |
|
| **api_key** | `str` | Your API key for authentication. |
|
||||||
|
|
||||||
|
|
||||||
## OpenAI Example Configuration
|
These are examples of how to configure LLMs for your agent.
|
||||||
|
|
||||||
```python Code
|
<AccordionGroup>
|
||||||
from crewai import LLM
|
<Accordion title="OpenAI">
|
||||||
|
|
||||||
llm = LLM(
|
```python Code
|
||||||
model="gpt-4",
|
from crewai import LLM
|
||||||
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, ...)
|
|
||||||
```
|
|
||||||
|
|
||||||
## 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
|
<Accordion title="Cerebras">
|
||||||
from crewai import LLM
|
|
||||||
|
|
||||||
llm = LLM(
|
```python Code
|
||||||
model="cerebras/llama-3.1-70b",
|
from crewai import LLM
|
||||||
base_url="https://api.cerebras.ai/v1",
|
|
||||||
api_key="your-api-key-here"
|
|
||||||
)
|
|
||||||
agent = Agent(llm=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/)
|
1. Install Ollama: [ollama.ai](https://ollama.ai/)
|
||||||
2. Run a model: `ollama run llama2`
|
2. Run a model: `ollama run llama2`
|
||||||
3. Configure agent:
|
3. Configure agent:
|
||||||
|
|
||||||
```python Code
|
```python Code
|
||||||
from crewai import LLM
|
from crewai import LLM
|
||||||
|
|
||||||
agent = Agent(
|
agent = Agent(
|
||||||
llm=LLM(model="ollama/llama3.1", base_url="http://localhost:11434"),
|
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
|
## 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
|
### Resetting Memory
|
||||||
|
|
||||||
```shell
|
```shell
|
||||||
|
|||||||
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)
|
||||||
@@ -1,17 +1,15 @@
|
|||||||
import base64
|
import base64
|
||||||
import os
|
import os
|
||||||
import platform
|
|
||||||
import subprocess
|
import subprocess
|
||||||
import tempfile
|
import tempfile
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from netrc import netrc
|
|
||||||
import stat
|
|
||||||
|
|
||||||
import click
|
import click
|
||||||
from rich.console import Console
|
from rich.console import Console
|
||||||
|
|
||||||
from crewai.cli import git
|
from crewai.cli import git
|
||||||
from crewai.cli.command import BaseCommand, PlusAPIMixin
|
from crewai.cli.command import BaseCommand, PlusAPIMixin
|
||||||
|
from crewai.cli.config import Settings
|
||||||
from crewai.cli.utils import (
|
from crewai.cli.utils import (
|
||||||
get_project_description,
|
get_project_description,
|
||||||
get_project_name,
|
get_project_name,
|
||||||
@@ -153,26 +151,16 @@ class ToolCommand(BaseCommand, PlusAPIMixin):
|
|||||||
raise SystemExit
|
raise SystemExit
|
||||||
|
|
||||||
login_response_json = login_response.json()
|
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(
|
console.print(
|
||||||
"Successfully authenticated to the tool repository.", style="bold green"
|
"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):
|
def _add_package(self, tool_details):
|
||||||
tool_handle = tool_details["handle"]
|
tool_handle = tool_details["handle"]
|
||||||
repository_handle = tool_details["repository"]["handle"]
|
repository_handle = tool_details["repository"]["handle"]
|
||||||
@@ -187,7 +175,11 @@ class ToolCommand(BaseCommand, PlusAPIMixin):
|
|||||||
tool_handle,
|
tool_handle,
|
||||||
]
|
]
|
||||||
add_package_result = subprocess.run(
|
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:
|
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."
|
"[bold yellow]Tip:[/bold yellow] Navigate to a different directory and try again."
|
||||||
)
|
)
|
||||||
raise SystemExit
|
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
|
||||||
|
|||||||
@@ -34,6 +34,7 @@ class ContextualMemory:
|
|||||||
formatted_results = "\n".join(
|
formatted_results = "\n".join(
|
||||||
[f"- {result['context']}" for result in stm_results]
|
[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 ""
|
return f"Recent Insights:\n{formatted_results}" if stm_results else ""
|
||||||
|
|
||||||
def _fetch_ltm_context(self, task) -> Optional[str]:
|
def _fetch_ltm_context(self, task) -> Optional[str]:
|
||||||
@@ -53,6 +54,8 @@ class ContextualMemory:
|
|||||||
formatted_results = list(dict.fromkeys(formatted_results))
|
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]")
|
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 ""
|
return f"Historical Data:\n{formatted_results}" if ltm_results else ""
|
||||||
|
|
||||||
def _fetch_entity_context(self, query) -> str:
|
def _fetch_entity_context(self, query) -> str:
|
||||||
@@ -64,4 +67,5 @@ class ContextualMemory:
|
|||||||
formatted_results = "\n".join(
|
formatted_results = "\n".join(
|
||||||
[f"- {result['context']}" for result in em_results] # type: ignore # Invalid index type "str" for "str"; expected type "SupportsIndex | slice"
|
[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 ""
|
return f"Entities:\n{formatted_results}" if em_results else ""
|
||||||
|
|||||||
@@ -70,7 +70,7 @@ class KickoffTaskOutputsSQLiteStorage:
|
|||||||
task.expected_output,
|
task.expected_output,
|
||||||
json.dumps(output, cls=CrewJSONEncoder),
|
json.dumps(output, cls=CrewJSONEncoder),
|
||||||
task_index,
|
task_index,
|
||||||
json.dumps(inputs),
|
json.dumps(inputs, cls=CrewJSONEncoder),
|
||||||
was_replayed,
|
was_replayed,
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -4,13 +4,13 @@ import logging
|
|||||||
import os
|
import os
|
||||||
import shutil
|
import shutil
|
||||||
import uuid
|
import uuid
|
||||||
from typing import Any, Dict, List, Optional
|
from typing import Any, Dict, List, Optional, cast
|
||||||
from crewai.memory.storage.base_rag_storage import BaseRAGStorage
|
|
||||||
from crewai.utilities.paths import db_storage_path
|
from chromadb import Documents, EmbeddingFunction, Embeddings
|
||||||
from chromadb.api import ClientAPI
|
from chromadb.api import ClientAPI
|
||||||
from chromadb.api.types import validate_embedding_function
|
from chromadb.api.types import validate_embedding_function
|
||||||
from chromadb import Documents, EmbeddingFunction, Embeddings
|
from crewai.memory.storage.base_rag_storage import BaseRAGStorage
|
||||||
from typing import cast
|
from crewai.utilities.paths import db_storage_path
|
||||||
|
|
||||||
|
|
||||||
@contextlib.contextmanager
|
@contextlib.contextmanager
|
||||||
@@ -21,9 +21,11 @@ def suppress_logging(
|
|||||||
logger = logging.getLogger(logger_name)
|
logger = logging.getLogger(logger_name)
|
||||||
original_level = logger.getEffectiveLevel()
|
original_level = logger.getEffectiveLevel()
|
||||||
logger.setLevel(level)
|
logger.setLevel(level)
|
||||||
with contextlib.redirect_stdout(io.StringIO()), contextlib.redirect_stderr(
|
with (
|
||||||
io.StringIO()
|
contextlib.redirect_stdout(io.StringIO()),
|
||||||
), contextlib.suppress(UserWarning):
|
contextlib.redirect_stderr(io.StringIO()),
|
||||||
|
contextlib.suppress(UserWarning),
|
||||||
|
):
|
||||||
yield
|
yield
|
||||||
logger.setLevel(original_level)
|
logger.setLevel(original_level)
|
||||||
|
|
||||||
@@ -113,12 +115,52 @@ class RAGStorage(BaseRAGStorage):
|
|||||||
self.embedder_config = embedding_functions.HuggingFaceEmbeddingServer(
|
self.embedder_config = embedding_functions.HuggingFaceEmbeddingServer(
|
||||||
url=config.get("api_url"),
|
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:
|
else:
|
||||||
raise Exception(
|
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:
|
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
|
self.embedder_config = self.embedder_config
|
||||||
|
|
||||||
def _initialize_app(self):
|
def _initialize_app(self):
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ with suppress_warnings():
|
|||||||
|
|
||||||
|
|
||||||
from opentelemetry import trace # noqa: E402
|
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.resources import SERVICE_NAME, Resource # noqa: E402
|
||||||
from opentelemetry.sdk.trace import TracerProvider # noqa: E402
|
from opentelemetry.sdk.trace import TracerProvider # noqa: E402
|
||||||
from opentelemetry.sdk.trace.export import BatchSpanProcessor # noqa: E402
|
from opentelemetry.sdk.trace.export import BatchSpanProcessor # noqa: E402
|
||||||
@@ -48,6 +48,10 @@ class Telemetry:
|
|||||||
def __init__(self):
|
def __init__(self):
|
||||||
self.ready = False
|
self.ready = False
|
||||||
self.trace_set = False
|
self.trace_set = False
|
||||||
|
|
||||||
|
if os.getenv("OTEL_SDK_DISABLED", "false").lower() == "true":
|
||||||
|
return
|
||||||
|
|
||||||
try:
|
try:
|
||||||
telemetry_endpoint = "https://telemetry.crewai.com:4319"
|
telemetry_endpoint = "https://telemetry.crewai.com:4319"
|
||||||
self.resource = Resource(
|
self.resource = Resource(
|
||||||
|
|||||||
@@ -2,13 +2,14 @@ from datetime import datetime, date
|
|||||||
import json
|
import json
|
||||||
from uuid import UUID
|
from uuid import UUID
|
||||||
from pydantic import BaseModel
|
from pydantic import BaseModel
|
||||||
|
from decimal import Decimal
|
||||||
|
|
||||||
|
|
||||||
class CrewJSONEncoder(json.JSONEncoder):
|
class CrewJSONEncoder(json.JSONEncoder):
|
||||||
def default(self, obj):
|
def default(self, obj):
|
||||||
if isinstance(obj, BaseModel):
|
if isinstance(obj, BaseModel):
|
||||||
return self._handle_pydantic_model(obj)
|
return self._handle_pydantic_model(obj)
|
||||||
elif isinstance(obj, UUID):
|
elif isinstance(obj, UUID) or isinstance(obj, Decimal):
|
||||||
return str(obj)
|
return str(obj)
|
||||||
|
|
||||||
elif isinstance(obj, datetime) or isinstance(obj, date):
|
elif isinstance(obj, datetime) or isinstance(obj, date):
|
||||||
|
|||||||
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,
|
capture_output=False,
|
||||||
text=True,
|
text=True,
|
||||||
check=True,
|
check=True,
|
||||||
|
env=unittest.mock.ANY
|
||||||
)
|
)
|
||||||
|
|
||||||
assert "Succesfully installed sample-tool" in output
|
assert "Succesfully installed sample-tool" in output
|
||||||
|
|||||||
Reference in New Issue
Block a user