From da5f60e7f3f9c39c9eef94141df9c1d4732a91b3 Mon Sep 17 00:00:00 2001 From: lucasgomide Date: Mon, 24 Mar 2025 13:59:17 -0300 Subject: [PATCH 01/14] fix: properly clone ConditionalTask instances Previously copying a Task always returned an instance of Task even when we are cloning a subclass, such ConditionalTask. This commit ensures that the clone preserve the original class type --- src/crewai/task.py | 12 +- .../test_before_kickoff_callback.yaml | 113 ++++++++++++++++++ tests/task_test.py | 19 +++ 3 files changed, 142 insertions(+), 2 deletions(-) diff --git a/src/crewai/task.py b/src/crewai/task.py index 10358147c..c74e0081e 100644 --- a/src/crewai/task.py +++ b/src/crewai/task.py @@ -572,7 +572,15 @@ class Task(BaseModel): def copy( self, agents: List["BaseAgent"], task_mapping: Dict[str, "Task"] ) -> "Task": - """Create a deep copy of the Task.""" + """Creates a deep copy of the Task while preserving its original class type. + + Args: + agents: List of agents available for the task. + task_mapping: Dictionary mapping task IDs to Task instances. + + Returns: + A copy of the task with the same class type as the original. + """ exclude = { "id", "agent", @@ -595,7 +603,7 @@ class Task(BaseModel): cloned_agent = get_agent_by_role(self.agent.role) if self.agent else None cloned_tools = copy(self.tools) if self.tools else [] - copied_task = Task( + copied_task = self.__class__( **copied_data, context=cloned_context, agent=cloned_agent, diff --git a/tests/cassettes/test_before_kickoff_callback.yaml b/tests/cassettes/test_before_kickoff_callback.yaml index ecc2833c3..75c10cfb5 100644 --- a/tests/cassettes/test_before_kickoff_callback.yaml +++ b/tests/cassettes/test_before_kickoff_callback.yaml @@ -710,4 +710,117 @@ interactions: - req_4ceac9bc8ae57f631959b91d2ab63c4d http_version: HTTP/1.1 status_code: 200 +- request: + body: '{"messages": [{"role": "system", "content": "You are Test Agent. Test agent + backstory\nYour personal goal is: Test agent goal\nTo give my best complete + final answer to the task respond using 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: Test task description\n\nThis is the expected criteria for your final + answer: Test expected 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", "stop": ["\nObservation:"]}' + headers: + accept: + - application/json + accept-encoding: + - gzip, deflate + connection: + - keep-alive + content-length: + - '840' + content-type: + - application/json + host: + - api.openai.com + user-agent: + - OpenAI/Python 1.61.0 + x-stainless-arch: + - x64 + x-stainless-async: + - 'false' + x-stainless-lang: + - python + x-stainless-os: + - MacOS + x-stainless-package-version: + - 1.61.0 + x-stainless-raw-response: + - 'true' + x-stainless-retry-count: + - '0' + x-stainless-runtime: + - CPython + x-stainless-runtime-version: + - 3.12.9 + method: POST + uri: https://api.openai.com/v1/chat/completions + response: + content: "{\n \"id\": \"chatcmpl-BExKOliqPgvHyozZaBu5oN50CHtsa\",\n \"object\": + \"chat.completion\",\n \"created\": 1742904348,\n \"model\": \"gpt-4o-2024-08-06\",\n + \ \"choices\": [\n {\n \"index\": 0,\n \"message\": {\n \"role\": + \"assistant\",\n \"content\": \"I now can give a great answer \\nFinal + Answer: Test expected output\",\n \"refusal\": null,\n \"annotations\": + []\n },\n \"logprobs\": null,\n \"finish_reason\": \"stop\"\n + \ }\n ],\n \"usage\": {\n \"prompt_tokens\": 158,\n \"completion_tokens\": + 15,\n \"total_tokens\": 173,\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 \"service_tier\": + \"default\",\n \"system_fingerprint\": \"fp_90d33c15d4\"\n}\n" + headers: + CF-RAY: + - 925e4749af02f227-GRU + Connection: + - keep-alive + Content-Encoding: + - gzip + Content-Type: + - application/json + Date: + - Tue, 25 Mar 2025 12:05:48 GMT + Server: + - cloudflare + Set-Cookie: + - __cf_bm=VHa7Z7dJYptxXpaMxgldvK6HqIM.m74xpi.80N_EBDc-1742904348-1.0.1.1-VthD2riCSnAprFYhOZxfIrTjT33tybJHpHWB25Q_Hx4vuACCyF00tix6e6eorDReGcW3jb5cUzbGqYi47TrMsS4LYjxBv5eCo7cU9OuFajs; + path=/; expires=Tue, 25-Mar-25 12:35:48 GMT; domain=.api.openai.com; HttpOnly; + Secure; SameSite=None + - _cfuvid=Is8fSaH3lU8yHyT3fI7cRZiDqIYSI6sPpzfzvEV8HMc-1742904348760-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 + cf-cache-status: + - DYNAMIC + openai-organization: + - crewai-iuxna1 + openai-processing-ms: + - '377' + openai-version: + - '2020-10-01' + strict-transport-security: + - max-age=31536000; includeSubDomains; preload + x-ratelimit-limit-requests: + - '50000' + x-ratelimit-limit-tokens: + - '150000000' + x-ratelimit-remaining-requests: + - '49999' + x-ratelimit-remaining-tokens: + - '149999822' + x-ratelimit-reset-requests: + - 1ms + x-ratelimit-reset-tokens: + - 0s + x-request-id: + - req_fd6b93e3b1a30868482c72306e7f63c2 + http_version: HTTP/1.1 + status_code: 200 version: 1 diff --git a/tests/task_test.py b/tests/task_test.py index 67ce99910..297ed084f 100644 --- a/tests/task_test.py +++ b/tests/task_test.py @@ -787,6 +787,25 @@ def test_conditional_task_definition_based_on_dict(): assert task.agent is None +def test_conditional_task_copy_preserves_type(): + task_config = { + "description": "Give me an integer score between 1-5 for the following title: 'The impact of AI in the future of work', check examples to based your evaluation.", + "expected_output": "The score of the title.", + } + original_task = Task(**task_config) + copied_task = original_task.copy(agents=[], task_mapping={}) + assert isinstance(copied_task, Task) + + original_conditional_config = { + "description": "Give me an integer score between 1-5 for the following title: 'The impact of AI in the future of work'. Check examples to base your evaluation on.", + "expected_output": "The score of the title.", + "condition": lambda x: True, + } + original_conditional_task = ConditionalTask(**original_conditional_config) + copied_conditional_task = original_conditional_task.copy(agents=[], task_mapping={}) + assert isinstance(copied_conditional_task, ConditionalTask) + + def test_interpolate_inputs(): task = Task( description="Give me a list of 5 interesting ideas about {topic} to explore for an article, what makes them unique and interesting.", From f09238e5129679399e756422e3f22a93955e9398 Mon Sep 17 00:00:00 2001 From: Abby Morgan <86856445+anmorgan24@users.noreply.github.com> Date: Tue, 25 Mar 2025 15:52:29 -0400 Subject: [PATCH 02/14] Update docs.json Add Opik to docs/docs.json --- docs/docs.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/docs/docs.json b/docs/docs.json index 3798bd244..cced352cf 100644 --- a/docs/docs.json +++ b/docs/docs.json @@ -109,6 +109,7 @@ "how-to/langtrace-observability", "how-to/mlflow-observability", "how-to/openlit-observability", + "how-to/opik-observability", "how-to/portkey-observability" ] }, @@ -228,4 +229,4 @@ "reddit": "https://www.reddit.com/r/crewAIInc/" } } -} \ No newline at end of file +} From 838b3bc09d93c0a9571254d284ba5394fe35048e Mon Sep 17 00:00:00 2001 From: Abby Morgan <86856445+anmorgan24@users.noreply.github.com> Date: Wed, 26 Mar 2025 09:36:05 -0400 Subject: [PATCH 03/14] Add opik screenshot --- docs/images/opik-crewai-dashboard.png | Bin 0 -> 101282 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 docs/images/opik-crewai-dashboard.png diff --git a/docs/images/opik-crewai-dashboard.png b/docs/images/opik-crewai-dashboard.png new file mode 100644 index 0000000000000000000000000000000000000000..80dc6fd8a4977de609b949693a770bb96b8238cb GIT binary patch literal 101282 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 Date: Wed, 26 Mar 2025 09:36:23 -0400 Subject: [PATCH 04/14] Create opik-observability.mdx --- docs/how-to/opik-observability.mdx | 116 +++++++++++++++++++++++++++++ 1 file changed, 116 insertions(+) create mode 100644 docs/how-to/opik-observability.mdx diff --git a/docs/how-to/opik-observability.mdx b/docs/how-to/opik-observability.mdx new file mode 100644 index 000000000..49e0c6495 --- /dev/null +++ b/docs/how-to/opik-observability.mdx @@ -0,0 +1,116 @@ +--- +title: Opik Integration +description: Learn how to use Comet Opik to debug, evaluate, and monitor your CrewAI applications with comprehensive tracing, automated evaluations, and production-ready dashboards. +icon: insights +--- + +# Opik Overview + +With [Comet Opik](https://www.comet.com/docs/opik/), debug, evaluate, and monitor your LLM applications, RAG systems, and agentic workflows with comprehensive tracing, automated evaluations, and production-ready dashboards. + +Opik provides comprehensive support for every stage of your CrewAI application development: + +- **Log Traces and Spans**: Automatically track LLM calls and application logic to debug and analyze development and production systems. Manually or programmatically annotate, view, and compare responses across projects. +- **Evaluate Your LLM Application's Performance**: Evaluate against a custom test set and run built-in evaluation metrics or define your own metrics in the SDK or UI. +- **Test Within Your CI/CD Pipeline**: Establish reliable performance baselines with Opik's LLM unit tests, built on PyTest. Run online evaluations for continuous monitoring in production. +- **Monitor & Analyze Production Data**: Understand your models' performance on unseen data in production and generate datasets for new dev iterations. + +## Setup +Comet provides a hosted version of the Opik platform, or you can run the platform locally. + +To use the hosted version, simply [create a Comet account](https://www.comet.com/signup?utm_medium=github&utm_source=crewai_docs) and grab you API Key. + +To run the Opik platform locally, see our [installation guide](https://www.comet.com/docs/opik/self-host/overview/) for more information. + +For this guide we will use CrewAI’s quickstart example. + + + + ```shell + %pip install crewai crewai-tools opik --upgrade + ``` + + + ```python + import opik + opik.configure(use_local=False) + ``` + + First, we set up our API keys for our LLM-provider as environment variables: + ```python + import os + import getpass + + if "OPENAI_API_KEY" not in os.environ: + os.environ["OPENAI_API_KEY"] = getpass.getpass("Enter your OpenAI API key: ") + ``` + + + The first step is to create our project. We will use an example from CrewAI’s documentation: + ```python + from crewai import Agent, Crew, Task, Process + + +class YourCrewName: + def agent_one(self) -> Agent: + return Agent( + role="Data Analyst", + goal="Analyze data trends in the market", + backstory="An experienced data analyst with a background in economics", + verbose=True, + ) + + def agent_two(self) -> Agent: + return Agent( + role="Market Researcher", + goal="Gather information on market dynamics", + backstory="A diligent researcher with a keen eye for detail", + verbose=True, + ) + + def task_one(self) -> Task: + return Task( + name="Collect Data Task", + description="Collect recent market data and identify trends.", + expected_output="A report summarizing key trends in the market.", + agent=self.agent_one(), + ) + + def task_two(self) -> Task: + return Task( + name="Market Research Task", + description="Research factors affecting market dynamics.", + expected_output="An analysis of factors influencing the market.", + agent=self.agent_two(), + ) + + def crew(self) -> Crew: + return Crew( + agents=[self.agent_one(), self.agent_two()], + tasks=[self.task_one(), self.task_two()], + process=Process.sequential, + verbose=True, + ) + + ``` + Now we can import Opik’s tracker and run our crew: + ```python + from opik.integrations.crewai import track_crewai + + track_crewai(project_name="crewai-integration-demo") + + my_crew = YourCrewName().crew() + result = my_crew.kickoff() + + print(result) + ``` + After running your CrewAI application, visit the Opik app to view: + - LLM traces, spans, and their metadata + - Agent interactions and task execution flow + - Performance metrics like latency and token usage + - Evaluation metrics (built-in or custom) + + + Opik agent monitoring example with CrewAI + +<\Steps> From 7def3a8acc4d88f8414f05d71c214086c8f098dd Mon Sep 17 00:00:00 2001 From: Abby Morgan <86856445+anmorgan24@users.noreply.github.com> Date: Wed, 26 Mar 2025 09:42:17 -0400 Subject: [PATCH 05/14] Update opik-observability.mdx Add resources --- docs/how-to/opik-observability.mdx | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/docs/how-to/opik-observability.mdx b/docs/how-to/opik-observability.mdx index 49e0c6495..c1f8c9226 100644 --- a/docs/how-to/opik-observability.mdx +++ b/docs/how-to/opik-observability.mdx @@ -114,3 +114,10 @@ class YourCrewName: Opik agent monitoring example with CrewAI <\Steps> + +## Resources + +- [🦉 Opik Documentation](https://www.comet.com/docs/opik/) +- [👉 Opik + CrewAI Colab](https://colab.research.google.com/github/comet-ml/opik/blob/main/apps/opik-documentation/documentation/docs/cookbook/crewai.ipynb) +- [🐦 X](https://x.com/cometml) +- [💬 Slack](https://slack.comet.com/) From 66124d9afb586db76cde6b5080647b1f32daaa4e Mon Sep 17 00:00:00 2001 From: Abby Morgan <86856445+anmorgan24@users.noreply.github.com> Date: Wed, 26 Mar 2025 09:57:32 -0400 Subject: [PATCH 06/14] Update opik-observability.mdx --- docs/how-to/opik-observability.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/how-to/opik-observability.mdx b/docs/how-to/opik-observability.mdx index c1f8c9226..b434411da 100644 --- a/docs/how-to/opik-observability.mdx +++ b/docs/how-to/opik-observability.mdx @@ -18,7 +18,7 @@ Opik provides comprehensive support for every stage of your CrewAI application d ## Setup Comet provides a hosted version of the Opik platform, or you can run the platform locally. -To use the hosted version, simply [create a Comet account](https://www.comet.com/signup?utm_medium=github&utm_source=crewai_docs) and grab you API Key. +To use the hosted version, simply [create a free Comet account](https://www.comet.com/signup?utm_medium=github&utm_source=crewai_docs) and grab you API Key. To run the Opik platform locally, see our [installation guide](https://www.comet.com/docs/opik/self-host/overview/) for more information. From 8df8255f1806ea462bd9c2b154f40f568afcadc5 Mon Sep 17 00:00:00 2001 From: Abby Morgan <86856445+anmorgan24@users.noreply.github.com> Date: Wed, 26 Mar 2025 10:04:53 -0400 Subject: [PATCH 07/14] Update opik-observability.mdx Fix typo --- docs/how-to/opik-observability.mdx | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/how-to/opik-observability.mdx b/docs/how-to/opik-observability.mdx index b434411da..0e2822f87 100644 --- a/docs/how-to/opik-observability.mdx +++ b/docs/how-to/opik-observability.mdx @@ -113,6 +113,7 @@ class YourCrewName: Opik agent monitoring example with CrewAI + <\Steps> ## Resources From 22c8e5f43354e4f0c58f4f50edfbe1affdc9caf9 Mon Sep 17 00:00:00 2001 From: Abby Morgan <86856445+anmorgan24@users.noreply.github.com> Date: Wed, 26 Mar 2025 10:06:36 -0400 Subject: [PATCH 08/14] Update opik-observability.mdx Fix typo --- docs/how-to/opik-observability.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/how-to/opik-observability.mdx b/docs/how-to/opik-observability.mdx index 0e2822f87..4fb5d2752 100644 --- a/docs/how-to/opik-observability.mdx +++ b/docs/how-to/opik-observability.mdx @@ -114,7 +114,7 @@ class YourCrewName: Opik agent monitoring example with CrewAI -<\Steps> + ## Resources From a25a27c3d35e03698217571aab14372c6b880c9b Mon Sep 17 00:00:00 2001 From: Vini Brasil Date: Wed, 26 Mar 2025 11:35:12 -0300 Subject: [PATCH 09/14] Add exclude option to `to_serializable()` (#2479) --- src/crewai/flow/state_utils.py | 28 ++++++++++++++++++++++++---- tests/flow/test_state_utils.py | 22 +++++++++++++++++++++- 2 files changed, 45 insertions(+), 5 deletions(-) diff --git a/src/crewai/flow/state_utils.py b/src/crewai/flow/state_utils.py index eaf0f21ce..533bc5e00 100644 --- a/src/crewai/flow/state_utils.py +++ b/src/crewai/flow/state_utils.py @@ -1,4 +1,5 @@ import json +import uuid from datetime import date, datetime from typing import Any, Dict, List, Union @@ -32,7 +33,7 @@ def export_state(flow: Flow) -> dict[str, Serializable]: def to_serializable( - obj: Any, max_depth: int = 5, _current_depth: int = 0 + obj: Any, exclude: set[str] | None = None, max_depth: int = 5, _current_depth: int = 0 ) -> Serializable: """Converts a Python object into a JSON-compatible representation. @@ -42,6 +43,7 @@ def to_serializable( Args: obj (Any): Object to transform. + exclude (set[str], optional): Set of keys to exclude from the result. max_depth (int, optional): Maximum recursion depth. Defaults to 5. Returns: @@ -50,21 +52,39 @@ def to_serializable( if _current_depth >= max_depth: return repr(obj) + if exclude is None: + exclude = set() + if isinstance(obj, (str, int, float, bool, type(None))): return obj + elif isinstance(obj, uuid.UUID): + return str(obj) elif isinstance(obj, (date, datetime)): return obj.isoformat() elif isinstance(obj, (list, tuple, set)): - return [to_serializable(item, max_depth, _current_depth + 1) for item in obj] + return [ + to_serializable( + item, max_depth=max_depth, _current_depth=_current_depth + 1 + ) + for item in obj + ] elif isinstance(obj, dict): return { _to_serializable_key(key): to_serializable( - value, max_depth, _current_depth + 1 + obj=value, + exclude=exclude, + max_depth=max_depth, + _current_depth=_current_depth + 1, ) for key, value in obj.items() + if key not in exclude } elif isinstance(obj, BaseModel): - return to_serializable(obj.model_dump(), max_depth, _current_depth + 1) + return to_serializable( + obj=obj.model_dump(exclude=exclude), + max_depth=max_depth, + _current_depth=_current_depth + 1, + ) else: return repr(obj) diff --git a/tests/flow/test_state_utils.py b/tests/flow/test_state_utils.py index 1b135f36b..48564f297 100644 --- a/tests/flow/test_state_utils.py +++ b/tests/flow/test_state_utils.py @@ -6,7 +6,7 @@ import pytest from pydantic import BaseModel from crewai.flow import Flow -from crewai.flow.state_utils import export_state, to_string +from crewai.flow.state_utils import export_state, to_serializable, to_string class Address(BaseModel): @@ -148,3 +148,23 @@ def test_depth_limit(mock_flow): } } } + + +def test_exclude_keys(): + result = to_serializable({"key1": "value1", "key2": "value2"}, exclude={"key1"}) + assert result == {"key2": "value2"} + + model = Person( + name="John Doe", + age=30, + address=Address(street="123 Main St", city="Tech City", country="Pythonia"), + birthday=date(1994, 1, 1), + skills=["Python", "Testing"], + ) + result = to_serializable(model, exclude={"address"}) + assert result == { + "name": "John Doe", + "age": 30, + "birthday": "1994-01-01", + "skills": ["Python", "Testing"], + } From ac848f9ff4a148e25fb8fd8e1abff9d0e1a1a9a3 Mon Sep 17 00:00:00 2001 From: Abby Morgan <86856445+anmorgan24@users.noreply.github.com> Date: Wed, 26 Mar 2025 10:46:59 -0400 Subject: [PATCH 10/14] Update opik-observability.mdx Changed icon to meteor as per tony's request --- docs/how-to/opik-observability.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/how-to/opik-observability.mdx b/docs/how-to/opik-observability.mdx index 4fb5d2752..a1d128b8f 100644 --- a/docs/how-to/opik-observability.mdx +++ b/docs/how-to/opik-observability.mdx @@ -1,7 +1,7 @@ --- title: Opik Integration description: Learn how to use Comet Opik to debug, evaluate, and monitor your CrewAI applications with comprehensive tracing, automated evaluations, and production-ready dashboards. -icon: insights +icon: meteor --- # Opik Overview From 12a815e5db087215dfe44a5a87505ebeef02f47e Mon Sep 17 00:00:00 2001 From: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> Date: Wed, 12 Mar 2025 16:48:40 +0000 Subject: [PATCH 11/14] Fix #2351: Sanitize collection names to meet ChromaDB requirements Co-Authored-By: Joe Moura --- src/crewai/agent.py | 4 +- src/crewai/utilities/__init__.py | 2 + src/crewai/utilities/string_utils.py | 45 +++ tests/utilities/test_string_utils.py | 46 ++- uv.lock | 415 +++++++++++++-------------- 5 files changed, 286 insertions(+), 226 deletions(-) diff --git a/src/crewai/agent.py b/src/crewai/agent.py index 1680f4e8e..b92a83d14 100644 --- a/src/crewai/agent.py +++ b/src/crewai/agent.py @@ -142,8 +142,8 @@ class Agent(BaseAgent): self.embedder = crew_embedder if self.knowledge_sources: - full_pattern = re.compile(r"[^a-zA-Z0-9\-_\r\n]|(\.\.)") - knowledge_agent_name = f"{re.sub(full_pattern, '_', self.role)}" + from crewai.utilities import sanitize_collection_name + knowledge_agent_name = sanitize_collection_name(self.role) if isinstance(self.knowledge_sources, list) and all( isinstance(k, BaseKnowledgeSource) for k in self.knowledge_sources ): diff --git a/src/crewai/utilities/__init__.py b/src/crewai/utilities/__init__.py index dd6d9fa44..f2badd2d4 100644 --- a/src/crewai/utilities/__init__.py +++ b/src/crewai/utilities/__init__.py @@ -7,6 +7,7 @@ from .parser import YamlParser from .printer import Printer from .prompts import Prompts from .rpm_controller import RPMController +from .string_utils import sanitize_collection_name from .exceptions.context_window_exceeding_exception import ( LLMContextLengthExceededException, ) @@ -25,4 +26,5 @@ __all__ = [ "YamlParser", "LLMContextLengthExceededException", "EmbeddingConfigurator", + "sanitize_collection_name", ] diff --git a/src/crewai/utilities/string_utils.py b/src/crewai/utilities/string_utils.py index 9a1857781..6da07b20d 100644 --- a/src/crewai/utilities/string_utils.py +++ b/src/crewai/utilities/string_utils.py @@ -80,3 +80,48 @@ def interpolate_only( result = result.replace(placeholder, value) return result + + +from typing import Optional + + +def sanitize_collection_name(name: Optional[str]) -> str: + """ + Sanitize a collection name to meet ChromaDB requirements: + 1. 3-63 characters long + 2. Starts and ends with alphanumeric character + 3. Contains only alphanumeric characters, underscores, or hyphens + 4. No consecutive periods + 5. Not a valid IPv4 address + + Args: + name: The original collection name to sanitize + + Returns: + A sanitized collection name that meets ChromaDB requirements + """ + if not name: + return "default_collection" + + # Replace spaces and invalid characters with underscores + sanitized = re.sub(r"[^a-zA-Z0-9_-]", "_", name) + + # Ensure it starts with alphanumeric + if not sanitized[0].isalnum(): + sanitized = "a" + sanitized + + # Ensure it ends with alphanumeric + if not sanitized[-1].isalnum(): + sanitized = sanitized[:-1] + "z" + + # Ensure length is between 3-63 characters + if len(sanitized) < 3: + # Add padding with alphanumeric character at the end + sanitized = sanitized + "x" * (3 - len(sanitized)) + if len(sanitized) > 63: + sanitized = sanitized[:63] + # Ensure it still ends with alphanumeric after truncation + if not sanitized[-1].isalnum(): + sanitized = sanitized[:-1] + "z" + + return sanitized diff --git a/tests/utilities/test_string_utils.py b/tests/utilities/test_string_utils.py index 441aae8c0..04a0dcb56 100644 --- a/tests/utilities/test_string_utils.py +++ b/tests/utilities/test_string_utils.py @@ -1,8 +1,9 @@ +import unittest from typing import Any, Dict, List, Union import pytest -from crewai.utilities.string_utils import interpolate_only +from crewai.utilities.string_utils import interpolate_only, sanitize_collection_name class TestInterpolateOnly: @@ -185,3 +186,46 @@ class TestInterpolateOnly: interpolate_only(template, inputs) assert "inputs dictionary cannot be empty" in str(excinfo.value).lower() + + +class TestStringUtils(unittest.TestCase): + def test_sanitize_collection_name_long_name(self): + """Test sanitizing a very long collection name.""" + long_name = "This is an extremely long role name that will definitely exceed the ChromaDB collection name limit of 63 characters and cause an error when used as a collection name" + sanitized = sanitize_collection_name(long_name) + self.assertLessEqual(len(sanitized), 63) + self.assertTrue(sanitized[0].isalnum()) + self.assertTrue(sanitized[-1].isalnum()) + self.assertTrue(all(c.isalnum() or c in ["_", "-"] for c in sanitized)) + + def test_sanitize_collection_name_special_chars(self): + """Test sanitizing a name with special characters.""" + special_chars = "Agent@123!#$%^&*()" + sanitized = sanitize_collection_name(special_chars) + self.assertTrue(sanitized[0].isalnum()) + self.assertTrue(sanitized[-1].isalnum()) + self.assertTrue(all(c.isalnum() or c in ["_", "-"] for c in sanitized)) + + def test_sanitize_collection_name_short_name(self): + """Test sanitizing a very short name.""" + short_name = "A" + sanitized = sanitize_collection_name(short_name) + self.assertGreaterEqual(len(sanitized), 3) + self.assertTrue(sanitized[0].isalnum()) + self.assertTrue(sanitized[-1].isalnum()) + + def test_sanitize_collection_name_bad_ends(self): + """Test sanitizing a name with non-alphanumeric start/end.""" + bad_ends = "_Agent_" + sanitized = sanitize_collection_name(bad_ends) + self.assertTrue(sanitized[0].isalnum()) + self.assertTrue(sanitized[-1].isalnum()) + + def test_sanitize_collection_name_none(self): + """Test sanitizing a None value.""" + sanitized = sanitize_collection_name(None) + self.assertEqual(sanitized, "default_collection") + + +if __name__ == "__main__": + unittest.main() diff --git a/uv.lock b/uv.lock index 3a3f30bab..2bbe20efd 100644 --- a/uv.lock +++ b/uv.lock @@ -1,42 +1,18 @@ version = 1 requires-python = ">=3.10, <3.13" resolution-markers = [ - "python_full_version < '3.11' and platform_system == 'Darwin' and sys_platform == 'darwin'", - "python_full_version < '3.11' and platform_machine == 'aarch64' and platform_system == 'Linux' and sys_platform == 'darwin'", - "(python_full_version < '3.11' and platform_machine != 'aarch64' and platform_system != 'Darwin' and sys_platform == 'darwin') or (python_full_version < '3.11' and platform_system != 'Darwin' and platform_system != 'Linux' and sys_platform == 'darwin')", - "python_full_version < '3.11' and platform_machine == 'aarch64' and platform_system == 'Darwin' and sys_platform == 'linux'", - "python_full_version < '3.11' and platform_machine == 'aarch64' and platform_system == 'Linux' and sys_platform == 'linux'", - "python_full_version < '3.11' and platform_machine == 'aarch64' and platform_system != 'Darwin' and platform_system != 'Linux' and sys_platform == 'linux'", - "(python_full_version < '3.11' and platform_machine != 'aarch64' and platform_system == 'Darwin' and sys_platform != 'darwin') or (python_full_version < '3.11' and platform_system == 'Darwin' and sys_platform != 'darwin' and sys_platform != 'linux')", - "python_full_version < '3.11' and platform_machine == 'aarch64' and platform_system == 'Linux' and sys_platform != 'darwin' and sys_platform != 'linux'", - "(python_full_version < '3.11' and platform_machine != 'aarch64' and platform_system != 'Darwin' and sys_platform != 'darwin') or (python_full_version < '3.11' and platform_system != 'Darwin' and platform_system != 'Linux' and sys_platform != 'darwin' and sys_platform != 'linux')", - "python_full_version == '3.11.*' and platform_system == 'Darwin' and sys_platform == 'darwin'", - "python_full_version == '3.11.*' and platform_machine == 'aarch64' and platform_system == 'Linux' and sys_platform == 'darwin'", - "(python_full_version == '3.11.*' and platform_machine != 'aarch64' and platform_system != 'Darwin' and sys_platform == 'darwin') or (python_full_version == '3.11.*' and platform_system != 'Darwin' and platform_system != 'Linux' and sys_platform == 'darwin')", - "python_full_version == '3.11.*' and platform_machine == 'aarch64' and platform_system == 'Darwin' and sys_platform == 'linux'", - "python_full_version == '3.11.*' and platform_machine == 'aarch64' and platform_system == 'Linux' and sys_platform == 'linux'", - "python_full_version == '3.11.*' and platform_machine == 'aarch64' and platform_system != 'Darwin' and platform_system != 'Linux' and sys_platform == 'linux'", - "(python_full_version == '3.11.*' and platform_machine != 'aarch64' and platform_system == 'Darwin' and sys_platform != 'darwin') or (python_full_version == '3.11.*' and platform_system == 'Darwin' and sys_platform != 'darwin' and sys_platform != 'linux')", - "python_full_version == '3.11.*' and platform_machine == 'aarch64' and platform_system == 'Linux' and sys_platform != 'darwin' and sys_platform != 'linux'", - "(python_full_version == '3.11.*' and platform_machine != 'aarch64' and platform_system != 'Darwin' and sys_platform != 'darwin') or (python_full_version == '3.11.*' and platform_system != 'Darwin' and platform_system != 'Linux' and sys_platform != 'darwin' and sys_platform != 'linux')", - "python_full_version >= '3.12' and python_full_version < '3.12.4' and platform_system == 'Darwin' and sys_platform == 'darwin'", - "python_full_version >= '3.12' and python_full_version < '3.12.4' and platform_machine == 'aarch64' and platform_system == 'Linux' and sys_platform == 'darwin'", - "(python_full_version >= '3.12' and python_full_version < '3.12.4' and platform_machine != 'aarch64' and platform_system != 'Darwin' and sys_platform == 'darwin') or (python_full_version >= '3.12' and python_full_version < '3.12.4' and platform_system != 'Darwin' and platform_system != 'Linux' and sys_platform == 'darwin')", - "python_full_version >= '3.12' and python_full_version < '3.12.4' and platform_machine == 'aarch64' and platform_system == 'Darwin' and sys_platform == 'linux'", - "python_full_version >= '3.12' and python_full_version < '3.12.4' and platform_machine == 'aarch64' and platform_system == 'Linux' and sys_platform == 'linux'", - "python_full_version >= '3.12' and python_full_version < '3.12.4' and platform_machine == 'aarch64' and platform_system != 'Darwin' and platform_system != 'Linux' and sys_platform == 'linux'", - "(python_full_version >= '3.12' and python_full_version < '3.12.4' and platform_machine != 'aarch64' and platform_system == 'Darwin' and sys_platform != 'darwin') or (python_full_version >= '3.12' and python_full_version < '3.12.4' and platform_system == 'Darwin' and sys_platform != 'darwin' and sys_platform != 'linux')", - "python_full_version >= '3.12' and python_full_version < '3.12.4' and platform_machine == 'aarch64' and platform_system == 'Linux' and sys_platform != 'darwin' and sys_platform != 'linux'", - "(python_full_version >= '3.12' and python_full_version < '3.12.4' and platform_machine != 'aarch64' and platform_system != 'Darwin' and sys_platform != 'darwin') or (python_full_version >= '3.12' and python_full_version < '3.12.4' and platform_system != 'Darwin' and platform_system != 'Linux' and sys_platform != 'darwin' and sys_platform != 'linux')", - "python_full_version >= '3.12.4' and platform_system == 'Darwin' and sys_platform == 'darwin'", - "python_full_version >= '3.12.4' and platform_machine == 'aarch64' and platform_system == 'Linux' and sys_platform == 'darwin'", - "(python_full_version >= '3.12.4' and platform_machine != 'aarch64' and platform_system != 'Darwin' and sys_platform == 'darwin') or (python_full_version >= '3.12.4' and platform_system != 'Darwin' and platform_system != 'Linux' and sys_platform == 'darwin')", - "python_full_version >= '3.12.4' and platform_machine == 'aarch64' and platform_system == 'Darwin' and sys_platform == 'linux'", - "python_full_version >= '3.12.4' and platform_machine == 'aarch64' and platform_system == 'Linux' and sys_platform == 'linux'", - "python_full_version >= '3.12.4' and platform_machine == 'aarch64' and platform_system != 'Darwin' and platform_system != 'Linux' and sys_platform == 'linux'", - "(python_full_version >= '3.12.4' and platform_machine != 'aarch64' and platform_system == 'Darwin' and sys_platform != 'darwin') or (python_full_version >= '3.12.4' and platform_system == 'Darwin' and sys_platform != 'darwin' and sys_platform != 'linux')", - "python_full_version >= '3.12.4' and platform_machine == 'aarch64' and platform_system == 'Linux' and sys_platform != 'darwin' and sys_platform != 'linux'", - "(python_full_version >= '3.12.4' and platform_machine != 'aarch64' and platform_system != 'Darwin' and sys_platform != 'darwin') or (python_full_version >= '3.12.4' and platform_system != 'Darwin' and platform_system != 'Linux' and sys_platform != 'darwin' and sys_platform != 'linux')", + "python_full_version < '3.11' and sys_platform == 'darwin'", + "python_full_version < '3.11' and platform_machine == 'aarch64' and sys_platform == 'linux'", + "(python_full_version < '3.11' and platform_machine != 'aarch64' and sys_platform == 'linux') or (python_full_version < '3.11' and sys_platform != 'darwin' and sys_platform != 'linux')", + "python_full_version == '3.11.*' and sys_platform == 'darwin'", + "python_full_version == '3.11.*' and platform_machine == 'aarch64' and sys_platform == 'linux'", + "(python_full_version == '3.11.*' and platform_machine != 'aarch64' and sys_platform == 'linux') or (python_full_version == '3.11.*' and sys_platform != 'darwin' and sys_platform != 'linux')", + "python_full_version >= '3.12' and python_full_version < '3.12.4' and sys_platform == 'darwin'", + "python_full_version >= '3.12' and python_full_version < '3.12.4' and platform_machine == 'aarch64' and sys_platform == 'linux'", + "(python_full_version >= '3.12' and python_full_version < '3.12.4' and platform_machine != 'aarch64' and sys_platform == 'linux') or (python_full_version >= '3.12' and python_full_version < '3.12.4' and sys_platform != 'darwin' and sys_platform != 'linux')", + "python_full_version >= '3.12.4' and sys_platform == 'darwin'", + "python_full_version >= '3.12.4' and platform_machine == 'aarch64' and sys_platform == 'linux'", + "(python_full_version >= '3.12.4' and platform_machine != 'aarch64' and sys_platform == 'linux') or (python_full_version >= '3.12.4' and sys_platform != 'darwin' and sys_platform != 'linux')", ] [[package]] @@ -66,7 +42,7 @@ wheels = [ [[package]] name = "aiohttp" -version = "3.11.11" +version = "3.10.10" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "aiohappyeyeballs" }, @@ -75,56 +51,55 @@ dependencies = [ { name = "attrs" }, { name = "frozenlist" }, { name = "multidict" }, - { name = "propcache" }, { name = "yarl" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/fe/ed/f26db39d29cd3cb2f5a3374304c713fe5ab5a0e4c8ee25a0c45cc6adf844/aiohttp-3.11.11.tar.gz", hash = "sha256:bb49c7f1e6ebf3821a42d81d494f538107610c3a705987f53068546b0e90303e", size = 7669618 } +sdist = { url = "https://files.pythonhosted.org/packages/17/7e/16e57e6cf20eb62481a2f9ce8674328407187950ccc602ad07c685279141/aiohttp-3.10.10.tar.gz", hash = "sha256:0631dd7c9f0822cc61c88586ca76d5b5ada26538097d0f1df510b082bad3411a", size = 7542993 } wheels = [ - { url = "https://files.pythonhosted.org/packages/75/7d/ff2e314b8f9e0b1df833e2d4778eaf23eae6b8cc8f922495d110ddcbf9e1/aiohttp-3.11.11-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:a60804bff28662cbcf340a4d61598891f12eea3a66af48ecfdc975ceec21e3c8", size = 708550 }, - { url = "https://files.pythonhosted.org/packages/09/b8/aeb4975d5bba233d6f246941f5957a5ad4e3def8b0855a72742e391925f2/aiohttp-3.11.11-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:4b4fa1cb5f270fb3eab079536b764ad740bb749ce69a94d4ec30ceee1b5940d5", size = 468430 }, - { url = "https://files.pythonhosted.org/packages/9c/5b/5b620279b3df46e597008b09fa1e10027a39467387c2332657288e25811a/aiohttp-3.11.11-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:731468f555656767cda219ab42e033355fe48c85fbe3ba83a349631541715ba2", size = 455593 }, - { url = "https://files.pythonhosted.org/packages/d8/75/0cdf014b816867d86c0bc26f3d3e3f194198dbf33037890beed629cd4f8f/aiohttp-3.11.11-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cb23d8bb86282b342481cad4370ea0853a39e4a32a0042bb52ca6bdde132df43", size = 1584635 }, - { url = "https://files.pythonhosted.org/packages/df/2f/95b8f4e4dfeb57c1d9ad9fa911ede35a0249d75aa339edd2c2270dc539da/aiohttp-3.11.11-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f047569d655f81cb70ea5be942ee5d4421b6219c3f05d131f64088c73bb0917f", size = 1632363 }, - { url = "https://files.pythonhosted.org/packages/39/cb/70cf69ea7c50f5b0021a84f4c59c3622b2b3b81695f48a2f0e42ef7eba6e/aiohttp-3.11.11-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:dd7659baae9ccf94ae5fe8bfaa2c7bc2e94d24611528395ce88d009107e00c6d", size = 1668315 }, - { url = "https://files.pythonhosted.org/packages/2f/cc/3a3fc7a290eabc59839a7e15289cd48f33dd9337d06e301064e1e7fb26c5/aiohttp-3.11.11-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:af01e42ad87ae24932138f154105e88da13ce7d202a6de93fafdafb2883a00ef", size = 1589546 }, - { url = "https://files.pythonhosted.org/packages/15/b4/0f7b0ed41ac6000e283e7332f0f608d734b675a8509763ca78e93714cfb0/aiohttp-3.11.11-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5854be2f3e5a729800bac57a8d76af464e160f19676ab6aea74bde18ad19d438", size = 1544581 }, - { url = "https://files.pythonhosted.org/packages/58/b9/4d06470fd85c687b6b0e31935ef73dde6e31767c9576d617309a2206556f/aiohttp-3.11.11-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:6526e5fb4e14f4bbf30411216780c9967c20c5a55f2f51d3abd6de68320cc2f3", size = 1529256 }, - { url = "https://files.pythonhosted.org/packages/61/a2/6958b1b880fc017fd35f5dfb2c26a9a50c755b75fd9ae001dc2236a4fb79/aiohttp-3.11.11-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:85992ee30a31835fc482468637b3e5bd085fa8fe9392ba0bdcbdc1ef5e9e3c55", size = 1536592 }, - { url = "https://files.pythonhosted.org/packages/0f/dd/b974012a9551fd654f5bb95a6dd3f03d6e6472a17e1a8216dd42e9638d6c/aiohttp-3.11.11-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:88a12ad8ccf325a8a5ed80e6d7c3bdc247d66175afedbe104ee2aaca72960d8e", size = 1607446 }, - { url = "https://files.pythonhosted.org/packages/e0/d3/6c98fd87e638e51f074a3f2061e81fcb92123bcaf1439ac1b4a896446e40/aiohttp-3.11.11-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:0a6d3fbf2232e3a08c41eca81ae4f1dff3d8f1a30bae415ebe0af2d2458b8a33", size = 1628809 }, - { url = "https://files.pythonhosted.org/packages/a8/2e/86e6f85cbca02be042c268c3d93e7f35977a0e127de56e319bdd1569eaa8/aiohttp-3.11.11-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:84a585799c58b795573c7fa9b84c455adf3e1d72f19a2bf498b54a95ae0d194c", size = 1564291 }, - { url = "https://files.pythonhosted.org/packages/0b/8d/1f4ef3503b767717f65e1f5178b0173ab03cba1a19997ebf7b052161189f/aiohttp-3.11.11-cp310-cp310-win32.whl", hash = "sha256:bfde76a8f430cf5c5584553adf9926534352251d379dcb266ad2b93c54a29745", size = 416601 }, - { url = "https://files.pythonhosted.org/packages/ad/86/81cb83691b5ace3d9aa148dc42bacc3450d749fc88c5ec1973573c1c1779/aiohttp-3.11.11-cp310-cp310-win_amd64.whl", hash = "sha256:0fd82b8e9c383af11d2b26f27a478640b6b83d669440c0a71481f7c865a51da9", size = 442007 }, - { url = "https://files.pythonhosted.org/packages/34/ae/e8806a9f054e15f1d18b04db75c23ec38ec954a10c0a68d3bd275d7e8be3/aiohttp-3.11.11-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:ba74ec819177af1ef7f59063c6d35a214a8fde6f987f7661f4f0eecc468a8f76", size = 708624 }, - { url = "https://files.pythonhosted.org/packages/c7/e0/313ef1a333fb4d58d0c55a6acb3cd772f5d7756604b455181049e222c020/aiohttp-3.11.11-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:4af57160800b7a815f3fe0eba9b46bf28aafc195555f1824555fa2cfab6c1538", size = 468507 }, - { url = "https://files.pythonhosted.org/packages/a9/60/03455476bf1f467e5b4a32a465c450548b2ce724eec39d69f737191f936a/aiohttp-3.11.11-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:ffa336210cf9cd8ed117011085817d00abe4c08f99968deef0013ea283547204", size = 455571 }, - { url = "https://files.pythonhosted.org/packages/be/f9/469588603bd75bf02c8ffb8c8a0d4b217eed446b49d4a767684685aa33fd/aiohttp-3.11.11-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:81b8fe282183e4a3c7a1b72f5ade1094ed1c6345a8f153506d114af5bf8accd9", size = 1685694 }, - { url = "https://files.pythonhosted.org/packages/88/b9/1b7fa43faf6c8616fa94c568dc1309ffee2b6b68b04ac268e5d64b738688/aiohttp-3.11.11-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3af41686ccec6a0f2bdc66686dc0f403c41ac2089f80e2214a0f82d001052c03", size = 1743660 }, - { url = "https://files.pythonhosted.org/packages/2a/8b/0248d19dbb16b67222e75f6aecedd014656225733157e5afaf6a6a07e2e8/aiohttp-3.11.11-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:70d1f9dde0e5dd9e292a6d4d00058737052b01f3532f69c0c65818dac26dc287", size = 1785421 }, - { url = "https://files.pythonhosted.org/packages/c4/11/f478e071815a46ca0a5ae974651ff0c7a35898c55063305a896e58aa1247/aiohttp-3.11.11-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:249cc6912405917344192b9f9ea5cd5b139d49e0d2f5c7f70bdfaf6b4dbf3a2e", size = 1675145 }, - { url = "https://files.pythonhosted.org/packages/26/5d/284d182fecbb5075ae10153ff7374f57314c93a8681666600e3a9e09c505/aiohttp-3.11.11-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0eb98d90b6690827dcc84c246811feeb4e1eea683c0eac6caed7549be9c84665", size = 1619804 }, - { url = "https://files.pythonhosted.org/packages/1b/78/980064c2ad685c64ce0e8aeeb7ef1e53f43c5b005edcd7d32e60809c4992/aiohttp-3.11.11-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:ec82bf1fda6cecce7f7b915f9196601a1bd1a3079796b76d16ae4cce6d0ef89b", size = 1654007 }, - { url = "https://files.pythonhosted.org/packages/21/8d/9e658d63b1438ad42b96f94da227f2e2c1d5c6001c9e8ffcc0bfb22e9105/aiohttp-3.11.11-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:9fd46ce0845cfe28f108888b3ab17abff84ff695e01e73657eec3f96d72eef34", size = 1650022 }, - { url = "https://files.pythonhosted.org/packages/85/fd/a032bf7f2755c2df4f87f9effa34ccc1ef5cea465377dbaeef93bb56bbd6/aiohttp-3.11.11-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:bd176afcf8f5d2aed50c3647d4925d0db0579d96f75a31e77cbaf67d8a87742d", size = 1732899 }, - { url = "https://files.pythonhosted.org/packages/c5/0c/c2b85fde167dd440c7ba50af2aac20b5a5666392b174df54c00f888c5a75/aiohttp-3.11.11-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:ec2aa89305006fba9ffb98970db6c8221541be7bee4c1d027421d6f6df7d1ce2", size = 1755142 }, - { url = "https://files.pythonhosted.org/packages/bc/78/91ae1a3b3b3bed8b893c5d69c07023e151b1c95d79544ad04cf68f596c2f/aiohttp-3.11.11-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:92cde43018a2e17d48bb09c79e4d4cb0e236de5063ce897a5e40ac7cb4878773", size = 1692736 }, - { url = "https://files.pythonhosted.org/packages/77/89/a7ef9c4b4cdb546fcc650ca7f7395aaffbd267f0e1f648a436bec33c9b95/aiohttp-3.11.11-cp311-cp311-win32.whl", hash = "sha256:aba807f9569455cba566882c8938f1a549f205ee43c27b126e5450dc9f83cc62", size = 416418 }, - { url = "https://files.pythonhosted.org/packages/fc/db/2192489a8a51b52e06627506f8ac8df69ee221de88ab9bdea77aa793aa6a/aiohttp-3.11.11-cp311-cp311-win_amd64.whl", hash = "sha256:ae545f31489548c87b0cced5755cfe5a5308d00407000e72c4fa30b19c3220ac", size = 442509 }, - { url = "https://files.pythonhosted.org/packages/69/cf/4bda538c502f9738d6b95ada11603c05ec260807246e15e869fc3ec5de97/aiohttp-3.11.11-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:e595c591a48bbc295ebf47cb91aebf9bd32f3ff76749ecf282ea7f9f6bb73886", size = 704666 }, - { url = "https://files.pythonhosted.org/packages/46/7b/87fcef2cad2fad420ca77bef981e815df6904047d0a1bd6aeded1b0d1d66/aiohttp-3.11.11-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:3ea1b59dc06396b0b424740a10a0a63974c725b1c64736ff788a3689d36c02d2", size = 464057 }, - { url = "https://files.pythonhosted.org/packages/5a/a6/789e1f17a1b6f4a38939fbc39d29e1d960d5f89f73d0629a939410171bc0/aiohttp-3.11.11-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:8811f3f098a78ffa16e0ea36dffd577eb031aea797cbdba81be039a4169e242c", size = 455996 }, - { url = "https://files.pythonhosted.org/packages/b7/dd/485061fbfef33165ce7320db36e530cd7116ee1098e9c3774d15a732b3fd/aiohttp-3.11.11-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bd7227b87a355ce1f4bf83bfae4399b1f5bb42e0259cb9405824bd03d2f4336a", size = 1682367 }, - { url = "https://files.pythonhosted.org/packages/e9/d7/9ec5b3ea9ae215c311d88b2093e8da17e67b8856673e4166c994e117ee3e/aiohttp-3.11.11-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d40f9da8cabbf295d3a9dae1295c69975b86d941bc20f0a087f0477fa0a66231", size = 1736989 }, - { url = "https://files.pythonhosted.org/packages/d6/fb/ea94927f7bfe1d86178c9d3e0a8c54f651a0a655214cce930b3c679b8f64/aiohttp-3.11.11-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ffb3dc385f6bb1568aa974fe65da84723210e5d9707e360e9ecb51f59406cd2e", size = 1793265 }, - { url = "https://files.pythonhosted.org/packages/40/7f/6de218084f9b653026bd7063cd8045123a7ba90c25176465f266976d8c82/aiohttp-3.11.11-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a8f5f7515f3552d899c61202d99dcb17d6e3b0de777900405611cd747cecd1b8", size = 1691841 }, - { url = "https://files.pythonhosted.org/packages/77/e2/992f43d87831cbddb6b09c57ab55499332f60ad6fdbf438ff4419c2925fc/aiohttp-3.11.11-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3499c7ffbfd9c6a3d8d6a2b01c26639da7e43d47c7b4f788016226b1e711caa8", size = 1619317 }, - { url = "https://files.pythonhosted.org/packages/96/74/879b23cdd816db4133325a201287c95bef4ce669acde37f8f1b8669e1755/aiohttp-3.11.11-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:8e2bf8029dbf0810c7bfbc3e594b51c4cc9101fbffb583a3923aea184724203c", size = 1641416 }, - { url = "https://files.pythonhosted.org/packages/30/98/b123f6b15d87c54e58fd7ae3558ff594f898d7f30a90899718f3215ad328/aiohttp-3.11.11-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:b6212a60e5c482ef90f2d788835387070a88d52cf6241d3916733c9176d39eab", size = 1646514 }, - { url = "https://files.pythonhosted.org/packages/d7/38/257fda3dc99d6978ab943141d5165ec74fd4b4164baa15e9c66fa21da86b/aiohttp-3.11.11-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:d119fafe7b634dbfa25a8c597718e69a930e4847f0b88e172744be24515140da", size = 1702095 }, - { url = "https://files.pythonhosted.org/packages/0c/f4/ddab089053f9fb96654df5505c0a69bde093214b3c3454f6bfdb1845f558/aiohttp-3.11.11-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:6fba278063559acc730abf49845d0e9a9e1ba74f85f0ee6efd5803f08b285853", size = 1734611 }, - { url = "https://files.pythonhosted.org/packages/c3/d6/f30b2bc520c38c8aa4657ed953186e535ae84abe55c08d0f70acd72ff577/aiohttp-3.11.11-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:92fc484e34b733704ad77210c7957679c5c3877bd1e6b6d74b185e9320cc716e", size = 1694576 }, - { url = "https://files.pythonhosted.org/packages/bc/97/b0a88c3f4c6d0020b34045ee6d954058abc870814f6e310c4c9b74254116/aiohttp-3.11.11-cp312-cp312-win32.whl", hash = "sha256:9f5b3c1ed63c8fa937a920b6c1bec78b74ee09593b3f5b979ab2ae5ef60d7600", size = 411363 }, - { url = "https://files.pythonhosted.org/packages/7f/23/cc36d9c398980acaeeb443100f0216f50a7cfe20c67a9fd0a2f1a5a846de/aiohttp-3.11.11-cp312-cp312-win_amd64.whl", hash = "sha256:1e69966ea6ef0c14ee53ef7a3d68b564cc408121ea56c0caa2dc918c1b2f553d", size = 437666 }, + { url = "https://files.pythonhosted.org/packages/3d/dd/3d40c0e67e79c5c42671e3e268742f1ff96c6573ca43823563d01abd9475/aiohttp-3.10.10-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:be7443669ae9c016b71f402e43208e13ddf00912f47f623ee5994e12fc7d4b3f", size = 586969 }, + { url = "https://files.pythonhosted.org/packages/75/64/8de41b5555e5b43ef6d4ed1261891d33fe45ecc6cb62875bfafb90b9ab93/aiohttp-3.10.10-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:7b06b7843929e41a94ea09eb1ce3927865387e3e23ebe108e0d0d09b08d25be9", size = 399367 }, + { url = "https://files.pythonhosted.org/packages/96/36/27bd62ea7ce43906d1443a73691823fc82ffb8fa03276b0e2f7e1037c286/aiohttp-3.10.10-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:333cf6cf8e65f6a1e06e9eb3e643a0c515bb850d470902274239fea02033e9a8", size = 390720 }, + { url = "https://files.pythonhosted.org/packages/e8/4d/d516b050d811ce0dd26325c383013c104ffa8b58bd361b82e52833f68e78/aiohttp-3.10.10-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:274cfa632350225ce3fdeb318c23b4a10ec25c0e2c880eff951a3842cf358ac1", size = 1228820 }, + { url = "https://files.pythonhosted.org/packages/53/94/964d9327a3e336d89aad52260836e4ec87fdfa1207176550fdf384eaffe7/aiohttp-3.10.10-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d9e5e4a85bdb56d224f412d9c98ae4cbd032cc4f3161818f692cd81766eee65a", size = 1264616 }, + { url = "https://files.pythonhosted.org/packages/0c/20/70ce17764b685ca8f5bf4d568881b4e1f1f4ea5e8170f512fdb1a33859d2/aiohttp-3.10.10-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2b606353da03edcc71130b52388d25f9a30a126e04caef1fd637e31683033abd", size = 1298402 }, + { url = "https://files.pythonhosted.org/packages/d1/d1/5248225ccc687f498d06c3bca5af2647a361c3687a85eb3aedcc247ee1aa/aiohttp-3.10.10-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ab5a5a0c7a7991d90446a198689c0535be89bbd6b410a1f9a66688f0880ec026", size = 1222205 }, + { url = "https://files.pythonhosted.org/packages/f2/a3/9296b27cc5d4feadf970a14d0694902a49a985f3fae71b8322a5f77b0baa/aiohttp-3.10.10-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:578a4b875af3e0daaf1ac6fa983d93e0bbfec3ead753b6d6f33d467100cdc67b", size = 1193804 }, + { url = "https://files.pythonhosted.org/packages/d9/07/f3760160feb12ac51a6168a6da251a4a8f2a70733d49e6ceb9b3e6ee2f03/aiohttp-3.10.10-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:8105fd8a890df77b76dd3054cddf01a879fc13e8af576805d667e0fa0224c35d", size = 1193544 }, + { url = "https://files.pythonhosted.org/packages/7e/4c/93a70f9a4ba1c30183a6dd68bfa79cddbf9a674f162f9c62e823a74a5515/aiohttp-3.10.10-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:3bcd391d083f636c06a68715e69467963d1f9600f85ef556ea82e9ef25f043f7", size = 1193047 }, + { url = "https://files.pythonhosted.org/packages/ff/a3/36a1e23ff00c7a0cd696c5a28db05db25dc42bfc78c508bd78623ff62a4a/aiohttp-3.10.10-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:fbc6264158392bad9df19537e872d476f7c57adf718944cc1e4495cbabf38e2a", size = 1247201 }, + { url = "https://files.pythonhosted.org/packages/55/ae/95399848557b98bb2c402d640b2276ce3a542b94dba202de5a5a1fe29abe/aiohttp-3.10.10-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:e48d5021a84d341bcaf95c8460b152cfbad770d28e5fe14a768988c461b821bc", size = 1264102 }, + { url = "https://files.pythonhosted.org/packages/38/f5/02e5c72c1b60d7cceb30b982679a26167e84ac029fd35a93dd4da52c50a3/aiohttp-3.10.10-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:2609e9ab08474702cc67b7702dbb8a80e392c54613ebe80db7e8dbdb79837c68", size = 1215760 }, + { url = "https://files.pythonhosted.org/packages/30/17/1463840bad10d02d0439068f37ce5af0b383884b0d5838f46fb027e233bf/aiohttp-3.10.10-cp310-cp310-win32.whl", hash = "sha256:84afcdea18eda514c25bc68b9af2a2b1adea7c08899175a51fe7c4fb6d551257", size = 362678 }, + { url = "https://files.pythonhosted.org/packages/dd/01/a0ef707d93e867a43abbffee3a2cdf30559910750b9176b891628c7ad074/aiohttp-3.10.10-cp310-cp310-win_amd64.whl", hash = "sha256:9c72109213eb9d3874f7ac8c0c5fa90e072d678e117d9061c06e30c85b4cf0e6", size = 381097 }, + { url = "https://files.pythonhosted.org/packages/72/31/3c351d17596194e5a38ef169a4da76458952b2497b4b54645b9d483cbbb0/aiohttp-3.10.10-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:c30a0eafc89d28e7f959281b58198a9fa5e99405f716c0289b7892ca345fe45f", size = 586501 }, + { url = "https://files.pythonhosted.org/packages/a4/a8/a559d09eb08478cdead6b7ce05b0c4a133ba27fcdfa91e05d2e62867300d/aiohttp-3.10.10-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:258c5dd01afc10015866114e210fb7365f0d02d9d059c3c3415382ab633fcbcb", size = 398993 }, + { url = "https://files.pythonhosted.org/packages/c5/47/7736d4174613feef61d25332c3bd1a4f8ff5591fbd7331988238a7299485/aiohttp-3.10.10-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:15ecd889a709b0080f02721255b3f80bb261c2293d3c748151274dfea93ac871", size = 390647 }, + { url = "https://files.pythonhosted.org/packages/27/21/e9ba192a04b7160f5a8952c98a1de7cf8072ad150fa3abd454ead1ab1d7f/aiohttp-3.10.10-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f3935f82f6f4a3820270842e90456ebad3af15810cf65932bd24da4463bc0a4c", size = 1306481 }, + { url = "https://files.pythonhosted.org/packages/cf/50/f364c01c8d0def1dc34747b2470969e216f5a37c7ece00fe558810f37013/aiohttp-3.10.10-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:413251f6fcf552a33c981c4709a6bba37b12710982fec8e558ae944bfb2abd38", size = 1344652 }, + { url = "https://files.pythonhosted.org/packages/1d/c2/74f608e984e9b585649e2e83883facad6fa3fc1d021de87b20cc67e8e5ae/aiohttp-3.10.10-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d1720b4f14c78a3089562b8875b53e36b51c97c51adc53325a69b79b4b48ebcb", size = 1378498 }, + { url = "https://files.pythonhosted.org/packages/9f/a7/05a48c7c0a7a80a5591b1203bf1b64ca2ed6a2050af918d09c05852dc42b/aiohttp-3.10.10-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:679abe5d3858b33c2cf74faec299fda60ea9de62916e8b67e625d65bf069a3b7", size = 1292718 }, + { url = "https://files.pythonhosted.org/packages/7d/78/a925655018747e9790350180330032e27d6e0d7ed30bde545fae42f8c49c/aiohttp-3.10.10-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:79019094f87c9fb44f8d769e41dbb664d6e8fcfd62f665ccce36762deaa0e911", size = 1251776 }, + { url = "https://files.pythonhosted.org/packages/47/9d/85c6b69f702351d1236594745a4fdc042fc43f494c247a98dac17e004026/aiohttp-3.10.10-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:fe2fb38c2ed905a2582948e2de560675e9dfbee94c6d5ccdb1301c6d0a5bf092", size = 1271716 }, + { url = "https://files.pythonhosted.org/packages/7f/a7/55fc805ff9b14af818903882ece08e2235b12b73b867b521b92994c52b14/aiohttp-3.10.10-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:a3f00003de6eba42d6e94fabb4125600d6e484846dbf90ea8e48a800430cc142", size = 1266263 }, + { url = "https://files.pythonhosted.org/packages/1f/ec/d2be2ca7b063e4f91519d550dbc9c1cb43040174a322470deed90b3d3333/aiohttp-3.10.10-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:1bbb122c557a16fafc10354b9d99ebf2f2808a660d78202f10ba9d50786384b9", size = 1321617 }, + { url = "https://files.pythonhosted.org/packages/c9/a3/b29f7920e1cd0a9a68a45dd3eb16140074d2efb1518d2e1f3e140357dc37/aiohttp-3.10.10-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:30ca7c3b94708a9d7ae76ff281b2f47d8eaf2579cd05971b5dc681db8caac6e1", size = 1339227 }, + { url = "https://files.pythonhosted.org/packages/8a/81/34b67235c47e232d807b4bbc42ba9b927c7ce9476872372fddcfd1e41b3d/aiohttp-3.10.10-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:df9270660711670e68803107d55c2b5949c2e0f2e4896da176e1ecfc068b974a", size = 1299068 }, + { url = "https://files.pythonhosted.org/packages/04/1f/26a7fe11b6ad3184f214733428353c89ae9fe3e4f605a657f5245c5e720c/aiohttp-3.10.10-cp311-cp311-win32.whl", hash = "sha256:aafc8ee9b742ce75044ae9a4d3e60e3d918d15a4c2e08a6c3c3e38fa59b92d94", size = 362223 }, + { url = "https://files.pythonhosted.org/packages/10/91/85dcd93f64011434359ce2666bece981f08d31bc49df33261e625b28595d/aiohttp-3.10.10-cp311-cp311-win_amd64.whl", hash = "sha256:362f641f9071e5f3ee6f8e7d37d5ed0d95aae656adf4ef578313ee585b585959", size = 381576 }, + { url = "https://files.pythonhosted.org/packages/ae/99/4c5aefe5ad06a1baf206aed6598c7cdcbc7c044c46801cd0d1ecb758cae3/aiohttp-3.10.10-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:9294bbb581f92770e6ed5c19559e1e99255e4ca604a22c5c6397b2f9dd3ee42c", size = 583536 }, + { url = "https://files.pythonhosted.org/packages/a9/36/8b3bc49b49cb6d2da40ee61ff15dbcc44fd345a3e6ab5bb20844df929821/aiohttp-3.10.10-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:a8fa23fe62c436ccf23ff930149c047f060c7126eae3ccea005f0483f27b2e28", size = 395693 }, + { url = "https://files.pythonhosted.org/packages/e1/77/0aa8660dcf11fa65d61712dbb458c4989de220a844bd69778dff25f2d50b/aiohttp-3.10.10-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:5c6a5b8c7926ba5d8545c7dd22961a107526562da31a7a32fa2456baf040939f", size = 390898 }, + { url = "https://files.pythonhosted.org/packages/38/d2/b833d95deb48c75db85bf6646de0a697e7fb5d87bd27cbade4f9746b48b1/aiohttp-3.10.10-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:007ec22fbc573e5eb2fb7dec4198ef8f6bf2fe4ce20020798b2eb5d0abda6138", size = 1312060 }, + { url = "https://files.pythonhosted.org/packages/aa/5f/29fd5113165a0893de8efedf9b4737e0ba92dfcd791415a528f947d10299/aiohttp-3.10.10-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9627cc1a10c8c409b5822a92d57a77f383b554463d1884008e051c32ab1b3742", size = 1350553 }, + { url = "https://files.pythonhosted.org/packages/ad/cc/f835f74b7d344428469200105236d44606cfa448be1e7c95ca52880d9bac/aiohttp-3.10.10-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:50edbcad60d8f0e3eccc68da67f37268b5144ecc34d59f27a02f9611c1d4eec7", size = 1392646 }, + { url = "https://files.pythonhosted.org/packages/bf/fe/1332409d845ca601893bbf2d76935e0b93d41686e5f333841c7d7a4a770d/aiohttp-3.10.10-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a45d85cf20b5e0d0aa5a8dca27cce8eddef3292bc29d72dcad1641f4ed50aa16", size = 1306310 }, + { url = "https://files.pythonhosted.org/packages/e4/a1/25a7633a5a513278a9892e333501e2e69c83e50be4b57a62285fb7a008c3/aiohttp-3.10.10-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0b00807e2605f16e1e198f33a53ce3c4523114059b0c09c337209ae55e3823a8", size = 1260255 }, + { url = "https://files.pythonhosted.org/packages/f2/39/30eafe89e0e2a06c25e4762844c8214c0c0cd0fd9ffc3471694a7986f421/aiohttp-3.10.10-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:f2d4324a98062be0525d16f768a03e0bbb3b9fe301ceee99611dc9a7953124e6", size = 1271141 }, + { url = "https://files.pythonhosted.org/packages/5b/fc/33125df728b48391ef1fcb512dfb02072158cc10d041414fb79803463020/aiohttp-3.10.10-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:438cd072f75bb6612f2aca29f8bd7cdf6e35e8f160bc312e49fbecab77c99e3a", size = 1280244 }, + { url = "https://files.pythonhosted.org/packages/3b/61/e42bf2c2934b5caa4e2ec0b5e5fd86989adb022b5ee60c2572a9d77cf6fe/aiohttp-3.10.10-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:baa42524a82f75303f714108fea528ccacf0386af429b69fff141ffef1c534f9", size = 1316805 }, + { url = "https://files.pythonhosted.org/packages/18/32/f52a5e2ae9ad3bba10e026a63a7a23abfa37c7d97aeeb9004eaa98df3ce3/aiohttp-3.10.10-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:a7d8d14fe962153fc681f6366bdec33d4356f98a3e3567782aac1b6e0e40109a", size = 1343930 }, + { url = "https://files.pythonhosted.org/packages/05/be/6a403b464dcab3631fe8e27b0f1d906d9e45c5e92aca97ee007e5a895560/aiohttp-3.10.10-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:c1277cd707c465cd09572a774559a3cc7c7a28802eb3a2a9472588f062097205", size = 1306186 }, + { url = "https://files.pythonhosted.org/packages/8e/fd/bb50fe781068a736a02bf5c7ad5f3ab53e39f1d1e63110da6d30f7605edc/aiohttp-3.10.10-cp312-cp312-win32.whl", hash = "sha256:59bb3c54aa420521dc4ce3cc2c3fe2ad82adf7b09403fa1f48ae45c0cbde6628", size = 359289 }, + { url = "https://files.pythonhosted.org/packages/70/9e/5add7e240f77ef67c275c82cc1d08afbca57b77593118c1f6e920ae8ad3f/aiohttp-3.10.10-cp312-cp312-win_amd64.whl", hash = "sha256:0e1b370d8007c4ae31ee6db7f9a2fe801a42b146cec80a86766e7ad5c4a259cf", size = 379313 }, ] [[package]] @@ -345,7 +320,7 @@ name = "build" version = "1.2.2.post1" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "colorama", marker = "os_name == 'nt'" }, + { name = "colorama", marker = "(os_name == 'nt' and platform_machine != 'aarch64' and sys_platform == 'linux') or (os_name == 'nt' and sys_platform != 'darwin' and sys_platform != 'linux')" }, { name = "importlib-metadata", marker = "python_full_version < '3.10.2'" }, { name = "packaging" }, { name = "pyproject-hooks" }, @@ -580,7 +555,7 @@ name = "click" version = "8.1.8" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "colorama", marker = "platform_system == 'Windows'" }, + { name = "colorama", marker = "sys_platform == 'win32'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/b9/2e/0090cbf739cee7d23781ad4b89a9894a41538e4fcf4c31dcdd705b78eb8b/click-8.1.8.tar.gz", hash = "sha256:ed53c9d8990d83c2a27deae68e4ee337473f6330c040a31d4225c9574d16096a", size = 226593 } wheels = [ @@ -768,7 +743,7 @@ dev = [ [[package]] name = "crewai-tools" -version = "0.37.0" +version = "0.38.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "chromadb" }, @@ -783,9 +758,9 @@ dependencies = [ { name = "pytube" }, { name = "requests" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/ef/a9/813ef7b721d11ac962c2a3cf4c98196d3ca8bca5bb0fa5e01da0af51ac23/crewai_tools-0.37.0.tar.gz", hash = "sha256:23c8428761809e30d164be32c2a02850c4648e4371e9934eb58842590bca9659", size = 722104 } +sdist = { url = "https://files.pythonhosted.org/packages/85/3f/d3b5697b4c6756cec65316c9ea9ccd9054f7b73670d1580befd3632ba031/crewai_tools-0.38.1.tar.gz", hash = "sha256:6abe75b3b339d53a9cf4e2d80124d863ff62a82b36753c30bec64318881876b2", size = 737620 } wheels = [ - { url = "https://files.pythonhosted.org/packages/f4/b3/6bf9b066f628875c383689ab72d21968e1108ebece887491dbf051ee39c5/crewai_tools-0.37.0-py3-none-any.whl", hash = "sha256:df5c9efade5c1f4fcfdf6ac8af13c422be7127a3083a5cda75d8f314c652bb10", size = 548490 }, + { url = "https://files.pythonhosted.org/packages/2b/2b/a6c9007647ffbb6a3c204b3ef26806030d6b041e3e012d4cec43c21335d6/crewai_tools-0.38.1-py3-none-any.whl", hash = "sha256:d9d3a88060f1f30c8f4ea044f6dd564a50d0a22b8a018a6fcec202b36246b9d8", size = 561414 }, ] [[package]] @@ -1746,7 +1721,7 @@ wheels = [ [[package]] name = "httpx" -version = "0.27.0" +version = "0.27.2" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "anyio" }, @@ -1755,9 +1730,9 @@ dependencies = [ { name = "idna" }, { name = "sniffio" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/5c/2d/3da5bdf4408b8b2800061c339f240c1802f2e82d55e50bd39c5a881f47f0/httpx-0.27.0.tar.gz", hash = "sha256:a0cb88a46f32dc874e04ee956e4c2764aba2aa228f650b06788ba6bda2962ab5", size = 126413 } +sdist = { url = "https://files.pythonhosted.org/packages/78/82/08f8c936781f67d9e6b9eeb8a0c8b4e406136ea4c3d1f89a5db71d42e0e6/httpx-0.27.2.tar.gz", hash = "sha256:f7c2be1d2f3c3c3160d441802406b206c2b76f5947b11115e6df10c6c65e66c2", size = 144189 } wheels = [ - { url = "https://files.pythonhosted.org/packages/41/7b/ddacf6dcebb42466abd03f368782142baa82e08fc0c1f8eaa05b4bae87d5/httpx-0.27.0-py3-none-any.whl", hash = "sha256:71d5465162c13681bff01ad59b2cc68dd838ea1f10e51574bac27103f00c91a5", size = 75590 }, + { url = "https://files.pythonhosted.org/packages/56/95/9377bcb415797e44274b51d46e3249eba641711cf3348050f76ee7b15ffc/httpx-0.27.2-py3-none-any.whl", hash = "sha256:7bb2708e112d8fdd7829cd4243970f0c223274051cb35ee80c03301ee29a3df0", size = 76395 }, ] [package.optional-dependencies] @@ -2519,7 +2494,7 @@ version = "1.6.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "click" }, - { name = "colorama", marker = "platform_system == 'Windows'" }, + { name = "colorama", marker = "sys_platform == 'win32'" }, { name = "ghp-import" }, { name = "jinja2" }, { name = "markdown" }, @@ -2700,7 +2675,7 @@ version = "2.10.2" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "pygments" }, - { name = "pywin32", marker = "platform_system == 'Windows'" }, + { name = "pywin32", marker = "sys_platform == 'win32'" }, { name = "tqdm" }, ] sdist = { url = "https://files.pythonhosted.org/packages/3a/93/80ac75c20ce54c785648b4ed363c88f148bf22637e10c9863db4fbe73e74/mpire-2.10.2.tar.gz", hash = "sha256:f66a321e93fadff34585a4bfa05e95bd946cf714b442f51c529038eb45773d97", size = 271270 } @@ -2947,7 +2922,7 @@ 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' and sys_platform != 'linux')" }, + { name = "nvidia-cublas-cu12", marker = "(platform_machine != 'aarch64' and sys_platform == 'linux') or (sys_platform != 'darwin' and sys_platform != '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 }, @@ -2974,9 +2949,9 @@ 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' and sys_platform != 'linux')" }, - { name = "nvidia-cusparse-cu12", marker = "(platform_machine != 'aarch64' and platform_system != 'Darwin') or (platform_system != 'Darwin' and platform_system != 'Linux' and sys_platform != 'linux')" }, - { name = "nvidia-nvjitlink-cu12", marker = "(platform_machine != 'aarch64' and platform_system != 'Darwin') or (platform_system != 'Darwin' and platform_system != 'Linux' and sys_platform != 'linux')" }, + { name = "nvidia-cublas-cu12", marker = "(platform_machine != 'aarch64' and sys_platform == 'linux') or (sys_platform != 'darwin' and sys_platform != 'linux')" }, + { name = "nvidia-cusparse-cu12", marker = "(platform_machine != 'aarch64' and sys_platform == 'linux') or (sys_platform != 'darwin' and sys_platform != 'linux')" }, + { name = "nvidia-nvjitlink-cu12", marker = "(platform_machine != 'aarch64' and sys_platform == 'linux') or (sys_platform != 'darwin' and sys_platform != '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 }, @@ -2987,7 +2962,7 @@ 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' and sys_platform != 'linux')" }, + { name = "nvidia-nvjitlink-cu12", marker = "(platform_machine != 'aarch64' and sys_platform == 'linux') or (sys_platform != 'darwin' and sys_platform != '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 }, @@ -3087,7 +3062,7 @@ wheels = [ [[package]] name = "openai" -version = "1.61.0" +version = "1.68.2" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "anyio" }, @@ -3099,9 +3074,9 @@ dependencies = [ { name = "tqdm" }, { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/32/2a/b3fa8790be17d632f59d4f50257b909a3f669036e5195c1ae55737274620/openai-1.61.0.tar.gz", hash = "sha256:216f325a24ed8578e929b0f1b3fb2052165f3b04b0461818adaa51aa29c71f8a", size = 350174 } +sdist = { url = "https://files.pythonhosted.org/packages/3f/6b/6b002d5d38794645437ae3ddb42083059d556558493408d39a0fcea608bc/openai-1.68.2.tar.gz", hash = "sha256:b720f0a95a1dbe1429c0d9bb62096a0d98057bcda82516f6e8af10284bdd5b19", size = 413429 } wheels = [ - { url = "https://files.pythonhosted.org/packages/93/76/70c5ad6612b3e4c89fa520266bbf2430a89cae8bd87c1e2284698af5927e/openai-1.61.0-py3-none-any.whl", hash = "sha256:e8c512c0743accbdbe77f3429a1490d862f8352045de8dc81969301eb4a4f666", size = 460623 }, + { url = "https://files.pythonhosted.org/packages/fd/34/cebce15f64eb4a3d609a83ac3568d43005cc9a1cba9d7fde5590fd415423/openai-1.68.2-py3-none-any.whl", hash = "sha256:24484cb5c9a33b58576fdc5acf0e5f92603024a4e39d0b99793dfa1eb14c2b36", size = 606073 }, ] [[package]] @@ -3526,7 +3501,7 @@ name = "portalocker" version = "2.10.1" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pywin32", marker = "platform_system == 'Windows'" }, + { name = "pywin32", marker = "sys_platform == 'win32'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/ed/d3/c6c64067759e87af98cc668c1cc75171347d0f1577fab7ca3749134e3cd4/portalocker-2.10.1.tar.gz", hash = "sha256:ef1bf844e878ab08aee7e40184156e1151f228f103aa5c6bd0724cc330960f8f", size = 40891 } wheels = [ @@ -3833,77 +3808,71 @@ wheels = [ [[package]] name = "pydantic" -version = "2.10.4" +version = "2.9.2" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "annotated-types" }, { name = "pydantic-core" }, { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/70/7e/fb60e6fee04d0ef8f15e4e01ff187a196fa976eb0f0ab524af4599e5754c/pydantic-2.10.4.tar.gz", hash = "sha256:82f12e9723da6de4fe2ba888b5971157b3be7ad914267dea8f05f82b28254f06", size = 762094 } +sdist = { url = "https://files.pythonhosted.org/packages/a9/b7/d9e3f12af310e1120c21603644a1cd86f59060e040ec5c3a80b8f05fae30/pydantic-2.9.2.tar.gz", hash = "sha256:d155cef71265d1e9807ed1c32b4c8deec042a44a50a4188b25ac67ecd81a9c0f", size = 769917 } wheels = [ - { url = "https://files.pythonhosted.org/packages/f3/26/3e1bbe954fde7ee22a6e7d31582c642aad9e84ffe4b5fb61e63b87cd326f/pydantic-2.10.4-py3-none-any.whl", hash = "sha256:597e135ea68be3a37552fb524bc7d0d66dcf93d395acd93a00682f1efcb8ee3d", size = 431765 }, + { url = "https://files.pythonhosted.org/packages/df/e4/ba44652d562cbf0bf320e0f3810206149c8a4e99cdbf66da82e97ab53a15/pydantic-2.9.2-py3-none-any.whl", hash = "sha256:f048cec7b26778210e28a0459867920654d48e5e62db0958433636cde4254f12", size = 434928 }, ] [[package]] name = "pydantic-core" -version = "2.27.2" +version = "2.23.4" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/fc/01/f3e5ac5e7c25833db5eb555f7b7ab24cd6f8c322d3a3ad2d67a952dc0abc/pydantic_core-2.27.2.tar.gz", hash = "sha256:eb026e5a4c1fee05726072337ff51d1efb6f59090b7da90d30ea58625b1ffb39", size = 413443 } +sdist = { url = "https://files.pythonhosted.org/packages/e2/aa/6b6a9b9f8537b872f552ddd46dd3da230367754b6f707b8e1e963f515ea3/pydantic_core-2.23.4.tar.gz", hash = "sha256:2584f7cf844ac4d970fba483a717dbe10c1c1c96a969bf65d61ffe94df1b2863", size = 402156 } wheels = [ - { url = "https://files.pythonhosted.org/packages/3a/bc/fed5f74b5d802cf9a03e83f60f18864e90e3aed7223adaca5ffb7a8d8d64/pydantic_core-2.27.2-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:2d367ca20b2f14095a8f4fa1210f5a7b78b8a20009ecced6b12818f455b1e9fa", size = 1895938 }, - { url = "https://files.pythonhosted.org/packages/71/2a/185aff24ce844e39abb8dd680f4e959f0006944f4a8a0ea372d9f9ae2e53/pydantic_core-2.27.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:491a2b73db93fab69731eaee494f320faa4e093dbed776be1a829c2eb222c34c", size = 1815684 }, - { url = "https://files.pythonhosted.org/packages/c3/43/fafabd3d94d159d4f1ed62e383e264f146a17dd4d48453319fd782e7979e/pydantic_core-2.27.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7969e133a6f183be60e9f6f56bfae753585680f3b7307a8e555a948d443cc05a", size = 1829169 }, - { url = "https://files.pythonhosted.org/packages/a2/d1/f2dfe1a2a637ce6800b799aa086d079998959f6f1215eb4497966efd2274/pydantic_core-2.27.2-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:3de9961f2a346257caf0aa508a4da705467f53778e9ef6fe744c038119737ef5", size = 1867227 }, - { url = "https://files.pythonhosted.org/packages/7d/39/e06fcbcc1c785daa3160ccf6c1c38fea31f5754b756e34b65f74e99780b5/pydantic_core-2.27.2-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e2bb4d3e5873c37bb3dd58714d4cd0b0e6238cebc4177ac8fe878f8b3aa8e74c", size = 2037695 }, - { url = "https://files.pythonhosted.org/packages/7a/67/61291ee98e07f0650eb756d44998214231f50751ba7e13f4f325d95249ab/pydantic_core-2.27.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:280d219beebb0752699480fe8f1dc61ab6615c2046d76b7ab7ee38858de0a4e7", size = 2741662 }, - { url = "https://files.pythonhosted.org/packages/32/90/3b15e31b88ca39e9e626630b4c4a1f5a0dfd09076366f4219429e6786076/pydantic_core-2.27.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:47956ae78b6422cbd46f772f1746799cbb862de838fd8d1fbd34a82e05b0983a", size = 1993370 }, - { url = "https://files.pythonhosted.org/packages/ff/83/c06d333ee3a67e2e13e07794995c1535565132940715931c1c43bfc85b11/pydantic_core-2.27.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:14d4a5c49d2f009d62a2a7140d3064f686d17a5d1a268bc641954ba181880236", size = 1996813 }, - { url = "https://files.pythonhosted.org/packages/7c/f7/89be1c8deb6e22618a74f0ca0d933fdcb8baa254753b26b25ad3acff8f74/pydantic_core-2.27.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:337b443af21d488716f8d0b6164de833e788aa6bd7e3a39c005febc1284f4962", size = 2005287 }, - { url = "https://files.pythonhosted.org/packages/b7/7d/8eb3e23206c00ef7feee17b83a4ffa0a623eb1a9d382e56e4aa46fd15ff2/pydantic_core-2.27.2-cp310-cp310-musllinux_1_1_armv7l.whl", hash = "sha256:03d0f86ea3184a12f41a2d23f7ccb79cdb5a18e06993f8a45baa8dfec746f0e9", size = 2128414 }, - { url = "https://files.pythonhosted.org/packages/4e/99/fe80f3ff8dd71a3ea15763878d464476e6cb0a2db95ff1c5c554133b6b83/pydantic_core-2.27.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:7041c36f5680c6e0f08d922aed302e98b3745d97fe1589db0a3eebf6624523af", size = 2155301 }, - { url = "https://files.pythonhosted.org/packages/2b/a3/e50460b9a5789ca1451b70d4f52546fa9e2b420ba3bfa6100105c0559238/pydantic_core-2.27.2-cp310-cp310-win32.whl", hash = "sha256:50a68f3e3819077be2c98110c1f9dcb3817e93f267ba80a2c05bb4f8799e2ff4", size = 1816685 }, - { url = "https://files.pythonhosted.org/packages/57/4c/a8838731cb0f2c2a39d3535376466de6049034d7b239c0202a64aaa05533/pydantic_core-2.27.2-cp310-cp310-win_amd64.whl", hash = "sha256:e0fd26b16394ead34a424eecf8a31a1f5137094cabe84a1bcb10fa6ba39d3d31", size = 1982876 }, - { url = "https://files.pythonhosted.org/packages/c2/89/f3450af9d09d44eea1f2c369f49e8f181d742f28220f88cc4dfaae91ea6e/pydantic_core-2.27.2-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:8e10c99ef58cfdf2a66fc15d66b16c4a04f62bca39db589ae8cba08bc55331bc", size = 1893421 }, - { url = "https://files.pythonhosted.org/packages/9e/e3/71fe85af2021f3f386da42d291412e5baf6ce7716bd7101ea49c810eda90/pydantic_core-2.27.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:26f32e0adf166a84d0cb63be85c562ca8a6fa8de28e5f0d92250c6b7e9e2aff7", size = 1814998 }, - { url = "https://files.pythonhosted.org/packages/a6/3c/724039e0d848fd69dbf5806894e26479577316c6f0f112bacaf67aa889ac/pydantic_core-2.27.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8c19d1ea0673cd13cc2f872f6c9ab42acc4e4f492a7ca9d3795ce2b112dd7e15", size = 1826167 }, - { url = "https://files.pythonhosted.org/packages/2b/5b/1b29e8c1fb5f3199a9a57c1452004ff39f494bbe9bdbe9a81e18172e40d3/pydantic_core-2.27.2-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:5e68c4446fe0810e959cdff46ab0a41ce2f2c86d227d96dc3847af0ba7def306", size = 1865071 }, - { url = "https://files.pythonhosted.org/packages/89/6c/3985203863d76bb7d7266e36970d7e3b6385148c18a68cc8915fd8c84d57/pydantic_core-2.27.2-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d9640b0059ff4f14d1f37321b94061c6db164fbe49b334b31643e0528d100d99", size = 2036244 }, - { url = "https://files.pythonhosted.org/packages/0e/41/f15316858a246b5d723f7d7f599f79e37493b2e84bfc789e58d88c209f8a/pydantic_core-2.27.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:40d02e7d45c9f8af700f3452f329ead92da4c5f4317ca9b896de7ce7199ea459", size = 2737470 }, - { url = "https://files.pythonhosted.org/packages/a8/7c/b860618c25678bbd6d1d99dbdfdf0510ccb50790099b963ff78a124b754f/pydantic_core-2.27.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1c1fd185014191700554795c99b347d64f2bb637966c4cfc16998a0ca700d048", size = 1992291 }, - { url = "https://files.pythonhosted.org/packages/bf/73/42c3742a391eccbeab39f15213ecda3104ae8682ba3c0c28069fbcb8c10d/pydantic_core-2.27.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:d81d2068e1c1228a565af076598f9e7451712700b673de8f502f0334f281387d", size = 1994613 }, - { url = "https://files.pythonhosted.org/packages/94/7a/941e89096d1175d56f59340f3a8ebaf20762fef222c298ea96d36a6328c5/pydantic_core-2.27.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:1a4207639fb02ec2dbb76227d7c751a20b1a6b4bc52850568e52260cae64ca3b", size = 2002355 }, - { url = "https://files.pythonhosted.org/packages/6e/95/2359937a73d49e336a5a19848713555605d4d8d6940c3ec6c6c0ca4dcf25/pydantic_core-2.27.2-cp311-cp311-musllinux_1_1_armv7l.whl", hash = "sha256:3de3ce3c9ddc8bbd88f6e0e304dea0e66d843ec9de1b0042b0911c1663ffd474", size = 2126661 }, - { url = "https://files.pythonhosted.org/packages/2b/4c/ca02b7bdb6012a1adef21a50625b14f43ed4d11f1fc237f9d7490aa5078c/pydantic_core-2.27.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:30c5f68ded0c36466acede341551106821043e9afaad516adfb6e8fa80a4e6a6", size = 2153261 }, - { url = "https://files.pythonhosted.org/packages/72/9d/a241db83f973049a1092a079272ffe2e3e82e98561ef6214ab53fe53b1c7/pydantic_core-2.27.2-cp311-cp311-win32.whl", hash = "sha256:c70c26d2c99f78b125a3459f8afe1aed4d9687c24fd677c6a4436bc042e50d6c", size = 1812361 }, - { url = "https://files.pythonhosted.org/packages/e8/ef/013f07248041b74abd48a385e2110aa3a9bbfef0fbd97d4e6d07d2f5b89a/pydantic_core-2.27.2-cp311-cp311-win_amd64.whl", hash = "sha256:08e125dbdc505fa69ca7d9c499639ab6407cfa909214d500897d02afb816e7cc", size = 1982484 }, - { url = "https://files.pythonhosted.org/packages/10/1c/16b3a3e3398fd29dca77cea0a1d998d6bde3902fa2706985191e2313cc76/pydantic_core-2.27.2-cp311-cp311-win_arm64.whl", hash = "sha256:26f0d68d4b235a2bae0c3fc585c585b4ecc51382db0e3ba402a22cbc440915e4", size = 1867102 }, - { url = "https://files.pythonhosted.org/packages/d6/74/51c8a5482ca447871c93e142d9d4a92ead74de6c8dc5e66733e22c9bba89/pydantic_core-2.27.2-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:9e0c8cfefa0ef83b4da9588448b6d8d2a2bf1a53c3f1ae5fca39eb3061e2f0b0", size = 1893127 }, - { url = "https://files.pythonhosted.org/packages/d3/f3/c97e80721735868313c58b89d2de85fa80fe8dfeeed84dc51598b92a135e/pydantic_core-2.27.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:83097677b8e3bd7eaa6775720ec8e0405f1575015a463285a92bfdfe254529ef", size = 1811340 }, - { url = "https://files.pythonhosted.org/packages/9e/91/840ec1375e686dbae1bd80a9e46c26a1e0083e1186abc610efa3d9a36180/pydantic_core-2.27.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:172fce187655fece0c90d90a678424b013f8fbb0ca8b036ac266749c09438cb7", size = 1822900 }, - { url = "https://files.pythonhosted.org/packages/f6/31/4240bc96025035500c18adc149aa6ffdf1a0062a4b525c932065ceb4d868/pydantic_core-2.27.2-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:519f29f5213271eeeeb3093f662ba2fd512b91c5f188f3bb7b27bc5973816934", size = 1869177 }, - { url = "https://files.pythonhosted.org/packages/fa/20/02fbaadb7808be578317015c462655c317a77a7c8f0ef274bc016a784c54/pydantic_core-2.27.2-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:05e3a55d124407fffba0dd6b0c0cd056d10e983ceb4e5dbd10dda135c31071d6", size = 2038046 }, - { url = "https://files.pythonhosted.org/packages/06/86/7f306b904e6c9eccf0668248b3f272090e49c275bc488a7b88b0823444a4/pydantic_core-2.27.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9c3ed807c7b91de05e63930188f19e921d1fe90de6b4f5cd43ee7fcc3525cb8c", size = 2685386 }, - { url = "https://files.pythonhosted.org/packages/8d/f0/49129b27c43396581a635d8710dae54a791b17dfc50c70164866bbf865e3/pydantic_core-2.27.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6fb4aadc0b9a0c063206846d603b92030eb6f03069151a625667f982887153e2", size = 1997060 }, - { url = "https://files.pythonhosted.org/packages/0d/0f/943b4af7cd416c477fd40b187036c4f89b416a33d3cc0ab7b82708a667aa/pydantic_core-2.27.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:28ccb213807e037460326424ceb8b5245acb88f32f3d2777427476e1b32c48c4", size = 2004870 }, - { url = "https://files.pythonhosted.org/packages/35/40/aea70b5b1a63911c53a4c8117c0a828d6790483f858041f47bab0b779f44/pydantic_core-2.27.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:de3cd1899e2c279b140adde9357c4495ed9d47131b4a4eaff9052f23398076b3", size = 1999822 }, - { url = "https://files.pythonhosted.org/packages/f2/b3/807b94fd337d58effc5498fd1a7a4d9d59af4133e83e32ae39a96fddec9d/pydantic_core-2.27.2-cp312-cp312-musllinux_1_1_armv7l.whl", hash = "sha256:220f892729375e2d736b97d0e51466252ad84c51857d4d15f5e9692f9ef12be4", size = 2130364 }, - { url = "https://files.pythonhosted.org/packages/fc/df/791c827cd4ee6efd59248dca9369fb35e80a9484462c33c6649a8d02b565/pydantic_core-2.27.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:a0fcd29cd6b4e74fe8ddd2c90330fd8edf2e30cb52acda47f06dd615ae72da57", size = 2158303 }, - { url = "https://files.pythonhosted.org/packages/9b/67/4e197c300976af185b7cef4c02203e175fb127e414125916bf1128b639a9/pydantic_core-2.27.2-cp312-cp312-win32.whl", hash = "sha256:1e2cb691ed9834cd6a8be61228471d0a503731abfb42f82458ff27be7b2186fc", size = 1834064 }, - { url = "https://files.pythonhosted.org/packages/1f/ea/cd7209a889163b8dcca139fe32b9687dd05249161a3edda62860430457a5/pydantic_core-2.27.2-cp312-cp312-win_amd64.whl", hash = "sha256:cc3f1a99a4f4f9dd1de4fe0312c114e740b5ddead65bb4102884b384c15d8bc9", size = 1989046 }, - { url = "https://files.pythonhosted.org/packages/bc/49/c54baab2f4658c26ac633d798dab66b4c3a9bbf47cff5284e9c182f4137a/pydantic_core-2.27.2-cp312-cp312-win_arm64.whl", hash = "sha256:3911ac9284cd8a1792d3cb26a2da18f3ca26c6908cc434a18f730dc0db7bfa3b", size = 1885092 }, - { url = "https://files.pythonhosted.org/packages/46/72/af70981a341500419e67d5cb45abe552a7c74b66326ac8877588488da1ac/pydantic_core-2.27.2-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:2bf14caea37e91198329b828eae1618c068dfb8ef17bb33287a7ad4b61ac314e", size = 1891159 }, - { url = "https://files.pythonhosted.org/packages/ad/3d/c5913cccdef93e0a6a95c2d057d2c2cba347815c845cda79ddd3c0f5e17d/pydantic_core-2.27.2-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:b0cb791f5b45307caae8810c2023a184c74605ec3bcbb67d13846c28ff731ff8", size = 1768331 }, - { url = "https://files.pythonhosted.org/packages/f6/f0/a3ae8fbee269e4934f14e2e0e00928f9346c5943174f2811193113e58252/pydantic_core-2.27.2-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:688d3fd9fcb71f41c4c015c023d12a79d1c4c0732ec9eb35d96e3388a120dcf3", size = 1822467 }, - { url = "https://files.pythonhosted.org/packages/d7/7a/7bbf241a04e9f9ea24cd5874354a83526d639b02674648af3f350554276c/pydantic_core-2.27.2-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3d591580c34f4d731592f0e9fe40f9cc1b430d297eecc70b962e93c5c668f15f", size = 1979797 }, - { url = "https://files.pythonhosted.org/packages/4f/5f/4784c6107731f89e0005a92ecb8a2efeafdb55eb992b8e9d0a2be5199335/pydantic_core-2.27.2-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:82f986faf4e644ffc189a7f1aafc86e46ef70372bb153e7001e8afccc6e54133", size = 1987839 }, - { url = "https://files.pythonhosted.org/packages/6d/a7/61246562b651dff00de86a5f01b6e4befb518df314c54dec187a78d81c84/pydantic_core-2.27.2-pp310-pypy310_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:bec317a27290e2537f922639cafd54990551725fc844249e64c523301d0822fc", size = 1998861 }, - { url = "https://files.pythonhosted.org/packages/86/aa/837821ecf0c022bbb74ca132e117c358321e72e7f9702d1b6a03758545e2/pydantic_core-2.27.2-pp310-pypy310_pp73-musllinux_1_1_armv7l.whl", hash = "sha256:0296abcb83a797db256b773f45773da397da75a08f5fcaef41f2044adec05f50", size = 2116582 }, - { url = "https://files.pythonhosted.org/packages/81/b0/5e74656e95623cbaa0a6278d16cf15e10a51f6002e3ec126541e95c29ea3/pydantic_core-2.27.2-pp310-pypy310_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:0d75070718e369e452075a6017fbf187f788e17ed67a3abd47fa934d001863d9", size = 2151985 }, - { url = "https://files.pythonhosted.org/packages/63/37/3e32eeb2a451fddaa3898e2163746b0cffbbdbb4740d38372db0490d67f3/pydantic_core-2.27.2-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:7e17b560be3c98a8e3aa66ce828bdebb9e9ac6ad5466fba92eb74c4c95cb1151", size = 2004715 }, + { url = "https://files.pythonhosted.org/packages/5c/8b/d3ae387f66277bd8104096d6ec0a145f4baa2966ebb2cad746c0920c9526/pydantic_core-2.23.4-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:b10bd51f823d891193d4717448fab065733958bdb6a6b351967bd349d48d5c9b", size = 1867835 }, + { url = "https://files.pythonhosted.org/packages/46/76/f68272e4c3a7df8777798282c5e47d508274917f29992d84e1898f8908c7/pydantic_core-2.23.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:4fc714bdbfb534f94034efaa6eadd74e5b93c8fa6315565a222f7b6f42ca1166", size = 1776689 }, + { url = "https://files.pythonhosted.org/packages/cc/69/5f945b4416f42ea3f3bc9d2aaec66c76084a6ff4ff27555bf9415ab43189/pydantic_core-2.23.4-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:63e46b3169866bd62849936de036f901a9356e36376079b05efa83caeaa02ceb", size = 1800748 }, + { url = "https://files.pythonhosted.org/packages/50/ab/891a7b0054bcc297fb02d44d05c50e68154e31788f2d9d41d0b72c89fdf7/pydantic_core-2.23.4-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:ed1a53de42fbe34853ba90513cea21673481cd81ed1be739f7f2efb931b24916", size = 1806469 }, + { url = "https://files.pythonhosted.org/packages/31/7c/6e3fa122075d78f277a8431c4c608f061881b76c2b7faca01d317ee39b5d/pydantic_core-2.23.4-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:cfdd16ab5e59fc31b5e906d1a3f666571abc367598e3e02c83403acabc092e07", size = 2002246 }, + { url = "https://files.pythonhosted.org/packages/ad/6f/22d5692b7ab63fc4acbc74de6ff61d185804a83160adba5e6cc6068e1128/pydantic_core-2.23.4-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:255a8ef062cbf6674450e668482456abac99a5583bbafb73f9ad469540a3a232", size = 2659404 }, + { url = "https://files.pythonhosted.org/packages/11/ac/1e647dc1121c028b691028fa61a4e7477e6aeb5132628fde41dd34c1671f/pydantic_core-2.23.4-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4a7cd62e831afe623fbb7aabbb4fe583212115b3ef38a9f6b71869ba644624a2", size = 2053940 }, + { url = "https://files.pythonhosted.org/packages/91/75/984740c17f12c3ce18b5a2fcc4bdceb785cce7df1511a4ce89bca17c7e2d/pydantic_core-2.23.4-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:f09e2ff1f17c2b51f2bc76d1cc33da96298f0a036a137f5440ab3ec5360b624f", size = 1921437 }, + { url = "https://files.pythonhosted.org/packages/a0/74/13c5f606b64d93f0721e7768cd3e8b2102164866c207b8cd6f90bb15d24f/pydantic_core-2.23.4-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:e38e63e6f3d1cec5a27e0afe90a085af8b6806ee208b33030e65b6516353f1a3", size = 1966129 }, + { url = "https://files.pythonhosted.org/packages/18/03/9c4aa5919457c7b57a016c1ab513b1a926ed9b2bb7915bf8e506bf65c34b/pydantic_core-2.23.4-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:0dbd8dbed2085ed23b5c04afa29d8fd2771674223135dc9bc937f3c09284d071", size = 2110908 }, + { url = "https://files.pythonhosted.org/packages/92/2c/053d33f029c5dc65e5cf44ff03ceeefb7cce908f8f3cca9265e7f9b540c8/pydantic_core-2.23.4-cp310-none-win32.whl", hash = "sha256:6531b7ca5f951d663c339002e91aaebda765ec7d61b7d1e3991051906ddde119", size = 1735278 }, + { url = "https://files.pythonhosted.org/packages/de/81/7dfe464eca78d76d31dd661b04b5f2036ec72ea8848dd87ab7375e185c23/pydantic_core-2.23.4-cp310-none-win_amd64.whl", hash = "sha256:7c9129eb40958b3d4500fa2467e6a83356b3b61bfff1b414c7361d9220f9ae8f", size = 1917453 }, + { url = "https://files.pythonhosted.org/packages/5d/30/890a583cd3f2be27ecf32b479d5d615710bb926d92da03e3f7838ff3e58b/pydantic_core-2.23.4-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:77733e3892bb0a7fa797826361ce8a9184d25c8dffaec60b7ffe928153680ba8", size = 1865160 }, + { url = "https://files.pythonhosted.org/packages/1d/9a/b634442e1253bc6889c87afe8bb59447f106ee042140bd57680b3b113ec7/pydantic_core-2.23.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:1b84d168f6c48fabd1f2027a3d1bdfe62f92cade1fb273a5d68e621da0e44e6d", size = 1776777 }, + { url = "https://files.pythonhosted.org/packages/75/9a/7816295124a6b08c24c96f9ce73085032d8bcbaf7e5a781cd41aa910c891/pydantic_core-2.23.4-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:df49e7a0861a8c36d089c1ed57d308623d60416dab2647a4a17fe050ba85de0e", size = 1799244 }, + { url = "https://files.pythonhosted.org/packages/a9/8f/89c1405176903e567c5f99ec53387449e62f1121894aa9fc2c4fdc51a59b/pydantic_core-2.23.4-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:ff02b6d461a6de369f07ec15e465a88895f3223eb75073ffea56b84d9331f607", size = 1805307 }, + { url = "https://files.pythonhosted.org/packages/d5/a5/1a194447d0da1ef492e3470680c66048fef56fc1f1a25cafbea4bc1d1c48/pydantic_core-2.23.4-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:996a38a83508c54c78a5f41456b0103c30508fed9abcad0a59b876d7398f25fd", size = 2000663 }, + { url = "https://files.pythonhosted.org/packages/13/a5/1df8541651de4455e7d587cf556201b4f7997191e110bca3b589218745a5/pydantic_core-2.23.4-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d97683ddee4723ae8c95d1eddac7c192e8c552da0c73a925a89fa8649bf13eea", size = 2655941 }, + { url = "https://files.pythonhosted.org/packages/44/31/a3899b5ce02c4316865e390107f145089876dff7e1dfc770a231d836aed8/pydantic_core-2.23.4-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:216f9b2d7713eb98cb83c80b9c794de1f6b7e3145eef40400c62e86cee5f4e1e", size = 2052105 }, + { url = "https://files.pythonhosted.org/packages/1b/aa/98e190f8745d5ec831f6d5449344c48c0627ac5fed4e5340a44b74878f8e/pydantic_core-2.23.4-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:6f783e0ec4803c787bcea93e13e9932edab72068f68ecffdf86a99fd5918878b", size = 1919967 }, + { url = "https://files.pythonhosted.org/packages/ae/35/b6e00b6abb2acfee3e8f85558c02a0822e9a8b2f2d812ea8b9079b118ba0/pydantic_core-2.23.4-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:d0776dea117cf5272382634bd2a5c1b6eb16767c223c6a5317cd3e2a757c61a0", size = 1964291 }, + { url = "https://files.pythonhosted.org/packages/13/46/7bee6d32b69191cd649bbbd2361af79c472d72cb29bb2024f0b6e350ba06/pydantic_core-2.23.4-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:d5f7a395a8cf1621939692dba2a6b6a830efa6b3cee787d82c7de1ad2930de64", size = 2109666 }, + { url = "https://files.pythonhosted.org/packages/39/ef/7b34f1b122a81b68ed0a7d0e564da9ccdc9a2924c8d6c6b5b11fa3a56970/pydantic_core-2.23.4-cp311-none-win32.whl", hash = "sha256:74b9127ffea03643e998e0c5ad9bd3811d3dac8c676e47db17b0ee7c3c3bf35f", size = 1732940 }, + { url = "https://files.pythonhosted.org/packages/2f/76/37b7e76c645843ff46c1d73e046207311ef298d3f7b2f7d8f6ac60113071/pydantic_core-2.23.4-cp311-none-win_amd64.whl", hash = "sha256:98d134c954828488b153d88ba1f34e14259284f256180ce659e8d83e9c05eaa3", size = 1916804 }, + { url = "https://files.pythonhosted.org/packages/74/7b/8e315f80666194b354966ec84b7d567da77ad927ed6323db4006cf915f3f/pydantic_core-2.23.4-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:f3e0da4ebaef65158d4dfd7d3678aad692f7666877df0002b8a522cdf088f231", size = 1856459 }, + { url = "https://files.pythonhosted.org/packages/14/de/866bdce10ed808323d437612aca1ec9971b981e1c52e5e42ad9b8e17a6f6/pydantic_core-2.23.4-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:f69a8e0b033b747bb3e36a44e7732f0c99f7edd5cea723d45bc0d6e95377ffee", size = 1770007 }, + { url = "https://files.pythonhosted.org/packages/dc/69/8edd5c3cd48bb833a3f7ef9b81d7666ccddd3c9a635225214e044b6e8281/pydantic_core-2.23.4-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:723314c1d51722ab28bfcd5240d858512ffd3116449c557a1336cbe3919beb87", size = 1790245 }, + { url = "https://files.pythonhosted.org/packages/80/33/9c24334e3af796ce80d2274940aae38dd4e5676298b4398eff103a79e02d/pydantic_core-2.23.4-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:bb2802e667b7051a1bebbfe93684841cc9351004e2badbd6411bf357ab8d5ac8", size = 1801260 }, + { url = "https://files.pythonhosted.org/packages/a5/6f/e9567fd90104b79b101ca9d120219644d3314962caa7948dd8b965e9f83e/pydantic_core-2.23.4-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d18ca8148bebe1b0a382a27a8ee60350091a6ddaf475fa05ef50dc35b5df6327", size = 1996872 }, + { url = "https://files.pythonhosted.org/packages/2d/ad/b5f0fe9e6cfee915dd144edbd10b6e9c9c9c9d7a56b69256d124b8ac682e/pydantic_core-2.23.4-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:33e3d65a85a2a4a0dc3b092b938a4062b1a05f3a9abde65ea93b233bca0e03f2", size = 2661617 }, + { url = "https://files.pythonhosted.org/packages/06/c8/7d4b708f8d05a5cbfda3243aad468052c6e99de7d0937c9146c24d9f12e9/pydantic_core-2.23.4-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:128585782e5bfa515c590ccee4b727fb76925dd04a98864182b22e89a4e6ed36", size = 2071831 }, + { url = "https://files.pythonhosted.org/packages/89/4d/3079d00c47f22c9a9a8220db088b309ad6e600a73d7a69473e3a8e5e3ea3/pydantic_core-2.23.4-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:68665f4c17edcceecc112dfed5dbe6f92261fb9d6054b47d01bf6371a6196126", size = 1917453 }, + { url = "https://files.pythonhosted.org/packages/e9/88/9df5b7ce880a4703fcc2d76c8c2d8eb9f861f79d0c56f4b8f5f2607ccec8/pydantic_core-2.23.4-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:20152074317d9bed6b7a95ade3b7d6054845d70584216160860425f4fbd5ee9e", size = 1968793 }, + { url = "https://files.pythonhosted.org/packages/e3/b9/41f7efe80f6ce2ed3ee3c2dcfe10ab7adc1172f778cc9659509a79518c43/pydantic_core-2.23.4-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:9261d3ce84fa1d38ed649c3638feefeae23d32ba9182963e465d58d62203bd24", size = 2116872 }, + { url = "https://files.pythonhosted.org/packages/63/08/b59b7a92e03dd25554b0436554bf23e7c29abae7cce4b1c459cd92746811/pydantic_core-2.23.4-cp312-none-win32.whl", hash = "sha256:4ba762ed58e8d68657fc1281e9bb72e1c3e79cc5d464be146e260c541ec12d84", size = 1738535 }, + { url = "https://files.pythonhosted.org/packages/88/8d/479293e4d39ab409747926eec4329de5b7129beaedc3786eca070605d07f/pydantic_core-2.23.4-cp312-none-win_amd64.whl", hash = "sha256:97df63000f4fea395b2824da80e169731088656d1818a11b95f3b173747b6cd9", size = 1917992 }, + { url = "https://files.pythonhosted.org/packages/13/a9/5d582eb3204464284611f636b55c0a7410d748ff338756323cb1ce721b96/pydantic_core-2.23.4-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:f455ee30a9d61d3e1a15abd5068827773d6e4dc513e795f380cdd59932c782d5", size = 1857135 }, + { url = "https://files.pythonhosted.org/packages/2c/57/faf36290933fe16717f97829eabfb1868182ac495f99cf0eda9f59687c9d/pydantic_core-2.23.4-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:1e90d2e3bd2c3863d48525d297cd143fe541be8bbf6f579504b9712cb6b643ec", size = 1740583 }, + { url = "https://files.pythonhosted.org/packages/91/7c/d99e3513dc191c4fec363aef1bf4c8af9125d8fa53af7cb97e8babef4e40/pydantic_core-2.23.4-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2e203fdf807ac7e12ab59ca2bfcabb38c7cf0b33c41efeb00f8e5da1d86af480", size = 1793637 }, + { url = "https://files.pythonhosted.org/packages/29/18/812222b6d18c2d13eebbb0f7cdc170a408d9ced65794fdb86147c77e1982/pydantic_core-2.23.4-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e08277a400de01bc72436a0ccd02bdf596631411f592ad985dcee21445bd0068", size = 1941963 }, + { url = "https://files.pythonhosted.org/packages/0f/36/c1f3642ac3f05e6bb4aec3ffc399fa3f84895d259cf5f0ce3054b7735c29/pydantic_core-2.23.4-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:f220b0eea5965dec25480b6333c788fb72ce5f9129e8759ef876a1d805d00801", size = 1915332 }, + { url = "https://files.pythonhosted.org/packages/f7/ca/9c0854829311fb446020ebb540ee22509731abad886d2859c855dd29b904/pydantic_core-2.23.4-pp310-pypy310_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:d06b0c8da4f16d1d1e352134427cb194a0a6e19ad5db9161bf32b2113409e728", size = 1957926 }, + { url = "https://files.pythonhosted.org/packages/c0/1c/7836b67c42d0cd4441fcd9fafbf6a027ad4b79b6559f80cf11f89fd83648/pydantic_core-2.23.4-pp310-pypy310_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:ba1a0996f6c2773bd83e63f18914c1de3c9dd26d55f4ac302a7efe93fb8e7433", size = 2100342 }, + { url = "https://files.pythonhosted.org/packages/a9/f9/b6bcaf874f410564a78908739c80861a171788ef4d4f76f5009656672dfe/pydantic_core-2.23.4-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:9a5bce9d23aac8f0cf0836ecfc033896aa8443b501c58d0602dbfd5bd5b37753", size = 1920344 }, ] [[package]] @@ -5039,19 +5008,19 @@ dependencies = [ { 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 = "nvidia-cublas-cu12", marker = "platform_machine == 'x86_64' and sys_platform == 'linux'" }, + { name = "nvidia-cuda-cupti-cu12", marker = "platform_machine == 'x86_64' and sys_platform == 'linux'" }, + { name = "nvidia-cuda-nvrtc-cu12", marker = "platform_machine == 'x86_64' and sys_platform == 'linux'" }, + { name = "nvidia-cuda-runtime-cu12", marker = "platform_machine == 'x86_64' and sys_platform == 'linux'" }, + { name = "nvidia-cudnn-cu12", marker = "platform_machine == 'x86_64' and sys_platform == 'linux'" }, + { name = "nvidia-cufft-cu12", marker = "platform_machine == 'x86_64' and sys_platform == 'linux'" }, + { name = "nvidia-curand-cu12", marker = "platform_machine == 'x86_64' and sys_platform == 'linux'" }, + { name = "nvidia-cusolver-cu12", marker = "platform_machine == 'x86_64' and sys_platform == 'linux'" }, + { name = "nvidia-cusparse-cu12", marker = "platform_machine == 'x86_64' and sys_platform == 'linux'" }, + { name = "nvidia-nccl-cu12", marker = "platform_machine == 'x86_64' and sys_platform == 'linux'" }, + { name = "nvidia-nvtx-cu12", marker = "platform_machine == 'x86_64' and sys_platform == 'linux'" }, { name = "sympy" }, - { name = "triton", marker = "platform_machine == 'x86_64' and platform_system == 'Linux'" }, + { name = "triton", marker = "platform_machine == 'x86_64' and sys_platform == 'linux'" }, { name = "typing-extensions" }, ] wheels = [ @@ -5098,7 +5067,7 @@ name = "tqdm" version = "4.66.5" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "colorama", marker = "platform_system == 'Windows'" }, + { name = "colorama", marker = "sys_platform == 'win32'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/58/83/6ba9844a41128c62e810fddddd72473201f3eacde02046066142a2d96cc5/tqdm-4.66.5.tar.gz", hash = "sha256:e1020aef2e5096702d8a025ac7d16b1577279c9d63f8375b63083e9a5f0fcbad", size = 169504 } wheels = [ @@ -5140,7 +5109,7 @@ 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' and sys_platform != 'linux')" }, + { name = "filelock", marker = "(platform_machine != 'aarch64' and sys_platform == 'linux') or (sys_platform != 'darwin' and sys_platform != '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 }, @@ -5535,64 +5504,64 @@ wheels = [ [[package]] name = "yarl" -version = "1.18.3" +version = "1.16.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "idna" }, { name = "multidict" }, { name = "propcache" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/b7/9d/4b94a8e6d2b51b599516a5cb88e5bc99b4d8d4583e468057eaa29d5f0918/yarl-1.18.3.tar.gz", hash = "sha256:ac1801c45cbf77b6c99242eeff4fffb5e4e73a800b5c4ad4fc0be5def634d2e1", size = 181062 } +sdist = { url = "https://files.pythonhosted.org/packages/23/52/e9766cc6c2eab7dd1e9749c52c9879317500b46fb97d4105223f86679f93/yarl-1.16.0.tar.gz", hash = "sha256:b6f687ced5510a9a2474bbae96a4352e5ace5fa34dc44a217b0537fec1db00b4", size = 176548 } wheels = [ - { url = "https://files.pythonhosted.org/packages/d2/98/e005bc608765a8a5569f58e650961314873c8469c333616eb40bff19ae97/yarl-1.18.3-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:7df647e8edd71f000a5208fe6ff8c382a1de8edfbccdbbfe649d263de07d8c34", size = 141458 }, - { url = "https://files.pythonhosted.org/packages/df/5d/f8106b263b8ae8a866b46d9be869ac01f9b3fb7f2325f3ecb3df8003f796/yarl-1.18.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:c69697d3adff5aa4f874b19c0e4ed65180ceed6318ec856ebc423aa5850d84f7", size = 94365 }, - { url = "https://files.pythonhosted.org/packages/56/3e/d8637ddb9ba69bf851f765a3ee288676f7cf64fb3be13760c18cbc9d10bd/yarl-1.18.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:602d98f2c2d929f8e697ed274fbadc09902c4025c5a9963bf4e9edfc3ab6f7ed", size = 92181 }, - { url = "https://files.pythonhosted.org/packages/76/f9/d616a5c2daae281171de10fba41e1c0e2d8207166fc3547252f7d469b4e1/yarl-1.18.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c654d5207c78e0bd6d749f6dae1dcbbfde3403ad3a4b11f3c5544d9906969dde", size = 315349 }, - { url = "https://files.pythonhosted.org/packages/bb/b4/3ea5e7b6f08f698b3769a06054783e434f6d59857181b5c4e145de83f59b/yarl-1.18.3-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5094d9206c64181d0f6e76ebd8fb2f8fe274950a63890ee9e0ebfd58bf9d787b", size = 330494 }, - { url = "https://files.pythonhosted.org/packages/55/f1/e0fc810554877b1b67420568afff51b967baed5b53bcc983ab164eebf9c9/yarl-1.18.3-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:35098b24e0327fc4ebdc8ffe336cee0a87a700c24ffed13161af80124b7dc8e5", size = 326927 }, - { url = "https://files.pythonhosted.org/packages/a9/42/b1753949b327b36f210899f2dd0a0947c0c74e42a32de3f8eb5c7d93edca/yarl-1.18.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3236da9272872443f81fedc389bace88408f64f89f75d1bdb2256069a8730ccc", size = 319703 }, - { url = "https://files.pythonhosted.org/packages/f0/6d/e87c62dc9635daefb064b56f5c97df55a2e9cc947a2b3afd4fd2f3b841c7/yarl-1.18.3-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e2c08cc9b16f4f4bc522771d96734c7901e7ebef70c6c5c35dd0f10845270bcd", size = 310246 }, - { url = "https://files.pythonhosted.org/packages/e3/ef/e2e8d1785cdcbd986f7622d7f0098205f3644546da7919c24b95790ec65a/yarl-1.18.3-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:80316a8bd5109320d38eef8833ccf5f89608c9107d02d2a7f985f98ed6876990", size = 319730 }, - { url = "https://files.pythonhosted.org/packages/fc/15/8723e22345bc160dfde68c4b3ae8b236e868f9963c74015f1bc8a614101c/yarl-1.18.3-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:c1e1cc06da1491e6734f0ea1e6294ce00792193c463350626571c287c9a704db", size = 321681 }, - { url = "https://files.pythonhosted.org/packages/86/09/bf764e974f1516efa0ae2801494a5951e959f1610dd41edbfc07e5e0f978/yarl-1.18.3-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:fea09ca13323376a2fdfb353a5fa2e59f90cd18d7ca4eaa1fd31f0a8b4f91e62", size = 324812 }, - { url = "https://files.pythonhosted.org/packages/f6/4c/20a0187e3b903c97d857cf0272d687c1b08b03438968ae8ffc50fe78b0d6/yarl-1.18.3-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:e3b9fd71836999aad54084906f8663dffcd2a7fb5cdafd6c37713b2e72be1760", size = 337011 }, - { url = "https://files.pythonhosted.org/packages/c9/71/6244599a6e1cc4c9f73254a627234e0dad3883ece40cc33dce6265977461/yarl-1.18.3-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:757e81cae69244257d125ff31663249b3013b5dc0a8520d73694aed497fb195b", size = 338132 }, - { url = "https://files.pythonhosted.org/packages/af/f5/e0c3efaf74566c4b4a41cb76d27097df424052a064216beccae8d303c90f/yarl-1.18.3-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:b1771de9944d875f1b98a745bc547e684b863abf8f8287da8466cf470ef52690", size = 331849 }, - { url = "https://files.pythonhosted.org/packages/8a/b8/3d16209c2014c2f98a8f658850a57b716efb97930aebf1ca0d9325933731/yarl-1.18.3-cp310-cp310-win32.whl", hash = "sha256:8874027a53e3aea659a6d62751800cf6e63314c160fd607489ba5c2edd753cf6", size = 84309 }, - { url = "https://files.pythonhosted.org/packages/fd/b7/2e9a5b18eb0fe24c3a0e8bae994e812ed9852ab4fd067c0107fadde0d5f0/yarl-1.18.3-cp310-cp310-win_amd64.whl", hash = "sha256:93b2e109287f93db79210f86deb6b9bbb81ac32fc97236b16f7433db7fc437d8", size = 90484 }, - { url = "https://files.pythonhosted.org/packages/40/93/282b5f4898d8e8efaf0790ba6d10e2245d2c9f30e199d1a85cae9356098c/yarl-1.18.3-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:8503ad47387b8ebd39cbbbdf0bf113e17330ffd339ba1144074da24c545f0069", size = 141555 }, - { url = "https://files.pythonhosted.org/packages/6d/9c/0a49af78df099c283ca3444560f10718fadb8a18dc8b3edf8c7bd9fd7d89/yarl-1.18.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:02ddb6756f8f4517a2d5e99d8b2f272488e18dd0bfbc802f31c16c6c20f22193", size = 94351 }, - { url = "https://files.pythonhosted.org/packages/5a/a1/205ab51e148fdcedad189ca8dd587794c6f119882437d04c33c01a75dece/yarl-1.18.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:67a283dd2882ac98cc6318384f565bffc751ab564605959df4752d42483ad889", size = 92286 }, - { url = "https://files.pythonhosted.org/packages/ed/fe/88b690b30f3f59275fb674f5f93ddd4a3ae796c2b62e5bb9ece8a4914b83/yarl-1.18.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d980e0325b6eddc81331d3f4551e2a333999fb176fd153e075c6d1c2530aa8a8", size = 340649 }, - { url = "https://files.pythonhosted.org/packages/07/eb/3b65499b568e01f36e847cebdc8d7ccb51fff716dbda1ae83c3cbb8ca1c9/yarl-1.18.3-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b643562c12680b01e17239be267bc306bbc6aac1f34f6444d1bded0c5ce438ca", size = 356623 }, - { url = "https://files.pythonhosted.org/packages/33/46/f559dc184280b745fc76ec6b1954de2c55595f0ec0a7614238b9ebf69618/yarl-1.18.3-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c017a3b6df3a1bd45b9fa49a0f54005e53fbcad16633870104b66fa1a30a29d8", size = 354007 }, - { url = "https://files.pythonhosted.org/packages/af/ba/1865d85212351ad160f19fb99808acf23aab9a0f8ff31c8c9f1b4d671fc9/yarl-1.18.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:75674776d96d7b851b6498f17824ba17849d790a44d282929c42dbb77d4f17ae", size = 344145 }, - { url = "https://files.pythonhosted.org/packages/94/cb/5c3e975d77755d7b3d5193e92056b19d83752ea2da7ab394e22260a7b824/yarl-1.18.3-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ccaa3a4b521b780a7e771cc336a2dba389a0861592bbce09a476190bb0c8b4b3", size = 336133 }, - { url = "https://files.pythonhosted.org/packages/19/89/b77d3fd249ab52a5c40859815765d35c91425b6bb82e7427ab2f78f5ff55/yarl-1.18.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:2d06d3005e668744e11ed80812e61efd77d70bb7f03e33c1598c301eea20efbb", size = 347967 }, - { url = "https://files.pythonhosted.org/packages/35/bd/f6b7630ba2cc06c319c3235634c582a6ab014d52311e7d7c22f9518189b5/yarl-1.18.3-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:9d41beda9dc97ca9ab0b9888cb71f7539124bc05df02c0cff6e5acc5a19dcc6e", size = 346397 }, - { url = "https://files.pythonhosted.org/packages/18/1a/0b4e367d5a72d1f095318344848e93ea70da728118221f84f1bf6c1e39e7/yarl-1.18.3-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:ba23302c0c61a9999784e73809427c9dbedd79f66a13d84ad1b1943802eaaf59", size = 350206 }, - { url = "https://files.pythonhosted.org/packages/b5/cf/320fff4367341fb77809a2d8d7fe75b5d323a8e1b35710aafe41fdbf327b/yarl-1.18.3-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:6748dbf9bfa5ba1afcc7556b71cda0d7ce5f24768043a02a58846e4a443d808d", size = 362089 }, - { url = "https://files.pythonhosted.org/packages/57/cf/aadba261d8b920253204085268bad5e8cdd86b50162fcb1b10c10834885a/yarl-1.18.3-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:0b0cad37311123211dc91eadcb322ef4d4a66008d3e1bdc404808992260e1a0e", size = 366267 }, - { url = "https://files.pythonhosted.org/packages/54/58/fb4cadd81acdee6dafe14abeb258f876e4dd410518099ae9a35c88d8097c/yarl-1.18.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:0fb2171a4486bb075316ee754c6d8382ea6eb8b399d4ec62fde2b591f879778a", size = 359141 }, - { url = "https://files.pythonhosted.org/packages/9a/7a/4c571597589da4cd5c14ed2a0b17ac56ec9ee7ee615013f74653169e702d/yarl-1.18.3-cp311-cp311-win32.whl", hash = "sha256:61b1a825a13bef4a5f10b1885245377d3cd0bf87cba068e1d9a88c2ae36880e1", size = 84402 }, - { url = "https://files.pythonhosted.org/packages/ae/7b/8600250b3d89b625f1121d897062f629883c2f45339623b69b1747ec65fa/yarl-1.18.3-cp311-cp311-win_amd64.whl", hash = "sha256:b9d60031cf568c627d028239693fd718025719c02c9f55df0a53e587aab951b5", size = 91030 }, - { url = "https://files.pythonhosted.org/packages/33/85/bd2e2729752ff4c77338e0102914897512e92496375e079ce0150a6dc306/yarl-1.18.3-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:1dd4bdd05407ced96fed3d7f25dbbf88d2ffb045a0db60dbc247f5b3c5c25d50", size = 142644 }, - { url = "https://files.pythonhosted.org/packages/ff/74/1178322cc0f10288d7eefa6e4a85d8d2e28187ccab13d5b844e8b5d7c88d/yarl-1.18.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:7c33dd1931a95e5d9a772d0ac5e44cac8957eaf58e3c8da8c1414de7dd27c576", size = 94962 }, - { url = "https://files.pythonhosted.org/packages/be/75/79c6acc0261e2c2ae8a1c41cf12265e91628c8c58ae91f5ff59e29c0787f/yarl-1.18.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:25b411eddcfd56a2f0cd6a384e9f4f7aa3efee14b188de13048c25b5e91f1640", size = 92795 }, - { url = "https://files.pythonhosted.org/packages/6b/32/927b2d67a412c31199e83fefdce6e645247b4fb164aa1ecb35a0f9eb2058/yarl-1.18.3-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:436c4fc0a4d66b2badc6c5fc5ef4e47bb10e4fd9bf0c79524ac719a01f3607c2", size = 332368 }, - { url = "https://files.pythonhosted.org/packages/19/e5/859fca07169d6eceeaa4fde1997c91d8abde4e9a7c018e371640c2da2b71/yarl-1.18.3-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e35ef8683211db69ffe129a25d5634319a677570ab6b2eba4afa860f54eeaf75", size = 342314 }, - { url = "https://files.pythonhosted.org/packages/08/75/76b63ccd91c9e03ab213ef27ae6add2e3400e77e5cdddf8ed2dbc36e3f21/yarl-1.18.3-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:84b2deecba4a3f1a398df819151eb72d29bfeb3b69abb145a00ddc8d30094512", size = 341987 }, - { url = "https://files.pythonhosted.org/packages/1a/e1/a097d5755d3ea8479a42856f51d97eeff7a3a7160593332d98f2709b3580/yarl-1.18.3-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:00e5a1fea0fd4f5bfa7440a47eff01d9822a65b4488f7cff83155a0f31a2ecba", size = 336914 }, - { url = "https://files.pythonhosted.org/packages/0b/42/e1b4d0e396b7987feceebe565286c27bc085bf07d61a59508cdaf2d45e63/yarl-1.18.3-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d0e883008013c0e4aef84dcfe2a0b172c4d23c2669412cf5b3371003941f72bb", size = 325765 }, - { url = "https://files.pythonhosted.org/packages/7e/18/03a5834ccc9177f97ca1bbb245b93c13e58e8225276f01eedc4cc98ab820/yarl-1.18.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:5a3f356548e34a70b0172d8890006c37be92995f62d95a07b4a42e90fba54272", size = 344444 }, - { url = "https://files.pythonhosted.org/packages/c8/03/a713633bdde0640b0472aa197b5b86e90fbc4c5bc05b727b714cd8a40e6d/yarl-1.18.3-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:ccd17349166b1bee6e529b4add61727d3f55edb7babbe4069b5764c9587a8cc6", size = 340760 }, - { url = "https://files.pythonhosted.org/packages/eb/99/f6567e3f3bbad8fd101886ea0276c68ecb86a2b58be0f64077396cd4b95e/yarl-1.18.3-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:b958ddd075ddba5b09bb0be8a6d9906d2ce933aee81100db289badbeb966f54e", size = 346484 }, - { url = "https://files.pythonhosted.org/packages/8e/a9/84717c896b2fc6cb15bd4eecd64e34a2f0a9fd6669e69170c73a8b46795a/yarl-1.18.3-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:c7d79f7d9aabd6011004e33b22bc13056a3e3fb54794d138af57f5ee9d9032cb", size = 359864 }, - { url = "https://files.pythonhosted.org/packages/1e/2e/d0f5f1bef7ee93ed17e739ec8dbcb47794af891f7d165fa6014517b48169/yarl-1.18.3-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:4891ed92157e5430874dad17b15eb1fda57627710756c27422200c52d8a4e393", size = 364537 }, - { url = "https://files.pythonhosted.org/packages/97/8a/568d07c5d4964da5b02621a517532adb8ec5ba181ad1687191fffeda0ab6/yarl-1.18.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:ce1af883b94304f493698b00d0f006d56aea98aeb49d75ec7d98cd4a777e9285", size = 357861 }, - { url = "https://files.pythonhosted.org/packages/7d/e3/924c3f64b6b3077889df9a1ece1ed8947e7b61b0a933f2ec93041990a677/yarl-1.18.3-cp312-cp312-win32.whl", hash = "sha256:f91c4803173928a25e1a55b943c81f55b8872f0018be83e3ad4938adffb77dd2", size = 84097 }, - { url = "https://files.pythonhosted.org/packages/34/45/0e055320daaabfc169b21ff6174567b2c910c45617b0d79c68d7ab349b02/yarl-1.18.3-cp312-cp312-win_amd64.whl", hash = "sha256:7e2ee16578af3b52ac2f334c3b1f92262f47e02cc6193c598502bd46f5cd1477", size = 90399 }, - { url = "https://files.pythonhosted.org/packages/f5/4b/a06e0ec3d155924f77835ed2d167ebd3b211a7b0853da1cf8d8414d784ef/yarl-1.18.3-py3-none-any.whl", hash = "sha256:b57f4f58099328dfb26c6a771d09fb20dbbae81d20cfb66141251ea063bd101b", size = 45109 }, + { url = "https://files.pythonhosted.org/packages/df/30/00b17348655202e4bd24f8d79cd062888e5d3bdbf2ba726615c5d21b54a5/yarl-1.16.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:32468f41242d72b87ab793a86d92f885355bcf35b3355aa650bfa846a5c60058", size = 140016 }, + { url = "https://files.pythonhosted.org/packages/a5/15/9b7b85b72b81f180689257b2bb6e54d5d0764a399679aa06d5dec8ca6e2e/yarl-1.16.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:234f3a3032b505b90e65b5bc6652c2329ea7ea8855d8de61e1642b74b4ee65d2", size = 92953 }, + { url = "https://files.pythonhosted.org/packages/31/41/91848bbb76789336d3b786ff144030001b5027b17729b3afa32da668f5b0/yarl-1.16.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:8a0296040e5cddf074c7f5af4a60f3fc42c0237440df7bcf5183be5f6c802ed5", size = 90793 }, + { url = "https://files.pythonhosted.org/packages/6c/99/f1ada764e350ab054e14902f3f68589a7d77469ac47fbc512aa1a78a2f35/yarl-1.16.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:de6c14dd7c7c0badba48157474ea1f03ebee991530ba742d381b28d4f314d6f3", size = 313155 }, + { url = "https://files.pythonhosted.org/packages/75/fd/998ccdb489ca97d9073d882265203a2fae4c5bff30eb9b8a0bbbed7aef2b/yarl-1.16.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b140e532fe0266003c936d017c1ac301e72ee4a3fd51784574c05f53718a55d8", size = 328624 }, + { url = "https://files.pythonhosted.org/packages/2d/5d/395bbae1f509f64e6d26b7ffffff178d70c5480f15af735dfb0afb8f0dc5/yarl-1.16.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:019f5d58093402aa8f6661e60fd82a28746ad6d156f6c5336a70a39bd7b162b9", size = 325163 }, + { url = "https://files.pythonhosted.org/packages/1d/25/65601d336189d122483f5ff0276b08278fa4778f833458cfcac5c6eddc87/yarl-1.16.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8c42998fd1cbeb53cd985bff0e4bc25fbe55fd6eb3a545a724c1012d69d5ec84", size = 318076 }, + { url = "https://files.pythonhosted.org/packages/50/bb/0c9692ec457c1ed023654a9fba6d0c69a20c79b56275d972f6a24ab18547/yarl-1.16.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7c7c30fb38c300fe8140df30a046a01769105e4cf4282567a29b5cdb635b66c4", size = 309551 }, + { url = "https://files.pythonhosted.org/packages/a5/2f/d0ced2050a203241a3f2e05c5bb86038b071f216897defd824dd85333f9e/yarl-1.16.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:e49e0fd86c295e743fd5be69b8b0712f70a686bc79a16e5268386c2defacaade", size = 317678 }, + { url = "https://files.pythonhosted.org/packages/46/93/b7359aa2bd0567eca72491cd20059744ed6ee00f08cd58c861243f656a90/yarl-1.16.0-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:b9ca7b9147eb1365c8bab03c003baa1300599575effad765e0b07dd3501ea9af", size = 317003 }, + { url = "https://files.pythonhosted.org/packages/87/18/77ef4d45d19ecafad0f7c07d5cf13a757a90122383494bc5a3e8ee68e2f2/yarl-1.16.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:27e11db3f1e6a51081a981509f75617b09810529de508a181319193d320bc5c7", size = 322795 }, + { url = "https://files.pythonhosted.org/packages/28/a9/b38880bf79665d1c8a3d4c09d6f7a686a50f8c74caf07603a2b8e5314038/yarl-1.16.0-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:8994c42f4ca25df5380ddf59f315c518c81df6a68fed5bb0c159c6cb6b92f120", size = 337022 }, + { url = "https://files.pythonhosted.org/packages/e9/79/865788b297fc17117e3ff6ea74d5f864185085d61adc3364444732095254/yarl-1.16.0-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:542fa8e09a581bcdcbb30607c7224beff3fdfb598c798ccd28a8184ffc18b7eb", size = 338357 }, + { url = "https://files.pythonhosted.org/packages/bd/5e/c5cba528448f73c7035c9d3c07261b54312d8caa8372eeeff5e1f07e43ec/yarl-1.16.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:2bd6a51010c7284d191b79d3b56e51a87d8e1c03b0902362945f15c3d50ed46b", size = 330470 }, + { url = "https://files.pythonhosted.org/packages/1a/e4/90757595d81ec328ad94afa62d0724903a6c72b76e0ee9c9af9d8a399dd2/yarl-1.16.0-cp310-cp310-win32.whl", hash = "sha256:178ccb856e265174a79f59721031060f885aca428983e75c06f78aa24b91d929", size = 82967 }, + { url = "https://files.pythonhosted.org/packages/01/5a/b82ec5e7557b0d938b9475cbb5dcbb1f98c8601101188d79e423dc215cd0/yarl-1.16.0-cp310-cp310-win_amd64.whl", hash = "sha256:fe8bba2545427418efc1929c5c42852bdb4143eb8d0a46b09de88d1fe99258e7", size = 89159 }, + { url = "https://files.pythonhosted.org/packages/0a/00/b29affe83de95e403f8a2a669b5a33f1e7dfe686264008100052eb0b05fd/yarl-1.16.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:d8643975a0080f361639787415a038bfc32d29208a4bf6b783ab3075a20b1ef3", size = 140120 }, + { url = "https://files.pythonhosted.org/packages/3f/22/bcc9799950281a5d4f646536854839ccdbb965e900827ef0750680f81faf/yarl-1.16.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:676d96bafc8c2d0039cea0cd3fd44cee7aa88b8185551a2bb93354668e8315c2", size = 92956 }, + { url = "https://files.pythonhosted.org/packages/33/0f/1b76d853d9d921d68bd9991648be17d34e7ac51e2e20e7658f8ee7e2e2ad/yarl-1.16.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:d9525f03269e64310416dbe6c68d3b23e5d34aaa8f47193a1c45ac568cecbc49", size = 90891 }, + { url = "https://files.pythonhosted.org/packages/61/19/3666d990c24aae98c748e2c262adc9b3a71e38834df007ac5317f4bbd789/yarl-1.16.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8b37d5ec034e668b22cf0ce1074d6c21fd2a08b90d11b1b73139b750a8b0dd97", size = 338857 }, + { url = "https://files.pythonhosted.org/packages/a0/3d/54acbb3cdfcfea03d6a3535cff1e060a2de23e419a4e3955c9661171b8a8/yarl-1.16.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4f32c4cb7386b41936894685f6e093c8dfaf0960124d91fe0ec29fe439e201d0", size = 354005 }, + { url = "https://files.pythonhosted.org/packages/15/98/cd9fe3938422c88775c94578a6c145aca89ff8368ff64e6032213ac12403/yarl-1.16.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5b8e265a0545637492a7e12fd7038370d66c9375a61d88c5567d0e044ded9202", size = 351195 }, + { url = "https://files.pythonhosted.org/packages/e2/13/b6eff6ea1667aee948ecd6b1c8fb6473234f8e48f49af97be93251869c51/yarl-1.16.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:789a3423f28a5fff46fbd04e339863c169ece97c827b44de16e1a7a42bc915d2", size = 342789 }, + { url = "https://files.pythonhosted.org/packages/fe/05/d98e65ea74a7e44bb033b2cf5bcc16edc1d5212bdc5ca7fbb5e380d89f8e/yarl-1.16.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f1d1f45e3e8d37c804dca99ab3cf4ab3ed2e7a62cd82542924b14c0a4f46d243", size = 336478 }, + { url = "https://files.pythonhosted.org/packages/7d/47/43de2e94b75f36d84733a35c807d0e33aaf084e98f32e2cbc685102f4ba4/yarl-1.16.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:621280719c4c5dad4c1391160a9b88925bb8b0ff6a7d5af3224643024871675f", size = 346008 }, + { url = "https://files.pythonhosted.org/packages/e2/de/9c2f900ec5e2f2e20329cfe7dcd9452e326d08cb5ecd098c2d4e9987b65c/yarl-1.16.0-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:ed097b26f18a1f5ff05f661dc36528c5f6735ba4ce8c9645e83b064665131349", size = 343745 }, + { url = "https://files.pythonhosted.org/packages/56/cd/b014dce22e37b77caa37f998c6c47434fd78d01e7be07119629f369f5ee1/yarl-1.16.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:2f1fe2b2e3ee418862f5ebc0c0083c97f6f6625781382f828f6d4e9b614eba9b", size = 349705 }, + { url = "https://files.pythonhosted.org/packages/07/17/bb191a26f7189423964e008ccb5146ce5258454ef3979f9d4c6860d282c7/yarl-1.16.0-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:87dd10bc0618991c66cee0cc65fa74a45f4ecb13bceec3c62d78ad2e42b27a16", size = 360767 }, + { url = "https://files.pythonhosted.org/packages/19/09/7d777369e151991b708a5b35280ea7444621d65af5f0545bcdce5d840867/yarl-1.16.0-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:4199db024b58a8abb2cfcedac7b1292c3ad421684571aeb622a02f242280e8d6", size = 364755 }, + { url = "https://files.pythonhosted.org/packages/00/32/7558997d1d2e53dab15f6db5db49fc6b412b63ede3cb8314e5dd7cff14fe/yarl-1.16.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:99a9dcd4b71dd5f5f949737ab3f356cfc058c709b4f49833aeffedc2652dac56", size = 357087 }, + { url = "https://files.pythonhosted.org/packages/28/20/c49a95a30c57224e5fb0fc83235295684b041300ce508b71821cb042527d/yarl-1.16.0-cp311-cp311-win32.whl", hash = "sha256:a9394c65ae0ed95679717d391c862dece9afacd8fa311683fc8b4362ce8a410c", size = 83030 }, + { url = "https://files.pythonhosted.org/packages/75/e3/2a746721d6f32886d9bafccdb80174349f180ccae0a287f25ba4312a2618/yarl-1.16.0-cp311-cp311-win_amd64.whl", hash = "sha256:5b9101f528ae0f8f65ac9d64dda2bb0627de8a50344b2f582779f32fda747c1d", size = 89616 }, + { url = "https://files.pythonhosted.org/packages/3a/be/82f696c8ce0395c37f62b955202368086e5cc114d5bb9cb1b634cff5e01d/yarl-1.16.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:4ffb7c129707dd76ced0a4a4128ff452cecf0b0e929f2668ea05a371d9e5c104", size = 141230 }, + { url = "https://files.pythonhosted.org/packages/38/60/45caaa748b53c4b0964f899879fcddc41faa4e0d12c6f0ae3311e8c151ff/yarl-1.16.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:1a5e9d8ce1185723419c487758d81ac2bde693711947032cce600ca7c9cda7d6", size = 93515 }, + { url = "https://files.pythonhosted.org/packages/54/bd/33aaca2f824dc1d630729e16e313797e8b24c8f7b6803307e5394274e443/yarl-1.16.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:d743e3118b2640cef7768ea955378c3536482d95550222f908f392167fe62059", size = 91441 }, + { url = "https://files.pythonhosted.org/packages/af/fa/1ce8ca85489925aabdb8d2e7bbeaf74e7d3e6ac069779d6d6b9c7c62a8ed/yarl-1.16.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:26768342f256e6e3c37533bf9433f5f15f3e59e3c14b2409098291b3efaceacb", size = 330871 }, + { url = "https://files.pythonhosted.org/packages/f1/2a/a8110a225e498b87315827f8b61d24de35f86041834cf8c9c5544380c46b/yarl-1.16.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d1b0796168b953bca6600c5f97f5ed407479889a36ad7d17183366260f29a6b9", size = 340641 }, + { url = "https://files.pythonhosted.org/packages/d0/64/20cd1cb1f60b3ff49e7d75c1a2083352e7c5939368aafa960712c9e53797/yarl-1.16.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:858728086914f3a407aa7979cab743bbda1fe2bdf39ffcd991469a370dd7414d", size = 340245 }, + { url = "https://files.pythonhosted.org/packages/77/a8/7f38bbefb22eb925a68ad1d8193b05f51515614a6c0ebcadf26e9ae5e5ad/yarl-1.16.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5570e6d47bcb03215baf4c9ad7bf7c013e56285d9d35013541f9ac2b372593e7", size = 336054 }, + { url = "https://files.pythonhosted.org/packages/b4/a6/ac633ea3ea0c4eb1057e6800db1d077e77493b4b3449a4a97b2fbefadef4/yarl-1.16.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:66ea8311422a7ba1fc79b4c42c2baa10566469fe5a78500d4e7754d6e6db8724", size = 324405 }, + { url = "https://files.pythonhosted.org/packages/93/cd/4fc87ce9b0df7afb610ffb904f4aef25f59e0ad40a49da19a475facf98b7/yarl-1.16.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:649bddcedee692ee8a9b7b6e38582cb4062dc4253de9711568e5620d8707c2a3", size = 342235 }, + { url = "https://files.pythonhosted.org/packages/9f/bc/38bae4b716da1206849d88e167d3d2c5695ae9b418a3915220947593e5ca/yarl-1.16.0-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:3a91654adb7643cb21b46f04244c5a315a440dcad63213033826549fa2435f71", size = 340835 }, + { url = "https://files.pythonhosted.org/packages/dc/0f/b9efbc0075916a450cbad41299dff3bdd3393cb1d8378bb831c4a6a836e1/yarl-1.16.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:b439cae82034ade094526a8f692b9a2b5ee936452de5e4c5f0f6c48df23f8604", size = 344323 }, + { url = "https://files.pythonhosted.org/packages/87/6d/dc483ea1574005f14ef4c5f5f726cf60327b07ac83bd417d98db23e5285f/yarl-1.16.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:571f781ae8ac463ce30bacebfaef2c6581543776d5970b2372fbe31d7bf31a07", size = 355112 }, + { url = "https://files.pythonhosted.org/packages/10/22/3b7c3728d26b3cc295c51160ae4e2612ab7d3f9df30beece44bf72861730/yarl-1.16.0-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:aa7943f04f36d6cafc0cf53ea89824ac2c37acbdb4b316a654176ab8ffd0f968", size = 361506 }, + { url = "https://files.pythonhosted.org/packages/ad/8d/b7b5d43cf22a020b564ddf7502d83df150d797e34f18f6bf5fe0f12cbd91/yarl-1.16.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:1a5cf32539373ff39d97723e39a9283a7277cbf1224f7aef0c56c9598b6486c3", size = 355746 }, + { url = "https://files.pythonhosted.org/packages/d9/a6/a2098bf3f09d38eb540b2b192e180d9d41c2ff64b692783db2188f0a55e3/yarl-1.16.0-cp312-cp312-win32.whl", hash = "sha256:a5b6c09b9b4253d6a208b0f4a2f9206e511ec68dce9198e0fbec4f160137aa67", size = 82675 }, + { url = "https://files.pythonhosted.org/packages/ed/a6/0a54b382cfc336e772b72681d6816a99222dc2d21876e649474973b8d244/yarl-1.16.0-cp312-cp312-win_amd64.whl", hash = "sha256:1208ca14eed2fda324042adf8d6c0adf4a31522fa95e0929027cd487875f0240", size = 88986 }, + { url = "https://files.pythonhosted.org/packages/fb/f7/87a32867ddc1a9817018bfd6109ee57646a543acf0d272843d8393e575f9/yarl-1.16.0-py3-none-any.whl", hash = "sha256:e6980a558d8461230c457218bd6c92dfc1d10205548215c2c21d79dc8d0a96f3", size = 43746 }, ] [[package]] From df25703cc25356f76f3d1db2344474ae2917fbc9 Mon Sep 17 00:00:00 2001 From: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> Date: Wed, 12 Mar 2025 17:05:01 +0000 Subject: [PATCH 12/14] Address PR review: Add constants, IPv4 validation, error handling, and expanded tests Co-Authored-By: Joe Moura --- src/crewai/agent.py | 9 ++++-- src/crewai/utilities/__init__.py | 3 +- src/crewai/utilities/string_utils.py | 40 +++++++++++++++++++++----- tests/utilities/test_string_utils.py | 42 ++++++++++++++++++++++++++-- 4 files changed, 81 insertions(+), 13 deletions(-) diff --git a/src/crewai/agent.py b/src/crewai/agent.py index b92a83d14..d8b6860e3 100644 --- a/src/crewai/agent.py +++ b/src/crewai/agent.py @@ -142,8 +142,13 @@ class Agent(BaseAgent): self.embedder = crew_embedder if self.knowledge_sources: - from crewai.utilities import sanitize_collection_name - knowledge_agent_name = sanitize_collection_name(self.role) + try: + from crewai.utilities import sanitize_collection_name + knowledge_agent_name = sanitize_collection_name(self.role) + except Exception as e: + self._logger.warning(f"Error sanitizing collection name: {e}") + knowledge_agent_name = "default_agent" + if isinstance(self.knowledge_sources, list) and all( isinstance(k, BaseKnowledgeSource) for k in self.knowledge_sources ): diff --git a/src/crewai/utilities/__init__.py b/src/crewai/utilities/__init__.py index f2badd2d4..946c4390a 100644 --- a/src/crewai/utilities/__init__.py +++ b/src/crewai/utilities/__init__.py @@ -7,7 +7,7 @@ from .parser import YamlParser from .printer import Printer from .prompts import Prompts from .rpm_controller import RPMController -from .string_utils import sanitize_collection_name +from .string_utils import sanitize_collection_name, is_ipv4_pattern from .exceptions.context_window_exceeding_exception import ( LLMContextLengthExceededException, ) @@ -27,4 +27,5 @@ __all__ = [ "LLMContextLengthExceededException", "EmbeddingConfigurator", "sanitize_collection_name", + "is_ipv4_pattern", ] diff --git a/src/crewai/utilities/string_utils.py b/src/crewai/utilities/string_utils.py index 6da07b20d..05a637383 100644 --- a/src/crewai/utilities/string_utils.py +++ b/src/crewai/utilities/string_utils.py @@ -84,6 +84,28 @@ def interpolate_only( from typing import Optional +# Constants for ChromaDB collection name requirements +MIN_LENGTH = 3 +MAX_LENGTH = 63 +DEFAULT_COLLECTION = "default_collection" + +# Compiled regex patterns for better performance +INVALID_CHARS_PATTERN = re.compile(r"[^a-zA-Z0-9_-]") +IPV4_PATTERN = re.compile(r"^(\d{1,3}\.){3}\d{1,3}$") + + +def is_ipv4_pattern(name: str) -> bool: + """ + Check if a string matches an IPv4 address pattern. + + Args: + name: The string to check + + Returns: + True if the string matches an IPv4 pattern, False otherwise + """ + return bool(IPV4_PATTERN.match(name)) + def sanitize_collection_name(name: Optional[str]) -> str: """ @@ -101,10 +123,14 @@ def sanitize_collection_name(name: Optional[str]) -> str: A sanitized collection name that meets ChromaDB requirements """ if not name: - return "default_collection" + return DEFAULT_COLLECTION + + # Handle IPv4 pattern + if is_ipv4_pattern(name): + name = f"ip_{name}" # Replace spaces and invalid characters with underscores - sanitized = re.sub(r"[^a-zA-Z0-9_-]", "_", name) + sanitized = INVALID_CHARS_PATTERN.sub("_", name) # Ensure it starts with alphanumeric if not sanitized[0].isalnum(): @@ -114,12 +140,12 @@ def sanitize_collection_name(name: Optional[str]) -> str: if not sanitized[-1].isalnum(): sanitized = sanitized[:-1] + "z" - # Ensure length is between 3-63 characters - if len(sanitized) < 3: + # Ensure length is between MIN_LENGTH-MAX_LENGTH characters + if len(sanitized) < MIN_LENGTH: # Add padding with alphanumeric character at the end - sanitized = sanitized + "x" * (3 - len(sanitized)) - if len(sanitized) > 63: - sanitized = sanitized[:63] + sanitized = sanitized + "x" * (MIN_LENGTH - len(sanitized)) + if len(sanitized) > MAX_LENGTH: + sanitized = sanitized[:MAX_LENGTH] # Ensure it still ends with alphanumeric after truncation if not sanitized[-1].isalnum(): sanitized = sanitized[:-1] + "z" diff --git a/tests/utilities/test_string_utils.py b/tests/utilities/test_string_utils.py index 04a0dcb56..2e2cf2e0c 100644 --- a/tests/utilities/test_string_utils.py +++ b/tests/utilities/test_string_utils.py @@ -3,7 +3,12 @@ from typing import Any, Dict, List, Union import pytest -from crewai.utilities.string_utils import interpolate_only, sanitize_collection_name +from crewai.utilities import is_ipv4_pattern, sanitize_collection_name +from crewai.utilities.string_utils import ( + MAX_LENGTH, + MIN_LENGTH, + interpolate_only, +) class TestInterpolateOnly: @@ -193,7 +198,7 @@ class TestStringUtils(unittest.TestCase): """Test sanitizing a very long collection name.""" long_name = "This is an extremely long role name that will definitely exceed the ChromaDB collection name limit of 63 characters and cause an error when used as a collection name" sanitized = sanitize_collection_name(long_name) - self.assertLessEqual(len(sanitized), 63) + self.assertLessEqual(len(sanitized), MAX_LENGTH) self.assertTrue(sanitized[0].isalnum()) self.assertTrue(sanitized[-1].isalnum()) self.assertTrue(all(c.isalnum() or c in ["_", "-"] for c in sanitized)) @@ -210,7 +215,7 @@ class TestStringUtils(unittest.TestCase): """Test sanitizing a very short name.""" short_name = "A" sanitized = sanitize_collection_name(short_name) - self.assertGreaterEqual(len(sanitized), 3) + self.assertGreaterEqual(len(sanitized), MIN_LENGTH) self.assertTrue(sanitized[0].isalnum()) self.assertTrue(sanitized[-1].isalnum()) @@ -226,6 +231,37 @@ class TestStringUtils(unittest.TestCase): sanitized = sanitize_collection_name(None) self.assertEqual(sanitized, "default_collection") + def test_sanitize_collection_name_ipv4_pattern(self): + """Test sanitizing an IPv4 address.""" + ipv4 = "192.168.1.1" + sanitized = sanitize_collection_name(ipv4) + self.assertTrue(sanitized.startswith("ip_")) + self.assertTrue(sanitized[0].isalnum()) + self.assertTrue(sanitized[-1].isalnum()) + self.assertTrue(all(c.isalnum() or c in ["_", "-"] for c in sanitized)) + + def test_is_ipv4_pattern(self): + """Test IPv4 pattern detection.""" + self.assertTrue(is_ipv4_pattern("192.168.1.1")) + self.assertFalse(is_ipv4_pattern("not.an.ip.address")) + + def test_sanitize_collection_name_properties(self): + """Test that sanitized collection names always meet ChromaDB requirements.""" + test_cases = [ + "A" * 100, # Very long name + "_start_with_underscore", + "end_with_underscore_", + "contains@special#characters", + "192.168.1.1", # IPv4 address + "a" * 2, # Too short + ] + for test_case in test_cases: + sanitized = sanitize_collection_name(test_case) + self.assertGreaterEqual(len(sanitized), MIN_LENGTH) + self.assertLessEqual(len(sanitized), MAX_LENGTH) + self.assertTrue(sanitized[0].isalnum()) + self.assertTrue(sanitized[-1].isalnum()) + if __name__ == "__main__": unittest.main() From 6b14ffcffb92abfd211b6068c39928c411ff35d0 Mon Sep 17 00:00:00 2001 From: lucasgomide Date: Tue, 25 Mar 2025 15:06:00 -0300 Subject: [PATCH 13/14] fix: delegate collection name sanitization to knowledge store --- src/crewai/agent.py | 9 +- .../knowledge/storage/knowledge_storage.py | 5 +- src/crewai/utilities/__init__.py | 3 - src/crewai/utilities/chromadb.py | 62 +++ src/crewai/utilities/string_utils.py | 71 ---- tests/agent_test.py | 32 ++ ...with_knowledge_sources_extensive_role.yaml | 382 ++++++++++++++++++ tests/utilities/test_chromadb_utils.py | 81 ++++ tests/utilities/test_string_utils.py | 82 +--- 9 files changed, 563 insertions(+), 164 deletions(-) create mode 100644 src/crewai/utilities/chromadb.py create mode 100644 tests/cassettes/test_agent_with_knowledge_sources_extensive_role.yaml create mode 100644 tests/utilities/test_chromadb_utils.py diff --git a/src/crewai/agent.py b/src/crewai/agent.py index d8b6860e3..a40841db1 100644 --- a/src/crewai/agent.py +++ b/src/crewai/agent.py @@ -142,20 +142,13 @@ class Agent(BaseAgent): self.embedder = crew_embedder if self.knowledge_sources: - try: - from crewai.utilities import sanitize_collection_name - knowledge_agent_name = sanitize_collection_name(self.role) - except Exception as e: - self._logger.warning(f"Error sanitizing collection name: {e}") - knowledge_agent_name = "default_agent" - if isinstance(self.knowledge_sources, list) and all( isinstance(k, BaseKnowledgeSource) for k in self.knowledge_sources ): self.knowledge = Knowledge( sources=self.knowledge_sources, embedder=self.embedder, - collection_name=knowledge_agent_name, + collection_name=self.role, storage=self.knowledge_storage or None, ) except (TypeError, ValueError) as e: diff --git a/src/crewai/knowledge/storage/knowledge_storage.py b/src/crewai/knowledge/storage/knowledge_storage.py index 72240e2b6..37b22ed24 100644 --- a/src/crewai/knowledge/storage/knowledge_storage.py +++ b/src/crewai/knowledge/storage/knowledge_storage.py @@ -98,8 +98,11 @@ class KnowledgeStorage(BaseKnowledgeStorage): else "knowledge" ) if self.app: + from crewai.utilities.chromadb import sanitize_collection_name + self.collection = self.app.get_or_create_collection( - name=collection_name, embedding_function=self.embedder + name=sanitize_collection_name(collection_name), + embedding_function=self.embedder, ) else: raise Exception("Vector Database Client not initialized") diff --git a/src/crewai/utilities/__init__.py b/src/crewai/utilities/__init__.py index 946c4390a..dd6d9fa44 100644 --- a/src/crewai/utilities/__init__.py +++ b/src/crewai/utilities/__init__.py @@ -7,7 +7,6 @@ from .parser import YamlParser from .printer import Printer from .prompts import Prompts from .rpm_controller import RPMController -from .string_utils import sanitize_collection_name, is_ipv4_pattern from .exceptions.context_window_exceeding_exception import ( LLMContextLengthExceededException, ) @@ -26,6 +25,4 @@ __all__ = [ "YamlParser", "LLMContextLengthExceededException", "EmbeddingConfigurator", - "sanitize_collection_name", - "is_ipv4_pattern", ] diff --git a/src/crewai/utilities/chromadb.py b/src/crewai/utilities/chromadb.py new file mode 100644 index 000000000..d993a5896 --- /dev/null +++ b/src/crewai/utilities/chromadb.py @@ -0,0 +1,62 @@ +import re +from typing import Optional + +MIN_COLLECTION_LENGTH = 3 +MAX_COLLECTION_LENGTH = 63 +DEFAULT_COLLECTION = "default_collection" + +# Compiled regex patterns for better performance +INVALID_CHARS_PATTERN = re.compile(r"[^a-zA-Z0-9_-]") +IPV4_PATTERN = re.compile(r"^(\d{1,3}\.){3}\d{1,3}$") + + +def is_ipv4_pattern(name: str) -> bool: + """ + Check if a string matches an IPv4 address pattern. + + Args: + name: The string to check + + Returns: + True if the string matches an IPv4 pattern, False otherwise + """ + return bool(IPV4_PATTERN.match(name)) + + +def sanitize_collection_name(name: Optional[str]) -> str: + """ + Sanitize a collection name to meet ChromaDB requirements: + 1. 3-63 characters long + 2. Starts and ends with alphanumeric character + 3. Contains only alphanumeric characters, underscores, or hyphens + 4. No consecutive periods + 5. Not a valid IPv4 address + + Args: + name: The original collection name to sanitize + + Returns: + A sanitized collection name that meets ChromaDB requirements + """ + if not name: + return DEFAULT_COLLECTION + + if is_ipv4_pattern(name): + name = f"ip_{name}" + + sanitized = INVALID_CHARS_PATTERN.sub("_", name) + + if not sanitized[0].isalnum(): + sanitized = "a" + sanitized + + if not sanitized[-1].isalnum(): + sanitized = sanitized[:-1] + "z" + + if len(sanitized) < MIN_COLLECTION_LENGTH: + sanitized = sanitized + "x" * (MIN_COLLECTION_LENGTH - len(sanitized)) + if len(sanitized) > MAX_COLLECTION_LENGTH: + sanitized = sanitized[:MAX_COLLECTION_LENGTH] + if not sanitized[-1].isalnum(): + sanitized = sanitized[:-1] + "z" + + return sanitized diff --git a/src/crewai/utilities/string_utils.py b/src/crewai/utilities/string_utils.py index 05a637383..9a1857781 100644 --- a/src/crewai/utilities/string_utils.py +++ b/src/crewai/utilities/string_utils.py @@ -80,74 +80,3 @@ def interpolate_only( result = result.replace(placeholder, value) return result - - -from typing import Optional - -# Constants for ChromaDB collection name requirements -MIN_LENGTH = 3 -MAX_LENGTH = 63 -DEFAULT_COLLECTION = "default_collection" - -# Compiled regex patterns for better performance -INVALID_CHARS_PATTERN = re.compile(r"[^a-zA-Z0-9_-]") -IPV4_PATTERN = re.compile(r"^(\d{1,3}\.){3}\d{1,3}$") - - -def is_ipv4_pattern(name: str) -> bool: - """ - Check if a string matches an IPv4 address pattern. - - Args: - name: The string to check - - Returns: - True if the string matches an IPv4 pattern, False otherwise - """ - return bool(IPV4_PATTERN.match(name)) - - -def sanitize_collection_name(name: Optional[str]) -> str: - """ - Sanitize a collection name to meet ChromaDB requirements: - 1. 3-63 characters long - 2. Starts and ends with alphanumeric character - 3. Contains only alphanumeric characters, underscores, or hyphens - 4. No consecutive periods - 5. Not a valid IPv4 address - - Args: - name: The original collection name to sanitize - - Returns: - A sanitized collection name that meets ChromaDB requirements - """ - if not name: - return DEFAULT_COLLECTION - - # Handle IPv4 pattern - if is_ipv4_pattern(name): - name = f"ip_{name}" - - # Replace spaces and invalid characters with underscores - sanitized = INVALID_CHARS_PATTERN.sub("_", name) - - # Ensure it starts with alphanumeric - if not sanitized[0].isalnum(): - sanitized = "a" + sanitized - - # Ensure it ends with alphanumeric - if not sanitized[-1].isalnum(): - sanitized = sanitized[:-1] + "z" - - # Ensure length is between MIN_LENGTH-MAX_LENGTH characters - if len(sanitized) < MIN_LENGTH: - # Add padding with alphanumeric character at the end - sanitized = sanitized + "x" * (MIN_LENGTH - len(sanitized)) - if len(sanitized) > MAX_LENGTH: - sanitized = sanitized[:MAX_LENGTH] - # Ensure it still ends with alphanumeric after truncation - if not sanitized[-1].isalnum(): - sanitized = sanitized[:-1] + "z" - - return sanitized diff --git a/tests/agent_test.py b/tests/agent_test.py index b5b3aae93..9abc84137 100644 --- a/tests/agent_test.py +++ b/tests/agent_test.py @@ -1621,6 +1621,38 @@ def test_agent_with_knowledge_sources(): assert "red" in result.raw.lower() +@pytest.mark.vcr(filter_headers=["authorization"]) +def test_agent_with_knowledge_sources_extensive_role(): + content = "Brandon's favorite color is red and he likes Mexican food." + string_source = StringKnowledgeSource(content=content) + + with patch( + "crewai.knowledge.storage.knowledge_storage.KnowledgeStorage" + ) as MockKnowledge: + mock_knowledge_instance = MockKnowledge.return_value + mock_knowledge_instance.sources = [string_source] + mock_knowledge_instance.query.return_value = [{"content": content}] + + agent = Agent( + role="Information Agent with extensive role description that is longer than 80 characters", + goal="Provide information based on knowledge sources", + backstory="You have access to specific knowledge sources.", + llm=LLM(model="gpt-4o-mini"), + knowledge_sources=[string_source], + ) + + task = Task( + description="What is Brandon's favorite color?", + expected_output="Brandon's favorite color.", + agent=agent, + ) + + crew = Crew(agents=[agent], tasks=[task]) + result = crew.kickoff() + + assert "red" in result.raw.lower() + + @pytest.mark.vcr(filter_headers=["authorization"]) def test_agent_with_knowledge_sources_works_with_copy(): content = "Brandon's favorite color is red and he likes Mexican food." diff --git a/tests/cassettes/test_agent_with_knowledge_sources_extensive_role.yaml b/tests/cassettes/test_agent_with_knowledge_sources_extensive_role.yaml new file mode 100644 index 000000000..bfa969b12 --- /dev/null +++ b/tests/cassettes/test_agent_with_knowledge_sources_extensive_role.yaml @@ -0,0 +1,382 @@ +interactions: +- request: + body: '{"input": ["Brandon''s favorite color is red and he likes Mexican food."], + "model": "text-embedding-3-small", "encoding_format": "base64"}' + headers: + accept: + - application/json + accept-encoding: + - gzip, deflate, zstd + connection: + - keep-alive + content-length: + - '137' + content-type: + - application/json + host: + - api.openai.com + user-agent: + - OpenAI/Python 1.68.2 + x-stainless-arch: + - x64 + x-stainless-async: + - 'false' + x-stainless-lang: + - python + x-stainless-os: + - MacOS + x-stainless-package-version: + - 1.68.2 + x-stainless-read-timeout: + - '600' + x-stainless-retry-count: + - '0' + x-stainless-runtime: + - CPython + x-stainless-runtime-version: + - 3.12.9 + method: POST + uri: https://api.openai.com/v1/embeddings + response: + content: "{\n \"object\": \"list\",\n \"data\": [\n {\n \"object\": + \"embedding\",\n \"index\": 0,\n \"embedding\": \"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\"\n + \ }\n ],\n \"model\": \"text-embedding-3-small\",\n \"usage\": {\n \"prompt_tokens\": + 12,\n \"total_tokens\": 12\n }\n}\n" + headers: + CF-RAY: + - 92606d69df737e05-GRU + Connection: + - keep-alive + Content-Encoding: + - gzip + Content-Type: + - application/json + Date: + - Tue, 25 Mar 2025 18:21:21 GMT + Server: + - cloudflare + Set-Cookie: + - __cf_bm=YQe0r6xlg5bRl1wJ70dt0Aocti_r13sABgw2peP46Yw-1742926881-1.0.1.1-.p2IX5HrpoSy4WAMkQFz0iswmLdbuLJl2rLIWZkOOdUZ3jUTwTTGdAZqO8N084.xjQYo12Qj_tSEQnzCcc4a8DtoXIRULYMPRzIPeTezIkU; + path=/; expires=Tue, 25-Mar-25 18:51:21 GMT; domain=.api.openai.com; HttpOnly; + Secure; SameSite=None + - _cfuvid=T7Hv3cCn64SAlcAT1xFBTjlHSm.Ut3gTDw3SwYO5H9o-1742926881514-0.0.1.1-604800000; + path=/; domain=.api.openai.com; HttpOnly; Secure; SameSite=None + Transfer-Encoding: + - chunked + X-Content-Type-Options: + - nosniff + access-control-allow-origin: + - '*' + access-control-expose-headers: + - X-Request-ID + alt-svc: + - h3=":443"; ma=86400 + cf-cache-status: + - DYNAMIC + openai-model: + - text-embedding-3-small + openai-organization: + - crewai-iuxna1 + openai-processing-ms: + - '111' + openai-version: + - '2020-10-01' + strict-transport-security: + - max-age=31536000; includeSubDomains; preload + via: + - envoy-router-678fbc785b-244c7 + x-envoy-upstream-service-time: + - '82' + x-ratelimit-limit-requests: + - '10000' + x-ratelimit-limit-tokens: + - '10000000' + x-ratelimit-remaining-requests: + - '9999' + x-ratelimit-remaining-tokens: + - '9999986' + x-ratelimit-reset-requests: + - 6ms + x-ratelimit-reset-tokens: + - 0s + x-request-id: + - req_eec876b36a4e41890b2123c7595d82bf + http_version: HTTP/1.1 + status_code: 200 +- request: + body: '{"input": ["What is Brandon''s favorite color? This is the expected criteria + for your final answer: Brandon''s favorite color. you MUST return the actual + complete content as the final answer, not a summary."], "model": "text-embedding-3-small", + "encoding_format": "base64"}' + headers: + accept: + - application/json + accept-encoding: + - gzip, deflate, zstd + connection: + - keep-alive + content-length: + - '272' + content-type: + - application/json + cookie: + - __cf_bm=YQe0r6xlg5bRl1wJ70dt0Aocti_r13sABgw2peP46Yw-1742926881-1.0.1.1-.p2IX5HrpoSy4WAMkQFz0iswmLdbuLJl2rLIWZkOOdUZ3jUTwTTGdAZqO8N084.xjQYo12Qj_tSEQnzCcc4a8DtoXIRULYMPRzIPeTezIkU; + _cfuvid=T7Hv3cCn64SAlcAT1xFBTjlHSm.Ut3gTDw3SwYO5H9o-1742926881514-0.0.1.1-604800000 + host: + - api.openai.com + user-agent: + - OpenAI/Python 1.68.2 + x-stainless-arch: + - x64 + x-stainless-async: + - 'false' + x-stainless-lang: + - python + x-stainless-os: + - MacOS + x-stainless-package-version: + - 1.68.2 + x-stainless-read-timeout: + - '600' + x-stainless-retry-count: + - '0' + x-stainless-runtime: + - CPython + x-stainless-runtime-version: + - 3.12.9 + method: POST + uri: https://api.openai.com/v1/embeddings + response: + content: "{\n \"object\": \"list\",\n \"data\": [\n {\n \"object\": + \"embedding\",\n \"index\": 0,\n \"embedding\": \"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\"\n + \ }\n ],\n \"model\": \"text-embedding-3-small\",\n \"usage\": {\n \"prompt_tokens\": + 39,\n \"total_tokens\": 39\n }\n}\n" + headers: + CF-RAY: + - 92606d71fadd7e05-GRU + Connection: + - keep-alive + Content-Encoding: + - gzip + Content-Type: + - application/json + Date: + - Tue, 25 Mar 2025 18:21:22 GMT + Server: + - cloudflare + Transfer-Encoding: + - chunked + X-Content-Type-Options: + - nosniff + access-control-allow-origin: + - '*' + access-control-expose-headers: + - X-Request-ID + alt-svc: + - h3=":443"; ma=86400 + cf-cache-status: + - DYNAMIC + openai-model: + - text-embedding-3-small + openai-organization: + - crewai-iuxna1 + openai-processing-ms: + - '176' + openai-version: + - '2020-10-01' + strict-transport-security: + - max-age=31536000; includeSubDomains; preload + via: + - envoy-router-84d4976dd6-kn9b2 + x-envoy-upstream-service-time: + - '76' + x-ratelimit-limit-requests: + - '10000' + x-ratelimit-limit-tokens: + - '10000000' + x-ratelimit-remaining-requests: + - '9999' + x-ratelimit-remaining-tokens: + - '9999951' + x-ratelimit-reset-requests: + - 6ms + x-ratelimit-reset-tokens: + - 0s + x-request-id: + - req_4a397f4ae14d9fcb88333d9ecb5be969 + http_version: HTTP/1.1 + status_code: 200 +- request: + body: !!binary | + CocLCiQKIgoMc2VydmljZS5uYW1lEhIKEGNyZXdBSS10ZWxlbWV0cnkS3goKEgoQY3Jld2FpLnRl + bGVtZXRyeRK2CAoQro1thsfReS7yOp6MTxegrxIItR6JoTTghDkqDENyZXcgQ3JlYXRlZDABOZgk + XbC/HjAYQdgJurC/HjAYShsKDmNyZXdhaV92ZXJzaW9uEgkKBzAuMTA4LjBKGgoOcHl0aG9uX3Zl + cnNpb24SCAoGMy4xMi45Si4KCGNyZXdfa2V5EiIKIDU1MjczOGJmMDQwZTcxZGEyMjJmOWQzNjU1 + MjIzMjdjSjEKB2NyZXdfaWQSJgokMmUzMGJhOWQtZWNkOS00MDg5LTk5YTctMGIwYTE0ODk5ODdh + ShwKDGNyZXdfcHJvY2VzcxIMCgpzZXF1ZW50aWFsShEKC2NyZXdfbWVtb3J5EgIQAEoaChRjcmV3 + X251bWJlcl9vZl90YXNrcxICGAFKGwoVY3Jld19udW1iZXJfb2ZfYWdlbnRzEgIYAUqaAwoLY3Jl + d19hZ2VudHMSigMKhwNbeyJrZXkiOiAiYTk2YTQyMjM1Y2U0M2RiZDgwNzc0ZWIyODhhNzM3MzUi + LCAiaWQiOiAiZjU5NjZlYTktODk2Zi00MDRmLWIwOGUtZDk1MWI4OWNmZTM3IiwgInJvbGUiOiAi + SW5mb3JtYXRpb24gQWdlbnQgd2l0aCBleHRlbnNpdmUgcm9sZSBkZXNjcmlwdGlvbiB0aGF0IGlz + IGxvbmdlciB0aGFuIDgwIGNoYXJhY3RlcnMiLCAidmVyYm9zZT8iOiBmYWxzZSwgIm1heF9pdGVy + IjogMjUsICJtYXhfcnBtIjogbnVsbCwgImZ1bmN0aW9uX2NhbGxpbmdfbGxtIjogIiIsICJsbG0i + OiAiZ3B0LTRvLW1pbmkiLCAiZGVsZWdhdGlvbl9lbmFibGVkPyI6IGZhbHNlLCAiYWxsb3dfY29k + ZV9leGVjdXRpb24/IjogZmFsc2UsICJtYXhfcmV0cnlfbGltaXQiOiAyLCAidG9vbHNfbmFtZXMi + OiBbXX1dSsgCCgpjcmV3X3Rhc2tzErkCCrYCW3sia2V5IjogIjg2ZmU1NTY3ZDFmNDFiMWY4NDQ1 + ZTRmOGQ0YmY0MGU2IiwgImlkIjogImM1ZTU3MTcwLWFkZWQtNDNkNS1iZTE3LTZhZDliM2ZjM2U3 + NCIsICJhc3luY19leGVjdXRpb24/IjogZmFsc2UsICJodW1hbl9pbnB1dD8iOiBmYWxzZSwgImFn + ZW50X3JvbGUiOiAiSW5mb3JtYXRpb24gQWdlbnQgd2l0aCBleHRlbnNpdmUgcm9sZSBkZXNjcmlw + dGlvbiB0aGF0IGlzIGxvbmdlciB0aGFuIDgwIGNoYXJhY3RlcnMiLCAiYWdlbnRfa2V5IjogImE5 + NmE0MjIzNWNlNDNkYmQ4MDc3NGViMjg4YTczNzM1IiwgInRvb2xzX25hbWVzIjogW119XXoCGAGF + AQABAAASjgIKELNeNWDbp5Ua0wTFrxxeOrASCIhmnaGTrxBNKgxUYXNrIENyZWF0ZWQwATkQlzso + wB4wGEE4Kz4owB4wGEouCghjcmV3X2tleRIiCiA1NTI3MzhiZjA0MGU3MWRhMjIyZjlkMzY1NTIy + MzI3Y0oxCgdjcmV3X2lkEiYKJDJlMzBiYTlkLWVjZDktNDA4OS05OWE3LTBiMGExNDg5OTg3YUou + Cgh0YXNrX2tleRIiCiA4NmZlNTU2N2QxZjQxYjFmODQ0NWU0ZjhkNGJmNDBlNkoxCgd0YXNrX2lk + EiYKJGM1ZTU3MTcwLWFkZWQtNDNkNS1iZTE3LTZhZDliM2ZjM2U3NHoCGAGFAQABAAA= + headers: + Accept: + - '*/*' + Accept-Encoding: + - gzip, deflate, zstd + Connection: + - keep-alive + Content-Length: + - '1418' + Content-Type: + - application/x-protobuf + User-Agent: + - OTel-OTLP-Exporter-Python/1.31.1 + 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, 25 Mar 2025 18:21:24 GMT + status: + code: 200 + message: OK +- request: + body: '{"messages": [{"role": "system", "content": "You are Information Agent + with extensive role description that is longer than 80 characters. You have + access to specific knowledge sources.\nYour personal goal is: Provide information + based on knowledge sources\nTo give my best complete final answer to the task + respond using the exact following format:\n\nThought: I now can give a great + answer\nFinal Answer: Your final answer must be the great and the most complete + as possible, it must be outcome described.\n\nI MUST use these formats, my job + depends on it!"}, {"role": "user", "content": "\nCurrent Task: What is Brandon''s + favorite color?\n\nThis is the expected criteria for your final answer: Brandon''s + favorite color.\nyou MUST return the actual complete content as the final answer, + not a summary.Additional Information: Brandon''s favorite color is red and he + likes Mexican food.\n\nBegin! This is VERY important to you, use the tools available + and give your best Final Answer, your job depends on it!\n\nThought:"}], "model": + "gpt-4o-mini", "stop": ["\nObservation:"]}' + headers: + accept: + - application/json + accept-encoding: + - gzip, deflate, zstd + connection: + - keep-alive + content-length: + - '1074' + content-type: + - application/json + host: + - api.openai.com + user-agent: + - OpenAI/Python 1.68.2 + x-stainless-arch: + - x64 + x-stainless-async: + - 'false' + x-stainless-lang: + - python + x-stainless-os: + - MacOS + x-stainless-package-version: + - 1.68.2 + x-stainless-raw-response: + - 'true' + x-stainless-read-timeout: + - '600.0' + x-stainless-retry-count: + - '0' + x-stainless-runtime: + - CPython + x-stainless-runtime-version: + - 3.12.9 + method: POST + uri: https://api.openai.com/v1/chat/completions + response: + content: "{\n \"id\": \"chatcmpl-BF3Br9QWbmiaKiPLFd5URBfj1B7NQ\",\n \"object\": + \"chat.completion\",\n \"created\": 1742926883,\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: Brandon's favorite color is red.\",\n \"refusal\": null,\n \"annotations\": + []\n },\n \"logprobs\": null,\n \"finish_reason\": \"stop\"\n + \ }\n ],\n \"usage\": {\n \"prompt_tokens\": 194,\n \"completion_tokens\": + 19,\n \"total_tokens\": 213,\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 \"service_tier\": + \"default\",\n \"system_fingerprint\": \"fp_27322b4e16\"\n}\n" + headers: + CF-RAY: + - 92606d78cdcb7def-GRU + Connection: + - keep-alive + Content-Encoding: + - gzip + Content-Type: + - application/json + Date: + - Tue, 25 Mar 2025 18:21:24 GMT + Server: + - cloudflare + Set-Cookie: + - __cf_bm=akSDYPP.eTuyDBep9apt00XQn2By0q4quUKYKaowxB4-1742926884-1.0.1.1-nmj4tC9iquLz9Y4C_Lm9AYbMb7_yjKru3.wztYGzcO7o4_kIFqmjYjAAdLL2ZOWQUXzhWiH_XRvDTY94ubficIUm7WB.5o4CQ41GRGDc6c0; + path=/; expires=Tue, 25-Mar-25 18:51:24 GMT; domain=.api.openai.com; HttpOnly; + Secure; SameSite=None + - _cfuvid=eJkVy2yBJBQb66LFc5ao3Y_Xwek6ZYZdKM7l5_pxS_E-1742926884663-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 + cf-cache-status: + - DYNAMIC + openai-organization: + - crewai-iuxna1 + openai-processing-ms: + - '1351' + 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: + - '149999765' + x-ratelimit-reset-requests: + - 2ms + x-ratelimit-reset-tokens: + - 0s + x-request-id: + - req_126a3481ff4c4d9e8e75ed2dacbe3719 + http_version: HTTP/1.1 + status_code: 200 +version: 1 diff --git a/tests/utilities/test_chromadb_utils.py b/tests/utilities/test_chromadb_utils.py new file mode 100644 index 000000000..9035562af --- /dev/null +++ b/tests/utilities/test_chromadb_utils.py @@ -0,0 +1,81 @@ +import unittest +from typing import Any, Dict, List, Union + +import pytest + +from crewai.utilities.chromadb import ( + MAX_COLLECTION_LENGTH, + MIN_COLLECTION_LENGTH, + is_ipv4_pattern, + sanitize_collection_name, +) + + +class TestChromadbUtils(unittest.TestCase): + def test_sanitize_collection_name_long_name(self): + """Test sanitizing a very long collection name.""" + long_name = "This is an extremely long role name that will definitely exceed the ChromaDB collection name limit of 63 characters and cause an error when used as a collection name" + sanitized = sanitize_collection_name(long_name) + self.assertLessEqual(len(sanitized), MAX_COLLECTION_LENGTH) + self.assertTrue(sanitized[0].isalnum()) + self.assertTrue(sanitized[-1].isalnum()) + self.assertTrue(all(c.isalnum() or c in ["_", "-"] for c in sanitized)) + + def test_sanitize_collection_name_special_chars(self): + """Test sanitizing a name with special characters.""" + special_chars = "Agent@123!#$%^&*()" + sanitized = sanitize_collection_name(special_chars) + self.assertTrue(sanitized[0].isalnum()) + self.assertTrue(sanitized[-1].isalnum()) + self.assertTrue(all(c.isalnum() or c in ["_", "-"] for c in sanitized)) + + def test_sanitize_collection_name_short_name(self): + """Test sanitizing a very short name.""" + short_name = "A" + sanitized = sanitize_collection_name(short_name) + self.assertGreaterEqual(len(sanitized), MIN_COLLECTION_LENGTH) + self.assertTrue(sanitized[0].isalnum()) + self.assertTrue(sanitized[-1].isalnum()) + + def test_sanitize_collection_name_bad_ends(self): + """Test sanitizing a name with non-alphanumeric start/end.""" + bad_ends = "_Agent_" + sanitized = sanitize_collection_name(bad_ends) + self.assertTrue(sanitized[0].isalnum()) + self.assertTrue(sanitized[-1].isalnum()) + + def test_sanitize_collection_name_none(self): + """Test sanitizing a None value.""" + sanitized = sanitize_collection_name(None) + self.assertEqual(sanitized, "default_collection") + + def test_sanitize_collection_name_ipv4_pattern(self): + """Test sanitizing an IPv4 address.""" + ipv4 = "192.168.1.1" + sanitized = sanitize_collection_name(ipv4) + self.assertTrue(sanitized.startswith("ip_")) + self.assertTrue(sanitized[0].isalnum()) + self.assertTrue(sanitized[-1].isalnum()) + self.assertTrue(all(c.isalnum() or c in ["_", "-"] for c in sanitized)) + + def test_is_ipv4_pattern(self): + """Test IPv4 pattern detection.""" + self.assertTrue(is_ipv4_pattern("192.168.1.1")) + self.assertFalse(is_ipv4_pattern("not.an.ip.address")) + + def test_sanitize_collection_name_properties(self): + """Test that sanitized collection names always meet ChromaDB requirements.""" + test_cases = [ + "A" * 100, # Very long name + "_start_with_underscore", + "end_with_underscore_", + "contains@special#characters", + "192.168.1.1", # IPv4 address + "a" * 2, # Too short + ] + for test_case in test_cases: + sanitized = sanitize_collection_name(test_case) + self.assertGreaterEqual(len(sanitized), MIN_COLLECTION_LENGTH) + self.assertLessEqual(len(sanitized), MAX_COLLECTION_LENGTH) + self.assertTrue(sanitized[0].isalnum()) + self.assertTrue(sanitized[-1].isalnum()) diff --git a/tests/utilities/test_string_utils.py b/tests/utilities/test_string_utils.py index 2e2cf2e0c..441aae8c0 100644 --- a/tests/utilities/test_string_utils.py +++ b/tests/utilities/test_string_utils.py @@ -1,14 +1,8 @@ -import unittest from typing import Any, Dict, List, Union import pytest -from crewai.utilities import is_ipv4_pattern, sanitize_collection_name -from crewai.utilities.string_utils import ( - MAX_LENGTH, - MIN_LENGTH, - interpolate_only, -) +from crewai.utilities.string_utils import interpolate_only class TestInterpolateOnly: @@ -191,77 +185,3 @@ class TestInterpolateOnly: interpolate_only(template, inputs) assert "inputs dictionary cannot be empty" in str(excinfo.value).lower() - - -class TestStringUtils(unittest.TestCase): - def test_sanitize_collection_name_long_name(self): - """Test sanitizing a very long collection name.""" - long_name = "This is an extremely long role name that will definitely exceed the ChromaDB collection name limit of 63 characters and cause an error when used as a collection name" - sanitized = sanitize_collection_name(long_name) - self.assertLessEqual(len(sanitized), MAX_LENGTH) - self.assertTrue(sanitized[0].isalnum()) - self.assertTrue(sanitized[-1].isalnum()) - self.assertTrue(all(c.isalnum() or c in ["_", "-"] for c in sanitized)) - - def test_sanitize_collection_name_special_chars(self): - """Test sanitizing a name with special characters.""" - special_chars = "Agent@123!#$%^&*()" - sanitized = sanitize_collection_name(special_chars) - self.assertTrue(sanitized[0].isalnum()) - self.assertTrue(sanitized[-1].isalnum()) - self.assertTrue(all(c.isalnum() or c in ["_", "-"] for c in sanitized)) - - def test_sanitize_collection_name_short_name(self): - """Test sanitizing a very short name.""" - short_name = "A" - sanitized = sanitize_collection_name(short_name) - self.assertGreaterEqual(len(sanitized), MIN_LENGTH) - self.assertTrue(sanitized[0].isalnum()) - self.assertTrue(sanitized[-1].isalnum()) - - def test_sanitize_collection_name_bad_ends(self): - """Test sanitizing a name with non-alphanumeric start/end.""" - bad_ends = "_Agent_" - sanitized = sanitize_collection_name(bad_ends) - self.assertTrue(sanitized[0].isalnum()) - self.assertTrue(sanitized[-1].isalnum()) - - def test_sanitize_collection_name_none(self): - """Test sanitizing a None value.""" - sanitized = sanitize_collection_name(None) - self.assertEqual(sanitized, "default_collection") - - def test_sanitize_collection_name_ipv4_pattern(self): - """Test sanitizing an IPv4 address.""" - ipv4 = "192.168.1.1" - sanitized = sanitize_collection_name(ipv4) - self.assertTrue(sanitized.startswith("ip_")) - self.assertTrue(sanitized[0].isalnum()) - self.assertTrue(sanitized[-1].isalnum()) - self.assertTrue(all(c.isalnum() or c in ["_", "-"] for c in sanitized)) - - def test_is_ipv4_pattern(self): - """Test IPv4 pattern detection.""" - self.assertTrue(is_ipv4_pattern("192.168.1.1")) - self.assertFalse(is_ipv4_pattern("not.an.ip.address")) - - def test_sanitize_collection_name_properties(self): - """Test that sanitized collection names always meet ChromaDB requirements.""" - test_cases = [ - "A" * 100, # Very long name - "_start_with_underscore", - "end_with_underscore_", - "contains@special#characters", - "192.168.1.1", # IPv4 address - "a" * 2, # Too short - ] - for test_case in test_cases: - sanitized = sanitize_collection_name(test_case) - self.assertGreaterEqual(len(sanitized), MIN_LENGTH) - self.assertLessEqual(len(sanitized), MAX_LENGTH) - self.assertTrue(sanitized[0].isalnum()) - self.assertTrue(sanitized[-1].isalnum()) - - -if __name__ == "__main__": - unittest.main() From 6c003e0382b62026d5b00da5fb3c5942d7d5db5f Mon Sep 17 00:00:00 2001 From: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> Date: Tue, 25 Mar 2025 20:31:22 +0000 Subject: [PATCH 14/14] Address PR comment: Move import to top level in knowledge_storage.py Co-Authored-By: Joe Moura --- src/crewai/knowledge/storage/knowledge_storage.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/crewai/knowledge/storage/knowledge_storage.py b/src/crewai/knowledge/storage/knowledge_storage.py index 37b22ed24..e23b9e120 100644 --- a/src/crewai/knowledge/storage/knowledge_storage.py +++ b/src/crewai/knowledge/storage/knowledge_storage.py @@ -14,6 +14,7 @@ from chromadb.config import Settings from crewai.knowledge.storage.base_knowledge_storage import BaseKnowledgeStorage from crewai.utilities import EmbeddingConfigurator +from crewai.utilities.chromadb import sanitize_collection_name from crewai.utilities.constants import KNOWLEDGE_DIRECTORY from crewai.utilities.logger import Logger from crewai.utilities.paths import db_storage_path @@ -98,8 +99,6 @@ class KnowledgeStorage(BaseKnowledgeStorage): else "knowledge" ) if self.app: - from crewai.utilities.chromadb import sanitize_collection_name - self.collection = self.app.get_or_create_collection( name=sanitize_collection_name(collection_name), embedding_function=self.embedder,