Skip to content

Commit 9d1d1ad

Browse files
JarbasAlcoderabbitai[bot]claude
authored
feat: tool plugins (#340)
* feat: tool plugins * Update requirements/requirements.txt Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com> * discovery * pydantic validation * better output validation * better input validation * Update ovos_plugin_manager/templates/agent_tools.py Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com> * fix coderabbit mess * fix: agent_tools bug fixes, Apache header, docs, and tests - 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 <noreply@anthropic.com> * feat: move tool plugins to opm.agents namespace, add AgenticLoopEngine - 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 <noreply@anthropic.com> * refactor: remove AgenticLoopEngine and AGENT_LOOP from OPM 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 <noreply@anthropic.com> * 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) * 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 --------- Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com> Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
1 parent 1a39311 commit 9d1d1ad

14 files changed

Lines changed: 1504 additions & 12 deletions

File tree

FAQ.md

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,3 +42,12 @@ uv run pytest ovos-plugin-manager/test/ --cov=ovos_plugin_manager
4242

4343
## What Python versions are supported?
4444
See `QUICK_FACTS.md` — currently `>=3.9`.
45+
46+
## What are Agent Tools and ToolBox plugins?
47+
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.
48+
49+
## How do ToolBox plugins handle dynamic tool discovery?
50+
`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`.
51+
52+
## What serialization mode does ToolBox use for bus responses?
53+
`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`.

