From 60efcad481f68c3d650c810df7ba5e45d2826574 Mon Sep 17 00:00:00 2001 From: Eduardo Chiarotti Date: Fri, 18 Oct 2024 15:45:01 -0300 Subject: [PATCH 001/126] feat: add poetry.lock to uv migration (#1468) --- src/crewai/cli/update_crew.py | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/crewai/cli/update_crew.py b/src/crewai/cli/update_crew.py index e2d39590d..d38e11a25 100644 --- a/src/crewai/cli/update_crew.py +++ b/src/crewai/cli/update_crew.py @@ -1,3 +1,4 @@ +import os import shutil import tomli_w @@ -94,6 +95,15 @@ def migrate_pyproject(input_file, output_file): shutil.copy2(input_file, backup_file) print(f"Original pyproject.toml backed up as {backup_file}") + # Rename the poetry.lock file + lock_file = "poetry.lock" + lock_backup = "poetry-old.lock" + if os.path.exists(lock_file): + os.rename(lock_file, lock_backup) + print(f"Original poetry.lock renamed to {lock_backup}") + else: + print("No poetry.lock file found to rename.") + # Write the new pyproject.toml with open(output_file, "wb") as f: tomli_w.dump(new_pyproject, f) From 84f48c465d602a2b121a82d464fe701f84160e9f Mon Sep 17 00:00:00 2001 From: "Brandon Hancock (bhancock_ai)" <109994880+bhancockio@users.noreply.github.com> Date: Fri, 18 Oct 2024 14:56:56 -0400 Subject: [PATCH 002/126] fix tool calling issue (#1467) * fix tool calling issue * Update tool type check * Drop print --- src/crewai/agent.py | 2 +- src/crewai/agents/crew_agent_executor.py | 8 +- src/crewai/tools/tool_usage.py | 10 +- tests/tools/test_tool_usage.py | 143 +++++++++++++++++++++++ 4 files changed, 154 insertions(+), 9 deletions(-) create mode 100644 tests/tools/test_tool_usage.py diff --git a/src/crewai/agent.py b/src/crewai/agent.py index 3f81ece21..165a40656 100644 --- a/src/crewai/agent.py +++ b/src/crewai/agent.py @@ -394,7 +394,7 @@ class Agent(BaseAgent): """ tool_strings = [] for tool in tools: - args_schema = str(tool.args) + args_schema = str(tool.model_fields) if hasattr(tool, "func") and tool.func: sig = signature(tool.func) description = ( diff --git a/src/crewai/agents/crew_agent_executor.py b/src/crewai/agents/crew_agent_executor.py index b901fe132..b11782ca1 100644 --- a/src/crewai/agents/crew_agent_executor.py +++ b/src/crewai/agents/crew_agent_executor.py @@ -2,6 +2,7 @@ import json import re from typing import Any, Dict, List, Union +from crewai.agents.agent_builder.base_agent import BaseAgent from crewai.agents.agent_builder.base_agent_executor_mixin import CrewAgentExecutorMixin from crewai.agents.parser import ( FINAL_ANSWER_AND_PARSABLE_ACTION_ERROR_MESSAGE, @@ -19,7 +20,6 @@ from crewai.utilities.exceptions.context_window_exceeding_exception import ( ) from crewai.utilities.logger import Logger from crewai.utilities.training_handler import CrewTrainingHandler -from crewai.agents.agent_builder.base_agent import BaseAgent class CrewAgentExecutor(CrewAgentExecutorMixin): @@ -323,9 +323,9 @@ class CrewAgentExecutor(CrewAgentExecutorMixin): if self.crew is not None and hasattr(self.crew, "_train_iteration"): train_iteration = self.crew._train_iteration if agent_id in training_data and isinstance(train_iteration, int): - training_data[agent_id][train_iteration]["improved_output"] = ( - result.output - ) + training_data[agent_id][train_iteration][ + "improved_output" + ] = result.output training_handler.save(training_data) else: self._logger.log( diff --git a/src/crewai/tools/tool_usage.py b/src/crewai/tools/tool_usage.py index f75a9443a..71c02fc3c 100644 --- a/src/crewai/tools/tool_usage.py +++ b/src/crewai/tools/tool_usage.py @@ -6,14 +6,13 @@ from difflib import SequenceMatcher from textwrap import dedent from typing import Any, List, Union +import crewai.utilities.events as events from crewai.agents.tools_handler import ToolsHandler from crewai.task import Task from crewai.telemetry import Telemetry from crewai.tools.tool_calling import InstructorToolCalling, ToolCalling from crewai.tools.tool_usage_events import ToolUsageError, ToolUsageFinished from crewai.utilities import I18N, Converter, ConverterError, Printer -import crewai.utilities.events as events - agentops = None if os.environ.get("AGENTOPS_API_KEY"): @@ -300,8 +299,11 @@ class ToolUsage: descriptions = [] for tool in self.tools: args = { - k: {k2: v2 for k2, v2 in v.items() if k2 in ["description", "type"]} - for k, v in tool.args.items() + name: { + "description": field.description, + "type": field.annotation.__name__, + } + for name, field in tool.args_schema.model_fields.items() } descriptions.append( "\n".join( diff --git a/tests/tools/test_tool_usage.py b/tests/tools/test_tool_usage.py new file mode 100644 index 000000000..d0a6ec0b0 --- /dev/null +++ b/tests/tools/test_tool_usage.py @@ -0,0 +1,143 @@ +import json +import random +from unittest.mock import MagicMock, patch + +import pytest +from crewai_tools import BaseTool +from pydantic import BaseModel, Field + +from crewai import Agent, Crew, Task +from crewai.tools.tool_usage import ToolUsage + + +class RandomNumberToolInput(BaseModel): + min_value: int = Field( + ..., description="The minimum value of the range (inclusive)" + ) + max_value: int = Field( + ..., description="The maximum value of the range (inclusive)" + ) + + +class RandomNumberTool(BaseTool): + name: str = "Random Number Generator" + description: str = "Generates a random number within a specified range" + args_schema: type[BaseModel] = RandomNumberToolInput + + def _run(self, min_value: int, max_value: int) -> int: + return random.randint(min_value, max_value) + + +# Example agent and task +example_agent = Agent( + role="Number Generator", + goal="Generate random numbers for various purposes", + backstory="You are an AI agent specialized in generating random numbers within specified ranges.", + tools=[RandomNumberTool()], + verbose=True, +) + +example_task = Task( + description="Generate a random number between 1 and 100", + expected_output="A random number between 1 and 100", + agent=example_agent, +) + + +def test_random_number_tool_usage(): + crew = Crew( + agents=[example_agent], + tasks=[example_task], + ) + + with patch.object(random, "randint", return_value=42): + result = crew.kickoff() + + assert "42" in result.raw + + +def test_random_number_tool_range(): + tool = RandomNumberTool() + result = tool._run(1, 10) + assert 1 <= result <= 10 + + +def test_random_number_tool_with_crew(): + crew = Crew( + agents=[example_agent], + tasks=[example_task], + ) + + result = crew.kickoff() + + # Check if the result contains a number between 1 and 100 + assert any(str(num) in result.raw for num in range(1, 101)) + + +def test_random_number_tool_invalid_range(): + tool = RandomNumberTool() + with pytest.raises(ValueError): + tool._run(10, 1) # min_value > max_value + + +def test_random_number_tool_schema(): + tool = RandomNumberTool() + + # Get the schema using model_json_schema() + schema = tool.args_schema.model_json_schema() + + # Convert the schema to a string + schema_str = json.dumps(schema) + + # Check if the schema string contains the expected fields + assert "min_value" in schema_str + assert "max_value" in schema_str + + # Parse the schema string back to a dictionary + schema_dict = json.loads(schema_str) + + # Check if the schema contains the correct field types + assert schema_dict["properties"]["min_value"]["type"] == "integer" + assert schema_dict["properties"]["max_value"]["type"] == "integer" + + # Check if the schema contains the field descriptions + assert ( + "minimum value" in schema_dict["properties"]["min_value"]["description"].lower() + ) + assert ( + "maximum value" in schema_dict["properties"]["max_value"]["description"].lower() + ) + + +def test_tool_usage_render(): + tool = RandomNumberTool() + + tool_usage = ToolUsage( + tools_handler=MagicMock(), + tools=[tool], + original_tools=[tool], + tools_description="Sample tool for testing", + tools_names="random_number_generator", + task=MagicMock(), + function_calling_llm=MagicMock(), + agent=MagicMock(), + action=MagicMock(), + ) + + rendered = tool_usage._render() + + # Updated checks to match the actual output + assert "Tool Name: random number generator" in rendered + assert ( + "Random Number Generator(min_value: 'integer', max_value: 'integer') - Generates a random number within a specified range min_value: 'The minimum value of the range (inclusive)', max_value: 'The maximum value of the range (inclusive)'" + in rendered + ) + assert "Tool Arguments:" in rendered + assert ( + "'min_value': {'description': 'The minimum value of the range (inclusive)', 'type': 'int'}" + in rendered + ) + assert ( + "'max_value': {'description': 'The maximum value of the range (inclusive)', 'type': 'int'}" + in rendered + ) From d1737a96fb732d172eb91b503ebd0c1b383af144 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Moura?= Date: Fri, 18 Oct 2024 17:56:28 -0300 Subject: [PATCH 003/126] cutting new version --- pyproject.toml | 8 ++++---- src/crewai/__init__.py | 2 +- src/crewai/cli/templates/crew/pyproject.toml | 2 +- src/crewai/cli/templates/flow/pyproject.toml | 2 +- src/crewai/cli/templates/pipeline/pyproject.toml | 2 +- .../cli/templates/pipeline_router/pyproject.toml | 2 +- src/crewai/cli/templates/tool/pyproject.toml | 2 +- uv.lock | 14 +++++++------- 8 files changed, 17 insertions(+), 17 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index e47748e28..436a01170 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "crewai" -version = "0.74.0" +version = "0.74.1" description = "Cutting-edge framework for orchestrating role-playing, autonomous AI agents. By fostering collaborative intelligence, CrewAI empowers agents to work together seamlessly, tackling complex tasks." readme = "README.md" requires-python = ">=3.10,<=3.13" @@ -16,7 +16,7 @@ dependencies = [ "opentelemetry-exporter-otlp-proto-http>=1.22.0", "instructor>=1.3.3", "regex>=2024.9.11", - "crewai-tools>=0.13.1", + "crewai-tools>=0.13.2", "click>=8.1.7", "python-dotenv>=1.0.0", "appdirs>=1.4.4", @@ -36,7 +36,7 @@ Documentation = "https://docs.crewai.com" Repository = "https://github.com/crewAIInc/crewAI" [project.optional-dependencies] -tools = ["crewai-tools>=0.12.1"] +tools = ["crewai-tools>=0.13.2"] agentops = ["agentops>=0.3.0"] [tool.uv] @@ -51,7 +51,7 @@ dev-dependencies = [ "mkdocs-material-extensions>=1.3.1", "pillow>=10.2.0", "cairosvg>=2.7.1", - "crewai-tools>=0.12.1", + "crewai-tools>=0.13.2", "pytest>=8.0.0", "pytest-vcr>=1.0.2", "python-dotenv>=1.0.0", diff --git a/src/crewai/__init__.py b/src/crewai/__init__.py index a72d6e805..41da7aa21 100644 --- a/src/crewai/__init__.py +++ b/src/crewai/__init__.py @@ -14,5 +14,5 @@ warnings.filterwarnings( category=UserWarning, module="pydantic.main", ) -__version__ = "0.74.0" +__version__ = "0.74.1" __all__ = ["Agent", "Crew", "Process", "Task", "Pipeline", "Router", "LLM", "Flow"] diff --git a/src/crewai/cli/templates/crew/pyproject.toml b/src/crewai/cli/templates/crew/pyproject.toml index d387443a1..35931e8e0 100644 --- a/src/crewai/cli/templates/crew/pyproject.toml +++ b/src/crewai/cli/templates/crew/pyproject.toml @@ -5,7 +5,7 @@ description = "{{name}} using crewAI" authors = [{ name = "Your Name", email = "you@example.com" }] requires-python = ">=3.10,<=3.13" dependencies = [ - "crewai[tools]>=0.74.0,<1.0.0" + "crewai[tools]>=0.74.1,<1.0.0" ] [project.scripts] diff --git a/src/crewai/cli/templates/flow/pyproject.toml b/src/crewai/cli/templates/flow/pyproject.toml index 9db16e2a2..0e81b72b9 100644 --- a/src/crewai/cli/templates/flow/pyproject.toml +++ b/src/crewai/cli/templates/flow/pyproject.toml @@ -5,7 +5,7 @@ description = "{{name}} using crewAI" authors = [{ name = "Your Name", email = "you@example.com" }] requires-python = ">=3.10,<=3.13" dependencies = [ - "crewai[tools]>=0.74.0,<1.0.0", + "crewai[tools]>=0.74.1,<1.0.0", "asyncio" ] diff --git a/src/crewai/cli/templates/pipeline/pyproject.toml b/src/crewai/cli/templates/pipeline/pyproject.toml index 3eb9b73d1..d8c2c64b3 100644 --- a/src/crewai/cli/templates/pipeline/pyproject.toml +++ b/src/crewai/cli/templates/pipeline/pyproject.toml @@ -6,7 +6,7 @@ authors = ["Your Name "] [tool.poetry.dependencies] python = ">=3.10,<=3.13" -crewai = { extras = ["tools"], version = ">=0.74.0,<1.0.0" } +crewai = { extras = ["tools"], version = ">=0.74.1,<1.0.0" } asyncio = "*" [tool.poetry.scripts] diff --git a/src/crewai/cli/templates/pipeline_router/pyproject.toml b/src/crewai/cli/templates/pipeline_router/pyproject.toml index dabc8d281..a672ba2b7 100644 --- a/src/crewai/cli/templates/pipeline_router/pyproject.toml +++ b/src/crewai/cli/templates/pipeline_router/pyproject.toml @@ -5,7 +5,7 @@ description = "{{name}} using crewAI" authors = ["Your Name "] requires-python = ">=3.10,<=3.13" dependencies = [ - "crewai[tools]>=0.74.0,<1.0.0" + "crewai[tools]>=0.74.1,<1.0.0" ] [project.scripts] diff --git a/src/crewai/cli/templates/tool/pyproject.toml b/src/crewai/cli/templates/tool/pyproject.toml index 0fef250f5..21e520146 100644 --- a/src/crewai/cli/templates/tool/pyproject.toml +++ b/src/crewai/cli/templates/tool/pyproject.toml @@ -5,6 +5,6 @@ description = "Power up your crews with {{folder_name}}" readme = "README.md" requires-python = ">=3.10,<=3.13" dependencies = [ - "crewai[tools]>=0.74.0" + "crewai[tools]>=0.74.1" ] diff --git a/uv.lock b/uv.lock index b0416fa72..1d2f9cb39 100644 --- a/uv.lock +++ b/uv.lock @@ -627,7 +627,7 @@ wheels = [ [[package]] name = "crewai" -version = "0.74.0" +version = "0.74.1" source = { editable = "." } dependencies = [ { name = "appdirs" }, @@ -687,8 +687,8 @@ requires-dist = [ { name = "auth0-python", specifier = ">=4.7.1" }, { name = "chromadb", specifier = ">=0.4.24" }, { name = "click", specifier = ">=8.1.7" }, - { name = "crewai-tools", specifier = ">=0.13.1" }, - { name = "crewai-tools", marker = "extra == 'tools'", specifier = ">=0.12.1" }, + { name = "crewai-tools", specifier = ">=0.13.2" }, + { name = "crewai-tools", marker = "extra == 'tools'", specifier = ">=0.13.2" }, { name = "instructor", specifier = ">=1.3.3" }, { name = "json-repair", specifier = ">=0.25.2" }, { name = "jsonref", specifier = ">=1.1.0" }, @@ -709,7 +709,7 @@ requires-dist = [ [package.metadata.requires-dev] dev = [ { name = "cairosvg", specifier = ">=2.7.1" }, - { name = "crewai-tools", specifier = ">=0.12.1" }, + { name = "crewai-tools", specifier = ">=0.13.2" }, { name = "mkdocs", specifier = ">=1.4.3" }, { name = "mkdocs-material", specifier = ">=9.5.7" }, { name = "mkdocs-material-extensions", specifier = ">=1.3.1" }, @@ -728,7 +728,7 @@ dev = [ [[package]] name = "crewai-tools" -version = "0.13.1" +version = "0.13.2" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "beautifulsoup4" }, @@ -746,9 +746,9 @@ dependencies = [ { name = "requests" }, { name = "selenium" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/d5/81/b8a0bb984aea2af49b0072e074c87c75a6c4581902b81f3a3d46f95f01c7/crewai_tools-0.13.1.tar.gz", hash = "sha256:363c7ec717f4c6f9b61cec9314a5ec2fbd026d75e8e6278f49f715ed5915cd4d", size = 816254 } +sdist = { url = "https://files.pythonhosted.org/packages/96/02/136f42ed8a7bd706a85663714c615bdcb684e43e95e4719c892aa0ce3d53/crewai_tools-0.13.2.tar.gz", hash = "sha256:c6782f2e868c0e96b25891f1b40fb8c90c01e920bab2fd1388f89ef1d7a4b99b", size = 816250 } wheels = [ - { url = "https://files.pythonhosted.org/packages/09/8a/04c885da3e01d1f11478dd866d3506906bfb60d7587627dd4b132ff10f64/crewai_tools-0.13.1-py3-none-any.whl", hash = "sha256:62067e2502bf66c0ae2e3a833c60b900bd1f793a9a80895a1f10a9cfa1b5dc3c", size = 463444 }, + { url = "https://files.pythonhosted.org/packages/28/30/df215173b6193b2cfb1902a339443be73056eae89579805b853c6f359761/crewai_tools-0.13.2-py3-none-any.whl", hash = "sha256:8c7583c9559fb625f594349c6553a5251ebd7b21918735ad6fbe8bab7ec3db50", size = 463444 }, ] [[package]] From 40f81aecf5a84dbb5361dfa86044d72990390084 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Moura?= Date: Fri, 18 Oct 2024 17:57:37 -0300 Subject: [PATCH 004/126] new verison --- pyproject.toml | 2 +- src/crewai/__init__.py | 2 +- src/crewai/cli/templates/crew/pyproject.toml | 2 +- src/crewai/cli/templates/flow/pyproject.toml | 2 +- src/crewai/cli/templates/pipeline/pyproject.toml | 2 +- src/crewai/cli/templates/pipeline_router/pyproject.toml | 2 +- src/crewai/cli/templates/tool/pyproject.toml | 2 +- uv.lock | 2 +- 8 files changed, 8 insertions(+), 8 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 436a01170..772d41b36 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "crewai" -version = "0.74.1" +version = "0.74.2" 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" diff --git a/src/crewai/__init__.py b/src/crewai/__init__.py index 41da7aa21..a41e3827f 100644 --- a/src/crewai/__init__.py +++ b/src/crewai/__init__.py @@ -14,5 +14,5 @@ warnings.filterwarnings( category=UserWarning, module="pydantic.main", ) -__version__ = "0.74.1" +__version__ = "0.74.2" __all__ = ["Agent", "Crew", "Process", "Task", "Pipeline", "Router", "LLM", "Flow"] diff --git a/src/crewai/cli/templates/crew/pyproject.toml b/src/crewai/cli/templates/crew/pyproject.toml index 35931e8e0..36ba1548c 100644 --- a/src/crewai/cli/templates/crew/pyproject.toml +++ b/src/crewai/cli/templates/crew/pyproject.toml @@ -5,7 +5,7 @@ description = "{{name}} using crewAI" authors = [{ name = "Your Name", email = "you@example.com" }] requires-python = ">=3.10,<=3.13" dependencies = [ - "crewai[tools]>=0.74.1,<1.0.0" + "crewai[tools]>=0.74.2,<1.0.0" ] [project.scripts] diff --git a/src/crewai/cli/templates/flow/pyproject.toml b/src/crewai/cli/templates/flow/pyproject.toml index 0e81b72b9..490d66346 100644 --- a/src/crewai/cli/templates/flow/pyproject.toml +++ b/src/crewai/cli/templates/flow/pyproject.toml @@ -5,7 +5,7 @@ description = "{{name}} using crewAI" authors = [{ name = "Your Name", email = "you@example.com" }] requires-python = ">=3.10,<=3.13" dependencies = [ - "crewai[tools]>=0.74.1,<1.0.0", + "crewai[tools]>=0.74.2,<1.0.0", "asyncio" ] diff --git a/src/crewai/cli/templates/pipeline/pyproject.toml b/src/crewai/cli/templates/pipeline/pyproject.toml index d8c2c64b3..517c2a4d1 100644 --- a/src/crewai/cli/templates/pipeline/pyproject.toml +++ b/src/crewai/cli/templates/pipeline/pyproject.toml @@ -6,7 +6,7 @@ authors = ["Your Name "] [tool.poetry.dependencies] python = ">=3.10,<=3.13" -crewai = { extras = ["tools"], version = ">=0.74.1,<1.0.0" } +crewai = { extras = ["tools"], version = ">=0.74.2,<1.0.0" } asyncio = "*" [tool.poetry.scripts] diff --git a/src/crewai/cli/templates/pipeline_router/pyproject.toml b/src/crewai/cli/templates/pipeline_router/pyproject.toml index a672ba2b7..b349e4dda 100644 --- a/src/crewai/cli/templates/pipeline_router/pyproject.toml +++ b/src/crewai/cli/templates/pipeline_router/pyproject.toml @@ -5,7 +5,7 @@ description = "{{name}} using crewAI" authors = ["Your Name "] requires-python = ">=3.10,<=3.13" dependencies = [ - "crewai[tools]>=0.74.1,<1.0.0" + "crewai[tools]>=0.74.2,<1.0.0" ] [project.scripts] diff --git a/src/crewai/cli/templates/tool/pyproject.toml b/src/crewai/cli/templates/tool/pyproject.toml index 21e520146..bd1df942f 100644 --- a/src/crewai/cli/templates/tool/pyproject.toml +++ b/src/crewai/cli/templates/tool/pyproject.toml @@ -5,6 +5,6 @@ description = "Power up your crews with {{folder_name}}" readme = "README.md" requires-python = ">=3.10,<=3.13" dependencies = [ - "crewai[tools]>=0.74.1" + "crewai[tools]>=0.74.2" ] diff --git a/uv.lock b/uv.lock index 1d2f9cb39..729338bcd 100644 --- a/uv.lock +++ b/uv.lock @@ -627,7 +627,7 @@ wheels = [ [[package]] name = "crewai" -version = "0.74.1" +version = "0.74.2" source = { editable = "." } dependencies = [ { name = "appdirs" }, From b98256e43435817395a288b1752cfc4a3de34640 Mon Sep 17 00:00:00 2001 From: Vini Brasil Date: Mon, 21 Oct 2024 09:24:03 -0300 Subject: [PATCH 005/126] Adapt `crewai tool install ` to uv (#1481) This commit updates the tool install comamnd to uv's new custom index feature. Related: https://github.com/astral-sh/uv/pull/7746/ --- pyproject.toml | 2 +- src/crewai/cli/tools/main.py | 8 ++++---- tests/cli/tools/test_main.py | 4 ++-- uv.lock | 40 ++++++++++++++++++------------------ 4 files changed, 27 insertions(+), 27 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 772d41b36..d7920c7b0 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -25,7 +25,7 @@ dependencies = [ "auth0-python>=4.7.1", "litellm>=1.44.22", "pyvis>=0.3.2", - "uv>=0.4.18", + "uv>=0.4.25", "tomli-w>=1.1.0", "chromadb>=0.4.24", ] diff --git a/src/crewai/cli/tools/main.py b/src/crewai/cli/tools/main.py index b6237c61d..c875229c3 100644 --- a/src/crewai/cli/tools/main.py +++ b/src/crewai/cli/tools/main.py @@ -28,8 +28,6 @@ class ToolCommand(BaseCommand, PlusAPIMixin): A class to handle tool repository related operations for CrewAI projects. """ - BASE_URL = "https://app.crewai.com/pypi/" - def __init__(self): BaseCommand.__init__(self) PlusAPIMixin.__init__(self, telemetry=self._telemetry) @@ -178,12 +176,14 @@ class ToolCommand(BaseCommand, PlusAPIMixin): def _add_package(self, tool_details): tool_handle = tool_details["handle"] repository_handle = tool_details["repository"]["handle"] + repository_url = tool_details["repository"]["url"] + index = f"{repository_handle}={repository_url}" add_package_command = [ "uv", "add", - "--extra-index-url", - self.BASE_URL + repository_handle, + "--index", + index, tool_handle, ] add_package_result = subprocess.run( diff --git a/tests/cli/tools/test_main.py b/tests/cli/tools/test_main.py index fda833a85..e4fc19be3 100644 --- a/tests/cli/tools/test_main.py +++ b/tests/cli/tools/test_main.py @@ -75,8 +75,8 @@ def test_install_success(mock_get, mock_subprocess_run): [ "uv", "add", - "--extra-index-url", - "https://app.crewai.com/pypi/sample-repo", + "--index", + "sample-repo=https://example.com/repo", "sample-tool", ], capture_output=False, diff --git a/uv.lock b/uv.lock index 729338bcd..5d4d5b0b7 100644 --- a/uv.lock +++ b/uv.lock @@ -703,7 +703,7 @@ requires-dist = [ { name = "pyvis", specifier = ">=0.3.2" }, { name = "regex", specifier = ">=2024.9.11" }, { name = "tomli-w", specifier = ">=1.1.0" }, - { name = "uv", specifier = ">=0.4.18" }, + { name = "uv", specifier = ">=0.4.25" }, ] [package.metadata.requires-dev] @@ -4542,27 +4542,27 @@ socks = [ [[package]] name = "uv" -version = "0.4.18" +version = "0.4.25" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/7d/60/bf5ad6895740e7269ee2f5cf7515cf2756cc8eb06c07c9783abcf1d7860f/uv-0.4.18.tar.gz", hash = "sha256:954964eff8c7e2bc63dd4beeb8d45bcaddb5149a7ef29a36abd77ec76c8b837e", size = 2008833 } +sdist = { url = "https://files.pythonhosted.org/packages/d0/bc/1a013408b7f9f437385705652f404b6b15127ecf108327d13be493bdfb81/uv-0.4.25.tar.gz", hash = "sha256:d39077cdfe3246885fcdf32e7066ae731a166101d063629f9cea08738f79e6a3", size = 2064863 } wheels = [ - { url = "https://files.pythonhosted.org/packages/9e/f9/b3f093abb8f91e2374461b903a4f5e37e96dd04dbf584e34b79bf9a6bbdf/uv-0.4.18-py3-none-linux_armv6l.whl", hash = "sha256:1944c0ee567ca7db60705c5d213a75b25601094b026cc17af3e704651c1e3753", size = 12264752 }, - { url = "https://files.pythonhosted.org/packages/b6/98/3623ca28954953a5abdc988eb68d0460e1decf37b245c84db2d1323b17f8/uv-0.4.18-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:5234d47abe339c15c318e8b1bbd136ea61c4574503eda6944a5aaea91b7f6775", size = 12488345 }, - { url = "https://files.pythonhosted.org/packages/29/2b/ff62b32b4a7cbfb445156b1d8757f29190f854aa702baa045e8645a19144/uv-0.4.18-py3-none-macosx_11_0_arm64.whl", hash = "sha256:0c4cb31594cb2ed21bd3b603a207e99dfb9610c3db44da9dbbff0f237270f582", size = 11568639 }, - { url = "https://files.pythonhosted.org/packages/bb/7f/49a724b0c8e09fca03c166e7f18ad48c8962c9be543899a27eecc13b8b86/uv-0.4.18-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.musllinux_1_1_aarch64.whl", hash = "sha256:8af0b60adcfa2e87c77a3008d3ed6e0b577c0535468dc58e06f905ccbd27124f", size = 11812252 }, - { url = "https://files.pythonhosted.org/packages/e5/88/0b20af8d76e7b8e6ae19af6d14180a0a9e3c23ef6f3cd38370a2ba663364/uv-0.4.18-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:f043c3c4514c149a00a86c3bf44df43062416d41002114e60df33895e8511c41", size = 12084699 }, - { url = "https://files.pythonhosted.org/packages/a1/fe/afd83b6ed495fe40a4a738cce0de77465af452f8bd58b254a6cf7544a581/uv-0.4.18-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1b59d742b81c7acf75a3aac71d9b24e07407e044bebcf39d3fc3c87094014e20", size = 12793964 }, - { url = "https://files.pythonhosted.org/packages/a6/54/623029d342f68518c25ed8a3863bc43ced0ad39da4dc83b310db3fe0a727/uv-0.4.18-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:fcc606da545d9a5ec5c2209e7eb2a4eb76627ad75df5eb5616c0b40789fe3933", size = 13386984 }, - { url = "https://files.pythonhosted.org/packages/e9/50/eace0e9326318bf278491aafc3d63e8675a3d03472d2bc58ef601564cbb4/uv-0.4.18-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:96c3ccee0fd8cf0a9d679407e157b76db1a854638a4ba4fa14f4d116b4e39b03", size = 13137886 }, - { url = "https://files.pythonhosted.org/packages/f7/f5/f21bec94affe10e677ecbc0cc1b89d766c950dbc8e23df87451c71848c3f/uv-0.4.18-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:df225a568da01f3d7e126d886c3694c5a4a7d8b85162a4d6e97822716ca0e7c4", size = 17098535 }, - { url = "https://files.pythonhosted.org/packages/4e/89/77ad3d48f2ea11fd4e416b8cc1be18b26f189a4f0bf7918ac6fdb4255fa6/uv-0.4.18-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b08564c8c7e8b3665ad1d6c8924d4654451f96c956eb5f3b8ec995c77734163d", size = 12909876 }, - { url = "https://files.pythonhosted.org/packages/ca/29/1f451ef9b2138fdc777e24654da24fa60e42435936d29bcba0fb5bae3c44/uv-0.4.18-py3-none-manylinux_2_28_aarch64.whl", hash = "sha256:4be600474db6733078503012f2811c4383f490f77366e66b5f686316db52c870", size = 11976385 }, - { url = "https://files.pythonhosted.org/packages/f3/ea/4ac40da05e070f411edb4e99f01846aa8694071ce85f4eb83313f2cce423/uv-0.4.18-py3-none-musllinux_1_1_armv7l.whl", hash = "sha256:3e3ade81af961f48517fcd99318192c9c635ef9a38a7ca65026af0c803c71906", size = 12067581 }, - { url = "https://files.pythonhosted.org/packages/cd/49/f6113c4cea8f7ba9e0a70723e8cb3b042c8cb1288f5671594a6b8de491bd/uv-0.4.18-py3-none-musllinux_1_1_i686.whl", hash = "sha256:4ec60141f92c9667548ebad8daf4c13aabdb58b22c21dcd834641e791e55f289", size = 12559831 }, - { url = "https://files.pythonhosted.org/packages/d2/e7/968414391249660bf4375123dd244eef36fc1c1676dcdc719aea1f319bd7/uv-0.4.18-py3-none-musllinux_1_1_ppc64le.whl", hash = "sha256:6566448278b6849846b6c586fc86748c66aa53ed70f5568e713122543cc86a50", size = 14181171 }, - { url = "https://files.pythonhosted.org/packages/bb/ec/1fa1cffaa837df4bfd545818779dc608d0465be5c0e57b4328b5ed91b97f/uv-0.4.18-py3-none-musllinux_1_1_x86_64.whl", hash = "sha256:ade18dbbeb05c8cba4f842cc15b20e59467069183f348844750901227df5008d", size = 13042177 }, - { url = "https://files.pythonhosted.org/packages/31/32/fcd60657f45c072fce9f14916b2fcb876b40d8e3ee0ad1f9f212aecd9bfa/uv-0.4.18-py3-none-win32.whl", hash = "sha256:157e4a2c063b270de348862dd31abfe600d5601183fd2a6efe552840ac179626", size = 12184460 }, - { url = "https://files.pythonhosted.org/packages/36/bd/35de80c6ac6d28383d5e7c91e8cea54b4aae8ae144c3411a16e9d28643c8/uv-0.4.18-py3-none-win_amd64.whl", hash = "sha256:8250148484e1b0f89ec19467946e86ee303619985c23228b5a2f2d94d15c6d8b", size = 13893818 }, + { url = "https://files.pythonhosted.org/packages/84/18/9c9056d373620b1cf5182ce9b2d258e86d117d667cf8883e12870f2a5edf/uv-0.4.25-py3-none-linux_armv6l.whl", hash = "sha256:94fb2b454afa6bdfeeea4b4581c878944ca9cf3a13712e6762f245f5fbaaf952", size = 13028246 }, + { url = "https://files.pythonhosted.org/packages/a1/19/8a3f09aba30ac5433dfecde55d5241a07c96bb12340c3b810bc58188a12e/uv-0.4.25-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:a7c3a18c20ddb527d296d1222bddf42b78031c50b5b4609d426569b5fb61f5b0", size = 13175265 }, + { url = "https://files.pythonhosted.org/packages/e8/c9/2f924bb29bd53c51b839c1c6126bd2cf4c451d4a7d8f34be078f9e31c57e/uv-0.4.25-py3-none-macosx_11_0_arm64.whl", hash = "sha256:18100f0f36419a154306ed6211e3490bf18384cdf3f1a0950848bf64b62fa251", size = 12255610 }, + { url = "https://files.pythonhosted.org/packages/b2/5a/d8f8971aeb3389679505cf633a786cd72a96ce232f80f14cfe5a693b4c64/uv-0.4.25-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.musllinux_1_1_aarch64.whl", hash = "sha256:6e981b1465e30102e41946adede9cb08051a5d70c6daf09f91a7ea84f0b75c08", size = 12506511 }, + { url = "https://files.pythonhosted.org/packages/e3/96/8c73520daeba5022cec8749e44afd4ca9ef774bf728af9c258bddec3577f/uv-0.4.25-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:578ae385fad6bd6f3868828e33d54994c716b315b1bc49106ec1f54c640837e4", size = 12836250 }, + { url = "https://files.pythonhosted.org/packages/67/3d/b0e810d365fb154fe1d380a0f43ee35a683cf9162f2501396d711bec2621/uv-0.4.25-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2d29a78f011ecc2f31c13605acb6574c2894c06d258b0f8d0dbb899986800450", size = 13521303 }, + { url = "https://files.pythonhosted.org/packages/2d/f4/dd3830ec7fc6e7e5237c184f30f2dbfed4f93605e472147eca1373bcc72b/uv-0.4.25-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:ec181be2bda10651a3558156409ac481549983e0276d0e3645e3b1464e7f8715", size = 14105308 }, + { url = "https://files.pythonhosted.org/packages/f4/4e/0fca02f8681e4870beda172552e747e0424f6e9186546b00a5e92525fea9/uv-0.4.25-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:50c7d0d9e7f392f81b13bf3b7e37768d1486f2fc9d533a54982aa0ed11e4db23", size = 13859475 }, + { url = "https://files.pythonhosted.org/packages/33/07/1100e9bc652f2850930f466869515d16ffe9582aaaaa99bac332ebdfe3ea/uv-0.4.25-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2fc35b5273f1e018aecd66b70e0fd7d2eb6698853dde3e2fc644e7ebf9f825b1", size = 18100840 }, + { url = "https://files.pythonhosted.org/packages/fa/98/ba1cb7dd2aa639a064a9e49721e08f12a3424456d60dde1327e7c6437930/uv-0.4.25-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a7022a71ff63a3838796f40e954b76bf7820fc27e96fe002c537e75ff8e34f1d", size = 13645464 }, + { url = "https://files.pythonhosted.org/packages/0d/05/b97fb8c828a070e8291826922b2712d1146b11563b4860bc9ba80f5635d1/uv-0.4.25-py3-none-manylinux_2_28_aarch64.whl", hash = "sha256:e02afb0f6d4b58718347f7d7cfa5a801e985ce42181ba971ed85ef149f6658ca", size = 12694995 }, + { url = "https://files.pythonhosted.org/packages/b3/97/63df050811379130202898f60e735a1a331ba3a93b8aa1e9bb466f533913/uv-0.4.25-py3-none-musllinux_1_1_armv7l.whl", hash = "sha256:3d7680795ea78cdbabbcce73d039b2651cf1fa635ddc1aa3082660f6d6255c50", size = 12831737 }, + { url = "https://files.pythonhosted.org/packages/dc/e0/08352dcffa6e8435328861ea60b2c05e8bd030f1e93998443ba66209db7b/uv-0.4.25-py3-none-musllinux_1_1_i686.whl", hash = "sha256:aae9dcafd20d5ba978c8a4939ab942e8e2e155c109e9945207fbbd81d2892c9e", size = 13273529 }, + { url = "https://files.pythonhosted.org/packages/25/f4/eaf95e5eee4e2e69884df0953d094deae07216f72068ef1df08c0f49841d/uv-0.4.25-py3-none-musllinux_1_1_ppc64le.whl", hash = "sha256:4c55040e67470f2b73e95e432aba06f103a0b348ea0b9c6689b1029c8d9e89fd", size = 15039860 }, + { url = "https://files.pythonhosted.org/packages/69/04/482b1cc9e8d599c7d766c4ba2d7a512ed3989921443792f92f26b8d44fe6/uv-0.4.25-py3-none-musllinux_1_1_x86_64.whl", hash = "sha256:bdbfd0c476b9e80a3f89af96aed6dd7d2782646311317a9c72614ccce99bb2ad", size = 13776302 }, + { url = "https://files.pythonhosted.org/packages/cd/7e/3d1cb735cc3df6341ac884b73eeec1f51a29192721be40be8e9b1d82666d/uv-0.4.25-py3-none-win32.whl", hash = "sha256:7d266e02fefef930609328c31c075084295c3cb472bab3f69549fad4fd9d82b3", size = 12970553 }, + { url = "https://files.pythonhosted.org/packages/04/e9/c00d2bb4a286b13fad0f06488ea9cbe9e76d0efcd81e7a907f72195d5b83/uv-0.4.25-py3-none-win_amd64.whl", hash = "sha256:be2a4fc4fcade9ea5e67e51738c95644360d6e59b6394b74fc579fb617f902f7", size = 14702875 }, ] [[package]] From 71a217b210019cacaeda17b444baa9346c77ce2a Mon Sep 17 00:00:00 2001 From: Sam Date: Tue, 22 Oct 2024 02:49:33 +1100 Subject: [PATCH 006/126] fix(docs): typo (#1470) --- src/crewai/cli/templates/crew/main.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/crewai/cli/templates/crew/main.py b/src/crewai/cli/templates/crew/main.py index a20125637..88edfcbff 100644 --- a/src/crewai/cli/templates/crew/main.py +++ b/src/crewai/cli/templates/crew/main.py @@ -3,7 +3,7 @@ import sys from {{folder_name}}.crew import {{crew_name}}Crew # This main file is intended to be a way for you to run your -# crew locally, so refrain from adding necessary logic into this file. +# crew locally, so refrain from adding unnecessary logic into this file. # Replace with inputs you want to test with, it will automatically # interpolate any tasks and agents information From 6bcb3d108050b9cf8a710886a336a81d4dedb6c3 Mon Sep 17 00:00:00 2001 From: "Brandon Hancock (bhancock_ai)" <109994880+bhancockio@users.noreply.github.com> Date: Mon, 21 Oct 2024 15:26:30 -0400 Subject: [PATCH 007/126] drop unneccesary tests (#1484) * drop uneccesary tests * fix linting --- tests/tools/test_tool_usage.py | 28 ++-------------------------- 1 file changed, 2 insertions(+), 26 deletions(-) diff --git a/tests/tools/test_tool_usage.py b/tests/tools/test_tool_usage.py index d0a6ec0b0..8af9b8abb 100644 --- a/tests/tools/test_tool_usage.py +++ b/tests/tools/test_tool_usage.py @@ -1,12 +1,12 @@ import json import random -from unittest.mock import MagicMock, patch +from unittest.mock import MagicMock import pytest from crewai_tools import BaseTool from pydantic import BaseModel, Field -from crewai import Agent, Crew, Task +from crewai import Agent, Task from crewai.tools.tool_usage import ToolUsage @@ -44,36 +44,12 @@ example_task = Task( ) -def test_random_number_tool_usage(): - crew = Crew( - agents=[example_agent], - tasks=[example_task], - ) - - with patch.object(random, "randint", return_value=42): - result = crew.kickoff() - - assert "42" in result.raw - - def test_random_number_tool_range(): tool = RandomNumberTool() result = tool._run(1, 10) assert 1 <= result <= 10 -def test_random_number_tool_with_crew(): - crew = Crew( - agents=[example_agent], - tasks=[example_task], - ) - - result = crew.kickoff() - - # Check if the result contains a number between 1 and 100 - assert any(str(num) in result.raw for num in range(1, 101)) - - def test_random_number_tool_invalid_range(): tool = RandomNumberTool() with pytest.raises(ValueError): From 093259389e2a3afaa45365ec275fc941c68dc028 Mon Sep 17 00:00:00 2001 From: "Brandon Hancock (bhancock_ai)" <109994880+bhancockio@users.noreply.github.com> Date: Mon, 21 Oct 2024 19:32:55 -0400 Subject: [PATCH 008/126] simplify flow (#1482) * simplify flow * propogate changes * Update docs and scripts * Template fix * make flow kickoff sync * Clean up docs --- docs/concepts/flows.mdx | 200 +++++++----------- src/crewai/cli/cli.py | 8 +- .../cli/{run_flow.py => kickoff_flow.py} | 6 +- src/crewai/cli/templates/flow/main.py | 46 ++-- src/crewai/cli/templates/flow/pyproject.toml | 6 +- src/crewai/flow/flow.py | 5 +- 6 files changed, 111 insertions(+), 160 deletions(-) rename src/crewai/cli/{run_flow.py => kickoff_flow.py} (78%) diff --git a/docs/concepts/flows.mdx b/docs/concepts/flows.mdx index 65cbb95c2..dee6fd4fa 100644 --- a/docs/concepts/flows.mdx +++ b/docs/concepts/flows.mdx @@ -23,9 +23,9 @@ Flows allow you to create structured, event-driven workflows. They provide a sea Let's create a simple Flow where you will use OpenAI to generate a random city in one task and then use that city to generate a fun fact in another task. ```python Code -import asyncio from crewai.flow.flow import Flow, listen, start +from dotenv import load_dotenv from litellm import completion @@ -67,19 +67,19 @@ class ExampleFlow(Flow): return fun_fact -async def main(): - flow = ExampleFlow() - result = await flow.kickoff() - print(f"Generated fun fact: {result}") +flow = ExampleFlow() +result = flow.kickoff() -asyncio.run(main()) +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. When you run the Flow, it will generate a random city and then generate a fun fact about that city. The output will be printed to the console. +**Note:** Ensure you have set up your `.env` file to store your `OPENAI_API_KEY`. This key is necessary for authenticating requests to the OpenAI API. + ### @start() The `@start()` decorator is used to mark a method as the starting point of a Flow. When a Flow is started, all the methods decorated with `@start()` are executed in parallel. You can have multiple start methods in a Flow, and they will all be executed when the Flow is started. @@ -119,7 +119,6 @@ Here's how you can access the final output: ```python Code -import asyncio from crewai.flow.flow import Flow, listen, start class OutputExampleFlow(Flow): @@ -131,26 +130,24 @@ class OutputExampleFlow(Flow): def second_method(self, first_output): return f"Second method received: {first_output}" -async def main(): - flow = OutputExampleFlow() - final_output = await flow.kickoff() - print("---- Final Output ----") - print(final_output) -asyncio.run(main()) -``` +flow = OutputExampleFlow() +final_output = flow.kickoff() + +print("---- Final Output ----") +print(final_output) +```` ``` text Output ---- Final Output ---- Second method received: Output from first_method -``` +```` -In this example, the `second_method` is the last method to complete, so its output will be the final output of the Flow. +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. - #### Accessing and Updating State In addition to retrieving the final output, you can also access and update the state within your Flow. The state can be used to store and share data between different methods in the Flow. After the Flow has run, you can access the state to retrieve any information that was added or updated during the execution. @@ -160,7 +157,6 @@ Here's an example of how to update and access the state: ```python Code -import asyncio from crewai.flow.flow import Flow, listen, start from pydantic import BaseModel @@ -181,42 +177,38 @@ class StateExampleFlow(Flow[ExampleState]): self.state.counter += 1 return self.state.message -async def main(): - flow = StateExampleFlow() - final_output = await flow.kickoff() - print(f"Final Output: {final_output}") - print("Final State:") - print(flow.state) - -asyncio.run(main()) +flow = StateExampleFlow() +final_output = flow.kickoff() +print(f"Final Output: {final_output}") +print("Final State:") +print(flow.state) ``` -``` text Output +```text Output Final Output: Hello from first_method - updated by second_method Final State: counter=2 message='Hello from first_method - updated by second_method' ``` + -In this example, the state is updated by both `first_method` and `second_method`. +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. -By ensuring that the final method's output is returned and providing access to the state, CrewAI Flows make it easy to integrate the results of your AI workflows into larger applications or systems, +By ensuring that the final method's output is returned and providing access to the state, CrewAI Flows make it easy to integrate the results of your AI workflows into larger applications or systems, while also maintaining and accessing the state throughout the Flow's execution. ## Flow State Management -Managing state effectively is crucial for building reliable and maintainable AI workflows. CrewAI Flows provides robust mechanisms for both unstructured and structured state management, +Managing state effectively is crucial for building reliable and maintainable AI workflows. CrewAI Flows provides robust mechanisms for both unstructured and structured state management, allowing developers to choose the approach that best fits their application's needs. ### Unstructured State Management -In unstructured state management, all state is stored in the `state` attribute of the `Flow` class. +In unstructured state management, all state is stored in the `state` attribute of the `Flow` class. This approach offers flexibility, enabling developers to add or modify state attributes on the fly without defining a strict schema. ```python Code -import asyncio - from crewai.flow.flow import Flow, listen, start class UntructuredExampleFlow(Flow): @@ -239,12 +231,8 @@ class UntructuredExampleFlow(Flow): print(f"State after third_method: {self.state}") -async def main(): - flow = UntructuredExampleFlow() - await flow.kickoff() - - -asyncio.run(main()) +flow = UntructuredExampleFlow() +flow.kickoff() ``` **Key Points:** @@ -254,12 +242,10 @@ asyncio.run(main()) ### Structured State Management -Structured state management leverages predefined schemas to ensure consistency and type safety across the workflow. +Structured state management leverages predefined schemas to ensure consistency and type safety across the workflow. By using models like Pydantic's `BaseModel`, developers can define the exact shape of the state, enabling better validation and auto-completion in development environments. ```python Code -import asyncio - from crewai.flow.flow import Flow, listen, start from pydantic import BaseModel @@ -288,12 +274,8 @@ class StructuredExampleFlow(Flow[ExampleState]): print(f"State after third_method: {self.state}") -async def main(): - flow = StructuredExampleFlow() - await flow.kickoff() - - -asyncio.run(main()) +flow = StructuredExampleFlow() +flow.kickoff() ``` **Key Points:** @@ -326,7 +308,6 @@ The `or_` function in Flows allows you to listen to multiple methods and trigger ```python Code -import asyncio from crewai.flow.flow import Flow, listen, or_, start class OrExampleFlow(Flow): @@ -344,22 +325,19 @@ class OrExampleFlow(Flow): print(f"Logger: {result}") -async def main(): - flow = OrExampleFlow() - await flow.kickoff() - -asyncio.run(main()) +flow = OrExampleFlow() +flow.kickoff() ``` -``` text Output +```text Output Logger: Hello from the start method Logger: Hello from the second method ``` -When you run this Flow, the `logger` method will be triggered by the output of either the `start_method` or the `second_method`. +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. ### Conditional Logic: `and` @@ -369,7 +347,6 @@ The `and_` function in Flows allows you to listen to multiple methods and trigge ```python Code -import asyncio from crewai.flow.flow import Flow, and_, listen, start class AndExampleFlow(Flow): @@ -387,34 +364,28 @@ class AndExampleFlow(Flow): print("---- Logger ----") print(self.state) - -async def main(): - flow = AndExampleFlow() - await flow.kickoff() - - -asyncio.run(main()) +flow = AndExampleFlow() +flow.kickoff() ``` -``` text Output +```text Output ---- Logger ---- {'greeting': 'Hello from the start method', 'joke': 'What do computers eat? Microchips.'} ``` -When you run this Flow, the `logger` method will be triggered only when both the `start_method` and the `second_method` emit an output. +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. ### Router -The `@router()` decorator in Flows allows you to define conditional routing logic based on the output of a method. +The `@router()` decorator in Flows allows you to define conditional routing logic based on the output of a method. You can specify different routes based on the output of the method, allowing you to control the flow of execution dynamically. ```python Code -import asyncio import random from crewai.flow.flow import Flow, listen, router, start from pydantic import BaseModel @@ -446,15 +417,11 @@ class RouterFlow(Flow[ExampleState]): print("Fourth method running") -async def main(): - flow = RouterFlow() - await flow.kickoff() - - -asyncio.run(main()) +flow = RouterFlow() +flow.kickoff() ``` -``` text Output +```text Output Starting the structured flow Third method running Fourth method running @@ -462,16 +429,16 @@ Fourth method running -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"`. +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"`. The `third_method` and `fourth_method` listen to the output of the `second_method` and execute based on the returned value. When you run this Flow, the output will change based on the random boolean value generated by the `start_method`. ## Adding Crews to Flows -Creating a flow with multiple crews in CrewAI is straightforward. +Creating a flow with multiple crews in CrewAI is straightforward. You can generate a new CrewAI project that includes all the scaffolding needed to create a flow with multiple crews by running the following command: @@ -485,22 +452,21 @@ This command will generate a new CrewAI project with the necessary folder struct After running the `crewai create flow name_of_flow` command, you will see a folder structure similar to the following: -| Directory/File | Description | -|:---------------------------------|:------------------------------------------------------------------| -| `name_of_flow/` | Root directory for the flow. | -| ├── `crews/` | Contains directories for specific crews. | -| │ └── `poem_crew/` | Directory for the "poem_crew" with its configurations and scripts.| -| │ ├── `config/` | Configuration files directory for the "poem_crew". | -| │ │ ├── `agents.yaml` | YAML file defining the agents for "poem_crew". | -| │ │ └── `tasks.yaml` | YAML file defining the tasks for "poem_crew". | -| │ ├── `poem_crew.py` | Script for "poem_crew" functionality. | -| ├── `tools/` | Directory for additional tools used in the flow. | -| │ └── `custom_tool.py` | Custom tool implementation. | -| ├── `main.py` | Main script for running the flow. | -| ├── `README.md` | Project description and instructions. | -| ├── `pyproject.toml` | Configuration file for project dependencies and settings. | -| └── `.gitignore` | Specifies files and directories to ignore in version control. | - +| Directory/File | Description | +| :--------------------- | :----------------------------------------------------------------- | +| `name_of_flow/` | Root directory for the flow. | +| ├── `crews/` | Contains directories for specific crews. | +| │ └── `poem_crew/` | Directory for the "poem_crew" with its configurations and scripts. | +| │ ├── `config/` | Configuration files directory for the "poem_crew". | +| │ │ ├── `agents.yaml` | YAML file defining the agents for "poem_crew". | +| │ │ └── `tasks.yaml` | YAML file defining the tasks for "poem_crew". | +| │ ├── `poem_crew.py` | Script for "poem_crew" functionality. | +| ├── `tools/` | Directory for additional tools used in the flow. | +| │ └── `custom_tool.py` | Custom tool implementation. | +| ├── `main.py` | Main script for running the flow. | +| ├── `README.md` | Project description and instructions. | +| ├── `pyproject.toml` | Configuration file for project dependencies and settings. | +| └── `.gitignore` | Specifies files and directories to ignore in version control. | ### Building Your Crews @@ -520,7 +486,6 @@ Here's an example of how you can connect the `poem_crew` in the `main.py` file: ```python Code #!/usr/bin/env python -import asyncio from random import randint from pydantic import BaseModel @@ -536,14 +501,12 @@ class PoemFlow(Flow[PoemState]): @start() def generate_sentence_count(self): print("Generating sentence count") - # Generate a number between 1 and 5 self.state.sentence_count = randint(1, 5) @listen(generate_sentence_count) def generate_poem(self): print("Generating poem") - poem_crew = PoemCrew().crew() - result = poem_crew.kickoff(inputs={"sentence_count": self.state.sentence_count}) + result = PoemCrew().crew().kickoff(inputs={"sentence_count": self.state.sentence_count}) print("Poem generated", result.raw) self.state.poem = result.raw @@ -554,18 +517,17 @@ class PoemFlow(Flow[PoemState]): with open("poem.txt", "w") as f: f.write(self.state.poem) -async def run(): - """ - Run the flow. - """ +def kickoff(): poem_flow = PoemFlow() - await poem_flow.kickoff() + poem_flow.kickoff() -def main(): - asyncio.run(run()) + +def plot(): + poem_flow = PoemFlow() + poem_flow.plot() if __name__ == "__main__": - main() + kickoff() ``` 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. @@ -587,13 +549,13 @@ source .venv/bin/activate After activating the virtual environment, you can run the flow by executing one of the following commands: ```bash -crewai flow run +crewai flow kickoff ``` or ```bash -uv run run_flow +uv run kickoff ``` The flow will execute, and you should see the output in the console. @@ -657,13 +619,13 @@ By exploring these examples, you can gain insights into how to leverage CrewAI F Also, check out our YouTube video on how to use flows in CrewAI below! - \ No newline at end of file + diff --git a/src/crewai/cli/cli.py b/src/crewai/cli/cli.py index 55c8e5b43..6c5263345 100644 --- a/src/crewai/cli/cli.py +++ b/src/crewai/cli/cli.py @@ -14,11 +14,11 @@ from .authentication.main import AuthenticationCommand from .deploy.main import DeployCommand from .evaluate_crew import evaluate_crew from .install_crew import install_crew +from .kickoff_flow import kickoff_flow from .plot_flow import plot_flow from .replay_from_task import replay_task_command from .reset_memories_command import reset_memories_command from .run_crew import run_crew -from .run_flow import run_flow from .tools.main import ToolCommand from .train_crew import train_crew from .update_crew import update_crew @@ -304,11 +304,11 @@ def flow(): pass -@flow.command(name="run") +@flow.command(name="kickoff") def flow_run(): - """Run the Flow.""" + """Kickoff the Flow.""" click.echo("Running the Flow") - run_flow() + kickoff_flow() @flow.command(name="plot") diff --git a/src/crewai/cli/run_flow.py b/src/crewai/cli/kickoff_flow.py similarity index 78% rename from src/crewai/cli/run_flow.py rename to src/crewai/cli/kickoff_flow.py index 2900e59b3..2123a6c15 100644 --- a/src/crewai/cli/run_flow.py +++ b/src/crewai/cli/kickoff_flow.py @@ -3,11 +3,11 @@ import subprocess import click -def run_flow() -> None: +def kickoff_flow() -> None: """ - Run the flow by running a command in the UV environment. + Kickoff the flow by running a command in the UV environment. """ - command = ["uv", "run", "run_flow"] + command = ["uv", "run", "kickoff"] try: result = subprocess.run(command, capture_output=False, text=True, check=True) diff --git a/src/crewai/cli/templates/flow/main.py b/src/crewai/cli/templates/flow/main.py index 38d2d8736..73684051a 100644 --- a/src/crewai/cli/templates/flow/main.py +++ b/src/crewai/cli/templates/flow/main.py @@ -1,65 +1,53 @@ #!/usr/bin/env python -import asyncio from random import randint from pydantic import BaseModel + from crewai.flow.flow import Flow, listen, start + from .crews.poem_crew.poem_crew import PoemCrew + class PoemState(BaseModel): sentence_count: int = 1 poem: str = "" + class PoemFlow(Flow[PoemState]): @start() def generate_sentence_count(self): print("Generating sentence count") - # Generate a number between 1 and 5 - self.state.sentence_count = randint(1, 5) + self.state.sentence_count = randint(1, 5) @listen(generate_sentence_count) def generate_poem(self): print("Generating poem") - print(f"State before poem: {self.state}") - result = PoemCrew().crew().kickoff(inputs={"sentence_count": self.state.sentence_count}) - + result = ( + PoemCrew() + .crew() + .kickoff(inputs={"sentence_count": self.state.sentence_count}) + ) + print("Poem generated", result.raw) self.state.poem = result.raw - - print(f"State after generate_poem: {self.state}") @listen(generate_poem) def save_poem(self): print("Saving poem") - print(f"State before save_poem: {self.state}") with open("poem.txt", "w") as f: f.write(self.state.poem) - print(f"State after save_poem: {self.state}") -async def run_flow(): - """ - Run the flow. - """ + +def kickoff(): poem_flow = PoemFlow() - await poem_flow.kickoff() + poem_flow.kickoff() -async def plot_flow(): - """ - Plot the flow. - """ + +def plot(): poem_flow = PoemFlow() poem_flow.plot() -def main(): - asyncio.run(run_flow()) - - -def plot(): - asyncio.run(plot_flow()) - - - if __name__ == "__main__": - main() + kickoff() diff --git a/src/crewai/cli/templates/flow/pyproject.toml b/src/crewai/cli/templates/flow/pyproject.toml index 490d66346..f93390741 100644 --- a/src/crewai/cli/templates/flow/pyproject.toml +++ b/src/crewai/cli/templates/flow/pyproject.toml @@ -6,13 +6,11 @@ authors = [{ name = "Your Name", email = "you@example.com" }] requires-python = ">=3.10,<=3.13" dependencies = [ "crewai[tools]>=0.74.2,<1.0.0", - "asyncio" ] [project.scripts] -{{folder_name}} = "{{folder_name}}.main:main" -run_flow = "{{folder_name}}.main:main" -plot_flow = "{{folder_name}}.main:plot" +kickoff = "{{folder_name}}.main:kickoff" +plot = "{{folder_name}}.main:plot" [build-system] requires = ["hatchling"] diff --git a/src/crewai/flow/flow.py b/src/crewai/flow/flow.py index 66fa49659..de9b2eeb2 100644 --- a/src/crewai/flow/flow.py +++ b/src/crewai/flow/flow.py @@ -190,7 +190,10 @@ class Flow(Generic[T], metaclass=FlowMeta): """Returns the list of all outputs from executed methods.""" return self._method_outputs - async def kickoff(self) -> Any: + def kickoff(self) -> Any: + return asyncio.run(self.kickoff_async()) + + async def kickoff_async(self) -> Any: if not self._start_methods: raise ValueError("No start method defined") From 873191533097bbe0718e1b4b353ec65bb8b7bd68 Mon Sep 17 00:00:00 2001 From: Tony Kipkemboi Date: Tue, 22 Oct 2024 13:41:29 -0400 Subject: [PATCH 009/126] Add Cerebras LLM example configuration to LLM docs (#1488) --- docs/concepts/llms.mdx | 26 ++++++++++++++++++++++++-- 1 file changed, 24 insertions(+), 2 deletions(-) diff --git a/docs/concepts/llms.mdx b/docs/concepts/llms.mdx index 728c88c36..90e86adaf 100644 --- a/docs/concepts/llms.mdx +++ b/docs/concepts/llms.mdx @@ -62,6 +62,8 @@ os.environ["OPENAI_API_BASE"] = "https://api.your-provider.com/v1" 2. Using LLM class attributes: ```python Code +from crewai import LLM + llm = LLM( model="custom-model-name", api_key="your-api-key", @@ -95,9 +97,11 @@ When configuring an LLM for your agent, you have access to a wide range of param | **api_key** | `str` | Your API key for authentication. | -Example: +## OpenAI Example Configuration ```python Code +from crewai import LLM + llm = LLM( model="gpt-4", temperature=0.8, @@ -112,15 +116,31 @@ llm = LLM( ) agent = Agent(llm=llm, ...) ``` + +## Cerebras Example Configuration + +```python Code +from crewai import LLM + +llm = LLM( + model="cerebras/llama-3.1-70b", + base_url="https://api.cerebras.ai/v1", + api_key="your-api-key-here" +) +agent = Agent(llm=llm, ...) +``` + ## Using Ollama (Local LLMs) -crewAI supports using Ollama for running open-source models locally: +CrewAI supports using Ollama for running open-source models locally: 1. Install Ollama: [ollama.ai](https://ollama.ai/) 2. Run a model: `ollama run llama2` 3. Configure agent: ```python Code +from crewai import LLM + agent = Agent( llm=LLM(model="ollama/llama3.1", base_url="http://localhost:11434"), ... @@ -132,6 +152,8 @@ agent = Agent( You can change the base API URL for any LLM provider by setting the `base_url` parameter: ```python Code +from crewai import LLM + llm = LLM( model="custom-model-name", base_url="https://api.your-provider.com/v1", From 4687779702e93f5b4163ea858557e6a4ae064b66 Mon Sep 17 00:00:00 2001 From: Lorenze Jay <63378463+lorenzejay@users.noreply.github.com> Date: Tue, 22 Oct 2024 12:30:30 -0700 Subject: [PATCH 010/126] ensure original embedding config works (#1476) * ensure original embedding config works * some fixes * raise error on unsupported provider * WIP: brandons notes * fixes * rm prints * fixed docs * fixed run types * updates to add more docs and correct imports with huggingface embedding server enabled --------- Co-authored-by: Brandon Hancock --- docs/concepts/memory.mdx | 86 +++++++++++++++++--- src/crewai/memory/entity/entity_memory.py | 2 +- src/crewai/memory/storage/rag_storage.py | 96 +++++++++++++++++++++-- 3 files changed, 166 insertions(+), 18 deletions(-) diff --git a/docs/concepts/memory.mdx b/docs/concepts/memory.mdx index b07096442..de1fb3510 100644 --- a/docs/concepts/memory.mdx +++ b/docs/concepts/memory.mdx @@ -105,9 +105,48 @@ my_crew = Crew( process=Process.sequential, memory=True, verbose=True, - embedder=embedding_functions.OpenAIEmbeddingFunction( - api_key=os.getenv("OPENAI_API_KEY"), model_name="text-embedding-3-small" - ) + embedder={ + "provider": "openai", + "config": { + "model": 'text-embedding-3-small' + } + } +) +``` +Alternatively, you can directly pass the OpenAIEmbeddingFunction to the embedder parameter. + +Example: +```python Code +from crewai import Crew, Agent, Task, Process +from chromadb.utils.embedding_functions.openai_embedding_function import OpenAIEmbeddingFunction + +my_crew = Crew( + agents=[...], + tasks=[...], + process=Process.sequential, + memory=True, + verbose=True, + embedder=OpenAIEmbeddingFunction(api_key=os.getenv("OPENAI_API_KEY"), model_name="text-embedding-3-small"), +) +``` + +### Using Ollama embeddings + +```python Code +from crewai import Crew, Agent, Task, Process + +my_crew = Crew( + agents=[...], + tasks=[...], + process=Process.sequential, + memory=True, + verbose=True, + embedder={ + "provider": "ollama", + "config": { + "model": "mxbai-embed-large" + } + } ) ``` @@ -122,10 +161,13 @@ my_crew = Crew( process=Process.sequential, memory=True, verbose=True, - embedder=embedding_functions.OpenAIEmbeddingFunction( - api_key=os.getenv("OPENAI_API_KEY"), - model_name="text-embedding-ada-002" - ) + embedder={ + "provider": "google", + "config": { + "api_key": "", + "model_name": "" + } + } ) ``` @@ -181,10 +223,32 @@ my_crew = Crew( process=Process.sequential, memory=True, verbose=True, - embedder=embedding_functions.CohereEmbeddingFunction( - api_key=YOUR_API_KEY, - model_name="" - ) + embedder={ + "provider": "cohere", + "config": { + "api_key": "YOUR_API_KEY", + "model_name": "" + } + } +) +``` +### Using HuggingFace embeddings + +```python Code +from crewai import Crew, Agent, Task, Process + +my_crew = Crew( + agents=[...], + tasks=[...], + process=Process.sequential, + memory=True, + verbose=True, + embedder={ + "provider": "huggingface", + "config": { + "api_url": "", + } + } ) ``` diff --git a/src/crewai/memory/entity/entity_memory.py b/src/crewai/memory/entity/entity_memory.py index d4f8d9aae..4de0594c7 100644 --- a/src/crewai/memory/entity/entity_memory.py +++ b/src/crewai/memory/entity/entity_memory.py @@ -16,7 +16,7 @@ class EntityMemory(Memory): if storage else RAGStorage( type="entities", - allow_reset=False, + allow_reset=True, embedder_config=embedder_config, crew=crew, ) diff --git a/src/crewai/memory/storage/rag_storage.py b/src/crewai/memory/storage/rag_storage.py index 8d45d9f5a..db98c0036 100644 --- a/src/crewai/memory/storage/rag_storage.py +++ b/src/crewai/memory/storage/rag_storage.py @@ -8,6 +8,9 @@ from typing import Any, Dict, List, Optional from crewai.memory.storage.base_rag_storage import BaseRAGStorage from crewai.utilities.paths import db_storage_path from chromadb.api import ClientAPI +from chromadb.api.types import validate_embedding_function +from chromadb import Documents, EmbeddingFunction, Embeddings +from typing import cast @contextlib.contextmanager @@ -41,16 +44,93 @@ class RAGStorage(BaseRAGStorage): self.agents = agents self.type = type - self.embedder_config = embedder_config or self._create_embedding_function() + self.allow_reset = allow_reset self._initialize_app() + def _set_embedder_config(self): + import chromadb.utils.embedding_functions as embedding_functions + + if self.embedder_config is None: + self.embedder_config = self._create_default_embedding_function() + + if isinstance(self.embedder_config, dict): + provider = self.embedder_config.get("provider") + config = self.embedder_config.get("config", {}) + model_name = config.get("model") + if provider == "openai": + self.embedder_config = embedding_functions.OpenAIEmbeddingFunction( + api_key=config.get("api_key") or os.getenv("OPENAI_API_KEY"), + model_name=model_name, + ) + elif provider == "azure": + self.embedder_config = embedding_functions.OpenAIEmbeddingFunction( + api_key=config.get("api_key"), + api_base=config.get("api_base"), + api_type=config.get("api_type", "azure"), + api_version=config.get("api_version"), + model_name=model_name, + ) + elif provider == "ollama": + from openai import OpenAI + + class OllamaEmbeddingFunction(EmbeddingFunction): + def __call__(self, input: Documents) -> Embeddings: + client = OpenAI( + base_url="http://localhost:11434/v1", + api_key=config.get("api_key", "ollama"), + ) + try: + response = client.embeddings.create( + input=input, model=model_name + ) + embeddings = [item.embedding for item in response.data] + return cast(Embeddings, embeddings) + except Exception as e: + raise e + + self.embedder_config = OllamaEmbeddingFunction() + elif provider == "vertexai": + self.embedder_config = ( + embedding_functions.GoogleVertexEmbeddingFunction( + model_name=model_name, + api_key=config.get("api_key"), + ) + ) + elif provider == "google": + self.embedder_config = ( + embedding_functions.GoogleGenerativeAiEmbeddingFunction( + model_name=model_name, + api_key=config.get("api_key"), + ) + ) + elif provider == "cohere": + self.embedder_config = embedding_functions.CohereEmbeddingFunction( + model_name=model_name, + api_key=config.get("api_key"), + ) + elif provider == "huggingface": + self.embedder_config = embedding_functions.HuggingFaceEmbeddingServer( + url=config.get("api_url"), + ) + else: + raise Exception( + f"Unsupported embedding provider: {provider}, supported providers: [openai, azure, ollama, vertexai, google, cohere, huggingface]" + ) + else: + validate_embedding_function(self.embedder_config) # type: ignore # used for validating embedder_config if defined a embedding function/class + self.embedder_config = self.embedder_config + def _initialize_app(self): import chromadb + from chromadb.config import Settings + self._set_embedder_config() chroma_client = chromadb.PersistentClient( - path=f"{db_storage_path()}/{self.type}/{self.agents}" + path=f"{db_storage_path()}/{self.type}/{self.agents}", + settings=Settings(allow_reset=self.allow_reset), ) + self.app = chroma_client try: @@ -122,11 +202,15 @@ class RAGStorage(BaseRAGStorage): if self.app: self.app.reset() except Exception as e: - raise Exception( - f"An error occurred while resetting the {self.type} memory: {e}" - ) + if "attempt to write a readonly database" in str(e): + # Ignore this specific error + pass + else: + raise Exception( + f"An error occurred while resetting the {self.type} memory: {e}" + ) - def _create_embedding_function(self): + def _create_default_embedding_function(self): import chromadb.utils.embedding_functions as embedding_functions return embedding_functions.OpenAIEmbeddingFunction( From 9cd4ff05c997fc77c0418c50e6a607419cd42ac2 Mon Sep 17 00:00:00 2001 From: "Brandon Hancock (bhancock_ai)" <109994880+bhancockio@users.noreply.github.com> Date: Tue, 22 Oct 2024 21:31:44 -0400 Subject: [PATCH 011/126] use copy to split testing and training on crews (#1491) * use copy to split testing and training on crews * make tests handle new copy functionality on train and test * fix last test * fix test --- src/crewai/crew.py | 19 ++++++---- tests/crew_test.py | 94 +++++++++++++++++----------------------------- 2 files changed, 45 insertions(+), 68 deletions(-) diff --git a/src/crewai/crew.py b/src/crewai/crew.py index 5450e6e67..377b6fec9 100644 --- a/src/crewai/crew.py +++ b/src/crewai/crew.py @@ -435,15 +435,16 @@ class Crew(BaseModel): self, n_iterations: int, filename: str, inputs: Optional[Dict[str, Any]] = {} ) -> None: """Trains the crew for a given number of iterations.""" - self._setup_for_training(filename) + train_crew = self.copy() + train_crew._setup_for_training(filename) for n_iteration in range(n_iterations): - self._train_iteration = n_iteration - self.kickoff(inputs=inputs) + train_crew._train_iteration = n_iteration + train_crew.kickoff(inputs=inputs) training_data = CrewTrainingHandler(TRAINING_DATA_FILE).load() - for agent in self.agents: + for agent in train_crew.agents: result = TaskEvaluator(agent).evaluate_training_data( training_data=training_data, agent_id=str(agent.id) ) @@ -987,17 +988,19 @@ class Crew(BaseModel): inputs: Optional[Dict[str, Any]] = None, ) -> None: """Test and evaluate the Crew with the given inputs for n iterations concurrently using concurrent.futures.""" - self._test_execution_span = self._telemetry.test_execution_span( - self, + test_crew = self.copy() + + self._test_execution_span = test_crew._telemetry.test_execution_span( + test_crew, n_iterations, inputs, openai_model_name, # type: ignore[arg-type] ) # type: ignore[arg-type] - evaluator = CrewEvaluator(self, openai_model_name) # type: ignore[arg-type] + evaluator = CrewEvaluator(test_crew, openai_model_name) # type: ignore[arg-type] for i in range(1, n_iterations + 1): evaluator.set_iteration(i) - self.kickoff(inputs=inputs) + test_crew.kickoff(inputs=inputs) evaluator.print_crew_evaluation_result() diff --git a/tests/crew_test.py b/tests/crew_test.py index c01e84f80..6444b781c 100644 --- a/tests/crew_test.py +++ b/tests/crew_test.py @@ -9,6 +9,7 @@ from unittest.mock import MagicMock, patch import instructor import pydantic_core import pytest + from crewai.agent import Agent from crewai.agents.cache import CacheHandler from crewai.crew import Crew @@ -497,6 +498,7 @@ def test_cache_hitting_between_agents(): @pytest.mark.vcr(filter_headers=["authorization"]) def test_api_calls_throttling(capsys): from unittest.mock import patch + from crewai_tools import tool @tool @@ -779,11 +781,14 @@ def test_async_task_execution_call_count(): list_important_history.output = mock_task_output write_article.output = mock_task_output - with patch.object( - Task, "execute_sync", return_value=mock_task_output - ) as mock_execute_sync, patch.object( - Task, "execute_async", return_value=mock_future - ) as mock_execute_async: + with ( + patch.object( + Task, "execute_sync", return_value=mock_task_output + ) as mock_execute_sync, + patch.object( + Task, "execute_async", return_value=mock_future + ) as mock_execute_async, + ): crew.kickoff() assert mock_execute_async.call_count == 2 @@ -1105,6 +1110,7 @@ def test_dont_set_agents_step_callback_if_already_set(): @pytest.mark.vcr(filter_headers=["authorization"]) def test_crew_function_calling_llm(): from unittest.mock import patch + from crewai_tools import tool llm = "gpt-4o" @@ -1448,52 +1454,6 @@ def test_crew_does_not_interpolate_without_inputs(): interpolate_task_inputs.assert_not_called() -# def test_crew_partial_inputs(): -# agent = Agent( -# role="{topic} Researcher", -# goal="Express hot takes on {topic}.", -# backstory="You have a lot of experience with {topic}.", -# ) - -# task = Task( -# description="Give me an analysis around {topic}.", -# expected_output="{points} bullet points about {topic}.", -# ) - -# crew = Crew(agents=[agent], tasks=[task], inputs={"topic": "AI"}) -# inputs = {"topic": "AI"} -# crew._interpolate_inputs(inputs=inputs) # Manual call for now - -# assert crew.tasks[0].description == "Give me an analysis around AI." -# assert crew.tasks[0].expected_output == "{points} bullet points about AI." -# assert crew.agents[0].role == "AI Researcher" -# assert crew.agents[0].goal == "Express hot takes on AI." -# assert crew.agents[0].backstory == "You have a lot of experience with AI." - - -# def test_crew_invalid_inputs(): -# agent = Agent( -# role="{topic} Researcher", -# goal="Express hot takes on {topic}.", -# backstory="You have a lot of experience with {topic}.", -# ) - -# task = Task( -# description="Give me an analysis around {topic}.", -# expected_output="{points} bullet points about {topic}.", -# ) - -# crew = Crew(agents=[agent], tasks=[task], inputs={"subject": "AI"}) -# inputs = {"subject": "AI"} -# crew._interpolate_inputs(inputs=inputs) # Manual call for now - -# assert crew.tasks[0].description == "Give me an analysis around {topic}." -# assert crew.tasks[0].expected_output == "{points} bullet points about {topic}." -# assert crew.agents[0].role == "{topic} Researcher" -# assert crew.agents[0].goal == "Express hot takes on {topic}." -# assert crew.agents[0].backstory == "You have a lot of experience with {topic}." - - def test_task_callback_on_crew(): from unittest.mock import MagicMock, patch @@ -1770,7 +1730,10 @@ def test_manager_agent_with_tools_raises_exception(): @patch("crewai.crew.Crew.kickoff") @patch("crewai.crew.CrewTrainingHandler") @patch("crewai.crew.TaskEvaluator") -def test_crew_train_success(task_evaluator, crew_training_handler, kickoff): +@patch("crewai.crew.Crew.copy") +def test_crew_train_success( + copy_mock, task_evaluator, crew_training_handler, kickoff_mock +): task = Task( description="Come up with a list of 5 interesting ideas to explore for an article, then write one 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.", expected_output="5 bullet points with a paragraph for each idea.", @@ -1781,9 +1744,19 @@ def test_crew_train_success(task_evaluator, crew_training_handler, kickoff): agents=[researcher, writer], tasks=[task], ) + + # Create a mock for the copied crew + copy_mock.return_value = crew + crew.train( n_iterations=2, inputs={"topic": "AI"}, filename="trained_agents_data.pkl" ) + + # Ensure kickoff is called on the copied crew + kickoff_mock.assert_has_calls( + [mock.call(inputs={"topic": "AI"}), mock.call(inputs={"topic": "AI"})] + ) + task_evaluator.assert_has_calls( [ mock.call(researcher), @@ -1822,10 +1795,6 @@ def test_crew_train_success(task_evaluator, crew_training_handler, kickoff): ] ) - kickoff.assert_has_calls( - [mock.call(inputs={"topic": "AI"}), mock.call(inputs={"topic": "AI"})] - ) - def test_crew_train_error(): task = Task( @@ -1840,7 +1809,7 @@ def test_crew_train_error(): ) with pytest.raises(TypeError) as e: - crew.train() + crew.train() # type: ignore purposefully throwing err assert "train() missing 1 required positional argument: 'n_iterations'" in str( e ) @@ -2536,8 +2505,9 @@ def test_conditional_should_execute(): @mock.patch("crewai.crew.CrewEvaluator") +@mock.patch("crewai.crew.Crew.copy") @mock.patch("crewai.crew.Crew.kickoff") -def test_crew_testing_function(mock_kickoff, crew_evaluator): +def test_crew_testing_function(kickoff_mock, copy_mock, crew_evaluator): task = Task( description="Come up with a list of 5 interesting ideas to explore for an article, then write one 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.", expected_output="5 bullet points with a paragraph for each idea.", @@ -2548,11 +2518,15 @@ def test_crew_testing_function(mock_kickoff, crew_evaluator): agents=[researcher], tasks=[task], ) + + # Create a mock for the copied crew + copy_mock.return_value = crew + n_iterations = 2 crew.test(n_iterations, openai_model_name="gpt-4o-mini", inputs={"topic": "AI"}) - assert len(mock_kickoff.mock_calls) == n_iterations - mock_kickoff.assert_has_calls( + # Ensure kickoff is called on the copied crew + kickoff_mock.assert_has_calls( [mock.call(inputs={"topic": "AI"}), mock.call(inputs={"topic": "AI"})] ) From b8a3c2974508a04d250acda40cd44eb4c87d1286 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Moura?= Date: Wed, 23 Oct 2024 05:34:34 -0300 Subject: [PATCH 012/126] preparing new verison --- pyproject.toml | 2 +- src/crewai/__init__.py | 2 +- src/crewai/cli/cli.py | 5 +++-- src/crewai/cli/create_crew.py | 17 +++++++++-------- src/crewai/cli/templates/crew/pyproject.toml | 2 +- src/crewai/cli/templates/flow/pyproject.toml | 2 +- .../cli/templates/pipeline/pyproject.toml | 2 +- .../templates/pipeline_router/pyproject.toml | 2 +- src/crewai/cli/templates/tool/pyproject.toml | 2 +- uv.lock | 2 +- 10 files changed, 20 insertions(+), 18 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index d7920c7b0..56fdee20d 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "crewai" -version = "0.74.2" +version = "0.75.1" 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" diff --git a/src/crewai/__init__.py b/src/crewai/__init__.py index a41e3827f..1a1439d57 100644 --- a/src/crewai/__init__.py +++ b/src/crewai/__init__.py @@ -14,5 +14,5 @@ warnings.filterwarnings( category=UserWarning, module="pydantic.main", ) -__version__ = "0.74.2" +__version__ = "0.75.1" __all__ = ["Agent", "Crew", "Process", "Task", "Pipeline", "Router", "LLM", "Flow"] diff --git a/src/crewai/cli/cli.py b/src/crewai/cli/cli.py index 6c5263345..ae2cac143 100644 --- a/src/crewai/cli/cli.py +++ b/src/crewai/cli/cli.py @@ -32,10 +32,11 @@ def crewai(): @crewai.command() @click.argument("type", type=click.Choice(["crew", "pipeline", "flow"])) @click.argument("name") -def create(type, name): +@click.option("--provider", type=str, help="The provider to use for the crew") +def create(type, name, provider): """Create a new crew, pipeline, or flow.""" if type == "crew": - create_crew(name) + create_crew(name, provider) elif type == "pipeline": create_pipeline(name) elif type == "flow": diff --git a/src/crewai/cli/create_crew.py b/src/crewai/cli/create_crew.py index f74331a23..f3a50f5f4 100644 --- a/src/crewai/cli/create_crew.py +++ b/src/crewai/cli/create_crew.py @@ -70,18 +70,19 @@ def copy_template_files(folder_path, name, class_name, parent_folder): copy_template(src_file, dst_file, name, class_name, folder_path.name) -def create_crew(name, parent_folder=None): +def create_crew(name, provider=None, parent_folder=None): folder_path, folder_name, class_name = create_folder_structure(name, parent_folder) env_vars = load_env_vars(folder_path) - provider_models = get_provider_data() - if not provider_models: - return + if not provider: + provider_models = get_provider_data() + if not provider_models: + return - selected_provider = select_provider(provider_models) - if not selected_provider: - return - provider = selected_provider + selected_provider = select_provider(provider_models) + if not selected_provider: + return + provider = selected_provider # selected_model = select_model(provider, provider_models) # if not selected_model: diff --git a/src/crewai/cli/templates/crew/pyproject.toml b/src/crewai/cli/templates/crew/pyproject.toml index 36ba1548c..a33e4bfce 100644 --- a/src/crewai/cli/templates/crew/pyproject.toml +++ b/src/crewai/cli/templates/crew/pyproject.toml @@ -5,7 +5,7 @@ description = "{{name}} using crewAI" authors = [{ name = "Your Name", email = "you@example.com" }] requires-python = ">=3.10,<=3.13" dependencies = [ - "crewai[tools]>=0.74.2,<1.0.0" + "crewai[tools]>=0.75.1,<1.0.0" ] [project.scripts] diff --git a/src/crewai/cli/templates/flow/pyproject.toml b/src/crewai/cli/templates/flow/pyproject.toml index f93390741..d0ef63377 100644 --- a/src/crewai/cli/templates/flow/pyproject.toml +++ b/src/crewai/cli/templates/flow/pyproject.toml @@ -5,7 +5,7 @@ description = "{{name}} using crewAI" authors = [{ name = "Your Name", email = "you@example.com" }] requires-python = ">=3.10,<=3.13" dependencies = [ - "crewai[tools]>=0.74.2,<1.0.0", + "crewai[tools]>=0.75.1,<1.0.0", ] [project.scripts] diff --git a/src/crewai/cli/templates/pipeline/pyproject.toml b/src/crewai/cli/templates/pipeline/pyproject.toml index 517c2a4d1..62a69f3a7 100644 --- a/src/crewai/cli/templates/pipeline/pyproject.toml +++ b/src/crewai/cli/templates/pipeline/pyproject.toml @@ -6,7 +6,7 @@ authors = ["Your Name "] [tool.poetry.dependencies] python = ">=3.10,<=3.13" -crewai = { extras = ["tools"], version = ">=0.74.2,<1.0.0" } +crewai = { extras = ["tools"], version = ">=0.75.1,<1.0.0" } asyncio = "*" [tool.poetry.scripts] diff --git a/src/crewai/cli/templates/pipeline_router/pyproject.toml b/src/crewai/cli/templates/pipeline_router/pyproject.toml index b349e4dda..20b3a7c07 100644 --- a/src/crewai/cli/templates/pipeline_router/pyproject.toml +++ b/src/crewai/cli/templates/pipeline_router/pyproject.toml @@ -5,7 +5,7 @@ description = "{{name}} using crewAI" authors = ["Your Name "] requires-python = ">=3.10,<=3.13" dependencies = [ - "crewai[tools]>=0.74.2,<1.0.0" + "crewai[tools]>=0.75.1,<1.0.0" ] [project.scripts] diff --git a/src/crewai/cli/templates/tool/pyproject.toml b/src/crewai/cli/templates/tool/pyproject.toml index bd1df942f..c5d6a47e7 100644 --- a/src/crewai/cli/templates/tool/pyproject.toml +++ b/src/crewai/cli/templates/tool/pyproject.toml @@ -5,6 +5,6 @@ description = "Power up your crews with {{folder_name}}" readme = "README.md" requires-python = ">=3.10,<=3.13" dependencies = [ - "crewai[tools]>=0.74.2" + "crewai[tools]>=0.75.1" ] diff --git a/uv.lock b/uv.lock index 5d4d5b0b7..ddd57b313 100644 --- a/uv.lock +++ b/uv.lock @@ -627,7 +627,7 @@ wheels = [ [[package]] name = "crewai" -version = "0.74.2" +version = "0.75.1" source = { editable = "." } dependencies = [ { name = "appdirs" }, From f39a975e208788209ed9b92815e8ee928afae503 Mon Sep 17 00:00:00 2001 From: Rip&Tear <84775494+theCyberTech@users.noreply.github.com> Date: Wed, 23 Oct 2024 21:41:14 +0800 Subject: [PATCH 013/126] fix/fixed missing API prompt + CLI docs update (#1464) * updated CLI to allow for submitting API keys * updated click prompt to remove default number * removed all unnecessary comments * feat: implement crew creation CLI command - refactor code to multiple functions - Added ability for users to select provider and model when uing crewai create command and ave API key to .env * refactered select_choice function for early return * refactored select_provider to have an ealry return * cleanup of comments * refactor/Move functions into utils file, added new provider file and migrated fucntions thre, new constants file + general function refactor * small comment cleanup * fix unnecessary deps * Added docs for new CLI provider + fixed missing API prompt * Minor doc updates * allow user to bypass api key entry + incorect number selected logic + ruff formatting * ruff updates * Fix spelling mistake --------- Co-authored-by: Brandon Hancock (bhancock_ai) <109994880+bhancockio@users.noreply.github.com> Co-authored-by: Brandon Hancock --- docs/concepts/cli.mdx | 33 +++++++- src/crewai/cli/create_crew.py | 120 ++++++++++++++++++++--------- src/crewai/cli/provider.py | 137 ++++++++++++++++++++++------------ 3 files changed, 206 insertions(+), 84 deletions(-) diff --git a/docs/concepts/cli.mdx b/docs/concepts/cli.mdx index 8297ee6aa..2afc6b56c 100644 --- a/docs/concepts/cli.mdx +++ b/docs/concepts/cli.mdx @@ -6,7 +6,7 @@ icon: terminal # CrewAI CLI Documentation -The CrewAI CLI provides a set of commands to interact with CrewAI, allowing you to create, train, run, and manage crews and pipelines. +The CrewAI CLI provides a set of commands to interact with CrewAI, allowing you to create, train, run, and manage crews & flows. ## Installation @@ -146,3 +146,34 @@ crewai run Make sure to run these commands from the directory where your CrewAI project is set up. Some commands may require additional configuration or setup within your project structure. + + +### 9. API Keys + +When running ```crewai create crew``` command, the CLI will first show you the top 5 most common LLM providers and ask you to select one. + +Once you've selected an LLM provider, you will be prompted for API keys. + +#### Initial API key providers + +The CLI will initially prompt for API keys for the following services: + +* OpenAI +* Groq +* Anthropic +* Google Gemini + +When you select a provider, the CLI will prompt you to enter your API key. + +#### Other Options + +If you select option 6, you will be able to select from a list of LiteLLM supported providers. + +When you select a provider, the CLI will prompt you to enter the Key name and the API key. + +See the following link for each provider's key name: + +* [LiteLLM Providers](https://docs.litellm.ai/docs/providers) + + + diff --git a/src/crewai/cli/create_crew.py b/src/crewai/cli/create_crew.py index f3a50f5f4..f336c3f52 100644 --- a/src/crewai/cli/create_crew.py +++ b/src/crewai/cli/create_crew.py @@ -1,8 +1,16 @@ +import sys from pathlib import Path + import click -from crewai.cli.utils import copy_template, load_env_vars, write_env_file -from crewai.cli.provider import get_provider_data, select_provider, PROVIDERS + from crewai.cli.constants import ENV_VARS +from crewai.cli.provider import ( + PROVIDERS, + get_provider_data, + select_model, + select_provider, +) +from crewai.cli.utils import copy_template, load_env_vars, write_env_file def create_folder_structure(name, parent_folder=None): @@ -14,11 +22,19 @@ def create_folder_structure(name, parent_folder=None): else: folder_path = Path(folder_name) - click.secho( - f"Creating {'crew' if parent_folder else 'folder'} {folder_name}...", - fg="green", - bold=True, - ) + if folder_path.exists(): + if not click.confirm( + f"Folder {folder_name} already exists. Do you want to override it?" + ): + click.secho("Operation cancelled.", fg="yellow") + sys.exit(0) + click.secho(f"Overriding folder {folder_name}...", fg="green", bold=True) + else: + click.secho( + f"Creating {'crew' if parent_folder else 'folder'} {folder_name}...", + fg="green", + bold=True, + ) if not folder_path.exists(): folder_path.mkdir(parents=True) @@ -27,11 +43,6 @@ def create_folder_structure(name, parent_folder=None): (folder_path / "src" / folder_name).mkdir(parents=True) (folder_path / "src" / folder_name / "tools").mkdir(parents=True) (folder_path / "src" / folder_name / "config").mkdir(parents=True) - else: - click.secho( - f"\tFolder {folder_name} already exists.", - fg="yellow", - ) return folder_path, folder_name, class_name @@ -70,38 +81,77 @@ def copy_template_files(folder_path, name, class_name, parent_folder): copy_template(src_file, dst_file, name, class_name, folder_path.name) -def create_crew(name, provider=None, parent_folder=None): +def create_crew(name, parent_folder=None): folder_path, folder_name, class_name = create_folder_structure(name, parent_folder) env_vars = load_env_vars(folder_path) - if not provider: - provider_models = get_provider_data() - if not provider_models: + existing_provider = None + for provider, env_keys in ENV_VARS.items(): + if any(key in env_vars for key in env_keys): + existing_provider = provider + break + + if existing_provider: + if not click.confirm( + f"Found existing environment variable configuration for {existing_provider.capitalize()}. Do you want to override it?" + ): + click.secho("Keeping existing provider configuration.", fg="yellow") return + provider_models = get_provider_data() + if not provider_models: + return + + while True: selected_provider = select_provider(provider_models) - if not selected_provider: - return - provider = selected_provider - - # selected_model = select_model(provider, provider_models) - # if not selected_model: - # return - # model = selected_model - - if provider in PROVIDERS: - api_key_var = ENV_VARS[provider][0] - else: - api_key_var = click.prompt( - f"Enter the environment variable name for your {provider.capitalize()} API key", - type=str, + if selected_provider is None: # User typed 'q' + click.secho("Exiting...", fg="yellow") + sys.exit(0) + if selected_provider: # Valid selection + break + click.secho( + "No provider selected. Please try again or press 'q' to exit.", fg="red" ) - env_vars = {api_key_var: "YOUR_API_KEY_HERE"} - write_env_file(folder_path, env_vars) + while True: + selected_model = select_model(selected_provider, provider_models) + if selected_model is None: # User typed 'q' + click.secho("Exiting...", fg="yellow") + sys.exit(0) + if selected_model: # Valid selection + break + click.secho( + "No model selected. Please try again or press 'q' to exit.", fg="red" + ) - # env_vars['MODEL'] = model - # click.secho(f"Selected model: {model}", fg="green") + if selected_provider in PROVIDERS: + api_key_var = ENV_VARS[selected_provider][0] + else: + api_key_var = click.prompt( + f"Enter the environment variable name for your {selected_provider.capitalize()} API key", + type=str, + default="", + ) + + api_key_value = "" + click.echo( + f"Enter your {selected_provider.capitalize()} API key (press Enter to skip): ", + nl=False, + ) + try: + api_key_value = input() + except (KeyboardInterrupt, EOFError): + api_key_value = "" + + if api_key_value.strip(): + env_vars = {api_key_var: api_key_value} + write_env_file(folder_path, env_vars) + click.secho("API key saved to .env file", fg="green") + else: + click.secho("No API key provided. Skipping .env file creation.", fg="yellow") + + env_vars["MODEL"] = selected_model + click.secho(f"Selected model: {selected_model}", fg="green") package_dir = Path(__file__).parent templates_dir = package_dir / "templates" / "crew" diff --git a/src/crewai/cli/provider.py b/src/crewai/cli/provider.py index f829ca9fd..4bfeb9324 100644 --- a/src/crewai/cli/provider.py +++ b/src/crewai/cli/provider.py @@ -1,67 +1,91 @@ import json import time -import requests from collections import defaultdict +from pathlib import Path + import click -from pathlib import Path -from crewai.cli.constants import PROVIDERS, MODELS, JSON_URL +import requests + +from crewai.cli.constants import JSON_URL, MODELS, PROVIDERS + def select_choice(prompt_message, choices): """ Presents a list of choices to the user and prompts them to select one. - + Args: - prompt_message (str): The message to display to the user before presenting the choices. - choices (list): A list of options to present to the user. - + Returns: - - str: The selected choice from the list, or None if the operation is aborted or an invalid selection is made. + - str: The selected choice from the list, or None if the user chooses to quit. """ + + provider_models = get_provider_data() + if not provider_models: + return click.secho(prompt_message, fg="cyan") for idx, choice in enumerate(choices, start=1): click.secho(f"{idx}. {choice}", fg="cyan") - try: - selected_index = click.prompt("Enter the number of your choice", type=int) - 1 - except click.exceptions.Abort: - click.secho("Operation aborted by the user.", fg="red") - return None - if not (0 <= selected_index < len(choices)): - click.secho("Invalid selection.", fg="red") - return None - return choices[selected_index] + click.secho("q. Quit", fg="cyan") + + while True: + choice = click.prompt( + "Enter the number of your choice or 'q' to quit", type=str + ) + + if choice.lower() == "q": + return None + + try: + selected_index = int(choice) - 1 + if 0 <= selected_index < len(choices): + return choices[selected_index] + except ValueError: + pass + + click.secho( + "Invalid selection. Please select a number between 1 and 6 or 'q' to quit.", + fg="red", + ) + def select_provider(provider_models): """ Presents a list of providers to the user and prompts them to select one. - + Args: - provider_models (dict): A dictionary of provider models. - + Returns: - - str: The selected provider, or None if the operation is aborted or an invalid selection is made. + - str: The selected provider + - None: If user explicitly quits """ predefined_providers = [p.lower() for p in PROVIDERS] all_providers = sorted(set(predefined_providers + list(provider_models.keys()))) - provider = select_choice("Select a provider to set up:", predefined_providers + ['other']) - if not provider: + provider = select_choice( + "Select a provider to set up:", predefined_providers + ["other"] + ) + if provider is None: # User typed 'q' return None - provider = provider.lower() - if provider == 'other': + if provider == "other": provider = select_choice("Select a provider from the full list:", all_providers) - if not provider: + if provider is None: # User typed 'q' return None - return provider + + return provider.lower() if provider else False + def select_model(provider, provider_models): """ Presents a list of models for a given provider to the user and prompts them to select one. - + Args: - provider (str): The provider for which to select a model. - provider_models (dict): A dictionary of provider models. - + Returns: - str: The selected model, or None if the operation is aborted or an invalid selection is made. """ @@ -76,37 +100,49 @@ def select_model(provider, provider_models): click.secho(f"No models available for provider '{provider}'.", fg="red") return None - selected_model = select_choice(f"Select a model to use for {provider.capitalize()}:", available_models) + selected_model = select_choice( + f"Select a model to use for {provider.capitalize()}:", available_models + ) return selected_model + def load_provider_data(cache_file, cache_expiry): """ Loads provider data from a cache file if it exists and is not expired. If the cache is expired or corrupted, it fetches the data from the web. - + Args: - cache_file (Path): The path to the cache file. - cache_expiry (int): The cache expiry time in seconds. - + Returns: - dict or None: The loaded provider data or None if the operation fails. """ current_time = time.time() - if cache_file.exists() and (current_time - cache_file.stat().st_mtime) < cache_expiry: + if ( + cache_file.exists() + and (current_time - cache_file.stat().st_mtime) < cache_expiry + ): data = read_cache_file(cache_file) if data: return data - click.secho("Cache is corrupted. Fetching provider data from the web...", fg="yellow") + click.secho( + "Cache is corrupted. Fetching provider data from the web...", fg="yellow" + ) else: - click.secho("Cache expired or not found. Fetching provider data from the web...", fg="cyan") + click.secho( + "Cache expired or not found. Fetching provider data from the web...", + fg="cyan", + ) return fetch_provider_data(cache_file) + def read_cache_file(cache_file): """ Reads and returns the JSON content from a cache file. Returns None if the file contains invalid JSON. - + Args: - cache_file (Path): The path to the cache file. - + Returns: - dict or None: The JSON content of the cache file or None if the JSON is invalid. """ @@ -116,13 +152,14 @@ def read_cache_file(cache_file): except json.JSONDecodeError: return None + def fetch_provider_data(cache_file): """ Fetches provider data from a specified URL and caches it to a file. - + Args: - cache_file (Path): The path to the cache file. - + Returns: - dict or None: The fetched provider data or None if the operation fails. """ @@ -139,38 +176,42 @@ def fetch_provider_data(cache_file): click.secho("Error parsing provider data. Invalid JSON format.", fg="red") return None + def download_data(response): """ Downloads data from a given HTTP response and returns the JSON content. - + Args: - response (requests.Response): The HTTP response object. - + Returns: - dict: The JSON content of the response. """ - total_size = int(response.headers.get('content-length', 0)) + total_size = int(response.headers.get("content-length", 0)) block_size = 8192 data_chunks = [] - with click.progressbar(length=total_size, label='Downloading', show_pos=True) as progress_bar: + with click.progressbar( + length=total_size, label="Downloading", show_pos=True + ) as progress_bar: for chunk in response.iter_content(block_size): if chunk: data_chunks.append(chunk) progress_bar.update(len(chunk)) - data_content = b''.join(data_chunks) - return json.loads(data_content.decode('utf-8')) + data_content = b"".join(data_chunks) + return json.loads(data_content.decode("utf-8")) + def get_provider_data(): """ Retrieves provider data from a cache file, filters out models based on provider criteria, and returns a dictionary of providers mapped to their models. - + Returns: - dict or None: A dictionary of providers mapped to their models or None if the operation fails. """ - cache_dir = Path.home() / '.crewai' + cache_dir = Path.home() / ".crewai" cache_dir.mkdir(exist_ok=True) - cache_file = cache_dir / 'provider_cache.json' - cache_expiry = 24 * 3600 + cache_file = cache_dir / "provider_cache.json" + cache_expiry = 24 * 3600 data = load_provider_data(cache_file, cache_expiry) if not data: @@ -179,8 +220,8 @@ def get_provider_data(): provider_models = defaultdict(list) for model_name, properties in data.items(): provider = properties.get("litellm_provider", "").strip().lower() - if 'http' in provider or provider == 'other': + if "http" in provider or provider == "other": continue if provider: provider_models[provider].append(model_name) - return provider_models \ No newline at end of file + return provider_models From 7d68e287ccab533ac6235856ac76e76e6d9b3120 Mon Sep 17 00:00:00 2001 From: Maicon Peixinho Date: Wed, 23 Oct 2024 09:38:41 -0500 Subject: [PATCH 014/126] chore(readme-fix): fixing step for 'running tests' in the contribution section (#1490) Co-authored-by: Eduardo Chiarotti --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index f79f2a011..ceec198c3 100644 --- a/README.md +++ b/README.md @@ -351,7 +351,7 @@ pre-commit install ### Running Tests ```bash -uvx pytest +uv run pytest . ``` ### Running static type checks From e1fd83e6a76bec489fc69e57869c3c1cc441c551 Mon Sep 17 00:00:00 2001 From: "Brandon Hancock (bhancock_ai)" <109994880+bhancockio@users.noreply.github.com> Date: Wed, 23 Oct 2024 11:01:00 -0400 Subject: [PATCH 015/126] support unsafe code execution. add in docker install and running checks. (#1496) * support unsafe code execution. add in docker install and running checks. * Update return type --- docs/concepts/agents.mdx | 8 +++++--- src/crewai/agent.py | 34 ++++++++++++++++++++++++++++++++-- 2 files changed, 37 insertions(+), 5 deletions(-) diff --git a/docs/concepts/agents.mdx b/docs/concepts/agents.mdx index f7e97f186..6c939b321 100644 --- a/docs/concepts/agents.mdx +++ b/docs/concepts/agents.mdx @@ -31,16 +31,17 @@ Think of an agent as a member of a team, with specific skills and a particular j | **Max RPM** *(optional)* | `max_rpm` | Max RPM is the maximum number of requests per minute the agent can perform to avoid rate limits. It's optional and can be left unspecified, with a default value of `None`. | | **Max Execution Time** *(optional)* | `max_execution_time` | Max Execution Time is the maximum execution time for an agent to execute a task. It's optional and can be left unspecified, with a default value of `None`, meaning no max execution time. | | **Verbose** *(optional)* | `verbose` | Setting this to `True` configures the internal logger to provide detailed execution logs, aiding in debugging and monitoring. Default is `False`. | -| **Allow Delegation** *(optional)* | `allow_delegation` | Agents can delegate tasks or questions to one another, ensuring that each task is handled by the most suitable agent. Default is `False`. +| **Allow Delegation** *(optional)* | `allow_delegation` | Agents can delegate tasks or questions to one another, ensuring that each task is handled by the most suitable agent. Default is `False`. | | **Step Callback** *(optional)* | `step_callback` | A function that is called after each step of the agent. This can be used to log the agent's actions or to perform other operations. It will overwrite the crew `step_callback`. | | **Cache** *(optional)* | `cache` | Indicates if the agent should use a cache for tool usage. Default is `True`. | | **System Template** *(optional)* | `system_template` | Specifies the system format for the agent. Default is `None`. | | **Prompt Template** *(optional)* | `prompt_template` | Specifies the prompt format for the agent. Default is `None`. | | **Response Template** *(optional)* | `response_template` | Specifies the response format for the agent. Default is `None`. | | **Allow Code Execution** *(optional)* | `allow_code_execution` | Enable code execution for the agent. Default is `False`. | -| **Max Retry Limit** *(optional)* | `max_retry_limit` | Maximum number of retries for an agent to execute a task when an error occurs. Default is `2`. +| **Max Retry Limit** *(optional)* | `max_retry_limit` | Maximum number of retries for an agent to execute a task when an error occurs. Default is `2`. | | **Use System Prompt** *(optional)* | `use_system_prompt` | Adds the ability to not use system prompt (to support o1 models). Default is `True`. | | **Respect Context Window** *(optional)* | `respect_context_window` | Summary strategy to avoid overflowing the context window. Default is `True`. | +| **Code Execution Mode** *(optional)* | `code_execution_mode` | Determines the mode for code execution: 'safe' (using Docker) or 'unsafe' (direct execution on the host machine). Default is `safe`. | ## Creating an agent @@ -83,6 +84,7 @@ agent = Agent( max_retry_limit=2, # Optional use_system_prompt=True, # Optional respect_context_window=True, # Optional + code_execution_mode='safe', # Optional, defaults to 'safe' ) ``` @@ -156,4 +158,4 @@ crew = my_crew.kickoff(inputs={"input": "Mark Twain"}) ## Conclusion Agents are the building blocks of the CrewAI framework. By understanding how to define and interact with agents, -you can create sophisticated AI systems that leverage the power of collaborative intelligence. \ No newline at end of file +you can create sophisticated AI systems that leverage the power of collaborative intelligence. The `code_execution_mode` attribute provides flexibility in how agents execute code, allowing for both secure and direct execution options. diff --git a/src/crewai/agent.py b/src/crewai/agent.py index 165a40656..f68d6401b 100644 --- a/src/crewai/agent.py +++ b/src/crewai/agent.py @@ -1,6 +1,8 @@ import os +import shutil +import subprocess from inspect import signature -from typing import Any, List, Optional, Union +from typing import Any, List, Literal, Optional, Union from pydantic import Field, InstanceOf, PrivateAttr, model_validator @@ -112,6 +114,10 @@ class Agent(BaseAgent): default=2, description="Maximum number of retries for an agent to execute a task when an error occurs.", ) + code_execution_mode: Literal["safe", "unsafe"] = Field( + default="safe", + description="Mode for code execution: 'safe' (using Docker) or 'unsafe' (direct execution).", + ) @model_validator(mode="after") def post_init_setup(self): @@ -173,6 +179,9 @@ class Agent(BaseAgent): if not self.agent_executor: self._setup_agent_executor() + if self.allow_code_execution: + self._validate_docker_installation() + return self def _setup_agent_executor(self): @@ -308,7 +317,9 @@ class Agent(BaseAgent): try: from crewai_tools import CodeInterpreterTool - return [CodeInterpreterTool()] + # Set the unsafe_mode based on the code_execution_mode attribute + unsafe_mode = self.code_execution_mode == "unsafe" + return [CodeInterpreterTool(unsafe_mode=unsafe_mode)] except ModuleNotFoundError: self._logger.log( "info", "Coding tools not available. Install crewai_tools. " @@ -408,6 +419,25 @@ class Agent(BaseAgent): return "\n".join(tool_strings) + def _validate_docker_installation(self) -> None: + """Check if Docker is installed and running.""" + if not shutil.which("docker"): + raise RuntimeError( + f"Docker is not installed. Please install Docker to use code execution with agent: {self.role}" + ) + + try: + subprocess.run( + ["docker", "info"], + check=True, + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + ) + except subprocess.CalledProcessError: + raise RuntimeError( + f"Docker is not running. Please start Docker to use code execution with agent: {self.role}" + ) + @staticmethod def __tools_names(tools) -> str: return ", ".join([t.name for t in tools]) From a921828e51f5687b971b9883e6040092933a85e2 Mon Sep 17 00:00:00 2001 From: "Brandon Hancock (bhancock_ai)" <109994880+bhancockio@users.noreply.github.com> Date: Wed, 23 Oct 2024 11:21:27 -0400 Subject: [PATCH 016/126] Fix memory imports for embedding functions (#1497) --- docs/concepts/memory.mdx | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/docs/concepts/memory.mdx b/docs/concepts/memory.mdx index de1fb3510..735ed861e 100644 --- a/docs/concepts/memory.mdx +++ b/docs/concepts/memory.mdx @@ -118,7 +118,7 @@ Alternatively, you can directly pass the OpenAIEmbeddingFunction to the embedder Example: ```python Code from crewai import Crew, Agent, Task, Process -from chromadb.utils.embedding_functions.openai_embedding_function import OpenAIEmbeddingFunction +from chromadb.utils.embedding_functions import OpenAIEmbeddingFunction my_crew = Crew( agents=[...], @@ -174,6 +174,7 @@ my_crew = Crew( ### Using Azure OpenAI embeddings ```python Code +from chromadb.utils.embedding_functions import OpenAIEmbeddingFunction from crewai import Crew, Agent, Task, Process my_crew = Crew( @@ -182,7 +183,7 @@ my_crew = Crew( process=Process.sequential, memory=True, verbose=True, - embedder=embedding_functions.OpenAIEmbeddingFunction( + embedder=OpenAIEmbeddingFunction( api_key="YOUR_API_KEY", api_base="YOUR_API_BASE_PATH", api_type="azure", @@ -195,6 +196,7 @@ my_crew = Crew( ### Using Vertex AI embeddings ```python Code +from chromadb.utils.embedding_functions import GoogleVertexEmbeddingFunction from crewai import Crew, Agent, Task, Process my_crew = Crew( @@ -203,7 +205,7 @@ my_crew = Crew( process=Process.sequential, memory=True, verbose=True, - embedder=embedding_functions.GoogleVertexEmbeddingFunction( + embedder=GoogleVertexEmbeddingFunction( project_id="YOUR_PROJECT_ID", region="YOUR_REGION", api_key="YOUR_API_KEY", From 74c17033108fc5392d4d09f3c0378cb45cd26f75 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Moura?= Date: Wed, 23 Oct 2024 17:56:21 -0300 Subject: [PATCH 017/126] updating crewai version --- pyproject.toml | 2 +- src/crewai/__init__.py | 2 +- src/crewai/cli/cli.py | 5 +- src/crewai/cli/create_crew.py | 125 +- src/crewai/cli/templates/crew/pyproject.toml | 2 +- src/crewai/cli/templates/flow/pyproject.toml | 2 +- .../cli/templates/pipeline/pyproject.toml | 2 +- .../templates/pipeline_router/pyproject.toml | 2 +- src/crewai/cli/templates/tool/pyproject.toml | 2 +- uv.lock | 1904 ++++++++--------- 10 files changed, 985 insertions(+), 1063 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 56fdee20d..929e68b9e 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "crewai" -version = "0.75.1" +version = "0.76.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" diff --git a/src/crewai/__init__.py b/src/crewai/__init__.py index 1a1439d57..a1b95951d 100644 --- a/src/crewai/__init__.py +++ b/src/crewai/__init__.py @@ -14,5 +14,5 @@ warnings.filterwarnings( category=UserWarning, module="pydantic.main", ) -__version__ = "0.75.1" +__version__ = "0.76.0" __all__ = ["Agent", "Crew", "Process", "Task", "Pipeline", "Router", "LLM", "Flow"] diff --git a/src/crewai/cli/cli.py b/src/crewai/cli/cli.py index ae2cac143..48bb26ff9 100644 --- a/src/crewai/cli/cli.py +++ b/src/crewai/cli/cli.py @@ -32,11 +32,12 @@ def crewai(): @crewai.command() @click.argument("type", type=click.Choice(["crew", "pipeline", "flow"])) @click.argument("name") +@click.option("--skip-provider", is_flag=True, help="Skip provider validation") @click.option("--provider", type=str, help="The provider to use for the crew") -def create(type, name, provider): +def create(type, name, provider, skip_provider_validation=False): """Create a new crew, pipeline, or flow.""" if type == "crew": - create_crew(name, provider) + create_crew(name, provider, skip_provider_validation) elif type == "pipeline": create_pipeline(name) elif type == "flow": diff --git a/src/crewai/cli/create_crew.py b/src/crewai/cli/create_crew.py index f336c3f52..90184ec31 100644 --- a/src/crewai/cli/create_crew.py +++ b/src/crewai/cli/create_crew.py @@ -81,77 +81,84 @@ def copy_template_files(folder_path, name, class_name, parent_folder): copy_template(src_file, dst_file, name, class_name, folder_path.name) -def create_crew(name, parent_folder=None): +def create_crew( + name, provider=None, skip_provider_validation=False, parent_folder=None +): folder_path, folder_name, class_name = create_folder_structure(name, parent_folder) env_vars = load_env_vars(folder_path) + if not skip_provider_validation: + if not provider: + provider_models = get_provider_data() + if not provider_models: + return - existing_provider = None - for provider, env_keys in ENV_VARS.items(): - if any(key in env_vars for key in env_keys): - existing_provider = provider - break + existing_provider = None + for provider, env_keys in ENV_VARS.items(): + if any(key in env_vars for key in env_keys): + existing_provider = provider + break - if existing_provider: - if not click.confirm( - f"Found existing environment variable configuration for {existing_provider.capitalize()}. Do you want to override it?" - ): - click.secho("Keeping existing provider configuration.", fg="yellow") + if existing_provider: + if not click.confirm( + f"Found existing environment variable configuration for {existing_provider.capitalize()}. Do you want to override it?" + ): + click.secho("Keeping existing provider configuration.", fg="yellow") + return + + provider_models = get_provider_data() + if not provider_models: return - provider_models = get_provider_data() - if not provider_models: - return + while True: + selected_provider = select_provider(provider_models) + if selected_provider is None: # User typed 'q' + click.secho("Exiting...", fg="yellow") + sys.exit(0) + if selected_provider: # Valid selection + break + click.secho( + "No provider selected. Please try again or press 'q' to exit.", fg="red" + ) - while True: - selected_provider = select_provider(provider_models) - if selected_provider is None: # User typed 'q' - click.secho("Exiting...", fg="yellow") - sys.exit(0) - if selected_provider: # Valid selection - break - click.secho( - "No provider selected. Please try again or press 'q' to exit.", fg="red" - ) + while True: + selected_model = select_model(selected_provider, provider_models) + if selected_model is None: # User typed 'q' + click.secho("Exiting...", fg="yellow") + sys.exit(0) + if selected_model: # Valid selection + break + click.secho( + "No model selected. Please try again or press 'q' to exit.", fg="red" + ) - while True: - selected_model = select_model(selected_provider, provider_models) - if selected_model is None: # User typed 'q' - click.secho("Exiting...", fg="yellow") - sys.exit(0) - if selected_model: # Valid selection - break - click.secho( - "No model selected. Please try again or press 'q' to exit.", fg="red" - ) + if selected_provider in PROVIDERS: + api_key_var = ENV_VARS[selected_provider][0] + else: + api_key_var = click.prompt( + f"Enter the environment variable name for your {selected_provider.capitalize()} API key", + type=str, + default="", + ) - if selected_provider in PROVIDERS: - api_key_var = ENV_VARS[selected_provider][0] - else: - api_key_var = click.prompt( - f"Enter the environment variable name for your {selected_provider.capitalize()} API key", - type=str, - default="", - ) - - api_key_value = "" - click.echo( - f"Enter your {selected_provider.capitalize()} API key (press Enter to skip): ", - nl=False, - ) - try: - api_key_value = input() - except (KeyboardInterrupt, EOFError): api_key_value = "" + click.echo( + f"Enter your {selected_provider.capitalize()} API key (press Enter to skip): ", + nl=False, + ) + try: + api_key_value = input() + except (KeyboardInterrupt, EOFError): + api_key_value = "" - if api_key_value.strip(): - env_vars = {api_key_var: api_key_value} - write_env_file(folder_path, env_vars) - click.secho("API key saved to .env file", fg="green") - else: - click.secho("No API key provided. Skipping .env file creation.", fg="yellow") + if api_key_value.strip(): + env_vars = {api_key_var: api_key_value} + write_env_file(folder_path, env_vars) + click.secho("API key saved to .env file", fg="green") + else: + click.secho("No API key provided. Skipping .env file creation.", fg="yellow") - env_vars["MODEL"] = selected_model - click.secho(f"Selected model: {selected_model}", fg="green") + env_vars["MODEL"] = selected_model + click.secho(f"Selected model: {selected_model}", fg="green") package_dir = Path(__file__).parent templates_dir = package_dir / "templates" / "crew" diff --git a/src/crewai/cli/templates/crew/pyproject.toml b/src/crewai/cli/templates/crew/pyproject.toml index a33e4bfce..6852f1292 100644 --- a/src/crewai/cli/templates/crew/pyproject.toml +++ b/src/crewai/cli/templates/crew/pyproject.toml @@ -5,7 +5,7 @@ description = "{{name}} using crewAI" authors = [{ name = "Your Name", email = "you@example.com" }] requires-python = ">=3.10,<=3.13" dependencies = [ - "crewai[tools]>=0.75.1,<1.0.0" + "crewai[tools]>=0.76.0,<1.0.0" ] [project.scripts] diff --git a/src/crewai/cli/templates/flow/pyproject.toml b/src/crewai/cli/templates/flow/pyproject.toml index d0ef63377..88a313ae9 100644 --- a/src/crewai/cli/templates/flow/pyproject.toml +++ b/src/crewai/cli/templates/flow/pyproject.toml @@ -5,7 +5,7 @@ description = "{{name}} using crewAI" authors = [{ name = "Your Name", email = "you@example.com" }] requires-python = ">=3.10,<=3.13" dependencies = [ - "crewai[tools]>=0.75.1,<1.0.0", + "crewai[tools]>=0.76.0,<1.0.0", ] [project.scripts] diff --git a/src/crewai/cli/templates/pipeline/pyproject.toml b/src/crewai/cli/templates/pipeline/pyproject.toml index 62a69f3a7..b2a4d36d4 100644 --- a/src/crewai/cli/templates/pipeline/pyproject.toml +++ b/src/crewai/cli/templates/pipeline/pyproject.toml @@ -6,7 +6,7 @@ authors = ["Your Name "] [tool.poetry.dependencies] python = ">=3.10,<=3.13" -crewai = { extras = ["tools"], version = ">=0.75.1,<1.0.0" } +crewai = { extras = ["tools"], version = ">=0.76.0,<1.0.0" } asyncio = "*" [tool.poetry.scripts] diff --git a/src/crewai/cli/templates/pipeline_router/pyproject.toml b/src/crewai/cli/templates/pipeline_router/pyproject.toml index 20b3a7c07..f7da504cf 100644 --- a/src/crewai/cli/templates/pipeline_router/pyproject.toml +++ b/src/crewai/cli/templates/pipeline_router/pyproject.toml @@ -5,7 +5,7 @@ description = "{{name}} using crewAI" authors = ["Your Name "] requires-python = ">=3.10,<=3.13" dependencies = [ - "crewai[tools]>=0.75.1,<1.0.0" + "crewai[tools]>=0.76.0,<1.0.0" ] [project.scripts] diff --git a/src/crewai/cli/templates/tool/pyproject.toml b/src/crewai/cli/templates/tool/pyproject.toml index c5d6a47e7..8f7194b7f 100644 --- a/src/crewai/cli/templates/tool/pyproject.toml +++ b/src/crewai/cli/templates/tool/pyproject.toml @@ -5,6 +5,6 @@ description = "Power up your crews with {{folder_name}}" readme = "README.md" requires-python = ">=3.10,<=3.13" dependencies = [ - "crewai[tools]>=0.75.1" + "crewai[tools]>=0.76.0" ] diff --git a/uv.lock b/uv.lock index ddd57b313..9d07a5bae 100644 --- a/uv.lock +++ b/uv.lock @@ -2,8 +2,9 @@ version = 1 requires-python = ">=3.10, <=3.13" resolution-markers = [ "python_full_version < '3.11' and platform_python_implementation == 'PyPy'", + "python_full_version == '3.11.*' and platform_python_implementation == 'PyPy'", "python_full_version < '3.11' and platform_python_implementation != 'PyPy'", - "python_full_version == '3.11.*'", + "python_full_version == '3.11.*' and platform_python_implementation != 'PyPy'", "python_full_version >= '3.12' and python_full_version < '3.12.4'", "python_full_version >= '3.12.4' and python_full_version < '3.13'", "python_full_version >= '3.13'", @@ -11,7 +12,7 @@ resolution-markers = [ [[package]] name = "agentops" -version = "0.3.13" +version = "0.3.14" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "packaging" }, @@ -20,9 +21,9 @@ dependencies = [ { name = "requests" }, { name = "termcolor" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/35/11/fb06b4cee96285a5f745809d0f4efddef70d2a82112a633ed53834d6fc64/agentops-0.3.13.tar.gz", hash = "sha256:319b7325fb79004ce996191aa21f0982489be22cc1acc2f3f6d02cdff1db2429", size = 48559 } +sdist = { url = "https://files.pythonhosted.org/packages/46/cb/183fdaf40ae97ac1806ba91f6f23d55dc0a1a5cdf0881a5c834c8ca7175a/agentops-0.3.14.tar.gz", hash = "sha256:fcb515e5743d73efee851b687692bed74797dc88e29a8327b2bbfb21d73a7447", size = 48548 } wheels = [ - { url = "https://files.pythonhosted.org/packages/68/ef/a3b8adc0de2e7daa1e6e2734af9a0e37c90e3346b8a804e3fdc322c82b6c/agentops-0.3.13-py3-none-any.whl", hash = "sha256:81bfdfedd990fbc3064ee42a67422ddbee07b6cd96c5fca7e124eb8c1e0cebdc", size = 50813 }, + { url = "https://files.pythonhosted.org/packages/1c/27/75ab5bf99341a6a02775e3858f54a18cbcda0f35b5c6c0f114a829d62b8e/agentops-0.3.14-py3-none-any.whl", hash = "sha256:f4a2fcf1a7caf1d5383bfb66d8a9d567f3cb88fc7495cfd81ade167b0c06a4ea", size = 50825 }, ] [[package]] @@ -36,7 +37,7 @@ wheels = [ [[package]] name = "aiohttp" -version = "3.10.8" +version = "3.10.10" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "aiohappyeyeballs" }, @@ -47,68 +48,68 @@ dependencies = [ { name = "multidict" }, { name = "yarl" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/4e/05/da5ff89c85444a6ade9079e73580fb3f78c6ba0e170a2472f15400d03e02/aiohttp-3.10.8.tar.gz", hash = "sha256:21f8225f7dc187018e8433c9326be01477fb2810721e048b33ac49091b19fb4a", size = 7540022 } +sdist = { url = "https://files.pythonhosted.org/packages/17/7e/16e57e6cf20eb62481a2f9ce8674328407187950ccc602ad07c685279141/aiohttp-3.10.10.tar.gz", hash = "sha256:0631dd7c9f0822cc61c88586ca76d5b5ada26538097d0f1df510b082bad3411a", size = 7542993 } wheels = [ - { url = "https://files.pythonhosted.org/packages/80/2f/602f6e79ab70fbd881ba599f66e29254efab7e63ac463f513d25d8878ce5/aiohttp-3.10.8-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:a1ba7bc139592339ddeb62c06486d0fa0f4ca61216e14137a40d626c81faf10c", size = 586808 }, - { url = "https://files.pythonhosted.org/packages/b3/28/a1ae71c7395c1d39972bf5b3fcb0a1056ddd51faf7ab29f38267ad0e9eb3/aiohttp-3.10.8-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:85e4d7bd05d18e4b348441e7584c681eff646e3bf38f68b2626807f3add21aa2", size = 399198 }, - { url = "https://files.pythonhosted.org/packages/c1/3b/23baa9c6cb19ae3b70cbe0f00881b33fc0994d3366808ad2621c3ed8f8d1/aiohttp-3.10.8-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:69de056022e7abf69cb9fec795515973cc3eeaff51e3ea8d72a77aa933a91c52", size = 390553 }, - { url = "https://files.pythonhosted.org/packages/e2/a0/0abb1c58ee9f5f418100fbdeb26bf7b030b33dc9dfc29f5acfb2d6896cc5/aiohttp-3.10.8-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ee3587506898d4a404b33bd19689286ccf226c3d44d7a73670c8498cd688e42c", size = 1228651 }, - { url = "https://files.pythonhosted.org/packages/cf/a9/89315ad2dade031c7e0eb2446475471c65267f841228b35d3f2b6aa5873f/aiohttp-3.10.8-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:fe285a697c851734285369614443451462ce78aac2b77db23567507484b1dc6f", size = 1264449 }, - { url = "https://files.pythonhosted.org/packages/d6/65/adf0db007864d6b26c8cb8d29b41bb79d2596640779fc3310a6e3b756715/aiohttp-3.10.8-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:10c7932337285a6bfa3a5fe1fd4da90b66ebfd9d0cbd1544402e1202eb9a8c3e", size = 1298232 }, - { url = "https://files.pythonhosted.org/packages/1e/99/5a99a25b101d3aacafd139a5477b23ab258ea3e8603dda253fdad1291e81/aiohttp-3.10.8-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cd9716ef0224fe0d0336997eb242f40619f9f8c5c57e66b525a1ebf9f1d8cebe", size = 1222030 }, - { url = "https://files.pythonhosted.org/packages/25/f7/70542eb6dce26c8ebdf7b3df7be0d88f338e3b26868d21b62e6f17f0469c/aiohttp-3.10.8-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ceacea31f8a55cdba02bc72c93eb2e1b77160e91f8abd605969c168502fd71eb", size = 1193639 }, - { url = "https://files.pythonhosted.org/packages/56/f8/6d21d76264743821a077e50e04a275c08e9dac181b1e840d795ce00ef24e/aiohttp-3.10.8-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:9721554bfa9e15f6e462da304374c2f1baede3cb06008c36c47fa37ea32f1dc4", size = 1193378 }, - { url = "https://files.pythonhosted.org/packages/ab/9f/46b12a637962e6795731ae8e43c43689e1261cbae98cdb9100a6b3458414/aiohttp-3.10.8-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:22cdeb684d8552490dd2697a5138c4ecb46f844892df437aaf94f7eea99af879", size = 1192876 }, - { url = "https://files.pythonhosted.org/packages/33/64/5524f2e70aedb9233e955b442243e5d2607c2a4875fcf1fb5c90ff2beff8/aiohttp-3.10.8-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:e56bb7e31c4bc79956b866163170bc89fd619e0581ce813330d4ea46921a4881", size = 1247035 }, - { url = "https://files.pythonhosted.org/packages/2e/d5/a03f5360bd1db3f465643f4a5e7e5ea98d8c3859ec29da43aad9fafcbad0/aiohttp-3.10.8-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:3a95d2686bc4794d66bd8de654e41b5339fab542b2bca9238aa63ed5f4f2ce82", size = 1263933 }, - { url = "https://files.pythonhosted.org/packages/e3/4a/1615e27e67e51c0f808bb4302a9d0e90e1522b179a26a4fb8bee88619f65/aiohttp-3.10.8-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:d82404a0e7b10e0d7f022cf44031b78af8a4f99bd01561ac68f7c24772fed021", size = 1215581 }, - { url = "https://files.pythonhosted.org/packages/e5/99/a71685093ba39102590b7110a8b57d00261a35f8fb1ac865a35498d67262/aiohttp-3.10.8-cp310-cp310-win32.whl", hash = "sha256:4e10b04542d27e21538e670156e88766543692a0a883f243ba8fad9ddea82e53", size = 362514 }, - { url = "https://files.pythonhosted.org/packages/ed/2a/e0b2544192abd09de44b3abb03d2ae6476bf2f35d3a911f2111c579e466d/aiohttp-3.10.8-cp310-cp310-win_amd64.whl", hash = "sha256:680dbcff5adc7f696ccf8bf671d38366a1f620b5616a1d333d0cb33956065395", size = 380931 }, - { url = "https://files.pythonhosted.org/packages/07/ca/2fc934c4c86865d0eb9c46f8f57443f0655f2a4a5c1dde60ec1d6d0f0881/aiohttp-3.10.8-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:33a68011a38020ed4ff41ae0dbf4a96a202562ecf2024bdd8f65385f1d07f6ef", size = 586333 }, - { url = "https://files.pythonhosted.org/packages/4a/07/7215d085dc10dd2e10f36832b2ca278f30970b4db98d5ebfed9e228d5c0c/aiohttp-3.10.8-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:6c7efa6616a95e3bd73b8a69691012d2ef1f95f9ea0189e42f338fae080c2fc6", size = 398817 }, - { url = "https://files.pythonhosted.org/packages/c4/e4/77b029c12d025d1e448662977f1e7c6fb33a19c42181c8d20c2791b5c5d9/aiohttp-3.10.8-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:ddb9b9764cfb4459acf01c02d2a59d3e5066b06a846a364fd1749aa168efa2be", size = 390465 }, - { url = "https://files.pythonhosted.org/packages/17/f5/206e6a58a3a5be39662a07f531a6033384e361e272735437c5c15176c601/aiohttp-3.10.8-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3c7f270f4ca92760f98a42c45a58674fff488e23b144ec80b1cc6fa2effed377", size = 1306316 }, - { url = "https://files.pythonhosted.org/packages/33/e7/3b6b5ad02e367f30927bb93263127c23290f5b11900d036429f4787e1948/aiohttp-3.10.8-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:6984dda9d79064361ab58d03f6c1e793ea845c6cfa89ffe1a7b9bb400dfd56bd", size = 1344486 }, - { url = "https://files.pythonhosted.org/packages/ae/9f/f27ba4cd2bffb4885aa35827a21878dbd3f50d6e5b205ce1107ce79edc40/aiohttp-3.10.8-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3f6d47e392c27206701565c8df4cac6ebed28fdf6dcaea5b1eea7a4631d8e6db", size = 1378320 }, - { url = "https://files.pythonhosted.org/packages/54/76/b106eb516d327527a6b1e0409a3553745ad34480eddfd0d7cad48ddc9848/aiohttp-3.10.8-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a72f89aea712c619b2ca32c6f4335c77125ede27530ad9705f4f349357833695", size = 1292542 }, - { url = "https://files.pythonhosted.org/packages/7d/0c/c116a27253c0bc76959ab8df5a109d482c0977d4028e1b3ec7fac038bb1a/aiohttp-3.10.8-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c36074b26f3263879ba8e4dbd33db2b79874a3392f403a70b772701363148b9f", size = 1251608 }, - { url = "https://files.pythonhosted.org/packages/9e/05/f9624dc401f72a3ee4cddea1a555b430e9a7be9d0cd2ab53dbec2fc78279/aiohttp-3.10.8-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:e32148b4a745e70a255a1d44b5664de1f2e24fcefb98a75b60c83b9e260ddb5b", size = 1271551 }, - { url = "https://files.pythonhosted.org/packages/6d/77/19a032cfb9fdfd69591cf173c23c62992774b2ff978e4dab3038a1955e14/aiohttp-3.10.8-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:5aa1a073514cf59c81ad49a4ed9b5d72b2433638cd53160fd2f3a9cfa94718db", size = 1266089 }, - { url = "https://files.pythonhosted.org/packages/12/63/58ebde5ea32cf5f19c83d6dc2c582ca5f0c42ce4cf084216a3cda4b2e34a/aiohttp-3.10.8-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:d3a79200a9d5e621c4623081ddb25380b713c8cf5233cd11c1aabad990bb9381", size = 1321455 }, - { url = "https://files.pythonhosted.org/packages/1a/22/d8439a280161b542a28f88794ab55917cdc672544b87db52d3c41ce8d9a1/aiohttp-3.10.8-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:e45fdfcb2d5bcad83373e4808825b7512953146d147488114575780640665027", size = 1339057 }, - { url = "https://files.pythonhosted.org/packages/bc/67/1a76a69adfe3013863df4142d37059fb357146815b29596945d61fb940cb/aiohttp-3.10.8-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:f78e2a78432c537ae876a93013b7bc0027ba5b93ad7b3463624c4b6906489332", size = 1298892 }, - { url = "https://files.pythonhosted.org/packages/38/13/7294cb679ab7a80e5b0d0aa97c527690cffed2f34cb8892d73ebdb4204e8/aiohttp-3.10.8-cp311-cp311-win32.whl", hash = "sha256:f8179855a4e4f3b931cb1764ec87673d3fbdcca2af496c8d30567d7b034a13db", size = 362066 }, - { url = "https://files.pythonhosted.org/packages/bc/4a/8881d4d7259427897e1a314c2724e65fd0d20084c72cac8360665f96c347/aiohttp-3.10.8-cp311-cp311-win_amd64.whl", hash = "sha256:ef9b484604af05ca745b6108ca1aaa22ae1919037ae4f93aaf9a37ba42e0b835", size = 381406 }, - { url = "https://files.pythonhosted.org/packages/bb/ce/a8ff9f5bd2b36e3049cfe8d53656fed03075221ff42f946c581325bdc8fc/aiohttp-3.10.8-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:ab2d6523575fc98896c80f49ac99e849c0b0e69cc80bf864eed6af2ae728a52b", size = 583366 }, - { url = "https://files.pythonhosted.org/packages/91/5c/75287ab8a6ae9cbe02d45ebb36b1e899c11da5eb47060e0dcb98ee30a951/aiohttp-3.10.8-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:f5d5d5401744dda50b943d8764508d0e60cc2d3305ac1e6420935861a9d544bc", size = 395525 }, - { url = "https://files.pythonhosted.org/packages/a8/5a/aca17d71eb7e0f4611b2f28cb04e05aaebe6c7c2a7d1364e494da9722714/aiohttp-3.10.8-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:de23085cf90911600ace512e909114385026b16324fa203cc74c81f21fd3276a", size = 390727 }, - { url = "https://files.pythonhosted.org/packages/1b/ee/c1663449864ec9dd3d2a61dde09112bea5e1d881496c36146a96fe85da62/aiohttp-3.10.8-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4618f0d2bf523043866a9ff8458900d8eb0a6d4018f251dae98e5f1fb699f3a8", size = 1311898 }, - { url = "https://files.pythonhosted.org/packages/8b/7e/ed2eb276fdf946a9303f3f80033555d3eaa0eadbcdd0c31b153e33b495fc/aiohttp-3.10.8-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:21c1925541ca84f7b5e0df361c0a813a7d6a56d3b0030ebd4b220b8d232015f9", size = 1350380 }, - { url = "https://files.pythonhosted.org/packages/0c/3f/1d74a1311b14a1d69aad06775ffc1c09c195db67d951c8319220b9c64fdc/aiohttp-3.10.8-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:497a7d20caea8855c5429db3cdb829385467217d7feb86952a6107e033e031b9", size = 1392486 }, - { url = "https://files.pythonhosted.org/packages/9f/95/b940d71b1f61cf2ed48f2918c292609d251dba012a8e033afc0c778ed6a7/aiohttp-3.10.8-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c887019dbcb4af58a091a45ccf376fffe800b5531b45c1efccda4bedf87747ea", size = 1306135 }, - { url = "https://files.pythonhosted.org/packages/9b/25/b096aebc2f9b3ed738a4a667b841780b1dcd23ce5dff7dfabab4d09de4c8/aiohttp-3.10.8-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:40d2d719c3c36a7a65ed26400e2b45b2d9ed7edf498f4df38b2ae130f25a0d01", size = 1260085 }, - { url = "https://files.pythonhosted.org/packages/9e/cf/bc024d8a848ee4feaae6a037034cf8b173a14ea9cb5c2988b6e5018abf33/aiohttp-3.10.8-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:57359785f27394a8bcab0da6dcd46706d087dfebf59a8d0ad2e64a4bc2f6f94f", size = 1270968 }, - { url = "https://files.pythonhosted.org/packages/40/1d/2513347c445d1aaa694e79f4d45f80d777ea3e4d772d9480577834dc2c1c/aiohttp-3.10.8-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:a961ee6f2cdd1a2be4735333ab284691180d40bad48f97bb598841bfcbfb94ec", size = 1280083 }, - { url = "https://files.pythonhosted.org/packages/22/e1/4be1b057044c3d874e795744446c682715b232281adbe94612ddc9877ee4/aiohttp-3.10.8-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:fe3d79d6af839ffa46fdc5d2cf34295390894471e9875050eafa584cb781508d", size = 1316638 }, - { url = "https://files.pythonhosted.org/packages/6d/c3/84492f103c724d3149bba413e1dc081e573c44013bd2cc8f4addd51cf365/aiohttp-3.10.8-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:9a281cba03bdaa341c70b7551b2256a88d45eead149f48b75a96d41128c240b3", size = 1343764 }, - { url = "https://files.pythonhosted.org/packages/cf/b7/50cc827dd54df087d7c30293b29fbc13a7ea45a3ac54a4a12127b271265c/aiohttp-3.10.8-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:c6769d71bfb1ed60321363a9bc05e94dcf05e38295ef41d46ac08919e5b00d19", size = 1306007 }, - { url = "https://files.pythonhosted.org/packages/1e/c0/a4cb21ad677757368743d73aff27047dfc0d7248cb39dec06c059b773c24/aiohttp-3.10.8-cp312-cp312-win32.whl", hash = "sha256:a3081246bab4d419697ee45e555cef5cd1def7ac193dff6f50be761d2e44f194", size = 359125 }, - { url = "https://files.pythonhosted.org/packages/d2/0f/1ecbc18eed29952393d5a9c4636bfe789dde3c98fe0a0a4759d323478e72/aiohttp-3.10.8-cp312-cp312-win_amd64.whl", hash = "sha256:ab1546fc8e00676febc81c548a876c7bde32f881b8334b77f84719ab2c7d28dc", size = 379143 }, - { url = "https://files.pythonhosted.org/packages/9f/dd/3d944769ed65d3d245f8f976040654b3eae2e21d05c81f91fb450365bddf/aiohttp-3.10.8-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:b1a012677b8e0a39e181e218de47d6741c5922202e3b0b65e412e2ce47c39337", size = 575934 }, - { url = "https://files.pythonhosted.org/packages/2a/bf/a6a1d14b0e5f90d53b1f0850204f9fafdfec7c1d99dda8aaea1dd93ba181/aiohttp-3.10.8-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:2df786c96c57cd6b87156ba4c5f166af7b88f3fc05f9d592252fdc83d8615a3c", size = 391728 }, - { url = "https://files.pythonhosted.org/packages/0e/1b/27cc6efa6ca3e563973c7e03e8b7e26b75b4046aefea991bad42c028a906/aiohttp-3.10.8-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:8885ca09d3a9317219c0831276bfe26984b17b2c37b7bf70dd478d17092a4772", size = 387247 }, - { url = "https://files.pythonhosted.org/packages/ae/fd/235401bd4a98ea31cdda7b3822921e2a9cbc3ca0af1958a12a2709261735/aiohttp-3.10.8-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4dbf252ac19860e0ab56cd480d2805498f47c5a2d04f5995d8d8a6effd04b48c", size = 1286909 }, - { url = "https://files.pythonhosted.org/packages/ab/1c/8ae6b12be2ae88e94be34d96765d6cc820d61d320f33c0423de8af0cfa47/aiohttp-3.10.8-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3b2036479b6b94afaaca7d07b8a68dc0e67b0caf5f6293bb6a5a1825f5923000", size = 1323446 }, - { url = "https://files.pythonhosted.org/packages/23/09/5ebe3a2dbdd008711b659dc2f2a6135bbc055b6c8869688083f4bec6b50a/aiohttp-3.10.8-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:365783e1b7c40b59ed4ce2b5a7491bae48f41cd2c30d52647a5b1ee8604c68ad", size = 1368237 }, - { url = "https://files.pythonhosted.org/packages/47/22/f184c27d03d34ce71e6d4b9976a4ff845d091b725f174b09f641e4a28f63/aiohttp-3.10.8-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:270e653b5a4b557476a1ed40e6b6ce82f331aab669620d7c95c658ef976c9c5e", size = 1282598 }, - { url = "https://files.pythonhosted.org/packages/82/f6/bae1703bfacb19bb35e3522632fc5279793070625a0b5e567b109c0f0e8d/aiohttp-3.10.8-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8960fabc20bfe4fafb941067cda8e23c8c17c98c121aa31c7bf0cdab11b07842", size = 1236350 }, - { url = "https://files.pythonhosted.org/packages/a4/bc/ad73aced93836b8749c70e617c5d389d17a36da9ee220cdb0804f803bd9b/aiohttp-3.10.8-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:f21e8f2abed9a44afc3d15bba22e0dfc71e5fa859bea916e42354c16102b036f", size = 1250172 }, - { url = "https://files.pythonhosted.org/packages/3b/18/027a8497caf3a9c247477831d67ede58e1e42a92fd635ecdb74cf5d45c8b/aiohttp-3.10.8-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:fecd55e7418fabd297fd836e65cbd6371aa4035a264998a091bbf13f94d9c44d", size = 1248783 }, - { url = "https://files.pythonhosted.org/packages/6f/d2/5080c27b656e6d478e820752d633d7a4dab4a2c4fd23a6f645b553fb9da5/aiohttp-3.10.8-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:badb51d851358cd7535b647bb67af4854b64f3c85f0d089c737f75504d5910ec", size = 1293209 }, - { url = "https://files.pythonhosted.org/packages/ae/ec/c38c8690e804cb9bf3e8c473a4a7bb339ed549cd63c469f19995269ca9ec/aiohttp-3.10.8-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:e860985f30f3a015979e63e7ba1a391526cdac1b22b7b332579df7867848e255", size = 1319943 }, - { url = "https://files.pythonhosted.org/packages/df/55/d6e3a13c3f37ad7a3e60a377c96541261c1943837d240f1ab2151a96da6b/aiohttp-3.10.8-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:71462f8eeca477cbc0c9700a9464e3f75f59068aed5e9d4a521a103692da72dc", size = 1281380 }, - { url = "https://files.pythonhosted.org/packages/c3/31/0b84027487fa58a124251b47f9dca781e4777a50d1c4eea4d3fc8950bd10/aiohttp-3.10.8-cp313-cp313-win32.whl", hash = "sha256:177126e971782769b34933e94fddd1089cef0fe6b82fee8a885e539f5b0f0c6a", size = 357352 }, - { url = "https://files.pythonhosted.org/packages/cb/8a/b4f3a8d0fb7f4fdb3869db6c3334e23e11878123605579e067be85f7e01f/aiohttp-3.10.8-cp313-cp313-win_amd64.whl", hash = "sha256:98a4eb60e27033dee9593814ca320ee8c199489fbc6b2699d0f710584db7feb7", size = 376618 }, + { url = "https://files.pythonhosted.org/packages/3d/dd/3d40c0e67e79c5c42671e3e268742f1ff96c6573ca43823563d01abd9475/aiohttp-3.10.10-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:be7443669ae9c016b71f402e43208e13ddf00912f47f623ee5994e12fc7d4b3f", size = 586969 }, + { url = "https://files.pythonhosted.org/packages/75/64/8de41b5555e5b43ef6d4ed1261891d33fe45ecc6cb62875bfafb90b9ab93/aiohttp-3.10.10-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:7b06b7843929e41a94ea09eb1ce3927865387e3e23ebe108e0d0d09b08d25be9", size = 399367 }, + { url = "https://files.pythonhosted.org/packages/96/36/27bd62ea7ce43906d1443a73691823fc82ffb8fa03276b0e2f7e1037c286/aiohttp-3.10.10-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:333cf6cf8e65f6a1e06e9eb3e643a0c515bb850d470902274239fea02033e9a8", size = 390720 }, + { url = "https://files.pythonhosted.org/packages/e8/4d/d516b050d811ce0dd26325c383013c104ffa8b58bd361b82e52833f68e78/aiohttp-3.10.10-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:274cfa632350225ce3fdeb318c23b4a10ec25c0e2c880eff951a3842cf358ac1", size = 1228820 }, + { url = "https://files.pythonhosted.org/packages/53/94/964d9327a3e336d89aad52260836e4ec87fdfa1207176550fdf384eaffe7/aiohttp-3.10.10-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d9e5e4a85bdb56d224f412d9c98ae4cbd032cc4f3161818f692cd81766eee65a", size = 1264616 }, + { url = "https://files.pythonhosted.org/packages/0c/20/70ce17764b685ca8f5bf4d568881b4e1f1f4ea5e8170f512fdb1a33859d2/aiohttp-3.10.10-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2b606353da03edcc71130b52388d25f9a30a126e04caef1fd637e31683033abd", size = 1298402 }, + { url = "https://files.pythonhosted.org/packages/d1/d1/5248225ccc687f498d06c3bca5af2647a361c3687a85eb3aedcc247ee1aa/aiohttp-3.10.10-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ab5a5a0c7a7991d90446a198689c0535be89bbd6b410a1f9a66688f0880ec026", size = 1222205 }, + { url = "https://files.pythonhosted.org/packages/f2/a3/9296b27cc5d4feadf970a14d0694902a49a985f3fae71b8322a5f77b0baa/aiohttp-3.10.10-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:578a4b875af3e0daaf1ac6fa983d93e0bbfec3ead753b6d6f33d467100cdc67b", size = 1193804 }, + { url = "https://files.pythonhosted.org/packages/d9/07/f3760160feb12ac51a6168a6da251a4a8f2a70733d49e6ceb9b3e6ee2f03/aiohttp-3.10.10-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:8105fd8a890df77b76dd3054cddf01a879fc13e8af576805d667e0fa0224c35d", size = 1193544 }, + { url = "https://files.pythonhosted.org/packages/7e/4c/93a70f9a4ba1c30183a6dd68bfa79cddbf9a674f162f9c62e823a74a5515/aiohttp-3.10.10-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:3bcd391d083f636c06a68715e69467963d1f9600f85ef556ea82e9ef25f043f7", size = 1193047 }, + { url = "https://files.pythonhosted.org/packages/ff/a3/36a1e23ff00c7a0cd696c5a28db05db25dc42bfc78c508bd78623ff62a4a/aiohttp-3.10.10-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:fbc6264158392bad9df19537e872d476f7c57adf718944cc1e4495cbabf38e2a", size = 1247201 }, + { url = "https://files.pythonhosted.org/packages/55/ae/95399848557b98bb2c402d640b2276ce3a542b94dba202de5a5a1fe29abe/aiohttp-3.10.10-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:e48d5021a84d341bcaf95c8460b152cfbad770d28e5fe14a768988c461b821bc", size = 1264102 }, + { url = "https://files.pythonhosted.org/packages/38/f5/02e5c72c1b60d7cceb30b982679a26167e84ac029fd35a93dd4da52c50a3/aiohttp-3.10.10-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:2609e9ab08474702cc67b7702dbb8a80e392c54613ebe80db7e8dbdb79837c68", size = 1215760 }, + { url = "https://files.pythonhosted.org/packages/30/17/1463840bad10d02d0439068f37ce5af0b383884b0d5838f46fb027e233bf/aiohttp-3.10.10-cp310-cp310-win32.whl", hash = "sha256:84afcdea18eda514c25bc68b9af2a2b1adea7c08899175a51fe7c4fb6d551257", size = 362678 }, + { url = "https://files.pythonhosted.org/packages/dd/01/a0ef707d93e867a43abbffee3a2cdf30559910750b9176b891628c7ad074/aiohttp-3.10.10-cp310-cp310-win_amd64.whl", hash = "sha256:9c72109213eb9d3874f7ac8c0c5fa90e072d678e117d9061c06e30c85b4cf0e6", size = 381097 }, + { url = "https://files.pythonhosted.org/packages/72/31/3c351d17596194e5a38ef169a4da76458952b2497b4b54645b9d483cbbb0/aiohttp-3.10.10-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:c30a0eafc89d28e7f959281b58198a9fa5e99405f716c0289b7892ca345fe45f", size = 586501 }, + { url = "https://files.pythonhosted.org/packages/a4/a8/a559d09eb08478cdead6b7ce05b0c4a133ba27fcdfa91e05d2e62867300d/aiohttp-3.10.10-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:258c5dd01afc10015866114e210fb7365f0d02d9d059c3c3415382ab633fcbcb", size = 398993 }, + { url = "https://files.pythonhosted.org/packages/c5/47/7736d4174613feef61d25332c3bd1a4f8ff5591fbd7331988238a7299485/aiohttp-3.10.10-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:15ecd889a709b0080f02721255b3f80bb261c2293d3c748151274dfea93ac871", size = 390647 }, + { url = "https://files.pythonhosted.org/packages/27/21/e9ba192a04b7160f5a8952c98a1de7cf8072ad150fa3abd454ead1ab1d7f/aiohttp-3.10.10-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f3935f82f6f4a3820270842e90456ebad3af15810cf65932bd24da4463bc0a4c", size = 1306481 }, + { url = "https://files.pythonhosted.org/packages/cf/50/f364c01c8d0def1dc34747b2470969e216f5a37c7ece00fe558810f37013/aiohttp-3.10.10-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:413251f6fcf552a33c981c4709a6bba37b12710982fec8e558ae944bfb2abd38", size = 1344652 }, + { url = "https://files.pythonhosted.org/packages/1d/c2/74f608e984e9b585649e2e83883facad6fa3fc1d021de87b20cc67e8e5ae/aiohttp-3.10.10-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d1720b4f14c78a3089562b8875b53e36b51c97c51adc53325a69b79b4b48ebcb", size = 1378498 }, + { url = "https://files.pythonhosted.org/packages/9f/a7/05a48c7c0a7a80a5591b1203bf1b64ca2ed6a2050af918d09c05852dc42b/aiohttp-3.10.10-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:679abe5d3858b33c2cf74faec299fda60ea9de62916e8b67e625d65bf069a3b7", size = 1292718 }, + { url = "https://files.pythonhosted.org/packages/7d/78/a925655018747e9790350180330032e27d6e0d7ed30bde545fae42f8c49c/aiohttp-3.10.10-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:79019094f87c9fb44f8d769e41dbb664d6e8fcfd62f665ccce36762deaa0e911", size = 1251776 }, + { url = "https://files.pythonhosted.org/packages/47/9d/85c6b69f702351d1236594745a4fdc042fc43f494c247a98dac17e004026/aiohttp-3.10.10-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:fe2fb38c2ed905a2582948e2de560675e9dfbee94c6d5ccdb1301c6d0a5bf092", size = 1271716 }, + { url = "https://files.pythonhosted.org/packages/7f/a7/55fc805ff9b14af818903882ece08e2235b12b73b867b521b92994c52b14/aiohttp-3.10.10-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:a3f00003de6eba42d6e94fabb4125600d6e484846dbf90ea8e48a800430cc142", size = 1266263 }, + { url = "https://files.pythonhosted.org/packages/1f/ec/d2be2ca7b063e4f91519d550dbc9c1cb43040174a322470deed90b3d3333/aiohttp-3.10.10-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:1bbb122c557a16fafc10354b9d99ebf2f2808a660d78202f10ba9d50786384b9", size = 1321617 }, + { url = "https://files.pythonhosted.org/packages/c9/a3/b29f7920e1cd0a9a68a45dd3eb16140074d2efb1518d2e1f3e140357dc37/aiohttp-3.10.10-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:30ca7c3b94708a9d7ae76ff281b2f47d8eaf2579cd05971b5dc681db8caac6e1", size = 1339227 }, + { url = "https://files.pythonhosted.org/packages/8a/81/34b67235c47e232d807b4bbc42ba9b927c7ce9476872372fddcfd1e41b3d/aiohttp-3.10.10-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:df9270660711670e68803107d55c2b5949c2e0f2e4896da176e1ecfc068b974a", size = 1299068 }, + { url = "https://files.pythonhosted.org/packages/04/1f/26a7fe11b6ad3184f214733428353c89ae9fe3e4f605a657f5245c5e720c/aiohttp-3.10.10-cp311-cp311-win32.whl", hash = "sha256:aafc8ee9b742ce75044ae9a4d3e60e3d918d15a4c2e08a6c3c3e38fa59b92d94", size = 362223 }, + { url = "https://files.pythonhosted.org/packages/10/91/85dcd93f64011434359ce2666bece981f08d31bc49df33261e625b28595d/aiohttp-3.10.10-cp311-cp311-win_amd64.whl", hash = "sha256:362f641f9071e5f3ee6f8e7d37d5ed0d95aae656adf4ef578313ee585b585959", size = 381576 }, + { url = "https://files.pythonhosted.org/packages/ae/99/4c5aefe5ad06a1baf206aed6598c7cdcbc7c044c46801cd0d1ecb758cae3/aiohttp-3.10.10-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:9294bbb581f92770e6ed5c19559e1e99255e4ca604a22c5c6397b2f9dd3ee42c", size = 583536 }, + { url = "https://files.pythonhosted.org/packages/a9/36/8b3bc49b49cb6d2da40ee61ff15dbcc44fd345a3e6ab5bb20844df929821/aiohttp-3.10.10-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:a8fa23fe62c436ccf23ff930149c047f060c7126eae3ccea005f0483f27b2e28", size = 395693 }, + { url = "https://files.pythonhosted.org/packages/e1/77/0aa8660dcf11fa65d61712dbb458c4989de220a844bd69778dff25f2d50b/aiohttp-3.10.10-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:5c6a5b8c7926ba5d8545c7dd22961a107526562da31a7a32fa2456baf040939f", size = 390898 }, + { url = "https://files.pythonhosted.org/packages/38/d2/b833d95deb48c75db85bf6646de0a697e7fb5d87bd27cbade4f9746b48b1/aiohttp-3.10.10-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:007ec22fbc573e5eb2fb7dec4198ef8f6bf2fe4ce20020798b2eb5d0abda6138", size = 1312060 }, + { url = "https://files.pythonhosted.org/packages/aa/5f/29fd5113165a0893de8efedf9b4737e0ba92dfcd791415a528f947d10299/aiohttp-3.10.10-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9627cc1a10c8c409b5822a92d57a77f383b554463d1884008e051c32ab1b3742", size = 1350553 }, + { url = "https://files.pythonhosted.org/packages/ad/cc/f835f74b7d344428469200105236d44606cfa448be1e7c95ca52880d9bac/aiohttp-3.10.10-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:50edbcad60d8f0e3eccc68da67f37268b5144ecc34d59f27a02f9611c1d4eec7", size = 1392646 }, + { url = "https://files.pythonhosted.org/packages/bf/fe/1332409d845ca601893bbf2d76935e0b93d41686e5f333841c7d7a4a770d/aiohttp-3.10.10-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a45d85cf20b5e0d0aa5a8dca27cce8eddef3292bc29d72dcad1641f4ed50aa16", size = 1306310 }, + { url = "https://files.pythonhosted.org/packages/e4/a1/25a7633a5a513278a9892e333501e2e69c83e50be4b57a62285fb7a008c3/aiohttp-3.10.10-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0b00807e2605f16e1e198f33a53ce3c4523114059b0c09c337209ae55e3823a8", size = 1260255 }, + { url = "https://files.pythonhosted.org/packages/f2/39/30eafe89e0e2a06c25e4762844c8214c0c0cd0fd9ffc3471694a7986f421/aiohttp-3.10.10-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:f2d4324a98062be0525d16f768a03e0bbb3b9fe301ceee99611dc9a7953124e6", size = 1271141 }, + { url = "https://files.pythonhosted.org/packages/5b/fc/33125df728b48391ef1fcb512dfb02072158cc10d041414fb79803463020/aiohttp-3.10.10-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:438cd072f75bb6612f2aca29f8bd7cdf6e35e8f160bc312e49fbecab77c99e3a", size = 1280244 }, + { url = "https://files.pythonhosted.org/packages/3b/61/e42bf2c2934b5caa4e2ec0b5e5fd86989adb022b5ee60c2572a9d77cf6fe/aiohttp-3.10.10-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:baa42524a82f75303f714108fea528ccacf0386af429b69fff141ffef1c534f9", size = 1316805 }, + { url = "https://files.pythonhosted.org/packages/18/32/f52a5e2ae9ad3bba10e026a63a7a23abfa37c7d97aeeb9004eaa98df3ce3/aiohttp-3.10.10-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:a7d8d14fe962153fc681f6366bdec33d4356f98a3e3567782aac1b6e0e40109a", size = 1343930 }, + { url = "https://files.pythonhosted.org/packages/05/be/6a403b464dcab3631fe8e27b0f1d906d9e45c5e92aca97ee007e5a895560/aiohttp-3.10.10-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:c1277cd707c465cd09572a774559a3cc7c7a28802eb3a2a9472588f062097205", size = 1306186 }, + { url = "https://files.pythonhosted.org/packages/8e/fd/bb50fe781068a736a02bf5c7ad5f3ab53e39f1d1e63110da6d30f7605edc/aiohttp-3.10.10-cp312-cp312-win32.whl", hash = "sha256:59bb3c54aa420521dc4ce3cc2c3fe2ad82adf7b09403fa1f48ae45c0cbde6628", size = 359289 }, + { url = "https://files.pythonhosted.org/packages/70/9e/5add7e240f77ef67c275c82cc1d08afbca57b77593118c1f6e920ae8ad3f/aiohttp-3.10.10-cp312-cp312-win_amd64.whl", hash = "sha256:0e1b370d8007c4ae31ee6db7f9a2fe801a42b146cec80a86766e7ad5c4a259cf", size = 379313 }, + { url = "https://files.pythonhosted.org/packages/b1/eb/618b1b76c7fe8082a71c9d62e3fe84c5b9af6703078caa9ec57850a12080/aiohttp-3.10.10-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:ad7593bb24b2ab09e65e8a1d385606f0f47c65b5a2ae6c551db67d6653e78c28", size = 576114 }, + { url = "https://files.pythonhosted.org/packages/aa/37/3126995d7869f8b30d05381b81a2d4fb4ec6ad313db788e009bc6d39c211/aiohttp-3.10.10-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:1eb89d3d29adaf533588f209768a9c02e44e4baf832b08118749c5fad191781d", size = 391901 }, + { url = "https://files.pythonhosted.org/packages/3e/f2/8fdfc845be1f811c31ceb797968523813f8e1263ee3e9120d61253f6848f/aiohttp-3.10.10-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:3fe407bf93533a6fa82dece0e74dbcaaf5d684e5a51862887f9eaebe6372cd79", size = 387418 }, + { url = "https://files.pythonhosted.org/packages/60/d5/33d2061d36bf07e80286e04b7e0a4de37ce04b5ebfed72dba67659a05250/aiohttp-3.10.10-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:50aed5155f819873d23520919e16703fc8925e509abbb1a1491b0087d1cd969e", size = 1287073 }, + { url = "https://files.pythonhosted.org/packages/00/52/affb55be16a4747740bd630b4c002dac6c5eac42f9bb64202fc3cf3f1930/aiohttp-3.10.10-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4f05e9727ce409358baa615dbeb9b969db94324a79b5a5cea45d39bdb01d82e6", size = 1323612 }, + { url = "https://files.pythonhosted.org/packages/94/f2/cddb69b975387daa2182a8442566971d6410b8a0179bb4540d81c97b1611/aiohttp-3.10.10-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3dffb610a30d643983aeb185ce134f97f290f8935f0abccdd32c77bed9388b42", size = 1368406 }, + { url = "https://files.pythonhosted.org/packages/c1/e4/afba7327da4d932da8c6e29aecaf855f9d52dace53ac15bfc8030a246f1b/aiohttp-3.10.10-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:aa6658732517ddabe22c9036479eabce6036655ba87a0224c612e1ae6af2087e", size = 1282761 }, + { url = "https://files.pythonhosted.org/packages/9f/6b/364856faa0c9031ea76e24ef0f7fef79cddd9fa8e7dba9a1771c6acc56b5/aiohttp-3.10.10-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:741a46d58677d8c733175d7e5aa618d277cd9d880301a380fd296975a9cdd7bc", size = 1236518 }, + { url = "https://files.pythonhosted.org/packages/46/af/c382846f8356fe64a7b5908bb9b477457aa23b71be7ed551013b7b7d4d87/aiohttp-3.10.10-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:e00e3505cd80440f6c98c6d69269dcc2a119f86ad0a9fd70bccc59504bebd68a", size = 1250344 }, + { url = "https://files.pythonhosted.org/packages/87/53/294f87fc086fd0772d0ab82497beb9df67f0f27a8b3dd5742a2656db2bc6/aiohttp-3.10.10-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:ffe595f10566f8276b76dc3a11ae4bb7eba1aac8ddd75811736a15b0d5311414", size = 1248956 }, + { url = "https://files.pythonhosted.org/packages/86/30/7d746717fe11bdfefb88bb6c09c5fc985d85c4632da8bb6018e273899254/aiohttp-3.10.10-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:bdfcf6443637c148c4e1a20c48c566aa694fa5e288d34b20fcdc58507882fed3", size = 1293379 }, + { url = "https://files.pythonhosted.org/packages/48/b9/45d670a834458db67a24258e9139ba61fa3bd7d69b98ecf3650c22806f8f/aiohttp-3.10.10-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:d183cf9c797a5291e8301790ed6d053480ed94070637bfaad914dd38b0981f67", size = 1320108 }, + { url = "https://files.pythonhosted.org/packages/72/8c/804bb2e837a175635d2000a0659eafc15b2e9d92d3d81c8f69e141ecd0b0/aiohttp-3.10.10-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:77abf6665ae54000b98b3c742bc6ea1d1fb31c394bcabf8b5d2c1ac3ebfe7f3b", size = 1281546 }, + { url = "https://files.pythonhosted.org/packages/89/c0/862e6a9de3d6eeb126cd9d9ea388243b70df9b871ce1a42b193b7a4a77fc/aiohttp-3.10.10-cp313-cp313-win32.whl", hash = "sha256:4470c73c12cd9109db8277287d11f9dd98f77fc54155fc71a7738a83ffcc8ea8", size = 357516 }, + { url = "https://files.pythonhosted.org/packages/ae/63/3e1aee3e554263f3f1011cca50d78a4894ae16ce99bf78101ac3a2f0ef74/aiohttp-3.10.10-cp313-cp313-win_amd64.whl", hash = "sha256:486f7aabfa292719a2753c016cc3a8f8172965cabb3ea2e7f7436c7f5a22a151", size = 376785 }, ] [[package]] @@ -148,7 +149,7 @@ wheels = [ [[package]] name = "anyio" -version = "4.6.0" +version = "4.6.2.post1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "exceptiongroup", marker = "python_full_version < '3.11'" }, @@ -156,9 +157,9 @@ dependencies = [ { name = "sniffio" }, { name = "typing-extensions", marker = "python_full_version < '3.11'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/78/49/f3f17ec11c4a91fe79275c426658e509b07547f874b14c1a526d86a83fc8/anyio-4.6.0.tar.gz", hash = "sha256:137b4559cbb034c477165047febb6ff83f390fc3b20bf181c1fc0a728cb8beeb", size = 170983 } +sdist = { url = "https://files.pythonhosted.org/packages/9f/09/45b9b7a6d4e45c6bcb5bf61d19e3ab87df68e0601fa8c5293de3542546cc/anyio-4.6.2.post1.tar.gz", hash = "sha256:4c8bc31ccdb51c7f7bd251f51c609e038d63e34219b44aa86e47576389880b4c", size = 173422 } wheels = [ - { url = "https://files.pythonhosted.org/packages/9e/ef/7a4f225581a0d7886ea28359179cb861d7fbcdefad29663fc1167b86f69f/anyio-4.6.0-py3-none-any.whl", hash = "sha256:c7d2e9d63e31599eeb636c8c5c03a7e108d73b345f064f1c19fdc87b79036a9a", size = 89631 }, + { url = "https://files.pythonhosted.org/packages/e4/f5/f2b75d2fc6f1a260f340f0e7c6a060f4dd2961cc16884ed851b0d18da06a/anyio-4.6.2.post1-py3-none-any.whl", hash = "sha256:6d170c36fba3bdd840c73d3868c1e777e33676a69c3a72cf0a0d5d6d8009b61d", size = 90377 }, ] [[package]] @@ -205,11 +206,11 @@ wheels = [ [[package]] name = "attrs" -version = "23.2.0" +version = "24.2.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/e3/fc/f800d51204003fa8ae392c4e8278f256206e7a919b708eef054f5f4b650d/attrs-23.2.0.tar.gz", hash = "sha256:935dc3b529c262f6cf76e50877d35a4bd3c1de194fd41f47a2b7ae8f19971f30", size = 780820 } +sdist = { url = "https://files.pythonhosted.org/packages/fc/0f/aafca9af9315aee06a89ffde799a10a582fe8de76c563ee80bbcdc08b3fb/attrs-24.2.0.tar.gz", hash = "sha256:5cfb1b9148b5b086569baec03f20d7b6bf3bcacc9a42bebf87ffaaca362f6346", size = 792678 } wheels = [ - { url = "https://files.pythonhosted.org/packages/e0/44/827b2a91a5816512fcaf3cc4ebc465ccd5d598c45cefa6703fcf4a79018f/attrs-23.2.0-py3-none-any.whl", hash = "sha256:99b87a485a5820b23b879f04c2305b44b951b502fd64be915879d77a7e8fc6f1", size = 60752 }, + { url = "https://files.pythonhosted.org/packages/6a/21/5b6702a7f963e95456c0de2d495f67bf5fd62840ac655dc451586d23d39a/attrs-24.2.0-py3-none-any.whl", hash = "sha256:81921eb96de3191c8258c199618104dd27ac608d9366f5e35d011eae1867ede2", size = 63001 }, ] [[package]] @@ -290,37 +291,9 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/b1/fe/e8c672695b37eecc5cbf43e1d0638d88d66ba3a44c4d321c796f4e59167f/beautifulsoup4-4.12.3-py3-none-any.whl", hash = "sha256:b80878c9f40111313e55da8ba20bdba06d8fa3969fc68304167741bbf9e082ed", size = 147925 }, ] -[[package]] -name = "boto3" -version = "1.35.32" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "botocore" }, - { name = "jmespath" }, - { name = "s3transfer" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/08/ff/9942108141a84f623fb79609b584c012b8285b4fcaf811956ac1f88df4d3/boto3-1.35.32.tar.gz", hash = "sha256:a7652962897340d34bc930ffc9311dcc441da975dd1b904d0172b06adbea3601", size = 110976 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/08/ef/f61364f552ed309c7f0cddbc4f352f574d28e6d98f7019290590c8e3f605/boto3-1.35.32-py3-none-any.whl", hash = "sha256:786a243f4b4827c6ae149442bf544c2ae449570cf23616a5d386f7a2633e0e08", size = 139140 }, -] - -[[package]] -name = "botocore" -version = "1.35.32" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "jmespath" }, - { name = "python-dateutil" }, - { name = "urllib3" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/81/fd/e7af84a98a14cd2708b93377cd5816e95b96a0205191746f14a48c1b77a3/botocore-1.35.32.tar.gz", hash = "sha256:5ecbcd2f112e991b1f95c88ebb0f8df1a7c4ad9681aff80ec77e67cc4836eaa9", size = 12790515 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/1e/00/c275d270ec02d1ea6e65c416a22f2d37a5f5b32445b0ffbd0eaf1940f9a7/botocore-1.35.32-py3-none-any.whl", hash = "sha256:2c0c2b62dd156daf904525f3f523ae22bf34ac109d727acf0bbfbca291440fc3", size = 12578543 }, -] - [[package]] name = "build" -version = "1.2.2" +version = "1.2.2.post1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "colorama", marker = "os_name == 'nt'" }, @@ -329,9 +302,9 @@ dependencies = [ { name = "pyproject-hooks" }, { name = "tomli", marker = "python_full_version < '3.11'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/dd/bb/4a1b7e3a7520e310cf7bfece43788071604e1ccf693a7f0c4638c59068d6/build-1.2.2.tar.gz", hash = "sha256:119b2fb462adef986483438377a13b2f42064a2a3a4161f24a0cca698a07ac8c", size = 46516 } +sdist = { url = "https://files.pythonhosted.org/packages/7d/46/aeab111f8e06793e4f0e421fcad593d547fb8313b50990f31681ee2fb1ad/build-1.2.2.post1.tar.gz", hash = "sha256:b36993e92ca9375a219c99e606a122ff365a760a2d4bba0caa09bd5278b608b7", size = 46701 } wheels = [ - { url = "https://files.pythonhosted.org/packages/91/fd/e4bda6228637ecae5732162b5ac2a5a822e2ba8e546eb4997cde51b231a3/build-1.2.2-py3-none-any.whl", hash = "sha256:277ccc71619d98afdd841a0e96ac9fe1593b823af481d3b0cea748e8894e0613", size = 22823 }, + { url = "https://files.pythonhosted.org/packages/84/c2/80633736cd183ee4a62107413def345f7e6e3c01563dbca1417363cf957e/build-1.2.2.post1-py3-none-any.whl", hash = "sha256:1d61c0887fa860c01971625baae8bdd338e517b836a2f70dd1f7aa3a6b2fc5b5", size = 22950 }, ] [[package]] @@ -448,56 +421,71 @@ wheels = [ [[package]] name = "charset-normalizer" -version = "3.3.2" +version = "3.4.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/63/09/c1bc53dab74b1816a00d8d030de5bf98f724c52c1635e07681d312f20be8/charset-normalizer-3.3.2.tar.gz", hash = "sha256:f30c3cb33b24454a82faecaf01b19c18562b1e89558fb6c56de4d9118a032fd5", size = 104809 } +sdist = { url = "https://files.pythonhosted.org/packages/f2/4f/e1808dc01273379acc506d18f1504eb2d299bd4131743b9fc54d7be4df1e/charset_normalizer-3.4.0.tar.gz", hash = "sha256:223217c3d4f82c3ac5e29032b3f1c2eb0fb591b72161f86d93f5719079dae93e", size = 106620 } wheels = [ - { url = "https://files.pythonhosted.org/packages/2b/61/095a0aa1a84d1481998b534177c8566fdc50bb1233ea9a0478cd3cc075bd/charset_normalizer-3.3.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:25baf083bf6f6b341f4121c2f3c548875ee6f5339300e08be3f2b2ba1721cdd3", size = 194219 }, - { url = "https://files.pythonhosted.org/packages/cc/94/f7cf5e5134175de79ad2059edf2adce18e0685ebdb9227ff0139975d0e93/charset_normalizer-3.3.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:06435b539f889b1f6f4ac1758871aae42dc3a8c0e24ac9e60c2384973ad73027", size = 122521 }, - { url = "https://files.pythonhosted.org/packages/46/6a/d5c26c41c49b546860cc1acabdddf48b0b3fb2685f4f5617ac59261b44ae/charset_normalizer-3.3.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:9063e24fdb1e498ab71cb7419e24622516c4a04476b17a2dab57e8baa30d6e03", size = 120383 }, - { url = "https://files.pythonhosted.org/packages/b8/60/e2f67915a51be59d4539ed189eb0a2b0d292bf79270410746becb32bc2c3/charset_normalizer-3.3.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6897af51655e3691ff853668779c7bad41579facacf5fd7253b0133308cf000d", size = 138223 }, - { url = "https://files.pythonhosted.org/packages/05/8c/eb854996d5fef5e4f33ad56927ad053d04dc820e4a3d39023f35cad72617/charset_normalizer-3.3.2-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1d3193f4a680c64b4b6a9115943538edb896edc190f0b222e73761716519268e", size = 148101 }, - { url = "https://files.pythonhosted.org/packages/f6/93/bb6cbeec3bf9da9b2eba458c15966658d1daa8b982c642f81c93ad9b40e1/charset_normalizer-3.3.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:cd70574b12bb8a4d2aaa0094515df2463cb429d8536cfb6c7ce983246983e5a6", size = 140699 }, - { url = "https://files.pythonhosted.org/packages/da/f1/3702ba2a7470666a62fd81c58a4c40be00670e5006a67f4d626e57f013ae/charset_normalizer-3.3.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8465322196c8b4d7ab6d1e049e4c5cb460d0394da4a27d23cc242fbf0034b6b5", size = 142065 }, - { url = "https://files.pythonhosted.org/packages/3f/ba/3f5e7be00b215fa10e13d64b1f6237eb6ebea66676a41b2bcdd09fe74323/charset_normalizer-3.3.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a9a8e9031d613fd2009c182b69c7b2c1ef8239a0efb1df3f7c8da66d5dd3d537", size = 144505 }, - { url = "https://files.pythonhosted.org/packages/33/c3/3b96a435c5109dd5b6adc8a59ba1d678b302a97938f032e3770cc84cd354/charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:beb58fe5cdb101e3a055192ac291b7a21e3b7ef4f67fa1d74e331a7f2124341c", size = 139425 }, - { url = "https://files.pythonhosted.org/packages/43/05/3bf613e719efe68fb3a77f9c536a389f35b95d75424b96b426a47a45ef1d/charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:e06ed3eb3218bc64786f7db41917d4e686cc4856944f53d5bdf83a6884432e12", size = 145287 }, - { url = "https://files.pythonhosted.org/packages/58/78/a0bc646900994df12e07b4ae5c713f2b3e5998f58b9d3720cce2aa45652f/charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:2e81c7b9c8979ce92ed306c249d46894776a909505d8f5a4ba55b14206e3222f", size = 149929 }, - { url = "https://files.pythonhosted.org/packages/eb/5c/97d97248af4920bc68687d9c3b3c0f47c910e21a8ff80af4565a576bd2f0/charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:572c3763a264ba47b3cf708a44ce965d98555f618ca42c926a9c1616d8f34269", size = 141605 }, - { url = "https://files.pythonhosted.org/packages/a8/31/47d018ef89f95b8aded95c589a77c072c55e94b50a41aa99c0a2008a45a4/charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:fd1abc0d89e30cc4e02e4064dc67fcc51bd941eb395c502aac3ec19fab46b519", size = 142646 }, - { url = "https://files.pythonhosted.org/packages/ae/d5/4fecf1d58bedb1340a50f165ba1c7ddc0400252d6832ff619c4568b36cc0/charset_normalizer-3.3.2-cp310-cp310-win32.whl", hash = "sha256:3d47fa203a7bd9c5b6cee4736ee84ca03b8ef23193c0d1ca99b5089f72645c73", size = 92846 }, - { url = "https://files.pythonhosted.org/packages/a2/a0/4af29e22cb5942488cf45630cbdd7cefd908768e69bdd90280842e4e8529/charset_normalizer-3.3.2-cp310-cp310-win_amd64.whl", hash = "sha256:10955842570876604d404661fbccbc9c7e684caf432c09c715ec38fbae45ae09", size = 100343 }, - { url = "https://files.pythonhosted.org/packages/68/77/02839016f6fbbf808e8b38601df6e0e66c17bbab76dff4613f7511413597/charset_normalizer-3.3.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:802fe99cca7457642125a8a88a084cef28ff0cf9407060f7b93dca5aa25480db", size = 191647 }, - { url = "https://files.pythonhosted.org/packages/3e/33/21a875a61057165e92227466e54ee076b73af1e21fe1b31f1e292251aa1e/charset_normalizer-3.3.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:573f6eac48f4769d667c4442081b1794f52919e7edada77495aaed9236d13a96", size = 121434 }, - { url = "https://files.pythonhosted.org/packages/dd/51/68b61b90b24ca35495956b718f35a9756ef7d3dd4b3c1508056fa98d1a1b/charset_normalizer-3.3.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:549a3a73da901d5bc3ce8d24e0600d1fa85524c10287f6004fbab87672bf3e1e", size = 118979 }, - { url = "https://files.pythonhosted.org/packages/e4/a6/7ee57823d46331ddc37dd00749c95b0edec2c79b15fc0d6e6efb532e89ac/charset_normalizer-3.3.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f27273b60488abe721a075bcca6d7f3964f9f6f067c8c4c605743023d7d3944f", size = 136582 }, - { url = "https://files.pythonhosted.org/packages/74/f1/0d9fe69ac441467b737ba7f48c68241487df2f4522dd7246d9426e7c690e/charset_normalizer-3.3.2-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1ceae2f17a9c33cb48e3263960dc5fc8005351ee19db217e9b1bb15d28c02574", size = 146645 }, - { url = "https://files.pythonhosted.org/packages/05/31/e1f51c76db7be1d4aef220d29fbfa5dbb4a99165d9833dcbf166753b6dc0/charset_normalizer-3.3.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:65f6f63034100ead094b8744b3b97965785388f308a64cf8d7c34f2f2e5be0c4", size = 139398 }, - { url = "https://files.pythonhosted.org/packages/40/26/f35951c45070edc957ba40a5b1db3cf60a9dbb1b350c2d5bef03e01e61de/charset_normalizer-3.3.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:753f10e867343b4511128c6ed8c82f7bec3bd026875576dfd88483c5c73b2fd8", size = 140273 }, - { url = "https://files.pythonhosted.org/packages/07/07/7e554f2bbce3295e191f7e653ff15d55309a9ca40d0362fcdab36f01063c/charset_normalizer-3.3.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4a78b2b446bd7c934f5dcedc588903fb2f5eec172f3d29e52a9096a43722adfc", size = 142577 }, - { url = "https://files.pythonhosted.org/packages/d8/b5/eb705c313100defa57da79277d9207dc8d8e45931035862fa64b625bfead/charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:e537484df0d8f426ce2afb2d0f8e1c3d0b114b83f8850e5f2fbea0e797bd82ae", size = 137747 }, - { url = "https://files.pythonhosted.org/packages/19/28/573147271fd041d351b438a5665be8223f1dd92f273713cb882ddafe214c/charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:eb6904c354526e758fda7167b33005998fb68c46fbc10e013ca97f21ca5c8887", size = 143375 }, - { url = "https://files.pythonhosted.org/packages/cf/7c/f3b682fa053cc21373c9a839e6beba7705857075686a05c72e0f8c4980ca/charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:deb6be0ac38ece9ba87dea880e438f25ca3eddfac8b002a2ec3d9183a454e8ae", size = 148474 }, - { url = "https://files.pythonhosted.org/packages/1e/49/7ab74d4ac537ece3bc3334ee08645e231f39f7d6df6347b29a74b0537103/charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:4ab2fe47fae9e0f9dee8c04187ce5d09f48eabe611be8259444906793ab7cbce", size = 140232 }, - { url = "https://files.pythonhosted.org/packages/2d/dc/9dacba68c9ac0ae781d40e1a0c0058e26302ea0660e574ddf6797a0347f7/charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:80402cd6ee291dcb72644d6eac93785fe2c8b9cb30893c1af5b8fdd753b9d40f", size = 140859 }, - { url = "https://files.pythonhosted.org/packages/6c/c2/4a583f800c0708dd22096298e49f887b49d9746d0e78bfc1d7e29816614c/charset_normalizer-3.3.2-cp311-cp311-win32.whl", hash = "sha256:7cd13a2e3ddeed6913a65e66e94b51d80a041145a026c27e6bb76c31a853c6ab", size = 92509 }, - { url = "https://files.pythonhosted.org/packages/57/ec/80c8d48ac8b1741d5b963797b7c0c869335619e13d4744ca2f67fc11c6fc/charset_normalizer-3.3.2-cp311-cp311-win_amd64.whl", hash = "sha256:663946639d296df6a2bb2aa51b60a2454ca1cb29835324c640dafb5ff2131a77", size = 99870 }, - { url = "https://files.pythonhosted.org/packages/d1/b2/fcedc8255ec42afee97f9e6f0145c734bbe104aac28300214593eb326f1d/charset_normalizer-3.3.2-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:0b2b64d2bb6d3fb9112bafa732def486049e63de9618b5843bcdd081d8144cd8", size = 192892 }, - { url = "https://files.pythonhosted.org/packages/2e/7d/2259318c202f3d17f3fe6438149b3b9e706d1070fe3fcbb28049730bb25c/charset_normalizer-3.3.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:ddbb2551d7e0102e7252db79ba445cdab71b26640817ab1e3e3648dad515003b", size = 122213 }, - { url = "https://files.pythonhosted.org/packages/3a/52/9f9d17c3b54dc238de384c4cb5a2ef0e27985b42a0e5cc8e8a31d918d48d/charset_normalizer-3.3.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:55086ee1064215781fff39a1af09518bc9255b50d6333f2e4c74ca09fac6a8f6", size = 119404 }, - { url = "https://files.pythonhosted.org/packages/99/b0/9c365f6d79a9f0f3c379ddb40a256a67aa69c59609608fe7feb6235896e1/charset_normalizer-3.3.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8f4a014bc36d3c57402e2977dada34f9c12300af536839dc38c0beab8878f38a", size = 137275 }, - { url = "https://files.pythonhosted.org/packages/91/33/749df346e93d7a30cdcb90cbfdd41a06026317bfbfb62cd68307c1a3c543/charset_normalizer-3.3.2-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a10af20b82360ab00827f916a6058451b723b4e65030c5a18577c8b2de5b3389", size = 147518 }, - { url = "https://files.pythonhosted.org/packages/72/1a/641d5c9f59e6af4c7b53da463d07600a695b9824e20849cb6eea8a627761/charset_normalizer-3.3.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8d756e44e94489e49571086ef83b2bb8ce311e730092d2c34ca8f7d925cb20aa", size = 140182 }, - { url = "https://files.pythonhosted.org/packages/ee/fb/14d30eb4956408ee3ae09ad34299131fb383c47df355ddb428a7331cfa1e/charset_normalizer-3.3.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:90d558489962fd4918143277a773316e56c72da56ec7aa3dc3dbbe20fdfed15b", size = 141869 }, - { url = "https://files.pythonhosted.org/packages/df/3e/a06b18788ca2eb6695c9b22325b6fde7dde0f1d1838b1792a0076f58fe9d/charset_normalizer-3.3.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6ac7ffc7ad6d040517be39eb591cac5ff87416c2537df6ba3cba3bae290c0fed", size = 144042 }, - { url = "https://files.pythonhosted.org/packages/45/59/3d27019d3b447a88fe7e7d004a1e04be220227760264cc41b405e863891b/charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:7ed9e526742851e8d5cc9e6cf41427dfc6068d4f5a3bb03659444b4cabf6bc26", size = 138275 }, - { url = "https://files.pythonhosted.org/packages/7b/ef/5eb105530b4da8ae37d506ccfa25057961b7b63d581def6f99165ea89c7e/charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:8bdb58ff7ba23002a4c5808d608e4e6c687175724f54a5dade5fa8c67b604e4d", size = 144819 }, - { url = "https://files.pythonhosted.org/packages/a2/51/e5023f937d7f307c948ed3e5c29c4b7a3e42ed2ee0b8cdf8f3a706089bf0/charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_ppc64le.whl", hash = "sha256:6b3251890fff30ee142c44144871185dbe13b11bab478a88887a639655be1068", size = 149415 }, - { url = "https://files.pythonhosted.org/packages/24/9d/2e3ef673dfd5be0154b20363c5cdcc5606f35666544381bee15af3778239/charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_s390x.whl", hash = "sha256:b4a23f61ce87adf89be746c8a8974fe1c823c891d8f86eb218bb957c924bb143", size = 141212 }, - { url = "https://files.pythonhosted.org/packages/5b/ae/ce2c12fcac59cb3860b2e2d76dc405253a4475436b1861d95fe75bdea520/charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:efcb3f6676480691518c177e3b465bcddf57cea040302f9f4e6e191af91174d4", size = 142167 }, - { url = "https://files.pythonhosted.org/packages/ed/3a/a448bf035dce5da359daf9ae8a16b8a39623cc395a2ffb1620aa1bce62b0/charset_normalizer-3.3.2-cp312-cp312-win32.whl", hash = "sha256:d965bba47ddeec8cd560687584e88cf699fd28f192ceb452d1d7ee807c5597b7", size = 93041 }, - { url = "https://files.pythonhosted.org/packages/b6/7c/8debebb4f90174074b827c63242c23851bdf00a532489fba57fef3416e40/charset_normalizer-3.3.2-cp312-cp312-win_amd64.whl", hash = "sha256:96b02a3dc4381e5494fad39be677abcb5e6634bf7b4fa83a6dd3112607547001", size = 100397 }, - { url = "https://files.pythonhosted.org/packages/28/76/e6222113b83e3622caa4bb41032d0b1bf785250607392e1b778aca0b8a7d/charset_normalizer-3.3.2-py3-none-any.whl", hash = "sha256:3e4d1f6587322d2788836a99c69062fbb091331ec940e02d12d179c1d53e25fc", size = 48543 }, + { url = "https://files.pythonhosted.org/packages/69/8b/825cc84cf13a28bfbcba7c416ec22bf85a9584971be15b21dd8300c65b7f/charset_normalizer-3.4.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:4f9fc98dad6c2eaa32fc3af1417d95b5e3d08aff968df0cd320066def971f9a6", size = 196363 }, + { url = "https://files.pythonhosted.org/packages/23/81/d7eef6a99e42c77f444fdd7bc894b0ceca6c3a95c51239e74a722039521c/charset_normalizer-3.4.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:0de7b687289d3c1b3e8660d0741874abe7888100efe14bd0f9fd7141bcbda92b", size = 125639 }, + { url = "https://files.pythonhosted.org/packages/21/67/b4564d81f48042f520c948abac7079356e94b30cb8ffb22e747532cf469d/charset_normalizer-3.4.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:5ed2e36c3e9b4f21dd9422f6893dec0abf2cca553af509b10cd630f878d3eb99", size = 120451 }, + { url = "https://files.pythonhosted.org/packages/c2/72/12a7f0943dd71fb5b4e7b55c41327ac0a1663046a868ee4d0d8e9c369b85/charset_normalizer-3.4.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:40d3ff7fc90b98c637bda91c89d51264a3dcf210cade3a2c6f838c7268d7a4ca", size = 140041 }, + { url = "https://files.pythonhosted.org/packages/67/56/fa28c2c3e31217c4c52158537a2cf5d98a6c1e89d31faf476c89391cd16b/charset_normalizer-3.4.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1110e22af8ca26b90bd6364fe4c763329b0ebf1ee213ba32b68c73de5752323d", size = 150333 }, + { url = "https://files.pythonhosted.org/packages/f9/d2/466a9be1f32d89eb1554cf84073a5ed9262047acee1ab39cbaefc19635d2/charset_normalizer-3.4.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:86f4e8cca779080f66ff4f191a685ced73d2f72d50216f7112185dc02b90b9b7", size = 142921 }, + { url = "https://files.pythonhosted.org/packages/f8/01/344ec40cf5d85c1da3c1f57566c59e0c9b56bcc5566c08804a95a6cc8257/charset_normalizer-3.4.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7f683ddc7eedd742e2889d2bfb96d69573fde1d92fcb811979cdb7165bb9c7d3", size = 144785 }, + { url = "https://files.pythonhosted.org/packages/73/8b/2102692cb6d7e9f03b9a33a710e0164cadfce312872e3efc7cfe22ed26b4/charset_normalizer-3.4.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:27623ba66c183eca01bf9ff833875b459cad267aeeb044477fedac35e19ba907", size = 146631 }, + { url = "https://files.pythonhosted.org/packages/d8/96/cc2c1b5d994119ce9f088a9a0c3ebd489d360a2eb058e2c8049f27092847/charset_normalizer-3.4.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:f606a1881d2663630ea5b8ce2efe2111740df4b687bd78b34a8131baa007f79b", size = 140867 }, + { url = "https://files.pythonhosted.org/packages/c9/27/cde291783715b8ec30a61c810d0120411844bc4c23b50189b81188b273db/charset_normalizer-3.4.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:0b309d1747110feb25d7ed6b01afdec269c647d382c857ef4663bbe6ad95a912", size = 149273 }, + { url = "https://files.pythonhosted.org/packages/3a/a4/8633b0fc1a2d1834d5393dafecce4a1cc56727bfd82b4dc18fc92f0d3cc3/charset_normalizer-3.4.0-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:136815f06a3ae311fae551c3df1f998a1ebd01ddd424aa5603a4336997629e95", size = 152437 }, + { url = "https://files.pythonhosted.org/packages/64/ea/69af161062166b5975ccbb0961fd2384853190c70786f288684490913bf5/charset_normalizer-3.4.0-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:14215b71a762336254351b00ec720a8e85cada43b987da5a042e4ce3e82bd68e", size = 150087 }, + { url = "https://files.pythonhosted.org/packages/3b/fd/e60a9d9fd967f4ad5a92810138192f825d77b4fa2a557990fd575a47695b/charset_normalizer-3.4.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:79983512b108e4a164b9c8d34de3992f76d48cadc9554c9e60b43f308988aabe", size = 145142 }, + { url = "https://files.pythonhosted.org/packages/6d/02/8cb0988a1e49ac9ce2eed1e07b77ff118f2923e9ebd0ede41ba85f2dcb04/charset_normalizer-3.4.0-cp310-cp310-win32.whl", hash = "sha256:c94057af19bc953643a33581844649a7fdab902624d2eb739738a30e2b3e60fc", size = 94701 }, + { url = "https://files.pythonhosted.org/packages/d6/20/f1d4670a8a723c46be695dff449d86d6092916f9e99c53051954ee33a1bc/charset_normalizer-3.4.0-cp310-cp310-win_amd64.whl", hash = "sha256:55f56e2ebd4e3bc50442fbc0888c9d8c94e4e06a933804e2af3e89e2f9c1c749", size = 102191 }, + { url = "https://files.pythonhosted.org/packages/9c/61/73589dcc7a719582bf56aae309b6103d2762b526bffe189d635a7fcfd998/charset_normalizer-3.4.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:0d99dd8ff461990f12d6e42c7347fd9ab2532fb70e9621ba520f9e8637161d7c", size = 193339 }, + { url = "https://files.pythonhosted.org/packages/77/d5/8c982d58144de49f59571f940e329ad6e8615e1e82ef84584c5eeb5e1d72/charset_normalizer-3.4.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:c57516e58fd17d03ebe67e181a4e4e2ccab1168f8c2976c6a334d4f819fe5944", size = 124366 }, + { url = "https://files.pythonhosted.org/packages/bf/19/411a64f01ee971bed3231111b69eb56f9331a769072de479eae7de52296d/charset_normalizer-3.4.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:6dba5d19c4dfab08e58d5b36304b3f92f3bd5d42c1a3fa37b5ba5cdf6dfcbcee", size = 118874 }, + { url = "https://files.pythonhosted.org/packages/4c/92/97509850f0d00e9f14a46bc751daabd0ad7765cff29cdfb66c68b6dad57f/charset_normalizer-3.4.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bf4475b82be41b07cc5e5ff94810e6a01f276e37c2d55571e3fe175e467a1a1c", size = 138243 }, + { url = "https://files.pythonhosted.org/packages/e2/29/d227805bff72ed6d6cb1ce08eec707f7cfbd9868044893617eb331f16295/charset_normalizer-3.4.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ce031db0408e487fd2775d745ce30a7cd2923667cf3b69d48d219f1d8f5ddeb6", size = 148676 }, + { url = "https://files.pythonhosted.org/packages/13/bc/87c2c9f2c144bedfa62f894c3007cd4530ba4b5351acb10dc786428a50f0/charset_normalizer-3.4.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8ff4e7cdfdb1ab5698e675ca622e72d58a6fa2a8aa58195de0c0061288e6e3ea", size = 141289 }, + { url = "https://files.pythonhosted.org/packages/eb/5b/6f10bad0f6461fa272bfbbdf5d0023b5fb9bc6217c92bf068fa5a99820f5/charset_normalizer-3.4.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3710a9751938947e6327ea9f3ea6332a09bf0ba0c09cae9cb1f250bd1f1549bc", size = 142585 }, + { url = "https://files.pythonhosted.org/packages/3b/a0/a68980ab8a1f45a36d9745d35049c1af57d27255eff8c907e3add84cf68f/charset_normalizer-3.4.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:82357d85de703176b5587dbe6ade8ff67f9f69a41c0733cf2425378b49954de5", size = 144408 }, + { url = "https://files.pythonhosted.org/packages/d7/a1/493919799446464ed0299c8eef3c3fad0daf1c3cd48bff9263c731b0d9e2/charset_normalizer-3.4.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:47334db71978b23ebcf3c0f9f5ee98b8d65992b65c9c4f2d34c2eaf5bcaf0594", size = 139076 }, + { url = "https://files.pythonhosted.org/packages/fb/9d/9c13753a5a6e0db4a0a6edb1cef7aee39859177b64e1a1e748a6e3ba62c2/charset_normalizer-3.4.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:8ce7fd6767a1cc5a92a639b391891bf1c268b03ec7e021c7d6d902285259685c", size = 146874 }, + { url = "https://files.pythonhosted.org/packages/75/d2/0ab54463d3410709c09266dfb416d032a08f97fd7d60e94b8c6ef54ae14b/charset_normalizer-3.4.0-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:f1a2f519ae173b5b6a2c9d5fa3116ce16e48b3462c8b96dfdded11055e3d6365", size = 150871 }, + { url = "https://files.pythonhosted.org/packages/8d/c9/27e41d481557be53d51e60750b85aa40eaf52b841946b3cdeff363105737/charset_normalizer-3.4.0-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:63bc5c4ae26e4bc6be6469943b8253c0fd4e4186c43ad46e713ea61a0ba49129", size = 148546 }, + { url = "https://files.pythonhosted.org/packages/ee/44/4f62042ca8cdc0cabf87c0fc00ae27cd8b53ab68be3605ba6d071f742ad3/charset_normalizer-3.4.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:bcb4f8ea87d03bc51ad04add8ceaf9b0f085ac045ab4d74e73bbc2dc033f0236", size = 143048 }, + { url = "https://files.pythonhosted.org/packages/01/f8/38842422988b795220eb8038745d27a675ce066e2ada79516c118f291f07/charset_normalizer-3.4.0-cp311-cp311-win32.whl", hash = "sha256:9ae4ef0b3f6b41bad6366fb0ea4fc1d7ed051528e113a60fa2a65a9abb5b1d99", size = 94389 }, + { url = "https://files.pythonhosted.org/packages/0b/6e/b13bd47fa9023b3699e94abf565b5a2f0b0be6e9ddac9812182596ee62e4/charset_normalizer-3.4.0-cp311-cp311-win_amd64.whl", hash = "sha256:cee4373f4d3ad28f1ab6290684d8e2ebdb9e7a1b74fdc39e4c211995f77bec27", size = 101752 }, + { url = "https://files.pythonhosted.org/packages/d3/0b/4b7a70987abf9b8196845806198975b6aab4ce016632f817ad758a5aa056/charset_normalizer-3.4.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:0713f3adb9d03d49d365b70b84775d0a0d18e4ab08d12bc46baa6132ba78aaf6", size = 194445 }, + { url = "https://files.pythonhosted.org/packages/50/89/354cc56cf4dd2449715bc9a0f54f3aef3dc700d2d62d1fa5bbea53b13426/charset_normalizer-3.4.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:de7376c29d95d6719048c194a9cf1a1b0393fbe8488a22008610b0361d834ecf", size = 125275 }, + { url = "https://files.pythonhosted.org/packages/fa/44/b730e2a2580110ced837ac083d8ad222343c96bb6b66e9e4e706e4d0b6df/charset_normalizer-3.4.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:4a51b48f42d9358460b78725283f04bddaf44a9358197b889657deba38f329db", size = 119020 }, + { url = "https://files.pythonhosted.org/packages/9d/e4/9263b8240ed9472a2ae7ddc3e516e71ef46617fe40eaa51221ccd4ad9a27/charset_normalizer-3.4.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b295729485b06c1a0683af02a9e42d2caa9db04a373dc38a6a58cdd1e8abddf1", size = 139128 }, + { url = "https://files.pythonhosted.org/packages/6b/e3/9f73e779315a54334240353eaea75854a9a690f3f580e4bd85d977cb2204/charset_normalizer-3.4.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ee803480535c44e7f5ad00788526da7d85525cfefaf8acf8ab9a310000be4b03", size = 149277 }, + { url = "https://files.pythonhosted.org/packages/1a/cf/f1f50c2f295312edb8a548d3fa56a5c923b146cd3f24114d5adb7e7be558/charset_normalizer-3.4.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3d59d125ffbd6d552765510e3f31ed75ebac2c7470c7274195b9161a32350284", size = 142174 }, + { url = "https://files.pythonhosted.org/packages/16/92/92a76dc2ff3a12e69ba94e7e05168d37d0345fa08c87e1fe24d0c2a42223/charset_normalizer-3.4.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8cda06946eac330cbe6598f77bb54e690b4ca93f593dee1568ad22b04f347c15", size = 143838 }, + { url = "https://files.pythonhosted.org/packages/a4/01/2117ff2b1dfc61695daf2babe4a874bca328489afa85952440b59819e9d7/charset_normalizer-3.4.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:07afec21bbbbf8a5cc3651aa96b980afe2526e7f048fdfb7f1014d84acc8b6d8", size = 146149 }, + { url = "https://files.pythonhosted.org/packages/f6/9b/93a332b8d25b347f6839ca0a61b7f0287b0930216994e8bf67a75d050255/charset_normalizer-3.4.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:6b40e8d38afe634559e398cc32b1472f376a4099c75fe6299ae607e404c033b2", size = 140043 }, + { url = "https://files.pythonhosted.org/packages/ab/f6/7ac4a01adcdecbc7a7587767c776d53d369b8b971382b91211489535acf0/charset_normalizer-3.4.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:b8dcd239c743aa2f9c22ce674a145e0a25cb1566c495928440a181ca1ccf6719", size = 148229 }, + { url = "https://files.pythonhosted.org/packages/9d/be/5708ad18161dee7dc6a0f7e6cf3a88ea6279c3e8484844c0590e50e803ef/charset_normalizer-3.4.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:84450ba661fb96e9fd67629b93d2941c871ca86fc38d835d19d4225ff946a631", size = 151556 }, + { url = "https://files.pythonhosted.org/packages/5a/bb/3d8bc22bacb9eb89785e83e6723f9888265f3a0de3b9ce724d66bd49884e/charset_normalizer-3.4.0-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:44aeb140295a2f0659e113b31cfe92c9061622cadbc9e2a2f7b8ef6b1e29ef4b", size = 149772 }, + { url = "https://files.pythonhosted.org/packages/f7/fa/d3fc622de05a86f30beea5fc4e9ac46aead4731e73fd9055496732bcc0a4/charset_normalizer-3.4.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:1db4e7fefefd0f548d73e2e2e041f9df5c59e178b4c72fbac4cc6f535cfb1565", size = 144800 }, + { url = "https://files.pythonhosted.org/packages/9a/65/bdb9bc496d7d190d725e96816e20e2ae3a6fa42a5cac99c3c3d6ff884118/charset_normalizer-3.4.0-cp312-cp312-win32.whl", hash = "sha256:5726cf76c982532c1863fb64d8c6dd0e4c90b6ece9feb06c9f202417a31f7dd7", size = 94836 }, + { url = "https://files.pythonhosted.org/packages/3e/67/7b72b69d25b89c0b3cea583ee372c43aa24df15f0e0f8d3982c57804984b/charset_normalizer-3.4.0-cp312-cp312-win_amd64.whl", hash = "sha256:b197e7094f232959f8f20541ead1d9862ac5ebea1d58e9849c1bf979255dfac9", size = 102187 }, + { url = "https://files.pythonhosted.org/packages/f3/89/68a4c86f1a0002810a27f12e9a7b22feb198c59b2f05231349fbce5c06f4/charset_normalizer-3.4.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:dd4eda173a9fcccb5f2e2bd2a9f423d180194b1bf17cf59e3269899235b2a114", size = 194617 }, + { url = "https://files.pythonhosted.org/packages/4f/cd/8947fe425e2ab0aa57aceb7807af13a0e4162cd21eee42ef5b053447edf5/charset_normalizer-3.4.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:e9e3c4c9e1ed40ea53acf11e2a386383c3304212c965773704e4603d589343ed", size = 125310 }, + { url = "https://files.pythonhosted.org/packages/5b/f0/b5263e8668a4ee9becc2b451ed909e9c27058337fda5b8c49588183c267a/charset_normalizer-3.4.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:92a7e36b000bf022ef3dbb9c46bfe2d52c047d5e3f3343f43204263c5addc250", size = 119126 }, + { url = "https://files.pythonhosted.org/packages/ff/6e/e445afe4f7fda27a533f3234b627b3e515a1b9429bc981c9a5e2aa5d97b6/charset_normalizer-3.4.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:54b6a92d009cbe2fb11054ba694bc9e284dad30a26757b1e372a1fdddaf21920", size = 139342 }, + { url = "https://files.pythonhosted.org/packages/a1/b2/4af9993b532d93270538ad4926c8e37dc29f2111c36f9c629840c57cd9b3/charset_normalizer-3.4.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1ffd9493de4c922f2a38c2bf62b831dcec90ac673ed1ca182fe11b4d8e9f2a64", size = 149383 }, + { url = "https://files.pythonhosted.org/packages/fb/6f/4e78c3b97686b871db9be6f31d64e9264e889f8c9d7ab33c771f847f79b7/charset_normalizer-3.4.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:35c404d74c2926d0287fbd63ed5d27eb911eb9e4a3bb2c6d294f3cfd4a9e0c23", size = 142214 }, + { url = "https://files.pythonhosted.org/packages/2b/c9/1c8fe3ce05d30c87eff498592c89015b19fade13df42850aafae09e94f35/charset_normalizer-3.4.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4796efc4faf6b53a18e3d46343535caed491776a22af773f366534056c4e1fbc", size = 144104 }, + { url = "https://files.pythonhosted.org/packages/ee/68/efad5dcb306bf37db7db338338e7bb8ebd8cf38ee5bbd5ceaaaa46f257e6/charset_normalizer-3.4.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e7fdd52961feb4c96507aa649550ec2a0d527c086d284749b2f582f2d40a2e0d", size = 146255 }, + { url = "https://files.pythonhosted.org/packages/0c/75/1ed813c3ffd200b1f3e71121c95da3f79e6d2a96120163443b3ad1057505/charset_normalizer-3.4.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:92db3c28b5b2a273346bebb24857fda45601aef6ae1c011c0a997106581e8a88", size = 140251 }, + { url = "https://files.pythonhosted.org/packages/7d/0d/6f32255c1979653b448d3c709583557a4d24ff97ac4f3a5be156b2e6a210/charset_normalizer-3.4.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:ab973df98fc99ab39080bfb0eb3a925181454d7c3ac8a1e695fddfae696d9e90", size = 148474 }, + { url = "https://files.pythonhosted.org/packages/ac/a0/c1b5298de4670d997101fef95b97ac440e8c8d8b4efa5a4d1ef44af82f0d/charset_normalizer-3.4.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:4b67fdab07fdd3c10bb21edab3cbfe8cf5696f453afce75d815d9d7223fbe88b", size = 151849 }, + { url = "https://files.pythonhosted.org/packages/04/4f/b3961ba0c664989ba63e30595a3ed0875d6790ff26671e2aae2fdc28a399/charset_normalizer-3.4.0-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:aa41e526a5d4a9dfcfbab0716c7e8a1b215abd3f3df5a45cf18a12721d31cb5d", size = 149781 }, + { url = "https://files.pythonhosted.org/packages/d8/90/6af4cd042066a4adad58ae25648a12c09c879efa4849c705719ba1b23d8c/charset_normalizer-3.4.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:ffc519621dce0c767e96b9c53f09c5d215578e10b02c285809f76509a3931482", size = 144970 }, + { url = "https://files.pythonhosted.org/packages/cc/67/e5e7e0cbfefc4ca79025238b43cdf8a2037854195b37d6417f3d0895c4c2/charset_normalizer-3.4.0-cp313-cp313-win32.whl", hash = "sha256:f19c1585933c82098c2a520f8ec1227f20e339e33aca8fa6f956f6691b784e67", size = 94973 }, + { url = "https://files.pythonhosted.org/packages/65/97/fc9bbc54ee13d33dc54a7fcf17b26368b18505500fc01e228c27b5222d80/charset_normalizer-3.4.0-cp313-cp313-win_amd64.whl", hash = "sha256:707b82d19e65c9bd28b81dde95249b07bf9f5b90ebe1ef17d9b57473f8a64b7b", size = 102308 }, + { url = "https://files.pythonhosted.org/packages/bf/9b/08c0432272d77b04803958a4598a51e2a4b51c06640af8b8f0f908c18bf2/charset_normalizer-3.4.0-py3-none-any.whl", hash = "sha256:fe9f97feb71aa9896b81973a7bbada8c49501dc73e58a10fcef6663af95e5079", size = 49446 }, ] [[package]] @@ -572,21 +560,11 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/00/2e/d53fa4befbf2cfa713304affc7ca780ce4fc1fd8710527771b58311a3229/click-8.1.7-py3-none-any.whl", hash = "sha256:ae74fb96c20a0277a1d615f1e4d73c8414f5a98db8b799a7931d1582f3390c28", size = 97941 }, ] -[[package]] -name = "cloudpickle" -version = "2.2.1" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/5f/51/913ecca3970a2227cf4d5e8937df52cc28f465ac442216110b8e3323262d/cloudpickle-2.2.1.tar.gz", hash = "sha256:d89684b8de9e34a2a43b3460fbca07d09d6e25ce858df4d5a44240403b6178f5", size = 60800 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/15/80/44286939ca215e88fa827b2aeb6fa3fd2b4a7af322485c7170d6f9fd96e0/cloudpickle-2.2.1-py3-none-any.whl", hash = "sha256:61f594d1f4c295fa5cd9014ceb3a1fc4a70b0de1164b94fbc2d854ccba056f9f", size = 25944 }, -] - [[package]] name = "cohere" -version = "5.11.0" +version = "5.11.1" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "boto3" }, { name = "fastavro" }, { name = "httpx" }, { name = "httpx-sse" }, @@ -594,14 +572,13 @@ dependencies = [ { name = "pydantic" }, { name = "pydantic-core" }, { name = "requests" }, - { name = "sagemaker" }, { name = "tokenizers" }, { name = "types-requests" }, { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/fc/34/96e9422624fb8f2a03bf8d60fdc331eb948155eca95859080595f0fe3a13/cohere-5.11.0.tar.gz", hash = "sha256:2c4d50d78f59e4fb97a3626df8b34b19f4fa75e8c0e7c16935de2ddda6f6621e", size = 128900 } +sdist = { url = "https://files.pythonhosted.org/packages/c7/c1/eb774c5d7f74f62db5f41cd36b3f723b32d1ae11e09932e48a0b45a9e1ac/cohere-5.11.1.tar.gz", hash = "sha256:821e20593def7796d314be9bcba87e9ecf69dc6ef17172f842447275f8679d0f", size = 129383 } wheels = [ - { url = "https://files.pythonhosted.org/packages/5e/ac/e16f7bbdb370607e991270747ba85664c643057b06c76e5d7030acecd164/cohere-5.11.0-py3-none-any.whl", hash = "sha256:13c337c7f69c657d4a4984e26f06d907fbc562626a4bbe43ec134ed481f24924", size = 249246 }, + { url = "https://files.pythonhosted.org/packages/b9/a4/48fe3fc5a07a17b0534d5ed4c1f76364e7a9d49487e72636e6f34f2ea8f7/cohere-5.11.1-py3-none-any.whl", hash = "sha256:117c718bfbc7637cf22c1025e8e2bf820ebeef51f7fbb2b9d74f3e9c0a9c6c25", size = 249700 }, ] [[package]] @@ -627,7 +604,7 @@ wheels = [ [[package]] name = "crewai" -version = "0.75.1" +version = "0.76.0" source = { editable = "." } dependencies = [ { name = "appdirs" }, @@ -753,35 +730,35 @@ wheels = [ [[package]] name = "cryptography" -version = "43.0.1" +version = "43.0.3" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "cffi", marker = "platform_python_implementation != 'PyPy'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/de/ba/0664727028b37e249e73879348cc46d45c5c1a2a2e81e8166462953c5755/cryptography-43.0.1.tar.gz", hash = "sha256:203e92a75716d8cfb491dc47c79e17d0d9207ccffcbcb35f598fbe463ae3444d", size = 686927 } +sdist = { url = "https://files.pythonhosted.org/packages/0d/05/07b55d1fa21ac18c3a8c79f764e2514e6f6a9698f1be44994f5adf0d29db/cryptography-43.0.3.tar.gz", hash = "sha256:315b9001266a492a6ff443b61238f956b214dbec9910a081ba5b6646a055a805", size = 686989 } wheels = [ - { url = "https://files.pythonhosted.org/packages/58/28/b92c98a04ba762f8cdeb54eba5c4c84e63cac037a7c5e70117d337b15ad6/cryptography-43.0.1-cp37-abi3-macosx_10_9_universal2.whl", hash = "sha256:8385d98f6a3bf8bb2d65a73e17ed87a3ba84f6991c155691c51112075f9ffc5d", size = 6223222 }, - { url = "https://files.pythonhosted.org/packages/33/13/1193774705783ba364121aa2a60132fa31a668b8ababd5edfa1662354ccd/cryptography-43.0.1-cp37-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:27e613d7077ac613e399270253259d9d53872aaf657471473ebfc9a52935c062", size = 3794751 }, - { url = "https://files.pythonhosted.org/packages/5e/4b/39bb3c4c8cfb3e94e736b8d8859ce5c81536e91a1033b1d26770c4249000/cryptography-43.0.1-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:68aaecc4178e90719e95298515979814bda0cbada1256a4485414860bd7ab962", size = 3981827 }, - { url = "https://files.pythonhosted.org/packages/ce/dc/1471d4d56608e1013237af334b8a4c35d53895694fbb73882d1c4fd3f55e/cryptography-43.0.1-cp37-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:de41fd81a41e53267cb020bb3a7212861da53a7d39f863585d13ea11049cf277", size = 3780034 }, - { url = "https://files.pythonhosted.org/packages/ad/43/7a9920135b0d5437cc2f8f529fa757431eb6a7736ddfadfdee1cc5890800/cryptography-43.0.1-cp37-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:f98bf604c82c416bc829e490c700ca1553eafdf2912a91e23a79d97d9801372a", size = 3993407 }, - { url = "https://files.pythonhosted.org/packages/cc/42/9ab8467af6c0b76f3d9b8f01d1cf25b9c9f3f2151f4acfab888d21c55a72/cryptography-43.0.1-cp37-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:61ec41068b7b74268fa86e3e9e12b9f0c21fcf65434571dbb13d954bceb08042", size = 3886457 }, - { url = "https://files.pythonhosted.org/packages/a4/65/430509e31700286ec02868a2457d2111d03ccefc20349d24e58d171ae0a7/cryptography-43.0.1-cp37-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:014f58110f53237ace6a408b5beb6c427b64e084eb451ef25a28308270086494", size = 4081499 }, - { url = "https://files.pythonhosted.org/packages/bb/18/a04b6467e6e09df8c73b91dcee8878f4a438a43a3603dc3cd6f8003b92d8/cryptography-43.0.1-cp37-abi3-win32.whl", hash = "sha256:2bd51274dcd59f09dd952afb696bf9c61a7a49dfc764c04dd33ef7a6b502a1e2", size = 2616504 }, - { url = "https://files.pythonhosted.org/packages/cc/73/0eacbdc437202edcbdc07f3576ed8fb8b0ab79d27bf2c5d822d758a72faa/cryptography-43.0.1-cp37-abi3-win_amd64.whl", hash = "sha256:666ae11966643886c2987b3b721899d250855718d6d9ce41b521252a17985f4d", size = 3067456 }, - { url = "https://files.pythonhosted.org/packages/8a/b6/bc54b371f02cffd35ff8dc6baba88304d7cf8e83632566b4b42e00383e03/cryptography-43.0.1-cp39-abi3-macosx_10_9_universal2.whl", hash = "sha256:ac119bb76b9faa00f48128b7f5679e1d8d437365c5d26f1c2c3f0da4ce1b553d", size = 6225263 }, - { url = "https://files.pythonhosted.org/packages/00/0e/8217e348a1fa417ec4c78cd3cdf24154f5e76fd7597343a35bd403650dfd/cryptography-43.0.1-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1bbcce1a551e262dfbafb6e6252f1ae36a248e615ca44ba302df077a846a8806", size = 3794368 }, - { url = "https://files.pythonhosted.org/packages/3d/ed/38b6be7254d8f7251fde8054af597ee8afa14f911da67a9410a45f602fc3/cryptography-43.0.1-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:58d4e9129985185a06d849aa6df265bdd5a74ca6e1b736a77959b498e0505b85", size = 3981750 }, - { url = "https://files.pythonhosted.org/packages/64/f3/b7946c3887cf7436f002f4cbb1e6aec77b8d299b86be48eeadfefb937c4b/cryptography-43.0.1-cp39-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:d03a475165f3134f773d1388aeb19c2d25ba88b6a9733c5c590b9ff7bbfa2e0c", size = 3778925 }, - { url = "https://files.pythonhosted.org/packages/ac/7e/ebda4dd4ae098a0990753efbb4b50954f1d03003846b943ea85070782da7/cryptography-43.0.1-cp39-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:511f4273808ab590912a93ddb4e3914dfd8a388fed883361b02dea3791f292e1", size = 3993152 }, - { url = "https://files.pythonhosted.org/packages/43/f6/feebbd78a3e341e3913846a3bb2c29d0b09b1b3af1573c6baabc2533e147/cryptography-43.0.1-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:80eda8b3e173f0f247f711eef62be51b599b5d425c429b5d4ca6a05e9e856baa", size = 3886392 }, - { url = "https://files.pythonhosted.org/packages/bd/4c/ab0b9407d5247576290b4fd8abd06b7f51bd414f04eef0f2800675512d61/cryptography-43.0.1-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:38926c50cff6f533f8a2dae3d7f19541432610d114a70808f0926d5aaa7121e4", size = 4082606 }, - { url = "https://files.pythonhosted.org/packages/05/36/e532a671998d6fcfdb9122da16434347a58a6bae9465e527e450e0bc60a5/cryptography-43.0.1-cp39-abi3-win32.whl", hash = "sha256:a575913fb06e05e6b4b814d7f7468c2c660e8bb16d8d5a1faf9b33ccc569dd47", size = 2617948 }, - { url = "https://files.pythonhosted.org/packages/b3/c6/c09cee6968add5ff868525c3815e5dccc0e3c6e89eec58dc9135d3c40e88/cryptography-43.0.1-cp39-abi3-win_amd64.whl", hash = "sha256:d75601ad10b059ec832e78823b348bfa1a59f6b8d545db3a24fd44362a1564cb", size = 3070445 }, - { url = "https://files.pythonhosted.org/packages/18/23/4175dcd935e1649865e1af7bd0b827cc9d9769a586dcc84f7cbe96839086/cryptography-43.0.1-pp310-pypy310_pp73-macosx_10_9_x86_64.whl", hash = "sha256:ea25acb556320250756e53f9e20a4177515f012c9eaea17eb7587a8c4d8ae034", size = 3152694 }, - { url = "https://files.pythonhosted.org/packages/ea/45/967da50269954b993d4484bf85026c7377bd551651ebdabba94905972556/cryptography-43.0.1-pp310-pypy310_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:c1332724be35d23a854994ff0b66530119500b6053d0bd3363265f7e5e77288d", size = 3713077 }, - { url = "https://files.pythonhosted.org/packages/df/e6/ccd29a1f9a6b71294e1e9f530c4d779d5dd37c8bb736c05d5fb6d98a971b/cryptography-43.0.1-pp310-pypy310_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:fba1007b3ef89946dbbb515aeeb41e30203b004f0b4b00e5e16078b518563289", size = 3915597 }, - { url = "https://files.pythonhosted.org/packages/a2/80/fb7d668f1be5e4443b7ac191f68390be24f7c2ebd36011741f62c7645eb2/cryptography-43.0.1-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:5b43d1ea6b378b54a1dc99dd8a2b5be47658fe9a7ce0a58ff0b55f4b43ef2b84", size = 2989208 }, + { url = "https://files.pythonhosted.org/packages/1f/f3/01fdf26701a26f4b4dbc337a26883ad5bccaa6f1bbbdd29cd89e22f18a1c/cryptography-43.0.3-cp37-abi3-macosx_10_9_universal2.whl", hash = "sha256:bf7a1932ac4176486eab36a19ed4c0492da5d97123f1406cf15e41b05e787d2e", size = 6225303 }, + { url = "https://files.pythonhosted.org/packages/a3/01/4896f3d1b392025d4fcbecf40fdea92d3df8662123f6835d0af828d148fd/cryptography-43.0.3-cp37-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:63efa177ff54aec6e1c0aefaa1a241232dcd37413835a9b674b6e3f0ae2bfd3e", size = 3760905 }, + { url = "https://files.pythonhosted.org/packages/0a/be/f9a1f673f0ed4b7f6c643164e513dbad28dd4f2dcdf5715004f172ef24b6/cryptography-43.0.3-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7e1ce50266f4f70bf41a2c6dc4358afadae90e2a1e5342d3c08883df1675374f", size = 3977271 }, + { url = "https://files.pythonhosted.org/packages/4e/49/80c3a7b5514d1b416d7350830e8c422a4d667b6d9b16a9392ebfd4a5388a/cryptography-43.0.3-cp37-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:443c4a81bb10daed9a8f334365fe52542771f25aedaf889fd323a853ce7377d6", size = 3746606 }, + { url = "https://files.pythonhosted.org/packages/0e/16/a28ddf78ac6e7e3f25ebcef69ab15c2c6be5ff9743dd0709a69a4f968472/cryptography-43.0.3-cp37-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:74f57f24754fe349223792466a709f8e0c093205ff0dca557af51072ff47ab18", size = 3986484 }, + { url = "https://files.pythonhosted.org/packages/01/f5/69ae8da70c19864a32b0315049866c4d411cce423ec169993d0434218762/cryptography-43.0.3-cp37-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:9762ea51a8fc2a88b70cf2995e5675b38d93bf36bd67d91721c309df184f49bd", size = 3852131 }, + { url = "https://files.pythonhosted.org/packages/fd/db/e74911d95c040f9afd3612b1f732e52b3e517cb80de8bf183be0b7d413c6/cryptography-43.0.3-cp37-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:81ef806b1fef6b06dcebad789f988d3b37ccaee225695cf3e07648eee0fc6b73", size = 4075647 }, + { url = "https://files.pythonhosted.org/packages/56/48/7b6b190f1462818b324e674fa20d1d5ef3e24f2328675b9b16189cbf0b3c/cryptography-43.0.3-cp37-abi3-win32.whl", hash = "sha256:cbeb489927bd7af4aa98d4b261af9a5bc025bd87f0e3547e11584be9e9427be2", size = 2623873 }, + { url = "https://files.pythonhosted.org/packages/eb/b1/0ebff61a004f7f89e7b65ca95f2f2375679d43d0290672f7713ee3162aff/cryptography-43.0.3-cp37-abi3-win_amd64.whl", hash = "sha256:f46304d6f0c6ab8e52770addfa2fc41e6629495548862279641972b6215451cd", size = 3068039 }, + { url = "https://files.pythonhosted.org/packages/30/d5/c8b32c047e2e81dd172138f772e81d852c51f0f2ad2ae8a24f1122e9e9a7/cryptography-43.0.3-cp39-abi3-macosx_10_9_universal2.whl", hash = "sha256:8ac43ae87929a5982f5948ceda07001ee5e83227fd69cf55b109144938d96984", size = 6222984 }, + { url = "https://files.pythonhosted.org/packages/2f/78/55356eb9075d0be6e81b59f45c7b48df87f76a20e73893872170471f3ee8/cryptography-43.0.3-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:846da004a5804145a5f441b8530b4bf35afbf7da70f82409f151695b127213d5", size = 3762968 }, + { url = "https://files.pythonhosted.org/packages/2a/2c/488776a3dc843f95f86d2f957ca0fc3407d0242b50bede7fad1e339be03f/cryptography-43.0.3-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0f996e7268af62598f2fc1204afa98a3b5712313a55c4c9d434aef49cadc91d4", size = 3977754 }, + { url = "https://files.pythonhosted.org/packages/7c/04/2345ca92f7a22f601a9c62961741ef7dd0127c39f7310dffa0041c80f16f/cryptography-43.0.3-cp39-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:f7b178f11ed3664fd0e995a47ed2b5ff0a12d893e41dd0494f406d1cf555cab7", size = 3749458 }, + { url = "https://files.pythonhosted.org/packages/ac/25/e715fa0bc24ac2114ed69da33adf451a38abb6f3f24ec207908112e9ba53/cryptography-43.0.3-cp39-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:c2e6fc39c4ab499049df3bdf567f768a723a5e8464816e8f009f121a5a9f4405", size = 3988220 }, + { url = "https://files.pythonhosted.org/packages/21/ce/b9c9ff56c7164d8e2edfb6c9305045fbc0df4508ccfdb13ee66eb8c95b0e/cryptography-43.0.3-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:e1be4655c7ef6e1bbe6b5d0403526601323420bcf414598955968c9ef3eb7d16", size = 3853898 }, + { url = "https://files.pythonhosted.org/packages/2a/33/b3682992ab2e9476b9c81fff22f02c8b0a1e6e1d49ee1750a67d85fd7ed2/cryptography-43.0.3-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:df6b6c6d742395dd77a23ea3728ab62f98379eff8fb61be2744d4679ab678f73", size = 4076592 }, + { url = "https://files.pythonhosted.org/packages/81/1e/ffcc41b3cebd64ca90b28fd58141c5f68c83d48563c88333ab660e002cd3/cryptography-43.0.3-cp39-abi3-win32.whl", hash = "sha256:d56e96520b1020449bbace2b78b603442e7e378a9b3bd68de65c782db1507995", size = 2623145 }, + { url = "https://files.pythonhosted.org/packages/87/5c/3dab83cc4aba1f4b0e733e3f0c3e7d4386440d660ba5b1e3ff995feb734d/cryptography-43.0.3-cp39-abi3-win_amd64.whl", hash = "sha256:0c580952eef9bf68c4747774cde7ec1d85a6e61de97281f2dba83c7d2c806362", size = 3068026 }, + { url = "https://files.pythonhosted.org/packages/6f/db/d8b8a039483f25fc3b70c90bc8f3e1d4497a99358d610c5067bf3bd4f0af/cryptography-43.0.3-pp310-pypy310_pp73-macosx_10_9_x86_64.whl", hash = "sha256:d03b5621a135bffecad2c73e9f4deb1a0f977b9a8ffe6f8e002bf6c9d07b918c", size = 3144545 }, + { url = "https://files.pythonhosted.org/packages/93/90/116edd5f8ec23b2dc879f7a42443e073cdad22950d3c8ee834e3b8124543/cryptography-43.0.3-pp310-pypy310_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:a2a431ee15799d6db9fe80c82b055bae5a752bef645bba795e8e52687c69efe3", size = 3679828 }, + { url = "https://files.pythonhosted.org/packages/d8/32/1e1d78b316aa22c0ba6493cc271c1c309969e5aa5c22c830a1d7ce3471e6/cryptography-43.0.3-pp310-pypy310_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:281c945d0e28c92ca5e5930664c1cefd85efe80e5c0d2bc58dd63383fda29f83", size = 3908132 }, + { url = "https://files.pythonhosted.org/packages/91/bb/cd2c13be3332e7af3cdf16154147952d39075b9f61ea5e6b5241bf4bf436/cryptography-43.0.3-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:f18c716be16bc1fea8e95def49edf46b82fccaa88587a45f8dc0ff6ab5d8e0a7", size = 2988811 }, ] [[package]] @@ -853,21 +830,12 @@ wheels = [ ] [[package]] -name = "dill" +name = "distlib" version = "0.3.9" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/70/43/86fe3f9e130c4137b0f1b50784dd70a5087b911fe07fa81e53e0c4c47fea/dill-0.3.9.tar.gz", hash = "sha256:81aa267dddf68cbfe8029c42ca9ec6a4ab3b22371d1c450abc54422577b4512c", size = 187000 } +sdist = { url = "https://files.pythonhosted.org/packages/0d/dd/1bec4c5ddb504ca60fc29472f3d27e8d4da1257a854e1d96742f15c1d02d/distlib-0.3.9.tar.gz", hash = "sha256:a60f20dea646b8a33f3e7772f74dc0b2d0772d2837ee1342a00645c81edf9403", size = 613923 } wheels = [ - { url = "https://files.pythonhosted.org/packages/46/d1/e73b6ad76f0b1fb7f23c35c6d95dbc506a9c8804f43dda8cb5b0fa6331fd/dill-0.3.9-py3-none-any.whl", hash = "sha256:468dff3b89520b474c0397703366b7b95eebe6303f108adf9b19da1f702be87a", size = 119418 }, -] - -[[package]] -name = "distlib" -version = "0.3.8" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/c4/91/e2df406fb4efacdf46871c25cde65d3c6ee5e173b7e5a4547a47bae91920/distlib-0.3.8.tar.gz", hash = "sha256:1530ea13e350031b6312d8580ddb6b27a104275a31106523b8f123787f494f64", size = 609931 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/8e/41/9307e4f5f9976bc8b7fea0b66367734e8faf3ec84bc0d412d8cfabbb66cd/distlib-0.3.8-py2.py3-none-any.whl", hash = "sha256:034db59a0b96f8ca18035f36290806a9a6e6bd9d1ff91e45a7f172eb17e51784", size = 468850 }, + { url = "https://files.pythonhosted.org/packages/91/a1/cf2472db20f7ce4a6be1253a81cfdf85ad9c7885ffbed7047fb72c24cf87/distlib-0.3.9-py2.py3-none-any.whl", hash = "sha256:47f8c22fd27c27e25a65601af709b38e4f0a45ea4fc2e710f65755fa8caaaf87", size = 468973 }, ] [[package]] @@ -969,16 +937,16 @@ wheels = [ [[package]] name = "fastapi" -version = "0.115.0" +version = "0.115.3" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "pydantic" }, { name = "starlette" }, { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/7b/5e/bf0471f14bf6ebfbee8208148a3396d1a23298531a6cc10776c59f4c0f87/fastapi-0.115.0.tar.gz", hash = "sha256:f93b4ca3529a8ebc6fc3fcf710e5efa8de3df9b41570958abf1d97d843138004", size = 302295 } +sdist = { url = "https://files.pythonhosted.org/packages/a9/ce/b64ce344d7b13c0768dc5b131a69d52f57202eb85839408a7637ca0dd7e2/fastapi-0.115.3.tar.gz", hash = "sha256:c091c6a35599c036d676fa24bd4a6e19fa30058d93d950216cdc672881f6f7db", size = 300453 } wheels = [ - { url = "https://files.pythonhosted.org/packages/06/ab/a1f7eed031aeb1c406a6e9d45ca04bff401c8a25a30dd0e4fd2caae767c3/fastapi-0.115.0-py3-none-any.whl", hash = "sha256:17ea427674467486e997206a5ab25760f6b09e069f099b96f5b55a32fb6f1631", size = 94625 }, + { url = "https://files.pythonhosted.org/packages/57/95/4c5b79e7ca1f7b372d16a32cad7c9cc6c3c899200bed8f45739f4415cfae/fastapi-0.115.3-py3-none-any.whl", hash = "sha256:8035e8f9a2b0aa89cea03b6c77721178ed5358e1aea4cd8570d9466895c0638c", size = 94647 }, ] [[package]] @@ -1027,65 +995,80 @@ wheels = [ [[package]] name = "frozenlist" -version = "1.4.1" +version = "1.5.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/cf/3d/2102257e7acad73efc4a0c306ad3953f68c504c16982bbdfee3ad75d8085/frozenlist-1.4.1.tar.gz", hash = "sha256:c037a86e8513059a2613aaba4d817bb90b9d9b6b69aace3ce9c877e8c8ed402b", size = 37820 } +sdist = { url = "https://files.pythonhosted.org/packages/8f/ed/0f4cec13a93c02c47ec32d81d11c0c1efbadf4a471e3f3ce7cad366cbbd3/frozenlist-1.5.0.tar.gz", hash = "sha256:81d5af29e61b9c8348e876d442253723928dce6433e0e76cd925cd83f1b4b817", size = 39930 } wheels = [ - { url = "https://files.pythonhosted.org/packages/7a/35/1328c7b0f780d34f8afc1d87ebdc2bb065a123b24766a0b475f0d67da637/frozenlist-1.4.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:f9aa1878d1083b276b0196f2dfbe00c9b7e752475ed3b682025ff20c1c1f51ac", size = 94315 }, - { url = "https://files.pythonhosted.org/packages/f4/d6/ca016b0adcf8327714ccef969740688808c86e0287bf3a639ff582f24e82/frozenlist-1.4.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:29acab3f66f0f24674b7dc4736477bcd4bc3ad4b896f5f45379a67bce8b96868", size = 53805 }, - { url = "https://files.pythonhosted.org/packages/ae/83/bcdaa437a9bd693ba658a0310f8cdccff26bd78e45fccf8e49897904a5cd/frozenlist-1.4.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:74fb4bee6880b529a0c6560885fce4dc95936920f9f20f53d99a213f7bf66776", size = 52163 }, - { url = "https://files.pythonhosted.org/packages/d4/e9/759043ab7d169b74fe05ebfbfa9ee5c881c303ebc838e308346204309cd0/frozenlist-1.4.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:590344787a90ae57d62511dd7c736ed56b428f04cd8c161fcc5e7232c130c69a", size = 238595 }, - { url = "https://files.pythonhosted.org/packages/f8/ce/b9de7dc61e753dc318cf0de862181b484178210c5361eae6eaf06792264d/frozenlist-1.4.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:068b63f23b17df8569b7fdca5517edef76171cf3897eb68beb01341131fbd2ad", size = 262428 }, - { url = "https://files.pythonhosted.org/packages/36/ce/dc6f29e0352fa34ebe45421960c8e7352ca63b31630a576e8ffb381e9c08/frozenlist-1.4.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5c849d495bf5154cd8da18a9eb15db127d4dba2968d88831aff6f0331ea9bd4c", size = 258867 }, - { url = "https://files.pythonhosted.org/packages/51/47/159ac53faf8a11ae5ee8bb9db10327575557504e549cfd76f447b969aa91/frozenlist-1.4.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9750cc7fe1ae3b1611bb8cfc3f9ec11d532244235d75901fb6b8e42ce9229dfe", size = 229412 }, - { url = "https://files.pythonhosted.org/packages/ec/25/0c87df2e53c0c5d90f7517ca0ff7aca78d050a8ec4d32c4278e8c0e52e51/frozenlist-1.4.1-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a9b2de4cf0cdd5bd2dee4c4f63a653c61d2408055ab77b151c1957f221cabf2a", size = 239539 }, - { url = "https://files.pythonhosted.org/packages/97/94/a1305fa4716726ae0abf3b1069c2d922fcfd442538cb850f1be543f58766/frozenlist-1.4.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:0633c8d5337cb5c77acbccc6357ac49a1770b8c487e5b3505c57b949b4b82e98", size = 253379 }, - { url = "https://files.pythonhosted.org/packages/53/82/274e19f122e124aee6d113188615f63b0736b4242a875f482a81f91e07e2/frozenlist-1.4.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:27657df69e8801be6c3638054e202a135c7f299267f1a55ed3a598934f6c0d75", size = 245901 }, - { url = "https://files.pythonhosted.org/packages/b8/28/899931015b8cffbe155392fe9ca663f981a17e1adc69589ee0e1e7cdc9a2/frozenlist-1.4.1-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:f9a3ea26252bd92f570600098783d1371354d89d5f6b7dfd87359d669f2109b5", size = 263797 }, - { url = "https://files.pythonhosted.org/packages/6e/4f/b8a5a2f10c4a58c52a52a40cf6cf1ffcdbf3a3b64f276f41dab989bf3ab5/frozenlist-1.4.1-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:4f57dab5fe3407b6c0c1cc907ac98e8a189f9e418f3b6e54d65a718aaafe3950", size = 264415 }, - { url = "https://files.pythonhosted.org/packages/b0/2c/7be3bdc59dbae444864dbd9cde82790314390ec54636baf6b9ce212627ad/frozenlist-1.4.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:e02a0e11cf6597299b9f3bbd3f93d79217cb90cfd1411aec33848b13f5c656cc", size = 253964 }, - { url = "https://files.pythonhosted.org/packages/2e/ec/4fb5a88f6b9a352aed45ab824dd7ce4801b7bcd379adcb927c17a8f0a1a8/frozenlist-1.4.1-cp310-cp310-win32.whl", hash = "sha256:a828c57f00f729620a442881cc60e57cfcec6842ba38e1b19fd3e47ac0ff8dc1", size = 44559 }, - { url = "https://files.pythonhosted.org/packages/61/15/2b5d644d81282f00b61e54f7b00a96f9c40224107282efe4cd9d2bf1433a/frozenlist-1.4.1-cp310-cp310-win_amd64.whl", hash = "sha256:f56e2333dda1fe0f909e7cc59f021eba0d2307bc6f012a1ccf2beca6ba362439", size = 50434 }, - { url = "https://files.pythonhosted.org/packages/01/bc/8d33f2d84b9368da83e69e42720cff01c5e199b5a868ba4486189a4d8fa9/frozenlist-1.4.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:a0cb6f11204443f27a1628b0e460f37fb30f624be6051d490fa7d7e26d4af3d0", size = 97060 }, - { url = "https://files.pythonhosted.org/packages/af/b2/904500d6a162b98a70e510e743e7ea992241b4f9add2c8063bf666ca21df/frozenlist-1.4.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:b46c8ae3a8f1f41a0d2ef350c0b6e65822d80772fe46b653ab6b6274f61d4a49", size = 55347 }, - { url = "https://files.pythonhosted.org/packages/5b/9c/f12b69997d3891ddc0d7895999a00b0c6a67f66f79498c0e30f27876435d/frozenlist-1.4.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:fde5bd59ab5357e3853313127f4d3565fc7dad314a74d7b5d43c22c6a5ed2ced", size = 53374 }, - { url = "https://files.pythonhosted.org/packages/ac/6e/e0322317b7c600ba21dec224498c0c5959b2bce3865277a7c0badae340a9/frozenlist-1.4.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:722e1124aec435320ae01ee3ac7bec11a5d47f25d0ed6328f2273d287bc3abb0", size = 273288 }, - { url = "https://files.pythonhosted.org/packages/a7/76/180ee1b021568dad5b35b7678616c24519af130ed3fa1e0f1ed4014e0f93/frozenlist-1.4.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:2471c201b70d58a0f0c1f91261542a03d9a5e088ed3dc6c160d614c01649c106", size = 284737 }, - { url = "https://files.pythonhosted.org/packages/05/08/40159d706a6ed983c8aca51922a93fc69f3c27909e82c537dd4054032674/frozenlist-1.4.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c757a9dd70d72b076d6f68efdbb9bc943665ae954dad2801b874c8c69e185068", size = 280267 }, - { url = "https://files.pythonhosted.org/packages/e0/18/9f09f84934c2b2aa37d539a322267939770362d5495f37783440ca9c1b74/frozenlist-1.4.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f146e0911cb2f1da549fc58fc7bcd2b836a44b79ef871980d605ec392ff6b0d2", size = 258778 }, - { url = "https://files.pythonhosted.org/packages/b3/c9/0bc5ee7e1f5cc7358ab67da0b7dfe60fbd05c254cea5c6108e7d1ae28c63/frozenlist-1.4.1-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4f9c515e7914626b2a2e1e311794b4c35720a0be87af52b79ff8e1429fc25f19", size = 272276 }, - { url = "https://files.pythonhosted.org/packages/12/5d/147556b73a53ad4df6da8bbb50715a66ac75c491fdedac3eca8b0b915345/frozenlist-1.4.1-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:c302220494f5c1ebeb0912ea782bcd5e2f8308037b3c7553fad0e48ebad6ad82", size = 272424 }, - { url = "https://files.pythonhosted.org/packages/83/61/2087bbf24070b66090c0af922685f1d0596c24bb3f3b5223625bdeaf03ca/frozenlist-1.4.1-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:442acde1e068288a4ba7acfe05f5f343e19fac87bfc96d89eb886b0363e977ec", size = 260881 }, - { url = "https://files.pythonhosted.org/packages/a8/be/a235bc937dd803258a370fe21b5aa2dd3e7bfe0287a186a4bec30c6cccd6/frozenlist-1.4.1-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:1b280e6507ea8a4fa0c0a7150b4e526a8d113989e28eaaef946cc77ffd7efc0a", size = 282327 }, - { url = "https://files.pythonhosted.org/packages/5d/e7/b2469e71f082948066b9382c7b908c22552cc705b960363c390d2e23f587/frozenlist-1.4.1-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:fe1a06da377e3a1062ae5fe0926e12b84eceb8a50b350ddca72dc85015873f74", size = 281502 }, - { url = "https://files.pythonhosted.org/packages/db/1b/6a5b970e55dffc1a7d0bb54f57b184b2a2a2ad0b7bca16a97ca26d73c5b5/frozenlist-1.4.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:db9e724bebd621d9beca794f2a4ff1d26eed5965b004a97f1f1685a173b869c2", size = 272292 }, - { url = "https://files.pythonhosted.org/packages/1a/05/ebad68130e6b6eb9b287dacad08ea357c33849c74550c015b355b75cc714/frozenlist-1.4.1-cp311-cp311-win32.whl", hash = "sha256:e774d53b1a477a67838a904131c4b0eef6b3d8a651f8b138b04f748fccfefe17", size = 44446 }, - { url = "https://files.pythonhosted.org/packages/b3/21/c5aaffac47fd305d69df46cfbf118768cdf049a92ee6b0b5cb029d449dcf/frozenlist-1.4.1-cp311-cp311-win_amd64.whl", hash = "sha256:fb3c2db03683b5767dedb5769b8a40ebb47d6f7f45b1b3e3b4b51ec8ad9d9825", size = 50459 }, - { url = "https://files.pythonhosted.org/packages/b4/db/4cf37556a735bcdb2582f2c3fa286aefde2322f92d3141e087b8aeb27177/frozenlist-1.4.1-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:1979bc0aeb89b33b588c51c54ab0161791149f2461ea7c7c946d95d5f93b56ae", size = 93937 }, - { url = "https://files.pythonhosted.org/packages/46/03/69eb64642ca8c05f30aa5931d6c55e50b43d0cd13256fdd01510a1f85221/frozenlist-1.4.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:cc7b01b3754ea68a62bd77ce6020afaffb44a590c2289089289363472d13aedb", size = 53656 }, - { url = "https://files.pythonhosted.org/packages/3f/ab/c543c13824a615955f57e082c8a5ee122d2d5368e80084f2834e6f4feced/frozenlist-1.4.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:c9c92be9fd329ac801cc420e08452b70e7aeab94ea4233a4804f0915c14eba9b", size = 51868 }, - { url = "https://files.pythonhosted.org/packages/a9/b8/438cfd92be2a124da8259b13409224d9b19ef8f5a5b2507174fc7e7ea18f/frozenlist-1.4.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5c3894db91f5a489fc8fa6a9991820f368f0b3cbdb9cd8849547ccfab3392d86", size = 280652 }, - { url = "https://files.pythonhosted.org/packages/54/72/716a955521b97a25d48315c6c3653f981041ce7a17ff79f701298195bca3/frozenlist-1.4.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ba60bb19387e13597fb059f32cd4d59445d7b18b69a745b8f8e5db0346f33480", size = 286739 }, - { url = "https://files.pythonhosted.org/packages/65/d8/934c08103637567084568e4d5b4219c1016c60b4d29353b1a5b3587827d6/frozenlist-1.4.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8aefbba5f69d42246543407ed2461db31006b0f76c4e32dfd6f42215a2c41d09", size = 289447 }, - { url = "https://files.pythonhosted.org/packages/70/bb/d3b98d83ec6ef88f9bd63d77104a305d68a146fd63a683569ea44c3085f6/frozenlist-1.4.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:780d3a35680ced9ce682fbcf4cb9c2bad3136eeff760ab33707b71db84664e3a", size = 265466 }, - { url = "https://files.pythonhosted.org/packages/0b/f2/b8158a0f06faefec33f4dff6345a575c18095a44e52d4f10c678c137d0e0/frozenlist-1.4.1-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9acbb16f06fe7f52f441bb6f413ebae6c37baa6ef9edd49cdd567216da8600cd", size = 281530 }, - { url = "https://files.pythonhosted.org/packages/ea/a2/20882c251e61be653764038ece62029bfb34bd5b842724fff32a5b7a2894/frozenlist-1.4.1-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:23b701e65c7b36e4bf15546a89279bd4d8675faabc287d06bbcfac7d3c33e1e6", size = 281295 }, - { url = "https://files.pythonhosted.org/packages/4c/f9/8894c05dc927af2a09663bdf31914d4fb5501653f240a5bbaf1e88cab1d3/frozenlist-1.4.1-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:3e0153a805a98f5ada7e09826255ba99fb4f7524bb81bf6b47fb702666484ae1", size = 268054 }, - { url = "https://files.pythonhosted.org/packages/37/ff/a613e58452b60166507d731812f3be253eb1229808e59980f0405d1eafbf/frozenlist-1.4.1-cp312-cp312-musllinux_1_1_ppc64le.whl", hash = "sha256:dd9b1baec094d91bf36ec729445f7769d0d0cf6b64d04d86e45baf89e2b9059b", size = 286904 }, - { url = "https://files.pythonhosted.org/packages/cc/6e/0091d785187f4c2020d5245796d04213f2261ad097e0c1cf35c44317d517/frozenlist-1.4.1-cp312-cp312-musllinux_1_1_s390x.whl", hash = "sha256:1a4471094e146b6790f61b98616ab8e44f72661879cc63fa1049d13ef711e71e", size = 290754 }, - { url = "https://files.pythonhosted.org/packages/a5/c2/e42ad54bae8bcffee22d1e12a8ee6c7717f7d5b5019261a8c861854f4776/frozenlist-1.4.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:5667ed53d68d91920defdf4035d1cdaa3c3121dc0b113255124bcfada1cfa1b8", size = 282602 }, - { url = "https://files.pythonhosted.org/packages/b6/61/56bad8cb94f0357c4bc134acc30822e90e203b5cb8ff82179947de90c17f/frozenlist-1.4.1-cp312-cp312-win32.whl", hash = "sha256:beee944ae828747fd7cb216a70f120767fc9f4f00bacae8543c14a6831673f89", size = 44063 }, - { url = "https://files.pythonhosted.org/packages/3e/dc/96647994a013bc72f3d453abab18340b7f5e222b7b7291e3697ca1fcfbd5/frozenlist-1.4.1-cp312-cp312-win_amd64.whl", hash = "sha256:64536573d0a2cb6e625cf309984e2d873979709f2cf22839bf2d61790b448ad5", size = 50452 }, - { url = "https://files.pythonhosted.org/packages/83/10/466fe96dae1bff622021ee687f68e5524d6392b0a2f80d05001cd3a451ba/frozenlist-1.4.1-py3-none-any.whl", hash = "sha256:04ced3e6a46b4cfffe20f9ae482818e34eba9b5fb0ce4056e4cc9b6e212d09b7", size = 11552 }, + { url = "https://files.pythonhosted.org/packages/54/79/29d44c4af36b2b240725dce566b20f63f9b36ef267aaaa64ee7466f4f2f8/frozenlist-1.5.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:5b6a66c18b5b9dd261ca98dffcb826a525334b2f29e7caa54e182255c5f6a65a", size = 94451 }, + { url = "https://files.pythonhosted.org/packages/47/47/0c999aeace6ead8a44441b4f4173e2261b18219e4ad1fe9a479871ca02fc/frozenlist-1.5.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:d1b3eb7b05ea246510b43a7e53ed1653e55c2121019a97e60cad7efb881a97bb", size = 54301 }, + { url = "https://files.pythonhosted.org/packages/8d/60/107a38c1e54176d12e06e9d4b5d755b677d71d1219217cee063911b1384f/frozenlist-1.5.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:15538c0cbf0e4fa11d1e3a71f823524b0c46299aed6e10ebb4c2089abd8c3bec", size = 52213 }, + { url = "https://files.pythonhosted.org/packages/17/62/594a6829ac5679c25755362a9dc93486a8a45241394564309641425d3ff6/frozenlist-1.5.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e79225373c317ff1e35f210dd5f1344ff31066ba8067c307ab60254cd3a78ad5", size = 240946 }, + { url = "https://files.pythonhosted.org/packages/7e/75/6c8419d8f92c80dd0ee3f63bdde2702ce6398b0ac8410ff459f9b6f2f9cb/frozenlist-1.5.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9272fa73ca71266702c4c3e2d4a28553ea03418e591e377a03b8e3659d94fa76", size = 264608 }, + { url = "https://files.pythonhosted.org/packages/88/3e/82a6f0b84bc6fb7e0be240e52863c6d4ab6098cd62e4f5b972cd31e002e8/frozenlist-1.5.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:498524025a5b8ba81695761d78c8dd7382ac0b052f34e66939c42df860b8ff17", size = 261361 }, + { url = "https://files.pythonhosted.org/packages/fd/85/14e5f9ccac1b64ff2f10c927b3ffdf88772aea875882406f9ba0cec8ad84/frozenlist-1.5.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:92b5278ed9d50fe610185ecd23c55d8b307d75ca18e94c0e7de328089ac5dcba", size = 231649 }, + { url = "https://files.pythonhosted.org/packages/ee/59/928322800306f6529d1852323014ee9008551e9bb027cc38d276cbc0b0e7/frozenlist-1.5.0-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7f3c8c1dacd037df16e85227bac13cca58c30da836c6f936ba1df0c05d046d8d", size = 241853 }, + { url = "https://files.pythonhosted.org/packages/7d/bd/e01fa4f146a6f6c18c5d34cab8abdc4013774a26c4ff851128cd1bd3008e/frozenlist-1.5.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:f2ac49a9bedb996086057b75bf93538240538c6d9b38e57c82d51f75a73409d2", size = 243652 }, + { url = "https://files.pythonhosted.org/packages/a5/bd/e4771fd18a8ec6757033f0fa903e447aecc3fbba54e3630397b61596acf0/frozenlist-1.5.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:e66cc454f97053b79c2ab09c17fbe3c825ea6b4de20baf1be28919460dd7877f", size = 241734 }, + { url = "https://files.pythonhosted.org/packages/21/13/c83821fa5544af4f60c5d3a65d054af3213c26b14d3f5f48e43e5fb48556/frozenlist-1.5.0-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:5a3ba5f9a0dfed20337d3e966dc359784c9f96503674c2faf015f7fe8e96798c", size = 260959 }, + { url = "https://files.pythonhosted.org/packages/71/f3/1f91c9a9bf7ed0e8edcf52698d23f3c211d8d00291a53c9f115ceb977ab1/frozenlist-1.5.0-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:6321899477db90bdeb9299ac3627a6a53c7399c8cd58d25da094007402b039ab", size = 262706 }, + { url = "https://files.pythonhosted.org/packages/4c/22/4a256fdf5d9bcb3ae32622c796ee5ff9451b3a13a68cfe3f68e2c95588ce/frozenlist-1.5.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:76e4753701248476e6286f2ef492af900ea67d9706a0155335a40ea21bf3b2f5", size = 250401 }, + { url = "https://files.pythonhosted.org/packages/af/89/c48ebe1f7991bd2be6d5f4ed202d94960c01b3017a03d6954dd5fa9ea1e8/frozenlist-1.5.0-cp310-cp310-win32.whl", hash = "sha256:977701c081c0241d0955c9586ffdd9ce44f7a7795df39b9151cd9a6fd0ce4cfb", size = 45498 }, + { url = "https://files.pythonhosted.org/packages/28/2f/cc27d5f43e023d21fe5c19538e08894db3d7e081cbf582ad5ed366c24446/frozenlist-1.5.0-cp310-cp310-win_amd64.whl", hash = "sha256:189f03b53e64144f90990d29a27ec4f7997d91ed3d01b51fa39d2dbe77540fd4", size = 51622 }, + { url = "https://files.pythonhosted.org/packages/79/43/0bed28bf5eb1c9e4301003b74453b8e7aa85fb293b31dde352aac528dafc/frozenlist-1.5.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:fd74520371c3c4175142d02a976aee0b4cb4a7cc912a60586ffd8d5929979b30", size = 94987 }, + { url = "https://files.pythonhosted.org/packages/bb/bf/b74e38f09a246e8abbe1e90eb65787ed745ccab6eaa58b9c9308e052323d/frozenlist-1.5.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:2f3f7a0fbc219fb4455264cae4d9f01ad41ae6ee8524500f381de64ffaa077d5", size = 54584 }, + { url = "https://files.pythonhosted.org/packages/2c/31/ab01375682f14f7613a1ade30149f684c84f9b8823a4391ed950c8285656/frozenlist-1.5.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:f47c9c9028f55a04ac254346e92977bf0f166c483c74b4232bee19a6697e4778", size = 52499 }, + { url = "https://files.pythonhosted.org/packages/98/a8/d0ac0b9276e1404f58fec3ab6e90a4f76b778a49373ccaf6a563f100dfbc/frozenlist-1.5.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0996c66760924da6e88922756d99b47512a71cfd45215f3570bf1e0b694c206a", size = 276357 }, + { url = "https://files.pythonhosted.org/packages/ad/c9/c7761084fa822f07dac38ac29f841d4587570dd211e2262544aa0b791d21/frozenlist-1.5.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a2fe128eb4edeabe11896cb6af88fca5346059f6c8d807e3b910069f39157869", size = 287516 }, + { url = "https://files.pythonhosted.org/packages/a1/ff/cd7479e703c39df7bdab431798cef89dc75010d8aa0ca2514c5b9321db27/frozenlist-1.5.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1a8ea951bbb6cacd492e3948b8da8c502a3f814f5d20935aae74b5df2b19cf3d", size = 283131 }, + { url = "https://files.pythonhosted.org/packages/59/a0/370941beb47d237eca4fbf27e4e91389fd68699e6f4b0ebcc95da463835b/frozenlist-1.5.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:de537c11e4aa01d37db0d403b57bd6f0546e71a82347a97c6a9f0dcc532b3a45", size = 261320 }, + { url = "https://files.pythonhosted.org/packages/b8/5f/c10123e8d64867bc9b4f2f510a32042a306ff5fcd7e2e09e5ae5100ee333/frozenlist-1.5.0-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9c2623347b933fcb9095841f1cc5d4ff0b278addd743e0e966cb3d460278840d", size = 274877 }, + { url = "https://files.pythonhosted.org/packages/fa/79/38c505601ae29d4348f21706c5d89755ceded02a745016ba2f58bd5f1ea6/frozenlist-1.5.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:cee6798eaf8b1416ef6909b06f7dc04b60755206bddc599f52232606e18179d3", size = 269592 }, + { url = "https://files.pythonhosted.org/packages/19/e2/39f3a53191b8204ba9f0bb574b926b73dd2efba2a2b9d2d730517e8f7622/frozenlist-1.5.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:f5f9da7f5dbc00a604fe74aa02ae7c98bcede8a3b8b9666f9f86fc13993bc71a", size = 265934 }, + { url = "https://files.pythonhosted.org/packages/d5/c9/3075eb7f7f3a91f1a6b00284af4de0a65a9ae47084930916f5528144c9dd/frozenlist-1.5.0-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:90646abbc7a5d5c7c19461d2e3eeb76eb0b204919e6ece342feb6032c9325ae9", size = 283859 }, + { url = "https://files.pythonhosted.org/packages/05/f5/549f44d314c29408b962fa2b0e69a1a67c59379fb143b92a0a065ffd1f0f/frozenlist-1.5.0-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:bdac3c7d9b705d253b2ce370fde941836a5f8b3c5c2b8fd70940a3ea3af7f4f2", size = 287560 }, + { url = "https://files.pythonhosted.org/packages/9d/f8/cb09b3c24a3eac02c4c07a9558e11e9e244fb02bf62c85ac2106d1eb0c0b/frozenlist-1.5.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:03d33c2ddbc1816237a67f66336616416e2bbb6beb306e5f890f2eb22b959cdf", size = 277150 }, + { url = "https://files.pythonhosted.org/packages/37/48/38c2db3f54d1501e692d6fe058f45b6ad1b358d82cd19436efab80cfc965/frozenlist-1.5.0-cp311-cp311-win32.whl", hash = "sha256:237f6b23ee0f44066219dae14c70ae38a63f0440ce6750f868ee08775073f942", size = 45244 }, + { url = "https://files.pythonhosted.org/packages/ca/8c/2ddffeb8b60a4bce3b196c32fcc30d8830d4615e7b492ec2071da801b8ad/frozenlist-1.5.0-cp311-cp311-win_amd64.whl", hash = "sha256:0cc974cc93d32c42e7b0f6cf242a6bd941c57c61b618e78b6c0a96cb72788c1d", size = 51634 }, + { url = "https://files.pythonhosted.org/packages/79/73/fa6d1a96ab7fd6e6d1c3500700963eab46813847f01ef0ccbaa726181dd5/frozenlist-1.5.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:31115ba75889723431aa9a4e77d5f398f5cf976eea3bdf61749731f62d4a4a21", size = 94026 }, + { url = "https://files.pythonhosted.org/packages/ab/04/ea8bf62c8868b8eada363f20ff1b647cf2e93377a7b284d36062d21d81d1/frozenlist-1.5.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:7437601c4d89d070eac8323f121fcf25f88674627505334654fd027b091db09d", size = 54150 }, + { url = "https://files.pythonhosted.org/packages/d0/9a/8e479b482a6f2070b26bda572c5e6889bb3ba48977e81beea35b5ae13ece/frozenlist-1.5.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:7948140d9f8ece1745be806f2bfdf390127cf1a763b925c4a805c603df5e697e", size = 51927 }, + { url = "https://files.pythonhosted.org/packages/e3/12/2aad87deb08a4e7ccfb33600871bbe8f0e08cb6d8224371387f3303654d7/frozenlist-1.5.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:feeb64bc9bcc6b45c6311c9e9b99406660a9c05ca8a5b30d14a78555088b0b3a", size = 282647 }, + { url = "https://files.pythonhosted.org/packages/77/f2/07f06b05d8a427ea0060a9cef6e63405ea9e0d761846b95ef3fb3be57111/frozenlist-1.5.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:683173d371daad49cffb8309779e886e59c2f369430ad28fe715f66d08d4ab1a", size = 289052 }, + { url = "https://files.pythonhosted.org/packages/bd/9f/8bf45a2f1cd4aa401acd271b077989c9267ae8463e7c8b1eb0d3f561b65e/frozenlist-1.5.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7d57d8f702221405a9d9b40f9da8ac2e4a1a8b5285aac6100f3393675f0a85ee", size = 291719 }, + { url = "https://files.pythonhosted.org/packages/41/d1/1f20fd05a6c42d3868709b7604c9f15538a29e4f734c694c6bcfc3d3b935/frozenlist-1.5.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:30c72000fbcc35b129cb09956836c7d7abf78ab5416595e4857d1cae8d6251a6", size = 267433 }, + { url = "https://files.pythonhosted.org/packages/af/f2/64b73a9bb86f5a89fb55450e97cd5c1f84a862d4ff90d9fd1a73ab0f64a5/frozenlist-1.5.0-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:000a77d6034fbad9b6bb880f7ec073027908f1b40254b5d6f26210d2dab1240e", size = 283591 }, + { url = "https://files.pythonhosted.org/packages/29/e2/ffbb1fae55a791fd6c2938dd9ea779509c977435ba3940b9f2e8dc9d5316/frozenlist-1.5.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:5d7f5a50342475962eb18b740f3beecc685a15b52c91f7d975257e13e029eca9", size = 273249 }, + { url = "https://files.pythonhosted.org/packages/2e/6e/008136a30798bb63618a114b9321b5971172a5abddff44a100c7edc5ad4f/frozenlist-1.5.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:87f724d055eb4785d9be84e9ebf0f24e392ddfad00b3fe036e43f489fafc9039", size = 271075 }, + { url = "https://files.pythonhosted.org/packages/ae/f0/4e71e54a026b06724cec9b6c54f0b13a4e9e298cc8db0f82ec70e151f5ce/frozenlist-1.5.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:6e9080bb2fb195a046e5177f10d9d82b8a204c0736a97a153c2466127de87784", size = 285398 }, + { url = "https://files.pythonhosted.org/packages/4d/36/70ec246851478b1c0b59f11ef8ade9c482ff447c1363c2bd5fad45098b12/frozenlist-1.5.0-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:9b93d7aaa36c966fa42efcaf716e6b3900438632a626fb09c049f6a2f09fc631", size = 294445 }, + { url = "https://files.pythonhosted.org/packages/37/e0/47f87544055b3349b633a03c4d94b405956cf2437f4ab46d0928b74b7526/frozenlist-1.5.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:52ef692a4bc60a6dd57f507429636c2af8b6046db8b31b18dac02cbc8f507f7f", size = 280569 }, + { url = "https://files.pythonhosted.org/packages/f9/7c/490133c160fb6b84ed374c266f42800e33b50c3bbab1652764e6e1fc498a/frozenlist-1.5.0-cp312-cp312-win32.whl", hash = "sha256:29d94c256679247b33a3dc96cce0f93cbc69c23bf75ff715919332fdbb6a32b8", size = 44721 }, + { url = "https://files.pythonhosted.org/packages/b1/56/4e45136ffc6bdbfa68c29ca56ef53783ef4c2fd395f7cbf99a2624aa9aaa/frozenlist-1.5.0-cp312-cp312-win_amd64.whl", hash = "sha256:8969190d709e7c48ea386db202d708eb94bdb29207a1f269bab1196ce0dcca1f", size = 51329 }, + { url = "https://files.pythonhosted.org/packages/da/3b/915f0bca8a7ea04483622e84a9bd90033bab54bdf485479556c74fd5eaf5/frozenlist-1.5.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:7a1a048f9215c90973402e26c01d1cff8a209e1f1b53f72b95c13db61b00f953", size = 91538 }, + { url = "https://files.pythonhosted.org/packages/c7/d1/a7c98aad7e44afe5306a2b068434a5830f1470675f0e715abb86eb15f15b/frozenlist-1.5.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:dd47a5181ce5fcb463b5d9e17ecfdb02b678cca31280639255ce9d0e5aa67af0", size = 52849 }, + { url = "https://files.pythonhosted.org/packages/3a/c8/76f23bf9ab15d5f760eb48701909645f686f9c64fbb8982674c241fbef14/frozenlist-1.5.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:1431d60b36d15cda188ea222033eec8e0eab488f39a272461f2e6d9e1a8e63c2", size = 50583 }, + { url = "https://files.pythonhosted.org/packages/1f/22/462a3dd093d11df623179d7754a3b3269de3b42de2808cddef50ee0f4f48/frozenlist-1.5.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6482a5851f5d72767fbd0e507e80737f9c8646ae7fd303def99bfe813f76cf7f", size = 265636 }, + { url = "https://files.pythonhosted.org/packages/80/cf/e075e407fc2ae7328155a1cd7e22f932773c8073c1fc78016607d19cc3e5/frozenlist-1.5.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:44c49271a937625619e862baacbd037a7ef86dd1ee215afc298a417ff3270608", size = 270214 }, + { url = "https://files.pythonhosted.org/packages/a1/58/0642d061d5de779f39c50cbb00df49682832923f3d2ebfb0fedf02d05f7f/frozenlist-1.5.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:12f78f98c2f1c2429d42e6a485f433722b0061d5c0b0139efa64f396efb5886b", size = 273905 }, + { url = "https://files.pythonhosted.org/packages/ab/66/3fe0f5f8f2add5b4ab7aa4e199f767fd3b55da26e3ca4ce2cc36698e50c4/frozenlist-1.5.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ce3aa154c452d2467487765e3adc730a8c153af77ad84096bc19ce19a2400840", size = 250542 }, + { url = "https://files.pythonhosted.org/packages/f6/b8/260791bde9198c87a465224e0e2bb62c4e716f5d198fc3a1dacc4895dbd1/frozenlist-1.5.0-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9b7dc0c4338e6b8b091e8faf0db3168a37101943e687f373dce00959583f7439", size = 267026 }, + { url = "https://files.pythonhosted.org/packages/2e/a4/3d24f88c527f08f8d44ade24eaee83b2627793fa62fa07cbb7ff7a2f7d42/frozenlist-1.5.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:45e0896250900b5aa25180f9aec243e84e92ac84bd4a74d9ad4138ef3f5c97de", size = 257690 }, + { url = "https://files.pythonhosted.org/packages/de/9a/d311d660420b2beeff3459b6626f2ab4fb236d07afbdac034a4371fe696e/frozenlist-1.5.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:561eb1c9579d495fddb6da8959fd2a1fca2c6d060d4113f5844b433fc02f2641", size = 253893 }, + { url = "https://files.pythonhosted.org/packages/c6/23/e491aadc25b56eabd0f18c53bb19f3cdc6de30b2129ee0bc39cd387cd560/frozenlist-1.5.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:df6e2f325bfee1f49f81aaac97d2aa757c7646534a06f8f577ce184afe2f0a9e", size = 267006 }, + { url = "https://files.pythonhosted.org/packages/08/c4/ab918ce636a35fb974d13d666dcbe03969592aeca6c3ab3835acff01f79c/frozenlist-1.5.0-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:140228863501b44b809fb39ec56b5d4071f4d0aa6d216c19cbb08b8c5a7eadb9", size = 276157 }, + { url = "https://files.pythonhosted.org/packages/c0/29/3b7a0bbbbe5a34833ba26f686aabfe982924adbdcafdc294a7a129c31688/frozenlist-1.5.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:7707a25d6a77f5d27ea7dc7d1fc608aa0a478193823f88511ef5e6b8a48f9d03", size = 264642 }, + { url = "https://files.pythonhosted.org/packages/ab/42/0595b3dbffc2e82d7fe658c12d5a5bafcd7516c6bf2d1d1feb5387caa9c1/frozenlist-1.5.0-cp313-cp313-win32.whl", hash = "sha256:31a9ac2b38ab9b5a8933b693db4939764ad3f299fcaa931a3e605bc3460e693c", size = 44914 }, + { url = "https://files.pythonhosted.org/packages/17/c4/b7db1206a3fea44bf3b838ca61deb6f74424a8a5db1dd53ecb21da669be6/frozenlist-1.5.0-cp313-cp313-win_amd64.whl", hash = "sha256:11aabdd62b8b9c4b84081a3c246506d1cddd2dd93ff0ad53ede5defec7886b28", size = 51167 }, + { url = "https://files.pythonhosted.org/packages/c6/c8/a5be5b7550c10858fcf9b0ea054baccab474da77d37f1e828ce043a3a5d4/frozenlist-1.5.0-py3-none-any.whl", hash = "sha256:d994863bba198a4a518b467bb971c56e1db3f180a25c6cf7bb1949c267f748c3", size = 11901 }, ] [[package]] name = "fsspec" -version = "2024.9.0" +version = "2024.10.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/62/7c/12b0943011daaaa9c35c2a2e22e5eb929ac90002f08f1259d69aedad84de/fsspec-2024.9.0.tar.gz", hash = "sha256:4b0afb90c2f21832df142f292649035d80b421f60a9e1c027802e5a0da2b04e8", size = 286206 } +sdist = { url = "https://files.pythonhosted.org/packages/a0/52/f16a068ebadae42526484c31f4398e62962504e5724a8ba5dc3409483df2/fsspec-2024.10.0.tar.gz", hash = "sha256:eda2d8a4116d4f2429db8550f2457da57279247dd930bb12f821b58391359493", size = 286853 } wheels = [ - { url = "https://files.pythonhosted.org/packages/1d/a0/6aaea0c2fbea2f89bfd5db25fb1e3481896a423002ebe4e55288907a97a3/fsspec-2024.9.0-py3-none-any.whl", hash = "sha256:a0947d552d8a6efa72cc2c730b12c41d043509156966cca4fb157b0f2a0c574b", size = 179253 }, + { url = "https://files.pythonhosted.org/packages/c6/b2/454d6e7f0158951d8a78c2e1eb4f69ae81beb8dca5fee9809c6c99e9d0d0/fsspec-2024.10.0-py3-none-any.whl", hash = "sha256:03b9a6785766a4de40368b88906366755e2819e758b83705c88cd7cb5fe81871", size = 179641 }, ] [[package]] @@ -1102,7 +1085,7 @@ wheels = [ [[package]] name = "google-api-core" -version = "2.20.0" +version = "2.21.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "google-auth" }, @@ -1111,9 +1094,9 @@ dependencies = [ { name = "protobuf" }, { name = "requests" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/c8/5c/31c1742a53b79c8a0c4757b5fae2e8ab9c519cbd7b98c587d4294e1d2d16/google_api_core-2.20.0.tar.gz", hash = "sha256:f74dff1889ba291a4b76c5079df0711810e2d9da81abfdc99957bc961c1eb28f", size = 152583 } +sdist = { url = "https://files.pythonhosted.org/packages/28/c8/046abf3ea11ec9cc3ea6d95e235a51161039d4a558484a997df60f9c51e9/google_api_core-2.21.0.tar.gz", hash = "sha256:4a152fd11a9f774ea606388d423b68aa7e6d6a0ffe4c8266f74979613ec09f81", size = 159313 } wheels = [ - { url = "https://files.pythonhosted.org/packages/c8/dc/6143f67acf5f30717c9e1b1c48fc04c0f59b869be046e6639d3f171640ae/google_api_core-2.20.0-py3-none-any.whl", hash = "sha256:ef0591ef03c30bb83f79b3d0575c3f31219001fc9c5cf37024d08310aeffed8a", size = 142162 }, + { url = "https://files.pythonhosted.org/packages/6a/ef/79fa8388c95edbd8fe36c763259dade36e5cb562dcf3e85c0e32070dc9b0/google_api_core-2.21.0-py3-none-any.whl", hash = "sha256:6869eacb2a37720380ba5898312af79a4d30b8bca1548fb4093e0697dc4bdf5d", size = 156437 }, ] [package.optional-dependencies] @@ -1138,7 +1121,7 @@ wheels = [ [[package]] name = "google-cloud-aiplatform" -version = "1.69.0" +version = "1.70.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "docstring-parser" }, @@ -1153,9 +1136,9 @@ dependencies = [ { name = "pydantic" }, { name = "shapely" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/d4/7a/37db97841b51411e51e80d62ce91eb8a3acef62a54bd38e861b4a9cfbb14/google-cloud-aiplatform-1.69.0.tar.gz", hash = "sha256:08be3a4432fd87d9cc86db83ba626f988d13597197bc53c6808e1c4c65a25bb0", size = 6291062 } +sdist = { url = "https://files.pythonhosted.org/packages/88/06/bc8028c03d4bedb85114c780a9f749b67ff06ce29d25dc7f1a99622f2692/google-cloud-aiplatform-1.70.0.tar.gz", hash = "sha256:e8edef6dbc7911380d0ea55c47544e799f62b891cb1a83b504ca1c09fff9884b", size = 6311624 } wheels = [ - { url = "https://files.pythonhosted.org/packages/ea/15/260229c9cd3b1bd386cd69a0dc39eeaf6cda08948d2707a4204966c71269/google_cloud_aiplatform-1.69.0-py2.py3-none-any.whl", hash = "sha256:6e21c29bf4506ed3bfb00cfe47ab1ad1788854b18f0ded2458016837c917e520", size = 5252576 }, + { url = "https://files.pythonhosted.org/packages/46/d9/280e5a9b5caf69322f64fa55f62bf447d76c5fe30e8df6e93373f22c4bd7/google_cloud_aiplatform-1.70.0-py2.py3-none-any.whl", hash = "sha256:690e6041f03d3aa85102ac3f316c958d6f43a99aefb7fb3f8938dee56d08abd9", size = 5267225 }, ] [[package]] @@ -1248,18 +1231,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/14/fb/54deefe679b7d1c1cc81d83396fcf28ad1a66d213bddeb275a8d28665918/google_crc32c-1.6.0-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:18e311c64008f1f1379158158bb3f0c8d72635b9eb4f9545f8cf990c5668e59d", size = 27866 }, ] -[[package]] -name = "google-pasta" -version = "0.2.0" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "six" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/35/4a/0bd53b36ff0323d10d5f24ebd67af2de10a1117f5cf4d7add90df92756f1/google-pasta-0.2.0.tar.gz", hash = "sha256:c9f2c8dfc8f96d0d5808299920721be30c9eec37f2389f28904f454565c8a16e", size = 40430 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/a3/de/c648ef6835192e6e2cc03f40b19eeda4382c49b5bafb43d88b931c4c74ac/google_pasta-0.2.0-py3-none-any.whl", hash = "sha256:b32482794a366b5366a32c92a9a9201b107821889935a02b3e51f6b432ea84ed", size = 57471 }, -] - [[package]] name = "google-resumable-media" version = "2.7.2" @@ -1356,14 +1327,14 @@ wheels = [ [[package]] name = "griffe" -version = "1.3.2" +version = "1.5.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "colorama" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/c2/37/813e72a3458fa3d996cf6bcc6a0caa919d16540f873366b0d328d51d684a/griffe-1.3.2.tar.gz", hash = "sha256:1ec50335aa507ed2445f2dd45a15c9fa3a45f52c9527e880571dfc61912fd60c", size = 382540 } +sdist = { url = "https://files.pythonhosted.org/packages/d4/c9/8167810358ca129839156dc002526e7398b5fad4a9d7b6e88b875e802d0d/griffe-1.5.1.tar.gz", hash = "sha256:72964f93e08c553257706d6cd2c42d1c172213feb48b2be386f243380b405d4b", size = 384113 } wheels = [ - { url = "https://files.pythonhosted.org/packages/2a/49/39967633dd3c5f06fde83fec140228671a7344289ece0cfdd3cbe4798d69/griffe-1.3.2-py3-none-any.whl", hash = "sha256:2e34b5e46507d615915c8e6288bb1a2234bd35dee44d01e40a2bc2f25bd4d10c", size = 126992 }, + { url = "https://files.pythonhosted.org/packages/ab/00/e693a155da0a2a72fd2df75b8fe338146cae59d590ad6f56800adde90cb5/griffe-1.5.1-py3-none-any.whl", hash = "sha256:ad6a7980f8c424c9102160aafa3bcdf799df0e75f7829d75af9ee5aef656f860", size = 127132 }, ] [[package]] @@ -1382,46 +1353,46 @@ wheels = [ [[package]] name = "grpcio" -version = "1.66.2" +version = "1.67.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/71/d1/49a96df4eb1d805cf546247df40636515416d2d5c66665e5129c8b4162a8/grpcio-1.66.2.tar.gz", hash = "sha256:563588c587b75c34b928bc428548e5b00ea38c46972181a4d8b75ba7e3f24231", size = 12489713 } +sdist = { url = "https://files.pythonhosted.org/packages/ec/ae/3c47d71ab4abd4bd60a7e2806071fe0a4b6937b9eabe522291787087ea1f/grpcio-1.67.0.tar.gz", hash = "sha256:e090b2553e0da1c875449c8e75073dd4415dd71c9bde6a406240fdf4c0ee467c", size = 12569330 } wheels = [ - { url = "https://files.pythonhosted.org/packages/04/b1/3188546f59df6a41998bdbac127373a21c5306a79fbf50bcffb24091fe7f/grpcio-1.66.2-cp310-cp310-linux_armv7l.whl", hash = "sha256:fe96281713168a3270878255983d2cb1a97e034325c8c2c25169a69289d3ecfa", size = 5025654 }, - { url = "https://files.pythonhosted.org/packages/da/b6/5fbf50889358228a344b93fe7c676de72fcf88073983c441e2ea92730adb/grpcio-1.66.2-cp310-cp310-macosx_12_0_universal2.whl", hash = "sha256:73fc8f8b9b5c4a03e802b3cd0c18b2b06b410d3c1dcbef989fdeb943bd44aff7", size = 10749112 }, - { url = "https://files.pythonhosted.org/packages/9c/8f/b1c53f3cb32ec808c7aa8ce6b4d5dfd8e50c3e85aa7d5d44ae1262294a73/grpcio-1.66.2-cp310-cp310-manylinux_2_17_aarch64.whl", hash = "sha256:03b0b307ba26fae695e067b94cbb014e27390f8bc5ac7a3a39b7723fed085604", size = 5541480 }, - { url = "https://files.pythonhosted.org/packages/e9/d8/85e57d340aa40ac6f7b5fb241a7d3805888a42ed96876f3107f6a828c6b7/grpcio-1.66.2-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7d69ce1f324dc2d71e40c9261d3fdbe7d4c9d60f332069ff9b2a4d8a257c7b2b", size = 6133888 }, - { url = "https://files.pythonhosted.org/packages/20/94/fffcd2a14bd79fc74c0c0f2a777299ec1702cc1bee32ca53c42405129bdd/grpcio-1.66.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:05bc2ceadc2529ab0b227b1310d249d95d9001cd106aa4d31e8871ad3c428d73", size = 5793512 }, - { url = "https://files.pythonhosted.org/packages/a3/54/a7fca38e8a71cc7d410873ffdc4e128b2881959d0607afb8a909f2bd7af9/grpcio-1.66.2-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:8ac475e8da31484efa25abb774674d837b343afb78bb3bcdef10f81a93e3d6bf", size = 6460939 }, - { url = "https://files.pythonhosted.org/packages/8e/0d/a83f9e7cbf620bbf99f5ee129a90b0891a967575f7bc2e227cd3376ebc53/grpcio-1.66.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:0be4e0490c28da5377283861bed2941d1d20ec017ca397a5df4394d1c31a9b50", size = 6053165 }, - { url = "https://files.pythonhosted.org/packages/c7/72/4021313e996285f4b6349114d107b5390b76acd5a1adefea50dac024a3b1/grpcio-1.66.2-cp310-cp310-win32.whl", hash = "sha256:4e504572433f4e72b12394977679161d495c4c9581ba34a88d843eaf0f2fbd39", size = 3554333 }, - { url = "https://files.pythonhosted.org/packages/24/7a/5cb5fd3db7a5779c44b6e7a267d71f13e65aaafcc6f792c795b06f11e46e/grpcio-1.66.2-cp310-cp310-win_amd64.whl", hash = "sha256:2018b053aa15782db2541ca01a7edb56a0bf18c77efed975392583725974b249", size = 4288611 }, - { url = "https://files.pythonhosted.org/packages/6f/30/eb9c490a1450f30a2f4f988c5227d38df1d3cf1b96bd7f86d1c01b975bd5/grpcio-1.66.2-cp311-cp311-linux_armv7l.whl", hash = "sha256:2335c58560a9e92ac58ff2bc5649952f9b37d0735608242973c7a8b94a6437d8", size = 5035597 }, - { url = "https://files.pythonhosted.org/packages/e4/81/e25c4e06e9c861760801812d60c4839bedfb62a955bbdbf3f4f9e1d21c9e/grpcio-1.66.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:45a3d462826f4868b442a6b8fdbe8b87b45eb4f5b5308168c156b21eca43f61c", size = 10815748 }, - { url = "https://files.pythonhosted.org/packages/d5/0e/f3458a4b480a9aa7ee28da8d38621898cb7b9c52bd6d7eeff4e65a9e54fd/grpcio-1.66.2-cp311-cp311-manylinux_2_17_aarch64.whl", hash = "sha256:a9539f01cb04950fd4b5ab458e64a15f84c2acc273670072abe49a3f29bbad54", size = 5535622 }, - { url = "https://files.pythonhosted.org/packages/88/63/83b994a95dec4d45bdd08a2c1ad78287c43ea8e05aa87f12fe73a034bec1/grpcio-1.66.2-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ce89f5876662f146d4c1f695dda29d4433a5d01c8681fbd2539afff535da14d4", size = 6133932 }, - { url = "https://files.pythonhosted.org/packages/35/90/a4f76c14230da281d51ef9eb30eb3ff2df129b83a4a98906756c063578c1/grpcio-1.66.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d25a14af966438cddf498b2e338f88d1c9706f3493b1d73b93f695c99c5f0e2a", size = 5791619 }, - { url = "https://files.pythonhosted.org/packages/ae/16/ae127be201e98a2bda5a602ea94a8e9b6351b2eb998c1177eb489ee03bb6/grpcio-1.66.2-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:6001e575b8bbd89eee11960bb640b6da6ae110cf08113a075f1e2051cc596cae", size = 6457847 }, - { url = "https://files.pythonhosted.org/packages/a0/98/b7c72630458b037f4b03bda4dbc22efcc44f6ce22ac0a90111d464d13849/grpcio-1.66.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:4ea1d062c9230278793820146c95d038dc0f468cbdd172eec3363e42ff1c7d01", size = 6051643 }, - { url = "https://files.pythonhosted.org/packages/53/47/268e0aeec678993a865ae7c14876a830224a1411aa98032969a6921ebd59/grpcio-1.66.2-cp311-cp311-win32.whl", hash = "sha256:38b68498ff579a3b1ee8f93a05eb48dc2595795f2f62716e797dc24774c1aaa8", size = 3555795 }, - { url = "https://files.pythonhosted.org/packages/f8/22/cf3e6ef61c62e631d5567810432a826a3f5752f132d6c3352f6cfbedbedb/grpcio-1.66.2-cp311-cp311-win_amd64.whl", hash = "sha256:6851de821249340bdb100df5eacfecfc4e6075fa85c6df7ee0eb213170ec8e5d", size = 4290733 }, - { url = "https://files.pythonhosted.org/packages/6b/5c/c4da36b7a77dbb15c4bc72228dff7161874752b2c6bddf7bb046d9da1b90/grpcio-1.66.2-cp312-cp312-linux_armv7l.whl", hash = "sha256:802d84fd3d50614170649853d121baaaa305de7b65b3e01759247e768d691ddf", size = 5002933 }, - { url = "https://files.pythonhosted.org/packages/a0/d5/b631445dff250a5301f51ff56c5fc917c7f955cd02fa55379f158a89abeb/grpcio-1.66.2-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:80fd702ba7e432994df208f27514280b4b5c6843e12a48759c9255679ad38db8", size = 10793953 }, - { url = "https://files.pythonhosted.org/packages/c8/1c/2179ac112152e92c02990f98183edf645df14aa3c38b39f1a3a60358b6c6/grpcio-1.66.2-cp312-cp312-manylinux_2_17_aarch64.whl", hash = "sha256:12fda97ffae55e6526825daf25ad0fa37483685952b5d0f910d6405c87e3adb6", size = 5499791 }, - { url = "https://files.pythonhosted.org/packages/0b/53/8d7ab865fbd983309c8242930f00b28a01047f70c2b2e4c79a5c92a46a08/grpcio-1.66.2-cp312-cp312-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:950da58d7d80abd0ea68757769c9db0a95b31163e53e5bb60438d263f4bed7b7", size = 6109606 }, - { url = "https://files.pythonhosted.org/packages/86/e9/3dfb5a3ff540636d46b8b723345e923e8c553d9b3f6a8d1b09b0d915eb46/grpcio-1.66.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e636ce23273683b00410f1971d209bf3689238cf5538d960adc3cdfe80dd0dbd", size = 5762866 }, - { url = "https://files.pythonhosted.org/packages/f1/cb/c07493ad5dd73d51e4e15b0d483ff212dfec136ee1e4f3b49d115bdc7a13/grpcio-1.66.2-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:a917d26e0fe980b0ac7bfcc1a3c4ad6a9a4612c911d33efb55ed7833c749b0ee", size = 6446819 }, - { url = "https://files.pythonhosted.org/packages/ff/5f/142e19db367a34ea0ee8a8451e43215d0a1a5dbffcfdcae8801f22903301/grpcio-1.66.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:49f0ca7ae850f59f828a723a9064cadbed90f1ece179d375966546499b8a2c9c", size = 6040273 }, - { url = "https://files.pythonhosted.org/packages/5c/3b/12fcd752c55002e4b0e0a7bd5faec101bc0a4e3890be3f95a43353142481/grpcio-1.66.2-cp312-cp312-win32.whl", hash = "sha256:31fd163105464797a72d901a06472860845ac157389e10f12631025b3e4d0453", size = 3537988 }, - { url = "https://files.pythonhosted.org/packages/f1/70/76bfea3faa862bfceccba255792e780691ff25b8227180759c9d38769379/grpcio-1.66.2-cp312-cp312-win_amd64.whl", hash = "sha256:ff1f7882e56c40b0d33c4922c15dfa30612f05fb785074a012f7cda74d1c3679", size = 4275553 }, - { url = "https://files.pythonhosted.org/packages/72/31/8708a8dfb3f1ac89926c27c5dd17412764157a2959dbc5a606eaf8ac71f6/grpcio-1.66.2-cp313-cp313-linux_armv7l.whl", hash = "sha256:3b00efc473b20d8bf83e0e1ae661b98951ca56111feb9b9611df8efc4fe5d55d", size = 5004245 }, - { url = "https://files.pythonhosted.org/packages/8b/37/0b57c3769efb3cc9ec97fcaa9f7243046660e7ed58c0faebc4ef315df92c/grpcio-1.66.2-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:1caa38fb22a8578ab8393da99d4b8641e3a80abc8fd52646f1ecc92bcb8dee34", size = 10756749 }, - { url = "https://files.pythonhosted.org/packages/bf/5a/425e995724a19a1b110340ed653bc7c5de8019d9fc84b3798a0f79c3eb31/grpcio-1.66.2-cp313-cp313-manylinux_2_17_aarch64.whl", hash = "sha256:c408f5ef75cfffa113cacd8b0c0e3611cbfd47701ca3cdc090594109b9fcbaed", size = 5499666 }, - { url = "https://files.pythonhosted.org/packages/2e/e4/86a5c5ec40a6b683671a1d044ebca433812d99da8fcfc2889e9c43cecbd4/grpcio-1.66.2-cp313-cp313-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c806852deaedee9ce8280fe98955c9103f62912a5b2d5ee7e3eaa284a6d8d8e7", size = 6109578 }, - { url = "https://files.pythonhosted.org/packages/2f/86/a86742f3deaa22385c3bff984c5947fc62d47d3fab26c508730037d027e5/grpcio-1.66.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f145cc21836c332c67baa6fc81099d1d27e266401565bf481948010d6ea32d46", size = 5763274 }, - { url = "https://files.pythonhosted.org/packages/c3/61/b9a2a4345dea0a354c4ed8ac7aacbdd0ff986acbc8f92680213cf3d2faa3/grpcio-1.66.2-cp313-cp313-musllinux_1_1_i686.whl", hash = "sha256:73e3b425c1e155730273f73e419de3074aa5c5e936771ee0e4af0814631fb30a", size = 6450416 }, - { url = "https://files.pythonhosted.org/packages/50/b9/ad303ce75d8cd71d855a661519aa160ce42f27498f589f1ae6d9f8c5e8ac/grpcio-1.66.2-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:9c509a4f78114cbc5f0740eb3d7a74985fd2eff022971bc9bc31f8bc93e66a3b", size = 6040045 }, - { url = "https://files.pythonhosted.org/packages/ac/b3/8db1873e3240ef1672ba87b89e949ece367089e29e4d221377bfdd288bd3/grpcio-1.66.2-cp313-cp313-win32.whl", hash = "sha256:20657d6b8cfed7db5e11b62ff7dfe2e12064ea78e93f1434d61888834bc86d75", size = 3537126 }, - { url = "https://files.pythonhosted.org/packages/a2/df/133216989fe7e17caeafd7ff5b17cc82c4e722025d0b8d5d2290c11fe2e6/grpcio-1.66.2-cp313-cp313-win_amd64.whl", hash = "sha256:fb70487c95786e345af5e854ffec8cb8cc781bcc5df7930c4fbb7feaa72e1cdf", size = 4278018 }, + { url = "https://files.pythonhosted.org/packages/46/da/c4a24a5245aba95c411a21c7525a41113b669b646a79ab8523551c4185cf/grpcio-1.67.0-cp310-cp310-linux_armv7l.whl", hash = "sha256:bd79929b3bb96b54df1296cd3bf4d2b770bd1df6c2bdf549b49bab286b925cdc", size = 5108503 }, + { url = "https://files.pythonhosted.org/packages/08/29/1f46e9d2d9d34f4117f7dccfd7e222f1b0ea1fa1c5bd319e7b7017f4bc32/grpcio-1.67.0-cp310-cp310-macosx_12_0_universal2.whl", hash = "sha256:16724ffc956ea42967f5758c2f043faef43cb7e48a51948ab593570570d1e68b", size = 10930122 }, + { url = "https://files.pythonhosted.org/packages/f0/ff/20774848a070b544c52a6e198d4bb439528bd440678f3bd3f65a41a9d804/grpcio-1.67.0-cp310-cp310-manylinux_2_17_aarch64.whl", hash = "sha256:2b7183c80b602b0ad816315d66f2fb7887614ead950416d60913a9a71c12560d", size = 5630547 }, + { url = "https://files.pythonhosted.org/packages/60/05/4986994d96011c6b853f2f40ea2bf0c7ed97fc3a2391d004064697de01b7/grpcio-1.67.0-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:efe32b45dd6d118f5ea2e5deaed417d8a14976325c93812dd831908522b402c9", size = 6237824 }, + { url = "https://files.pythonhosted.org/packages/fa/1c/772a501cd18baffba5f9eeb54ce353c8749e9217c262bb7953427417db40/grpcio-1.67.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fe89295219b9c9e47780a0f1c75ca44211e706d1c598242249fe717af3385ec8", size = 5881526 }, + { url = "https://files.pythonhosted.org/packages/6c/38/6f0243ce5b5f2b5f4cc34c8e0ba6b466db4b333bfb643f61e459bbe0b92c/grpcio-1.67.0-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:aa8d025fae1595a207b4e47c2e087cb88d47008494db258ac561c00877d4c8f8", size = 6582793 }, + { url = "https://files.pythonhosted.org/packages/ed/9f/c489cd122618ea808593d20a47ff68722b3c99c030c175550b85bb256fb0/grpcio-1.67.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:f95e15db43e75a534420e04822df91f645664bf4ad21dfaad7d51773c80e6bb4", size = 6162111 }, + { url = "https://files.pythonhosted.org/packages/b7/a6/6384d59d26a5dbc7adffc0abf3d88107494ba3eb92bc9bd3f7fc7c18679d/grpcio-1.67.0-cp310-cp310-win32.whl", hash = "sha256:a6b9a5c18863fd4b6624a42e2712103fb0f57799a3b29651c0e5b8119a519d65", size = 3614488 }, + { url = "https://files.pythonhosted.org/packages/6b/20/5da50579c2b6341490459a44a97fd53d23a5c0e928bea78cf80ce67f8b1a/grpcio-1.67.0-cp310-cp310-win_amd64.whl", hash = "sha256:b6eb68493a05d38b426604e1dc93bfc0137c4157f7ab4fac5771fd9a104bbaa6", size = 4350825 }, + { url = "https://files.pythonhosted.org/packages/86/a2/5d3b07fe984e3eab147ebe141f0111ab19eb0c27dfdf19360c3de60a0341/grpcio-1.67.0-cp311-cp311-linux_armv7l.whl", hash = "sha256:e91d154689639932305b6ea6f45c6e46bb51ecc8ea77c10ef25aa77f75443ad4", size = 5116425 }, + { url = "https://files.pythonhosted.org/packages/79/23/18730cca0d18ffde1de132a9230745a5c113cbc6dd8cde71c2288a21f5a3/grpcio-1.67.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:cb204a742997277da678611a809a8409657b1398aaeebf73b3d9563b7d154c13", size = 11005387 }, + { url = "https://files.pythonhosted.org/packages/33/30/f8fa49eb3f30e4c730f3f37aa33f49cbad592906b93a9445e8ceedeaa96c/grpcio-1.67.0-cp311-cp311-manylinux_2_17_aarch64.whl", hash = "sha256:ae6de510f670137e755eb2a74b04d1041e7210af2444103c8c95f193340d17ee", size = 5627195 }, + { url = "https://files.pythonhosted.org/packages/80/39/e1f7ac3938ac7763732d545fcfdcff23ed8e993513321b3d21cae146beb4/grpcio-1.67.0-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:74b900566bdf68241118f2918d312d3bf554b2ce0b12b90178091ea7d0a17b3d", size = 6237935 }, + { url = "https://files.pythonhosted.org/packages/8e/a5/b99333f0a9f4599468bb4b7cb59aa1a7e2a2f67a59b5b13fdc7ea0acf0ad/grpcio-1.67.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a4e95e43447a02aa603abcc6b5e727d093d161a869c83b073f50b9390ecf0fa8", size = 5879332 }, + { url = "https://files.pythonhosted.org/packages/6a/22/b9800736805c5bddd0c9a9d3b1556c682a0dee8ae63051c565d888a2bc87/grpcio-1.67.0-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:0bb94e66cd8f0baf29bd3184b6aa09aeb1a660f9ec3d85da615c5003154bc2bf", size = 6578617 }, + { url = "https://files.pythonhosted.org/packages/20/a5/dd2e69777767c321ddaa886047dccc555f09f4fcdfc5164e440f1f4b589d/grpcio-1.67.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:82e5bd4b67b17c8c597273663794a6a46a45e44165b960517fe6d8a2f7f16d23", size = 6160509 }, + { url = "https://files.pythonhosted.org/packages/b7/5a/b12f69f687d9eb593405fa450a24ba4ee8f6058c6c43d1995bed023c6a61/grpcio-1.67.0-cp311-cp311-win32.whl", hash = "sha256:7fc1d2b9fd549264ae585026b266ac2db53735510a207381be509c315b4af4e8", size = 3614902 }, + { url = "https://files.pythonhosted.org/packages/aa/81/5a3503b9757a89c7d1fa7672b788fcbcafce91cdc94a3e0c53513a3201d7/grpcio-1.67.0-cp311-cp311-win_amd64.whl", hash = "sha256:ac11ecb34a86b831239cc38245403a8de25037b448464f95c3315819e7519772", size = 4352547 }, + { url = "https://files.pythonhosted.org/packages/b0/2d/b2a783f1d93735a259676de5558ef019ac3511e894b8e9d224edc0d7d034/grpcio-1.67.0-cp312-cp312-linux_armv7l.whl", hash = "sha256:227316b5631260e0bef8a3ce04fa7db4cc81756fea1258b007950b6efc90c05d", size = 5086495 }, + { url = "https://files.pythonhosted.org/packages/7b/13/c1f537a88dad543ca0a7be4dfee80a21b3b02b7df27750997777355e5840/grpcio-1.67.0-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:d90cfdafcf4b45a7a076e3e2a58e7bc3d59c698c4f6470b0bb13a4d869cf2273", size = 10979109 }, + { url = "https://files.pythonhosted.org/packages/b7/83/d7cb72f2202fe8d608d25c7e9d6d75184bf6ef658688c818821add102211/grpcio-1.67.0-cp312-cp312-manylinux_2_17_aarch64.whl", hash = "sha256:77196216d5dd6f99af1c51e235af2dd339159f657280e65ce7e12c1a8feffd1d", size = 5586952 }, + { url = "https://files.pythonhosted.org/packages/e5/18/8df585d0158af9e2b46ee2388bdb21de0e7f5bf4a47a86a861ebdbf947b5/grpcio-1.67.0-cp312-cp312-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:15c05a26a0f7047f720da41dc49406b395c1470eef44ff7e2c506a47ac2c0591", size = 6212460 }, + { url = "https://files.pythonhosted.org/packages/47/46/027f8943113961784ce1eb69a28544d9a62ffb286332820ba634d979c91c/grpcio-1.67.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3840994689cc8cbb73d60485c594424ad8adb56c71a30d8948d6453083624b52", size = 5849002 }, + { url = "https://files.pythonhosted.org/packages/eb/26/fb19d5bc277e665382c835d7af1f8c1e3197576eed76327824d79e2a4bef/grpcio-1.67.0-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:5a1e03c3102b6451028d5dc9f8591131d6ab3c8a0e023d94c28cb930ed4b5f81", size = 6568222 }, + { url = "https://files.pythonhosted.org/packages/e0/cc/387efa986f166c068d48331c699e6ee662a057371065f35d3ca1bc09d799/grpcio-1.67.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:682968427a63d898759474e3b3178d42546e878fdce034fd7474ef75143b64e3", size = 6148002 }, + { url = "https://files.pythonhosted.org/packages/24/57/529504e3e3e910f0537a0a36184cb7241d0d111109d6588096a9f8c139bf/grpcio-1.67.0-cp312-cp312-win32.whl", hash = "sha256:d01793653248f49cf47e5695e0a79805b1d9d4eacef85b310118ba1dfcd1b955", size = 3596220 }, + { url = "https://files.pythonhosted.org/packages/1d/1f/acf03ee901313446d52c3916d527d4981de9f6f3edc69267d05509dcfa7b/grpcio-1.67.0-cp312-cp312-win_amd64.whl", hash = "sha256:985b2686f786f3e20326c4367eebdaed3e7aa65848260ff0c6644f817042cb15", size = 4343545 }, + { url = "https://files.pythonhosted.org/packages/7a/e7/cc7feccb18ef0b5aa67ccb7859a091fa836c5d361a0109b9fca578e59e64/grpcio-1.67.0-cp313-cp313-linux_armv7l.whl", hash = "sha256:8c9a35b8bc50db35ab8e3e02a4f2a35cfba46c8705c3911c34ce343bd777813a", size = 5087009 }, + { url = "https://files.pythonhosted.org/packages/bd/56/10175f4b1600b16e601680df053361924a9fcd9e1c0ad9b8bd1ba2b4c864/grpcio-1.67.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:42199e704095b62688998c2d84c89e59a26a7d5d32eed86d43dc90e7a3bd04aa", size = 10937553 }, + { url = "https://files.pythonhosted.org/packages/aa/85/115538b1aeb09d66c6e637608a56eddacd59eb71ab0161ad59172c01d436/grpcio-1.67.0-cp313-cp313-manylinux_2_17_aarch64.whl", hash = "sha256:c4c425f440fb81f8d0237c07b9322fc0fb6ee2b29fbef5f62a322ff8fcce240d", size = 5586507 }, + { url = "https://files.pythonhosted.org/packages/0f/db/f402a455e287154683235183c2843c27fffe2fc03fa4c45b57dd90011401/grpcio-1.67.0-cp313-cp313-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:323741b6699cd2b04a71cb38f502db98f90532e8a40cb675393d248126a268af", size = 6211948 }, + { url = "https://files.pythonhosted.org/packages/92/e4/5957806105aad556f7df6a420b6c69044b6f707926392118772a8ba96de4/grpcio-1.67.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:662c8e105c5e5cee0317d500eb186ed7a93229586e431c1bf0c9236c2407352c", size = 5849392 }, + { url = "https://files.pythonhosted.org/packages/88/ab/c496a406f4682c56e933bef6b0ed22b9eaec84c6915f83d5cddd94126e16/grpcio-1.67.0-cp313-cp313-musllinux_1_1_i686.whl", hash = "sha256:f6bd2ab135c64a4d1e9e44679a616c9bc944547357c830fafea5c3caa3de5153", size = 6571359 }, + { url = "https://files.pythonhosted.org/packages/9e/a8/96b3ef565791d7282c300c07c2a7080471311e7d5a239db15678aaac47eb/grpcio-1.67.0-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:2f55c1e0e2ae9bdd23b3c63459ee4c06d223b68aeb1961d83c48fb63dc29bc03", size = 6147905 }, + { url = "https://files.pythonhosted.org/packages/cd/b7/846cc563209ff5af88bc7dcb269948210674c2f743e7fd8e1a2ad9708e89/grpcio-1.67.0-cp313-cp313-win32.whl", hash = "sha256:fd6bc27861e460fe28e94226e3673d46e294ca4673d46b224428d197c5935e69", size = 3594603 }, + { url = "https://files.pythonhosted.org/packages/bd/74/49d27908b369b72fd3373ec0f16d7f58614fb7101cb38b266afeab846cca/grpcio-1.67.0-cp313-cp313-win_amd64.whl", hash = "sha256:cf51d28063338608cd8d3cd64677e922134837902b70ce00dad7f116e3998210", size = 4345468 }, ] [[package]] @@ -1521,36 +1492,43 @@ wheels = [ [[package]] name = "httptools" -version = "0.6.1" +version = "0.6.4" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/67/1d/d77686502fced061b3ead1c35a2d70f6b281b5f723c4eff7a2277c04e4a2/httptools-0.6.1.tar.gz", hash = "sha256:c6e26c30455600b95d94b1b836085138e82f177351454ee841c148f93a9bad5a", size = 191228 } +sdist = { url = "https://files.pythonhosted.org/packages/a7/9a/ce5e1f7e131522e6d3426e8e7a490b3a01f39a6696602e1c4f33f9e94277/httptools-0.6.4.tar.gz", hash = "sha256:4e93eee4add6493b59a5c514da98c939b244fce4a0d8879cd3f466562f4b7d5c", size = 240639 } wheels = [ - { url = "https://files.pythonhosted.org/packages/a9/6a/80bce0216b63babf51cdc34814c3f0f10489e13ab89fb6bc91202736a8a2/httptools-0.6.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:d2f6c3c4cb1948d912538217838f6e9960bc4a521d7f9b323b3da579cd14532f", size = 149778 }, - { url = "https://files.pythonhosted.org/packages/bd/7d/4cd75356dfe0ed0b40ca6873646bf9ff7b5138236c72338dc569dc57d509/httptools-0.6.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:00d5d4b68a717765b1fabfd9ca755bd12bf44105eeb806c03d1962acd9b8e563", size = 77604 }, - { url = "https://files.pythonhosted.org/packages/4e/74/6348ce41fb5c1484f35184c172efb8854a288e6090bb54e2210598268369/httptools-0.6.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:639dc4f381a870c9ec860ce5c45921db50205a37cc3334e756269736ff0aac58", size = 346717 }, - { url = "https://files.pythonhosted.org/packages/65/e7/dd5ba95c84047118a363f0755ad78e639e0529be92424bb020496578aa3b/httptools-0.6.1-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e57997ac7fb7ee43140cc03664de5f268813a481dff6245e0075925adc6aa185", size = 341442 }, - { url = "https://files.pythonhosted.org/packages/d8/97/b37d596bc32be291477a8912bf9d1508d7e8553aa11a30cd871fd89cbae4/httptools-0.6.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:0ac5a0ae3d9f4fe004318d64b8a854edd85ab76cffbf7ef5e32920faef62f142", size = 354531 }, - { url = "https://files.pythonhosted.org/packages/99/c9/53ed7176583ec4b4364d941a08624288f2ae55b4ff58b392cdb68db1e1ed/httptools-0.6.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:3f30d3ce413088a98b9db71c60a6ada2001a08945cb42dd65a9a9fe228627658", size = 347754 }, - { url = "https://files.pythonhosted.org/packages/1e/fc/8a26c2adcd3f141e4729897633f03832b71ebea6f4c31cce67a92ded1961/httptools-0.6.1-cp310-cp310-win_amd64.whl", hash = "sha256:1ed99a373e327f0107cb513b61820102ee4f3675656a37a50083eda05dc9541b", size = 58165 }, - { url = "https://files.pythonhosted.org/packages/f5/d1/53283b96ed823d5e4d89ee9aa0f29df5a1bdf67f148e061549a595d534e4/httptools-0.6.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:7a7ea483c1a4485c71cb5f38be9db078f8b0e8b4c4dc0210f531cdd2ddac1ef1", size = 145855 }, - { url = "https://files.pythonhosted.org/packages/80/dd/cebc9d4b1d4b70e9f3d40d1db0829a28d57ca139d0b04197713816a11996/httptools-0.6.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:85ed077c995e942b6f1b07583e4eb0a8d324d418954fc6af913d36db7c05a5a0", size = 75604 }, - { url = "https://files.pythonhosted.org/packages/76/7a/45c5a9a2e9d21f7381866eb7b6ead5a84d8fe7e54e35208eeb18320a29b4/httptools-0.6.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8b0bb634338334385351a1600a73e558ce619af390c2b38386206ac6a27fecfc", size = 324784 }, - { url = "https://files.pythonhosted.org/packages/59/23/047a89e66045232fb82c50ae57699e40f70e073ae5ccd53f54e532fbd2a2/httptools-0.6.1-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7d9ceb2c957320def533671fc9c715a80c47025139c8d1f3797477decbc6edd2", size = 318547 }, - { url = "https://files.pythonhosted.org/packages/82/f5/50708abc7965d7d93c0ee14a148ccc6d078a508f47fe9357c79d5360f252/httptools-0.6.1-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:4f0f8271c0a4db459f9dc807acd0eadd4839934a4b9b892f6f160e94da309837", size = 330211 }, - { url = "https://files.pythonhosted.org/packages/e3/1e/9823ca7aab323c0e0e9dd82ce835a6e93b69f69aedffbc94d31e327f4283/httptools-0.6.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:6a4f5ccead6d18ec072ac0b84420e95d27c1cdf5c9f1bc8fbd8daf86bd94f43d", size = 322174 }, - { url = "https://files.pythonhosted.org/packages/14/e4/20d28dfe7f5b5603b6b04c33bb88662ad749de51f0c539a561f235f42666/httptools-0.6.1-cp311-cp311-win_amd64.whl", hash = "sha256:5cceac09f164bcba55c0500a18fe3c47df29b62353198e4f37bbcc5d591172c3", size = 55434 }, - { url = "https://files.pythonhosted.org/packages/60/13/b62e086b650752adf9094b7e62dab97f4cb7701005664544494b7956a51e/httptools-0.6.1-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:75c8022dca7935cba14741a42744eee13ba05db00b27a4b940f0d646bd4d56d0", size = 146354 }, - { url = "https://files.pythonhosted.org/packages/f8/5d/9ad32b79b6c24524087e78aa3f0a2dfcf58c11c90e090e4593b35def8a86/httptools-0.6.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:48ed8129cd9a0d62cf4d1575fcf90fb37e3ff7d5654d3a5814eb3d55f36478c2", size = 75785 }, - { url = "https://files.pythonhosted.org/packages/d0/a4/b503851c40f20bcbd453db24ed35d961f62abdae0dccc8f672cd5d350d87/httptools-0.6.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6f58e335a1402fb5a650e271e8c2d03cfa7cea46ae124649346d17bd30d59c90", size = 345396 }, - { url = "https://files.pythonhosted.org/packages/a2/9a/aa406864f3108e06f7320425a528ff8267124dead1fd72a3e9da2067f893/httptools-0.6.1-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:93ad80d7176aa5788902f207a4e79885f0576134695dfb0fefc15b7a4648d503", size = 344741 }, - { url = "https://files.pythonhosted.org/packages/cf/3a/3fd8dfb987c4247651baf2ac6f28e8e9f889d484ca1a41a9ad0f04dfe300/httptools-0.6.1-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:9bb68d3a085c2174c2477eb3ffe84ae9fb4fde8792edb7bcd09a1d8467e30a84", size = 345096 }, - { url = "https://files.pythonhosted.org/packages/80/01/379f6466d8e2edb861c1f44ccac255ed1f8a0d4c5c666a1ceb34caad7555/httptools-0.6.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:b512aa728bc02354e5ac086ce76c3ce635b62f5fbc32ab7082b5e582d27867bb", size = 343535 }, - { url = "https://files.pythonhosted.org/packages/d3/97/60860e9ee87a7d4712b98f7e1411730520053b9d69e9e42b0b9751809c17/httptools-0.6.1-cp312-cp312-win_amd64.whl", hash = "sha256:97662ce7fb196c785344d00d638fc9ad69e18ee4bfb4000b35a52efe5adcc949", size = 55660 }, + { url = "https://files.pythonhosted.org/packages/3b/6f/972f8eb0ea7d98a1c6be436e2142d51ad2a64ee18e02b0e7ff1f62171ab1/httptools-0.6.4-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:3c73ce323711a6ffb0d247dcd5a550b8babf0f757e86a52558fe5b86d6fefcc0", size = 198780 }, + { url = "https://files.pythonhosted.org/packages/6a/b0/17c672b4bc5c7ba7f201eada4e96c71d0a59fbc185e60e42580093a86f21/httptools-0.6.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:345c288418f0944a6fe67be8e6afa9262b18c7626c3ef3c28adc5eabc06a68da", size = 103297 }, + { url = "https://files.pythonhosted.org/packages/92/5e/b4a826fe91971a0b68e8c2bd4e7db3e7519882f5a8ccdb1194be2b3ab98f/httptools-0.6.4-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:deee0e3343f98ee8047e9f4c5bc7cedbf69f5734454a94c38ee829fb2d5fa3c1", size = 443130 }, + { url = "https://files.pythonhosted.org/packages/b0/51/ce61e531e40289a681a463e1258fa1e05e0be54540e40d91d065a264cd8f/httptools-0.6.4-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ca80b7485c76f768a3bc83ea58373f8db7b015551117375e4918e2aa77ea9b50", size = 442148 }, + { url = "https://files.pythonhosted.org/packages/ea/9e/270b7d767849b0c96f275c695d27ca76c30671f8eb8cc1bab6ced5c5e1d0/httptools-0.6.4-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:90d96a385fa941283ebd231464045187a31ad932ebfa541be8edf5b3c2328959", size = 415949 }, + { url = "https://files.pythonhosted.org/packages/81/86/ced96e3179c48c6f656354e106934e65c8963d48b69be78f355797f0e1b3/httptools-0.6.4-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:59e724f8b332319e2875efd360e61ac07f33b492889284a3e05e6d13746876f4", size = 417591 }, + { url = "https://files.pythonhosted.org/packages/75/73/187a3f620ed3175364ddb56847d7a608a6fc42d551e133197098c0143eca/httptools-0.6.4-cp310-cp310-win_amd64.whl", hash = "sha256:c26f313951f6e26147833fc923f78f95604bbec812a43e5ee37f26dc9e5a686c", size = 88344 }, + { url = "https://files.pythonhosted.org/packages/7b/26/bb526d4d14c2774fe07113ca1db7255737ffbb119315839af2065abfdac3/httptools-0.6.4-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:f47f8ed67cc0ff862b84a1189831d1d33c963fb3ce1ee0c65d3b0cbe7b711069", size = 199029 }, + { url = "https://files.pythonhosted.org/packages/a6/17/3e0d3e9b901c732987a45f4f94d4e2c62b89a041d93db89eafb262afd8d5/httptools-0.6.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:0614154d5454c21b6410fdf5262b4a3ddb0f53f1e1721cfd59d55f32138c578a", size = 103492 }, + { url = "https://files.pythonhosted.org/packages/b7/24/0fe235d7b69c42423c7698d086d4db96475f9b50b6ad26a718ef27a0bce6/httptools-0.6.4-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f8787367fbdfccae38e35abf7641dafc5310310a5987b689f4c32cc8cc3ee975", size = 462891 }, + { url = "https://files.pythonhosted.org/packages/b1/2f/205d1f2a190b72da6ffb5f41a3736c26d6fa7871101212b15e9b5cd8f61d/httptools-0.6.4-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:40b0f7fe4fd38e6a507bdb751db0379df1e99120c65fbdc8ee6c1d044897a636", size = 459788 }, + { url = "https://files.pythonhosted.org/packages/6e/4c/d09ce0eff09057a206a74575ae8f1e1e2f0364d20e2442224f9e6612c8b9/httptools-0.6.4-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:40a5ec98d3f49904b9fe36827dcf1aadfef3b89e2bd05b0e35e94f97c2b14721", size = 433214 }, + { url = "https://files.pythonhosted.org/packages/3e/d2/84c9e23edbccc4a4c6f96a1b8d99dfd2350289e94f00e9ccc7aadde26fb5/httptools-0.6.4-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:dacdd3d10ea1b4ca9df97a0a303cbacafc04b5cd375fa98732678151643d4988", size = 434120 }, + { url = "https://files.pythonhosted.org/packages/d0/46/4d8e7ba9581416de1c425b8264e2cadd201eb709ec1584c381f3e98f51c1/httptools-0.6.4-cp311-cp311-win_amd64.whl", hash = "sha256:288cd628406cc53f9a541cfaf06041b4c71d751856bab45e3702191f931ccd17", size = 88565 }, + { url = "https://files.pythonhosted.org/packages/bb/0e/d0b71465c66b9185f90a091ab36389a7352985fe857e352801c39d6127c8/httptools-0.6.4-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:df017d6c780287d5c80601dafa31f17bddb170232d85c066604d8558683711a2", size = 200683 }, + { url = "https://files.pythonhosted.org/packages/e2/b8/412a9bb28d0a8988de3296e01efa0bd62068b33856cdda47fe1b5e890954/httptools-0.6.4-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:85071a1e8c2d051b507161f6c3e26155b5c790e4e28d7f236422dbacc2a9cc44", size = 104337 }, + { url = "https://files.pythonhosted.org/packages/9b/01/6fb20be3196ffdc8eeec4e653bc2a275eca7f36634c86302242c4fbb2760/httptools-0.6.4-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:69422b7f458c5af875922cdb5bd586cc1f1033295aa9ff63ee196a87519ac8e1", size = 508796 }, + { url = "https://files.pythonhosted.org/packages/f7/d8/b644c44acc1368938317d76ac991c9bba1166311880bcc0ac297cb9d6bd7/httptools-0.6.4-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:16e603a3bff50db08cd578d54f07032ca1631450ceb972c2f834c2b860c28ea2", size = 510837 }, + { url = "https://files.pythonhosted.org/packages/52/d8/254d16a31d543073a0e57f1c329ca7378d8924e7e292eda72d0064987486/httptools-0.6.4-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:ec4f178901fa1834d4a060320d2f3abc5c9e39766953d038f1458cb885f47e81", size = 485289 }, + { url = "https://files.pythonhosted.org/packages/5f/3c/4aee161b4b7a971660b8be71a92c24d6c64372c1ab3ae7f366b3680df20f/httptools-0.6.4-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:f9eb89ecf8b290f2e293325c646a211ff1c2493222798bb80a530c5e7502494f", size = 489779 }, + { url = "https://files.pythonhosted.org/packages/12/b7/5cae71a8868e555f3f67a50ee7f673ce36eac970f029c0c5e9d584352961/httptools-0.6.4-cp312-cp312-win_amd64.whl", hash = "sha256:db78cb9ca56b59b016e64b6031eda5653be0589dba2b1b43453f6e8b405a0970", size = 88634 }, + { url = "https://files.pythonhosted.org/packages/94/a3/9fe9ad23fd35f7de6b91eeb60848986058bd8b5a5c1e256f5860a160cc3e/httptools-0.6.4-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:ade273d7e767d5fae13fa637f4d53b6e961fb7fd93c7797562663f0171c26660", size = 197214 }, + { url = "https://files.pythonhosted.org/packages/ea/d9/82d5e68bab783b632023f2fa31db20bebb4e89dfc4d2293945fd68484ee4/httptools-0.6.4-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:856f4bc0478ae143bad54a4242fccb1f3f86a6e1be5548fecfd4102061b3a083", size = 102431 }, + { url = "https://files.pythonhosted.org/packages/96/c1/cb499655cbdbfb57b577734fde02f6fa0bbc3fe9fb4d87b742b512908dff/httptools-0.6.4-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:322d20ea9cdd1fa98bd6a74b77e2ec5b818abdc3d36695ab402a0de8ef2865a3", size = 473121 }, + { url = "https://files.pythonhosted.org/packages/af/71/ee32fd358f8a3bb199b03261f10921716990808a675d8160b5383487a317/httptools-0.6.4-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4d87b29bd4486c0093fc64dea80231f7c7f7eb4dc70ae394d70a495ab8436071", size = 473805 }, + { url = "https://files.pythonhosted.org/packages/8a/0a/0d4df132bfca1507114198b766f1737d57580c9ad1cf93c1ff673e3387be/httptools-0.6.4-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:342dd6946aa6bda4b8f18c734576106b8a31f2fe31492881a9a160ec84ff4bd5", size = 448858 }, + { url = "https://files.pythonhosted.org/packages/1e/6a/787004fdef2cabea27bad1073bf6a33f2437b4dbd3b6fb4a9d71172b1c7c/httptools-0.6.4-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:4b36913ba52008249223042dca46e69967985fb4051951f94357ea681e1f5dc0", size = 452042 }, + { url = "https://files.pythonhosted.org/packages/4d/dc/7decab5c404d1d2cdc1bb330b1bf70e83d6af0396fd4fc76fc60c0d522bf/httptools-0.6.4-cp313-cp313-win_amd64.whl", hash = "sha256:28908df1b9bb8187393d5b5db91435ccc9c8e891657f9cbb42a2541b44c82fc8", size = 87682 }, ] [[package]] name = "httpx" -version = "0.27.0" +version = "0.27.2" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "anyio" }, @@ -1559,9 +1537,9 @@ dependencies = [ { name = "idna" }, { name = "sniffio" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/5c/2d/3da5bdf4408b8b2800061c339f240c1802f2e82d55e50bd39c5a881f47f0/httpx-0.27.0.tar.gz", hash = "sha256:a0cb88a46f32dc874e04ee956e4c2764aba2aa228f650b06788ba6bda2962ab5", size = 126413 } +sdist = { url = "https://files.pythonhosted.org/packages/78/82/08f8c936781f67d9e6b9eeb8a0c8b4e406136ea4c3d1f89a5db71d42e0e6/httpx-0.27.2.tar.gz", hash = "sha256:f7c2be1d2f3c3c3160d441802406b206c2b76f5947b11115e6df10c6c65e66c2", size = 144189 } wheels = [ - { url = "https://files.pythonhosted.org/packages/41/7b/ddacf6dcebb42466abd03f368782142baa82e08fc0c1f8eaa05b4bae87d5/httpx-0.27.0-py3-none-any.whl", hash = "sha256:71d5465162c13681bff01ad59b2cc68dd838ea1f10e51574bac27103f00c91a5", size = 75590 }, + { url = "https://files.pythonhosted.org/packages/56/95/9377bcb415797e44274b51d46e3249eba641711cf3348050f76ee7b15ffc/httpx-0.27.2-py3-none-any.whl", hash = "sha256:7bb2708e112d8fdd7829cd4243970f0c223274051cb35ee80c03301ee29a3df0", size = 76395 }, ] [package.optional-dependencies] @@ -1580,7 +1558,7 @@ wheels = [ [[package]] name = "huggingface-hub" -version = "0.25.1" +version = "0.26.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "filelock" }, @@ -1591,9 +1569,9 @@ dependencies = [ { name = "tqdm" }, { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/3d/88/3598259f226c37279e219810cc47cdeec39da1d07ad2e8c146af410d2cc6/huggingface_hub-0.25.1.tar.gz", hash = "sha256:9ff7cb327343211fbd06e2b149b8f362fd1e389454f3f14c6db75a4999ee20ff", size = 365676 } +sdist = { url = "https://files.pythonhosted.org/packages/44/99/c8fdef6fe09a1719e5e5de24b012de5824889168c96143f5531cab5af42b/huggingface_hub-0.26.1.tar.gz", hash = "sha256:414c0d9b769eecc86c70f9d939d0f48bb28e8461dd1130021542eff0212db890", size = 375458 } wheels = [ - { url = "https://files.pythonhosted.org/packages/5f/f1/15dc793cb109a801346f910a6b350530f2a763a6e83b221725a0bcc1e297/huggingface_hub-0.25.1-py3-none-any.whl", hash = "sha256:a5158ded931b3188f54ea9028097312cb0acd50bffaaa2612014c3c526b44972", size = 436438 }, + { url = "https://files.pythonhosted.org/packages/d7/4d/017d8d7cff5100092da8ea19139bcb1965bbadcbb5ddd0480e2badc299e8/huggingface_hub-0.26.1-py3-none-any.whl", hash = "sha256:5927a8fc64ae68859cd954b7cc29d1c8390a5e15caba6d3d349c973be8fdacf3", size = 447439 }, ] [[package]] @@ -1637,14 +1615,14 @@ wheels = [ [[package]] name = "importlib-metadata" -version = "6.11.0" +version = "8.4.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "zipp" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/ee/eb/58c2ab27ee628ad801f56d4017fe62afab0293116f6d0b08f1d5bd46e06f/importlib_metadata-6.11.0.tar.gz", hash = "sha256:1231cf92d825c9e03cfc4da076a16de6422c863558229ea0b22b675657463443", size = 54593 } +sdist = { url = "https://files.pythonhosted.org/packages/c0/bd/fa8ce65b0a7d4b6d143ec23b0f5fd3f7ab80121078c465bc02baeaab22dc/importlib_metadata-8.4.0.tar.gz", hash = "sha256:9a547d3bc3608b025f93d403fdd1aae741c24fbb8314df4b155675742ce303c5", size = 54320 } wheels = [ - { url = "https://files.pythonhosted.org/packages/59/9b/ecce94952ab5ea74c31dcf9ccf78ccd484eebebef06019bf8cb579ab4519/importlib_metadata-6.11.0-py3-none-any.whl", hash = "sha256:f0afba6205ad8f8947c7d338b5342d5db2afbfd82f9cbef7879a9539cc12eb9b", size = 23427 }, + { url = "https://files.pythonhosted.org/packages/c0/14/362d31bf1076b21e1bcdcb0dc61944822ff263937b804a79231df2774d28/importlib_metadata-8.4.0-py3-none-any.whl", hash = "sha256:66f342cc6ac9818fc6ff340576acd24d65ba0b3efabb2b4ac08b598965a4a2f1", size = 26269 }, ] [[package]] @@ -1667,11 +1645,12 @@ wheels = [ [[package]] name = "instructor" -version = "1.5.0" +version = "1.6.3" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "aiohttp" }, { name = "docstring-parser" }, + { name = "jinja2" }, { name = "jiter" }, { name = "openai" }, { name = "pydantic" }, @@ -1680,9 +1659,9 @@ dependencies = [ { name = "tenacity" }, { name = "typer" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/de/3a/367957bc8a8e478446896ebc35e294521e23fe287a235ce6057b89bb6c41/instructor-1.5.0.tar.gz", hash = "sha256:cebbd58d417cbacf74981264786d2cf089c3318eab55a5e52459d0de06fc6359", size = 47306 } +sdist = { url = "https://files.pythonhosted.org/packages/b8/e6/21969fe0de9d278979872240b6af17510af8bd5020f6845891719c1d3eef/instructor-1.6.3.tar.gz", hash = "sha256:399cd90e30b5bc7cbd47acd7399c9c4e84926a96c20c8b5d00c5a04b41ed41ab", size = 56708 } wheels = [ - { url = "https://files.pythonhosted.org/packages/60/94/2d6c138fe085234afbc6d59c0708860378ba90bd86196c38c055929c4979/instructor-1.5.0-py3-none-any.whl", hash = "sha256:ed96807ae5d50be13131e9f914a11fdb01e592132e4a8af90d4d0c1de7f59867", size = 58552 }, + { url = "https://files.pythonhosted.org/packages/10/98/c96bf0b1656173d06cd6c3a5adaf3ac429f86d5d696ae8e90e6eb15e89be/instructor-1.6.3-py3-none-any.whl", hash = "sha256:a8f973fea621c0188009b65a3429a526c24aeb249fc24100b605ea496e92d622", size = 69447 }, ] [[package]] @@ -1775,22 +1754,13 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/15/81/296b1e25c43db67848728cdab34ac3eb5c5cbb4955ceb3f51ae60d4a5e3d/jiter-0.5.0-cp312-none-win_amd64.whl", hash = "sha256:a586832f70c3f1481732919215f36d41c59ca080fa27a65cf23d9490e75b2ef5", size = 189720 }, ] -[[package]] -name = "jmespath" -version = "1.0.1" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/00/2a/e867e8531cf3e36b41201936b7fa7ba7b5702dbef42922193f05c8976cd6/jmespath-1.0.1.tar.gz", hash = "sha256:90261b206d6defd58fdd5e85f478bf633a2901798906be2ad389150c5c60edbe", size = 25843 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/31/b4/b9b800c45527aadd64d5b442f9b932b00648617eb5d63d2c7a6587b7cafc/jmespath-1.0.1-py3-none-any.whl", hash = "sha256:02e2e4cc71b5bcab88332eebf907519190dd9e6e82107fa7f83b1003a6252980", size = 20256 }, -] - [[package]] name = "json-repair" -version = "0.29.7" +version = "0.30.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/37/b6/d2faff7fcce71ef23909684203f87325e9e392dd43a95016347afe258190/json_repair-0.29.7.tar.gz", hash = "sha256:d43c3aae2dd743e0ea55a865b8a507b3bd6d5bf54d97701dc56d71b49e45b41a", size = 25751 } +sdist = { url = "https://files.pythonhosted.org/packages/3d/31/42365a1fc9a1c4eab42f50013b7e75390bcb1a1be59d68c9b472822dabc9/json_repair-0.30.0.tar.gz", hash = "sha256:24f12087a0e385ed47207eab1fca12bffd473e48db5bb803793d6c4fd97377ce", size = 26019 } wheels = [ - { url = "https://files.pythonhosted.org/packages/23/26/79570e0a1a025a4d9362ea378de8f021609788670479cff24b860cd684db/json_repair-0.29.7-py3-none-any.whl", hash = "sha256:efbc4d541001bda23012a68902d38f28ce1db4981ccb6f9e7371e264f10196c8", size = 17463 }, + { url = "https://files.pythonhosted.org/packages/23/38/34cb843cee4c5c27aa5c822e90e99bf96feb3dfa705713b5b6e601d17f5c/json_repair-0.30.0-py3-none-any.whl", hash = "sha256:bda4a5552dc12085c6363ff5acfcdb0c9cafc629989a2112081b7e205828228d", size = 17641 }, ] [[package]] @@ -1849,14 +1819,14 @@ wheels = [ [[package]] name = "jsonschema-specifications" -version = "2023.12.1" +version = "2024.10.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "referencing" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/f8/b9/cc0cc592e7c195fb8a650c1d5990b10175cf13b4c97465c72ec841de9e4b/jsonschema_specifications-2023.12.1.tar.gz", hash = "sha256:48a76787b3e70f5ed53f1160d2b81f586e4ca6d1548c5de7085d1682674764cc", size = 13983 } +sdist = { url = "https://files.pythonhosted.org/packages/10/db/58f950c996c793472e336ff3655b13fbcf1e3b359dcf52dcf3ed3b52c352/jsonschema_specifications-2024.10.1.tar.gz", hash = "sha256:0f38b83639958ce1152d02a7f062902c41c8fd20d558b0c34344292d417ae272", size = 15561 } wheels = [ - { url = "https://files.pythonhosted.org/packages/ee/07/44bd408781594c4d0a027666ef27fab1e441b109dc3b76b4f836f8fd04fe/jsonschema_specifications-2023.12.1-py3-none-any.whl", hash = "sha256:87e4fdf3a94858b8a2ba2778d9ba57d8a9cafca7c7489c46ba0d30a8bc6a9c3c", size = 18482 }, + { url = "https://files.pythonhosted.org/packages/d1/0f/8910b19ac0670a0f80ce1008e5e751c4a57e14d2c4c13a482aa6079fa9d6/jsonschema_specifications-2024.10.1-py3-none-any.whl", hash = "sha256:a09a0680616357d9a0ecf05c12ad234479f549239d0f5b55f3deea67475da9bf", size = 18459 }, ] [[package]] @@ -1883,31 +1853,32 @@ wheels = [ [[package]] name = "lancedb" -version = "0.5.7" +version = "0.14.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "attrs" }, { name = "cachetools" }, - { name = "click" }, { name = "deprecation" }, { name = "overrides" }, + { name = "packaging" }, { name = "pydantic" }, { name = "pylance" }, - { name = "pyyaml" }, - { name = "ratelimiter" }, { name = "requests" }, { name = "retry" }, - { name = "semver" }, { name = "tqdm" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/14/1b/f87a2b6420f6f55ea64e5f8f18f231450cc602a0854739bcf946cebc080a/lancedb-0.5.7.tar.gz", hash = "sha256:878914b493f91d09a77b14f1528104741f273234cbdd6671be705f447701fd51", size = 102890 } wheels = [ - { url = "https://files.pythonhosted.org/packages/01/21/ecb191feff512640a59e17fe1737bd9c33970bc857c59a77fa61d5e314d9/lancedb-0.5.7-py3-none-any.whl", hash = "sha256:6169966f715ef530be545950e1aaf9f3f160967e4ba7456cd67c9f30f678095d", size = 115104 }, + { url = "https://files.pythonhosted.org/packages/c8/b0/b0257ef87ccc19ddd29c5827f133c10e155af5944ce8708a2f46488741eb/lancedb-0.14.0-cp38-abi3-macosx_10_15_x86_64.whl", hash = "sha256:6b970e6f503464918789d76c43d70d93d85ef82dc6dbec9685483c60c36ba491", size = 24110162 }, + { url = "https://files.pythonhosted.org/packages/bb/3e/848197dc9eef74ae8015d1a9ed02435740cf467a98423e967dc89cb6315a/lancedb-0.14.0-cp38-abi3-macosx_11_0_arm64.whl", hash = "sha256:e28932882a0f893a295b391b05b0af9d95918e2cd10d6d58991e3282c06c0bd3", size = 22276153 }, + { url = "https://files.pythonhosted.org/packages/9a/07/d56eab12e3a6b5764dc4b3001cf94720eacb946a1f62804034ee1ca7d878/lancedb-0.14.0-cp38-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:faef7fe76af9373656660e2e652e3d330735e84680649f0d74c558a0460f0d55", size = 27532046 }, + { url = "https://files.pythonhosted.org/packages/6c/ed/3eb2934225f125307a5c6ef7f820cff7152a68c029d8878713ba7bdb9ce9/lancedb-0.14.0-cp38-abi3-manylinux_2_24_aarch64.whl", hash = "sha256:777e2d483f13814a2a5624c6824936f400aeab52b961853f1352cc21564f7d6f", size = 26046469 }, + { url = "https://files.pythonhosted.org/packages/26/97/33e7c5f89711a2c62090fc74590827085638dd2f27dc150f4710542990c1/lancedb-0.14.0-cp38-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:31fec6c05edf657542d91c396b895b2ba02f0e6114188ea9bb03a3112907a71e", size = 27004229 }, + { url = "https://files.pythonhosted.org/packages/a9/65/2e4a5cdb897b61497c5da23057f126594a84e6895277429c92db2fb0c287/lancedb-0.14.0-cp38-abi3-win_amd64.whl", hash = "sha256:a4e758156554e2a2a493ad569278d8f938e209f38f215924ed1c5f368d1f402e", size = 24921552 }, ] [[package]] name = "langchain" -version = "0.3.3" +version = "0.3.4" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "aiohttp" }, @@ -1922,9 +1893,9 @@ dependencies = [ { name = "sqlalchemy" }, { name = "tenacity" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/70/b2/258c6a33b5e5f817a57ecd22b1e74756f7246ac66f39d0cf6d2ef515fcb7/langchain-0.3.3.tar.gz", hash = "sha256:6435882996a029a60c61c356bbe51bab4a8f43a54210f5f03e3c4474d19d1842", size = 416891 } +sdist = { url = "https://files.pythonhosted.org/packages/0e/08/c1085fabe1994bc38fe83fc40eca9382afae4464a48d3cb7c2972010e09c/langchain-0.3.4.tar.gz", hash = "sha256:3596515fcd0157dece6ec96e0240d29f4cf542d91ecffc815d32e35198dfff37", size = 416470 } wheels = [ - { url = "https://files.pythonhosted.org/packages/92/82/c17abaa44074ec716409305da4783f633b0eb9b09bb28ed5005220269bdb/langchain-0.3.3-py3-none-any.whl", hash = "sha256:05ac98c674853c2386d043172820e37ceac9b913aaaf1e51217f0fc424112c72", size = 1005176 }, + { url = "https://files.pythonhosted.org/packages/53/b9/290c361a4976947ac6fad11af0a4c11db0b5d8357dc3447d28c1ecd9a1a3/langchain-0.3.4-py3-none-any.whl", hash = "sha256:7a1241d9429510d2083c62df0da998a7b2b05c730cd4255b89da9d47c57f48fd", size = 1005196 }, ] [[package]] @@ -1946,7 +1917,7 @@ wheels = [ [[package]] name = "langchain-community" -version = "0.3.2" +version = "0.3.3" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "aiohttp" }, @@ -1961,9 +1932,9 @@ dependencies = [ { name = "sqlalchemy" }, { name = "tenacity" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/86/6e/119bbbd4d55ab14dc6fc4a82a2466b88f7ddb989bdbdfcf96327c5daba4e/langchain_community-0.3.2.tar.gz", hash = "sha256:469bf5357a08c915cebc4c506dca4617eec737d82a9b6e340df5f3b814dc89bc", size = 1608524 } +sdist = { url = "https://files.pythonhosted.org/packages/14/f6/6cc1e552268f8426dc7830911cbdc8d1f262392be804dd36af598378b59a/langchain_community-0.3.3.tar.gz", hash = "sha256:bfb3f2b219aed21087e0ecb7d2ebd1c81401c02b92239e11645c822d5be63f80", size = 1623395 } wheels = [ - { url = "https://files.pythonhosted.org/packages/cc/57/a8b4826eaa29d3663c957251ab32275a0c178bdb0e262a1204ed820f430c/langchain_community-0.3.2-py3-none-any.whl", hash = "sha256:fffcd484c7674e81ceaa72a809962338bfb17ec8f9e0377ce4e9d884e6fe8ca5", size = 2367818 }, + { url = "https://files.pythonhosted.org/packages/6f/f2/230de86ff6a4de7f6a784f52600bdac3f8d6c18123398e4c073f8d8ceaf8/langchain_community-0.3.3-py3-none-any.whl", hash = "sha256:319cfc2f923a066c91fbb8e02decd7814018af952b6b98298b8ac9d30ea1da56", size = 2383695 }, ] [[package]] @@ -1999,16 +1970,16 @@ wheels = [ [[package]] name = "langchain-openai" -version = "0.2.2" +version = "0.2.3" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "langchain-core" }, { name = "openai" }, { name = "tiktoken" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/55/4c/0a88c51192b0aeef5212019060da7112191750ab7a185195d8b45835578c/langchain_openai-0.2.2.tar.gz", hash = "sha256:9ae8e2ec7d1ca84fd3bfa82186724528d68e1510a1dc9cdf617a7c669b7a7768", size = 42364 } +sdist = { url = "https://files.pythonhosted.org/packages/41/31/82c8a33354dd0a59438973cfdfc771fde0df2c9fb8388e0c23dc36119959/langchain_openai-0.2.3.tar.gz", hash = "sha256:e142031704de1104735f503f76352c53b27ac0a2806466392993c4508c42bf0c", size = 42572 } wheels = [ - { url = "https://files.pythonhosted.org/packages/b0/4e/c62ce98a5412f031f7f03dda5c35b6ed474e0083986261073ca9da5554d5/langchain_openai-0.2.2-py3-none-any.whl", hash = "sha256:3a203228cb38e4711ebd8c0a3bd51854e447f1d017e8475b6467b07ce7dd3e88", size = 49687 }, + { url = "https://files.pythonhosted.org/packages/66/ea/dcc59d9b818a4d7f25d4d6b3018355a0e0243a351b1d4ef8b26ec107ee00/langchain_openai-0.2.3-py3-none-any.whl", hash = "sha256:f498c94817c980cb302439b95d3f3275cdf2743e022ee674692c75898523cf57", size = 49907 }, ] [[package]] @@ -2025,7 +1996,7 @@ wheels = [ [[package]] name = "langsmith" -version = "0.1.130" +version = "0.1.137" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "httpx" }, @@ -2034,14 +2005,14 @@ dependencies = [ { name = "requests" }, { name = "requests-toolbelt" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/00/f8/cabb9c5e1292cf880231ead95e7e454a7b07074bef58fd319785fd86d690/langsmith-0.1.130.tar.gz", hash = "sha256:3e43f87655a86395133e3a745d5968667d4d05dc9a24c617f89224c8cbf54dce", size = 286038 } +sdist = { url = "https://files.pythonhosted.org/packages/95/b0/b6c112e5080765ad31272b92f16478d2d38c54727e00cc8bbc9a66bbaa44/langsmith-0.1.137.tar.gz", hash = "sha256:56cdfcc6c74cb20a3f437d5bd144feb5bf93f54c5a2918d1e568cbd084a372d4", size = 287888 } wheels = [ - { url = "https://files.pythonhosted.org/packages/bf/b0/4c21096c582639a3c949e8e603aa3f53d07104cccdd500153ac3e7135701/langsmith-0.1.130-py3-none-any.whl", hash = "sha256:acf27d77e699d84b03045f3f226e78be1dffb3e756aa1a085f9993a45380e8b2", size = 294650 }, + { url = "https://files.pythonhosted.org/packages/71/fd/7713b0e737f4e171112e44134790823ccec4aabe31f07d6e836fcbeb3b8a/langsmith-0.1.137-py3-none-any.whl", hash = "sha256:4256d5c61133749890f7b5c88321dbb133ce0f440c621ea28e76513285859b81", size = 296895 }, ] [[package]] name = "litellm" -version = "1.48.10" +version = "1.50.2" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "aiohttp" }, @@ -2056,21 +2027,21 @@ dependencies = [ { name = "tiktoken" }, { name = "tokenizers" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/8b/d7/ed47df9f2de3c1995516e309c20e750d40a0cd5f6639c11858cc3d7d0c4f/litellm-1.48.10.tar.gz", hash = "sha256:0a4ff75da78e66baeae0658ad8de498298310a5efda74c3d840ce2b013e8401d", size = 6037860 } +sdist = { url = "https://files.pythonhosted.org/packages/a7/45/4d54617b267a96f1f7c17c0010ea1aba20e30a3672b873fe92a6001e5952/litellm-1.50.2.tar.gz", hash = "sha256:b244c9a0e069cc626b85fb9f5cc252114aaff1225500da30ce0940f841aef8ea", size = 6096949 } wheels = [ - { url = "https://files.pythonhosted.org/packages/fb/5b/b6eb2098ed289f99abb55ab966b4f318a467294c218ad846e96ba72949b0/litellm-1.48.10-py3-none-any.whl", hash = "sha256:752efd59747a0895f4695d025c66f0b2258d80a61175f7cfa41dbe4894ef95e1", size = 6238318 }, + { url = "https://files.pythonhosted.org/packages/22/f3/89a4d65d1b9286eb5ac6a6e92dd93523d92f3142a832e60c00d5cad64176/litellm-1.50.2-py3-none-any.whl", hash = "sha256:99cac60c78037946ab809b7cfbbadad53507bb2db8ae39391b4be215a0869fdd", size = 6318265 }, ] [[package]] name = "mako" -version = "1.3.5" +version = "1.3.6" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "markupsafe" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/67/03/fb5ba97ff65ce64f6d35b582aacffc26b693a98053fa831ab43a437cbddb/Mako-1.3.5.tar.gz", hash = "sha256:48dbc20568c1d276a2698b36d968fa76161bf127194907ea6fc594fa81f943bc", size = 392738 } +sdist = { url = "https://files.pythonhosted.org/packages/fa/0b/29bc5a230948bf209d3ed3165006d257e547c02c3c2a96f6286320dfe8dc/mako-1.3.6.tar.gz", hash = "sha256:9ec3a1583713479fae654f83ed9fa8c9a4c16b7bb0daba0e6bbebff50c0d983d", size = 390206 } wheels = [ - { url = "https://files.pythonhosted.org/packages/03/62/70f5a0c2dd208f9f3f2f9afd103aec42ee4d9ad2401d78342f75e9b8da36/Mako-1.3.5-py3-none-any.whl", hash = "sha256:260f1dbc3a519453a9c856dedfe4beb4e50bd5a26d96386cb6c80856556bb91a", size = 78565 }, + { url = "https://files.pythonhosted.org/packages/48/22/bc14c6f02e6dccaafb3eba95764c8f096714260c2aa5f76f654fd16a23dd/Mako-1.3.6-py3-none-any.whl", hash = "sha256:a91198468092a2f1a0de86ca92690fb0cfc43ca90ee17e15d93662b4c04b241a", size = 78557 }, ] [[package]] @@ -2096,52 +2067,72 @@ wheels = [ [[package]] name = "markupsafe" -version = "2.1.5" +version = "3.0.2" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/87/5b/aae44c6655f3801e81aa3eef09dbbf012431987ba564d7231722f68df02d/MarkupSafe-2.1.5.tar.gz", hash = "sha256:d283d37a890ba4c1ae73ffadf8046435c76e7bc2247bbb63c00bd1a709c6544b", size = 19384 } +sdist = { url = "https://files.pythonhosted.org/packages/b2/97/5d42485e71dfc078108a86d6de8fa46db44a1a9295e89c5d6d4a06e23a62/markupsafe-3.0.2.tar.gz", hash = "sha256:ee55d3edf80167e48ea11a923c7386f4669df67d7994554387f84e7d8b0a2bf0", size = 20537 } wheels = [ - { url = "https://files.pythonhosted.org/packages/e4/54/ad5eb37bf9d51800010a74e4665425831a9db4e7c4e0fde4352e391e808e/MarkupSafe-2.1.5-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:a17a92de5231666cfbe003f0e4b9b3a7ae3afb1ec2845aadc2bacc93ff85febc", size = 18206 }, - { url = "https://files.pythonhosted.org/packages/6a/4a/a4d49415e600bacae038c67f9fecc1d5433b9d3c71a4de6f33537b89654c/MarkupSafe-2.1.5-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:72b6be590cc35924b02c78ef34b467da4ba07e4e0f0454a2c5907f473fc50ce5", size = 14079 }, - { url = "https://files.pythonhosted.org/packages/0a/7b/85681ae3c33c385b10ac0f8dd025c30af83c78cec1c37a6aa3b55e67f5ec/MarkupSafe-2.1.5-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e61659ba32cf2cf1481e575d0462554625196a1f2fc06a1c777d3f48e8865d46", size = 26620 }, - { url = "https://files.pythonhosted.org/packages/7c/52/2b1b570f6b8b803cef5ac28fdf78c0da318916c7d2fe9402a84d591b394c/MarkupSafe-2.1.5-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2174c595a0d73a3080ca3257b40096db99799265e1c27cc5a610743acd86d62f", size = 25818 }, - { url = "https://files.pythonhosted.org/packages/29/fe/a36ba8c7ca55621620b2d7c585313efd10729e63ef81e4e61f52330da781/MarkupSafe-2.1.5-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ae2ad8ae6ebee9d2d94b17fb62763125f3f374c25618198f40cbb8b525411900", size = 25493 }, - { url = "https://files.pythonhosted.org/packages/60/ae/9c60231cdfda003434e8bd27282b1f4e197ad5a710c14bee8bea8a9ca4f0/MarkupSafe-2.1.5-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:075202fa5b72c86ad32dc7d0b56024ebdbcf2048c0ba09f1cde31bfdd57bcfff", size = 30630 }, - { url = "https://files.pythonhosted.org/packages/65/dc/1510be4d179869f5dafe071aecb3f1f41b45d37c02329dfba01ff59e5ac5/MarkupSafe-2.1.5-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:598e3276b64aff0e7b3451b72e94fa3c238d452e7ddcd893c3ab324717456bad", size = 29745 }, - { url = "https://files.pythonhosted.org/packages/30/39/8d845dd7d0b0613d86e0ef89549bfb5f61ed781f59af45fc96496e897f3a/MarkupSafe-2.1.5-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:fce659a462a1be54d2ffcacea5e3ba2d74daa74f30f5f143fe0c58636e355fdd", size = 30021 }, - { url = "https://files.pythonhosted.org/packages/c7/5c/356a6f62e4f3c5fbf2602b4771376af22a3b16efa74eb8716fb4e328e01e/MarkupSafe-2.1.5-cp310-cp310-win32.whl", hash = "sha256:d9fad5155d72433c921b782e58892377c44bd6252b5af2f67f16b194987338a4", size = 16659 }, - { url = "https://files.pythonhosted.org/packages/69/48/acbf292615c65f0604a0c6fc402ce6d8c991276e16c80c46a8f758fbd30c/MarkupSafe-2.1.5-cp310-cp310-win_amd64.whl", hash = "sha256:bf50cd79a75d181c9181df03572cdce0fbb75cc353bc350712073108cba98de5", size = 17213 }, - { url = "https://files.pythonhosted.org/packages/11/e7/291e55127bb2ae67c64d66cef01432b5933859dfb7d6949daa721b89d0b3/MarkupSafe-2.1.5-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:629ddd2ca402ae6dbedfceeba9c46d5f7b2a61d9749597d4307f943ef198fc1f", size = 18219 }, - { url = "https://files.pythonhosted.org/packages/6b/cb/aed7a284c00dfa7c0682d14df85ad4955a350a21d2e3b06d8240497359bf/MarkupSafe-2.1.5-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:5b7b716f97b52c5a14bffdf688f971b2d5ef4029127f1ad7a513973cfd818df2", size = 14098 }, - { url = "https://files.pythonhosted.org/packages/1c/cf/35fe557e53709e93feb65575c93927942087e9b97213eabc3fe9d5b25a55/MarkupSafe-2.1.5-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6ec585f69cec0aa07d945b20805be741395e28ac1627333b1c5b0105962ffced", size = 29014 }, - { url = "https://files.pythonhosted.org/packages/97/18/c30da5e7a0e7f4603abfc6780574131221d9148f323752c2755d48abad30/MarkupSafe-2.1.5-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b91c037585eba9095565a3556f611e3cbfaa42ca1e865f7b8015fe5c7336d5a5", size = 28220 }, - { url = "https://files.pythonhosted.org/packages/0c/40/2e73e7d532d030b1e41180807a80d564eda53babaf04d65e15c1cf897e40/MarkupSafe-2.1.5-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7502934a33b54030eaf1194c21c692a534196063db72176b0c4028e140f8f32c", size = 27756 }, - { url = "https://files.pythonhosted.org/packages/18/46/5dca760547e8c59c5311b332f70605d24c99d1303dd9a6e1fc3ed0d73561/MarkupSafe-2.1.5-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:0e397ac966fdf721b2c528cf028494e86172b4feba51d65f81ffd65c63798f3f", size = 33988 }, - { url = "https://files.pythonhosted.org/packages/6d/c5/27febe918ac36397919cd4a67d5579cbbfa8da027fa1238af6285bb368ea/MarkupSafe-2.1.5-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:c061bb86a71b42465156a3ee7bd58c8c2ceacdbeb95d05a99893e08b8467359a", size = 32718 }, - { url = "https://files.pythonhosted.org/packages/f8/81/56e567126a2c2bc2684d6391332e357589a96a76cb9f8e5052d85cb0ead8/MarkupSafe-2.1.5-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:3a57fdd7ce31c7ff06cdfbf31dafa96cc533c21e443d57f5b1ecc6cdc668ec7f", size = 33317 }, - { url = "https://files.pythonhosted.org/packages/00/0b/23f4b2470accb53285c613a3ab9ec19dc944eaf53592cb6d9e2af8aa24cc/MarkupSafe-2.1.5-cp311-cp311-win32.whl", hash = "sha256:397081c1a0bfb5124355710fe79478cdbeb39626492b15d399526ae53422b906", size = 16670 }, - { url = "https://files.pythonhosted.org/packages/b7/a2/c78a06a9ec6d04b3445a949615c4c7ed86a0b2eb68e44e7541b9d57067cc/MarkupSafe-2.1.5-cp311-cp311-win_amd64.whl", hash = "sha256:2b7c57a4dfc4f16f7142221afe5ba4e093e09e728ca65c51f5620c9aaeb9a617", size = 17224 }, - { url = "https://files.pythonhosted.org/packages/53/bd/583bf3e4c8d6a321938c13f49d44024dbe5ed63e0a7ba127e454a66da974/MarkupSafe-2.1.5-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:8dec4936e9c3100156f8a2dc89c4b88d5c435175ff03413b443469c7c8c5f4d1", size = 18215 }, - { url = "https://files.pythonhosted.org/packages/48/d6/e7cd795fc710292c3af3a06d80868ce4b02bfbbf370b7cee11d282815a2a/MarkupSafe-2.1.5-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:3c6b973f22eb18a789b1460b4b91bf04ae3f0c4234a0a6aa6b0a92f6f7b951d4", size = 14069 }, - { url = "https://files.pythonhosted.org/packages/51/b5/5d8ec796e2a08fc814a2c7d2584b55f889a55cf17dd1a90f2beb70744e5c/MarkupSafe-2.1.5-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ac07bad82163452a6884fe8fa0963fb98c2346ba78d779ec06bd7a6262132aee", size = 29452 }, - { url = "https://files.pythonhosted.org/packages/0a/0d/2454f072fae3b5a137c119abf15465d1771319dfe9e4acbb31722a0fff91/MarkupSafe-2.1.5-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f5dfb42c4604dddc8e4305050aa6deb084540643ed5804d7455b5df8fe16f5e5", size = 28462 }, - { url = "https://files.pythonhosted.org/packages/2d/75/fd6cb2e68780f72d47e6671840ca517bda5ef663d30ada7616b0462ad1e3/MarkupSafe-2.1.5-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ea3d8a3d18833cf4304cd2fc9cbb1efe188ca9b5efef2bdac7adc20594a0e46b", size = 27869 }, - { url = "https://files.pythonhosted.org/packages/b0/81/147c477391c2750e8fc7705829f7351cf1cd3be64406edcf900dc633feb2/MarkupSafe-2.1.5-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:d050b3361367a06d752db6ead6e7edeb0009be66bc3bae0ee9d97fb326badc2a", size = 33906 }, - { url = "https://files.pythonhosted.org/packages/8b/ff/9a52b71839d7a256b563e85d11050e307121000dcebc97df120176b3ad93/MarkupSafe-2.1.5-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:bec0a414d016ac1a18862a519e54b2fd0fc8bbfd6890376898a6c0891dd82e9f", size = 32296 }, - { url = "https://files.pythonhosted.org/packages/88/07/2dc76aa51b481eb96a4c3198894f38b480490e834479611a4053fbf08623/MarkupSafe-2.1.5-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:58c98fee265677f63a4385256a6d7683ab1832f3ddd1e66fe948d5880c21a169", size = 33038 }, - { url = "https://files.pythonhosted.org/packages/96/0c/620c1fb3661858c0e37eb3cbffd8c6f732a67cd97296f725789679801b31/MarkupSafe-2.1.5-cp312-cp312-win32.whl", hash = "sha256:8590b4ae07a35970728874632fed7bd57b26b0102df2d2b233b6d9d82f6c62ad", size = 16572 }, - { url = "https://files.pythonhosted.org/packages/3f/14/c3554d512d5f9100a95e737502f4a2323a1959f6d0d01e0d0997b35f7b10/MarkupSafe-2.1.5-cp312-cp312-win_amd64.whl", hash = "sha256:823b65d8706e32ad2df51ed89496147a42a2a6e01c13cfb6ffb8b1e92bc910bb", size = 17127 }, + { url = "https://files.pythonhosted.org/packages/04/90/d08277ce111dd22f77149fd1a5d4653eeb3b3eaacbdfcbae5afb2600eebd/MarkupSafe-3.0.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:7e94c425039cde14257288fd61dcfb01963e658efbc0ff54f5306b06054700f8", size = 14357 }, + { url = "https://files.pythonhosted.org/packages/04/e1/6e2194baeae0bca1fae6629dc0cbbb968d4d941469cbab11a3872edff374/MarkupSafe-3.0.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:9e2d922824181480953426608b81967de705c3cef4d1af983af849d7bd619158", size = 12393 }, + { url = "https://files.pythonhosted.org/packages/1d/69/35fa85a8ece0a437493dc61ce0bb6d459dcba482c34197e3efc829aa357f/MarkupSafe-3.0.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:38a9ef736c01fccdd6600705b09dc574584b89bea478200c5fbf112a6b0d5579", size = 21732 }, + { url = "https://files.pythonhosted.org/packages/22/35/137da042dfb4720b638d2937c38a9c2df83fe32d20e8c8f3185dbfef05f7/MarkupSafe-3.0.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bbcb445fa71794da8f178f0f6d66789a28d7319071af7a496d4d507ed566270d", size = 20866 }, + { url = "https://files.pythonhosted.org/packages/29/28/6d029a903727a1b62edb51863232152fd335d602def598dade38996887f0/MarkupSafe-3.0.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:57cb5a3cf367aeb1d316576250f65edec5bb3be939e9247ae594b4bcbc317dfb", size = 20964 }, + { url = "https://files.pythonhosted.org/packages/cc/cd/07438f95f83e8bc028279909d9c9bd39e24149b0d60053a97b2bc4f8aa51/MarkupSafe-3.0.2-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:3809ede931876f5b2ec92eef964286840ed3540dadf803dd570c3b7e13141a3b", size = 21977 }, + { url = "https://files.pythonhosted.org/packages/29/01/84b57395b4cc062f9c4c55ce0df7d3108ca32397299d9df00fedd9117d3d/MarkupSafe-3.0.2-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:e07c3764494e3776c602c1e78e298937c3315ccc9043ead7e685b7f2b8d47b3c", size = 21366 }, + { url = "https://files.pythonhosted.org/packages/bd/6e/61ebf08d8940553afff20d1fb1ba7294b6f8d279df9fd0c0db911b4bbcfd/MarkupSafe-3.0.2-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:b424c77b206d63d500bcb69fa55ed8d0e6a3774056bdc4839fc9298a7edca171", size = 21091 }, + { url = "https://files.pythonhosted.org/packages/11/23/ffbf53694e8c94ebd1e7e491de185124277964344733c45481f32ede2499/MarkupSafe-3.0.2-cp310-cp310-win32.whl", hash = "sha256:fcabf5ff6eea076f859677f5f0b6b5c1a51e70a376b0579e0eadef8db48c6b50", size = 15065 }, + { url = "https://files.pythonhosted.org/packages/44/06/e7175d06dd6e9172d4a69a72592cb3f7a996a9c396eee29082826449bbc3/MarkupSafe-3.0.2-cp310-cp310-win_amd64.whl", hash = "sha256:6af100e168aa82a50e186c82875a5893c5597a0c1ccdb0d8b40240b1f28b969a", size = 15514 }, + { url = "https://files.pythonhosted.org/packages/6b/28/bbf83e3f76936960b850435576dd5e67034e200469571be53f69174a2dfd/MarkupSafe-3.0.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:9025b4018f3a1314059769c7bf15441064b2207cb3f065e6ea1e7359cb46db9d", size = 14353 }, + { url = "https://files.pythonhosted.org/packages/6c/30/316d194b093cde57d448a4c3209f22e3046c5bb2fb0820b118292b334be7/MarkupSafe-3.0.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:93335ca3812df2f366e80509ae119189886b0f3c2b81325d39efdb84a1e2ae93", size = 12392 }, + { url = "https://files.pythonhosted.org/packages/f2/96/9cdafba8445d3a53cae530aaf83c38ec64c4d5427d975c974084af5bc5d2/MarkupSafe-3.0.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2cb8438c3cbb25e220c2ab33bb226559e7afb3baec11c4f218ffa7308603c832", size = 23984 }, + { url = "https://files.pythonhosted.org/packages/f1/a4/aefb044a2cd8d7334c8a47d3fb2c9f328ac48cb349468cc31c20b539305f/MarkupSafe-3.0.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a123e330ef0853c6e822384873bef7507557d8e4a082961e1defa947aa59ba84", size = 23120 }, + { url = "https://files.pythonhosted.org/packages/8d/21/5e4851379f88f3fad1de30361db501300d4f07bcad047d3cb0449fc51f8c/MarkupSafe-3.0.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1e084f686b92e5b83186b07e8a17fc09e38fff551f3602b249881fec658d3eca", size = 23032 }, + { url = "https://files.pythonhosted.org/packages/00/7b/e92c64e079b2d0d7ddf69899c98842f3f9a60a1ae72657c89ce2655c999d/MarkupSafe-3.0.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:d8213e09c917a951de9d09ecee036d5c7d36cb6cb7dbaece4c71a60d79fb9798", size = 24057 }, + { url = "https://files.pythonhosted.org/packages/f9/ac/46f960ca323037caa0a10662ef97d0a4728e890334fc156b9f9e52bcc4ca/MarkupSafe-3.0.2-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:5b02fb34468b6aaa40dfc198d813a641e3a63b98c2b05a16b9f80b7ec314185e", size = 23359 }, + { url = "https://files.pythonhosted.org/packages/69/84/83439e16197337b8b14b6a5b9c2105fff81d42c2a7c5b58ac7b62ee2c3b1/MarkupSafe-3.0.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:0bff5e0ae4ef2e1ae4fdf2dfd5b76c75e5c2fa4132d05fc1b0dabcd20c7e28c4", size = 23306 }, + { url = "https://files.pythonhosted.org/packages/9a/34/a15aa69f01e2181ed8d2b685c0d2f6655d5cca2c4db0ddea775e631918cd/MarkupSafe-3.0.2-cp311-cp311-win32.whl", hash = "sha256:6c89876f41da747c8d3677a2b540fb32ef5715f97b66eeb0c6b66f5e3ef6f59d", size = 15094 }, + { url = "https://files.pythonhosted.org/packages/da/b8/3a3bd761922d416f3dc5d00bfbed11f66b1ab89a0c2b6e887240a30b0f6b/MarkupSafe-3.0.2-cp311-cp311-win_amd64.whl", hash = "sha256:70a87b411535ccad5ef2f1df5136506a10775d267e197e4cf531ced10537bd6b", size = 15521 }, + { url = "https://files.pythonhosted.org/packages/22/09/d1f21434c97fc42f09d290cbb6350d44eb12f09cc62c9476effdb33a18aa/MarkupSafe-3.0.2-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:9778bd8ab0a994ebf6f84c2b949e65736d5575320a17ae8984a77fab08db94cf", size = 14274 }, + { url = "https://files.pythonhosted.org/packages/6b/b0/18f76bba336fa5aecf79d45dcd6c806c280ec44538b3c13671d49099fdd0/MarkupSafe-3.0.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:846ade7b71e3536c4e56b386c2a47adf5741d2d8b94ec9dc3e92e5e1ee1e2225", size = 12348 }, + { url = "https://files.pythonhosted.org/packages/e0/25/dd5c0f6ac1311e9b40f4af06c78efde0f3b5cbf02502f8ef9501294c425b/MarkupSafe-3.0.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1c99d261bd2d5f6b59325c92c73df481e05e57f19837bdca8413b9eac4bd8028", size = 24149 }, + { url = "https://files.pythonhosted.org/packages/f3/f0/89e7aadfb3749d0f52234a0c8c7867877876e0a20b60e2188e9850794c17/MarkupSafe-3.0.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e17c96c14e19278594aa4841ec148115f9c7615a47382ecb6b82bd8fea3ab0c8", size = 23118 }, + { url = "https://files.pythonhosted.org/packages/d5/da/f2eeb64c723f5e3777bc081da884b414671982008c47dcc1873d81f625b6/MarkupSafe-3.0.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:88416bd1e65dcea10bc7569faacb2c20ce071dd1f87539ca2ab364bf6231393c", size = 22993 }, + { url = "https://files.pythonhosted.org/packages/da/0e/1f32af846df486dce7c227fe0f2398dc7e2e51d4a370508281f3c1c5cddc/MarkupSafe-3.0.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:2181e67807fc2fa785d0592dc2d6206c019b9502410671cc905d132a92866557", size = 24178 }, + { url = "https://files.pythonhosted.org/packages/c4/f6/bb3ca0532de8086cbff5f06d137064c8410d10779c4c127e0e47d17c0b71/MarkupSafe-3.0.2-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:52305740fe773d09cffb16f8ed0427942901f00adedac82ec8b67752f58a1b22", size = 23319 }, + { url = "https://files.pythonhosted.org/packages/a2/82/8be4c96ffee03c5b4a034e60a31294daf481e12c7c43ab8e34a1453ee48b/MarkupSafe-3.0.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:ad10d3ded218f1039f11a75f8091880239651b52e9bb592ca27de44eed242a48", size = 23352 }, + { url = "https://files.pythonhosted.org/packages/51/ae/97827349d3fcffee7e184bdf7f41cd6b88d9919c80f0263ba7acd1bbcb18/MarkupSafe-3.0.2-cp312-cp312-win32.whl", hash = "sha256:0f4ca02bea9a23221c0182836703cbf8930c5e9454bacce27e767509fa286a30", size = 15097 }, + { url = "https://files.pythonhosted.org/packages/c1/80/a61f99dc3a936413c3ee4e1eecac96c0da5ed07ad56fd975f1a9da5bc630/MarkupSafe-3.0.2-cp312-cp312-win_amd64.whl", hash = "sha256:8e06879fc22a25ca47312fbe7c8264eb0b662f6db27cb2d3bbbc74b1df4b9b87", size = 15601 }, + { url = "https://files.pythonhosted.org/packages/83/0e/67eb10a7ecc77a0c2bbe2b0235765b98d164d81600746914bebada795e97/MarkupSafe-3.0.2-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:ba9527cdd4c926ed0760bc301f6728ef34d841f405abf9d4f959c478421e4efd", size = 14274 }, + { url = "https://files.pythonhosted.org/packages/2b/6d/9409f3684d3335375d04e5f05744dfe7e9f120062c9857df4ab490a1031a/MarkupSafe-3.0.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:f8b3d067f2e40fe93e1ccdd6b2e1d16c43140e76f02fb1319a05cf2b79d99430", size = 12352 }, + { url = "https://files.pythonhosted.org/packages/d2/f5/6eadfcd3885ea85fe2a7c128315cc1bb7241e1987443d78c8fe712d03091/MarkupSafe-3.0.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:569511d3b58c8791ab4c2e1285575265991e6d8f8700c7be0e88f86cb0672094", size = 24122 }, + { url = "https://files.pythonhosted.org/packages/0c/91/96cf928db8236f1bfab6ce15ad070dfdd02ed88261c2afafd4b43575e9e9/MarkupSafe-3.0.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:15ab75ef81add55874e7ab7055e9c397312385bd9ced94920f2802310c930396", size = 23085 }, + { url = "https://files.pythonhosted.org/packages/c2/cf/c9d56af24d56ea04daae7ac0940232d31d5a8354f2b457c6d856b2057d69/MarkupSafe-3.0.2-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f3818cb119498c0678015754eba762e0d61e5b52d34c8b13d770f0719f7b1d79", size = 22978 }, + { url = "https://files.pythonhosted.org/packages/2a/9f/8619835cd6a711d6272d62abb78c033bda638fdc54c4e7f4272cf1c0962b/MarkupSafe-3.0.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:cdb82a876c47801bb54a690c5ae105a46b392ac6099881cdfb9f6e95e4014c6a", size = 24208 }, + { url = "https://files.pythonhosted.org/packages/f9/bf/176950a1792b2cd2102b8ffeb5133e1ed984547b75db47c25a67d3359f77/MarkupSafe-3.0.2-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:cabc348d87e913db6ab4aa100f01b08f481097838bdddf7c7a84b7575b7309ca", size = 23357 }, + { url = "https://files.pythonhosted.org/packages/ce/4f/9a02c1d335caabe5c4efb90e1b6e8ee944aa245c1aaaab8e8a618987d816/MarkupSafe-3.0.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:444dcda765c8a838eaae23112db52f1efaf750daddb2d9ca300bcae1039adc5c", size = 23344 }, + { url = "https://files.pythonhosted.org/packages/ee/55/c271b57db36f748f0e04a759ace9f8f759ccf22b4960c270c78a394f58be/MarkupSafe-3.0.2-cp313-cp313-win32.whl", hash = "sha256:bcf3e58998965654fdaff38e58584d8937aa3096ab5354d493c77d1fdd66d7a1", size = 15101 }, + { url = "https://files.pythonhosted.org/packages/29/88/07df22d2dd4df40aba9f3e402e6dc1b8ee86297dddbad4872bd5e7b0094f/MarkupSafe-3.0.2-cp313-cp313-win_amd64.whl", hash = "sha256:e6a2a455bd412959b57a172ce6328d2dd1f01cb2135efda2e4576e8a23fa3b0f", size = 15603 }, + { url = "https://files.pythonhosted.org/packages/62/6a/8b89d24db2d32d433dffcd6a8779159da109842434f1dd2f6e71f32f738c/MarkupSafe-3.0.2-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:b5a6b3ada725cea8a5e634536b1b01c30bcdcd7f9c6fff4151548d5bf6b3a36c", size = 14510 }, + { url = "https://files.pythonhosted.org/packages/7a/06/a10f955f70a2e5a9bf78d11a161029d278eeacbd35ef806c3fd17b13060d/MarkupSafe-3.0.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:a904af0a6162c73e3edcb969eeeb53a63ceeb5d8cf642fade7d39e7963a22ddb", size = 12486 }, + { url = "https://files.pythonhosted.org/packages/34/cf/65d4a571869a1a9078198ca28f39fba5fbb910f952f9dbc5220afff9f5e6/MarkupSafe-3.0.2-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4aa4e5faecf353ed117801a068ebab7b7e09ffb6e1d5e412dc852e0da018126c", size = 25480 }, + { url = "https://files.pythonhosted.org/packages/0c/e3/90e9651924c430b885468b56b3d597cabf6d72be4b24a0acd1fa0e12af67/MarkupSafe-3.0.2-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c0ef13eaeee5b615fb07c9a7dadb38eac06a0608b41570d8ade51c56539e509d", size = 23914 }, + { url = "https://files.pythonhosted.org/packages/66/8c/6c7cf61f95d63bb866db39085150df1f2a5bd3335298f14a66b48e92659c/MarkupSafe-3.0.2-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d16a81a06776313e817c951135cf7340a3e91e8c1ff2fac444cfd75fffa04afe", size = 23796 }, + { url = "https://files.pythonhosted.org/packages/bb/35/cbe9238ec3f47ac9a7c8b3df7a808e7cb50fe149dc7039f5f454b3fba218/MarkupSafe-3.0.2-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:6381026f158fdb7c72a168278597a5e3a5222e83ea18f543112b2662a9b699c5", size = 25473 }, + { url = "https://files.pythonhosted.org/packages/e6/32/7621a4382488aa283cc05e8984a9c219abad3bca087be9ec77e89939ded9/MarkupSafe-3.0.2-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:3d79d162e7be8f996986c064d1c7c817f6df3a77fe3d6859f6f9e7be4b8c213a", size = 24114 }, + { url = "https://files.pythonhosted.org/packages/0d/80/0985960e4b89922cb5a0bac0ed39c5b96cbc1a536a99f30e8c220a996ed9/MarkupSafe-3.0.2-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:131a3c7689c85f5ad20f9f6fb1b866f402c445b220c19fe4308c0b147ccd2ad9", size = 24098 }, + { url = "https://files.pythonhosted.org/packages/82/78/fedb03c7d5380df2427038ec8d973587e90561b2d90cd472ce9254cf348b/MarkupSafe-3.0.2-cp313-cp313t-win32.whl", hash = "sha256:ba8062ed2cf21c07a9e295d5b8a2a5ce678b913b45fdf68c32d95d6c1291e0b6", size = 15208 }, + { url = "https://files.pythonhosted.org/packages/4f/65/6079a46068dfceaeabb5dcad6d674f5f5c61a6fa5673746f42a9f4c233b3/MarkupSafe-3.0.2-cp313-cp313t-win_amd64.whl", hash = "sha256:e444a31f8db13eb18ada366ab3cf45fd4b31e4db1236a4448f68778c1d1a5a2f", size = 15739 }, ] [[package]] name = "marshmallow" -version = "3.22.0" +version = "3.23.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "packaging" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/70/40/faa10dc4500bca85f41ca9d8cefab282dd23d0fcc7a9b5fab40691e72e76/marshmallow-3.22.0.tar.gz", hash = "sha256:4972f529104a220bb8637d595aa4c9762afbe7f7a77d82dc58c1615d70c5823e", size = 176836 } +sdist = { url = "https://files.pythonhosted.org/packages/b7/41/05580fed5798ba8032341e7e330b866adc88dfca3bc3ec86c04e4ffdc427/marshmallow-3.23.0.tar.gz", hash = "sha256:98d8827a9f10c03d44ead298d2e99c6aea8197df18ccfad360dae7f89a50da2e", size = 177439 } wheels = [ - { url = "https://files.pythonhosted.org/packages/3c/78/c1de55eb3311f2c200a8b91724414b8d6f5ae78891c15d9d936ea43c3dba/marshmallow-3.22.0-py3-none-any.whl", hash = "sha256:71a2dce49ef901c3f97ed296ae5051135fd3febd2bf43afe0ae9a82143a494d9", size = 49334 }, + { url = "https://files.pythonhosted.org/packages/9a/9e/f8f0308b66ff5fcc3b351ffa5fcba19ae725dfeda75d3c673f4427f3fc99/marshmallow-3.23.0-py3-none-any.whl", hash = "sha256:82f20a2397834fe6d9611b241f2f7e7b680ed89c49f84728a1ad937be6b4bdf4", size = 49490 }, ] [[package]] @@ -2167,22 +2158,19 @@ wheels = [ [[package]] name = "mem0ai" -version = "0.1.19" +version = "0.1.22" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "langchain-community" }, - { name = "neo4j" }, { name = "openai" }, { name = "posthog" }, { name = "pydantic" }, { name = "pytz" }, { name = "qdrant-client" }, - { name = "rank-bm25" }, { name = "sqlalchemy" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/6e/12/23f8f250a2ce798a51841417acbbfc9c12c294d3ae427e81a0a0dbab54f6/mem0ai-0.1.19.tar.gz", hash = "sha256:faf7c198a85df2f502ac41fe2bc1593ca0383f993b431a4e4a36e0aed3fa533c", size = 51167 } +sdist = { url = "https://files.pythonhosted.org/packages/7f/b4/64c6f7d9684bd1f9b46d251abfc7d5b2cc8371d70f1f9eec097f9872c719/mem0ai-0.1.22.tar.gz", hash = "sha256:d01aa028763719bd0ede2de4602121a7c3bf023f46112cd50cc9169140e11be2", size = 53117 } wheels = [ - { url = "https://files.pythonhosted.org/packages/7e/43/04d22bc9cac6fa19b10a405c59c21e94b8ae2a180b40307ec4a577f6ee39/mem0ai-0.1.19-py3-none-any.whl", hash = "sha256:dfff9cfe191072abd34ed8bb4fcbee2819603eed430d89611ef3181b1a46fff9", size = 73240 }, + { url = "https://files.pythonhosted.org/packages/2b/27/3ef75abb28bf8b46c2cc34730f6be733ef2584652474216215019ee036a2/mem0ai-0.1.22-py3-none-any.whl", hash = "sha256:c783e15131c16a0d91e44e30195c1eeae9c36468de40006d5e42cf4516059855", size = 75695 }, ] [[package]] @@ -2248,7 +2236,7 @@ wheels = [ [[package]] name = "mkdocs-material" -version = "9.5.39" +version = "9.5.42" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "babel" }, @@ -2263,9 +2251,9 @@ dependencies = [ { name = "regex" }, { name = "requests" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/be/f3/87688912b3ac75d5934830bef86762310a7df881ea9c9f50f4e4f5f49754/mkdocs_material-9.5.39.tar.gz", hash = "sha256:25faa06142afa38549d2b781d475a86fb61de93189f532b88e69bf11e5e5c3be", size = 4001520 } +sdist = { url = "https://files.pythonhosted.org/packages/f9/33/b3343ed975fbbd6798b8d8a7c4f1bf8489cc321fc8fd426eba3d87b0242e/mkdocs_material-9.5.42.tar.gz", hash = "sha256:92779b5e9b5934540c574c11647131d217dc540dce72b05feeda088c8eb1b8f2", size = 3963891 } wheels = [ - { url = "https://files.pythonhosted.org/packages/5b/12/419d1e0e6a1a8757bc6c371a895789960000fc56a40b22752a824068dc50/mkdocs_material-9.5.39-py3-none-any.whl", hash = "sha256:0f2f68c8db89523cb4a59705cd01b4acd62b2f71218ccb67e1e004e560410d2b", size = 8708939 }, + { url = "https://files.pythonhosted.org/packages/55/55/ad3e6a60ac1e8e76025543c49c1f24ecd80fb38e8a57000403bf2f0a4293/mkdocs_material-9.5.42-py3-none-any.whl", hash = "sha256:452a7c5d21284b373f36b981a2cbebfff59263feebeede1bc28652e9c5bbe316", size = 8672619 }, ] [[package]] @@ -2279,7 +2267,7 @@ wheels = [ [[package]] name = "mkdocstrings" -version = "0.26.1" +version = "0.26.2" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "click" }, @@ -2291,23 +2279,23 @@ dependencies = [ { name = "platformdirs" }, { name = "pymdown-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/e6/bf/170ff04de72227f715d67da32950c7b8434449f3805b2ec3dd1085db4d7c/mkdocstrings-0.26.1.tar.gz", hash = "sha256:bb8b8854d6713d5348ad05b069a09f3b79edbc6a0f33a34c6821141adb03fe33", size = 92677 } +sdist = { url = "https://files.pythonhosted.org/packages/c0/76/0475d10d27f3384df3a6ddfdf4a4fdfef83766f77cd4e327d905dc956c15/mkdocstrings-0.26.2.tar.gz", hash = "sha256:34a8b50f1e6cfd29546c6c09fbe02154adfb0b361bb758834bf56aa284ba876e", size = 92512 } wheels = [ - { url = "https://files.pythonhosted.org/packages/23/cc/8ba127aaee5d1e9046b0d33fa5b3d17da95a9d705d44902792e0569257fd/mkdocstrings-0.26.1-py3-none-any.whl", hash = "sha256:29738bfb72b4608e8e55cc50fb8a54f325dc7ebd2014e4e3881a49892d5983cf", size = 29643 }, + { url = "https://files.pythonhosted.org/packages/80/b6/4ee320d7c313da3774eff225875eb278f7e6bb26a9cd8e680b8dbc38fdea/mkdocstrings-0.26.2-py3-none-any.whl", hash = "sha256:1248f3228464f3b8d1a15bd91249ce1701fe3104ac517a5f167a0e01ca850ba5", size = 29716 }, ] [[package]] name = "mkdocstrings-python" -version = "1.11.1" +version = "1.12.2" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "griffe" }, { name = "mkdocs-autorefs" }, { name = "mkdocstrings" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/fc/ba/534c934cd0a809f51c91332d6ed278782ee4126b8ba8db02c2003f162b47/mkdocstrings_python-1.11.1.tar.gz", hash = "sha256:8824b115c5359304ab0b5378a91f6202324a849e1da907a3485b59208b797322", size = 166890 } +sdist = { url = "https://files.pythonhosted.org/packages/23/ec/cb6debe2db77f1ef42b25b21d93b5021474de3037cd82385e586aee72545/mkdocstrings_python-1.12.2.tar.gz", hash = "sha256:7a1760941c0b52a2cd87b960a9e21112ffe52e7df9d0b9583d04d47ed2e186f3", size = 168207 } wheels = [ - { url = "https://files.pythonhosted.org/packages/2f/f2/2a2c48fda645ac6bbe73bcc974587a579092b6868e6ff8bc6d177f4db38a/mkdocstrings_python-1.11.1-py3-none-any.whl", hash = "sha256:a21a1c05acef129a618517bb5aae3e33114f569b11588b1e7af3e9d4061a71af", size = 109297 }, + { url = "https://files.pythonhosted.org/packages/5b/c1/ac524e1026d9580cbc654b5d19f5843c8b364a66d30f956372cd09fd2f92/mkdocstrings_python-1.12.2-py3-none-any.whl", hash = "sha256:7f7d40d6db3cb1f5d19dbcd80e3efe4d0ba32b073272c0c0de9de2e604eda62a", size = 111759 }, ] [[package]] @@ -2382,15 +2370,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/9d/d3/f7e6d7d062b8d7072c3989a528d9d47486ee5d5ae75250f6e26b4976d098/mmh3-5.0.1-cp313-cp313-win_arm64.whl", hash = "sha256:122fa9ec148383f9124292962bda745f192b47bfd470b2af5fe7bb3982b17896", size = 36539 }, ] -[[package]] -name = "mock" -version = "4.0.3" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/e2/be/3ea39a8fd4ed3f9a25aae18a1bff2df7a610bca93c8ede7475e32d8b73a0/mock-4.0.3.tar.gz", hash = "sha256:7d3fbbde18228f4ff2f1f119a45cdffa458b4c0dee32eb4d2bb2f82554bac7bc", size = 72316 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/5c/03/b7e605db4a57c0f6fba744b11ef3ddf4ddebcada35022927a2b5fc623fdf/mock-4.0.3-py3-none-any.whl", hash = "sha256:122fcb64ee37cfad5b3f48d7a7d51875d7031aaf3d8be7c42e2bee25044eee62", size = 28536 }, -] - [[package]] name = "monotonic" version = "1.6" @@ -2481,53 +2460,38 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/99/b7/b9e70fde2c0f0c9af4cc5277782a89b66d35948ea3369ec9f598358c3ac5/multidict-6.1.0-py3-none-any.whl", hash = "sha256:48e171e52d1c4d33888e529b999e5900356b9ae588c2f09a52dcefb158b27506", size = 10051 }, ] -[[package]] -name = "multiprocess" -version = "0.70.17" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "dill" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/e9/34/1acca6e18697017ad5c8b45279b59305d660ecf2fbed13e5f406f69890e4/multiprocess-0.70.17.tar.gz", hash = "sha256:4ae2f11a3416809ebc9a48abfc8b14ecce0652a0944731a1493a3c1ba44ff57a", size = 1785744 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/f2/97/e57eaa8a4dc4036460d13162470eb0da520e6496a90b943529cf1ca40ebd/multiprocess-0.70.17-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:7ddb24e5bcdb64e90ec5543a1f05a39463068b6d3b804aa3f2a4e16ec28562d6", size = 135007 }, - { url = "https://files.pythonhosted.org/packages/8f/0a/bb06ea45e5b400cd9944e05878fdbb9016ba78ffb9190c541eec9c8e8380/multiprocess-0.70.17-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:d729f55198a3579f6879766a6d9b72b42d4b320c0dcb7844afb774d75b573c62", size = 135008 }, - { url = "https://files.pythonhosted.org/packages/20/e3/db48b10f0a25569c5c3a20288d82f9677cb312bccbd1da16cf8fb759649f/multiprocess-0.70.17-pp310-pypy310_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:c2c82d0375baed8d8dd0d8c38eb87c5ae9c471f8e384ad203a36f095ee860f67", size = 135012 }, - { url = "https://files.pythonhosted.org/packages/e7/a9/39cf856d03690af6fd570cf40331f1f79acdbb3132a9c35d2c5002f7f30b/multiprocess-0.70.17-py310-none-any.whl", hash = "sha256:38357ca266b51a2e22841b755d9a91e4bb7b937979a54d411677111716c32744", size = 134830 }, - { url = "https://files.pythonhosted.org/packages/b2/07/8cbb75d6cfbe8712d8f7f6a5615f083c6e710ab916b748fbb20373ddb142/multiprocess-0.70.17-py311-none-any.whl", hash = "sha256:2884701445d0177aec5bd5f6ee0df296773e4fb65b11903b94c613fb46cfb7d1", size = 144346 }, - { url = "https://files.pythonhosted.org/packages/a4/69/d3f343a61a2f86ef10ed7865a26beda7c71554136ce187b0384b1c2c9ca3/multiprocess-0.70.17-py312-none-any.whl", hash = "sha256:2818af14c52446b9617d1b0755fa70ca2f77c28b25ed97bdaa2c69a22c47b46c", size = 147990 }, - { url = "https://files.pythonhosted.org/packages/c8/b7/2e9a4fcd871b81e1f2a812cd5c6fb52ad1e8da7bf0d7646c55eaae220484/multiprocess-0.70.17-py313-none-any.whl", hash = "sha256:20c28ca19079a6c879258103a6d60b94d4ffe2d9da07dda93fb1c8bc6243f522", size = 149843 }, - { url = "https://files.pythonhosted.org/packages/ae/d7/fd7a092fc0ab1845a1a97ca88e61b9b7cc2e9d6fcf0ed24e9480590c2336/multiprocess-0.70.17-py38-none-any.whl", hash = "sha256:1d52f068357acd1e5bbc670b273ef8f81d57863235d9fbf9314751886e141968", size = 132635 }, - { url = "https://files.pythonhosted.org/packages/f9/41/0618ac724b8a56254962c143759e04fa01c73b37aa69dd433f16643bd38b/multiprocess-0.70.17-py39-none-any.whl", hash = "sha256:c3feb874ba574fbccfb335980020c1ac631fbf2a3f7bee4e2042ede62558a021", size = 133359 }, -] - [[package]] name = "mypy" -version = "1.11.2" +version = "1.13.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "mypy-extensions" }, { name = "tomli", marker = "python_full_version < '3.11'" }, { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/5c/86/5d7cbc4974fd564550b80fbb8103c05501ea11aa7835edf3351d90095896/mypy-1.11.2.tar.gz", hash = "sha256:7f9993ad3e0ffdc95c2a14b66dee63729f021968bff8ad911867579c65d13a79", size = 3078806 } +sdist = { url = "https://files.pythonhosted.org/packages/e8/21/7e9e523537991d145ab8a0a2fd98548d67646dc2aaaf6091c31ad883e7c1/mypy-1.13.0.tar.gz", hash = "sha256:0291a61b6fbf3e6673e3405cfcc0e7650bebc7939659fdca2702958038bd835e", size = 3152532 } wheels = [ - { url = "https://files.pythonhosted.org/packages/78/cd/815368cd83c3a31873e5e55b317551500b12f2d1d7549720632f32630333/mypy-1.11.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:d42a6dd818ffce7be66cce644f1dff482f1d97c53ca70908dff0b9ddc120b77a", size = 10939401 }, - { url = "https://files.pythonhosted.org/packages/f1/27/e18c93a195d2fad75eb96e1f1cbc431842c332e8eba2e2b77eaf7313c6b7/mypy-1.11.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:801780c56d1cdb896eacd5619a83e427ce436d86a3bdf9112527f24a66618fef", size = 10111697 }, - { url = "https://files.pythonhosted.org/packages/dc/08/cdc1fc6d0d5a67d354741344cc4aa7d53f7128902ebcbe699ddd4f15a61c/mypy-1.11.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:41ea707d036a5307ac674ea172875f40c9d55c5394f888b168033177fce47383", size = 12500508 }, - { url = "https://files.pythonhosted.org/packages/64/12/aad3af008c92c2d5d0720ea3b6674ba94a98cdb86888d389acdb5f218c30/mypy-1.11.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:6e658bd2d20565ea86da7d91331b0eed6d2eee22dc031579e6297f3e12c758c8", size = 13020712 }, - { url = "https://files.pythonhosted.org/packages/03/e6/a7d97cc124a565be5e9b7d5c2a6ebf082379ffba99646e4863ed5bbcb3c3/mypy-1.11.2-cp310-cp310-win_amd64.whl", hash = "sha256:478db5f5036817fe45adb7332d927daa62417159d49783041338921dcf646fc7", size = 9567319 }, - { url = "https://files.pythonhosted.org/packages/e2/aa/cc56fb53ebe14c64f1fe91d32d838d6f4db948b9494e200d2f61b820b85d/mypy-1.11.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:75746e06d5fa1e91bfd5432448d00d34593b52e7e91a187d981d08d1f33d4385", size = 10859630 }, - { url = "https://files.pythonhosted.org/packages/04/c8/b19a760fab491c22c51975cf74e3d253b8c8ce2be7afaa2490fbf95a8c59/mypy-1.11.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:a976775ab2256aadc6add633d44f100a2517d2388906ec4f13231fafbb0eccca", size = 10037973 }, - { url = "https://files.pythonhosted.org/packages/88/57/7e7e39f2619c8f74a22efb9a4c4eff32b09d3798335625a124436d121d89/mypy-1.11.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:cd953f221ac1379050a8a646585a29574488974f79d8082cedef62744f0a0104", size = 12416659 }, - { url = "https://files.pythonhosted.org/packages/fc/a6/37f7544666b63a27e46c48f49caeee388bf3ce95f9c570eb5cfba5234405/mypy-1.11.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:57555a7715c0a34421013144a33d280e73c08df70f3a18a552938587ce9274f4", size = 12897010 }, - { url = "https://files.pythonhosted.org/packages/84/8b/459a513badc4d34acb31c736a0101c22d2bd0697b969796ad93294165cfb/mypy-1.11.2-cp311-cp311-win_amd64.whl", hash = "sha256:36383a4fcbad95f2657642a07ba22ff797de26277158f1cc7bd234821468b1b6", size = 9562873 }, - { url = "https://files.pythonhosted.org/packages/35/3a/ed7b12ecc3f6db2f664ccf85cb2e004d3e90bec928e9d7be6aa2f16b7cdf/mypy-1.11.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:e8960dbbbf36906c5c0b7f4fbf2f0c7ffb20f4898e6a879fcf56a41a08b0d318", size = 10990335 }, - { url = "https://files.pythonhosted.org/packages/04/e4/1a9051e2ef10296d206519f1df13d2cc896aea39e8683302f89bf5792a59/mypy-1.11.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:06d26c277962f3fb50e13044674aa10553981ae514288cb7d0a738f495550b36", size = 10007119 }, - { url = "https://files.pythonhosted.org/packages/f3/3c/350a9da895f8a7e87ade0028b962be0252d152e0c2fbaafa6f0658b4d0d4/mypy-1.11.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6e7184632d89d677973a14d00ae4d03214c8bc301ceefcdaf5c474866814c987", size = 12506856 }, - { url = "https://files.pythonhosted.org/packages/b6/49/ee5adf6a49ff13f4202d949544d3d08abb0ea1f3e7f2a6d5b4c10ba0360a/mypy-1.11.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:3a66169b92452f72117e2da3a576087025449018afc2d8e9bfe5ffab865709ca", size = 12952066 }, - { url = "https://files.pythonhosted.org/packages/27/c0/b19d709a42b24004d720db37446a42abadf844d5c46a2c442e2a074d70d9/mypy-1.11.2-cp312-cp312-win_amd64.whl", hash = "sha256:969ea3ef09617aff826885a22ece0ddef69d95852cdad2f60c8bb06bf1f71f70", size = 9664000 }, - { url = "https://files.pythonhosted.org/packages/42/3a/bdf730640ac523229dd6578e8a581795720a9321399de494374afc437ec5/mypy-1.11.2-py3-none-any.whl", hash = "sha256:b499bc07dbdcd3de92b0a8b29fdf592c111276f6a12fe29c30f6c417dd546d12", size = 2619625 }, + { url = "https://files.pythonhosted.org/packages/5e/8c/206de95a27722b5b5a8c85ba3100467bd86299d92a4f71c6b9aa448bfa2f/mypy-1.13.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:6607e0f1dd1fb7f0aca14d936d13fd19eba5e17e1cd2a14f808fa5f8f6d8f60a", size = 11020731 }, + { url = "https://files.pythonhosted.org/packages/ab/bb/b31695a29eea76b1569fd28b4ab141a1adc9842edde080d1e8e1776862c7/mypy-1.13.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:8a21be69bd26fa81b1f80a61ee7ab05b076c674d9b18fb56239d72e21d9f4c80", size = 10184276 }, + { url = "https://files.pythonhosted.org/packages/a5/2d/4a23849729bb27934a0e079c9c1aad912167d875c7b070382a408d459651/mypy-1.13.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:7b2353a44d2179846a096e25691d54d59904559f4232519d420d64da6828a3a7", size = 12587706 }, + { url = "https://files.pythonhosted.org/packages/5c/c3/d318e38ada50255e22e23353a469c791379825240e71b0ad03e76ca07ae6/mypy-1.13.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:0730d1c6a2739d4511dc4253f8274cdd140c55c32dfb0a4cf8b7a43f40abfa6f", size = 13105586 }, + { url = "https://files.pythonhosted.org/packages/4a/25/3918bc64952370c3dbdbd8c82c363804678127815febd2925b7273d9482c/mypy-1.13.0-cp310-cp310-win_amd64.whl", hash = "sha256:c5fc54dbb712ff5e5a0fca797e6e0aa25726c7e72c6a5850cfd2adbc1eb0a372", size = 9632318 }, + { url = "https://files.pythonhosted.org/packages/d0/19/de0822609e5b93d02579075248c7aa6ceaddcea92f00bf4ea8e4c22e3598/mypy-1.13.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:581665e6f3a8a9078f28d5502f4c334c0c8d802ef55ea0e7276a6e409bc0d82d", size = 10939027 }, + { url = "https://files.pythonhosted.org/packages/c8/71/6950fcc6ca84179137e4cbf7cf41e6b68b4a339a1f5d3e954f8c34e02d66/mypy-1.13.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:3ddb5b9bf82e05cc9a627e84707b528e5c7caaa1c55c69e175abb15a761cec2d", size = 10108699 }, + { url = "https://files.pythonhosted.org/packages/26/50/29d3e7dd166e74dc13d46050b23f7d6d7533acf48f5217663a3719db024e/mypy-1.13.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:20c7ee0bc0d5a9595c46f38beb04201f2620065a93755704e141fcac9f59db2b", size = 12506263 }, + { url = "https://files.pythonhosted.org/packages/3f/1d/676e76f07f7d5ddcd4227af3938a9c9640f293b7d8a44dd4ff41d4db25c1/mypy-1.13.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:3790ded76f0b34bc9c8ba4def8f919dd6a46db0f5a6610fb994fe8efdd447f73", size = 12984688 }, + { url = "https://files.pythonhosted.org/packages/9c/03/5a85a30ae5407b1d28fab51bd3e2103e52ad0918d1e68f02a7778669a307/mypy-1.13.0-cp311-cp311-win_amd64.whl", hash = "sha256:51f869f4b6b538229c1d1bcc1dd7d119817206e2bc54e8e374b3dfa202defcca", size = 9626811 }, + { url = "https://files.pythonhosted.org/packages/fb/31/c526a7bd2e5c710ae47717c7a5f53f616db6d9097caf48ad650581e81748/mypy-1.13.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:5c7051a3461ae84dfb5dd15eff5094640c61c5f22257c8b766794e6dd85e72d5", size = 11077900 }, + { url = "https://files.pythonhosted.org/packages/83/67/b7419c6b503679d10bd26fc67529bc6a1f7a5f220bbb9f292dc10d33352f/mypy-1.13.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:39bb21c69a5d6342f4ce526e4584bc5c197fd20a60d14a8624d8743fffb9472e", size = 10074818 }, + { url = "https://files.pythonhosted.org/packages/ba/07/37d67048786ae84e6612575e173d713c9a05d0ae495dde1e68d972207d98/mypy-1.13.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:164f28cb9d6367439031f4c81e84d3ccaa1e19232d9d05d37cb0bd880d3f93c2", size = 12589275 }, + { url = "https://files.pythonhosted.org/packages/1f/17/b1018c6bb3e9f1ce3956722b3bf91bff86c1cefccca71cec05eae49d6d41/mypy-1.13.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:a4c1bfcdbce96ff5d96fc9b08e3831acb30dc44ab02671eca5953eadad07d6d0", size = 13037783 }, + { url = "https://files.pythonhosted.org/packages/cb/32/cd540755579e54a88099aee0287086d996f5a24281a673f78a0e14dba150/mypy-1.13.0-cp312-cp312-win_amd64.whl", hash = "sha256:a0affb3a79a256b4183ba09811e3577c5163ed06685e4d4b46429a271ba174d2", size = 9726197 }, + { url = "https://files.pythonhosted.org/packages/11/bb/ab4cfdc562cad80418f077d8be9b4491ee4fb257440da951b85cbb0a639e/mypy-1.13.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:a7b44178c9760ce1a43f544e595d35ed61ac2c3de306599fa59b38a6048e1aa7", size = 11069721 }, + { url = "https://files.pythonhosted.org/packages/59/3b/a393b1607cb749ea2c621def5ba8c58308ff05e30d9dbdc7c15028bca111/mypy-1.13.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:5d5092efb8516d08440e36626f0153b5006d4088c1d663d88bf79625af3d1d62", size = 10063996 }, + { url = "https://files.pythonhosted.org/packages/d1/1f/6b76be289a5a521bb1caedc1f08e76ff17ab59061007f201a8a18cc514d1/mypy-1.13.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:de2904956dac40ced10931ac967ae63c5089bd498542194b436eb097a9f77bc8", size = 12584043 }, + { url = "https://files.pythonhosted.org/packages/a6/83/5a85c9a5976c6f96e3a5a7591aa28b4a6ca3a07e9e5ba0cec090c8b596d6/mypy-1.13.0-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:7bfd8836970d33c2105562650656b6846149374dc8ed77d98424b40b09340ba7", size = 13036996 }, + { url = "https://files.pythonhosted.org/packages/b4/59/c39a6f752f1f893fccbcf1bdd2aca67c79c842402b5283563d006a67cf76/mypy-1.13.0-cp313-cp313-win_amd64.whl", hash = "sha256:9f73dba9ec77acb86457a8fc04b5239822df0c14a082564737833d2963677dbc", size = 9737709 }, + { url = "https://files.pythonhosted.org/packages/3b/86/72ce7f57431d87a7ff17d442f521146a6585019eb8f4f31b7c02801f78ad/mypy-1.13.0-py3-none-any.whl", hash = "sha256:9c250883f9fd81d212e0952c92dbfcc96fc237f4b7c92f56ac81fd48460b3e5a", size = 2647043 }, ] [[package]] @@ -2539,25 +2503,13 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/2a/e2/5d3f6ada4297caebe1a2add3b126fe800c96f56dbe5d1988a2cbe0b267aa/mypy_extensions-1.0.0-py3-none-any.whl", hash = "sha256:4392f6c0eb8a5668a69e23d168ffa70f0be9ccfd32b5cc2d26a34ae5b844552d", size = 4695 }, ] -[[package]] -name = "neo4j" -version = "5.25.0" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "pytz" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/40/2d/72fa7ad776831e3c94ca0cdc777be3699407d97f85a13ff0cfecf6dc2deb/neo4j-5.25.0.tar.gz", hash = "sha256:7c82001c45319092cc0b5df4c92894553b7ab97bd4f59655156fa9acab83aec9", size = 218596 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/e4/f6/629192f27d9ae0ade5b34ba1341065ccf0176d01b76f60d732cce84ec7e9/neo4j-5.25.0-py3-none-any.whl", hash = "sha256:df310eee9a4f9749fb32bb9f1aa68711ac417b7eba3e42faefd6848038345ffa", size = 296624 }, -] - [[package]] name = "networkx" -version = "3.3" +version = "3.4.2" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/04/e6/b164f94c869d6b2c605b5128b7b0cfe912795a87fc90e78533920001f3ec/networkx-3.3.tar.gz", hash = "sha256:0c127d8b2f4865f59ae9cb8aafcd60b5c70f3241ebd66f7defad7c4ab90126c9", size = 2126579 } +sdist = { url = "https://files.pythonhosted.org/packages/fd/1d/06475e1cd5264c0b870ea2cc6fdb3e37177c1e565c43f56ff17a10e3937f/networkx-3.4.2.tar.gz", hash = "sha256:307c3669428c5362aab27c8a1260aa8f47c4e91d3891f48be0141738d8d053e1", size = 2151368 } wheels = [ - { url = "https://files.pythonhosted.org/packages/38/e9/5f72929373e1a0e8d142a130f3f97e6ff920070f87f91c4e13e40e0fba5a/networkx-3.3-py3-none-any.whl", hash = "sha256:28575580c6ebdaf4505b22c6256a2b9de86b316dc63ba9e93abde3d78dfdbcf2", size = 1702396 }, + { url = "https://files.pythonhosted.org/packages/b9/54/dd730b32ea14ea797530a4479b2ed46a6fb250f682a9cfb997e968bf0261/networkx-3.4.2-py3-none-any.whl", hash = "sha256:df5d4365b724cf81b8c6a7312509d0c22386097011ad1abe274afd5e9d3bbc5f", size = 1723263 }, ] [[package]] @@ -2642,7 +2594,7 @@ wheels = [ [[package]] name = "openai" -version = "1.51.0" +version = "1.52.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "anyio" }, @@ -2654,9 +2606,9 @@ dependencies = [ { name = "tqdm" }, { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/28/af/cc59b1447f5a02bb1f25b9b0cd94b607aa2c969a81d9a244d4067f91f6fe/openai-1.51.0.tar.gz", hash = "sha256:8dc4f9d75ccdd5466fc8c99a952186eddceb9fd6ba694044773f3736a847149d", size = 306880 } +sdist = { url = "https://files.pythonhosted.org/packages/80/ac/54c76352d493866637756b7c0ecec44f0b5bafb8fe753d98472cf6cfe4ce/openai-1.52.1.tar.gz", hash = "sha256:383b96c7e937cbec23cad5bf5718085381e4313ca33c5c5896b54f8e1b19d144", size = 310069 } wheels = [ - { url = "https://files.pythonhosted.org/packages/c7/08/9f22356d4fbd273f734db1e6663b7ca6987943080567f5580471022e57ca/openai-1.51.0-py3-none-any.whl", hash = "sha256:d9affafb7e51e5a27dce78589d4964ce4d6f6d560307265933a94b2e3f3c5d2c", size = 383533 }, + { url = "https://files.pythonhosted.org/packages/ad/31/28a83e124e9f9dd04c83b5aeb6f8b1770f45addde4dd3d34d9a9091590ad/openai-1.52.1-py3-none-any.whl", hash = "sha256:f23e83df5ba04ee0e82c8562571e8cb596cd88f9a84ab783e6c6259e5ffbfb4a", size = 386945 }, ] [[package]] @@ -2816,46 +2768,47 @@ wheels = [ [[package]] name = "orjson" -version = "3.10.7" +version = "3.10.10" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/9e/03/821c8197d0515e46ea19439f5c5d5fd9a9889f76800613cfac947b5d7845/orjson-3.10.7.tar.gz", hash = "sha256:75ef0640403f945f3a1f9f6400686560dbfb0fb5b16589ad62cd477043c4eee3", size = 5056450 } +sdist = { url = "https://files.pythonhosted.org/packages/80/44/d36e86b33fc84f224b5f2cdf525adf3b8f9f475753e721c402b1ddef731e/orjson-3.10.10.tar.gz", hash = "sha256:37949383c4df7b4337ce82ee35b6d7471e55195efa7dcb45ab8226ceadb0fe3b", size = 5404170 } wheels = [ - { url = "https://files.pythonhosted.org/packages/49/12/60931cf808b9334f26210ab496442f4a7a3d66e29d1cf12e0a01857e756f/orjson-3.10.7-cp310-cp310-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:74f4544f5a6405b90da8ea724d15ac9c36da4d72a738c64685003337401f5c12", size = 251312 }, - { url = "https://files.pythonhosted.org/packages/fe/0e/efbd0a2d25f8e82b230eb20b6b8424be6dd95b6811b669be9af16234b6db/orjson-3.10.7-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:34a566f22c28222b08875b18b0dfbf8a947e69df21a9ed5c51a6bf91cfb944ac", size = 148124 }, - { url = "https://files.pythonhosted.org/packages/dd/47/1ddff6e23fe5f4aeaaed996a3cde422b3eaac4558c03751723e106184c68/orjson-3.10.7-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:bf6ba8ebc8ef5792e2337fb0419f8009729335bb400ece005606336b7fd7bab7", size = 147277 }, - { url = "https://files.pythonhosted.org/packages/04/da/d03d72b54bdd60d05de372114abfbd9f05050946895140c6ff5f27ab8f49/orjson-3.10.7-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ac7cf6222b29fbda9e3a472b41e6a5538b48f2c8f99261eecd60aafbdb60690c", size = 152955 }, - { url = "https://files.pythonhosted.org/packages/7f/7e/ef8522dbba112af6cc52227dcc746dd3447c7d53ea8cea35740239b547ee/orjson-3.10.7-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:de817e2f5fc75a9e7dd350c4b0f54617b280e26d1631811a43e7e968fa71e3e9", size = 163955 }, - { url = "https://files.pythonhosted.org/packages/b6/bc/fbd345d771a73cacc5b0e774d034cd081590b336754c511f4ead9fdc4cf1/orjson-3.10.7-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:348bdd16b32556cf8d7257b17cf2bdb7ab7976af4af41ebe79f9796c218f7e91", size = 141896 }, - { url = "https://files.pythonhosted.org/packages/82/0a/1f09c12d15b1e83156b7f3f621561d38650fe5b8f39f38f04a64de1a87fc/orjson-3.10.7-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:479fd0844ddc3ca77e0fd99644c7fe2de8e8be1efcd57705b5c92e5186e8a250", size = 170166 }, - { url = "https://files.pythonhosted.org/packages/a6/d8/eee30caba21a8d6a9df06d2519bb0ecd0adbcd57f2e79d360de5570031cf/orjson-3.10.7-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:fdf5197a21dd660cf19dfd2a3ce79574588f8f5e2dbf21bda9ee2d2b46924d84", size = 167804 }, - { url = "https://files.pythonhosted.org/packages/44/fe/d1d89d3f15e343511417195f6ccd2bdeb7ebc5a48a882a79ab3bbcdf5fc7/orjson-3.10.7-cp310-none-win32.whl", hash = "sha256:d374d36726746c81a49f3ff8daa2898dccab6596864ebe43d50733275c629175", size = 143010 }, - { url = "https://files.pythonhosted.org/packages/88/8c/0e7b8d5a523927774758ac4ce2de4d8ca5dda569955ba3aeb5e208344eda/orjson-3.10.7-cp310-none-win_amd64.whl", hash = "sha256:cb61938aec8b0ffb6eef484d480188a1777e67b05d58e41b435c74b9d84e0b9c", size = 137306 }, - { url = "https://files.pythonhosted.org/packages/89/c9/dd286c97c2f478d43839bd859ca4d9820e2177d4e07a64c516dc3e018062/orjson-3.10.7-cp311-cp311-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:7db8539039698ddfb9a524b4dd19508256107568cdad24f3682d5773e60504a2", size = 251312 }, - { url = "https://files.pythonhosted.org/packages/b9/72/d90bd11e83a0e9623b3803b079478a93de8ec4316c98fa66110d594de5fa/orjson-3.10.7-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:480f455222cb7a1dea35c57a67578848537d2602b46c464472c995297117fa09", size = 148125 }, - { url = "https://files.pythonhosted.org/packages/9d/b6/ed61e87f327a4cbb2075ed0716e32ba68cb029aa654a68c3eb27803050d8/orjson-3.10.7-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:8a9c9b168b3a19e37fe2778c0003359f07822c90fdff8f98d9d2a91b3144d8e0", size = 147278 }, - { url = "https://files.pythonhosted.org/packages/66/9f/e6a11b5d1ad11e9dc869d938707ef93ff5ed20b53d6cda8b5e2ac532a9d2/orjson-3.10.7-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8de062de550f63185e4c1c54151bdddfc5625e37daf0aa1e75d2a1293e3b7d9a", size = 152954 }, - { url = "https://files.pythonhosted.org/packages/92/ee/702d5e8ccd42dc2b9d1043f22daa1ba75165616aa021dc19fb0c5a726ce8/orjson-3.10.7-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:6b0dd04483499d1de9c8f6203f8975caf17a6000b9c0c54630cef02e44ee624e", size = 163953 }, - { url = "https://files.pythonhosted.org/packages/d3/cb/55205f3f1ee6ba80c0a9a18ca07423003ca8de99192b18be30f1f31b4cdd/orjson-3.10.7-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b58d3795dafa334fc8fd46f7c5dc013e6ad06fd5b9a4cc98cb1456e7d3558bd6", size = 141895 }, - { url = "https://files.pythonhosted.org/packages/bb/ab/1185e472f15c00d37d09c395e478803ed0eae7a3a3d055a5f3885e1ea136/orjson-3.10.7-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:33cfb96c24034a878d83d1a9415799a73dc77480e6c40417e5dda0710d559ee6", size = 170169 }, - { url = "https://files.pythonhosted.org/packages/53/b9/10abe9089bdb08cd4218cc45eb7abfd787c82cf301cecbfe7f141542d7f4/orjson-3.10.7-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:e724cebe1fadc2b23c6f7415bad5ee6239e00a69f30ee423f319c6af70e2a5c0", size = 167808 }, - { url = "https://files.pythonhosted.org/packages/8a/ad/26b40ccef119dcb0f4a39745ffd7d2d319152c1a52859b1ebbd114eca19c/orjson-3.10.7-cp311-none-win32.whl", hash = "sha256:82763b46053727a7168d29c772ed5c870fdae2f61aa8a25994c7984a19b1021f", size = 143010 }, - { url = "https://files.pythonhosted.org/packages/e7/63/5f4101e4895b78ada568f4cf8f870dd594139ca2e75e654e373da78b03b0/orjson-3.10.7-cp311-none-win_amd64.whl", hash = "sha256:eb8d384a24778abf29afb8e41d68fdd9a156cf6e5390c04cc07bbc24b89e98b5", size = 137307 }, - { url = "https://files.pythonhosted.org/packages/14/7c/b4ecc2069210489696a36e42862ccccef7e49e1454a3422030ef52881b01/orjson-3.10.7-cp312-cp312-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:44a96f2d4c3af51bfac6bc4ef7b182aa33f2f054fd7f34cc0ee9a320d051d41f", size = 251409 }, - { url = "https://files.pythonhosted.org/packages/60/84/e495edb919ef0c98d054a9b6d05f2700fdeba3886edd58f1c4dfb25d514a/orjson-3.10.7-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:76ac14cd57df0572453543f8f2575e2d01ae9e790c21f57627803f5e79b0d3c3", size = 147913 }, - { url = "https://files.pythonhosted.org/packages/c5/27/e40bc7d79c4afb7e9264f22320c285d06d2c9574c9c682ba0f1be3012833/orjson-3.10.7-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:bdbb61dcc365dd9be94e8f7df91975edc9364d6a78c8f7adb69c1cdff318ec93", size = 147390 }, - { url = "https://files.pythonhosted.org/packages/30/be/fd646fb1a461de4958a6eacf4ecf064b8d5479c023e0e71cc89b28fa91ac/orjson-3.10.7-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b48b3db6bb6e0a08fa8c83b47bc169623f801e5cc4f24442ab2b6617da3b5313", size = 152973 }, - { url = "https://files.pythonhosted.org/packages/b1/00/414f8d4bc5ec3447e27b5c26b4e996e4ef08594d599e79b3648f64da060c/orjson-3.10.7-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:23820a1563a1d386414fef15c249040042b8e5d07b40ab3fe3efbfbbcbcb8864", size = 164039 }, - { url = "https://files.pythonhosted.org/packages/a0/6b/34e6904ac99df811a06e42d8461d47b6e0c9b86e2fe7ee84934df6e35f0d/orjson-3.10.7-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a0c6a008e91d10a2564edbb6ee5069a9e66df3fbe11c9a005cb411f441fd2c09", size = 142035 }, - { url = "https://files.pythonhosted.org/packages/17/7e/254189d9b6df89660f65aec878d5eeaa5b1ae371bd2c458f85940445d36f/orjson-3.10.7-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:d352ee8ac1926d6193f602cbe36b1643bbd1bbcb25e3c1a657a4390f3000c9a5", size = 169941 }, - { url = "https://files.pythonhosted.org/packages/02/1a/d11805670c29d3a1b29fc4bd048dc90b094784779690592efe8c9f71249a/orjson-3.10.7-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:d2d9f990623f15c0ae7ac608103c33dfe1486d2ed974ac3f40b693bad1a22a7b", size = 167994 }, - { url = "https://files.pythonhosted.org/packages/20/5f/03d89b007f9d6733dc11bc35d64812101c85d6c4e9c53af9fa7e7689cb11/orjson-3.10.7-cp312-none-win32.whl", hash = "sha256:7c4c17f8157bd520cdb7195f75ddbd31671997cbe10aee559c2d613592e7d7eb", size = 143130 }, - { url = "https://files.pythonhosted.org/packages/c6/9d/9b9fb6c60b8a0e04031ba85414915e19ecea484ebb625402d968ea45b8d5/orjson-3.10.7-cp312-none-win_amd64.whl", hash = "sha256:1d9c0e733e02ada3ed6098a10a8ee0052dd55774de3d9110d29868d24b17faa1", size = 137326 }, - { url = "https://files.pythonhosted.org/packages/15/05/121af8a87513c56745d01ad7cf215c30d08356da9ad882ebe2ba890824cd/orjson-3.10.7-cp313-cp313-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:77d325ed866876c0fa6492598ec01fe30e803272a6e8b10e992288b009cbe149", size = 251331 }, - { url = "https://files.pythonhosted.org/packages/73/7f/8d6ccd64a6f8bdbfe6c9be7c58aeb8094aa52a01fbbb2cda42ff7e312bd7/orjson-3.10.7-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9ea2c232deedcb605e853ae1db2cc94f7390ac776743b699b50b071b02bea6fe", size = 142012 }, - { url = "https://files.pythonhosted.org/packages/04/65/f2a03fd1d4f0308f01d372e004c049f7eb9bc5676763a15f20f383fa9c01/orjson-3.10.7-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:3dcfbede6737fdbef3ce9c37af3fb6142e8e1ebc10336daa05872bfb1d87839c", size = 169920 }, - { url = "https://files.pythonhosted.org/packages/e2/1c/3ef8d83d7c6a619ad3d69a4d5318591b4ce5862e6eda7c26bbe8208652ca/orjson-3.10.7-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:11748c135f281203f4ee695b7f80bb1358a82a63905f9f0b794769483ea854ad", size = 167916 }, - { url = "https://files.pythonhosted.org/packages/f2/0d/820a640e5a7dfbe525e789c70871ebb82aff73b0c7bf80082653f86b9431/orjson-3.10.7-cp313-none-win32.whl", hash = "sha256:a7e19150d215c7a13f39eb787d84db274298d3f83d85463e61d277bbd7f401d2", size = 143089 }, - { url = "https://files.pythonhosted.org/packages/1a/72/a424db9116c7cad2950a8f9e4aeb655a7b57de988eb015acd0fcd1b4609b/orjson-3.10.7-cp313-none-win_amd64.whl", hash = "sha256:eef44224729e9525d5261cc8d28d6b11cafc90e6bd0be2157bde69a52ec83024", size = 137081 }, + { url = "https://files.pythonhosted.org/packages/98/c7/07ca73c32d49550490572235e5000aa0d75e333997cbb3a221890ef0fa04/orjson-3.10.10-cp310-cp310-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:b788a579b113acf1c57e0a68e558be71d5d09aa67f62ca1f68e01117e550a998", size = 270718 }, + { url = "https://files.pythonhosted.org/packages/4d/6e/eaefdfe4b11fd64b38f6663c71a3c9063054c8c643a52555c5b6d4350446/orjson-3.10.10-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:804b18e2b88022c8905bb79bd2cbe59c0cd014b9328f43da8d3b28441995cda4", size = 153292 }, + { url = "https://files.pythonhosted.org/packages/cf/87/94474cbf63306f196a0a85a2f3ea6cea261328b4141a260b7ec5e7145bc5/orjson-3.10.10-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:9972572a1d042ec9ee421b6da69f7cc823da5962237563fa548ab17f152f0b9b", size = 168625 }, + { url = "https://files.pythonhosted.org/packages/0a/67/1a6bd763282bc89fcc0269e3a44a8ecbb236a1e4b6f5a6320301726b36a1/orjson-3.10.10-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:dc6993ab1c2ae7dd0711161e303f1db69062955ac2668181bfdf2dd410e65258", size = 155845 }, + { url = "https://files.pythonhosted.org/packages/ae/28/bb2dd7a988159896be9fa59ef4c991dca8cca9af85ebdc27164234929008/orjson-3.10.10-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d78e4cacced5781b01d9bc0f0cd8b70b906a0e109825cb41c1b03f9c41e4ce86", size = 166406 }, + { url = "https://files.pythonhosted.org/packages/e3/88/42199849c791b4b5b92fcace0e8ef178d5ae1ea9865dfd4d5809e650d652/orjson-3.10.10-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e6eb2598df518281ba0cbc30d24c5b06124ccf7e19169e883c14e0831217a0bc", size = 144518 }, + { url = "https://files.pythonhosted.org/packages/c7/77/e684fe4ed34e73149bc0e7320b91a459386693279cd62efab6e82da072a3/orjson-3.10.10-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:23776265c5215ec532de6238a52707048401a568f0fa0d938008e92a147fe2c7", size = 172184 }, + { url = "https://files.pythonhosted.org/packages/fa/b2/9dc2ed13121b27b9f99acba077c821ad2c0deff9feeb617efef4699fad35/orjson-3.10.10-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:8cc2a654c08755cef90b468ff17c102e2def0edd62898b2486767204a7f5cc9c", size = 170148 }, + { url = "https://files.pythonhosted.org/packages/86/0a/b06967f9374856f491f297a914c588eae97ef9490a77ec0e146a2e4bfe7f/orjson-3.10.10-cp310-none-win32.whl", hash = "sha256:081b3fc6a86d72efeb67c13d0ea7c030017bd95f9868b1e329a376edc456153b", size = 145116 }, + { url = "https://files.pythonhosted.org/packages/1f/c7/1aecf5e320828261ece0683e472ee77c520f4e6c52c468486862e2257962/orjson-3.10.10-cp310-none-win_amd64.whl", hash = "sha256:ff38c5fb749347768a603be1fb8a31856458af839f31f064c5aa74aca5be9efe", size = 139307 }, + { url = "https://files.pythonhosted.org/packages/79/bc/2a0eb0029729f1e466d5a595261446e5c5b6ed9213759ee56b6202f99417/orjson-3.10.10-cp311-cp311-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:879e99486c0fbb256266c7c6a67ff84f46035e4f8749ac6317cc83dacd7f993a", size = 270717 }, + { url = "https://files.pythonhosted.org/packages/3d/2b/5af226f183ce264bf64f15afe58647b09263dc1bde06aaadae6bbeca17f1/orjson-3.10.10-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:019481fa9ea5ff13b5d5d95e6fd5ab25ded0810c80b150c2c7b1cc8660b662a7", size = 153294 }, + { url = "https://files.pythonhosted.org/packages/1d/95/d6a68ab51ed76e3794669dabb51bf7fa6ec2f4745f66e4af4518aeab4b73/orjson-3.10.10-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:0dd57eff09894938b4c86d4b871a479260f9e156fa7f12f8cad4b39ea8028bb5", size = 168628 }, + { url = "https://files.pythonhosted.org/packages/c0/c9/1bbe5262f5e9df3e1aeec44ca8cc86846c7afb2746fa76bf668a7d0979e9/orjson-3.10.10-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:dbde6d70cd95ab4d11ea8ac5e738e30764e510fc54d777336eec09bb93b8576c", size = 155845 }, + { url = "https://files.pythonhosted.org/packages/bf/22/e17b14ff74646e6c080dccb2859686a820bc6468f6b62ea3fe29a8bd3b05/orjson-3.10.10-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3b2625cb37b8fb42e2147404e5ff7ef08712099197a9cd38895006d7053e69d6", size = 166406 }, + { url = "https://files.pythonhosted.org/packages/8a/1e/b3abbe352f648f96a418acd1e602b1c77ffcc60cf801a57033da990b2c49/orjson-3.10.10-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dbf3c20c6a7db69df58672a0d5815647ecf78c8e62a4d9bd284e8621c1fe5ccb", size = 144518 }, + { url = "https://files.pythonhosted.org/packages/0e/5e/28f521ee0950d279489db1522e7a2460d0596df7c5ca452e242ff1509cfe/orjson-3.10.10-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:75c38f5647e02d423807d252ce4528bf6a95bd776af999cb1fb48867ed01d1f6", size = 172187 }, + { url = "https://files.pythonhosted.org/packages/04/b4/538bf6f42eb0fd5a485abbe61e488d401a23fd6d6a758daefcf7811b6807/orjson-3.10.10-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:23458d31fa50ec18e0ec4b0b4343730928296b11111df5f547c75913714116b2", size = 170152 }, + { url = "https://files.pythonhosted.org/packages/94/5c/a1a326a58452f9261972ad326ae3bb46d7945681239b7062a1b85d8811e2/orjson-3.10.10-cp311-none-win32.whl", hash = "sha256:2787cd9dedc591c989f3facd7e3e86508eafdc9536a26ec277699c0aa63c685b", size = 145116 }, + { url = "https://files.pythonhosted.org/packages/df/12/a02965df75f5a247091306d6cf40a77d20bf6c0490d0a5cb8719551ee815/orjson-3.10.10-cp311-none-win_amd64.whl", hash = "sha256:6514449d2c202a75183f807bc755167713297c69f1db57a89a1ef4a0170ee269", size = 139307 }, + { url = "https://files.pythonhosted.org/packages/21/c6/f1d2ec3ffe9d6a23a62af0477cd11dd2926762e0186a1fad8658a4f48117/orjson-3.10.10-cp312-cp312-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:8564f48f3620861f5ef1e080ce7cd122ee89d7d6dacf25fcae675ff63b4d6e05", size = 270801 }, + { url = "https://files.pythonhosted.org/packages/52/01/eba0226efaa4d4be8e44d9685750428503a3803648878fa5607100a74f81/orjson-3.10.10-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c5bf161a32b479034098c5b81f2608f09167ad2fa1c06abd4e527ea6bf4837a9", size = 153221 }, + { url = "https://files.pythonhosted.org/packages/da/4b/a705f9d3ae4786955ee0ac840b20960add357e612f1b0a54883d1811fe1a/orjson-3.10.10-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:68b65c93617bcafa7f04b74ae8bc2cc214bd5cb45168a953256ff83015c6747d", size = 168590 }, + { url = "https://files.pythonhosted.org/packages/de/6c/eb405252e7d9ae9905a12bad582cfe37ef8ef18fdfee941549cb5834c7b2/orjson-3.10.10-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e8e28406f97fc2ea0c6150f4c1b6e8261453318930b334abc419214c82314f85", size = 156052 }, + { url = "https://files.pythonhosted.org/packages/9f/e7/65a0461574078a38f204575153524876350f0865162faa6e6e300ecaa199/orjson-3.10.10-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e4d0d9fe174cc7a5bdce2e6c378bcdb4c49b2bf522a8f996aa586020e1b96cee", size = 166562 }, + { url = "https://files.pythonhosted.org/packages/dd/99/85780be173e7014428859ba0211e6f2a8f8038ea6ebabe344b42d5daa277/orjson-3.10.10-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b3be81c42f1242cbed03cbb3973501fcaa2675a0af638f8be494eaf37143d999", size = 144892 }, + { url = "https://files.pythonhosted.org/packages/ed/c0/c7c42a2daeb262da417f70064746b700786ee0811b9a5821d9d37543b29d/orjson-3.10.10-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:65f9886d3bae65be026219c0a5f32dbbe91a9e6272f56d092ab22561ad0ea33b", size = 172093 }, + { url = "https://files.pythonhosted.org/packages/ad/9b/be8b3d3aec42aa47f6058482ace0d2ca3023477a46643d766e96281d5d31/orjson-3.10.10-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:730ed5350147db7beb23ddaf072f490329e90a1d059711d364b49fe352ec987b", size = 170424 }, + { url = "https://files.pythonhosted.org/packages/1b/15/a4cc61e23c39b9dec4620cb95817c83c84078be1771d602f6d03f0e5c696/orjson-3.10.10-cp312-none-win32.whl", hash = "sha256:a8f4bf5f1c85bea2170800020d53a8877812892697f9c2de73d576c9307a8a5f", size = 145132 }, + { url = "https://files.pythonhosted.org/packages/9f/8a/ce7c28e4ea337f6d95261345d7c61322f8561c52f57b263a3ad7025984f4/orjson-3.10.10-cp312-none-win_amd64.whl", hash = "sha256:384cd13579a1b4cd689d218e329f459eb9ddc504fa48c5a83ef4889db7fd7a4f", size = 139389 }, + { url = "https://files.pythonhosted.org/packages/0c/69/f1c4382cd44bdaf10006c4e82cb85d2bcae735369f84031e203c4e5d87de/orjson-3.10.10-cp313-cp313-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:44bffae68c291f94ff5a9b4149fe9d1bdd4cd0ff0fb575bcea8351d48db629a1", size = 270695 }, + { url = "https://files.pythonhosted.org/packages/61/29/aeb5153271d4953872b06ed239eb54993a5f344353727c42d3aabb2046f6/orjson-3.10.10-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e27b4c6437315df3024f0835887127dac2a0a3ff643500ec27088d2588fa5ae1", size = 141632 }, + { url = "https://files.pythonhosted.org/packages/bc/a2/c8ac38d8fb461a9b717c766fbe1f7d3acf9bde2f12488eb13194960782e4/orjson-3.10.10-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bca84df16d6b49325a4084fd8b2fe2229cb415e15c46c529f868c3387bb1339d", size = 144854 }, + { url = "https://files.pythonhosted.org/packages/79/51/e7698fdb28bdec633888cc667edc29fd5376fce9ade0a5b3e22f5ebe0343/orjson-3.10.10-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:c14ce70e8f39bd71f9f80423801b5d10bf93d1dceffdecd04df0f64d2c69bc01", size = 172023 }, + { url = "https://files.pythonhosted.org/packages/02/2d/0d99c20878658c7e33b90e6a4bb75cf2924d6ff29c2365262cff3c26589a/orjson-3.10.10-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:24ac62336da9bda1bd93c0491eff0613003b48d3cb5d01470842e7b52a40d5b4", size = 170429 }, + { url = "https://files.pythonhosted.org/packages/cd/45/6a4a446f4fb29bb4703c3537d5c6a2bf7fed768cb4d7b7dce9d71b72fc93/orjson-3.10.10-cp313-none-win32.whl", hash = "sha256:eb0a42831372ec2b05acc9ee45af77bcaccbd91257345f93780a8e654efc75db", size = 145099 }, + { url = "https://files.pythonhosted.org/packages/72/6e/4631fe219a4203aa111e9bb763ad2e2e0cdd1a03805029e4da124d96863f/orjson-3.10.10-cp313-none-win_amd64.whl", hash = "sha256:f0c4f37f8bf3f1075c6cc8dd8a9f843689a4b618628f8812d0a71e6968b95ffd", size = 139176 }, ] [[package]] @@ -2963,21 +2916,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/c6/ac/dac4a63f978e4dcb3c6d3a78c4d8e0192a113d288502a1216950c41b1027/parso-0.8.4-py2.py3-none-any.whl", hash = "sha256:a418670a20291dacd2dddc80c377c5c3791378ee1e8d12bffc35420643d43f18", size = 103650 }, ] -[[package]] -name = "pathos" -version = "0.3.3" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "dill" }, - { name = "multiprocess" }, - { name = "pox" }, - { name = "ppft" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/5b/a4/6274bddc49a00873d3269b7612c1553763bae6466c0c82913e16810abd51/pathos-0.3.3.tar.gz", hash = "sha256:dcb2a5f321aa34ca541c1c1861011ea49df357bb908379c21dd5741f666e0a58", size = 166953 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/77/f6/a459cf58ff6b2d1c0a1961ee7084f4bb549d50e288f064e7e7be5ae3a7ab/pathos-0.3.3-py3-none-any.whl", hash = "sha256:e04616c6448608ad1f809360be22e3f2078d949a36a81e6991da6c2dd1f82513", size = 82142 }, -] - [[package]] name = "pathspec" version = "0.12.1" @@ -3001,61 +2939,69 @@ wheels = [ [[package]] name = "pillow" -version = "10.4.0" +version = "11.0.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/cd/74/ad3d526f3bf7b6d3f408b73fde271ec69dfac8b81341a318ce825f2b3812/pillow-10.4.0.tar.gz", hash = "sha256:166c1cd4d24309b30d61f79f4a9114b7b2313d7450912277855ff5dfd7cd4a06", size = 46555059 } +sdist = { url = "https://files.pythonhosted.org/packages/a5/26/0d95c04c868f6bdb0c447e3ee2de5564411845e36a858cfd63766bc7b563/pillow-11.0.0.tar.gz", hash = "sha256:72bacbaf24ac003fea9bff9837d1eedb6088758d41e100c1552930151f677739", size = 46737780 } wheels = [ - { url = "https://files.pythonhosted.org/packages/0e/69/a31cccd538ca0b5272be2a38347f8839b97a14be104ea08b0db92f749c74/pillow-10.4.0-cp310-cp310-macosx_10_10_x86_64.whl", hash = "sha256:4d9667937cfa347525b319ae34375c37b9ee6b525440f3ef48542fcf66f2731e", size = 3509271 }, - { url = "https://files.pythonhosted.org/packages/9a/9e/4143b907be8ea0bce215f2ae4f7480027473f8b61fcedfda9d851082a5d2/pillow-10.4.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:543f3dc61c18dafb755773efc89aae60d06b6596a63914107f75459cf984164d", size = 3375658 }, - { url = "https://files.pythonhosted.org/packages/8a/25/1fc45761955f9359b1169aa75e241551e74ac01a09f487adaaf4c3472d11/pillow-10.4.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7928ecbf1ece13956b95d9cbcfc77137652b02763ba384d9ab508099a2eca856", size = 4332075 }, - { url = "https://files.pythonhosted.org/packages/5e/dd/425b95d0151e1d6c951f45051112394f130df3da67363b6bc75dc4c27aba/pillow-10.4.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e4d49b85c4348ea0b31ea63bc75a9f3857869174e2bf17e7aba02945cd218e6f", size = 4444808 }, - { url = "https://files.pythonhosted.org/packages/b1/84/9a15cc5726cbbfe7f9f90bfb11f5d028586595907cd093815ca6644932e3/pillow-10.4.0-cp310-cp310-manylinux_2_28_aarch64.whl", hash = "sha256:6c762a5b0997f5659a5ef2266abc1d8851ad7749ad9a6a5506eb23d314e4f46b", size = 4356290 }, - { url = "https://files.pythonhosted.org/packages/b5/5b/6651c288b08df3b8c1e2f8c1152201e0b25d240e22ddade0f1e242fc9fa0/pillow-10.4.0-cp310-cp310-manylinux_2_28_x86_64.whl", hash = "sha256:a985e028fc183bf12a77a8bbf36318db4238a3ded7fa9df1b9a133f1cb79f8fc", size = 4525163 }, - { url = "https://files.pythonhosted.org/packages/07/8b/34854bf11a83c248505c8cb0fcf8d3d0b459a2246c8809b967963b6b12ae/pillow-10.4.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:812f7342b0eee081eaec84d91423d1b4650bb9828eb53d8511bcef8ce5aecf1e", size = 4463100 }, - { url = "https://files.pythonhosted.org/packages/78/63/0632aee4e82476d9cbe5200c0cdf9ba41ee04ed77887432845264d81116d/pillow-10.4.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:ac1452d2fbe4978c2eec89fb5a23b8387aba707ac72810d9490118817d9c0b46", size = 4592880 }, - { url = "https://files.pythonhosted.org/packages/df/56/b8663d7520671b4398b9d97e1ed9f583d4afcbefbda3c6188325e8c297bd/pillow-10.4.0-cp310-cp310-win32.whl", hash = "sha256:bcd5e41a859bf2e84fdc42f4edb7d9aba0a13d29a2abadccafad99de3feff984", size = 2235218 }, - { url = "https://files.pythonhosted.org/packages/f4/72/0203e94a91ddb4a9d5238434ae6c1ca10e610e8487036132ea9bf806ca2a/pillow-10.4.0-cp310-cp310-win_amd64.whl", hash = "sha256:ecd85a8d3e79cd7158dec1c9e5808e821feea088e2f69a974db5edf84dc53141", size = 2554487 }, - { url = "https://files.pythonhosted.org/packages/bd/52/7e7e93d7a6e4290543f17dc6f7d3af4bd0b3dd9926e2e8a35ac2282bc5f4/pillow-10.4.0-cp310-cp310-win_arm64.whl", hash = "sha256:ff337c552345e95702c5fde3158acb0625111017d0e5f24bf3acdb9cc16b90d1", size = 2243219 }, - { url = "https://files.pythonhosted.org/packages/a7/62/c9449f9c3043c37f73e7487ec4ef0c03eb9c9afc91a92b977a67b3c0bbc5/pillow-10.4.0-cp311-cp311-macosx_10_10_x86_64.whl", hash = "sha256:0a9ec697746f268507404647e531e92889890a087e03681a3606d9b920fbee3c", size = 3509265 }, - { url = "https://files.pythonhosted.org/packages/f4/5f/491dafc7bbf5a3cc1845dc0430872e8096eb9e2b6f8161509d124594ec2d/pillow-10.4.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:dfe91cb65544a1321e631e696759491ae04a2ea11d36715eca01ce07284738be", size = 3375655 }, - { url = "https://files.pythonhosted.org/packages/73/d5/c4011a76f4207a3c151134cd22a1415741e42fa5ddecec7c0182887deb3d/pillow-10.4.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5dc6761a6efc781e6a1544206f22c80c3af4c8cf461206d46a1e6006e4429ff3", size = 4340304 }, - { url = "https://files.pythonhosted.org/packages/ac/10/c67e20445a707f7a610699bba4fe050583b688d8cd2d202572b257f46600/pillow-10.4.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5e84b6cc6a4a3d76c153a6b19270b3526a5a8ed6b09501d3af891daa2a9de7d6", size = 4452804 }, - { url = "https://files.pythonhosted.org/packages/a9/83/6523837906d1da2b269dee787e31df3b0acb12e3d08f024965a3e7f64665/pillow-10.4.0-cp311-cp311-manylinux_2_28_aarch64.whl", hash = "sha256:bbc527b519bd3aa9d7f429d152fea69f9ad37c95f0b02aebddff592688998abe", size = 4365126 }, - { url = "https://files.pythonhosted.org/packages/ba/e5/8c68ff608a4203085158cff5cc2a3c534ec384536d9438c405ed6370d080/pillow-10.4.0-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:76a911dfe51a36041f2e756b00f96ed84677cdeb75d25c767f296c1c1eda1319", size = 4533541 }, - { url = "https://files.pythonhosted.org/packages/f4/7c/01b8dbdca5bc6785573f4cee96e2358b0918b7b2c7b60d8b6f3abf87a070/pillow-10.4.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:59291fb29317122398786c2d44427bbd1a6d7ff54017075b22be9d21aa59bd8d", size = 4471616 }, - { url = "https://files.pythonhosted.org/packages/c8/57/2899b82394a35a0fbfd352e290945440e3b3785655a03365c0ca8279f351/pillow-10.4.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:416d3a5d0e8cfe4f27f574362435bc9bae57f679a7158e0096ad2beb427b8696", size = 4600802 }, - { url = "https://files.pythonhosted.org/packages/4d/d7/a44f193d4c26e58ee5d2d9db3d4854b2cfb5b5e08d360a5e03fe987c0086/pillow-10.4.0-cp311-cp311-win32.whl", hash = "sha256:7086cc1d5eebb91ad24ded9f58bec6c688e9f0ed7eb3dbbf1e4800280a896496", size = 2235213 }, - { url = "https://files.pythonhosted.org/packages/c1/d0/5866318eec2b801cdb8c82abf190c8343d8a1cd8bf5a0c17444a6f268291/pillow-10.4.0-cp311-cp311-win_amd64.whl", hash = "sha256:cbed61494057c0f83b83eb3a310f0bf774b09513307c434d4366ed64f4128a91", size = 2554498 }, - { url = "https://files.pythonhosted.org/packages/d4/c8/310ac16ac2b97e902d9eb438688de0d961660a87703ad1561fd3dfbd2aa0/pillow-10.4.0-cp311-cp311-win_arm64.whl", hash = "sha256:f5f0c3e969c8f12dd2bb7e0b15d5c468b51e5017e01e2e867335c81903046a22", size = 2243219 }, - { url = "https://files.pythonhosted.org/packages/05/cb/0353013dc30c02a8be34eb91d25e4e4cf594b59e5a55ea1128fde1e5f8ea/pillow-10.4.0-cp312-cp312-macosx_10_10_x86_64.whl", hash = "sha256:673655af3eadf4df6b5457033f086e90299fdd7a47983a13827acf7459c15d94", size = 3509350 }, - { url = "https://files.pythonhosted.org/packages/e7/cf/5c558a0f247e0bf9cec92bff9b46ae6474dd736f6d906315e60e4075f737/pillow-10.4.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:866b6942a92f56300012f5fbac71f2d610312ee65e22f1aa2609e491284e5597", size = 3374980 }, - { url = "https://files.pythonhosted.org/packages/84/48/6e394b86369a4eb68b8a1382c78dc092245af517385c086c5094e3b34428/pillow-10.4.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:29dbdc4207642ea6aad70fbde1a9338753d33fb23ed6956e706936706f52dd80", size = 4343799 }, - { url = "https://files.pythonhosted.org/packages/3b/f3/a8c6c11fa84b59b9df0cd5694492da8c039a24cd159f0f6918690105c3be/pillow-10.4.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bf2342ac639c4cf38799a44950bbc2dfcb685f052b9e262f446482afaf4bffca", size = 4459973 }, - { url = "https://files.pythonhosted.org/packages/7d/1b/c14b4197b80150fb64453585247e6fb2e1d93761fa0fa9cf63b102fde822/pillow-10.4.0-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:f5b92f4d70791b4a67157321c4e8225d60b119c5cc9aee8ecf153aace4aad4ef", size = 4370054 }, - { url = "https://files.pythonhosted.org/packages/55/77/40daddf677897a923d5d33329acd52a2144d54a9644f2a5422c028c6bf2d/pillow-10.4.0-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:86dcb5a1eb778d8b25659d5e4341269e8590ad6b4e8b44d9f4b07f8d136c414a", size = 4539484 }, - { url = "https://files.pythonhosted.org/packages/40/54/90de3e4256b1207300fb2b1d7168dd912a2fb4b2401e439ba23c2b2cabde/pillow-10.4.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:780c072c2e11c9b2c7ca37f9a2ee8ba66f44367ac3e5c7832afcfe5104fd6d1b", size = 4477375 }, - { url = "https://files.pythonhosted.org/packages/13/24/1bfba52f44193860918ff7c93d03d95e3f8748ca1de3ceaf11157a14cf16/pillow-10.4.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:37fb69d905be665f68f28a8bba3c6d3223c8efe1edf14cc4cfa06c241f8c81d9", size = 4608773 }, - { url = "https://files.pythonhosted.org/packages/55/04/5e6de6e6120451ec0c24516c41dbaf80cce1b6451f96561235ef2429da2e/pillow-10.4.0-cp312-cp312-win32.whl", hash = "sha256:7dfecdbad5c301d7b5bde160150b4db4c659cee2b69589705b6f8a0c509d9f42", size = 2235690 }, - { url = "https://files.pythonhosted.org/packages/74/0a/d4ce3c44bca8635bd29a2eab5aa181b654a734a29b263ca8efe013beea98/pillow-10.4.0-cp312-cp312-win_amd64.whl", hash = "sha256:1d846aea995ad352d4bdcc847535bd56e0fd88d36829d2c90be880ef1ee4668a", size = 2554951 }, - { url = "https://files.pythonhosted.org/packages/b5/ca/184349ee40f2e92439be9b3502ae6cfc43ac4b50bc4fc6b3de7957563894/pillow-10.4.0-cp312-cp312-win_arm64.whl", hash = "sha256:e553cad5179a66ba15bb18b353a19020e73a7921296a7979c4a2b7f6a5cd57f9", size = 2243427 }, - { url = "https://files.pythonhosted.org/packages/c3/00/706cebe7c2c12a6318aabe5d354836f54adff7156fd9e1bd6c89f4ba0e98/pillow-10.4.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:8bc1a764ed8c957a2e9cacf97c8b2b053b70307cf2996aafd70e91a082e70df3", size = 3525685 }, - { url = "https://files.pythonhosted.org/packages/cf/76/f658cbfa49405e5ecbfb9ba42d07074ad9792031267e782d409fd8fe7c69/pillow-10.4.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:6209bb41dc692ddfee4942517c19ee81b86c864b626dbfca272ec0f7cff5d9fb", size = 3374883 }, - { url = "https://files.pythonhosted.org/packages/46/2b/99c28c4379a85e65378211971c0b430d9c7234b1ec4d59b2668f6299e011/pillow-10.4.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bee197b30783295d2eb680b311af15a20a8b24024a19c3a26431ff83eb8d1f70", size = 4339837 }, - { url = "https://files.pythonhosted.org/packages/f1/74/b1ec314f624c0c43711fdf0d8076f82d9d802afd58f1d62c2a86878e8615/pillow-10.4.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1ef61f5dd14c300786318482456481463b9d6b91ebe5ef12f405afbba77ed0be", size = 4455562 }, - { url = "https://files.pythonhosted.org/packages/4a/2a/4b04157cb7b9c74372fa867096a1607e6fedad93a44deeff553ccd307868/pillow-10.4.0-cp313-cp313-manylinux_2_28_aarch64.whl", hash = "sha256:297e388da6e248c98bc4a02e018966af0c5f92dfacf5a5ca22fa01cb3179bca0", size = 4366761 }, - { url = "https://files.pythonhosted.org/packages/ac/7b/8f1d815c1a6a268fe90481232c98dd0e5fa8c75e341a75f060037bd5ceae/pillow-10.4.0-cp313-cp313-manylinux_2_28_x86_64.whl", hash = "sha256:e4db64794ccdf6cb83a59d73405f63adbe2a1887012e308828596100a0b2f6cc", size = 4536767 }, - { url = "https://files.pythonhosted.org/packages/e5/77/05fa64d1f45d12c22c314e7b97398ffb28ef2813a485465017b7978b3ce7/pillow-10.4.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:bd2880a07482090a3bcb01f4265f1936a903d70bc740bfcb1fd4e8a2ffe5cf5a", size = 4477989 }, - { url = "https://files.pythonhosted.org/packages/12/63/b0397cfc2caae05c3fb2f4ed1b4fc4fc878f0243510a7a6034ca59726494/pillow-10.4.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:4b35b21b819ac1dbd1233317adeecd63495f6babf21b7b2512d244ff6c6ce309", size = 4610255 }, - { url = "https://files.pythonhosted.org/packages/7b/f9/cfaa5082ca9bc4a6de66ffe1c12c2d90bf09c309a5f52b27759a596900e7/pillow-10.4.0-cp313-cp313-win32.whl", hash = "sha256:551d3fd6e9dc15e4c1eb6fc4ba2b39c0c7933fa113b220057a34f4bb3268a060", size = 2235603 }, - { url = "https://files.pythonhosted.org/packages/01/6a/30ff0eef6e0c0e71e55ded56a38d4859bf9d3634a94a88743897b5f96936/pillow-10.4.0-cp313-cp313-win_amd64.whl", hash = "sha256:030abdbe43ee02e0de642aee345efa443740aa4d828bfe8e2eb11922ea6a21ea", size = 2554972 }, - { url = "https://files.pythonhosted.org/packages/48/2c/2e0a52890f269435eee38b21c8218e102c621fe8d8df8b9dd06fabf879ba/pillow-10.4.0-cp313-cp313-win_arm64.whl", hash = "sha256:5b001114dd152cfd6b23befeb28d7aee43553e2402c9f159807bf55f33af8a8d", size = 2243375 }, - { url = "https://files.pythonhosted.org/packages/38/30/095d4f55f3a053392f75e2eae45eba3228452783bab3d9a920b951ac495c/pillow-10.4.0-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:5b4815f2e65b30f5fbae9dfffa8636d992d49705723fe86a3661806e069352d4", size = 3493889 }, - { url = "https://files.pythonhosted.org/packages/f3/e8/4ff79788803a5fcd5dc35efdc9386af153569853767bff74540725b45863/pillow-10.4.0-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:8f0aef4ef59694b12cadee839e2ba6afeab89c0f39a3adc02ed51d109117b8da", size = 3346160 }, - { url = "https://files.pythonhosted.org/packages/d7/ac/4184edd511b14f760c73f5bb8a5d6fd85c591c8aff7c2229677a355c4179/pillow-10.4.0-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9f4727572e2918acaa9077c919cbbeb73bd2b3ebcfe033b72f858fc9fbef0026", size = 3435020 }, - { url = "https://files.pythonhosted.org/packages/da/21/1749cd09160149c0a246a81d646e05f35041619ce76f6493d6a96e8d1103/pillow-10.4.0-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ff25afb18123cea58a591ea0244b92eb1e61a1fd497bf6d6384f09bc3262ec3e", size = 3490539 }, - { url = "https://files.pythonhosted.org/packages/b6/f5/f71fe1888b96083b3f6dfa0709101f61fc9e972c0c8d04e9d93ccef2a045/pillow-10.4.0-pp310-pypy310_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:dc3e2db6ba09ffd7d02ae9141cfa0ae23393ee7687248d46a7507b75d610f4f5", size = 3476125 }, - { url = "https://files.pythonhosted.org/packages/96/b9/c0362c54290a31866c3526848583a2f45a535aa9d725fd31e25d318c805f/pillow-10.4.0-pp310-pypy310_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:02a2be69f9c9b8c1e97cf2713e789d4e398c751ecfd9967c18d0ce304efbf885", size = 3579373 }, - { url = "https://files.pythonhosted.org/packages/52/3b/ce7a01026a7cf46e5452afa86f97a5e88ca97f562cafa76570178ab56d8d/pillow-10.4.0-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:0755ffd4a0c6f267cccbae2e9903d95477ca2f77c4fcf3a3a09570001856c8a5", size = 2554661 }, + { url = "https://files.pythonhosted.org/packages/98/fb/a6ce6836bd7fd93fbf9144bf54789e02babc27403b50a9e1583ee877d6da/pillow-11.0.0-cp310-cp310-macosx_10_10_x86_64.whl", hash = "sha256:6619654954dc4936fcff82db8eb6401d3159ec6be81e33c6000dfd76ae189947", size = 3154708 }, + { url = "https://files.pythonhosted.org/packages/6a/1d/1f51e6e912d8ff316bb3935a8cda617c801783e0b998bf7a894e91d3bd4c/pillow-11.0.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:b3c5ac4bed7519088103d9450a1107f76308ecf91d6dabc8a33a2fcfb18d0fba", size = 2979223 }, + { url = "https://files.pythonhosted.org/packages/90/83/e2077b0192ca8a9ef794dbb74700c7e48384706467067976c2a95a0f40a1/pillow-11.0.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a65149d8ada1055029fcb665452b2814fe7d7082fcb0c5bed6db851cb69b2086", size = 4183167 }, + { url = "https://files.pythonhosted.org/packages/0e/74/467af0146970a98349cdf39e9b79a6cc8a2e7558f2c01c28a7b6b85c5bda/pillow-11.0.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:88a58d8ac0cc0e7f3a014509f0455248a76629ca9b604eca7dc5927cc593c5e9", size = 4283912 }, + { url = "https://files.pythonhosted.org/packages/85/b1/d95d4f7ca3a6c1ae120959605875a31a3c209c4e50f0029dc1a87566cf46/pillow-11.0.0-cp310-cp310-manylinux_2_28_aarch64.whl", hash = "sha256:c26845094b1af3c91852745ae78e3ea47abf3dbcd1cf962f16b9a5fbe3ee8488", size = 4195815 }, + { url = "https://files.pythonhosted.org/packages/41/c3/94f33af0762ed76b5a237c5797e088aa57f2b7fa8ee7932d399087be66a8/pillow-11.0.0-cp310-cp310-manylinux_2_28_x86_64.whl", hash = "sha256:1a61b54f87ab5786b8479f81c4b11f4d61702830354520837f8cc791ebba0f5f", size = 4366117 }, + { url = "https://files.pythonhosted.org/packages/ba/3c/443e7ef01f597497268899e1cca95c0de947c9bbf77a8f18b3c126681e5d/pillow-11.0.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:674629ff60030d144b7bca2b8330225a9b11c482ed408813924619c6f302fdbb", size = 4278607 }, + { url = "https://files.pythonhosted.org/packages/26/95/1495304448b0081e60c0c5d63f928ef48bb290acee7385804426fa395a21/pillow-11.0.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:598b4e238f13276e0008299bd2482003f48158e2b11826862b1eb2ad7c768b97", size = 4410685 }, + { url = "https://files.pythonhosted.org/packages/45/da/861e1df971ef0de9870720cb309ca4d553b26a9483ec9be3a7bf1de4a095/pillow-11.0.0-cp310-cp310-win32.whl", hash = "sha256:9a0f748eaa434a41fccf8e1ee7a3eed68af1b690e75328fd7a60af123c193b50", size = 2249185 }, + { url = "https://files.pythonhosted.org/packages/d5/4e/78f7c5202ea2a772a5ab05069c1b82503e6353cd79c7e474d4945f4b82c3/pillow-11.0.0-cp310-cp310-win_amd64.whl", hash = "sha256:a5629742881bcbc1f42e840af185fd4d83a5edeb96475a575f4da50d6ede337c", size = 2566726 }, + { url = "https://files.pythonhosted.org/packages/77/e4/6e84eada35cbcc646fc1870f72ccfd4afacb0fae0c37ffbffe7f5dc24bf1/pillow-11.0.0-cp310-cp310-win_arm64.whl", hash = "sha256:ee217c198f2e41f184f3869f3e485557296d505b5195c513b2bfe0062dc537f1", size = 2254585 }, + { url = "https://files.pythonhosted.org/packages/f0/eb/f7e21b113dd48a9c97d364e0915b3988c6a0b6207652f5a92372871b7aa4/pillow-11.0.0-cp311-cp311-macosx_10_10_x86_64.whl", hash = "sha256:1c1d72714f429a521d8d2d018badc42414c3077eb187a59579f28e4270b4b0fc", size = 3154705 }, + { url = "https://files.pythonhosted.org/packages/25/b3/2b54a1d541accebe6bd8b1358b34ceb2c509f51cb7dcda8687362490da5b/pillow-11.0.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:499c3a1b0d6fc8213519e193796eb1a86a1be4b1877d678b30f83fd979811d1a", size = 2979222 }, + { url = "https://files.pythonhosted.org/packages/20/12/1a41eddad8265c5c19dda8fb6c269ce15ee25e0b9f8f26286e6202df6693/pillow-11.0.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c8b2351c85d855293a299038e1f89db92a2f35e8d2f783489c6f0b2b5f3fe8a3", size = 4190220 }, + { url = "https://files.pythonhosted.org/packages/a9/9b/8a8c4d07d77447b7457164b861d18f5a31ae6418ef5c07f6f878fa09039a/pillow-11.0.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6f4dba50cfa56f910241eb7f883c20f1e7b1d8f7d91c750cd0b318bad443f4d5", size = 4291399 }, + { url = "https://files.pythonhosted.org/packages/fc/e4/130c5fab4a54d3991129800dd2801feeb4b118d7630148cd67f0e6269d4c/pillow-11.0.0-cp311-cp311-manylinux_2_28_aarch64.whl", hash = "sha256:5ddbfd761ee00c12ee1be86c9c0683ecf5bb14c9772ddbd782085779a63dd55b", size = 4202709 }, + { url = "https://files.pythonhosted.org/packages/39/63/b3fc299528d7df1f678b0666002b37affe6b8751225c3d9c12cf530e73ed/pillow-11.0.0-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:45c566eb10b8967d71bf1ab8e4a525e5a93519e29ea071459ce517f6b903d7fa", size = 4372556 }, + { url = "https://files.pythonhosted.org/packages/c6/a6/694122c55b855b586c26c694937d36bb8d3b09c735ff41b2f315c6e66a10/pillow-11.0.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:b4fd7bd29610a83a8c9b564d457cf5bd92b4e11e79a4ee4716a63c959699b306", size = 4287187 }, + { url = "https://files.pythonhosted.org/packages/ba/a9/f9d763e2671a8acd53d29b1e284ca298bc10a595527f6be30233cdb9659d/pillow-11.0.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:cb929ca942d0ec4fac404cbf520ee6cac37bf35be479b970c4ffadf2b6a1cad9", size = 4418468 }, + { url = "https://files.pythonhosted.org/packages/6e/0e/b5cbad2621377f11313a94aeb44ca55a9639adabcaaa073597a1925f8c26/pillow-11.0.0-cp311-cp311-win32.whl", hash = "sha256:006bcdd307cc47ba43e924099a038cbf9591062e6c50e570819743f5607404f5", size = 2249249 }, + { url = "https://files.pythonhosted.org/packages/dc/83/1470c220a4ff06cd75fc609068f6605e567ea51df70557555c2ab6516b2c/pillow-11.0.0-cp311-cp311-win_amd64.whl", hash = "sha256:52a2d8323a465f84faaba5236567d212c3668f2ab53e1c74c15583cf507a0291", size = 2566769 }, + { url = "https://files.pythonhosted.org/packages/52/98/def78c3a23acee2bcdb2e52005fb2810ed54305602ec1bfcfab2bda6f49f/pillow-11.0.0-cp311-cp311-win_arm64.whl", hash = "sha256:16095692a253047fe3ec028e951fa4221a1f3ed3d80c397e83541a3037ff67c9", size = 2254611 }, + { url = "https://files.pythonhosted.org/packages/1c/a3/26e606ff0b2daaf120543e537311fa3ae2eb6bf061490e4fea51771540be/pillow-11.0.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:d2c0a187a92a1cb5ef2c8ed5412dd8d4334272617f532d4ad4de31e0495bd923", size = 3147642 }, + { url = "https://files.pythonhosted.org/packages/4f/d5/1caabedd8863526a6cfa44ee7a833bd97f945dc1d56824d6d76e11731939/pillow-11.0.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:084a07ef0821cfe4858fe86652fffac8e187b6ae677e9906e192aafcc1b69903", size = 2978999 }, + { url = "https://files.pythonhosted.org/packages/d9/ff/5a45000826a1aa1ac6874b3ec5a856474821a1b59d838c4f6ce2ee518fe9/pillow-11.0.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8069c5179902dcdce0be9bfc8235347fdbac249d23bd90514b7a47a72d9fecf4", size = 4196794 }, + { url = "https://files.pythonhosted.org/packages/9d/21/84c9f287d17180f26263b5f5c8fb201de0f88b1afddf8a2597a5c9fe787f/pillow-11.0.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f02541ef64077f22bf4924f225c0fd1248c168f86e4b7abdedd87d6ebaceab0f", size = 4300762 }, + { url = "https://files.pythonhosted.org/packages/84/39/63fb87cd07cc541438b448b1fed467c4d687ad18aa786a7f8e67b255d1aa/pillow-11.0.0-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:fcb4621042ac4b7865c179bb972ed0da0218a076dc1820ffc48b1d74c1e37fe9", size = 4210468 }, + { url = "https://files.pythonhosted.org/packages/7f/42/6e0f2c2d5c60f499aa29be14f860dd4539de322cd8fb84ee01553493fb4d/pillow-11.0.0-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:00177a63030d612148e659b55ba99527803288cea7c75fb05766ab7981a8c1b7", size = 4381824 }, + { url = "https://files.pythonhosted.org/packages/31/69/1ef0fb9d2f8d2d114db982b78ca4eeb9db9a29f7477821e160b8c1253f67/pillow-11.0.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:8853a3bf12afddfdf15f57c4b02d7ded92c7a75a5d7331d19f4f9572a89c17e6", size = 4296436 }, + { url = "https://files.pythonhosted.org/packages/44/ea/dad2818c675c44f6012289a7c4f46068c548768bc6c7f4e8c4ae5bbbc811/pillow-11.0.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:3107c66e43bda25359d5ef446f59c497de2b5ed4c7fdba0894f8d6cf3822dafc", size = 4429714 }, + { url = "https://files.pythonhosted.org/packages/af/3a/da80224a6eb15bba7a0dcb2346e2b686bb9bf98378c0b4353cd88e62b171/pillow-11.0.0-cp312-cp312-win32.whl", hash = "sha256:86510e3f5eca0ab87429dd77fafc04693195eec7fd6a137c389c3eeb4cfb77c6", size = 2249631 }, + { url = "https://files.pythonhosted.org/packages/57/97/73f756c338c1d86bb802ee88c3cab015ad7ce4b838f8a24f16b676b1ac7c/pillow-11.0.0-cp312-cp312-win_amd64.whl", hash = "sha256:8ec4a89295cd6cd4d1058a5e6aec6bf51e0eaaf9714774e1bfac7cfc9051db47", size = 2567533 }, + { url = "https://files.pythonhosted.org/packages/0b/30/2b61876e2722374558b871dfbfcbe4e406626d63f4f6ed92e9c8e24cac37/pillow-11.0.0-cp312-cp312-win_arm64.whl", hash = "sha256:27a7860107500d813fcd203b4ea19b04babe79448268403172782754870dac25", size = 2254890 }, + { url = "https://files.pythonhosted.org/packages/63/24/e2e15e392d00fcf4215907465d8ec2a2f23bcec1481a8ebe4ae760459995/pillow-11.0.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:bcd1fb5bb7b07f64c15618c89efcc2cfa3e95f0e3bcdbaf4642509de1942a699", size = 3147300 }, + { url = "https://files.pythonhosted.org/packages/43/72/92ad4afaa2afc233dc44184adff289c2e77e8cd916b3ddb72ac69495bda3/pillow-11.0.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:0e038b0745997c7dcaae350d35859c9715c71e92ffb7e0f4a8e8a16732150f38", size = 2978742 }, + { url = "https://files.pythonhosted.org/packages/9e/da/c8d69c5bc85d72a8523fe862f05ababdc52c0a755cfe3d362656bb86552b/pillow-11.0.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0ae08bd8ffc41aebf578c2af2f9d8749d91f448b3bfd41d7d9ff573d74f2a6b2", size = 4194349 }, + { url = "https://files.pythonhosted.org/packages/cd/e8/686d0caeed6b998351d57796496a70185376ed9c8ec7d99e1d19ad591fc6/pillow-11.0.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d69bfd8ec3219ae71bcde1f942b728903cad25fafe3100ba2258b973bd2bc1b2", size = 4298714 }, + { url = "https://files.pythonhosted.org/packages/ec/da/430015cec620d622f06854be67fd2f6721f52fc17fca8ac34b32e2d60739/pillow-11.0.0-cp313-cp313-manylinux_2_28_aarch64.whl", hash = "sha256:61b887f9ddba63ddf62fd02a3ba7add935d053b6dd7d58998c630e6dbade8527", size = 4208514 }, + { url = "https://files.pythonhosted.org/packages/44/ae/7e4f6662a9b1cb5f92b9cc9cab8321c381ffbee309210940e57432a4063a/pillow-11.0.0-cp313-cp313-manylinux_2_28_x86_64.whl", hash = "sha256:c6a660307ca9d4867caa8d9ca2c2658ab685de83792d1876274991adec7b93fa", size = 4380055 }, + { url = "https://files.pythonhosted.org/packages/74/d5/1a807779ac8a0eeed57f2b92a3c32ea1b696e6140c15bd42eaf908a261cd/pillow-11.0.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:73e3a0200cdda995c7e43dd47436c1548f87a30bb27fb871f352a22ab8dcf45f", size = 4296751 }, + { url = "https://files.pythonhosted.org/packages/38/8c/5fa3385163ee7080bc13026d59656267daaaaf3c728c233d530e2c2757c8/pillow-11.0.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:fba162b8872d30fea8c52b258a542c5dfd7b235fb5cb352240c8d63b414013eb", size = 4430378 }, + { url = "https://files.pythonhosted.org/packages/ca/1d/ad9c14811133977ff87035bf426875b93097fb50af747793f013979facdb/pillow-11.0.0-cp313-cp313-win32.whl", hash = "sha256:f1b82c27e89fffc6da125d5eb0ca6e68017faf5efc078128cfaa42cf5cb38798", size = 2249588 }, + { url = "https://files.pythonhosted.org/packages/fb/01/3755ba287dac715e6afdb333cb1f6d69740a7475220b4637b5ce3d78cec2/pillow-11.0.0-cp313-cp313-win_amd64.whl", hash = "sha256:8ba470552b48e5835f1d23ecb936bb7f71d206f9dfeee64245f30c3270b994de", size = 2567509 }, + { url = "https://files.pythonhosted.org/packages/c0/98/2c7d727079b6be1aba82d195767d35fcc2d32204c7a5820f822df5330152/pillow-11.0.0-cp313-cp313-win_arm64.whl", hash = "sha256:846e193e103b41e984ac921b335df59195356ce3f71dcfd155aa79c603873b84", size = 2254791 }, + { url = "https://files.pythonhosted.org/packages/eb/38/998b04cc6f474e78b563716b20eecf42a2fa16a84589d23c8898e64b0ffd/pillow-11.0.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:4ad70c4214f67d7466bea6a08061eba35c01b1b89eaa098040a35272a8efb22b", size = 3150854 }, + { url = "https://files.pythonhosted.org/packages/13/8e/be23a96292113c6cb26b2aa3c8b3681ec62b44ed5c2bd0b258bd59503d3c/pillow-11.0.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:6ec0d5af64f2e3d64a165f490d96368bb5dea8b8f9ad04487f9ab60dc4bb6003", size = 2982369 }, + { url = "https://files.pythonhosted.org/packages/97/8a/3db4eaabb7a2ae8203cd3a332a005e4aba00067fc514aaaf3e9721be31f1/pillow-11.0.0-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c809a70e43c7977c4a42aefd62f0131823ebf7dd73556fa5d5950f5b354087e2", size = 4333703 }, + { url = "https://files.pythonhosted.org/packages/28/ac/629ffc84ff67b9228fe87a97272ab125bbd4dc462745f35f192d37b822f1/pillow-11.0.0-cp313-cp313t-manylinux_2_28_x86_64.whl", hash = "sha256:4b60c9520f7207aaf2e1d94de026682fc227806c6e1f55bba7606d1c94dd623a", size = 4412550 }, + { url = "https://files.pythonhosted.org/packages/d6/07/a505921d36bb2df6868806eaf56ef58699c16c388e378b0dcdb6e5b2fb36/pillow-11.0.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:1e2688958a840c822279fda0086fec1fdab2f95bf2b717b66871c4ad9859d7e8", size = 4461038 }, + { url = "https://files.pythonhosted.org/packages/d6/b9/fb620dd47fc7cc9678af8f8bd8c772034ca4977237049287e99dda360b66/pillow-11.0.0-cp313-cp313t-win32.whl", hash = "sha256:607bbe123c74e272e381a8d1957083a9463401f7bd01287f50521ecb05a313f8", size = 2253197 }, + { url = "https://files.pythonhosted.org/packages/df/86/25dde85c06c89d7fc5db17940f07aae0a56ac69aa9ccb5eb0f09798862a8/pillow-11.0.0-cp313-cp313t-win_amd64.whl", hash = "sha256:5c39ed17edea3bc69c743a8dd3e9853b7509625c2462532e62baa0732163a904", size = 2572169 }, + { url = "https://files.pythonhosted.org/packages/51/85/9c33f2517add612e17f3381aee7c4072779130c634921a756c97bc29fb49/pillow-11.0.0-cp313-cp313t-win_arm64.whl", hash = "sha256:75acbbeb05b86bc53cbe7b7e6fe00fbcf82ad7c684b3ad82e3d711da9ba287d3", size = 2256828 }, + { url = "https://files.pythonhosted.org/packages/36/57/42a4dd825eab762ba9e690d696d894ba366e06791936056e26e099398cda/pillow-11.0.0-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:1187739620f2b365de756ce086fdb3604573337cc28a0d3ac4a01ab6b2d2a6d2", size = 3119239 }, + { url = "https://files.pythonhosted.org/packages/98/f7/25f9f9e368226a1d6cf3507081a1a7944eddd3ca7821023377043f5a83c8/pillow-11.0.0-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:fbbcb7b57dc9c794843e3d1258c0fbf0f48656d46ffe9e09b63bbd6e8cd5d0a2", size = 2950803 }, + { url = "https://files.pythonhosted.org/packages/59/01/98ead48a6c2e31e6185d4c16c978a67fe3ccb5da5c2ff2ba8475379bb693/pillow-11.0.0-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5d203af30149ae339ad1b4f710d9844ed8796e97fda23ffbc4cc472968a47d0b", size = 3281098 }, + { url = "https://files.pythonhosted.org/packages/51/c0/570255b2866a0e4d500a14f950803a2ec273bac7badc43320120b9262450/pillow-11.0.0-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:21a0d3b115009ebb8ac3d2ebec5c2982cc693da935f4ab7bb5c8ebe2f47d36f2", size = 3323665 }, + { url = "https://files.pythonhosted.org/packages/0e/75/689b4ec0483c42bfc7d1aacd32ade7a226db4f4fac57c6fdcdf90c0731e3/pillow-11.0.0-pp310-pypy310_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:73853108f56df97baf2bb8b522f3578221e56f646ba345a372c78326710d3830", size = 3310533 }, + { url = "https://files.pythonhosted.org/packages/3d/30/38bd6149cf53da1db4bad304c543ade775d225961c4310f30425995cb9ec/pillow-11.0.0-pp310-pypy310_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:e58876c91f97b0952eb766123bfef372792ab3f4e3e1f1a2267834c2ab131734", size = 3414886 }, + { url = "https://files.pythonhosted.org/packages/ec/3d/c32a51d848401bd94cabb8767a39621496491ee7cd5199856b77da9b18ad/pillow-11.0.0-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:224aaa38177597bb179f3ec87eeefcce8e4f85e608025e9cfac60de237ba6316", size = 2567508 }, ] [[package]] @@ -3104,27 +3050,9 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/c2/11/a8d4283b324cda992fbb72611c46c5c68f87902a10383dba1bde91660cc6/posthog-3.7.0-py2.py3-none-any.whl", hash = "sha256:3555161c3a9557b5666f96d8e1f17f410ea0f07db56e399e336a1656d4e5c722", size = 54359 }, ] -[[package]] -name = "pox" -version = "0.3.5" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/2e/0d/f2eb94b4d1358a60f3539a6abcbbd757fbcb78538fe8d4cfa49850356ccf/pox-0.3.5.tar.gz", hash = "sha256:8120ee4c94e950e6e0483e050a4f0e56076e590ba0a9add19524c254bd23c2d1", size = 119452 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/1d/4c/490d8f7825f38fa77bff188c568163f222d01f6c6d76f574429135edfc49/pox-0.3.5-py3-none-any.whl", hash = "sha256:9e82bcc9e578b43e80a99cad80f0d8f44f4d424f0ee4ee8d4db27260a6aa365a", size = 29492 }, -] - -[[package]] -name = "ppft" -version = "1.7.6.9" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/2b/06/305532df3e1b0c601f60854b6e080991835809d077934cf41976d0f224ce/ppft-1.7.6.9.tar.gz", hash = "sha256:73161c67474ea9d81d04bcdad166d399cff3f084d5d2dc21ebdd46c075bbc265", size = 136395 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/02/b3/45a04dabc39d93ad4836d99625e7c5350257b48e9ae2c5b701f3d5da6960/ppft-1.7.6.9-py3-none-any.whl", hash = "sha256:dab36548db5ca3055067fbe6b1a17db5fee29f3c366c579a9a27cebb52ed96f0", size = 56792 }, -] - [[package]] name = "pre-commit" -version = "3.8.0" +version = "4.0.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "cfgv" }, @@ -3133,9 +3061,9 @@ dependencies = [ { name = "pyyaml" }, { name = "virtualenv" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/64/10/97ee2fa54dff1e9da9badbc5e35d0bbaef0776271ea5907eccf64140f72f/pre_commit-3.8.0.tar.gz", hash = "sha256:8bb6494d4a20423842e198980c9ecf9f96607a07ea29549e180eef9ae80fe7af", size = 177815 } +sdist = { url = "https://files.pythonhosted.org/packages/2e/c8/e22c292035f1bac8b9f5237a2622305bc0304e776080b246f3df57c4ff9f/pre_commit-4.0.1.tar.gz", hash = "sha256:80905ac375958c0444c65e9cebebd948b3cdb518f335a091a670a89d652139d2", size = 191678 } wheels = [ - { url = "https://files.pythonhosted.org/packages/07/92/caae8c86e94681b42c246f0bca35c059a2f0529e5b92619f6aba4cf7e7b6/pre_commit-3.8.0-py2.py3-none-any.whl", hash = "sha256:9a90a53bf82fdd8778d58085faf8d83df56e40dfe18f45b19446e26bf1b3a63f", size = 204643 }, + { url = "https://files.pythonhosted.org/packages/16/8f/496e10d51edd6671ebe0432e33ff800aa86775d2d147ce7d43389324a525/pre_commit-4.0.1-py2.py3-none-any.whl", hash = "sha256:efde913840816312445dc98787724647c65473daefe420785f885e8ed9a06878", size = 218713 }, ] [[package]] @@ -3150,16 +3078,89 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/a9/6a/fd08d94654f7e67c52ca30523a178b3f8ccc4237fce4be90d39c938a831a/prompt_toolkit-3.0.48-py3-none-any.whl", hash = "sha256:f49a827f90062e411f1ce1f854f2aedb3c23353244f8108b89283587397ac10e", size = 386595 }, ] +[[package]] +name = "propcache" +version = "0.2.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/a9/4d/5e5a60b78dbc1d464f8a7bbaeb30957257afdc8512cbb9dfd5659304f5cd/propcache-0.2.0.tar.gz", hash = "sha256:df81779732feb9d01e5d513fad0122efb3d53bbc75f61b2a4f29a020bc985e70", size = 40951 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/3a/08/1963dfb932b8d74d5b09098507b37e9b96c835ba89ab8aad35aa330f4ff3/propcache-0.2.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:c5869b8fd70b81835a6f187c5fdbe67917a04d7e52b6e7cc4e5fe39d55c39d58", size = 80712 }, + { url = "https://files.pythonhosted.org/packages/e6/59/49072aba9bf8a8ed958e576182d46f038e595b17ff7408bc7e8807e721e1/propcache-0.2.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:952e0d9d07609d9c5be361f33b0d6d650cd2bae393aabb11d9b719364521984b", size = 46301 }, + { url = "https://files.pythonhosted.org/packages/33/a2/6b1978c2e0d80a678e2c483f45e5443c15fe5d32c483902e92a073314ef1/propcache-0.2.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:33ac8f098df0585c0b53009f039dfd913b38c1d2edafed0cedcc0c32a05aa110", size = 45581 }, + { url = "https://files.pythonhosted.org/packages/43/95/55acc9adff8f997c7572f23d41993042290dfb29e404cdadb07039a4386f/propcache-0.2.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:97e48e8875e6c13909c800fa344cd54cc4b2b0db1d5f911f840458a500fde2c2", size = 208659 }, + { url = "https://files.pythonhosted.org/packages/bd/2c/ef7371ff715e6cd19ea03fdd5637ecefbaa0752fee5b0f2fe8ea8407ee01/propcache-0.2.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:388f3217649d6d59292b722d940d4d2e1e6a7003259eb835724092a1cca0203a", size = 222613 }, + { url = "https://files.pythonhosted.org/packages/5e/1c/fef251f79fd4971a413fa4b1ae369ee07727b4cc2c71e2d90dfcde664fbb/propcache-0.2.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f571aea50ba5623c308aa146eb650eebf7dbe0fd8c5d946e28343cb3b5aad577", size = 221067 }, + { url = "https://files.pythonhosted.org/packages/8d/e7/22e76ae6fc5a1708bdce92bdb49de5ebe89a173db87e4ef597d6bbe9145a/propcache-0.2.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3dfafb44f7bb35c0c06eda6b2ab4bfd58f02729e7c4045e179f9a861b07c9850", size = 208920 }, + { url = "https://files.pythonhosted.org/packages/04/3e/f10aa562781bcd8a1e0b37683a23bef32bdbe501d9cc7e76969becaac30d/propcache-0.2.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a3ebe9a75be7ab0b7da2464a77bb27febcb4fab46a34f9288f39d74833db7f61", size = 200050 }, + { url = "https://files.pythonhosted.org/packages/d0/98/8ac69f638358c5f2a0043809c917802f96f86026e86726b65006830f3dc6/propcache-0.2.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:d2f0d0f976985f85dfb5f3d685697ef769faa6b71993b46b295cdbbd6be8cc37", size = 202346 }, + { url = "https://files.pythonhosted.org/packages/ee/78/4acfc5544a5075d8e660af4d4e468d60c418bba93203d1363848444511ad/propcache-0.2.0-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:a3dc1a4b165283bd865e8f8cb5f0c64c05001e0718ed06250d8cac9bec115b48", size = 199750 }, + { url = "https://files.pythonhosted.org/packages/a2/8f/90ada38448ca2e9cf25adc2fe05d08358bda1b9446f54a606ea38f41798b/propcache-0.2.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:9e0f07b42d2a50c7dd2d8675d50f7343d998c64008f1da5fef888396b7f84630", size = 201279 }, + { url = "https://files.pythonhosted.org/packages/08/31/0e299f650f73903da851f50f576ef09bfffc8e1519e6a2f1e5ed2d19c591/propcache-0.2.0-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:e63e3e1e0271f374ed489ff5ee73d4b6e7c60710e1f76af5f0e1a6117cd26394", size = 211035 }, + { url = "https://files.pythonhosted.org/packages/85/3e/e356cc6b09064bff1c06d0b2413593e7c925726f0139bc7acef8a21e87a8/propcache-0.2.0-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:56bb5c98f058a41bb58eead194b4db8c05b088c93d94d5161728515bd52b052b", size = 215565 }, + { url = "https://files.pythonhosted.org/packages/8b/54/4ef7236cd657e53098bd05aa59cbc3cbf7018fba37b40eaed112c3921e51/propcache-0.2.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:7665f04d0c7f26ff8bb534e1c65068409bf4687aa2534faf7104d7182debb336", size = 207604 }, + { url = "https://files.pythonhosted.org/packages/1f/27/d01d7799c068443ee64002f0655d82fb067496897bf74b632e28ee6a32cf/propcache-0.2.0-cp310-cp310-win32.whl", hash = "sha256:7cf18abf9764746b9c8704774d8b06714bcb0a63641518a3a89c7f85cc02c2ad", size = 40526 }, + { url = "https://files.pythonhosted.org/packages/bb/44/6c2add5eeafb7f31ff0d25fbc005d930bea040a1364cf0f5768750ddf4d1/propcache-0.2.0-cp310-cp310-win_amd64.whl", hash = "sha256:cfac69017ef97db2438efb854edf24f5a29fd09a536ff3a992b75990720cdc99", size = 44958 }, + { url = "https://files.pythonhosted.org/packages/e0/1c/71eec730e12aec6511e702ad0cd73c2872eccb7cad39de8ba3ba9de693ef/propcache-0.2.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:63f13bf09cc3336eb04a837490b8f332e0db41da66995c9fd1ba04552e516354", size = 80811 }, + { url = "https://files.pythonhosted.org/packages/89/c3/7e94009f9a4934c48a371632197406a8860b9f08e3f7f7d922ab69e57a41/propcache-0.2.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:608cce1da6f2672a56b24a015b42db4ac612ee709f3d29f27a00c943d9e851de", size = 46365 }, + { url = "https://files.pythonhosted.org/packages/c0/1d/c700d16d1d6903aeab28372fe9999762f074b80b96a0ccc953175b858743/propcache-0.2.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:466c219deee4536fbc83c08d09115249db301550625c7fef1c5563a584c9bc87", size = 45602 }, + { url = "https://files.pythonhosted.org/packages/2e/5e/4a3e96380805bf742712e39a4534689f4cddf5fa2d3a93f22e9fd8001b23/propcache-0.2.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fc2db02409338bf36590aa985a461b2c96fce91f8e7e0f14c50c5fcc4f229016", size = 236161 }, + { url = "https://files.pythonhosted.org/packages/a5/85/90132481183d1436dff6e29f4fa81b891afb6cb89a7306f32ac500a25932/propcache-0.2.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a6ed8db0a556343d566a5c124ee483ae113acc9a557a807d439bcecc44e7dfbb", size = 244938 }, + { url = "https://files.pythonhosted.org/packages/4a/89/c893533cb45c79c970834274e2d0f6d64383ec740be631b6a0a1d2b4ddc0/propcache-0.2.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:91997d9cb4a325b60d4e3f20967f8eb08dfcb32b22554d5ef78e6fd1dda743a2", size = 243576 }, + { url = "https://files.pythonhosted.org/packages/8c/56/98c2054c8526331a05f205bf45cbb2cda4e58e56df70e76d6a509e5d6ec6/propcache-0.2.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4c7dde9e533c0a49d802b4f3f218fa9ad0a1ce21f2c2eb80d5216565202acab4", size = 236011 }, + { url = "https://files.pythonhosted.org/packages/2d/0c/8b8b9f8a6e1abd869c0fa79b907228e7abb966919047d294ef5df0d136cf/propcache-0.2.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ffcad6c564fe6b9b8916c1aefbb37a362deebf9394bd2974e9d84232e3e08504", size = 224834 }, + { url = "https://files.pythonhosted.org/packages/18/bb/397d05a7298b7711b90e13108db697732325cafdcd8484c894885c1bf109/propcache-0.2.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:97a58a28bcf63284e8b4d7b460cbee1edaab24634e82059c7b8c09e65284f178", size = 224946 }, + { url = "https://files.pythonhosted.org/packages/25/19/4fc08dac19297ac58135c03770b42377be211622fd0147f015f78d47cd31/propcache-0.2.0-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:945db8ee295d3af9dbdbb698cce9bbc5c59b5c3fe328bbc4387f59a8a35f998d", size = 217280 }, + { url = "https://files.pythonhosted.org/packages/7e/76/c79276a43df2096ce2aba07ce47576832b1174c0c480fe6b04bd70120e59/propcache-0.2.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:39e104da444a34830751715f45ef9fc537475ba21b7f1f5b0f4d71a3b60d7fe2", size = 220088 }, + { url = "https://files.pythonhosted.org/packages/c3/9a/8a8cf428a91b1336b883f09c8b884e1734c87f724d74b917129a24fe2093/propcache-0.2.0-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:c5ecca8f9bab618340c8e848d340baf68bcd8ad90a8ecd7a4524a81c1764b3db", size = 233008 }, + { url = "https://files.pythonhosted.org/packages/25/7b/768a8969abd447d5f0f3333df85c6a5d94982a1bc9a89c53c154bf7a8b11/propcache-0.2.0-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:c436130cc779806bdf5d5fae0d848713105472b8566b75ff70048c47d3961c5b", size = 237719 }, + { url = "https://files.pythonhosted.org/packages/ed/0d/e5d68ccc7976ef8b57d80613ac07bbaf0614d43f4750cf953f0168ef114f/propcache-0.2.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:191db28dc6dcd29d1a3e063c3be0b40688ed76434622c53a284e5427565bbd9b", size = 227729 }, + { url = "https://files.pythonhosted.org/packages/05/64/17eb2796e2d1c3d0c431dc5f40078d7282f4645af0bb4da9097fbb628c6c/propcache-0.2.0-cp311-cp311-win32.whl", hash = "sha256:5f2564ec89058ee7c7989a7b719115bdfe2a2fb8e7a4543b8d1c0cc4cf6478c1", size = 40473 }, + { url = "https://files.pythonhosted.org/packages/83/c5/e89fc428ccdc897ade08cd7605f174c69390147526627a7650fb883e0cd0/propcache-0.2.0-cp311-cp311-win_amd64.whl", hash = "sha256:6e2e54267980349b723cff366d1e29b138b9a60fa376664a157a342689553f71", size = 44921 }, + { url = "https://files.pythonhosted.org/packages/7c/46/a41ca1097769fc548fc9216ec4c1471b772cc39720eb47ed7e38ef0006a9/propcache-0.2.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:2ee7606193fb267be4b2e3b32714f2d58cad27217638db98a60f9efb5efeccc2", size = 80800 }, + { url = "https://files.pythonhosted.org/packages/75/4f/93df46aab9cc473498ff56be39b5f6ee1e33529223d7a4d8c0a6101a9ba2/propcache-0.2.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:91ee8fc02ca52e24bcb77b234f22afc03288e1dafbb1f88fe24db308910c4ac7", size = 46443 }, + { url = "https://files.pythonhosted.org/packages/0b/17/308acc6aee65d0f9a8375e36c4807ac6605d1f38074b1581bd4042b9fb37/propcache-0.2.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:2e900bad2a8456d00a113cad8c13343f3b1f327534e3589acc2219729237a2e8", size = 45676 }, + { url = "https://files.pythonhosted.org/packages/65/44/626599d2854d6c1d4530b9a05e7ff2ee22b790358334b475ed7c89f7d625/propcache-0.2.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f52a68c21363c45297aca15561812d542f8fc683c85201df0bebe209e349f793", size = 246191 }, + { url = "https://files.pythonhosted.org/packages/f2/df/5d996d7cb18df076debae7d76ac3da085c0575a9f2be6b1f707fe227b54c/propcache-0.2.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1e41d67757ff4fbc8ef2af99b338bfb955010444b92929e9e55a6d4dcc3c4f09", size = 251791 }, + { url = "https://files.pythonhosted.org/packages/2e/6d/9f91e5dde8b1f662f6dd4dff36098ed22a1ef4e08e1316f05f4758f1576c/propcache-0.2.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a64e32f8bd94c105cc27f42d3b658902b5bcc947ece3c8fe7bc1b05982f60e89", size = 253434 }, + { url = "https://files.pythonhosted.org/packages/3c/e9/1b54b7e26f50b3e0497cd13d3483d781d284452c2c50dd2a615a92a087a3/propcache-0.2.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:55346705687dbd7ef0d77883ab4f6fabc48232f587925bdaf95219bae072491e", size = 248150 }, + { url = "https://files.pythonhosted.org/packages/a7/ef/a35bf191c8038fe3ce9a414b907371c81d102384eda5dbafe6f4dce0cf9b/propcache-0.2.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:00181262b17e517df2cd85656fcd6b4e70946fe62cd625b9d74ac9977b64d8d9", size = 233568 }, + { url = "https://files.pythonhosted.org/packages/97/d9/d00bb9277a9165a5e6d60f2142cd1a38a750045c9c12e47ae087f686d781/propcache-0.2.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:6994984550eaf25dd7fc7bd1b700ff45c894149341725bb4edc67f0ffa94efa4", size = 229874 }, + { url = "https://files.pythonhosted.org/packages/8e/78/c123cf22469bdc4b18efb78893e69c70a8b16de88e6160b69ca6bdd88b5d/propcache-0.2.0-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:56295eb1e5f3aecd516d91b00cfd8bf3a13991de5a479df9e27dd569ea23959c", size = 225857 }, + { url = "https://files.pythonhosted.org/packages/31/1b/fd6b2f1f36d028820d35475be78859d8c89c8f091ad30e377ac49fd66359/propcache-0.2.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:439e76255daa0f8151d3cb325f6dd4a3e93043e6403e6491813bcaaaa8733887", size = 227604 }, + { url = "https://files.pythonhosted.org/packages/99/36/b07be976edf77a07233ba712e53262937625af02154353171716894a86a6/propcache-0.2.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:f6475a1b2ecb310c98c28d271a30df74f9dd436ee46d09236a6b750a7599ce57", size = 238430 }, + { url = "https://files.pythonhosted.org/packages/0d/64/5822f496c9010e3966e934a011ac08cac8734561842bc7c1f65586e0683c/propcache-0.2.0-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:3444cdba6628accf384e349014084b1cacd866fbb88433cd9d279d90a54e0b23", size = 244814 }, + { url = "https://files.pythonhosted.org/packages/fd/bd/8657918a35d50b18a9e4d78a5df7b6c82a637a311ab20851eef4326305c1/propcache-0.2.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:4a9d9b4d0a9b38d1c391bb4ad24aa65f306c6f01b512e10a8a34a2dc5675d348", size = 235922 }, + { url = "https://files.pythonhosted.org/packages/a8/6f/ec0095e1647b4727db945213a9f395b1103c442ef65e54c62e92a72a3f75/propcache-0.2.0-cp312-cp312-win32.whl", hash = "sha256:69d3a98eebae99a420d4b28756c8ce6ea5a29291baf2dc9ff9414b42676f61d5", size = 40177 }, + { url = "https://files.pythonhosted.org/packages/20/a2/bd0896fdc4f4c1db46d9bc361c8c79a9bf08ccc08ba054a98e38e7ba1557/propcache-0.2.0-cp312-cp312-win_amd64.whl", hash = "sha256:ad9c9b99b05f163109466638bd30ada1722abb01bbb85c739c50b6dc11f92dc3", size = 44446 }, + { url = "https://files.pythonhosted.org/packages/a8/a7/5f37b69197d4f558bfef5b4bceaff7c43cc9b51adf5bd75e9081d7ea80e4/propcache-0.2.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:ecddc221a077a8132cf7c747d5352a15ed763b674c0448d811f408bf803d9ad7", size = 78120 }, + { url = "https://files.pythonhosted.org/packages/c8/cd/48ab2b30a6b353ecb95a244915f85756d74f815862eb2ecc7a518d565b48/propcache-0.2.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:0e53cb83fdd61cbd67202735e6a6687a7b491c8742dfc39c9e01e80354956763", size = 45127 }, + { url = "https://files.pythonhosted.org/packages/a5/ba/0a1ef94a3412aab057bd996ed5f0ac7458be5bf469e85c70fa9ceb43290b/propcache-0.2.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:92fe151145a990c22cbccf9ae15cae8ae9eddabfc949a219c9f667877e40853d", size = 44419 }, + { url = "https://files.pythonhosted.org/packages/b4/6c/ca70bee4f22fa99eacd04f4d2f1699be9d13538ccf22b3169a61c60a27fa/propcache-0.2.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d6a21ef516d36909931a2967621eecb256018aeb11fc48656e3257e73e2e247a", size = 229611 }, + { url = "https://files.pythonhosted.org/packages/19/70/47b872a263e8511ca33718d96a10c17d3c853aefadeb86dc26e8421184b9/propcache-0.2.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3f88a4095e913f98988f5b338c1d4d5d07dbb0b6bad19892fd447484e483ba6b", size = 234005 }, + { url = "https://files.pythonhosted.org/packages/4f/be/3b0ab8c84a22e4a3224719099c1229ddfdd8a6a1558cf75cb55ee1e35c25/propcache-0.2.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5a5b3bb545ead161be780ee85a2b54fdf7092815995661947812dde94a40f6fb", size = 237270 }, + { url = "https://files.pythonhosted.org/packages/04/d8/f071bb000d4b8f851d312c3c75701e586b3f643fe14a2e3409b1b9ab3936/propcache-0.2.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:67aeb72e0f482709991aa91345a831d0b707d16b0257e8ef88a2ad246a7280bf", size = 231877 }, + { url = "https://files.pythonhosted.org/packages/93/e7/57a035a1359e542bbb0a7df95aad6b9871ebee6dce2840cb157a415bd1f3/propcache-0.2.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3c997f8c44ec9b9b0bcbf2d422cc00a1d9b9c681f56efa6ca149a941e5560da2", size = 217848 }, + { url = "https://files.pythonhosted.org/packages/f0/93/d1dea40f112ec183398fb6c42fde340edd7bab202411c4aa1a8289f461b6/propcache-0.2.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:2a66df3d4992bc1d725b9aa803e8c5a66c010c65c741ad901e260ece77f58d2f", size = 216987 }, + { url = "https://files.pythonhosted.org/packages/62/4c/877340871251145d3522c2b5d25c16a1690ad655fbab7bb9ece6b117e39f/propcache-0.2.0-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:3ebbcf2a07621f29638799828b8d8668c421bfb94c6cb04269130d8de4fb7136", size = 212451 }, + { url = "https://files.pythonhosted.org/packages/7c/bb/a91b72efeeb42906ef58ccf0cdb87947b54d7475fee3c93425d732f16a61/propcache-0.2.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:1235c01ddaa80da8235741e80815ce381c5267f96cc49b1477fdcf8c047ef325", size = 212879 }, + { url = "https://files.pythonhosted.org/packages/9b/7f/ee7fea8faac57b3ec5d91ff47470c6c5d40d7f15d0b1fccac806348fa59e/propcache-0.2.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:3947483a381259c06921612550867b37d22e1df6d6d7e8361264b6d037595f44", size = 222288 }, + { url = "https://files.pythonhosted.org/packages/ff/d7/acd67901c43d2e6b20a7a973d9d5fd543c6e277af29b1eb0e1f7bd7ca7d2/propcache-0.2.0-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:d5bed7f9805cc29c780f3aee05de3262ee7ce1f47083cfe9f77471e9d6777e83", size = 228257 }, + { url = "https://files.pythonhosted.org/packages/8d/6f/6272ecc7a8daad1d0754cfc6c8846076a8cb13f810005c79b15ce0ef0cf2/propcache-0.2.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:e4a91d44379f45f5e540971d41e4626dacd7f01004826a18cb048e7da7e96544", size = 221075 }, + { url = "https://files.pythonhosted.org/packages/7c/bd/c7a6a719a6b3dd8b3aeadb3675b5783983529e4a3185946aa444d3e078f6/propcache-0.2.0-cp313-cp313-win32.whl", hash = "sha256:f902804113e032e2cdf8c71015651c97af6418363bea8d78dc0911d56c335032", size = 39654 }, + { url = "https://files.pythonhosted.org/packages/88/e7/0eef39eff84fa3e001b44de0bd41c7c0e3432e7648ffd3d64955910f002d/propcache-0.2.0-cp313-cp313-win_amd64.whl", hash = "sha256:8f188cfcc64fb1266f4684206c9de0e80f54622c3f22a910cbd200478aeae61e", size = 43705 }, + { url = "https://files.pythonhosted.org/packages/3d/b6/e6d98278f2d49b22b4d033c9f792eda783b9ab2094b041f013fc69bcde87/propcache-0.2.0-py3-none-any.whl", hash = "sha256:2ccc28197af5313706511fab3a8b66dcd6da067a1331372c82ea1cb74285e036", size = 11603 }, +] + [[package]] name = "proto-plus" -version = "1.24.0" +version = "1.25.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "protobuf" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/3e/fc/e9a65cd52c1330d8d23af6013651a0bc50b6d76bcbdf91fae7cd19c68f29/proto-plus-1.24.0.tar.gz", hash = "sha256:30b72a5ecafe4406b0d339db35b56c4059064e69227b8c3bda7462397f966445", size = 55942 } +sdist = { url = "https://files.pythonhosted.org/packages/7e/05/74417b2061e1bf1b82776037cad97094228fa1c1b6e82d08a78d3fb6ddb6/proto_plus-1.25.0.tar.gz", hash = "sha256:fbb17f57f7bd05a68b7707e745e26528b0b3c34e378db91eef93912c54982d91", size = 56124 } wheels = [ - { url = "https://files.pythonhosted.org/packages/7c/6f/db31f0711c0402aa477257205ce7d29e86a75cb52cd19f7afb585f75cda0/proto_plus-1.24.0-py3-none-any.whl", hash = "sha256:402576830425e5f6ce4c2a6702400ac79897dab0b4343821aa5188b0fab81a12", size = 50080 }, + { url = "https://files.pythonhosted.org/packages/dd/25/0b7cc838ae3d76d46539020ec39fc92bfc9acc29367e58fe912702c2a79e/proto_plus-1.25.0-py3-none-any.whl", hash = "sha256:c91fc4a65074ade8e458e95ef8bac34d4008daa7cce4a12d6707066fca648961", size = 50126 }, ] [[package]] @@ -3421,18 +3422,19 @@ wheels = [ [[package]] name = "pylance" -version = "0.9.18" +version = "0.18.2" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "numpy" }, { name = "pyarrow" }, ] wheels = [ - { url = "https://files.pythonhosted.org/packages/ca/b8/15d4d380f0858dde46d42891776017e3bf9eb40129b3fe222637eecf8f43/pylance-0.9.18-cp38-abi3-macosx_10_15_x86_64.whl", hash = "sha256:fe2445d922c594d90e89111385106f6b152caab27996217db7bb4b8947eb0bea", size = 20319043 }, - { url = "https://files.pythonhosted.org/packages/1f/f8/69f927a215d415362300d14a50b3cbc6575fd640ca5e632d488e022d3af1/pylance-0.9.18-cp38-abi3-macosx_11_0_arm64.whl", hash = "sha256:a2c424c50f5186edbbcc5a26f34063ed09d9a7390e28033395728ce02b5658f0", size = 18780426 }, - { url = "https://files.pythonhosted.org/packages/a1/b8/991e4544cfa21de2c7de5dd6bd8410df454fec5b374680fa96cd8698763b/pylance-0.9.18-cp38-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:10af06edfde3e8451bf2251381d3980a0a164eab9d4c3d4dc8b6318969e958a6", size = 21584420 }, - { url = "https://files.pythonhosted.org/packages/3c/5e/ff80f31d995315790393cbe599565f55d03eb717654cfeb65b701803e887/pylance-0.9.18-cp38-abi3-manylinux_2_24_aarch64.whl", hash = "sha256:d8bb9045d7163cc966b9fe34a917044192be37a90915475b77461e5b7d89e442", size = 19960982 }, - { url = "https://files.pythonhosted.org/packages/2d/e5/c0e0a6cad08ab86a9c0bce7e8caef8f666337bb7950e2ab151ea4f88242d/pylance-0.9.18-cp38-abi3-win_amd64.whl", hash = "sha256:5ea80b7bf70d992f3fe63bce2d2f064f742124c04eaedeb76baca408ded85a2c", size = 22089079 }, + { url = "https://files.pythonhosted.org/packages/25/0a/16ae3434c8747028b2adc14cf9e15982005168b173ffa7f181e62af78537/pylance-0.18.2-cp39-abi3-macosx_10_15_x86_64.whl", hash = "sha256:017422b058724dfbe8426c1ac42f0ede77324f3783e177cb4239dc034758b50b", size = 28268045 }, + { url = "https://files.pythonhosted.org/packages/da/9f/d8f6ed331d6d57b53616bdce1d88efe724335663ee3b6337f1412b104e42/pylance-0.18.2-cp39-abi3-macosx_11_0_arm64.whl", hash = "sha256:c4c4049eb6a6075cef721a20dd28ccba6d89b66f13e8d20ef65a284ae1c02e30", size = 26291690 }, + { url = "https://files.pythonhosted.org/packages/0a/1f/4e6df8eba3c9d78bea8c0713e07ae500d837247d9697c0612720d7f048c7/pylance-0.18.2-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:89dcf2dadee940ea86ac0b3bf7ba81c68e9774a449d8de206bc60cdc8804b853", size = 30065809 }, + { url = "https://files.pythonhosted.org/packages/05/c0/83519992d4a56989fc37fa4baf00ba8c5c8f3bea0cc83a85359751572d64/pylance-0.18.2-cp39-abi3-manylinux_2_24_aarch64.whl", hash = "sha256:f37fb7ad0e53076c731014c210a45919f3b2620c967e2f62cf8b7c26fdc9aace", size = 29243695 }, + { url = "https://files.pythonhosted.org/packages/05/40/648f74da0449699b40792b7b9d6db8aedc80fa4e25c61e1f75a8299ec8c5/pylance-0.18.2-cp39-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:a913920f591d8404c46c74e3911fe0c29d47b923b9c3c7e521d3354c1663d812", size = 30017919 }, + { url = "https://files.pythonhosted.org/packages/81/1b/9dcb3d95fd08b2a2ce7f972a3dce25551b29a9fd0e1ee22e39d8bec36b3e/pylance-0.18.2-cp39-abi3-win_amd64.whl", hash = "sha256:72796676d7647ba9f6e86531daf67880f5e69ba8f842e237ad0c1ca419c6378c", size = 28072707 }, ] [[package]] @@ -3486,15 +3488,15 @@ wheels = [ [[package]] name = "pyright" -version = "1.1.383" +version = "1.1.386" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "nodeenv" }, { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/78/a9/4654d15f4125d8dca6318d7be36a3283a8b3039661291c59bbdd1e576dcf/pyright-1.1.383.tar.gz", hash = "sha256:1df7f12407f3710c9c6df938d98ec53f70053e6c6bbf71ce7bcb038d42f10070", size = 21971 } +sdist = { url = "https://files.pythonhosted.org/packages/92/50/1a57054b5585fa72a93a6244c1b4b5639f8f7a1cc60b2e807cc67da8f0bc/pyright-1.1.386.tar.gz", hash = "sha256:8e9975e34948ba5f8e07792a9c9d2bdceb2c6c0b61742b068d2229ca2bc4a9d9", size = 21949 } wheels = [ - { url = "https://files.pythonhosted.org/packages/1c/55/40a6559cea209b551c81dcd31cb351a6ffdb5876e7865ee242e269af72d8/pyright-1.1.383-py3-none-any.whl", hash = "sha256:d864d1182a313f45aaf99e9bfc7d2668eeabc99b29a556b5344894fd73cb1959", size = 18577 }, + { url = "https://files.pythonhosted.org/packages/cc/68/47fd6b3ffa27c99d7e0c866c618f07784b8806712059049daa492ca7e526/pyright-1.1.386-py3-none-any.whl", hash = "sha256:7071ac495593b2258ccdbbf495f1a5c0e5f27951f6b429bed4e8b296eb5cd21d", size = 18577 }, ] [[package]] @@ -3561,8 +3563,8 @@ version = "1.0.2" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "pytest" }, - { name = "vcrpy", version = "5.1.0", source = { registry = "https://pypi.org/simple" }, marker = "platform_python_implementation == 'PyPy' or python_full_version >= '3.11'" }, - { name = "vcrpy", version = "6.0.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11' and platform_python_implementation != 'PyPy'" }, + { name = "vcrpy", version = "5.1.0", source = { registry = "https://pypi.org/simple" }, marker = "platform_python_implementation == 'PyPy' or python_full_version >= '3.12'" }, + { name = "vcrpy", version = "6.0.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.12' and platform_python_implementation != 'PyPy'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/1a/60/104c619483c1a42775d3f8b27293f1ecfc0728014874d065e68cb9702d49/pytest-vcr-1.0.2.tar.gz", hash = "sha256:23ee51b75abbcc43d926272773aae4f39f93aceb75ed56852d0bf618f92e1896", size = 3810 } wheels = [ @@ -3624,17 +3626,21 @@ wheels = [ [[package]] name = "pywin32" -version = "306" +version = "308" source = { registry = "https://pypi.org/simple" } wheels = [ - { url = "https://files.pythonhosted.org/packages/08/dc/28c668097edfaf4eac4617ef7adf081b9cf50d254672fcf399a70f5efc41/pywin32-306-cp310-cp310-win32.whl", hash = "sha256:06d3420a5155ba65f0b72f2699b5bacf3109f36acbe8923765c22938a69dfc8d", size = 8506422 }, - { url = "https://files.pythonhosted.org/packages/d3/d6/891894edec688e72c2e308b3243fad98b4066e1839fd2fe78f04129a9d31/pywin32-306-cp310-cp310-win_amd64.whl", hash = "sha256:84f4471dbca1887ea3803d8848a1616429ac94a4a8d05f4bc9c5dcfd42ca99c8", size = 9226392 }, - { url = "https://files.pythonhosted.org/packages/8b/1e/fc18ad83ca553e01b97aa8393ff10e33c1fb57801db05488b83282ee9913/pywin32-306-cp311-cp311-win32.whl", hash = "sha256:e65028133d15b64d2ed8f06dd9fbc268352478d4f9289e69c190ecd6818b6407", size = 8507689 }, - { url = "https://files.pythonhosted.org/packages/7e/9e/ad6b1ae2a5ad1066dc509350e0fbf74d8d50251a51e420a2a8feaa0cecbd/pywin32-306-cp311-cp311-win_amd64.whl", hash = "sha256:a7639f51c184c0272e93f244eb24dafca9b1855707d94c192d4a0b4c01e1100e", size = 9227547 }, - { url = "https://files.pythonhosted.org/packages/91/20/f744bff1da8f43388498503634378dbbefbe493e65675f2cc52f7185c2c2/pywin32-306-cp311-cp311-win_arm64.whl", hash = "sha256:70dba0c913d19f942a2db25217d9a1b726c278f483a919f1abfed79c9cf64d3a", size = 10388324 }, - { url = "https://files.pythonhosted.org/packages/14/91/17e016d5923e178346aabda3dfec6629d1a26efe587d19667542105cf0a6/pywin32-306-cp312-cp312-win32.whl", hash = "sha256:383229d515657f4e3ed1343da8be101000562bf514591ff383ae940cad65458b", size = 8507705 }, - { url = "https://files.pythonhosted.org/packages/83/1c/25b79fc3ec99b19b0a0730cc47356f7e2959863bf9f3cd314332bddb4f68/pywin32-306-cp312-cp312-win_amd64.whl", hash = "sha256:37257794c1ad39ee9be652da0462dc2e394c8159dfd913a8a4e8eb6fd346da0e", size = 9227429 }, - { url = "https://files.pythonhosted.org/packages/1c/43/e3444dc9a12f8365d9603c2145d16bf0a2f8180f343cf87be47f5579e547/pywin32-306-cp312-cp312-win_arm64.whl", hash = "sha256:5821ec52f6d321aa59e2db7e0a35b997de60c201943557d108af9d4ae1ec7040", size = 10388145 }, + { url = "https://files.pythonhosted.org/packages/72/a6/3e9f2c474895c1bb61b11fa9640be00067b5c5b363c501ee9c3fa53aec01/pywin32-308-cp310-cp310-win32.whl", hash = "sha256:796ff4426437896550d2981b9c2ac0ffd75238ad9ea2d3bfa67a1abd546d262e", size = 5927028 }, + { url = "https://files.pythonhosted.org/packages/d9/b4/84e2463422f869b4b718f79eb7530a4c1693e96b8a4e5e968de38be4d2ba/pywin32-308-cp310-cp310-win_amd64.whl", hash = "sha256:4fc888c59b3c0bef905ce7eb7e2106a07712015ea1c8234b703a088d46110e8e", size = 6558484 }, + { url = "https://files.pythonhosted.org/packages/9f/8f/fb84ab789713f7c6feacaa08dad3ec8105b88ade8d1c4f0f0dfcaaa017d6/pywin32-308-cp310-cp310-win_arm64.whl", hash = "sha256:a5ab5381813b40f264fa3495b98af850098f814a25a63589a8e9eb12560f450c", size = 7971454 }, + { url = "https://files.pythonhosted.org/packages/eb/e2/02652007469263fe1466e98439831d65d4ca80ea1a2df29abecedf7e47b7/pywin32-308-cp311-cp311-win32.whl", hash = "sha256:5d8c8015b24a7d6855b1550d8e660d8daa09983c80e5daf89a273e5c6fb5095a", size = 5928156 }, + { url = "https://files.pythonhosted.org/packages/48/ef/f4fb45e2196bc7ffe09cad0542d9aff66b0e33f6c0954b43e49c33cad7bd/pywin32-308-cp311-cp311-win_amd64.whl", hash = "sha256:575621b90f0dc2695fec346b2d6302faebd4f0f45c05ea29404cefe35d89442b", size = 6559559 }, + { url = "https://files.pythonhosted.org/packages/79/ef/68bb6aa865c5c9b11a35771329e95917b5559845bd75b65549407f9fc6b4/pywin32-308-cp311-cp311-win_arm64.whl", hash = "sha256:100a5442b7332070983c4cd03f2e906a5648a5104b8a7f50175f7906efd16bb6", size = 7972495 }, + { url = "https://files.pythonhosted.org/packages/00/7c/d00d6bdd96de4344e06c4afbf218bc86b54436a94c01c71a8701f613aa56/pywin32-308-cp312-cp312-win32.whl", hash = "sha256:587f3e19696f4bf96fde9d8a57cec74a57021ad5f204c9e627e15c33ff568897", size = 5939729 }, + { url = "https://files.pythonhosted.org/packages/21/27/0c8811fbc3ca188f93b5354e7c286eb91f80a53afa4e11007ef661afa746/pywin32-308-cp312-cp312-win_amd64.whl", hash = "sha256:00b3e11ef09ede56c6a43c71f2d31857cf7c54b0ab6e78ac659497abd2834f47", size = 6543015 }, + { url = "https://files.pythonhosted.org/packages/9d/0f/d40f8373608caed2255781a3ad9a51d03a594a1248cd632d6a298daca693/pywin32-308-cp312-cp312-win_arm64.whl", hash = "sha256:9b4de86c8d909aed15b7011182c8cab38c8850de36e6afb1f0db22b8959e3091", size = 7976033 }, + { url = "https://files.pythonhosted.org/packages/a9/a4/aa562d8935e3df5e49c161b427a3a2efad2ed4e9cf81c3de636f1fdddfd0/pywin32-308-cp313-cp313-win32.whl", hash = "sha256:1c44539a37a5b7b21d02ab34e6a4d314e0788f1690d65b48e9b0b89f31abbbed", size = 5938579 }, + { url = "https://files.pythonhosted.org/packages/c7/50/b0efb8bb66210da67a53ab95fd7a98826a97ee21f1d22949863e6d588b22/pywin32-308-cp313-cp313-win_amd64.whl", hash = "sha256:fd380990e792eaf6827fcb7e187b2b4b1cede0585e3d0c9e84201ec27b9905e4", size = 6542056 }, + { url = "https://files.pythonhosted.org/packages/26/df/2b63e3e4f2df0224f8aaf6d131f54fe4e8c96400eb9df563e2aae2e1a1f9/pywin32-308-cp313-cp313-win_arm64.whl", hash = "sha256:ef313c46d4c18dfb82a2431e3051ac8f112ccee1a34f29c263c583c568db63cd", size = 7974986 }, ] [[package]] @@ -3695,7 +3701,7 @@ wheels = [ [[package]] name = "qdrant-client" -version = "1.11.3" +version = "1.12.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "grpcio" }, @@ -3706,30 +3712,9 @@ dependencies = [ { name = "pydantic" }, { name = "urllib3" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/68/10/2fe2d50c7c27eb0dad80266639c11db04c5664780f200fbe615b6daac0e5/qdrant_client-1.11.3.tar.gz", hash = "sha256:5a155d8281a224ac18acef512eae2f5e9a0907975d52a7627ec66fa6586d0285", size = 229322 } +sdist = { url = "https://files.pythonhosted.org/packages/cf/5e/197f128bd99515e8fdd9d37e62e3cb1090f81664505bdf835c020e59bb85/qdrant_client-1.12.0.tar.gz", hash = "sha256:f443db39988aa6ff7c7a605770084ddaca8fdb5f8b22f77c10e661bdf0974cda", size = 236777 } wheels = [ - { url = "https://files.pythonhosted.org/packages/89/91/af901af30928f2a6409546bcfec412c06b5c15b37329d553ddc59914e012/qdrant_client-1.11.3-py3-none-any.whl", hash = "sha256:fcf040b58203ed0827608c9ad957da671b1e31bf27e5e35b322c1b577b6ec133", size = 258936 }, -] - -[[package]] -name = "rank-bm25" -version = "0.2.2" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "numpy" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/fc/0a/f9579384aa017d8b4c15613f86954b92a95a93d641cc849182467cf0bb3b/rank_bm25-0.2.2.tar.gz", hash = "sha256:096ccef76f8188563419aaf384a02f0ea459503fdf77901378d4fd9d87e5e51d", size = 8347 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/2a/21/f691fb2613100a62b3fa91e9988c991e9ca5b89ea31c0d3152a3210344f9/rank_bm25-0.2.2-py3-none-any.whl", hash = "sha256:7bd4a95571adadfc271746fa146a4bcfd89c0cf731e49c3d1ad863290adbe8ae", size = 8584 }, -] - -[[package]] -name = "ratelimiter" -version = "1.2.0.post0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/5b/e0/b36010bddcf91444ff51179c076e4a09c513674a56758d7cfea4f6520e29/ratelimiter-1.2.0.post0.tar.gz", hash = "sha256:5c395dcabdbbde2e5178ef3f89b568a3066454a6ddc223b76473dac22f89b4f7", size = 9182 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/51/80/2164fa1e863ad52cc8d870855fba0fbb51edd943edffd516d54b5f6f8ff8/ratelimiter-1.2.0.post0-py3-none-any.whl", hash = "sha256:a52be07bc0bb0b3674b4b304550f10c769bbb00fead3072e035904474259809f", size = 6642 }, + { url = "https://files.pythonhosted.org/packages/b6/31/92beed37e86f10bc9c67078a56c0144974dd1cc7a6dfe1f998e2fd9be895/qdrant_client-1.12.0-py3-none-any.whl", hash = "sha256:6db5ac1e244272f8b67e9dbc0da557816efef6f919cd8ee134469c751fe72c03", size = 266397 }, ] [[package]] @@ -3869,16 +3854,16 @@ wheels = [ [[package]] name = "rich" -version = "13.9.1" +version = "13.9.3" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "markdown-it-py" }, { name = "pygments" }, { name = "typing-extensions", marker = "python_full_version < '3.11'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/b3/78/87d00a1df7c457ad9aa0139f01b8a11c67209f27f927c503b0109bf2ed6c/rich-13.9.1.tar.gz", hash = "sha256:097cffdf85db1babe30cc7deba5ab3a29e1b9885047dab24c57e9a7f8a9c1466", size = 222907 } +sdist = { url = "https://files.pythonhosted.org/packages/d9/e9/cf9ef5245d835065e6673781dbd4b8911d352fb770d56cf0879cf11b7ee1/rich-13.9.3.tar.gz", hash = "sha256:bc1e01b899537598cf02579d2b9f4a415104d3fc439313a7a2c165d76557a08e", size = 222889 } wheels = [ - { url = "https://files.pythonhosted.org/packages/ab/71/cd9549551f1aa11cf7e5f92bae5817979e8b3a19e31e8810c15f3f45c311/rich-13.9.1-py3-none-any.whl", hash = "sha256:b340e739f30aa58921dc477b8adaa9ecdb7cecc217be01d93730ee1bc8aa83be", size = 242147 }, + { url = "https://files.pythonhosted.org/packages/9a/e2/10e9819cf4a20bd8ea2f5dabafc2e6bf4a78d6a0965daeb60a4b34d1c11f/rich-13.9.3-py3-none-any.whl", hash = "sha256:9836f5096eb2172c9e77df411c1b009bace4193d6a481d534fea75ebba758283", size = 242157 }, ] [[package]] @@ -3967,91 +3952,27 @@ wheels = [ [[package]] name = "ruff" -version = "0.6.9" +version = "0.7.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/26/0d/6148a48dab5662ca1d5a93b7c0d13c03abd3cc7e2f35db08410e47cef15d/ruff-0.6.9.tar.gz", hash = "sha256:b076ef717a8e5bc819514ee1d602bbdca5b4420ae13a9cf61a0c0a4f53a2baa2", size = 3095355 } +sdist = { url = "https://files.pythonhosted.org/packages/2c/c7/f3367d1da5d568192968c5c9e7f3d51fb317b9ac04828493b23d8fce8ce6/ruff-0.7.0.tar.gz", hash = "sha256:47a86360cf62d9cd53ebfb0b5eb0e882193fc191c6d717e8bef4462bc3b9ea2b", size = 3146645 } wheels = [ - { url = "https://files.pythonhosted.org/packages/6e/8f/f7a0a0ef1818662efb32ed6df16078c95da7a0a3248d64c2410c1e27799f/ruff-0.6.9-py3-none-linux_armv6l.whl", hash = "sha256:064df58d84ccc0ac0fcd63bc3090b251d90e2a372558c0f057c3f75ed73e1ccd", size = 10440526 }, - { url = "https://files.pythonhosted.org/packages/8b/69/b179a5faf936a9e2ab45bb412a668e4661eded964ccfa19d533f29463ef6/ruff-0.6.9-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:140d4b5c9f5fc7a7b074908a78ab8d384dd7f6510402267bc76c37195c02a7ec", size = 10034612 }, - { url = "https://files.pythonhosted.org/packages/c7/ef/fd1b4be979c579d191eeac37b5cfc0ec906de72c8bcd8595e2c81bb700c1/ruff-0.6.9-py3-none-macosx_11_0_arm64.whl", hash = "sha256:53fd8ca5e82bdee8da7f506d7b03a261f24cd43d090ea9db9a1dc59d9313914c", size = 9706197 }, - { url = "https://files.pythonhosted.org/packages/29/61/b376d775deb5851cb48d893c568b511a6d3625ef2c129ad5698b64fb523c/ruff-0.6.9-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:645d7d8761f915e48a00d4ecc3686969761df69fb561dd914a773c1a8266e14e", size = 10751855 }, - { url = "https://files.pythonhosted.org/packages/13/d7/def9e5f446d75b9a9c19b24231a3a658c075d79163b08582e56fa5dcfa38/ruff-0.6.9-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:eae02b700763e3847595b9d2891488989cac00214da7f845f4bcf2989007d577", size = 10200889 }, - { url = "https://files.pythonhosted.org/packages/6c/d6/7f34160818bcb6e84ce293a5966cba368d9112ff0289b273fbb689046047/ruff-0.6.9-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7d5ccc9e58112441de8ad4b29dcb7a86dc25c5f770e3c06a9d57e0e5eba48829", size = 11038678 }, - { url = "https://files.pythonhosted.org/packages/13/34/a40ff8ae62fb1b26fb8e6fa7e64bc0e0a834b47317880de22edd6bfb54fb/ruff-0.6.9-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:417b81aa1c9b60b2f8edc463c58363075412866ae4e2b9ab0f690dc1e87ac1b5", size = 11808682 }, - { url = "https://files.pythonhosted.org/packages/2e/6d/25a4386ae4009fc798bd10ba48c942d1b0b3e459b5403028f1214b6dd161/ruff-0.6.9-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3c866b631f5fbce896a74a6e4383407ba7507b815ccc52bcedabb6810fdb3ef7", size = 11330446 }, - { url = "https://files.pythonhosted.org/packages/f7/f6/bdf891a9200d692c94ebcd06ae5a2fa5894e522f2c66c2a12dd5d8cb2654/ruff-0.6.9-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7b118afbb3202f5911486ad52da86d1d52305b59e7ef2031cea3425142b97d6f", size = 12483048 }, - { url = "https://files.pythonhosted.org/packages/a7/86/96f4252f41840e325b3fa6c48297e661abb9f564bd7dcc0572398c8daa42/ruff-0.6.9-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a67267654edc23c97335586774790cde402fb6bbdb3c2314f1fc087dee320bfa", size = 10936855 }, - { url = "https://files.pythonhosted.org/packages/45/87/801a52d26c8dbf73424238e9908b9ceac430d903c8ef35eab1b44fcfa2bd/ruff-0.6.9-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:3ef0cc774b00fec123f635ce5c547dac263f6ee9fb9cc83437c5904183b55ceb", size = 10713007 }, - { url = "https://files.pythonhosted.org/packages/be/27/6f7161d90320a389695e32b6ebdbfbedde28ccbf52451e4b723d7ce744ad/ruff-0.6.9-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:12edd2af0c60fa61ff31cefb90aef4288ac4d372b4962c2864aeea3a1a2460c0", size = 10274594 }, - { url = "https://files.pythonhosted.org/packages/00/52/dc311775e7b5f5b19831563cb1572ecce63e62681bccc609867711fae317/ruff-0.6.9-py3-none-musllinux_1_2_i686.whl", hash = "sha256:55bb01caeaf3a60b2b2bba07308a02fca6ab56233302406ed5245180a05c5625", size = 10608024 }, - { url = "https://files.pythonhosted.org/packages/98/b6/be0a1ddcbac65a30c985cf7224c4fce786ba2c51e7efeb5178fe410ed3cf/ruff-0.6.9-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:925d26471fa24b0ce5a6cdfab1bb526fb4159952385f386bdcc643813d472039", size = 10982085 }, - { url = "https://files.pythonhosted.org/packages/bb/a4/c84bc13d0b573cf7bb7d17b16d6d29f84267c92d79b2f478d4ce322e8e72/ruff-0.6.9-py3-none-win32.whl", hash = "sha256:eb61ec9bdb2506cffd492e05ac40e5bc6284873aceb605503d8494180d6fc84d", size = 8522088 }, - { url = "https://files.pythonhosted.org/packages/74/be/fc352bd8ca40daae8740b54c1c3e905a7efe470d420a268cd62150248c91/ruff-0.6.9-py3-none-win_amd64.whl", hash = "sha256:785d31851c1ae91f45b3d8fe23b8ae4b5170089021fbb42402d811135f0b7117", size = 9359275 }, - { url = "https://files.pythonhosted.org/packages/3e/14/fd026bc74ded05e2351681545a5f626e78ef831f8edce064d61acd2e6ec7/ruff-0.6.9-py3-none-win_arm64.whl", hash = "sha256:a9641e31476d601f83cd602608739a0840e348bda93fec9f1ee816f8b6798b93", size = 8679879 }, -] - -[[package]] -name = "s3transfer" -version = "0.10.2" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "botocore" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/cb/67/94c6730ee4c34505b14d94040e2f31edf144c230b6b49e971b4f25ff8fab/s3transfer-0.10.2.tar.gz", hash = "sha256:0711534e9356d3cc692fdde846b4a1e4b0cb6519971860796e6bc4c7aea00ef6", size = 144095 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/3c/4a/b221409913760d26cf4498b7b1741d510c82d3ad38381984a3ddc135ec66/s3transfer-0.10.2-py3-none-any.whl", hash = "sha256:eca1c20de70a39daee580aef4986996620f365c4e0fda6a86100231d62f1bf69", size = 82716 }, -] - -[[package]] -name = "sagemaker" -version = "2.232.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "attrs" }, - { name = "boto3" }, - { name = "cloudpickle" }, - { name = "docker" }, - { name = "google-pasta" }, - { name = "importlib-metadata" }, - { name = "jsonschema" }, - { name = "numpy" }, - { name = "packaging" }, - { name = "pandas" }, - { name = "pathos" }, - { name = "platformdirs" }, - { name = "protobuf" }, - { name = "psutil" }, - { name = "pyyaml" }, - { name = "requests" }, - { name = "sagemaker-core" }, - { name = "schema" }, - { name = "smdebug-rulesconfig" }, - { name = "tblib" }, - { name = "tqdm" }, - { name = "urllib3" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/27/ce/196d21315156597b3d316dfc65ef2009cc7c4b2ea43b3b5b8cd8d6ca8c41/sagemaker-2.232.1.tar.gz", hash = "sha256:e59e1ac79bc31235b8d5c766abee5c6b2fd526814d1dde62b98e7dca9654503c", size = 1125030 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/18/c7/b058d9986117e3c15ee60dc099acc60918bacf6b1cd98ac2ca876bf1fc4d/sagemaker-2.232.1-py3-none-any.whl", hash = "sha256:64b92639918613b8042ddbc13f34cfac65145d916ff0c0001b249f9f33012cb1", size = 1551362 }, -] - -[[package]] -name = "sagemaker-core" -version = "1.0.10" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "boto3" }, - { name = "importlib-metadata" }, - { name = "jsonschema" }, - { name = "mock" }, - { name = "platformdirs" }, - { name = "pydantic" }, - { name = "pyyaml" }, - { name = "rich" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/70/78/d416e08a3ecd422a37e184b70a3d9a352f8897db181a8b9d9ced4ae9bc32/sagemaker_core-1.0.10.tar.gz", hash = "sha256:6d34a9b6dc5e17e8bfffd1d0650726865779c92b3b8f1b59fc15d42061a0dd29", size = 374530 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/18/af/6bec7c84b1a4dd4160711bf9dd80fae87fe421892b3416971e78cac6f903/sagemaker_core-1.0.10-py3-none-any.whl", hash = "sha256:0bdcf6a467db988919cc6b6d0077f74871ee24c24adf7f759f9cb98460e08953", size = 388373 }, + { url = "https://files.pythonhosted.org/packages/48/59/a0275a0913f3539498d116046dd679cd657fe3b7caf5afe1733319414932/ruff-0.7.0-py3-none-linux_armv6l.whl", hash = "sha256:0cdf20c2b6ff98e37df47b2b0bd3a34aaa155f59a11182c1303cce79be715628", size = 10434007 }, + { url = "https://files.pythonhosted.org/packages/cd/94/da0ba5f956d04c90dd899209904210600009dcda039ce840d83eb4298c7d/ruff-0.7.0-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:496494d350c7fdeb36ca4ef1c9f21d80d182423718782222c29b3e72b3512737", size = 10048066 }, + { url = "https://files.pythonhosted.org/packages/57/1d/e5cc149ecc46e4f203403a79ccd170fad52d316f98b87d0f63b1945567db/ruff-0.7.0-py3-none-macosx_11_0_arm64.whl", hash = "sha256:214b88498684e20b6b2b8852c01d50f0651f3cc6118dfa113b4def9f14faaf06", size = 9711389 }, + { url = "https://files.pythonhosted.org/packages/05/67/fb7ea2c869c539725a16c5bc294e9aa34f8b1b6fe702f1d173a5da517c2b/ruff-0.7.0-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:630fce3fefe9844e91ea5bbf7ceadab4f9981f42b704fae011bb8efcaf5d84be", size = 10755174 }, + { url = "https://files.pythonhosted.org/packages/5f/f0/13703bc50536a0613ea3dce991116e5f0917a1f05528c6ab738b33c08d3f/ruff-0.7.0-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:211d877674e9373d4bb0f1c80f97a0201c61bcd1e9d045b6e9726adc42c156aa", size = 10196040 }, + { url = "https://files.pythonhosted.org/packages/99/c1/77b04ab20324ab03d333522ee55fb0f1c38e3ca0d326b4905f82ce6b6c70/ruff-0.7.0-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:194d6c46c98c73949a106425ed40a576f52291c12bc21399eb8f13a0f7073495", size = 11033684 }, + { url = "https://files.pythonhosted.org/packages/f2/97/f463334dc4efeea3551cd109163df15561c18a1c3ec13d51643740fd36ba/ruff-0.7.0-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:82c2579b82b9973a110fab281860403b397c08c403de92de19568f32f7178598", size = 11803700 }, + { url = "https://files.pythonhosted.org/packages/b4/f8/a31d40c4bb92933d376a53e7c5d0245d9b27841357e4820e96d38f54b480/ruff-0.7.0-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9af971fe85dcd5eaed8f585ddbc6bdbe8c217fb8fcf510ea6bca5bdfff56040e", size = 11347848 }, + { url = "https://files.pythonhosted.org/packages/83/62/0c133b35ddaf91c65c30a56718b80bdef36bfffc35684d29e3a4878e0ea3/ruff-0.7.0-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b641c7f16939b7d24b7bfc0be4102c56562a18281f84f635604e8a6989948914", size = 12480632 }, + { url = "https://files.pythonhosted.org/packages/46/96/464058dd1d980014fb5aa0a1254e78799efb3096fc7a4823cd66a1621276/ruff-0.7.0-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d71672336e46b34e0c90a790afeac8a31954fd42872c1f6adaea1dff76fd44f9", size = 10941919 }, + { url = "https://files.pythonhosted.org/packages/a0/f7/bda37ec77986a435dde44e1f59374aebf4282a5fa9cf17735315b847141f/ruff-0.7.0-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:ab7d98c7eed355166f367597e513a6c82408df4181a937628dbec79abb2a1fe4", size = 10745519 }, + { url = "https://files.pythonhosted.org/packages/c2/33/5f77fc317027c057b61a848020a47442a1cbf12e592df0e41e21f4d0f3bd/ruff-0.7.0-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:1eb54986f770f49edb14f71d33312d79e00e629a57387382200b1ef12d6a4ef9", size = 10284872 }, + { url = "https://files.pythonhosted.org/packages/ff/50/98aec292bc9537f640b8d031c55f3414bf15b6ed13b3e943fed75ac927b9/ruff-0.7.0-py3-none-musllinux_1_2_i686.whl", hash = "sha256:dc452ba6f2bb9cf8726a84aa877061a2462afe9ae0ea1d411c53d226661c601d", size = 10600334 }, + { url = "https://files.pythonhosted.org/packages/f2/85/12607ae3201423a179b8cfadc7cb1e57d02cd0135e45bd0445acb4cef327/ruff-0.7.0-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:4b406c2dce5be9bad59f2de26139a86017a517e6bcd2688da515481c05a2cb11", size = 11017333 }, + { url = "https://files.pythonhosted.org/packages/d4/7f/3b85a56879e705d5f46ec14daf8a439fca05c3081720fe3dc3209100922d/ruff-0.7.0-py3-none-win32.whl", hash = "sha256:f6c968509f767776f524a8430426539587d5ec5c662f6addb6aa25bc2e8195ec", size = 8570962 }, + { url = "https://files.pythonhosted.org/packages/39/9f/c5ee2b40d377354dabcc23cff47eb299de4b4d06d345068f8f8cc1eadac8/ruff-0.7.0-py3-none-win_amd64.whl", hash = "sha256:ff4aabfbaaba880e85d394603b9e75d32b0693152e16fa659a3064a85df7fce2", size = 9365544 }, + { url = "https://files.pythonhosted.org/packages/89/8b/ee1509f60148cecba644aa718f6633216784302458340311898aaf0b1bed/ruff-0.7.0-py3-none-win_arm64.whl", hash = "sha256:10842f69c245e78d6adec7e1db0a7d9ddc2fff0621d730e61657b64fa36f207e", size = 8695763 }, ] [[package]] @@ -4080,22 +4001,13 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/aa/85/fa44f23dd5d5066a72f7c4304cce4b5ff9a6e7fd92431a48b2c63fbf63ec/selenium-4.25.0-py3-none-any.whl", hash = "sha256:3798d2d12b4a570bc5790163ba57fef10b2afee958bf1d80f2a3cf07c4141f33", size = 9693127 }, ] -[[package]] -name = "semver" -version = "3.0.2" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/41/6c/a536cc008f38fd83b3c1b98ce19ead13b746b5588c9a0cb9dd9f6ea434bc/semver-3.0.2.tar.gz", hash = "sha256:6253adb39c70f6e51afed2fa7152bcd414c411286088fb4b9effb133885ab4cc", size = 214988 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/9a/77/0cc7a8a3bc7e53d07e8f47f147b92b0960e902b8254859f4aee5c4d7866b/semver-3.0.2-py3-none-any.whl", hash = "sha256:b1ea4686fe70b981f85359eda33199d60c53964284e0cfb4977d243e37cf4bf4", size = 17099 }, -] - [[package]] name = "setuptools" -version = "75.1.0" +version = "75.2.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/27/b8/f21073fde99492b33ca357876430822e4800cdf522011f18041351dfa74b/setuptools-75.1.0.tar.gz", hash = "sha256:d59a21b17a275fb872a9c3dae73963160ae079f1049ed956880cd7c09b120538", size = 1348057 } +sdist = { url = "https://files.pythonhosted.org/packages/07/37/b31be7e4b9f13b59cde9dcaeff112d401d49e0dc5b37ed4a9fc8fb12f409/setuptools-75.2.0.tar.gz", hash = "sha256:753bb6ebf1f465a1912e19ed1d41f403a79173a9acf66a42e7e6aec45c3c16ec", size = 1350308 } wheels = [ - { url = "https://files.pythonhosted.org/packages/ff/ae/f19306b5a221f6a436d8f2238d5b80925004093fa3edea59835b514d9057/setuptools-75.1.0-py3-none-any.whl", hash = "sha256:35ab7fd3bcd95e6b7fd704e4a1539513edad446c097797f2985e0e4b960772f2", size = 1248506 }, + { url = "https://files.pythonhosted.org/packages/31/2d/90165d51ecd38f9a02c6832198c13a4e48652485e2ccf863ebb942c531b6/setuptools-75.2.0-py3-none-any.whl", hash = "sha256:a7fcb66f68b4d9e8e66b42f9876150a3371558f98fa32222ffaa5bced76406f8", size = 1249825 }, ] [[package]] @@ -4151,15 +4063,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/d9/5a/e7c31adbe875f2abbb91bd84cf2dc52d792b5a01506781dbcf25c91daf11/six-1.16.0-py2.py3-none-any.whl", hash = "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254", size = 11053 }, ] -[[package]] -name = "smdebug-rulesconfig" -version = "1.0.1" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/cc/7d/8ad6a2098e03c1f811d1277a2cedb81265828f144f6d323b83a2392e8bb9/smdebug_rulesconfig-1.0.1.tar.gz", hash = "sha256:7a19e6eb2e6bcfefbc07e4a86ef7a88f32495001a038bf28c7d8e77ab793fcd6", size = 12060 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/26/a1/45a13a05198bbe9527bab2c5e5daa8bd02678aa825eec14783e767bfa7d1/smdebug_rulesconfig-1.0.1-py2.py3-none-any.whl", hash = "sha256:104da3e6931ecf879dfc687ca4bbb3bee5ea2bc27f4478e9dbb3ee3655f1ae61", size = 20282 }, -] - [[package]] name = "sniffio" version = "1.3.1" @@ -4189,39 +4092,47 @@ wheels = [ [[package]] name = "sqlalchemy" -version = "2.0.35" +version = "2.0.36" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "greenlet", marker = "(python_full_version < '3.13' and platform_machine == 'AMD64') or (python_full_version < '3.13' and platform_machine == 'WIN32') or (python_full_version < '3.13' and platform_machine == 'aarch64') or (python_full_version < '3.13' and platform_machine == 'amd64') or (python_full_version < '3.13' and platform_machine == 'ppc64le') or (python_full_version < '3.13' and platform_machine == 'win32') or (python_full_version < '3.13' and platform_machine == 'x86_64')" }, { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/36/48/4f190a83525f5cefefa44f6adc9e6386c4de5218d686c27eda92eb1f5424/sqlalchemy-2.0.35.tar.gz", hash = "sha256:e11d7ea4d24f0a262bccf9a7cd6284c976c5369dac21db237cff59586045ab9f", size = 9562798 } +sdist = { url = "https://files.pythonhosted.org/packages/50/65/9cbc9c4c3287bed2499e05033e207473504dc4df999ce49385fb1f8b058a/sqlalchemy-2.0.36.tar.gz", hash = "sha256:7f2767680b6d2398aea7082e45a774b2b0767b5c8d8ffb9c8b683088ea9b29c5", size = 9574485 } wheels = [ - { url = "https://files.pythonhosted.org/packages/1a/61/19395d0ae78c94f6f80c8adf39a142f3fe56cfb2235d8f2317d6dae1bf0e/SQLAlchemy-2.0.35-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:67219632be22f14750f0d1c70e62f204ba69d28f62fd6432ba05ab295853de9b", size = 2090086 }, - { url = "https://files.pythonhosted.org/packages/e6/82/06b5fcbe5d49043e40cf4e01e3b33c471c8d9292d478420b08538cae8928/SQLAlchemy-2.0.35-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:4668bd8faf7e5b71c0319407b608f278f279668f358857dbfd10ef1954ac9f90", size = 2081278 }, - { url = "https://files.pythonhosted.org/packages/68/d1/7fb7ee46949a5fb34005795b1fc06a8fef67587a66da731c14e545f7eb5b/SQLAlchemy-2.0.35-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cb8bea573863762bbf45d1e13f87c2d2fd32cee2dbd50d050f83f87429c9e1ea", size = 3063763 }, - { url = "https://files.pythonhosted.org/packages/7e/ff/a1eacd78b31e52a5073e9924fb4722ecc2a72f093ca8181ed81fc61aed2e/SQLAlchemy-2.0.35-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f552023710d4b93d8fb29a91fadf97de89c5926c6bd758897875435f2a939f33", size = 3072032 }, - { url = "https://files.pythonhosted.org/packages/21/ae/ddfecf149a6d16af87408bca7bd108eef7ef23d376cc8464317efb3cea3f/SQLAlchemy-2.0.35-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:016b2e665f778f13d3c438651dd4de244214b527a275e0acf1d44c05bc6026a9", size = 3028092 }, - { url = "https://files.pythonhosted.org/packages/cc/51/3e84d42121662a160bacd311cfacb29c1e6a229d59dd8edb09caa8ab283b/SQLAlchemy-2.0.35-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:7befc148de64b6060937231cbff8d01ccf0bfd75aa26383ffdf8d82b12ec04ff", size = 3053543 }, - { url = "https://files.pythonhosted.org/packages/3e/7a/039c78105958da3fc361887f0a82c974cb6fa5bba965c1689ec778be1c01/SQLAlchemy-2.0.35-cp310-cp310-win32.whl", hash = "sha256:22b83aed390e3099584b839b93f80a0f4a95ee7f48270c97c90acd40ee646f0b", size = 2062372 }, - { url = "https://files.pythonhosted.org/packages/a2/50/f31e927d32f9729f69d150ffe47e7cf51e3e0bb2148fc400b3e93a92ca4c/SQLAlchemy-2.0.35-cp310-cp310-win_amd64.whl", hash = "sha256:a29762cd3d116585278ffb2e5b8cc311fb095ea278b96feef28d0b423154858e", size = 2086485 }, - { url = "https://files.pythonhosted.org/packages/c3/46/9215a35bf98c3a2528e987791e6180eb51624d2c7d5cb8e2d96a6450b657/SQLAlchemy-2.0.35-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:e21f66748ab725ade40fa7af8ec8b5019c68ab00b929f6643e1b1af461eddb60", size = 2091274 }, - { url = "https://files.pythonhosted.org/packages/1e/69/919673c5101a0c633658d58b11b454b251ca82300941fba801201434755d/SQLAlchemy-2.0.35-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:8a6219108a15fc6d24de499d0d515c7235c617b2540d97116b663dade1a54d62", size = 2081672 }, - { url = "https://files.pythonhosted.org/packages/67/ea/a6b0597cbda12796be2302153369dbbe90573fdab3bc4885f8efac499247/SQLAlchemy-2.0.35-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:042622a5306c23b972192283f4e22372da3b8ddf5f7aac1cc5d9c9b222ab3ff6", size = 3200083 }, - { url = "https://files.pythonhosted.org/packages/8c/d6/97bdc8d714fb21762f2092511f380f18cdb2d985d516071fa925bb433a90/SQLAlchemy-2.0.35-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:627dee0c280eea91aed87b20a1f849e9ae2fe719d52cbf847c0e0ea34464b3f7", size = 3200080 }, - { url = "https://files.pythonhosted.org/packages/87/d2/8c2adaf2ade4f6f1b725acd0b0be9210bb6a2df41024729a8eec6a86fe5a/SQLAlchemy-2.0.35-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:4fdcd72a789c1c31ed242fd8c1bcd9ea186a98ee8e5408a50e610edfef980d71", size = 3137108 }, - { url = "https://files.pythonhosted.org/packages/7e/ae/ea05d0bfa8f2b25ae34591895147152854fc950f491c4ce362ae06035db8/SQLAlchemy-2.0.35-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:89b64cd8898a3a6f642db4eb7b26d1b28a497d4022eccd7717ca066823e9fb01", size = 3157437 }, - { url = "https://files.pythonhosted.org/packages/fe/5d/8ad6df01398388a766163d27960b3365f1bbd8bb7b05b5cad321a8b69b25/SQLAlchemy-2.0.35-cp311-cp311-win32.whl", hash = "sha256:6a93c5a0dfe8d34951e8a6f499a9479ffb9258123551fa007fc708ae2ac2bc5e", size = 2061935 }, - { url = "https://files.pythonhosted.org/packages/ff/68/8557efc0c32c8e2c147cb6512237448b8ed594a57cd015fda67f8e56bb3f/SQLAlchemy-2.0.35-cp311-cp311-win_amd64.whl", hash = "sha256:c68fe3fcde03920c46697585620135b4ecfdfc1ed23e75cc2c2ae9f8502c10b8", size = 2087281 }, - { url = "https://files.pythonhosted.org/packages/2f/2b/fff87e6db0da31212c98bbc445f83fb608ea92b96bda3f3f10e373bac76c/SQLAlchemy-2.0.35-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:eb60b026d8ad0c97917cb81d3662d0b39b8ff1335e3fabb24984c6acd0c900a2", size = 2089790 }, - { url = "https://files.pythonhosted.org/packages/68/92/4bb761bd82764d5827bf6b6095168c40fb5dbbd23670203aef2f96ba6bc6/SQLAlchemy-2.0.35-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:6921ee01caf375363be5e9ae70d08ce7ca9d7e0e8983183080211a062d299468", size = 2080266 }, - { url = "https://files.pythonhosted.org/packages/22/46/068a65db6dc253c6f25a7598d99e0a1d60b14f661f9d09ef6c73c718fa4e/SQLAlchemy-2.0.35-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8cdf1a0dbe5ced887a9b127da4ffd7354e9c1a3b9bb330dce84df6b70ccb3a8d", size = 3229760 }, - { url = "https://files.pythonhosted.org/packages/6e/36/59830dafe40dda592304debd4cd86e583f63472f3a62c9e2695a5795e786/SQLAlchemy-2.0.35-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:93a71c8601e823236ac0e5d087e4f397874a421017b3318fd92c0b14acf2b6db", size = 3240649 }, - { url = "https://files.pythonhosted.org/packages/00/50/844c50c6996f9c7f000c959dd1a7436a6c94e449ee113046a1d19e470089/SQLAlchemy-2.0.35-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:e04b622bb8a88f10e439084486f2f6349bf4d50605ac3e445869c7ea5cf0fa8c", size = 3176138 }, - { url = "https://files.pythonhosted.org/packages/df/d2/336b18cac68eecb67de474fc15c85f13be4e615c6f5bae87ea38c6734ce0/SQLAlchemy-2.0.35-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:1b56961e2d31389aaadf4906d453859f35302b4eb818d34a26fab72596076bb8", size = 3202753 }, - { url = "https://files.pythonhosted.org/packages/f0/f3/ee1e62fabdc10910b5ef720ae08e59bc785f26652876af3a50b89b97b412/SQLAlchemy-2.0.35-cp312-cp312-win32.whl", hash = "sha256:0f9f3f9a3763b9c4deb8c5d09c4cc52ffe49f9876af41cc1b2ad0138878453cf", size = 2060113 }, - { url = "https://files.pythonhosted.org/packages/60/63/a3cef44a52979169d884f3583d0640e64b3c28122c096474a1d7cfcaf1f3/SQLAlchemy-2.0.35-cp312-cp312-win_amd64.whl", hash = "sha256:25b0f63e7fcc2a6290cb5f7f5b4fc4047843504983a28856ce9b35d8f7de03cc", size = 2085839 }, - { url = "https://files.pythonhosted.org/packages/0e/c6/33c706449cdd92b1b6d756b247761e27d32230fd6b2de5f44c4c3e5632b2/SQLAlchemy-2.0.35-py3-none-any.whl", hash = "sha256:2ab3f0336c0387662ce6221ad30ab3a5e6499aab01b9790879b6578fd9b8faa1", size = 1881276 }, + { url = "https://files.pythonhosted.org/packages/db/72/14ab694b8b3f0e35ef5beb74a8fea2811aa791ba1611c44dc90cdf46af17/SQLAlchemy-2.0.36-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:59b8f3adb3971929a3e660337f5dacc5942c2cdb760afcabb2614ffbda9f9f72", size = 2092604 }, + { url = "https://files.pythonhosted.org/packages/1e/59/333fcbca58b79f5b8b61853d6137530198823392151fa8fd9425f367519e/SQLAlchemy-2.0.36-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:37350015056a553e442ff672c2d20e6f4b6d0b2495691fa239d8aa18bb3bc908", size = 2083796 }, + { url = "https://files.pythonhosted.org/packages/6c/a0/ec3c188d2b0c1bc742262e76408d44104598d7247c23f5b06bb97ee21bfa/SQLAlchemy-2.0.36-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8318f4776c85abc3f40ab185e388bee7a6ea99e7fa3a30686580b209eaa35c08", size = 3066165 }, + { url = "https://files.pythonhosted.org/packages/07/15/68ef91de5b8b7f80fb2d2b3b31ed42180c6227fe0a701aed9d01d34f98ec/SQLAlchemy-2.0.36-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c245b1fbade9c35e5bd3b64270ab49ce990369018289ecfde3f9c318411aaa07", size = 3074428 }, + { url = "https://files.pythonhosted.org/packages/e2/4c/9dfea5e63b87325eef6d9cdaac913459aa6a157a05a05ea6ff20004aee8e/SQLAlchemy-2.0.36-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:69f93723edbca7342624d09f6704e7126b152eaed3cdbb634cb657a54332a3c5", size = 3030477 }, + { url = "https://files.pythonhosted.org/packages/16/a5/fcfde8e74ea5f683b24add22463bfc21e431d4a5531c8a5b55bc6fbea164/SQLAlchemy-2.0.36-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:f9511d8dd4a6e9271d07d150fb2f81874a3c8c95e11ff9af3a2dfc35fe42ee44", size = 3055942 }, + { url = "https://files.pythonhosted.org/packages/3c/ee/c22c415a771d791ae99146d72ffdb20e43625acd24835ea7fc157436d59f/SQLAlchemy-2.0.36-cp310-cp310-win32.whl", hash = "sha256:c3f3631693003d8e585d4200730616b78fafd5a01ef8b698f6967da5c605b3fa", size = 2064960 }, + { url = "https://files.pythonhosted.org/packages/aa/af/ad9c25cadc79bd851bdb9d82b68af9bdb91ff05f56d0da2f8a654825974f/SQLAlchemy-2.0.36-cp310-cp310-win_amd64.whl", hash = "sha256:a86bfab2ef46d63300c0f06936bd6e6c0105faa11d509083ba8f2f9d237fb5b5", size = 2089078 }, + { url = "https://files.pythonhosted.org/packages/00/4e/5a67963fd7cbc1beb8bd2152e907419f4c940ef04600b10151a751fe9e06/SQLAlchemy-2.0.36-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:fd3a55deef00f689ce931d4d1b23fa9f04c880a48ee97af488fd215cf24e2a6c", size = 2093782 }, + { url = "https://files.pythonhosted.org/packages/b3/24/30e33b6389ebb5a17df2a4243b091bc709fb3dfc9a48c8d72f8e037c943d/SQLAlchemy-2.0.36-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:4f5e9cd989b45b73bd359f693b935364f7e1f79486e29015813c338450aa5a71", size = 2084180 }, + { url = "https://files.pythonhosted.org/packages/10/1e/70e9ed2143a27065246be40f78637ad5160ea0f5fd32f8cab819a31ff54d/SQLAlchemy-2.0.36-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d0ddd9db6e59c44875211bc4c7953a9f6638b937b0a88ae6d09eb46cced54eff", size = 3202469 }, + { url = "https://files.pythonhosted.org/packages/b4/5f/95e0ed74093ac3c0db6acfa944d4d8ac6284ef5e1136b878a327ea1f975a/SQLAlchemy-2.0.36-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2519f3a5d0517fc159afab1015e54bb81b4406c278749779be57a569d8d1bb0d", size = 3202464 }, + { url = "https://files.pythonhosted.org/packages/91/95/2cf9b85a6bc2ee660e40594dffe04e777e7b8617fd0c6d77a0f782ea96c9/SQLAlchemy-2.0.36-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:59b1ee96617135f6e1d6f275bbe988f419c5178016f3d41d3c0abb0c819f75bb", size = 3139508 }, + { url = "https://files.pythonhosted.org/packages/92/ea/f0c01bc646456e4345c0fb5a3ddef457326285c2dc60435b0eb96b61bf31/SQLAlchemy-2.0.36-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:39769a115f730d683b0eb7b694db9789267bcd027326cccc3125e862eb03bfd8", size = 3159837 }, + { url = "https://files.pythonhosted.org/packages/a6/93/c8edbf153ee38fe529773240877bf1332ed95328aceef6254288f446994e/SQLAlchemy-2.0.36-cp311-cp311-win32.whl", hash = "sha256:66bffbad8d6271bb1cc2f9a4ea4f86f80fe5e2e3e501a5ae2a3dc6a76e604e6f", size = 2064529 }, + { url = "https://files.pythonhosted.org/packages/b1/03/d12b7c1d36fd80150c1d52e121614cf9377dac99e5497af8d8f5b2a8db64/SQLAlchemy-2.0.36-cp311-cp311-win_amd64.whl", hash = "sha256:23623166bfefe1487d81b698c423f8678e80df8b54614c2bf4b4cfcd7c711959", size = 2089874 }, + { url = "https://files.pythonhosted.org/packages/b8/bf/005dc47f0e57556e14512d5542f3f183b94fde46e15ff1588ec58ca89555/SQLAlchemy-2.0.36-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:f7b64e6ec3f02c35647be6b4851008b26cff592a95ecb13b6788a54ef80bbdd4", size = 2092378 }, + { url = "https://files.pythonhosted.org/packages/94/65/f109d5720779a08e6e324ec89a744f5f92c48bd8005edc814bf72fbb24e5/SQLAlchemy-2.0.36-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:46331b00096a6db1fdc052d55b101dbbfc99155a548e20a0e4a8e5e4d1362855", size = 2082778 }, + { url = "https://files.pythonhosted.org/packages/60/f6/d9aa8c49c44f9b8c9b9dada1f12fa78df3d4c42aa2de437164b83ee1123c/SQLAlchemy-2.0.36-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fdf3386a801ea5aba17c6410dd1dc8d39cf454ca2565541b5ac42a84e1e28f53", size = 3232191 }, + { url = "https://files.pythonhosted.org/packages/8a/ab/81d4514527c068670cb1d7ab62a81a185df53a7c379bd2a5636e83d09ede/SQLAlchemy-2.0.36-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ac9dfa18ff2a67b09b372d5db8743c27966abf0e5344c555d86cc7199f7ad83a", size = 3243044 }, + { url = "https://files.pythonhosted.org/packages/35/b4/f87c014ecf5167dc669199cafdb20a7358ff4b1d49ce3622cc48571f811c/SQLAlchemy-2.0.36-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:90812a8933df713fdf748b355527e3af257a11e415b613dd794512461eb8a686", size = 3178511 }, + { url = "https://files.pythonhosted.org/packages/ea/09/badfc9293bc3ccba6ede05e5f2b44a760aa47d84da1fc5a326e963e3d4d9/SQLAlchemy-2.0.36-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:1bc330d9d29c7f06f003ab10e1eaced295e87940405afe1b110f2eb93a233588", size = 3205147 }, + { url = "https://files.pythonhosted.org/packages/c8/60/70e681de02a13c4b27979b7b78da3058c49bacc9858c89ba672e030f03f2/SQLAlchemy-2.0.36-cp312-cp312-win32.whl", hash = "sha256:79d2e78abc26d871875b419e1fd3c0bca31a1cb0043277d0d850014599626c2e", size = 2062709 }, + { url = "https://files.pythonhosted.org/packages/b7/ed/f6cd9395e41bfe47dd253d74d2dfc3cab34980d4e20c8878cb1117306085/SQLAlchemy-2.0.36-cp312-cp312-win_amd64.whl", hash = "sha256:b544ad1935a8541d177cb402948b94e871067656b3a0b9e91dbec136b06a2ff5", size = 2088433 }, + { url = "https://files.pythonhosted.org/packages/78/5c/236398ae3678b3237726819b484f15f5c038a9549da01703a771f05a00d6/SQLAlchemy-2.0.36-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:b5cc79df7f4bc3d11e4b542596c03826063092611e481fcf1c9dfee3c94355ef", size = 2087651 }, + { url = "https://files.pythonhosted.org/packages/a8/14/55c47420c0d23fb67a35af8be4719199b81c59f3084c28d131a7767b0b0b/SQLAlchemy-2.0.36-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:3c01117dd36800f2ecaa238c65365b7b16497adc1522bf84906e5710ee9ba0e8", size = 2078132 }, + { url = "https://files.pythonhosted.org/packages/3d/97/1e843b36abff8c4a7aa2e37f9bea364f90d021754c2de94d792c2d91405b/SQLAlchemy-2.0.36-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9bc633f4ee4b4c46e7adcb3a9b5ec083bf1d9a97c1d3854b92749d935de40b9b", size = 3164559 }, + { url = "https://files.pythonhosted.org/packages/7b/c5/07f18a897b997f6d6b234fab2bf31dccf66d5d16a79fe329aefc95cd7461/SQLAlchemy-2.0.36-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9e46ed38affdfc95d2c958de328d037d87801cfcbea6d421000859e9789e61c2", size = 3177897 }, + { url = "https://files.pythonhosted.org/packages/b3/cd/e16f3cbefd82b5c40b33732da634ec67a5f33b587744c7ab41699789d492/SQLAlchemy-2.0.36-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:b2985c0b06e989c043f1dc09d4fe89e1616aadd35392aea2844f0458a989eacf", size = 3111289 }, + { url = "https://files.pythonhosted.org/packages/15/85/5b8a3b0bc29c9928aa62b5c91fcc8335f57c1de0a6343873b5f372e3672b/SQLAlchemy-2.0.36-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:4a121d62ebe7d26fec9155f83f8be5189ef1405f5973ea4874a26fab9f1e262c", size = 3139491 }, + { url = "https://files.pythonhosted.org/packages/a1/95/81babb6089938680dfe2cd3f88cd3fd39cccd1543b7cb603b21ad881bff1/SQLAlchemy-2.0.36-cp313-cp313-win32.whl", hash = "sha256:0572f4bd6f94752167adfd7c1bed84f4b240ee6203a95e05d1e208d488d0d436", size = 2060439 }, + { url = "https://files.pythonhosted.org/packages/c1/ce/5f7428df55660d6879d0522adc73a3364970b5ef33ec17fa125c5dbcac1d/SQLAlchemy-2.0.36-cp313-cp313-win_amd64.whl", hash = "sha256:8c78ac40bde930c60e0f78b3cd184c580f89456dd87fc08f9e3ee3ce8765ce88", size = 2084574 }, + { url = "https://files.pythonhosted.org/packages/b8/49/21633706dd6feb14cd3f7935fc00b60870ea057686035e1a99ae6d9d9d53/SQLAlchemy-2.0.36-py3-none-any.whl", hash = "sha256:fddbe92b4760c6f5d48162aef14824add991aeda8ddadb3c31d56eb15ca69f8e", size = 1883787 }, ] [[package]] @@ -4240,14 +4151,14 @@ wheels = [ [[package]] name = "starlette" -version = "0.38.6" +version = "0.41.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "anyio" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/42/b4/e25c3b688ef703d85e55017c6edd0cbf38e5770ab748234363d54ff0251a/starlette-0.38.6.tar.gz", hash = "sha256:863a1588f5574e70a821dadefb41e4881ea451a47a3cd1b4df359d4ffefe5ead", size = 2569491 } +sdist = { url = "https://files.pythonhosted.org/packages/78/53/c3a36690a923706e7ac841f649c64f5108889ab1ec44218dac45771f252a/starlette-0.41.0.tar.gz", hash = "sha256:39cbd8768b107d68bfe1ff1672b38a2c38b49777de46d2a592841d58e3bf7c2a", size = 2573755 } wheels = [ - { url = "https://files.pythonhosted.org/packages/b7/9c/93f7bc03ff03199074e81974cc148908ead60dcf189f68ba1761a0ee35cf/starlette-0.38.6-py3-none-any.whl", hash = "sha256:4517a1409e2e73ee4951214ba012052b9e16f60e90d73cfb06192c19203bbb05", size = 71451 }, + { url = "https://files.pythonhosted.org/packages/35/c6/a4443bfabf5629129512ca0e07866c4c3c094079ba4e9b2551006927253c/starlette-0.41.0-py3-none-any.whl", hash = "sha256:a0193a3c413ebc9c78bff1c3546a45bb8c8bcb4a84cae8747d650a65bd37210a", size = 73216 }, ] [[package]] @@ -4271,22 +4182,13 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/40/44/4a5f08c96eb108af5cb50b41f76142f0afa346dfa99d5296fe7202a11854/tabulate-0.9.0-py3-none-any.whl", hash = "sha256:024ca478df22e9340661486f85298cff5f6dcdba14f3813e8830015b9ed1948f", size = 35252 }, ] -[[package]] -name = "tblib" -version = "3.0.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/1a/df/4f2cd7eaa6d41a7994d46527349569d46e34d9cdd07590b5c5b0dcf53de3/tblib-3.0.0.tar.gz", hash = "sha256:93622790a0a29e04f0346458face1e144dc4d32f493714c6c3dff82a4adb77e6", size = 30616 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/9b/87/ce70db7cae60e67851eb94e1a2127d4abb573d3866d2efd302ceb0d4d2a5/tblib-3.0.0-py3-none-any.whl", hash = "sha256:80a6c77e59b55e83911e1e607c649836a69c103963c5f28a46cbeef44acf8129", size = 12478 }, -] - [[package]] name = "tenacity" -version = "8.5.0" +version = "9.0.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/a3/4d/6a19536c50b849338fcbe9290d562b52cbdcf30d8963d3588a68a4107df1/tenacity-8.5.0.tar.gz", hash = "sha256:8bc6c0c8a09b31e6cad13c47afbed1a567518250a9a171418582ed8d9c20ca78", size = 47309 } +sdist = { url = "https://files.pythonhosted.org/packages/cd/94/91fccdb4b8110642462e653d5dcb27e7b674742ad68efd146367da7bdb10/tenacity-9.0.0.tar.gz", hash = "sha256:807f37ca97d62aa361264d497b0e31e92b8027044942bfa756160d908320d73b", size = 47421 } wheels = [ - { url = "https://files.pythonhosted.org/packages/d2/3f/8ba87d9e287b9d385a02a7114ddcef61b26f86411e121c9003eb509a1773/tenacity-8.5.0-py3-none-any.whl", hash = "sha256:b594c2a5945830c267ce6b79a166228323ed52718f30302c1359836112346687", size = 28165 }, + { url = "https://files.pythonhosted.org/packages/b6/cb/b86984bed139586d01532a587464b5805f12e397594f19f931c4c2fbfa61/tenacity-9.0.0-py3-none-any.whl", hash = "sha256:93de0c98785b27fcf659856aa9f54bfbd399e29969b0621bc7f762bd441b4539", size = 28169 }, ] [[package]] @@ -4345,56 +4247,56 @@ wheels = [ [[package]] name = "tokenizers" -version = "0.20.0" +version = "0.20.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "huggingface-hub" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/02/3a/508a4875f69e12b08fb3dabfc746039fe763838ff45d6e42229ed09a41c2/tokenizers-0.20.0.tar.gz", hash = "sha256:39d7acc43f564c274085cafcd1dae9d36f332456de1a31970296a6b8da4eac8d", size = 337421 } +sdist = { url = "https://files.pythonhosted.org/packages/d7/fb/373b66ba58cbf5eda371480e4e051d8892ea1433a73f1f92c48657a699a6/tokenizers-0.20.1.tar.gz", hash = "sha256:84edcc7cdeeee45ceedb65d518fffb77aec69311c9c8e30f77ad84da3025f002", size = 339552 } wheels = [ - { url = "https://files.pythonhosted.org/packages/0d/47/88f92fb433fe2fb59b35bbce28455095bcb7b40fff385223b1e7818cec38/tokenizers-0.20.0-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:6cff5c5e37c41bc5faa519d6f3df0679e4b37da54ea1f42121719c5e2b4905c0", size = 2624575 }, - { url = "https://files.pythonhosted.org/packages/fc/e5/74c6ab076de7d2d4d347e8781086117889d202628dfd5f5fba8ebefb1ea2/tokenizers-0.20.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:62a56bf75c27443432456f4ca5ca055befa95e25be8a28141cc495cac8ae4d6d", size = 2515759 }, - { url = "https://files.pythonhosted.org/packages/4e/f5/1087cb5100e704dce9a1419d6f3e8ac843c98efa11579c3287ddb036b476/tokenizers-0.20.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:68cc7de6a63f09c4a86909c2597b995aa66e19df852a23aea894929c74369929", size = 2892020 }, - { url = "https://files.pythonhosted.org/packages/35/07/7004003098e3d442bba9b9821b78f34043248bdf6a78433846944b7d9a61/tokenizers-0.20.0-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:053c37ecee482cc958fdee53af3c6534286a86f5d35aac476f7c246830e53ae5", size = 2754734 }, - { url = "https://files.pythonhosted.org/packages/d0/61/9f3def0db2db72d8da6c4c318481a35c5c71172dad54ff3813f765ab2a45/tokenizers-0.20.0-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3d7074aaabc151a6363fa03db5493fc95b423b2a1874456783989e96d541c7b6", size = 3009897 }, - { url = "https://files.pythonhosted.org/packages/c1/98/f4a9a18a4e2e254c6ed253b3e5344d8f48760d3af6813df4415446db1b4c/tokenizers-0.20.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a11435780f2acd89e8fefe5e81cecf01776f6edb9b3ac95bcb76baee76b30b90", size = 3032295 }, - { url = "https://files.pythonhosted.org/packages/87/43/52b096d5aacb3eb698f1b791e8a6c1b7ecd39b17724c38312804b79429fa/tokenizers-0.20.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9a81cd2712973b007d84268d45fc3f6f90a79c31dfe7f1925e6732f8d2959987", size = 3328639 }, - { url = "https://files.pythonhosted.org/packages/fc/7e/794850f99752d1811952722c18652a5c0125b0ef595d9ed069d00da9a5db/tokenizers-0.20.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d7dfd796ab9d909f76fb93080e1c7c8309f196ecb316eb130718cd5e34231c69", size = 2936169 }, - { url = "https://files.pythonhosted.org/packages/ea/3d/d573173b0cd78cd64e95b5c8f268f3a619877bc6a484b649d98af4de24bf/tokenizers-0.20.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:8029ad2aa8cb00605c9374566034c1cc1b15130713e0eb5afcef6cface8255c9", size = 8965441 }, - { url = "https://files.pythonhosted.org/packages/27/cb/76636123a5bc550c48aa8048def1ae3d86421723be2cca8f195f464c20f6/tokenizers-0.20.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:ca4d54260ebe97d59dfa9a30baa20d0c4dd9137d99a8801700055c561145c24e", size = 9284485 }, - { url = "https://files.pythonhosted.org/packages/32/16/5eaa1405e15ca91a9e0f6c07963cd91f48daf8f999ff731b589078a4caa1/tokenizers-0.20.0-cp310-none-win32.whl", hash = "sha256:95ee16b57cec11b86a7940174ec5197d506439b0f415ab3859f254b1dffe9df0", size = 2125655 }, - { url = "https://files.pythonhosted.org/packages/63/90/84534f81ff1453a1bcc049b03ea6820ca7ab497519b79b129d7297bb4e60/tokenizers-0.20.0-cp310-none-win_amd64.whl", hash = "sha256:0a61a11e93eeadbf02aea082ffc75241c4198e0608bbbac4f65a9026851dcf37", size = 2326217 }, - { url = "https://files.pythonhosted.org/packages/a4/f6/ae042eeae413bae9af5adceed7fe6f30fb0abc9868a55916d4e07c8ea1fb/tokenizers-0.20.0-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:6636b798b3c4d6c9b1af1a918bd07c867808e5a21c64324e95318a237e6366c3", size = 2625296 }, - { url = "https://files.pythonhosted.org/packages/62/8b/dab4d716e9a00c1581443213283c9fdfdb982cdad6ecc046bae9c7e42fc8/tokenizers-0.20.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:5ec603e42eaf499ffd58b9258162add948717cf21372458132f14e13a6bc7172", size = 2516726 }, - { url = "https://files.pythonhosted.org/packages/95/1e/800e0896ea43ab86d70cfc6ed6a30d6aefcab498eff49db79cc92e08e1fe/tokenizers-0.20.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cce124264903a8ea6f8f48e1cc7669e5ef638c18bd4ab0a88769d5f92debdf7f", size = 2891801 }, - { url = "https://files.pythonhosted.org/packages/02/80/22ceab06d120df5b589f993248bceef177a932024ae8ee033ec3da5cc87f/tokenizers-0.20.0-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:07bbeba0231cf8de07aa6b9e33e9779ff103d47042eeeb859a8c432e3292fb98", size = 2753762 }, - { url = "https://files.pythonhosted.org/packages/22/7c/02431f0711162ab3994e4099b9ece4b6a00755e3180bf5dfe70da0c13836/tokenizers-0.20.0-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:06c0ca8397b35d38b83a44a9c6929790c1692957d88541df061cb34d82ebbf08", size = 3010928 }, - { url = "https://files.pythonhosted.org/packages/bc/14/193b7e58017e9592799498686df718c5f68bfb72205d3075ce9cdd441db7/tokenizers-0.20.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ca6557ac3b83d912dfbb1f70ab56bd4b0594043916688e906ede09f42e192401", size = 3032435 }, - { url = "https://files.pythonhosted.org/packages/71/ae/c7fc7a614ce78cab7b8f82f7a24a074837cbc7e0086960cbe4801b2b3c83/tokenizers-0.20.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2a5ad94c9e80ac6098328bee2e3264dbced4c6faa34429994d473f795ec58ef4", size = 3328437 }, - { url = "https://files.pythonhosted.org/packages/a5/0e/e4421e6b8c8b3ae093bef22faa28c50d7dbd654f661edc5f5880a93dbf10/tokenizers-0.20.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0b5c7f906ee6bec30a9dc20268a8b80f3b9584de1c9f051671cb057dc6ce28f6", size = 2936532 }, - { url = "https://files.pythonhosted.org/packages/b9/08/ac9c8fe9c1f5b4ef89bcbf543cda890e76c2ea1c2e957bf77fd5fcf72b6c/tokenizers-0.20.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:31e087e9ee1b8f075b002bfee257e858dc695f955b43903e1bb4aa9f170e37fe", size = 8965273 }, - { url = "https://files.pythonhosted.org/packages/fb/71/b9626f9f5a33dd1d80bb6d3721f0a4b0b48ced0c702e65aad5c8c7c1ae7e/tokenizers-0.20.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:c3124fb6f3346cb3d8d775375d3b429bf4dcfc24f739822702009d20a4297990", size = 9283768 }, - { url = "https://files.pythonhosted.org/packages/ba/78/70f79f939385579bb25f14cb14ab0eaa49e46a7d099577c2e08e3c3597d8/tokenizers-0.20.0-cp311-none-win32.whl", hash = "sha256:a4bb8b40ba9eefa621fdcabf04a74aa6038ae3be0c614c6458bd91a4697a452f", size = 2126085 }, - { url = "https://files.pythonhosted.org/packages/c0/3c/9228601e180b177755fd9f35cbb229c13f1919a55f07a602b1bd7d716470/tokenizers-0.20.0-cp311-none-win_amd64.whl", hash = "sha256:2b709d371f1fe60a28ef0c5c67815952d455ca7f34dbe7197eaaed3cc54b658e", size = 2327670 }, - { url = "https://files.pythonhosted.org/packages/ce/d4/152f9964cee16b43b9147212e925793df1a469324b29b4c7a6cb60280c99/tokenizers-0.20.0-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:15c81a17d0d66f4987c6ca16f4bea7ec253b8c7ed1bb00fdc5d038b1bb56e714", size = 2613552 }, - { url = "https://files.pythonhosted.org/packages/6e/99/594b518d44ba2b099753816a9c0c33dbdcf77cc3ec5b256690f70d7431c2/tokenizers-0.20.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:6a531cdf1fb6dc41c984c785a3b299cb0586de0b35683842a3afbb1e5207f910", size = 2513918 }, - { url = "https://files.pythonhosted.org/packages/24/fa/77f0cf9b3c662b4de18953fb06126c424059f4b09ca2d1b720beabc6afde/tokenizers-0.20.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:06caabeb4587f8404e0cd9d40f458e9cba3e815c8155a38e579a74ff3e2a4301", size = 2892465 }, - { url = "https://files.pythonhosted.org/packages/2d/e6/59abfc09f1dc23a47fd03dd8e3bf3fce67d9be2b8ba15a73c9a86b5a646c/tokenizers-0.20.0-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:8768f964f23f5b9f50546c0369c75ab3262de926983888bbe8b98be05392a79c", size = 2750862 }, - { url = "https://files.pythonhosted.org/packages/0f/b2/f212ca05c1b246b9429905c18a4d68abacf2a35214eceedb1d65c6c37831/tokenizers-0.20.0-cp312-cp312-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:626403860152c816f97b649fd279bd622c3d417678c93b4b1a8909b6380b69a8", size = 3012971 }, - { url = "https://files.pythonhosted.org/packages/16/0b/099f5e5b97e8323837a5828f6d21f4bb2a3b529507dc19bd274e48e15825/tokenizers-0.20.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9c1b88fa9e5ff062326f4bf82681da5a96fca7104d921a6bd7b1e6fcf224af26", size = 3038445 }, - { url = "https://files.pythonhosted.org/packages/62/7c/4e3cb25dc1c5eea6053752f55007071da6b33a96021e0cea4b45b6ef0908/tokenizers-0.20.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3d7e559436a07dc547f22ce1101f26d8b2fad387e28ec8e7e1e3b11695d681d8", size = 3329352 }, - { url = "https://files.pythonhosted.org/packages/32/20/a8fe63317d4f3c015cbd5b6dec0ce08e2722685ca836ad4a44dec53d000f/tokenizers-0.20.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e48afb75e50449848964e4a67b0da01261dd3aa8df8daecf10db8fd7f5b076eb", size = 2938786 }, - { url = "https://files.pythonhosted.org/packages/06/e8/78f1c0f356d0a6e4e4e450e2419ace1918bfab875100c3047021a8261ba0/tokenizers-0.20.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:baf5d0e1ff44710a95eefc196dd87666ffc609fd447c5e5b68272a7c3d342a1d", size = 8967350 }, - { url = "https://files.pythonhosted.org/packages/e6/eb/3a1edfc1ffb876ffc1f668c8fa2b2ffb57edf8e9188af49218cf41f9cd9f/tokenizers-0.20.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:e5e56df0e8ed23ba60ae3848c3f069a0710c4b197218fe4f89e27eba38510768", size = 9284785 }, - { url = "https://files.pythonhosted.org/packages/00/75/426a93399ba5e6e879215e1abb696adb83b1e2a98d65b47b8ba4262b3d17/tokenizers-0.20.0-cp312-none-win32.whl", hash = "sha256:ec53e5ecc142a82432f9c6c677dbbe5a2bfee92b8abf409a9ecb0d425ee0ce75", size = 2125012 }, - { url = "https://files.pythonhosted.org/packages/a5/45/9c19187645401ec30884379ada74aa6e71fb5eaf20485a82ea37a0fd3659/tokenizers-0.20.0-cp312-none-win_amd64.whl", hash = "sha256:f18661ece72e39c0dfaa174d6223248a15b457dbd4b0fc07809b8e6d3ca1a234", size = 2314154 }, - { url = "https://files.pythonhosted.org/packages/cd/99/dba2f18ba180aefddb65852d2cea69de607232f4cf1d999e789899d56c19/tokenizers-0.20.0-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:d68e15f1815357b059ec266062340c343ea7f98f7f330602df81ffa3474b6122", size = 2626438 }, - { url = "https://files.pythonhosted.org/packages/79/e6/eb28c3c7d23f3feaa9fb6ae16ff313210474b3c9f81689afe6d132915da0/tokenizers-0.20.0-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:23f9ecec637b9bc80da5f703808d29ed5329e56b5aa8d791d1088014f48afadc", size = 2517016 }, - { url = "https://files.pythonhosted.org/packages/18/2f/35f7fdbf1ae6fa3d0348531596a63651fdb117ff367e3dfe8a6be5f31f5a/tokenizers-0.20.0-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f830b318ee599e3d0665b3e325f85bc75ee2d2ca6285f52e439dc22b64691580", size = 2890784 }, - { url = "https://files.pythonhosted.org/packages/97/10/7b74d7e5663f886d058df470f14fd492078533a5aee52bf1553eed83a49d/tokenizers-0.20.0-pp310-pypy310_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b3dc750def789cb1de1b5a37657919545e1d9ffa667658b3fa9cb7862407a1b8", size = 3007139 }, - { url = "https://files.pythonhosted.org/packages/77/5a/a59c9f97000fce432e3728fbe32c23cf3dd9933255d76166101c2b12a916/tokenizers-0.20.0-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e26e6c755ae884c2ea6135cd215bdd0fccafe4ee62405014b8c3cd19954e3ab9", size = 2933499 }, - { url = "https://files.pythonhosted.org/packages/bd/7a/fde367e46596855e172c466655fc416d98be6c7ae792afdb5315ca38bed0/tokenizers-0.20.0-pp310-pypy310_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:a1158c7174f427182e08baa2a8ded2940f2b4a3e94969a85cc9cfd16004cbcea", size = 8964991 }, - { url = "https://files.pythonhosted.org/packages/9f/fa/075959c7d901a55b2a3198d0ecfbc624c553f5ff8027bc4fac0aa6bab70a/tokenizers-0.20.0-pp310-pypy310_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:6324826287a3fc198898d3dcf758fe4a8479e42d6039f4c59e2cedd3cf92f64e", size = 9284502 }, + { url = "https://files.pythonhosted.org/packages/72/d2/3c05efeeccefa833b82038ce49ee736756eed10ab66fc723ce423a747b0e/tokenizers-0.20.1-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:439261da7c0a5c88bda97acb284d49fbdaf67e9d3b623c0bfd107512d22787a9", size = 2673220 }, + { url = "https://files.pythonhosted.org/packages/24/d4/a529aa06db71600c1688210ce035cbff637ece919dcaca599c9235ad832d/tokenizers-0.20.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:03dae629d99068b1ea5416d50de0fea13008f04129cc79af77a2a6392792d93c", size = 2563056 }, + { url = "https://files.pythonhosted.org/packages/25/e2/5046ad3b0426548b37c96cc4262a7f2ba6ac9593ee10be69effc78a91764/tokenizers-0.20.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b61f561f329ffe4b28367798b89d60c4abf3f815d37413b6352bc6412a359867", size = 2943369 }, + { url = "https://files.pythonhosted.org/packages/5f/f0/c1ed45ff90088eba4f15eca9763b5e439cb86b71fc9e66a827318b61e44d/tokenizers-0.20.1-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:ec870fce1ee5248a10be69f7a8408a234d6f2109f8ea827b4f7ecdbf08c9fd15", size = 2827000 }, + { url = "https://files.pythonhosted.org/packages/22/09/6e0a378a35f215b40ae1c04b4d0fe43e9ddfaf3a08a2b7d7fab8953a6587/tokenizers-0.20.1-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d388d1ea8b7447da784e32e3b86a75cce55887e3b22b31c19d0b186b1c677800", size = 3090881 }, + { url = "https://files.pythonhosted.org/packages/cf/03/801e91d41e2134a32089af2d382a6c40b3d8b932b42fa96443d77258ab28/tokenizers-0.20.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:299c85c1d21135bc01542237979bf25c32efa0d66595dd0069ae259b97fb2dbe", size = 3096826 }, + { url = "https://files.pythonhosted.org/packages/2a/39/3d11780b82d9ba4d8fda093daa48622ed5f2616d6ac8cb638ac290d39d95/tokenizers-0.20.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e96f6c14c9752bb82145636b614d5a78e9cde95edfbe0a85dad0dd5ddd6ec95c", size = 3417666 }, + { url = "https://files.pythonhosted.org/packages/4b/35/326b9642307a53b3d9ae145b5c7f157aae9ecaa930888f920124412e0bd2/tokenizers-0.20.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fc9e95ad49c932b80abfbfeaf63b155761e695ad9f8a58c52a47d962d76e310f", size = 2984468 }, + { url = "https://files.pythonhosted.org/packages/db/b2/5e45632799d816291de4d04149decf19cf6c2faf42bb99574d80050c87bd/tokenizers-0.20.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:f22dee205329a636148c325921c73cf3e412e87d31f4d9c3153b302a0200057b", size = 8981675 }, + { url = "https://files.pythonhosted.org/packages/df/f7/8c0ec102f0a723d09347ff6cd617c7e5e8d44efd342305f52a7fcd3e30e2/tokenizers-0.20.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:a2ffd9a8895575ac636d44500c66dffaef133823b6b25067604fa73bbc5ec09d", size = 9300378 }, + { url = "https://files.pythonhosted.org/packages/e8/54/22825bc3d00ae8a801314a6d96e7e83c180b626a40299179073364c7eac7/tokenizers-0.20.1-cp310-none-win32.whl", hash = "sha256:2847843c53f445e0f19ea842a4e48b89dd0db4e62ba6e1e47a2749d6ec11f50d", size = 2203820 }, + { url = "https://files.pythonhosted.org/packages/7a/da/c7728bb6be0ccfbd5662f054ee28d8ba7883558cc9fcd102e6cdce07bbbf/tokenizers-0.20.1-cp310-none-win_amd64.whl", hash = "sha256:f9aa93eacd865f2798b9e62f7ce4533cfff4f5fbd50c02926a78e81c74e432cd", size = 2384778 }, + { url = "https://files.pythonhosted.org/packages/61/9a/be5f00cd37ad4fab0e5d1dbf31404a66ac2c1c33973beda9fc8e248a37ab/tokenizers-0.20.1-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:4a717dcb08f2dabbf27ae4b6b20cbbb2ad7ed78ce05a829fae100ff4b3c7ff15", size = 2673182 }, + { url = "https://files.pythonhosted.org/packages/26/a2/92af8a5f19d0e8bc480759a9975489ebd429b94a81ad46e1422c7927f246/tokenizers-0.20.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:3f84dad1ff1863c648d80628b1b55353d16303431283e4efbb6ab1af56a75832", size = 2562556 }, + { url = "https://files.pythonhosted.org/packages/2d/ca/f3a294ed89f2a1b900fba072ef4cb5331d4f156e2d5ea2d34f60160ef5bd/tokenizers-0.20.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:929c8f3afa16a5130a81ab5079c589226273ec618949cce79b46d96e59a84f61", size = 2943343 }, + { url = "https://files.pythonhosted.org/packages/31/88/740a6a069e997dc3e96941083fe3264162f4d198a5e5841acb625f84adbd/tokenizers-0.20.1-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:d10766473954397e2d370f215ebed1cc46dcf6fd3906a2a116aa1d6219bfedc3", size = 2825954 }, + { url = "https://files.pythonhosted.org/packages/ff/71/b220deba78e42e483e2856c9cc83a8352c7c5d7322dad61eed4e1ca09c49/tokenizers-0.20.1-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9300fac73ddc7e4b0330acbdda4efaabf74929a4a61e119a32a181f534a11b47", size = 3091324 }, + { url = "https://files.pythonhosted.org/packages/fe/f4/4302dce958ce0e7f2d85a4725cebe6b02161c2d82990a89317580e17469a/tokenizers-0.20.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:0ecaf7b0e39caeb1aa6dd6e0975c405716c82c1312b55ac4f716ef563a906969", size = 3098587 }, + { url = "https://files.pythonhosted.org/packages/7e/0f/9136bc0ea492d29f1d72217c6231dc584bccd3ba41dde12d4a85c75eb12a/tokenizers-0.20.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5170be9ec942f3d1d317817ced8d749b3e1202670865e4fd465e35d8c259de83", size = 3414366 }, + { url = "https://files.pythonhosted.org/packages/09/6c/1b573998fe3f0e18ac5d434e43966de2d225d6837f099ce0df7df4274c87/tokenizers-0.20.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ef3f1ae08fa9aea5891cbd69df29913e11d3841798e0bfb1ff78b78e4e7ea0a4", size = 2984510 }, + { url = "https://files.pythonhosted.org/packages/d3/92/e5b80e42c24e564ac892c9135e4b9ec34bbcd6cdf0cc7a04735c44fe2ced/tokenizers-0.20.1-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:ee86d4095d3542d73579e953c2e5e07d9321af2ffea6ecc097d16d538a2dea16", size = 8982324 }, + { url = "https://files.pythonhosted.org/packages/d0/42/c287d28ebcb3ba4f712e7a58d8f170a7b569528acf2d2a8fd1f684c24c0c/tokenizers-0.20.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:86dcd08da163912e17b27bbaba5efdc71b4fbffb841530fdb74c5707f3c49216", size = 9301853 }, + { url = "https://files.pythonhosted.org/packages/ea/48/7d4ac79588b5b1c3651b753b0a1bdd1343d81af57be18138dfdb304a710a/tokenizers-0.20.1-cp311-none-win32.whl", hash = "sha256:9af2dc4ee97d037bc6b05fa4429ddc87532c706316c5e11ce2f0596dfcfa77af", size = 2201968 }, + { url = "https://files.pythonhosted.org/packages/f1/95/f1b56f4b1fbd54bd7f170aa64258d0650500e9f45de217ffe4d4663809b6/tokenizers-0.20.1-cp311-none-win_amd64.whl", hash = "sha256:899152a78b095559c287b4c6d0099469573bb2055347bb8154db106651296f39", size = 2384963 }, + { url = "https://files.pythonhosted.org/packages/8e/8d/a051f979f955c6717099718054d7f51fea0a92d807a7d078a48f2684e54f/tokenizers-0.20.1-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:407ab666b38e02228fa785e81f7cf79ef929f104bcccf68a64525a54a93ceac9", size = 2667300 }, + { url = "https://files.pythonhosted.org/packages/99/c3/2132487ca51148392f0d1ed7f35c23179f67d66fd64c233ff50f091258b4/tokenizers-0.20.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:2f13a2d16032ebc8bd812eb8099b035ac65887d8f0c207261472803b9633cf3e", size = 2556581 }, + { url = "https://files.pythonhosted.org/packages/f4/6e/9dfd1afcfd38fcc5b3a84bca54c33025561f7cab8ea375fa88f03407adc1/tokenizers-0.20.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e98eee4dca22849fbb56a80acaa899eec5b72055d79637dd6aa15d5e4b8628c9", size = 2937857 }, + { url = "https://files.pythonhosted.org/packages/28/51/92e3b25eb41be7fd65219c832c4ff61bf5c8cc1c3d0543e9a117d63a0876/tokenizers-0.20.1-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:47c1bcdd61e61136087459cb9e0b069ff23b5568b008265e5cbc927eae3387ce", size = 2823012 }, + { url = "https://files.pythonhosted.org/packages/f7/59/185ff0bb35d46d88613e87bd76b03989ef8537ebf4f39876bddf9bed2fc1/tokenizers-0.20.1-cp312-cp312-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:128c1110e950534426e2274837fc06b118ab5f2fa61c3436e60e0aada0ccfd67", size = 3086473 }, + { url = "https://files.pythonhosted.org/packages/a4/2a/da72c32446ad7f3e6e5cb3c625222a5b9b0bc10b50456f6cb79f6230ae1f/tokenizers-0.20.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e2e2d47a819d2954f2c1cd0ad51bb58ffac6f53a872d5d82d65d79bf76b9896d", size = 3101655 }, + { url = "https://files.pythonhosted.org/packages/cf/7d/c895f076e552cb39ea0491f62ff6551cb3e60323a7496017182bd57cc314/tokenizers-0.20.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:bdd67a0e3503a9a7cf8bc5a4a49cdde5fa5bada09a51e4c7e1c73900297539bd", size = 3405410 }, + { url = "https://files.pythonhosted.org/packages/24/59/664121cb41b4f738479e2e1271013a2a7c9160955922536fb723a9c690b7/tokenizers-0.20.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:689b93d2e26d04da337ac407acec8b5d081d8d135e3e5066a88edd5bdb5aff89", size = 2977249 }, + { url = "https://files.pythonhosted.org/packages/d4/ab/ceb7bdb3394431e92b18123faef9862877009f61377bfa45ffe5135747a5/tokenizers-0.20.1-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:0c6a796ddcd9a19ad13cf146997cd5895a421fe6aec8fd970d69f9117bddb45c", size = 8989781 }, + { url = "https://files.pythonhosted.org/packages/bb/37/eaa072b848471d31ae3df6e6d5be5ae594ed5fe39ca921e65cabf193dbde/tokenizers-0.20.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:3ea919687aa7001a8ff1ba36ac64f165c4e89035f57998fa6cedcfd877be619d", size = 9304427 }, + { url = "https://files.pythonhosted.org/packages/41/ff/4aeb924d09f6561209b57af9123a0a28fa69472cc71ee40415f036253203/tokenizers-0.20.1-cp312-none-win32.whl", hash = "sha256:6d3ac5c1f48358ffe20086bf065e843c0d0a9fce0d7f0f45d5f2f9fba3609ca5", size = 2195986 }, + { url = "https://files.pythonhosted.org/packages/7e/ba/18bf6a7ad04f8225b71aa862b57188748d1d81e268de4a9aac1aed237246/tokenizers-0.20.1-cp312-none-win_amd64.whl", hash = "sha256:b0874481aea54a178f2bccc45aa2d0c99cd3f79143a0948af6a9a21dcc49173b", size = 2377984 }, + { url = "https://files.pythonhosted.org/packages/4b/9e/cf0911565ae302e4e4ed3d53bba28f2db75a9418f4e89e2434246723f01a/tokenizers-0.20.1-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:48689da7a395df41114f516208d6550e3e905e1239cc5ad386686d9358e9cef0", size = 2666975 }, + { url = "https://files.pythonhosted.org/packages/37/98/8221a62aed679aefcbc1793ed8bb33f1e060f8b7d95bb20809db1b5c0e0e/tokenizers-0.20.1-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:712f90ea33f9bd2586b4a90d697c26d56d0a22fd3c91104c5858c4b5b6489a79", size = 2557365 }, + { url = "https://files.pythonhosted.org/packages/97/e3/167ca1981b3f512030a28f591b8ef786585b625d45f0fbf1c42723474ecd/tokenizers-0.20.1-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:359eceb6a620c965988fc559cebc0a98db26713758ec4df43fb76d41486a8ed5", size = 2940885 }, + { url = "https://files.pythonhosted.org/packages/c1/e6/ec76a7761eb7ba3cf95e2485cb2e7999a8eb0900d771616c0efa61beb1cd/tokenizers-0.20.1-pp310-pypy310_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0d3caf244ce89d24c87545aafc3448be15870096e796c703a0d68547187192e1", size = 3092338 }, + { url = "https://files.pythonhosted.org/packages/9c/2c/9f04aa030ba8994d478ab35464f8c541aad264556811f12afce9369cc0d3/tokenizers-0.20.1-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:03b03cf8b9a32254b1bf8a305fb95c6daf1baae0c1f93b27f2b08c9759f41dee", size = 2981389 }, + { url = "https://files.pythonhosted.org/packages/cb/f7/79a74f8c54d1232ddbd68967ce56a00cc9589a31b94bee4cf9f34af91ace/tokenizers-0.20.1-pp310-pypy310_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:218e5a3561561ea0f0ef1559c6d95b825308dbec23fb55b70b92589e7ff2e1e8", size = 8986321 }, + { url = "https://files.pythonhosted.org/packages/d4/f2/ea998aaf69966a87f92e31db7cba887125994bb9cd9a4dfcc83ac202d446/tokenizers-0.20.1-pp310-pypy310_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:f40df5e0294a95131cc5f0e0eb91fe86d88837abfbee46b9b3610b09860195a7", size = 9300207 }, ] [[package]] @@ -4438,7 +4340,7 @@ wheels = [ [[package]] name = "trio" -version = "0.26.2" +version = "0.27.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "attrs" }, @@ -4449,9 +4351,9 @@ dependencies = [ { name = "sniffio" }, { name = "sortedcontainers" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/9a/03/ab0e9509be0c6465e2773768ec25ee0cb8053c0b91471ab3854bbf2294b2/trio-0.26.2.tar.gz", hash = "sha256:0346c3852c15e5c7d40ea15972c4805689ef2cb8b5206f794c9c19450119f3a4", size = 561156 } +sdist = { url = "https://files.pythonhosted.org/packages/17/d1/a83dee5be404da7afe5a71783a33b8907bacb935a6dc8c69ab785e4a3eed/trio-0.27.0.tar.gz", hash = "sha256:1dcc95ab1726b2da054afea8fd761af74bad79bd52381b84eae408e983c76831", size = 568064 } wheels = [ - { url = "https://files.pythonhosted.org/packages/1c/70/efa56ce2271c44a7f4f43533a0477e6854a0948e9f7b76491de1fd3be7c9/trio-0.26.2-py3-none-any.whl", hash = "sha256:c5237e8133eb0a1d72f09a971a55c28ebe69e351c783fc64bc37db8db8bbe1d0", size = 475996 }, + { url = "https://files.pythonhosted.org/packages/3c/83/ec3196c360afffbc5b342ead48d1eb7393dd74fa70bca75d33905a86f211/trio-0.27.0-py3-none-any.whl", hash = "sha256:68eabbcf8f457d925df62da780eff15ff5dc68fd6b367e2dde59f7aaf2a0b884", size = 481734 }, ] [[package]] @@ -4485,14 +4387,14 @@ wheels = [ [[package]] name = "types-requests" -version = "2.32.0.20240914" +version = "2.32.0.20241016" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "urllib3" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/9b/9e/aea33405c230cc3984c9f1065012d3a2003cef910730c367a0e91e7a4901/types-requests-2.32.0.20240914.tar.gz", hash = "sha256:2850e178db3919d9bf809e434eef65ba49d0e7e33ac92d588f4a5e295fffd405", size = 18030 } +sdist = { url = "https://files.pythonhosted.org/packages/fa/3c/4f2a430c01a22abd49a583b6b944173e39e7d01b688190a5618bd59a2e22/types-requests-2.32.0.20241016.tar.gz", hash = "sha256:0d9cad2f27515d0e3e3da7134a1b6f28fb97129d86b867f24d9c726452634d95", size = 18065 } wheels = [ - { url = "https://files.pythonhosted.org/packages/8f/55/ea44dad71b9d92f86198f7448f5ba46ac919355f4f69bb1c0fa1af02b1b4/types_requests-2.32.0.20240914-py3-none-any.whl", hash = "sha256:59c2f673eb55f32a99b2894faf6020e1a9f4a402ad0f192bfee0b64469054310", size = 15838 }, + { url = "https://files.pythonhosted.org/packages/d7/01/485b3026ff90e5190b5e24f1711522e06c79f4a56c8f4b95848ac072e20f/types_requests-2.32.0.20241016-py3-none-any.whl", hash = "sha256:4195d62d6d3e043a4eaaf08ff8a62184584d2e8684e9d2aa178c7915a7da3747", size = 15836 }, ] [[package]] @@ -4542,41 +4444,41 @@ socks = [ [[package]] name = "uv" -version = "0.4.25" +version = "0.4.26" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/d0/bc/1a013408b7f9f437385705652f404b6b15127ecf108327d13be493bdfb81/uv-0.4.25.tar.gz", hash = "sha256:d39077cdfe3246885fcdf32e7066ae731a166101d063629f9cea08738f79e6a3", size = 2064863 } +sdist = { url = "https://files.pythonhosted.org/packages/cb/90/500da91a6d2fdad8060d27b0c2dd948bb807a7cfc5fe32abc90dfaeb363f/uv-0.4.26.tar.gz", hash = "sha256:e9f45d8765a037a13ddedebb9e36fdcf06b7957654cfa8055d84f19eba12957e", size = 2072287 } wheels = [ - { url = "https://files.pythonhosted.org/packages/84/18/9c9056d373620b1cf5182ce9b2d258e86d117d667cf8883e12870f2a5edf/uv-0.4.25-py3-none-linux_armv6l.whl", hash = "sha256:94fb2b454afa6bdfeeea4b4581c878944ca9cf3a13712e6762f245f5fbaaf952", size = 13028246 }, - { url = "https://files.pythonhosted.org/packages/a1/19/8a3f09aba30ac5433dfecde55d5241a07c96bb12340c3b810bc58188a12e/uv-0.4.25-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:a7c3a18c20ddb527d296d1222bddf42b78031c50b5b4609d426569b5fb61f5b0", size = 13175265 }, - { url = "https://files.pythonhosted.org/packages/e8/c9/2f924bb29bd53c51b839c1c6126bd2cf4c451d4a7d8f34be078f9e31c57e/uv-0.4.25-py3-none-macosx_11_0_arm64.whl", hash = "sha256:18100f0f36419a154306ed6211e3490bf18384cdf3f1a0950848bf64b62fa251", size = 12255610 }, - { url = "https://files.pythonhosted.org/packages/b2/5a/d8f8971aeb3389679505cf633a786cd72a96ce232f80f14cfe5a693b4c64/uv-0.4.25-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.musllinux_1_1_aarch64.whl", hash = "sha256:6e981b1465e30102e41946adede9cb08051a5d70c6daf09f91a7ea84f0b75c08", size = 12506511 }, - { url = "https://files.pythonhosted.org/packages/e3/96/8c73520daeba5022cec8749e44afd4ca9ef774bf728af9c258bddec3577f/uv-0.4.25-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:578ae385fad6bd6f3868828e33d54994c716b315b1bc49106ec1f54c640837e4", size = 12836250 }, - { url = "https://files.pythonhosted.org/packages/67/3d/b0e810d365fb154fe1d380a0f43ee35a683cf9162f2501396d711bec2621/uv-0.4.25-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2d29a78f011ecc2f31c13605acb6574c2894c06d258b0f8d0dbb899986800450", size = 13521303 }, - { url = "https://files.pythonhosted.org/packages/2d/f4/dd3830ec7fc6e7e5237c184f30f2dbfed4f93605e472147eca1373bcc72b/uv-0.4.25-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:ec181be2bda10651a3558156409ac481549983e0276d0e3645e3b1464e7f8715", size = 14105308 }, - { url = "https://files.pythonhosted.org/packages/f4/4e/0fca02f8681e4870beda172552e747e0424f6e9186546b00a5e92525fea9/uv-0.4.25-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:50c7d0d9e7f392f81b13bf3b7e37768d1486f2fc9d533a54982aa0ed11e4db23", size = 13859475 }, - { url = "https://files.pythonhosted.org/packages/33/07/1100e9bc652f2850930f466869515d16ffe9582aaaaa99bac332ebdfe3ea/uv-0.4.25-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2fc35b5273f1e018aecd66b70e0fd7d2eb6698853dde3e2fc644e7ebf9f825b1", size = 18100840 }, - { url = "https://files.pythonhosted.org/packages/fa/98/ba1cb7dd2aa639a064a9e49721e08f12a3424456d60dde1327e7c6437930/uv-0.4.25-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a7022a71ff63a3838796f40e954b76bf7820fc27e96fe002c537e75ff8e34f1d", size = 13645464 }, - { url = "https://files.pythonhosted.org/packages/0d/05/b97fb8c828a070e8291826922b2712d1146b11563b4860bc9ba80f5635d1/uv-0.4.25-py3-none-manylinux_2_28_aarch64.whl", hash = "sha256:e02afb0f6d4b58718347f7d7cfa5a801e985ce42181ba971ed85ef149f6658ca", size = 12694995 }, - { url = "https://files.pythonhosted.org/packages/b3/97/63df050811379130202898f60e735a1a331ba3a93b8aa1e9bb466f533913/uv-0.4.25-py3-none-musllinux_1_1_armv7l.whl", hash = "sha256:3d7680795ea78cdbabbcce73d039b2651cf1fa635ddc1aa3082660f6d6255c50", size = 12831737 }, - { url = "https://files.pythonhosted.org/packages/dc/e0/08352dcffa6e8435328861ea60b2c05e8bd030f1e93998443ba66209db7b/uv-0.4.25-py3-none-musllinux_1_1_i686.whl", hash = "sha256:aae9dcafd20d5ba978c8a4939ab942e8e2e155c109e9945207fbbd81d2892c9e", size = 13273529 }, - { url = "https://files.pythonhosted.org/packages/25/f4/eaf95e5eee4e2e69884df0953d094deae07216f72068ef1df08c0f49841d/uv-0.4.25-py3-none-musllinux_1_1_ppc64le.whl", hash = "sha256:4c55040e67470f2b73e95e432aba06f103a0b348ea0b9c6689b1029c8d9e89fd", size = 15039860 }, - { url = "https://files.pythonhosted.org/packages/69/04/482b1cc9e8d599c7d766c4ba2d7a512ed3989921443792f92f26b8d44fe6/uv-0.4.25-py3-none-musllinux_1_1_x86_64.whl", hash = "sha256:bdbfd0c476b9e80a3f89af96aed6dd7d2782646311317a9c72614ccce99bb2ad", size = 13776302 }, - { url = "https://files.pythonhosted.org/packages/cd/7e/3d1cb735cc3df6341ac884b73eeec1f51a29192721be40be8e9b1d82666d/uv-0.4.25-py3-none-win32.whl", hash = "sha256:7d266e02fefef930609328c31c075084295c3cb472bab3f69549fad4fd9d82b3", size = 12970553 }, - { url = "https://files.pythonhosted.org/packages/04/e9/c00d2bb4a286b13fad0f06488ea9cbe9e76d0efcd81e7a907f72195d5b83/uv-0.4.25-py3-none-win_amd64.whl", hash = "sha256:be2a4fc4fcade9ea5e67e51738c95644360d6e59b6394b74fc579fb617f902f7", size = 14702875 }, + { url = "https://files.pythonhosted.org/packages/bf/1f/1e1af6656e83a9b0347c22328ad6d899760819e5f19fa80aee88b56d1e02/uv-0.4.26-py3-none-linux_armv6l.whl", hash = "sha256:d1ca5183afab454f28573a286811019b3552625af2cd1cd3996049d3bbfdb1ca", size = 13055731 }, + { url = "https://files.pythonhosted.org/packages/92/27/2235628adcf468bc6be98b84e509afa54240d359b4705454e7e957a9650d/uv-0.4.26-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:391a6f5e31b212cb72a8f460493bbdf4088e66049666ad064ac8530230031289", size = 13230933 }, + { url = "https://files.pythonhosted.org/packages/36/ce/dd9b312c2230705119d3de910a32bbd32dc500bf147c7a0076a31bdfd153/uv-0.4.26-py3-none-macosx_11_0_arm64.whl", hash = "sha256:acaa25b304db6f1e8064d3280532ecb80a58346e37f4199659269847848c4da0", size = 12266060 }, + { url = "https://files.pythonhosted.org/packages/4d/64/ef6532d84841f5e77e240df9a7dbdc3ca5bf45fae323f247b7bd57bea037/uv-0.4.26-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.musllinux_1_1_aarch64.whl", hash = "sha256:2ddb60d508b668b8da055651b30ff56c1efb79d57b064c218a7622b5c74b2af8", size = 12539139 }, + { url = "https://files.pythonhosted.org/packages/1b/30/b4f98f5e28a8c41e370be1a6ef9d48a619e20d3caeb2bf437f1560fab2df/uv-0.4.26-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:6f66f11e088d231b7e305f089dc949b0e6b1d65e0a877b50ba5c3ae26e151144", size = 12867987 }, + { url = "https://files.pythonhosted.org/packages/7f/5f/605fe50a0710a78013ad5b2b1034d8f056b5971fc023b6510a24e9350637/uv-0.4.26-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e086ebe200e9718e9622af405d45caad9d84b60824306fcb220335fe6fc90966", size = 13594669 }, + { url = "https://files.pythonhosted.org/packages/ae/4b/e3d02b963f9f83f76d1b0757204a210aceebe8ae16f69fcb431b09bc3926/uv-0.4.26-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:41f9876c22ad5b4518bffe9e50ec7169e242b64f139cdcaf42a76f70a9bd5c78", size = 14156314 }, + { url = "https://files.pythonhosted.org/packages/40/8e/7803d3b76d8694ba939509e49d0c37e70a6d580ef5b7f0242701533920e5/uv-0.4.26-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:6091075420eda571b0377d351c393b096514cb036a3199e033e003edaa0ff880", size = 13897243 }, + { url = "https://files.pythonhosted.org/packages/97/ee/8d5b63b590d3cb9dae5ac396cc099dcad2e368794d77e34a52dd896e5d8e/uv-0.4.26-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1214caacc6b9f9c72749634c7a82a5d93123a44b70a1fa6a9d13993c126ca33e", size = 17961411 }, + { url = "https://files.pythonhosted.org/packages/da/9a/5a6a3ea6c2bc42904343897b666cb8c9ac921bf9551b463aeb592cd49d45/uv-0.4.26-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9a63a6fe6f249a9fff72328204c3e6b457aae5914590e6881b9b39dcc72d24df", size = 13700388 }, + { url = "https://files.pythonhosted.org/packages/33/52/009ea704318c5d0f290fb2ea4e1874d5625a60b290c6e5e49aae4d140091/uv-0.4.26-py3-none-manylinux_2_28_aarch64.whl", hash = "sha256:c4c69532cb4d0c1e160883142b8bf0133a5a67e9aed5148e13743ae55c2dfc03", size = 12702036 }, + { url = "https://files.pythonhosted.org/packages/72/38/4dc590872e5c1810c6ec203d9b070278ed396a1ebf3396e556079946c894/uv-0.4.26-py3-none-musllinux_1_1_armv7l.whl", hash = "sha256:9560c2eb234ea92276bbc647854d4a9e75556981c1193c3cc59f6613f7d177f2", size = 12854127 }, + { url = "https://files.pythonhosted.org/packages/76/73/124820b37d1c8784fbebfc4b5b7812b4fa8e4e680c35b77a38be444dac9f/uv-0.4.26-py3-none-musllinux_1_1_i686.whl", hash = "sha256:a41bdd09b9a3ddc8f459c73e924485e1caae43e43305cedb65f5feac05cf184a", size = 13309009 }, + { url = "https://files.pythonhosted.org/packages/f4/e7/37cf24861c6f76ba85ac80c15c391848524668be8dcd218ed04da80a96b6/uv-0.4.26-py3-none-musllinux_1_1_ppc64le.whl", hash = "sha256:23cee82020b9e973a5feba81c2cf359a5a09020216d98534926f45ee7b74521d", size = 15079442 }, + { url = "https://files.pythonhosted.org/packages/ca/ac/fa29079ee0c26c65efca5c447ef6ce66f0afca1f73c09d599229d2d9dfd4/uv-0.4.26-py3-none-musllinux_1_1_x86_64.whl", hash = "sha256:468f806e841229c0bd6e1cffaaffc064720704623890cee15b42b877cef748c5", size = 13827888 }, + { url = "https://files.pythonhosted.org/packages/40/e8/f9824ecb8b13da5e8b0e9b8fbc81edb9e0d41923ebc6e287ae2e5a04bc62/uv-0.4.26-py3-none-win32.whl", hash = "sha256:70a108399d6c9e3d1f4a0f105d6d016f97f292dbb6c724e1ed2e6dc9f6872c79", size = 13092190 }, + { url = "https://files.pythonhosted.org/packages/46/91/c76682177dbe46dc0cc9221f9483b186ad3d8e0b59056c2cdae5c011609c/uv-0.4.26-py3-none-win_amd64.whl", hash = "sha256:e826b544020ef407387ed734a89850cac011ee4b5daf94b4f616b71eff2c8a94", size = 14757412 }, ] [[package]] name = "uvicorn" -version = "0.31.0" +version = "0.32.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "click" }, { name = "h11" }, { name = "typing-extensions", marker = "python_full_version < '3.11'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/0a/96/ee52d900f8e41cc35eaebfda76f3619c2e45b741f3ee957d6fe32be1b2aa/uvicorn-0.31.0.tar.gz", hash = "sha256:13bc21373d103859f68fe739608e2eb054a816dea79189bc3ca08ea89a275906", size = 77140 } +sdist = { url = "https://files.pythonhosted.org/packages/e0/fc/1d785078eefd6945f3e5bab5c076e4230698046231eb0f3747bc5c8fa992/uvicorn-0.32.0.tar.gz", hash = "sha256:f78b36b143c16f54ccdb8190d0a26b5f1901fe5a3c777e1ab29f26391af8551e", size = 77564 } wheels = [ - { url = "https://files.pythonhosted.org/packages/05/12/206aca5442524d16be7702d08b453d7c274c86fd759266b1f709d4ef43ba/uvicorn-0.31.0-py3-none-any.whl", hash = "sha256:cac7be4dd4d891c363cd942160a7b02e69150dcbc7a36be04d5f4af4b17c8ced", size = 63656 }, + { url = "https://files.pythonhosted.org/packages/eb/14/78bd0e95dd2444b6caacbca2b730671d4295ccb628ef58b81bee903629df/uvicorn-0.32.0-py3-none-any.whl", hash = "sha256:60b8f3a5ac027dcd31448f411ced12b5ef452c646f76f02f8cc3f25d8d26fd82", size = 63723 }, ] [package.optional-dependencies] @@ -4592,28 +4494,34 @@ standard = [ [[package]] name = "uvloop" -version = "0.20.0" +version = "0.21.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/bc/f1/dc9577455e011ad43d9379e836ee73f40b4f99c02946849a44f7ae64835e/uvloop-0.20.0.tar.gz", hash = "sha256:4603ca714a754fc8d9b197e325db25b2ea045385e8a3ad05d3463de725fdf469", size = 2329938 } +sdist = { url = "https://files.pythonhosted.org/packages/af/c0/854216d09d33c543f12a44b393c402e89a920b1a0a7dc634c42de91b9cf6/uvloop-0.21.0.tar.gz", hash = "sha256:3bf12b0fda68447806a7ad847bfa591613177275d35b6724b1ee573faa3704e3", size = 2492741 } wheels = [ - { url = "https://files.pythonhosted.org/packages/f3/69/cc1ad125ea8ce4a4d3ba7d9836062c3fc9063cf163ddf0f168e73f3268e3/uvloop-0.20.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:9ebafa0b96c62881d5cafa02d9da2e44c23f9f0cd829f3a32a6aff771449c996", size = 1363922 }, - { url = "https://files.pythonhosted.org/packages/f7/45/5a3f7a32372e4a90dfd83f30507183ec38990b8c5930ed7e36c6a15af47b/uvloop-0.20.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:35968fc697b0527a06e134999eef859b4034b37aebca537daeb598b9d45a137b", size = 760386 }, - { url = "https://files.pythonhosted.org/packages/9e/a5/9e973b25ade12c938940751bce71d0cb36efee3489014471f7d9c0a3c379/uvloop-0.20.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b16696f10e59d7580979b420eedf6650010a4a9c3bd8113f24a103dfdb770b10", size = 3432586 }, - { url = "https://files.pythonhosted.org/packages/a9/e0/0bec8a25b2e9cf14fdfcf0229637b437c923b4e5ca22f8e988363c49bb51/uvloop-0.20.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9b04d96188d365151d1af41fa2d23257b674e7ead68cfd61c725a422764062ae", size = 3431802 }, - { url = "https://files.pythonhosted.org/packages/95/3b/14cef46dcec6237d858666a4a1fdb171361528c70fcd930bfc312920e7a9/uvloop-0.20.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:94707205efbe809dfa3a0d09c08bef1352f5d3d6612a506f10a319933757c006", size = 4144444 }, - { url = "https://files.pythonhosted.org/packages/9d/5a/0ac516562ff783f760cab3b061f10fdeb4a9f985ad4b44e7e4564ff11691/uvloop-0.20.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:89e8d33bb88d7263f74dc57d69f0063e06b5a5ce50bb9a6b32f5fcbe655f9e73", size = 4147039 }, - { url = "https://files.pythonhosted.org/packages/64/bf/45828beccf685b7ed9638d9b77ef382b470c6ca3b5bff78067e02ffd5663/uvloop-0.20.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:e50289c101495e0d1bb0bfcb4a60adde56e32f4449a67216a1ab2750aa84f037", size = 1320593 }, - { url = "https://files.pythonhosted.org/packages/27/c0/3c24e50bee7802a2add96ca9f0d5eb0ebab07e0a5615539d38aeb89499b9/uvloop-0.20.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:e237f9c1e8a00e7d9ddaa288e535dc337a39bcbf679f290aee9d26df9e72bce9", size = 736676 }, - { url = "https://files.pythonhosted.org/packages/83/ce/ffa3c72954eae36825acfafd2b6a9221d79abd2670c0d25e04d6ef4a2007/uvloop-0.20.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:746242cd703dc2b37f9d8b9f173749c15e9a918ddb021575a0205ec29a38d31e", size = 3494573 }, - { url = "https://files.pythonhosted.org/packages/46/6d/4caab3a36199ba52b98d519feccfcf48921d7a6649daf14a93c7e77497e9/uvloop-0.20.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:82edbfd3df39fb3d108fc079ebc461330f7c2e33dbd002d146bf7c445ba6e756", size = 3489932 }, - { url = "https://files.pythonhosted.org/packages/e4/4f/49c51595bd794945c88613df88922c38076eae2d7653f4624aa6f4980b07/uvloop-0.20.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:80dc1b139516be2077b3e57ce1cb65bfed09149e1d175e0478e7a987863b68f0", size = 4185596 }, - { url = "https://files.pythonhosted.org/packages/b8/94/7e256731260d313f5049717d1c4582d52a3b132424c95e16954a50ab95d3/uvloop-0.20.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:4f44af67bf39af25db4c1ac27e82e9665717f9c26af2369c404be865c8818dcf", size = 4185746 }, - { url = "https://files.pythonhosted.org/packages/2d/64/31cbd379d6e260ac8de3f672f904e924f09715c3f192b09f26cc8e9f574c/uvloop-0.20.0-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:4b75f2950ddb6feed85336412b9a0c310a2edbcf4cf931aa5cfe29034829676d", size = 1324302 }, - { url = "https://files.pythonhosted.org/packages/1e/6b/9207e7177ff30f78299401f2e1163ea41130d4fd29bcdc6d12572c06b728/uvloop-0.20.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:77fbc69c287596880ecec2d4c7a62346bef08b6209749bf6ce8c22bbaca0239e", size = 738105 }, - { url = "https://files.pythonhosted.org/packages/c1/ba/b64b10f577519d875992dc07e2365899a1a4c0d28327059ce1e1bdfb6854/uvloop-0.20.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6462c95f48e2d8d4c993a2950cd3d31ab061864d1c226bbf0ee2f1a8f36674b9", size = 4090658 }, - { url = "https://files.pythonhosted.org/packages/0a/f8/5ceea6876154d926604f10c1dd896adf9bce6d55a55911364337b8a5ed8d/uvloop-0.20.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:649c33034979273fa71aa25d0fe120ad1777c551d8c4cd2c0c9851d88fcb13ab", size = 4173357 }, - { url = "https://files.pythonhosted.org/packages/18/b2/117ab6bfb18274753fbc319607bf06e216bd7eea8be81d5bac22c912d6a7/uvloop-0.20.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:3a609780e942d43a275a617c0839d85f95c334bad29c4c0918252085113285b5", size = 4029868 }, - { url = "https://files.pythonhosted.org/packages/6f/52/deb4be09060637ef4752adaa0b75bf770c20c823e8108705792f99cd4a6f/uvloop-0.20.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:aea15c78e0d9ad6555ed201344ae36db5c63d428818b4b2a42842b3870127c00", size = 4115980 }, + { url = "https://files.pythonhosted.org/packages/3d/76/44a55515e8c9505aa1420aebacf4dd82552e5e15691654894e90d0bd051a/uvloop-0.21.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:ec7e6b09a6fdded42403182ab6b832b71f4edaf7f37a9a0e371a01db5f0cb45f", size = 1442019 }, + { url = "https://files.pythonhosted.org/packages/35/5a/62d5800358a78cc25c8a6c72ef8b10851bdb8cca22e14d9c74167b7f86da/uvloop-0.21.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:196274f2adb9689a289ad7d65700d37df0c0930fd8e4e743fa4834e850d7719d", size = 801898 }, + { url = "https://files.pythonhosted.org/packages/f3/96/63695e0ebd7da6c741ccd4489b5947394435e198a1382349c17b1146bb97/uvloop-0.21.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f38b2e090258d051d68a5b14d1da7203a3c3677321cf32a95a6f4db4dd8b6f26", size = 3827735 }, + { url = "https://files.pythonhosted.org/packages/61/e0/f0f8ec84979068ffae132c58c79af1de9cceeb664076beea86d941af1a30/uvloop-0.21.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:87c43e0f13022b998eb9b973b5e97200c8b90823454d4bc06ab33829e09fb9bb", size = 3825126 }, + { url = "https://files.pythonhosted.org/packages/bf/fe/5e94a977d058a54a19df95f12f7161ab6e323ad49f4dabc28822eb2df7ea/uvloop-0.21.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:10d66943def5fcb6e7b37310eb6b5639fd2ccbc38df1177262b0640c3ca68c1f", size = 3705789 }, + { url = "https://files.pythonhosted.org/packages/26/dd/c7179618e46092a77e036650c1f056041a028a35c4d76945089fcfc38af8/uvloop-0.21.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:67dd654b8ca23aed0a8e99010b4c34aca62f4b7fce88f39d452ed7622c94845c", size = 3800523 }, + { url = "https://files.pythonhosted.org/packages/57/a7/4cf0334105c1160dd6819f3297f8700fda7fc30ab4f61fbf3e725acbc7cc/uvloop-0.21.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:c0f3fa6200b3108919f8bdabb9a7f87f20e7097ea3c543754cabc7d717d95cf8", size = 1447410 }, + { url = "https://files.pythonhosted.org/packages/8c/7c/1517b0bbc2dbe784b563d6ab54f2ef88c890fdad77232c98ed490aa07132/uvloop-0.21.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:0878c2640cf341b269b7e128b1a5fed890adc4455513ca710d77d5e93aa6d6a0", size = 805476 }, + { url = "https://files.pythonhosted.org/packages/ee/ea/0bfae1aceb82a503f358d8d2fa126ca9dbdb2ba9c7866974faec1cb5875c/uvloop-0.21.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b9fb766bb57b7388745d8bcc53a359b116b8a04c83a2288069809d2b3466c37e", size = 3960855 }, + { url = "https://files.pythonhosted.org/packages/8a/ca/0864176a649838b838f36d44bf31c451597ab363b60dc9e09c9630619d41/uvloop-0.21.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8a375441696e2eda1c43c44ccb66e04d61ceeffcd76e4929e527b7fa401b90fb", size = 3973185 }, + { url = "https://files.pythonhosted.org/packages/30/bf/08ad29979a936d63787ba47a540de2132169f140d54aa25bc8c3df3e67f4/uvloop-0.21.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:baa0e6291d91649c6ba4ed4b2f982f9fa165b5bbd50a9e203c416a2797bab3c6", size = 3820256 }, + { url = "https://files.pythonhosted.org/packages/da/e2/5cf6ef37e3daf2f06e651aae5ea108ad30df3cb269102678b61ebf1fdf42/uvloop-0.21.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:4509360fcc4c3bd2c70d87573ad472de40c13387f5fda8cb58350a1d7475e58d", size = 3937323 }, + { url = "https://files.pythonhosted.org/packages/8c/4c/03f93178830dc7ce8b4cdee1d36770d2f5ebb6f3d37d354e061eefc73545/uvloop-0.21.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:359ec2c888397b9e592a889c4d72ba3d6befba8b2bb01743f72fffbde663b59c", size = 1471284 }, + { url = "https://files.pythonhosted.org/packages/43/3e/92c03f4d05e50f09251bd8b2b2b584a2a7f8fe600008bcc4523337abe676/uvloop-0.21.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:f7089d2dc73179ce5ac255bdf37c236a9f914b264825fdaacaded6990a7fb4c2", size = 821349 }, + { url = "https://files.pythonhosted.org/packages/a6/ef/a02ec5da49909dbbfb1fd205a9a1ac4e88ea92dcae885e7c961847cd51e2/uvloop-0.21.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:baa4dcdbd9ae0a372f2167a207cd98c9f9a1ea1188a8a526431eef2f8116cc8d", size = 4580089 }, + { url = "https://files.pythonhosted.org/packages/06/a7/b4e6a19925c900be9f98bec0a75e6e8f79bb53bdeb891916609ab3958967/uvloop-0.21.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:86975dca1c773a2c9864f4c52c5a55631038e387b47eaf56210f873887b6c8dc", size = 4693770 }, + { url = "https://files.pythonhosted.org/packages/ce/0c/f07435a18a4b94ce6bd0677d8319cd3de61f3a9eeb1e5f8ab4e8b5edfcb3/uvloop-0.21.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:461d9ae6660fbbafedd07559c6a2e57cd553b34b0065b6550685f6653a98c1cb", size = 4451321 }, + { url = "https://files.pythonhosted.org/packages/8f/eb/f7032be105877bcf924709c97b1bf3b90255b4ec251f9340cef912559f28/uvloop-0.21.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:183aef7c8730e54c9a3ee3227464daed66e37ba13040bb3f350bc2ddc040f22f", size = 4659022 }, + { url = "https://files.pythonhosted.org/packages/3f/8d/2cbef610ca21539f0f36e2b34da49302029e7c9f09acef0b1c3b5839412b/uvloop-0.21.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:bfd55dfcc2a512316e65f16e503e9e450cab148ef11df4e4e679b5e8253a5281", size = 1468123 }, + { url = "https://files.pythonhosted.org/packages/93/0d/b0038d5a469f94ed8f2b2fce2434a18396d8fbfb5da85a0a9781ebbdec14/uvloop-0.21.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:787ae31ad8a2856fc4e7c095341cccc7209bd657d0e71ad0dc2ea83c4a6fa8af", size = 819325 }, + { url = "https://files.pythonhosted.org/packages/50/94/0a687f39e78c4c1e02e3272c6b2ccdb4e0085fda3b8352fecd0410ccf915/uvloop-0.21.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5ee4d4ef48036ff6e5cfffb09dd192c7a5027153948d85b8da7ff705065bacc6", size = 4582806 }, + { url = "https://files.pythonhosted.org/packages/d2/19/f5b78616566ea68edd42aacaf645adbf71fbd83fc52281fba555dc27e3f1/uvloop-0.21.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f3df876acd7ec037a3d005b3ab85a7e4110422e4d9c1571d4fc89b0fc41b6816", size = 4701068 }, + { url = "https://files.pythonhosted.org/packages/47/57/66f061ee118f413cd22a656de622925097170b9380b30091b78ea0c6ea75/uvloop-0.21.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:bd53ecc9a0f3d87ab847503c2e1552b690362e005ab54e8a48ba97da3924c0dc", size = 4454428 }, + { url = "https://files.pythonhosted.org/packages/63/9a/0962b05b308494e3202d3f794a6e85abe471fe3cafdbcf95c2e8c713aabd/uvloop-0.21.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:a5c39f217ab3c663dc699c04cbd50c13813e31d917642d459fdcec07555cc553", size = 4660018 }, ] [[package]] @@ -4622,15 +4530,15 @@ version = "5.1.0" source = { registry = "https://pypi.org/simple" } resolution-markers = [ "python_full_version < '3.11' and platform_python_implementation == 'PyPy'", - "python_full_version == '3.11.*'", + "python_full_version == '3.11.*' and platform_python_implementation == 'PyPy'", "python_full_version >= '3.12' and python_full_version < '3.12.4'", "python_full_version >= '3.12.4' and python_full_version < '3.13'", "python_full_version >= '3.13'", ] dependencies = [ - { name = "pyyaml", marker = "platform_python_implementation == 'PyPy' or python_full_version >= '3.11'" }, - { name = "wrapt", marker = "platform_python_implementation == 'PyPy' or python_full_version >= '3.11'" }, - { name = "yarl", marker = "platform_python_implementation == 'PyPy' or python_full_version >= '3.11'" }, + { name = "pyyaml", marker = "platform_python_implementation == 'PyPy' or python_full_version >= '3.12'" }, + { name = "wrapt", marker = "platform_python_implementation == 'PyPy' or python_full_version >= '3.12'" }, + { name = "yarl", marker = "platform_python_implementation == 'PyPy' or python_full_version >= '3.12'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/a5/ea/a166a3cce4ac5958ba9bbd9768acdb1ba38ae17ff7986da09fa5b9dbc633/vcrpy-5.1.0.tar.gz", hash = "sha256:bbf1532f2618a04f11bce2a99af3a9647a32c880957293ff91e0a5f187b6b3d2", size = 84576 } wheels = [ @@ -4643,12 +4551,13 @@ version = "6.0.2" source = { registry = "https://pypi.org/simple" } resolution-markers = [ "python_full_version < '3.11' and platform_python_implementation != 'PyPy'", + "python_full_version == '3.11.*' and platform_python_implementation != 'PyPy'", ] dependencies = [ - { name = "pyyaml", marker = "python_full_version < '3.11' and platform_python_implementation != 'PyPy'" }, - { name = "urllib3", marker = "python_full_version < '3.11' and platform_python_implementation != 'PyPy'" }, - { name = "wrapt", marker = "python_full_version < '3.11' and platform_python_implementation != 'PyPy'" }, - { name = "yarl", marker = "python_full_version < '3.11' and platform_python_implementation != 'PyPy'" }, + { name = "pyyaml", marker = "python_full_version < '3.12' and platform_python_implementation != 'PyPy'" }, + { name = "urllib3", marker = "python_full_version < '3.12' and platform_python_implementation != 'PyPy'" }, + { name = "wrapt", marker = "python_full_version < '3.12' and platform_python_implementation != 'PyPy'" }, + { name = "yarl", marker = "python_full_version < '3.12' and platform_python_implementation != 'PyPy'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/16/4e/fff59599826793f9e3460c22c0af0377abb27dc9781a7d5daca8cb03da25/vcrpy-6.0.2.tar.gz", hash = "sha256:88e13d9111846745898411dbc74a75ce85870af96dd320d75f1ee33158addc09", size = 85472 } wheels = [ @@ -4657,16 +4566,16 @@ wheels = [ [[package]] name = "virtualenv" -version = "20.26.6" +version = "20.27.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "distlib" }, { name = "filelock" }, { name = "platformdirs" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/3f/40/abc5a766da6b0b2457f819feab8e9203cbeae29327bd241359f866a3da9d/virtualenv-20.26.6.tar.gz", hash = "sha256:280aede09a2a5c317e409a00102e7077c6432c5a38f0ef938e643805a7ad2c48", size = 9372482 } +sdist = { url = "https://files.pythonhosted.org/packages/10/7f/192dd6ab6d91ebea7adf6c030eaf549b1ec0badda9f67a77b633602f66ac/virtualenv-20.27.0.tar.gz", hash = "sha256:2ca56a68ed615b8fe4326d11a0dca5dfbe8fd68510fb6c6349163bed3c15f2b2", size = 6483858 } wheels = [ - { url = "https://files.pythonhosted.org/packages/59/90/57b8ac0c8a231545adc7698c64c5a36fa7cd8e376c691b9bde877269f2eb/virtualenv-20.26.6-py3-none-any.whl", hash = "sha256:7345cc5b25405607a624d8418154577459c3e0277f5466dd79c49d5e492995f2", size = 5999862 }, + { url = "https://files.pythonhosted.org/packages/c8/15/828ec11907aee2349a9342fa71fba4ba7f3af938162a382dd7da339dea16/virtualenv-20.27.0-py3-none-any.whl", hash = "sha256:44a72c29cceb0ee08f300b314848c86e57bf8d1f13107a5e671fb9274138d655", size = 3110969 }, ] [[package]] @@ -4905,75 +4814,80 @@ wheels = [ [[package]] name = "yarl" -version = "1.13.1" +version = "1.16.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "idna" }, { name = "multidict" }, + { name = "propcache" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/e0/11/2b8334f4192646677a2e7da435670d043f536088af943ec242f31453e5ba/yarl-1.13.1.tar.gz", hash = "sha256:ec8cfe2295f3e5e44c51f57272afbd69414ae629ec7c6b27f5a410efc78b70a0", size = 165912 } +sdist = { url = "https://files.pythonhosted.org/packages/23/52/e9766cc6c2eab7dd1e9749c52c9879317500b46fb97d4105223f86679f93/yarl-1.16.0.tar.gz", hash = "sha256:b6f687ced5510a9a2474bbae96a4352e5ace5fa34dc44a217b0537fec1db00b4", size = 176548 } wheels = [ - { url = "https://files.pythonhosted.org/packages/db/4e/d161aa815f68a5632167f3d9ce93cb12065329c304db687b43394f17619a/yarl-1.13.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:82e692fb325013a18a5b73a4fed5a1edaa7c58144dc67ad9ef3d604eccd451ad", size = 189674 }, - { url = "https://files.pythonhosted.org/packages/6d/c3/362da910c77ef36675fe5a32bd57b827b76712dd150b8a25551fc91d206b/yarl-1.13.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:df4e82e68f43a07735ae70a2d84c0353e58e20add20ec0af611f32cd5ba43fb4", size = 115573 }, - { url = "https://files.pythonhosted.org/packages/ac/01/803c73f65c63f4262113719da0555b8041ed95ea6e765929b7661b8c3d8c/yarl-1.13.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:ec9dd328016d8d25702a24ee274932aebf6be9787ed1c28d021945d264235b3c", size = 113738 }, - { url = "https://files.pythonhosted.org/packages/87/23/2c195189a0f4234eea0b7a50fc2973fe7485abcbfd432879b6993a5d99f5/yarl-1.13.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5820bd4178e6a639b3ef1db8b18500a82ceab6d8b89309e121a6859f56585b05", size = 443662 }, - { url = "https://files.pythonhosted.org/packages/04/ea/22c607588d580e730abb06d1b376a28685d7152f895440957954d2be23ce/yarl-1.13.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:86c438ce920e089c8c2388c7dcc8ab30dfe13c09b8af3d306bcabb46a053d6f7", size = 469154 }, - { url = "https://files.pythonhosted.org/packages/d8/f6/76ad096858595be3ec15d2c98598937925bece686cf2e7cd283229bd87ad/yarl-1.13.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3de86547c820e4f4da4606d1c8ab5765dd633189791f15247706a2eeabc783ae", size = 463253 }, - { url = "https://files.pythonhosted.org/packages/d2/1f/2e5de9d13157b5b39a1d42db1c6368ab5553ac69cf439d35827cbe26090e/yarl-1.13.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8ca53632007c69ddcdefe1e8cbc3920dd88825e618153795b57e6ebcc92e752a", size = 447938 }, - { url = "https://files.pythonhosted.org/packages/89/78/295efe65aba97b94419e56edb6ef8f3b9fe1f0dbb6c9720119aed6d398c5/yarl-1.13.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d4ee1d240b84e2f213565f0ec08caef27a0e657d4c42859809155cf3a29d1735", size = 432870 }, - { url = "https://files.pythonhosted.org/packages/d9/b6/6ce12ef399e4211fa01c963926dd114751b08552c6ad82ab3155f4391671/yarl-1.13.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:c49f3e379177f4477f929097f7ed4b0622a586b0aa40c07ac8c0f8e40659a1ac", size = 444827 }, - { url = "https://files.pythonhosted.org/packages/09/ea/d49f2fdf3d5bf6246aabe68910807f9f44be80691e19ee604d7275e38664/yarl-1.13.1-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:5c5e32fef09ce101fe14acd0f498232b5710effe13abac14cd95de9c274e689e", size = 449406 }, - { url = "https://files.pythonhosted.org/packages/d9/9c/57ef876f736f4480a381037d22773344f23086591492b749da8eddbd532f/yarl-1.13.1-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:ab9524e45ee809a083338a749af3b53cc7efec458c3ad084361c1dbf7aaf82a2", size = 472776 }, - { url = "https://files.pythonhosted.org/packages/7d/b0/f3601283529e7eb08784d19e3aeefe2b5b8f1254a607b6c0b792bd7696fe/yarl-1.13.1-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:b1481c048fe787f65e34cb06f7d6824376d5d99f1231eae4778bbe5c3831076d", size = 470967 }, - { url = "https://files.pythonhosted.org/packages/1e/20/57a5d8f7537d5e552b5ceb989dcf0c0a41bfed55098cfc3b45f66a96db84/yarl-1.13.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:31497aefd68036d8e31bfbacef915826ca2e741dbb97a8d6c7eac66deda3b606", size = 458793 }, - { url = "https://files.pythonhosted.org/packages/a0/57/0b505b511636f30c4631b63579829b5edd7cea00bef025517d2f5e70eba4/yarl-1.13.1-cp310-cp310-win32.whl", hash = "sha256:1fa56f34b2236f5192cb5fceba7bbb09620e5337e0b6dfe2ea0ddbd19dd5b154", size = 102394 }, - { url = "https://files.pythonhosted.org/packages/5a/49/7c9eef0a40e91460fee9f8a077ec39582399e6a1ad0f70f6a3a1c683cd10/yarl-1.13.1-cp310-cp310-win_amd64.whl", hash = "sha256:1bbb418f46c7f7355084833051701b2301092e4611d9e392360c3ba2e3e69f88", size = 111387 }, - { url = "https://files.pythonhosted.org/packages/37/64/1eaa5d080ceb8742b75a25eff4d510439459ff9c7fbe03e8e929a732ca07/yarl-1.13.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:216a6785f296169ed52cd7dcdc2612f82c20f8c9634bf7446327f50398732a51", size = 189609 }, - { url = "https://files.pythonhosted.org/packages/e2/49/7faf592dd5d4ae4b789988750739c327b81070aa6d428848ce71f6112c1b/yarl-1.13.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:40c6e73c03a6befb85b72da213638b8aaa80fe4136ec8691560cf98b11b8ae6e", size = 115504 }, - { url = "https://files.pythonhosted.org/packages/0c/02/6dd48672009bdf135a298a7250875321098b7cbbca5af8c49d8dae07b635/yarl-1.13.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:2430cf996113abe5aee387d39ee19529327205cda975d2b82c0e7e96e5fdabdc", size = 113754 }, - { url = "https://files.pythonhosted.org/packages/0e/4c/dd49a78833691ccdc15738eb814e37df47f0f25baeefb1cec64ecb4459eb/yarl-1.13.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9fb4134cc6e005b99fa29dbc86f1ea0a298440ab6b07c6b3ee09232a3b48f495", size = 486101 }, - { url = "https://files.pythonhosted.org/packages/36/ec/e5e6ed4344de34d3554a22d181df4d90a4d0f257575c28b767ad8c1add0b/yarl-1.13.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:309c104ecf67626c033845b860d31594a41343766a46fa58c3309c538a1e22b2", size = 505989 }, - { url = "https://files.pythonhosted.org/packages/7d/af/0318b0d03471207b3959e0e6ca2964b689744d8482fdbfdc2958854373b4/yarl-1.13.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f90575e9fe3aae2c1e686393a9689c724cd00045275407f71771ae5d690ccf38", size = 500428 }, - { url = "https://files.pythonhosted.org/packages/c4/09/5e47823e3abb26ddda447b500be28137971d246b0c771a02f855dd06b30b/yarl-1.13.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9d2e1626be8712333a9f71270366f4a132f476ffbe83b689dd6dc0d114796c74", size = 488954 }, - { url = "https://files.pythonhosted.org/packages/9a/c4/e26317d48bd6bf59dfbb6049d022582a376de01440e5c2bbe92009f8117a/yarl-1.13.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5b66c87da3c6da8f8e8b648878903ca54589038a0b1e08dde2c86d9cd92d4ac9", size = 471561 }, - { url = "https://files.pythonhosted.org/packages/93/c5/4dfb00b84fc6df79b3e42d8716ba8f747d7ebf0c14640c7e65d923f39ea7/yarl-1.13.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:cf1ad338620249f8dd6d4b6a91a69d1f265387df3697ad5dc996305cf6c26fb2", size = 485652 }, - { url = "https://files.pythonhosted.org/packages/9d/fb/bde1430c94d6e5de27d0031e3fb5d85467d975aecdc67e6c686f5c36bbfd/yarl-1.13.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:9915300fe5a0aa663c01363db37e4ae8e7c15996ebe2c6cce995e7033ff6457f", size = 483530 }, - { url = "https://files.pythonhosted.org/packages/5c/80/9f9c9d567ac5fb355e252dc27b75ccf92a3e4bea8b1c5610d5d1240c1b30/yarl-1.13.1-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:703b0f584fcf157ef87816a3c0ff868e8c9f3c370009a8b23b56255885528f10", size = 514085 }, - { url = "https://files.pythonhosted.org/packages/aa/9b/3aeb817a60bde4be6acb476a46bc6184c27b5c91f23ec726d9e6e46b89cf/yarl-1.13.1-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:1d8e3ca29f643dd121f264a7c89f329f0fcb2e4461833f02de6e39fef80f89da", size = 516342 }, - { url = "https://files.pythonhosted.org/packages/71/9d/d7aa4fd8b16e174c4c16b826f54a0e9e4533fb3ae09741906ccc811362d0/yarl-1.13.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:7055bbade838d68af73aea13f8c86588e4bcc00c2235b4b6d6edb0dbd174e246", size = 498430 }, - { url = "https://files.pythonhosted.org/packages/b0/3d/b46aad1725f8d043beee2d47ffddffb1939178bec6f9584b46215efe5a78/yarl-1.13.1-cp311-cp311-win32.whl", hash = "sha256:a3442c31c11088e462d44a644a454d48110f0588de830921fd201060ff19612a", size = 102436 }, - { url = "https://files.pythonhosted.org/packages/89/9e/bbbda05279230dc12d879dfcf971f77f9c932e457fbcd870efb4c3bdf10c/yarl-1.13.1-cp311-cp311-win_amd64.whl", hash = "sha256:81bad32c8f8b5897c909bf3468bf601f1b855d12f53b6af0271963ee67fff0d2", size = 111678 }, - { url = "https://files.pythonhosted.org/packages/64/de/1602352e5bb47c4b86921b004fe84d0646ef9abeda3dfc55f1d2271829e4/yarl-1.13.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:f452cc1436151387d3d50533523291d5f77c6bc7913c116eb985304abdbd9ec9", size = 190253 }, - { url = "https://files.pythonhosted.org/packages/83/f0/2abc6f0af8f243c4a5190e687897e7684baea2c97f5f1be2321418163c7e/yarl-1.13.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:9cec42a20eae8bebf81e9ce23fb0d0c729fc54cf00643eb251ce7c0215ad49fe", size = 116079 }, - { url = "https://files.pythonhosted.org/packages/ad/eb/a578f935e2b6834a00b38156f81f3a6545e14a360ff8a296019116502a9c/yarl-1.13.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:d959fe96e5c2712c1876d69af0507d98f0b0e8d81bee14cfb3f6737470205419", size = 113943 }, - { url = "https://files.pythonhosted.org/packages/da/ee/2bf5f8ffbea5b18fbca274dd04e300a033e43e92d261ac60722361b216ce/yarl-1.13.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b8c837ab90c455f3ea8e68bee143472ee87828bff19ba19776e16ff961425b57", size = 483984 }, - { url = "https://files.pythonhosted.org/packages/05/9f/20d07ed84cbac847b989ef61130f2cbec6dc60f273b81d51041c35740eb3/yarl-1.13.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:94a993f976cdcb2dc1b855d8b89b792893220db8862d1a619efa7451817c836b", size = 499723 }, - { url = "https://files.pythonhosted.org/packages/e5/90/cc6d3dab4fc33b6f80d498c6276995fcbe16db1005141be6133345b597c1/yarl-1.13.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2b2442a415a5f4c55ced0fade7b72123210d579f7d950e0b5527fc598866e62c", size = 497279 }, - { url = "https://files.pythonhosted.org/packages/47/a0/c1404aa8c7e025aa05a81f3a34c42131f8b11836e49450e1558bcd64a3bb/yarl-1.13.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3fdbf0418489525231723cdb6c79e7738b3cbacbaed2b750cb033e4ea208f220", size = 490188 }, - { url = "https://files.pythonhosted.org/packages/2e/8b/ebb195c4a4a5b5a84b0ade8469404609d68adf8f1dcf88e8b2b5297566cc/yarl-1.13.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6b7f6e699304717fdc265a7e1922561b02a93ceffdaefdc877acaf9b9f3080b8", size = 469378 }, - { url = "https://files.pythonhosted.org/packages/40/8f/6a00380c6653006ac0112ebbf0ff24eb7b2d71359ac2c410a98822d89bfa/yarl-1.13.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:bcd5bf4132e6a8d3eb54b8d56885f3d3a38ecd7ecae8426ecf7d9673b270de43", size = 485681 }, - { url = "https://files.pythonhosted.org/packages/2c/94/797d18a3b9ea125a24ba3c69cd71b3561d227d5bb61dbadf2cb2afd6c319/yarl-1.13.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:2a93a4557f7fc74a38ca5a404abb443a242217b91cd0c4840b1ebedaad8919d4", size = 486049 }, - { url = "https://files.pythonhosted.org/packages/75/b2/3573e18eb52ca204ee076a94c145edc80c3df21694648b35ae34c19ac9bb/yarl-1.13.1-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:22b739f99c7e4787922903f27a892744189482125cc7b95b747f04dd5c83aa9f", size = 506742 }, - { url = "https://files.pythonhosted.org/packages/1f/36/f6b5b0fb7c771d5c6c08b7d00a53cd523793454113d4c96460e3f49a1cdd/yarl-1.13.1-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:2db874dd1d22d4c2c657807562411ffdfabec38ce4c5ce48b4c654be552759dc", size = 517070 }, - { url = "https://files.pythonhosted.org/packages/8e/17/48637d4ddcb606f5591afee78d060eab70e172e14766e1fd23453bfed846/yarl-1.13.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:4feaaa4742517eaceafcbe74595ed335a494c84634d33961214b278126ec1485", size = 502397 }, - { url = "https://files.pythonhosted.org/packages/83/2c/7392645dc1c9eeb8a5485696302a33e3d59bea8a448c8e2f36f98a728e0a/yarl-1.13.1-cp312-cp312-win32.whl", hash = "sha256:bbf9c2a589be7414ac4a534d54e4517d03f1cbb142c0041191b729c2fa23f320", size = 102343 }, - { url = "https://files.pythonhosted.org/packages/9c/c0/7329799080d7e0bf7b10db417900701ba6810e78a249aef1f4bf3fc2cccb/yarl-1.13.1-cp312-cp312-win_amd64.whl", hash = "sha256:d07b52c8c450f9366c34aa205754355e933922c79135125541daae6cbf31c799", size = 111719 }, - { url = "https://files.pythonhosted.org/packages/d3/d2/9542e6207a6e64c32b14b2d9ca4fad6ff80310fc75e70cdbe31680a758c2/yarl-1.13.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:95c6737f28069153c399d875317f226bbdea939fd48a6349a3b03da6829fb550", size = 186266 }, - { url = "https://files.pythonhosted.org/packages/8b/68/4c6d1aacbc23a05e84c3fab7aaa68c5a7d4531290021c2370fa1e5524fb1/yarl-1.13.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:cd66152561632ed4b2a9192e7f8e5a1d41e28f58120b4761622e0355f0fe034c", size = 114268 }, - { url = "https://files.pythonhosted.org/packages/ed/87/6ad8e22c918d745092329ec427c0778b5c85ffd5b805e38750024b7464f2/yarl-1.13.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:6a2acde25be0cf9be23a8f6cbd31734536a264723fca860af3ae5e89d771cd71", size = 112164 }, - { url = "https://files.pythonhosted.org/packages/ca/5b/c6c4ac4be1edea6759f05ad74d87a1c61329737bdb90da5f66e188310461/yarl-1.13.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9a18595e6a2ee0826bf7dfdee823b6ab55c9b70e8f80f8b77c37e694288f5de1", size = 471437 }, - { url = "https://files.pythonhosted.org/packages/c1/5c/ec7f0121a5fa67ee76325e1aaa27470d5521d80a25aa1bad5dde773edbe1/yarl-1.13.1-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a31d21089894942f7d9a8df166b495101b7258ff11ae0abec58e32daf8088813", size = 485894 }, - { url = "https://files.pythonhosted.org/packages/d7/e8/624fc8082cbff62c537798ce837a6044f70e2e00472ab719deb376ff6e39/yarl-1.13.1-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:45f209fb4bbfe8630e3d2e2052535ca5b53d4ce2d2026bed4d0637b0416830da", size = 486702 }, - { url = "https://files.pythonhosted.org/packages/dc/18/013f7d2e3f0ff28b85299ed19164f899ea4f02da8812621a40937428bf48/yarl-1.13.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8f722f30366474a99745533cc4015b1781ee54b08de73260b2bbe13316079851", size = 478911 }, - { url = "https://files.pythonhosted.org/packages/d7/3c/5b628939e3a22fb9375df453188e97190d21f6244c49637e19799896cd41/yarl-1.13.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f3bf60444269345d712838bb11cc4eadaf51ff1a364ae39ce87a5ca8ad3bb2c8", size = 456488 }, - { url = "https://files.pythonhosted.org/packages/8b/2b/a3548db86510c1d95bff344c1c588b84582eeb3a55ea15a149a24d7069f0/yarl-1.13.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:942c80a832a79c3707cca46bd12ab8aa58fddb34b1626d42b05aa8f0bcefc206", size = 475016 }, - { url = "https://files.pythonhosted.org/packages/d8/e2/e2a540f18f849909e3ee594766bf7b0a7fde176ff0cfb2f95121033752e2/yarl-1.13.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:44b07e1690f010c3c01d353b5790ec73b2f59b4eae5b0000593199766b3f7a5c", size = 477521 }, - { url = "https://files.pythonhosted.org/packages/3a/df/4cda4052da48a57ce4f20a0849b7344902aa3e149a0b409525509fc43985/yarl-1.13.1-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:396e59b8de7e4d59ff5507fb4322d2329865b909f29a7ed7ca37e63ade7f835c", size = 492000 }, - { url = "https://files.pythonhosted.org/packages/bf/b6/180dbb0aa846cafb9ce89bd33c477e200dd00072c7775372f34651c20b9a/yarl-1.13.1-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:3bb83a0f12701c0b91112a11148b5217617982e1e466069d0555be9b372f2734", size = 502195 }, - { url = "https://files.pythonhosted.org/packages/ff/37/e97c280344342e326a1860a70054a0488c379e8937325f97f9a9fe6b453d/yarl-1.13.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:c92b89bffc660f1274779cb6fbb290ec1f90d6dfe14492523a0667f10170de26", size = 492892 }, - { url = "https://files.pythonhosted.org/packages/ed/97/cd35f39ba8183ef193a6709aa0b2fcaabebd6915202d6999b01fa630b2bb/yarl-1.13.1-cp313-cp313-win32.whl", hash = "sha256:269c201bbc01d2cbba5b86997a1e0f73ba5e2f471cfa6e226bcaa7fd664b598d", size = 486463 }, - { url = "https://files.pythonhosted.org/packages/05/33/bd9d33503a0f73d095b01ed438423b924e6786e90102ca4912e573cc5aa3/yarl-1.13.1-cp313-cp313-win_amd64.whl", hash = "sha256:1d0828e17fa701b557c6eaed5edbd9098eb62d8838344486248489ff233998b8", size = 493804 }, - { url = "https://files.pythonhosted.org/packages/74/81/419c24f7c94f56b96d04955482efb5b381635ad265b5b7fbab333a9dfde3/yarl-1.13.1-py3-none-any.whl", hash = "sha256:6a5185ad722ab4dd52d5fb1f30dcc73282eb1ed494906a92d1a228d3f89607b0", size = 39862 }, + { url = "https://files.pythonhosted.org/packages/df/30/00b17348655202e4bd24f8d79cd062888e5d3bdbf2ba726615c5d21b54a5/yarl-1.16.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:32468f41242d72b87ab793a86d92f885355bcf35b3355aa650bfa846a5c60058", size = 140016 }, + { url = "https://files.pythonhosted.org/packages/a5/15/9b7b85b72b81f180689257b2bb6e54d5d0764a399679aa06d5dec8ca6e2e/yarl-1.16.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:234f3a3032b505b90e65b5bc6652c2329ea7ea8855d8de61e1642b74b4ee65d2", size = 92953 }, + { url = "https://files.pythonhosted.org/packages/31/41/91848bbb76789336d3b786ff144030001b5027b17729b3afa32da668f5b0/yarl-1.16.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:8a0296040e5cddf074c7f5af4a60f3fc42c0237440df7bcf5183be5f6c802ed5", size = 90793 }, + { url = "https://files.pythonhosted.org/packages/6c/99/f1ada764e350ab054e14902f3f68589a7d77469ac47fbc512aa1a78a2f35/yarl-1.16.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:de6c14dd7c7c0badba48157474ea1f03ebee991530ba742d381b28d4f314d6f3", size = 313155 }, + { url = "https://files.pythonhosted.org/packages/75/fd/998ccdb489ca97d9073d882265203a2fae4c5bff30eb9b8a0bbbed7aef2b/yarl-1.16.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b140e532fe0266003c936d017c1ac301e72ee4a3fd51784574c05f53718a55d8", size = 328624 }, + { url = "https://files.pythonhosted.org/packages/2d/5d/395bbae1f509f64e6d26b7ffffff178d70c5480f15af735dfb0afb8f0dc5/yarl-1.16.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:019f5d58093402aa8f6661e60fd82a28746ad6d156f6c5336a70a39bd7b162b9", size = 325163 }, + { url = "https://files.pythonhosted.org/packages/1d/25/65601d336189d122483f5ff0276b08278fa4778f833458cfcac5c6eddc87/yarl-1.16.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8c42998fd1cbeb53cd985bff0e4bc25fbe55fd6eb3a545a724c1012d69d5ec84", size = 318076 }, + { url = "https://files.pythonhosted.org/packages/50/bb/0c9692ec457c1ed023654a9fba6d0c69a20c79b56275d972f6a24ab18547/yarl-1.16.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7c7c30fb38c300fe8140df30a046a01769105e4cf4282567a29b5cdb635b66c4", size = 309551 }, + { url = "https://files.pythonhosted.org/packages/a5/2f/d0ced2050a203241a3f2e05c5bb86038b071f216897defd824dd85333f9e/yarl-1.16.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:e49e0fd86c295e743fd5be69b8b0712f70a686bc79a16e5268386c2defacaade", size = 317678 }, + { url = "https://files.pythonhosted.org/packages/46/93/b7359aa2bd0567eca72491cd20059744ed6ee00f08cd58c861243f656a90/yarl-1.16.0-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:b9ca7b9147eb1365c8bab03c003baa1300599575effad765e0b07dd3501ea9af", size = 317003 }, + { url = "https://files.pythonhosted.org/packages/87/18/77ef4d45d19ecafad0f7c07d5cf13a757a90122383494bc5a3e8ee68e2f2/yarl-1.16.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:27e11db3f1e6a51081a981509f75617b09810529de508a181319193d320bc5c7", size = 322795 }, + { url = "https://files.pythonhosted.org/packages/28/a9/b38880bf79665d1c8a3d4c09d6f7a686a50f8c74caf07603a2b8e5314038/yarl-1.16.0-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:8994c42f4ca25df5380ddf59f315c518c81df6a68fed5bb0c159c6cb6b92f120", size = 337022 }, + { url = "https://files.pythonhosted.org/packages/e9/79/865788b297fc17117e3ff6ea74d5f864185085d61adc3364444732095254/yarl-1.16.0-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:542fa8e09a581bcdcbb30607c7224beff3fdfb598c798ccd28a8184ffc18b7eb", size = 338357 }, + { url = "https://files.pythonhosted.org/packages/bd/5e/c5cba528448f73c7035c9d3c07261b54312d8caa8372eeeff5e1f07e43ec/yarl-1.16.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:2bd6a51010c7284d191b79d3b56e51a87d8e1c03b0902362945f15c3d50ed46b", size = 330470 }, + { url = "https://files.pythonhosted.org/packages/1a/e4/90757595d81ec328ad94afa62d0724903a6c72b76e0ee9c9af9d8a399dd2/yarl-1.16.0-cp310-cp310-win32.whl", hash = "sha256:178ccb856e265174a79f59721031060f885aca428983e75c06f78aa24b91d929", size = 82967 }, + { url = "https://files.pythonhosted.org/packages/01/5a/b82ec5e7557b0d938b9475cbb5dcbb1f98c8601101188d79e423dc215cd0/yarl-1.16.0-cp310-cp310-win_amd64.whl", hash = "sha256:fe8bba2545427418efc1929c5c42852bdb4143eb8d0a46b09de88d1fe99258e7", size = 89159 }, + { url = "https://files.pythonhosted.org/packages/0a/00/b29affe83de95e403f8a2a669b5a33f1e7dfe686264008100052eb0b05fd/yarl-1.16.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:d8643975a0080f361639787415a038bfc32d29208a4bf6b783ab3075a20b1ef3", size = 140120 }, + { url = "https://files.pythonhosted.org/packages/3f/22/bcc9799950281a5d4f646536854839ccdbb965e900827ef0750680f81faf/yarl-1.16.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:676d96bafc8c2d0039cea0cd3fd44cee7aa88b8185551a2bb93354668e8315c2", size = 92956 }, + { url = "https://files.pythonhosted.org/packages/33/0f/1b76d853d9d921d68bd9991648be17d34e7ac51e2e20e7658f8ee7e2e2ad/yarl-1.16.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:d9525f03269e64310416dbe6c68d3b23e5d34aaa8f47193a1c45ac568cecbc49", size = 90891 }, + { url = "https://files.pythonhosted.org/packages/61/19/3666d990c24aae98c748e2c262adc9b3a71e38834df007ac5317f4bbd789/yarl-1.16.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8b37d5ec034e668b22cf0ce1074d6c21fd2a08b90d11b1b73139b750a8b0dd97", size = 338857 }, + { url = "https://files.pythonhosted.org/packages/a0/3d/54acbb3cdfcfea03d6a3535cff1e060a2de23e419a4e3955c9661171b8a8/yarl-1.16.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4f32c4cb7386b41936894685f6e093c8dfaf0960124d91fe0ec29fe439e201d0", size = 354005 }, + { url = "https://files.pythonhosted.org/packages/15/98/cd9fe3938422c88775c94578a6c145aca89ff8368ff64e6032213ac12403/yarl-1.16.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5b8e265a0545637492a7e12fd7038370d66c9375a61d88c5567d0e044ded9202", size = 351195 }, + { url = "https://files.pythonhosted.org/packages/e2/13/b6eff6ea1667aee948ecd6b1c8fb6473234f8e48f49af97be93251869c51/yarl-1.16.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:789a3423f28a5fff46fbd04e339863c169ece97c827b44de16e1a7a42bc915d2", size = 342789 }, + { url = "https://files.pythonhosted.org/packages/fe/05/d98e65ea74a7e44bb033b2cf5bcc16edc1d5212bdc5ca7fbb5e380d89f8e/yarl-1.16.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f1d1f45e3e8d37c804dca99ab3cf4ab3ed2e7a62cd82542924b14c0a4f46d243", size = 336478 }, + { url = "https://files.pythonhosted.org/packages/7d/47/43de2e94b75f36d84733a35c807d0e33aaf084e98f32e2cbc685102f4ba4/yarl-1.16.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:621280719c4c5dad4c1391160a9b88925bb8b0ff6a7d5af3224643024871675f", size = 346008 }, + { url = "https://files.pythonhosted.org/packages/e2/de/9c2f900ec5e2f2e20329cfe7dcd9452e326d08cb5ecd098c2d4e9987b65c/yarl-1.16.0-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:ed097b26f18a1f5ff05f661dc36528c5f6735ba4ce8c9645e83b064665131349", size = 343745 }, + { url = "https://files.pythonhosted.org/packages/56/cd/b014dce22e37b77caa37f998c6c47434fd78d01e7be07119629f369f5ee1/yarl-1.16.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:2f1fe2b2e3ee418862f5ebc0c0083c97f6f6625781382f828f6d4e9b614eba9b", size = 349705 }, + { url = "https://files.pythonhosted.org/packages/07/17/bb191a26f7189423964e008ccb5146ce5258454ef3979f9d4c6860d282c7/yarl-1.16.0-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:87dd10bc0618991c66cee0cc65fa74a45f4ecb13bceec3c62d78ad2e42b27a16", size = 360767 }, + { url = "https://files.pythonhosted.org/packages/19/09/7d777369e151991b708a5b35280ea7444621d65af5f0545bcdce5d840867/yarl-1.16.0-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:4199db024b58a8abb2cfcedac7b1292c3ad421684571aeb622a02f242280e8d6", size = 364755 }, + { url = "https://files.pythonhosted.org/packages/00/32/7558997d1d2e53dab15f6db5db49fc6b412b63ede3cb8314e5dd7cff14fe/yarl-1.16.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:99a9dcd4b71dd5f5f949737ab3f356cfc058c709b4f49833aeffedc2652dac56", size = 357087 }, + { url = "https://files.pythonhosted.org/packages/28/20/c49a95a30c57224e5fb0fc83235295684b041300ce508b71821cb042527d/yarl-1.16.0-cp311-cp311-win32.whl", hash = "sha256:a9394c65ae0ed95679717d391c862dece9afacd8fa311683fc8b4362ce8a410c", size = 83030 }, + { url = "https://files.pythonhosted.org/packages/75/e3/2a746721d6f32886d9bafccdb80174349f180ccae0a287f25ba4312a2618/yarl-1.16.0-cp311-cp311-win_amd64.whl", hash = "sha256:5b9101f528ae0f8f65ac9d64dda2bb0627de8a50344b2f582779f32fda747c1d", size = 89616 }, + { url = "https://files.pythonhosted.org/packages/3a/be/82f696c8ce0395c37f62b955202368086e5cc114d5bb9cb1b634cff5e01d/yarl-1.16.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:4ffb7c129707dd76ced0a4a4128ff452cecf0b0e929f2668ea05a371d9e5c104", size = 141230 }, + { url = "https://files.pythonhosted.org/packages/38/60/45caaa748b53c4b0964f899879fcddc41faa4e0d12c6f0ae3311e8c151ff/yarl-1.16.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:1a5e9d8ce1185723419c487758d81ac2bde693711947032cce600ca7c9cda7d6", size = 93515 }, + { url = "https://files.pythonhosted.org/packages/54/bd/33aaca2f824dc1d630729e16e313797e8b24c8f7b6803307e5394274e443/yarl-1.16.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:d743e3118b2640cef7768ea955378c3536482d95550222f908f392167fe62059", size = 91441 }, + { url = "https://files.pythonhosted.org/packages/af/fa/1ce8ca85489925aabdb8d2e7bbeaf74e7d3e6ac069779d6d6b9c7c62a8ed/yarl-1.16.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:26768342f256e6e3c37533bf9433f5f15f3e59e3c14b2409098291b3efaceacb", size = 330871 }, + { url = "https://files.pythonhosted.org/packages/f1/2a/a8110a225e498b87315827f8b61d24de35f86041834cf8c9c5544380c46b/yarl-1.16.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d1b0796168b953bca6600c5f97f5ed407479889a36ad7d17183366260f29a6b9", size = 340641 }, + { url = "https://files.pythonhosted.org/packages/d0/64/20cd1cb1f60b3ff49e7d75c1a2083352e7c5939368aafa960712c9e53797/yarl-1.16.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:858728086914f3a407aa7979cab743bbda1fe2bdf39ffcd991469a370dd7414d", size = 340245 }, + { url = "https://files.pythonhosted.org/packages/77/a8/7f38bbefb22eb925a68ad1d8193b05f51515614a6c0ebcadf26e9ae5e5ad/yarl-1.16.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5570e6d47bcb03215baf4c9ad7bf7c013e56285d9d35013541f9ac2b372593e7", size = 336054 }, + { url = "https://files.pythonhosted.org/packages/b4/a6/ac633ea3ea0c4eb1057e6800db1d077e77493b4b3449a4a97b2fbefadef4/yarl-1.16.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:66ea8311422a7ba1fc79b4c42c2baa10566469fe5a78500d4e7754d6e6db8724", size = 324405 }, + { url = "https://files.pythonhosted.org/packages/93/cd/4fc87ce9b0df7afb610ffb904f4aef25f59e0ad40a49da19a475facf98b7/yarl-1.16.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:649bddcedee692ee8a9b7b6e38582cb4062dc4253de9711568e5620d8707c2a3", size = 342235 }, + { url = "https://files.pythonhosted.org/packages/9f/bc/38bae4b716da1206849d88e167d3d2c5695ae9b418a3915220947593e5ca/yarl-1.16.0-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:3a91654adb7643cb21b46f04244c5a315a440dcad63213033826549fa2435f71", size = 340835 }, + { url = "https://files.pythonhosted.org/packages/dc/0f/b9efbc0075916a450cbad41299dff3bdd3393cb1d8378bb831c4a6a836e1/yarl-1.16.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:b439cae82034ade094526a8f692b9a2b5ee936452de5e4c5f0f6c48df23f8604", size = 344323 }, + { url = "https://files.pythonhosted.org/packages/87/6d/dc483ea1574005f14ef4c5f5f726cf60327b07ac83bd417d98db23e5285f/yarl-1.16.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:571f781ae8ac463ce30bacebfaef2c6581543776d5970b2372fbe31d7bf31a07", size = 355112 }, + { url = "https://files.pythonhosted.org/packages/10/22/3b7c3728d26b3cc295c51160ae4e2612ab7d3f9df30beece44bf72861730/yarl-1.16.0-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:aa7943f04f36d6cafc0cf53ea89824ac2c37acbdb4b316a654176ab8ffd0f968", size = 361506 }, + { url = "https://files.pythonhosted.org/packages/ad/8d/b7b5d43cf22a020b564ddf7502d83df150d797e34f18f6bf5fe0f12cbd91/yarl-1.16.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:1a5cf32539373ff39d97723e39a9283a7277cbf1224f7aef0c56c9598b6486c3", size = 355746 }, + { url = "https://files.pythonhosted.org/packages/d9/a6/a2098bf3f09d38eb540b2b192e180d9d41c2ff64b692783db2188f0a55e3/yarl-1.16.0-cp312-cp312-win32.whl", hash = "sha256:a5b6c09b9b4253d6a208b0f4a2f9206e511ec68dce9198e0fbec4f160137aa67", size = 82675 }, + { url = "https://files.pythonhosted.org/packages/ed/a6/0a54b382cfc336e772b72681d6816a99222dc2d21876e649474973b8d244/yarl-1.16.0-cp312-cp312-win_amd64.whl", hash = "sha256:1208ca14eed2fda324042adf8d6c0adf4a31522fa95e0929027cd487875f0240", size = 88986 }, + { url = "https://files.pythonhosted.org/packages/57/56/eef0a7050fcd11d70c536453f014d4b2dfd83fb934c9857fa1a912832405/yarl-1.16.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:a5ace0177520bd4caa99295a9b6fb831d0e9a57d8e0501a22ffaa61b4c024283", size = 139373 }, + { url = "https://files.pythonhosted.org/packages/3f/b2/88eb9e98c5a4549606ebf673cba0d701f13ec855021b781f8e3fd7c04190/yarl-1.16.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:7118bdb5e3ed81acaa2095cba7ec02a0fe74b52a16ab9f9ac8e28e53ee299732", size = 92759 }, + { url = "https://files.pythonhosted.org/packages/95/1d/c3b794ef82a3b1894a9f8fc1012b073a85464b95c646ac217e8013137ea3/yarl-1.16.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:38fec8a2a94c58bd47c9a50a45d321ab2285ad133adefbbadf3012c054b7e656", size = 90573 }, + { url = "https://files.pythonhosted.org/packages/7f/35/39a5dcbf7ef320607bcfd1c0498ce348181b97627c3901139b429d806cf1/yarl-1.16.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8791d66d81ee45866a7bb15a517b01a2bcf583a18ebf5d72a84e6064c417e64b", size = 332461 }, + { url = "https://files.pythonhosted.org/packages/36/29/2a468c8b44aa750d0f3416bc24d58464237b402388a8f03091a58537274a/yarl-1.16.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1cf936ba67bc6c734f3aa1c01391da74ab7fc046a9f8bbfa230b8393b90cf472", size = 343045 }, + { url = "https://files.pythonhosted.org/packages/91/6a/002300c86ed7ef3cd5ac890a0e17101aee06c64abe2e43f9dad85bc32c70/yarl-1.16.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d1aab176dd55b59f77a63b27cffaca67d29987d91a5b615cbead41331e6b7428", size = 344592 }, + { url = "https://files.pythonhosted.org/packages/ea/69/ca4228e0f560f0c5817e0ebd789690c78ab17e6a876b38a5d000889b2f63/yarl-1.16.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:995d0759004c08abd5d1b81300a91d18c8577c6389300bed1c7c11675105a44d", size = 338127 }, + { url = "https://files.pythonhosted.org/packages/81/df/32eea6e5199f7298ec15cf708895f35a7d2899177ed556e6bdf6819462aa/yarl-1.16.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1bc22e00edeb068f71967ab99081e9406cd56dbed864fc3a8259442999d71552", size = 326127 }, + { url = "https://files.pythonhosted.org/packages/9a/11/1a888df53acd3d1d4b8dc803e0c8ed4a4b6cabc2abe19e4de31aa6b86857/yarl-1.16.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:35b4f7842154176523e0a63c9b871168c69b98065d05a4f637fce342a6a2693a", size = 345219 }, + { url = "https://files.pythonhosted.org/packages/34/88/44fd8b372c4c50c010e66c62bfb34e67d6bd217c973599e0ee03f74e74ec/yarl-1.16.0-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:7ace71c4b7a0c41f317ae24be62bb61e9d80838d38acb20e70697c625e71f120", size = 339742 }, + { url = "https://files.pythonhosted.org/packages/ee/c8/eaa53bd40db61265cec09d3c432d8bcd8ab9fd3a9fc5b0afdd13ab27b4a8/yarl-1.16.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:8f639e3f5795a6568aa4f7d2ac6057c757dcd187593679f035adbf12b892bb00", size = 344695 }, + { url = "https://files.pythonhosted.org/packages/1b/8f/b00aa91bd3bc8ef41781b13ac967c9c5c2e3ca0c516cffdd15ac035a1839/yarl-1.16.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:e8be3aff14f0120ad049121322b107f8a759be76a6a62138322d4c8a337a9e2c", size = 353617 }, + { url = "https://files.pythonhosted.org/packages/f1/88/8e86a28a840b8dc30c880fdde127f9610c56e55796a2cc969949b4a60fe7/yarl-1.16.0-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:122d8e7986043d0549e9eb23c7fd23be078be4b70c9eb42a20052b3d3149c6f2", size = 359911 }, + { url = "https://files.pythonhosted.org/packages/ee/61/9d59f7096fd72d5f68168ed8134773982ee48a8cb4009ecb34344e064999/yarl-1.16.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:0fd9c227990f609c165f56b46107d0bc34553fe0387818c42c02f77974402c36", size = 358847 }, + { url = "https://files.pythonhosted.org/packages/f7/25/c323097b066a2b5a554f77e27a35bc067aebfcd3a001a0a3a6bc14190460/yarl-1.16.0-cp313-cp313-win32.whl", hash = "sha256:595ca5e943baed31d56b33b34736461a371c6ea0038d3baec399949dd628560b", size = 308302 }, + { url = "https://files.pythonhosted.org/packages/52/76/ca2c3de3511a127fc4124723e7ccc641aef5e0ec56c66d25dbd11f19ab84/yarl-1.16.0-cp313-cp313-win_amd64.whl", hash = "sha256:921b81b8d78f0e60242fb3db615ea3f368827a76af095d5a69f1c3366db3f596", size = 314035 }, + { url = "https://files.pythonhosted.org/packages/fb/f7/87a32867ddc1a9817018bfd6109ee57646a543acf0d272843d8393e575f9/yarl-1.16.0-py3-none-any.whl", hash = "sha256:e6980a558d8461230c457218bd6c92dfc1d10205548215c2c21d79dc8d0a96f3", size = 43746 }, ] [[package]] From 6baaad045a5709a537c094e24c4aa42ffd451a3e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Moura?= Date: Wed, 23 Oct 2024 18:08:49 -0300 Subject: [PATCH 018/126] new version --- pyproject.toml | 2 +- src/crewai/__init__.py | 2 +- src/crewai/cli/cli.py | 6 +++--- src/crewai/cli/create_crew.py | 10 +++++----- src/crewai/cli/templates/crew/pyproject.toml | 2 +- src/crewai/cli/templates/flow/pyproject.toml | 2 +- src/crewai/cli/templates/pipeline/pyproject.toml | 2 +- .../cli/templates/pipeline_router/pyproject.toml | 2 +- src/crewai/cli/templates/tool/pyproject.toml | 2 +- uv.lock | 2 +- 10 files changed, 16 insertions(+), 16 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 929e68b9e..36d43d90f 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "crewai" -version = "0.76.0" +version = "0.76.1" 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" diff --git a/src/crewai/__init__.py b/src/crewai/__init__.py index a1b95951d..92e85f98e 100644 --- a/src/crewai/__init__.py +++ b/src/crewai/__init__.py @@ -14,5 +14,5 @@ warnings.filterwarnings( category=UserWarning, module="pydantic.main", ) -__version__ = "0.76.0" +__version__ = "0.76.1" __all__ = ["Agent", "Crew", "Process", "Task", "Pipeline", "Router", "LLM", "Flow"] diff --git a/src/crewai/cli/cli.py b/src/crewai/cli/cli.py index 48bb26ff9..460128634 100644 --- a/src/crewai/cli/cli.py +++ b/src/crewai/cli/cli.py @@ -32,12 +32,12 @@ def crewai(): @crewai.command() @click.argument("type", type=click.Choice(["crew", "pipeline", "flow"])) @click.argument("name") -@click.option("--skip-provider", is_flag=True, help="Skip provider validation") @click.option("--provider", type=str, help="The provider to use for the crew") -def create(type, name, provider, skip_provider_validation=False): +@click.option("--skip_provider", is_flag=True, help="Skip provider validation") +def create(type, name, provider, skip_provider=False): """Create a new crew, pipeline, or flow.""" if type == "crew": - create_crew(name, provider, skip_provider_validation) + create_crew(name, provider, skip_provider) elif type == "pipeline": create_pipeline(name) elif type == "flow": diff --git a/src/crewai/cli/create_crew.py b/src/crewai/cli/create_crew.py index 90184ec31..5767b82a1 100644 --- a/src/crewai/cli/create_crew.py +++ b/src/crewai/cli/create_crew.py @@ -81,12 +81,10 @@ def copy_template_files(folder_path, name, class_name, parent_folder): copy_template(src_file, dst_file, name, class_name, folder_path.name) -def create_crew( - name, provider=None, skip_provider_validation=False, parent_folder=None -): +def create_crew(name, provider=None, skip_provider=False, parent_folder=None): folder_path, folder_name, class_name = create_folder_structure(name, parent_folder) env_vars = load_env_vars(folder_path) - if not skip_provider_validation: + if not skip_provider: if not provider: provider_models = get_provider_data() if not provider_models: @@ -155,7 +153,9 @@ def create_crew( write_env_file(folder_path, env_vars) click.secho("API key saved to .env file", fg="green") else: - click.secho("No API key provided. Skipping .env file creation.", fg="yellow") + click.secho( + "No API key provided. Skipping .env file creation.", fg="yellow" + ) env_vars["MODEL"] = selected_model click.secho(f"Selected model: {selected_model}", fg="green") diff --git a/src/crewai/cli/templates/crew/pyproject.toml b/src/crewai/cli/templates/crew/pyproject.toml index 6852f1292..0c169013c 100644 --- a/src/crewai/cli/templates/crew/pyproject.toml +++ b/src/crewai/cli/templates/crew/pyproject.toml @@ -5,7 +5,7 @@ description = "{{name}} using crewAI" authors = [{ name = "Your Name", email = "you@example.com" }] requires-python = ">=3.10,<=3.13" dependencies = [ - "crewai[tools]>=0.76.0,<1.0.0" + "crewai[tools]>=0.76.1,<1.0.0" ] [project.scripts] diff --git a/src/crewai/cli/templates/flow/pyproject.toml b/src/crewai/cli/templates/flow/pyproject.toml index 88a313ae9..3b6d7d54d 100644 --- a/src/crewai/cli/templates/flow/pyproject.toml +++ b/src/crewai/cli/templates/flow/pyproject.toml @@ -5,7 +5,7 @@ description = "{{name}} using crewAI" authors = [{ name = "Your Name", email = "you@example.com" }] requires-python = ">=3.10,<=3.13" dependencies = [ - "crewai[tools]>=0.76.0,<1.0.0", + "crewai[tools]>=0.76.1,<1.0.0", ] [project.scripts] diff --git a/src/crewai/cli/templates/pipeline/pyproject.toml b/src/crewai/cli/templates/pipeline/pyproject.toml index b2a4d36d4..6b6366cb7 100644 --- a/src/crewai/cli/templates/pipeline/pyproject.toml +++ b/src/crewai/cli/templates/pipeline/pyproject.toml @@ -6,7 +6,7 @@ authors = ["Your Name "] [tool.poetry.dependencies] python = ">=3.10,<=3.13" -crewai = { extras = ["tools"], version = ">=0.76.0,<1.0.0" } +crewai = { extras = ["tools"], version = ">=0.76.1,<1.0.0" } asyncio = "*" [tool.poetry.scripts] diff --git a/src/crewai/cli/templates/pipeline_router/pyproject.toml b/src/crewai/cli/templates/pipeline_router/pyproject.toml index f7da504cf..1c6b6bc3b 100644 --- a/src/crewai/cli/templates/pipeline_router/pyproject.toml +++ b/src/crewai/cli/templates/pipeline_router/pyproject.toml @@ -5,7 +5,7 @@ description = "{{name}} using crewAI" authors = ["Your Name "] requires-python = ">=3.10,<=3.13" dependencies = [ - "crewai[tools]>=0.76.0,<1.0.0" + "crewai[tools]>=0.76.1,<1.0.0" ] [project.scripts] diff --git a/src/crewai/cli/templates/tool/pyproject.toml b/src/crewai/cli/templates/tool/pyproject.toml index 8f7194b7f..712c35969 100644 --- a/src/crewai/cli/templates/tool/pyproject.toml +++ b/src/crewai/cli/templates/tool/pyproject.toml @@ -5,6 +5,6 @@ description = "Power up your crews with {{folder_name}}" readme = "README.md" requires-python = ">=3.10,<=3.13" dependencies = [ - "crewai[tools]>=0.76.0" + "crewai[tools]>=0.76.1" ] diff --git a/uv.lock b/uv.lock index 9d07a5bae..9743146f8 100644 --- a/uv.lock +++ b/uv.lock @@ -604,7 +604,7 @@ wheels = [ [[package]] name = "crewai" -version = "0.76.0" +version = "0.76.1" source = { editable = "." } dependencies = [ { name = "appdirs" }, From 8bc07e60711b79ad97d30463f3964f90f5a71c06 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Moura?= Date: Wed, 23 Oct 2024 18:10:37 -0300 Subject: [PATCH 019/126] new version --- pyproject.toml | 2 +- src/crewai/__init__.py | 2 +- src/crewai/cli/templates/crew/pyproject.toml | 2 +- src/crewai/cli/templates/flow/pyproject.toml | 2 +- src/crewai/cli/templates/pipeline/pyproject.toml | 2 +- src/crewai/cli/templates/pipeline_router/pyproject.toml | 2 +- src/crewai/cli/templates/tool/pyproject.toml | 2 +- uv.lock | 2 +- 8 files changed, 8 insertions(+), 8 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 36d43d90f..85a296867 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "crewai" -version = "0.76.1" +version = "0.76.2" 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" diff --git a/src/crewai/__init__.py b/src/crewai/__init__.py index 92e85f98e..5160aa77f 100644 --- a/src/crewai/__init__.py +++ b/src/crewai/__init__.py @@ -14,5 +14,5 @@ warnings.filterwarnings( category=UserWarning, module="pydantic.main", ) -__version__ = "0.76.1" +__version__ = "0.76.2" __all__ = ["Agent", "Crew", "Process", "Task", "Pipeline", "Router", "LLM", "Flow"] diff --git a/src/crewai/cli/templates/crew/pyproject.toml b/src/crewai/cli/templates/crew/pyproject.toml index 0c169013c..ed32aa600 100644 --- a/src/crewai/cli/templates/crew/pyproject.toml +++ b/src/crewai/cli/templates/crew/pyproject.toml @@ -5,7 +5,7 @@ description = "{{name}} using crewAI" authors = [{ name = "Your Name", email = "you@example.com" }] requires-python = ">=3.10,<=3.13" dependencies = [ - "crewai[tools]>=0.76.1,<1.0.0" + "crewai[tools]>=0.76.2,<1.0.0" ] [project.scripts] diff --git a/src/crewai/cli/templates/flow/pyproject.toml b/src/crewai/cli/templates/flow/pyproject.toml index 3b6d7d54d..815e8b205 100644 --- a/src/crewai/cli/templates/flow/pyproject.toml +++ b/src/crewai/cli/templates/flow/pyproject.toml @@ -5,7 +5,7 @@ description = "{{name}} using crewAI" authors = [{ name = "Your Name", email = "you@example.com" }] requires-python = ">=3.10,<=3.13" dependencies = [ - "crewai[tools]>=0.76.1,<1.0.0", + "crewai[tools]>=0.76.2,<1.0.0", ] [project.scripts] diff --git a/src/crewai/cli/templates/pipeline/pyproject.toml b/src/crewai/cli/templates/pipeline/pyproject.toml index 6b6366cb7..b4e74abea 100644 --- a/src/crewai/cli/templates/pipeline/pyproject.toml +++ b/src/crewai/cli/templates/pipeline/pyproject.toml @@ -6,7 +6,7 @@ authors = ["Your Name "] [tool.poetry.dependencies] python = ">=3.10,<=3.13" -crewai = { extras = ["tools"], version = ">=0.76.1,<1.0.0" } +crewai = { extras = ["tools"], version = ">=0.76.2,<1.0.0" } asyncio = "*" [tool.poetry.scripts] diff --git a/src/crewai/cli/templates/pipeline_router/pyproject.toml b/src/crewai/cli/templates/pipeline_router/pyproject.toml index 1c6b6bc3b..bd487ce95 100644 --- a/src/crewai/cli/templates/pipeline_router/pyproject.toml +++ b/src/crewai/cli/templates/pipeline_router/pyproject.toml @@ -5,7 +5,7 @@ description = "{{name}} using crewAI" authors = ["Your Name "] requires-python = ">=3.10,<=3.13" dependencies = [ - "crewai[tools]>=0.76.1,<1.0.0" + "crewai[tools]>=0.76.2,<1.0.0" ] [project.scripts] diff --git a/src/crewai/cli/templates/tool/pyproject.toml b/src/crewai/cli/templates/tool/pyproject.toml index 712c35969..360a940ed 100644 --- a/src/crewai/cli/templates/tool/pyproject.toml +++ b/src/crewai/cli/templates/tool/pyproject.toml @@ -5,6 +5,6 @@ description = "Power up your crews with {{folder_name}}" readme = "README.md" requires-python = ">=3.10,<=3.13" dependencies = [ - "crewai[tools]>=0.76.1" + "crewai[tools]>=0.76.2" ] diff --git a/uv.lock b/uv.lock index 9743146f8..f242185db 100644 --- a/uv.lock +++ b/uv.lock @@ -604,7 +604,7 @@ wheels = [ [[package]] name = "crewai" -version = "0.76.1" +version = "0.76.2" source = { editable = "." } dependencies = [ { name = "appdirs" }, From 201e652fa2bdb44123609e00c0a2f33f5ad154cb Mon Sep 17 00:00:00 2001 From: "Brandon Hancock (bhancock_ai)" <109994880+bhancockio@users.noreply.github.com> Date: Thu, 24 Oct 2024 14:44:30 -0400 Subject: [PATCH 020/126] update plot command (#1504) --- src/crewai/cli/plot_flow.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/crewai/cli/plot_flow.py b/src/crewai/cli/plot_flow.py index 2183a02ec..848c55d69 100644 --- a/src/crewai/cli/plot_flow.py +++ b/src/crewai/cli/plot_flow.py @@ -7,7 +7,7 @@ def plot_flow() -> None: """ Plot the flow by running a command in the UV environment. """ - command = ["uv", "run", "plot_flow"] + command = ["uv", "run", "plot"] try: result = subprocess.run(command, capture_output=False, text=True, check=True) From 4589d6fe9d976b491a86bdf0aca1d9972077b900 Mon Sep 17 00:00:00 2001 From: Eduardo Chiarotti Date: Fri, 25 Oct 2024 10:33:21 -0300 Subject: [PATCH 021/126] feat: add tomli so we can support 3.10 (#1506) * feat: add tomli so we can support 3.10 * feat: add validation for poetry data --- pyproject.toml | 1 + src/crewai/cli/run_crew.py | 13 ++++-------- src/crewai/cli/update_crew.py | 40 +++++++++++++++++------------------ src/crewai/cli/utils.py | 8 +++++++ uv.lock | 2 ++ 5 files changed, 35 insertions(+), 29 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 85a296867..ef8181cbc 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -28,6 +28,7 @@ dependencies = [ "uv>=0.4.25", "tomli-w>=1.1.0", "chromadb>=0.4.24", + "tomli>=2.0.2", ] [project.urls] diff --git a/src/crewai/cli/run_crew.py b/src/crewai/cli/run_crew.py index 829d8ed95..20d6aed01 100644 --- a/src/crewai/cli/run_crew.py +++ b/src/crewai/cli/run_crew.py @@ -1,10 +1,9 @@ import subprocess import click -import tomllib from packaging import version -from crewai.cli.utils import get_crewai_version +from crewai.cli.utils import get_crewai_version, read_toml def run_crew() -> None: @@ -15,10 +14,9 @@ def run_crew() -> None: crewai_version = get_crewai_version() min_required_version = "0.71.0" - with open("pyproject.toml", "rb") as f: - data = tomllib.load(f) + pyproject_data = read_toml() - if data.get("tool", {}).get("poetry") and ( + if pyproject_data.get("tool", {}).get("poetry") and ( version.parse(crewai_version) < version.parse(min_required_version) ): click.secho( @@ -35,10 +33,7 @@ def run_crew() -> None: click.echo(f"An error occurred while running the crew: {e}", err=True) click.echo(e.output, err=True, nl=True) - with open("pyproject.toml", "rb") as f: - data = tomllib.load(f) - - if data.get("tool", {}).get("poetry"): + if pyproject_data.get("tool", {}).get("poetry"): click.secho( "It's possible that you are using an old version of crewAI that uses poetry, please run `crewai update` to update your pyproject.toml to use uv.", fg="yellow", diff --git a/src/crewai/cli/update_crew.py b/src/crewai/cli/update_crew.py index d38e11a25..e7ed69aa1 100644 --- a/src/crewai/cli/update_crew.py +++ b/src/crewai/cli/update_crew.py @@ -2,7 +2,8 @@ import os import shutil import tomli_w -import tomllib + +from crewai.cli.utils import read_toml def update_crew() -> None: @@ -18,10 +19,9 @@ def migrate_pyproject(input_file, output_file): And it will be used to migrate the pyproject.toml to the new format when uv is used. When the time comes that uv supports the new format, this function will be deprecated. """ - + poetry_data = {} # Read the input pyproject.toml - with open(input_file, "rb") as f: - pyproject = tomllib.load(f) + pyproject_data = read_toml() # Initialize the new project structure new_pyproject = { @@ -30,30 +30,30 @@ def migrate_pyproject(input_file, output_file): } # Migrate project metadata - if "tool" in pyproject and "poetry" in pyproject["tool"]: - poetry = pyproject["tool"]["poetry"] - new_pyproject["project"]["name"] = poetry.get("name") - new_pyproject["project"]["version"] = poetry.get("version") - new_pyproject["project"]["description"] = poetry.get("description") + if "tool" in pyproject_data and "poetry" in pyproject_data["tool"]: + poetry_data = pyproject_data["tool"]["poetry"] + new_pyproject["project"]["name"] = poetry_data.get("name") + new_pyproject["project"]["version"] = poetry_data.get("version") + new_pyproject["project"]["description"] = poetry_data.get("description") new_pyproject["project"]["authors"] = [ { "name": author.split("<")[0].strip(), "email": author.split("<")[1].strip(">").strip(), } - for author in poetry.get("authors", []) + for author in poetry_data.get("authors", []) ] - new_pyproject["project"]["requires-python"] = poetry.get("python") + new_pyproject["project"]["requires-python"] = poetry_data.get("python") else: # If it's already in the new format, just copy the project section - new_pyproject["project"] = pyproject.get("project", {}) + new_pyproject["project"] = pyproject_data.get("project", {}) # Migrate or copy dependencies if "dependencies" in new_pyproject["project"]: # If dependencies are already in the new format, keep them as is pass - elif "dependencies" in poetry: + elif poetry_data and "dependencies" in poetry_data: new_pyproject["project"]["dependencies"] = [] - for dep, version in poetry["dependencies"].items(): + for dep, version in poetry_data["dependencies"].items(): if isinstance(version, dict): # Handle extras extras = ",".join(version.get("extras", [])) new_dep = f"{dep}[{extras}]" @@ -67,10 +67,10 @@ def migrate_pyproject(input_file, output_file): new_pyproject["project"]["dependencies"].append(new_dep) # Migrate or copy scripts - if "scripts" in poetry: - new_pyproject["project"]["scripts"] = poetry["scripts"] - elif "scripts" in pyproject.get("project", {}): - new_pyproject["project"]["scripts"] = pyproject["project"]["scripts"] + if poetry_data and "scripts" in poetry_data: + new_pyproject["project"]["scripts"] = poetry_data["scripts"] + elif pyproject_data.get("project", {}) and "scripts" in pyproject_data["project"]: + new_pyproject["project"]["scripts"] = pyproject_data["project"]["scripts"] else: new_pyproject["project"]["scripts"] = {} @@ -87,8 +87,8 @@ def migrate_pyproject(input_file, output_file): new_pyproject["project"]["scripts"]["run_crew"] = f"{module_name}.main:run" # Migrate optional dependencies - if "extras" in poetry: - new_pyproject["project"]["optional-dependencies"] = poetry["extras"] + if poetry_data and "extras" in poetry_data: + new_pyproject["project"]["optional-dependencies"] = poetry_data["extras"] # Backup the old pyproject.toml backup_file = "pyproject-old.toml" diff --git a/src/crewai/cli/utils.py b/src/crewai/cli/utils.py index c32113faa..25da9e31a 100644 --- a/src/crewai/cli/utils.py +++ b/src/crewai/cli/utils.py @@ -6,6 +6,7 @@ from functools import reduce from typing import Any, Dict, List import click +import tomli from rich.console import Console from crewai.cli.authentication.utils import TokenManager @@ -54,6 +55,13 @@ def simple_toml_parser(content): return result +def read_toml(file_path: str = "pyproject.toml"): + """Read the content of a TOML file and return it as a dictionary.""" + with open(file_path, "rb") as f: + toml_dict = tomli.load(f) + return toml_dict + + def parse_toml(content): if sys.version_info >= (3, 11): return tomllib.loads(content) diff --git a/uv.lock b/uv.lock index f242185db..df2572402 100644 --- a/uv.lock +++ b/uv.lock @@ -625,6 +625,7 @@ dependencies = [ { name = "python-dotenv" }, { name = "pyvis" }, { name = "regex" }, + { name = "tomli" }, { name = "tomli-w" }, { name = "uv" }, ] @@ -679,6 +680,7 @@ requires-dist = [ { name = "python-dotenv", specifier = ">=1.0.0" }, { name = "pyvis", specifier = ">=0.3.2" }, { name = "regex", specifier = ">=2024.9.11" }, + { name = "tomli", specifier = ">=2.0.2" }, { name = "tomli-w", specifier = ">=1.1.0" }, { name = "uv", specifier = ">=0.4.25" }, ] From f29f4abdd7ea4d3b4afeb8e1b403360ad7309ace Mon Sep 17 00:00:00 2001 From: Vini Brasil Date: Fri, 25 Oct 2024 11:20:41 -0300 Subject: [PATCH 022/126] Forward install command options to `uv sync` (#1510) Allow passing additional options from `crewai install` directly to `uv sync`. This enables commands like `crewai install --locked` to work as expected by forwarding all flags and options to the underlying uv command. --- src/crewai/cli/cli.py | 10 +++++++--- src/crewai/cli/install_crew.py | 5 +++-- 2 files changed, 10 insertions(+), 5 deletions(-) diff --git a/src/crewai/cli/cli.py b/src/crewai/cli/cli.py index 460128634..1ea2ef4e2 100644 --- a/src/crewai/cli/cli.py +++ b/src/crewai/cli/cli.py @@ -178,10 +178,14 @@ def test(n_iterations: int, model: str): evaluate_crew(n_iterations, model) -@crewai.command() -def install(): +@crewai.command(context_settings=dict( + ignore_unknown_options=True, + allow_extra_args=True, +)) +@click.pass_context +def install(context): """Install the Crew.""" - install_crew() + install_crew(context.args) @crewai.command() diff --git a/src/crewai/cli/install_crew.py b/src/crewai/cli/install_crew.py index 35206e5eb..d1d0ab9da 100644 --- a/src/crewai/cli/install_crew.py +++ b/src/crewai/cli/install_crew.py @@ -3,12 +3,13 @@ import subprocess import click -def install_crew() -> None: +def install_crew(proxy_options: list[str]) -> None: """ Install the crew by running the UV command to lock and install. """ try: - subprocess.run(["uv", "sync"], check=True, capture_output=False, text=True) + command = ["uv", "sync"] + proxy_options + subprocess.run(command, check=True, capture_output=False, text=True) except subprocess.CalledProcessError as e: click.echo(f"An error occurred while running the crew: {e}", err=True) From 26afee9bed0b11f6b22c894a68701c3a854c96f5 Mon Sep 17 00:00:00 2001 From: "Brandon Hancock (bhancock_ai)" <109994880+bhancockio@users.noreply.github.com> Date: Fri, 25 Oct 2024 18:42:55 -0400 Subject: [PATCH 023/126] improve tool text description and args (#1512) * improve tool text descriptoin and args * fix lint * Drop print * add back in docstring --- src/crewai/agent.py | 27 +++++++++++++-------------- 1 file changed, 13 insertions(+), 14 deletions(-) diff --git a/src/crewai/agent.py b/src/crewai/agent.py index f68d6401b..937710f59 100644 --- a/src/crewai/agent.py +++ b/src/crewai/agent.py @@ -1,7 +1,6 @@ import os import shutil import subprocess -from inspect import signature from typing import Any, List, Literal, Optional, Union from pydantic import Field, InstanceOf, PrivateAttr, model_validator @@ -395,26 +394,26 @@ class Agent(BaseAgent): def _render_text_description_and_args(self, tools: List[Any]) -> str: """Render the tool name, description, and args in plain text. - Output will be in the format of: + Output will be in the format of: - .. code-block:: markdown + .. code-block:: markdown search: This tool is used for search, args: {"query": {"type": "string"}} calculator: This tool is used for math, \ - args: {"expression": {"type": "string"}} + args: {"expression": {"type": "string"}} """ tool_strings = [] for tool in tools: - args_schema = str(tool.model_fields) - if hasattr(tool, "func") and tool.func: - sig = signature(tool.func) - description = ( - f"Tool Name: {tool.name}{sig}\nTool Description: {tool.description}" - ) - else: - description = ( - f"Tool Name: {tool.name}\nTool Description: {tool.description}" - ) + args_schema = { + name: { + "description": field.description, + "type": field.annotation.__name__, + } + for name, field in tool.args_schema.model_fields.items() + } + description = ( + f"Tool Name: {tool.name}\nTool Description: {tool.description}" + ) tool_strings.append(f"{description}\nTool Arguments: {args_schema}") return "\n".join(tool_strings) From 04bcfa6e2d7dcc6995ff381b6fc5396c3281117d Mon Sep 17 00:00:00 2001 From: Brandon Hancock Date: Mon, 28 Oct 2024 09:40:56 -0500 Subject: [PATCH 024/126] Improve tooling docs --- docs/how-to/create-custom-tools.mdx | 9 ++++++++- src/crewai/cli/templates/crew/tools/custom_tool.py | 6 ++++++ src/crewai/cli/templates/flow/tools/custom_tool.py | 10 ++++++++++ src/crewai/cli/templates/pipeline/tools/custom_tool.py | 6 ++++++ .../cli/templates/pipeline_router/tools/custom_tool.py | 6 ++++++ 5 files changed, 36 insertions(+), 1 deletion(-) diff --git a/docs/how-to/create-custom-tools.mdx b/docs/how-to/create-custom-tools.mdx index 31fd09e3f..2caab716b 100644 --- a/docs/how-to/create-custom-tools.mdx +++ b/docs/how-to/create-custom-tools.mdx @@ -20,14 +20,21 @@ pip install 'crewai[tools]' ### Subclassing `BaseTool` -To create a personalized tool, inherit from `BaseTool` and define the necessary attributes and the `_run` method. +To create a personalized tool, inherit from `BaseTool` and define the necessary attributes, including the `args_schema` for input validation, and the `_run` method. ```python Code +from typing import Type from crewai_tools import BaseTool +from pydantic import BaseModel, Field + +class MyToolInput(BaseModel): + """Input schema for MyCustomTool.""" + argument: str = Field(..., description="Description of the argument.") class MyCustomTool(BaseTool): name: str = "Name of my tool" description: str = "What this tool does. It's vital for effective utilization." + args_schema: Type[BaseModel] = MyToolInput def _run(self, argument: str) -> str: # Your tool's logic here diff --git a/src/crewai/cli/templates/crew/tools/custom_tool.py b/src/crewai/cli/templates/crew/tools/custom_tool.py index b12529303..d38318051 100644 --- a/src/crewai/cli/templates/crew/tools/custom_tool.py +++ b/src/crewai/cli/templates/crew/tools/custom_tool.py @@ -1,11 +1,17 @@ +from typing import Type from crewai_tools import BaseTool +from pydantic import BaseModel, Field +class MyCustomToolInput(BaseModel): + """Input schema for MyCustomTool.""" + argument: str = Field(..., description="Description of the argument.") class MyCustomTool(BaseTool): name: str = "Name of my tool" description: str = ( "Clear description for what this tool is useful for, you agent will need this information to use it." ) + args_schema: Type[BaseModel] = MyCustomToolInput def _run(self, argument: str) -> str: # Implementation goes here diff --git a/src/crewai/cli/templates/flow/tools/custom_tool.py b/src/crewai/cli/templates/flow/tools/custom_tool.py index b12529303..030e575ec 100644 --- a/src/crewai/cli/templates/flow/tools/custom_tool.py +++ b/src/crewai/cli/templates/flow/tools/custom_tool.py @@ -1,4 +1,13 @@ +from typing import Type + from crewai_tools import BaseTool +from pydantic import BaseModel, Field + + +class MyCustomToolInput(BaseModel): + """Input schema for MyCustomTool.""" + + argument: str = Field(..., description="Description of the argument.") class MyCustomTool(BaseTool): @@ -6,6 +15,7 @@ class MyCustomTool(BaseTool): description: str = ( "Clear description for what this tool is useful for, you agent will need this information to use it." ) + args_schema: Type[BaseModel] = MyCustomToolInput def _run(self, argument: str) -> str: # Implementation goes here diff --git a/src/crewai/cli/templates/pipeline/tools/custom_tool.py b/src/crewai/cli/templates/pipeline/tools/custom_tool.py index b12529303..d38318051 100644 --- a/src/crewai/cli/templates/pipeline/tools/custom_tool.py +++ b/src/crewai/cli/templates/pipeline/tools/custom_tool.py @@ -1,11 +1,17 @@ +from typing import Type from crewai_tools import BaseTool +from pydantic import BaseModel, Field +class MyCustomToolInput(BaseModel): + """Input schema for MyCustomTool.""" + argument: str = Field(..., description="Description of the argument.") class MyCustomTool(BaseTool): name: str = "Name of my tool" description: str = ( "Clear description for what this tool is useful for, you agent will need this information to use it." ) + args_schema: Type[BaseModel] = MyCustomToolInput def _run(self, argument: str) -> str: # Implementation goes here diff --git a/src/crewai/cli/templates/pipeline_router/tools/custom_tool.py b/src/crewai/cli/templates/pipeline_router/tools/custom_tool.py index b12529303..d38318051 100644 --- a/src/crewai/cli/templates/pipeline_router/tools/custom_tool.py +++ b/src/crewai/cli/templates/pipeline_router/tools/custom_tool.py @@ -1,11 +1,17 @@ +from typing import Type from crewai_tools import BaseTool +from pydantic import BaseModel, Field +class MyCustomToolInput(BaseModel): + """Input schema for MyCustomTool.""" + argument: str = Field(..., description="Description of the argument.") class MyCustomTool(BaseTool): name: str = "Name of my tool" description: str = ( "Clear description for what this tool is useful for, you agent will need this information to use it." ) + args_schema: Type[BaseModel] = MyCustomToolInput def _run(self, argument: str) -> str: # Implementation goes here From 500072d855d4aef709f04cf6b6944df291367238 Mon Sep 17 00:00:00 2001 From: Brandon Hancock Date: Mon, 28 Oct 2024 12:17:44 -0500 Subject: [PATCH 025/126] Update flow docs to talk about self evaluation example --- docs/concepts/flows.mdx | 105 +++++++++++++++++++++++++++++++++++++++- 1 file changed, 103 insertions(+), 2 deletions(-) diff --git a/docs/concepts/flows.mdx b/docs/concepts/flows.mdx index dee6fd4fa..b2e7b600f 100644 --- a/docs/concepts/flows.mdx +++ b/docs/concepts/flows.mdx @@ -599,9 +599,110 @@ The generated plot will display nodes representing the tasks in your flow, with By visualizing your flows, you can gain a clearer understanding of the workflow's structure, making it easier to debug, optimize, and communicate your AI processes to others. -### Conclusion -Plotting your flows is a powerful feature of CrewAI that enhances your ability to design and manage complex AI workflows. Whether you choose to use the `plot()` method or the command line, generating plots will provide you with a visual representation of your workflows, aiding in both development and presentation. +## Advanced + +In this section, we explore more complex use cases of CrewAI Flows, starting with a self-evaluation loop. This pattern is crucial for developing AI systems that can iteratively improve their outputs through feedback. + +### 1) Self-Evaluation Loop + +The self-evaluation loop is a powerful pattern that allows AI workflows to automatically assess and refine their outputs. This example demonstrates how to set up a flow that generates content, evaluates it, and iterates based on feedback until the desired quality is achieved. + +#### Overview + +The self-evaluation loop involves two main Crews: + +1. **ShakespeareanXPostCrew**: Generates a Shakespearean-style post on a given topic. +2. **XPostReviewCrew**: Evaluates the generated post, providing feedback on its validity and quality. + +The process iterates until the post meets the criteria or a maximum retry limit is reached. This approach ensures high-quality outputs through iterative refinement. + +#### Importance + +This pattern is essential for building robust AI systems that can adapt and improve over time. By automating the evaluation and feedback loop, developers can ensure that their AI workflows produce reliable and high-quality results. + +#### Main Code Highlights + +Below is the `main.py` file for the self-evaluation loop flow: + +```python +from typing import Optional +from crewai.flow.flow import Flow, listen, router, start +from pydantic import BaseModel +from self_evaluation_loop_flow.crews.shakespeare_crew.shakespeare_crew import ( + ShakespeareanXPostCrew, +) +from self_evaluation_loop_flow.crews.x_post_review_crew.x_post_review_crew import ( + XPostReviewCrew, +) + +class ShakespeareXPostFlowState(BaseModel): + x_post: str = "" + feedback: Optional[str] = None + valid: bool = False + retry_count: int = 0 + +class ShakespeareXPostFlow(Flow[ShakespeareXPostFlowState]): + + @start("retry") + def generate_shakespeare_x_post(self): + print("Generating Shakespearean X post") + topic = "Flying cars" + result = ( + ShakespeareanXPostCrew() + .crew() + .kickoff(inputs={"topic": topic, "feedback": self.state.feedback}) + ) + print("X post generated", result.raw) + self.state.x_post = result.raw + + @router(generate_shakespeare_x_post) + def evaluate_x_post(self): + if self.state.retry_count > 3: + return "max_retry_exceeded" + result = XPostReviewCrew().crew().kickoff(inputs={"x_post": self.state.x_post}) + self.state.valid = result["valid"] + self.state.feedback = result["feedback"] + print("valid", self.state.valid) + print("feedback", self.state.feedback) + self.state.retry_count += 1 + if self.state.valid: + return "complete" + return "retry" + + @listen("complete") + def save_result(self): + print("X post is valid") + print("X post:", self.state.x_post) + with open("x_post.txt", "w") as file: + file.write(self.state.x_post) + + @listen("max_retry_exceeded") + def max_retry_exceeded_exit(self): + print("Max retry count exceeded") + print("X post:", self.state.x_post) + print("Feedback:", self.state.feedback) + +def kickoff(): + shakespeare_flow = ShakespeareXPostFlow() + shakespeare_flow.kickoff() + +def plot(): + shakespeare_flow = ShakespeareXPostFlow() + shakespeare_flow.plot() + +if __name__ == "__main__": + kickoff() +``` + +#### Code Highlights + +- **Retry Mechanism**: The flow uses a retry mechanism to regenerate the post if it doesn't meet the criteria, up to a maximum of three retries. +- **Feedback Loop**: Feedback from the `XPostReviewCrew` is used to refine the post iteratively. +- **State Management**: The flow maintains state using a Pydantic model, ensuring type safety and clarity. + +For a complete example and further details, please refer to the [Self Evaluation Loop Flow repository](https://github.com/crewAIInc/crewAI-examples/tree/main/self_evaluation_loop_flow). + ## Next Steps From fa9949b9d0af809299678f02f1d827d2038514b4 Mon Sep 17 00:00:00 2001 From: Brandon Hancock Date: Mon, 28 Oct 2024 12:18:03 -0500 Subject: [PATCH 026/126] Update flow docs to talk about self evaluation example --- docs/concepts/flows.mdx | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/docs/concepts/flows.mdx b/docs/concepts/flows.mdx index b2e7b600f..093e7b6b7 100644 --- a/docs/concepts/flows.mdx +++ b/docs/concepts/flows.mdx @@ -706,7 +706,7 @@ For a complete example and further details, please refer to the [Self Evaluation ## Next Steps -If you're interested in exploring additional examples of flows, we have a variety of recommendations in our examples repository. Here are four specific flow examples, each showcasing unique use cases to help you match your current problem type to a specific example: +If you're interested in exploring additional examples of flows, we have a variety of recommendations in our examples repository. Here are five specific flow examples, each showcasing unique use cases to help you match your current problem type to a specific example: 1. **Email Auto Responder Flow**: This example demonstrates an infinite loop where a background job continually runs to automate email responses. It's a great use case for tasks that need to be performed repeatedly without manual intervention. [View Example](https://github.com/crewAIInc/crewAI-examples/tree/main/email_auto_responder_flow) @@ -716,6 +716,8 @@ If you're interested in exploring additional examples of flows, we have a variet 4. **Meeting Assistant Flow**: This flow demonstrates how to broadcast one event to trigger multiple follow-up actions. For instance, after a meeting is completed, the flow can update a Trello board, send a Slack message, and save the results. It's a great example of handling multiple outcomes from a single event, making it ideal for comprehensive task management and notification systems. [View Example](https://github.com/crewAIInc/crewAI-examples/tree/main/meeting_assistant_flow) +5. **Self Evaluation Loop Flow**: This flow demonstrates a self-evaluation loop where AI workflows automatically assess and refine their outputs through feedback. It involves generating content, evaluating it, and iterating until the desired quality is achieved. This pattern is crucial for developing robust AI systems that can adapt and improve over time. [View Example](https://github.com/joaomdmoura/crewai) + By exploring these examples, you can gain insights into how to leverage CrewAI Flows for various use cases, from automating repetitive tasks to managing complex, multi-step processes with dynamic decision-making and human feedback. Also, check out our YouTube video on how to use flows in CrewAI below! From 048aa6cbcc108166f9639a835b286899cf14a031 Mon Sep 17 00:00:00 2001 From: "Brandon Hancock (bhancock_ai)" <109994880+bhancockio@users.noreply.github.com> Date: Tue, 29 Oct 2024 10:40:49 -0400 Subject: [PATCH 027/126] Update flows.mdx - Fix link --- docs/concepts/flows.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/concepts/flows.mdx b/docs/concepts/flows.mdx index 093e7b6b7..bf9ce8e67 100644 --- a/docs/concepts/flows.mdx +++ b/docs/concepts/flows.mdx @@ -716,7 +716,7 @@ If you're interested in exploring additional examples of flows, we have a variet 4. **Meeting Assistant Flow**: This flow demonstrates how to broadcast one event to trigger multiple follow-up actions. For instance, after a meeting is completed, the flow can update a Trello board, send a Slack message, and save the results. It's a great example of handling multiple outcomes from a single event, making it ideal for comprehensive task management and notification systems. [View Example](https://github.com/crewAIInc/crewAI-examples/tree/main/meeting_assistant_flow) -5. **Self Evaluation Loop Flow**: This flow demonstrates a self-evaluation loop where AI workflows automatically assess and refine their outputs through feedback. It involves generating content, evaluating it, and iterating until the desired quality is achieved. This pattern is crucial for developing robust AI systems that can adapt and improve over time. [View Example](https://github.com/joaomdmoura/crewai) +5. **Self Evaluation Loop Flow**: This flow demonstrates a self-evaluation loop where AI workflows automatically assess and refine their outputs through feedback. It involves generating content, evaluating it, and iterating until the desired quality is achieved. This pattern is crucial for developing robust AI systems that can adapt and improve over time. [View Example](https://github.com/crewAIInc/crewAI-examples/tree/main/self_evaluation_loop_flow) By exploring these examples, you can gain insights into how to leverage CrewAI Flows for various use cases, from automating repetitive tasks to managing complex, multi-step processes with dynamic decision-making and human feedback. From b43f3987ecae1c2867a2209a3f6de89fe444cabe Mon Sep 17 00:00:00 2001 From: "Brandon Hancock (bhancock_ai)" <109994880+bhancockio@users.noreply.github.com> Date: Tue, 29 Oct 2024 11:53:48 -0400 Subject: [PATCH 028/126] Update flows cli to allow you to easily add additional crews to a flow (#1525) * Update flows cli to allow you to easily add additional crews to a flow * fix failing test * adding more error logs to test thats failing * try again --- docs/concepts/flows.mdx | 34 +++++++++++++++ src/crewai/cli/add_crew_to_flow.py | 70 ++++++++++++++++++++++++++++++ src/crewai/cli/cli.py | 19 ++++++-- tests/cli/cli_test.py | 42 ++++++++++++++++++ 4 files changed, 161 insertions(+), 4 deletions(-) create mode 100644 src/crewai/cli/add_crew_to_flow.py diff --git a/docs/concepts/flows.mdx b/docs/concepts/flows.mdx index bf9ce8e67..c86776982 100644 --- a/docs/concepts/flows.mdx +++ b/docs/concepts/flows.mdx @@ -560,6 +560,40 @@ uv run kickoff The flow will execute, and you should see the output in the console. + +### Adding Additional Crews Using the CLI + +Once you have created your initial flow, you can easily add additional crews to your project using the CLI. This allows you to expand your flow's capabilities by integrating new crews without starting from scratch. + +To add a new crew to your existing flow, use the following command: + +```bash +crewai flow add-crew +``` + +This command will create a new directory for your crew within the `crews` folder of your flow project. It will include the necessary configuration files and a crew definition file, similar to the initial setup. + +#### Folder Structure + +After adding a new crew, your folder structure will look like this: + +name_of_flow/ +├── crews/ +│ ├── poem_crew/ +│ │ ├── config/ +│ │ │ ├── agents.yaml +│ │ │ └── tasks.yaml +│ │ └── poem_crew.py +│ └── name_of_crew/ +│ ├── config/ +│ │ ├── agents.yaml +│ │ └── tasks.yaml +│ └── name_of_crew.py + +You can then customize the `agents.yaml` and `tasks.yaml` files to define the agents and tasks for your new crew. The `name_of_crew.py` file will contain the crew's logic, which you can modify to suit your needs. + +By using the CLI to add additional crews, you can efficiently build complex AI workflows that leverage multiple crews working together. + ## Plot Flows Visualizing your AI workflows can provide valuable insights into the structure and execution paths of your flows. CrewAI offers a powerful visualization tool that allows you to generate interactive plots of your flows, making it easier to understand and optimize your AI workflows. diff --git a/src/crewai/cli/add_crew_to_flow.py b/src/crewai/cli/add_crew_to_flow.py new file mode 100644 index 000000000..e4901fa89 --- /dev/null +++ b/src/crewai/cli/add_crew_to_flow.py @@ -0,0 +1,70 @@ +from pathlib import Path + +import click + +from crewai.cli.utils import copy_template + + +def add_crew_to_flow(crew_name: str) -> None: + """Add a new crew to the current flow.""" + # Check if pyproject.toml exists in the current directory + if not Path("pyproject.toml").exists(): + print("This command must be run from the root of a flow project.") + raise click.ClickException( + "This command must be run from the root of a flow project." + ) + + # Determine the flow folder based on the current directory + flow_folder = Path.cwd() + crews_folder = flow_folder / "src" / flow_folder.name / "crews" + + if not crews_folder.exists(): + print("Crews folder does not exist in the current flow.") + raise click.ClickException("Crews folder does not exist in the current flow.") + + # Create the crew within the flow's crews directory + create_embedded_crew(crew_name, parent_folder=crews_folder) + + click.echo( + f"Crew {crew_name} added to the current flow successfully!", + ) + + +def create_embedded_crew(crew_name: str, parent_folder: Path) -> None: + """Create a new crew within an existing flow project.""" + folder_name = crew_name.replace(" ", "_").replace("-", "_").lower() + class_name = crew_name.replace("_", " ").replace("-", " ").title().replace(" ", "") + + crew_folder = parent_folder / folder_name + + if crew_folder.exists(): + if not click.confirm( + f"Crew {folder_name} already exists. Do you want to override it?" + ): + click.secho("Operation cancelled.", fg="yellow") + return + click.secho(f"Overriding crew {folder_name}...", fg="green", bold=True) + else: + click.secho(f"Creating crew {folder_name}...", fg="green", bold=True) + crew_folder.mkdir(parents=True) + + # Create config and crew.py files + config_folder = crew_folder / "config" + config_folder.mkdir(exist_ok=True) + + templates_dir = Path(__file__).parent / "templates" / "crew" + config_template_files = ["agents.yaml", "tasks.yaml"] + crew_template_file = f"{folder_name}_crew.py" # Updated file name + + for file_name in config_template_files: + src_file = templates_dir / "config" / file_name + dst_file = config_folder / file_name + copy_template(src_file, dst_file, crew_name, class_name, folder_name) + + src_file = templates_dir / "crew.py" + dst_file = crew_folder / crew_template_file + copy_template(src_file, dst_file, crew_name, class_name, folder_name) + + click.secho( + f"Crew {crew_name} added to the flow successfully!", fg="green", bold=True + ) diff --git a/src/crewai/cli/cli.py b/src/crewai/cli/cli.py index 1ea2ef4e2..0f43ff3f4 100644 --- a/src/crewai/cli/cli.py +++ b/src/crewai/cli/cli.py @@ -3,6 +3,7 @@ from typing import Optional import click import pkg_resources +from crewai.cli.add_crew_to_flow import add_crew_to_flow from crewai.cli.create_crew import create_crew from crewai.cli.create_flow import create_flow from crewai.cli.create_pipeline import create_pipeline @@ -178,10 +179,12 @@ def test(n_iterations: int, model: str): evaluate_crew(n_iterations, model) -@crewai.command(context_settings=dict( - ignore_unknown_options=True, - allow_extra_args=True, -)) +@crewai.command( + context_settings=dict( + ignore_unknown_options=True, + allow_extra_args=True, + ) +) @click.pass_context def install(context): """Install the Crew.""" @@ -324,5 +327,13 @@ def flow_plot(): plot_flow() +@flow.command(name="add-crew") +@click.argument("crew_name") +def flow_add_crew(crew_name): + """Add a crew to an existing flow.""" + click.echo(f"Adding crew {crew_name} to the flow") + add_crew_to_flow(crew_name) + + if __name__ == "__main__": crewai() diff --git a/tests/cli/cli_test.py b/tests/cli/cli_test.py index b2fb8d0e5..05e1cf03a 100644 --- a/tests/cli/cli_test.py +++ b/tests/cli/cli_test.py @@ -1,7 +1,9 @@ +from pathlib import Path from unittest import mock import pytest from click.testing import CliRunner + from crewai.cli.cli import ( deploy_create, deploy_list, @@ -9,6 +11,7 @@ from crewai.cli.cli import ( deploy_push, deploy_remove, deply_status, + flow_add_crew, reset_memories, signup, test, @@ -277,3 +280,42 @@ def test_deploy_remove_no_uuid(command, runner): assert result.exit_code == 0 mock_deploy.remove_crew.assert_called_once_with(uuid=None) + + +@mock.patch("crewai.cli.add_crew_to_flow.create_embedded_crew") +@mock.patch("pathlib.Path.exists", return_value=True) # Mock the existence check +def test_flow_add_crew(mock_path_exists, mock_create_embedded_crew, runner): + crew_name = "new_crew" + result = runner.invoke(flow_add_crew, [crew_name]) + + # Log the output for debugging + print(result.output) + + assert result.exit_code == 0, f"Command failed with output: {result.output}" + assert f"Adding crew {crew_name} to the flow" in result.output + + # Verify that create_embedded_crew was called with the correct arguments + mock_create_embedded_crew.assert_called_once() + call_args, call_kwargs = mock_create_embedded_crew.call_args + assert call_args[0] == crew_name + assert "parent_folder" in call_kwargs + assert isinstance(call_kwargs["parent_folder"], Path) + + +def test_add_crew_to_flow_not_in_root(runner): + # Simulate not being in the root of a flow project + with mock.patch("pathlib.Path.exists", autospec=True) as mock_exists: + # Mock Path.exists to return False when checking for pyproject.toml + def exists_side_effect(self): + if self.name == "pyproject.toml": + return False # Simulate that pyproject.toml does not exist + return True # All other paths exist + + mock_exists.side_effect = exists_side_effect + + result = runner.invoke(flow_add_crew, ["new_crew"]) + + assert result.exit_code != 0 + assert "This command must be run from the root of a flow project." in str( + result.output + ) From cdfbd5f62b23ed8753a48bd8df1a0ef4dbf68363 Mon Sep 17 00:00:00 2001 From: "Brandon Hancock (bhancock_ai)" <109994880+bhancockio@users.noreply.github.com> Date: Tue, 29 Oct 2024 18:36:53 -0400 Subject: [PATCH 029/126] Bugfix/flows with multiple starts plus ands breaking (#1531) * bugfix/flows-with-multiple-starts-plus-ands-breaking * fix user found issue * remove prints --- src/crewai/flow/flow.py | 75 +++++++++++++++++++++++++++-------------- 1 file changed, 49 insertions(+), 26 deletions(-) diff --git a/src/crewai/flow/flow.py b/src/crewai/flow/flow.py index de9b2eeb2..e7231e13f 100644 --- a/src/crewai/flow/flow.py +++ b/src/crewai/flow/flow.py @@ -1,5 +1,3 @@ -# flow.py - import asyncio import inspect from typing import Any, Callable, Dict, Generic, List, Set, Type, TypeVar, Union @@ -120,6 +118,8 @@ class FlowMeta(type): methods = attr_value.__trigger_methods__ condition_type = getattr(attr_value, "__condition_type__", "OR") listeners[attr_name] = (condition_type, methods) + + # TODO: should we add a check for __condition_type__ 'AND'? elif hasattr(attr_value, "__is_router__"): routers[attr_value.__router_for__] = attr_name possible_returns = get_possible_return_constants(attr_value) @@ -159,7 +159,8 @@ class Flow(Generic[T], metaclass=FlowMeta): def __init__(self) -> None: self._methods: Dict[str, Callable] = {} self._state: T = self._create_initial_state() - self._completed_methods: Set[str] = set() + self._executed_methods: Set[str] = set() + self._scheduled_tasks: Set[str] = set() self._pending_and_listeners: Dict[str, Set[str]] = {} self._method_outputs: List[Any] = [] # List to store all method outputs @@ -216,17 +217,24 @@ class Flow(Generic[T], metaclass=FlowMeta): else: return None # Or raise an exception if no methods were executed - async def _execute_start_method(self, start_method: str) -> None: - result = await self._execute_method(self._methods[start_method]) - await self._execute_listeners(start_method, result) + async def _execute_start_method(self, start_method_name: str) -> None: + result = await self._execute_method( + start_method_name, self._methods[start_method_name] + ) + await self._execute_listeners(start_method_name, result) - async def _execute_method(self, method: Callable, *args: Any, **kwargs: Any) -> Any: + async def _execute_method( + self, method_name: str, method: Callable, *args: Any, **kwargs: Any + ) -> Any: result = ( await method(*args, **kwargs) if asyncio.iscoroutinefunction(method) else method(*args, **kwargs) ) self._method_outputs.append(result) # Store the output + + self._executed_methods.add(method_name) + return result async def _execute_listeners(self, trigger_method: str, result: Any) -> None: @@ -234,32 +242,40 @@ class Flow(Generic[T], metaclass=FlowMeta): if trigger_method in self._routers: router_method = self._methods[self._routers[trigger_method]] - path = await self._execute_method(router_method) + path = await self._execute_method( + trigger_method, router_method + ) # TODO: Change or not? # Use the path as the new trigger method trigger_method = path - for listener, (condition_type, methods) in self._listeners.items(): + for listener_name, (condition_type, methods) in self._listeners.items(): if condition_type == "OR": if trigger_method in methods: - listener_tasks.append( - self._execute_single_listener(listener, result) - ) + if ( + listener_name not in self._executed_methods + and listener_name not in self._scheduled_tasks + ): + self._scheduled_tasks.add(listener_name) + listener_tasks.append( + self._execute_single_listener(listener_name, result) + ) elif condition_type == "AND": - if listener not in self._pending_and_listeners: - self._pending_and_listeners[listener] = set() - self._pending_and_listeners[listener].add(trigger_method) - if set(methods) == self._pending_and_listeners[listener]: - listener_tasks.append( - self._execute_single_listener(listener, result) - ) - del self._pending_and_listeners[listener] + if all(method in self._executed_methods for method in methods): + if ( + listener_name not in self._executed_methods + and listener_name not in self._scheduled_tasks + ): + self._scheduled_tasks.add(listener_name) + listener_tasks.append( + self._execute_single_listener(listener_name, result) + ) # Run all listener tasks concurrently and wait for them to complete await asyncio.gather(*listener_tasks) - async def _execute_single_listener(self, listener: str, result: Any) -> None: + async def _execute_single_listener(self, listener_name: str, result: Any) -> None: try: - method = self._methods[listener] + method = self._methods[listener_name] sig = inspect.signature(method) params = list(sig.parameters.values()) @@ -268,15 +284,22 @@ class Flow(Generic[T], metaclass=FlowMeta): if method_params: # If listener expects parameters, pass the result - listener_result = await self._execute_method(method, result) + listener_result = await self._execute_method( + listener_name, method, result + ) else: # If listener does not expect parameters, call without arguments - listener_result = await self._execute_method(method) + listener_result = await self._execute_method(listener_name, method) + + # Remove from scheduled tasks after execution + self._scheduled_tasks.discard(listener_name) # Execute listeners of this listener - await self._execute_listeners(listener, listener_result) + await self._execute_listeners(listener_name, listener_result) except Exception as e: - print(f"[Flow._execute_single_listener] Error in method {listener}: {e}") + print( + f"[Flow._execute_single_listener] Error in method {listener_name}: {e}" + ) import traceback traceback.print_exc() From 5f46ff883632e4b3396c6dfbae98b9347e807f99 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Moura?= Date: Wed, 30 Oct 2024 00:07:46 -0300 Subject: [PATCH 030/126] prepare new version --- pyproject.toml | 8 ++++---- src/crewai/__init__.py | 2 +- src/crewai/cli/templates/crew/pyproject.toml | 2 +- src/crewai/cli/templates/flow/pyproject.toml | 2 +- src/crewai/cli/templates/pipeline/pyproject.toml | 2 +- .../cli/templates/pipeline_router/pyproject.toml | 2 +- src/crewai/cli/templates/tool/pyproject.toml | 2 +- uv.lock | 14 +++++++------- 8 files changed, 17 insertions(+), 17 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index ef8181cbc..b9e6bc986 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "crewai" -version = "0.76.2" +version = "0.76.9" description = "Cutting-edge framework for orchestrating role-playing, autonomous AI agents. By fostering collaborative intelligence, CrewAI empowers agents to work together seamlessly, tackling complex tasks." readme = "README.md" requires-python = ">=3.10,<=3.13" @@ -16,7 +16,7 @@ dependencies = [ "opentelemetry-exporter-otlp-proto-http>=1.22.0", "instructor>=1.3.3", "regex>=2024.9.11", - "crewai-tools>=0.13.2", + "crewai-tools>=0.13.4", "click>=8.1.7", "python-dotenv>=1.0.0", "appdirs>=1.4.4", @@ -37,7 +37,7 @@ Documentation = "https://docs.crewai.com" Repository = "https://github.com/crewAIInc/crewAI" [project.optional-dependencies] -tools = ["crewai-tools>=0.13.2"] +tools = ["crewai-tools>=0.13.4"] agentops = ["agentops>=0.3.0"] [tool.uv] @@ -52,7 +52,7 @@ dev-dependencies = [ "mkdocs-material-extensions>=1.3.1", "pillow>=10.2.0", "cairosvg>=2.7.1", - "crewai-tools>=0.13.2", + "crewai-tools>=0.13.4", "pytest>=8.0.0", "pytest-vcr>=1.0.2", "python-dotenv>=1.0.0", diff --git a/src/crewai/__init__.py b/src/crewai/__init__.py index 5160aa77f..0a2f02a59 100644 --- a/src/crewai/__init__.py +++ b/src/crewai/__init__.py @@ -14,5 +14,5 @@ warnings.filterwarnings( category=UserWarning, module="pydantic.main", ) -__version__ = "0.76.2" +__version__ = "0.76.9" __all__ = ["Agent", "Crew", "Process", "Task", "Pipeline", "Router", "LLM", "Flow"] diff --git a/src/crewai/cli/templates/crew/pyproject.toml b/src/crewai/cli/templates/crew/pyproject.toml index ed32aa600..447cfcb86 100644 --- a/src/crewai/cli/templates/crew/pyproject.toml +++ b/src/crewai/cli/templates/crew/pyproject.toml @@ -5,7 +5,7 @@ description = "{{name}} using crewAI" authors = [{ name = "Your Name", email = "you@example.com" }] requires-python = ">=3.10,<=3.13" dependencies = [ - "crewai[tools]>=0.76.2,<1.0.0" + "crewai[tools]>=0.76.9,<1.0.0" ] [project.scripts] diff --git a/src/crewai/cli/templates/flow/pyproject.toml b/src/crewai/cli/templates/flow/pyproject.toml index 815e8b205..1ef8f7b36 100644 --- a/src/crewai/cli/templates/flow/pyproject.toml +++ b/src/crewai/cli/templates/flow/pyproject.toml @@ -5,7 +5,7 @@ description = "{{name}} using crewAI" authors = [{ name = "Your Name", email = "you@example.com" }] requires-python = ">=3.10,<=3.13" dependencies = [ - "crewai[tools]>=0.76.2,<1.0.0", + "crewai[tools]>=0.76.9,<1.0.0", ] [project.scripts] diff --git a/src/crewai/cli/templates/pipeline/pyproject.toml b/src/crewai/cli/templates/pipeline/pyproject.toml index b4e74abea..53f304283 100644 --- a/src/crewai/cli/templates/pipeline/pyproject.toml +++ b/src/crewai/cli/templates/pipeline/pyproject.toml @@ -6,7 +6,7 @@ authors = ["Your Name "] [tool.poetry.dependencies] python = ">=3.10,<=3.13" -crewai = { extras = ["tools"], version = ">=0.76.2,<1.0.0" } +crewai = { extras = ["tools"], version = ">=0.76.9,<1.0.0" } asyncio = "*" [tool.poetry.scripts] diff --git a/src/crewai/cli/templates/pipeline_router/pyproject.toml b/src/crewai/cli/templates/pipeline_router/pyproject.toml index bd487ce95..33d5c58af 100644 --- a/src/crewai/cli/templates/pipeline_router/pyproject.toml +++ b/src/crewai/cli/templates/pipeline_router/pyproject.toml @@ -5,7 +5,7 @@ description = "{{name}} using crewAI" authors = ["Your Name "] requires-python = ">=3.10,<=3.13" dependencies = [ - "crewai[tools]>=0.76.2,<1.0.0" + "crewai[tools]>=0.76.9,<1.0.0" ] [project.scripts] diff --git a/src/crewai/cli/templates/tool/pyproject.toml b/src/crewai/cli/templates/tool/pyproject.toml index 360a940ed..849298b6b 100644 --- a/src/crewai/cli/templates/tool/pyproject.toml +++ b/src/crewai/cli/templates/tool/pyproject.toml @@ -5,6 +5,6 @@ description = "Power up your crews with {{folder_name}}" readme = "README.md" requires-python = ">=3.10,<=3.13" dependencies = [ - "crewai[tools]>=0.76.2" + "crewai[tools]>=0.76.9" ] diff --git a/uv.lock b/uv.lock index df2572402..f7f6a1839 100644 --- a/uv.lock +++ b/uv.lock @@ -604,7 +604,7 @@ wheels = [ [[package]] name = "crewai" -version = "0.76.2" +version = "0.76.9" source = { editable = "." } dependencies = [ { name = "appdirs" }, @@ -665,8 +665,8 @@ requires-dist = [ { name = "auth0-python", specifier = ">=4.7.1" }, { name = "chromadb", specifier = ">=0.4.24" }, { name = "click", specifier = ">=8.1.7" }, - { name = "crewai-tools", specifier = ">=0.13.2" }, - { name = "crewai-tools", marker = "extra == 'tools'", specifier = ">=0.13.2" }, + { name = "crewai-tools", specifier = ">=0.13.4" }, + { name = "crewai-tools", marker = "extra == 'tools'", specifier = ">=0.13.4" }, { name = "instructor", specifier = ">=1.3.3" }, { name = "json-repair", specifier = ">=0.25.2" }, { name = "jsonref", specifier = ">=1.1.0" }, @@ -688,7 +688,7 @@ requires-dist = [ [package.metadata.requires-dev] dev = [ { name = "cairosvg", specifier = ">=2.7.1" }, - { name = "crewai-tools", specifier = ">=0.13.2" }, + { name = "crewai-tools", specifier = ">=0.13.4" }, { name = "mkdocs", specifier = ">=1.4.3" }, { name = "mkdocs-material", specifier = ">=9.5.7" }, { name = "mkdocs-material-extensions", specifier = ">=1.3.1" }, @@ -707,7 +707,7 @@ dev = [ [[package]] name = "crewai-tools" -version = "0.13.2" +version = "0.13.4" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "beautifulsoup4" }, @@ -725,9 +725,9 @@ dependencies = [ { name = "requests" }, { name = "selenium" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/96/02/136f42ed8a7bd706a85663714c615bdcb684e43e95e4719c892aa0ce3d53/crewai_tools-0.13.2.tar.gz", hash = "sha256:c6782f2e868c0e96b25891f1b40fb8c90c01e920bab2fd1388f89ef1d7a4b99b", size = 816250 } +sdist = { url = "https://files.pythonhosted.org/packages/64/bd/eff7b633a0b28ff4ed115adde1499e3dcc683e4f0b5c378a4c6f5c0c1bf6/crewai_tools-0.13.4.tar.gz", hash = "sha256:b6ac527633b7018471d892c21ac96bc961a86b6626d996b1ed7d53cd481d4505", size = 816588 } wheels = [ - { url = "https://files.pythonhosted.org/packages/28/30/df215173b6193b2cfb1902a339443be73056eae89579805b853c6f359761/crewai_tools-0.13.2-py3-none-any.whl", hash = "sha256:8c7583c9559fb625f594349c6553a5251ebd7b21918735ad6fbe8bab7ec3db50", size = 463444 }, + { url = "https://files.pythonhosted.org/packages/6c/40/93cd347d854059cf5e54a81b70f896deea7ad1f03e9c024549eb323c4da5/crewai_tools-0.13.4-py3-none-any.whl", hash = "sha256:eda78fe3c4df57676259d8dd6b2610fa31f89b90909512f15893adb57fb9e825", size = 463703 }, ] [[package]] From 55cd15bfc629ab4aad159126c2aaf9dd10e647bf Mon Sep 17 00:00:00 2001 From: Rip&Tear <84775494+theCyberTech@users.noreply.github.com> Date: Thu, 31 Oct 2024 00:07:38 +0800 Subject: [PATCH 031/126] Added security.md file (#1533) --- .github/security.md | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) create mode 100644 .github/security.md diff --git a/.github/security.md b/.github/security.md new file mode 100644 index 000000000..5bc967228 --- /dev/null +++ b/.github/security.md @@ -0,0 +1,19 @@ +CrewAI takes the security of our software products and services seriously, which includes all source code repositories managed through our GitHub organization. +If you believe you have found a security vulnerability in any CrewAI product or service, please report it to us as described below. + + ## Reporting a Vulnerability + Please do not report security vulnerabilities through public GitHub issues. + To report a vulnerability, please email us at security@crewai.com. + Please include the requested information listed below so that we can triage your report more quickly + + - Type of issue (e.g. SQL injection, cross-site scripting, etc.) + - Full paths of source file(s) related to the manifestation of the issue + - The location of the affected source code (tag/branch/commit or direct URL) + - Any special configuration required to reproduce the issue + - Step-by-step instructions to reproduce the issue (please include screenshots if needed) + - Proof-of-concept or exploit code (if possible) + - Impact of the issue, including how an attacker might exploit the issue + + Once we have received your report, we will respond to you at the email address you provide. If the issue is confirmed, we will release a patch as soon as possible depending on the complexity of the issue. + + At this time, we are not offering a bug bounty program. Any rewards will be at our discretion. \ No newline at end of file From 6193eb13fa0b56a214d920c60f6b1e5225899e65 Mon Sep 17 00:00:00 2001 From: "Brandon Hancock (bhancock_ai)" <109994880+bhancockio@users.noreply.github.com> Date: Wed, 30 Oct 2024 16:37:21 -0400 Subject: [PATCH 032/126] Disable telemetry explicitly (#1536) * Disable telemetry explicitly * fix linting * revert parts to og --- src/crewai/telemetry/telemetry.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/crewai/telemetry/telemetry.py b/src/crewai/telemetry/telemetry.py index a08ccd96f..e191f8d4d 100644 --- a/src/crewai/telemetry/telemetry.py +++ b/src/crewai/telemetry/telemetry.py @@ -21,7 +21,7 @@ with suppress_warnings(): from opentelemetry import trace # noqa: E402 -from opentelemetry.exporter.otlp.proto.http.trace_exporter import OTLPSpanExporter # noqa: E402 +from opentelemetry.exporter.otlp.proto.http.trace_exporter import OTLPSpanExporter # noqa: E402 from opentelemetry.sdk.resources import SERVICE_NAME, Resource # noqa: E402 from opentelemetry.sdk.trace import TracerProvider # noqa: E402 from opentelemetry.sdk.trace.export import BatchSpanProcessor # noqa: E402 @@ -48,6 +48,10 @@ class Telemetry: def __init__(self): self.ready = False self.trace_set = False + + if os.getenv("OTEL_SDK_DISABLED", "false").lower() == "true": + return + try: telemetry_endpoint = "https://telemetry.crewai.com:4319" self.resource = Resource( From 4ae07468f37db34e92df20a6aaae255ffdec7950 Mon Sep 17 00:00:00 2001 From: Robin Wang <6220861+MottoX@users.noreply.github.com> Date: Thu, 31 Oct 2024 04:45:19 +0800 Subject: [PATCH 033/126] Enhance log storage to support more data types (#1530) --- src/crewai/memory/storage/kickoff_task_outputs_storage.py | 2 +- src/crewai/utilities/crew_json_encoder.py | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/src/crewai/memory/storage/kickoff_task_outputs_storage.py b/src/crewai/memory/storage/kickoff_task_outputs_storage.py index 57623eef8..dbb5f124b 100644 --- a/src/crewai/memory/storage/kickoff_task_outputs_storage.py +++ b/src/crewai/memory/storage/kickoff_task_outputs_storage.py @@ -70,7 +70,7 @@ class KickoffTaskOutputsSQLiteStorage: task.expected_output, json.dumps(output, cls=CrewJSONEncoder), task_index, - json.dumps(inputs), + json.dumps(inputs, cls=CrewJSONEncoder), was_replayed, ), ) diff --git a/src/crewai/utilities/crew_json_encoder.py b/src/crewai/utilities/crew_json_encoder.py index 3cab07ffc..c3f95fcf6 100644 --- a/src/crewai/utilities/crew_json_encoder.py +++ b/src/crewai/utilities/crew_json_encoder.py @@ -2,13 +2,14 @@ from datetime import datetime, date import json from uuid import UUID from pydantic import BaseModel +from decimal import Decimal class CrewJSONEncoder(json.JSONEncoder): def default(self, obj): if isinstance(obj, BaseModel): return self._handle_pydantic_model(obj) - elif isinstance(obj, UUID): + elif isinstance(obj, UUID) or isinstance(obj, Decimal): return str(obj) elif isinstance(obj, datetime) or isinstance(obj, date): From ec2967c3624f355cb652541f4a64f21df1d9dc17 Mon Sep 17 00:00:00 2001 From: Tony Kipkemboi Date: Wed, 30 Oct 2024 21:56:13 -0400 Subject: [PATCH 034/126] Add llm providers accordion group (#1534) * add llm providers accordion group * fix numbering --- docs/concepts/llms.mdx | 249 ++++++++++++++++++++++++++++------------- 1 file changed, 173 insertions(+), 76 deletions(-) diff --git a/docs/concepts/llms.mdx b/docs/concepts/llms.mdx index 90e86adaf..835c2491f 100644 --- a/docs/concepts/llms.mdx +++ b/docs/concepts/llms.mdx @@ -25,52 +25,55 @@ By default, CrewAI uses the `gpt-4o-mini` model. It uses environment variables i - `OPENAI_API_BASE` - `OPENAI_API_KEY` -### 2. String Identifier - -```python Code -agent = Agent(llm="gpt-4o", ...) -``` - -### 3. LLM Instance - -List of [more providers](https://docs.litellm.ai/docs/providers). - -```python Code -from crewai import LLM - -llm = LLM(model="gpt-4", temperature=0.7) -agent = Agent(llm=llm, ...) -``` - -### 4. Custom LLM Objects +### 2. Custom LLM Objects Pass a custom LLM implementation or object from another library. +See below for examples. + + + + ```python Code + agent = Agent(llm="gpt-4o", ...) + ``` + + + + ```python Code + from crewai import LLM + + llm = LLM(model="gpt-4", temperature=0.7) + agent = Agent(llm=llm, ...) + ``` + + + ## Connecting to OpenAI-Compatible LLMs You can connect to OpenAI-compatible LLMs using either environment variables or by setting specific attributes on the LLM class: -1. Using environment variables: + + + ```python Code + import os -```python Code -import os + os.environ["OPENAI_API_KEY"] = "your-api-key" + os.environ["OPENAI_API_BASE"] = "https://api.your-provider.com/v1" + ``` + + + ```python Code + from crewai import LLM -os.environ["OPENAI_API_KEY"] = "your-api-key" -os.environ["OPENAI_API_BASE"] = "https://api.your-provider.com/v1" -``` - -2. Using LLM class attributes: - -```python Code -from crewai import LLM - -llm = LLM( - model="custom-model-name", - api_key="your-api-key", - base_url="https://api.your-provider.com/v1" -) -agent = Agent(llm=llm, ...) -``` + llm = LLM( + model="custom-model-name", + api_key="your-api-key", + base_url="https://api.your-provider.com/v1" + ) + agent = Agent(llm=llm, ...) + ``` + + ## LLM Configuration Options @@ -97,55 +100,149 @@ When configuring an LLM for your agent, you have access to a wide range of param | **api_key** | `str` | Your API key for authentication. | -## OpenAI Example Configuration +These are examples of how to configure LLMs for your agent. -```python Code -from crewai import LLM + + -llm = LLM( - model="gpt-4", - temperature=0.8, - max_tokens=150, - top_p=0.9, - frequency_penalty=0.1, - presence_penalty=0.1, - stop=["END"], - seed=42, - base_url="https://api.openai.com/v1", - api_key="your-api-key-here" -) -agent = Agent(llm=llm, ...) -``` + ```python Code + from crewai import LLM -## Cerebras Example Configuration + llm = LLM( + model="gpt-4", + temperature=0.8, + max_tokens=150, + top_p=0.9, + frequency_penalty=0.1, + presence_penalty=0.1, + stop=["END"], + seed=42, + base_url="https://api.openai.com/v1", + api_key="your-api-key-here" + ) + agent = Agent(llm=llm, ...) + ``` + -```python Code -from crewai import LLM + -llm = LLM( - model="cerebras/llama-3.1-70b", - base_url="https://api.cerebras.ai/v1", - api_key="your-api-key-here" -) -agent = Agent(llm=llm, ...) -``` + ```python Code + from crewai import LLM -## Using Ollama (Local LLMs) + llm = LLM( + model="cerebras/llama-3.1-70b", + base_url="https://api.cerebras.ai/v1", + api_key="your-api-key-here" + ) + agent = Agent(llm=llm, ...) + ``` + + + -CrewAI supports using Ollama for running open-source models locally: + CrewAI supports using Ollama for running open-source models locally: -1. Install Ollama: [ollama.ai](https://ollama.ai/) -2. Run a model: `ollama run llama2` -3. Configure agent: + 1. Install Ollama: [ollama.ai](https://ollama.ai/) + 2. Run a model: `ollama run llama2` + 3. Configure agent: -```python Code -from crewai import LLM + ```python Code + from crewai import LLM -agent = Agent( - llm=LLM(model="ollama/llama3.1", base_url="http://localhost:11434"), - ... -) -``` + agent = Agent( + llm=LLM( + model="ollama/llama3.1", + base_url="http://localhost:11434" + ), + ... + ) + ``` + + + + + ```python Code + from crewai import LLM + + llm = LLM( + model="groq/llama3-8b-8192", + base_url="https://api.groq.com/openai/v1", + api_key="your-api-key-here" + ) + agent = Agent(llm=llm, ...) + ``` + + + + + ```python Code + from crewai import LLM + + llm = LLM( + model="anthropic/claude-3-5-sonnet-20241022", + base_url="https://api.anthropic.com/v1", + api_key="your-api-key-here" + ) + agent = Agent(llm=llm, ...) + ``` + + + + + ```python Code + from crewai import LLM + + llm = LLM( + model="fireworks/meta-llama-3.1-8b-instruct", + base_url="https://api.fireworks.ai/inference/v1", + api_key="your-api-key-here" + ) + agent = Agent(llm=llm, ...) + ``` + + + + + ```python Code + from crewai import LLM + + llm = LLM( + model="gemini/gemini-1.5-flash", + base_url="https://api.gemini.google.com/v1", + api_key="your-api-key-here" + ) + agent = Agent(llm=llm, ...) + ``` + + + + + ```python Code + from crewai import LLM + + llm = LLM( + model="perplexity/mistral-7b-instruct", + base_url="https://api.perplexity.ai/v1", + api_key="your-api-key-here" + ) + agent = Agent(llm=llm, ...) + ``` + + + + + ```python Code + from crewai import LLM + + llm = LLM( + model="watsonx/ibm/granite-13b-chat-v2", + base_url="https://api.watsonx.ai/v1", + api_key="your-api-key-here" + ) + agent = Agent(llm=llm, ...) + ``` + + ## Changing the Base API URL From 66698503b878459ed5d7d208c7ec37e995a54f28 Mon Sep 17 00:00:00 2001 From: Vini Brasil Date: Thu, 31 Oct 2024 15:00:58 -0300 Subject: [PATCH 035/126] Replace .netrc with uv environment variables (#1541) This commit replaces .netrc with uv environment variables for installing tools from private repositories. To store credentials, I created a new and reusable settings file for the CLI in `$HOME/.config/crewai/settings.json`. The issue with .netrc files is that they are applied system-wide and are scoped by hostname, meaning we can't differentiate tool repositories requests from regular requests to CrewAI's API. --- src/crewai/cli/config.py | 38 ++++++++++++ src/crewai/cli/tools/main.py | 40 +++++++------ tests/cli/config_test.py | 109 +++++++++++++++++++++++++++++++++++ tests/cli/tools/test_main.py | 1 + 4 files changed, 169 insertions(+), 19 deletions(-) create mode 100644 src/crewai/cli/config.py create mode 100644 tests/cli/config_test.py diff --git a/src/crewai/cli/config.py b/src/crewai/cli/config.py new file mode 100644 index 000000000..000f1e6d0 --- /dev/null +++ b/src/crewai/cli/config.py @@ -0,0 +1,38 @@ +import json +from pathlib import Path +from pydantic import BaseModel, Field +from typing import Optional + +DEFAULT_CONFIG_PATH = Path.home() / ".config" / "crewai" / "settings.json" + +class Settings(BaseModel): + tool_repository_username: Optional[str] = Field(None, description="Username for interacting with the Tool Repository") + tool_repository_password: Optional[str] = Field(None, description="Password for interacting with the Tool Repository") + config_path: Path = Field(default=DEFAULT_CONFIG_PATH, exclude=True) + + def __init__(self, config_path: Path = DEFAULT_CONFIG_PATH, **data): + """Load Settings from config path""" + config_path.parent.mkdir(parents=True, exist_ok=True) + + file_data = {} + if config_path.is_file(): + try: + with config_path.open("r") as f: + file_data = json.load(f) + except json.JSONDecodeError: + file_data = {} + + merged_data = {**file_data, **data} + super().__init__(config_path=config_path, **merged_data) + + def dump(self) -> None: + """Save current settings to settings.json""" + if self.config_path.is_file(): + with self.config_path.open("r") as f: + existing_data = json.load(f) + else: + existing_data = {} + + updated_data = {**existing_data, **self.model_dump(exclude_unset=True)} + with self.config_path.open("w") as f: + json.dump(updated_data, f, indent=4) diff --git a/src/crewai/cli/tools/main.py b/src/crewai/cli/tools/main.py index c875229c3..35652a1ec 100644 --- a/src/crewai/cli/tools/main.py +++ b/src/crewai/cli/tools/main.py @@ -1,17 +1,15 @@ import base64 import os -import platform import subprocess import tempfile from pathlib import Path -from netrc import netrc -import stat import click from rich.console import Console from crewai.cli import git from crewai.cli.command import BaseCommand, PlusAPIMixin +from crewai.cli.config import Settings from crewai.cli.utils import ( get_project_description, get_project_name, @@ -153,26 +151,16 @@ class ToolCommand(BaseCommand, PlusAPIMixin): raise SystemExit login_response_json = login_response.json() - self._set_netrc_credentials(login_response_json["credential"]) + + settings = Settings() + settings.tool_repository_username = login_response_json["credential"]["username"] + settings.tool_repository_password = login_response_json["credential"]["password"] + settings.dump() console.print( "Successfully authenticated to the tool repository.", style="bold green" ) - def _set_netrc_credentials(self, credentials, netrc_path=None): - if not netrc_path: - netrc_filename = "_netrc" if platform.system() == "Windows" else ".netrc" - netrc_path = Path.home() / netrc_filename - netrc_path.touch(mode=stat.S_IRUSR | stat.S_IWUSR, exist_ok=True) - - netrc_instance = netrc(file=netrc_path) - netrc_instance.hosts["app.crewai.com"] = (credentials["username"], "", credentials["password"]) - - with open(netrc_path, 'w') as file: - file.write(str(netrc_instance)) - - console.print(f"Added credentials to {netrc_path}", style="bold green") - def _add_package(self, tool_details): tool_handle = tool_details["handle"] repository_handle = tool_details["repository"]["handle"] @@ -187,7 +175,11 @@ class ToolCommand(BaseCommand, PlusAPIMixin): tool_handle, ] add_package_result = subprocess.run( - add_package_command, capture_output=False, text=True, check=True + add_package_command, + capture_output=False, + env=self._build_env_with_credentials(repository_handle), + text=True, + check=True ) if add_package_result.stderr: @@ -206,3 +198,13 @@ class ToolCommand(BaseCommand, PlusAPIMixin): "[bold yellow]Tip:[/bold yellow] Navigate to a different directory and try again." ) raise SystemExit + + def _build_env_with_credentials(self, repository_handle: str): + repository_handle = repository_handle.upper().replace("-", "_") + settings = Settings() + + env = os.environ.copy() + env[f"UV_INDEX_{repository_handle}_USERNAME"] = str(settings.tool_repository_username or "") + env[f"UV_INDEX_{repository_handle}_PASSWORD"] = str(settings.tool_repository_password or "") + + return env diff --git a/tests/cli/config_test.py b/tests/cli/config_test.py new file mode 100644 index 000000000..1065d4730 --- /dev/null +++ b/tests/cli/config_test.py @@ -0,0 +1,109 @@ +import unittest +import json +import tempfile +import shutil +from pathlib import Path +from crewai.cli.config import Settings + +class TestSettings(unittest.TestCase): + def setUp(self): + self.test_dir = Path(tempfile.mkdtemp()) + self.config_path = self.test_dir / "settings.json" + + def tearDown(self): + shutil.rmtree(self.test_dir) + + def test_empty_initialization(self): + settings = Settings(config_path=self.config_path) + self.assertIsNone(settings.tool_repository_username) + self.assertIsNone(settings.tool_repository_password) + + def test_initialization_with_data(self): + settings = Settings( + config_path=self.config_path, + tool_repository_username="user1" + ) + self.assertEqual(settings.tool_repository_username, "user1") + self.assertIsNone(settings.tool_repository_password) + + def test_initialization_with_existing_file(self): + self.config_path.parent.mkdir(parents=True, exist_ok=True) + with self.config_path.open("w") as f: + json.dump({"tool_repository_username": "file_user"}, f) + + settings = Settings(config_path=self.config_path) + self.assertEqual(settings.tool_repository_username, "file_user") + + def test_merge_file_and_input_data(self): + self.config_path.parent.mkdir(parents=True, exist_ok=True) + with self.config_path.open("w") as f: + json.dump({ + "tool_repository_username": "file_user", + "tool_repository_password": "file_pass" + }, f) + + settings = Settings( + config_path=self.config_path, + tool_repository_username="new_user" + ) + self.assertEqual(settings.tool_repository_username, "new_user") + self.assertEqual(settings.tool_repository_password, "file_pass") + + def test_dump_new_settings(self): + settings = Settings( + config_path=self.config_path, + tool_repository_username="user1" + ) + settings.dump() + + with self.config_path.open("r") as f: + saved_data = json.load(f) + + self.assertEqual(saved_data["tool_repository_username"], "user1") + + def test_update_existing_settings(self): + self.config_path.parent.mkdir(parents=True, exist_ok=True) + with self.config_path.open("w") as f: + json.dump({"existing_setting": "value"}, f) + + settings = Settings( + config_path=self.config_path, + tool_repository_username="user1" + ) + settings.dump() + + with self.config_path.open("r") as f: + saved_data = json.load(f) + + self.assertEqual(saved_data["existing_setting"], "value") + self.assertEqual(saved_data["tool_repository_username"], "user1") + + def test_none_values(self): + settings = Settings( + config_path=self.config_path, + tool_repository_username=None + ) + settings.dump() + + with self.config_path.open("r") as f: + saved_data = json.load(f) + + self.assertIsNone(saved_data.get("tool_repository_username")) + + def test_invalid_json_in_config(self): + self.config_path.parent.mkdir(parents=True, exist_ok=True) + with self.config_path.open("w") as f: + f.write("invalid json") + + try: + settings = Settings(config_path=self.config_path) + self.assertIsNone(settings.tool_repository_username) + except json.JSONDecodeError: + self.fail("Settings initialization should handle invalid JSON") + + def test_empty_config_file(self): + self.config_path.parent.mkdir(parents=True, exist_ok=True) + self.config_path.touch() + + settings = Settings(config_path=self.config_path) + self.assertIsNone(settings.tool_repository_username) diff --git a/tests/cli/tools/test_main.py b/tests/cli/tools/test_main.py index e4fc19be3..3804fb9b4 100644 --- a/tests/cli/tools/test_main.py +++ b/tests/cli/tools/test_main.py @@ -82,6 +82,7 @@ def test_install_success(mock_get, mock_subprocess_run): capture_output=False, text=True, check=True, + env=unittest.mock.ANY ) assert "Succesfully installed sample-tool" in output From e66a135d5d41a1dec37b32ddebd329d0f0dab670 Mon Sep 17 00:00:00 2001 From: C0deZ Date: Fri, 1 Nov 2024 12:30:48 -0400 Subject: [PATCH 036/126] refactor: Move BaseTool to main package and centralize tool description generation (#1514) * move base_tool to main package and consolidate tool desscription generation * update import path * update tests * update doc * add base_tool test * migrate agent delegation tools to use BaseTool * update tests * update import path for tool * fix lint * update param signature * add from_langchain to BaseTool for backwards support of langchain tools * fix the case where StructuredTool doesn't have func --------- Co-authored-by: c0dez Co-authored-by: Brandon Hancock (bhancock_ai) <109994880+bhancockio@users.noreply.github.com> --- docs/concepts/tools.mdx | 99 +++++----- docs/how-to/create-custom-tools.mdx | 18 +- src/crewai/agent.py | 25 +-- src/crewai/agents/agent_builder/base_agent.py | 13 +- src/crewai/agents/tools_handler.py | 2 +- .../cli/templates/crew/tools/custom_tool.py | 3 +- .../cli/templates/flow/tools/custom_tool.py | 2 +- .../templates/pipeline/tools/custom_tool.py | 3 +- .../pipeline_router/tools/custom_tool.py | 3 +- .../tool/src/{{folder_name}}/tool.py | 3 +- src/crewai/crew.py | 2 +- src/crewai/task.py | 7 +- src/crewai/tools/__init__.py | 1 + src/crewai/tools/agent_tools.py | 25 --- src/crewai/tools/agent_tools/agent_tools.py | 32 +++ .../tools/agent_tools/ask_question_tool.py | 26 +++ .../agent_tools/base_agent_tools.py} | 40 +--- .../tools/agent_tools/delegate_work_tool.py | 29 +++ src/crewai/tools/base_tool.py | 186 ++++++++++++++++++ .../tools/{ => cache_tools}/cache_tools.py | 0 src/crewai/tools/tool_usage.py | 20 +- tests/agent_test.py | 36 ++-- tests/agent_tools/lol.py | 0 tests/agents/agent_builder/base_agent_test.py | 5 +- tests/crew_test.py | 23 ++- tests/task_test.py | 6 +- tests/{ => tools}/agent_tools/__init__.py | 0 .../agent_tools/agent_tools_test.py | 30 +-- .../cassettes/test_ask_question.yaml | 0 ...t_ask_question_with_coworker_as_array.yaml | 0 ...uestion_with_wrong_co_worker_variable.yaml | 0 .../cassettes/test_delegate_work.yaml | 0 ...te_work_with_wrong_co_worker_variable.yaml | 0 ...egate_work_withwith_coworker_as_array.yaml | 0 tests/tools/test_base_tool.py | 109 ++++++++++ tests/tools/test_tool_usage.py | 16 +- 36 files changed, 547 insertions(+), 217 deletions(-) delete mode 100644 src/crewai/tools/agent_tools.py create mode 100644 src/crewai/tools/agent_tools/agent_tools.py create mode 100644 src/crewai/tools/agent_tools/ask_question_tool.py rename src/crewai/{agents/agent_builder/utilities/base_agent_tool.py => tools/agent_tools/base_agent_tools.py} (67%) create mode 100644 src/crewai/tools/agent_tools/delegate_work_tool.py create mode 100644 src/crewai/tools/base_tool.py rename src/crewai/tools/{ => cache_tools}/cache_tools.py (100%) delete mode 100644 tests/agent_tools/lol.py rename tests/{ => tools}/agent_tools/__init__.py (100%) rename tests/{ => tools}/agent_tools/agent_tools_test.py (95%) rename tests/{ => tools}/agent_tools/cassettes/test_ask_question.yaml (100%) rename tests/{ => tools}/agent_tools/cassettes/test_ask_question_with_coworker_as_array.yaml (100%) rename tests/{ => tools}/agent_tools/cassettes/test_ask_question_with_wrong_co_worker_variable.yaml (100%) rename tests/{ => tools}/agent_tools/cassettes/test_delegate_work.yaml (100%) rename tests/{ => tools}/agent_tools/cassettes/test_delegate_work_with_wrong_co_worker_variable.yaml (100%) rename tests/{ => tools}/agent_tools/cassettes/test_delegate_work_withwith_coworker_as_array.yaml (100%) create mode 100644 tests/tools/test_base_tool.py diff --git a/docs/concepts/tools.mdx b/docs/concepts/tools.mdx index 12ab96c6e..9b9c6a32e 100644 --- a/docs/concepts/tools.mdx +++ b/docs/concepts/tools.mdx @@ -5,13 +5,14 @@ icon: screwdriver-wrench --- ## Introduction -CrewAI tools empower agents with capabilities ranging from web searching and data analysis to collaboration and delegating tasks among coworkers. + +CrewAI tools empower agents with capabilities ranging from web searching and data analysis to collaboration and delegating tasks among coworkers. This documentation outlines how to create, integrate, and leverage these tools within the CrewAI framework, including a new focus on collaboration tools. ## What is a Tool? -A tool in CrewAI is a skill or function that agents can utilize to perform various actions. -This includes tools from the [CrewAI Toolkit](https://github.com/joaomdmoura/crewai-tools) and [LangChain Tools](https://python.langchain.com/docs/integrations/tools), +A tool in CrewAI is a skill or function that agents can utilize to perform various actions. +This includes tools from the [CrewAI Toolkit](https://github.com/joaomdmoura/crewai-tools) and [LangChain Tools](https://python.langchain.com/docs/integrations/tools), enabling everything from simple searches to complex interactions and effective teamwork among agents. ## Key Characteristics of Tools @@ -103,57 +104,53 @@ crew.kickoff() Here is a list of the available tools and their descriptions: -| Tool | Description | -| :-------------------------- | :-------------------------------------------------------------------------------------------- | -| **BrowserbaseLoadTool** | A tool for interacting with and extracting data from web browsers. | -| **CodeDocsSearchTool** | A RAG tool optimized for searching through code documentation and related technical documents. | -| **CodeInterpreterTool** | A tool for interpreting python code. | -| **ComposioTool** | Enables use of Composio tools. | -| **CSVSearchTool** | A RAG tool designed for searching within CSV files, tailored to handle structured data. | -| **DALL-E Tool** | A tool for generating images using the DALL-E API. | -| **DirectorySearchTool** | A RAG tool for searching within directories, useful for navigating through file systems. | -| **DOCXSearchTool** | A RAG tool aimed at searching within DOCX documents, ideal for processing Word files. | -| **DirectoryReadTool** | Facilitates reading and processing of directory structures and their contents. | -| **EXASearchTool** | A tool designed for performing exhaustive searches across various data sources. | -| **FileReadTool** | Enables reading and extracting data from files, supporting various file formats. | -| **FirecrawlSearchTool** | A tool to search webpages using Firecrawl and return the results. | -| **FirecrawlCrawlWebsiteTool** | A tool for crawling webpages using Firecrawl. | -| **FirecrawlScrapeWebsiteTool** | A tool for scraping webpages URL using Firecrawl and returning its contents. | -| **GithubSearchTool** | A RAG tool for searching within GitHub repositories, useful for code and documentation search.| -| **SerperDevTool** | A specialized tool for development purposes, with specific functionalities under development. | -| **TXTSearchTool** | A RAG tool focused on searching within text (.txt) files, suitable for unstructured data. | -| **JSONSearchTool** | A RAG tool designed for searching within JSON files, catering to structured data handling. | -| **LlamaIndexTool** | Enables the use of LlamaIndex tools. | -| **MDXSearchTool** | A RAG tool tailored for searching within Markdown (MDX) files, useful for documentation. | -| **PDFSearchTool** | A RAG tool aimed at searching within PDF documents, ideal for processing scanned documents. | -| **PGSearchTool** | A RAG tool optimized for searching within PostgreSQL databases, suitable for database queries. | -| **Vision Tool** | A tool for generating images using the DALL-E API. | -| **RagTool** | A general-purpose RAG tool capable of handling various data sources and types. | -| **ScrapeElementFromWebsiteTool** | Enables scraping specific elements from websites, useful for targeted data extraction. | -| **ScrapeWebsiteTool** | Facilitates scraping entire websites, ideal for comprehensive data collection. | -| **WebsiteSearchTool** | A RAG tool for searching website content, optimized for web data extraction. | -| **XMLSearchTool** | A RAG tool designed for searching within XML files, suitable for structured data formats. | -| **YoutubeChannelSearchTool**| A RAG tool for searching within YouTube channels, useful for video content analysis. | -| **YoutubeVideoSearchTool** | A RAG tool aimed at searching within YouTube videos, ideal for video data extraction. | +| Tool | Description | +| :------------------------------- | :--------------------------------------------------------------------------------------------- | +| **BrowserbaseLoadTool** | A tool for interacting with and extracting data from web browsers. | +| **CodeDocsSearchTool** | A RAG tool optimized for searching through code documentation and related technical documents. | +| **CodeInterpreterTool** | A tool for interpreting python code. | +| **ComposioTool** | Enables use of Composio tools. | +| **CSVSearchTool** | A RAG tool designed for searching within CSV files, tailored to handle structured data. | +| **DALL-E Tool** | A tool for generating images using the DALL-E API. | +| **DirectorySearchTool** | A RAG tool for searching within directories, useful for navigating through file systems. | +| **DOCXSearchTool** | A RAG tool aimed at searching within DOCX documents, ideal for processing Word files. | +| **DirectoryReadTool** | Facilitates reading and processing of directory structures and their contents. | +| **EXASearchTool** | A tool designed for performing exhaustive searches across various data sources. | +| **FileReadTool** | Enables reading and extracting data from files, supporting various file formats. | +| **FirecrawlSearchTool** | A tool to search webpages using Firecrawl and return the results. | +| **FirecrawlCrawlWebsiteTool** | A tool for crawling webpages using Firecrawl. | +| **FirecrawlScrapeWebsiteTool** | A tool for scraping webpages URL using Firecrawl and returning its contents. | +| **GithubSearchTool** | A RAG tool for searching within GitHub repositories, useful for code and documentation search. | +| **SerperDevTool** | A specialized tool for development purposes, with specific functionalities under development. | +| **TXTSearchTool** | A RAG tool focused on searching within text (.txt) files, suitable for unstructured data. | +| **JSONSearchTool** | A RAG tool designed for searching within JSON files, catering to structured data handling. | +| **LlamaIndexTool** | Enables the use of LlamaIndex tools. | +| **MDXSearchTool** | A RAG tool tailored for searching within Markdown (MDX) files, useful for documentation. | +| **PDFSearchTool** | A RAG tool aimed at searching within PDF documents, ideal for processing scanned documents. | +| **PGSearchTool** | A RAG tool optimized for searching within PostgreSQL databases, suitable for database queries. | +| **Vision Tool** | A tool for generating images using the DALL-E API. | +| **RagTool** | A general-purpose RAG tool capable of handling various data sources and types. | +| **ScrapeElementFromWebsiteTool** | Enables scraping specific elements from websites, useful for targeted data extraction. | +| **ScrapeWebsiteTool** | Facilitates scraping entire websites, ideal for comprehensive data collection. | +| **WebsiteSearchTool** | A RAG tool for searching website content, optimized for web data extraction. | +| **XMLSearchTool** | A RAG tool designed for searching within XML files, suitable for structured data formats. | +| **YoutubeChannelSearchTool** | A RAG tool for searching within YouTube channels, useful for video content analysis. | +| **YoutubeVideoSearchTool** | A RAG tool aimed at searching within YouTube videos, ideal for video data extraction. | ## Creating your own Tools - Developers can craft `custom tools` tailored for their agent’s needs or utilize pre-built options. + Developers can craft `custom tools` tailored for their agent’s needs or + utilize pre-built options. -To create your own CrewAI tools you will need to install our extra tools package: - -```bash -pip install 'crewai[tools]' -``` - -Once you do that there are two main ways for one to create a CrewAI tool: +There are two main ways for one to create a CrewAI tool: ### Subclassing `BaseTool` ```python Code -from crewai_tools import BaseTool +from crewai.tools import BaseTool + class MyCustomTool(BaseTool): name: str = "Name of my tool" @@ -167,7 +164,7 @@ class MyCustomTool(BaseTool): ### Utilizing the `tool` Decorator ```python Code -from crewai_tools import tool +from crewai.tools import tool @tool("Name of my tool") def my_tool(question: str) -> str: """Clear description for what this tool is useful for, your agent will need this information to use it.""" @@ -178,11 +175,13 @@ def my_tool(question: str) -> str: ### Custom Caching Mechanism - Tools can optionally implement a `cache_function` to fine-tune caching behavior. This function determines when to cache results based on specific conditions, offering granular control over caching logic. + Tools can optionally implement a `cache_function` to fine-tune caching + behavior. This function determines when to cache results based on specific + conditions, offering granular control over caching logic. ```python Code -from crewai_tools import tool +from crewai.tools import tool @tool def multiplication_tool(first_number: int, second_number: int) -> str: @@ -208,6 +207,6 @@ writer1 = Agent( ## Conclusion -Tools are pivotal in extending the capabilities of CrewAI agents, enabling them to undertake a broad spectrum of tasks and collaborate effectively. -When building solutions with CrewAI, leverage both custom and existing tools to empower your agents and enhance the AI ecosystem. Consider utilizing error handling, -caching mechanisms, and the flexibility of tool arguments to optimize your agents' performance and capabilities. \ No newline at end of file +Tools are pivotal in extending the capabilities of CrewAI agents, enabling them to undertake a broad spectrum of tasks and collaborate effectively. +When building solutions with CrewAI, leverage both custom and existing tools to empower your agents and enhance the AI ecosystem. Consider utilizing error handling, +caching mechanisms, and the flexibility of tool arguments to optimize your agents' performance and capabilities. diff --git a/docs/how-to/create-custom-tools.mdx b/docs/how-to/create-custom-tools.mdx index 2caab716b..e7ee2e9a9 100644 --- a/docs/how-to/create-custom-tools.mdx +++ b/docs/how-to/create-custom-tools.mdx @@ -6,25 +6,17 @@ icon: hammer ## Creating and Utilizing Tools in CrewAI -This guide provides detailed instructions on creating custom tools for the CrewAI framework and how to efficiently manage and utilize these tools, -incorporating the latest functionalities such as tool delegation, error handling, and dynamic tool calling. It also highlights the importance of collaboration tools, +This guide provides detailed instructions on creating custom tools for the CrewAI framework and how to efficiently manage and utilize these tools, +incorporating the latest functionalities such as tool delegation, error handling, and dynamic tool calling. It also highlights the importance of collaboration tools, enabling agents to perform a wide range of actions. -### Prerequisites - -Before creating your own tools, ensure you have the crewAI extra tools package installed: - -```bash -pip install 'crewai[tools]' -``` - ### Subclassing `BaseTool` To create a personalized tool, inherit from `BaseTool` and define the necessary attributes, including the `args_schema` for input validation, and the `_run` method. ```python Code from typing import Type -from crewai_tools import BaseTool +from crewai.tools import BaseTool from pydantic import BaseModel, Field class MyToolInput(BaseModel): @@ -47,7 +39,7 @@ Alternatively, you can use the tool decorator `@tool`. This approach allows you offering a concise and efficient way to create specialized tools tailored to your needs. ```python Code -from crewai_tools import tool +from crewai.tools import tool @tool("Tool Name") def my_simple_tool(question: str) -> str: @@ -73,5 +65,5 @@ def my_cache_strategy(arguments: dict, result: str) -> bool: cached_tool.cache_function = my_cache_strategy ``` -By adhering to these guidelines and incorporating new functionalities and collaboration tools into your tool creation and management processes, +By adhering to these guidelines and incorporating new functionalities and collaboration tools into your tool creation and management processes, you can leverage the full capabilities of the CrewAI framework, enhancing both the development experience and the efficiency of your AI agents. diff --git a/src/crewai/agent.py b/src/crewai/agent.py index 937710f59..f2a0a5c31 100644 --- a/src/crewai/agent.py +++ b/src/crewai/agent.py @@ -10,7 +10,8 @@ from crewai.agents.agent_builder.base_agent import BaseAgent from crewai.agents.crew_agent_executor import CrewAgentExecutor from crewai.llm import LLM from crewai.memory.contextual.contextual_memory import ContextualMemory -from crewai.tools.agent_tools import AgentTools +from crewai.tools.agent_tools.agent_tools import AgentTools +from crewai.tools import BaseTool from crewai.utilities import Converter, Prompts from crewai.utilities.constants import TRAINED_AGENTS_DATA_FILE, TRAINING_DATA_FILE from crewai.utilities.token_counter_callback import TokenCalcHandler @@ -192,7 +193,7 @@ class Agent(BaseAgent): self, task: Any, context: Optional[str] = None, - tools: Optional[List[Any]] = None, + tools: Optional[List[BaseTool]] = None, ) -> str: """Execute a task with the agent. @@ -259,7 +260,9 @@ class Agent(BaseAgent): return result - def create_agent_executor(self, tools=None, task=None) -> None: + def create_agent_executor( + self, tools: Optional[List[BaseTool]] = None, task=None + ) -> None: """Create an agent executor for the agent. Returns: @@ -332,7 +335,7 @@ class Agent(BaseAgent): tools_list = [] try: # tentatively try to import from crewai_tools import BaseTool as CrewAITool - from crewai_tools import BaseTool as CrewAITool + from crewai.tools import BaseTool as CrewAITool for tool in tools: if isinstance(tool, CrewAITool): @@ -391,7 +394,7 @@ class Agent(BaseAgent): return description - def _render_text_description_and_args(self, tools: List[Any]) -> str: + def _render_text_description_and_args(self, tools: List[BaseTool]) -> str: """Render the tool name, description, and args in plain text. Output will be in the format of: @@ -404,17 +407,7 @@ class Agent(BaseAgent): """ tool_strings = [] for tool in tools: - args_schema = { - name: { - "description": field.description, - "type": field.annotation.__name__, - } - for name, field in tool.args_schema.model_fields.items() - } - description = ( - f"Tool Name: {tool.name}\nTool Description: {tool.description}" - ) - tool_strings.append(f"{description}\nTool Arguments: {args_schema}") + tool_strings.append(tool.description) return "\n".join(tool_strings) diff --git a/src/crewai/agents/agent_builder/base_agent.py b/src/crewai/agents/agent_builder/base_agent.py index f42ab3172..55315e7ff 100644 --- a/src/crewai/agents/agent_builder/base_agent.py +++ b/src/crewai/agents/agent_builder/base_agent.py @@ -18,6 +18,7 @@ from pydantic_core import PydanticCustomError from crewai.agents.agent_builder.utilities.base_token_process import TokenProcess from crewai.agents.cache.cache_handler import CacheHandler from crewai.agents.tools_handler import ToolsHandler +from crewai.tools import BaseTool from crewai.utilities import I18N, Logger, RPMController from crewai.utilities.config import process_config @@ -49,11 +50,11 @@ class BaseAgent(ABC, BaseModel): Methods: - execute_task(task: Any, context: Optional[str] = None, tools: Optional[List[Any]] = None) -> str: + execute_task(task: Any, context: Optional[str] = None, tools: Optional[List[BaseTool]] = None) -> str: Abstract method to execute a task. create_agent_executor(tools=None) -> None: Abstract method to create an agent executor. - _parse_tools(tools: List[Any]) -> List[Any]: + _parse_tools(tools: List[BaseTool]) -> List[Any]: Abstract method to parse tools. get_delegation_tools(agents: List["BaseAgent"]): Abstract method to set the agents task tools for handling delegation and question asking to other agents in crew. @@ -105,7 +106,7 @@ class BaseAgent(ABC, BaseModel): default=False, description="Enable agent to delegate and ask questions among each other.", ) - tools: Optional[List[Any]] = Field( + tools: Optional[List[BaseTool]] = Field( default_factory=list, description="Tools at agents' disposal" ) max_iter: Optional[int] = Field( @@ -188,7 +189,7 @@ class BaseAgent(ABC, BaseModel): self, task: Any, context: Optional[str] = None, - tools: Optional[List[Any]] = None, + tools: Optional[List[BaseTool]] = None, ) -> str: pass @@ -197,11 +198,11 @@ class BaseAgent(ABC, BaseModel): pass @abstractmethod - def _parse_tools(self, tools: List[Any]) -> List[Any]: + def _parse_tools(self, tools: List[BaseTool]) -> List[BaseTool]: pass @abstractmethod - def get_delegation_tools(self, agents: List["BaseAgent"]) -> List[Any]: + def get_delegation_tools(self, agents: List["BaseAgent"]) -> List[BaseTool]: """Set the task tools that init BaseAgenTools class.""" pass diff --git a/src/crewai/agents/tools_handler.py b/src/crewai/agents/tools_handler.py index c82f7de8c..fd4bec7ee 100644 --- a/src/crewai/agents/tools_handler.py +++ b/src/crewai/agents/tools_handler.py @@ -1,6 +1,6 @@ from typing import Any, Optional, Union -from ..tools.cache_tools import CacheTools +from ..tools.cache_tools.cache_tools import CacheTools from ..tools.tool_calling import InstructorToolCalling, ToolCalling from .cache.cache_handler import CacheHandler diff --git a/src/crewai/cli/templates/crew/tools/custom_tool.py b/src/crewai/cli/templates/crew/tools/custom_tool.py index d38318051..50bffa505 100644 --- a/src/crewai/cli/templates/crew/tools/custom_tool.py +++ b/src/crewai/cli/templates/crew/tools/custom_tool.py @@ -1,7 +1,8 @@ +from crewai.tools import BaseTool from typing import Type -from crewai_tools import BaseTool from pydantic import BaseModel, Field + class MyCustomToolInput(BaseModel): """Input schema for MyCustomTool.""" argument: str = Field(..., description="Description of the argument.") diff --git a/src/crewai/cli/templates/flow/tools/custom_tool.py b/src/crewai/cli/templates/flow/tools/custom_tool.py index 030e575ec..e669b6c3b 100644 --- a/src/crewai/cli/templates/flow/tools/custom_tool.py +++ b/src/crewai/cli/templates/flow/tools/custom_tool.py @@ -1,6 +1,6 @@ from typing import Type -from crewai_tools import BaseTool +from crewai.tools import BaseTool from pydantic import BaseModel, Field diff --git a/src/crewai/cli/templates/pipeline/tools/custom_tool.py b/src/crewai/cli/templates/pipeline/tools/custom_tool.py index d38318051..fbd53cf02 100644 --- a/src/crewai/cli/templates/pipeline/tools/custom_tool.py +++ b/src/crewai/cli/templates/pipeline/tools/custom_tool.py @@ -1,7 +1,8 @@ from typing import Type -from crewai_tools import BaseTool +from crewai.tools import BaseTool from pydantic import BaseModel, Field + class MyCustomToolInput(BaseModel): """Input schema for MyCustomTool.""" argument: str = Field(..., description="Description of the argument.") diff --git a/src/crewai/cli/templates/pipeline_router/tools/custom_tool.py b/src/crewai/cli/templates/pipeline_router/tools/custom_tool.py index d38318051..fbd53cf02 100644 --- a/src/crewai/cli/templates/pipeline_router/tools/custom_tool.py +++ b/src/crewai/cli/templates/pipeline_router/tools/custom_tool.py @@ -1,7 +1,8 @@ from typing import Type -from crewai_tools import BaseTool +from crewai.tools import BaseTool from pydantic import BaseModel, Field + class MyCustomToolInput(BaseModel): """Input schema for MyCustomTool.""" argument: str = Field(..., description="Description of the argument.") diff --git a/src/crewai/cli/templates/tool/src/{{folder_name}}/tool.py b/src/crewai/cli/templates/tool/src/{{folder_name}}/tool.py index 63c653a6c..24bc36017 100644 --- a/src/crewai/cli/templates/tool/src/{{folder_name}}/tool.py +++ b/src/crewai/cli/templates/tool/src/{{folder_name}}/tool.py @@ -1,4 +1,5 @@ -from crewai_tools import BaseTool +from crewai.tools import BaseTool + class {{class_name}}(BaseTool): name: str = "Name of my tool" diff --git a/src/crewai/crew.py b/src/crewai/crew.py index 377b6fec9..e65024ed6 100644 --- a/src/crewai/crew.py +++ b/src/crewai/crew.py @@ -32,7 +32,7 @@ from crewai.task import Task from crewai.tasks.conditional_task import ConditionalTask from crewai.tasks.task_output import TaskOutput from crewai.telemetry import Telemetry -from crewai.tools.agent_tools import AgentTools +from crewai.tools.agent_tools.agent_tools import AgentTools from crewai.types.usage_metrics import UsageMetrics from crewai.utilities import I18N, FileHandler, Logger, RPMController from crewai.utilities.constants import ( diff --git a/src/crewai/task.py b/src/crewai/task.py index 82baa9959..21278af98 100644 --- a/src/crewai/task.py +++ b/src/crewai/task.py @@ -20,6 +20,7 @@ from pydantic import ( from pydantic_core import PydanticCustomError from crewai.agents.agent_builder.base_agent import BaseAgent +from crewai.tools.base_tool import BaseTool from crewai.tasks.output_format import OutputFormat from crewai.tasks.task_output import TaskOutput from crewai.telemetry.telemetry import Telemetry @@ -91,7 +92,7 @@ class Task(BaseModel): output: Optional[TaskOutput] = Field( description="Task output, it's final result after being executed", default=None ) - tools: Optional[List[Any]] = Field( + tools: Optional[List[BaseTool]] = Field( default_factory=list, description="Tools the agent is limited to use for this task.", ) @@ -185,7 +186,7 @@ class Task(BaseModel): self, agent: Optional[BaseAgent] = None, context: Optional[str] = None, - tools: Optional[List[Any]] = None, + tools: Optional[List[BaseTool]] = None, ) -> TaskOutput: """Execute the task synchronously.""" return self._execute_core(agent, context, tools) @@ -202,7 +203,7 @@ class Task(BaseModel): self, agent: BaseAgent | None = None, context: Optional[str] = None, - tools: Optional[List[Any]] = None, + tools: Optional[List[BaseTool]] = None, ) -> Future[TaskOutput]: """Execute the task asynchronously.""" future: Future[TaskOutput] = Future() diff --git a/src/crewai/tools/__init__.py b/src/crewai/tools/__init__.py index e69de29bb..41819ccbc 100644 --- a/src/crewai/tools/__init__.py +++ b/src/crewai/tools/__init__.py @@ -0,0 +1 @@ +from .base_tool import BaseTool, tool diff --git a/src/crewai/tools/agent_tools.py b/src/crewai/tools/agent_tools.py deleted file mode 100644 index 7831285cf..000000000 --- a/src/crewai/tools/agent_tools.py +++ /dev/null @@ -1,25 +0,0 @@ -from crewai.agents.agent_builder.utilities.base_agent_tool import BaseAgentTools - - -class AgentTools(BaseAgentTools): - """Default tools around agent delegation""" - - def tools(self): - from langchain.tools import StructuredTool - - coworkers = ", ".join([f"{agent.role}" for agent in self.agents]) - tools = [ - StructuredTool.from_function( - func=self.delegate_work, - name="Delegate work to coworker", - description=self.i18n.tools("delegate_work").format( - coworkers=coworkers - ), - ), - StructuredTool.from_function( - func=self.ask_question, - name="Ask question to coworker", - description=self.i18n.tools("ask_question").format(coworkers=coworkers), - ), - ] - return tools diff --git a/src/crewai/tools/agent_tools/agent_tools.py b/src/crewai/tools/agent_tools/agent_tools.py new file mode 100644 index 000000000..1db55a29b --- /dev/null +++ b/src/crewai/tools/agent_tools/agent_tools.py @@ -0,0 +1,32 @@ +from crewai.tools.base_tool import BaseTool +from crewai.agents.agent_builder.base_agent import BaseAgent +from crewai.utilities import I18N + +from .delegate_work_tool import DelegateWorkTool +from .ask_question_tool import AskQuestionTool + + +class AgentTools: + """Manager class for agent-related tools""" + + def __init__(self, agents: list[BaseAgent], i18n: I18N = I18N()): + self.agents = agents + self.i18n = i18n + + def tools(self) -> list[BaseTool]: + """Get all available agent tools""" + coworkers = ", ".join([f"{agent.role}" for agent in self.agents]) + + delegate_tool = DelegateWorkTool( + agents=self.agents, + i18n=self.i18n, + description=self.i18n.tools("delegate_work").format(coworkers=coworkers), + ) + + ask_tool = AskQuestionTool( + agents=self.agents, + i18n=self.i18n, + description=self.i18n.tools("ask_question").format(coworkers=coworkers), + ) + + return [delegate_tool, ask_tool] diff --git a/src/crewai/tools/agent_tools/ask_question_tool.py b/src/crewai/tools/agent_tools/ask_question_tool.py new file mode 100644 index 000000000..af739b8fc --- /dev/null +++ b/src/crewai/tools/agent_tools/ask_question_tool.py @@ -0,0 +1,26 @@ +from crewai.tools.agent_tools.base_agent_tools import BaseAgentTool +from typing import Optional +from pydantic import BaseModel, Field + + +class AskQuestionToolSchema(BaseModel): + question: str = Field(..., description="The question to ask") + context: str = Field(..., description="The context for the question") + coworker: str = Field(..., description="The role/name of the coworker to ask") + + +class AskQuestionTool(BaseAgentTool): + """Tool for asking questions to coworkers""" + + name: str = "Ask question to coworker" + args_schema: type[BaseModel] = AskQuestionToolSchema + + def _run( + self, + question: str, + context: str, + coworker: Optional[str] = None, + **kwargs, + ) -> str: + coworker = self._get_coworker(coworker, **kwargs) + return self._execute(coworker, question, context) diff --git a/src/crewai/agents/agent_builder/utilities/base_agent_tool.py b/src/crewai/tools/agent_tools/base_agent_tools.py similarity index 67% rename from src/crewai/agents/agent_builder/utilities/base_agent_tool.py rename to src/crewai/tools/agent_tools/base_agent_tools.py index fef4ee9ef..bedb2aed6 100644 --- a/src/crewai/agents/agent_builder/utilities/base_agent_tool.py +++ b/src/crewai/tools/agent_tools/base_agent_tools.py @@ -1,22 +1,19 @@ -from abc import ABC, abstractmethod -from typing import List, Optional, Union - -from pydantic import BaseModel, Field +from typing import Optional, Union +from pydantic import Field +from crewai.tools.base_tool import BaseTool from crewai.agents.agent_builder.base_agent import BaseAgent from crewai.task import Task from crewai.utilities import I18N -class BaseAgentTools(BaseModel, ABC): - """Default tools around agent delegation""" +class BaseAgentTool(BaseTool): + """Base class for agent-related tools""" - agents: List[BaseAgent] = Field(description="List of agents in this crew.") - i18n: I18N = Field(default=I18N(), description="Internationalization settings.") - - @abstractmethod - def tools(self): - pass + agents: list[BaseAgent] = Field(description="List of available agents") + i18n: I18N = Field( + default_factory=I18N, description="Internationalization settings" + ) def _get_coworker(self, coworker: Optional[str], **kwargs) -> Optional[str]: coworker = coworker or kwargs.get("co_worker") or kwargs.get("coworker") @@ -24,27 +21,11 @@ class BaseAgentTools(BaseModel, ABC): is_list = coworker.startswith("[") and coworker.endswith("]") if is_list: coworker = coworker[1:-1].split(",")[0] - return coworker - def delegate_work( - self, task: str, context: str, coworker: Optional[str] = None, **kwargs - ): - """Useful to delegate a specific task to a coworker passing all necessary context and names.""" - coworker = self._get_coworker(coworker, **kwargs) - return self._execute(coworker, task, context) - - def ask_question( - self, question: str, context: str, coworker: Optional[str] = None, **kwargs - ): - """Useful to ask a question, opinion or take from a coworker passing all necessary context and names.""" - coworker = self._get_coworker(coworker, **kwargs) - return self._execute(coworker, question, context) - def _execute( self, agent_name: Union[str, None], task: str, context: Union[str, None] - ): - """Execute the command.""" + ) -> str: try: if agent_name is None: agent_name = "" @@ -57,7 +38,6 @@ class BaseAgentTools(BaseModel, ABC): # when it should look like this: # {"task": "....", "coworker": "...."} agent_name = agent_name.casefold().replace('"', "").replace("\n", "") - agent = [ # type: ignore # Incompatible types in assignment (expression has type "list[BaseAgent]", variable has type "str | None") available_agent for available_agent in self.agents diff --git a/src/crewai/tools/agent_tools/delegate_work_tool.py b/src/crewai/tools/agent_tools/delegate_work_tool.py new file mode 100644 index 000000000..7f2007597 --- /dev/null +++ b/src/crewai/tools/agent_tools/delegate_work_tool.py @@ -0,0 +1,29 @@ +from crewai.tools.agent_tools.base_agent_tools import BaseAgentTool +from typing import Optional + +from pydantic import BaseModel, Field + + +class DelegateWorkToolSchema(BaseModel): + task: str = Field(..., description="The task to delegate") + context: str = Field(..., description="The context for the task") + coworker: str = Field( + ..., description="The role/name of the coworker to delegate to" + ) + + +class DelegateWorkTool(BaseAgentTool): + """Tool for delegating work to coworkers""" + + name: str = "Delegate work to coworker" + args_schema: type[BaseModel] = DelegateWorkToolSchema + + def _run( + self, + task: str, + context: str, + coworker: Optional[str] = None, + **kwargs, + ) -> str: + coworker = self._get_coworker(coworker, **kwargs) + return self._execute(coworker, task, context) diff --git a/src/crewai/tools/base_tool.py b/src/crewai/tools/base_tool.py new file mode 100644 index 000000000..f41fb7c0b --- /dev/null +++ b/src/crewai/tools/base_tool.py @@ -0,0 +1,186 @@ +from abc import ABC, abstractmethod +from typing import Any, Callable, Type, get_args, get_origin + +from langchain_core.tools import StructuredTool +from pydantic import BaseModel, ConfigDict, Field, validator +from pydantic import BaseModel as PydanticBaseModel + + +class BaseTool(BaseModel, ABC): + class _ArgsSchemaPlaceholder(PydanticBaseModel): + pass + + model_config = ConfigDict() + + name: str + """The unique name of the tool that clearly communicates its purpose.""" + description: str + """Used to tell the model how/when/why to use the tool.""" + args_schema: Type[PydanticBaseModel] = Field(default_factory=_ArgsSchemaPlaceholder) + """The schema for the arguments that the tool accepts.""" + description_updated: bool = False + """Flag to check if the description has been updated.""" + cache_function: Callable = lambda _args=None, _result=None: True + """Function that will be used to determine if the tool should be cached, should return a boolean. If None, the tool will be cached.""" + result_as_answer: bool = False + """Flag to check if the tool should be the final agent answer.""" + + @validator("args_schema", always=True, pre=True) + def _default_args_schema( + cls, v: Type[PydanticBaseModel] + ) -> Type[PydanticBaseModel]: + if not isinstance(v, cls._ArgsSchemaPlaceholder): + return v + + return type( + f"{cls.__name__}Schema", + (PydanticBaseModel,), + { + "__annotations__": { + k: v for k, v in cls._run.__annotations__.items() if k != "return" + }, + }, + ) + + def model_post_init(self, __context: Any) -> None: + self._generate_description() + + super().model_post_init(__context) + + def run( + self, + *args: Any, + **kwargs: Any, + ) -> Any: + print(f"Using Tool: {self.name}") + return self._run(*args, **kwargs) + + @abstractmethod + def _run( + self, + *args: Any, + **kwargs: Any, + ) -> Any: + """Here goes the actual implementation of the tool.""" + + def to_langchain(self) -> StructuredTool: + self._set_args_schema() + return StructuredTool( + name=self.name, + description=self.description, + args_schema=self.args_schema, + func=self._run, + ) + + @classmethod + def from_langchain(cls, tool: StructuredTool) -> "BaseTool": + if cls == Tool: + if tool.func is None: + raise ValueError("StructuredTool must have a callable 'func'") + return Tool( + name=tool.name, + description=tool.description, + args_schema=tool.args_schema, + func=tool.func, + ) + raise NotImplementedError(f"from_langchain not implemented for {cls.__name__}") + + def _set_args_schema(self): + if self.args_schema is None: + class_name = f"{self.__class__.__name__}Schema" + self.args_schema = type( + class_name, + (PydanticBaseModel,), + { + "__annotations__": { + k: v + for k, v in self._run.__annotations__.items() + if k != "return" + }, + }, + ) + + def _generate_description(self): + args_schema = { + name: { + "description": field.description, + "type": BaseTool._get_arg_annotations(field.annotation), + } + for name, field in self.args_schema.model_fields.items() + } + + self.description = f"Tool Name: {self.name}\nTool Arguments: {args_schema}\nTool Description: {self.description}" + + @staticmethod + def _get_arg_annotations(annotation: type[Any] | None) -> str: + if annotation is None: + return "None" + + origin = get_origin(annotation) + args = get_args(annotation) + + if origin is None: + return ( + annotation.__name__ + if hasattr(annotation, "__name__") + else str(annotation) + ) + + if args: + args_str = ", ".join(BaseTool._get_arg_annotations(arg) for arg in args) + return f"{origin.__name__}[{args_str}]" + + return origin.__name__ + + +class Tool(BaseTool): + func: Callable + """The function that will be executed when the tool is called.""" + + def _run(self, *args: Any, **kwargs: Any) -> Any: + return self.func(*args, **kwargs) + + +def to_langchain( + tools: list[BaseTool | StructuredTool], +) -> list[StructuredTool]: + return [t.to_langchain() if isinstance(t, BaseTool) else t for t in tools] + + +def tool(*args): + """ + Decorator to create a tool from a function. + """ + + def _make_with_name(tool_name: str) -> Callable: + def _make_tool(f: Callable) -> BaseTool: + if f.__doc__ is None: + raise ValueError("Function must have a docstring") + if f.__annotations__ is None: + raise ValueError("Function must have type annotations") + + class_name = "".join(tool_name.split()).title() + args_schema = type( + class_name, + (PydanticBaseModel,), + { + "__annotations__": { + k: v for k, v in f.__annotations__.items() if k != "return" + }, + }, + ) + + return Tool( + name=tool_name, + description=f.__doc__, + func=f, + args_schema=args_schema, + ) + + return _make_tool + + if len(args) == 1 and callable(args[0]): + return _make_with_name(args[0].__name__)(args[0]) + if len(args) == 1 and isinstance(args[0], str): + return _make_with_name(args[0]) + raise ValueError("Invalid arguments") diff --git a/src/crewai/tools/cache_tools.py b/src/crewai/tools/cache_tools/cache_tools.py similarity index 100% rename from src/crewai/tools/cache_tools.py rename to src/crewai/tools/cache_tools/cache_tools.py diff --git a/src/crewai/tools/tool_usage.py b/src/crewai/tools/tool_usage.py index 71c02fc3c..e4108c8ba 100644 --- a/src/crewai/tools/tool_usage.py +++ b/src/crewai/tools/tool_usage.py @@ -10,6 +10,7 @@ import crewai.utilities.events as events from crewai.agents.tools_handler import ToolsHandler from crewai.task import Task from crewai.telemetry import Telemetry +from crewai.tools import BaseTool from crewai.tools.tool_calling import InstructorToolCalling, ToolCalling from crewai.tools.tool_usage_events import ToolUsageError, ToolUsageFinished from crewai.utilities import I18N, Converter, ConverterError, Printer @@ -49,7 +50,7 @@ class ToolUsage: def __init__( self, tools_handler: ToolsHandler, - tools: List[Any], + tools: List[BaseTool], original_tools: List[Any], tools_description: str, tools_names: str, @@ -298,22 +299,7 @@ class ToolUsage: """Render the tool name and description in plain text.""" descriptions = [] for tool in self.tools: - args = { - name: { - "description": field.description, - "type": field.annotation.__name__, - } - for name, field in tool.args_schema.model_fields.items() - } - descriptions.append( - "\n".join( - [ - f"Tool Name: {tool.name.lower()}", - f"Tool Description: {tool.description}", - f"Tool Arguments: {args}", - ] - ) - ) + descriptions.append(tool.description) return "\n--\n".join(descriptions) def _function_calling(self, tool_string: str): diff --git a/tests/agent_test.py b/tests/agent_test.py index 154677ccf..c4094d15c 100644 --- a/tests/agent_test.py +++ b/tests/agent_test.py @@ -5,7 +5,6 @@ from unittest import mock from unittest.mock import patch import pytest -from crewai_tools import tool from crewai import Agent, Crew, Task from crewai.agents.cache import CacheHandler @@ -14,6 +13,7 @@ from crewai.agents.parser import AgentAction, CrewAgentParser, OutputParserExcep from crewai.llm import LLM from crewai.tools.tool_calling import InstructorToolCalling from crewai.tools.tool_usage import ToolUsage +from crewai.tools import tool from crewai.tools.tool_usage_events import ToolUsageFinished from crewai.utilities import RPMController from crewai.utilities.events import Emitter @@ -277,9 +277,10 @@ def test_cache_hitting(): "multiplier-{'first_number': 12, 'second_number': 3}": 36, } - with patch.object(CacheHandler, "read") as read, patch.object( - Emitter, "emit" - ) as emit: + with ( + patch.object(CacheHandler, "read") as read, + patch.object(Emitter, "emit") as emit, + ): 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.", @@ -604,7 +605,7 @@ def test_agent_respect_the_max_rpm_set(capsys): def test_agent_respect_the_max_rpm_set_over_crew_rpm(capsys): from unittest.mock import patch - from crewai_tools import tool + from crewai.tools import tool @tool def get_final_answer() -> float: @@ -642,7 +643,7 @@ def test_agent_respect_the_max_rpm_set_over_crew_rpm(capsys): def test_agent_without_max_rpm_respet_crew_rpm(capsys): from unittest.mock import patch - from crewai_tools import tool + from crewai.tools import tool @tool def get_final_answer() -> float: @@ -696,7 +697,7 @@ def test_agent_without_max_rpm_respet_crew_rpm(capsys): def test_agent_error_on_parsing_tool(capsys): from unittest.mock import patch - from crewai_tools import tool + from crewai.tools import tool @tool def get_final_answer() -> float: @@ -739,7 +740,7 @@ def test_agent_error_on_parsing_tool(capsys): def test_agent_remembers_output_format_after_using_tools_too_many_times(): from unittest.mock import patch - from crewai_tools import tool + from crewai.tools import tool @tool def get_final_answer() -> float: @@ -863,11 +864,16 @@ def test_agent_function_calling_llm(): from crewai.tools.tool_usage import ToolUsage - with patch.object( - instructor, "from_litellm", wraps=instructor.from_litellm - ) as mock_from_litellm, patch.object( - ToolUsage, "_original_tool_calling", side_effect=Exception("Forced exception") - ) as mock_original_tool_calling: + with ( + patch.object( + instructor, "from_litellm", wraps=instructor.from_litellm + ) as mock_from_litellm, + patch.object( + ToolUsage, + "_original_tool_calling", + side_effect=Exception("Forced exception"), + ) as mock_original_tool_calling, + ): crew.kickoff() mock_from_litellm.assert_called() mock_original_tool_calling.assert_called() @@ -894,7 +900,7 @@ def test_agent_count_formatting_error(): @pytest.mark.vcr(filter_headers=["authorization"]) def test_tool_result_as_answer_is_the_final_answer_for_the_agent(): - from crewai_tools import BaseTool + from crewai.tools import BaseTool class MyCustomTool(BaseTool): name: str = "Get Greetings" @@ -924,7 +930,7 @@ def test_tool_result_as_answer_is_the_final_answer_for_the_agent(): @pytest.mark.vcr(filter_headers=["authorization"]) def test_tool_usage_information_is_appended_to_agent(): - from crewai_tools import BaseTool + from crewai.tools import BaseTool class MyCustomTool(BaseTool): name: str = "Decide Greetings" diff --git a/tests/agent_tools/lol.py b/tests/agent_tools/lol.py deleted file mode 100644 index e69de29bb..000000000 diff --git a/tests/agents/agent_builder/base_agent_test.py b/tests/agents/agent_builder/base_agent_test.py index 4e47f2271..fcd5d58b6 100644 --- a/tests/agents/agent_builder/base_agent_test.py +++ b/tests/agents/agent_builder/base_agent_test.py @@ -2,6 +2,7 @@ import hashlib from typing import Any, List, Optional from crewai.agents.agent_builder.base_agent import BaseAgent +from crewai.tools.base_tool import BaseTool from pydantic import BaseModel @@ -10,13 +11,13 @@ class TestAgent(BaseAgent): self, task: Any, context: Optional[str] = None, - tools: Optional[List[Any]] = None, + tools: Optional[List[BaseTool]] = None, ) -> str: return "" def create_agent_executor(self, tools=None) -> None: ... - def _parse_tools(self, tools: List[Any]) -> List[Any]: + def _parse_tools(self, tools: List[BaseTool]) -> List[BaseTool]: return [] def get_delegation_tools(self, agents: List["BaseAgent"]): ... diff --git a/tests/crew_test.py b/tests/crew_test.py index 6444b781c..24b892271 100644 --- a/tests/crew_test.py +++ b/tests/crew_test.py @@ -456,7 +456,7 @@ def test_crew_verbose_output(capsys): def test_cache_hitting_between_agents(): from unittest.mock import call, patch - from crewai_tools import tool + from crewai.tools import tool @tool def multiplier(first_number: int, second_number: int) -> float: @@ -499,7 +499,7 @@ def test_cache_hitting_between_agents(): def test_api_calls_throttling(capsys): from unittest.mock import patch - from crewai_tools import tool + from crewai.tools import tool @tool def get_final_answer() -> float: @@ -1111,7 +1111,7 @@ def test_dont_set_agents_step_callback_if_already_set(): def test_crew_function_calling_llm(): from unittest.mock import patch - from crewai_tools import tool + from crewai.tools import tool llm = "gpt-4o" @@ -1146,7 +1146,7 @@ def test_crew_function_calling_llm(): @pytest.mark.vcr(filter_headers=["authorization"]) def test_task_with_no_arguments(): - from crewai_tools import tool + from crewai.tools import tool @tool def return_data() -> str: @@ -1309,8 +1309,9 @@ def test_hierarchical_crew_creation_tasks_with_agents(): assert crew.manager_agent is not None assert crew.manager_agent.tools is not None - assert crew.manager_agent.tools[0].description.startswith( - "Delegate a specific task to one of the following coworkers: Senior Writer" + assert ( + "Delegate a specific task to one of the following coworkers: Senior Writer\n" + in crew.manager_agent.tools[0].description ) @@ -1337,8 +1338,9 @@ def test_hierarchical_crew_creation_tasks_with_async_execution(): crew.kickoff() assert crew.manager_agent is not None assert crew.manager_agent.tools is not None - assert crew.manager_agent.tools[0].description.startswith( + assert ( "Delegate a specific task to one of the following coworkers: Senior Writer\n" + in crew.manager_agent.tools[0].description ) @@ -1370,8 +1372,9 @@ def test_hierarchical_crew_creation_tasks_with_sync_last(): crew.kickoff() assert crew.manager_agent is not None assert crew.manager_agent.tools is not None - assert crew.manager_agent.tools[0].description.startswith( + assert ( "Delegate a specific task to one of the following coworkers: Senior Writer, Researcher, CEO\n" + in crew.manager_agent.tools[0].description ) @@ -1494,7 +1497,7 @@ def test_task_callback_on_crew(): def test_tools_with_custom_caching(): from unittest.mock import patch - from crewai_tools import tool + from crewai.tools import tool @tool def multiplcation_tool(first_number: int, second_number: int) -> int: @@ -1696,7 +1699,7 @@ def test_manager_agent_in_agents_raises_exception(): def test_manager_agent_with_tools_raises_exception(): - from crewai_tools import tool + from crewai.tools import tool @tool def testing_tool(first_number: int, second_number: int) -> int: diff --git a/tests/task_test.py b/tests/task_test.py index 1e20c9491..3b3de0bed 100644 --- a/tests/task_test.py +++ b/tests/task_test.py @@ -15,7 +15,7 @@ from pydantic_core import ValidationError def test_task_tool_reflect_agent_tools(): - from crewai_tools import tool + from crewai.tools import tool @tool def fake_tool() -> None: @@ -39,7 +39,7 @@ def test_task_tool_reflect_agent_tools(): def test_task_tool_takes_precedence_over_agent_tools(): - from crewai_tools import tool + from crewai.tools import tool @tool def fake_tool() -> None: @@ -656,7 +656,7 @@ def test_increment_delegations_for_sequential_process(): @pytest.mark.vcr(filter_headers=["authorization"]) def test_increment_tool_errors(): - from crewai_tools import tool + from crewai.tools import tool @tool def scoring_examples() -> None: diff --git a/tests/agent_tools/__init__.py b/tests/tools/agent_tools/__init__.py similarity index 100% rename from tests/agent_tools/__init__.py rename to tests/tools/agent_tools/__init__.py diff --git a/tests/agent_tools/agent_tools_test.py b/tests/tools/agent_tools/agent_tools_test.py similarity index 95% rename from tests/agent_tools/agent_tools_test.py rename to tests/tools/agent_tools/agent_tools_test.py index 8d9345b46..9aea7b4bc 100644 --- a/tests/agent_tools/agent_tools_test.py +++ b/tests/tools/agent_tools/agent_tools_test.py @@ -3,7 +3,7 @@ import pytest from crewai.agent import Agent -from crewai.tools.agent_tools import AgentTools +from crewai.tools.agent_tools.agent_tools import AgentTools researcher = Agent( role="researcher", @@ -11,12 +11,14 @@ researcher = Agent( backstory="You're an expert researcher, specialized in technology", allow_delegation=False, ) -tools = AgentTools(agents=[researcher]) +tools = AgentTools(agents=[researcher]).tools() +delegate_tool = tools[0] +ask_tool = tools[1] @pytest.mark.vcr(filter_headers=["authorization"]) def test_delegate_work(): - result = tools.delegate_work( + result = delegate_tool.run( coworker="researcher", task="share your take on AI Agents", context="I heard you hate them", @@ -30,8 +32,8 @@ def test_delegate_work(): @pytest.mark.vcr(filter_headers=["authorization"]) def test_delegate_work_with_wrong_co_worker_variable(): - result = tools.delegate_work( - co_worker="researcher", + result = delegate_tool.run( + coworker="researcher", task="share your take on AI Agents", context="I heard you hate them", ) @@ -44,7 +46,7 @@ def test_delegate_work_with_wrong_co_worker_variable(): @pytest.mark.vcr(filter_headers=["authorization"]) def test_ask_question(): - result = tools.ask_question( + result = ask_tool.run( coworker="researcher", question="do you hate AI Agents?", context="I heard you LOVE them", @@ -58,8 +60,8 @@ def test_ask_question(): @pytest.mark.vcr(filter_headers=["authorization"]) def test_ask_question_with_wrong_co_worker_variable(): - result = tools.ask_question( - co_worker="researcher", + result = ask_tool.run( + coworker="researcher", question="do you hate AI Agents?", context="I heard you LOVE them", ) @@ -72,8 +74,8 @@ def test_ask_question_with_wrong_co_worker_variable(): @pytest.mark.vcr(filter_headers=["authorization"]) def test_delegate_work_withwith_coworker_as_array(): - result = tools.delegate_work( - co_worker="[researcher]", + result = delegate_tool.run( + coworker="[researcher]", task="share your take on AI Agents", context="I heard you hate them", ) @@ -86,8 +88,8 @@ def test_delegate_work_withwith_coworker_as_array(): @pytest.mark.vcr(filter_headers=["authorization"]) def test_ask_question_with_coworker_as_array(): - result = tools.ask_question( - co_worker="[researcher]", + result = ask_tool.run( + coworker="[researcher]", question="do you hate AI Agents?", context="I heard you LOVE them", ) @@ -99,7 +101,7 @@ def test_ask_question_with_coworker_as_array(): def test_delegate_work_to_wrong_agent(): - result = tools.ask_question( + result = ask_tool.run( coworker="writer", question="share your take on AI Agents", context="I heard you hate them", @@ -112,7 +114,7 @@ def test_delegate_work_to_wrong_agent(): def test_ask_question_to_wrong_agent(): - result = tools.ask_question( + result = ask_tool.run( coworker="writer", question="do you hate AI Agents?", context="I heard you LOVE them", diff --git a/tests/agent_tools/cassettes/test_ask_question.yaml b/tests/tools/agent_tools/cassettes/test_ask_question.yaml similarity index 100% rename from tests/agent_tools/cassettes/test_ask_question.yaml rename to tests/tools/agent_tools/cassettes/test_ask_question.yaml diff --git a/tests/agent_tools/cassettes/test_ask_question_with_coworker_as_array.yaml b/tests/tools/agent_tools/cassettes/test_ask_question_with_coworker_as_array.yaml similarity index 100% rename from tests/agent_tools/cassettes/test_ask_question_with_coworker_as_array.yaml rename to tests/tools/agent_tools/cassettes/test_ask_question_with_coworker_as_array.yaml diff --git a/tests/agent_tools/cassettes/test_ask_question_with_wrong_co_worker_variable.yaml b/tests/tools/agent_tools/cassettes/test_ask_question_with_wrong_co_worker_variable.yaml similarity index 100% rename from tests/agent_tools/cassettes/test_ask_question_with_wrong_co_worker_variable.yaml rename to tests/tools/agent_tools/cassettes/test_ask_question_with_wrong_co_worker_variable.yaml diff --git a/tests/agent_tools/cassettes/test_delegate_work.yaml b/tests/tools/agent_tools/cassettes/test_delegate_work.yaml similarity index 100% rename from tests/agent_tools/cassettes/test_delegate_work.yaml rename to tests/tools/agent_tools/cassettes/test_delegate_work.yaml diff --git a/tests/agent_tools/cassettes/test_delegate_work_with_wrong_co_worker_variable.yaml b/tests/tools/agent_tools/cassettes/test_delegate_work_with_wrong_co_worker_variable.yaml similarity index 100% rename from tests/agent_tools/cassettes/test_delegate_work_with_wrong_co_worker_variable.yaml rename to tests/tools/agent_tools/cassettes/test_delegate_work_with_wrong_co_worker_variable.yaml diff --git a/tests/agent_tools/cassettes/test_delegate_work_withwith_coworker_as_array.yaml b/tests/tools/agent_tools/cassettes/test_delegate_work_withwith_coworker_as_array.yaml similarity index 100% rename from tests/agent_tools/cassettes/test_delegate_work_withwith_coworker_as_array.yaml rename to tests/tools/agent_tools/cassettes/test_delegate_work_withwith_coworker_as_array.yaml diff --git a/tests/tools/test_base_tool.py b/tests/tools/test_base_tool.py new file mode 100644 index 000000000..eca36739c --- /dev/null +++ b/tests/tools/test_base_tool.py @@ -0,0 +1,109 @@ +from typing import Callable +from crewai.tools import BaseTool, tool + + +def test_creating_a_tool_using_annotation(): + @tool("Name of my tool") + def my_tool(question: str) -> str: + """Clear description for what this tool is useful for, you agent will need this information to use it.""" + return question + + # Assert all the right attributes were defined + assert my_tool.name == "Name of my tool" + assert ( + my_tool.description + == "Tool Name: Name of my tool\nTool Arguments: {'question': {'description': None, 'type': 'str'}}\nTool Description: Clear description for what this tool is useful for, you agent will need this information to use it." + ) + assert my_tool.args_schema.schema()["properties"] == { + "question": {"title": "Question", "type": "string"} + } + assert ( + my_tool.func("What is the meaning of life?") == "What is the meaning of life?" + ) + + # Assert the langchain tool conversion worked as expected + converted_tool = my_tool.to_langchain() + assert converted_tool.name == "Name of my tool" + + assert ( + converted_tool.description + == "Tool Name: Name of my tool\nTool Arguments: {'question': {'description': None, 'type': 'str'}}\nTool Description: Clear description for what this tool is useful for, you agent will need this information to use it." + ) + assert converted_tool.args_schema.schema()["properties"] == { + "question": {"title": "Question", "type": "string"} + } + assert ( + converted_tool.func("What is the meaning of life?") + == "What is the meaning of life?" + ) + + +def test_creating_a_tool_using_baseclass(): + class MyCustomTool(BaseTool): + name: str = "Name of my tool" + description: str = ( + "Clear description for what this tool is useful for, you agent will need this information to use it." + ) + + def _run(self, question: str) -> str: + return question + + my_tool = MyCustomTool() + # Assert all the right attributes were defined + assert my_tool.name == "Name of my tool" + + assert ( + my_tool.description + == "Tool Name: Name of my tool\nTool Arguments: {'question': {'description': None, 'type': 'str'}}\nTool Description: Clear description for what this tool is useful for, you agent will need this information to use it." + ) + assert my_tool.args_schema.schema()["properties"] == { + "question": {"title": "Question", "type": "string"} + } + assert my_tool.run("What is the meaning of life?") == "What is the meaning of life?" + + # Assert the langchain tool conversion worked as expected + converted_tool = my_tool.to_langchain() + assert converted_tool.name == "Name of my tool" + + assert ( + converted_tool.description + == "Tool Name: Name of my tool\nTool Arguments: {'question': {'description': None, 'type': 'str'}}\nTool Description: Clear description for what this tool is useful for, you agent will need this information to use it." + ) + assert converted_tool.args_schema.schema()["properties"] == { + "question": {"title": "Question", "type": "string"} + } + assert ( + converted_tool.run("What is the meaning of life?") + == "What is the meaning of life?" + ) + + +def test_setting_cache_function(): + class MyCustomTool(BaseTool): + name: str = "Name of my tool" + description: str = ( + "Clear description for what this tool is useful for, you agent will need this information to use it." + ) + cache_function: Callable = lambda: False + + def _run(self, question: str) -> str: + return question + + my_tool = MyCustomTool() + # Assert all the right attributes were defined + assert not my_tool.cache_function() + + +def test_default_cache_function_is_true(): + class MyCustomTool(BaseTool): + name: str = "Name of my tool" + description: str = ( + "Clear description for what this tool is useful for, you agent will need this information to use it." + ) + + def _run(self, question: str) -> str: + return question + + my_tool = MyCustomTool() + # Assert all the right attributes were defined + assert my_tool.cache_function() diff --git a/tests/tools/test_tool_usage.py b/tests/tools/test_tool_usage.py index 8af9b8abb..fbe93a628 100644 --- a/tests/tools/test_tool_usage.py +++ b/tests/tools/test_tool_usage.py @@ -3,11 +3,11 @@ import random from unittest.mock import MagicMock import pytest -from crewai_tools import BaseTool from pydantic import BaseModel, Field from crewai import Agent, Task from crewai.tools.tool_usage import ToolUsage +from crewai.tools import BaseTool class RandomNumberToolInput(BaseModel): @@ -103,11 +103,7 @@ def test_tool_usage_render(): rendered = tool_usage._render() # Updated checks to match the actual output - assert "Tool Name: random number generator" in rendered - assert ( - "Random Number Generator(min_value: 'integer', max_value: 'integer') - Generates a random number within a specified range min_value: 'The minimum value of the range (inclusive)', max_value: 'The maximum value of the range (inclusive)'" - in rendered - ) + assert "Tool Name: Random Number Generator" in rendered assert "Tool Arguments:" in rendered assert ( "'min_value': {'description': 'The minimum value of the range (inclusive)', 'type': 'int'}" @@ -117,3 +113,11 @@ def test_tool_usage_render(): "'max_value': {'description': 'The maximum value of the range (inclusive)', 'type': 'int'}" in rendered ) + assert ( + "Tool Description: Generates a random number within a specified range" + in rendered + ) + assert ( + "Tool Name: Random Number Generator\nTool Arguments: {'min_value': {'description': 'The minimum value of the range (inclusive)', 'type': 'int'}, 'max_value': {'description': 'The maximum value of the range (inclusive)', 'type': 'int'}}\nTool Description: Generates a random number within a specified range" + in rendered + ) From 34954e6f74525c6f28d24124524aa8984059322b Mon Sep 17 00:00:00 2001 From: Tony Kipkemboi Date: Fri, 1 Nov 2024 15:58:36 -0400 Subject: [PATCH 037/126] Update docs (#1550) * add llm providers accordion group * fix numbering * Fix directory tree & add llms to accordion --- docs/concepts/flows.mdx | 26 ++++++++++++++------------ docs/concepts/llms.mdx | 26 +++++++++++++++++--------- 2 files changed, 31 insertions(+), 21 deletions(-) diff --git a/docs/concepts/flows.mdx b/docs/concepts/flows.mdx index c86776982..e3f0e6b45 100644 --- a/docs/concepts/flows.mdx +++ b/docs/concepts/flows.mdx @@ -577,18 +577,20 @@ This command will create a new directory for your crew within the `crews` folder After adding a new crew, your folder structure will look like this: -name_of_flow/ -├── crews/ -│ ├── poem_crew/ -│ │ ├── config/ -│ │ │ ├── agents.yaml -│ │ │ └── tasks.yaml -│ │ └── poem_crew.py -│ └── name_of_crew/ -│ ├── config/ -│ │ ├── agents.yaml -│ │ └── tasks.yaml -│ └── name_of_crew.py +| Directory/File | Description | +| :--------------------- | :----------------------------------------------------------------- | +| `name_of_flow/` | Root directory for the flow. | +| ├── `crews/` | Contains directories for specific crews. | +| │ ├── `poem_crew/` | Directory for the "poem_crew" with its configurations and scripts. | +| │ │ ├── `config/` | Configuration files directory for the "poem_crew". | +| │ │ │ ├── `agents.yaml` | YAML file defining the agents for "poem_crew". | +| │ │ │ └── `tasks.yaml` | YAML file defining the tasks for "poem_crew". | +| │ │ └── `poem_crew.py` | Script for "poem_crew" functionality. | +| └── `name_of_crew/` | Directory for the new crew. | +| ├── `config/` | Configuration files directory for the new crew. | +| │ ├── `agents.yaml` | YAML file defining the agents for the new crew. | +| │ └── `tasks.yaml` | YAML file defining the tasks for the new crew. | +| └── `name_of_crew.py` | Script for the new crew functionality. | You can then customize the `agents.yaml` and `tasks.yaml` files to define the agents and tasks for your new crew. The `name_of_crew.py` file will contain the crew's logic, which you can modify to suit your needs. diff --git a/docs/concepts/llms.mdx b/docs/concepts/llms.mdx index 835c2491f..d432070d4 100644 --- a/docs/concepts/llms.mdx +++ b/docs/concepts/llms.mdx @@ -131,7 +131,6 @@ These are examples of how to configure LLMs for your agent. llm = LLM( model="cerebras/llama-3.1-70b", - base_url="https://api.cerebras.ai/v1", api_key="your-api-key-here" ) agent = Agent(llm=llm, ...) @@ -166,7 +165,6 @@ These are examples of how to configure LLMs for your agent. llm = LLM( model="groq/llama3-8b-8192", - base_url="https://api.groq.com/openai/v1", api_key="your-api-key-here" ) agent = Agent(llm=llm, ...) @@ -180,21 +178,18 @@ These are examples of how to configure LLMs for your agent. llm = LLM( model="anthropic/claude-3-5-sonnet-20241022", - base_url="https://api.anthropic.com/v1", api_key="your-api-key-here" ) agent = Agent(llm=llm, ...) ``` - - + ```python Code from crewai import LLM llm = LLM( - model="fireworks/meta-llama-3.1-8b-instruct", - base_url="https://api.fireworks.ai/inference/v1", + model="fireworks_ai/accounts/fireworks/models/llama-v3-70b-instruct", api_key="your-api-key-here" ) agent = Agent(llm=llm, ...) @@ -207,8 +202,7 @@ These are examples of how to configure LLMs for your agent. from crewai import LLM llm = LLM( - model="gemini/gemini-1.5-flash", - base_url="https://api.gemini.google.com/v1", + model="gemini/gemini-1.5-pro-002", api_key="your-api-key-here" ) agent = Agent(llm=llm, ...) @@ -242,6 +236,20 @@ These are examples of how to configure LLMs for your agent. agent = Agent(llm=llm, ...) ``` + + + + ```python Code + from crewai import LLM + + llm = LLM( + model="huggingface/meta-llama/Meta-Llama-3.1-8B-Instruct", + api_key="your-api-key-here", + base_url="your_api_endpoint" + ) + agent = Agent(llm=llm, ...) + ``` + ## Changing the Base API URL From 3878daffd68d14ee0379d11c19bef63c4dc02edc Mon Sep 17 00:00:00 2001 From: "Brandon Hancock (bhancock_ai)" <109994880+bhancockio@users.noreply.github.com> Date: Fri, 1 Nov 2024 16:42:46 -0400 Subject: [PATCH 038/126] Feat/ibm memory (#1549) * Everything looks like its working. Waiting for lorenze review. * Update docs as well. * clean up for PR --- docs/concepts/memory.mdx | 25 ++++++++ .../memory/contextual/contextual_memory.py | 4 ++ src/crewai/memory/storage/rag_storage.py | 62 ++++++++++++++++--- 3 files changed, 81 insertions(+), 10 deletions(-) diff --git a/docs/concepts/memory.mdx b/docs/concepts/memory.mdx index 735ed861e..bda9f3401 100644 --- a/docs/concepts/memory.mdx +++ b/docs/concepts/memory.mdx @@ -254,6 +254,31 @@ my_crew = Crew( ) ``` +### Using Watson embeddings + +```python Code +from crewai import Crew, Agent, Task, Process + +# Note: Ensure you have installed and imported `ibm_watsonx_ai` for Watson embeddings to work. + +my_crew = Crew( + agents=[...], + tasks=[...], + process=Process.sequential, + memory=True, + verbose=True, + embedder={ + "provider": "watson", + "config": { + "model": "", + "api_url": "", + "api_key": "", + "project_id": "", + } + } +) +``` + ### Resetting Memory ```shell diff --git a/src/crewai/memory/contextual/contextual_memory.py b/src/crewai/memory/contextual/contextual_memory.py index 5d91cf47d..3d3a9c6c1 100644 --- a/src/crewai/memory/contextual/contextual_memory.py +++ b/src/crewai/memory/contextual/contextual_memory.py @@ -34,6 +34,7 @@ class ContextualMemory: formatted_results = "\n".join( [f"- {result['context']}" for result in stm_results] ) + print("formatted_results stm", formatted_results) return f"Recent Insights:\n{formatted_results}" if stm_results else "" def _fetch_ltm_context(self, task) -> Optional[str]: @@ -53,6 +54,8 @@ class ContextualMemory: formatted_results = list(dict.fromkeys(formatted_results)) formatted_results = "\n".join([f"- {result}" for result in formatted_results]) # type: ignore # Incompatible types in assignment (expression has type "str", variable has type "list[str]") + print("formatted_results ltm", formatted_results) + return f"Historical Data:\n{formatted_results}" if ltm_results else "" def _fetch_entity_context(self, query) -> str: @@ -64,4 +67,5 @@ class ContextualMemory: formatted_results = "\n".join( [f"- {result['context']}" for result in em_results] # type: ignore # Invalid index type "str" for "str"; expected type "SupportsIndex | slice" ) + print("formatted_results em", formatted_results) return f"Entities:\n{formatted_results}" if em_results else "" diff --git a/src/crewai/memory/storage/rag_storage.py b/src/crewai/memory/storage/rag_storage.py index db98c0036..d0f1cfc64 100644 --- a/src/crewai/memory/storage/rag_storage.py +++ b/src/crewai/memory/storage/rag_storage.py @@ -4,13 +4,13 @@ import logging import os import shutil import uuid -from typing import Any, Dict, List, Optional -from crewai.memory.storage.base_rag_storage import BaseRAGStorage -from crewai.utilities.paths import db_storage_path +from typing import Any, Dict, List, Optional, cast + +from chromadb import Documents, EmbeddingFunction, Embeddings from chromadb.api import ClientAPI from chromadb.api.types import validate_embedding_function -from chromadb import Documents, EmbeddingFunction, Embeddings -from typing import cast +from crewai.memory.storage.base_rag_storage import BaseRAGStorage +from crewai.utilities.paths import db_storage_path @contextlib.contextmanager @@ -21,9 +21,11 @@ def suppress_logging( logger = logging.getLogger(logger_name) original_level = logger.getEffectiveLevel() logger.setLevel(level) - with contextlib.redirect_stdout(io.StringIO()), contextlib.redirect_stderr( - io.StringIO() - ), contextlib.suppress(UserWarning): + with ( + contextlib.redirect_stdout(io.StringIO()), + contextlib.redirect_stderr(io.StringIO()), + contextlib.suppress(UserWarning), + ): yield logger.setLevel(original_level) @@ -113,12 +115,52 @@ class RAGStorage(BaseRAGStorage): self.embedder_config = embedding_functions.HuggingFaceEmbeddingServer( url=config.get("api_url"), ) + elif provider == "watson": + try: + import ibm_watsonx_ai.foundation_models as watson_models + from ibm_watsonx_ai import Credentials + from ibm_watsonx_ai.metanames import ( + EmbedTextParamsMetaNames as EmbedParams, + ) + except ImportError as e: + raise ImportError( + "IBM Watson dependencies are not installed. Please install them to use Watson embedding." + ) from e + + class WatsonEmbeddingFunction(EmbeddingFunction): + def __call__(self, input: Documents) -> Embeddings: + if isinstance(input, str): + input = [input] + + embed_params = { + EmbedParams.TRUNCATE_INPUT_TOKENS: 3, + EmbedParams.RETURN_OPTIONS: {"input_text": True}, + } + + embedding = watson_models.Embeddings( + model_id=config.get("model"), + params=embed_params, + credentials=Credentials( + api_key=config.get("api_key"), url=config.get("api_url") + ), + project_id=config.get("project_id"), + ) + + try: + embeddings = embedding.embed_documents(input) + return cast(Embeddings, embeddings) + + except Exception as e: + print("Error during Watson embedding:", e) + raise e + + self.embedder_config = WatsonEmbeddingFunction() else: raise Exception( - f"Unsupported embedding provider: {provider}, supported providers: [openai, azure, ollama, vertexai, google, cohere, huggingface]" + f"Unsupported embedding provider: {provider}, supported providers: [openai, azure, ollama, vertexai, google, cohere, huggingface, watson]" ) else: - validate_embedding_function(self.embedder_config) # type: ignore # used for validating embedder_config if defined a embedding function/class + validate_embedding_function(self.embedder_config) self.embedder_config = self.embedder_config def _initialize_app(self): From 9b142e580b5a9eb158ea45bf4ac05ee8c0e662ad Mon Sep 17 00:00:00 2001 From: "Brandon Hancock (bhancock_ai)" <109994880+bhancockio@users.noreply.github.com> Date: Fri, 1 Nov 2024 17:37:02 -0400 Subject: [PATCH 039/126] add inputs to flows (#1553) * add inputs to flows * fix flows lint --- docs/concepts/flows.mdx | 123 +++++++++++++++++++--------------------- src/crewai/flow/flow.py | 84 +++++++++++++++++++++++++-- 2 files changed, 139 insertions(+), 68 deletions(-) diff --git a/docs/concepts/flows.mdx b/docs/concepts/flows.mdx index e3f0e6b45..9ead26d70 100644 --- a/docs/concepts/flows.mdx +++ b/docs/concepts/flows.mdx @@ -18,60 +18,63 @@ Flows allow you to create structured, event-driven workflows. They provide a sea 4. **Flexible Control Flow**: Implement conditional logic, loops, and branching within your workflows. +5. **Input Flexibility**: Flows can accept inputs to initialize or update their state, with different handling for structured and unstructured state management. + ## Getting Started Let's create a simple Flow where you will use OpenAI to generate a random city in one task and then use that city to generate a fun fact in another task. -```python Code +### Passing Inputs to Flows +Flows can accept inputs to initialize or update their state before execution. The way inputs are handled depends on whether the flow uses structured or unstructured state management. + +#### Structured State Management + +In structured state management, the flow's state is defined using a Pydantic `BaseModel`. Inputs must match the model's schema, and any updates will overwrite the default values. + +```python from crewai.flow.flow import Flow, listen, start -from dotenv import load_dotenv -from litellm import completion +from pydantic import BaseModel +class ExampleState(BaseModel): + counter: int = 0 + message: str = "" -class ExampleFlow(Flow): - model = "gpt-4o-mini" - +class StructuredExampleFlow(Flow[ExampleState]): @start() - def generate_city(self): - print("Starting flow") + def first_method(self): + # Implementation - response = completion( - model=self.model, - messages=[ - { - "role": "user", - "content": "Return the name of a random city in the world.", - }, - ], - ) +flow = StructuredExampleFlow() +flow.kickoff(inputs={"counter": 10}) +``` - random_city = response["choices"][0]["message"]["content"] - print(f"Random City: {random_city}") +In this example, the `counter` is initialized to `10`, while `message` retains its default value. - return random_city +#### Unstructured State Management - @listen(generate_city) - def generate_fun_fact(self, random_city): - response = completion( - model=self.model, - messages=[ - { - "role": "user", - "content": f"Tell me a fun fact about {random_city}", - }, - ], - ) +In unstructured state management, the flow's state is a dictionary. You can pass any dictionary to update the state. - fun_fact = response["choices"][0]["message"]["content"] - return fun_fact +```python +from crewai.flow.flow import Flow, listen, start +class UnstructuredExampleFlow(Flow): + @start() + def first_method(self): + # Implementation +flow = UnstructuredExampleFlow() +flow.kickoff(inputs={"counter": 5, "message": "Initial message"}) +``` -flow = ExampleFlow() -result = flow.kickoff() +Here, both `counter` and `message` are updated based on the provided inputs. -print(f"Generated fun fact: {result}") +**Note:** Ensure that inputs for structured state management adhere to the defined schema to avoid validation errors. + +### Example Flow + +```python +# Existing example code ``` 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. @@ -94,14 +97,14 @@ The `@listen()` decorator can be used in several ways: 1. **Listening to a Method by Name**: You can pass the name of the method you want to listen to as a string. When that method completes, the listener method will be triggered. - ```python Code + ```python @listen("generate_city") def generate_fun_fact(self, random_city): # Implementation ``` 2. **Listening to a Method Directly**: You can pass the method itself. When that method completes, the listener method will be triggered. - ```python Code + ```python @listen(generate_city) def generate_fun_fact(self, random_city): # Implementation @@ -118,7 +121,7 @@ When you run a Flow, the final output is determined by the last method that comp Here's how you can access the final output: -```python Code +```python from crewai.flow.flow import Flow, listen, start class OutputExampleFlow(Flow): @@ -130,18 +133,17 @@ class OutputExampleFlow(Flow): def second_method(self, first_output): return f"Second method received: {first_output}" - flow = OutputExampleFlow() final_output = flow.kickoff() print("---- Final Output ----") print(final_output) -```` +``` -``` text Output +```text ---- Final Output ---- Second method received: Output from first_method -```` +``` @@ -156,7 +158,7 @@ Here's an example of how to update and access the state: -```python Code +```python from crewai.flow.flow import Flow, listen, start from pydantic import BaseModel @@ -184,7 +186,7 @@ print("Final State:") print(flow.state) ``` -```text Output +```text Final Output: Hello from first_method - updated by second_method Final State: counter=2 message='Hello from first_method - updated by second_method' @@ -208,10 +210,10 @@ allowing developers to choose the approach that best fits their application's ne In unstructured state management, all state is stored in the `state` attribute of the `Flow` class. This approach offers flexibility, enabling developers to add or modify state attributes on the fly without defining a strict schema. -```python Code +```python from crewai.flow.flow import Flow, listen, start -class UntructuredExampleFlow(Flow): +class UnstructuredExampleFlow(Flow): @start() def first_method(self): @@ -230,8 +232,7 @@ class UntructuredExampleFlow(Flow): print(f"State after third_method: {self.state}") - -flow = UntructuredExampleFlow() +flow = UnstructuredExampleFlow() flow.kickoff() ``` @@ -245,16 +246,14 @@ flow.kickoff() Structured state management leverages predefined schemas to ensure consistency and type safety across the workflow. By using models like Pydantic's `BaseModel`, developers can define the exact shape of the state, enabling better validation and auto-completion in development environments. -```python Code +```python from crewai.flow.flow import Flow, listen, start from pydantic import BaseModel - class ExampleState(BaseModel): counter: int = 0 message: str = "" - class StructuredExampleFlow(Flow[ExampleState]): @start() @@ -273,7 +272,6 @@ class StructuredExampleFlow(Flow[ExampleState]): print(f"State after third_method: {self.state}") - flow = StructuredExampleFlow() flow.kickoff() ``` @@ -307,7 +305,7 @@ The `or_` function in Flows allows you to listen to multiple methods and trigger -```python Code +```python from crewai.flow.flow import Flow, listen, or_, start class OrExampleFlow(Flow): @@ -324,13 +322,11 @@ class OrExampleFlow(Flow): def logger(self, result): print(f"Logger: {result}") - - flow = OrExampleFlow() flow.kickoff() ``` -```text Output +```text Logger: Hello from the start method Logger: Hello from the second method ``` @@ -346,7 +342,7 @@ The `and_` function in Flows allows you to listen to multiple methods and trigge -```python Code +```python from crewai.flow.flow import Flow, and_, listen, start class AndExampleFlow(Flow): @@ -368,7 +364,7 @@ flow = AndExampleFlow() flow.kickoff() ``` -```text Output +```text ---- Logger ---- {'greeting': 'Hello from the start method', 'joke': 'What do computers eat? Microchips.'} ``` @@ -385,7 +381,7 @@ You can specify different routes based on the output of the method, allowing you -```python Code +```python import random from crewai.flow.flow import Flow, listen, router, start from pydantic import BaseModel @@ -416,12 +412,11 @@ class RouterFlow(Flow[ExampleState]): def fourth_method(self): print("Fourth method running") - flow = RouterFlow() flow.kickoff() ``` -```text Output +```text Starting the structured flow Third method running Fourth method running @@ -484,7 +479,7 @@ The `main.py` file is where you create your flow and connect the crews together. Here's an example of how you can connect the `poem_crew` in the `main.py` file: -```python Code +```python #!/usr/bin/env python from random import randint @@ -612,7 +607,7 @@ CrewAI provides two convenient methods to generate plots of your flows: If you are working directly with a flow instance, you can generate a plot by calling the `plot()` method on your flow object. This method will create an HTML file containing the interactive plot of your flow. -```python Code +```python # Assuming you have a flow instance flow.plot("my_flow_plot") ``` diff --git a/src/crewai/flow/flow.py b/src/crewai/flow/flow.py index e7231e13f..9b6463d65 100644 --- a/src/crewai/flow/flow.py +++ b/src/crewai/flow/flow.py @@ -1,8 +1,20 @@ import asyncio import inspect -from typing import Any, Callable, Dict, Generic, List, Set, Type, TypeVar, Union +from typing import ( + Any, + Callable, + Dict, + Generic, + List, + Optional, + Set, + Type, + TypeVar, + Union, + cast, +) -from pydantic import BaseModel +from pydantic import BaseModel, ValidationError from crewai.flow.flow_visualizer import plot_flow from crewai.flow.utils import get_possible_return_constants @@ -191,10 +203,74 @@ class Flow(Generic[T], metaclass=FlowMeta): """Returns the list of all outputs from executed methods.""" return self._method_outputs - def kickoff(self) -> Any: + def _initialize_state(self, inputs: Dict[str, Any]) -> None: + """ + Initializes or updates the state with the provided inputs. + + Args: + inputs: Dictionary of inputs to initialize or update the state. + + Raises: + ValueError: If inputs do not match the structured state model. + TypeError: If state is neither a BaseModel instance nor a dictionary. + """ + if isinstance(self._state, BaseModel): + # Structured state management + try: + # Define a function to create the dynamic class + def create_model_with_extra_forbid( + base_model: Type[BaseModel], + ) -> Type[BaseModel]: + class ModelWithExtraForbid(base_model): # type: ignore + model_config = base_model.model_config.copy() + model_config["extra"] = "forbid" + + return ModelWithExtraForbid + + # Create the dynamic class + ModelWithExtraForbid = create_model_with_extra_forbid( + self._state.__class__ + ) + + # Create a new instance using the combined state and inputs + self._state = cast( + T, ModelWithExtraForbid(**{**self._state.model_dump(), **inputs}) + ) + + except ValidationError as e: + raise ValueError(f"Invalid inputs for structured state: {e}") from e + elif isinstance(self._state, dict): + # Unstructured state management + self._state.update(inputs) + else: + raise TypeError("State must be a BaseModel instance or a dictionary.") + + def kickoff(self, inputs: Optional[Dict[str, Any]] = None) -> Any: + """ + Starts the execution of the flow synchronously. + + Args: + inputs: Optional dictionary of inputs to initialize or update the state. + + Returns: + The final output from the flow execution. + """ + if inputs is not None: + self._initialize_state(inputs) return asyncio.run(self.kickoff_async()) - async def kickoff_async(self) -> Any: + async def kickoff_async(self, inputs: Optional[Dict[str, Any]] = None) -> Any: + """ + Starts the execution of the flow asynchronously. + + Args: + inputs: Optional dictionary of inputs to initialize or update the state. + + Returns: + The final output from the flow execution. + """ + if inputs is not None: + self._initialize_state(inputs) if not self._start_methods: raise ValueError("No start method defined") From 57201fb856ea729e0b5ca73b798198d31012ebc3 Mon Sep 17 00:00:00 2001 From: Gui Vieira Date: Fri, 1 Nov 2024 18:54:40 -0300 Subject: [PATCH 040/126] Increase providers fetching timeout --- src/crewai/cli/provider.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/crewai/cli/provider.py b/src/crewai/cli/provider.py index 4bfeb9324..529ca5e26 100644 --- a/src/crewai/cli/provider.py +++ b/src/crewai/cli/provider.py @@ -164,7 +164,7 @@ def fetch_provider_data(cache_file): - dict or None: The fetched provider data or None if the operation fails. """ try: - response = requests.get(JSON_URL, stream=True, timeout=10) + response = requests.get(JSON_URL, stream=True, timeout=60) response.raise_for_status() data = download_data(response) with open(cache_file, "w") as f: From d70c54254750cc31842b3814eb1526625fae7f69 Mon Sep 17 00:00:00 2001 From: "Brandon Hancock (bhancock_ai)" <109994880+bhancockio@users.noreply.github.com> Date: Mon, 4 Nov 2024 11:42:38 -0500 Subject: [PATCH 041/126] Raise an error if an LLM doesnt return a response (#1548) --- src/crewai/agents/crew_agent_executor.py | 9 +++++++++ src/crewai/llm.py | 10 +++++----- 2 files changed, 14 insertions(+), 5 deletions(-) diff --git a/src/crewai/agents/crew_agent_executor.py b/src/crewai/agents/crew_agent_executor.py index b11782ca1..9e9ad9c7e 100644 --- a/src/crewai/agents/crew_agent_executor.py +++ b/src/crewai/agents/crew_agent_executor.py @@ -117,6 +117,15 @@ class CrewAgentExecutor(CrewAgentExecutorMixin): callbacks=self.callbacks, ) + if answer is None or answer == "": + self._printer.print( + content="Received None or empty response from LLM call.", + color="red", + ) + raise ValueError( + "Invalid response from LLM call - None or empty." + ) + if not self.use_stop_words: try: self._format_answer(answer) diff --git a/src/crewai/llm.py b/src/crewai/llm.py index 000bc2509..577cb6a43 100644 --- a/src/crewai/llm.py +++ b/src/crewai/llm.py @@ -1,7 +1,10 @@ +import io +import logging +import sys +import warnings from contextlib import contextmanager from typing import Any, Dict, List, Optional, Union -import logging -import warnings + import litellm from litellm import get_supported_openai_params @@ -9,9 +12,6 @@ from crewai.utilities.exceptions.context_window_exceeding_exception import ( LLMContextLengthExceededException, ) -import sys -import io - class FilteredStream(io.StringIO): def write(self, s): From f50e709985281a8ff9e37372b02e3bcdf577d86b Mon Sep 17 00:00:00 2001 From: Tony Kipkemboi Date: Tue, 5 Nov 2024 11:26:19 -0500 Subject: [PATCH 042/126] docs update (#1558) * add llm providers accordion group * fix numbering * Fix directory tree & add llms to accordion * update crewai enterprise link in docs --- docs/quickstart.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/quickstart.mdx b/docs/quickstart.mdx index 49a690093..ef149bfcc 100644 --- a/docs/quickstart.mdx +++ b/docs/quickstart.mdx @@ -330,4 +330,4 @@ This will clear the crew's memory, allowing for a fresh start. ## Deploying Your Project -The easiest way to deploy your crew is through [CrewAI Enterprise](https://www.crewai.com/crewaiplus), where you can deploy your crew in a few clicks. +The easiest way to deploy your crew is through [CrewAI Enterprise](http://app.crewai.com/), where you can deploy your crew in a few clicks. From 3d4479547668828b18430624a3fe58f72c861f41 Mon Sep 17 00:00:00 2001 From: "Brandon Hancock (bhancock_ai)" <109994880+bhancockio@users.noreply.github.com> Date: Tue, 5 Nov 2024 12:01:57 -0500 Subject: [PATCH 043/126] Feat/watson in cli (#1535) * getting cli and .env to work together for different models * support new models * clean up prints * Add support for cerebras * Fix watson keys --- src/crewai/agent.py | 45 ++++++- src/crewai/cli/constants.py | 173 ++++++++++++++++++++++++-- src/crewai/cli/create_crew.py | 102 +++++++-------- src/crewai/cli/templates/crew/crew.py | 2 +- src/crewai/cli/templates/crew/main.py | 4 + 5 files changed, 259 insertions(+), 67 deletions(-) diff --git a/src/crewai/agent.py b/src/crewai/agent.py index f2a0a5c31..d30746808 100644 --- a/src/crewai/agent.py +++ b/src/crewai/agent.py @@ -8,6 +8,7 @@ from pydantic import Field, InstanceOf, PrivateAttr, model_validator from crewai.agents import CacheHandler from crewai.agents.agent_builder.base_agent import BaseAgent from crewai.agents.crew_agent_executor import CrewAgentExecutor +from crewai.cli.constants import ENV_VARS from crewai.llm import LLM from crewai.memory.contextual.contextual_memory import ContextualMemory from crewai.tools.agent_tools.agent_tools import AgentTools @@ -131,8 +132,12 @@ class Agent(BaseAgent): # If it's already an LLM instance, keep it as is pass elif self.llm is None: - # If it's None, use environment variables or default - model_name = os.environ.get("OPENAI_MODEL_NAME", "gpt-4o-mini") + # Determine the model name from environment variables or use default + model_name = ( + os.environ.get("OPENAI_MODEL_NAME") + or os.environ.get("MODEL") + or "gpt-4o-mini" + ) llm_params = {"model": model_name} api_base = os.environ.get("OPENAI_API_BASE") or os.environ.get( @@ -141,9 +146,39 @@ class Agent(BaseAgent): if api_base: llm_params["base_url"] = api_base - api_key = os.environ.get("OPENAI_API_KEY") - if api_key: - llm_params["api_key"] = api_key + # Iterate over all environment variables to find matching API keys or use defaults + for provider, env_vars in ENV_VARS.items(): + for env_var in env_vars: + # Check if the environment variable is set + if "key_name" in env_var: + env_value = os.environ.get(env_var["key_name"]) + if env_value: + # Map key names containing "API_KEY" to "api_key" + key_name = ( + "api_key" + if "API_KEY" in env_var["key_name"] + else env_var["key_name"] + ) + # Map key names containing "API_BASE" to "api_base" + key_name = ( + "api_base" + if "API_BASE" in env_var["key_name"] + else key_name + ) + # Map key names containing "API_VERSION" to "api_version" + key_name = ( + "api_version" + if "API_VERSION" in env_var["key_name"] + else key_name + ) + llm_params[key_name] = env_value + # Check for default values if the environment variable is not set + elif env_var.get("default", False): + for key, value in env_var.items(): + if key not in ["prompt", "key_name", "default"]: + # Only add default if the key is already set in os.environ + if key in os.environ: + llm_params[key] = value self.llm = LLM(**llm_params) else: diff --git a/src/crewai/cli/constants.py b/src/crewai/cli/constants.py index 9a0b36c39..4be08fa2a 100644 --- a/src/crewai/cli/constants.py +++ b/src/crewai/cli/constants.py @@ -1,19 +1,168 @@ ENV_VARS = { - 'openai': ['OPENAI_API_KEY'], - 'anthropic': ['ANTHROPIC_API_KEY'], - 'gemini': ['GEMINI_API_KEY'], - 'groq': ['GROQ_API_KEY'], - 'ollama': ['FAKE_KEY'], + "openai": [ + { + "prompt": "Enter your OPENAI API key (press Enter to skip)", + "key_name": "OPENAI_API_KEY", + } + ], + "anthropic": [ + { + "prompt": "Enter your ANTHROPIC API key (press Enter to skip)", + "key_name": "ANTHROPIC_API_KEY", + } + ], + "gemini": [ + { + "prompt": "Enter your GEMINI API key (press Enter to skip)", + "key_name": "GEMINI_API_KEY", + } + ], + "groq": [ + { + "prompt": "Enter your GROQ API key (press Enter to skip)", + "key_name": "GROQ_API_KEY", + } + ], + "watson": [ + { + "prompt": "Enter your WATSONX URL (press Enter to skip)", + "key_name": "WATSONX_URL", + }, + { + "prompt": "Enter your WATSONX API Key (press Enter to skip)", + "key_name": "WATSONX_APIKEY", + }, + { + "prompt": "Enter your WATSONX Project Id (press Enter to skip)", + "key_name": "WATSONX_PROJECT_ID", + }, + ], + "ollama": [ + { + "default": True, + "API_BASE": "http://localhost:11434", + } + ], + "bedrock": [ + { + "prompt": "Enter your AWS Access Key ID (press Enter to skip)", + "key_name": "AWS_ACCESS_KEY_ID", + }, + { + "prompt": "Enter your AWS Secret Access Key (press Enter to skip)", + "key_name": "AWS_SECRET_ACCESS_KEY", + }, + { + "prompt": "Enter your AWS Region Name (press Enter to skip)", + "key_name": "AWS_REGION_NAME", + }, + ], + "azure": [ + { + "prompt": "Enter your Azure deployment name (must start with 'azure/')", + "key_name": "model", + }, + { + "prompt": "Enter your AZURE API key (press Enter to skip)", + "key_name": "AZURE_API_KEY", + }, + { + "prompt": "Enter your AZURE API base URL (press Enter to skip)", + "key_name": "AZURE_API_BASE", + }, + { + "prompt": "Enter your AZURE API version (press Enter to skip)", + "key_name": "AZURE_API_VERSION", + }, + ], + "cerebras": [ + { + "prompt": "Enter your Cerebras model name (must start with 'cerebras/')", + "key_name": "model", + }, + { + "prompt": "Enter your Cerebras API version (press Enter to skip)", + "key_name": "CEREBRAS_API_KEY", + }, + ], } -PROVIDERS = ['openai', 'anthropic', 'gemini', 'groq', 'ollama'] + +PROVIDERS = [ + "openai", + "anthropic", + "gemini", + "groq", + "ollama", + "watson", + "bedrock", + "azure", + "cerebras", +] MODELS = { - 'openai': ['gpt-4', 'gpt-4o', 'gpt-4o-mini', 'o1-mini', 'o1-preview'], - 'anthropic': ['claude-3-5-sonnet-20240620', 'claude-3-sonnet-20240229', 'claude-3-opus-20240229', 'claude-3-haiku-20240307'], - 'gemini': ['gemini-1.5-flash', 'gemini-1.5-pro', 'gemini-gemma-2-9b-it', 'gemini-gemma-2-27b-it'], - 'groq': ['llama-3.1-8b-instant', 'llama-3.1-70b-versatile', 'llama-3.1-405b-reasoning', 'gemma2-9b-it', 'gemma-7b-it'], - 'ollama': ['llama3.1', 'mixtral'], + "openai": ["gpt-4", "gpt-4o", "gpt-4o-mini", "o1-mini", "o1-preview"], + "anthropic": [ + "claude-3-5-sonnet-20240620", + "claude-3-sonnet-20240229", + "claude-3-opus-20240229", + "claude-3-haiku-20240307", + ], + "gemini": [ + "gemini/gemini-1.5-flash", + "gemini/gemini-1.5-pro", + "gemini/gemini-gemma-2-9b-it", + "gemini/gemini-gemma-2-27b-it", + ], + "groq": [ + "groq/llama-3.1-8b-instant", + "groq/llama-3.1-70b-versatile", + "groq/llama-3.1-405b-reasoning", + "groq/gemma2-9b-it", + "groq/gemma-7b-it", + ], + "ollama": ["ollama/llama3.1", "ollama/mixtral"], + "watson": [ + "watsonx/google/flan-t5-xxl", + "watsonx/google/flan-ul2", + "watsonx/bigscience/mt0-xxl", + "watsonx/eleutherai/gpt-neox-20b", + "watsonx/ibm/mpt-7b-instruct2", + "watsonx/bigcode/starcoder", + "watsonx/meta-llama/llama-2-70b-chat", + "watsonx/meta-llama/llama-2-13b-chat", + "watsonx/ibm/granite-13b-instruct-v1", + "watsonx/ibm/granite-13b-chat-v1", + "watsonx/google/flan-t5-xl", + "watsonx/ibm/granite-13b-chat-v2", + "watsonx/ibm/granite-13b-instruct-v2", + "watsonx/elyza/elyza-japanese-llama-2-7b-instruct", + "watsonx/ibm-mistralai/mixtral-8x7b-instruct-v01-q", + ], + "bedrock": [ + "bedrock/anthropic.claude-3-5-sonnet-20240620-v1:0", + "bedrock/anthropic.claude-3-sonnet-20240229-v1:0", + "bedrock/anthropic.claude-3-haiku-20240307-v1:0", + "bedrock/anthropic.claude-3-opus-20240229-v1:0", + "bedrock/anthropic.claude-v2:1", + "bedrock/anthropic.claude-v2", + "bedrock/anthropic.claude-instant-v1", + "bedrock/meta.llama3-1-405b-instruct-v1:0", + "bedrock/meta.llama3-1-70b-instruct-v1:0", + "bedrock/meta.llama3-1-8b-instruct-v1:0", + "bedrock/meta.llama3-70b-instruct-v1:0", + "bedrock/meta.llama3-8b-instruct-v1:0", + "bedrock/amazon.titan-text-lite-v1", + "bedrock/amazon.titan-text-express-v1", + "bedrock/cohere.command-text-v14", + "bedrock/ai21.j2-mid-v1", + "bedrock/ai21.j2-ultra-v1", + "bedrock/ai21.jamba-instruct-v1:0", + "bedrock/meta.llama2-13b-chat-v1", + "bedrock/meta.llama2-70b-chat-v1", + "bedrock/mistral.mistral-7b-instruct-v0:2", + "bedrock/mistral.mixtral-8x7b-instruct-v0:1", + ], } -JSON_URL = "https://raw.githubusercontent.com/BerriAI/litellm/main/model_prices_and_context_window.json" \ No newline at end of file +JSON_URL = "https://raw.githubusercontent.com/BerriAI/litellm/main/model_prices_and_context_window.json" diff --git a/src/crewai/cli/create_crew.py b/src/crewai/cli/create_crew.py index 5767b82a1..06440d74e 100644 --- a/src/crewai/cli/create_crew.py +++ b/src/crewai/cli/create_crew.py @@ -1,11 +1,11 @@ +import shutil import sys from pathlib import Path import click -from crewai.cli.constants import ENV_VARS +from crewai.cli.constants import ENV_VARS, MODELS from crewai.cli.provider import ( - PROVIDERS, get_provider_data, select_model, select_provider, @@ -29,20 +29,20 @@ def create_folder_structure(name, parent_folder=None): click.secho("Operation cancelled.", fg="yellow") sys.exit(0) click.secho(f"Overriding folder {folder_name}...", fg="green", bold=True) - else: - click.secho( - f"Creating {'crew' if parent_folder else 'folder'} {folder_name}...", - fg="green", - bold=True, - ) + shutil.rmtree(folder_path) # Delete the existing folder and its contents - if not folder_path.exists(): - folder_path.mkdir(parents=True) - (folder_path / "tests").mkdir(exist_ok=True) - if not parent_folder: - (folder_path / "src" / folder_name).mkdir(parents=True) - (folder_path / "src" / folder_name / "tools").mkdir(parents=True) - (folder_path / "src" / folder_name / "config").mkdir(parents=True) + click.secho( + f"Creating {'crew' if parent_folder else 'folder'} {folder_name}...", + fg="green", + bold=True, + ) + + folder_path.mkdir(parents=True) + (folder_path / "tests").mkdir(exist_ok=True) + if not parent_folder: + (folder_path / "src" / folder_name).mkdir(parents=True) + (folder_path / "src" / folder_name / "tools").mkdir(parents=True) + (folder_path / "src" / folder_name / "config").mkdir(parents=True) return folder_path, folder_name, class_name @@ -92,7 +92,10 @@ def create_crew(name, provider=None, skip_provider=False, parent_folder=None): existing_provider = None for provider, env_keys in ENV_VARS.items(): - if any(key in env_vars for key in env_keys): + if any( + "key_name" in details and details["key_name"] in env_vars + for details in env_keys + ): existing_provider = provider break @@ -118,47 +121,48 @@ def create_crew(name, provider=None, skip_provider=False, parent_folder=None): "No provider selected. Please try again or press 'q' to exit.", fg="red" ) - while True: - selected_model = select_model(selected_provider, provider_models) - if selected_model is None: # User typed 'q' - click.secho("Exiting...", fg="yellow") - sys.exit(0) - if selected_model: # Valid selection - break - click.secho( - "No model selected. Please try again or press 'q' to exit.", fg="red" - ) + # Check if the selected provider has predefined models + if selected_provider in MODELS and MODELS[selected_provider]: + while True: + selected_model = select_model(selected_provider, provider_models) + if selected_model is None: # User typed 'q' + click.secho("Exiting...", fg="yellow") + sys.exit(0) + if selected_model: # Valid selection + break + click.secho( + "No model selected. Please try again or press 'q' to exit.", + fg="red", + ) + env_vars["MODEL"] = selected_model - if selected_provider in PROVIDERS: - api_key_var = ENV_VARS[selected_provider][0] - else: - api_key_var = click.prompt( - f"Enter the environment variable name for your {selected_provider.capitalize()} API key", - type=str, - default="", - ) + # Check if the selected provider requires API keys + if selected_provider in ENV_VARS: + provider_env_vars = ENV_VARS[selected_provider] + for details in provider_env_vars: + if details.get("default", False): + # Automatically add default key-value pairs + for key, value in details.items(): + if key not in ["prompt", "key_name", "default"]: + env_vars[key] = value + elif "key_name" in details: + # Prompt for non-default key-value pairs + prompt = details["prompt"] + key_name = details["key_name"] + api_key_value = click.prompt(prompt, default="", show_default=False) - api_key_value = "" - click.echo( - f"Enter your {selected_provider.capitalize()} API key (press Enter to skip): ", - nl=False, - ) - try: - api_key_value = input() - except (KeyboardInterrupt, EOFError): - api_key_value = "" + if api_key_value.strip(): + env_vars[key_name] = api_key_value - if api_key_value.strip(): - env_vars = {api_key_var: api_key_value} + if env_vars: write_env_file(folder_path, env_vars) - click.secho("API key saved to .env file", fg="green") + click.secho("API keys and model saved to .env file", fg="green") else: click.secho( - "No API key provided. Skipping .env file creation.", fg="yellow" + "No API keys provided. Skipping .env file creation.", fg="yellow" ) - env_vars["MODEL"] = selected_model - click.secho(f"Selected model: {selected_model}", fg="green") + click.secho(f"Selected model: {env_vars.get('MODEL', 'N/A')}", fg="green") package_dir = Path(__file__).parent templates_dir = package_dir / "templates" / "crew" diff --git a/src/crewai/cli/templates/crew/crew.py b/src/crewai/cli/templates/crew/crew.py index f950d13d4..392e29edd 100644 --- a/src/crewai/cli/templates/crew/crew.py +++ b/src/crewai/cli/templates/crew/crew.py @@ -48,4 +48,4 @@ class {{crew_name}}Crew(): process=Process.sequential, verbose=True, # process=Process.hierarchical, # In case you wanna use that instead https://docs.crewai.com/how-to/Hierarchical/ - ) \ No newline at end of file + ) diff --git a/src/crewai/cli/templates/crew/main.py b/src/crewai/cli/templates/crew/main.py index 88edfcbff..d441fa0fa 100644 --- a/src/crewai/cli/templates/crew/main.py +++ b/src/crewai/cli/templates/crew/main.py @@ -1,7 +1,11 @@ #!/usr/bin/env python import sys +import warnings + from {{folder_name}}.crew import {{crew_name}}Crew +warnings.filterwarnings("ignore", category=SyntaxWarning, module="pysbd") + # This main file is intended to be a way for you to run your # crew locally, so refrain from adding unnecessary logic into this file. # Replace with inputs you want to test with, it will automatically From faa231e27880c4b9cab4f5a3d92c749e1de9bd93 Mon Sep 17 00:00:00 2001 From: "Brandon Hancock (bhancock_ai)" <109994880+bhancockio@users.noreply.github.com> Date: Tue, 5 Nov 2024 12:02:54 -0500 Subject: [PATCH 044/126] Fix flows to support cycles and added in test (#1556) --- src/crewai/flow/flow.py | 53 ++++---- tests/flow_test.py | 264 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 289 insertions(+), 28 deletions(-) create mode 100644 tests/flow_test.py diff --git a/src/crewai/flow/flow.py b/src/crewai/flow/flow.py index 9b6463d65..fa0902594 100644 --- a/src/crewai/flow/flow.py +++ b/src/crewai/flow/flow.py @@ -131,7 +131,6 @@ class FlowMeta(type): condition_type = getattr(attr_value, "__condition_type__", "OR") listeners[attr_name] = (condition_type, methods) - # TODO: should we add a check for __condition_type__ 'AND'? elif hasattr(attr_value, "__is_router__"): routers[attr_value.__router_for__] = attr_name possible_returns = get_possible_return_constants(attr_value) @@ -171,8 +170,7 @@ class Flow(Generic[T], metaclass=FlowMeta): def __init__(self) -> None: self._methods: Dict[str, Callable] = {} self._state: T = self._create_initial_state() - self._executed_methods: Set[str] = set() - self._scheduled_tasks: Set[str] = set() + self._method_execution_counts: Dict[str, int] = {} self._pending_and_listeners: Dict[str, Set[str]] = {} self._method_outputs: List[Any] = [] # List to store all method outputs @@ -309,7 +307,10 @@ class Flow(Generic[T], metaclass=FlowMeta): ) self._method_outputs.append(result) # Store the output - self._executed_methods.add(method_name) + # Track method execution counts + self._method_execution_counts[method_name] = ( + self._method_execution_counts.get(method_name, 0) + 1 + ) return result @@ -319,35 +320,34 @@ class Flow(Generic[T], metaclass=FlowMeta): if trigger_method in self._routers: router_method = self._methods[self._routers[trigger_method]] path = await self._execute_method( - trigger_method, router_method - ) # TODO: Change or not? - # Use the path as the new trigger method + self._routers[trigger_method], router_method + ) trigger_method = path for listener_name, (condition_type, methods) in self._listeners.items(): if condition_type == "OR": if trigger_method in methods: - if ( - listener_name not in self._executed_methods - and listener_name not in self._scheduled_tasks - ): - self._scheduled_tasks.add(listener_name) - listener_tasks.append( - self._execute_single_listener(listener_name, result) - ) + # Schedule the listener without preventing re-execution + listener_tasks.append( + self._execute_single_listener(listener_name, result) + ) elif condition_type == "AND": - if all(method in self._executed_methods for method in methods): - if ( - listener_name not in self._executed_methods - and listener_name not in self._scheduled_tasks - ): - self._scheduled_tasks.add(listener_name) - listener_tasks.append( - self._execute_single_listener(listener_name, result) - ) + # Initialize pending methods for this listener if not already done + if listener_name not in self._pending_and_listeners: + self._pending_and_listeners[listener_name] = set(methods) + # Remove the trigger method from pending methods + self._pending_and_listeners[listener_name].discard(trigger_method) + if not self._pending_and_listeners[listener_name]: + # All required methods have been executed + listener_tasks.append( + self._execute_single_listener(listener_name, result) + ) + # Reset pending methods for this listener + self._pending_and_listeners.pop(listener_name, None) # Run all listener tasks concurrently and wait for them to complete - await asyncio.gather(*listener_tasks) + if listener_tasks: + await asyncio.gather(*listener_tasks) async def _execute_single_listener(self, listener_name: str, result: Any) -> None: try: @@ -367,9 +367,6 @@ class Flow(Generic[T], metaclass=FlowMeta): # If listener does not expect parameters, call without arguments listener_result = await self._execute_method(listener_name, method) - # Remove from scheduled tasks after execution - self._scheduled_tasks.discard(listener_name) - # Execute listeners of this listener await self._execute_listeners(listener_name, listener_result) except Exception as e: diff --git a/tests/flow_test.py b/tests/flow_test.py new file mode 100644 index 000000000..ffd82367c --- /dev/null +++ b/tests/flow_test.py @@ -0,0 +1,264 @@ +"""Test Flow creation and execution basic functionality.""" + +import asyncio + +import pytest +from crewai.flow.flow import Flow, and_, listen, or_, router, start + + +def test_simple_sequential_flow(): + """Test a simple flow with two steps called sequentially.""" + execution_order = [] + + class SimpleFlow(Flow): + @start() + def step_1(self): + execution_order.append("step_1") + + @listen(step_1) + def step_2(self): + execution_order.append("step_2") + + flow = SimpleFlow() + flow.kickoff() + + assert execution_order == ["step_1", "step_2"] + + +def test_flow_with_multiple_starts(): + """Test a flow with multiple start methods.""" + execution_order = [] + + class MultiStartFlow(Flow): + @start() + def step_a(self): + execution_order.append("step_a") + + @start() + def step_b(self): + execution_order.append("step_b") + + @listen(step_a) + def step_c(self): + execution_order.append("step_c") + + @listen(step_b) + def step_d(self): + execution_order.append("step_d") + + flow = MultiStartFlow() + flow.kickoff() + + assert "step_a" in execution_order + assert "step_b" in execution_order + assert "step_c" in execution_order + assert "step_d" in execution_order + assert execution_order.index("step_c") > execution_order.index("step_a") + assert execution_order.index("step_d") > execution_order.index("step_b") + + +def test_cyclic_flow(): + """Test a cyclic flow that runs a finite number of iterations.""" + execution_order = [] + + class CyclicFlow(Flow): + iteration = 0 + max_iterations = 3 + + @start("loop") + def step_1(self): + if self.iteration >= self.max_iterations: + return # Do not proceed further + execution_order.append(f"step_1_{self.iteration}") + + @listen(step_1) + def step_2(self): + execution_order.append(f"step_2_{self.iteration}") + + @router(step_2) + def step_3(self): + execution_order.append(f"step_3_{self.iteration}") + self.iteration += 1 + if self.iteration < self.max_iterations: + return "loop" + + return "exit" + + flow = CyclicFlow() + flow.kickoff() + + expected_order = [] + for i in range(flow.max_iterations): + expected_order.extend([f"step_1_{i}", f"step_2_{i}", f"step_3_{i}"]) + + assert execution_order == expected_order + + +def test_flow_with_and_condition(): + """Test a flow where a step waits for multiple other steps to complete.""" + execution_order = [] + + class AndConditionFlow(Flow): + @start() + def step_1(self): + execution_order.append("step_1") + + @start() + def step_2(self): + execution_order.append("step_2") + + @listen(and_(step_1, step_2)) + def step_3(self): + execution_order.append("step_3") + + flow = AndConditionFlow() + flow.kickoff() + + assert "step_1" in execution_order + assert "step_2" in execution_order + assert execution_order[-1] == "step_3" + assert execution_order.index("step_3") > execution_order.index("step_1") + assert execution_order.index("step_3") > execution_order.index("step_2") + + +def test_flow_with_or_condition(): + """Test a flow where a step is triggered when any of multiple steps complete.""" + execution_order = [] + + class OrConditionFlow(Flow): + @start() + def step_a(self): + execution_order.append("step_a") + + @start() + def step_b(self): + execution_order.append("step_b") + + @listen(or_(step_a, step_b)) + def step_c(self): + execution_order.append("step_c") + + flow = OrConditionFlow() + flow.kickoff() + + assert "step_a" in execution_order or "step_b" in execution_order + assert "step_c" in execution_order + assert execution_order.index("step_c") > min( + execution_order.index("step_a"), execution_order.index("step_b") + ) + + +def test_flow_with_router(): + """Test a flow that uses a router method to determine the next step.""" + execution_order = [] + + class RouterFlow(Flow): + @start() + def start_method(self): + execution_order.append("start_method") + + @router(start_method) + def router(self): + execution_order.append("router") + # Ensure the condition is set to True to follow the "step_if_true" path + condition = True + return "step_if_true" if condition else "step_if_false" + + @listen("step_if_true") + def truthy(self): + execution_order.append("step_if_true") + + @listen("step_if_false") + def falsy(self): + execution_order.append("step_if_false") + + flow = RouterFlow() + flow.kickoff() + + assert execution_order == ["start_method", "router", "step_if_true"] + + +def test_async_flow(): + """Test an asynchronous flow.""" + execution_order = [] + + class AsyncFlow(Flow): + @start() + async def step_1(self): + execution_order.append("step_1") + await asyncio.sleep(0.1) + + @listen(step_1) + async def step_2(self): + execution_order.append("step_2") + await asyncio.sleep(0.1) + + flow = AsyncFlow() + asyncio.run(flow.kickoff_async()) + + assert execution_order == ["step_1", "step_2"] + + +def test_flow_with_exceptions(): + """Test flow behavior when exceptions occur in steps.""" + execution_order = [] + + class ExceptionFlow(Flow): + @start() + def step_1(self): + execution_order.append("step_1") + raise ValueError("An error occurred in step_1") + + @listen(step_1) + def step_2(self): + execution_order.append("step_2") + + flow = ExceptionFlow() + + with pytest.raises(ValueError): + flow.kickoff() + + # Ensure step_2 did not execute + assert execution_order == ["step_1"] + + +def test_flow_restart(): + """Test restarting a flow after it has completed.""" + execution_order = [] + + class RestartableFlow(Flow): + @start() + def step_1(self): + execution_order.append("step_1") + + @listen(step_1) + def step_2(self): + execution_order.append("step_2") + + flow = RestartableFlow() + flow.kickoff() + flow.kickoff() # Restart the flow + + assert execution_order == ["step_1", "step_2", "step_1", "step_2"] + + +def test_flow_with_custom_state(): + """Test a flow that maintains and modifies internal state.""" + + class StateFlow(Flow): + def __init__(self): + super().__init__() + self.counter = 0 + + @start() + def step_1(self): + self.counter += 1 + + @listen(step_1) + def step_2(self): + self.counter *= 2 + assert self.counter == 2 + + flow = StateFlow() + flow.kickoff() + assert flow.counter == 2 From e856359e2333bc97c6f0a3e62b0f79aecba84204 Mon Sep 17 00:00:00 2001 From: "Brandon Hancock (bhancock_ai)" <109994880+bhancockio@users.noreply.github.com> Date: Tue, 5 Nov 2024 12:07:29 -0500 Subject: [PATCH 045/126] fix missing config (#1557) --- src/crewai/cli/add_crew_to_flow.py | 2 +- src/crewai/cli/templates/crew/crew.py | 5 ++++- src/crewai/cli/templates/crew/main.py | 10 +++++----- 3 files changed, 10 insertions(+), 7 deletions(-) diff --git a/src/crewai/cli/add_crew_to_flow.py b/src/crewai/cli/add_crew_to_flow.py index e4901fa89..ef693a22b 100644 --- a/src/crewai/cli/add_crew_to_flow.py +++ b/src/crewai/cli/add_crew_to_flow.py @@ -54,7 +54,7 @@ def create_embedded_crew(crew_name: str, parent_folder: Path) -> None: templates_dir = Path(__file__).parent / "templates" / "crew" config_template_files = ["agents.yaml", "tasks.yaml"] - crew_template_file = f"{folder_name}_crew.py" # Updated file name + crew_template_file = f"{folder_name}.py" # Updated file name for file_name in config_template_files: src_file = templates_dir / "config" / file_name diff --git a/src/crewai/cli/templates/crew/crew.py b/src/crewai/cli/templates/crew/crew.py index 392e29edd..c47315415 100644 --- a/src/crewai/cli/templates/crew/crew.py +++ b/src/crewai/cli/templates/crew/crew.py @@ -8,9 +8,12 @@ from crewai.project import CrewBase, agent, crew, task # from crewai_tools import SerperDevTool @CrewBase -class {{crew_name}}Crew(): +class {{crew_name}}(): """{{crew_name}} crew""" + agents_config = 'config/agents.yaml' + tasks_config = 'config/tasks.yaml' + @agent def researcher(self) -> Agent: return Agent( diff --git a/src/crewai/cli/templates/crew/main.py b/src/crewai/cli/templates/crew/main.py index d441fa0fa..d5224edcf 100644 --- a/src/crewai/cli/templates/crew/main.py +++ b/src/crewai/cli/templates/crew/main.py @@ -2,7 +2,7 @@ import sys import warnings -from {{folder_name}}.crew import {{crew_name}}Crew +from {{folder_name}}.crew import {{crew_name}} warnings.filterwarnings("ignore", category=SyntaxWarning, module="pysbd") @@ -18,7 +18,7 @@ def run(): inputs = { 'topic': 'AI LLMs' } - {{crew_name}}Crew().crew().kickoff(inputs=inputs) + {{crew_name}}().crew().kickoff(inputs=inputs) def train(): @@ -29,7 +29,7 @@ def train(): "topic": "AI LLMs" } try: - {{crew_name}}Crew().crew().train(n_iterations=int(sys.argv[1]), filename=sys.argv[2], inputs=inputs) + {{crew_name}}().crew().train(n_iterations=int(sys.argv[1]), filename=sys.argv[2], inputs=inputs) except Exception as e: raise Exception(f"An error occurred while training the crew: {e}") @@ -39,7 +39,7 @@ def replay(): Replay the crew execution from a specific task. """ try: - {{crew_name}}Crew().crew().replay(task_id=sys.argv[1]) + {{crew_name}}().crew().replay(task_id=sys.argv[1]) except Exception as e: raise Exception(f"An error occurred while replaying the crew: {e}") @@ -52,7 +52,7 @@ def test(): "topic": "AI LLMs" } try: - {{crew_name}}Crew().crew().test(n_iterations=int(sys.argv[1]), openai_model_name=sys.argv[2], inputs=inputs) + {{crew_name}}().crew().test(n_iterations=int(sys.argv[1]), openai_model_name=sys.argv[2], inputs=inputs) except Exception as e: raise Exception(f"An error occurred while replaying the crew: {e}") From 9f2acfe91f3dafab2aef1c465810b7ef8780eee7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Moura?= Date: Wed, 6 Nov 2024 23:03:58 -0300 Subject: [PATCH 046/126] making sure we don't check for agents that were not used in the crew --- src/crewai/crew.py | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/src/crewai/crew.py b/src/crewai/crew.py index e65024ed6..7bcaa82ad 100644 --- a/src/crewai/crew.py +++ b/src/crewai/crew.py @@ -445,13 +445,14 @@ class Crew(BaseModel): training_data = CrewTrainingHandler(TRAINING_DATA_FILE).load() for agent in train_crew.agents: - result = TaskEvaluator(agent).evaluate_training_data( - training_data=training_data, agent_id=str(agent.id) - ) + if training_data.get(str(agent.id)): + result = TaskEvaluator(agent).evaluate_training_data( + training_data=training_data, agent_id=str(agent.id) + ) - CrewTrainingHandler(filename).save_trained_data( - agent_id=str(agent.role), trained_data=result.model_dump() - ) + CrewTrainingHandler(filename).save_trained_data( + agent_id=str(agent.role), trained_data=result.model_dump() + ) def kickoff( self, From 1b09b085a741bdc87b1786410edef04c34b28f4f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Moura?= Date: Sun, 10 Nov 2024 11:00:16 -0300 Subject: [PATCH 047/126] preparing new version --- pyproject.toml | 2 +- src/crewai/__init__.py | 2 +- src/crewai/cli/templates/crew/pyproject.toml | 2 +- src/crewai/cli/templates/flow/pyproject.toml | 2 +- src/crewai/cli/templates/pipeline/pyproject.toml | 2 +- src/crewai/cli/templates/pipeline_router/pyproject.toml | 2 +- src/crewai/cli/templates/tool/pyproject.toml | 2 +- uv.lock | 2 +- 8 files changed, 8 insertions(+), 8 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index b9e6bc986..7102c9efa 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "crewai" -version = "0.76.9" +version = "0.79.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" diff --git a/src/crewai/__init__.py b/src/crewai/__init__.py index 0a2f02a59..353c11b88 100644 --- a/src/crewai/__init__.py +++ b/src/crewai/__init__.py @@ -14,5 +14,5 @@ warnings.filterwarnings( category=UserWarning, module="pydantic.main", ) -__version__ = "0.76.9" +__version__ = "0.79.0" __all__ = ["Agent", "Crew", "Process", "Task", "Pipeline", "Router", "LLM", "Flow"] diff --git a/src/crewai/cli/templates/crew/pyproject.toml b/src/crewai/cli/templates/crew/pyproject.toml index 447cfcb86..136fa2026 100644 --- a/src/crewai/cli/templates/crew/pyproject.toml +++ b/src/crewai/cli/templates/crew/pyproject.toml @@ -5,7 +5,7 @@ description = "{{name}} using crewAI" authors = [{ name = "Your Name", email = "you@example.com" }] requires-python = ">=3.10,<=3.13" dependencies = [ - "crewai[tools]>=0.76.9,<1.0.0" + "crewai[tools]>=0.79.0,<1.0.0" ] [project.scripts] diff --git a/src/crewai/cli/templates/flow/pyproject.toml b/src/crewai/cli/templates/flow/pyproject.toml index 1ef8f7b36..032f7ee9b 100644 --- a/src/crewai/cli/templates/flow/pyproject.toml +++ b/src/crewai/cli/templates/flow/pyproject.toml @@ -5,7 +5,7 @@ description = "{{name}} using crewAI" authors = [{ name = "Your Name", email = "you@example.com" }] requires-python = ">=3.10,<=3.13" dependencies = [ - "crewai[tools]>=0.76.9,<1.0.0", + "crewai[tools]>=0.79.0,<1.0.0", ] [project.scripts] diff --git a/src/crewai/cli/templates/pipeline/pyproject.toml b/src/crewai/cli/templates/pipeline/pyproject.toml index 53f304283..53a81a620 100644 --- a/src/crewai/cli/templates/pipeline/pyproject.toml +++ b/src/crewai/cli/templates/pipeline/pyproject.toml @@ -6,7 +6,7 @@ authors = ["Your Name "] [tool.poetry.dependencies] python = ">=3.10,<=3.13" -crewai = { extras = ["tools"], version = ">=0.76.9,<1.0.0" } +crewai = { extras = ["tools"], version = ">=0.79.0,<1.0.0" } asyncio = "*" [tool.poetry.scripts] diff --git a/src/crewai/cli/templates/pipeline_router/pyproject.toml b/src/crewai/cli/templates/pipeline_router/pyproject.toml index 33d5c58af..89275861d 100644 --- a/src/crewai/cli/templates/pipeline_router/pyproject.toml +++ b/src/crewai/cli/templates/pipeline_router/pyproject.toml @@ -5,7 +5,7 @@ description = "{{name}} using crewAI" authors = ["Your Name "] requires-python = ">=3.10,<=3.13" dependencies = [ - "crewai[tools]>=0.76.9,<1.0.0" + "crewai[tools]>=0.79.0,<1.0.0" ] [project.scripts] diff --git a/src/crewai/cli/templates/tool/pyproject.toml b/src/crewai/cli/templates/tool/pyproject.toml index 849298b6b..b60edf21b 100644 --- a/src/crewai/cli/templates/tool/pyproject.toml +++ b/src/crewai/cli/templates/tool/pyproject.toml @@ -5,6 +5,6 @@ description = "Power up your crews with {{folder_name}}" readme = "README.md" requires-python = ">=3.10,<=3.13" dependencies = [ - "crewai[tools]>=0.76.9" + "crewai[tools]>=0.79.0" ] diff --git a/uv.lock b/uv.lock index f7f6a1839..0034b42e0 100644 --- a/uv.lock +++ b/uv.lock @@ -604,7 +604,7 @@ wheels = [ [[package]] name = "crewai" -version = "0.76.9" +version = "0.79.0" source = { editable = "." } dependencies = [ { name = "appdirs" }, From 40d378abfbcdd2a71fdb4999f679a0a4b9ff6552 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Moura?= Date: Sun, 10 Nov 2024 11:36:03 -0300 Subject: [PATCH 048/126] updating LLM docs --- docs/concepts/llms.mdx | 132 ++++++++++++++++++++++++++++++++++++++--- 1 file changed, 124 insertions(+), 8 deletions(-) diff --git a/docs/concepts/llms.mdx b/docs/concepts/llms.mdx index d432070d4..5757feca3 100644 --- a/docs/concepts/llms.mdx +++ b/docs/concepts/llms.mdx @@ -25,7 +25,100 @@ By default, CrewAI uses the `gpt-4o-mini` model. It uses environment variables i - `OPENAI_API_BASE` - `OPENAI_API_KEY` -### 2. Custom LLM Objects +### 2. Updating YAML files + +You can update the `agents.yml` file to refer to the LLM you want to use: + +```yaml Code +researcher: + role: Research Specialist + goal: Conduct comprehensive research and analysis to gather relevant information, + synthesize findings, and produce well-documented insights. + backstory: A dedicated research professional with years of experience in academic + investigation, literature review, and data analysis, known for thorough and + methodical approaches to complex research questions. + verbose: true + llm: openai/gpt-4o + # llm: azure/gpt-4o-mini + # llm: gemini/gemini-pro + # llm: anthropic/claude-3-5-sonnet-20240620 + # llm: bedrock/anthropic.claude-3-sonnet-20240229-v1:0 + # llm: mistral/mistral-large-latest + # llm: ollama/llama3:70b + # llm: groq/llama-3.2-90b-vision-preview + # llm: watsonx/meta-llama/llama-3-1-70b-instruct + # ... +``` + +Keep in mind that you will need to set certain ENV vars depending on the model you are +using to account for the credentials or set a custom LLM object like described below. +Here are some of the required ENV vars for some of the LLM integrations: + + + + ```python Code + OPENAI_API_KEY= + OPENAI_API_BASE= + OPENAI_MODEL_NAME= + OPENAI_ORGANIZATION= # OPTIONAL + OPENAI_API_BASE= # OPTIONAL + ``` + + + + ```python Code + ANTHROPIC_API_KEY= + ``` + + + + ```python Code + GEMINI_API_KEY= + ``` + + + + ```python Code + AZURE_API_KEY= # "my-azure-api-key" + AZURE_API_BASE= # "https://example-endpoint.openai.azure.com" + AZURE_API_VERSION= # "2023-05-15" + AZURE_AD_TOKEN= # Optional + AZURE_API_TYPE= # Optional + ``` + + + + ```python Code + AWS_ACCESS_KEY_ID= + AWS_SECRET_ACCESS_KEY= + AWS_DEFAULT_REGION= + ``` + + + + ```python Code + MISTRAL_API_KEY= + ``` + + + + ```python Code + GROQ_API_KEY= + ``` + + + + ```python Code + WATSONX_URL= # (required) Base URL of your WatsonX instance + WATSONX_APIKEY= # (required) IBM cloud API key + WATSONX_TOKEN= # (required) IAM auth token (alternative to APIKEY) + WATSONX_PROJECT_ID= # (optional) Project ID of your WatsonX instance + WATSONX_DEPLOYMENT_SPACE_ID= # (optional) ID of deployment space for deployed models + ``` + + + +### 3. Custom LLM Objects Pass a custom LLM implementation or object from another library. @@ -102,7 +195,7 @@ When configuring an LLM for your agent, you have access to a wide range of param These are examples of how to configure LLMs for your agent. - + ```python Code @@ -133,10 +226,10 @@ These are examples of how to configure LLMs for your agent. model="cerebras/llama-3.1-70b", api_key="your-api-key-here" ) - agent = Agent(llm=llm, ...) + agent = Agent(llm=llm, ...) ``` - + CrewAI supports using Ollama for running open-source models locally: @@ -150,7 +243,7 @@ These are examples of how to configure LLMs for your agent. agent = Agent( llm=LLM( - model="ollama/llama3.1", + model="ollama/llama3.1", base_url="http://localhost:11434" ), ... @@ -164,7 +257,7 @@ These are examples of how to configure LLMs for your agent. from crewai import LLM llm = LLM( - model="groq/llama3-8b-8192", + model="groq/llama3-8b-8192", api_key="your-api-key-here" ) agent = Agent(llm=llm, ...) @@ -189,7 +282,7 @@ These are examples of how to configure LLMs for your agent. from crewai import LLM llm = LLM( - model="fireworks_ai/accounts/fireworks/models/llama-v3-70b-instruct", + model="fireworks_ai/accounts/fireworks/models/llama-v3-70b-instruct", api_key="your-api-key-here" ) agent = Agent(llm=llm, ...) @@ -224,6 +317,29 @@ These are examples of how to configure LLMs for your agent. + You can use IBM Watson by seeting the following ENV vars: + + ```python Code + WATSONX_URL= + WATSONX_APIKEY= + WATSONX_PROJECT_ID= + ``` + + You can then define your agents llms by updating the `agents.yml` + + ```yaml Code + researcher: + role: Research Specialist + goal: Conduct comprehensive research and analysis to gather relevant information, + synthesize findings, and produce well-documented insights. + backstory: A dedicated research professional with years of experience in academic + investigation, literature review, and data analysis, known for thorough and + methodical approaches to complex research questions. + verbose: true + llm: watsonx/meta-llama/llama-3-1-70b-instruct + ``` + + You can also set up agents more dynamically as a base level LLM instance, like bellow: ```python Code from crewai import LLM @@ -247,7 +363,7 @@ These are examples of how to configure LLMs for your agent. api_key="your-api-key-here", base_url="your_api_endpoint" ) - agent = Agent(llm=llm, ...) + agent = Agent(llm=llm, ...) ``` From 50bf146d1e3319c84d342d42f9f914e8744fdcba Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Moura?= Date: Sun, 10 Nov 2024 20:47:56 -0300 Subject: [PATCH 049/126] preparing new version --- pyproject.toml | 8 ++++---- src/crewai/__init__.py | 2 +- src/crewai/cli/templates/crew/pyproject.toml | 2 +- src/crewai/cli/templates/flow/pyproject.toml | 2 +- src/crewai/cli/templates/pipeline/pyproject.toml | 2 +- .../cli/templates/pipeline_router/pyproject.toml | 2 +- src/crewai/cli/templates/tool/pyproject.toml | 2 +- uv.lock | 14 +++++++------- 8 files changed, 17 insertions(+), 17 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 7102c9efa..d57811f32 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "crewai" -version = "0.79.0" +version = "0.79.1" description = "Cutting-edge framework for orchestrating role-playing, autonomous AI agents. By fostering collaborative intelligence, CrewAI empowers agents to work together seamlessly, tackling complex tasks." readme = "README.md" requires-python = ">=3.10,<=3.13" @@ -16,7 +16,7 @@ dependencies = [ "opentelemetry-exporter-otlp-proto-http>=1.22.0", "instructor>=1.3.3", "regex>=2024.9.11", - "crewai-tools>=0.13.4", + "crewai-tools>=0.14.0", "click>=8.1.7", "python-dotenv>=1.0.0", "appdirs>=1.4.4", @@ -37,7 +37,7 @@ Documentation = "https://docs.crewai.com" Repository = "https://github.com/crewAIInc/crewAI" [project.optional-dependencies] -tools = ["crewai-tools>=0.13.4"] +tools = ["crewai-tools>=0.14.0"] agentops = ["agentops>=0.3.0"] [tool.uv] @@ -52,7 +52,7 @@ dev-dependencies = [ "mkdocs-material-extensions>=1.3.1", "pillow>=10.2.0", "cairosvg>=2.7.1", - "crewai-tools>=0.13.4", + "crewai-tools>=0.14.0", "pytest>=8.0.0", "pytest-vcr>=1.0.2", "python-dotenv>=1.0.0", diff --git a/src/crewai/__init__.py b/src/crewai/__init__.py index 353c11b88..1da8e2455 100644 --- a/src/crewai/__init__.py +++ b/src/crewai/__init__.py @@ -14,5 +14,5 @@ warnings.filterwarnings( category=UserWarning, module="pydantic.main", ) -__version__ = "0.79.0" +__version__ = "0.79.1" __all__ = ["Agent", "Crew", "Process", "Task", "Pipeline", "Router", "LLM", "Flow"] diff --git a/src/crewai/cli/templates/crew/pyproject.toml b/src/crewai/cli/templates/crew/pyproject.toml index 136fa2026..4c5a7cb39 100644 --- a/src/crewai/cli/templates/crew/pyproject.toml +++ b/src/crewai/cli/templates/crew/pyproject.toml @@ -5,7 +5,7 @@ description = "{{name}} using crewAI" authors = [{ name = "Your Name", email = "you@example.com" }] requires-python = ">=3.10,<=3.13" dependencies = [ - "crewai[tools]>=0.79.0,<1.0.0" + "crewai[tools]>=0.79.1,<1.0.0" ] [project.scripts] diff --git a/src/crewai/cli/templates/flow/pyproject.toml b/src/crewai/cli/templates/flow/pyproject.toml index 032f7ee9b..a90efe904 100644 --- a/src/crewai/cli/templates/flow/pyproject.toml +++ b/src/crewai/cli/templates/flow/pyproject.toml @@ -5,7 +5,7 @@ description = "{{name}} using crewAI" authors = [{ name = "Your Name", email = "you@example.com" }] requires-python = ">=3.10,<=3.13" dependencies = [ - "crewai[tools]>=0.79.0,<1.0.0", + "crewai[tools]>=0.79.1,<1.0.0", ] [project.scripts] diff --git a/src/crewai/cli/templates/pipeline/pyproject.toml b/src/crewai/cli/templates/pipeline/pyproject.toml index 53a81a620..b049a98f5 100644 --- a/src/crewai/cli/templates/pipeline/pyproject.toml +++ b/src/crewai/cli/templates/pipeline/pyproject.toml @@ -6,7 +6,7 @@ authors = ["Your Name "] [tool.poetry.dependencies] python = ">=3.10,<=3.13" -crewai = { extras = ["tools"], version = ">=0.79.0,<1.0.0" } +crewai = { extras = ["tools"], version = ">=0.79.1,<1.0.0" } asyncio = "*" [tool.poetry.scripts] diff --git a/src/crewai/cli/templates/pipeline_router/pyproject.toml b/src/crewai/cli/templates/pipeline_router/pyproject.toml index 89275861d..ee36d771b 100644 --- a/src/crewai/cli/templates/pipeline_router/pyproject.toml +++ b/src/crewai/cli/templates/pipeline_router/pyproject.toml @@ -5,7 +5,7 @@ description = "{{name}} using crewAI" authors = ["Your Name "] requires-python = ">=3.10,<=3.13" dependencies = [ - "crewai[tools]>=0.79.0,<1.0.0" + "crewai[tools]>=0.79.1,<1.0.0" ] [project.scripts] diff --git a/src/crewai/cli/templates/tool/pyproject.toml b/src/crewai/cli/templates/tool/pyproject.toml index b60edf21b..a60e0b668 100644 --- a/src/crewai/cli/templates/tool/pyproject.toml +++ b/src/crewai/cli/templates/tool/pyproject.toml @@ -5,6 +5,6 @@ description = "Power up your crews with {{folder_name}}" readme = "README.md" requires-python = ">=3.10,<=3.13" dependencies = [ - "crewai[tools]>=0.79.0" + "crewai[tools]>=0.79.1" ] diff --git a/uv.lock b/uv.lock index 0034b42e0..a71556e2d 100644 --- a/uv.lock +++ b/uv.lock @@ -604,7 +604,7 @@ wheels = [ [[package]] name = "crewai" -version = "0.79.0" +version = "0.79.1" source = { editable = "." } dependencies = [ { name = "appdirs" }, @@ -665,8 +665,8 @@ requires-dist = [ { name = "auth0-python", specifier = ">=4.7.1" }, { name = "chromadb", specifier = ">=0.4.24" }, { name = "click", specifier = ">=8.1.7" }, - { name = "crewai-tools", specifier = ">=0.13.4" }, - { name = "crewai-tools", marker = "extra == 'tools'", specifier = ">=0.13.4" }, + { name = "crewai-tools", specifier = ">=0.14.0" }, + { name = "crewai-tools", marker = "extra == 'tools'", specifier = ">=0.14.0" }, { name = "instructor", specifier = ">=1.3.3" }, { name = "json-repair", specifier = ">=0.25.2" }, { name = "jsonref", specifier = ">=1.1.0" }, @@ -688,7 +688,7 @@ requires-dist = [ [package.metadata.requires-dev] dev = [ { name = "cairosvg", specifier = ">=2.7.1" }, - { name = "crewai-tools", specifier = ">=0.13.4" }, + { name = "crewai-tools", specifier = ">=0.14.0" }, { name = "mkdocs", specifier = ">=1.4.3" }, { name = "mkdocs-material", specifier = ">=9.5.7" }, { name = "mkdocs-material-extensions", specifier = ">=1.3.1" }, @@ -707,7 +707,7 @@ dev = [ [[package]] name = "crewai-tools" -version = "0.13.4" +version = "0.14.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "beautifulsoup4" }, @@ -725,9 +725,9 @@ dependencies = [ { name = "requests" }, { name = "selenium" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/64/bd/eff7b633a0b28ff4ed115adde1499e3dcc683e4f0b5c378a4c6f5c0c1bf6/crewai_tools-0.13.4.tar.gz", hash = "sha256:b6ac527633b7018471d892c21ac96bc961a86b6626d996b1ed7d53cd481d4505", size = 816588 } +sdist = { url = "https://files.pythonhosted.org/packages/64/bd/eff7b633a0b28ff4ed115adde1499e3dcc683e4f0b5c378a4c6f5c0c1bf6/crewai_tools-0.14.0.tar.gz", hash = "sha256:b6ac527633b7018471d892c21ac96bc961a86b6626d996b1ed7d53cd481d4505", size = 816588 } wheels = [ - { url = "https://files.pythonhosted.org/packages/6c/40/93cd347d854059cf5e54a81b70f896deea7ad1f03e9c024549eb323c4da5/crewai_tools-0.13.4-py3-none-any.whl", hash = "sha256:eda78fe3c4df57676259d8dd6b2610fa31f89b90909512f15893adb57fb9e825", size = 463703 }, + { url = "https://files.pythonhosted.org/packages/6c/40/93cd347d854059cf5e54a81b70f896deea7ad1f03e9c024549eb323c4da5/crewai_tools-0.14.0-py3-none-any.whl", hash = "sha256:eda78fe3c4df57676259d8dd6b2610fa31f89b90909512f15893adb57fb9e825", size = 463703 }, ] [[package]] From 40a676b7ac8a2d429f99a27917dc25133f44c583 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Moura?= Date: Sun, 10 Nov 2024 21:16:36 -0300 Subject: [PATCH 050/126] curring new version --- pyproject.toml | 2 +- src/crewai/__init__.py | 2 +- src/crewai/cli/templates/crew/pyproject.toml | 2 +- src/crewai/cli/templates/flow/pyproject.toml | 2 +- src/crewai/cli/templates/pipeline/pyproject.toml | 2 +- src/crewai/cli/templates/pipeline_router/pyproject.toml | 2 +- src/crewai/cli/templates/tool/pyproject.toml | 2 +- src/crewai/memory/contextual/contextual_memory.py | 4 ---- uv.lock | 2 +- 9 files changed, 8 insertions(+), 12 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index d57811f32..31853a82d 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "crewai" -version = "0.79.1" +version = "0.79.2" 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" diff --git a/src/crewai/__init__.py b/src/crewai/__init__.py index 1da8e2455..77cbc19f5 100644 --- a/src/crewai/__init__.py +++ b/src/crewai/__init__.py @@ -14,5 +14,5 @@ warnings.filterwarnings( category=UserWarning, module="pydantic.main", ) -__version__ = "0.79.1" +__version__ = "0.79.2" __all__ = ["Agent", "Crew", "Process", "Task", "Pipeline", "Router", "LLM", "Flow"] diff --git a/src/crewai/cli/templates/crew/pyproject.toml b/src/crewai/cli/templates/crew/pyproject.toml index 4c5a7cb39..fea666134 100644 --- a/src/crewai/cli/templates/crew/pyproject.toml +++ b/src/crewai/cli/templates/crew/pyproject.toml @@ -5,7 +5,7 @@ description = "{{name}} using crewAI" authors = [{ name = "Your Name", email = "you@example.com" }] requires-python = ">=3.10,<=3.13" dependencies = [ - "crewai[tools]>=0.79.1,<1.0.0" + "crewai[tools]>=0.79.2,<1.0.0" ] [project.scripts] diff --git a/src/crewai/cli/templates/flow/pyproject.toml b/src/crewai/cli/templates/flow/pyproject.toml index a90efe904..3a708f780 100644 --- a/src/crewai/cli/templates/flow/pyproject.toml +++ b/src/crewai/cli/templates/flow/pyproject.toml @@ -5,7 +5,7 @@ description = "{{name}} using crewAI" authors = [{ name = "Your Name", email = "you@example.com" }] requires-python = ">=3.10,<=3.13" dependencies = [ - "crewai[tools]>=0.79.1,<1.0.0", + "crewai[tools]>=0.79.2,<1.0.0", ] [project.scripts] diff --git a/src/crewai/cli/templates/pipeline/pyproject.toml b/src/crewai/cli/templates/pipeline/pyproject.toml index b049a98f5..616fa0836 100644 --- a/src/crewai/cli/templates/pipeline/pyproject.toml +++ b/src/crewai/cli/templates/pipeline/pyproject.toml @@ -6,7 +6,7 @@ authors = ["Your Name "] [tool.poetry.dependencies] python = ">=3.10,<=3.13" -crewai = { extras = ["tools"], version = ">=0.79.1,<1.0.0" } +crewai = { extras = ["tools"], version = ">=0.79.2,<1.0.0" } asyncio = "*" [tool.poetry.scripts] diff --git a/src/crewai/cli/templates/pipeline_router/pyproject.toml b/src/crewai/cli/templates/pipeline_router/pyproject.toml index ee36d771b..ca8d99629 100644 --- a/src/crewai/cli/templates/pipeline_router/pyproject.toml +++ b/src/crewai/cli/templates/pipeline_router/pyproject.toml @@ -5,7 +5,7 @@ description = "{{name}} using crewAI" authors = ["Your Name "] requires-python = ">=3.10,<=3.13" dependencies = [ - "crewai[tools]>=0.79.1,<1.0.0" + "crewai[tools]>=0.79.2,<1.0.0" ] [project.scripts] diff --git a/src/crewai/cli/templates/tool/pyproject.toml b/src/crewai/cli/templates/tool/pyproject.toml index a60e0b668..8a412ab25 100644 --- a/src/crewai/cli/templates/tool/pyproject.toml +++ b/src/crewai/cli/templates/tool/pyproject.toml @@ -5,6 +5,6 @@ description = "Power up your crews with {{folder_name}}" readme = "README.md" requires-python = ">=3.10,<=3.13" dependencies = [ - "crewai[tools]>=0.79.1" + "crewai[tools]>=0.79.2" ] diff --git a/src/crewai/memory/contextual/contextual_memory.py b/src/crewai/memory/contextual/contextual_memory.py index 3d3a9c6c1..5d91cf47d 100644 --- a/src/crewai/memory/contextual/contextual_memory.py +++ b/src/crewai/memory/contextual/contextual_memory.py @@ -34,7 +34,6 @@ class ContextualMemory: formatted_results = "\n".join( [f"- {result['context']}" for result in stm_results] ) - print("formatted_results stm", formatted_results) return f"Recent Insights:\n{formatted_results}" if stm_results else "" def _fetch_ltm_context(self, task) -> Optional[str]: @@ -54,8 +53,6 @@ class ContextualMemory: formatted_results = list(dict.fromkeys(formatted_results)) formatted_results = "\n".join([f"- {result}" for result in formatted_results]) # type: ignore # Incompatible types in assignment (expression has type "str", variable has type "list[str]") - print("formatted_results ltm", formatted_results) - return f"Historical Data:\n{formatted_results}" if ltm_results else "" def _fetch_entity_context(self, query) -> str: @@ -67,5 +64,4 @@ class ContextualMemory: formatted_results = "\n".join( [f"- {result['context']}" for result in em_results] # type: ignore # Invalid index type "str" for "str"; expected type "SupportsIndex | slice" ) - print("formatted_results em", formatted_results) return f"Entities:\n{formatted_results}" if em_results else "" diff --git a/uv.lock b/uv.lock index a71556e2d..b59d377ee 100644 --- a/uv.lock +++ b/uv.lock @@ -604,7 +604,7 @@ wheels = [ [[package]] name = "crewai" -version = "0.79.1" +version = "0.79.2" source = { editable = "." } dependencies = [ { name = "appdirs" }, From 49220ec163f87aa4a289231a119043409d8d89ad Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Moura?= Date: Sun, 10 Nov 2024 23:46:38 -0300 Subject: [PATCH 051/126] preparing new version --- pyproject.toml | 2 +- src/crewai/agent.py | 75 ++++++++++++++---------- src/crewai/agents/crew_agent_executor.py | 7 ++- uv.lock | 6 +- 4 files changed, 53 insertions(+), 37 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 31853a82d..b9e403140 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "crewai" -version = "0.79.2" +version = "0.79.3" 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" diff --git a/src/crewai/agent.py b/src/crewai/agent.py index d30746808..c8998fd19 100644 --- a/src/crewai/agent.py +++ b/src/crewai/agent.py @@ -123,6 +123,11 @@ class Agent(BaseAgent): @model_validator(mode="after") def post_init_setup(self): self.agent_ops_agent_name = self.role + unnacepted_attributes = [ + "AWS_ACCESS_KEY_ID", + "AWS_SECRET_ACCESS_KEY", + "AWS_REGION_NAME", + ] # Handle different cases for self.llm if isinstance(self.llm, str): @@ -146,39 +151,49 @@ class Agent(BaseAgent): if api_base: llm_params["base_url"] = api_base + set_provider = model_name.split("/")[0] if "/" in model_name else "openai" + # Iterate over all environment variables to find matching API keys or use defaults for provider, env_vars in ENV_VARS.items(): - for env_var in env_vars: - # Check if the environment variable is set - if "key_name" in env_var: - env_value = os.environ.get(env_var["key_name"]) - if env_value: - # Map key names containing "API_KEY" to "api_key" - key_name = ( - "api_key" - if "API_KEY" in env_var["key_name"] - else env_var["key_name"] + if provider == set_provider: + for env_var in env_vars: + if env_var["key_name"] in unnacepted_attributes: + continue + # Check if the environment variable is set + if "key_name" in env_var: + env_value = os.environ.get(env_var["key_name"]) + print( + f"Checking env var {env_var['key_name']}: {env_value}" ) - # Map key names containing "API_BASE" to "api_base" - key_name = ( - "api_base" - if "API_BASE" in env_var["key_name"] - else key_name - ) - # Map key names containing "API_VERSION" to "api_version" - key_name = ( - "api_version" - if "API_VERSION" in env_var["key_name"] - else key_name - ) - llm_params[key_name] = env_value - # Check for default values if the environment variable is not set - elif env_var.get("default", False): - for key, value in env_var.items(): - if key not in ["prompt", "key_name", "default"]: - # Only add default if the key is already set in os.environ - if key in os.environ: - llm_params[key] = value + if env_value: + # Map key names containing "API_KEY" to "api_key" + key_name = ( + "api_key" + if "API_KEY" in env_var["key_name"] + else env_var["key_name"] + ) + # Map key names containing "API_BASE" to "api_base" + key_name = ( + "api_base" + if "API_BASE" in env_var["key_name"] + else key_name + ) + # Map key names containing "API_VERSION" to "api_version" + key_name = ( + "api_version" + if "API_VERSION" in env_var["key_name"] + else key_name + ) + print(f"Mapped key name: {key_name}") + llm_params[key_name] = env_value + # Check for default values if the environment variable is not set + elif env_var.get("default", False): + for key, value in env_var.items(): + if key not in ["prompt", "key_name", "default"]: + # Only add default if the key is already set in os.environ + if key in os.environ: + print(f"Using default value for {key}: {value}") + llm_params[key] = value self.llm = LLM(**llm_params) else: diff --git a/src/crewai/agents/crew_agent_executor.py b/src/crewai/agents/crew_agent_executor.py index 9e9ad9c7e..aa641e061 100644 --- a/src/crewai/agents/crew_agent_executor.py +++ b/src/crewai/agents/crew_agent_executor.py @@ -332,9 +332,9 @@ class CrewAgentExecutor(CrewAgentExecutorMixin): if self.crew is not None and hasattr(self.crew, "_train_iteration"): train_iteration = self.crew._train_iteration if agent_id in training_data and isinstance(train_iteration, int): - training_data[agent_id][train_iteration][ - "improved_output" - ] = result.output + training_data[agent_id][train_iteration]["improved_output"] = ( + result.output + ) training_handler.save(training_data) else: self._logger.log( @@ -385,4 +385,5 @@ class CrewAgentExecutor(CrewAgentExecutorMixin): return CrewAgentParser(agent=self.agent).parse(answer) def _format_msg(self, prompt: str, role: str = "user") -> Dict[str, str]: + prompt = prompt.rstrip() return {"role": role, "content": prompt} diff --git a/uv.lock b/uv.lock index b59d377ee..0b541a3e7 100644 --- a/uv.lock +++ b/uv.lock @@ -604,7 +604,7 @@ wheels = [ [[package]] name = "crewai" -version = "0.79.2" +version = "0.79.3" source = { editable = "." } dependencies = [ { name = "appdirs" }, @@ -725,9 +725,9 @@ dependencies = [ { name = "requests" }, { name = "selenium" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/64/bd/eff7b633a0b28ff4ed115adde1499e3dcc683e4f0b5c378a4c6f5c0c1bf6/crewai_tools-0.14.0.tar.gz", hash = "sha256:b6ac527633b7018471d892c21ac96bc961a86b6626d996b1ed7d53cd481d4505", size = 816588 } +sdist = { url = "https://files.pythonhosted.org/packages/9b/6d/4fa91b481b120f83bb58f365203d8aa8564e8ced1035d79f8aedb7d71e2f/crewai_tools-0.14.0.tar.gz", hash = "sha256:510f3a194bcda4fdae4314bd775521964b5f229ddbe451e5d9e0216cae57f4e3", size = 815892 } wheels = [ - { url = "https://files.pythonhosted.org/packages/6c/40/93cd347d854059cf5e54a81b70f896deea7ad1f03e9c024549eb323c4da5/crewai_tools-0.14.0-py3-none-any.whl", hash = "sha256:eda78fe3c4df57676259d8dd6b2610fa31f89b90909512f15893adb57fb9e825", size = 463703 }, + { url = "https://files.pythonhosted.org/packages/c8/ed/9f4e64e1507062957b0118085332d38b621c1000874baef2d1c4069bfd97/crewai_tools-0.14.0-py3-none-any.whl", hash = "sha256:0a804a828c29869c3af3253f4fc4c3967a3f80f06dab22e9bbe9526608a31564", size = 462980 }, ] [[package]] From 6d677541c738edd10e80dbe8d5d07bb4f2b60d7f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Moura?= Date: Mon, 11 Nov 2024 00:03:52 -0300 Subject: [PATCH 052/126] preparing new version --- pyproject.toml | 2 +- src/crewai/__init__.py | 2 +- src/crewai/cli/templates/crew/pyproject.toml | 2 +- src/crewai/cli/templates/flow/pyproject.toml | 2 +- src/crewai/cli/templates/pipeline/pyproject.toml | 2 +- src/crewai/cli/templates/pipeline_router/pyproject.toml | 2 +- src/crewai/cli/templates/tool/pyproject.toml | 2 +- uv.lock | 2 +- 8 files changed, 8 insertions(+), 8 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index b9e403140..8a5d2f1b9 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "crewai" -version = "0.79.3" +version = "0.79.4" 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" diff --git a/src/crewai/__init__.py b/src/crewai/__init__.py index 77cbc19f5..8d01b7cb5 100644 --- a/src/crewai/__init__.py +++ b/src/crewai/__init__.py @@ -14,5 +14,5 @@ warnings.filterwarnings( category=UserWarning, module="pydantic.main", ) -__version__ = "0.79.2" +__version__ = "0.79.4" __all__ = ["Agent", "Crew", "Process", "Task", "Pipeline", "Router", "LLM", "Flow"] diff --git a/src/crewai/cli/templates/crew/pyproject.toml b/src/crewai/cli/templates/crew/pyproject.toml index fea666134..8606a2134 100644 --- a/src/crewai/cli/templates/crew/pyproject.toml +++ b/src/crewai/cli/templates/crew/pyproject.toml @@ -5,7 +5,7 @@ description = "{{name}} using crewAI" authors = [{ name = "Your Name", email = "you@example.com" }] requires-python = ">=3.10,<=3.13" dependencies = [ - "crewai[tools]>=0.79.2,<1.0.0" + "crewai[tools]>=0.79.4,<1.0.0" ] [project.scripts] diff --git a/src/crewai/cli/templates/flow/pyproject.toml b/src/crewai/cli/templates/flow/pyproject.toml index 3a708f780..69e26f220 100644 --- a/src/crewai/cli/templates/flow/pyproject.toml +++ b/src/crewai/cli/templates/flow/pyproject.toml @@ -5,7 +5,7 @@ description = "{{name}} using crewAI" authors = [{ name = "Your Name", email = "you@example.com" }] requires-python = ">=3.10,<=3.13" dependencies = [ - "crewai[tools]>=0.79.2,<1.0.0", + "crewai[tools]>=0.79.4,<1.0.0", ] [project.scripts] diff --git a/src/crewai/cli/templates/pipeline/pyproject.toml b/src/crewai/cli/templates/pipeline/pyproject.toml index 616fa0836..b09ce842f 100644 --- a/src/crewai/cli/templates/pipeline/pyproject.toml +++ b/src/crewai/cli/templates/pipeline/pyproject.toml @@ -6,7 +6,7 @@ authors = ["Your Name "] [tool.poetry.dependencies] python = ">=3.10,<=3.13" -crewai = { extras = ["tools"], version = ">=0.79.2,<1.0.0" } +crewai = { extras = ["tools"], version = ">=0.79.4,<1.0.0" } asyncio = "*" [tool.poetry.scripts] diff --git a/src/crewai/cli/templates/pipeline_router/pyproject.toml b/src/crewai/cli/templates/pipeline_router/pyproject.toml index ca8d99629..16d6360db 100644 --- a/src/crewai/cli/templates/pipeline_router/pyproject.toml +++ b/src/crewai/cli/templates/pipeline_router/pyproject.toml @@ -5,7 +5,7 @@ description = "{{name}} using crewAI" authors = ["Your Name "] requires-python = ">=3.10,<=3.13" dependencies = [ - "crewai[tools]>=0.79.2,<1.0.0" + "crewai[tools]>=0.79.4,<1.0.0" ] [project.scripts] diff --git a/src/crewai/cli/templates/tool/pyproject.toml b/src/crewai/cli/templates/tool/pyproject.toml index 8a412ab25..dc928ad1e 100644 --- a/src/crewai/cli/templates/tool/pyproject.toml +++ b/src/crewai/cli/templates/tool/pyproject.toml @@ -5,6 +5,6 @@ description = "Power up your crews with {{folder_name}}" readme = "README.md" requires-python = ">=3.10,<=3.13" dependencies = [ - "crewai[tools]>=0.79.2" + "crewai[tools]>=0.79.4" ] diff --git a/uv.lock b/uv.lock index 0b541a3e7..844e0b862 100644 --- a/uv.lock +++ b/uv.lock @@ -604,7 +604,7 @@ wheels = [ [[package]] name = "crewai" -version = "0.79.3" +version = "0.79.4" source = { editable = "." } dependencies = [ { name = "appdirs" }, From 8610faef22024fb24486851402ae6ee7df95625c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Moura?= Date: Mon, 11 Nov 2024 02:29:40 -0300 Subject: [PATCH 053/126] add missing init --- src/crewai/tools/cache_tools/__init__.py | 0 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 src/crewai/tools/cache_tools/__init__.py diff --git a/src/crewai/tools/cache_tools/__init__.py b/src/crewai/tools/cache_tools/__init__.py new file mode 100644 index 000000000..e69de29bb From 4afb022572e2f48ee5379aaa591ead833ec0959b Mon Sep 17 00:00:00 2001 From: Thiago Moretto Date: Tue, 12 Nov 2024 15:04:57 -0300 Subject: [PATCH 054/126] fix LiteLLM callback replacement --- src/crewai/llm.py | 16 +- .../test_llm_callback_replacement.yaml | 205 ++++++++++++++++++ tests/llm_test.py | 30 +++ 3 files changed, 249 insertions(+), 2 deletions(-) create mode 100644 tests/cassettes/test_llm_callback_replacement.yaml create mode 100644 tests/llm_test.py diff --git a/src/crewai/llm.py b/src/crewai/llm.py index 577cb6a43..7ea8155a6 100644 --- a/src/crewai/llm.py +++ b/src/crewai/llm.py @@ -118,12 +118,12 @@ class LLM: litellm.drop_params = True litellm.set_verbose = False - litellm.callbacks = callbacks + self.set_callbacks(callbacks) def call(self, messages: List[Dict[str, str]], callbacks: List[Any] = []) -> str: with suppress_warnings(): if callbacks and len(callbacks) > 0: - litellm.callbacks = callbacks + self.set_callbacks(callbacks) try: params = { @@ -181,3 +181,15 @@ class LLM: def get_context_window_size(self) -> int: # Only using 75% of the context window size to avoid cutting the message in the middle return int(LLM_CONTEXT_WINDOW_SIZES.get(self.model, 8192) * 0.75) + + def set_callbacks(self, callbacks: List[Any]): + callback_types = [type(callback) for callback in callbacks] + for callback in litellm.success_callback[:]: + if type(callback) in callback_types: + litellm.success_callback.remove(callback) + + for callback in litellm._async_success_callback[:]: + if type(callback) in callback_types: + litellm._async_success_callback.remove(callback) + + litellm.callbacks = callbacks diff --git a/tests/cassettes/test_llm_callback_replacement.yaml b/tests/cassettes/test_llm_callback_replacement.yaml new file mode 100644 index 000000000..7b8f7e707 --- /dev/null +++ b/tests/cassettes/test_llm_callback_replacement.yaml @@ -0,0 +1,205 @@ +interactions: +- request: + body: '{"messages": [{"role": "user", "content": "Hello, world!"}], "model": "gpt-4o-mini", + "stream": false}' + headers: + accept: + - application/json + accept-encoding: + - gzip, deflate + connection: + - keep-alive + content-length: + - '101' + content-type: + - application/json + host: + - api.openai.com + user-agent: + - OpenAI/Python 1.52.1 + x-stainless-arch: + - x64 + x-stainless-async: + - 'false' + x-stainless-lang: + - python + x-stainless-os: + - Linux + x-stainless-package-version: + - 1.52.1 + x-stainless-raw-response: + - 'true' + x-stainless-retry-count: + - '0' + x-stainless-runtime: + - CPython + x-stainless-runtime-version: + - 3.11.9 + method: POST + uri: https://api.openai.com/v1/chat/completions + response: + body: + string: !!binary | + H4sIAAAAAAAAA4xSwWrcMBS8+ytedY6LvWvYZi8lpZSkBJLSQiChGK307FUi66nSc9Ml7L8H2e56 + l7bQiw8zb8Yzg14yAGG0WINQW8mq8za/+Oqv5MUmXv+8+/Hl3uO3j59u1efreHO+/PAszpKCNo+o + +LfqraLOW2RDbqRVQMmYXMvVsqyWy1VVDERHGm2StZ7zivLOOJMvikWVF6u8fDept2QURrGGhwwA + 4GX4ppxO4y+xhsFrQDqMUbYo1ocjABHIJkTIGE1k6ViczaQix+iG6JdoLb2BS3oGJR1cwSiAHfXA + pOXu/bEwYNNHmcK73toJ3x+SWGp9oE2c+APeGGfitg4oI7n018jkxcDuM4DvQ+P+pITwgTrPNdMT + umRYlqOdmHeeyfOJY2JpZ3gxjXRqVmtkaWw8GkwoqbaoZ+W8ruy1oSMiO6r8Z5a/eY+1jWv/x34m + lELPqGsfUBt12nc+C5ge4b/ODhMPgUXcRcauboxrMfhgxifQ+LrYyEKXi6opRbbPXgEAAP//AwAM + DMWoEAMAAA== + headers: + CF-Cache-Status: + - DYNAMIC + CF-RAY: + - 8e185b2c1b790303-GRU + Connection: + - keep-alive + Content-Encoding: + - gzip + Content-Type: + - application/json + Date: + - Tue, 12 Nov 2024 17:49:00 GMT + Server: + - cloudflare + Set-Cookie: + - __cf_bm=l.QrRLcNZkML_KSfxjir6YCV35B8GNTitBTNh7cPGc4-1731433740-1.0.1.1-j1ejlmykyoI8yk6i6pQjtPoovGzfxI2f5vG6u0EqodQMjCvhbHfNyN_wmYkeT._BMvFi.zDQ8m_PqEHr8tSdEQ; + path=/; expires=Tue, 12-Nov-24 18:19:00 GMT; domain=.api.openai.com; HttpOnly; + Secure; SameSite=None + - _cfuvid=jcCDyMK__Fd0V5DMeqt9yXdlKc7Hsw87a1K01pZu9l0-1731433740848-0.0.1.1-604800000; + path=/; domain=.api.openai.com; HttpOnly; Secure; SameSite=None + Transfer-Encoding: + - chunked + X-Content-Type-Options: + - nosniff + access-control-expose-headers: + - X-Request-ID + alt-svc: + - h3=":443"; ma=86400 + openai-organization: + - user-tqfegqsiobpvvjmn0giaipdq + openai-processing-ms: + - '322' + openai-version: + - '2020-10-01' + strict-transport-security: + - max-age=31536000; includeSubDomains; preload + x-ratelimit-limit-requests: + - '10000' + x-ratelimit-limit-tokens: + - '200000' + x-ratelimit-remaining-requests: + - '9999' + x-ratelimit-remaining-tokens: + - '199978' + x-ratelimit-reset-requests: + - 8.64s + x-ratelimit-reset-tokens: + - 6ms + x-request-id: + - req_037288753767e763a51a04eae757ca84 + status: + code: 200 + message: OK +- request: + body: '{"messages": [{"role": "user", "content": "Hello, world from another agent!"}], + "model": "gpt-4o-mini", "stream": false}' + headers: + accept: + - application/json + accept-encoding: + - gzip, deflate + connection: + - keep-alive + content-length: + - '120' + content-type: + - application/json + cookie: + - __cf_bm=l.QrRLcNZkML_KSfxjir6YCV35B8GNTitBTNh7cPGc4-1731433740-1.0.1.1-j1ejlmykyoI8yk6i6pQjtPoovGzfxI2f5vG6u0EqodQMjCvhbHfNyN_wmYkeT._BMvFi.zDQ8m_PqEHr8tSdEQ; + _cfuvid=jcCDyMK__Fd0V5DMeqt9yXdlKc7Hsw87a1K01pZu9l0-1731433740848-0.0.1.1-604800000 + host: + - api.openai.com + user-agent: + - OpenAI/Python 1.52.1 + x-stainless-arch: + - x64 + x-stainless-async: + - 'false' + x-stainless-lang: + - python + x-stainless-os: + - Linux + x-stainless-package-version: + - 1.52.1 + x-stainless-raw-response: + - 'true' + x-stainless-retry-count: + - '0' + x-stainless-runtime: + - CPython + x-stainless-runtime-version: + - 3.11.9 + method: POST + uri: https://api.openai.com/v1/chat/completions + response: + body: + string: !!binary | + H4sIAAAAAAAAA4xSy27bMBC86yu2PFuBZAt14UvRU5MA7aVAEKAIBJpcSUwoLkuu6jiB/z3QI5aM + tkAvPMzsDGZ2+ZoACKPFDoRqJKvW2/TLD3+z//oiD8dfL7d339zvW125x9zX90/3mVj1Cto/ouJ3 + 1ZWi1ltkQ26kVUDJ2Lvm201ebDbbIh+IljTaXlZ7TgtKW+NMus7WRZpt0/zTpG7IKIxiBz8TAIDX + 4e1zOo3PYgfZ6h1pMUZZo9idhwBEINsjQsZoIkvHYjWTihyjG6Jfo7X0Ab4bhcAEipxDxXAw3IB0 + xA0GkDU6voJrOoCSDm5gNIUjdcCk5fHz0jxg1UXZF3SdtRN+Oqe1VPtA+zjxZ7wyzsSmDCgjuT5Z + ZPJiYE8JwMOwle6iqPCBWs8l0xO63jAvRjsx32JBfpxIJpZ2xjfTJi/dSo0sjY2LrQolVYN6Vs4n + kJ02tCCSRec/w/zNe+xtXP0/9jOhFHpGXfqA2qjLwvNYwP6n/mvsvOMhsIjHyNiWlXE1Bh/M+E8q + X2Z7mel8XVS5SE7JGwAAAP//AwA/cK4yNQMAAA== + headers: + CF-Cache-Status: + - DYNAMIC + CF-RAY: + - 8e185b31398a0303-GRU + Connection: + - keep-alive + Content-Encoding: + - gzip + Content-Type: + - application/json + Date: + - Tue, 12 Nov 2024 17:49:02 GMT + Server: + - cloudflare + Transfer-Encoding: + - chunked + X-Content-Type-Options: + - nosniff + access-control-expose-headers: + - X-Request-ID + alt-svc: + - h3=":443"; ma=86400 + openai-organization: + - user-tqfegqsiobpvvjmn0giaipdq + openai-processing-ms: + - '889' + openai-version: + - '2020-10-01' + strict-transport-security: + - max-age=31536000; includeSubDomains; preload + x-ratelimit-limit-requests: + - '10000' + x-ratelimit-limit-tokens: + - '200000' + x-ratelimit-remaining-requests: + - '9998' + x-ratelimit-remaining-tokens: + - '199975' + x-ratelimit-reset-requests: + - 16.489s + x-ratelimit-reset-tokens: + - 7ms + x-request-id: + - req_bde3810b36a4859688e53d1df64bdd20 + status: + code: 200 + message: OK +version: 1 diff --git a/tests/llm_test.py b/tests/llm_test.py new file mode 100644 index 000000000..e824d54c9 --- /dev/null +++ b/tests/llm_test.py @@ -0,0 +1,30 @@ +import pytest + +from crewai.agents.agent_builder.utilities.base_token_process import TokenProcess +from crewai.llm import LLM +from crewai.utilities.token_counter_callback import TokenCalcHandler + + +@pytest.mark.vcr(filter_headers=["authorization"]) +def test_llm_callback_replacement(): + llm = LLM(model="gpt-4o-mini") + + calc_handler_1 = TokenCalcHandler(token_cost_process=TokenProcess()) + calc_handler_2 = TokenCalcHandler(token_cost_process=TokenProcess()) + + llm.call( + messages=[{"role": "user", "content": "Hello, world!"}], + callbacks=[calc_handler_1], + ) + usage_metrics_1 = calc_handler_1.token_cost_process.get_summary() + + llm.call( + messages=[{"role": "user", "content": "Hello, world from another agent!"}], + callbacks=[calc_handler_2], + ) + usage_metrics_2 = calc_handler_2.token_cost_process.get_summary() + + # The first handler should not have been updated + assert usage_metrics_1.successful_requests == 1 + assert usage_metrics_2.successful_requests == 1 + assert usage_metrics_1 == calc_handler_1.token_cost_process.get_summary() From c7b9ae02fd48ff0d43852398a11f945a69951fc9 Mon Sep 17 00:00:00 2001 From: Thiago Moretto Date: Tue, 12 Nov 2024 16:43:43 -0300 Subject: [PATCH 055/126] fix test_agent_usage_metrics_are_captured_for_hierarchical_process --- tests/crew_test.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/tests/crew_test.py b/tests/crew_test.py index 24b892271..5f39557e0 100644 --- a/tests/crew_test.py +++ b/tests/crew_test.py @@ -1280,10 +1280,10 @@ def test_agent_usage_metrics_are_captured_for_hierarchical_process(): assert result.raw == "Howdy!" assert result.token_usage == UsageMetrics( - total_tokens=2626, - prompt_tokens=2482, - completion_tokens=144, - successful_requests=5, + total_tokens=1673, + prompt_tokens=1562, + completion_tokens=111, + successful_requests=3, ) From bcfcf88e789e14ca20113b0a3268a2cecb380836 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Moura?= Date: Tue, 12 Nov 2024 18:37:50 -0300 Subject: [PATCH 056/126] removing prints --- src/crewai/agent.py | 5 ----- src/crewai/cli/run_crew.py | 1 - 2 files changed, 6 deletions(-) diff --git a/src/crewai/agent.py b/src/crewai/agent.py index c8998fd19..fc43137a2 100644 --- a/src/crewai/agent.py +++ b/src/crewai/agent.py @@ -162,9 +162,6 @@ class Agent(BaseAgent): # Check if the environment variable is set if "key_name" in env_var: env_value = os.environ.get(env_var["key_name"]) - print( - f"Checking env var {env_var['key_name']}: {env_value}" - ) if env_value: # Map key names containing "API_KEY" to "api_key" key_name = ( @@ -184,7 +181,6 @@ class Agent(BaseAgent): if "API_VERSION" in env_var["key_name"] else key_name ) - print(f"Mapped key name: {key_name}") llm_params[key_name] = env_value # Check for default values if the environment variable is not set elif env_var.get("default", False): @@ -192,7 +188,6 @@ class Agent(BaseAgent): if key not in ["prompt", "key_name", "default"]: # Only add default if the key is already set in os.environ if key in os.environ: - print(f"Using default value for {key}: {value}") llm_params[key] = value self.llm = LLM(**llm_params) diff --git a/src/crewai/cli/run_crew.py b/src/crewai/cli/run_crew.py index 20d6aed01..5450cf32b 100644 --- a/src/crewai/cli/run_crew.py +++ b/src/crewai/cli/run_crew.py @@ -24,7 +24,6 @@ def run_crew() -> None: f"Please run `crewai update` to update your pyproject.toml to use uv.", fg="red", ) - print() try: subprocess.run(command, capture_output=False, text=True, check=True) From b98f8f9fe1f79d9d380f3bd8ddb6bc800cf59d0e Mon Sep 17 00:00:00 2001 From: Eduardo Chiarotti Date: Wed, 13 Nov 2024 10:07:28 -0300 Subject: [PATCH 057/126] fix: Step callback issue (#1595) * fix: Step callback issue * fix: Add empty thought since its required --- src/crewai/agents/crew_agent_executor.py | 37 ++++++++++++------------ 1 file changed, 19 insertions(+), 18 deletions(-) diff --git a/src/crewai/agents/crew_agent_executor.py b/src/crewai/agents/crew_agent_executor.py index aa641e061..bf14e6915 100644 --- a/src/crewai/agents/crew_agent_executor.py +++ b/src/crewai/agents/crew_agent_executor.py @@ -145,25 +145,26 @@ class CrewAgentExecutor(CrewAgentExecutorMixin): formatted_answer.result = action_result self._show_logs(formatted_answer) - if self.step_callback: - self.step_callback(formatted_answer) + if self.step_callback: + self.step_callback(formatted_answer) - if self._should_force_answer(): - if self.have_forced_answer: - return AgentFinish( - output=self._i18n.errors( - "force_final_answer_error" - ).format(formatted_answer.text), - text=formatted_answer.text, - ) - else: - formatted_answer.text += ( - f'\n{self._i18n.errors("force_final_answer")}' - ) - self.have_forced_answer = True - self.messages.append( - self._format_msg(formatted_answer.text, role="assistant") - ) + if self._should_force_answer(): + if self.have_forced_answer: + return AgentFinish( + thought="", + output=self._i18n.errors( + "force_final_answer_error" + ).format(formatted_answer.text), + text=formatted_answer.text, + ) + else: + formatted_answer.text += ( + f'\n{self._i18n.errors("force_final_answer")}' + ) + self.have_forced_answer = True + self.messages.append( + self._format_msg(formatted_answer.text, role="assistant") + ) except OutputParserException as e: self.messages.append({"role": "user", "content": e.error}) From 36aa4bcb4615bc5da33d65bd24dbceb80291ce51 Mon Sep 17 00:00:00 2001 From: Thiago Moretto Date: Wed, 13 Nov 2024 10:16:30 -0300 Subject: [PATCH 058/126] Cached prompt tokens on usage metrics --- .../utilities/base_token_process.py | 6 + src/crewai/types/usage_metrics.py | 5 + .../utilities/token_counter_callback.py | 13 +- .../test_crew_kickoff_usage_metrics.yaml | 206 ++++++++++-------- tests/crew_test.py | 2 + 5 files changed, 137 insertions(+), 95 deletions(-) diff --git a/src/crewai/agents/agent_builder/utilities/base_token_process.py b/src/crewai/agents/agent_builder/utilities/base_token_process.py index e971d018e..91df8140e 100644 --- a/src/crewai/agents/agent_builder/utilities/base_token_process.py +++ b/src/crewai/agents/agent_builder/utilities/base_token_process.py @@ -4,6 +4,7 @@ from crewai.types.usage_metrics import UsageMetrics class TokenProcess: total_tokens: int = 0 prompt_tokens: int = 0 + cached_prompt_tokens: int = 0 completion_tokens: int = 0 successful_requests: int = 0 @@ -15,6 +16,10 @@ class TokenProcess: self.completion_tokens = self.completion_tokens + tokens self.total_tokens = self.total_tokens + tokens + def sum_cached_prompt_tokens(self, tokens: int): + self.cached_prompt_tokens = self.cached_prompt_tokens + tokens + self.total_tokens = self.total_tokens + tokens + def sum_successful_requests(self, requests: int): self.successful_requests = self.successful_requests + requests @@ -22,6 +27,7 @@ class TokenProcess: return UsageMetrics( total_tokens=self.total_tokens, prompt_tokens=self.prompt_tokens, + cached_prompt_tokens=self.cached_prompt_tokens, completion_tokens=self.completion_tokens, successful_requests=self.successful_requests, ) diff --git a/src/crewai/types/usage_metrics.py b/src/crewai/types/usage_metrics.py index a5cee6a0f..e87a79e33 100644 --- a/src/crewai/types/usage_metrics.py +++ b/src/crewai/types/usage_metrics.py @@ -8,6 +8,7 @@ class UsageMetrics(BaseModel): Attributes: total_tokens: Total number of tokens used. prompt_tokens: Number of tokens used in prompts. + cached_prompt_tokens: Number of cached prompt tokens used. completion_tokens: Number of tokens used in completions. successful_requests: Number of successful requests made. """ @@ -16,6 +17,9 @@ class UsageMetrics(BaseModel): prompt_tokens: int = Field( default=0, description="Number of tokens used in prompts." ) + cached_prompt_tokens: int = Field( + default=0, description="Number of cached prompt tokens used." + ) completion_tokens: int = Field( default=0, description="Number of tokens used in completions." ) @@ -32,5 +36,6 @@ class UsageMetrics(BaseModel): """ self.total_tokens += usage_metrics.total_tokens self.prompt_tokens += usage_metrics.prompt_tokens + self.cached_prompt_tokens += usage_metrics.cached_prompt_tokens self.completion_tokens += usage_metrics.completion_tokens self.successful_requests += usage_metrics.successful_requests diff --git a/src/crewai/utilities/token_counter_callback.py b/src/crewai/utilities/token_counter_callback.py index 1b6215232..feaaa4c22 100644 --- a/src/crewai/utilities/token_counter_callback.py +++ b/src/crewai/utilities/token_counter_callback.py @@ -1,5 +1,5 @@ from litellm.integrations.custom_logger import CustomLogger - +from litellm.types.utils import Usage from crewai.agents.agent_builder.utilities.base_token_process import TokenProcess @@ -11,8 +11,11 @@ class TokenCalcHandler(CustomLogger): if self.token_cost_process is None: return + usage : Usage = response_obj["usage"] self.token_cost_process.sum_successful_requests(1) - self.token_cost_process.sum_prompt_tokens(response_obj["usage"].prompt_tokens) - self.token_cost_process.sum_completion_tokens( - response_obj["usage"].completion_tokens - ) + self.token_cost_process.sum_prompt_tokens(usage.prompt_tokens) + self.token_cost_process.sum_completion_tokens(usage.completion_tokens) + if usage.prompt_tokens_details: + self.token_cost_process.sum_cached_prompt_tokens( + usage.prompt_tokens_details.cached_tokens + ) diff --git a/tests/cassettes/test_crew_kickoff_usage_metrics.yaml b/tests/cassettes/test_crew_kickoff_usage_metrics.yaml index cc0863ee4..1851d0901 100644 --- a/tests/cassettes/test_crew_kickoff_usage_metrics.yaml +++ b/tests/cassettes/test_crew_kickoff_usage_metrics.yaml @@ -10,7 +10,8 @@ interactions: criteria for your final answer: 1 bullet point about dog that''s under 15 words.\nyou MUST return the actual complete content as the final answer, not a summary.\n\nBegin! This is VERY important to you, use the tools available and give your best Final - Answer, your job depends on it!\n\nThought:"}], "model": "gpt-4o"}' + Answer, your job depends on it!\n\nThought:"}], "model": "gpt-4o-mini", "stop": + ["\nObservation:"], "stream": false}' headers: accept: - application/json @@ -19,49 +20,50 @@ interactions: connection: - keep-alive content-length: - - '869' + - '919' content-type: - application/json - cookie: - - __cf_bm=9.8sBYBkvBR8R1K_bVF7xgU..80XKlEIg3N2OBbTSCU-1727214102-1.0.1.1-.qiTLXbPamYUMSuyNsOEB9jhGu.jOifujOrx9E2JZvStbIZ9RTIiE44xKKNfLPxQkOi6qAT3h6htK8lPDGV_5g; - _cfuvid=lbRdAddVWV6W3f5Dm9SaOPWDUOxqtZBSPr_fTW26nEA-1727213194587-0.0.1.1-604800000 host: - api.openai.com user-agent: - - OpenAI/Python 1.47.0 + - OpenAI/Python 1.52.1 x-stainless-arch: - - arm64 + - x64 x-stainless-async: - 'false' x-stainless-lang: - python x-stainless-os: - - MacOS + - Linux x-stainless-package-version: - - 1.47.0 + - 1.52.1 x-stainless-raw-response: - 'true' + x-stainless-retry-count: + - '0' x-stainless-runtime: - CPython x-stainless-runtime-version: - - 3.11.7 + - 3.11.9 method: POST uri: https://api.openai.com/v1/chat/completions response: - content: "{\n \"id\": \"chatcmpl-AB7auGDrAVE0iXSBBhySZp3xE8gvP\",\n \"object\": - \"chat.completion\",\n \"created\": 1727214164,\n \"model\": \"gpt-4o-2024-05-13\",\n - \ \"choices\": [\n {\n \"index\": 0,\n \"message\": {\n \"role\": - \"assistant\",\n \"content\": \"I now can give a great answer\\nFinal - Answer: Dogs are unparalleled in loyalty and companionship to humans.\",\n \"refusal\": - null\n },\n \"logprobs\": null,\n \"finish_reason\": \"stop\"\n - \ }\n ],\n \"usage\": {\n \"prompt_tokens\": 175,\n \"completion_tokens\": - 21,\n \"total_tokens\": 196,\n \"completion_tokens_details\": {\n \"reasoning_tokens\": - 0\n }\n },\n \"system_fingerprint\": \"fp_e375328146\"\n}\n" + body: + string: !!binary | + H4sIAAAAAAAAA4xSy27bMBC86ysWPEuB7ciV7VuAIkUObQ+59QFhTa0kttQuS9Jx08D/XkhyLAVJ + gV4EaGdnMLPDpwRAmUrtQOkWo+6czW7u41q2t3+cvCvuPvxafSG+58XHTzXlxWeV9gzZ/yAdn1lX + WjpnKRrhEdaeMFKvuiyul3m+uV7lA9BJRbanNS5muWSdYZOtFqs8WxTZcnNmt2I0BbWDrwkAwNPw + 7X1yRb/VDhbp86SjELAhtbssASgvtp8oDMGEiBxVOoFaOBIP1u+A5QgaGRrzQIDQ9LYBORzJA3zj + W8No4Wb438F7aQKgJ7DyiBb6zMhGOKRA3CJrww10xBEttIQ2toBcgTyQR2vhSNZmezLcXM39eKoP + Afub8MHa8/x0CWilcV724Yxf5rVhE9rSEwbhPkyI4tSAnhKA78MhDy9uo5yXzsUyyk/iMHSzHvXU + 1N+Ejo0BqCgR7Yy13aZv6JUVRTQ2zKpQGnVL1USdesNDZWQGJLPUr928pT0mN9z8j/wEaE0uUlU6 + T5XRLxNPa5765/2vtcuVB8MqPIZIXVkbbsg7b8bHVbuyXm9xs8xXRa2SU/IXAAD//wMAq2ZCBWoD + AAA= headers: CF-Cache-Status: - DYNAMIC CF-RAY: - - 8c85f22ddda01cf3-GRU + - 8e19bf36db158761-GRU Connection: - keep-alive Content-Encoding: @@ -69,19 +71,27 @@ interactions: Content-Type: - application/json Date: - - Tue, 24 Sep 2024 21:42:44 GMT + - Tue, 12 Nov 2024 21:52:04 GMT Server: - cloudflare + Set-Cookie: + - __cf_bm=MkvcnvacGpTyn.y0OkFRoFXuAwg4oxjMhViZJTt9mw0-1731448324-1.0.1.1-oekkH_B0xOoPnIFw15LpqFCkZ2cu7VBTJVLDGylan4I67NjX.tlPvOiX9kvtP5Acewi28IE2IwlwtrZWzCH3vw; + path=/; expires=Tue, 12-Nov-24 22:22:04 GMT; domain=.api.openai.com; HttpOnly; + Secure; SameSite=None + - _cfuvid=4.17346mfw5npZfYNbCx3Vj1VAVPy.tH0Jm2gkTteJ8-1731448324998-0.0.1.1-604800000; + path=/; domain=.api.openai.com; HttpOnly; Secure; SameSite=None Transfer-Encoding: - chunked X-Content-Type-Options: - nosniff access-control-expose-headers: - X-Request-ID + alt-svc: + - h3=":443"; ma=86400 openai-organization: - - crewai-iuxna1 + - user-tqfegqsiobpvvjmn0giaipdq openai-processing-ms: - - '349' + - '601' openai-version: - '2020-10-01' strict-transport-security: @@ -89,19 +99,20 @@ interactions: x-ratelimit-limit-requests: - '10000' x-ratelimit-limit-tokens: - - '30000000' + - '200000' x-ratelimit-remaining-requests: - '9999' x-ratelimit-remaining-tokens: - - '29999792' + - '199793' x-ratelimit-reset-requests: - - 6ms + - 8.64s x-ratelimit-reset-tokens: - - 0s + - 62ms x-request-id: - - req_4c8cd76fdfba7b65e5ce85397b33c22b - http_version: HTTP/1.1 - status_code: 200 + - req_77fb166b4e272bfd45c37c08d2b93b0c + status: + code: 200 + message: OK - request: body: '{"messages": [{"role": "system", "content": "You are cat Researcher. You have a lot of experience with cat.\nYour personal goal is: Express hot takes @@ -113,7 +124,8 @@ interactions: criteria for your final answer: 1 bullet point about cat that''s under 15 words.\nyou MUST return the actual complete content as the final answer, not a summary.\n\nBegin! This is VERY important to you, use the tools available and give your best Final - Answer, your job depends on it!\n\nThought:"}], "model": "gpt-4o"}' + Answer, your job depends on it!\n\nThought:"}], "model": "gpt-4o-mini", "stop": + ["\nObservation:"], "stream": false}' headers: accept: - application/json @@ -122,49 +134,53 @@ interactions: connection: - keep-alive content-length: - - '869' + - '919' content-type: - application/json cookie: - - __cf_bm=9.8sBYBkvBR8R1K_bVF7xgU..80XKlEIg3N2OBbTSCU-1727214102-1.0.1.1-.qiTLXbPamYUMSuyNsOEB9jhGu.jOifujOrx9E2JZvStbIZ9RTIiE44xKKNfLPxQkOi6qAT3h6htK8lPDGV_5g; - _cfuvid=lbRdAddVWV6W3f5Dm9SaOPWDUOxqtZBSPr_fTW26nEA-1727213194587-0.0.1.1-604800000 + - __cf_bm=MkvcnvacGpTyn.y0OkFRoFXuAwg4oxjMhViZJTt9mw0-1731448324-1.0.1.1-oekkH_B0xOoPnIFw15LpqFCkZ2cu7VBTJVLDGylan4I67NjX.tlPvOiX9kvtP5Acewi28IE2IwlwtrZWzCH3vw; + _cfuvid=4.17346mfw5npZfYNbCx3Vj1VAVPy.tH0Jm2gkTteJ8-1731448324998-0.0.1.1-604800000 host: - api.openai.com user-agent: - - OpenAI/Python 1.47.0 + - OpenAI/Python 1.52.1 x-stainless-arch: - - arm64 + - x64 x-stainless-async: - 'false' x-stainless-lang: - python x-stainless-os: - - MacOS + - Linux x-stainless-package-version: - - 1.47.0 + - 1.52.1 x-stainless-raw-response: - 'true' + x-stainless-retry-count: + - '0' x-stainless-runtime: - CPython x-stainless-runtime-version: - - 3.11.7 + - 3.11.9 method: POST uri: https://api.openai.com/v1/chat/completions response: - content: "{\n \"id\": \"chatcmpl-AB7auNbAqjT3rgBX92rhxBLuhaLBj\",\n \"object\": - \"chat.completion\",\n \"created\": 1727214164,\n \"model\": \"gpt-4o-2024-05-13\",\n - \ \"choices\": [\n {\n \"index\": 0,\n \"message\": {\n \"role\": - \"assistant\",\n \"content\": \"Thought: I now can give a great answer\\nFinal - Answer: Cats are highly independent, agile, and intuitive creatures beloved - by millions worldwide.\",\n \"refusal\": null\n },\n \"logprobs\": - null,\n \"finish_reason\": \"stop\"\n }\n ],\n \"usage\": {\n \"prompt_tokens\": - 175,\n \"completion_tokens\": 28,\n \"total_tokens\": 203,\n \"completion_tokens_details\": - {\n \"reasoning_tokens\": 0\n }\n },\n \"system_fingerprint\": \"fp_e375328146\"\n}\n" + body: + string: !!binary | + H4sIAAAAAAAAA4xSy27bMBC86ysWPFuB7MhN6ltQIGmBnlL00BcEmlxJ21JLhlzFLQL/eyH5IRlt + gV4EaGZnMLPLlwxAkVUbUKbVYrrg8rsPsg4P+Orxs9XvPz0U8eP966dS6sdo3wa1GBR++x2NnFRX + xnfBoZDnA20iasHBdXlzvSzL2+vVeiQ6b9ENsiZIXvq8I6Z8VazKvLjJl7dHdevJYFIb+JIBALyM + 3yEnW/ypNlAsTkiHKekG1eY8BKCidwOidEqURLOoxUQaz4I8Rn8H7HdgNENDzwgamiE2aE47jABf + +Z5YO7gb/zfwRksCHRGGGAHZIg/D1GmXFiBtpGfiBjyDtEgR/I5BMHYJNFvomZ56hIAxedaOhDBd + zYNFrPukh+Vw79wR35+bOt+E6LfpyJ/xmphSW0XUyfPQKokPamT3GcC3caP9xZJUiL4LUon/gZzG + I60Pfmo65MSuTqR40W6GF8c7XPpVFkWTS7ObKKNNi3aSTgfUvSU/I7JZ6z/T/M370Jy4+R/7iTAG + g6CtQkRL5rLxNBZxeOf/GjtveQys0q8k2FU1cYMxRDq8sjpUxVYXdrkq66XK9tlvAAAA//8DAIjK + KzJzAwAA headers: CF-Cache-Status: - DYNAMIC CF-RAY: - - 8c85f2321c1c1cf3-GRU + - 8e19bf3fae118761-GRU Connection: - keep-alive Content-Encoding: @@ -172,7 +188,7 @@ interactions: Content-Type: - application/json Date: - - Tue, 24 Sep 2024 21:42:45 GMT + - Tue, 12 Nov 2024 21:52:05 GMT Server: - cloudflare Transfer-Encoding: @@ -181,10 +197,12 @@ interactions: - nosniff access-control-expose-headers: - X-Request-ID + alt-svc: + - h3=":443"; ma=86400 openai-organization: - - crewai-iuxna1 + - user-tqfegqsiobpvvjmn0giaipdq openai-processing-ms: - - '430' + - '464' openai-version: - '2020-10-01' strict-transport-security: @@ -192,19 +210,20 @@ interactions: x-ratelimit-limit-requests: - '10000' x-ratelimit-limit-tokens: - - '30000000' + - '200000' x-ratelimit-remaining-requests: - - '9999' + - '9998' x-ratelimit-remaining-tokens: - - '29999792' + - '199792' x-ratelimit-reset-requests: - - 6ms + - 16.369s x-ratelimit-reset-tokens: - - 0s + - 62ms x-request-id: - - req_ace859b7d9e83d9fa7753ce23bb03716 - http_version: HTTP/1.1 - status_code: 200 + - req_91706b23d0ef23458ba63ec18304cd28 + status: + code: 200 + message: OK - request: body: '{"messages": [{"role": "system", "content": "You are apple Researcher. You have a lot of experience with apple.\nYour personal goal is: Express hot @@ -217,7 +236,7 @@ interactions: under 15 words.\nyou MUST return the actual complete content as the final answer, not a summary.\n\nBegin! This is VERY important to you, use the tools available and give your best Final Answer, your job depends on it!\n\nThought:"}], "model": - "gpt-4o"}' + "gpt-4o-mini", "stop": ["\nObservation:"], "stream": false}' headers: accept: - application/json @@ -226,49 +245,53 @@ interactions: connection: - keep-alive content-length: - - '879' + - '929' content-type: - application/json cookie: - - __cf_bm=9.8sBYBkvBR8R1K_bVF7xgU..80XKlEIg3N2OBbTSCU-1727214102-1.0.1.1-.qiTLXbPamYUMSuyNsOEB9jhGu.jOifujOrx9E2JZvStbIZ9RTIiE44xKKNfLPxQkOi6qAT3h6htK8lPDGV_5g; - _cfuvid=lbRdAddVWV6W3f5Dm9SaOPWDUOxqtZBSPr_fTW26nEA-1727213194587-0.0.1.1-604800000 + - __cf_bm=MkvcnvacGpTyn.y0OkFRoFXuAwg4oxjMhViZJTt9mw0-1731448324-1.0.1.1-oekkH_B0xOoPnIFw15LpqFCkZ2cu7VBTJVLDGylan4I67NjX.tlPvOiX9kvtP5Acewi28IE2IwlwtrZWzCH3vw; + _cfuvid=4.17346mfw5npZfYNbCx3Vj1VAVPy.tH0Jm2gkTteJ8-1731448324998-0.0.1.1-604800000 host: - api.openai.com user-agent: - - OpenAI/Python 1.47.0 + - OpenAI/Python 1.52.1 x-stainless-arch: - - arm64 + - x64 x-stainless-async: - 'false' x-stainless-lang: - python x-stainless-os: - - MacOS + - Linux x-stainless-package-version: - - 1.47.0 + - 1.52.1 x-stainless-raw-response: - 'true' + x-stainless-retry-count: + - '0' x-stainless-runtime: - CPython x-stainless-runtime-version: - - 3.11.7 + - 3.11.9 method: POST uri: https://api.openai.com/v1/chat/completions response: - content: "{\n \"id\": \"chatcmpl-AB7avZ0yqY18ukQS7SnLkZydsx72b\",\n \"object\": - \"chat.completion\",\n \"created\": 1727214165,\n \"model\": \"gpt-4o-2024-05-13\",\n - \ \"choices\": [\n {\n \"index\": 0,\n \"message\": {\n \"role\": - \"assistant\",\n \"content\": \"I now can give a great answer.\\n\\nFinal - Answer: Apples are incredibly versatile, nutritious, and a staple in diets globally.\",\n - \ \"refusal\": null\n },\n \"logprobs\": null,\n \"finish_reason\": - \"stop\"\n }\n ],\n \"usage\": {\n \"prompt_tokens\": 175,\n \"completion_tokens\": - 25,\n \"total_tokens\": 200,\n \"completion_tokens_details\": {\n \"reasoning_tokens\": - 0\n }\n },\n \"system_fingerprint\": \"fp_a5d11b2ef2\"\n}\n" + body: + string: !!binary | + H4sIAAAAAAAAA4xSPW/bMBDd9SsOXLpIgeTITarNS4t26JJubSHQ5IliSh1ZHv0RBP7vhSTHctAU + 6CJQ7909vHd3zxmAsFo0IFQvkxqCKzYPaf1b2/hhW+8PR9N9Kh9W5Zdhjebr4zeRjx1++4gqvXTd + KD8Eh8l6mmkVUSYcVau726qu729X7ydi8Brd2GZCKmpfDJZssSpXdVHeFdX9ubv3ViGLBr5nAADP + 03f0SRqPooEyf0EGZJYGRXMpAhDRuxERktlykpREvpDKU0KarH8G8gdQksDYPYIEM9oGSXzACPCD + PlqSDjbTfwObEBy+Y0Dl+YkTDmApoYkyIUMvoz7IiDmw79L8kqSBMe7HMMAoB4fM7ikHpF6SsmRg + xxgBjwGjRVJ4c+00YrdjOU6Lds6d8dMluvMmRL/lM3/BO0uW+zaiZE9jTE4+iIk9ZQA/pxHvXk1N + hOiHkNrkfyHxtLX1rCeWzS7svEsAkXyS7govq/wNvVZjktbx1ZKEkqpHvbQuG5U7bf0VkV2l/tvN + W9pzckvmf+QXQikMCXUbImqrXideyiKOh/+vssuUJ8NiPpO2s2Qwhmjns+tCW25lqatV3VUiO2V/ + AAAA//8DAPtpFJCEAwAA headers: CF-Cache-Status: - DYNAMIC CF-RAY: - - 8c85f2369a761cf3-GRU + - 8e19bf447ba48761-GRU Connection: - keep-alive Content-Encoding: @@ -276,7 +299,7 @@ interactions: Content-Type: - application/json Date: - - Tue, 24 Sep 2024 21:42:46 GMT + - Tue, 12 Nov 2024 21:52:06 GMT Server: - cloudflare Transfer-Encoding: @@ -285,10 +308,12 @@ interactions: - nosniff access-control-expose-headers: - X-Request-ID + alt-svc: + - h3=":443"; ma=86400 openai-organization: - - crewai-iuxna1 + - user-tqfegqsiobpvvjmn0giaipdq openai-processing-ms: - - '389' + - '655' openai-version: - '2020-10-01' strict-transport-security: @@ -296,17 +321,18 @@ interactions: x-ratelimit-limit-requests: - '10000' x-ratelimit-limit-tokens: - - '30000000' + - '200000' x-ratelimit-remaining-requests: - - '9999' + - '9997' x-ratelimit-remaining-tokens: - - '29999791' + - '199791' x-ratelimit-reset-requests: - - 6ms + - 24.239s x-ratelimit-reset-tokens: - - 0s + - 62ms x-request-id: - - req_0167388f0a7a7f1a1026409834ceb914 - http_version: HTTP/1.1 - status_code: 200 + - req_a228208b0e965ecee334a6947d6c9e7c + status: + code: 200 + message: OK version: 1 diff --git a/tests/crew_test.py b/tests/crew_test.py index 5f39557e0..8a7fc193a 100644 --- a/tests/crew_test.py +++ b/tests/crew_test.py @@ -564,6 +564,7 @@ def test_crew_kickoff_usage_metrics(): assert result.token_usage.prompt_tokens > 0 assert result.token_usage.completion_tokens > 0 assert result.token_usage.successful_requests > 0 + assert result.token_usage.cached_prompt_tokens == 0 def test_agents_rpm_is_never_set_if_crew_max_RPM_is_not_set(): @@ -1284,6 +1285,7 @@ def test_agent_usage_metrics_are_captured_for_hierarchical_process(): prompt_tokens=1562, completion_tokens=111, successful_requests=3, + cached_prompt_tokens=0 ) From c725105b1f49da983e029cdbb38093ed01507ba7 Mon Sep 17 00:00:00 2001 From: Thiago Moretto Date: Wed, 13 Nov 2024 10:18:30 -0300 Subject: [PATCH 059/126] do not include cached on total --- src/crewai/agents/agent_builder/utilities/base_token_process.py | 1 - 1 file changed, 1 deletion(-) diff --git a/src/crewai/agents/agent_builder/utilities/base_token_process.py b/src/crewai/agents/agent_builder/utilities/base_token_process.py index 91df8140e..320d34caa 100644 --- a/src/crewai/agents/agent_builder/utilities/base_token_process.py +++ b/src/crewai/agents/agent_builder/utilities/base_token_process.py @@ -18,7 +18,6 @@ class TokenProcess: def sum_cached_prompt_tokens(self, tokens: int): self.cached_prompt_tokens = self.cached_prompt_tokens + tokens - self.total_tokens = self.total_tokens + tokens def sum_successful_requests(self, requests: int): self.successful_requests = self.successful_requests + requests From c57cbd8591dc352485d4bb213649ca5c085fd5b5 Mon Sep 17 00:00:00 2001 From: Thiago Moretto Date: Wed, 13 Nov 2024 10:47:49 -0300 Subject: [PATCH 060/126] Fix crew_train_success test --- tests/crew_test.py | 36 ++++++++++++++++-------------------- 1 file changed, 16 insertions(+), 20 deletions(-) diff --git a/tests/crew_test.py b/tests/crew_test.py index 8a7fc193a..5f31061ec 100644 --- a/tests/crew_test.py +++ b/tests/crew_test.py @@ -1779,26 +1779,22 @@ def test_crew_train_success( ] ) - crew_training_handler.assert_has_calls( - [ - mock.call("training_data.pkl"), - mock.call().load(), - mock.call("trained_agents_data.pkl"), - mock.call().save_trained_data( - agent_id="Researcher", - trained_data=task_evaluator().evaluate_training_data().model_dump(), - ), - mock.call("trained_agents_data.pkl"), - mock.call().save_trained_data( - agent_id="Senior Writer", - trained_data=task_evaluator().evaluate_training_data().model_dump(), - ), - mock.call(), - mock.call().load(), - mock.call(), - mock.call().load(), - ] - ) + crew_training_handler.assert_any_call("training_data.pkl") + crew_training_handler().load.assert_called() + + crew_training_handler.assert_any_call("trained_agents_data.pkl") + crew_training_handler().load.assert_called() + + crew_training_handler().save_trained_data.assert_has_calls([ + mock.call( + agent_id="Researcher", + trained_data=task_evaluator().evaluate_training_data().model_dump(), + ), + mock.call( + agent_id="Senior Writer", + trained_data=task_evaluator().evaluate_training_data().model_dump(), + ) + ]) def test_crew_train_error(): From 9285ebf8a210d076d392ef02901a620ef568327f Mon Sep 17 00:00:00 2001 From: Eduardo Chiarotti Date: Thu, 14 Nov 2024 13:12:35 -0300 Subject: [PATCH 061/126] feat: Reduce level for Bandit and fix code to adapt (#1604) --- .github/workflows/security-checker.yml | 2 +- src/crewai/cli/authentication/main.py | 6 ++++-- src/crewai/memory/storage/kickoff_task_outputs_storage.py | 2 +- src/crewai/memory/storage/ltm_sqlite_storage.py | 2 +- src/crewai/utilities/file_handler.py | 8 ++++++-- 5 files changed, 13 insertions(+), 7 deletions(-) diff --git a/.github/workflows/security-checker.yml b/.github/workflows/security-checker.yml index d0d309b4c..665f49292 100644 --- a/.github/workflows/security-checker.yml +++ b/.github/workflows/security-checker.yml @@ -19,5 +19,5 @@ jobs: run: pip install bandit - name: Run Bandit - run: bandit -c pyproject.toml -r src/ -lll + run: bandit -c pyproject.toml -r src/ -ll diff --git a/src/crewai/cli/authentication/main.py b/src/crewai/cli/authentication/main.py index 331b583e8..543f06844 100644 --- a/src/crewai/cli/authentication/main.py +++ b/src/crewai/cli/authentication/main.py @@ -34,7 +34,9 @@ class AuthenticationCommand: "scope": "openid", "audience": AUTH0_AUDIENCE, } - response = requests.post(url=self.DEVICE_CODE_URL, data=device_code_payload) + response = requests.post( + url=self.DEVICE_CODE_URL, data=device_code_payload, timeout=20 + ) response.raise_for_status() return response.json() @@ -54,7 +56,7 @@ class AuthenticationCommand: attempts = 0 while True and attempts < 5: - response = requests.post(self.TOKEN_URL, data=token_payload) + response = requests.post(self.TOKEN_URL, data=token_payload, timeout=30) token_data = response.json() if response.status_code == 200: diff --git a/src/crewai/memory/storage/kickoff_task_outputs_storage.py b/src/crewai/memory/storage/kickoff_task_outputs_storage.py index dbb5f124b..26905191c 100644 --- a/src/crewai/memory/storage/kickoff_task_outputs_storage.py +++ b/src/crewai/memory/storage/kickoff_task_outputs_storage.py @@ -103,7 +103,7 @@ class KickoffTaskOutputsSQLiteStorage: else value ) - query = f"UPDATE latest_kickoff_task_outputs SET {', '.join(fields)} WHERE task_index = ?" + query = f"UPDATE latest_kickoff_task_outputs SET {', '.join(fields)} WHERE task_index = ?" # nosec values.append(task_index) cursor.execute(query, tuple(values)) diff --git a/src/crewai/memory/storage/ltm_sqlite_storage.py b/src/crewai/memory/storage/ltm_sqlite_storage.py index 7fb388a62..93d993ee6 100644 --- a/src/crewai/memory/storage/ltm_sqlite_storage.py +++ b/src/crewai/memory/storage/ltm_sqlite_storage.py @@ -83,7 +83,7 @@ class LTMSQLiteStorage: WHERE task_description = ? ORDER BY datetime DESC, score ASC LIMIT {latest_n} - """, + """, # nosec (task_description,), ) rows = cursor.fetchall() diff --git a/src/crewai/utilities/file_handler.py b/src/crewai/utilities/file_handler.py index 091bd930a..bb97b940f 100644 --- a/src/crewai/utilities/file_handler.py +++ b/src/crewai/utilities/file_handler.py @@ -16,7 +16,11 @@ class FileHandler: def log(self, **kwargs): now = datetime.now().strftime("%Y-%m-%d %H:%M:%S") - message = f"{now}: " + ", ".join([f"{key}=\"{value}\"" for key, value in kwargs.items()]) + "\n" + message = ( + f"{now}: " + + ", ".join([f'{key}="{value}"' for key, value in kwargs.items()]) + + "\n" + ) with open(self._path, "a", encoding="utf-8") as file: file.write(message + "\n") @@ -63,7 +67,7 @@ class PickleHandler: with open(self.file_path, "rb") as file: try: - return pickle.load(file) + return pickle.load(file) # nosec except EOFError: return {} # Return an empty dictionary if the file is empty or corrupted except Exception: From e70bc94ab6c14fbfe0491a562d5252908160e033 Mon Sep 17 00:00:00 2001 From: Dev Khant Date: Fri, 15 Nov 2024 00:29:24 +0530 Subject: [PATCH 062/126] Add support for retrieving user preferences and memories using Mem0 (#1209) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Integrate Mem0 * Update src/crewai/memory/contextual/contextual_memory.py Co-authored-by: Deshraj Yadav * pending commit for _fetch_user_memories * update poetry.lock * fixes mypy issues * fix mypy checks * New fixes for user_id * remove memory_provider * handle memory_provider * checks for memory_config * add mem0 to dependency * Update pyproject.toml Co-authored-by: Deshraj Yadav * update docs * update doc * bump mem0 version * fix api error msg and mypy issue * mypy fix * resolve comments * fix memory usage without mem0 * mem0 version bump * lazy import mem0 --------- Co-authored-by: Deshraj Yadav Co-authored-by: João Moura Co-authored-by: Brandon Hancock (bhancock_ai) <109994880+bhancockio@users.noreply.github.com> --- docs/concepts/crews.mdx | 3 +- docs/concepts/memory.mdx | 42 +++ poetry.lock | 6 +- pyproject.toml | 1 + src/crewai/agent.py | 2 + src/crewai/crew.py | 24 +- src/crewai/memory/__init__.py | 3 +- .../memory/contextual/contextual_memory.py | 47 ++- src/crewai/memory/entity/entity_memory.py | 42 ++- src/crewai/memory/memory.py | 11 +- .../memory/short_term/short_term_memory.py | 39 ++- src/crewai/memory/storage/interface.py | 6 +- src/crewai/memory/storage/mem0_storage.py | 104 +++++++ src/crewai/memory/user/__init__.py | 0 src/crewai/memory/user/user_memory.py | 45 +++ src/crewai/memory/user/user_memory_item.py | 8 + .../test_save_and_search_with_provider.yaml | 270 ++++++++++++++++++ 17 files changed, 619 insertions(+), 34 deletions(-) create mode 100644 src/crewai/memory/storage/mem0_storage.py create mode 100644 src/crewai/memory/user/__init__.py create mode 100644 src/crewai/memory/user/user_memory.py create mode 100644 src/crewai/memory/user/user_memory_item.py create mode 100644 tests/memory/cassettes/test_save_and_search_with_provider.yaml diff --git a/docs/concepts/crews.mdx b/docs/concepts/crews.mdx index 43451ca4b..ec0f190de 100644 --- a/docs/concepts/crews.mdx +++ b/docs/concepts/crews.mdx @@ -22,7 +22,8 @@ A crew in crewAI represents a collaborative group of agents working together to | **Max RPM** _(optional)_ | `max_rpm` | Maximum requests per minute the crew adheres to during execution. Defaults to `None`. | | **Language** _(optional)_ | `language` | Language used for the crew, defaults to English. | | **Language File** _(optional)_ | `language_file` | Path to the language file to be used for the crew. | -| **Memory** _(optional)_ | `memory` | Utilized for storing execution memories (short-term, long-term, entity memory). Defaults to `False`. | +| **Memory** _(optional)_ | `memory` | Utilized for storing execution memories (short-term, long-term, entity memory). | +| **Memory Config** _(optional)_ | `memory_config` | Configuration for the memory provider to be used by the crew. | | **Cache** _(optional)_ | `cache` | Specifies whether to use a cache for storing the results of tools' execution. Defaults to `True`. | | **Embedder** _(optional)_ | `embedder` | Configuration for the embedder to be used by the crew. Mostly used by memory for now. Default is `{"provider": "openai"}`. | | **Full Output** _(optional)_ | `full_output` | Whether the crew should return the full output with all tasks outputs or just the final output. Defaults to `False`. | diff --git a/docs/concepts/memory.mdx b/docs/concepts/memory.mdx index bda9f3401..a7677cec1 100644 --- a/docs/concepts/memory.mdx +++ b/docs/concepts/memory.mdx @@ -18,6 +18,7 @@ reason, and learn from past interactions. | **Long-Term Memory** | Preserves valuable insights and learnings from past executions, allowing agents to build and refine their knowledge over time. | | **Entity Memory** | Captures and organizes information about entities (people, places, concepts) encountered during tasks, facilitating deeper understanding and relationship mapping. Uses `RAG` for storing entity information. | | **Contextual Memory**| Maintains the context of interactions by combining `ShortTermMemory`, `LongTermMemory`, and `EntityMemory`, aiding in the coherence and relevance of agent responses over a sequence of tasks or a conversation. | +| **User Memory** | Stores user-specific information and preferences, enhancing personalization and user experience. | ## How Memory Systems Empower Agents @@ -92,6 +93,47 @@ my_crew = Crew( ) ``` +## Integrating Mem0 for Enhanced User Memory + +[Mem0](https://mem0.ai/) is a self-improving memory layer for LLM applications, enabling personalized AI experiences. + +To include user-specific memory you can get your API key [here](https://app.mem0.ai/dashboard/api-keys) and refer the [docs](https://docs.mem0.ai/platform/quickstart#4-1-create-memories) for adding user preferences. + + +```python Code +import os +from crewai import Crew, Process +from mem0 import MemoryClient + +# Set environment variables for Mem0 +os.environ["MEM0_API_KEY"] = "m0-xx" + +# Step 1: Record preferences based on past conversation or user input +client = MemoryClient() +messages = [ + {"role": "user", "content": "Hi there! I'm planning a vacation and could use some advice."}, + {"role": "assistant", "content": "Hello! I'd be happy to help with your vacation planning. What kind of destination do you prefer?"}, + {"role": "user", "content": "I am more of a beach person than a mountain person."}, + {"role": "assistant", "content": "That's interesting. Do you like hotels or Airbnb?"}, + {"role": "user", "content": "I like Airbnb more."}, +] +client.add(messages, user_id="john") + +# Step 2: Create a Crew with User Memory + +crew = Crew( + agents=[...], + tasks=[...], + verbose=True, + process=Process.sequential, + memory=True, + memory_config={ + "provider": "mem0", + "config": {"user_id": "john"}, + }, +) +``` + ## Additional Embedding Providers diff --git a/poetry.lock b/poetry.lock index 8ba39f6c3..094b84664 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1597,12 +1597,12 @@ files = [ google-auth = ">=2.14.1,<3.0.dev0" googleapis-common-protos = ">=1.56.2,<2.0.dev0" grpcio = [ - {version = ">=1.49.1,<2.0dev", optional = true, markers = "python_version >= \"3.11\" and extra == \"grpc\""}, {version = ">=1.33.2,<2.0dev", optional = true, markers = "python_version < \"3.11\" and extra == \"grpc\""}, + {version = ">=1.49.1,<2.0dev", optional = true, markers = "python_version >= \"3.11\" and extra == \"grpc\""}, ] grpcio-status = [ - {version = ">=1.49.1,<2.0.dev0", optional = true, markers = "python_version >= \"3.11\" and extra == \"grpc\""}, {version = ">=1.33.2,<2.0.dev0", optional = true, markers = "python_version < \"3.11\" and extra == \"grpc\""}, + {version = ">=1.49.1,<2.0.dev0", optional = true, markers = "python_version >= \"3.11\" and extra == \"grpc\""}, ] proto-plus = ">=1.22.3,<2.0.0dev" protobuf = ">=3.19.5,<3.20.0 || >3.20.0,<3.20.1 || >3.20.1,<4.21.0 || >4.21.0,<4.21.1 || >4.21.1,<4.21.2 || >4.21.2,<4.21.3 || >4.21.3,<4.21.4 || >4.21.4,<4.21.5 || >4.21.5,<6.0.0.dev0" @@ -4286,8 +4286,8 @@ files = [ [package.dependencies] numpy = [ - {version = ">=1.23.2", markers = "python_version == \"3.11\""}, {version = ">=1.22.4", markers = "python_version < \"3.11\""}, + {version = ">=1.23.2", markers = "python_version == \"3.11\""}, {version = ">=1.26.0", markers = "python_version >= \"3.12\""}, ] python-dateutil = ">=2.8.2" diff --git a/pyproject.toml b/pyproject.toml index 8a5d2f1b9..66adfee3e 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -39,6 +39,7 @@ Repository = "https://github.com/crewAIInc/crewAI" [project.optional-dependencies] tools = ["crewai-tools>=0.14.0"] agentops = ["agentops>=0.3.0"] +mem0 = ["mem0ai>=0.1.29"] [tool.uv] dev-dependencies = [ diff --git a/src/crewai/agent.py b/src/crewai/agent.py index fc43137a2..4e9a0685f 100644 --- a/src/crewai/agent.py +++ b/src/crewai/agent.py @@ -262,9 +262,11 @@ class Agent(BaseAgent): if self.crew and self.crew.memory: contextual_memory = ContextualMemory( + self.crew.memory_config, self.crew._short_term_memory, self.crew._long_term_memory, self.crew._entity_memory, + self.crew._user_memory, ) memory = contextual_memory.build_context_for_task(task, context) if memory.strip() != "": diff --git a/src/crewai/crew.py b/src/crewai/crew.py index 7bcaa82ad..04820adf8 100644 --- a/src/crewai/crew.py +++ b/src/crewai/crew.py @@ -27,6 +27,7 @@ from crewai.llm import LLM from crewai.memory.entity.entity_memory import EntityMemory from crewai.memory.long_term.long_term_memory import LongTermMemory from crewai.memory.short_term.short_term_memory import ShortTermMemory +from crewai.memory.user.user_memory import UserMemory from crewai.process import Process from crewai.task import Task from crewai.tasks.conditional_task import ConditionalTask @@ -71,6 +72,7 @@ class Crew(BaseModel): manager_llm: The language model that will run manager agent. manager_agent: Custom agent that will be used as manager. memory: Whether the crew should use memory to store memories of it's execution. + memory_config: Configuration for the memory to be used for the crew. cache: Whether the crew should use a cache to store the results of the tools execution. function_calling_llm: The language model that will run the tool calling for all the agents. process: The process flow that the crew will follow (e.g., sequential, hierarchical). @@ -94,6 +96,7 @@ class Crew(BaseModel): _short_term_memory: Optional[InstanceOf[ShortTermMemory]] = PrivateAttr() _long_term_memory: Optional[InstanceOf[LongTermMemory]] = PrivateAttr() _entity_memory: Optional[InstanceOf[EntityMemory]] = PrivateAttr() + _user_memory: Optional[InstanceOf[UserMemory]] = PrivateAttr() _train: Optional[bool] = PrivateAttr(default=False) _train_iteration: Optional[int] = PrivateAttr() _inputs: Optional[Dict[str, Any]] = PrivateAttr(default=None) @@ -114,6 +117,10 @@ class Crew(BaseModel): default=False, description="Whether the crew should use memory to store memories of it's execution", ) + memory_config: Optional[Dict[str, Any]] = Field( + default=None, + description="Configuration for the memory to be used for the crew.", + ) short_term_memory: Optional[InstanceOf[ShortTermMemory]] = Field( default=None, description="An Instance of the ShortTermMemory to be used by the Crew", @@ -126,7 +133,11 @@ class Crew(BaseModel): default=None, description="An Instance of the EntityMemory to be used by the Crew", ) - embedder: Optional[Any] = Field( + user_memory: Optional[InstanceOf[UserMemory]] = Field( + default=None, + description="An instance of the UserMemory to be used by the Crew to store/fetch memories of a specific user.", + ) + embedder: Optional[dict] = Field( default=None, description="Configuration for the embedder to be used for the crew.", ) @@ -238,13 +249,22 @@ class Crew(BaseModel): self._short_term_memory = ( self.short_term_memory if self.short_term_memory - else ShortTermMemory(crew=self, embedder_config=self.embedder) + else ShortTermMemory( + crew=self, + embedder_config=self.embedder, + ) ) self._entity_memory = ( self.entity_memory if self.entity_memory else EntityMemory(crew=self, embedder_config=self.embedder) ) + if hasattr(self, "memory_config") and self.memory_config is not None: + self._user_memory = ( + self.user_memory if self.user_memory else UserMemory(crew=self) + ) + else: + self._user_memory = None return self @model_validator(mode="after") diff --git a/src/crewai/memory/__init__.py b/src/crewai/memory/__init__.py index 8182bede7..3f7ca2ad6 100644 --- a/src/crewai/memory/__init__.py +++ b/src/crewai/memory/__init__.py @@ -1,5 +1,6 @@ from .entity.entity_memory import EntityMemory from .long_term.long_term_memory import LongTermMemory from .short_term.short_term_memory import ShortTermMemory +from .user.user_memory import UserMemory -__all__ = ["EntityMemory", "LongTermMemory", "ShortTermMemory"] +__all__ = ["UserMemory", "EntityMemory", "LongTermMemory", "ShortTermMemory"] diff --git a/src/crewai/memory/contextual/contextual_memory.py b/src/crewai/memory/contextual/contextual_memory.py index 5d91cf47d..9598fe6ee 100644 --- a/src/crewai/memory/contextual/contextual_memory.py +++ b/src/crewai/memory/contextual/contextual_memory.py @@ -1,13 +1,25 @@ -from typing import Optional +from typing import Optional, Dict, Any -from crewai.memory import EntityMemory, LongTermMemory, ShortTermMemory +from crewai.memory import EntityMemory, LongTermMemory, ShortTermMemory, UserMemory class ContextualMemory: - def __init__(self, stm: ShortTermMemory, ltm: LongTermMemory, em: EntityMemory): + def __init__( + self, + memory_config: Optional[Dict[str, Any]], + stm: ShortTermMemory, + ltm: LongTermMemory, + em: EntityMemory, + um: UserMemory, + ): + if memory_config is not None: + self.memory_provider = memory_config.get("provider") + else: + self.memory_provider = None self.stm = stm self.ltm = ltm self.em = em + self.um = um def build_context_for_task(self, task, context) -> str: """ @@ -23,6 +35,8 @@ class ContextualMemory: context.append(self._fetch_ltm_context(task.description)) context.append(self._fetch_stm_context(query)) context.append(self._fetch_entity_context(query)) + if self.memory_provider == "mem0": + context.append(self._fetch_user_context(query)) return "\n".join(filter(None, context)) def _fetch_stm_context(self, query) -> str: @@ -32,7 +46,10 @@ class ContextualMemory: """ stm_results = self.stm.search(query) formatted_results = "\n".join( - [f"- {result['context']}" for result in stm_results] + [ + f"- {result['memory'] if self.memory_provider == 'mem0' else result['context']}" + for result in stm_results + ] ) return f"Recent Insights:\n{formatted_results}" if stm_results else "" @@ -62,6 +79,26 @@ class ContextualMemory: """ em_results = self.em.search(query) formatted_results = "\n".join( - [f"- {result['context']}" for result in em_results] # type: ignore # Invalid index type "str" for "str"; expected type "SupportsIndex | slice" + [ + f"- {result['memory'] if self.memory_provider == 'mem0' else result['context']}" + for result in em_results + ] # type: ignore # Invalid index type "str" for "str"; expected type "SupportsIndex | slice" ) return f"Entities:\n{formatted_results}" if em_results else "" + + def _fetch_user_context(self, query: str) -> str: + """ + Fetches and formats relevant user information from User Memory. + Args: + query (str): The search query to find relevant user memories. + Returns: + str: Formatted user memories as bullet points, or an empty string if none found. + """ + user_memories = self.um.search(query) + if not user_memories: + return "" + + formatted_memories = "\n".join( + f"- {result['memory']}" for result in user_memories + ) + return f"User memories/preferences:\n{formatted_memories}" diff --git a/src/crewai/memory/entity/entity_memory.py b/src/crewai/memory/entity/entity_memory.py index 4de0594c7..134e19bfa 100644 --- a/src/crewai/memory/entity/entity_memory.py +++ b/src/crewai/memory/entity/entity_memory.py @@ -11,21 +11,43 @@ class EntityMemory(Memory): """ def __init__(self, crew=None, embedder_config=None, storage=None): - storage = ( - storage - if storage - else RAGStorage( - type="entities", - allow_reset=True, - embedder_config=embedder_config, - crew=crew, + if hasattr(crew, "memory_config") and crew.memory_config is not None: + self.memory_provider = crew.memory_config.get("provider") + else: + self.memory_provider = None + + if self.memory_provider == "mem0": + try: + from crewai.memory.storage.mem0_storage import Mem0Storage + except ImportError: + raise ImportError( + "Mem0 is not installed. Please install it with `pip install mem0ai`." + ) + storage = Mem0Storage(type="entities", crew=crew) + else: + storage = ( + storage + if storage + else RAGStorage( + type="entities", + allow_reset=False, + embedder_config=embedder_config, + crew=crew, + ) ) - ) super().__init__(storage) def save(self, item: EntityMemoryItem) -> None: # type: ignore # BUG?: Signature of "save" incompatible with supertype "Memory" """Saves an entity item into the SQLite storage.""" - data = f"{item.name}({item.type}): {item.description}" + if self.memory_provider == "mem0": + data = f""" + Remember details about the following entity: + Name: {item.name} + Type: {item.type} + Entity Description: {item.description} + """ + else: + data = f"{item.name}({item.type}): {item.description}" super().save(data, item.metadata) def reset(self) -> None: diff --git a/src/crewai/memory/memory.py b/src/crewai/memory/memory.py index d0bcd614f..4869f8e6b 100644 --- a/src/crewai/memory/memory.py +++ b/src/crewai/memory/memory.py @@ -23,5 +23,12 @@ class Memory: self.storage.save(value, metadata) - def search(self, query: str) -> List[Dict[str, Any]]: - return self.storage.search(query) + def search( + self, + query: str, + limit: int = 3, + score_threshold: float = 0.35, + ) -> List[Any]: + return self.storage.search( + query=query, limit=limit, score_threshold=score_threshold + ) diff --git a/src/crewai/memory/short_term/short_term_memory.py b/src/crewai/memory/short_term/short_term_memory.py index 919fb6115..67a568d63 100644 --- a/src/crewai/memory/short_term/short_term_memory.py +++ b/src/crewai/memory/short_term/short_term_memory.py @@ -14,13 +14,27 @@ class ShortTermMemory(Memory): """ def __init__(self, crew=None, embedder_config=None, storage=None): - storage = ( - storage - if storage - else RAGStorage( - type="short_term", embedder_config=embedder_config, crew=crew + if hasattr(crew, "memory_config") and crew.memory_config is not None: + self.memory_provider = crew.memory_config.get("provider") + else: + self.memory_provider = None + + if self.memory_provider == "mem0": + try: + from crewai.memory.storage.mem0_storage import Mem0Storage + except ImportError: + raise ImportError( + "Mem0 is not installed. Please install it with `pip install mem0ai`." + ) + storage = Mem0Storage(type="short_term", crew=crew) + else: + storage = ( + storage + if storage + else RAGStorage( + type="short_term", embedder_config=embedder_config, crew=crew + ) ) - ) super().__init__(storage) def save( @@ -30,11 +44,20 @@ class ShortTermMemory(Memory): agent: Optional[str] = None, ) -> None: item = ShortTermMemoryItem(data=value, metadata=metadata, agent=agent) + if self.memory_provider == "mem0": + item.data = f"Remember the following insights from Agent run: {item.data}" super().save(value=item.data, metadata=item.metadata, agent=item.agent) - def search(self, query: str, score_threshold: float = 0.35): - return self.storage.search(query=query, score_threshold=score_threshold) # type: ignore # BUG? The reference is to the parent class, but the parent class does not have this parameters + def search( + self, + query: str, + limit: int = 3, + score_threshold: float = 0.35, + ): + return self.storage.search( + query=query, limit=limit, score_threshold=score_threshold + ) # type: ignore # BUG? The reference is to the parent class, but the parent class does not have this parameters def reset(self) -> None: try: diff --git a/src/crewai/memory/storage/interface.py b/src/crewai/memory/storage/interface.py index 8fbe10b03..8bec9a14f 100644 --- a/src/crewai/memory/storage/interface.py +++ b/src/crewai/memory/storage/interface.py @@ -7,8 +7,10 @@ class Storage: def save(self, value: Any, metadata: Dict[str, Any]) -> None: pass - def search(self, key: str) -> List[Dict[str, Any]]: # type: ignore - pass + def search( + self, query: str, limit: int, score_threshold: float + ) -> Dict[str, Any] | List[Any]: + return {} def reset(self) -> None: pass diff --git a/src/crewai/memory/storage/mem0_storage.py b/src/crewai/memory/storage/mem0_storage.py new file mode 100644 index 000000000..34aab9716 --- /dev/null +++ b/src/crewai/memory/storage/mem0_storage.py @@ -0,0 +1,104 @@ +import os +from typing import Any, Dict, List + +from mem0 import MemoryClient +from crewai.memory.storage.interface import Storage + + +class Mem0Storage(Storage): + """ + Extends Storage to handle embedding and searching across entities using Mem0. + """ + + def __init__(self, type, crew=None): + super().__init__() + + if type not in ["user", "short_term", "long_term", "entities"]: + raise ValueError("Invalid type for Mem0Storage. Must be 'user' or 'agent'.") + + self.memory_type = type + self.crew = crew + self.memory_config = crew.memory_config + + # User ID is required for user memory type "user" since it's used as a unique identifier for the user. + user_id = self._get_user_id() + if type == "user" and not user_id: + raise ValueError("User ID is required for user memory type") + + # API key in memory config overrides the environment variable + mem0_api_key = self.memory_config.get("config", {}).get("api_key") or os.getenv( + "MEM0_API_KEY" + ) + self.memory = MemoryClient(api_key=mem0_api_key) + + def _sanitize_role(self, role: str) -> str: + """ + Sanitizes agent roles to ensure valid directory names. + """ + return role.replace("\n", "").replace(" ", "_").replace("/", "_") + + def save(self, value: Any, metadata: Dict[str, Any]) -> None: + user_id = self._get_user_id() + agent_name = self._get_agent_name() + if self.memory_type == "user": + self.memory.add(value, user_id=user_id, metadata={**metadata}) + elif self.memory_type == "short_term": + agent_name = self._get_agent_name() + self.memory.add( + value, agent_id=agent_name, metadata={"type": "short_term", **metadata} + ) + elif self.memory_type == "long_term": + agent_name = self._get_agent_name() + self.memory.add( + value, + agent_id=agent_name, + infer=False, + metadata={"type": "long_term", **metadata}, + ) + elif self.memory_type == "entities": + entity_name = None + self.memory.add( + value, user_id=entity_name, metadata={"type": "entity", **metadata} + ) + + def search( + self, + query: str, + limit: int = 3, + score_threshold: float = 0.35, + ) -> List[Any]: + params = {"query": query, "limit": limit} + if self.memory_type == "user": + user_id = self._get_user_id() + params["user_id"] = user_id + elif self.memory_type == "short_term": + agent_name = self._get_agent_name() + params["agent_id"] = agent_name + params["metadata"] = {"type": "short_term"} + elif self.memory_type == "long_term": + agent_name = self._get_agent_name() + params["agent_id"] = agent_name + params["metadata"] = {"type": "long_term"} + elif self.memory_type == "entities": + agent_name = self._get_agent_name() + params["agent_id"] = agent_name + params["metadata"] = {"type": "entity"} + + # Discard the filters for now since we create the filters + # automatically when the crew is created. + results = self.memory.search(**params) + return [r for r in results if r["score"] >= score_threshold] + + def _get_user_id(self): + if self.memory_type == "user": + if hasattr(self, "memory_config") and self.memory_config is not None: + return self.memory_config.get("config", {}).get("user_id") + else: + return None + return None + + def _get_agent_name(self): + agents = self.crew.agents if self.crew else [] + agents = [self._sanitize_role(agent.role) for agent in agents] + agents = "_".join(agents) + return agents diff --git a/src/crewai/memory/user/__init__.py b/src/crewai/memory/user/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/src/crewai/memory/user/user_memory.py b/src/crewai/memory/user/user_memory.py new file mode 100644 index 000000000..25e36617c --- /dev/null +++ b/src/crewai/memory/user/user_memory.py @@ -0,0 +1,45 @@ +from typing import Any, Dict, Optional + +from crewai.memory.memory import Memory + + +class UserMemory(Memory): + """ + UserMemory class for handling user memory storage and retrieval. + Inherits from the Memory class and utilizes an instance of a class that + adheres to the Storage for data storage, specifically working with + MemoryItem instances. + """ + + def __init__(self, crew=None): + try: + from crewai.memory.storage.mem0_storage import Mem0Storage + except ImportError: + raise ImportError( + "Mem0 is not installed. Please install it with `pip install mem0ai`." + ) + storage = Mem0Storage(type="user", crew=crew) + super().__init__(storage) + + def save( + self, + value, + metadata: Optional[Dict[str, Any]] = None, + agent: Optional[str] = None, + ) -> None: + # TODO: Change this function since we want to take care of the case where we save memories for the usr + data = f"Remember the details about the user: {value}" + super().save(data, metadata) + + def search( + self, + query: str, + limit: int = 3, + score_threshold: float = 0.35, + ): + results = super().search( + query=query, + limit=limit, + score_threshold=score_threshold, + ) + return results diff --git a/src/crewai/memory/user/user_memory_item.py b/src/crewai/memory/user/user_memory_item.py new file mode 100644 index 000000000..288c1544a --- /dev/null +++ b/src/crewai/memory/user/user_memory_item.py @@ -0,0 +1,8 @@ +from typing import Any, Dict, Optional + + +class UserMemoryItem: + def __init__(self, data: Any, user: str, metadata: Optional[Dict[str, Any]] = None): + self.data = data + self.user = user + self.metadata = metadata if metadata is not None else {} diff --git a/tests/memory/cassettes/test_save_and_search_with_provider.yaml b/tests/memory/cassettes/test_save_and_search_with_provider.yaml new file mode 100644 index 000000000..c30f3f065 --- /dev/null +++ b/tests/memory/cassettes/test_save_and_search_with_provider.yaml @@ -0,0 +1,270 @@ +interactions: +- request: + body: '' + headers: + accept: + - '*/*' + accept-encoding: + - gzip, deflate + connection: + - keep-alive + host: + - api.mem0.ai + user-agent: + - python-httpx/0.27.0 + method: GET + uri: https://api.mem0.ai/v1/memories/?user_id=test + response: + body: + string: '[]' + headers: + CF-Cache-Status: + - DYNAMIC + CF-RAY: + - 8b477138bad847b9-BOM + Connection: + - keep-alive + Content-Length: + - '2' + Content-Type: + - application/json + Date: + - Sat, 17 Aug 2024 06:00:11 GMT + NEL: + - '{"success_fraction":0,"report_to":"cf-nel","max_age":604800}' + Report-To: + - '{"endpoints":[{"url":"https:\/\/a.nel.cloudflare.com\/report\/v4?s=uuyH2foMJVDpV%2FH52g1q%2FnvXKe3dBKVzvsK0mqmSNezkiszNR9OgrEJfVqmkX%2FlPFRP2sH4zrOuzGo6k%2FjzsjYJczqSWJUZHN2pPujiwnr1E9W%2BdLGKmG6%2FqPrGYAy2SBRWkkJVWsTO3OQ%3D%3D"}],"group":"cf-nel","max_age":604800}' + Server: + - cloudflare + allow: + - GET, POST, DELETE, OPTIONS + alt-svc: + - h3=":443"; ma=86400 + cross-origin-opener-policy: + - same-origin + referrer-policy: + - same-origin + vary: + - Accept, origin, Cookie + x-content-type-options: + - nosniff + x-frame-options: + - DENY + status: + code: 200 + message: OK +- request: + body: '{"batch": [{"properties": {"python_version": "3.12.4 (v3.12.4:8e8a4baf65, + Jun 6 2024, 17:33:18) [Clang 13.0.0 (clang-1300.0.29.30)]", "os": "darwin", + "os_version": "Darwin Kernel Version 23.4.0: Wed Feb 21 21:44:54 PST 2024; root:xnu-10063.101.15~2/RELEASE_ARM64_T6030", + "os_release": "23.4.0", "processor": "arm", "machine": "arm64", "function": + "mem0.client.main.MemoryClient", "$lib": "posthog-python", "$lib_version": "3.5.0", + "$geoip_disable": true}, "timestamp": "2024-08-17T06:00:11.526640+00:00", "context": + {}, "distinct_id": "fd411bd3-99a2-42d6-acd7-9fca8ad09580", "event": "client.init"}], + "historical_migration": false, "sentAt": "2024-08-17T06:00:11.701621+00:00", + "api_key": "phc_hgJkUVJFYtmaJqrvf6CYN67TIQ8yhXAkWzUn9AMU4yX"}' + headers: + Accept: + - '*/*' + Accept-Encoding: + - gzip, deflate + Connection: + - keep-alive + Content-Length: + - '740' + Content-Type: + - application/json + User-Agent: + - posthog-python/3.5.0 + method: POST + uri: https://us.i.posthog.com/batch/ + response: + body: + string: '{"status":"Ok"}' + headers: + Connection: + - keep-alive + Content-Length: + - '15' + Content-Type: + - application/json + Date: + - Sat, 17 Aug 2024 06:00:12 GMT + access-control-allow-credentials: + - 'true' + server: + - envoy + vary: + - origin, access-control-request-method, access-control-request-headers + x-envoy-upstream-service-time: + - '69' + status: + code: 200 + message: OK +- request: + body: '{"messages": [{"role": "user", "content": "Remember the following insights + from Agent run: test value with provider"}], "metadata": {"task": "test_task_provider", + "agent": "test_agent_provider"}, "app_id": "Researcher"}' + headers: + accept: + - '*/*' + accept-encoding: + - gzip, deflate + connection: + - keep-alive + content-length: + - '219' + content-type: + - application/json + host: + - api.mem0.ai + user-agent: + - python-httpx/0.27.0 + method: POST + uri: https://api.mem0.ai/v1/memories/ + response: + body: + string: '{"message":"ok"}' + headers: + CF-Cache-Status: + - DYNAMIC + CF-RAY: + - 8b477140282547b9-BOM + Connection: + - keep-alive + Content-Length: + - '16' + Content-Type: + - application/json + Date: + - Sat, 17 Aug 2024 06:00:13 GMT + NEL: + - '{"success_fraction":0,"report_to":"cf-nel","max_age":604800}' + Report-To: + - '{"endpoints":[{"url":"https:\/\/a.nel.cloudflare.com\/report\/v4?s=FRjJKSk3YxVj03wA7S05H8ts35KnWfqS3wb6Rfy4kVZ4BgXfw7nJbm92wI6vEv5fWcAcHVnOlkJDggs11B01BMuB2k3a9RqlBi0dJNiMuk%2Bgm5xE%2BODMPWJctYNRwQMjNVbteUpS%2Fad8YA%3D%3D"}],"group":"cf-nel","max_age":604800}' + Server: + - cloudflare + allow: + - GET, POST, DELETE, OPTIONS + alt-svc: + - h3=":443"; ma=86400 + cross-origin-opener-policy: + - same-origin + referrer-policy: + - same-origin + vary: + - Accept, origin, Cookie + x-content-type-options: + - nosniff + x-frame-options: + - DENY + status: + code: 200 + message: OK +- request: + body: '{"query": "test value with provider", "limit": 3, "app_id": "Researcher"}' + headers: + accept: + - '*/*' + accept-encoding: + - gzip, deflate + connection: + - keep-alive + content-length: + - '73' + content-type: + - application/json + host: + - api.mem0.ai + user-agent: + - python-httpx/0.27.0 + method: POST + uri: https://api.mem0.ai/v1/memories/search/ + response: + body: + string: '[]' + headers: + CF-Cache-Status: + - DYNAMIC + CF-RAY: + - 8b47714d083b47b9-BOM + Connection: + - keep-alive + Content-Length: + - '2' + Content-Type: + - application/json + Date: + - Sat, 17 Aug 2024 06:00:14 GMT + NEL: + - '{"success_fraction":0,"report_to":"cf-nel","max_age":604800}' + Report-To: + - '{"endpoints":[{"url":"https:\/\/a.nel.cloudflare.com\/report\/v4?s=2DRWL1cdKdMvnE8vx1fPUGeTITOgSGl3N5g84PS6w30GRqpfz79BtSx6REhpnOiFV8kM6KGqln0iCZ5yoHc2jBVVJXhPJhQ5t0uerD9JFnkphjISrJOU1MJjZWneT9PlNABddxvVNCmluA%3D%3D"}],"group":"cf-nel","max_age":604800}' + Server: + - cloudflare + allow: + - POST, OPTIONS + alt-svc: + - h3=":443"; ma=86400 + cross-origin-opener-policy: + - same-origin + referrer-policy: + - same-origin + vary: + - Accept, origin, Cookie + x-content-type-options: + - nosniff + x-frame-options: + - DENY + status: + code: 200 + message: OK +- request: + body: '{"batch": [{"properties": {"python_version": "3.12.4 (v3.12.4:8e8a4baf65, + Jun 6 2024, 17:33:18) [Clang 13.0.0 (clang-1300.0.29.30)]", "os": "darwin", + "os_version": "Darwin Kernel Version 23.4.0: Wed Feb 21 21:44:54 PST 2024; root:xnu-10063.101.15~2/RELEASE_ARM64_T6030", + "os_release": "23.4.0", "processor": "arm", "machine": "arm64", "function": + "mem0.client.main.MemoryClient", "$lib": "posthog-python", "$lib_version": "3.5.0", + "$geoip_disable": true}, "timestamp": "2024-08-17T06:00:13.593952+00:00", "context": + {}, "distinct_id": "fd411bd3-99a2-42d6-acd7-9fca8ad09580", "event": "client.add"}], + "historical_migration": false, "sentAt": "2024-08-17T06:00:13.858277+00:00", + "api_key": "phc_hgJkUVJFYtmaJqrvf6CYN67TIQ8yhXAkWzUn9AMU4yX"}' + headers: + Accept: + - '*/*' + Accept-Encoding: + - gzip, deflate + Connection: + - keep-alive + Content-Length: + - '739' + Content-Type: + - application/json + User-Agent: + - posthog-python/3.5.0 + method: POST + uri: https://us.i.posthog.com/batch/ + response: + body: + string: '{"status":"Ok"}' + headers: + Connection: + - keep-alive + Content-Length: + - '15' + Content-Type: + - application/json + Date: + - Sat, 17 Aug 2024 06:00:13 GMT + access-control-allow-credentials: + - 'true' + server: + - envoy + vary: + - origin, access-control-request-method, access-control-request-headers + x-envoy-upstream-service-time: + - '33' + status: + code: 200 + message: OK +version: 1 From 3dc02310b62c473c0764f10f08aef678291e9aaf Mon Sep 17 00:00:00 2001 From: Lorenze Jay <63378463+lorenzejay@users.noreply.github.com> Date: Thu, 14 Nov 2024 14:13:12 -0800 Subject: [PATCH 063/126] upgrade chroma and adjust embedder function generator (#1607) * upgrade chroma and adjust embedder function generator * >= version * linted --- pyproject.toml | 2 +- src/crewai/memory/entity/entity_memory.py | 2 +- src/crewai/memory/storage/rag_storage.py | 94 ++++++++++++++--------- uv.lock | 84 ++++++++------------ 4 files changed, 90 insertions(+), 92 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 66adfee3e..57868636f 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -27,8 +27,8 @@ dependencies = [ "pyvis>=0.3.2", "uv>=0.4.25", "tomli-w>=1.1.0", - "chromadb>=0.4.24", "tomli>=2.0.2", + "chromadb>=0.5.18", ] [project.urls] diff --git a/src/crewai/memory/entity/entity_memory.py b/src/crewai/memory/entity/entity_memory.py index 134e19bfa..88d33c09a 100644 --- a/src/crewai/memory/entity/entity_memory.py +++ b/src/crewai/memory/entity/entity_memory.py @@ -30,7 +30,7 @@ class EntityMemory(Memory): if storage else RAGStorage( type="entities", - allow_reset=False, + allow_reset=True, embedder_config=embedder_config, crew=crew, ) diff --git a/src/crewai/memory/storage/rag_storage.py b/src/crewai/memory/storage/rag_storage.py index d0f1cfc64..7af5fb554 100644 --- a/src/crewai/memory/storage/rag_storage.py +++ b/src/crewai/memory/storage/rag_storage.py @@ -51,8 +51,6 @@ class RAGStorage(BaseRAGStorage): self._initialize_app() def _set_embedder_config(self): - import chromadb.utils.embedding_functions as embedding_functions - if self.embedder_config is None: self.embedder_config = self._create_default_embedding_function() @@ -61,12 +59,20 @@ class RAGStorage(BaseRAGStorage): config = self.embedder_config.get("config", {}) model_name = config.get("model") if provider == "openai": - self.embedder_config = embedding_functions.OpenAIEmbeddingFunction( + from chromadb.utils.embedding_functions.openai_embedding_function import ( + OpenAIEmbeddingFunction, + ) + + self.embedder_config = OpenAIEmbeddingFunction( api_key=config.get("api_key") or os.getenv("OPENAI_API_KEY"), model_name=model_name, ) elif provider == "azure": - self.embedder_config = embedding_functions.OpenAIEmbeddingFunction( + from chromadb.utils.embedding_functions.openai_embedding_function import ( + OpenAIEmbeddingFunction, + ) + + self.embedder_config = OpenAIEmbeddingFunction( api_key=config.get("api_key"), api_base=config.get("api_base"), api_type=config.get("api_type", "azure"), @@ -74,45 +80,55 @@ class RAGStorage(BaseRAGStorage): model_name=model_name, ) elif provider == "ollama": - from openai import OpenAI + from chromadb.utils.embedding_functions.ollama_embedding_function import ( + OllamaEmbeddingFunction, + ) - class OllamaEmbeddingFunction(EmbeddingFunction): - def __call__(self, input: Documents) -> Embeddings: - client = OpenAI( - base_url="http://localhost:11434/v1", - api_key=config.get("api_key", "ollama"), - ) - try: - response = client.embeddings.create( - input=input, model=model_name - ) - embeddings = [item.embedding for item in response.data] - return cast(Embeddings, embeddings) - except Exception as e: - raise e - - self.embedder_config = OllamaEmbeddingFunction() + self.embedder_config = OllamaEmbeddingFunction( + url=config.get("url", "http://localhost:11434/api/embeddings"), + model_name=model_name, + ) elif provider == "vertexai": - self.embedder_config = ( - embedding_functions.GoogleVertexEmbeddingFunction( - model_name=model_name, - api_key=config.get("api_key"), - ) + from chromadb.utils.embedding_functions.google_embedding_function import ( + GoogleVertexEmbeddingFunction, ) - elif provider == "google": - self.embedder_config = ( - embedding_functions.GoogleGenerativeAiEmbeddingFunction( - model_name=model_name, - api_key=config.get("api_key"), - ) - ) - elif provider == "cohere": - self.embedder_config = embedding_functions.CohereEmbeddingFunction( + + self.embedder_config = GoogleVertexEmbeddingFunction( model_name=model_name, api_key=config.get("api_key"), ) + elif provider == "google": + from chromadb.utils.embedding_functions.google_embedding_function import ( + GoogleGenerativeAiEmbeddingFunction, + ) + + self.embedder_config = GoogleGenerativeAiEmbeddingFunction( + model_name=model_name, + api_key=config.get("api_key"), + ) + elif provider == "cohere": + from chromadb.utils.embedding_functions.cohere_embedding_function import ( + CohereEmbeddingFunction, + ) + + self.embedder_config = CohereEmbeddingFunction( + model_name=model_name, + api_key=config.get("api_key"), + ) + elif provider == "bedrock": + from chromadb.utils.embedding_functions.amazon_bedrock_embedding_function import ( + AmazonBedrockEmbeddingFunction, + ) + + self.embedder_config = AmazonBedrockEmbeddingFunction( + session=config.get("session"), + ) elif provider == "huggingface": - self.embedder_config = embedding_functions.HuggingFaceEmbeddingServer( + from chromadb.utils.embedding_functions.huggingface_embedding_function import ( + HuggingFaceEmbeddingServer, + ) + + self.embedder_config = HuggingFaceEmbeddingServer( url=config.get("api_url"), ) elif provider == "watson": @@ -253,8 +269,10 @@ class RAGStorage(BaseRAGStorage): ) def _create_default_embedding_function(self): - import chromadb.utils.embedding_functions as embedding_functions + from chromadb.utils.embedding_functions.openai_embedding_function import ( + OpenAIEmbeddingFunction, + ) - return embedding_functions.OpenAIEmbeddingFunction( + return OpenAIEmbeddingFunction( api_key=os.getenv("OPENAI_API_KEY"), model_name="text-embedding-3-small" ) diff --git a/uv.lock b/uv.lock index 844e0b862..f19648220 100644 --- a/uv.lock +++ b/uv.lock @@ -490,28 +490,32 @@ wheels = [ [[package]] name = "chroma-hnswlib" -version = "0.7.3" +version = "0.7.6" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "numpy" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/c0/59/1224cbae62c7b84c84088cdf6c106b9b2b893783c000d22c442a1672bc75/chroma-hnswlib-0.7.3.tar.gz", hash = "sha256:b6137bedde49fffda6af93b0297fe00429fc61e5a072b1ed9377f909ed95a932", size = 31876 } +sdist = { url = "https://files.pythonhosted.org/packages/73/09/10d57569e399ce9cbc5eee2134996581c957f63a9addfa6ca657daf006b8/chroma_hnswlib-0.7.6.tar.gz", hash = "sha256:4dce282543039681160259d29fcde6151cc9106c6461e0485f57cdccd83059b7", size = 32256 } wheels = [ - { url = "https://files.pythonhosted.org/packages/1a/36/d1069ffa520efcf93f6d81b527e3c7311e12363742fdc786cbdaea3ab02e/chroma_hnswlib-0.7.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:59d6a7c6f863c67aeb23e79a64001d537060b6995c3eca9a06e349ff7b0998ca", size = 219588 }, - { url = "https://files.pythonhosted.org/packages/c3/e8/263d331f5ce29367f6f8854cd7fa1f54fce72ab4f92ab957525ef9165a9c/chroma_hnswlib-0.7.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:d71a3f4f232f537b6152947006bd32bc1629a8686df22fd97777b70f416c127a", size = 197094 }, - { url = "https://files.pythonhosted.org/packages/a9/72/a9b61ae00d490c26359a8e10f3974c0d38065b894e6a2573ec6a7597f8e3/chroma_hnswlib-0.7.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1c92dc1ebe062188e53970ba13f6b07e0ae32e64c9770eb7f7ffa83f149d4210", size = 2315620 }, - { url = "https://files.pythonhosted.org/packages/2f/48/f7609a3cb15a24c5d8ec18911ce10ac94144e9a89584f0a86bf9871b024c/chroma_hnswlib-0.7.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:49da700a6656fed8753f68d44b8cc8ae46efc99fc8a22a6d970dc1697f49b403", size = 2350956 }, - { url = "https://files.pythonhosted.org/packages/cc/3d/ca311b8f79744db3f4faad8fd9140af80d34c94829d3ed1726c98cf4a611/chroma_hnswlib-0.7.3-cp310-cp310-win_amd64.whl", hash = "sha256:108bc4c293d819b56476d8f7865803cb03afd6ca128a2a04d678fffc139af029", size = 150598 }, - { url = "https://files.pythonhosted.org/packages/94/3f/844393b0d2ea1072b7704d6eff5c595e05ae8b831b96340cdb76b2fe995c/chroma_hnswlib-0.7.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:11e7ca93fb8192214ac2b9c0943641ac0daf8f9d4591bb7b73be808a83835667", size = 221219 }, - { url = "https://files.pythonhosted.org/packages/11/7a/673ccb9bb2faf9cf655d9040e970c02a96645966e06837fde7d10edf242a/chroma_hnswlib-0.7.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:6f552e4d23edc06cdeb553cdc757d2fe190cdeb10d43093d6a3319f8d4bf1c6b", size = 198652 }, - { url = "https://files.pythonhosted.org/packages/ba/f4/c81a40da5473d5d80fc9d0c5bd5b1cb64e530a6ea941c69f195fe81c488c/chroma_hnswlib-0.7.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f96f4d5699e486eb1fb95849fe35ab79ab0901265805be7e60f4eaa83ce263ec", size = 2332260 }, - { url = "https://files.pythonhosted.org/packages/48/0e/068b658a547d6090b969014146321e28dae1411da54b76d081e51a2af22b/chroma_hnswlib-0.7.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:368e57fe9ebae05ee5844840fa588028a023d1182b0cfdb1d13f607c9ea05756", size = 2367211 }, - { url = "https://files.pythonhosted.org/packages/d2/32/a91850c7aa8a34f61838913155103808fe90da6f1ea4302731b59e9ba6f2/chroma_hnswlib-0.7.3-cp311-cp311-win_amd64.whl", hash = "sha256:b7dca27b8896b494456db0fd705b689ac6b73af78e186eb6a42fea2de4f71c6f", size = 151574 }, + { url = "https://files.pythonhosted.org/packages/a8/74/b9dde05ea8685d2f8c4681b517e61c7887e974f6272bb24ebc8f2105875b/chroma_hnswlib-0.7.6-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:f35192fbbeadc8c0633f0a69c3d3e9f1a4eab3a46b65458bbcbcabdd9e895c36", size = 195821 }, + { url = "https://files.pythonhosted.org/packages/fd/58/101bfa6bc41bc6cc55fbb5103c75462a7bf882e1704256eb4934df85b6a8/chroma_hnswlib-0.7.6-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:6f007b608c96362b8f0c8b6b2ac94f67f83fcbabd857c378ae82007ec92f4d82", size = 183854 }, + { url = "https://files.pythonhosted.org/packages/17/ff/95d49bb5ce134f10d6aa08d5f3bec624eaff945f0b17d8c3fce888b9a54a/chroma_hnswlib-0.7.6-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:456fd88fa0d14e6b385358515aef69fc89b3c2191706fd9aee62087b62aad09c", size = 2358774 }, + { url = "https://files.pythonhosted.org/packages/3a/6d/27826180a54df80dbba8a4f338b022ba21c0c8af96fd08ff8510626dee8f/chroma_hnswlib-0.7.6-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5dfaae825499c2beaa3b75a12d7ec713b64226df72a5c4097203e3ed532680da", size = 2392739 }, + { url = "https://files.pythonhosted.org/packages/d6/63/ee3e8b7a8f931918755faacf783093b61f32f59042769d9db615999c3de0/chroma_hnswlib-0.7.6-cp310-cp310-win_amd64.whl", hash = "sha256:2487201982241fb1581be26524145092c95902cb09fc2646ccfbc407de3328ec", size = 150955 }, + { url = "https://files.pythonhosted.org/packages/f5/af/d15fdfed2a204c0f9467ad35084fbac894c755820b203e62f5dcba2d41f1/chroma_hnswlib-0.7.6-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:81181d54a2b1e4727369486a631f977ffc53c5533d26e3d366dda243fb0998ca", size = 196911 }, + { url = "https://files.pythonhosted.org/packages/0d/19/aa6f2139f1ff7ad23a690ebf2a511b2594ab359915d7979f76f3213e46c4/chroma_hnswlib-0.7.6-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:4b4ab4e11f1083dd0a11ee4f0e0b183ca9f0f2ed63ededba1935b13ce2b3606f", size = 185000 }, + { url = "https://files.pythonhosted.org/packages/79/b1/1b269c750e985ec7d40b9bbe7d66d0a890e420525187786718e7f6b07913/chroma_hnswlib-0.7.6-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:53db45cd9173d95b4b0bdccb4dbff4c54a42b51420599c32267f3abbeb795170", size = 2377289 }, + { url = "https://files.pythonhosted.org/packages/c7/2d/d5663e134436e5933bc63516a20b5edc08b4c1b1588b9680908a5f1afd04/chroma_hnswlib-0.7.6-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5c093f07a010b499c00a15bc9376036ee4800d335360570b14f7fe92badcdcf9", size = 2411755 }, + { url = "https://files.pythonhosted.org/packages/3e/79/1bce519cf186112d6d5ce2985392a89528c6e1e9332d680bf752694a4cdf/chroma_hnswlib-0.7.6-cp311-cp311-win_amd64.whl", hash = "sha256:0540b0ac96e47d0aa39e88ea4714358ae05d64bbe6bf33c52f316c664190a6a3", size = 151888 }, + { url = "https://files.pythonhosted.org/packages/93/ac/782b8d72de1c57b64fdf5cb94711540db99a92768d93d973174c62d45eb8/chroma_hnswlib-0.7.6-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:e87e9b616c281bfbe748d01705817c71211613c3b063021f7ed5e47173556cb7", size = 197804 }, + { url = "https://files.pythonhosted.org/packages/32/4e/fd9ce0764228e9a98f6ff46af05e92804090b5557035968c5b4198bc7af9/chroma_hnswlib-0.7.6-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ec5ca25bc7b66d2ecbf14502b5729cde25f70945d22f2aaf523c2d747ea68912", size = 185421 }, + { url = "https://files.pythonhosted.org/packages/d9/3d/b59a8dedebd82545d873235ef2d06f95be244dfece7ee4a1a6044f080b18/chroma_hnswlib-0.7.6-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:305ae491de9d5f3c51e8bd52d84fdf2545a4a2bc7af49765cda286b7bb30b1d4", size = 2389672 }, + { url = "https://files.pythonhosted.org/packages/74/1e/80a033ea4466338824974a34f418e7b034a7748bf906f56466f5caa434b0/chroma_hnswlib-0.7.6-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:822ede968d25a2c88823ca078a58f92c9b5c4142e38c7c8b4c48178894a0a3c5", size = 2436986 }, ] [[package]] name = "chromadb" -version = "0.4.24" +version = "0.5.18" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "bcrypt" }, @@ -519,6 +523,7 @@ dependencies = [ { name = "chroma-hnswlib" }, { name = "fastapi" }, { name = "grpcio" }, + { name = "httpx" }, { name = "importlib-resources" }, { name = "kubernetes" }, { name = "mmh3" }, @@ -531,11 +536,10 @@ dependencies = [ { name = "orjson" }, { name = "overrides" }, { name = "posthog" }, - { name = "pulsar-client" }, { name = "pydantic" }, { name = "pypika" }, { name = "pyyaml" }, - { name = "requests" }, + { name = "rich" }, { name = "tenacity" }, { name = "tokenizers" }, { name = "tqdm" }, @@ -543,9 +547,9 @@ dependencies = [ { name = "typing-extensions" }, { name = "uvicorn", extra = ["standard"] }, ] -sdist = { url = "https://files.pythonhosted.org/packages/47/6b/a5465827d8017b658d18ad1e63d2dc31109dec717c6bd068e82485186f4b/chromadb-0.4.24.tar.gz", hash = "sha256:a5c80b4e4ad9b236ed2d4899a5b9e8002b489293f2881cb2cadab5b199ee1c72", size = 13667084 } +sdist = { url = "https://files.pythonhosted.org/packages/15/95/d1a3f14c864e37d009606b82bd837090902b5e5a8e892fcab07eeaec0438/chromadb-0.5.18.tar.gz", hash = "sha256:cfbb3e5aeeb1dd532b47d80ed9185e8a9886c09af41c8e6123edf94395d76aec", size = 33620708 } wheels = [ - { url = "https://files.pythonhosted.org/packages/cc/63/b7d76109331318423f9cfb89bd89c99e19f5d0b47a5105439a629224d297/chromadb-0.4.24-py3-none-any.whl", hash = "sha256:3a08e237a4ad28b5d176685bd22429a03717fe09d35022fb230d516108da01da", size = 525452 }, + { url = "https://files.pythonhosted.org/packages/82/85/4d2f8b9202153105ad4514ae09e9fe6f3b353a45e44e0ef7eca03dd8b9dc/chromadb-0.5.18-py3-none-any.whl", hash = "sha256:9dd3827b5e04b4ff0a5ea0df28a78bac88a09f45be37fcd7fe20f879b57c43cf", size = 615499 }, ] [[package]] @@ -634,6 +638,9 @@ dependencies = [ agentops = [ { name = "agentops" }, ] +mem0 = [ + { name = "mem0ai" }, +] tools = [ { name = "crewai-tools" }, ] @@ -663,7 +670,7 @@ requires-dist = [ { name = "agentops", marker = "extra == 'agentops'", specifier = ">=0.3.0" }, { name = "appdirs", specifier = ">=1.4.4" }, { name = "auth0-python", specifier = ">=4.7.1" }, - { name = "chromadb", specifier = ">=0.4.24" }, + { name = "chromadb", specifier = ">=0.5.18" }, { name = "click", specifier = ">=8.1.7" }, { name = "crewai-tools", specifier = ">=0.14.0" }, { name = "crewai-tools", marker = "extra == 'tools'", specifier = ">=0.14.0" }, @@ -672,6 +679,7 @@ requires-dist = [ { name = "jsonref", specifier = ">=1.1.0" }, { name = "langchain", specifier = ">=0.2.16" }, { name = "litellm", specifier = ">=1.44.22" }, + { name = "mem0ai", marker = "extra == 'mem0'", specifier = ">=0.1.29" }, { name = "openai", specifier = ">=1.13.3" }, { name = "opentelemetry-api", specifier = ">=1.22.0" }, { name = "opentelemetry-exporter-otlp-proto-http", specifier = ">=1.22.0" }, @@ -889,7 +897,7 @@ wheels = [ [[package]] name = "embedchain" -version = "0.1.123" +version = "0.1.125" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "alembic" }, @@ -914,9 +922,9 @@ dependencies = [ { name = "sqlalchemy" }, { name = "tiktoken" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/5d/6a/955b5a72fa6727db203c4d46ae0e30ac47f4f50389f663cd5ea157b0d819/embedchain-0.1.123.tar.gz", hash = "sha256:aecaf81c21de05b5cdb649b6cde95ef68ffa759c69c54f6ff2eaa667f2ad0580", size = 124797 } +sdist = { url = "https://files.pythonhosted.org/packages/6c/ea/eedb6016719f94fe4bd4c5aa44cc5463d85494bbd0864cc465e4317d4987/embedchain-0.1.125.tar.gz", hash = "sha256:15a6d368b48ba33feb93b237caa54f6e9078537c02a49c1373e59cc32627a138", size = 125176 } wheels = [ - { url = "https://files.pythonhosted.org/packages/a7/51/0c78d26da4afbe68370306669556b274f1021cac02f3155d8da2be407763/embedchain-0.1.123-py3-none-any.whl", hash = "sha256:1210e993b6364d7c702b6bd44b053fc244dd77f2a65ea4b90b62709114ea6c25", size = 210909 }, + { url = "https://files.pythonhosted.org/packages/52/82/3d0355c22bc68cfbb8fbcf670da4c01b31bd7eb516974a08cf7533e89887/embedchain-0.1.125-py3-none-any.whl", hash = "sha256:f87b49732dc192c6b61221830f29e59cf2aff26d8f5d69df81f6f6cf482715c2", size = 211367 }, ] [[package]] @@ -2160,7 +2168,7 @@ wheels = [ [[package]] name = "mem0ai" -version = "0.1.22" +version = "0.1.29" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "openai" }, @@ -2170,9 +2178,9 @@ dependencies = [ { name = "qdrant-client" }, { name = "sqlalchemy" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/7f/b4/64c6f7d9684bd1f9b46d251abfc7d5b2cc8371d70f1f9eec097f9872c719/mem0ai-0.1.22.tar.gz", hash = "sha256:d01aa028763719bd0ede2de4602121a7c3bf023f46112cd50cc9169140e11be2", size = 53117 } +sdist = { url = "https://files.pythonhosted.org/packages/a9/bf/152718f9da3844dd24d4c45850b2e719798b5ce9389adf4ec873ee8905ca/mem0ai-0.1.29.tar.gz", hash = "sha256:42adefb7a9b241be03fbcabadf5328abf91b4ac390bc97e5966e55e3cac192c5", size = 55201 } wheels = [ - { url = "https://files.pythonhosted.org/packages/2b/27/3ef75abb28bf8b46c2cc34730f6be733ef2584652474216215019ee036a2/mem0ai-0.1.22-py3-none-any.whl", hash = "sha256:c783e15131c16a0d91e44e30195c1eeae9c36468de40006d5e42cf4516059855", size = 75695 }, + { url = "https://files.pythonhosted.org/packages/65/9b/755be84f669415b3b513cfd935e768c4c84ac5c1ab6ff6ac2dab990a261a/mem0ai-0.1.29-py3-none-any.whl", hash = "sha256:07bbfd4238d0d7da65d5e4cf75a217eeb5b2829834e399074b05bb046730a57f", size = 79558 }, ] [[package]] @@ -3202,34 +3210,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/22/a6/858897256d0deac81a172289110f31629fc4cee19b6f01283303e18c8db3/ptyprocess-0.7.0-py2.py3-none-any.whl", hash = "sha256:4b41f3967fce3af57cc7e94b888626c18bf37a083e3651ca8feeb66d492fef35", size = 13993 }, ] -[[package]] -name = "pulsar-client" -version = "3.5.0" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "certifi" }, -] -wheels = [ - { url = "https://files.pythonhosted.org/packages/e0/aa/eb3b04be87b961324e49748f3a715a12127d45d76258150bfa61b2a002d8/pulsar_client-3.5.0-cp310-cp310-macosx_10_15_universal2.whl", hash = "sha256:c18552edb2f785de85280fe624bc507467152bff810fc81d7660fa2dfa861f38", size = 10953552 }, - { url = "https://files.pythonhosted.org/packages/cc/20/d59bf89ccdda45edd89f5b54bd1e93605ebe5ad3cb73f4f4f5e8eca8f9e6/pulsar_client-3.5.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:18d438e456c146f01be41ef146f649dedc8f7bc714d9eaef94cff2e34099812b", size = 5190714 }, - { url = "https://files.pythonhosted.org/packages/1a/02/ca7e96b97d564d0375b8e3de65f95ac86c8502c40f6ff750e9d145709d9a/pulsar_client-3.5.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:18a26a0719841103c7a89eb1492c4a8fedf89adaa386375baecbb4fa2707e88f", size = 5429820 }, - { url = "https://files.pythonhosted.org/packages/47/f3/682670cdc951b830cd3d8d1287521997327254e59508772664aaa656e246/pulsar_client-3.5.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:ab0e1605dc5f44a126163fd06cd0a768494ad05123f6e0de89a2c71d6e2d2319", size = 5710427 }, - { url = "https://files.pythonhosted.org/packages/bc/00/119cd039286dfc1c91a5580963e9ba79204cd4717b16b7a6fdc57d1c1673/pulsar_client-3.5.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:cdef720891b97656fdce3bf5913ea7729b2156b84ba64314f432c1e72c6117fa", size = 5916490 }, - { url = "https://files.pythonhosted.org/packages/0a/cc/d606b483dbb263cbaf7fc7c3d2ec4032628cf3324266cf9a4ccdb2a73076/pulsar_client-3.5.0-cp310-cp310-win_amd64.whl", hash = "sha256:a42544e38773191fe550644a90e8050579476bb2dcf17ac69a4aed62a6cb70e7", size = 3305387 }, - { url = "https://files.pythonhosted.org/packages/0d/2e/aec6886a6d67f09230476182399b7fad694fbcbbaf004ce914725d4eddd9/pulsar_client-3.5.0-cp311-cp311-macosx_10_15_universal2.whl", hash = "sha256:fd94432ea5d398ea78f8f2e09a217ec5058d26330c137a22690478c031e116da", size = 10954116 }, - { url = "https://files.pythonhosted.org/packages/43/06/b98df9300f60e5fad3396f843dd633c31176a495a2d60ba111c99511658a/pulsar_client-3.5.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d6252ae462e07ece4071213fdd9c76eab82ca522a749f2dc678037d4cbacd40b", size = 5189618 }, - { url = "https://files.pythonhosted.org/packages/72/05/c9aef7da7802a03c0b65ffe8f00a24289ff992f99ed5d5d1fd0ed63d9cf6/pulsar_client-3.5.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:03b4d440b2d74323784328b082872ee2f206c440b5d224d7941eb3c083ec06c6", size = 5429329 }, - { url = "https://files.pythonhosted.org/packages/06/96/9acfe6f1d827cdd53b8460b04c63b4081333ef64a49a2f425419f1eb6b6b/pulsar_client-3.5.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:f60af840b8d64a2fac5a0c1ce6ae0ddffec5f42267c6ded2c5e74bad8345f2a1", size = 5710106 }, - { url = "https://files.pythonhosted.org/packages/e1/7b/877a06eff5c9ac828cdb75e378ee29b0adac9328da9ee173eaf7076d8c56/pulsar_client-3.5.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:2277a447c3b7f6571cb1eb9fc5c25da3fdd43d0b2fb91cf52054adfadc7d6842", size = 5916541 }, - { url = "https://files.pythonhosted.org/packages/fb/62/ed1da1ef72c95ba6a830e43995550ed0a1d26c223fb4b036ac6cd028c2ed/pulsar_client-3.5.0-cp311-cp311-win_amd64.whl", hash = "sha256:f20f3e9dd50db2a37059abccad42078b7a4754b8bc1d3ae6502e71c1ad2209f0", size = 3305485 }, - { url = "https://files.pythonhosted.org/packages/81/19/4b145766df706aa5e09f60bbf5f87b934e6ac950fddd18f4acd520c465b9/pulsar_client-3.5.0-cp312-cp312-macosx_10_15_universal2.whl", hash = "sha256:d61f663d85308e12f44033ba95af88730f581a7e8da44f7a5c080a3aaea4878d", size = 10967548 }, - { url = "https://files.pythonhosted.org/packages/bf/bd/9bc05ee861b46884554a4c61f96edb9602de131dd07982c27920e554ab5b/pulsar_client-3.5.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2a1ba0be25b6f747bcb28102b7d906ec1de48dc9f1a2d9eacdcc6f44ab2c9e17", size = 5189598 }, - { url = "https://files.pythonhosted.org/packages/76/00/379bedfa6f1c810553996a4cb0984fa2e2c89afc5953df0936e1c9636003/pulsar_client-3.5.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a181e3e60ac39df72ccb3c415d7aeac61ad0286497a6e02739a560d5af28393a", size = 5430145 }, - { url = "https://files.pythonhosted.org/packages/88/c8/8a37d75aa9132a69a28061c9e5f4b516328a1968b58bbae018f431c6d3d4/pulsar_client-3.5.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:3c72895ff7f51347e4f78b0375b2213fa70dd4790bbb78177b4002846f1fd290", size = 5708960 }, - { url = "https://files.pythonhosted.org/packages/6e/9a/abd98661e3f7ae3a8e1d3fb0fc7eba1a30005391ebd575ab06a66021256c/pulsar_client-3.5.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:547dba1b185a17eba915e51d0a3aca27c80747b6187e5cd7a71a3ca33921decc", size = 5915227 }, - { url = "https://files.pythonhosted.org/packages/a2/51/db376181d05716de595515fac736e3d06e96d3345ba0e31c0a90c352eae1/pulsar_client-3.5.0-cp312-cp312-win_amd64.whl", hash = "sha256:443b786eed96bc86d2297a6a42e79f39d1abf217ec603e0bd303f3488c0234af", size = 3306515 }, -] - [[package]] name = "pure-eval" version = "0.2.3" From 8376698534b4c3012443760eb967b58fa43c111d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Moura?= Date: Sun, 17 Nov 2024 04:07:21 -0300 Subject: [PATCH 064/126] preparing enw version --- pyproject.toml | 2 +- src/crewai/__init__.py | 2 +- src/crewai/cli/templates/crew/pyproject.toml | 2 +- src/crewai/cli/templates/flow/pyproject.toml | 2 +- src/crewai/cli/templates/pipeline/pyproject.toml | 2 +- src/crewai/cli/templates/pipeline_router/pyproject.toml | 2 +- src/crewai/cli/templates/tool/pyproject.toml | 2 +- uv.lock | 2 +- 8 files changed, 8 insertions(+), 8 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 57868636f..5955baf33 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "crewai" -version = "0.79.4" +version = "0.80.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" diff --git a/src/crewai/__init__.py b/src/crewai/__init__.py index 8d01b7cb5..fbad09663 100644 --- a/src/crewai/__init__.py +++ b/src/crewai/__init__.py @@ -14,5 +14,5 @@ warnings.filterwarnings( category=UserWarning, module="pydantic.main", ) -__version__ = "0.79.4" +__version__ = "0.80.0" __all__ = ["Agent", "Crew", "Process", "Task", "Pipeline", "Router", "LLM", "Flow"] diff --git a/src/crewai/cli/templates/crew/pyproject.toml b/src/crewai/cli/templates/crew/pyproject.toml index 8606a2134..a5ab36877 100644 --- a/src/crewai/cli/templates/crew/pyproject.toml +++ b/src/crewai/cli/templates/crew/pyproject.toml @@ -5,7 +5,7 @@ description = "{{name}} using crewAI" authors = [{ name = "Your Name", email = "you@example.com" }] requires-python = ">=3.10,<=3.13" dependencies = [ - "crewai[tools]>=0.79.4,<1.0.0" + "crewai[tools]>=0.80.0,<1.0.0" ] [project.scripts] diff --git a/src/crewai/cli/templates/flow/pyproject.toml b/src/crewai/cli/templates/flow/pyproject.toml index 69e26f220..e863f20b7 100644 --- a/src/crewai/cli/templates/flow/pyproject.toml +++ b/src/crewai/cli/templates/flow/pyproject.toml @@ -5,7 +5,7 @@ description = "{{name}} using crewAI" authors = [{ name = "Your Name", email = "you@example.com" }] requires-python = ">=3.10,<=3.13" dependencies = [ - "crewai[tools]>=0.79.4,<1.0.0", + "crewai[tools]>=0.80.0,<1.0.0", ] [project.scripts] diff --git a/src/crewai/cli/templates/pipeline/pyproject.toml b/src/crewai/cli/templates/pipeline/pyproject.toml index b09ce842f..60294740d 100644 --- a/src/crewai/cli/templates/pipeline/pyproject.toml +++ b/src/crewai/cli/templates/pipeline/pyproject.toml @@ -6,7 +6,7 @@ authors = ["Your Name "] [tool.poetry.dependencies] python = ">=3.10,<=3.13" -crewai = { extras = ["tools"], version = ">=0.79.4,<1.0.0" } +crewai = { extras = ["tools"], version = ">=0.80.0,<1.0.0" } asyncio = "*" [tool.poetry.scripts] diff --git a/src/crewai/cli/templates/pipeline_router/pyproject.toml b/src/crewai/cli/templates/pipeline_router/pyproject.toml index 16d6360db..7c022b1f7 100644 --- a/src/crewai/cli/templates/pipeline_router/pyproject.toml +++ b/src/crewai/cli/templates/pipeline_router/pyproject.toml @@ -5,7 +5,7 @@ description = "{{name}} using crewAI" authors = ["Your Name "] requires-python = ">=3.10,<=3.13" dependencies = [ - "crewai[tools]>=0.79.4,<1.0.0" + "crewai[tools]>=0.80.0,<1.0.0" ] [project.scripts] diff --git a/src/crewai/cli/templates/tool/pyproject.toml b/src/crewai/cli/templates/tool/pyproject.toml index dc928ad1e..4142c325d 100644 --- a/src/crewai/cli/templates/tool/pyproject.toml +++ b/src/crewai/cli/templates/tool/pyproject.toml @@ -5,6 +5,6 @@ description = "Power up your crews with {{folder_name}}" readme = "README.md" requires-python = ">=3.10,<=3.13" dependencies = [ - "crewai[tools]>=0.79.4" + "crewai[tools]>=0.80.0" ] diff --git a/uv.lock b/uv.lock index f19648220..ab78a63d0 100644 --- a/uv.lock +++ b/uv.lock @@ -608,7 +608,7 @@ wheels = [ [[package]] name = "crewai" -version = "0.79.4" +version = "0.80.0" source = { editable = "." } dependencies = [ { name = "appdirs" }, From 0b9092702bc39f2f8bf823fa3543b9056404e5f4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Moura?= Date: Mon, 18 Nov 2024 00:21:19 -0300 Subject: [PATCH 065/126] adding before and after crew --- .gitignore | 4 + src/crewai/project/__init__.py | 4 + src/crewai/project/annotations.py | 10 + src/crewai/project/crew_base.py | 58 +- .../test_after_crew_modification.yaml | 449 ++++++++++++++ .../test_before_crew_modification.yaml | 445 ++++++++++++++ .../test_before_crew_with_none_input.yaml | 438 ++++++++++++++ .../cassettes/test_crew_execution_order.yaml | 450 ++++++++++++++ .../test_multiple_before_after_crew.yaml | 565 ++++++++++++++++++ tests/config/agents.yaml | 21 + tests/config/tasks.yaml | 17 + tests/project_test.py | 172 +++++- 12 files changed, 2624 insertions(+), 9 deletions(-) create mode 100644 tests/cassettes/test_after_crew_modification.yaml create mode 100644 tests/cassettes/test_before_crew_modification.yaml create mode 100644 tests/cassettes/test_before_crew_with_none_input.yaml create mode 100644 tests/cassettes/test_crew_execution_order.yaml create mode 100644 tests/cassettes/test_multiple_before_after_crew.yaml create mode 100644 tests/config/agents.yaml create mode 100644 tests/config/tasks.yaml diff --git a/.gitignore b/.gitignore index ad64db4e7..8559a290c 100644 --- a/.gitignore +++ b/.gitignore @@ -17,3 +17,7 @@ rc-tests/* temp/* .vscode/* crew_tasks_output.json +.codesight +.mypy_cache +.ruff_cache +.venv diff --git a/src/crewai/project/__init__.py b/src/crewai/project/__init__.py index 34759f465..1dbb6e41b 100644 --- a/src/crewai/project/__init__.py +++ b/src/crewai/project/__init__.py @@ -9,6 +9,8 @@ from .annotations import ( pipeline, task, tool, + before_crew, + after_crew, ) from .crew_base import CrewBase from .pipeline_base import PipelineBase @@ -26,4 +28,6 @@ __all__ = [ "llm", "cache_handler", "pipeline", + "before_crew", + "after_crew", ] diff --git a/src/crewai/project/annotations.py b/src/crewai/project/annotations.py index 6c0b9942a..c1a497fa1 100644 --- a/src/crewai/project/annotations.py +++ b/src/crewai/project/annotations.py @@ -5,6 +5,16 @@ from crewai import Crew from crewai.project.utils import memoize +def before_crew(func): + func.is_before_crew = True + return func + + +def after_crew(func): + func.is_after_crew = True + return func + + def task(func): func.is_task = True diff --git a/src/crewai/project/crew_base.py b/src/crewai/project/crew_base.py index a420c4dd2..ed48b24d1 100644 --- a/src/crewai/project/crew_base.py +++ b/src/crewai/project/crew_base.py @@ -34,18 +34,39 @@ def CrewBase(cls: T) -> T: self.map_all_agent_variables() self.map_all_task_variables() - # Preserve task and agent information - self._original_tasks = { + # Preserve all decorated functions + self._original_functions = { name: method for name, method in cls.__dict__.items() - if hasattr(method, "is_task") and method.is_task - } - self._original_agents = { - name: method - for name, method in cls.__dict__.items() - if hasattr(method, "is_agent") and method.is_agent + if any( + hasattr(method, attr) + for attr in [ + "is_task", + "is_agent", + "is_before_crew", + "is_after_crew", + "is_kickoff", + ] + ) } + # Store specific function types + self._original_tasks = self._filter_functions( + self._original_functions, "is_task" + ) + self._original_agents = self._filter_functions( + self._original_functions, "is_agent" + ) + self._before_crew = self._filter_functions( + self._original_functions, "is_before_crew" + ) + self._after_crew = self._filter_functions( + self._original_functions, "is_after_crew" + ) + self._kickoff = self._filter_functions( + self._original_functions, "is_kickoff" + ) + @staticmethod def load_yaml(config_path: Path): try: @@ -192,4 +213,25 @@ def CrewBase(cls: T) -> T: callback_functions[callback]() for callback in callbacks ] + def kickoff(self, inputs=None): + # Execute before_crew functions and allow them to modify inputs + for _, func in self._before_crew.items(): + modified_inputs = func(self, inputs) + if modified_inputs is not None: + inputs = modified_inputs + + # Get the crew instance + crew_instance = self.crew() + + # Execute the crew's tasks + result = crew_instance.kickoff(inputs=inputs) + + # Execute after_crew functions and allow them to modify the output + for _, func in self._after_crew.items(): + modified_result = func(self, result) + if modified_result is not None: + result = modified_result + + return result + return cast(T, WrappedClass) diff --git a/tests/cassettes/test_after_crew_modification.yaml b/tests/cassettes/test_after_crew_modification.yaml new file mode 100644 index 000000000..e9d6926b0 --- /dev/null +++ b/tests/cassettes/test_after_crew_modification.yaml @@ -0,0 +1,449 @@ +interactions: +- request: + body: !!binary | + CuMOCiQKIgoMc2VydmljZS5uYW1lEhIKEGNyZXdBSS10ZWxlbWV0cnkSug4KEgoQY3Jld2FpLnRl + bGVtZXRyeRKSDAoQK+dPhrB8w3HKFlxX60XzYRIIk5aB+A8oCWQqDENyZXcgQ3JlYXRlZDABObix + K+HWrwgYQcBiMeHWrwgYShoKDmNyZXdhaV92ZXJzaW9uEggKBjAuODAuMEoaCg5weXRob25fdmVy + c2lvbhIICgYzLjExLjdKLgoIY3Jld19rZXkSIgogZjM0NmE5YWQ2ZDczMDYzZTA2NzdiMTdjZTlj + NTAxNzdKMQoHY3Jld19pZBImCiQ3NjRjZWM1YS04NzkxLTRmN2MtOWY0MC1hNTMzMzJmOTk3YzBK + HAoMY3Jld19wcm9jZXNzEgwKCnNlcXVlbnRpYWxKEQoLY3Jld19tZW1vcnkSAhAAShoKFGNyZXdf + bnVtYmVyX29mX3Rhc2tzEgIYAkobChVjcmV3X251bWJlcl9vZl9hZ2VudHMSAhgCSqwFCgtjcmV3 + X2FnZW50cxKcBQqZBVt7ImtleSI6ICI3M2MzNDljOTNjMTYzYjVkNGRmOThhNjRmYWMxYzQzMCIs + ICJpZCI6ICJjZDgwYjlhNy1hN2QzLTQzNTQtYjUyOC1jMzAyODA0MjA3YzgiLCAicm9sZSI6ICJ7 + dG9waWN9IFNlbmlvciBEYXRhIFJlc2VhcmNoZXJcbiIsICJ2ZXJib3NlPyI6IGZhbHNlLCAibWF4 + X2l0ZXIiOiAyMCwgIm1heF9ycG0iOiBudWxsLCAiZnVuY3Rpb25fY2FsbGluZ19sbG0iOiAiIiwg + ImxsbSI6ICJncHQtNG8iLCAiZGVsZWdhdGlvbl9lbmFibGVkPyI6IGZhbHNlLCAiYWxsb3dfY29k + ZV9leGVjdXRpb24/IjogZmFsc2UsICJtYXhfcmV0cnlfbGltaXQiOiAyLCAidG9vbHNfbmFtZXMi + OiBbXX0sIHsia2V5IjogImJiMDY4Mzc3YzE2NDFiZTZkN2Q5N2E1MTY1OWRiNjEzIiwgImlkIjog + ImJmZjc3YmUyLWU4MjQtNGEyOS1hZTFlLTQyMWFjMzc2MjY2YyIsICJyb2xlIjogInt0b3BpY30g + UmVwb3J0aW5nIEFuYWx5c3RcbiIsICJ2ZXJib3NlPyI6IGZhbHNlLCAibWF4X2l0ZXIiOiAyMCwg + Im1heF9ycG0iOiBudWxsLCAiZnVuY3Rpb25fY2FsbGluZ19sbG0iOiAiIiwgImxsbSI6ICJncHQt + NG8iLCAiZGVsZWdhdGlvbl9lbmFibGVkPyI6IGZhbHNlLCAiYWxsb3dfY29kZV9leGVjdXRpb24/ + IjogZmFsc2UsICJtYXhfcmV0cnlfbGltaXQiOiAyLCAidG9vbHNfbmFtZXMiOiBbXX1dSpMECgpj + cmV3X3Rhc2tzEoQECoEEW3sia2V5IjogIjZhZmM0YjM5NjI1OWZiYjc2ODFmNTZjNzc1NWNjOTM3 + IiwgImlkIjogIjRmNTFlYzM2LTVlMDctNGU4Ni1iYzIxLWU1MTQ0Mzg2YmIyYSIsICJhc3luY19l + eGVjdXRpb24/IjogZmFsc2UsICJodW1hbl9pbnB1dD8iOiBmYWxzZSwgImFnZW50X3JvbGUiOiAi + e3RvcGljfSBTZW5pb3IgRGF0YSBSZXNlYXJjaGVyXG4iLCAiYWdlbnRfa2V5IjogIjczYzM0OWM5 + M2MxNjNiNWQ0ZGY5OGE2NGZhYzFjNDMwIiwgInRvb2xzX25hbWVzIjogW119LCB7ImtleSI6ICJi + MTdiMTg4ZGJmMTRmOTNhOThlNWI5NWFhZDM2NzU3NyIsICJpZCI6ICIwMGJmZDY5ZC03OWZiLTRj + MjctYTM0Yi02NzBkZWJlMzU0NWYiLCAiYXN5bmNfZXhlY3V0aW9uPyI6IGZhbHNlLCAiaHVtYW5f + aW5wdXQ/IjogZmFsc2UsICJhZ2VudF9yb2xlIjogInt0b3BpY30gUmVwb3J0aW5nIEFuYWx5c3Rc + biIsICJhZ2VudF9rZXkiOiAiYmIwNjgzNzdjMTY0MWJlNmQ3ZDk3YTUxNjU5ZGI2MTMiLCAidG9v + bHNfbmFtZXMiOiBbXX1degIYAYUBAAEAABKOAgoQsN5cQC9ZzBr2B0OKBR2WCxII3ULL7Wk965Yq + DFRhc2sgQ3JlYXRlZDABOWB9ROHWrwgYQfg0ReHWrwgYSi4KCGNyZXdfa2V5EiIKIGYzNDZhOWFk + NmQ3MzA2M2UwNjc3YjE3Y2U5YzUwMTc3SjEKB2NyZXdfaWQSJgokNzY0Y2VjNWEtODc5MS00Zjdj + LTlmNDAtYTUzMzMyZjk5N2MwSi4KCHRhc2tfa2V5EiIKIDZhZmM0YjM5NjI1OWZiYjc2ODFmNTZj + Nzc1NWNjOTM3SjEKB3Rhc2tfaWQSJgokNGY1MWVjMzYtNWUwNy00ZTg2LWJjMjEtZTUxNDQzODZi + YjJhegIYAYUBAAEAAA== + headers: + Accept: + - '*/*' + Accept-Encoding: + - gzip, deflate + Connection: + - keep-alive + Content-Length: + - '1894' + Content-Type: + - application/x-protobuf + User-Agent: + - OTel-OTLP-Exporter-Python/1.27.0 + method: POST + uri: https://telemetry.crewai.com:4319/v1/traces + response: + body: + string: "\n\0" + headers: + Content-Length: + - '2' + Content-Type: + - application/x-protobuf + Date: + - Sun, 17 Nov 2024 07:09:57 GMT + status: + code: 200 + message: OK +- request: + body: '{"messages": [{"role": "system", "content": "You are LLMs Senior Data Researcher\n. + You''re a seasoned researcher with a knack for uncovering the latest developments + in LLMs. Known for your ability to find the most relevant information and present + it in a clear and concise manner.\n\nYour personal goal is: Uncover cutting-edge + developments in LLMs\n\nTo give my best complete final answer to the task use + the exact following format:\n\nThought: I now can give a great answer\nFinal + Answer: Your final answer must be the great and the most complete as possible, + it must be outcome described.\n\nI MUST use these formats, my job depends on + it!"}, {"role": "user", "content": "\nCurrent Task: Conduct a thorough research + about LLMs Make sure you find any interesting and relevant information given + the current year is 2024.\n\n\nThis is the expect criteria for your final answer: + A list with 10 bullet points of the most relevant information about LLMs\n\nyou + MUST return the actual complete content as the final answer, not a summary.\n\nBegin! + This is VERY important to you, use the tools available and give your best Final + Answer, your job depends on it!\n\nThought:"}], "model": "gpt-4o", "stop": ["\nObservation:"], + "stream": false}' + headers: + accept: + - application/json + accept-encoding: + - gzip, deflate + connection: + - keep-alive + content-length: + - '1235' + content-type: + - application/json + cookie: + - __cf_bm=08pKRcLhS1PDw0mYfL2jz19ac6M.T31GoiMuI5DlX6w-1731827382-1.0.1.1-UfOLu3AaIUuXP1sGzdV6oggJ1q7iMTC46t08FDhYVrKcW5YmD4CbifudOJiSgx8h0JLTwZdgk.aG05S0eAO_PQ; + _cfuvid=74kaPOoAcp8YRSA0XocQ1FFNksu9V0_KiWdQfo7wQuQ-1731827382509-0.0.1.1-604800000 + host: + - api.openai.com + user-agent: + - OpenAI/Python 1.52.1 + x-stainless-arch: + - arm64 + x-stainless-async: + - 'false' + x-stainless-lang: + - python + x-stainless-os: + - MacOS + x-stainless-package-version: + - 1.52.1 + x-stainless-raw-response: + - 'true' + x-stainless-retry-count: + - '0' + x-stainless-runtime: + - CPython + x-stainless-runtime-version: + - 3.11.7 + method: POST + uri: https://api.openai.com/v1/chat/completions + response: + body: + string: !!binary | + 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 + AhiZyAcAAP//AwCidyXxtw8AAA== + headers: + CF-Cache-Status: + - DYNAMIC + CF-RAY: + - 8e3de5de7b6c6217-GRU + Connection: + - keep-alive + Content-Encoding: + - gzip + Content-Type: + - application/json + Date: + - Sun, 17 Nov 2024 07:10:00 GMT + Server: + - cloudflare + Transfer-Encoding: + - chunked + X-Content-Type-Options: + - nosniff + access-control-expose-headers: + - X-Request-ID + alt-svc: + - h3=":443"; ma=86400 + openai-organization: + - crewai-iuxna1 + openai-processing-ms: + - '6026' + openai-version: + - '2020-10-01' + strict-transport-security: + - max-age=31536000; includeSubDomains; preload + x-ratelimit-limit-requests: + - '10000' + x-ratelimit-limit-tokens: + - '30000000' + x-ratelimit-remaining-requests: + - '9999' + x-ratelimit-remaining-tokens: + - '29999713' + x-ratelimit-reset-requests: + - 6ms + x-ratelimit-reset-tokens: + - 0s + x-request-id: + - req_553f04a622d026a28dd3c5da55568fcd + status: + code: 200 + message: OK +- request: + body: !!binary | + Cs4CCiQKIgoMc2VydmljZS5uYW1lEhIKEGNyZXdBSS10ZWxlbWV0cnkSpQIKEgoQY3Jld2FpLnRl + bGVtZXRyeRKOAgoQIn+FuHJydyMnR3y/Qfb8GBII2zXFs4gynEgqDFRhc2sgQ3JlYXRlZDABOShs + 9ljYrwgYQQiP+FjYrwgYSi4KCGNyZXdfa2V5EiIKIGYzNDZhOWFkNmQ3MzA2M2UwNjc3YjE3Y2U5 + YzUwMTc3SjEKB2NyZXdfaWQSJgokNzY0Y2VjNWEtODc5MS00ZjdjLTlmNDAtYTUzMzMyZjk5N2Mw + Si4KCHRhc2tfa2V5EiIKIGIxN2IxODhkYmYxNGY5M2E5OGU1Yjk1YWFkMzY3NTc3SjEKB3Rhc2tf + aWQSJgokMDBiZmQ2OWQtNzlmYi00YzI3LWEzNGItNjcwZGViZTM1NDVmegIYAYUBAAEAAA== + headers: + Accept: + - '*/*' + Accept-Encoding: + - gzip, deflate + Connection: + - keep-alive + Content-Length: + - '337' + Content-Type: + - application/x-protobuf + User-Agent: + - OTel-OTLP-Exporter-Python/1.27.0 + method: POST + uri: https://telemetry.crewai.com:4319/v1/traces + response: + body: + string: "\n\0" + headers: + Content-Length: + - '2' + Content-Type: + - application/x-protobuf + Date: + - Sun, 17 Nov 2024 07:10:01 GMT + status: + code: 200 + message: OK +- request: + body: '{"messages": [{"role": "system", "content": "You are LLMs Reporting Analyst\n. + You''re a meticulous analyst with a keen eye for detail. You''re known for your + ability to turn complex data into clear and concise reports, making it easy + for others to understand and act on the information you provide.\nYour personal + goal is: Create detailed reports based on LLMs data analysis and research findings\n\nTo + give my best complete final answer to the task use the exact following format:\n\nThought: + I now can give a great answer\nFinal Answer: Your final answer must be the great + and the most complete as possible, it must be outcome described.\n\nI MUST use + these formats, my job depends on it!"}, {"role": "user", "content": "\nCurrent + Task: Review the context you got and expand each topic into a full section for + a report. Make sure the report is detailed and contains any and all relevant + information.\n\n\nThis is the expect criteria for your final answer: A fully + fledge reports with the mains topics, each with a full section of information. + Formatted as markdown without ''```''\n\nyou MUST return the actual complete + content as the final answer, not a summary.\n\nThis is the context you''re working + with:\n1. **OpenAI''s GPT-4 Released**: OpenAI released GPT-4, which further + improves contextual understanding and generation capabilities. It offers increased + accuracy, creativity, and coherence in responses compared to its predecessors, + making it an essential tool for businesses, educators, and developers.\n\n2. + **Google''s Gemini Model**: In 2024, Google launched the Gemini model, focusing + on merging language understanding with multimodal capabilities. This model can + process text, audio, and images simultaneously, enhancing its applications in + fields like voice assistants and image captioning.\n\n3. **Anthropic''s Claude**: + Claude, by Anthropic, is designed to prioritize safety and ethical considerations + in LLM usage. It''s built to minimize harmful outputs and biases prevalent in + earlier language models, demonstrating a shift towards responsible AI deployment.\n\n4. + **Meta''s Open Pre-trained Transformer (OPT) 3.0**: Meta has introduced OPT + 3.0, boasting efficient training approaches that lower computational costs while + maintaining high performance. The model excels in translation tasks and has + become a popular choice for academic research due to its open-access policy.\n\n5. + **Language Model Distillation Advances**: Recent advances in model distillation + techniques have allowed developers to deploy smaller, more efficient language + models on edge devices without notably compromising performance, expanding the + accessibility and application of LLMs in mobile and IoT devices.\n\n6. **Fine-Tuning + with Limited Data**: Methods for fine-tuning LLMs with limited data have improved, + enabling customization for niche applications without the need for vast datasets. + This development has opened doors to using LLMs in specialized industries, like + legal and medical sectors.\n\n7. **Ethical and Transparent AI Use**: 2024 has + seen a significant emphasis on ethical AI, with organizations establishing standardized + frameworks for LLM transparency and accountability. More companies are adopting + practices like AI model cards to disclose model capabilities, limitations, and + data sources.\n\n8. **The Rise of Prompt Engineering**: As models become more + advanced, prompt engineering has emerged as a crucial technique for optimizing + model outputs. This involves designing specific input prompts that guide LLMs + to yield desired results, thus enhancing usability in content creation and customer + service.\n\n9. **Integration with Augmented Reality**: Language models in 2024 + are increasingly being integrated with AR technologies to provide real-time + language translation, virtual assistants in immersive environments, and interactive + educational tools, enriching the user''s experience with contextualized AI assistance.\n\n10. + **Regulatory Developments**: Governments worldwide are progressing towards formalizing + regulations around LLM usage, focusing on data privacy, security, and ethical + concerns. These developments aim to safeguard users while encouraging innovation + in AI technology.\n\nThese points reflect the cutting-edge advancements and + trends in the field of large language models (LLMs) as of 2024, highlighting + their growing influence and the increasing focus on ethical and practical deployment.\n\nBegin! + This is VERY important to you, use the tools available and give your best Final + Answer, your job depends on it!\n\nThought:"}], "model": "gpt-4o", "stop": ["\nObservation:"], + "stream": false}' + headers: + accept: + - application/json + accept-encoding: + - gzip, deflate + connection: + - keep-alive + content-length: + - '4624' + content-type: + - application/json + cookie: + - __cf_bm=08pKRcLhS1PDw0mYfL2jz19ac6M.T31GoiMuI5DlX6w-1731827382-1.0.1.1-UfOLu3AaIUuXP1sGzdV6oggJ1q7iMTC46t08FDhYVrKcW5YmD4CbifudOJiSgx8h0JLTwZdgk.aG05S0eAO_PQ; + _cfuvid=74kaPOoAcp8YRSA0XocQ1FFNksu9V0_KiWdQfo7wQuQ-1731827382509-0.0.1.1-604800000 + host: + - api.openai.com + user-agent: + - OpenAI/Python 1.52.1 + x-stainless-arch: + - arm64 + x-stainless-async: + - 'false' + x-stainless-lang: + - python + x-stainless-os: + - MacOS + x-stainless-package-version: + - 1.52.1 + x-stainless-raw-response: + - 'true' + x-stainless-retry-count: + - '0' + x-stainless-runtime: + - CPython + x-stainless-runtime-version: + - 3.11.7 + method: POST + uri: https://api.openai.com/v1/chat/completions + response: + body: + string: !!binary | + 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 + AAAA//8DAMIhqlTfHQAA + headers: + CF-Cache-Status: + - DYNAMIC + CF-RAY: + - 8e3de605dca06217-GRU + Connection: + - keep-alive + Content-Encoding: + - gzip + Content-Type: + - application/json + Date: + - Sun, 17 Nov 2024 07:10:10 GMT + Server: + - cloudflare + Transfer-Encoding: + - chunked + X-Content-Type-Options: + - nosniff + access-control-expose-headers: + - X-Request-ID + alt-svc: + - h3=":443"; ma=86400 + openai-organization: + - crewai-iuxna1 + openai-processing-ms: + - '9951' + openai-version: + - '2020-10-01' + strict-transport-security: + - max-age=31536000; includeSubDomains; preload + x-ratelimit-limit-requests: + - '10000' + x-ratelimit-limit-tokens: + - '30000000' + x-ratelimit-remaining-requests: + - '9999' + x-ratelimit-remaining-tokens: + - '29998873' + x-ratelimit-reset-requests: + - 6ms + x-ratelimit-reset-tokens: + - 2ms + x-request-id: + - req_52a4f98f9c08fbaa1724634d237f3245 + status: + code: 200 + message: OK +version: 1 diff --git a/tests/cassettes/test_before_crew_modification.yaml b/tests/cassettes/test_before_crew_modification.yaml new file mode 100644 index 000000000..b476c30f7 --- /dev/null +++ b/tests/cassettes/test_before_crew_modification.yaml @@ -0,0 +1,445 @@ +interactions: +- request: + body: '{"messages": [{"role": "system", "content": "You are Bicycles Senior Data + Researcher\n. You''re a seasoned researcher with a knack for uncovering the + latest developments in Bicycles. Known for your ability to find the most relevant + information and present it in a clear and concise manner.\n\nYour personal goal + is: Uncover cutting-edge developments in Bicycles\n\nTo give my best complete + final answer to the task use the exact following format:\n\nThought: I now can + give a great answer\nFinal Answer: Your final answer must be the great and the + most complete as possible, it must be outcome described.\n\nI MUST use these + formats, my job depends on it!"}, {"role": "user", "content": "\nCurrent Task: + Conduct a thorough research about Bicycles Make sure you find any interesting + and relevant information given the current year is 2024.\n\n\nThis is the expect + criteria for your final answer: A list with 10 bullet points of the most relevant + information about Bicycles\n\nyou MUST return the actual complete content as + the final answer, not a summary.\n\nBegin! This is VERY important to you, use + the tools available and give your best Final Answer, your job depends on it!\n\nThought:"}], + "model": "gpt-4o", "stop": ["\nObservation:"], "stream": false}' + headers: + accept: + - application/json + accept-encoding: + - gzip, deflate + connection: + - keep-alive + content-length: + - '1255' + content-type: + - application/json + host: + - api.openai.com + user-agent: + - OpenAI/Python 1.52.1 + x-stainless-arch: + - arm64 + x-stainless-async: + - 'false' + x-stainless-lang: + - python + x-stainless-os: + - MacOS + x-stainless-package-version: + - 1.52.1 + x-stainless-raw-response: + - 'true' + x-stainless-retry-count: + - '0' + x-stainless-runtime: + - CPython + x-stainless-runtime-version: + - 3.11.7 + method: POST + uri: https://api.openai.com/v1/chat/completions + response: + body: + string: !!binary | + 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 + Ag4AAA== + headers: + CF-Cache-Status: + - DYNAMIC + CF-RAY: + - 8e44d29989a61ab0-GRU + Connection: + - keep-alive + Content-Encoding: + - gzip + Content-Type: + - application/json + Date: + - Mon, 18 Nov 2024 03:20:10 GMT + Server: + - cloudflare + Set-Cookie: + - __cf_bm=elWqsM.3Jt5.vyzDrpCmVftKrlxb0_fRVMZxBGUYfcE-1731900010-1.0.1.1-AxUZI4aRPPnqgUcewvytSN0TcEpcfBqYEZ.h2A96g3wUsy6Ui_pr4y81nyHf2Pcn1S3lz4zSmufsGDmnNKtHDQ; + path=/; expires=Mon, 18-Nov-24 03:50:10 GMT; domain=.api.openai.com; HttpOnly; + Secure; SameSite=None + - _cfuvid=lzrs54cKet3l28qlaoF9_vtIs55.7H9Sbr6IhTssBmk-1731900010790-0.0.1.1-604800000; + path=/; domain=.api.openai.com; HttpOnly; Secure; SameSite=None + Transfer-Encoding: + - chunked + X-Content-Type-Options: + - nosniff + access-control-expose-headers: + - X-Request-ID + alt-svc: + - h3=":443"; ma=86400 + openai-organization: + - crewai-iuxna1 + openai-processing-ms: + - '5249' + openai-version: + - '2020-10-01' + strict-transport-security: + - max-age=31536000; includeSubDomains; preload + x-ratelimit-limit-requests: + - '10000' + x-ratelimit-limit-tokens: + - '30000000' + x-ratelimit-remaining-requests: + - '9999' + x-ratelimit-remaining-tokens: + - '29999708' + x-ratelimit-reset-requests: + - 6ms + x-ratelimit-reset-tokens: + - 0s + x-request-id: + - req_a9781a68655042f161d8089cc3819728 + status: + code: 200 + message: OK +- request: + body: !!binary | + CuEOCiQKIgoMc2VydmljZS5uYW1lEhIKEGNyZXdBSS10ZWxlbWV0cnkSuA4KEgoQY3Jld2FpLnRl + bGVtZXRyeRKQDAoQ2G6ncUKutPIsOmTplHC6bBIIclCMqGiNvUoqDENyZXcgQ3JlYXRlZDABOcBv + KPXg8QgYQXAFLvXg8QgYShoKDmNyZXdhaV92ZXJzaW9uEggKBjAuODAuMEoaCg5weXRob25fdmVy + c2lvbhIICgYzLjExLjdKLgoIY3Jld19rZXkSIgogMWYxMjhiZGI3YmFhNGI2NzcxNGYxZGFlZGMy + ZjNhYjZKMQoHY3Jld19pZBImCiQzNDEwYmI2Mi01NzYxLTRhMGQtOGY1Zi1hOTliZWY5NDYxM2VK + HAoMY3Jld19wcm9jZXNzEgwKCnNlcXVlbnRpYWxKEQoLY3Jld19tZW1vcnkSAhAAShoKFGNyZXdf + bnVtYmVyX29mX3Rhc2tzEgIYAkobChVjcmV3X251bWJlcl9vZl9hZ2VudHMSAhgCSqoFCgtjcmV3 + X2FnZW50cxKaBQqXBVt7ImtleSI6ICI3M2MzNDljOTNjMTYzYjVkNGRmOThhNjRmYWMxYzQzMCIs + ICJpZCI6ICI1YzgyZGRkOS1kMTM3LTQ3MDMtODY0My1iNTFmZDBlMTUxMjkiLCAicm9sZSI6ICJ7 + dG9waWN9IFNlbmlvciBEYXRhIFJlc2VhcmNoZXJcbiIsICJ2ZXJib3NlPyI6IHRydWUsICJtYXhf + aXRlciI6IDIwLCAibWF4X3JwbSI6IG51bGwsICJmdW5jdGlvbl9jYWxsaW5nX2xsbSI6ICIiLCAi + bGxtIjogImdwdC00byIsICJkZWxlZ2F0aW9uX2VuYWJsZWQ/IjogZmFsc2UsICJhbGxvd19jb2Rl + X2V4ZWN1dGlvbj8iOiBmYWxzZSwgIm1heF9yZXRyeV9saW1pdCI6IDIsICJ0b29sc19uYW1lcyI6 + IFtdfSwgeyJrZXkiOiAiMTA0ZmUwNjU5ZTEwYjQyNmNmODhmMDI0ZmI1NzE1NTMiLCAiaWQiOiAi + ODdlYmRiYTMtNDRmZS00ODBmLWI2MWQtMWYzZjIyMWE5MDE2IiwgInJvbGUiOiAie3RvcGljfSBS + ZXBvcnRpbmcgQW5hbHlzdFxuIiwgInZlcmJvc2U/IjogdHJ1ZSwgIm1heF9pdGVyIjogMjAsICJt + YXhfcnBtIjogbnVsbCwgImZ1bmN0aW9uX2NhbGxpbmdfbGxtIjogIiIsICJsbG0iOiAiZ3B0LTRv + IiwgImRlbGVnYXRpb25fZW5hYmxlZD8iOiBmYWxzZSwgImFsbG93X2NvZGVfZXhlY3V0aW9uPyI6 + IGZhbHNlLCAibWF4X3JldHJ5X2xpbWl0IjogMiwgInRvb2xzX25hbWVzIjogW119XUqTBAoKY3Jl + d190YXNrcxKEBAqBBFt7ImtleSI6ICI2YWZjNGIzOTYyNTlmYmI3NjgxZjU2Yzc3NTVjYzkzNyIs + ICJpZCI6ICI2ZTIzZmMzMS02OGI2LTRjZTMtODZjNC0zMDcxZGUwZDdjMWIiLCAiYXN5bmNfZXhl + Y3V0aW9uPyI6IGZhbHNlLCAiaHVtYW5faW5wdXQ/IjogZmFsc2UsICJhZ2VudF9yb2xlIjogInt0 + b3BpY30gU2VuaW9yIERhdGEgUmVzZWFyY2hlclxuIiwgImFnZW50X2tleSI6ICI3M2MzNDljOTNj + MTYzYjVkNGRmOThhNjRmYWMxYzQzMCIsICJ0b29sc19uYW1lcyI6IFtdfSwgeyJrZXkiOiAiYjE3 + YjE4OGRiZjE0ZjkzYTk4ZTViOTVhYWQzNjc1NzciLCAiaWQiOiAiNzRhOWVhMjMtNzVmYy00NWFi + LWIyMDAtMTllZTk0ZjU0Y2JkIiwgImFzeW5jX2V4ZWN1dGlvbj8iOiBmYWxzZSwgImh1bWFuX2lu + cHV0PyI6IGZhbHNlLCAiYWdlbnRfcm9sZSI6ICJ7dG9waWN9IFJlcG9ydGluZyBBbmFseXN0XG4i + LCAiYWdlbnRfa2V5IjogIjEwNGZlMDY1OWUxMGI0MjZjZjg4ZjAyNGZiNTcxNTUzIiwgInRvb2xz + X25hbWVzIjogW119XXoCGAGFAQABAAASjgIKEDkgSkh9vBYObKyMriyidxwSCG3RsAoOYBU/KgxU + YXNrIENyZWF0ZWQwATkgjkr14PEIGEHYFkv14PEIGEouCghjcmV3X2tleRIiCiAxZjEyOGJkYjdi + YWE0YjY3NzE0ZjFkYWVkYzJmM2FiNkoxCgdjcmV3X2lkEiYKJDM0MTBiYjYyLTU3NjEtNGEwZC04 + ZjVmLWE5OWJlZjk0NjEzZUouCgh0YXNrX2tleRIiCiA2YWZjNGIzOTYyNTlmYmI3NjgxZjU2Yzc3 + NTVjYzkzN0oxCgd0YXNrX2lkEiYKJDZlMjNmYzMxLTY4YjYtNGNlMy04NmM0LTMwNzFkZTBkN2Mx + YnoCGAGFAQABAAA= + headers: + Accept: + - '*/*' + Accept-Encoding: + - gzip, deflate + Connection: + - keep-alive + Content-Length: + - '1892' + Content-Type: + - application/x-protobuf + User-Agent: + - OTel-OTLP-Exporter-Python/1.27.0 + method: POST + uri: https://telemetry.crewai.com:4319/v1/traces + response: + body: + string: "\n\0" + headers: + Content-Length: + - '2' + Content-Type: + - application/x-protobuf + Date: + - Mon, 18 Nov 2024 03:20:10 GMT + status: + code: 200 + message: OK +- request: + body: !!binary | + Cs4CCiQKIgoMc2VydmljZS5uYW1lEhIKEGNyZXdBSS10ZWxlbWV0cnkSpQIKEgoQY3Jld2FpLnRl + bGVtZXRyeRKOAgoQwB8k3adY9mK031pcBVZJKhII3fxizKFNiGkqDFRhc2sgQ3JlYXRlZDABOaj7 + K0Xi8QgYQUiCLUXi8QgYSi4KCGNyZXdfa2V5EiIKIDFmMTI4YmRiN2JhYTRiNjc3MTRmMWRhZWRj + MmYzYWI2SjEKB2NyZXdfaWQSJgokMzQxMGJiNjItNTc2MS00YTBkLThmNWYtYTk5YmVmOTQ2MTNl + Si4KCHRhc2tfa2V5EiIKIGIxN2IxODhkYmYxNGY5M2E5OGU1Yjk1YWFkMzY3NTc3SjEKB3Rhc2tf + aWQSJgokNzRhOWVhMjMtNzVmYy00NWFiLWIyMDAtMTllZTk0ZjU0Y2JkegIYAYUBAAEAAA== + headers: + Accept: + - '*/*' + Accept-Encoding: + - gzip, deflate + Connection: + - keep-alive + Content-Length: + - '337' + Content-Type: + - application/x-protobuf + User-Agent: + - OTel-OTLP-Exporter-Python/1.27.0 + method: POST + uri: https://telemetry.crewai.com:4319/v1/traces + response: + body: + string: "\n\0" + headers: + Content-Length: + - '2' + Content-Type: + - application/x-protobuf + Date: + - Mon, 18 Nov 2024 03:20:15 GMT + status: + code: 200 + message: OK +- request: + body: '{"messages": [{"role": "system", "content": "You are Bicycles Reporting + Analyst\n. You''re a meticulous analyst with a keen eye for detail. You''re + known for your ability to turn complex data into clear and concise reports, + making it easy for others to understand and act on the information you provide.\n\nYour + personal goal is: Create detailed reports based on Bicycles data analysis and + research findings\n\nTo give my best complete final answer to the task use the + exact following format:\n\nThought: I now can give a great answer\nFinal Answer: + Your final answer must be the great and the most complete as possible, it must + be outcome described.\n\nI MUST use these formats, my job depends on it!"}, + {"role": "user", "content": "\nCurrent Task: Review the context you got and + expand each topic into a full section for a report. Make sure the report is + detailed and contains any and all relevant information.\n\n\nThis is the expect + criteria for your final answer: A fully fledge reports with the mains topics, + each with a full section of information. Formatted as markdown without ''```''\n\nyou + MUST return the actual complete content as the final answer, not a summary.\n\nThis + is the context you''re working with:\n1. **Electric Bicycles (E-Bikes) Dominate + the Market:** In 2024, e-bikes continue to lead in sales growth globally. Their + popularity is fueled by the advancement in battery technology, offering longer + ranges and shorter charging times, making commuting more efficient and sustainable + in urban environments.\n\n2. **Integration with Smart Technology:** Bicycle + manufacturers are increasingly incorporating IoT technology to enhance user + experience. Features like GPS tracking, fitness data logging, and anti-theft + systems directly linked to smartphones are becoming standard in newer models.\n\n3. + **Sustainable Manufacturing Techniques:** Environmental concerns have pushed + companies to adopt greener manufacturing processes, such as utilizing recycled + materials, reducing carbon footprints in production, and implementing circular + economies within the bicycle industry.\n\n4. **Innovations in Lightweight Materials:** + The development of new composite materials, including carbon and graphene, results + in extremely lightweight and durable frames. This advancement is particularly + noticeable in racing and mountain bikes, enhancing performance and speed.\n\n5. + **Customizable and Modular Bike Designs:** In 2024, there is a notable trend + toward bikes with modular designs that allow riders to customize parts and accessories + easily. This trend caters to diverse consumer needs and promotes longer bike + life cycles by allowing for parts replacement instead of whole bikes.\n\n6. + **Expansion of Urban Cycling Infrastructure:** More cities worldwide are investing + in cycling-friendly infrastructure, such as dedicated bike lanes and bike-sharing + schemes, to encourage eco-friendly commuting and reduce traffic congestion.\n\n7. + **Health and Wellness Benefits:** With growing awareness of health and fitness, + more people are choosing cycling as a daily exercise routine. The industry sees + a surge in sales of fitness-oriented bicycles designed to maximize cardiovascular + and strength training benefits.\n\n8. **Rise of Cargo and Utility Bicycles:** + There is an increase in demand for cargo bicycles, which are used for transporting + goods over short distances, reflecting a shift towards sustainable business + delivery options, particularly in urban settings.\n\n9. **Competitive Cycling + and Esports:** Competitive cycling has embraced digital platforms, with virtual + reality and augmented reality races gaining traction among cycling enthusiasts + and professional athletes for training and competition purposes.\n\n10. **Focus + on Bike Safety Innovations:** Advances in bicycle safety technology, including + smart helmets with built-in communication systems and advanced lighting for + night visibility, are considerably improving rider security, making cycling + a safer mode of transport.\n\nBegin! This is VERY important to you, use the + tools available and give your best Final Answer, your job depends on it!\n\nThought:"}], + "model": "gpt-4o", "stop": ["\nObservation:"], "stream": false}' + headers: + accept: + - application/json + accept-encoding: + - gzip, deflate + connection: + - keep-alive + content-length: + - '4197' + content-type: + - application/json + cookie: + - __cf_bm=elWqsM.3Jt5.vyzDrpCmVftKrlxb0_fRVMZxBGUYfcE-1731900010-1.0.1.1-AxUZI4aRPPnqgUcewvytSN0TcEpcfBqYEZ.h2A96g3wUsy6Ui_pr4y81nyHf2Pcn1S3lz4zSmufsGDmnNKtHDQ; + _cfuvid=lzrs54cKet3l28qlaoF9_vtIs55.7H9Sbr6IhTssBmk-1731900010790-0.0.1.1-604800000 + host: + - api.openai.com + user-agent: + - OpenAI/Python 1.52.1 + x-stainless-arch: + - arm64 + x-stainless-async: + - 'false' + x-stainless-lang: + - python + x-stainless-os: + - MacOS + x-stainless-package-version: + - 1.52.1 + x-stainless-raw-response: + - 'true' + x-stainless-retry-count: + - '0' + x-stainless-runtime: + - CPython + x-stainless-runtime-version: + - 3.11.7 + method: POST + uri: https://api.openai.com/v1/chat/completions + response: + body: + string: !!binary | + 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 + headers: + CF-Cache-Status: + - DYNAMIC + CF-RAY: + - 8e44d2bc2bec1ab0-GRU + Connection: + - keep-alive + Content-Encoding: + - gzip + Content-Type: + - application/json + Date: + - Mon, 18 Nov 2024 03:20:25 GMT + Server: + - cloudflare + Transfer-Encoding: + - chunked + X-Content-Type-Options: + - nosniff + access-control-expose-headers: + - X-Request-ID + alt-svc: + - h3=":443"; ma=86400 + openai-organization: + - crewai-iuxna1 + openai-processing-ms: + - '13936' + openai-version: + - '2020-10-01' + strict-transport-security: + - max-age=31536000; includeSubDomains; preload + x-ratelimit-limit-requests: + - '10000' + x-ratelimit-limit-tokens: + - '30000000' + x-ratelimit-remaining-requests: + - '9999' + x-ratelimit-remaining-tokens: + - '29998979' + x-ratelimit-reset-requests: + - 6ms + x-ratelimit-reset-tokens: + - 2ms + x-request-id: + - req_602e9ec1c4bc0da2fdb284f809c50872 + status: + code: 200 + message: OK +version: 1 diff --git a/tests/cassettes/test_before_crew_with_none_input.yaml b/tests/cassettes/test_before_crew_with_none_input.yaml new file mode 100644 index 000000000..22ab29a38 --- /dev/null +++ b/tests/cassettes/test_before_crew_with_none_input.yaml @@ -0,0 +1,438 @@ +interactions: +- request: + body: !!binary | + CuMOCiQKIgoMc2VydmljZS5uYW1lEhIKEGNyZXdBSS10ZWxlbWV0cnkSug4KEgoQY3Jld2FpLnRl + bGVtZXRyeRKSDAoQf/zeqxfqyNP5BgW6rZrC0BIIiXyYjb3bUBcqDENyZXcgQ3JlYXRlZDABOXha + vrnarwgYQcCbxrnarwgYShoKDmNyZXdhaV92ZXJzaW9uEggKBjAuODAuMEoaCg5weXRob25fdmVy + c2lvbhIICgYzLjExLjdKLgoIY3Jld19rZXkSIgogZjM0NmE5YWQ2ZDczMDYzZTA2NzdiMTdjZTlj + NTAxNzdKMQoHY3Jld19pZBImCiQ2Yzg5NDczNy0zNWJjLTRhZDEtYjE2Ni1hZTY3ODhhMTA4YWZK + HAoMY3Jld19wcm9jZXNzEgwKCnNlcXVlbnRpYWxKEQoLY3Jld19tZW1vcnkSAhAAShoKFGNyZXdf + bnVtYmVyX29mX3Rhc2tzEgIYAkobChVjcmV3X251bWJlcl9vZl9hZ2VudHMSAhgCSqwFCgtjcmV3 + X2FnZW50cxKcBQqZBVt7ImtleSI6ICI3M2MzNDljOTNjMTYzYjVkNGRmOThhNjRmYWMxYzQzMCIs + ICJpZCI6ICIzNDQ2YWRlOS05YWM0LTQ1NTUtOTlkNS0zYWM0MzdhMmMxNmUiLCAicm9sZSI6ICJ7 + dG9waWN9IFNlbmlvciBEYXRhIFJlc2VhcmNoZXJcbiIsICJ2ZXJib3NlPyI6IGZhbHNlLCAibWF4 + X2l0ZXIiOiAyMCwgIm1heF9ycG0iOiBudWxsLCAiZnVuY3Rpb25fY2FsbGluZ19sbG0iOiAiIiwg + ImxsbSI6ICJncHQtNG8iLCAiZGVsZWdhdGlvbl9lbmFibGVkPyI6IGZhbHNlLCAiYWxsb3dfY29k + ZV9leGVjdXRpb24/IjogZmFsc2UsICJtYXhfcmV0cnlfbGltaXQiOiAyLCAidG9vbHNfbmFtZXMi + OiBbXX0sIHsia2V5IjogImJiMDY4Mzc3YzE2NDFiZTZkN2Q5N2E1MTY1OWRiNjEzIiwgImlkIjog + IjExMzVjODkzLTRlZGUtNDRiNC1hMjZmLTIxYWUxNzA0ZDRlZCIsICJyb2xlIjogInt0b3BpY30g + UmVwb3J0aW5nIEFuYWx5c3RcbiIsICJ2ZXJib3NlPyI6IGZhbHNlLCAibWF4X2l0ZXIiOiAyMCwg + Im1heF9ycG0iOiBudWxsLCAiZnVuY3Rpb25fY2FsbGluZ19sbG0iOiAiIiwgImxsbSI6ICJncHQt + NG8iLCAiZGVsZWdhdGlvbl9lbmFibGVkPyI6IGZhbHNlLCAiYWxsb3dfY29kZV9leGVjdXRpb24/ + IjogZmFsc2UsICJtYXhfcmV0cnlfbGltaXQiOiAyLCAidG9vbHNfbmFtZXMiOiBbXX1dSpMECgpj + cmV3X3Rhc2tzEoQECoEEW3sia2V5IjogIjZhZmM0YjM5NjI1OWZiYjc2ODFmNTZjNzc1NWNjOTM3 + IiwgImlkIjogImIxZjQ5ODJiLTRjZGItNDk1MC04ZmNjLWMwZDcxNzRhYzY0NiIsICJhc3luY19l + eGVjdXRpb24/IjogZmFsc2UsICJodW1hbl9pbnB1dD8iOiBmYWxzZSwgImFnZW50X3JvbGUiOiAi + e3RvcGljfSBTZW5pb3IgRGF0YSBSZXNlYXJjaGVyXG4iLCAiYWdlbnRfa2V5IjogIjczYzM0OWM5 + M2MxNjNiNWQ0ZGY5OGE2NGZhYzFjNDMwIiwgInRvb2xzX25hbWVzIjogW119LCB7ImtleSI6ICJi + MTdiMTg4ZGJmMTRmOTNhOThlNWI5NWFhZDM2NzU3NyIsICJpZCI6ICIyY2VkNGVhNC01YjcwLTRh + MDctOTEyOS00MzQ2ZDQ1OWM4NjIiLCAiYXN5bmNfZXhlY3V0aW9uPyI6IGZhbHNlLCAiaHVtYW5f + aW5wdXQ/IjogZmFsc2UsICJhZ2VudF9yb2xlIjogInt0b3BpY30gUmVwb3J0aW5nIEFuYWx5c3Rc + biIsICJhZ2VudF9rZXkiOiAiYmIwNjgzNzdjMTY0MWJlNmQ3ZDk3YTUxNjU5ZGI2MTMiLCAidG9v + bHNfbmFtZXMiOiBbXX1degIYAYUBAAEAABKOAgoQOaRyuH2UERJ3sHC1ImhOgxIIq8DZc4P2KYMq + DFRhc2sgQ3JlYXRlZDABOTA127narwgYQVjV27narwgYSi4KCGNyZXdfa2V5EiIKIGYzNDZhOWFk + NmQ3MzA2M2UwNjc3YjE3Y2U5YzUwMTc3SjEKB2NyZXdfaWQSJgokNmM4OTQ3MzctMzViYy00YWQx + LWIxNjYtYWU2Nzg4YTEwOGFmSi4KCHRhc2tfa2V5EiIKIDZhZmM0YjM5NjI1OWZiYjc2ODFmNTZj + Nzc1NWNjOTM3SjEKB3Rhc2tfaWQSJgokYjFmNDk4MmItNGNkYi00OTUwLThmY2MtYzBkNzE3NGFj + NjQ2egIYAYUBAAEAAA== + headers: + Accept: + - '*/*' + Accept-Encoding: + - gzip, deflate + Connection: + - keep-alive + Content-Length: + - '1894' + Content-Type: + - application/x-protobuf + User-Agent: + - OTel-OTLP-Exporter-Python/1.27.0 + method: POST + uri: https://telemetry.crewai.com:4319/v1/traces + response: + body: + string: "\n\0" + headers: + Content-Length: + - '2' + Content-Type: + - application/x-protobuf + Date: + - Sun, 17 Nov 2024 07:10:11 GMT + status: + code: 200 + message: OK +- request: + body: '{"messages": [{"role": "system", "content": "You are {topic} Senior Data + Researcher\n. You''re a seasoned researcher with a knack for uncovering the + latest developments in {topic}. Known for your ability to find the most relevant + information and present it in a clear and concise manner.\n\nYour personal goal + is: Uncover cutting-edge developments in {topic}\n\nTo give my best complete + final answer to the task use the exact following format:\n\nThought: I now can + give a great answer\nFinal Answer: Your final answer must be the great and the + most complete as possible, it must be outcome described.\n\nI MUST use these + formats, my job depends on it!"}, {"role": "user", "content": "\nCurrent Task: + Conduct a thorough research about {topic} Make sure you find any interesting + and relevant information given the current year is 2024.\n\n\nThis is the expect + criteria for your final answer: A list with 10 bullet points of the most relevant + information about {topic}\n\nyou MUST return the actual complete content as + the final answer, not a summary.\n\nBegin! This is VERY important to you, use + the tools available and give your best Final Answer, your job depends on it!\n\nThought:"}], + "model": "gpt-4o", "stop": ["\nObservation:"], "stream": false}' + headers: + accept: + - application/json + accept-encoding: + - gzip, deflate + connection: + - keep-alive + content-length: + - '1250' + content-type: + - application/json + cookie: + - __cf_bm=08pKRcLhS1PDw0mYfL2jz19ac6M.T31GoiMuI5DlX6w-1731827382-1.0.1.1-UfOLu3AaIUuXP1sGzdV6oggJ1q7iMTC46t08FDhYVrKcW5YmD4CbifudOJiSgx8h0JLTwZdgk.aG05S0eAO_PQ; + _cfuvid=74kaPOoAcp8YRSA0XocQ1FFNksu9V0_KiWdQfo7wQuQ-1731827382509-0.0.1.1-604800000 + host: + - api.openai.com + user-agent: + - OpenAI/Python 1.52.1 + x-stainless-arch: + - arm64 + x-stainless-async: + - 'false' + x-stainless-lang: + - python + x-stainless-os: + - MacOS + x-stainless-package-version: + - 1.52.1 + x-stainless-raw-response: + - 'true' + x-stainless-retry-count: + - '0' + x-stainless-runtime: + - CPython + x-stainless-runtime-version: + - 3.11.7 + method: POST + uri: https://api.openai.com/v1/chat/completions + response: + body: + string: !!binary | + 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 + headers: + CF-Cache-Status: + - DYNAMIC + CF-RAY: + - 8e3de645a8666217-GRU + Connection: + - keep-alive + Content-Encoding: + - gzip + Content-Type: + - application/json + Date: + - Sun, 17 Nov 2024 07:10:16 GMT + Server: + - cloudflare + Transfer-Encoding: + - chunked + X-Content-Type-Options: + - nosniff + access-control-expose-headers: + - X-Request-ID + alt-svc: + - h3=":443"; ma=86400 + openai-organization: + - crewai-iuxna1 + openai-processing-ms: + - '5537' + openai-version: + - '2020-10-01' + strict-transport-security: + - max-age=31536000; includeSubDomains; preload + x-ratelimit-limit-requests: + - '10000' + x-ratelimit-limit-tokens: + - '30000000' + x-ratelimit-remaining-requests: + - '9999' + x-ratelimit-remaining-tokens: + - '29999711' + x-ratelimit-reset-requests: + - 6ms + x-ratelimit-reset-tokens: + - 0s + x-request-id: + - req_220e7945d04e84ab7b58c252c98630b5 + status: + code: 200 + message: OK +- request: + body: !!binary | + Cs4CCiQKIgoMc2VydmljZS5uYW1lEhIKEGNyZXdBSS10ZWxlbWV0cnkSpQIKEgoQY3Jld2FpLnRl + bGVtZXRyeRKOAgoQrqG+rs9H9Iyyqr2ZU1qS4RIIWspPh5zdoVMqDFRhc2sgQ3JlYXRlZDABOeiB + tw7crwgYQZgvuQ7crwgYSi4KCGNyZXdfa2V5EiIKIGYzNDZhOWFkNmQ3MzA2M2UwNjc3YjE3Y2U5 + YzUwMTc3SjEKB2NyZXdfaWQSJgokNmM4OTQ3MzctMzViYy00YWQxLWIxNjYtYWU2Nzg4YTEwOGFm + Si4KCHRhc2tfa2V5EiIKIGIxN2IxODhkYmYxNGY5M2E5OGU1Yjk1YWFkMzY3NTc3SjEKB3Rhc2tf + aWQSJgokMmNlZDRlYTQtNWI3MC00YTA3LTkxMjktNDM0NmQ0NTljODYyegIYAYUBAAEAAA== + headers: + Accept: + - '*/*' + Accept-Encoding: + - gzip, deflate + Connection: + - keep-alive + Content-Length: + - '337' + Content-Type: + - application/x-protobuf + User-Agent: + - OTel-OTLP-Exporter-Python/1.27.0 + method: POST + uri: https://telemetry.crewai.com:4319/v1/traces + response: + body: + string: "\n\0" + headers: + Content-Length: + - '2' + Content-Type: + - application/x-protobuf + Date: + - Sun, 17 Nov 2024 07:10:22 GMT + status: + code: 200 + message: OK +- request: + body: '{"messages": [{"role": "system", "content": "You are {topic} Reporting + Analyst\n. You''re a meticulous analyst with a keen eye for detail. You''re + known for your ability to turn complex data into clear and concise reports, + making it easy for others to understand and act on the information you provide.\nYour + personal goal is: Create detailed reports based on {topic} data analysis and + research findings\n\nTo give my best complete final answer to the task use the + exact following format:\n\nThought: I now can give a great answer\nFinal Answer: + Your final answer must be the great and the most complete as possible, it must + be outcome described.\n\nI MUST use these formats, my job depends on it!"}, + {"role": "user", "content": "\nCurrent Task: Review the context you got and + expand each topic into a full section for a report. Make sure the report is + detailed and contains any and all relevant information.\n\n\nThis is the expect + criteria for your final answer: A fully fledge reports with the mains topics, + each with a full section of information. Formatted as markdown without ''```''\n\nyou + MUST return the actual complete content as the final answer, not a summary.\n\nThis + is the context you''re working with:\n1. **Breakthrough in Quantum Computing**: + By 2024, advancements in quantum computing have led to more reliable qubit processing, + paving the way for practical applications in cryptography, complex system simulations, + and optimization problems.\n\n2. **AI Integration in Healthcare**: Artificial + intelligence continues to transform healthcare, with AI algorithms now more + accurately diagnosing diseases, predicting patient outcomes, and personalizing + treatment plans than ever before.\n\n3. **Renewable Energy Innovations**: The + year 2024 has seen significant improvements in renewable energy technologies. + Next-generation solar panels and wind turbines are more efficient, leading to + widespread adoption and reduced reliance on fossil fuels.\n\n4. **5G Expansion + and 6G Development**: The global rollout of 5G technology reaches near completion, + and research into 6G has commenced. This development promises to introduce unprecedented + data transfer speeds and connectivity.\n\n5. **Advances in Biotechnology**: + Gene-editing technologies, such as CRISPR, have advanced to a stage where genetic + disorders can be effectively treated, sparking ethical debates and regulatory + considerations.\n\n6. **Space Exploration Milestones**: The space industry achieves + new milestones with the establishment of permanent lunar bases and the first + manned missions to Mars, driven by both government and private sector collaboration.\n\n7. + **Sustainable Agriculture Practices**: In response to climate change challenges, + sustainable agriculture practices, including vertical farming and precision + agriculture, are gaining traction globally, boosting food production and reducing + environmental impact.\n\n8. **Cybersecurity Enhancements**: With increasing + digital threats, 2024 sees robust advancements in cybersecurity technologies, + including AI-driven threat detection, and enhanced data encryption methodologies.\n\n9. + **Transportation Innovation**: Public transportation and electric vehicle technology + continue to evolve with the introduction of autonomous public transit systems + and improvements in EV battery longevity and charging infrastructures.\n\n10. + **Mental Health Awareness**: A global increase in mental health awareness leads + to increased funding for research and the integration of digital therapies and + apps that provide more accessible mental health support.\n\nThese bullet points + summarize significant areas of development and interest in the given topic in + 2024, highlighting the profound impacts in various fields.\n\nBegin! This is + VERY important to you, use the tools available and give your best Final Answer, + your job depends on it!\n\nThought:"}], "model": "gpt-4o", "stop": ["\nObservation:"], + "stream": false}' + headers: + accept: + - application/json + accept-encoding: + - gzip, deflate + connection: + - keep-alive + content-length: + - '3935' + content-type: + - application/json + cookie: + - __cf_bm=08pKRcLhS1PDw0mYfL2jz19ac6M.T31GoiMuI5DlX6w-1731827382-1.0.1.1-UfOLu3AaIUuXP1sGzdV6oggJ1q7iMTC46t08FDhYVrKcW5YmD4CbifudOJiSgx8h0JLTwZdgk.aG05S0eAO_PQ; + _cfuvid=74kaPOoAcp8YRSA0XocQ1FFNksu9V0_KiWdQfo7wQuQ-1731827382509-0.0.1.1-604800000 + host: + - api.openai.com + user-agent: + - OpenAI/Python 1.52.1 + x-stainless-arch: + - arm64 + x-stainless-async: + - 'false' + x-stainless-lang: + - python + x-stainless-os: + - MacOS + x-stainless-package-version: + - 1.52.1 + x-stainless-raw-response: + - 'true' + x-stainless-retry-count: + - '0' + x-stainless-runtime: + - CPython + x-stainless-runtime-version: + - 3.11.7 + method: POST + uri: https://api.openai.com/v1/chat/completions + response: + body: + string: !!binary | + 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 + headers: + CF-Cache-Status: + - DYNAMIC + CF-RAY: + - 8e3de6697f976217-GRU + Connection: + - keep-alive + Content-Encoding: + - gzip + Content-Type: + - application/json + Date: + - Sun, 17 Nov 2024 07:10:27 GMT + Server: + - cloudflare + Transfer-Encoding: + - chunked + X-Content-Type-Options: + - nosniff + access-control-expose-headers: + - X-Request-ID + alt-svc: + - h3=":443"; ma=86400 + openai-organization: + - crewai-iuxna1 + openai-processing-ms: + - '10658' + openai-version: + - '2020-10-01' + strict-transport-security: + - max-age=31536000; includeSubDomains; preload + x-ratelimit-limit-requests: + - '10000' + x-ratelimit-limit-tokens: + - '30000000' + x-ratelimit-remaining-requests: + - '9999' + x-ratelimit-remaining-tokens: + - '29999045' + x-ratelimit-reset-requests: + - 6ms + x-ratelimit-reset-tokens: + - 1ms + x-request-id: + - req_f0af67637da5bc0e6b11fc3e5db59f62 + status: + code: 200 + message: OK +version: 1 diff --git a/tests/cassettes/test_crew_execution_order.yaml b/tests/cassettes/test_crew_execution_order.yaml new file mode 100644 index 000000000..5f05a3258 --- /dev/null +++ b/tests/cassettes/test_crew_execution_order.yaml @@ -0,0 +1,450 @@ +interactions: +- request: + body: !!binary | + CuMOCiQKIgoMc2VydmljZS5uYW1lEhIKEGNyZXdBSS10ZWxlbWV0cnkSug4KEgoQY3Jld2FpLnRl + bGVtZXRyeRKSDAoQiPNFpMW9CoeJ9Zm3+M0txRIIWLnalHui3g4qDENyZXcgQ3JlYXRlZDABOcAS + H7rirwgYQRBNIbrirwgYShoKDmNyZXdhaV92ZXJzaW9uEggKBjAuODAuMEoaCg5weXRob25fdmVy + c2lvbhIICgYzLjExLjdKLgoIY3Jld19rZXkSIgogZjM0NmE5YWQ2ZDczMDYzZTA2NzdiMTdjZTlj + NTAxNzdKMQoHY3Jld19pZBImCiQ1MTZhMTZiNi1mMWY2LTRlMzUtYjY3Ni05ZjRiMGY3MTBhZDRK + HAoMY3Jld19wcm9jZXNzEgwKCnNlcXVlbnRpYWxKEQoLY3Jld19tZW1vcnkSAhAAShoKFGNyZXdf + bnVtYmVyX29mX3Rhc2tzEgIYAkobChVjcmV3X251bWJlcl9vZl9hZ2VudHMSAhgCSqwFCgtjcmV3 + X2FnZW50cxKcBQqZBVt7ImtleSI6ICI3M2MzNDljOTNjMTYzYjVkNGRmOThhNjRmYWMxYzQzMCIs + ICJpZCI6ICI1N2ZhM2QwNC04ZTQ5LTQyMDMtOTg2OS1lNzliMDdiYWJkNGMiLCAicm9sZSI6ICJ7 + dG9waWN9IFNlbmlvciBEYXRhIFJlc2VhcmNoZXJcbiIsICJ2ZXJib3NlPyI6IGZhbHNlLCAibWF4 + X2l0ZXIiOiAyMCwgIm1heF9ycG0iOiBudWxsLCAiZnVuY3Rpb25fY2FsbGluZ19sbG0iOiAiIiwg + ImxsbSI6ICJncHQtNG8iLCAiZGVsZWdhdGlvbl9lbmFibGVkPyI6IGZhbHNlLCAiYWxsb3dfY29k + ZV9leGVjdXRpb24/IjogZmFsc2UsICJtYXhfcmV0cnlfbGltaXQiOiAyLCAidG9vbHNfbmFtZXMi + OiBbXX0sIHsia2V5IjogImJiMDY4Mzc3YzE2NDFiZTZkN2Q5N2E1MTY1OWRiNjEzIiwgImlkIjog + ImM5NzA4YTVjLTI4MjEtNDI4Ny05ZDkyLWE3MmU1NWEyMTZlYiIsICJyb2xlIjogInt0b3BpY30g + UmVwb3J0aW5nIEFuYWx5c3RcbiIsICJ2ZXJib3NlPyI6IGZhbHNlLCAibWF4X2l0ZXIiOiAyMCwg + Im1heF9ycG0iOiBudWxsLCAiZnVuY3Rpb25fY2FsbGluZ19sbG0iOiAiIiwgImxsbSI6ICJncHQt + NG8iLCAiZGVsZWdhdGlvbl9lbmFibGVkPyI6IGZhbHNlLCAiYWxsb3dfY29kZV9leGVjdXRpb24/ + IjogZmFsc2UsICJtYXhfcmV0cnlfbGltaXQiOiAyLCAidG9vbHNfbmFtZXMiOiBbXX1dSpMECgpj + cmV3X3Rhc2tzEoQECoEEW3sia2V5IjogIjZhZmM0YjM5NjI1OWZiYjc2ODFmNTZjNzc1NWNjOTM3 + IiwgImlkIjogIjYxOWYxNWFhLTc1NDItNDJiOC04MDZhLWJlNmVhZTQwYmYyMyIsICJhc3luY19l + eGVjdXRpb24/IjogZmFsc2UsICJodW1hbl9pbnB1dD8iOiBmYWxzZSwgImFnZW50X3JvbGUiOiAi + e3RvcGljfSBTZW5pb3IgRGF0YSBSZXNlYXJjaGVyXG4iLCAiYWdlbnRfa2V5IjogIjczYzM0OWM5 + M2MxNjNiNWQ0ZGY5OGE2NGZhYzFjNDMwIiwgInRvb2xzX25hbWVzIjogW119LCB7ImtleSI6ICJi + MTdiMTg4ZGJmMTRmOTNhOThlNWI5NWFhZDM2NzU3NyIsICJpZCI6ICJhNGIzOGJjYi1jN2Y3LTRk + MTMtYjFiNC00Yzc0ZmE4NzIxOTQiLCAiYXN5bmNfZXhlY3V0aW9uPyI6IGZhbHNlLCAiaHVtYW5f + aW5wdXQ/IjogZmFsc2UsICJhZ2VudF9yb2xlIjogInt0b3BpY30gUmVwb3J0aW5nIEFuYWx5c3Rc + biIsICJhZ2VudF9rZXkiOiAiYmIwNjgzNzdjMTY0MWJlNmQ3ZDk3YTUxNjU5ZGI2MTMiLCAidG9v + bHNfbmFtZXMiOiBbXX1degIYAYUBAAEAABKOAgoQaSYAFNgUUCxMT0z+KwJCrBIIrjh6pRvY2zEq + DFRhc2sgQ3JlYXRlZDABOag/LbrirwgYQTilLbrirwgYSi4KCGNyZXdfa2V5EiIKIGYzNDZhOWFk + NmQ3MzA2M2UwNjc3YjE3Y2U5YzUwMTc3SjEKB2NyZXdfaWQSJgokNTE2YTE2YjYtZjFmNi00ZTM1 + LWI2NzYtOWY0YjBmNzEwYWQ0Si4KCHRhc2tfa2V5EiIKIDZhZmM0YjM5NjI1OWZiYjc2ODFmNTZj + Nzc1NWNjOTM3SjEKB3Rhc2tfaWQSJgokNjE5ZjE1YWEtNzU0Mi00MmI4LTgwNmEtYmU2ZWFlNDBi + ZjIzegIYAYUBAAEAAA== + headers: + Accept: + - '*/*' + Accept-Encoding: + - gzip, deflate + Connection: + - keep-alive + Content-Length: + - '1894' + Content-Type: + - application/x-protobuf + User-Agent: + - OTel-OTLP-Exporter-Python/1.27.0 + method: POST + uri: https://telemetry.crewai.com:4319/v1/traces + response: + body: + string: "\n\0" + headers: + Content-Length: + - '2' + Content-Type: + - application/x-protobuf + Date: + - Sun, 17 Nov 2024 07:10:46 GMT + status: + code: 200 + message: OK +- request: + body: '{"messages": [{"role": "system", "content": "You are LLMs Senior Data Researcher\n. + You''re a seasoned researcher with a knack for uncovering the latest developments + in LLMs. Known for your ability to find the most relevant information and present + it in a clear and concise manner.\n\nYour personal goal is: Uncover cutting-edge + developments in LLMs\n\nTo give my best complete final answer to the task use + the exact following format:\n\nThought: I now can give a great answer\nFinal + Answer: Your final answer must be the great and the most complete as possible, + it must be outcome described.\n\nI MUST use these formats, my job depends on + it!"}, {"role": "user", "content": "\nCurrent Task: Conduct a thorough research + about LLMs Make sure you find any interesting and relevant information given + the current year is 2024.\n\n\nThis is the expect criteria for your final answer: + A list with 10 bullet points of the most relevant information about LLMs\n\nyou + MUST return the actual complete content as the final answer, not a summary.\n\nBegin! + This is VERY important to you, use the tools available and give your best Final + Answer, your job depends on it!\n\nThought:"}], "model": "gpt-4o", "stop": ["\nObservation:"], + "stream": false}' + headers: + accept: + - application/json + accept-encoding: + - gzip, deflate + connection: + - keep-alive + content-length: + - '1235' + content-type: + - application/json + cookie: + - __cf_bm=08pKRcLhS1PDw0mYfL2jz19ac6M.T31GoiMuI5DlX6w-1731827382-1.0.1.1-UfOLu3AaIUuXP1sGzdV6oggJ1q7iMTC46t08FDhYVrKcW5YmD4CbifudOJiSgx8h0JLTwZdgk.aG05S0eAO_PQ; + _cfuvid=74kaPOoAcp8YRSA0XocQ1FFNksu9V0_KiWdQfo7wQuQ-1731827382509-0.0.1.1-604800000 + host: + - api.openai.com + user-agent: + - OpenAI/Python 1.52.1 + x-stainless-arch: + - arm64 + x-stainless-async: + - 'false' + x-stainless-lang: + - python + x-stainless-os: + - MacOS + x-stainless-package-version: + - 1.52.1 + x-stainless-raw-response: + - 'true' + x-stainless-retry-count: + - '0' + x-stainless-runtime: + - CPython + x-stainless-runtime-version: + - 3.11.7 + method: POST + uri: https://api.openai.com/v1/chat/completions + response: + body: + string: !!binary | + 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 + headers: + CF-Cache-Status: + - DYNAMIC + CF-RAY: + - 8e3de71c79986217-GRU + Connection: + - keep-alive + Content-Encoding: + - gzip + Content-Type: + - application/json + Date: + - Sun, 17 Nov 2024 07:10:50 GMT + Server: + - cloudflare + Transfer-Encoding: + - chunked + X-Content-Type-Options: + - nosniff + access-control-expose-headers: + - X-Request-ID + alt-svc: + - h3=":443"; ma=86400 + openai-organization: + - crewai-iuxna1 + openai-processing-ms: + - '5446' + openai-version: + - '2020-10-01' + strict-transport-security: + - max-age=31536000; includeSubDomains; preload + x-ratelimit-limit-requests: + - '10000' + x-ratelimit-limit-tokens: + - '30000000' + x-ratelimit-remaining-requests: + - '9999' + x-ratelimit-remaining-tokens: + - '29999713' + x-ratelimit-reset-requests: + - 6ms + x-ratelimit-reset-tokens: + - 0s + x-request-id: + - req_dda88f1d73ab714daf68612f9936d111 + status: + code: 200 + message: OK +- request: + body: !!binary | + Cs4CCiQKIgoMc2VydmljZS5uYW1lEhIKEGNyZXdBSS10ZWxlbWV0cnkSpQIKEgoQY3Jld2FpLnRl + bGVtZXRyeRKOAgoQyRM1X58vc+Q8vTI/ofl1GBIIY3Nm20DB7JsqDFRhc2sgQ3JlYXRlZDABOZgm + aAvkrwgYQehgagvkrwgYSi4KCGNyZXdfa2V5EiIKIGYzNDZhOWFkNmQ3MzA2M2UwNjc3YjE3Y2U5 + YzUwMTc3SjEKB2NyZXdfaWQSJgokNTE2YTE2YjYtZjFmNi00ZTM1LWI2NzYtOWY0YjBmNzEwYWQ0 + Si4KCHRhc2tfa2V5EiIKIGIxN2IxODhkYmYxNGY5M2E5OGU1Yjk1YWFkMzY3NTc3SjEKB3Rhc2tf + aWQSJgokYTRiMzhiY2ItYzdmNy00ZDEzLWIxYjQtNGM3NGZhODcyMTk0egIYAYUBAAEAAA== + headers: + Accept: + - '*/*' + Accept-Encoding: + - gzip, deflate + Connection: + - keep-alive + Content-Length: + - '337' + Content-Type: + - application/x-protobuf + User-Agent: + - OTel-OTLP-Exporter-Python/1.27.0 + method: POST + uri: https://telemetry.crewai.com:4319/v1/traces + response: + body: + string: "\n\0" + headers: + Content-Length: + - '2' + Content-Type: + - application/x-protobuf + Date: + - Sun, 17 Nov 2024 07:10:51 GMT + status: + code: 200 + message: OK +- request: + body: '{"messages": [{"role": "system", "content": "You are LLMs Reporting Analyst\n. + You''re a meticulous analyst with a keen eye for detail. You''re known for your + ability to turn complex data into clear and concise reports, making it easy + for others to understand and act on the information you provide.\nYour personal + goal is: Create detailed reports based on LLMs data analysis and research findings\n\nTo + give my best complete final answer to the task use the exact following format:\n\nThought: + I now can give a great answer\nFinal Answer: Your final answer must be the great + and the most complete as possible, it must be outcome described.\n\nI MUST use + these formats, my job depends on it!"}, {"role": "user", "content": "\nCurrent + Task: Review the context you got and expand each topic into a full section for + a report. Make sure the report is detailed and contains any and all relevant + information.\n\n\nThis is the expect criteria for your final answer: A fully + fledge reports with the mains topics, each with a full section of information. + Formatted as markdown without ''```''\n\nyou MUST return the actual complete + content as the final answer, not a summary.\n\nThis is the context you''re working + with:\n1. **Advancements in Architectural Design**: In 2024, LLMs have seen + significant improvements in architecture, with models adopting more layered + and complex structures that enhance contextual understanding and prevent degradation + in performance over long text sequences. This has improved accuracy and coherence + in generating human-like text.\n\n2. **Integration with Multimodal Data**: The + latest LLMs have integrated multimodal capabilities, allowing them to process + and generate not just text, but also integrate and interpret images, audio, + and video data. This integration significantly enhances user interaction capabilities + and widens practical applications, particularly in fields such as creative industries + and customer service.\n\n3. **Efficiency and Scalability**: There is a strong + focus on reducing the carbon footprint of training and deploying LLMs. Techniques + such as model distillation and the use of more efficient training processes + are commonplace. Models are also being designed for scalability to better serve + a wide range of applications without requiring extensive computational resources.\n\n4. + **Fine-Tuning and Customization**: The ability to finely tune LLMs for specific + industry or organizational needs has become more advanced. Developers can now + more easily customize these models to align with particular styles, industry + jargon, or brands, enhancing relevance and utility across diverse applications.\n\n5. + **Ethical AI and Safety Measures**: A growing emphasis is placed on ethical + considerations. Frameworks for ensuring fairness, reducing biases, and enhancing + transparency in outputs are being developed. New protocols are in place to predict + and mitigate potential misuses of LLMs to improve trust and reliability.\n\n6. + **Proliferation of Open Source Models**: With the community demanding transparency + and flexibility, many open-source LLMs have been developed to promote research + and innovation. These models provide robust alternatives to commercial counterparts + and enhance collaborative development efforts across the globe.\n\n7. **Emergence + of LLM-Driven Applications**: 2024 has seen a rise in applications based on + LLM technology, including advanced customer service bots, dynamic content creation + tools, and educational software that personalizes learning experiences. This + expands the role of LLMs beyond traditional academic or entertainment purposes.\n\n8. + **Focus on Low-Resource Language Support**: LLMs are becoming more adept at + understanding and generating languages with fewer resources, vastly improving + global accessibility and allowing technology to support a wider array of linguistic + communities. This helps bridge digital divides and promote inclusivity.\n\n9. + **Enhanced Interaction Techniques**: Novel interaction paradigms, such as real-time + adaptations and on-the-fly learning, enable LLMs to adjust tone, complexity, + and content more responsively to user feedback. This leads to more natural, + engaging, and personalized interactions in various interface environments.\n\n10. + **Collaborative Intelligence**: There''s an emerging trend of using LLMs in + collaborative settings where AI assists human decision-making by synthesizing + large datasets to suggest insights and trends. This symbiosis enhances productivity + and innovation in research and business contexts.\n\nBegin! This is VERY important + to you, use the tools available and give your best Final Answer, your job depends + on it!\n\nThought:"}], "model": "gpt-4o", "stop": ["\nObservation:"], "stream": + false}' + headers: + accept: + - application/json + accept-encoding: + - gzip, deflate + connection: + - keep-alive + content-length: + - '4742' + content-type: + - application/json + cookie: + - __cf_bm=08pKRcLhS1PDw0mYfL2jz19ac6M.T31GoiMuI5DlX6w-1731827382-1.0.1.1-UfOLu3AaIUuXP1sGzdV6oggJ1q7iMTC46t08FDhYVrKcW5YmD4CbifudOJiSgx8h0JLTwZdgk.aG05S0eAO_PQ; + _cfuvid=74kaPOoAcp8YRSA0XocQ1FFNksu9V0_KiWdQfo7wQuQ-1731827382509-0.0.1.1-604800000 + host: + - api.openai.com + user-agent: + - OpenAI/Python 1.52.1 + x-stainless-arch: + - arm64 + x-stainless-async: + - 'false' + x-stainless-lang: + - python + x-stainless-os: + - MacOS + x-stainless-package-version: + - 1.52.1 + x-stainless-raw-response: + - 'true' + x-stainless-retry-count: + - '0' + x-stainless-runtime: + - CPython + x-stainless-runtime-version: + - 3.11.7 + method: POST + uri: https://api.openai.com/v1/chat/completions + response: + body: + string: !!binary | + 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 + L4f7OwUCdjevN/8BAAD//wMA9xdqlskdAAA= + headers: + CF-Cache-Status: + - DYNAMIC + CF-RAY: + - 8e3de74049116217-GRU + Connection: + - keep-alive + Content-Encoding: + - gzip + Content-Type: + - application/json + Date: + - Sun, 17 Nov 2024 07:11:00 GMT + Server: + - cloudflare + Transfer-Encoding: + - chunked + X-Content-Type-Options: + - nosniff + access-control-expose-headers: + - X-Request-ID + alt-svc: + - h3=":443"; ma=86400 + openai-organization: + - crewai-iuxna1 + openai-processing-ms: + - '9414' + openai-version: + - '2020-10-01' + strict-transport-security: + - max-age=31536000; includeSubDomains; preload + x-ratelimit-limit-requests: + - '10000' + x-ratelimit-limit-tokens: + - '30000000' + x-ratelimit-remaining-requests: + - '9999' + x-ratelimit-remaining-tokens: + - '29998843' + x-ratelimit-reset-requests: + - 6ms + x-ratelimit-reset-tokens: + - 2ms + x-request-id: + - req_ffd793f9d60c093888d2483ea4305dd6 + status: + code: 200 + message: OK +version: 1 diff --git a/tests/cassettes/test_multiple_before_after_crew.yaml b/tests/cassettes/test_multiple_before_after_crew.yaml new file mode 100644 index 000000000..590ee1d37 --- /dev/null +++ b/tests/cassettes/test_multiple_before_after_crew.yaml @@ -0,0 +1,565 @@ +interactions: +- request: + body: !!binary | + CoBACiQKIgoMc2VydmljZS5uYW1lEhIKEGNyZXdBSS10ZWxlbWV0cnkS1z8KEgoQY3Jld2FpLnRl + bGVtZXRyeRKQDAoQsz1SskG+EjJPD44lvCSFCxIIlcKcY48gxQAqDENyZXcgQ3JlYXRlZDABORht + kkfU8QgYQZgEmUfU8QgYShoKDmNyZXdhaV92ZXJzaW9uEggKBjAuODAuMEoaCg5weXRob25fdmVy + c2lvbhIICgYzLjExLjdKLgoIY3Jld19rZXkSIgogMWYxMjhiZGI3YmFhNGI2NzcxNGYxZGFlZGMy + ZjNhYjZKMQoHY3Jld19pZBImCiQ0MzA0ODRhNS0zODM3LTRkZDktOTBmYS1kMTg3NDM0MjRmZDRK + HAoMY3Jld19wcm9jZXNzEgwKCnNlcXVlbnRpYWxKEQoLY3Jld19tZW1vcnkSAhAAShoKFGNyZXdf + bnVtYmVyX29mX3Rhc2tzEgIYAkobChVjcmV3X251bWJlcl9vZl9hZ2VudHMSAhgCSqoFCgtjcmV3 + X2FnZW50cxKaBQqXBVt7ImtleSI6ICI3M2MzNDljOTNjMTYzYjVkNGRmOThhNjRmYWMxYzQzMCIs + ICJpZCI6ICI0NWU2MTcyOC04MDQzLTQ4ZTUtYjY1YS1mZjAxM2E5OGIwZjMiLCAicm9sZSI6ICJ7 + dG9waWN9IFNlbmlvciBEYXRhIFJlc2VhcmNoZXJcbiIsICJ2ZXJib3NlPyI6IHRydWUsICJtYXhf + aXRlciI6IDIwLCAibWF4X3JwbSI6IG51bGwsICJmdW5jdGlvbl9jYWxsaW5nX2xsbSI6ICIiLCAi + bGxtIjogImdwdC00byIsICJkZWxlZ2F0aW9uX2VuYWJsZWQ/IjogZmFsc2UsICJhbGxvd19jb2Rl + X2V4ZWN1dGlvbj8iOiBmYWxzZSwgIm1heF9yZXRyeV9saW1pdCI6IDIsICJ0b29sc19uYW1lcyI6 + IFtdfSwgeyJrZXkiOiAiMTA0ZmUwNjU5ZTEwYjQyNmNmODhmMDI0ZmI1NzE1NTMiLCAiaWQiOiAi + MTgyOGQ3NTktYzgzMS00YTBhLTk5YmQtNzU4OWM3ZGMzNjM1IiwgInJvbGUiOiAie3RvcGljfSBS + ZXBvcnRpbmcgQW5hbHlzdFxuIiwgInZlcmJvc2U/IjogdHJ1ZSwgIm1heF9pdGVyIjogMjAsICJt + YXhfcnBtIjogbnVsbCwgImZ1bmN0aW9uX2NhbGxpbmdfbGxtIjogIiIsICJsbG0iOiAiZ3B0LTRv + IiwgImRlbGVnYXRpb25fZW5hYmxlZD8iOiBmYWxzZSwgImFsbG93X2NvZGVfZXhlY3V0aW9uPyI6 + IGZhbHNlLCAibWF4X3JldHJ5X2xpbWl0IjogMiwgInRvb2xzX25hbWVzIjogW119XUqTBAoKY3Jl + d190YXNrcxKEBAqBBFt7ImtleSI6ICI2YWZjNGIzOTYyNTlmYmI3NjgxZjU2Yzc3NTVjYzkzNyIs + ICJpZCI6ICIxZWQ2YmQ5Yy0wNTFhLTRhYjUtYjQ5NC01NzI1NDY1OWIyODQiLCAiYXN5bmNfZXhl + Y3V0aW9uPyI6IGZhbHNlLCAiaHVtYW5faW5wdXQ/IjogZmFsc2UsICJhZ2VudF9yb2xlIjogInt0 + b3BpY30gU2VuaW9yIERhdGEgUmVzZWFyY2hlclxuIiwgImFnZW50X2tleSI6ICI3M2MzNDljOTNj + MTYzYjVkNGRmOThhNjRmYWMxYzQzMCIsICJ0b29sc19uYW1lcyI6IFtdfSwgeyJrZXkiOiAiYjE3 + YjE4OGRiZjE0ZjkzYTk4ZTViOTVhYWQzNjc1NzciLCAiaWQiOiAiZWE2MmJjM2EtMGYzNi00NGFl + LWJiYTktOWJlY2RmNTFlNGE4IiwgImFzeW5jX2V4ZWN1dGlvbj8iOiBmYWxzZSwgImh1bWFuX2lu + cHV0PyI6IGZhbHNlLCAiYWdlbnRfcm9sZSI6ICJ7dG9waWN9IFJlcG9ydGluZyBBbmFseXN0XG4i + LCAiYWdlbnRfa2V5IjogIjEwNGZlMDY1OWUxMGI0MjZjZjg4ZjAyNGZiNTcxNTUzIiwgInRvb2xz + X25hbWVzIjogW119XXoCGAGFAQABAAASjgIKEJohcMRpBfZqBQCPSM3m2SQSCIXDnmVDTos7KgxU + YXNrIENyZWF0ZWQwATnAMqxH1PEIGEHAr6xH1PEIGEouCghjcmV3X2tleRIiCiAxZjEyOGJkYjdi + YWE0YjY3NzE0ZjFkYWVkYzJmM2FiNkoxCgdjcmV3X2lkEiYKJDQzMDQ4NGE1LTM4MzctNGRkOS05 + MGZhLWQxODc0MzQyNGZkNEouCgh0YXNrX2tleRIiCiA2YWZjNGIzOTYyNTlmYmI3NjgxZjU2Yzc3 + NTVjYzkzN0oxCgd0YXNrX2lkEiYKJDFlZDZiZDljLTA1MWEtNGFiNS1iNDk0LTU3MjU0NjU5YjI4 + NHoCGAGFAQABAAASjgIKEFme5tfbl7IKlOtxKXOBwbkSCEkXsxaQXDs+KgxUYXNrIENyZWF0ZWQw + ATloDuBJ1PEIGEGABOFJ1PEIGEouCghjcmV3X2tleRIiCiAxZjEyOGJkYjdiYWE0YjY3NzE0ZjFk + YWVkYzJmM2FiNkoxCgdjcmV3X2lkEiYKJDQzMDQ4NGE1LTM4MzctNGRkOS05MGZhLWQxODc0MzQy + NGZkNEouCgh0YXNrX2tleRIiCiBiMTdiMTg4ZGJmMTRmOTNhOThlNWI5NWFhZDM2NzU3N0oxCgd0 + YXNrX2lkEiYKJGVhNjJiYzNhLTBmMzYtNDRhZS1iYmE5LTliZWNkZjUxZTRhOHoCGAGFAQABAAAS + kAwKEFJJx8FrKG3eBx+YFpoVWbsSCPJRoDF/VPvQKgxDcmV3IENyZWF0ZWQwATnYWWZO1PEIGEGw + JGlO1PEIGEoaCg5jcmV3YWlfdmVyc2lvbhIICgYwLjgwLjBKGgoOcHl0aG9uX3ZlcnNpb24SCAoG + My4xMS43Si4KCGNyZXdfa2V5EiIKIDFmMTI4YmRiN2JhYTRiNjc3MTRmMWRhZWRjMmYzYWI2SjEK + B2NyZXdfaWQSJgokYmViNmFlOTEtYTdjMS00YWVmLTg0ZjUtMzNhZjUwYTc3NjVhShwKDGNyZXdf + cHJvY2VzcxIMCgpzZXF1ZW50aWFsShEKC2NyZXdfbWVtb3J5EgIQAEoaChRjcmV3X251bWJlcl9v + Zl90YXNrcxICGAJKGwoVY3Jld19udW1iZXJfb2ZfYWdlbnRzEgIYAkqqBQoLY3Jld19hZ2VudHMS + mgUKlwVbeyJrZXkiOiAiNzNjMzQ5YzkzYzE2M2I1ZDRkZjk4YTY0ZmFjMWM0MzAiLCAiaWQiOiAi + MTk5NmFkYzctODQzNy00ODM3LThhYWYtNzQ1NWFlNjU1MzNkIiwgInJvbGUiOiAie3RvcGljfSBT + ZW5pb3IgRGF0YSBSZXNlYXJjaGVyXG4iLCAidmVyYm9zZT8iOiB0cnVlLCAibWF4X2l0ZXIiOiAy + MCwgIm1heF9ycG0iOiBudWxsLCAiZnVuY3Rpb25fY2FsbGluZ19sbG0iOiAiIiwgImxsbSI6ICJn + cHQtNG8iLCAiZGVsZWdhdGlvbl9lbmFibGVkPyI6IGZhbHNlLCAiYWxsb3dfY29kZV9leGVjdXRp + b24/IjogZmFsc2UsICJtYXhfcmV0cnlfbGltaXQiOiAyLCAidG9vbHNfbmFtZXMiOiBbXX0sIHsi + a2V5IjogIjEwNGZlMDY1OWUxMGI0MjZjZjg4ZjAyNGZiNTcxNTUzIiwgImlkIjogIjhiYTA2NGM3 + LWQ2NTItNGFmMi1hYjQ0LWJhNDA2NDRkMDJlMSIsICJyb2xlIjogInt0b3BpY30gUmVwb3J0aW5n + IEFuYWx5c3RcbiIsICJ2ZXJib3NlPyI6IHRydWUsICJtYXhfaXRlciI6IDIwLCAibWF4X3JwbSI6 + IG51bGwsICJmdW5jdGlvbl9jYWxsaW5nX2xsbSI6ICIiLCAibGxtIjogImdwdC00byIsICJkZWxl + Z2F0aW9uX2VuYWJsZWQ/IjogZmFsc2UsICJhbGxvd19jb2RlX2V4ZWN1dGlvbj8iOiBmYWxzZSwg + Im1heF9yZXRyeV9saW1pdCI6IDIsICJ0b29sc19uYW1lcyI6IFtdfV1KkwQKCmNyZXdfdGFza3MS + hAQKgQRbeyJrZXkiOiAiNmFmYzRiMzk2MjU5ZmJiNzY4MWY1NmM3NzU1Y2M5MzciLCAiaWQiOiAi + YTQzMTgxZjUtYzkwYi00ZDk0LWI1NTAtMTI2ZmNjM2Y4NjU3IiwgImFzeW5jX2V4ZWN1dGlvbj8i + OiBmYWxzZSwgImh1bWFuX2lucHV0PyI6IGZhbHNlLCAiYWdlbnRfcm9sZSI6ICJ7dG9waWN9IFNl + bmlvciBEYXRhIFJlc2VhcmNoZXJcbiIsICJhZ2VudF9rZXkiOiAiNzNjMzQ5YzkzYzE2M2I1ZDRk + Zjk4YTY0ZmFjMWM0MzAiLCAidG9vbHNfbmFtZXMiOiBbXX0sIHsia2V5IjogImIxN2IxODhkYmYx + NGY5M2E5OGU1Yjk1YWFkMzY3NTc3IiwgImlkIjogImE3NzczMTA2LTEwNDEtNDI2ZS05NjVhLTRj + YzExYjNkMjhiNyIsICJhc3luY19leGVjdXRpb24/IjogZmFsc2UsICJodW1hbl9pbnB1dD8iOiBm + YWxzZSwgImFnZW50X3JvbGUiOiAie3RvcGljfSBSZXBvcnRpbmcgQW5hbHlzdFxuIiwgImFnZW50 + X2tleSI6ICIxMDRmZTA2NTllMTBiNDI2Y2Y4OGYwMjRmYjU3MTU1MyIsICJ0b29sc19uYW1lcyI6 + IFtdfV16AhgBhQEAAQAAEo4CChCfS8p2nUmN4gbwYTXM780iEgiD3bJ3GYKbCyoMVGFzayBDcmVh + dGVkMAE5oK91TtTxCBhBoCx2TtTxCBhKLgoIY3Jld19rZXkSIgogMWYxMjhiZGI3YmFhNGI2Nzcx + NGYxZGFlZGMyZjNhYjZKMQoHY3Jld19pZBImCiRiZWI2YWU5MS1hN2MxLTRhZWYtODRmNS0zM2Fm + NTBhNzc2NWFKLgoIdGFza19rZXkSIgogNmFmYzRiMzk2MjU5ZmJiNzY4MWY1NmM3NzU1Y2M5MzdK + MQoHdGFza19pZBImCiRhNDMxODFmNS1jOTBiLTRkOTQtYjU1MC0xMjZmY2MzZjg2NTd6AhgBhQEA + AQAAEo4CChD6YglTfqRETeD91myfQQucEgjVnKDpP/acdSoMVGFzayBDcmVhdGVkMAE54FB+UNTx + CBhBOOl+UNTxCBhKLgoIY3Jld19rZXkSIgogMWYxMjhiZGI3YmFhNGI2NzcxNGYxZGFlZGMyZjNh + YjZKMQoHY3Jld19pZBImCiRiZWI2YWU5MS1hN2MxLTRhZWYtODRmNS0zM2FmNTBhNzc2NWFKLgoI + dGFza19rZXkSIgogYjE3YjE4OGRiZjE0ZjkzYTk4ZTViOTVhYWQzNjc1NzdKMQoHdGFza19pZBIm + CiRhNzc3MzEwNi0xMDQxLTQyNmUtOTY1YS00Y2MxMWIzZDI4Yjd6AhgBhQEAAQAAEpAMChD6odHS + 2zFS6x5ow3o0vrPMEgjfWriALnu/VyoMQ3JldyBDcmVhdGVkMAE5gE7+UNTxCBhB6IQAUdTxCBhK + GgoOY3Jld2FpX3ZlcnNpb24SCAoGMC44MC4wShoKDnB5dGhvbl92ZXJzaW9uEggKBjMuMTEuN0ou + CghjcmV3X2tleRIiCiAxZjEyOGJkYjdiYWE0YjY3NzE0ZjFkYWVkYzJmM2FiNkoxCgdjcmV3X2lk + EiYKJDAxN2I1M2M0LWU0N2EtNDRjMy1hZTZhLWE4NDc2N2Q3MDEwMkocCgxjcmV3X3Byb2Nlc3MS + DAoKc2VxdWVudGlhbEoRCgtjcmV3X21lbW9yeRICEABKGgoUY3Jld19udW1iZXJfb2ZfdGFza3MS + AhgCShsKFWNyZXdfbnVtYmVyX29mX2FnZW50cxICGAJKqgUKC2NyZXdfYWdlbnRzEpoFCpcFW3si + a2V5IjogIjczYzM0OWM5M2MxNjNiNWQ0ZGY5OGE2NGZhYzFjNDMwIiwgImlkIjogImE1YjNmZjcz + LTVjMmQtNDI5Ny05M2ExLTY2NDMyOWJmNGNiYiIsICJyb2xlIjogInt0b3BpY30gU2VuaW9yIERh + dGEgUmVzZWFyY2hlclxuIiwgInZlcmJvc2U/IjogdHJ1ZSwgIm1heF9pdGVyIjogMjAsICJtYXhf + cnBtIjogbnVsbCwgImZ1bmN0aW9uX2NhbGxpbmdfbGxtIjogIiIsICJsbG0iOiAiZ3B0LTRvIiwg + ImRlbGVnYXRpb25fZW5hYmxlZD8iOiBmYWxzZSwgImFsbG93X2NvZGVfZXhlY3V0aW9uPyI6IGZh + bHNlLCAibWF4X3JldHJ5X2xpbWl0IjogMiwgInRvb2xzX25hbWVzIjogW119LCB7ImtleSI6ICIx + MDRmZTA2NTllMTBiNDI2Y2Y4OGYwMjRmYjU3MTU1MyIsICJpZCI6ICJiMTU4Y2RjNS1iYzZmLTQ3 + YTktYjE2NS1kNDQ2YzFmODhkNDUiLCAicm9sZSI6ICJ7dG9waWN9IFJlcG9ydGluZyBBbmFseXN0 + XG4iLCAidmVyYm9zZT8iOiB0cnVlLCAibWF4X2l0ZXIiOiAyMCwgIm1heF9ycG0iOiBudWxsLCAi + ZnVuY3Rpb25fY2FsbGluZ19sbG0iOiAiIiwgImxsbSI6ICJncHQtNG8iLCAiZGVsZWdhdGlvbl9l + bmFibGVkPyI6IGZhbHNlLCAiYWxsb3dfY29kZV9leGVjdXRpb24/IjogZmFsc2UsICJtYXhfcmV0 + cnlfbGltaXQiOiAyLCAidG9vbHNfbmFtZXMiOiBbXX1dSpMECgpjcmV3X3Rhc2tzEoQECoEEW3si + a2V5IjogIjZhZmM0YjM5NjI1OWZiYjc2ODFmNTZjNzc1NWNjOTM3IiwgImlkIjogImY0ZjZiZWI0 + LWRhOGQtNDU1MC05ZWVhLThlOWM5NzY1NWNlNCIsICJhc3luY19leGVjdXRpb24/IjogZmFsc2Us + ICJodW1hbl9pbnB1dD8iOiBmYWxzZSwgImFnZW50X3JvbGUiOiAie3RvcGljfSBTZW5pb3IgRGF0 + YSBSZXNlYXJjaGVyXG4iLCAiYWdlbnRfa2V5IjogIjczYzM0OWM5M2MxNjNiNWQ0ZGY5OGE2NGZh + YzFjNDMwIiwgInRvb2xzX25hbWVzIjogW119LCB7ImtleSI6ICJiMTdiMTg4ZGJmMTRmOTNhOThl + NWI5NWFhZDM2NzU3NyIsICJpZCI6ICJlMzA0ZWI4ZC1jYTViLTRmYmMtYmRiNS1mODZlNjE3ZWJl + ZDYiLCAiYXN5bmNfZXhlY3V0aW9uPyI6IGZhbHNlLCAiaHVtYW5faW5wdXQ/IjogZmFsc2UsICJh + Z2VudF9yb2xlIjogInt0b3BpY30gUmVwb3J0aW5nIEFuYWx5c3RcbiIsICJhZ2VudF9rZXkiOiAi + MTA0ZmUwNjU5ZTEwYjQyNmNmODhmMDI0ZmI1NzE1NTMiLCAidG9vbHNfbmFtZXMiOiBbXX1degIY + AYUBAAEAABKOAgoQ4nBhY/C/2pvPVCF+gQM4/xIIZfcNXrNnmOMqDFRhc2sgQ3JlYXRlZDABORiU + EVHU8QgYQUgJElHU8QgYSi4KCGNyZXdfa2V5EiIKIDFmMTI4YmRiN2JhYTRiNjc3MTRmMWRhZWRj + MmYzYWI2SjEKB2NyZXdfaWQSJgokMDE3YjUzYzQtZTQ3YS00NGMzLWFlNmEtYTg0NzY3ZDcwMTAy + Si4KCHRhc2tfa2V5EiIKIDZhZmM0YjM5NjI1OWZiYjc2ODFmNTZjNzc1NWNjOTM3SjEKB3Rhc2tf + aWQSJgokZjRmNmJlYjQtZGE4ZC00NTUwLTllZWEtOGU5Yzk3NjU1Y2U0egIYAYUBAAEAABKOAgoQ + /NE8mExW1Yc4m8BzpCjK4BII6h0SL5AwtM8qDFRhc2sgQ3JlYXRlZDABObAHR1HU8QgYQeB8R1HU + 8QgYSi4KCGNyZXdfa2V5EiIKIDFmMTI4YmRiN2JhYTRiNjc3MTRmMWRhZWRjMmYzYWI2SjEKB2Ny + ZXdfaWQSJgokMDE3YjUzYzQtZTQ3YS00NGMzLWFlNmEtYTg0NzY3ZDcwMTAySi4KCHRhc2tfa2V5 + EiIKIGIxN2IxODhkYmYxNGY5M2E5OGU1Yjk1YWFkMzY3NTc3SjEKB3Rhc2tfaWQSJgokZTMwNGVi + OGQtY2E1Yi00ZmJjLWJkYjUtZjg2ZTYxN2ViZWQ2egIYAYUBAAEAABKQDAoQ3X0ZU0/DPkeXYe62 + 8W/vqRII03hxvkN2cu4qDENyZXcgQ3JlYXRlZDABOUDsalnU8QgYQZAmbVnU8QgYShoKDmNyZXdh + aV92ZXJzaW9uEggKBjAuODAuMEoaCg5weXRob25fdmVyc2lvbhIICgYzLjExLjdKLgoIY3Jld19r + ZXkSIgogMWYxMjhiZGI3YmFhNGI2NzcxNGYxZGFlZGMyZjNhYjZKMQoHY3Jld19pZBImCiQ5NjNm + ODBkMy1jYTJkLTQ4MDktODRmMC1hNjgxNzEzM2YzOWFKHAoMY3Jld19wcm9jZXNzEgwKCnNlcXVl + bnRpYWxKEQoLY3Jld19tZW1vcnkSAhAAShoKFGNyZXdfbnVtYmVyX29mX3Rhc2tzEgIYAkobChVj + cmV3X251bWJlcl9vZl9hZ2VudHMSAhgCSqoFCgtjcmV3X2FnZW50cxKaBQqXBVt7ImtleSI6ICI3 + M2MzNDljOTNjMTYzYjVkNGRmOThhNjRmYWMxYzQzMCIsICJpZCI6ICJmNDFlMTI2YS0wZTNhLTRm + OWEtODdhNi05MzQ3ZGU5YTUxODQiLCAicm9sZSI6ICJ7dG9waWN9IFNlbmlvciBEYXRhIFJlc2Vh + cmNoZXJcbiIsICJ2ZXJib3NlPyI6IHRydWUsICJtYXhfaXRlciI6IDIwLCAibWF4X3JwbSI6IG51 + bGwsICJmdW5jdGlvbl9jYWxsaW5nX2xsbSI6ICIiLCAibGxtIjogImdwdC00byIsICJkZWxlZ2F0 + aW9uX2VuYWJsZWQ/IjogZmFsc2UsICJhbGxvd19jb2RlX2V4ZWN1dGlvbj8iOiBmYWxzZSwgIm1h + eF9yZXRyeV9saW1pdCI6IDIsICJ0b29sc19uYW1lcyI6IFtdfSwgeyJrZXkiOiAiMTA0ZmUwNjU5 + ZTEwYjQyNmNmODhmMDI0ZmI1NzE1NTMiLCAiaWQiOiAiMGZkMzJmYzAtNGY4Mi00OWNiLWI1MDct + MWZmZTU5YzBlNWRmIiwgInJvbGUiOiAie3RvcGljfSBSZXBvcnRpbmcgQW5hbHlzdFxuIiwgInZl + cmJvc2U/IjogdHJ1ZSwgIm1heF9pdGVyIjogMjAsICJtYXhfcnBtIjogbnVsbCwgImZ1bmN0aW9u + X2NhbGxpbmdfbGxtIjogIiIsICJsbG0iOiAiZ3B0LTRvIiwgImRlbGVnYXRpb25fZW5hYmxlZD8i + OiBmYWxzZSwgImFsbG93X2NvZGVfZXhlY3V0aW9uPyI6IGZhbHNlLCAibWF4X3JldHJ5X2xpbWl0 + IjogMiwgInRvb2xzX25hbWVzIjogW119XUqTBAoKY3Jld190YXNrcxKEBAqBBFt7ImtleSI6ICI2 + YWZjNGIzOTYyNTlmYmI3NjgxZjU2Yzc3NTVjYzkzNyIsICJpZCI6ICI1YjlkYTE0Ni0xOGQ1LTQ1 + NjctODYwNi1hMTU3MGU3YzQyYTgiLCAiYXN5bmNfZXhlY3V0aW9uPyI6IGZhbHNlLCAiaHVtYW5f + aW5wdXQ/IjogZmFsc2UsICJhZ2VudF9yb2xlIjogInt0b3BpY30gU2VuaW9yIERhdGEgUmVzZWFy + Y2hlclxuIiwgImFnZW50X2tleSI6ICI3M2MzNDljOTNjMTYzYjVkNGRmOThhNjRmYWMxYzQzMCIs + ICJ0b29sc19uYW1lcyI6IFtdfSwgeyJrZXkiOiAiYjE3YjE4OGRiZjE0ZjkzYTk4ZTViOTVhYWQz + Njc1NzciLCAiaWQiOiAiZDI1ZjVlZmQtZjMzNC00YjRjLWE1NjktNTI2ZjAyZGY3MDAzIiwgImFz + eW5jX2V4ZWN1dGlvbj8iOiBmYWxzZSwgImh1bWFuX2lucHV0PyI6IGZhbHNlLCAiYWdlbnRfcm9s + ZSI6ICJ7dG9waWN9IFJlcG9ydGluZyBBbmFseXN0XG4iLCAiYWdlbnRfa2V5IjogIjEwNGZlMDY1 + OWUxMGI0MjZjZjg4ZjAyNGZiNTcxNTUzIiwgInRvb2xzX25hbWVzIjogW119XXoCGAGFAQABAAAS + jgIKEDpNX1u5dj3lqgySCZra2sISCEMmeziSVN2NKgxUYXNrIENyZWF0ZWQwATmoV3lZ1PEIGEHQ + 93lZ1PEIGEouCghjcmV3X2tleRIiCiAxZjEyOGJkYjdiYWE0YjY3NzE0ZjFkYWVkYzJmM2FiNkox + CgdjcmV3X2lkEiYKJDk2M2Y4MGQzLWNhMmQtNDgwOS04NGYwLWE2ODE3MTMzZjM5YUouCgh0YXNr + X2tleRIiCiA2YWZjNGIzOTYyNTlmYmI3NjgxZjU2Yzc3NTVjYzkzN0oxCgd0YXNrX2lkEiYKJDVi + OWRhMTQ2LTE4ZDUtNDU2Ny04NjA2LWExNTcwZTdjNDJhOHoCGAGFAQABAAA= + headers: + Accept: + - '*/*' + Accept-Encoding: + - gzip, deflate + Connection: + - keep-alive + Content-Length: + - '8195' + Content-Type: + - application/x-protobuf + User-Agent: + - OTel-OTLP-Exporter-Python/1.27.0 + method: POST + uri: https://telemetry.crewai.com:4319/v1/traces + response: + body: + string: "\n\0" + headers: + Content-Length: + - '2' + Content-Type: + - application/x-protobuf + Date: + - Mon, 18 Nov 2024 03:19:16 GMT + status: + code: 200 + message: OK +- request: + body: '{"messages": [{"role": "system", "content": "You are plants Senior Data + Researcher\n. You''re a seasoned researcher with a knack for uncovering the + latest developments in plants. Known for your ability to find the most relevant + information and present it in a clear and concise manner.\n\nYour personal goal + is: Uncover cutting-edge developments in plants\n\nTo give my best complete + final answer to the task use the exact following format:\n\nThought: I now can + give a great answer\nFinal Answer: Your final answer must be the great and the + most complete as possible, it must be outcome described.\n\nI MUST use these + formats, my job depends on it!"}, {"role": "user", "content": "\nCurrent Task: + Conduct a thorough research about plants Make sure you find any interesting + and relevant information given the current year is 2024.\n\n\nThis is the expect + criteria for your final answer: A list with 10 bullet points of the most relevant + information about plants\n\nyou MUST return the actual complete content as the + final answer, not a summary.\n\nBegin! This is VERY important to you, use the + tools available and give your best Final Answer, your job depends on it!\n\nThought:"}], + "model": "gpt-4o", "stop": ["\nObservation:"], "stream": false}' + headers: + accept: + - application/json + accept-encoding: + - gzip, deflate + connection: + - keep-alive + content-length: + - '1245' + content-type: + - application/json + cookie: + - _cfuvid=74kaPOoAcp8YRSA0XocQ1FFNksu9V0_KiWdQfo7wQuQ-1731827382509-0.0.1.1-604800000 + host: + - api.openai.com + user-agent: + - OpenAI/Python 1.52.1 + x-stainless-arch: + - arm64 + x-stainless-async: + - 'false' + x-stainless-lang: + - python + x-stainless-os: + - MacOS + x-stainless-package-version: + - 1.52.1 + x-stainless-raw-response: + - 'true' + x-stainless-retry-count: + - '0' + x-stainless-runtime: + - CPython + x-stainless-runtime-version: + - 3.11.7 + method: POST + uri: https://api.openai.com/v1/chat/completions + response: + body: + string: !!binary | + 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 + IimVsFkOAAA= + headers: + CF-Cache-Status: + - DYNAMIC + CF-RAY: + - 8e44d146eed6a423-GRU + Connection: + - keep-alive + Content-Encoding: + - gzip + Content-Type: + - application/json + Date: + - Mon, 18 Nov 2024 03:19:25 GMT + Server: + - cloudflare + Set-Cookie: + - __cf_bm=TExeV_B53ShoY.Ag2_Czvi.2L9gx.ekuTvv6twEsyZs-1731899965-1.0.1.1-TI1CwjC1TYPFLagqlZnBGPwghLqfQ14IMBF7MxpAfc1ZVDU6ahhzFq9sUtZDpajNRPyefUF9MUCXzF8vfGAyPw; + path=/; expires=Mon, 18-Nov-24 03:49:25 GMT; domain=.api.openai.com; HttpOnly; + Secure; SameSite=None + Transfer-Encoding: + - chunked + X-Content-Type-Options: + - nosniff + access-control-expose-headers: + - X-Request-ID + alt-svc: + - h3=":443"; ma=86400 + openai-organization: + - crewai-iuxna1 + openai-processing-ms: + - '13765' + openai-version: + - '2020-10-01' + strict-transport-security: + - max-age=31536000; includeSubDomains; preload + x-ratelimit-limit-requests: + - '10000' + x-ratelimit-limit-tokens: + - '30000000' + x-ratelimit-remaining-requests: + - '9999' + x-ratelimit-remaining-tokens: + - '29999712' + x-ratelimit-reset-requests: + - 6ms + x-ratelimit-reset-tokens: + - 0s + x-request-id: + - req_2d538d2fe02ebd3efaa4c15c43b6f88a + status: + code: 200 + message: OK +- request: + body: !!binary | + Cs4CCiQKIgoMc2VydmljZS5uYW1lEhIKEGNyZXdBSS10ZWxlbWV0cnkSpQIKEgoQY3Jld2FpLnRl + bGVtZXRyeRKOAgoQGHdygqOzofI91Tjg07fdjRIIXwWg5RZ9w0oqDFRhc2sgQ3JlYXRlZDABOej4 + TaXX8QgYQcgbUKXX8QgYSi4KCGNyZXdfa2V5EiIKIDFmMTI4YmRiN2JhYTRiNjc3MTRmMWRhZWRj + MmYzYWI2SjEKB2NyZXdfaWQSJgokOTYzZjgwZDMtY2EyZC00ODA5LTg0ZjAtYTY4MTcxMzNmMzlh + Si4KCHRhc2tfa2V5EiIKIGIxN2IxODhkYmYxNGY5M2E5OGU1Yjk1YWFkMzY3NTc3SjEKB3Rhc2tf + aWQSJgokZDI1ZjVlZmQtZjMzNC00YjRjLWE1NjktNTI2ZjAyZGY3MDAzegIYAYUBAAEAAA== + headers: + Accept: + - '*/*' + Accept-Encoding: + - gzip, deflate + Connection: + - keep-alive + Content-Length: + - '337' + Content-Type: + - application/x-protobuf + User-Agent: + - OTel-OTLP-Exporter-Python/1.27.0 + method: POST + uri: https://telemetry.crewai.com:4319/v1/traces + response: + body: + string: "\n\0" + headers: + Content-Length: + - '2' + Content-Type: + - application/x-protobuf + Date: + - Mon, 18 Nov 2024 03:19:26 GMT + status: + code: 200 + message: OK +- request: + body: '{"messages": [{"role": "system", "content": "You are plants Reporting Analyst\n. + You''re a meticulous analyst with a keen eye for detail. You''re known for your + ability to turn complex data into clear and concise reports, making it easy + for others to understand and act on the information you provide.\n\nYour personal + goal is: Create detailed reports based on plants data analysis and research + findings\n\nTo give my best complete final answer to the task use the exact + following format:\n\nThought: I now can give a great answer\nFinal Answer: Your + final answer must be the great and the most complete as possible, it must be + outcome described.\n\nI MUST use these formats, my job depends on it!"}, {"role": + "user", "content": "\nCurrent Task: Review the context you got and expand each + topic into a full section for a report. Make sure the report is detailed and + contains any and all relevant information.\n\n\nThis is the expect criteria + for your final answer: A fully fledge reports with the mains topics, each with + a full section of information. Formatted as markdown without ''```''\n\nyou + MUST return the actual complete content as the final answer, not a summary.\n\nThis + is the context you''re working with:\n1. **Vertical Farming Advancements**: + In 2024, vertical farming is set to revolutionize how we grow plants by maximizing + space and resource efficiency. New technologies allow for urban agriculture + with minimal ecological impact, using hydroponics and aeroponics to cultivate + fresh produce in city environments.\n\n2. **CRISPR and Plant Genetics**: CRISPR + technology has advanced significantly, enabling precise genome editing in plants. + This allows for the development of crops that are more resistant to diseases, + pests, and climate change, potentially increasing yield and food security worldwide.\n\n3. + **Biodiversity Conservation**: Efforts to conserve plant biodiversity have gained + traction, with initiatives focusing on seed banks and botanical gardens. These + efforts aim to preserve the genetic diversity necessary for ecological resilience + in the face of changing environmental conditions.\n\n4. **Carbon Sequestration + Research**: Plants'' role in carbon capture is being harnessed more effectively, + with research focused on enhancing the carbon-sequestering abilities of trees + and soil through improved plant varieties and land management practices.\n\n5. + **Phytoremediation Technologies**: Innovative use of plants to clean up polluted + environments, known as phytoremediation, has advanced. Researchers are developing + plants specifically engineered to absorb heavy metals and other toxins from + the soil and water, aiding in environmental restoration.\n\n6. **Synthetic Botany**: + This emerging field involves redesigning plant biological processes to create + new functionalities such as biofuels, pharmaceuticals, and other valuable compounds. + This interdisciplinary approach opens new avenues for plant-based technology.\n\n7. + **Climate-Resilient Crops**: With climate change posing significant threats + to agriculture, developing crops that can withstand extreme weather conditions + such as drought and floods is a top priority. 2024 sees breakthroughs in engineering + crops that maintain productivity under these stressors.\n\n8. **Algae Farming + Innovations**: Algae are increasingly used in various industries due to their + efficiency in photosynthesis and rapid growth. Innovations in algae farming + are contributing to biofuel production, carbon capture, and sustainable aquaculture + feeds.\n\n9. **Edible Plant Packaging**: The trend towards sustainability in + reducing plastic waste has led to innovations in plant-based, edible packaging. + These biodegradable solutions are making headway in food safety, reducing waste, + and providing a more sustainable packaging alternative.\n\n10. **Forest Restoration + Projects**: Large-scale reforestation efforts are underway globally to combat + deforestation and habitat loss. These projects integrate traditional knowledge + with modern practices to restore ecosystems, promote biodiversity, and mitigate + climate impacts.\n\nBegin! This is VERY important to you, use the tools available + and give your best Final Answer, your job depends on it!\n\nThought:"}], "model": + "gpt-4o", "stop": ["\nObservation:"], "stream": false}' + headers: + accept: + - application/json + accept-encoding: + - gzip, deflate + connection: + - keep-alive + content-length: + - '4283' + content-type: + - application/json + cookie: + - _cfuvid=74kaPOoAcp8YRSA0XocQ1FFNksu9V0_KiWdQfo7wQuQ-1731827382509-0.0.1.1-604800000; + __cf_bm=TExeV_B53ShoY.Ag2_Czvi.2L9gx.ekuTvv6twEsyZs-1731899965-1.0.1.1-TI1CwjC1TYPFLagqlZnBGPwghLqfQ14IMBF7MxpAfc1ZVDU6ahhzFq9sUtZDpajNRPyefUF9MUCXzF8vfGAyPw + host: + - api.openai.com + user-agent: + - OpenAI/Python 1.52.1 + x-stainless-arch: + - arm64 + x-stainless-async: + - 'false' + x-stainless-lang: + - python + x-stainless-os: + - MacOS + x-stainless-package-version: + - 1.52.1 + x-stainless-raw-response: + - 'true' + x-stainless-retry-count: + - '0' + x-stainless-runtime: + - CPython + x-stainless-runtime-version: + - 3.11.7 + method: POST + uri: https://api.openai.com/v1/chat/completions + response: + body: + string: !!binary | + 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 + 75799uz/AAAA//8DAD3tznO2JQAA + headers: + CF-Cache-Status: + - DYNAMIC + CF-RAY: + - 8e44d19eed83a423-GRU + Connection: + - keep-alive + Content-Encoding: + - gzip + Content-Type: + - application/json + Date: + - Mon, 18 Nov 2024 03:19:42 GMT + Server: + - cloudflare + Transfer-Encoding: + - chunked + X-Content-Type-Options: + - nosniff + access-control-expose-headers: + - X-Request-ID + alt-svc: + - h3=":443"; ma=86400 + openai-organization: + - crewai-iuxna1 + openai-processing-ms: + - '17464' + openai-version: + - '2020-10-01' + strict-transport-security: + - max-age=31536000; includeSubDomains; preload + x-ratelimit-limit-requests: + - '10000' + x-ratelimit-limit-tokens: + - '30000000' + x-ratelimit-remaining-requests: + - '9999' + x-ratelimit-remaining-tokens: + - '29998958' + x-ratelimit-reset-requests: + - 6ms + x-ratelimit-reset-tokens: + - 2ms + x-request-id: + - req_9b9ed646842761c5b091045f5e85bfd3 + status: + code: 200 + message: OK +version: 1 diff --git a/tests/config/agents.yaml b/tests/config/agents.yaml new file mode 100644 index 000000000..84e8ef3cc --- /dev/null +++ b/tests/config/agents.yaml @@ -0,0 +1,21 @@ +researcher: + role: > + {topic} Senior Data Researcher + goal: > + Uncover cutting-edge developments in {topic} + backstory: > + You're a seasoned researcher with a knack for uncovering the latest + developments in {topic}. Known for your ability to find the most relevant + information and present it in a clear and concise manner. + verbose: true + +reporting_analyst: + role: > + {topic} Reporting Analyst + goal: > + Create detailed reports based on {topic} data analysis and research findings + backstory: > + You're a meticulous analyst with a keen eye for detail. You're known for + your ability to turn complex data into clear and concise reports, making + it easy for others to understand and act on the information you provide. + verbose: true \ No newline at end of file diff --git a/tests/config/tasks.yaml b/tests/config/tasks.yaml new file mode 100644 index 000000000..f30820855 --- /dev/null +++ b/tests/config/tasks.yaml @@ -0,0 +1,17 @@ +research_task: + description: > + Conduct a thorough research about {topic} + Make sure you find any interesting and relevant information given + the current year is 2024. + expected_output: > + A list with 10 bullet points of the most relevant information about {topic} + agent: researcher + +reporting_task: + description: > + Review the context you got and expand each topic into a full section for a report. + Make sure the report is detailed and contains any and all relevant information. + expected_output: > + A fully fledge reports with the mains topics, each with a full section of information. + Formatted as markdown without '```' + agent: reporting_analyst diff --git a/tests/project_test.py b/tests/project_test.py index 188ba024d..329886a25 100644 --- a/tests/project_test.py +++ b/tests/project_test.py @@ -1,6 +1,9 @@ from crewai.agent import Agent -from crewai.project import agent, task +from crewai.project import agent, task, before_crew, after_crew, crew +from crewai.project import CrewBase from crewai.task import Task +from crewai.crew import Crew +import pytest class SimpleCrew: @@ -23,6 +26,43 @@ class SimpleCrew: ) +@CrewBase +class TestCrew: + agents_config = "config/agents.yaml" + tasks_config = "config/tasks.yaml" + + @agent + def researcher(self): + return Agent(config=self.agents_config["researcher"]) + + @agent + def reporting_analyst(self): + return Agent(config=self.agents_config["reporting_analyst"]) + + @task + def research_task(self): + return Task(config=self.tasks_config["research_task"]) + + @task + def reporting_task(self): + return Task(config=self.tasks_config["reporting_task"]) + + @before_crew + def modify_inputs(self, inputs): + if inputs: + inputs["topic"] = "Bicycles" + return inputs + + @after_crew + def modify_outputs(self, outputs): + outputs.raw = outputs.raw + " post processed" + return outputs + + @crew + def crew(self): + return Crew(agents=self.agents, tasks=self.tasks, verbose=True) + + def test_agent_memoization(): crew = SimpleCrew() first_call_result = crew.simple_agent() @@ -53,3 +93,133 @@ def test_task_name(): assert ( custom_named_task.name == "Custom" ), "Custom task name is not being set as expected" + + +@pytest.mark.vcr(filter_headers=["authorization"]) +def test_before_crew_modification(): + crew = TestCrew() + inputs = {"topic": "LLMs"} + result = crew.kickoff(inputs=inputs) + print(result.raw) + assert "bicycles" in result.raw, "Before crew function did not modify inputs" + + +@pytest.mark.vcr(filter_headers=["authorization"]) +def test_after_crew_modification(): + crew = TestCrew() + # Assuming the crew execution returns a dict + result = crew.kickoff({"topic": "LLMs"}) + + assert "post processed" in result.raw, "After crew function did not modify outputs" + + +@pytest.mark.vcr(filter_headers=["authorization"]) +def test_before_crew_with_none_input(): + crew = TestCrew() + crew.crew().kickoff(None) + # Test should pass without raising exceptions + + +@pytest.mark.vcr(filter_headers=["authorization"]) +def test_multiple_before_after_crew(): + @CrewBase + class MultipleHooksCrew: + agents_config = "config/agents.yaml" + tasks_config = "config/tasks.yaml" + + @agent + def researcher(self): + return Agent(config=self.agents_config["researcher"]) + + @agent + def reporting_analyst(self): + return Agent(config=self.agents_config["reporting_analyst"]) + + @task + def research_task(self): + return Task(config=self.tasks_config["research_task"]) + + @task + def reporting_task(self): + return Task(config=self.tasks_config["reporting_task"]) + + @before_crew + def first_before(self, inputs): + inputs["topic"] = "Bicycles" + return inputs + + @before_crew + def second_before(self, inputs): + inputs["topic"] = "plants" + return inputs + + @after_crew + def first_after(self, outputs): + outputs.raw = outputs.raw + " processed first" + return outputs + + @after_crew + def second_after(self, outputs): + outputs.raw = outputs.raw + " processed second" + return outputs + + @crew + def crew(self): + return Crew(agents=self.agents, tasks=self.tasks, verbose=True) + + crew = MultipleHooksCrew() + result = crew.kickoff({"topic": "LLMs"}) + + assert "plants" in result.raw, "First before_crew not executed" + assert "processed first" in result.raw, "First after_crew not executed" + assert "processed second" in result.raw, "Second after_crew not executed" + + +@pytest.mark.vcr(filter_headers=["authorization"]) +def test_crew_execution_order(): + execution_order = [] + + @CrewBase + class OrderTestCrew: + agents_config = "config/agents.yaml" + tasks_config = "config/tasks.yaml" + + @agent + def researcher(self): + return Agent(config=self.agents_config["researcher"]) + + @agent + def reporting_analyst(self): + return Agent(config=self.agents_config["reporting_analyst"]) + + @task + def research_task(self): + execution_order.append("task") + return Task(config=self.tasks_config["research_task"]) + + @task + def reporting_task(self): + return Task(config=self.tasks_config["reporting_task"]) + + @before_crew + def before(self, inputs): + execution_order.append("before") + return inputs + + @after_crew + def after(self, outputs): + execution_order.append("after") + return outputs + + @crew + def crew(self): + return Crew(agents=self.agents, tasks=self.tasks, verbose=True) + + crew = OrderTestCrew() + crew.kickoff({"topic": "LLMs"}) + + assert execution_order == [ + "before", + "task", + "after", + ], "Crew execution order is incorrect" From 55e968c9e0ba0e4bfb716e5827550156a13681b0 Mon Sep 17 00:00:00 2001 From: Tony Kipkemboi Date: Tue, 19 Nov 2024 17:42:54 -0500 Subject: [PATCH 066/126] Update CLI Watson supported models + docs (#1628) --- docs/concepts/llms.mdx | 2 ++ src/crewai/cli/constants.py | 23 ++++++++--------------- 2 files changed, 10 insertions(+), 15 deletions(-) diff --git a/docs/concepts/llms.mdx b/docs/concepts/llms.mdx index 5757feca3..5211e6652 100644 --- a/docs/concepts/llms.mdx +++ b/docs/concepts/llms.mdx @@ -47,6 +47,8 @@ researcher: # llm: ollama/llama3:70b # llm: groq/llama-3.2-90b-vision-preview # llm: watsonx/meta-llama/llama-3-1-70b-instruct + # llm: nvidia_nim/meta/llama3-70b-instruct + # llm: sambanova/Meta-Llama-3.1-8B-Instruct # ... ``` diff --git a/src/crewai/cli/constants.py b/src/crewai/cli/constants.py index 4be08fa2a..e13349155 100644 --- a/src/crewai/cli/constants.py +++ b/src/crewai/cli/constants.py @@ -123,21 +123,14 @@ MODELS = { ], "ollama": ["ollama/llama3.1", "ollama/mixtral"], "watson": [ - "watsonx/google/flan-t5-xxl", - "watsonx/google/flan-ul2", - "watsonx/bigscience/mt0-xxl", - "watsonx/eleutherai/gpt-neox-20b", - "watsonx/ibm/mpt-7b-instruct2", - "watsonx/bigcode/starcoder", - "watsonx/meta-llama/llama-2-70b-chat", - "watsonx/meta-llama/llama-2-13b-chat", - "watsonx/ibm/granite-13b-instruct-v1", - "watsonx/ibm/granite-13b-chat-v1", - "watsonx/google/flan-t5-xl", - "watsonx/ibm/granite-13b-chat-v2", - "watsonx/ibm/granite-13b-instruct-v2", - "watsonx/elyza/elyza-japanese-llama-2-7b-instruct", - "watsonx/ibm-mistralai/mixtral-8x7b-instruct-v01-q", + "watsonx/meta-llama/llama-3-1-70b-instruct", + "watsonx/meta-llama/llama-3-1-8b-instruct", + "watsonx/meta-llama/llama-3-2-11b-vision-instruct", + "watsonx/meta-llama/llama-3-2-1b-instruct", + "watsonx/meta-llama/llama-3-2-90b-vision-instruct", + "watsonx/meta-llama/llama-3-405b-instruct", + "watsonx/mistral/mistral-large", + "watsonx/ibm/granite-3-8b-instruct", ], "bedrock": [ "bedrock/anthropic.claude-3-5-sonnet-20240620-v1:0", From 58af5c08f9f3f01c1c2120c85dc36aafb879a97f Mon Sep 17 00:00:00 2001 From: theCyberTech <84775494+theCyberTech@users.noreply.github.com> Date: Wed, 20 Nov 2024 19:23:09 +0800 Subject: [PATCH 067/126] docs: add gh_token documentation to GithubSearchTool --- docs/tools/githubsearchtool.mdx | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/docs/tools/githubsearchtool.mdx b/docs/tools/githubsearchtool.mdx index 6fd0e686a..80b363a3c 100644 --- a/docs/tools/githubsearchtool.mdx +++ b/docs/tools/githubsearchtool.mdx @@ -34,6 +34,7 @@ from crewai_tools import GithubSearchTool # Initialize the tool for semantic searches within a specific GitHub repository tool = GithubSearchTool( github_repo='https://github.com/example/repo', + gh_token='your_github_personal_access_token', content_types=['code', 'issue'] # Options: code, repo, pr, issue ) @@ -41,6 +42,7 @@ tool = GithubSearchTool( # Initialize the tool for semantic searches within a specific GitHub repository, so the agent can search any repository if it learns about during its execution tool = GithubSearchTool( + gh_token='your_github_personal_access_token', content_types=['code', 'issue'] # Options: code, repo, pr, issue ) ``` @@ -48,6 +50,7 @@ tool = GithubSearchTool( ## Arguments - `github_repo` : The URL of the GitHub repository where the search will be conducted. This is a mandatory field and specifies the target repository for your search. +- `gh_token` : Your GitHub Personal Access Token (PAT) required for authentication. You can create one in your GitHub account settings under Developer Settings > Personal Access Tokens. - `content_types` : Specifies the types of content to include in your search. You must provide a list of content types from the following options: `code` for searching within the code, `repo` for searching within the repository's general information, `pr` for searching within pull requests, and `issue` for searching within issues. This field is mandatory and allows tailoring the search to specific content types within the GitHub repository. @@ -77,5 +80,4 @@ tool = GithubSearchTool( ), ), ) -) -``` \ No newline at end of file +) \ No newline at end of file From 3e003f5e3209e0ce815b0a0214c56fd6c830e861 Mon Sep 17 00:00:00 2001 From: Gui Vieira Date: Wed, 20 Nov 2024 10:06:49 -0300 Subject: [PATCH 068/126] Move kickoff callbacks to crew's domain --- src/crewai/crew.py | 22 +- src/crewai/project/__init__.py | 8 +- src/crewai/project/annotations.py | 25 +- src/crewai/project/crew_base.py | 33 +- .../cassettes/test_crew_execution_order.yaml | 450 ------------------ tests/project_test.py | 109 ++--- 6 files changed, 82 insertions(+), 565 deletions(-) delete mode 100644 tests/cassettes/test_crew_execution_order.yaml diff --git a/src/crewai/crew.py b/src/crewai/crew.py index 04820adf8..fb03acb5b 100644 --- a/src/crewai/crew.py +++ b/src/crewai/crew.py @@ -5,7 +5,7 @@ import uuid import warnings from concurrent.futures import Future from hashlib import md5 -from typing import TYPE_CHECKING, Any, Dict, List, Optional, Tuple, Union +from typing import TYPE_CHECKING, Any, Callable, Dict, List, Optional, Tuple, Union from pydantic import ( UUID4, @@ -36,9 +36,7 @@ from crewai.telemetry import Telemetry from crewai.tools.agent_tools.agent_tools import AgentTools from crewai.types.usage_metrics import UsageMetrics from crewai.utilities import I18N, FileHandler, Logger, RPMController -from crewai.utilities.constants import ( - 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.formatter import ( @@ -165,6 +163,16 @@ class Crew(BaseModel): default=None, description="Callback to be executed after each task for all agents execution.", ) + before_kickoff_callbacks: List[ + Callable[[Optional[Dict[str, Any]]], Optional[Dict[str, Any]]] + ] = Field( + default_factory=list, + description="List of callbacks to be executed before crew kickoff. It may be used to adjust inputs before the crew is executed.", + ) + after_kickoff_callbacks: List[Callable[[CrewOutput], CrewOutput]] = Field( + default_factory=list, + description="List of callbacks to be executed after crew kickoff. It may be used to adjust the output of the crew.", + ) max_rpm: Optional[int] = Field( default=None, description="Maximum number of requests per minute for the crew execution to be respected.", @@ -478,6 +486,9 @@ class Crew(BaseModel): self, inputs: Optional[Dict[str, Any]] = None, ) -> CrewOutput: + for callback in self.before_kickoff_callbacks: + inputs = callback(inputs) + """Starts the crew to work on its assigned tasks.""" self._execution_span = self._telemetry.crew_execution_span(self, inputs) self._task_output_handler.reset() @@ -520,6 +531,9 @@ class Crew(BaseModel): f"The process '{self.process}' is not implemented yet." ) + for callback in self.after_kickoff_callbacks: + result = callback(result) + metrics += [agent._token_process.get_summary() for agent in self.agents] self.usage_metrics = UsageMetrics() diff --git a/src/crewai/project/__init__.py b/src/crewai/project/__init__.py index 1dbb6e41b..25f6f5e04 100644 --- a/src/crewai/project/__init__.py +++ b/src/crewai/project/__init__.py @@ -1,5 +1,7 @@ from .annotations import ( + after_kickoff, agent, + before_kickoff, cache_handler, callback, crew, @@ -9,8 +11,6 @@ from .annotations import ( pipeline, task, tool, - before_crew, - after_crew, ) from .crew_base import CrewBase from .pipeline_base import PipelineBase @@ -28,6 +28,6 @@ __all__ = [ "llm", "cache_handler", "pipeline", - "before_crew", - "after_crew", + "before_kickoff", + "after_kickoff", ] diff --git a/src/crewai/project/annotations.py b/src/crewai/project/annotations.py index c1a497fa1..83e396a4c 100644 --- a/src/crewai/project/annotations.py +++ b/src/crewai/project/annotations.py @@ -5,13 +5,13 @@ from crewai import Crew from crewai.project.utils import memoize -def before_crew(func): - func.is_before_crew = True +def before_kickoff(func): + func.is_before_kickoff = True return func -def after_crew(func): - func.is_after_crew = True +def after_kickoff(func): + func.is_after_kickoff = True return func @@ -109,6 +109,19 @@ def crew(func) -> Callable[..., Crew]: self.agents = instantiated_agents self.tasks = instantiated_tasks - return func(self, *args, **kwargs) + crew = func(self, *args, **kwargs) - return wrapper + def callback_wrapper(callback, instance): + def wrapper(*args, **kwargs): + return callback(instance, *args, **kwargs) + + return wrapper + + for _, callback in self._before_kickoff.items(): + crew.before_kickoff_callbacks.append(callback_wrapper(callback, self)) + for _, callback in self._after_kickoff.items(): + crew.after_kickoff_callbacks.append(callback_wrapper(callback, self)) + + return crew + + return memoize(wrapper) diff --git a/src/crewai/project/crew_base.py b/src/crewai/project/crew_base.py index ed48b24d1..e93452a6e 100644 --- a/src/crewai/project/crew_base.py +++ b/src/crewai/project/crew_base.py @@ -43,8 +43,8 @@ def CrewBase(cls: T) -> T: for attr in [ "is_task", "is_agent", - "is_before_crew", - "is_after_crew", + "is_before_kickoff", + "is_after_kickoff", "is_kickoff", ] ) @@ -57,11 +57,11 @@ def CrewBase(cls: T) -> T: self._original_agents = self._filter_functions( self._original_functions, "is_agent" ) - self._before_crew = self._filter_functions( - self._original_functions, "is_before_crew" + self._before_kickoff = self._filter_functions( + self._original_functions, "is_before_kickoff" ) - self._after_crew = self._filter_functions( - self._original_functions, "is_after_crew" + self._after_kickoff = self._filter_functions( + self._original_functions, "is_after_kickoff" ) self._kickoff = self._filter_functions( self._original_functions, "is_kickoff" @@ -213,25 +213,4 @@ def CrewBase(cls: T) -> T: callback_functions[callback]() for callback in callbacks ] - def kickoff(self, inputs=None): - # Execute before_crew functions and allow them to modify inputs - for _, func in self._before_crew.items(): - modified_inputs = func(self, inputs) - if modified_inputs is not None: - inputs = modified_inputs - - # Get the crew instance - crew_instance = self.crew() - - # Execute the crew's tasks - result = crew_instance.kickoff(inputs=inputs) - - # Execute after_crew functions and allow them to modify the output - for _, func in self._after_crew.items(): - modified_result = func(self, result) - if modified_result is not None: - result = modified_result - - return result - return cast(T, WrappedClass) diff --git a/tests/cassettes/test_crew_execution_order.yaml b/tests/cassettes/test_crew_execution_order.yaml deleted file mode 100644 index 5f05a3258..000000000 --- a/tests/cassettes/test_crew_execution_order.yaml +++ /dev/null @@ -1,450 +0,0 @@ -interactions: -- request: - body: !!binary | - CuMOCiQKIgoMc2VydmljZS5uYW1lEhIKEGNyZXdBSS10ZWxlbWV0cnkSug4KEgoQY3Jld2FpLnRl - bGVtZXRyeRKSDAoQiPNFpMW9CoeJ9Zm3+M0txRIIWLnalHui3g4qDENyZXcgQ3JlYXRlZDABOcAS - H7rirwgYQRBNIbrirwgYShoKDmNyZXdhaV92ZXJzaW9uEggKBjAuODAuMEoaCg5weXRob25fdmVy - c2lvbhIICgYzLjExLjdKLgoIY3Jld19rZXkSIgogZjM0NmE5YWQ2ZDczMDYzZTA2NzdiMTdjZTlj - NTAxNzdKMQoHY3Jld19pZBImCiQ1MTZhMTZiNi1mMWY2LTRlMzUtYjY3Ni05ZjRiMGY3MTBhZDRK - HAoMY3Jld19wcm9jZXNzEgwKCnNlcXVlbnRpYWxKEQoLY3Jld19tZW1vcnkSAhAAShoKFGNyZXdf - bnVtYmVyX29mX3Rhc2tzEgIYAkobChVjcmV3X251bWJlcl9vZl9hZ2VudHMSAhgCSqwFCgtjcmV3 - X2FnZW50cxKcBQqZBVt7ImtleSI6ICI3M2MzNDljOTNjMTYzYjVkNGRmOThhNjRmYWMxYzQzMCIs - ICJpZCI6ICI1N2ZhM2QwNC04ZTQ5LTQyMDMtOTg2OS1lNzliMDdiYWJkNGMiLCAicm9sZSI6ICJ7 - dG9waWN9IFNlbmlvciBEYXRhIFJlc2VhcmNoZXJcbiIsICJ2ZXJib3NlPyI6IGZhbHNlLCAibWF4 - X2l0ZXIiOiAyMCwgIm1heF9ycG0iOiBudWxsLCAiZnVuY3Rpb25fY2FsbGluZ19sbG0iOiAiIiwg - ImxsbSI6ICJncHQtNG8iLCAiZGVsZWdhdGlvbl9lbmFibGVkPyI6IGZhbHNlLCAiYWxsb3dfY29k - ZV9leGVjdXRpb24/IjogZmFsc2UsICJtYXhfcmV0cnlfbGltaXQiOiAyLCAidG9vbHNfbmFtZXMi - OiBbXX0sIHsia2V5IjogImJiMDY4Mzc3YzE2NDFiZTZkN2Q5N2E1MTY1OWRiNjEzIiwgImlkIjog - ImM5NzA4YTVjLTI4MjEtNDI4Ny05ZDkyLWE3MmU1NWEyMTZlYiIsICJyb2xlIjogInt0b3BpY30g - UmVwb3J0aW5nIEFuYWx5c3RcbiIsICJ2ZXJib3NlPyI6IGZhbHNlLCAibWF4X2l0ZXIiOiAyMCwg - Im1heF9ycG0iOiBudWxsLCAiZnVuY3Rpb25fY2FsbGluZ19sbG0iOiAiIiwgImxsbSI6ICJncHQt - NG8iLCAiZGVsZWdhdGlvbl9lbmFibGVkPyI6IGZhbHNlLCAiYWxsb3dfY29kZV9leGVjdXRpb24/ - IjogZmFsc2UsICJtYXhfcmV0cnlfbGltaXQiOiAyLCAidG9vbHNfbmFtZXMiOiBbXX1dSpMECgpj - cmV3X3Rhc2tzEoQECoEEW3sia2V5IjogIjZhZmM0YjM5NjI1OWZiYjc2ODFmNTZjNzc1NWNjOTM3 - IiwgImlkIjogIjYxOWYxNWFhLTc1NDItNDJiOC04MDZhLWJlNmVhZTQwYmYyMyIsICJhc3luY19l - eGVjdXRpb24/IjogZmFsc2UsICJodW1hbl9pbnB1dD8iOiBmYWxzZSwgImFnZW50X3JvbGUiOiAi - e3RvcGljfSBTZW5pb3IgRGF0YSBSZXNlYXJjaGVyXG4iLCAiYWdlbnRfa2V5IjogIjczYzM0OWM5 - M2MxNjNiNWQ0ZGY5OGE2NGZhYzFjNDMwIiwgInRvb2xzX25hbWVzIjogW119LCB7ImtleSI6ICJi - MTdiMTg4ZGJmMTRmOTNhOThlNWI5NWFhZDM2NzU3NyIsICJpZCI6ICJhNGIzOGJjYi1jN2Y3LTRk - MTMtYjFiNC00Yzc0ZmE4NzIxOTQiLCAiYXN5bmNfZXhlY3V0aW9uPyI6IGZhbHNlLCAiaHVtYW5f - aW5wdXQ/IjogZmFsc2UsICJhZ2VudF9yb2xlIjogInt0b3BpY30gUmVwb3J0aW5nIEFuYWx5c3Rc - biIsICJhZ2VudF9rZXkiOiAiYmIwNjgzNzdjMTY0MWJlNmQ3ZDk3YTUxNjU5ZGI2MTMiLCAidG9v - bHNfbmFtZXMiOiBbXX1degIYAYUBAAEAABKOAgoQaSYAFNgUUCxMT0z+KwJCrBIIrjh6pRvY2zEq - DFRhc2sgQ3JlYXRlZDABOag/LbrirwgYQTilLbrirwgYSi4KCGNyZXdfa2V5EiIKIGYzNDZhOWFk - NmQ3MzA2M2UwNjc3YjE3Y2U5YzUwMTc3SjEKB2NyZXdfaWQSJgokNTE2YTE2YjYtZjFmNi00ZTM1 - LWI2NzYtOWY0YjBmNzEwYWQ0Si4KCHRhc2tfa2V5EiIKIDZhZmM0YjM5NjI1OWZiYjc2ODFmNTZj - Nzc1NWNjOTM3SjEKB3Rhc2tfaWQSJgokNjE5ZjE1YWEtNzU0Mi00MmI4LTgwNmEtYmU2ZWFlNDBi - ZjIzegIYAYUBAAEAAA== - headers: - Accept: - - '*/*' - Accept-Encoding: - - gzip, deflate - Connection: - - keep-alive - Content-Length: - - '1894' - Content-Type: - - application/x-protobuf - User-Agent: - - OTel-OTLP-Exporter-Python/1.27.0 - method: POST - uri: https://telemetry.crewai.com:4319/v1/traces - response: - body: - string: "\n\0" - headers: - Content-Length: - - '2' - Content-Type: - - application/x-protobuf - Date: - - Sun, 17 Nov 2024 07:10:46 GMT - status: - code: 200 - message: OK -- request: - body: '{"messages": [{"role": "system", "content": "You are LLMs Senior Data Researcher\n. - You''re a seasoned researcher with a knack for uncovering the latest developments - in LLMs. Known for your ability to find the most relevant information and present - it in a clear and concise manner.\n\nYour personal goal is: Uncover cutting-edge - developments in LLMs\n\nTo give my best complete final answer to the task use - the exact following format:\n\nThought: I now can give a great answer\nFinal - Answer: Your final answer must be the great and the most complete as possible, - it must be outcome described.\n\nI MUST use these formats, my job depends on - it!"}, {"role": "user", "content": "\nCurrent Task: Conduct a thorough research - about LLMs Make sure you find any interesting and relevant information given - the current year is 2024.\n\n\nThis is the expect criteria for your final answer: - A list with 10 bullet points of the most relevant information about LLMs\n\nyou - MUST return the actual complete content as the final answer, not a summary.\n\nBegin! - This is VERY important to you, use the tools available and give your best Final - Answer, your job depends on it!\n\nThought:"}], "model": "gpt-4o", "stop": ["\nObservation:"], - "stream": false}' - headers: - accept: - - application/json - accept-encoding: - - gzip, deflate - connection: - - keep-alive - content-length: - - '1235' - content-type: - - application/json - cookie: - - __cf_bm=08pKRcLhS1PDw0mYfL2jz19ac6M.T31GoiMuI5DlX6w-1731827382-1.0.1.1-UfOLu3AaIUuXP1sGzdV6oggJ1q7iMTC46t08FDhYVrKcW5YmD4CbifudOJiSgx8h0JLTwZdgk.aG05S0eAO_PQ; - _cfuvid=74kaPOoAcp8YRSA0XocQ1FFNksu9V0_KiWdQfo7wQuQ-1731827382509-0.0.1.1-604800000 - host: - - api.openai.com - user-agent: - - OpenAI/Python 1.52.1 - x-stainless-arch: - - arm64 - x-stainless-async: - - 'false' - x-stainless-lang: - - python - x-stainless-os: - - MacOS - x-stainless-package-version: - - 1.52.1 - x-stainless-raw-response: - - 'true' - x-stainless-retry-count: - - '0' - x-stainless-runtime: - - CPython - x-stainless-runtime-version: - - 3.11.7 - method: POST - uri: https://api.openai.com/v1/chat/completions - response: - body: - string: !!binary | - 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 - headers: - CF-Cache-Status: - - DYNAMIC - CF-RAY: - - 8e3de71c79986217-GRU - Connection: - - keep-alive - Content-Encoding: - - gzip - Content-Type: - - application/json - Date: - - Sun, 17 Nov 2024 07:10:50 GMT - Server: - - cloudflare - Transfer-Encoding: - - chunked - X-Content-Type-Options: - - nosniff - access-control-expose-headers: - - X-Request-ID - alt-svc: - - h3=":443"; ma=86400 - openai-organization: - - crewai-iuxna1 - openai-processing-ms: - - '5446' - openai-version: - - '2020-10-01' - strict-transport-security: - - max-age=31536000; includeSubDomains; preload - x-ratelimit-limit-requests: - - '10000' - x-ratelimit-limit-tokens: - - '30000000' - x-ratelimit-remaining-requests: - - '9999' - x-ratelimit-remaining-tokens: - - '29999713' - x-ratelimit-reset-requests: - - 6ms - x-ratelimit-reset-tokens: - - 0s - x-request-id: - - req_dda88f1d73ab714daf68612f9936d111 - status: - code: 200 - message: OK -- request: - body: !!binary | - Cs4CCiQKIgoMc2VydmljZS5uYW1lEhIKEGNyZXdBSS10ZWxlbWV0cnkSpQIKEgoQY3Jld2FpLnRl - bGVtZXRyeRKOAgoQyRM1X58vc+Q8vTI/ofl1GBIIY3Nm20DB7JsqDFRhc2sgQ3JlYXRlZDABOZgm - aAvkrwgYQehgagvkrwgYSi4KCGNyZXdfa2V5EiIKIGYzNDZhOWFkNmQ3MzA2M2UwNjc3YjE3Y2U5 - YzUwMTc3SjEKB2NyZXdfaWQSJgokNTE2YTE2YjYtZjFmNi00ZTM1LWI2NzYtOWY0YjBmNzEwYWQ0 - Si4KCHRhc2tfa2V5EiIKIGIxN2IxODhkYmYxNGY5M2E5OGU1Yjk1YWFkMzY3NTc3SjEKB3Rhc2tf - aWQSJgokYTRiMzhiY2ItYzdmNy00ZDEzLWIxYjQtNGM3NGZhODcyMTk0egIYAYUBAAEAAA== - headers: - Accept: - - '*/*' - Accept-Encoding: - - gzip, deflate - Connection: - - keep-alive - Content-Length: - - '337' - Content-Type: - - application/x-protobuf - User-Agent: - - OTel-OTLP-Exporter-Python/1.27.0 - method: POST - uri: https://telemetry.crewai.com:4319/v1/traces - response: - body: - string: "\n\0" - headers: - Content-Length: - - '2' - Content-Type: - - application/x-protobuf - Date: - - Sun, 17 Nov 2024 07:10:51 GMT - status: - code: 200 - message: OK -- request: - body: '{"messages": [{"role": "system", "content": "You are LLMs Reporting Analyst\n. - You''re a meticulous analyst with a keen eye for detail. You''re known for your - ability to turn complex data into clear and concise reports, making it easy - for others to understand and act on the information you provide.\nYour personal - goal is: Create detailed reports based on LLMs data analysis and research findings\n\nTo - give my best complete final answer to the task use the exact following format:\n\nThought: - I now can give a great answer\nFinal Answer: Your final answer must be the great - and the most complete as possible, it must be outcome described.\n\nI MUST use - these formats, my job depends on it!"}, {"role": "user", "content": "\nCurrent - Task: Review the context you got and expand each topic into a full section for - a report. Make sure the report is detailed and contains any and all relevant - information.\n\n\nThis is the expect criteria for your final answer: A fully - fledge reports with the mains topics, each with a full section of information. - Formatted as markdown without ''```''\n\nyou MUST return the actual complete - content as the final answer, not a summary.\n\nThis is the context you''re working - with:\n1. **Advancements in Architectural Design**: In 2024, LLMs have seen - significant improvements in architecture, with models adopting more layered - and complex structures that enhance contextual understanding and prevent degradation - in performance over long text sequences. This has improved accuracy and coherence - in generating human-like text.\n\n2. **Integration with Multimodal Data**: The - latest LLMs have integrated multimodal capabilities, allowing them to process - and generate not just text, but also integrate and interpret images, audio, - and video data. This integration significantly enhances user interaction capabilities - and widens practical applications, particularly in fields such as creative industries - and customer service.\n\n3. **Efficiency and Scalability**: There is a strong - focus on reducing the carbon footprint of training and deploying LLMs. Techniques - such as model distillation and the use of more efficient training processes - are commonplace. Models are also being designed for scalability to better serve - a wide range of applications without requiring extensive computational resources.\n\n4. - **Fine-Tuning and Customization**: The ability to finely tune LLMs for specific - industry or organizational needs has become more advanced. Developers can now - more easily customize these models to align with particular styles, industry - jargon, or brands, enhancing relevance and utility across diverse applications.\n\n5. - **Ethical AI and Safety Measures**: A growing emphasis is placed on ethical - considerations. Frameworks for ensuring fairness, reducing biases, and enhancing - transparency in outputs are being developed. New protocols are in place to predict - and mitigate potential misuses of LLMs to improve trust and reliability.\n\n6. - **Proliferation of Open Source Models**: With the community demanding transparency - and flexibility, many open-source LLMs have been developed to promote research - and innovation. These models provide robust alternatives to commercial counterparts - and enhance collaborative development efforts across the globe.\n\n7. **Emergence - of LLM-Driven Applications**: 2024 has seen a rise in applications based on - LLM technology, including advanced customer service bots, dynamic content creation - tools, and educational software that personalizes learning experiences. This - expands the role of LLMs beyond traditional academic or entertainment purposes.\n\n8. - **Focus on Low-Resource Language Support**: LLMs are becoming more adept at - understanding and generating languages with fewer resources, vastly improving - global accessibility and allowing technology to support a wider array of linguistic - communities. This helps bridge digital divides and promote inclusivity.\n\n9. - **Enhanced Interaction Techniques**: Novel interaction paradigms, such as real-time - adaptations and on-the-fly learning, enable LLMs to adjust tone, complexity, - and content more responsively to user feedback. This leads to more natural, - engaging, and personalized interactions in various interface environments.\n\n10. - **Collaborative Intelligence**: There''s an emerging trend of using LLMs in - collaborative settings where AI assists human decision-making by synthesizing - large datasets to suggest insights and trends. This symbiosis enhances productivity - and innovation in research and business contexts.\n\nBegin! This is VERY important - to you, use the tools available and give your best Final Answer, your job depends - on it!\n\nThought:"}], "model": "gpt-4o", "stop": ["\nObservation:"], "stream": - false}' - headers: - accept: - - application/json - accept-encoding: - - gzip, deflate - connection: - - keep-alive - content-length: - - '4742' - content-type: - - application/json - cookie: - - __cf_bm=08pKRcLhS1PDw0mYfL2jz19ac6M.T31GoiMuI5DlX6w-1731827382-1.0.1.1-UfOLu3AaIUuXP1sGzdV6oggJ1q7iMTC46t08FDhYVrKcW5YmD4CbifudOJiSgx8h0JLTwZdgk.aG05S0eAO_PQ; - _cfuvid=74kaPOoAcp8YRSA0XocQ1FFNksu9V0_KiWdQfo7wQuQ-1731827382509-0.0.1.1-604800000 - host: - - api.openai.com - user-agent: - - OpenAI/Python 1.52.1 - x-stainless-arch: - - arm64 - x-stainless-async: - - 'false' - x-stainless-lang: - - python - x-stainless-os: - - MacOS - x-stainless-package-version: - - 1.52.1 - x-stainless-raw-response: - - 'true' - x-stainless-retry-count: - - '0' - x-stainless-runtime: - - CPython - x-stainless-runtime-version: - - 3.11.7 - method: POST - uri: https://api.openai.com/v1/chat/completions - response: - body: - string: !!binary | - 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 - L4f7OwUCdjevN/8BAAD//wMA9xdqlskdAAA= - headers: - CF-Cache-Status: - - DYNAMIC - CF-RAY: - - 8e3de74049116217-GRU - Connection: - - keep-alive - Content-Encoding: - - gzip - Content-Type: - - application/json - Date: - - Sun, 17 Nov 2024 07:11:00 GMT - Server: - - cloudflare - Transfer-Encoding: - - chunked - X-Content-Type-Options: - - nosniff - access-control-expose-headers: - - X-Request-ID - alt-svc: - - h3=":443"; ma=86400 - openai-organization: - - crewai-iuxna1 - openai-processing-ms: - - '9414' - openai-version: - - '2020-10-01' - strict-transport-security: - - max-age=31536000; includeSubDomains; preload - x-ratelimit-limit-requests: - - '10000' - x-ratelimit-limit-tokens: - - '30000000' - x-ratelimit-remaining-requests: - - '9999' - x-ratelimit-remaining-tokens: - - '29998843' - x-ratelimit-reset-requests: - - 6ms - x-ratelimit-reset-tokens: - - 2ms - x-request-id: - - req_ffd793f9d60c093888d2483ea4305dd6 - status: - code: 200 - message: OK -version: 1 diff --git a/tests/project_test.py b/tests/project_test.py index 329886a25..6c68f4993 100644 --- a/tests/project_test.py +++ b/tests/project_test.py @@ -1,10 +1,10 @@ -from crewai.agent import Agent -from crewai.project import agent, task, before_crew, after_crew, crew -from crewai.project import CrewBase -from crewai.task import Task -from crewai.crew import Crew import pytest +from crewai.agent import Agent +from crewai.crew import Crew +from crewai.project import CrewBase, after_kickoff, agent, before_kickoff, crew, task +from crewai.task import Task + class SimpleCrew: @agent @@ -47,13 +47,13 @@ class TestCrew: def reporting_task(self): return Task(config=self.tasks_config["reporting_task"]) - @before_crew + @before_kickoff def modify_inputs(self, inputs): if inputs: inputs["topic"] = "Bicycles" return inputs - @after_crew + @after_kickoff def modify_outputs(self, outputs): outputs.raw = outputs.raw + " post processed" return outputs @@ -83,6 +83,16 @@ def test_task_memoization(): ), "Task memoization is not working as expected" +def test_crew_memoization(): + crew = TestCrew() + first_call_result = crew.crew() + second_call_result = crew.crew() + + assert ( + first_call_result is second_call_result + ), "Crew references should point to the same object" + + def test_task_name(): simple_task = SimpleCrew().simple_task() assert ( @@ -96,32 +106,33 @@ def test_task_name(): @pytest.mark.vcr(filter_headers=["authorization"]) -def test_before_crew_modification(): +def test_before_kickoff_modification(): crew = TestCrew() inputs = {"topic": "LLMs"} - result = crew.kickoff(inputs=inputs) - print(result.raw) - assert "bicycles" in result.raw, "Before crew function did not modify inputs" + result = crew.crew().kickoff(inputs=inputs) + assert "bicycles" in result.raw, "Before kickoff function did not modify inputs" @pytest.mark.vcr(filter_headers=["authorization"]) -def test_after_crew_modification(): +def test_after_kickoff_modification(): crew = TestCrew() # Assuming the crew execution returns a dict - result = crew.kickoff({"topic": "LLMs"}) + result = crew.crew().kickoff({"topic": "LLMs"}) - assert "post processed" in result.raw, "After crew function did not modify outputs" + assert ( + "post processed" in result.raw + ), "After kickoff function did not modify outputs" @pytest.mark.vcr(filter_headers=["authorization"]) -def test_before_crew_with_none_input(): +def test_before_kickoff_with_none_input(): crew = TestCrew() crew.crew().kickoff(None) # Test should pass without raising exceptions @pytest.mark.vcr(filter_headers=["authorization"]) -def test_multiple_before_after_crew(): +def test_multiple_before_after_kickoff(): @CrewBase class MultipleHooksCrew: agents_config = "config/agents.yaml" @@ -143,22 +154,22 @@ def test_multiple_before_after_crew(): def reporting_task(self): return Task(config=self.tasks_config["reporting_task"]) - @before_crew + @before_kickoff def first_before(self, inputs): inputs["topic"] = "Bicycles" return inputs - @before_crew + @before_kickoff def second_before(self, inputs): inputs["topic"] = "plants" return inputs - @after_crew + @after_kickoff def first_after(self, outputs): outputs.raw = outputs.raw + " processed first" return outputs - @after_crew + @after_kickoff def second_after(self, outputs): outputs.raw = outputs.raw + " processed second" return outputs @@ -168,58 +179,8 @@ def test_multiple_before_after_crew(): return Crew(agents=self.agents, tasks=self.tasks, verbose=True) crew = MultipleHooksCrew() - result = crew.kickoff({"topic": "LLMs"}) + result = crew.crew().kickoff({"topic": "LLMs"}) - assert "plants" in result.raw, "First before_crew not executed" - assert "processed first" in result.raw, "First after_crew not executed" - assert "processed second" in result.raw, "Second after_crew not executed" - - -@pytest.mark.vcr(filter_headers=["authorization"]) -def test_crew_execution_order(): - execution_order = [] - - @CrewBase - class OrderTestCrew: - agents_config = "config/agents.yaml" - tasks_config = "config/tasks.yaml" - - @agent - def researcher(self): - return Agent(config=self.agents_config["researcher"]) - - @agent - def reporting_analyst(self): - return Agent(config=self.agents_config["reporting_analyst"]) - - @task - def research_task(self): - execution_order.append("task") - return Task(config=self.tasks_config["research_task"]) - - @task - def reporting_task(self): - return Task(config=self.tasks_config["reporting_task"]) - - @before_crew - def before(self, inputs): - execution_order.append("before") - return inputs - - @after_crew - def after(self, outputs): - execution_order.append("after") - return outputs - - @crew - def crew(self): - return Crew(agents=self.agents, tasks=self.tasks, verbose=True) - - crew = OrderTestCrew() - crew.kickoff({"topic": "LLMs"}) - - assert execution_order == [ - "before", - "task", - "after", - ], "Crew execution order is incorrect" + assert "plants" in result.raw, "First before_kickoff not executed" + assert "processed first" in result.raw, "First after_kickoff not executed" + assert "processed second" in result.raw, "Second after_kickoff not executed" From 495c3859af015b970c7c478946769ae18983de17 Mon Sep 17 00:00:00 2001 From: Gui Vieira Date: Wed, 20 Nov 2024 10:26:00 -0300 Subject: [PATCH 069/126] Cassettes --- .../test_after_kickoff_modification.yaml | 487 +++++++++++++++++ .../test_before_kickoff_modification.yaml | 500 ++++++++++++++++++ .../test_before_kickoff_with_none_input.yaml | 492 +++++++++++++++++ .../test_multiple_before_after_kickoff.yaml | 495 +++++++++++++++++ 4 files changed, 1974 insertions(+) create mode 100644 tests/cassettes/test_after_kickoff_modification.yaml create mode 100644 tests/cassettes/test_before_kickoff_modification.yaml create mode 100644 tests/cassettes/test_before_kickoff_with_none_input.yaml create mode 100644 tests/cassettes/test_multiple_before_after_kickoff.yaml diff --git a/tests/cassettes/test_after_kickoff_modification.yaml b/tests/cassettes/test_after_kickoff_modification.yaml new file mode 100644 index 000000000..bf63b910d --- /dev/null +++ b/tests/cassettes/test_after_kickoff_modification.yaml @@ -0,0 +1,487 @@ +interactions: +- request: + body: !!binary | + CusOCiQKIgoMc2VydmljZS5uYW1lEhIKEGNyZXdBSS10ZWxlbWV0cnkSwg4KEgoQY3Jld2FpLnRl + bGVtZXRyeRKaDAoQJ2RtlOW3xhPcNjmbKwSJaxIIMUF8zJjQkvQqDENyZXcgQ3JlYXRlZDABOThF + x7PrrgkYQWiczLPrrgkYShoKDmNyZXdhaV92ZXJzaW9uEggKBjAuODAuMEoaCg5weXRob25fdmVy + c2lvbhIICgYzLjEyLjdKLgoIY3Jld19rZXkSIgogMWYxMjhiZGI3YmFhNGI2NzcxNGYxZGFlZGMy + ZjNhYjZKMQoHY3Jld19pZBImCiQzNGJiYzZjYS03MmRiLTQwMzktODQzMy01NTFmOWNmNDM0YTdK + HAoMY3Jld19wcm9jZXNzEgwKCnNlcXVlbnRpYWxKEQoLY3Jld19tZW1vcnkSAhAAShoKFGNyZXdf + bnVtYmVyX29mX3Rhc2tzEgIYAkobChVjcmV3X251bWJlcl9vZl9hZ2VudHMSAhgCSrQFCgtjcmV3 + X2FnZW50cxKkBQqhBVt7ImtleSI6ICI3M2MzNDljOTNjMTYzYjVkNGRmOThhNjRmYWMxYzQzMCIs + ICJpZCI6ICI4MjJkOGM2OC01NzlkLTQ4ZWUtOTBhMi1hNjJiNDgzY2JhNGUiLCAicm9sZSI6ICJ7 + dG9waWN9IFNlbmlvciBEYXRhIFJlc2VhcmNoZXJcbiIsICJ2ZXJib3NlPyI6IHRydWUsICJtYXhf + aXRlciI6IDIwLCAibWF4X3JwbSI6IG51bGwsICJmdW5jdGlvbl9jYWxsaW5nX2xsbSI6ICIiLCAi + bGxtIjogImdwdC00by1taW5pIiwgImRlbGVnYXRpb25fZW5hYmxlZD8iOiBmYWxzZSwgImFsbG93 + X2NvZGVfZXhlY3V0aW9uPyI6IGZhbHNlLCAibWF4X3JldHJ5X2xpbWl0IjogMiwgInRvb2xzX25h + bWVzIjogW119LCB7ImtleSI6ICIxMDRmZTA2NTllMTBiNDI2Y2Y4OGYwMjRmYjU3MTU1MyIsICJp + ZCI6ICI0YTY4NDQwZi0xMjRkLTQ3YmEtYWEzNy1hZTZmMTI2NzlkMmIiLCAicm9sZSI6ICJ7dG9w + aWN9IFJlcG9ydGluZyBBbmFseXN0XG4iLCAidmVyYm9zZT8iOiB0cnVlLCAibWF4X2l0ZXIiOiAy + MCwgIm1heF9ycG0iOiBudWxsLCAiZnVuY3Rpb25fY2FsbGluZ19sbG0iOiAiIiwgImxsbSI6ICJn + cHQtNG8tbWluaSIsICJkZWxlZ2F0aW9uX2VuYWJsZWQ/IjogZmFsc2UsICJhbGxvd19jb2RlX2V4 + ZWN1dGlvbj8iOiBmYWxzZSwgIm1heF9yZXRyeV9saW1pdCI6IDIsICJ0b29sc19uYW1lcyI6IFtd + fV1KkwQKCmNyZXdfdGFza3MShAQKgQRbeyJrZXkiOiAiNmFmYzRiMzk2MjU5ZmJiNzY4MWY1NmM3 + NzU1Y2M5MzciLCAiaWQiOiAiODE2YzI1ZDgtNDg3NC00MmMxLWJmNzEtODc2OTcxZDNmYmExIiwg + ImFzeW5jX2V4ZWN1dGlvbj8iOiBmYWxzZSwgImh1bWFuX2lucHV0PyI6IGZhbHNlLCAiYWdlbnRf + cm9sZSI6ICJ7dG9waWN9IFNlbmlvciBEYXRhIFJlc2VhcmNoZXJcbiIsICJhZ2VudF9rZXkiOiAi + NzNjMzQ5YzkzYzE2M2I1ZDRkZjk4YTY0ZmFjMWM0MzAiLCAidG9vbHNfbmFtZXMiOiBbXX0sIHsi + a2V5IjogImIxN2IxODhkYmYxNGY5M2E5OGU1Yjk1YWFkMzY3NTc3IiwgImlkIjogIjM4YzU1NTI5 + LTc2ODAtNDc5OS1iODdiLTFmMDY2NjE5MGU2NyIsICJhc3luY19leGVjdXRpb24/IjogZmFsc2Us + ICJodW1hbl9pbnB1dD8iOiBmYWxzZSwgImFnZW50X3JvbGUiOiAie3RvcGljfSBSZXBvcnRpbmcg + QW5hbHlzdFxuIiwgImFnZW50X2tleSI6ICIxMDRmZTA2NTllMTBiNDI2Y2Y4OGYwMjRmYjU3MTU1 + MyIsICJ0b29sc19uYW1lcyI6IFtdfV16AhgBhQEAAQAAEo4CChCo3E4xT/U6O20NrD4/Zkt6EggD + /w74tbrrOCoMVGFzayBDcmVhdGVkMAE5SPTas+uuCRhB6IDbs+uuCRhKLgoIY3Jld19rZXkSIgog + MWYxMjhiZGI3YmFhNGI2NzcxNGYxZGFlZGMyZjNhYjZKMQoHY3Jld19pZBImCiQzNGJiYzZjYS03 + MmRiLTQwMzktODQzMy01NTFmOWNmNDM0YTdKLgoIdGFza19rZXkSIgogNmFmYzRiMzk2MjU5ZmJi + NzY4MWY1NmM3NzU1Y2M5MzdKMQoHdGFza19pZBImCiQ4MTZjMjVkOC00ODc0LTQyYzEtYmY3MS04 + NzY5NzFkM2ZiYTF6AhgBhQEAAQAA + headers: + Accept: + - '*/*' + Accept-Encoding: + - gzip, deflate + Connection: + - keep-alive + Content-Length: + - '1902' + Content-Type: + - application/x-protobuf + User-Agent: + - OTel-OTLP-Exporter-Python/1.27.0 + method: POST + uri: https://telemetry.crewai.com:4319/v1/traces + response: + body: + string: "\n\0" + headers: + Content-Length: + - '2' + Content-Type: + - application/x-protobuf + Date: + - Wed, 20 Nov 2024 13:04:24 GMT + status: + code: 200 + message: OK +- request: + body: '{"messages": [{"role": "system", "content": "You are Bicycles Senior Data + Researcher\n. You''re a seasoned researcher with a knack for uncovering the + latest developments in Bicycles. Known for your ability to find the most relevant + information and present it in a clear and concise manner.\n\nYour personal goal + is: Uncover cutting-edge developments in Bicycles\n\nTo give my best complete + final answer to the task use the exact following format:\n\nThought: I now can + give a great answer\nFinal Answer: Your final answer must be the great and the + most complete as possible, it must be outcome described.\n\nI MUST use these + formats, my job depends on it!"}, {"role": "user", "content": "\nCurrent Task: + Conduct a thorough research about Bicycles Make sure you find any interesting + and relevant information given the current year is 2024.\n\n\nThis is the expect + criteria for your final answer: A list with 10 bullet points of the most relevant + information about Bicycles\n\nyou MUST return the actual complete content as + the final answer, not a summary.\n\nBegin! This is VERY important to you, use + the tools available and give your best Final Answer, your job depends on it!\n\nThought:"}], + "model": "gpt-4o-mini", "stop": ["\nObservation:"], "stream": false}' + headers: + accept: + - application/json + accept-encoding: + - gzip, deflate + connection: + - keep-alive + content-length: + - '1260' + content-type: + - application/json + cookie: + - __cf_bm=CkK4UvBd9ukXvn50uJwGambJcz5zERAJfeXJ9xge6H4-1732107842-1.0.1.1-IOK2yVL3RlD75MgmnKzIEyE38HNknwn6I8BBJ1wjGz4jCTd0YWIBPnvUm9gB8D_zLlUA9G7p_wbrfyc4mO_Bmg; + _cfuvid=MmeN9oHWrBLThkEJdaSFHBfWe95JvA8iFnnt7CC92tk-1732107842102-0.0.1.1-604800000 + host: + - api.openai.com + user-agent: + - OpenAI/Python 1.52.1 + x-stainless-arch: + - x64 + x-stainless-async: + - 'false' + x-stainless-lang: + - python + x-stainless-os: + - MacOS + x-stainless-package-version: + - 1.52.1 + x-stainless-raw-response: + - 'true' + x-stainless-retry-count: + - '0' + x-stainless-runtime: + - CPython + x-stainless-runtime-version: + - 3.12.7 + method: POST + uri: https://api.openai.com/v1/chat/completions + response: + content: "{\n \"id\": \"chatcmpl-AVefTnyhy126z54bX4Wq0TjWFUGJI\",\n \"object\": + \"chat.completion\",\n \"created\": 1732107859,\n \"model\": \"gpt-4o-mini-2024-07-18\",\n + \ \"choices\": [\n {\n \"index\": 0,\n \"message\": {\n \"role\": + \"assistant\",\n \"content\": \"I now can give a great answer. \\nFinal + Answer: \\n\\n1. **E-Bike Boom**: Electric bikes (e-bikes) have seen a significant + rise in popularity, with industry reports indicating a projected growth of 60% + in sales compared to previous years. Many cities are paving bike lanes specifically + designed for e-bikes to accommodate this surge.\\n\\n2. **Sustainability in + Manufacturing**: Bicycle manufacturers are increasingly adopting sustainable + practices, such as using recycled materials for frames and parts, and implementing + environmentally friendly production processes. This shift is driven by consumer + demand for greener products.\\n\\n3. **Smart Bicycles**: The integration of + technology in bicycles has progressed with smart bikes featuring built-in GPS, + automated gear shifting, and performance analytics. These innovations enhance + the cycling experience and cater to data-driven enthusiasts.\\n\\n4. **Bike + Sharing Programs**: Urban areas are continuing to expand bike-sharing programs, + with some cities introducing electric bike options and introducing smartphone + apps to streamline the renting process, increasing accessibility and convenience + for riders.\\n\\n5. **Safety Innovations**: Advances in safety technology such + as smart helmets that incorporate lights and indicators, anti-collision systems + using sensor technology, and built-in communication systems to connect with + smartphones are on the rise, aimed at reducing accidents.\\n\\n6. **Adventure + Cycling Trends**: There is a growing popularity in adventure and gravel cycling, + with more cyclists seeking off-road experiences. This has prompted manufacturers + to develop dedicated bikes that cater to rugged terrains, with features such + as wider tires and durable frames.\\n\\n7. **Customization and Personalization**: + The market for customizable bicycles is expanding. Consumers are now able to + choose colors, styles, and features that suit their personal preferences, leading + to a more personalized cycling experience.\\n\\n8. **Communities and Events**: + Cycling communities are thriving globally, with an increase in events such as + group rides, competitive races, and festivals celebrating biking culture. This + fosters social engagement and promotes cycling as a lifestyle.\\n\\n9. **Cargo + Bikes for Urban Living**: The rise of cargo bikes, particularly in urban environments, + allows for efficient transportation of goods, making them an appealing choice + for small businesses and families. This trend is encouraged by city planners + promoting cycling as an alternative to car deliveries.\\n\\n10. **Regulatory + Changes**: Governments around the world are increasingly implementing policies + to support cycling infrastructure, such as funding for bike lanes, subsidies + for bicycle purchases, and stricter emissions standards for motor vehicles, + making cycling a more attractive option for commuting.\\n\\nEach of these points + represents the latest developments in the bicycle industry as we move through + 2024, highlighting advancements in technology, trends in user preferences, and + a broader societal shift towards sustainability and health.\",\n \"refusal\": + null\n },\n \"logprobs\": null,\n \"finish_reason\": \"stop\"\n + \ }\n ],\n \"usage\": {\n \"prompt_tokens\": 237,\n \"completion_tokens\": + 539,\n \"total_tokens\": 776,\n \"prompt_tokens_details\": {\n \"cached_tokens\": + 0,\n \"audio_tokens\": 0\n },\n \"completion_tokens_details\": {\n + \ \"reasoning_tokens\": 0,\n \"audio_tokens\": 0,\n \"accepted_prediction_tokens\": + 0,\n \"rejected_prediction_tokens\": 0\n }\n },\n \"system_fingerprint\": + \"fp_0705bf87c0\"\n}\n" + headers: + CF-Cache-Status: + - DYNAMIC + CF-RAY: + - 8e58a5276a096225-GRU + Connection: + - keep-alive + Content-Encoding: + - gzip + Content-Type: + - application/json + Date: + - Wed, 20 Nov 2024 13:04:26 GMT + Server: + - cloudflare + Transfer-Encoding: + - chunked + X-Content-Type-Options: + - nosniff + access-control-expose-headers: + - X-Request-ID + alt-svc: + - h3=":443"; ma=86400 + openai-organization: + - crewai-iuxna1 + openai-processing-ms: + - '7355' + openai-version: + - '2020-10-01' + strict-transport-security: + - max-age=31536000; includeSubDomains; preload + x-ratelimit-limit-requests: + - '30000' + x-ratelimit-limit-tokens: + - '150000000' + x-ratelimit-remaining-requests: + - '29999' + x-ratelimit-remaining-tokens: + - '149999708' + x-ratelimit-reset-requests: + - 2ms + x-ratelimit-reset-tokens: + - 0s + x-request-id: + - req_5536f2a242886d3949f0cdc1628b2996 + http_version: HTTP/1.1 + status_code: 200 +- request: + body: !!binary | + Cs4CCiQKIgoMc2VydmljZS5uYW1lEhIKEGNyZXdBSS10ZWxlbWV0cnkSpQIKEgoQY3Jld2FpLnRl + bGVtZXRyeRKOAgoQpBIRwGH/fJtGJT1cIWsC5BIIM3YyJZEYUUgqDFRhc2sgQ3JlYXRlZDABOYgb + lILtrgkYQZBnlYLtrgkYSi4KCGNyZXdfa2V5EiIKIDFmMTI4YmRiN2JhYTRiNjc3MTRmMWRhZWRj + MmYzYWI2SjEKB2NyZXdfaWQSJgokMzRiYmM2Y2EtNzJkYi00MDM5LTg0MzMtNTUxZjljZjQzNGE3 + Si4KCHRhc2tfa2V5EiIKIGIxN2IxODhkYmYxNGY5M2E5OGU1Yjk1YWFkMzY3NTc3SjEKB3Rhc2tf + aWQSJgokMzhjNTU1MjktNzY4MC00Nzk5LWI4N2ItMWYwNjY2MTkwZTY3egIYAYUBAAEAAA== + headers: + Accept: + - '*/*' + Accept-Encoding: + - gzip, deflate + Connection: + - keep-alive + Content-Length: + - '337' + Content-Type: + - application/x-protobuf + User-Agent: + - OTel-OTLP-Exporter-Python/1.27.0 + method: POST + uri: https://telemetry.crewai.com:4319/v1/traces + response: + body: + string: "\n\0" + headers: + Content-Length: + - '2' + Content-Type: + - application/x-protobuf + Date: + - Wed, 20 Nov 2024 13:04:29 GMT + status: + code: 200 + message: OK +- request: + body: '{"messages": [{"role": "system", "content": "You are Bicycles Reporting + Analyst\n. You''re a meticulous analyst with a keen eye for detail. You''re + known for your ability to turn complex data into clear and concise reports, + making it easy for others to understand and act on the information you provide.\n\nYour + personal goal is: Create detailed reports based on Bicycles data analysis and + research findings\n\nTo give my best complete final answer to the task use the + exact following format:\n\nThought: I now can give a great answer\nFinal Answer: + Your final answer must be the great and the most complete as possible, it must + be outcome described.\n\nI MUST use these formats, my job depends on it!"}, + {"role": "user", "content": "\nCurrent Task: Review the context you got and + expand each topic into a full section for a report. Make sure the report is + detailed and contains any and all relevant information.\n\n\nThis is the expect + criteria for your final answer: A fully fledge reports with the mains topics, + each with a full section of information. Formatted as markdown without ''```''\n\nyou + MUST return the actual complete content as the final answer, not a summary.\n\nThis + is the context you''re working with:\n1. **E-Bike Boom**: Electric bikes (e-bikes) + have seen a significant rise in popularity, with industry reports indicating + a projected growth of 60% in sales compared to previous years. Many cities are + paving bike lanes specifically designed for e-bikes to accommodate this surge.\n\n2. + **Sustainability in Manufacturing**: Bicycle manufacturers are increasingly + adopting sustainable practices, such as using recycled materials for frames + and parts, and implementing environmentally friendly production processes. This + shift is driven by consumer demand for greener products.\n\n3. **Smart Bicycles**: + The integration of technology in bicycles has progressed with smart bikes featuring + built-in GPS, automated gear shifting, and performance analytics. These innovations + enhance the cycling experience and cater to data-driven enthusiasts.\n\n4. **Bike + Sharing Programs**: Urban areas are continuing to expand bike-sharing programs, + with some cities introducing electric bike options and introducing smartphone + apps to streamline the renting process, increasing accessibility and convenience + for riders.\n\n5. **Safety Innovations**: Advances in safety technology such + as smart helmets that incorporate lights and indicators, anti-collision systems + using sensor technology, and built-in communication systems to connect with + smartphones are on the rise, aimed at reducing accidents.\n\n6. **Adventure + Cycling Trends**: There is a growing popularity in adventure and gravel cycling, + with more cyclists seeking off-road experiences. This has prompted manufacturers + to develop dedicated bikes that cater to rugged terrains, with features such + as wider tires and durable frames.\n\n7. **Customization and Personalization**: + The market for customizable bicycles is expanding. Consumers are now able to + choose colors, styles, and features that suit their personal preferences, leading + to a more personalized cycling experience.\n\n8. **Communities and Events**: + Cycling communities are thriving globally, with an increase in events such as + group rides, competitive races, and festivals celebrating biking culture. This + fosters social engagement and promotes cycling as a lifestyle.\n\n9. **Cargo + Bikes for Urban Living**: The rise of cargo bikes, particularly in urban environments, + allows for efficient transportation of goods, making them an appealing choice + for small businesses and families. This trend is encouraged by city planners + promoting cycling as an alternative to car deliveries.\n\n10. **Regulatory Changes**: + Governments around the world are increasingly implementing policies to support + cycling infrastructure, such as funding for bike lanes, subsidies for bicycle + purchases, and stricter emissions standards for motor vehicles, making cycling + a more attractive option for commuting.\n\nEach of these points represents the + latest developments in the bicycle industry as we move through 2024, highlighting + advancements in technology, trends in user preferences, and a broader societal + shift towards sustainability and health.\n\nBegin! This is VERY important to + you, use the tools available and give your best Final Answer, your job depends + on it!\n\nThought:"}], "model": "gpt-4o-mini", "stop": ["\nObservation:"], "stream": + false}' + headers: + accept: + - application/json + accept-encoding: + - gzip, deflate + connection: + - keep-alive + content-length: + - '4457' + content-type: + - application/json + cookie: + - __cf_bm=CkK4UvBd9ukXvn50uJwGambJcz5zERAJfeXJ9xge6H4-1732107842-1.0.1.1-IOK2yVL3RlD75MgmnKzIEyE38HNknwn6I8BBJ1wjGz4jCTd0YWIBPnvUm9gB8D_zLlUA9G7p_wbrfyc4mO_Bmg; + _cfuvid=MmeN9oHWrBLThkEJdaSFHBfWe95JvA8iFnnt7CC92tk-1732107842102-0.0.1.1-604800000 + host: + - api.openai.com + user-agent: + - OpenAI/Python 1.52.1 + x-stainless-arch: + - x64 + x-stainless-async: + - 'false' + x-stainless-lang: + - python + x-stainless-os: + - MacOS + x-stainless-package-version: + - 1.52.1 + x-stainless-raw-response: + - 'true' + x-stainless-retry-count: + - '0' + x-stainless-runtime: + - CPython + x-stainless-runtime-version: + - 3.12.7 + method: POST + uri: https://api.openai.com/v1/chat/completions + response: + content: "{\n \"id\": \"chatcmpl-AVefbpMLcvelEguI3pyXOpfbaXLGG\",\n \"object\": + \"chat.completion\",\n \"created\": 1732107867,\n \"model\": \"gpt-4o-mini-2024-07-18\",\n + \ \"choices\": [\n {\n \"index\": 0,\n \"message\": {\n \"role\": + \"assistant\",\n \"content\": \"I now can give a great answer \\nFinal + Answer: \\n\\n# Comprehensive Report on the Latest Developments in the Bicycle + Industry (2024)\\n\\n## 1. E-Bike Boom\\nThe popularity of electric bikes (e-bikes) + has surged dramatically in recent years, with industry reports indicating a + projected growth of 60% in sales compared to previous years. This growth can + be attributed to increasing urbanization, the rising need for more sustainable + modes of transport, and technological advancements that have made e-bikes more + accessible and desirable. Cities worldwide are responding to this boom by developing + dedicated bike lanes specifically designed for e-bikes, which not only promotes + safety but also encourages more individuals to consider cycling as a primary + mode of transportation.\\n\\n## 2. Sustainability in Manufacturing\\nIn line + with global trends towards sustainability, bicycle manufacturers are increasingly + adopting eco-friendlier practices. They are utilizing recycled materials for + frames and components and implementing environmentally friendly production processes. + This shift is not just a response to regulatory pressures but also driven by + consumer demand for greener products. Companies that prioritize sustainability + are seeing a competitive edge in an increasingly eco-conscious market, as consumers + are more likely to align their purchases with their values regarding environmental + responsibility.\\n\\n## 3. Smart Bicycles\\nThe integration of technology in + bicycles has advanced significantly, resulting in the emergence of smart bikes. + These bicycles often feature built-in GPS for navigation, automated gear shifting + for smoother rides, and performance analytics that allow users to track their + cycling metrics. Such innovations enhance the overall cycling experience and + cater to performance-focused cyclists who seek data to optimize their rides. + By merging cycling with technology, manufacturers are not only attracting tech + enthusiasts but also making cycling more mainstream.\\n\\n## 4. Bike Sharing + Programs\\nBike-sharing programs are rapidly expanding, particularly in urban + areas. Many cities have started introducing electric bike options within these + programs to meet the growing demand. The introduction of smartphone apps has + streamlined the renting process, increasing accessibility and convenience for + users. This trend not only promotes a healthier lifestyle but also reduces traffic + congestion and environmental impact in densely populated areas, making cycling + a more viable option for commuting.\\n\\n## 5. Safety Innovations\\nRecent advancements + in safety technology are working towards making cycling safer. Innovations such + as smart helmets equipped with lights and turn indicators, anti-collision systems + utilizing sensor technology, and integrated communication systems linking bicycles + with smartphones are increasingly gaining traction. These developments aim to + minimize accidents and enhance the overall sense of security for cyclists, thereby + encouraging more people to take up cycling as a daily activity.\\n\\n## 6. Adventure + Cycling Trends\\nAdventure and gravel cycling are witnessing a renaissance, + with many cyclists seeking off-road experiences that enable a connection with + nature. This trend has led manufacturers to innovate by developing dedicated + bikes suited for rugged terrains, characterized by features like wider tires + and durable frames. As consumers become more adventurous in their hobbies, manufacturers + are recognizing the need to cater to this niche market, fostering the growth + of adventure cycling as a distinct segment in the industry.\\n\\n## 7. Customization + and Personalization\\nThe demand for customizable bicycles is on the rise, allowing + consumers to choose various aspects of their bikes, including colors, styles, + and features. This trend towards personalization is enhancing the cycling experience, + as riders can tailor their bicycles to their preferences. The flourishing market + for custom bikes reflects a broader societal shift towards individuality and + self-expression, as consumers are no longer content with one-size-fits-all solutions.\\n\\n## + 8. Communities and Events\\nCycling communities are thriving worldwide, reflected + in an increase in events such as group rides, competitive races, and festivals + celebrating biking culture. These gatherings not only foster a sense of camaraderie + among cyclists but also promote cycling as a lifestyle choice to the wider community. + The growth of these events is instrumental in building a culture around cycling, + driving advocacy for cycling infrastructure and safety, and ultimately increasing + the number of people who cycle.\\n\\n## 9. Cargo Bikes for Urban Living\\nThe + rise of cargo bikes, especially in urban settings, represents an innovative + solution for transporting goods efficiently while reducing reliance on motor + vehicles. Such bikes serve as an appealing alternative for small businesses + and families alike, allowing for easy deliveries and shopping. City planners + are increasingly promoting cargo bikes within urban transport strategies, recognizing + them as a sustainable option that aligns with broader goals for reducing carbon + footprints and enhancing urban mobility.\\n\\n## 10. Regulatory Changes\\nGovernments + around the globe are progressively enacting regulations to support and grow + cycling infrastructure. Initiatives include funding for bike lanes, subsidies + for bicycle purchases, and stricter emissions standards for cars. These regulatory + changes are making cycling a more attractive option for commuting and are an + acknowledgment of the role that cycling plays in reducing pollution and traffic + congestion. Such policies are instrumental in fostering a cycling-friendly environment + and encouraging more people to adopt biking as a daily mode of transportation.\\n\\nThis + report highlights the most significant developments in the bicycle industry + as we advance through 2024, showcasing the technological breakthroughs, shifts + in user preferences, and an overarching movement toward sustainability and health. + These trends are indicative of a vibrant cycling culture that continues to evolve + to meet the needs of modern society.\",\n \"refusal\": null\n },\n + \ \"logprobs\": null,\n \"finish_reason\": \"stop\"\n }\n ],\n + \ \"usage\": {\n \"prompt_tokens\": 790,\n \"completion_tokens\": 1022,\n + \ \"total_tokens\": 1812,\n \"prompt_tokens_details\": {\n \"cached_tokens\": + 0,\n \"audio_tokens\": 0\n },\n \"completion_tokens_details\": {\n + \ \"reasoning_tokens\": 0,\n \"audio_tokens\": 0,\n \"accepted_prediction_tokens\": + 0,\n \"rejected_prediction_tokens\": 0\n }\n },\n \"system_fingerprint\": + \"fp_0705bf87c0\"\n}\n" + headers: + CF-Cache-Status: + - DYNAMIC + CF-RAY: + - 8e58a5580add6225-GRU + Connection: + - keep-alive + Content-Encoding: + - gzip + Content-Type: + - application/json + Date: + - Wed, 20 Nov 2024 13:04:46 GMT + Server: + - cloudflare + Transfer-Encoding: + - chunked + X-Content-Type-Options: + - nosniff + access-control-expose-headers: + - X-Request-ID + alt-svc: + - h3=":443"; ma=86400 + openai-organization: + - crewai-iuxna1 + openai-processing-ms: + - '18921' + openai-version: + - '2020-10-01' + strict-transport-security: + - max-age=31536000; includeSubDomains; preload + x-ratelimit-limit-requests: + - '30000' + x-ratelimit-limit-tokens: + - '150000000' + x-ratelimit-remaining-requests: + - '29999' + x-ratelimit-remaining-tokens: + - '149998916' + x-ratelimit-reset-requests: + - 2ms + x-ratelimit-reset-tokens: + - 0s + x-request-id: + - req_32b801874a2fed46b91251052364ec47 + http_version: HTTP/1.1 + status_code: 200 +version: 1 diff --git a/tests/cassettes/test_before_kickoff_modification.yaml b/tests/cassettes/test_before_kickoff_modification.yaml new file mode 100644 index 000000000..f1f60ea55 --- /dev/null +++ b/tests/cassettes/test_before_kickoff_modification.yaml @@ -0,0 +1,500 @@ +interactions: +- request: + body: !!binary | + CusOCiQKIgoMc2VydmljZS5uYW1lEhIKEGNyZXdBSS10ZWxlbWV0cnkSwg4KEgoQY3Jld2FpLnRl + bGVtZXRyeRKaDAoQFHOMv8VK3fCTALziX07PIRIIN6Cmi+pyjGkqDENyZXcgQ3JlYXRlZDABORgw + kr/lrgkYQWDinL/lrgkYShoKDmNyZXdhaV92ZXJzaW9uEggKBjAuODAuMEoaCg5weXRob25fdmVy + c2lvbhIICgYzLjEyLjdKLgoIY3Jld19rZXkSIgogMWYxMjhiZGI3YmFhNGI2NzcxNGYxZGFlZGMy + ZjNhYjZKMQoHY3Jld19pZBImCiQ5MWYxYTY2OC05Y2MwLTQxODctYWZmOS03NzJkNzZlMzg3NDlK + HAoMY3Jld19wcm9jZXNzEgwKCnNlcXVlbnRpYWxKEQoLY3Jld19tZW1vcnkSAhAAShoKFGNyZXdf + bnVtYmVyX29mX3Rhc2tzEgIYAkobChVjcmV3X251bWJlcl9vZl9hZ2VudHMSAhgCSrQFCgtjcmV3 + X2FnZW50cxKkBQqhBVt7ImtleSI6ICI3M2MzNDljOTNjMTYzYjVkNGRmOThhNjRmYWMxYzQzMCIs + ICJpZCI6ICIxNDFhOGY2NS0zODRjLTQxMDMtODgwZS02ODMzNTQ0NmVkN2YiLCAicm9sZSI6ICJ7 + dG9waWN9IFNlbmlvciBEYXRhIFJlc2VhcmNoZXJcbiIsICJ2ZXJib3NlPyI6IHRydWUsICJtYXhf + aXRlciI6IDIwLCAibWF4X3JwbSI6IG51bGwsICJmdW5jdGlvbl9jYWxsaW5nX2xsbSI6ICIiLCAi + bGxtIjogImdwdC00by1taW5pIiwgImRlbGVnYXRpb25fZW5hYmxlZD8iOiBmYWxzZSwgImFsbG93 + X2NvZGVfZXhlY3V0aW9uPyI6IGZhbHNlLCAibWF4X3JldHJ5X2xpbWl0IjogMiwgInRvb2xzX25h + bWVzIjogW119LCB7ImtleSI6ICIxMDRmZTA2NTllMTBiNDI2Y2Y4OGYwMjRmYjU3MTU1MyIsICJp + ZCI6ICI5YWFkMWUxMi00MTgxLTQ5NTctYmNlNS01ZWNhODg2YjMxYWYiLCAicm9sZSI6ICJ7dG9w + aWN9IFJlcG9ydGluZyBBbmFseXN0XG4iLCAidmVyYm9zZT8iOiB0cnVlLCAibWF4X2l0ZXIiOiAy + MCwgIm1heF9ycG0iOiBudWxsLCAiZnVuY3Rpb25fY2FsbGluZ19sbG0iOiAiIiwgImxsbSI6ICJn + cHQtNG8tbWluaSIsICJkZWxlZ2F0aW9uX2VuYWJsZWQ/IjogZmFsc2UsICJhbGxvd19jb2RlX2V4 + ZWN1dGlvbj8iOiBmYWxzZSwgIm1heF9yZXRyeV9saW1pdCI6IDIsICJ0b29sc19uYW1lcyI6IFtd + fV1KkwQKCmNyZXdfdGFza3MShAQKgQRbeyJrZXkiOiAiNmFmYzRiMzk2MjU5ZmJiNzY4MWY1NmM3 + NzU1Y2M5MzciLCAiaWQiOiAiNTI5YmU1NTMtM2Y3Mi00YTU2LWFhNWItYWE0ZTZmMzhlOWJhIiwg + ImFzeW5jX2V4ZWN1dGlvbj8iOiBmYWxzZSwgImh1bWFuX2lucHV0PyI6IGZhbHNlLCAiYWdlbnRf + cm9sZSI6ICJ7dG9waWN9IFNlbmlvciBEYXRhIFJlc2VhcmNoZXJcbiIsICJhZ2VudF9rZXkiOiAi + NzNjMzQ5YzkzYzE2M2I1ZDRkZjk4YTY0ZmFjMWM0MzAiLCAidG9vbHNfbmFtZXMiOiBbXX0sIHsi + a2V5IjogImIxN2IxODhkYmYxNGY5M2E5OGU1Yjk1YWFkMzY3NTc3IiwgImlkIjogImI2NzQyNmI0 + LTM2NTAtNDY5MS1iYTU4LWYwZTRmOWM0NTk3YyIsICJhc3luY19leGVjdXRpb24/IjogZmFsc2Us + ICJodW1hbl9pbnB1dD8iOiBmYWxzZSwgImFnZW50X3JvbGUiOiAie3RvcGljfSBSZXBvcnRpbmcg + QW5hbHlzdFxuIiwgImFnZW50X2tleSI6ICIxMDRmZTA2NTllMTBiNDI2Y2Y4OGYwMjRmYjU3MTU1 + MyIsICJ0b29sc19uYW1lcyI6IFtdfV16AhgBhQEAAQAAEo4CChBM7T06NWnnx9b1Sl8dbVH+Eghz + 9rR/8DUNEioMVGFzayBDcmVhdGVkMAE5yJqzv+WuCRhBqEa0v+WuCRhKLgoIY3Jld19rZXkSIgog + MWYxMjhiZGI3YmFhNGI2NzcxNGYxZGFlZGMyZjNhYjZKMQoHY3Jld19pZBImCiQ5MWYxYTY2OC05 + Y2MwLTQxODctYWZmOS03NzJkNzZlMzg3NDlKLgoIdGFza19rZXkSIgogNmFmYzRiMzk2MjU5ZmJi + NzY4MWY1NmM3NzU1Y2M5MzdKMQoHdGFza19pZBImCiQ1MjliZTU1My0zZjcyLTRhNTYtYWE1Yi1h + YTRlNmYzOGU5YmF6AhgBhQEAAQAA + headers: + Accept: + - '*/*' + Accept-Encoding: + - gzip, deflate + Connection: + - keep-alive + Content-Length: + - '1902' + Content-Type: + - application/x-protobuf + User-Agent: + - OTel-OTLP-Exporter-Python/1.27.0 + method: POST + uri: https://telemetry.crewai.com:4319/v1/traces + response: + body: + string: "\n\0" + headers: + Content-Length: + - '2' + Content-Type: + - application/x-protobuf + Date: + - Wed, 20 Nov 2024 13:03:59 GMT + status: + code: 200 + message: OK +- request: + body: '{"messages": [{"role": "system", "content": "You are Bicycles Senior Data + Researcher\n. You''re a seasoned researcher with a knack for uncovering the + latest developments in Bicycles. Known for your ability to find the most relevant + information and present it in a clear and concise manner.\n\nYour personal goal + is: Uncover cutting-edge developments in Bicycles\n\nTo give my best complete + final answer to the task use the exact following format:\n\nThought: I now can + give a great answer\nFinal Answer: Your final answer must be the great and the + most complete as possible, it must be outcome described.\n\nI MUST use these + formats, my job depends on it!"}, {"role": "user", "content": "\nCurrent Task: + Conduct a thorough research about Bicycles Make sure you find any interesting + and relevant information given the current year is 2024.\n\n\nThis is the expect + criteria for your final answer: A list with 10 bullet points of the most relevant + information about Bicycles\n\nyou MUST return the actual complete content as + the final answer, not a summary.\n\nBegin! This is VERY important to you, use + the tools available and give your best Final Answer, your job depends on it!\n\nThought:"}], + "model": "gpt-4o-mini", "stop": ["\nObservation:"], "stream": false}' + headers: + accept: + - application/json + accept-encoding: + - gzip, deflate + connection: + - keep-alive + content-length: + - '1260' + content-type: + - application/json + host: + - api.openai.com + user-agent: + - OpenAI/Python 1.52.1 + x-stainless-arch: + - x64 + x-stainless-async: + - 'false' + x-stainless-lang: + - python + x-stainless-os: + - MacOS + x-stainless-package-version: + - 1.52.1 + x-stainless-raw-response: + - 'true' + x-stainless-retry-count: + - '0' + x-stainless-runtime: + - CPython + x-stainless-runtime-version: + - 3.12.7 + method: POST + uri: https://api.openai.com/v1/chat/completions + response: + content: "{\n \"id\": \"chatcmpl-AVef48hbtmEEfHJzc9KI6SOG72L6j\",\n \"object\": + \"chat.completion\",\n \"created\": 1732107834,\n \"model\": \"gpt-4o-mini-2024-07-18\",\n + \ \"choices\": [\n {\n \"index\": 0,\n \"message\": {\n \"role\": + \"assistant\",\n \"content\": \"I now can give a great answer. \\nFinal + Answer: \\n\\n1. **E-Bike Market Growth**: The e-bike market has experienced + unprecedented growth, with sales increasing by over 45% in 2023 compared to + the previous year, driven by rising fuel prices and increased urbanization. + For 2024, predictions suggest this trend will continue as more consumers seek + sustainable transportation options.\\n\\n2. **Smart Technology Integration**: + Bicycle manufacturers are increasingly integrating smart technology into their + models. Features like GPS navigation, smartphone connectivity, anti-theft alarms, + and fitness tracking are becoming standard, enhancing the cycling experience + while providing riders with valuable data.\\n\\n3. **Sustainable Materials**: + Many companies are now focusing on using sustainable and eco-friendly materials + for bicycle production, with significant advancements in recycled aluminum and + carbon fiber technologies. This approach not only reduces environmental impact + but also appeals to eco-conscious consumers.\\n\\n4. **Urban Infrastructure + Improvements**: Cities worldwide are investing heavily in improving cycling + infrastructure, including the addition of dedicated bike lanes, bike-sharing + programs, and parking facilities, aiming to promote cycling as a primary mode + of transport and improve safety for cyclists.\\n\\n5. **Global Cycling Tourism + Increase**: Cycling tourism has seen a surge in popularity, with destinations + specifically catering to cyclists emerging across Europe, North America, and + Asia. This trend encourages eco-friendly travel options and boosts local economies, + offering curated cycling paths and accommodations.\\n\\n6. **Bike Repair & Maintenance + Innovations**: Innovative solutions like mobile bike repair services and self-service + bike repair stations are becoming more common, addressing the maintenance needs + of cyclists and reducing barriers to cycling.\\n\\n7. **Safety Innovations**: + The development of safety features such as automatic lights that respond to + ambient light, integrated turn signals in helmets, and advanced brake systems + have become essential selling points for new bikes, increasing rider visibility + and safety.\\n\\n8. **Performance Enhancements**: Advances in bike design and + materials, such as lightweight titanium and carbon fiber frames, have enhanced + performance for competitive cyclists. Additionally, innovations in gear shifting + and suspension systems are improving efficiency and comfort.\\n\\n9. **Inclusivity + in Cycling**: An increasing number of brands are focusing on inclusivity, producing + step-through frames and bikes tailored for various body types and abilities, + thus promoting cycling for people of all ages and physical conditions.\\n\\n10. + **Data Analytics for Cycling Trends**: The use of data analytics to study cycling + patterns has increased, helping cities and businesses understand cycling behaviors + and improve services. Insights gathered are being used to optimize bike-sharing + programs and enhance cycling infrastructure strategically.\\n\\nThis comprehensive + understanding highlights the diverse and exciting developments in the bicycle + industry, reflective of the shifting trends and technological advancements as + we move through 2024.\",\n \"refusal\": null\n },\n \"logprobs\": + null,\n \"finish_reason\": \"stop\"\n }\n ],\n \"usage\": {\n \"prompt_tokens\": + 237,\n \"completion_tokens\": 540,\n \"total_tokens\": 777,\n \"prompt_tokens_details\": + {\n \"cached_tokens\": 0,\n \"audio_tokens\": 0\n },\n \"completion_tokens_details\": + {\n \"reasoning_tokens\": 0,\n \"audio_tokens\": 0,\n \"accepted_prediction_tokens\": + 0,\n \"rejected_prediction_tokens\": 0\n }\n },\n \"system_fingerprint\": + \"fp_0705bf87c0\"\n}\n" + headers: + CF-Cache-Status: + - DYNAMIC + CF-RAY: + - 8e58a48a783d6225-GRU + Connection: + - keep-alive + Content-Encoding: + - gzip + Content-Type: + - application/json + Date: + - Wed, 20 Nov 2024 13:04:02 GMT + Server: + - cloudflare + Set-Cookie: + - __cf_bm=CkK4UvBd9ukXvn50uJwGambJcz5zERAJfeXJ9xge6H4-1732107842-1.0.1.1-IOK2yVL3RlD75MgmnKzIEyE38HNknwn6I8BBJ1wjGz4jCTd0YWIBPnvUm9gB8D_zLlUA9G7p_wbrfyc4mO_Bmg; + path=/; expires=Wed, 20-Nov-24 13:34:02 GMT; domain=.api.openai.com; HttpOnly; + Secure; SameSite=None + - _cfuvid=MmeN9oHWrBLThkEJdaSFHBfWe95JvA8iFnnt7CC92tk-1732107842102-0.0.1.1-604800000; + path=/; domain=.api.openai.com; HttpOnly; Secure; SameSite=None + Transfer-Encoding: + - chunked + X-Content-Type-Options: + - nosniff + access-control-expose-headers: + - X-Request-ID + alt-svc: + - h3=":443"; ma=86400 + openai-organization: + - crewai-iuxna1 + openai-processing-ms: + - '7649' + openai-version: + - '2020-10-01' + strict-transport-security: + - max-age=31536000; includeSubDomains; preload + x-ratelimit-limit-requests: + - '30000' + x-ratelimit-limit-tokens: + - '150000000' + x-ratelimit-remaining-requests: + - '29999' + x-ratelimit-remaining-tokens: + - '149999708' + x-ratelimit-reset-requests: + - 2ms + x-ratelimit-reset-tokens: + - 0s + x-request-id: + - req_60a333db2dbe3378c077ae0b2af16f8e + http_version: HTTP/1.1 + status_code: 200 +- request: + body: !!binary | + Cs4CCiQKIgoMc2VydmljZS5uYW1lEhIKEGNyZXdBSS10ZWxlbWV0cnkSpQIKEgoQY3Jld2FpLnRl + bGVtZXRyeRKOAgoQU4pBe1pQxsUBVChkPK41ghII8dnGjmMshHkqDFRhc2sgQ3JlYXRlZDABOQiW + uMjnrgkYQYjOucjnrgkYSi4KCGNyZXdfa2V5EiIKIDFmMTI4YmRiN2JhYTRiNjc3MTRmMWRhZWRj + MmYzYWI2SjEKB2NyZXdfaWQSJgokOTFmMWE2NjgtOWNjMC00MTg3LWFmZjktNzcyZDc2ZTM4NzQ5 + Si4KCHRhc2tfa2V5EiIKIGIxN2IxODhkYmYxNGY5M2E5OGU1Yjk1YWFkMzY3NTc3SjEKB3Rhc2tf + aWQSJgokYjY3NDI2YjQtMzY1MC00NjkxLWJhNTgtZjBlNGY5YzQ1OTdjegIYAYUBAAEAAA== + headers: + Accept: + - '*/*' + Accept-Encoding: + - gzip, deflate + Connection: + - keep-alive + Content-Length: + - '337' + Content-Type: + - application/x-protobuf + User-Agent: + - OTel-OTLP-Exporter-Python/1.27.0 + method: POST + uri: https://telemetry.crewai.com:4319/v1/traces + response: + body: + string: "\n\0" + headers: + Content-Length: + - '2' + Content-Type: + - application/x-protobuf + Date: + - Wed, 20 Nov 2024 13:04:04 GMT + status: + code: 200 + message: OK +- request: + body: '{"messages": [{"role": "system", "content": "You are Bicycles Reporting + Analyst\n. You''re a meticulous analyst with a keen eye for detail. You''re + known for your ability to turn complex data into clear and concise reports, + making it easy for others to understand and act on the information you provide.\n\nYour + personal goal is: Create detailed reports based on Bicycles data analysis and + research findings\n\nTo give my best complete final answer to the task use the + exact following format:\n\nThought: I now can give a great answer\nFinal Answer: + Your final answer must be the great and the most complete as possible, it must + be outcome described.\n\nI MUST use these formats, my job depends on it!"}, + {"role": "user", "content": "\nCurrent Task: Review the context you got and + expand each topic into a full section for a report. Make sure the report is + detailed and contains any and all relevant information.\n\n\nThis is the expect + criteria for your final answer: A fully fledge reports with the mains topics, + each with a full section of information. Formatted as markdown without ''```''\n\nyou + MUST return the actual complete content as the final answer, not a summary.\n\nThis + is the context you''re working with:\n1. **E-Bike Market Growth**: The e-bike + market has experienced unprecedented growth, with sales increasing by over 45% + in 2023 compared to the previous year, driven by rising fuel prices and increased + urbanization. For 2024, predictions suggest this trend will continue as more + consumers seek sustainable transportation options.\n\n2. **Smart Technology + Integration**: Bicycle manufacturers are increasingly integrating smart technology + into their models. Features like GPS navigation, smartphone connectivity, anti-theft + alarms, and fitness tracking are becoming standard, enhancing the cycling experience + while providing riders with valuable data.\n\n3. **Sustainable Materials**: + Many companies are now focusing on using sustainable and eco-friendly materials + for bicycle production, with significant advancements in recycled aluminum and + carbon fiber technologies. This approach not only reduces environmental impact + but also appeals to eco-conscious consumers.\n\n4. **Urban Infrastructure Improvements**: + Cities worldwide are investing heavily in improving cycling infrastructure, + including the addition of dedicated bike lanes, bike-sharing programs, and parking + facilities, aiming to promote cycling as a primary mode of transport and improve + safety for cyclists.\n\n5. **Global Cycling Tourism Increase**: Cycling tourism + has seen a surge in popularity, with destinations specifically catering to cyclists + emerging across Europe, North America, and Asia. This trend encourages eco-friendly + travel options and boosts local economies, offering curated cycling paths and + accommodations.\n\n6. **Bike Repair & Maintenance Innovations**: Innovative + solutions like mobile bike repair services and self-service bike repair stations + are becoming more common, addressing the maintenance needs of cyclists and reducing + barriers to cycling.\n\n7. **Safety Innovations**: The development of safety + features such as automatic lights that respond to ambient light, integrated + turn signals in helmets, and advanced brake systems have become essential selling + points for new bikes, increasing rider visibility and safety.\n\n8. **Performance + Enhancements**: Advances in bike design and materials, such as lightweight titanium + and carbon fiber frames, have enhanced performance for competitive cyclists. + Additionally, innovations in gear shifting and suspension systems are improving + efficiency and comfort.\n\n9. **Inclusivity in Cycling**: An increasing number + of brands are focusing on inclusivity, producing step-through frames and bikes + tailored for various body types and abilities, thus promoting cycling for people + of all ages and physical conditions.\n\n10. **Data Analytics for Cycling Trends**: + The use of data analytics to study cycling patterns has increased, helping cities + and businesses understand cycling behaviors and improve services. Insights gathered + are being used to optimize bike-sharing programs and enhance cycling infrastructure + strategically.\n\nThis comprehensive understanding highlights the diverse and + exciting developments in the bicycle industry, reflective of the shifting trends + and technological advancements as we move through 2024.\n\nBegin! This is VERY + important to you, use the tools available and give your best Final Answer, your + job depends on it!\n\nThought:"}], "model": "gpt-4o-mini", "stop": ["\nObservation:"], + "stream": false}' + headers: + accept: + - application/json + accept-encoding: + - gzip, deflate + connection: + - keep-alive + content-length: + - '4587' + content-type: + - application/json + cookie: + - __cf_bm=CkK4UvBd9ukXvn50uJwGambJcz5zERAJfeXJ9xge6H4-1732107842-1.0.1.1-IOK2yVL3RlD75MgmnKzIEyE38HNknwn6I8BBJ1wjGz4jCTd0YWIBPnvUm9gB8D_zLlUA9G7p_wbrfyc4mO_Bmg; + _cfuvid=MmeN9oHWrBLThkEJdaSFHBfWe95JvA8iFnnt7CC92tk-1732107842102-0.0.1.1-604800000 + host: + - api.openai.com + user-agent: + - OpenAI/Python 1.52.1 + x-stainless-arch: + - x64 + x-stainless-async: + - 'false' + x-stainless-lang: + - python + x-stainless-os: + - MacOS + x-stainless-package-version: + - 1.52.1 + x-stainless-raw-response: + - 'true' + x-stainless-retry-count: + - '0' + x-stainless-runtime: + - CPython + x-stainless-runtime-version: + - 3.12.7 + method: POST + uri: https://api.openai.com/v1/chat/completions + response: + content: "{\n \"id\": \"chatcmpl-AVefC4hfHvHYaSnPpfpnDBIn5IOgg\",\n \"object\": + \"chat.completion\",\n \"created\": 1732107842,\n \"model\": \"gpt-4o-mini-2024-07-18\",\n + \ \"choices\": [\n {\n \"index\": 0,\n \"message\": {\n \"role\": + \"assistant\",\n \"content\": \"I now can give a great answer \\nFinal + Answer: \\n\\n# Detailed Report on Current Bicycle Industry Trends\\n\\n## 1. + E-Bike Market Growth\\nThe e-bike market has experienced unprecedented growth + in 2023, with sales skyrocketing by over 45% compared to the previous year. + The surge in popularity is largely attributed to rising fuel prices that compel + consumers to explore alternative modes of transportation, as well as increased + urbanization that pushes individuals towards more sustainable commuting options. + In 2024, forecasts indicate that this robust growth is likely to persist as + more consumers prioritize eco-friendly transportation solutions. Factors driving + this momentum include government incentives for electric vehicle purchases, + improved battery technology providing longer ranges, and the appeal of e-bikes + as a viable solution for first-and-last-mile connectivity in urban environments.\\n\\n## + 2. Smart Technology Integration\\nThe bicycle manufacturing industry is witnessing + an increasing trend towards integrating smart technology into their products. + Modern bicycles now come equipped with features such as GPS navigation systems, + Bluetooth connectivity, anti-theft alarms, and fitness tracking capabilities. + These enhancements not only enrich the cycling experience by providing cyclists + with valuable data\u2014such as speed, distance traveled, and route optimization\u2014but + also position cycling as a technologically advanced means of transport. Such + innovations cater particularly to tech-savvy consumers looking for a comprehensive + solution that addresses both utility and convenience.\\n\\n## 3. Sustainable + Materials\\nIn response to growing environmental concerns, many bicycle manufacturers + are now focusing on the use of sustainable and eco-friendly materials in their + production processes. Innovations in recycled aluminum production and advancements + in carbon fiber manufacturing are leading the way to minimize the ecological + footprint of bicycles. The shift to sustainable materials not only attracts + eco-conscious consumers but also aligns with the broader movement towards sustainability + within various industries. This commitment to responsible sourcing and production + practices is intended to resonate with consumers increasingly prioritizing sustainability + in their purchasing decisions.\\n\\n## 4. Urban Infrastructure Improvements\\nCities + across the globe are investing significantly to enhance cycling infrastructure, + which includes creating dedicated bike lanes, establishing bike-sharing programs, + and increasing the availability of secure bike parking facilities. The aim of + these investment strategies is to promote cycling as a primary mode of transportation, + thereby alleviating traffic congestion and reducing urban air pollution. These + improvements not only make cycling safer and more appealing but also encourage + a cultural shift towards embracing cycling as a sustainable form of transport, + contributing to healthier urban populations.\\n\\n## 5. Global Cycling Tourism + Increase\\nCycling tourism has emerged as a rapidly growing sector, with numerous + destinations catering specifically to the needs of cyclists. Regions in Europe, + North America, and Asia have begun to promote curated cycling paths and accommodations + that enhance the travel experience for biking enthusiasts. This trend encourages + eco-friendly travel options and provides a substantial boost to local economies + reliant on tourism. With more travelers seeking unique and sustainable adventure + experiences, cycling tourism is cementing its place as a desirable and responsible + leisure activity.\\n\\n## 6. Bike Repair & Maintenance Innovations\\nAs cycling + becomes more popular, addressing the maintenance needs of bicycles is critical. + The advent of innovative solutions such as mobile bike repair services and self-service + repair stations is helping cyclists maintain their bikes more conveniently. + These services remove barriers to cycling by providing quick access to repair + assistance, thus ensuring that cyclists can get back on the road with minimal + downtime. Additionally, the proliferation of these services reflects an increasingly + proactive approach to bicycle maintenance within the industry.\\n\\n## 7. Safety + Innovations\\nSafety remains a paramount concern for cyclists, prompting the + development of several innovative features that enhance visibility and rider + protection. New safety technologies include automatic lights that adjust to + ambient lighting, integrated turn signals built into helmets, and advanced braking + systems that improve stopping power. These innovations not only elevate the + overall safety of new bicycles but also serve as essential selling points for + manufacturers, helping to reassure potential buyers about the security of their + cycling experiences.\\n\\n## 8. Performance Enhancements\\nContinual advancements + in bike design and materials have significantly improved performance for competitive + cyclists. The adoption of lightweight materials like titanium and carbon fiber + frames enhances speed and maneuverability. Moreover, state-of-the-art gear shifting + mechanisms and suspension systems are optimizing cycling efficiency and rider + comfort. These innovations cater to both amateur and professional cyclists alike, + emphasizing the drive for enhanced performance in the marketplace.\\n\\n## 9. + Inclusivity in Cycling\\nThe bicycle industry is progressively recognizing the + importance of inclusivity by producing more diverse models catering to a wide + range of body types and abilities. This includes step-through frames designed + for easier mounting and dismounting as well as specialized bikes accommodating + unique ergonomic needs. By promoting cycling as an approachable and accessible + activity for individuals of various ages and physical conditions, brands are + broadening their market reach and fostering a more inclusive cycling community.\\n\\n## + 10. Data Analytics for Cycling Trends\\nThe utilization of data analytics in + cycling is on the rise, as cities and businesses increasingly turn to data-driven + insights to understand cyclist behaviors and optimize offerings. Analytics are + being harnessed to fine-tune bike-sharing programs, enhancing user experience + through informed decision-making. This strategic approach not only aids in the + identification of high-demand cycling routes but also informs infrastructure + investments, ensuring that cycling continues to become a more viable and attractive + option for urban transport.\\n\\nIn summary, these trends reflect a dynamic + and rapidly evolving bicycle industry characterized by technological advancements, + sustainability efforts, and a commitment to inclusivity. As we advance through + 2024, these developments will shape the future of cycling, making it not just + a mode of transport but a lifestyle choice that emphasizes health, environment, + and community engagement.\",\n \"refusal\": null\n },\n \"logprobs\": + null,\n \"finish_reason\": \"stop\"\n }\n ],\n \"usage\": {\n \"prompt_tokens\": + 791,\n \"completion_tokens\": 1102,\n \"total_tokens\": 1893,\n \"prompt_tokens_details\": + {\n \"cached_tokens\": 0,\n \"audio_tokens\": 0\n },\n \"completion_tokens_details\": + {\n \"reasoning_tokens\": 0,\n \"audio_tokens\": 0,\n \"accepted_prediction_tokens\": + 0,\n \"rejected_prediction_tokens\": 0\n }\n },\n \"system_fingerprint\": + \"fp_0705bf87c0\"\n}\n" + headers: + CF-Cache-Status: + - DYNAMIC + CF-RAY: + - 8e58a4be3c906225-GRU + Connection: + - keep-alive + Content-Encoding: + - gzip + Content-Type: + - application/json + Date: + - Wed, 20 Nov 2024 13:04:18 GMT + Server: + - cloudflare + Transfer-Encoding: + - chunked + X-Content-Type-Options: + - nosniff + access-control-expose-headers: + - X-Request-ID + alt-svc: + - h3=":443"; ma=86400 + openai-organization: + - crewai-iuxna1 + openai-processing-ms: + - '16287' + openai-version: + - '2020-10-01' + strict-transport-security: + - max-age=31536000; includeSubDomains; preload + x-ratelimit-limit-requests: + - '30000' + x-ratelimit-limit-tokens: + - '150000000' + x-ratelimit-remaining-requests: + - '29999' + x-ratelimit-remaining-tokens: + - '149998883' + x-ratelimit-reset-requests: + - 2ms + x-ratelimit-reset-tokens: + - 0s + x-request-id: + - req_bb43402829dc4dc60bf6f4b76a72e6c9 + http_version: HTTP/1.1 + status_code: 200 +version: 1 diff --git a/tests/cassettes/test_before_kickoff_with_none_input.yaml b/tests/cassettes/test_before_kickoff_with_none_input.yaml new file mode 100644 index 000000000..e906e8018 --- /dev/null +++ b/tests/cassettes/test_before_kickoff_with_none_input.yaml @@ -0,0 +1,492 @@ +interactions: +- request: + body: !!binary | + CusOCiQKIgoMc2VydmljZS5uYW1lEhIKEGNyZXdBSS10ZWxlbWV0cnkSwg4KEgoQY3Jld2FpLnRl + bGVtZXRyeRKaDAoQ4G43ZjKxBKDC/tbsjP4YXxIINS4tBd9tcREqDENyZXcgQ3JlYXRlZDABOQB0 + FQ7yrgkYQdg+GA7yrgkYShoKDmNyZXdhaV92ZXJzaW9uEggKBjAuODAuMEoaCg5weXRob25fdmVy + c2lvbhIICgYzLjEyLjdKLgoIY3Jld19rZXkSIgogMWYxMjhiZGI3YmFhNGI2NzcxNGYxZGFlZGMy + ZjNhYjZKMQoHY3Jld19pZBImCiQzNTE4YjRjNS0xYTM5LTRkYjEtODEwMy03MzllNjQ5YzAwZDhK + HAoMY3Jld19wcm9jZXNzEgwKCnNlcXVlbnRpYWxKEQoLY3Jld19tZW1vcnkSAhAAShoKFGNyZXdf + bnVtYmVyX29mX3Rhc2tzEgIYAkobChVjcmV3X251bWJlcl9vZl9hZ2VudHMSAhgCSrQFCgtjcmV3 + X2FnZW50cxKkBQqhBVt7ImtleSI6ICI3M2MzNDljOTNjMTYzYjVkNGRmOThhNjRmYWMxYzQzMCIs + ICJpZCI6ICIyZmFkNjUwMC0wYTk1LTRmMTMtYjk5YS0zMTE1YzRkOTM3ODgiLCAicm9sZSI6ICJ7 + dG9waWN9IFNlbmlvciBEYXRhIFJlc2VhcmNoZXJcbiIsICJ2ZXJib3NlPyI6IHRydWUsICJtYXhf + aXRlciI6IDIwLCAibWF4X3JwbSI6IG51bGwsICJmdW5jdGlvbl9jYWxsaW5nX2xsbSI6ICIiLCAi + bGxtIjogImdwdC00by1taW5pIiwgImRlbGVnYXRpb25fZW5hYmxlZD8iOiBmYWxzZSwgImFsbG93 + X2NvZGVfZXhlY3V0aW9uPyI6IGZhbHNlLCAibWF4X3JldHJ5X2xpbWl0IjogMiwgInRvb2xzX25h + bWVzIjogW119LCB7ImtleSI6ICIxMDRmZTA2NTllMTBiNDI2Y2Y4OGYwMjRmYjU3MTU1MyIsICJp + ZCI6ICIxYTQ0MjFiOC1lZWMzLTQ1ZjItODY1NS01NDcyMWIyOTk5NDciLCAicm9sZSI6ICJ7dG9w + aWN9IFJlcG9ydGluZyBBbmFseXN0XG4iLCAidmVyYm9zZT8iOiB0cnVlLCAibWF4X2l0ZXIiOiAy + MCwgIm1heF9ycG0iOiBudWxsLCAiZnVuY3Rpb25fY2FsbGluZ19sbG0iOiAiIiwgImxsbSI6ICJn + cHQtNG8tbWluaSIsICJkZWxlZ2F0aW9uX2VuYWJsZWQ/IjogZmFsc2UsICJhbGxvd19jb2RlX2V4 + ZWN1dGlvbj8iOiBmYWxzZSwgIm1heF9yZXRyeV9saW1pdCI6IDIsICJ0b29sc19uYW1lcyI6IFtd + fV1KkwQKCmNyZXdfdGFza3MShAQKgQRbeyJrZXkiOiAiNmFmYzRiMzk2MjU5ZmJiNzY4MWY1NmM3 + NzU1Y2M5MzciLCAiaWQiOiAiMmY2ODFlY2YtNmY0Yy00NzlhLWE0ZWEtY2Y0ZTVmNGM2ZWFlIiwg + ImFzeW5jX2V4ZWN1dGlvbj8iOiBmYWxzZSwgImh1bWFuX2lucHV0PyI6IGZhbHNlLCAiYWdlbnRf + cm9sZSI6ICJ7dG9waWN9IFNlbmlvciBEYXRhIFJlc2VhcmNoZXJcbiIsICJhZ2VudF9rZXkiOiAi + NzNjMzQ5YzkzYzE2M2I1ZDRkZjk4YTY0ZmFjMWM0MzAiLCAidG9vbHNfbmFtZXMiOiBbXX0sIHsi + a2V5IjogImIxN2IxODhkYmYxNGY5M2E5OGU1Yjk1YWFkMzY3NTc3IiwgImlkIjogIjgwM2Q5YWYy + LTdhYjAtNDYzNy1iMWJjLTkxNDJmMWJkMDM0YSIsICJhc3luY19leGVjdXRpb24/IjogZmFsc2Us + ICJodW1hbl9pbnB1dD8iOiBmYWxzZSwgImFnZW50X3JvbGUiOiAie3RvcGljfSBSZXBvcnRpbmcg + QW5hbHlzdFxuIiwgImFnZW50X2tleSI6ICIxMDRmZTA2NTllMTBiNDI2Y2Y4OGYwMjRmYjU3MTU1 + MyIsICJ0b29sc19uYW1lcyI6IFtdfV16AhgBhQEAAQAAEo4CChCkKf4+mBo3buykKHqmcwYdEgit + HkuXVEC4UCoMVGFzayBDcmVhdGVkMAE5uJAnDvKuCRhBcBkoDvKuCRhKLgoIY3Jld19rZXkSIgog + MWYxMjhiZGI3YmFhNGI2NzcxNGYxZGFlZGMyZjNhYjZKMQoHY3Jld19pZBImCiQzNTE4YjRjNS0x + YTM5LTRkYjEtODEwMy03MzllNjQ5YzAwZDhKLgoIdGFza19rZXkSIgogNmFmYzRiMzk2MjU5ZmJi + NzY4MWY1NmM3NzU1Y2M5MzdKMQoHdGFza19pZBImCiQyZjY4MWVjZi02ZjRjLTQ3OWEtYTRlYS1j + ZjRlNWY0YzZlYWV6AhgBhQEAAQAA + headers: + Accept: + - '*/*' + Accept-Encoding: + - gzip, deflate + Connection: + - keep-alive + Content-Length: + - '1902' + Content-Type: + - application/x-protobuf + User-Agent: + - OTel-OTLP-Exporter-Python/1.27.0 + method: POST + uri: https://telemetry.crewai.com:4319/v1/traces + response: + body: + string: "\n\0" + headers: + Content-Length: + - '2' + Content-Type: + - application/x-protobuf + Date: + - Wed, 20 Nov 2024 13:04:49 GMT + status: + code: 200 + message: OK +- request: + body: '{"messages": [{"role": "system", "content": "You are {topic} Senior Data + Researcher\n. You''re a seasoned researcher with a knack for uncovering the + latest developments in {topic}. Known for your ability to find the most relevant + information and present it in a clear and concise manner.\n\nYour personal goal + is: Uncover cutting-edge developments in {topic}\n\nTo give my best complete + final answer to the task use the exact following format:\n\nThought: I now can + give a great answer\nFinal Answer: Your final answer must be the great and the + most complete as possible, it must be outcome described.\n\nI MUST use these + formats, my job depends on it!"}, {"role": "user", "content": "\nCurrent Task: + Conduct a thorough research about {topic} Make sure you find any interesting + and relevant information given the current year is 2024.\n\n\nThis is the expect + criteria for your final answer: A list with 10 bullet points of the most relevant + information about {topic}\n\nyou MUST return the actual complete content as + the final answer, not a summary.\n\nBegin! This is VERY important to you, use + the tools available and give your best Final Answer, your job depends on it!\n\nThought:"}], + "model": "gpt-4o-mini", "stop": ["\nObservation:"], "stream": false}' + headers: + accept: + - application/json + accept-encoding: + - gzip, deflate + connection: + - keep-alive + content-length: + - '1255' + content-type: + - application/json + cookie: + - __cf_bm=CkK4UvBd9ukXvn50uJwGambJcz5zERAJfeXJ9xge6H4-1732107842-1.0.1.1-IOK2yVL3RlD75MgmnKzIEyE38HNknwn6I8BBJ1wjGz4jCTd0YWIBPnvUm9gB8D_zLlUA9G7p_wbrfyc4mO_Bmg; + _cfuvid=MmeN9oHWrBLThkEJdaSFHBfWe95JvA8iFnnt7CC92tk-1732107842102-0.0.1.1-604800000 + host: + - api.openai.com + user-agent: + - OpenAI/Python 1.52.1 + x-stainless-arch: + - x64 + x-stainless-async: + - 'false' + x-stainless-lang: + - python + x-stainless-os: + - MacOS + x-stainless-package-version: + - 1.52.1 + x-stainless-raw-response: + - 'true' + x-stainless-retry-count: + - '0' + x-stainless-runtime: + - CPython + x-stainless-runtime-version: + - 3.12.7 + method: POST + uri: https://api.openai.com/v1/chat/completions + response: + content: "{\n \"id\": \"chatcmpl-AVefuCEPMJPCqhgvBPhOk55hlNQ0m\",\n \"object\": + \"chat.completion\",\n \"created\": 1732107886,\n \"model\": \"gpt-4o-mini-2024-07-18\",\n + \ \"choices\": [\n {\n \"index\": 0,\n \"message\": {\n \"role\": + \"assistant\",\n \"content\": \"I now can give a great answer. \\nFinal + Answer: \\n\\n1. **Artificial Intelligence Advancements**: In 2024, AI has made + significant strides in natural language processing and computer vision, with + models achieving near-human-level understanding and interpretation capabilities. + This has led to more sophisticated AI applications across industries.\\n\\n2. + **Quantum Computing Progress**: Quantum computers are now capable of surpassing + traditional computing power for specific tasks, with breakthroughs in error + correction and qubit coherence. This achievement is paving the way for real-world + applications in cryptography and complex problem-solving.\\n\\n3. **Sustainable + Energy Technologies**: The shift toward renewable energy sources has accelerated, + with innovations in solar panel efficiency and the rise of hydrogen fuel cells + gaining traction as viable alternatives for energy storage and transportation.\\n\\n4. + **Augmented Reality Enhancements**: In 2024, augmented reality (AR) technologies + are being integrated into everyday applications, from retail to education, providing + immersive experiences that enhance learning and consumer engagement.\\n\\n5. + **5G Expansion and 6G Development**: The rollout of 5G continues to expand globally, + while foundational work on 6G is underway, promising enhanced connectivity speeds, + low latency, and the potential for new applications like smart cities and automated + industries.\\n\\n6. **Data Privacy Regulations**: As data breaches become increasingly + sophisticated, worldwide regulations around data privacy have tightened, with + new laws being implemented to protect consumer information and corporate accountability + in data handling.\\n\\n7. **Biotechnology Breakthroughs**: Advances in gene + editing technologies, particularly CRISPR, have progressed rapidly, making personalized + medicine and agricultural improvements more feasible, aiming to address genetic + diseases and food security.\\n\\n8. **Blockchain Applications**: Beyond cryptocurrencies, + blockchain technology is being applied in supply chain management and digital + identity verification, offering transparent and secure methods for transactions + and record-keeping.\\n\\n9. **Mental Health Technology**: The integration of + technology in mental health care is expanding, with virtual reality and AI-driven + apps providing new therapeutic options for patients, drastically improving accessibility + and treatment personalization.\\n\\n10. **Transportation Innovations**: Electric + vehicles (EVs) have seen increased adoption due to advancements in battery technology, + while autonomous vehicles are becoming more prevalent, with pilot programs indicating + potential for widespread urban deployment by 2025.\",\n \"refusal\": + null\n },\n \"logprobs\": null,\n \"finish_reason\": \"stop\"\n + \ }\n ],\n \"usage\": {\n \"prompt_tokens\": 234,\n \"completion_tokens\": + 457,\n \"total_tokens\": 691,\n \"prompt_tokens_details\": {\n \"cached_tokens\": + 0,\n \"audio_tokens\": 0\n },\n \"completion_tokens_details\": {\n + \ \"reasoning_tokens\": 0,\n \"audio_tokens\": 0,\n \"accepted_prediction_tokens\": + 0,\n \"rejected_prediction_tokens\": 0\n }\n },\n \"system_fingerprint\": + \"fp_0705bf87c0\"\n}\n" + headers: + CF-Cache-Status: + - DYNAMIC + CF-RAY: + - 8e58a5d1ef736225-GRU + Connection: + - keep-alive + Content-Encoding: + - gzip + Content-Type: + - application/json + Date: + - Wed, 20 Nov 2024 13:04:50 GMT + Server: + - cloudflare + Transfer-Encoding: + - chunked + X-Content-Type-Options: + - nosniff + access-control-expose-headers: + - X-Request-ID + alt-svc: + - h3=":443"; ma=86400 + openai-organization: + - crewai-iuxna1 + openai-processing-ms: + - '3814' + openai-version: + - '2020-10-01' + strict-transport-security: + - max-age=31536000; includeSubDomains; preload + x-ratelimit-limit-requests: + - '30000' + x-ratelimit-limit-tokens: + - '150000000' + x-ratelimit-remaining-requests: + - '29999' + x-ratelimit-remaining-tokens: + - '149999710' + x-ratelimit-reset-requests: + - 2ms + x-ratelimit-reset-tokens: + - 0s + x-request-id: + - req_0e0bf8c81c9997414688b5188337104b + http_version: HTTP/1.1 + status_code: 200 +- request: + body: !!binary | + Cs4CCiQKIgoMc2VydmljZS5uYW1lEhIKEGNyZXdBSS10ZWxlbWV0cnkSpQIKEgoQY3Jld2FpLnRl + bGVtZXRyeRKOAgoQT0LRe4bJ4FgqPQObXTZKYRIIpR3A/gdzzPQqDFRhc2sgQ3JlYXRlZDABOfCJ + CAXzrgkYQcAICgXzrgkYSi4KCGNyZXdfa2V5EiIKIDFmMTI4YmRiN2JhYTRiNjc3MTRmMWRhZWRj + MmYzYWI2SjEKB2NyZXdfaWQSJgokMzUxOGI0YzUtMWEzOS00ZGIxLTgxMDMtNzM5ZTY0OWMwMGQ4 + Si4KCHRhc2tfa2V5EiIKIGIxN2IxODhkYmYxNGY5M2E5OGU1Yjk1YWFkMzY3NTc3SjEKB3Rhc2tf + aWQSJgokODAzZDlhZjItN2FiMC00NjM3LWIxYmMtOTE0MmYxYmQwMzRhegIYAYUBAAEAAA== + headers: + Accept: + - '*/*' + Accept-Encoding: + - gzip, deflate + Connection: + - keep-alive + Content-Length: + - '337' + Content-Type: + - application/x-protobuf + User-Agent: + - OTel-OTLP-Exporter-Python/1.27.0 + method: POST + uri: https://telemetry.crewai.com:4319/v1/traces + response: + body: + string: "\n\0" + headers: + Content-Length: + - '2' + Content-Type: + - application/x-protobuf + Date: + - Wed, 20 Nov 2024 13:04:54 GMT + status: + code: 200 + message: OK +- request: + body: '{"messages": [{"role": "system", "content": "You are {topic} Reporting + Analyst\n. You''re a meticulous analyst with a keen eye for detail. You''re + known for your ability to turn complex data into clear and concise reports, + making it easy for others to understand and act on the information you provide.\n\nYour + personal goal is: Create detailed reports based on {topic} data analysis and + research findings\n\nTo give my best complete final answer to the task use the + exact following format:\n\nThought: I now can give a great answer\nFinal Answer: + Your final answer must be the great and the most complete as possible, it must + be outcome described.\n\nI MUST use these formats, my job depends on it!"}, + {"role": "user", "content": "\nCurrent Task: Review the context you got and + expand each topic into a full section for a report. Make sure the report is + detailed and contains any and all relevant information.\n\n\nThis is the expect + criteria for your final answer: A fully fledge reports with the mains topics, + each with a full section of information. Formatted as markdown without ''```''\n\nyou + MUST return the actual complete content as the final answer, not a summary.\n\nThis + is the context you''re working with:\n1. **Artificial Intelligence Advancements**: + In 2024, AI has made significant strides in natural language processing and + computer vision, with models achieving near-human-level understanding and interpretation + capabilities. This has led to more sophisticated AI applications across industries.\n\n2. + **Quantum Computing Progress**: Quantum computers are now capable of surpassing + traditional computing power for specific tasks, with breakthroughs in error + correction and qubit coherence. This achievement is paving the way for real-world + applications in cryptography and complex problem-solving.\n\n3. **Sustainable + Energy Technologies**: The shift toward renewable energy sources has accelerated, + with innovations in solar panel efficiency and the rise of hydrogen fuel cells + gaining traction as viable alternatives for energy storage and transportation.\n\n4. + **Augmented Reality Enhancements**: In 2024, augmented reality (AR) technologies + are being integrated into everyday applications, from retail to education, providing + immersive experiences that enhance learning and consumer engagement.\n\n5. **5G + Expansion and 6G Development**: The rollout of 5G continues to expand globally, + while foundational work on 6G is underway, promising enhanced connectivity speeds, + low latency, and the potential for new applications like smart cities and automated + industries.\n\n6. **Data Privacy Regulations**: As data breaches become increasingly + sophisticated, worldwide regulations around data privacy have tightened, with + new laws being implemented to protect consumer information and corporate accountability + in data handling.\n\n7. **Biotechnology Breakthroughs**: Advances in gene editing + technologies, particularly CRISPR, have progressed rapidly, making personalized + medicine and agricultural improvements more feasible, aiming to address genetic + diseases and food security.\n\n8. **Blockchain Applications**: Beyond cryptocurrencies, + blockchain technology is being applied in supply chain management and digital + identity verification, offering transparent and secure methods for transactions + and record-keeping.\n\n9. **Mental Health Technology**: The integration of technology + in mental health care is expanding, with virtual reality and AI-driven apps + providing new therapeutic options for patients, drastically improving accessibility + and treatment personalization.\n\n10. **Transportation Innovations**: Electric + vehicles (EVs) have seen increased adoption due to advancements in battery technology, + while autonomous vehicles are becoming more prevalent, with pilot programs indicating + potential for widespread urban deployment by 2025.\n\nBegin! This is VERY important + to you, use the tools available and give your best Final Answer, your job depends + on it!\n\nThought:"}], "model": "gpt-4o-mini", "stop": ["\nObservation:"], "stream": + false}' + headers: + accept: + - application/json + accept-encoding: + - gzip, deflate + connection: + - keep-alive + content-length: + - '4065' + content-type: + - application/json + cookie: + - __cf_bm=CkK4UvBd9ukXvn50uJwGambJcz5zERAJfeXJ9xge6H4-1732107842-1.0.1.1-IOK2yVL3RlD75MgmnKzIEyE38HNknwn6I8BBJ1wjGz4jCTd0YWIBPnvUm9gB8D_zLlUA9G7p_wbrfyc4mO_Bmg; + _cfuvid=MmeN9oHWrBLThkEJdaSFHBfWe95JvA8iFnnt7CC92tk-1732107842102-0.0.1.1-604800000 + host: + - api.openai.com + user-agent: + - OpenAI/Python 1.52.1 + x-stainless-arch: + - x64 + x-stainless-async: + - 'false' + x-stainless-lang: + - python + x-stainless-os: + - MacOS + x-stainless-package-version: + - 1.52.1 + x-stainless-raw-response: + - 'true' + x-stainless-retry-count: + - '0' + x-stainless-runtime: + - CPython + x-stainless-runtime-version: + - 3.12.7 + method: POST + uri: https://api.openai.com/v1/chat/completions + response: + content: "{\n \"id\": \"chatcmpl-AVefym1A3aTi6N7szB8ei85GCHkyG\",\n \"object\": + \"chat.completion\",\n \"created\": 1732107890,\n \"model\": \"gpt-4o-mini-2024-07-18\",\n + \ \"choices\": [\n {\n \"index\": 0,\n \"message\": {\n \"role\": + \"assistant\",\n \"content\": \"I now can give a great answer \\nFinal + Answer: \\n\\n# Comprehensive Report on Key Technology Trends in 2024\\n\\n## + 1. Artificial Intelligence Advancements\\nIn 2024, artificial intelligence (AI) + has undergone remarkable advancements, particularly in the fields of natural + language processing (NLP) and computer vision. AI models are now achieving near-human-level + understanding and interpretation capabilities, enabling more nuanced interactions + between humans and machines. This progression has spurred the development of + sophisticated AI applications across various sectors, from healthcare, where + AI can analyze medical images and assist in diagnostic processes, to finance, + where predictive analytics enhances decision-making and risk management. With + AI being employed in customer service chatbots and personal assistants, the + technology's integration into daily operations significantly improves productivity + and user experience.\\n\\n## 2. Quantum Computing Progress\\nThe capabilities + of quantum computers have expanded significantly, showcasing their potential + to exceed traditional computing power for specific tasks. In 2024, key breakthroughs + have been made in areas such as error correction and qubit coherence, addressing + longstanding challenges in the field. These advancements are not only enhancing + the performance of quantum systems but are also paving the way for practical + applications in cryptography, where quantum encryption could revolutionize data + security protocols, and in complex problem-solving scenarios across scientific + research and logistics. As quantum technology matures, it holds the promise + to solve problems that are currently intractable for classical computers.\\n\\n## + 3. Sustainable Energy Technologies\\nThe global shift towards sustainable energy + sources has gained remarkable momentum in 2024, driven by innovations in solar + panel efficiency and the adoption of hydrogen fuel cells. Advances in photovoltaic + technology have led to the development of more efficient solar panels capable + of capturing a higher percentage of sunlight, reducing reliance on fossil fuels. + Simultaneously, hydrogen fuel cells are emerging as a viable alternative for + energy storage and transportation, particularly in heavy-duty vehicles and public + transport systems. This transformation towards greener energy solutions is critical + in combating climate change while fostering economic growth through new job + creation in the clean technology sector.\\n\\n## 4. Augmented Reality Enhancements\\nAugmented + reality (AR) technologies are becoming increasingly integrated into everyday + applications as of 2024, providing immersive experiences across various industries, + including retail and education. In retail, AR is enhancing consumer engagement + by allowing customers to visualize products in a real-world context before making + a purchase. In education, AR is facilitating interactive learning experiences, + enabling students to engage with complex subjects through visual simulations + and augmented textbooks. These advancements not only improve user engagement + but also foster greater understanding and retention of information.\\n\\n## + 5. 5G Expansion and 6G Development\\nThe global rollout of 5G technology continues + at a rapid pace, significantly enhancing connectivity speeds and reducing latency. + As 2024 progresses, foundational work on the next-generation 6G networks is + also underway, promising even greater improvements in connectivity and the potential + for groundbreaking applications. These advancements are facilitating the emergence + of smart cities, automated industries, and enhanced telecommunications services. + The increased bandwidth provided by 5G and the anticipation surrounding 6G enable + new possibilities in mobile communications, Internet of Things (IoT) implementations, + and real-time data processing.\\n\\n## 6. Data Privacy Regulations\\nAs data + breaches become increasingly sophisticated and pervasive, regulations surrounding + data privacy have intensified globally in 2024. Many countries have enacted + new laws aimed at protecting consumer information and holding businesses accountable + for their data handling practices. This regulatory environment requires organizations + to implement robust data protection measures, maintain transparency, and establish + trust with consumers. The emphasis on data privacy not only safeguards individuals' + personal information but also promotes ethical practices within the tech industry, + thereby fostering greater public confidence in emerging digital services.\\n\\n## + 7. Biotechnology Breakthroughs\\nThe biotechnology sector has experienced significant + developments in 2024, particularly regarding gene editing technologies such + as CRISPR. These advances enable researchers to make precise modifications to + genetic material, paving the way for personalized medicine that targets genetic + diseases at the source. Agricultural improvements are also on the horizon as + genetically modified crops become more resilient to climate change and pests, + addressing global food security challenges. With ongoing research and clinical + trials, the potential applications of biotechnology in healthcare and agriculture + present transformative opportunities to improve quality of life and sustainability.\\n\\n## + 8. Blockchain Applications\\nBeyond its initial use in cryptocurrencies, blockchain + technology is finding diverse applications in areas such as supply chain management + and digital identity verification. In 2024, businesses leverage blockchain's + inherent transparency and security to streamline operations, enhance traceability, + and foster trust among stakeholders. For example, in supply chain management, + blockchain allows for real-time tracking of products, enabling greater accountability + and efficient inventory management. Similarly, digital identity verification + is becoming more secure through decentralized systems, reducing the risk of + identity theft and fraud, thereby enhancing overall trust in digital transactions.\\n\\n## + 9. Mental Health Technology\\nIn recent years, technology's role in mental health + care has expanded dramatically, with innovative solutions such as virtual reality + (VR) therapy and AI-driven mental health applications emerging in 2024. These + technologies offer patients new therapeutic options that enhance accessibility + and treatment personalization. VR environments can simulate therapeutic situations + for exposure therapy while AI algorithms tailor mental health interventions + according to individual needs, improving overall efficacy. This integration + of technology into mental health care has the potential to bridge gaps in traditional + therapy access, providing vital support to those in need.\\n\\n## 10. Transportation + Innovations\\nTransportation has seen significant innovations as of 2024, driven + primarily by advancements in electric vehicles (EVs) and autonomous technology. + Increasing adoption of EVs is correlated with enhanced battery technologies + that extend range and reduce charging time, making them more appealing to consumers. + Simultaneously, pilot programs for autonomous vehicles are indicating promising + results for safe integration into urban environments. These developments present + the opportunity for reduced traffic congestion, lower emissions, and enhanced + mobility solutions, fundamentally reshaping the landscape of transportation + in cities worldwide by 2025. \\n\\nThrough these comprehensive explorations + of emerging technologies, it is clear that 2024 marks a pivotal year for innovation, + shaping future directions in various industries and enhancing societal progress.\",\n + \ \"refusal\": null\n },\n \"logprobs\": null,\n \"finish_reason\": + \"stop\"\n }\n ],\n \"usage\": {\n \"prompt_tokens\": 708,\n \"completion_tokens\": + 1223,\n \"total_tokens\": 1931,\n \"prompt_tokens_details\": {\n \"cached_tokens\": + 0,\n \"audio_tokens\": 0\n },\n \"completion_tokens_details\": {\n + \ \"reasoning_tokens\": 0,\n \"audio_tokens\": 0,\n \"accepted_prediction_tokens\": + 0,\n \"rejected_prediction_tokens\": 0\n }\n },\n \"system_fingerprint\": + \"fp_0705bf87c0\"\n}\n" + headers: + CF-Cache-Status: + - DYNAMIC + CF-RAY: + - 8e58a5ec0f936225-GRU + Connection: + - keep-alive + Content-Encoding: + - gzip + Content-Type: + - application/json + Date: + - Wed, 20 Nov 2024 13:05:05 GMT + Server: + - cloudflare + Transfer-Encoding: + - chunked + X-Content-Type-Options: + - nosniff + access-control-expose-headers: + - X-Request-ID + alt-svc: + - h3=":443"; ma=86400 + openai-organization: + - crewai-iuxna1 + openai-processing-ms: + - '15043' + openai-version: + - '2020-10-01' + strict-transport-security: + - max-age=31536000; includeSubDomains; preload + x-ratelimit-limit-requests: + - '30000' + x-ratelimit-limit-tokens: + - '150000000' + x-ratelimit-remaining-requests: + - '29999' + x-ratelimit-remaining-tokens: + - '149999013' + x-ratelimit-reset-requests: + - 2ms + x-ratelimit-reset-tokens: + - 0s + x-request-id: + - req_4bd436f5144121694f8df654ed8514ea + http_version: HTTP/1.1 + status_code: 200 +version: 1 diff --git a/tests/cassettes/test_multiple_before_after_kickoff.yaml b/tests/cassettes/test_multiple_before_after_kickoff.yaml new file mode 100644 index 000000000..7cd59d474 --- /dev/null +++ b/tests/cassettes/test_multiple_before_after_kickoff.yaml @@ -0,0 +1,495 @@ +interactions: +- request: + body: !!binary | + CusOCiQKIgoMc2VydmljZS5uYW1lEhIKEGNyZXdBSS10ZWxlbWV0cnkSwg4KEgoQY3Jld2FpLnRl + bGVtZXRyeRKaDAoQoIrabVYsFbbHfYiDTst34xIIG0YGNNs8p2gqDENyZXcgQ3JlYXRlZDABOYhW + grn2rgkYQUBQhbn2rgkYShoKDmNyZXdhaV92ZXJzaW9uEggKBjAuODAuMEoaCg5weXRob25fdmVy + c2lvbhIICgYzLjEyLjdKLgoIY3Jld19rZXkSIgogMWYxMjhiZGI3YmFhNGI2NzcxNGYxZGFlZGMy + ZjNhYjZKMQoHY3Jld19pZBImCiQzM2Q1NTk3MS1iZmI2LTQ5MTgtODNhZC1iZWMxZmEyYzc0NjhK + HAoMY3Jld19wcm9jZXNzEgwKCnNlcXVlbnRpYWxKEQoLY3Jld19tZW1vcnkSAhAAShoKFGNyZXdf + bnVtYmVyX29mX3Rhc2tzEgIYAkobChVjcmV3X251bWJlcl9vZl9hZ2VudHMSAhgCSrQFCgtjcmV3 + X2FnZW50cxKkBQqhBVt7ImtleSI6ICI3M2MzNDljOTNjMTYzYjVkNGRmOThhNjRmYWMxYzQzMCIs + ICJpZCI6ICIwZGZjYzg3MS01ZGI5LTRkYjItOWIyNy0xN2I0MmIyZmZiMTAiLCAicm9sZSI6ICJ7 + dG9waWN9IFNlbmlvciBEYXRhIFJlc2VhcmNoZXJcbiIsICJ2ZXJib3NlPyI6IHRydWUsICJtYXhf + aXRlciI6IDIwLCAibWF4X3JwbSI6IG51bGwsICJmdW5jdGlvbl9jYWxsaW5nX2xsbSI6ICIiLCAi + bGxtIjogImdwdC00by1taW5pIiwgImRlbGVnYXRpb25fZW5hYmxlZD8iOiBmYWxzZSwgImFsbG93 + X2NvZGVfZXhlY3V0aW9uPyI6IGZhbHNlLCAibWF4X3JldHJ5X2xpbWl0IjogMiwgInRvb2xzX25h + bWVzIjogW119LCB7ImtleSI6ICIxMDRmZTA2NTllMTBiNDI2Y2Y4OGYwMjRmYjU3MTU1MyIsICJp + ZCI6ICJiZjFkODdkZC0zZmUyLTRjYTctOTI1My0xYTQyYTljNWE5NjYiLCAicm9sZSI6ICJ7dG9w + aWN9IFJlcG9ydGluZyBBbmFseXN0XG4iLCAidmVyYm9zZT8iOiB0cnVlLCAibWF4X2l0ZXIiOiAy + MCwgIm1heF9ycG0iOiBudWxsLCAiZnVuY3Rpb25fY2FsbGluZ19sbG0iOiAiIiwgImxsbSI6ICJn + cHQtNG8tbWluaSIsICJkZWxlZ2F0aW9uX2VuYWJsZWQ/IjogZmFsc2UsICJhbGxvd19jb2RlX2V4 + ZWN1dGlvbj8iOiBmYWxzZSwgIm1heF9yZXRyeV9saW1pdCI6IDIsICJ0b29sc19uYW1lcyI6IFtd + fV1KkwQKCmNyZXdfdGFza3MShAQKgQRbeyJrZXkiOiAiNmFmYzRiMzk2MjU5ZmJiNzY4MWY1NmM3 + NzU1Y2M5MzciLCAiaWQiOiAiNjhhZmY3NzctODEwYy00N2Q0LTlmMjItMjBlY2VhY2Y3ZTFhIiwg + ImFzeW5jX2V4ZWN1dGlvbj8iOiBmYWxzZSwgImh1bWFuX2lucHV0PyI6IGZhbHNlLCAiYWdlbnRf + cm9sZSI6ICJ7dG9waWN9IFNlbmlvciBEYXRhIFJlc2VhcmNoZXJcbiIsICJhZ2VudF9rZXkiOiAi + NzNjMzQ5YzkzYzE2M2I1ZDRkZjk4YTY0ZmFjMWM0MzAiLCAidG9vbHNfbmFtZXMiOiBbXX0sIHsi + a2V5IjogImIxN2IxODhkYmYxNGY5M2E5OGU1Yjk1YWFkMzY3NTc3IiwgImlkIjogImNlZmFlNzU1 + LTMzNzctNGE3OS1hNGMyLTZkMDk5Yzk0YmRlYiIsICJhc3luY19leGVjdXRpb24/IjogZmFsc2Us + ICJodW1hbl9pbnB1dD8iOiBmYWxzZSwgImFnZW50X3JvbGUiOiAie3RvcGljfSBSZXBvcnRpbmcg + QW5hbHlzdFxuIiwgImFnZW50X2tleSI6ICIxMDRmZTA2NTllMTBiNDI2Y2Y4OGYwMjRmYjU3MTU1 + MyIsICJ0b29sc19uYW1lcyI6IFtdfV16AhgBhQEAAQAAEo4CChAk4SmgCGgI1cLD7bspORIREgiA + ME8JXP1gfioMVGFzayBDcmVhdGVkMAE56EuWufauCRhBWOCWufauCRhKLgoIY3Jld19rZXkSIgog + MWYxMjhiZGI3YmFhNGI2NzcxNGYxZGFlZGMyZjNhYjZKMQoHY3Jld19pZBImCiQzM2Q1NTk3MS1i + ZmI2LTQ5MTgtODNhZC1iZWMxZmEyYzc0NjhKLgoIdGFza19rZXkSIgogNmFmYzRiMzk2MjU5ZmJi + NzY4MWY1NmM3NzU1Y2M5MzdKMQoHdGFza19pZBImCiQ2OGFmZjc3Ny04MTBjLTQ3ZDQtOWYyMi0y + MGVjZWFjZjdlMWF6AhgBhQEAAQAA + headers: + Accept: + - '*/*' + Accept-Encoding: + - gzip, deflate + Connection: + - keep-alive + Content-Length: + - '1902' + Content-Type: + - application/x-protobuf + User-Agent: + - OTel-OTLP-Exporter-Python/1.27.0 + method: POST + uri: https://telemetry.crewai.com:4319/v1/traces + response: + body: + string: "\n\0" + headers: + Content-Length: + - '2' + Content-Type: + - application/x-protobuf + Date: + - Wed, 20 Nov 2024 13:05:09 GMT + status: + code: 200 + message: OK +- request: + body: '{"messages": [{"role": "system", "content": "You are plants Senior Data + Researcher\n. You''re a seasoned researcher with a knack for uncovering the + latest developments in plants. Known for your ability to find the most relevant + information and present it in a clear and concise manner.\n\nYour personal goal + is: Uncover cutting-edge developments in plants\n\nTo give my best complete + final answer to the task use the exact following format:\n\nThought: I now can + give a great answer\nFinal Answer: Your final answer must be the great and the + most complete as possible, it must be outcome described.\n\nI MUST use these + formats, my job depends on it!"}, {"role": "user", "content": "\nCurrent Task: + Conduct a thorough research about plants Make sure you find any interesting + and relevant information given the current year is 2024.\n\n\nThis is the expect + criteria for your final answer: A list with 10 bullet points of the most relevant + information about plants\n\nyou MUST return the actual complete content as the + final answer, not a summary.\n\nBegin! This is VERY important to you, use the + tools available and give your best Final Answer, your job depends on it!\n\nThought:"}], + "model": "gpt-4o-mini", "stop": ["\nObservation:"], "stream": false}' + headers: + accept: + - application/json + accept-encoding: + - gzip, deflate + connection: + - keep-alive + content-length: + - '1250' + content-type: + - application/json + cookie: + - __cf_bm=CkK4UvBd9ukXvn50uJwGambJcz5zERAJfeXJ9xge6H4-1732107842-1.0.1.1-IOK2yVL3RlD75MgmnKzIEyE38HNknwn6I8BBJ1wjGz4jCTd0YWIBPnvUm9gB8D_zLlUA9G7p_wbrfyc4mO_Bmg; + _cfuvid=MmeN9oHWrBLThkEJdaSFHBfWe95JvA8iFnnt7CC92tk-1732107842102-0.0.1.1-604800000 + host: + - api.openai.com + user-agent: + - OpenAI/Python 1.52.1 + x-stainless-arch: + - x64 + x-stainless-async: + - 'false' + x-stainless-lang: + - python + x-stainless-os: + - MacOS + x-stainless-package-version: + - 1.52.1 + x-stainless-raw-response: + - 'true' + x-stainless-retry-count: + - '0' + x-stainless-runtime: + - CPython + x-stainless-runtime-version: + - 3.12.7 + method: POST + uri: https://api.openai.com/v1/chat/completions + response: + content: "{\n \"id\": \"chatcmpl-AVegEGAMTASvlfwjAjy5PsqGwtN6X\",\n \"object\": + \"chat.completion\",\n \"created\": 1732107906,\n \"model\": \"gpt-4o-mini-2024-07-18\",\n + \ \"choices\": [\n {\n \"index\": 0,\n \"message\": {\n \"role\": + \"assistant\",\n \"content\": \"I now can give a great answer \\nFinal + Answer: \\n1. **Plant-Based Plastics**: 2024 has seen significant advancements + in bioplastics derived from plants such as corn and sugarcane, which are being + used as sustainable alternatives to petroleum-based plastics. These innovations + aim to reduce plastic waste and reliance on fossil fuels.\\n\\n2. **Gene Editing + Breakthroughs**: CRISPR technology continues to revolutionize agricultural practices. + Researchers have developed genetically modified crops that are more resistant + to pests and diseases, thereby decreasing the need for chemical pesticides and + increasing food security.\\n\\n3. **Vertical Farming Expansion**: Urban agriculture + has gained momentum with vertical farming techniques that utilize less water + and space. In 2024, cities worldwide are adopting these innovative systems to + meet the growing food demand while reducing transportation emissions.\\n\\n4. + **Climate-Resilient Plants**: Scientists are identifying and breeding plant + varieties that can withstand extreme weather conditions such as droughts, floods, + and high temperatures, helping farmers adapt to climate change and ensuring + crop yields remain stable.\\n\\n5. **Edible Vaccines in Plants**: Research into + producing vaccines in plants has progressed, with trials showing that certain + plants can be engineered to express antigens that could serve as edible vaccines, + offering a low-cost and easy delivery method for immunizations.\\n\\n6. **Fungi + and Plant Collaborations**: The study of mycorrhizal fungi and their symbiotic + relationships with plants has gained traction. Research indicates that these + fungi enhance nutrient uptake and enhance plant resilience to environmental + stressors, making them pivotal in sustainable agriculture.\\n\\n7. **Biophilic + Design**: The trend of integrating nature into architecture and urban planning + has expanded, with an emphasis on incorporating plants into building designs + to improve air quality, enhance mental well-being, and reduce overall energy + consumption.\\n\\n8. **Deciduous Trees and Urban Cooling**: Research shows that + strategically planted deciduous trees can significantly lower urban temperatures, + reduce energy consumption for cooling, and improve urban biodiversity, highlighting + the importance of green infrastructure.\\n\\n9. **Carnivorous Plant Studies**: + The understanding of carnivorous plants and their unique adaptations has advanced, + with findings suggesting potential applications in pest control and sustainable + agriculture due to their natural predatory methods.\\n\\n10. **Smart Agriculture + Technology**: The integration of IoT and AI in agriculture is revolutionizing + plant cultivation. Farmers now use sensors and data analytics to monitor plant + health in real-time, optimize water usage, and increase crop yields sustainably.\\n\\nThese + insights underline the ongoing research and innovations in the plant science + field that can lead to more sustainable and resilient agricultural practices + while addressing critical global challenges.\",\n \"refusal\": null\n + \ },\n \"logprobs\": null,\n \"finish_reason\": \"stop\"\n }\n + \ ],\n \"usage\": {\n \"prompt_tokens\": 227,\n \"completion_tokens\": + 529,\n \"total_tokens\": 756,\n \"prompt_tokens_details\": {\n \"cached_tokens\": + 0,\n \"audio_tokens\": 0\n },\n \"completion_tokens_details\": {\n + \ \"reasoning_tokens\": 0,\n \"audio_tokens\": 0,\n \"accepted_prediction_tokens\": + 0,\n \"rejected_prediction_tokens\": 0\n }\n },\n \"system_fingerprint\": + \"fp_0705bf87c0\"\n}\n" + headers: + CF-Cache-Status: + - DYNAMIC + CF-RAY: + - 8e58a64f3a306225-GRU + Connection: + - keep-alive + Content-Encoding: + - gzip + Content-Type: + - application/json + Date: + - Wed, 20 Nov 2024 13:05:13 GMT + Server: + - cloudflare + Transfer-Encoding: + - chunked + X-Content-Type-Options: + - nosniff + access-control-expose-headers: + - X-Request-ID + alt-svc: + - h3=":443"; ma=86400 + openai-organization: + - crewai-iuxna1 + openai-processing-ms: + - '7289' + openai-version: + - '2020-10-01' + strict-transport-security: + - max-age=31536000; includeSubDomains; preload + x-ratelimit-limit-requests: + - '30000' + x-ratelimit-limit-tokens: + - '150000000' + x-ratelimit-remaining-requests: + - '29999' + x-ratelimit-remaining-tokens: + - '149999711' + x-ratelimit-reset-requests: + - 2ms + x-ratelimit-reset-tokens: + - 0s + x-request-id: + - req_250abe3944c3c859e59c2c976b0a1248 + http_version: HTTP/1.1 + status_code: 200 +- request: + body: !!binary | + Cs4CCiQKIgoMc2VydmljZS5uYW1lEhIKEGNyZXdBSS10ZWxlbWV0cnkSpQIKEgoQY3Jld2FpLnRl + bGVtZXRyeRKOAgoQDrK/EDJCqiT+GyzEBtuJzBIIDx9rCaKAVKMqDFRhc2sgQ3JlYXRlZDABOTCH + rYH4rgkYQdCQroH4rgkYSi4KCGNyZXdfa2V5EiIKIDFmMTI4YmRiN2JhYTRiNjc3MTRmMWRhZWRj + MmYzYWI2SjEKB2NyZXdfaWQSJgokMzNkNTU5NzEtYmZiNi00OTE4LTgzYWQtYmVjMWZhMmM3NDY4 + Si4KCHRhc2tfa2V5EiIKIGIxN2IxODhkYmYxNGY5M2E5OGU1Yjk1YWFkMzY3NTc3SjEKB3Rhc2tf + aWQSJgokY2VmYWU3NTUtMzM3Ny00YTc5LWE0YzItNmQwOTljOTRiZGViegIYAYUBAAEAAA== + headers: + Accept: + - '*/*' + Accept-Encoding: + - gzip, deflate + Connection: + - keep-alive + Content-Length: + - '337' + Content-Type: + - application/x-protobuf + User-Agent: + - OTel-OTLP-Exporter-Python/1.27.0 + method: POST + uri: https://telemetry.crewai.com:4319/v1/traces + response: + body: + string: "\n\0" + headers: + Content-Length: + - '2' + Content-Type: + - application/x-protobuf + Date: + - Wed, 20 Nov 2024 13:05:19 GMT + status: + code: 200 + message: OK +- request: + body: '{"messages": [{"role": "system", "content": "You are plants Reporting Analyst\n. + You''re a meticulous analyst with a keen eye for detail. You''re known for your + ability to turn complex data into clear and concise reports, making it easy + for others to understand and act on the information you provide.\n\nYour personal + goal is: Create detailed reports based on plants data analysis and research + findings\n\nTo give my best complete final answer to the task use the exact + following format:\n\nThought: I now can give a great answer\nFinal Answer: Your + final answer must be the great and the most complete as possible, it must be + outcome described.\n\nI MUST use these formats, my job depends on it!"}, {"role": + "user", "content": "\nCurrent Task: Review the context you got and expand each + topic into a full section for a report. Make sure the report is detailed and + contains any and all relevant information.\n\n\nThis is the expect criteria + for your final answer: A fully fledge reports with the mains topics, each with + a full section of information. Formatted as markdown without ''```''\n\nyou + MUST return the actual complete content as the final answer, not a summary.\n\nThis + is the context you''re working with:\n1. **Plant-Based Plastics**: 2024 has + seen significant advancements in bioplastics derived from plants such as corn + and sugarcane, which are being used as sustainable alternatives to petroleum-based + plastics. These innovations aim to reduce plastic waste and reliance on fossil + fuels.\n\n2. **Gene Editing Breakthroughs**: CRISPR technology continues to + revolutionize agricultural practices. Researchers have developed genetically + modified crops that are more resistant to pests and diseases, thereby decreasing + the need for chemical pesticides and increasing food security.\n\n3. **Vertical + Farming Expansion**: Urban agriculture has gained momentum with vertical farming + techniques that utilize less water and space. In 2024, cities worldwide are + adopting these innovative systems to meet the growing food demand while reducing + transportation emissions.\n\n4. **Climate-Resilient Plants**: Scientists are + identifying and breeding plant varieties that can withstand extreme weather + conditions such as droughts, floods, and high temperatures, helping farmers + adapt to climate change and ensuring crop yields remain stable.\n\n5. **Edible + Vaccines in Plants**: Research into producing vaccines in plants has progressed, + with trials showing that certain plants can be engineered to express antigens + that could serve as edible vaccines, offering a low-cost and easy delivery method + for immunizations.\n\n6. **Fungi and Plant Collaborations**: The study of mycorrhizal + fungi and their symbiotic relationships with plants has gained traction. Research + indicates that these fungi enhance nutrient uptake and enhance plant resilience + to environmental stressors, making them pivotal in sustainable agriculture.\n\n7. + **Biophilic Design**: The trend of integrating nature into architecture and + urban planning has expanded, with an emphasis on incorporating plants into building + designs to improve air quality, enhance mental well-being, and reduce overall + energy consumption.\n\n8. **Deciduous Trees and Urban Cooling**: Research shows + that strategically planted deciduous trees can significantly lower urban temperatures, + reduce energy consumption for cooling, and improve urban biodiversity, highlighting + the importance of green infrastructure.\n\n9. **Carnivorous Plant Studies**: + The understanding of carnivorous plants and their unique adaptations has advanced, + with findings suggesting potential applications in pest control and sustainable + agriculture due to their natural predatory methods.\n\n10. **Smart Agriculture + Technology**: The integration of IoT and AI in agriculture is revolutionizing + plant cultivation. Farmers now use sensors and data analytics to monitor plant + health in real-time, optimize water usage, and increase crop yields sustainably.\n\nThese + insights underline the ongoing research and innovations in the plant science + field that can lead to more sustainable and resilient agricultural practices + while addressing critical global challenges.\n\nBegin! This is VERY important + to you, use the tools available and give your best Final Answer, your job depends + on it!\n\nThought:"}], "model": "gpt-4o-mini", "stop": ["\nObservation:"], "stream": + false}' + headers: + accept: + - application/json + accept-encoding: + - gzip, deflate + connection: + - keep-alive + content-length: + - '4383' + content-type: + - application/json + cookie: + - __cf_bm=CkK4UvBd9ukXvn50uJwGambJcz5zERAJfeXJ9xge6H4-1732107842-1.0.1.1-IOK2yVL3RlD75MgmnKzIEyE38HNknwn6I8BBJ1wjGz4jCTd0YWIBPnvUm9gB8D_zLlUA9G7p_wbrfyc4mO_Bmg; + _cfuvid=MmeN9oHWrBLThkEJdaSFHBfWe95JvA8iFnnt7CC92tk-1732107842102-0.0.1.1-604800000 + host: + - api.openai.com + user-agent: + - OpenAI/Python 1.52.1 + x-stainless-arch: + - x64 + x-stainless-async: + - 'false' + x-stainless-lang: + - python + x-stainless-os: + - MacOS + x-stainless-package-version: + - 1.52.1 + x-stainless-raw-response: + - 'true' + x-stainless-retry-count: + - '0' + x-stainless-runtime: + - CPython + x-stainless-runtime-version: + - 3.12.7 + method: POST + uri: https://api.openai.com/v1/chat/completions + response: + content: "{\n \"id\": \"chatcmpl-AVegMn5Ai2jlIz10QbT0vQQ6TPvD0\",\n \"object\": + \"chat.completion\",\n \"created\": 1732107914,\n \"model\": \"gpt-4o-mini-2024-07-18\",\n + \ \"choices\": [\n {\n \"index\": 0,\n \"message\": {\n \"role\": + \"assistant\",\n \"content\": \"I now can give a great answer \\nFinal + Answer: \\n\\n# Comprehensive Report on Advancements in Plant Science (2024)\\n\\n## + 1. Plant-Based Plastics\\n2024 has witnessed remarkable progress in the development + of plant-based plastics, particularly those derived from renewable resources + such as corn and sugarcane. Unlike traditional petroleum-based plastics, these + bioplastics are designed to be more environmentally friendly, addressing pressing + concerns regarding plastic waste and the continuous reliance on fossil fuels. + The production processes have been optimized to ensure lower carbon footprints + and reduced greenhouse gas emissions. Moreover, major manufacturers are beginning + to scale up production of these materials, integrating them into a wide array + of products, from packaging to consumer goods, thereby offering sustainable + alternatives and contributing positively to circular economy initiatives.\\n\\n## + 2. Gene Editing Breakthroughs\\nThe introduction of CRISPR technology has emerged + as a game-changer in agricultural methodologies. In 2024, cutting-edge research + has led to the development of genetically modified crops that exhibit enhanced + resistance to pests and diseases. This advancement not only supports increased + food production but also significantly reduces dependency on chemical pesticides, + which can have detrimental effects on both environmental health and biodiversity. + By facilitating the creation of hardier crops, these innovations play a crucial + role in bolstering food security, enabling farmers to achieve higher yields + even under challenging agricultural conditions.\\n\\n## 3. Vertical Farming + Expansion\\nVertical farming techniques have surged in popularity as urban agriculture + continues to evolve in 2024. These innovative systems are engineered to optimize + space and resource usage, utilizing cutting-edge hydroponic and aeroponic methods + to grow food in controlled environments. Cities around the globe are implementing + vertical farms to tackle the increasing demand for fresh produce while simultaneously + minimizing transportation emissions and enhancing food security in urban settings. + By using significantly less water and land compared to traditional farming, + vertical farming represents a sustainable solution to urban food production + challenges in the face of rapid urbanization.\\n\\n## 4. Climate-Resilient Plants\\nIn + response to the unpredictable impacts of climate change, researchers are focused + on identifying and breeding plant varieties capable of enduring extreme weather + conditions, such as droughts, floods, and extreme temperatures. This initiative + aims to empower farmers with viable options to adapt to shifting climate patterns, + ensuring stable crop yields despite adverse environmental conditions. The development + of these climate-resilient plants is vital for maintaining food supply chains + and safeguarding agricultural biodiversity, ultimately contributing to the long-term + sustainability of farming practices.\\n\\n## 5. Edible Vaccines in Plants\\nInnovative + research in the area of edible vaccines is making significant strides in 2024. + Scientists are exploring the potential of genetically engineered plants to express + specific antigens that can serve as immunizations. These advancements hold promising + implications for public health, particularly in developing regions where traditional + vaccine delivery systems may be less feasible. Edible vaccines offer a cost-effective, + accessible, and non-invasive way to promote immunity against various diseases, + highlighting the intersection of agriculture and health science in addressing + global health challenges.\\n\\n## 6. Fungi and Plant Collaborations\\nThe symbiotic + relationships between mycorrhizal fungi and plants have gained increasing attention + in research circles. Studies conducted in 2024 reveal that these fungi enhance + nutrient uptake, improve soil health, and bolster plant resilience when faced + with environmental stressors, such as drought and soil degradation. The incorporation + of these beneficial fungi into sustainable agricultural practices not only promotes + plant health but also contributes to organic farming resiliency, highlighting + the importance of understanding and utilizing biological partnerships in cultivating + healthy crops.\\n\\n## 7. Biophilic Design\\nThe concept of biophilic design + continues to gain traction in architecture and urban planning throughout 2024, + underscoring the importance of integrating nature into built environments. This + approach encourages the incorporation of plants and green spaces into architectural + designs, enhancing air quality and promoting mental well-being. By reducing + reliance on artificial heating and cooling systems, biophilic design offers + opportunities to lower overall energy consumption. As cities recognize the benefits + of urban greenery, significant investments are being made to create healthier, + more sustainable urban ecosystems.\\n\\n## 8. Deciduous Trees and Urban Cooling\\nResearch + conducted this year has demonstrated that strategic planting of deciduous trees + in urban areas can lead to significant temperature reductions. These trees act + as natural air conditioners by providing shade and releasing moisture. The benefits + extend beyond cooling cities\u2014for instance, they contribute to lower energy + consumption for air conditioning, enhance local biodiversity, and improve overall + urban livability. Understanding the role of green infrastructure in combating + urban heat islands is essential for developing climate-responsive cities.\\n\\n## + 9. Carnivorous Plant Studies\\nRecent advancements in the study of carnivorous + plants reveal exciting potential applications in sustainable pest control and + agriculture. By understanding their unique adaptations and natural predatory + methods, researchers envision the use of these plants as biological pest management + tools in crop production. In 2024, interest in harnessing the ecological roles + of these plants may lead to more sustainable farming practices, alleviating + the need for chemical pesticides and promoting healthier ecosystems.\\n\\n## + 10. Smart Agriculture Technology\\nThe integration of Internet of Things (IoT) + and Artificial Intelligence (AI) into agriculture has transformed how farming + is conducted in 2024. Smart agriculture technologies enable farmers to monitor + plant health, soil conditions, and microclimates through advanced sensors and + data analytics. This real-time data helps optimize water usage, elevate crop + yields, and foster sustainable farming practices. As farmers harness these technological + advancements, the sector moves toward a more efficient and environmentally conscious + model of production.\\n\\nIn conclusion, these insights reflect ongoing research + and innovation in the field of plant science, laying the groundwork for more + sustainable and resilient agricultural practices. As the world faces critical + global challenges, these advancements illustrate the potential to leverage plant + science in creating solutions that address both environmental concerns and food + security.\",\n \"refusal\": null\n },\n \"logprobs\": null,\n + \ \"finish_reason\": \"stop\"\n }\n ],\n \"usage\": {\n \"prompt_tokens\": + 777,\n \"completion_tokens\": 1163,\n \"total_tokens\": 1940,\n \"prompt_tokens_details\": + {\n \"cached_tokens\": 0,\n \"audio_tokens\": 0\n },\n \"completion_tokens_details\": + {\n \"reasoning_tokens\": 0,\n \"audio_tokens\": 0,\n \"accepted_prediction_tokens\": + 0,\n \"rejected_prediction_tokens\": 0\n }\n },\n \"system_fingerprint\": + \"fp_0705bf87c0\"\n}\n" + headers: + CF-Cache-Status: + - DYNAMIC + CF-RAY: + - 8e58a67f18856225-GRU + Connection: + - keep-alive + Content-Encoding: + - gzip + Content-Type: + - application/json + Date: + - Wed, 20 Nov 2024 13:05:35 GMT + Server: + - cloudflare + Transfer-Encoding: + - chunked + X-Content-Type-Options: + - nosniff + access-control-expose-headers: + - X-Request-ID + alt-svc: + - h3=":443"; ma=86400 + openai-organization: + - crewai-iuxna1 + openai-processing-ms: + - '20960' + openai-version: + - '2020-10-01' + strict-transport-security: + - max-age=31536000; includeSubDomains; preload + x-ratelimit-limit-requests: + - '30000' + x-ratelimit-limit-tokens: + - '150000000' + x-ratelimit-remaining-requests: + - '29999' + x-ratelimit-remaining-tokens: + - '149998934' + x-ratelimit-reset-requests: + - 2ms + x-ratelimit-reset-tokens: + - 0s + x-request-id: + - req_1a11ba8b9c0cb1803e99cd95fa1fb890 + http_version: HTTP/1.1 + status_code: 200 +version: 1 From 6774bc2c53d6a81acf104be9ad6e82f4beb5a543 Mon Sep 17 00:00:00 2001 From: Gui Vieira Date: Wed, 20 Nov 2024 16:08:08 -0300 Subject: [PATCH 070/126] Make mypy happy --- src/crewai/crew.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/crewai/crew.py b/src/crewai/crew.py index fb03acb5b..7f5539976 100644 --- a/src/crewai/crew.py +++ b/src/crewai/crew.py @@ -486,8 +486,8 @@ class Crew(BaseModel): self, inputs: Optional[Dict[str, Any]] = None, ) -> CrewOutput: - for callback in self.before_kickoff_callbacks: - inputs = callback(inputs) + for before_callback in self.before_kickoff_callbacks: + inputs = before_callback(inputs) """Starts the crew to work on its assigned tasks.""" self._execution_span = self._telemetry.crew_execution_span(self, inputs) @@ -531,8 +531,8 @@ class Crew(BaseModel): f"The process '{self.process}' is not implemented yet." ) - for callback in self.after_kickoff_callbacks: - result = callback(result) + for after_callback in self.after_kickoff_callbacks: + result = after_callback(result) metrics += [agent._token_process.get_summary() for agent in self.agents] From 14a36d3f5efef6772bce705955a2a024f7afa74c Mon Sep 17 00:00:00 2001 From: "Brandon Hancock (bhancock_ai)" <109994880+bhancockio@users.noreply.github.com> Date: Wed, 20 Nov 2024 18:40:08 -0500 Subject: [PATCH 071/126] Knowledge (#1567) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * initial knowledge * WIP * Adding core knowledge sources * Improve types and better support for file paths * added additional sources * fix linting * update yaml to include optional deps * adding in lorenze feedback * ensure embeddings are persisted * improvements all around Knowledge class * return this * properly reset memory * properly reset memory+knowledge * consolodation and improvements * linted * cleanup rm unused embedder * fix test * fix duplicate * generating cassettes for knowledge test * updated default embedder * None embedder to use default on pipeline cloning * improvements * fixed text_file_knowledge * mypysrc fixes * type check fixes * added extra cassette * just mocks * linted * mock knowledge query to not spin up db * linted * verbose run * put a flag * fix * adding docs * better docs * improvements from review * more docs * linted * rm print * more fixes * clearer docs * added docstrings and type hints for cli --------- Co-authored-by: João Moura Co-authored-by: Lorenze Jay --- .github/workflows/tests.yml | 4 +- docs/concepts/knowledge.mdx | 75 +++ pyproject.toml | 10 + src/crewai/__init__.py | 14 +- src/crewai/agent.py | 15 +- src/crewai/cli/cli.py | 14 +- src/crewai/cli/reset_memories_command.py | 15 +- src/crewai/crew.py | 14 + src/crewai/knowledge/__init__.py | 0 src/crewai/knowledge/embedder/__init__.py | 0 .../knowledge/embedder/base_embedder.py | 55 ++ src/crewai/knowledge/embedder/fastembed.py | 93 +++ src/crewai/knowledge/knowledge.py | 54 ++ src/crewai/knowledge/source/__init__.py | 0 .../source/base_file_knowledge_source.py | 36 ++ .../knowledge/source/base_knowledge_source.py | 48 ++ .../knowledge/source/csv_knowledge_source.py | 44 ++ .../source/excel_knowledge_source.py | 56 ++ .../knowledge/source/json_knowledge_source.py | 54 ++ .../knowledge/source/pdf_knowledge_source.py | 54 ++ .../source/string_knowledge_source.py | 33 ++ .../source/text_file_knowledge_source.py | 35 ++ src/crewai/knowledge/storage/__init__.py | 0 .../storage/base_knowledge_storage.py | 29 + .../knowledge/storage/knowledge_storage.py | 132 +++++ src/crewai/memory/storage/rag_storage.py | 134 +---- src/crewai/utilities/__init__.py | 2 + src/crewai/utilities/constants.py | 1 + .../utilities/embedding_configurator.py | 183 ++++++ tests/agent_test.py | 42 +- .../test_agent_with_knowledge_sources.yaml | 115 ++++ .../test_kickoff_for_each_error_handling.yaml | 232 ++++++++ tests/knowledge/__init__.py | 0 tests/knowledge/crewai_quickstart.pdf | Bin 0 -> 952178 bytes tests/knowledge/knowledge_test.py | 545 ++++++++++++++++++ tests/pipeline/test_pipeline.py | 2 + uv.lock | 428 ++++++++++---- 37 files changed, 2302 insertions(+), 266 deletions(-) create mode 100644 docs/concepts/knowledge.mdx create mode 100644 src/crewai/knowledge/__init__.py create mode 100644 src/crewai/knowledge/embedder/__init__.py create mode 100644 src/crewai/knowledge/embedder/base_embedder.py create mode 100644 src/crewai/knowledge/embedder/fastembed.py create mode 100644 src/crewai/knowledge/knowledge.py create mode 100644 src/crewai/knowledge/source/__init__.py create mode 100644 src/crewai/knowledge/source/base_file_knowledge_source.py create mode 100644 src/crewai/knowledge/source/base_knowledge_source.py create mode 100644 src/crewai/knowledge/source/csv_knowledge_source.py create mode 100644 src/crewai/knowledge/source/excel_knowledge_source.py create mode 100644 src/crewai/knowledge/source/json_knowledge_source.py create mode 100644 src/crewai/knowledge/source/pdf_knowledge_source.py create mode 100644 src/crewai/knowledge/source/string_knowledge_source.py create mode 100644 src/crewai/knowledge/source/text_file_knowledge_source.py create mode 100644 src/crewai/knowledge/storage/__init__.py create mode 100644 src/crewai/knowledge/storage/base_knowledge_storage.py create mode 100644 src/crewai/knowledge/storage/knowledge_storage.py create mode 100644 src/crewai/utilities/embedding_configurator.py create mode 100644 tests/cassettes/test_agent_with_knowledge_sources.yaml create mode 100644 tests/cassettes/test_kickoff_for_each_error_handling.yaml create mode 100644 tests/knowledge/__init__.py create mode 100644 tests/knowledge/crewai_quickstart.pdf create mode 100644 tests/knowledge/knowledge_test.py diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 04f6693d0..76ef2a611 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -26,7 +26,7 @@ jobs: run: uv python install 3.11.9 - name: Install the project - run: uv sync --dev + run: uv sync --dev --all-extras - name: Run tests - run: uv run pytest tests + run: uv run pytest tests -vv diff --git a/docs/concepts/knowledge.mdx b/docs/concepts/knowledge.mdx new file mode 100644 index 000000000..2afb1b568 --- /dev/null +++ b/docs/concepts/knowledge.mdx @@ -0,0 +1,75 @@ +--- +title: Knowledge +description: What is knowledge in CrewAI and how to use it. +icon: book +--- + +# Using Knowledge in CrewAI + +## Introduction + +The Knowledge class in CrewAI provides a powerful way to manage and query knowledge sources for your AI agents. This guide will show you how to implement knowledge management in your CrewAI projects. +Additionally, we have specific tools for generate knowledge sources for strings, text files, PDF's, and Spreadsheets. You can expand on any source type by extending the `KnowledgeSource` class. + +## Basic Implementation + +Here's a simple example of how to use the Knowledge class: + +```python +from crewai import Agent, Task, Crew, Process, LLM +from crewai.knowledge.source.string_knowledge_source import StringKnowledgeSource + +# Create a knowledge source +content = "Users name is John. He is 30 years old and lives in San Francisco." +string_source = StringKnowledgeSource( + content=content, metadata={"preference": "personal"} +) + + +llm = LLM(model="gpt-4o-mini", temperature=0) + # Create an agent with the knowledge store +agent = Agent( + role="About User", + goal="You know everything about the user.", + backstory="""You are a master at understanding people and their preferences.""", + verbose=True, + allow_delegation=False, + llm=llm, +) +task = Task( + description="Answer the following questions about the user: {question}", + expected_output="An answer to the question.", + agent=agent, +) + +crew = Crew( + agents=[agent], + tasks=[task], + verbose=True, + process=Process.sequential, + knowledge={"sources": [string_source], "metadata": {"preference": "personal"}}, # Enable knowledge by adding the sources here. You can also add more sources to the sources list. +) + +result = crew.kickoff(inputs={"question": "What city does John live in and how old is he?"}) +``` + + +## Embedder Configuration + +You can also configure the embedder for the knowledge store. This is useful if you want to use a different embedder for the knowledge store than the one used for the agents. + +```python +... +string_source = StringKnowledgeSource( + content="Users name is John. He is 30 years old and lives in San Francisco.", + metadata={"preference": "personal"} +) +crew = Crew( + ... + knowledge={ + "sources": [string_source], + "metadata": {"preference": "personal"}, + "embedder_config": {"provider": "openai", "config": {"model": "text-embedding-3-small"}}, + }, +) +``` diff --git a/pyproject.toml b/pyproject.toml index 5955baf33..2dbc00e24 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -39,6 +39,16 @@ Repository = "https://github.com/crewAIInc/crewAI" [project.optional-dependencies] tools = ["crewai-tools>=0.14.0"] agentops = ["agentops>=0.3.0"] +fastembed = ["fastembed>=0.4.1"] +pdfplumber = [ + "pdfplumber>=0.11.4", +] +pandas = [ + "pandas>=2.2.3", +] +openpyxl = [ + "openpyxl>=3.1.5", +] mem0 = ["mem0ai>=0.1.29"] [tool.uv] diff --git a/src/crewai/__init__.py b/src/crewai/__init__.py index fbad09663..6cfa381de 100644 --- a/src/crewai/__init__.py +++ b/src/crewai/__init__.py @@ -1,7 +1,9 @@ import warnings + from crewai.agent import Agent from crewai.crew import Crew from crewai.flow.flow import Flow +from crewai.knowledge.knowledge import Knowledge from crewai.llm import LLM from crewai.pipeline import Pipeline from crewai.process import Process @@ -15,4 +17,14 @@ warnings.filterwarnings( module="pydantic.main", ) __version__ = "0.80.0" -__all__ = ["Agent", "Crew", "Process", "Task", "Pipeline", "Router", "LLM", "Flow"] +__all__ = [ + "Agent", + "Crew", + "Process", + "Task", + "Pipeline", + "Router", + "LLM", + "Flow", + "Knowledge", +] diff --git a/src/crewai/agent.py b/src/crewai/agent.py index 4e9a0685f..d17cbbdfe 100644 --- a/src/crewai/agent.py +++ b/src/crewai/agent.py @@ -11,8 +11,8 @@ from crewai.agents.crew_agent_executor import CrewAgentExecutor from crewai.cli.constants import ENV_VARS from crewai.llm import LLM from crewai.memory.contextual.contextual_memory import ContextualMemory -from crewai.tools.agent_tools.agent_tools import AgentTools from crewai.tools import BaseTool +from crewai.tools.agent_tools.agent_tools import AgentTools from crewai.utilities import Converter, Prompts from crewai.utilities.constants import TRAINED_AGENTS_DATA_FILE, TRAINING_DATA_FILE from crewai.utilities.token_counter_callback import TokenCalcHandler @@ -52,6 +52,7 @@ class Agent(BaseAgent): role: The role of the agent. goal: The objective of the agent. backstory: The backstory of the agent. + knowledge: The knowledge base of the agent. config: Dict representation of agent configuration. llm: The language model that will run the agent. function_calling_llm: The language model that will handle the tool calling for this agent, it overrides the crew function_calling_llm. @@ -272,6 +273,18 @@ class Agent(BaseAgent): if memory.strip() != "": task_prompt += self.i18n.slice("memory").format(memory=memory) + # Integrate the knowledge base + if self.crew and self.crew.knowledge: + knowledge_snippets = self.crew.knowledge.query([task.prompt()]) + valid_snippets = [ + result["context"] + for result in knowledge_snippets + if result and result.get("context") + ] + if valid_snippets: + formatted_knowledge = "\n".join(valid_snippets) + task_prompt += f"\n\nAdditional Information:\n{formatted_knowledge}" + tools = tools or self.tools or [] self.create_agent_executor(tools=tools, task=task) diff --git a/src/crewai/cli/cli.py b/src/crewai/cli/cli.py index 0f43ff3f4..600eb6142 100644 --- a/src/crewai/cli/cli.py +++ b/src/crewai/cli/cli.py @@ -136,6 +136,7 @@ def log_tasks_outputs() -> None: @click.option("-l", "--long", is_flag=True, help="Reset LONG TERM memory") @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( "-k", "--kickoff-outputs", @@ -143,17 +144,24 @@ def log_tasks_outputs() -> None: help="Reset LATEST KICKOFF TASK OUTPUTS", ) @click.option("-a", "--all", is_flag=True, help="Reset ALL memories") -def reset_memories(long, short, entities, kickoff_outputs, all): +def reset_memories( + long: bool, + short: bool, + entities: bool, + knowledge: bool, + kickoff_outputs: bool, + all: bool, +) -> None: """ Reset the crew memories (long, short, entity, latest_crew_kickoff_ouputs). This will delete all the data saved. """ try: - if not all and not (long or short or entities or kickoff_outputs): + 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, 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) diff --git a/src/crewai/cli/reset_memories_command.py b/src/crewai/cli/reset_memories_command.py index c4808594f..31624cfc3 100644 --- a/src/crewai/cli/reset_memories_command.py +++ b/src/crewai/cli/reset_memories_command.py @@ -5,9 +5,17 @@ from crewai.memory.entity.entity_memory import EntityMemory from crewai.memory.long_term.long_term_memory import LongTermMemory from crewai.memory.short_term.short_term_memory import ShortTermMemory from crewai.utilities.task_output_storage_handler import TaskOutputStorageHandler +from crewai.knowledge.storage.knowledge_storage import KnowledgeStorage -def reset_memories_command(long, short, entity, kickoff_outputs, all) -> None: +def reset_memories_command( + long, + short, + entity, + knowledge, + kickoff_outputs, + all, +) -> None: """ Reset the crew memories. @@ -17,6 +25,7 @@ def reset_memories_command(long, short, entity, kickoff_outputs, all) -> None: entity (bool): Whether to reset the entity memory. 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. """ try: @@ -25,6 +34,7 @@ def reset_memories_command(long, short, entity, kickoff_outputs, all) -> None: EntityMemory().reset() LongTermMemory().reset() TaskOutputStorageHandler().reset() + KnowledgeStorage().reset() click.echo("All memories have been reset.") else: if long: @@ -40,6 +50,9 @@ def reset_memories_command(long, short, entity, kickoff_outputs, all) -> None: if kickoff_outputs: TaskOutputStorageHandler().reset() click.echo("Latest Kickoff outputs stored has been reset.") + if knowledge: + KnowledgeStorage().reset() + click.echo("Knowledge has been reset.") except subprocess.CalledProcessError as e: click.echo(f"An error occurred while resetting the memories: {e}", err=True) diff --git a/src/crewai/crew.py b/src/crewai/crew.py index 7f5539976..4c3886a3f 100644 --- a/src/crewai/crew.py +++ b/src/crewai/crew.py @@ -27,6 +27,7 @@ from crewai.llm import LLM from crewai.memory.entity.entity_memory import EntityMemory from crewai.memory.long_term.long_term_memory import LongTermMemory from crewai.memory.short_term.short_term_memory import ShortTermMemory +from crewai.knowledge.knowledge import Knowledge from crewai.memory.user.user_memory import UserMemory from crewai.process import Process from crewai.task import Task @@ -201,6 +202,10 @@ class Crew(BaseModel): default=[], description="List of execution logs for tasks", ) + knowledge: Optional[Dict[str, Any]] = Field( + default=None, description="Knowledge for the crew. Add knowledge sources to the knowledge object." + ) + @field_validator("id", mode="before") @classmethod @@ -275,6 +280,15 @@ class Crew(BaseModel): self._user_memory = None return self + @model_validator(mode="after") + def create_crew_knowledge(self) -> "Crew": + if self.knowledge: + try: + self.knowledge = Knowledge(**self.knowledge) if isinstance(self.knowledge, dict) else self.knowledge + except (TypeError, ValueError) as e: + raise ValueError(f"Invalid knowledge configuration: {str(e)}") + return self + @model_validator(mode="after") def check_manager_llm(self): """Validates that the language model is set when using hierarchical process.""" diff --git a/src/crewai/knowledge/__init__.py b/src/crewai/knowledge/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/src/crewai/knowledge/embedder/__init__.py b/src/crewai/knowledge/embedder/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/src/crewai/knowledge/embedder/base_embedder.py b/src/crewai/knowledge/embedder/base_embedder.py new file mode 100644 index 000000000..c3252bf43 --- /dev/null +++ b/src/crewai/knowledge/embedder/base_embedder.py @@ -0,0 +1,55 @@ +from abc import ABC, abstractmethod +from typing import List + +import numpy as np + + +class BaseEmbedder(ABC): + """ + Abstract base class for text embedding models + """ + + @abstractmethod + def embed_chunks(self, chunks: List[str]) -> np.ndarray: + """ + Generate embeddings for a list of text chunks + + Args: + chunks: List of text chunks to embed + + Returns: + Array of embeddings + """ + pass + + @abstractmethod + def embed_texts(self, texts: List[str]) -> np.ndarray: + """ + Generate embeddings for a list of texts + + Args: + texts: List of texts to embed + + Returns: + Array of embeddings + """ + pass + + @abstractmethod + def embed_text(self, text: str) -> np.ndarray: + """ + Generate embedding for a single text + + Args: + text: Text to embed + + Returns: + Embedding array + """ + pass + + @property + @abstractmethod + def dimension(self) -> int: + """Get the dimension of the embeddings""" + pass diff --git a/src/crewai/knowledge/embedder/fastembed.py b/src/crewai/knowledge/embedder/fastembed.py new file mode 100644 index 000000000..54db11643 --- /dev/null +++ b/src/crewai/knowledge/embedder/fastembed.py @@ -0,0 +1,93 @@ +from pathlib import Path +from typing import List, Optional, Union + +import numpy as np + +from .base_embedder import BaseEmbedder + +try: + from fastembed_gpu import TextEmbedding # type: ignore + + FASTEMBED_AVAILABLE = True +except ImportError: + try: + from fastembed import TextEmbedding + + FASTEMBED_AVAILABLE = True + except ImportError: + FASTEMBED_AVAILABLE = False + + +class FastEmbed(BaseEmbedder): + """ + A wrapper class for text embedding models using FastEmbed + """ + + def __init__( + self, + model_name: str = "BAAI/bge-small-en-v1.5", + cache_dir: Optional[Union[str, Path]] = None, + ): + """ + Initialize the embedding model + + Args: + model_name: Name of the model to use + cache_dir: Directory to cache the model + gpu: Whether to use GPU acceleration + """ + if not FASTEMBED_AVAILABLE: + raise ImportError( + "FastEmbed is not installed. Please install it with: " + "uv pip install fastembed or uv pip install fastembed-gpu for GPU support" + ) + + self.model = TextEmbedding( + model_name=model_name, + cache_dir=str(cache_dir) if cache_dir else None, + ) + + def embed_chunks(self, chunks: List[str]) -> List[np.ndarray]: + """ + Generate embeddings for a list of text chunks + + Args: + chunks: List of text chunks to embed + + Returns: + List of embeddings + """ + embeddings = list(self.model.embed(chunks)) + return embeddings + + def embed_texts(self, texts: List[str]) -> List[np.ndarray]: + """ + Generate embeddings for a list of texts + + Args: + texts: List of texts to embed + + Returns: + List of embeddings + """ + embeddings = list(self.model.embed(texts)) + return embeddings + + def embed_text(self, text: str) -> np.ndarray: + """ + Generate embedding for a single text + + Args: + text: Text to embed + + Returns: + Embedding array + """ + return self.embed_texts([text])[0] + + @property + def dimension(self) -> int: + """Get the dimension of the embeddings""" + # Generate a test embedding to get dimensions + test_embed = self.embed_text("test") + return len(test_embed) diff --git a/src/crewai/knowledge/knowledge.py b/src/crewai/knowledge/knowledge.py new file mode 100644 index 000000000..cf2907e67 --- /dev/null +++ b/src/crewai/knowledge/knowledge.py @@ -0,0 +1,54 @@ +import os + +from typing import List, Optional, Dict, Any +from pydantic import BaseModel, ConfigDict, Field + +from crewai.knowledge.source.base_knowledge_source import BaseKnowledgeSource +from crewai.knowledge.storage.knowledge_storage import KnowledgeStorage +from crewai.utilities.logger import Logger +from crewai.utilities.constants import DEFAULT_SCORE_THRESHOLD +os.environ["TOKENIZERS_PARALLELISM"] = "false" # removes logging from fastembed + + +class Knowledge(BaseModel): + """ + Knowledge is a collection of sources and setup for the vector store to save and query relevant context. + Args: + sources: List[BaseKnowledgeSource] = Field(default_factory=list) + storage: KnowledgeStorage = Field(default_factory=KnowledgeStorage) + embedder_config: Optional[Dict[str, Any]] = None + """ + sources: List[BaseKnowledgeSource] = Field(default_factory=list) + model_config = ConfigDict(arbitrary_types_allowed=True) + storage: KnowledgeStorage = Field(default_factory=KnowledgeStorage) + embedder_config: Optional[Dict[str, Any]] = None + + def __init__(self, embedder_config: Optional[Dict[str, Any]] = None, **data): + super().__init__(**data) + self.storage = KnowledgeStorage(embedder_config=embedder_config or None) + + try: + for source in self.sources: + source.add() + except Exception as e: + Logger(verbose=True).log( + "warning", + f"Failed to init knowledge: {e}", + color="yellow", + ) + + def query( + self, query: List[str], limit: int = 3, preference: Optional[str] = None + ) -> List[Dict[str, Any]]: + """ + Query across all knowledge sources to find the most relevant information. + Returns the top_k most relevant chunks. + """ + + results = self.storage.search( + query, + limit, + filter={"preference": preference} if preference else None, + score_threshold=DEFAULT_SCORE_THRESHOLD, + ) + return results diff --git a/src/crewai/knowledge/source/__init__.py b/src/crewai/knowledge/source/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/src/crewai/knowledge/source/base_file_knowledge_source.py b/src/crewai/knowledge/source/base_file_knowledge_source.py new file mode 100644 index 000000000..b6e346534 --- /dev/null +++ b/src/crewai/knowledge/source/base_file_knowledge_source.py @@ -0,0 +1,36 @@ +from pathlib import Path +from typing import Union, List + +from pydantic import Field + +from crewai.knowledge.source.base_knowledge_source import BaseKnowledgeSource +from typing import Dict, Any +from crewai.knowledge.storage.knowledge_storage import KnowledgeStorage + + +class BaseFileKnowledgeSource(BaseKnowledgeSource): + """Base class for knowledge sources that load content from files.""" + + file_path: Union[Path, List[Path]] = Field(...) + content: Dict[Path, str] = Field(init=False, default_factory=dict) + storage: KnowledgeStorage = Field(default_factory=KnowledgeStorage) + + def model_post_init(self, _): + """Post-initialization method to load content.""" + self.content = self.load_content() + + def load_content(self) -> Dict[Path, str]: + """Load and preprocess file content. Should be overridden by subclasses.""" + paths = [self.file_path] if isinstance(self.file_path, Path) else self.file_path + + for path in paths: + if not path.exists(): + raise FileNotFoundError(f"File not found: {path}") + if not path.is_file(): + raise ValueError(f"Path is not a file: {path}") + return {} + + def save_documents(self, metadata: Dict[str, Any]): + """Save the documents to the storage.""" + chunk_metadatas = [metadata.copy() for _ in self.chunks] + self.storage.save(self.chunks, chunk_metadatas) diff --git a/src/crewai/knowledge/source/base_knowledge_source.py b/src/crewai/knowledge/source/base_knowledge_source.py new file mode 100644 index 000000000..bb4c69cf3 --- /dev/null +++ b/src/crewai/knowledge/source/base_knowledge_source.py @@ -0,0 +1,48 @@ +from abc import ABC, abstractmethod +from typing import List, Dict, Any + +import numpy as np +from pydantic import BaseModel, ConfigDict, Field + +from crewai.knowledge.storage.knowledge_storage import KnowledgeStorage + + +class BaseKnowledgeSource(BaseModel, ABC): + """Abstract base class for knowledge sources.""" + + chunk_size: int = 4000 + chunk_overlap: int = 200 + chunks: List[str] = Field(default_factory=list) + chunk_embeddings: List[np.ndarray] = Field(default_factory=list) + + model_config = ConfigDict(arbitrary_types_allowed=True) + storage: KnowledgeStorage = Field(default_factory=KnowledgeStorage) + metadata: Dict[str, Any] = Field(default_factory=dict) + + @abstractmethod + def load_content(self) -> Dict[Any, str]: + """Load and preprocess content from the source.""" + pass + + @abstractmethod + def add(self) -> None: + """Process content, chunk it, compute embeddings, and save them.""" + pass + + def get_embeddings(self) -> List[np.ndarray]: + """Return the list of embeddings for the chunks.""" + return self.chunk_embeddings + + def _chunk_text(self, text: str) -> List[str]: + """Utility method to split text into chunks.""" + return [ + text[i : i + self.chunk_size] + for i in range(0, len(text), self.chunk_size - self.chunk_overlap) + ] + + def save_documents(self, metadata: Dict[str, Any]): + """ + Save the documents to the storage. + This method should be called after the chunks and embeddings are generated. + """ + self.storage.save(self.chunks, metadata) diff --git a/src/crewai/knowledge/source/csv_knowledge_source.py b/src/crewai/knowledge/source/csv_knowledge_source.py new file mode 100644 index 000000000..0946104a4 --- /dev/null +++ b/src/crewai/knowledge/source/csv_knowledge_source.py @@ -0,0 +1,44 @@ +import csv +from typing import Dict, List +from pathlib import Path + +from crewai.knowledge.source.base_file_knowledge_source import BaseFileKnowledgeSource + + +class CSVKnowledgeSource(BaseFileKnowledgeSource): + """A knowledge source that stores and queries CSV file content using embeddings.""" + + def load_content(self) -> Dict[Path, str]: + """Load and preprocess CSV file content.""" + super().load_content() # Validate the file path + + file_path = ( + self.file_path[0] if isinstance(self.file_path, list) else self.file_path + ) + file_path = Path(file_path) if isinstance(file_path, str) else file_path + + with open(file_path, "r", encoding="utf-8") as csvfile: + reader = csv.reader(csvfile) + content = "" + for row in reader: + content += " ".join(row) + "\n" + return {file_path: content} + + def add(self) -> None: + """ + Add CSV file content to the knowledge source, chunk it, compute embeddings, + and save the embeddings. + """ + content_str = ( + str(self.content) if isinstance(self.content, dict) else self.content + ) + new_chunks = self._chunk_text(content_str) + self.chunks.extend(new_chunks) + self.save_documents(metadata=self.metadata) + + def _chunk_text(self, text: str) -> List[str]: + """Utility method to split text into chunks.""" + return [ + text[i : i + self.chunk_size] + for i in range(0, len(text), self.chunk_size - self.chunk_overlap) + ] diff --git a/src/crewai/knowledge/source/excel_knowledge_source.py b/src/crewai/knowledge/source/excel_knowledge_source.py new file mode 100644 index 000000000..3b5c71514 --- /dev/null +++ b/src/crewai/knowledge/source/excel_knowledge_source.py @@ -0,0 +1,56 @@ +from typing import Dict, List +from pathlib import Path +from crewai.knowledge.source.base_file_knowledge_source import BaseFileKnowledgeSource + + +class ExcelKnowledgeSource(BaseFileKnowledgeSource): + """A knowledge source that stores and queries Excel file content using embeddings.""" + + def load_content(self) -> Dict[Path, str]: + """Load and preprocess Excel file content.""" + super().load_content() # Validate the file path + pd = self._import_dependencies() + + if isinstance(self.file_path, list): + file_path = self.file_path[0] + else: + file_path = self.file_path + + df = pd.read_excel(file_path) + content = df.to_csv(index=False) + return {file_path: content} + + def _import_dependencies(self): + """Dynamically import dependencies.""" + try: + import openpyxl # noqa + import pandas as pd + + return pd + except ImportError as e: + missing_package = str(e).split()[-1] + raise ImportError( + f"{missing_package} is not installed. Please install it with: pip install {missing_package}" + ) + + def add(self) -> None: + """ + Add Excel file content to the knowledge source, chunk it, compute embeddings, + and save the embeddings. + """ + # Convert dictionary values to a single string if content is a dictionary + if isinstance(self.content, dict): + content_str = "\n".join(str(value) for value in self.content.values()) + else: + content_str = str(self.content) + + new_chunks = self._chunk_text(content_str) + self.chunks.extend(new_chunks) + self.save_documents(metadata=self.metadata) + + def _chunk_text(self, text: str) -> List[str]: + """Utility method to split text into chunks.""" + return [ + text[i : i + self.chunk_size] + for i in range(0, len(text), self.chunk_size - self.chunk_overlap) + ] diff --git a/src/crewai/knowledge/source/json_knowledge_source.py b/src/crewai/knowledge/source/json_knowledge_source.py new file mode 100644 index 000000000..490423a00 --- /dev/null +++ b/src/crewai/knowledge/source/json_knowledge_source.py @@ -0,0 +1,54 @@ +import json +from typing import Any, Dict, List +from pathlib import Path + +from crewai.knowledge.source.base_file_knowledge_source import BaseFileKnowledgeSource + + +class JSONKnowledgeSource(BaseFileKnowledgeSource): + """A knowledge source that stores and queries JSON file content using embeddings.""" + + def load_content(self) -> Dict[Path, str]: + """Load and preprocess JSON file content.""" + super().load_content() # Validate the file path + paths = [self.file_path] if isinstance(self.file_path, Path) else self.file_path + + content: Dict[Path, str] = {} + for path in paths: + with open(path, "r", encoding="utf-8") as json_file: + data = json.load(json_file) + content[path] = self._json_to_text(data) + return content + + def _json_to_text(self, data: Any, level: int = 0) -> str: + """Recursively convert JSON data to a text representation.""" + text = "" + indent = " " * level + if isinstance(data, dict): + for key, value in data.items(): + text += f"{indent}{key}: {self._json_to_text(value, level + 1)}\n" + elif isinstance(data, list): + for item in data: + text += f"{indent}- {self._json_to_text(item, level + 1)}\n" + else: + text += f"{str(data)}" + return text + + def add(self) -> None: + """ + Add JSON file content to the knowledge source, chunk it, compute embeddings, + and save the embeddings. + """ + content_str = ( + str(self.content) if isinstance(self.content, dict) else self.content + ) + new_chunks = self._chunk_text(content_str) + self.chunks.extend(new_chunks) + self.save_documents(metadata=self.metadata) + + def _chunk_text(self, text: str) -> List[str]: + """Utility method to split text into chunks.""" + return [ + text[i : i + self.chunk_size] + for i in range(0, len(text), self.chunk_size - self.chunk_overlap) + ] diff --git a/src/crewai/knowledge/source/pdf_knowledge_source.py b/src/crewai/knowledge/source/pdf_knowledge_source.py new file mode 100644 index 000000000..623ba30a2 --- /dev/null +++ b/src/crewai/knowledge/source/pdf_knowledge_source.py @@ -0,0 +1,54 @@ +from typing import List, Dict +from pathlib import Path + +from crewai.knowledge.source.base_file_knowledge_source import BaseFileKnowledgeSource + + +class PDFKnowledgeSource(BaseFileKnowledgeSource): + """A knowledge source that stores and queries PDF file content using embeddings.""" + + def load_content(self) -> Dict[Path, str]: + """Load and preprocess PDF file content.""" + super().load_content() # Validate the file paths + pdfplumber = self._import_pdfplumber() + + paths = [self.file_path] if isinstance(self.file_path, Path) else self.file_path + content = {} + + for path in paths: + text = "" + with pdfplumber.open(path) as pdf: + for page in pdf.pages: + page_text = page.extract_text() + if page_text: + text += page_text + "\n" + content[path] = text + return content + + def _import_pdfplumber(self): + """Dynamically import pdfplumber.""" + try: + import pdfplumber + + return pdfplumber + except ImportError: + raise ImportError( + "pdfplumber is not installed. Please install it with: pip install pdfplumber" + ) + + def add(self) -> None: + """ + Add PDF file content to the knowledge source, chunk it, compute embeddings, + and save the embeddings. + """ + for _, text in self.content.items(): + new_chunks = self._chunk_text(text) + self.chunks.extend(new_chunks) + self.save_documents(metadata=self.metadata) + + def _chunk_text(self, text: str) -> List[str]: + """Utility method to split text into chunks.""" + return [ + text[i : i + self.chunk_size] + for i in range(0, len(text), self.chunk_size - self.chunk_overlap) + ] diff --git a/src/crewai/knowledge/source/string_knowledge_source.py b/src/crewai/knowledge/source/string_knowledge_source.py new file mode 100644 index 000000000..d4c22e3c1 --- /dev/null +++ b/src/crewai/knowledge/source/string_knowledge_source.py @@ -0,0 +1,33 @@ +from typing import List + +from pydantic import Field + +from crewai.knowledge.source.base_knowledge_source import BaseKnowledgeSource + + +class StringKnowledgeSource(BaseKnowledgeSource): + """A knowledge source that stores and queries plain text content using embeddings.""" + + content: str = Field(...) + + def model_post_init(self, _): + """Post-initialization method to validate content.""" + self.load_content() + + def load_content(self): + """Validate string content.""" + if not isinstance(self.content, str): + raise ValueError("StringKnowledgeSource only accepts string content") + + def add(self) -> None: + """Add string content to the knowledge source, chunk it, compute embeddings, and save them.""" + new_chunks = self._chunk_text(self.content) + self.chunks.extend(new_chunks) + self.save_documents(metadata=self.metadata) + + def _chunk_text(self, text: str) -> List[str]: + """Utility method to split text into chunks.""" + return [ + text[i : i + self.chunk_size] + for i in range(0, len(text), self.chunk_size - self.chunk_overlap) + ] diff --git a/src/crewai/knowledge/source/text_file_knowledge_source.py b/src/crewai/knowledge/source/text_file_knowledge_source.py new file mode 100644 index 000000000..640db4ef9 --- /dev/null +++ b/src/crewai/knowledge/source/text_file_knowledge_source.py @@ -0,0 +1,35 @@ +from typing import Dict, List +from pathlib import Path + +from crewai.knowledge.source.base_file_knowledge_source import BaseFileKnowledgeSource + + +class TextFileKnowledgeSource(BaseFileKnowledgeSource): + """A knowledge source that stores and queries text file content using embeddings.""" + + def load_content(self) -> Dict[Path, str]: + """Load and preprocess text file content.""" + super().load_content() + paths = [self.file_path] if isinstance(self.file_path, Path) else self.file_path + content = {} + for path in paths: + with path.open("r", encoding="utf-8") as f: + content[path] = f.read() # type: ignore + return content + + def add(self) -> None: + """ + Add text file content to the knowledge source, chunk it, compute embeddings, + and save the embeddings. + """ + for _, text in self.content.items(): + new_chunks = self._chunk_text(text) + self.chunks.extend(new_chunks) + self.save_documents(metadata=self.metadata) + + def _chunk_text(self, text: str) -> List[str]: + """Utility method to split text into chunks.""" + return [ + text[i : i + self.chunk_size] + for i in range(0, len(text), self.chunk_size - self.chunk_overlap) + ] diff --git a/src/crewai/knowledge/storage/__init__.py b/src/crewai/knowledge/storage/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/src/crewai/knowledge/storage/base_knowledge_storage.py b/src/crewai/knowledge/storage/base_knowledge_storage.py new file mode 100644 index 000000000..78d370e04 --- /dev/null +++ b/src/crewai/knowledge/storage/base_knowledge_storage.py @@ -0,0 +1,29 @@ +from abc import ABC, abstractmethod +from typing import Dict, Any, List, Optional + + +class BaseKnowledgeStorage(ABC): + """Abstract base class for knowledge storage implementations.""" + + @abstractmethod + def search( + self, + query: List[str], + limit: int = 3, + filter: Optional[dict] = None, + score_threshold: float = 0.35, + ) -> List[Dict[str, Any]]: + """Search for documents in the knowledge base.""" + pass + + @abstractmethod + def save( + self, documents: List[str], metadata: Dict[str, Any] | List[Dict[str, Any]] + ) -> None: + """Save documents to the knowledge base.""" + pass + + @abstractmethod + def reset(self) -> None: + """Reset the knowledge base.""" + pass diff --git a/src/crewai/knowledge/storage/knowledge_storage.py b/src/crewai/knowledge/storage/knowledge_storage.py new file mode 100644 index 000000000..b3d5ba750 --- /dev/null +++ b/src/crewai/knowledge/storage/knowledge_storage.py @@ -0,0 +1,132 @@ +import contextlib +import io +import logging +import chromadb +import os +from crewai.utilities.paths import db_storage_path +from typing import Optional, List +from typing import Dict, Any +from crewai.utilities import EmbeddingConfigurator +from crewai.knowledge.storage.base_knowledge_storage import BaseKnowledgeStorage +import hashlib + + +@contextlib.contextmanager +def suppress_logging( + logger_name="chromadb.segment.impl.vector.local_persistent_hnsw", + level=logging.ERROR, +): + logger = logging.getLogger(logger_name) + original_level = logger.getEffectiveLevel() + logger.setLevel(level) + with ( + contextlib.redirect_stdout(io.StringIO()), + contextlib.redirect_stderr(io.StringIO()), + contextlib.suppress(UserWarning), + ): + yield + logger.setLevel(original_level) + + +class KnowledgeStorage(BaseKnowledgeStorage): + """ + Extends Storage to handle embeddings for memory entries, improving + search efficiency. + """ + + collection: Optional[chromadb.Collection] = None + + def __init__(self, embedder_config: Optional[Dict[str, Any]] = None): + self._initialize_app(embedder_config or {}) + + def search( + self, + query: List[str], + limit: int = 3, + filter: Optional[dict] = None, + score_threshold: float = 0.35, + ) -> List[Dict[str, Any]]: + with suppress_logging(): + if self.collection: + fetched = self.collection.query( + query_texts=query, + n_results=limit, + where=filter, + ) + results = [] + for i in range(len(fetched["ids"][0])): # type: ignore + result = { + "id": fetched["ids"][0][i], # type: ignore + "metadata": fetched["metadatas"][0][i], # type: ignore + "context": fetched["documents"][0][i], # type: ignore + "score": fetched["distances"][0][i], # type: ignore + } + if result["score"] >= score_threshold: # type: ignore + results.append(result) + return results + else: + raise Exception("Collection not initialized") + + def _initialize_app(self, embedder_config: Optional[Dict[str, Any]] = None): + import chromadb + from chromadb.config import Settings + + self._set_embedder_config(embedder_config) + + chroma_client = chromadb.PersistentClient( + path=f"{db_storage_path()}/knowledge", + settings=Settings(allow_reset=True), + ) + + self.app = chroma_client + + try: + self.collection = self.app.get_or_create_collection(name="knowledge") + except Exception: + raise Exception("Failed to create or get collection") + + def reset(self): + if self.app: + self.app.reset() + + def save( + self, documents: List[str], metadata: Dict[str, Any] | List[Dict[str, Any]] + ): + if self.collection: + metadatas = [metadata] if isinstance(metadata, dict) else metadata + + ids = [ + hashlib.sha256(doc.encode("utf-8")).hexdigest() for doc in documents + ] + + self.collection.upsert( + documents=documents, + metadatas=metadatas, + ids=ids, + ) + else: + raise Exception("Collection not initialized") + + def _create_default_embedding_function(self): + from chromadb.utils.embedding_functions.openai_embedding_function import ( + OpenAIEmbeddingFunction, + ) + + return OpenAIEmbeddingFunction( + api_key=os.getenv("OPENAI_API_KEY"), model_name="text-embedding-3-small" + ) + + def _set_embedder_config( + self, embedder_config: Optional[Dict[str, Any]] = None + ) -> None: + """Set the embedding configuration for the knowledge storage. + + Args: + embedder_config (Optional[Dict[str, Any]]): Configuration dictionary for the embedder. + If None or empty, defaults to the default embedding function. + """ + self.embedder_config = ( + EmbeddingConfigurator().configure_embedder(embedder_config) + if embedder_config + else self._create_default_embedding_function() + ) diff --git a/src/crewai/memory/storage/rag_storage.py b/src/crewai/memory/storage/rag_storage.py index 7af5fb554..4023cf558 100644 --- a/src/crewai/memory/storage/rag_storage.py +++ b/src/crewai/memory/storage/rag_storage.py @@ -4,13 +4,12 @@ import logging import os import shutil import uuid -from typing import Any, Dict, List, Optional, cast -from chromadb import Documents, EmbeddingFunction, Embeddings +from typing import Any, Dict, List, Optional from chromadb.api import ClientAPI -from chromadb.api.types import validate_embedding_function from crewai.memory.storage.base_rag_storage import BaseRAGStorage from crewai.utilities.paths import db_storage_path +from crewai.utilities import EmbeddingConfigurator @contextlib.contextmanager @@ -51,133 +50,8 @@ class RAGStorage(BaseRAGStorage): self._initialize_app() def _set_embedder_config(self): - if self.embedder_config is None: - self.embedder_config = self._create_default_embedding_function() - - if isinstance(self.embedder_config, dict): - provider = self.embedder_config.get("provider") - config = self.embedder_config.get("config", {}) - model_name = config.get("model") - if provider == "openai": - from chromadb.utils.embedding_functions.openai_embedding_function import ( - OpenAIEmbeddingFunction, - ) - - self.embedder_config = OpenAIEmbeddingFunction( - api_key=config.get("api_key") or os.getenv("OPENAI_API_KEY"), - model_name=model_name, - ) - elif provider == "azure": - from chromadb.utils.embedding_functions.openai_embedding_function import ( - OpenAIEmbeddingFunction, - ) - - self.embedder_config = OpenAIEmbeddingFunction( - api_key=config.get("api_key"), - api_base=config.get("api_base"), - api_type=config.get("api_type", "azure"), - api_version=config.get("api_version"), - model_name=model_name, - ) - elif provider == "ollama": - from chromadb.utils.embedding_functions.ollama_embedding_function import ( - OllamaEmbeddingFunction, - ) - - self.embedder_config = OllamaEmbeddingFunction( - url=config.get("url", "http://localhost:11434/api/embeddings"), - model_name=model_name, - ) - elif provider == "vertexai": - from chromadb.utils.embedding_functions.google_embedding_function import ( - GoogleVertexEmbeddingFunction, - ) - - self.embedder_config = GoogleVertexEmbeddingFunction( - model_name=model_name, - api_key=config.get("api_key"), - ) - elif provider == "google": - from chromadb.utils.embedding_functions.google_embedding_function import ( - GoogleGenerativeAiEmbeddingFunction, - ) - - self.embedder_config = GoogleGenerativeAiEmbeddingFunction( - model_name=model_name, - api_key=config.get("api_key"), - ) - elif provider == "cohere": - from chromadb.utils.embedding_functions.cohere_embedding_function import ( - CohereEmbeddingFunction, - ) - - self.embedder_config = CohereEmbeddingFunction( - model_name=model_name, - api_key=config.get("api_key"), - ) - elif provider == "bedrock": - from chromadb.utils.embedding_functions.amazon_bedrock_embedding_function import ( - AmazonBedrockEmbeddingFunction, - ) - - self.embedder_config = AmazonBedrockEmbeddingFunction( - session=config.get("session"), - ) - elif provider == "huggingface": - from chromadb.utils.embedding_functions.huggingface_embedding_function import ( - HuggingFaceEmbeddingServer, - ) - - self.embedder_config = HuggingFaceEmbeddingServer( - url=config.get("api_url"), - ) - elif provider == "watson": - try: - import ibm_watsonx_ai.foundation_models as watson_models - from ibm_watsonx_ai import Credentials - from ibm_watsonx_ai.metanames import ( - EmbedTextParamsMetaNames as EmbedParams, - ) - except ImportError as e: - raise ImportError( - "IBM Watson dependencies are not installed. Please install them to use Watson embedding." - ) from e - - class WatsonEmbeddingFunction(EmbeddingFunction): - def __call__(self, input: Documents) -> Embeddings: - if isinstance(input, str): - input = [input] - - embed_params = { - EmbedParams.TRUNCATE_INPUT_TOKENS: 3, - EmbedParams.RETURN_OPTIONS: {"input_text": True}, - } - - embedding = watson_models.Embeddings( - model_id=config.get("model"), - params=embed_params, - credentials=Credentials( - api_key=config.get("api_key"), url=config.get("api_url") - ), - project_id=config.get("project_id"), - ) - - try: - embeddings = embedding.embed_documents(input) - return cast(Embeddings, embeddings) - - except Exception as e: - print("Error during Watson embedding:", e) - raise e - - self.embedder_config = WatsonEmbeddingFunction() - else: - raise Exception( - f"Unsupported embedding provider: {provider}, supported providers: [openai, azure, ollama, vertexai, google, cohere, huggingface, watson]" - ) - else: - validate_embedding_function(self.embedder_config) - self.embedder_config = self.embedder_config + configurator = EmbeddingConfigurator() + self.embedder_config = configurator.configure_embedder(self.embedder_config) def _initialize_app(self): import chromadb diff --git a/src/crewai/utilities/__init__.py b/src/crewai/utilities/__init__.py index 26d35a6cc..dd6d9fa44 100644 --- a/src/crewai/utilities/__init__.py +++ b/src/crewai/utilities/__init__.py @@ -10,6 +10,7 @@ from .rpm_controller import RPMController from .exceptions.context_window_exceeding_exception import ( LLMContextLengthExceededException, ) +from .embedding_configurator import EmbeddingConfigurator __all__ = [ "Converter", @@ -23,4 +24,5 @@ __all__ = [ "RPMController", "YamlParser", "LLMContextLengthExceededException", + "EmbeddingConfigurator", ] diff --git a/src/crewai/utilities/constants.py b/src/crewai/utilities/constants.py index 22cc2ffbe..59f789913 100644 --- a/src/crewai/utilities/constants.py +++ b/src/crewai/utilities/constants.py @@ -1,2 +1,3 @@ TRAINING_DATA_FILE = "training_data.pkl" TRAINED_AGENTS_DATA_FILE = "trained_agents_data.pkl" +DEFAULT_SCORE_THRESHOLD = 0.35 diff --git a/src/crewai/utilities/embedding_configurator.py b/src/crewai/utilities/embedding_configurator.py new file mode 100644 index 000000000..f0f77ffca --- /dev/null +++ b/src/crewai/utilities/embedding_configurator.py @@ -0,0 +1,183 @@ +import os +from typing import Any, Dict, cast +from chromadb import EmbeddingFunction, Documents, Embeddings +from chromadb.api.types import validate_embedding_function + + +class EmbeddingConfigurator: + def __init__(self): + self.embedding_functions = { + "openai": self._configure_openai, + "azure": self._configure_azure, + "ollama": self._configure_ollama, + "vertexai": self._configure_vertexai, + "google": self._configure_google, + "cohere": self._configure_cohere, + "bedrock": self._configure_bedrock, + "huggingface": self._configure_huggingface, + "watson": self._configure_watson, + } + + def configure_embedder( + self, + embedder_config: Dict[str, Any] | None = None, + ) -> EmbeddingFunction: + """Configures and returns an embedding function based on the provided config.""" + if embedder_config is None: + return self._create_default_embedding_function() + + provider = embedder_config.get("provider") + config = embedder_config.get("config", {}) + model_name = config.get("model") + + if isinstance(provider, EmbeddingFunction): + try: + validate_embedding_function(provider) + return provider + except Exception as e: + raise ValueError(f"Invalid custom embedding function: {str(e)}") + + if provider not in self.embedding_functions: + raise Exception( + f"Unsupported embedding provider: {provider}, supported providers: {list(self.embedding_functions.keys())}" + ) + + return self.embedding_functions[provider](config, model_name) + + @staticmethod + def _create_default_embedding_function(): + from chromadb.utils.embedding_functions.openai_embedding_function import ( + OpenAIEmbeddingFunction, + ) + + return OpenAIEmbeddingFunction( + api_key=os.getenv("OPENAI_API_KEY"), model_name="text-embedding-3-small" + ) + + @staticmethod + def _configure_openai(config, model_name): + from chromadb.utils.embedding_functions.openai_embedding_function import ( + OpenAIEmbeddingFunction, + ) + + return OpenAIEmbeddingFunction( + api_key=config.get("api_key") or os.getenv("OPENAI_API_KEY"), + model_name=model_name, + ) + + @staticmethod + def _configure_azure(config, model_name): + from chromadb.utils.embedding_functions.openai_embedding_function import ( + OpenAIEmbeddingFunction, + ) + + return OpenAIEmbeddingFunction( + api_key=config.get("api_key"), + api_base=config.get("api_base"), + api_type=config.get("api_type", "azure"), + api_version=config.get("api_version"), + model_name=model_name, + ) + + @staticmethod + def _configure_ollama(config, model_name): + from chromadb.utils.embedding_functions.ollama_embedding_function import ( + OllamaEmbeddingFunction, + ) + + return OllamaEmbeddingFunction( + url=config.get("url", "http://localhost:11434/api/embeddings"), + model_name=model_name, + ) + + @staticmethod + def _configure_vertexai(config, model_name): + from chromadb.utils.embedding_functions.google_embedding_function import ( + GoogleVertexEmbeddingFunction, + ) + + return GoogleVertexEmbeddingFunction( + model_name=model_name, + api_key=config.get("api_key"), + ) + + @staticmethod + def _configure_google(config, model_name): + from chromadb.utils.embedding_functions.google_embedding_function import ( + GoogleGenerativeAiEmbeddingFunction, + ) + + return GoogleGenerativeAiEmbeddingFunction( + model_name=model_name, + api_key=config.get("api_key"), + ) + + @staticmethod + def _configure_cohere(config, model_name): + from chromadb.utils.embedding_functions.cohere_embedding_function import ( + CohereEmbeddingFunction, + ) + + return CohereEmbeddingFunction( + model_name=model_name, + api_key=config.get("api_key"), + ) + + @staticmethod + def _configure_bedrock(config, model_name): + from chromadb.utils.embedding_functions.amazon_bedrock_embedding_function import ( + AmazonBedrockEmbeddingFunction, + ) + + return AmazonBedrockEmbeddingFunction( + session=config.get("session"), + ) + + @staticmethod + def _configure_huggingface(config, model_name): + from chromadb.utils.embedding_functions.huggingface_embedding_function import ( + HuggingFaceEmbeddingServer, + ) + + return HuggingFaceEmbeddingServer( + url=config.get("api_url"), + ) + + @staticmethod + def _configure_watson(config, model_name): + try: + import ibm_watsonx_ai.foundation_models as watson_models + from ibm_watsonx_ai import Credentials + from ibm_watsonx_ai.metanames import EmbedTextParamsMetaNames as EmbedParams + except ImportError as e: + raise ImportError( + "IBM Watson dependencies are not installed. Please install them to use Watson embedding." + ) from e + + class WatsonEmbeddingFunction(EmbeddingFunction): + def __call__(self, input: Documents) -> Embeddings: + if isinstance(input, str): + input = [input] + + embed_params = { + EmbedParams.TRUNCATE_INPUT_TOKENS: 3, + EmbedParams.RETURN_OPTIONS: {"input_text": True}, + } + + embedding = watson_models.Embeddings( + model_id=config.get("model"), + params=embed_params, + credentials=Credentials( + api_key=config.get("api_key"), url=config.get("api_url") + ), + project_id=config.get("project_id"), + ) + + try: + embeddings = embedding.embed_documents(input) + return cast(Embeddings, embeddings) + except Exception as e: + print("Error during Watson embedding:", e) + raise e + + return WatsonEmbeddingFunction() diff --git a/tests/agent_test.py b/tests/agent_test.py index c4094d15c..fb6ab22b8 100644 --- a/tests/agent_test.py +++ b/tests/agent_test.py @@ -10,10 +10,11 @@ from crewai import Agent, Crew, Task from crewai.agents.cache import CacheHandler from crewai.agents.crew_agent_executor import CrewAgentExecutor from crewai.agents.parser import AgentAction, CrewAgentParser, OutputParserException +from crewai.knowledge.source.string_knowledge_source import StringKnowledgeSource from crewai.llm import LLM +from crewai.tools import tool from crewai.tools.tool_calling import InstructorToolCalling from crewai.tools.tool_usage import ToolUsage -from crewai.tools import tool from crewai.tools.tool_usage_events import ToolUsageFinished from crewai.utilities import RPMController from crewai.utilities.events import Emitter @@ -1574,3 +1575,42 @@ def test_agent_execute_task_with_ollama(): result = agent.execute_task(task) assert len(result.split(".")) == 2 assert "AI" in result or "artificial intelligence" in result.lower() + + +@pytest.mark.vcr(filter_headers=["authorization"]) +def test_agent_with_knowledge_sources(): + # Create a knowledge source with some content + content = "Brandon's favorite color is blue and he likes Mexican food." + string_source = StringKnowledgeSource( + content=content, metadata={"preference": "personal"} + ) + + + with patch('crewai.knowledge.storage.knowledge_storage.KnowledgeStorage') as MockKnowledge: + mock_knowledge_instance = MockKnowledge.return_value + mock_knowledge_instance.sources = [string_source] + mock_knowledge_instance.query.return_value = [{ + "content": content, + "metadata": {"preference": "personal"} + }] + + agent = Agent( + role="Information Agent", + goal="Provide information based on knowledge sources", + backstory="You have access to specific knowledge sources.", + llm=LLM(model="gpt-4o-mini"), + ) + + # Create a task that requires the agent to use the knowledge + task = Task( + description="What is Brandon's favorite color?", + expected_output="Brandon's favorite color.", + agent=agent, + ) + + crew = Crew(agents=[agent], tasks=[task]) + result = crew.kickoff() + + # Assert that the agent provides the correct information + assert "blue" in result.raw.lower() + diff --git a/tests/cassettes/test_agent_with_knowledge_sources.yaml b/tests/cassettes/test_agent_with_knowledge_sources.yaml new file mode 100644 index 000000000..d483a19d2 --- /dev/null +++ b/tests/cassettes/test_agent_with_knowledge_sources.yaml @@ -0,0 +1,115 @@ +interactions: +- request: + body: '{"messages": [{"role": "system", "content": "You are Information Agent. + You have access to specific knowledge sources.\nYour personal goal is: Provide + information based on knowledge sources\nTo give my best complete final answer + to the task use the exact following format:\n\nThought: I now can give a great + answer\nFinal Answer: Your final answer must be the great and the most complete + as possible, it must be outcome described.\n\nI MUST use these formats, my job + depends on it!"}, {"role": "user", "content": "\nCurrent Task: What is Brandon''s + favorite color?\n\nThis is the expect criteria for your final answer: Brandon''s + favorite color.\nyou MUST return the actual complete content as the final answer, + not a summary.\n\nBegin! This is VERY important to you, use the tools available + and give your best Final Answer, your job depends on it!\n\nThought:"}], "model": + "gpt-4o-mini", "stop": ["\nObservation:"], "stream": false}' + headers: + accept: + - application/json + accept-encoding: + - gzip, deflate + connection: + - keep-alive + content-length: + - '931' + content-type: + - application/json + host: + - api.openai.com + user-agent: + - OpenAI/Python 1.52.1 + x-stainless-arch: + - arm64 + x-stainless-async: + - 'false' + x-stainless-lang: + - python + x-stainless-os: + - MacOS + x-stainless-package-version: + - 1.52.1 + x-stainless-raw-response: + - 'true' + x-stainless-retry-count: + - '0' + x-stainless-runtime: + - CPython + x-stainless-runtime-version: + - 3.11.9 + method: POST + uri: https://api.openai.com/v1/chat/completions + response: + body: + string: !!binary | + H4sIAAAAAAAAA4xSQW7bMBC86xULXnqxAtmxI1e3FEWBtJekCXJpC4GmVhIdapcgqbhN4L8HlB1L + QVOgFwGa2RnOLPmcAAhdiQKEamVQnTXp5f3d9lsdbndh++C+757Or6/bm6uvn59WH/FGzKKCN1tU + 4VV1prizBoNmOtDKoQwYXef5+SK7WK3ny4HouEITZY0N6ZLTTpNOF9limWZ5Ol8f1S1rhV4U8CMB + AHgevjEnVfhbFJDNXpEOvZcNiuI0BCAcm4gI6b32QVIQs5FUTAFpiH4FxDtQkqDRjwgSmhgbJPkd + OoCf9EWTNHA5/BfwyUmqmD54qOUjOx0QFBt2oD1sTI9n02Mc1r2XsSr1xhzx/Sm34cY63vgjf8Jr + Tdq3pUPpmWJGH9iKgd0nAL+G/fRvKgvruLOhDPyAFA3nF/nBT4zXMmHXRzJwkGaKr2bv+JUVBqmN + n2xYKKlarEbpeB2yrzRPiGTS+u8073kfmmtq/sd+JJRCG7AqrcNKq7eNxzGH8dX+a+y05SGw8H98 + wK6sNTXorNOHN1PbMsuz1aZe5yoTyT55AQAA//8DAPaYLdRBAwAA + headers: + CF-Cache-Status: + - DYNAMIC + CF-RAY: + - 8e54a2a7d81467f7-SJC + Connection: + - keep-alive + Content-Encoding: + - gzip + Content-Type: + - application/json + Date: + - Wed, 20 Nov 2024 01:23:34 GMT + Server: + - cloudflare + Set-Cookie: + - __cf_bm=DoHo1Z11nN9bxkwZmJGnaxRhyrWE0UfyimYuUVRU6A4-1732065814-1.0.1.1-JVRvFrIJLHEq9OaFQS0qcgYcawE7t2XQ4Tpqd58n2Yfx3mvEqD34MJmooi1LtvdvjB2J8x1Rs.rCdXD.msLlKw; + path=/; expires=Wed, 20-Nov-24 01:53:34 GMT; domain=.api.openai.com; HttpOnly; + Secure; SameSite=None + - _cfuvid=n3RrNhFMqC3HtJ7n3e3agyxnM1YOQ6eKESz_eeXLtZA-1732065814630-0.0.1.1-604800000; + path=/; domain=.api.openai.com; HttpOnly; Secure; SameSite=None + Transfer-Encoding: + - chunked + X-Content-Type-Options: + - nosniff + access-control-expose-headers: + - X-Request-ID + alt-svc: + - h3=":443"; ma=86400 + openai-organization: + - crewai-iuxna1 + openai-processing-ms: + - '344' + openai-version: + - '2020-10-01' + strict-transport-security: + - max-age=31536000; includeSubDomains; preload + x-ratelimit-limit-requests: + - '30000' + x-ratelimit-limit-tokens: + - '150000000' + x-ratelimit-remaining-requests: + - '29999' + x-ratelimit-remaining-tokens: + - '149999790' + x-ratelimit-reset-requests: + - 2ms + x-ratelimit-reset-tokens: + - 0s + x-request-id: + - req_8f1622677c64913753a595f679596614 + status: + code: 200 + message: OK +version: 1 diff --git a/tests/cassettes/test_kickoff_for_each_error_handling.yaml b/tests/cassettes/test_kickoff_for_each_error_handling.yaml new file mode 100644 index 000000000..6a479332d --- /dev/null +++ b/tests/cassettes/test_kickoff_for_each_error_handling.yaml @@ -0,0 +1,232 @@ +interactions: +- request: + body: !!binary | + Cv1YCiQKIgoMc2VydmljZS5uYW1lEhIKEGNyZXdBSS10ZWxlbWV0cnkS1FgKEgoQY3Jld2FpLnRl + bGVtZXRyeRLADQoQ5TzgW9QzcBbzMl1hJozLcxIIl3adf7U81wwqDENyZXcgQ3JlYXRlZDABOaAJ + txhffgkYQfiVuRhffgkYShoKDmNyZXdhaV92ZXJzaW9uEggKBjAuODAuMEoaCg5weXRob25fdmVy + c2lvbhIICgYzLjEyLjVKLgoIY3Jld19rZXkSIgogM2Y4ZDVjM2FiODgyZDY4NjlkOTNjYjgxZjBl + MmVkNGFKMQoHY3Jld19pZBImCiRjYjRiY2Q1Zi0xYWJkLTQyYmYtOGQ1OC02ZmEzMDU3ZDFjOTZK + HAoMY3Jld19wcm9jZXNzEgwKCnNlcXVlbnRpYWxKEQoLY3Jld19tZW1vcnkSAhAAShoKFGNyZXdf + bnVtYmVyX29mX3Rhc2tzEgIYA0obChVjcmV3X251bWJlcl9vZl9hZ2VudHMSAhgCSpIFCgtjcmV3 + X2FnZW50cxKCBQr/BFt7ImtleSI6ICI4YmQyMTM5YjU5NzUxODE1MDZlNDFmZDljNDU2M2Q3NSIs + ICJpZCI6ICI1ZThjNTM1MS1jNWVlLTRhZGUtODY5MC1kM2RhOWI1NzI5YzciLCAicm9sZSI6ICJS + ZXNlYXJjaGVyIiwgInZlcmJvc2U/IjogZmFsc2UsICJtYXhfaXRlciI6IDIwLCAibWF4X3JwbSI6 + IG51bGwsICJmdW5jdGlvbl9jYWxsaW5nX2xsbSI6ICIiLCAibGxtIjogImdwdC00by1taW5pIiwg + ImRlbGVnYXRpb25fZW5hYmxlZD8iOiBmYWxzZSwgImFsbG93X2NvZGVfZXhlY3V0aW9uPyI6IGZh + bHNlLCAibWF4X3JldHJ5X2xpbWl0IjogMiwgInRvb2xzX25hbWVzIjogW119LCB7ImtleSI6ICI5 + YTUwMTVlZjQ4OTVkYzYyNzhkNTQ4MThiYTQ0NmFmNyIsICJpZCI6ICJhMTcwODczOC0yYWE2LTRk + ZmYtODFlNy00OGFkMDNjNWFjY2QiLCAicm9sZSI6ICJTZW5pb3IgV3JpdGVyIiwgInZlcmJvc2U/ + IjogZmFsc2UsICJtYXhfaXRlciI6IDIwLCAibWF4X3JwbSI6IG51bGwsICJmdW5jdGlvbl9jYWxs + aW5nX2xsbSI6ICIiLCAibGxtIjogImdwdC00by1taW5pIiwgImRlbGVnYXRpb25fZW5hYmxlZD8i + OiBmYWxzZSwgImFsbG93X2NvZGVfZXhlY3V0aW9uPyI6IGZhbHNlLCAibWF4X3JldHJ5X2xpbWl0 + IjogMiwgInRvb2xzX25hbWVzIjogW119XUrbBQoKY3Jld190YXNrcxLMBQrJBVt7ImtleSI6ICI2 + Nzg0OWZmNzE3ZGJhZGFiYTFiOTVkNWYyZGZjZWVhMSIsICJpZCI6ICIyNzkxNTMxMy0wNDBhLTRk + ZWItOTVkMy1mNWVmMzg2Mjk3NTEiLCAiYXN5bmNfZXhlY3V0aW9uPyI6IHRydWUsICJodW1hbl9p + bnB1dD8iOiBmYWxzZSwgImFnZW50X3JvbGUiOiAiUmVzZWFyY2hlciIsICJhZ2VudF9rZXkiOiAi + OGJkMjEzOWI1OTc1MTgxNTA2ZTQxZmQ5YzQ1NjNkNzUiLCAidG9vbHNfbmFtZXMiOiBbXX0sIHsi + a2V5IjogImZjNTZkZWEzOGM5OTc0YjZmNTVhMmUyOGMxNDk5ODg2IiwgImlkIjogIjc3NzQ3MmVl + LWYzNzAtNDQyZS05NWMyLWVlMGVkYzZiMTgyZiIsICJhc3luY19leGVjdXRpb24/IjogZmFsc2Us + ICJodW1hbl9pbnB1dD8iOiBmYWxzZSwgImFnZW50X3JvbGUiOiAiUmVzZWFyY2hlciIsICJhZ2Vu + dF9rZXkiOiAiOGJkMjEzOWI1OTc1MTgxNTA2ZTQxZmQ5YzQ1NjNkNzUiLCAidG9vbHNfbmFtZXMi + OiBbXX0sIHsia2V5IjogIjk0YTgyNmMxOTMwNTU5Njg2YmFmYjQwOWVlODM4NzZmIiwgImlkIjog + ImM4OWEzODA2LTg5MDItNGQ2My1iYzA0LTdjMzRhZTJmM2UxNyIsICJhc3luY19leGVjdXRpb24/ + IjogZmFsc2UsICJodW1hbl9pbnB1dD8iOiBmYWxzZSwgImFnZW50X3JvbGUiOiAiU2VuaW9yIFdy + aXRlciIsICJhZ2VudF9rZXkiOiAiOWE1MDE1ZWY0ODk1ZGM2Mjc4ZDU0ODE4YmE0NDZhZjciLCAi + dG9vbHNfbmFtZXMiOiBbXX1degIYAYUBAAEAABKOAgoQSqupTllrk2mxgu2AqenZUhIIspWxig2+ + 1M0qDFRhc2sgQ3JlYXRlZDABOcCj1BhffgkYQfhq1RhffgkYSi4KCGNyZXdfa2V5EiIKIDNmOGQ1 + YzNhYjg4MmQ2ODY5ZDkzY2I4MWYwZTJlZDRhSjEKB2NyZXdfaWQSJgokY2I0YmNkNWYtMWFiZC00 + MmJmLThkNTgtNmZhMzA1N2QxYzk2Si4KCHRhc2tfa2V5EiIKIDY3ODQ5ZmY3MTdkYmFkYWJhMWI5 + NWQ1ZjJkZmNlZWExSjEKB3Rhc2tfaWQSJgokMjc5MTUzMTMtMDQwYS00ZGViLTk1ZDMtZjVlZjM4 + NjI5NzUxegIYAYUBAAEAABKOAgoQ3dJesXQA5ISCqVgmwvBMgRIIdrWBiVQuihcqDFRhc2sgQ3Jl + YXRlZDABOdjAch1ffgkYQVh8cx1ffgkYSi4KCGNyZXdfa2V5EiIKIDNmOGQ1YzNhYjg4MmQ2ODY5 + ZDkzY2I4MWYwZTJlZDRhSjEKB2NyZXdfaWQSJgokY2I0YmNkNWYtMWFiZC00MmJmLThkNTgtNmZh + MzA1N2QxYzk2Si4KCHRhc2tfa2V5EiIKIGZjNTZkZWEzOGM5OTc0YjZmNTVhMmUyOGMxNDk5ODg2 + SjEKB3Rhc2tfaWQSJgokNzc3NDcyZWUtZjM3MC00NDJlLTk1YzItZWUwZWRjNmIxODJmegIYAYUB + AAEAABKOAgoQCBmV+4VbArZNiL5MaefbahII1fRxaC46KKgqDFRhc2sgQ3JlYXRlZDABOaDs4SNf + fgkYQai74iNffgkYSi4KCGNyZXdfa2V5EiIKIDNmOGQ1YzNhYjg4MmQ2ODY5ZDkzY2I4MWYwZTJl + ZDRhSjEKB2NyZXdfaWQSJgokY2I0YmNkNWYtMWFiZC00MmJmLThkNTgtNmZhMzA1N2QxYzk2Si4K + CHRhc2tfa2V5EiIKIDk0YTgyNmMxOTMwNTU5Njg2YmFmYjQwOWVlODM4NzZmSjEKB3Rhc2tfaWQS + JgokYzg5YTM4MDYtODkwMi00ZDYzLWJjMDQtN2MzNGFlMmYzZTE3egIYAYUBAAEAABKiBwoQhITI + U8q3JLgneRv1MZQY8RIIF2CpEmiZsP4qDENyZXcgQ3JlYXRlZDABOZDBCytffgkYQTDFDStffgkY + ShoKDmNyZXdhaV92ZXJzaW9uEggKBjAuODAuMEoaCg5weXRob25fdmVyc2lvbhIICgYzLjEyLjVK + LgoIY3Jld19rZXkSIgogYTljYzVkNDMzOTViMjFiMTgxYzgwYmQ0MzUxY2NlYzhKMQoHY3Jld19p + ZBImCiQ2MTMwMWVmYS0yOGQ4LTQyNTItOWVjNi1iM2JmZDcyMWM0MzVKHAoMY3Jld19wcm9jZXNz + EgwKCnNlcXVlbnRpYWxKEQoLY3Jld19tZW1vcnkSAhAAShoKFGNyZXdfbnVtYmVyX29mX3Rhc2tz + EgIYAUobChVjcmV3X251bWJlcl9vZl9hZ2VudHMSAhgBStECCgtjcmV3X2FnZW50cxLBAgq+Alt7 + ImtleSI6ICI4YmQyMTM5YjU5NzUxODE1MDZlNDFmZDljNDU2M2Q3NSIsICJpZCI6ICI3NWRjOTUw + OS02MjQ4LTQ0YWQtYTExZC1iZjdlZWVhOWI0NTQiLCAicm9sZSI6ICJSZXNlYXJjaGVyIiwgInZl + cmJvc2U/IjogZmFsc2UsICJtYXhfaXRlciI6IDIwLCAibWF4X3JwbSI6IG51bGwsICJmdW5jdGlv + bl9jYWxsaW5nX2xsbSI6ICIiLCAibGxtIjogImdwdC00by1taW5pIiwgImRlbGVnYXRpb25fZW5h + YmxlZD8iOiBmYWxzZSwgImFsbG93X2NvZGVfZXhlY3V0aW9uPyI6IGZhbHNlLCAibWF4X3JldHJ5 + X2xpbWl0IjogMiwgInRvb2xzX25hbWVzIjogW119XUr+AQoKY3Jld190YXNrcxLvAQrsAVt7Imtl + eSI6ICJlOWU2YjcyYWFjMzI2NDU5ZGQ3MDY4ZjBiMTcxN2MxYyIsICJpZCI6ICIxOTBlMGQ1Zi0y + NDg1LTQ3N2ItYWIxNC1kMTlmNDE5YTFlYjQiLCAiYXN5bmNfZXhlY3V0aW9uPyI6IHRydWUsICJo + dW1hbl9pbnB1dD8iOiBmYWxzZSwgImFnZW50X3JvbGUiOiAiUmVzZWFyY2hlciIsICJhZ2VudF9r + ZXkiOiAiOGJkMjEzOWI1OTc1MTgxNTA2ZTQxZmQ5YzQ1NjNkNzUiLCAidG9vbHNfbmFtZXMiOiBb + XX1degIYAYUBAAEAABKOAgoQxgDNe1lQGKnixKPk3O1TDBIISyqKkjcA7OYqDFRhc2sgQ3JlYXRl + ZDABOfCYJCtffgkYQZAlJStffgkYSi4KCGNyZXdfa2V5EiIKIGE5Y2M1ZDQzMzk1YjIxYjE4MWM4 + MGJkNDM1MWNjZWM4SjEKB2NyZXdfaWQSJgokNjEzMDFlZmEtMjhkOC00MjUyLTllYzYtYjNiZmQ3 + MjFjNDM1Si4KCHRhc2tfa2V5EiIKIGU5ZTZiNzJhYWMzMjY0NTlkZDcwNjhmMGIxNzE3YzFjSjEK + B3Rhc2tfaWQSJgokMTkwZTBkNWYtMjQ4NS00NzdiLWFiMTQtZDE5ZjQxOWExZWI0egIYAYUBAAEA + ABK/DQoQwwR54Z8nOGgj2VSb63WRwhIIonLT+7Mwj00qDENyZXcgQ3JlYXRlZDABObDfvzhffgkY + QfBvwjhffgkYShoKDmNyZXdhaV92ZXJzaW9uEggKBjAuODAuMEoaCg5weXRob25fdmVyc2lvbhII + CgYzLjEyLjVKLgoIY3Jld19rZXkSIgogNjZhOTYwZGM2OWZmZjU3OGIyNmM2MWQ0ZjdjNWE5ZmVK + MQoHY3Jld19pZBImCiQxNThhMTkzOS01OWUzLTRlODgtYTRkYi04M2IzN2U5MjgxZWVKHAoMY3Jl + d19wcm9jZXNzEgwKCnNlcXVlbnRpYWxKEQoLY3Jld19tZW1vcnkSAhAAShoKFGNyZXdfbnVtYmVy + X29mX3Rhc2tzEgIYA0obChVjcmV3X251bWJlcl9vZl9hZ2VudHMSAhgCSpIFCgtjcmV3X2FnZW50 + cxKCBQr/BFt7ImtleSI6ICI4YmQyMTM5YjU5NzUxODE1MDZlNDFmZDljNDU2M2Q3NSIsICJpZCI6 + ICI1ZThjNTM1MS1jNWVlLTRhZGUtODY5MC1kM2RhOWI1NzI5YzciLCAicm9sZSI6ICJSZXNlYXJj + aGVyIiwgInZlcmJvc2U/IjogZmFsc2UsICJtYXhfaXRlciI6IDIwLCAibWF4X3JwbSI6IG51bGws + ICJmdW5jdGlvbl9jYWxsaW5nX2xsbSI6ICIiLCAibGxtIjogImdwdC00by1taW5pIiwgImRlbGVn + YXRpb25fZW5hYmxlZD8iOiBmYWxzZSwgImFsbG93X2NvZGVfZXhlY3V0aW9uPyI6IGZhbHNlLCAi + bWF4X3JldHJ5X2xpbWl0IjogMiwgInRvb2xzX25hbWVzIjogW119LCB7ImtleSI6ICI5YTUwMTVl + ZjQ4OTVkYzYyNzhkNTQ4MThiYTQ0NmFmNyIsICJpZCI6ICJhMTcwODczOC0yYWE2LTRkZmYtODFl + Ny00OGFkMDNjNWFjY2QiLCAicm9sZSI6ICJTZW5pb3IgV3JpdGVyIiwgInZlcmJvc2U/IjogZmFs + c2UsICJtYXhfaXRlciI6IDIwLCAibWF4X3JwbSI6IG51bGwsICJmdW5jdGlvbl9jYWxsaW5nX2xs + bSI6ICIiLCAibGxtIjogImdwdC00by1taW5pIiwgImRlbGVnYXRpb25fZW5hYmxlZD8iOiBmYWxz + ZSwgImFsbG93X2NvZGVfZXhlY3V0aW9uPyI6IGZhbHNlLCAibWF4X3JldHJ5X2xpbWl0IjogMiwg + InRvb2xzX25hbWVzIjogW119XUraBQoKY3Jld190YXNrcxLLBQrIBVt7ImtleSI6ICI5NDRhZWYw + YmFjODQwZjFjMjdiZDgzYTkzN2JjMzYxYiIsICJpZCI6ICIzN2FkNzI5MC04Yjg5LTRjNWEtYmNl + Zi03YzY0ZWJhMWM5NjciLCAiYXN5bmNfZXhlY3V0aW9uPyI6IHRydWUsICJodW1hbl9pbnB1dD8i + OiBmYWxzZSwgImFnZW50X3JvbGUiOiAiUmVzZWFyY2hlciIsICJhZ2VudF9rZXkiOiAiOGJkMjEz + OWI1OTc1MTgxNTA2ZTQxZmQ5YzQ1NjNkNzUiLCAidG9vbHNfbmFtZXMiOiBbXX0sIHsia2V5Ijog + ImZjNTZkZWEzOGM5OTc0YjZmNTVhMmUyOGMxNDk5ODg2IiwgImlkIjogIjZhMmViMGY2LTgwZTIt + NDkxOC05Zjk3LWVhNDY3OTNkMjI2YyIsICJhc3luY19leGVjdXRpb24/IjogdHJ1ZSwgImh1bWFu + X2lucHV0PyI6IGZhbHNlLCAiYWdlbnRfcm9sZSI6ICJSZXNlYXJjaGVyIiwgImFnZW50X2tleSI6 + ICI4YmQyMTM5YjU5NzUxODE1MDZlNDFmZDljNDU2M2Q3NSIsICJ0b29sc19uYW1lcyI6IFtdfSwg + eyJrZXkiOiAiOTRhODI2YzE5MzA1NTk2ODZiYWZiNDA5ZWU4Mzg3NmYiLCAiaWQiOiAiZGQ2Yzkz + NzAtOGYwNC00ZDFmLThjODMtMmFiM2IyYzIwYWI3IiwgImFzeW5jX2V4ZWN1dGlvbj8iOiBmYWxz + ZSwgImh1bWFuX2lucHV0PyI6IGZhbHNlLCAiYWdlbnRfcm9sZSI6ICJTZW5pb3IgV3JpdGVyIiwg + ImFnZW50X2tleSI6ICI5YTUwMTVlZjQ4OTVkYzYyNzhkNTQ4MThiYTQ0NmFmNyIsICJ0b29sc19u + YW1lcyI6IFtdfV16AhgBhQEAAQAAErMHChBV+1WNQzpVlY6l4C/mUgHzEgi3vWQXjOQJ5CoMQ3Jl + dyBDcmVhdGVkMAE5sH1kPF9+CRhBaH1mPF9+CRhKGgoOY3Jld2FpX3ZlcnNpb24SCAoGMC44MC4w + ShoKDnB5dGhvbl92ZXJzaW9uEggKBjMuMTIuNUouCghjcmV3X2tleRIiCiBlZTY3NDVkN2M4YWU4 + MmUwMGRmOTRkZTBmN2Y4NzExOEoxCgdjcmV3X2lkEiYKJDAwOThmODNmLTdkNTAtNGI2Mi1hYmIy + LTJlNTc0N2ZlMWE4OUocCgxjcmV3X3Byb2Nlc3MSDAoKc2VxdWVudGlhbEoRCgtjcmV3X21lbW9y + eRICEABKGgoUY3Jld19udW1iZXJfb2ZfdGFza3MSAhgBShsKFWNyZXdfbnVtYmVyX29mX2FnZW50 + cxICGAFK2QIKC2NyZXdfYWdlbnRzEskCCsYCW3sia2V5IjogImYzMzg2ZjZkOGRhNzVhYTQxNmE2 + ZTMxMDA1M2Y3Njk4IiwgImlkIjogIjEzODI4ZDViLWIyOWMtNDllMy05NWVhLTkyOGQ2ZmZhY2I0 + NSIsICJyb2xlIjogInt0b3BpY30gUmVzZWFyY2hlciIsICJ2ZXJib3NlPyI6IGZhbHNlLCAibWF4 + X2l0ZXIiOiAyMCwgIm1heF9ycG0iOiBudWxsLCAiZnVuY3Rpb25fY2FsbGluZ19sbG0iOiAiIiwg + ImxsbSI6ICJncHQtNG8tbWluaSIsICJkZWxlZ2F0aW9uX2VuYWJsZWQ/IjogZmFsc2UsICJhbGxv + d19jb2RlX2V4ZWN1dGlvbj8iOiBmYWxzZSwgIm1heF9yZXRyeV9saW1pdCI6IDIsICJ0b29sc19u + YW1lcyI6IFtdfV1KhwIKCmNyZXdfdGFza3MS+AEK9QFbeyJrZXkiOiAiMDZhNzMyMjBmNDE0OGE0 + YmJkNWJhY2IwZDBiNDRmY2UiLCAiaWQiOiAiNWM4MjM1ZmYtNWVjNy00NzFhLWI4NWEtNWFkZjk3 + YzJkYzI3IiwgImFzeW5jX2V4ZWN1dGlvbj8iOiBmYWxzZSwgImh1bWFuX2lucHV0PyI6IGZhbHNl + LCAiYWdlbnRfcm9sZSI6ICJ7dG9waWN9IFJlc2VhcmNoZXIiLCAiYWdlbnRfa2V5IjogImYzMzg2 + ZjZkOGRhNzVhYTQxNmE2ZTMxMDA1M2Y3Njk4IiwgInRvb2xzX25hbWVzIjogW119XXoCGAGFAQAB + AAASjgIKEHOZ/a+LQwTLSkMO0sPwg9gSCL1SwQw1+0iKKgxUYXNrIENyZWF0ZWQwATl4kX48X34J + GEFIFn88X34JGEouCghjcmV3X2tleRIiCiBlZTY3NDVkN2M4YWU4MmUwMGRmOTRkZTBmN2Y4NzEx + OEoxCgdjcmV3X2lkEiYKJDAwOThmODNmLTdkNTAtNGI2Mi1hYmIyLTJlNTc0N2ZlMWE4OUouCgh0 + YXNrX2tleRIiCiAwNmE3MzIyMGY0MTQ4YTRiYmQ1YmFjYjBkMGI0NGZjZUoxCgd0YXNrX2lkEiYK + JDVjODIzNWZmLTVlYzctNDcxYS1iODVhLTVhZGY5N2MyZGMyN3oCGAGFAQABAAASswcKEHZQCRd7 + z4ZBCh4+06qs1r4SCNzrNsw+dn2zKgxDcmV3IENyZWF0ZWQwATlgWdtDX34JGEGIcN1DX34JGEoa + Cg5jcmV3YWlfdmVyc2lvbhIICgYwLjgwLjBKGgoOcHl0aG9uX3ZlcnNpb24SCAoGMy4xMi41Si4K + CGNyZXdfa2V5EiIKIGVlNjc0NWQ3YzhhZTgyZTAwZGY5NGRlMGY3Zjg3MTE4SjEKB2NyZXdfaWQS + JgokZTgzMTdjNzEtNmZiZS00MjI5LWE3MzctYTkxM2I0ZmU0ZTU0ShwKDGNyZXdfcHJvY2VzcxIM + CgpzZXF1ZW50aWFsShEKC2NyZXdfbWVtb3J5EgIQAEoaChRjcmV3X251bWJlcl9vZl90YXNrcxIC + GAFKGwoVY3Jld19udW1iZXJfb2ZfYWdlbnRzEgIYAUrZAgoLY3Jld19hZ2VudHMSyQIKxgJbeyJr + ZXkiOiAiZjMzODZmNmQ4ZGE3NWFhNDE2YTZlMzEwMDUzZjc2OTgiLCAiaWQiOiAiNjAwMzU5OTYt + ZWU1ZS00YmZhLThmODctMGM1ZTY0OTBlMmE4IiwgInJvbGUiOiAie3RvcGljfSBSZXNlYXJjaGVy + IiwgInZlcmJvc2U/IjogZmFsc2UsICJtYXhfaXRlciI6IDIwLCAibWF4X3JwbSI6IG51bGwsICJm + dW5jdGlvbl9jYWxsaW5nX2xsbSI6ICIiLCAibGxtIjogImdwdC00by1taW5pIiwgImRlbGVnYXRp + b25fZW5hYmxlZD8iOiBmYWxzZSwgImFsbG93X2NvZGVfZXhlY3V0aW9uPyI6IGZhbHNlLCAibWF4 + X3JldHJ5X2xpbWl0IjogMiwgInRvb2xzX25hbWVzIjogW119XUqHAgoKY3Jld190YXNrcxL4AQr1 + AVt7ImtleSI6ICIwNmE3MzIyMGY0MTQ4YTRiYmQ1YmFjYjBkMGI0NGZjZSIsICJpZCI6ICI4MDc3 + MDhjNS0yN2RkLTQ4ZDEtYTU0ZC1lZTRkNTZmMzBiZTQiLCAiYXN5bmNfZXhlY3V0aW9uPyI6IGZh + bHNlLCAiaHVtYW5faW5wdXQ/IjogZmFsc2UsICJhZ2VudF9yb2xlIjogInt0b3BpY30gUmVzZWFy + Y2hlciIsICJhZ2VudF9rZXkiOiAiZjMzODZmNmQ4ZGE3NWFhNDE2YTZlMzEwMDUzZjc2OTgiLCAi + dG9vbHNfbmFtZXMiOiBbXX1degIYAYUBAAEAABKOAgoQtqHg5uy2kZsnJlJTYgmZoxIIlgUHkQ7m + LugqDFRhc2sgQ3JlYXRlZDABOTi470NffgkYQQg98ENffgkYSi4KCGNyZXdfa2V5EiIKIGVlNjc0 + NWQ3YzhhZTgyZTAwZGY5NGRlMGY3Zjg3MTE4SjEKB2NyZXdfaWQSJgokZTgzMTdjNzEtNmZiZS00 + MjI5LWE3MzctYTkxM2I0ZmU0ZTU0Si4KCHRhc2tfa2V5EiIKIDA2YTczMjIwZjQxNDhhNGJiZDVi + YWNiMGQwYjQ0ZmNlSjEKB3Rhc2tfaWQSJgokODA3NzA4YzUtMjdkZC00OGQxLWE1NGQtZWU0ZDU2 + ZjMwYmU0egIYAYUBAAEAABKzBwoQpfKhpM9cCoiT5Mun1aoNQhII4HhX0QHHc/0qDENyZXcgQ3Jl + YXRlZDABORiH20lffgkYQcix3UlffgkYShoKDmNyZXdhaV92ZXJzaW9uEggKBjAuODAuMEoaCg5w + eXRob25fdmVyc2lvbhIICgYzLjEyLjVKLgoIY3Jld19rZXkSIgogZWU2NzQ1ZDdjOGFlODJlMDBk + Zjk0ZGUwZjdmODcxMThKMQoHY3Jld19pZBImCiRmZDk2MmQwMi0wNGY0LTQ3NDUtODc5YS02NTFm + MzFmMmZhOTZKHAoMY3Jld19wcm9jZXNzEgwKCnNlcXVlbnRpYWxKEQoLY3Jld19tZW1vcnkSAhAA + ShoKFGNyZXdfbnVtYmVyX29mX3Rhc2tzEgIYAUobChVjcmV3X251bWJlcl9vZl9hZ2VudHMSAhgB + StkCCgtjcmV3X2FnZW50cxLJAgrGAlt7ImtleSI6ICJmMzM4NmY2ZDhkYTc1YWE0MTZhNmUzMTAw + NTNmNzY5OCIsICJpZCI6ICIzNmZhMTEyZS02ZDVlLTRhMzgtODk0Yy01M2M5YjAzNTI5ODUiLCAi + cm9sZSI6ICJ7dG9waWN9IFJlc2VhcmNoZXIiLCAidmVyYm9zZT8iOiBmYWxzZSwgIm1heF9pdGVy + IjogMjAsICJtYXhfcnBtIjogbnVsbCwgImZ1bmN0aW9uX2NhbGxpbmdfbGxtIjogIiIsICJsbG0i + OiAiZ3B0LTRvLW1pbmkiLCAiZGVsZWdhdGlvbl9lbmFibGVkPyI6IGZhbHNlLCAiYWxsb3dfY29k + ZV9leGVjdXRpb24/IjogZmFsc2UsICJtYXhfcmV0cnlfbGltaXQiOiAyLCAidG9vbHNfbmFtZXMi + OiBbXX1dSocCCgpjcmV3X3Rhc2tzEvgBCvUBW3sia2V5IjogIjA2YTczMjIwZjQxNDhhNGJiZDVi + YWNiMGQwYjQ0ZmNlIiwgImlkIjogIjY3NTE3ZjY1LThhYzMtNDIyZi1hMmJhLTM4NDcyZDRkYmZl + NSIsICJhc3luY19leGVjdXRpb24/IjogZmFsc2UsICJodW1hbl9pbnB1dD8iOiBmYWxzZSwgImFn + ZW50X3JvbGUiOiAie3RvcGljfSBSZXNlYXJjaGVyIiwgImFnZW50X2tleSI6ICJmMzM4NmY2ZDhk + YTc1YWE0MTZhNmUzMTAwNTNmNzY5OCIsICJ0b29sc19uYW1lcyI6IFtdfV16AhgBhQEAAQAAEo4C + ChAzGUzQMDZOgJ090im3887lEgik7+/nVnqntioMVGFzayBDcmVhdGVkMAE5UO7rSV9+CRhBWEDs + SV9+CRhKLgoIY3Jld19rZXkSIgogZWU2NzQ1ZDdjOGFlODJlMDBkZjk0ZGUwZjdmODcxMThKMQoH + Y3Jld19pZBImCiRmZDk2MmQwMi0wNGY0LTQ3NDUtODc5YS02NTFmMzFmMmZhOTZKLgoIdGFza19r + ZXkSIgogMDZhNzMyMjBmNDE0OGE0YmJkNWJhY2IwZDBiNDRmY2VKMQoHdGFza19pZBImCiQ2NzUx + N2Y2NS04YWMzLTQyMmYtYTJiYS0zODQ3MmQ0ZGJmZTV6AhgBhQEAAQAAErMHChCB1TPvVbWX62DF + 102NfOHLEghdZ/LjI40W8SoMQ3JldyBDcmVhdGVkMAE5sJDUT19+CRhBEHXWT19+CRhKGgoOY3Jl + d2FpX3ZlcnNpb24SCAoGMC44MC4wShoKDnB5dGhvbl92ZXJzaW9uEggKBjMuMTIuNUouCghjcmV3 + X2tleRIiCiBlZTY3NDVkN2M4YWU4MmUwMGRmOTRkZTBmN2Y4NzExOEoxCgdjcmV3X2lkEiYKJDUx + YmI1NGQ0LWM4MTAtNDA0Yy04MTQzLWVmNTgwMTlhN2Q2OEocCgxjcmV3X3Byb2Nlc3MSDAoKc2Vx + dWVudGlhbEoRCgtjcmV3X21lbW9yeRICEABKGgoUY3Jld19udW1iZXJfb2ZfdGFza3MSAhgBShsK + FWNyZXdfbnVtYmVyX29mX2FnZW50cxICGAFK2QIKC2NyZXdfYWdlbnRzEskCCsYCW3sia2V5Ijog + ImYzMzg2ZjZkOGRhNzVhYTQxNmE2ZTMxMDA1M2Y3Njk4IiwgImlkIjogIjRlNTExYTRhLTM1Yzkt + NDA0NC1iMzBlLWM4OGZjZTJiMzc5YiIsICJyb2xlIjogInt0b3BpY30gUmVzZWFyY2hlciIsICJ2 + ZXJib3NlPyI6IGZhbHNlLCAibWF4X2l0ZXIiOiAyMCwgIm1heF9ycG0iOiBudWxsLCAiZnVuY3Rp + b25fY2FsbGluZ19sbG0iOiAiIiwgImxsbSI6ICJncHQtNG8tbWluaSIsICJkZWxlZ2F0aW9uX2Vu + YWJsZWQ/IjogZmFsc2UsICJhbGxvd19jb2RlX2V4ZWN1dGlvbj8iOiBmYWxzZSwgIm1heF9yZXRy + eV9saW1pdCI6IDIsICJ0b29sc19uYW1lcyI6IFtdfV1KhwIKCmNyZXdfdGFza3MS+AEK9QFbeyJr + ZXkiOiAiMDZhNzMyMjBmNDE0OGE0YmJkNWJhY2IwZDBiNDRmY2UiLCAiaWQiOiAiOTIzMWJmZjIt + ODFmOS00MjU4LTgyMDktMjkzMjUyOWI1ZjlmIiwgImFzeW5jX2V4ZWN1dGlvbj8iOiBmYWxzZSwg + Imh1bWFuX2lucHV0PyI6IGZhbHNlLCAiYWdlbnRfcm9sZSI6ICJ7dG9waWN9IFJlc2VhcmNoZXIi + LCAiYWdlbnRfa2V5IjogImYzMzg2ZjZkOGRhNzVhYTQxNmE2ZTMxMDA1M2Y3Njk4IiwgInRvb2xz + X25hbWVzIjogW119XXoCGAGFAQABAAASjgIKECTO19lNFYzBivlrqiZfSxASCIAH8VhjiPfQKgxU + YXNrIENyZWF0ZWQwATnIr+NPX34JGEHQAeRPX34JGEouCghjcmV3X2tleRIiCiBlZTY3NDVkN2M4 + YWU4MmUwMGRmOTRkZTBmN2Y4NzExOEoxCgdjcmV3X2lkEiYKJDUxYmI1NGQ0LWM4MTAtNDA0Yy04 + MTQzLWVmNTgwMTlhN2Q2OEouCgh0YXNrX2tleRIiCiAwNmE3MzIyMGY0MTQ4YTRiYmQ1YmFjYjBk + MGI0NGZjZUoxCgd0YXNrX2lkEiYKJDkyMzFiZmYyLTgxZjktNDI1OC04MjA5LTI5MzI1MjliNWY5 + ZnoCGAGFAQABAAASswcKEHGb7KITfOkYfQT7CRjWfUcSCIn6YlQJ1QVbKgxDcmV3IENyZWF0ZWQw + ATmYH/BYX34JGEEgJ/JYX34JGEoaCg5jcmV3YWlfdmVyc2lvbhIICgYwLjgwLjBKGgoOcHl0aG9u + X3ZlcnNpb24SCAoGMy4xMi41Si4KCGNyZXdfa2V5EiIKIGVlNjc0NWQ3YzhhZTgyZTAwZGY5NGRl + MGY3Zjg3MTE4SjEKB2NyZXdfaWQSJgokZDc5Y2UyMWUtYmU1Ny00NTdiLWExMzEtNjZkMDFmZjQx + ZTI2ShwKDGNyZXdfcHJvY2VzcxIMCgpzZXF1ZW50aWFsShEKC2NyZXdfbWVtb3J5EgIQAEoaChRj + cmV3X251bWJlcl9vZl90YXNrcxICGAFKGwoVY3Jld19udW1iZXJfb2ZfYWdlbnRzEgIYAUrZAgoL + Y3Jld19hZ2VudHMSyQIKxgJbeyJrZXkiOiAiZjMzODZmNmQ4ZGE3NWFhNDE2YTZlMzEwMDUzZjc2 + OTgiLCAiaWQiOiAiNzRhNDUxNzgtNmExOS00N2RjLThlZjktZDdhZmQ5YzUwMDQ0IiwgInJvbGUi + OiAie3RvcGljfSBSZXNlYXJjaGVyIiwgInZlcmJvc2U/IjogZmFsc2UsICJtYXhfaXRlciI6IDIw + LCAibWF4X3JwbSI6IG51bGwsICJmdW5jdGlvbl9jYWxsaW5nX2xsbSI6ICIiLCAibGxtIjogImdw + dC00by1taW5pIiwgImRlbGVnYXRpb25fZW5hYmxlZD8iOiBmYWxzZSwgImFsbG93X2NvZGVfZXhl + Y3V0aW9uPyI6IGZhbHNlLCAibWF4X3JldHJ5X2xpbWl0IjogMiwgInRvb2xzX25hbWVzIjogW119 + XUqHAgoKY3Jld190YXNrcxL4AQr1AVt7ImtleSI6ICIwNmE3MzIyMGY0MTQ4YTRiYmQ1YmFjYjBk + MGI0NGZjZSIsICJpZCI6ICJjZWZiYjE1ZS01Y2M4LTQwZTctYTViMS03ODkzYjJlZGFkYmQiLCAi + YXN5bmNfZXhlY3V0aW9uPyI6IGZhbHNlLCAiaHVtYW5faW5wdXQ/IjogZmFsc2UsICJhZ2VudF9y + b2xlIjogInt0b3BpY30gUmVzZWFyY2hlciIsICJhZ2VudF9rZXkiOiAiZjMzODZmNmQ4ZGE3NWFh + NDE2YTZlMzEwMDUzZjc2OTgiLCAidG9vbHNfbmFtZXMiOiBbXX1degIYAYUBAAEAAA== + headers: + Accept: + - '*/*' + Accept-Encoding: + - gzip, deflate + Connection: + - keep-alive + Content-Length: + - '11392' + Content-Type: + - application/x-protobuf + User-Agent: + - OTel-OTLP-Exporter-Python/1.27.0 + method: POST + uri: https://telemetry.crewai.com:4319/v1/traces + response: + body: + string: "\n\0" + headers: + Content-Length: + - '2' + Content-Type: + - application/x-protobuf + Date: + - Tue, 19 Nov 2024 22:14:39 GMT + status: + code: 200 + message: OK +version: 1 diff --git a/tests/knowledge/__init__.py b/tests/knowledge/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/tests/knowledge/crewai_quickstart.pdf b/tests/knowledge/crewai_quickstart.pdf new file mode 100644 index 0000000000000000000000000000000000000000..671baf782c64529284e4aa2940f65434a906d4f6 GIT binary patch literal 952178 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 z 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ⅆs(4`U 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 Oh Date: Wed, 20 Nov 2024 18:52:41 -0600 Subject: [PATCH 072/126] Updated README.md, fix typo(s) (#1637) --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index ceec198c3..c6be68dc1 100644 --- a/README.md +++ b/README.md @@ -100,7 +100,7 @@ You can now start developing your crew by editing the files in the `src/my_proje #### Example of a simple crew with a sequential process: -Instatiate your crew: +Instantiate your crew: ```shell crewai create crew latest-ai-development @@ -399,7 +399,7 @@ Data collected includes: - Roles of agents in a crew - Understand high level use cases so we can build better tools, integrations and examples about it - Tools names available - - Understand out of the publically available tools, which ones are being used the most so we can improve them + - Understand out of the publicly available tools, which ones are being used the most so we can improve them Users can opt-in to Further Telemetry, sharing the complete telemetry data by setting the `share_crew` attribute to `True` on their Crews. Enabling `share_crew` results in the collection of detailed crew and task execution data, including `goal`, `backstory`, `context`, and `output` of tasks. This enables a deeper insight into usage patterns while respecting the user's choice to share. From f8ca49d8df36cfbc8762b604ca94427cf4db0413 Mon Sep 17 00:00:00 2001 From: Andy Bromberg Date: Wed, 20 Nov 2024 16:54:04 -0800 Subject: [PATCH 073/126] Update Perplexity example in documentation (#1623) --- docs/concepts/llms.mdx | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/concepts/llms.mdx b/docs/concepts/llms.mdx index 5211e6652..798fea726 100644 --- a/docs/concepts/llms.mdx +++ b/docs/concepts/llms.mdx @@ -310,8 +310,8 @@ These are examples of how to configure LLMs for your agent. from crewai import LLM llm = LLM( - model="perplexity/mistral-7b-instruct", - base_url="https://api.perplexity.ai/v1", + model="llama-3.1-sonar-large-128k-online", + base_url="https://api.perplexity.ai/", api_key="your-api-key-here" ) agent = Agent(llm=llm, ...) @@ -400,4 +400,4 @@ This is particularly useful when working with OpenAI-compatible APIs or when you - **API Errors**: Check your API key, network connection, and rate limits. - **Unexpected Outputs**: Refine your prompts and adjust temperature or top_p. - **Performance Issues**: Consider using a more powerful model or optimizing your queries. -- **Timeout Errors**: Increase the `timeout` parameter or optimize your input. \ No newline at end of file +- **Timeout Errors**: Increase the `timeout` parameter or optimize your input. From 8f5f67de411fcb0606822f216f59676691be6c1a Mon Sep 17 00:00:00 2001 From: Gui Vieira Date: Thu, 21 Nov 2024 15:33:20 -0300 Subject: [PATCH 074/126] Fix threading --- src/crewai/llm.py | 35 ++++++++++++++++++++++------------- src/crewai/task.py | 10 +++++++--- 2 files changed, 29 insertions(+), 16 deletions(-) diff --git a/src/crewai/llm.py b/src/crewai/llm.py index 7ea8155a6..bfdaa4418 100644 --- a/src/crewai/llm.py +++ b/src/crewai/llm.py @@ -1,6 +1,6 @@ -import io import logging import sys +import threading import warnings from contextlib import contextmanager from typing import Any, Dict, List, Optional, Union @@ -13,16 +13,25 @@ from crewai.utilities.exceptions.context_window_exceeding_exception import ( ) -class FilteredStream(io.StringIO): - def write(self, s): - if ( - "Give Feedback / Get Help: https://github.com/BerriAI/litellm/issues/new" - in s - or "LiteLLM.Info: If you need to debug this error, use `litellm.set_verbose=True`" - in s - ): - return - super().write(s) +class FilteredStream: + def __init__(self, original_stream): + self._original_stream = original_stream + self._lock = threading.Lock() + + def write(self, s) -> int: + with self._lock: + if ( + "Give Feedback / Get Help: https://github.com/BerriAI/litellm/issues/new" + in s + or "LiteLLM.Info: If you need to debug this error, use `litellm.set_verbose=True`" + in s + ): + return 0 + return self._original_stream.write(s) + + def flush(self): + with self._lock: + return self._original_stream.flush() LLM_CONTEXT_WINDOW_SIZES = { @@ -60,8 +69,8 @@ def suppress_warnings(): # Redirect stdout and stderr old_stdout = sys.stdout old_stderr = sys.stderr - sys.stdout = FilteredStream() - sys.stderr = FilteredStream() + sys.stdout = FilteredStream(old_stdout) + sys.stderr = FilteredStream(old_stderr) try: yield diff --git a/src/crewai/task.py b/src/crewai/task.py index 21278af98..59415f1e9 100644 --- a/src/crewai/task.py +++ b/src/crewai/task.py @@ -20,10 +20,10 @@ from pydantic import ( from pydantic_core import PydanticCustomError from crewai.agents.agent_builder.base_agent import BaseAgent -from crewai.tools.base_tool import BaseTool from crewai.tasks.output_format import OutputFormat from crewai.tasks.task_output import TaskOutput from crewai.telemetry.telemetry import Telemetry +from crewai.tools.base_tool import BaseTool from crewai.utilities.config import process_config from crewai.utilities.converter import Converter, convert_to_model from crewai.utilities.i18n import I18N @@ -208,7 +208,9 @@ class Task(BaseModel): """Execute the task asynchronously.""" future: Future[TaskOutput] = Future() threading.Thread( - target=self._execute_task_async, args=(agent, context, tools, future) + daemon=True, + target=self._execute_task_async, + args=(agent, context, tools, future), ).start() return future @@ -277,7 +279,9 @@ class Task(BaseModel): content = ( json_output if json_output - else pydantic_output.model_dump_json() if pydantic_output else result + else pydantic_output.model_dump_json() + if pydantic_output + else result ) self._save_file(content) From 8cf1cd5a62ee68d57445a1eacd3407fbd9808106 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Moura?= Date: Mon, 25 Nov 2024 10:05:15 -0300 Subject: [PATCH 075/126] preparing new version --- docs/concepts/knowledge.mdx | 18 +++--- .../how-to/before-and-after-kickoff-hooks.mdx | 59 +++++++++++++++++++ docs/quickstart.mdx | 48 +++++++++++---- pyproject.toml | 4 +- src/crewai/__init__.py | 2 +- src/crewai/cli/templates/crew/crew.py | 14 ++++- src/crewai/cli/templates/crew/pyproject.toml | 2 +- src/crewai/cli/templates/flow/pyproject.toml | 2 +- .../cli/templates/pipeline/pyproject.toml | 2 +- .../templates/pipeline_router/pyproject.toml | 2 +- src/crewai/cli/templates/tool/pyproject.toml | 2 +- uv.lock | 13 ++-- 12 files changed, 137 insertions(+), 31 deletions(-) create mode 100644 docs/how-to/before-and-after-kickoff-hooks.mdx diff --git a/docs/concepts/knowledge.mdx b/docs/concepts/knowledge.mdx index 2afb1b568..ce0203e13 100644 --- a/docs/concepts/knowledge.mdx +++ b/docs/concepts/knowledge.mdx @@ -1,6 +1,6 @@ --- title: Knowledge -description: What is knowledge in CrewAI and how to use it. +description: Understand what knowledge is in CrewAI and how to effectively use it. icon: book --- @@ -8,7 +8,14 @@ icon: book ## Introduction +Knowledge in CrewAI serves as a foundational component for enriching AI agents with contextual and relevant information. It enables agents to access and utilize structured data sources during their execution processes, making them more intelligent and responsive. + The Knowledge class in CrewAI provides a powerful way to manage and query knowledge sources for your AI agents. This guide will show you how to implement knowledge management in your CrewAI projects. + +## What is Knowledge? + +The `Knowledge` class in CrewAI manages various sources that store information, which can be queried and retrieved by AI agents. This modular approach allows you to integrate diverse data formats such as text, PDFs, spreadsheets, and more into your AI workflows. + Additionally, we have specific tools for generate knowledge sources for strings, text files, PDF's, and Spreadsheets. You can expand on any source type by extending the `KnowledgeSource` class. ## Basic Implementation @@ -25,17 +32,14 @@ string_source = StringKnowledgeSource( content=content, metadata={"preference": "personal"} ) - -llm = LLM(model="gpt-4o-mini", temperature=0) - # Create an agent with the knowledge store +# Create an agent with the knowledge store agent = Agent( role="About User", goal="You know everything about the user.", backstory="""You are a master at understanding people and their preferences.""", - verbose=True, - allow_delegation=False, - llm=llm, + verbose=True ) + task = Task( description="Answer the following questions about the user: {question}", expected_output="An answer to the question.", diff --git a/docs/how-to/before-and-after-kickoff-hooks.mdx b/docs/how-to/before-and-after-kickoff-hooks.mdx new file mode 100644 index 000000000..83058ce3c --- /dev/null +++ b/docs/how-to/before-and-after-kickoff-hooks.mdx @@ -0,0 +1,59 @@ +--- +title: Before and After Kickoff Hooks +description: Learn how to use before and after kickoff hooks in CrewAI +--- + +CrewAI provides hooks that allow you to execute code before and after a crew's kickoff. These hooks are useful for preprocessing inputs or post-processing results. + +## Before Kickoff Hook + +The before kickoff hook is executed before the crew starts its tasks. It receives the input dictionary and can modify it before passing it to the crew. You can use this hook to set up your environment, load necessary data, or preprocess your inputs. This is useful in scenarios where the input data might need enrichment or validation before being processed by the crew. + +Here's an example of defining a before kickoff function in your `crew.py`: + +```python +from crewai import CrewBase, before_kickoff + +@CrewBase +class MyCrew: + @before_kickoff + def prepare_data(self, inputs): + # Preprocess or modify inputs + inputs['processed'] = True + return inputs + +#... +``` + +In this example, the prepare_data function modifies the inputs by adding a new key-value pair indicating that the inputs have been processed. + +## After Kickoff Hook + +The after kickoff hook is executed after the crew has completed its tasks. It receives the result object, which contains the outputs of the crew's execution. This hook is ideal for post-processing results, such as logging, data transformation, or further analysis. + +Here's how you can define an after kickoff function in your `crew.py`: + +```python +from crewai import CrewBase, after_kickoff + +@CrewBase +class MyCrew: + @after_kickoff + def log_results(self, result): + # Log or modify the results + print("Crew execution completed with result:", result) + return result + +# ... +``` + + +In the `log_results` function, the results of the crew execution are simply printed out. You can extend this to perform more complex operations such as sending notifications or integrating with other services. + +## Utilizing Both Hooks + +Both hooks can be used together to provide a comprehensive setup and teardown process for your crew's execution. They are particularly useful in maintaining clean code architecture by separating concerns and enhancing the modularity of your CrewAI implementations. + +## Conclusion + +Before and after kickoff hooks in CrewAI offer powerful ways to interact with the lifecycle of a crew's execution. By understanding and utilizing these hooks, you can greatly enhance the robustness and flexibility of your AI agents. diff --git a/docs/quickstart.mdx b/docs/quickstart.mdx index ef149bfcc..246bbdf84 100644 --- a/docs/quickstart.mdx +++ b/docs/quickstart.mdx @@ -8,7 +8,7 @@ icon: rocket Let's create a simple crew that will help us `research` and `report` on the `latest AI developments` for a given topic or subject. -Before we proceed, make sure you have `crewai` and `crewai-tools` installed. +Before we proceed, make sure you have `crewai` and `crewai-tools` installed. If you haven't installed them yet, you can do so by following the [installation guide](/installation). Follow the steps below to get crewing! 🚣‍♂️ @@ -23,7 +23,7 @@ Follow the steps below to get crewing! 🚣‍♂️ ``` - + You can also modify the agents as needed to fit your use case or copy and paste as is to your project. Any variable interpolated in your `agents.yaml` and `tasks.yaml` files like `{topic}` will be replaced by the value of the variable in the `main.py` file. @@ -39,7 +39,7 @@ Follow the steps below to get crewing! 🚣‍♂️ You're a seasoned researcher with a knack for uncovering the latest developments in {topic}. Known for your ability to find the most relevant information and present it in a clear and concise manner. - + reporting_analyst: role: > {topic} Reporting Analyst @@ -51,7 +51,7 @@ Follow the steps below to get crewing! 🚣‍♂️ it easy for others to understand and act on the information you provide. ``` - + ```yaml tasks.yaml # src/latest_ai_development/config/tasks.yaml research_task: @@ -73,8 +73,8 @@ Follow the steps below to get crewing! 🚣‍♂️ agent: reporting_analyst output_file: report.md ``` - - + + ```python crew.py # src/latest_ai_development/crew.py from crewai import Agent, Crew, Process, Task @@ -121,10 +121,34 @@ Follow the steps below to get crewing! 🚣‍♂️ tasks=self.tasks, # Automatically created by the @task decorator process=Process.sequential, verbose=True, - ) + ) ``` - + + ```python crew.py + # src/latest_ai_development/crew.py + from crewai import Agent, Crew, Process, Task + from crewai.project import CrewBase, agent, crew, task, before_kickoff, after_kickoff + from crewai_tools import SerperDevTool + + @CrewBase + class LatestAiDevelopmentCrew(): + """LatestAiDevelopment crew""" + + @before_kickoff + def before_kickoff_function(self, inputs): + print(f"Before kickoff function with inputs: {inputs}") + return inputs # You can return the inputs or modify them as needed + + @after_kickoff + def after_kickoff_function(self, result): + print(f"After kickoff function with result: {result}") + return result # You can return the result or modify it as needed + + # ... remaining code + ``` + + For example, you can pass the `topic` input to your crew to customize the research and reporting. ```python main.py #!/usr/bin/env python @@ -237,14 +261,14 @@ Follow the steps below to get crewing! 🚣‍♂️ ### Note on Consistency in Naming The names you use in your YAML files (`agents.yaml` and `tasks.yaml`) should match the method names in your Python code. -For example, you can reference the agent for specific tasks from `tasks.yaml` file. +For example, you can reference the agent for specific tasks from `tasks.yaml` file. This naming consistency allows CrewAI to automatically link your configurations with your code; otherwise, your task won't recognize the reference properly. #### Example References Note how we use the same name for the agent in the `agents.yaml` (`email_summarizer`) file as the method name in the `crew.py` (`email_summarizer`) file. - + ```yaml agents.yaml email_summarizer: @@ -281,6 +305,8 @@ Use the annotations to properly reference the agent and task in the `crew.py` fi * `@task` * `@crew` * `@tool` +* `@before_kickoff` +* `@after_kickoff` * `@callback` * `@output_json` * `@output_pydantic` @@ -304,7 +330,7 @@ def email_summarizer_task(self) -> Task: In addition to the [sequential process](../how-to/sequential-process), you can use the [hierarchical process](../how-to/hierarchical-process), -which automatically assigns a manager to the defined crew to properly coordinate the planning and execution of tasks through delegation and validation of results. +which automatically assigns a manager to the defined crew to properly coordinate the planning and execution of tasks through delegation and validation of results. You can learn more about the core concepts [here](/concepts). diff --git a/pyproject.toml b/pyproject.toml index 2dbc00e24..4bd416ab5 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "crewai" -version = "0.80.0" +version = "0.83.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" @@ -29,6 +29,8 @@ dependencies = [ "tomli-w>=1.1.0", "tomli>=2.0.2", "chromadb>=0.5.18", + "pdfplumber>=0.11.4", + "openpyxl>=3.1.5", ] [project.urls] diff --git a/src/crewai/__init__.py b/src/crewai/__init__.py index 6cfa381de..34d7f17c9 100644 --- a/src/crewai/__init__.py +++ b/src/crewai/__init__.py @@ -16,7 +16,7 @@ warnings.filterwarnings( category=UserWarning, module="pydantic.main", ) -__version__ = "0.80.0" +__version__ = "0.83.0" __all__ = [ "Agent", "Crew", diff --git a/src/crewai/cli/templates/crew/crew.py b/src/crewai/cli/templates/crew/crew.py index c47315415..6f8e66c4a 100644 --- a/src/crewai/cli/templates/crew/crew.py +++ b/src/crewai/cli/templates/crew/crew.py @@ -1,5 +1,5 @@ from crewai import Agent, Crew, Process, Task -from crewai.project import CrewBase, agent, crew, task +from crewai.project import CrewBase, agent, crew, task, before_kickoff, after_kickoff # Uncomment the following line to use an example of a custom tool # from {{folder_name}}.tools.custom_tool import MyCustomTool @@ -14,6 +14,18 @@ class {{crew_name}}(): agents_config = 'config/agents.yaml' tasks_config = 'config/tasks.yaml' + @before_kickoff # Optional hook to be executed before the crew starts + def pull_data_example(self, inputs): + # Example of pulling data from an external API, dynamically changing the inputs + inputs['extra_data'] = "This is extra data" + return inputs + + @after_kickoff # Optional hook to be executed after the crew has finished + def log_results(self, output): + # Example of logging results, dynamically changing the output + print(f"Results: {output}") + return output + @agent def researcher(self) -> Agent: return Agent( diff --git a/src/crewai/cli/templates/crew/pyproject.toml b/src/crewai/cli/templates/crew/pyproject.toml index a5ab36877..1e456c725 100644 --- a/src/crewai/cli/templates/crew/pyproject.toml +++ b/src/crewai/cli/templates/crew/pyproject.toml @@ -5,7 +5,7 @@ description = "{{name}} using crewAI" authors = [{ name = "Your Name", email = "you@example.com" }] requires-python = ">=3.10,<=3.13" dependencies = [ - "crewai[tools]>=0.80.0,<1.0.0" + "crewai[tools]>=0.83.0,<1.0.0" ] [project.scripts] diff --git a/src/crewai/cli/templates/flow/pyproject.toml b/src/crewai/cli/templates/flow/pyproject.toml index e863f20b7..575aaf086 100644 --- a/src/crewai/cli/templates/flow/pyproject.toml +++ b/src/crewai/cli/templates/flow/pyproject.toml @@ -5,7 +5,7 @@ description = "{{name}} using crewAI" authors = [{ name = "Your Name", email = "you@example.com" }] requires-python = ">=3.10,<=3.13" dependencies = [ - "crewai[tools]>=0.80.0,<1.0.0", + "crewai[tools]>=0.83.0,<1.0.0", ] [project.scripts] diff --git a/src/crewai/cli/templates/pipeline/pyproject.toml b/src/crewai/cli/templates/pipeline/pyproject.toml index 60294740d..d12dccf11 100644 --- a/src/crewai/cli/templates/pipeline/pyproject.toml +++ b/src/crewai/cli/templates/pipeline/pyproject.toml @@ -6,7 +6,7 @@ authors = ["Your Name "] [tool.poetry.dependencies] python = ">=3.10,<=3.13" -crewai = { extras = ["tools"], version = ">=0.80.0,<1.0.0" } +crewai = { extras = ["tools"], version = ">=0.83.0,<1.0.0" } asyncio = "*" [tool.poetry.scripts] diff --git a/src/crewai/cli/templates/pipeline_router/pyproject.toml b/src/crewai/cli/templates/pipeline_router/pyproject.toml index 7c022b1f7..06487bcfa 100644 --- a/src/crewai/cli/templates/pipeline_router/pyproject.toml +++ b/src/crewai/cli/templates/pipeline_router/pyproject.toml @@ -5,7 +5,7 @@ description = "{{name}} using crewAI" authors = ["Your Name "] requires-python = ">=3.10,<=3.13" dependencies = [ - "crewai[tools]>=0.80.0,<1.0.0" + "crewai[tools]>=0.83.0,<1.0.0" ] [project.scripts] diff --git a/src/crewai/cli/templates/tool/pyproject.toml b/src/crewai/cli/templates/tool/pyproject.toml index 4142c325d..7c1afddfa 100644 --- a/src/crewai/cli/templates/tool/pyproject.toml +++ b/src/crewai/cli/templates/tool/pyproject.toml @@ -5,6 +5,6 @@ description = "Power up your crews with {{folder_name}}" readme = "README.md" requires-python = ">=3.10,<=3.13" dependencies = [ - "crewai[tools]>=0.80.0" + "crewai[tools]>=0.83.0" ] diff --git a/uv.lock b/uv.lock index a4b545d07..baff09ac8 100644 --- a/uv.lock +++ b/uv.lock @@ -608,7 +608,7 @@ wheels = [ [[package]] name = "crewai" -version = "0.80.0" +version = "0.83.0" source = { editable = "." } dependencies = [ { name = "appdirs" }, @@ -622,9 +622,11 @@ dependencies = [ { name = "langchain" }, { name = "litellm" }, { name = "openai" }, + { name = "openpyxl" }, { name = "opentelemetry-api" }, { name = "opentelemetry-exporter-otlp-proto-http" }, { name = "opentelemetry-sdk" }, + { name = "pdfplumber" }, { name = "pydantic" }, { name = "python-dotenv" }, { name = "pyvis" }, @@ -641,6 +643,9 @@ agentops = [ fastembed = [ { name = "fastembed" }, ] +mem0 = [ + { name = "mem0ai" }, +] openpyxl = [ { name = "openpyxl" }, ] @@ -650,9 +655,6 @@ pandas = [ pdfplumber = [ { name = "pdfplumber" }, ] -mem0 = [ - { name = "mem0ai" }, -] tools = [ { name = "crewai-tools" }, ] @@ -694,11 +696,13 @@ requires-dist = [ { name = "litellm", specifier = ">=1.44.22" }, { name = "mem0ai", marker = "extra == 'mem0'", specifier = ">=0.1.29" }, { name = "openai", specifier = ">=1.13.3" }, + { name = "openpyxl", specifier = ">=3.1.5" }, { name = "openpyxl", marker = "extra == 'openpyxl'", specifier = ">=3.1.5" }, { name = "opentelemetry-api", specifier = ">=1.22.0" }, { name = "opentelemetry-exporter-otlp-proto-http", specifier = ">=1.22.0" }, { name = "opentelemetry-sdk", specifier = ">=1.22.0" }, { name = "pandas", marker = "extra == 'pandas'", specifier = ">=2.2.3" }, + { name = "pdfplumber", specifier = ">=0.11.4" }, { name = "pdfplumber", marker = "extra == 'pdfplumber'", specifier = ">=0.11.4" }, { name = "pydantic", specifier = ">=2.4.2" }, { name = "python-dotenv", specifier = ">=1.0.0" }, @@ -952,7 +956,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/c1/8b/5fe2cc11fee489817272089c4203e679c63b570a5aaeb18d852ae3cbba6a/et_xmlfile-2.0.0-py3-none-any.whl", hash = "sha256:7a91720bc756843502c3b7504c77b8fe44217c85c537d85037f0f536151b2caa", size = 18059 }, ] - [[package]] name = "exceptiongroup" version = "1.2.2" From 63ecb7395df9a9d69b0ef429213b8be410732b35 Mon Sep 17 00:00:00 2001 From: Vini Brasil Date: Mon, 25 Nov 2024 15:57:47 -0300 Subject: [PATCH 076/126] Log in to Tool Repository on `crewai login` (#1650) This commit adds an extra step to `crewai login` to ensure users also log in to Tool Repository, that is, exchanging their Auth0 tokens for a Tool Repository username and password to be used by UV downloads and API tool uploads. --- src/crewai/cli/authentication/main.py | 18 +++++++++++++++++- src/crewai/cli/authentication/token.py | 10 ++++++++++ src/crewai/cli/command.py | 2 +- src/crewai/cli/plus_api.py | 2 +- src/crewai/cli/run_crew.py | 3 ++- src/crewai/cli/utils.py | 15 --------------- src/crewai/cli/version.py | 6 ++++++ tests/cli/authentication/test_auth_main.py | 8 ++++++-- tests/cli/deploy/test_deploy_main.py | 2 +- 9 files changed, 44 insertions(+), 22 deletions(-) create mode 100644 src/crewai/cli/authentication/token.py create mode 100644 src/crewai/cli/version.py diff --git a/src/crewai/cli/authentication/main.py b/src/crewai/cli/authentication/main.py index 543f06844..c64045801 100644 --- a/src/crewai/cli/authentication/main.py +++ b/src/crewai/cli/authentication/main.py @@ -7,6 +7,7 @@ from rich.console import Console from .constants import AUTH0_AUDIENCE, AUTH0_CLIENT_ID, AUTH0_DOMAIN from .utils import TokenManager, validate_token +from crewai.cli.tools.main import ToolCommand console = Console() @@ -63,7 +64,22 @@ class AuthenticationCommand: validate_token(token_data["id_token"]) expires_in = 360000 # Token expiration time in seconds self.token_manager.save_tokens(token_data["access_token"], expires_in) - console.print("\nWelcome to CrewAI+ !!", style="green") + + try: + ToolCommand().login() + except Exception: + console.print( + "\n[bold yellow]Warning:[/bold yellow] Authentication with the Tool Repository failed.", + style="yellow", + ) + console.print( + "Other features will work normally, but you may experience limitations " + "with downloading and publishing tools." + "\nRun [bold]crewai login[/bold] to try logging in again.\n", + style="yellow", + ) + + console.print("\n[bold green]Welcome to CrewAI Enterprise![/bold green]\n") return if token_data["error"] not in ("authorization_pending", "slow_down"): diff --git a/src/crewai/cli/authentication/token.py b/src/crewai/cli/authentication/token.py new file mode 100644 index 000000000..889fdc2eb --- /dev/null +++ b/src/crewai/cli/authentication/token.py @@ -0,0 +1,10 @@ +from .utils import TokenManager + +def get_auth_token() -> str: + """Get the authentication token.""" + access_token = TokenManager().get_token() + if not access_token: + raise Exception() + return access_token + + diff --git a/src/crewai/cli/command.py b/src/crewai/cli/command.py index f05fe237f..f2af92bf5 100644 --- a/src/crewai/cli/command.py +++ b/src/crewai/cli/command.py @@ -2,7 +2,7 @@ import requests from requests.exceptions import JSONDecodeError from rich.console import Console from crewai.cli.plus_api import PlusAPI -from crewai.cli.utils import get_auth_token +from crewai.cli.authentication.token import get_auth_token from crewai.telemetry.telemetry import Telemetry console = Console() diff --git a/src/crewai/cli/plus_api.py b/src/crewai/cli/plus_api.py index 04f6fb8ff..2fce0d6d8 100644 --- a/src/crewai/cli/plus_api.py +++ b/src/crewai/cli/plus_api.py @@ -1,7 +1,7 @@ from typing import Optional import requests from os import getenv -from crewai.cli.utils import get_crewai_version +from crewai.cli.version import get_crewai_version from urllib.parse import urljoin diff --git a/src/crewai/cli/run_crew.py b/src/crewai/cli/run_crew.py index 5450cf32b..95b560109 100644 --- a/src/crewai/cli/run_crew.py +++ b/src/crewai/cli/run_crew.py @@ -3,7 +3,8 @@ import subprocess import click from packaging import version -from crewai.cli.utils import get_crewai_version, read_toml +from crewai.cli.utils import read_toml +from crewai.cli.version import get_crewai_version def run_crew() -> None: diff --git a/src/crewai/cli/utils.py b/src/crewai/cli/utils.py index 25da9e31a..2daba4111 100644 --- a/src/crewai/cli/utils.py +++ b/src/crewai/cli/utils.py @@ -1,4 +1,3 @@ -import importlib.metadata import os import shutil import sys @@ -9,7 +8,6 @@ import click import tomli from rich.console import Console -from crewai.cli.authentication.utils import TokenManager from crewai.cli.constants import ENV_VARS if sys.version_info >= (3, 11): @@ -137,11 +135,6 @@ def _get_nested_value(data: Dict[str, Any], keys: List[str]) -> Any: return reduce(dict.__getitem__, keys, data) -def get_crewai_version() -> str: - """Get the version number of CrewAI running the CLI""" - return importlib.metadata.version("crewai") - - def fetch_and_json_env_file(env_file_path: str = ".env") -> dict: """Fetch the environment variables from a .env file and return them as a dictionary.""" try: @@ -166,14 +159,6 @@ def fetch_and_json_env_file(env_file_path: str = ".env") -> dict: return {} -def get_auth_token() -> str: - """Get the authentication token.""" - access_token = TokenManager().get_token() - if not access_token: - raise Exception() - return access_token - - def tree_copy(source, destination): """Copies the entire directory structure from the source to the destination.""" for item in os.listdir(source): diff --git a/src/crewai/cli/version.py b/src/crewai/cli/version.py new file mode 100644 index 000000000..543be9c32 --- /dev/null +++ b/src/crewai/cli/version.py @@ -0,0 +1,6 @@ +import importlib.metadata + +def get_crewai_version() -> str: + """Get the version number of CrewAI running the CLI""" + return importlib.metadata.version("crewai") + diff --git a/tests/cli/authentication/test_auth_main.py b/tests/cli/authentication/test_auth_main.py index c56968aab..4466cc999 100644 --- a/tests/cli/authentication/test_auth_main.py +++ b/tests/cli/authentication/test_auth_main.py @@ -43,10 +43,11 @@ class TestAuthenticationCommand(unittest.TestCase): mock_print.assert_any_call("2. Enter the following code: ", "ABCDEF") mock_open.assert_called_once_with("https://example.com") + @patch("crewai.cli.authentication.main.ToolCommand") @patch("crewai.cli.authentication.main.requests.post") @patch("crewai.cli.authentication.main.validate_token") @patch("crewai.cli.authentication.main.console.print") - def test_poll_for_token_success(self, mock_print, mock_validate_token, mock_post): + def test_poll_for_token_success(self, mock_print, mock_validate_token, mock_post, mock_tool): mock_response = MagicMock() mock_response.status_code = 200 mock_response.json.return_value = { @@ -55,10 +56,13 @@ class TestAuthenticationCommand(unittest.TestCase): } mock_post.return_value = mock_response + mock_instance = mock_tool.return_value + mock_instance.login.return_value = None + self.auth_command._poll_for_token({"device_code": "123456"}) mock_validate_token.assert_called_once_with("TOKEN") - mock_print.assert_called_once_with("\nWelcome to CrewAI+ !!", style="green") + mock_print.assert_called_once_with("\n[bold green]Welcome to CrewAI Enterprise![/bold green]\n") @patch("crewai.cli.authentication.main.requests.post") @patch("crewai.cli.authentication.main.console.print") diff --git a/tests/cli/deploy/test_deploy_main.py b/tests/cli/deploy/test_deploy_main.py index 385dbb8a5..bf1198a0b 100644 --- a/tests/cli/deploy/test_deploy_main.py +++ b/tests/cli/deploy/test_deploy_main.py @@ -260,6 +260,6 @@ class TestDeployCommand(unittest.TestCase): self.assertEqual(project_name, "test_project") def test_get_crewai_version(self): - from crewai.cli.utils import get_crewai_version + from crewai.cli.version import get_crewai_version assert isinstance(get_crewai_version(), str) From 6fe308202e99b706f6925b6968d5209ee6d15e17 Mon Sep 17 00:00:00 2001 From: Tony Kipkemboi Date: Mon, 25 Nov 2024 20:37:27 +0000 Subject: [PATCH 077/126] add knowledge to mint.json --- docs/mint.json | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/mint.json b/docs/mint.json index 3ea9f5baf..d5aa8cb8f 100644 --- a/docs/mint.json +++ b/docs/mint.json @@ -68,6 +68,7 @@ "concepts/tasks", "concepts/crews", "concepts/flows", + "concepts/knowledge", "concepts/llms", "concepts/processes", "concepts/collaboration", From 4069b621d540a493afd685a706eccbab1dbcff46 Mon Sep 17 00:00:00 2001 From: "Brandon Hancock (bhancock_ai)" <109994880+bhancockio@users.noreply.github.com> Date: Tue, 26 Nov 2024 09:41:14 -0500 Subject: [PATCH 078/126] Improve typed task outputs (#1651) * V1 working * clean up imports and prints * more clean up and add tests * fixing tests * fix test * fix linting * Fix tests * Fix linting * add doc string as requested by eduardo --- src/crewai/agent.py | 24 ++++++++++++++-- src/crewai/task.py | 4 +-- src/crewai/translations/en.json | 5 ++-- src/crewai/utilities/converter.py | 37 +++++++++++++++++++++++- tests/utilities/test_converter.py | 48 +++++++++++++++++++++++++++++-- 5 files changed, 107 insertions(+), 11 deletions(-) diff --git a/src/crewai/agent.py b/src/crewai/agent.py index d17cbbdfe..386a7cc57 100644 --- a/src/crewai/agent.py +++ b/src/crewai/agent.py @@ -11,10 +11,12 @@ from crewai.agents.crew_agent_executor import CrewAgentExecutor from crewai.cli.constants import ENV_VARS from crewai.llm import LLM from crewai.memory.contextual.contextual_memory import ContextualMemory +from crewai.task import Task from crewai.tools import BaseTool from crewai.tools.agent_tools.agent_tools import AgentTools from crewai.utilities import Converter, Prompts from crewai.utilities.constants import TRAINED_AGENTS_DATA_FILE, TRAINING_DATA_FILE +from crewai.utilities.converter import generate_model_description from crewai.utilities.token_counter_callback import TokenCalcHandler from crewai.utilities.training_handler import CrewTrainingHandler @@ -237,7 +239,7 @@ class Agent(BaseAgent): def execute_task( self, - task: Any, + task: Task, context: Optional[str] = None, tools: Optional[List[BaseTool]] = None, ) -> str: @@ -256,6 +258,22 @@ class Agent(BaseAgent): task_prompt = task.prompt() + # If the task requires output in JSON or Pydantic format, + # append specific instructions to the task prompt to ensure + # that the final answer does not include any code block markers + if task.output_json or task.output_pydantic: + # Generate the schema based on the output format + if task.output_json: + # schema = json.dumps(task.output_json, indent=2) + schema = generate_model_description(task.output_json) + + elif task.output_pydantic: + schema = generate_model_description(task.output_pydantic) + + task_prompt += "\n" + self.i18n.slice("formatted_task_instructions").format( + output_format=schema + ) + if context: task_prompt = self.i18n.slice("task_with_context").format( task=task_prompt, context=context @@ -277,8 +295,8 @@ class Agent(BaseAgent): if self.crew and self.crew.knowledge: knowledge_snippets = self.crew.knowledge.query([task.prompt()]) valid_snippets = [ - result["context"] - for result in knowledge_snippets + result["context"] + for result in knowledge_snippets if result and result.get("context") ] if valid_snippets: diff --git a/src/crewai/task.py b/src/crewai/task.py index 59415f1e9..af5b34c54 100644 --- a/src/crewai/task.py +++ b/src/crewai/task.py @@ -279,9 +279,7 @@ class Task(BaseModel): content = ( json_output if json_output - else pydantic_output.model_dump_json() - if pydantic_output - else result + else pydantic_output.model_dump_json() if pydantic_output else result ) self._save_file(content) diff --git a/src/crewai/translations/en.json b/src/crewai/translations/en.json index aad49ff5f..d79076bb7 100644 --- a/src/crewai/translations/en.json +++ b/src/crewai/translations/en.json @@ -11,7 +11,7 @@ "role_playing": "You are {role}. {backstory}\nYour personal goal is: {goal}", "tools": "\nYou ONLY have access to the following tools, and should NEVER make up tools that are not listed here:\n\n{tools}\n\nUse the following format:\n\nThought: you should always think about what to do\nAction: the action to take, only one name of [{tool_names}], just the name, exactly as it's written.\nAction Input: the input to the action, just a simple python dictionary, enclosed in curly braces, using \" to wrap keys and values.\nObservation: the result of the action\n\nOnce all necessary information is gathered:\n\nThought: I now know the final answer\nFinal Answer: the final answer to the original input question\n", "no_tools": "\nTo give my best complete final answer to the task use the exact following format:\n\nThought: I now can give a great answer\nFinal Answer: Your final answer must be the great and the most complete as possible, it must be outcome described.\n\nI MUST use these formats, my job depends on it!", - "format": "I MUST either use a tool (use one at time) OR give my best final answer not both at the same time. To Use the following format:\n\nThought: you should always think about what to do\nAction: the action to take, should be one of [{tool_names}]\nAction Input: the input to the action, dictionary enclosed in curly braces\nObservation: the result of the action\n... (this Thought/Action/Action Input/Result can repeat N times)\nThought: I now can give a great answer\nFinal Answer: Your final answer must be the great and the most complete as possible, it must be outcome described\n\n ", + "format": "I MUST either use a tool (use one at time) OR give my best final answer not both at the same time. To Use the following format:\n\nThought: you should always think about what to do\nAction: the action to take, should be one of [{tool_names}]\nAction Input: the input to the action, dictionary enclosed in curly braces\nObservation: the result of the action\n... (this Thought/Action/Action Input/Result can repeat N times)\nThought: I now can give a great answer\nFinal Answer: Your final answer must be the great and the most complete as possible, it must be outcome described\n\n", "final_answer_format": "If you don't need to use any more tools, you must give your best complete final answer, make sure it satisfy the expect criteria, use the EXACT format below:\n\nThought: I now can give a great answer\nFinal Answer: my best complete final answer to the task.\n\n", "format_without_tools": "\nSorry, I didn't use the right format. I MUST either use a tool (among the available ones), OR give my best final answer.\nI just remembered the expected format I must follow:\n\nQuestion: the input question you must answer\nThought: you should always think about what to do\nAction: the action to take, should be one of [{tool_names}]\nAction Input: the input to the action\nObservation: the result of the action\n... (this Thought/Action/Action Input/Result can repeat N times)\nThought: I now can give a great answer\nFinal Answer: Your final answer must be the great and the most complete as possible, it must be outcome described\n\n", "task_with_context": "{task}\n\nThis is the context you're working with:\n{context}", @@ -21,7 +21,8 @@ "summarizer_system_message": "You are a helpful assistant that summarizes text.", "sumamrize_instruction": "Summarize the following text, make sure to include all the important information: {group}", "summary": "This is a summary of our conversation so far:\n{merged_summary}", - "manager_request": "Your best answer to your coworker asking you this, accounting for the context shared." + "manager_request": "Your best answer to your coworker asking you this, accounting for the context shared.", + "formatted_task_instructions": "Ensure your final answer contains only the content in the following format: {output_format}\n\nEnsure the final output does not include any code block markers like ```json or ```python." }, "errors": { "force_final_answer_error": "You can't keep going, this was the best you could do.\n {formatted_answer.text}", diff --git a/src/crewai/utilities/converter.py b/src/crewai/utilities/converter.py index 4d0af67ca..ba958ddc6 100644 --- a/src/crewai/utilities/converter.py +++ b/src/crewai/utilities/converter.py @@ -1,6 +1,6 @@ import json import re -from typing import Any, Optional, Type, Union +from typing import Any, Optional, Type, Union, get_args, get_origin from pydantic import BaseModel, ValidationError @@ -214,3 +214,38 @@ def create_converter( raise Exception("No output converter found or set.") return converter + + +def generate_model_description(model: Type[BaseModel]) -> str: + """ + Generate a string description of a Pydantic model's fields and their types. + + This function takes a Pydantic model class and returns a string that describes + the model's fields and their respective types. The description includes handling + of complex types such as `Optional`, `List`, and `Dict`, as well as nested Pydantic + models. + """ + + def describe_field(field_type): + origin = get_origin(field_type) + args = get_args(field_type) + + if origin is Union and type(None) in args: + non_none_args = [arg for arg in args if arg is not type(None)] + return f"Optional[{describe_field(non_none_args[0])}]" + elif origin is list: + return f"List[{describe_field(args[0])}]" + elif origin is dict: + key_type = describe_field(args[0]) + value_type = describe_field(args[1]) + return f"Dict[{key_type}, {value_type}]" + elif isinstance(field_type, type) and issubclass(field_type, BaseModel): + return generate_model_description(field_type) + else: + return field_type.__name__ + + fields = model.__annotations__ + field_descriptions = [ + f'"{name}": {describe_field(type_)}' for name, type_ in fields.items() + ] + return "{\n " + ",\n ".join(field_descriptions) + "\n}" diff --git a/tests/utilities/test_converter.py b/tests/utilities/test_converter.py index 0bb6b7263..c63d6dba3 100644 --- a/tests/utilities/test_converter.py +++ b/tests/utilities/test_converter.py @@ -1,7 +1,10 @@ import json +from typing import Dict, List, Optional from unittest.mock import MagicMock, Mock, patch import pytest +from pydantic import BaseModel + from crewai.llm import LLM from crewai.utilities.converter import ( Converter, @@ -9,12 +12,11 @@ from crewai.utilities.converter import ( convert_to_model, convert_with_instructions, create_converter, + generate_model_description, get_conversion_instructions, handle_partial_json, validate_model, ) -from pydantic import BaseModel - from crewai.utilities.pydantic_schema_parser import PydanticSchemaParser @@ -269,3 +271,45 @@ def test_create_converter_fails_without_agent_or_converter_cls(): create_converter( llm=Mock(), text="Sample", model=SimpleModel, instructions="Convert" ) + + +def test_generate_model_description_simple_model(): + description = generate_model_description(SimpleModel) + expected_description = '{\n "name": str,\n "age": int\n}' + assert description == expected_description + + +def test_generate_model_description_nested_model(): + description = generate_model_description(NestedModel) + expected_description = ( + '{\n "id": int,\n "data": {\n "name": str,\n "age": int\n}\n}' + ) + assert description == expected_description + + +def test_generate_model_description_optional_field(): + class ModelWithOptionalField(BaseModel): + name: Optional[str] + age: int + + description = generate_model_description(ModelWithOptionalField) + expected_description = '{\n "name": Optional[str],\n "age": int\n}' + assert description == expected_description + + +def test_generate_model_description_list_field(): + class ModelWithListField(BaseModel): + items: List[int] + + description = generate_model_description(ModelWithListField) + expected_description = '{\n "items": List[int]\n}' + assert description == expected_description + + +def test_generate_model_description_dict_field(): + class ModelWithDictField(BaseModel): + attributes: Dict[str, int] + + description = generate_model_description(ModelWithDictField) + expected_description = '{\n "attributes": Dict[str, int]\n}' + assert description == expected_description From 49d173a02dd7f61fa3670af7d43b099e4f9a6c32 Mon Sep 17 00:00:00 2001 From: Bowen Liang Date: Wed, 27 Nov 2024 00:08:50 +0800 Subject: [PATCH 079/126] Update Github actions (#1639) * actions/checkout@v4 * actions/cache@v4 * actions/setup-python@v5 --------- Co-authored-by: Brandon Hancock (bhancock_ai) <109994880+bhancockio@users.noreply.github.com> --- .github/workflows/linter.yml | 2 +- .github/workflows/mkdocs.yml | 8 ++++---- .github/workflows/security-checker.yml | 2 +- .github/workflows/type-checker.yml | 2 +- 4 files changed, 7 insertions(+), 7 deletions(-) diff --git a/.github/workflows/linter.yml b/.github/workflows/linter.yml index a7223c506..7ced70df9 100644 --- a/.github/workflows/linter.yml +++ b/.github/workflows/linter.yml @@ -6,7 +6,7 @@ jobs: lint: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: Install Requirements run: | diff --git a/.github/workflows/mkdocs.yml b/.github/workflows/mkdocs.yml index a15ae0363..84d627db4 100644 --- a/.github/workflows/mkdocs.yml +++ b/.github/workflows/mkdocs.yml @@ -13,10 +13,10 @@ jobs: steps: - name: Checkout code - uses: actions/checkout@v2 + uses: actions/checkout@v4 - name: Setup Python - uses: actions/setup-python@v4 + uses: actions/setup-python@v5 with: python-version: '3.10' @@ -25,7 +25,7 @@ jobs: run: echo "::set-output name=hash::$(sha256sum requirements-doc.txt | awk '{print $1}')" - name: Setup cache - uses: actions/cache@v3 + uses: actions/cache@v4 with: key: mkdocs-material-${{ steps.req-hash.outputs.hash }} path: .cache @@ -42,4 +42,4 @@ jobs: GH_TOKEN: ${{ secrets.GH_TOKEN }} - name: Build and deploy MkDocs - run: mkdocs gh-deploy --force \ No newline at end of file + run: mkdocs gh-deploy --force diff --git a/.github/workflows/security-checker.yml b/.github/workflows/security-checker.yml index 665f49292..1588e3ddf 100644 --- a/.github/workflows/security-checker.yml +++ b/.github/workflows/security-checker.yml @@ -11,7 +11,7 @@ jobs: uses: actions/checkout@v4 - name: Set up Python - uses: actions/setup-python@v4 + uses: actions/setup-python@v5 with: python-version: "3.11.9" diff --git a/.github/workflows/type-checker.yml b/.github/workflows/type-checker.yml index 72c64dbd3..c694c8cec 100644 --- a/.github/workflows/type-checker.yml +++ b/.github/workflows/type-checker.yml @@ -14,7 +14,7 @@ jobs: uses: actions/checkout@v4 - name: Setup Python - uses: actions/setup-python@v4 + uses: actions/setup-python@v5 with: python-version: "3.11.9" From 6f32bf52ccf5a5a412ac4c67a8177300a76fc6a5 Mon Sep 17 00:00:00 2001 From: Bowen Liang Date: Wed, 27 Nov 2024 00:24:21 +0800 Subject: [PATCH 080/126] update (#1638) Co-authored-by: Brandon Hancock (bhancock_ai) <109994880+bhancockio@users.noreply.github.com> --- src/crewai/agent.py | 17 ++++++----------- 1 file changed, 6 insertions(+), 11 deletions(-) diff --git a/src/crewai/agent.py b/src/crewai/agent.py index 386a7cc57..a3b40c2c9 100644 --- a/src/crewai/agent.py +++ b/src/crewai/agent.py @@ -160,28 +160,23 @@ class Agent(BaseAgent): for provider, env_vars in ENV_VARS.items(): if provider == set_provider: for env_var in env_vars: - if env_var["key_name"] in unnacepted_attributes: - continue # Check if the environment variable is set - if "key_name" in env_var: - env_value = os.environ.get(env_var["key_name"]) + key_name = env_var.get("key_name") + if key_name and key_name not in unnacepted_attributes: + env_value = os.environ.get(key_name) if env_value: # Map key names containing "API_KEY" to "api_key" key_name = ( - "api_key" - if "API_KEY" in env_var["key_name"] - else env_var["key_name"] + "api_key" if "API_KEY" in key_name else key_name ) # Map key names containing "API_BASE" to "api_base" key_name = ( - "api_base" - if "API_BASE" in env_var["key_name"] - else key_name + "api_base" if "API_BASE" in key_name else key_name ) # Map key names containing "API_VERSION" to "api_version" key_name = ( "api_version" - if "API_VERSION" in env_var["key_name"] + if "API_VERSION" in key_name else key_name ) llm_params[key_name] = env_value From db1b678c3a58d2443b4dab2fb79a31bb10812a2c Mon Sep 17 00:00:00 2001 From: "Brandon Hancock (bhancock_ai)" <109994880+bhancockio@users.noreply.github.com> Date: Tue, 26 Nov 2024 11:36:29 -0500 Subject: [PATCH 081/126] fix spelling issue found by @Jacques-Murray (#1660) --- src/crewai/agent.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/crewai/agent.py b/src/crewai/agent.py index a3b40c2c9..520ee40fd 100644 --- a/src/crewai/agent.py +++ b/src/crewai/agent.py @@ -126,7 +126,7 @@ class Agent(BaseAgent): @model_validator(mode="after") def post_init_setup(self): self.agent_ops_agent_name = self.role - unnacepted_attributes = [ + unaccepted_attributes = [ "AWS_ACCESS_KEY_ID", "AWS_SECRET_ACCESS_KEY", "AWS_REGION_NAME", @@ -162,7 +162,7 @@ class Agent(BaseAgent): for env_var in env_vars: # Check if the environment variable is set key_name = env_var.get("key_name") - if key_name and key_name not in unnacepted_attributes: + if key_name and key_name not in unaccepted_attributes: env_value = os.environ.get(key_name) if env_value: # Map key names containing "API_KEY" to "api_key" From 8bc09eb0545a05fb802f0423483f30e21c772bfc Mon Sep 17 00:00:00 2001 From: Ivan Peevski <133036+ipeevski@users.noreply.github.com> Date: Wed, 27 Nov 2024 04:15:08 +1030 Subject: [PATCH 082/126] Update readme for running mypy (#1614) Co-authored-by: Brandon Hancock (bhancock_ai) <109994880+bhancockio@users.noreply.github.com> --- README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index c6be68dc1..041735354 100644 --- a/README.md +++ b/README.md @@ -121,7 +121,7 @@ researcher: You're a seasoned researcher with a knack for uncovering the latest developments in {topic}. Known for your ability to find the most relevant information and present it in a clear and concise manner. - + reporting_analyst: role: > {topic} Reporting Analyst @@ -205,7 +205,7 @@ class LatestAiDevelopmentCrew(): tasks=self.tasks, # Automatically created by the @task decorator process=Process.sequential, verbose=True, - ) + ) ``` **main.py** @@ -357,7 +357,7 @@ uv run pytest . ### Running static type checks ```bash -uvx mypy +uvx mypy src ``` ### Packaging From 293305790d60ff7a8940ff1fafc1ffb27329f4cd Mon Sep 17 00:00:00 2001 From: Eduardo Chiarotti Date: Tue, 26 Nov 2024 16:59:52 -0300 Subject: [PATCH 083/126] Feat/remove langchain (#1654) * feat: add initial changes from langchain * feat: remove kwargs of being processed * feat: remove langchain, update uv.lock and fix type_hint * feat: change docs * feat: remove forced requirements for parameter * feat add tests for new structure tool * feat: fix tests and adapt code for args --- pyproject.toml | 1 - src/crewai/agent.py | 2 +- src/crewai/tools/base_tool.py | 18 +- src/crewai/tools/cache_tools/cache_tools.py | 5 +- src/crewai/tools/structured_tool.py | 242 ++++++++++++++++++++ tests/tools/test_base_tool.py | 21 +- tests/tools/test_structured_tool.py | 146 ++++++++++++ uv.lock | 2 - 8 files changed, 408 insertions(+), 29 deletions(-) create mode 100644 src/crewai/tools/structured_tool.py create mode 100644 tests/tools/test_structured_tool.py diff --git a/pyproject.toml b/pyproject.toml index 4bd416ab5..1d7d8cc43 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -9,7 +9,6 @@ authors = [ ] dependencies = [ "pydantic>=2.4.2", - "langchain>=0.2.16", "openai>=1.13.3", "opentelemetry-api>=1.22.0", "opentelemetry-sdk>=1.22.0", diff --git a/src/crewai/agent.py b/src/crewai/agent.py index 520ee40fd..30edb3be4 100644 --- a/src/crewai/agent.py +++ b/src/crewai/agent.py @@ -412,7 +412,7 @@ class Agent(BaseAgent): for tool in tools: if isinstance(tool, CrewAITool): - tools_list.append(tool.to_langchain()) + tools_list.append(tool.to_structured_tool()) else: tools_list.append(tool) except ModuleNotFoundError: diff --git a/src/crewai/tools/base_tool.py b/src/crewai/tools/base_tool.py index f41fb7c0b..06e427528 100644 --- a/src/crewai/tools/base_tool.py +++ b/src/crewai/tools/base_tool.py @@ -1,10 +1,11 @@ from abc import ABC, abstractmethod from typing import Any, Callable, Type, get_args, get_origin -from langchain_core.tools import StructuredTool from pydantic import BaseModel, ConfigDict, Field, validator from pydantic import BaseModel as PydanticBaseModel +from crewai.tools.structured_tool import CrewStructuredTool + class BaseTool(BaseModel, ABC): class _ArgsSchemaPlaceholder(PydanticBaseModel): @@ -63,9 +64,10 @@ class BaseTool(BaseModel, ABC): ) -> Any: """Here goes the actual implementation of the tool.""" - def to_langchain(self) -> StructuredTool: + def to_structured_tool(self) -> CrewStructuredTool: + """Convert this tool to a CrewStructuredTool instance.""" self._set_args_schema() - return StructuredTool( + return CrewStructuredTool( name=self.name, description=self.description, args_schema=self.args_schema, @@ -73,10 +75,10 @@ class BaseTool(BaseModel, ABC): ) @classmethod - def from_langchain(cls, tool: StructuredTool) -> "BaseTool": + def from_langchain(cls, tool: CrewStructuredTool) -> "BaseTool": if cls == Tool: if tool.func is None: - raise ValueError("StructuredTool must have a callable 'func'") + raise ValueError("CrewStructuredTool must have a callable 'func'") return Tool( name=tool.name, description=tool.description, @@ -142,9 +144,9 @@ class Tool(BaseTool): def to_langchain( - tools: list[BaseTool | StructuredTool], -) -> list[StructuredTool]: - return [t.to_langchain() if isinstance(t, BaseTool) else t for t in tools] + tools: list[BaseTool | CrewStructuredTool], +) -> list[CrewStructuredTool]: + return [t.to_structured_tool() if isinstance(t, BaseTool) else t for t in tools] def tool(*args): diff --git a/src/crewai/tools/cache_tools/cache_tools.py b/src/crewai/tools/cache_tools/cache_tools.py index a0bb2dbad..a81ce98cf 100644 --- a/src/crewai/tools/cache_tools/cache_tools.py +++ b/src/crewai/tools/cache_tools/cache_tools.py @@ -1,6 +1,7 @@ from pydantic import BaseModel, Field from crewai.agents.cache import CacheHandler +from crewai.tools.structured_tool import CrewStructuredTool class CacheTools(BaseModel): @@ -13,9 +14,7 @@ class CacheTools(BaseModel): ) def tool(self): - from langchain.tools import StructuredTool - - return StructuredTool.from_function( + return CrewStructuredTool.from_function( func=self.hit_cache, name=self.name, description="Reads directly from the cache", diff --git a/src/crewai/tools/structured_tool.py b/src/crewai/tools/structured_tool.py new file mode 100644 index 000000000..bd6818605 --- /dev/null +++ b/src/crewai/tools/structured_tool.py @@ -0,0 +1,242 @@ +from __future__ import annotations + +import inspect +import textwrap +from typing import Any, Callable, Optional, Union, get_type_hints + +from pydantic import BaseModel, Field, create_model + +from crewai.utilities.logger import Logger + + +class CrewStructuredTool: + """A structured tool that can operate on any number of inputs. + + This tool intends to replace StructuredTool with a custom implementation + that integrates better with CrewAI's ecosystem. + """ + + def __init__( + self, + name: str, + description: str, + args_schema: type[BaseModel], + func: Callable[..., Any], + ) -> None: + """Initialize the structured tool. + + Args: + name: The name of the tool + description: A description of what the tool does + args_schema: The pydantic model for the tool's arguments + func: The function to run when the tool is called + """ + self.name = name + self.description = description + self.args_schema = args_schema + self.func = func + self._logger = Logger() + + # Validate the function signature matches the schema + self._validate_function_signature() + + @classmethod + def from_function( + cls, + func: Callable, + name: Optional[str] = None, + description: Optional[str] = None, + return_direct: bool = False, + args_schema: Optional[type[BaseModel]] = None, + infer_schema: bool = True, + **kwargs: Any, + ) -> CrewStructuredTool: + """Create a tool from a function. + + Args: + func: The function to create a tool from + name: The name of the tool. Defaults to the function name + description: The description of the tool. Defaults to the function docstring + return_direct: Whether to return the output directly + args_schema: Optional schema for the function arguments + infer_schema: Whether to infer the schema from the function signature + **kwargs: Additional arguments to pass to the tool + + Returns: + A CrewStructuredTool instance + + Example: + >>> def add(a: int, b: int) -> int: + ... '''Add two numbers''' + ... return a + b + >>> tool = CrewStructuredTool.from_function(add) + """ + name = name or func.__name__ + description = description or inspect.getdoc(func) + + if description is None: + raise ValueError( + f"Function {name} must have a docstring if description not provided." + ) + + # Clean up the description + description = textwrap.dedent(description).strip() + + if args_schema is not None: + # Use provided schema + schema = args_schema + elif infer_schema: + # Infer schema from function signature + schema = cls._create_schema_from_function(name, func) + else: + raise ValueError( + "Either args_schema must be provided or infer_schema must be True." + ) + + return cls( + name=name, + description=description, + args_schema=schema, + func=func, + ) + + @staticmethod + def _create_schema_from_function( + name: str, + func: Callable, + ) -> type[BaseModel]: + """Create a Pydantic schema from a function's signature. + + Args: + name: The name to use for the schema + func: The function to create a schema from + + Returns: + A Pydantic model class + """ + # Get function signature + sig = inspect.signature(func) + + # Get type hints + type_hints = get_type_hints(func) + + # Create field definitions + fields = {} + for param_name, param in sig.parameters.items(): + # Skip self/cls for methods + if param_name in ("self", "cls"): + continue + + # Get type annotation + annotation = type_hints.get(param_name, Any) + + # Get default value + default = ... if param.default == param.empty else param.default + + # Add field + fields[param_name] = (annotation, Field(default=default)) + + # Create model + schema_name = f"{name.title()}Schema" + return create_model(schema_name, **fields) + + def _validate_function_signature(self) -> None: + """Validate that the function signature matches the args schema.""" + sig = inspect.signature(self.func) + schema_fields = self.args_schema.model_fields + + # Check required parameters + for param_name, param in sig.parameters.items(): + # Skip self/cls for methods + if param_name in ("self", "cls"): + continue + + # Skip **kwargs parameters + if param.kind in ( + inspect.Parameter.VAR_KEYWORD, + inspect.Parameter.VAR_POSITIONAL, + ): + continue + + # Only validate required parameters without defaults + if param.default == inspect.Parameter.empty: + if param_name not in schema_fields: + raise ValueError( + f"Required function parameter '{param_name}' " + f"not found in args_schema" + ) + + def _parse_args(self, raw_args: Union[str, dict]) -> dict: + """Parse and validate the input arguments against the schema. + + Args: + raw_args: The raw arguments to parse, either as a string or dict + + Returns: + The validated arguments as a dictionary + """ + if isinstance(raw_args, str): + try: + import json + + raw_args = json.loads(raw_args) + except json.JSONDecodeError as e: + raise ValueError(f"Failed to parse arguments as JSON: {e}") + + try: + validated_args = self.args_schema.model_validate(raw_args) + return validated_args.model_dump() + except Exception as e: + raise ValueError(f"Arguments validation failed: {e}") + + async def ainvoke( + self, + input: Union[str, dict], + config: Optional[dict] = None, + **kwargs: Any, + ) -> Any: + """Asynchronously invoke the tool. + + Args: + input: The input arguments + config: Optional configuration + **kwargs: Additional keyword arguments + + Returns: + The result of the tool execution + """ + parsed_args = self._parse_args(input) + + if inspect.iscoroutinefunction(self.func): + return await self.func(**parsed_args, **kwargs) + else: + # Run sync functions in a thread pool + import asyncio + + return await asyncio.get_event_loop().run_in_executor( + None, lambda: self.func(**parsed_args, **kwargs) + ) + + def _run(self, *args, **kwargs) -> Any: + """Legacy method for compatibility.""" + # Convert args/kwargs to our expected format + input_dict = dict(zip(self.args_schema.model_fields.keys(), args)) + input_dict.update(kwargs) + return self.invoke(input_dict) + + def invoke( + self, input: Union[str, dict], config: Optional[dict] = None, **kwargs: Any + ) -> Any: + """Main method for tool execution.""" + parsed_args = self._parse_args(input) + return self.func(**parsed_args, **kwargs) + + @property + def args(self) -> dict: + """Get the tool's input arguments schema.""" + return self.args_schema.model_json_schema()["properties"] + + def __repr__(self) -> str: + return ( + f"CrewStructuredTool(name='{self.name}', description='{self.description}')" + ) diff --git a/tests/tools/test_base_tool.py b/tests/tools/test_base_tool.py index eca36739c..cd4b53caf 100644 --- a/tests/tools/test_base_tool.py +++ b/tests/tools/test_base_tool.py @@ -1,4 +1,5 @@ from typing import Callable + from crewai.tools import BaseTool, tool @@ -21,8 +22,7 @@ def test_creating_a_tool_using_annotation(): my_tool.func("What is the meaning of life?") == "What is the meaning of life?" ) - # Assert the langchain tool conversion worked as expected - converted_tool = my_tool.to_langchain() + converted_tool = my_tool.to_structured_tool() assert converted_tool.name == "Name of my tool" assert ( @@ -41,9 +41,7 @@ def test_creating_a_tool_using_annotation(): def test_creating_a_tool_using_baseclass(): class MyCustomTool(BaseTool): name: str = "Name of my tool" - description: str = ( - "Clear description for what this tool is useful for, you agent will need this information to use it." - ) + description: str = "Clear description for what this tool is useful for, you agent will need this information to use it." def _run(self, question: str) -> str: return question @@ -61,8 +59,7 @@ def test_creating_a_tool_using_baseclass(): } assert my_tool.run("What is the meaning of life?") == "What is the meaning of life?" - # Assert the langchain tool conversion worked as expected - converted_tool = my_tool.to_langchain() + converted_tool = my_tool.to_structured_tool() assert converted_tool.name == "Name of my tool" assert ( @@ -73,7 +70,7 @@ def test_creating_a_tool_using_baseclass(): "question": {"title": "Question", "type": "string"} } assert ( - converted_tool.run("What is the meaning of life?") + converted_tool._run("What is the meaning of life?") == "What is the meaning of life?" ) @@ -81,9 +78,7 @@ def test_creating_a_tool_using_baseclass(): def test_setting_cache_function(): class MyCustomTool(BaseTool): name: str = "Name of my tool" - description: str = ( - "Clear description for what this tool is useful for, you agent will need this information to use it." - ) + description: str = "Clear description for what this tool is useful for, you agent will need this information to use it." cache_function: Callable = lambda: False def _run(self, question: str) -> str: @@ -97,9 +92,7 @@ def test_setting_cache_function(): def test_default_cache_function_is_true(): class MyCustomTool(BaseTool): name: str = "Name of my tool" - description: str = ( - "Clear description for what this tool is useful for, you agent will need this information to use it." - ) + description: str = "Clear description for what this tool is useful for, you agent will need this information to use it." def _run(self, question: str) -> str: return question diff --git a/tests/tools/test_structured_tool.py b/tests/tools/test_structured_tool.py new file mode 100644 index 000000000..32ebd805b --- /dev/null +++ b/tests/tools/test_structured_tool.py @@ -0,0 +1,146 @@ +from typing import Optional + +import pytest +from pydantic import BaseModel, Field + +from crewai.tools.structured_tool import CrewStructuredTool + + +# Test fixtures +@pytest.fixture +def basic_function(): + def test_func(param1: str, param2: int = 0) -> str: + """Test function with basic params.""" + return f"{param1} {param2}" + + return test_func + + +@pytest.fixture +def schema_class(): + class TestSchema(BaseModel): + param1: str + param2: int = Field(default=0) + + return TestSchema + + +class TestCrewStructuredTool: + def test_initialization(self, basic_function, schema_class): + """Test basic initialization of CrewStructuredTool""" + tool = CrewStructuredTool( + name="test_tool", + description="Test tool description", + func=basic_function, + args_schema=schema_class, + ) + + assert tool.name == "test_tool" + assert tool.description == "Test tool description" + assert tool.func == basic_function + assert tool.args_schema == schema_class + + def test_from_function(self, basic_function): + """Test creating tool from function""" + tool = CrewStructuredTool.from_function( + func=basic_function, name="test_tool", description="Test description" + ) + + assert tool.name == "test_tool" + assert tool.description == "Test description" + assert tool.func == basic_function + assert isinstance(tool.args_schema, type(BaseModel)) + + def test_validate_function_signature(self, basic_function, schema_class): + """Test function signature validation""" + tool = CrewStructuredTool( + name="test_tool", + description="Test tool", + func=basic_function, + args_schema=schema_class, + ) + + # Should not raise any exceptions + tool._validate_function_signature() + + @pytest.mark.asyncio + async def test_ainvoke(self, basic_function): + """Test asynchronous invocation""" + tool = CrewStructuredTool.from_function(func=basic_function, name="test_tool") + + result = await tool.ainvoke(input={"param1": "test"}) + assert result == "test 0" + + def test_parse_args_dict(self, basic_function): + """Test parsing dictionary arguments""" + tool = CrewStructuredTool.from_function(func=basic_function, name="test_tool") + + parsed = tool._parse_args({"param1": "test", "param2": 42}) + assert parsed["param1"] == "test" + assert parsed["param2"] == 42 + + def test_parse_args_string(self, basic_function): + """Test parsing string arguments""" + tool = CrewStructuredTool.from_function(func=basic_function, name="test_tool") + + parsed = tool._parse_args('{"param1": "test", "param2": 42}') + assert parsed["param1"] == "test" + assert parsed["param2"] == 42 + + def test_complex_types(self): + """Test handling of complex parameter types""" + + def complex_func(nested: dict, items: list) -> str: + """Process complex types.""" + return f"Processed {len(items)} items with {len(nested)} nested keys" + + tool = CrewStructuredTool.from_function( + func=complex_func, name="test_tool", description="Test complex types" + ) + result = tool.invoke({"nested": {"key": "value"}, "items": [1, 2, 3]}) + assert result == "Processed 3 items with 1 nested keys" + + def test_schema_inheritance(self): + """Test tool creation with inherited schema""" + + def extended_func(base_param: str, extra_param: int) -> str: + """Test function with inherited schema.""" + return f"{base_param} {extra_param}" + + class BaseSchema(BaseModel): + base_param: str + + class ExtendedSchema(BaseSchema): + extra_param: int + + tool = CrewStructuredTool.from_function( + func=extended_func, name="test_tool", args_schema=ExtendedSchema + ) + + result = tool.invoke({"base_param": "test", "extra_param": 42}) + assert result == "test 42" + + def test_default_values_in_schema(self): + """Test handling of default values in schema""" + + def default_func( + required_param: str, + optional_param: str = "default", + nullable_param: Optional[int] = None, + ) -> str: + """Test function with default values.""" + return f"{required_param} {optional_param} {nullable_param}" + + tool = CrewStructuredTool.from_function( + func=default_func, name="test_tool", description="Test defaults" + ) + + # Test with minimal parameters + result = tool.invoke({"required_param": "test"}) + assert result == "test default None" + + # Test with all parameters + result = tool.invoke( + {"required_param": "test", "optional_param": "custom", "nullable_param": 42} + ) + assert result == "test custom 42" diff --git a/uv.lock b/uv.lock index baff09ac8..050602e61 100644 --- a/uv.lock +++ b/uv.lock @@ -619,7 +619,6 @@ dependencies = [ { name = "instructor" }, { name = "json-repair" }, { name = "jsonref" }, - { name = "langchain" }, { name = "litellm" }, { name = "openai" }, { name = "openpyxl" }, @@ -692,7 +691,6 @@ requires-dist = [ { name = "instructor", specifier = ">=1.3.3" }, { name = "json-repair", specifier = ">=0.25.2" }, { name = "jsonref", specifier = ">=1.1.0" }, - { name = "langchain", specifier = ">=0.2.16" }, { name = "litellm", specifier = ">=1.44.22" }, { name = "mem0ai", marker = "extra == 'mem0'", specifier = ">=0.1.29" }, { name = "openai", specifier = ">=1.13.3" }, From 366bbbbea3d6c0e341cb4dd4ef3584ef05c86e40 Mon Sep 17 00:00:00 2001 From: "Brandon Hancock (bhancock_ai)" <109994880+bhancockio@users.noreply.github.com> Date: Wed, 27 Nov 2024 11:22:49 -0500 Subject: [PATCH 084/126] Feat/remove langchain (#1668) * feat: add initial changes from langchain * feat: remove kwargs of being processed * feat: remove langchain, update uv.lock and fix type_hint * feat: change docs * feat: remove forced requirements for parameter * feat add tests for new structure tool * feat: fix tests and adapt code for args * fix tool calling for langchain tools * doc strings --------- Co-authored-by: Eduardo Chiarotti --- src/crewai/agents/agent_builder/base_agent.py | 32 ++++- src/crewai/tools/base_tool.py | 110 +++++++++++++++--- 2 files changed, 128 insertions(+), 14 deletions(-) diff --git a/src/crewai/agents/agent_builder/base_agent.py b/src/crewai/agents/agent_builder/base_agent.py index 55315e7ff..207a1769a 100644 --- a/src/crewai/agents/agent_builder/base_agent.py +++ b/src/crewai/agents/agent_builder/base_agent.py @@ -19,6 +19,7 @@ from crewai.agents.agent_builder.utilities.base_token_process import TokenProces from crewai.agents.cache.cache_handler import CacheHandler from crewai.agents.tools_handler import ToolsHandler from crewai.tools import BaseTool +from crewai.tools.base_tool import Tool from crewai.utilities import I18N, Logger, RPMController from crewai.utilities.config import process_config @@ -106,7 +107,7 @@ class BaseAgent(ABC, BaseModel): default=False, description="Enable agent to delegate and ask questions among each other.", ) - tools: Optional[List[BaseTool]] = Field( + tools: Optional[List[Any]] = Field( default_factory=list, description="Tools at agents' disposal" ) max_iter: Optional[int] = Field( @@ -135,6 +136,35 @@ class BaseAgent(ABC, BaseModel): def process_model_config(cls, values): return process_config(values, cls) + @field_validator("tools") + @classmethod + def validate_tools(cls, tools: List[Any]) -> List[BaseTool]: + """Validate and process the tools provided to the agent. + + This method ensures that each tool is either an instance of BaseTool + or an object with 'name', 'func', and 'description' attributes. If the + tool meets these criteria, it is processed and added to the list of + tools. Otherwise, a ValueError is raised. + """ + processed_tools = [] + for tool in tools: + if isinstance(tool, BaseTool): + processed_tools.append(tool) + elif ( + hasattr(tool, "name") + and hasattr(tool, "func") + and hasattr(tool, "description") + ): + # Tool has the required attributes, create a Tool instance + processed_tools.append(Tool.from_langchain(tool)) + else: + raise ValueError( + f"Invalid tool type: {type(tool)}. " + "Tool must be an instance of BaseTool or " + "an object with 'name', 'func', and 'description' attributes." + ) + return processed_tools + @model_validator(mode="after") def validate_and_set_attributes(self): # Validate required fields diff --git a/src/crewai/tools/base_tool.py b/src/crewai/tools/base_tool.py index 06e427528..46bfc03a6 100644 --- a/src/crewai/tools/base_tool.py +++ b/src/crewai/tools/base_tool.py @@ -1,7 +1,8 @@ from abc import ABC, abstractmethod +from inspect import signature from typing import Any, Callable, Type, get_args, get_origin -from pydantic import BaseModel, ConfigDict, Field, validator +from pydantic import BaseModel, ConfigDict, Field, create_model, validator from pydantic import BaseModel as PydanticBaseModel from crewai.tools.structured_tool import CrewStructuredTool @@ -75,17 +76,47 @@ class BaseTool(BaseModel, ABC): ) @classmethod - def from_langchain(cls, tool: CrewStructuredTool) -> "BaseTool": - if cls == Tool: - if tool.func is None: - raise ValueError("CrewStructuredTool must have a callable 'func'") - return Tool( - name=tool.name, - description=tool.description, - args_schema=tool.args_schema, - func=tool.func, - ) - raise NotImplementedError(f"from_langchain not implemented for {cls.__name__}") + def from_langchain(cls, tool: Any) -> "BaseTool": + """Create a Tool instance from a CrewStructuredTool. + + This method takes a CrewStructuredTool object and converts it into a + Tool instance. It ensures that the provided tool has a callable 'func' + attribute and infers the argument schema if not explicitly provided. + """ + if not hasattr(tool, "func") or not callable(tool.func): + raise ValueError("The provided tool must have a callable 'func' attribute.") + + args_schema = getattr(tool, "args_schema", None) + + if args_schema is None: + # Infer args_schema from the function signature if not provided + func_signature = signature(tool.func) + annotations = func_signature.parameters + args_fields = {} + for name, param in annotations.items(): + if name != "self": + param_annotation = ( + param.annotation if param.annotation != param.empty else Any + ) + field_info = Field( + default=..., + description="", + ) + args_fields[name] = (param_annotation, field_info) + if args_fields: + args_schema = create_model(f"{tool.name}Input", **args_fields) + else: + # Create a default schema with no fields if no parameters are found + args_schema = create_model( + f"{tool.name}Input", __base__=PydanticBaseModel + ) + + return cls( + name=getattr(tool, "name", "Unnamed Tool"), + description=getattr(tool, "description", ""), + func=tool.func, + args_schema=args_schema, + ) def _set_args_schema(self): if self.args_schema is None: @@ -136,12 +167,65 @@ class BaseTool(BaseModel, ABC): class Tool(BaseTool): - func: Callable """The function that will be executed when the tool is called.""" + func: Callable + def _run(self, *args: Any, **kwargs: Any) -> Any: return self.func(*args, **kwargs) + @classmethod + def from_langchain(cls, tool: Any) -> "Tool": + """Create a Tool instance from a CrewStructuredTool. + + This method takes a CrewStructuredTool object and converts it into a + Tool instance. It ensures that the provided tool has a callable 'func' + attribute and infers the argument schema if not explicitly provided. + + Args: + tool (Any): The CrewStructuredTool object to be converted. + + Returns: + Tool: A new Tool instance created from the provided CrewStructuredTool. + + Raises: + ValueError: If the provided tool does not have a callable 'func' attribute. + """ + if not hasattr(tool, "func") or not callable(tool.func): + raise ValueError("The provided tool must have a callable 'func' attribute.") + + args_schema = getattr(tool, "args_schema", None) + + if args_schema is None: + # Infer args_schema from the function signature if not provided + func_signature = signature(tool.func) + annotations = func_signature.parameters + args_fields = {} + for name, param in annotations.items(): + if name != "self": + param_annotation = ( + param.annotation if param.annotation != param.empty else Any + ) + field_info = Field( + default=..., + description="", + ) + args_fields[name] = (param_annotation, field_info) + if args_fields: + args_schema = create_model(f"{tool.name}Input", **args_fields) + else: + # Create a default schema with no fields if no parameters are found + args_schema = create_model( + f"{tool.name}Input", __base__=PydanticBaseModel + ) + + return cls( + name=getattr(tool, "name", "Unnamed Tool"), + description=getattr(tool, "description", ""), + func=tool.func, + args_schema=args_schema, + ) + def to_langchain( tools: list[BaseTool | CrewStructuredTool], From c6a6c918e0eba167be1fb82831c73dd664c641e3 Mon Sep 17 00:00:00 2001 From: Lorenze Jay <63378463+lorenzejay@users.noreply.github.com> Date: Wed, 27 Nov 2024 11:33:07 -0800 Subject: [PATCH 085/126] added knowledge to agent level (#1655) * added knowledge to agent level * linted * added doc * added from suggestions * added test * fixes from discussion * fix docs * fix test * rm cassette for knowledge_sources test as its a mock and update agent doc string * fix test * rm unused * linted --- docs/concepts/knowledge.mdx | 38 +- src/crewai/agent.py | 59 ++- src/crewai/crew.py | 34 +- src/crewai/knowledge/knowledge.py | 38 +- .../knowledge/source/base_knowledge_source.py | 3 +- .../source/string_knowledge_source.py | 3 +- .../knowledge/storage/knowledge_storage.py | 87 +++- src/crewai/knowledge/utils/knowledge_utils.py | 12 + tests/agent_test.py | 19 +- .../test_agent_with_knowledge_sources.yaml | 452 +++++++++++++++++- 10 files changed, 654 insertions(+), 91 deletions(-) create mode 100644 src/crewai/knowledge/utils/knowledge_utils.py diff --git a/docs/concepts/knowledge.mdx b/docs/concepts/knowledge.mdx index ce0203e13..e51602947 100644 --- a/docs/concepts/knowledge.mdx +++ b/docs/concepts/knowledge.mdx @@ -51,12 +51,41 @@ crew = Crew( tasks=[task], verbose=True, process=Process.sequential, - knowledge={"sources": [string_source], "metadata": {"preference": "personal"}}, # Enable knowledge by adding the sources here. You can also add more sources to the sources list. + knowledge_sources=[string_source], # Enable knowledge by adding the sources here. ) result = crew.kickoff(inputs={"question": "What city does John live in and how old is he?"}) ``` +## Appending Knowledge Sources To Individual Agents +Sometimes you may want to append knowledge sources to an individual agent. You can do this by setting the `knowledge` parameter in the `Agent` class. + +```python +agent = Agent( + ... + knowledge_sources=[ + StringKnowledgeSource( + content="Users name is John. He is 30 years old and lives in San Francisco.", + metadata={"preference": "personal"}, + ) + ], +) +``` + +## Agent Level Knowledge Sources + +You can also append knowledge sources to an individual agent by setting the `knowledge_sources` parameter in the `Agent` class. + +```python +string_source = StringKnowledgeSource( + content="Users name is John. He is 30 years old and lives in San Francisco.", + metadata={"preference": "personal"}, +) +agent = Agent( + ... + knowledge_sources=[string_source], +) +``` ## Embedder Configuration @@ -70,10 +99,7 @@ string_source = StringKnowledgeSource( ) crew = Crew( ... - knowledge={ - "sources": [string_source], - "metadata": {"preference": "personal"}, - "embedder_config": {"provider": "openai", "config": {"model": "text-embedding-3-small"}}, - }, + knowledge_sources=[string_source], + embedder_config={"provider": "ollama", "config": {"model": "nomic-embed-text:latest"}}, ) ``` diff --git a/src/crewai/agent.py b/src/crewai/agent.py index 30edb3be4..26380ebc2 100644 --- a/src/crewai/agent.py +++ b/src/crewai/agent.py @@ -1,7 +1,7 @@ import os import shutil import subprocess -from typing import Any, List, Literal, Optional, Union +from typing import Any, List, Literal, Optional, Union, Dict from pydantic import Field, InstanceOf, PrivateAttr, model_validator @@ -10,6 +10,8 @@ from crewai.agents.agent_builder.base_agent import BaseAgent from crewai.agents.crew_agent_executor import CrewAgentExecutor from crewai.cli.constants import ENV_VARS from crewai.llm import LLM +from crewai.knowledge.knowledge import Knowledge +from crewai.knowledge.source.base_knowledge_source import BaseKnowledgeSource from crewai.memory.contextual.contextual_memory import ContextualMemory from crewai.task import Task from crewai.tools import BaseTool @@ -19,6 +21,7 @@ from crewai.utilities.constants import TRAINED_AGENTS_DATA_FILE, TRAINING_DATA_F from crewai.utilities.converter import generate_model_description from crewai.utilities.token_counter_callback import TokenCalcHandler from crewai.utilities.training_handler import CrewTrainingHandler +from crewai.knowledge.utils.knowledge_utils import extract_knowledge_context def mock_agent_ops_provider(): @@ -65,6 +68,7 @@ class Agent(BaseAgent): allow_delegation: Whether the agent is allowed to delegate tasks to other agents. tools: Tools at agents disposal step_callback: Callback to be executed after each step of the agent execution. + knowledge_sources: Knowledge sources for the agent. """ _times_executed: int = PrivateAttr(default=0) @@ -122,9 +126,21 @@ class Agent(BaseAgent): default="safe", description="Mode for code execution: 'safe' (using Docker) or 'unsafe' (direct execution).", ) + embedder_config: Optional[Dict[str, Any]] = Field( + default=None, + description="Embedder configuration for the agent.", + ) + knowledge_sources: Optional[List[BaseKnowledgeSource]] = Field( + default=None, + description="Knowledge sources for the agent.", + ) + _knowledge: Optional[Knowledge] = PrivateAttr( + default=None, + ) @model_validator(mode="after") def post_init_setup(self): + self._set_knowledge() self.agent_ops_agent_name = self.role unaccepted_attributes = [ "AWS_ACCESS_KEY_ID", @@ -232,6 +248,21 @@ class Agent(BaseAgent): self.cache_handler = CacheHandler() self.set_cache_handler(self.cache_handler) + def _set_knowledge(self): + try: + if self.knowledge_sources: + knowledge_agent_name = f"{self.role.replace(' ', '_')}" + if isinstance(self.knowledge_sources, list) and all( + isinstance(k, BaseKnowledgeSource) for k in self.knowledge_sources + ): + self._knowledge = Knowledge( + sources=self.knowledge_sources, + embedder_config=self.embedder_config, + collection_name=knowledge_agent_name, + ) + except (TypeError, ValueError) as e: + raise ValueError(f"Invalid Knowledge Configuration: {str(e)}") + def execute_task( self, task: Task, @@ -286,17 +317,21 @@ class Agent(BaseAgent): if memory.strip() != "": task_prompt += self.i18n.slice("memory").format(memory=memory) - # Integrate the knowledge base - if self.crew and self.crew.knowledge: - knowledge_snippets = self.crew.knowledge.query([task.prompt()]) - valid_snippets = [ - result["context"] - for result in knowledge_snippets - if result and result.get("context") - ] - if valid_snippets: - formatted_knowledge = "\n".join(valid_snippets) - task_prompt += f"\n\nAdditional Information:\n{formatted_knowledge}" + if self._knowledge: + agent_knowledge_snippets = self._knowledge.query([task.prompt()]) + if agent_knowledge_snippets: + agent_knowledge_context = extract_knowledge_context( + agent_knowledge_snippets + ) + if agent_knowledge_context: + task_prompt += agent_knowledge_context + + if self.crew: + knowledge_snippets = self.crew.query_knowledge([task.prompt()]) + if knowledge_snippets: + crew_knowledge_context = extract_knowledge_context(knowledge_snippets) + if crew_knowledge_context: + task_prompt += crew_knowledge_context tools = tools or self.tools or [] self.create_agent_executor(tools=tools, task=task) diff --git a/src/crewai/crew.py b/src/crewai/crew.py index 4c3886a3f..3b6ee44b4 100644 --- a/src/crewai/crew.py +++ b/src/crewai/crew.py @@ -28,6 +28,7 @@ from crewai.memory.entity.entity_memory import EntityMemory from crewai.memory.long_term.long_term_memory import LongTermMemory from crewai.memory.short_term.short_term_memory import ShortTermMemory from crewai.knowledge.knowledge import Knowledge +from crewai.knowledge.source.base_knowledge_source import BaseKnowledgeSource from crewai.memory.user.user_memory import UserMemory from crewai.process import Process from crewai.task import Task @@ -202,10 +203,13 @@ class Crew(BaseModel): default=[], description="List of execution logs for tasks", ) - knowledge: Optional[Dict[str, Any]] = Field( - default=None, description="Knowledge for the crew. Add knowledge sources to the knowledge object." + knowledge_sources: Optional[List[BaseKnowledgeSource]] = Field( + default=None, + description="Knowledge sources for the crew. Add knowledge sources to the knowledge object.", + ) + _knowledge: Optional[Knowledge] = PrivateAttr( + default=None, ) - @field_validator("id", mode="before") @classmethod @@ -282,11 +286,22 @@ class Crew(BaseModel): @model_validator(mode="after") def create_crew_knowledge(self) -> "Crew": - if self.knowledge: + """Create the knowledge for the crew.""" + if self.knowledge_sources: try: - self.knowledge = Knowledge(**self.knowledge) if isinstance(self.knowledge, dict) else self.knowledge - except (TypeError, ValueError) as e: - raise ValueError(f"Invalid knowledge configuration: {str(e)}") + if isinstance(self.knowledge_sources, list) and all( + isinstance(k, BaseKnowledgeSource) for k in self.knowledge_sources + ): + self._knowledge = Knowledge( + sources=self.knowledge_sources, + embedder_config=self.embedder, + collection_name="crew", + ) + + except Exception as e: + self._logger.log( + "warning", f"Failed to init knowledge: {e}", color="yellow" + ) return self @model_validator(mode="after") @@ -942,6 +957,11 @@ class Crew(BaseModel): result = self._execute_tasks(self.tasks, start_index, True) return result + def query_knowledge(self, query: List[str]) -> Union[List[Dict[str, Any]], None]: + if self._knowledge: + return self._knowledge.query(query) + return None + def copy(self): """Create a deep copy of the Crew.""" diff --git a/src/crewai/knowledge/knowledge.py b/src/crewai/knowledge/knowledge.py index cf2907e67..4a1331799 100644 --- a/src/crewai/knowledge/knowledge.py +++ b/src/crewai/knowledge/knowledge.py @@ -5,8 +5,8 @@ from pydantic import BaseModel, ConfigDict, Field from crewai.knowledge.source.base_knowledge_source import BaseKnowledgeSource from crewai.knowledge.storage.knowledge_storage import KnowledgeStorage -from crewai.utilities.logger import Logger from crewai.utilities.constants import DEFAULT_SCORE_THRESHOLD + os.environ["TOKENIZERS_PARALLELISM"] = "false" # removes logging from fastembed @@ -18,24 +18,33 @@ class Knowledge(BaseModel): storage: KnowledgeStorage = Field(default_factory=KnowledgeStorage) embedder_config: Optional[Dict[str, Any]] = None """ + sources: List[BaseKnowledgeSource] = Field(default_factory=list) model_config = ConfigDict(arbitrary_types_allowed=True) storage: KnowledgeStorage = Field(default_factory=KnowledgeStorage) embedder_config: Optional[Dict[str, Any]] = None + collection_name: Optional[str] = None - def __init__(self, embedder_config: Optional[Dict[str, Any]] = None, **data): + def __init__( + self, + collection_name: str, + sources: List[BaseKnowledgeSource], + embedder_config: Optional[Dict[str, Any]] = None, + storage: Optional[KnowledgeStorage] = None, + **data, + ): super().__init__(**data) - self.storage = KnowledgeStorage(embedder_config=embedder_config or None) - - try: - for source in self.sources: - source.add() - except Exception as e: - Logger(verbose=True).log( - "warning", - f"Failed to init knowledge: {e}", - color="yellow", + if storage: + self.storage = storage + else: + self.storage = KnowledgeStorage( + embedder_config=embedder_config, collection_name=collection_name ) + self.sources = sources + self.storage.initialize_knowledge_storage() + for source in sources: + source.storage = self.storage + source.add() def query( self, query: List[str], limit: int = 3, preference: Optional[str] = None @@ -52,3 +61,8 @@ class Knowledge(BaseModel): score_threshold=DEFAULT_SCORE_THRESHOLD, ) return results + + def _add_sources(self): + for source in self.sources: + source.storage = self.storage + source.add() diff --git a/src/crewai/knowledge/source/base_knowledge_source.py b/src/crewai/knowledge/source/base_knowledge_source.py index bb4c69cf3..6be76ca40 100644 --- a/src/crewai/knowledge/source/base_knowledge_source.py +++ b/src/crewai/knowledge/source/base_knowledge_source.py @@ -1,5 +1,5 @@ from abc import ABC, abstractmethod -from typing import List, Dict, Any +from typing import List, Dict, Any, Optional import numpy as np from pydantic import BaseModel, ConfigDict, Field @@ -18,6 +18,7 @@ class BaseKnowledgeSource(BaseModel, ABC): model_config = ConfigDict(arbitrary_types_allowed=True) storage: KnowledgeStorage = Field(default_factory=KnowledgeStorage) metadata: Dict[str, Any] = Field(default_factory=dict) + collection_name: Optional[str] = Field(default=None) @abstractmethod def load_content(self) -> Dict[Any, str]: diff --git a/src/crewai/knowledge/source/string_knowledge_source.py b/src/crewai/knowledge/source/string_knowledge_source.py index d4c22e3c1..7336fd3ea 100644 --- a/src/crewai/knowledge/source/string_knowledge_source.py +++ b/src/crewai/knowledge/source/string_knowledge_source.py @@ -1,4 +1,4 @@ -from typing import List +from typing import List, Optional from pydantic import Field @@ -9,6 +9,7 @@ class StringKnowledgeSource(BaseKnowledgeSource): """A knowledge source that stores and queries plain text content using embeddings.""" content: str = Field(...) + collection_name: Optional[str] = Field(default=None) def model_post_init(self, _): """Post-initialization method to validate content.""" diff --git a/src/crewai/knowledge/storage/knowledge_storage.py b/src/crewai/knowledge/storage/knowledge_storage.py index b3d5ba750..79aa385a8 100644 --- a/src/crewai/knowledge/storage/knowledge_storage.py +++ b/src/crewai/knowledge/storage/knowledge_storage.py @@ -3,12 +3,16 @@ import io import logging import chromadb import os + +import chromadb.errors from crewai.utilities.paths import db_storage_path -from typing import Optional, List -from typing import Dict, Any +from typing import Optional, List, Dict, Any, Union from crewai.utilities import EmbeddingConfigurator from crewai.knowledge.storage.base_knowledge_storage import BaseKnowledgeStorage import hashlib +from chromadb.config import Settings +from chromadb.api import ClientAPI +from crewai.utilities.logger import Logger @contextlib.contextmanager @@ -35,9 +39,16 @@ class KnowledgeStorage(BaseKnowledgeStorage): """ collection: Optional[chromadb.Collection] = None + collection_name: Optional[str] = "knowledge" + app: Optional[ClientAPI] = None - def __init__(self, embedder_config: Optional[Dict[str, Any]] = None): - self._initialize_app(embedder_config or {}) + def __init__( + self, + embedder_config: Optional[Dict[str, Any]] = None, + collection_name: Optional[str] = None, + ): + self.collection_name = collection_name + self._set_embedder_config(embedder_config) def search( self, @@ -67,43 +78,75 @@ class KnowledgeStorage(BaseKnowledgeStorage): else: raise Exception("Collection not initialized") - def _initialize_app(self, embedder_config: Optional[Dict[str, Any]] = None): - import chromadb - from chromadb.config import Settings - - self._set_embedder_config(embedder_config) - + def initialize_knowledge_storage(self): + base_path = os.path.join(db_storage_path(), "knowledge") chroma_client = chromadb.PersistentClient( - path=f"{db_storage_path()}/knowledge", + path=base_path, settings=Settings(allow_reset=True), ) self.app = chroma_client try: - self.collection = self.app.get_or_create_collection(name="knowledge") + collection_name = ( + f"knowledge_{self.collection_name}" + if self.collection_name + else "knowledge" + ) + if self.app: + self.collection = self.app.get_or_create_collection( + name=collection_name, embedding_function=self.embedder_config + ) + else: + raise Exception("Vector Database Client not initialized") except Exception: raise Exception("Failed to create or get collection") def reset(self): if self.app: self.app.reset() + else: + base_path = os.path.join(db_storage_path(), "knowledge") + self.app = chromadb.PersistentClient( + path=base_path, + settings=Settings(allow_reset=True), + ) + self.app.reset() def save( - self, documents: List[str], metadata: Dict[str, Any] | List[Dict[str, Any]] + self, + documents: List[str], + metadata: Union[Dict[str, Any], List[Dict[str, Any]]], ): if self.collection: - metadatas = [metadata] if isinstance(metadata, dict) else metadata + try: + metadatas = [metadata] if isinstance(metadata, dict) else metadata - ids = [ - hashlib.sha256(doc.encode("utf-8")).hexdigest() for doc in documents - ] + ids = [ + hashlib.sha256(doc.encode("utf-8")).hexdigest() for doc in documents + ] - self.collection.upsert( - documents=documents, - metadatas=metadatas, - ids=ids, - ) + self.collection.upsert( + documents=documents, + metadatas=metadatas, + ids=ids, + ) + except chromadb.errors.InvalidDimensionException as e: + Logger(verbose=True).log( + "error", + "Embedding dimension mismatch. This usually happens when mixing different embedding models. Try resetting the collection using `crewai reset-memories -a`", + "red", + ) + raise ValueError( + "Embedding dimension mismatch. Make sure you're using the same embedding model " + "across all operations with this collection." + "Try resetting the collection using `crewai reset-memories -a`" + ) from e + except Exception as e: + Logger(verbose=True).log( + "error", f"Failed to upsert documents: {e}", "red" + ) + raise else: raise Exception("Collection not initialized") diff --git a/src/crewai/knowledge/utils/knowledge_utils.py b/src/crewai/knowledge/utils/knowledge_utils.py new file mode 100644 index 000000000..bdd8b9a4e --- /dev/null +++ b/src/crewai/knowledge/utils/knowledge_utils.py @@ -0,0 +1,12 @@ +from typing import Any, Dict, List + + +def extract_knowledge_context(knowledge_snippets: List[Dict[str, Any]]) -> str: + """Extract knowledge from the task prompt.""" + valid_snippets = [ + result["context"] + for result in knowledge_snippets + if result and result.get("context") + ] + snippet = "\n".join(valid_snippets) + return f"Additional Information: {snippet}" if valid_snippets else "" diff --git a/tests/agent_test.py b/tests/agent_test.py index fb6ab22b8..1f4b62fd0 100644 --- a/tests/agent_test.py +++ b/tests/agent_test.py @@ -3,20 +3,19 @@ import os from unittest import mock from unittest.mock import patch - import pytest from crewai import Agent, Crew, Task from crewai.agents.cache import CacheHandler from crewai.agents.crew_agent_executor import CrewAgentExecutor from crewai.agents.parser import AgentAction, CrewAgentParser, OutputParserException -from crewai.knowledge.source.string_knowledge_source import StringKnowledgeSource from crewai.llm import LLM from crewai.tools import tool from crewai.tools.tool_calling import InstructorToolCalling from crewai.tools.tool_usage import ToolUsage from crewai.tools.tool_usage_events import ToolUsageFinished from crewai.utilities import RPMController +from crewai.knowledge.source.string_knowledge_source import StringKnowledgeSource from crewai.utilities.events import Emitter @@ -1584,21 +1583,22 @@ def test_agent_with_knowledge_sources(): string_source = StringKnowledgeSource( content=content, metadata={"preference": "personal"} ) - - with patch('crewai.knowledge.storage.knowledge_storage.KnowledgeStorage') as MockKnowledge: + with patch( + "crewai.knowledge.storage.knowledge_storage.KnowledgeStorage" + ) as MockKnowledge: mock_knowledge_instance = MockKnowledge.return_value mock_knowledge_instance.sources = [string_source] - mock_knowledge_instance.query.return_value = [{ - "content": content, - "metadata": {"preference": "personal"} - }] - + mock_knowledge_instance.query.return_value = [ + {"content": content, "metadata": {"preference": "personal"}} + ] + agent = Agent( role="Information Agent", goal="Provide information based on knowledge sources", backstory="You have access to specific knowledge sources.", llm=LLM(model="gpt-4o-mini"), + knowledge_sources=[string_source], ) # Create a task that requires the agent to use the knowledge @@ -1613,4 +1613,3 @@ def test_agent_with_knowledge_sources(): # Assert that the agent provides the correct information assert "blue" in result.raw.lower() - diff --git a/tests/cassettes/test_agent_with_knowledge_sources.yaml b/tests/cassettes/test_agent_with_knowledge_sources.yaml index d483a19d2..fdc9f912e 100644 --- a/tests/cassettes/test_agent_with_knowledge_sources.yaml +++ b/tests/cassettes/test_agent_with_knowledge_sources.yaml @@ -1,4 +1,415 @@ interactions: +- request: + body: '{"input": ["Brandon''s favorite color is blue and he likes Mexican food."], + "model": "text-embedding-3-small", "encoding_format": "base64"}' + headers: + accept: + - application/json + accept-encoding: + - gzip, deflate + connection: + - keep-alive + content-length: + - '138' + content-type: + - application/json + host: + - api.openai.com + user-agent: + - OpenAI/Python 1.52.1 + x-stainless-arch: + - arm64 + x-stainless-async: + - 'false' + x-stainless-lang: + - python + x-stainless-os: + - MacOS + x-stainless-package-version: + - 1.52.1 + x-stainless-retry-count: + - '0' + x-stainless-runtime: + - CPython + x-stainless-runtime-version: + - 3.11.9 + method: POST + uri: https://api.openai.com/v1/embeddings + response: + body: + string: !!binary | + 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 + //f6v75/9V//+j8AAAD//wMAOXBqMeAgAAA= + headers: + CF-Cache-Status: + - DYNAMIC + CF-RAY: + - 8e94839cd9e9967f-SJC + Connection: + - keep-alive + Content-Encoding: + - gzip + Content-Type: + - application/json + Date: + - Wed, 27 Nov 2024 19:27:11 GMT + Server: + - cloudflare + Set-Cookie: + - __cf_bm=XviX9Hjm.Uy8aR.6KFXUsi._PlZSGHz_33BG8yN1gNU-1732735631-1.0.1.1-xpDmkFSh5aO2fugj8VCyrc23NL7wf6Q8eq_yaxcwutJZAO5nSx9Eeqko_4UhxH4IQBfS8cJSaEmHnXWPD6lTJg; + path=/; expires=Wed, 27-Nov-24 19:57:11 GMT; domain=.api.openai.com; HttpOnly; + Secure; SameSite=None + - _cfuvid=Xz2QlgphZCJYG8KTd5zZKB.lSwPBCu24Nwv2aB6FkeE-1732735631371-0.0.1.1-604800000; + path=/; domain=.api.openai.com; HttpOnly; Secure; SameSite=None + Transfer-Encoding: + - chunked + X-Content-Type-Options: + - nosniff + access-control-allow-origin: + - '*' + access-control-expose-headers: + - X-Request-ID + alt-svc: + - h3=":443"; ma=86400 + openai-model: + - text-embedding-3-small + openai-organization: + - crewai-iuxna1 + openai-processing-ms: + - '272' + openai-version: + - '2020-10-01' + strict-transport-security: + - max-age=31536000; includeSubDomains; preload + x-ratelimit-limit-requests: + - '10000' + x-ratelimit-limit-tokens: + - '10000000' + x-ratelimit-remaining-requests: + - '9999' + x-ratelimit-remaining-tokens: + - '9999986' + x-ratelimit-reset-requests: + - 6ms + x-ratelimit-reset-tokens: + - 0s + x-request-id: + - req_5cba1175a36bccbbad92e3ef21b7021d + status: + code: 200 + message: OK +- request: + body: '{"input": ["What is Brandon''s favorite color? This is the expect criteria + for your final answer: Brandon''s favorite color. you MUST return the actual + complete content as the final answer, not a summary."], "model": "text-embedding-3-small", + "encoding_format": "base64"}' + headers: + accept: + - application/json + accept-encoding: + - gzip, deflate + connection: + - keep-alive + content-length: + - '270' + content-type: + - application/json + cookie: + - __cf_bm=XviX9Hjm.Uy8aR.6KFXUsi._PlZSGHz_33BG8yN1gNU-1732735631-1.0.1.1-xpDmkFSh5aO2fugj8VCyrc23NL7wf6Q8eq_yaxcwutJZAO5nSx9Eeqko_4UhxH4IQBfS8cJSaEmHnXWPD6lTJg; + _cfuvid=Xz2QlgphZCJYG8KTd5zZKB.lSwPBCu24Nwv2aB6FkeE-1732735631371-0.0.1.1-604800000 + host: + - api.openai.com + user-agent: + - OpenAI/Python 1.52.1 + x-stainless-arch: + - arm64 + x-stainless-async: + - 'false' + x-stainless-lang: + - python + x-stainless-os: + - MacOS + x-stainless-package-version: + - 1.52.1 + x-stainless-retry-count: + - '0' + x-stainless-runtime: + - CPython + x-stainless-runtime-version: + - 3.11.9 + method: POST + uri: https://api.openai.com/v1/embeddings + response: + body: + string: !!binary | + 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 + headers: + CF-Cache-Status: + - DYNAMIC + CF-RAY: + - 8e9483a10d7c967f-SJC + Connection: + - keep-alive + Content-Encoding: + - gzip + Content-Type: + - application/json + Date: + - Wed, 27 Nov 2024 19:27:11 GMT + Server: + - cloudflare + Transfer-Encoding: + - chunked + X-Content-Type-Options: + - nosniff + access-control-allow-origin: + - '*' + access-control-expose-headers: + - X-Request-ID + alt-svc: + - h3=":443"; ma=86400 + openai-model: + - text-embedding-3-small + openai-organization: + - crewai-iuxna1 + openai-processing-ms: + - '68' + openai-version: + - '2020-10-01' + strict-transport-security: + - max-age=31536000; includeSubDomains; preload + x-ratelimit-limit-requests: + - '10000' + x-ratelimit-limit-tokens: + - '10000000' + x-ratelimit-remaining-requests: + - '9999' + x-ratelimit-remaining-tokens: + - '9999953' + x-ratelimit-reset-requests: + - 6ms + x-ratelimit-reset-tokens: + - 0s + x-request-id: + - req_09708939ca92f32d9d7143e8b7843b12 + status: + code: 200 + message: OK - request: body: '{"messages": [{"role": "system", "content": "You are Information Agent. You have access to specific knowledge sources.\nYour personal goal is: Provide @@ -9,9 +420,10 @@ interactions: depends on it!"}, {"role": "user", "content": "\nCurrent Task: What is Brandon''s favorite color?\n\nThis is the expect criteria for your final answer: Brandon''s favorite color.\nyou MUST return the actual complete content as the final answer, - not a summary.\n\nBegin! This is VERY important to you, use the tools available - and give your best Final Answer, your job depends on it!\n\nThought:"}], "model": - "gpt-4o-mini", "stop": ["\nObservation:"], "stream": false}' + not a summary.Additional Information: Brandon''s favorite color is blue and + he likes Mexican food.\n\nBegin! This is VERY important to you, use the tools + available and give your best Final Answer, your job depends on it!\n\nThought:"}], + "model": "gpt-4o-mini", "stop": ["\nObservation:"], "stream": false}' headers: accept: - application/json @@ -20,7 +432,7 @@ interactions: connection: - keep-alive content-length: - - '931' + - '1014' content-type: - application/json host: @@ -50,19 +462,19 @@ interactions: response: body: string: !!binary | - H4sIAAAAAAAAA4xSQW7bMBC86xULXnqxAtmxI1e3FEWBtJekCXJpC4GmVhIdapcgqbhN4L8HlB1L - QVOgFwGa2RnOLPmcAAhdiQKEamVQnTXp5f3d9lsdbndh++C+757Or6/bm6uvn59WH/FGzKKCN1tU - 4VV1prizBoNmOtDKoQwYXef5+SK7WK3ny4HouEITZY0N6ZLTTpNOF9limWZ5Ol8f1S1rhV4U8CMB - AHgevjEnVfhbFJDNXpEOvZcNiuI0BCAcm4gI6b32QVIQs5FUTAFpiH4FxDtQkqDRjwgSmhgbJPkd - OoCf9EWTNHA5/BfwyUmqmD54qOUjOx0QFBt2oD1sTI9n02Mc1r2XsSr1xhzx/Sm34cY63vgjf8Jr - Tdq3pUPpmWJGH9iKgd0nAL+G/fRvKgvruLOhDPyAFA3nF/nBT4zXMmHXRzJwkGaKr2bv+JUVBqmN - n2xYKKlarEbpeB2yrzRPiGTS+u8073kfmmtq/sd+JJRCG7AqrcNKq7eNxzGH8dX+a+y05SGw8H98 - wK6sNTXorNOHN1PbMsuz1aZe5yoTyT55AQAA//8DAPaYLdRBAwAA + H4sIAAAAAAAAA4xSwY7TMBS85yuefOHSrNJ0l1S5bVdCLHAHBChy7Zf0geNnbGeX1ar/jpxmm1SA + xCVSZt6MZ579nAEI0qIGoQ4yqt6Z/Pbz26efjNXW+0/64917r82H3W64f9fpOxKrpOD9d1TxRXWl + uHcGI7E90cqjjJhc19WmrDY3rzflSPSs0SRZ52J+zXlPlvKyKK/zosrX20l9YFIYRA1fMgCA5/Gb + clqNv0QNxeoF6TEE2aGoz0MAwrNJiJAhUIjSRrGaScU2oh2j34PlR1DSQkcPCBK6FBukDY/oAb7a + N2Slgdvxv4adl1azfRWglQ/sKSIoNuyBAuzNgFfLYzy2Q5Cpqh2MmfDjObfhznneh4k/4y1ZCofG + owxsU8YQ2YmRPWYA38b9DBeVhfPcu9hE/oE2Ga635clPzNeyZCcycpRmxsti2uqlX6MxSjJhsWGh + pDqgnqXzdchBEy+IbNH6zzR/8z41J9v9j/1MKIUuom6cR03qsvE85jG92n+Nnbc8BhbhKUTsm5Zs + h955Or2Z1jVFVdzs222lCpEds98AAAD//wMAfDYBg0EDAAA= headers: CF-Cache-Status: - DYNAMIC CF-RAY: - - 8e54a2a7d81467f7-SJC + - 8e9483a44b2fcf51-SJC Connection: - keep-alive Content-Encoding: @@ -70,14 +482,14 @@ interactions: Content-Type: - application/json Date: - - Wed, 20 Nov 2024 01:23:34 GMT + - Wed, 27 Nov 2024 19:27:12 GMT Server: - cloudflare Set-Cookie: - - __cf_bm=DoHo1Z11nN9bxkwZmJGnaxRhyrWE0UfyimYuUVRU6A4-1732065814-1.0.1.1-JVRvFrIJLHEq9OaFQS0qcgYcawE7t2XQ4Tpqd58n2Yfx3mvEqD34MJmooi1LtvdvjB2J8x1Rs.rCdXD.msLlKw; - path=/; expires=Wed, 20-Nov-24 01:53:34 GMT; domain=.api.openai.com; HttpOnly; + - __cf_bm=pBzYx.9r7fU6srtt2lLWBrgojr5QFAfVuDKoOwUKCK4-1732735632-1.0.1.1-jYgG33D0s.RUVr6OV4fPXS7bQR9Yp5AwbbIAqdxaZCrcisNIYqPqOqxNO9.Lo3Ok7K8FXfSBrrnAOOJDVLa6bA; + path=/; expires=Wed, 27-Nov-24 19:57:12 GMT; domain=.api.openai.com; HttpOnly; Secure; SameSite=None - - _cfuvid=n3RrNhFMqC3HtJ7n3e3agyxnM1YOQ6eKESz_eeXLtZA-1732065814630-0.0.1.1-604800000; + - _cfuvid=TYAi3OpktKJu15t1e4y3VbRnbHK6QYaCeSYJuT6e5Sk-1732735632634-0.0.1.1-604800000; path=/; domain=.api.openai.com; HttpOnly; Secure; SameSite=None Transfer-Encoding: - chunked @@ -90,7 +502,7 @@ interactions: openai-organization: - crewai-iuxna1 openai-processing-ms: - - '344' + - '535' openai-version: - '2020-10-01' strict-transport-security: @@ -102,13 +514,13 @@ interactions: x-ratelimit-remaining-requests: - '29999' x-ratelimit-remaining-tokens: - - '149999790' + - '149999769' x-ratelimit-reset-requests: - 2ms x-ratelimit-reset-tokens: - 0s x-request-id: - - req_8f1622677c64913753a595f679596614 + - req_8501f29c09575f05c51fdec5c1c36090 status: code: 200 message: OK From 588ad3c4a437096d42b390679064da21af4c8b85 Mon Sep 17 00:00:00 2001 From: Rok Benko Date: Thu, 28 Nov 2024 17:20:53 +0100 Subject: [PATCH 086/126] Update Agents docs to include two approaches for creating an agent: with and without YAML configuration --- docs/concepts/agents.mdx | 170 +++++++++++++++++++++++++++++++-------- 1 file changed, 138 insertions(+), 32 deletions(-) diff --git a/docs/concepts/agents.mdx b/docs/concepts/agents.mdx index 6c939b321..84e22ef89 100644 --- a/docs/concepts/agents.mdx +++ b/docs/concepts/agents.mdx @@ -7,49 +7,154 @@ icon: robot ## What is an agent? An agent is an **autonomous unit** programmed to: +

-Think of an agent as a member of a team, with specific skills and a particular job to do. Agents can have different roles like `Researcher`, `Writer`, or `Customer Support`, each contributing to the overall goal of the crew. + Think of an agent as a member of a team, with specific skills and a particular + job to do. Agents can have different roles like `Researcher`, `Writer`, or + `Customer Support`, each contributing to the overall goal of the crew. ## Agent attributes -| Attribute | Parameter | Description | -| :------------------------- | :--------- | :--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| **Role** | `role` | Defines the agent's function within the crew. It determines the kind of tasks the agent is best suited for. | -| **Goal** | `goal` | The individual objective that the agent aims to achieve. It guides the agent's decision-making process. | -| **Backstory** | `backstory`| Provides context to the agent's role and goal, enriching the interaction and collaboration dynamics. | -| **LLM** *(optional)* | `llm` | Represents the language model that will run the agent. It dynamically fetches the model name from the `OPENAI_MODEL_NAME` environment variable, defaulting to "gpt-4" if not specified. | -| **Tools** *(optional)* | `tools` | Set of capabilities or functions that the agent can use to perform tasks. Expected to be instances of custom classes compatible with the agent's execution environment. Tools are initialized with a default value of an empty list. | -| **Function Calling LLM** *(optional)* | `function_calling_llm` | Specifies the language model that will handle the tool calling for this agent, overriding the crew function calling LLM if passed. Default is `None`. | -| **Max Iter** *(optional)* | `max_iter` | Max Iter is the maximum number of iterations the agent can perform before being forced to give its best answer. Default is `25`. | -| **Max RPM** *(optional)* | `max_rpm` | Max RPM is the maximum number of requests per minute the agent can perform to avoid rate limits. It's optional and can be left unspecified, with a default value of `None`. | -| **Max Execution Time** *(optional)* | `max_execution_time` | Max Execution Time is the maximum execution time for an agent to execute a task. It's optional and can be left unspecified, with a default value of `None`, meaning no max execution time. | -| **Verbose** *(optional)* | `verbose` | Setting this to `True` configures the internal logger to provide detailed execution logs, aiding in debugging and monitoring. Default is `False`. | -| **Allow Delegation** *(optional)* | `allow_delegation` | Agents can delegate tasks or questions to one another, ensuring that each task is handled by the most suitable agent. Default is `False`. | -| **Step Callback** *(optional)* | `step_callback` | A function that is called after each step of the agent. This can be used to log the agent's actions or to perform other operations. It will overwrite the crew `step_callback`. | -| **Cache** *(optional)* | `cache` | Indicates if the agent should use a cache for tool usage. Default is `True`. | -| **System Template** *(optional)* | `system_template` | Specifies the system format for the agent. Default is `None`. | -| **Prompt Template** *(optional)* | `prompt_template` | Specifies the prompt format for the agent. Default is `None`. | -| **Response Template** *(optional)* | `response_template` | Specifies the response format for the agent. Default is `None`. | -| **Allow Code Execution** *(optional)* | `allow_code_execution` | Enable code execution for the agent. Default is `False`. | -| **Max Retry Limit** *(optional)* | `max_retry_limit` | Maximum number of retries for an agent to execute a task when an error occurs. Default is `2`. | -| **Use System Prompt** *(optional)* | `use_system_prompt` | Adds the ability to not use system prompt (to support o1 models). Default is `True`. | -| **Respect Context Window** *(optional)* | `respect_context_window` | Summary strategy to avoid overflowing the context window. Default is `True`. | -| **Code Execution Mode** *(optional)* | `code_execution_mode` | Determines the mode for code execution: 'safe' (using Docker) or 'unsafe' (direct execution on the host machine). Default is `safe`. | +| Attribute | Parameter | Description | +| :-------------------------------------- | :----------------------- | :----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| **Role** | `role` | Defines the agent's function within the crew. It determines the kind of tasks the agent is best suited for. | +| **Goal** | `goal` | The individual objective that the agent aims to achieve. It guides the agent's decision-making process. | +| **Backstory** | `backstory` | Provides context to the agent's role and goal, enriching the interaction and collaboration dynamics. | +| **LLM** _(optional)_ | `llm` | Represents the language model that will run the agent. It dynamically fetches the model name from the `OPENAI_MODEL_NAME` environment variable, defaulting to "gpt-4" if not specified. | +| **Tools** _(optional)_ | `tools` | Set of capabilities or functions that the agent can use to perform tasks. Expected to be instances of custom classes compatible with the agent's execution environment. Tools are initialized with a default value of an empty list. | +| **Function Calling LLM** _(optional)_ | `function_calling_llm` | Specifies the language model that will handle the tool calling for this agent, overriding the crew function calling LLM if passed. Default is `None`. | +| **Max Iter** _(optional)_ | `max_iter` | Max Iter is the maximum number of iterations the agent can perform before being forced to give its best answer. Default is `25`. | +| **Max RPM** _(optional)_ | `max_rpm` | Max RPM is the maximum number of requests per minute the agent can perform to avoid rate limits. It's optional and can be left unspecified, with a default value of `None`. | +| **Max Execution Time** _(optional)_ | `max_execution_time` | Max Execution Time is the maximum execution time for an agent to execute a task. It's optional and can be left unspecified, with a default value of `None`, meaning no max execution time. | +| **Verbose** _(optional)_ | `verbose` | Setting this to `True` configures the internal logger to provide detailed execution logs, aiding in debugging and monitoring. Default is `False`. | +| **Allow Delegation** _(optional)_ | `allow_delegation` | Agents can delegate tasks or questions to one another, ensuring that each task is handled by the most suitable agent. Default is `False`. | +| **Step Callback** _(optional)_ | `step_callback` | A function that is called after each step of the agent. This can be used to log the agent's actions or to perform other operations. It will overwrite the crew `step_callback`. | +| **Cache** _(optional)_ | `cache` | Indicates if the agent should use a cache for tool usage. Default is `True`. | +| **System Template** _(optional)_ | `system_template` | Specifies the system format for the agent. Default is `None`. | +| **Prompt Template** _(optional)_ | `prompt_template` | Specifies the prompt format for the agent. Default is `None`. | +| **Response Template** _(optional)_ | `response_template` | Specifies the response format for the agent. Default is `None`. | +| **Allow Code Execution** _(optional)_ | `allow_code_execution` | Enable code execution for the agent. Default is `False`. | +| **Max Retry Limit** _(optional)_ | `max_retry_limit` | Maximum number of retries for an agent to execute a task when an error occurs. Default is `2`. | +| **Use System Prompt** _(optional)_ | `use_system_prompt` | Adds the ability to not use system prompt (to support o1 models). Default is `True`. | +| **Respect Context Window** _(optional)_ | `respect_context_window` | Summary strategy to avoid overflowing the context window. Default is `True`. | +| **Code Execution Mode** _(optional)_ | `code_execution_mode` | Determines the mode for code execution: 'safe' (using Docker) or 'unsafe' (direct execution on the host machine). Default is `safe`. | ## Creating an agent - **Agent interaction**: Agents can interact with each other using CrewAI's built-in delegation and communication mechanisms. This allows for dynamic task management and problem-solving within the crew. + **Agent interaction**: Agents can interact with each other using CrewAI's + built-in delegation and communication mechanisms. This allows for dynamic task + management and problem-solving within the crew. -To create an agent, you would typically initialize an instance of the `Agent` class with the desired properties. Here's a conceptual example including all attributes: +Agents can be created using one of the following methods: + +
    +
  • + **With a YAML configuration (recommended):** Define agent properties in a + structured YAML file, promoting reusability and cleaner code. +
  • +
  • + **Without a YAML configuration:** Define agent properties directly in your + code. +
  • +
+ +### Creating an agent with a YAML configuration (recommended) + +The YAML configuration approach allows you to separate the agent's required properties (i.e., `role`, `goal`, and `backstory`) from the code logic. This makes your setup modular, easier to manage, and reusable across multiple projects. + +#### Step 1: Define an agent in a YAML file + +The YAML file contains required properties of the agent: + +
    +
  • + `role`: A short description of the agent's purpose or position. +
  • +
  • + `goal`: The primary objective the agent is designed to accomplish. +
  • +
  • + `backstory`: A detailed description of the agent's context to enhance its + understanding and responses. +
  • +
+ + + The YAML file should only include these three properties (i.e., `role`, + `goal`, and `backstory`). Any additional configurations must be defined in the + `crew.py` file. + + +```yaml agents.yaml +data_analyst: + role: | + Data Analyst + goal: | + Extract actionable insights + backstory: > + You're a data analyst at a large company. You're responsible for analyzing + data and providing insights to the business. You're currently working on a + project to analyze the performance of our marketing campaigns. +``` + +#### Step 2: Initialize the agent in `crew.py` + +After defining the agent in the YAML file, you need to create an instance of the `Agent` class in your code and link it to the YAML configuration. This is done in the `crew.py` file, where additional properties can be specified. + +```python crew.py +from crewai import Agent, Crew, Process, Task +from crewai.project import CrewBase, agent, crew, task + +@CrewBase +class ResearchCrew(): + """Research crew""" + + agents_config = "your/path/to/agents.yaml" + + @agent + def data_analyst(self) -> Agent: + return Agent( + config=self.agents_config['data_analyst'], + tools=[my_tool1, my_tool2], # Optional, defaults to an empty list + llm=my_llm, # Optional + function_calling_llm=my_llm, # Optional + max_iter=15, # Optional + max_rpm=None, # Optional + max_execution_time=None, # Optional + verbose=True, # Optional + allow_delegation=False, # Optional + step_callback=my_intermediate_step_callback, # Optional + cache=True, # Optional + system_template=my_system_template, # Optional + prompt_template=my_prompt_template, # Optional + response_template=my_response_template, # Optional + config=my_config, # Optional + crew=my_crew, # Optional + tools_handler=my_tools_handler, # Optional + cache_handler=my_cache_handler, # Optional + callbacks=[callback1, callback2], # Optional + allow_code_execution=True, # Optional + max_retry_limit=2, # Optional + use_system_prompt=True, # Optional + respect_context_window=True, # Optional + code_execution_mode='safe', # Optional, defaults to 'safe' + ) + + # ... remaining code +``` + +### Creating an agent without a YAML configuration + +The non-YAML configuration approach allows you to define the agent's properties directly in your code. Initialize an instance of the `Agent` class with the desired properties. ```python Code example from crewai import Agent @@ -110,8 +215,9 @@ agent = Agent( Extend your third-party agents like LlamaIndex, Langchain, Autogen or fully custom agents using the the CrewAI's `BaseAgent` class. - - **BaseAgent** includes attributes and methods required to integrate with your crews to run and delegate tasks to other agents within your own crew. + + **BaseAgent** includes attributes and methods required to integrate with your + crews to run and delegate tasks to other agents within your own crew. CrewAI is a universal multi-agent framework that allows for all agents to work together to automate tasks and solve problems. @@ -157,5 +263,5 @@ crew = my_crew.kickoff(inputs={"input": "Mark Twain"}) ## Conclusion -Agents are the building blocks of the CrewAI framework. By understanding how to define and interact with agents, +Agents are the building blocks of the CrewAI framework. By understanding how to define and interact with agents, you can create sophisticated AI systems that leverage the power of collaborative intelligence. The `code_execution_mode` attribute provides flexibility in how agents execute code, allowing for both secure and direct execution options. From 4bc23affe0e322f4c48be7ea4e5abb0e273569d6 Mon Sep 17 00:00:00 2001 From: Tony Kipkemboi Date: Mon, 2 Dec 2024 09:50:12 -0500 Subject: [PATCH 087/126] Documentation Improvements: LLM Configuration and Usage (#1684) * docs: improve tasks documentation clarity and structure - Add Task Execution Flow section - Add variable interpolation explanation - Add Task Dependencies section with examples - Improve overall document structure and readability - Update code examples with proper syntax highlighting * docs: update agent documentation with improved examples and formatting - Replace DuckDuckGoSearchRun with SerperDevTool - Update code block formatting to be consistent - Improve template examples with actual syntax - Update LLM examples to use current models - Clean up formatting and remove redundant comments * docs: enhance LLM documentation with Cerebras provider and formatting improvements * docs: simplify LLMs documentation title * docs: improve installation guide clarity and structure - Add clear Python version requirements with check command - Simplify installation options to recommended method - Improve upgrade section clarity for existing users - Add better visual structure with Notes and Tips - Update description and formatting * docs: improve introduction page organization and clarity - Update organizational analogy in Note section - Improve table formatting and alignment - Remove emojis from component table for cleaner look - Add 'helps you' to make the note more action-oriented * docs: add enterprise and community cards - Add Enterprise deployment card in quickstart - Add community card focused on open source discussions - Remove deployment reference from community description - Clean up introduction page cards - Remove link from Enterprise description text --- docs/concepts/agents.mdx | 490 ++++++++++++--------- docs/concepts/knowledge.mdx | 253 +++++++---- docs/concepts/llms.mdx | 821 +++++++++++++++++++++++------------- docs/concepts/tasks.mdx | 203 +++++++-- docs/installation.mdx | 223 +++++----- docs/introduction.mdx | 96 +++-- docs/quickstart.mdx | 19 +- 7 files changed, 1369 insertions(+), 736 deletions(-) diff --git a/docs/concepts/agents.mdx b/docs/concepts/agents.mdx index 84e22ef89..fda4fc55f 100644 --- a/docs/concepts/agents.mdx +++ b/docs/concepts/agents.mdx @@ -1,267 +1,343 @@ --- title: Agents -description: What are CrewAI Agents and how to use them. +description: Detailed guide on creating and managing agents within the CrewAI framework. icon: robot --- -## What is an agent? +## Overview of an Agent -An agent is an **autonomous unit** programmed to: - -
    -
  • Perform tasks
  • -
  • Make decisions
  • -
  • Communicate with other agents
  • -
+In the CrewAI framework, an `Agent` is an autonomous unit that can: +- Perform specific tasks +- Make decisions based on its role and goal +- Use tools to accomplish objectives +- Communicate and collaborate with other agents +- Maintain memory of interactions +- Delegate tasks when allowed - Think of an agent as a member of a team, with specific skills and a particular - job to do. Agents can have different roles like `Researcher`, `Writer`, or - `Customer Support`, each contributing to the overall goal of the crew. +Think of an agent as a specialized team member with specific skills, expertise, and responsibilities. For example, a `Researcher` agent might excel at gathering and analyzing information, while a `Writer` agent might be better at creating content. -## Agent attributes +## Agent Attributes -| Attribute | Parameter | Description | -| :-------------------------------------- | :----------------------- | :----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| **Role** | `role` | Defines the agent's function within the crew. It determines the kind of tasks the agent is best suited for. | -| **Goal** | `goal` | The individual objective that the agent aims to achieve. It guides the agent's decision-making process. | -| **Backstory** | `backstory` | Provides context to the agent's role and goal, enriching the interaction and collaboration dynamics. | -| **LLM** _(optional)_ | `llm` | Represents the language model that will run the agent. It dynamically fetches the model name from the `OPENAI_MODEL_NAME` environment variable, defaulting to "gpt-4" if not specified. | -| **Tools** _(optional)_ | `tools` | Set of capabilities or functions that the agent can use to perform tasks. Expected to be instances of custom classes compatible with the agent's execution environment. Tools are initialized with a default value of an empty list. | -| **Function Calling LLM** _(optional)_ | `function_calling_llm` | Specifies the language model that will handle the tool calling for this agent, overriding the crew function calling LLM if passed. Default is `None`. | -| **Max Iter** _(optional)_ | `max_iter` | Max Iter is the maximum number of iterations the agent can perform before being forced to give its best answer. Default is `25`. | -| **Max RPM** _(optional)_ | `max_rpm` | Max RPM is the maximum number of requests per minute the agent can perform to avoid rate limits. It's optional and can be left unspecified, with a default value of `None`. | -| **Max Execution Time** _(optional)_ | `max_execution_time` | Max Execution Time is the maximum execution time for an agent to execute a task. It's optional and can be left unspecified, with a default value of `None`, meaning no max execution time. | -| **Verbose** _(optional)_ | `verbose` | Setting this to `True` configures the internal logger to provide detailed execution logs, aiding in debugging and monitoring. Default is `False`. | -| **Allow Delegation** _(optional)_ | `allow_delegation` | Agents can delegate tasks or questions to one another, ensuring that each task is handled by the most suitable agent. Default is `False`. | -| **Step Callback** _(optional)_ | `step_callback` | A function that is called after each step of the agent. This can be used to log the agent's actions or to perform other operations. It will overwrite the crew `step_callback`. | -| **Cache** _(optional)_ | `cache` | Indicates if the agent should use a cache for tool usage. Default is `True`. | -| **System Template** _(optional)_ | `system_template` | Specifies the system format for the agent. Default is `None`. | -| **Prompt Template** _(optional)_ | `prompt_template` | Specifies the prompt format for the agent. Default is `None`. | -| **Response Template** _(optional)_ | `response_template` | Specifies the response format for the agent. Default is `None`. | -| **Allow Code Execution** _(optional)_ | `allow_code_execution` | Enable code execution for the agent. Default is `False`. | -| **Max Retry Limit** _(optional)_ | `max_retry_limit` | Maximum number of retries for an agent to execute a task when an error occurs. Default is `2`. | -| **Use System Prompt** _(optional)_ | `use_system_prompt` | Adds the ability to not use system prompt (to support o1 models). Default is `True`. | -| **Respect Context Window** _(optional)_ | `respect_context_window` | Summary strategy to avoid overflowing the context window. Default is `True`. | -| **Code Execution Mode** _(optional)_ | `code_execution_mode` | Determines the mode for code execution: 'safe' (using Docker) or 'unsafe' (direct execution on the host machine). Default is `safe`. | +| Attribute | Parameter | Type | Description | +| :-------------------------------------- | :----------------------- | :---------------------------- | :------------------------------------------------------------------------------------------------------------------- | +| **Role** | `role` | `str` | Defines the agent's function and expertise within the crew. | +| **Goal** | `goal` | `str` | The individual objective that guides the agent's decision-making. | +| **Backstory** | `backstory` | `str` | Provides context and personality to the agent, enriching interactions. | +| **LLM** _(optional)_ | `llm` | `Union[str, LLM, Any]` | Language model that powers the agent. Defaults to the model specified in `OPENAI_MODEL_NAME` or "gpt-4". | +| **Tools** _(optional)_ | `tools` | `List[BaseTool]` | Capabilities or functions available to the agent. Defaults to an empty list. | +| **Function Calling LLM** _(optional)_ | `function_calling_llm` | `Optional[Any]` | Language model for tool calling, overrides crew's LLM if specified. | +| **Max Iterations** _(optional)_ | `max_iter` | `int` | Maximum iterations before the agent must provide its best answer. Default is 20. | +| **Max RPM** _(optional)_ | `max_rpm` | `Optional[int]` | Maximum requests per minute to avoid rate limits. | +| **Max Execution Time** _(optional)_ | `max_execution_time` | `Optional[int]` | Maximum time (in seconds) for task execution. | +| **Memory** _(optional)_ | `memory` | `bool` | Whether the agent should maintain memory of interactions. Default is True. | +| **Verbose** _(optional)_ | `verbose` | `bool` | Enable detailed execution logs for debugging. Default is False. | +| **Allow Delegation** _(optional)_ | `allow_delegation` | `bool` | Allow the agent to delegate tasks to other agents. Default is False. | +| **Step Callback** _(optional)_ | `step_callback` | `Optional[Any]` | Function called after each agent step, overrides crew callback. | +| **Cache** _(optional)_ | `cache` | `bool` | Enable caching for tool usage. Default is True. | +| **System Template** _(optional)_ | `system_template` | `Optional[str]` | Custom system prompt template for the agent. | +| **Prompt Template** _(optional)_ | `prompt_template` | `Optional[str]` | Custom prompt template for the agent. | +| **Response Template** _(optional)_ | `response_template` | `Optional[str]` | Custom response template for the agent. | +| **Allow Code Execution** _(optional)_ | `allow_code_execution` | `Optional[bool]` | Enable code execution for the agent. Default is False. | +| **Max Retry Limit** _(optional)_ | `max_retry_limit` | `int` | Maximum number of retries when an error occurs. Default is 2. | +| **Respect Context Window** _(optional)_ | `respect_context_window` | `bool` | Keep messages under context window size by summarizing. Default is True. | +| **Code Execution Mode** _(optional)_ | `code_execution_mode` | `Literal["safe", "unsafe"]` | Mode for code execution: 'safe' (using Docker) or 'unsafe' (direct). Default is 'safe'. | +| **Embedder Config** _(optional)_ | `embedder_config` | `Optional[Dict[str, Any]]` | Configuration for the embedder used by the agent. | +| **Knowledge Sources** _(optional)_ | `knowledge_sources` | `Optional[List[BaseKnowledgeSource]]` | Knowledge sources available to the agent. | +| **Use System Prompt** _(optional)_ | `use_system_prompt` | `Optional[bool]` | Whether to use system prompt (for o1 model support). Default is True. | -## Creating an agent +## Creating Agents + +There are two ways to create agents in CrewAI: using **YAML configuration (recommended)** or defining them **directly in code**. + +### YAML Configuration (Recommended) + +Using YAML configuration provides a cleaner, more maintainable way to define agents. We strongly recommend using this approach in your CrewAI projects. + +After creating your CrewAI project as outlined in the [Installation](/installation) section, navigate to the `src/latest_ai_development/config/agents.yaml` file and modify the template to match your requirements. - **Agent interaction**: Agents can interact with each other using CrewAI's - built-in delegation and communication mechanisms. This allows for dynamic task - management and problem-solving within the crew. +Variables in your YAML files (like `{topic}`) will be replaced with values from your inputs when running the crew: +```python Code +crew.kickoff(inputs={'topic': 'AI Agents'}) +``` -Agents can be created using one of the following methods: - -
    -
  • - **With a YAML configuration (recommended):** Define agent properties in a - structured YAML file, promoting reusability and cleaner code. -
  • -
  • - **Without a YAML configuration:** Define agent properties directly in your - code. -
  • -
- -### Creating an agent with a YAML configuration (recommended) - -The YAML configuration approach allows you to separate the agent's required properties (i.e., `role`, `goal`, and `backstory`) from the code logic. This makes your setup modular, easier to manage, and reusable across multiple projects. - -#### Step 1: Define an agent in a YAML file - -The YAML file contains required properties of the agent: - -
    -
  • - `role`: A short description of the agent's purpose or position. -
  • -
  • - `goal`: The primary objective the agent is designed to accomplish. -
  • -
  • - `backstory`: A detailed description of the agent's context to enhance its - understanding and responses. -
  • -
- - - The YAML file should only include these three properties (i.e., `role`, - `goal`, and `backstory`). Any additional configurations must be defined in the - `crew.py` file. - +Here's an example of how to configure agents using YAML: ```yaml agents.yaml -data_analyst: - role: | - Data Analyst - goal: | - Extract actionable insights +# src/latest_ai_development/config/agents.yaml +researcher: + role: > + {topic} Senior Data Researcher + goal: > + Uncover cutting-edge developments in {topic} backstory: > - You're a data analyst at a large company. You're responsible for analyzing - data and providing insights to the business. You're currently working on a - project to analyze the performance of our marketing campaigns. + You're a seasoned researcher with a knack for uncovering the latest + developments in {topic}. Known for your ability to find the most relevant + information and present it in a clear and concise manner. + +reporting_analyst: + role: > + {topic} Reporting Analyst + goal: > + Create detailed reports based on {topic} data analysis and research findings + backstory: > + You're a meticulous analyst with a keen eye for detail. You're known for + your ability to turn complex data into clear and concise reports, making + it easy for others to understand and act on the information you provide. ``` -#### Step 2: Initialize the agent in `crew.py` +To use this YAML configuration in your code, create a crew class that inherits from `CrewBase`: -After defining the agent in the YAML file, you need to create an instance of the `Agent` class in your code and link it to the YAML configuration. This is done in the `crew.py` file, where additional properties can be specified. - -```python crew.py -from crewai import Agent, Crew, Process, Task -from crewai.project import CrewBase, agent, crew, task +```python Code +# src/latest_ai_development/crew.py +from crewai import Agent, Crew, Process +from crewai.project import CrewBase, agent, crew +from crewai_tools import SerperDevTool @CrewBase -class ResearchCrew(): - """Research crew""" - - agents_config = "your/path/to/agents.yaml" +class LatestAiDevelopmentCrew(): + """LatestAiDevelopment crew""" @agent - def data_analyst(self) -> Agent: + def researcher(self) -> Agent: return Agent( - config=self.agents_config['data_analyst'], - tools=[my_tool1, my_tool2], # Optional, defaults to an empty list - llm=my_llm, # Optional - function_calling_llm=my_llm, # Optional - max_iter=15, # Optional - max_rpm=None, # Optional - max_execution_time=None, # Optional - verbose=True, # Optional - allow_delegation=False, # Optional - step_callback=my_intermediate_step_callback, # Optional - cache=True, # Optional - system_template=my_system_template, # Optional - prompt_template=my_prompt_template, # Optional - response_template=my_response_template, # Optional - config=my_config, # Optional - crew=my_crew, # Optional - tools_handler=my_tools_handler, # Optional - cache_handler=my_cache_handler, # Optional - callbacks=[callback1, callback2], # Optional - allow_code_execution=True, # Optional - max_retry_limit=2, # Optional - use_system_prompt=True, # Optional - respect_context_window=True, # Optional - code_execution_mode='safe', # Optional, defaults to 'safe' + config=self.agents_config['researcher'], + verbose=True, + tools=[SerperDevTool()] ) - # ... remaining code + @agent + def reporting_analyst(self) -> Agent: + return Agent( + config=self.agents_config['reporting_analyst'], + verbose=True + ) ``` -### Creating an agent without a YAML configuration + +The names you use in your YAML files (`agents.yaml`) should match the method names in your Python code. + -The non-YAML configuration approach allows you to define the agent's properties directly in your code. Initialize an instance of the `Agent` class with the desired properties. +### Direct Code Definition -```python Code example +You can create agents directly in code by instantiating the `Agent` class. Here's a comprehensive example showing all available parameters: + +```python Code from crewai import Agent +from crewai_tools import SerperDevTool +# Create an agent with all available parameters agent = Agent( - role='Data Analyst', - goal='Extract actionable insights', - backstory="""You're a data analyst at a large company. - You're responsible for analyzing data and providing insights - to the business. - You're currently working on a project to analyze the - performance of our marketing campaigns.""", - tools=[my_tool1, my_tool2], # Optional, defaults to an empty list - llm=my_llm, # Optional - function_calling_llm=my_llm, # Optional - max_iter=15, # Optional - max_rpm=None, # Optional - max_execution_time=None, # Optional - verbose=True, # Optional - allow_delegation=False, # Optional - step_callback=my_intermediate_step_callback, # Optional - cache=True, # Optional - system_template=my_system_template, # Optional - prompt_template=my_prompt_template, # Optional - response_template=my_response_template, # Optional - config=my_config, # Optional - crew=my_crew, # Optional - tools_handler=my_tools_handler, # Optional - cache_handler=my_cache_handler, # Optional - callbacks=[callback1, callback2], # Optional - allow_code_execution=True, # Optional - max_retry_limit=2, # Optional - use_system_prompt=True, # Optional - respect_context_window=True, # Optional - code_execution_mode='safe', # Optional, defaults to 'safe' + role="Senior Data Scientist", + goal="Analyze and interpret complex datasets to provide actionable insights", + backstory="With over 10 years of experience in data science and machine learning, " + "you excel at finding patterns in complex datasets.", + llm="gpt-4", # Default: OPENAI_MODEL_NAME or "gpt-4" + function_calling_llm=None, # Optional: Separate LLM for tool calling + memory=True, # Default: True + verbose=False, # Default: False + allow_delegation=False, # Default: False + max_iter=20, # Default: 20 iterations + max_rpm=None, # Optional: Rate limit for API calls + max_execution_time=None, # Optional: Maximum execution time in seconds + max_retry_limit=2, # Default: 2 retries on error + allow_code_execution=False, # Default: False + code_execution_mode="safe", # Default: "safe" (options: "safe", "unsafe") + respect_context_window=True, # Default: True + use_system_prompt=True, # Default: True + tools=[SerperDevTool()], # Optional: List of tools + knowledge_sources=None, # Optional: List of knowledge sources + embedder_config=None, # Optional: Custom embedder configuration + system_template=None, # Optional: Custom system prompt template + prompt_template=None, # Optional: Custom prompt template + response_template=None, # Optional: Custom response template + step_callback=None, # Optional: Callback function for monitoring ) ``` -## Setting prompt templates +Let's break down some key parameter combinations for common use cases: -Prompt templates are used to format the prompt for the agent. You can use to update the system, regular and response templates for the agent. Here's an example of how to set prompt templates: +#### Basic Research Agent +```python Code +research_agent = Agent( + role="Research Analyst", + goal="Find and summarize information about specific topics", + backstory="You are an experienced researcher with attention to detail", + tools=[SerperDevTool()], + verbose=True # Enable logging for debugging +) +``` -```python Code example -agent = Agent( - role="{topic} specialist", - goal="Figure {goal} out", - backstory="I am the master of {role}", - system_template="""<|start_header_id|>system<|end_header_id|> +#### Code Development Agent +```python Code +dev_agent = Agent( + role="Senior Python Developer", + goal="Write and debug Python code", + backstory="Expert Python developer with 10 years of experience", + allow_code_execution=True, + code_execution_mode="safe", # Uses Docker for safety + max_execution_time=300, # 5-minute timeout + max_retry_limit=3 # More retries for complex code tasks +) +``` + +#### Long-Running Analysis Agent +```python Code +analysis_agent = Agent( + role="Data Analyst", + goal="Perform deep analysis of large datasets", + backstory="Specialized in big data analysis and pattern recognition", + memory=True, + respect_context_window=True, + max_rpm=10, # Limit API calls + function_calling_llm="gpt-4o-mini" # Cheaper model for tool calls +) +``` + +#### Custom Template Agent +```python Code +custom_agent = Agent( + role="Customer Service Representative", + goal="Assist customers with their inquiries", + backstory="Experienced in customer support with a focus on satisfaction", + system_template="""<|start_header_id|>system<|end_header_id|> {{ .System }}<|eot_id|>""", - prompt_template="""<|start_header_id|>user<|end_header_id|> + prompt_template="""<|start_header_id|>user<|end_header_id|> {{ .Prompt }}<|eot_id|>""", - response_template="""<|start_header_id|>assistant<|end_header_id|> + response_template="""<|start_header_id|>assistant<|end_header_id|> {{ .Response }}<|eot_id|>""", ) ``` -## Bring your third-party agents +### Parameter Details -Extend your third-party agents like LlamaIndex, Langchain, Autogen or fully custom agents using the the CrewAI's `BaseAgent` class. +#### Critical Parameters +- `role`, `goal`, and `backstory` are required and shape the agent's behavior +- `llm` determines the language model used (default: OpenAI's GPT-4) + +#### Memory and Context +- `memory`: Enable to maintain conversation history +- `respect_context_window`: Prevents token limit issues +- `knowledge_sources`: Add domain-specific knowledge bases + +#### Execution Control +- `max_iter`: Maximum attempts before giving best answer +- `max_execution_time`: Timeout in seconds +- `max_rpm`: Rate limiting for API calls +- `max_retry_limit`: Retries on error + +#### Code Execution +- `allow_code_execution`: Must be True to run code +- `code_execution_mode`: + - `"safe"`: Uses Docker (recommended for production) + - `"unsafe"`: Direct execution (use only in trusted environments) + +#### Templates +- `system_template`: Defines agent's core behavior +- `prompt_template`: Structures input format +- `response_template`: Formats agent responses - **BaseAgent** includes attributes and methods required to integrate with your - crews to run and delegate tasks to other agents within your own crew. +When using custom templates, you can use variables like `{role}`, `{goal}`, and `{input}` in your templates. These will be automatically populated during execution. -CrewAI is a universal multi-agent framework that allows for all agents to work together to automate tasks and solve problems. +## Agent Tools -```python Code example -from crewai import Agent, Task, Crew -from custom_agent import CustomAgent # You need to build and extend your own agent logic with the CrewAI BaseAgent class then import it here. +Agents can be equipped with various tools to enhance their capabilities. CrewAI supports tools from: +- [CrewAI Toolkit](https://github.com/joaomdmoura/crewai-tools) +- [LangChain Tools](https://python.langchain.com/docs/integrations/tools) -from langchain.agents import load_tools +Here's how to add tools to an agent: -langchain_tools = load_tools(["google-serper"], llm=llm) +```python Code +from crewai import Agent +from crewai_tools import SerperDevTool, WikipediaTools -agent1 = CustomAgent( - role="agent role", - goal="who is {input}?", - backstory="agent backstory", - verbose=True, +# Create tools +search_tool = SerperDevTool() +wiki_tool = WikipediaTools() + +# Add tools to agent +researcher = Agent( + role="AI Technology Researcher", + goal="Research the latest AI developments", + tools=[search_tool, wiki_tool], + verbose=True ) - -task1 = Task( - expected_output="a short biography of {input}", - description="a short biography of {input}", - agent=agent1, -) - -agent2 = Agent( - role="agent role", - goal="summarize the short bio for {input} and if needed do more research", - backstory="agent backstory", - verbose=True, -) - -task2 = Task( - description="a tldr summary of the short biography", - expected_output="5 bullet point summary of the biography", - agent=agent2, - context=[task1], -) - -my_crew = Crew(agents=[agent1, agent2], tasks=[task1, task2]) -crew = my_crew.kickoff(inputs={"input": "Mark Twain"}) ``` -## Conclusion +## Agent Memory and Context -Agents are the building blocks of the CrewAI framework. By understanding how to define and interact with agents, -you can create sophisticated AI systems that leverage the power of collaborative intelligence. The `code_execution_mode` attribute provides flexibility in how agents execute code, allowing for both secure and direct execution options. +Agents can maintain memory of their interactions and use context from previous tasks. This is particularly useful for complex workflows where information needs to be retained across multiple tasks. + +```python Code +from crewai import Agent + +analyst = Agent( + role="Data Analyst", + goal="Analyze and remember complex data patterns", + memory=True, # Enable memory + verbose=True +) +``` + + +When `memory` is enabled, the agent will maintain context across multiple interactions, improving its ability to handle complex, multi-step tasks. + + +## Important Considerations and Best Practices + +### Security and Code Execution +- When using `allow_code_execution`, be cautious with user input and always validate it +- Use `code_execution_mode: "safe"` (Docker) in production environments +- Consider setting appropriate `max_execution_time` limits to prevent infinite loops + +### Performance Optimization +- Use `respect_context_window: true` to prevent token limit issues +- Set appropriate `max_rpm` to avoid rate limiting +- Enable `cache: true` to improve performance for repetitive tasks +- Adjust `max_iter` and `max_retry_limit` based on task complexity + +### Memory and Context Management +- Use `memory: true` for tasks requiring historical context +- Leverage `knowledge_sources` for domain-specific information +- Configure `embedder_config` when using custom embedding models +- Use custom templates (`system_template`, `prompt_template`, `response_template`) for fine-grained control over agent behavior + +### Agent Collaboration +- Enable `allow_delegation: true` when agents need to work together +- Use `step_callback` to monitor and log agent interactions +- Consider using different LLMs for different purposes: + - Main `llm` for complex reasoning + - `function_calling_llm` for efficient tool usage + +### Model Compatibility +- Set `use_system_prompt: false` for older models that don't support system messages +- Ensure your chosen `llm` supports the features you need (like function calling) + +## Troubleshooting Common Issues + +1. **Rate Limiting**: If you're hitting API rate limits: + - Implement appropriate `max_rpm` + - Use caching for repetitive operations + - Consider batching requests + +2. **Context Window Errors**: If you're exceeding context limits: + - Enable `respect_context_window` + - Use more efficient prompts + - Clear agent memory periodically + +3. **Code Execution Issues**: If code execution fails: + - Verify Docker is installed for safe mode + - Check execution permissions + - Review code sandbox settings + +4. **Memory Issues**: If agent responses seem inconsistent: + - Verify memory is enabled + - Check knowledge source configuration + - Review conversation history management + +Remember that agents are most effective when configured according to their specific use case. Take time to understand your requirements and adjust these parameters accordingly. diff --git a/docs/concepts/knowledge.mdx b/docs/concepts/knowledge.mdx index e51602947..69fa4e644 100644 --- a/docs/concepts/knowledge.mdx +++ b/docs/concepts/knowledge.mdx @@ -6,100 +6,205 @@ icon: book # Using Knowledge in CrewAI -## Introduction - -Knowledge in CrewAI serves as a foundational component for enriching AI agents with contextual and relevant information. It enables agents to access and utilize structured data sources during their execution processes, making them more intelligent and responsive. - -The Knowledge class in CrewAI provides a powerful way to manage and query knowledge sources for your AI agents. This guide will show you how to implement knowledge management in your CrewAI projects. - ## What is Knowledge? -The `Knowledge` class in CrewAI manages various sources that store information, which can be queried and retrieved by AI agents. This modular approach allows you to integrate diverse data formats such as text, PDFs, spreadsheets, and more into your AI workflows. +Knowledge in CrewAI is a powerful system that allows AI agents to access and utilize external information sources during their tasks. Think of it as giving your agents a reference library they can consult while working. -Additionally, we have specific tools for generate knowledge sources for strings, text files, PDF's, and Spreadsheets. You can expand on any source type by extending the `KnowledgeSource` class. + + Key benefits of using Knowledge: + - Enhance agents with domain-specific information + - Support decisions with real-world data + - Maintain context across conversations + - Ground responses in factual information + -## Basic Implementation +## Supported Knowledge Sources -Here's a simple example of how to use the Knowledge class: +CrewAI supports various types of knowledge sources out of the box: + + + + - Raw strings + - Text files (.txt) + - PDF documents + + + - CSV files + - Excel spreadsheets + - JSON documents + + + +## Quick Start + +Here's a simple example using string-based knowledge: ```python -from crewai import Agent, Task, Crew, Process, LLM -from crewai.knowledge.source.string_knowledge_source import StringKnowledgeSource +from crewai import Agent, Task, Crew +from crewai.knowledge import StringKnowledgeSource -# Create a knowledge source -content = "Users name is John. He is 30 years old and lives in San Francisco." -string_source = StringKnowledgeSource( - content=content, metadata={"preference": "personal"} +# 1. Create a knowledge source +product_info = StringKnowledgeSource( + content="""Our product X1000 has the following features: + - 10-hour battery life + - Water-resistant + - Available in black and silver + Price: $299.99""", + metadata={"category": "product"} ) -# Create an agent with the knowledge store -agent = Agent( - role="About User", - goal="You know everything about the user.", - backstory="""You are a master at understanding people and their preferences.""", - verbose=True +# 2. Create an agent with knowledge +sales_agent = Agent( + role="Sales Representative", + goal="Accurately answer customer questions about products", + backstory="Expert in product features and customer service", + knowledge_sources=[product_info] # Attach knowledge to agent ) -task = Task( - description="Answer the following questions about the user: {question}", - expected_output="An answer to the question.", - agent=agent, +# 3. Create a task +answer_task = Task( + description="Answer: What colors is the X1000 available in and how much does it cost?", + agent=sales_agent ) +# 4. Create and run the crew +crew = Crew( + agents=[sales_agent], + tasks=[answer_task] +) + +result = crew.kickoff() +``` + +## Knowledge Configuration + +### Collection Names + +Knowledge sources are organized into collections for better management: + +```python +# Create knowledge sources with specific collections +tech_specs = StringKnowledgeSource( + content="Technical specifications...", + collection_name="product_tech_specs" +) + +pricing_info = StringKnowledgeSource( + content="Pricing information...", + collection_name="product_pricing" +) +``` + +### Metadata and Filtering + +Add metadata to organize and filter knowledge: + +```python +knowledge_source = StringKnowledgeSource( + content="Product details...", + metadata={ + "category": "electronics", + "product_line": "premium", + "last_updated": "2024-03" + } +) +``` + +### Chunking Configuration + +Control how your content is split for processing: + +```python +knowledge_source = PDFKnowledgeSource( + file_path="product_manual.pdf", + chunk_size=2000, # Characters per chunk + chunk_overlap=200 # Overlap between chunks +) +``` + +## Advanced Usage + +### Custom Knowledge Sources + +Create your own knowledge source by extending the base class: + +```python +from crewai.knowledge.source import BaseKnowledgeSource + +class APIKnowledgeSource(BaseKnowledgeSource): + def __init__(self, api_endpoint: str, **kwargs): + super().__init__(**kwargs) + self.api_endpoint = api_endpoint + + def load_content(self): + # Implement API data fetching + response = requests.get(self.api_endpoint) + return response.json() + + def add(self): + content = self.load_content() + # Process and store content + self.save_documents({"source": "api"}) +``` + +### Embedder Configuration + +Customize the embedding process: + +```python crew = Crew( agents=[agent], tasks=[task], - verbose=True, - process=Process.sequential, - knowledge_sources=[string_source], # Enable knowledge by adding the sources here. -) - -result = crew.kickoff(inputs={"question": "What city does John live in and how old is he?"}) -``` - -## Appending Knowledge Sources To Individual Agents -Sometimes you may want to append knowledge sources to an individual agent. You can do this by setting the `knowledge` parameter in the `Agent` class. - -```python -agent = Agent( - ... - knowledge_sources=[ - StringKnowledgeSource( - content="Users name is John. He is 30 years old and lives in San Francisco.", - metadata={"preference": "personal"}, - ) - ], + knowledge_sources=[source], + embedder_config={ + "model": "BAAI/bge-small-en-v1.5", + "normalize": True, + "max_length": 512 + } ) ``` -## Agent Level Knowledge Sources +## Best Practices -You can also append knowledge sources to an individual agent by setting the `knowledge_sources` parameter in the `Agent` class. + + + - Use meaningful collection names + - Add detailed metadata for filtering + - Keep chunk sizes appropriate for your content + - Consider content overlap for context preservation + + + + - Use smaller chunk sizes for precise retrieval + - Implement metadata filtering for faster searches + - Choose appropriate embedding models for your use case + - Cache frequently accessed knowledge + + + + - Validate knowledge source content + - Handle missing or corrupted files + - Monitor embedding generation + - Implement fallback options + + -```python -string_source = StringKnowledgeSource( - content="Users name is John. He is 30 years old and lives in San Francisco.", - metadata={"preference": "personal"}, -) -agent = Agent( - ... - knowledge_sources=[string_source], -) -``` +## Common Issues and Solutions -## Embedder Configuration - -You can also configure the embedder for the knowledge store. This is useful if you want to use a different embedder for the knowledge store than the one used for the agents. - -```python -... -string_source = StringKnowledgeSource( - content="Users name is John. He is 30 years old and lives in San Francisco.", - metadata={"preference": "personal"} -) -crew = Crew( - ... - knowledge_sources=[string_source], - embedder_config={"provider": "ollama", "config": {"model": "nomic-embed-text:latest"}}, -) -``` + + + If agents can't find relevant information: + - Check chunk sizes + - Verify knowledge source loading + - Review metadata filters + - Test with simpler queries first + + + + If knowledge retrieval is slow: + - Reduce chunk sizes + - Optimize metadata filtering + - Consider using a lighter embedding model + - Cache frequently accessed content + + diff --git a/docs/concepts/llms.mdx b/docs/concepts/llms.mdx index 798fea726..1f01ca0eb 100644 --- a/docs/concepts/llms.mdx +++ b/docs/concepts/llms.mdx @@ -1,205 +1,323 @@ --- -title: LLMs -description: Learn how to configure and optimize LLMs for your CrewAI projects. -icon: microchip-ai +title: 'LLMs' +description: 'A comprehensive guide to configuring and using Large Language Models (LLMs) in your CrewAI projects' +icon: 'microchip-ai' --- -# Large Language Models (LLMs) in CrewAI + + CrewAI integrates with multiple LLM providers through LiteLLM, giving you the flexibility to choose the right model for your specific use case. This guide will help you understand how to configure and use different LLM providers in your CrewAI projects. + -Large Language Models (LLMs) are the backbone of intelligent agents in the CrewAI framework. This guide will help you understand, configure, and optimize LLM usage for your CrewAI projects. +## What are LLMs? -## Key Concepts +Large Language Models (LLMs) are the core intelligence behind CrewAI agents. They enable agents to understand context, make decisions, and generate human-like responses. Here's what you need to know: -- **LLM**: Large Language Model, the AI powering agent intelligence -- **Agent**: A CrewAI entity that uses an LLM to perform tasks -- **Provider**: A service that offers LLM capabilities (e.g., OpenAI, Anthropic, Ollama, [more providers](https://docs.litellm.ai/docs/providers)) + + + Large Language Models are AI systems trained on vast amounts of text data. They power the intelligence of your CrewAI agents, enabling them to understand and generate human-like text. + + + The context window determines how much text an LLM can process at once. Larger windows (e.g., 128K tokens) allow for more context but may be more expensive and slower. + + + Temperature (0.0 to 1.0) controls response randomness. Lower values (e.g., 0.2) produce more focused, deterministic outputs, while higher values (e.g., 0.8) increase creativity and variability. + + + Each LLM provider (e.g., OpenAI, Anthropic, Google) offers different models with varying capabilities, pricing, and features. Choose based on your needs for accuracy, speed, and cost. + + -## Configuring LLMs for Agents +## Available Models and Their Capabilities -CrewAI offers flexible options for setting up LLMs: - -### 1. Default Configuration - -By default, CrewAI uses the `gpt-4o-mini` model. It uses environment variables if no LLM is specified: -- `OPENAI_MODEL_NAME` (defaults to "gpt-4o-mini" if not set) -- `OPENAI_API_BASE` -- `OPENAI_API_KEY` - -### 2. Updating YAML files - -You can update the `agents.yml` file to refer to the LLM you want to use: - -```yaml Code -researcher: - role: Research Specialist - goal: Conduct comprehensive research and analysis to gather relevant information, - synthesize findings, and produce well-documented insights. - backstory: A dedicated research professional with years of experience in academic - investigation, literature review, and data analysis, known for thorough and - methodical approaches to complex research questions. - verbose: true - llm: openai/gpt-4o - # llm: azure/gpt-4o-mini - # llm: gemini/gemini-pro - # llm: anthropic/claude-3-5-sonnet-20240620 - # llm: bedrock/anthropic.claude-3-sonnet-20240229-v1:0 - # llm: mistral/mistral-large-latest - # llm: ollama/llama3:70b - # llm: groq/llama-3.2-90b-vision-preview - # llm: watsonx/meta-llama/llama-3-1-70b-instruct - # llm: nvidia_nim/meta/llama3-70b-instruct - # llm: sambanova/Meta-Llama-3.1-8B-Instruct - # ... -``` - -Keep in mind that you will need to set certain ENV vars depending on the model you are -using to account for the credentials or set a custom LLM object like described below. -Here are some of the required ENV vars for some of the LLM integrations: - - - - ```python Code - OPENAI_API_KEY= - OPENAI_API_BASE= - OPENAI_MODEL_NAME= - OPENAI_ORGANIZATION= # OPTIONAL - OPENAI_API_BASE= # OPTIONAL - ``` - - - - ```python Code - ANTHROPIC_API_KEY= - ``` - - - - ```python Code - GEMINI_API_KEY= - ``` - - - - ```python Code - AZURE_API_KEY= # "my-azure-api-key" - AZURE_API_BASE= # "https://example-endpoint.openai.azure.com" - AZURE_API_VERSION= # "2023-05-15" - AZURE_AD_TOKEN= # Optional - AZURE_API_TYPE= # Optional - ``` - - - - ```python Code - AWS_ACCESS_KEY_ID= - AWS_SECRET_ACCESS_KEY= - AWS_DEFAULT_REGION= - ``` - - - - ```python Code - MISTRAL_API_KEY= - ``` - - - - ```python Code - GROQ_API_KEY= - ``` - - - - ```python Code - WATSONX_URL= # (required) Base URL of your WatsonX instance - WATSONX_APIKEY= # (required) IBM cloud API key - WATSONX_TOKEN= # (required) IAM auth token (alternative to APIKEY) - WATSONX_PROJECT_ID= # (optional) Project ID of your WatsonX instance - WATSONX_DEPLOYMENT_SPACE_ID= # (optional) ID of deployment space for deployed models - ``` - - - -### 3. Custom LLM Objects - -Pass a custom LLM implementation or object from another library. - -See below for examples. +Here's a detailed breakdown of supported models and their capabilities: - - ```python Code - agent = Agent(llm="gpt-4o", ...) - ``` - + + | Model | Context Window | Best For | + |-------|---------------|-----------| + | GPT-4 | 8,192 tokens | High-accuracy tasks, complex reasoning | + | GPT-4 Turbo | 128,000 tokens | Long-form content, document analysis | + | GPT-4o & GPT-4o-mini | 128,000 tokens | Cost-effective large context processing | - - ```python Code - from crewai import LLM + + 1 token ≈ 4 characters in English. For example, 8,192 tokens ≈ 32,768 characters or about 6,000 words. + + + + | Model | Context Window | Best For | + |-------|---------------|-----------| + | Llama 3.1 70B/8B | 131,072 tokens | High-performance, large context tasks | + | Llama 3.2 Series | 8,192 tokens | General-purpose tasks | + | Mixtral 8x7B | 32,768 tokens | Balanced performance and context | + | Gemma Series | 8,192 tokens | Efficient, smaller-scale tasks | - llm = LLM(model="gpt-4", temperature=0.7) - agent = Agent(llm=llm, ...) - ``` - + + Groq is known for its fast inference speeds, making it suitable for real-time applications. + + + + | Provider | Context Window | Key Features | + |----------|---------------|--------------| + | Deepseek Chat | 128,000 tokens | Specialized in technical discussions | + | Claude 3 | Up to 200K tokens | Strong reasoning, code understanding | + | Gemini | Varies by model | Multimodal capabilities | + + + Provider selection should consider factors like: + - API availability in your region + - Pricing structure + - Required features (e.g., streaming, function calling) + - Performance requirements + + -## Connecting to OpenAI-Compatible LLMs +## Setting Up Your LLM -You can connect to OpenAI-compatible LLMs using either environment variables or by setting specific attributes on the LLM class: +There are three ways to configure LLMs in CrewAI. Choose the method that best fits your workflow: - - ```python Code - import os + + The simplest way to get started. Set these variables in your environment: - os.environ["OPENAI_API_KEY"] = "your-api-key" - os.environ["OPENAI_API_BASE"] = "https://api.your-provider.com/v1" + ```bash + # Required: Your API key for authentication + OPENAI_API_KEY= + + # Optional: Default model selection + OPENAI_MODEL_NAME=gpt-4o-mini # Default if not set + + # Optional: Organization ID (if applicable) + OPENAI_ORGANIZATION_ID= ``` - - - ```python Code + + + Never commit API keys to version control. Use environment files (.env) or your system's secret management. + + + + Create a YAML file to define your agent configurations. This method is great for version control and team collaboration: + + ```yaml + researcher: + # Agent Definition + role: Research Specialist + goal: Conduct comprehensive research and analysis + backstory: A dedicated research professional with years of experience + verbose: true + + # Model Selection (uncomment your choice) + + # OpenAI Models - Known for reliability and performance + llm: openai/gpt-4o-mini + # llm: openai/gpt-4 # More accurate but expensive + # llm: openai/gpt-4-turbo # Fast with large context + # llm: openai/gpt-4o # Optimized for longer texts + # llm: openai/o1-preview # Latest features + # llm: openai/o1-mini # Cost-effective + + # Azure Models - For enterprise deployments + # llm: azure/gpt-4o-mini + # llm: azure/gpt-4 + # llm: azure/gpt-35-turbo + + # Anthropic Models - Strong reasoning capabilities + # llm: anthropic/claude-3-opus-20240229-v1:0 + # llm: anthropic/claude-3-sonnet-20240229-v1:0 + # llm: anthropic/claude-3-haiku-20240307-v1:0 + # llm: anthropic/claude-2.1 + # llm: anthropic/claude-2.0 + + # Google Models - Good for general tasks + # llm: gemini/gemini-pro + # llm: gemini/gemini-1.5-pro-latest + # llm: gemini/gemini-1.0-pro-latest + + # AWS Bedrock Models - Enterprise-grade + # llm: bedrock/anthropic.claude-3-sonnet-20240229-v1:0 + # llm: bedrock/anthropic.claude-v2:1 + # llm: bedrock/amazon.titan-text-express-v1 + # llm: bedrock/meta.llama2-70b-chat-v1 + + # Mistral Models - Open source alternative + # llm: mistral/mistral-large-latest + # llm: mistral/mistral-medium-latest + # llm: mistral/mistral-small-latest + + # Groq Models - Fast inference + # llm: groq/mixtral-8x7b-32768 + # llm: groq/llama-3.1-70b-versatile + # llm: groq/llama-3.2-90b-text-preview + # llm: groq/gemma2-9b-it + # llm: groq/gemma-7b-it + + # IBM watsonx.ai Models - Enterprise features + # llm: watsonx/ibm/granite-13b-chat-v2 + # llm: watsonx/meta-llama/llama-3-1-70b-instruct + # llm: watsonx/bigcode/starcoder2-15b + + # Ollama Models - Local deployment + # llm: ollama/llama3:70b + # llm: ollama/codellama + # llm: ollama/mistral + # llm: ollama/mixtral + # llm: ollama/phi + + # Fireworks AI Models - Specialized tasks + # llm: fireworks_ai/accounts/fireworks/models/llama-v3-70b-instruct + # llm: fireworks_ai/accounts/fireworks/models/mixtral-8x7b + # llm: fireworks_ai/accounts/fireworks/models/zephyr-7b-beta + + # Perplexity AI Models - Research focused + # llm: pplx/llama-3.1-sonar-large-128k-online + # llm: pplx/mistral-7b-instruct + # llm: pplx/codellama-34b-instruct + # llm: pplx/mixtral-8x7b-instruct + + # Hugging Face Models - Community models + # llm: huggingface/meta-llama/Meta-Llama-3.1-8B-Instruct + # llm: huggingface/mistralai/Mixtral-8x7B-Instruct-v0.1 + # llm: huggingface/tiiuae/falcon-180B-chat + # llm: huggingface/google/gemma-7b-it + + # Nvidia NIM Models - GPU-optimized + # llm: nvidia_nim/meta/llama3-70b-instruct + # llm: nvidia_nim/mistral/mixtral-8x7b + # llm: nvidia_nim/google/gemma-7b + + # SambaNova Models - Enterprise AI + # llm: sambanova/Meta-Llama-3.1-8B-Instruct + # llm: sambanova/BioMistral-7B + # llm: sambanova/Falcon-180B + ``` + + + The YAML configuration allows you to: + - Version control your agent settings + - Easily switch between different models + - Share configurations across team members + - Document model choices and their purposes + + + + For maximum flexibility, configure LLMs directly in your Python code: + + ```python from crewai import LLM + # Basic configuration + llm = LLM(model="gpt-4") + + # Advanced configuration with detailed parameters + llm = LLM( + model="gpt-4o-mini", + temperature=0.7, # Higher for more creative outputs + timeout=120, # Seconds to wait for response + max_tokens=4000, # Maximum length of response + top_p=0.9, # Nucleus sampling parameter + frequency_penalty=0.1, # Reduce repetition + presence_penalty=0.1, # Encourage topic diversity + response_format={"type": "json"}, # For structured outputs + seed=42 # For reproducible results + ) + ``` + + + Parameter explanations: + - `temperature`: Controls randomness (0.0-1.0) + - `timeout`: Maximum wait time for response + - `max_tokens`: Limits response length + - `top_p`: Alternative to temperature for sampling + - `frequency_penalty`: Reduces word repetition + - `presence_penalty`: Encourages new topics + - `response_format`: Specifies output structure + - `seed`: Ensures consistent outputs + + + + +## Advanced Features and Optimization + +Learn how to get the most out of your LLM configuration: + + + + CrewAI includes smart context management features: + + ```python + from crewai import LLM + + # CrewAI automatically handles: + # 1. Token counting and tracking + # 2. Content summarization when needed + # 3. Task splitting for large contexts + llm = LLM( - model="custom-model-name", - api_key="your-api-key", - base_url="https://api.your-provider.com/v1" + model="gpt-4", + max_tokens=4000, # Limit response length ) - agent = Agent(llm=llm, ...) ``` - - -## LLM Configuration Options + + Best practices for context management: + 1. Choose models with appropriate context windows + 2. Pre-process long inputs when possible + 3. Use chunking for large documents + 4. Monitor token usage to optimize costs + + -When configuring an LLM for your agent, you have access to a wide range of parameters: + + + + Choose the right context window for your task: + - Small tasks (up to 4K tokens): Standard models + - Medium tasks (between 4K-32K): Enhanced models + - Large tasks (over 32K): Large context models + + ```python + # Configure model with appropriate settings + llm = LLM( + model="openai/gpt-4-turbo-preview", + temperature=0.7, # Adjust based on task + max_tokens=4096, # Set based on output needs + timeout=300 # Longer timeout for complex tasks + ) + ``` + + - Lower temperature (0.1 to 0.3) for factual responses + - Higher temperature (0.7 to 0.9) for creative tasks + + -| Parameter | Type | Description | -|:------------------|:---------------:|:-------------------------------------------------------------------------------------------------| -| **model** | `str` | Name of the model to use (e.g., "gpt-4", "gpt-3.5-turbo", "ollama/llama3.1"). For more options, visit the providers documentation. | -| **timeout** | `float, int` | Maximum time (in seconds) to wait for a response. | -| **temperature** | `float` | Controls randomness in output (0.0 to 1.0). | -| **top_p** | `float` | Controls diversity of output (0.0 to 1.0). | -| **n** | `int` | Number of completions to generate. | -| **stop** | `str, List[str]` | Sequence(s) where generation should stop. | -| **max_tokens** | `int` | Maximum number of tokens to generate. | -| **presence_penalty** | `float` | Penalizes new tokens based on their presence in prior text. | -| **frequency_penalty**| `float` | Penalizes new tokens based on their frequency in prior text. | -| **logit_bias** | `Dict[int, float]`| Modifies likelihood of specified tokens appearing. | -| **response_format** | `Dict[str, Any]` | Specifies the format of the response (e.g., JSON object). | -| **seed** | `int` | Sets a random seed for deterministic results. | -| **logprobs** | `bool` | Returns log probabilities of output tokens if enabled. | -| **top_logprobs** | `int` | Number of most likely tokens for which to return log probabilities. | -| **base_url** | `str` | The base URL for the API endpoint. | -| **api_version** | `str` | Version of the API to use. | -| **api_key** | `str` | Your API key for authentication. | + + 1. Monitor token usage + 2. Implement rate limiting + 3. Use caching when possible + 4. Set appropriate max_tokens limits + + + + Remember to regularly monitor your token usage and adjust your configuration as needed to optimize costs and performance. + + + -These are examples of how to configure LLMs for your agent. +## Provider Configuration Examples - + + ```python Code + # Required + OPENAI_API_KEY=sk-... + + # Optional + OPENAI_API_BASE= + OPENAI_ORGANIZATION= + ``` + Example usage: ```python Code from crewai import LLM @@ -211,193 +329,306 @@ These are examples of how to configure LLMs for your agent. frequency_penalty=0.1, presence_penalty=0.1, stop=["END"], - seed=42, - base_url="https://api.openai.com/v1", - api_key="your-api-key-here" + seed=42 ) - agent = Agent(llm=llm, ...) ``` - - - + + ```python Code - from crewai import LLM + ANTHROPIC_API_KEY=sk-ant-... + ``` + Example usage: + ```python Code llm = LLM( - model="cerebras/llama-3.1-70b", - api_key="your-api-key-here" - ) - agent = Agent(llm=llm, ...) - ``` - - - - - CrewAI supports using Ollama for running open-source models locally: - - 1. Install Ollama: [ollama.ai](https://ollama.ai/) - 2. Run a model: `ollama run llama2` - 3. Configure agent: - - ```python Code - from crewai import LLM - - agent = Agent( - llm=LLM( - model="ollama/llama3.1", - base_url="http://localhost:11434" - ), - ... + model="anthropic/claude-3-sonnet-20240229-v1:0", + temperature=0.7 ) ``` - - - + + ```python Code - from crewai import LLM + GEMINI_API_KEY= + ``` + Example usage: + ```python Code llm = LLM( - model="groq/llama3-8b-8192", - api_key="your-api-key-here" + model="gemini/gemini-pro", + temperature=0.7 ) - agent = Agent(llm=llm, ...) ``` - - - + + ```python Code - from crewai import LLM + # Required + AZURE_API_KEY= + AZURE_API_BASE= + AZURE_API_VERSION= + + # Optional + AZURE_AD_TOKEN= + AZURE_API_TYPE= + ``` + Example usage: + ```python Code llm = LLM( - model="anthropic/claude-3-5-sonnet-20241022", - api_key="your-api-key-here" + model="azure/gpt-4", + api_version="2023-05-15" ) - agent = Agent(llm=llm, ...) ``` - + - + ```python Code - from crewai import LLM + AWS_ACCESS_KEY_ID= + AWS_SECRET_ACCESS_KEY= + AWS_DEFAULT_REGION= + ``` + Example usage: + ```python Code llm = LLM( - model="fireworks_ai/accounts/fireworks/models/llama-v3-70b-instruct", - api_key="your-api-key-here" + model="bedrock/anthropic.claude-3-sonnet-20240229-v1:0" ) - agent = Agent(llm=llm, ...) ``` - - - + + ```python Code - from crewai import LLM + MISTRAL_API_KEY= + ``` + Example usage: + ```python Code llm = LLM( - model="gemini/gemini-1.5-pro-002", - api_key="your-api-key-here" + model="mistral/mistral-large-latest", + temperature=0.7 ) - agent = Agent(llm=llm, ...) ``` - - - + + ```python Code - from crewai import LLM + GROQ_API_KEY= + ``` + Example usage: + ```python Code llm = LLM( - model="llama-3.1-sonar-large-128k-online", - base_url="https://api.perplexity.ai/", - api_key="your-api-key-here" + model="groq/llama-3.2-90b-text-preview", + temperature=0.7 ) - agent = Agent(llm=llm, ...) ``` - - - - You can use IBM Watson by seeting the following ENV vars: + + ```python Code + # Required WATSONX_URL= WATSONX_APIKEY= WATSONX_PROJECT_ID= + + # Optional + WATSONX_TOKEN= + WATSONX_DEPLOYMENT_SPACE_ID= ``` - You can then define your agents llms by updating the `agents.yml` - - ```yaml Code - researcher: - role: Research Specialist - goal: Conduct comprehensive research and analysis to gather relevant information, - synthesize findings, and produce well-documented insights. - backstory: A dedicated research professional with years of experience in academic - investigation, literature review, and data analysis, known for thorough and - methodical approaches to complex research questions. - verbose: true - llm: watsonx/meta-llama/llama-3-1-70b-instruct - ``` - - You can also set up agents more dynamically as a base level LLM instance, like bellow: - + Example usage: ```python Code - from crewai import LLM - llm = LLM( - model="watsonx/ibm/granite-13b-chat-v2", - base_url="https://api.watsonx.ai/v1", - api_key="your-api-key-here" + model="watsonx/meta-llama/llama-3-1-70b-instruct", + base_url="https://api.watsonx.ai/v1" ) - agent = Agent(llm=llm, ...) ``` - + - + + 1. Install Ollama: [ollama.ai](https://ollama.ai/) + 2. Run a model: `ollama run llama2` + 3. Configure: ```python Code - from crewai import LLM + llm = LLM( + model="ollama/llama3:70b", + base_url="http://localhost:11434" + ) + ``` + + + ```python Code + FIREWORKS_API_KEY= + ``` + + Example usage: + ```python Code + llm = LLM( + model="fireworks_ai/accounts/fireworks/models/llama-v3-70b-instruct", + temperature=0.7 + ) + ``` + + + + ```python Code + PERPLEXITY_API_KEY= + ``` + + Example usage: + ```python Code + llm = LLM( + model="llama-3.1-sonar-large-128k-online", + base_url="https://api.perplexity.ai/" + ) + ``` + + + + ```python Code + HUGGINGFACE_API_KEY= + ``` + + Example usage: + ```python Code llm = LLM( model="huggingface/meta-llama/Meta-Llama-3.1-8B-Instruct", - api_key="your-api-key-here", base_url="your_api_endpoint" ) - agent = Agent(llm=llm, ...) ``` - + + + + ```python Code + NVIDIA_API_KEY= + ``` + + Example usage: + ```python Code + llm = LLM( + model="nvidia_nim/meta/llama3-70b-instruct", + temperature=0.7 + ) + ``` + + + + ```python Code + SAMBANOVA_API_KEY= + ``` + + Example usage: + ```python Code + llm = LLM( + model="sambanova/Meta-Llama-3.1-8B-Instruct", + temperature=0.7 + ) + ``` + + + + ```python Code + # Required + CEREBRAS_API_KEY= + ``` + + Example usage: + ```python Code + llm = LLM( + model="cerebras/llama3.1-70b", + temperature=0.7, + max_tokens=8192 + ) + ``` + + + Cerebras features: + - Fast inference speeds + - Competitive pricing + - Good balance of speed and quality + - Support for long context windows + + -## Changing the Base API URL +## Common Issues and Solutions -You can change the base API URL for any LLM provider by setting the `base_url` parameter: + + + + Most authentication issues can be resolved by checking API key format and environment variable names. + + + ```bash + # OpenAI + OPENAI_API_KEY=sk-... + + # Anthropic + ANTHROPIC_API_KEY=sk-ant-... + ``` + + + + Always include the provider prefix in model names + + + ```python + # Correct + llm = LLM(model="openai/gpt-4") + + # Incorrect + llm = LLM(model="gpt-4") + ``` + + + + Use larger context models for extensive tasks + + + ```python + # Large context model + llm = LLM(model="openai/gpt-4o") # 128K tokens + ``` + + -```python Code -from crewai import LLM +## Getting Help -llm = LLM( - model="custom-model-name", - base_url="https://api.your-provider.com/v1", - api_key="your-api-key" -) -agent = Agent(llm=llm, ...) -``` +If you need assistance, these resources are available: -This is particularly useful when working with OpenAI-compatible APIs or when you need to specify a different endpoint for your chosen provider. + + + Comprehensive documentation for LiteLLM integration and troubleshooting common issues. + + + Report bugs, request features, or browse existing issues for solutions. + + + Connect with other CrewAI users, share experiences, and get help from the community. + + -## Best Practices - -1. **Choose the right model**: Balance capability and cost. -2. **Optimize prompts**: Clear, concise instructions improve output. -3. **Manage tokens**: Monitor and limit token usage for efficiency. -4. **Use appropriate temperature**: Lower for factual tasks, higher for creative ones. -5. **Implement error handling**: Gracefully manage API errors and rate limits. - -## Troubleshooting - -- **API Errors**: Check your API key, network connection, and rate limits. -- **Unexpected Outputs**: Refine your prompts and adjust temperature or top_p. -- **Performance Issues**: Consider using a more powerful model or optimizing your queries. -- **Timeout Errors**: Increase the `timeout` parameter or optimize your input. + + Best Practices for API Key Security: + - Use environment variables or secure vaults + - Never commit keys to version control + - Rotate keys regularly + - Use separate keys for development and production + - Monitor key usage for unusual patterns + diff --git a/docs/concepts/tasks.mdx b/docs/concepts/tasks.mdx index 884c8fd27..fd48c242d 100644 --- a/docs/concepts/tasks.mdx +++ b/docs/concepts/tasks.mdx @@ -1,6 +1,6 @@ --- title: Tasks -description: Detailed guide on managing and creating tasks within the CrewAI framework, reflecting the latest codebase updates. +description: Detailed guide on managing and creating tasks within the CrewAI framework. icon: list-check --- @@ -8,41 +8,171 @@ icon: list-check In the CrewAI framework, a `Task` is a specific assignment completed by an `Agent`. -They provide all necessary details for execution, such as a description, the agent responsible, required tools, and more, facilitating a wide range of action complexities. - +Tasks provide all necessary details for execution, such as a description, the agent responsible, required tools, and more, facilitating a wide range of action complexities. Tasks within CrewAI can be collaborative, requiring multiple agents to work together. This is managed through the task properties and orchestrated by the Crew's process, enhancing teamwork and efficiency. +### Task Execution Flow + +Tasks can be executed in two ways: +- **Sequential**: Tasks are executed in the order they are defined +- **Hierarchical**: Tasks are assigned to agents based on their roles and expertise + +The execution flow is defined when creating the crew: +```python Code +crew = Crew( + agents=[agent1, agent2], + tasks=[task1, task2], + process=Process.sequential # or Process.hierarchical +) +``` + ## Task Attributes | Attribute | Parameters | Type | Description | | :------------------------------- | :---------------- | :---------------------------- | :------------------------------------------------------------------------------------------------------------------- | | **Description** | `description` | `str` | A clear, concise statement of what the task entails. | -| **Agent** | `agent` | `Optional[BaseAgent]` | The agent responsible for the task, assigned either directly or by the crew's process. | | **Expected Output** | `expected_output` | `str` | A detailed description of what the task's completion looks like. | -| **Tools** _(optional)_ | `tools` | `Optional[List[Any]]` | The functions or capabilities the agent can utilize to perform the task. Defaults to an empty list. | -| **Async Execution** _(optional)_ | `async_execution` | `Optional[bool]` | If set, the task executes asynchronously, allowing progression without waiting for completion. Defaults to False. | -| **Context** _(optional)_ | `context` | `Optional[List["Task"]]` | Specifies tasks whose outputs are used as context for this task. | -| **Config** _(optional)_ | `config` | `Optional[Dict[str, Any]]` | Additional configuration details for the agent executing the task, allowing further customization. Defaults to None. | -| **Output JSON** _(optional)_ | `output_json` | `Optional[Type[BaseModel]]` | Outputs a JSON object, requiring an OpenAI client. Only one output format can be set. | -| **Output Pydantic** _(optional)_ | `output_pydantic` | `Optional[Type[BaseModel]]` | Outputs a Pydantic model object, requiring an OpenAI client. Only one output format can be set. | -| **Output File** _(optional)_ | `output_file` | `Optional[str]` | Saves the task output to a file. If used with `Output JSON` or `Output Pydantic`, specifies how the output is saved. | -| **Output** _(optional)_ | `output` | `Optional[TaskOutput]` | An instance of `TaskOutput`, containing the raw, JSON, and Pydantic output plus additional details. | -| **Callback** _(optional)_ | `callback` | `Optional[Any]` | A callable that is executed with the task's output upon completion. | -| **Human Input** _(optional)_ | `human_input` | `Optional[bool]` | Indicates if the task should involve human review at the end, useful for tasks needing human oversight. Defaults to False.| -| **Converter Class** _(optional)_ | `converter_cls` | `Optional[Type[Converter]]` | A converter class used to export structured output. Defaults to None. | +| **Name** _(optional)_ | `name` | `Optional[str]` | A name identifier for the task. | +| **Agent** _(optional)_ | `agent` | `Optional[BaseAgent]` | The agent responsible for executing the task. | +| **Tools** _(optional)_ | `tools` | `List[BaseTool]` | The tools/resources the agent is limited to use for this task. | +| **Context** _(optional)_ | `context` | `Optional[List["Task"]]` | Other tasks whose outputs will be used as context for this task. | +| **Async Execution** _(optional)_ | `async_execution` | `Optional[bool]` | Whether the task should be executed asynchronously. Defaults to False. | +| **Config** _(optional)_ | `config` | `Optional[Dict[str, Any]]` | Task-specific configuration parameters. | +| **Output File** _(optional)_ | `output_file` | `Optional[str]` | File path for storing the task output. | +| **Output JSON** _(optional)_ | `output_json` | `Optional[Type[BaseModel]]` | A Pydantic model to structure the JSON output. | +| **Output Pydantic** _(optional)_ | `output_pydantic` | `Optional[Type[BaseModel]]` | A Pydantic model for task output. | +| **Callback** _(optional)_ | `callback` | `Optional[Any]` | Function/object to be executed after task completion. | -## Creating a Task +## Creating Tasks -Creating a task involves defining its scope, responsible agent, and any additional attributes for flexibility: +There are two ways to create tasks in CrewAI: using **YAML configuration (recommended)** or defining them **directly in code**. +### YAML Configuration (Recommended) + +Using YAML configuration provides a cleaner, more maintainable way to define tasks. We strongly recommend using this approach to define tasks in your CrewAI projects. + +After creating your CrewAI project as outlined in the [Installation](/installation) section, navigate to the `src/latest_ai_development/config/tasks.yaml` file and modify the template to match your specific task requirements. + + +Variables in your YAML files (like `{topic}`) will be replaced with values from your inputs when running the crew: ```python Code +crew.kickoff(inputs={'topic': 'AI Agents'}) +``` + + +Here's an example of how to configure tasks using YAML: + +```yaml tasks.yaml +research_task: + description: > + Conduct a thorough research about {topic} + Make sure you find any interesting and relevant information given + the current year is 2024. + expected_output: > + A list with 10 bullet points of the most relevant information about {topic} + agent: researcher + +reporting_task: + description: > + Review the context you got and expand each topic into a full section for a report. + Make sure the report is detailed and contains any and all relevant information. + expected_output: > + A fully fledge reports with the mains topics, each with a full section of information. + Formatted as markdown without '```' + agent: reporting_analyst + output_file: report.md +``` + +To use this YAML configuration in your code, create a crew class that inherits from `CrewBase`: + +```python crew.py +# src/latest_ai_development/crew.py + +from crewai import Agent, Crew, Process, Task +from crewai.project import CrewBase, agent, crew, task +from crewai_tools import SerperDevTool + +@CrewBase +class LatestAiDevelopmentCrew(): + """LatestAiDevelopment crew""" + + @agent + def researcher(self) -> Agent: + return Agent( + config=self.agents_config['researcher'], + verbose=True, + tools=[SerperDevTool()] + ) + + @agent + def reporting_analyst(self) -> Agent: + return Agent( + config=self.agents_config['reporting_analyst'], + verbose=True + ) + + @task + def research_task(self) -> Task: + return Task( + config=self.tasks_config['research_task'] + ) + + @task + def reporting_task(self) -> Task: + return Task( + config=self.tasks_config['reporting_task'] + ) + + @crew + def crew(self) -> Crew: + return Crew( + agents=[ + self.researcher(), + self.reporting_analyst() + ], + tasks=[ + self.research_task(), + self.reporting_task() + ], + process=Process.sequential + ) +``` + + +The names you use in your YAML files (`agents.yaml` and `tasks.yaml`) should match the method names in your Python code. + + +### Direct Code Definition (Alternative) + +Alternatively, you can define tasks directly in your code without using YAML configuration: + +```python task.py from crewai import Task -task = Task( - description='Find and summarize the latest and most relevant news on AI', - agent=sales_agent, - expected_output='A bullet list summary of the top 5 most important AI news', +research_task = Task( + description=""" + Conduct a thorough research about AI Agents. + Make sure you find any interesting and relevant information given + the current year is 2024. + """, + expected_output=""" + A list with 10 bullet points of the most relevant information about AI Agents + """, + agent=researcher +) + +reporting_task = Task( + description=""" + Review the context you got and expand each topic into a full section for a report. + Make sure the report is detailed and contains any and all relevant information. + """, + expected_output=""" + A fully fledge reports with the mains topics, each with a full section of information. + Formatted as markdown without '```' + """, + agent=reporting_analyst, + output_file="report.md" ) ``` @@ -52,6 +182,8 @@ task = Task( ## Task Output +Understanding task outputs is crucial for building effective AI workflows. CrewAI provides a structured way to handle task results through the `TaskOutput` class, which supports multiple output formats and can be easily passed between tasks. + The output of a task in CrewAI framework is encapsulated within the `TaskOutput` class. This class provides a structured way to access results of a task, including various formats such as raw output, JSON, and Pydantic models. By default, the `TaskOutput` will only include the `raw` output. A `TaskOutput` will only include the `pydantic` or `json_dict` output if the original `Task` object was configured with `output_pydantic` or `output_json`, respectively. @@ -112,6 +244,25 @@ if task_output.pydantic: print(f"Pydantic Output: {task_output.pydantic}") ``` +## Task Dependencies and Context + +Tasks can depend on the output of other tasks using the `context` attribute. For example: + +```python Code +research_task = Task( + description="Research the latest developments in AI", + expected_output="A list of recent AI developments", + agent=researcher +) + +analysis_task = Task( + description="Analyze the research findings and identify key trends", + expected_output="Analysis report of AI trends", + agent=analyst, + context=[research_task] # This task will wait for research_task to complete +) +``` + ## Integrating Tools with Tasks Leverage tools from the [CrewAI Toolkit](https://github.com/joaomdmoura/crewai-tools) and [LangChain Tools](https://python.langchain.com/docs/integrations/tools) for enhanced task performance and agent interaction. @@ -167,16 +318,16 @@ This is useful when you have a task that depends on the output of another task t # ... research_ai_task = Task( - description='Find and summarize the latest AI news', - expected_output='A bullet list summary of the top 5 most important AI news', + description="Research the latest developments in AI", + expected_output="A list of recent AI developments", async_execution=True, agent=research_agent, tools=[search_tool] ) research_ops_task = Task( - description='Find and summarize the latest AI Ops news', - expected_output='A bullet list summary of the top 5 most important AI Ops news', + description="Research the latest developments in AI Ops", + expected_output="A list of recent AI Ops developments", async_execution=True, agent=research_agent, tools=[search_tool] @@ -184,7 +335,7 @@ research_ops_task = Task( write_blog_task = Task( description="Write a full blog post about the importance of AI and its latest news", - expected_output='Full blog post that is 4 paragraphs long', + expected_output="Full blog post that is 4 paragraphs long", agent=writer_agent, context=[research_ai_task, research_ops_task] ) diff --git a/docs/installation.mdx b/docs/installation.mdx index ec3da38b7..ab3bf341b 100644 --- a/docs/installation.mdx +++ b/docs/installation.mdx @@ -1,128 +1,145 @@ --- title: Installation -description: +description: Get started with CrewAI - Install, configure, and build your first AI crew icon: wrench --- -This guide will walk you through the installation process for CrewAI and its dependencies. -CrewAI is a flexible and powerful AI framework that enables you to create and manage AI agents, tools, and tasks efficiently. -Let's get started! 🚀 + + **Python Version Requirements** + + CrewAI requires `Python >=3.10 and <=3.13`. Here's how to check your version: + ```bash + python3 --version + ``` + + If you need to update Python, visit [python.org/downloads](https://python.org/downloads) + - - Make sure you have `Python >=3.10 <=3.13` installed on your system before you proceed. - +# Installing CrewAI + +CrewAI is a flexible and powerful AI framework that enables you to create and manage AI agents, tools, and tasks efficiently. +Let's get you set up! 🚀 - Install the main CrewAI package with the following command: - - ```shell Terminal - pip install crewai - ``` - - You can also install the main CrewAI package and the tools package that include a series of helpful tools for your agents: - - ```shell Terminal - pip install 'crewai[tools]' - ``` - - Alternatively, you can also use: - - ```shell Terminal - pip install crewai crewai-tools - ``` - - - - To upgrade CrewAI and CrewAI Tools to the latest version, run the following command - - ```shell Terminal - pip install --upgrade crewai crewai-tools - ``` - - - 1. If you're using an older version of CrewAI, you may receive a warning about using `Poetry` for dependency management. - ![Error from older versions](./images/crewai-run-poetry-error.png) - - 2. In this case, you'll need to run the command below to update your project. - This command will migrate your project to use [UV](https://github.com/astral-sh/uv) and update the necessary files. + Install CrewAI with all recommended tools using either method: ```shell Terminal - crewai update + pip install 'crewai[tools]' + ``` + or + ```shell Terminal + pip install crewai crewai-tools ``` - 3. After running the command above, you should see the following output: - ![Successfully migrated to UV](./images/crewai-update.png) - 4. You're all set! You can now proceed to the next step! 🎉 - + + Both methods install the core package and additional tools needed for most use cases. + - - To verify that `crewai` and `crewai-tools` are installed correctly, run the following command - - ```shell Terminal - pip freeze | grep crewai - ``` - - You should see the version number of `crewai` and `crewai-tools`. - - ```markdown Version - crewai==X.X.X - crewai-tools==X.X.X - ``` - - If you see the version number, then the installation was successful! 🎉 + + + If you have an older version of CrewAI installed, you can upgrade it: + ```shell Terminal + pip install --upgrade crewai crewai-tools + ``` + + + If you see a Poetry-related warning, you'll need to migrate to our new dependency manager: + ```shell Terminal + crewai update + ``` + This will update your project to use [UV](https://github.com/astral-sh/uv), our new faster dependency manager. + + + + Skip this step if you're doing a fresh installation. + + + + + Check your installed versions: + ```shell Terminal + pip freeze | grep crewai + ``` + + You should see something like: + ```markdown Output + crewai==X.X.X + crewai-tools==X.X.X + ``` + Installation successful! You're ready to create your first crew. -## Create a new CrewAI project +# Creating a New Project -The next step is to create a new CrewAI project. -We recommend using the YAML Template scaffolding to get started as it provides a structured approach to defining agents and tasks. + + We recommend using the YAML Template scaffolding for a structured approach to defining agents and tasks. + - - To create a new CrewAI project, run the following CLI (Command Line Interface) command: - - ```shell Terminal - crewai create crew - ``` - - This command creates a new project folder with the following structure: - | File/Directory | Description | - |:------------------------|:-------------------------------------------------| - | `my_project/` | Root directory of the project | - | ├── `.gitignore` | Specifies files and directories to ignore in Git | - | ├── `pyproject.toml` | Project configuration and dependencies | - | ├── `README.md` | Project documentation | - | ├── `.env` | Environment variables | - | └── `src/` | Source code directory | - |     └── `my_project/` | Main application package | - |         ├── `__init__.py` | Marks the directory as a Python package | - |         ├── `main.py` | Main application script | - |         ├── `crew.py` | Crew-related functionalities | - |         ├── `tools/` | Custom tools directory | - |         │ ├── `custom_tool.py` | Custom tool implementation | - |         │ └── `__init__.py` | Marks tools directory as a package | - |         └── `config/` | Configuration files directory | - |             ├── `agents.yaml` | Agent configurations | - |             └── `tasks.yaml` | Task configurations | + + Run the CrewAI CLI command: + ```shell Terminal + crewai create crew + ``` - You can now start developing your crew by editing the files in the `src/my_project` folder. - The `main.py` file is the entry point of the project, the `crew.py` file is where you define your crew, the `agents.yaml` file is where you define your agents, - and the `tasks.yaml` file is where you define your tasks. + This creates a new project with the following structure: + + ``` + my_project/ + ├── .gitignore + ├── pyproject.toml + ├── README.md + ├── .env + └── src/ + └── my_project/ + ├── __init__.py + ├── main.py + ├── crew.py + ├── tools/ + │ ├── custom_tool.py + │ └── __init__.py + └── config/ + ├── agents.yaml + └── tasks.yaml + ``` + - - To customize your project, you can: - - Modify `src/my_project/config/agents.yaml` to define your agents. - - Modify `src/my_project/config/tasks.yaml` to define your tasks. - - Modify `src/my_project/crew.py` to add your own logic, tools, and specific arguments. - - Modify `src/my_project/main.py` to add custom inputs for your agents and tasks. - - Add your environment variables into the `.env` file. + + + Your project will contain these essential files: + + | File | Purpose | + | --- | --- | + | `agents.yaml` | Define your AI agents and their roles | + | `tasks.yaml` | Set up agent tasks and workflows | + | `.env` | Store API keys and environment variables | + | `main.py` | Project entry point and execution flow | + | `crew.py` | Crew orchestration and coordination | + | `tools/` | Directory for custom agent tools | + + + Start by editing `agents.yaml` and `tasks.yaml` to define your crew's behavior. + Keep sensitive information like API keys in `.env`. + -## Next steps +## Next Steps -Now that you have installed `crewai` and `crewai-tools`, you're ready to spin up your first crew! - -- 👨‍💻 Build your first agent with CrewAI by following the [Quickstart](/quickstart) guide. -- 💬 Join the [Community](https://community.crewai.com) to get help and share your feedback. + + + Follow our quickstart guide to create your first CrewAI agent and get hands-on experience. + + + Connect with other developers, get help, and share your CrewAI experiences. + + diff --git a/docs/introduction.mdx b/docs/introduction.mdx index d657c9fb2..9aed6cdea 100644 --- a/docs/introduction.mdx +++ b/docs/introduction.mdx @@ -1,49 +1,85 @@ --- title: Introduction -description: Welcome to CrewAI docs! +description: Build AI agent teams that work together to tackle complex tasks icon: handshake --- # What is CrewAI? -**CrewAI is a cutting-edge Python framework for orchestrating role-playing, autonomous AI agents.** +**CrewAI is a cutting-edge framework for orchestrating autonomous AI agents.** -By fostering collaborative intelligence, CrewAI empowers agents to work together seamlessly, tackling complex tasks. +CrewAI enables you to create AI teams where each agent has specific roles, tools, and goals, working together to accomplish complex tasks. - - CrewAI Mindmap - +Think of it as assembling your dream team - each member (agent) brings unique skills and expertise, collaborating seamlessly to achieve your objectives. -## Why CrewAI? -- 🤼‍♀️ **Role-Playing Agents**: Agents can take on different roles and personas to better understand and interact with complex systems. -- 🤖 **Autonomous Decision Making**: Agents can make decisions autonomously based on the given context and available tools. -- 🤝 **Seamless Collaboration**: Agents can work together seamlessly, sharing information and resources to achieve common goals. -- 🧠 **Complex Task Tackling**: CrewAI is designed to tackle complex tasks, such as multi-step workflows, decision making, and problem solving. +## How CrewAI Works -# Get Started with CrewAI + + Just like a company has departments (Sales, Engineering, Marketing) working together under leadership to achieve business goals, CrewAI helps you create an organization of AI agents with specialized roles collaborating to accomplish complex tasks. + + + + CrewAI Framework Overview + + +| Component | Description | Key Features | +|:----------|:-----------:|:------------| +| **Crew** | The top-level organization | • Manages AI agent teams
• Oversees workflows
• Ensures collaboration
• Delivers outcomes | +| **AI Agents** | Specialized team members | • Have specific roles (researcher, writer)
• Use designated tools
• Can delegate tasks
• Make autonomous decisions | +| **Process** | Workflow management system | • Defines collaboration patterns
• Controls task assignments
• Manages interactions
• Ensures efficient execution | +| **Tasks** | Individual assignments | • Have clear objectives
• Use specific tools
• Feed into larger process
• Produce actionable results | + +### How It All Works Together + +1. The **Crew** organizes the overall operation +2. **AI Agents** work on their specialized tasks +3. The **Process** ensures smooth collaboration +4. **Tasks** get completed to achieve the goal + +## Key Features + + Create specialized agents with defined roles, expertise, and goals - from researchers to analysts to writers + + + Equip agents with custom tools and APIs to interact with external services and data sources + + + Agents work together, sharing insights and coordinating tasks to achieve complex objectives + + + Define sequential or parallel workflows, with agents automatically handling task dependencies + + + +## Why Choose CrewAI? + +- 🧠 **Autonomous Operation**: Agents make intelligent decisions based on their roles and available tools +- 📝 **Natural Interaction**: Agents communicate and collaborate like human team members +- 🛠️ **Extensible Design**: Easy to add new tools, roles, and capabilities +- 🚀 **Production Ready**: Built for reliability and scalability in real-world applications + + - Getting started with CrewAI + Get started with CrewAI in your development environment. + + + Follow our quickstart guide to create your first CrewAI agent and get hands-on experience. - Join the CrewAI community and get help with your project! - - - -## Next Step - -- [Install CrewAI](/installation) to get started with your first agent. - + > + Connect with other developers, get help, and share your CrewAI experiences. + + \ No newline at end of file diff --git a/docs/quickstart.mdx b/docs/quickstart.mdx index 246bbdf84..a78a56182 100644 --- a/docs/quickstart.mdx +++ b/docs/quickstart.mdx @@ -356,4 +356,21 @@ This will clear the crew's memory, allowing for a fresh start. ## Deploying Your Project -The easiest way to deploy your crew is through [CrewAI Enterprise](http://app.crewai.com/), where you can deploy your crew in a few clicks. +The easiest way to deploy your crew is through CrewAI Enterprise, where you can deploy your crew in a few clicks. + + + + Get started with CrewAI Enterprise and deploy your crew in a production environment with just a few clicks. + + + Join our open source community to discuss ideas, share your projects, and connect with other CrewAI developers. + + From 3285c1b1967027f97ddda4133f1f6f9142692904 Mon Sep 17 00:00:00 2001 From: "Brandon Hancock (bhancock_ai)" <109994880+bhancockio@users.noreply.github.com> Date: Mon, 2 Dec 2024 13:38:17 -0500 Subject: [PATCH 088/126] Fixes issues with result as answer not properly exiting LLM loop (#1689) * v1 of fix implemented. Need to confirm with tokens. * remove print statements --- src/crewai/agent.py | 6 +-- src/crewai/agents/crew_agent_executor.py | 48 ++++++++++++++++++------ src/crewai/tools/base_tool.py | 1 + src/crewai/tools/structured_tool.py | 4 ++ 4 files changed, 44 insertions(+), 15 deletions(-) diff --git a/src/crewai/agent.py b/src/crewai/agent.py index 26380ebc2..abe678db1 100644 --- a/src/crewai/agent.py +++ b/src/crewai/agent.py @@ -1,7 +1,7 @@ import os import shutil import subprocess -from typing import Any, List, Literal, Optional, Union, Dict +from typing import Any, Dict, List, Literal, Optional, Union from pydantic import Field, InstanceOf, PrivateAttr, model_validator @@ -9,9 +9,10 @@ from crewai.agents import CacheHandler from crewai.agents.agent_builder.base_agent import BaseAgent from crewai.agents.crew_agent_executor import CrewAgentExecutor from crewai.cli.constants import ENV_VARS -from crewai.llm import LLM from crewai.knowledge.knowledge import Knowledge from crewai.knowledge.source.base_knowledge_source import BaseKnowledgeSource +from crewai.knowledge.utils.knowledge_utils import extract_knowledge_context +from crewai.llm import LLM from crewai.memory.contextual.contextual_memory import ContextualMemory from crewai.task import Task from crewai.tools import BaseTool @@ -21,7 +22,6 @@ from crewai.utilities.constants import TRAINED_AGENTS_DATA_FILE, TRAINING_DATA_F from crewai.utilities.converter import generate_model_description from crewai.utilities.token_counter_callback import TokenCalcHandler from crewai.utilities.training_handler import CrewTrainingHandler -from crewai.knowledge.utils.knowledge_utils import extract_knowledge_context def mock_agent_ops_provider(): diff --git a/src/crewai/agents/crew_agent_executor.py b/src/crewai/agents/crew_agent_executor.py index bf14e6915..2c880a634 100644 --- a/src/crewai/agents/crew_agent_executor.py +++ b/src/crewai/agents/crew_agent_executor.py @@ -1,5 +1,6 @@ import json import re +from dataclasses import dataclass from typing import Any, Dict, List, Union from crewai.agents.agent_builder.base_agent import BaseAgent @@ -12,6 +13,7 @@ from crewai.agents.parser import ( OutputParserException, ) from crewai.agents.tools_handler import ToolsHandler +from crewai.tools.base_tool import BaseTool from crewai.tools.tool_usage import ToolUsage, ToolUsageErrorException from crewai.utilities import I18N, Printer from crewai.utilities.constants import TRAINING_DATA_FILE @@ -22,6 +24,12 @@ from crewai.utilities.logger import Logger from crewai.utilities.training_handler import CrewTrainingHandler +@dataclass +class ToolResult: + result: Any + result_as_answer: bool + + class CrewAgentExecutor(CrewAgentExecutorMixin): _logger: Logger = Logger() @@ -33,7 +41,7 @@ class CrewAgentExecutor(CrewAgentExecutorMixin): agent: BaseAgent, prompt: dict[str, str], max_iter: int, - tools: List[Any], + tools: List[BaseTool], tools_names: str, stop_words: List[str], tools_description: str, @@ -70,7 +78,9 @@ class CrewAgentExecutor(CrewAgentExecutorMixin): self.iterations = 0 self.log_error_after = 3 self.have_forced_answer = False - self.name_to_tool_map = {tool.name: tool for tool in self.tools} + self.tool_name_to_tool_map: Dict[str, BaseTool] = { + tool.name: tool for tool in self.tools + } if self.llm.stop: self.llm.stop = list(set(self.llm.stop + self.stop)) else: @@ -140,9 +150,17 @@ class CrewAgentExecutor(CrewAgentExecutorMixin): formatted_answer = self._format_answer(answer) if isinstance(formatted_answer, AgentAction): - action_result = self._use_tool(formatted_answer) - formatted_answer.text += f"\nObservation: {action_result}" - formatted_answer.result = action_result + tool_result = self._execute_tool_and_check_finality( + formatted_answer + ) + formatted_answer.text += f"\nObservation: {tool_result.result}" + formatted_answer.result = tool_result.result + if tool_result.result_as_answer: + return AgentFinish( + thought="", + output=tool_result.result, + text=formatted_answer.text, + ) self._show_logs(formatted_answer) if self.step_callback: @@ -239,7 +257,7 @@ class CrewAgentExecutor(CrewAgentExecutorMixin): content=f"\033[95m## Final Answer:\033[00m \033[92m\n{formatted_answer.output}\033[00m\n\n" ) - def _use_tool(self, agent_action: AgentAction) -> Any: + def _execute_tool_and_check_finality(self, agent_action: AgentAction) -> ToolResult: tool_usage = ToolUsage( tools_handler=self.tools_handler, tools=self.tools, @@ -255,19 +273,25 @@ class CrewAgentExecutor(CrewAgentExecutorMixin): if isinstance(tool_calling, ToolUsageErrorException): tool_result = tool_calling.message + return ToolResult(result=tool_result, result_as_answer=False) else: if tool_calling.tool_name.casefold().strip() in [ - name.casefold().strip() for name in self.name_to_tool_map + name.casefold().strip() for name in self.tool_name_to_tool_map ] or tool_calling.tool_name.casefold().replace("_", " ") in [ - name.casefold().strip() for name in self.name_to_tool_map + name.casefold().strip() for name in self.tool_name_to_tool_map ]: tool_result = tool_usage.use(tool_calling, agent_action.text) + tool = self.tool_name_to_tool_map.get(tool_calling.tool_name) + if tool: + return ToolResult( + result=tool_result, result_as_answer=tool.result_as_answer + ) else: tool_result = self._i18n.errors("wrong_tool_name").format( tool=tool_calling.tool_name, tools=", ".join([tool.name.casefold() for tool in self.tools]), ) - return tool_result + return ToolResult(result=tool_result, result_as_answer=False) def _summarize_messages(self) -> None: messages_groups = [] @@ -333,9 +357,9 @@ class CrewAgentExecutor(CrewAgentExecutorMixin): if self.crew is not None and hasattr(self.crew, "_train_iteration"): train_iteration = self.crew._train_iteration if agent_id in training_data and isinstance(train_iteration, int): - training_data[agent_id][train_iteration]["improved_output"] = ( - result.output - ) + training_data[agent_id][train_iteration][ + "improved_output" + ] = result.output training_handler.save(training_data) else: self._logger.log( diff --git a/src/crewai/tools/base_tool.py b/src/crewai/tools/base_tool.py index 46bfc03a6..c3840d23c 100644 --- a/src/crewai/tools/base_tool.py +++ b/src/crewai/tools/base_tool.py @@ -73,6 +73,7 @@ class BaseTool(BaseModel, ABC): description=self.description, args_schema=self.args_schema, func=self._run, + result_as_answer=self.result_as_answer, ) @classmethod diff --git a/src/crewai/tools/structured_tool.py b/src/crewai/tools/structured_tool.py index bd6818605..dfd23a9cb 100644 --- a/src/crewai/tools/structured_tool.py +++ b/src/crewai/tools/structured_tool.py @@ -22,6 +22,7 @@ class CrewStructuredTool: description: str, args_schema: type[BaseModel], func: Callable[..., Any], + result_as_answer: bool = False, ) -> None: """Initialize the structured tool. @@ -30,12 +31,14 @@ class CrewStructuredTool: description: A description of what the tool does args_schema: The pydantic model for the tool's arguments func: The function to run when the tool is called + result_as_answer: Whether to return the output directly """ self.name = name self.description = description self.args_schema = args_schema self.func = func self._logger = Logger() + self.result_as_answer = result_as_answer # Validate the function signature matches the schema self._validate_function_signature() @@ -98,6 +101,7 @@ class CrewStructuredTool: description=description, args_schema=schema, func=func, + result_as_answer=return_direct, ) @staticmethod From f8a8e7b2a5e35205453f114b78d7768c7e37607e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Moura?= Date: Mon, 2 Dec 2024 18:28:58 -0300 Subject: [PATCH 089/126] preparing new version --- pyproject.toml | 2 +- src/crewai/__init__.py | 2 +- src/crewai/cli/templates/crew/pyproject.toml | 2 +- src/crewai/cli/templates/flow/pyproject.toml | 2 +- src/crewai/cli/templates/pipeline/pyproject.toml | 2 +- src/crewai/cli/templates/pipeline_router/pyproject.toml | 2 +- src/crewai/cli/templates/tool/pyproject.toml | 2 +- uv.lock | 2 +- 8 files changed, 8 insertions(+), 8 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 1d7d8cc43..86891294c 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "crewai" -version = "0.83.0" +version = "0.85.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" diff --git a/src/crewai/__init__.py b/src/crewai/__init__.py index 34d7f17c9..440ba090c 100644 --- a/src/crewai/__init__.py +++ b/src/crewai/__init__.py @@ -16,7 +16,7 @@ warnings.filterwarnings( category=UserWarning, module="pydantic.main", ) -__version__ = "0.83.0" +__version__ = "0.85.0" __all__ = [ "Agent", "Crew", diff --git a/src/crewai/cli/templates/crew/pyproject.toml b/src/crewai/cli/templates/crew/pyproject.toml index 1e456c725..e45732685 100644 --- a/src/crewai/cli/templates/crew/pyproject.toml +++ b/src/crewai/cli/templates/crew/pyproject.toml @@ -5,7 +5,7 @@ description = "{{name}} using crewAI" authors = [{ name = "Your Name", email = "you@example.com" }] requires-python = ">=3.10,<=3.13" dependencies = [ - "crewai[tools]>=0.83.0,<1.0.0" + "crewai[tools]>=0.85.0,<1.0.0" ] [project.scripts] diff --git a/src/crewai/cli/templates/flow/pyproject.toml b/src/crewai/cli/templates/flow/pyproject.toml index 575aaf086..d981987c8 100644 --- a/src/crewai/cli/templates/flow/pyproject.toml +++ b/src/crewai/cli/templates/flow/pyproject.toml @@ -5,7 +5,7 @@ description = "{{name}} using crewAI" authors = [{ name = "Your Name", email = "you@example.com" }] requires-python = ">=3.10,<=3.13" dependencies = [ - "crewai[tools]>=0.83.0,<1.0.0", + "crewai[tools]>=0.85.0,<1.0.0", ] [project.scripts] diff --git a/src/crewai/cli/templates/pipeline/pyproject.toml b/src/crewai/cli/templates/pipeline/pyproject.toml index d12dccf11..1e7f4efd5 100644 --- a/src/crewai/cli/templates/pipeline/pyproject.toml +++ b/src/crewai/cli/templates/pipeline/pyproject.toml @@ -6,7 +6,7 @@ authors = ["Your Name "] [tool.poetry.dependencies] python = ">=3.10,<=3.13" -crewai = { extras = ["tools"], version = ">=0.83.0,<1.0.0" } +crewai = { extras = ["tools"], version = ">=0.85.0,<1.0.0" } asyncio = "*" [tool.poetry.scripts] diff --git a/src/crewai/cli/templates/pipeline_router/pyproject.toml b/src/crewai/cli/templates/pipeline_router/pyproject.toml index 06487bcfa..49208d120 100644 --- a/src/crewai/cli/templates/pipeline_router/pyproject.toml +++ b/src/crewai/cli/templates/pipeline_router/pyproject.toml @@ -5,7 +5,7 @@ description = "{{name}} using crewAI" authors = ["Your Name "] requires-python = ">=3.10,<=3.13" dependencies = [ - "crewai[tools]>=0.83.0,<1.0.0" + "crewai[tools]>=0.85.0,<1.0.0" ] [project.scripts] diff --git a/src/crewai/cli/templates/tool/pyproject.toml b/src/crewai/cli/templates/tool/pyproject.toml index 7c1afddfa..33dabd38e 100644 --- a/src/crewai/cli/templates/tool/pyproject.toml +++ b/src/crewai/cli/templates/tool/pyproject.toml @@ -5,6 +5,6 @@ description = "Power up your crews with {{folder_name}}" readme = "README.md" requires-python = ">=3.10,<=3.13" dependencies = [ - "crewai[tools]>=0.83.0" + "crewai[tools]>=0.85.0" ] diff --git a/uv.lock b/uv.lock index 050602e61..900e730da 100644 --- a/uv.lock +++ b/uv.lock @@ -608,7 +608,7 @@ wheels = [ [[package]] name = "crewai" -version = "0.83.0" +version = "0.85.0" source = { editable = "." } dependencies = [ { name = "appdirs" }, From 7d9d0ff6f7fe38395ee2c7f2e8f26b1afa54f5d3 Mon Sep 17 00:00:00 2001 From: Tony Kipkemboi Date: Tue, 3 Dec 2024 10:02:06 -0500 Subject: [PATCH 090/126] fix missing code in flows docs (#1690) * docs: improve tasks documentation clarity and structure - Add Task Execution Flow section - Add variable interpolation explanation - Add Task Dependencies section with examples - Improve overall document structure and readability - Update code examples with proper syntax highlighting * docs: update agent documentation with improved examples and formatting - Replace DuckDuckGoSearchRun with SerperDevTool - Update code block formatting to be consistent - Improve template examples with actual syntax - Update LLM examples to use current models - Clean up formatting and remove redundant comments * docs: enhance LLM documentation with Cerebras provider and formatting improvements * docs: simplify LLMs documentation title * docs: improve installation guide clarity and structure - Add clear Python version requirements with check command - Simplify installation options to recommended method - Improve upgrade section clarity for existing users - Add better visual structure with Notes and Tips - Update description and formatting * docs: improve introduction page organization and clarity - Update organizational analogy in Note section - Improve table formatting and alignment - Remove emojis from component table for cleaner look - Add 'helps you' to make the note more action-oriented * docs: add enterprise and community cards - Add Enterprise deployment card in quickstart - Add community card focused on open source discussions - Remove deployment reference from community description - Clean up introduction page cards - Remove link from Enterprise description text * docs: add code snippet to Getting Started section in flows.mdx --------- Co-authored-by: Brandon Hancock (bhancock_ai) <109994880+bhancockio@users.noreply.github.com> --- docs/concepts/flows.mdx | 270 ++++++++++------------------------------ 1 file changed, 68 insertions(+), 202 deletions(-) diff --git a/docs/concepts/flows.mdx b/docs/concepts/flows.mdx index 9ead26d70..324118310 100644 --- a/docs/concepts/flows.mdx +++ b/docs/concepts/flows.mdx @@ -18,63 +18,60 @@ Flows allow you to create structured, event-driven workflows. They provide a sea 4. **Flexible Control Flow**: Implement conditional logic, loops, and branching within your workflows. -5. **Input Flexibility**: Flows can accept inputs to initialize or update their state, with different handling for structured and unstructured state management. - ## Getting Started Let's create a simple Flow where you will use OpenAI to generate a random city in one task and then use that city to generate a fun fact in another task. -### Passing Inputs to Flows +```python Code -Flows can accept inputs to initialize or update their state before execution. The way inputs are handled depends on whether the flow uses structured or unstructured state management. - -#### Structured State Management - -In structured state management, the flow's state is defined using a Pydantic `BaseModel`. Inputs must match the model's schema, and any updates will overwrite the default values. - -```python from crewai.flow.flow import Flow, listen, start -from pydantic import BaseModel +from dotenv import load_dotenv +from litellm import completion -class ExampleState(BaseModel): - counter: int = 0 - message: str = "" -class StructuredExampleFlow(Flow[ExampleState]): +class ExampleFlow(Flow): + model = "gpt-4o-mini" + @start() - def first_method(self): - # Implementation + def generate_city(self): + print("Starting flow") -flow = StructuredExampleFlow() -flow.kickoff(inputs={"counter": 10}) -``` + response = completion( + model=self.model, + messages=[ + { + "role": "user", + "content": "Return the name of a random city in the world.", + }, + ], + ) -In this example, the `counter` is initialized to `10`, while `message` retains its default value. + random_city = response["choices"][0]["message"]["content"] + print(f"Random City: {random_city}") -#### Unstructured State Management + return random_city -In unstructured state management, the flow's state is a dictionary. You can pass any dictionary to update the state. + @listen(generate_city) + def generate_fun_fact(self, random_city): + response = completion( + model=self.model, + messages=[ + { + "role": "user", + "content": f"Tell me a fun fact about {random_city}", + }, + ], + ) -```python -from crewai.flow.flow import Flow, listen, start + fun_fact = response["choices"][0]["message"]["content"] + return fun_fact -class UnstructuredExampleFlow(Flow): - @start() - def first_method(self): - # Implementation -flow = UnstructuredExampleFlow() -flow.kickoff(inputs={"counter": 5, "message": "Initial message"}) -``` -Here, both `counter` and `message` are updated based on the provided inputs. +flow = ExampleFlow() +result = flow.kickoff() -**Note:** Ensure that inputs for structured state management adhere to the defined schema to avoid validation errors. - -### Example Flow - -```python -# Existing example code +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. @@ -97,14 +94,14 @@ The `@listen()` decorator can be used in several ways: 1. **Listening to a Method by Name**: You can pass the name of the method you want to listen to as a string. When that method completes, the listener method will be triggered. - ```python + ```python Code @listen("generate_city") def generate_fun_fact(self, random_city): # Implementation ``` 2. **Listening to a Method Directly**: You can pass the method itself. When that method completes, the listener method will be triggered. - ```python + ```python Code @listen(generate_city) def generate_fun_fact(self, random_city): # Implementation @@ -121,7 +118,7 @@ When you run a Flow, the final output is determined by the last method that comp Here's how you can access the final output: -```python +```python Code from crewai.flow.flow import Flow, listen, start class OutputExampleFlow(Flow): @@ -133,17 +130,18 @@ class OutputExampleFlow(Flow): def second_method(self, first_output): return f"Second method received: {first_output}" + flow = OutputExampleFlow() final_output = flow.kickoff() print("---- Final Output ----") print(final_output) -``` +```` -```text +``` text Output ---- Final Output ---- Second method received: Output from first_method -``` +```` @@ -158,7 +156,7 @@ Here's an example of how to update and access the state: -```python +```python Code from crewai.flow.flow import Flow, listen, start from pydantic import BaseModel @@ -186,7 +184,7 @@ print("Final State:") print(flow.state) ``` -```text +```text Output Final Output: Hello from first_method - updated by second_method Final State: counter=2 message='Hello from first_method - updated by second_method' @@ -210,10 +208,10 @@ allowing developers to choose the approach that best fits their application's ne In unstructured state management, all state is stored in the `state` attribute of the `Flow` class. This approach offers flexibility, enabling developers to add or modify state attributes on the fly without defining a strict schema. -```python +```python Code from crewai.flow.flow import Flow, listen, start -class UnstructuredExampleFlow(Flow): +class UntructuredExampleFlow(Flow): @start() def first_method(self): @@ -232,7 +230,8 @@ class UnstructuredExampleFlow(Flow): print(f"State after third_method: {self.state}") -flow = UnstructuredExampleFlow() + +flow = UntructuredExampleFlow() flow.kickoff() ``` @@ -246,14 +245,16 @@ flow.kickoff() Structured state management leverages predefined schemas to ensure consistency and type safety across the workflow. By using models like Pydantic's `BaseModel`, developers can define the exact shape of the state, enabling better validation and auto-completion in development environments. -```python +```python Code from crewai.flow.flow import Flow, listen, start from pydantic import BaseModel + class ExampleState(BaseModel): counter: int = 0 message: str = "" + class StructuredExampleFlow(Flow[ExampleState]): @start() @@ -272,6 +273,7 @@ class StructuredExampleFlow(Flow[ExampleState]): print(f"State after third_method: {self.state}") + flow = StructuredExampleFlow() flow.kickoff() ``` @@ -305,7 +307,7 @@ The `or_` function in Flows allows you to listen to multiple methods and trigger -```python +```python Code from crewai.flow.flow import Flow, listen, or_, start class OrExampleFlow(Flow): @@ -322,11 +324,13 @@ class OrExampleFlow(Flow): def logger(self, result): print(f"Logger: {result}") + + flow = OrExampleFlow() flow.kickoff() ``` -```text +```text Output Logger: Hello from the start method Logger: Hello from the second method ``` @@ -342,7 +346,7 @@ The `and_` function in Flows allows you to listen to multiple methods and trigge -```python +```python Code from crewai.flow.flow import Flow, and_, listen, start class AndExampleFlow(Flow): @@ -364,7 +368,7 @@ flow = AndExampleFlow() flow.kickoff() ``` -```text +```text Output ---- Logger ---- {'greeting': 'Hello from the start method', 'joke': 'What do computers eat? Microchips.'} ``` @@ -381,7 +385,7 @@ You can specify different routes based on the output of the method, allowing you -```python +```python Code import random from crewai.flow.flow import Flow, listen, router, start from pydantic import BaseModel @@ -412,11 +416,12 @@ class RouterFlow(Flow[ExampleState]): def fourth_method(self): print("Fourth method running") + flow = RouterFlow() flow.kickoff() ``` -```text +```text Output Starting the structured flow Third method running Fourth method running @@ -479,7 +484,7 @@ The `main.py` file is where you create your flow and connect the crews together. Here's an example of how you can connect the `poem_crew` in the `main.py` file: -```python +```python Code #!/usr/bin/env python from random import randint @@ -555,42 +560,6 @@ uv run kickoff The flow will execute, and you should see the output in the console. - -### Adding Additional Crews Using the CLI - -Once you have created your initial flow, you can easily add additional crews to your project using the CLI. This allows you to expand your flow's capabilities by integrating new crews without starting from scratch. - -To add a new crew to your existing flow, use the following command: - -```bash -crewai flow add-crew -``` - -This command will create a new directory for your crew within the `crews` folder of your flow project. It will include the necessary configuration files and a crew definition file, similar to the initial setup. - -#### Folder Structure - -After adding a new crew, your folder structure will look like this: - -| Directory/File | Description | -| :--------------------- | :----------------------------------------------------------------- | -| `name_of_flow/` | Root directory for the flow. | -| ├── `crews/` | Contains directories for specific crews. | -| │ ├── `poem_crew/` | Directory for the "poem_crew" with its configurations and scripts. | -| │ │ ├── `config/` | Configuration files directory for the "poem_crew". | -| │ │ │ ├── `agents.yaml` | YAML file defining the agents for "poem_crew". | -| │ │ │ └── `tasks.yaml` | YAML file defining the tasks for "poem_crew". | -| │ │ └── `poem_crew.py` | Script for "poem_crew" functionality. | -| └── `name_of_crew/` | Directory for the new crew. | -| ├── `config/` | Configuration files directory for the new crew. | -| │ ├── `agents.yaml` | YAML file defining the agents for the new crew. | -| │ └── `tasks.yaml` | YAML file defining the tasks for the new crew. | -| └── `name_of_crew.py` | Script for the new crew functionality. | - -You can then customize the `agents.yaml` and `tasks.yaml` files to define the agents and tasks for your new crew. The `name_of_crew.py` file will contain the crew's logic, which you can modify to suit your needs. - -By using the CLI to add additional crews, you can efficiently build complex AI workflows that leverage multiple crews working together. - ## Plot Flows Visualizing your AI workflows can provide valuable insights into the structure and execution paths of your flows. CrewAI offers a powerful visualization tool that allows you to generate interactive plots of your flows, making it easier to understand and optimize your AI workflows. @@ -607,7 +576,7 @@ CrewAI provides two convenient methods to generate plots of your flows: If you are working directly with a flow instance, you can generate a plot by calling the `plot()` method on your flow object. This method will create an HTML file containing the interactive plot of your flow. -```python +```python Code # Assuming you have a flow instance flow.plot("my_flow_plot") ``` @@ -630,114 +599,13 @@ The generated plot will display nodes representing the tasks in your flow, with By visualizing your flows, you can gain a clearer understanding of the workflow's structure, making it easier to debug, optimize, and communicate your AI processes to others. +### Conclusion -## Advanced - -In this section, we explore more complex use cases of CrewAI Flows, starting with a self-evaluation loop. This pattern is crucial for developing AI systems that can iteratively improve their outputs through feedback. - -### 1) Self-Evaluation Loop - -The self-evaluation loop is a powerful pattern that allows AI workflows to automatically assess and refine their outputs. This example demonstrates how to set up a flow that generates content, evaluates it, and iterates based on feedback until the desired quality is achieved. - -#### Overview - -The self-evaluation loop involves two main Crews: - -1. **ShakespeareanXPostCrew**: Generates a Shakespearean-style post on a given topic. -2. **XPostReviewCrew**: Evaluates the generated post, providing feedback on its validity and quality. - -The process iterates until the post meets the criteria or a maximum retry limit is reached. This approach ensures high-quality outputs through iterative refinement. - -#### Importance - -This pattern is essential for building robust AI systems that can adapt and improve over time. By automating the evaluation and feedback loop, developers can ensure that their AI workflows produce reliable and high-quality results. - -#### Main Code Highlights - -Below is the `main.py` file for the self-evaluation loop flow: - -```python -from typing import Optional -from crewai.flow.flow import Flow, listen, router, start -from pydantic import BaseModel -from self_evaluation_loop_flow.crews.shakespeare_crew.shakespeare_crew import ( - ShakespeareanXPostCrew, -) -from self_evaluation_loop_flow.crews.x_post_review_crew.x_post_review_crew import ( - XPostReviewCrew, -) - -class ShakespeareXPostFlowState(BaseModel): - x_post: str = "" - feedback: Optional[str] = None - valid: bool = False - retry_count: int = 0 - -class ShakespeareXPostFlow(Flow[ShakespeareXPostFlowState]): - - @start("retry") - def generate_shakespeare_x_post(self): - print("Generating Shakespearean X post") - topic = "Flying cars" - result = ( - ShakespeareanXPostCrew() - .crew() - .kickoff(inputs={"topic": topic, "feedback": self.state.feedback}) - ) - print("X post generated", result.raw) - self.state.x_post = result.raw - - @router(generate_shakespeare_x_post) - def evaluate_x_post(self): - if self.state.retry_count > 3: - return "max_retry_exceeded" - result = XPostReviewCrew().crew().kickoff(inputs={"x_post": self.state.x_post}) - self.state.valid = result["valid"] - self.state.feedback = result["feedback"] - print("valid", self.state.valid) - print("feedback", self.state.feedback) - self.state.retry_count += 1 - if self.state.valid: - return "complete" - return "retry" - - @listen("complete") - def save_result(self): - print("X post is valid") - print("X post:", self.state.x_post) - with open("x_post.txt", "w") as file: - file.write(self.state.x_post) - - @listen("max_retry_exceeded") - def max_retry_exceeded_exit(self): - print("Max retry count exceeded") - print("X post:", self.state.x_post) - print("Feedback:", self.state.feedback) - -def kickoff(): - shakespeare_flow = ShakespeareXPostFlow() - shakespeare_flow.kickoff() - -def plot(): - shakespeare_flow = ShakespeareXPostFlow() - shakespeare_flow.plot() - -if __name__ == "__main__": - kickoff() -``` - -#### Code Highlights - -- **Retry Mechanism**: The flow uses a retry mechanism to regenerate the post if it doesn't meet the criteria, up to a maximum of three retries. -- **Feedback Loop**: Feedback from the `XPostReviewCrew` is used to refine the post iteratively. -- **State Management**: The flow maintains state using a Pydantic model, ensuring type safety and clarity. - -For a complete example and further details, please refer to the [Self Evaluation Loop Flow repository](https://github.com/crewAIInc/crewAI-examples/tree/main/self_evaluation_loop_flow). - +Plotting your flows is a powerful feature of CrewAI that enhances your ability to design and manage complex AI workflows. Whether you choose to use the `plot()` method or the command line, generating plots will provide you with a visual representation of your workflows, aiding in both development and presentation. ## Next Steps -If you're interested in exploring additional examples of flows, we have a variety of recommendations in our examples repository. Here are five specific flow examples, each showcasing unique use cases to help you match your current problem type to a specific example: +If you're interested in exploring additional examples of flows, we have a variety of recommendations in our examples repository. Here are four specific flow examples, each showcasing unique use cases to help you match your current problem type to a specific example: 1. **Email Auto Responder Flow**: This example demonstrates an infinite loop where a background job continually runs to automate email responses. It's a great use case for tasks that need to be performed repeatedly without manual intervention. [View Example](https://github.com/crewAIInc/crewAI-examples/tree/main/email_auto_responder_flow) @@ -747,8 +615,6 @@ If you're interested in exploring additional examples of flows, we have a variet 4. **Meeting Assistant Flow**: This flow demonstrates how to broadcast one event to trigger multiple follow-up actions. For instance, after a meeting is completed, the flow can update a Trello board, send a Slack message, and save the results. It's a great example of handling multiple outcomes from a single event, making it ideal for comprehensive task management and notification systems. [View Example](https://github.com/crewAIInc/crewAI-examples/tree/main/meeting_assistant_flow) -5. **Self Evaluation Loop Flow**: This flow demonstrates a self-evaluation loop where AI workflows automatically assess and refine their outputs through feedback. It involves generating content, evaluating it, and iterating until the desired quality is achieved. This pattern is crucial for developing robust AI systems that can adapt and improve over time. [View Example](https://github.com/crewAIInc/crewAI-examples/tree/main/self_evaluation_loop_flow) - By exploring these examples, you can gain insights into how to leverage CrewAI Flows for various use cases, from automating repetitive tasks to managing complex, multi-step processes with dynamic decision-making and human feedback. Also, check out our YouTube video on how to use flows in CrewAI below! @@ -762,4 +628,4 @@ Also, check out our YouTube video on how to use flows in CrewAI below! allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" referrerpolicy="strict-origin-when-cross-origin" allowfullscreen -> +> \ No newline at end of file From 308a8dc925ecc719c5f3fabaa05b301b0e600a45 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Javier=20Salda=C3=B1a?= Date: Tue, 3 Dec 2024 10:09:30 -0500 Subject: [PATCH 091/126] Update reset memories command based on the SDK (#1688) Co-authored-by: Brandon Hancock (bhancock_ai) <109994880+bhancockio@users.noreply.github.com> --- docs/quickstart.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/quickstart.mdx b/docs/quickstart.mdx index a78a56182..9ec3170e1 100644 --- a/docs/quickstart.mdx +++ b/docs/quickstart.mdx @@ -349,7 +349,7 @@ Replace `` with the ID of the task you want to replay. If you need to reset the memory of your crew before running it again, you can do so by calling the reset memory feature: ```shell -crewai reset-memory +crewai reset-memories --all ``` This will clear the crew's memory, allowing for a fresh start. From 9e9b945a4662e6cea9f6ca621cac3224a3119622 Mon Sep 17 00:00:00 2001 From: Ola Hungerford Date: Tue, 3 Dec 2024 08:13:06 -0800 Subject: [PATCH 092/126] Update using langchain tools docs (#1664) * Update example of how to use LangChain tools with correct syntax * Use .env * Add Code back --------- Co-authored-by: Brandon Hancock (bhancock_ai) <109994880+bhancockio@users.noreply.github.com> --- docs/concepts/langchain-tools.mdx | 53 +++++++++++++++++++------------ 1 file changed, 33 insertions(+), 20 deletions(-) diff --git a/docs/concepts/langchain-tools.mdx b/docs/concepts/langchain-tools.mdx index 538581aee..68a7998a9 100644 --- a/docs/concepts/langchain-tools.mdx +++ b/docs/concepts/langchain-tools.mdx @@ -7,32 +7,45 @@ icon: link ## Using LangChain Tools - CrewAI seamlessly integrates with LangChain’s comprehensive [list of tools](https://python.langchain.com/docs/integrations/tools/), all of which can be used with CrewAI. + CrewAI seamlessly integrates with LangChain's comprehensive [list of tools](https://python.langchain.com/docs/integrations/tools/), all of which can be used with CrewAI. ```python Code import os -from crewai import Agent -from langchain.agents import Tool -from langchain.utilities import GoogleSerperAPIWrapper +from dotenv import load_dotenv +from crewai import Agent, Task, Crew +from crewai.tools import BaseTool +from pydantic import Field +from langchain_community.utilities import GoogleSerperAPIWrapper -# Setup API keys -os.environ["SERPER_API_KEY"] = "Your Key" +# Set up your SERPER_API_KEY key in an .env file, eg: +# SERPER_API_KEY= +load_dotenv() search = GoogleSerperAPIWrapper() -# Create and assign the search tool to an agent -serper_tool = Tool( - name="Intermediate Answer", - func=search.run, - description="Useful for search-based queries", -) +class SearchTool(BaseTool): + name: str = "Search" + description: str = "Useful for search-based queries. Use this to find current information about markets, companies, and trends." + search: GoogleSerperAPIWrapper = Field(default_factory=GoogleSerperAPIWrapper) -agent = Agent( - role='Research Analyst', - goal='Provide up-to-date market analysis', - backstory='An expert analyst with a keen eye for market trends.', - tools=[serper_tool] + def _run(self, query: str) -> str: + """Execute the search query and return results""" + try: + return self.search.run(query) + except Exception as e: + return f"Error performing search: {str(e)}" + +# Create Agents +researcher = Agent( + role='Research Analyst', + goal='Gather current market data and trends', + backstory="""You are an expert research analyst with years of experience in + gathering market intelligence. You're known for your ability to find + relevant and up-to-date market information and present it in a clear, + actionable format.""", + tools=[SearchTool()], + verbose=True ) # rest of the code ... @@ -40,6 +53,6 @@ agent = Agent( ## Conclusion -Tools are pivotal in extending the capabilities of CrewAI agents, enabling them to undertake a broad spectrum of tasks and collaborate effectively. -When building solutions with CrewAI, leverage both custom and existing tools to empower your agents and enhance the AI ecosystem. Consider utilizing error handling, caching mechanisms, -and the flexibility of tool arguments to optimize your agents' performance and capabilities. \ No newline at end of file +Tools are pivotal in extending the capabilities of CrewAI agents, enabling them to undertake a broad spectrum of tasks and collaborate effectively. +When building solutions with CrewAI, leverage both custom and existing tools to empower your agents and enhance the AI ecosystem. Consider utilizing error handling, caching mechanisms, +and the flexibility of tool arguments to optimize your agents' performance and capabilities. From aaf80d1d43a02a9b135743b3e67c2d4cccd16d23 Mon Sep 17 00:00:00 2001 From: "Tom Mahler, PhD" Date: Tue, 3 Dec 2024 19:22:29 +0200 Subject: [PATCH 093/126] [FEATURE] Support for custom path in RAGStorage (#1659) * added path to RAGStorage * added path to short term and entity memory * add path for long_term_storage for completeness --------- Co-authored-by: Brandon Hancock (bhancock_ai) <109994880+bhancockio@users.noreply.github.com> --- src/crewai/memory/entity/entity_memory.py | 3 ++- src/crewai/memory/long_term/long_term_memory.py | 5 +++-- src/crewai/memory/short_term/short_term_memory.py | 4 ++-- src/crewai/memory/storage/rag_storage.py | 5 +++-- 4 files changed, 10 insertions(+), 7 deletions(-) diff --git a/src/crewai/memory/entity/entity_memory.py b/src/crewai/memory/entity/entity_memory.py index 88d33c09a..67c72e927 100644 --- a/src/crewai/memory/entity/entity_memory.py +++ b/src/crewai/memory/entity/entity_memory.py @@ -10,7 +10,7 @@ class EntityMemory(Memory): Inherits from the Memory class. """ - def __init__(self, crew=None, embedder_config=None, storage=None): + def __init__(self, crew=None, embedder_config=None, storage=None, path=None): if hasattr(crew, "memory_config") and crew.memory_config is not None: self.memory_provider = crew.memory_config.get("provider") else: @@ -33,6 +33,7 @@ class EntityMemory(Memory): allow_reset=True, embedder_config=embedder_config, crew=crew, + path=path, ) ) super().__init__(storage) diff --git a/src/crewai/memory/long_term/long_term_memory.py b/src/crewai/memory/long_term/long_term_memory.py index b9c36bdc9..656709ac9 100644 --- a/src/crewai/memory/long_term/long_term_memory.py +++ b/src/crewai/memory/long_term/long_term_memory.py @@ -14,8 +14,9 @@ class LongTermMemory(Memory): LongTermMemoryItem instances. """ - def __init__(self, storage=None): - storage = storage if storage else LTMSQLiteStorage() + def __init__(self, storage=None, path=None): + if not storage: + storage = LTMSQLiteStorage(db_path=path) if path else LTMSQLiteStorage() super().__init__(storage) def save(self, item: LongTermMemoryItem) -> None: # type: ignore # BUG?: Signature of "save" incompatible with supertype "Memory" diff --git a/src/crewai/memory/short_term/short_term_memory.py b/src/crewai/memory/short_term/short_term_memory.py index 67a568d63..4ade7eb93 100644 --- a/src/crewai/memory/short_term/short_term_memory.py +++ b/src/crewai/memory/short_term/short_term_memory.py @@ -13,7 +13,7 @@ class ShortTermMemory(Memory): MemoryItem instances. """ - def __init__(self, crew=None, embedder_config=None, storage=None): + def __init__(self, crew=None, embedder_config=None, storage=None, path=None): if hasattr(crew, "memory_config") and crew.memory_config is not None: self.memory_provider = crew.memory_config.get("provider") else: @@ -32,7 +32,7 @@ class ShortTermMemory(Memory): storage if storage else RAGStorage( - type="short_term", embedder_config=embedder_config, crew=crew + type="short_term", embedder_config=embedder_config, crew=crew, path=path ) ) super().__init__(storage) diff --git a/src/crewai/memory/storage/rag_storage.py b/src/crewai/memory/storage/rag_storage.py index 4023cf558..ded340a19 100644 --- a/src/crewai/memory/storage/rag_storage.py +++ b/src/crewai/memory/storage/rag_storage.py @@ -37,7 +37,7 @@ class RAGStorage(BaseRAGStorage): app: ClientAPI | None = None - def __init__(self, type, allow_reset=True, embedder_config=None, crew=None): + def __init__(self, type, allow_reset=True, embedder_config=None, crew=None, path=None): super().__init__(type, allow_reset, embedder_config, crew) agents = crew.agents if crew else [] agents = [self._sanitize_role(agent.role) for agent in agents] @@ -47,6 +47,7 @@ class RAGStorage(BaseRAGStorage): self.type = type self.allow_reset = allow_reset + self.path = path self._initialize_app() def _set_embedder_config(self): @@ -59,7 +60,7 @@ class RAGStorage(BaseRAGStorage): self._set_embedder_config() chroma_client = chromadb.PersistentClient( - path=f"{db_storage_path()}/{self.type}/{self.agents}", + path=self.path if self.path else f"{db_storage_path()}/{self.type}/{self.agents}", settings=Settings(allow_reset=self.allow_reset), ) From 77af733e448ce25c6d539c36e731e7668336370d Mon Sep 17 00:00:00 2001 From: Patcher Date: Tue, 3 Dec 2024 12:38:49 -0500 Subject: [PATCH 094/126] [Doc]: Add documenation for openlit observability (#1612) * Create openlit-observability.mdx * Update doc with images and steps * Update mkdocs.yml and add OpenLIT guide link --------- Co-authored-by: Brandon Hancock (bhancock_ai) <109994880+bhancockio@users.noreply.github.com> --- docs/how-to/openlit-observability.mdx | 181 ++++++++++++++++++++++++++ docs/images/openlit1.png | Bin 0 -> 399532 bytes docs/images/openlit2.png | Bin 0 -> 432643 bytes docs/images/openlit3.png | Bin 0 -> 818085 bytes docs/mint.json | 3 +- mkdocs.yml | 1 + 6 files changed, 184 insertions(+), 1 deletion(-) create mode 100644 docs/how-to/openlit-observability.mdx create mode 100644 docs/images/openlit1.png create mode 100644 docs/images/openlit2.png create mode 100644 docs/images/openlit3.png diff --git a/docs/how-to/openlit-observability.mdx b/docs/how-to/openlit-observability.mdx new file mode 100644 index 000000000..e95989e8e --- /dev/null +++ b/docs/how-to/openlit-observability.mdx @@ -0,0 +1,181 @@ +--- +title: Agent Monitoring with OpenLIT +description: Quickly start monitoring your Agents in just a single line of code with OpenTelemetry. +icon: magnifying-glass-chart +--- + +# OpenLIT Overview + +[OpenLIT](https://github.com/openlit/openlit?src=crewai-docs) is an open-source tool that makes it simple to monitor the performance of AI agents, LLMs, VectorDBs, and GPUs with just **one** line of code. + +It provides OpenTelemetry-native tracing and metrics to track important parameters like cost, latency, interactions and task sequences. +This setup enables you to track hyperparameters and monitor for performance issues, helping you find ways to enhance and fine-tune your agents over time. + + + Overview Agent usage including cost and tokens + Overview of agent otel traces and metrics + Overview of agent traces in details + + +### Features + +- **Analytics Dashboard**: Monitor your Agents health and performance with detailed dashboards that track metrics, costs, and user interactions. +- **OpenTelemetry-native Observability SDK**: Vendor-neutral SDKs to send traces and metrics to your existing observability tools like Grafana, DataDog and more. +- **Cost Tracking for Custom and Fine-Tuned Models**: Tailor cost estimations for specific models using custom pricing files for precise budgeting. +- **Exceptions Monitoring Dashboard**: Quickly spot and resolve issues by tracking common exceptions and errors with a monitoring dashboard. +- **Compliance and Security**: Detect potential threats such as profanity and PII leaks. +- **Prompt Injection Detection**: Identify potential code injection and secret leaks. +- **API Keys and Secrets Management**: Securely handle your LLM API keys and secrets centrally, avoiding insecure practices. +- **Prompt Management**: Manage and version Agent prompts using PromptHub for consistent and easy access across Agents. +- **Model Playground** Test and compare different models for your CrewAI agents before deployment. + +## Setup Instructions + + + + + + ```shell + git clone git@github.com:openlit/openlit.git + ``` + + + From the root directory of the [OpenLIT Repo](https://github.com/openlit/openlit), Run the below command: + ```shell + docker compose up -d + ``` + + + + + ```shell + pip install openlit + ``` + + + Add the following two lines to your application code: + + + ```python + import openlit + openlit.init(otlp_endpoint="http://127.0.0.1:4318") + ``` + + Example Usage for monitoring a CrewAI Agent: + + ```python + from crewai import Agent, Task, Crew, Process + import openlit + + openlit.init(disable_metrics=True) + # Define your agents + researcher = Agent( + role="Researcher", + goal="Conduct thorough research and analysis on AI and AI agents", + backstory="You're an expert researcher, specialized in technology, software engineering, AI, and startups. You work as a freelancer and are currently researching for a new client.", + allow_delegation=False, + llm='command-r' + ) + + + # Define your task + task = Task( + description="Generate a list of 5 interesting ideas for an article, then write one captivating paragraph for each idea that showcases the potential of a full article on this topic. Return the list of ideas with their paragraphs and your notes.", + expected_output="5 bullet points, each with a paragraph and accompanying notes.", + ) + + # Define the manager agent + manager = Agent( + role="Project Manager", + goal="Efficiently manage the crew and ensure high-quality task completion", + backstory="You're an experienced project manager, skilled in overseeing complex projects and guiding teams to success. Your role is to coordinate the efforts of the crew members, ensuring that each task is completed on time and to the highest standard.", + allow_delegation=True, + llm='command-r' + ) + + # Instantiate your crew with a custom manager + crew = Crew( + agents=[researcher], + tasks=[task], + manager_agent=manager, + process=Process.hierarchical, + ) + + # Start the crew's work + result = crew.kickoff() + + print(result) + ``` + + + + Add the following two lines to your application code: + ```python + import openlit + + openlit.init() + ``` + + Run the following command to configure the OTEL export endpoint: + ```shell + export OTEL_EXPORTER_OTLP_ENDPOINT = "http://127.0.0.1:4318" + ``` + + Example Usage for monitoring a CrewAI Async Agent: + + ```python + import asyncio + from crewai import Crew, Agent, Task + import openlit + + openlit.init(otlp_endpoint="http://127.0.0.1:4318") + + # Create an agent with code execution enabled + coding_agent = Agent( + role="Python Data Analyst", + goal="Analyze data and provide insights using Python", + backstory="You are an experienced data analyst with strong Python skills.", + allow_code_execution=True, + llm="command-r" + ) + + # Create a task that requires code execution + data_analysis_task = Task( + description="Analyze the given dataset and calculate the average age of participants. Ages: {ages}", + agent=coding_agent, + expected_output="5 bullet points, each with a paragraph and accompanying notes.", + ) + + # Create a crew and add the task + analysis_crew = Crew( + agents=[coding_agent], + tasks=[data_analysis_task] + ) + + # Async function to kickoff the crew asynchronously + async def async_crew_execution(): + result = await analysis_crew.kickoff_async(inputs={"ages": [25, 30, 35, 40, 45]}) + print("Crew Result:", result) + + # Run the async function + asyncio.run(async_crew_execution()) + ``` + + + Refer to OpenLIT [Python SDK repository](https://github.com/openlit/openlit/tree/main/sdk/python) for more advanced configurations and use cases. + + + With the Agent Observability data now being collected and sent to OpenLIT, the next step is to visualize and analyze this data to get insights into your Agent's performance, behavior, and identify areas of improvement. + + Just head over to OpenLIT at `127.0.0.1:3000` on your browser to start exploring. You can login using the default credentials + - **Email**: `user@openlit.io` + - **Password**: `openlituser` + + + Overview Agent usage including cost and tokens + Overview of agent otel traces and metrics + + + + + diff --git a/docs/images/openlit1.png b/docs/images/openlit1.png new file mode 100644 index 0000000000000000000000000000000000000000..4fab0340d974c5501dd14c32e57d927320296bc4 GIT binary patch literal 399532 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%0D+^Y`YK- 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

3^ispH5(oP9Rxkk+hT;b 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�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 @Y4I@*nz_2(`^Z$JLf60`E8`?0x81>x;y$I80 z&aC0WYC!Fi+ax2bz7I9U6oCC-SOJMg&=|yqCYJ3`Iaz^g8~Ug26nyt@9J*UMtciLIJFdJw)dxD=Q`T!(|-aXerACc4o+y=jR5xew(1^<`d zG3vZJY#|}@-NevUb>?U2l8v$=(J3guL5esX&qZ+f@jp~>gpuW+D`^}7hoQv#?`LmXIxD?ZU_l~{mzAf z13BGepmw+@9Md=e5)Ef1Xg7ZXo_kuhb!amW$Vk_}Cq^~___r<&&y-dt;*`@tJ#~?n z)}C1~SyhUVT{|RfB@hyt^nHhPVOIu%+?8m4*(KII#=SquEx*D}&C)gy-{KrB`|N*i zv)D^}j@Ull&Ny}QlT{#|73R3wK`Ie<=b6UR1}<gxM`@FO##!uarxa>HW%bd^O4B=tY&H!M1U75}w5M|>vtv3S9`@2(063he8*|#4+7*MbaHfrPD7?f+T2hw23`P)MU z3)S8Q^#tkzCRQD;k<;WouM~vU3zf1=I7srxL8DQWW}ZZHj*Vk;Xq|ji0>VwKDhD@r zVi6X;5cR5|05oag@qxVOD!>32!#z^dA=Ni=tO^9+&(GWhJ=~K`D;rGM&z^i(2V%se zmrJDweXdD`X;43$;Gr@n65w1#dK@BvoiD+KQx4q)(4yWhhW9Z~RNr-tDT|v1pfjA@ zSbhSuTHF>rD7kaQFL0SV%X(-Nv~?N~b(5wisOocd4<;0viS(Mt1jvL>+H%e6$28ldLxvgS#htzx-{x9>B_0r zD{Fe;@(PfC9ahc&WG~fbOa_n*&#@w#_iLC z5apP_!HJ}EcP9l5`fzoHWP)`wYU3aA9-mkUE)3R%CqN|8tjaBywCtdPk_~*MkYdH{!T&Y5e=D1oAG=!tNusU$gn%ePo)a4f zn2P77Vxg}$ftH4emE}@#M;mxXkJw!(C|={FIvlYZ;u;;_Y)mv!O$$$Beq1TsVA6di z47yL4(t;D0aUTnh;fbQ>3?Sndirr8(#1aI?6Mc0hk02;K-N6klGtD#BpUD$Ib%r_l z{?DWCXUgT*w8S{^QzTZB^uIMDFcb6FhTQ*t`2aD?gAOXUwze8f1cTy&Oykx%Wsq;{ zyYfOD;hpcep)pORY!*T^C}gyQS!PHZcLEr*cY^hJaNu8bv`|Y5#IAziRk>+kTrFO! zYYFv_xvr}D4MmKH`qKNrGwr+Wb)I*#Km(9M$Z79708N~zZQ=4f_9}>PXuc(nTF4GJ zrHy*tfB+NOEC}7HoSTmX6QuZ{?K~D3wvXoLeS|M?4KS%SYdQU?#qDtG@kL!(imy49 zrkE6&XZ6%)YZL1|x@iz&C!XWBD9=%!X+k*71>9Tzq3awZrC3gjaFE)-TRlMZf9LefMH$Qp{U66 z=Av9w8YGqnEu@;(D0teCD_sQS$(jznCwVt-(BhZQwoJ04}GD!y!i6`J4-vhLxpDp5GXFYTWx?AxI9lL3vST zUO>$sdU(X5WOg4Q6c@`c7JpS5aO#B5#bEC5%F8AHnOMdZepy7yQdubvaEnW_=Vs`J zVgN?#PX4|dav{x%Tt3*d&sGnVVU^Q{geWdWo{GMk`5C|6aE20gyMqha6!W$XUkFre zx&{s<&2AM}g>-e*c7&*t^T<#<8{^CCi3h>y}6(UiFsr?}OL7O*yyv=)r_|B*l`N+RG;3lIAL zGKwvm%{c(I)WT3Qa8OkV>|91eCQ$v-1I03H?)%i#?`rd0^MjA z@Dhz=EW(_5h|F3>u2Dx~zES!?gF#gh#j~24K;W*$6&ocXIVgfjzXaJ-FyOq%34=(x zV)%oV4&;Kf8TFt!=Ld?G0wrfD9#20=-jHZOu-fgDay@tMRL#Ca-Cdwif2;w_gK)nc z_)2Ia>g%J1ky{nRc;G-W@w-hB%&w}a*}!lfV}%{)?2oYbx<@+~j74>D50cH2%aUlb@lyjQg`R)(c%6

Ukfn}*%s{kp;X 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 pk+Hk<9^uzzv;hZ1i;XK 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