Merge pull request #15 from greysonlalonde/gl/devops/ci-code-formatting-enhancements

Update Python to 3.9, Add Code Quality Tools, & Update Lockfile
This commit is contained in:
João Moura
2023-12-27 17:34:56 -03:00
committed by GitHub
16 changed files with 905 additions and 598 deletions

21
.pre-commit-config.yaml Normal file
View File

@@ -0,0 +1,21 @@
repos:
- repo: https://github.com/psf/black-pre-commit-mirror
rev: 23.12.1
hooks:
- id: black
language_version: python3.11
files: \.(py)$
- repo: https://github.com/pycqa/isort
rev: 5.13.2
hooks:
- id: isort
name: isort (python)
args: ["--profile", "black", "--filter-files"]
- repo: https://github.com/PyCQA/autoflake
rev: v2.2.1
hooks:
- id: autoflake
args: ['--in-place', '--remove-all-unused-imports', '--remove-unused-variables', '--ignore-init-module-imports']

View File

@@ -144,6 +144,12 @@ poetry install
poetry shell poetry shell
``` ```
### Pre-commit hooks
```bash
pre-commit install
```
### Running Tests ### Running Tests
```bash ```bash
poetry run pytest poetry run pytest

View File

@@ -1,4 +1,4 @@
from .task import Task
from .crew import Crew
from .agent import Agent from .agent import Agent
from .process import Process from .crew import Crew
from .process import Process
from .task import Task

View File

