Skip to content

Conversation

@JarbasAl
Copy link
Member

@JarbasAl JarbasAl commented Oct 17, 2025

docs: OpenVoiceOS/ovos-technical-manual#38

Summary by CodeRabbit

  • New Features

    • Adds a toolbox framework to expose and manage agent tools over the message bus with validated inputs/outputs and JSON-Schema exposure.
  • Public API

    • Adds toolbox discovery and registration surface, stronger typing for persona listings, and a new persona-tool plugin identifier.
  • Dependencies

    • Adds pydantic for validation and JSON Schema generation.

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Oct 17, 2025

Walkthrough

Adds a ToolBox framework exposing agent tools over a message bus (Pydantic input/output models, AgentTool dataclass, type aliases), an abstract ToolBox with discovery/call handlers and validation, a toolbox discovery helper, a new PluginTypes member, and adds pydantic~=2.0 to requirements.

Changes

Cohort / File(s) Summary
New ToolBox framework
ovos_plugin_manager/templates/agent_tools.py
New module introducing ToolArguments and ToolOutput (Pydantic BaseModel), AgentTool dataclass, type aliases (ToolCallFunc, ToolCallReturn), and abstract ToolBox with lifecycle, discover_tools (abstract), bus binding, discovery/call handlers, call_tool/get_tool, static validate_input/validate_output, and tool_json_list (JSON Schema exposure).
Persona & toolbox discovery
ovos_plugin_manager/persona.py
Adds ToolBox import, changes find_persona_plugins() return type to Dict[str, Dict[str, Any]], and adds find_toolbox_plugins() -> Dict[str, Type[ToolBox]] for toolbox entrypoint discovery.
PluginTypes enum update
ovos_plugin_manager/utils/__init__.py
Adds enum member PERSONA_TOOL = "opm.persona.tool" to PluginTypes.
Dependency update
requirements/requirements.txt
Adds pydantic~=2.0 to project dependencies.

Sequence Diagram(s)

sequenceDiagram
    participant Caller
    participant Bus
    participant ToolBox
    participant Discover as discover_tools()

    Caller->>ToolBox: instantiate(toolbox_id, bus?)
    ToolBox->>Discover: discover_tools()
    Discover-->>ToolBox: List[AgentTool]
    ToolBox->>ToolBox: cache tools

    alt bus provided
        ToolBox->>Bus: bind(discovery_channel, call_channel)
    end

    Note over Caller,Bus: Discovery flow
    Caller->>Bus: publish(discovery_channel)
    Bus->>ToolBox: handle_discover(msg)
    ToolBox->>ToolBox: tool_json_list
    ToolBox-->>Bus: emit(toolbox_id, tool_schemas)
    Bus-->>Caller: tool list

    Note over Caller,Bus: Call flow
    Caller->>Bus: publish(call_channel, tool_name, kwargs)
    Bus->>ToolBox: handle_call(msg)
    ToolBox->>ToolBox: call_tool(name, kwargs)
    alt tool exists & success
        ToolBox-->>Bus: emit(result)
    else not found / error
        ToolBox-->>Bus: emit(error)
    end
    Bus-->>Caller: response
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Poem

🐇 I found new tools beneath the code-lined hill,
I pried up schemas, tidy, bright, and still,
I bound them to the bus with a gentle hop,
Now calls come running and the outputs pop,
A rabbit’s toolbox hums and keeps me on the hop.

Pre-merge checks and finishing touches

✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title Check ✅ Passed The title "feat: tool plugins" clearly and directly summarizes the main objective of this pull request: introducing support for tool plugins through a new ToolBox framework. The title is concise, follows conventional commit format, and accurately captures the primary change across all modified files, including the new ToolBox abstract base class, plugin discovery mechanism, and PERSONA_TOOL plugin type. A developer scanning the commit history would understand that this PR adds a new plugin category for managing and exposing agent tools. The title is neither overly broad nor misleadingly vague—it appropriately reflects the scope of the changeset.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch tool_plugins

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@github-actions github-actions bot added feature and removed feature labels Oct 17, 2025
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🧹 Nitpick comments (1)
ovos_plugin_manager/templates/agent_tools.py (1)

