Fix: cuga integration (#10976)

* feat: upgrade cuga version

* chore: add component index

* [autofix.ci] apply automated fixes

* [autofix.ci] apply automated fixes (attempt 2/3)

* [autofix.ci] apply automated fixes (attempt 3/3)

* fix: cuga component

* chore: update index

* [autofix.ci] apply automated fixes

* [autofix.ci] apply automated fixes (attempt 2/3)

* fix: upgrade cuga

* fix: new component index

* [autofix.ci] apply automated fixes

* [autofix.ci] apply automated fixes (attempt 2/3)

* chore: add component index

* [autofix.ci] apply automated fixes

* chore: update package

* chore: update index

* [autofix.ci] apply automated fixes

* [autofix.ci] apply automated fixes (attempt 2/3)

* [autofix.ci] apply automated fixes (attempt 3/3)

* fix: cuga relatetive temp

* fix: update cuga

* chore: add component index

* [autofix.ci] apply automated fixes

* [autofix.ci] apply automated fixes (attempt 2/3)

* fix: remove space

* [autofix.ci] apply automated fixes

* [autofix.ci] apply automated fixes (attempt 2/3)

* [autofix.ci] apply automated fixes (attempt 3/3)

---------

Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
This commit is contained in:
Sami Marreed
2025-12-12 16:19:32 +02:00
committed by GitHub
parent 3fed9fe1b5
commit e9a537b299
5 changed files with 149 additions and 93 deletions

View File

@ -135,7 +135,7 @@ dependencies = [
"fastparquet>=2024.11.0,<2025.0.0",
"traceloop-sdk>=0.43.1,<1.0.0",
"vlmrun[all]>=0.2.0",
"cuga~=0.1.11",
"cuga~=0.2.5",
"agent-lifecycle-toolkit~=0.4.4",
"astrapy>=2.1.0,<3.0.0",
"aioboto3>=15.2.0,<16.0.0"

View File

@ -1,4 +1,3 @@
import os
from typing import Any
from uuid import uuid4
@ -64,7 +63,7 @@ class TestCugaComponent(ComponentTestBaseWithoutClient):
"_type": "Cuga",
"add_current_date_tool": True,
"agent_llm": MockLanguageModel(),
"policies": "You are a helpful assistant.",
"instructions": "You are a helpful assistant.",
"input_value": "",
"n_messages": 100,
"browser_enabled": False,
@ -75,47 +74,32 @@ class TestCugaComponent(ComponentTestBaseWithoutClient):
}
async def test_build_config_update(self, component_class, default_kwargs):
"""Test that build configuration updates correctly for different providers.
"""Test that build configuration updates correctly for different model provider selections.
This test verifies that the component's build configuration is properly
updated when switching between different model providers (OpenAI, Custom).
updated when selecting different model providers using the provider system.
"""
component = await self.component_setup(component_class, default_kwargs)
frontend_node = component.to_frontend_node()
build_config = frontend_node["data"]["node"]["template"]
# Test updating build config for OpenAI
component.set(agent_llm="OpenAI")
# Test that agent_llm field exists and has proper structure
assert "agent_llm" in build_config
agent_llm_config = build_config["agent_llm"]
assert "options" in agent_llm_config
assert "OpenAI" in agent_llm_config["options"]
# Test updating build config with OpenAI provider
updated_config = await component.update_build_config(build_config, "OpenAI", "agent_llm")
assert "agent_llm" in updated_config
assert updated_config["agent_llm"]["value"] == "OpenAI"
assert isinstance(updated_config["agent_llm"]["options"], list)
assert len(updated_config["agent_llm"]["options"]) > 0
assert all(provider in updated_config["agent_llm"]["options"] for provider in ["OpenAI", "Custom"])
assert "Custom" in updated_config["agent_llm"]["options"]
# When OpenAI is selected, OpenAI-specific fields should be present
assert "openai_api_key" in updated_config or "model_name" in updated_config
# Verify model_name field is populated for OpenAI
assert "model_name" in updated_config
model_name_dict = updated_config["model_name"]
assert isinstance(model_name_dict["options"], list)
assert len(model_name_dict["options"]) > 0 # OpenAI should have available models
assert "gpt-4o" in model_name_dict["options"]
# Test Anthropic
# TBD: Add test for Anthropic currently cuga does not support Anthropic
# Test updating build config for Custom
# Test updating build config with "Custom" (should add input types for LanguageModel)
updated_config = await component.update_build_config(build_config, "Custom", "agent_llm")
assert "agent_llm" in updated_config
assert updated_config["agent_llm"]["value"] == "Custom"
assert isinstance(updated_config["agent_llm"]["options"], list)
assert len(updated_config["agent_llm"]["options"]) > 0
assert all(provider in updated_config["agent_llm"]["options"] for provider in ["OpenAI", "Custom"])
assert "Custom" in updated_config["agent_llm"]["options"]
assert updated_config["agent_llm"]["input_types"] == ["LanguageModel"]
# Verify model_name field is cleared for Custom
assert "model_name" not in updated_config
assert "LanguageModel" in updated_config["agent_llm"]["input_types"]
async def test_cuga_component_initialization(self, component_class, default_kwargs):
"""Test that Cuga component initializes correctly with filtered inputs.
@ -142,9 +126,9 @@ class TestCugaComponent(ComponentTestBaseWithoutClient):
frontend_node = component.to_frontend_node()
build_config = frontend_node["data"]["node"]["template"]
# Verify other expected fields are present
# Verify expected fields are present (using field name 'agent_llm')
assert "agent_llm" in build_config
assert "policies" in build_config
assert "instructions" in build_config
assert "add_current_date_tool" in build_config
assert "browser_enabled" in build_config
assert "web_apps" in build_config
@ -160,7 +144,7 @@ class TestCugaComponent(ComponentTestBaseWithoutClient):
input_names = [inp.name for inp in component.inputs if hasattr(inp, "name")]
# Test for new fields specific to Cuga
assert "policies" in input_names
assert "instructions" in input_names
assert "n_messages" in input_names
assert "browser_enabled" in input_names
assert "web_apps" in input_names
@ -169,7 +153,7 @@ class TestCugaComponent(ComponentTestBaseWithoutClient):
assert "decomposition_strategy" in input_names
# Verify default values
assert hasattr(component, "policies")
assert hasattr(component, "instructions")
assert hasattr(component, "n_messages")
assert hasattr(component, "browser_enabled")
assert hasattr(component, "web_apps")
@ -210,6 +194,32 @@ class TestCugaComponent(ComponentTestBaseWithoutClient):
component.decomposition_strategy = "flexible"
assert component.decomposition_strategy == "flexible"
async def test_advanced_fields_configuration(self, component_class, default_kwargs):
"""Test that browser and cuga lite fields are properly configured as advanced.
This test verifies that browser_enabled, web_apps, lite_mode, and
lite_mode_tool_threshold fields are all set to advanced.
"""
component = await self.component_setup(component_class, default_kwargs)
# Find all the advanced fields we want to test
field_checks = {
"browser_enabled": False,
"web_apps": False,
"lite_mode": False,
"lite_mode_tool_threshold": False,
}
for inp in component.inputs:
if hasattr(inp, "name") and inp.name in field_checks:
field_checks[inp.name] = inp.advanced
# Assert all fields are set to advanced
assert field_checks["browser_enabled"] is True, "browser_enabled should be advanced"
assert field_checks["web_apps"] is True, "web_apps should be advanced"
assert field_checks["lite_mode"] is True, "lite_mode should be advanced"
assert field_checks["lite_mode_tool_threshold"] is True, "lite_mode_tool_threshold should be advanced"
async def test_memory_inputs_advanced_setting(self, component_class, default_kwargs):
"""Test that memory inputs are properly set to advanced.
@ -276,21 +286,29 @@ class TestCugaComponentWithClient(ComponentTestBaseWithClient):
Requires:
OPENAI_API_KEY environment variable
"""
# Now you can access the environment variables
api_key = os.getenv("OPENAI_API_KEY")
from tests.api_keys import get_openai_api_key
api_key = get_openai_api_key()
tools = [CalculatorToolComponent().build_tool()] # Use the Calculator component as a tool
input_value = "What is 2 + 2?"
temperature = 0.1
# Initialize the CugaComponent with mocked inputs
# Initialize the CugaComponent with unified model format
cuga = CugaComponent(
tools=tools,
input_value=input_value,
api_key=api_key,
model_name="gpt-4o",
agent_llm="OpenAI",
temperature=temperature,
model=[
{
"name": "gpt-4o",
"provider": "OpenAI",
"icon": "OpenAI",
"metadata": {
"model_class": "ChatOpenAI",
"model_name_param": "model",
"api_key_param": "api_key", # pragma: allowlist secret
},
}
],
_session_id=str(uuid4()),
)
@ -309,8 +327,9 @@ class TestCugaComponentWithClient(ComponentTestBaseWithClient):
Requires:
OPENAI_API_KEY environment variable
"""
# Mock inputs
api_key = os.getenv("OPENAI_API_KEY")
from tests.api_keys import get_openai_api_key
api_key = get_openai_api_key()
input_value = "What is 2 + 2?"
# Test only key OpenAI models to avoid timeout and complexity
@ -319,14 +338,24 @@ class TestCugaComponentWithClient(ComponentTestBaseWithClient):
for model_name in key_models:
try:
# Initialize the CugaComponent with mocked inputs
# Initialize the CugaComponent with unified model format
tools = [CalculatorToolComponent().build_tool()] # Use the Calculator component as a tool
cuga = CugaComponent(
tools=tools,
input_value=input_value,
api_key=api_key,
model_name=model_name,
agent_llm="OpenAI",
model=[
{
"name": model_name,
"provider": "OpenAI",
"icon": "OpenAI",
"metadata": {
"model_class": "ChatOpenAI",
"model_name_param": "model",
"api_key_param": "api_key", # pragma: allowlist secret
},
}
],
_session_id=str(uuid4()),
)
@ -340,25 +369,37 @@ class TestCugaComponentWithClient(ComponentTestBaseWithClient):
@pytest.mark.api_key_required
@pytest.mark.no_blockbuster
async def test_cuga_with_policies(self):
"""Test Cuga with custom policies.
async def test_cuga_with_instructions(self):
"""Test Cuga with custom instructions.
This integration test verifies that the CugaComponent can apply
custom policies to modify its behavior during execution.
custom instructions to modify its behavior during execution.
Requires:
OPENAI_API_KEY environment variable
"""
api_key = os.getenv("OPENAI_API_KEY")
from tests.api_keys import get_openai_api_key
api_key = get_openai_api_key()
input_value = "What is 2 + 2?"
policies = "## Answer\n\nYou must always respond with enthusiasm and use exclamation marks!"
instructions = "## Answer\n\nYou must always respond with enthusiasm and use exclamation marks!"
tools = [CalculatorToolComponent().build_tool()]
cuga = CugaComponent(
input_value=input_value,
api_key=api_key,
model_name="gpt-4o",
agent_llm="OpenAI",
policies=policies,
model=[
{
"name": "gpt-4o",
"provider": "OpenAI",
"icon": "OpenAI",
"metadata": {
"model_class": "ChatOpenAI",
"model_name_param": "model",
"api_key_param": "api_key", # pragma: allowlist secret
},
}
],
instructions=instructions,
tools=tools,
_session_id=str(uuid4()),
)

File diff suppressed because one or more lines are too long

View File

@ -53,7 +53,7 @@ class CugaComponent(ToolCallingAgentComponent):
"""Cuga Agent Component for advanced AI task execution.
The Cuga component is an advanced AI agent that can execute complex tasks using
various tools and browser automation. It supports custom policies, web applications,
various tools and browser automation. It supports custom instructions, web applications,
and API interactions.
Attributes:
@ -65,7 +65,7 @@ class CugaComponent(ToolCallingAgentComponent):
"""
display_name: str = "Cuga"
description: str = "Define the Cuga agent's policies, then assign it a task."
description: str = "Define the Cuga agent's instructions, then assign it a task."
documentation: str = "https://docs.langflow.org/bundles-cuga"
icon = "bot"
name = "Cuga"
@ -85,10 +85,10 @@ class CugaComponent(ToolCallingAgentComponent):
),
*MODEL_PROVIDERS_DICT["OpenAI"]["inputs"],
MultilineInput(
name="policies",
display_name="Policies",
name="instructions",
display_name="Instructions",
info=(
"Custom instructions or policies for the agent to adhere to during its operation.\n"
"Custom instructions for the agent to adhere to during its operation.\n"
"Example:\n"
"## Plan\n"
"< planning instructions e.g. which tools and when to use>\n"
@ -117,16 +117,16 @@ class CugaComponent(ToolCallingAgentComponent):
BoolInput(
name="lite_mode",
display_name="Enable CugaLite",
info="Enable CugaLite for simple API tasks (faster execution).",
info="Faster reasoning for simple tasks. Enable CugaLite for simple API tasks.",
value=True,
advanced=False,
advanced=True,
),
IntInput(
name="lite_mode_tool_threshold",
display_name="CugaLite Tool Threshold",
info="Route to CugaLite if app has fewer than this many tools.",
value=25,
advanced=False,
advanced=True,
),
DropdownInput(
name="decomposition_strategy",
@ -142,17 +142,17 @@ class CugaComponent(ToolCallingAgentComponent):
display_name="Enable Browser",
info="Toggle to enable a built-in browser tool for web scraping and searching.",
value=False,
advanced=False,
advanced=True,
),
MultilineInput(
name="web_apps",
display_name="Web applications",
info=(
"Define a list of web applications that cuga will open when enable browser is true. "
"Cuga will automatically start this web application when Enable Browser is true. "
"Currently only supports one web application. Example: https://example.com"
),
value="",
advanced=False,
advanced=True,
),
]
outputs = [
@ -211,7 +211,6 @@ class CugaComponent(ToolCallingAgentComponent):
settings.advanced_features.mode = "api"
from cuga.backend.activity_tracker.tracker import ActivityTracker
from cuga.backend.cuga_graph.nodes.api.variables_manager.manager import VariablesManager
from cuga.backend.cuga_graph.utils.agent_loop import StreamEvent
from cuga.backend.cuga_graph.utils.controller import (
AgentRunner as CugaAgent,
@ -222,13 +221,10 @@ class CugaComponent(ToolCallingAgentComponent):
from cuga.backend.llm.models import LLMManager
from cuga.configurations.instructions_manager import InstructionsManager
var_manager = VariablesManager()
# Reset var_manager if this is the first message in history
logger.debug(f"[CUGA] Checking history_messages: count={len(history_messages) if history_messages else 0}")
if not history_messages or len(history_messages) == 0:
logger.debug("[CUGA] First message in history detected, resetting var_manager")
var_manager.reset()
else:
logger.debug(f"[CUGA] Continuing conversation with {len(history_messages)} previous messages")
@ -236,12 +232,14 @@ class CugaComponent(ToolCallingAgentComponent):
llm_manager.set_llm(llm)
instructions_manager = InstructionsManager()
policies_to_use = self.policies or ""
logger.debug(f"[CUGA] policies are: {policies_to_use}")
instructions_manager.set_instructions_from_one_file(policies_to_use)
instructions_to_use = self.instructions or ""
logger.debug(f"[CUGA] instructions are: {instructions_to_use}")
instructions_manager.set_instructions_from_one_file(instructions_to_use)
tracker = ActivityTracker()
tracker.set_tools(tools)
cuga_agent = CugaAgent(browser_enabled=self.browser_enabled)
thread_id = self.graph.session_id
logger.debug(f"[CUGA] Using thread_id (session_id): {thread_id}")
cuga_agent = CugaAgent(browser_enabled=self.browser_enabled, thread_id=thread_id)
if self.browser_enabled:
await cuga_agent.initialize_freemode_env(start_url=self.web_apps.strip(), interface_mode="browser_only")
else:
@ -257,13 +255,20 @@ class CugaComponent(ToolCallingAgentComponent):
logger.debug(f"[CUGA] Processing input: {current_input}")
try:
# Convert history to LangChain format for the event
logger.debug(f"[CUGA] Converting {len(history_messages)} history messages to LangChain format")
lc_messages = []
for msg in history_messages:
for i, msg in enumerate(history_messages):
msg_text = getattr(msg, "text", "N/A")[:50] if hasattr(msg, "text") else "N/A"
logger.debug(
f"[CUGA] Message {i}: type={type(msg)}, sender={getattr(msg, 'sender', 'N/A')}, "
f"text={msg_text}..."
)
if hasattr(msg, "sender") and msg.sender == "Human":
lc_messages.append(HumanMessage(content=msg.text))
else:
lc_messages.append(AIMessage(content=msg.text))
logger.debug(f"[CUGA] Converted to {len(lc_messages)} LangChain messages")
await asyncio.sleep(0.5)
# 2. Build final response
@ -274,7 +279,9 @@ class CugaComponent(ToolCallingAgentComponent):
last_event: StreamEvent | None = None
tool_run_id: str | None = None
# 3. Chain end event with AgentFinish
async for event in cuga_agent.run_task_generic_yield(eval_mode=False, goal=current_input):
async for event in cuga_agent.run_task_generic_yield(
eval_mode=False, goal=current_input, chat_messages=lc_messages
):
logger.debug(f"[CUGA] recieved event {event}")
if last_event is not None and tool_run_id is not None:
logger.debug(f"[CUGA] last event {last_event}")
@ -350,12 +357,12 @@ class CugaComponent(ToolCallingAgentComponent):
raise ValueError(msg)
try:
llm_model, self.chat_history, self.tools = await self.get_agent_requirements()
# Create agent message for event processing
from lfx.schema.content_block import ContentBlock
from lfx.schema.message import MESSAGE_SENDER_AI
llm_model, self.chat_history, self.tools = await self.get_agent_requirements()
# Create agent message for event processing
agent_message = Message(
sender=MESSAGE_SENDER_AI,
sender_name="Cuga",
@ -368,7 +375,7 @@ class CugaComponent(ToolCallingAgentComponent):
# This ensures streaming works even when not connected to ChatOutput
if not self.is_connected_to_chat_output():
# When not connected to ChatOutput, assign ID upfront for streaming support
agent_message.data["id"] = str(uuid.uuid4())
agent_message.data["id"] = uuid.uuid4()
# Get input text
input_text = self.input_value.text if hasattr(self.input_value, "text") else str(self.input_value)
@ -476,9 +483,14 @@ class CugaComponent(ToolCallingAgentComponent):
"""
logger.debug("[CUGA] Retrieving chat history messages.")
logger.debug(f"[CUGA] Session ID: {self.graph.session_id}")
logger.debug(f"[CUGA] n_messages: {self.n_messages}")
logger.debug(f"[CUGA] input_value: {self.input_value}")
logger.debug(f"[CUGA] input_value type: {type(self.input_value)}")
logger.debug(f"[CUGA] input_value id: {getattr(self.input_value, 'id', None)}")
messages = (
await MemoryComponent(**self.get_base_args())
.set(session_id=self.graph.session_id, order="Ascending", n_messages=self.n_messages)
.set(session_id=str(self.graph.session_id), order="Ascending", n_messages=self.n_messages)
.retrieve_messages()
)
logger.debug(f"[CUGA] Retrieved {len(messages)} messages from memory")
@ -678,7 +690,7 @@ class CugaComponent(ToolCallingAgentComponent):
"tools",
"input_value",
"add_current_date_tool",
"policies",
"instructions",
"agent_description",
"max_iterations",
"handle_parsing_errors",

19
uv.lock generated
View File

@ -1,5 +1,5 @@
version = 1
revision = 2
revision = 3
requires-python = ">=3.10, <3.14"
resolution-markers = [
"python_full_version >= '3.13' and platform_machine == 'arm64' and sys_platform == 'darwin'",
@ -1811,7 +1811,7 @@ wheels = [
[[package]]
name = "cuga"
version = "0.1.11"
version = "0.2.5"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "aiohttp" },
@ -1821,6 +1821,7 @@ dependencies = [
{ name = "fastapi", extra = ["standard"] },
{ name = "fastmcp" },
{ name = "html2text" },
{ name = "httpx" },
{ name = "langchain" },
{ name = "langchain-core" },
{ name = "langchain-ibm" },
@ -1832,13 +1833,15 @@ dependencies = [
{ name = "mcp", extra = ["cli"] },
{ name = "playwright" },
{ name = "psutil" },
{ name = "pymilvus" },
{ name = "python-dotenv" },
{ name = "pyyaml" },
{ name = "typer" },
{ name = "uvicorn" },
]
sdist = { url = "https://files.pythonhosted.org/packages/49/e8/5a5d9b2d6f894a9c667485c654b9946e01ab8d96364d6142acc4e233cec3/cuga-0.1.11.tar.gz", hash = "sha256:9c0f66401ac200d4d94c8b3bdaff96cb7d3d52cc5d7dd07a5a7ec62cdaa6e5c7", size = 472123, upload-time = "2025-11-26T17:35:20.024Z" }
sdist = { url = "https://files.pythonhosted.org/packages/3e/da/5affdc32c01f2a14457df8cdbeb9f81823a4a86485cfb98ff74ca72e27fd/cuga-0.2.5.tar.gz", hash = "sha256:485e1228a8a0865e82bdf19022c65220ffe3aeab78c5774680b47aa01664ab1b", size = 520712, upload-time = "2025-12-12T13:04:05.816Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/8b/fa/b8e8e19d84d4e47d0f3654366f6f7e33b5f5ec05bdf0787fd16981696bde/cuga-0.1.11-py3-none-any.whl", hash = "sha256:cd5e05680134599268878c6c7b0c94913605b182e013f01bf480f924abfdf5b3", size = 642770, upload-time = "2025-11-26T17:35:18.593Z" },
{ url = "https://files.pythonhosted.org/packages/66/02/d1f6204b1895586480c7d94bdefb99cb6c0655635cbd7f58acf920d2c72d/cuga-0.2.5-py3-none-any.whl", hash = "sha256:797ade7fedb840fc1bb3b737c19ed2db882543e7a3e093439455f0ea32bee2de", size = 693891, upload-time = "2025-12-12T13:04:04.277Z" },
]
[[package]]
@ -5762,7 +5765,7 @@ requires-dist = [
{ name = "couchbase", marker = "extra == 'couchbase'", specifier = ">=4.2.1" },
{ name = "cryptography", specifier = ">=43.0.1,<44.0.0" },
{ name = "ctransformers", marker = "extra == 'local'", specifier = ">=0.2.10" },
{ name = "cuga", specifier = "~=0.1.11" },
{ name = "cuga", specifier = "~=0.2.5" },
{ name = "datasets", specifier = ">2.14.7,<4.0.0" },
{ name = "docling", marker = "platform_machine != 'x86_64' or sys_platform != 'darwin'", specifier = ">=2.36.1,<3.0.0" },
{ name = "docling-core", specifier = ">=2.36.1,<3.0.0" },
@ -7092,7 +7095,7 @@ wheels = [
[[package]]
name = "mcp"
version = "1.22.0"
version = "1.23.3"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "anyio" },
@ -7110,9 +7113,9 @@ dependencies = [
{ name = "typing-inspection" },
{ name = "uvicorn", marker = "sys_platform != 'emscripten'" },
]
sdist = { url = "https://files.pythonhosted.org/packages/a3/a2/c5ec0ab38b35ade2ae49a90fada718fbc76811dc5aa1760414c6aaa6b08a/mcp-1.22.0.tar.gz", hash = "sha256:769b9ac90ed42134375b19e777a2858ca300f95f2e800982b3e2be62dfc0ba01", size = 471788, upload-time = "2025-11-20T20:11:28.095Z" }
sdist = { url = "https://files.pythonhosted.org/packages/a7/a4/d06a303f45997e266f2c228081abe299bbcba216cb806128e2e49095d25f/mcp-1.23.3.tar.gz", hash = "sha256:b3b0da2cc949950ce1259c7bfc1b081905a51916fcd7c8182125b85e70825201", size = 600697, upload-time = "2025-12-09T16:04:37.351Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/a9/bb/711099f9c6bb52770f56e56401cdfb10da5b67029f701e0df29362df4c8e/mcp-1.22.0-py3-none-any.whl", hash = "sha256:bed758e24df1ed6846989c909ba4e3df339a27b4f30f1b8b627862a4bade4e98", size = 175489, upload-time = "2025-11-20T20:11:26.542Z" },
{ url = "https://files.pythonhosted.org/packages/32/c6/13c1a26b47b3f3a3b480783001ada4268917c9f42d78a079c336da2e75e5/mcp-1.23.3-py3-none-any.whl", hash = "sha256:32768af4b46a1b4f7df34e2bfdf5c6011e7b63d7f1b0e321d0fdef4cd6082031", size = 231570, upload-time = "2025-12-09T16:04:35.56Z" },
]
[package.optional-dependencies]