Files
nc-talk-ai/docs/TOOL_PROVIDERS.md
Pascal Kienast 0739d3da6a Initial open-source release of Talk AI
Talk AI is a multi-bot AI assistant manager for Nextcloud Talk:
per-bot prompts and models, agentic tool calling (MCP + built-in
tools), RAG over Nextcloud files, room-document search, vision and
speech-to-text attachments, persistent bot wikis, approval workflows,
rate limiting, and multi-provider LLM support (any OpenAI-compatible
endpoint).

Developed within EDUC - the European Digital UniverCity
(https://educalliance.eu), where it runs as the 'EDUC AI' assistant on
the alliance-wide Nextcloud portal. This public repository is the
upstream point of truth; deployment-specific tools plug in via the
tool-provider extension point (docs/TOOL_PROVIDERS.md).

License: AGPL-3.0-or-later.
2026-07-08 21:13:13 +02:00

87 lines
3.5 KiB
Markdown

# Tool-Provider Extension Point
Other Nextcloud apps can contribute additional agent tools to Talk AI bots
without any changes to this app. Contributed tools behave exactly like the
core built-in tools: they share the name-based per-bot loadout storage, the
per-bot enable/disable UI, execution policies and the agent-loop dispatch.
This is the mechanism used to keep deployment-specific tools out of the
generic core: a companion app can ship its own tools (e.g. a course-catalogue
search) that plug into every bot without forking this app.
## How it works
1. The core app exposes `OCA\EducAI\ToolProvider\ToolProviderRegistry`, which
all consumers (agent executor, tools API, bot service) use to list and
execute name-based tools.
2. On first use per request, the registry dispatches
`OCA\EducAI\ToolProvider\CollectToolProvidersEvent`.
3. Listeners register implementations of
`OCA\EducAI\ToolProvider\IToolProvider` on the event.
## Registering a provider from another app
In your app's `Application::register()`:
```php
$context->registerEventListener(
\OCA\EducAI\ToolProvider\CollectToolProvidersEvent::class,
\OCA\MyApp\Listener\MyToolProviderListener::class
);
```
The listener:
```php
class MyToolProviderListener implements IEventListener {
public function __construct(private MyToolProvider $provider) {
}
public function handle(Event $event): void {
if (!$event instanceof CollectToolProvidersEvent) {
return;
}
$event->registerProvider($this->provider);
}
}
```
## Implementing `IToolProvider`
| Method | Purpose |
|---|---|
| `getTools()` | Currently available tool definitions (may be `[]` when unconfigured). Called when assembling a bot's loadout and for the tools UI. |
| `providesTool($name)` | Whether this provider executes the given tool name (include legacy aliases). |
| `executeTool($name, $arguments, $config)` | Run the tool; return `{content: [{type, text}], isError}` (MCP-style result). |
| `getToolMetadata()` | Static UI labels/summaries per tool name — must work even when the provider is disabled, so assigned tools still render properly in the UI. |
| `setInvocationContext($context)` | Receives bot id / room token / attachments before execution; no-op for stateless providers. |
Tool definition shape (same as core built-ins):
```php
[
'name' => 'my_tool', // unique, snake_case
'description' => 'LLM-facing description',
'schema' => [ /* JSON schema */ ],
'policy' => $policyService->searchToolPolicy(), // or readToolPolicy()
'label' => 'My Tool', // optional UI label
'summary' => 'One-line UI summary', // optional UI description
]
```
Execution policies drive loop budgets and the forced-search heuristics; use
`ToolExecutionPolicyService::searchToolPolicy()` / `readToolPolicy()` for
read-only tools. Mutating provider tools should not be marked read-only —
construct a stricter policy accordingly.
## Guarantees & caveats
- **Name collisions:** core tools can never be shadowed. A provider tool whose
name collides with an existing tool is skipped with a warning in the log.
- **Failure isolation:** a provider that throws in `getTools()` or
`setInvocationContext()` is logged and skipped; it cannot break the loadout.
- **Storage:** assignments are stored by tool *name* in the existing
`bot_tools` table (`is_built_in` path) — no schema changes needed for
provider tools, and removing a provider app leaves stale assignments
inert (they simply stop matching any definition).