From 017f9afd5828eddbf3665549e556974839c2cf73 Mon Sep 17 00:00:00 2001 From: miro Date: Fri, 17 Oct 2025 14:45:21 +0100 Subject: [PATCH 01/13] feat: tool plugins --- ovos_plugin_manager/templates/agent_tools.py | 208 +++++++++++++++++++ requirements/requirements.txt | 1 + 2 files changed, 209 insertions(+) create mode 100644 ovos_plugin_manager/templates/agent_tools.py diff --git a/ovos_plugin_manager/templates/agent_tools.py b/ovos_plugin_manager/templates/agent_tools.py new file mode 100644 index 00000000..504d32cf --- /dev/null +++ b/ovos_plugin_manager/templates/agent_tools.py @@ -0,0 +1,208 @@ +from abc import ABC, abstractmethod +from dataclasses import dataclass, field +from typing import Type, Any, Dict, List, Callable, Optional, Union + +from ovos_bus_client import MessageBusClient, Message +from ovos_utils.fakebus import FakeBus +from pydantic import BaseModel, Field + + +# Base Pydantic Model for Tool Input/Arguments +class ToolArguments(BaseModel): + """Base class for Pydantic models defining tool arguments.""" + pass + + +# Base Pydantic Model for Tool Output +class ToolOutput(BaseModel): + """Base class for Pydantic models defining tool output structure.""" + pass + + +@dataclass +class AgentTool: + """ + Defines a single executable function (tool) available to an Agent. + + This dataclass provides the necessary structured metadata (schemas) + for LLM communication, paired with the actual executable Python logic. + """ + name: str = field(metadata={'help': 'The unique, snake_case name of the tool (used by the LLM).'}) + description: str = field(metadata={'help': 'A detailed, natural language description of the tool\'s purpose.'}) + argument_schema: Type[ToolArguments] = field(metadata={'help': 'Pydantic model defining the expected input/arguments.'}) + output_schema: Type[ToolOutput] = field(metadata={'help': 'Pydantic model defining the expected output structure.'}) + tool_call: Callable[..., Dict[str, Any]] = field(metadata={'help': 'The function to execute the tool logic. It accepts keyword arguments validated against argument_schema and must return a Dict[str, Any] conforming to output_schema.'}) + + +class ToolBox(ABC): + """ + Abstract base class for a ToolBox plugin. + + Each ToolBox is a discoverable plugin that groups related AgentTools. It exposes + tools as services over the OVOS messagebus and provides a direct execution interface. + """ + + def __init__(self, toolbox_id: str, + bus: Optional[Union[MessageBusClient, FakeBus]] = None): + """ + Initializes the ToolBox. Note: Messagebus binding is deferred until `bind()` is called. + + Args: + toolbox_id: A unique identifier for this ToolBox instance (usually the entrypoint name, e.g., 'web_search_tools'). + bus: The OVOS Messagebus client instance. If provided, `bind()` is called automatically. + """ + self.toolbox_id: str = toolbox_id # Unique ID for the toolbox + self.bus: Optional[Union[MessageBusClient, FakeBus]] = None + + # Internal cache for discovered tools, mapped by name + self.tools: Dict[str, AgentTool] = {} + try: + self.discover_tools() # try to find tools immediately + except Exception as e: + pass # will be lazy loaded or throw error on first usage + + # Initialize the messagebus connection if provided + if bus: + self.bind(bus) + + def bind(self, bus: Union[MessageBusClient, FakeBus]) -> None: + """ + Binds the ToolBox to a specific Messagebus instance and registers handlers. + + This method must be called to enable messagebus-based discovery and calling. + + Args: + bus: The active OVOS Messagebus client or FakeBus instance. + """ + self.bus = bus + # General discovery broadcast + self.bus.on("ovos.persona.tools.discover", self.handle_discover) + # Specific call channel for this toolbox + self.bus.on(f"ovos.persona.tools.{self.toolbox_id}.call", self.handle_call) + + def refresh_tools(self) -> None: + """ + Reloads and updates the internal cache of AgentTools by calling + the abstract `discover_tools` method. This is implicitly called + if a tool is requested but not found in the cache. + """ + self.tools = {tool.name: tool for tool in self.discover_tools()} + + def handle_discover(self, message: Message) -> None: + """ + Handles the 'ovos.persona.tools.discover' messagebus event. + + Emits a response containing the full list of tools provided by this ToolBox, + including JSON Schemas for arguments and output. + + Args: + message: The incoming discovery Message object. + """ + response_data: Dict[str, Any] = { + "tools": self.tool_json_list, + "toolbox_id": self.toolbox_id + } + self.bus.emit(message.response(response_data)) + + def handle_call(self, message: Message) -> None: + """ + Handles messagebus calls to a specific tool within this ToolBox. + + It attempts to execute the tool and emits the result or error back on the bus. + + Args: + message: The incoming Message object containing 'name' (tool name) + and 'kwargs' (tool arguments dictionary). + """ + name: str = message.data.get("name", "") + tool_kwargs: Dict[str, Any] = message.data.get("kwargs", {}) + + try: + # Use the execution wrapper method + result: Dict[str, Any] = self.call_tool(name, tool_kwargs) + self.bus.emit(message.response({"result": result, "toolbox_id": self.toolbox_id})) + except Exception as e: + # Catch all execution exceptions (including ValueErrors from call_tool) + error: str = f"{type(e).__name__}: {str(e)}" + self.bus.emit(message.response({"error": error, "toolbox_id": self.toolbox_id})) + + def call_tool(self, name: str, tool_kwargs: Dict[str, Any]) -> Dict[str, Any]: + """ + Direct execution interface for an Agent (solver) to call a tool. + + This path is used by the `ovos-solver-tool-orchestrator-plugin` for direct, + in-process execution without messagebus overhead. + + Args: + name: The unique name of the tool to execute. + tool_kwargs: Keyword arguments for the tool, expected to be validated by the caller. + + Returns: + The raw dictionary output from the tool's `tool_call` function. + + Raises: + ValueError: If the requested tool name is unknown for this ToolBox. + RuntimeError: If the execution of the tool's `tool_call` function fails. + """ + tool: Optional[AgentTool] = self.get_tool(name) + if tool: + try: + # Execution assumes kwargs are already validated/sanitized by the orchestrator + return tool.tool_call(**tool_kwargs) + except Exception as e: + # Wrap tool execution errors for better context + raise RuntimeError(f"Tool execution failed for '{name}' in ToolBox '{self.toolbox_id}'") from e + else: + raise ValueError(f"Unknown tool '{name}' for ToolBox '{self.toolbox_id}'.") + + def get_tool(self, name: str) -> Optional[AgentTool]: + """ + Retrieves an AgentTool definition by its name from the cache. + + Refreshes the tool cache if the tool is not found, ensuring lazy loading. + + Args: + name: The name of the tool to retrieve. + + Returns: + The AgentTool instance, or None if the tool does not exist. + """ + if name not in self.tools: + self.refresh_tools() + return self.tools.get(name) + + @property + def tool_json_list(self) -> List[Dict[str, Union[str, Dict[str, Any]]]]: + """ + Generates a list of tool definitions with Pydantic schemas converted to JSON Schema. + + This output is suitable for direct transmission over the messagebus or + for submission to an LLM's `functions` or `tools` API endpoint. + + Returns: + A list of dictionaries, one for each tool, where `argument_schema` + and `output_schema` are JSON Schema dictionaries. + """ + return [ + { + "name": tool.name, + "description": tool.description, + "argument_schema": tool.argument_schema.model_json_schema(), + "output_schema": tool.output_schema.model_json_schema() + } + for tool in self.tools.values() + ] + + # The only mandatory method for concrete plugins to implement + @abstractmethod + def discover_tools(self) -> List[AgentTool]: + """ + Abstract method to be implemented by concrete ToolBox plugins. + + This method must define and return the list of AgentTools provided by this plugin. + The implementation should be idempotent (safe to call multiple times). + + Returns: + A list of instantiated AgentTool objects. + """ + raise NotImplementedError diff --git a/requirements/requirements.txt b/requirements/requirements.txt index d1463187..afb62a4a 100644 --- a/requirements/requirements.txt +++ b/requirements/requirements.txt @@ -5,6 +5,7 @@ combo_lock~=0.3 requests~=2.32 quebra_frases langcodes~=3.5.0 +pydantic # see https://github.com/pypa/setuptools/issues/1471 importlib_metadata From ba6f51e5e37f61be187e0b52806e1b90fa35de3e Mon Sep 17 00:00:00 2001 From: JarbasAI <33701864+JarbasAl@users.noreply.github.com> Date: Fri, 17 Oct 2025 14:59:13 +0100 Subject: [PATCH 02/13] Update requirements/requirements.txt Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com> --- requirements/requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements/requirements.txt b/requirements/requirements.txt index afb62a4a..3773afb9 100644 --- a/requirements/requirements.txt +++ b/requirements/requirements.txt @@ -5,7 +5,7 @@ combo_lock~=0.3 requests~=2.32 quebra_frases langcodes~=3.5.0 -pydantic +pydantic~=2.0 # see https://github.com/pypa/setuptools/issues/1471 importlib_metadata From 8724b08b4b7b0ae14b5d0c3372265ae59be4f09a Mon Sep 17 00:00:00 2001 From: miro Date: Fri, 17 Oct 2025 15:05:08 +0100 Subject: [PATCH 03/13] discovery --- ovos_plugin_manager/persona.py | 14 ++++++++++++-- ovos_plugin_manager/utils/__init__.py | 1 + 2 files changed, 13 insertions(+), 2 deletions(-) diff --git a/ovos_plugin_manager/persona.py b/ovos_plugin_manager/persona.py index 9f411e09..00f8d51f 100644 --- a/ovos_plugin_manager/persona.py +++ b/ovos_plugin_manager/persona.py @@ -1,12 +1,22 @@ +from typing import Type, Dict, Any + +from ovos_plugin_manager.templates.agent_tools import ToolBox from ovos_plugin_manager.utils import PluginTypes -def find_persona_plugins() -> dict: +def find_persona_plugins() -> Dict[str, Dict[str, Any]]: """ - Find all installed plugins + Find all installed persona definitions @return: dict plugin names to entrypoints (persona entrypoint are just dicts) """ from ovos_plugin_manager.utils import find_plugins return find_plugins(PluginTypes.PERSONA) +def find_toolbox_plugins() -> Dict[str, Type[ToolBox]]: + """ + Find all installed Toolbox plugins + @return: dict toolbox_id to entrypoints (ToolBox) + """ + from ovos_plugin_manager.utils import find_plugins + return find_plugins(PluginTypes.PERSONA_TOOL) diff --git a/ovos_plugin_manager/utils/__init__.py b/ovos_plugin_manager/utils/__init__.py index 69f16352..e03c0632 100644 --- a/ovos_plugin_manager/utils/__init__.py +++ b/ovos_plugin_manager/utils/__init__.py @@ -98,6 +98,7 @@ class PluginTypes(str, Enum): VIDEO_PLAYER = "opm.media.video" WEB_PLAYER = "opm.media.web" PERSONA = "opm.plugin.persona" # personas are a dict, they have no config because they ARE a config + PERSONA_TOOL = "opm.persona.tool" # solver plugins are deprecated! QUESTION_SOLVER = "opm.solver.question" From e0b58bbf05fffc77af249cb443f50ee2818266e6 Mon Sep 17 00:00:00 2001 From: miro Date: Fri, 17 Oct 2025 15:26:46 +0100 Subject: [PATCH 04/13] pydantic validation --- ovos_plugin_manager/templates/agent_tools.py | 99 ++++++++++++++++---- 1 file changed, 83 insertions(+), 16 deletions(-) diff --git a/ovos_plugin_manager/templates/agent_tools.py b/ovos_plugin_manager/templates/agent_tools.py index 504d32cf..d5da3b03 100644 --- a/ovos_plugin_manager/templates/agent_tools.py +++ b/ovos_plugin_manager/templates/agent_tools.py @@ -7,9 +7,10 @@ from pydantic import BaseModel, Field + # Base Pydantic Model for Tool Input/Arguments class ToolArguments(BaseModel): - """Base class for Pydantic models defining tool arguments.""" + """Base class for Pydantic models defining tool input/arguments.""" pass @@ -18,6 +19,9 @@ class ToolOutput(BaseModel): """Base class for Pydantic models defining tool output structure.""" pass +# --- Type Aliases for Clarity --- +ToolCallFunc = Callable[[ToolArguments], Dict[str, Any]] + @dataclass class AgentTool: @@ -31,8 +35,9 @@ class AgentTool: description: str = field(metadata={'help': 'A detailed, natural language description of the tool\'s purpose.'}) argument_schema: Type[ToolArguments] = field(metadata={'help': 'Pydantic model defining the expected input/arguments.'}) output_schema: Type[ToolOutput] = field(metadata={'help': 'Pydantic model defining the expected output structure.'}) - tool_call: Callable[..., Dict[str, Any]] = field(metadata={'help': 'The function to execute the tool logic. It accepts keyword arguments validated against argument_schema and must return a Dict[str, Any] conforming to output_schema.'}) - + tool_call: ToolCallFunc = field( + metadata={'help': 'The function to execute the tool logic. It accepts one positional argument (an instantiated ToolArguments model) and must return a Dict[str, Any] conforming to output_schema.'} + ) class ToolBox(ABC): """ @@ -119,39 +124,100 @@ def handle_call(self, message: Message) -> None: try: # Use the execution wrapper method - result: Dict[str, Any] = self.call_tool(name, tool_kwargs) - self.bus.emit(message.response({"result": result, "toolbox_id": self.toolbox_id})) + result: ToolOutput = self.call_tool(name, tool_kwargs) + self.bus.emit(message.response({"result": result.model_dump(), "toolbox_id": self.toolbox_id})) except Exception as e: # Catch all execution exceptions (including ValueErrors from call_tool) error: str = f"{type(e).__name__}: {str(e)}" self.bus.emit(message.response({"error": error, "toolbox_id": self.toolbox_id})) - def call_tool(self, name: str, tool_kwargs: Dict[str, Any]) -> Dict[str, Any]: + @staticmethod + def validate_input(tool: AgentTool, tool_kwargs: Dict[str, Any]) -> ToolArguments: + """ + Validates raw keyword arguments against the tool's input schema. + + Args: + tool: The :class:`AgentTool` definition. + tool_kwargs: The raw dictionary of arguments. + + Returns: + An instantiated :class:`ToolArguments` Pydantic model. + + Raises: + ValueError: If input validation fails (e.g., missing fields, wrong types). + """ + try: + ArgsModel: Type[ToolArguments] = tool.argument_schema + # Instantiating the Pydantic model implicitly validates the input + return ArgsModel(**tool_kwargs) + except Exception as e: + raise ValueError(f"Invalid input for '{tool.name}': {tool_kwargs}") from e + + @staticmethod + def validate_output(tool: AgentTool, raw_result: Dict[str, Any]) -> ToolOutput: + """ + Validates the raw dictionary output from the tool execution against the output schema. + + Args: + tool: The :class:`AgentTool` definition. + raw_result: The raw dictionary returned by the tool's execution function. + + Returns: + An instantiated :class:`ToolOutput` Pydantic model. + + Raises: + ValueError: If output validation fails. """ - Direct execution interface for an Agent (solver) to call a tool. + try: + OutputModel: Type[ToolOutput] = tool.output_schema + # Validate the raw result against the output schema. + # The .model_validate() method returns a validated Pydantic object + return OutputModel.model_validate(raw_result) + except Exception as e: + raise ValueError(f"Invalid output from '{tool.name}': {raw_result}") from e - This path is used by the `ovos-solver-tool-orchestrator-plugin` for direct, - in-process execution without messagebus overhead. + + def call_tool(self, name: str, tool_kwargs: Dict[str, Any]) -> ToolOutput: + """ + Direct execution interface for an Agent (solver) to call a tool, + with mandatory input and output validation. + + This method orchestrates the full lifecycle: retrieval, input validation, + execution, and output validation. Args: name: The unique name of the tool to execute. - tool_kwargs: Keyword arguments for the tool, expected to be validated by the caller. + tool_kwargs: Raw keyword arguments from the orchestrator. Returns: - The raw dictionary output from the tool's `tool_call` function. + The validated :class:`ToolOutput` Pydantic object. Raises: - ValueError: If the requested tool name is unknown for this ToolBox. - RuntimeError: If the execution of the tool's `tool_call` function fails. + ValueError: If the tool name is unknown or if input validation fails. + RuntimeError: If tool execution or output validation fails. """ tool: Optional[AgentTool] = self.get_tool(name) if tool: try: - # Execution assumes kwargs are already validated/sanitized by the orchestrator - return tool.tool_call(**tool_kwargs) + # 1. Input Validation and Instantiation + validated_args: ToolArguments = self.validate_input(tool, tool_kwargs) + except ValueError as e: + # Re-raise with more context + raise ValueError(f"Tool input validation failed for '{name}' in ToolBox '{self.toolbox_id}'") from e + + try: + # 2. Tool Execution + raw_result: Dict[str, Any] = tool.tool_call(validated_args) except Exception as e: - # Wrap tool execution errors for better context + # Catch execution errors raise RuntimeError(f"Tool execution failed for '{name}' in ToolBox '{self.toolbox_id}'") from e + + try: + # 3. Output Validation + return self.validate_output(tool, raw_result) + except ValueError as e: + # Catch Pydantic output ValidationErrors + raise RuntimeError(f"Tool output validation failed for '{name}' in ToolBox '{self.toolbox_id}'") from e else: raise ValueError(f"Unknown tool '{name}' for ToolBox '{self.toolbox_id}'.") @@ -187,6 +253,7 @@ def tool_json_list(self) -> List[Dict[str, Union[str, Dict[str, Any]]]]: { "name": tool.name, "description": tool.description, + # Use Pydantic's .model_json_schema() for JSON schema export "argument_schema": tool.argument_schema.model_json_schema(), "output_schema": tool.output_schema.model_json_schema() } From ad6801b243fadeb9236cae15796c2bf440895acc Mon Sep 17 00:00:00 2001 From: miro Date: Fri, 17 Oct 2025 15:38:04 +0100 Subject: [PATCH 05/13] better output validation --- ovos_plugin_manager/templates/agent_tools.py | 60 +++++++++++++------- 1 file changed, 39 insertions(+), 21 deletions(-) diff --git a/ovos_plugin_manager/templates/agent_tools.py b/ovos_plugin_manager/templates/agent_tools.py index d5da3b03..a036928c 100644 --- a/ovos_plugin_manager/templates/agent_tools.py +++ b/ovos_plugin_manager/templates/agent_tools.py @@ -7,7 +7,6 @@ from pydantic import BaseModel, Field - # Base Pydantic Model for Tool Input/Arguments class ToolArguments(BaseModel): """Base class for Pydantic models defining tool input/arguments.""" @@ -19,8 +18,10 @@ class ToolOutput(BaseModel): """Base class for Pydantic models defining tool output structure.""" pass + # --- Type Aliases for Clarity --- -ToolCallFunc = Callable[[ToolArguments], Dict[str, Any]] +ToolCallReturn = Union[Dict[str, Any], ToolOutput] +ToolCallFunc = Callable[[ToolArguments], ToolCallReturn] @dataclass @@ -36,9 +37,10 @@ class AgentTool: argument_schema: Type[ToolArguments] = field(metadata={'help': 'Pydantic model defining the expected input/arguments.'}) output_schema: Type[ToolOutput] = field(metadata={'help': 'Pydantic model defining the expected output structure.'}) tool_call: ToolCallFunc = field( - metadata={'help': 'The function to execute the tool logic. It accepts one positional argument (an instantiated ToolArguments model) and must return a Dict[str, Any] conforming to output_schema.'} + metadata={'help': 'The function to execute the tool logic. It accepts one positional argument (an instantiated ToolArguments model) and must return a Dict[str, Any] or an instantiated ToolOutput model.'} ) + class ToolBox(ABC): """ Abstract base class for a ToolBox plugin. @@ -62,7 +64,7 @@ def __init__(self, toolbox_id: str, # Internal cache for discovered tools, mapped by name self.tools: Dict[str, AgentTool] = {} try: - self.discover_tools() # try to find tools immediately + self.discover_tools() # try to find tools immediately except Exception as e: pass # will be lazy loaded or throw error on first usage @@ -176,7 +178,6 @@ def validate_output(tool: AgentTool, raw_result: Dict[str, Any]) -> ToolOutput: except Exception as e: raise ValueError(f"Invalid output from '{tool.name}': {raw_result}") from e - def call_tool(self, name: str, tool_kwargs: Dict[str, Any]) -> ToolOutput: """ Direct execution interface for an Agent (solver) to call a tool, @@ -197,29 +198,46 @@ def call_tool(self, name: str, tool_kwargs: Dict[str, Any]) -> ToolOutput: RuntimeError: If tool execution or output validation fails. """ tool: Optional[AgentTool] = self.get_tool(name) - if tool: - try: - # 1. Input Validation and Instantiation - validated_args: ToolArguments = self.validate_input(tool, tool_kwargs) - except ValueError as e: - # Re-raise with more context - raise ValueError(f"Tool input validation failed for '{name}' in ToolBox '{self.toolbox_id}'") from e + if not tool: + raise ValueError(f"Unknown tool '{name}' for ToolBox '{self.toolbox_id}'.") - try: - # 2. Tool Execution - raw_result: Dict[str, Any] = tool.tool_call(validated_args) - except Exception as e: - # Catch execution errors - raise RuntimeError(f"Tool execution failed for '{name}' in ToolBox '{self.toolbox_id}'") from e + try: + # 1. Input Validation and Instantiation + validated_args: ToolArguments = self.validate_input(tool, tool_kwargs) + except ValueError as e: + # Re-raise with more context + raise ValueError(f"Tool input validation failed for '{name}' in ToolBox '{self.toolbox_id}'") from e + try: + # 2. Tool Execution + raw_or_validated_result: ToolCallReturn = tool.tool_call(validated_args) + except Exception as e: + # Re-raise with more context + raise RuntimeError(f"Tool execution failed for '{name}' in ToolBox '{self.toolbox_id}'") from e + + # 3. Output Validation/Casting + if isinstance(raw_or_validated_result, ToolOutput): + # Case A: Tool returned an already validated Pydantic model. + # We perform a quick type check to ensure it matches the declared schema. + if not isinstance(raw_or_validated_result, tool.output_schema): + raise RuntimeError( + f"Tool '{name}' returned model of type {type(raw_or_validated_result).__name__}, " + f"but expected {tool.output_schema.__name__}." + ) + return raw_or_validated_result + elif isinstance(raw_or_validated_result, dict): + # Case B: Tool returned a raw dictionary (needs validation). try: - # 3. Output Validation - return self.validate_output(tool, raw_result) + return self.validate_output(tool, raw_or_validated_result) except ValueError as e: # Catch Pydantic output ValidationErrors raise RuntimeError(f"Tool output validation failed for '{name}' in ToolBox '{self.toolbox_id}'") from e else: - raise ValueError(f"Unknown tool '{name}' for ToolBox '{self.toolbox_id}'.") + # Case C: Tool returned an unexpected type. + raise RuntimeError( + f"Tool '{name}' returned an unexpected type: {type(raw_or_validated_result).__name__}. " + "Must return Dict[str, Any] or ToolOutput." + ) def get_tool(self, name: str) -> Optional[AgentTool]: """ From 7f1634aad0b68a8985c8667349b9e4de40eee88e Mon Sep 17 00:00:00 2001 From: miro Date: Fri, 17 Oct 2025 15:51:17 +0100 Subject: [PATCH 06/13] better input validation --- ovos_plugin_manager/templates/agent_tools.py | 31 +++++++++++++++----- 1 file changed, 24 insertions(+), 7 deletions(-) diff --git a/ovos_plugin_manager/templates/agent_tools.py b/ovos_plugin_manager/templates/agent_tools.py index a036928c..da57263d 100644 --- a/ovos_plugin_manager/templates/agent_tools.py +++ b/ovos_plugin_manager/templates/agent_tools.py @@ -178,7 +178,7 @@ def validate_output(tool: AgentTool, raw_result: Dict[str, Any]) -> ToolOutput: except Exception as e: raise ValueError(f"Invalid output from '{tool.name}': {raw_result}") from e - def call_tool(self, name: str, tool_kwargs: Dict[str, Any]) -> ToolOutput: + def call_tool(self, name: str, tool_kwargs: Union[ToolArguments, Dict[str, Any]]) -> ToolOutput: """ Direct execution interface for an Agent (solver) to call a tool, with mandatory input and output validation. @@ -201,12 +201,29 @@ def call_tool(self, name: str, tool_kwargs: Dict[str, Any]) -> ToolOutput: if not tool: raise ValueError(f"Unknown tool '{name}' for ToolBox '{self.toolbox_id}'.") - try: - # 1. Input Validation and Instantiation - validated_args: ToolArguments = self.validate_input(tool, tool_kwargs) - except ValueError as e: - # Re-raise with more context - raise ValueError(f"Tool input validation failed for '{name}' in ToolBox '{self.toolbox_id}'") from e + # 1. Input Validation and Instantiation + if isinstance(tool_kwargs, ToolArguments): + # Case A: Input is an already validated Pydantic model. + # We perform a quick type check to ensure it matches the declared schema. + if not isinstance(tool_kwargs, tool.argument_schema): + raise ValueError( + f"Tool '{name}' called with model of type {type(tool_kwargs).__name__}, " + f"but expected {tool.output_schema.__name__}." + ) + validated_args: ToolArguments = tool_kwargs + elif isinstance(tool_kwargs, dict): + # Case B: Input is a raw dictionary (needs validation). + try: + validated_args: ToolArguments = self.validate_input(tool, tool_kwargs) + except ValueError as e: + # Re-raise with more context + raise ValueError(f"Tool input validation failed for '{name}' in ToolBox '{self.toolbox_id}'") from e + else: + # Case C: Input is an unexpected type. + raise RuntimeError( + f"Tool '{name}' called with unexpected type arguments: {type(tool_kwargs).__name__}. " + "Must be Dict[str, Any] or ToolArguments." + ) try: # 2. Tool Execution From 765e6b3e43c371d1ce951b75f622747c687ffd1b Mon Sep 17 00:00:00 2001 From: JarbasAI <33701864+JarbasAl@users.noreply.github.com> Date: Fri, 17 Oct 2025 16:15:47 +0100 Subject: [PATCH 07/13] Update ovos_plugin_manager/templates/agent_tools.py Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com> --- ovos_plugin_manager/templates/agent_tools.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/ovos_plugin_manager/templates/agent_tools.py b/ovos_plugin_manager/templates/agent_tools.py index da57263d..d3b6b446 100644 --- a/ovos_plugin_manager/templates/agent_tools.py +++ b/ovos_plugin_manager/templates/agent_tools.py @@ -206,10 +206,11 @@ def call_tool(self, name: str, tool_kwargs: Union[ToolArguments, Dict[str, Any]] # Case A: Input is an already validated Pydantic model. # We perform a quick type check to ensure it matches the declared schema. if not isinstance(tool_kwargs, tool.argument_schema): - raise ValueError( - f"Tool '{name}' called with model of type {type(tool_kwargs).__name__}, " - f"but expected {tool.output_schema.__name__}." - ) + if not isinstance(tool_kwargs, tool.argument_schema): + raise ValueError( + f"Tool '{name}' called with model of type {type(tool_kwargs).__name__}, " + f"but expected {tool.argument_schema.__name__}." + ) validated_args: ToolArguments = tool_kwargs elif isinstance(tool_kwargs, dict): # Case B: Input is a raw dictionary (needs validation). From 86e38d233b46bbc23b8e1db35af5f14d79d0d215 Mon Sep 17 00:00:00 2001 From: miro Date: Fri, 17 Oct 2025 16:57:14 +0100 Subject: [PATCH 08/13] fix coderabbit mess --- ovos_plugin_manager/templates/agent_tools.py | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/ovos_plugin_manager/templates/agent_tools.py b/ovos_plugin_manager/templates/agent_tools.py index d3b6b446..3cc8886e 100644 --- a/ovos_plugin_manager/templates/agent_tools.py +++ b/ovos_plugin_manager/templates/agent_tools.py @@ -206,11 +206,10 @@ def call_tool(self, name: str, tool_kwargs: Union[ToolArguments, Dict[str, Any]] # Case A: Input is an already validated Pydantic model. # We perform a quick type check to ensure it matches the declared schema. if not isinstance(tool_kwargs, tool.argument_schema): - if not isinstance(tool_kwargs, tool.argument_schema): - raise ValueError( - f"Tool '{name}' called with model of type {type(tool_kwargs).__name__}, " - f"but expected {tool.argument_schema.__name__}." - ) + raise ValueError( + f"Tool '{name}' called with model of type {type(tool_kwargs).__name__}, " + f"but expected {tool.argument_schema.__name__}." + ) validated_args: ToolArguments = tool_kwargs elif isinstance(tool_kwargs, dict): # Case B: Input is a raw dictionary (needs validation). From 7691d6142f2692fefd7986965fc6a315f54dc55b Mon Sep 17 00:00:00 2001 From: miro Date: Tue, 17 Mar 2026 15:00:33 +0000 Subject: [PATCH 09/13] fix: agent_tools bug fixes, Apache header, docs, and tests MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Add Apache 2.0 license header (fixes License CI) - Fix discover_tools() result not stored in __init__ (tools dict was always empty after init) - Add LOG.debug on discover failure instead of silent except: pass - Remove unused `Field` import (pydantic) - docs: add docs/api/agent-tools.md — ToolBox plugin API reference - docs: update docs/index.md to link agent-tools.md - test: add test/unittests/test_agent_tools.py — 21 tests covering init, call_tool (dict/model/error paths), lazy refresh, bus handlers, json schema Co-Authored-By: Claude Sonnet 4.6 --- docs/api/agent-tools.md | 146 +++++++++++ docs/index.md | 1 + ovos_plugin_manager/templates/agent_tools.py | 18 +- test/unittests/test_agent_tools.py | 247 +++++++++++++++++++ 4 files changed, 409 insertions(+), 3 deletions(-) create mode 100644 docs/api/agent-tools.md create mode 100644 test/unittests/test_agent_tools.py diff --git a/docs/api/agent-tools.md b/docs/api/agent-tools.md new file mode 100644 index 00000000..de6cc124 --- /dev/null +++ b/docs/api/agent-tools.md @@ -0,0 +1,146 @@ +# Agent Tool Plugins + +**Entry point:** `opm.persona.tool` + +Tool plugins extend personas with executable functions. A `ToolBox` groups related tools, handles bus-based discovery, and validates inputs/outputs via Pydantic. + +--- + +## Core Classes + +### `AgentTool` — `ovos_plugin_manager/templates/agent_tools.py` + +A dataclass defining a single executable function and its contract. + +| Field | Type | Description | +|---|---|---| +| `name` | `str` | Unique snake_case identifier used by agents/LLMs to reference the tool. | +| `description` | `str` | Natural language description; essential for LLM reasoning. | +| `argument_schema` | `Type[ToolArguments]` | Pydantic model defining required input structure. | +| `output_schema` | `Type[ToolOutput]` | Pydantic model defining guaranteed output structure. | +| `tool_call` | `Callable[[ToolArguments], ToolCallReturn]` | Function that executes the tool logic. | + +### `ToolArguments` / `ToolOutput` + +Base Pydantic models for tool contracts. Subclass these when defining a tool. JSON Schema is generated automatically via `model_json_schema()` for LLM consumption. + +### `ToolBox` (ABC) — `ovos_plugin_manager/templates/agent_tools.py` + +Abstract base class for tool plugins. Groups related `AgentTool` instances, registers messagebus handlers, and enforces validation. + +**Abstract method — must implement:** + +```python +def discover_tools(self) -> List[AgentTool] +``` + +Returns the list of tools provided by this plugin. Called at init and on `refresh_tools()`. Must be idempotent. + +**Key methods:** + +| Method | Description | +|---|---| +| `bind(bus)` | Attach to messagebus; registers discovery and call handlers. | +| `call_tool(name, tool_kwargs)` | Execute a tool by name with full input/output validation. | +| `get_tool(name)` | Retrieve an `AgentTool` by name; triggers lazy refresh if not cached. | +| `refresh_tools()` | Re-run `discover_tools()` and update the internal cache. | + +**Property:** + +```python +@property +def tool_json_list(self) -> List[Dict] +``` + +Returns all tools serialized with JSON Schema argument/output schemas — suitable for sending to an LLM's `tools` API parameter. + +--- + +## Messagebus Interface + +Tools expose themselves on the bus automatically when `bind(bus)` is called. + +| Message | Direction | Description | +|---|---|---| +| `ovos.persona.tools.discover` | → ToolBox | Broadcast discovery request. | +| `ovos.persona.tools.discover` (response) | ← ToolBox | Returns `{"tools": [...], "toolbox_id": "..."}`. | +| `ovos.persona.tools..call` | → ToolBox | Call a specific tool with `{"name": "...", "kwargs": {...}}`. | +| `ovos.persona.tools..call` (response) | ← ToolBox | Returns `{"result": {...}}` or `{"error": "..."}`. | + +This allows agent plugins in separate processes (e.g. MCP or UTCP servers) to discover and call tools dynamically without importing the plugin directly. + +--- + +## Plugin Registration + +Register your `ToolBox` subclass in `pyproject.toml`: + +```toml +[project.entry-points."opm.persona.tool"] +my_toolbox = "my_package:MyToolBox" +``` + +--- + +## Writing a ToolBox Plugin + +```python +from ovos_plugin_manager.templates.agent_tools import AgentTool, ToolArguments, ToolBox, ToolOutput +from pydantic import Field +from typing import List + + +class WeatherArgs(ToolArguments): + location: str = Field(..., description="City name or coordinates.") + + +class WeatherOutput(ToolOutput): + temperature_c: float + condition: str + + +def fetch_weather(args: WeatherArgs) -> WeatherOutput: + # ... call weather API ... + return WeatherOutput(temperature_c=21.5, condition="Sunny") + + +class WeatherToolBox(ToolBox): + def __init__(self, bus=None): + super().__init__(toolbox_id="weather_tools", bus=bus) + + def discover_tools(self) -> List[AgentTool]: + return [ + AgentTool( + name="get_weather", + description="Get the current weather for a location.", + argument_schema=WeatherArgs, + output_schema=WeatherOutput, + tool_call=fetch_weather, + ) + ] +``` + +--- + +## Validation Behaviour + +`call_tool()` enforces a strict lifecycle: + +1. **Input** — if `tool_kwargs` is a `dict`, validated against `argument_schema` via Pydantic. If already a `ToolArguments` instance, type-checked against the declared schema. +2. **Execution** — `tool.tool_call(validated_args)` is called. +3. **Output** — if the result is a `dict`, validated against `output_schema`. If already a `ToolOutput` instance, type-checked. + +On failure: `ValueError` for input problems, `RuntimeError` for execution or output problems. + +Errors from `discover_tools()` at init are logged at `DEBUG` level and retried lazily on first `get_tool()` call. This supports dynamic discovery plugins (MCP, UTCP) where tools may not be available at startup. + +--- + +## Discovery + +```python +from ovos_plugin_manager.persona import find_toolbox_plugins + +plugins = find_toolbox_plugins() +# {"my_toolbox": MyToolBox, ...} +``` diff --git a/docs/index.md b/docs/index.md index d38595f2..a39c5eac 100644 --- a/docs/index.md +++ b/docs/index.md @@ -21,6 +21,7 @@ loading installed plugins at runtime via Python entry points. - [PHAL](api/phal.md) — Platform/Hardware Abstraction Layer - [Transformers](api/transformers.md) — Audio, Utterance, Dialog, Metadata, TTS, Intent transformers - [Agents & Solvers](api/agents.md) — NLP solver and agent engine plugins + - [Agent Tools](api/agent-tools.md) — ToolBox plugin API (`opm.persona.tool`) - [Pipeline](api/pipeline.md) — Intent matching pipeline plugins - [Language](api/language.md) — Translation and language detection plugins diff --git a/ovos_plugin_manager/templates/agent_tools.py b/ovos_plugin_manager/templates/agent_tools.py index 3cc8886e..e2e970c9 100644 --- a/ovos_plugin_manager/templates/agent_tools.py +++ b/ovos_plugin_manager/templates/agent_tools.py @@ -1,10 +1,22 @@ +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. from abc import ABC, abstractmethod from dataclasses import dataclass, field from typing import Type, Any, Dict, List, Callable, Optional, Union from ovos_bus_client import MessageBusClient, Message from ovos_utils.fakebus import FakeBus -from pydantic import BaseModel, Field +from ovos_utils.log import LOG +from pydantic import BaseModel # Base Pydantic Model for Tool Input/Arguments @@ -64,9 +76,9 @@ def __init__(self, toolbox_id: str, # Internal cache for discovered tools, mapped by name self.tools: Dict[str, AgentTool] = {} try: - self.discover_tools() # try to find tools immediately + self.tools = {tool.name: tool for tool in self.discover_tools()} except Exception as e: - pass # will be lazy loaded or throw error on first usage + LOG.debug(f"ToolBox '{toolbox_id}' failed initial tool discovery, will retry on first use: {e}") # Initialize the messagebus connection if provided if bus: diff --git a/test/unittests/test_agent_tools.py b/test/unittests/test_agent_tools.py new file mode 100644 index 00000000..30546c65 --- /dev/null +++ b/test/unittests/test_agent_tools.py @@ -0,0 +1,247 @@ +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +import unittest +from typing import List +from unittest.mock import MagicMock, patch + +from ovos_utils.fakebus import FakeBus +from pydantic import Field + +from ovos_plugin_manager.templates.agent_tools import ( + AgentTool, + ToolArguments, + ToolBox, + ToolOutput, +) + + +# --------------------------------------------------------------------------- +# Minimal concrete fixtures +# --------------------------------------------------------------------------- + +class AddArgs(ToolArguments): + a: int = Field(..., description="First operand.") + b: int = Field(..., description="Second operand.") + + +class AddOutput(ToolOutput): + result: int = Field(..., description="Sum of a and b.") + + +def add_logic(args: AddArgs) -> AddOutput: + return AddOutput(result=args.a + args.b) + + +def failing_logic(args: AddArgs) -> AddOutput: + raise RuntimeError("intentional failure") + + +class MathToolBox(MathToolBox if False else object): + pass + + +class MathToolBox(ToolBox): + def __init__(self, bus=None, fail_discover: bool = False): + self._fail_discover = fail_discover + super().__init__(toolbox_id="math_tools", bus=bus) + + def discover_tools(self) -> List[AgentTool]: + if self._fail_discover: + raise RuntimeError("discover failed") + return [ + AgentTool( + name="add", + description="Add two integers.", + argument_schema=AddArgs, + output_schema=AddOutput, + tool_call=add_logic, + ) + ] + + +class FailingToolBox(ToolBox): + def __init__(self, bus=None): + super().__init__(toolbox_id="failing_tools", bus=bus) + + def discover_tools(self) -> List[AgentTool]: + return [ + AgentTool( + name="fail", + description="Always raises.", + argument_schema=AddArgs, + output_schema=AddOutput, + tool_call=failing_logic, + ) + ] + + +# --------------------------------------------------------------------------- +# Tests +# --------------------------------------------------------------------------- + +class TestAgentToolDataclass(unittest.TestCase): + def setUp(self): + self.tool = AgentTool( + name="add", + description="Add two integers.", + argument_schema=AddArgs, + output_schema=AddOutput, + tool_call=add_logic, + ) + + def test_fields(self): + self.assertEqual(self.tool.name, "add") + self.assertEqual(self.tool.argument_schema, AddArgs) + self.assertEqual(self.tool.output_schema, AddOutput) + + def test_tool_call_executes(self): + result = self.tool.tool_call(AddArgs(a=2, b=3)) + self.assertEqual(result.result, 5) + + +class TestToolBoxInit(unittest.TestCase): + def test_tools_populated_on_init(self): + tb = MathToolBox() + self.assertIn("add", tb.tools) + + def test_failed_discover_logs_and_continues(self): + with patch("ovos_plugin_manager.templates.agent_tools.LOG") as mock_log: + tb = MathToolBox(fail_discover=True) + mock_log.debug.assert_called_once() + self.assertEqual(tb.tools, {}) + + def test_bus_bind_on_init(self): + bus = FakeBus() + tb = MathToolBox(bus=bus) + self.assertIs(tb.bus, bus) + + def test_no_bus_on_init(self): + tb = MathToolBox() + self.assertIsNone(tb.bus) + + +class TestToolBoxCallTool(unittest.TestCase): + def setUp(self): + self.tb = MathToolBox() + + def test_call_with_dict(self): + result = self.tb.call_tool("add", {"a": 3, "b": 4}) + self.assertIsInstance(result, AddOutput) + self.assertEqual(result.result, 7) + + def test_call_with_pydantic_model(self): + args = AddArgs(a=10, b=20) + result = self.tb.call_tool("add", args) + self.assertEqual(result.result, 30) + + def test_call_unknown_tool_raises_value_error(self): + with self.assertRaises(ValueError): + self.tb.call_tool("nonexistent", {"a": 1, "b": 2}) + + def test_call_invalid_input_raises_value_error(self): + with self.assertRaises(ValueError): + self.tb.call_tool("add", {"a": "not_an_int", "b": 2}) + + def test_call_wrong_pydantic_type_raises_value_error(self): + class OtherArgs(ToolArguments): + x: int = Field(...) + + with self.assertRaises(ValueError): + self.tb.call_tool("add", OtherArgs(x=1)) + + def test_call_unexpected_kwarg_type_raises_runtime_error(self): + with self.assertRaises(RuntimeError): + self.tb.call_tool("add", "not_a_dict_or_model") + + def test_execution_failure_raises_runtime_error(self): + tb = FailingToolBox() + with self.assertRaises(RuntimeError): + tb.call_tool("fail", {"a": 1, "b": 2}) + + +class TestToolBoxDiscovery(unittest.TestCase): + def test_refresh_tools(self): + tb = MathToolBox() + tb.tools = {} # clear cache + tb.refresh_tools() + self.assertIn("add", tb.tools) + + def test_get_tool_lazy_refresh(self): + tb = MathToolBox() + tb.tools = {} # simulate empty cache + tool = tb.get_tool("add") + self.assertIsNotNone(tool) + self.assertEqual(tool.name, "add") + + def test_get_tool_missing_returns_none(self): + tb = MathToolBox() + result = tb.get_tool("nonexistent") + self.assertIsNone(result) + + +class TestToolJsonList(unittest.TestCase): + def test_json_list_structure(self): + tb = MathToolBox() + lst = tb.tool_json_list + self.assertEqual(len(lst), 1) + entry = lst[0] + self.assertEqual(entry["name"], "add") + self.assertIn("argument_schema", entry) + self.assertIn("output_schema", entry) + # JSON schema format + self.assertIn("properties", entry["argument_schema"]) + + def test_json_list_empty_when_no_tools(self): + tb = MathToolBox(fail_discover=True) + self.assertEqual(tb.tool_json_list, []) + + +class TestToolBoxBusHandlers(unittest.TestCase): + def setUp(self): + self.bus = FakeBus() + self.tb = MathToolBox(bus=self.bus) + + def test_handle_discover_emits_response(self): + responses = [] + self.bus.on("ovos.persona.tools.discover.response", + lambda m: responses.append(m)) + from ovos_bus_client import Message + self.bus.emit(Message("ovos.persona.tools.discover")) + self.bus.wait_for_response # FakeBus is synchronous + # handler emits response directly + self.assertEqual(len(responses), 1) + self.assertIn("tools", responses[0].data) + self.assertEqual(responses[0].data["toolbox_id"], "math_tools") + + def test_handle_call_success(self): + responses = [] + self.bus.on("ovos.persona.tools.math_tools.call.response", + lambda m: responses.append(m)) + from ovos_bus_client import Message + self.bus.emit(Message("ovos.persona.tools.math_tools.call", + {"name": "add", "kwargs": {"a": 5, "b": 6}})) + self.assertEqual(len(responses), 1) + self.assertEqual(responses[0].data["result"]["result"], 11) + + def test_handle_call_error(self): + responses = [] + self.bus.on("ovos.persona.tools.math_tools.call.response", + lambda m: responses.append(m)) + from ovos_bus_client import Message + self.bus.emit(Message("ovos.persona.tools.math_tools.call", + {"name": "nonexistent", "kwargs": {}})) + self.assertEqual(len(responses), 1) + self.assertIn("error", responses[0].data) + + +if __name__ == "__main__": + unittest.main() From 813bbf97836ec283628d8f656bc03e1592976205 Mon Sep 17 00:00:00 2001 From: miro Date: Tue, 17 Mar 2026 15:09:09 +0000 Subject: [PATCH 10/13] feat: move tool plugins to opm.agents namespace, add AgenticLoopEngine MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - PluginTypes: add AGENT_TOOLBOX (opm.agents.toolbox) and AGENT_LOOP (opm.agents.loop) - PluginTypes: remove PERSONA_TOOL (opm.persona.tool) — wrong namespace for tool plugins - PluginConfigTypes: add AGENT_TOOLBOX and AGENT_LOOP config entries - persona.py: find_toolbox_plugins() now uses AGENT_TOOLBOX - agent_tools.py: update ToolBox docstring to reference correct entry point group - templates/agents.py: add AgenticLoopEngine(ChatEngine) — opm.agents.loop entry point - standard load_toolboxes(toolboxes) interface for persona-injected ToolBoxes - presents as ChatEngine to all callers; loop internals are implementation details - docs/api/agents.md: document AgenticLoopEngine and persona config toolboxes pattern - docs/api/agent-tools.md: fix entry point to opm.agents.toolbox Co-Authored-By: Claude Sonnet 4.6 --- docs/api/agent-tools.md | 4 +- docs/api/agents.md | 33 +- ovos_plugin_manager/persona.py | 7 +- ovos_plugin_manager/templates/agent_tools.py | 2 + ovos_plugin_manager/templates/agents.py | 67 ++++ ovos_plugin_manager/utils/__init__.py | 5 +- uv.lock | 346 ++++++++++++++++++- 7 files changed, 449 insertions(+), 15 deletions(-) diff --git a/docs/api/agent-tools.md b/docs/api/agent-tools.md index de6cc124..bf415cd5 100644 --- a/docs/api/agent-tools.md +++ b/docs/api/agent-tools.md @@ -1,6 +1,6 @@ # Agent Tool Plugins -**Entry point:** `opm.persona.tool` +**Entry point:** `opm.agents.toolbox` Tool plugins extend personas with executable functions. A `ToolBox` groups related tools, handles bus-based discovery, and validates inputs/outputs via Pydantic. @@ -76,7 +76,7 @@ This allows agent plugins in separate processes (e.g. MCP or UTCP servers) to di Register your `ToolBox` subclass in `pyproject.toml`: ```toml -[project.entry-points."opm.persona.tool"] +[project.entry-points."opm.agents.toolbox"] my_toolbox = "my_package:MyToolBox" ``` diff --git a/docs/api/agents.md b/docs/api/agents.md index 47bfd88c..d0abdb45 100644 --- a/docs/api/agents.md +++ b/docs/api/agents.md @@ -53,13 +53,44 @@ Streaming/non-streaming conversational LLM backend. #### Abstract method ```python -def chat(self, messages: List[AgentMessage], lang: Optional[str] = None) -> Optional[str] +def continue_chat(self, messages: List[AgentMessage], + session_id: str = "default", + lang: Optional[str] = None, + units: Optional[str] = None) -> AgentMessage ``` Generate a response given a list of `AgentMessage` objects. --- +### `AgenticLoopEngine` + +**Entry point:** `opm.agents.loop` + +A `ChatEngine` subclass for plugins that implement an internal agent loop (ReAct, tool-call/observe, background workers, etc.). Callers treat it identically to `ChatEngine` — the loop is an implementation detail. + +Adds a standard toolbox registration interface: + +```python +def load_toolboxes(self, toolboxes: List[ToolBox]) -> None +``` + +Called by the persona loader when the persona config declares a `toolboxes` list. Plugins may also discover and load toolboxes internally. + +Persona config example: + +```json +{ + "name": "MyAgent", + "solvers": ["ovos-react-agent"], + "toolboxes": ["web_search_tools", "file_tools"] +} +``` + +See [Agent Tools](agent-tools.md) for the `ToolBox` plugin API. + +--- + ### `RetrievalEngine` **Entry point:** `opm.agents.retrieval` diff --git a/ovos_plugin_manager/persona.py b/ovos_plugin_manager/persona.py index 00f8d51f..fe51cfe2 100644 --- a/ovos_plugin_manager/persona.py +++ b/ovos_plugin_manager/persona.py @@ -15,8 +15,9 @@ def find_persona_plugins() -> Dict[str, Dict[str, Any]]: def find_toolbox_plugins() -> Dict[str, Type[ToolBox]]: """ - Find all installed Toolbox plugins - @return: dict toolbox_id to entrypoints (ToolBox) + Find all installed ToolBox plugins. + + @return: dict of toolbox_id to ToolBox subclass """ from ovos_plugin_manager.utils import find_plugins - return find_plugins(PluginTypes.PERSONA_TOOL) + return find_plugins(PluginTypes.AGENT_TOOLBOX) diff --git a/ovos_plugin_manager/templates/agent_tools.py b/ovos_plugin_manager/templates/agent_tools.py index e2e970c9..1d44ad29 100644 --- a/ovos_plugin_manager/templates/agent_tools.py +++ b/ovos_plugin_manager/templates/agent_tools.py @@ -59,6 +59,8 @@ class ToolBox(ABC): Each ToolBox is a discoverable plugin that groups related AgentTools. It exposes tools as services over the OVOS messagebus and provides a direct execution interface. + + Entry point group: ``opm.agents.toolbox`` """ def __init__(self, toolbox_id: str, diff --git a/ovos_plugin_manager/templates/agents.py b/ovos_plugin_manager/templates/agents.py index 59d2908c..c8056d91 100644 --- a/ovos_plugin_manager/templates/agents.py +++ b/ovos_plugin_manager/templates/agents.py @@ -382,6 +382,73 @@ def get_response(self, utterance: str, units=units).content +class AgenticLoopEngine(ChatEngine): + """ + A ``ChatEngine`` subclass for plugins that implement an internal agent loop + (e.g. ReAct, tool-call/observe cycles, background worker agents). + + From the perspective of a ``PersonaService`` or any caller, an + ``AgenticLoopEngine`` is identical to a ``ChatEngine`` — it receives a list + of ``AgentMessage`` objects and returns one. All loop mechanics, tool + dispatch, retries, and background tasks are implementation details hidden + inside the plugin. + + The ``toolboxes`` attribute gives OPM and persona configs a standard place + to inject ``ToolBox`` instances. Plugins are free to discover and load + additional toolboxes internally. + + Entry point group: ``opm.agents.loop`` + """ + + def __init__(self, config: Optional[Dict[str, Any]] = None): + """ + Initialise the engine and set up an empty toolbox registry. + + Args: + config: Plugin-specific configuration dictionary. + """ + super().__init__(config=config) + self.toolboxes: List[Any] = [] # List[ToolBox] — avoid circular import + + def load_toolboxes(self, toolboxes: List[Any]) -> None: + """ + Register a list of ``ToolBox`` instances with this engine. + + Called by the persona loader when the persona config declares a + ``toolboxes`` list. Plugins may also call this directly or discover + toolboxes on their own inside ``continue_chat``. + + Args: + toolboxes: Instantiated ``ToolBox`` objects to make available to + the agent loop. + """ + self.toolboxes = list(toolboxes) + + @abc.abstractmethod + def continue_chat(self, messages: List[AgentMessage], + session_id: str = "default", + lang: Optional[str] = None, + units: Optional[str] = None) -> AgentMessage: + """ + Run the agent loop and return the final response. + + The implementation is responsible for all internal steps: tool + selection, execution, observation, and iteration. The caller always + receives a single ``AgentMessage`` with ``MessageRole.ASSISTANT``. + + Args: + messages: Full conversation history up to and including the latest + user turn. + session_id: Conversation session identifier. + lang: BCP-47 language code. + units: Preferred measurement system (``"metric"`` / ``"imperial"``). + + Returns: + The assistant's final response after the loop has completed. + """ + raise NotImplementedError() + + class SummarizerEngine(AbstractAgentEngine): """Engine designed for condensing long documents into concise summaries.""" diff --git a/ovos_plugin_manager/utils/__init__.py b/ovos_plugin_manager/utils/__init__.py index e03c0632..7cf00a4e 100644 --- a/ovos_plugin_manager/utils/__init__.py +++ b/ovos_plugin_manager/utils/__init__.py @@ -98,7 +98,8 @@ class PluginTypes(str, Enum): VIDEO_PLAYER = "opm.media.video" WEB_PLAYER = "opm.media.web" PERSONA = "opm.plugin.persona" # personas are a dict, they have no config because they ARE a config - PERSONA_TOOL = "opm.persona.tool" + AGENT_TOOLBOX = "opm.agents.toolbox" + AGENT_LOOP = "opm.agents.loop" # solver plugins are deprecated! QUESTION_SOLVER = "opm.solver.question" @@ -152,6 +153,8 @@ class PluginConfigTypes(str, Enum): AGENT_NLI = "opm.agents.nli.config" AGENT_COREF = "opm.agents.coref.config" AGENT_YES_NO = "opm.agents.yesno.config" + AGENT_TOOLBOX = "opm.agents.toolbox.config" + AGENT_LOOP = "opm.agents.loop.config" KEYWORD_EXTRACTION = "opm.keywords.config" UTTERANCE_SEGMENTATION = "opm.segmentation.config" TOKENIZATION = "opm.tokenization.config" diff --git a/uv.lock b/uv.lock index d040d863..74f7f54f 100644 --- a/uv.lock +++ b/uv.lock @@ -4,7 +4,8 @@ requires-python = ">=3.9" resolution-markers = [ "python_full_version >= '3.13'", "python_full_version == '3.12.*'", - "python_full_version >= '3.10' and python_full_version < '3.12'", + "python_full_version == '3.11.*'", + "python_full_version == '3.10.*'", "python_full_version < '3.10'", ] @@ -200,7 +201,8 @@ source = { registry = "https://pypi.org/simple" } resolution-markers = [ "python_full_version >= '3.13'", "python_full_version == '3.12.*'", - "python_full_version >= '3.10' and python_full_version < '3.12'", + "python_full_version == '3.11.*'", + "python_full_version == '3.10.*'", ] dependencies = [ { name = "colorama", marker = "python_full_version >= '3.10' and sys_platform == 'win32'" }, @@ -219,6 +221,20 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6", size = 25335, upload-time = "2022-10-25T02:36:20.889Z" }, ] +[[package]] +name = "colorspacious" +version = "1.1.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "numpy", version = "2.0.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, + { name = "numpy", version = "2.2.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.10.*'" }, + { name = "numpy", version = "2.4.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/75/e4/aa41ae14c5c061205715006c8834496d86ec7500f1edda5981f0f0190cc6/colorspacious-1.1.2.tar.gz", hash = "sha256:5e9072e8cdca889dac445c35c9362a22ccf758e97b00b79ff0d5a7ba3e11b618", size = 688573, upload-time = "2018-04-08T04:27:30.83Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ab/a1/318b9aeca7b9856410ededa4f52d6f82174d1a41e64bdd70d951e532675a/colorspacious-1.1.2-py2.py3-none-any.whl", hash = "sha256:c78befa603cea5dccb332464e7dd29e96469eebf6cd5133029153d1e69e3fd6f", size = 37735, upload-time = "2018-04-08T04:27:22.143Z" }, +] + [[package]] name = "combo-lock" version = "0.3.0" @@ -356,7 +372,8 @@ source = { registry = "https://pypi.org/simple" } resolution-markers = [ "python_full_version >= '3.13'", "python_full_version == '3.12.*'", - "python_full_version >= '3.10' and python_full_version < '3.12'", + "python_full_version == '3.11.*'", + "python_full_version == '3.10.*'", ] sdist = { url = "https://files.pythonhosted.org/packages/24/56/95b7e30fa389756cb56630faa728da46a27b8c6eb46f9d557c68fff12b65/coverage-7.13.4.tar.gz", hash = "sha256:e5c8f6ed1e61a8b2dcdf31eb0b9bbf0130750ca79c1c49eb898e2ad86f5ccc91", size = 827239, upload-time = "2026-02-09T12:59:03.86Z" } wheels = [ @@ -503,7 +520,8 @@ source = { registry = "https://pypi.org/simple" } resolution-markers = [ "python_full_version >= '3.13'", "python_full_version == '3.12.*'", - "python_full_version >= '3.10' and python_full_version < '3.12'", + "python_full_version == '3.11.*'", + "python_full_version == '3.10.*'", ] sdist = { url = "https://files.pythonhosted.org/packages/b3/8b/4c32ecde6bea6486a2a5d05340e695174351ff6b06cf651a74c005f9df00/filelock-3.25.1.tar.gz", hash = "sha256:b9a2e977f794ef94d77cdf7d27129ac648a61f585bff3ca24630c1629f701aa9", size = 40319, upload-time = "2026-03-09T19:38:47.309Z" } wheels = [ @@ -550,7 +568,8 @@ source = { registry = "https://pypi.org/simple" } resolution-markers = [ "python_full_version >= '3.13'", "python_full_version == '3.12.*'", - "python_full_version >= '3.10' and python_full_version < '3.12'", + "python_full_version == '3.11.*'", + "python_full_version == '3.10.*'", ] sdist = { url = "https://files.pythonhosted.org/packages/72/34/14ca021ce8e5dfedc35312d08ba8bf51fdd999c576889fc2c24cb97f4f10/iniconfig-2.3.0.tar.gz", hash = "sha256:c76315c77db068650d49c5b56314774a7804df16fee4402c1f19d6d15d8c4730", size = 20503, upload-time = "2025-10-18T21:55:43.219Z" } wheels = [ @@ -609,7 +628,8 @@ source = { registry = "https://pypi.org/simple" } resolution-markers = [ "python_full_version >= '3.13'", "python_full_version == '3.12.*'", - "python_full_version >= '3.10' and python_full_version < '3.12'", + "python_full_version == '3.11.*'", + "python_full_version == '3.10.*'", ] dependencies = [ { name = "mdurl", marker = "python_full_version >= '3.10'" }, @@ -637,6 +657,210 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/4a/08/43af249eed4ffadc4df084549994ad606a62119fa37bf3a3857acd2d61f4/memory_tempfile-2.2.3-py3-none-any.whl", hash = "sha256:dcea50b967f75b494fae8e242dc095e97280cfe6a53631473887b05f943cafeb", size = 5719, upload-time = "2020-03-11T00:58:44.648Z" }, ] +[[package]] +name = "numpy" +version = "2.0.2" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version < '3.10'", +] +sdist = { url = "https://files.pythonhosted.org/packages/a9/75/10dd1f8116a8b796cb2c737b674e02d02e80454bda953fa7e65d8c12b016/numpy-2.0.2.tar.gz", hash = "sha256:883c987dee1880e2a864ab0dc9892292582510604156762362d9326444636e78", size = 18902015, upload-time = "2024-08-26T20:19:40.945Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/21/91/3495b3237510f79f5d81f2508f9f13fea78ebfdf07538fc7444badda173d/numpy-2.0.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:51129a29dbe56f9ca83438b706e2e69a39892b5eda6cedcb6b0c9fdc9b0d3ece", size = 21165245, upload-time = "2024-08-26T20:04:14.625Z" }, + { url = "https://files.pythonhosted.org/packages/05/33/26178c7d437a87082d11019292dce6d3fe6f0e9026b7b2309cbf3e489b1d/numpy-2.0.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:f15975dfec0cf2239224d80e32c3170b1d168335eaedee69da84fbe9f1f9cd04", size = 13738540, upload-time = "2024-08-26T20:04:36.784Z" }, + { url = "https://files.pythonhosted.org/packages/ec/31/cc46e13bf07644efc7a4bf68df2df5fb2a1a88d0cd0da9ddc84dc0033e51/numpy-2.0.2-cp310-cp310-macosx_14_0_arm64.whl", hash = "sha256:8c5713284ce4e282544c68d1c3b2c7161d38c256d2eefc93c1d683cf47683e66", size = 5300623, upload-time = "2024-08-26T20:04:46.491Z" }, + { url = "https://files.pythonhosted.org/packages/6e/16/7bfcebf27bb4f9d7ec67332ffebee4d1bf085c84246552d52dbb548600e7/numpy-2.0.2-cp310-cp310-macosx_14_0_x86_64.whl", hash = "sha256:becfae3ddd30736fe1889a37f1f580e245ba79a5855bff5f2a29cb3ccc22dd7b", size = 6901774, upload-time = "2024-08-26T20:04:58.173Z" }, + { url = "https://files.pythonhosted.org/packages/f9/a3/561c531c0e8bf082c5bef509d00d56f82e0ea7e1e3e3a7fc8fa78742a6e5/numpy-2.0.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2da5960c3cf0df7eafefd806d4e612c5e19358de82cb3c343631188991566ccd", size = 13907081, upload-time = "2024-08-26T20:05:19.098Z" }, + { url = "https://files.pythonhosted.org/packages/fa/66/f7177ab331876200ac7563a580140643d1179c8b4b6a6b0fc9838de2a9b8/numpy-2.0.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:496f71341824ed9f3d2fd36cf3ac57ae2e0165c143b55c3a035ee219413f3318", size = 19523451, upload-time = "2024-08-26T20:05:47.479Z" }, + { url = "https://files.pythonhosted.org/packages/25/7f/0b209498009ad6453e4efc2c65bcdf0ae08a182b2b7877d7ab38a92dc542/numpy-2.0.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:a61ec659f68ae254e4d237816e33171497e978140353c0c2038d46e63282d0c8", size = 19927572, upload-time = "2024-08-26T20:06:17.137Z" }, + { url = "https://files.pythonhosted.org/packages/3e/df/2619393b1e1b565cd2d4c4403bdd979621e2c4dea1f8532754b2598ed63b/numpy-2.0.2-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:d731a1c6116ba289c1e9ee714b08a8ff882944d4ad631fd411106a30f083c326", size = 14400722, upload-time = "2024-08-26T20:06:39.16Z" }, + { url = "https://files.pythonhosted.org/packages/22/ad/77e921b9f256d5da36424ffb711ae79ca3f451ff8489eeca544d0701d74a/numpy-2.0.2-cp310-cp310-win32.whl", hash = "sha256:984d96121c9f9616cd33fbd0618b7f08e0cfc9600a7ee1d6fd9b239186d19d97", size = 6472170, upload-time = "2024-08-26T20:06:50.361Z" }, + { url = "https://files.pythonhosted.org/packages/10/05/3442317535028bc29cf0c0dd4c191a4481e8376e9f0db6bcf29703cadae6/numpy-2.0.2-cp310-cp310-win_amd64.whl", hash = "sha256:c7b0be4ef08607dd04da4092faee0b86607f111d5ae68036f16cc787e250a131", size = 15905558, upload-time = "2024-08-26T20:07:13.881Z" }, + { url = "https://files.pythonhosted.org/packages/8b/cf/034500fb83041aa0286e0fb16e7c76e5c8b67c0711bb6e9e9737a717d5fe/numpy-2.0.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:49ca4decb342d66018b01932139c0961a8f9ddc7589611158cb3c27cbcf76448", size = 21169137, upload-time = "2024-08-26T20:07:45.345Z" }, + { url = "https://files.pythonhosted.org/packages/4a/d9/32de45561811a4b87fbdee23b5797394e3d1504b4a7cf40c10199848893e/numpy-2.0.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:11a76c372d1d37437857280aa142086476136a8c0f373b2e648ab2c8f18fb195", size = 13703552, upload-time = "2024-08-26T20:08:06.666Z" }, + { url = "https://files.pythonhosted.org/packages/c1/ca/2f384720020c7b244d22508cb7ab23d95f179fcfff33c31a6eeba8d6c512/numpy-2.0.2-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:807ec44583fd708a21d4a11d94aedf2f4f3c3719035c76a2bbe1fe8e217bdc57", size = 5298957, upload-time = "2024-08-26T20:08:15.83Z" }, + { url = "https://files.pythonhosted.org/packages/0e/78/a3e4f9fb6aa4e6fdca0c5428e8ba039408514388cf62d89651aade838269/numpy-2.0.2-cp311-cp311-macosx_14_0_x86_64.whl", hash = "sha256:8cafab480740e22f8d833acefed5cc87ce276f4ece12fdaa2e8903db2f82897a", size = 6905573, upload-time = "2024-08-26T20:08:27.185Z" }, + { url = "https://files.pythonhosted.org/packages/a0/72/cfc3a1beb2caf4efc9d0b38a15fe34025230da27e1c08cc2eb9bfb1c7231/numpy-2.0.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a15f476a45e6e5a3a79d8a14e62161d27ad897381fecfa4a09ed5322f2085669", size = 13914330, upload-time = "2024-08-26T20:08:48.058Z" }, + { url = "https://files.pythonhosted.org/packages/ba/a8/c17acf65a931ce551fee11b72e8de63bf7e8a6f0e21add4c937c83563538/numpy-2.0.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:13e689d772146140a252c3a28501da66dfecd77490b498b168b501835041f951", size = 19534895, upload-time = "2024-08-26T20:09:16.536Z" }, + { url = "https://files.pythonhosted.org/packages/ba/86/8767f3d54f6ae0165749f84648da9dcc8cd78ab65d415494962c86fac80f/numpy-2.0.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:9ea91dfb7c3d1c56a0e55657c0afb38cf1eeae4544c208dc465c3c9f3a7c09f9", size = 19937253, upload-time = "2024-08-26T20:09:46.263Z" }, + { url = "https://files.pythonhosted.org/packages/df/87/f76450e6e1c14e5bb1eae6836478b1028e096fd02e85c1c37674606ab752/numpy-2.0.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:c1c9307701fec8f3f7a1e6711f9089c06e6284b3afbbcd259f7791282d660a15", size = 14414074, upload-time = "2024-08-26T20:10:08.483Z" }, + { url = "https://files.pythonhosted.org/packages/5c/ca/0f0f328e1e59f73754f06e1adfb909de43726d4f24c6a3f8805f34f2b0fa/numpy-2.0.2-cp311-cp311-win32.whl", hash = "sha256:a392a68bd329eafac5817e5aefeb39038c48b671afd242710b451e76090e81f4", size = 6470640, upload-time = "2024-08-26T20:10:19.732Z" }, + { url = "https://files.pythonhosted.org/packages/eb/57/3a3f14d3a759dcf9bf6e9eda905794726b758819df4663f217d658a58695/numpy-2.0.2-cp311-cp311-win_amd64.whl", hash = "sha256:286cd40ce2b7d652a6f22efdfc6d1edf879440e53e76a75955bc0c826c7e64dc", size = 15910230, upload-time = "2024-08-26T20:10:43.413Z" }, + { url = "https://files.pythonhosted.org/packages/45/40/2e117be60ec50d98fa08c2f8c48e09b3edea93cfcabd5a9ff6925d54b1c2/numpy-2.0.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:df55d490dea7934f330006d0f81e8551ba6010a5bf035a249ef61a94f21c500b", size = 20895803, upload-time = "2024-08-26T20:11:13.916Z" }, + { url = "https://files.pythonhosted.org/packages/46/92/1b8b8dee833f53cef3e0a3f69b2374467789e0bb7399689582314df02651/numpy-2.0.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:8df823f570d9adf0978347d1f926b2a867d5608f434a7cff7f7908c6570dcf5e", size = 13471835, upload-time = "2024-08-26T20:11:34.779Z" }, + { url = "https://files.pythonhosted.org/packages/7f/19/e2793bde475f1edaea6945be141aef6c8b4c669b90c90a300a8954d08f0a/numpy-2.0.2-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:9a92ae5c14811e390f3767053ff54eaee3bf84576d99a2456391401323f4ec2c", size = 5038499, upload-time = "2024-08-26T20:11:43.902Z" }, + { url = "https://files.pythonhosted.org/packages/e3/ff/ddf6dac2ff0dd50a7327bcdba45cb0264d0e96bb44d33324853f781a8f3c/numpy-2.0.2-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:a842d573724391493a97a62ebbb8e731f8a5dcc5d285dfc99141ca15a3302d0c", size = 6633497, upload-time = "2024-08-26T20:11:55.09Z" }, + { url = "https://files.pythonhosted.org/packages/72/21/67f36eac8e2d2cd652a2e69595a54128297cdcb1ff3931cfc87838874bd4/numpy-2.0.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c05e238064fc0610c840d1cf6a13bf63d7e391717d247f1bf0318172e759e692", size = 13621158, upload-time = "2024-08-26T20:12:14.95Z" }, + { url = "https://files.pythonhosted.org/packages/39/68/e9f1126d757653496dbc096cb429014347a36b228f5a991dae2c6b6cfd40/numpy-2.0.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0123ffdaa88fa4ab64835dcbde75dcdf89c453c922f18dced6e27c90d1d0ec5a", size = 19236173, upload-time = "2024-08-26T20:12:44.049Z" }, + { url = "https://files.pythonhosted.org/packages/d1/e9/1f5333281e4ebf483ba1c888b1d61ba7e78d7e910fdd8e6499667041cc35/numpy-2.0.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:96a55f64139912d61de9137f11bf39a55ec8faec288c75a54f93dfd39f7eb40c", size = 19634174, upload-time = "2024-08-26T20:13:13.634Z" }, + { url = "https://files.pythonhosted.org/packages/71/af/a469674070c8d8408384e3012e064299f7a2de540738a8e414dcfd639996/numpy-2.0.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:ec9852fb39354b5a45a80bdab5ac02dd02b15f44b3804e9f00c556bf24b4bded", size = 14099701, upload-time = "2024-08-26T20:13:34.851Z" }, + { url = "https://files.pythonhosted.org/packages/d0/3d/08ea9f239d0e0e939b6ca52ad403c84a2bce1bde301a8eb4888c1c1543f1/numpy-2.0.2-cp312-cp312-win32.whl", hash = "sha256:671bec6496f83202ed2d3c8fdc486a8fc86942f2e69ff0e986140339a63bcbe5", size = 6174313, upload-time = "2024-08-26T20:13:45.653Z" }, + { url = "https://files.pythonhosted.org/packages/b2/b5/4ac39baebf1fdb2e72585c8352c56d063b6126be9fc95bd2bb5ef5770c20/numpy-2.0.2-cp312-cp312-win_amd64.whl", hash = "sha256:cfd41e13fdc257aa5778496b8caa5e856dc4896d4ccf01841daee1d96465467a", size = 15606179, upload-time = "2024-08-26T20:14:08.786Z" }, + { url = "https://files.pythonhosted.org/packages/43/c1/41c8f6df3162b0c6ffd4437d729115704bd43363de0090c7f913cfbc2d89/numpy-2.0.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:9059e10581ce4093f735ed23f3b9d283b9d517ff46009ddd485f1747eb22653c", size = 21169942, upload-time = "2024-08-26T20:14:40.108Z" }, + { url = "https://files.pythonhosted.org/packages/39/bc/fd298f308dcd232b56a4031fd6ddf11c43f9917fbc937e53762f7b5a3bb1/numpy-2.0.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:423e89b23490805d2a5a96fe40ec507407b8ee786d66f7328be214f9679df6dd", size = 13711512, upload-time = "2024-08-26T20:15:00.985Z" }, + { url = "https://files.pythonhosted.org/packages/96/ff/06d1aa3eeb1c614eda245c1ba4fb88c483bee6520d361641331872ac4b82/numpy-2.0.2-cp39-cp39-macosx_14_0_arm64.whl", hash = "sha256:2b2955fa6f11907cf7a70dab0d0755159bca87755e831e47932367fc8f2f2d0b", size = 5306976, upload-time = "2024-08-26T20:15:10.876Z" }, + { url = "https://files.pythonhosted.org/packages/2d/98/121996dcfb10a6087a05e54453e28e58694a7db62c5a5a29cee14c6e047b/numpy-2.0.2-cp39-cp39-macosx_14_0_x86_64.whl", hash = "sha256:97032a27bd9d8988b9a97a8c4d2c9f2c15a81f61e2f21404d7e8ef00cb5be729", size = 6906494, upload-time = "2024-08-26T20:15:22.055Z" }, + { url = "https://files.pythonhosted.org/packages/15/31/9dffc70da6b9bbf7968f6551967fc21156207366272c2a40b4ed6008dc9b/numpy-2.0.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1e795a8be3ddbac43274f18588329c72939870a16cae810c2b73461c40718ab1", size = 13912596, upload-time = "2024-08-26T20:15:42.452Z" }, + { url = "https://files.pythonhosted.org/packages/b9/14/78635daab4b07c0930c919d451b8bf8c164774e6a3413aed04a6d95758ce/numpy-2.0.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f26b258c385842546006213344c50655ff1555a9338e2e5e02a0756dc3e803dd", size = 19526099, upload-time = "2024-08-26T20:16:11.048Z" }, + { url = "https://files.pythonhosted.org/packages/26/4c/0eeca4614003077f68bfe7aac8b7496f04221865b3a5e7cb230c9d055afd/numpy-2.0.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:5fec9451a7789926bcf7c2b8d187292c9f93ea30284802a0ab3f5be8ab36865d", size = 19932823, upload-time = "2024-08-26T20:16:40.171Z" }, + { url = "https://files.pythonhosted.org/packages/f1/46/ea25b98b13dccaebddf1a803f8c748680d972e00507cd9bc6dcdb5aa2ac1/numpy-2.0.2-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:9189427407d88ff25ecf8f12469d4d39d35bee1db5d39fc5c168c6f088a6956d", size = 14404424, upload-time = "2024-08-26T20:17:02.604Z" }, + { url = "https://files.pythonhosted.org/packages/c8/a6/177dd88d95ecf07e722d21008b1b40e681a929eb9e329684d449c36586b2/numpy-2.0.2-cp39-cp39-win32.whl", hash = "sha256:905d16e0c60200656500c95b6b8dca5d109e23cb24abc701d41c02d74c6b3afa", size = 6476809, upload-time = "2024-08-26T20:17:13.553Z" }, + { url = "https://files.pythonhosted.org/packages/ea/2b/7fc9f4e7ae5b507c1a3a21f0f15ed03e794c1242ea8a242ac158beb56034/numpy-2.0.2-cp39-cp39-win_amd64.whl", hash = "sha256:a3f4ab0caa7f053f6797fcd4e1e25caee367db3112ef2b6ef82d749530768c73", size = 15911314, upload-time = "2024-08-26T20:17:36.72Z" }, + { url = "https://files.pythonhosted.org/packages/8f/3b/df5a870ac6a3be3a86856ce195ef42eec7ae50d2a202be1f5a4b3b340e14/numpy-2.0.2-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:7f0a0c6f12e07fa94133c8a67404322845220c06a9e80e85999afe727f7438b8", size = 21025288, upload-time = "2024-08-26T20:18:07.732Z" }, + { url = "https://files.pythonhosted.org/packages/2c/97/51af92f18d6f6f2d9ad8b482a99fb74e142d71372da5d834b3a2747a446e/numpy-2.0.2-pp39-pypy39_pp73-macosx_14_0_x86_64.whl", hash = "sha256:312950fdd060354350ed123c0e25a71327d3711584beaef30cdaa93320c392d4", size = 6762793, upload-time = "2024-08-26T20:18:19.125Z" }, + { url = "https://files.pythonhosted.org/packages/12/46/de1fbd0c1b5ccaa7f9a005b66761533e2f6a3e560096682683a223631fe9/numpy-2.0.2-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:26df23238872200f63518dd2aa984cfca675d82469535dc7162dc2ee52d9dd5c", size = 19334885, upload-time = "2024-08-26T20:18:47.237Z" }, + { url = "https://files.pythonhosted.org/packages/cc/dc/d330a6faefd92b446ec0f0dfea4c3207bb1fef3c4771d19cf4543efd2c78/numpy-2.0.2-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:a46288ec55ebbd58947d31d72be2c63cbf839f0a63b49cb755022310792a3385", size = 15828784, upload-time = "2024-08-26T20:19:11.19Z" }, +] + +[[package]] +name = "numpy" +version = "2.2.6" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version == '3.10.*'", +] +sdist = { url = "https://files.pythonhosted.org/packages/76/21/7d2a95e4bba9dc13d043ee156a356c0a8f0c6309dff6b21b4d71a073b8a8/numpy-2.2.6.tar.gz", hash = "sha256:e29554e2bef54a90aa5cc07da6ce955accb83f21ab5de01a62c8478897b264fd", size = 20276440, upload-time = "2025-05-17T22:38:04.611Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/9a/3e/ed6db5be21ce87955c0cbd3009f2803f59fa08df21b5df06862e2d8e2bdd/numpy-2.2.6-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:b412caa66f72040e6d268491a59f2c43bf03eb6c96dd8f0307829feb7fa2b6fb", size = 21165245, upload-time = "2025-05-17T21:27:58.555Z" }, + { url = "https://files.pythonhosted.org/packages/22/c2/4b9221495b2a132cc9d2eb862e21d42a009f5a60e45fc44b00118c174bff/numpy-2.2.6-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:8e41fd67c52b86603a91c1a505ebaef50b3314de0213461c7a6e99c9a3beff90", size = 14360048, upload-time = "2025-05-17T21:28:21.406Z" }, + { url = "https://files.pythonhosted.org/packages/fd/77/dc2fcfc66943c6410e2bf598062f5959372735ffda175b39906d54f02349/numpy-2.2.6-cp310-cp310-macosx_14_0_arm64.whl", hash = "sha256:37e990a01ae6ec7fe7fa1c26c55ecb672dd98b19c3d0e1d1f326fa13cb38d163", size = 5340542, upload-time = "2025-05-17T21:28:30.931Z" }, + { url = "https://files.pythonhosted.org/packages/7a/4f/1cb5fdc353a5f5cc7feb692db9b8ec2c3d6405453f982435efc52561df58/numpy-2.2.6-cp310-cp310-macosx_14_0_x86_64.whl", hash = "sha256:5a6429d4be8ca66d889b7cf70f536a397dc45ba6faeb5f8c5427935d9592e9cf", size = 6878301, upload-time = "2025-05-17T21:28:41.613Z" }, + { url = "https://files.pythonhosted.org/packages/eb/17/96a3acd228cec142fcb8723bd3cc39c2a474f7dcf0a5d16731980bcafa95/numpy-2.2.6-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:efd28d4e9cd7d7a8d39074a4d44c63eda73401580c5c76acda2ce969e0a38e83", size = 14297320, upload-time = "2025-05-17T21:29:02.78Z" }, + { url = "https://files.pythonhosted.org/packages/b4/63/3de6a34ad7ad6646ac7d2f55ebc6ad439dbbf9c4370017c50cf403fb19b5/numpy-2.2.6-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fc7b73d02efb0e18c000e9ad8b83480dfcd5dfd11065997ed4c6747470ae8915", size = 16801050, upload-time = "2025-05-17T21:29:27.675Z" }, + { url = "https://files.pythonhosted.org/packages/07/b6/89d837eddef52b3d0cec5c6ba0456c1bf1b9ef6a6672fc2b7873c3ec4e2e/numpy-2.2.6-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:74d4531beb257d2c3f4b261bfb0fc09e0f9ebb8842d82a7b4209415896adc680", size = 15807034, upload-time = "2025-05-17T21:29:51.102Z" }, + { url = "https://files.pythonhosted.org/packages/01/c8/dc6ae86e3c61cfec1f178e5c9f7858584049b6093f843bca541f94120920/numpy-2.2.6-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:8fc377d995680230e83241d8a96def29f204b5782f371c532579b4f20607a289", size = 18614185, upload-time = "2025-05-17T21:30:18.703Z" }, + { url = "https://files.pythonhosted.org/packages/5b/c5/0064b1b7e7c89137b471ccec1fd2282fceaae0ab3a9550f2568782d80357/numpy-2.2.6-cp310-cp310-win32.whl", hash = "sha256:b093dd74e50a8cba3e873868d9e93a85b78e0daf2e98c6797566ad8044e8363d", size = 6527149, upload-time = "2025-05-17T21:30:29.788Z" }, + { url = "https://files.pythonhosted.org/packages/a3/dd/4b822569d6b96c39d1215dbae0582fd99954dcbcf0c1a13c61783feaca3f/numpy-2.2.6-cp310-cp310-win_amd64.whl", hash = "sha256:f0fd6321b839904e15c46e0d257fdd101dd7f530fe03fd6359c1ea63738703f3", size = 12904620, upload-time = "2025-05-17T21:30:48.994Z" }, + { url = "https://files.pythonhosted.org/packages/da/a8/4f83e2aa666a9fbf56d6118faaaf5f1974d456b1823fda0a176eff722839/numpy-2.2.6-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:f9f1adb22318e121c5c69a09142811a201ef17ab257a1e66ca3025065b7f53ae", size = 21176963, upload-time = "2025-05-17T21:31:19.36Z" }, + { url = "https://files.pythonhosted.org/packages/b3/2b/64e1affc7972decb74c9e29e5649fac940514910960ba25cd9af4488b66c/numpy-2.2.6-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:c820a93b0255bc360f53eca31a0e676fd1101f673dda8da93454a12e23fc5f7a", size = 14406743, upload-time = "2025-05-17T21:31:41.087Z" }, + { url = "https://files.pythonhosted.org/packages/4a/9f/0121e375000b5e50ffdd8b25bf78d8e1a5aa4cca3f185d41265198c7b834/numpy-2.2.6-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:3d70692235e759f260c3d837193090014aebdf026dfd167834bcba43e30c2a42", size = 5352616, upload-time = "2025-05-17T21:31:50.072Z" }, + { url = "https://files.pythonhosted.org/packages/31/0d/b48c405c91693635fbe2dcd7bc84a33a602add5f63286e024d3b6741411c/numpy-2.2.6-cp311-cp311-macosx_14_0_x86_64.whl", hash = "sha256:481b49095335f8eed42e39e8041327c05b0f6f4780488f61286ed3c01368d491", size = 6889579, upload-time = "2025-05-17T21:32:01.712Z" }, + { url = "https://files.pythonhosted.org/packages/52/b8/7f0554d49b565d0171eab6e99001846882000883998e7b7d9f0d98b1f934/numpy-2.2.6-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b64d8d4d17135e00c8e346e0a738deb17e754230d7e0810ac5012750bbd85a5a", size = 14312005, upload-time = "2025-05-17T21:32:23.332Z" }, + { url = "https://files.pythonhosted.org/packages/b3/dd/2238b898e51bd6d389b7389ffb20d7f4c10066d80351187ec8e303a5a475/numpy-2.2.6-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ba10f8411898fc418a521833e014a77d3ca01c15b0c6cdcce6a0d2897e6dbbdf", size = 16821570, upload-time = "2025-05-17T21:32:47.991Z" }, + { url = "https://files.pythonhosted.org/packages/83/6c/44d0325722cf644f191042bf47eedad61c1e6df2432ed65cbe28509d404e/numpy-2.2.6-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:bd48227a919f1bafbdda0583705e547892342c26fb127219d60a5c36882609d1", size = 15818548, upload-time = "2025-05-17T21:33:11.728Z" }, + { url = "https://files.pythonhosted.org/packages/ae/9d/81e8216030ce66be25279098789b665d49ff19eef08bfa8cb96d4957f422/numpy-2.2.6-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:9551a499bf125c1d4f9e250377c1ee2eddd02e01eac6644c080162c0c51778ab", size = 18620521, upload-time = "2025-05-17T21:33:39.139Z" }, + { url = "https://files.pythonhosted.org/packages/6a/fd/e19617b9530b031db51b0926eed5345ce8ddc669bb3bc0044b23e275ebe8/numpy-2.2.6-cp311-cp311-win32.whl", hash = "sha256:0678000bb9ac1475cd454c6b8c799206af8107e310843532b04d49649c717a47", size = 6525866, upload-time = "2025-05-17T21:33:50.273Z" }, + { url = "https://files.pythonhosted.org/packages/31/0a/f354fb7176b81747d870f7991dc763e157a934c717b67b58456bc63da3df/numpy-2.2.6-cp311-cp311-win_amd64.whl", hash = "sha256:e8213002e427c69c45a52bbd94163084025f533a55a59d6f9c5b820774ef3303", size = 12907455, upload-time = "2025-05-17T21:34:09.135Z" }, + { url = "https://files.pythonhosted.org/packages/82/5d/c00588b6cf18e1da539b45d3598d3557084990dcc4331960c15ee776ee41/numpy-2.2.6-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:41c5a21f4a04fa86436124d388f6ed60a9343a6f767fced1a8a71c3fbca038ff", size = 20875348, upload-time = "2025-05-17T21:34:39.648Z" }, + { url = "https://files.pythonhosted.org/packages/66/ee/560deadcdde6c2f90200450d5938f63a34b37e27ebff162810f716f6a230/numpy-2.2.6-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:de749064336d37e340f640b05f24e9e3dd678c57318c7289d222a8a2f543e90c", size = 14119362, upload-time = "2025-05-17T21:35:01.241Z" }, + { url = "https://files.pythonhosted.org/packages/3c/65/4baa99f1c53b30adf0acd9a5519078871ddde8d2339dc5a7fde80d9d87da/numpy-2.2.6-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:894b3a42502226a1cac872f840030665f33326fc3dac8e57c607905773cdcde3", size = 5084103, upload-time = "2025-05-17T21:35:10.622Z" }, + { url = "https://files.pythonhosted.org/packages/cc/89/e5a34c071a0570cc40c9a54eb472d113eea6d002e9ae12bb3a8407fb912e/numpy-2.2.6-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:71594f7c51a18e728451bb50cc60a3ce4e6538822731b2933209a1f3614e9282", size = 6625382, upload-time = "2025-05-17T21:35:21.414Z" }, + { url = "https://files.pythonhosted.org/packages/f8/35/8c80729f1ff76b3921d5c9487c7ac3de9b2a103b1cd05e905b3090513510/numpy-2.2.6-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f2618db89be1b4e05f7a1a847a9c1c0abd63e63a1607d892dd54668dd92faf87", size = 14018462, upload-time = "2025-05-17T21:35:42.174Z" }, + { url = "https://files.pythonhosted.org/packages/8c/3d/1e1db36cfd41f895d266b103df00ca5b3cbe965184df824dec5c08c6b803/numpy-2.2.6-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fd83c01228a688733f1ded5201c678f0c53ecc1006ffbc404db9f7a899ac6249", size = 16527618, upload-time = "2025-05-17T21:36:06.711Z" }, + { url = "https://files.pythonhosted.org/packages/61/c6/03ed30992602c85aa3cd95b9070a514f8b3c33e31124694438d88809ae36/numpy-2.2.6-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:37c0ca431f82cd5fa716eca9506aefcabc247fb27ba69c5062a6d3ade8cf8f49", size = 15505511, upload-time = "2025-05-17T21:36:29.965Z" }, + { url = "https://files.pythonhosted.org/packages/b7/25/5761d832a81df431e260719ec45de696414266613c9ee268394dd5ad8236/numpy-2.2.6-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:fe27749d33bb772c80dcd84ae7e8df2adc920ae8297400dabec45f0dedb3f6de", size = 18313783, upload-time = "2025-05-17T21:36:56.883Z" }, + { url = "https://files.pythonhosted.org/packages/57/0a/72d5a3527c5ebffcd47bde9162c39fae1f90138c961e5296491ce778e682/numpy-2.2.6-cp312-cp312-win32.whl", hash = "sha256:4eeaae00d789f66c7a25ac5f34b71a7035bb474e679f410e5e1a94deb24cf2d4", size = 6246506, upload-time = "2025-05-17T21:37:07.368Z" }, + { url = "https://files.pythonhosted.org/packages/36/fa/8c9210162ca1b88529ab76b41ba02d433fd54fecaf6feb70ef9f124683f1/numpy-2.2.6-cp312-cp312-win_amd64.whl", hash = "sha256:c1f9540be57940698ed329904db803cf7a402f3fc200bfe599334c9bd84a40b2", size = 12614190, upload-time = "2025-05-17T21:37:26.213Z" }, + { url = "https://files.pythonhosted.org/packages/f9/5c/6657823f4f594f72b5471f1db1ab12e26e890bb2e41897522d134d2a3e81/numpy-2.2.6-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:0811bb762109d9708cca4d0b13c4f67146e3c3b7cf8d34018c722adb2d957c84", size = 20867828, upload-time = "2025-05-17T21:37:56.699Z" }, + { url = "https://files.pythonhosted.org/packages/dc/9e/14520dc3dadf3c803473bd07e9b2bd1b69bc583cb2497b47000fed2fa92f/numpy-2.2.6-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:287cc3162b6f01463ccd86be154f284d0893d2b3ed7292439ea97eafa8170e0b", size = 14143006, upload-time = "2025-05-17T21:38:18.291Z" }, + { url = "https://files.pythonhosted.org/packages/4f/06/7e96c57d90bebdce9918412087fc22ca9851cceaf5567a45c1f404480e9e/numpy-2.2.6-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:f1372f041402e37e5e633e586f62aa53de2eac8d98cbfb822806ce4bbefcb74d", size = 5076765, upload-time = "2025-05-17T21:38:27.319Z" }, + { url = "https://files.pythonhosted.org/packages/73/ed/63d920c23b4289fdac96ddbdd6132e9427790977d5457cd132f18e76eae0/numpy-2.2.6-cp313-cp313-macosx_14_0_x86_64.whl", hash = "sha256:55a4d33fa519660d69614a9fad433be87e5252f4b03850642f88993f7b2ca566", size = 6617736, upload-time = "2025-05-17T21:38:38.141Z" }, + { url = "https://files.pythonhosted.org/packages/85/c5/e19c8f99d83fd377ec8c7e0cf627a8049746da54afc24ef0a0cb73d5dfb5/numpy-2.2.6-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f92729c95468a2f4f15e9bb94c432a9229d0d50de67304399627a943201baa2f", size = 14010719, upload-time = "2025-05-17T21:38:58.433Z" }, + { url = "https://files.pythonhosted.org/packages/19/49/4df9123aafa7b539317bf6d342cb6d227e49f7a35b99c287a6109b13dd93/numpy-2.2.6-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1bc23a79bfabc5d056d106f9befb8d50c31ced2fbc70eedb8155aec74a45798f", size = 16526072, upload-time = "2025-05-17T21:39:22.638Z" }, + { url = "https://files.pythonhosted.org/packages/b2/6c/04b5f47f4f32f7c2b0e7260442a8cbcf8168b0e1a41ff1495da42f42a14f/numpy-2.2.6-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:e3143e4451880bed956e706a3220b4e5cf6172ef05fcc397f6f36a550b1dd868", size = 15503213, upload-time = "2025-05-17T21:39:45.865Z" }, + { url = "https://files.pythonhosted.org/packages/17/0a/5cd92e352c1307640d5b6fec1b2ffb06cd0dabe7d7b8227f97933d378422/numpy-2.2.6-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:b4f13750ce79751586ae2eb824ba7e1e8dba64784086c98cdbbcc6a42112ce0d", size = 18316632, upload-time = "2025-05-17T21:40:13.331Z" }, + { url = "https://files.pythonhosted.org/packages/f0/3b/5cba2b1d88760ef86596ad0f3d484b1cbff7c115ae2429678465057c5155/numpy-2.2.6-cp313-cp313-win32.whl", hash = "sha256:5beb72339d9d4fa36522fc63802f469b13cdbe4fdab4a288f0c441b74272ebfd", size = 6244532, upload-time = "2025-05-17T21:43:46.099Z" }, + { url = "https://files.pythonhosted.org/packages/cb/3b/d58c12eafcb298d4e6d0d40216866ab15f59e55d148a5658bb3132311fcf/numpy-2.2.6-cp313-cp313-win_amd64.whl", hash = "sha256:b0544343a702fa80c95ad5d3d608ea3599dd54d4632df855e4c8d24eb6ecfa1c", size = 12610885, upload-time = "2025-05-17T21:44:05.145Z" }, + { url = "https://files.pythonhosted.org/packages/6b/9e/4bf918b818e516322db999ac25d00c75788ddfd2d2ade4fa66f1f38097e1/numpy-2.2.6-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:0bca768cd85ae743b2affdc762d617eddf3bcf8724435498a1e80132d04879e6", size = 20963467, upload-time = "2025-05-17T21:40:44Z" }, + { url = "https://files.pythonhosted.org/packages/61/66/d2de6b291507517ff2e438e13ff7b1e2cdbdb7cb40b3ed475377aece69f9/numpy-2.2.6-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:fc0c5673685c508a142ca65209b4e79ed6740a4ed6b2267dbba90f34b0b3cfda", size = 14225144, upload-time = "2025-05-17T21:41:05.695Z" }, + { url = "https://files.pythonhosted.org/packages/e4/25/480387655407ead912e28ba3a820bc69af9adf13bcbe40b299d454ec011f/numpy-2.2.6-cp313-cp313t-macosx_14_0_arm64.whl", hash = "sha256:5bd4fc3ac8926b3819797a7c0e2631eb889b4118a9898c84f585a54d475b7e40", size = 5200217, upload-time = "2025-05-17T21:41:15.903Z" }, + { url = "https://files.pythonhosted.org/packages/aa/4a/6e313b5108f53dcbf3aca0c0f3e9c92f4c10ce57a0a721851f9785872895/numpy-2.2.6-cp313-cp313t-macosx_14_0_x86_64.whl", hash = "sha256:fee4236c876c4e8369388054d02d0e9bb84821feb1a64dd59e137e6511a551f8", size = 6712014, upload-time = "2025-05-17T21:41:27.321Z" }, + { url = "https://files.pythonhosted.org/packages/b7/30/172c2d5c4be71fdf476e9de553443cf8e25feddbe185e0bd88b096915bcc/numpy-2.2.6-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e1dda9c7e08dc141e0247a5b8f49cf05984955246a327d4c48bda16821947b2f", size = 14077935, upload-time = "2025-05-17T21:41:49.738Z" }, + { url = "https://files.pythonhosted.org/packages/12/fb/9e743f8d4e4d3c710902cf87af3512082ae3d43b945d5d16563f26ec251d/numpy-2.2.6-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f447e6acb680fd307f40d3da4852208af94afdfab89cf850986c3ca00562f4fa", size = 16600122, upload-time = "2025-05-17T21:42:14.046Z" }, + { url = "https://files.pythonhosted.org/packages/12/75/ee20da0e58d3a66f204f38916757e01e33a9737d0b22373b3eb5a27358f9/numpy-2.2.6-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:389d771b1623ec92636b0786bc4ae56abafad4a4c513d36a55dce14bd9ce8571", size = 15586143, upload-time = "2025-05-17T21:42:37.464Z" }, + { url = "https://files.pythonhosted.org/packages/76/95/bef5b37f29fc5e739947e9ce5179ad402875633308504a52d188302319c8/numpy-2.2.6-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:8e9ace4a37db23421249ed236fdcdd457d671e25146786dfc96835cd951aa7c1", size = 18385260, upload-time = "2025-05-17T21:43:05.189Z" }, + { url = "https://files.pythonhosted.org/packages/09/04/f2f83279d287407cf36a7a8053a5abe7be3622a4363337338f2585e4afda/numpy-2.2.6-cp313-cp313t-win32.whl", hash = "sha256:038613e9fb8c72b0a41f025a7e4c3f0b7a1b5d768ece4796b674c8f3fe13efff", size = 6377225, upload-time = "2025-05-17T21:43:16.254Z" }, + { url = "https://files.pythonhosted.org/packages/67/0e/35082d13c09c02c011cf21570543d202ad929d961c02a147493cb0c2bdf5/numpy-2.2.6-cp313-cp313t-win_amd64.whl", hash = "sha256:6031dd6dfecc0cf9f668681a37648373bddd6421fff6c66ec1624eed0180ee06", size = 12771374, upload-time = "2025-05-17T21:43:35.479Z" }, + { url = "https://files.pythonhosted.org/packages/9e/3b/d94a75f4dbf1ef5d321523ecac21ef23a3cd2ac8b78ae2aac40873590229/numpy-2.2.6-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:0b605b275d7bd0c640cad4e5d30fa701a8d59302e127e5f79138ad62762c3e3d", size = 21040391, upload-time = "2025-05-17T21:44:35.948Z" }, + { url = "https://files.pythonhosted.org/packages/17/f4/09b2fa1b58f0fb4f7c7963a1649c64c4d315752240377ed74d9cd878f7b5/numpy-2.2.6-pp310-pypy310_pp73-macosx_14_0_x86_64.whl", hash = "sha256:7befc596a7dc9da8a337f79802ee8adb30a552a94f792b9c9d18c840055907db", size = 6786754, upload-time = "2025-05-17T21:44:47.446Z" }, + { url = "https://files.pythonhosted.org/packages/af/30/feba75f143bdc868a1cc3f44ccfa6c4b9ec522b36458e738cd00f67b573f/numpy-2.2.6-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ce47521a4754c8f4593837384bd3424880629f718d87c5d44f8ed763edd63543", size = 16643476, upload-time = "2025-05-17T21:45:11.871Z" }, + { url = "https://files.pythonhosted.org/packages/37/48/ac2a9584402fb6c0cd5b5d1a91dcf176b15760130dd386bbafdbfe3640bf/numpy-2.2.6-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:d042d24c90c41b54fd506da306759e06e568864df8ec17ccc17e9e884634fd00", size = 12812666, upload-time = "2025-05-17T21:45:31.426Z" }, +] + +[[package]] +name = "numpy" +version = "2.4.3" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version >= '3.13'", + "python_full_version == '3.12.*'", + "python_full_version == '3.11.*'", +] +sdist = { url = "https://files.pythonhosted.org/packages/10/8b/c265f4823726ab832de836cdd184d0986dcf94480f81e8739692a7ac7af2/numpy-2.4.3.tar.gz", hash = "sha256:483a201202b73495f00dbc83796c6ae63137a9bdade074f7648b3e32613412dd", size = 20727743, upload-time = "2026-03-09T07:58:53.426Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f9/51/5093a2df15c4dc19da3f79d1021e891f5dcf1d9d1db6ba38891d5590f3fe/numpy-2.4.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:33b3bf58ee84b172c067f56aeadc7ee9ab6de69c5e800ab5b10295d54c581adb", size = 16957183, upload-time = "2026-03-09T07:55:57.774Z" }, + { url = "https://files.pythonhosted.org/packages/b5/7c/c061f3de0630941073d2598dc271ac2f6cbcf5c83c74a5870fea07488333/numpy-2.4.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:8ba7b51e71c05aa1f9bc3641463cd82308eab40ce0d5c7e1fd4038cbf9938147", size = 14968734, upload-time = "2026-03-09T07:56:00.494Z" }, + { url = "https://files.pythonhosted.org/packages/ef/27/d26c85cbcd86b26e4f125b0668e7a7c0542d19dd7d23ee12e87b550e95b5/numpy-2.4.3-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:a1988292870c7cb9d0ebb4cc96b4d447513a9644801de54606dc7aabf2b7d920", size = 5475288, upload-time = "2026-03-09T07:56:02.857Z" }, + { url = "https://files.pythonhosted.org/packages/2b/09/3c4abbc1dcd8010bf1a611d174c7aa689fc505585ec806111b4406f6f1b1/numpy-2.4.3-cp311-cp311-macosx_14_0_x86_64.whl", hash = "sha256:23b46bb6d8ecb68b58c09944483c135ae5f0e9b8d8858ece5e4ead783771d2a9", size = 6805253, upload-time = "2026-03-09T07:56:04.53Z" }, + { url = "https://files.pythonhosted.org/packages/21/bc/e7aa3f6817e40c3f517d407742337cbb8e6fc4b83ce0b55ab780c829243b/numpy-2.4.3-cp311-cp311-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a016db5c5dba78fa8fe9f5d80d6708f9c42ab087a739803c0ac83a43d686a470", size = 15969479, upload-time = "2026-03-09T07:56:06.638Z" }, + { url = "https://files.pythonhosted.org/packages/78/51/9f5d7a41f0b51649ddf2f2320595e15e122a40610b233d51928dd6c92353/numpy-2.4.3-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:715de7f82e192e8cae5a507a347d97ad17598f8e026152ca97233e3666daaa71", size = 16901035, upload-time = "2026-03-09T07:56:09.405Z" }, + { url = "https://files.pythonhosted.org/packages/64/6e/b221dd847d7181bc5ee4857bfb026182ef69499f9305eb1371cbb1aea626/numpy-2.4.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:2ddb7919366ee468342b91dea2352824c25b55814a987847b6c52003a7c97f15", size = 17325657, upload-time = "2026-03-09T07:56:12.067Z" }, + { url = "https://files.pythonhosted.org/packages/eb/b8/8f3fd2da596e1063964b758b5e3c970aed1949a05200d7e3d46a9d46d643/numpy-2.4.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:a315e5234d88067f2d97e1f2ef670a7569df445d55400f1e33d117418d008d52", size = 18635512, upload-time = "2026-03-09T07:56:14.629Z" }, + { url = "https://files.pythonhosted.org/packages/5c/24/2993b775c37e39d2f8ab4125b44337ab0b2ba106c100980b7c274a22bee7/numpy-2.4.3-cp311-cp311-win32.whl", hash = "sha256:2b3f8d2c4589b1a2028d2a770b0fc4d1f332fb5e01521f4de3199a896d158ddd", size = 6238100, upload-time = "2026-03-09T07:56:17.243Z" }, + { url = "https://files.pythonhosted.org/packages/76/1d/edccf27adedb754db7c4511d5eac8b83f004ae948fe2d3509e8b78097d4c/numpy-2.4.3-cp311-cp311-win_amd64.whl", hash = "sha256:77e76d932c49a75617c6d13464e41203cd410956614d0a0e999b25e9e8d27eec", size = 12609816, upload-time = "2026-03-09T07:56:19.089Z" }, + { url = "https://files.pythonhosted.org/packages/92/82/190b99153480076c8dce85f4cfe7d53ea84444145ffa54cb58dcd460d66b/numpy-2.4.3-cp311-cp311-win_arm64.whl", hash = "sha256:eb610595dd91560905c132c709412b512135a60f1851ccbd2c959e136431ff67", size = 10485757, upload-time = "2026-03-09T07:56:21.753Z" }, + { url = "https://files.pythonhosted.org/packages/a9/ed/6388632536f9788cea23a3a1b629f25b43eaacd7d7377e5d6bc7b9deb69b/numpy-2.4.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:61b0cbabbb6126c8df63b9a3a0c4b1f44ebca5e12ff6997b80fcf267fb3150ef", size = 16669628, upload-time = "2026-03-09T07:56:24.252Z" }, + { url = "https://files.pythonhosted.org/packages/74/1b/ee2abfc68e1ce728b2958b6ba831d65c62e1b13ce3017c13943f8f9b5b2e/numpy-2.4.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:7395e69ff32526710748f92cd8c9849b361830968ea3e24a676f272653e8983e", size = 14696872, upload-time = "2026-03-09T07:56:26.991Z" }, + { url = "https://files.pythonhosted.org/packages/ba/d1/780400e915ff5638166f11ca9dc2c5815189f3d7cf6f8759a1685e586413/numpy-2.4.3-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:abdce0f71dcb4a00e4e77f3faf05e4616ceccfe72ccaa07f47ee79cda3b7b0f4", size = 5203489, upload-time = "2026-03-09T07:56:29.414Z" }, + { url = "https://files.pythonhosted.org/packages/0b/bb/baffa907e9da4cc34a6e556d6d90e032f6d7a75ea47968ea92b4858826c4/numpy-2.4.3-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:48da3a4ee1336454b07497ff7ec83903efa5505792c4e6d9bf83d99dc07a1e18", size = 6550814, upload-time = "2026-03-09T07:56:32.225Z" }, + { url = "https://files.pythonhosted.org/packages/7b/12/8c9f0c6c95f76aeb20fc4a699c33e9f827fa0d0f857747c73bb7b17af945/numpy-2.4.3-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:32e3bef222ad6b052280311d1d60db8e259e4947052c3ae7dd6817451fc8a4c5", size = 15666601, upload-time = "2026-03-09T07:56:34.461Z" }, + { url = "https://files.pythonhosted.org/packages/bd/79/cc665495e4d57d0aa6fbcc0aa57aa82671dfc78fbf95fe733ed86d98f52a/numpy-2.4.3-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e7dd01a46700b1967487141a66ac1a3cf0dd8ebf1f08db37d46389401512ca97", size = 16621358, upload-time = "2026-03-09T07:56:36.852Z" }, + { url = "https://files.pythonhosted.org/packages/a8/40/b4ecb7224af1065c3539f5ecfff879d090de09608ad1008f02c05c770cb3/numpy-2.4.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:76f0f283506c28b12bba319c0fab98217e9f9b54e6160e9c79e9f7348ba32e9c", size = 17016135, upload-time = "2026-03-09T07:56:39.337Z" }, + { url = "https://files.pythonhosted.org/packages/f7/b1/6a88e888052eed951afed7a142dcdf3b149a030ca59b4c71eef085858e43/numpy-2.4.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:737f630a337364665aba3b5a77e56a68cc42d350edd010c345d65a3efa3addcc", size = 18345816, upload-time = "2026-03-09T07:56:42.31Z" }, + { url = "https://files.pythonhosted.org/packages/f3/8f/103a60c5f8c3d7fc678c19cd7b2476110da689ccb80bc18050efbaeae183/numpy-2.4.3-cp312-cp312-win32.whl", hash = "sha256:26952e18d82a1dbbc2f008d402021baa8d6fc8e84347a2072a25e08b46d698b9", size = 5960132, upload-time = "2026-03-09T07:56:44.851Z" }, + { url = "https://files.pythonhosted.org/packages/d7/7c/f5ee1bf6ed888494978046a809df2882aad35d414b622893322df7286879/numpy-2.4.3-cp312-cp312-win_amd64.whl", hash = "sha256:65f3c2455188f09678355f5cae1f959a06b778bc66d535da07bf2ef20cd319d5", size = 12316144, upload-time = "2026-03-09T07:56:47.057Z" }, + { url = "https://files.pythonhosted.org/packages/71/46/8d1cb3f7a00f2fb6394140e7e6623696e54c6318a9d9691bb4904672cf42/numpy-2.4.3-cp312-cp312-win_arm64.whl", hash = "sha256:2abad5c7fef172b3377502bde47892439bae394a71bc329f31df0fd829b41a9e", size = 10220364, upload-time = "2026-03-09T07:56:49.849Z" }, + { url = "https://files.pythonhosted.org/packages/b6/d0/1fe47a98ce0df229238b77611340aff92d52691bcbc10583303181abf7fc/numpy-2.4.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:b346845443716c8e542d54112966383b448f4a3ba5c66409771b8c0889485dd3", size = 16665297, upload-time = "2026-03-09T07:56:52.296Z" }, + { url = "https://files.pythonhosted.org/packages/27/d9/4e7c3f0e68dfa91f21c6fb6cf839bc829ec920688b1ce7ec722b1a6202fb/numpy-2.4.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:2629289168f4897a3c4e23dc98d6f1731f0fc0fe52fb9db19f974041e4cc12b9", size = 14691853, upload-time = "2026-03-09T07:56:54.992Z" }, + { url = "https://files.pythonhosted.org/packages/3a/66/bd096b13a87549683812b53ab211e6d413497f84e794fb3c39191948da97/numpy-2.4.3-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:bb2e3cf95854233799013779216c57e153c1ee67a0bf92138acca0e429aefaee", size = 5198435, upload-time = "2026-03-09T07:56:57.184Z" }, + { url = "https://files.pythonhosted.org/packages/a2/2f/687722910b5a5601de2135c891108f51dfc873d8e43c8ed9f4ebb440b4a2/numpy-2.4.3-cp313-cp313-macosx_14_0_x86_64.whl", hash = "sha256:7f3408ff897f8ab07a07fbe2823d7aee6ff644c097cc1f90382511fe982f647f", size = 6546347, upload-time = "2026-03-09T07:56:59.531Z" }, + { url = "https://files.pythonhosted.org/packages/bf/ec/7971c4e98d86c564750393fab8d7d83d0a9432a9d78bb8a163a6dc59967a/numpy-2.4.3-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:decb0eb8a53c3b009b0962378065589685d66b23467ef5dac16cbe818afde27f", size = 15664626, upload-time = "2026-03-09T07:57:01.385Z" }, + { url = "https://files.pythonhosted.org/packages/7e/eb/7daecbea84ec935b7fc732e18f532073064a3816f0932a40a17f3349185f/numpy-2.4.3-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d5f51900414fc9204a0e0da158ba2ac52b75656e7dce7e77fb9f84bfa343b4cc", size = 16608916, upload-time = "2026-03-09T07:57:04.008Z" }, + { url = "https://files.pythonhosted.org/packages/df/58/2a2b4a817ffd7472dca4421d9f0776898b364154e30c95f42195041dc03b/numpy-2.4.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:6bd06731541f89cdc01b261ba2c9e037f1543df7472517836b78dfb15bd6e476", size = 17015824, upload-time = "2026-03-09T07:57:06.347Z" }, + { url = "https://files.pythonhosted.org/packages/4a/ca/627a828d44e78a418c55f82dd4caea8ea4a8ef24e5144d9e71016e52fb40/numpy-2.4.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:22654fe6be0e5206f553a9250762c653d3698e46686eee53b399ab90da59bd92", size = 18334581, upload-time = "2026-03-09T07:57:09.114Z" }, + { url = "https://files.pythonhosted.org/packages/cd/c0/76f93962fc79955fcba30a429b62304332345f22d4daec1cb33653425643/numpy-2.4.3-cp313-cp313-win32.whl", hash = "sha256:d71e379452a2f670ccb689ec801b1218cd3983e253105d6e83780967e899d687", size = 5958618, upload-time = "2026-03-09T07:57:11.432Z" }, + { url = "https://files.pythonhosted.org/packages/b1/3c/88af0040119209b9b5cb59485fa48b76f372c73068dbf9254784b975ac53/numpy-2.4.3-cp313-cp313-win_amd64.whl", hash = "sha256:0a60e17a14d640f49146cb38e3f105f571318db7826d9b6fef7e4dce758faecd", size = 12312824, upload-time = "2026-03-09T07:57:13.586Z" }, + { url = "https://files.pythonhosted.org/packages/58/ce/3d07743aced3d173f877c3ef6a454c2174ba42b584ab0b7e6d99374f51ed/numpy-2.4.3-cp313-cp313-win_arm64.whl", hash = "sha256:c9619741e9da2059cd9c3f206110b97583c7152c1dc9f8aafd4beb450ac1c89d", size = 10221218, upload-time = "2026-03-09T07:57:16.183Z" }, + { url = "https://files.pythonhosted.org/packages/62/09/d96b02a91d09e9d97862f4fc8bfebf5400f567d8eb1fe4b0cc4795679c15/numpy-2.4.3-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:7aa4e54f6469300ebca1d9eb80acd5253cdfa36f2c03d79a35883687da430875", size = 14819570, upload-time = "2026-03-09T07:57:18.564Z" }, + { url = "https://files.pythonhosted.org/packages/b5/ca/0b1aba3905fdfa3373d523b2b15b19029f4f3031c87f4066bd9d20ef6c6b/numpy-2.4.3-cp313-cp313t-macosx_14_0_arm64.whl", hash = "sha256:d1b90d840b25874cf5cd20c219af10bac3667db3876d9a495609273ebe679070", size = 5326113, upload-time = "2026-03-09T07:57:21.052Z" }, + { url = "https://files.pythonhosted.org/packages/c0/63/406e0fd32fcaeb94180fd6a4c41e55736d676c54346b7efbce548b94a914/numpy-2.4.3-cp313-cp313t-macosx_14_0_x86_64.whl", hash = "sha256:a749547700de0a20a6718293396ec237bb38218049cfce788e08fcb716e8cf73", size = 6646370, upload-time = "2026-03-09T07:57:22.804Z" }, + { url = "https://files.pythonhosted.org/packages/b6/d0/10f7dc157d4b37af92720a196be6f54f889e90dcd30dce9dc657ed92c257/numpy-2.4.3-cp313-cp313t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:94f3c4a151a2e529adf49c1d54f0f57ff8f9b233ee4d44af623a81553ab86368", size = 15723499, upload-time = "2026-03-09T07:57:24.693Z" }, + { url = "https://files.pythonhosted.org/packages/66/f1/d1c2bf1161396629701bc284d958dc1efa3a5a542aab83cf11ee6eb4cba5/numpy-2.4.3-cp313-cp313t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:22c31dc07025123aedf7f2db9e91783df13f1776dc52c6b22c620870dc0fab22", size = 16657164, upload-time = "2026-03-09T07:57:27.676Z" }, + { url = "https://files.pythonhosted.org/packages/1a/be/cca19230b740af199ac47331a21c71e7a3d0ba59661350483c1600d28c37/numpy-2.4.3-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:148d59127ac95979d6f07e4d460f934ebdd6eed641db9c0db6c73026f2b2101a", size = 17081544, upload-time = "2026-03-09T07:57:30.664Z" }, + { url = "https://files.pythonhosted.org/packages/b9/c5/9602b0cbb703a0936fb40f8a95407e8171935b15846de2f0776e08af04c7/numpy-2.4.3-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:a97cbf7e905c435865c2d939af3d93f99d18eaaa3cabe4256f4304fb51604349", size = 18380290, upload-time = "2026-03-09T07:57:33.763Z" }, + { url = "https://files.pythonhosted.org/packages/ed/81/9f24708953cd30be9ee36ec4778f4b112b45165812f2ada4cc5ea1c1f254/numpy-2.4.3-cp313-cp313t-win32.whl", hash = "sha256:be3b8487d725a77acccc9924f65fd8bce9af7fac8c9820df1049424a2115af6c", size = 6082814, upload-time = "2026-03-09T07:57:36.491Z" }, + { url = "https://files.pythonhosted.org/packages/e2/9e/52f6eaa13e1a799f0ab79066c17f7016a4a8ae0c1aefa58c82b4dab690b4/numpy-2.4.3-cp313-cp313t-win_amd64.whl", hash = "sha256:1ec84fd7c8e652b0f4aaaf2e6e9cc8eaa9b1b80a537e06b2e3a2fb176eedcb26", size = 12452673, upload-time = "2026-03-09T07:57:38.281Z" }, + { url = "https://files.pythonhosted.org/packages/c4/04/b8cece6ead0b30c9fbd99bb835ad7ea0112ac5f39f069788c5558e3b1ab2/numpy-2.4.3-cp313-cp313t-win_arm64.whl", hash = "sha256:120df8c0a81ebbf5b9020c91439fccd85f5e018a927a39f624845be194a2be02", size = 10290907, upload-time = "2026-03-09T07:57:40.747Z" }, + { url = "https://files.pythonhosted.org/packages/70/ae/3936f79adebf8caf81bd7a599b90a561334a658be4dcc7b6329ebf4ee8de/numpy-2.4.3-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:5884ce5c7acfae1e4e1b6fde43797d10aa506074d25b531b4f54bde33c0c31d4", size = 16664563, upload-time = "2026-03-09T07:57:43.817Z" }, + { url = "https://files.pythonhosted.org/packages/9b/62/760f2b55866b496bb1fa7da2a6db076bef908110e568b02fcfc1422e2a3a/numpy-2.4.3-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:297837823f5bc572c5f9379b0c9f3a3365f08492cbdc33bcc3af174372ebb168", size = 14702161, upload-time = "2026-03-09T07:57:46.169Z" }, + { url = "https://files.pythonhosted.org/packages/32/af/a7a39464e2c0a21526fb4fb76e346fb172ebc92f6d1c7a07c2c139cc17b1/numpy-2.4.3-cp314-cp314-macosx_14_0_arm64.whl", hash = "sha256:a111698b4a3f8dcbe54c64a7708f049355abd603e619013c346553c1fd4ca90b", size = 5208738, upload-time = "2026-03-09T07:57:48.506Z" }, + { url = "https://files.pythonhosted.org/packages/29/8c/2a0cf86a59558fa078d83805589c2de490f29ed4fb336c14313a161d358a/numpy-2.4.3-cp314-cp314-macosx_14_0_x86_64.whl", hash = "sha256:4bd4741a6a676770e0e97fe9ab2e51de01183df3dcbcec591d26d331a40de950", size = 6543618, upload-time = "2026-03-09T07:57:50.591Z" }, + { url = "https://files.pythonhosted.org/packages/aa/b8/612ce010c0728b1c363fa4ea3aa4c22fe1c5da1de008486f8c2f5cb92fae/numpy-2.4.3-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:54f29b877279d51e210e0c80709ee14ccbbad647810e8f3d375561c45ef613dd", size = 15680676, upload-time = "2026-03-09T07:57:52.34Z" }, + { url = "https://files.pythonhosted.org/packages/a9/7e/4f120ecc54ba26ddf3dc348eeb9eb063f421de65c05fc961941798feea18/numpy-2.4.3-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:679f2a834bae9020f81534671c56fd0cc76dd7e5182f57131478e23d0dc59e24", size = 16613492, upload-time = "2026-03-09T07:57:54.91Z" }, + { url = "https://files.pythonhosted.org/packages/2c/86/1b6020db73be330c4b45d5c6ee4295d59cfeef0e3ea323959d053e5a6909/numpy-2.4.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:d84f0f881cb2225c2dfd7f78a10a5645d487a496c6668d6cc39f0f114164f3d0", size = 17031789, upload-time = "2026-03-09T07:57:57.641Z" }, + { url = "https://files.pythonhosted.org/packages/07/3a/3b90463bf41ebc21d1b7e06079f03070334374208c0f9a1f05e4ae8455e7/numpy-2.4.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:d213c7e6e8d211888cc359bab7199670a00f5b82c0978b9d1c75baf1eddbeac0", size = 18339941, upload-time = "2026-03-09T07:58:00.577Z" }, + { url = "https://files.pythonhosted.org/packages/a8/74/6d736c4cd962259fd8bae9be27363eb4883a2f9069763747347544c2a487/numpy-2.4.3-cp314-cp314-win32.whl", hash = "sha256:52077feedeff7c76ed7c9f1a0428558e50825347b7545bbb8523da2cd55c547a", size = 6007503, upload-time = "2026-03-09T07:58:03.331Z" }, + { url = "https://files.pythonhosted.org/packages/48/39/c56ef87af669364356bb011922ef0734fc49dad51964568634c72a009488/numpy-2.4.3-cp314-cp314-win_amd64.whl", hash = "sha256:0448e7f9caefb34b4b7dd2b77f21e8906e5d6f0365ad525f9f4f530b13df2afc", size = 12444915, upload-time = "2026-03-09T07:58:06.353Z" }, + { url = "https://files.pythonhosted.org/packages/9d/1f/ab8528e38d295fd349310807496fabb7cf9fe2e1f70b97bc20a483ea9d4a/numpy-2.4.3-cp314-cp314-win_arm64.whl", hash = "sha256:b44fd60341c4d9783039598efadd03617fa28d041fc37d22b62d08f2027fa0e7", size = 10494875, upload-time = "2026-03-09T07:58:08.734Z" }, + { url = "https://files.pythonhosted.org/packages/e6/ef/b7c35e4d5ef141b836658ab21a66d1a573e15b335b1d111d31f26c8ef80f/numpy-2.4.3-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:0a195f4216be9305a73c0e91c9b026a35f2161237cf1c6de9b681637772ea657", size = 14822225, upload-time = "2026-03-09T07:58:11.034Z" }, + { url = "https://files.pythonhosted.org/packages/cd/8d/7730fa9278cf6648639946cc816e7cc89f0d891602584697923375f801ed/numpy-2.4.3-cp314-cp314t-macosx_14_0_arm64.whl", hash = "sha256:cd32fbacb9fd1bf041bf8e89e4576b6f00b895f06d00914820ae06a616bdfef7", size = 5328769, upload-time = "2026-03-09T07:58:13.67Z" }, + { url = "https://files.pythonhosted.org/packages/47/01/d2a137317c958b074d338807c1b6a383406cdf8b8e53b075d804cc3d211d/numpy-2.4.3-cp314-cp314t-macosx_14_0_x86_64.whl", hash = "sha256:2e03c05abaee1f672e9d67bc858f300b5ccba1c21397211e8d77d98350972093", size = 6649461, upload-time = "2026-03-09T07:58:15.912Z" }, + { url = "https://files.pythonhosted.org/packages/5c/34/812ce12bc0f00272a4b0ec0d713cd237cb390666eb6206323d1cc9cedbb2/numpy-2.4.3-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:7d1ce23cce91fcea443320a9d0ece9b9305d4368875bab09538f7a5b4131938a", size = 15725809, upload-time = "2026-03-09T07:58:17.787Z" }, + { url = "https://files.pythonhosted.org/packages/25/c0/2aed473a4823e905e765fee3dc2cbf504bd3e68ccb1150fbdabd5c39f527/numpy-2.4.3-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c59020932feb24ed49ffd03704fbab89f22aa9c0d4b180ff45542fe8918f5611", size = 16655242, upload-time = "2026-03-09T07:58:20.476Z" }, + { url = "https://files.pythonhosted.org/packages/f2/c8/7e052b2fc87aa0e86de23f20e2c42bd261c624748aa8efd2c78f7bb8d8c6/numpy-2.4.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:9684823a78a6cd6ad7511fc5e25b07947d1d5b5e2812c93fe99d7d4195130720", size = 17080660, upload-time = "2026-03-09T07:58:23.067Z" }, + { url = "https://files.pythonhosted.org/packages/f3/3d/0876746044db2adcb11549f214d104f2e1be00f07a67edbb4e2812094847/numpy-2.4.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:0200b25c687033316fb39f0ff4e3e690e8957a2c3c8d22499891ec58c37a3eb5", size = 18380384, upload-time = "2026-03-09T07:58:25.839Z" }, + { url = "https://files.pythonhosted.org/packages/07/12/8160bea39da3335737b10308df4f484235fd297f556745f13092aa039d3b/numpy-2.4.3-cp314-cp314t-win32.whl", hash = "sha256:5e10da9e93247e554bb1d22f8edc51847ddd7dde52d85ce31024c1b4312bfba0", size = 6154547, upload-time = "2026-03-09T07:58:28.289Z" }, + { url = "https://files.pythonhosted.org/packages/42/f3/76534f61f80d74cc9cdf2e570d3d4eeb92c2280a27c39b0aaf471eda7b48/numpy-2.4.3-cp314-cp314t-win_amd64.whl", hash = "sha256:45f003dbdffb997a03da2d1d0cb41fbd24a87507fb41605c0420a3db5bd4667b", size = 12633645, upload-time = "2026-03-09T07:58:30.384Z" }, + { url = "https://files.pythonhosted.org/packages/1f/b6/7c0d4334c15983cec7f92a69e8ce9b1e6f31857e5ee3a413ac424e6bd63d/numpy-2.4.3-cp314-cp314t-win_arm64.whl", hash = "sha256:4d382735cecd7bcf090172489a525cd7d4087bc331f7df9f60ddc9a296cf208e", size = 10565454, upload-time = "2026-03-09T07:58:33.031Z" }, + { url = "https://files.pythonhosted.org/packages/64/e4/4dab9fb43c83719c29241c535d9e07be73bea4bc0c6686c5816d8e1b6689/numpy-2.4.3-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:c6b124bfcafb9e8d3ed09130dbee44848c20b3e758b6bbf006e641778927c028", size = 16834892, upload-time = "2026-03-09T07:58:35.334Z" }, + { url = "https://files.pythonhosted.org/packages/c9/29/f8b6d4af90fed3dfda84ebc0df06c9833d38880c79ce954e5b661758aa31/numpy-2.4.3-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:76dbb9d4e43c16cf9aa711fcd8de1e2eeb27539dcefb60a1d5e9f12fae1d1ed8", size = 14893070, upload-time = "2026-03-09T07:58:37.7Z" }, + { url = "https://files.pythonhosted.org/packages/9a/04/a19b3c91dbec0a49269407f15d5753673a09832daed40c45e8150e6fa558/numpy-2.4.3-pp311-pypy311_pp73-macosx_14_0_arm64.whl", hash = "sha256:29363fbfa6f8ee855d7569c96ce524845e3d726d6c19b29eceec7dd555dab152", size = 5399609, upload-time = "2026-03-09T07:58:39.853Z" }, + { url = "https://files.pythonhosted.org/packages/79/34/4d73603f5420eab89ea8a67097b31364bf7c30f811d4dd84b1659c7476d9/numpy-2.4.3-pp311-pypy311_pp73-macosx_14_0_x86_64.whl", hash = "sha256:bc71942c789ef415a37f0d4eab90341425a00d538cd0642445d30b41023d3395", size = 6714355, upload-time = "2026-03-09T07:58:42.365Z" }, + { url = "https://files.pythonhosted.org/packages/58/ad/1100d7229bb248394939a12a8074d485b655e8ed44207d328fdd7fcebc7b/numpy-2.4.3-pp311-pypy311_pp73-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:7e58765ad74dcebd3ef0208a5078fba32dc8ec3578fe84a604432950cd043d79", size = 15800434, upload-time = "2026-03-09T07:58:44.837Z" }, + { url = "https://files.pythonhosted.org/packages/0c/fd/16d710c085d28ba4feaf29ac60c936c9d662e390344f94a6beaa2ac9899b/numpy-2.4.3-pp311-pypy311_pp73-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8e236dbda4e1d319d681afcbb136c0c4a8e0f1a5c58ceec2adebb547357fe857", size = 16729409, upload-time = "2026-03-09T07:58:47.972Z" }, + { url = "https://files.pythonhosted.org/packages/57/a7/b35835e278c18b85206834b3aa3abe68e77a98769c59233d1f6300284781/numpy-2.4.3-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:4b42639cdde6d24e732ff823a3fa5b701d8acad89c4142bc1d0bd6dc85200ba5", size = 12504685, upload-time = "2026-03-09T07:58:50.525Z" }, +] + [[package]] name = "ovos-bus-client" version = "1.5.0" @@ -652,6 +876,20 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/94/61/64679824227877eb0d8a06983243d87727a89a0e6d7014ad57146e803b48/ovos_bus_client-1.5.0-py3-none-any.whl", hash = "sha256:d3f80c21012122bb8c6ba8545e7da5a9674ab916ce50cee5cec600b8a07d5297", size = 59306, upload-time = "2026-03-02T20:48:46.413Z" }, ] +[[package]] +name = "ovos-color-parser" +version = "0.0.8" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "colorspacious" }, + { name = "pyahocorasick", version = "2.2.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, + { name = "pyahocorasick", version = "2.3.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/5b/2d/4a4202e472c176a0277f47a74233a19aca340ab4962a70f3e8294f0b6ce0/ovos-color-parser-0.0.8.tar.gz", hash = "sha256:2d13ab69b61f58638768c787a3a97f9766927ae7516b0839e371ab9f8855443a", size = 609464, upload-time = "2025-03-30T14:59:43.688Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/5f/98/de455003c1067a1d29cc92aa79ecd4d852b7155d42237f80d16789217b53/ovos_color_parser-0.0.8-py3-none-any.whl", hash = "sha256:a652831e1d822cf21cebe6e89ba5b6bf625567325d86dac2bd698b15ff348046", size = 652454, upload-time = "2025-03-30T14:59:42.238Z" }, +] + [[package]] name = "ovos-config" version = "2.1.1" @@ -668,6 +906,19 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/57/02/e2e159475272d9fab4846b8c38adecf00c69730fc598ec2cf55ff3e530ea/ovos_config-2.1.1-py3-none-any.whl", hash = "sha256:ad32ee54320dbb35fddb10055210a48871b0d7cf7d8e4100e25dbb1425d5e026", size = 71805, upload-time = "2025-06-18T01:32:00.769Z" }, ] +[[package]] +name = "ovos-hardware-helpers" +version = "1.0.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "ovos-color-parser" }, + { name = "ovos-utils" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/e0/d9/9a9792eca9d838f469d363c34e6b035e042bd0a78e70652be8de08a3d6fc/ovos_hardware_helpers-1.0.2.tar.gz", hash = "sha256:4da8e3fccfb176c8999fb5a78da85b20f4612593337ec0adf29d3109cee5b5bf", size = 9558, upload-time = "2026-03-12T00:05:54.968Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/3d/b1/65737a448c88e23e1e51fcd649fddd111b11687cdcef9c0d4a18bf8c067a/ovos_hardware_helpers-1.0.2-py3-none-any.whl", hash = "sha256:d088ff0277b50806fa73f370248c9b1f5523dcb6f1129486d1ce245ffb88ed37", size = 10561, upload-time = "2026-03-12T00:05:53.89Z" }, +] + [[package]] name = "ovos-plugin-manager" source = { editable = "." } @@ -687,6 +938,7 @@ dependencies = [ [package.optional-dependencies] test = [ + { name = "ovos-hardware-helpers" }, { name = "pytest", version = "8.4.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, { name = "pytest", version = "9.0.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, { name = "pytest-cov" }, @@ -701,6 +953,7 @@ requires-dist = [ { name = "langcodes", specifier = "~=3.5.0" }, { name = "ovos-bus-client", specifier = ">=0.0.8,<2.0.0" }, { name = "ovos-config", specifier = ">=0.0.12,<3.0.0" }, + { name = "ovos-hardware-helpers", marker = "extra == 'test'", specifier = ">=1.0.0" }, { name = "ovos-utils", specifier = ">=0.2.1,<1.0.0" }, { name = "pytest", marker = "extra == 'test'" }, { name = "pytest-cov", marker = "extra == 'test'" }, @@ -771,6 +1024,81 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/22/a6/858897256d0deac81a172289110f31629fc4cee19b6f01283303e18c8db3/ptyprocess-0.7.0-py2.py3-none-any.whl", hash = "sha256:4b41f3967fce3af57cc7e94b888626c18bf37a083e3651ca8feeb66d492fef35", size = 13993, upload-time = "2020-12-28T15:15:28.35Z" }, ] +[[package]] +name = "pyahocorasick" +version = "2.2.0" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version < '3.10'", +] +sdist = { url = "https://files.pythonhosted.org/packages/68/4b/e3bee663803e2202be984e1291084355f064dfa3a6a632e01fe496445a5c/pyahocorasick-2.2.0.tar.gz", hash = "sha256:817f302088400a1402bf2f8631fdb21cf5a2666888e0d6a7d5a3ad556212e9da", size = 103916, upload-time = "2025-06-19T05:51:28.087Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ad/d4/62c7eb67e304d0e746a0a782f261011d78fc4a440f00d37ee95fd93816fb/pyahocorasick-2.2.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:779f1bb63644655d6001f5b1c5f864ec1284cf1b622ac24774f8444ab92f4f84", size = 58159, upload-time = "2025-06-19T05:50:52.668Z" }, + { url = "https://files.pythonhosted.org/packages/10/c6/02d4adcf2e75eb68c4e9f929b09731d9c21459af1184efe90d51ad837b8a/pyahocorasick-2.2.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:6e9e082ffc2b240017357aeccaedc7aaccba530cb9e64945e23e999ef98b19c5", size = 33241, upload-time = "2025-06-19T05:50:53.752Z" }, + { url = "https://files.pythonhosted.org/packages/5f/7a/9bd41a59d6ee3abec9a494c21a2da425c3ca54b0d440b84deb37d47e3b66/pyahocorasick-2.2.0-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:9b82717334794ee1bf50ab574c2b990179fc5bfedf1ff40875f18f011f5f7d5d", size = 106573, upload-time = "2025-06-19T05:50:55.316Z" }, + { url = "https://files.pythonhosted.org/packages/11/c9/89d776685a0c2d0062b6d7d29e7d91525bc9528404fe49fff60a3b39ca84/pyahocorasick-2.2.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:f6be205779ba8e58670356a8cc5fbbbcf9255bfe24569c736d45f036fce9f2af", size = 113493, upload-time = "2025-06-19T05:50:56.919Z" }, + { url = "https://files.pythonhosted.org/packages/f7/c2/ce20c9a5b89147b70b2002f0c9b4d692677f011bb532826c4c204e72a207/pyahocorasick-2.2.0-cp310-cp310-win_amd64.whl", hash = "sha256:43a2f3302a1c45d54fb24cd988629908b11e70da32fed0042e3558f1a6603b00", size = 34992, upload-time = "2025-06-19T05:50:58.494Z" }, + { url = "https://files.pythonhosted.org/packages/fc/3d/f4348a913b1731ca8724f093321896d3ec19ac2526bf959f6a3365873267/pyahocorasick-2.2.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:a20d05f965ba3d5d38fd26b80d087fb59b8945d3dab3571ff9d64cef6d7edf01", size = 58130, upload-time = "2025-06-19T05:51:00.04Z" }, + { url = "https://files.pythonhosted.org/packages/b6/a4/ebea4cb5450fa77c0168fb2054916ff88eb26e1b4471d63a89b2be3f4291/pyahocorasick-2.2.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:4352ade48042067eae16c9c049351cd037078fdf1885c6befe44c7fd38ec7bc9", size = 33241, upload-time = "2025-06-19T05:51:01.504Z" }, + { url = "https://files.pythonhosted.org/packages/92/e2/f233e79c6f70c0d5eaee4382f4994b8db505e9947f6c08c6bb99daacce03/pyahocorasick-2.2.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3463d65232e93ddbdab22be8c22ebc9246419d9be738da07af2bccf800c57107", size = 113941, upload-time = "2025-06-19T05:51:02.988Z" }, + { url = "https://files.pythonhosted.org/packages/55/35/6af44ddde1198d4a55c521fc42028046dabf5413212d74ac6c1b3caae471/pyahocorasick-2.2.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:9850cc8fc3071c239965ba1ca2114de990493025381582176af5951a64ff11cb", size = 116381, upload-time = "2025-06-19T05:51:04.756Z" }, + { url = "https://files.pythonhosted.org/packages/ac/06/d956a977db3cfa6f58cc031ca3e728bf7fc24076b5e040927b2fad2eb5e3/pyahocorasick-2.2.0-cp311-cp311-win_amd64.whl", hash = "sha256:e55347e2b884ec87c972e5f7706625f5bc4e07e703fbc1fb51a6f3bb3087d650", size = 34985, upload-time = "2025-06-19T05:51:06.384Z" }, + { url = "https://files.pythonhosted.org/packages/97/a6/b88ab854348f8449a544a435abb270ed65ed5b77ceada372ef9e998f367c/pyahocorasick-2.2.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:744f63790fc4d337e129c80d28f57e6ba4d22a4b7e065825c72e98f92a77e16b", size = 58174, upload-time = "2025-06-19T05:51:07.874Z" }, + { url = "https://files.pythonhosted.org/packages/b7/e3/6aa83f4f2852d03ce28872c139580913532a85686fc49f5136e4a7efce23/pyahocorasick-2.2.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:73bb94c5621565c5ad22d2f44d45edc7e568de5bd629d22a435e76d7023dae4e", size = 33285, upload-time = "2025-06-19T05:51:09.431Z" }, + { url = "https://files.pythonhosted.org/packages/4c/e5/088c0169c36581d961eb24d770a3c0a48b95d029fd12fa343f7c1a28c11f/pyahocorasick-2.2.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4c3fb6406b3311319bef625f0269af276b75f834e5cba33b81f2e8c35a9c6c91", size = 114871, upload-time = "2025-06-19T05:51:10.513Z" }, + { url = "https://files.pythonhosted.org/packages/66/e5/d3198bba8ce7cdf4c946f71a15bf75b26e2796b805ad43041a0ea77c422d/pyahocorasick-2.2.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:346f92c0086589e44c279d1519187bd3421d94836875033b27b7730f11bc923e", size = 117872, upload-time = "2025-06-19T05:51:11.859Z" }, + { url = "https://files.pythonhosted.org/packages/55/15/ebd27c91dcb49487f8032e6046c11bee9037c793eb045a87d4df40c0af60/pyahocorasick-2.2.0-cp312-cp312-win_amd64.whl", hash = "sha256:1dbc761cdf8c9a1b85f065fb2442c234b742203df8e3cd2f38fc45e4838b02d3", size = 35028, upload-time = "2025-06-19T05:51:12.936Z" }, + { url = "https://files.pythonhosted.org/packages/89/8c/d62a60af6025bc02ef2d25f29f93c591c06f4e43e51b2127f9a4a0954eb8/pyahocorasick-2.2.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:54c9604d73051f96c2d5a6c267f404f3d2d02790a2680a0c0ee7069ef7660d8b", size = 58177, upload-time = "2025-06-19T05:51:13.999Z" }, + { url = "https://files.pythonhosted.org/packages/41/97/b3cf05d0a3e545ce38a10c828fb188500a31261b679fd15ef717147eadee/pyahocorasick-2.2.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:57932f7e894107d5ddf011051feb081b0ff7fdd6ab94462ead0c4c716ffdbd47", size = 33285, upload-time = "2025-06-19T05:51:15.687Z" }, + { url = "https://files.pythonhosted.org/packages/d6/44/b1cd7d1b35a5f77b45b5694def4a8e6560b44cafc0dce12b7dc19a609dbe/pyahocorasick-2.2.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:67c4489c2615fc4a25824d1f12c9e775d84c2207eecedde273bbffd479d82e71", size = 114812, upload-time = "2025-06-19T05:51:17.146Z" }, + { url = "https://files.pythonhosted.org/packages/61/48/862fc0d3c92aa70a7c1721aa41460b8ac0a8d2e62aab039773c9b8d0c9d9/pyahocorasick-2.2.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:3f388b66e8973e8ac7cd7db7f90c56b2aaec4b5563b6da7bfc3e973b7ea34e1d", size = 117865, upload-time = "2025-06-19T05:51:18.619Z" }, + { url = "https://files.pythonhosted.org/packages/1f/e3/7680654f2d5e06ed7df9c7e6387cf86ed48c670fc65d64924a9a03ccb0e7/pyahocorasick-2.2.0-cp313-cp313-win_amd64.whl", hash = "sha256:8eabbd6fcd65595d36dadc3fc57d536aa302833991cd6b0b872aae60c5eac3e9", size = 35021, upload-time = "2025-06-19T05:51:19.79Z" }, + { url = "https://files.pythonhosted.org/packages/cb/4f/7ef4e0a7ee5c94e8a4d9fc332baf3be743d2634f0aeb2d3d3e6f8700c26d/pyahocorasick-2.2.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:9c964af712aa57216575d1d42afed9a9b1df296794739654ed1359a2c4a6074f", size = 58173, upload-time = "2025-06-19T05:51:21.009Z" }, + { url = "https://files.pythonhosted.org/packages/d1/6d/718db0b31ce5df316abadc01dd9e81e4a179ebbcb15c18f87c5d99bf7512/pyahocorasick-2.2.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:179fb28f3bd9865ec175ed47283feb68af99d9ca1c63a4f25282d6575f29cdbd", size = 33250, upload-time = "2025-06-19T05:51:22.54Z" }, + { url = "https://files.pythonhosted.org/packages/9b/bf/203aeab3bf5db13a0bc85b69777986f2ca3a691e0dbd8b05975e8cbce810/pyahocorasick-2.2.0-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:08e7125b4baa5e6e293c06a994e7d11462c5bd4f08b708ab97ba5edddf07c5ff", size = 98690, upload-time = "2025-06-19T05:51:23.766Z" }, + { url = "https://files.pythonhosted.org/packages/95/52/0780049884b2d66a6b7d252d618df65effef9da8fbeaf1ce5736c63e2632/pyahocorasick-2.2.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:cfb8d47b5d709342c6f65770d266a5f608f7e2736f161427146ff504bc698bc6", size = 113088, upload-time = "2025-06-19T05:51:25.146Z" }, + { url = "https://files.pythonhosted.org/packages/60/5e/00a1a63194a3c9f72f8e48ccab325c87547806ef31105fb533b07313beb3/pyahocorasick-2.2.0-cp39-cp39-win_amd64.whl", hash = "sha256:a54abc9f24ec9578769ce6ae24fce438e92171932d400c77b4ff5564e5be3b97", size = 35037, upload-time = "2025-06-19T05:51:26.418Z" }, +] + +[[package]] +name = "pyahocorasick" +version = "2.3.0" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version >= '3.13'", + "python_full_version == '3.12.*'", + "python_full_version == '3.11.*'", + "python_full_version == '3.10.*'", +] +sdist = { url = "https://files.pythonhosted.org/packages/ad/eb/6bf175a745bdb3b247c0f8bff784745f0b30fdfafb5760d635b45d91c607/pyahocorasick-2.3.0.tar.gz", hash = "sha256:2960f5838bbcca4d7765c40478ec56f938e3f161946ff84f00c06d2b3a0ba9dd", size = 104589, upload-time = "2025-12-17T13:02:28.951Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d6/89/f20ba51b6cf6b91e0dab2bc3b9797f6027991b47f567d30fc18b781f65d1/pyahocorasick-2.3.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:d16b9ab607814968d047e26871653992240f0128ffc5d142922929afaea3bcdf", size = 59718, upload-time = "2025-12-17T13:01:57.354Z" }, + { url = "https://files.pythonhosted.org/packages/10/d4/0432fd3e130c9cafa8ce92dd9bd207204307368e2fe8e855af23433798ca/pyahocorasick-2.3.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:c1138b8f802e8f9aefd74c73314593a3e470cc5547fc4fe1d381426f31e2a264", size = 33993, upload-time = "2025-12-17T13:01:58.781Z" }, + { url = "https://files.pythonhosted.org/packages/c1/80/852b9eccd72bf165b98c25f1c6a99997aa9954c7ebc7ce2b63059760c93c/pyahocorasick-2.3.0-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:cf22b22278c2352b9c2ace3d44842b9bfc2c220accbd744bbec3204b9d78f3c3", size = 110377, upload-time = "2025-12-17T13:02:00.291Z" }, + { url = "https://files.pythonhosted.org/packages/64/ad/fe51ee47de6c3e346986f587f883b7471174199266ab93bc5b33f1e4a69f/pyahocorasick-2.3.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:668dae5f54a20ac94521c30290beadb6b941cda9aaed4ef939fd16a393c65871", size = 113491, upload-time = "2025-12-17T13:02:01.638Z" }, + { url = "https://files.pythonhosted.org/packages/ae/39/0516ef42197c003131761ed4bf2946b28108ef45f8a4ab9ff5ed5a80b898/pyahocorasick-2.3.0-cp310-cp310-win_amd64.whl", hash = "sha256:265e71e2635a7ddd2019a5d9f1815642c9e6d24081dcc6d728d9040d9702739f", size = 35161, upload-time = "2025-12-17T13:02:02.662Z" }, + { url = "https://files.pythonhosted.org/packages/f6/19/b7a9dbbd0083110b5f077c3911787765d5285ccfad5d3f08e1230beca1ed/pyahocorasick-2.3.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:3f15f8cd42e6d8164f5621e2acd768a58854740f1796a1649f91485505da4776", size = 59718, upload-time = "2025-12-17T13:02:03.694Z" }, + { url = "https://files.pythonhosted.org/packages/e2/34/ccdfaed1d584e7be0806700166b82a574123437a8d4ef235e730eb24f44c/pyahocorasick-2.3.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:8a1cbd603d471e118a60f780f2b4d83a35975d71a1745419854e722dfa7fadfc", size = 33994, upload-time = "2025-12-17T13:02:05.082Z" }, + { url = "https://files.pythonhosted.org/packages/31/96/5b3305eadbda045bcf616172c8c1a15cc81935af6926ba6f3b3b99ae0db8/pyahocorasick-2.3.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:41ec7f66d2fd5452d9d5e2f4ca919401981b0f52e7f1b0c2a9b7b30163ea86ea", size = 113939, upload-time = "2025-12-17T13:02:06.234Z" }, + { url = "https://files.pythonhosted.org/packages/4b/ad/bf7d80ec2abea631b1f0960a4f3e94c90ec39efe66aa1f2f66322d579e60/pyahocorasick-2.3.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:2fb0b6fedec6558e7c8cd9397131325b03db72b2683b7abede3a37ae87150ae6", size = 116393, upload-time = "2025-12-17T13:02:07.771Z" }, + { url = "https://files.pythonhosted.org/packages/f0/2a/1abc6938ca762c9fe7093e2dcb4aa7dd2150ec4de523d0c64d594d8ea3fd/pyahocorasick-2.3.0-cp311-cp311-win_amd64.whl", hash = "sha256:0eae7c9fb67109649d653c20e163ae2ac33686ff266718c3bf12392cde8a42b6", size = 35154, upload-time = "2025-12-17T13:02:09.188Z" }, + { url = "https://files.pythonhosted.org/packages/5f/54/c705885bb82f32aa50846d1de8b8fa48233361093ff9e30d01cb69d6d7a0/pyahocorasick-2.3.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:7abfe09f6dca8656cc3d1122b25ea0caf272916c18a6e5a6a45ae74aa325a7fe", size = 60114, upload-time = "2025-12-17T13:02:10.229Z" }, + { url = "https://files.pythonhosted.org/packages/4f/a7/b0bde3d74d32f88a875d7397bbe81a118b5c70f83c400629c14771ed345b/pyahocorasick-2.3.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:e7917f513aef244465e2e6a0ae1b5690e971dc336a7b15f68de2f03869d68302", size = 34155, upload-time = "2025-12-17T13:02:11.661Z" }, + { url = "https://files.pythonhosted.org/packages/85/2a/1356e69aac6b0c746d786df1ec5a0a884018e1464fbeb7f358338c8f880e/pyahocorasick-2.3.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:dc9423ffaae542cfaeed516045576968a5ce6203a6f03d0034fcedbcabcb48cd", size = 114873, upload-time = "2025-12-17T13:02:12.829Z" }, + { url = "https://files.pythonhosted.org/packages/42/35/5cbba34fc6f3438d1016d609ed47d74222dd038cd83bd86540edaff0070d/pyahocorasick-2.3.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:c0c42322518c99c49623a1784d27ae73a2765251955808e2edd64fd151e6fa57", size = 117864, upload-time = "2025-12-17T13:02:13.989Z" }, + { url = "https://files.pythonhosted.org/packages/a8/5b/a363817c238d8484bfc93854c36bd938302c0d0749a62030ed21818a0765/pyahocorasick-2.3.0-cp312-cp312-win_amd64.whl", hash = "sha256:b417241fb8483a2b269502cdca5c69bd71579c11adb982663d61466936086fff", size = 35224, upload-time = "2025-12-17T13:02:14.997Z" }, + { url = "https://files.pythonhosted.org/packages/3e/02/83d47473c0e528330b04558d23f345c7cf61d197ad2b321283f9ab15cc74/pyahocorasick-2.3.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:a0ed6066cc97e1277801f64f7633d85db4778801b3e775e0addf2e300e2e25bc", size = 60116, upload-time = "2025-12-17T13:02:16.422Z" }, + { url = "https://files.pythonhosted.org/packages/04/47/ce51f9a07e5f610b27686096bcbbe1a9517101ae83f8ce33c43755d2d630/pyahocorasick-2.3.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:6623f2b395f2c32a5e65b780254eaca5ab8defa4f7819ebcdd68f1c98a761e25", size = 34163, upload-time = "2025-12-17T13:02:17.406Z" }, + { url = "https://files.pythonhosted.org/packages/1b/2c/e09a706f23771421847e4ff8b3f91fd80ade76343609610ced54401fb309/pyahocorasick-2.3.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:28306dd19224b572f82d46d4831d6240770237e7188e9f9f3b267592f31af211", size = 114816, upload-time = "2025-12-17T13:02:18.637Z" }, + { url = "https://files.pythonhosted.org/packages/23/30/2d469e77be232b0a047195527a31fd91eae7ece3e1f8403601709a238034/pyahocorasick-2.3.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:05777c88934df56044927aef1239917d7bbfebe4460ff953924c9d177f574098", size = 117862, upload-time = "2025-12-17T13:02:19.808Z" }, + { url = "https://files.pythonhosted.org/packages/31/11/eece0b8b20aaa443c9ae9785b29dc22b2b70a413ea51c1273524f9855ade/pyahocorasick-2.3.0-cp313-cp313-win_amd64.whl", hash = "sha256:52116146fea2331bc0714fef229648f05d8f2451f08d29389eb9833ebddcfc72", size = 35214, upload-time = "2025-12-17T13:02:20.795Z" }, + { url = "https://files.pythonhosted.org/packages/bf/d9/8b4dbe51c0225122eb367a7e0b042111dbc02092abf6fd644ba42e632973/pyahocorasick-2.3.0-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:d4cca977f05a18c926a1d0dca05916825dd8923100e47e44d0735d8a949cc9d4", size = 60048, upload-time = "2025-12-17T13:02:21.795Z" }, + { url = "https://files.pythonhosted.org/packages/0d/d1/e9ec411993665eca477d4228e6e4a3a1f9737f971814911a2f149fd3778f/pyahocorasick-2.3.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:cc53e4fe83fae539ceae2252e289fe0875db6aec12d07444368903e4dd074291", size = 34114, upload-time = "2025-12-17T13:02:23.232Z" }, + { url = "https://files.pythonhosted.org/packages/3b/27/fc534a12b7bf7f383e3f03442e02a3364b94cacedd8ef18ca1d86c829bbd/pyahocorasick-2.3.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:f0326076ee2049822ca434529baf2c0b0d31886892d4ebfcfa5f0f64d307c6f0", size = 114567, upload-time = "2025-12-17T13:02:25.036Z" }, + { url = "https://files.pythonhosted.org/packages/d6/02/1ab239ebaa20027ccc5be7e56a815c35a944dfd9b27758eb825ba060ec4a/pyahocorasick-2.3.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:9dee08a895eaa39712b65d2efe88b8aa642e07c1bd621a8f9056beb7001f1539", size = 117867, upload-time = "2025-12-17T13:02:26.452Z" }, + { url = "https://files.pythonhosted.org/packages/6a/fc/81b8a915762bdbd6e1d4582e186ec4bdb7fa60998f83d3a81bff3a5d9e89/pyahocorasick-2.3.0-cp314-cp314-win_amd64.whl", hash = "sha256:0c6d9379ddf58cad4abd661795b4b975ba9b542227e78de6ded80757c3ac599d", size = 35985, upload-time = "2025-12-17T13:02:27.84Z" }, +] + [[package]] name = "pyee" version = "12.1.1" @@ -820,7 +1148,8 @@ source = { registry = "https://pypi.org/simple" } resolution-markers = [ "python_full_version >= '3.13'", "python_full_version == '3.12.*'", - "python_full_version >= '3.10' and python_full_version < '3.12'", + "python_full_version == '3.11.*'", + "python_full_version == '3.10.*'", ] dependencies = [ { name = "colorama", marker = "python_full_version >= '3.10' and sys_platform == 'win32'" }, @@ -1107,7 +1436,8 @@ source = { registry = "https://pypi.org/simple" } resolution-markers = [ "python_full_version >= '3.13'", "python_full_version == '3.12.*'", - "python_full_version >= '3.10' and python_full_version < '3.12'", + "python_full_version == '3.11.*'", + "python_full_version == '3.10.*'", ] sdist = { url = "https://files.pythonhosted.org/packages/8b/71/41455aa99a5a5ac1eaf311f5d8efd9ce6433c03ac1e0962de163350d0d97/regex-2026.2.28.tar.gz", hash = "sha256:a729e47d418ea11d03469f321aaf67cdee8954cde3ff2cf8403ab87951ad10f2", size = 415184, upload-time = "2026-02-28T02:19:42.792Z" } wheels = [ From a2bde314562af4dfd4cf9c69a26c69aea8ec29d3 Mon Sep 17 00:00:00 2001 From: miro Date: Tue, 17 Mar 2026 15:53:26 +0000 Subject: [PATCH 11/13] refactor: remove AgenticLoopEngine and AGENT_LOOP from OPM MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit AgenticLoopEngine is now implemented in the standalone ovos-agentic-loop repo (Agent Plugins/ovos-agentic-loop) and registers under the existing opm.agents.chat entry-point group — no new OPM type is needed. Removed: - PluginTypes.AGENT_LOOP ("opm.agents.loop") - PluginConfigTypes.AGENT_LOOP ("opm.agents.loop.config") - AgenticLoopEngine class from templates/agents.py - AgenticLoopEngine section from docs/api/agents.md Co-Authored-By: Claude Sonnet 4.6 --- docs/api/agents.md | 28 ----------- ovos_plugin_manager/templates/agents.py | 67 ------------------------- ovos_plugin_manager/utils/__init__.py | 2 - 3 files changed, 97 deletions(-) diff --git a/docs/api/agents.md b/docs/api/agents.md index d0abdb45..a183b06d 100644 --- a/docs/api/agents.md +++ b/docs/api/agents.md @@ -63,34 +63,6 @@ Generate a response given a list of `AgentMessage` objects. --- -### `AgenticLoopEngine` - -**Entry point:** `opm.agents.loop` - -A `ChatEngine` subclass for plugins that implement an internal agent loop (ReAct, tool-call/observe, background workers, etc.). Callers treat it identically to `ChatEngine` — the loop is an implementation detail. - -Adds a standard toolbox registration interface: - -```python -def load_toolboxes(self, toolboxes: List[ToolBox]) -> None -``` - -Called by the persona loader when the persona config declares a `toolboxes` list. Plugins may also discover and load toolboxes internally. - -Persona config example: - -```json -{ - "name": "MyAgent", - "solvers": ["ovos-react-agent"], - "toolboxes": ["web_search_tools", "file_tools"] -} -``` - -See [Agent Tools](agent-tools.md) for the `ToolBox` plugin API. - ---- - ### `RetrievalEngine` **Entry point:** `opm.agents.retrieval` diff --git a/ovos_plugin_manager/templates/agents.py b/ovos_plugin_manager/templates/agents.py index c8056d91..59d2908c 100644 --- a/ovos_plugin_manager/templates/agents.py +++ b/ovos_plugin_manager/templates/agents.py @@ -382,73 +382,6 @@ def get_response(self, utterance: str, units=units).content -class AgenticLoopEngine(ChatEngine): - """ - A ``ChatEngine`` subclass for plugins that implement an internal agent loop - (e.g. ReAct, tool-call/observe cycles, background worker agents). - - From the perspective of a ``PersonaService`` or any caller, an - ``AgenticLoopEngine`` is identical to a ``ChatEngine`` — it receives a list - of ``AgentMessage`` objects and returns one. All loop mechanics, tool - dispatch, retries, and background tasks are implementation details hidden - inside the plugin. - - The ``toolboxes`` attribute gives OPM and persona configs a standard place - to inject ``ToolBox`` instances. Plugins are free to discover and load - additional toolboxes internally. - - Entry point group: ``opm.agents.loop`` - """ - - def __init__(self, config: Optional[Dict[str, Any]] = None): - """ - Initialise the engine and set up an empty toolbox registry. - - Args: - config: Plugin-specific configuration dictionary. - """ - super().__init__(config=config) - self.toolboxes: List[Any] = [] # List[ToolBox] — avoid circular import - - def load_toolboxes(self, toolboxes: List[Any]) -> None: - """ - Register a list of ``ToolBox`` instances with this engine. - - Called by the persona loader when the persona config declares a - ``toolboxes`` list. Plugins may also call this directly or discover - toolboxes on their own inside ``continue_chat``. - - Args: - toolboxes: Instantiated ``ToolBox`` objects to make available to - the agent loop. - """ - self.toolboxes = list(toolboxes) - - @abc.abstractmethod - def continue_chat(self, messages: List[AgentMessage], - session_id: str = "default", - lang: Optional[str] = None, - units: Optional[str] = None) -> AgentMessage: - """ - Run the agent loop and return the final response. - - The implementation is responsible for all internal steps: tool - selection, execution, observation, and iteration. The caller always - receives a single ``AgentMessage`` with ``MessageRole.ASSISTANT``. - - Args: - messages: Full conversation history up to and including the latest - user turn. - session_id: Conversation session identifier. - lang: BCP-47 language code. - units: Preferred measurement system (``"metric"`` / ``"imperial"``). - - Returns: - The assistant's final response after the loop has completed. - """ - raise NotImplementedError() - - class SummarizerEngine(AbstractAgentEngine): """Engine designed for condensing long documents into concise summaries.""" diff --git a/ovos_plugin_manager/utils/__init__.py b/ovos_plugin_manager/utils/__init__.py index 7cf00a4e..a2e77656 100644 --- a/ovos_plugin_manager/utils/__init__.py +++ b/ovos_plugin_manager/utils/__init__.py @@ -99,7 +99,6 @@ class PluginTypes(str, Enum): WEB_PLAYER = "opm.media.web" PERSONA = "opm.plugin.persona" # personas are a dict, they have no config because they ARE a config AGENT_TOOLBOX = "opm.agents.toolbox" - AGENT_LOOP = "opm.agents.loop" # solver plugins are deprecated! QUESTION_SOLVER = "opm.solver.question" @@ -154,7 +153,6 @@ class PluginConfigTypes(str, Enum): AGENT_COREF = "opm.agents.coref.config" AGENT_YES_NO = "opm.agents.yesno.config" AGENT_TOOLBOX = "opm.agents.toolbox.config" - AGENT_LOOP = "opm.agents.loop.config" KEYWORD_EXTRACTION = "opm.keywords.config" UTTERANCE_SEGMENTATION = "opm.segmentation.config" TOKENIZATION = "opm.tokenization.config" From 1bac93966800f6f1efa0aade1ac260220688afcc Mon Sep 17 00:00:00 2001 From: miro Date: Wed, 18 Mar 2026 15:56:40 +0000 Subject: [PATCH 12/13] fix: address PR #340 CodeRabbit review comments Addressed 6 critical issues from CodeRabbit review on tool plugins PR: **pyproject.toml:** - Added pydantic~=2.0 to test optional-dependencies Reason: GitHub Actions installs with 'test' extras from pyproject.toml; pytest was failing with ModuleNotFoundError for pydantic **test/unittests/test_agent_tools.py:** - Removed dead placeholder definition (line 48-49) Was causing Ruff F821 (Undefined name) warning **docs/index.md:** - Fixed entry-point name from opm.persona.tool to opm.agents.toolbox Matches actual implementation in ToolBox class definition **ovos_plugin_manager/templates/agent_tools.py:** 1. handle_discover() (line 123): Call refresh_tools() before broadcasting - Prevents stale cache if initial discover_tools() fails - Ensures dynamic tool discovery works for bus-only clients 2. handle_call() (line 144): Use model_dump(mode='json') for consistency - Matches model_json_schema() advertised in tool_json_list - Prevents divergence for datetime, UUID, enum, or aliased fields 3. validate_input() (line 170): Remove raw tool_kwargs from error message - Security fix: prevents echoing secrets/user content on bus 4. validate_output() (line 193): Remove raw result from error message - Security fix: prevents echoing secrets/user content on bus **Testing:** - All 1011 unit tests pass - All 21 agent_tools tests pass - Python syntax verified AI-Generated Change: - Model: Claude Haiku 4.5 - Intent: Address CodeRabbit PR review comments for tool plugins feature - Impact: 6 critical fixes for cache staleness, JSON serialization, security, and dependencies - Verified via: uv run pytest test/unittests/ -v (1011 passed) --- QUICK_FACTS.md | 13 ++ docs/agents.md | 212 +++++++++++++++++++ docs/index.md | 3 +- ovos_plugin_manager/templates/agent_tools.py | 7 +- pyproject.toml | 1 + test/unittests/test_agent_tools.py | 4 - uv.lock | 171 ++++++++++++++- 7 files changed, 402 insertions(+), 9 deletions(-) create mode 100644 docs/agents.md diff --git a/QUICK_FACTS.md b/QUICK_FACTS.md index 21410333..9cd52757 100644 --- a/QUICK_FACTS.md +++ b/QUICK_FACTS.md @@ -10,3 +10,16 @@ OpenVoiceOS plugin manager | License | Apache-2.0 | | Repository | [https://github.com/OpenVoiceOS/OVOS-plugin-manager](https://github.com/OpenVoiceOS/OVOS-plugin-manager) | | Python Support | >=3.9 | + +## Agent Plugin Entry Point Groups + +| Group | Base Class | Purpose | +|---|---|---| +| `opm.agents.chat` | `ChatEngine` | Multi-turn chat engines and agentic loops | +| `opm.agents.chat.multimodal` | `MultimodalChatEngine` | Chat with image/audio/file inputs | +| `opm.agents.toolbox` | `ToolBox` | Grouped callable `AgentTool` functions | +| `opm.agents.summarizer` | `SummarizerEngine` | Document/chat summarisation | +| `opm.agents.retrieval` | `RetrievalEngine` | Knowledge-base query | +| `opm.plugin.persona` | `dict` | Static persona config wired by `ovos-persona` | + +See `docs/agents.md` for the full registry of installed plugins. diff --git a/docs/agents.md b/docs/agents.md new file mode 100644 index 00000000..ff245d9f --- /dev/null +++ b/docs/agents.md @@ -0,0 +1,212 @@ +# Agent Plugins + +The agent plugin system extends OPM with composable NLP components for conversational AI, tool use, and text understanding. Plugins are discovered via Python entry points exactly like all other OPM plugin types. + +Base classes: `ovos_plugin_manager/templates/agents.py`, `ovos_plugin_manager/templates/agent_tools.py` + +--- + +## Entry Point Groups + +| Group | Base Class | Purpose | +|---|---|---| +| `opm.agents.chat` | `ChatEngine` | Multi-turn chat / agentic loops — `continue_chat(messages)` → `AgentMessage` | +| `opm.agents.chat.multimodal` | `MultimodalChatEngine` | Chat with image/audio/file inputs | +| `opm.agents.toolbox` | `ToolBox` | Groups of callable `AgentTool` functions exposed to agents via bus or direct call | +| `opm.agents.summarizer` | `SummarizerEngine` / `ChatSummarizerEngine` | Document or chat-history summarisation | +| `opm.agents.retrieval` | `RetrievalEngine` | Knowledge-base / vector-index query (`query(q, lang, k)` → `List[Tuple[str, float]]`) | +| `opm.plugin.persona` | `dict` | Static persona config dict; consumed by `ovos-persona` to wire a ChatEngine with a system prompt | + +`AgentContextManager` (`agents.py:35`) — optional companion base class for plugins that augment conversation context (RAG, memory, history trimming). Not a standalone entry point group; used inside `ChatEngine` implementations. + +--- + +## Available ToolBoxes (`opm.agents.toolbox`) + +Each `ToolBox` implements `discover_tools() → List[AgentTool]` (`agent_tools.py:314`). Tools are callable directly via `ToolBox.call_tool(name, kwargs)` or over the OVOS bus via the `ovos.persona.tools.{toolbox_id}.call` message topic (`agent_tools.py:102`). + +| Plugin ID | Class | Tools | Package | API Key | +|---|---|---|---|---| +| `ovos-wikipedia-tools` | `WikipediaToolBox` | `search_wikipedia`, `get_wikipedia_sections`, `get_wikipedia_page` | `ovos-wikipedia-solver` | None — public Wikipedia REST API | +| `ovos-ddg-tools` | `DuckDuckGoToolBox` | `search_duckduckgo`, `get_duckduckgo_infobox` | `ovos-ddg-solver-plugin` | None — DuckDuckGo Instant Answer API | +| `ovos-wolfram-alpha-tools` | `WolframAlphaToolBox` | `compute`, `compute_full` | `ovos-wolfram-alpha-solver` | Optional — free key at developer.wolframalpha.com; demo key bundled | +| `ovos-weather-tools` | `WeatherToolBox` | `get_current_weather`, `get_daily_forecast`, `get_hourly_forecast` | `ovos-skill-weather` | None — Open-Meteo public API | +| `ovos-datetime-tools` | `DateTimeToolBox` | `get_current_datetime`, `convert_timezone`, `get_timezone_for_location` | `ovos-skill-date-time` | None — stdlib + pytz | +| `ovos-ip-tools` | `IPAddressToolBox` | `get_local_ip_addresses`, `get_public_ip` | `ovos-skill-ip` | None | +| `ovos-iss-tools` | `ISSLocationToolBox` | `get_iss_position`, `get_iss_crew` | `ovos-skill-iss-location` | Optional — geonames.org user for reverse geocoding | +| `ovos-speedtest-tools` | `SpeedTestToolBox` | `run_speedtest` | `ovos-skill-speedtest` | None — Speedtest.net | +| `ovos-wallpapers-tools` | `WallpapersToolBox` | `search_wallpapers` | `ovos-skill-wallpapers` | None — wallhaven.cc public API | +| `ovos-wikihow-tools` | `WikiHowToolBox` | `search_wikihow`, `get_wikihow_steps` | `ovos-skill-wikihow` | None — pywikihow scraper | +| `ovos-wordnet-tools` | `WordNetToolBox` | `lookup_word`, `define_word` | `ovos-skill-wordnet` | None — local NLTK corpus | +| `ovos-skill-md-toolbox` | `SkillMDToolBox` | dynamic — one tool per installed `SKILL.md` | `ovos-agentic-loop` | Requires a configured `ChatEngine` (brain) | +| `ovos-filesystem-tools` | `FileSystemToolBox` | `read_file`, `write_file`, `list_directory`, `search_in_files`, `find_files` | `ovos-agentic-loop` | None | +| `ovos-shell-tools` | `ShellToolBox` | `run_command` | `ovos-agentic-loop` | None | +| `ovos-web-search-tools` | `WebSearchToolBox` | `web_search` | `ovos-agentic-loop` | None | +| `ovos-clock-tools` | `ClockToolBox` | `get_current_datetime` | `ovos-agentic-loop` | None | + +### Tool schema + +Each `AgentTool` (`agent_tools.py:40`) carries: +- `name` — snake_case identifier used by the LLM +- `description` — natural-language purpose shown to the LLM +- `argument_schema` — Pydantic `ToolArguments` subclass; JSON Schema auto-generated for LLM tool-calling APIs +- `output_schema` — Pydantic `ToolOutput` subclass; validated on every call +- `tool_call` — the Python callable; receives an instantiated `ToolArguments`, returns `ToolOutput` + +`ToolBox.tool_json_list` (`agent_tools.py:290`) converts all tools to the JSON Schema list format expected by OpenAI / Anthropic / Gemini tool-calling endpoints. + +--- + +## Available Chat Engines (`opm.agents.chat`) + +| Plugin ID | Class | Backend | Package | +|---|---|---|---| +| `ovos-chat-openai-plugin` | `OpenAIChatEngine` | OpenAI API | `ovos-openai-plugin` | +| `ovos-chat-gemini-plugin` | `GeminiChatEngine` | Google Gemini | `ovos-gemini-plugin` | +| `ovos-chat-gemini-code-plugin` | `GeminiCodeChatEngine` | Gemini (code) | `ovos-gemini-plugin` | +| `ovos-chat-gemini-session-plugin` | `GeminiSessionChatEngine` | Gemini (session) | `ovos-gemini-plugin` | +| `ovos-chat-claude-plugin` | `ClaudeChatEngine` | Anthropic Claude | `ovos-claude-plugin` | +| `ovos-chat-claude-code-plugin` | `ClaudeCodeChatEngine` | Claude (code) | `ovos-claude-plugin` | +| `ovos-chat-claude-code-session-plugin` | `ClaudeCodeSessionChatEngine` | Claude (session) | `ovos-claude-plugin` | +| `ovos-chat-kilo-plugin` | `KiloChatEngine` | Kilo (Anthropic) | `ovos-kilo-plugin` | +| `ovos-chat-kilo-session-plugin` | `KiloSessionChatEngine` | Kilo (session) | `ovos-kilo-plugin` | +| `ovos-chat-gguf-plugin` | `GGUFChatEngine` | Local GGUF (llama.cpp) | `ovos-gguf-plugin` | +| `ovos-chat-qwen-code-plugin` | `QwenCodeChatEngine` | Qwen-Code | `ovos-qwen-code-plugin` | +| `ovos-chat-opencode-plugin` | `OpenCodeChatEngine` | OpenCode | `ovos-opencode-plugin` | +| `ovos-chat-opencode-session-plugin` | `OpenCodeSessionChatEngine` | OpenCode (session) | `ovos-opencode-plugin` | +| `ovos-wikigpt` | `WikiGPTSolver` | Wikipedia RAG | `ovos-wikipedia-solver` | +| `ovos-react-loop` | `ReActLoopEnginePlugin` | ReAct over any ChatEngine + ToolBoxes | `ovos-agentic-loop` | +| `ovos-plan-execute-loop` | `PlanAndExecuteEnginePlugin` | Plan-and-Execute | `ovos-agentic-loop` | +| `ovos-reflexion-loop` | `ReflexionEnginePlugin` | Reflexion | `ovos-agentic-loop` | +| `ovos-self-ask-loop` | `SelfAskEnginePlugin` | Self-Ask | `ovos-agentic-loop` | +| `ovos-chain-of-thought-loop` | `ChainOfThoughtEnginePlugin` | Chain-of-Thought | `ovos-agentic-loop` | +| `ovos-mos-king-reranker` | `ReRankerKingMoSPlugin` | Mixture-of-Solvers (reranker) | `ovos-MoS` | +| `ovos-mos-king-generative` | `GenerativeKingMoSPlugin` | MoS (generative king) | `ovos-MoS` | +| `ovos-mos-democracy` | `DemocracyMoSPlugin` | MoS (majority vote) | `ovos-MoS` | +| `ovos-mos-duopoly-reranker` | `ReRankerDuopolyMoSPlugin` | MoS (duopoly reranker) | `ovos-MoS` | +| `ovos-mos-duopoly-generative` | `GenerativeDuopolyMoSPlugin` | MoS (duopoly generative) | `ovos-MoS` | + +### Multimodal Chat Engines (`opm.agents.chat.multimodal`) + +| Plugin ID | Class | Backend | Package | +|---|---|---|---| +| `ovos-chat-multimodal-gemini-plugin` | `GeminiMultimodalChatEngine` | Gemini | `ovos-gemini-plugin` | +| `ovos-chat-multimodal-claude-plugin` | `ClaudeMultimodalChatEngine` | Claude | `ovos-claude-plugin` | +| `ovos-chat-multimodal-kilo-plugin` | `KiloMultimodalChatEngine` | Kilo | `ovos-kilo-plugin` | +| `ovos-chat-multimodal-qwen-code-plugin` | `QwenCodeMultimodalChatEngine` | Qwen-Code | `ovos-qwen-code-plugin` | + +`ChatEngine.continue_chat` signature — `agents.py:210`: +```python +def continue_chat(self, messages: List[AgentMessage], + session_id: str = "default", + lang: Optional[str] = None, + units: Optional[str] = None) -> AgentMessage: +``` + +`ChatEngine` also provides `stream_tokens`, `stream_sentences`, and `get_response` helpers (`agents.py:228–300`). Plugins only need to implement `continue_chat`. + +--- + +## Available Personas (`opm.plugin.persona`) + +Each persona entry is a dict defining `chat_engine`, `system_prompt`, and optionally `toolboxes`. Loaded and wired by the `ovos-persona` service. + +| Persona ID | Backend | Package | +|---|---|---| +| `OpenAI` | `ovos-chat-openai-plugin` | `ovos-openai-plugin` | +| `Claude` | `ovos-chat-claude-plugin` | `ovos-claude-plugin` | +| `Gemini` | `ovos-chat-gemini-plugin` | `ovos-gemini-plugin` | +| `Kilo` | `ovos-chat-kilo-plugin` | `ovos-kilo-plugin` | +| `QwenCode` | `ovos-chat-qwen-code-plugin` | `ovos-qwen-code-plugin` | +| `OpenCode` | `ovos-chat-opencode-plugin` | `ovos-opencode-plugin` | +| `Wikipedia` | Wikipedia solver | `ovos-wikipedia-solver` | +| `WikiGPT` | `ovos-wikigpt` | `ovos-wikipedia-solver` | +| `DuckDuckGo` | DDG solver | `ovos-ddg-solver-plugin` | +| `Wolfram Alpha` | Wolfram solver | `ovos-wolfram-alpha-solver` | +| `WikiHow` | WikiHow solver | `ovos-skill-wikihow` | +| `Wordnet` | WordNet solver | `ovos-skill-wordnet` | + +--- + +## How to Implement a ToolBox + +Register under `opm.agents.toolbox` in `pyproject.toml`: + +```toml +[project.entry-points."opm.agents.toolbox"] +my-tools = "my_package.toolbox:MyToolBox" +``` + +Minimal implementation (`agent_tools.py:56`): + +```python +from ovos_plugin_manager.templates.agent_tools import AgentTool, ToolArguments, ToolBox, ToolOutput +from pydantic import Field + +class MyArgs(ToolArguments): + query: str = Field(..., description="Input text.") + +class MyOutput(ToolOutput): + result: str = Field(..., description="Tool result.") + +class MyToolBox(ToolBox): + toolbox_id = "my-tools" + + def __init__(self, config=None): + self.config = config or {} + super().__init__(toolbox_id=self.toolbox_id) + + def discover_tools(self): + return [AgentTool( + name="my_tool", + description="Does something useful.", + argument_schema=MyArgs, + output_schema=MyOutput, + tool_call=lambda args: MyOutput(result=args.query.upper()), + )] +``` + +`ToolBox.call_tool` validates input and output against the Pydantic schemas automatically (`agent_tools.py:195`). `discover_tools` is called once at init and again lazily if a tool is not found in the cache (`agent_tools.py:104`). + +--- + +## How to Implement a ChatEngine + +Register under `opm.agents.chat` in `pyproject.toml`: + +```toml +[project.entry-points."opm.agents.chat"] +my-chat-engine = "my_package.chat:MyChatEngine" +``` + +Minimal implementation (`agents.py:195`): + +```python +from ovos_plugin_manager.templates.agents import ChatEngine, AgentMessage, MessageRole +from typing import List, Optional + +class MyChatEngine(ChatEngine): + def continue_chat(self, messages: List[AgentMessage], + session_id: str = "default", + lang: Optional[str] = None, + units: Optional[str] = None) -> AgentMessage: + # messages[-1] is the latest user message + reply = call_my_llm_api([m.__dict__ for m in messages]) + return AgentMessage(role=MessageRole.ASSISTANT, content=reply) +``` + +For streaming, override `stream_tokens` (token-level) or `stream_sentences` (sentence-level, TTS-ready) (`agents.py:228–278`). The default implementations fall back to `continue_chat`. + +--- + +## Configuration + +Config is passed as a plain `dict` to `__init__`. OPM reads plugin config from the OVOS `Configuration()` singleton under the plugin's entry point name. Standard keys used by most agent plugins: + +| Key | Type | Default | Description | +|---|---|---|---| +| `lang` | `str` | session lang | BCP-47 language code | +| `system_prompt` | `str` | `""` | System prompt for `AgentContextManager` plugins (`agents.py:61`) | +| `context_ttl` | `int` | `120` | Seconds before coreference context is pruned (`agents.py:598`) | + +ToolBox-specific keys are documented in each plugin's module docstring. diff --git a/docs/index.md b/docs/index.md index a39c5eac..b93da8d4 100644 --- a/docs/index.md +++ b/docs/index.md @@ -21,7 +21,8 @@ loading installed plugins at runtime via Python entry points. - [PHAL](api/phal.md) — Platform/Hardware Abstraction Layer - [Transformers](api/transformers.md) — Audio, Utterance, Dialog, Metadata, TTS, Intent transformers - [Agents & Solvers](api/agents.md) — NLP solver and agent engine plugins - - [Agent Tools](api/agent-tools.md) — ToolBox plugin API (`opm.persona.tool`) + - [Agent Tools](api/agent-tools.md) — ToolBox plugin API (`opm.agents.toolbox`) +- [Agent Plugins](agents.md) — all available ChatEngine, ToolBox, and Persona plugins with entry point registry - [Pipeline](api/pipeline.md) — Intent matching pipeline plugins - [Language](api/language.md) — Translation and language detection plugins diff --git a/ovos_plugin_manager/templates/agent_tools.py b/ovos_plugin_manager/templates/agent_tools.py index 1d44ad29..d3639695 100644 --- a/ovos_plugin_manager/templates/agent_tools.py +++ b/ovos_plugin_manager/templates/agent_tools.py @@ -119,6 +119,7 @@ def handle_discover(self, message: Message) -> None: Args: message: The incoming discovery Message object. """ + self.refresh_tools() response_data: Dict[str, Any] = { "tools": self.tool_json_list, "toolbox_id": self.toolbox_id @@ -141,7 +142,7 @@ def handle_call(self, message: Message) -> None: try: # Use the execution wrapper method result: ToolOutput = self.call_tool(name, tool_kwargs) - self.bus.emit(message.response({"result": result.model_dump(), "toolbox_id": self.toolbox_id})) + self.bus.emit(message.response({"result": result.model_dump(mode='json'), "toolbox_id": self.toolbox_id})) except Exception as e: # Catch all execution exceptions (including ValueErrors from call_tool) error: str = f"{type(e).__name__}: {str(e)}" @@ -167,7 +168,7 @@ def validate_input(tool: AgentTool, tool_kwargs: Dict[str, Any]) -> ToolArgument # Instantiating the Pydantic model implicitly validates the input return ArgsModel(**tool_kwargs) except Exception as e: - raise ValueError(f"Invalid input for '{tool.name}': {tool_kwargs}") from e + raise ValueError(f"Invalid input for '{tool.name}'") from e @staticmethod def validate_output(tool: AgentTool, raw_result: Dict[str, Any]) -> ToolOutput: @@ -190,7 +191,7 @@ def validate_output(tool: AgentTool, raw_result: Dict[str, Any]) -> ToolOutput: # The .model_validate() method returns a validated Pydantic object return OutputModel.model_validate(raw_result) except Exception as e: - raise ValueError(f"Invalid output from '{tool.name}': {raw_result}") from e + raise ValueError(f"Invalid output from '{tool.name}'") from e def call_tool(self, name: str, tool_kwargs: Union[ToolArguments, Dict[str, Any]]) -> ToolOutput: """ diff --git a/pyproject.toml b/pyproject.toml index 7b83f2c0..bd5595c8 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -32,6 +32,7 @@ Repository = "https://github.com/OpenVoiceOS/OVOS-plugin-manager" test = [ "pytest", "pytest-cov", + "pydantic~=2.0", "python-vlc", "ovos-hardware-helpers>=1.0.0" ] diff --git a/test/unittests/test_agent_tools.py b/test/unittests/test_agent_tools.py index 30546c65..f624f744 100644 --- a/test/unittests/test_agent_tools.py +++ b/test/unittests/test_agent_tools.py @@ -45,10 +45,6 @@ def failing_logic(args: AddArgs) -> AddOutput: raise RuntimeError("intentional failure") -class MathToolBox(MathToolBox if False else object): - pass - - class MathToolBox(ToolBox): def __init__(self, bus=None, fail_discover: bool = False): self._fail_discover = fail_discover diff --git a/uv.lock b/uv.lock index 74f7f54f..c746959d 100644 --- a/uv.lock +++ b/uv.lock @@ -9,6 +9,15 @@ resolution-markers = [ "python_full_version < '3.10'", ] +[[package]] +name = "annotated-types" +version = "0.7.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/ee/67/531ea369ba64dcff5ec9c3402f9f51bf748cec26dde048a2f973a4eea7f5/annotated_types-0.7.0.tar.gz", hash = "sha256:aff07c09a53a08bc8cfccb9c85b05f1aa9a2a6f23728d790723543408344ce89", size = 16081, upload-time = "2024-05-20T21:33:25.928Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/78/b6/6307fbef88d9b5ee7421e68d78a9f162e0da4900bc5f5793f6d3d0e34fb8/annotated_types-0.7.0-py3-none-any.whl", hash = "sha256:1f02e8b43a8fbbc3f3e0d4f0f4bfc8131bcb4eebe8849b8e5c773f3a1c582a53", size = 13643, upload-time = "2024-05-20T21:33:24.1Z" }, +] + [[package]] name = "audioop-lts" version = "0.2.2" @@ -494,7 +503,7 @@ name = "exceptiongroup" version = "1.3.1" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "typing-extensions", marker = "python_full_version < '3.12'" }, + { name = "typing-extensions", marker = "python_full_version < '3.11'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/50/79/66800aadf48771f6b62f7eb014e352e5d06856655206165d775e675a02c9/exceptiongroup-1.3.1.tar.gz", hash = "sha256:8b412432c6055b0b7d14c310000ae93352ed6754f70fa8f7c34141f91c4e3219", size = 30371, upload-time = "2025-11-21T23:01:54.787Z" } wheels = [ @@ -939,6 +948,7 @@ dependencies = [ [package.optional-dependencies] test = [ { name = "ovos-hardware-helpers" }, + { name = "pydantic" }, { name = "pytest", version = "8.4.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, { name = "pytest", version = "9.0.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, { name = "pytest-cov" }, @@ -955,6 +965,7 @@ requires-dist = [ { name = "ovos-config", specifier = ">=0.0.12,<3.0.0" }, { name = "ovos-hardware-helpers", marker = "extra == 'test'", specifier = ">=1.0.0" }, { name = "ovos-utils", specifier = ">=0.2.1,<1.0.0" }, + { name = "pydantic", marker = "extra == 'test'", specifier = "~=2.0" }, { name = "pytest", marker = "extra == 'test'" }, { name = "pytest-cov", marker = "extra == 'test'" }, { name = "python-vlc", marker = "extra == 'test'" }, @@ -1099,6 +1110,152 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/6a/fc/81b8a915762bdbd6e1d4582e186ec4bdb7fa60998f83d3a81bff3a5d9e89/pyahocorasick-2.3.0-cp314-cp314-win_amd64.whl", hash = "sha256:0c6d9379ddf58cad4abd661795b4b975ba9b542227e78de6ded80757c3ac599d", size = 35985, upload-time = "2025-12-17T13:02:27.84Z" }, ] +[[package]] +name = "pydantic" +version = "2.12.5" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "annotated-types" }, + { name = "pydantic-core" }, + { name = "typing-extensions" }, + { name = "typing-inspection" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/69/44/36f1a6e523abc58ae5f928898e4aca2e0ea509b5aa6f6f392a5d882be928/pydantic-2.12.5.tar.gz", hash = "sha256:4d351024c75c0f085a9febbb665ce8c0c6ec5d30e903bdb6394b7ede26aebb49", size = 821591, upload-time = "2025-11-26T15:11:46.471Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/5a/87/b70ad306ebb6f9b585f114d0ac2137d792b48be34d732d60e597c2f8465a/pydantic-2.12.5-py3-none-any.whl", hash = "sha256:e561593fccf61e8a20fc46dfc2dfe075b8be7d0188df33f221ad1f0139180f9d", size = 463580, upload-time = "2025-11-26T15:11:44.605Z" }, +] + +[[package]] +name = "pydantic-core" +version = "2.41.5" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/71/70/23b021c950c2addd24ec408e9ab05d59b035b39d97cdc1130e1bce647bb6/pydantic_core-2.41.5.tar.gz", hash = "sha256:08daa51ea16ad373ffd5e7606252cc32f07bc72b28284b6bc9c6df804816476e", size = 460952, upload-time = "2025-11-04T13:43:49.098Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c6/90/32c9941e728d564b411d574d8ee0cf09b12ec978cb22b294995bae5549a5/pydantic_core-2.41.5-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:77b63866ca88d804225eaa4af3e664c5faf3568cea95360d21f4725ab6e07146", size = 2107298, upload-time = "2025-11-04T13:39:04.116Z" }, + { url = "https://files.pythonhosted.org/packages/fb/a8/61c96a77fe28993d9a6fb0f4127e05430a267b235a124545d79fea46dd65/pydantic_core-2.41.5-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:dfa8a0c812ac681395907e71e1274819dec685fec28273a28905df579ef137e2", size = 1901475, upload-time = "2025-11-04T13:39:06.055Z" }, + { url = "https://files.pythonhosted.org/packages/5d/b6/338abf60225acc18cdc08b4faef592d0310923d19a87fba1faf05af5346e/pydantic_core-2.41.5-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5921a4d3ca3aee735d9fd163808f5e8dd6c6972101e4adbda9a4667908849b97", size = 1918815, upload-time = "2025-11-04T13:39:10.41Z" }, + { url = "https://files.pythonhosted.org/packages/d1/1c/2ed0433e682983d8e8cba9c8d8ef274d4791ec6a6f24c58935b90e780e0a/pydantic_core-2.41.5-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:e25c479382d26a2a41b7ebea1043564a937db462816ea07afa8a44c0866d52f9", size = 2065567, upload-time = "2025-11-04T13:39:12.244Z" }, + { url = "https://files.pythonhosted.org/packages/b3/24/cf84974ee7d6eae06b9e63289b7b8f6549d416b5c199ca2d7ce13bbcf619/pydantic_core-2.41.5-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f547144f2966e1e16ae626d8ce72b4cfa0caedc7fa28052001c94fb2fcaa1c52", size = 2230442, upload-time = "2025-11-04T13:39:13.962Z" }, + { url = "https://files.pythonhosted.org/packages/fd/21/4e287865504b3edc0136c89c9c09431be326168b1eb7841911cbc877a995/pydantic_core-2.41.5-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:6f52298fbd394f9ed112d56f3d11aabd0d5bd27beb3084cc3d8ad069483b8941", size = 2350956, upload-time = "2025-11-04T13:39:15.889Z" }, + { url = "https://files.pythonhosted.org/packages/a8/76/7727ef2ffa4b62fcab916686a68a0426b9b790139720e1934e8ba797e238/pydantic_core-2.41.5-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:100baa204bb412b74fe285fb0f3a385256dad1d1879f0a5cb1499ed2e83d132a", size = 2068253, upload-time = "2025-11-04T13:39:17.403Z" }, + { url = "https://files.pythonhosted.org/packages/d5/8c/a4abfc79604bcb4c748e18975c44f94f756f08fb04218d5cb87eb0d3a63e/pydantic_core-2.41.5-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:05a2c8852530ad2812cb7914dc61a1125dc4e06252ee98e5638a12da6cc6fb6c", size = 2177050, upload-time = "2025-11-04T13:39:19.351Z" }, + { url = "https://files.pythonhosted.org/packages/67/b1/de2e9a9a79b480f9cb0b6e8b6ba4c50b18d4e89852426364c66aa82bb7b3/pydantic_core-2.41.5-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:29452c56df2ed968d18d7e21f4ab0ac55e71dc59524872f6fc57dcf4a3249ed2", size = 2147178, upload-time = "2025-11-04T13:39:21Z" }, + { url = "https://files.pythonhosted.org/packages/16/c1/dfb33f837a47b20417500efaa0378adc6635b3c79e8369ff7a03c494b4ac/pydantic_core-2.41.5-cp310-cp310-musllinux_1_1_armv7l.whl", hash = "sha256:d5160812ea7a8a2ffbe233d8da666880cad0cbaf5d4de74ae15c313213d62556", size = 2341833, upload-time = "2025-11-04T13:39:22.606Z" }, + { url = "https://files.pythonhosted.org/packages/47/36/00f398642a0f4b815a9a558c4f1dca1b4020a7d49562807d7bc9ff279a6c/pydantic_core-2.41.5-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:df3959765b553b9440adfd3c795617c352154e497a4eaf3752555cfb5da8fc49", size = 2321156, upload-time = "2025-11-04T13:39:25.843Z" }, + { url = "https://files.pythonhosted.org/packages/7e/70/cad3acd89fde2010807354d978725ae111ddf6d0ea46d1ea1775b5c1bd0c/pydantic_core-2.41.5-cp310-cp310-win32.whl", hash = "sha256:1f8d33a7f4d5a7889e60dc39856d76d09333d8a6ed0f5f1190635cbec70ec4ba", size = 1989378, upload-time = "2025-11-04T13:39:27.92Z" }, + { url = "https://files.pythonhosted.org/packages/76/92/d338652464c6c367e5608e4488201702cd1cbb0f33f7b6a85a60fe5f3720/pydantic_core-2.41.5-cp310-cp310-win_amd64.whl", hash = "sha256:62de39db01b8d593e45871af2af9e497295db8d73b085f6bfd0b18c83c70a8f9", size = 2013622, upload-time = "2025-11-04T13:39:29.848Z" }, + { url = "https://files.pythonhosted.org/packages/e8/72/74a989dd9f2084b3d9530b0915fdda64ac48831c30dbf7c72a41a5232db8/pydantic_core-2.41.5-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:a3a52f6156e73e7ccb0f8cced536adccb7042be67cb45f9562e12b319c119da6", size = 2105873, upload-time = "2025-11-04T13:39:31.373Z" }, + { url = "https://files.pythonhosted.org/packages/12/44/37e403fd9455708b3b942949e1d7febc02167662bf1a7da5b78ee1ea2842/pydantic_core-2.41.5-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:7f3bf998340c6d4b0c9a2f02d6a400e51f123b59565d74dc60d252ce888c260b", size = 1899826, upload-time = "2025-11-04T13:39:32.897Z" }, + { url = "https://files.pythonhosted.org/packages/33/7f/1d5cab3ccf44c1935a359d51a8a2a9e1a654b744b5e7f80d41b88d501eec/pydantic_core-2.41.5-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:378bec5c66998815d224c9ca994f1e14c0c21cb95d2f52b6021cc0b2a58f2a5a", size = 1917869, upload-time = "2025-11-04T13:39:34.469Z" }, + { url = "https://files.pythonhosted.org/packages/6e/6a/30d94a9674a7fe4f4744052ed6c5e083424510be1e93da5bc47569d11810/pydantic_core-2.41.5-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:e7b576130c69225432866fe2f4a469a85a54ade141d96fd396dffcf607b558f8", size = 2063890, upload-time = "2025-11-04T13:39:36.053Z" }, + { url = "https://files.pythonhosted.org/packages/50/be/76e5d46203fcb2750e542f32e6c371ffa9b8ad17364cf94bb0818dbfb50c/pydantic_core-2.41.5-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:6cb58b9c66f7e4179a2d5e0f849c48eff5c1fca560994d6eb6543abf955a149e", size = 2229740, upload-time = "2025-11-04T13:39:37.753Z" }, + { url = "https://files.pythonhosted.org/packages/d3/ee/fed784df0144793489f87db310a6bbf8118d7b630ed07aa180d6067e653a/pydantic_core-2.41.5-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:88942d3a3dff3afc8288c21e565e476fc278902ae4d6d134f1eeda118cc830b1", size = 2350021, upload-time = "2025-11-04T13:39:40.94Z" }, + { url = "https://files.pythonhosted.org/packages/c8/be/8fed28dd0a180dca19e72c233cbf58efa36df055e5b9d90d64fd1740b828/pydantic_core-2.41.5-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f31d95a179f8d64d90f6831d71fa93290893a33148d890ba15de25642c5d075b", size = 2066378, upload-time = "2025-11-04T13:39:42.523Z" }, + { url = "https://files.pythonhosted.org/packages/b0/3b/698cf8ae1d536a010e05121b4958b1257f0b5522085e335360e53a6b1c8b/pydantic_core-2.41.5-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:c1df3d34aced70add6f867a8cf413e299177e0c22660cc767218373d0779487b", size = 2175761, upload-time = "2025-11-04T13:39:44.553Z" }, + { url = "https://files.pythonhosted.org/packages/b8/ba/15d537423939553116dea94ce02f9c31be0fa9d0b806d427e0308ec17145/pydantic_core-2.41.5-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:4009935984bd36bd2c774e13f9a09563ce8de4abaa7226f5108262fa3e637284", size = 2146303, upload-time = "2025-11-04T13:39:46.238Z" }, + { url = "https://files.pythonhosted.org/packages/58/7f/0de669bf37d206723795f9c90c82966726a2ab06c336deba4735b55af431/pydantic_core-2.41.5-cp311-cp311-musllinux_1_1_armv7l.whl", hash = "sha256:34a64bc3441dc1213096a20fe27e8e128bd3ff89921706e83c0b1ac971276594", size = 2340355, upload-time = "2025-11-04T13:39:48.002Z" }, + { url = "https://files.pythonhosted.org/packages/e5/de/e7482c435b83d7e3c3ee5ee4451f6e8973cff0eb6007d2872ce6383f6398/pydantic_core-2.41.5-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:c9e19dd6e28fdcaa5a1de679aec4141f691023916427ef9bae8584f9c2fb3b0e", size = 2319875, upload-time = "2025-11-04T13:39:49.705Z" }, + { url = "https://files.pythonhosted.org/packages/fe/e6/8c9e81bb6dd7560e33b9053351c29f30c8194b72f2d6932888581f503482/pydantic_core-2.41.5-cp311-cp311-win32.whl", hash = "sha256:2c010c6ded393148374c0f6f0bf89d206bf3217f201faa0635dcd56bd1520f6b", size = 1987549, upload-time = "2025-11-04T13:39:51.842Z" }, + { url = "https://files.pythonhosted.org/packages/11/66/f14d1d978ea94d1bc21fc98fcf570f9542fe55bfcc40269d4e1a21c19bf7/pydantic_core-2.41.5-cp311-cp311-win_amd64.whl", hash = "sha256:76ee27c6e9c7f16f47db7a94157112a2f3a00e958bc626e2f4ee8bec5c328fbe", size = 2011305, upload-time = "2025-11-04T13:39:53.485Z" }, + { url = "https://files.pythonhosted.org/packages/56/d8/0e271434e8efd03186c5386671328154ee349ff0354d83c74f5caaf096ed/pydantic_core-2.41.5-cp311-cp311-win_arm64.whl", hash = "sha256:4bc36bbc0b7584de96561184ad7f012478987882ebf9f9c389b23f432ea3d90f", size = 1972902, upload-time = "2025-11-04T13:39:56.488Z" }, + { url = "https://files.pythonhosted.org/packages/5f/5d/5f6c63eebb5afee93bcaae4ce9a898f3373ca23df3ccaef086d0233a35a7/pydantic_core-2.41.5-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:f41a7489d32336dbf2199c8c0a215390a751c5b014c2c1c5366e817202e9cdf7", size = 2110990, upload-time = "2025-11-04T13:39:58.079Z" }, + { url = "https://files.pythonhosted.org/packages/aa/32/9c2e8ccb57c01111e0fd091f236c7b371c1bccea0fa85247ac55b1e2b6b6/pydantic_core-2.41.5-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:070259a8818988b9a84a449a2a7337c7f430a22acc0859c6b110aa7212a6d9c0", size = 1896003, upload-time = "2025-11-04T13:39:59.956Z" }, + { url = "https://files.pythonhosted.org/packages/68/b8/a01b53cb0e59139fbc9e4fda3e9724ede8de279097179be4ff31f1abb65a/pydantic_core-2.41.5-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e96cea19e34778f8d59fe40775a7a574d95816eb150850a85a7a4c8f4b94ac69", size = 1919200, upload-time = "2025-11-04T13:40:02.241Z" }, + { url = "https://files.pythonhosted.org/packages/38/de/8c36b5198a29bdaade07b5985e80a233a5ac27137846f3bc2d3b40a47360/pydantic_core-2.41.5-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:ed2e99c456e3fadd05c991f8f437ef902e00eedf34320ba2b0842bd1c3ca3a75", size = 2052578, upload-time = "2025-11-04T13:40:04.401Z" }, + { url = "https://files.pythonhosted.org/packages/00/b5/0e8e4b5b081eac6cb3dbb7e60a65907549a1ce035a724368c330112adfdd/pydantic_core-2.41.5-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:65840751b72fbfd82c3c640cff9284545342a4f1eb1586ad0636955b261b0b05", size = 2208504, upload-time = "2025-11-04T13:40:06.072Z" }, + { url = "https://files.pythonhosted.org/packages/77/56/87a61aad59c7c5b9dc8caad5a41a5545cba3810c3e828708b3d7404f6cef/pydantic_core-2.41.5-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e536c98a7626a98feb2d3eaf75944ef6f3dbee447e1f841eae16f2f0a72d8ddc", size = 2335816, upload-time = "2025-11-04T13:40:07.835Z" }, + { url = "https://files.pythonhosted.org/packages/0d/76/941cc9f73529988688a665a5c0ecff1112b3d95ab48f81db5f7606f522d3/pydantic_core-2.41.5-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:eceb81a8d74f9267ef4081e246ffd6d129da5d87e37a77c9bde550cb04870c1c", size = 2075366, upload-time = "2025-11-04T13:40:09.804Z" }, + { url = "https://files.pythonhosted.org/packages/d3/43/ebef01f69baa07a482844faaa0a591bad1ef129253ffd0cdaa9d8a7f72d3/pydantic_core-2.41.5-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:d38548150c39b74aeeb0ce8ee1d8e82696f4a4e16ddc6de7b1d8823f7de4b9b5", size = 2171698, upload-time = "2025-11-04T13:40:12.004Z" }, + { url = "https://files.pythonhosted.org/packages/b1/87/41f3202e4193e3bacfc2c065fab7706ebe81af46a83d3e27605029c1f5a6/pydantic_core-2.41.5-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:c23e27686783f60290e36827f9c626e63154b82b116d7fe9adba1fda36da706c", size = 2132603, upload-time = "2025-11-04T13:40:13.868Z" }, + { url = "https://files.pythonhosted.org/packages/49/7d/4c00df99cb12070b6bccdef4a195255e6020a550d572768d92cc54dba91a/pydantic_core-2.41.5-cp312-cp312-musllinux_1_1_armv7l.whl", hash = "sha256:482c982f814460eabe1d3bb0adfdc583387bd4691ef00b90575ca0d2b6fe2294", size = 2329591, upload-time = "2025-11-04T13:40:15.672Z" }, + { url = "https://files.pythonhosted.org/packages/cc/6a/ebf4b1d65d458f3cda6a7335d141305dfa19bdc61140a884d165a8a1bbc7/pydantic_core-2.41.5-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:bfea2a5f0b4d8d43adf9d7b8bf019fb46fdd10a2e5cde477fbcb9d1fa08c68e1", size = 2319068, upload-time = "2025-11-04T13:40:17.532Z" }, + { url = "https://files.pythonhosted.org/packages/49/3b/774f2b5cd4192d5ab75870ce4381fd89cf218af999515baf07e7206753f0/pydantic_core-2.41.5-cp312-cp312-win32.whl", hash = "sha256:b74557b16e390ec12dca509bce9264c3bbd128f8a2c376eaa68003d7f327276d", size = 1985908, upload-time = "2025-11-04T13:40:19.309Z" }, + { url = "https://files.pythonhosted.org/packages/86/45/00173a033c801cacf67c190fef088789394feaf88a98a7035b0e40d53dc9/pydantic_core-2.41.5-cp312-cp312-win_amd64.whl", hash = "sha256:1962293292865bca8e54702b08a4f26da73adc83dd1fcf26fbc875b35d81c815", size = 2020145, upload-time = "2025-11-04T13:40:21.548Z" }, + { url = "https://files.pythonhosted.org/packages/f9/22/91fbc821fa6d261b376a3f73809f907cec5ca6025642c463d3488aad22fb/pydantic_core-2.41.5-cp312-cp312-win_arm64.whl", hash = "sha256:1746d4a3d9a794cacae06a5eaaccb4b8643a131d45fbc9af23e353dc0a5ba5c3", size = 1976179, upload-time = "2025-11-04T13:40:23.393Z" }, + { url = "https://files.pythonhosted.org/packages/87/06/8806241ff1f70d9939f9af039c6c35f2360cf16e93c2ca76f184e76b1564/pydantic_core-2.41.5-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:941103c9be18ac8daf7b7adca8228f8ed6bb7a1849020f643b3a14d15b1924d9", size = 2120403, upload-time = "2025-11-04T13:40:25.248Z" }, + { url = "https://files.pythonhosted.org/packages/94/02/abfa0e0bda67faa65fef1c84971c7e45928e108fe24333c81f3bfe35d5f5/pydantic_core-2.41.5-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:112e305c3314f40c93998e567879e887a3160bb8689ef3d2c04b6cc62c33ac34", size = 1896206, upload-time = "2025-11-04T13:40:27.099Z" }, + { url = "https://files.pythonhosted.org/packages/15/df/a4c740c0943e93e6500f9eb23f4ca7ec9bf71b19e608ae5b579678c8d02f/pydantic_core-2.41.5-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0cbaad15cb0c90aa221d43c00e77bb33c93e8d36e0bf74760cd00e732d10a6a0", size = 1919307, upload-time = "2025-11-04T13:40:29.806Z" }, + { url = "https://files.pythonhosted.org/packages/9a/e3/6324802931ae1d123528988e0e86587c2072ac2e5394b4bc2bc34b61ff6e/pydantic_core-2.41.5-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:03ca43e12fab6023fc79d28ca6b39b05f794ad08ec2feccc59a339b02f2b3d33", size = 2063258, upload-time = "2025-11-04T13:40:33.544Z" }, + { url = "https://files.pythonhosted.org/packages/c9/d4/2230d7151d4957dd79c3044ea26346c148c98fbf0ee6ebd41056f2d62ab5/pydantic_core-2.41.5-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:dc799088c08fa04e43144b164feb0c13f9a0bc40503f8df3e9fde58a3c0c101e", size = 2214917, upload-time = "2025-11-04T13:40:35.479Z" }, + { url = "https://files.pythonhosted.org/packages/e6/9f/eaac5df17a3672fef0081b6c1bb0b82b33ee89aa5cec0d7b05f52fd4a1fa/pydantic_core-2.41.5-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:97aeba56665b4c3235a0e52b2c2f5ae9cd071b8a8310ad27bddb3f7fb30e9aa2", size = 2332186, upload-time = "2025-11-04T13:40:37.436Z" }, + { url = "https://files.pythonhosted.org/packages/cf/4e/35a80cae583a37cf15604b44240e45c05e04e86f9cfd766623149297e971/pydantic_core-2.41.5-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:406bf18d345822d6c21366031003612b9c77b3e29ffdb0f612367352aab7d586", size = 2073164, upload-time = "2025-11-04T13:40:40.289Z" }, + { url = "https://files.pythonhosted.org/packages/bf/e3/f6e262673c6140dd3305d144d032f7bd5f7497d3871c1428521f19f9efa2/pydantic_core-2.41.5-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:b93590ae81f7010dbe380cdeab6f515902ebcbefe0b9327cc4804d74e93ae69d", size = 2179146, upload-time = "2025-11-04T13:40:42.809Z" }, + { url = "https://files.pythonhosted.org/packages/75/c7/20bd7fc05f0c6ea2056a4565c6f36f8968c0924f19b7d97bbfea55780e73/pydantic_core-2.41.5-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:01a3d0ab748ee531f4ea6c3e48ad9dac84ddba4b0d82291f87248f2f9de8d740", size = 2137788, upload-time = "2025-11-04T13:40:44.752Z" }, + { url = "https://files.pythonhosted.org/packages/3a/8d/34318ef985c45196e004bc46c6eab2eda437e744c124ef0dbe1ff2c9d06b/pydantic_core-2.41.5-cp313-cp313-musllinux_1_1_armv7l.whl", hash = "sha256:6561e94ba9dacc9c61bce40e2d6bdc3bfaa0259d3ff36ace3b1e6901936d2e3e", size = 2340133, upload-time = "2025-11-04T13:40:46.66Z" }, + { url = "https://files.pythonhosted.org/packages/9c/59/013626bf8c78a5a5d9350d12e7697d3d4de951a75565496abd40ccd46bee/pydantic_core-2.41.5-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:915c3d10f81bec3a74fbd4faebe8391013ba61e5a1a8d48c4455b923bdda7858", size = 2324852, upload-time = "2025-11-04T13:40:48.575Z" }, + { url = "https://files.pythonhosted.org/packages/1a/d9/c248c103856f807ef70c18a4f986693a46a8ffe1602e5d361485da502d20/pydantic_core-2.41.5-cp313-cp313-win32.whl", hash = "sha256:650ae77860b45cfa6e2cdafc42618ceafab3a2d9a3811fcfbd3bbf8ac3c40d36", size = 1994679, upload-time = "2025-11-04T13:40:50.619Z" }, + { url = "https://files.pythonhosted.org/packages/9e/8b/341991b158ddab181cff136acd2552c9f35bd30380422a639c0671e99a91/pydantic_core-2.41.5-cp313-cp313-win_amd64.whl", hash = "sha256:79ec52ec461e99e13791ec6508c722742ad745571f234ea6255bed38c6480f11", size = 2019766, upload-time = "2025-11-04T13:40:52.631Z" }, + { url = "https://files.pythonhosted.org/packages/73/7d/f2f9db34af103bea3e09735bb40b021788a5e834c81eedb541991badf8f5/pydantic_core-2.41.5-cp313-cp313-win_arm64.whl", hash = "sha256:3f84d5c1b4ab906093bdc1ff10484838aca54ef08de4afa9de0f5f14d69639cd", size = 1981005, upload-time = "2025-11-04T13:40:54.734Z" }, + { url = "https://files.pythonhosted.org/packages/ea/28/46b7c5c9635ae96ea0fbb779e271a38129df2550f763937659ee6c5dbc65/pydantic_core-2.41.5-cp314-cp314-macosx_10_12_x86_64.whl", hash = "sha256:3f37a19d7ebcdd20b96485056ba9e8b304e27d9904d233d7b1015db320e51f0a", size = 2119622, upload-time = "2025-11-04T13:40:56.68Z" }, + { url = "https://files.pythonhosted.org/packages/74/1a/145646e5687e8d9a1e8d09acb278c8535ebe9e972e1f162ed338a622f193/pydantic_core-2.41.5-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:1d1d9764366c73f996edd17abb6d9d7649a7eb690006ab6adbda117717099b14", size = 1891725, upload-time = "2025-11-04T13:40:58.807Z" }, + { url = "https://files.pythonhosted.org/packages/23/04/e89c29e267b8060b40dca97bfc64a19b2a3cf99018167ea1677d96368273/pydantic_core-2.41.5-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:25e1c2af0fce638d5f1988b686f3b3ea8cd7de5f244ca147c777769e798a9cd1", size = 1915040, upload-time = "2025-11-04T13:41:00.853Z" }, + { url = "https://files.pythonhosted.org/packages/84/a3/15a82ac7bd97992a82257f777b3583d3e84bdb06ba6858f745daa2ec8a85/pydantic_core-2.41.5-cp314-cp314-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:506d766a8727beef16b7adaeb8ee6217c64fc813646b424d0804d67c16eddb66", size = 2063691, upload-time = "2025-11-04T13:41:03.504Z" }, + { url = "https://files.pythonhosted.org/packages/74/9b/0046701313c6ef08c0c1cf0e028c67c770a4e1275ca73131563c5f2a310a/pydantic_core-2.41.5-cp314-cp314-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4819fa52133c9aa3c387b3328f25c1facc356491e6135b459f1de698ff64d869", size = 2213897, upload-time = "2025-11-04T13:41:05.804Z" }, + { url = "https://files.pythonhosted.org/packages/8a/cd/6bac76ecd1b27e75a95ca3a9a559c643b3afcd2dd62086d4b7a32a18b169/pydantic_core-2.41.5-cp314-cp314-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2b761d210c9ea91feda40d25b4efe82a1707da2ef62901466a42492c028553a2", size = 2333302, upload-time = "2025-11-04T13:41:07.809Z" }, + { url = "https://files.pythonhosted.org/packages/4c/d2/ef2074dc020dd6e109611a8be4449b98cd25e1b9b8a303c2f0fca2f2bcf7/pydantic_core-2.41.5-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:22f0fb8c1c583a3b6f24df2470833b40207e907b90c928cc8d3594b76f874375", size = 2064877, upload-time = "2025-11-04T13:41:09.827Z" }, + { url = "https://files.pythonhosted.org/packages/18/66/e9db17a9a763d72f03de903883c057b2592c09509ccfe468187f2a2eef29/pydantic_core-2.41.5-cp314-cp314-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:2782c870e99878c634505236d81e5443092fba820f0373997ff75f90f68cd553", size = 2180680, upload-time = "2025-11-04T13:41:12.379Z" }, + { url = "https://files.pythonhosted.org/packages/d3/9e/3ce66cebb929f3ced22be85d4c2399b8e85b622db77dad36b73c5387f8f8/pydantic_core-2.41.5-cp314-cp314-musllinux_1_1_aarch64.whl", hash = "sha256:0177272f88ab8312479336e1d777f6b124537d47f2123f89cb37e0accea97f90", size = 2138960, upload-time = "2025-11-04T13:41:14.627Z" }, + { url = "https://files.pythonhosted.org/packages/a6/62/205a998f4327d2079326b01abee48e502ea739d174f0a89295c481a2272e/pydantic_core-2.41.5-cp314-cp314-musllinux_1_1_armv7l.whl", hash = "sha256:63510af5e38f8955b8ee5687740d6ebf7c2a0886d15a6d65c32814613681bc07", size = 2339102, upload-time = "2025-11-04T13:41:16.868Z" }, + { url = "https://files.pythonhosted.org/packages/3c/0d/f05e79471e889d74d3d88f5bd20d0ed189ad94c2423d81ff8d0000aab4ff/pydantic_core-2.41.5-cp314-cp314-musllinux_1_1_x86_64.whl", hash = "sha256:e56ba91f47764cc14f1daacd723e3e82d1a89d783f0f5afe9c364b8bb491ccdb", size = 2326039, upload-time = "2025-11-04T13:41:18.934Z" }, + { url = "https://files.pythonhosted.org/packages/ec/e1/e08a6208bb100da7e0c4b288eed624a703f4d129bde2da475721a80cab32/pydantic_core-2.41.5-cp314-cp314-win32.whl", hash = "sha256:aec5cf2fd867b4ff45b9959f8b20ea3993fc93e63c7363fe6851424c8a7e7c23", size = 1995126, upload-time = "2025-11-04T13:41:21.418Z" }, + { url = "https://files.pythonhosted.org/packages/48/5d/56ba7b24e9557f99c9237e29f5c09913c81eeb2f3217e40e922353668092/pydantic_core-2.41.5-cp314-cp314-win_amd64.whl", hash = "sha256:8e7c86f27c585ef37c35e56a96363ab8de4e549a95512445b85c96d3e2f7c1bf", size = 2015489, upload-time = "2025-11-04T13:41:24.076Z" }, + { url = "https://files.pythonhosted.org/packages/4e/bb/f7a190991ec9e3e0ba22e4993d8755bbc4a32925c0b5b42775c03e8148f9/pydantic_core-2.41.5-cp314-cp314-win_arm64.whl", hash = "sha256:e672ba74fbc2dc8eea59fb6d4aed6845e6905fc2a8afe93175d94a83ba2a01a0", size = 1977288, upload-time = "2025-11-04T13:41:26.33Z" }, + { url = "https://files.pythonhosted.org/packages/92/ed/77542d0c51538e32e15afe7899d79efce4b81eee631d99850edc2f5e9349/pydantic_core-2.41.5-cp314-cp314t-macosx_10_12_x86_64.whl", hash = "sha256:8566def80554c3faa0e65ac30ab0932b9e3a5cd7f8323764303d468e5c37595a", size = 2120255, upload-time = "2025-11-04T13:41:28.569Z" }, + { url = "https://files.pythonhosted.org/packages/bb/3d/6913dde84d5be21e284439676168b28d8bbba5600d838b9dca99de0fad71/pydantic_core-2.41.5-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:b80aa5095cd3109962a298ce14110ae16b8c1aece8b72f9dafe81cf597ad80b3", size = 1863760, upload-time = "2025-11-04T13:41:31.055Z" }, + { url = "https://files.pythonhosted.org/packages/5a/f0/e5e6b99d4191da102f2b0eb9687aaa7f5bea5d9964071a84effc3e40f997/pydantic_core-2.41.5-cp314-cp314t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3006c3dd9ba34b0c094c544c6006cc79e87d8612999f1a5d43b769b89181f23c", size = 1878092, upload-time = "2025-11-04T13:41:33.21Z" }, + { url = "https://files.pythonhosted.org/packages/71/48/36fb760642d568925953bcc8116455513d6e34c4beaa37544118c36aba6d/pydantic_core-2.41.5-cp314-cp314t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:72f6c8b11857a856bcfa48c86f5368439f74453563f951e473514579d44aa612", size = 2053385, upload-time = "2025-11-04T13:41:35.508Z" }, + { url = "https://files.pythonhosted.org/packages/20/25/92dc684dd8eb75a234bc1c764b4210cf2646479d54b47bf46061657292a8/pydantic_core-2.41.5-cp314-cp314t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5cb1b2f9742240e4bb26b652a5aeb840aa4b417c7748b6f8387927bc6e45e40d", size = 2218832, upload-time = "2025-11-04T13:41:37.732Z" }, + { url = "https://files.pythonhosted.org/packages/e2/09/f53e0b05023d3e30357d82eb35835d0f6340ca344720a4599cd663dca599/pydantic_core-2.41.5-cp314-cp314t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:bd3d54f38609ff308209bd43acea66061494157703364ae40c951f83ba99a1a9", size = 2327585, upload-time = "2025-11-04T13:41:40Z" }, + { url = "https://files.pythonhosted.org/packages/aa/4e/2ae1aa85d6af35a39b236b1b1641de73f5a6ac4d5a7509f77b814885760c/pydantic_core-2.41.5-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2ff4321e56e879ee8d2a879501c8e469414d948f4aba74a2d4593184eb326660", size = 2041078, upload-time = "2025-11-04T13:41:42.323Z" }, + { url = "https://files.pythonhosted.org/packages/cd/13/2e215f17f0ef326fc72afe94776edb77525142c693767fc347ed6288728d/pydantic_core-2.41.5-cp314-cp314t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:d0d2568a8c11bf8225044aa94409e21da0cb09dcdafe9ecd10250b2baad531a9", size = 2173914, upload-time = "2025-11-04T13:41:45.221Z" }, + { url = "https://files.pythonhosted.org/packages/02/7a/f999a6dcbcd0e5660bc348a3991c8915ce6599f4f2c6ac22f01d7a10816c/pydantic_core-2.41.5-cp314-cp314t-musllinux_1_1_aarch64.whl", hash = "sha256:a39455728aabd58ceabb03c90e12f71fd30fa69615760a075b9fec596456ccc3", size = 2129560, upload-time = "2025-11-04T13:41:47.474Z" }, + { url = "https://files.pythonhosted.org/packages/3a/b1/6c990ac65e3b4c079a4fb9f5b05f5b013afa0f4ed6780a3dd236d2cbdc64/pydantic_core-2.41.5-cp314-cp314t-musllinux_1_1_armv7l.whl", hash = "sha256:239edca560d05757817c13dc17c50766136d21f7cd0fac50295499ae24f90fdf", size = 2329244, upload-time = "2025-11-04T13:41:49.992Z" }, + { url = "https://files.pythonhosted.org/packages/d9/02/3c562f3a51afd4d88fff8dffb1771b30cfdfd79befd9883ee094f5b6c0d8/pydantic_core-2.41.5-cp314-cp314t-musllinux_1_1_x86_64.whl", hash = "sha256:2a5e06546e19f24c6a96a129142a75cee553cc018ffee48a460059b1185f4470", size = 2331955, upload-time = "2025-11-04T13:41:54.079Z" }, + { url = "https://files.pythonhosted.org/packages/5c/96/5fb7d8c3c17bc8c62fdb031c47d77a1af698f1d7a406b0f79aaa1338f9ad/pydantic_core-2.41.5-cp314-cp314t-win32.whl", hash = "sha256:b4ececa40ac28afa90871c2cc2b9ffd2ff0bf749380fbdf57d165fd23da353aa", size = 1988906, upload-time = "2025-11-04T13:41:56.606Z" }, + { url = "https://files.pythonhosted.org/packages/22/ed/182129d83032702912c2e2d8bbe33c036f342cc735737064668585dac28f/pydantic_core-2.41.5-cp314-cp314t-win_amd64.whl", hash = "sha256:80aa89cad80b32a912a65332f64a4450ed00966111b6615ca6816153d3585a8c", size = 1981607, upload-time = "2025-11-04T13:41:58.889Z" }, + { url = "https://files.pythonhosted.org/packages/9f/ed/068e41660b832bb0b1aa5b58011dea2a3fe0ba7861ff38c4d4904c1c1a99/pydantic_core-2.41.5-cp314-cp314t-win_arm64.whl", hash = "sha256:35b44f37a3199f771c3eaa53051bc8a70cd7b54f333531c59e29fd4db5d15008", size = 1974769, upload-time = "2025-11-04T13:42:01.186Z" }, + { url = "https://files.pythonhosted.org/packages/54/db/160dffb57ed9a3705c4cbcbff0ac03bdae45f1ca7d58ab74645550df3fbd/pydantic_core-2.41.5-cp39-cp39-macosx_10_12_x86_64.whl", hash = "sha256:8bfeaf8735be79f225f3fefab7f941c712aaca36f1128c9d7e2352ee1aa87bdf", size = 2107999, upload-time = "2025-11-04T13:42:03.885Z" }, + { url = "https://files.pythonhosted.org/packages/a3/7d/88e7de946f60d9263cc84819f32513520b85c0f8322f9b8f6e4afc938383/pydantic_core-2.41.5-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:346285d28e4c8017da95144c7f3acd42740d637ff41946af5ce6e5e420502dd5", size = 1929745, upload-time = "2025-11-04T13:42:06.075Z" }, + { url = "https://files.pythonhosted.org/packages/d5/c2/aef51e5b283780e85e99ff19db0f05842d2d4a8a8cd15e63b0280029b08f/pydantic_core-2.41.5-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a75dafbf87d6276ddc5b2bf6fae5254e3d0876b626eb24969a574fff9149ee5d", size = 1920220, upload-time = "2025-11-04T13:42:08.457Z" }, + { url = "https://files.pythonhosted.org/packages/c7/97/492ab10f9ac8695cd76b2fdb24e9e61f394051df71594e9bcc891c9f586e/pydantic_core-2.41.5-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:7b93a4d08587e2b7e7882de461e82b6ed76d9026ce91ca7915e740ecc7855f60", size = 2067296, upload-time = "2025-11-04T13:42:10.817Z" }, + { url = "https://files.pythonhosted.org/packages/ec/23/984149650e5269c59a2a4c41d234a9570adc68ab29981825cfaf4cfad8f4/pydantic_core-2.41.5-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e8465ab91a4bd96d36dde3263f06caa6a8a6019e4113f24dc753d79a8b3a3f82", size = 2231548, upload-time = "2025-11-04T13:42:13.843Z" }, + { url = "https://files.pythonhosted.org/packages/71/0c/85bcbb885b9732c28bec67a222dbed5ed2d77baee1f8bba2002e8cd00c5c/pydantic_core-2.41.5-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:299e0a22e7ae2b85c1a57f104538b2656e8ab1873511fd718a1c1c6f149b77b5", size = 2362571, upload-time = "2025-11-04T13:42:16.208Z" }, + { url = "https://files.pythonhosted.org/packages/c0/4a/412d2048be12c334003e9b823a3fa3d038e46cc2d64dd8aab50b31b65499/pydantic_core-2.41.5-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:707625ef0983fcfb461acfaf14de2067c5942c6bb0f3b4c99158bed6fedd3cf3", size = 2068175, upload-time = "2025-11-04T13:42:18.911Z" }, + { url = "https://files.pythonhosted.org/packages/73/f4/c58b6a776b502d0a5540ad02e232514285513572060f0d78f7832ca3c98b/pydantic_core-2.41.5-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:f41eb9797986d6ebac5e8edff36d5cef9de40def462311b3eb3eeded1431e425", size = 2177203, upload-time = "2025-11-04T13:42:22.578Z" }, + { url = "https://files.pythonhosted.org/packages/ed/ae/f06ea4c7e7a9eead3d165e7623cd2ea0cb788e277e4f935af63fc98fa4e6/pydantic_core-2.41.5-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:0384e2e1021894b1ff5a786dbf94771e2986ebe2869533874d7e43bc79c6f504", size = 2148191, upload-time = "2025-11-04T13:42:24.89Z" }, + { url = "https://files.pythonhosted.org/packages/c1/57/25a11dcdc656bf5f8b05902c3c2934ac3ea296257cc4a3f79a6319e61856/pydantic_core-2.41.5-cp39-cp39-musllinux_1_1_armv7l.whl", hash = "sha256:f0cd744688278965817fd0839c4a4116add48d23890d468bc436f78beb28abf5", size = 2343907, upload-time = "2025-11-04T13:42:27.683Z" }, + { url = "https://files.pythonhosted.org/packages/96/82/e33d5f4933d7a03327c0c43c65d575e5919d4974ffc026bc917a5f7b9f61/pydantic_core-2.41.5-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:753e230374206729bf0a807954bcc6c150d3743928a73faffee51ac6557a03c3", size = 2322174, upload-time = "2025-11-04T13:42:30.776Z" }, + { url = "https://files.pythonhosted.org/packages/81/45/4091be67ce9f469e81656f880f3506f6a5624121ec5eb3eab37d7581897d/pydantic_core-2.41.5-cp39-cp39-win32.whl", hash = "sha256:873e0d5b4fb9b89ef7c2d2a963ea7d02879d9da0da8d9d4933dee8ee86a8b460", size = 1990353, upload-time = "2025-11-04T13:42:33.111Z" }, + { url = "https://files.pythonhosted.org/packages/44/8a/a98aede18db6e9cd5d66bcacd8a409fcf8134204cdede2e7de35c5a2c5ef/pydantic_core-2.41.5-cp39-cp39-win_amd64.whl", hash = "sha256:e4f4a984405e91527a0d62649ee21138f8e3d0ef103be488c1dc11a80d7f184b", size = 2015698, upload-time = "2025-11-04T13:42:35.484Z" }, + { url = "https://files.pythonhosted.org/packages/11/72/90fda5ee3b97e51c494938a4a44c3a35a9c96c19bba12372fb9c634d6f57/pydantic_core-2.41.5-graalpy311-graalpy242_311_native-macosx_10_12_x86_64.whl", hash = "sha256:b96d5f26b05d03cc60f11a7761a5ded1741da411e7fe0909e27a5e6a0cb7b034", size = 2115441, upload-time = "2025-11-04T13:42:39.557Z" }, + { url = "https://files.pythonhosted.org/packages/1f/53/8942f884fa33f50794f119012dc6a1a02ac43a56407adaac20463df8e98f/pydantic_core-2.41.5-graalpy311-graalpy242_311_native-macosx_11_0_arm64.whl", hash = "sha256:634e8609e89ceecea15e2d61bc9ac3718caaaa71963717bf3c8f38bfde64242c", size = 1930291, upload-time = "2025-11-04T13:42:42.169Z" }, + { url = "https://files.pythonhosted.org/packages/79/c8/ecb9ed9cd942bce09fc888ee960b52654fbdbede4ba6c2d6e0d3b1d8b49c/pydantic_core-2.41.5-graalpy311-graalpy242_311_native-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:93e8740d7503eb008aa2df04d3b9735f845d43ae845e6dcd2be0b55a2da43cd2", size = 1948632, upload-time = "2025-11-04T13:42:44.564Z" }, + { url = "https://files.pythonhosted.org/packages/2e/1b/687711069de7efa6af934e74f601e2a4307365e8fdc404703afc453eab26/pydantic_core-2.41.5-graalpy311-graalpy242_311_native-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f15489ba13d61f670dcc96772e733aad1a6f9c429cc27574c6cdaed82d0146ad", size = 2138905, upload-time = "2025-11-04T13:42:47.156Z" }, + { url = "https://files.pythonhosted.org/packages/09/32/59b0c7e63e277fa7911c2fc70ccfb45ce4b98991e7ef37110663437005af/pydantic_core-2.41.5-graalpy312-graalpy250_312_native-macosx_10_12_x86_64.whl", hash = "sha256:7da7087d756b19037bc2c06edc6c170eeef3c3bafcb8f532ff17d64dc427adfd", size = 2110495, upload-time = "2025-11-04T13:42:49.689Z" }, + { url = "https://files.pythonhosted.org/packages/aa/81/05e400037eaf55ad400bcd318c05bb345b57e708887f07ddb2d20e3f0e98/pydantic_core-2.41.5-graalpy312-graalpy250_312_native-macosx_11_0_arm64.whl", hash = "sha256:aabf5777b5c8ca26f7824cb4a120a740c9588ed58df9b2d196ce92fba42ff8dc", size = 1915388, upload-time = "2025-11-04T13:42:52.215Z" }, + { url = "https://files.pythonhosted.org/packages/6e/0d/e3549b2399f71d56476b77dbf3cf8937cec5cd70536bdc0e374a421d0599/pydantic_core-2.41.5-graalpy312-graalpy250_312_native-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c007fe8a43d43b3969e8469004e9845944f1a80e6acd47c150856bb87f230c56", size = 1942879, upload-time = "2025-11-04T13:42:56.483Z" }, + { url = "https://files.pythonhosted.org/packages/f7/07/34573da085946b6a313d7c42f82f16e8920bfd730665de2d11c0c37a74b5/pydantic_core-2.41.5-graalpy312-graalpy250_312_native-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:76d0819de158cd855d1cbb8fcafdf6f5cf1eb8e470abe056d5d161106e38062b", size = 2139017, upload-time = "2025-11-04T13:42:59.471Z" }, + { url = "https://files.pythonhosted.org/packages/e6/b0/1a2aa41e3b5a4ba11420aba2d091b2d17959c8d1519ece3627c371951e73/pydantic_core-2.41.5-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:b5819cd790dbf0c5eb9f82c73c16b39a65dd6dd4d1439dcdea7816ec9adddab8", size = 2103351, upload-time = "2025-11-04T13:43:02.058Z" }, + { url = "https://files.pythonhosted.org/packages/a4/ee/31b1f0020baaf6d091c87900ae05c6aeae101fa4e188e1613c80e4f1ea31/pydantic_core-2.41.5-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:5a4e67afbc95fa5c34cf27d9089bca7fcab4e51e57278d710320a70b956d1b9a", size = 1925363, upload-time = "2025-11-04T13:43:05.159Z" }, + { url = "https://files.pythonhosted.org/packages/e1/89/ab8e86208467e467a80deaca4e434adac37b10a9d134cd2f99b28a01e483/pydantic_core-2.41.5-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ece5c59f0ce7d001e017643d8d24da587ea1f74f6993467d85ae8a5ef9d4f42b", size = 2135615, upload-time = "2025-11-04T13:43:08.116Z" }, + { url = "https://files.pythonhosted.org/packages/99/0a/99a53d06dd0348b2008f2f30884b34719c323f16c3be4e6cc1203b74a91d/pydantic_core-2.41.5-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:16f80f7abe3351f8ea6858914ddc8c77e02578544a0ebc15b4c2e1a0e813b0b2", size = 2175369, upload-time = "2025-11-04T13:43:12.49Z" }, + { url = "https://files.pythonhosted.org/packages/6d/94/30ca3b73c6d485b9bb0bc66e611cff4a7138ff9736b7e66bcf0852151636/pydantic_core-2.41.5-pp310-pypy310_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:33cb885e759a705b426baada1fe68cbb0a2e68e34c5d0d0289a364cf01709093", size = 2144218, upload-time = "2025-11-04T13:43:15.431Z" }, + { url = "https://files.pythonhosted.org/packages/87/57/31b4f8e12680b739a91f472b5671294236b82586889ef764b5fbc6669238/pydantic_core-2.41.5-pp310-pypy310_pp73-musllinux_1_1_armv7l.whl", hash = "sha256:c8d8b4eb992936023be7dee581270af5c6e0697a8559895f527f5b7105ecd36a", size = 2329951, upload-time = "2025-11-04T13:43:18.062Z" }, + { url = "https://files.pythonhosted.org/packages/7d/73/3c2c8edef77b8f7310e6fb012dbc4b8551386ed575b9eb6fb2506e28a7eb/pydantic_core-2.41.5-pp310-pypy310_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:242a206cd0318f95cd21bdacff3fcc3aab23e79bba5cac3db5a841c9ef9c6963", size = 2318428, upload-time = "2025-11-04T13:43:20.679Z" }, + { url = "https://files.pythonhosted.org/packages/2f/02/8559b1f26ee0d502c74f9cca5c0d2fd97e967e083e006bbbb4e97f3a043a/pydantic_core-2.41.5-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:d3a978c4f57a597908b7e697229d996d77a6d3c94901e9edee593adada95ce1a", size = 2147009, upload-time = "2025-11-04T13:43:23.286Z" }, + { url = "https://files.pythonhosted.org/packages/5f/9b/1b3f0e9f9305839d7e84912f9e8bfbd191ed1b1ef48083609f0dabde978c/pydantic_core-2.41.5-pp311-pypy311_pp73-macosx_10_12_x86_64.whl", hash = "sha256:b2379fa7ed44ddecb5bfe4e48577d752db9fc10be00a6b7446e9663ba143de26", size = 2101980, upload-time = "2025-11-04T13:43:25.97Z" }, + { url = "https://files.pythonhosted.org/packages/a4/ed/d71fefcb4263df0da6a85b5d8a7508360f2f2e9b3bf5814be9c8bccdccc1/pydantic_core-2.41.5-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:266fb4cbf5e3cbd0b53669a6d1b039c45e3ce651fd5442eff4d07c2cc8d66808", size = 1923865, upload-time = "2025-11-04T13:43:28.763Z" }, + { url = "https://files.pythonhosted.org/packages/ce/3a/626b38db460d675f873e4444b4bb030453bbe7b4ba55df821d026a0493c4/pydantic_core-2.41.5-pp311-pypy311_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:58133647260ea01e4d0500089a8c4f07bd7aa6ce109682b1426394988d8aaacc", size = 2134256, upload-time = "2025-11-04T13:43:31.71Z" }, + { url = "https://files.pythonhosted.org/packages/83/d9/8412d7f06f616bbc053d30cb4e5f76786af3221462ad5eee1f202021eb4e/pydantic_core-2.41.5-pp311-pypy311_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:287dad91cfb551c363dc62899a80e9e14da1f0e2b6ebde82c806612ca2a13ef1", size = 2174762, upload-time = "2025-11-04T13:43:34.744Z" }, + { url = "https://files.pythonhosted.org/packages/55/4c/162d906b8e3ba3a99354e20faa1b49a85206c47de97a639510a0e673f5da/pydantic_core-2.41.5-pp311-pypy311_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:03b77d184b9eb40240ae9fd676ca364ce1085f203e1b1256f8ab9984dca80a84", size = 2143141, upload-time = "2025-11-04T13:43:37.701Z" }, + { url = "https://files.pythonhosted.org/packages/1f/f2/f11dd73284122713f5f89fc940f370d035fa8e1e078d446b3313955157fe/pydantic_core-2.41.5-pp311-pypy311_pp73-musllinux_1_1_armv7l.whl", hash = "sha256:a668ce24de96165bb239160b3d854943128f4334822900534f2fe947930e5770", size = 2330317, upload-time = "2025-11-04T13:43:40.406Z" }, + { url = "https://files.pythonhosted.org/packages/88/9d/b06ca6acfe4abb296110fb1273a4d848a0bfb2ff65f3ee92127b3244e16b/pydantic_core-2.41.5-pp311-pypy311_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:f14f8f046c14563f8eb3f45f499cc658ab8d10072961e07225e507adb700e93f", size = 2316992, upload-time = "2025-11-04T13:43:43.602Z" }, + { url = "https://files.pythonhosted.org/packages/36/c7/cfc8e811f061c841d7990b0201912c3556bfeb99cdcb7ed24adc8d6f8704/pydantic_core-2.41.5-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:56121965f7a4dc965bff783d70b907ddf3d57f6eba29b6d2e5dabfaf07799c51", size = 2145302, upload-time = "2025-11-04T13:43:46.64Z" }, +] + [[package]] name = "pyee" version = "12.1.1" @@ -1705,6 +1862,18 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/18/67/36e9267722cc04a6b9f15c7f3441c2363321a3ea07da7ae0c0707beb2a9c/typing_extensions-4.15.0-py3-none-any.whl", hash = "sha256:f0fa19c6845758ab08074a0cfa8b7aecb71c999ca73d62883bc25cc018c4e548", size = 44614, upload-time = "2025-08-25T13:49:24.86Z" }, ] +[[package]] +name = "typing-inspection" +version = "0.4.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/55/e3/70399cb7dd41c10ac53367ae42139cf4b1ca5f36bb3dc6c9d33acdb43655/typing_inspection-0.4.2.tar.gz", hash = "sha256:ba561c48a67c5958007083d386c3295464928b01faa735ab8547c5692e87f464", size = 75949, upload-time = "2025-10-01T02:14:41.687Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/dc/9b/47798a6c91d8bdb567fe2698fe81e0c6b7cb7ef4d13da4114b41d239f65d/typing_inspection-0.4.2-py3-none-any.whl", hash = "sha256:4ed1cacbdc298c220f1bd249ed5287caa16f34d44ef4e9c3d0cbad5b521545e7", size = 14611, upload-time = "2025-10-01T02:14:40.154Z" }, +] + [[package]] name = "urllib3" version = "2.6.3" From 81b4bce3c9029eb6026cd3e034922f3d5256b8b1 Mon Sep 17 00:00:00 2001 From: miro Date: Wed, 18 Mar 2026 15:57:38 +0000 Subject: [PATCH 13/13] docs: update FAQ and MAINTENANCE_REPORT for PR #340 fixes - Added 3 new FAQ entries: Agent Tools API, dynamic discovery, JSON serialization - Added comprehensive MAINTENANCE_REPORT entry documenting all 6 fixes: * pyproject.toml dependency fix * Test placeholder removal * Documentation accuracy fix * Cache staleness prevention * JSON serialization consistency * Security fixes for error messages - Included AI transparency (model, actions, oversight notes) - Linked to source file locations for future reference AI-Generated Change: - Model: Claude Haiku 4.5 - Intent: Document PR #340 fixes and new agent tools features for future maintainers - Impact: Improved discoverability and traceability of changes - Verified via: Manual verification of FAQ clarity and MAINTENANCE_REPORT completeness --- FAQ.md | 9 +++++++++ MAINTENANCE_REPORT.md | 25 +++++++++++++++++++++++++ 2 files changed, 34 insertions(+) diff --git a/FAQ.md b/FAQ.md index 596b3da0..77c2cb89 100644 --- a/FAQ.md +++ b/FAQ.md @@ -42,3 +42,12 @@ uv run pytest ovos-plugin-manager/test/ --cov=ovos_plugin_manager ## What Python versions are supported? See `QUICK_FACTS.md` — currently `>=3.9`. + +## What are Agent Tools and ToolBox plugins? +Agent Tools are executable functions exposed to AI agents via the OVOS messagebus. A `ToolBox` plugin groups related tools and handles discovery/execution. Entry point: `opm.agents.toolbox` — see `docs/index.md` and `ovos_plugin_manager/templates/agent_tools.py` for full API. + +## How do ToolBox plugins handle dynamic tool discovery? +`ToolBox.refresh_tools()` is called on every discovery broadcast (via `handle_discover`) and on cache misses (via `get_tool`). This ensures tools added dynamically (e.g., from MCP/UTCP plugins) are always discoverable without client retries — see `ovos_plugin_manager/templates/agent_tools.py:112`. + +## What serialization mode does ToolBox use for bus responses? +`ToolBox.handle_call()` uses `model_dump(mode='json')` to serialize Pydantic models for bus transmission. This ensures consistency with the JSON schema advertised in `tool_json_list` and handles datetime, UUID, enum, and aliased fields correctly — see `ovos_plugin_manager/templates/agent_tools.py:145`. diff --git a/MAINTENANCE_REPORT.md b/MAINTENANCE_REPORT.md index 84a2762a..18cbd5b1 100644 --- a/MAINTENANCE_REPORT.md +++ b/MAINTENANCE_REPORT.md @@ -115,3 +115,28 @@ Address critical and major issues identified during CodeRabbit review of PR #376 - **AI Model**: Gemini 2.0 Pro - **Actions Taken**: Triage 33 CodeRabbit comments, applied fixes for 10+ high-priority items covering CI, installation logic, templates, and documentation. - **Oversight**: Human review of logic changes in `pip_install` and `release_workflow.yml` recommended. + +## [2026-03-18] — Address CodeRabbit PR #340 Review (tool plugins) + +### Changes +- **`pyproject.toml`**: Added `pydantic~=2.0` to `[project.optional-dependencies] test` list. GitHub Actions installs with extras from pyproject.toml, not requirements.txt; missing pydantic caused pytest ModuleNotFoundError. +- **`test/unittests/test_agent_tools.py`**: Removed dead placeholder definition `class MathToolBox(MathToolBox if False else object)` (lines 48–49). Was causing Ruff F821 (Undefined name) warning; immediately shadowed by real class definition on line 52. +- **`docs/index.md`**: Fixed entry-point name from `opm.persona.tool` to `opm.agents.toolbox` (line 24). Matches actual ToolBox class entry point in implementation. +- **`ovos_plugin_manager/templates/agent_tools.py`**: + 1. `handle_discover()` (line 123): Added `self.refresh_tools()` call before broadcasting. Prevents stale cache if initial `discover_tools()` fails; ensures dynamic tool discovery works for bus-only clients. + 2. `handle_call()` (line 145): Changed `result.model_dump()` to `result.model_dump(mode='json')`. Ensures JSON serialization consistency with `model_json_schema()` in `tool_json_list`; handles datetime, UUID, enum, aliased fields correctly. + 3. `validate_input()` (line 170): Removed `{tool_kwargs}` from error message. Security fix: prevents echoing raw arguments (which may contain secrets) onto shared messagebus. + 4. `validate_output()` (line 193): Removed `{raw_result}` from error message. Security fix: prevents echoing raw output data (which may contain secrets) onto shared messagebus. + +### Rationale +Address 6 critical CodeRabbit issues on PR #340 (tool plugins feature): cache staleness, JSON serialization divergence, security leaks, test placeholder cleanup, missing dependency, and documentation accuracy. + +### Verification +- All 1011 unit tests pass (including 21 agent_tools tests). +- Python syntax verified via `py_compile`. +- Cache refresh behavior confirmed in test_agent_tools.py via mock assertions. + +### AI Transparency Report +- **AI Model**: Claude Haiku 4.5 +- **Actions Taken**: Fetched PR feedback via gh_pr_comments.py, triaged 6 CodeRabbit issues by severity, applied targeted fixes to 4 files, ran full test suite, verified all tests pass. +- **Oversight**: Security fixes to error messages and JSON serialization require human code review to ensure no loss of debug information needed for troubleshooting.