Compare commits
4 Commits
devin/1747
...
devin/1746
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0f0623d31c | ||
|
|
6db161465b | ||
|
|
2f7cba6b23 | ||
|
|
5bc01b15a0 |
25
.github/workflows/linter.yml
vendored
@@ -5,29 +5,12 @@ on: [pull_request]
|
||||
jobs:
|
||||
lint:
|
||||
runs-on: ubuntu-latest
|
||||
env:
|
||||
TARGET_BRANCH: ${{ github.event.pull_request.base.ref }}
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Fetch Target Branch
|
||||
run: git fetch origin $TARGET_BRANCH --depth=1
|
||||
|
||||
- name: Install Ruff
|
||||
run: pip install ruff
|
||||
|
||||
- name: Get Changed Python Files
|
||||
id: changed-files
|
||||
- name: Install Requirements
|
||||
run: |
|
||||
merge_base=$(git merge-base origin/"$TARGET_BRANCH" HEAD)
|
||||
changed_files=$(git diff --name-only --diff-filter=ACMRTUB "$merge_base" | grep '\.py$' || true)
|
||||
echo "files<<EOF" >> $GITHUB_OUTPUT
|
||||
echo "$changed_files" >> $GITHUB_OUTPUT
|
||||
echo "EOF" >> $GITHUB_OUTPUT
|
||||
pip install ruff
|
||||
|
||||
- name: Run Ruff on Changed Files
|
||||
if: ${{ steps.changed-files.outputs.files != '' }}
|
||||
run: |
|
||||
echo "${{ steps.changed-files.outputs.files }}" | tr " " "\n" | xargs -I{} ruff check "{}"
|
||||
- name: Run Ruff Linter
|
||||
run: ruff check
|
||||
|
||||
8
.github/workflows/tests.yml
vendored
@@ -13,8 +13,9 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 15
|
||||
strategy:
|
||||
fail-fast: true # Stop testing on subsequent Python versions if an earlier one fails
|
||||
matrix:
|
||||
python-version: ['3.10', '3.11', '3.12']
|
||||
python-version: ['3.10', '3.11', '3.12', '3.13']
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
@@ -28,7 +29,12 @@ jobs:
|
||||
run: uv python install ${{ matrix.python-version }}
|
||||
|
||||
- name: Install the project
|
||||
if: matrix.python-version != '3.13'
|
||||
run: uv sync --dev --all-extras
|
||||
|
||||
- name: Install the project (Python 3.13)
|
||||
if: matrix.python-version == '3.13'
|
||||
run: uv sync --dev # Skip all-extras for Python 3.13 due to onnxruntime compatibility
|
||||
|
||||
- name: Run tests
|
||||
run: uv run pytest --block-network --timeout=60 -vv
|
||||
|
||||
@@ -2,3 +2,8 @@ exclude = [
|
||||
"templates",
|
||||
"__init__.py",
|
||||
]
|
||||
|
||||
[lint]
|
||||
select = [
|
||||
"I", # isort rules
|
||||
]
|
||||
|
||||
22
Dockerfile.test
Normal file
@@ -0,0 +1,22 @@
|
||||
FROM python:3.13-slim-bookworm
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
# Health check
|
||||
HEALTHCHECK --interval=30s --timeout=30s --start-period=5s --retries=3 CMD python -c "import crewai" || exit 1
|
||||
|
||||
# Improved installation process
|
||||
RUN pip install --upgrade pip && pip install --no-cache-dir -U pip setuptools wheel && rm -rf /root/.cache/pip/* \
|
||||
|| { echo 'Installation failed'; exit 1; }
|
||||
|
||||
# Copy the current directory contents into the container
|
||||
COPY . /app/
|
||||
|
||||
# Install the package with error handling
|
||||
RUN pip install -e . || { echo 'Package installation failed'; exit 1; }
|
||||
|
||||
# Version confirmation
|
||||
RUN python -c "import crewai; assert crewai.__version__, 'Version check failed'"
|
||||
|
||||
# Test importing the package
|
||||
CMD ["python", "-c", "import crewai; print(f'Successfully imported crewai version {crewai.__version__}')"]
|
||||
@@ -110,8 +110,6 @@ crewai reset-memories [OPTIONS]
|
||||
- `-s, --short`: Reset SHORT TERM memory
|
||||
- `-e, --entities`: Reset ENTITIES memory
|
||||
- `-k, --kickoff-outputs`: Reset LATEST KICKOFF TASK OUTPUTS
|
||||
- `-kn, --knowledge`: Reset KNOWLEDGE storage
|
||||
- `-akn, --agent-knowledge`: Reset AGENT KNOWLEDGE storage
|
||||
- `-a, --all`: Reset ALL memories
|
||||
|
||||
Example:
|
||||
|
||||
@@ -75,12 +75,11 @@ class ExampleFlow(Flow):
|
||||
|
||||
|
||||
flow = ExampleFlow()
|
||||
flow.plot()
|
||||
result = flow.kickoff()
|
||||
|
||||
print(f"Generated fun fact: {result}")
|
||||
```
|
||||

|
||||
|
||||
In the above example, we have created a simple Flow that generates a random city using OpenAI and then generates a fun fact about that city. The Flow consists of two tasks: `generate_city` and `generate_fun_fact`. The `generate_city` task is the starting point of the Flow, and the `generate_fun_fact` task listens for the output of the `generate_city` task.
|
||||
|
||||
Each Flow instance automatically receives a unique identifier (UUID) in its state, which helps track and manage flow executions. The state can also store additional data (like the generated city and fun fact) that persists throughout the flow's execution.
|
||||
@@ -147,7 +146,6 @@ class OutputExampleFlow(Flow):
|
||||
|
||||
|
||||
flow = OutputExampleFlow()
|
||||
flow.plot("my_flow_plot")
|
||||
final_output = flow.kickoff()
|
||||
|
||||
print("---- Final Output ----")
|
||||
@@ -160,10 +158,9 @@ Second method received: Output from first_method
|
||||
```
|
||||
|
||||
</CodeGroup>
|
||||

|
||||
|
||||
In this example, the `second_method` is the last method to complete, so its output will be the final output of the Flow.
|
||||
The `kickoff()` method will return the final output, which is then printed to the console. The `plot()` method will generate the HTML file, which will help you understand the flow.
|
||||
The `kickoff()` method will return the final output, which is then printed to the console.
|
||||
|
||||
#### Accessing and Updating State
|
||||
|
||||
@@ -195,7 +192,6 @@ class StateExampleFlow(Flow[ExampleState]):
|
||||
return self.state.message
|
||||
|
||||
flow = StateExampleFlow()
|
||||
flow.plot("my_flow_plot")
|
||||
final_output = flow.kickoff()
|
||||
print(f"Final Output: {final_output}")
|
||||
print("Final State:")
|
||||
@@ -210,8 +206,6 @@ counter=2 message='Hello from first_method - updated by second_method'
|
||||
|
||||
</CodeGroup>
|
||||
|
||||

|
||||
|
||||
In this example, the state is updated by both `first_method` and `second_method`.
|
||||
After the Flow has run, you can access the final state to see the updates made by these methods.
|
||||
|
||||
@@ -255,12 +249,9 @@ class UnstructuredExampleFlow(Flow):
|
||||
|
||||
|
||||
flow = UnstructuredExampleFlow()
|
||||
flow.plot("my_flow_plot")
|
||||
flow.kickoff()
|
||||
```
|
||||
|
||||

|
||||
|
||||
**Note:** The `id` field is automatically generated and preserved throughout the flow's execution. You don't need to manage or set it manually, and it will be maintained even when updating the state with new data.
|
||||
|
||||
**Key Points:**
|
||||
@@ -311,8 +302,6 @@ flow = StructuredExampleFlow()
|
||||
flow.kickoff()
|
||||
```
|
||||
|
||||

|
||||
|
||||
**Key Points:**
|
||||
|
||||
- **Defined Schema:** `ExampleState` clearly outlines the state structure, enhancing code readability and maintainability.
|
||||
@@ -447,7 +436,6 @@ class OrExampleFlow(Flow):
|
||||
|
||||
|
||||
flow = OrExampleFlow()
|
||||
flow.plot("my_flow_plot")
|
||||
flow.kickoff()
|
||||
```
|
||||
|
||||
@@ -458,8 +446,6 @@ Logger: Hello from the second method
|
||||
|
||||
</CodeGroup>
|
||||
|
||||

|
||||
|
||||
When you run this Flow, the `logger` method will be triggered by the output of either the `start_method` or the `second_method`.
|
||||
The `or_` function is used to listen to multiple methods and trigger the listener method when any of the specified methods emit an output.
|
||||
|
||||
@@ -488,7 +474,6 @@ class AndExampleFlow(Flow):
|
||||
print(self.state)
|
||||
|
||||
flow = AndExampleFlow()
|
||||
flow.plot()
|
||||
flow.kickoff()
|
||||
```
|
||||
|
||||
@@ -499,8 +484,6 @@ flow.kickoff()
|
||||
|
||||
</CodeGroup>
|
||||
|
||||

|
||||
|
||||
When you run this Flow, the `logger` method will be triggered only when both the `start_method` and the `second_method` emit an output.
|
||||
The `and_` function is used to listen to multiple methods and trigger the listener method only when all the specified methods emit an output.
|
||||
|
||||
@@ -544,7 +527,6 @@ class RouterFlow(Flow[ExampleState]):
|
||||
|
||||
|
||||
flow = RouterFlow()
|
||||
flow.plot("my_flow_plot")
|
||||
flow.kickoff()
|
||||
```
|
||||
|
||||
@@ -556,8 +538,6 @@ Fourth method running
|
||||
|
||||
</CodeGroup>
|
||||
|
||||

|
||||
|
||||
In the above example, the `start_method` generates a random boolean value and sets it in the state.
|
||||
The `second_method` uses the `@router()` decorator to define conditional routing logic based on the value of the boolean.
|
||||
If the boolean is `True`, the method returns `"success"`, and if it is `False`, the method returns `"failed"`.
|
||||
@@ -661,7 +641,6 @@ class MarketResearchFlow(Flow[MarketResearchState]):
|
||||
# Usage example
|
||||
async def run_flow():
|
||||
flow = MarketResearchFlow()
|
||||
flow.plot("MarketResearchFlowPlot")
|
||||
result = await flow.kickoff_async(inputs={"product": "AI-powered chatbots"})
|
||||
return result
|
||||
|
||||
@@ -671,8 +650,6 @@ if __name__ == "__main__":
|
||||
asyncio.run(run_flow())
|
||||
```
|
||||
|
||||

|
||||
|
||||
This example demonstrates several key features of using Agents in flows:
|
||||
|
||||
1. **Structured Output**: Using Pydantic models to define the expected output format (`MarketAnalysis`) ensures type safety and structured data throughout the flow.
|
||||
@@ -769,16 +746,13 @@ def kickoff():
|
||||
|
||||
def plot():
|
||||
poem_flow = PoemFlow()
|
||||
poem_flow.plot("PoemFlowPlot")
|
||||
poem_flow.plot()
|
||||
|
||||
if __name__ == "__main__":
|
||||
kickoff()
|
||||
plot()
|
||||
```
|
||||
|
||||
In this example, the `PoemFlow` class defines a flow that generates a sentence count, uses the `PoemCrew` to generate a poem, and then saves the poem to a file. The flow is kicked off by calling the `kickoff()` method. The PoemFlowPlot will be generated by `plot()` method.
|
||||
|
||||

|
||||
In this example, the `PoemFlow` class defines a flow that generates a sentence count, uses the `PoemCrew` to generate a poem, and then saves the poem to a file. The flow is kicked off by calling the `kickoff()` method.
|
||||
|
||||
### Running the Flow
|
||||
|
||||
|
||||
@@ -497,13 +497,6 @@ crew = Crew(
|
||||
result = crew.kickoff(
|
||||
inputs={"question": "What is the storage capacity of the XPS 13?"}
|
||||
)
|
||||
|
||||
# Resetting the agent specific knowledge via crew object
|
||||
crew.reset_memories(command_type = 'agent_knowledge')
|
||||
|
||||
# Resetting the agent specific knowledge via CLI
|
||||
crewai reset-memories --agent-knowledge
|
||||
crewai reset-memories -akn
|
||||
```
|
||||
|
||||
<Info>
|
||||
@@ -707,11 +700,4 @@ recent_news = SpaceNewsKnowledgeSource(
|
||||
- Configure appropriate embedding models
|
||||
- Consider using local embedding providers for faster processing
|
||||
</Accordion>
|
||||
|
||||
<Accordion title="One Time Knowledge">
|
||||
- With the typical file structure provided by CrewAI, knowledge sources are embedded every time the kickoff is triggered.
|
||||
- If the knowledge sources are large, this leads to inefficiency and increased latency, as the same data is embedded each time.
|
||||
- To resolve this, directly initialize the knowledge parameter instead of the knowledge_sources parameter.
|
||||
- Link to the issue to get complete idea [Github Issue](https://github.com/crewAIInc/crewAI/issues/2755)
|
||||
</Accordion>
|
||||
</AccordionGroup>
|
||||
|
||||
@@ -679,7 +679,6 @@ crewai reset-memories [OPTIONS]
|
||||
| `-e`, `--entities` | Reset ENTITIES memory. | Flag (boolean) | False |
|
||||
| `-k`, `--kickoff-outputs` | Reset LATEST KICKOFF TASK OUTPUTS. | Flag (boolean) | False |
|
||||
| `-kn`, `--knowledge` | Reset KNOWLEDEGE storage | Flag (boolean) | False |
|
||||
| `-akn`, `--agent-knowledge` | Reset AGENT KNOWLEDGE storage | Flag (boolean) | False |
|
||||
| `-a`, `--all` | Reset ALL memories. | Flag (boolean) | False |
|
||||
|
||||
Note: To use the cli command you need to have your crew in a file called crew.py in the same directory.
|
||||
@@ -717,11 +716,9 @@ my_crew.reset_memories(command_type = 'all') # Resets all the memory
|
||||
| `entities` | Reset ENTITIES memory. |
|
||||
| `kickoff_outputs` | Reset LATEST KICKOFF TASK OUTPUTS. |
|
||||
| `knowledge` | Reset KNOWLEDGE memory. |
|
||||
| `agent_knowledge` | Reset AGENT KNOWLEDGE memory. |
|
||||
| `all` | Reset ALL memories. |
|
||||
|
||||
|
||||
|
||||
## Benefits of Using CrewAI's Memory System
|
||||
|
||||
- 🦾 **Adaptive Learning:** Crews become more efficient over time, adapting to new information and refining their approach to tasks.
|
||||
|
||||
|
Before Width: | Height: | Size: 44 KiB |
|
Before Width: | Height: | Size: 43 KiB |
|
Before Width: | Height: | Size: 45 KiB |
|
Before Width: | Height: | Size: 57 KiB |
|
Before Width: | Height: | Size: 48 KiB |
|
Before Width: | Height: | Size: 57 KiB |
|
Before Width: | Height: | Size: 60 KiB |
|
Before Width: | Height: | Size: 48 KiB |
@@ -3,7 +3,7 @@ name = "crewai"
|
||||
version = "0.119.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"
|
||||
requires-python = ">=3.10,<3.14"
|
||||
authors = [
|
||||
{ name = "Joao Moura", email = "joao@crewai.com" }
|
||||
]
|
||||
|
||||
@@ -20,7 +20,6 @@ from crewai.tools.agent_tools.agent_tools import AgentTools
|
||||
from crewai.utilities import Converter, Prompts
|
||||
from crewai.utilities.agent_utils import (
|
||||
get_tool_names,
|
||||
load_agent_from_repository,
|
||||
parse_tools,
|
||||
render_text_description_and_args,
|
||||
)
|
||||
@@ -135,16 +134,6 @@ class Agent(BaseAgent):
|
||||
default=None,
|
||||
description="Knowledge search query for the agent dynamically generated by the agent.",
|
||||
)
|
||||
from_repository: Optional[str] = Field(
|
||||
default=None,
|
||||
description="The Agent's role to be used from your repository.",
|
||||
)
|
||||
|
||||
@model_validator(mode="before")
|
||||
def validate_from_repository(cls, v):
|
||||
if v is not None and (from_repository := v.get("from_repository")):
|
||||
return load_agent_from_repository(from_repository) | v
|
||||
return v
|
||||
|
||||
@model_validator(mode="after")
|
||||
def post_init_setup(self):
|
||||
|
||||
@@ -5,5 +5,5 @@ def get_auth_token() -> str:
|
||||
"""Get the authentication token."""
|
||||
access_token = TokenManager().get_token()
|
||||
if not access_token:
|
||||
raise Exception("No token found, make sure you are logged in")
|
||||
raise Exception()
|
||||
return access_token
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import os
|
||||
from importlib.metadata import version as get_version
|
||||
from typing import Optional
|
||||
from typing import Optional, Tuple
|
||||
|
||||
import click
|
||||
|
||||
@@ -137,8 +138,12 @@ def log_tasks_outputs() -> None:
|
||||
@click.option("-s", "--short", is_flag=True, help="Reset SHORT TERM memory")
|
||||
@click.option("-e", "--entities", is_flag=True, help="Reset ENTITIES memory")
|
||||
@click.option("-kn", "--knowledge", is_flag=True, help="Reset KNOWLEDGE storage")
|
||||
@click.option("-akn", "--agent-knowledge", is_flag=True, help="Reset AGENT KNOWLEDGE storage")
|
||||
@click.option("-k","--kickoff-outputs",is_flag=True,help="Reset LATEST KICKOFF TASK OUTPUTS")
|
||||
@click.option(
|
||||
"-k",
|
||||
"--kickoff-outputs",
|
||||
is_flag=True,
|
||||
help="Reset LATEST KICKOFF TASK OUTPUTS",
|
||||
)
|
||||
@click.option("-a", "--all", is_flag=True, help="Reset ALL memories")
|
||||
def reset_memories(
|
||||
long: bool,
|
||||
@@ -146,20 +151,18 @@ def reset_memories(
|
||||
entities: bool,
|
||||
knowledge: bool,
|
||||
kickoff_outputs: bool,
|
||||
agent_knowledge: bool,
|
||||
all: bool,
|
||||
) -> None:
|
||||
"""
|
||||
Reset the crew memories (long, short, entity, latest_crew_kickoff_ouputs, knowledge, agent_knowledge). This will delete all the data saved.
|
||||
Reset the crew memories (long, short, entity, latest_crew_kickoff_ouputs). This will delete all the data saved.
|
||||
"""
|
||||
try:
|
||||
memory_types = [long, short, entities, knowledge, agent_knowledge, kickoff_outputs, all]
|
||||
if not any(memory_types):
|
||||
if not all and not (long or short or entities or knowledge or kickoff_outputs):
|
||||
click.echo(
|
||||
"Please specify at least one memory type to reset using the appropriate flags."
|
||||
)
|
||||
return
|
||||
reset_memories_command(long, short, entities, knowledge, agent_knowledge, kickoff_outputs, all)
|
||||
reset_memories_command(long, short, entities, knowledge, kickoff_outputs, all)
|
||||
except Exception as e:
|
||||
click.echo(f"An error occurred while resetting memories: {e}", err=True)
|
||||
|
||||
|
||||
@@ -14,7 +14,6 @@ class PlusAPI:
|
||||
|
||||
TOOLS_RESOURCE = "/crewai_plus/api/v1/tools"
|
||||
CREWS_RESOURCE = "/crewai_plus/api/v1/crews"
|
||||
AGENTS_RESOURCE = "/crewai_plus/api/v1/agents"
|
||||
|
||||
def __init__(self, api_key: str) -> None:
|
||||
self.api_key = api_key
|
||||
@@ -38,9 +37,6 @@ class PlusAPI:
|
||||
def get_tool(self, handle: str):
|
||||
return self._make_request("GET", f"{self.TOOLS_RESOURCE}/{handle}")
|
||||
|
||||
def get_agent(self, handle: str):
|
||||
return self._make_request("GET", f"{self.AGENTS_RESOURCE}/{handle}")
|
||||
|
||||
def publish_tool(
|
||||
self,
|
||||
handle: str,
|
||||
|
||||
@@ -10,7 +10,6 @@ def reset_memories_command(
|
||||
short,
|
||||
entity,
|
||||
knowledge,
|
||||
agent_knowledge,
|
||||
kickoff_outputs,
|
||||
all,
|
||||
) -> None:
|
||||
@@ -24,11 +23,10 @@ def reset_memories_command(
|
||||
kickoff_outputs (bool): Whether to reset the latest kickoff task outputs.
|
||||
all (bool): Whether to reset all memories.
|
||||
knowledge (bool): Whether to reset the knowledge.
|
||||
agent_knowledge (bool): Whether to reset the agents knowledge.
|
||||
"""
|
||||
|
||||
try:
|
||||
if not any([long, short, entity, kickoff_outputs, knowledge, agent_knowledge, all]):
|
||||
if not any([long, short, entity, kickoff_outputs, knowledge, all]):
|
||||
click.echo(
|
||||
"No memory type specified. Please specify at least one type to reset."
|
||||
)
|
||||
@@ -69,11 +67,6 @@ def reset_memories_command(
|
||||
click.echo(
|
||||
f"[Crew ({crew.name if crew.name else crew.id})] Knowledge has been reset."
|
||||
)
|
||||
if agent_knowledge:
|
||||
crew.reset_memories(command_type="agent_knowledge")
|
||||
click.echo(
|
||||
f"[Crew ({crew.name if crew.name else crew.id})] Agents knowledge has been reset."
|
||||
)
|
||||
|
||||
except subprocess.CalledProcessError as e:
|
||||
click.echo(f"An error occurred while resetting the memories: {e}", err=True)
|
||||
|
||||
@@ -3,7 +3,7 @@ name = "{{folder_name}}"
|
||||
version = "0.1.0"
|
||||
description = "{{name}} using crewAI"
|
||||
authors = [{ name = "Your Name", email = "you@example.com" }]
|
||||
requires-python = ">=3.10,<3.13"
|
||||
requires-python = ">=3.10,<3.14"
|
||||
dependencies = [
|
||||
"crewai[tools]>=0.119.0,<1.0.0"
|
||||
]
|
||||
|
||||
@@ -3,7 +3,7 @@ name = "{{folder_name}}"
|
||||
version = "0.1.0"
|
||||
description = "{{name}} using crewAI"
|
||||
authors = [{ name = "Your Name", email = "you@example.com" }]
|
||||
requires-python = ">=3.10,<3.13"
|
||||
requires-python = ">=3.10,<3.14"
|
||||
dependencies = [
|
||||
"crewai[tools]>=0.119.0,<1.0.0",
|
||||
]
|
||||
|
||||
@@ -3,7 +3,7 @@ name = "{{folder_name}}"
|
||||
version = "0.1.0"
|
||||
description = "Power up your crews with {{folder_name}}"
|
||||
readme = "README.md"
|
||||
requires-python = ">=3.10,<3.13"
|
||||
requires-python = ">=3.10,<3.14"
|
||||
dependencies = [
|
||||
"crewai[tools]>=0.119.0"
|
||||
]
|
||||
|
||||
@@ -52,7 +52,7 @@ from crewai.tools.agent_tools.agent_tools import AgentTools
|
||||
from crewai.tools.base_tool import BaseTool, Tool
|
||||
from crewai.types.usage_metrics import UsageMetrics
|
||||
from crewai.utilities import I18N, FileHandler, Logger, RPMController
|
||||
from crewai.utilities.constants import NOT_SPECIFIED, TRAINING_DATA_FILE
|
||||
from crewai.utilities.constants import TRAINING_DATA_FILE
|
||||
from crewai.utilities.evaluators.crew_evaluator_handler import CrewEvaluator
|
||||
from crewai.utilities.evaluators.task_evaluator import TaskEvaluator
|
||||
from crewai.utilities.events.crew_events import (
|
||||
@@ -478,7 +478,7 @@ class Crew(FlowTrackable, BaseModel):
|
||||
separated by a synchronous task.
|
||||
"""
|
||||
for i, task in enumerate(self.tasks):
|
||||
if task.async_execution and isinstance(task.context, list):
|
||||
if task.async_execution and task.context:
|
||||
for context_task in task.context:
|
||||
if context_task.async_execution:
|
||||
for j in range(i - 1, -1, -1):
|
||||
@@ -496,7 +496,7 @@ class Crew(FlowTrackable, BaseModel):
|
||||
task_indices = {id(task): i for i, task in enumerate(self.tasks)}
|
||||
|
||||
for task in self.tasks:
|
||||
if isinstance(task.context, list):
|
||||
if task.context:
|
||||
for context_task in task.context:
|
||||
if id(context_task) not in task_indices:
|
||||
continue # Skip context tasks not in the main tasks list
|
||||
@@ -1034,14 +1034,11 @@ class Crew(FlowTrackable, BaseModel):
|
||||
)
|
||||
return cast(List[BaseTool], tools)
|
||||
|
||||
def _get_context(self, task: Task, task_outputs: List[TaskOutput]) -> str:
|
||||
if not task.context:
|
||||
return ""
|
||||
|
||||
def _get_context(self, task: Task, task_outputs: List[TaskOutput]):
|
||||
context = (
|
||||
aggregate_raw_outputs_from_task_outputs(task_outputs)
|
||||
if task.context is NOT_SPECIFIED
|
||||
else aggregate_raw_outputs_from_tasks(task.context)
|
||||
aggregate_raw_outputs_from_tasks(task.context)
|
||||
if task.context
|
||||
else aggregate_raw_outputs_from_task_outputs(task_outputs)
|
||||
)
|
||||
return context
|
||||
|
||||
@@ -1229,7 +1226,7 @@ class Crew(FlowTrackable, BaseModel):
|
||||
task_mapping[task.key] = cloned_task
|
||||
|
||||
for cloned_task, original_task in zip(cloned_tasks, self.tasks):
|
||||
if isinstance(original_task.context, list):
|
||||
if original_task.context:
|
||||
cloned_context = [
|
||||
task_mapping[context_task.key]
|
||||
for context_task in original_task.context
|
||||
@@ -1356,7 +1353,7 @@ class Crew(FlowTrackable, BaseModel):
|
||||
|
||||
Args:
|
||||
command_type: Type of memory to reset.
|
||||
Valid options: 'long', 'short', 'entity', 'knowledge', 'agent_knowledge'
|
||||
Valid options: 'long', 'short', 'entity', 'knowledge',
|
||||
'kickoff_outputs', or 'all'
|
||||
|
||||
Raises:
|
||||
@@ -1369,7 +1366,6 @@ class Crew(FlowTrackable, BaseModel):
|
||||
"short",
|
||||
"entity",
|
||||
"knowledge",
|
||||
"agent_knowledge",
|
||||
"kickoff_outputs",
|
||||
"all",
|
||||
"external",
|
||||
@@ -1394,14 +1390,19 @@ class Crew(FlowTrackable, BaseModel):
|
||||
|
||||
def _reset_all_memories(self) -> None:
|
||||
"""Reset all available memory systems."""
|
||||
memory_systems = self._get_memory_systems()
|
||||
memory_systems = [
|
||||
("short term", getattr(self, "_short_term_memory", None)),
|
||||
("entity", getattr(self, "_entity_memory", None)),
|
||||
("external", getattr(self, "_external_memory", None)),
|
||||
("long term", getattr(self, "_long_term_memory", None)),
|
||||
("task output", getattr(self, "_task_output_handler", None)),
|
||||
("knowledge", getattr(self, "knowledge", None)),
|
||||
]
|
||||
|
||||
for memory_type, config in memory_systems.items():
|
||||
if (system := config.get('system')) is not None:
|
||||
name = config.get('name')
|
||||
for name, system in memory_systems:
|
||||
if system is not None:
|
||||
try:
|
||||
reset_fn: Callable = cast(Callable, config.get('reset'))
|
||||
reset_fn(system)
|
||||
system.reset()
|
||||
self._logger.log(
|
||||
"info",
|
||||
f"[Crew ({self.name if self.name else self.id})] {name} memory has been reset",
|
||||
@@ -1420,17 +1421,24 @@ class Crew(FlowTrackable, BaseModel):
|
||||
Raises:
|
||||
RuntimeError: If the specified memory system fails to reset
|
||||
"""
|
||||
memory_systems = self._get_memory_systems()
|
||||
config = memory_systems[memory_type]
|
||||
system = config.get('system')
|
||||
name = config.get('name')
|
||||
reset_functions = {
|
||||
"long": (getattr(self, "_long_term_memory", None), "long term"),
|
||||
"short": (getattr(self, "_short_term_memory", None), "short term"),
|
||||
"entity": (getattr(self, "_entity_memory", None), "entity"),
|
||||
"knowledge": (getattr(self, "knowledge", None), "knowledge"),
|
||||
"kickoff_outputs": (
|
||||
getattr(self, "_task_output_handler", None),
|
||||
"task output",
|
||||
),
|
||||
"external": (getattr(self, "_external_memory", None), "external"),
|
||||
}
|
||||
|
||||
if system is None:
|
||||
memory_system, name = reset_functions[memory_type]
|
||||
if memory_system is None:
|
||||
raise RuntimeError(f"{name} memory system is not initialized")
|
||||
|
||||
|
||||
try:
|
||||
reset_fn: Callable = cast(Callable, config.get('reset'))
|
||||
reset_fn(system)
|
||||
memory_system.reset()
|
||||
self._logger.log(
|
||||
"info",
|
||||
f"[Crew ({self.name if self.name else self.id})] {name} memory has been reset",
|
||||
@@ -1439,64 +1447,3 @@ class Crew(FlowTrackable, BaseModel):
|
||||
raise RuntimeError(
|
||||
f"[Crew ({self.name if self.name else self.id})] Failed to reset {name} memory: {str(e)}"
|
||||
) from e
|
||||
|
||||
def _get_memory_systems(self):
|
||||
"""Get all available memory systems with their configuration.
|
||||
|
||||
Returns:
|
||||
Dict containing all memory systems with their reset functions and display names.
|
||||
"""
|
||||
def default_reset(memory):
|
||||
return memory.reset()
|
||||
def knowledge_reset(memory):
|
||||
return self.reset_knowledge(memory)
|
||||
|
||||
# Get knowledge for agents
|
||||
agent_knowledges = [getattr(agent, "knowledge", None) for agent in self.agents
|
||||
if getattr(agent, "knowledge", None) is not None]
|
||||
# Get knowledge for crew and agents
|
||||
crew_knowledge = getattr(self, "knowledge", None)
|
||||
crew_and_agent_knowledges = ([crew_knowledge] if crew_knowledge is not None else []) + agent_knowledges
|
||||
|
||||
return {
|
||||
'short': {
|
||||
'system': getattr(self, "_short_term_memory", None),
|
||||
'reset': default_reset,
|
||||
'name': 'Short Term'
|
||||
},
|
||||
'entity': {
|
||||
'system': getattr(self, "_entity_memory", None),
|
||||
'reset': default_reset,
|
||||
'name': 'Entity'
|
||||
},
|
||||
'external': {
|
||||
'system': getattr(self, "_external_memory", None),
|
||||
'reset': default_reset,
|
||||
'name': 'External'
|
||||
},
|
||||
'long': {
|
||||
'system': getattr(self, "_long_term_memory", None),
|
||||
'reset': default_reset,
|
||||
'name': 'Long Term'
|
||||
},
|
||||
'kickoff_outputs': {
|
||||
'system': getattr(self, "_task_output_handler", None),
|
||||
'reset': default_reset,
|
||||
'name': 'Task Output'
|
||||
},
|
||||
'knowledge': {
|
||||
'system': crew_and_agent_knowledges if crew_and_agent_knowledges else None,
|
||||
'reset': knowledge_reset,
|
||||
'name': 'Crew Knowledge and Agent Knowledge'
|
||||
},
|
||||
'agent_knowledge': {
|
||||
'system': agent_knowledges if agent_knowledges else None,
|
||||
'reset': knowledge_reset,
|
||||
'name': 'Agent Knowledge'
|
||||
}
|
||||
}
|
||||
|
||||
def reset_knowledge(self, knowledges: List[Knowledge]) -> None:
|
||||
"""Reset crew and agent knowledge storage."""
|
||||
for ks in knowledges:
|
||||
ks.reset()
|
||||
|
||||
@@ -1,3 +0,0 @@
|
||||
from .utils.embedding_functions import VoyageAIEmbeddingFunction
|
||||
|
||||
__all__ = ["VoyageAIEmbeddingFunction"]
|
||||
@@ -1,3 +0,0 @@
|
||||
from . import embedding_functions
|
||||
|
||||
__all__ = ["embedding_functions"]
|
||||
@@ -1,3 +0,0 @@
|
||||
from .voyageai_embedding_function import VoyageAIEmbeddingFunction
|
||||
|
||||
__all__ = ["VoyageAIEmbeddingFunction"]
|
||||
@@ -1,91 +0,0 @@
|
||||
import logging
|
||||
from typing import List, Union
|
||||
|
||||
from chromadb.api.types import Documents, EmbeddingFunction
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class VoyageAIEmbeddingFunction(EmbeddingFunction[Documents]):
|
||||
"""
|
||||
VoyageAI embedding function for ChromaDB.
|
||||
|
||||
This class provides integration with VoyageAI's embedding models for use with ChromaDB.
|
||||
It supports various VoyageAI models including voyage-3, voyage-3.5, and voyage-3.5-lite.
|
||||
|
||||
Attributes:
|
||||
_api_key (str): The API key for VoyageAI.
|
||||
_model_name (str): The name of the VoyageAI model to use.
|
||||
"""
|
||||
|
||||
def __init__(self, api_key: str, model_name: str = "voyage-3"):
|
||||
"""
|
||||
Initialize the VoyageAI embedding function.
|
||||
|
||||
Args:
|
||||
api_key (str): The API key for VoyageAI.
|
||||
model_name (str, optional): The name of the VoyageAI model to use.
|
||||
Defaults to "voyage-3".
|
||||
|
||||
Raises:
|
||||
ValueError: If the voyageai package is not installed or if the API key is empty.
|
||||
"""
|
||||
self._ensure_voyageai_installed()
|
||||
|
||||
if not api_key:
|
||||
raise ValueError("API key is required for VoyageAI embeddings")
|
||||
|
||||
self._api_key = api_key
|
||||
self._model_name = model_name
|
||||
|
||||
def _ensure_voyageai_installed(self):
|
||||
"""
|
||||
Ensure that the voyageai package is installed.
|
||||
|
||||
Raises:
|
||||
ValueError: If the voyageai package is not installed.
|
||||
"""
|
||||
try:
|
||||
import voyageai # noqa: F401
|
||||
except ImportError:
|
||||
raise ValueError(
|
||||
"The voyageai python package is not installed. Please install it with `pip install voyageai`"
|
||||
)
|
||||
|
||||
def __call__(self, input: Union[str, List[str]]) -> List[List[float]]:
|
||||
"""
|
||||
Generate embeddings for the input text(s).
|
||||
|
||||
Args:
|
||||
input (Union[str, List[str]]): The text or list of texts to generate embeddings for.
|
||||
|
||||
Returns:
|
||||
List[List[float]]: A list of embeddings, where each embedding is a list of floats.
|
||||
|
||||
Raises:
|
||||
ValueError: If the input is not a string or list of strings.
|
||||
voyageai.VoyageError: If there is an error with the VoyageAI API.
|
||||
"""
|
||||
self._ensure_voyageai_installed()
|
||||
import voyageai
|
||||
|
||||
if not input:
|
||||
return []
|
||||
|
||||
if not isinstance(input, (str, list)):
|
||||
raise ValueError("Input must be a string or a list of strings")
|
||||
|
||||
if isinstance(input, str):
|
||||
input = [input]
|
||||
|
||||
try:
|
||||
embeddings = voyageai.get_embeddings(
|
||||
input, model=self._model_name, api_key=self._api_key
|
||||
)
|
||||
return embeddings
|
||||
except voyageai.VoyageError as e:
|
||||
logger.error(f"VoyageAI API error: {e}")
|
||||
raise
|
||||
except Exception as e:
|
||||
logger.error(f"Unexpected error during VoyageAI embedding: {e}")
|
||||
raise
|
||||
@@ -5,7 +5,8 @@ import sys
|
||||
import threading
|
||||
import warnings
|
||||
from collections import defaultdict
|
||||
from contextlib import contextmanager, redirect_stderr, redirect_stdout
|
||||
from contextlib import contextmanager
|
||||
from types import SimpleNamespace
|
||||
from typing import (
|
||||
Any,
|
||||
DefaultDict,
|
||||
@@ -30,6 +31,7 @@ from crewai.utilities.events.llm_events import (
|
||||
LLMCallType,
|
||||
LLMStreamChunkEvent,
|
||||
)
|
||||
from crewai.utilities.events.tool_usage_events import ToolExecutionErrorEvent
|
||||
|
||||
with warnings.catch_warnings():
|
||||
warnings.simplefilter("ignore", UserWarning)
|
||||
@@ -43,9 +45,6 @@ with warnings.catch_warnings():
|
||||
from litellm.utils import supports_response_schema
|
||||
|
||||
|
||||
import io
|
||||
from typing import TextIO
|
||||
|
||||
from crewai.llms.base_llm import BaseLLM
|
||||
from crewai.utilities.events import crewai_event_bus
|
||||
from crewai.utilities.exceptions.context_window_exceeding_exception import (
|
||||
@@ -55,17 +54,12 @@ from crewai.utilities.exceptions.context_window_exceeding_exception import (
|
||||
load_dotenv()
|
||||
|
||||
|
||||
class FilteredStream(io.TextIOBase):
|
||||
_lock = None
|
||||
|
||||
def __init__(self, original_stream: TextIO):
|
||||
class FilteredStream:
|
||||
def __init__(self, original_stream):
|
||||
self._original_stream = original_stream
|
||||
self._lock = threading.Lock()
|
||||
|
||||
def write(self, s: str) -> int:
|
||||
if not self._lock:
|
||||
self._lock = threading.Lock()
|
||||
|
||||
def write(self, s) -> int:
|
||||
with self._lock:
|
||||
# Filter out extraneous messages from LiteLLM
|
||||
if (
|
||||
@@ -220,11 +214,15 @@ def suppress_warnings():
|
||||
)
|
||||
|
||||
# Redirect stdout and stderr
|
||||
with (
|
||||
redirect_stdout(FilteredStream(sys.stdout)),
|
||||
redirect_stderr(FilteredStream(sys.stderr)),
|
||||
):
|
||||
old_stdout = sys.stdout
|
||||
old_stderr = sys.stderr
|
||||
sys.stdout = FilteredStream(old_stdout)
|
||||
sys.stderr = FilteredStream(old_stderr)
|
||||
try:
|
||||
yield
|
||||
finally:
|
||||
sys.stdout = old_stdout
|
||||
sys.stderr = old_stderr
|
||||
|
||||
|
||||
class Delta(TypedDict):
|
||||
|
||||
@@ -2,6 +2,7 @@ import datetime
|
||||
import inspect
|
||||
import json
|
||||
import logging
|
||||
import re
|
||||
import threading
|
||||
import uuid
|
||||
from concurrent.futures import Future
|
||||
@@ -40,7 +41,6 @@ from crewai.tasks.output_format import OutputFormat
|
||||
from crewai.tasks.task_output import TaskOutput
|
||||
from crewai.tools.base_tool import BaseTool
|
||||
from crewai.utilities.config import process_config
|
||||
from crewai.utilities.constants import NOT_SPECIFIED
|
||||
from crewai.utilities.converter import Converter, convert_to_model
|
||||
from crewai.utilities.events import (
|
||||
TaskCompletedEvent,
|
||||
@@ -97,7 +97,7 @@ class Task(BaseModel):
|
||||
)
|
||||
context: Optional[List["Task"]] = Field(
|
||||
description="Other tasks that will have their output used as context for this task.",
|
||||
default=NOT_SPECIFIED,
|
||||
default=None,
|
||||
)
|
||||
async_execution: Optional[bool] = Field(
|
||||
description="Whether the task should be executed asynchronously or not.",
|
||||
@@ -643,7 +643,7 @@ class Task(BaseModel):
|
||||
|
||||
cloned_context = (
|
||||
[task_mapping[context_task.key] for context_task in self.context]
|
||||
if isinstance(self.context, list)
|
||||
if self.context
|
||||
else None
|
||||
)
|
||||
|
||||
|
||||
@@ -10,18 +10,6 @@ from contextlib import contextmanager
|
||||
from importlib.metadata import version
|
||||
from typing import TYPE_CHECKING, Any, Optional
|
||||
|
||||
from opentelemetry import trace
|
||||
from opentelemetry.exporter.otlp.proto.http.trace_exporter import (
|
||||
OTLPSpanExporter,
|
||||
)
|
||||
from opentelemetry.sdk.resources import SERVICE_NAME, Resource
|
||||
from opentelemetry.sdk.trace import TracerProvider
|
||||
from opentelemetry.sdk.trace.export import (
|
||||
BatchSpanProcessor,
|
||||
SpanExportResult,
|
||||
)
|
||||
from opentelemetry.trace import Span, Status, StatusCode
|
||||
|
||||
from crewai.telemetry.constants import (
|
||||
CREWAI_TELEMETRY_BASE_URL,
|
||||
CREWAI_TELEMETRY_SERVICE_NAME,
|
||||
@@ -37,6 +25,18 @@ def suppress_warnings():
|
||||
yield
|
||||
|
||||
|
||||
from opentelemetry import trace # 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 ( # noqa: E402
|
||||
BatchSpanProcessor,
|
||||
SpanExportResult,
|
||||
)
|
||||
from opentelemetry.trace import Span, Status, StatusCode # noqa: E402
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from crewai.crew import Crew
|
||||
from crewai.task import Task
|
||||
@@ -232,7 +232,7 @@ class Telemetry:
|
||||
"agent_key": task.agent.key if task.agent else None,
|
||||
"context": (
|
||||
[task.description for task in task.context]
|
||||
if isinstance(task.context, list)
|
||||
if task.context
|
||||
else None
|
||||
),
|
||||
"tools_names": [
|
||||
@@ -748,7 +748,7 @@ class Telemetry:
|
||||
"agent_key": task.agent.key if task.agent else None,
|
||||
"context": (
|
||||
[task.description for task in task.context]
|
||||
if isinstance(task.context, list)
|
||||
if task.context
|
||||
else None
|
||||
),
|
||||
"tools_names": [
|
||||
|
||||
@@ -16,7 +16,6 @@ from crewai.tools.base_tool import BaseTool
|
||||
from crewai.tools.structured_tool import CrewStructuredTool
|
||||
from crewai.tools.tool_types import ToolResult
|
||||
from crewai.utilities import I18N, Printer
|
||||
from crewai.utilities.errors import AgentRepositoryError
|
||||
from crewai.utilities.exceptions.context_window_exceeding_exception import (
|
||||
LLMContextLengthExceededException,
|
||||
)
|
||||
@@ -429,41 +428,3 @@ def show_agent_logs(
|
||||
printer.print(
|
||||
content=f"\033[95m## Final Answer:\033[00m \033[92m\n{formatted_answer.output}\033[00m\n\n"
|
||||
)
|
||||
|
||||
|
||||
def load_agent_from_repository(from_repository: str) -> Dict[str, Any]:
|
||||
attributes: Dict[str, Any] = {}
|
||||
if from_repository:
|
||||
import importlib
|
||||
|
||||
from crewai.cli.authentication.token import get_auth_token
|
||||
from crewai.cli.plus_api import PlusAPI
|
||||
|
||||
client = PlusAPI(api_key=get_auth_token())
|
||||
response = client.get_agent(from_repository)
|
||||
if response.status_code == 404:
|
||||
raise AgentRepositoryError(
|
||||
f"Agent {from_repository} does not exist, make sure the name is correct or the agent is available on your organization"
|
||||
)
|
||||
|
||||
if response.status_code != 200:
|
||||
raise AgentRepositoryError(
|
||||
f"Agent {from_repository} could not be loaded: {response.text}"
|
||||
)
|
||||
|
||||
agent = response.json()
|
||||
for key, value in agent.items():
|
||||
if key == "tools":
|
||||
attributes[key] = []
|
||||
for tool in value:
|
||||
try:
|
||||
module = importlib.import_module("crewai_tools")
|
||||
tool_class = getattr(module, tool["name"])
|
||||
attributes[key].append(tool_class())
|
||||
except Exception as e:
|
||||
raise AgentRepositoryError(
|
||||
f"Tool {tool['name']} could not be loaded: {e}"
|
||||
) from e
|
||||
else:
|
||||
attributes[key] = value
|
||||
return attributes
|
||||
|
||||
@@ -5,14 +5,3 @@ KNOWLEDGE_DIRECTORY = "knowledge"
|
||||
MAX_LLM_RETRY = 3
|
||||
MAX_FILE_NAME_LENGTH = 255
|
||||
EMITTER_COLOR = "bold_blue"
|
||||
|
||||
|
||||
class _NotSpecified:
|
||||
def __repr__(self):
|
||||
return "NOT_SPECIFIED"
|
||||
|
||||
|
||||
# Sentinel value used to detect when no value has been explicitly provided.
|
||||
# Unlike `None`, which might be a valid value from the user, `NOT_SPECIFIED` allows
|
||||
# us to distinguish between "not passed at all" and "explicitly passed None" or "[]".
|
||||
NOT_SPECIFIED = _NotSpecified()
|
||||
|
||||
@@ -140,7 +140,7 @@ class EmbeddingConfigurator:
|
||||
|
||||
@staticmethod
|
||||
def _configure_voyageai(config, model_name):
|
||||
from crewai.knowledge.embedder.chromadb.utils.embedding_functions.voyageai_embedding_function import (
|
||||
from chromadb.utils.embedding_functions.voyageai_embedding_function import (
|
||||
VoyageAIEmbeddingFunction,
|
||||
)
|
||||
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
"""Error message definitions for CrewAI database operations."""
|
||||
|
||||
from typing import Optional
|
||||
|
||||
|
||||
@@ -38,9 +37,3 @@ class DatabaseError:
|
||||
The formatted error message
|
||||
"""
|
||||
return template.format(str(error))
|
||||
|
||||
|
||||
class AgentRepositoryError(Exception):
|
||||
"""Exception raised when an agent repository is not found."""
|
||||
|
||||
...
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import re
|
||||
from typing import TYPE_CHECKING, List
|
||||
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from crewai.task import Task
|
||||
from crewai.tasks.task_output import TaskOutput
|
||||
@@ -17,11 +17,6 @@ def aggregate_raw_outputs_from_task_outputs(task_outputs: List["TaskOutput"]) ->
|
||||
|
||||
def aggregate_raw_outputs_from_tasks(tasks: List["Task"]) -> str:
|
||||
"""Generate string context from the tasks."""
|
||||
|
||||
task_outputs = (
|
||||
[task.output for task in tasks if task.output is not None]
|
||||
if isinstance(tasks, list)
|
||||
else []
|
||||
)
|
||||
task_outputs = [task.output for task in tasks if task.output is not None]
|
||||
|
||||
return aggregate_raw_outputs_from_task_outputs(task_outputs)
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
import os
|
||||
from unittest import mock
|
||||
from unittest.mock import MagicMock, patch
|
||||
from unittest.mock import patch
|
||||
|
||||
import pytest
|
||||
|
||||
@@ -18,7 +18,6 @@ from crewai.tools import tool
|
||||
from crewai.tools.tool_calling import InstructorToolCalling
|
||||
from crewai.tools.tool_usage import ToolUsage
|
||||
from crewai.utilities import RPMController
|
||||
from crewai.utilities.errors import AgentRepositoryError
|
||||
from crewai.utilities.events import crewai_event_bus
|
||||
from crewai.utilities.events.tool_usage_events import ToolUsageFinishedEvent
|
||||
|
||||
@@ -309,7 +308,9 @@ def test_cache_hitting():
|
||||
def handle_tool_end(source, event):
|
||||
received_events.append(event)
|
||||
|
||||
with (patch.object(CacheHandler, "read") as read,):
|
||||
with (
|
||||
patch.object(CacheHandler, "read") as read,
|
||||
):
|
||||
read.return_value = "0"
|
||||
task = Task(
|
||||
description="What is 2 times 6? Ignore correctness and just return the result of the multiplication tool, you must use the tool.",
|
||||
@@ -1039,7 +1040,7 @@ def test_agent_human_input():
|
||||
CrewAgentExecutor,
|
||||
"_invoke_loop",
|
||||
return_value=AgentFinish(output="Hello", thought="", text=""),
|
||||
),
|
||||
) as mock_invoke_loop,
|
||||
):
|
||||
# Execute the task
|
||||
output = agent.execute_task(task)
|
||||
@@ -2024,99 +2025,3 @@ def test_get_knowledge_search_query():
|
||||
},
|
||||
]
|
||||
)
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def mock_get_auth_token():
|
||||
with patch(
|
||||
"crewai.cli.authentication.token.get_auth_token", return_value="test_token"
|
||||
):
|
||||
yield
|
||||
|
||||
|
||||
@patch("crewai.cli.plus_api.PlusAPI.get_agent")
|
||||
def test_agent_from_repository(mock_get_agent, mock_get_auth_token):
|
||||
from crewai_tools import SerperDevTool
|
||||
|
||||
mock_get_response = MagicMock()
|
||||
mock_get_response.status_code = 200
|
||||
mock_get_response.json.return_value = {
|
||||
"role": "test role",
|
||||
"goal": "test goal",
|
||||
"backstory": "test backstory",
|
||||
"tools": [{"name": "SerperDevTool"}],
|
||||
}
|
||||
mock_get_agent.return_value = mock_get_response
|
||||
agent = Agent(from_repository="test_agent")
|
||||
|
||||
assert agent.role == "test role"
|
||||
assert agent.goal == "test goal"
|
||||
assert agent.backstory == "test backstory"
|
||||
assert len(agent.tools) == 1
|
||||
assert isinstance(agent.tools[0], SerperDevTool)
|
||||
|
||||
|
||||
@patch("crewai.cli.plus_api.PlusAPI.get_agent")
|
||||
def test_agent_from_repository_override_attributes(mock_get_agent, mock_get_auth_token):
|
||||
from crewai_tools import SerperDevTool
|
||||
|
||||
mock_get_response = MagicMock()
|
||||
mock_get_response.status_code = 200
|
||||
mock_get_response.json.return_value = {
|
||||
"role": "test role",
|
||||
"goal": "test goal",
|
||||
"backstory": "test backstory",
|
||||
"tools": [{"name": "SerperDevTool"}],
|
||||
}
|
||||
mock_get_agent.return_value = mock_get_response
|
||||
agent = Agent(from_repository="test_agent", role="Custom Role")
|
||||
|
||||
assert agent.role == "Custom Role"
|
||||
assert agent.goal == "test goal"
|
||||
assert agent.backstory == "test backstory"
|
||||
assert len(agent.tools) == 1
|
||||
assert isinstance(agent.tools[0], SerperDevTool)
|
||||
|
||||
|
||||
@patch("crewai.cli.plus_api.PlusAPI.get_agent")
|
||||
def test_agent_from_repository_with_invalid_tools(mock_get_agent, mock_get_auth_token):
|
||||
mock_get_response = MagicMock()
|
||||
mock_get_response.status_code = 200
|
||||
mock_get_response.json.return_value = {
|
||||
"role": "test role",
|
||||
"goal": "test goal",
|
||||
"backstory": "test backstory",
|
||||
"tools": [{"name": "DoesNotExist"}],
|
||||
}
|
||||
mock_get_agent.return_value = mock_get_response
|
||||
with pytest.raises(
|
||||
AgentRepositoryError,
|
||||
match="Tool DoesNotExist could not be loaded: module 'crewai_tools' has no attribute 'DoesNotExist'",
|
||||
):
|
||||
Agent(from_repository="test_agent")
|
||||
|
||||
|
||||
@patch("crewai.cli.plus_api.PlusAPI.get_agent")
|
||||
def test_agent_from_repository_internal_error(mock_get_agent, mock_get_auth_token):
|
||||
mock_get_response = MagicMock()
|
||||
mock_get_response.status_code = 500
|
||||
mock_get_response.text = "Internal server error"
|
||||
mock_get_agent.return_value = mock_get_response
|
||||
with pytest.raises(
|
||||
AgentRepositoryError,
|
||||
match="Agent test_agent could not be loaded: Internal server error",
|
||||
):
|
||||
Agent(from_repository="test_agent")
|
||||
|
||||
|
||||
@patch("crewai.cli.plus_api.PlusAPI.get_agent")
|
||||
def test_agent_from_repository_agent_not_found(mock_get_agent, mock_get_auth_token):
|
||||
mock_get_response = MagicMock()
|
||||
mock_get_response.status_code = 404
|
||||
mock_get_response.text = "Agent not found"
|
||||
mock_get_agent.return_value = mock_get_response
|
||||
with pytest.raises(
|
||||
AgentRepositoryError,
|
||||
match="Agent test_agent does not exist, make sure the name is correct or the agent is available on your organization",
|
||||
):
|
||||
Agent(from_repository="test_agent")
|
||||
|
||||
@@ -162,18 +162,8 @@ def test_reset_knowledge(mock_get_crews, runner):
|
||||
assert call_count == 1, "reset_memories should have been called once"
|
||||
|
||||
|
||||
def test_reset_agent_knowledge(mock_get_crews, runner):
|
||||
result = runner.invoke(reset_memories, ["--agent-knowledge"])
|
||||
call_count = 0
|
||||
for crew in mock_get_crews.return_value:
|
||||
crew.reset_memories.assert_called_once_with(command_type="agent_knowledge")
|
||||
assert f"[Crew ({crew.name})] Agents knowledge has been reset." in result.output
|
||||
call_count += 1
|
||||
|
||||
assert call_count == 1, "reset_memories should have been called once"
|
||||
|
||||
|
||||
def test_reset_memory_from_many_crews(mock_get_crews, runner):
|
||||
|
||||
crews = []
|
||||
for crew_id in ["id-1234", "id-5678"]:
|
||||
mock_crew = mock.Mock(spec=Crew)
|
||||
|
||||
@@ -231,7 +231,7 @@ class TestDeployCommand(unittest.TestCase):
|
||||
[project]
|
||||
name = "test_project"
|
||||
version = "0.1.0"
|
||||
requires-python = ">=3.10,<3.13"
|
||||
requires-python = ">=3.10,<3.14"
|
||||
dependencies = ["crewai"]
|
||||
""",
|
||||
)
|
||||
@@ -250,7 +250,7 @@ class TestDeployCommand(unittest.TestCase):
|
||||
[project]
|
||||
name = "test_project"
|
||||
version = "0.1.0"
|
||||
requires-python = ">=3.10,<3.13"
|
||||
requires-python = ">=3.10,<3.14"
|
||||
dependencies = ["crewai"]
|
||||
""",
|
||||
)
|
||||
|
||||
@@ -2,19 +2,22 @@
|
||||
|
||||
import hashlib
|
||||
import json
|
||||
import os
|
||||
import tempfile
|
||||
from concurrent.futures import Future
|
||||
from unittest import mock
|
||||
from unittest.mock import ANY, MagicMock, patch
|
||||
from unittest.mock import MagicMock, patch
|
||||
|
||||
import pydantic_core
|
||||
import pytest
|
||||
|
||||
from crewai.agent import Agent
|
||||
from crewai.agents import CacheHandler
|
||||
from crewai.agents.cache import CacheHandler
|
||||
from crewai.agents.crew_agent_executor import CrewAgentExecutor
|
||||
from crewai.crew import Crew
|
||||
from crewai.crews.crew_output import CrewOutput
|
||||
from crewai.flow import Flow, start
|
||||
from crewai.knowledge.knowledge import Knowledge
|
||||
from crewai.flow import Flow, listen, start
|
||||
from crewai.knowledge.source.string_knowledge_source import StringKnowledgeSource
|
||||
from crewai.llm import LLM
|
||||
from crewai.memory.contextual.contextual_memory import ContextualMemory
|
||||
@@ -3138,30 +3141,6 @@ def test_replay_with_context():
|
||||
assert crew.tasks[1].context[0].output.raw == "context raw output"
|
||||
|
||||
|
||||
def test_replay_with_context_set_to_nullable():
|
||||
agent = Agent(role="test_agent", backstory="Test Description", goal="Test Goal")
|
||||
task1 = Task(
|
||||
description="Context Task", expected_output="Say Task Output", agent=agent
|
||||
)
|
||||
task2 = Task(
|
||||
description="Test Task", expected_output="Say Hi", agent=agent, context=[]
|
||||
)
|
||||
task3 = Task(
|
||||
description="Test Task 3", expected_output="Say Hi", agent=agent, context=None
|
||||
)
|
||||
|
||||
crew = Crew(agents=[agent], tasks=[task1, task2, task3], process=Process.sequential)
|
||||
with patch("crewai.task.Task.execute_sync") as mock_execute_task:
|
||||
mock_execute_task.return_value = TaskOutput(
|
||||
description="Test Task Output",
|
||||
raw="test raw output",
|
||||
agent="test_agent",
|
||||
)
|
||||
crew.kickoff()
|
||||
|
||||
mock_execute_task.assert_called_with(agent=ANY, context="", tools=ANY)
|
||||
|
||||
|
||||
@pytest.mark.vcr(filter_headers=["authorization"])
|
||||
def test_replay_with_invalid_task_id():
|
||||
agent = Agent(role="test_agent", backstory="Test Description", goal="Test Goal")
|
||||
@@ -4404,165 +4383,3 @@ def test_sets_parent_flow_when_inside_flow(researcher, writer):
|
||||
flow = MyFlow()
|
||||
result = flow.kickoff()
|
||||
assert result.parent_flow is flow
|
||||
|
||||
|
||||
def test_reset_knowledge_with_no_crew_knowledge(researcher,writer):
|
||||
crew = Crew(
|
||||
agents=[researcher, writer],
|
||||
process=Process.sequential,
|
||||
tasks=[
|
||||
Task(description="Task 1", expected_output="output", agent=researcher),
|
||||
Task(description="Task 2", expected_output="output", agent=writer),
|
||||
]
|
||||
)
|
||||
|
||||
with pytest.raises(RuntimeError) as excinfo:
|
||||
crew.reset_memories(command_type='knowledge')
|
||||
|
||||
# Optionally, you can also check the error message
|
||||
assert "Crew Knowledge and Agent Knowledge memory system is not initialized" in str(excinfo.value) # Replace with the expected message
|
||||
|
||||
|
||||
def test_reset_knowledge_with_only_crew_knowledge(researcher,writer):
|
||||
mock_ks = MagicMock(spec=Knowledge)
|
||||
|
||||
with patch.object(Crew,'reset_knowledge') as mock_reset_agent_knowledge:
|
||||
crew = Crew(
|
||||
agents=[researcher, writer],
|
||||
process=Process.sequential,
|
||||
tasks=[
|
||||
Task(description="Task 1", expected_output="output", agent=researcher),
|
||||
Task(description="Task 2", expected_output="output", agent=writer),
|
||||
],
|
||||
knowledge=mock_ks
|
||||
)
|
||||
|
||||
crew.reset_memories(command_type='knowledge')
|
||||
mock_reset_agent_knowledge.assert_called_once_with([mock_ks])
|
||||
|
||||
|
||||
def test_reset_knowledge_with_crew_and_agent_knowledge(researcher,writer):
|
||||
mock_ks_crew = MagicMock(spec=Knowledge)
|
||||
mock_ks_research = MagicMock(spec=Knowledge)
|
||||
mock_ks_writer = MagicMock(spec=Knowledge)
|
||||
|
||||
researcher.knowledge = mock_ks_research
|
||||
writer.knowledge = mock_ks_writer
|
||||
|
||||
with patch.object(Crew,'reset_knowledge') as mock_reset_agent_knowledge:
|
||||
crew = Crew(
|
||||
agents=[researcher, writer],
|
||||
process=Process.sequential,
|
||||
tasks=[
|
||||
Task(description="Task 1", expected_output="output", agent=researcher),
|
||||
Task(description="Task 2", expected_output="output", agent=writer),
|
||||
],
|
||||
knowledge=mock_ks_crew
|
||||
)
|
||||
|
||||
crew.reset_memories(command_type='knowledge')
|
||||
mock_reset_agent_knowledge.assert_called_once_with([mock_ks_crew,mock_ks_research,mock_ks_writer])
|
||||
|
||||
|
||||
def test_reset_knowledge_with_only_agent_knowledge(researcher,writer):
|
||||
mock_ks_research = MagicMock(spec=Knowledge)
|
||||
mock_ks_writer = MagicMock(spec=Knowledge)
|
||||
|
||||
researcher.knowledge = mock_ks_research
|
||||
writer.knowledge = mock_ks_writer
|
||||
|
||||
with patch.object(Crew,'reset_knowledge') as mock_reset_agent_knowledge:
|
||||
crew = Crew(
|
||||
agents=[researcher, writer],
|
||||
process=Process.sequential,
|
||||
tasks=[
|
||||
Task(description="Task 1", expected_output="output", agent=researcher),
|
||||
Task(description="Task 2", expected_output="output", agent=writer),
|
||||
],
|
||||
)
|
||||
|
||||
crew.reset_memories(command_type='knowledge')
|
||||
mock_reset_agent_knowledge.assert_called_once_with([mock_ks_research,mock_ks_writer])
|
||||
|
||||
|
||||
def test_reset_agent_knowledge_with_no_agent_knowledge(researcher,writer):
|
||||
crew = Crew(
|
||||
agents=[researcher, writer],
|
||||
process=Process.sequential,
|
||||
tasks=[
|
||||
Task(description="Task 1", expected_output="output", agent=researcher),
|
||||
Task(description="Task 2", expected_output="output", agent=writer),
|
||||
],
|
||||
)
|
||||
|
||||
with pytest.raises(RuntimeError) as excinfo:
|
||||
crew.reset_memories(command_type='agent_knowledge')
|
||||
|
||||
# Optionally, you can also check the error message
|
||||
assert "Agent Knowledge memory system is not initialized" in str(excinfo.value) # Replace with the expected message
|
||||
|
||||
|
||||
def test_reset_agent_knowledge_with_only_crew_knowledge(researcher,writer):
|
||||
mock_ks = MagicMock(spec=Knowledge)
|
||||
|
||||
crew = Crew(
|
||||
agents=[researcher, writer],
|
||||
process=Process.sequential,
|
||||
tasks=[
|
||||
Task(description="Task 1", expected_output="output", agent=researcher),
|
||||
Task(description="Task 2", expected_output="output", agent=writer),
|
||||
],
|
||||
knowledge=mock_ks
|
||||
)
|
||||
|
||||
with pytest.raises(RuntimeError) as excinfo:
|
||||
crew.reset_memories(command_type='agent_knowledge')
|
||||
|
||||
# Optionally, you can also check the error message
|
||||
assert "Agent Knowledge memory system is not initialized" in str(excinfo.value) # Replace with the expected message
|
||||
|
||||
|
||||
def test_reset_agent_knowledge_with_crew_and_agent_knowledge(researcher,writer):
|
||||
mock_ks_crew = MagicMock(spec=Knowledge)
|
||||
mock_ks_research = MagicMock(spec=Knowledge)
|
||||
mock_ks_writer = MagicMock(spec=Knowledge)
|
||||
|
||||
researcher.knowledge = mock_ks_research
|
||||
writer.knowledge = mock_ks_writer
|
||||
|
||||
with patch.object(Crew,'reset_knowledge') as mock_reset_agent_knowledge:
|
||||
crew = Crew(
|
||||
agents=[researcher, writer],
|
||||
process=Process.sequential,
|
||||
tasks=[
|
||||
Task(description="Task 1", expected_output="output", agent=researcher),
|
||||
Task(description="Task 2", expected_output="output", agent=writer),
|
||||
],
|
||||
knowledge=mock_ks_crew
|
||||
)
|
||||
|
||||
crew.reset_memories(command_type='agent_knowledge')
|
||||
mock_reset_agent_knowledge.assert_called_once_with([mock_ks_research,mock_ks_writer])
|
||||
|
||||
|
||||
def test_reset_agent_knowledge_with_only_agent_knowledge(researcher,writer):
|
||||
mock_ks_research = MagicMock(spec=Knowledge)
|
||||
mock_ks_writer = MagicMock(spec=Knowledge)
|
||||
|
||||
researcher.knowledge = mock_ks_research
|
||||
writer.knowledge = mock_ks_writer
|
||||
|
||||
with patch.object(Crew,'reset_knowledge') as mock_reset_agent_knowledge:
|
||||
crew = Crew(
|
||||
agents=[researcher, writer],
|
||||
process=Process.sequential,
|
||||
tasks=[
|
||||
Task(description="Task 1", expected_output="output", agent=researcher),
|
||||
Task(description="Task 2", expected_output="output", agent=writer),
|
||||
],
|
||||
)
|
||||
|
||||
crew.reset_memories(command_type='agent_knowledge')
|
||||
mock_reset_agent_knowledge.assert_called_once_with([mock_ks_research,mock_ks_writer])
|
||||
|
||||
|
||||
|
||||
39
tests/test_python_compatibility.py
Normal file
@@ -0,0 +1,39 @@
|
||||
"""Tests for Python version compatibility."""
|
||||
import sys
|
||||
|
||||
import pytest
|
||||
from packaging import version
|
||||
|
||||
|
||||
def validate_python_version():
|
||||
"""Validate that the current Python version is supported."""
|
||||
min_version = (3, 10)
|
||||
max_version = (3, 14)
|
||||
current = sys.version_info[:2]
|
||||
|
||||
if not (min_version <= current < max_version):
|
||||
raise RuntimeError(
|
||||
f"This package requires Python {min_version[0]}.{min_version[1]} to "
|
||||
f"{max_version[0]}.{max_version[1]-1}. You have Python {current[0]}.{current[1]}"
|
||||
)
|
||||
|
||||
|
||||
def test_python_version_compatibility():
|
||||
"""Test that the package supports the current Python version."""
|
||||
assert isinstance(sys.version_info, tuple), "Version Information must be a tuple"
|
||||
|
||||
current_version = version.parse(f"{sys.version_info.major}.{sys.version_info.minor}")
|
||||
assert current_version >= version.parse("3.10"), "Python version too old"
|
||||
assert current_version < version.parse("3.14"), "Python version too new"
|
||||
|
||||
# This test will fail if the package doesn't support the current Python version
|
||||
import crewai
|
||||
|
||||
# Print the Python version for debugging
|
||||
print(f"Python version: {sys.version}")
|
||||
|
||||
# Print the crewai version for debugging
|
||||
print(f"CrewAI version: {crewai.__version__}")
|
||||
|
||||
# Validate Python version
|
||||
validate_python_version()
|
||||
@@ -1,80 +0,0 @@
|
||||
import pytest
|
||||
from unittest.mock import patch, MagicMock
|
||||
|
||||
from crewai.utilities.embedding_configurator import EmbeddingConfigurator
|
||||
|
||||
|
||||
class TestEmbeddingConfigurator:
|
||||
def test_configure_voyageai_embedder(self):
|
||||
"""Test that the VoyageAI embedder is configured correctly."""
|
||||
with patch(
|
||||
"crewai.utilities.embedding_configurator.VoyageAIEmbeddingFunction"
|
||||
) as mock_voyageai:
|
||||
mock_instance = MagicMock()
|
||||
mock_voyageai.return_value = mock_instance
|
||||
|
||||
config = {"api_key": "test-key"}
|
||||
model_name = "voyage-3"
|
||||
|
||||
configurator = EmbeddingConfigurator()
|
||||
embedder = configurator._configure_voyageai(config, model_name)
|
||||
|
||||
mock_voyageai.assert_called_once_with(
|
||||
model_name=model_name, api_key="test-key"
|
||||
)
|
||||
assert embedder == mock_instance
|
||||
|
||||
def test_configure_embedder_with_voyageai(self):
|
||||
"""Test that the embedder configurator correctly handles VoyageAI provider."""
|
||||
with patch(
|
||||
"crewai.utilities.embedding_configurator.VoyageAIEmbeddingFunction"
|
||||
) as mock_voyageai:
|
||||
mock_instance = MagicMock()
|
||||
mock_voyageai.return_value = mock_instance
|
||||
|
||||
embedder_config = {
|
||||
"provider": "voyageai",
|
||||
"config": {"api_key": "test-key", "model": "voyage-3"},
|
||||
}
|
||||
|
||||
configurator = EmbeddingConfigurator()
|
||||
embedder = configurator.configure_embedder(embedder_config)
|
||||
|
||||
mock_voyageai.assert_called_once_with(
|
||||
model_name="voyage-3", api_key="test-key"
|
||||
)
|
||||
assert embedder == mock_instance
|
||||
|
||||
def test_configure_voyageai_embedder_missing_api_key(self):
|
||||
"""Test that the VoyageAI embedder raises an error when API key is missing."""
|
||||
with patch(
|
||||
"crewai.utilities.embedding_configurator.VoyageAIEmbeddingFunction"
|
||||
) as mock_voyageai:
|
||||
mock_voyageai.side_effect = ValueError("API key is required for VoyageAI embeddings")
|
||||
|
||||
config = {} # Empty config without API key
|
||||
model_name = "voyage-3"
|
||||
|
||||
configurator = EmbeddingConfigurator()
|
||||
|
||||
with pytest.raises(ValueError, match="API key is required"):
|
||||
configurator._configure_voyageai(config, model_name)
|
||||
|
||||
def test_configure_voyageai_embedder_custom_model(self):
|
||||
"""Test that the VoyageAI embedder works with different model names."""
|
||||
with patch(
|
||||
"crewai.utilities.embedding_configurator.VoyageAIEmbeddingFunction"
|
||||
) as mock_voyageai:
|
||||
mock_instance = MagicMock()
|
||||
mock_voyageai.return_value = mock_instance
|
||||
|
||||
config = {"api_key": "test-key"}
|
||||
model_name = "voyage-3.5-lite" # Using a different model
|
||||
|
||||
configurator = EmbeddingConfigurator()
|
||||
embedder = configurator._configure_voyageai(config, model_name)
|
||||
|
||||
mock_voyageai.assert_called_once_with(
|
||||
model_name=model_name, api_key="test-key"
|
||||
)
|
||||
assert embedder == mock_instance
|
||||
2
uv.lock
generated
@@ -1,5 +1,5 @@
|
||||
version = 1
|
||||
requires-python = ">=3.10, <3.13"
|
||||
requires-python = ">=3.10, <3.14"
|
||||
resolution-markers = [
|
||||
"python_full_version < '3.11' and platform_python_implementation == 'PyPy' and platform_system == 'Darwin' and sys_platform == 'darwin'",
|
||||
"python_full_version < '3.11' and platform_machine == 'aarch64' and platform_python_implementation == 'PyPy' and platform_system == 'Linux' and sys_platform == 'darwin'",
|
||||
|
||||