From 94eaa6740e95040b04feb350b5838c0313d68e08 Mon Sep 17 00:00:00 2001 From: Karan Vaidya Date: Tue, 17 Dec 2024 02:53:53 +0530 Subject: [PATCH 01/18] Fix bool and null handling (#1771) --- src/crewai/tools/tool_usage.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/crewai/tools/tool_usage.py b/src/crewai/tools/tool_usage.py index 15227542d9..3de4a8fab5 100644 --- a/src/crewai/tools/tool_usage.py +++ b/src/crewai/tools/tool_usage.py @@ -419,9 +419,10 @@ def _validate_tool_input(self, tool_input: str) -> str: elif value.lower() in [ "true", "false", - "null", ]: # Check for boolean and null values - value = value.lower() + value = value.lower().capitalize() + elif value.lower() == "null": + value = "None" else: # Assume the value is a string and needs quotes value = '"' + value.replace('"', '\\"') + '"' From bf459bf98399282cb4173bd68f90c7c14f84a6ad Mon Sep 17 00:00:00 2001 From: Brandon Hancock Date: Tue, 17 Dec 2024 15:29:11 -0500 Subject: [PATCH 02/18] include 12 but not 13 --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 5dd4e64b61..27a4785b42 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -3,7 +3,7 @@ name = "crewai" version = "0.86.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.12" +requires-python = ">=3.10,<3.13" authors = [ { name = "Joao Moura", email = "joao@crewai.com" } ] From ee239b1c068cdfdb125ee2efa735a4514175b0bb Mon Sep 17 00:00:00 2001 From: Brandon Hancock Date: Tue, 17 Dec 2024 16:00:15 -0500 Subject: [PATCH 03/18] change to <13 instead of <=12 --- README.md | 2 +- docs/installation.mdx | 2 +- src/crewai/cli/templates/crew/README.md | 2 +- src/crewai/cli/templates/crew/pyproject.toml | 2 +- src/crewai/cli/templates/flow/README.md | 2 +- src/crewai/cli/templates/flow/pyproject.toml | 2 +- src/crewai/cli/templates/tool/README.md | 2 +- src/crewai/cli/templates/tool/pyproject.toml | 2 +- tests/cli/deploy/test_deploy_main.py | 4 ++-- uv.lock | 5 +++-- 10 files changed, 13 insertions(+), 12 deletions(-) diff --git a/README.md b/README.md index b6d950a3b6..5669c71a27 100644 --- a/README.md +++ b/README.md @@ -44,7 +44,7 @@ To get started with CrewAI, follow these simple steps: ### 1. Installation -Ensure you have Python >=3.10 <=3.12 installed on your system. CrewAI uses [UV](https://docs.astral.sh/uv/) for dependency management and package handling, offering a seamless setup and execution experience. +Ensure you have Python >=3.10 <3.13 installed on your system. CrewAI uses [UV](https://docs.astral.sh/uv/) for dependency management and package handling, offering a seamless setup and execution experience. First, install CrewAI: diff --git a/docs/installation.mdx b/docs/installation.mdx index ca330e9ca8..c98bce9ef2 100644 --- a/docs/installation.mdx +++ b/docs/installation.mdx @@ -7,7 +7,7 @@ icon: wrench **Python Version Requirements** - CrewAI requires `Python >=3.10 and <=3.12`. Here's how to check your version: + CrewAI requires `Python >=3.10 and <3.13`. Here's how to check your version: ```bash python3 --version ``` diff --git a/src/crewai/cli/templates/crew/README.md b/src/crewai/cli/templates/crew/README.md index ca3cbd3eb0..327499d0af 100644 --- a/src/crewai/cli/templates/crew/README.md +++ b/src/crewai/cli/templates/crew/README.md @@ -4,7 +4,7 @@ Welcome to the {{crew_name}} Crew project, powered by [crewAI](https://crewai.co ## Installation -Ensure you have Python >=3.10 <=3.12 installed on your system. This project uses [UV](https://docs.astral.sh/uv/) for dependency management and package handling, offering a seamless setup and execution experience. +Ensure you have Python >=3.10 <3.13 installed on your system. This project uses [UV](https://docs.astral.sh/uv/) for dependency management and package handling, offering a seamless setup and execution experience. First, if you haven't already, install uv: diff --git a/src/crewai/cli/templates/crew/pyproject.toml b/src/crewai/cli/templates/crew/pyproject.toml index 29014881bf..5c306e62d5 100644 --- a/src/crewai/cli/templates/crew/pyproject.toml +++ b/src/crewai/cli/templates/crew/pyproject.toml @@ -3,7 +3,7 @@ name = "{{folder_name}}" version = "0.1.0" description = "{{name}} using crewAI" authors = [{ name = "Your Name", email = "you@example.com" }] -requires-python = ">=3.10,<=3.12" +requires-python = ">=3.10,<3.13" dependencies = [ "crewai[tools]>=0.86.0,<1.0.0" ] diff --git a/src/crewai/cli/templates/flow/README.md b/src/crewai/cli/templates/flow/README.md index fead65ece7..b6ce2da711 100644 --- a/src/crewai/cli/templates/flow/README.md +++ b/src/crewai/cli/templates/flow/README.md @@ -4,7 +4,7 @@ Welcome to the {{crew_name}} Crew project, powered by [crewAI](https://crewai.co ## Installation -Ensure you have Python >=3.10 <=3.12 installed on your system. This project uses [UV](https://docs.astral.sh/uv/) for dependency management and package handling, offering a seamless setup and execution experience. +Ensure you have Python >=3.10 <3.13 installed on your system. This project uses [UV](https://docs.astral.sh/uv/) for dependency management and package handling, offering a seamless setup and execution experience. First, if you haven't already, install uv: diff --git a/src/crewai/cli/templates/flow/pyproject.toml b/src/crewai/cli/templates/flow/pyproject.toml index a20653eff5..e55744ff34 100644 --- a/src/crewai/cli/templates/flow/pyproject.toml +++ b/src/crewai/cli/templates/flow/pyproject.toml @@ -3,7 +3,7 @@ name = "{{folder_name}}" version = "0.1.0" description = "{{name}} using crewAI" authors = [{ name = "Your Name", email = "you@example.com" }] -requires-python = ">=3.10,<=3.12" +requires-python = ">=3.10,<3.13" dependencies = [ "crewai[tools]>=0.86.0,<1.0.0", ] diff --git a/src/crewai/cli/templates/tool/README.md b/src/crewai/cli/templates/tool/README.md index 1291ff6a69..d332fa197c 100644 --- a/src/crewai/cli/templates/tool/README.md +++ b/src/crewai/cli/templates/tool/README.md @@ -5,7 +5,7 @@ custom tools to power up your crews. ## Installing -Ensure you have Python >=3.10 <=3.12 installed on your system. This project +Ensure you have Python >=3.10 <3.13 installed on your system. This project uses [UV](https://docs.astral.sh/uv/) for dependency management and package handling, offering a seamless setup and execution experience. diff --git a/src/crewai/cli/templates/tool/pyproject.toml b/src/crewai/cli/templates/tool/pyproject.toml index a1557bd7f7..e6e08a3894 100644 --- a/src/crewai/cli/templates/tool/pyproject.toml +++ b/src/crewai/cli/templates/tool/pyproject.toml @@ -3,7 +3,7 @@ name = "{{folder_name}}" version = "0.1.0" description = "Power up your crews with {{folder_name}}" readme = "README.md" -requires-python = ">=3.10,<=3.12" +requires-python = ">=3.10,<3.13" dependencies = [ "crewai[tools]>=0.86.0" ] diff --git a/tests/cli/deploy/test_deploy_main.py b/tests/cli/deploy/test_deploy_main.py index 00e355a62b..ca89b2aa2d 100644 --- a/tests/cli/deploy/test_deploy_main.py +++ b/tests/cli/deploy/test_deploy_main.py @@ -231,7 +231,7 @@ def test_parse_toml_python_311_plus(self): [project] name = "test_project" version = "0.1.0" - requires-python = ">=3.10,<=3.12" + requires-python = ">=3.10,<3.13" dependencies = ["crewai"] """, ) @@ -250,7 +250,7 @@ def test_get_project_name_python_310(self, mock_open): [project] name = "test_project" version = "0.1.0" - requires-python = ">=3.10,<=3.12" + requires-python = ">=3.10,<3.13" dependencies = ["crewai"] """, ) diff --git a/uv.lock b/uv.lock index f6f0750f6b..5cf51b18c7 100644 --- a/uv.lock +++ b/uv.lock @@ -1,9 +1,10 @@ version = 1 -requires-python = ">=3.10, <=3.12" +requires-python = ">=3.10, <3.13" resolution-markers = [ "python_full_version < '3.11'", "python_full_version == '3.11.*'", - "python_full_version >= '3.12'", + "python_full_version >= '3.12' and python_full_version < '3.12.4'", + "python_full_version >= '3.12.4'", ] [[package]] From 1b8001bf986f5b78e9dc1a4513550518cca59e0a Mon Sep 17 00:00:00 2001 From: alan blount Date: Tue, 17 Dec 2024 16:44:10 -0500 Subject: [PATCH 04/18] Gemini 2.0 (#1773) * Update llms.mdx (Gemini 2.0) - Add Gemini 2.0 flash to Gemini table. - Add link to 2 hosting paths for Gemini in Tip. - Change to lower case model slugs vs names, user convenience. - Add https://artificialanalysis.ai/ as alternate leaderboard. - Move Gemma to "other" tab. * Update llm.py (gemini 2.0) Add setting for Gemini 2.0 context window to llm.py --------- Co-authored-by: Brandon Hancock (bhancock_ai) <109994880+bhancockio@users.noreply.github.com> --- docs/concepts/llms.mdx | 17 +++++++++++------ src/crewai/llm.py | 1 + 2 files changed, 12 insertions(+), 6 deletions(-) diff --git a/docs/concepts/llms.mdx b/docs/concepts/llms.mdx index 915ce141c1..4e269d71ad 100644 --- a/docs/concepts/llms.mdx +++ b/docs/concepts/llms.mdx @@ -29,7 +29,7 @@ Large Language Models (LLMs) are the core intelligence behind CrewAI agents. The ## Available Models and Their Capabilities -Here's a detailed breakdown of supported models and their capabilities, you can compare performance at [lmarena.ai](https://lmarena.ai/): +Here's a detailed breakdown of supported models and their capabilities, you can compare performance at [lmarena.ai](https://lmarena.ai/?leaderboard) and [artificialanalysis.ai](https://artificialanalysis.ai/): @@ -121,12 +121,18 @@ Here's a detailed breakdown of supported models and their capabilities, you can | Model | Context Window | Best For | |-------|---------------|-----------| - | Gemini 1.5 Flash | 1M tokens | Balanced multimodal model, good for most tasks | - | Gemini 1.5 Flash 8B | 1M tokens | Fastest, most cost-efficient, good for high-frequency tasks | - | Gemini 1.5 Pro | 2M tokens | Best performing, wide variety of reasoning tasks including logical reasoning, coding, and creative collaboration | + | gemini-2.0-flash-exp | 1M tokens | Higher quality at faster speed, multimodal model, good for most tasks | + | gemini-1.5-flash | 1M tokens | Balanced multimodal model, good for most tasks | + | gemini-1.5-flash-8B | 1M tokens | Fastest, most cost-efficient, good for high-frequency tasks | + | gemini-1.5-pro | 2M tokens | Best performing, wide variety of reasoning tasks including logical reasoning, coding, and creative collaboration | Google's Gemini models are all multimodal, supporting audio, images, video and text, supporting context caching, json schema, function calling, etc. + + These models are available via API_KEY from + [The Gemini API](https://ai.google.dev/gemini-api/docs) and also from + [Google Cloud Vertex](https://cloud.google.com/vertex-ai/generative-ai/docs/migrate/migrate-google-ai) as part of the + [Model Garden](https://cloud.google.com/vertex-ai/generative-ai/docs/model-garden/explore-models). @@ -135,7 +141,6 @@ Here's a detailed breakdown of supported models and their capabilities, you can | 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 | Groq is known for its fast inference speeds, making it suitable for real-time applications. @@ -146,7 +151,7 @@ Here's a detailed breakdown of supported models and their capabilities, you can |----------|---------------|--------------| | 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 | + | Gemma Series | 8,192 tokens | Efficient, smaller-scale tasks | Provider selection should consider factors like: diff --git a/src/crewai/llm.py b/src/crewai/llm.py index 058589056e..1b0ac9b0a1 100644 --- a/src/crewai/llm.py +++ b/src/crewai/llm.py @@ -44,6 +44,7 @@ def flush(self): "o1-preview": 128000, "o1-mini": 128000, # gemini + "gemini-2.0-flash": 1048576, "gemini-1.5-pro": 2097152, "gemini-1.5-flash": 1048576, "gemini-1.5-flash-8b": 1048576, From 627b9f1abb3de753d8b2e097c3baaf1d90d473d3 Mon Sep 17 00:00:00 2001 From: Vini Brasil Date: Wed, 18 Dec 2024 10:47:44 -0300 Subject: [PATCH 05/18] Remove relative import in flow `main.py` template (#1782) --- src/crewai/cli/templates/flow/main.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/crewai/cli/templates/flow/main.py b/src/crewai/cli/templates/flow/main.py index 73684051ac..83f48ddd69 100644 --- a/src/crewai/cli/templates/flow/main.py +++ b/src/crewai/cli/templates/flow/main.py @@ -5,7 +5,7 @@ from crewai.flow.flow import Flow, listen, start -from .crews.poem_crew.poem_crew import PoemCrew +from {{folder_name}}.crews.poem_crew.poem_crew import PoemCrew class PoemState(BaseModel): From da73865f258b0e28192ff252baf028dfa69872af Mon Sep 17 00:00:00 2001 From: Vini Brasil Date: Fri, 20 Dec 2024 10:36:18 -0300 Subject: [PATCH 06/18] Add `tool.crewai.type` pyproject attribute in templates (#1789) --- src/crewai/cli/templates/crew/pyproject.toml | 3 +++ src/crewai/cli/templates/flow/pyproject.toml | 3 +++ src/crewai/cli/templates/tool/pyproject.toml | 2 ++ 3 files changed, 8 insertions(+) diff --git a/src/crewai/cli/templates/crew/pyproject.toml b/src/crewai/cli/templates/crew/pyproject.toml index 5c306e62d5..5ea8194c85 100644 --- a/src/crewai/cli/templates/crew/pyproject.toml +++ b/src/crewai/cli/templates/crew/pyproject.toml @@ -18,3 +18,6 @@ test = "{{folder_name}}.main:test" [build-system] requires = ["hatchling"] build-backend = "hatchling.build" + +[tool.crewai] +type = "crew" diff --git a/src/crewai/cli/templates/flow/pyproject.toml b/src/crewai/cli/templates/flow/pyproject.toml index e55744ff34..8a523d2ede 100644 --- a/src/crewai/cli/templates/flow/pyproject.toml +++ b/src/crewai/cli/templates/flow/pyproject.toml @@ -15,3 +15,6 @@ plot = "{{folder_name}}.main:plot" [build-system] requires = ["hatchling"] build-backend = "hatchling.build" + +[tool.crewai] +type = "flow" diff --git a/src/crewai/cli/templates/tool/pyproject.toml b/src/crewai/cli/templates/tool/pyproject.toml index e6e08a3894..69b8d355fb 100644 --- a/src/crewai/cli/templates/tool/pyproject.toml +++ b/src/crewai/cli/templates/tool/pyproject.toml @@ -8,3 +8,5 @@ dependencies = [ "crewai[tools]>=0.86.0" ] +[tool.crewai] +type = "tool" From 9ee6824ccd85e6c6231f35db07bb6fe6f90959c7 Mon Sep 17 00:00:00 2001 From: PJ Date: Fri, 20 Dec 2024 10:17:34 -0500 Subject: [PATCH 07/18] Correcting a small grammatical issue that was bugging me: from _satisfy the expect criteria_ to _satisfies the expected criteria_ (#1783) Signed-off-by: PJ Hagerty Co-authored-by: Brandon Hancock (bhancock_ai) <109994880+bhancockio@users.noreply.github.com> --- src/crewai/translations/en.json | 2 +- .../test_agent_function_calling_llm.yaml | 34 +++++++++---------- ...odel_family_that_allows_skipping_tool.yaml | 2 +- 3 files changed, 19 insertions(+), 19 deletions(-) diff --git a/src/crewai/translations/en.json b/src/crewai/translations/en.json index 2b8fec44b8..6bdbb7c297 100644 --- a/src/crewai/translations/en.json +++ b/src/crewai/translations/en.json @@ -12,7 +12,7 @@ "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", - "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", + "final_answer_format": "If you don't need to use any more tools, you must give your best complete final answer, make sure it satisfies the expected 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}", "expected_output": "\nThis is the expect criteria for your final answer: {expected_output}\nyou MUST return the actual complete content as the final answer, not a summary.", diff --git a/tests/cassettes/test_agent_function_calling_llm.yaml b/tests/cassettes/test_agent_function_calling_llm.yaml index d3c68b6e2c..fdeabed210 100644 --- a/tests/cassettes/test_agent_function_calling_llm.yaml +++ b/tests/cassettes/test_agent_function_calling_llm.yaml @@ -26237,7 +26237,7 @@ interactions: answer."}, {"role": "user", "content": "I did it wrong. Invalid Format: I missed the ''Action:'' after ''Thought:''. I will do right next, and don''t use a tool I have already used.\n\nIf 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 + your best complete final answer, make sure it satisfies the expected 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"}], "model": "gpt-4o"}' headers: @@ -26590,7 +26590,7 @@ interactions: answer."}, {"role": "user", "content": "I did it wrong. Invalid Format: I missed the ''Action:'' after ''Thought:''. I will do right next, and don''t use a tool I have already used.\n\nIf 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 + your best complete final answer, make sure it satisfies the expected 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"}, {"role": "user", "content": "I did it wrong. Tried to both perform Action and give a Final Answer at the @@ -26941,7 +26941,7 @@ interactions: answer."}, {"role": "user", "content": "I did it wrong. Invalid Format: I missed the ''Action:'' after ''Thought:''. I will do right next, and don''t use a tool I have already used.\n\nIf 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 + your best complete final answer, make sure it satisfies the expected 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"}, {"role": "user", "content": "I did it wrong. Tried to both perform Action and give a Final Answer at the @@ -27292,7 +27292,7 @@ interactions: answer."}, {"role": "user", "content": "I did it wrong. Invalid Format: I missed the ''Action:'' after ''Thought:''. I will do right next, and don''t use a tool I have already used.\n\nIf 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 + your best complete final answer, make sure it satisfies the expected 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"}, {"role": "user", "content": "I did it wrong. Tried to both perform Action and give a Final Answer at the @@ -27647,7 +27647,7 @@ interactions: answer."}, {"role": "user", "content": "I did it wrong. Invalid Format: I missed the ''Action:'' after ''Thought:''. I will do right next, and don''t use a tool I have already used.\n\nIf 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 + your best complete final answer, make sure it satisfies the expected 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"}, {"role": "user", "content": "I did it wrong. Tried to both perform Action and give a Final Answer at the @@ -28005,7 +28005,7 @@ interactions: answer."}, {"role": "user", "content": "I did it wrong. Invalid Format: I missed the ''Action:'' after ''Thought:''. I will do right next, and don''t use a tool I have already used.\n\nIf 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 + your best complete final answer, make sure it satisfies the expected 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"}, {"role": "user", "content": "I did it wrong. Tried to both perform Action and give a Final Answer at the @@ -28364,7 +28364,7 @@ interactions: answer."}, {"role": "user", "content": "I did it wrong. Invalid Format: I missed the ''Action:'' after ''Thought:''. I will do right next, and don''t use a tool I have already used.\n\nIf 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 + your best complete final answer, make sure it satisfies the expected 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"}, {"role": "user", "content": "I did it wrong. Tried to both perform Action and give a Final Answer at the @@ -28718,7 +28718,7 @@ interactions: answer."}, {"role": "user", "content": "I did it wrong. Invalid Format: I missed the ''Action:'' after ''Thought:''. I will do right next, and don''t use a tool I have already used.\n\nIf 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 + your best complete final answer, make sure it satisfies the expected 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"}, {"role": "user", "content": "I did it wrong. Tried to both perform Action and give a Final Answer at the @@ -29082,7 +29082,7 @@ interactions: answer."}, {"role": "user", "content": "I did it wrong. Invalid Format: I missed the ''Action:'' after ''Thought:''. I will do right next, and don''t use a tool I have already used.\n\nIf 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 + your best complete final answer, make sure it satisfies the expected 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"}, {"role": "user", "content": "I did it wrong. Tried to both perform Action and give a Final Answer at the @@ -29441,7 +29441,7 @@ interactions: answer."}, {"role": "user", "content": "I did it wrong. Invalid Format: I missed the ''Action:'' after ''Thought:''. I will do right next, and don''t use a tool I have already used.\n\nIf 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 + your best complete final answer, make sure it satisfies the expected 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"}, {"role": "user", "content": "I did it wrong. Tried to both perform Action and give a Final Answer at the @@ -29802,7 +29802,7 @@ interactions: answer."}, {"role": "user", "content": "I did it wrong. Invalid Format: I missed the ''Action:'' after ''Thought:''. I will do right next, and don''t use a tool I have already used.\n\nIf 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 + your best complete final answer, make sure it satisfies the expected 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"}, {"role": "user", "content": "I did it wrong. Tried to both perform Action and give a Final Answer at the @@ -30170,7 +30170,7 @@ interactions: answer."}, {"role": "user", "content": "I did it wrong. Invalid Format: I missed the ''Action:'' after ''Thought:''. I will do right next, and don''t use a tool I have already used.\n\nIf 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 + your best complete final answer, make sure it satisfies the expected 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"}, {"role": "user", "content": "I did it wrong. Tried to both perform Action and give a Final Answer at the @@ -30533,7 +30533,7 @@ interactions: answer."}, {"role": "user", "content": "I did it wrong. Invalid Format: I missed the ''Action:'' after ''Thought:''. I will do right next, and don''t use a tool I have already used.\n\nIf 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 + your best complete final answer, make sure it satisfies the expected 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"}, {"role": "user", "content": "I did it wrong. Tried to both perform Action and give a Final Answer at the @@ -30907,7 +30907,7 @@ interactions: answer."}, {"role": "user", "content": "I did it wrong. Invalid Format: I missed the ''Action:'' after ''Thought:''. I will do right next, and don''t use a tool I have already used.\n\nIf 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 + your best complete final answer, make sure it satisfies the expected 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"}, {"role": "user", "content": "I did it wrong. Tried to both perform Action and give a Final Answer at the @@ -31273,7 +31273,7 @@ interactions: answer."}, {"role": "user", "content": "I did it wrong. Invalid Format: I missed the ''Action:'' after ''Thought:''. I will do right next, and don''t use a tool I have already used.\n\nIf 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 + your best complete final answer, make sure it satisfies the expected 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"}, {"role": "user", "content": "I did it wrong. Tried to both perform Action and give a Final Answer at the @@ -31644,7 +31644,7 @@ interactions: answer."}, {"role": "user", "content": "I did it wrong. Invalid Format: I missed the ''Action:'' after ''Thought:''. I will do right next, and don''t use a tool I have already used.\n\nIf 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 + your best complete final answer, make sure it satisfies the expected 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"}, {"role": "user", "content": "I did it wrong. Tried to both perform Action and give a Final Answer at the @@ -32015,7 +32015,7 @@ interactions: answer."}, {"role": "user", "content": "I did it wrong. Invalid Format: I missed the ''Action:'' after ''Thought:''. I will do right next, and don''t use a tool I have already used.\n\nIf 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 + your best complete final answer, make sure it satisfies the expected 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"}, {"role": "user", "content": "I did it wrong. Tried to both perform Action and give a Final Answer at the diff --git a/tests/cassettes/test_agent_powered_by_new_o_model_family_that_allows_skipping_tool.yaml b/tests/cassettes/test_agent_powered_by_new_o_model_family_that_allows_skipping_tool.yaml index 36fb1638e9..3ad1472782 100644 --- a/tests/cassettes/test_agent_powered_by_new_o_model_family_that_allows_skipping_tool.yaml +++ b/tests/cassettes/test_agent_powered_by_new_o_model_family_that_allows_skipping_tool.yaml @@ -247,7 +247,7 @@ interactions: {"role": "user", "content": "I did it wrong. Invalid Format: I missed the ''Action:'' after ''Thought:''. I will do right next, and don''t use a tool I have already used.\n\nIf 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 + final answer, make sure it satisfies the expected 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"}], "model": "o1-preview"}' headers: From 22e5d39884393518fd6d8755e3c80880e34010dd Mon Sep 17 00:00:00 2001 From: "devin-ai-integration[bot]" <158243242+devin-ai-integration[bot]@users.noreply.github.com> Date: Sun, 22 Dec 2024 00:52:02 -0300 Subject: [PATCH 08/18] feat: Add task guardrails feature (#1742) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat: Add task guardrails feature Add support for custom code guardrails in tasks that validate outputs before proceeding to the next task. Features include: - Optional task-level guardrail function - Pre-next-task execution timing - Tuple return format (success, data) - Automatic result/error routing - Configurable retry mechanism - Comprehensive documentation and tests Link to Devin run: https://app.devin.ai/sessions/39f6cfd6c5a24d25a7bd70ce070ed29a Co-Authored-By: Joe Moura * fix: Add type check for guardrail result and remove unused import Co-Authored-By: Joe Moura * fix: Remove unnecessary f-string prefix Co-Authored-By: Joe Moura * feat: Add guardrail validation improvements - Add result/error exclusivity validation in GuardrailResult - Make return type annotations optional in Task guardrail validator - Improve error messages for validation failures Co-Authored-By: Joe Moura * docs: Add comprehensive guardrails documentation - Add type hints and examples - Add error handling best practices - Add structured error response patterns - Document retry mechanisms - Improve documentation organization Co-Authored-By: Joe Moura * refactor: Update guardrail functions to handle TaskOutput objects Co-Authored-By: Joe Moura * feat: Add task guardrails feature Add support for custom code guardrails in tasks that validate outputs before proceeding to the next task. Features include: - Optional task-level guardrail function - Pre-next-task execution timing - Tuple return format (success, data) - Automatic result/error routing - Configurable retry mechanism - Comprehensive documentation and tests Link to Devin run: https://app.devin.ai/sessions/39f6cfd6c5a24d25a7bd70ce070ed29a Co-Authored-By: Joe Moura * fix: Add type check for guardrail result and remove unused import Co-Authored-By: Joe Moura * fix: Remove unnecessary f-string prefix Co-Authored-By: Joe Moura * feat: Add guardrail validation improvements - Add result/error exclusivity validation in GuardrailResult - Make return type annotations optional in Task guardrail validator - Improve error messages for validation failures Co-Authored-By: Joe Moura * docs: Add comprehensive guardrails documentation - Add type hints and examples - Add error handling best practices - Add structured error response patterns - Document retry mechanisms - Improve documentation organization Co-Authored-By: Joe Moura * refactor: Update guardrail functions to handle TaskOutput objects Co-Authored-By: Joe Moura * style: Fix import sorting in task guardrails files Co-Authored-By: Joe Moura * fixing docs * Fixing guardarils implementation * docs: Enhance guardrail validator docstring with runtime validation rationale Co-Authored-By: Joe Moura --------- Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> Co-authored-by: Joe Moura Co-authored-by: Brandon Hancock (bhancock_ai) <109994880+bhancockio@users.noreply.github.com> Co-authored-by: João Moura --- docs/concepts/tasks.mdx | 258 ++++++++++++++++++++++++++- src/crewai/task.py | 85 ++++++++- src/crewai/tasks/guardrail_result.py | 56 ++++++ tests/test_task_guardrails.py | 134 ++++++++++++++ 4 files changed, 526 insertions(+), 7 deletions(-) create mode 100644 src/crewai/tasks/guardrail_result.py create mode 100644 tests/test_task_guardrails.py diff --git a/docs/concepts/tasks.mdx b/docs/concepts/tasks.mdx index 9ca90fcb5a..6ffd95e194 100644 --- a/docs/concepts/tasks.mdx +++ b/docs/concepts/tasks.mdx @@ -6,7 +6,7 @@ icon: list-check ## Overview of a Task -In the CrewAI framework, a `Task` is a specific assignment completed by an `Agent`. +In the CrewAI framework, a `Task` is a specific assignment completed by an `Agent`. 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. @@ -263,8 +263,148 @@ analysis_task = Task( ) ``` +## Task Guardrails + +Task guardrails provide a way to validate and transform task outputs before they +are passed to the next task. This feature helps ensure data quality and provides +efeedback to agents when their output doesn't meet specific criteria. + +### Using Task Guardrails + +To add a guardrail to a task, provide a validation function through the `guardrail` parameter: + +```python Code +from typing import Tuple, Union, Dict, Any + +def validate_blog_content(result: str) -> Tuple[bool, Union[Dict[str, Any], str]]: + """Validate blog content meets requirements.""" + try: + # Check word count + word_count = len(result.split()) + if word_count > 200: + return (False, { + "error": "Blog content exceeds 200 words", + "code": "WORD_COUNT_ERROR", + "context": {"word_count": word_count} + }) + + # Additional validation logic here + return (True, result.strip()) + except Exception as e: + return (False, { + "error": "Unexpected error during validation", + "code": "SYSTEM_ERROR" + }) + +blog_task = Task( + description="Write a blog post about AI", + expected_output="A blog post under 200 words", + agent=blog_agent, + guardrail=validate_blog_content # Add the guardrail function +) +``` + +### Guardrail Function Requirements + +1. **Function Signature**: + - Must accept exactly one parameter (the task output) + - Should return a tuple of `(bool, Any)` + - Type hints are recommended but optional + +2. **Return Values**: + - Success: Return `(True, validated_result)` + - Failure: Return `(False, error_details)` + +### Error Handling Best Practices + +1. **Structured Error Responses**: +```python Code +def validate_with_context(result: str) -> Tuple[bool, Union[Dict[str, Any], str]]: + try: + # Main validation logic + validated_data = perform_validation(result) + return (True, validated_data) + except ValidationError as e: + return (False, { + "error": str(e), + "code": "VALIDATION_ERROR", + "context": {"input": result} + }) + except Exception as e: + return (False, { + "error": "Unexpected error", + "code": "SYSTEM_ERROR" + }) +``` + +2. **Error Categories**: + - Use specific error codes + - Include relevant context + - Provide actionable feedback + +3. **Validation Chain**: +```python Code +from typing import Any, Dict, List, Tuple, Union + +def complex_validation(result: str) -> Tuple[bool, Union[str, Dict[str, Any]]]: + """Chain multiple validation steps.""" + # Step 1: Basic validation + if not result: + return (False, {"error": "Empty result", "code": "EMPTY_INPUT"}) + + # Step 2: Content validation + try: + validated = validate_content(result) + if not validated: + return (False, {"error": "Invalid content", "code": "CONTENT_ERROR"}) + + # Step 3: Format validation + formatted = format_output(validated) + return (True, formatted) + except Exception as e: + return (False, { + "error": str(e), + "code": "VALIDATION_ERROR", + "context": {"step": "content_validation"} + }) +``` + +### Handling Guardrail Results + +When a guardrail returns `(False, error)`: +1. The error is sent back to the agent +2. The agent attempts to fix the issue +3. The process repeats until: + - The guardrail returns `(True, result)` + - Maximum retries are reached + +Example with retry handling: +```python Code +from typing import Optional, Tuple, Union + +def validate_json_output(result: str) -> Tuple[bool, Union[Dict[str, Any], str]]: + """Validate and parse JSON output.""" + try: + # Try to parse as JSON + data = json.loads(result) + return (True, data) + except json.JSONDecodeError as e: + return (False, { + "error": "Invalid JSON format", + "code": "JSON_ERROR", + "context": {"line": e.lineno, "column": e.colno} + }) + +task = Task( + description="Generate a JSON report", + expected_output="A valid JSON object", + agent=analyst, + guardrail=validate_json_output, + max_retries=3 # Limit retry attempts +) +``` + ## Getting Structured Consistent Outputs from Tasks -When you need to ensure that a task outputs a structured and consistent format, you can use the `output_pydantic` or `output_json` properties on a task. These properties allow you to define the expected output structure, making it easier to parse and utilize the results in your application. It's also important to note that the output of the final task of a crew becomes the final output of the actual crew itself. @@ -608,6 +748,114 @@ While creating and executing tasks, certain validation mechanisms are in place t These validations help in maintaining the consistency and reliability of task executions within the crewAI framework. +## Task Guardrails + +Task guardrails provide a powerful way to validate, transform, or filter task outputs before they are passed to the next task. Guardrails are optional functions that execute before the next task starts, allowing you to ensure that task outputs meet specific requirements or formats. + +### Basic Usage + +```python Code +from typing import Tuple, Union +from crewai import Task + +def validate_json_output(result: str) -> Tuple[bool, Union[dict, str]]: + """Validate that the output is valid JSON.""" + try: + json_data = json.loads(result) + return (True, json_data) + except json.JSONDecodeError: + return (False, "Output must be valid JSON") + +task = Task( + description="Generate JSON data", + expected_output="Valid JSON object", + guardrail=validate_json_output +) +``` + +### How Guardrails Work + +1. **Optional Attribute**: Guardrails are an optional attribute at the task level, allowing you to add validation only where needed. +2. **Execution Timing**: The guardrail function is executed before the next task starts, ensuring valid data flow between tasks. +3. **Return Format**: Guardrails must return a tuple of `(success, data)`: + - If `success` is `True`, `data` is the validated/transformed result + - If `success` is `False`, `data` is the error message +4. **Result Routing**: + - On success (`True`), the result is automatically passed to the next task + - On failure (`False`), the error is sent back to the agent to generate a new answer + +### Common Use Cases + +#### Data Format Validation +```python Code +def validate_email_format(result: str) -> Tuple[bool, Union[str, str]]: + """Ensure the output contains a valid email address.""" + import re + email_pattern = r'^[\w\.-]+@[\w\.-]+\.\w+$' + if re.match(email_pattern, result.strip()): + return (True, result.strip()) + return (False, "Output must be a valid email address") +``` + +#### Content Filtering +```python Code +def filter_sensitive_info(result: str) -> Tuple[bool, Union[str, str]]: + """Remove or validate sensitive information.""" + sensitive_patterns = ['SSN:', 'password:', 'secret:'] + for pattern in sensitive_patterns: + if pattern.lower() in result.lower(): + return (False, f"Output contains sensitive information ({pattern})") + return (True, result) +``` + +#### Data Transformation +```python Code +def normalize_phone_number(result: str) -> Tuple[bool, Union[str, str]]: + """Ensure phone numbers are in a consistent format.""" + import re + digits = re.sub(r'\D', '', result) + if len(digits) == 10: + formatted = f"({digits[:3]}) {digits[3:6]}-{digits[6:]}" + return (True, formatted) + return (False, "Output must be a 10-digit phone number") +``` + +### Advanced Features + +#### Chaining Multiple Validations +```python Code +def chain_validations(*validators): + """Chain multiple validators together.""" + def combined_validator(result): + for validator in validators: + success, data = validator(result) + if not success: + return (False, data) + result = data + return (True, result) + return combined_validator + +# Usage +task = Task( + description="Get user contact info", + expected_output="Email and phone", + guardrail=chain_validations( + validate_email_format, + filter_sensitive_info + ) +) +``` + +#### Custom Retry Logic +```python Code +task = Task( + description="Generate data", + expected_output="Valid data", + guardrail=validate_data, + max_retries=5 # Override default retry limit +) +``` + ## Creating Directories when Saving Files You can now specify if a task should create directories when saving its output to a file. This is particularly useful for organizing outputs and ensuring that file paths are correctly structured. @@ -629,7 +877,7 @@ save_output_task = Task( ## Conclusion -Tasks are the driving force behind the actions of agents in CrewAI. -By properly defining tasks and their outcomes, you set the stage for your AI agents to work effectively, either independently or as a collaborative unit. -Equipping tasks with appropriate tools, understanding the execution process, and following robust validation practices are crucial for maximizing CrewAI's potential, +Tasks are the driving force behind the actions of agents in CrewAI. +By properly defining tasks and their outcomes, you set the stage for your AI agents to work effectively, either independently or as a collaborative unit. +Equipping tasks with appropriate tools, understanding the execution process, and following robust validation practices are crucial for maximizing CrewAI's potential, ensuring agents are effectively prepared for their assignments and that tasks are executed as intended. diff --git a/src/crewai/task.py b/src/crewai/task.py index b585e0c696..ed15fda2cb 100644 --- a/src/crewai/task.py +++ b/src/crewai/task.py @@ -1,4 +1,5 @@ import datetime +import inspect import json import threading import uuid @@ -6,7 +7,7 @@ from copy import copy from hashlib import md5 from pathlib import Path -from typing import Any, Dict, List, Optional, Set, Tuple, Type, Union +from typing import Any, Callable, Dict, List, Optional, Set, Tuple, Type, Union from opentelemetry.trace import Span from pydantic import ( @@ -20,6 +21,7 @@ from pydantic_core import PydanticCustomError from crewai.agents.agent_builder.base_agent import BaseAgent +from crewai.tasks.guardrail_result import GuardrailResult from crewai.tasks.output_format import OutputFormat from crewai.tasks.task_output import TaskOutput from crewai.telemetry.telemetry import Telemetry @@ -110,6 +112,55 @@ class Task(BaseModel): default=None, ) processed_by_agents: Set[str] = Field(default_factory=set) + guardrail: Optional[Callable[[TaskOutput], Tuple[bool, Any]]] = Field( + default=None, + description="Function to validate task output before proceeding to next task" + ) + max_retries: int = Field( + default=3, + description="Maximum number of retries when guardrail fails" + ) + retry_count: int = Field( + default=0, + description="Current number of retries" + ) + + @field_validator("guardrail") + @classmethod + def validate_guardrail_function(cls, v: Optional[Callable]) -> Optional[Callable]: + """Validate that the guardrail function has the correct signature and behavior. + + While type hints provide static checking, this validator ensures runtime safety by: + 1. Verifying the function accepts exactly one parameter (the TaskOutput) + 2. Checking return type annotations match Tuple[bool, Any] if present + 3. Providing clear, immediate error messages for debugging + + This runtime validation is crucial because: + - Type hints are optional and can be ignored at runtime + - Function signatures need immediate validation before task execution + - Clear error messages help users debug guardrail implementation issues + + Args: + v: The guardrail function to validate + + Returns: + The validated guardrail function + + Raises: + ValueError: If the function signature is invalid or return annotation + doesn't match Tuple[bool, Any] + """ + if v is not None: + sig = inspect.signature(v) + if len(sig.parameters) != 1: + raise ValueError("Guardrail function must accept exactly one parameter") + + # Check return annotation if present, but don't require it + return_annotation = sig.return_annotation + if return_annotation != inspect.Signature.empty: + if not (return_annotation == Tuple[bool, Any] or str(return_annotation) == 'Tuple[bool, Any]'): + raise ValueError("If return type is annotated, it must be Tuple[bool, Any]") + return v _telemetry: Telemetry = PrivateAttr(default_factory=Telemetry) _execution_span: Optional[Span] = PrivateAttr(default=None) @@ -254,7 +305,6 @@ def _execute_core( ) pydantic_output, json_output = self._export_output(result) - task_output = TaskOutput( name=self.name, description=self.description, @@ -265,6 +315,37 @@ def _execute_core( agent=agent.role, output_format=self._get_output_format(), ) + + if self.guardrail: + guardrail_result = GuardrailResult.from_tuple(self.guardrail(task_output)) + if not guardrail_result.success: + if self.retry_count >= self.max_retries: + raise Exception( + f"Task failed guardrail validation after {self.max_retries} retries. " + f"Last error: {guardrail_result.error}" + ) + + self.retry_count += 1 + context = ( + f"### Previous attempt failed validation: {guardrail_result.error}\n\n\n" + f"### Previous result:\n{task_output.raw}\n\n\n" + "Try again, making sure to address the validation error." + ) + return self._execute_core(agent, context, tools) + + if guardrail_result.result is None: + raise Exception( + "Task guardrail returned None as result. This is not allowed." + ) + + if isinstance(guardrail_result.result, str): + task_output.raw = guardrail_result.result + pydantic_output, json_output = self._export_output(guardrail_result.result) + task_output.pydantic = pydantic_output + task_output.json_dict = json_output + elif isinstance(guardrail_result.result, TaskOutput): + task_output = guardrail_result.result + self.output = task_output self._set_end_execution_time(start_time) diff --git a/src/crewai/tasks/guardrail_result.py b/src/crewai/tasks/guardrail_result.py new file mode 100644 index 0000000000..ba8ebc552f --- /dev/null +++ b/src/crewai/tasks/guardrail_result.py @@ -0,0 +1,56 @@ +""" +Module for handling task guardrail validation results. + +This module provides the GuardrailResult class which standardizes +the way task guardrails return their validation results. +""" + +from typing import Any, Optional, Tuple, Union + +from pydantic import BaseModel, field_validator + + +class GuardrailResult(BaseModel): + """Result from a task guardrail execution. + + This class standardizes the return format of task guardrails, + converting tuple responses into a structured format that can + be easily handled by the task execution system. + + Attributes: + success (bool): Whether the guardrail validation passed + result (Any, optional): The validated/transformed result if successful + error (str, optional): Error message if validation failed + """ + success: bool + result: Optional[Any] = None + error: Optional[str] = None + + @field_validator("result", "error") + @classmethod + def validate_result_error_exclusivity(cls, v: Any, info) -> Any: + values = info.data + if "success" in values: + if values["success"] and v and "error" in values and values["error"]: + raise ValueError("Cannot have both result and error when success is True") + if not values["success"] and v and "result" in values and values["result"]: + raise ValueError("Cannot have both result and error when success is False") + return v + + @classmethod + def from_tuple(cls, result: Tuple[bool, Union[Any, str]]) -> "GuardrailResult": + """Create a GuardrailResult from a validation tuple. + + Args: + result: A tuple of (success, data) where data is either + the validated result or error message. + + Returns: + GuardrailResult: A new instance with the tuple data. + """ + success, data = result + return cls( + success=success, + result=data if success else None, + error=data if not success else None + ) diff --git a/tests/test_task_guardrails.py b/tests/test_task_guardrails.py new file mode 100644 index 0000000000..dc96cb8789 --- /dev/null +++ b/tests/test_task_guardrails.py @@ -0,0 +1,134 @@ +"""Tests for task guardrails functionality.""" + +from unittest.mock import Mock + +import pytest + +from crewai.task import Task +from crewai.tasks.task_output import TaskOutput + + +def test_task_without_guardrail(): + """Test that tasks work normally without guardrails (backward compatibility).""" + agent = Mock() + agent.role = "test_agent" + agent.execute_task.return_value = "test result" + agent.crew = None + + task = Task( + description="Test task", + expected_output="Output" + ) + + result = task.execute_sync(agent=agent) + assert isinstance(result, TaskOutput) + assert result.raw == "test result" + + +def test_task_with_successful_guardrail(): + """Test that successful guardrail validation passes transformed result.""" + def guardrail(result: TaskOutput): + return (True, result.raw.upper()) + + agent = Mock() + agent.role = "test_agent" + agent.execute_task.return_value = "test result" + agent.crew = None + + task = Task( + description="Test task", + expected_output="Output", + guardrail=guardrail + ) + + result = task.execute_sync(agent=agent) + assert isinstance(result, TaskOutput) + assert result.raw == "TEST RESULT" + + +def test_task_with_failing_guardrail(): + """Test that failing guardrail triggers retry with error context.""" + def guardrail(result: TaskOutput): + return (False, "Invalid format") + + agent = Mock() + agent.role = "test_agent" + agent.execute_task.side_effect = [ + "bad result", + "good result" + ] + agent.crew = None + + task = Task( + description="Test task", + expected_output="Output", + guardrail=guardrail, + max_retries=1 + ) + + # First execution fails guardrail, second succeeds + agent.execute_task.side_effect = ["bad result", "good result"] + with pytest.raises(Exception) as exc_info: + task.execute_sync(agent=agent) + + assert "Task failed guardrail validation" in str(exc_info.value) + assert task.retry_count == 1 + + +def test_task_with_guardrail_retries(): + """Test that guardrail respects max_retries configuration.""" + def guardrail(result: TaskOutput): + return (False, "Invalid format") + + agent = Mock() + agent.role = "test_agent" + agent.execute_task.return_value = "bad result" + agent.crew = None + + task = Task( + description="Test task", + expected_output="Output", + guardrail=guardrail, + max_retries=2 + ) + + with pytest.raises(Exception) as exc_info: + task.execute_sync(agent=agent) + + assert task.retry_count == 2 + assert "Task failed guardrail validation after 2 retries" in str(exc_info.value) + assert "Invalid format" in str(exc_info.value) + + +def test_guardrail_error_in_context(): + """Test that guardrail error is passed in context for retry.""" + def guardrail(result: TaskOutput): + return (False, "Expected JSON, got string") + + agent = Mock() + agent.role = "test_agent" + agent.crew = None + + task = Task( + description="Test task", + expected_output="Output", + guardrail=guardrail, + max_retries=1 + ) + + # Mock execute_task to succeed on second attempt + first_call = True + def execute_task(task, context, tools): + nonlocal first_call + if first_call: + first_call = False + return "invalid" + return '{"valid": "json"}' + + agent.execute_task.side_effect = execute_task + + with pytest.raises(Exception) as exc_info: + task.execute_sync(agent=agent) + + assert "Task failed guardrail validation" in str(exc_info.value) + assert "Expected JSON, got string" in str(exc_info.value) From c887ff1f47d6c0f8ccb30c3b453eaf3ae4b5bdc5 Mon Sep 17 00:00:00 2001 From: "devin-ai-integration[bot]" <158243242+devin-ai-integration[bot]@users.noreply.github.com> Date: Mon, 23 Dec 2024 13:05:29 -0500 Subject: [PATCH 09/18] feat: Add interpolate_only method and improve error handling (#1791) * Fixed output_file not respecting system path * Fixed yaml config is not escaped properly for output requirements * feat: Add interpolate_only method and improve error handling - Add interpolate_only method for string interpolation while preserving JSON structure - Add comprehensive test coverage for interpolate_only - Add proper type annotation for logger using ClassVar - Improve error handling and documentation for _save_file method Co-Authored-By: Joe Moura * fix: Sort imports to fix lint issues Co-Authored-By: Joe Moura * fix: Reorganize imports using ruff --fix Co-Authored-By: Joe Moura * fix: Consolidate imports and fix formatting Co-Authored-By: Joe Moura * fix: Apply ruff automatic import sorting Co-Authored-By: Joe Moura * fix: Sort imports using ruff --fix Co-Authored-By: Joe Moura --------- Co-authored-by: Frieda (Jingying) Huang Co-authored-by: Brandon Hancock (bhancock_ai) <109994880+bhancockio@users.noreply.github.com> Co-authored-by: Frieda Huang <124417784+frieda-huang@users.noreply.github.com> Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> Co-authored-by: Joe Moura --- src/crewai/task.py | 65 +++++++++++++++++++++++++++++++++++----------- tests/task_test.py | 42 ++++++++++++++++++++++++++++++ 2 files changed, 92 insertions(+), 15 deletions(-) diff --git a/src/crewai/task.py b/src/crewai/task.py index ed15fda2cb..30ab79c005 100644 --- a/src/crewai/task.py +++ b/src/crewai/task.py @@ -1,13 +1,25 @@ import datetime import inspect import json +import logging import threading import uuid from concurrent.futures import Future from copy import copy from hashlib import md5 from pathlib import Path -from typing import Any, Callable, Dict, List, Optional, Set, Tuple, Type, Union +from typing import ( + Any, + Callable, + ClassVar, + Dict, + List, + Optional, + Set, + Tuple, + Type, + Union, +) from opentelemetry.trace import Span from pydantic import ( @@ -51,6 +63,7 @@ class Task(BaseModel): """ __hash__ = object.__hash__ # type: ignore + logger: ClassVar[logging.Logger] = logging.getLogger(__name__) used_tools: int = 0 tools_errors: int = 0 delegations: int = 0 @@ -389,7 +402,18 @@ def interpolate_inputs(self, inputs: Dict[str, Any]) -> None: if inputs: self.description = self._original_description.format(**inputs) - self.expected_output = self._original_expected_output.format(**inputs) + self.expected_output = self.interpolate_only( + input_string=self._original_expected_output, inputs=inputs + ) + + def interpolate_only(self, input_string: str, inputs: Dict[str, Any]) -> str: + """Interpolate placeholders (e.g., {key}) in a string while leaving JSON untouched.""" + escaped_string = input_string.replace("{", "{{").replace("}", "}}") + + for key in inputs.keys(): + escaped_string = escaped_string.replace(f"{{{{{key}}}}}", f"{{{key}}}") + + return escaped_string.format(**inputs) def increment_tools_errors(self) -> None: """Increment the tools errors counter.""" @@ -471,22 +495,33 @@ def _get_output_format(self) -> OutputFormat: return OutputFormat.RAW def _save_file(self, result: Any) -> None: + """Save task output to a file. + + Args: + result: The result to save to the file. Can be a dict or any stringifiable object. + + Raises: + ValueError: If output_file is not set + RuntimeError: If there is an error writing to the file + """ if self.output_file is None: raise ValueError("output_file is not set.") - resolved_path = Path(self.output_file).expanduser().resolve() - directory = resolved_path.parent - - if not directory.exists(): - directory.mkdir(parents=True, exist_ok=True) - - with resolved_path.open("w", encoding="utf-8") as file: - if isinstance(result, dict): - import json - - json.dump(result, file, ensure_ascii=False, indent=2) - else: - file.write(str(result)) + try: + resolved_path = Path(self.output_file).expanduser().resolve() + directory = resolved_path.parent + + if not directory.exists(): + directory.mkdir(parents=True, exist_ok=True) + + with resolved_path.open("w", encoding="utf-8") as file: + if isinstance(result, dict): + import json + json.dump(result, file, ensure_ascii=False, indent=2) + else: + file.write(str(result)) + except (OSError, IOError) as e: + raise RuntimeError(f"Failed to save output file: {e}") return None def __repr__(self): diff --git a/tests/task_test.py b/tests/task_test.py index 7a77042db7..40eb98e540 100644 --- a/tests/task_test.py +++ b/tests/task_test.py @@ -736,6 +736,48 @@ def test_interpolate_inputs(): assert task.expected_output == "Bullet point list of 5 interesting ideas about ML." +def test_interpolate_only(): + """Test the interpolate_only method for various scenarios including JSON structure preservation.""" + task = Task( + description="Unused in this test", + expected_output="Unused in this test" + ) + + # Test JSON structure preservation + json_string = '{"info": "Look at {placeholder}", "nested": {"val": "{nestedVal}"}}' + result = task.interpolate_only( + input_string=json_string, + inputs={"placeholder": "the data", "nestedVal": "something else"} + ) + assert '"info": "Look at the data"' in result + assert '"val": "something else"' in result + assert "{placeholder}" not in result + assert "{nestedVal}" not in result + + # Test normal string interpolation + normal_string = "Hello {name}, welcome to {place}!" + result = task.interpolate_only( + input_string=normal_string, + inputs={"name": "John", "place": "CrewAI"} + ) + assert result == "Hello John, welcome to CrewAI!" + + # Test empty string + result = task.interpolate_only( + input_string="", + inputs={"unused": "value"} + ) + assert result == "" + + # Test string with no placeholders + no_placeholders = "Hello, this is a test" + result = task.interpolate_only( + input_string=no_placeholders, + inputs={"unused": "value"} + ) + assert result == no_placeholders + + def test_task_output_str_with_pydantic(): from crewai.tasks.output_format import OutputFormat From b3185ad90c1cf1fae84f3db793ac1cd0acca9a08 Mon Sep 17 00:00:00 2001 From: Lorenze Jay <63378463+lorenzejay@users.noreply.github.com> Date: Mon, 23 Dec 2024 10:19:58 -0800 Subject: [PATCH 10/18] Feat/docling-support (#1763) * added tool for docling support * docling support installation * use file_paths instead of file_path * fix import * organized imports * run_type docs * needs to be list * fixed logic * logged but file_path is backwards compatible * use file_paths instead of file_path 2 * added test for multiple sources for file_paths * fix run-types * enabling local files to work and type cleanup * linted * fix test and types * fixed run types * fix types * renamed to CrewDoclingSource * linted * added docs * resolve conflicts --------- Co-authored-by: Brandon Hancock (bhancock_ai) <109994880+bhancockio@users.noreply.github.com> Co-authored-by: Brandon Hancock --- docs/concepts/knowledge.mdx | 49 + pyproject.toml | 3 + .../source/base_file_knowledge_source.py | 53 +- .../knowledge/source/base_knowledge_source.py | 2 +- .../knowledge/source/crew_docling_source.py | 120 +++ .../source/string_knowledge_source.py | 4 +- tests/knowledge/knowledge_test.py | 59 +- uv.lock | 911 +++++++++++++++++- 8 files changed, 1166 insertions(+), 35 deletions(-) create mode 100644 src/crewai/knowledge/source/crew_docling_source.py diff --git a/docs/concepts/knowledge.mdx b/docs/concepts/knowledge.mdx index 8af9b70a2b..8df6f623fb 100644 --- a/docs/concepts/knowledge.mdx +++ b/docs/concepts/knowledge.mdx @@ -79,6 +79,55 @@ crew = Crew( result = crew.kickoff(inputs={"question": "What city does John live in and how old is he?"}) ``` + +Here's another example with the `CrewDoclingSource` +```python Code +from crewai import LLM, Agent, Crew, Process, Task +from crewai.knowledge.source.crew_docling_source import CrewDoclingSource + +# Create a knowledge source +content_source = CrewDoclingSource( + file_paths=[ + "https://lilianweng.github.io/posts/2024-11-28-reward-hacking", + "https://lilianweng.github.io/posts/2024-07-07-hallucination", + ], +) + +# Create an LLM with a temperature of 0 to ensure deterministic outputs +llm = LLM(model="gpt-4o-mini", temperature=0) + +# Create an agent with the knowledge store +agent = Agent( + role="About papers", + goal="You know everything about the papers.", + backstory="""You are a master at understanding papers and their content.""", + verbose=True, + allow_delegation=False, + llm=llm, +) +task = Task( + description="Answer the following questions about the papers: {question}", + expected_output="An answer to the question.", + agent=agent, +) + +crew = Crew( + agents=[agent], + tasks=[task], + verbose=True, + process=Process.sequential, + knowledge_sources=[ + content_source + ], # Enable knowledge by adding the sources here. You can also add more sources to the sources list. +) + +result = crew.kickoff( + inputs={ + "question": "What is the reward hacking paper about? Be sure to provide sources." + } +) +``` + ## Knowledge Configuration ### Chunking Configuration diff --git a/pyproject.toml b/pyproject.toml index 27a4785b42..f9533a6f92 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -51,6 +51,9 @@ openpyxl = [ "openpyxl>=3.1.5", ] mem0 = ["mem0ai>=0.1.29"] +docling = [ + "docling>=2.12.0", +] [tool.uv] dev-dependencies = [ diff --git a/src/crewai/knowledge/source/base_file_knowledge_source.py b/src/crewai/knowledge/source/base_file_knowledge_source.py index ae9b3bf713..e086cbf659 100644 --- a/src/crewai/knowledge/source/base_file_knowledge_source.py +++ b/src/crewai/knowledge/source/base_file_knowledge_source.py @@ -1,8 +1,8 @@ from abc import ABC, abstractmethod from pathlib import Path -from typing import Dict, List, Union +from typing import Dict, List, Optional, Union -from pydantic import Field +from pydantic import Field, field_validator from crewai.knowledge.source.base_knowledge_source import BaseKnowledgeSource from crewai.knowledge.storage.knowledge_storage import KnowledgeStorage @@ -14,17 +14,28 @@ class BaseFileKnowledgeSource(BaseKnowledgeSource, ABC): """Base class for knowledge sources that load content from files.""" _logger: Logger = Logger(verbose=True) - file_path: Union[Path, List[Path], str, List[str]] = Field( - ..., description="The path to the file" + file_path: Optional[Union[Path, List[Path], str, List[str]]] = Field( + default=None, + description="[Deprecated] The path to the file. Use file_paths instead.", + ) + file_paths: Optional[Union[Path, List[Path], str, List[str]]] = Field( + default_factory=list, description="The path to the file" ) content: Dict[Path, str] = Field(init=False, default_factory=dict) storage: KnowledgeStorage = Field(default_factory=KnowledgeStorage) safe_file_paths: List[Path] = Field(default_factory=list) + @field_validator("file_path", "file_paths", mode="before") + def validate_file_path(cls, v, values): + """Validate that at least one of file_path or file_paths is provided.""" + if v is None and ("file_path" not in values or values.get("file_path") is None): + raise ValueError("Either file_path or file_paths must be provided") + return v + def model_post_init(self, _): """Post-initialization method to load content.""" self.safe_file_paths = self._process_file_paths() - self.validate_paths() + self.validate_content() self.content = self.load_content() @abstractmethod @@ -32,7 +43,7 @@ def load_content(self) -> Dict[Path, str]: """Load and preprocess file content. Should be overridden by subclasses. Assume that the file path is relative to the project root in the knowledge directory.""" pass - def validate_paths(self): + def validate_content(self): """Validate the paths.""" for path in self.safe_file_paths: if not path.exists(): @@ -59,13 +70,29 @@ def convert_to_path(self, path: Union[Path, str]) -> Path: def _process_file_paths(self) -> List[Path]: """Convert file_path to a list of Path objects.""" - paths = ( - [self.file_path] - if isinstance(self.file_path, (str, Path)) - else self.file_path - ) - if not isinstance(paths, list): - raise ValueError("file_path must be a Path, str, or a list of these types") + # Check if old file_path is being used + if hasattr(self, "file_path") and self.file_path is not None: + self._logger.log( + "warning", + "The 'file_path' attribute is deprecated and will be removed in a future version. Please use 'file_paths' instead.", + color="yellow", + ) + paths = ( + [self.file_path] + if isinstance(self.file_path, (str, Path)) + else self.file_path + ) + else: + if self.file_paths is None: + raise ValueError("Your source must be provided with a file_paths: []") + elif isinstance(self.file_paths, list) and len(self.file_paths) == 0: + raise ValueError("Empty file_paths are not allowed") + else: + paths = ( + [self.file_paths] + if isinstance(self.file_paths, (str, Path)) + else self.file_paths + ) return [self.convert_to_path(path) for path in paths] diff --git a/src/crewai/knowledge/source/base_knowledge_source.py b/src/crewai/knowledge/source/base_knowledge_source.py index 07a9a0f25f..88c3ab360c 100644 --- a/src/crewai/knowledge/source/base_knowledge_source.py +++ b/src/crewai/knowledge/source/base_knowledge_source.py @@ -21,7 +21,7 @@ class BaseKnowledgeSource(BaseModel, ABC): collection_name: Optional[str] = Field(default=None) @abstractmethod - def load_content(self) -> Dict[Any, str]: + def validate_content(self) -> Any: """Load and preprocess content from the source.""" pass diff --git a/src/crewai/knowledge/source/crew_docling_source.py b/src/crewai/knowledge/source/crew_docling_source.py new file mode 100644 index 0000000000..8b197168b7 --- /dev/null +++ b/src/crewai/knowledge/source/crew_docling_source.py @@ -0,0 +1,120 @@ +from pathlib import Path +from typing import Iterator, List, Optional, Union +from urllib.parse import urlparse + +from docling.datamodel.base_models import InputFormat +from docling.document_converter import DocumentConverter +from docling.exceptions import ConversionError +from docling_core.transforms.chunker.hierarchical_chunker import HierarchicalChunker +from docling_core.types.doc.document import DoclingDocument +from pydantic import Field + +from crewai.knowledge.source.base_knowledge_source import BaseKnowledgeSource +from crewai.utilities.constants import KNOWLEDGE_DIRECTORY +from crewai.utilities.logger import Logger + + +class CrewDoclingSource(BaseKnowledgeSource): + """Default Source class for converting documents to markdown or json + This will auto support PDF, DOCX, and TXT, XLSX, Images, and HTML files without any additional dependencies and follows the docling package as the source of truth. + """ + + _logger: Logger = Logger(verbose=True) + + file_path: Optional[List[Union[Path, str]]] = Field(default=None) + file_paths: List[Union[Path, str]] = Field(default_factory=list) + chunks: List[str] = Field(default_factory=list) + safe_file_paths: List[Union[Path, str]] = Field(default_factory=list) + content: List[DoclingDocument] = Field(default_factory=list) + document_converter: DocumentConverter = Field( + default_factory=lambda: DocumentConverter( + allowed_formats=[ + InputFormat.MD, + InputFormat.ASCIIDOC, + InputFormat.PDF, + InputFormat.DOCX, + InputFormat.HTML, + InputFormat.IMAGE, + InputFormat.XLSX, + InputFormat.PPTX, + ] + ) + ) + + def model_post_init(self, _) -> None: + if self.file_path: + self._logger.log( + "warning", + "The 'file_path' attribute is deprecated and will be removed in a future version. Please use 'file_paths' instead.", + color="yellow", + ) + self.file_paths = self.file_path + self.safe_file_paths = self.validate_content() + self.content = self._load_content() + + def _load_content(self) -> List[DoclingDocument]: + try: + return self._convert_source_to_docling_documents() + except ConversionError as e: + self._logger.log( + "error", + f"Error loading content: {e}. Supported formats: {self.document_converter.allowed_formats}", + "red", + ) + raise e + except Exception as e: + self._logger.log("error", f"Error loading content: {e}") + raise e + + def add(self) -> None: + if self.content is None: + return + for doc in self.content: + new_chunks_iterable = self._chunk_doc(doc) + self.chunks.extend(list(new_chunks_iterable)) + self._save_documents() + + def _convert_source_to_docling_documents(self) -> List[DoclingDocument]: + conv_results_iter = self.document_converter.convert_all(self.safe_file_paths) + return [result.document for result in conv_results_iter] + + def _chunk_doc(self, doc: DoclingDocument) -> Iterator[str]: + chunker = HierarchicalChunker() + for chunk in chunker.chunk(doc): + yield chunk.text + + def validate_content(self) -> List[Union[Path, str]]: + processed_paths: List[Union[Path, str]] = [] + for path in self.file_paths: + if isinstance(path, str): + if path.startswith(("http://", "https://")): + try: + if self._validate_url(path): + processed_paths.append(path) + else: + raise ValueError(f"Invalid URL format: {path}") + except Exception as e: + raise ValueError(f"Invalid URL: {path}. Error: {str(e)}") + else: + local_path = Path(KNOWLEDGE_DIRECTORY + "/" + path) + if local_path.exists(): + processed_paths.append(local_path) + else: + raise FileNotFoundError(f"File not found: {local_path}") + else: + # this is an instance of Path + processed_paths.append(path) + return processed_paths + + def _validate_url(self, url: str) -> bool: + try: + result = urlparse(url) + return all( + [ + result.scheme in ("http", "https"), + result.netloc, + len(result.netloc.split(".")) >= 2, # Ensure domain has TLD + ] + ) + except Exception: + return False diff --git a/src/crewai/knowledge/source/string_knowledge_source.py b/src/crewai/knowledge/source/string_knowledge_source.py index 1d40ac4b87..614303b1fb 100644 --- a/src/crewai/knowledge/source/string_knowledge_source.py +++ b/src/crewai/knowledge/source/string_knowledge_source.py @@ -13,9 +13,9 @@ class StringKnowledgeSource(BaseKnowledgeSource): def model_post_init(self, _): """Post-initialization method to validate content.""" - self.load_content() + self.validate_content() - def load_content(self): + def validate_content(self): """Validate string content.""" if not isinstance(self.content, str): raise ValueError("StringKnowledgeSource only accepts string content") diff --git a/tests/knowledge/knowledge_test.py b/tests/knowledge/knowledge_test.py index 201ddea12e..366067587a 100644 --- a/tests/knowledge/knowledge_test.py +++ b/tests/knowledge/knowledge_test.py @@ -1,10 +1,12 @@ """Test Knowledge creation and querying functionality.""" from pathlib import Path +from typing import List, Union from unittest.mock import patch import pytest +from crewai.knowledge.source.crew_docling_source import CrewDoclingSource from crewai.knowledge.source.csv_knowledge_source import CSVKnowledgeSource from crewai.knowledge.source.excel_knowledge_source import ExcelKnowledgeSource from crewai.knowledge.source.json_knowledge_source import JSONKnowledgeSource @@ -200,7 +202,7 @@ def test_single_short_file(mock_vector_db, tmpdir): f.write(content) file_source = TextFileKnowledgeSource( - file_path=file_path, metadata={"preference": "personal"} + file_paths=[file_path], metadata={"preference": "personal"} ) mock_vector_db.sources = [file_source] mock_vector_db.query.return_value = [{"context": content, "score": 0.9}] @@ -242,7 +244,7 @@ def test_single_2k_character_file(mock_vector_db, tmpdir): f.write(content) file_source = TextFileKnowledgeSource( - file_path=file_path, metadata={"preference": "personal"} + file_paths=[file_path], metadata={"preference": "personal"} ) mock_vector_db.sources = [file_source] mock_vector_db.query.return_value = [{"context": content, "score": 0.9}] @@ -279,7 +281,7 @@ def test_multiple_short_files(mock_vector_db, tmpdir): file_paths.append((file_path, item["metadata"])) file_sources = [ - TextFileKnowledgeSource(file_path=path, metadata=metadata) + TextFileKnowledgeSource(file_paths=[path], metadata=metadata) for path, metadata in file_paths ] mock_vector_db.sources = file_sources @@ -352,7 +354,7 @@ def test_multiple_2k_character_files(mock_vector_db, tmpdir): file_paths.append(file_path) file_sources = [ - TextFileKnowledgeSource(file_path=path, metadata={"preference": "personal"}) + TextFileKnowledgeSource(file_paths=[path], metadata={"preference": "personal"}) for path in file_paths ] mock_vector_db.sources = file_sources @@ -399,7 +401,7 @@ def test_hybrid_string_and_files(mock_vector_db, tmpdir): file_paths.append(file_path) file_sources = [ - TextFileKnowledgeSource(file_path=path, metadata={"preference": "personal"}) + TextFileKnowledgeSource(file_paths=[path], metadata={"preference": "personal"}) for path in file_paths ] @@ -424,7 +426,7 @@ def test_pdf_knowledge_source(mock_vector_db): # Create a PDFKnowledgeSource pdf_source = PDFKnowledgeSource( - file_path=pdf_path, metadata={"preference": "personal"} + file_paths=[pdf_path], metadata={"preference": "personal"} ) mock_vector_db.sources = [pdf_source] mock_vector_db.query.return_value = [ @@ -461,7 +463,7 @@ def test_csv_knowledge_source(mock_vector_db, tmpdir): # Create a CSVKnowledgeSource csv_source = CSVKnowledgeSource( - file_path=csv_path, metadata={"preference": "personal"} + file_paths=[csv_path], metadata={"preference": "personal"} ) mock_vector_db.sources = [csv_source] mock_vector_db.query.return_value = [ @@ -496,7 +498,7 @@ def test_json_knowledge_source(mock_vector_db, tmpdir): # Create a JSONKnowledgeSource json_source = JSONKnowledgeSource( - file_path=json_path, metadata={"preference": "personal"} + file_paths=[json_path], metadata={"preference": "personal"} ) mock_vector_db.sources = [json_source] mock_vector_db.query.return_value = [ @@ -529,7 +531,7 @@ def test_excel_knowledge_source(mock_vector_db, tmpdir): # Create an ExcelKnowledgeSource excel_source = ExcelKnowledgeSource( - file_path=excel_path, metadata={"preference": "personal"} + file_paths=[excel_path], metadata={"preference": "personal"} ) mock_vector_db.sources = [excel_source] mock_vector_db.query.return_value = [ @@ -543,3 +545,42 @@ def test_excel_knowledge_source(mock_vector_db, tmpdir): # Assert that the correct information is retrieved assert any("30" in result["context"] for result in results) mock_vector_db.query.assert_called_once() + + +def test_docling_source(mock_vector_db): + docling_source = CrewDoclingSource( + file_paths=[ + "https://lilianweng.github.io/posts/2024-11-28-reward-hacking/", + ], + ) + mock_vector_db.sources = [docling_source] + mock_vector_db.query.return_value = [ + { + "context": "Reward hacking is a technique used to improve the performance of reinforcement learning agents.", + "score": 0.9, + } + ] + # Perform a query + query = "What is reward hacking?" + results = mock_vector_db.query(query) + assert any("reward hacking" in result["context"].lower() for result in results) + mock_vector_db.query.assert_called_once() + + +def test_multiple_docling_sources(): + urls: List[Union[Path, str]] = [ + "https://lilianweng.github.io/posts/2024-11-28-reward-hacking/", + "https://lilianweng.github.io/posts/2024-07-07-hallucination/", + ] + docling_source = CrewDoclingSource(file_paths=urls) + + assert docling_source.file_paths == urls + assert docling_source.content is not None + + +def test_docling_source_with_local_file(): + current_dir = Path(__file__).parent + pdf_path = current_dir / "crewai_quickstart.pdf" + docling_source = CrewDoclingSource(file_paths=[pdf_path]) + assert docling_source.file_paths == [pdf_path] + assert docling_source.content is not None diff --git a/uv.lock b/uv.lock index 5cf51b18c7..c37a1fa4e3 100644 --- a/uv.lock +++ b/uv.lock @@ -211,6 +211,19 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/e4/0e/38cb7b781371e79e9c697fb78f3ccd18fda8bd547d0a2e76e616561a3792/auth0_python-4.7.2-py3-none-any.whl", hash = "sha256:df2224f9b1e170b3aa12d8bc7ff02eadb7cc229307a09ec6b8a55fd1e0e05dc8", size = 131834 }, ] +[[package]] +name = "autoflake" +version = "2.3.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pyflakes" }, + { name = "tomli", marker = "python_full_version < '3.11'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/2a/cb/486f912d6171bc5748c311a2984a301f4e2d054833a1da78485866c71522/autoflake-2.3.1.tar.gz", hash = "sha256:c98b75dc5b0a86459c4f01a1d32ac7eb4338ec4317a4469515ff1e687ecd909e", size = 27642 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a2/ee/3fd29bf416eb4f1c5579cf12bf393ae954099258abd7bde03c4f9716ef6b/autoflake-2.3.1-py3-none-any.whl", hash = "sha256:3ae7495db9084b7b32818b4140e6dc4fc280b712fb414f5b8fe57b0a8e85a840", size = 32483 }, +] + [[package]] name = "babel" version = "2.16.0" @@ -604,6 +617,9 @@ dependencies = [ agentops = [ { name = "agentops" }, ] +docling = [ + { name = "docling" }, +] fastembed = [ { name = "fastembed" }, ] @@ -652,6 +668,7 @@ requires-dist = [ { name = "chromadb", specifier = ">=0.5.23" }, { name = "click", specifier = ">=8.1.7" }, { name = "crewai-tools", marker = "extra == 'tools'", specifier = ">=0.17.0" }, + { name = "docling", marker = "extra == 'docling'", specifier = ">=2.12.0" }, { name = "fastembed", marker = "extra == 'fastembed'", specifier = ">=0.4.1" }, { name = "instructor", specifier = ">=1.3.3" }, { name = "json-repair", specifier = ">=0.25.2" }, @@ -788,6 +805,33 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/d5/50/83c593b07763e1161326b3b8c6686f0f4b0f24d5526546bee538c89837d6/decorator-5.1.1-py3-none-any.whl", hash = "sha256:b8c3f85900b9dc423225913c5aace94729fe1fa9763b38939a95226f02d37186", size = 9073 }, ] +[[package]] +name = "deepsearch-glm" +version = "1.0.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pywin32", marker = "sys_platform == 'win32'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/73/d5/a907234e57f5c4f6480c9ddbc3cdacc47f727c768e502be3d361719fac4e/deepsearch_glm-1.0.0.tar.gz", hash = "sha256:e8dce88ac519a693c260f28bd3c4ec409811e65ade84fb508f6c6e37ca065e62", size = 2401014 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/40/65/4b2013784d5ed8d3664a2efa61f15600c8bf090766b0363c036d78aca550/deepsearch_glm-1.0.0-cp310-cp310-macosx_13_0_x86_64.whl", hash = "sha256:94792b57df7a1c4ba8b47ebd8f36ea0a090d4f27a4fba39bd7b166b6b537260a", size = 6303790 }, + { url = "https://files.pythonhosted.org/packages/45/2a/1e95260a712948a21b74dcb239032d9e612f7e1a273657008655749f4115/deepsearch_glm-1.0.0-cp310-cp310-macosx_14_0_arm64.whl", hash = "sha256:ff46e352e96a2f56ce7ae4fdf04b271ee841c29ff159b1dec0e5ecaaadba8d4d", size = 5945851 }, + { url = "https://files.pythonhosted.org/packages/9e/1a/5c37a98f27644fd02bc447df651e8d5ce484cd6ce7cb178218625b4de5bc/deepsearch_glm-1.0.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9d77d3d94d49641888aa15f3ad23e81158e791aa9d9608dd8168dc71788e56f3", size = 7431282 }, + { url = "https://files.pythonhosted.org/packages/e8/e2/56b5e7ae3ccc4d8ee758427c8c9a403c985e250a468c53538c269897bef2/deepsearch_glm-1.0.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:143de0fd111a570be12935d8799a2715fe1775d4dc4e256337860b429cee5d36", size = 7759571 }, + { url = "https://files.pythonhosted.org/packages/61/f4/e39a5090a2bf0d641449918865566ad5adabef156993a922bdbf4a3ebb60/deepsearch_glm-1.0.0-cp310-cp310-win_amd64.whl", hash = "sha256:9f2872dd573cd2206ce7f9e2e6016c38b66d9ecbd983283ff5e8c6023813c311", size = 7904646 }, + { url = "https://files.pythonhosted.org/packages/41/f7/8e8dd9738554f97522b59b0a6d7680ccf2d527bd3471ec4aa4e52acf552a/deepsearch_glm-1.0.0-cp311-cp311-macosx_13_0_x86_64.whl", hash = "sha256:e64d94ff5209f0a11e8c75c6b28b033ef27b95a22c2fbcbd945e7fe8cc421545", size = 6309301 }, + { url = "https://files.pythonhosted.org/packages/17/37/4d8514d8ef851e44513a71f675a7ebb373f109aece38e324c7d444ced20c/deepsearch_glm-1.0.0-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:a5702205677b768b51f881d15d933370f6ef3c826dfac3b9aa0b904d2e6c495a", size = 5951522 }, + { url = "https://files.pythonhosted.org/packages/0c/c6/3680318e66df278fa7f0811dc862d6cb3c328ce168b4f36736eb77120b6d/deepsearch_glm-1.0.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0417a2ae998e1709f03458cfb9adb55423bb1328224eb055300796baa757879f", size = 7434315 }, + { url = "https://files.pythonhosted.org/packages/c3/cd/9ffb616d347d568f868f47585b3261c16e277aa7b37740e8720eee71c539/deepsearch_glm-1.0.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6f0e1efe9af0d28e9b473fe599246deb3a0be7c3d546a478da284747144d086a", size = 7761264 }, + { url = "https://files.pythonhosted.org/packages/3d/d3/e5ebdda9cee8a1c846e6a960a0e5b97624aff2f248c2bc89ae490b9a1342/deepsearch_glm-1.0.0-cp311-cp311-win_amd64.whl", hash = "sha256:807faf13eb0deea55a1951d479a85d5e20de0ff8b2e0b57b2f7939552759a426", size = 7908603 }, + { url = "https://files.pythonhosted.org/packages/60/ca/6adbadc979910b11594cd0242f1991942c22528eead431d47de064ac2860/deepsearch_glm-1.0.0-cp312-cp312-macosx_13_0_x86_64.whl", hash = "sha256:56d9575df9eceb8c2ae33e3d15e133924cc195714c3d268599b6f8414c1f6bb8", size = 6308715 }, + { url = "https://files.pythonhosted.org/packages/20/7c/bf1e9c458705c7143c6630cb6847554ad694d25dc6f1f038512b9c86160a/deepsearch_glm-1.0.0-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:51f5c6522f60ba73eb12eeb7217bd98d871ba7c078337a4059d05878d8baf2d6", size = 5949609 }, + { url = "https://files.pythonhosted.org/packages/21/b1/eb0cd0db50d05f2d7a510a77960e85e6caee727eb3d931ed0ec067917813/deepsearch_glm-1.0.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c6211eaf497ad7cfcb68f80f9b5387940be0204fe149a9fc03988a95145f410a", size = 7433929 }, + { url = "https://files.pythonhosted.org/packages/3a/7e/2b7db77ff02fe9eec41f3605fcd72e3eb4e6b48561b344d432b417a75cfe/deepsearch_glm-1.0.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1b003bf457fce61ea4de79e2d7d0228a1ae349f677eb6570e745f79d4429804f", size = 7760438 }, + { url = "https://files.pythonhosted.org/packages/ab/97/ffb2bb5d2432c7b0e9f3a3e6b5873fbcd6e19e82b620393bfb8e01bdecb1/deepsearch_glm-1.0.0-cp312-cp312-win_amd64.whl", hash = "sha256:9d61f66048e6ab60fe9f84c823fd593bf8517755833bd9efb59156d77a2b42d0", size = 7907583 }, + { url = "https://files.pythonhosted.org/packages/1f/cd/e6507d924aa69e9647f917ed671e2d62e19e41d4f120a15fcbb583661667/deepsearch_glm-1.0.0-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:e2315cc4ffe7032dada294a0cd72a47dbc6c0121fd07d4b5719f9a9e9519d091", size = 14644989 }, +] + [[package]] name = "defusedxml" version = "0.7.1" @@ -821,6 +865,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/02/c3/253a89ee03fc9b9682f1541728eb66db7db22148cd94f89ab22528cd1e1b/deprecation-2.1.0-py2.py3-none-any.whl", hash = "sha256:a10811591210e1fb0e768a8c25517cabeabcba6f0bf96564f8ff45189f90b14a", size = 11178 }, ] +[[package]] +name = "dill" +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 } +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.9" @@ -853,6 +906,116 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/e3/26/57c6fb270950d476074c087527a558ccb6f4436657314bfb6cdf484114c4/docker-7.1.0-py3-none-any.whl", hash = "sha256:c96b93b7f0a746f9e77d325bcfb87422a3d8bd4f03136ae8a85b37f1898d5fc0", size = 147774 }, ] +[[package]] +name = "docling" +version = "2.12.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "beautifulsoup4" }, + { name = "certifi" }, + { name = "deepsearch-glm" }, + { name = "docling-core", extra = ["chunking"] }, + { name = "docling-ibm-models" }, + { name = "docling-parse" }, + { name = "easyocr" }, + { name = "filetype" }, + { name = "huggingface-hub" }, + { name = "lxml" }, + { name = "marko" }, + { name = "openpyxl" }, + { name = "pandas" }, + { name = "pydantic" }, + { name = "pydantic-settings" }, + { name = "pypdfium2" }, + { name = "python-docx" }, + { name = "python-pptx" }, + { name = "requests" }, + { name = "rtree" }, + { name = "scipy" }, + { name = "typer" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/e1/05/b344f2858016efe930b9ea53e45751066a3d7c4d7a5fcb382ebcac6594e5/docling-2.12.0.tar.gz", hash = "sha256:19e3cdf6c805bc1bf40d8f22ce06aef16de072de66f257eca907d1fc59bde742", size = 74811 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f9/78/f42d34dd0a5902d6967d232cd773363d410652e4748676120858f7b0b87e/docling-2.12.0-py3-none-any.whl", hash = "sha256:6dc79b2e33ab4dc0a5048090164a61204c589afc1e1769333a3c93b94a54a04e", size = 98258 }, +] + +[[package]] +name = "docling-core" +version = "2.10.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "jsonref" }, + { name = "jsonschema" }, + { name = "pandas" }, + { name = "pillow" }, + { name = "pydantic" }, + { name = "pyyaml" }, + { name = "tabulate" }, + { name = "typer" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/0e/6f/6b8e191041537103409541511864f8b32a7a8f555a7027269a98e6938429/docling_core-2.10.0.tar.gz", hash = "sha256:f9b33074de048afb4cb6be784d52f97f8723d1d41737096e575629e0bb30add8", size = 70414 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/97/9c/10349929b432f6c75eba5a07a9a8cb3a53e812db457d66f84e460ab14935/docling_core-2.10.0-py3-none-any.whl", hash = "sha256:b4fe310cd0f1edde7d727e15cb39f8b5a31d2bd5b1ac5af3f4670ac5209c9057", size = 90559 }, +] + +[package.optional-dependencies] +chunking = [ + { name = "semchunk" }, + { name = "transformers" }, +] + +[[package]] +name = "docling-ibm-models" +version = "3.1.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "huggingface-hub" }, + { name = "jsonlines" }, + { name = "numpy" }, + { name = "opencv-python-headless" }, + { name = "pillow" }, + { name = "safetensors", extra = ["torch"] }, + { name = "torch" }, + { name = "torchvision" }, + { name = "tqdm" }, + { name = "transformers" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/01/0b/5e6928b63480bbb0db5cfa570dfbfe31b5844cb35313ca5bf6192bf60c08/docling_ibm_models-3.1.0.tar.gz", hash = "sha256:65d734ffa490edc4e2301d296b6e893afa536c63b7daae7bbda781bd15b3431e", size = 58731 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/51/ef/d0f86b3017051126188d7445edb471d066414fa9fee07fed86193b0f3e10/docling_ibm_models-3.1.0-py3-none-any.whl", hash = "sha256:a381a45dff16fdb2246b99c15a2e3d6ba880c573d48a1d6477d3ffb36bab807f", size = 65910 }, +] + +[[package]] +name = "docling-parse" +version = "3.0.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "autoflake" }, + { name = "pillow" }, + { name = "pywin32", marker = "sys_platform == 'win32'" }, + { name = "tabulate" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/44/00/470cb9e2eaea562b7a7c8d61f70cee22eecfe1cc3a02848cead9ded95048/docling_parse-3.0.0.tar.gz", hash = "sha256:62a50d0fc4bb437ba840fb0419a466361d93071f300ae5f0cebe9b842ef0c8d4", size = 34665630 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b0/1a/644481461387f7221752cd1d35b411747f806a1229a62bf6ffa52b3f03a7/docling_parse-3.0.0-cp310-cp310-macosx_13_0_x86_64.whl", hash = "sha256:8de583f9562549379b8878f4054c17a715ac492999187855a6178c258388d1c6", size = 22023624 }, + { url = "https://files.pythonhosted.org/packages/21/cf/7191ec8b9b2276b96b06dec36c5a83fe9631a5046f15a680e8d0225df2c9/docling_parse-3.0.0-cp310-cp310-macosx_14_0_arm64.whl", hash = "sha256:0a504152836b52119c84ce6f2124006b2297eca9576c1e961745f774b8f55f59", size = 21909089 }, + { url = "https://files.pythonhosted.org/packages/d2/d1/8bbe6dbc2c4065f0e9aa833b73fb6d5eb3bd3fecf50491f490ab829a6855/docling_parse-3.0.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e73836d75127b168073e76a4170ec615ee49d6d46ac37d1a3f9d5c585b2c4363", size = 22350328 }, + { url = "https://files.pythonhosted.org/packages/1b/30/fe8fc2a28000a7045ab29f7b38810b4f90f4e25b6009676fc48ad20cf937/docling_parse-3.0.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1fdff7e14e50c0f66350346082f1fdf6cbc0584bef809532075593fa0c2a2ab2", size = 22416774 }, + { url = "https://files.pythonhosted.org/packages/cb/dd/2f74b66529ffc44e6ede99345783ed5200a568d05d2e1958d018d75072a7/docling_parse-3.0.0-cp310-cp310-win_amd64.whl", hash = "sha256:f56ae44328f7242e7420330d3d737d5284ec256af8ecd0b02fe6e34719b3040a", size = 23215427 }, + { url = "https://files.pythonhosted.org/packages/e9/e9/6acb98d30633b04bb4ab522154d7491c04e4e21eedb8ba6270672a5074fa/docling_parse-3.0.0-cp311-cp311-macosx_13_0_x86_64.whl", hash = "sha256:f228587e0d3a8f46fec46934e324d74be90d7f1ad96579c775644b130f28acdb", size = 22025643 }, + { url = "https://files.pythonhosted.org/packages/18/15/ac79595f38f743bbf501a4911f4c196d0c95c8c5ab773e2393c20742e41f/docling_parse-3.0.0-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:25da7fa46449386956906f04cad5e9bec87816c00146caaef1112c8cdda6b79c", size = 21910337 }, + { url = "https://files.pythonhosted.org/packages/e1/79/8bfd1de8db8fecd8b45cbc13e5d6d4d6e8bb1eec173d1cdca725b28d1aa3/docling_parse-3.0.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:787c200081af2fb2d267d8f404a1b57464ee2fbcda4abd8d7bab99244c1716cb", size = 22351347 }, + { url = "https://files.pythonhosted.org/packages/34/42/8e513f48b7a91f824abe3c7744f3ab86d3be6050363a8d591abd42c2a500/docling_parse-3.0.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:be7a28e7a3ae6e198722dbb29341956c565ab9d8fdbddaee91f81dc21d870dde", size = 22417964 }, + { url = "https://files.pythonhosted.org/packages/54/c9/308f994ad5df170dd7f609f763d374d67618e0228ead8fd8e1a938f35378/docling_parse-3.0.0-cp311-cp311-win_amd64.whl", hash = "sha256:4251888da7c0ff946ce77ea8f14a0896ffe24b79422155db5871b7ee1b9fbc0a", size = 23216577 }, + { url = "https://files.pythonhosted.org/packages/6b/c3/cadfde769f9db1bb81eefceaa47238791ab19d1a27cff72d5c5226010e64/docling_parse-3.0.0-cp312-cp312-macosx_13_0_x86_64.whl", hash = "sha256:642e47bdf090b89766e035b74cc849abffe0df520f2907ff4dede5c819b31d4a", size = 22025947 }, + { url = "https://files.pythonhosted.org/packages/78/e7/009e2fc15e9c3b5888ccfc50458b0e814890aea5934a84f1da1a2243de6b/docling_parse-3.0.0-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:731de22e279af1505f962dc10102b6405bcaac3d855657bf3542048e7182b440", size = 21909782 }, + { url = "https://files.pythonhosted.org/packages/e2/98/84987768c8a4d3c060873f0f3822723c44123877c3fd2d413e0a009bd0c1/docling_parse-3.0.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:afd553a715e6282fc5aadd3bfd402faab4e43b77f4952bd065e3941218118f39", size = 22350717 }, + { url = "https://files.pythonhosted.org/packages/7f/ef/b473b9900b5749013d41071ea10320ce4d1c3fa46329435c864df66ca6b8/docling_parse-3.0.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6cfb02830a918958a47144ca13ce985f09578a353c97da941935591e8917f432", size = 22417413 }, + { url = "https://files.pythonhosted.org/packages/c3/79/449ef8095543cd43da20af7b425ae7712f586174e5e990eccf9f760a8357/docling_parse-3.0.0-cp312-cp312-win_amd64.whl", hash = "sha256:85ca7610e5debcfc37e7b6311f4fc7c62c9d0eeea11b8bf2b33a760e65dd64fe", size = 23217163 }, + { url = "https://files.pythonhosted.org/packages/0b/e2/160f43afc3742ca5ac8122d5f36630ce47afb4ad42f5a490fb0ee0ac5c0c/docling_parse-3.0.0-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:1ba1c3469a38b404123bb615e220c046496d5d47e161cc5af7ae749e8cf181ab", size = 24989806 }, +] + [[package]] name = "docstring-parser" version = "0.16" @@ -877,6 +1040,28 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/4c/a3/ac312faeceffd2d8f86bc6dcb5c401188ba5a01bc88e69bed97578a0dfcd/durationpy-0.9-py3-none-any.whl", hash = "sha256:e65359a7af5cedad07fb77a2dd3f390f8eb0b74cb845589fa6c057086834dd38", size = 3461 }, ] +[[package]] +name = "easyocr" +version = "1.7.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "ninja" }, + { name = "numpy" }, + { name = "opencv-python-headless" }, + { name = "pillow" }, + { name = "pyclipper" }, + { name = "python-bidi" }, + { name = "pyyaml" }, + { name = "scikit-image" }, + { name = "scipy" }, + { name = "shapely" }, + { name = "torch" }, + { name = "torchvision" }, +] +wheels = [ + { url = "https://files.pythonhosted.org/packages/bb/84/4a2cab0e6adde6a85e7ba543862e5fc0250c51f3ac721a078a55cdcff250/easyocr-1.7.2-py3-none-any.whl", hash = "sha256:5be12f9b0e595d443c9c3d10b0542074b50f0ec2d98b141a109cd961fd1c177c", size = 2870178 }, +] + [[package]] name = "embedchain" version = "0.1.125" @@ -1007,6 +1192,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/b9/f8/feced7779d755758a52d1f6635d990b8d98dc0a29fa568bbe0625f18fdf3/filelock-3.16.1-py3-none-any.whl", hash = "sha256:2082e5703d51fbf98ea75855d9d5527e33d8ff23099bec374a134febee6946b0", size = 16163 }, ] +[[package]] +name = "filetype" +version = "1.2.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/bb/29/745f7d30d47fe0f251d3ad3dc2978a23141917661998763bebb6da007eb1/filetype-1.2.0.tar.gz", hash = "sha256:66b56cd6474bf41d8c54660347d37afcc3f7d1970648de365c102ef77548aadb", size = 998020 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/18/79/1b8fa1bb3568781e84c9200f951c735f3f157429f44be0495da55894d620/filetype-1.2.0-py2.py3-none-any.whl", hash = "sha256:7ce71b6880181241cf7ac8697a2f1eb6a8bd9b429f7ad6d27b8db9ba5f1c2d25", size = 19970 }, +] + [[package]] name = "flatbuffers" version = "24.3.25" @@ -1589,6 +1783,19 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/76/c6/c88e154df9c4e1a2a66ccf0005a88dfb2650c1dffb6f5ce603dfbd452ce3/idna-3.10-py3-none-any.whl", hash = "sha256:946d195a0d259cbba61165e88e65941f16e9b36ea6ddb97f00452bae8b1287d3", size = 70442 }, ] +[[package]] +name = "imageio" +version = "2.36.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "numpy" }, + { name = "pillow" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/70/aa/2e7a49259339e691ff2b477ae0696b1784a09313c5872700bbbdd00a3030/imageio-2.36.1.tar.gz", hash = "sha256:e4e1d231f47f9a9e16100b0f7ce1a86e8856fb4d1c0fa2c4365a316f1746be62", size = 389522 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/5c/f9/f78e7f5ac8077c481bf6b43b8bc736605363034b3d5eb3ce8eb79f53f5f1/imageio-2.36.1-py3-none-any.whl", hash = "sha256:20abd2cae58e55ca1af8a8dcf43293336a59adf0391f1917bf8518633cfc2cdf", size = 315435 }, +] + [[package]] name = "importlib-metadata" version = "8.4.0" @@ -1739,6 +1946,18 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/23/38/34cb843cee4c5c27aa5c822e90e99bf96feb3dfa705713b5b6e601d17f5c/json_repair-0.30.0-py3-none-any.whl", hash = "sha256:bda4a5552dc12085c6363ff5acfcdb0c9cafc629989a2112081b7e205828228d", size = 17641 }, ] +[[package]] +name = "jsonlines" +version = "3.1.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "attrs" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/2a/c8/efdb87403dae07cf20faf75449eae41898b71d6a8d4ebaf9c80d5be215f5/jsonlines-3.1.0.tar.gz", hash = "sha256:2579cb488d96f815b0eb81629e3e6b0332da0962a18fa3532958f7ba14a5c37f", size = 8510 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/68/32/290ca20eb3a2b97ffa6ba1791fcafacb3cd2f41f539c96eb54cfc3cfcf47/jsonlines-3.1.0-py3-none-any.whl", hash = "sha256:632f5e38f93dfcb1ac8c4e09780b92af3a55f38f26e7c47ae85109d420b6ad39", size = 8592 }, +] + [[package]] name = "jsonpatch" version = "1.33" @@ -1986,6 +2205,18 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/71/fd/7713b0e737f4e171112e44134790823ccec4aabe31f07d6e836fcbeb3b8a/langsmith-0.1.137-py3-none-any.whl", hash = "sha256:4256d5c61133749890f7b5c88321dbb133ce0f440c621ea28e76513285859b81", size = 296895 }, ] +[[package]] +name = "lazy-loader" +version = "0.4" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "packaging" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/6f/6b/c875b30a1ba490860c93da4cabf479e03f584eba06fe5963f6f6644653d8/lazy_loader-0.4.tar.gz", hash = "sha256:47c75182589b91a4e1a85a136c074285a5ad4d9f39c63e0d7fb76391c4574cd1", size = 15431 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/83/60/d497a310bde3f01cb805196ac61b7ad6dc5dcf8dce66634dc34364b20b4f/lazy_loader-0.4-py3-none-any.whl", hash = "sha256:342aa8e14d543a154047afb4ba8ef17f5563baad3fc610d7b15b213b0f119efc", size = 12097 }, +] + [[package]] name = "litellm" version = "1.50.2" @@ -2021,6 +2252,71 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/03/0a/4f6fed21aa246c6b49b561ca55facacc2a44b87d65b8b92362a8e99ba202/loguru-0.7.2-py3-none-any.whl", hash = "sha256:003d71e3d3ed35f0f8984898359d65b79e5b21943f78af86aa5491210429b8eb", size = 62549 }, ] +[[package]] +name = "lxml" +version = "5.3.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/e7/6b/20c3a4b24751377aaa6307eb230b66701024012c29dd374999cc92983269/lxml-5.3.0.tar.gz", hash = "sha256:4e109ca30d1edec1ac60cdbe341905dc3b8f55b16855e03a54aaf59e51ec8c6f", size = 3679318 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a1/ce/2789e39eddf2b13fac29878bfa465f0910eb6b0096e29090e5176bc8cf43/lxml-5.3.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:dd36439be765e2dde7660212b5275641edbc813e7b24668831a5c8ac91180656", size = 8124570 }, + { url = "https://files.pythonhosted.org/packages/24/a8/f4010166a25d41715527129af2675981a50d3bbf7df09c5d9ab8ca24fbf9/lxml-5.3.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:ae5fe5c4b525aa82b8076c1a59d642c17b6e8739ecf852522c6321852178119d", size = 4413042 }, + { url = "https://files.pythonhosted.org/packages/41/a4/7e45756cecdd7577ddf67a68b69c1db0f5ddbf0c9f65021ee769165ffc5a/lxml-5.3.0-cp310-cp310-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:501d0d7e26b4d261fca8132854d845e4988097611ba2531408ec91cf3fd9d20a", size = 5139213 }, + { url = "https://files.pythonhosted.org/packages/02/e2/ecf845b12323c92748077e1818b64e8b4dba509a4cb12920b3762ebe7552/lxml-5.3.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fb66442c2546446944437df74379e9cf9e9db353e61301d1a0e26482f43f0dd8", size = 4838814 }, + { url = "https://files.pythonhosted.org/packages/12/91/619f9fb72cf75e9ceb8700706f7276f23995f6ad757e6d400fbe35ca4990/lxml-5.3.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9e41506fec7a7f9405b14aa2d5c8abbb4dbbd09d88f9496958b6d00cb4d45330", size = 5425084 }, + { url = "https://files.pythonhosted.org/packages/25/3b/162a85a8f0fd2a3032ec3f936636911c6e9523a8e263fffcfd581ce98b54/lxml-5.3.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f7d4a670107d75dfe5ad080bed6c341d18c4442f9378c9f58e5851e86eb79965", size = 4875993 }, + { url = "https://files.pythonhosted.org/packages/43/af/dd3f58cc7d946da6ae42909629a2b1d5dd2d1b583334d4af9396697d6863/lxml-5.3.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:41ce1f1e2c7755abfc7e759dc34d7d05fd221723ff822947132dc934d122fe22", size = 5012462 }, + { url = "https://files.pythonhosted.org/packages/69/c1/5ea46b2d4c98f5bf5c83fffab8a0ad293c9bc74df9ecfbafef10f77f7201/lxml-5.3.0-cp310-cp310-manylinux_2_28_aarch64.whl", hash = "sha256:44264ecae91b30e5633013fb66f6ddd05c006d3e0e884f75ce0b4755b3e3847b", size = 4815288 }, + { url = "https://files.pythonhosted.org/packages/1d/51/a0acca077ad35da458f4d3f729ef98effd2b90f003440d35fc36323f8ae6/lxml-5.3.0-cp310-cp310-manylinux_2_28_ppc64le.whl", hash = "sha256:3c174dc350d3ec52deb77f2faf05c439331d6ed5e702fc247ccb4e6b62d884b7", size = 5472435 }, + { url = "https://files.pythonhosted.org/packages/4d/6b/0989c9368986961a6b0f55b46c80404c4b758417acdb6d87bfc3bd5f4967/lxml-5.3.0-cp310-cp310-manylinux_2_28_s390x.whl", hash = "sha256:2dfab5fa6a28a0b60a20638dc48e6343c02ea9933e3279ccb132f555a62323d8", size = 4976354 }, + { url = "https://files.pythonhosted.org/packages/05/9e/87492d03ff604fbf656ed2bf3e2e8d28f5d58ea1f00ff27ac27b06509079/lxml-5.3.0-cp310-cp310-manylinux_2_28_x86_64.whl", hash = "sha256:b1c8c20847b9f34e98080da785bb2336ea982e7f913eed5809e5a3c872900f32", size = 5029973 }, + { url = "https://files.pythonhosted.org/packages/f9/cc/9ae1baf5472af88e19e2c454b3710c1be9ecafb20eb474eeabcd88a055d2/lxml-5.3.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:2c86bf781b12ba417f64f3422cfc302523ac9cd1d8ae8c0f92a1c66e56ef2e86", size = 4888837 }, + { url = "https://files.pythonhosted.org/packages/d2/10/5594ffaec8c120d75b17e3ad23439b740a51549a9b5fd7484b2179adfe8f/lxml-5.3.0-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:c162b216070f280fa7da844531169be0baf9ccb17263cf5a8bf876fcd3117fa5", size = 5530555 }, + { url = "https://files.pythonhosted.org/packages/ea/9b/de17f05377c8833343b629905571fb06cff2028f15a6f58ae2267662e341/lxml-5.3.0-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:36aef61a1678cb778097b4a6eeae96a69875d51d1e8f4d4b491ab3cfb54b5a03", size = 5405314 }, + { url = "https://files.pythonhosted.org/packages/8a/b4/227be0f1f3cca8255925985164c3838b8b36e441ff0cc10c1d3c6bdba031/lxml-5.3.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:f65e5120863c2b266dbcc927b306c5b78e502c71edf3295dfcb9501ec96e5fc7", size = 5079303 }, + { url = "https://files.pythonhosted.org/packages/5c/ee/19abcebb7fc40319bb71cd6adefa1ad94d09b5660228715854d6cc420713/lxml-5.3.0-cp310-cp310-win32.whl", hash = "sha256:ef0c1fe22171dd7c7c27147f2e9c3e86f8bdf473fed75f16b0c2e84a5030ce80", size = 3475126 }, + { url = "https://files.pythonhosted.org/packages/a1/35/183d32551447e280032b2331738cd850da435a42f850b71ebeaab42c1313/lxml-5.3.0-cp310-cp310-win_amd64.whl", hash = "sha256:052d99051e77a4f3e8482c65014cf6372e61b0a6f4fe9edb98503bb5364cfee3", size = 3805065 }, + { url = "https://files.pythonhosted.org/packages/5c/a8/449faa2a3cbe6a99f8d38dcd51a3ee8844c17862841a6f769ea7c2a9cd0f/lxml-5.3.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:74bcb423462233bc5d6066e4e98b0264e7c1bed7541fff2f4e34fe6b21563c8b", size = 8141056 }, + { url = "https://files.pythonhosted.org/packages/ac/8a/ae6325e994e2052de92f894363b038351c50ee38749d30cc6b6d96aaf90f/lxml-5.3.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:a3d819eb6f9b8677f57f9664265d0a10dd6551d227afb4af2b9cd7bdc2ccbf18", size = 4425238 }, + { url = "https://files.pythonhosted.org/packages/f8/fb/128dddb7f9086236bce0eeae2bfb316d138b49b159f50bc681d56c1bdd19/lxml-5.3.0-cp311-cp311-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5b8f5db71b28b8c404956ddf79575ea77aa8b1538e8b2ef9ec877945b3f46442", size = 5095197 }, + { url = "https://files.pythonhosted.org/packages/b4/f9/a181a8ef106e41e3086629c8bdb2d21a942f14c84a0e77452c22d6b22091/lxml-5.3.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2c3406b63232fc7e9b8783ab0b765d7c59e7c59ff96759d8ef9632fca27c7ee4", size = 4809809 }, + { url = "https://files.pythonhosted.org/packages/25/2f/b20565e808f7f6868aacea48ddcdd7e9e9fb4c799287f21f1a6c7c2e8b71/lxml-5.3.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:2ecdd78ab768f844c7a1d4a03595038c166b609f6395e25af9b0f3f26ae1230f", size = 5407593 }, + { url = "https://files.pythonhosted.org/packages/23/0e/caac672ec246d3189a16c4d364ed4f7d6bf856c080215382c06764058c08/lxml-5.3.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:168f2dfcfdedf611eb285efac1516c8454c8c99caf271dccda8943576b67552e", size = 4866657 }, + { url = "https://files.pythonhosted.org/packages/67/a4/1f5fbd3f58d4069000522196b0b776a014f3feec1796da03e495cf23532d/lxml-5.3.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:aa617107a410245b8660028a7483b68e7914304a6d4882b5ff3d2d3eb5948d8c", size = 4967017 }, + { url = "https://files.pythonhosted.org/packages/ee/73/623ecea6ca3c530dd0a4ed0d00d9702e0e85cd5624e2d5b93b005fe00abd/lxml-5.3.0-cp311-cp311-manylinux_2_28_aarch64.whl", hash = "sha256:69959bd3167b993e6e710b99051265654133a98f20cec1d9b493b931942e9c16", size = 4810730 }, + { url = "https://files.pythonhosted.org/packages/1d/ce/fb84fb8e3c298f3a245ae3ea6221c2426f1bbaa82d10a88787412a498145/lxml-5.3.0-cp311-cp311-manylinux_2_28_ppc64le.whl", hash = "sha256:bd96517ef76c8654446fc3db9242d019a1bb5fe8b751ba414765d59f99210b79", size = 5455154 }, + { url = "https://files.pythonhosted.org/packages/b1/72/4d1ad363748a72c7c0411c28be2b0dc7150d91e823eadad3b91a4514cbea/lxml-5.3.0-cp311-cp311-manylinux_2_28_s390x.whl", hash = "sha256:ab6dd83b970dc97c2d10bc71aa925b84788c7c05de30241b9e96f9b6d9ea3080", size = 4969416 }, + { url = "https://files.pythonhosted.org/packages/42/07/b29571a58a3a80681722ea8ed0ba569211d9bb8531ad49b5cacf6d409185/lxml-5.3.0-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:eec1bb8cdbba2925bedc887bc0609a80e599c75b12d87ae42ac23fd199445654", size = 5013672 }, + { url = "https://files.pythonhosted.org/packages/b9/93/bde740d5a58cf04cbd38e3dd93ad1e36c2f95553bbf7d57807bc6815d926/lxml-5.3.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:6a7095eeec6f89111d03dabfe5883a1fd54da319c94e0fb104ee8f23616b572d", size = 4878644 }, + { url = "https://files.pythonhosted.org/packages/56/b5/645c8c02721d49927c93181de4017164ec0e141413577687c3df8ff0800f/lxml-5.3.0-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:6f651ebd0b21ec65dfca93aa629610a0dbc13dbc13554f19b0113da2e61a4763", size = 5511531 }, + { url = "https://files.pythonhosted.org/packages/85/3f/6a99a12d9438316f4fc86ef88c5d4c8fb674247b17f3173ecadd8346b671/lxml-5.3.0-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:f422a209d2455c56849442ae42f25dbaaba1c6c3f501d58761c619c7836642ec", size = 5402065 }, + { url = "https://files.pythonhosted.org/packages/80/8a/df47bff6ad5ac57335bf552babfb2408f9eb680c074ec1ba412a1a6af2c5/lxml-5.3.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:62f7fdb0d1ed2065451f086519865b4c90aa19aed51081979ecd05a21eb4d1be", size = 5069775 }, + { url = "https://files.pythonhosted.org/packages/08/ae/e7ad0f0fbe4b6368c5ee1e3ef0c3365098d806d42379c46c1ba2802a52f7/lxml-5.3.0-cp311-cp311-win32.whl", hash = "sha256:c6379f35350b655fd817cd0d6cbeef7f265f3ae5fedb1caae2eb442bbeae9ab9", size = 3474226 }, + { url = "https://files.pythonhosted.org/packages/c3/b5/91c2249bfac02ee514ab135e9304b89d55967be7e53e94a879b74eec7a5c/lxml-5.3.0-cp311-cp311-win_amd64.whl", hash = "sha256:9c52100e2c2dbb0649b90467935c4b0de5528833c76a35ea1a2691ec9f1ee7a1", size = 3814971 }, + { url = "https://files.pythonhosted.org/packages/eb/6d/d1f1c5e40c64bf62afd7a3f9b34ce18a586a1cccbf71e783cd0a6d8e8971/lxml-5.3.0-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:e99f5507401436fdcc85036a2e7dc2e28d962550afe1cbfc07c40e454256a859", size = 8171753 }, + { url = "https://files.pythonhosted.org/packages/bd/83/26b1864921869784355459f374896dcf8b44d4af3b15d7697e9156cb2de9/lxml-5.3.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:384aacddf2e5813a36495233b64cb96b1949da72bef933918ba5c84e06af8f0e", size = 4441955 }, + { url = "https://files.pythonhosted.org/packages/e0/d2/e9bff9fb359226c25cda3538f664f54f2804f4b37b0d7c944639e1a51f69/lxml-5.3.0-cp312-cp312-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:874a216bf6afaf97c263b56371434e47e2c652d215788396f60477540298218f", size = 5050778 }, + { url = "https://files.pythonhosted.org/packages/88/69/6972bfafa8cd3ddc8562b126dd607011e218e17be313a8b1b9cc5a0ee876/lxml-5.3.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:65ab5685d56914b9a2a34d67dd5488b83213d680b0c5d10b47f81da5a16b0b0e", size = 4748628 }, + { url = "https://files.pythonhosted.org/packages/5d/ea/a6523c7c7f6dc755a6eed3d2f6d6646617cad4d3d6d8ce4ed71bfd2362c8/lxml-5.3.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:aac0bbd3e8dd2d9c45ceb82249e8bdd3ac99131a32b4d35c8af3cc9db1657179", size = 5322215 }, + { url = "https://files.pythonhosted.org/packages/99/37/396fbd24a70f62b31d988e4500f2068c7f3fd399d2fd45257d13eab51a6f/lxml-5.3.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b369d3db3c22ed14c75ccd5af429086f166a19627e84a8fdade3f8f31426e52a", size = 4813963 }, + { url = "https://files.pythonhosted.org/packages/09/91/e6136f17459a11ce1757df864b213efbeab7adcb2efa63efb1b846ab6723/lxml-5.3.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c24037349665434f375645fa9d1f5304800cec574d0310f618490c871fd902b3", size = 4923353 }, + { url = "https://files.pythonhosted.org/packages/1d/7c/2eeecf87c9a1fca4f84f991067c693e67340f2b7127fc3eca8fa29d75ee3/lxml-5.3.0-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:62d172f358f33a26d6b41b28c170c63886742f5b6772a42b59b4f0fa10526cb1", size = 4740541 }, + { url = "https://files.pythonhosted.org/packages/3b/ed/4c38ba58defca84f5f0d0ac2480fdcd99fc7ae4b28fc417c93640a6949ae/lxml-5.3.0-cp312-cp312-manylinux_2_28_ppc64le.whl", hash = "sha256:c1f794c02903c2824fccce5b20c339a1a14b114e83b306ff11b597c5f71a1c8d", size = 5346504 }, + { url = "https://files.pythonhosted.org/packages/a5/22/bbd3995437e5745cb4c2b5d89088d70ab19d4feabf8a27a24cecb9745464/lxml-5.3.0-cp312-cp312-manylinux_2_28_s390x.whl", hash = "sha256:5d6a6972b93c426ace71e0be9a6f4b2cfae9b1baed2eed2006076a746692288c", size = 4898077 }, + { url = "https://files.pythonhosted.org/packages/0a/6e/94537acfb5b8f18235d13186d247bca478fea5e87d224644e0fe907df976/lxml-5.3.0-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:3879cc6ce938ff4eb4900d901ed63555c778731a96365e53fadb36437a131a99", size = 4946543 }, + { url = "https://files.pythonhosted.org/packages/8d/e8/4b15df533fe8e8d53363b23a41df9be907330e1fa28c7ca36893fad338ee/lxml-5.3.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:74068c601baff6ff021c70f0935b0c7bc528baa8ea210c202e03757c68c5a4ff", size = 4816841 }, + { url = "https://files.pythonhosted.org/packages/1a/e7/03f390ea37d1acda50bc538feb5b2bda6745b25731e4e76ab48fae7106bf/lxml-5.3.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:ecd4ad8453ac17bc7ba3868371bffb46f628161ad0eefbd0a855d2c8c32dd81a", size = 5417341 }, + { url = "https://files.pythonhosted.org/packages/ea/99/d1133ab4c250da85a883c3b60249d3d3e7c64f24faff494cf0fd23f91e80/lxml-5.3.0-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:7e2f58095acc211eb9d8b5771bf04df9ff37d6b87618d1cbf85f92399c98dae8", size = 5327539 }, + { url = "https://files.pythonhosted.org/packages/7d/ed/e6276c8d9668028213df01f598f385b05b55a4e1b4662ee12ef05dab35aa/lxml-5.3.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:e63601ad5cd8f860aa99d109889b5ac34de571c7ee902d6812d5d9ddcc77fa7d", size = 5012542 }, + { url = "https://files.pythonhosted.org/packages/36/88/684d4e800f5aa28df2a991a6a622783fb73cf0e46235cfa690f9776f032e/lxml-5.3.0-cp312-cp312-win32.whl", hash = "sha256:17e8d968d04a37c50ad9c456a286b525d78c4a1c15dd53aa46c1d8e06bf6fa30", size = 3486454 }, + { url = "https://files.pythonhosted.org/packages/fc/82/ace5a5676051e60355bd8fb945df7b1ba4f4fb8447f2010fb816bfd57724/lxml-5.3.0-cp312-cp312-win_amd64.whl", hash = "sha256:c1a69e58a6bb2de65902051d57fde951febad631a20a64572677a1052690482f", size = 3816857 }, + { url = "https://files.pythonhosted.org/packages/99/f7/b73a431c8500565aa500e99e60b448d305eaf7c0b4c893c7c5a8a69cc595/lxml-5.3.0-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:7b1cd427cb0d5f7393c31b7496419da594fe600e6fdc4b105a54f82405e6626c", size = 3925431 }, + { url = "https://files.pythonhosted.org/packages/db/48/4a206623c0d093d0e3b15f415ffb4345b0bdf661a3d0b15a112948c033c7/lxml-5.3.0-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:51806cfe0279e06ed8500ce19479d757db42a30fd509940b1701be9c86a5ff9a", size = 4216683 }, + { url = "https://files.pythonhosted.org/packages/54/47/577820c45dd954523ae8453b632d91e76da94ca6d9ee40d8c98dd86f916b/lxml-5.3.0-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ee70d08fd60c9565ba8190f41a46a54096afa0eeb8f76bd66f2c25d3b1b83005", size = 4326732 }, + { url = "https://files.pythonhosted.org/packages/68/de/96cb6d3269bc994b4f5ede8ca7bf0840f5de0a278bc6e50cb317ff71cafa/lxml-5.3.0-pp310-pypy310_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:8dc2c0395bea8254d8daebc76dcf8eb3a95ec2a46fa6fae5eaccee366bfe02ce", size = 4218377 }, + { url = "https://files.pythonhosted.org/packages/a5/43/19b1ef6cbffa4244a217f95cc5f41a6cb4720fed33510a49670b03c5f1a0/lxml-5.3.0-pp310-pypy310_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:6ba0d3dcac281aad8a0e5b14c7ed6f9fa89c8612b47939fc94f80b16e2e9bc83", size = 4351237 }, + { url = "https://files.pythonhosted.org/packages/ba/b2/6a22fb5c0885da3b00e116aee81f0b829ec9ac8f736cd414b4a09413fc7d/lxml-5.3.0-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:6e91cf736959057f7aac7adfc83481e03615a8e8dd5758aa1d95ea69e8931dba", size = 3487557 }, +] + [[package]] name = "mako" version = "1.3.6" @@ -2054,6 +2350,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/42/d7/1ec15b46af6af88f19b8e5ffea08fa375d433c998b8a7639e76935c14f1f/markdown_it_py-3.0.0-py3-none-any.whl", hash = "sha256:355216845c60bd96232cd8d8c40e8f9765cc86f46880e43a8fd22dc1a1a8cab1", size = 87528 }, ] +[[package]] +name = "marko" +version = "2.1.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/8c/38/6ea5d8600b94432656c669816a479580d9f1c49ef6b426282f4ba261ae9b/marko-2.1.2.tar.gz", hash = "sha256:a9170006b879376e6845c91b1ae3dce2992772954b99b70175ff888537186011", size = 142593 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ef/9b/3dbfbe6ee255b1c37a37e2a6046adb2e77763a020591dae63e5005a2c8d7/marko-2.1.2-py3-none-any.whl", hash = "sha256:c14aa7a77468aaaf53cf056dcd3d32398b9df4c3fb81f5e120dd37cbb9f8c859", size = 42089 }, +] + [[package]] name = "markupsafe" version = "3.0.2" @@ -2332,6 +2637,25 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/9a/67/7e8406a29b6c45be7af7740456f7f37025f0506ae2e05fb9009a53946860/monotonic-1.6-py2.py3-none-any.whl", hash = "sha256:68687e19a14f11f26d140dd5c86f3dba4bf5df58003000ed467e0e2a69bca96c", size = 8154 }, ] +[[package]] +name = "mpire" +version = "2.10.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pygments" }, + { name = "pywin32", marker = "platform_system == 'Windows'" }, + { name = "tqdm" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/3a/93/80ac75c20ce54c785648b4ed363c88f148bf22637e10c9863db4fbe73e74/mpire-2.10.2.tar.gz", hash = "sha256:f66a321e93fadff34585a4bfa05e95bd946cf714b442f51c529038eb45773d97", size = 271270 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/20/14/1db1729ad6db4999c3a16c47937d601fcb909aaa4224f5eca5a2f145a605/mpire-2.10.2-py3-none-any.whl", hash = "sha256:d627707f7a8d02aa4c7f7d59de399dec5290945ddf7fbd36cbb1d6ebb37a51fb", size = 272756 }, +] + +[package.optional-dependencies] +dill = [ + { name = "multiprocess" }, +] + [[package]] name = "mpmath" version = "1.3.0" @@ -2398,6 +2722,25 @@ 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/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.13.0" @@ -2445,6 +2788,30 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/b9/54/dd730b32ea14ea797530a4479b2ed46a6fb250f682a9cfb997e968bf0261/networkx-3.4.2-py3-none-any.whl", hash = "sha256:df5d4365b724cf81b8c6a7312509d0c22386097011ad1abe274afd5e9d3bbc5f", size = 1723263 }, ] +[[package]] +name = "ninja" +version = "1.11.1.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/bd/8f/21a2701f95b7d0d5137736561b3427ece0c4a1e085d4a223b92d16ab7d8b/ninja-1.11.1.3.tar.gz", hash = "sha256:edfa0d2e9d7ead1635b03e40a32ad56cc8f56798b6e2e9848d8300b174897076", size = 129532 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ea/ba/0069cd4a83d68f7b0308be70e219b15d675e50c8ea28763a3f0373c45bfc/ninja-1.11.1.3-py3-none-macosx_10_9_universal2.whl", hash = "sha256:2b4879ea3f1169f3d855182c57dcc84d1b5048628c8b7be0d702b81882a37237", size = 279132 }, + { url = "https://files.pythonhosted.org/packages/72/6b/3805be87df8417a0c7b21078c8045f2a1e59b34f371bfe4cb4fb0d6df7f2/ninja-1.11.1.3-py3-none-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:bc3ebc8b2e47716149f3541742b5cd8e0b08f51013b825c05baca3e34854370d", size = 472101 }, + { url = "https://files.pythonhosted.org/packages/6b/35/a8e38d54768e67324e365e2a41162be298f51ec93e6bd4b18d237d7250d8/ninja-1.11.1.3-py3-none-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:a27e78ca71316c8654965ee94b286a98c83877bfebe2607db96897bbfe458af0", size = 422884 }, + { url = "https://files.pythonhosted.org/packages/2f/99/7996457319e139c02697fb2aa28e42fe32bb0752cef492edc69d56a3552e/ninja-1.11.1.3-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2883ea46b3c5079074f56820f9989c6261fcc6fd873d914ee49010ecf283c3b2", size = 157046 }, + { url = "https://files.pythonhosted.org/packages/6d/8b/93f38e5cddf76ccfdab70946515b554f25d2b4c95ef9b2f9cfbc43fa7cc1/ninja-1.11.1.3-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8c4bdb9fd2d0c06501ae15abfd23407660e95659e384acd36e013b6dd7d8a8e4", size = 180014 }, + { url = "https://files.pythonhosted.org/packages/7d/1d/713884d0fa3c972164f69d552e0701d30e2bf25eba9ef160bfb3dc69926a/ninja-1.11.1.3-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:114ed5c61c8474df6a69ab89097a20749b769e2c219a452cb2fadc49b0d581b0", size = 157098 }, + { url = "https://files.pythonhosted.org/packages/c7/22/ecb0f70e77c9e22ee250aa717a608a142756833a34d43943d7d658ee0e56/ninja-1.11.1.3-py3-none-manylinux_2_28_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:7fa2247fce98f683bc712562d82b22b8a0a5c000738a13147ca2d1b68c122298", size = 130089 }, + { url = "https://files.pythonhosted.org/packages/ec/a6/3ee846c20ab6ad95b90c5c8703c76cb1f39cc8ce2d1ae468956e3b1b2581/ninja-1.11.1.3-py3-none-musllinux_1_1_aarch64.whl", hash = "sha256:a38c6c6c8032bed68b70c3b065d944c35e9f903342875d3a3218c1607987077c", size = 372508 }, + { url = "https://files.pythonhosted.org/packages/95/0d/aa44abe4141f29148ce671ac8c92045878906b18691c6f87a29711c2ff1c/ninja-1.11.1.3-py3-none-musllinux_1_1_i686.whl", hash = "sha256:56ada5d33b8741d298836644042faddebc83ee669782d661e21563034beb5aba", size = 419369 }, + { url = "https://files.pythonhosted.org/packages/f7/ec/48bf5105568ac9bd2016b701777bdd5000cc09a14ac837fef9f15e8d634e/ninja-1.11.1.3-py3-none-musllinux_1_1_ppc64le.whl", hash = "sha256:53409151da081f3c198bb0bfc220a7f4e821e022c5b7d29719adda892ddb31bb", size = 420304 }, + { url = "https://files.pythonhosted.org/packages/18/e5/69df63976cf971a03379899f8520a036c9dbab26330b37197512aed5b3df/ninja-1.11.1.3-py3-none-musllinux_1_1_s390x.whl", hash = "sha256:1ad2112c2b0159ed7c4ae3731595191b1546ba62316fc40808edecd0306fefa3", size = 416056 }, + { url = "https://files.pythonhosted.org/packages/6f/4f/bdb401af7ed0e24a3fef058e13a149f2de1ce4b176699076993615d55610/ninja-1.11.1.3-py3-none-musllinux_1_1_x86_64.whl", hash = "sha256:28aea3c1c280cba95b8608d50797169f3a34280e3e9a6379b6e340f0c9eaeeb0", size = 379725 }, + { url = "https://files.pythonhosted.org/packages/bd/68/05e7863bf13128c61652eeb3ec7096c3d3a602f32f31752dbfb034e3fa07/ninja-1.11.1.3-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:b6966f83064a88a51693073eea3decd47e08c3965241e09578ef7aa3a7738329", size = 434881 }, + { url = "https://files.pythonhosted.org/packages/bd/ad/edc0d1efe77f29f45bbca2e1dab07ef597f61a88de6e4bccffc0aec2256c/ninja-1.11.1.3-py3-none-win32.whl", hash = "sha256:a4a3b71490557e18c010cbb26bd1ea9a0c32ee67e8f105e9731515b6e0af792e", size = 255988 }, + { url = "https://files.pythonhosted.org/packages/03/93/09a9f7672b4f97438aca6217ac54212a63273f1cd3b46b731d0bb22c53e7/ninja-1.11.1.3-py3-none-win_amd64.whl", hash = "sha256:04d48d14ea7ba11951c156599ab526bdda575450797ff57c6fdf99b2554d09c7", size = 296502 }, + { url = "https://files.pythonhosted.org/packages/d9/9d/0cc1e82849070ff3cbee69f326cb48a839407bcd15d8844443c30a5e7509/ninja-1.11.1.3-py3-none-win_arm64.whl", hash = "sha256:17978ad611d8ead578d83637f5ae80c2261b033db0b493a7ce94f88623f29e1b", size = 270571 }, +] + [[package]] name = "nodeenv" version = "1.9.1" @@ -2486,6 +2853,115 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/16/2e/86f24451c2d530c88daf997cb8d6ac622c1d40d19f5a031ed68a4b73a374/numpy-1.26.4-cp312-cp312-win_amd64.whl", hash = "sha256:08beddf13648eb95f8d867350f6a018a4be2e5ad54c8d8caed89ebca558b2818", size = 15517754 }, ] +[[package]] +name = "nvidia-cublas-cu12" +version = "12.1.3.1" +source = { registry = "https://pypi.org/simple" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/37/6d/121efd7382d5b0284239f4ab1fc1590d86d34ed4a4a2fdb13b30ca8e5740/nvidia_cublas_cu12-12.1.3.1-py3-none-manylinux1_x86_64.whl", hash = "sha256:ee53ccca76a6fc08fb9701aa95b6ceb242cdaab118c3bb152af4e579af792728", size = 410594774 }, +] + +[[package]] +name = "nvidia-cuda-cupti-cu12" +version = "12.1.105" +source = { registry = "https://pypi.org/simple" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/7e/00/6b218edd739ecfc60524e585ba8e6b00554dd908de2c9c66c1af3e44e18d/nvidia_cuda_cupti_cu12-12.1.105-py3-none-manylinux1_x86_64.whl", hash = "sha256:e54fde3983165c624cb79254ae9818a456eb6e87a7fd4d56a2352c24ee542d7e", size = 14109015 }, +] + +[[package]] +name = "nvidia-cuda-nvrtc-cu12" +version = "12.1.105" +source = { registry = "https://pypi.org/simple" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b6/9f/c64c03f49d6fbc56196664d05dba14e3a561038a81a638eeb47f4d4cfd48/nvidia_cuda_nvrtc_cu12-12.1.105-py3-none-manylinux1_x86_64.whl", hash = "sha256:339b385f50c309763ca65456ec75e17bbefcbbf2893f462cb8b90584cd27a1c2", size = 23671734 }, +] + +[[package]] +name = "nvidia-cuda-runtime-cu12" +version = "12.1.105" +source = { registry = "https://pypi.org/simple" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/eb/d5/c68b1d2cdfcc59e72e8a5949a37ddb22ae6cade80cd4a57a84d4c8b55472/nvidia_cuda_runtime_cu12-12.1.105-py3-none-manylinux1_x86_64.whl", hash = "sha256:6e258468ddf5796e25f1dc591a31029fa317d97a0a94ed93468fc86301d61e40", size = 823596 }, +] + +[[package]] +name = "nvidia-cudnn-cu12" +version = "9.1.0.70" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "nvidia-cublas-cu12", marker = "(platform_machine != 'aarch64' and platform_system != 'Darwin') or (platform_system != 'Darwin' and platform_system != 'Linux')" }, +] +wheels = [ + { url = "https://files.pythonhosted.org/packages/9f/fd/713452cd72343f682b1c7b9321e23829f00b842ceaedcda96e742ea0b0b3/nvidia_cudnn_cu12-9.1.0.70-py3-none-manylinux2014_x86_64.whl", hash = "sha256:165764f44ef8c61fcdfdfdbe769d687e06374059fbb388b6c89ecb0e28793a6f", size = 664752741 }, +] + +[[package]] +name = "nvidia-cufft-cu12" +version = "11.0.2.54" +source = { registry = "https://pypi.org/simple" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/86/94/eb540db023ce1d162e7bea9f8f5aa781d57c65aed513c33ee9a5123ead4d/nvidia_cufft_cu12-11.0.2.54-py3-none-manylinux1_x86_64.whl", hash = "sha256:794e3948a1aa71fd817c3775866943936774d1c14e7628c74f6f7417224cdf56", size = 121635161 }, +] + +[[package]] +name = "nvidia-curand-cu12" +version = "10.3.2.106" +source = { registry = "https://pypi.org/simple" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/44/31/4890b1c9abc496303412947fc7dcea3d14861720642b49e8ceed89636705/nvidia_curand_cu12-10.3.2.106-py3-none-manylinux1_x86_64.whl", hash = "sha256:9d264c5036dde4e64f1de8c50ae753237c12e0b1348738169cd0f8a536c0e1e0", size = 56467784 }, +] + +[[package]] +name = "nvidia-cusolver-cu12" +version = "11.4.5.107" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "nvidia-cublas-cu12", marker = "(platform_machine != 'aarch64' and platform_system != 'Darwin') or (platform_system != 'Darwin' and platform_system != 'Linux')" }, + { name = "nvidia-cusparse-cu12", marker = "(platform_machine != 'aarch64' and platform_system != 'Darwin') or (platform_system != 'Darwin' and platform_system != 'Linux')" }, + { name = "nvidia-nvjitlink-cu12", marker = "(platform_machine != 'aarch64' and platform_system != 'Darwin') or (platform_system != 'Darwin' and platform_system != 'Linux')" }, +] +wheels = [ + { url = "https://files.pythonhosted.org/packages/bc/1d/8de1e5c67099015c834315e333911273a8c6aaba78923dd1d1e25fc5f217/nvidia_cusolver_cu12-11.4.5.107-py3-none-manylinux1_x86_64.whl", hash = "sha256:8a7ec542f0412294b15072fa7dab71d31334014a69f953004ea7a118206fe0dd", size = 124161928 }, +] + +[[package]] +name = "nvidia-cusparse-cu12" +version = "12.1.0.106" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "nvidia-nvjitlink-cu12", marker = "(platform_machine != 'aarch64' and platform_system != 'Darwin') or (platform_system != 'Darwin' and platform_system != 'Linux')" }, +] +wheels = [ + { url = "https://files.pythonhosted.org/packages/65/5b/cfaeebf25cd9fdec14338ccb16f6b2c4c7fa9163aefcf057d86b9cc248bb/nvidia_cusparse_cu12-12.1.0.106-py3-none-manylinux1_x86_64.whl", hash = "sha256:f3b50f42cf363f86ab21f720998517a659a48131e8d538dc02f8768237bd884c", size = 195958278 }, +] + +[[package]] +name = "nvidia-nccl-cu12" +version = "2.20.5" +source = { registry = "https://pypi.org/simple" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c1/bb/d09dda47c881f9ff504afd6f9ca4f502ded6d8fc2f572cacc5e39da91c28/nvidia_nccl_cu12-2.20.5-py3-none-manylinux2014_aarch64.whl", hash = "sha256:1fc150d5c3250b170b29410ba682384b14581db722b2531b0d8d33c595f33d01", size = 176238458 }, + { url = "https://files.pythonhosted.org/packages/4b/2a/0a131f572aa09f741c30ccd45a8e56316e8be8dfc7bc19bf0ab7cfef7b19/nvidia_nccl_cu12-2.20.5-py3-none-manylinux2014_x86_64.whl", hash = "sha256:057f6bf9685f75215d0c53bf3ac4a10b3e6578351de307abad9e18a99182af56", size = 176249402 }, +] + +[[package]] +name = "nvidia-nvjitlink-cu12" +version = "12.6.85" +source = { registry = "https://pypi.org/simple" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/9d/d7/c5383e47c7e9bf1c99d5bd2a8c935af2b6d705ad831a7ec5c97db4d82f4f/nvidia_nvjitlink_cu12-12.6.85-py3-none-manylinux2010_x86_64.manylinux_2_12_x86_64.whl", hash = "sha256:eedc36df9e88b682efe4309aa16b5b4e78c2407eac59e8c10a6a47535164369a", size = 19744971 }, + { url = "https://files.pythonhosted.org/packages/31/db/dc71113d441f208cdfe7ae10d4983884e13f464a6252450693365e166dcf/nvidia_nvjitlink_cu12-12.6.85-py3-none-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:cf4eaa7d4b6b543ffd69d6abfb11efdeb2db48270d94dfd3a452c24150829e41", size = 19270338 }, +] + +[[package]] +name = "nvidia-nvtx-cu12" +version = "12.1.105" +source = { registry = "https://pypi.org/simple" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/da/d3/8057f0587683ed2fcd4dbfbdfdfa807b9160b809976099d36b8f60d08f03/nvidia_nvtx_cu12-12.1.105-py3-none-manylinux1_x86_64.whl", hash = "sha256:dc21cf308ca5691e7c04d962e213f8a4aa9bbfa23d95412f452254c2caeb09e5", size = 99138 }, +] + [[package]] name = "oauthlib" version = "3.2.2" @@ -2571,6 +3047,23 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/ad/31/28a83e124e9f9dd04c83b5aeb6f8b1770f45addde4dd3d34d9a9091590ad/openai-1.52.1-py3-none-any.whl", hash = "sha256:f23e83df5ba04ee0e82c8562571e8cb596cd88f9a84ab783e6c6259e5ffbfb4a", size = 386945 }, ] +[[package]] +name = "opencv-python-headless" +version = "4.10.0.84" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "numpy" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/2f/7e/d20f68a5f1487adf19d74378d349932a386b1ece3be9be9915e5986db468/opencv-python-headless-4.10.0.84.tar.gz", hash = "sha256:f2017c6101d7c2ef8d7bc3b414c37ff7f54d64413a1847d89970b6b7069b4e1a", size = 95117755 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/1c/9b/583c8d9259f6fc19413f83fd18dd8e6cbc8eefb0b4dc6da52dd151fe3272/opencv_python_headless-4.10.0.84-cp37-abi3-macosx_11_0_arm64.whl", hash = "sha256:a4f4bcb07d8f8a7704d9c8564c224c8b064c63f430e95b61ac0bffaa374d330e", size = 54835657 }, + { url = "https://files.pythonhosted.org/packages/c0/7b/b4c67f5dad7a9a61c47f7a39e4050e8a4628bd64b3c3daaeb755d759f928/opencv_python_headless-4.10.0.84-cp37-abi3-macosx_12_0_x86_64.whl", hash = "sha256:5ae454ebac0eb0a0b932e3406370aaf4212e6a3fdb5038cc86c7aea15a6851da", size = 56475470 }, + { url = "https://files.pythonhosted.org/packages/91/61/f838ce2046f3ec3591ea59ea3549085e399525d3b4558c4ed60b55ed88c0/opencv_python_headless-4.10.0.84-cp37-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:46071015ff9ab40fccd8a163da0ee14ce9846349f06c6c8c0f2870856ffa45db", size = 29329705 }, + { url = "https://files.pythonhosted.org/packages/d1/09/248f86a404567303cdf120e4a301f389b68e3b18e5c0cc428de327da609c/opencv_python_headless-4.10.0.84-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:377d08a7e48a1405b5e84afcbe4798464ce7ee17081c1c23619c8b398ff18295", size = 49858781 }, + { url = "https://files.pythonhosted.org/packages/30/c0/66f88d58500e990a9a0a5c06f98862edf1d0a3a430781218a8c193948438/opencv_python_headless-4.10.0.84-cp37-abi3-win32.whl", hash = "sha256:9092404b65458ed87ce932f613ffbb1106ed2c843577501e5768912360fc50ec", size = 28675298 }, + { url = "https://files.pythonhosted.org/packages/26/d0/22f68eb23eea053a31655960f133c0be9726c6a881547e6e9e7e2a946c4f/opencv_python_headless-4.10.0.84-cp37-abi3-win_amd64.whl", hash = "sha256:afcf28bd1209dd58810d33defb622b325d3cbe49dcd7a43a902982c33e5fad05", size = 38754031 }, +] + [[package]] name = "openpyxl" version = "3.1.5" @@ -3257,6 +3750,32 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/77/89/bc88a6711935ba795a679ea6ebee07e128050d6382eaa35a0a47c8032bdc/pyasn1_modules-0.4.1-py3-none-any.whl", hash = "sha256:49bfa96b45a292b711e986f222502c1c9a5e1f4e568fc30e2574a6c7d07838fd", size = 181537 }, ] +[[package]] +name = "pyclipper" +version = "1.3.0.post6" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/4a/b2/550fe500e49c464d73fabcb8cb04d47e4885d6ca4cfc1f5b0a125a95b19a/pyclipper-1.3.0.post6.tar.gz", hash = "sha256:42bff0102fa7a7f2abdd795a2594654d62b786d0c6cd67b72d469114fdeb608c", size = 165909 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b5/34/0dca299fe41e9a92e78735502fed5238a4ac734755e624488df9b2eeec46/pyclipper-1.3.0.post6-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:fa0f5e78cfa8262277bb3d0225537b3c2a90ef68fd90a229d5d24cf49955dcf4", size = 269504 }, + { url = "https://files.pythonhosted.org/packages/8a/5b/81528b08134b3c2abdfae821e1eff975c0703802d41974b02dfb2e101c55/pyclipper-1.3.0.post6-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:a01f182d8938c1dc515e8508ed2442f7eebd2c25c7d5cb29281f583c1a8008a4", size = 142599 }, + { url = "https://files.pythonhosted.org/packages/84/a4/3e304f6c0d000382cd54d4a1e5f0d8fc28e1ae97413a2ec1016a7b840319/pyclipper-1.3.0.post6-cp310-cp310-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:640f20975727994d4abacd07396f564e9e5665ba5cb66ceb36b300c281f84fa4", size = 912209 }, + { url = "https://files.pythonhosted.org/packages/f5/6a/28ec55cc3f972368b211fca017e081cf5a71009d1b8ec3559767cda5b289/pyclipper-1.3.0.post6-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a63002f6bb0f1efa87c0b81634cbb571066f237067e23707dabf746306c92ba5", size = 929511 }, + { url = "https://files.pythonhosted.org/packages/c4/56/c326f3454c5f30a31f58a5c3154d891fce58ad73ccbf1d3f4aacfcbd344d/pyclipper-1.3.0.post6-cp310-cp310-win32.whl", hash = "sha256:106b8622cd9fb07d80cbf9b1d752334c55839203bae962376a8c59087788af26", size = 100126 }, + { url = "https://files.pythonhosted.org/packages/f8/e6/f8239af6346848b20a3448c554782fe59298ab06c1d040490242dc7e3c26/pyclipper-1.3.0.post6-cp310-cp310-win_amd64.whl", hash = "sha256:9699e98862dadefd0bea2360c31fa61ca553c660cbf6fb44993acde1b959f58f", size = 110470 }, + { url = "https://files.pythonhosted.org/packages/50/a9/66ca5f252dcac93ca076698591b838ba17f9729591edf4b74fef7fbe1414/pyclipper-1.3.0.post6-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:c4247e7c44b34c87acbf38f99d48fb1acaf5da4a2cf4dcd601a9b24d431be4ef", size = 270930 }, + { url = "https://files.pythonhosted.org/packages/59/fe/2ab5818b3504e179086e54a37ecc245525d069267b8c31b18ec3d0830cbf/pyclipper-1.3.0.post6-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:851b3e58106c62a5534a1201295fe20c21714dee2eda68081b37ddb0367e6caa", size = 143411 }, + { url = "https://files.pythonhosted.org/packages/09/f7/b58794f643e033a6d14da7c70f517315c3072f3c5fccdf4232fa8c8090c1/pyclipper-1.3.0.post6-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:16cc1705a915896d2aff52131c427df02265631279eac849ebda766432714cc0", size = 951754 }, + { url = "https://files.pythonhosted.org/packages/c1/77/846a21957cd4ed266c36705ee340beaa923eb57d2bba013cfd7a5c417cfd/pyclipper-1.3.0.post6-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ace1f0753cf71c5c5f6488b8feef5dd0fa8b976ad86b24bb51f708f513df4aac", size = 969608 }, + { url = "https://files.pythonhosted.org/packages/c9/2b/580703daa6606d160caf596522d4cfdf62ae619b062a7ce6f905821a57e8/pyclipper-1.3.0.post6-cp311-cp311-win32.whl", hash = "sha256:dbc828641667142751b1127fd5c4291663490cf05689c85be4c5bcc89aaa236a", size = 100227 }, + { url = "https://files.pythonhosted.org/packages/17/4b/a4cda18e8556d913ff75052585eb0d658500596b5f97fe8401d05123d47b/pyclipper-1.3.0.post6-cp311-cp311-win_amd64.whl", hash = "sha256:1c03f1ae43b18ee07730c3c774cc3cf88a10c12a4b097239b33365ec24a0a14a", size = 110442 }, + { url = "https://files.pythonhosted.org/packages/fc/c8/197d9a1d8354922d24d11d22fb2e0cc1ebc182f8a30496b7ddbe89467ce1/pyclipper-1.3.0.post6-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:6363b9d79ba1b5d8f32d1623e797c1e9f994600943402e68d5266067bdde173e", size = 270487 }, + { url = "https://files.pythonhosted.org/packages/8e/8e/eb14eadf054494ad81446e21c4ea163b941747610b0eb9051644395f567e/pyclipper-1.3.0.post6-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:32cd7fb9c1c893eb87f82a072dbb5e26224ea7cebbad9dc306d67e1ac62dd229", size = 143469 }, + { url = "https://files.pythonhosted.org/packages/cf/e5/6c4a8df6e904c133bb4c5309d211d31c751db60cbd36a7250c02b05494a1/pyclipper-1.3.0.post6-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e3aab10e3c10ed8fa60c608fb87c040089b83325c937f98f06450cf9fcfdaf1d", size = 944206 }, + { url = "https://files.pythonhosted.org/packages/76/65/cb014acc41cd5bf6bbfa4671c7faffffb9cee01706642c2dec70c5209ac8/pyclipper-1.3.0.post6-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:58eae2ff92a8cae1331568df076c4c5775bf946afab0068b217f0cf8e188eb3c", size = 963797 }, + { url = "https://files.pythonhosted.org/packages/80/ec/b40cd81ab7598984167508a5369a2fa31a09fe3b3e3d0b73aa50e06d4b3f/pyclipper-1.3.0.post6-cp312-cp312-win32.whl", hash = "sha256:793b0aa54b914257aa7dc76b793dd4dcfb3c84011d48df7e41ba02b571616eaf", size = 99456 }, + { url = "https://files.pythonhosted.org/packages/24/3a/7d6292e3c94fb6b872d8d7e80d909dc527ee6b0af73b753c63fdde65a7da/pyclipper-1.3.0.post6-cp312-cp312-win_amd64.whl", hash = "sha256:d3f9da96f83b8892504923beb21a481cd4516c19be1d39eb57a92ef1c9a29548", size = 110278 }, +] + [[package]] name = "pycparser" version = "2.22" @@ -3348,6 +3867,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/34/19/26bb6bdb9fdad5f0dfce538780814084fb667b4bc37fcb28459c14b8d3b5/pydantic_settings-2.6.0-py3-none-any.whl", hash = "sha256:4a819166f119b74d7f8c765196b165f95cc7487ce58ea27dec8a5a26be0970e0", size = 28578 }, ] +[[package]] +name = "pyflakes" +version = "3.2.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/57/f9/669d8c9c86613c9d568757c7f5824bd3197d7b1c6c27553bc5618a27cce2/pyflakes-3.2.0.tar.gz", hash = "sha256:1c61603ff154621fb2a9172037d84dca3500def8c8b630657d1701f026f8af3f", size = 63788 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d4/d7/f1b7db88d8e4417c5d47adad627a93547f44bdc9028372dbd2313f34a855/pyflakes-3.2.0-py2.py3-none-any.whl", hash = "sha256:84b5be138a2dfbb40689ca07e2152deb896a65c3a3e24c251c5c62489568074a", size = 62725 }, +] + [[package]] name = "pygments" version = "2.18.0" @@ -3536,6 +4064,68 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/9d/d3/ff520d11e6ee400602711d1ece8168dcfc5b6d8146fb7db4244a6ad6a9c3/pytest_vcr-1.0.2-py2.py3-none-any.whl", hash = "sha256:2f316e0539399bea0296e8b8401145c62b6f85e9066af7e57b6151481b0d6d9c", size = 4137 }, ] +[[package]] +name = "python-bidi" +version = "0.6.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/74/b5/c1684fb8120ad868b43e4b037ac83aafaa70724cdb078d066bae836c31f6/python_bidi-0.6.3.tar.gz", hash = "sha256:e12114969001a328aea859f79efc30ab9c15241befb86e07029d8961d97fae36", size = 45054 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/29/60/2e3f97f2b47dbfbaf4af9b0a2b44c50cbc2fe189a8c6aafefb1fc9f437d2/python_bidi-0.6.3-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:7e2a62d7ebb4af9831c85921063154ab4067c73768ad04f466dff1359e6f2650", size = 257485 }, + { url = "https://files.pythonhosted.org/packages/99/b4/a8a28b0380d3117ae8f206e38111c98b24b34a06c5c03457e5796998222b/python_bidi-0.6.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:6b8035f02c3fcb52d372bfe51db00a0c95a3fdd6f0504a32e70d4f799809070d", size = 253214 }, + { url = "https://files.pythonhosted.org/packages/94/aa/2f8db528f278a65b1e4358c08f8c5c227cf9ca1cd2bbdd54ea22c680008b/python_bidi-0.6.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:854edec3ef1ef50c49f689b44900fb6c51d35f277e10b4749755d053f405a44a", size = 294838 }, + { url = "https://files.pythonhosted.org/packages/33/e0/7f4953f874edbf19fded721c6dcb77aff1915709735b5781816d78980d30/python_bidi-0.6.3-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:fe4c4ab61701a5e3b916c6b63811c6fd708539a3f189ec6ca6bd22948a125af0", size = 297562 }, + { url = "https://files.pythonhosted.org/packages/52/c1/e579542a519f7999421670f6d0b825371111a8542cd8cbccde1400ef655a/python_bidi-0.6.3-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:855a4dc2d237587a734babc6179130f9e7b7c028651cdead6ec5b162115ac112", size = 323416 }, + { url = "https://files.pythonhosted.org/packages/d9/6b/09c31613ca545da21191d8e4f1956a7a1cd737a963ebbe5bf951301a045b/python_bidi-0.6.3-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3c0635bf46ddd56cf3f71d0711fbc160fd90c36fd3176b3e91b0bf7447e549f1", size = 329408 }, + { url = "https://files.pythonhosted.org/packages/2c/ea/e451f3c838dd4931756f5bdd2bdea88d47c09399a5b7f018907281359569/python_bidi-0.6.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7a4b7b6e458173614348db8e4a4406e468338c13ecc7b74d1e208d38d0d1d264", size = 286782 }, + { url = "https://files.pythonhosted.org/packages/79/fa/e008ae3d0da6f6414195754aa52d8d7c203fb2cc891d2af5d029e67096b3/python_bidi-0.6.3-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:25a39a3b61851506ed489867c69f3580ba75063195bf4b00f1983de88e02bf30", size = 301152 }, + { url = "https://files.pythonhosted.org/packages/a0/cf/515e91808c2778a0611dd8c09f11708ee23f5bad43ef6546ed2bbecd6734/python_bidi-0.6.3-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:24ea5c9f5cf9f3919d81669d24a1405709f4d66c82c3ffa7f982fcece856b325", size = 468021 }, + { url = "https://files.pythonhosted.org/packages/26/51/fe80453916c398f67f2ff33023fe683c8c8f873dadda3a2a99388b504f4a/python_bidi-0.6.3-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:631d32fd1414d4795348122b820dadbff1ddaa6e53a70c1ee9d5a84911cc3c2d", size = 549822 }, + { url = "https://files.pythonhosted.org/packages/f5/21/282ea630232e8dad595d2e5a55303eb86496f22fbcee730868bfb426403d/python_bidi-0.6.3-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:205aac547f8166005e041b33069da2c8a345171b0d7c8177c3d16408acde9acd", size = 471868 }, + { url = "https://files.pythonhosted.org/packages/46/80/7d2c44d640d71b7c236936043ec48b4a72a320b8dde6e19597649c0152b6/python_bidi-0.6.3-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:a05249eac27e983a103babb9a2812726312bd8f685fdc3264f78b8ff8124d09a", size = 452051 }, + { url = "https://files.pythonhosted.org/packages/83/c4/1cff2dfaf363daa8e17ece18f4eee2d9543cb1f55803f6269aba1eb2329a/python_bidi-0.6.3-cp310-none-win32.whl", hash = "sha256:44023d51ae78ae119ef11043b5fb8f3dfc5de5ec04d937d7c5abc4da8cba1770", size = 151742 }, + { url = "https://files.pythonhosted.org/packages/ef/d6/5ed0414bdb9ba0c7d2710d967197c7b3a2b3c40d4f2b1974ba1ed175b8e6/python_bidi-0.6.3-cp310-none-win_amd64.whl", hash = "sha256:866865bbbc97a144e74508e2513373bb590d38fca3b6e52b6905de54b34ddbd9", size = 157244 }, + { url = "https://files.pythonhosted.org/packages/00/b0/c3921d4c7b498d749cd0ca6ee2be9a78b3442e871bf590fd344118160f78/python_bidi-0.6.3-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:a656b91c74b77a5b005e6dac092947f00d546cce5d0ca70b6b6741b93f7705bf", size = 257551 }, + { url = "https://files.pythonhosted.org/packages/29/29/298501cf59ed614bb1e78728d1cad57555dda7d114e7f34aabf215a45c41/python_bidi-0.6.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:4cb80856ce1e3f24c0d878fc85ab767c201ab8891a68f41d8da87eaf39c827de", size = 253211 }, + { url = "https://files.pythonhosted.org/packages/fc/e4/dab2d83eabaae763ae637190a0af97676caf4f7bbe3fc12f25e5d3f4e2db/python_bidi-0.6.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9ad3f50925a5943d244c6ca05e0553922e917b3cc415580460d86af6a385ee23", size = 294937 }, + { url = "https://files.pythonhosted.org/packages/ef/36/747d1eb808c075d065b6fbab065b42c8be4925361b40af01e65afa598ff3/python_bidi-0.6.3-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:22f293338ec7d44e02991787d306d39e02f0b145810eef60802abd7833b6c2d0", size = 297497 }, + { url = "https://files.pythonhosted.org/packages/3b/b6/a04586be453fe3abaed5c190e3a00f19e33e7c0d98362bb522533457d2fe/python_bidi-0.6.3-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:12b1d522cbd0af85094ccce8ae95c57a6a9d4f98e85f3e7c1ad1fb5d1c2cd09e", size = 323300 }, + { url = "https://files.pythonhosted.org/packages/4d/48/6bf8b48d1d8712b951cc5594bfd3f0d53cfc5ffb02e1948475888c9c1945/python_bidi-0.6.3-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:da00726ebf17f857d458b310e868cae4b3bac668396cd5e874e17809894417e5", size = 329011 }, + { url = "https://files.pythonhosted.org/packages/59/61/c707bf04de816c3278943e3fb1378ca41a4381ea8fedf9ca2feba87748e6/python_bidi-0.6.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3b1204f2aa62ac6226f11dd1bee250d428abb128046cf1999317b3f303c70ea2", size = 286608 }, + { url = "https://files.pythonhosted.org/packages/61/3b/57b33a0785ec9899b02d70d061607ba33e4479c48ade36b2892c461a7f0d/python_bidi-0.6.3-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:7c99881440b2a4d8de7c2d7f3ac23e5f0a0ee0c5ae652f53188a21e9b0911f2d", size = 301050 }, + { url = "https://files.pythonhosted.org/packages/48/93/c36235c48d68003a63401591fd4884cade4369ec2e666af3029f1db12667/python_bidi-0.6.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:10f7c23dbb23dd0d2b0f67f7d4c2ba59eb42f777e1749ed9e13dbc8c4d28ea75", size = 467960 }, + { url = "https://files.pythonhosted.org/packages/56/cd/0e197aad0a8b8ff9c980adb4dc5dfc34ff1c1709c8dc3557dc15efafc73f/python_bidi-0.6.3-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:d7527247a9d8e0aa9d2d4ecd24cbd8216bc4e3e89e77f9c833eedf278d9761cc", size = 549878 }, + { url = "https://files.pythonhosted.org/packages/b4/21/15e9444084c234758b32cbcde7be57f03ca9a153d8d82817723c85df27cb/python_bidi-0.6.3-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:5d6829865ff94925280af066c8536ff9595a6e40d300f9fc0e6ca4ebbf3bc306", size = 471859 }, + { url = "https://files.pythonhosted.org/packages/ee/6f/583ab5578e73487466f1f12130689422f866be20b9b1b61d06dd160fc981/python_bidi-0.6.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:0e0d574c22fbab1ea996ddb1ebb3eabae521f5d129d7c699445cad81e81bc351", size = 451875 }, + { url = "https://files.pythonhosted.org/packages/26/f4/4f427662c154cd2ad7383ffa14740d39f3032ba088a973b95796d868f065/python_bidi-0.6.3-cp311-none-win32.whl", hash = "sha256:8c5fc9f065c24bd8058d7e9a5d42415134de3cc1aa480eebc27e2ca132919dd8", size = 151834 }, + { url = "https://files.pythonhosted.org/packages/f5/44/1fcb4350e6283a1ccd64666373274dd76eb0a9f91c3c1ac8e06e5046c5a5/python_bidi-0.6.3-cp311-none-win_amd64.whl", hash = "sha256:46ee694cf5a632a8d47cc35de6926581e586425b582216962d3e6d913aea0b88", size = 157241 }, + { url = "https://files.pythonhosted.org/packages/99/15/4f316e1b463ea71b4533e904548f47000f9e20ad394b3e8c83ac65638534/python_bidi-0.6.3-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:4bdc9dc1143c558ca6931d6712339a30470959f2b7eecb3d0687db7075c20a87", size = 257039 }, + { url = "https://files.pythonhosted.org/packages/ad/2a/9b54acafa641c360ba3a632726046a8da38da1f3ed7b816ae6eec748ca95/python_bidi-0.6.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:0775499b8037103278f05b2bf92d25bf04f40a9f77884ec3d42b01a1e52a40fe", size = 252791 }, + { url = "https://files.pythonhosted.org/packages/ca/b2/55f4d18760dac53e542817b3a2e11e3d5ae631678f2c2248c6f1e67336ea/python_bidi-0.6.3-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:eb3091aa5efbfc4da6fd52a2fccbf7853c6dc253ddaf9a189bcf3c4345865aa9", size = 294297 }, + { url = "https://files.pythonhosted.org/packages/63/7b/d1ca351cdab44300296a4b758a370bf923666deaa4d19819a7fc4478dc52/python_bidi-0.6.3-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:c75a9b68b3f5a8da9a33fe37607d9b267a8a3c5806d283a4a47365256773dd1e", size = 297650 }, + { url = "https://files.pythonhosted.org/packages/e4/9e/bcb93f0e30abbd9fbbda736402e3ee86566b1253ea57d1e9b465eb4b62a0/python_bidi-0.6.3-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:208e09819ee0485c2ed4dc1932c39fc073dac3f2cb70b6d2ae0b7296e86831e6", size = 323959 }, + { url = "https://files.pythonhosted.org/packages/26/dd/e795e1186ba6b9e3ebb9bc316c6572036e59d0c2841a5e182403d483eb57/python_bidi-0.6.3-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e17b67d86cd38f2bebc4a46090f83cabb0d1da3a3c920c68efe8093ae1a8d0d1", size = 327207 }, + { url = "https://files.pythonhosted.org/packages/c9/27/fc5777273bcb3de7b82524852995edd67dc9948ed916a3f9236714628ae3/python_bidi-0.6.3-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:933a17938f767fa64a8365732eba787a81c26214d89e1b3abe87912325ba26a9", size = 286371 }, + { url = "https://files.pythonhosted.org/packages/e9/11/90b4072461759074564a29d05a83128f02214fea1012ca0984d3a5a7fdbb/python_bidi-0.6.3-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:772efb3e0ef17396bfd9d47da4805c74ed6c04f27cac08d7757f76602837fb9d", size = 300797 }, + { url = "https://files.pythonhosted.org/packages/61/2c/d50a50adf51b6c53022b9ad247d65cad50acde94f877fbab49a8854ccaae/python_bidi-0.6.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:9a99114f33f8c0273a61b4afe7d4d715e098318ee4e5ce8f6bb5da8dcd3f95c7", size = 468349 }, + { url = "https://files.pythonhosted.org/packages/68/46/a9d285552961670aedc0bbac403defa7c9c4b17ab6957ef0b7db05d12eec/python_bidi-0.6.3-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:b30e620d39e85a30bb42f460fd8b5274caf261517edeb853b975d9ea1939b6bd", size = 549521 }, + { url = "https://files.pythonhosted.org/packages/36/3a/5b6b6b73a210cc99b1d1ea697741fc4fc1853aa788b3db1737bfdcaabc75/python_bidi-0.6.3-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:bee94e3152a6c9ba731e086c9cc6203904290506ba52c505a2e59abab481eb13", size = 471509 }, + { url = "https://files.pythonhosted.org/packages/dd/5a/d3d0588caeb041bbf85b421b30a7a9893057c72fcddcaaf2d54289123487/python_bidi-0.6.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:926164ec594e9ea9a64faf54273c711d5e3233bcc6ef8966c6eeaddfb3b3075f", size = 451688 }, + { url = "https://files.pythonhosted.org/packages/1d/1f/0539881d381dda2a281056ccfb55905439bad86cfb2787792065a2d8d08b/python_bidi-0.6.3-cp312-none-win32.whl", hash = "sha256:cea395a7daee14c7d50a7e20890d12b9ff1938d81b23eb564f1707a175c37202", size = 151600 }, + { url = "https://files.pythonhosted.org/packages/aa/e7/b4f6a71a7e7aec0e17c36bfca2ff5d962b86b0e04e278de6fc6c0cd6d643/python_bidi-0.6.3-cp312-none-win_amd64.whl", hash = "sha256:350e6c76f942465871f2b473a2076f5002f1df06e4c7abee3029ccca5f006786", size = 156933 }, + { url = "https://files.pythonhosted.org/packages/72/b9/99e91d30bcf0f61f1ef055aa180d7337321c09b5684246b83c6b2b4aed69/python_bidi-0.6.3-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:28cd25ef6141a77e04a7fb6fef0a19cc307106f84a891777fcdd3306ae8cfc20", size = 258864 }, + { url = "https://files.pythonhosted.org/packages/05/2f/8330d6c9ff768d6e76f765ba4bf082c74c1abcfda390efb4888421a47c5d/python_bidi-0.6.3-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:e4eab3736a14b8d9daea3e8e638ca5a24051497152ba32fb08db9259dd77b858", size = 254338 }, + { url = "https://files.pythonhosted.org/packages/2a/e5/97c0285bfd52b26f0050c24ac568968fc911c12f9b1128dc05fca3616ccf/python_bidi-0.6.3-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:78d12927cab0f6b8304f04c9ed72bc1a2880df8974d8596e40e7e596c6a98b2e", size = 296023 }, + { url = "https://files.pythonhosted.org/packages/87/c5/6480d468ae982df6985d7c99ce48b46b4cff2fb1c8264d9bce576a8eac96/python_bidi-0.6.3-pp310-pypy310_pp73-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:440be542b334da05673bd94d53ba4922482b06fa3f4daca6c8fa7434afb33e8a", size = 298678 }, + { url = "https://files.pythonhosted.org/packages/07/73/653ca3948deee129ca5a674bd81300cd825e22875397a45c208570cf5cb8/python_bidi-0.6.3-pp310-pypy310_pp73-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a9635ae0c9ee71b69f11cb6ab9523165c79fdb82ca53afb5afb0d401616fef80", size = 325314 }, + { url = "https://files.pythonhosted.org/packages/f6/6a/b2d13d7ca80221ff072f8cfa1d78a8c11ca85d8e728312ad065520cd783c/python_bidi-0.6.3-pp310-pypy310_pp73-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1ebac008620916b0c02623926fd80719f2e61e4fa9b626ed1e309a6818b57486", size = 331300 }, + { url = "https://files.pythonhosted.org/packages/b2/b9/b52b327837644d25debc6d1bff633578bf3376c10bff3a2e212c8f3721b8/python_bidi-0.6.3-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:57bb5fd4d9ccad52584ce8ad1468ec2e5b535519840ab1debe05c7fe4d32b800", size = 288331 }, + { url = "https://files.pythonhosted.org/packages/9f/8e/be90d3fd423848baf6ddc2728af168a4688e273115439093ff4881dab1f2/python_bidi-0.6.3-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:1caacb766872c54742cdb8a5c042bec1282c5a3144e4aeba6f8650ab8911d7f3", size = 302091 }, + { url = "https://files.pythonhosted.org/packages/0a/e6/55ae3cb6f117fe4d150d22860c7409eb733c97daf7f3b34f204867c19b88/python_bidi-0.6.3-pp310-pypy310_pp73-musllinux_1_2_aarch64.whl", hash = "sha256:584dd7c4617ea0ef39900ef7b06b8c61e6ce3ccb4b90c28ed28fa3bf770c5124", size = 470230 }, + { url = "https://files.pythonhosted.org/packages/41/3f/c886a8ddd11798ced50470f6e2beedee338b6a6960e190ce1f025079d333/python_bidi-0.6.3-pp310-pypy310_pp73-musllinux_1_2_armv7l.whl", hash = "sha256:a3bdc284cc4a1d70942ba0582b91853403c5ca7df79909b755be69089ecc5e17", size = 551042 }, + { url = "https://files.pythonhosted.org/packages/19/8c/ecb89be8ccf908bcbd1ccccd36662ac7c0710b194867525508385fa162e1/python_bidi-0.6.3-pp310-pypy310_pp73-musllinux_1_2_i686.whl", hash = "sha256:995ed295f2d9095facbef3025d79e209ec7ae1be0d1f385a49818edb2cb4421e", size = 473635 }, + { url = "https://files.pythonhosted.org/packages/84/9e/6956703cb46b9bf7dae4256123b363e9f8550bdfe3fddd3e0eabd3c75f6d/python_bidi-0.6.3-pp310-pypy310_pp73-musllinux_1_2_x86_64.whl", hash = "sha256:a50d62f4f1e10682babd529d46e9e62236ff202d3025a223c17ead32035cb410", size = 453347 }, +] + [[package]] name = "python-dateutil" version = "2.9.0.post0" @@ -3548,6 +4138,19 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/ec/57/56b9bcc3c9c6a792fcbaf139543cee77261f3651ca9da0c93f5c1221264b/python_dateutil-2.9.0.post0-py2.py3-none-any.whl", hash = "sha256:a8b2bc7bffae282281c8140a97d3aa9c14da0b136dfe83f850eea9a5f7470427", size = 229892 }, ] +[[package]] +name = "python-docx" +version = "1.1.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "lxml" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/35/e4/386c514c53684772885009c12b67a7edd526c15157778ac1b138bc75063e/python_docx-1.1.2.tar.gz", hash = "sha256:0cf1f22e95b9002addca7948e16f2cd7acdfd498047f1941ca5d293db7762efd", size = 5656581 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/3e/3d/330d9efbdb816d3f60bf2ad92f05e1708e4a1b9abe80461ac3444c83f749/python_docx-1.1.2-py3-none-any.whl", hash = "sha256:08c20d6058916fb19853fcf080f7f42b6270d89eac9fa5f8c15f691c0017fabe", size = 244315 }, +] + [[package]] name = "python-dotenv" version = "1.0.1" @@ -3557,6 +4160,21 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/6a/3e/b68c118422ec867fa7ab88444e1274aa40681c606d59ac27de5a5588f082/python_dotenv-1.0.1-py3-none-any.whl", hash = "sha256:f7b63ef50f1b690dddf550d03497b66d609393b40b564ed0d674909a68ebf16a", size = 19863 }, ] +[[package]] +name = "python-pptx" +version = "1.0.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "lxml" }, + { name = "pillow" }, + { name = "typing-extensions" }, + { name = "xlsxwriter" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/52/a9/0c0db8d37b2b8a645666f7fd8accea4c6224e013c42b1d5c17c93590cd06/python_pptx-1.0.2.tar.gz", hash = "sha256:479a8af0eaf0f0d76b6f00b0887732874ad2e3188230315290cd1f9dd9cc7095", size = 10109297 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d9/4f/00be2196329ebbff56ce564aa94efb0fbc828d00de250b1980de1a34ab49/python_pptx-1.0.2-py3-none-any.whl", hash = "sha256:160838e0b8565a8b1f67947675886e9fea18aa5e795db7ae531606d68e785cba", size = 472788 }, +] + [[package]] name = "pytube" version = "15.0.0" @@ -3591,18 +4209,18 @@ wheels = [ [[package]] name = "pywin32" -version = "308" +version = "307" source = { registry = "https://pypi.org/simple" } wheels = [ - { 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/12/3d/91d710c40cc61fd241025351fd61fb674859973c5a0b3111e532d7229012/pywin32-307-cp310-cp310-win32.whl", hash = "sha256:f8f25d893c1e1ce2d685ef6d0a481e87c6f510d0f3f117932781f412e0eba31b", size = 5904291 }, + { url = "https://files.pythonhosted.org/packages/94/b4/20804bb7528419d503c71cfcb8988f0eb9f3596501a9d86eb528c9998055/pywin32-307-cp310-cp310-win_amd64.whl", hash = "sha256:36e650c5e5e6b29b5d317385b02d20803ddbac5d1031e1f88d20d76676dd103d", size = 6535115 }, + { url = "https://files.pythonhosted.org/packages/65/55/f1c84fcccbd5b75c09aa2a948551ad4569f9c14994a39959d3fee3267911/pywin32-307-cp310-cp310-win_arm64.whl", hash = "sha256:0c12d61e0274e0c62acee79e3e503c312426ddd0e8d4899c626cddc1cafe0ff4", size = 7948521 }, + { url = "https://files.pythonhosted.org/packages/f9/29/5f50cb02aef57711bf941e1d93bfe602625f89faf33abb737441ab698496/pywin32-307-cp311-cp311-win32.whl", hash = "sha256:fec5d27cc893178fab299de911b8e4d12c5954e1baf83e8a664311e56a272b75", size = 5905392 }, + { url = "https://files.pythonhosted.org/packages/5e/8d/dd2bf7e5dbfed3ea17b07763bc13d007583ef48914ed446be1c329c8e601/pywin32-307-cp311-cp311-win_amd64.whl", hash = "sha256:987a86971753ed7fdd52a7fb5747aba955b2c7fbbc3d8b76ec850358c1cc28c3", size = 6536159 }, + { url = "https://files.pythonhosted.org/packages/63/72/dce6d08a2adeaf9e7e0462173610900d01d16a449aa74c9e035b7c2ec8f8/pywin32-307-cp311-cp311-win_arm64.whl", hash = "sha256:fd436897c186a2e693cd0437386ed79f989f4d13d6f353f8787ecbb0ae719398", size = 7949586 }, + { url = "https://files.pythonhosted.org/packages/90/4e/9c660fa6c34db3c9542c9682b0ccd9edd63a6a4cb6ac4d22014b2c3355c9/pywin32-307-cp312-cp312-win32.whl", hash = "sha256:07649ec6b01712f36debf39fc94f3d696a46579e852f60157a729ac039df0815", size = 5916997 }, + { url = "https://files.pythonhosted.org/packages/9c/11/c56e771d2cdbd2dac8e656edb2c814e4b2239da2c9028aa7265cdfff8aed/pywin32-307-cp312-cp312-win_amd64.whl", hash = "sha256:00d047992bb5dcf79f8b9b7c81f72e0130f9fe4b22df613f755ab1cc021d8347", size = 6519708 }, + { url = "https://files.pythonhosted.org/packages/cd/64/53b1112cb05f85a6c87339a9f90a3b82d67ecb46f16b45abaac3bf4dee2b/pywin32-307-cp312-cp312-win_arm64.whl", hash = "sha256:b53658acbfc6a8241d72cc09e9d1d666be4e6c99376bc59e26cdb6223c4554d2", size = 7952978 }, ] [[package]] @@ -3875,6 +4493,23 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/49/97/fa78e3d2f65c02c8e1268b9aba606569fe97f6c8f7c2d74394553347c145/rsa-4.9-py3-none-any.whl", hash = "sha256:90260d9058e514786967344d0ef75fa8727eed8a7d2e43ce9f4bcf1b536174f7", size = 34315 }, ] +[[package]] +name = "rtree" +version = "1.3.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/6e/79/44fdc619e87bd7b5388f76418719bd8b99de5565475f74a2e0d82b401062/rtree-1.3.0.tar.gz", hash = "sha256:b36e9dd2dc60ffe3d02e367242d2c26f7281b00e1aaf0c39590442edaaadd916", size = 48190 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/06/cc/1b494bde9c99a5cf27e980bf36ef99e76abac6316736231007c04e3a7b28/Rtree-1.3.0-py3-none-macosx_10_9_x86_64.whl", hash = "sha256:80879d9db282a2273ca3a0d896c84583940e9777477727a277624ebfd424c517", size = 475526 }, + { url = "https://files.pythonhosted.org/packages/dd/5b/085d6fad9d45c0cc2acbea5b78c3a2d7f1e7ccc7c05929633461a6a741d8/Rtree-1.3.0-py3-none-macosx_11_0_arm64.whl", hash = "sha256:4328e9e421797c347e6eb08efbbade962fe3664ebd60c1dffe82c40911b1e125", size = 432890 }, + { url = "https://files.pythonhosted.org/packages/12/70/f0553ffb163c47a62c09e4bdc5e0c7fb3392a03cd5a3dbde965aa6a85052/Rtree-1.3.0-py3-none-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:037130d3ce1fc029de81941ec416ba5546f66228380ba19bb41f2ea1294e8423", size = 500384 }, + { url = "https://files.pythonhosted.org/packages/4e/92/3c972e534ce0508214b9ed0cfeba03d1e26d193e8fa624131b5324b91b25/Rtree-1.3.0-py3-none-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:864a05d0c3b7ce6c5e34378b7ab630057603b79179368bc50624258bdf2ff631", size = 569246 }, + { url = "https://files.pythonhosted.org/packages/70/db/6c8bc20061572c33766ade296071d0127e7365d4d3ff54a6c2c075de637b/Rtree-1.3.0-py3-none-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:ec2ed6d1635753dab966e68f592a9c4896f3f4ec6ad2b09b776d592eacd883a9", size = 543195 }, + { url = "https://files.pythonhosted.org/packages/71/2c/5d04fa6010f2d4d4b38078efdc6f371430f499ef2cf7eeced3d18f57daaa/Rtree-1.3.0-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:b4485fb3e5c5e85b94a95f0a930a3848e040d2699cfb012940ba5b0130f1e09a", size = 1416562 }, + { url = "https://files.pythonhosted.org/packages/b6/63/0a2bee2940a8ba116d845ac8b360e49c315a57aeb4aa92ea12a4cb84eb4f/Rtree-1.3.0-py3-none-musllinux_1_2_i686.whl", hash = "sha256:7e2e9211f4fb404c06a08fd2cbebb03234214f73c51913bb371c3d9954e99cc9", size = 1630693 }, + { url = "https://files.pythonhosted.org/packages/10/8a/8a50fc8d58807ba5780485ecc502136aa814f6a08e1cce4f9c4f109ba2b4/Rtree-1.3.0-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:c021f4772b25cc24915da8073e553ded6fa8d0b317caa4202255ed26b2344c1c", size = 1506863 }, + { url = "https://files.pythonhosted.org/packages/85/d2/5bb7617faa3b23b51e2259f9d23e0b33f6ff0ed9811b0d05511e9b7ed84e/Rtree-1.3.0-py3-none-win_amd64.whl", hash = "sha256:97f835801d24c10bbf02381abe5e327345c8296ec711dde7658792376abafc66", size = 377458 }, +] + [[package]] name = "ruff" version = "0.8.2" @@ -3900,6 +4535,63 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/23/34/db20e12d3db11b8a2a8874258f0f6d96a9a4d631659d54575840557164c8/ruff-0.8.2-py3-none-win_arm64.whl", hash = "sha256:fb88e2a506b70cfbc2de6fae6681c4f944f7dd5f2fe87233a7233d888bad73e8", size = 9035131 }, ] +[[package]] +name = "safetensors" +version = "0.4.5" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/cb/46/a1c56ed856c6ac3b1a8b37abe5be0cac53219367af1331e721b04d122577/safetensors-0.4.5.tar.gz", hash = "sha256:d73de19682deabb02524b3d5d1f8b3aaba94c72f1bbfc7911b9b9d5d391c0310", size = 65702 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/38/10/0798ec2c8704c2d172620d8a3725bed92cdd75516357b1a3e64d4229ea4e/safetensors-0.4.5-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:a63eaccd22243c67e4f2b1c3e258b257effc4acd78f3b9d397edc8cf8f1298a7", size = 392312 }, + { url = "https://files.pythonhosted.org/packages/2b/9e/9648d8dbb485c40a4a0212b7537626ae440b48156cc74601ca0b7a7615e0/safetensors-0.4.5-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:23fc9b4ec7b602915cbb4ec1a7c1ad96d2743c322f20ab709e2c35d1b66dad27", size = 381858 }, + { url = "https://files.pythonhosted.org/packages/8b/67/49556aeacc00df353767ed31d68b492fecf38c3f664c52692e4d92aa0032/safetensors-0.4.5-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6885016f34bef80ea1085b7e99b3c1f92cb1be78a49839203060f67b40aee761", size = 441382 }, + { url = "https://files.pythonhosted.org/packages/5d/ce/e9f4869a37bb11229e6cdb4e73a6ef23b4f360eee9dca5f7e40982779704/safetensors-0.4.5-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:133620f443450429322f238fda74d512c4008621227fccf2f8cf4a76206fea7c", size = 439001 }, + { url = "https://files.pythonhosted.org/packages/a0/27/aee8cf031b89c34caf83194ec6b7f2eed28d053fff8b6da6d00c85c56035/safetensors-0.4.5-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4fb3e0609ec12d2a77e882f07cced530b8262027f64b75d399f1504ffec0ba56", size = 478026 }, + { url = "https://files.pythonhosted.org/packages/da/33/1d9fc4805c623636e7d460f28eec92ebd1856f7a552df8eb78398a1ef4de/safetensors-0.4.5-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d0f1dd769f064adc33831f5e97ad07babbd728427f98e3e1db6902e369122737", size = 495545 }, + { url = "https://files.pythonhosted.org/packages/b9/df/6f766b56690709d22e83836e4067a1109a7d84ea152a6deb5692743a2805/safetensors-0.4.5-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c6d156bdb26732feada84f9388a9f135528c1ef5b05fae153da365ad4319c4c5", size = 435016 }, + { url = "https://files.pythonhosted.org/packages/90/fa/7bc3f18086201b1e55a42c88b822ae197d0158e12c54cd45c887305f1b7e/safetensors-0.4.5-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:9e347d77e2c77eb7624400ccd09bed69d35c0332f417ce8c048d404a096c593b", size = 456273 }, + { url = "https://files.pythonhosted.org/packages/3e/59/2ae50150d37a65c1c5f01aec74dc737707b8bbecdc76307e5a1a12c8a376/safetensors-0.4.5-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:9f556eea3aec1d3d955403159fe2123ddd68e880f83954ee9b4a3f2e15e716b6", size = 619669 }, + { url = "https://files.pythonhosted.org/packages/fe/43/10f0bb597aef62c9c154152e265057089f3c729bdd980e6c32c3ec2407a4/safetensors-0.4.5-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:9483f42be3b6bc8ff77dd67302de8ae411c4db39f7224dec66b0eb95822e4163", size = 605212 }, + { url = "https://files.pythonhosted.org/packages/7c/75/ede6887ea0ceaba55730988bfc7668dc147a8758f907fa6db26fbb681b8e/safetensors-0.4.5-cp310-none-win32.whl", hash = "sha256:7389129c03fadd1ccc37fd1ebbc773f2b031483b04700923c3511d2a939252cc", size = 272652 }, + { url = "https://files.pythonhosted.org/packages/ba/f0/919c72a9eef843781e652d0650f2819039943e69b69d5af2d0451a23edc3/safetensors-0.4.5-cp310-none-win_amd64.whl", hash = "sha256:e98ef5524f8b6620c8cdef97220c0b6a5c1cef69852fcd2f174bb96c2bb316b1", size = 285879 }, + { url = "https://files.pythonhosted.org/packages/9a/a5/25bcf75e373412daf1fd88045ab3aa8140a0d804ef0e70712c4f2c5b94d8/safetensors-0.4.5-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:21f848d7aebd5954f92538552d6d75f7c1b4500f51664078b5b49720d180e47c", size = 392256 }, + { url = "https://files.pythonhosted.org/packages/08/8c/ece3bf8756506a890bd980eca02f47f9d98dfbf5ce16eda1368f53560f67/safetensors-0.4.5-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:bb07000b19d41e35eecef9a454f31a8b4718a185293f0d0b1c4b61d6e4487971", size = 381490 }, + { url = "https://files.pythonhosted.org/packages/39/83/c4a7ce01d626e46ea2b45887f2e59b16441408031e2ce2f9fe01860c6946/safetensors-0.4.5-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:09dedf7c2fda934ee68143202acff6e9e8eb0ddeeb4cfc24182bef999efa9f42", size = 441093 }, + { url = "https://files.pythonhosted.org/packages/47/26/cc52de647e71bd9a0b0d78ead0d31d9c462b35550a817aa9e0cab51d6db4/safetensors-0.4.5-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:59b77e4b7a708988d84f26de3ebead61ef1659c73dcbc9946c18f3b1786d2688", size = 438960 }, + { url = "https://files.pythonhosted.org/packages/06/78/332538546775ee97e749867df2d58f2282d9c48a1681e4891eed8b94ec94/safetensors-0.4.5-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5d3bc83e14d67adc2e9387e511097f254bd1b43c3020440e708858c684cbac68", size = 478031 }, + { url = "https://files.pythonhosted.org/packages/d9/03/a3c8663f1ddda54e624ecf43fce651659b49e8e1603c52c3e464b442acfa/safetensors-0.4.5-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:39371fc551c1072976073ab258c3119395294cf49cdc1f8476794627de3130df", size = 494754 }, + { url = "https://files.pythonhosted.org/packages/e6/ee/69e498a892f208bd1da4104d4b9be887f8611bf4942144718b6738482250/safetensors-0.4.5-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a6c19feda32b931cae0acd42748a670bdf56bee6476a046af20181ad3fee4090", size = 435013 }, + { url = "https://files.pythonhosted.org/packages/a2/61/f0cfce984515b86d1260f556ba3b782158e2855e6a318446ac2613786fa9/safetensors-0.4.5-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:a659467495de201e2f282063808a41170448c78bada1e62707b07a27b05e6943", size = 455984 }, + { url = "https://files.pythonhosted.org/packages/e7/a9/3e3b48fcaade3eb4e347d39ebf0bd44291db21a3e4507854b42a7cb910ac/safetensors-0.4.5-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:bad5e4b2476949bcd638a89f71b6916fa9a5cae5c1ae7eede337aca2100435c0", size = 619513 }, + { url = "https://files.pythonhosted.org/packages/80/23/2a7a1be24258c0e44c1d356896fd63dc0545a98d2d0184925fa09cd3ec76/safetensors-0.4.5-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:a3a315a6d0054bc6889a17f5668a73f94f7fe55121ff59e0a199e3519c08565f", size = 604841 }, + { url = "https://files.pythonhosted.org/packages/b4/5c/34d082ff1fffffd8545fb22cbae3285ab4236f1f0cfc64b7e58261c2363b/safetensors-0.4.5-cp311-none-win32.whl", hash = "sha256:a01e232e6d3d5cf8b1667bc3b657a77bdab73f0743c26c1d3c5dd7ce86bd3a92", size = 272602 }, + { url = "https://files.pythonhosted.org/packages/6d/41/948c96c8a7e9fef57c2e051f1871c108a6dbbc6d285598bdb1d89b98617c/safetensors-0.4.5-cp311-none-win_amd64.whl", hash = "sha256:cbd39cae1ad3e3ef6f63a6f07296b080c951f24cec60188378e43d3713000c04", size = 285973 }, + { url = "https://files.pythonhosted.org/packages/bf/ac/5a63082f931e99200db95fd46fb6734f050bb6e96bf02521904c6518b7aa/safetensors-0.4.5-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:473300314e026bd1043cef391bb16a8689453363381561b8a3e443870937cc1e", size = 392015 }, + { url = "https://files.pythonhosted.org/packages/73/95/ab32aa6e9bdc832ff87784cdf9da26192b93de3ef82b8d1ada8f345c5044/safetensors-0.4.5-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:801183a0f76dc647f51a2d9141ad341f9665602a7899a693207a82fb102cc53e", size = 381774 }, + { url = "https://files.pythonhosted.org/packages/d6/6c/7e04b7626809fc63f3698f4c50e43aff2864b40089aa4506c918a75b8eed/safetensors-0.4.5-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1524b54246e422ad6fb6aea1ac71edeeb77666efa67230e1faf6999df9b2e27f", size = 441134 }, + { url = "https://files.pythonhosted.org/packages/58/2b/ffe7c86a277e6c1595fbdf415cfe2903f253f574a5405e93fda8baaa582c/safetensors-0.4.5-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:b3139098e3e8b2ad7afbca96d30ad29157b50c90861084e69fcb80dec7430461", size = 438467 }, + { url = "https://files.pythonhosted.org/packages/67/9c/f271bd804e08c7fda954d17b70ff281228a88077337a9e70feace4f4cc93/safetensors-0.4.5-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:65573dc35be9059770808e276b017256fa30058802c29e1038eb1c00028502ea", size = 476566 }, + { url = "https://files.pythonhosted.org/packages/4c/ad/4cf76a3e430a8a26108407fa6cb93e6f80d996a5cb75d9540c8fe3862990/safetensors-0.4.5-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:fd33da8e9407559f8779c82a0448e2133737f922d71f884da27184549416bfed", size = 492253 }, + { url = "https://files.pythonhosted.org/packages/d9/40/a6f75ea449a9647423ec8b6f72c16998d35aa4b43cb38536ac060c5c7bf5/safetensors-0.4.5-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3685ce7ed036f916316b567152482b7e959dc754fcc4a8342333d222e05f407c", size = 434769 }, + { url = "https://files.pythonhosted.org/packages/52/47/d4b49b1231abf3131f7bb0bc60ebb94b27ee33e0a1f9569da05f8ac65dee/safetensors-0.4.5-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:dde2bf390d25f67908278d6f5d59e46211ef98e44108727084d4637ee70ab4f1", size = 457166 }, + { url = "https://files.pythonhosted.org/packages/c3/cd/006468b03b0fa42ff82d795d47c4193e99001e96c3f08bd62ef1b5cab586/safetensors-0.4.5-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:7469d70d3de970b1698d47c11ebbf296a308702cbaae7fcb993944751cf985f4", size = 619280 }, + { url = "https://files.pythonhosted.org/packages/22/4d/b6208d918e83daa84b424c0ac3191ae61b44b3191613a3a5a7b38f94b8ad/safetensors-0.4.5-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:3a6ba28118636a130ccbb968bc33d4684c48678695dba2590169d5ab03a45646", size = 605390 }, + { url = "https://files.pythonhosted.org/packages/e8/20/bf0e01825dc01ed75538021a98b9a046e60ead63c6c6700764c821a8c873/safetensors-0.4.5-cp312-none-win32.whl", hash = "sha256:c859c7ed90b0047f58ee27751c8e56951452ed36a67afee1b0a87847d065eec6", size = 273250 }, + { url = "https://files.pythonhosted.org/packages/f1/5f/ab6b6cec85b40789801f35b7d2fb579ae242d8193929974a106d5ff5c835/safetensors-0.4.5-cp312-none-win_amd64.whl", hash = "sha256:b5a8810ad6a6f933fff6c276eae92c1da217b39b4d8b1bc1c0b8af2d270dc532", size = 286307 }, + { url = "https://files.pythonhosted.org/packages/cf/ff/037ae4c0ee32db496669365e66079b6329906c6814722b159aa700e67208/safetensors-0.4.5-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:fdadf66b5a22ceb645d5435a0be7a0292ce59648ca1d46b352f13cff3ea80410", size = 392951 }, + { url = "https://files.pythonhosted.org/packages/f1/d6/6621e16b35bf83ae099eaab07338f04991a26c9aa43879d05f19f35e149c/safetensors-0.4.5-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:d42ffd4c2259f31832cb17ff866c111684c87bd930892a1ba53fed28370c918c", size = 383417 }, + { url = "https://files.pythonhosted.org/packages/ae/88/3068e1bb16f5e9f9068901de3cf7b3db270b9bfe6e7d51d4b55c1da0425d/safetensors-0.4.5-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:dd8a1f6d2063a92cd04145c7fd9e31a1c7d85fbec20113a14b487563fdbc0597", size = 442311 }, + { url = "https://files.pythonhosted.org/packages/f7/15/a2bb77ebbaa76b61ec2e9f731fe4db7f9473fd855d881957c51b3a168892/safetensors-0.4.5-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:951d2fcf1817f4fb0ef0b48f6696688a4e852a95922a042b3f96aaa67eedc920", size = 436678 }, + { url = "https://files.pythonhosted.org/packages/ec/79/9608c4546cdbfe3860dd7aa59e3562c9289113398b1a0bd89b68ce0a9d41/safetensors-0.4.5-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:6ac85d9a8c1af0e3132371d9f2d134695a06a96993c2e2f0bbe25debb9e3f67a", size = 457316 }, + { url = "https://files.pythonhosted.org/packages/0f/23/b17b483f2857835962ad33e38014efd4911791187e177bc23b057d35bee8/safetensors-0.4.5-pp310-pypy310_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:e3cec4a29eb7fe8da0b1c7988bc3828183080439dd559f720414450de076fcab", size = 620565 }, + { url = "https://files.pythonhosted.org/packages/19/46/5d11dc300feaad285c2f1bd784ff3f689f5e0ab6be49aaf568f3a77019eb/safetensors-0.4.5-pp310-pypy310_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:21742b391b859e67b26c0b2ac37f52c9c0944a879a25ad2f9f9f3cd61e7fda8f", size = 606660 }, +] + +[package.optional-dependencies] +torch = [ + { name = "numpy" }, + { name = "torch" }, +] + [[package]] name = "schema" version = "0.7.7" @@ -3909,6 +4601,74 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/ad/1b/81855a88c6db2b114d5b2e9f96339190d5ee4d1b981d217fa32127bb00e0/schema-0.7.7-py2.py3-none-any.whl", hash = "sha256:5d976a5b50f36e74e2157b47097b60002bd4d42e65425fcc9c9befadb4255dde", size = 18632 }, ] +[[package]] +name = "scikit-image" +version = "0.25.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "imageio" }, + { name = "lazy-loader" }, + { name = "networkx" }, + { name = "numpy" }, + { name = "packaging" }, + { name = "pillow" }, + { name = "scipy" }, + { name = "tifffile" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/e6/8d/383e5438c807804b66d68ed2c09202d185ea781b6022aa8b9fac3851137f/scikit_image-0.25.0.tar.gz", hash = "sha256:58d94fea11b6b3306b3770417dc1cbca7fa9bcbd6a13945d7910399c88c2018c", size = 22696477 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/4f/13/c3ae240871592139d80b77a531b39fc644d480f219520cedde5a701deb05/scikit_image-0.25.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:2e1ab19beedb2adaaf5173b0c508687a4c7d392ffb1c21513887ba2331b517e3", size = 13984587 }, + { url = "https://files.pythonhosted.org/packages/b6/01/eb0a7f29db6d215a95af4a6d56086fb3fb98385efcd840271e3e6b9c7459/scikit_image-0.25.0-cp310-cp310-macosx_12_0_arm64.whl", hash = "sha256:6a749e8d7ba1216e3bd0da7156ddf6e1d9a4d03aa8bc86880b29aadd954b0b11", size = 13187433 }, + { url = "https://files.pythonhosted.org/packages/9d/49/866c3acc5dce86fffbc0852c1090b4df9b36407680691b1e04a4315f4851/scikit_image-0.25.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2a8e6c3d58ab8cad95cd695bd0fe1be8b8708acdf02ebfcb6c0225e267250021", size = 14152916 }, + { url = "https://files.pythonhosted.org/packages/c9/6b/bd86ed3813d5da0c118ea971577532abbaacaed154cc1e10cf7aa83d041b/scikit_image-0.25.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bd419e765f33a43eebb3509cdab382938085c9e269c01d8da80dbe519e89ec3f", size = 14767782 }, + { url = "https://files.pythonhosted.org/packages/a0/9e/38a8e351227cedf246ddaa62d0c550396c9a436b992d2bdca019f16a2b23/scikit_image-0.25.0-cp310-cp310-win_amd64.whl", hash = "sha256:ea2577b6f68cba3a06ac6f362ab39a62701fefce2138a6bf3e978ecbab71a024", size = 12808323 }, + { url = "https://files.pythonhosted.org/packages/3e/5c/8182c9e7560a46a7c138c855b8b1804ddf5dc4c0a926fbc0f3c4092d2112/scikit_image-0.25.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:7e235726d9b404527445679030209965c5365767b8728584fadd8dbfa29e29de", size = 13998703 }, + { url = "https://files.pythonhosted.org/packages/ed/26/0188429b5a81cb58255b73a9c22bd22853438ab3c066f287db045efb5938/scikit_image-0.25.0-cp311-cp311-macosx_12_0_arm64.whl", hash = "sha256:854b88b7d8b862ccd8f22e660c16fd54f9430c87e079c8dfe46c7f0cebbb1de3", size = 13175073 }, + { url = "https://files.pythonhosted.org/packages/24/12/46688700f5c3b54976a56500f8f4294147fbbd252dde357e228671024436/scikit_image-0.25.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:70e2d90b5bfffffe0880d25d40ddab9ca5c145912461d6c8f6bd3449f4e527bb", size = 14144390 }, + { url = "https://files.pythonhosted.org/packages/35/e8/67e4bd1c5f6c4cd0f53505ebb9eb15f143d6fed1fb4938b542013fa3ec25/scikit_image-0.25.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d4287052dcd8fe63934daa6cbe28b2abe817d75e9b952290fdb4de42254740fc", size = 14783976 }, + { url = "https://files.pythonhosted.org/packages/26/72/0653e3274310972bd053fc9271aa29df2de0d51ad2db2d47b199bf6070d5/scikit_image-0.25.0-cp311-cp311-win_amd64.whl", hash = "sha256:d1e25ff6a3cdd8be938a5a06b441020aac304fa9f457a808bd359f5260468739", size = 12787254 }, + { url = "https://files.pythonhosted.org/packages/21/6a/a8df6953a85042a8a219c97e1758486b997c9dd319e1c474362229406e57/scikit_image-0.25.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:7e63f18b10f9b74590d2ca62cbc4147e696a5e72cfcbcd4af52395fd94fcdc6e", size = 13981411 }, + { url = "https://files.pythonhosted.org/packages/dd/4c/e40a77c57a6b90dda710bc64ed761c93e7b3dd1cef3815675a2bc6807755/scikit_image-0.25.0-cp312-cp312-macosx_12_0_arm64.whl", hash = "sha256:bad4af5edf58775607c153af5bc3f193c2b67261ea9817b62362c746e439d094", size = 13230600 }, + { url = "https://files.pythonhosted.org/packages/63/3f/fac8e1eefbe4a885fa1c9a384db8e11e47c19ab5558b25f370ade3901868/scikit_image-0.25.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:44f7681ff99eed2c33d993bc4bfc17b62e6cadbca1081c7fdbb3607ce89b15e6", size = 14173033 }, + { url = "https://files.pythonhosted.org/packages/47/fe/f09efbf54782996a7f1d3db0e33cb9097f3cc6033392fb53459d7254fa7c/scikit_image-0.25.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:758f55d858aa796114a4275051ca4bb41d8b40c53eb78cb60f0b1ed235d4144b", size = 15002211 }, + { url = "https://files.pythonhosted.org/packages/89/30/4f95a7462411def5563c01d56674bd122bd6db55ae1e8c31ad68586e2d27/scikit_image-0.25.0-cp312-cp312-win_amd64.whl", hash = "sha256:4f7178c6fb6163710571522847326ad936a603646255b22d3d76b6ba58153890", size = 12894520 }, +] + +[[package]] +name = "scipy" +version = "1.14.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "numpy" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/62/11/4d44a1f274e002784e4dbdb81e0ea96d2de2d1045b2132d5af62cc31fd28/scipy-1.14.1.tar.gz", hash = "sha256:5a275584e726026a5699459aa72f828a610821006228e841b94275c4a7c08417", size = 58620554 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/64/68/3bc0cfaf64ff507d82b1e5d5b64521df4c8bf7e22bc0b897827cbee9872c/scipy-1.14.1-cp310-cp310-macosx_10_13_x86_64.whl", hash = "sha256:b28d2ca4add7ac16ae8bb6632a3c86e4b9e4d52d3e34267f6e1b0c1f8d87e389", size = 39069598 }, + { url = "https://files.pythonhosted.org/packages/43/a5/8d02f9c372790326ad405d94f04d4339482ec082455b9e6e288f7100513b/scipy-1.14.1-cp310-cp310-macosx_12_0_arm64.whl", hash = "sha256:d0d2821003174de06b69e58cef2316a6622b60ee613121199cb2852a873f8cf3", size = 29879676 }, + { url = "https://files.pythonhosted.org/packages/07/42/0e0bea9666fcbf2cb6ea0205db42c81b1f34d7b729ba251010edf9c80ebd/scipy-1.14.1-cp310-cp310-macosx_14_0_arm64.whl", hash = "sha256:8bddf15838ba768bb5f5083c1ea012d64c9a444e16192762bd858f1e126196d0", size = 23088696 }, + { url = "https://files.pythonhosted.org/packages/15/47/298ab6fef5ebf31b426560e978b8b8548421d4ed0bf99263e1eb44532306/scipy-1.14.1-cp310-cp310-macosx_14_0_x86_64.whl", hash = "sha256:97c5dddd5932bd2a1a31c927ba5e1463a53b87ca96b5c9bdf5dfd6096e27efc3", size = 25470699 }, + { url = "https://files.pythonhosted.org/packages/d8/df/cdb6be5274bc694c4c22862ac3438cb04f360ed9df0aecee02ce0b798380/scipy-1.14.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2ff0a7e01e422c15739ecd64432743cf7aae2b03f3084288f399affcefe5222d", size = 35606631 }, + { url = "https://files.pythonhosted.org/packages/47/78/b0c2c23880dd1e99e938ad49ccfb011ae353758a2dc5ed7ee59baff684c3/scipy-1.14.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8e32dced201274bf96899e6491d9ba3e9a5f6b336708656466ad0522d8528f69", size = 41178528 }, + { url = "https://files.pythonhosted.org/packages/5d/aa/994b45c34b897637b853ec04334afa55a85650a0d11dacfa67232260fb0a/scipy-1.14.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:8426251ad1e4ad903a4514712d2fa8fdd5382c978010d1c6f5f37ef286a713ad", size = 42784535 }, + { url = "https://files.pythonhosted.org/packages/e7/1c/8daa6df17a945cb1a2a1e3bae3c49643f7b3b94017ff01a4787064f03f84/scipy-1.14.1-cp310-cp310-win_amd64.whl", hash = "sha256:a49f6ed96f83966f576b33a44257d869756df6cf1ef4934f59dd58b25e0327e5", size = 44772117 }, + { url = "https://files.pythonhosted.org/packages/b2/ab/070ccfabe870d9f105b04aee1e2860520460ef7ca0213172abfe871463b9/scipy-1.14.1-cp311-cp311-macosx_10_13_x86_64.whl", hash = "sha256:2da0469a4ef0ecd3693761acbdc20f2fdeafb69e6819cc081308cc978153c675", size = 39076999 }, + { url = "https://files.pythonhosted.org/packages/a7/c5/02ac82f9bb8f70818099df7e86c3ad28dae64e1347b421d8e3adf26acab6/scipy-1.14.1-cp311-cp311-macosx_12_0_arm64.whl", hash = "sha256:c0ee987efa6737242745f347835da2cc5bb9f1b42996a4d97d5c7ff7928cb6f2", size = 29894570 }, + { url = "https://files.pythonhosted.org/packages/ed/05/7f03e680cc5249c4f96c9e4e845acde08eb1aee5bc216eff8a089baa4ddb/scipy-1.14.1-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:3a1b111fac6baec1c1d92f27e76511c9e7218f1695d61b59e05e0fe04dc59617", size = 23103567 }, + { url = "https://files.pythonhosted.org/packages/5e/fc/9f1413bef53171f379d786aabc104d4abeea48ee84c553a3e3d8c9f96a9c/scipy-1.14.1-cp311-cp311-macosx_14_0_x86_64.whl", hash = "sha256:8475230e55549ab3f207bff11ebfc91c805dc3463ef62eda3ccf593254524ce8", size = 25499102 }, + { url = "https://files.pythonhosted.org/packages/c2/4b/b44bee3c2ddc316b0159b3d87a3d467ef8d7edfd525e6f7364a62cd87d90/scipy-1.14.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:278266012eb69f4a720827bdd2dc54b2271c97d84255b2faaa8f161a158c3b37", size = 35586346 }, + { url = "https://files.pythonhosted.org/packages/93/6b/701776d4bd6bdd9b629c387b5140f006185bd8ddea16788a44434376b98f/scipy-1.14.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fef8c87f8abfb884dac04e97824b61299880c43f4ce675dd2cbeadd3c9b466d2", size = 41165244 }, + { url = "https://files.pythonhosted.org/packages/06/57/e6aa6f55729a8f245d8a6984f2855696c5992113a5dc789065020f8be753/scipy-1.14.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:b05d43735bb2f07d689f56f7b474788a13ed8adc484a85aa65c0fd931cf9ccd2", size = 42817917 }, + { url = "https://files.pythonhosted.org/packages/ea/c2/5ecadc5fcccefaece775feadcd795060adf5c3b29a883bff0e678cfe89af/scipy-1.14.1-cp311-cp311-win_amd64.whl", hash = "sha256:716e389b694c4bb564b4fc0c51bc84d381735e0d39d3f26ec1af2556ec6aad94", size = 44781033 }, + { url = "https://files.pythonhosted.org/packages/c0/04/2bdacc8ac6387b15db6faa40295f8bd25eccf33f1f13e68a72dc3c60a99e/scipy-1.14.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:631f07b3734d34aced009aaf6fedfd0eb3498a97e581c3b1e5f14a04164a456d", size = 39128781 }, + { url = "https://files.pythonhosted.org/packages/c8/53/35b4d41f5fd42f5781dbd0dd6c05d35ba8aa75c84ecddc7d44756cd8da2e/scipy-1.14.1-cp312-cp312-macosx_12_0_arm64.whl", hash = "sha256:af29a935803cc707ab2ed7791c44288a682f9c8107bc00f0eccc4f92c08d6e07", size = 29939542 }, + { url = "https://files.pythonhosted.org/packages/66/67/6ef192e0e4d77b20cc33a01e743b00bc9e68fb83b88e06e636d2619a8767/scipy-1.14.1-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:2843f2d527d9eebec9a43e6b406fb7266f3af25a751aa91d62ff416f54170bc5", size = 23148375 }, + { url = "https://files.pythonhosted.org/packages/f6/32/3a6dedd51d68eb7b8e7dc7947d5d841bcb699f1bf4463639554986f4d782/scipy-1.14.1-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:eb58ca0abd96911932f688528977858681a59d61a7ce908ffd355957f7025cfc", size = 25578573 }, + { url = "https://files.pythonhosted.org/packages/f0/5a/efa92a58dc3a2898705f1dc9dbaf390ca7d4fba26d6ab8cfffb0c72f656f/scipy-1.14.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:30ac8812c1d2aab7131a79ba62933a2a76f582d5dbbc695192453dae67ad6310", size = 35319299 }, + { url = "https://files.pythonhosted.org/packages/8e/ee/8a26858ca517e9c64f84b4c7734b89bda8e63bec85c3d2f432d225bb1886/scipy-1.14.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8f9ea80f2e65bdaa0b7627fb00cbeb2daf163caa015e59b7516395fe3bd1e066", size = 40849331 }, + { url = "https://files.pythonhosted.org/packages/a5/cd/06f72bc9187840f1c99e1a8750aad4216fc7dfdd7df46e6280add14b4822/scipy-1.14.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:edaf02b82cd7639db00dbff629995ef185c8df4c3ffa71a5562a595765a06ce1", size = 42544049 }, + { url = "https://files.pythonhosted.org/packages/aa/7d/43ab67228ef98c6b5dd42ab386eae2d7877036970a0d7e3dd3eb47a0d530/scipy-1.14.1-cp312-cp312-win_amd64.whl", hash = "sha256:2ff38e22128e6c03ff73b6bb0f85f897d2362f8c052e3b8ad00532198fbdae3f", size = 44521212 }, +] + [[package]] name = "selenium" version = "4.25.0" @@ -3926,6 +4686,19 @@ 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 = "semchunk" +version = "2.2.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "mpire", extra = ["dill"] }, + { name = "tqdm" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/b1/d6/edca6e3ac07b08a761cbc9fa54a1c4db5c9af9b62233c0d4046d363f022e/semchunk-2.2.0.tar.gz", hash = "sha256:4de761ce614036fa3bea61adbe47e3ade7c96ac9b062f223b3ac353dbfd26743", size = 12060 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f8/85/3940bb4c586e10603d169d13ffccd59ed32fcb8d1b8104c3aef0e525b3b2/semchunk-2.2.0-py3-none-any.whl", hash = "sha256:7db19ca90ddb48f99265e789e07a7bb111ae25185f9cc3d44b94e1e61b9067fc", size = 10243 }, +] + [[package]] name = "setuptools" version = "75.2.0" @@ -4111,6 +4884,18 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/d9/5f/8c716e47b3a50cbd7c146f45881e11d9414def768b7cd9c5e6650ec2a80a/termcolor-2.4.0-py3-none-any.whl", hash = "sha256:9297c0df9c99445c2412e832e882a7884038a25617c60cea2ad69488d4040d63", size = 7719 }, ] +[[package]] +name = "tifffile" +version = "2024.12.12" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "numpy" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/37/c9/fc4e490c5b0ccad68c98ea1d6e0f409bd7d50e2e8fc30a0725594d3104ff/tifffile-2024.12.12.tar.gz", hash = "sha256:c38e929bf74c04b6c8708d87f16b32c85c6d7c2514b99559ea3db8003ba4edda", size = 365416 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d8/1e/76cbc758f6865a9da18001ac70d1a4154603b71e233f704401fc7d62493e/tifffile-2024.12.12-py3-none-any.whl", hash = "sha256:6ff0f196a46a75c8c0661c70995e06ea4d08a81fe343193e69f1673f4807d508", size = 227538 }, +] + [[package]] name = "tiktoken" version = "0.7.0" @@ -4228,6 +5013,69 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/c4/ac/ce90573ba446a9bbe65838ded066a805234d159b4446ae9f8ec5bbd36cbd/tomli_w-1.1.0-py3-none-any.whl", hash = "sha256:1403179c78193e3184bfaade390ddbd071cba48a32a2e62ba11aae47490c63f7", size = 6440 }, ] +[[package]] +name = "torch" +version = "2.4.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "filelock" }, + { name = "fsspec" }, + { name = "jinja2" }, + { name = "networkx" }, + { name = "nvidia-cublas-cu12", marker = "platform_machine == 'x86_64' and platform_system == 'Linux'" }, + { name = "nvidia-cuda-cupti-cu12", marker = "platform_machine == 'x86_64' and platform_system == 'Linux'" }, + { name = "nvidia-cuda-nvrtc-cu12", marker = "platform_machine == 'x86_64' and platform_system == 'Linux'" }, + { name = "nvidia-cuda-runtime-cu12", marker = "platform_machine == 'x86_64' and platform_system == 'Linux'" }, + { name = "nvidia-cudnn-cu12", marker = "platform_machine == 'x86_64' and platform_system == 'Linux'" }, + { name = "nvidia-cufft-cu12", marker = "platform_machine == 'x86_64' and platform_system == 'Linux'" }, + { name = "nvidia-curand-cu12", marker = "platform_machine == 'x86_64' and platform_system == 'Linux'" }, + { name = "nvidia-cusolver-cu12", marker = "platform_machine == 'x86_64' and platform_system == 'Linux'" }, + { name = "nvidia-cusparse-cu12", marker = "platform_machine == 'x86_64' and platform_system == 'Linux'" }, + { name = "nvidia-nccl-cu12", marker = "platform_machine == 'x86_64' and platform_system == 'Linux'" }, + { name = "nvidia-nvtx-cu12", marker = "platform_machine == 'x86_64' and platform_system == 'Linux'" }, + { name = "sympy" }, + { name = "triton", marker = "platform_machine == 'x86_64' and platform_system == 'Linux'" }, + { name = "typing-extensions" }, +] +wheels = [ + { url = "https://files.pythonhosted.org/packages/41/05/d540049b1832d1062510efc6829634b7fbef5394c757d8312414fb65a3cb/torch-2.4.1-cp310-cp310-manylinux1_x86_64.whl", hash = "sha256:362f82e23a4cd46341daabb76fba08f04cd646df9bfaf5da50af97cb60ca4971", size = 797072810 }, + { url = "https://files.pythonhosted.org/packages/a0/12/2162df9c47386ae7cedbc938f9703fee4792d93504fab8608d541e71ece3/torch-2.4.1-cp310-cp310-manylinux2014_aarch64.whl", hash = "sha256:e8ac1985c3ff0f60d85b991954cfc2cc25f79c84545aead422763148ed2759e3", size = 89699259 }, + { url = "https://files.pythonhosted.org/packages/5d/4c/b2a59ff0e265f5ee154f0d81e948b1518b94f545357731e1a3245ee5d45b/torch-2.4.1-cp310-cp310-win_amd64.whl", hash = "sha256:91e326e2ccfb1496e3bee58f70ef605aeb27bd26be07ba64f37dcaac3d070ada", size = 199433813 }, + { url = "https://files.pythonhosted.org/packages/dc/fb/1333ba666bbd53846638dd75a7a1d4eaf964aff1c482fc046e2311a1b499/torch-2.4.1-cp310-none-macosx_11_0_arm64.whl", hash = "sha256:d36a8ef100f5bff3e9c3cea934b9e0d7ea277cb8210c7152d34a9a6c5830eadd", size = 62139309 }, + { url = "https://files.pythonhosted.org/packages/ea/ea/4ab009e953bca6ff35ad75b8ab58c0923308636c182c145dc63084f7d136/torch-2.4.1-cp311-cp311-manylinux1_x86_64.whl", hash = "sha256:0b5f88afdfa05a335d80351e3cea57d38e578c8689f751d35e0ff36bce872113", size = 797111232 }, + { url = "https://files.pythonhosted.org/packages/8f/a1/b31f94b4631c1731261db9fdc9a749ef58facc3b76094a6fe974f611f239/torch-2.4.1-cp311-cp311-manylinux2014_aarch64.whl", hash = "sha256:ef503165f2341942bfdf2bd520152f19540d0c0e34961232f134dc59ad435be8", size = 89719574 }, + { url = "https://files.pythonhosted.org/packages/5a/6a/775b93d6888c31f1f1fc457e4f5cc89f0984412d5dcdef792b8f2aa6e812/torch-2.4.1-cp311-cp311-win_amd64.whl", hash = "sha256:092e7c2280c860eff762ac08c4bdcd53d701677851670695e0c22d6d345b269c", size = 199436128 }, + { url = "https://files.pythonhosted.org/packages/1f/34/c93873c37f93154d982172755f7e504fdbae6c760499303a3111ce6ce327/torch-2.4.1-cp311-none-macosx_11_0_arm64.whl", hash = "sha256:ddddbd8b066e743934a4200b3d54267a46db02106876d21cf31f7da7a96f98ea", size = 62145176 }, + { url = "https://files.pythonhosted.org/packages/cc/df/5204a13a7a973c23c7ade615bafb1a3112b5d0ec258d8390f078fa4ab0f7/torch-2.4.1-cp312-cp312-manylinux1_x86_64.whl", hash = "sha256:fdc4fe11db3eb93c1115d3e973a27ac7c1a8318af8934ffa36b0370efe28e042", size = 797019590 }, + { url = "https://files.pythonhosted.org/packages/4f/16/d23a689e5ef8001ed2ace1a3a59f2fda842889b0c3f3877799089925282a/torch-2.4.1-cp312-cp312-manylinux2014_aarch64.whl", hash = "sha256:18835374f599207a9e82c262153c20ddf42ea49bc76b6eadad8e5f49729f6e4d", size = 89613802 }, + { url = "https://files.pythonhosted.org/packages/a8/e0/ca8354dfb8d834a76da51b06e8248b70fc182bc163540507919124974bdf/torch-2.4.1-cp312-cp312-win_amd64.whl", hash = "sha256:ebea70ff30544fc021d441ce6b219a88b67524f01170b1c538d7d3ebb5e7f56c", size = 199387694 }, + { url = "https://files.pythonhosted.org/packages/ac/30/8b6f77ea4ce84f015ee024b8dfef0dac289396254e8bfd493906d4cbb848/torch-2.4.1-cp312-none-macosx_11_0_arm64.whl", hash = "sha256:72b484d5b6cec1a735bf3fa5a1c4883d01748698c5e9cfdbeb4ffab7c7987e0d", size = 62123443 }, +] + +[[package]] +name = "torchvision" +version = "0.19.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "numpy" }, + { name = "pillow" }, + { name = "torch" }, +] +wheels = [ + { url = "https://files.pythonhosted.org/packages/d4/90/cab820b96d4d1a36b088774209d2379cf49eda8210c8fee13552383860b7/torchvision-0.19.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:54e8513099e6f586356c70f809d34f391af71ad182fe071cc328a28af2c40608", size = 1660236 }, + { url = "https://files.pythonhosted.org/packages/72/55/e0b3821c5595a9a2c8ec98d234b4a0d1142d91daac61f007503d3158f857/torchvision-0.19.1-cp310-cp310-manylinux1_x86_64.whl", hash = "sha256:20a1f5e02bfdad7714e55fa3fa698347c11d829fa65e11e5a84df07d93350eed", size = 7026373 }, + { url = "https://files.pythonhosted.org/packages/db/71/da0f71c2765feee125b1dc280a6432aa88c510aedf9a36987f3fe7ed05ea/torchvision-0.19.1-cp310-cp310-manylinux2014_aarch64.whl", hash = "sha256:7b063116164be52fc6deb4762de7f8c90bfa3a65f8d5caf17f8e2d5aadc75a04", size = 14072253 }, + { url = "https://files.pythonhosted.org/packages/f7/8e/cbae11f8046d433881b478afc9e7589a76158124779cbc3a40163ec716bf/torchvision-0.19.1-cp310-cp310-win_amd64.whl", hash = "sha256:f40b6acabfa886da1bc3768f47679c61feee6bde90deb979d9f300df8c8a0145", size = 1288329 }, + { url = "https://files.pythonhosted.org/packages/66/f6/a2f07a3f5385b37c45b8e14448b8610a8618dfad18ea437cb23b4edc50c5/torchvision-0.19.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:40514282b4896d62765b8e26d7091c32e17c35817d00ec4be2362ea3ba3d1787", size = 1660235 }, + { url = "https://files.pythonhosted.org/packages/28/9d/40d1b943bbbd02a30d6b4f691d6de37a7e4c92f90bed0f8f47379e90eec6/torchvision-0.19.1-cp311-cp311-manylinux1_x86_64.whl", hash = "sha256:5a91be061ae5d6d5b95e833b93e57ca4d3c56c5a57444dd15da2e3e7fba96050", size = 7026152 }, + { url = "https://files.pythonhosted.org/packages/36/04/36e1d35b864f4a7c8f3056a427542b14b3bcdbc66edd36faadee109b86c5/torchvision-0.19.1-cp311-cp311-manylinux2014_aarch64.whl", hash = "sha256:d71a6a6fe3a5281ca3487d4c56ad4aad20ff70f82f1d7c79bcb6e7b0c2af00c8", size = 14072255 }, + { url = "https://files.pythonhosted.org/packages/f8/69/dc769cf54df8e828c0b8957b4521f35178f5bd4cc5b8fbe8a37ffd89a27c/torchvision-0.19.1-cp311-cp311-win_amd64.whl", hash = "sha256:70dea324174f5e9981b68e4b7cd524512c106ba64aedef560a86a0bbf2fbf62c", size = 1288330 }, + { url = "https://files.pythonhosted.org/packages/a4/d0/b1029ab95d9219cac2dfc0d835e9ab4cebb01f5cb6b48e736778020fb995/torchvision-0.19.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:27ece277ff0f6cdc7fed0627279c632dcb2e58187da771eca24b0fbcf3f8590d", size = 1660230 }, + { url = "https://files.pythonhosted.org/packages/8b/34/fdd2d9e01228a069b28473a7c020bf1812c8ecab8565666feb247659ed30/torchvision-0.19.1-cp312-cp312-manylinux1_x86_64.whl", hash = "sha256:c659ff92a61f188a1a7baef2850f3c0b6c85685447453c03d0e645ba8f1dcc1c", size = 7026404 }, + { url = "https://files.pythonhosted.org/packages/da/b2/9da42d67dfc30d9e3b161f7a37f6c7eca86a80e6caef4a9aa11727faa4f5/torchvision-0.19.1-cp312-cp312-manylinux2014_aarch64.whl", hash = "sha256:c07bf43c2a145d792ecd9d0503d6c73577147ece508d45600d8aac77e4cdfcf9", size = 14072022 }, + { url = "https://files.pythonhosted.org/packages/6b/b2/fd577e1622b43cdeb74782a60cea4909f88f471813c215ea7b4e7ea84a74/torchvision-0.19.1-cp312-cp312-win_amd64.whl", hash = "sha256:b4283d283675556bb0eae31d29996f53861b17cbdcdf3509e6bc050414ac9289", size = 1288328 }, +] + [[package]] name = "tqdm" version = "4.66.5" @@ -4249,6 +5097,27 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/00/c0/8f5d070730d7836adc9c9b6408dec68c6ced86b304a9b26a14df072a6e8c/traitlets-5.14.3-py3-none-any.whl", hash = "sha256:b74e89e397b1ed28cc831db7aea759ba6640cb3de13090ca145426688ff1ac4f", size = 85359 }, ] +[[package]] +name = "transformers" +version = "4.46.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "filelock" }, + { name = "huggingface-hub" }, + { name = "numpy" }, + { name = "packaging" }, + { name = "pyyaml" }, + { name = "regex" }, + { name = "requests" }, + { name = "safetensors" }, + { name = "tokenizers" }, + { name = "tqdm" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/37/5a/58f96c83e566f907ae39f16d4401bbefd8bb85c60bd1e6a95c419752ab90/transformers-4.46.3.tar.gz", hash = "sha256:8ee4b3ae943fe33e82afff8e837f4b052058b07ca9be3cb5b729ed31295f72cc", size = 8627944 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/51/51/b87caa939fedf307496e4dbf412f4b909af3d9ca8b189fc3b65c1faa456f/transformers-4.46.3-py3-none-any.whl", hash = "sha256:a12ef6f52841fd190a3e5602145b542d03507222f2c64ebb7ee92e8788093aef", size = 10034536 }, +] + [[package]] name = "trio" version = "0.27.0" @@ -4281,6 +5150,19 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/48/be/a9ae5f50cad5b6f85bd2574c2c923730098530096e170c1ce7452394d7aa/trio_websocket-0.11.1-py3-none-any.whl", hash = "sha256:520d046b0d030cf970b8b2b2e00c4c2245b3807853ecd44214acd33d74581638", size = 17408 }, ] +[[package]] +name = "triton" +version = "3.0.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "filelock", marker = "(platform_machine != 'aarch64' and platform_system != 'Darwin') or (platform_system != 'Darwin' and platform_system != 'Linux')" }, +] +wheels = [ + { url = "https://files.pythonhosted.org/packages/45/27/14cc3101409b9b4b9241d2ba7deaa93535a217a211c86c4cc7151fb12181/triton-3.0.0-1-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:e1efef76935b2febc365bfadf74bcb65a6f959a9872e5bddf44cc9e0adce1e1a", size = 209376304 }, + { url = "https://files.pythonhosted.org/packages/33/3e/a2f59384587eff6aeb7d37b6780de7fedd2214935e27520430ca9f5b7975/triton-3.0.0-1-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:5ce8520437c602fb633f1324cc3871c47bee3b67acf9756c1a66309b60e3216c", size = 209438883 }, + { url = "https://files.pythonhosted.org/packages/fe/7b/7757205dee3628f75e7991021d15cd1bd0c9b044ca9affe99b50879fc0e1/triton-3.0.0-1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:34e509deb77f1c067d8640725ef00c5cbfcb2052a1a3cb6a6d343841f92624eb", size = 209464695 }, +] + [[package]] name = "typer" version = "0.12.5" @@ -4674,6 +5556,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/78/58/e860788190eba3bcce367f74d29c4675466ce8dddfba85f7827588416f01/wsproto-1.2.0-py3-none-any.whl", hash = "sha256:b9acddd652b585d75b20477888c56642fdade28bdfd3579aa24a4d2c037dd736", size = 24226 }, ] +[[package]] +name = "xlsxwriter" +version = "3.2.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/a6/c3/b36fa44a0610a0f65d2e65ba6a262cbe2554b819f1449731971f7c16ea3c/XlsxWriter-3.2.0.tar.gz", hash = "sha256:9977d0c661a72866a61f9f7a809e25ebbb0fb7036baa3b9fe74afcfca6b3cb8c", size = 198732 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a7/ea/53d1fe468e63e092cf16e2c18d16f50c29851242f9dd12d6a66e0d7f0d02/XlsxWriter-3.2.0-py3-none-any.whl", hash = "sha256:ecfd5405b3e0e228219bcaf24c2ca0915e012ca9464a14048021d21a995d490e", size = 159925 }, +] + [[package]] name = "yarl" version = "1.16.0" From 9a65abf6b81038128f0c0977d9ecb132a7292382 Mon Sep 17 00:00:00 2001 From: Lorenze Jay <63378463+lorenzejay@users.noreply.github.com> Date: Mon, 23 Dec 2024 10:54:16 -0800 Subject: [PATCH 11/18] removed some redundancies (#1796) * removed some redundancies * cleanup --- .../source/base_file_knowledge_source.py | 35 ++++++++++--------- 1 file changed, 18 insertions(+), 17 deletions(-) diff --git a/src/crewai/knowledge/source/base_file_knowledge_source.py b/src/crewai/knowledge/source/base_file_knowledge_source.py index e086cbf659..8cee77e168 100644 --- a/src/crewai/knowledge/source/base_file_knowledge_source.py +++ b/src/crewai/knowledge/source/base_file_knowledge_source.py @@ -71,28 +71,29 @@ def convert_to_path(self, path: Union[Path, str]) -> Path: def _process_file_paths(self) -> List[Path]: """Convert file_path to a list of Path objects.""" - # Check if old file_path is being used if hasattr(self, "file_path") and self.file_path is not None: self._logger.log( "warning", "The 'file_path' attribute is deprecated and will be removed in a future version. Please use 'file_paths' instead.", color="yellow", ) - paths = ( - [self.file_path] - if isinstance(self.file_path, (str, Path)) - else self.file_path + self.file_paths = self.file_path + + if self.file_paths is None: + raise ValueError("Your source must be provided with a file_paths: []") + + # Convert single path to list + path_list: List[Union[Path, str]] = ( + [self.file_paths] + if isinstance(self.file_paths, (str, Path)) + else list(self.file_paths) + if isinstance(self.file_paths, list) + else [] + ) + + if not path_list: + raise ValueError( + "file_path/file_paths must be a Path, str, or a list of these types" ) - else: - if self.file_paths is None: - raise ValueError("Your source must be provided with a file_paths: []") - elif isinstance(self.file_paths, list) and len(self.file_paths) == 0: - raise ValueError("Empty file_paths are not allowed") - else: - paths = ( - [self.file_paths] - if isinstance(self.file_paths, (str, Path)) - else self.file_paths - ) - return [self.convert_to_path(path) for path in paths] + return [self.convert_to_path(path) for path in path_list] From 6cc2f510bf737b0053ae01a8a6d5582086ae5ec0 Mon Sep 17 00:00:00 2001 From: "Brandon Hancock (bhancock_ai)" <109994880+bhancockio@users.noreply.github.com> Date: Tue, 24 Dec 2024 16:55:44 -0500 Subject: [PATCH 12/18] Feat/joao flow improvement requests (#1795) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Add in or and and in router * In the middle of improving plotting * final plot changes --------- Co-authored-by: João Moura --- src/crewai/flow/flow.py | 157 ++++++++++++------------- src/crewai/flow/utils.py | 56 +++++++-- src/crewai/flow/visualization_utils.py | 94 ++++++++++----- tests/flow_test.py | 59 ++++++++++ 4 files changed, 237 insertions(+), 129 deletions(-) diff --git a/src/crewai/flow/flow.py b/src/crewai/flow/flow.py index ccc76dc95c..4a6361cce4 100644 --- a/src/crewai/flow/flow.py +++ b/src/crewai/flow/flow.py @@ -80,10 +80,27 @@ def decorator(func): return decorator -def router(method): +def router(condition): def decorator(func): func.__is_router__ = True - func.__router_for__ = method.__name__ + # Handle conditions like listen/start + if isinstance(condition, str): + func.__trigger_methods__ = [condition] + func.__condition_type__ = "OR" + elif ( + isinstance(condition, dict) + and "type" in condition + and "methods" in condition + ): + func.__trigger_methods__ = condition["methods"] + func.__condition_type__ = condition["type"] + elif callable(condition) and hasattr(condition, "__name__"): + func.__trigger_methods__ = [condition.__name__] + func.__condition_type__ = "OR" + else: + raise ValueError( + "Condition must be a method, string, or a result of or_() or and_()" + ) return func return decorator @@ -123,8 +140,8 @@ def __new__(mcs, name, bases, dct): start_methods = [] listeners = {} - routers = {} router_paths = {} + routers = set() for attr_name, attr_value in dct.items(): if hasattr(attr_value, "__is_start_method__"): @@ -137,18 +154,11 @@ def __new__(mcs, name, bases, dct): methods = attr_value.__trigger_methods__ condition_type = getattr(attr_value, "__condition_type__", "OR") listeners[attr_name] = (condition_type, methods) - - elif hasattr(attr_value, "__is_router__"): - routers[attr_value.__router_for__] = attr_name - possible_returns = get_possible_return_constants(attr_value) - if possible_returns: - router_paths[attr_name] = possible_returns - - # Register router as a listener to its triggering method - trigger_method_name = attr_value.__router_for__ - methods = [trigger_method_name] - condition_type = "OR" - listeners[attr_name] = (condition_type, methods) + if hasattr(attr_value, "__is_router__") and attr_value.__is_router__: + routers.add(attr_name) + possible_returns = get_possible_return_constants(attr_value) + if possible_returns: + router_paths[attr_name] = possible_returns setattr(cls, "_start_methods", start_methods) setattr(cls, "_listeners", listeners) @@ -163,7 +173,7 @@ class Flow(Generic[T], metaclass=FlowMeta): _start_methods: List[str] = [] _listeners: Dict[str, tuple[str, List[str]]] = {} - _routers: Dict[str, str] = {} + _routers: Set[str] = set() _router_paths: Dict[str, List[str]] = {} initial_state: Union[Type[T], T, None] = None event_emitter = Signal("event_emitter") @@ -210,20 +220,10 @@ def method_outputs(self) -> List[Any]: return self._method_outputs 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 + # Structured state try: - # Define a function to create the dynamic class + def create_model_with_extra_forbid( base_model: Type[BaseModel], ) -> Type[BaseModel]: @@ -233,34 +233,20 @@ class ModelWithExtraForbid(base_model): # type: ignore 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. - """ self.event_emitter.send( self, event=FlowStartedEvent( @@ -274,15 +260,6 @@ def kickoff(self, inputs: Optional[Dict[str, Any]] = None) -> Any: return asyncio.run(self.kickoff_async()) 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 not self._start_methods: raise ValueError("No start method defined") @@ -290,16 +267,12 @@ async def kickoff_async(self, inputs: Optional[Dict[str, Any]] = None) -> Any: self.__class__.__name__, list(self._methods.keys()) ) - # Create tasks for all start methods tasks = [ self._execute_start_method(start_method) for start_method in self._start_methods ] - - # Run all start methods concurrently await asyncio.gather(*tasks) - # Determine the final output (from the last executed method) final_output = self._method_outputs[-1] if self._method_outputs else None self.event_emitter.send( @@ -310,7 +283,6 @@ async def kickoff_async(self, inputs: Optional[Dict[str, Any]] = None) -> Any: result=final_output, ), ) - return final_output async def _execute_start_method(self, start_method_name: str) -> None: @@ -327,49 +299,68 @@ async def _execute_method( if asyncio.iscoroutinefunction(method) else method(*args, **kwargs) ) - self._method_outputs.append(result) # Store the output - - # Track method execution counts + self._method_outputs.append(result) self._method_execution_counts[method_name] = ( self._method_execution_counts.get(method_name, 0) + 1 ) - return result async def _execute_listeners(self, trigger_method: str, result: Any) -> None: - listener_tasks = [] - - if trigger_method in self._routers: - router_method = self._methods[self._routers[trigger_method]] - path = await self._execute_method( - self._routers[trigger_method], router_method + # First, handle routers repeatedly until no router triggers anymore + while True: + routers_triggered = self._find_triggered_methods( + trigger_method, router_only=True ) - trigger_method = path - + if not routers_triggered: + break + for router_name in routers_triggered: + await self._execute_single_listener(router_name, result) + # After executing router, the router's result is the path + # The last router executed sets the trigger_method + # The router result is the last element in self._method_outputs + trigger_method = self._method_outputs[-1] + + # Now that no more routers are triggered by current trigger_method, + # execute normal listeners + listeners_triggered = self._find_triggered_methods( + trigger_method, router_only=False + ) + if listeners_triggered: + tasks = [ + self._execute_single_listener(listener_name, result) + for listener_name in listeners_triggered + ] + await asyncio.gather(*tasks) + + def _find_triggered_methods( + self, trigger_method: str, router_only: bool + ) -> List[str]: + triggered = [] for listener_name, (condition_type, methods) in self._listeners.items(): + is_router = listener_name in self._routers + + if router_only != is_router: + continue + if condition_type == "OR": + # If the trigger_method matches any in methods, run this if trigger_method in methods: - # Schedule the listener without preventing re-execution - listener_tasks.append( - self._execute_single_listener(listener_name, result) - ) + triggered.append(listener_name) elif condition_type == "AND": # 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 trigger_method in self._pending_and_listeners[listener_name]: + 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) - ) + triggered.append(listener_name) # 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 - if listener_tasks: - await asyncio.gather(*listener_tasks) + return triggered async def _execute_single_listener(self, listener_name: str, result: Any) -> None: try: @@ -386,17 +377,13 @@ async def _execute_single_listener(self, listener_name: str, result: Any) -> Non sig = inspect.signature(method) params = list(sig.parameters.values()) - - # Exclude 'self' parameter method_params = [p for p in params if p.name != "self"] if method_params: - # If listener expects parameters, pass the 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(listener_name, method) self.event_emitter.send( @@ -408,8 +395,9 @@ async def _execute_single_listener(self, listener_name: str, result: Any) -> Non ), ) - # Execute listeners of this listener + # Execute listeners (and possibly routers) of this listener await self._execute_listeners(listener_name, listener_result) + except Exception as e: print( f"[Flow._execute_single_listener] Error in method {listener_name}: {e}" @@ -422,5 +410,4 @@ def plot(self, filename: str = "crewai_flow") -> None: self._telemetry.flow_plotting_span( self.__class__.__name__, list(self._methods.keys()) ) - plot_flow(self, filename) diff --git a/src/crewai/flow/utils.py b/src/crewai/flow/utils.py index 98d03f24f6..dc1f611fbb 100644 --- a/src/crewai/flow/utils.py +++ b/src/crewai/flow/utils.py @@ -31,16 +31,50 @@ def get_possible_return_constants(function): print(f"Source code:\n{source}") return None - return_values = [] + return_values = set() + dict_definitions = {} + + class DictionaryAssignmentVisitor(ast.NodeVisitor): + def visit_Assign(self, node): + # Check if this assignment is assigning a dictionary literal to a variable + if isinstance(node.value, ast.Dict) and len(node.targets) == 1: + target = node.targets[0] + if isinstance(target, ast.Name): + var_name = target.id + dict_values = [] + # Extract string values from the dictionary + for val in node.value.values: + if isinstance(val, ast.Constant) and isinstance(val.value, str): + dict_values.append(val.value) + # If non-string, skip or just ignore + if dict_values: + dict_definitions[var_name] = dict_values + self.generic_visit(node) class ReturnVisitor(ast.NodeVisitor): def visit_Return(self, node): - # Check if the return value is a constant (Python 3.8+) - if isinstance(node.value, ast.Constant): - return_values.append(node.value.value) - + # Direct string return + if isinstance(node.value, ast.Constant) and isinstance( + node.value.value, str + ): + return_values.add(node.value.value) + # Dictionary-based return, like return paths[result] + elif isinstance(node.value, ast.Subscript): + # Check if we're subscripting a known dictionary variable + if isinstance(node.value.value, ast.Name): + var_name = node.value.value.id + if var_name in dict_definitions: + # Add all possible dictionary values + for v in dict_definitions[var_name]: + return_values.add(v) + self.generic_visit(node) + + # First pass: identify dictionary assignments + DictionaryAssignmentVisitor().visit(code_ast) + # Second pass: identify returns ReturnVisitor().visit(code_ast) - return return_values + + return list(return_values) if return_values else None def calculate_node_levels(flow): @@ -61,10 +95,7 @@ def calculate_node_levels(flow): current_level = levels[current] visited.add(current) - for listener_name, ( - condition_type, - trigger_methods, - ) in flow._listeners.items(): + for listener_name, (condition_type, trigger_methods) in flow._listeners.items(): if condition_type == "OR": if current in trigger_methods: if ( @@ -89,7 +120,7 @@ def calculate_node_levels(flow): queue.append(listener_name) # Handle router connections - if current in flow._routers.values(): + if current in flow._routers: router_method_name = current paths = flow._router_paths.get(router_method_name, []) for path in paths: @@ -105,6 +136,7 @@ def calculate_node_levels(flow): levels[listener_name] = current_level + 1 if listener_name not in visited: queue.append(listener_name) + return levels @@ -142,7 +174,7 @@ def dfs_ancestors(node, ancestors, visited, flow): dfs_ancestors(listener_name, ancestors, visited, flow) # Handle router methods separately - if node in flow._routers.values(): + if node in flow._routers: router_method_name = node paths = flow._router_paths.get(router_method_name, []) for path in paths: diff --git a/src/crewai/flow/visualization_utils.py b/src/crewai/flow/visualization_utils.py index 5b95a13699..321f633443 100644 --- a/src/crewai/flow/visualization_utils.py +++ b/src/crewai/flow/visualization_utils.py @@ -94,12 +94,14 @@ def add_edges(net, flow, node_positions, colors): ancestors = build_ancestor_dict(flow) parent_children = build_parent_children_dict(flow) + # Edges for normal listeners for method_name in flow._listeners: condition_type, trigger_methods = flow._listeners[method_name] is_and_condition = condition_type == "AND" for trigger in trigger_methods: - if trigger in flow._methods or trigger in flow._routers.values(): + # Check if nodes exist before adding edges + if trigger in node_positions and method_name in node_positions: is_router_edge = any( trigger in paths for paths in flow._router_paths.values() ) @@ -135,7 +137,22 @@ def add_edges(net, flow, node_positions, colors): } net.add_edge(trigger, method_name, **edge_style) + else: + # Nodes not found in node_positions. Check if it's a known router outcome and a known method. + is_router_edge = any( + trigger in paths for paths in flow._router_paths.values() + ) + # Check if method_name is a known method + method_known = method_name in flow._methods + + # If it's a known router edge and the method is known, don't warn. + # This means the path is legitimate, just not reflected as nodes here. + if not (is_router_edge and method_known): + print( + f"Warning: No node found for '{trigger}' or '{method_name}'. Skipping edge." + ) + # Edges for router return paths for router_method_name, paths in flow._router_paths.items(): for path in paths: for listener_name, ( @@ -143,36 +160,49 @@ def add_edges(net, flow, node_positions, colors): trigger_methods, ) in flow._listeners.items(): if path in trigger_methods: - is_cycle_edge = is_ancestor(trigger, method_name, ancestors) - parent_has_multiple_children = ( - len(parent_children.get(router_method_name, [])) > 1 - ) - needs_curvature = is_cycle_edge or parent_has_multiple_children - - if needs_curvature: - source_pos = node_positions.get(router_method_name) - target_pos = node_positions.get(listener_name) - - if source_pos and target_pos: - dx = target_pos[0] - source_pos[0] - smooth_type = "curvedCCW" if dx <= 0 else "curvedCW" - index = get_child_index( - router_method_name, listener_name, parent_children - ) - edge_smooth = { - "type": smooth_type, - "roundness": 0.2 + (0.1 * index), - } + if ( + router_method_name in node_positions + and listener_name in node_positions + ): + is_cycle_edge = is_ancestor( + router_method_name, listener_name, ancestors + ) + parent_has_multiple_children = ( + len(parent_children.get(router_method_name, [])) > 1 + ) + needs_curvature = is_cycle_edge or parent_has_multiple_children + + if needs_curvature: + source_pos = node_positions.get(router_method_name) + target_pos = node_positions.get(listener_name) + + if source_pos and target_pos: + dx = target_pos[0] - source_pos[0] + smooth_type = "curvedCCW" if dx <= 0 else "curvedCW" + index = get_child_index( + router_method_name, listener_name, parent_children + ) + edge_smooth = { + "type": smooth_type, + "roundness": 0.2 + (0.1 * index), + } + else: + edge_smooth = {"type": "cubicBezier"} else: - edge_smooth = {"type": "cubicBezier"} + edge_smooth = False + + edge_style = { + "color": colors["router_edge"], + "width": 2, + "arrows": "to", + "dashes": True, + "smooth": edge_smooth, + } + net.add_edge(router_method_name, listener_name, **edge_style) else: - edge_smooth = False - - edge_style = { - "color": colors["router_edge"], - "width": 2, - "arrows": "to", - "dashes": True, - "smooth": edge_smooth, - } - net.add_edge(router_method_name, listener_name, **edge_style) + # Same check here: known router edge and known method? + method_known = listener_name in flow._methods + if not method_known: + print( + f"Warning: No node found for '{router_method_name}' or '{listener_name}'. Skipping edge." + ) diff --git a/tests/flow_test.py b/tests/flow_test.py index 2e20203619..d52c459ce8 100644 --- a/tests/flow_test.py +++ b/tests/flow_test.py @@ -263,3 +263,62 @@ def step_2(self): flow = StateFlow() flow.kickoff() assert flow.counter == 2 + + +def test_router_with_multiple_conditions(): + """Test a router that triggers when any of multiple steps complete (OR condition), + and another router that triggers only after all specified steps complete (AND condition). + """ + + execution_order = [] + + class ComplexRouterFlow(Flow): + @start() + def step_a(self): + execution_order.append("step_a") + + @start() + def step_b(self): + execution_order.append("step_b") + + @router(or_("step_a", "step_b")) + def router_or(self): + execution_order.append("router_or") + return "next_step_or" + + @listen("next_step_or") + def handle_next_step_or_event(self): + execution_order.append("handle_next_step_or_event") + + @listen(handle_next_step_or_event) + def branch_2_step(self): + execution_order.append("branch_2_step") + + @router(and_(handle_next_step_or_event, branch_2_step)) + def router_and(self): + execution_order.append("router_and") + return "final_step" + + @listen("final_step") + def log_final_step(self): + execution_order.append("log_final_step") + + flow = ComplexRouterFlow() + flow.kickoff() + + assert "step_a" in execution_order + assert "step_b" in execution_order + assert "router_or" in execution_order + assert "handle_next_step_or_event" in execution_order + assert "branch_2_step" in execution_order + assert "router_and" in execution_order + assert "log_final_step" in execution_order + + # Check that the AND router triggered after both relevant steps: + assert execution_order.index("router_and") > execution_order.index( + "handle_next_step_or_event" + ) + assert execution_order.index("router_and") > execution_order.index("branch_2_step") + + # final_step should run after router_and + assert execution_order.index("log_final_step") > execution_order.index("router_and") From 82647358b26f4f214d08c5a4cd85e7ebedd7366a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Moura?= Date: Fri, 27 Dec 2024 17:03:35 -0300 Subject: [PATCH 13/18] Adding Multimodal Abilities to Crew (#1805) * initial fix on delegation tools * fixing tests for delegations and coding * Refactor prepare tool and adding initial add images logic * supporting image tool * fixing linter * fix linter * Making sure multimodal feature support i18n * fix linter and types * mixxing translations * fix types and linter * Revert "fixing linter" This reverts commit 2eda5fdeed6f97b1e9c5bff4a124832f8e6c1f1e. * fix linters * test * fix * fix * fix linter * fix * ignore * type improvements --- src/crewai/agent.py | 9 + src/crewai/agents/crew_agent_executor.py | 16 +- src/crewai/crew.py | 112 ++-- src/crewai/llm.py | 2 + .../tools/agent_tools/add_image_tool.py | 45 ++ src/crewai/tools/agent_tools/agent_tools.py | 4 +- src/crewai/tools/tool_usage.py | 17 +- src/crewai/translations/en.json | 7 +- src/crewai/utilities/i18n.py | 6 +- .../test_crew_with_delegating_agents.yaml | 556 +++------------- ...gents_should_not_override_agent_tools.yaml | 480 ++++++++++++++ ...agents_should_not_override_task_tools.yaml | 623 ++++++++++++++++++ ..._multimodal_agent_live_image_analysis.yaml | 481 ++++++++++++++ .../test_task_tools_override_agent_tools.yaml | 569 ++++++++++++++++ tests/crew_test.py | 607 ++++++++++++++++- 15 files changed, 2990 insertions(+), 544 deletions(-) create mode 100644 src/crewai/tools/agent_tools/add_image_tool.py create mode 100644 tests/cassettes/test_crew_with_delegating_agents_should_not_override_agent_tools.yaml create mode 100644 tests/cassettes/test_crew_with_delegating_agents_should_not_override_task_tools.yaml create mode 100644 tests/cassettes/test_multimodal_agent_live_image_analysis.yaml create mode 100644 tests/cassettes/test_task_tools_override_agent_tools.yaml diff --git a/src/crewai/agent.py b/src/crewai/agent.py index cdad8263a2..999d1d8006 100644 --- a/src/crewai/agent.py +++ b/src/crewai/agent.py @@ -17,6 +17,7 @@ from crewai.task import Task from crewai.tools import BaseTool from crewai.tools.agent_tools.agent_tools import AgentTools +from crewai.tools.base_tool import Tool 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 @@ -114,6 +115,10 @@ class Agent(BaseAgent): default=2, description="Maximum number of retries for an agent to execute a task when an error occurs.", ) + multimodal: bool = Field( + default=False, + description="Whether the agent is multimodal.", + ) code_execution_mode: Literal["safe", "unsafe"] = Field( default="safe", description="Mode for code execution: 'safe' (using Docker) or 'unsafe' (direct execution).", @@ -406,6 +411,10 @@ def get_delegation_tools(self, agents: List[BaseAgent]): tools = agent_tools.tools() return tools + def get_multimodal_tools(self) -> List[Tool]: + from crewai.tools.agent_tools.add_image_tool import AddImageTool + return [AddImageTool()] + def get_code_execution_tools(self): try: from crewai_tools import CodeInterpreterTool diff --git a/src/crewai/agents/crew_agent_executor.py b/src/crewai/agents/crew_agent_executor.py index eb0ff7c5ae..813ac8a086 100644 --- a/src/crewai/agents/crew_agent_executor.py +++ b/src/crewai/agents/crew_agent_executor.py @@ -143,10 +143,20 @@ def _invoke_loop(self, formatted_answer=None): tool_result = self._execute_tool_and_check_finality( formatted_answer ) - if self.step_callback: - self.step_callback(tool_result) - formatted_answer.text += f"\nObservation: {tool_result.result}" + # Directly append the result to the messages if the + # tool is "Add image to content" in case of multimodal + # agents + if formatted_answer.tool == self._i18n.tools("add_image")["name"]: + self.messages.append(tool_result.result) + continue + + else: + if self.step_callback: + self.step_callback(tool_result) + + formatted_answer.text += f"\nObservation: {tool_result.result}" + formatted_answer.result = tool_result.result if tool_result.result_as_answer: return AgentFinish( diff --git a/src/crewai/crew.py b/src/crewai/crew.py index 8138781cc3..d488783eae 100644 --- a/src/crewai/crew.py +++ b/src/crewai/crew.py @@ -35,6 +35,7 @@ from crewai.tasks.task_output import TaskOutput from crewai.telemetry import Telemetry from crewai.tools.agent_tools.agent_tools import AgentTools +from crewai.tools.base_tool import Tool from crewai.types.usage_metrics import UsageMetrics from crewai.utilities import I18N, FileHandler, Logger, RPMController from crewai.utilities.constants import TRAINING_DATA_FILE @@ -533,9 +534,6 @@ def kickoff( if not agent.function_calling_llm: # type: ignore # "BaseAgent" has no attribute "function_calling_llm" agent.function_calling_llm = self.function_calling_llm # type: ignore # "BaseAgent" has no attribute "function_calling_llm" - if agent.allow_code_execution: # type: ignore # BaseAgent" has no attribute "allow_code_execution" - agent.tools += agent.get_code_execution_tools() # type: ignore # "BaseAgent" has no attribute "get_code_execution_tools"; maybe "get_delegation_tools"? - if not agent.step_callback: # type: ignore # "BaseAgent" has no attribute "step_callback" agent.step_callback = self.step_callback # type: ignore # "BaseAgent" has no attribute "step_callback" @@ -672,7 +670,6 @@ def _create_manager_agent(self): ) manager.tools = [] raise Exception("Manager agent should not have tools") - manager.tools = self.manager_agent.get_delegation_tools(self.agents) else: self.manager_llm = ( getattr(self.manager_llm, "model_name", None) @@ -684,6 +681,7 @@ def _create_manager_agent(self): goal=i18n.retrieve("hierarchical_manager_agent", "goal"), backstory=i18n.retrieve("hierarchical_manager_agent", "backstory"), tools=AgentTools(agents=self.agents).tools(), + allow_delegation=True, llm=self.manager_llm, verbose=self.verbose, ) @@ -726,7 +724,14 @@ def _execute_tasks( f"No agent available for task: {task.description}. Ensure that either the task has an assigned agent or a manager agent is provided." ) - self._prepare_agent_tools(task) + # Determine which tools to use - task tools take precedence over agent tools + tools_for_task = task.tools or agent_to_use.tools or [] + tools_for_task = self._prepare_tools( + agent_to_use, + task, + tools_for_task + ) + self._log_task_start(task, agent_to_use.role) if isinstance(task, ConditionalTask): @@ -743,7 +748,7 @@ def _execute_tasks( future = task.execute_async( agent=agent_to_use, context=context, - tools=agent_to_use.tools, + tools=tools_for_task, ) futures.append((task, future, task_index)) else: @@ -755,7 +760,7 @@ def _execute_tasks( task_output = task.execute_sync( agent=agent_to_use, context=context, - tools=agent_to_use.tools, + tools=tools_for_task, ) task_outputs = [task_output] self._process_task_result(task, task_output) @@ -792,45 +797,67 @@ def _handle_conditional_task( return skipped_task_output return None - def _prepare_agent_tools(self, task: Task): - if self.process == Process.hierarchical: - if self.manager_agent: - self._update_manager_tools(task) - else: - raise ValueError("Manager agent is required for hierarchical process.") - elif task.agent and task.agent.allow_delegation: - self._add_delegation_tools(task) + def _prepare_tools(self, agent: BaseAgent, task: Task, tools: List[Tool]) -> List[Tool]: + # Add delegation tools if agent allows delegation + if agent.allow_delegation: + if self.process == Process.hierarchical: + if self.manager_agent: + tools = self._update_manager_tools(task, tools) + else: + raise ValueError("Manager agent is required for hierarchical process.") + + elif agent and agent.allow_delegation: + tools = self._add_delegation_tools(task, tools) + + # Add code execution tools if agent allows code execution + if agent.allow_code_execution: + tools = self._add_code_execution_tools(agent, tools) + + if agent and agent.multimodal: + tools = self._add_multimodal_tools(agent, tools) + + return tools def _get_agent_to_use(self, task: Task) -> Optional[BaseAgent]: if self.process == Process.hierarchical: return self.manager_agent return task.agent - def _add_delegation_tools(self, task: Task): + def _merge_tools(self, existing_tools: List[Tool], new_tools: List[Tool]) -> List[Tool]: + """Merge new tools into existing tools list, avoiding duplicates by tool name.""" + if not new_tools: + return existing_tools + + # Create mapping of tool names to new tools + new_tool_map = {tool.name: tool for tool in new_tools} + + # Remove any existing tools that will be replaced + tools = [tool for tool in existing_tools if tool.name not in new_tool_map] + + # Add all new tools + tools.extend(new_tools) + + return tools + + def _inject_delegation_tools(self, tools: List[Tool], task_agent: BaseAgent, agents: List[BaseAgent]): + delegation_tools = task_agent.get_delegation_tools(agents) + return self._merge_tools(tools, delegation_tools) + + def _add_multimodal_tools(self, agent: BaseAgent, tools: List[Tool]): + multimodal_tools = agent.get_multimodal_tools() + return self._merge_tools(tools, multimodal_tools) + + def _add_code_execution_tools(self, agent: BaseAgent, tools: List[Tool]): + code_tools = agent.get_code_execution_tools() + return self._merge_tools(tools, code_tools) + + def _add_delegation_tools(self, task: Task, tools: List[Tool]): agents_for_delegation = [agent for agent in self.agents if agent != task.agent] if len(self.agents) > 1 and len(agents_for_delegation) > 0 and task.agent: - delegation_tools = task.agent.get_delegation_tools(agents_for_delegation) - - # Add tools if they are not already in task.tools - for new_tool in delegation_tools: - # Find the index of the tool with the same name - existing_tool_index = next( - ( - index - for index, tool in enumerate(task.tools or []) - if tool.name == new_tool.name - ), - None, - ) - if not task.tools: - task.tools = [] - - if existing_tool_index is not None: - # Replace the existing tool - task.tools[existing_tool_index] = new_tool - else: - # Add the new tool - task.tools.append(new_tool) + if not tools: + tools = [] + tools = self._inject_delegation_tools(tools, task.agent, agents_for_delegation) + return tools def _log_task_start(self, task: Task, role: str = "None"): if self.output_log_file: @@ -838,14 +865,13 @@ def _log_task_start(self, task: Task, role: str = "None"): task_name=task.name, task=task.description, agent=role, status="started" ) - def _update_manager_tools(self, task: Task): + def _update_manager_tools(self, task: Task, tools: List[Tool]): if self.manager_agent: if task.agent: - self.manager_agent.tools = task.agent.get_delegation_tools([task.agent]) + tools = self._inject_delegation_tools(tools, task.agent, [task.agent]) else: - self.manager_agent.tools = self.manager_agent.get_delegation_tools( - self.agents - ) + tools = self._inject_delegation_tools(tools, self.manager_agent, self.agents) + return tools def _get_context(self, task: Task, task_outputs: List[TaskOutput]): context = ( diff --git a/src/crewai/llm.py b/src/crewai/llm.py index 1b0ac9b0a1..5d6a0ccf55 100644 --- a/src/crewai/llm.py +++ b/src/crewai/llm.py @@ -64,6 +64,8 @@ def flush(self): "llama3-70b-8192": 8192, "llama3-8b-8192": 8192, "mixtral-8x7b-32768": 32768, + "llama-3.3-70b-versatile": 128000, + "llama-3.3-70b-instruct": 128000, } DEFAULT_CONTEXT_WINDOW_SIZE = 8192 diff --git a/src/crewai/tools/agent_tools/add_image_tool.py b/src/crewai/tools/agent_tools/add_image_tool.py new file mode 100644 index 0000000000..06bdfcf5b3 --- /dev/null +++ b/src/crewai/tools/agent_tools/add_image_tool.py @@ -0,0 +1,45 @@ +from typing import Dict, Optional, Union + +from pydantic import BaseModel, Field + +from crewai.tools.base_tool import BaseTool +from crewai.utilities import I18N + +i18n = I18N() + +class AddImageToolSchema(BaseModel): + image_url: str = Field(..., description="The URL or path of the image to add") + action: Optional[str] = Field( + default=None, + description="Optional context or question about the image" + ) + + +class AddImageTool(BaseTool): + """Tool for adding images to the content""" + + name: str = Field(default_factory=lambda: i18n.tools("add_image")["name"]) # type: ignore + description: str = Field(default_factory=lambda: i18n.tools("add_image")["description"]) # type: ignore + args_schema: type[BaseModel] = AddImageToolSchema + + def _run( + self, + image_url: str, + action: Optional[str] = None, + **kwargs, + ) -> dict: + action = action or i18n.tools("add_image")["default_action"] # type: ignore + content = [ + {"type": "text", "text": action}, + { + "type": "image_url", + "image_url": { + "url": image_url, + }, + } + ] + + return { + "role": "user", + "content": content + } diff --git a/src/crewai/tools/agent_tools/agent_tools.py b/src/crewai/tools/agent_tools/agent_tools.py index 08383c244e..77d3c2d891 100644 --- a/src/crewai/tools/agent_tools/agent_tools.py +++ b/src/crewai/tools/agent_tools/agent_tools.py @@ -20,13 +20,13 @@ def tools(self) -> list[BaseTool]: delegate_tool = DelegateWorkTool( agents=self.agents, i18n=self.i18n, - description=self.i18n.tools("delegate_work").format(coworkers=coworkers), + description=self.i18n.tools("delegate_work").format(coworkers=coworkers), # type: ignore ) ask_tool = AskQuestionTool( agents=self.agents, i18n=self.i18n, - description=self.i18n.tools("ask_question").format(coworkers=coworkers), + description=self.i18n.tools("ask_question").format(coworkers=coworkers), # type: ignore ) return [delegate_tool, ask_tool] diff --git a/src/crewai/tools/tool_usage.py b/src/crewai/tools/tool_usage.py index 3de4a8fab5..532587cedd 100644 --- a/src/crewai/tools/tool_usage.py +++ b/src/crewai/tools/tool_usage.py @@ -10,6 +10,7 @@ from crewai.task import Task from crewai.telemetry import Telemetry from crewai.tools import BaseTool +from crewai.tools.structured_tool import CrewStructuredTool 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 @@ -18,8 +19,7 @@ import agentops # type: ignore except ImportError: agentops = None - -OPENAI_BIGGER_MODELS = ["gpt-4", "gpt-4o", "o1-preview", "o1-mini"] +OPENAI_BIGGER_MODELS = ["gpt-4", "gpt-4o", "o1-preview", "o1-mini", "o1", "o3", "o3-mini"] class ToolUsageErrorException(Exception): @@ -103,6 +103,19 @@ def use( if self.agent.verbose: self._printer.print(content=f"\n\n{error}\n", color="red") return error + + if isinstance(tool, CrewStructuredTool) and tool.name == self._i18n.tools("add_image")["name"]: # type: ignore + try: + result = self._use(tool_string=tool_string, tool=tool, calling=calling) + return result + + except Exception as e: + error = getattr(e, "message", str(e)) + self.task.increment_tools_errors() + if self.agent.verbose: + self._printer.print(content=f"\n\n{error}\n", color="red") + return error + return f"{self._use(tool_string=tool_string, tool=tool, calling=calling)}" # type: ignore # BUG?: "_use" of "ToolUsage" does not return a value (it only ever returns None) def _use( diff --git a/src/crewai/translations/en.json b/src/crewai/translations/en.json index 6bdbb7c297..12850c9e2a 100644 --- a/src/crewai/translations/en.json +++ b/src/crewai/translations/en.json @@ -37,6 +37,11 @@ }, "tools": { "delegate_work": "Delegate a specific task to one of the following coworkers: {coworkers}\nThe input to this tool should be the coworker, the task you want them to do, and ALL necessary context to execute the task, they know nothing about the task, so share absolute everything you know, don't reference things but instead explain them.", - "ask_question": "Ask a specific question to one of the following coworkers: {coworkers}\nThe input to this tool should be the coworker, the question you have for them, and ALL necessary context to ask the question properly, they know nothing about the question, so share absolute everything you know, don't reference things but instead explain them." + "ask_question": "Ask a specific question to one of the following coworkers: {coworkers}\nThe input to this tool should be the coworker, the question you have for them, and ALL necessary context to ask the question properly, they know nothing about the question, so share absolute everything you know, don't reference things but instead explain them.", + "add_image": { + "name": "Add image to content", + "description": "See image to understand it's content, you can optionally ask a question about the image", + "default_action": "Please provide a detailed description of this image, including all visual elements, context, and any notable details you can observe." + } } } diff --git a/src/crewai/utilities/i18n.py b/src/crewai/utilities/i18n.py index e325834f3b..ebf1abcda5 100644 --- a/src/crewai/utilities/i18n.py +++ b/src/crewai/utilities/i18n.py @@ -1,6 +1,6 @@ import json import os -from typing import Dict, Optional +from typing import Dict, Optional, Union from pydantic import BaseModel, Field, PrivateAttr, model_validator @@ -41,8 +41,8 @@ def slice(self, slice: str) -> str: def errors(self, error: str) -> str: return self.retrieve("errors", error) - def tools(self, error: str) -> str: - return self.retrieve("tools", error) + def tools(self, tool: str) -> Union[str, Dict[str, str]]: + return self.retrieve("tools", tool) def retrieve(self, kind, key) -> str: try: diff --git a/tests/cassettes/test_crew_with_delegating_agents.yaml b/tests/cassettes/test_crew_with_delegating_agents.yaml index 651b821dec..a6e074224a 100644 --- a/tests/cassettes/test_crew_with_delegating_agents.yaml +++ b/tests/cassettes/test_crew_with_delegating_agents.yaml @@ -3,223 +3,17 @@ interactions: body: '{"messages": [{"role": "system", "content": "You are CEO. You''re an long time CEO of a content creation agency with a Senior Writer on the team. You''re now working on a new project and want to make sure the content produced is amazing.\nYour - personal goal is: Make sure the writers in your company produce amazing content.\nYou - ONLY have access to the following tools, and should NEVER make up tools that - are not listed here:\n\nTool Name: Delegate work to coworker(task: str, context: - str, coworker: Optional[str] = None, **kwargs)\nTool Description: Delegate a - specific task to one of the following coworkers: Senior Writer\nThe input to - this tool should be the coworker, the task you want them to do, and ALL necessary - context to execute the task, they know nothing about the task, so share absolute - everything you know, don''t reference things but instead explain them.\nTool - Arguments: {''task'': {''title'': ''Task'', ''type'': ''string''}, ''context'': - {''title'': ''Context'', ''type'': ''string''}, ''coworker'': {''title'': ''Coworker'', - ''type'': ''string''}, ''kwargs'': {''title'': ''Kwargs'', ''type'': ''object''}}\nTool - Name: Ask question to coworker(question: str, context: str, coworker: Optional[str] - = None, **kwargs)\nTool Description: Ask a specific question to one of the following - coworkers: Senior Writer\nThe input to this tool should be the coworker, the - question you have for them, and ALL necessary context to ask the question properly, - they know nothing about the question, so share absolute everything you know, - don''t reference things but instead explain them.\nTool Arguments: {''question'': - {''title'': ''Question'', ''type'': ''string''}, ''context'': {''title'': ''Context'', - ''type'': ''string''}, ''coworker'': {''title'': ''Coworker'', ''type'': ''string''}, - ''kwargs'': {''title'': ''Kwargs'', ''type'': ''object''}}\n\nUse the following - format:\n\nThought: you should always think about what to do\nAction: the action - to take, only one name of [Delegate work to coworker, Ask question to coworker], - 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"}, {"role": "user", "content": - "\nCurrent Task: Produce and amazing 1 paragraph draft of an article about AI - Agents.\n\nThis is the expect criteria for your final answer: A 4 paragraph - article about AI.\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"}' - headers: - accept: - - application/json - accept-encoding: - - gzip, deflate - connection: - - keep-alive - content-length: - - '2762' - 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 - x-stainless-arch: - - arm64 - x-stainless-async: - - 'false' - x-stainless-lang: - - python - x-stainless-os: - - MacOS - x-stainless-package-version: - - 1.47.0 - x-stainless-raw-response: - - 'true' - x-stainless-runtime: - - CPython - x-stainless-runtime-version: - - 3.11.7 - method: POST - uri: https://api.openai.com/v1/chat/completions - response: - content: "{\n \"id\": \"chatcmpl-AB7ZvxqgeOayGTQWwR61ASlZp0s74\",\n \"object\": - \"chat.completion\",\n \"created\": 1727214103,\n \"model\": \"gpt-4o-2024-05-13\",\n - \ \"choices\": [\n {\n \"index\": 0,\n \"message\": {\n \"role\": - \"assistant\",\n \"content\": \"Thought: To ensure the content is amazing, - I'll delegate the task of producing a one-paragraph draft of an article about - AI Agents to the Senior Writer with all necessary context.\\n\\nAction: Delegate - work to coworker\\nAction Input: \\n{\\n \\\"coworker\\\": \\\"Senior Writer\\\", - \\n \\\"task\\\": \\\"Produce a one paragraph draft of an article about AI - Agents\\\", \\n \\\"context\\\": \\\"We need an amazing one-paragraph draft - as the beginning of a 4-paragraph article about AI Agents. This is for a high-stakes - project that critically impacts our company. The paragraph should highlight - what AI Agents are, their significance, and how they are transforming various - industries. The tone should be professional yet engaging. Make sure the content - is original, insightful, and thoroughly researched.\\\"\\n}\",\n \"refusal\": - null\n },\n \"logprobs\": null,\n \"finish_reason\": \"stop\"\n - \ }\n ],\n \"usage\": {\n \"prompt_tokens\": 608,\n \"completion_tokens\": - 160,\n \"total_tokens\": 768,\n \"completion_tokens_details\": {\n \"reasoning_tokens\": - 0\n }\n },\n \"system_fingerprint\": \"fp_e375328146\"\n}\n" - headers: - CF-Cache-Status: - - DYNAMIC - CF-RAY: - - 8c85f0b038a71cf3-GRU - Connection: - - keep-alive - Content-Encoding: - - gzip - Content-Type: - - application/json - Date: - - Tue, 24 Sep 2024 21:41:45 GMT - Server: - - cloudflare - Transfer-Encoding: - - chunked - X-Content-Type-Options: - - nosniff - access-control-expose-headers: - - X-Request-ID - openai-organization: - - crewai-iuxna1 - openai-processing-ms: - - '1826' - 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: - - '29999325' - x-ratelimit-reset-requests: - - 6ms - x-ratelimit-reset-tokens: - - 1ms - x-request-id: - - req_79054638deeb01da76c5bba273bffc28 - http_version: HTTP/1.1 - status_code: 200 -- request: - body: !!binary | - Cq8OCiQKIgoMc2VydmljZS5uYW1lEhIKEGNyZXdBSS10ZWxlbWV0cnkShg4KEgoQY3Jld2FpLnRl - bGVtZXRyeRKQAgoQg15EMIBbDpydrcK3GAUYfBII5VYz5B10kmgqDlRhc2sgRXhlY3V0aW9uMAE5 - aGIpYwtM+BdBIO6VVRNM+BdKLgoIY3Jld19rZXkSIgogZTNmZGEwZjMxMTBmZTgwYjE4OTQ3YzAx - NDcxNDMwYTRKMQoHY3Jld19pZBImCiRjNzM1NzdhYi0xYThhLTQzMGYtYjYyZi01MTBlYWMyMWI3 - MThKLgoIdGFza19rZXkSIgogNWZhNjVjMDZhOWUzMWYyYzY5NTQzMjY2OGFjZDYyZGRKMQoHdGFz - a19pZBImCiQ3MjAzMjYyMC0yMzJmLTQ5ZTMtOGMyNy0xYzBlOWJhNjFiZDB6AhgBhQEAAQAAEssJ - ChB+du4H1wHcku5blhLQBtuoEgiXVguc5KA1RyoMQ3JldyBDcmVhdGVkMAE54IJsVxNM+BdBcCN4 - VxNM+BdKGgoOY3Jld2FpX3ZlcnNpb24SCAoGMC42MS4wShoKDnB5dGhvbl92ZXJzaW9uEggKBjMu - MTEuN0ouCghjcmV3X2tleRIiCiBlNjQ5NTczYTI2ZTU4NzkwY2FjMjFhMzdjZDQ0NDM3YUoxCgdj - cmV3X2lkEiYKJDI4ZTY0YmQ3LWNlYWMtNDYxOS04MmM3LTIzNmRkNTQxOGM4N0ocCgxjcmV3X3By - b2Nlc3MSDAoKc2VxdWVudGlhbEoRCgtjcmV3X21lbW9yeRICEABKGgoUY3Jld19udW1iZXJfb2Zf - dGFza3MSAhgBShsKFWNyZXdfbnVtYmVyX29mX2FnZW50cxICGAJKgAUKC2NyZXdfYWdlbnRzEvAE - Cu0EW3sia2V5IjogIjMyODIxN2I2YzI5NTliZGZjNDdjYWQwMGU4NDg5MGQwIiwgImlkIjogIjQ1 - NjMxMmU3LThkMmMtNDcyMi1iNWNkLTlhMGRhMzg5MmM3OCIsICJyb2xlIjogIkNFTyIsICJ2ZXJi - b3NlPyI6IGZhbHNlLCAibWF4X2l0ZXIiOiAxNSwgIm1heF9ycG0iOiBudWxsLCAiZnVuY3Rpb25f - Y2FsbGluZ19sbG0iOiAiIiwgImxsbSI6ICJncHQtNG8iLCAiZGVsZWdhdGlvbl9lbmFibGVkPyI6 - IHRydWUsICJhbGxvd19jb2RlX2V4ZWN1dGlvbj8iOiBmYWxzZSwgIm1heF9yZXRyeV9saW1pdCI6 - IDIsICJ0b29sc19uYW1lcyI6IFtdfSwgeyJrZXkiOiAiOWE1MDE1ZWY0ODk1ZGM2Mjc4ZDU0ODE4 - YmE0NDZhZjciLCAiaWQiOiAiMTM0MDg5MjAtNzVjOC00MTk3LWIwNmQtY2I4MmNkZjhkZDhhIiwg - InJvbGUiOiAiU2VuaW9yIFdyaXRlciIsICJ2ZXJib3NlPyI6IGZhbHNlLCAibWF4X2l0ZXIiOiAx - NSwgIm1heF9ycG0iOiBudWxsLCAiZnVuY3Rpb25fY2FsbGluZ19sbG0iOiAiIiwgImxsbSI6ICJn - cHQtNG8iLCAiZGVsZWdhdGlvbl9lbmFibGVkPyI6IGZhbHNlLCAiYWxsb3dfY29kZV9leGVjdXRp - b24/IjogZmFsc2UsICJtYXhfcmV0cnlfbGltaXQiOiAyLCAidG9vbHNfbmFtZXMiOiBbXX1dSvgB - CgpjcmV3X3Rhc2tzEukBCuYBW3sia2V5IjogIjBiOWQ2NWRiNmI3YWVkZmIzOThjNTllMmE5Zjcx - ZWM1IiwgImlkIjogImQ0YjVhZmE2LTczNTEtNDUxMy04NzY2LTIzOGNjYTk5ZjRlZiIsICJhc3lu - Y19leGVjdXRpb24/IjogZmFsc2UsICJodW1hbl9pbnB1dD8iOiBmYWxzZSwgImFnZW50X3JvbGUi - OiAiQ0VPIiwgImFnZW50X2tleSI6ICIzMjgyMTdiNmMyOTU5YmRmYzQ3Y2FkMDBlODQ4OTBkMCIs - ICJ0b29sc19uYW1lcyI6IFtdfV16AhgBhQEAAQAAEo4CChCLEGLGYlBkv0YucoYjY1NeEghRpGin - zpZUiSoMVGFzayBDcmVhdGVkMAE5KCA2WBNM+BdBaLw2WBNM+BdKLgoIY3Jld19rZXkSIgogZTY0 - OTU3M2EyNmU1ODc5MGNhYzIxYTM3Y2Q0NDQzN2FKMQoHY3Jld19pZBImCiQyOGU2NGJkNy1jZWFj - LTQ2MTktODJjNy0yMzZkZDU0MThjODdKLgoIdGFza19rZXkSIgogMGI5ZDY1ZGI2YjdhZWRmYjM5 - OGM1OWUyYTlmNzFlYzVKMQoHdGFza19pZBImCiRkNGI1YWZhNi03MzUxLTQ1MTMtODc2Ni0yMzhj - Y2E5OWY0ZWZ6AhgBhQEAAQAA - headers: - Accept: - - '*/*' - Accept-Encoding: - - gzip, deflate - Connection: - - keep-alive - Content-Length: - - '1842' - 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, 24 Sep 2024 21:41:46 GMT - status: - code: 200 - message: OK -- request: - body: '{"messages": [{"role": "system", "content": "You are Senior Writer. You''re - a senior writer, specialized in technology, software engineering, AI and startups. - You work as a freelancer and are now working on writing content for a new customer.\nYour - personal goal is: Write the best content about AI and AI agents.\nTo give my - best complete final answer to the task use the exact following format:\n\nThought: + personal goal is: Make sure the writers in your company produce amazing content.\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: Produce a one paragraph draft of an article about AI Agents\n\nThis is - the expect criteria for your final answer: Your best answer to your coworker - asking you this, accounting for the context shared.\nyou MUST return the actual - complete content as the final answer, not a summary.\n\nThis is the context - you''re working with:\nWe need an amazing one-paragraph draft as the beginning - of a 4-paragraph article about AI Agents. This is for a high-stakes project - that critically impacts our company. The paragraph should highlight what AI - Agents are, their significance, and how they are transforming various industries. - The tone should be professional yet engaging. Make sure the content is original, - insightful, and thoroughly researched.\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"}' + Task: Produce and amazing 1 paragraph draft of an article about AI Agents.\n\nThis + is the expect criteria for your final answer: A 4 paragraph article about AI.\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 @@ -228,16 +22,13 @@ interactions: connection: - keep-alive content-length: - - '1545' + - '1105' 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 x-stainless-async: @@ -247,9 +38,11 @@ interactions: x-stainless-os: - MacOS 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: @@ -257,31 +50,51 @@ interactions: method: POST uri: https://api.openai.com/v1/chat/completions response: - content: "{\n \"id\": \"chatcmpl-AB7ZxDYcPlSiBZsftdRs2cWbUJllW\",\n \"object\": - \"chat.completion\",\n \"created\": 1727214105,\n \"model\": \"gpt-4o-2024-05-13\",\n + content: "{\n \"id\": \"chatcmpl-Ahe7liUPejwfqxMe8aEWmKGJ837em\",\n \"object\": + \"chat.completion\",\n \"created\": 1734965705,\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: Artificial Intelligence (AI) Agents are sophisticated computer programs - designed to perform tasks that typically require human intelligence, such as - decision making, problem-solving, and learning. These agents operate autonomously, - utilizing vast amounts of data, advanced algorithms, and machine learning techniques - to analyze their environment, adapt to new information, and improve their performance - over time. The significance of AI Agents lies in their transformative potential - across various industries. In healthcare, they assist in diagnosing diseases - with greater accuracy; in finance, they predict market trends and manage risks; - in customer service, they provide personalized and efficient responses. As these - AI-powered entities continue to evolve, they are not only enhancing operational - efficiencies but also driving innovation and creating new opportunities for - growth and development in every sector they penetrate.\",\n \"refusal\": - null\n },\n \"logprobs\": null,\n \"finish_reason\": \"stop\"\n - \ }\n ],\n \"usage\": {\n \"prompt_tokens\": 297,\n \"completion_tokens\": - 160,\n \"total_tokens\": 457,\n \"completion_tokens_details\": {\n \"reasoning_tokens\": - 0\n }\n },\n \"system_fingerprint\": \"fp_e375328146\"\n}\n" + Answer: In the rapidly evolving landscape of technology, AI agents have emerged + as formidable tools, revolutionizing how we interact with data and automate + tasks. These sophisticated systems leverage machine learning and natural language + processing to perform a myriad of functions, from virtual personal assistants + to complex decision-making companions in industries such as finance, healthcare, + and education. By mimicking human intelligence, AI agents can analyze massive + data sets at unparalleled speeds, enabling businesses to uncover valuable insights, + enhance productivity, and elevate user experiences to unprecedented levels.\\n\\nOne + of the most striking aspects of AI agents is their adaptability; they learn + from their interactions and continuously improve their performance over time. + This feature is particularly valuable in customer service where AI agents can + address inquiries, resolve issues, and provide personalized recommendations + without the limitations of human fatigue. Moreover, with intuitive interfaces, + AI agents enhance user interactions, making technology more accessible and user-friendly, + thereby breaking down barriers that have historically hindered digital engagement.\\n\\nDespite + their immense potential, the deployment of AI agents raises important ethical + and practical considerations. Issues related to privacy, data security, and + the potential for job displacement necessitate thoughtful dialogue and proactive + measures. Striking a balance between technological innovation and societal impact + will be crucial as organizations integrate these agents into their operations. + Additionally, ensuring transparency in AI decision-making processes is vital + to maintain public trust as AI agents become an integral part of daily life.\\n\\nLooking + ahead, the future of AI agents appears bright, with ongoing advancements promising + even greater capabilities. As we continue to harness the power of AI, we can + expect these agents to play a transformative role in shaping various sectors\u2014streamlining + workflows, enabling smarter decision-making, and fostering more personalized + experiences. Embracing this technology responsibly can lead to a future where + AI agents not only augment human effort but also inspire creativity and efficiency + across the board, ultimately redefining our interaction with the digital world.\",\n + \ \"refusal\": null\n },\n \"logprobs\": null,\n \"finish_reason\": + \"stop\"\n }\n ],\n \"usage\": {\n \"prompt_tokens\": 208,\n \"completion_tokens\": + 382,\n \"total_tokens\": 590,\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_0aa8d3e20b\"\n}\n" headers: CF-Cache-Status: - DYNAMIC CF-RAY: - - 8c85f0c0cf961cf3-GRU + - 8f6930c97a33ae54-GRU Connection: - keep-alive Content-Encoding: @@ -289,45 +102,77 @@ interactions: Content-Type: - application/json Date: - - Tue, 24 Sep 2024 21:41:48 GMT + - Mon, 23 Dec 2024 14:55:10 GMT Server: - cloudflare + Set-Cookie: + - __cf_bm=g58erGPkGAltcfYpDRU4IsdEEzb955dGmBOAZacFlPA-1734965710-1.0.1.1-IiodiX3uxbT5xSa4seI7M.gRM4Jj46h2d6ZW2wCkSUYUAX.ivRh_sGQN2hucEMzdG8O87pc00dCl7E5W8KkyEA; + path=/; expires=Mon, 23-Dec-24 15:25:10 GMT; domain=.api.openai.com; HttpOnly; + Secure; SameSite=None + - _cfuvid=eQzzWvIXDS8Me1OIBdCG5F1qFyVfAo3sumvYRE7J41E-1734965710778-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: - - '2468' + - '5401' openai-version: - '2020-10-01' strict-transport-security: - max-age=31536000; includeSubDomains; preload x-ratelimit-limit-requests: - - '10000' + - '30000' x-ratelimit-limit-tokens: - - '30000000' + - '150000000' x-ratelimit-remaining-requests: - - '9999' + - '29999' x-ratelimit-remaining-tokens: - - '29999625' + - '149999746' x-ratelimit-reset-requests: - - 6ms + - 2ms x-ratelimit-reset-tokens: - 0s x-request-id: - - req_66c8801b42ac865249246d98225c1492 + - req_30791533923ae20626ef35a03ae66172 http_version: HTTP/1.1 status_code: 200 - request: body: !!binary | - CtwBCiQKIgoMc2VydmljZS5uYW1lEhIKEGNyZXdBSS10ZWxlbWV0cnkSswEKEgoQY3Jld2FpLnRl - bGVtZXRyeRKcAQoQROg/k5NCUGdgfvfLrFlQDxIIlfh6oMbmqu0qClRvb2wgVXNhZ2UwATlws+Wj - FEz4F0EwBeijFEz4F0oaCg5jcmV3YWlfdmVyc2lvbhIICgYwLjYxLjBKKAoJdG9vbF9uYW1lEhsK - GURlbGVnYXRlIHdvcmsgdG8gY293b3JrZXJKDgoIYXR0ZW1wdHMSAhgBegIYAYUBAAEAAA== + CqYMCiQKIgoMc2VydmljZS5uYW1lEhIKEGNyZXdBSS10ZWxlbWV0cnkS/QsKEgoQY3Jld2FpLnRl + bGVtZXRyeRLVCQoQLH3VghpS+l/DatJl8rrpvRIIUpNEm7ELU08qDENyZXcgQ3JlYXRlZDABObgs + nNId1hMYQfgVpdId1hMYShoKDmNyZXdhaV92ZXJzaW9uEggKBjAuODYuMEoaCg5weXRob25fdmVy + c2lvbhIICgYzLjExLjdKLgoIY3Jld19rZXkSIgogZTY0OTU3M2EyNmU1ODc5MGNhYzIxYTM3Y2Q0 + NDQzN2FKMQoHY3Jld19pZBImCiQzYjVkNDFjNC1kZWJiLTQ2MzItYmIwMC1mNTdhNmM2M2QwMThK + HAoMY3Jld19wcm9jZXNzEgwKCnNlcXVlbnRpYWxKEQoLY3Jld19tZW1vcnkSAhAAShoKFGNyZXdf + bnVtYmVyX29mX3Rhc2tzEgIYAUobChVjcmV3X251bWJlcl9vZl9hZ2VudHMSAhgCSooFCgtjcmV3 + X2FnZW50cxL6BAr3BFt7ImtleSI6ICIzMjgyMTdiNmMyOTU5YmRmYzQ3Y2FkMDBlODQ4OTBkMCIs + ICJpZCI6ICI1Yjk4NDA2OS03MjVlLTQxOWYtYjdiZS1mMDdjMTYyOGNkZjIiLCAicm9sZSI6ICJD + RU8iLCAidmVyYm9zZT8iOiBmYWxzZSwgIm1heF9pdGVyIjogMjAsICJtYXhfcnBtIjogbnVsbCwg + ImZ1bmN0aW9uX2NhbGxpbmdfbGxtIjogIiIsICJsbG0iOiAiZ3B0LTRvLW1pbmkiLCAiZGVsZWdh + dGlvbl9lbmFibGVkPyI6IHRydWUsICJhbGxvd19jb2RlX2V4ZWN1dGlvbj8iOiBmYWxzZSwgIm1h + eF9yZXRyeV9saW1pdCI6IDIsICJ0b29sc19uYW1lcyI6IFtdfSwgeyJrZXkiOiAiOWE1MDE1ZWY0 + ODk1ZGM2Mjc4ZDU0ODE4YmE0NDZhZjciLCAiaWQiOiAiZjkwZWI0ZmItMzUyMC00ZDAyLTlhNDYt + NDE2ZTNlNTQ5NWYxIiwgInJvbGUiOiAiU2VuaW9yIFdyaXRlciIsICJ2ZXJib3NlPyI6IGZhbHNl + LCAibWF4X2l0ZXIiOiAyMCwgIm1heF9ycG0iOiBudWxsLCAiZnVuY3Rpb25fY2FsbGluZ19sbG0i + OiAiIiwgImxsbSI6ICJncHQtNG8tbWluaSIsICJkZWxlZ2F0aW9uX2VuYWJsZWQ/IjogZmFsc2Us + ICJhbGxvd19jb2RlX2V4ZWN1dGlvbj8iOiBmYWxzZSwgIm1heF9yZXRyeV9saW1pdCI6IDIsICJ0 + b29sc19uYW1lcyI6IFtdfV1K+AEKCmNyZXdfdGFza3MS6QEK5gFbeyJrZXkiOiAiMGI5ZDY1ZGI2 + YjdhZWRmYjM5OGM1OWUyYTlmNzFlYzUiLCAiaWQiOiAiNzdmNDY3MDYtNzRjZi00ZGVkLThlMDUt + NmRlZGM0MmYwZDliIiwgImFzeW5jX2V4ZWN1dGlvbj8iOiBmYWxzZSwgImh1bWFuX2lucHV0PyI6 + IGZhbHNlLCAiYWdlbnRfcm9sZSI6ICJDRU8iLCAiYWdlbnRfa2V5IjogIjMyODIxN2I2YzI5NTli + ZGZjNDdjYWQwMGU4NDg5MGQwIiwgInRvb2xzX25hbWVzIjogW119XXoCGAGFAQABAAASjgIKEBvb + LkoAnHiD1gUnbftefpYSCNb1+4JxldizKgxUYXNrIENyZWF0ZWQwATmwYcTSHdYTGEEQz8TSHdYT + GEouCghjcmV3X2tleRIiCiBlNjQ5NTczYTI2ZTU4NzkwY2FjMjFhMzdjZDQ0NDM3YUoxCgdjcmV3 + X2lkEiYKJDNiNWQ0MWM0LWRlYmItNDYzMi1iYjAwLWY1N2E2YzYzZDAxOEouCgh0YXNrX2tleRIi + CiAwYjlkNjVkYjZiN2FlZGZiMzk4YzU5ZTJhOWY3MWVjNUoxCgd0YXNrX2lkEiYKJDc3ZjQ2NzA2 + LTc0Y2YtNGRlZC04ZTA1LTZkZWRjNDJmMGQ5YnoCGAGFAQABAAA= headers: Accept: - '*/*' @@ -336,7 +181,7 @@ interactions: Connection: - keep-alive Content-Length: - - '223' + - '1577' Content-Type: - application/x-protobuf User-Agent: @@ -352,213 +197,8 @@ interactions: Content-Type: - application/x-protobuf Date: - - Tue, 24 Sep 2024 21:41:51 GMT + - Mon, 23 Dec 2024 14:55:10 GMT status: code: 200 message: OK -- request: - body: '{"messages": [{"role": "system", "content": "You are CEO. You''re an long - time CEO of a content creation agency with a Senior Writer on the team. You''re - now working on a new project and want to make sure the content produced is amazing.\nYour - personal goal is: Make sure the writers in your company produce amazing content.\nYou - ONLY have access to the following tools, and should NEVER make up tools that - are not listed here:\n\nTool Name: Delegate work to coworker(task: str, context: - str, coworker: Optional[str] = None, **kwargs)\nTool Description: Delegate a - specific task to one of the following coworkers: Senior Writer\nThe input to - this tool should be the coworker, the task you want them to do, and ALL necessary - context to execute the task, they know nothing about the task, so share absolute - everything you know, don''t reference things but instead explain them.\nTool - Arguments: {''task'': {''title'': ''Task'', ''type'': ''string''}, ''context'': - {''title'': ''Context'', ''type'': ''string''}, ''coworker'': {''title'': ''Coworker'', - ''type'': ''string''}, ''kwargs'': {''title'': ''Kwargs'', ''type'': ''object''}}\nTool - Name: Ask question to coworker(question: str, context: str, coworker: Optional[str] - = None, **kwargs)\nTool Description: Ask a specific question to one of the following - coworkers: Senior Writer\nThe input to this tool should be the coworker, the - question you have for them, and ALL necessary context to ask the question properly, - they know nothing about the question, so share absolute everything you know, - don''t reference things but instead explain them.\nTool Arguments: {''question'': - {''title'': ''Question'', ''type'': ''string''}, ''context'': {''title'': ''Context'', - ''type'': ''string''}, ''coworker'': {''title'': ''Coworker'', ''type'': ''string''}, - ''kwargs'': {''title'': ''Kwargs'', ''type'': ''object''}}\n\nUse the following - format:\n\nThought: you should always think about what to do\nAction: the action - to take, only one name of [Delegate work to coworker, Ask question to coworker], - 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"}, {"role": "user", "content": - "\nCurrent Task: Produce and amazing 1 paragraph draft of an article about AI - Agents.\n\nThis is the expect criteria for your final answer: A 4 paragraph - article about AI.\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:"}, - {"role": "assistant", "content": "Thought: To ensure the content is amazing, - I''ll delegate the task of producing a one-paragraph draft of an article about - AI Agents to the Senior Writer with all necessary context.\n\nAction: Delegate - work to coworker\nAction Input: \n{\n \"coworker\": \"Senior Writer\", \n \"task\": - \"Produce a one paragraph draft of an article about AI Agents\", \n \"context\": - \"We need an amazing one-paragraph draft as the beginning of a 4-paragraph article - about AI Agents. This is for a high-stakes project that critically impacts our - company. The paragraph should highlight what AI Agents are, their significance, - and how they are transforming various industries. The tone should be professional - yet engaging. Make sure the content is original, insightful, and thoroughly - researched.\"\n}\nObservation: Artificial Intelligence (AI) Agents are sophisticated - computer programs designed to perform tasks that typically require human intelligence, - such as decision making, problem-solving, and learning. These agents operate - autonomously, utilizing vast amounts of data, advanced algorithms, and machine - learning techniques to analyze their environment, adapt to new information, - and improve their performance over time. The significance of AI Agents lies - in their transformative potential across various industries. In healthcare, - they assist in diagnosing diseases with greater accuracy; in finance, they predict - market trends and manage risks; in customer service, they provide personalized - and efficient responses. As these AI-powered entities continue to evolve, they - are not only enhancing operational efficiencies but also driving innovation - and creating new opportunities for growth and development in every sector they - penetrate."}], "model": "gpt-4o"}' - headers: - accept: - - application/json - accept-encoding: - - gzip, deflate - connection: - - keep-alive - content-length: - - '4536' - 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 - x-stainless-arch: - - arm64 - x-stainless-async: - - 'false' - x-stainless-lang: - - python - x-stainless-os: - - MacOS - x-stainless-package-version: - - 1.47.0 - x-stainless-raw-response: - - 'true' - x-stainless-runtime: - - CPython - x-stainless-runtime-version: - - 3.11.7 - method: POST - uri: https://api.openai.com/v1/chat/completions - response: - content: "{\n \"id\": \"chatcmpl-AB7a1PO3pMNybn76wXDFc5HE7ZRsL\",\n \"object\": - \"chat.completion\",\n \"created\": 1727214109,\n \"model\": \"gpt-4o-2024-05-13\",\n - \ \"choices\": [\n {\n \"index\": 0,\n \"message\": {\n \"role\": - \"assistant\",\n \"content\": \"Thought: The paragraph provided by the - Senior Writer is well-detailed and engaging. I will now structure the 4-paragraph - article around this draft, adding an introduction, expanding on the specific - applications in various industries, and concluding with the potential future - impact of AI Agents.\\n\\nFinal Answer:\\n\\nArtificial Intelligence (AI) Agents - are sophisticated computer programs designed to perform tasks that typically - require human intelligence, such as decision making, problem-solving, and learning. - These agents operate autonomously, utilizing vast amounts of data, advanced - algorithms, and machine learning techniques to analyze their environment, adapt - to new information, and improve their performance over time.\\n\\nThe significance - of AI Agents lies in their transformative potential across various industries. - In healthcare, for example, they assist in diagnosing diseases with greater - accuracy and speed than human practitioners, offering personalized treatment - plans by analyzing patient data. In finance, AI Agents predict market trends, - manage risks, and even execute trades, contributing to more stable and profitable - financial systems. Customer service sectors benefit significantly from AI Agents, - as they provide personalized and efficient responses, often resolving issues - faster than traditional methods.\\n\\nMoreover, AI Agents are also making substantial - contributions in fields like education and manufacturing. In education, they - offer tailored learning experiences by assessing individual student needs and - adjusting teaching methods accordingly. They help educators identify students - who might need additional support and provide resources to enhance learning - outcomes. In manufacturing, AI Agents optimize production lines, predict equipment - failures, and improve supply chain management, thus boosting productivity and - reducing downtime.\\n\\nAs these AI-powered entities continue to evolve, they - are not only enhancing operational efficiencies but also driving innovation - and creating new opportunities for growth and development in every sector they - penetrate. The future of AI Agents looks promising, with the potential to revolutionize - the way we live and work, making processes more efficient, decisions more data-driven, - and solutions more innovative than ever before.\\n\\nThis is now a well-rounded, - four-paragraph article that comprehensively covers the topic of AI Agents.\\n\\nFinal - Answer: This is the complete content as specified:\\nArtificial Intelligence - (AI) Agents are sophisticated computer programs designed to perform tasks that - typically require human intelligence, such as decision making, problem-solving, - and learning. These agents operate autonomously, utilizing vast amounts of data, - advanced algorithms, and machine learning techniques to analyze their environment, - adapt to new information, and improve their performance over time.\\n\\nThe - significance of AI Agents lies in their transformative potential across various - industries. In healthcare, for example, they assist in diagnosing diseases with - greater accuracy and speed than human practitioners, offering personalized treatment - plans by analyzing patient data. In finance, AI Agents predict market trends, - manage risks, and even execute trades, contributing to more stable and profitable - financial systems. Customer service sectors benefit significantly from AI Agents, - as they provide personalized and efficient responses, often resolving issues - faster than traditional methods.\\n\\nMoreover, AI Agents are also making substantial - contributions in fields like education and manufacturing. In education, they - offer tailored learning experiences by assessing individual student needs and - adjusting teaching methods accordingly. They help educators identify students - who might need additional support and provide resources to enhance learning - outcomes. In manufacturing, AI Agents optimize production lines, predict equipment - failures, and improve supply chain management, thus boosting productivity and - reducing downtime.\\n\\nAs these AI-powered entities continue to evolve, they - are not only enhancing operational efficiencies but also driving innovation - and creating new opportunities for growth and development in every sector they - penetrate. The future of AI Agents looks promising, with the potential to revolutionize - the way we live and work, making processes more efficient, decisions more data-driven, - and solutions more innovative than ever before.\",\n \"refusal\": null\n - \ },\n \"logprobs\": null,\n \"finish_reason\": \"stop\"\n }\n - \ ],\n \"usage\": {\n \"prompt_tokens\": 923,\n \"completion_tokens\": - 715,\n \"total_tokens\": 1638,\n \"completion_tokens_details\": {\n \"reasoning_tokens\": - 0\n }\n },\n \"system_fingerprint\": \"fp_e375328146\"\n}\n" - headers: - CF-Cache-Status: - - DYNAMIC - CF-RAY: - - 8c85f0d2f90c1cf3-GRU - Connection: - - keep-alive - Content-Encoding: - - gzip - Content-Type: - - application/json - Date: - - Tue, 24 Sep 2024 21:41:58 GMT - Server: - - cloudflare - Transfer-Encoding: - - chunked - X-Content-Type-Options: - - nosniff - access-control-expose-headers: - - X-Request-ID - openai-organization: - - crewai-iuxna1 - openai-processing-ms: - - '8591' - 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: - - '29998895' - x-ratelimit-reset-requests: - - 6ms - x-ratelimit-reset-tokens: - - 2ms - x-request-id: - - req_2b51b5cff02148d29b04284b40ca6081 - http_version: HTTP/1.1 - status_code: 200 version: 1 diff --git a/tests/cassettes/test_crew_with_delegating_agents_should_not_override_agent_tools.yaml b/tests/cassettes/test_crew_with_delegating_agents_should_not_override_agent_tools.yaml new file mode 100644 index 0000000000..93191bb6f0 --- /dev/null +++ b/tests/cassettes/test_crew_with_delegating_agents_should_not_override_agent_tools.yaml @@ -0,0 +1,480 @@ +interactions: +- request: + body: '{"messages": [{"role": "system", "content": "You are CEO. You''re an long + time CEO of a content creation agency with a Senior Writer on the team. You''re + now working on a new project and want to make sure the content produced is amazing.\nYour + personal goal is: Make sure the writers in your company produce amazing content.\nYou + ONLY have access to the following tools, and should NEVER make up tools that + are not listed here:\n\nTool Name: Test Tool\nTool Arguments: {''query'': {''description'': + ''Query to process'', ''type'': ''str''}}\nTool Description: A test tool that + just returns the input\n\nUse the following format:\n\nThought: you should always + think about what to do\nAction: the action to take, only one name of [Test Tool], + 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"}, {"role": "user", "content": + "\nCurrent Task: Produce and amazing 1 paragraph draft of an article about AI + Agents.\n\nThis is the expect criteria for your final answer: A 4 paragraph + article about AI.\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: + - '1581' + 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: + content: "{\n \"id\": \"chatcmpl-AhLsKP8xKkISk8ntUscyUKL30xRXW\",\n \"object\": + \"chat.completion\",\n \"created\": 1734895556,\n \"model\": \"gpt-4o-mini-2024-07-18\",\n + \ \"choices\": [\n {\n \"index\": 0,\n \"message\": {\n \"role\": + \"assistant\",\n \"content\": \"I need to gather information to create + an amazing paragraph draft about AI Agents that aligns with the expected criteria + of a 4-paragraph article about AI. \\n\\nAction: Test Tool \\nAction Input: + {\\\"query\\\": \\\"Write a captivating and informative paragraph about AI Agents, + focusing on their capabilities, applications, and significance in modern technology.\\\"} + \ \",\n \"refusal\": null\n },\n \"logprobs\": null,\n \"finish_reason\": + \"stop\"\n }\n ],\n \"usage\": {\n \"prompt_tokens\": 309,\n \"completion_tokens\": + 68,\n \"total_tokens\": 377,\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_0aa8d3e20b\"\n}\n" + headers: + CF-Cache-Status: + - DYNAMIC + CF-RAY: + - 8f62802d0b3f00d5-GRU + Connection: + - keep-alive + Content-Encoding: + - gzip + Content-Type: + - application/json + Date: + - Sun, 22 Dec 2024 19:25:57 GMT + Server: + - cloudflare + Set-Cookie: + - __cf_bm=vwBNilrHRgMLd8ALWzYrBO5Lm8ieJzbQ3WCVOgmuJ.s-1734895557-1.0.1.1-z.QnDsynL_Ndu.JkWrh_wGMo57vvpK88nWDBTA8P.6prlSRmA91GQLpP62yRUbCW6yoKFbDxroSaYO6qrzZPRg; + path=/; expires=Sun, 22-Dec-24 19:55:57 GMT; domain=.api.openai.com; HttpOnly; + Secure; SameSite=None + - _cfuvid=2u_Xw.i716TDjD2vb2mvMyWxhA4q1MM1JvbrA8CNZpI-1734895557894-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: + - '1075' + 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: + - '149999630' + x-ratelimit-reset-requests: + - 2ms + x-ratelimit-reset-tokens: + - 0s + x-request-id: + - req_80fbcef3505afac708a24ef167b701bb + http_version: HTTP/1.1 + status_code: 200 +- request: + body: '{"messages": [{"role": "system", "content": "You are CEO. You''re an long + time CEO of a content creation agency with a Senior Writer on the team. You''re + now working on a new project and want to make sure the content produced is amazing.\nYour + personal goal is: Make sure the writers in your company produce amazing content.\nYou + ONLY have access to the following tools, and should NEVER make up tools that + are not listed here:\n\nTool Name: Test Tool\nTool Arguments: {''query'': {''description'': + ''Query to process'', ''type'': ''str''}}\nTool Description: A test tool that + just returns the input\n\nUse the following format:\n\nThought: you should always + think about what to do\nAction: the action to take, only one name of [Test Tool], + 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"}, {"role": "user", "content": + "\nCurrent Task: Produce and amazing 1 paragraph draft of an article about AI + Agents.\n\nThis is the expect criteria for your final answer: A 4 paragraph + article about AI.\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:"}, + {"role": "assistant", "content": "I need to gather information to create an + amazing paragraph draft about AI Agents that aligns with the expected criteria + of a 4-paragraph article about AI. \n\nAction: Test Tool \nAction Input: {\"query\": + \"Write a captivating and informative paragraph about AI Agents, focusing on + their capabilities, applications, and significance in modern technology.\"} \nObservation: + Processed: Write a captivating and informative paragraph about AI Agents, focusing + on their capabilities, applications, and significance in modern technology."}], + "model": "gpt-4o-mini", "stop": ["\nObservation:"], "stream": false}' + headers: + accept: + - application/json + accept-encoding: + - gzip, deflate + connection: + - keep-alive + content-length: + - '2153' + content-type: + - application/json + cookie: + - __cf_bm=vwBNilrHRgMLd8ALWzYrBO5Lm8ieJzbQ3WCVOgmuJ.s-1734895557-1.0.1.1-z.QnDsynL_Ndu.JkWrh_wGMo57vvpK88nWDBTA8P.6prlSRmA91GQLpP62yRUbCW6yoKFbDxroSaYO6qrzZPRg; + _cfuvid=2u_Xw.i716TDjD2vb2mvMyWxhA4q1MM1JvbrA8CNZpI-1734895557894-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: + content: "{\n \"id\": \"chatcmpl-AhLsMt1AgrzynC2TSJZZSwr9El8FV\",\n \"object\": + \"chat.completion\",\n \"created\": 1734895558,\n \"model\": \"gpt-4o-mini-2024-07-18\",\n + \ \"choices\": [\n {\n \"index\": 0,\n \"message\": {\n \"role\": + \"assistant\",\n \"content\": \"Thought: I have received the content + related to AI Agents, which I need to now use as a foundation for creating a + complete 4-paragraph article about AI. \\n\\nAction: Test Tool \\nAction Input: + {\\\"query\\\": \\\"Based on the previous paragraph about AI Agents, write a + 4-paragraph article about AI, including an introduction, discussion of AI Agents, + their applications, and a conclusion on the future of AI.\\\"} \",\n \"refusal\": + null\n },\n \"logprobs\": null,\n \"finish_reason\": \"stop\"\n + \ }\n ],\n \"usage\": {\n \"prompt_tokens\": 409,\n \"completion_tokens\": + 88,\n \"total_tokens\": 497,\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_0aa8d3e20b\"\n}\n" + headers: + CF-Cache-Status: + - DYNAMIC + CF-RAY: + - 8f6280352b9400d5-GRU + Connection: + - keep-alive + Content-Encoding: + - gzip + Content-Type: + - application/json + Date: + - Sun, 22 Dec 2024 19:25:59 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: + - '1346' + 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: + - '149999498' + x-ratelimit-reset-requests: + - 2ms + x-ratelimit-reset-tokens: + - 0s + x-request-id: + - req_e25b377af34ef03b9a6955c9cfca5738 + http_version: HTTP/1.1 + status_code: 200 +- request: + body: !!binary | + CtoOCiQKIgoMc2VydmljZS5uYW1lEhIKEGNyZXdBSS10ZWxlbWV0cnkSsQ4KEgoQY3Jld2FpLnRl + bGVtZXRyeRLrCQoQHzrcBLmZm6+CB9ZGtTnz1BIISnyRX3cExT4qDENyZXcgQ3JlYXRlZDABOdCK + UxFRlhMYQdiyWhFRlhMYShoKDmNyZXdhaV92ZXJzaW9uEggKBjAuODYuMEoaCg5weXRob25fdmVy + c2lvbhIICgYzLjExLjdKLgoIY3Jld19rZXkSIgogZTY0OTU3M2EyNmU1ODc5MGNhYzIxYTM3Y2Q0 + NDQzN2FKMQoHY3Jld19pZBImCiQyYWFjYzYwZC0xYzE5LTRjZGYtYmJhNy1iM2RiMGM4YzFlZWZK + HAoMY3Jld19wcm9jZXNzEgwKCnNlcXVlbnRpYWxKEQoLY3Jld19tZW1vcnkSAhAAShoKFGNyZXdf + bnVtYmVyX29mX3Rhc2tzEgIYAUobChVjcmV3X251bWJlcl9vZl9hZ2VudHMSAhgCSpUFCgtjcmV3 + X2FnZW50cxKFBQqCBVt7ImtleSI6ICIzMjgyMTdiNmMyOTU5YmRmYzQ3Y2FkMDBlODQ4OTBkMCIs + ICJpZCI6ICJlZmE4ZWRlNS0wN2IyLTQzOWUtYWQ4Yi1iNmQ0Nzg5NjBkNzkiLCAicm9sZSI6ICJD + RU8iLCAidmVyYm9zZT8iOiBmYWxzZSwgIm1heF9pdGVyIjogMjAsICJtYXhfcnBtIjogbnVsbCwg + ImZ1bmN0aW9uX2NhbGxpbmdfbGxtIjogIiIsICJsbG0iOiAiZ3B0LTRvLW1pbmkiLCAiZGVsZWdh + dGlvbl9lbmFibGVkPyI6IHRydWUsICJhbGxvd19jb2RlX2V4ZWN1dGlvbj8iOiBmYWxzZSwgIm1h + eF9yZXRyeV9saW1pdCI6IDIsICJ0b29sc19uYW1lcyI6IFsidGVzdCB0b29sIl19LCB7ImtleSI6 + ICI5YTUwMTVlZjQ4OTVkYzYyNzhkNTQ4MThiYTQ0NmFmNyIsICJpZCI6ICIxMDE2MGEzMC0zM2U4 + LTRlN2YtOTAzOC1lODU3Zjc2MzI0ZTUiLCAicm9sZSI6ICJTZW5pb3IgV3JpdGVyIiwgInZlcmJv + c2U/IjogZmFsc2UsICJtYXhfaXRlciI6IDIwLCAibWF4X3JwbSI6IG51bGwsICJmdW5jdGlvbl9j + YWxsaW5nX2xsbSI6ICIiLCAibGxtIjogImdwdC00by1taW5pIiwgImRlbGVnYXRpb25fZW5hYmxl + ZD8iOiBmYWxzZSwgImFsbG93X2NvZGVfZXhlY3V0aW9uPyI6IGZhbHNlLCAibWF4X3JldHJ5X2xp + bWl0IjogMiwgInRvb2xzX25hbWVzIjogW119XUqDAgoKY3Jld190YXNrcxL0AQrxAVt7ImtleSI6 + ICIwYjlkNjVkYjZiN2FlZGZiMzk4YzU5ZTJhOWY3MWVjNSIsICJpZCI6ICJiNjYyZWVkOS1kYzcy + LTQ1NTEtYTdmMC1kY2E4ZTk3MmU3NjciLCAiYXN5bmNfZXhlY3V0aW9uPyI6IGZhbHNlLCAiaHVt + YW5faW5wdXQ/IjogZmFsc2UsICJhZ2VudF9yb2xlIjogIkNFTyIsICJhZ2VudF9rZXkiOiAiMzI4 + MjE3YjZjMjk1OWJkZmM0N2NhZDAwZTg0ODkwZDAiLCAidG9vbHNfbmFtZXMiOiBbInRlc3QgdG9v + bCJdfV16AhgBhQEAAQAAEo4CChDkOw+7vfeJwW1bc0PIIqxeEggzmQQt0SPl+ioMVGFzayBDcmVh + dGVkMAE5OBlxEVGWExhBwKlxEVGWExhKLgoIY3Jld19rZXkSIgogZTY0OTU3M2EyNmU1ODc5MGNh + YzIxYTM3Y2Q0NDQzN2FKMQoHY3Jld19pZBImCiQyYWFjYzYwZC0xYzE5LTRjZGYtYmJhNy1iM2Ri + MGM4YzFlZWZKLgoIdGFza19rZXkSIgogMGI5ZDY1ZGI2YjdhZWRmYjM5OGM1OWUyYTlmNzFlYzVK + MQoHdGFza19pZBImCiRiNjYyZWVkOS1kYzcyLTQ1NTEtYTdmMC1kY2E4ZTk3MmU3Njd6AhgBhQEA + AQAAEowBChDS1rm7Q+c0w96t+encwsGJEgjRF+jTQh1PCyoKVG9vbCBVc2FnZTABOaAiFGtRlhMY + QdiVImtRlhMYShoKDmNyZXdhaV92ZXJzaW9uEggKBjAuODYuMEoYCgl0b29sX25hbWUSCwoJVGVz + dCBUb29sSg4KCGF0dGVtcHRzEgIYAXoCGAGFAQABAAASjAEKECYGxNLnTRLCS76uAAOuzGwSCPmX + kSTjWKCcKgpUb29sIFVzYWdlMAE5CH3Wx1GWExhBGH/xx1GWExhKGgoOY3Jld2FpX3ZlcnNpb24S + CAoGMC44Ni4wShgKCXRvb2xfbmFtZRILCglUZXN0IFRvb2xKDgoIYXR0ZW1wdHMSAhgBegIYAYUB + AAEAAA== + headers: + Accept: + - '*/*' + Accept-Encoding: + - gzip, deflate + Connection: + - keep-alive + Content-Length: + - '1885' + 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, 22 Dec 2024 19:26:01 GMT + status: + code: 200 + message: OK +- request: + body: '{"messages": [{"role": "system", "content": "You are CEO. You''re an long + time CEO of a content creation agency with a Senior Writer on the team. You''re + now working on a new project and want to make sure the content produced is amazing.\nYour + personal goal is: Make sure the writers in your company produce amazing content.\nYou + ONLY have access to the following tools, and should NEVER make up tools that + are not listed here:\n\nTool Name: Test Tool\nTool Arguments: {''query'': {''description'': + ''Query to process'', ''type'': ''str''}}\nTool Description: A test tool that + just returns the input\n\nUse the following format:\n\nThought: you should always + think about what to do\nAction: the action to take, only one name of [Test Tool], + 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"}, {"role": "user", "content": + "\nCurrent Task: Produce and amazing 1 paragraph draft of an article about AI + Agents.\n\nThis is the expect criteria for your final answer: A 4 paragraph + article about AI.\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:"}, + {"role": "assistant", "content": "I need to gather information to create an + amazing paragraph draft about AI Agents that aligns with the expected criteria + of a 4-paragraph article about AI. \n\nAction: Test Tool \nAction Input: {\"query\": + \"Write a captivating and informative paragraph about AI Agents, focusing on + their capabilities, applications, and significance in modern technology.\"} \nObservation: + Processed: Write a captivating and informative paragraph about AI Agents, focusing + on their capabilities, applications, and significance in modern technology."}, + {"role": "assistant", "content": "Thought: I have received the content related + to AI Agents, which I need to now use as a foundation for creating a complete + 4-paragraph article about AI. \n\nAction: Test Tool \nAction Input: {\"query\": + \"Based on the previous paragraph about AI Agents, write a 4-paragraph article + about AI, including an introduction, discussion of AI Agents, their applications, + and a conclusion on the future of AI.\"} \nObservation: Processed: Based on + the previous paragraph about AI Agents, write a 4-paragraph article about AI, + including an introduction, discussion of AI Agents, their applications, and + a conclusion on the future of AI."}], "model": "gpt-4o-mini", "stop": ["\nObservation:"], + "stream": false}' + headers: + accept: + - application/json + accept-encoding: + - gzip, deflate + connection: + - keep-alive + content-length: + - '2820' + content-type: + - application/json + cookie: + - __cf_bm=vwBNilrHRgMLd8ALWzYrBO5Lm8ieJzbQ3WCVOgmuJ.s-1734895557-1.0.1.1-z.QnDsynL_Ndu.JkWrh_wGMo57vvpK88nWDBTA8P.6prlSRmA91GQLpP62yRUbCW6yoKFbDxroSaYO6qrzZPRg; + _cfuvid=2u_Xw.i716TDjD2vb2mvMyWxhA4q1MM1JvbrA8CNZpI-1734895557894-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: + content: "{\n \"id\": \"chatcmpl-AhLsNJa6GxRIHF8l8eViU7D6CyBHP\",\n \"object\": + \"chat.completion\",\n \"created\": 1734895559,\n \"model\": \"gpt-4o-mini-2024-07-18\",\n + \ \"choices\": [\n {\n \"index\": 0,\n \"message\": {\n \"role\": + \"assistant\",\n \"content\": \"Thought: I have gathered the complete + article on AI, which aligns with the expected criteria. Now I will present the + final answer as required. \\n\\nFinal Answer: \\n\\nArtificial Intelligence + (AI) has rapidly evolved to become an integral part of our modern world, driving + significant advancements across various industries. AI refers to the simulation + of human intelligence in machines programmed to think and learn like humans. + This technology enables machines to analyze data, recognize patterns, and make + decisions with minimal human intervention, paving the way for innovation in + fields like healthcare, finance, and transportation.\\n\\nAI Agents, in particular, + embody the future of artificial intelligence by acting autonomously to perform + complex tasks. These agents leverage machine learning and natural language processing + to interact with users and understand their needs. They're deployed in customer + service applications, virtual assistants, and personal scheduling tools, showcasing + their capability to streamline processes and enhance user experience. By mimicking + human reasoning, AI Agents can adapt to changing situations and provide personalized + solutions.\\n\\nThe applications of AI Agents extend beyond mere task completion; + they are transforming the way businesses operate. In the realm of customer engagement, + AI Agents analyze customer behavior to provide insights that help companies + tailor their offerings. In healthcare, they assist in diagnosing illnesses by + analyzing patient data and suggesting treatments. The versatility of AI Agents + makes them invaluable assets in our increasingly automated world.\\n\\nAs we + look to the future, the potential of AI continues to expand. With ongoing advancements + in technology, AI Agents are set to become even more sophisticated, further + bridging the gap between humans and machines. The prospects of AI promise not + only to improve efficiency and productivity but also to change the way we live + and work, promising a future where intelligent, autonomous agents support us + in our daily lives.\",\n \"refusal\": null\n },\n \"logprobs\": + null,\n \"finish_reason\": \"stop\"\n }\n ],\n \"usage\": {\n \"prompt_tokens\": + 546,\n \"completion_tokens\": 343,\n \"total_tokens\": 889,\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_0aa8d3e20b\"\n}\n" + headers: + CF-Cache-Status: + - DYNAMIC + CF-RAY: + - 8f62803eed8100d5-GRU + Connection: + - keep-alive + Content-Encoding: + - gzip + Content-Type: + - application/json + Date: + - Sun, 22 Dec 2024 19:26:04 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: + - '4897' + 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: + - '149999342' + x-ratelimit-reset-requests: + - 2ms + x-ratelimit-reset-tokens: + - 0s + x-request-id: + - req_65fdf94aa8bbc10f64f2a27ccdcc5cc8 + http_version: HTTP/1.1 + status_code: 200 +version: 1 diff --git a/tests/cassettes/test_crew_with_delegating_agents_should_not_override_task_tools.yaml b/tests/cassettes/test_crew_with_delegating_agents_should_not_override_task_tools.yaml new file mode 100644 index 0000000000..ee95ca7966 --- /dev/null +++ b/tests/cassettes/test_crew_with_delegating_agents_should_not_override_task_tools.yaml @@ -0,0 +1,623 @@ +interactions: +- request: + body: '{"messages": [{"role": "system", "content": "You are CEO. You''re an long + time CEO of a content creation agency with a Senior Writer on the team. You''re + now working on a new project and want to make sure the content produced is amazing.\nYour + personal goal is: Make sure the writers in your company produce amazing content.\nYou + ONLY have access to the following tools, and should NEVER make up tools that + are not listed here:\n\nTool Name: Test Tool\nTool Arguments: {''query'': {''description'': + ''Query to process'', ''type'': ''str''}}\nTool Description: A test tool that + just returns the input\nTool Name: Delegate work to coworker\nTool Arguments: + {''task'': {''description'': ''The task to delegate'', ''type'': ''str''}, ''context'': + {''description'': ''The context for the task'', ''type'': ''str''}, ''coworker'': + {''description'': ''The role/name of the coworker to delegate to'', ''type'': + ''str''}}\nTool Description: Delegate a specific task to one of the following + coworkers: Senior Writer\nThe input to this tool should be the coworker, the + task you want them to do, and ALL necessary context to execute the task, they + know nothing about the task, so share absolute everything you know, don''t reference + things but instead explain them.\nTool Name: Ask question to coworker\nTool + Arguments: {''question'': {''description'': ''The question to ask'', ''type'': + ''str''}, ''context'': {''description'': ''The context for the question'', ''type'': + ''str''}, ''coworker'': {''description'': ''The role/name of the coworker to + ask'', ''type'': ''str''}}\nTool Description: Ask a specific question to one + of the following coworkers: Senior Writer\nThe input to this tool should be + the coworker, the question you have for them, and ALL necessary context to ask + the question properly, they know nothing about the question, so share absolute + everything you know, don''t reference things but instead explain them.\n\nUse + the following format:\n\nThought: you should always think about what to do\nAction: + the action to take, only one name of [Test Tool, Delegate work to coworker, + Ask question to coworker], 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"}, {"role": "user", "content": "\nCurrent Task: Produce and amazing + 1 paragraph draft of an article about AI Agents.\n\nThis is the expect criteria + for your final answer: A 4 paragraph article about AI.\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: + - '2892' + 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: + content: "{\n \"id\": \"chatcmpl-AhLQELAjJpn76wiLmWBinm3sqf32l\",\n \"object\": + \"chat.completion\",\n \"created\": 1734893814,\n \"model\": \"gpt-4o-mini-2024-07-18\",\n + \ \"choices\": [\n {\n \"index\": 0,\n \"message\": {\n \"role\": + \"assistant\",\n \"content\": \"I need to gather information and insights + to ensure the Senior Writer produces a high-quality draft about AI Agents, which + will then serve as a foundation for the complete article.\\n\\nAction: Ask question + to coworker \\nAction Input: {\\\"question\\\":\\\"Can you provide a detailed + overview of what AI Agents are, their functionalities, and their applications + in real-world scenarios? Please include examples of how they are being used + in various industries, and discuss their potential impact on the future of technology + and society.\\\",\\\"context\\\":\\\"We are looking to create a comprehensive + understanding of AI Agents as part of a four-paragraph article. This will help + generate a high-quality draft for the article.\\\",\\\"coworker\\\":\\\"Senior + Writer\\\"} \",\n \"refusal\": null\n },\n \"logprobs\": null,\n + \ \"finish_reason\": \"stop\"\n }\n ],\n \"usage\": {\n \"prompt_tokens\": + 604,\n \"completion_tokens\": 138,\n \"total_tokens\": 742,\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_0aa8d3e20b\"\n}\n" + headers: + CF-Cache-Status: + - DYNAMIC + CF-RAY: + - 8f6255a1bf08a519-GRU + Connection: + - keep-alive + Content-Encoding: + - gzip + Content-Type: + - application/json + Date: + - Sun, 22 Dec 2024 18:56:56 GMT + Server: + - cloudflare + Set-Cookie: + - __cf_bm=rVquIlcnYXc7wMkbKG5Ii90HxfQ_ukNJjDgSHhsWb1k-1734893816-1.0.1.1-33qDl8KNWcxLAGBuPhT8FrZ6QUnEy9oOYh2Fp2hIjDnF.cQlyrgxiWcuHljTTxG_mH7eQrf1AHJ6p8sxZJZ30A; + path=/; expires=Sun, 22-Dec-24 19:26:56 GMT; domain=.api.openai.com; HttpOnly; + Secure; SameSite=None + - _cfuvid=xqgLJsf3h5lBKZFTADRNNUqizeChNBBFoLvWiR2WPnw-1734893816555-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: + - '2340' + 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: + - '149999305' + x-ratelimit-reset-requests: + - 2ms + x-ratelimit-reset-tokens: + - 0s + x-request-id: + - req_53956b48bd1188451efc104e8a234ef4 + http_version: HTTP/1.1 + status_code: 200 +- request: + body: !!binary | + CrEMCiQKIgoMc2VydmljZS5uYW1lEhIKEGNyZXdBSS10ZWxlbWV0cnkSiAwKEgoQY3Jld2FpLnRl + bGVtZXRyeRLgCQoQg/HA64g3phKbzz/hvUtbahIIpu+Csq+uWc0qDENyZXcgQ3JlYXRlZDABOSDm + Uli7lBMYQcgRXFi7lBMYShoKDmNyZXdhaV92ZXJzaW9uEggKBjAuODYuMEoaCg5weXRob25fdmVy + c2lvbhIICgYzLjExLjdKLgoIY3Jld19rZXkSIgogZTY0OTU3M2EyNmU1ODc5MGNhYzIxYTM3Y2Q0 + NDQzN2FKMQoHY3Jld19pZBImCiRhMWFjMTc0Ny0xMTA0LTRlZjItODZkNi02ZGRhNTFmMDlmMTdK + HAoMY3Jld19wcm9jZXNzEgwKCnNlcXVlbnRpYWxKEQoLY3Jld19tZW1vcnkSAhAAShoKFGNyZXdf + bnVtYmVyX29mX3Rhc2tzEgIYAUobChVjcmV3X251bWJlcl9vZl9hZ2VudHMSAhgCSooFCgtjcmV3 + X2FnZW50cxL6BAr3BFt7ImtleSI6ICIzMjgyMTdiNmMyOTU5YmRmYzQ3Y2FkMDBlODQ4OTBkMCIs + ICJpZCI6ICI4YWUwNGY0Yy0wMjNiLTRkNWQtODAwZC02ZjlkMWFmMWExOTkiLCAicm9sZSI6ICJD + RU8iLCAidmVyYm9zZT8iOiBmYWxzZSwgIm1heF9pdGVyIjogMjAsICJtYXhfcnBtIjogbnVsbCwg + ImZ1bmN0aW9uX2NhbGxpbmdfbGxtIjogIiIsICJsbG0iOiAiZ3B0LTRvLW1pbmkiLCAiZGVsZWdh + dGlvbl9lbmFibGVkPyI6IHRydWUsICJhbGxvd19jb2RlX2V4ZWN1dGlvbj8iOiBmYWxzZSwgIm1h + eF9yZXRyeV9saW1pdCI6IDIsICJ0b29sc19uYW1lcyI6IFtdfSwgeyJrZXkiOiAiOWE1MDE1ZWY0 + ODk1ZGM2Mjc4ZDU0ODE4YmE0NDZhZjciLCAiaWQiOiAiNDg2MWQ4YTMtMjMxYS00Mzc5LTk2ZmEt + MWQwZmQyZDI1MGYxIiwgInJvbGUiOiAiU2VuaW9yIFdyaXRlciIsICJ2ZXJib3NlPyI6IGZhbHNl + LCAibWF4X2l0ZXIiOiAyMCwgIm1heF9ycG0iOiBudWxsLCAiZnVuY3Rpb25fY2FsbGluZ19sbG0i + OiAiIiwgImxsbSI6ICJncHQtNG8tbWluaSIsICJkZWxlZ2F0aW9uX2VuYWJsZWQ/IjogZmFsc2Us + ICJhbGxvd19jb2RlX2V4ZWN1dGlvbj8iOiBmYWxzZSwgIm1heF9yZXRyeV9saW1pdCI6IDIsICJ0 + b29sc19uYW1lcyI6IFtdfV1KgwIKCmNyZXdfdGFza3MS9AEK8QFbeyJrZXkiOiAiMGI5ZDY1ZGI2 + YjdhZWRmYjM5OGM1OWUyYTlmNzFlYzUiLCAiaWQiOiAiY2IyMmIxMzctZTA3ZC00NDA5LWI5NmMt + ZWQ2ZDU3MjFhNDNiIiwgImFzeW5jX2V4ZWN1dGlvbj8iOiBmYWxzZSwgImh1bWFuX2lucHV0PyI6 + IGZhbHNlLCAiYWdlbnRfcm9sZSI6ICJDRU8iLCAiYWdlbnRfa2V5IjogIjMyODIxN2I2YzI5NTli + ZGZjNDdjYWQwMGU4NDg5MGQwIiwgInRvb2xzX25hbWVzIjogWyJ0ZXN0IHRvb2wiXX1degIYAYUB + AAEAABKOAgoQB7Z9AEDI9OTStHqguBSbLxIIj9dttVFJs9cqDFRhc2sgQ3JlYXRlZDABOYDae1i7 + lBMYQeBHfFi7lBMYSi4KCGNyZXdfa2V5EiIKIGU2NDk1NzNhMjZlNTg3OTBjYWMyMWEzN2NkNDQ0 + MzdhSjEKB2NyZXdfaWQSJgokYTFhYzE3NDctMTEwNC00ZWYyLTg2ZDYtNmRkYTUxZjA5ZjE3Si4K + CHRhc2tfa2V5EiIKIDBiOWQ2NWRiNmI3YWVkZmIzOThjNTllMmE5ZjcxZWM1SjEKB3Rhc2tfaWQS + JgokY2IyMmIxMzctZTA3ZC00NDA5LWI5NmMtZWQ2ZDU3MjFhNDNiegIYAYUBAAEAAA== + headers: + Accept: + - '*/*' + Accept-Encoding: + - gzip, deflate + Connection: + - keep-alive + Content-Length: + - '1588' + 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, 22 Dec 2024 18:56:59 GMT + status: + code: 200 + message: OK +- request: + body: '{"messages": [{"role": "system", "content": "You are Senior Writer. You''re + a senior writer, specialized in technology, software engineering, AI and startups. + You work as a freelancer and are now working on writing content for a new customer.\nYour + personal goal is: Write the best content about AI and AI agents.\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: Can you provide a detailed overview of what AI Agents are, their functionalities, + and their applications in real-world scenarios? Please include examples of how + they are being used in various industries, and discuss their potential impact + on the future of technology and society.\n\nThis is the expect criteria for + your final answer: Your best answer to your coworker asking you this, accounting + for the context shared.\nyou MUST return the actual complete content as the + final answer, not a summary.\n\nThis is the context you''re working with:\nWe + are looking to create a comprehensive understanding of AI Agents as part of + a four-paragraph article. This will help generate a high-quality draft for the + article.\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: + - '1572' + content-type: + - application/json + cookie: + - __cf_bm=rVquIlcnYXc7wMkbKG5Ii90HxfQ_ukNJjDgSHhsWb1k-1734893816-1.0.1.1-33qDl8KNWcxLAGBuPhT8FrZ6QUnEy9oOYh2Fp2hIjDnF.cQlyrgxiWcuHljTTxG_mH7eQrf1AHJ6p8sxZJZ30A; + _cfuvid=xqgLJsf3h5lBKZFTADRNNUqizeChNBBFoLvWiR2WPnw-1734893816555-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: + content: "{\n \"id\": \"chatcmpl-AhLQG5ubl99yeBYm6TTV0sodagMND\",\n \"object\": + \"chat.completion\",\n \"created\": 1734893816,\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**Overview of AI Agents** \\nAI agents are advanced software + systems designed to autonomously perform tasks, make decisions, and learn from + their environments without needing constant human intervention. They leverage + machine learning, natural language processing, and various AI techniques to + simulate human-like understanding and autonomy. These agents can be categorized + into three types: reactive agents (which operate purely based on their environment), + deliberative agents (which can make decisions based on reasoning), and hybrid + agents that incorporate aspects of both types. Their ability to adapt and learn + over time makes them instrumental in automating processes across various domains.\\n\\n**Functionalities + of AI Agents** \\nThe core functionalities of AI agents include perception, + action, learning, and interaction. They perceive data through sensors or data + feeds, process information through algorithms, and take actions based on this + data. Machine learning allows them to refine their performance over time by + analyzing outcomes and adjusting their strategies accordingly. Interaction capabilities + enable them to communicate with users, providing insights, answering queries, + or even negotiating in some cases. These functionalities make AI agents invaluable + for tasks such as predictive analytics, personal assistance, and real-time decision-making + in complex systems.\\n\\n**Applications in Various Industries** \\nAI agents + are already being utilized across multiple industries, demonstrating their versatility + and efficiency. In healthcare, AI agents assist in diagnostics by analyzing + medical records and suggesting treatment plans tailored to individual patients. + In finance, they power robo-advisors that manage investment portfolios, automate + trading strategies, and provide financial advice based on real-time market analysis. + Furthermore, in customer service, AI chatbots serve as virtual assistants, enhancing + user experience by providing instant support and resolving queries without human + intervention. The logistics and supply chain industries have also seen AI agents + optimize inventory management and route planning, significantly improving operational + efficiency.\\n\\n**Future Impact on Technology and Society** \\nThe ongoing + development of AI agents is poised to have a profound impact on technology and + society. As these agents become more sophisticated, we can anticipate a shift + towards increased automation in both professional and personal spheres, leading + to enhanced productivity and new business models. However, this automation introduces + challenges such as job displacement and ethical considerations regarding decision-making + by AI. It is essential to foster an ongoing dialogue on the implications of + AI agents to ensure responsible development and integration into our daily lives. + As AI agents continue to evolve, they will undoubtedly play a pivotal role in + shaping the future of technology and its intersection with societal dynamics, + making it critical for us to engage thoughtfully with this emerging paradigm.\",\n + \ \"refusal\": null\n },\n \"logprobs\": null,\n \"finish_reason\": + \"stop\"\n }\n ],\n \"usage\": {\n \"prompt_tokens\": 289,\n \"completion_tokens\": + 506,\n \"total_tokens\": 795,\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_0aa8d3e20b\"\n}\n" + headers: + CF-Cache-Status: + - DYNAMIC + CF-RAY: + - 8f6255b1f832a519-GRU + Connection: + - keep-alive + Content-Encoding: + - gzip + Content-Type: + - application/json + Date: + - Sun, 22 Dec 2024 18:57:04 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: + - '7836' + 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: + - '149999630' + x-ratelimit-reset-requests: + - 2ms + x-ratelimit-reset-tokens: + - 0s + x-request-id: + - req_c14268346d6ce72ceea4b1472f73c5ae + http_version: HTTP/1.1 + status_code: 200 +- request: + body: !!binary | + CtsBCiQKIgoMc2VydmljZS5uYW1lEhIKEGNyZXdBSS10ZWxlbWV0cnkSsgEKEgoQY3Jld2FpLnRl + bGVtZXRyeRKbAQoQ7U/1ZgBSTkCXtesUNPA2URIIrnRWFVT58Z8qClRvb2wgVXNhZ2UwATl4lN3i + vZQTGEGgevzivZQTGEoaCg5jcmV3YWlfdmVyc2lvbhIICgYwLjg2LjBKJwoJdG9vbF9uYW1lEhoK + GEFzayBxdWVzdGlvbiB0byBjb3dvcmtlckoOCghhdHRlbXB0cxICGAF6AhgBhQEAAQAA + headers: + Accept: + - '*/*' + Accept-Encoding: + - gzip, deflate + Connection: + - keep-alive + Content-Length: + - '222' + 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, 22 Dec 2024 18:57:09 GMT + status: + code: 200 + message: OK +- request: + body: '{"messages": [{"role": "system", "content": "You are CEO. You''re an long + time CEO of a content creation agency with a Senior Writer on the team. You''re + now working on a new project and want to make sure the content produced is amazing.\nYour + personal goal is: Make sure the writers in your company produce amazing content.\nYou + ONLY have access to the following tools, and should NEVER make up tools that + are not listed here:\n\nTool Name: Test Tool\nTool Arguments: {''query'': {''description'': + ''Query to process'', ''type'': ''str''}}\nTool Description: A test tool that + just returns the input\nTool Name: Delegate work to coworker\nTool Arguments: + {''task'': {''description'': ''The task to delegate'', ''type'': ''str''}, ''context'': + {''description'': ''The context for the task'', ''type'': ''str''}, ''coworker'': + {''description'': ''The role/name of the coworker to delegate to'', ''type'': + ''str''}}\nTool Description: Delegate a specific task to one of the following + coworkers: Senior Writer\nThe input to this tool should be the coworker, the + task you want them to do, and ALL necessary context to execute the task, they + know nothing about the task, so share absolute everything you know, don''t reference + things but instead explain them.\nTool Name: Ask question to coworker\nTool + Arguments: {''question'': {''description'': ''The question to ask'', ''type'': + ''str''}, ''context'': {''description'': ''The context for the question'', ''type'': + ''str''}, ''coworker'': {''description'': ''The role/name of the coworker to + ask'', ''type'': ''str''}}\nTool Description: Ask a specific question to one + of the following coworkers: Senior Writer\nThe input to this tool should be + the coworker, the question you have for them, and ALL necessary context to ask + the question properly, they know nothing about the question, so share absolute + everything you know, don''t reference things but instead explain them.\n\nUse + the following format:\n\nThought: you should always think about what to do\nAction: + the action to take, only one name of [Test Tool, Delegate work to coworker, + Ask question to coworker], 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"}, {"role": "user", "content": "\nCurrent Task: Produce and amazing + 1 paragraph draft of an article about AI Agents.\n\nThis is the expect criteria + for your final answer: A 4 paragraph article about AI.\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:"}, {"role": "assistant", "content": "I need + to gather information and insights to ensure the Senior Writer produces a high-quality + draft about AI Agents, which will then serve as a foundation for the complete + article.\n\nAction: Ask question to coworker \nAction Input: {\"question\":\"Can + you provide a detailed overview of what AI Agents are, their functionalities, + and their applications in real-world scenarios? Please include examples of how + they are being used in various industries, and discuss their potential impact + on the future of technology and society.\",\"context\":\"We are looking to create + a comprehensive understanding of AI Agents as part of a four-paragraph article. + This will help generate a high-quality draft for the article.\",\"coworker\":\"Senior + Writer\"} \nObservation: **Overview of AI Agents** \nAI agents are advanced + software systems designed to autonomously perform tasks, make decisions, and + learn from their environments without needing constant human intervention. They + leverage machine learning, natural language processing, and various AI techniques + to simulate human-like understanding and autonomy. These agents can be categorized + into three types: reactive agents (which operate purely based on their environment), + deliberative agents (which can make decisions based on reasoning), and hybrid + agents that incorporate aspects of both types. Their ability to adapt and learn + over time makes them instrumental in automating processes across various domains.\n\n**Functionalities + of AI Agents** \nThe core functionalities of AI agents include perception, + action, learning, and interaction. They perceive data through sensors or data + feeds, process information through algorithms, and take actions based on this + data. Machine learning allows them to refine their performance over time by + analyzing outcomes and adjusting their strategies accordingly. Interaction capabilities + enable them to communicate with users, providing insights, answering queries, + or even negotiating in some cases. These functionalities make AI agents invaluable + for tasks such as predictive analytics, personal assistance, and real-time decision-making + in complex systems.\n\n**Applications in Various Industries** \nAI agents are + already being utilized across multiple industries, demonstrating their versatility + and efficiency. In healthcare, AI agents assist in diagnostics by analyzing + medical records and suggesting treatment plans tailored to individual patients. + In finance, they power robo-advisors that manage investment portfolios, automate + trading strategies, and provide financial advice based on real-time market analysis. + Furthermore, in customer service, AI chatbots serve as virtual assistants, enhancing + user experience by providing instant support and resolving queries without human + intervention. The logistics and supply chain industries have also seen AI agents + optimize inventory management and route planning, significantly improving operational + efficiency.\n\n**Future Impact on Technology and Society** \nThe ongoing development + of AI agents is poised to have a profound impact on technology and society. + As these agents become more sophisticated, we can anticipate a shift towards + increased automation in both professional and personal spheres, leading to enhanced + productivity and new business models. However, this automation introduces challenges + such as job displacement and ethical considerations regarding decision-making + by AI. It is essential to foster an ongoing dialogue on the implications of + AI agents to ensure responsible development and integration into our daily lives. + As AI agents continue to evolve, they will undoubtedly play a pivotal role in + shaping the future of technology and its intersection with societal dynamics, + making it critical for us to engage thoughtfully with this emerging paradigm."}], + "model": "gpt-4o-mini", "stop": ["\nObservation:"], "stream": false}' + headers: + accept: + - application/json + accept-encoding: + - gzip, deflate + connection: + - keep-alive + content-length: + - '6755' + content-type: + - application/json + cookie: + - __cf_bm=rVquIlcnYXc7wMkbKG5Ii90HxfQ_ukNJjDgSHhsWb1k-1734893816-1.0.1.1-33qDl8KNWcxLAGBuPhT8FrZ6QUnEy9oOYh2Fp2hIjDnF.cQlyrgxiWcuHljTTxG_mH7eQrf1AHJ6p8sxZJZ30A; + _cfuvid=xqgLJsf3h5lBKZFTADRNNUqizeChNBBFoLvWiR2WPnw-1734893816555-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: + content: "{\n \"id\": \"chatcmpl-AhLQOmLKuevpaWtRyyhHjHVYqvNVk\",\n \"object\": + \"chat.completion\",\n \"created\": 1734893824,\n \"model\": \"gpt-4o-mini-2024-07-18\",\n + \ \"choices\": [\n {\n \"index\": 0,\n \"message\": {\n \"role\": + \"assistant\",\n \"content\": \"Thought: I have received a comprehensive + overview from the Senior Writer that includes the necessary information about + AI Agents, their functionalities, applications, and future implications. Now, + I can compile this into a final answer following the specified format: a complete + four-paragraph article.\\n\\nFinal Answer: \\n\\n**Overview of AI Agents** \\nAI + agents are advanced software systems designed to autonomously perform tasks, + make decisions, and learn from their environments without needing constant human + intervention. They leverage machine learning, natural language processing, and + various AI techniques to simulate human-like understanding and autonomy. These + agents can be categorized into three types: reactive agents (which operate purely + based on their environment), deliberative agents (which can make decisions based + on reasoning), and hybrid agents that incorporate aspects of both types. Their + ability to adapt and learn over time makes them instrumental in automating processes + across various domains.\\n\\n**Functionalities of AI Agents** \\nThe core functionalities + of AI agents include perception, action, learning, and interaction. They perceive + data through sensors or data feeds, process information through algorithms, + and take actions based on this data. Machine learning allows them to refine + their performance over time by analyzing outcomes and adjusting their strategies + accordingly. Interaction capabilities enable them to communicate with users, + providing insights, answering queries, or even negotiating in some cases. These + functionalities make AI agents invaluable for tasks such as predictive analytics, + personal assistance, and real-time decision-making in complex systems.\\n\\n**Applications + in Various Industries** \\nAI agents are already being utilized across multiple + industries, demonstrating their versatility and efficiency. In healthcare, AI + agents assist in diagnostics by analyzing medical records and suggesting treatment + plans tailored to individual patients. In finance, they power robo-advisors + that manage investment portfolios, automate trading strategies, and provide + financial advice based on real-time market analysis. Furthermore, in customer + service, AI chatbots serve as virtual assistants, enhancing user experience + by providing instant support and resolving queries without human intervention. + The logistics and supply chain industries have also seen AI agents optimize + inventory management and route planning, significantly improving operational + efficiency.\\n\\n**Future Impact on Technology and Society** \\nThe ongoing + development of AI agents is poised to have a profound impact on technology and + society. As these agents become more sophisticated, we can anticipate a shift + towards increased automation in both professional and personal spheres, leading + to enhanced productivity and new business models. However, this automation introduces + challenges such as job displacement and ethical considerations regarding decision-making + by AI. It is essential to foster an ongoing dialogue on the implications of + AI agents to ensure responsible development and integration into our daily lives. + As AI agents continue to evolve, they will undoubtedly play a pivotal role in + shaping the future of technology and its intersection with societal dynamics, + making it critical for us to engage thoughtfully with this emerging paradigm.\",\n + \ \"refusal\": null\n },\n \"logprobs\": null,\n \"finish_reason\": + \"stop\"\n }\n ],\n \"usage\": {\n \"prompt_tokens\": 1242,\n \"completion_tokens\": + 550,\n \"total_tokens\": 1792,\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_0aa8d3e20b\"\n}\n" + headers: + CF-Cache-Status: + - DYNAMIC + CF-RAY: + - 8f6255e49b37a519-GRU + Connection: + - keep-alive + Content-Encoding: + - gzip + Content-Type: + - application/json + Date: + - Sun, 22 Dec 2024 18:57:12 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: + - '7562' + 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: + - '149998353' + x-ratelimit-reset-requests: + - 2ms + x-ratelimit-reset-tokens: + - 0s + x-request-id: + - req_a812bbb85b3d785660c4662212614ab9 + http_version: HTTP/1.1 + status_code: 200 +version: 1 diff --git a/tests/cassettes/test_multimodal_agent_live_image_analysis.yaml b/tests/cassettes/test_multimodal_agent_live_image_analysis.yaml new file mode 100644 index 0000000000..76417ce276 --- /dev/null +++ b/tests/cassettes/test_multimodal_agent_live_image_analysis.yaml @@ -0,0 +1,481 @@ +interactions: +- request: + body: '{"messages": [{"role": "system", "content": "You are Image Analyst. You''re + an expert at visual analysis, trained to notice and describe details in images.\nYour + personal goal is: Analyze images with high attention to detail\nYou ONLY have + access to the following tools, and should NEVER make up tools that are not listed + here:\n\nTool Name: Add image to content\nTool Arguments: {''image_url'': {''description'': + ''The URL or path of the image to add'', ''type'': ''str''}, ''action'': {''description'': + ''Optional context or question about the image'', ''type'': ''str''}}\nTool + Description: See image to understand it''s content, you can optionally ask a + question about the image\n\nUse the following format:\n\nThought: you should + always think about what to do\nAction: the action to take, only one name of + [Add image to content], 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"}, {"role": "user", + "content": "\nCurrent Task: \n Analyze the provided image and describe + what you see in detail.\n Focus on main elements, colors, composition, + and any notable details.\n Image: https://media.istockphoto.com/id/946087016/photo/aerial-view-of-lower-manhattan-new-york.jpg?s=612x612&w=0&k=20&c=viLiMRznQ8v5LzKTt_LvtfPFUVl1oiyiemVdSlm29_k=\n \n\nThis + is the expect criteria for your final answer: A comprehensive description of + the image contents.\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: + - '1948' + 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: + content: "{\n \"id\": \"chatcmpl-AiuIfzzcje5KdvKIG5CkFeORroiKk\",\n \"object\": + \"chat.completion\",\n \"created\": 1735266213,\n \"model\": \"gpt-4o-2024-08-06\",\n + \ \"choices\": [\n {\n \"index\": 0,\n \"message\": {\n \"role\": + \"assistant\",\n \"content\": \"Action: Add image to content\\nAction + Input: {\\\"image_url\\\": \\\"https://media.istockphoto.com/id/946087016/photo/aerial-view-of-lower-manhattan-new-york.jpg?s=612x612&w=0&k=20&c=viLiMRznQ8v5LzKTt_LvtfPFUVl1oiyiemVdSlm29_k=\\\", + \\\"action\\\": \\\"Analyze the provided image and describe what you see in + detail.\\\"}\",\n \"refusal\": null\n },\n \"logprobs\": null,\n + \ \"finish_reason\": \"stop\"\n }\n ],\n \"usage\": {\n \"prompt_tokens\": + 417,\n \"completion_tokens\": 103,\n \"total_tokens\": 520,\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_5f20662549\"\n}\n" + headers: + CF-Cache-Status: + - DYNAMIC + CF-RAY: + - 8f85d96b280df217-GRU + Connection: + - keep-alive + Content-Encoding: + - gzip + Content-Type: + - application/json + Date: + - Fri, 27 Dec 2024 02:23:35 GMT + Server: + - cloudflare + Set-Cookie: + - __cf_bm=kJ1pw1xjCMSxjHSS8iJC5z_j2PZxl.i387KCpj9xNZU-1735266215-1.0.1.1-Ybg0wVTsrBlpVZmtQyA1ullY8m3v2Ix0N_SYlhr9z7zKfbLeqGZEVL37YSY.dvIiLVY3XPZzMtG8Xwo6UucW6A; + path=/; expires=Fri, 27-Dec-24 02:53:35 GMT; domain=.api.openai.com; HttpOnly; + Secure; SameSite=None + - _cfuvid=v_wJZ5m7qCjrnRfks0gT2GAk9yR14BdIDAQiQR7xxI8-1735266215000-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: + - '1212' + 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: + - '29999539' + x-ratelimit-reset-requests: + - 6ms + x-ratelimit-reset-tokens: + - 0s + x-request-id: + - req_663a2b18099a18361d6b02befc175289 + http_version: HTTP/1.1 + status_code: 200 +- request: + body: !!binary | + Co4LCiQKIgoMc2VydmljZS5uYW1lEhIKEGNyZXdBSS10ZWxlbWV0cnkS5QoKEgoQY3Jld2FpLnRl + bGVtZXRyeRKjBwoQHmzzumMNXHOgpJ4zCIxJSxII72WnLlLfRyYqDENyZXcgQ3JlYXRlZDABOQjB + gFxt5xQYQYhMiVxt5xQYShoKDmNyZXdhaV92ZXJzaW9uEggKBjAuODYuMEoaCg5weXRob25fdmVy + c2lvbhIICgYzLjExLjdKLgoIY3Jld19rZXkSIgogZTM5NTY3YjUwNTI5MDljYTMzNDA5ODRiODM4 + OTgwZWFKMQoHY3Jld19pZBImCiQ4MDA0YTA1NC0zYjNkLTQ4OGEtYTlkNC1kZWQzMDVhMDIxY2FK + HAoMY3Jld19wcm9jZXNzEgwKCnNlcXVlbnRpYWxKEQoLY3Jld19tZW1vcnkSAhAAShoKFGNyZXdf + bnVtYmVyX29mX3Rhc2tzEgIYAUobChVjcmV3X251bWJlcl9vZl9hZ2VudHMSAhgBSs4CCgtjcmV3 + X2FnZW50cxK+Agq7Alt7ImtleSI6ICI5ZGM4Y2NlMDMwNDY4MTk2MDQxYjRjMzgwYjYxN2NiMCIs + ICJpZCI6ICJjNTZhZGI2Mi1lMGIwLTQzYzAtYmQ4OC0xYzEwYTNhNmU5NDQiLCAicm9sZSI6ICJJ + bWFnZSBBbmFseXN0IiwgInZlcmJvc2U/IjogdHJ1ZSwgIm1heF9pdGVyIjogMjAsICJtYXhfcnBt + IjogbnVsbCwgImZ1bmN0aW9uX2NhbGxpbmdfbGxtIjogIiIsICJsbG0iOiAiZ3B0LTRvIiwgImRl + bGVnYXRpb25fZW5hYmxlZD8iOiBmYWxzZSwgImFsbG93X2NvZGVfZXhlY3V0aW9uPyI6IGZhbHNl + LCAibWF4X3JldHJ5X2xpbWl0IjogMiwgInRvb2xzX25hbWVzIjogW119XUqCAgoKY3Jld190YXNr + cxLzAQrwAVt7ImtleSI6ICJhOWE3NmNhNjk1N2QwYmZmYTY5ZWFiMjBiNjY0ODIyYiIsICJpZCI6 + ICJhNzFiZDllNC0wNzdkLTRmMTQtODg0MS03MGMwZWM4MGZkMmMiLCAiYXN5bmNfZXhlY3V0aW9u + PyI6IGZhbHNlLCAiaHVtYW5faW5wdXQ/IjogZmFsc2UsICJhZ2VudF9yb2xlIjogIkltYWdlIEFu + YWx5c3QiLCAiYWdlbnRfa2V5IjogIjlkYzhjY2UwMzA0NjgxOTYwNDFiNGMzODBiNjE3Y2IwIiwg + InRvb2xzX25hbWVzIjogW119XXoCGAGFAQABAAASjgIKEOZ5pMdq9ep85DrP1Vv8Y8MSCE7ahOkm + 2IDHKgxUYXNrIENyZWF0ZWQwATlIg85cbecUGEGQ9M5cbecUGEouCghjcmV3X2tleRIiCiBlMzk1 + NjdiNTA1MjkwOWNhMzM0MDk4NGI4Mzg5ODBlYUoxCgdjcmV3X2lkEiYKJDgwMDRhMDU0LTNiM2Qt + NDg4YS1hOWQ0LWRlZDMwNWEwMjFjYUouCgh0YXNrX2tleRIiCiBhOWE3NmNhNjk1N2QwYmZmYTY5 + ZWFiMjBiNjY0ODIyYkoxCgd0YXNrX2lkEiYKJGE3MWJkOWU0LTA3N2QtNGYxNC04ODQxLTcwYzBl + YzgwZmQyY3oCGAGFAQABAAASlwEKECyaQQK8JkKLh6S2mWHTeDgSCPWCpr7v9CQZKgpUb29sIFVz + YWdlMAE5MLyst23nFBhBOJy/t23nFBhKGgoOY3Jld2FpX3ZlcnNpb24SCAoGMC44Ni4wSiMKCXRv + b2xfbmFtZRIWChRBZGQgaW1hZ2UgdG8gY29udGVudEoOCghhdHRlbXB0cxICGAF6AhgBhQEAAQAA + headers: + Accept: + - '*/*' + Accept-Encoding: + - gzip, deflate + Connection: + - keep-alive + Content-Length: + - '1425' + 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: + - Fri, 27 Dec 2024 02:23:39 GMT + status: + code: 200 + message: OK +- request: + body: '{"messages": [{"role": "system", "content": "You are Image Analyst. You''re + an expert at visual analysis, trained to notice and describe details in images.\nYour + personal goal is: Analyze images with high attention to detail\nYou ONLY have + access to the following tools, and should NEVER make up tools that are not listed + here:\n\nTool Name: Add image to content\nTool Arguments: {''image_url'': {''description'': + ''The URL or path of the image to add'', ''type'': ''str''}, ''action'': {''description'': + ''Optional context or question about the image'', ''type'': ''str''}}\nTool + Description: See image to understand it''s content, you can optionally ask a + question about the image\n\nUse the following format:\n\nThought: you should + always think about what to do\nAction: the action to take, only one name of + [Add image to content], 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"}, {"role": "user", + "content": "\nCurrent Task: \n Analyze the provided image and describe + what you see in detail.\n Focus on main elements, colors, composition, + and any notable details.\n Image: https://media.istockphoto.com/id/946087016/photo/aerial-view-of-lower-manhattan-new-york.jpg?s=612x612&w=0&k=20&c=viLiMRznQ8v5LzKTt_LvtfPFUVl1oiyiemVdSlm29_k=\n \n\nThis + is the expect criteria for your final answer: A comprehensive description of + the image contents.\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:"}, + {"role": "user", "content": [{"type": "text", "text": "Analyze the provided + image and describe what you see in detail."}, {"type": "image_url", "image_url": + {"url": "https://media.istockphoto.com/id/946087016/photo/aerial-view-of-lower-manhattan-new-york.jpg?s=612x612&w=0&k=20&c=viLiMRznQ8v5LzKTt_LvtfPFUVl1oiyiemVdSlm29_k="}}]}], + "model": "gpt-4o", "stop": ["\nObservation:"], "stream": false}' + headers: + accept: + - application/json + accept-encoding: + - gzip, deflate + connection: + - keep-alive + content-length: + - '2279' + content-type: + - application/json + cookie: + - __cf_bm=kJ1pw1xjCMSxjHSS8iJC5z_j2PZxl.i387KCpj9xNZU-1735266215-1.0.1.1-Ybg0wVTsrBlpVZmtQyA1ullY8m3v2Ix0N_SYlhr9z7zKfbLeqGZEVL37YSY.dvIiLVY3XPZzMtG8Xwo6UucW6A; + _cfuvid=v_wJZ5m7qCjrnRfks0gT2GAk9yR14BdIDAQiQR7xxI8-1735266215000-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: + content: "{\n \"id\": \"chatcmpl-AiuIiqT33ROFMdw1gNmqH9jiw6PfF\",\n \"object\": + \"chat.completion\",\n \"created\": 1735266216,\n \"model\": \"gpt-4o-2024-08-06\",\n + \ \"choices\": [\n {\n \"index\": 0,\n \"message\": {\n \"role\": + \"assistant\",\n \"content\": \"The image is an aerial view of Lower + Manhattan in New York City. \\n\\nMain Elements:\\n- The One World Trade Center + tower stands prominently, distinguishable by its sleek, tapering structure reaching + into the sky, surrounded by other skyscrapers.\\n- Skyscrapers in varying heights + and architectural styles, fill the densely packed urban landscape.\\n- A waterfront + is visible at the edges, with docks and piers extending into the water.\\n\\nColors:\\n- + The buildings exhibit a mix of colors, predominantly grays, whites, and browns, + against the blues of the sky and water.\\n- There's a section of greenery visible, + likely a park or recreational space, offering a contrast with its vibrant green + hues.\\n\\nComposition:\\n- The angle of the photograph showcases the expanse + of the city, highlighting the density and scale of the buildings.\\n- Water + borders the city on two prominent sides, creating a natural boundary and enhancing + the island's urban island feel.\\n\\nNotable Details:\\n- The image captures + the iconic layout of Manhattan, with the surrounding Hudson River and New York + Harbor visible in the background.\\n- Beyond Lower Manhattan, more of the cityscape + stretches into the distance, illustrating the vastness of New York City.\\n- + The day appears clear and sunny, with shadows casting from the buildings, indicating + time in the morning or late afternoon.\\n\\nOverall, the image is a striking + depiction of the dynamic and bustling environment of New York's Lower Manhattan, + encapsulating its urban character and proximity to the water.\",\n \"refusal\": + null\n },\n \"logprobs\": null,\n \"finish_reason\": \"stop\"\n + \ }\n ],\n \"usage\": {\n \"prompt_tokens\": 858,\n \"completion_tokens\": + 295,\n \"total_tokens\": 1153,\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_5f20662549\"\n}\n" + headers: + CF-Cache-Status: + - DYNAMIC + CF-RAY: + - 8f85d9741d0cf217-GRU + Connection: + - keep-alive + Content-Encoding: + - gzip + Content-Type: + - application/json + Date: + - Fri, 27 Dec 2024 02:23:40 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: + - '5136' + openai-version: + - '2020-10-01' + strict-transport-security: + - max-age=31536000; includeSubDomains; preload + x-ratelimit-limit-input-images: + - '50000' + x-ratelimit-limit-requests: + - '10000' + x-ratelimit-limit-tokens: + - '30000000' + x-ratelimit-remaining-input-images: + - '49999' + x-ratelimit-remaining-requests: + - '9999' + x-ratelimit-remaining-tokens: + - '29998756' + x-ratelimit-reset-input-images: + - 1ms + x-ratelimit-reset-requests: + - 6ms + x-ratelimit-reset-tokens: + - 2ms + x-request-id: + - req_57a7430712d4ff4a81f600ffb94d3b6e + http_version: HTTP/1.1 + status_code: 200 +- request: + body: '{"messages": [{"role": "system", "content": "You are Image Analyst. You''re + an expert at visual analysis, trained to notice and describe details in images.\nYour + personal goal is: Analyze images with high attention to detail\nYou ONLY have + access to the following tools, and should NEVER make up tools that are not listed + here:\n\nTool Name: Add image to content\nTool Arguments: {''image_url'': {''description'': + ''The URL or path of the image to add'', ''type'': ''str''}, ''action'': {''description'': + ''Optional context or question about the image'', ''type'': ''str''}}\nTool + Description: See image to understand it''s content, you can optionally ask a + question about the image\n\nUse the following format:\n\nThought: you should + always think about what to do\nAction: the action to take, only one name of + [Add image to content], 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"}, {"role": "user", + "content": "\nCurrent Task: \n Analyze the provided image and describe + what you see in detail.\n Focus on main elements, colors, composition, + and any notable details.\n Image: https://media.istockphoto.com/id/946087016/photo/aerial-view-of-lower-manhattan-new-york.jpg?s=612x612&w=0&k=20&c=viLiMRznQ8v5LzKTt_LvtfPFUVl1oiyiemVdSlm29_k=\n \n\nThis + is the expect criteria for your final answer: A comprehensive description of + the image contents.\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:"}, + {"role": "user", "content": [{"type": "text", "text": "Analyze the provided + image and describe what you see in detail."}, {"type": "image_url", "image_url": + {"url": "https://media.istockphoto.com/id/946087016/photo/aerial-view-of-lower-manhattan-new-york.jpg?s=612x612&w=0&k=20&c=viLiMRznQ8v5LzKTt_LvtfPFUVl1oiyiemVdSlm29_k="}}]}, + {"role": "user", "content": "I did it wrong. Invalid Format: I missed the ''Action:'' + after ''Thought:''. I will do right next, and don''t use a tool I have already + used.\n\nIf you don''t need to use any more tools, you must give your best complete + final answer, make sure it satisfies the expected 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"}], "model": "gpt-4o", "stop": ["\nObservation:"], + "stream": false}' + headers: + accept: + - application/json + accept-encoding: + - gzip, deflate + connection: + - keep-alive + content-length: + - '2717' + content-type: + - application/json + cookie: + - __cf_bm=kJ1pw1xjCMSxjHSS8iJC5z_j2PZxl.i387KCpj9xNZU-1735266215-1.0.1.1-Ybg0wVTsrBlpVZmtQyA1ullY8m3v2Ix0N_SYlhr9z7zKfbLeqGZEVL37YSY.dvIiLVY3XPZzMtG8Xwo6UucW6A; + _cfuvid=v_wJZ5m7qCjrnRfks0gT2GAk9yR14BdIDAQiQR7xxI8-1735266215000-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: + content: "{\n \"id\": \"chatcmpl-AiuInuYNldaQVo6B1EsEquT1VFMN7\",\n \"object\": + \"chat.completion\",\n \"created\": 1735266221,\n \"model\": \"gpt-4o-2024-08-06\",\n + \ \"choices\": [\n {\n \"index\": 0,\n \"message\": {\n \"role\": + \"assistant\",\n \"content\": \"Thought: I now can give a great answer\\nFinal + Answer: The image is an aerial view of Lower Manhattan in New York City. The + photograph prominently features the cluster of skyscrapers that characterizes + the area, with One World Trade Center standing out as a particularly tall and + iconic structure. The buildings vary in color, with shades of glassy blue, grey, + and natural stone dominating the skyline. In the bottom part of the image, there + is a green space, likely Battery Park, providing a stark contrast to the dense + urban environment, with trees and pathways visible. The water surrounding Manhattan + is a deep blue, and several piers jut into the harbor. The Hudson River is visible + on the left, and the East River can be seen on the right, framing the island. + The overall composition captures the bustling and vibrant nature of New York\u2019s + financial hub, with bright sunlight illuminating the buildings, casting sharp + shadows and enhancing the depth of the cityscape. The sky is clear, suggesting + a sunny day with good visibility.\",\n \"refusal\": null\n },\n + \ \"logprobs\": null,\n \"finish_reason\": \"stop\"\n }\n ],\n + \ \"usage\": {\n \"prompt_tokens\": 952,\n \"completion_tokens\": 203,\n + \ \"total_tokens\": 1155,\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_5f20662549\"\n}\n" + headers: + CF-Cache-Status: + - DYNAMIC + CF-RAY: + - 8f85d995ad1ef217-GRU + Connection: + - keep-alive + Content-Encoding: + - gzip + Content-Type: + - application/json + Date: + - Fri, 27 Dec 2024 02:23:43 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: + - '3108' + openai-version: + - '2020-10-01' + strict-transport-security: + - max-age=31536000; includeSubDomains; preload + x-ratelimit-limit-input-images: + - '50000' + x-ratelimit-limit-requests: + - '10000' + x-ratelimit-limit-tokens: + - '30000000' + x-ratelimit-remaining-input-images: + - '49999' + x-ratelimit-remaining-requests: + - '9999' + x-ratelimit-remaining-tokens: + - '29998656' + x-ratelimit-reset-input-images: + - 1ms + x-ratelimit-reset-requests: + - 6ms + x-ratelimit-reset-tokens: + - 2ms + x-request-id: + - req_45f0e3d457a18f973a59074d16f137b6 + http_version: HTTP/1.1 + status_code: 200 +version: 1 diff --git a/tests/cassettes/test_task_tools_override_agent_tools.yaml b/tests/cassettes/test_task_tools_override_agent_tools.yaml new file mode 100644 index 0000000000..971363af47 --- /dev/null +++ b/tests/cassettes/test_task_tools_override_agent_tools.yaml @@ -0,0 +1,569 @@ +interactions: +- request: + body: '{"messages": [{"role": "system", "content": "You are Researcher. You''re + an expert researcher, specialized in technology, software engineering, AI and + startups. You work as a freelancer and is now working on doing research and + analysis for a new customer.\nYour personal goal is: Make the best research + and analysis on content about AI and AI agents\nYou ONLY have access to the + following tools, and should NEVER make up tools that are not listed here:\n\nTool + Name: Test Tool\nTool Arguments: {''query'': {''description'': ''Query to process'', + ''type'': ''str''}}\nTool Description: A test tool that just returns the input\n\nUse + the following format:\n\nThought: you should always think about what to do\nAction: + the action to take, only one name of [Test Tool], 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"}, {"role": "user", "content": "\nCurrent Task: Write a test + task\n\nThis is the expect criteria for your final answer: Test output\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: + - '1536' + content-type: + - application/json + cookie: + - _cfuvid=2u_Xw.i716TDjD2vb2mvMyWxhA4q1MM1JvbrA8CNZpI-1734895557894-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: + content: "{\n \"id\": \"chatcmpl-AhQfznhDMtsr58XvTuRDZoB1kxwfK\",\n \"object\": + \"chat.completion\",\n \"created\": 1734914011,\n \"model\": \"gpt-4o-mini-2024-07-18\",\n + \ \"choices\": [\n {\n \"index\": 0,\n \"message\": {\n \"role\": + \"assistant\",\n \"content\": \"I need to come up with a suitable test + task that meets the criteria provided. I will focus on outlining a clear and + effective test task related to AI and AI agents.\\n\\nAction: Test Tool\\nAction + Input: {\\\"query\\\": \\\"Create a test task that involves evaluating the performance + of an AI agent in a given scenario, including criteria for success, tools required, + and process for assessment.\\\"}\",\n \"refusal\": null\n },\n \"logprobs\": + null,\n \"finish_reason\": \"stop\"\n }\n ],\n \"usage\": {\n \"prompt_tokens\": + 298,\n \"completion_tokens\": 78,\n \"total_tokens\": 376,\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_d02d531b47\"\n}\n" + headers: + CF-RAY: + - 8f6442b868fda486-GRU + Connection: + - keep-alive + Content-Encoding: + - gzip + Content-Type: + - application/json + Date: + - Mon, 23 Dec 2024 00:33:32 GMT + Server: + - cloudflare + Set-Cookie: + - __cf_bm=i6jvNjhsDne300GPAeEmyiJJKYqy7OPuamFG_kht3KE-1734914012-1.0.1.1-tCeVANAF521vkXpBdgYw.ov.fYUr6t5QC4LG_DugWyzu4C60Pi2CruTVniUgfCvkcu6rdHA5DwnaEZf2jFaRCQ; + path=/; expires=Mon, 23-Dec-24 01:03:32 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 + cf-cache-status: + - DYNAMIC + openai-organization: + - crewai-iuxna1 + openai-processing-ms: + - '1400' + 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: + - '149999642' + x-ratelimit-reset-requests: + - 2ms + x-ratelimit-reset-tokens: + - 0s + x-request-id: + - req_c3e50e9ca9dc22de5572692e1a9c0f16 + http_version: HTTP/1.1 + status_code: 200 +- request: + body: !!binary | + CrBzCiQKIgoMc2VydmljZS5uYW1lEhIKEGNyZXdBSS10ZWxlbWV0cnkSh3MKEgoQY3Jld2FpLnRl + bGVtZXRyeRLUCwoQEr8cFisEEEEUtXBvovq6lhIIYdkQ+ekBh3wqDENyZXcgQ3JlYXRlZDABOThc + YLAZpxMYQfCuabAZpxMYShoKDmNyZXdhaV92ZXJzaW9uEggKBjAuODYuMEoaCg5weXRob25fdmVy + c2lvbhIICgYzLjExLjdKLgoIY3Jld19rZXkSIgogZGUxMDFkODU1M2VhMDI0NTM3YTA4ZjgxMmVl + NmI3NGFKMQoHY3Jld19pZBImCiRmNTc2MjViZC1jZmY3LTRlNGMtYWM1Zi0xZWFiNjQyMzJjMmRK + HAoMY3Jld19wcm9jZXNzEgwKCnNlcXVlbnRpYWxKEQoLY3Jld19tZW1vcnkSAhAAShoKFGNyZXdf + bnVtYmVyX29mX3Rhc2tzEgIYAkobChVjcmV3X251bWJlcl9vZl9hZ2VudHMSAhgCSpIFCgtjcmV3 + X2FnZW50cxKCBQr/BFt7ImtleSI6ICI4YmQyMTM5YjU5NzUxODE1MDZlNDFmZDljNDU2M2Q3NSIs + ICJpZCI6ICI1Y2Y0OWVjNy05NWYzLTRkZDctODU3Mi1mODAwNDA4NjBiMjgiLCAicm9sZSI6ICJS + ZXNlYXJjaGVyIiwgInZlcmJvc2U/IjogZmFsc2UsICJtYXhfaXRlciI6IDIwLCAibWF4X3JwbSI6 + IG51bGwsICJmdW5jdGlvbl9jYWxsaW5nX2xsbSI6ICIiLCAibGxtIjogImdwdC00by1taW5pIiwg + ImRlbGVnYXRpb25fZW5hYmxlZD8iOiBmYWxzZSwgImFsbG93X2NvZGVfZXhlY3V0aW9uPyI6IGZh + bHNlLCAibWF4X3JldHJ5X2xpbWl0IjogMiwgInRvb2xzX25hbWVzIjogW119LCB7ImtleSI6ICI5 + YTUwMTVlZjQ4OTVkYzYyNzhkNTQ4MThiYTQ0NmFmNyIsICJpZCI6ICI0MTEyM2QzZC01NmEwLTRh + NTgtYTljNi1mZjUwNjRmZjNmNTEiLCAicm9sZSI6ICJTZW5pb3IgV3JpdGVyIiwgInZlcmJvc2U/ + IjogZmFsc2UsICJtYXhfaXRlciI6IDIwLCAibWF4X3JwbSI6IG51bGwsICJmdW5jdGlvbl9jYWxs + aW5nX2xsbSI6ICIiLCAibGxtIjogImdwdC00by1taW5pIiwgImRlbGVnYXRpb25fZW5hYmxlZD8i + OiBmYWxzZSwgImFsbG93X2NvZGVfZXhlY3V0aW9uPyI6IGZhbHNlLCAibWF4X3JldHJ5X2xpbWl0 + IjogMiwgInRvb2xzX25hbWVzIjogW119XUrvAwoKY3Jld190YXNrcxLgAwrdA1t7ImtleSI6ICI5 + NDRhZWYwYmFjODQwZjFjMjdiZDgzYTkzN2JjMzYxYiIsICJpZCI6ICI3ZDM2NDFhNi1hZmM4LTRj + NmMtYjkzMy0wNGZlZjY2NjUxN2MiLCAiYXN5bmNfZXhlY3V0aW9uPyI6IGZhbHNlLCAiaHVtYW5f + aW5wdXQ/IjogZmFsc2UsICJhZ2VudF9yb2xlIjogIlJlc2VhcmNoZXIiLCAiYWdlbnRfa2V5Ijog + IjhiZDIxMzliNTk3NTE4MTUwNmU0MWZkOWM0NTYzZDc1IiwgInRvb2xzX25hbWVzIjogW119LCB7 + ImtleSI6ICI5ZjJkNGU5M2FiNTkwYzcyNTg4NzAyNzUwOGFmOTI3OCIsICJpZCI6ICIzNTVjZjFh + OS1lOTkzLTQxMTQtOWM0NC0yZDM5MDlhMDljNWYiLCAiYXN5bmNfZXhlY3V0aW9uPyI6IGZhbHNl + LCAiaHVtYW5faW5wdXQ/IjogZmFsc2UsICJhZ2VudF9yb2xlIjogIlNlbmlvciBXcml0ZXIiLCAi + YWdlbnRfa2V5IjogIjlhNTAxNWVmNDg5NWRjNjI3OGQ1NDgxOGJhNDQ2YWY3IiwgInRvb2xzX25h + bWVzIjogW119XXoCGAGFAQABAAASjgIKEHbV3nDt+ndNQNix1f+5+cASCL+l6KV3+FEpKgxUYXNr + IENyZWF0ZWQwATmgfo+wGacTGEEQE5CwGacTGEouCghjcmV3X2tleRIiCiBkZTEwMWQ4NTUzZWEw + MjQ1MzdhMDhmODEyZWU2Yjc0YUoxCgdjcmV3X2lkEiYKJGY1NzYyNWJkLWNmZjctNGU0Yy1hYzVm + LTFlYWI2NDIzMmMyZEouCgh0YXNrX2tleRIiCiA5NDRhZWYwYmFjODQwZjFjMjdiZDgzYTkzN2Jj + MzYxYkoxCgd0YXNrX2lkEiYKJDdkMzY0MWE2LWFmYzgtNGM2Yy1iOTMzLTA0ZmVmNjY2NTE3Y3oC + GAGFAQABAAASjgIKECqDENVoAz+3ybVKR/wz7dMSCKI9ILLFYx8SKgxUYXNrIENyZWF0ZWQwATng + 63CzGacTGEE4AXKzGacTGEouCghjcmV3X2tleRIiCiBkZTEwMWQ4NTUzZWEwMjQ1MzdhMDhmODEy + ZWU2Yjc0YUoxCgdjcmV3X2lkEiYKJGY1NzYyNWJkLWNmZjctNGU0Yy1hYzVmLTFlYWI2NDIzMmMy + ZEouCgh0YXNrX2tleRIiCiA5ZjJkNGU5M2FiNTkwYzcyNTg4NzAyNzUwOGFmOTI3OEoxCgd0YXNr + X2lkEiYKJDM1NWNmMWE5LWU5OTMtNDExNC05YzQ0LTJkMzkwOWEwOWM1ZnoCGAGFAQABAAAS1AsK + EOofSLF1HDmhYMt7eIAeFo8SCCaKUQMuWNdnKgxDcmV3IENyZWF0ZWQwATkYKA62GacTGEFwlhW2 + GacTGEoaCg5jcmV3YWlfdmVyc2lvbhIICgYwLjg2LjBKGgoOcHl0aG9uX3ZlcnNpb24SCAoGMy4x + MS43Si4KCGNyZXdfa2V5EiIKIDRlOGU0MmNmMWVhN2U2NjhhMGU5MzJhNzAyMDY1NzQ5SjEKB2Ny + ZXdfaWQSJgokMmIzNTVjZDMtY2MwNi00Y2QxLTk0YjgtZTU5YjM5OGI3MjEzShwKDGNyZXdfcHJv + Y2VzcxIMCgpzZXF1ZW50aWFsShEKC2NyZXdfbWVtb3J5EgIQAEoaChRjcmV3X251bWJlcl9vZl90 + YXNrcxICGAJKGwoVY3Jld19udW1iZXJfb2ZfYWdlbnRzEgIYAkqSBQoLY3Jld19hZ2VudHMSggUK + /wRbeyJrZXkiOiAiOGJkMjEzOWI1OTc1MTgxNTA2ZTQxZmQ5YzQ1NjNkNzUiLCAiaWQiOiAiNWNm + NDllYzctOTVmMy00ZGQ3LTg1NzItZjgwMDQwODYwYjI4IiwgInJvbGUiOiAiUmVzZWFyY2hlciIs + ICJ2ZXJib3NlPyI6IGZhbHNlLCAibWF4X2l0ZXIiOiAyMCwgIm1heF9ycG0iOiBudWxsLCAiZnVu + Y3Rpb25fY2FsbGluZ19sbG0iOiAiIiwgImxsbSI6ICJncHQtNG8tbWluaSIsICJkZWxlZ2F0aW9u + X2VuYWJsZWQ/IjogZmFsc2UsICJhbGxvd19jb2RlX2V4ZWN1dGlvbj8iOiBmYWxzZSwgIm1heF9y + ZXRyeV9saW1pdCI6IDIsICJ0b29sc19uYW1lcyI6IFtdfSwgeyJrZXkiOiAiOWE1MDE1ZWY0ODk1 + ZGM2Mjc4ZDU0ODE4YmE0NDZhZjciLCAiaWQiOiAiNDExMjNkM2QtNTZhMC00YTU4LWE5YzYtZmY1 + MDY0ZmYzZjUxIiwgInJvbGUiOiAiU2VuaW9yIFdyaXRlciIsICJ2ZXJib3NlPyI6IGZhbHNlLCAi + bWF4X2l0ZXIiOiAyMCwgIm1heF9ycG0iOiBudWxsLCAiZnVuY3Rpb25fY2FsbGluZ19sbG0iOiAi + IiwgImxsbSI6ICJncHQtNG8tbWluaSIsICJkZWxlZ2F0aW9uX2VuYWJsZWQ/IjogZmFsc2UsICJh + bGxvd19jb2RlX2V4ZWN1dGlvbj8iOiBmYWxzZSwgIm1heF9yZXRyeV9saW1pdCI6IDIsICJ0b29s + c19uYW1lcyI6IFtdfV1K7wMKCmNyZXdfdGFza3MS4AMK3QNbeyJrZXkiOiAiNjc4NDlmZjcxN2Ri + YWRhYmExYjk1ZDVmMmRmY2VlYTEiLCAiaWQiOiAiOGE5OTgxMDYtZjg5Zi00YTQ5LThjZjEtYjk4 + MzQ5ZDE1NDRmIiwgImFzeW5jX2V4ZWN1dGlvbj8iOiBmYWxzZSwgImh1bWFuX2lucHV0PyI6IGZh + bHNlLCAiYWdlbnRfcm9sZSI6ICJSZXNlYXJjaGVyIiwgImFnZW50X2tleSI6ICI4YmQyMTM5YjU5 + NzUxODE1MDZlNDFmZDljNDU2M2Q3NSIsICJ0b29sc19uYW1lcyI6IFtdfSwgeyJrZXkiOiAiODRh + ZjlmYzFjZDMzMTk5Y2ViYjlkNDE0MjE4NWY4MDIiLCAiaWQiOiAiYTViMTg0MDgtYjA1OC00ZDE1 + LTkyMmUtNDJkN2M5Y2ViYjFhIiwgImFzeW5jX2V4ZWN1dGlvbj8iOiBmYWxzZSwgImh1bWFuX2lu + cHV0PyI6IGZhbHNlLCAiYWdlbnRfcm9sZSI6ICJTZW5pb3IgV3JpdGVyIiwgImFnZW50X2tleSI6 + ICI5YTUwMTVlZjQ4OTVkYzYyNzhkNTQ4MThiYTQ0NmFmNyIsICJ0b29sc19uYW1lcyI6IFtdfV16 + AhgBhQEAAQAAEsIJChDCLrcWQ+nu3SxOgnq50XhSEghjozRtuCFA0SoMQ3JldyBDcmVhdGVkMAE5 + CDeCthmnExhBmHiIthmnExhKGgoOY3Jld2FpX3ZlcnNpb24SCAoGMC44Ni4wShoKDnB5dGhvbl92 + ZXJzaW9uEggKBjMuMTEuN0ouCghjcmV3X2tleRIiCiBlM2ZkYTBmMzExMGZlODBiMTg5NDdjMDE0 + NzE0MzBhNEoxCgdjcmV3X2lkEiYKJGM1ZDQ0YjY5LTRhNzMtNDA3Zi1iY2RhLTUzZmUxZTQ3YTU3 + M0oeCgxjcmV3X3Byb2Nlc3MSDgoMaGllcmFyY2hpY2FsShEKC2NyZXdfbWVtb3J5EgIQAEoaChRj + cmV3X251bWJlcl9vZl90YXNrcxICGAFKGwoVY3Jld19udW1iZXJfb2ZfYWdlbnRzEgIYAkqSBQoL + Y3Jld19hZ2VudHMSggUK/wRbeyJrZXkiOiAiOGJkMjEzOWI1OTc1MTgxNTA2ZTQxZmQ5YzQ1NjNk + NzUiLCAiaWQiOiAiNWNmNDllYzctOTVmMy00ZGQ3LTg1NzItZjgwMDQwODYwYjI4IiwgInJvbGUi + OiAiUmVzZWFyY2hlciIsICJ2ZXJib3NlPyI6IGZhbHNlLCAibWF4X2l0ZXIiOiAyMCwgIm1heF9y + cG0iOiBudWxsLCAiZnVuY3Rpb25fY2FsbGluZ19sbG0iOiAiIiwgImxsbSI6ICJncHQtNG8tbWlu + aSIsICJkZWxlZ2F0aW9uX2VuYWJsZWQ/IjogZmFsc2UsICJhbGxvd19jb2RlX2V4ZWN1dGlvbj8i + OiBmYWxzZSwgIm1heF9yZXRyeV9saW1pdCI6IDIsICJ0b29sc19uYW1lcyI6IFtdfSwgeyJrZXki + OiAiOWE1MDE1ZWY0ODk1ZGM2Mjc4ZDU0ODE4YmE0NDZhZjciLCAiaWQiOiAiNDExMjNkM2QtNTZh + MC00YTU4LWE5YzYtZmY1MDY0ZmYzZjUxIiwgInJvbGUiOiAiU2VuaW9yIFdyaXRlciIsICJ2ZXJi + b3NlPyI6IGZhbHNlLCAibWF4X2l0ZXIiOiAyMCwgIm1heF9ycG0iOiBudWxsLCAiZnVuY3Rpb25f + Y2FsbGluZ19sbG0iOiAiIiwgImxsbSI6ICJncHQtNG8tbWluaSIsICJkZWxlZ2F0aW9uX2VuYWJs + ZWQ/IjogZmFsc2UsICJhbGxvd19jb2RlX2V4ZWN1dGlvbj8iOiBmYWxzZSwgIm1heF9yZXRyeV9s + aW1pdCI6IDIsICJ0b29sc19uYW1lcyI6IFtdfV1K2wEKCmNyZXdfdGFza3MSzAEKyQFbeyJrZXki + OiAiNWZhNjVjMDZhOWUzMWYyYzY5NTQzMjY2OGFjZDYyZGQiLCAiaWQiOiAiNjNhYTVlOTYtYTM4 + Yy00YjcyLWJiZDQtYjM2NmU5NTlhOWZhIiwgImFzeW5jX2V4ZWN1dGlvbj8iOiBmYWxzZSwgImh1 + bWFuX2lucHV0PyI6IGZhbHNlLCAiYWdlbnRfcm9sZSI6ICJOb25lIiwgImFnZW50X2tleSI6IG51 + bGwsICJ0b29sc19uYW1lcyI6IFtdfV16AhgBhQEAAQAAEuYJChA8kiyQ+AFdDSYkp0+TUWKvEgjW + 0grLw8r5KioMQ3JldyBDcmVhdGVkMAE5iLivvhmnExhBeG21vhmnExhKGgoOY3Jld2FpX3ZlcnNp + b24SCAoGMC44Ni4wShoKDnB5dGhvbl92ZXJzaW9uEggKBjMuMTEuN0ouCghjcmV3X2tleRIiCiBl + M2ZkYTBmMzExMGZlODBiMTg5NDdjMDE0NzE0MzBhNEoxCgdjcmV3X2lkEiYKJGIzZGQ1MGYxLTI0 + YWQtNDE5OC04ZGFhLTMwZTU0OTQ3MTlhMEoeCgxjcmV3X3Byb2Nlc3MSDgoMaGllcmFyY2hpY2Fs + ShEKC2NyZXdfbWVtb3J5EgIQAEoaChRjcmV3X251bWJlcl9vZl90YXNrcxICGAFKGwoVY3Jld19u + dW1iZXJfb2ZfYWdlbnRzEgIYAkqSBQoLY3Jld19hZ2VudHMSggUK/wRbeyJrZXkiOiAiOGJkMjEz + OWI1OTc1MTgxNTA2ZTQxZmQ5YzQ1NjNkNzUiLCAiaWQiOiAiNWNmNDllYzctOTVmMy00ZGQ3LTg1 + NzItZjgwMDQwODYwYjI4IiwgInJvbGUiOiAiUmVzZWFyY2hlciIsICJ2ZXJib3NlPyI6IGZhbHNl + LCAibWF4X2l0ZXIiOiAyMCwgIm1heF9ycG0iOiBudWxsLCAiZnVuY3Rpb25fY2FsbGluZ19sbG0i + OiAiIiwgImxsbSI6ICJncHQtNG8tbWluaSIsICJkZWxlZ2F0aW9uX2VuYWJsZWQ/IjogZmFsc2Us + ICJhbGxvd19jb2RlX2V4ZWN1dGlvbj8iOiBmYWxzZSwgIm1heF9yZXRyeV9saW1pdCI6IDIsICJ0 + b29sc19uYW1lcyI6IFtdfSwgeyJrZXkiOiAiOWE1MDE1ZWY0ODk1ZGM2Mjc4ZDU0ODE4YmE0NDZh + ZjciLCAiaWQiOiAiNDExMjNkM2QtNTZhMC00YTU4LWE5YzYtZmY1MDY0ZmYzZjUxIiwgInJvbGUi + OiAiU2VuaW9yIFdyaXRlciIsICJ2ZXJib3NlPyI6IGZhbHNlLCAibWF4X2l0ZXIiOiAyMCwgIm1h + eF9ycG0iOiBudWxsLCAiZnVuY3Rpb25fY2FsbGluZ19sbG0iOiAiIiwgImxsbSI6ICJncHQtNG8t + bWluaSIsICJkZWxlZ2F0aW9uX2VuYWJsZWQ/IjogZmFsc2UsICJhbGxvd19jb2RlX2V4ZWN1dGlv + bj8iOiBmYWxzZSwgIm1heF9yZXRyeV9saW1pdCI6IDIsICJ0b29sc19uYW1lcyI6IFtdfV1K/wEK + CmNyZXdfdGFza3MS8AEK7QFbeyJrZXkiOiAiNWZhNjVjMDZhOWUzMWYyYzY5NTQzMjY2OGFjZDYy + ZGQiLCAiaWQiOiAiNzEyODlkZTAtODQ4My00NDM2LWI2OGMtNDc1MWIzNTU0ZmUzIiwgImFzeW5j + X2V4ZWN1dGlvbj8iOiBmYWxzZSwgImh1bWFuX2lucHV0PyI6IGZhbHNlLCAiYWdlbnRfcm9sZSI6 + ICJSZXNlYXJjaGVyIiwgImFnZW50X2tleSI6ICI4YmQyMTM5YjU5NzUxODE1MDZlNDFmZDljNDU2 + M2Q3NSIsICJ0b29sc19uYW1lcyI6IFtdfV16AhgBhQEAAQAAEo4CChCTiJL+KK5ff9xnie6eZbEc + EghbtQixNaG5DioMVGFzayBDcmVhdGVkMAE5cIXNvhmnExhBuPbNvhmnExhKLgoIY3Jld19rZXkS + IgogZTNmZGEwZjMxMTBmZTgwYjE4OTQ3YzAxNDcxNDMwYTRKMQoHY3Jld19pZBImCiRiM2RkNTBm + MS0yNGFkLTQxOTgtOGRhYS0zMGU1NDk0NzE5YTBKLgoIdGFza19rZXkSIgogNWZhNjVjMDZhOWUz + MWYyYzY5NTQzMjY2OGFjZDYyZGRKMQoHdGFza19pZBImCiQ3MTI4OWRlMC04NDgzLTQ0MzYtYjY4 + Yy00NzUxYjM1NTRmZTN6AhgBhQEAAQAAEpwBChBCdDi/i+SH0kHHlJKQjmYgEgiemV9jVU5fQSoK + VG9vbCBVc2FnZTABOVj/YL8ZpxMYQWCwZr8ZpxMYShoKDmNyZXdhaV92ZXJzaW9uEggKBjAuODYu + MEooCgl0b29sX25hbWUSGwoZRGVsZWdhdGUgd29yayB0byBjb3dvcmtlckoOCghhdHRlbXB0cxIC + GAF6AhgBhQEAAQAAEqUBChBRuZ6Z/nNag4ubLeZ8L/8pEghCX4biKNFb6SoTVG9vbCBSZXBlYXRl + ZCBVc2FnZTABOUj9wr8ZpxMYQdg+yb8ZpxMYShoKDmNyZXdhaV92ZXJzaW9uEggKBjAuODYuMEoo + Cgl0b29sX25hbWUSGwoZRGVsZWdhdGUgd29yayB0byBjb3dvcmtlckoOCghhdHRlbXB0cxICGAF6 + AhgBhQEAAQAAEpwBChDnt1bxQsOb0LVscG9GDYVtEgjf62keNMl5ZyoKVG9vbCBVc2FnZTABOdha + 6MAZpxMYQWii7cAZpxMYShoKDmNyZXdhaV92ZXJzaW9uEggKBjAuODYuMEooCgl0b29sX25hbWUS + GwoZRGVsZWdhdGUgd29yayB0byBjb3dvcmtlckoOCghhdHRlbXB0cxICGAF6AhgBhQEAAQAAEpsB + ChDFqFA9b42EIwUxeNLTeScxEgiGFk7FwiNxVioKVG9vbCBVc2FnZTABObDAY8EZpxMYQdhIaMEZ + pxMYShoKDmNyZXdhaV92ZXJzaW9uEggKBjAuODYuMEonCgl0b29sX25hbWUSGgoYQXNrIHF1ZXN0 + aW9uIHRvIGNvd29ya2VySg4KCGF0dGVtcHRzEgIYAXoCGAGFAQABAAASwgkKEHpB0rbuWbSXijzV + QdTa3oQSCNSPnbmqe2PfKgxDcmV3IENyZWF0ZWQwATmIXxTCGacTGEF4GhnCGacTGEoaCg5jcmV3 + YWlfdmVyc2lvbhIICgYwLjg2LjBKGgoOcHl0aG9uX3ZlcnNpb24SCAoGMy4xMS43Si4KCGNyZXdf + a2V5EiIKIGUzZmRhMGYzMTEwZmU4MGIxODk0N2MwMTQ3MTQzMGE0SjEKB2NyZXdfaWQSJgokZGJm + YzNjMjctMmRjZS00MjIyLThiYmQtYmMxMjU3OTVlNWI1Sh4KDGNyZXdfcHJvY2VzcxIOCgxoaWVy + YXJjaGljYWxKEQoLY3Jld19tZW1vcnkSAhAAShoKFGNyZXdfbnVtYmVyX29mX3Rhc2tzEgIYAUob + ChVjcmV3X251bWJlcl9vZl9hZ2VudHMSAhgCSpIFCgtjcmV3X2FnZW50cxKCBQr/BFt7ImtleSI6 + ICI4YmQyMTM5YjU5NzUxODE1MDZlNDFmZDljNDU2M2Q3NSIsICJpZCI6ICI1Y2Y0OWVjNy05NWYz + LTRkZDctODU3Mi1mODAwNDA4NjBiMjgiLCAicm9sZSI6ICJSZXNlYXJjaGVyIiwgInZlcmJvc2U/ + IjogZmFsc2UsICJtYXhfaXRlciI6IDIwLCAibWF4X3JwbSI6IG51bGwsICJmdW5jdGlvbl9jYWxs + aW5nX2xsbSI6ICIiLCAibGxtIjogImdwdC00by1taW5pIiwgImRlbGVnYXRpb25fZW5hYmxlZD8i + OiBmYWxzZSwgImFsbG93X2NvZGVfZXhlY3V0aW9uPyI6IGZhbHNlLCAibWF4X3JldHJ5X2xpbWl0 + IjogMiwgInRvb2xzX25hbWVzIjogW119LCB7ImtleSI6ICI5YTUwMTVlZjQ4OTVkYzYyNzhkNTQ4 + MThiYTQ0NmFmNyIsICJpZCI6ICI0MTEyM2QzZC01NmEwLTRhNTgtYTljNi1mZjUwNjRmZjNmNTEi + LCAicm9sZSI6ICJTZW5pb3IgV3JpdGVyIiwgInZlcmJvc2U/IjogZmFsc2UsICJtYXhfaXRlciI6 + IDIwLCAibWF4X3JwbSI6IG51bGwsICJmdW5jdGlvbl9jYWxsaW5nX2xsbSI6ICIiLCAibGxtIjog + ImdwdC00by1taW5pIiwgImRlbGVnYXRpb25fZW5hYmxlZD8iOiBmYWxzZSwgImFsbG93X2NvZGVf + ZXhlY3V0aW9uPyI6IGZhbHNlLCAibWF4X3JldHJ5X2xpbWl0IjogMiwgInRvb2xzX25hbWVzIjog + W119XUrbAQoKY3Jld190YXNrcxLMAQrJAVt7ImtleSI6ICI1ZmE2NWMwNmE5ZTMxZjJjNjk1NDMy + NjY4YWNkNjJkZCIsICJpZCI6ICIyYWFjOTllMC0yNWVmLTQzN2MtYTJmZi1jZGFlMjg2ZWU2MzQi + LCAiYXN5bmNfZXhlY3V0aW9uPyI6IGZhbHNlLCAiaHVtYW5faW5wdXQ/IjogZmFsc2UsICJhZ2Vu + dF9yb2xlIjogIk5vbmUiLCAiYWdlbnRfa2V5IjogbnVsbCwgInRvb2xzX25hbWVzIjogW119XXoC + GAGFAQABAAAS1QkKEM6Xt0BvAHy+TI7iLC6ovN0SCEfHP30NZESSKgxDcmV3IENyZWF0ZWQwATkg + PdnDGacTGEFIPN/DGacTGEoaCg5jcmV3YWlfdmVyc2lvbhIICgYwLjg2LjBKGgoOcHl0aG9uX3Zl + cnNpb24SCAoGMy4xMS43Si4KCGNyZXdfa2V5EiIKIGU2NDk1NzNhMjZlNTg3OTBjYWMyMWEzN2Nk + NDQ0MzdhSjEKB2NyZXdfaWQSJgokNjE3MDA3NGMtYzU5OS00ODkyLTkwYzYtMTcxYjhkM2Y1OTRh + ShwKDGNyZXdfcHJvY2VzcxIMCgpzZXF1ZW50aWFsShEKC2NyZXdfbWVtb3J5EgIQAEoaChRjcmV3 + X251bWJlcl9vZl90YXNrcxICGAFKGwoVY3Jld19udW1iZXJfb2ZfYWdlbnRzEgIYAkqKBQoLY3Jl + d19hZ2VudHMS+gQK9wRbeyJrZXkiOiAiMzI4MjE3YjZjMjk1OWJkZmM0N2NhZDAwZTg0ODkwZDAi + LCAiaWQiOiAiYjNmMTczZTktNjY3NS00OTFkLTgyYjctODM4NmRkMjExMDM1IiwgInJvbGUiOiAi + Q0VPIiwgInZlcmJvc2U/IjogZmFsc2UsICJtYXhfaXRlciI6IDIwLCAibWF4X3JwbSI6IG51bGws + ICJmdW5jdGlvbl9jYWxsaW5nX2xsbSI6ICIiLCAibGxtIjogImdwdC00by1taW5pIiwgImRlbGVn + YXRpb25fZW5hYmxlZD8iOiB0cnVlLCAiYWxsb3dfY29kZV9leGVjdXRpb24/IjogZmFsc2UsICJt + YXhfcmV0cnlfbGltaXQiOiAyLCAidG9vbHNfbmFtZXMiOiBbXX0sIHsia2V5IjogIjlhNTAxNWVm + NDg5NWRjNjI3OGQ1NDgxOGJhNDQ2YWY3IiwgImlkIjogIjQxMTIzZDNkLTU2YTAtNGE1OC1hOWM2 + LWZmNTA2NGZmM2Y1MSIsICJyb2xlIjogIlNlbmlvciBXcml0ZXIiLCAidmVyYm9zZT8iOiBmYWxz + ZSwgIm1heF9pdGVyIjogMjAsICJtYXhfcnBtIjogbnVsbCwgImZ1bmN0aW9uX2NhbGxpbmdfbGxt + IjogIiIsICJsbG0iOiAiZ3B0LTRvLW1pbmkiLCAiZGVsZWdhdGlvbl9lbmFibGVkPyI6IGZhbHNl + LCAiYWxsb3dfY29kZV9leGVjdXRpb24/IjogZmFsc2UsICJtYXhfcmV0cnlfbGltaXQiOiAyLCAi + dG9vbHNfbmFtZXMiOiBbXX1dSvgBCgpjcmV3X3Rhc2tzEukBCuYBW3sia2V5IjogIjBiOWQ2NWRi + NmI3YWVkZmIzOThjNTllMmE5ZjcxZWM1IiwgImlkIjogImJiNmI1Njg3LTg5NGMtNDAyNS05M2My + LTMyYjdkZmEwZTUxMyIsICJhc3luY19leGVjdXRpb24/IjogZmFsc2UsICJodW1hbl9pbnB1dD8i + OiBmYWxzZSwgImFnZW50X3JvbGUiOiAiQ0VPIiwgImFnZW50X2tleSI6ICIzMjgyMTdiNmMyOTU5 + YmRmYzQ3Y2FkMDBlODQ4OTBkMCIsICJ0b29sc19uYW1lcyI6IFtdfV16AhgBhQEAAQAAEo4CChCK + KIL9w7sqoMzG3JItjK8eEgiR4RSmJw+SMSoMVGFzayBDcmVhdGVkMAE5CCjywxmnExhByIXywxmn + ExhKLgoIY3Jld19rZXkSIgogZTY0OTU3M2EyNmU1ODc5MGNhYzIxYTM3Y2Q0NDQzN2FKMQoHY3Jl + d19pZBImCiQ2MTcwMDc0Yy1jNTk5LTQ4OTItOTBjNi0xNzFiOGQzZjU5NGFKLgoIdGFza19rZXkS + IgogMGI5ZDY1ZGI2YjdhZWRmYjM5OGM1OWUyYTlmNzFlYzVKMQoHdGFza19pZBImCiRiYjZiNTY4 + Ny04OTRjLTQwMjUtOTNjMi0zMmI3ZGZhMGU1MTN6AhgBhQEAAQAAEpwBChD+/zv5udkceIEyIb7d + ne5vEgj1My75q1O7UCoKVG9vbCBVc2FnZTABOThPfMQZpxMYQcA4g8QZpxMYShoKDmNyZXdhaV92 + ZXJzaW9uEggKBjAuODYuMEooCgl0b29sX25hbWUSGwoZRGVsZWdhdGUgd29yayB0byBjb3dvcmtl + ckoOCghhdHRlbXB0cxICGAF6AhgBhQEAAQAAEuAJChBIzM1Xa9IhegFDHxt6rj3eEgj9z56V1hXk + aCoMQ3JldyBDcmVhdGVkMAE5mEoMxRmnExhBoPsRxRmnExhKGgoOY3Jld2FpX3ZlcnNpb24SCAoG + MC44Ni4wShoKDnB5dGhvbl92ZXJzaW9uEggKBjMuMTEuN0ouCghjcmV3X2tleRIiCiBlNjQ5NTcz + YTI2ZTU4NzkwY2FjMjFhMzdjZDQ0NDM3YUoxCgdjcmV3X2lkEiYKJGQ4MjhhZWM2LTg2N2MtNDdh + YS04ODY4LWQwMWYwNGM0MGE0MUocCgxjcmV3X3Byb2Nlc3MSDAoKc2VxdWVudGlhbEoRCgtjcmV3 + X21lbW9yeRICEABKGgoUY3Jld19udW1iZXJfb2ZfdGFza3MSAhgBShsKFWNyZXdfbnVtYmVyX29m + X2FnZW50cxICGAJKigUKC2NyZXdfYWdlbnRzEvoECvcEW3sia2V5IjogIjMyODIxN2I2YzI5NTli + ZGZjNDdjYWQwMGU4NDg5MGQwIiwgImlkIjogImIzZjE3M2U5LTY2NzUtNDkxZC04MmI3LTgzODZk + ZDIxMTAzNSIsICJyb2xlIjogIkNFTyIsICJ2ZXJib3NlPyI6IGZhbHNlLCAibWF4X2l0ZXIiOiAy + MCwgIm1heF9ycG0iOiBudWxsLCAiZnVuY3Rpb25fY2FsbGluZ19sbG0iOiAiIiwgImxsbSI6ICJn + cHQtNG8tbWluaSIsICJkZWxlZ2F0aW9uX2VuYWJsZWQ/IjogdHJ1ZSwgImFsbG93X2NvZGVfZXhl + Y3V0aW9uPyI6IGZhbHNlLCAibWF4X3JldHJ5X2xpbWl0IjogMiwgInRvb2xzX25hbWVzIjogW119 + LCB7ImtleSI6ICI5YTUwMTVlZjQ4OTVkYzYyNzhkNTQ4MThiYTQ0NmFmNyIsICJpZCI6ICI0MTEy + M2QzZC01NmEwLTRhNTgtYTljNi1mZjUwNjRmZjNmNTEiLCAicm9sZSI6ICJTZW5pb3IgV3JpdGVy + IiwgInZlcmJvc2U/IjogZmFsc2UsICJtYXhfaXRlciI6IDIwLCAibWF4X3JwbSI6IG51bGwsICJm + dW5jdGlvbl9jYWxsaW5nX2xsbSI6ICIiLCAibGxtIjogImdwdC00by1taW5pIiwgImRlbGVnYXRp + b25fZW5hYmxlZD8iOiBmYWxzZSwgImFsbG93X2NvZGVfZXhlY3V0aW9uPyI6IGZhbHNlLCAibWF4 + X3JldHJ5X2xpbWl0IjogMiwgInRvb2xzX25hbWVzIjogW119XUqDAgoKY3Jld190YXNrcxL0AQrx + AVt7ImtleSI6ICIwYjlkNjVkYjZiN2FlZGZiMzk4YzU5ZTJhOWY3MWVjNSIsICJpZCI6ICI5YTBj + ODZhZi0wYTE0LTQ4MzgtOTJmZC02NDhhZGM1NzJlMDMiLCAiYXN5bmNfZXhlY3V0aW9uPyI6IGZh + bHNlLCAiaHVtYW5faW5wdXQ/IjogZmFsc2UsICJhZ2VudF9yb2xlIjogIkNFTyIsICJhZ2VudF9r + ZXkiOiAiMzI4MjE3YjZjMjk1OWJkZmM0N2NhZDAwZTg0ODkwZDAiLCAidG9vbHNfbmFtZXMiOiBb + InRlc3QgdG9vbCJdfV16AhgBhQEAAQAAEo4CChDl0EBv/8sdeV8eJ45EUBpxEgj+C7UlokySqSoM + VGFzayBDcmVhdGVkMAE5oI8jxRmnExhBYO0jxRmnExhKLgoIY3Jld19rZXkSIgogZTY0OTU3M2Ey + NmU1ODc5MGNhYzIxYTM3Y2Q0NDQzN2FKMQoHY3Jld19pZBImCiRkODI4YWVjNi04NjdjLTQ3YWEt + ODg2OC1kMDFmMDRjNDBhNDFKLgoIdGFza19rZXkSIgogMGI5ZDY1ZGI2YjdhZWRmYjM5OGM1OWUy + YTlmNzFlYzVKMQoHdGFza19pZBImCiQ5YTBjODZhZi0wYTE0LTQ4MzgtOTJmZC02NDhhZGM1NzJl + MDN6AhgBhQEAAQAAEpsBChArkcRTKJCaWLUYbx8DLyvTEgikYuS5tmbKNioKVG9vbCBVc2FnZTAB + OSh+MscZpxMYQdgTOMcZpxMYShoKDmNyZXdhaV92ZXJzaW9uEggKBjAuODYuMEonCgl0b29sX25h + bWUSGgoYQXNrIHF1ZXN0aW9uIHRvIGNvd29ya2VySg4KCGF0dGVtcHRzEgIYAXoCGAGFAQABAAAS + 6wkKEHxFJsjiUgQromzfQHpYYMISCBkGairjk9kkKgxDcmV3IENyZWF0ZWQwATk4/rXHGacTGEGY + yrvHGacTGEoaCg5jcmV3YWlfdmVyc2lvbhIICgYwLjg2LjBKGgoOcHl0aG9uX3ZlcnNpb24SCAoG + My4xMS43Si4KCGNyZXdfa2V5EiIKIGU2NDk1NzNhMjZlNTg3OTBjYWMyMWEzN2NkNDQ0MzdhSjEK + B2NyZXdfaWQSJgokMjY3NzEyNzItOTRlZC00NDVkLTg1MGEtYTkyYTZjOWI5YmJkShwKDGNyZXdf + cHJvY2VzcxIMCgpzZXF1ZW50aWFsShEKC2NyZXdfbWVtb3J5EgIQAEoaChRjcmV3X251bWJlcl9v + Zl90YXNrcxICGAFKGwoVY3Jld19udW1iZXJfb2ZfYWdlbnRzEgIYAkqVBQoLY3Jld19hZ2VudHMS + hQUKggVbeyJrZXkiOiAiMzI4MjE3YjZjMjk1OWJkZmM0N2NhZDAwZTg0ODkwZDAiLCAiaWQiOiAi + YjNmMTczZTktNjY3NS00OTFkLTgyYjctODM4NmRkMjExMDM1IiwgInJvbGUiOiAiQ0VPIiwgInZl + cmJvc2U/IjogZmFsc2UsICJtYXhfaXRlciI6IDIwLCAibWF4X3JwbSI6IG51bGwsICJmdW5jdGlv + bl9jYWxsaW5nX2xsbSI6ICIiLCAibGxtIjogImdwdC00by1taW5pIiwgImRlbGVnYXRpb25fZW5h + YmxlZD8iOiB0cnVlLCAiYWxsb3dfY29kZV9leGVjdXRpb24/IjogZmFsc2UsICJtYXhfcmV0cnlf + bGltaXQiOiAyLCAidG9vbHNfbmFtZXMiOiBbInRlc3QgdG9vbCJdfSwgeyJrZXkiOiAiOWE1MDE1 + ZWY0ODk1ZGM2Mjc4ZDU0ODE4YmE0NDZhZjciLCAiaWQiOiAiNDExMjNkM2QtNTZhMC00YTU4LWE5 + YzYtZmY1MDY0ZmYzZjUxIiwgInJvbGUiOiAiU2VuaW9yIFdyaXRlciIsICJ2ZXJib3NlPyI6IGZh + bHNlLCAibWF4X2l0ZXIiOiAyMCwgIm1heF9ycG0iOiBudWxsLCAiZnVuY3Rpb25fY2FsbGluZ19s + bG0iOiAiIiwgImxsbSI6ICJncHQtNG8tbWluaSIsICJkZWxlZ2F0aW9uX2VuYWJsZWQ/IjogZmFs + c2UsICJhbGxvd19jb2RlX2V4ZWN1dGlvbj8iOiBmYWxzZSwgIm1heF9yZXRyeV9saW1pdCI6IDIs + ICJ0b29sc19uYW1lcyI6IFtdfV1KgwIKCmNyZXdfdGFza3MS9AEK8QFbeyJrZXkiOiAiMGI5ZDY1 + ZGI2YjdhZWRmYjM5OGM1OWUyYTlmNzFlYzUiLCAiaWQiOiAiNjYzOTEwZjYtNTlkYS00NjE3LTli + ZTMtNTBmMDdhNmQ5N2U3IiwgImFzeW5jX2V4ZWN1dGlvbj8iOiBmYWxzZSwgImh1bWFuX2lucHV0 + PyI6IGZhbHNlLCAiYWdlbnRfcm9sZSI6ICJDRU8iLCAiYWdlbnRfa2V5IjogIjMyODIxN2I2YzI5 + NTliZGZjNDdjYWQwMGU4NDg5MGQwIiwgInRvb2xzX25hbWVzIjogWyJ0ZXN0IHRvb2wiXX1degIY + AYUBAAEAABKOAgoQ1qBlNY8Yu1muyMaMnchyJBII0vE2y9FMwz0qDFRhc2sgQ3JlYXRlZDABObDR + zscZpxMYQah5z8cZpxMYSi4KCGNyZXdfa2V5EiIKIGU2NDk1NzNhMjZlNTg3OTBjYWMyMWEzN2Nk + NDQ0MzdhSjEKB2NyZXdfaWQSJgokMjY3NzEyNzItOTRlZC00NDVkLTg1MGEtYTkyYTZjOWI5YmJk + Si4KCHRhc2tfa2V5EiIKIDBiOWQ2NWRiNmI3YWVkZmIzOThjNTllMmE5ZjcxZWM1SjEKB3Rhc2tf + aWQSJgokNjYzOTEwZjYtNTlkYS00NjE3LTliZTMtNTBmMDdhNmQ5N2U3egIYAYUBAAEAABKMAQoQ + a8ZDV3ZaBmcOZE5dJ87f1hII7iBRAQfEmdAqClRvb2wgVXNhZ2UwATmYcwjIGacTGEE4RxLIGacT + GEoaCg5jcmV3YWlfdmVyc2lvbhIICgYwLjg2LjBKGAoJdG9vbF9uYW1lEgsKCVRlc3QgVG9vbEoO + CghhdHRlbXB0cxICGAF6AhgBhQEAAQAAEowBChBqK4036ypaH1gZ3OIOE/0HEgiF8wTQDQGRlSoK + VG9vbCBVc2FnZTABOYBiSsgZpxMYQRCYUsgZpxMYShoKDmNyZXdhaV92ZXJzaW9uEggKBjAuODYu + MEoYCgl0b29sX25hbWUSCwoJVGVzdCBUb29sSg4KCGF0dGVtcHRzEgIYAXoCGAGFAQABAAASwQcK + EIWSiNjtKgeNQ6oIv8gjJ+MSCG8YnypCXfw1KgxDcmV3IENyZWF0ZWQwATnYUW/KGacTGEEoenTK + GacTGEoaCg5jcmV3YWlfdmVyc2lvbhIICgYwLjg2LjBKGgoOcHl0aG9uX3ZlcnNpb24SCAoGMy4x + MS43Si4KCGNyZXdfa2V5EiIKIDk4MjQ2MGVlMmRkMmNmMTJhNzEzOGI3MDg1OWZlODE3SjEKB2Ny + ZXdfaWQSJgokZDNkODZjNmEtNWNmMi00MGI0LWExZGQtMzA5NTYyODdjNWE3ShwKDGNyZXdfcHJv + Y2VzcxIMCgpzZXF1ZW50aWFsShEKC2NyZXdfbWVtb3J5EgIQAEoaChRjcmV3X251bWJlcl9vZl90 + YXNrcxICGAFKGwoVY3Jld19udW1iZXJfb2ZfYWdlbnRzEgIYAUrcAgoLY3Jld19hZ2VudHMSzAIK + yQJbeyJrZXkiOiAiOGJkMjEzOWI1OTc1MTgxNTA2ZTQxZmQ5YzQ1NjNkNzUiLCAiaWQiOiAiNWNm + NDllYzctOTVmMy00ZGQ3LTg1NzItZjgwMDQwODYwYjI4IiwgInJvbGUiOiAiUmVzZWFyY2hlciIs + ICJ2ZXJib3NlPyI6IGZhbHNlLCAibWF4X2l0ZXIiOiAyMCwgIm1heF9ycG0iOiBudWxsLCAiZnVu + Y3Rpb25fY2FsbGluZ19sbG0iOiAiIiwgImxsbSI6ICJncHQtNG8tbWluaSIsICJkZWxlZ2F0aW9u + X2VuYWJsZWQ/IjogZmFsc2UsICJhbGxvd19jb2RlX2V4ZWN1dGlvbj8iOiBmYWxzZSwgIm1heF9y + ZXRyeV9saW1pdCI6IDIsICJ0b29sc19uYW1lcyI6IFsidGVzdCB0b29sIl19XUqSAgoKY3Jld190 + YXNrcxKDAgqAAlt7ImtleSI6ICJmODM5Yzg3YzNkNzU3Yzg4N2Y0Y2U3NGQxODY0YjAyYSIsICJp + ZCI6ICJjM2Y2NjY2MS00YWNjLTQ5OWQtYjJkNC1kZjI0Nzg1MTJhZGYiLCAiYXN5bmNfZXhlY3V0 + aW9uPyI6IGZhbHNlLCAiaHVtYW5faW5wdXQ/IjogZmFsc2UsICJhZ2VudF9yb2xlIjogIlJlc2Vh + cmNoZXIiLCAiYWdlbnRfa2V5IjogIjhiZDIxMzliNTk3NTE4MTUwNmU0MWZkOWM0NTYzZDc1Iiwg + InRvb2xzX25hbWVzIjogWyJhbm90aGVyIHRlc3QgdG9vbCJdfV16AhgBhQEAAQAAEo4CChD8dNvp + UItERukk59GnvESYEghtjirHyG3B3SoMVGFzayBDcmVhdGVkMAE5MAGByhmnExhBIFeByhmnExhK + LgoIY3Jld19rZXkSIgogOTgyNDYwZWUyZGQyY2YxMmE3MTM4YjcwODU5ZmU4MTdKMQoHY3Jld19p + ZBImCiRkM2Q4NmM2YS01Y2YyLTQwYjQtYTFkZC0zMDk1NjI4N2M1YTdKLgoIdGFza19rZXkSIgog + ZjgzOWM4N2MzZDc1N2M4ODdmNGNlNzRkMTg2NGIwMmFKMQoHdGFza19pZBImCiRjM2Y2NjY2MS00 + YWNjLTQ5OWQtYjJkNC1kZjI0Nzg1MTJhZGZ6AhgBhQEAAQAAEowBChDdoNfQMW/Om7LQU9gZGDrl + Egjw71DM3bnOWCoKVG9vbCBVc2FnZTABOUgPFC8apxMYQdhtKi8apxMYShoKDmNyZXdhaV92ZXJz + aW9uEggKBjAuODYuMEoYCgl0b29sX25hbWUSCwoJVGVzdCBUb29sSg4KCGF0dGVtcHRzEgIYAXoC + GAGFAQABAAA= + headers: + Accept: + - '*/*' + Accept-Encoding: + - gzip, deflate + Connection: + - keep-alive + Content-Length: + - '14771' + 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, 23 Dec 2024 00:33:37 GMT + status: + code: 200 + message: OK +- request: + body: '{"messages": [{"role": "system", "content": "You are Researcher. You''re + an expert researcher, specialized in technology, software engineering, AI and + startups. You work as a freelancer and is now working on doing research and + analysis for a new customer.\nYour personal goal is: Make the best research + and analysis on content about AI and AI agents\nYou ONLY have access to the + following tools, and should NEVER make up tools that are not listed here:\n\nTool + Name: Test Tool\nTool Arguments: {''query'': {''description'': ''Query to process'', + ''type'': ''str''}}\nTool Description: A test tool that just returns the input\n\nUse + the following format:\n\nThought: you should always think about what to do\nAction: + the action to take, only one name of [Test Tool], 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"}, {"role": "user", "content": "\nCurrent Task: Write a test + task\n\nThis is the expect criteria for your final answer: Test output\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:"}, {"role": "assistant", "content": + "I need to come up with a suitable test task that meets the criteria provided. + I will focus on outlining a clear and effective test task related to AI and + AI agents.\n\nAction: Test Tool\nAction Input: {\"query\": \"Create a test task + that involves evaluating the performance of an AI agent in a given scenario, + including criteria for success, tools required, and process for assessment.\"}\nObservation: + Processed: Create a test task that involves evaluating the performance of an + AI agent in a given scenario, including criteria for success, tools required, + and process for assessment."}], "model": "gpt-4o-mini", "stop": ["\nObservation:"], + "stream": false}' + headers: + accept: + - application/json + accept-encoding: + - gzip, deflate + connection: + - keep-alive + content-length: + - '2160' + content-type: + - application/json + cookie: + - _cfuvid=2u_Xw.i716TDjD2vb2mvMyWxhA4q1MM1JvbrA8CNZpI-1734895557894-0.0.1.1-604800000; + __cf_bm=i6jvNjhsDne300GPAeEmyiJJKYqy7OPuamFG_kht3KE-1734914012-1.0.1.1-tCeVANAF521vkXpBdgYw.ov.fYUr6t5QC4LG_DugWyzu4C60Pi2CruTVniUgfCvkcu6rdHA5DwnaEZf2jFaRCQ + 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: + content: "{\n \"id\": \"chatcmpl-AhQg0Cx1FED7gOwMpJUD5cQxVzQBK\",\n \"object\": + \"chat.completion\",\n \"created\": 1734914012,\n \"model\": \"gpt-4o-mini-2024-07-18\",\n + \ \"choices\": [\n {\n \"index\": 0,\n \"message\": {\n \"role\": + \"assistant\",\n \"content\": \"Thought: I need to compile a complete + and detailed test task based on the processed input, ensuring it includes all + necessary components as per the expect criteria.\\n\\nFinal Answer: \\n### Test + Task: Evaluating the Performance of an AI Agent\\n\\n**Objective:** \\nTo evaluate + the performance of an AI agent in simulating conversation with users in a customer + service context.\\n\\n**Scenario:**\\nAn AI agent is tasked with handling customer + inquiries on an e-commerce platform. The agent must respond to questions about + product availability, order tracking, returns, and other common queries.\\n\\n**Criteria + for Success:**\\n1. **Accuracy:** The AI should provide correct information + in at least 90% of the interactions.\\n2. **Response Time:** The average response + time should be under 2 seconds.\\n3. **User Satisfaction:** Aim for a user satisfaction + score of 85% or higher based on follow-up surveys after interactions.\\n4. **Fallback + Rate:** The AI should not default to a human agent more than 10% of the time.\\n\\n**Tools + Required:**\\n- Chatbot development platform (e.g., Dialogflow, Rasa)\\n- Metrics + tracking software (to measure accuracy, response times, and user satisfaction)\\n- + Survey tool (e.g., Google Forms, SurveyMonkey) for feedback collection\\n\\n**Process + for Assessment:**\\n1. **Setup:** Deploy the AI agent on a testing environment + simulating real customer inquiries.\\n2. **Data Collection:** Run the test for + a predetermined period (e.g., one week) or until a set number of interactions + (e.g., 1000).\\n3. **Measurement:**\\n - Record the interactions and analyze + the accuracy of the AI's responses.\\n - Measure the average response time + for each interaction.\\n - Collect user satisfaction scores via surveys sent + after the interaction.\\n4. **Analysis:** Compile the data to see if the AI + met the success criteria. Identify strengths and weaknesses in the responses.\\n5. + **Review:** Share findings with the development team to strategize improvements.\\n\\nThis + detailed task will help assess the AI agent\u2019s capabilities and provide + insights for further enhancements.\",\n \"refusal\": null\n },\n + \ \"logprobs\": null,\n \"finish_reason\": \"stop\"\n }\n ],\n + \ \"usage\": {\n \"prompt_tokens\": 416,\n \"completion_tokens\": 422,\n + \ \"total_tokens\": 838,\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_d02d531b47\"\n}\n" + headers: + CF-Cache-Status: + - DYNAMIC + CF-RAY: + - 8f6442c2ba15a486-GRU + Connection: + - keep-alive + Content-Encoding: + - gzip + Content-Type: + - application/json + Date: + - Mon, 23 Dec 2024 00:33:39 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: + - '6734' + 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: + - '149999497' + x-ratelimit-reset-requests: + - 2ms + x-ratelimit-reset-tokens: + - 0s + x-request-id: + - req_7d8df8b840e279bd64280d161d854161 + http_version: HTTP/1.1 + status_code: 200 +version: 1 diff --git a/tests/crew_test.py b/tests/crew_test.py index caecf4524e..2003ddada1 100644 --- a/tests/crew_test.py +++ b/tests/crew_test.py @@ -332,21 +332,30 @@ def test_manager_agent_delegating_to_assigned_task_agent(): tasks=[task], ) - crew.kickoff() + mock_task_output = TaskOutput( + description="Mock description", + raw="mocked output", + agent="mocked agent" + ) - # Check if the manager agent has the correct tools - assert crew.manager_agent is not None - assert crew.manager_agent.tools is not None + # Because we are mocking execute_sync, we never hit the underlying _execute_core + # which sets the output attribute of the task + task.output = mock_task_output - assert len(crew.manager_agent.tools) == 2 - assert ( - "Delegate a specific task to one of the following coworkers: Researcher\n" - in crew.manager_agent.tools[0].description - ) - assert ( - "Ask a specific question to one of the following coworkers: Researcher\n" - in crew.manager_agent.tools[1].description - ) + with patch.object(Task, 'execute_sync', return_value=mock_task_output) as mock_execute_sync: + crew.kickoff() + + # Verify execute_sync was called once + mock_execute_sync.assert_called_once() + + # Get the tools argument from the call + _, kwargs = mock_execute_sync.call_args + tools = kwargs['tools'] + + # Verify the delegation tools were passed correctly + assert len(tools) == 2 + assert any("Delegate a specific task to one of the following coworkers: Researcher" in tool.description for tool in tools) + assert any("Ask a specific question to one of the following coworkers: Researcher" in tool.description for tool in tools) @pytest.mark.vcr(filter_headers=["authorization"]) @@ -402,9 +411,255 @@ def test_crew_with_delegating_agents(): assert ( result.raw - == "This is the complete content as specified:\nArtificial Intelligence (AI) Agents are sophisticated computer programs designed to perform tasks that typically require human intelligence, such as decision making, problem-solving, and learning. These agents operate autonomously, utilizing vast amounts of data, advanced algorithms, and machine learning techniques to analyze their environment, adapt to new information, and improve their performance over time.\n\nThe significance of AI Agents lies in their transformative potential across various industries. In healthcare, for example, they assist in diagnosing diseases with greater accuracy and speed than human practitioners, offering personalized treatment plans by analyzing patient data. In finance, AI Agents predict market trends, manage risks, and even execute trades, contributing to more stable and profitable financial systems. Customer service sectors benefit significantly from AI Agents, as they provide personalized and efficient responses, often resolving issues faster than traditional methods.\n\nMoreover, AI Agents are also making substantial contributions in fields like education and manufacturing. In education, they offer tailored learning experiences by assessing individual student needs and adjusting teaching methods accordingly. They help educators identify students who might need additional support and provide resources to enhance learning outcomes. In manufacturing, AI Agents optimize production lines, predict equipment failures, and improve supply chain management, thus boosting productivity and reducing downtime.\n\nAs these AI-powered entities continue to evolve, they are not only enhancing operational efficiencies but also driving innovation and creating new opportunities for growth and development in every sector they penetrate. The future of AI Agents looks promising, with the potential to revolutionize the way we live and work, making processes more efficient, decisions more data-driven, and solutions more innovative than ever before." + == "In the rapidly evolving landscape of technology, AI agents have emerged as formidable tools, revolutionizing how we interact with data and automate tasks. These sophisticated systems leverage machine learning and natural language processing to perform a myriad of functions, from virtual personal assistants to complex decision-making companions in industries such as finance, healthcare, and education. By mimicking human intelligence, AI agents can analyze massive data sets at unparalleled speeds, enabling businesses to uncover valuable insights, enhance productivity, and elevate user experiences to unprecedented levels.\n\nOne of the most striking aspects of AI agents is their adaptability; they learn from their interactions and continuously improve their performance over time. This feature is particularly valuable in customer service where AI agents can address inquiries, resolve issues, and provide personalized recommendations without the limitations of human fatigue. Moreover, with intuitive interfaces, AI agents enhance user interactions, making technology more accessible and user-friendly, thereby breaking down barriers that have historically hindered digital engagement.\n\nDespite their immense potential, the deployment of AI agents raises important ethical and practical considerations. Issues related to privacy, data security, and the potential for job displacement necessitate thoughtful dialogue and proactive measures. Striking a balance between technological innovation and societal impact will be crucial as organizations integrate these agents into their operations. Additionally, ensuring transparency in AI decision-making processes is vital to maintain public trust as AI agents become an integral part of daily life.\n\nLooking ahead, the future of AI agents appears bright, with ongoing advancements promising even greater capabilities. As we continue to harness the power of AI, we can expect these agents to play a transformative role in shaping various sectors—streamlining workflows, enabling smarter decision-making, and fostering more personalized experiences. Embracing this technology responsibly can lead to a future where AI agents not only augment human effort but also inspire creativity and efficiency across the board, ultimately redefining our interaction with the digital world." + ) + +@pytest.mark.vcr(filter_headers=["authorization"]) +def test_crew_with_delegating_agents_should_not_override_task_tools(): + from typing import Type + + from pydantic import BaseModel, Field + + from crewai.tools import BaseTool + + class TestToolInput(BaseModel): + """Input schema for TestTool.""" + query: str = Field(..., description="Query to process") + + class TestTool(BaseTool): + name: str = "Test Tool" + description: str = "A test tool that just returns the input" + args_schema: Type[BaseModel] = TestToolInput + + def _run(self, query: str) -> str: + return f"Processed: {query}" + + # Create a task with the test tool + tasks = [ + Task( + description="Produce and amazing 1 paragraph draft of an article about AI Agents.", + expected_output="A 4 paragraph article about AI.", + agent=ceo, + tools=[TestTool()], + ) + ] + + crew = Crew( + agents=[ceo, writer], + process=Process.sequential, + tasks=tasks, + ) + + mock_task_output = TaskOutput( + description="Mock description", + raw="mocked output", + agent="mocked agent" + ) + + # Because we are mocking execute_sync, we never hit the underlying _execute_core + # which sets the output attribute of the task + tasks[0].output = mock_task_output + + with patch.object(Task, 'execute_sync', return_value=mock_task_output) as mock_execute_sync: + crew.kickoff() + + # Execute the task and verify both tools are present + _, kwargs = mock_execute_sync.call_args + tools = kwargs['tools'] + + assert any(isinstance(tool, TestTool) for tool in tools), "TestTool should be present" + assert any("delegate" in tool.name.lower() for tool in tools), "Delegation tool should be present" + +@pytest.mark.vcr(filter_headers=["authorization"]) +def test_crew_with_delegating_agents_should_not_override_agent_tools(): + from typing import Type + + from pydantic import BaseModel, Field + + from crewai.tools import BaseTool + + class TestToolInput(BaseModel): + """Input schema for TestTool.""" + query: str = Field(..., description="Query to process") + + class TestTool(BaseTool): + name: str = "Test Tool" + description: str = "A test tool that just returns the input" + args_schema: Type[BaseModel] = TestToolInput + + def _run(self, query: str) -> str: + return f"Processed: {query}" + + new_ceo = ceo.model_copy() + new_ceo.tools = [TestTool()] + + # Create a task with the test tool + tasks = [ + Task( + description="Produce and amazing 1 paragraph draft of an article about AI Agents.", + expected_output="A 4 paragraph article about AI.", + agent=new_ceo + ) + ] + + crew = Crew( + agents=[new_ceo, writer], + process=Process.sequential, + tasks=tasks, + ) + + mock_task_output = TaskOutput( + description="Mock description", + raw="mocked output", + agent="mocked agent" + ) + + # Because we are mocking execute_sync, we never hit the underlying _execute_core + # which sets the output attribute of the task + tasks[0].output = mock_task_output + + with patch.object(Task, 'execute_sync', return_value=mock_task_output) as mock_execute_sync: + crew.kickoff() + + # Execute the task and verify both tools are present + _, kwargs = mock_execute_sync.call_args + tools = kwargs['tools'] + + assert any(isinstance(tool, TestTool) for tool in new_ceo.tools), "TestTool should be present" + assert any("delegate" in tool.name.lower() for tool in tools), "Delegation tool should be present" + +@pytest.mark.vcr(filter_headers=["authorization"]) +def test_task_tools_override_agent_tools(): + from typing import Type + + from pydantic import BaseModel, Field + + from crewai.tools import BaseTool + + class TestToolInput(BaseModel): + """Input schema for TestTool.""" + query: str = Field(..., description="Query to process") + + class TestTool(BaseTool): + name: str = "Test Tool" + description: str = "A test tool that just returns the input" + args_schema: Type[BaseModel] = TestToolInput + + def _run(self, query: str) -> str: + return f"Processed: {query}" + + class AnotherTestTool(BaseTool): + name: str = "Another Test Tool" + description: str = "Another test tool" + args_schema: Type[BaseModel] = TestToolInput + + def _run(self, query: str) -> str: + return f"Another processed: {query}" + + # Set agent tools + new_researcher = researcher.model_copy() + new_researcher.tools = [TestTool()] + + # Create task with different tools + task = Task( + description="Write a test task", + expected_output="Test output", + agent=new_researcher, + tools=[AnotherTestTool()] ) + crew = Crew( + agents=[new_researcher], + tasks=[task], + process=Process.sequential + ) + + crew.kickoff() + + # Verify task tools override agent tools + assert len(task.tools) == 1 # AnotherTestTool + assert any(isinstance(tool, AnotherTestTool) for tool in task.tools) + assert not any(isinstance(tool, TestTool) for tool in task.tools) + + # Verify agent tools remain unchanged + assert len(new_researcher.tools) == 1 + assert isinstance(new_researcher.tools[0], TestTool) + +@pytest.mark.vcr(filter_headers=["authorization"]) +def test_task_tools_override_agent_tools_with_allow_delegation(): + """ + Test that task tools override agent tools while preserving delegation tools when allow_delegation=True + """ + from typing import Type + + from pydantic import BaseModel, Field + + from crewai.tools import BaseTool + + class TestToolInput(BaseModel): + query: str = Field(..., description="Query to process") + + class TestTool(BaseTool): + name: str = "Test Tool" + description: str = "A test tool that just returns the input" + args_schema: Type[BaseModel] = TestToolInput + + def _run(self, query: str) -> str: + return f"Processed: {query}" + + class AnotherTestTool(BaseTool): + name: str = "Another Test Tool" + description: str = "Another test tool" + args_schema: Type[BaseModel] = TestToolInput + + def _run(self, query: str) -> str: + return f"Another processed: {query}" + + # Set up agents with tools and allow_delegation + researcher_with_delegation = researcher.model_copy() + researcher_with_delegation.allow_delegation = True + researcher_with_delegation.tools = [TestTool()] + + writer_for_delegation = writer.model_copy() + + # Create a task with different tools + task = Task( + description="Write a test task", + expected_output="Test output", + agent=researcher_with_delegation, + tools=[AnotherTestTool()], + ) + + crew = Crew( + agents=[researcher_with_delegation, writer_for_delegation], + tasks=[task], + process=Process.sequential, + ) + + mock_task_output = TaskOutput( + description="Mock description", + raw="mocked output", + agent="mocked agent" + ) + + # We mock execute_sync to verify which tools get used at runtime + with patch.object(Task, "execute_sync", return_value=mock_task_output) as mock_execute_sync: + crew.kickoff() + + # Inspect the call kwargs to verify the actual tools passed to execution + _, kwargs = mock_execute_sync.call_args + used_tools = kwargs["tools"] + + # Confirm AnotherTestTool is present but TestTool is not + assert any(isinstance(tool, AnotherTestTool) for tool in used_tools), "AnotherTestTool should be present" + assert not any(isinstance(tool, TestTool) for tool in used_tools), "TestTool should not be present among used tools" + + # Confirm delegation tool(s) are present + assert any("delegate" in tool.name.lower() for tool in used_tools), "Delegation tool should be present" + + # Finally, make sure the agent's original tools remain unchanged + assert len(researcher_with_delegation.tools) == 1 + assert isinstance(researcher_with_delegation.tools[0], TestTool) @pytest.mark.vcr(filter_headers=["authorization"]) def test_crew_verbose_output(capsys): @@ -1193,12 +1448,22 @@ def test_code_execution_flag_adds_code_tool_upon_kickoff(): crew = Crew(agents=[programmer], tasks=[task]) - with patch.object(Agent, "execute_task") as executor: - executor.return_value = "ok" + mock_task_output = TaskOutput( + description="Mock description", + raw="mocked output", + agent="mocked agent" + ) + + with patch.object(Task, "execute_sync", return_value=mock_task_output) as mock_execute_sync: crew.kickoff() - assert len(programmer.tools) == 1 - assert programmer.tools[0].__class__ == CodeInterpreterTool + # Get the tools that were actually used in execution + _, kwargs = mock_execute_sync.call_args + used_tools = kwargs["tools"] + + # Verify that exactly one tool was used and it was a CodeInterpreterTool + assert len(used_tools) == 1, "Should have exactly one tool" + assert isinstance(used_tools[0], CodeInterpreterTool), "Tool should be CodeInterpreterTool" @pytest.mark.vcr(filter_headers=["authorization"]) def test_delegation_is_not_enabled_if_there_are_only_one_agent(): @@ -1307,21 +1572,37 @@ def test_hierarchical_crew_creation_tasks_with_agents(): process=Process.hierarchical, manager_llm="gpt-4o", ) - crew.kickoff() - assert crew.manager_agent is not None - assert crew.manager_agent.tools is not None - assert ( - "Delegate a specific task to one of the following coworkers: Senior Writer\n" - in crew.manager_agent.tools[0].description + mock_task_output = TaskOutput( + description="Mock description", + raw="mocked output", + agent="mocked agent" ) + # Because we are mocking execute_sync, we never hit the underlying _execute_core + # which sets the output attribute of the task + task.output = mock_task_output + + with patch.object(Task, 'execute_sync', return_value=mock_task_output) as mock_execute_sync: + crew.kickoff() + + # Verify execute_sync was called once + mock_execute_sync.assert_called_once() + + # Get the tools argument from the call + _, kwargs = mock_execute_sync.call_args + tools = kwargs['tools'] + + # Verify the delegation tools were passed correctly + assert len(tools) == 2 + assert any("Delegate a specific task to one of the following coworkers: Senior Writer" in tool.description for tool in tools) + assert any("Ask a specific question to one of the following coworkers: Senior Writer" in tool.description for tool in tools) + @pytest.mark.vcr(filter_headers=["authorization"]) def test_hierarchical_crew_creation_tasks_with_async_execution(): """ - Agents are not required for tasks in a hierarchical process but sometimes they are still added - This test makes sure that the manager still delegates the task to the agent even if the agent is passed in the task + Tests that async tasks in hierarchical crews are handled correctly with proper delegation tools """ task = Task( description="Write one amazing paragraph about AI.", @@ -1337,14 +1618,35 @@ def test_hierarchical_crew_creation_tasks_with_async_execution(): manager_llm="gpt-4o", ) - crew.kickoff() - assert crew.manager_agent is not None - assert crew.manager_agent.tools is not None - assert ( - "Delegate a specific task to one of the following coworkers: Senior Writer\n" - in crew.manager_agent.tools[0].description + mock_task_output = TaskOutput( + description="Mock description", + raw="mocked output", + agent="mocked agent" ) + # Create a mock Future that returns our TaskOutput + mock_future = MagicMock(spec=Future) + mock_future.result.return_value = mock_task_output + + # Because we are mocking execute_async, we never hit the underlying _execute_core + # which sets the output attribute of the task + task.output = mock_task_output + + with patch.object(Task, 'execute_async', return_value=mock_future) as mock_execute_async: + crew.kickoff() + + # Verify execute_async was called once + mock_execute_async.assert_called_once() + + # Get the tools argument from the call + _, kwargs = mock_execute_async.call_args + tools = kwargs['tools'] + + # Verify the delegation tools were passed correctly + assert len(tools) == 2 + assert any("Delegate a specific task to one of the following coworkers: Senior Writer\n" in tool.description for tool in tools) + assert any("Ask a specific question to one of the following coworkers: Senior Writer\n" in tool.description for tool in tools) + @pytest.mark.vcr(filter_headers=["authorization"]) def test_hierarchical_crew_creation_tasks_with_sync_last(): @@ -2583,3 +2885,244 @@ def test_hierarchical_verbose_false_manager_agent(): assert crew.manager_agent is not None assert not crew.manager_agent.verbose + + +def test_task_tools_preserve_code_execution_tools(): + """ + Test that task tools don't override code execution tools when allow_code_execution=True + """ + from typing import Type + + from crewai_tools import CodeInterpreterTool + from pydantic import BaseModel, Field + + from crewai.tools import BaseTool + + class TestToolInput(BaseModel): + """Input schema for TestTool.""" + query: str = Field(..., description="Query to process") + + class TestTool(BaseTool): + name: str = "Test Tool" + description: str = "A test tool that just returns the input" + args_schema: Type[BaseModel] = TestToolInput + + def _run(self, query: str) -> str: + return f"Processed: {query}" + + # Create a programmer agent with code execution enabled + programmer = Agent( + role="Programmer", + goal="Write code to solve problems.", + backstory="You're a programmer who loves to solve problems with code.", + allow_delegation=True, + allow_code_execution=True, + ) + + # Create a code reviewer agent + reviewer = Agent( + role="Code Reviewer", + goal="Review code for bugs and improvements", + backstory="You're an experienced code reviewer who ensures code quality and best practices.", + allow_delegation=True, + allow_code_execution=True, + ) + + # Create a task with its own tools + task = Task( + description="Write a program to calculate fibonacci numbers.", + expected_output="A working fibonacci calculator.", + agent=programmer, + tools=[TestTool()] + ) + + crew = Crew( + agents=[programmer, reviewer], + tasks=[task], + process=Process.sequential, + ) + + mock_task_output = TaskOutput( + description="Mock description", + raw="mocked output", + agent="mocked agent" + ) + + with patch.object(Task, "execute_sync", return_value=mock_task_output) as mock_execute_sync: + crew.kickoff() + + # Get the tools that were actually used in execution + _, kwargs = mock_execute_sync.call_args + used_tools = kwargs["tools"] + + # Verify all expected tools are present + assert any(isinstance(tool, TestTool) for tool in used_tools), "Task's TestTool should be present" + assert any(isinstance(tool, CodeInterpreterTool) for tool in used_tools), "CodeInterpreterTool should be present" + assert any("delegate" in tool.name.lower() for tool in used_tools), "Delegation tool should be present" + + # Verify the total number of tools (TestTool + CodeInterpreter + 2 delegation tools) + assert len(used_tools) == 4, "Should have TestTool, CodeInterpreter, and 2 delegation tools" + +@pytest.mark.vcr(filter_headers=["authorization"]) +def test_multimodal_flag_adds_multimodal_tools(): + """ + Test that an agent with multimodal=True automatically has multimodal tools added to the task execution. + """ + from crewai.tools.agent_tools.add_image_tool import AddImageTool + + # Create an agent that supports multimodal + multimodal_agent = Agent( + role="Multimodal Analyst", + goal="Handle multiple media types (text, images, etc.).", + backstory="You're an agent specialized in analyzing text, images, and other media.", + allow_delegation=False, + multimodal=True, # crucial for adding the multimodal tool + ) + + # Create a dummy task + task = Task( + description="Describe what's in this image and generate relevant metadata.", + expected_output="An image description plus any relevant metadata.", + agent=multimodal_agent, + ) + + # Define a crew with the multimodal agent + crew = Crew(agents=[multimodal_agent], tasks=[task], process=Process.sequential) + + mock_task_output = TaskOutput( + description="Mock description", + raw="mocked output", + agent="mocked agent" + ) + + # Mock execute_sync to verify the tools passed at runtime + with patch.object(Task, "execute_sync", return_value=mock_task_output) as mock_execute_sync: + crew.kickoff() + + # Get the tools that were actually used in execution + _, kwargs = mock_execute_sync.call_args + used_tools = kwargs["tools"] + + # Check that the multimodal tool was added + assert any(isinstance(tool, AddImageTool) for tool in used_tools), ( + "AddImageTool should be present when agent is multimodal" + ) + + # Verify we have exactly one tool (just the AddImageTool) + assert len(used_tools) == 1, "Should only have the AddImageTool" + +@pytest.mark.vcr(filter_headers=["authorization"]) +def test_multimodal_agent_image_tool_handling(): + """ + Test that multimodal agents properly handle image tools in the CrewAgentExecutor + """ + # Create a multimodal agent + multimodal_agent = Agent( + role="Image Analyst", + goal="Analyze images and provide descriptions", + backstory="You're an expert at analyzing and describing images.", + allow_delegation=False, + multimodal=True, + ) + + # Create a task that involves image analysis + task = Task( + description="Analyze this image and describe what you see.", + expected_output="A detailed description of the image.", + agent=multimodal_agent, + ) + + crew = Crew(agents=[multimodal_agent], tasks=[task]) + + # Mock the image tool response + mock_image_tool_result = { + "role": "user", + "content": [ + {"type": "text", "text": "Please analyze this image"}, + { + "type": "image_url", + "image_url": { + "url": "https://example.com/test-image.jpg", + }, + }, + ], + } + + # Create a mock task output for the final result + mock_task_output = TaskOutput( + description="Mock description", + raw="A detailed analysis of the image", + agent="Image Analyst" + ) + + with patch.object(Task, 'execute_sync') as mock_execute_sync: + # Set up the mock to return our task output + mock_execute_sync.return_value = mock_task_output + + # Execute the crew + crew.kickoff() + + # Get the tools that were passed to execute_sync + _, kwargs = mock_execute_sync.call_args + tools = kwargs['tools'] + + # Verify the AddImageTool is present and properly configured + image_tools = [tool for tool in tools if tool.name == "Add image to content"] + assert len(image_tools) == 1, "Should have exactly one AddImageTool" + + # Test the tool's execution + image_tool = image_tools[0] + result = image_tool._run( + image_url="https://example.com/test-image.jpg", + action="Please analyze this image" + ) + + # Verify the tool returns the expected format + assert result == mock_image_tool_result + assert result["role"] == "user" + assert len(result["content"]) == 2 + assert result["content"][0]["type"] == "text" + assert result["content"][1]["type"] == "image_url" + +@pytest.mark.vcr(filter_headers=["authorization"]) +def test_multimodal_agent_live_image_analysis(): + """ + Test that multimodal agents can analyze images through a real API call + """ + # Create a multimodal agent + image_analyst = Agent( + role="Image Analyst", + goal="Analyze images with high attention to detail", + backstory="You're an expert at visual analysis, trained to notice and describe details in images.", + allow_delegation=False, + multimodal=True, + verbose=True, + llm="gpt-4o" + ) + + # Create a task for image analysis + analyze_image = Task( + description=""" + Analyze the provided image and describe what you see in detail. + Focus on main elements, colors, composition, and any notable details. + Image: {image_url} + """, + expected_output="A comprehensive description of the image contents.", + agent=image_analyst + ) + + # Create and run the crew + crew = Crew( + agents=[image_analyst], + tasks=[analyze_image] + ) + + # Execute with an image URL + result = crew.kickoff(inputs={ + "image_url": "https://media.istockphoto.com/id/946087016/photo/aerial-view-of-lower-manhattan-new-york.jpg?s=612x612&w=0&k=20&c=viLiMRznQ8v5LzKTt_LvtfPFUVl1oiyiemVdSlm29_k=" + }) + + # Verify we got a meaningful response + assert isinstance(result.raw, str) + assert len(result.raw) > 100 # Expecting a detailed analysis + assert "error" not in result.raw.lower() # No error messages in response \ No newline at end of file From 4cf8913d31e061e7bdfea7831773a3e63a04ef44 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Igor?= Date: Fri, 27 Dec 2024 17:45:06 -0300 Subject: [PATCH 14/18] chore: removing crewai-tools from dev-dependencies (#1760) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit As mentioned in issue #1759, listing crewai-tools as dev-dependencies makes pip install it a required dependency, and not an optional Co-authored-by: João Moura --- pyproject.toml | 1 - 1 file changed, 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index f9533a6f92..3f10c1a875 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -67,7 +67,6 @@ dev-dependencies = [ "mkdocs-material-extensions>=1.3.1", "pillow>=10.2.0", "cairosvg>=2.7.1", - "crewai-tools>=0.17.0", "pytest>=8.0.0", "pytest-vcr>=1.0.2", "python-dotenv>=1.0.0", From 62f3df7ed5e43dca20c19e1b2c3e9492858b1dab Mon Sep 17 00:00:00 2001 From: "devin-ai-integration[bot]" <158243242+devin-ai-integration[bot]@users.noreply.github.com> Date: Fri, 27 Dec 2024 18:16:02 -0300 Subject: [PATCH 15/18] docs: add guide for multimodal agents (#1807) Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> Co-authored-by: Joe Moura --- docs/how-to/multimodal-agents.mdx | 138 ++++++++++++++++++++++++++++++ 1 file changed, 138 insertions(+) create mode 100644 docs/how-to/multimodal-agents.mdx diff --git a/docs/how-to/multimodal-agents.mdx b/docs/how-to/multimodal-agents.mdx new file mode 100644 index 0000000000..1dcf50d257 --- /dev/null +++ b/docs/how-to/multimodal-agents.mdx @@ -0,0 +1,138 @@ +--- +title: Using Multimodal Agents +description: Learn how to enable and use multimodal capabilities in your agents for processing images and other non-text content within the CrewAI framework. +icon: image +--- + +# Using Multimodal Agents + +CrewAI supports multimodal agents that can process both text and non-text content like images. This guide will show you how to enable and use multimodal capabilities in your agents. + +## Enabling Multimodal Capabilities + +To create a multimodal agent, simply set the `multimodal` parameter to `True` when initializing your agent: + +```python +from crewai import Agent + +agent = Agent( + role="Image Analyst", + goal="Analyze and extract insights from images", + backstory="An expert in visual content interpretation with years of experience in image analysis", + multimodal=True # This enables multimodal capabilities +) +``` + +When you set `multimodal=True`, the agent is automatically configured with the necessary tools for handling non-text content, including the `AddImageTool`. + +## Working with Images + +The multimodal agent comes pre-configured with the `AddImageTool`, which allows it to process images. You don't need to manually add this tool - it's automatically included when you enable multimodal capabilities. + +Here's a complete example showing how to use a multimodal agent to analyze an image: + +```python +from crewai import Agent, Task, Crew + +# Create a multimodal agent +image_analyst = Agent( + role="Product Analyst", + goal="Analyze product images and provide detailed descriptions", + backstory="Expert in visual product analysis with deep knowledge of design and features", + multimodal=True +) + +# Create a task for image analysis +task = Task( + description="Analyze the product image at https://example.com/product.jpg and provide a detailed description", + agent=image_analyst +) + +# Create and run the crew +crew = Crew( + agents=[image_analyst], + tasks=[task] +) + +result = crew.kickoff() +``` + +### Advanced Usage with Context + +You can provide additional context or specific questions about the image when creating tasks for multimodal agents. The task description can include specific aspects you want the agent to focus on: + +```python +from crewai import Agent, Task, Crew + +# Create a multimodal agent for detailed analysis +expert_analyst = Agent( + role="Visual Quality Inspector", + goal="Perform detailed quality analysis of product images", + backstory="Senior quality control expert with expertise in visual inspection", + multimodal=True # AddImageTool is automatically included +) + +# Create a task with specific analysis requirements +inspection_task = Task( + description=""" + Analyze the product image at https://example.com/product.jpg with focus on: + 1. Quality of materials + 2. Manufacturing defects + 3. Compliance with standards + Provide a detailed report highlighting any issues found. + """, + agent=expert_analyst +) + +# Create and run the crew +crew = Crew( + agents=[expert_analyst], + tasks=[inspection_task] +) + +result = crew.kickoff() +``` + +### Tool Details + +When working with multimodal agents, the `AddImageTool` is automatically configured with the following schema: + +```python +class AddImageToolSchema: + image_url: str # Required: The URL or path of the image to process + action: Optional[str] = None # Optional: Additional context or specific questions about the image +``` + +The multimodal agent will automatically handle the image processing through its built-in tools, allowing it to: +- Access images via URLs or local file paths +- Process image content with optional context or specific questions +- Provide analysis and insights based on the visual information and task requirements + +## Best Practices + +When working with multimodal agents, keep these best practices in mind: + +1. **Image Access** + - Ensure your images are accessible via URLs that the agent can reach + - For local images, consider hosting them temporarily or using absolute file paths + - Verify that image URLs are valid and accessible before running tasks + +2. **Task Description** + - Be specific about what aspects of the image you want the agent to analyze + - Include clear questions or requirements in the task description + - Consider using the optional `action` parameter for focused analysis + +3. **Resource Management** + - Image processing may require more computational resources than text-only tasks + - Some language models may require base64 encoding for image data + - Consider batch processing for multiple images to optimize performance + +4. **Environment Setup** + - Verify that your environment has the necessary dependencies for image processing + - Ensure your language model supports multimodal capabilities + - Test with small images first to validate your setup + +5. **Error Handling** + - Implement proper error handling for image loading failures + - Have fallback strategies for when image processing fails + - Monitor and log image processing operations for debugging From 409892d65fab88e4a390f05c46b399b5a9260463 Mon Sep 17 00:00:00 2001 From: siddharth Sambharia Date: Sat, 28 Dec 2024 02:46:47 +0530 Subject: [PATCH 16/18] Portkey Integration with CrewAI (#1233) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Create Portkey-Observability-and-Guardrails.md * crewAI update with new changes * small change --------- Co-authored-by: siddharthsambharia-portkey Co-authored-by: João Moura --- .../Portkey-Observability-and-Guardrails.md | 211 ++++++++++++++++++ 1 file changed, 211 insertions(+) create mode 100644 docs/how-to/Portkey-Observability-and-Guardrails.md diff --git a/docs/how-to/Portkey-Observability-and-Guardrails.md b/docs/how-to/Portkey-Observability-and-Guardrails.md new file mode 100644 index 0000000000..f4f7a696e1 --- /dev/null +++ b/docs/how-to/Portkey-Observability-and-Guardrails.md @@ -0,0 +1,211 @@ +# Portkey Integration with CrewAI +Portkey CrewAI Header Image + + +[Portkey](https://portkey.ai/?utm_source=crewai&utm_medium=crewai&utm_campaign=crewai) is a 2-line upgrade to make your CrewAI agents reliable, cost-efficient, and fast. + +Portkey adds 4 core production capabilities to any CrewAI agent: +1. Routing to **200+ LLMs** +2. Making each LLM call more robust +3. Full-stack tracing & cost, performance analytics +4. Real-time guardrails to enforce behavior + + + + + +## Getting Started + +1. **Install Required Packages:** + +```bash +pip install -qU crewai portkey-ai +``` + +2. **Configure the LLM Client:** + +To build CrewAI Agents with Portkey, you'll need two keys: +- **Portkey API Key**: Sign up on the [Portkey app](https://app.portkey.ai/?utm_source=crewai&utm_medium=crewai&utm_campaign=crewai) and copy your API key +- **Virtual Key**: Virtual Keys securely manage your LLM API keys in one place. Store your LLM provider API keys securely in Portkey's vault + +```python +from crewai import LLM +from portkey_ai import createHeaders, PORTKEY_GATEWAY_URL + +gpt_llm = LLM( + model="gpt-4", + base_url=PORTKEY_GATEWAY_URL, + api_key="dummy", # We are using Virtual key + extra_headers=createHeaders( + api_key="YOUR_PORTKEY_API_KEY", + virtual_key="YOUR_VIRTUAL_KEY", # Enter your Virtual key from Portkey + ) +) +``` + +3. **Create and Run Your First Agent:** + +```python +from crewai import Agent, Task, Crew + +# Define your agents with roles and goals +coder = Agent( + role='Software developer', + goal='Write clear, concise code on demand', + backstory='An expert coder with a keen eye for software trends.', + llm=gpt_llm +) + +# Create tasks for your agents +task1 = Task( + description="Define the HTML for making a simple website with heading- Hello World! Portkey is working!", + expected_output="A clear and concise HTML code", + agent=coder +) + +# Instantiate your crew +crew = Crew( + agents=[coder], + tasks=[task1], +) + +result = crew.kickoff() +print(result) +``` + + +## Key Features + +| Feature | Description | +|---------|-------------| +| 🌐 Multi-LLM Support | Access OpenAI, Anthropic, Gemini, Azure, and 250+ providers through a unified interface | +| 🛡️ Production Reliability | Implement retries, timeouts, load balancing, and fallbacks | +| 📊 Advanced Observability | Track 40+ metrics including costs, tokens, latency, and custom metadata | +| 🔍 Comprehensive Logging | Debug with detailed execution traces and function call logs | +| 🚧 Security Controls | Set budget limits and implement role-based access control | +| 🔄 Performance Analytics | Capture and analyze feedback for continuous improvement | +| 💾 Intelligent Caching | Reduce costs and latency with semantic or simple caching | + + +## Production Features with Portkey Configs + +All features mentioned below are through Portkey's Config system. Portkey's Config system allows you to define routing strategies using simple JSON objects in your LLM API calls. You can create and manage Configs directly in your code or through the Portkey Dashboard. Each Config has a unique ID for easy reference. + + + + + + +### 1. Use 250+ LLMs +Access various LLMs like Anthropic, Gemini, Mistral, Azure OpenAI, and more with minimal code changes. Switch between providers or use them together seamlessly. [Learn more about Universal API](https://portkey.ai/docs/product/ai-gateway/universal-api) + + +Easily switch between different LLM providers: + +```python +# Anthropic Configuration +anthropic_llm = LLM( + model="claude-3-5-sonnet-latest", + base_url=PORTKEY_GATEWAY_URL, + api_key="dummy", + extra_headers=createHeaders( + api_key="YOUR_PORTKEY_API_KEY", + virtual_key="YOUR_ANTHROPIC_VIRTUAL_KEY", #You don't need provider when using Virtual keys + trace_id="anthropic_agent" + ) +) + +# Azure OpenAI Configuration +azure_llm = LLM( + model="gpt-4", + base_url=PORTKEY_GATEWAY_URL, + api_key="dummy", + extra_headers=createHeaders( + api_key="YOUR_PORTKEY_API_KEY", + virtual_key="YOUR_AZURE_VIRTUAL_KEY", #You don't need provider when using Virtual keys + trace_id="azure_agent" + ) +) +``` + + +### 2. Caching +Improve response times and reduce costs with two powerful caching modes: +- **Simple Cache**: Perfect for exact matches +- **Semantic Cache**: Matches responses for requests that are semantically similar +[Learn more about Caching](https://portkey.ai/docs/product/ai-gateway/cache-simple-and-semantic) + +```py +config = { + "cache": { + "mode": "semantic", # or "simple" for exact matching + } +} +``` + +### 3. Production Reliability +Portkey provides comprehensive reliability features: +- **Automatic Retries**: Handle temporary failures gracefully +- **Request Timeouts**: Prevent hanging operations +- **Conditional Routing**: Route requests based on specific conditions +- **Fallbacks**: Set up automatic provider failovers +- **Load Balancing**: Distribute requests efficiently + +[Learn more about Reliability Features](https://portkey.ai/docs/product/ai-gateway/) + + + +### 4. Metrics + +Agent runs are complex. Portkey automatically logs **40+ comprehensive metrics** for your AI agents, including cost, tokens used, latency, etc. Whether you need a broad overview or granular insights into your agent runs, Portkey's customizable filters provide the metrics you need. + + +- Cost per agent interaction +- Response times and latency +- Token usage and efficiency +- Success/failure rates +- Cache hit rates + +Portkey Dashboard + +### 5. Detailed Logging +Logs are essential for understanding agent behavior, diagnosing issues, and improving performance. They provide a detailed record of agent activities and tool use, which is crucial for debugging and optimizing processes. + + +Access a dedicated section to view records of agent executions, including parameters, outcomes, function calls, and errors. Filter logs based on multiple parameters such as trace ID, model, tokens used, and metadata. + +
+ Traces + Portkey Traces +
+ +
+ Logs + Portkey Logs +
+ +### 6. Enterprise Security Features +- Set budget limit and rate limts per Virtual Key (disposable API keys) +- Implement role-based access control +- Track system changes with audit logs +- Configure data retention policies + + + +For detailed information on creating and managing Configs, visit the [Portkey documentation](https://docs.portkey.ai/product/ai-gateway/configs). + +## Resources + +- [📘 Portkey Documentation](https://docs.portkey.ai) +- [📊 Portkey Dashboard](https://app.portkey.ai/?utm_source=crewai&utm_medium=crewai&utm_campaign=crewai) +- [🐦 Twitter](https://twitter.com/portkeyai) +- [💬 Discord Community](https://discord.gg/DD7vgKK299) + + + + + + + + + From 97fc44c9301f63b9756a8622cec23804e8f85491 Mon Sep 17 00:00:00 2001 From: Erick Amorim <73451993+ericklima-ca@users.noreply.github.com> Date: Fri, 27 Dec 2024 20:18:25 -0400 Subject: [PATCH 17/18] fix: Change storage initialization to None for KnowledgeStorage (#1804) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * fix: Change storage initialization to None for KnowledgeStorage * refactor: Change storage field to optional and improve error handling when saving documents --------- Co-authored-by: João Moura --- src/crewai/knowledge/knowledge.py | 4 ++-- src/crewai/knowledge/source/base_file_knowledge_source.py | 7 +++++-- src/crewai/knowledge/source/base_knowledge_source.py | 7 +++++-- 3 files changed, 12 insertions(+), 6 deletions(-) diff --git a/src/crewai/knowledge/knowledge.py b/src/crewai/knowledge/knowledge.py index f9f55a5173..571542994a 100644 --- a/src/crewai/knowledge/knowledge.py +++ b/src/crewai/knowledge/knowledge.py @@ -14,13 +14,13 @@ 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) + storage: Optional[KnowledgeStorage] = Field(default=None) 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) + storage: Optional[KnowledgeStorage] = Field(default=None) embedder_config: Optional[Dict[str, Any]] = None collection_name: Optional[str] = None diff --git a/src/crewai/knowledge/source/base_file_knowledge_source.py b/src/crewai/knowledge/source/base_file_knowledge_source.py index 8cee77e168..5743b1704f 100644 --- a/src/crewai/knowledge/source/base_file_knowledge_source.py +++ b/src/crewai/knowledge/source/base_file_knowledge_source.py @@ -22,7 +22,7 @@ class BaseFileKnowledgeSource(BaseKnowledgeSource, ABC): default_factory=list, description="The path to the file" ) content: Dict[Path, str] = Field(init=False, default_factory=dict) - storage: KnowledgeStorage = Field(default_factory=KnowledgeStorage) + storage: Optional[KnowledgeStorage] = Field(default=None) safe_file_paths: List[Path] = Field(default_factory=list) @field_validator("file_path", "file_paths", mode="before") @@ -62,7 +62,10 @@ def validate_content(self): def _save_documents(self): """Save the documents to the storage.""" - self.storage.save(self.chunks) + if self.storage: + self.storage.save(self.chunks) + else: + raise ValueError("No storage found to save documents.") def convert_to_path(self, path: Union[Path, str]) -> Path: """Convert a path to a Path object.""" diff --git a/src/crewai/knowledge/source/base_knowledge_source.py b/src/crewai/knowledge/source/base_knowledge_source.py index 88c3ab360c..b558a4b9ae 100644 --- a/src/crewai/knowledge/source/base_knowledge_source.py +++ b/src/crewai/knowledge/source/base_knowledge_source.py @@ -16,7 +16,7 @@ class BaseKnowledgeSource(BaseModel, ABC): chunk_embeddings: List[np.ndarray] = Field(default_factory=list) model_config = ConfigDict(arbitrary_types_allowed=True) - storage: KnowledgeStorage = Field(default_factory=KnowledgeStorage) + storage: Optional[KnowledgeStorage] = Field(default=None) metadata: Dict[str, Any] = Field(default_factory=dict) # Currently unused collection_name: Optional[str] = Field(default=None) @@ -46,4 +46,7 @@ def _save_documents(self): Save the documents to the storage. This method should be called after the chunks and embeddings are generated. """ - self.storage.save(self.chunks) + if self.storage: + self.storage.save(self.chunks) + else: + raise ValueError("No storage found to save documents.") From 2433819c4f56088f0f8790890887e13c4691a356 Mon Sep 17 00:00:00 2001 From: "devin-ai-integration[bot]" <158243242+devin-ai-integration[bot]@users.noreply.github.com> Date: Fri, 27 Dec 2024 21:30:39 -0300 Subject: [PATCH 18/18] fix: handle optional storage with null checks (#1808) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> Co-authored-by: João Moura --- src/crewai/knowledge/knowledge.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/crewai/knowledge/knowledge.py b/src/crewai/knowledge/knowledge.py index 571542994a..c964333c82 100644 --- a/src/crewai/knowledge/knowledge.py +++ b/src/crewai/knowledge/knowledge.py @@ -49,8 +49,13 @@ def query(self, query: List[str], limit: int = 3) -> List[Dict[str, Any]]: """ Query across all knowledge sources to find the most relevant information. Returns the top_k most relevant chunks. + + Raises: + ValueError: If storage is not initialized. """ - + if self.storage is None: + raise ValueError("Storage is not initialized.") + results = self.storage.search( query, limit,