Skip to content

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.py

Example: 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:

AttributeDescription
nameUnique tool name used in API calls. Must not conflict with other tools
descriptionTells the LLM when and how to use this tool. Be specific
input_schemaJSON Schema defining the tool’s parameters
categoryUI grouping label (e.g. “Publishing”, “Research”)
config_schemaOptional per-assistant tool configuration schema (different from the plugin-level config)
uses_canvasSet True if the tool outputs content to the chat canvas

The execute Method

The execute method receives two arguments:

  • params — a dict matching input_schema, containing the LLM’s chosen parameter values
  • ctx — a ToolContext with 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 project
  • ctx.tool_config — per-assistant tool configuration, if your tool defines a config_schema

Available Services on ToolContext

PropertyDescription
ctx.conversationCurrent conversation model
ctx.assistantCurrent assistant model
ctx.userCurrent user (may be None for scheduled tasks)
ctx.projectThe conversation’s project (may be None)
ctx.db_sessionSQLAlchemy database session
ctx.knowledge_serviceAccess to the project’s knowledge base
ctx.media_serviceFile storage service
ctx.app_configFlask 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 a SandboxToolContext with plain dicts (no database or service access)
  • Configuration values are still available via ctx.plugin_config and ctx.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.