Tool Plugins
Tool plugins add capabilities that assistants can invoke during conversations. Each tool appears in the project’s plugin list and can be enabled or disabled per project.
Directory Layout
my_tool/
manifest.py
tools.pyExample: A Weather Lookup Tool
manifest.py:
from app.plugins.base import PluginManifest, PluginType
manifest = PluginManifest(
name="weather",
plugin_type=PluginType.TOOL,
version="1.0.0",
description="Look up current weather conditions",
author="Your Name",
config_schema={
"api_key": {
"type": "string",
"required": True,
"secret": True,
"label": "Weather API Key",
},
},
)tools.py:
import logging
import time
from typing import Any
import requests
from app.plugins.base import BaseTool, ToolContext
logger = logging.getLogger(__name__)
class GetWeatherTool(BaseTool):
"""Look up current weather for a location."""
name = "get_weather"
description = (
"Get the current weather conditions for a city."
" Returns temperature, conditions, and humidity."
)
category = "Research"
input_schema: dict[str, Any] = {
"type": "object",
"properties": {
"city": {
"type": "string",
"description": "City name (e.g. 'London' or 'New York')",
},
},
"required": ["city"],
}
def execute(self, params: dict[str, Any], ctx: ToolContext) -> str:
"""Fetch weather data from the API.
Args:
params: Tool input with city name.
ctx: Execution context with plugin_config.
Returns:
Weather summary string.
"""
api_key = ctx.plugin_config.get("api_key", "")
if not api_key:
return "Error: Weather API key not configured."
city = params["city"]
url = "https://api.weatherapi.com/v1/current.json"
start = time.time()
resp = requests.get(url, params={"key": api_key, "q": city}, timeout=10)
duration_ms = int((time.time() - start) * 1000)
# Log the API call so it appears in TeamWeb AI's API logs
ctx.log_api_call(
service="WeatherAPI",
method="GET",
url=url,
status_code=resp.status_code,
duration_ms=duration_ms,
)
if resp.status_code != 200:
return f"Error: Weather API returned {resp.status_code}"
data = resp.json()
current = data["current"]
return (
f"Weather in {city}: {current['condition']['text']}, "
f"{current['temp_c']}°C, humidity {current['humidity']}%"
)Class Attributes
Define these as class-level attributes on your BaseTool subclass:
| Attribute | Description |
|---|---|
name | Unique tool name used in API calls. Must not conflict with other tools |
description | Tells the LLM when and how to use this tool. Be specific |
input_schema | JSON Schema defining the tool’s parameters |
category | UI grouping label (e.g. “Publishing”, “Research”) |
config_schema | Optional per-assistant tool configuration schema (different from the plugin-level config) |
uses_canvas | Set True if the tool outputs content to the chat canvas |
The execute Method
The execute method receives two arguments:
params— a dict matchinginput_schema, containing the LLM’s chosen parameter valuesctx— aToolContextwith access to the current conversation, assistant, services, and configuration
Return a string that will be sent back to the LLM as the tool result.
Accessing Configuration
ctx.plugin_config— the plugin-level configuration (API keys, URLs) stored per projectctx.tool_config— per-assistant tool configuration, if your tool defines aconfig_schema
Available Services on ToolContext
| Property | Description |
|---|---|
ctx.conversation | Current conversation model |
ctx.assistant | Current assistant model |
ctx.user | Current user (may be None for scheduled tasks) |
ctx.project | The conversation’s project (may be None) |
ctx.db_session | SQLAlchemy database session |
ctx.knowledge_service | Access to the project’s knowledge base |
ctx.media_service | File storage service |
ctx.app_config | Flask app configuration dict |
Logging API Calls
Call ctx.log_api_call() for every outbound HTTP request your tool makes. This ensures the call appears in TeamWeb AI’s API logs timeline for debugging and auditing.
ctx.log_api_call(
service="MyService",
method="POST",
url="https://api.example.com/data",
status_code=200,
duration_ms=150,
request_summary="Created item",
response_summary='{"id": 42}',
)Multiple Tools per Plugin
A single tool plugin can define multiple BaseTool subclasses in tools.py. Each class with a non-empty name attribute is automatically registered as a separate tool. This is useful for grouping related tools under one plugin (e.g. the Unsplash plugin provides search_unsplash, select_unsplash_photo, and lookup_unsplash_photo).
Sandboxed Execution
If your plugin is marked sandboxable=True in the manifest, administrators can choose to run it inside an isolated Docker container. In sandbox mode:
- Your tool code runs inside a container with no network access to internal services
- Instead of a full
ToolContext, your code receives aSandboxToolContextwith plain dicts (no database or service access) - Configuration values are still available via
ctx.plugin_configandctx.tool_config - The plugin directory is mounted read-only in the container
This is useful for plugins that run untrusted code or where isolation is desired for security.