158-172: Consider logging lazy tool discovery for observability.

The lazy loading pattern is sound, but operators may benefit from knowing when tool discovery is deferred from initialization to first usage.

Apply this diff to add optional debug logging:

+from ovos_utils.log import LOG
+
 def get_tool(self, name: str) -> Optional[AgentTool]:
     """..."""
     if name not in self.tools:
+        LOG.debug(f"Tool '{name}' not in cache, refreshing tools for ToolBox '{self.toolbox_id}'")
         self.refresh_tools()
     return self.tools.get(name)
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 91d7205 and e93c405.

📒 Files selected for processing (2)
  • ovos_plugin_manager/templates/agent_tools.py (1 hunks)
  • requirements/requirements.txt (1 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
ovos_plugin_manager/templates/agent_tools.py (1)
ovos_plugin_manager/templates/phal.py (1)
  • emit (138-141)
🪛 Ruff (0.14.0)
ovos_plugin_manager/templates/agent_tools.py

61-62: try-except-pass detected, consider logging the exception

(S110)


61-61: Do not catch blind exception: Exception

(BLE001)


61-61: Local variable e is assigned to but never used

Remove assignment to unused variable e

(F841)


124-124: Do not catch blind exception: Exception

(BLE001)


126-126: Use explicit conversion flag

Replace with conversion flag

(RUF010)


154-154: Avoid specifying long messages outside the exception class

(TRY003)


156-156: Avoid specifying long messages outside the exception class

(TRY003)

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (3)
  • GitHub Check: unit_tests (3.10)
  • GitHub Check: unit_tests (3.11)
  • GitHub Check: build_tests (3.10)
🔇 Additional comments (9)
ovos_plugin_manager/templates/agent_tools.py (9)

10-19: LGTM!

The empty base model pattern is appropriate for framework extension points, allowing plugin developers to define their own tool argument and output schemas.


22-34: LGTM!

The dataclass structure cleanly separates declarative metadata (schemas, description) from executable logic (tool_call). The flexible callable signature appropriately accommodates diverse tool implementations.


83-89: LGTM!

The tool cache refresh logic is clean and idempotent, appropriately delegating to the abstract discover_tools() method.


91-105: LGTM!

The discovery handler correctly returns tool schemas as JSON, making tools discoverable over the message bus. The property access for tool_json_list is appropriate.


107-127: LGTM!

The message bus handler correctly delegates to call_tool() and emits both success and error responses. The broad exception catch is appropriate for a message bus integration point, ensuring the bus remains stable even if tool execution fails.


129-156: LGTM!

The direct execution interface provides clear error handling with proper exception chaining. The descriptive error messages aid debugging by including tool name and toolbox context.


174-194: LGTM!

The JSON schema generation correctly leverages Pydantic's model_json_schema() to produce LLM-compatible tool definitions. The output format is well-structured for both message bus transmission and LLM API consumption.


197-208: LGTM!

The abstract method contract is clear and well-documented. The idempotency requirement appropriately supports the refresh/lazy-loading design.


68-81: ---

No issues identified with message bus event naming.

The ovos.persona.tools.* patterns align with existing OVOS conventions found throughout the codebase. Similar hierarchical dot-notation patterns are used consistently in other components (e.g., mycroft.gui.screen.close, enclosure.system.reset), confirming the naming convention is appropriate for the plugin manager integration.

Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
@github-actions github-actions bot added feature and removed feature labels Oct 17, 2025
@github-actions github-actions bot added feature and removed feature labels Oct 17, 2025
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

🧹 Nitpick comments (1)
ovos_plugin_manager/persona.py (1)

16-22: Consider clarifying the docstring terminology.

The implementation is correct and follows the established pattern. However, the docstring mentions "toolbox_id to entrypoints (ToolBox)" which could be clearer. The function returns a mapping of plugin names (which serve as toolbox identifiers) to ToolBox class types, not entry point objects.

Consider this docstring refinement:

 def find_toolbox_plugins() -> Dict[str, Type[ToolBox]]:
     """
     Find all installed Toolbox plugins
-    @return: dict toolbox_id to entrypoints (ToolBox)
+    @return: dict mapping plugin names to ToolBox class types
     """
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between ac0aa44 and ff87f15.

📒 Files selected for processing (2)
  • ovos_plugin_manager/persona.py (1 hunks)
  • ovos_plugin_manager/utils/__init__.py (1 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
ovos_plugin_manager/persona.py (2)
ovos_plugin_manager/templates/agent_tools.py (1)
  • ToolBox (37-208)
ovos_plugin_manager/utils/__init__.py (2)
  • PluginTypes (51-95)
  • find_plugins (143-172)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: unit_tests (3.11)
  • GitHub Check: unit_tests (3.10)
🔇 Additional comments (4)
ovos_plugin_manager/utils/__init__.py (1)

95-95: LGTM! Enum addition follows existing patterns.

The new PERSONA_TOOL enum member is correctly placed, follows the naming convention, and uses the appropriate "opm.*" identifier pattern. The absence of a corresponding PluginConfigTypes entry is consistent with the PERSONA type, which also has no config entry.

ovos_plugin_manager/persona.py (3)

1-3: LGTM! Imports are correct and necessary.

The typing imports support the new type annotations, and the ToolBox import aligns with the new toolbox plugin framework introduced in this PR.


7-9: LGTM! Type annotation and docstring improvements enhance code clarity.

The return type annotation accurately reflects that persona plugins are dictionary-based configurations, and the updated docstring ("definitions" vs "plugins") is more precise terminology.


3-3: Pydantic dependency is properly declared.

The verification confirms that pydantic~=2.0 is listed in the project's requirements file, which satisfies the requirement for ToolBox to use Pydantic 2.0+ features like model_json_schema().

@github-actions github-actions bot added feature and removed feature labels Oct 17, 2025
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

♻️ Duplicate comments (1)
ovos_plugin_manager/templates/agent_tools.py (1)

64-67: Add logging to avoid silent initialization failures.

This silent exception swallowing was previously flagged and remains unaddressed. While lazy loading provides a fallback (line 236), silently hiding initialization errors (missing dependencies, import failures, configuration issues) makes debugging significantly harder for operators.

Apply this diff to add logging:

+from ovos_utils.log import LOG
+
 # (in __init__ method)
 try:
     self.discover_tools() # try to find tools immediately
-except Exception as e:
-    pass  # will be lazy loaded or throw error on first usage
+except Exception:
+    LOG.warning(f"Failed to discover tools for ToolBox '{toolbox_id}' during init", exc_info=True)
+    # will be lazy loaded or throw error on first usage

Note: Removed the unused variable e and used exc_info=True to capture the full traceback.

Based on past review comments.

🧹 Nitpick comments (1)
ovos_plugin_manager/templates/agent_tools.py (1)

96-132: LGTM!

The message bus handlers are correctly implemented. The broad exception catching on line 129 is appropriate for a message bus handler—it ensures all errors are captured and returned as error responses rather than crashing the handler.

Minor style note: Line 131 could use explicit str() conversion for clarity:

-        error: str = f"{type(e).__name__}: {str(e)}"
+        error: str = f"{type(e).__name__}: {e!s}"

However, this is purely stylistic and the current code is functionally correct.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between ff87f15 and 561ad2f.

📒 Files selected for processing (1)
  • ovos_plugin_manager/templates/agent_tools.py (1 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
ovos_plugin_manager/templates/agent_tools.py (1)
ovos_plugin_manager/plugin_entry.py (2)
  • name (51-52)
  • description (82-85)
🪛 Ruff (0.14.0)
ovos_plugin_manager/templates/agent_tools.py

66-67: try-except-pass detected, consider logging the exception

(S110)


66-66: Do not catch blind exception: Exception

(BLE001)


66-66: Local variable e is assigned to but never used

Remove assignment to unused variable e

(F841)


129-129: Do not catch blind exception: Exception

(BLE001)


131-131: Use explicit conversion flag

Replace with conversion flag

(RUF010)


154-154: Avoid specifying long messages outside the exception class

(TRY003)


177-177: Avoid specifying long messages outside the exception class

(TRY003)


206-206: Avoid specifying long messages outside the exception class

(TRY003)


213-213: Avoid specifying long messages outside the exception class

(TRY003)


220-220: Avoid specifying long messages outside the exception class

(TRY003)


222-222: Avoid specifying long messages outside the exception class

(TRY003)

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: unit_tests (3.11)
  • GitHub Check: unit_tests (3.10)
🔇 Additional comments (5)
ovos_plugin_manager/templates/agent_tools.py (5)

1-20: LGTM!

The imports and base Pydantic models are well-structured. Using empty base classes for ToolArguments and ToolOutput is a valid pattern that allows concrete toolbox plugins to define their own schemas while maintaining type consistency.


23-40: LGTM!

The ToolCallFunc type alias and AgentTool dataclass are well-designed. The use of field metadata for documentation is excellent, and the type signature is used consistently throughout the codebase (e.g., line 210).


73-94: LGTM!

The message bus binding and tool refresh logic is correctly implemented. The lack of exception handling in refresh_tools() is appropriate since it's called on-demand and errors should propagate to the caller.


134-177: LGTM!

Both validation methods correctly use Pydantic v2 APIs (model_validate() on line 175) and provide informative error messages that include the tool name and problematic data for debugging.


180-275: LGTM!

The tool execution orchestration is well-designed with clear separation of concerns:

  • Input validation failures raise ValueError (line 206)
  • Execution failures raise RuntimeError (line 213)
  • Output validation failures raise RuntimeError (line 220)

This error categorization helps callers distinguish between validation issues (caller's fault) and runtime issues (tool implementation's fault). The lazy loading in get_tool() (lines 236-237) and the use of Pydantic v2's model_json_schema() (lines 257-258) are both correct.

@github-actions github-actions bot added feature and removed feature labels Oct 17, 2025
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

♻️ Duplicate comments (1)
ovos_plugin_manager/templates/agent_tools.py (1)

66-69: Address the exception logging as noted in previous review.

This silent exception swallowing was already flagged. Please implement the suggested logging to aid debugging of initialization failures.

🧹 Nitpick comments (1)
ovos_plugin_manager/templates/agent_tools.py (1)

181-240: LGTM! Comprehensive tool execution with thorough validation.

The execution orchestration is well-structured with clear separation of concerns: input validation, execution, and output validation. The error handling provides good context at each stage.

Optional refinement: Lines 223-226 and 237-240 could use TypeError instead of RuntimeError for type mismatches, as it more precisely indicates the nature of the error.

Optional diff for semantic precision:
         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(
+                raise TypeError(
                     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:
                 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:
             # Case C: Tool returned an unexpected type.
-            raise RuntimeError(
+            raise TypeError(
                 f"Tool '{name}' returned an unexpected type: {type(raw_or_validated_result).__name__}. "
                 "Must return Dict[str, Any] or ToolOutput."
             )
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 561ad2f and 1247124.

📒 Files selected for processing (1)
  • ovos_plugin_manager/templates/agent_tools.py (1 hunks)
🧰 Additional context used
🪛 Ruff (0.14.0)
ovos_plugin_manager/templates/agent_tools.py

68-69: try-except-pass detected, consider logging the exception

(S110)


68-68: Do not catch blind exception: Exception

(BLE001)


68-68: Local variable e is assigned to but never used

Remove assignment to unused variable e

(F841)


131-131: Do not catch blind exception: Exception

(BLE001)


133-133: Use explicit conversion flag

Replace with conversion flag

(RUF010)


156-156: Avoid specifying long messages outside the exception class

(TRY003)


179-179: Avoid specifying long messages outside the exception class

(TRY003)


202-202: Avoid specifying long messages outside the exception class

(TRY003)


209-209: Avoid specifying long messages outside the exception class

(TRY003)


216-216: Avoid specifying long messages outside the exception class

(TRY003)


223-226: Prefer TypeError exception for invalid type

(TRY004)


223-226: Avoid specifying long messages outside the exception class

(TRY003)


234-234: Avoid specifying long messages outside the exception class

(TRY003)


237-240: Prefer TypeError exception for invalid type

(TRY004)


237-240: Avoid specifying long messages outside the exception class

(TRY003)

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (3)
  • GitHub Check: unit_tests (3.10)
  • GitHub Check: unit_tests (3.11)
  • GitHub Check: build_tests (3.10)
🔇 Additional comments (5)
ovos_plugin_manager/templates/agent_tools.py (5)

1-19: LGTM! Clean foundation for the ToolBox framework.

The imports are appropriate, and the empty Pydantic base classes provide clear extension points for concrete tool implementations.


22-41: LGTM! Well-structured tool definition.

The type aliases improve clarity, and the AgentTool dataclass provides a clean, self-documenting structure for tool metadata and execution logic.


90-134: LGTM! Solid messagebus integration and error handling.

The refresh, discovery, and execution handlers are well-implemented. The broad exception catch at line 131 is appropriate for messagebus handlers to ensure graceful error responses without crashing.


136-179: LGTM! Robust validation with clear error context.

Both validation methods provide clear error messages that will aid debugging. The error message detail flagged by Ruff is actually valuable for troubleshooting validation failures.


242-293: LGTM! Well-designed lazy loading and schema generation.

The lazy loading pattern in get_tool() ensures tools are discovered on-demand, and the tool_json_list property correctly leverages Pydantic's model_json_schema() for LLM-compatible tool definitions. The abstract discover_tools() method properly enforces the plugin contract.

@github-actions github-actions bot added feature and removed feature labels Oct 17, 2025
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

♻️ Duplicate comments (1)
ovos_plugin_manager/templates/agent_tools.py (1)

66-69: Log initialization failures to aid debugging.

This issue was already identified in a previous review. Silently swallowing exceptions during discover_tools() can hide critical setup errors.

🧹 Nitpick comments (2)
ovos_plugin_manager/templates/agent_tools.py (2)

75-88: Consider adding an unbind/cleanup method.

The bind method registers message bus handlers, but there's no corresponding method to unregister them. This could lead to resource leaks or duplicate handlers if a ToolBox instance is re-bound or needs to be cleanly shut down.

Consider adding an unbind method:

def unbind(self) -> None:
    """
    Unregisters messagebus handlers and cleans up the connection.
    """
    if self.bus:
        self.bus.remove("ovos.persona.tools.discover", self.handle_discover)
        self.bus.remove(f"ovos.persona.tools.{self.toolbox_id}.call", self.handle_call)
        self.bus = None

209-212: Prefer TypeError for type validation failures.

The code raises ValueError for type mismatches, but Python convention (and Ruff TRY004) suggests using TypeError when the issue is specifically an incorrect type rather than an invalid value.

Apply this diff to use the more semantically appropriate exception type:

             if not isinstance(tool_kwargs, tool.argument_schema):
-                raise ValueError(
+                raise TypeError(
                     f"Tool '{name}' called with model of type {type(tool_kwargs).__name__}, "
                     f"but expected {tool.argument_schema.__name__}."
                 )
         else:
             # Case C: Input is an unexpected type.
-            raise RuntimeError(
+            raise TypeError(
                 f"Tool '{name}' called with unexpected type arguments: {type(tool_kwargs).__name__}. "
                 "Must be Dict[str, Any] or ToolArguments."
             )
             if not isinstance(raw_or_validated_result, tool.output_schema):
-                raise RuntimeError(
+                raise TypeError(
                     f"Tool '{name}' returned model of type {type(raw_or_validated_result).__name__}, "
                     f"but expected {tool.output_schema.__name__}."
                 )
         else:
             # Case C: Tool returned an unexpected type.
-            raise RuntimeError(
+            raise TypeError(
                 f"Tool '{name}' returned an unexpected type: {type(raw_or_validated_result).__name__}. "
                 "Must return Dict[str, Any] or ToolOutput."
             )

Also applies to: 223-226, 240-243, 254-257

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 1247124 and af1f610.

📒 Files selected for processing (1)
  • ovos_plugin_manager/templates/agent_tools.py (1 hunks)
🧰 Additional context used
🪛 Ruff (0.14.0)
ovos_plugin_manager/templates/agent_tools.py

68-69: try-except-pass detected, consider logging the exception

(S110)


68-68: Do not catch blind exception: Exception

(BLE001)


68-68: Local variable e is assigned to but never used

Remove assignment to unused variable e

(F841)


131-131: Do not catch blind exception: Exception

(BLE001)


133-133: Use explicit conversion flag

Replace with conversion flag

(RUF010)


156-156: Avoid specifying long messages outside the exception class

(TRY003)


179-179: Avoid specifying long messages outside the exception class

(TRY003)


202-202: Avoid specifying long messages outside the exception class

(TRY003)


209-212: Prefer TypeError exception for invalid type

(TRY004)


209-212: Avoid specifying long messages outside the exception class

(TRY003)


220-220: Avoid specifying long messages outside the exception class

(TRY003)


223-226: Prefer TypeError exception for invalid type

(TRY004)


223-226: Avoid specifying long messages outside the exception class

(TRY003)


233-233: Avoid specifying long messages outside the exception class

(TRY003)


240-243: Prefer TypeError exception for invalid type

(TRY004)


240-243: Avoid specifying long messages outside the exception class

(TRY003)


251-251: Avoid specifying long messages outside the exception class

(TRY003)


254-257: Prefer TypeError exception for invalid type

(TRY004)


254-257: Avoid specifying long messages outside the exception class

(TRY003)

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: unit_tests (3.10)
  • GitHub Check: unit_tests (3.11)
🔇 Additional comments (5)
ovos_plugin_manager/templates/agent_tools.py (5)

1-24: LGTM! Clean setup with appropriate abstractions.

The imports, base Pydantic models, and type aliases are well-structured. Using empty base classes for ToolArguments and ToolOutput is the correct pattern for creating a type hierarchy that concrete implementations can extend.


27-41: LGTM! Well-designed tool metadata structure.

The AgentTool dataclass provides clear separation between metadata (schemas, descriptions) and execution logic (tool_call). The field metadata is a nice touch for documentation.


90-134: LGTM! Message bus handlers are resilient and well-structured.

The handlers appropriately catch exceptions and emit responses/errors back on the bus. The broad exception handling at line 131 is justified for a message bus handler that needs to remain resilient and always respond.


136-179: LGTM! Validation methods provide clear error context.

Both validation methods correctly leverage Pydantic's validation and re-raise with additional context. The detailed error messages (flagged by Ruff TRY003) are actually beneficial for debugging tool integration issues.


275-310: LGTM! Clean JSON schema export and abstract method definition.

The tool_json_list property correctly leverages Pydantic's model_json_schema() to generate LLM-compatible tool definitions. The abstract discover_tools method provides a clear contract for plugin implementations.

Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
@github-actions github-actions bot added feature and removed feature labels Oct 17, 2025
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

♻️ Duplicate comments (2)
ovos_plugin_manager/templates/agent_tools.py (2)

260-274: Consider preventing repeated refreshes for non-existent tools.

The past review comment about inefficient repeated refreshes for non-existent tools is still valid. Consider adding a flag to track whether refresh_tools() has already been called.


66-69: Log initialization failures to aid debugging.

Silently swallowing exceptions during discover_tools() can hide critical setup errors. The past review comment requesting logging is still valid and should be addressed.

🧹 Nitpick comments (1)
ovos_plugin_manager/templates/agent_tools.py (1)

210-213: Consider using TypeError for type validation failures.

For better semantic clarity, consider using TypeError instead of ValueError or RuntimeError when checking type compatibility (lines 210, 224, 241, 255). This would align with Python conventions where TypeError indicates an operation received an inappropriate type.

Example for line 210:

-            raise ValueError(
+            raise TypeError(
                 f"Tool '{name}' called with model of type {type(tool_kwargs).__name__}, "
                 f"but expected {tool.argument_schema.__name__}."
             )

Apply similar changes to lines 224, 241, and 255.

Also applies to: 224-227, 241-244, 255-258

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between af1f610 and e672ce7.

📒 Files selected for processing (1)
  • ovos_plugin_manager/templates/agent_tools.py (1 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
ovos_plugin_manager/templates/agent_tools.py (1)
ovos_plugin_manager/templates/phal.py (1)
  • emit (138-141)
🪛 Ruff (0.14.0)
ovos_plugin_manager/templates/agent_tools.py

68-69: try-except-pass detected, consider logging the exception

(S110)


68-68: Do not catch blind exception: Exception

(BLE001)


68-68: Local variable e is assigned to but never used

Remove assignment to unused variable e

(F841)


131-131: Do not catch blind exception: Exception

(BLE001)


133-133: Use explicit conversion flag

Replace with conversion flag

(RUF010)


156-156: Avoid specifying long messages outside the exception class

(TRY003)


179-179: Avoid specifying long messages outside the exception class

(TRY003)


202-202: Avoid specifying long messages outside the exception class

(TRY003)


210-213: Prefer TypeError exception for invalid type

(TRY004)


210-213: Avoid specifying long messages outside the exception class

(TRY003)


221-221: Avoid specifying long messages outside the exception class

(TRY003)


224-227: Prefer TypeError exception for invalid type

(TRY004)


224-227: Avoid specifying long messages outside the exception class

(TRY003)


234-234: Avoid specifying long messages outside the exception class

(TRY003)


241-244: Prefer TypeError exception for invalid type

(TRY004)


241-244: Avoid specifying long messages outside the exception class

(TRY003)


252-252: Avoid specifying long messages outside the exception class

(TRY003)


255-258: Prefer TypeError exception for invalid type

(TRY004)


255-258: Avoid specifying long messages outside the exception class

(TRY003)

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: unit_tests (3.11)
  • GitHub Check: unit_tests (3.10)
🔇 Additional comments (7)
ovos_plugin_manager/templates/agent_tools.py (7)

1-20: LGTM! Clean base model definitions.

The imports and base Pydantic models are well-structured and appropriate for the ToolBox framework.


22-41: LGTM! Well-designed tool metadata structure.

The type aliases and AgentTool dataclass provide a clean, type-safe interface for defining agent tools with proper schema validation.


75-96: LGTM! Clean message bus integration.

The bind() and refresh_tools() methods properly set up message bus handlers and tool caching.


98-134: LGTM! Robust message bus handlers.

Both handle_discover() and handle_call() properly handle message bus interactions with appropriate error handling. The broad exception catch in handle_call() is justified to ensure failures are communicated back over the bus.


136-179: LGTM! Proper validation with clear error messages.

Both validation methods correctly leverage Pydantic's validation capabilities and provide helpful error context.


276-297: LGTM! Clean JSON schema generation.

The tool_json_list property correctly uses Pydantic v2's model_json_schema() method to generate LLM-compatible tool definitions.


299-311: LGTM! Proper abstract method definition.

The discover_tools() abstract method is correctly defined with clear documentation about implementation requirements, including the important note about idempotency.

@github-actions github-actions bot added feature and removed feature labels Oct 17, 2025
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

♻️ Duplicate comments (1)
ovos_plugin_manager/templates/agent_tools.py (1)

66-69: Log initialization failures to aid debugging.

This issue was flagged in a previous review but remains unaddressed. Silently swallowing all exceptions during discover_tools() can hide critical setup errors (missing dependencies, configuration issues, import failures). While lazy loading provides a fallback, operators need visibility when initialization fails.

Apply this diff to add logging:

+from ovos_utils.log import LOG
+
 # (in __init__ method)
 try:
     self.discover_tools()  # try to find tools immediately
 except Exception as e:
-    pass  # will be lazy loaded or throw error on first usage
+    LOG.warning(f"Failed to discover tools for ToolBox '{toolbox_id}' during init: {e}")
+    # will be lazy loaded or throw error on first usage

Based on learnings

🧹 Nitpick comments (1)
ovos_plugin_manager/templates/agent_tools.py (1)

205-226: Consider using TypeError for type validation failures.

When validating input types, TypeError is more semantically appropriate than ValueError or RuntimeError. This applies to lines 209-212 (type mismatch for ToolArguments), 223-226 (unexpected input type), and similar patterns in the output validation section (lines 240-243, 254-257).

Apply this diff to improve semantic precision:

         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(
+                raise TypeError(
                     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).
             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(
+            raise TypeError(
                 f"Tool '{name}' called with unexpected type arguments: {type(tool_kwargs).__name__}. "
                 "Must be Dict[str, Any] or ToolArguments."
             )

Apply similar changes to output validation:

         # 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(
+                raise TypeError(
                     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:
                 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:
             # Case C: Tool returned an unexpected type.
-            raise RuntimeError(
+            raise TypeError(
                 f"Tool '{name}' returned an unexpected type: {type(raw_or_validated_result).__name__}. "
                 "Must return Dict[str, Any] or ToolOutput."
             )
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between e672ce7 and e448eba.

📒 Files selected for processing (1)
  • ovos_plugin_manager/templates/agent_tools.py (1 hunks)
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: 2025-10-17T15:18:51.714Z
Learnt from: JarbasAl
PR: OpenVoiceOS/ovos-plugin-manager#340
File: ovos_plugin_manager/templates/agent_tools.py:260-274
Timestamp: 2025-10-17T15:18:51.714Z
Learning: In ovos_plugin_manager/templates/agent_tools.py, the ToolBox.get_tool() method intentionally calls refresh_tools() on every cache miss to support dynamic tool discovery in MCP (Model Context Protocol) and UTCP server plugins, where tools can be added or modified at runtime. Do not suggest caching the discovery state to avoid repeated refreshes.

Applied to files:

  • ovos_plugin_manager/templates/agent_tools.py
🪛 Ruff (0.14.0)
ovos_plugin_manager/templates/agent_tools.py

68-69: try-except-pass detected, consider logging the exception

(S110)


68-68: Do not catch blind exception: Exception

(BLE001)


68-68: Local variable e is assigned to but never used

Remove assignment to unused variable e

(F841)


131-131: Do not catch blind exception: Exception

(BLE001)


133-133: Use explicit conversion flag

Replace with conversion flag

(RUF010)


156-156: Avoid specifying long messages outside the exception class

(TRY003)


179-179: Avoid specifying long messages outside the exception class

(TRY003)


202-202: Avoid specifying long messages outside the exception class

(TRY003)


209-212: Prefer TypeError exception for invalid type

(TRY004)


209-212: Avoid specifying long messages outside the exception class

(TRY003)


220-220: Avoid specifying long messages outside the exception class

(TRY003)


223-226: Prefer TypeError exception for invalid type

(TRY004)


223-226: Avoid specifying long messages outside the exception class

(TRY003)


233-233: Avoid specifying long messages outside the exception class

(TRY003)


240-243: Prefer TypeError exception for invalid type

(TRY004)


240-243: Avoid specifying long messages outside the exception class

(TRY003)


251-251: Avoid specifying long messages outside the exception class

(TRY003)


254-257: Prefer TypeError exception for invalid type

(TRY004)


254-257: Avoid specifying long messages outside the exception class

(TRY003)

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: build_tests (3.11)
🔇 Additional comments (6)
ovos_plugin_manager/templates/agent_tools.py (6)

1-19: LGTM! Clean foundation for the ToolBox framework.

The imports and base Pydantic models provide a solid foundation for type-safe tool definitions with schema validation.


22-41: LGTM! Well-structured tool definition.

The type aliases enhance readability, and the AgentTool dataclass provides comprehensive metadata for LLM integration with clear field documentation.


75-112: LGTM! Message bus integration looks solid.

The bind(), refresh_tools(), and handle_discover() methods correctly implement the messagebus lifecycle for tool discovery and registration.


114-134: LGTM! Robust message bus error handling.

The broad exception catch in handle_call() is appropriate for a message bus handler, ensuring errors are propagated without crashing the service. The error response format is clear and includes the toolbox identifier.


136-179: LGTM! Clean validation logic.

The static validation methods properly leverage Pydantic's validation capabilities with appropriate error context. The separation of input and output validation improves clarity.


259-310: LGTM! Dynamic tool discovery and JSON schema generation work correctly.

The get_tool() method's refresh-on-miss behavior supports dynamic tool discovery for runtime-modifiable toolboxes. The tool_json_list property correctly leverages Pydantic's schema generation for LLM integration. The abstract discover_tools() method provides a clear extension point for concrete implementations.

Based on learnings

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants