Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions FAQ.md
Original file line number Diff line number Diff line change
Expand Up @@ -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`.
25 changes: 25 additions & 0 deletions MAINTENANCE_REPORT.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
13 changes: 13 additions & 0 deletions QUICK_FACTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
212 changes: 212 additions & 0 deletions docs/agents.md
Original file line number Diff line number Diff line change
@@ -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.
Loading
Loading