@@ -1,128 +1,120 @@
"""Generic agent.""" """Generic agent."""
from typing import List, Any, Optional from typing import Any, List, Optional
from pydantic.v1 import BaseModel, PrivateAttr, Field, root_validator
from langchain.agents import AgentExecutor from langchain.agents import AgentExecutor
from langchain.chat_models import ChatOpenAI
from langchain.tools.render import render_text_description
from langchain.agents.format_scratchpad import format_log_to_str from langchain.agents.format_scratchpad import format_log_to_str
from langchain.agents.output_parsers import ReActSingleInputOutputParser from langchain.agents.output_parsers import ReActSingleInputOutputParser
from langchain.chat_models import ChatOpenAI
from langchain.memory import ConversationSummaryMemory from langchain.memory import ConversationSummaryMemory
from langchain.tools.render import render_text_description
from pydantic.v1 import BaseModel, Field, PrivateAttr, root_validator
from .prompts import Prompts from .prompts import Prompts
class Agent(BaseModel): class Agent(BaseModel):
""" """
Represents an agent in a system. Represents an agent in a system.
Each agent has a role, a goal, a backstory, and an optional language model (llm). Each agent has a role, a goal, a backstory, and an optional language model (llm).
The agent can also have memory, can operate in verbose mode, and can delegate tasks to other agents. The agent can also have memory, can operate in verbose mode, and can delegate tasks to other agents.
Attributes: Attributes:
agent_executor: An instance of the AgentExecutor class. agent_executor: An instance of the AgentExecutor class.
role: The role of the agent. role: The role of the agent.
goal: The objective of the agent. goal: The objective of the agent.
backstory: The backstory of the agent. backstory: The backstory of the agent.
llm: The language model that will run the agent. llm: The language model that will run the agent.
memory: Whether the agent should have memory or not. memory: Whether the agent should have memory or not.
verbose: Whether the agent execution should be in verbose mode. verbose: Whether the agent execution should be in verbose mode.
allow_delegation: Whether the agent is allowed to delegate tasks to other agents. allow_delegation: Whether the agent is allowed to delegate tasks to other agents.
""" """
agent_executor: AgentExecutor = None
role: str = Field(description="Role of the agent")
goal: str = Field(description="Objective of the agent")
backstory: str = Field(description="Backstory of the agent")
llm: Optional[Any] = Field(description="LLM that will run the agent")
memory: bool = Field(
description="Whether the agent should have memory or not",
default=True
)
verbose: bool = Field(
description="Verbose mode for the Agent Execution",
default=False
)
allow_delegation: bool = Field(
description="Allow delegation of tasks to agents",
default=True
)
tools: List[Any] = Field(
description="Tools at agents disposal",
default=[]
)
_task_calls: List[Any] = PrivateAttr()
@root_validator(pre=True) agent_executor: AgentExecutor = None
def check_llm(_cls, values): role: str = Field(description="Role of the agent")
if not values.get('llm'): goal: str = Field(description="Objective of the agent")
values['llm'] = ChatOpenAI( backstory: str = Field(description="Backstory of the agent")
temperature=0.7, llm: Optional[Any] = Field(description="LLM that will run the agent")
model_name="gpt-4" memory: bool = Field(
) description="Whether the agent should have memory or not", default=True
return values )
verbose: bool = Field(
description="Verbose mode for the Agent Execution", default=False
)
allow_delegation: bool = Field(
description="Allow delegation of tasks to agents", default=True
)
tools: List[Any] = Field(description="Tools at agents disposal", default=[])
_task_calls: List[Any] = PrivateAttr()
def __init__(self, **data): @root_validator(pre=True)
super().__init__(**data) def check_llm(_cls, values):
agent_args = { if not values.get("llm"):
"input": lambda x: x["input"], values["llm"] = ChatOpenAI(temperature=0.7, model_name="gpt-4")
"tools": lambda x: x["tools"], return values
"tool_names": lambda x: x["tool_names"],
"agent_scratchpad": lambda x: format_log_to_str(x['intermediate_steps']),
}
executor_args = {
"tools": self.tools,
"verbose": self.verbose,
"handle_parsing_errors": True,
}
if self.memory: def __init__(self, **data):
summary_memory = ConversationSummaryMemory( super().__init__(**data)
llm=self.llm, agent_args = {
memory_key='chat_history', "input": lambda x: x["input"],
input_key="input" "tools": lambda x: x["tools"],
) "tool_names": lambda x: x["tool_names"],
executor_args['memory'] = summary_memory "agent_scratchpad": lambda x: format_log_to_str(x["intermediate_steps"]),
agent_args['chat_history'] = lambda x: x["chat_history"] }
prompt = Prompts.TASK_EXECUTION_WITH_MEMORY_PROMPT executor_args = {
else: "tools": self.tools,
prompt = Prompts.TASK_EXECUTION_PROMPT "verbose": self.verbose,
"handle_parsing_errors": True,
}
execution_prompt = prompt.partial( if self.memory:
goal=self.goal, summary_memory = ConversationSummaryMemory(
role=self.role, llm=self.llm, memory_key="chat_history", input_key="input"
backstory=self.backstory, )
) executor_args["memory"] = summary_memory
agent_args["chat_history"] = lambda x: x["chat_history"]
prompt = Prompts.TASK_EXECUTION_WITH_MEMORY_PROMPT
else:
prompt = Prompts.TASK_EXECUTION_PROMPT
bind = self.llm.bind(stop=["\nObservation"]) execution_prompt = prompt.partial(
inner_agent = agent_args | execution_prompt | bind | ReActSingleInputOutputParser() goal=self.goal,
role=self.role,
backstory=self.backstory,
)
self.agent_executor = AgentExecutor( bind = self.llm.bind(stop=["\nObservation"])
agent=inner_agent, inner_agent = (
**executor_args agent_args | execution_prompt | bind | ReActSingleInputOutputParser()
) )
def execute_task(self, task: str, context: str = None, tools: List[Any] = None) -> str: self.agent_executor = AgentExecutor(agent=inner_agent, **executor_args)
"""
Execute a task with the agent.
Parameters:
task (str): Task to execute
Returns:
output (str): Output of the agent
"""
if context:
task = "\n".join([
task,
"\nThis is the context you are working with:",
context
])
tools = tools or self.tools def execute_task(
self.agent_executor.tools = tools self, task: str, context: str = None, tools: List[Any] = None
return self.agent_executor.invoke({ ) -> str:
"input": task, """
"tool_names": self.__tools_names(tools), Execute a task with the agent.
"tools": render_text_description(tools), Parameters:
})['output'] task (str): Task to execute
Returns:
output (str): Output of the agent
"""
if context:
task = "\n".join(
[task, "\nThis is the context you are working with:", context]
)
def __tools_names(self, tools) -> str: tools = tools or self.tools
return ", ".join([t.name for t in tools]) self.agent_executor.tools = tools
return self.agent_executor.invoke(
{
"input": task,
"tool_names": self.__tools_names(tools),
"tools": render_text_description(tools),
}
)["output"]
def __tools_names(self, tools) -> str:
return ", ".join([t.name for t in tools])

View File

@@ -1,89 +1,88 @@
import json import json
from typing import List, Optional from typing import List, Optional
from pydantic.v1 import BaseModel, Field, Json, root_validator from pydantic.v1 import BaseModel, Field, Json, root_validator
from .process import Process
from .agent import Agent from .agent import Agent
from .process import Process
from .task import Task from .task import Task
from .tools.agent_tools import AgentTools from .tools.agent_tools import AgentTools
class Crew(BaseModel): class Crew(BaseModel):
""" """
Class that represents a group of agents, how they should work together and Class that represents a group of agents, how they should work together and
their tasks. their tasks.
""" """
tasks: Optional[List[Task]] = Field(description="List of tasks")
agents: Optional[List[Agent]] = Field(description="List of agents in this crew.")
process: Process = Field(
description="Process that the crew will follow.",
default=Process.sequential
)
verbose: bool = Field(
description="Verbose mode for the Agent Execution",
default=False
)
config: Optional[Json] = Field(
description="Configuration of the crew.",
default=None
)
@root_validator(pre=True) tasks: Optional[List[Task]] = Field(description="List of tasks")
def check_config(_cls, values): agents: Optional[List[Agent]] = Field(description="List of agents in this crew.")
if ( process: Process = Field(
not values.get('config') description="Process that the crew will follow.", default=Process.sequential
and ( )
not values.get('agents') and not values.get('tasks') verbose: bool = Field(
) description="Verbose mode for the Agent Execution", default=False
): )
raise ValueError('Either agents and task need to be set or config.') config: Optional[Json] = Field(
description="Configuration of the crew.", default=None
if values.get('config'): )
config = json.loads(values.get('config'))
if not config.get('agents') or not config.get('tasks'):
raise ValueError('Config should have agents and tasks.')
values['agents'] = [Agent(**agent) for agent in config['agents']]
tasks = []
for task in config['tasks']:
task_agent = [agt for agt in values['agents'] if agt.role == task['agent']][0]
del task['agent']
tasks.append(Task(**task, agent=task_agent))
values['tasks'] = tasks
return values
def kickoff(self) -> str: @root_validator(pre=True)
""" def check_config(_cls, values):
Kickoff the crew to work on it's tasks. if not values.get("config") and (
Returns: not values.get("agents") and not values.get("tasks")
output (List[str]): Output of the crew for each task. ):
""" raise ValueError("Either agents and task need to be set or config.")
if self.process == Process.sequential:
return self.__sequential_loop()
def __sequential_loop(self) -> str: if values.get("config"):
""" config = json.loads(values.get("config"))
Loop that executes the sequential process. if not config.get("agents") or not config.get("tasks"):
Returns: raise ValueError("Config should have agents and tasks.")
output (str): Output of the crew.
"""
task_outcome = None
for task in self.tasks:
# Add delegation tools to the task if the agent allows it
if task.agent.allow_delegation:
tools = AgentTools(agents=self.agents).tools()
task.tools += tools
self.__log(f"\nWorking Agent: {task.agent.role}") values["agents"] = [Agent(**agent) for agent in config["agents"]]
self.__log(f"Starting Task: {task.description} ...")
task_outcome = task.execute(task_outcome) tasks = []
for task in config["tasks"]:
task_agent = [
agt for agt in values["agents"] if agt.role == task["agent"]
][0]
del task["agent"]
tasks.append(Task(**task, agent=task_agent))
self.__log(f"Task output: {task_outcome}") values["tasks"] = tasks
return values
return task_outcome
def kickoff(self) -> str:
def __log(self, message): """
if self.verbose: Kickoff the crew to work on it's tasks.
print(message) Returns:
output (List[str]): Output of the crew for each task.
"""
if self.process == Process.sequential:
return self.__sequential_loop()
def __sequential_loop(self) -> str:
"""
Loop that executes the sequential process.
Returns:
output (str): Output of the crew.
"""
task_outcome = None
for task in self.tasks:
# Add delegation tools to the task if the agent allows it
if task.agent.allow_delegation:
tools = AgentTools(agents=self.agents).tools()
task.tools += tools
self.__log(f"\nWorking Agent: {task.agent.role}")
self.__log(f"Starting Task: {task.description} ...")
task_outcome = task.execute(task_outcome)
self.__log(f"Task output: {task_outcome}")
return task_outcome
def __log(self, message):
if self.verbose:
print(message)

View File

@@ -1,9 +1,11 @@
from enum import Enum from enum import Enum
class Process(str, Enum): class Process(str, Enum):
""" """
Class representing the different processes that can be used to tackle tasks Class representing the different processes that can be used to tackle tasks
""" """
sequential = 'sequential'
# TODO: consensual = 'consensual' sequential = "sequential"
# TODO: hierarchical = 'hierarchical' # TODO: consensual = 'consensual'
# TODO: hierarchical = 'hierarchical'

View File

@@ -2,32 +2,41 @@
from textwrap import dedent from textwrap import dedent
from typing import ClassVar from typing import ClassVar
from pydantic.v1 import BaseModel
from langchain.prompts import PromptTemplate from langchain.prompts import PromptTemplate
from pydantic.v1 import BaseModel
class Prompts(BaseModel): class Prompts(BaseModel):
"""Prompts for generic agent.""" """Prompts for generic agent."""
TASK_SLICE: ClassVar[str] = dedent("""\ TASK_SLICE: ClassVar[str] = dedent(
"""\
Begin! This is VERY important to you, your job depends on it! Begin! This is VERY important to you, your job depends on it!
Current Task: {input} Current Task: {input}
{agent_scratchpad} {agent_scratchpad}
""") """
)
MEMORY_SLICE: ClassVar[str] = dedent("""\ MEMORY_SLICE: ClassVar[str] = dedent(
"""\
This is the summary of your work so far: This is the summary of your work so far:
{chat_history} {chat_history}
""") """
)
ROLE_PLAYING_SLICE: ClassVar[str] = dedent("""\ ROLE_PLAYING_SLICE: ClassVar[str] = dedent(
"""\
You are {role}. You are {role}.
{backstory} {backstory}
Your personal goal is: {goal} Your personal goal is: {goal}
""") """
)
TOOLS_SLICE: ClassVar[str] = dedent("""\ TOOLS_SLICE: ClassVar[str] = dedent(
"""\
TOOLS: TOOLS:
------ ------
@@ -50,9 +59,11 @@ class Prompts(BaseModel):
Thought: Do I need to use a tool? No Thought: Do I need to use a tool? No
Final Answer: [your response here] Final Answer: [your response here]
``` ```
""") """
)
VOTING_SLICE: ClassVar[str] = dedent("""\ VOTING_SLICE: ClassVar[str] = dedent(
"""\
You are working on a crew with your co-workers and need to decide who will execute the task. You are working on a crew with your co-workers and need to decide who will execute the task.
These are your format instructions: These are your format instructions:
@@ -60,16 +71,17 @@ class Prompts(BaseModel):
These are your co-workers and their roles: These are your co-workers and their roles:
{coworkers} {coworkers}
""") """
)
TASK_EXECUTION_WITH_MEMORY_PROMPT: ClassVar[str] = PromptTemplate.from_template( TASK_EXECUTION_WITH_MEMORY_PROMPT: ClassVar[str] = PromptTemplate.from_template(
ROLE_PLAYING_SLICE + TOOLS_SLICE + MEMORY_SLICE + TASK_SLICE ROLE_PLAYING_SLICE + TOOLS_SLICE + MEMORY_SLICE + TASK_SLICE
) )
TASK_EXECUTION_PROMPT: ClassVar[str] = PromptTemplate.from_template( TASK_EXECUTION_PROMPT: ClassVar[str] = PromptTemplate.from_template(
ROLE_PLAYING_SLICE + TOOLS_SLICE + TASK_SLICE ROLE_PLAYING_SLICE + TOOLS_SLICE + TASK_SLICE
) )
CONSENSUNS_VOTING_PROMPT: ClassVar[str] = PromptTemplate.from_template( CONSENSUNS_VOTING_PROMPT: ClassVar[str] = PromptTemplate.from_template(
ROLE_PLAYING_SLICE + VOTING_SLICE + TASK_SLICE ROLE_PLAYING_SLICE + VOTING_SLICE + TASK_SLICE
) )

View File

@@ -1,42 +1,41 @@
from typing import List, Optional from typing import List, Optional
from pydantic.v1 import BaseModel, Field, root_validator
from langchain.tools import Tool from langchain.tools import Tool
from pydantic.v1 import BaseModel, Field, root_validator
from .agent import Agent from .agent import Agent
class Task(BaseModel): class Task(BaseModel):
""" """
Class that represent a task to be executed. Class that represent a task to be executed.
""" """
description: str = Field(description="Description of the actual task.")
agent: Optional[Agent] = Field(
description="Agent responsible for the task.",
default=None
)
tools: Optional[List[Tool]] = Field(
description="Tools the agent are limited to use for this task.",
default=[]
)
@root_validator(pre=False) description: str = Field(description="Description of the actual task.")
def _set_tools(_cls, values): agent: Optional[Agent] = Field(
if (values.get('agent')) and not (values.get('tools')): description="Agent responsible for the task.", default=None
values['tools'] = values.get('agent').tools )
return values tools: Optional[List[Tool]] = Field(
description="Tools the agent are limited to use for this task.", default=[]
)
def execute(self, context: str = None) -> str: @root_validator(pre=False)
""" def _set_tools(_cls, values):
Execute the task. if (values.get("agent")) and not (values.get("tools")):
Returns: values["tools"] = values.get("agent").tools
output (str): Output of the task. return values
"""
if self.agent: def execute(self, context: str = None) -> str:
return self.agent.execute_task( """
task = self.description, Execute the task.
context = context, Returns:
tools = self.tools output (str): Output of the task.
) """
else: if self.agent:
raise Exception(f"The task '{self.description}' has no agent assigned, therefore it can't be executed directly and should be executed in a Crew using a specific process that support that, either consensual or hierarchical.") return self.agent.execute_task(
task=self.description, context=context, tools=self.tools
)
else:
raise Exception(
f"The task '{self.description}' has no agent assigned, therefore it can't be executed directly and should be executed in a Crew using a specific process that support that, either consensual or hierarchical."
)

0
crewai/tools/__init__.py Normal file
View File

View File

@@ -1,61 +1,74 @@
from typing import List, Any
from pydantic.v1 import BaseModel, Field
from textwrap import dedent from textwrap import dedent
from typing import List
from langchain.tools import Tool from langchain.tools import Tool
from pydantic.v1 import BaseModel, Field
from ..agent import Agent from ..agent import Agent
class AgentTools(BaseModel):
"""Tools for generic agent."""
agents: List[Agent] = Field(description="List of agents in this crew.")
def tools(self): class AgentTools(BaseModel):
return [ """Tools for generic agent."""
Tool.from_function(
func=self.delegate_work, agents: List[Agent] = Field(description="List of agents in this crew.")
name="Delegate Work to Co-Worker",
description=dedent(f"""Useful to delegate a specific task to one of the def tools(self):
return [
Tool.from_function(
func=self.delegate_work,
name="Delegate Work to Co-Worker",
description=dedent(
f"""Useful to delegate a specific task to one of the
following co-workers: [{', '.join([agent.role for agent in self.agents])}]. following co-workers: [{', '.join([agent.role for agent in self.agents])}].
The input to this tool should be a pipe (|) separated text of length The input to this tool should be a pipe (|) separated text of length
three, representing the role you want to delegate it to, the task and three, representing the role you want to delegate it to, the task and
information necessary. For example, `coworker|task|information`. information necessary. For example, `coworker|task|information`.
""") """
), ),
Tool.from_function( ),
func=self.ask_question, Tool.from_function(
name="Ask Question to Co-Worker", func=self.ask_question,
description=dedent(f"""Useful to ask a question, opinion or take from on name="Ask Question to Co-Worker",
description=dedent(
f"""Useful to ask a question, opinion or take from on
of the following co-workers: [{', '.join([agent.role for agent in self.agents])}]. of the following co-workers: [{', '.join([agent.role for agent in self.agents])}].
The input to this tool should be a pipe (|) separated text of length The input to this tool should be a pipe (|) separated text of length
three, representing the role you want to ask it to, the question and three, representing the role you want to ask it to, the question and
information necessary. For example, `coworker|question|information`. information necessary. For example, `coworker|question|information`.
""") """
), ),
] ),
]
def delegate_work(self, command): def delegate_work(self, command):
"""Useful to delegate a specific task to a coworker.""" """Useful to delegate a specific task to a coworker."""
return self.__execute(command) return self.__execute(command)
def ask_question(self, command): def ask_question(self, command):
"""Useful to ask a question, opinion or take from a coworker.""" """Useful to ask a question, opinion or take from a coworker."""
return self.__execute(command) return self.__execute(command)
def __execute(self, command): def __execute(self, command):
"""Execute the command.""" """Execute the command."""
try: try:
agent, task, information = command.split("|") agent, task, information = command.split("|")
except ValueError: except ValueError:
return "Error executing tool. Missing exact 3 pipe (|) separated values. For example, `coworker|task|information`." return "Error executing tool. Missing exact 3 pipe (|) separated values. For example, `coworker|task|information`."
if not agent or not task or not information: if not agent or not task or not information:
return "Error executing tool. Missing exact 3 pipe (|) separated values. For example, `coworker|question|information`." return "Error executing tool. Missing exact 3 pipe (|) separated values. For example, `coworker|question|information`."
agent = [available_agent for available_agent in self.agents if available_agent.role == agent] agent = [
available_agent
for available_agent in self.agents
if available_agent.role == agent
]
if len(agent) == 0: if len(agent) == 0:
return "Error executing tool. Co-worker not found, double check the co-worker." return (
"Error executing tool. Co-worker not found, double check the co-worker."
)
agent = agent[0] agent = agent[0]
result = agent.execute_task(task, information) result = agent.execute_task(task, information)
return result return result

321
poetry.lock generated
View File

@@ -121,9 +121,6 @@ files = [
{file = "annotated_types-0.6.0.tar.gz", hash = "sha256:563339e807e53ffd9c267e99fc6d9ea23eb8443c08f112651963e24e22f84a5d"}, {file = "annotated_types-0.6.0.tar.gz", hash = "sha256:563339e807e53ffd9c267e99fc6d9ea23eb8443c08f112651963e24e22f84a5d"},
] ]
[package.dependencies]
typing-extensions = {version = ">=4.0.0", markers = "python_version < \"3.9\""}
[[package]] [[package]]
name = "anyio" name = "anyio"
version = "4.2.0" version = "4.2.0"
@@ -175,6 +172,67 @@ docs = ["furo", "myst-parser", "sphinx", "sphinx-notfound-page", "sphinxcontrib-
tests = ["attrs[tests-no-zope]", "zope-interface"] tests = ["attrs[tests-no-zope]", "zope-interface"]
tests-no-zope = ["cloudpickle", "hypothesis", "mypy (>=1.1.1)", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins", "pytest-xdist[psutil]"] tests-no-zope = ["cloudpickle", "hypothesis", "mypy (>=1.1.1)", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins", "pytest-xdist[psutil]"]
[[package]]
name = "autoflake"
version = "2.2.1"
description = "Removes unused imports and unused variables"
optional = false
python-versions = ">=3.8"
files = [
{file = "autoflake-2.2.1-py3-none-any.whl", hash = "sha256:265cde0a43c1f44ecfb4f30d95b0437796759d07be7706a2f70e4719234c0f79"},
{file = "autoflake-2.2.1.tar.gz", hash = "sha256:62b7b6449a692c3c9b0c916919bbc21648da7281e8506bcf8d3f8280e431ebc1"},
]
[package.dependencies]
pyflakes = ">=3.0.0"
tomli = {version = ">=2.0.1", markers = "python_version < \"3.11\""}
[[package]]
name = "black"
version = "23.12.1"
description = "The uncompromising code formatter."
optional = false
python-versions = ">=3.8"
files = [
{file = "black-23.12.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:e0aaf6041986767a5e0ce663c7a2f0e9eaf21e6ff87a5f95cbf3675bfd4c41d2"},
{file = "black-23.12.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:c88b3711d12905b74206227109272673edce0cb29f27e1385f33b0163c414bba"},
{file = "black-23.12.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a920b569dc6b3472513ba6ddea21f440d4b4c699494d2e972a1753cdc25df7b0"},
{file = "black-23.12.1-cp310-cp310-win_amd64.whl", hash = "sha256:3fa4be75ef2a6b96ea8d92b1587dd8cb3a35c7e3d51f0738ced0781c3aa3a5a3"},
{file = "black-23.12.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:8d4df77958a622f9b5a4c96edb4b8c0034f8434032ab11077ec6c56ae9f384ba"},
{file = "black-23.12.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:602cfb1196dc692424c70b6507593a2b29aac0547c1be9a1d1365f0d964c353b"},
{file = "black-23.12.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9c4352800f14be5b4864016882cdba10755bd50805c95f728011bcb47a4afd59"},
{file = "black-23.12.1-cp311-cp311-win_amd64.whl", hash = "sha256:0808494f2b2df923ffc5723ed3c7b096bd76341f6213989759287611e9837d50"},
{file = "black-23.12.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:25e57fd232a6d6ff3f4478a6fd0580838e47c93c83eaf1ccc92d4faf27112c4e"},
{file = "black-23.12.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:2d9e13db441c509a3763a7a3d9a49ccc1b4e974a47be4e08ade2a228876500ec"},
{file = "black-23.12.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6d1bd9c210f8b109b1762ec9fd36592fdd528485aadb3f5849b2740ef17e674e"},
{file = "black-23.12.1-cp312-cp312-win_amd64.whl", hash = "sha256:ae76c22bde5cbb6bfd211ec343ded2163bba7883c7bc77f6b756a1049436fbb9"},
{file = "black-23.12.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:1fa88a0f74e50e4487477bc0bb900c6781dbddfdfa32691e780bf854c3b4a47f"},
{file = "black-23.12.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:a4d6a9668e45ad99d2f8ec70d5c8c04ef4f32f648ef39048d010b0689832ec6d"},
{file = "black-23.12.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b18fb2ae6c4bb63eebe5be6bd869ba2f14fd0259bda7d18a46b764d8fb86298a"},
{file = "black-23.12.1-cp38-cp38-win_amd64.whl", hash = "sha256:c04b6d9d20e9c13f43eee8ea87d44156b8505ca8a3c878773f68b4e4812a421e"},
{file = "black-23.12.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:3e1b38b3135fd4c025c28c55ddfc236b05af657828a8a6abe5deec419a0b7055"},
{file = "black-23.12.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:4f0031eaa7b921db76decd73636ef3a12c942ed367d8c3841a0739412b260a54"},
{file = "black-23.12.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:97e56155c6b737854e60a9ab1c598ff2533d57e7506d97af5481141671abf3ea"},
{file = "black-23.12.1-cp39-cp39-win_amd64.whl", hash = "sha256:dd15245c8b68fe2b6bd0f32c1556509d11bb33aec9b5d0866dd8e2ed3dba09c2"},
{file = "black-23.12.1-py3-none-any.whl", hash = "sha256:78baad24af0f033958cad29731e27363183e140962595def56423e626f4bee3e"},
{file = "black-23.12.1.tar.gz", hash = "sha256:4ce3ef14ebe8d9509188014d96af1c456a910d5b5cbf434a09fef7e024b3d0d5"},
]
[package.dependencies]
click = ">=8.0.0"
mypy-extensions = ">=0.4.3"
packaging = ">=22.0"
pathspec = ">=0.9.0"
platformdirs = ">=2"
tomli = {version = ">=1.1.0", markers = "python_version < \"3.11\""}
typing-extensions = {version = ">=4.0.1", markers = "python_version < \"3.11\""}
[package.extras]
colorama = ["colorama (>=0.4.3)"]
d = ["aiohttp (>=3.7.4)", "aiohttp (>=3.7.4,!=3.9.0)"]
jupyter = ["ipython (>=7.8.0)", "tokenize-rt (>=3.2.0)"]
uvloop = ["uvloop (>=0.15.2)"]
[[package]] [[package]]
name = "certifi" name = "certifi"
version = "2023.11.17" version = "2023.11.17"
@@ -186,6 +244,17 @@ files = [
{file = "certifi-2023.11.17.tar.gz", hash = "sha256:9b469f3a900bf28dc19b8cfbf8019bf47f7fdd1a65a1d4ffb98fc14166beb4d1"}, {file = "certifi-2023.11.17.tar.gz", hash = "sha256:9b469f3a900bf28dc19b8cfbf8019bf47f7fdd1a65a1d4ffb98fc14166beb4d1"},
] ]
[[package]]
name = "cfgv"
version = "3.4.0"
description = "Validate configuration and produce human readable error messages."
optional = false
python-versions = ">=3.8"
files = [
{file = "cfgv-3.4.0-py2.py3-none-any.whl", hash = "sha256:b7265b1f29fd3316bfcd2b330d63d024f2bfd8bcb8b0272f8e19a504856c48f9"},
{file = "cfgv-3.4.0.tar.gz", hash = "sha256:e52591d4c5f5dead8e0f673fb16db7949d2cfb3f7da4582893288f0ded8fe560"},
]
[[package]] [[package]]
name = "charset-normalizer" name = "charset-normalizer"
version = "3.3.2" version = "3.3.2"
@@ -285,6 +354,20 @@ files = [
{file = "charset_normalizer-3.3.2-py3-none-any.whl", hash = "sha256:3e4d1f6587322d2788836a99c69062fbb091331ec940e02d12d179c1d53e25fc"}, {file = "charset_normalizer-3.3.2-py3-none-any.whl", hash = "sha256:3e4d1f6587322d2788836a99c69062fbb091331ec940e02d12d179c1d53e25fc"},
] ]
[[package]]
name = "click"
version = "8.1.7"
description = "Composable command line interface toolkit"
optional = false
python-versions = ">=3.7"
files = [
{file = "click-8.1.7-py3-none-any.whl", hash = "sha256:ae74fb96c20a0277a1d615f1e4d73c8414f5a98db8b799a7931d1582f3390c28"},
{file = "click-8.1.7.tar.gz", hash = "sha256:ca9853ad459e787e2192211578cc907e7594e294c7ccc834310722b41b9ca6de"},
]
[package.dependencies]
colorama = {version = "*", markers = "platform_system == \"Windows\""}
[[package]] [[package]]
name = "colorama" name = "colorama"
version = "0.4.6" version = "0.4.6"
@@ -311,6 +394,17 @@ files = [
marshmallow = ">=3.18.0,<4.0.0" marshmallow = ">=3.18.0,<4.0.0"
typing-inspect = ">=0.4.0,<1" typing-inspect = ">=0.4.0,<1"
[[package]]
name = "distlib"
version = "0.3.8"
description = "Distribution utilities"
optional = false
python-versions = "*"
files = [
{file = "distlib-0.3.8-py2.py3-none-any.whl", hash = "sha256:034db59a0b96f8ca18035f36290806a9a6e6bd9d1ff91e45a7f172eb17e51784"},
{file = "distlib-0.3.8.tar.gz", hash = "sha256:1530ea13e350031b6312d8580ddb6b27a104275a31106523b8f123787f494f64"},
]
[[package]] [[package]]
name = "distro" name = "distro"
version = "1.9.0" version = "1.9.0"
@@ -336,6 +430,22 @@ files = [
[package.extras] [package.extras]
test = ["pytest (>=6)"] test = ["pytest (>=6)"]
[[package]]
name = "filelock"
version = "3.13.1"
description = "A platform independent file lock."
optional = false
python-versions = ">=3.8"
files = [
{file = "filelock-3.13.1-py3-none-any.whl", hash = "sha256:57dbda9b35157b05fb3e58ee91448612eb674172fab98ee235ccb0b5bee19a1c"},
{file = "filelock-3.13.1.tar.gz", hash = "sha256:521f5f56c50f8426f5e03ad3b281b490a87ef15bc6c526f168290f0c7148d44e"},
]
[package.extras]
docs = ["furo (>=2023.9.10)", "sphinx (>=7.2.6)", "sphinx-autodoc-typehints (>=1.24)"]
testing = ["covdefaults (>=2.3)", "coverage (>=7.3.2)", "diff-cover (>=8)", "pytest (>=7.4.3)", "pytest-cov (>=4.1)", "pytest-mock (>=3.12)", "pytest-timeout (>=2.2)"]
typing = ["typing-extensions (>=4.8)"]
[[package]] [[package]]
name = "frozenlist" name = "frozenlist"
version = "1.4.1" version = "1.4.1"
@@ -549,6 +659,20 @@ cli = ["click (==8.*)", "pygments (==2.*)", "rich (>=10,<14)"]
http2 = ["h2 (>=3,<5)"] http2 = ["h2 (>=3,<5)"]
socks = ["socksio (==1.*)"] socks = ["socksio (==1.*)"]
[[package]]
name = "identify"
version = "2.5.33"
description = "File identification library for Python"
optional = false
python-versions = ">=3.8"
files = [
{file = "identify-2.5.33-py2.py3-none-any.whl", hash = "sha256:d40ce5fcd762817627670da8a7d8d8e65f24342d14539c59488dc603bf662e34"},
{file = "identify-2.5.33.tar.gz", hash = "sha256:161558f9fe4559e1557e1bff323e8631f6a0e4837f7497767c1782832f16b62d"},
]
[package.extras]
license = ["ukkonen"]
[[package]] [[package]]
name = "idna" name = "idna"
version = "3.6" version = "3.6"
@@ -571,6 +695,20 @@ files = [
{file = "iniconfig-2.0.0.tar.gz", hash = "sha256:2d91e135bf72d31a410b17c16da610a82cb55f6b0477d1a902134b24a455b8b3"}, {file = "iniconfig-2.0.0.tar.gz", hash = "sha256:2d91e135bf72d31a410b17c16da610a82cb55f6b0477d1a902134b24a455b8b3"},
] ]
[[package]]
name = "isort"
version = "5.13.2"
description = "A Python utility / library to sort Python imports."
optional = false
python-versions = ">=3.8.0"
files = [
{file = "isort-5.13.2-py3-none-any.whl", hash = "sha256:8ca5e72a8d85860d5a3fa69b8745237f2939afe12dbf656afbcb47fe72d947a6"},
{file = "isort-5.13.2.tar.gz", hash = "sha256:48fdfcb9face5d58a4f6dde2e72a1fb8dcaf8ab26f95ab49fab84c2ddefb0109"},
]
[package.extras]
colors = ["colorama (>=0.4.6)"]
[[package]] [[package]]
name = "jsonpatch" name = "jsonpatch"
version = "1.33" version = "1.33"
@@ -815,41 +953,63 @@ files = [
{file = "mypy_extensions-1.0.0.tar.gz", hash = "sha256:75dbf8955dc00442a438fc4d0666508a9a97b6bd41aa2f0ffe9d2f2725af0782"}, {file = "mypy_extensions-1.0.0.tar.gz", hash = "sha256:75dbf8955dc00442a438fc4d0666508a9a97b6bd41aa2f0ffe9d2f2725af0782"},
] ]
[[package]]
name = "nodeenv"
version = "1.8.0"
description = "Node.js virtual environment builder"
optional = false
python-versions = ">=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*"
files = [
{file = "nodeenv-1.8.0-py2.py3-none-any.whl", hash = "sha256:df865724bb3c3adc86b3876fa209771517b0cfe596beff01a92700e0e8be4cec"},
{file = "nodeenv-1.8.0.tar.gz", hash = "sha256:d51e0c37e64fbf47d017feac3145cdbb58836d7eee8c6f6d3b6880c5456227d2"},
]
[package.dependencies]
setuptools = "*"
[[package]] [[package]]
name = "numpy" name = "numpy"
version = "1.24.4" version = "1.26.2"
description = "Fundamental package for array computing in Python" description = "Fundamental package for array computing in Python"
optional = false optional = false
python-versions = ">=3.8" python-versions = ">=3.9"
files = [ files = [
{file = "numpy-1.24.4-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:c0bfb52d2169d58c1cdb8cc1f16989101639b34c7d3ce60ed70b19c63eba0b64"}, {file = "numpy-1.26.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:3703fc9258a4a122d17043e57b35e5ef1c5a5837c3db8be396c82e04c1cf9b0f"},
{file = "numpy-1.24.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:ed094d4f0c177b1b8e7aa9cba7d6ceed51c0e569a5318ac0ca9a090680a6a1b1"}, {file = "numpy-1.26.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:cc392fdcbd21d4be6ae1bb4475a03ce3b025cd49a9be5345d76d7585aea69440"},
{file = "numpy-1.24.4-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:79fc682a374c4a8ed08b331bef9c5f582585d1048fa6d80bc6c35bc384eee9b4"}, {file = "numpy-1.26.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:36340109af8da8805d8851ef1d74761b3b88e81a9bd80b290bbfed61bd2b4f75"},
{file = "numpy-1.24.4-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7ffe43c74893dbf38c2b0a1f5428760a1a9c98285553c89e12d70a96a7f3a4d6"}, {file = "numpy-1.26.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bcc008217145b3d77abd3e4d5ef586e3bdfba8fe17940769f8aa09b99e856c00"},
{file = "numpy-1.24.4-cp310-cp310-win32.whl", hash = "sha256:4c21decb6ea94057331e111a5bed9a79d335658c27ce2adb580fb4d54f2ad9bc"}, {file = "numpy-1.26.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:3ced40d4e9e18242f70dd02d739e44698df3dcb010d31f495ff00a31ef6014fe"},
{file = "numpy-1.24.4-cp310-cp310-win_amd64.whl", hash = "sha256:b4bea75e47d9586d31e892a7401f76e909712a0fd510f58f5337bea9572c571e"}, {file = "numpy-1.26.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:b272d4cecc32c9e19911891446b72e986157e6a1809b7b56518b4f3755267523"},
{file = "numpy-1.24.4-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:f136bab9c2cfd8da131132c2cf6cc27331dd6fae65f95f69dcd4ae3c3639c810"}, {file = "numpy-1.26.2-cp310-cp310-win32.whl", hash = "sha256:22f8fc02fdbc829e7a8c578dd8d2e15a9074b630d4da29cda483337e300e3ee9"},
{file = "numpy-1.24.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:e2926dac25b313635e4d6cf4dc4e51c8c0ebfed60b801c799ffc4c32bf3d1254"}, {file = "numpy-1.26.2-cp310-cp310-win_amd64.whl", hash = "sha256:26c9d33f8e8b846d5a65dd068c14e04018d05533b348d9eaeef6c1bd787f9919"},
{file = "numpy-1.24.4-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:222e40d0e2548690405b0b3c7b21d1169117391c2e82c378467ef9ab4c8f0da7"}, {file = "numpy-1.26.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:b96e7b9c624ef3ae2ae0e04fa9b460f6b9f17ad8b4bec6d7756510f1f6c0c841"},
{file = "numpy-1.24.4-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7215847ce88a85ce39baf9e89070cb860c98fdddacbaa6c0da3ffb31b3350bd5"}, {file = "numpy-1.26.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:aa18428111fb9a591d7a9cc1b48150097ba6a7e8299fb56bdf574df650e7d1f1"},
{file = "numpy-1.24.4-cp311-cp311-win32.whl", hash = "sha256:4979217d7de511a8d57f4b4b5b2b965f707768440c17cb70fbf254c4b225238d"}, {file = "numpy-1.26.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:06fa1ed84aa60ea6ef9f91ba57b5ed963c3729534e6e54055fc151fad0423f0a"},
{file = "numpy-1.24.4-cp311-cp311-win_amd64.whl", hash = "sha256:b7b1fc9864d7d39e28f41d089bfd6353cb5f27ecd9905348c24187a768c79694"}, {file = "numpy-1.26.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:96ca5482c3dbdd051bcd1fce8034603d6ebfc125a7bd59f55b40d8f5d246832b"},
{file = "numpy-1.24.4-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:1452241c290f3e2a312c137a9999cdbf63f78864d63c79039bda65ee86943f61"}, {file = "numpy-1.26.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:854ab91a2906ef29dc3925a064fcd365c7b4da743f84b123002f6139bcb3f8a7"},
{file = "numpy-1.24.4-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:04640dab83f7c6c85abf9cd729c5b65f1ebd0ccf9de90b270cd61935eef0197f"}, {file = "numpy-1.26.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:f43740ab089277d403aa07567be138fc2a89d4d9892d113b76153e0e412409f8"},
{file = "numpy-1.24.4-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a5425b114831d1e77e4b5d812b69d11d962e104095a5b9c3b641a218abcc050e"}, {file = "numpy-1.26.2-cp311-cp311-win32.whl", hash = "sha256:a2bbc29fcb1771cd7b7425f98b05307776a6baf43035d3b80c4b0f29e9545186"},
{file = "numpy-1.24.4-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dd80e219fd4c71fc3699fc1dadac5dcf4fd882bfc6f7ec53d30fa197b8ee22dc"}, {file = "numpy-1.26.2-cp311-cp311-win_amd64.whl", hash = "sha256:2b3fca8a5b00184828d12b073af4d0fc5fdd94b1632c2477526f6bd7842d700d"},
{file = "numpy-1.24.4-cp38-cp38-win32.whl", hash = "sha256:4602244f345453db537be5314d3983dbf5834a9701b7723ec28923e2889e0bb2"}, {file = "numpy-1.26.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:a4cd6ed4a339c21f1d1b0fdf13426cb3b284555c27ac2f156dfdaaa7e16bfab0"},
{file = "numpy-1.24.4-cp38-cp38-win_amd64.whl", hash = "sha256:692f2e0f55794943c5bfff12b3f56f99af76f902fc47487bdfe97856de51a706"}, {file = "numpy-1.26.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:5d5244aabd6ed7f312268b9247be47343a654ebea52a60f002dc70c769048e75"},
{file = "numpy-1.24.4-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:2541312fbf09977f3b3ad449c4e5f4bb55d0dbf79226d7724211acc905049400"}, {file = "numpy-1.26.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6a3cdb4d9c70e6b8c0814239ead47da00934666f668426fc6e94cce869e13fd7"},
{file = "numpy-1.24.4-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:9667575fb6d13c95f1b36aca12c5ee3356bf001b714fc354eb5465ce1609e62f"}, {file = "numpy-1.26.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:aa317b2325f7aa0a9471663e6093c210cb2ae9c0ad824732b307d2c51983d5b6"},
{file = "numpy-1.24.4-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f3a86ed21e4f87050382c7bc96571755193c4c1392490744ac73d660e8f564a9"}, {file = "numpy-1.26.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:174a8880739c16c925799c018f3f55b8130c1f7c8e75ab0a6fa9d41cab092fd6"},
{file = "numpy-1.24.4-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d11efb4dbecbdf22508d55e48d9c8384db795e1b7b51ea735289ff96613ff74d"}, {file = "numpy-1.26.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:f79b231bf5c16b1f39c7f4875e1ded36abee1591e98742b05d8a0fb55d8a3eec"},
{file = "numpy-1.24.4-cp39-cp39-win32.whl", hash = "sha256:6620c0acd41dbcb368610bb2f4d83145674040025e5536954782467100aa8835"}, {file = "numpy-1.26.2-cp312-cp312-win32.whl", hash = "sha256:4a06263321dfd3598cacb252f51e521a8cb4b6df471bb12a7ee5cbab20ea9167"},
{file = "numpy-1.24.4-cp39-cp39-win_amd64.whl", hash = "sha256:befe2bf740fd8373cf56149a5c23a0f601e82869598d41f8e188a0e9869926f8"}, {file = "numpy-1.26.2-cp312-cp312-win_amd64.whl", hash = "sha256:b04f5dc6b3efdaab541f7857351aac359e6ae3c126e2edb376929bd3b7f92d7e"},
{file = "numpy-1.24.4-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:31f13e25b4e304632a4619d0e0777662c2ffea99fcae2029556b17d8ff958aef"}, {file = "numpy-1.26.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:4eb8df4bf8d3d90d091e0146f6c28492b0be84da3e409ebef54349f71ed271ef"},
{file = "numpy-1.24.4-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:95f7ac6540e95bc440ad77f56e520da5bf877f87dca58bd095288dce8940532a"}, {file = "numpy-1.26.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:1a13860fdcd95de7cf58bd6f8bc5a5ef81c0b0625eb2c9a783948847abbef2c2"},
{file = "numpy-1.24.4-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:e98f220aa76ca2a977fe435f5b04d7b3470c0a2e6312907b37ba6068f26787f2"}, {file = "numpy-1.26.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:64308ebc366a8ed63fd0bf426b6a9468060962f1a4339ab1074c228fa6ade8e3"},
{file = "numpy-1.24.4.tar.gz", hash = "sha256:80f5e3a4e498641401868df4208b74581206afbee7cf7b8329daae82676d9463"}, {file = "numpy-1.26.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:baf8aab04a2c0e859da118f0b38617e5ee65d75b83795055fb66c0d5e9e9b818"},
{file = "numpy-1.26.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:d73a3abcac238250091b11caef9ad12413dab01669511779bc9b29261dd50210"},
{file = "numpy-1.26.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:b361d369fc7e5e1714cf827b731ca32bff8d411212fccd29ad98ad622449cc36"},
{file = "numpy-1.26.2-cp39-cp39-win32.whl", hash = "sha256:bd3f0091e845164a20bd5a326860c840fe2af79fa12e0469a12768a3ec578d80"},
{file = "numpy-1.26.2-cp39-cp39-win_amd64.whl", hash = "sha256:2beef57fb031dcc0dc8fa4fe297a742027b954949cabb52a2a376c144e5e6060"},
{file = "numpy-1.26.2-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:1cc3d5029a30fb5f06704ad6b23b35e11309491c999838c31f124fee32107c79"},
{file = "numpy-1.26.2-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:94cc3c222bb9fb5a12e334d0479b97bb2df446fbe622b470928f5284ffca3f8d"},
{file = "numpy-1.26.2-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:fe6b44fb8fcdf7eda4ef4461b97b3f63c466b27ab151bec2366db8b197387841"},
{file = "numpy-1.26.2.tar.gz", hash = "sha256:f65738447676ab5777f11e6bbbdb8ce11b785e105f690bc45966574816b6d3ea"},
] ]
[[package]] [[package]]
@@ -886,6 +1046,32 @@ files = [
{file = "packaging-23.2.tar.gz", hash = "sha256:048fb0e9405036518eaaf48a55953c750c11e1a1b68e0dd1a9d62ed0c092cfc5"}, {file = "packaging-23.2.tar.gz", hash = "sha256:048fb0e9405036518eaaf48a55953c750c11e1a1b68e0dd1a9d62ed0c092cfc5"},
] ]
[[package]]
name = "pathspec"
version = "0.12.1"
description = "Utility library for gitignore style pattern matching of file paths."
optional = false
python-versions = ">=3.8"
files = [
{file = "pathspec-0.12.1-py3-none-any.whl", hash = "sha256:a0d503e138a4c123b27490a4f7beda6a01c6f288df0e4a8b79c7eb0dc7b4cc08"},
{file = "pathspec-0.12.1.tar.gz", hash = "sha256:a482d51503a1ab33b1c67a6c3813a26953dbdc71c31dacaef9a838c4e29f5712"},
]
[[package]]
name = "platformdirs"
version = "4.1.0"
description = "A small Python package for determining appropriate platform-specific dirs, e.g. a \"user data dir\"."
optional = false
python-versions = ">=3.8"
files = [
{file = "platformdirs-4.1.0-py3-none-any.whl", hash = "sha256:11c8f37bcca40db96d8144522d925583bdb7a31f7b0e37e3ed4318400a8e2380"},
{file = "platformdirs-4.1.0.tar.gz", hash = "sha256:906d548203468492d432bcb294d4bc2fff751bf84971fbb2c10918cc206ee420"},
]
[package.extras]
docs = ["furo (>=2023.7.26)", "proselint (>=0.13)", "sphinx (>=7.1.1)", "sphinx-autodoc-typehints (>=1.24)"]
test = ["appdirs (==1.4.4)", "covdefaults (>=2.3)", "pytest (>=7.4)", "pytest-cov (>=4.1)", "pytest-mock (>=3.11.1)"]
[[package]] [[package]]
name = "pluggy" name = "pluggy"
version = "1.3.0" version = "1.3.0"
@@ -901,6 +1087,24 @@ files = [
dev = ["pre-commit", "tox"] dev = ["pre-commit", "tox"]
testing = ["pytest", "pytest-benchmark"] testing = ["pytest", "pytest-benchmark"]
[[package]]
name = "pre-commit"
version = "3.6.0"
description = "A framework for managing and maintaining multi-language pre-commit hooks."
optional = false
python-versions = ">=3.9"
files = [
{file = "pre_commit-3.6.0-py2.py3-none-any.whl", hash = "sha256:c255039ef399049a5544b6ce13d135caba8f2c28c3b4033277a788f434308376"},
{file = "pre_commit-3.6.0.tar.gz", hash = "sha256:d30bad9abf165f7785c15a21a1f46da7d0677cb00ee7ff4c579fd38922efe15d"},
]
[package.dependencies]
cfgv = ">=2.0.0"
identify = ">=1.0.0"
nodeenv = ">=0.11.1"
pyyaml = ">=5.1"
virtualenv = ">=20.10.0"
[[package]] [[package]]
name = "pydantic" name = "pydantic"
version = "2.5.3" version = "2.5.3"
@@ -1037,6 +1241,17 @@ files = [
[package.dependencies] [package.dependencies]
typing-extensions = ">=4.6.0,<4.7.0 || >4.7.0" typing-extensions = ">=4.6.0,<4.7.0 || >4.7.0"
[[package]]
name = "pyflakes"
version = "3.1.0"
description = "passive checker of Python programs"
optional = false
python-versions = ">=3.8"
files = [
{file = "pyflakes-3.1.0-py2.py3-none-any.whl", hash = "sha256:4132f6d49cb4dae6819e5379898f2b8cce3c5f23994194c24b77d5da2e36f774"},
{file = "pyflakes-3.1.0.tar.gz", hash = "sha256:a0aae034c444db0071aa077972ba4768d40c830d9539fd45bf4cd3f8f6992efc"},
]
[[package]] [[package]]
name = "pytest" name = "pytest"
version = "7.4.3" version = "7.4.3"
@@ -1168,6 +1383,22 @@ urllib3 = ">=1.21.1,<3"
socks = ["PySocks (>=1.5.6,!=1.5.7)"] socks = ["PySocks (>=1.5.6,!=1.5.7)"]
use-chardet-on-py3 = ["chardet (>=3.0.2,<6)"] use-chardet-on-py3 = ["chardet (>=3.0.2,<6)"]
[[package]]
name = "setuptools"
version = "69.0.3"
description = "Easily download, build, install, upgrade, and uninstall Python packages"
optional = false
python-versions = ">=3.8"
files = [
{file = "setuptools-69.0.3-py3-none-any.whl", hash = "sha256:385eb4edd9c9d5c17540511303e39a147ce2fc04bc55289c322b9e5904fe2c05"},
{file = "setuptools-69.0.3.tar.gz", hash = "sha256:be1af57fc409f93647f2e8e4573a142ed38724b8cdd389706a867bb4efcf1e78"},
]
[package.extras]
docs = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "pygments-github-lexers (==0.0.5)", "rst.linker (>=1.9)", "sphinx (<7.2.5)", "sphinx (>=3.5)", "sphinx-favicon", "sphinx-inline-tabs", "sphinx-lint", "sphinx-notfound-page (>=1,<2)", "sphinx-reredirects", "sphinxcontrib-towncrier"]
testing = ["build[virtualenv]", "filelock (>=3.4.0)", "flake8-2020", "ini2toml[lite] (>=0.9)", "jaraco.develop (>=7.21)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "pip (>=19.1)", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-mypy (>=0.9.1)", "pytest-perf", "pytest-ruff", "pytest-timeout", "pytest-xdist", "tomli-w (>=1.0.0)", "virtualenv (>=13.0.0)", "wheel"]
testing-integration = ["build[virtualenv] (>=1.0.3)", "filelock (>=3.4.0)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "packaging (>=23.1)", "pytest", "pytest-enabler", "pytest-xdist", "tomli", "virtualenv (>=13.0.0)", "wheel"]
[[package]] [[package]]
name = "sniffio" name = "sniffio"
version = "1.3.0" version = "1.3.0"
@@ -1370,6 +1601,26 @@ urllib3 = {version = "<2", markers = "python_version < \"3.10\""}
wrapt = "*" wrapt = "*"
yarl = "*" yarl = "*"
[[package]]
name = "virtualenv"
version = "20.25.0"
description = "Virtual Python Environment builder"
optional = false
python-versions = ">=3.7"
files = [
{file = "virtualenv-20.25.0-py3-none-any.whl", hash = "sha256:4238949c5ffe6876362d9c0180fc6c3a824a7b12b80604eeb8085f2ed7460de3"},
{file = "virtualenv-20.25.0.tar.gz", hash = "sha256:bf51c0d9c7dd63ea8e44086fa1e4fb1093a31e963b86959257378aef020e1f1b"},
]
[package.dependencies]
distlib = ">=0.3.7,<1"
filelock = ">=3.12.2,<4"
platformdirs = ">=3.9.1,<5"
[package.extras]
docs = ["furo (>=2023.7.26)", "proselint (>=0.13)", "sphinx (>=7.1.2)", "sphinx-argparse (>=0.4)", "sphinxcontrib-towncrier (>=0.2.1a0)", "towncrier (>=23.6)"]
test = ["covdefaults (>=2.3)", "coverage (>=7.2.7)", "coverage-enable-subprocess (>=1)", "flaky (>=3.7)", "packaging (>=23.1)", "pytest (>=7.4)", "pytest-env (>=0.8.2)", "pytest-freezer (>=0.4.8)", "pytest-mock (>=3.11.1)", "pytest-randomly (>=3.12)", "pytest-timeout (>=2.1)", "setuptools (>=68)", "time-machine (>=2.10)"]
[[package]] [[package]]
name = "wrapt" name = "wrapt"
version = "1.16.0" version = "1.16.0"
@@ -1554,5 +1805,5 @@ multidict = ">=4.0"
[metadata] [metadata]
lock-version = "2.0" lock-version = "2.0"
python-versions = ">=3.8.1,<4.0" python-versions = ">=3.9,<4.0"
content-hash = "d4b313f67f1942e153e515c12288b3252f955e1ebf75b4b7b28aa0c0f9dd4bd7" content-hash = "6518523eef59e35f89783fbdd92ff6f06a5a1b0d32c3d4241b5cce9713c84e47"

View File

@@ -12,11 +12,20 @@ Documentation = "https://github.com/joaomdmoura/CrewAI/wiki/Index"
Repository = "https://github.com/joaomdmoura/crewai" Repository = "https://github.com/joaomdmoura/crewai"
[tool.poetry.dependencies] [tool.poetry.dependencies]
python = ">=3.8.1,<4.0" python = ">=3.9,<4.0"
pydantic = "^2.4.2" pydantic = "^2.4.2"
langchain = "^0.0.351" langchain = "^0.0.351"
openai = "^1.5.0" openai = "^1.5.0"
[tool.poetry.group.dev.dependencies]
isort = "^5.13.2"
black = "^23.12.1"
autoflake = "^2.2.1"
pre-commit = "^3.6.0"
[tool.isort]
profile = "black"
[tool.poetry.group.test.dependencies] [tool.poetry.group.test.dependencies]
pytest = "^7.4" pytest = "^7.4"
pytest-vcr = "^1.0.2" pytest-vcr = "^1.0.2"

View File

@@ -1,139 +1,125 @@
"""Test Agent creation and execution basic functionality.""" """Test Agent creation and execution basic functionality."""
import pytest import pytest
from langchain.chat_models import ChatOpenAI as OpenAI from langchain.chat_models import ChatOpenAI as OpenAI
from ..crewai import Agent from ..crewai import Agent
def test_agent_creation():
agent = Agent(
role="test role",
goal="test goal",
backstory="test backstory"
)
assert agent.role == "test role" def test_agent_creation():
assert agent.goal == "test goal" agent = Agent(role="test role", goal="test goal", backstory="test backstory")
assert agent.backstory == "test backstory"
assert agent.tools == [] assert agent.role == "test role"
assert agent.goal == "test goal"
assert agent.backstory == "test backstory"
assert agent.tools == []
def test_agent_default_values(): def test_agent_default_values():
agent = Agent( agent = Agent(role="test role", goal="test goal", backstory="test backstory")
role="test role",
goal="test goal", assert isinstance(agent.llm, OpenAI)
backstory="test backstory" assert agent.llm.model_name == "gpt-4"
) assert agent.llm.temperature == 0.7
assert agent.llm.verbose == False
assert agent.allow_delegation == True
assert isinstance(agent.llm, OpenAI)
assert agent.llm.model_name == "gpt-4"
assert agent.llm.temperature == 0.7
assert agent.llm.verbose == False
assert agent.allow_delegation == True
def test_custom_llm(): def test_custom_llm():
agent = Agent( agent = Agent(
role="test role", role="test role",
goal="test goal", goal="test goal",
backstory="test backstory", backstory="test backstory",
llm=OpenAI( llm=OpenAI(temperature=0, model="gpt-4"),
temperature=0, )
model="gpt-4"
) assert isinstance(agent.llm, OpenAI)
) assert agent.llm.model_name == "gpt-4"
assert agent.llm.temperature == 0
assert isinstance(agent.llm, OpenAI)
assert agent.llm.model_name == "gpt-4"
assert agent.llm.temperature == 0
@pytest.mark.vcr(filter_headers=["authorization"]) @pytest.mark.vcr(filter_headers=["authorization"])
def test_agent_without_memory(): def test_agent_without_memory():
no_memory_agent = Agent( no_memory_agent = Agent(
role="test role", role="test role",
goal="test goal", goal="test goal",
backstory="test backstory", backstory="test backstory",
memory=False, memory=False,
llm=OpenAI( llm=OpenAI(temperature=0, model="gpt-4"),
temperature=0, )
model="gpt-4"
)
)
memory_agent = Agent( memory_agent = Agent(
role="test role", role="test role",
goal="test goal", goal="test goal",
backstory="test backstory", backstory="test backstory",
memory=True, memory=True,
llm=OpenAI( llm=OpenAI(temperature=0, model="gpt-4"),
temperature=0, )
model="gpt-4"
)
)
result = no_memory_agent.execute_task("How much is 1 + 1?") result = no_memory_agent.execute_task("How much is 1 + 1?")
assert result == "1 + 1 equals 2."
assert no_memory_agent.agent_executor.memory is None
assert memory_agent.agent_executor.memory is not None
assert result == "1 + 1 equals 2."
assert no_memory_agent.agent_executor.memory is None
assert memory_agent.agent_executor.memory is not None
@pytest.mark.vcr(filter_headers=["authorization"]) @pytest.mark.vcr(filter_headers=["authorization"])
def test_agent_execution(): def test_agent_execution():
agent = Agent( agent = Agent(
role="test role", role="test role",
goal="test goal", goal="test goal",
backstory="test backstory", backstory="test backstory",
allow_delegation=False allow_delegation=False,
) )
output = agent.execute_task("How much is 1 + 1?")
assert output == "2"
output = agent.execute_task("How much is 1 + 1?")
assert output == "2"
@pytest.mark.vcr(filter_headers=["authorization"]) @pytest.mark.vcr(filter_headers=["authorization"])
def test_agent_execution_with_tools(): def test_agent_execution_with_tools():
from langchain.tools import tool from langchain.tools import tool
@tool @tool
def multiplier(numbers) -> float: def multiplier(numbers) -> float:
"""Useful for when you need to multiply two numbers together. """Useful for when you need to multiply two numbers together.
The input to this tool should be a comma separated list of numbers of The input to this tool should be a comma separated list of numbers of
length two, representing the two numbers you want to multiply together. length two, representing the two numbers you want to multiply together.
For example, `1,2` would be the input if you wanted to multiply 1 by 2.""" For example, `1,2` would be the input if you wanted to multiply 1 by 2."""
a, b = numbers.split(',') a, b = numbers.split(",")
return int(a) * int(b) return int(a) * int(b)
agent = Agent( agent = Agent(
role="test role", role="test role",
goal="test goal", goal="test goal",
backstory="test backstory", backstory="test backstory",
tools=[multiplier], tools=[multiplier],
allow_delegation=False allow_delegation=False,
) )
output = agent.execute_task("What is 3 times 4")
assert output == "12"
output = agent.execute_task("What is 3 times 4")
assert output == "12"
@pytest.mark.vcr(filter_headers=["authorization"]) @pytest.mark.vcr(filter_headers=["authorization"])
def test_agent_execution_with_specific_tools(): def test_agent_execution_with_specific_tools():
from langchain.tools import tool from langchain.tools import tool
@tool @tool
def multiplier(numbers) -> float: def multiplier(numbers) -> float:
"""Useful for when you need to multiply two numbers together. """Useful for when you need to multiply two numbers together.
The input to this tool should be a comma separated list of numbers of The input to this tool should be a comma separated list of numbers of
length two, representing the two numbers you want to multiply together. length two, representing the two numbers you want to multiply together.
For example, `1,2` would be the input if you wanted to multiply 1 by 2.""" For example, `1,2` would be the input if you wanted to multiply 1 by 2."""
a, b = numbers.split(',') a, b = numbers.split(",")
return int(a) * int(b) return int(a) * int(b)
agent = Agent( agent = Agent(
role="test role", role="test role",
goal="test goal", goal="test goal",
backstory="test backstory", backstory="test backstory",
allow_delegation=False allow_delegation=False,
) )
output = agent.execute_task( output = agent.execute_task(task="What is 3 times 4", tools=[multiplier])
task="What is 3 times 4", assert output == "3 times 4 is 12."
tools=[multiplier]
)
assert output == "3 times 4 is 12."

View File

@@ -1,3 +1,4 @@
# conftest.py # conftest.py
from dotenv import load_dotenv from dotenv import load_dotenv
load_result = load_dotenv(override=True)
load_result = load_dotenv(override=True)

View File

@@ -1,114 +1,131 @@
"""Test Agent creation and execution basic functionality.""" """Test Agent creation and execution basic functionality."""
import json import json
import pytest import pytest
from ..crewai import Agent, Crew, Task, Process
from ..crewai import Agent, Crew, Process, Task
ceo = Agent( ceo = Agent(
role="CEO", role="CEO",
goal="Make sure the writers in your company produce amazing content.", goal="Make sure the writers in your company produce amazing content.",
backstory="You're an long time CEO of a content creation agency with a Senior Writer on the team. You're now working on a new project and want to make sure the content produced is amazing.", backstory="You're an long time CEO of a content creation agency with a Senior Writer on the team. You're now working on a new project and want to make sure the content produced is amazing.",
allow_delegation=True allow_delegation=True,
) )
researcher = Agent( researcher = Agent(
role="Researcher", role="Researcher",
goal="Make the best research and analysis on content about AI and AI agents", goal="Make the best research and analysis on content about AI and AI agents",
backstory="You're an expert researcher, specialized in technology, software engineering, AI and startups. You work as a freelancer and is now working on doing research and analysis for a new customer.", backstory="You're an expert researcher, specialized in technology, software engineering, AI and startups. You work as a freelancer and is now working on doing research and analysis for a new customer.",
allow_delegation=False allow_delegation=False,
) )
writer = Agent( writer = Agent(
role="Senior Writer", role="Senior Writer",
goal="Write the best content about AI and AI agents.", goal="Write the best content about AI and AI agents.",
backstory="You're a senior writer, specialized in technology, software engineering, AI and startups. You work as a freelancer and are now working on writing content for a new customer.", backstory="You're a senior writer, specialized in technology, software engineering, AI and startups. You work as a freelancer and are now working on writing content for a new customer.",
allow_delegation=False allow_delegation=False,
) )
def test_crew_config_conditional_requirement(): def test_crew_config_conditional_requirement():
with pytest.raises(ValueError): with pytest.raises(ValueError):
Crew(process=Process.sequential) Crew(process=Process.sequential)
config = json.dumps({ config = json.dumps(
"agents": [ {
{ "agents": [
"role": "Senior Researcher", {
"goal": "Make the best research and analysis on content about AI and AI agents", "role": "Senior Researcher",
"backstory": "You're an expert researcher, specialized in technology, software engineering, AI and startups. You work as a freelancer and is now working on doing research and analysis for a new customer." "goal": "Make the best research and analysis on content about AI and AI agents",
}, "backstory": "You're an expert researcher, specialized in technology, software engineering, AI and startups. You work as a freelancer and is now working on doing research and analysis for a new customer.",
{ },
"role": "Senior Writer", {
"goal": "Write the best content about AI and AI agents.", "role": "Senior Writer",
"backstory": "You're a senior writer, specialized in technology, software engineering, AI and startups. You work as a freelancer and are now working on writing content for a new customer." "goal": "Write the best content about AI and AI agents.",
} "backstory": "You're a senior writer, specialized in technology, software engineering, AI and startups. You work as a freelancer and are now working on writing content for a new customer.",
], },
"tasks": [ ],
{ "tasks": [
"description": "Give me a list of 5 interesting ideas to explore for na article, what makes them unique and interesting.", {
"agent": "Senior Researcher" "description": "Give me a list of 5 interesting ideas to explore for na article, what makes them unique and interesting.",
}, "agent": "Senior Researcher",
{ },
"description": "Write a 1 amazing paragraph highlight for each idead that showcases how good an article about this topic could be, check references if necessary or search for more content but make sure it's unique, interesting and well written. Return the list of ideas with their paragraph and your notes.", {
"agent": "Senior Writer" "description": "Write a 1 amazing paragraph highlight for each idead that showcases how good an article about this topic could be, check references if necessary or search for more content but make sure it's unique, interesting and well written. Return the list of ideas with their paragraph and your notes.",
} "agent": "Senior Writer",
] },
}) ],
parsed_config = json.loads(config) }
)
parsed_config = json.loads(config)
try: try:
crew = Crew(process=Process.sequential, config=config) crew = Crew(process=Process.sequential, config=config)
except ValueError: except ValueError:
pytest.fail("Unexpected ValidationError raised") pytest.fail("Unexpected ValidationError raised")
assert [agent.role for agent in crew.agents] == [
agent["role"] for agent in parsed_config["agents"]
]
assert [task.description for task in crew.tasks] == [
task["description"] for task in parsed_config["tasks"]
]
assert [agent.role for agent in crew.agents] == [agent['role'] for agent in parsed_config['agents']]
assert [task.description for task in crew.tasks] == [task['description'] for task in parsed_config['tasks']]
def test_crew_config_with_wrong_keys(): def test_crew_config_with_wrong_keys():
no_tasks_config = json.dumps({ no_tasks_config = json.dumps(
"agents": [ {
{ "agents": [
"role": "Senior Researcher", {
"goal": "Make the best research and analysis on content about AI and AI agents", "role": "Senior Researcher",
"backstory": "You're an expert researcher, specialized in technology, software engineering, AI and startups. You work as a freelancer and is now working on doing research and analysis for a new customer." "goal": "Make the best research and analysis on content about AI and AI agents",
} "backstory": "You're an expert researcher, specialized in technology, software engineering, AI and startups. You work as a freelancer and is now working on doing research and analysis for a new customer.",
] }
}) ]
}
)
no_agents_config = json.dumps(
{
"tasks": [
{
"description": "Give me a list of 5 interesting ideas to explore for na article, what makes them unique and interesting.",
"agent": "Senior Researcher",
}
]
}
)
with pytest.raises(ValueError):
Crew(process=Process.sequential, config='{"wrong_key": "wrong_value"}')
with pytest.raises(ValueError):
Crew(process=Process.sequential, config=no_tasks_config)
with pytest.raises(ValueError):
Crew(process=Process.sequential, config=no_agents_config)
no_agents_config = json.dumps({
"tasks": [
{
"description": "Give me a list of 5 interesting ideas to explore for na article, what makes them unique and interesting.",
"agent": "Senior Researcher"
}
]
})
with pytest.raises(ValueError):
Crew(process=Process.sequential, config='{"wrong_key": "wrong_value"}')
with pytest.raises(ValueError):
Crew(process=Process.sequential, config=no_tasks_config)
with pytest.raises(ValueError):
Crew(process=Process.sequential, config=no_agents_config)
@pytest.mark.vcr(filter_headers=["authorization"]) @pytest.mark.vcr(filter_headers=["authorization"])
def test_crew_creation(): def test_crew_creation():
tasks = [ tasks = [
Task( Task(
description="Give me a list of 5 interesting ideas to explore for na article, what makes them unique and interesting.", description="Give me a list of 5 interesting ideas to explore for na article, what makes them unique and interesting.",
agent=researcher agent=researcher,
), ),
Task( Task(
description="Write a 1 amazing paragraph highlight for each idea that showcases how good an article about this topic could be. Return the list of ideas with their paragraph and your notes.", description="Write a 1 amazing paragraph highlight for each idea that showcases how good an article about this topic could be. Return the list of ideas with their paragraph and your notes.",
agent=writer agent=writer,
) ),
] ]
crew = Crew( crew = Crew(
agents=[researcher, writer], agents=[researcher, writer],
process=Process.sequential, process=Process.sequential,
tasks=tasks, tasks=tasks,
) )
assert crew.kickoff() == """1. **The Evolution of AI: From Old Concepts to New Frontiers** - Journey with us as we traverse the fascinating timeline of artificial intelligence - from its philosophical and mathematical infancy to the sophisticated, problem-solving tool it has become today. This riveting account will not only educate but also inspire, as we delve deep into the milestones that brought us here and shine a beacon on the potential that lies ahead. assert (
crew.kickoff()
== """1. **The Evolution of AI: From Old Concepts to New Frontiers** - Journey with us as we traverse the fascinating timeline of artificial intelligence - from its philosophical and mathematical infancy to the sophisticated, problem-solving tool it has become today. This riveting account will not only educate but also inspire, as we delve deep into the milestones that brought us here and shine a beacon on the potential that lies ahead.
2. **AI Agents in Healthcare: The Future of Medicine** - Imagine a world where illnesses are diagnosed before symptoms appear, where patient outcomes are not mere guesses but accurate predictions. This is the world AI is crafting in healthcare - a revolution that's saving lives and changing the face of medicine as we know it. This article will spotlight this transformative journey, underlining the profound impact AI is having on our health and well-being. 2. **AI Agents in Healthcare: The Future of Medicine** - Imagine a world where illnesses are diagnosed before symptoms appear, where patient outcomes are not mere guesses but accurate predictions. This is the world AI is crafting in healthcare - a revolution that's saving lives and changing the face of medicine as we know it. This article will spotlight this transformative journey, underlining the profound impact AI is having on our health and well-being.
@@ -117,60 +134,60 @@ def test_crew_creation():
4. **Demystifying AI Algorithms: A Deep Dive into Machine Learning** - Ever wondered what goes on behind the scenes of AI? This enlightening article will break down the complex world of machine learning algorithms into digestible insights, unraveling the mystery of AI's 'black box'. It's a rare opportunity for the non-technical audience to appreciate the inner workings of AI, fostering a deeper understanding of this revolutionary technology. 4. **Demystifying AI Algorithms: A Deep Dive into Machine Learning** - Ever wondered what goes on behind the scenes of AI? This enlightening article will break down the complex world of machine learning algorithms into digestible insights, unraveling the mystery of AI's 'black box'. It's a rare opportunity for the non-technical audience to appreciate the inner workings of AI, fostering a deeper understanding of this revolutionary technology.
5. **AI Startups: The Game Changers of the Tech Industry** - In the world of tech, AI startups are the bold pioneers charting new territories. This article will spotlight these game changers, showcasing how their innovative products and services are driving the AI revolution. It's a unique opportunity to catch a glimpse of the entrepreneurial side of AI, offering inspiration for the tech enthusiasts and dreamers alike.""" 5. **AI Startups: The Game Changers of the Tech Industry** - In the world of tech, AI startups are the bold pioneers charting new territories. This article will spotlight these game changers, showcasing how their innovative products and services are driving the AI revolution. It's a unique opportunity to catch a glimpse of the entrepreneurial side of AI, offering inspiration for the tech enthusiasts and dreamers alike."""
)
@pytest.mark.vcr(filter_headers=["authorization"]) @pytest.mark.vcr(filter_headers=["authorization"])
def test_crew_with_delegating_agents(): def test_crew_with_delegating_agents():
tasks = [ tasks = [
Task( Task(
description="Produce and amazing 1 paragraph draft of an article about AI Agents.", description="Produce and amazing 1 paragraph draft of an article about AI Agents.",
agent=ceo agent=ceo,
) )
] ]
crew = Crew( crew = Crew(
agents=[ceo, writer], agents=[ceo, writer],
process=Process.sequential, process=Process.sequential,
tasks=tasks, tasks=tasks,
) )
assert (
crew.kickoff()
== "The Senior Writer has created a compelling and engaging 1 paragraph draft about AI agents. The paragraph provides a brief yet comprehensive overview of AI agents, their uses, and implications in the current world. It emphasizes their potential and the role they can play in the future. The tone is informative but captivating, meeting the objectives of the task."
)
assert crew.kickoff() == 'The Senior Writer has created a compelling and engaging 1 paragraph draft about AI agents. The paragraph provides a brief yet comprehensive overview of AI agents, their uses, and implications in the current world. It emphasizes their potential and the role they can play in the future. The tone is informative but captivating, meeting the objectives of the task.'
@pytest.mark.vcr(filter_headers=["authorization"]) @pytest.mark.vcr(filter_headers=["authorization"])
def test_crew_verbose_output(capsys): def test_crew_verbose_output(capsys):
tasks = [ tasks = [
Task( Task(description="Research AI advancements.", agent=researcher),
description="Research AI advancements.", Task(description="Write about AI in healthcare.", agent=writer),
agent=researcher ]
),
Task(
description="Write about AI in healthcare.",
agent=writer
)
]
crew = Crew( crew = Crew(
agents=[researcher, writer], agents=[researcher, writer],
tasks=tasks, tasks=tasks,
process=Process.sequential, process=Process.sequential,
verbose=True verbose=True,
) )
crew.kickoff() crew.kickoff()
captured = capsys.readouterr() captured = capsys.readouterr()
expected_strings = [ expected_strings = [
"Working Agent: Researcher", "Working Agent: Researcher",
"Starting Task: Research AI advancements. ...", "Starting Task: Research AI advancements. ...",
"Task output:", "Task output:",
"Working Agent: Senior Writer", "Working Agent: Senior Writer",
"Starting Task: Write about AI in healthcare. ...", "Starting Task: Write about AI in healthcare. ...",
"Task output:" "Task output:",
] ]
for expected_string in expected_strings: for expected_string in expected_strings:
assert expected_string in captured.out assert expected_string in captured.out
# Now test with verbose set to False # Now test with verbose set to False
crew.verbose = False crew.verbose = False
crew.kickoff() crew.kickoff()
captured = capsys.readouterr() captured = capsys.readouterr()
assert captured.out == "" assert captured.out == ""

View File

@@ -1,57 +1,56 @@
"""Test Agent creation and execution basic functionality.""" """Test Agent creation and execution basic functionality."""
import pytest
from ..crewai import Agent, Task from ..crewai import Agent, Task
def test_task_tool_reflect_agent_tools(): def test_task_tool_reflect_agent_tools():
from langchain.tools import tool from langchain.tools import tool
@tool @tool
def fake_tool() -> None: def fake_tool() -> None:
"Fake tool" "Fake tool"
pass
researcher = Agent(
researcher = Agent( role="Researcher",
role="Researcher", goal="Make the best research and analysis on content about AI and AI agents",
goal="Make the best research and analysis on content about AI and AI agents", backstory="You're an expert researcher, specialized in technology, software engineering, AI and startups. You work as a freelancer and is now working on doing research and analysis for a new customer.",
backstory="You're an expert researcher, specialized in technology, software engineering, AI and startups. You work as a freelancer and is now working on doing research and analysis for a new customer.", tools=[fake_tool],
tools=[fake_tool], allow_delegation=False,
allow_delegation=False )
)
task = Task(
description="Give me a list of 5 interesting ideas to explore for na article, what makes them unique and interesting.",
agent=researcher,
)
assert task.tools == [fake_tool]
task = Task(
description="Give me a list of 5 interesting ideas to explore for na article, what makes them unique and interesting.",
agent=researcher
)
assert task.tools == [fake_tool]
def test_task_tool_takes_precedence_ove_agent_tools(): def test_task_tool_takes_precedence_ove_agent_tools():
from langchain.tools import tool from langchain.tools import tool
@tool @tool
def fake_tool() -> None: def fake_tool() -> None:
"Fake tool" "Fake tool"
pass
@tool @tool
def fake_task_tool() -> None: def fake_task_tool() -> None:
"Fake tool" "Fake tool"
pass
researcher = Agent(
role="Researcher",
goal="Make the best research and analysis on content about AI and AI agents",
backstory="You're an expert researcher, specialized in technology, software engineering, AI and startups. You work as a freelancer and is now working on doing research and analysis for a new customer.",
tools=[fake_tool],
allow_delegation=False
)
task = Task( researcher = Agent(
description="Give me a list of 5 interesting ideas to explore for na article, what makes them unique and interesting.", role="Researcher",
agent=researcher, goal="Make the best research and analysis on content about AI and AI agents",
tools=[fake_task_tool], backstory="You're an expert researcher, specialized in technology, software engineering, AI and startups. You work as a freelancer and is now working on doing research and analysis for a new customer.",
allow_delegation=False tools=[fake_tool],
) allow_delegation=False,
)
assert task.tools == [fake_task_tool]
task = Task(
description="Give me a list of 5 interesting ideas to explore for na article, what makes them unique and interesting.",
agent=researcher,
tools=[fake_task_tool],
allow_delegation=False,
)
assert task.tools == [fake_task_tool]