MAINTENANCE_REPORT.md

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -115,3 +115,28 @@ Address critical and major issues identified during CodeRabbit review of PR #376
115115
- **AI Model**: Gemini 2.0 Pro
116116
- **Actions Taken**: Triage 33 CodeRabbit comments, applied fixes for 10+ high-priority items covering CI, installation logic, templates, and documentation.
117117
- **Oversight**: Human review of logic changes in `pip_install` and `release_workflow.yml` recommended.
118+
119+
## [2026-03-18] — Address CodeRabbit PR #340 Review (tool plugins)
120+
121+
### Changes
122+
- **`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.
123+
- **`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.
124+
- **`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.
125+
- **`ovos_plugin_manager/templates/agent_tools.py`**:
126+
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.
127+
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.
128+
3. `validate_input()` (line 170): Removed `{tool_kwargs}` from error message. Security fix: prevents echoing raw arguments (which may contain secrets) onto shared messagebus.
129+
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.
130+
131+
### Rationale
132+
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.
133+
134+
### Verification
135+
- All 1011 unit tests pass (including 21 agent_tools tests).
136+
- Python syntax verified via `py_compile`.
137+
- Cache refresh behavior confirmed in test_agent_tools.py via mock assertions.
138+
139+
### AI Transparency Report
140+
- **AI Model**: Claude Haiku 4.5
141+
- **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.
142+
- **Oversight**: Security fixes to error messages and JSON serialization require human code review to ensure no loss of debug information needed for troubleshooting.

QUICK_FACTS.md

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,3 +10,16 @@ OpenVoiceOS plugin manager
1010
| License | Apache-2.0 |
1111
| Repository | [https://github.com/OpenVoiceOS/OVOS-plugin-manager](https://github.com/OpenVoiceOS/OVOS-plugin-manager) |
1212
| Python Support | >=3.9 |
13+
14+
## Agent Plugin Entry Point Groups
15+
16+
| Group | Base Class | Purpose |
17+
|---|---|---|
18+
| `opm.agents.chat` | `ChatEngine` | Multi-turn chat engines and agentic loops |
19+
| `opm.agents.chat.multimodal` | `MultimodalChatEngine` | Chat with image/audio/file inputs |
20+
| `opm.agents.toolbox` | `ToolBox` | Grouped callable `AgentTool` functions |
21+
| `opm.agents.summarizer` | `SummarizerEngine` | Document/chat summarisation |
22+
| `opm.agents.retrieval` | `RetrievalEngine` | Knowledge-base query |
23+
| `opm.plugin.persona` | `dict` | Static persona config wired by `ovos-persona` |
24+
25+
See `docs/agents.md` for the full registry of installed plugins.

docs/agents.md

Lines changed: 212 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,212 @@
1+
# Agent Plugins
2+
3+
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.
4+
5+
Base classes: `ovos_plugin_manager/templates/agents.py`, `ovos_plugin_manager/templates/agent_tools.py`
6+
7+
---
8+
9+
## Entry Point Groups
10+
11+
| Group | Base Class | Purpose |
12+
|---|---|---|
13+
| `opm.agents.chat` | `ChatEngine` | Multi-turn chat / agentic loops — `continue_chat(messages)``AgentMessage` |
14+
| `opm.agents.chat.multimodal` | `MultimodalChatEngine` | Chat with image/audio/file inputs |
15+
| `opm.agents.toolbox` | `ToolBox` | Groups of callable `AgentTool` functions exposed to agents via bus or direct call |
16+
| `opm.agents.summarizer` | `SummarizerEngine` / `ChatSummarizerEngine` | Document or chat-history summarisation |
17+
| `opm.agents.retrieval` | `RetrievalEngine` | Knowledge-base / vector-index query (`query(q, lang, k)``List[Tuple[str, float]]`) |
18+
| `opm.plugin.persona` | `dict` | Static persona config dict; consumed by `ovos-persona` to wire a ChatEngine with a system prompt |
19+
20+
`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.
21+
22+
---
23+
24+
## Available ToolBoxes (`opm.agents.toolbox`)
25+
26+
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`).
27+
28+
| Plugin ID | Class | Tools | Package | API Key |
29+
|---|---|---|---|---|
30+
| `ovos-wikipedia-tools` | `WikipediaToolBox` | `search_wikipedia`, `get_wikipedia_sections`, `get_wikipedia_page` | `ovos-wikipedia-solver` | None — public Wikipedia REST API |
31+
| `ovos-ddg-tools` | `DuckDuckGoToolBox` | `search_duckduckgo`, `get_duckduckgo_infobox` | `ovos-ddg-solver-plugin` | None — DuckDuckGo Instant Answer API |
32+
| `ovos-wolfram-alpha-tools` | `WolframAlphaToolBox` | `compute`, `compute_full` | `ovos-wolfram-alpha-solver` | Optional — free key at developer.wolframalpha.com; demo key bundled |
33+
| `ovos-weather-tools` | `WeatherToolBox` | `get_current_weather`, `get_daily_forecast`, `get_hourly_forecast` | `ovos-skill-weather` | None — Open-Meteo public API |
34+
| `ovos-datetime-tools` | `DateTimeToolBox` | `get_current_datetime`, `convert_timezone`, `get_timezone_for_location` | `ovos-skill-date-time` | None — stdlib + pytz |
35+
| `ovos-ip-tools` | `IPAddressToolBox` | `get_local_ip_addresses`, `get_public_ip` | `ovos-skill-ip` | None |
36+
| `ovos-iss-tools` | `ISSLocationToolBox` | `get_iss_position`, `get_iss_crew` | `ovos-skill-iss-location` | Optional — geonames.org user for reverse geocoding |
37+
| `ovos-speedtest-tools` | `SpeedTestToolBox` | `run_speedtest` | `ovos-skill-speedtest` | None — Speedtest.net |
38+
| `ovos-wallpapers-tools` | `WallpapersToolBox` | `search_wallpapers` | `ovos-skill-wallpapers` | None — wallhaven.cc public API |
39+
| `ovos-wikihow-tools` | `WikiHowToolBox` | `search_wikihow`, `get_wikihow_steps` | `ovos-skill-wikihow` | None — pywikihow scraper |
40+
| `ovos-wordnet-tools` | `WordNetToolBox` | `lookup_word`, `define_word` | `ovos-skill-wordnet` | None — local NLTK corpus |
41+
| `ovos-skill-md-toolbox` | `SkillMDToolBox` | dynamic — one tool per installed `SKILL.md` | `ovos-agentic-loop` | Requires a configured `ChatEngine` (brain) |
42+
| `ovos-filesystem-tools` | `FileSystemToolBox` | `read_file`, `write_file`, `list_directory`, `search_in_files`, `find_files` | `ovos-agentic-loop` | None |
43+
| `ovos-shell-tools` | `ShellToolBox` | `run_command` | `ovos-agentic-loop` | None |
44+
| `ovos-web-search-tools` | `WebSearchToolBox` | `web_search` | `ovos-agentic-loop` | None |
45+
| `ovos-clock-tools` | `ClockToolBox` | `get_current_datetime` | `ovos-agentic-loop` | None |
46+
47+
### Tool schema
48+
49+
Each `AgentTool` (`agent_tools.py:40`) carries:
50+
- `name` — snake_case identifier used by the LLM
51+
- `description` — natural-language purpose shown to the LLM
52+
- `argument_schema` — Pydantic `ToolArguments` subclass; JSON Schema auto-generated for LLM tool-calling APIs
53+
- `output_schema` — Pydantic `ToolOutput` subclass; validated on every call
54+
- `tool_call` — the Python callable; receives an instantiated `ToolArguments`, returns `ToolOutput`
55+
56+
`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.
57+
58+
---
59+
60+
## Available Chat Engines (`opm.agents.chat`)
61+
62+
| Plugin ID | Class | Backend | Package |
63+
|---|---|---|---|
64+
| `ovos-chat-openai-plugin` | `OpenAIChatEngine` | OpenAI API | `ovos-openai-plugin` |
65+
| `ovos-chat-gemini-plugin` | `GeminiChatEngine` | Google Gemini | `ovos-gemini-plugin` |
66+
| `ovos-chat-gemini-code-plugin` | `GeminiCodeChatEngine` | Gemini (code) | `ovos-gemini-plugin` |
67+
| `ovos-chat-gemini-session-plugin` | `GeminiSessionChatEngine` | Gemini (session) | `ovos-gemini-plugin` |
68+
| `ovos-chat-claude-plugin` | `ClaudeChatEngine` | Anthropic Claude | `ovos-claude-plugin` |
69+
| `ovos-chat-claude-code-plugin` | `ClaudeCodeChatEngine` | Claude (code) | `ovos-claude-plugin` |
70+
| `ovos-chat-claude-code-session-plugin` | `ClaudeCodeSessionChatEngine` | Claude (session) | `ovos-claude-plugin` |
71+
| `ovos-chat-kilo-plugin` | `KiloChatEngine` | Kilo (Anthropic) | `ovos-kilo-plugin` |
72+
| `ovos-chat-kilo-session-plugin` | `KiloSessionChatEngine` | Kilo (session) | `ovos-kilo-plugin` |
73+
| `ovos-chat-gguf-plugin` | `GGUFChatEngine` | Local GGUF (llama.cpp) | `ovos-gguf-plugin` |
74+
| `ovos-chat-qwen-code-plugin` | `QwenCodeChatEngine` | Qwen-Code | `ovos-qwen-code-plugin` |
75+
| `ovos-chat-opencode-plugin` | `OpenCodeChatEngine` | OpenCode | `ovos-opencode-plugin` |
76+
| `ovos-chat-opencode-session-plugin` | `OpenCodeSessionChatEngine` | OpenCode (session) | `ovos-opencode-plugin` |
77+
| `ovos-wikigpt` | `WikiGPTSolver` | Wikipedia RAG | `ovos-wikipedia-solver` |
78+
| `ovos-react-loop` | `ReActLoopEnginePlugin` | ReAct over any ChatEngine + ToolBoxes | `ovos-agentic-loop` |
79+
| `ovos-plan-execute-loop` | `PlanAndExecuteEnginePlugin` | Plan-and-Execute | `ovos-agentic-loop` |
80+
| `ovos-reflexion-loop` | `ReflexionEnginePlugin` | Reflexion | `ovos-agentic-loop` |
81+
| `ovos-self-ask-loop` | `SelfAskEnginePlugin` | Self-Ask | `ovos-agentic-loop` |
82+
| `ovos-chain-of-thought-loop` | `ChainOfThoughtEnginePlugin` | Chain-of-Thought | `ovos-agentic-loop` |
83+
| `ovos-mos-king-reranker` | `ReRankerKingMoSPlugin` | Mixture-of-Solvers (reranker) | `ovos-MoS` |
84+
| `ovos-mos-king-generative` | `GenerativeKingMoSPlugin` | MoS (generative king) | `ovos-MoS` |
85+
| `ovos-mos-democracy` | `DemocracyMoSPlugin` | MoS (majority vote) | `ovos-MoS` |
86+
| `ovos-mos-duopoly-reranker` | `ReRankerDuopolyMoSPlugin` | MoS (duopoly reranker) | `ovos-MoS` |
87+
| `ovos-mos-duopoly-generative` | `GenerativeDuopolyMoSPlugin` | MoS (duopoly generative) | `ovos-MoS` |
88+
89+
### Multimodal Chat Engines (`opm.agents.chat.multimodal`)
90+
91+
| Plugin ID | Class | Backend | Package |
92+
|---|---|---|---|
93+
| `ovos-chat-multimodal-gemini-plugin` | `GeminiMultimodalChatEngine` | Gemini | `ovos-gemini-plugin` |
94+
| `ovos-chat-multimodal-claude-plugin` | `ClaudeMultimodalChatEngine` | Claude | `ovos-claude-plugin` |
95+
| `ovos-chat-multimodal-kilo-plugin` | `KiloMultimodalChatEngine` | Kilo | `ovos-kilo-plugin` |
96+
| `ovos-chat-multimodal-qwen-code-plugin` | `QwenCodeMultimodalChatEngine` | Qwen-Code | `ovos-qwen-code-plugin` |
97+
98+
`ChatEngine.continue_chat` signature — `agents.py:210`:
99+
```python
100+
def continue_chat(self, messages: List[AgentMessage],
101+
session_id: str = "default",
102+
lang: Optional[str] = None,
103+
units: Optional[str] = None) -> AgentMessage:
104+
```
105+
106+
`ChatEngine` also provides `stream_tokens`, `stream_sentences`, and `get_response` helpers (`agents.py:228–300`). Plugins only need to implement `continue_chat`.
107+
108+
---
109+
110+
## Available Personas (`opm.plugin.persona`)
111+
112+
Each persona entry is a dict defining `chat_engine`, `system_prompt`, and optionally `toolboxes`. Loaded and wired by the `ovos-persona` service.
113+
114+
| Persona ID | Backend | Package |
115+
|---|---|---|
116+
| `OpenAI` | `ovos-chat-openai-plugin` | `ovos-openai-plugin` |
117+
| `Claude` | `ovos-chat-claude-plugin` | `ovos-claude-plugin` |
118+
| `Gemini` | `ovos-chat-gemini-plugin` | `ovos-gemini-plugin` |
119+
| `Kilo` | `ovos-chat-kilo-plugin` | `ovos-kilo-plugin` |
120+
| `QwenCode` | `ovos-chat-qwen-code-plugin` | `ovos-qwen-code-plugin` |
121+
| `OpenCode` | `ovos-chat-opencode-plugin` | `ovos-opencode-plugin` |
122+
| `Wikipedia` | Wikipedia solver | `ovos-wikipedia-solver` |
123+
| `WikiGPT` | `ovos-wikigpt` | `ovos-wikipedia-solver` |
124+
| `DuckDuckGo` | DDG solver | `ovos-ddg-solver-plugin` |
125+
| `Wolfram Alpha` | Wolfram solver | `ovos-wolfram-alpha-solver` |
126+
| `WikiHow` | WikiHow solver | `ovos-skill-wikihow` |
127+
| `Wordnet` | WordNet solver | `ovos-skill-wordnet` |
128+
129+
---
130+
131+
## How to Implement a ToolBox
132+
133+
Register under `opm.agents.toolbox` in `pyproject.toml`:
134+
135+
```toml
136+
[project.entry-points."opm.agents.toolbox"]
137+
my-tools = "my_package.toolbox:MyToolBox"
138+
```
139+
140+
Minimal implementation (`agent_tools.py:56`):
141+
142+
```python
143+
from ovos_plugin_manager.templates.agent_tools import AgentTool, ToolArguments, ToolBox, ToolOutput
144+
from pydantic import Field
145+
146+
class MyArgs(ToolArguments):
147+
query: str = Field(..., description="Input text.")
148+
149+
class MyOutput(ToolOutput):
150+
result: str = Field(..., description="Tool result.")
151+
152+
class MyToolBox(ToolBox):
153+
toolbox_id = "my-tools"
154+
155+
def __init__(self, config=None):
156+
self.config = config or {}
157+
super().__init__(toolbox_id=self.toolbox_id)
158+
159+
def discover_tools(self):
160+
return [AgentTool(
161+
name="my_tool",
162+
description="Does something useful.",
163+
argument_schema=MyArgs,
164+
output_schema=MyOutput,
165+
tool_call=lambda args: MyOutput(result=args.query.upper()),
166+
)]
167+
```
168+
169+
`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`).
170+
171+
---
172+
173+
## How to Implement a ChatEngine
174+
175+
Register under `opm.agents.chat` in `pyproject.toml`:
176+
177+
```toml
178+
[project.entry-points."opm.agents.chat"]
179+
my-chat-engine = "my_package.chat:MyChatEngine"
180+
```
181+
182+
Minimal implementation (`agents.py:195`):
183+
184+
```python
185+
from ovos_plugin_manager.templates.agents import ChatEngine, AgentMessage, MessageRole
186+
from typing import List, Optional
187+
188+
class MyChatEngine(ChatEngine):
189+
def continue_chat(self, messages: List[AgentMessage],
190+
session_id: str = "default",
191+
lang: Optional[str] = None,
192+
units: Optional[str] = None) -> AgentMessage:
193+
# messages[-1] is the latest user message
194+
reply = call_my_llm_api([m.__dict__ for m in messages])
195+
return AgentMessage(role=MessageRole.ASSISTANT, content=reply)
196+
```
197+
198+
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`.
199+
200+
---
201+
202+
## Configuration
203+
204+
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:
205+
206+
| Key | Type | Default | Description |
207+
|---|---|---|---|
208+
| `lang` | `str` | session lang | BCP-47 language code |
209+
| `system_prompt` | `str` | `""` | System prompt for `AgentContextManager` plugins (`agents.py:61`) |
210+
| `context_ttl` | `int` | `120` | Seconds before coreference context is pruned (`agents.py:598`) |
211+
212+
ToolBox-specific keys are documented in each plugin's module docstring.

0 commit comments

Comments
 (0)