Skip to content

Tools Reference

Look up a tool's inputs, output behavior, and required setup here. To use tools in a conversation, start with Working with Tools.

Registered means the server loaded the tool. Enabled for a chat means its schema is available to that chat's model. Core tools are active when registered; optional tools need their group enabled in the composer. The authenticated GET /api/v1/tools endpoint lists this instance's registered schemas and groups.

Parameter blocks below describe each tool's base schema before provider normalization and description compaction. Configurable limits show the packaged defaults; check this instance's /api/v1/tools response for active values. Additional input requirements enforced by execute() are noted separately.

Task Tools
Search and read the web web_search, fetch_web_page
Work with files and data view, code_interpreter, create_file
Generate images generate_image
Save or forget facts remember_fact, forget_fact
Research, recipes, shopping, and groceries Optional integrations
Implement a tool Tool Schema, Adding Custom Tools

Built-in Tools

request_tool_activation

Description: Show an approval card for an optional tool group that is currently disabled for the chat.

Parameters:

{
  "type": "object",
  "properties": {
    "group": {
      "type": "string",
      "enum": [
        "Scientific Research",
        "Grocery",
        "Recipes",
        "Shopping",
        "eBay",
        "MyDealz",
        "Price Search"
      ]
    },
    "reason": {
      "type": "string",
      "maxLength": 240
    }
  },
  "required": ["group", "reason"],
  "additionalProperties": false
}

The tool does not activate anything by itself. It waits for the user to choose Enable, No, or Ignore. The selected tool names are persisted on the chat.

Registered by default: Yes, unless denied. The agent receives this tool when at least one available optional group is disabled for the chat.

Description: Batched web search via Parallel Search API, always using advanced mode. Accepts keyword queries and a research objective, and returns excerpts with citations.

Parameters:

{
  "type": "object",
  "properties": {
    "query": {
      "type": "array",
      "items": {"type": "string"},
      "minItems": 1,
      "maxItems": 5,
      "description": "Keyword queries, usually 2-3 diverse queries (max 5)."
    },
    "objective": {"type": "string"},
    "num_results": {"type": "integer", "default": 10, "minimum": 1, "maximum": 20},
    "include_domains": {"type": "array", "items": {"type": "string"}, "maxItems": 20},
    "exclude_domains": {"type": "array", "items": {"type": "string"}, "maxItems": 20},
    "start_published_date": {"type": "string"},
    "freshness": {"type": "string", "enum": ["default", "live"], "default": "default"},
    "content_mode": {"type": "string", "enum": ["highlights", "none"], "default": "highlights"},
    "max_characters": {"type": "integer", "default": 4000, "minimum": 500, "maximum": 20000}
  },
  "required": ["query"],
  "additionalProperties": false
}

Features:

  • Batched queries (up to 5 at once)
  • Advanced retrieval for every search
  • Returns up to 10 results total by default, capped at 20 across all queries
  • Excerpts use max_characters per result (default 4000, range 500–20000)
  • freshness="live" allows at most 10-minute-old cached content and disables stale fallback
  • Domain filtering accepts either include_domains or exclude_domains; start_published_date filters after a date
  • Search returns excerpts, not full documents; use fetch_web_page to read full text
  • Unsupported legacy cache_only, text/summary, and end-date options return explicit errors
  • Validated output with query, results, and optional errors
  • Each result entry is schema-validated and includes query, url, title, optional published_date, and snippet
  • Result entries include url, title, and snippet fields used by web citation UX ([web:n] chips, hover details, and copy-time markdown links)

Configuration:

  • PARALLEL_API_KEY environment variable required

Registered by default: Yes

fetch_web_page

Description: Download and return text from known URLs. Ordinary pages use local SSRF-safe extraction for mode="text" without a query when DIRECT_WEB_FETCH_ENABLED=true, then fall back to Parallel; focused highlights use Parallel. PDFs, arXiv preprints, and YouTube transcripts use native text handlers. Scanned PDFs use local OCR. Supports offset pagination and full reading.

Parameters:

{
  "type": "object",
  "properties": {
    "urls": {
      "type": "array",
      "items": {"type": "string"},
      "minItems": 1,
      "maxItems": 10,
      "description": "Real user/search URLs (web pages, direct PDF links, arXiv preprints, YouTube videos)."
    },
    "mode": {
      "type": "string",
      "enum": ["highlights", "text"],
      "default": "text",
      "description": "text: sequential content; omit query. highlights: focused web excerpts; offset must be 0 and read_full must be false."
    },
    "query": {"type": "string", "description": "Optional focus query for mode=highlights only. For mode=text, omit query or use an empty string."},
    "offset": {
      "type": "integer",
      "default": 0,
      "minimum": 0,
      "description": "Character offset for mode=text (default 0). For highlights, omit or set to 0."
    },
    "max_characters": {
      "type": "integer",
      "default": 16000,
      "minimum": 500,
      "maximum": 90000,
      "description": "Max characters per page to return in this chunk (default 16000, max 90000)."
    },
    "read_full": {
      "type": "boolean",
      "default": false,
      "description": "Text only: true bypasses max_characters and reads from offset within retrieval and total tool output limits. If truncated, follow the returned continuation guidance. For highlights, omit or set to false."
    },
    "freshness": {"type": "string", "enum": ["default", "live"], "default": "default"}
  },
  "required": ["urls"],
  "additionalProperties": false
}

Features:

  • URL-pattern routing: YouTube, arXiv, and direct PDF links are handled natively in Python; ordinary pages use local SSRF-safe extraction when enabled, then fall back to Parallel
  • Batch URL content fetching (mixed YouTube, arXiv, PDF, and regular URLs in one call)
  • Token-efficient offset pagination (offset + max_characters) with truncation guidance
  • Full document extraction option (read_full: true)
  • Text responses share the executor's output budget across all URLs, including JSON overhead. Oversized results remain successful, with truncated: true and an exact continuation offset in the text; read_full also respects this budget.
  • Parallel returns at most the first 90,000 characters; neither offsets nor read_full can retrieve beyond that prefix. Native handlers may read further, subject to their download and extraction limits.
  • Native PDF/arXiv/YouTube handlers require mode="text"; focused excerpts are for ordinary webpages
  • For mode="text", omit query or use ""; offset and read_full are supported
  • For mode="highlights", optionally provide a focus query; omit offset/read_full or use 0/false
  • Direct handlers fetch from the source; Parallel live allows a maximum cache age of 10 minutes and disables stale fallback
  • Unimplemented legacy crawling, link extraction, summary and cache-only options return explicit errors
  • Failed, missing, or empty provider results remain errors and are excluded from citations
  • Always registered (YouTube, arXiv, and PDF handlers work without PARALLEL_API_KEY)
  • Enforced output contract (PageContentResultContract with url, title, author, text, offset, total_characters, truncated, fetch_status, and error)

Configuration:

  • DIRECT_WEB_FETCH_ENABLED=true enables local direct HTML extraction before Parallel; it defaults to false
  • PARALLEL_API_KEY is required only for ordinary pages when direct fetching is disabled or falls back

Registered by default: Yes

weather_fetch

Description: Get current weather, up to 15 daily forecasts starting tomorrow, and up to 48 hours of weather with precipitation probability.

Parameters:

{
  "type": "object",
  "properties": {
    "city": {"type": "string", "description": "City name (e.g., 'New York', 'London')"},
    "forecast_days": {
      "type": "integer",
      "description": "Daily forecasts starting tomorrow (0=off, maximum 15).",
      "default": 0
    },
    "forecast_hours": {
      "type": "integer",
      "description": "Hourly forecast from the current local hour.",
      "default": 0,
      "minimum": 0,
      "maximum": 48
    },
    "latitude": {"type": "number", "description": "Latitude (optional, use with longitude)"},
    "longitude": {"type": "number", "description": "Longitude (optional, use with latitude)"}
  },
  "required": [],
  "additionalProperties": false
}

Features:

  • Current weather conditions
  • Temperature, humidity, wind speed
  • Daily forecasts for the next 1–15 days
  • Hourly temperature, conditions, wind, precipitation amount and probability
  • Local timestamps, timezone and UTC offset; precipitation covers the hour preceding each timestamp
  • No API key required (uses Open-Meteo)

Provide a city or both coordinates. When both are supplied, the city lookup takes precedence. forecast_days is clamped to 0–15; forecast_hours must be an integer from 0 through 48.

Registered by default: Yes

code_interpreter

Description: Run stateless Python with Monty for supported standard-library code and a fresh Deno/Pyodide sandbox for third-party libraries. Supports calculations, analysis, charts, Excel, Word, PDF, and downloadable files.

Parameters:

{
  "type": "object",
  "properties": {
    "code": {
      "type": "string",
      "maxLength": 100000,
      "description": "Self-contained Python; re-import libraries each call"
    },
    "dependencies": {
      "type": "array",
      "items": {
        "type": "string",
        "enum": [
          "matplotlib",
          "numpy",
          "openpyxl",
          "pandas",
          "pypdf",
          "python-docx",
          "reportlab",
          "scikit-learn",
          "scipy",
          "seaborn",
          "sympy",
          "xlsxwriter"
        ]
      },
      "maxItems": 12,
      "uniqueItems": true,
      "description": "Allowlisted third-party dependencies to load"
    }
  },
  "required": ["code"],
  "additionalProperties": false
}

Features:

  • Fresh execution state for every call
  • Monty first for eligible scripts; Deno/Pyodide when needed or after a Monty failure
  • AST-validated imports plus dependency/import allowlists
  • Current-chat uploads and reusable generated files mounted in the virtual filesystem
  • Execution timeout protection (90 seconds)
  • Bounded output and file sizes
  • Matplotlib plot capture through plt.show() or plt.savefig()
  • Downloadable artifacts through save_file(filename, str_or_bytes)
  • XLSX creation/reading/editing with openpyxl/xlsxwriter, DOCX with python-docx, PDF with reportlab/pypdf
  • Imports automatically resolve package names, including docxpython-docx and sklearnscikit-learn
  • Package installation finishes before Deno network permission is revoked; user code and files are sent only after successful revocation when networking is disabled

Configuration:

  • TOOLS_CODE_INTERPRETER_DEPS_ALLOWLIST: Allowed packages; the schema above shows defaults. Existing overrides must include the office packages to enable them.
  • TOOLS_CODE_INTERPRETER_IMPORT_ALLOWLIST: Allowed Python modules
  • TOOLS_CODE_INTERPRETER_IMPORT_DENYLIST: Denied Python modules (includes networking)
  • TOOLS_CODE_INTERPRETER_TIMEOUT: Execution timeout in seconds (default: 90)
  • TOOLS_CODE_INTERPRETER_ALLOW_NETWORKING: Enable outbound networking in sandbox (default: false)
  • TOOLS_CODE_INTERPRETER_UNSANDBOXED: Disable sandboxing (dangerous, default: false)

The parameter schema above is for sandboxed mode with the default dependency allowlist. Its dependencies enum and maxItems change with that allowlist. Unsandboxed mode exposes only code; it does not expose dependencies.

Dependencies:

  • pydantic-monty for eligible scripts without third-party dependencies
  • mcp-run-python and Deno for third-party libraries and fallback execution
  • Optional: DENO_PATH if Deno not in PATH (legacy D_AI_DENO_PATH still works)

Generated Files: Plots and files registered with save_file() return chat-owned /generated-files/<chat-uuid>/... URLs. The authenticated Web UI renders supported images inline and downloads other files. Do not expose virtual paths such as /mnt/data/... as links.

For office files, serialize to io.BytesIO and call save_file("report.xlsx", buffer.getvalue()). Returned files can be reopened in later calls through their code_interpreter_file_map paths. Excel libraries preserve/write formulas but do not calculate them.

Run the real package/file/network-boundary check with:

NUMI_TEST_SANDBOX=1 uv run pytest tests/test_code_interpreter.py -k office_files_roundtrip -q

Security Note: Sandboxed execution has no host write, environment, or subprocess permission, and networking defaults off. The timeout is not a memory quota; apply OS/container resource limits for hostile multi-tenant deployments. Only enable unsandboxed mode on trusted personal instances.

Registered by default: Yes, including when a runtime dependency is missing. Eligible scripts can run with Monty alone. Scripts requiring the Deno/Pyodide path return a setup error if that runtime is unavailable.

forget_fact

Description: Remove a stored user fact by content match. Use only when the user explicitly asks to forget something.

Parameters:

{
  "type": "object",
  "properties": {
    "match": {
      "type": "string",
      "description": "Substring used to locate the fact to remove; be specific enough to match exactly one fact"
    }
  },
  "required": ["match"],
  "additionalProperties": false
}

Features:

  • Searches stored facts by substring match
  • Returns candidates if multiple facts match, asking for clarification
  • Confirms deletion of the specific fact

Usage Note: Only call when the user explicitly requests to forget a specific fact (e.g., 'forget my old address', 'don't remember I use Windows'). If the match is ambiguous, the tool returns candidates so you can ask the user which one to remove.

Registered by default: Yes

remember_fact

Description: Persist a single durable user fact for future sessions (preference, hardware, recurring context, long-term goal). Write-only agent surface; use for storing important information the user wants remembered across sessions.

Parameters:

{
  "type": "object",
  "properties": {
    "fact": {
      "type": "string",
      "description": "The fact to remember in third person (e.g. 'User prefers TypeScript', 'User's GPU is RX 9070 XT')"
    }
  },
  "required": ["fact"],
  "additionalProperties": false
}

Features:

  • Store durable user facts that persist across all sessions
  • Automatically included in user context for future conversations
  • Facts are written only when explicitly requested by the user
  • Searchable by content when checking for duplicates

Usage Note: Already stored facts appear in <user_context> above – do not call this tool to recall or verify stored facts. Only call when the user explicitly asks you to remember something new.

Registered by default: Yes

view

Description: Read and inspect a file uploaded to the current chat without using the code interpreter. The server resolves the chat directory internally; the model supplies only the displayed filename.

Parameters:

{
  "type": "object",
  "properties": {
    "filename": {"type": "string", "description": "Uploaded filename shown in the chat context (e.g., 'data.csv')"},
    "max_chars": {"type": "integer", "description": "Maximum characters to return (default 10000)", "default": 10000},
    "head_lines": {"type": "integer", "description": "Return only the first N lines (optional)"},
    "tail_lines": {"type": "integer", "description": "Return only the last N lines (optional)"},
    "csv_preview": {
      "type": "boolean",
      "description": "For CSV/TSV: return structured preview with headers + sample rows (runtime default true)"
    },
    "csv_preview_rows": {"type": "integer", "description": "Number of sample rows for CSV preview", "default": 10},
    "raw_text": {
      "type": "boolean",
      "description": "Force raw text output instead of structured CSV/TSV metadata",
      "default": false
    }
  },
  "required": ["filename"],
  "additionalProperties": false
}

Features:

  • Current-chat ownership enforced by server-injected context
  • Read text-based files (txt, md, csv, json, code files, etc.)
  • CSV/TSV structured preview with inferred types
  • Head/tail line selection
  • Size and line count metadata
  • XLSX, DOCX and PDF return metadata, a file citation, and code_interpreter_path; use the interpreter to read their contents

Ordinary text reads clamp max_chars to 100–30,000. Head/tail line selection and CSV previews use their own requested line/row counts rather than that character limit. Tail selection takes precedence over head selection; either disables the structured CSV preview.

Supported Extensions: .txt, .md, .csv, .tsv, .json, .jsonl, .xml, .html, .htm, .yaml, .yml, .toml, .ini, .cfg, .conf, .log, .py, .js, .ts, .sh, .bash, .sql, .r, .rs, .go, .java, .c, .cpp, .h, .hpp, .css, .scss

Security: Only reads files from chat's upload directory

Registered by default: Yes

create_file

Description: Create a downloadable file from directly authored text or CSV data. For files produced by Python, or for XLSX, PDF, and images, use code_interpreter.

Parameters:

{
  "type": "object",
  "properties": {
    "filename": {"type": "string", "description": "Output filename with extension (e.g., 'report.md', 'data.csv')"},
    "content": {"type": "string", "description": "File content as text (for all non-CSV formats)"},
    "csv_headers": {"type": "array", "items": {"type": "string"}, "description": "Column headers for CSV output"},
    "csv_rows": {
      "type": "array",
      "items": {"type": "array", "items": {"type": "string"}},
      "description": "Data rows for CSV output"
    }
  },
  "required": ["filename"],
  "additionalProperties": false
}

Features:

  • Create downloadable files from text or structured data
  • Support for the text formats listed below
  • CSV generation with headers and rows
  • Returns download URL

Supported Extensions: .txt, .md, .csv, .tsv, .json, .jsonl, .xml, .yaml, .yml, .toml, .py, .sh, .sql

Supply a plain filename without path separators and either content or, for .csv, csv_headers and optional csv_rows. content is limited to 500,000 characters; that limit does not apply to the structured CSV-writing branch.

Storage: Files are stored as chat-owned generated artifacts. The result includes download_url under /generated-files/<chat-uuid>/... and a code_interpreter_path for reopening the file in later interpreter calls.

Registered by default: Yes

generate_image

Description: Generate images from a text prompt via OpenRouter-backed image generation models (configured default: google/gemini-3.1-flash-lite-image). Images are persisted as chat-owned artifacts and rendered inline in the authenticated chat.

Parameters:

{
  "type": "object",
  "properties": {
    "prompt": {
      "type": "string",
      "description": "Detailed image prompt (subject, scene, composition, lighting, style, constraints; quote exact text to render)"
    },
    "aspect_ratio": {
      "type": "string",
      "enum": [
        "16:9",
        "1:1",
        "1:4",
        "1:8",
        "21:9",
        "2:3",
        "3:2",
        "3:4",
        "4:1",
        "4:3",
        "4:5",
        "5:4",
        "8:1",
        "9:16"
      ],
      "description": "Aspect ratio. Default 1:1. 16:9 for banners, 9:16 for stories."
    },
    "image_size": {
      "type": "string",
      "enum": ["0.5K", "1K", "2K", "4K"],
      "description": "Resolution tier. Default 1K; other tiers require a model that supports them."
    },
    "reference_image_urls": {
      "type": "array",
      "items": {"type": "string"},
      "maxItems": 8,
      "description": "Optional reference image URLs (http(s) or data: URIs) for edit/composition workflows"
    }
  },
  "required": ["prompt"],
  "additionalProperties": false
}

Features:

  • Text-to-image plus reference-based editing/composition (up to 8 reference URLs)
  • Defaults to requesting 1:1 at 1K
  • Returns assistant text plus one or more chat-owned image URLs (/generated-files/<chat-uuid>/<uuid>.<ext>)
  • The enum lists values accepted by the tool; support for the requested ratio and size depends on the configured image model

Configuration (env vars, all optional):

  • IMAGE_GEN_MODEL (default google/gemini-3.1-flash-lite-image)
  • IMAGE_GEN_ASPECT_RATIO (default 1:1)
  • IMAGE_GEN_IMAGE_SIZE (default 1K)
  • IMAGE_GEN_IMAGE_CONFIG_ENABLED (default false): By default, ratio and size are appended as a text instruction. Set this to true to send the provider's image_config object when the selected model supports it.
  • IMAGE_GEN_MAX_PROMPT_CHARS (default 4000)
  • IMAGE_GEN_TIMEOUT_CONNECT (default 10.0)
  • IMAGE_GEN_TIMEOUT_READ (default 120.0)
  • Requires OPENROUTER_API_KEY (shared with other OpenRouter-backed tools)

Prompts longer than IMAGE_GEN_MAX_PROMPT_CHARS are truncated. The tool persists at most four returned images per call.

Cost guardrails: The tool's system hint instructs the agent to only call it when the user explicitly asks for a visual, and to keep the defaults unless the user asks for a banner or HD output.

Registered by default: Yes

Optional integrations

Most of these tools are in the packaged allowlist, but they are disabled for each chat until its group is enabled. Provider credentials, availability checks, and operator allowlist overrides can further restrict them. Bash and Geizhals require explicit operator setup.

Description: Search scholarly literature through OpenAlex, Semantic Scholar, arXiv, Crossref, and PubMed APIs. Public endpoints can rate-limit requests.

Parameters:

{
  "type": "object",
  "properties": {
    "query": {"type": "string"},
    "source": {
      "type": "string",
      "enum": ["arxiv", "auto", "crossref", "openalex", "pubmed", "semantic_scholar"],
      "default": "auto"
    },
    "limit": {"type": "integer", "default": 5, "minimum": 1, "maximum": 10},
    "year": {"type": "string", "description": "2025, inclusive 2022-2026, or >2020 (2021 onward)."},
    "sort": {"type": "string", "enum": ["citations", "date", "newest", "relevance"], "default": "relevance"}
  },
  "required": ["query"],
  "additionalProperties": false
}

With source="auto", the tool chooses an order based on the query and stops at the first provider with results. Year filters apply across providers; arXiv uses submission dates. Citation sorting is unavailable for arXiv/PubMed; explicit requests return an error, while automatic selection skips them. Semantic Scholar date/citation sorting uses bulk search, which matches all query terms, and returns at most the requested limit.

SEMANTIC_SCHOLAR_API_KEY is optional. Set it in the server process environment (or Compose's env_file); this tool reads it directly and a local .env entry alone does not load it.

Registered by default: Yes. Enable the Scientific Research group for the chat.

wiki_lookup

Description: Look up concise Wikipedia summaries or structured Wikidata entity information. No API key is required.

Parameters:

{
  "type": "object",
  "properties": {
    "query": {
      "type": "string",
      "description": "Entity name, concept, scientific topic, or Wikidata Q-ID (e.g. 'CRISPR', 'Q937')."
    },
    "source": {
      "type": "string",
      "enum": ["wikipedia", "wikidata", "auto"],
      "description": "Wikipedia summaries or Wikidata entities. auto selects Wikidata for a Q-ID, otherwise Wikipedia.",
      "default": "wikipedia"
    },
    "language": {
      "type": "string",
      "description": "2-letter Wikipedia language code (default 'en', 'de', 'fr', 'es', etc.).",
      "default": "en"
    },
    "limit": {
      "type": "integer",
      "description": "Number of entities or pages to return (1-5, default 3).",
      "default": 3,
      "minimum": 1,
      "maximum": 5
    }
  },
  "required": ["query"],
  "additionalProperties": false
}

Registered by default: Yes. Enable the Scientific Research group for the chat.

grocery_search_stores

Description: Search for nearby supermarket stores across chains. Returns store IDs, names, and addresses. Supports REWE (PLZ only), Lidl, Kaufland, PENNY, and ALDI Nord. Use the store IDs with grocery_profile to save preferred stores, then grocery_get_offers to fetch their weekly deals.

The schema below shows all five providers. Its chain enum contains only providers whose local availability check passes, plus all. Missing REWE certificates or Kaufland credentials remove those choices.

Parameters:

{
  "type": "object",
  "properties": {
    "chain": {
      "type": "string",
      "enum": ["rewe", "lidl", "kaufland", "penny", "aldi_nord", "all"],
      "description": "Which supermarket chain to search. Use 'all' to search all available chains."
    },
    "query": {
      "type": "string",
      "description": "Search query: city name, postal code (PLZ), or street. REWE only supports postal codes."
    },
    "country": {"type": "string", "description": "Country code, default 'DE'."},
    "latitude": {"type": "number", "description": "Optional latitude for distance-based ranking."},
    "longitude": {"type": "number", "description": "Optional longitude for distance-based ranking."},
    "limit": {"type": "integer", "description": "Max results per chain. Default 10."}
  },
  "required": ["chain", "query"],
  "additionalProperties": false
}

Features:

  • Distance-based store ranking with coordinate inputs
  • Multi-chain unified results
  • Auto-validation against configured provider availability

Registered by default: Yes. Enable the Grocery group for the chat; available chains depend on their provider configuration.

REWE setup: REWE uses the grocery tools above, not the old standalone rewe_search_markets or rewe_get_offers names. Extract its client certificate on the host:

uv run python src/numi_chat/tools/rewpi/get_rewe_certs.py --output-dir src/numi_chat/tools/rewpi

This downloads the REWE app bundle and writes private_rewe.pem and private_rewe.key to that directory. They are gitignored and excluded from the image; Compose mounts them read-only. Treat both as credentials. If REWE starts rejecting the certificate, repeat the extraction with the current app bundle.

Retain grocery_search_stores, grocery_profile, and grocery_get_offers in any custom allowlist, restart Numi, and enable the Grocery group. Ask for stores using a German postal code, save the chosen store through grocery_profile, then request its offers. REWE is unavailable without the certificate files; other configured grocery chains still work.

See the REWE utility documentation for standalone market lookup and offer exports.

Other chains: Kaufland requires KAUFLAND_APP_BASIC_USER and KAUFLAND_APP_BASIC_PASSWORD. Lidl, PENNY, and ALDI Nord have no user-credential requirement in their provider implementations. Lidl settings use the LIDL_ prefix; see Configuration.

grocery_profile

Description: Manage the user's grocery profile: view, add, remove, enable/disable preferred supermarket stores, and set a home location. Saved stores are used automatically by grocery_get_offers.

Parameters:

{
  "type": "object",
  "properties": {
    "action": {
      "type": "string",
      "enum": ["get", "add_store", "remove_store", "enable_store", "disable_store", "set_location"],
      "description": "What to do."
    },
    "chain": {
      "type": "string",
      "description": "Chain name: 'rewe', 'lidl', 'kaufland', 'penny', or 'aldi_nord'. Required for store actions."
    },
    "store_id": {"type": "string", "description": "Store ID from grocery_search_stores. Required for store actions."},
    "label": {"type": "string", "description": "Human-readable store name. Required for add_store."},
    "address": {"type": "string", "description": "Store address. Optional for add_store."},
    "latitude": {"type": "number", "description": "Latitude for set_location."},
    "longitude": {"type": "number", "description": "Longitude for set_location."},
    "location_label": {"type": "string", "description": "Location description for set_location (e.g. 'Lübeck Innenstadt')."}
  },
  "required": ["action"],
  "additionalProperties": false
}

Features:

  • Persistent, user-specific profile storage in SQL database
  • Convenient toggling (enable/disable) of saved stores without full removal
  • Stores home coordinates to support proximity store searches

Registered by default: Yes. Enable the Grocery group for the chat; available chains depend on their provider configuration.

grocery_get_offers

Description: Fetch current (or next week's) weekly offers from saved supermarket stores. Returns all deals in a unified format across REWE, Lidl, Kaufland, PENNY, and ALDI Nord.

Parameters:

{
  "type": "object",
  "properties": {
    "chains": {
      "type": "array",
      "items": {"type": "string", "enum": ["rewe", "lidl", "kaufland", "penny", "aldi_nord"]},
      "description": "Limit to specific chains. Default: all enabled stores in profile."
    },
    "week": {
      "type": "string",
      "enum": ["current", "next"],
      "description": "Which week's offers. Default: 'current'."
    },
    "limit_per_store": {"type": "integer", "description": "Max offers per store. Default 300. Reduce to save tokens."}
  },
  "required": [],
  "additionalProperties": false
}

Features:

  • Aggregates offers from different chains in parallel
  • Sorts and normalizes raw store offers into a cohesive schema
  • Distinguishes between standard price, regular discount, and loyalty app/card price (loyalty_price)

Registered by default: Yes. Enable the Grocery group for the chat; available chains depend on their provider configuration.

Description: Search listings on Kleinanzeigen (German classifieds). Uses the Kleinanzeigen app API.

Parameters:

{
  "type": "object",
  "properties": {
    "query": {"type": "string", "description": "Search keyword(s)."},
    "location": {"type": "string", "description": "Optional city name or location ID; numeric strings are treated as IDs."},
    "radius_km": {"type": "integer", "description": "Distance/radius in km.", "minimum": 1, "maximum": 500},
    "min_price": {"type": "integer", "description": "Minimum EUR price.", "minimum": 0},
    "max_price": {"type": "integer", "description": "Maximum EUR price.", "minimum": 0},
    "sort_by": {"type": "string", "description": "Sort order.", "enum": ["date", "price_asc", "price_desc"]},
    "page_count": {"type": "integer", "minimum": 1, "maximum": 10, "default": 1},
    "start_page": {"type": "integer", "minimum": 1, "maximum": 200, "default": 1},
    "category_id": {"type": "string", "description": "Optional category ID."},
    "category_name": {"type": "string", "description": "Category name."},
    "shipping_only": {"type": "boolean", "description": "Only shippable listings."},
    "max_age_days": {"type": "integer", "description": "Only return listings created in the last N days.", "minimum": 0}
  },
  "required": [],
  "additionalProperties": false
}

Registered by default: Yes. Enable the Shopping group for the chat. Requests require KLEINANZEIGEN_APP_BASIC; missing credentials produce a configuration error when the tool is called.

Setup: Supply your own Kleinanzeigen app API access credentials:

KLEINANZEIGEN_APP_BASIC="user:password"

Keep kleinanzeigen_search, kleinanzeigen_listing, and kleinanzeigen_categories in your allowlist, restart Numi, and enable the Shopping group for the chat. The default API base is https://api.kleinanzeigen.de; override it with KLEINANZEIGEN_API_BASE. Without API access, leave the group disabled. Use TOOLS_DENYLIST to prevent registration.

KLEINANZEIGEN_ENABLED=false is available for the provider's availability check; these tools defer that check at registration, so it should not replace the denylist as an access control.

kleinanzeigen_listing

Description: Get details for one Kleinanzeigen listing or a batch of up to 20 listings. Supply listing_id or listing_ids; a nonempty listing_ids batch takes precedence when both are supplied.

Parameters:

{
  "type": "object",
  "properties": {
    "listing_id": {"type": "string", "description": "Numeric Kleinanzeigen listing ID."},
    "listing_ids": {
      "type": "array",
      "items": {"type": "string"},
      "description": "Batch of listing/ad ids. Max 20.",
      "maxItems": 20
    },
    "image_size": {
      "type": "string",
      "description": "Image URL size.",
      "enum": ["thumbnail", "teaser", "large", "full"],
      "default": "teaser"
    }
  },
  "required": [],
  "additionalProperties": false
}

Registered by default: Yes. Enable the Shopping group for the chat. Requests require KLEINANZEIGEN_APP_BASIC; missing credentials produce a configuration error when the tool is called.

kleinanzeigen_categories

Description: Get available categories of Kleinanzeigen for filtering searches.

Parameters:

{
  "type": "object",
  "properties": {
    "query": {"type": "string", "description": "Category name filter."},
    "limit": {"type": "integer", "minimum": 1, "maximum": 200, "default": 50}
  },
  "required": [],
  "additionalProperties": false
}

Registered by default: Yes. Enable the Shopping group for the chat. Requests require KLEINANZEIGEN_APP_BASIC; missing credentials produce a configuration error when the tool is called.

Description: Search and filter eBay listings. Supports keywords or GTIN, category, price, condition, buying option, shipping, returns, seller country, sorting, and pagination. Output omits images and other verbose API fields.

Key parameters: query, gtin, category_id, min_price, max_price, condition, buying_options, free_shipping, returns_accepted, item_location_country, sort, page, and limit.

Provide at least one of query, gtin, or category_id; query and gtin cannot be combined. Queries are limited to 100 characters and cannot contain *. Prices must be finite and nonnegative, with min_price <= max_price. The calculated offset (page - 1) * limit must not exceed 9,999.

Parameter schema:

{
  "type": "object",
  "properties": {
    "query": {"type": "string"},
    "gtin": {"type": "string"},
    "category_id": {"type": "string"},
    "min_price": {"type": "number", "minimum": 0},
    "max_price": {"type": "number", "minimum": 0},
    "condition": {"type": "string", "enum": ["NEW", "USED", "UNSPECIFIED"]},
    "buying_options": {
      "type": "array",
      "items": {"type": "string", "enum": ["FIXED_PRICE", "AUCTION", "BEST_OFFER"]},
      "uniqueItems": true
    },
    "free_shipping": {"type": "boolean", "default": false},
    "returns_accepted": {"type": "boolean", "default": false},
    "item_location_country": {"type": "string", "minLength": 2, "maxLength": 2},
    "sort": {
      "type": "string",
      "enum": ["best_match", "price", "price_desc", "newly_listed", "ending_soonest"],
      "default": "best_match"
    },
    "page": {"type": "integer", "minimum": 1, "maximum": 1000, "default": 1},
    "limit": {"type": "integer", "minimum": 1, "maximum": 50, "default": 12}
  },
  "required": [],
  "additionalProperties": false
}

Registered by default: Allowlisted, but registered only when eBay is enabled and EBAY_CLIENT_ID and EBAY_CLIENT_SECRET are configured. Enable the eBay group for the chat.

Setup: Configure credentials for an eBay Production application with Browse API access:

EBAY_CLIENT_ID="your-app-id"
EBAY_CLIENT_SECRET="your-cert-id"
EBAY_DELIVERY_POSTAL_CODE="10115"

The postal code is optional. Retain ebay_search and ebay_listing in any custom allowlist, restart Numi, and enable the eBay group. Defaults target EBAY_DE, German responses and delivery, and EUR. The client obtains and caches OAuth application tokens.

EBAY_ENABLED=false disables the integration. Output defaults are EBAY_MAX_SEARCH_RESULTS=50 and EBAY_MAX_DESCRIPTION_CHARS=3000; see Configuration for the other settings.

ebay_listing

Description: Inspect one or multiple eBay listings using REST item IDs returned by ebay_search. Returns compact price, shipping, seller, availability, returns, aspects, and bounded description information without images.

Parameters: item_id for one listing, or item_ids for up to 10 listings in one call. Batched detail lookup uses parallel single-item requests and does not require eBay's restricted bulk scope.

Supply exactly one of item_id or item_ids; neither an empty request nor combining the two forms is accepted.

Parameter schema:

{
  "type": "object",
  "properties": {
    "item_id": {"type": "string", "minLength": 1},
    "item_ids": {
      "type": "array",
      "items": {"type": "string", "minLength": 1},
      "minItems": 1,
      "maxItems": 10,
      "uniqueItems": true
    }
  },
  "required": [],
  "additionalProperties": false
}

Registered by default: Allowlisted, but registered only when eBay is enabled and EBAY_CLIENT_ID and EBAY_CLIENT_SECRET are configured. Enable the eBay group for the chat.

Description: Search or browse MyDealz deals with query, hot/new tab, deal type, group, merchant, price, temperature, online/expiry, and cursor filters. Returns compact deal facts and exact next/previous cursors without images.

With no query or explicit tab, the tool browses hot deals. Use after or before for pagination; the two cursors cannot be combined. Prices must be finite and nonnegative, with min_price <= max_price.

Parameter schema:

{
  "type": "object",
  "properties": {
    "query": {"type": "string", "maxLength": 120},
    "tab": {"type": "string", "enum": ["hot", "new"]},
    "kind": {"type": "string", "enum": ["all", "deals", "discussions", "vouchers"], "default": "all"},
    "group_id": {"type": "integer", "minimum": 1},
    "merchant_id": {"type": "integer", "minimum": 1},
    "min_price": {"type": "number", "minimum": 0},
    "max_price": {"type": "number", "minimum": 0},
    "min_temperature": {"type": "integer"},
    "only_non_expired": {"type": "boolean", "default": true},
    "online_only": {"type": "boolean", "default": false},
    "after": {"type": "string"},
    "before": {"type": "string"},
    "limit": {"type": "integer", "minimum": 1, "maximum": 30, "default": 12}
  },
  "required": [],
  "additionalProperties": false
}

Registered by default: Yes; enabled per chat through the MyDealz group.

Setup: No user credentials are required. Retain mydealz_search, mydealz_deal, and mydealz_discover in any custom allowlist, then enable the MyDealz group for the chat.

Output limits default to MYDEALZ_MAX_SEARCH_RESULTS=30, MYDEALZ_MAX_DESCRIPTION_CHARS=2400, and MYDEALZ_MAX_COMMENT_CHARS=600. Set MYDEALZ_ENABLED=false and restart to disable registration.

mydealz_deal

Description: Inspect one deal by ID with a bounded description and optionally up to ten compact comments. Community text is treated as untrusted content.

Parameter schema:

{
  "type": "object",
  "properties": {
    "deal_id": {"type": "integer", "minimum": 1},
    "comments_limit": {
      "type": "integer",
      "minimum": 0,
      "maximum": 10,
      "default": 0,
      "description": "0 omits comments; use 3-5 for community sentiment/caveats. Comments are not a merchant rating."
    },
    "comment_sort": {"type": "string", "enum": ["newest", "oldest"], "default": "newest"}
  },
  "required": ["deal_id"],
  "additionalProperties": false
}

Registered by default: Yes; enabled per chat through the MyDealz group.

mydealz_discover

Description: Discover group IDs, category/search suggestions, or popular keywords before calling mydealz_search.

mode="suggestions" requires a nonempty query. groups uses literal name/slug substring matching; popular_keywords needs no query.

Parameter schema:

{
  "type": "object",
  "properties": {
    "mode": {"type": "string", "enum": ["groups", "popular_keywords", "suggestions"]},
    "query": {"type": "string", "maxLength": 100},
    "limit": {"type": "integer", "minimum": 1, "maximum": 30, "default": 12}
  },
  "required": ["mode"],
  "additionalProperties": false
}

Registered by default: Yes; enabled per chat through the MyDealz group.

Description: Search for products on Geizhals price comparison. Requires the Geizhals sidecar server to be running.

Parameters:

{
  "type": "object",
  "properties": {
    "query": {
      "type": "string",
      "description": "Product keywords.",
      "minLength": 1
    },
    "limit": {
      "type": "integer",
      "description": "Max results (default 8, max 25).",
      "minimum": 1,
      "maximum": 25,
      "default": 8
    }
  },
  "required": [
    "query"
  ],
  "additionalProperties": false
}

Registered by default: No. Append all three Geizhals tool names to the allowlist and enable the Price Search group.

Setup: Run the separate Geizhals API service using its own installation instructions.

In Numi's configuration, append geizhals_search, geizhals_variant, and geizhals_product to the existing allowlist. The client defaults to http://127.0.0.1:8000; set TOOLS_GEIZHALS_API_BASE_URL in the server process environment to the sidecar address reachable from Numi. A local .env entry alone does not load this direct environment setting. In Docker, Compose's env_file injects it, and container localhost does not refer to a service on the host.

Restart Numi and enable the Price Search group. The sidecar must return {"status": "ok"} at /health for its availability check. Discovery defers that check, so allowlisted tools can remain registered while the sidecar is offline; calls will fail until it is reachable. Use the denylist to block access. TOOLS_GEIZHALS_ENABLED controls the availability probe, not registration or each execution.

geizhals_variant

Description: Get specific price and retailer details for a product variant.

Parameters:

{
  "type": "object",
  "properties": {
    "variant_id": {
      "type": "integer",
      "description": "Variant ID from search or -v URL."
    },
    "ratings_limit": {
      "type": "integer",
      "minimum": 1,
      "maximum": 20,
      "default": 5,
      "description": "Number of user reviews to fetch."
    },
    "specs_limit": {
      "type": "integer",
      "minimum": 0,
      "maximum": 30,
      "default": 0,
      "description": "Max specs to include (0 = omit specs)."
    },
    "loc": {
      "type": "string",
      "default": "de",
      "description": "Locale for ratings: de or at."
    }
  },
  "required": [
    "variant_id"
  ],
  "additionalProperties": false
}

Registered by default: No

geizhals_product

Description: Get detailed product specs and listing overview on Geizhals.

Parameters:

{
  "type": "object",
  "properties": {
    "product_id": {
      "type": "integer",
      "description": "Product ID from search or -a URL."
    },
    "days": {
      "type": "integer",
      "minimum": 1,
      "maximum": 365,
      "default": 31,
      "description": "Price history lookback in days."
    },
    "loc": {
      "type": "string",
      "default": "de",
      "description": "Locale: de or at."
    },
    "offers_limit": {
      "type": "integer",
      "minimum": 1,
      "maximum": 10,
      "default": 5,
      "description": "Max cheapest offers to return."
    },
    "specs_limit": {
      "type": "integer",
      "minimum": 0,
      "maximum": 30,
      "default": 10,
      "description": "Max specs to include (0 = omit)."
    }
  },
  "required": [
    "product_id"
  ],
  "additionalProperties": false
}

Registered by default: No

Description: Search Chefkoch for tested German recipes by keyword. Returns compact summaries with id, title, rating, times, and url. Use chefkoch_recipe for full ingredients and steps.

No user credentials are required. TOOLS_CHEFKOCH_ENABLED=false makes the provider's availability check fail; these tools defer that check during discovery. Use the denylist to block registration. This switch is read from the process environment, not directly from a local .env file.

Parameters:

{
  "type": "object",
  "properties": {
    "query": {"type": "string", "description": "Recipe search keyword(s)."},
    "limit": {"type": "integer", "description": "Max results. Default 10, max 25.", "default": 10},
    "offset": {"type": "integer", "description": "Pagination offset.", "default": 0},
    "maximum_time": {"type": "integer", "description": "Max total preparation time in minutes."},
    "minimum_rating": {"type": "number", "description": "Minimum average rating (1-5)."},
    "has_image": {"type": "boolean", "description": "Only recipes with photos."},
    "tags": {"type": "string", "description": "Comma-separated Chefkoch tag filter."}
  },
  "required": ["query"],
  "additionalProperties": false
}

Registered by default: Yes. Enable the Recipes group for the chat.

chefkoch_recipe

Description: Fetch full Chefkoch recipe details by recipe id. Returns servings, times, ingredients, instructions, tips, and url.

Supply recipe_id or recipe_ids. A nonempty batch takes precedence if both are supplied; requests with no IDs fail.

Parameters:

{
  "type": "object",
  "properties": {
    "recipe_id": {"type": "string", "description": "One Chefkoch recipe id from search results."},
    "recipe_ids": {
      "type": "array",
      "items": {"type": "string"},
      "description": "Batch of recipe ids. Max 5.",
      "maxItems": 5
    }
  },
  "required": [],
  "additionalProperties": false
}

Registered by default: Yes. Enable the Recipes group for the chat.

chefkoch_recipe_of_the_day

Description: Fetch Chefkoch recipe-of-the-day entries as compact summaries with promoted_on, id, title, rating, times, and url.

Parameters:

{
  "type": "object",
  "properties": {
    "limit": {"type": "integer", "description": "Max entries. Default 3, max 10.", "default": 3},
    "start_date": {"type": "string", "description": "Start date filter (YYYY-MM-DD)."},
    "end_date": {"type": "string", "description": "End date filter (YYYY-MM-DD)."},
    "form_of_nutrition": {"type": "integer", "description": "Optional nutrition form filter id."}
  },
  "required": [],
  "additionalProperties": false
}

Registered by default: Yes. Enable the Recipes group for the chat.

bash_tool

Description: Executes shell commands directly in the host environment with working-directory persistence across calls. Only enable on a single-user instance you fully control.

Parameters:

{
  "type": "object",
  "properties": {
    "command": {
      "type": "string",
      "description": "Shell command to run."
    },
    "timeout": {"type": "integer", "description": "Execution timeout in seconds."},
    "description": {"type": "string", "description": "Short summary of the command."}
  },
  "required": ["command"],
  "additionalProperties": false
}

Registered by default: No. Its exact allowlist name is bash_tool. It is also absent from the web app's core/custom tool sets, so allowlisting alone does not expose it to a chat. Follow the custom-tool integration steps if you deliberately want to make it selectable. TOOLS_BASH_ENABLED=false disables it; Windows hosts are unsupported.

Setup: For a deliberate personal deployment, append bash_tool to the allowlist and to CUSTOM_TOOL_NAMES, restart Numi, and enable Other for the chat. This exposes host shell execution with the server's filesystem, process, and network permissions. It uses no sandbox; the command denylist is not an isolation boundary. Calls run serially within an execution batch.

timeout defaults to 120 seconds and is clamped to 1–600 seconds; zero uses the default. TOOLS_BASH_TIMEOUT changes the default. Bash settings are read from the process environment, so export them before starting Numi locally or provide them through Compose's env_file.

get_youtube_transcript

Description: Get transcript and metadata from YouTube videos. Not registered as a standalone tool - use fetch_web_page with YouTube URLs instead.

Note: This tool is not directly exposed to the LLM. YouTube URLs passed to fetch_web_page are automatically routed to this tool via YouTubeUrlHandler.

Parameters:

{
  "type": "object",
  "properties": {
    "url": {"type": "string", "description": "YouTube video URL or ID"},
    "languages": {
      "type": "array",
      "items": {"type": "string"},
      "description": "Preferred transcript languages in priority order (e.g., ['de', 'en'])",
      "default": ["de", "en"]
    },
    "include_description": {"type": "boolean", "description": "Include the video description (truncated)", "default": false},
    "include_counts": {
      "type": "boolean",
      "description": "Include view/like/comment/channel follower counts",
      "default": false
    }
  },
  "required": ["url"],
  "additionalProperties": false
}

Features:

  • Fetches video metadata via yt-dlp
  • Fetches transcripts via youtube-transcript-api
  • Auto-selects transcript from available languages
  • Returns structured metadata and transcript text

Dependencies:

  • yt-dlp package
  • youtube-transcript-api package

Note: YouTube frequently changes their systems, so this tool may occasionally fail. Try upgrading yt-dlp if issues occur.

Registered by default: No (used internally by fetch_web_page)

Tool Configuration

Tool Allowlist/Denylist

Tools can be configured using environment variables:

TOOLS_ALLOWLIST

Comma-separated list of tool names allowed at startup. The default is:

web_search,fetch_web_page,weather_fetch,code_interpreter,remember_fact,forget_fact,
view,create_file,generate_image,grocery_search_stores,grocery_profile,
grocery_get_offers,chefkoch_search,chefkoch_recipe,chefkoch_recipe_of_the_day,
kleinanzeigen_search,kleinanzeigen_listing,kleinanzeigen_categories,
academic_search,wiki_lookup,ebay_search,ebay_listing,mydealz_search,mydealz_deal,
mydealz_discover

Optional tools also need their chat group activated. TOOLS_DENYLIST blocks a tool even when it is allowlisted; its default is empty.

Rules:

  1. Denylist match → blocked.
  2. Missing allowlist match → blocked, except request_tool_activation.
  3. Availability checks may omit a tool; tools that defer this check report setup errors when called.

Tool Discovery

Tools are auto-discovered by the ToolRegistry at startup based on:

  1. Python files directly in src/numi_chat/tools/ are imported.
  2. Concrete tool classes are instantiated without arguments.
  3. The allowlist, denylist, and availability policy determine registration.

New tools should inherit BaseTool. register_in_registry = False excludes helper classes. A new optional tool must also be listed in CUSTOM_TOOL_NAMES to be selectable in the web app; see Adding Custom Tools.

Tool Availability

During discovery, ToolRegistry checks the allowlist, denylist, and each tool's availability policy. Core tools such as code_interpreter remain registered when optional runtime setup is missing and return a structured error from execute(). Custom integrations may be omitted when credentials or a sidecar are unavailable.

Tool Execution

ToolExecutorService checks the chat's active tool names and supplies server-owned chat/user context. Concurrency-safe tools use a thread pool; tools with concurrency_safe = False run serially within a batch. Execution admission, output size, and the agent deadline are bounded. A running Python call is not forcibly stopped at the deadline, so network tools must set their own request timeouts.

Schema Modes

From config, TOOLS_SCHEMA_MODE="compact":

  • compact: Truncate descriptions to TOOLS_SCHEMA_DESCRIPTION_MAX_LEN
  • verbose: Keep full descriptions

TOOLS_SCHEMA_STRIP_PARAM_DESCRIPTIONS defaults to false; set it to true only when provider token budgets require smaller schemas.

Tool Schema

BaseTool Abstract Class

All tools inherit from the BaseTool abstract class with these properties:

name

  • Type: String
  • Description: The name of the tool (must be unique)
  • Required: Yes

description

  • Type: String
  • Description: A description of what the tool does
  • Required: Yes

parameters

  • Type: Dict[str, Any]
  • Description: The JSON schema for the tool's parameters
  • Required: Yes

execute()

  • Type: Method
  • Description: Execute the tool's logic with given parameters
  • Returns: Use the response helpers below to return a JSON string

is_available()

  • Type: Function
  • Description: Check if the tool can run in the current environment
  • Returns: Boolean

availability_reason()

  • Type: Optional method returning str | None
  • Description: Human-readable reason used when a tool is skipped during discovery
  • Default: Not defined by BaseTool; implement it when useful

Tool Reminders (system_hint)

  • Declaration: @tool_system_hints({...}) decorator on the tool class
  • Purpose: Output-only reminders that steer the model during tool workflows
  • Injection point: Added to tool result payload as system_hint
  • Not included in: Function schema/definition

Response Methods

Tools have three response methods:

  • _ok(): Success response with data
  • _noop(): No operation response
  • _error(): Error response

Tool Schema Format

Tools are converted to OpenAI-compatible function schemas:

{
  "type": "function",
  "function": {
    "name": "tool_name",
    "description": "Tool description",
    "parameters": {
      "type": "object",
      "properties": {
        "param_name": {
          "type": "string",
          "description": "Parameter description"
        }
      },
      "required": ["param_name"]
    }
  }
}

URL Handlers

fetch_web_page delegates per-URL fetching to a small handler registry defined in src/numi_chat/tools/url_handlers.py. Each handler matches a URL pattern and knows how to retrieve content from that source.

UrlHandler ABC

class UrlHandler(ABC):
    def matches(self, url: str) -> bool: ...
    def fetch(self, url: str, **options: Any) -> dict[str, Any]: ...

fetch() must return a dict with at least url, title, and text keys. Add an error key on failure.

Built-in handlers

Handler Matches Implementation
YouTubeUrlHandler youtube.com, youtu.be URLs YouTubeTranscriptTool (transcript + metadata)
ArxivUrlHandler arxiv.org, export.arxiv.org, ar5iv.labs.arxiv.org arXiv Atom API metadata + PDF text extraction via pypdf
PdfUrlHandler Direct .pdf URLs, /pdf/ endpoints Multi-page structured PDF extraction via pypdf (25MB limit)
### Adding a custom handler

Implement the UrlHandler contract and call register_handler(instance) before requests are handled. See src/numi_chat/tools/url_handlers.py and tests/test_url_handlers.py for the existing pattern. Match parsed hostnames and preserve the existing URL validation and bounded-fetch behavior.

Tool Best Practices

Validate model-supplied inputs inside execute; a JSON schema is not a runtime authorization check. Use the server-supplied chat/user context for ownership checks, give external requests timeouts, and keep output bounded. Custom Python tools run with the server's permissions unless their implementation provides its own sandbox.

Tool Examples

Example: Using web_search tool

{
  "name": "web_search",
  "parameters": {
    "query": ["Python async await", "Python coroutines tutorial"]
  }
}

Illustrative response excerpt:

{
  "status": "success",
  "data": {
    "query": ["Python async await", "Python coroutines tutorial"],
    "results": [
      {
        "query": "Python async await",
        "title": "Async IO in Python: A Complete Walkthrough",
        "url": "https://realpython.com/async-io-python/",
        "snippet": "Async IO is a concurrent programming design..."
      }
    ]
  }
}

Example: Using fetch_web_page with YouTube

{
  "name": "fetch_web_page",
  "parameters": {
    "urls": [
      "https://www.youtube.com/watch?v=dQw4w9WgXcQ",
      "https://example.com/article"
    ]
  }
}

The YouTube URL will be handled by the native transcript handler, while the other URL uses local extraction when enabled and otherwise uses Parallel.

Example: Using code_interpreter tool

{
  "name": "code_interpreter",
  "parameters": {
    "code": "import matplotlib.pyplot as plt\nimport pandas as pd\ndf = pd.DataFrame({'x': [1, 2, 3], 'y': [4, 5, 6]})\nsave_file('data.csv', df.to_csv(index=False))\nplt.plot(df['x'], df['y'])\nplt.show()"
  }
}

Illustrative response (generated filenames vary):

{
  "status": "success",
  "output": "",
  "files": [
    "/generated-files/11111111-1111-4111-8111-111111111111/abc123.png",
    "/generated-files/11111111-1111-4111-8111-111111111111/def456_data.csv"
  ],
  "file_map": {
    "abc123.png": "/generated-files/11111111-1111-4111-8111-111111111111/abc123.png",
    "data.csv": "/generated-files/11111111-1111-4111-8111-111111111111/def456_data.csv"
  },
  "data": {
    "status": "success",
    "output": "",
    "files": [
      "/generated-files/11111111-1111-4111-8111-111111111111/abc123.png",
      "/generated-files/11111111-1111-4111-8111-111111111111/def456_data.csv"
    ],
    "execution_time_seconds": 1.24,
    "error": null,
    "file_map": {
      "abc123.png": "/generated-files/11111111-1111-4111-8111-111111111111/abc123.png",
      "data.csv": "/generated-files/11111111-1111-4111-8111-111111111111/def456_data.csv"
    },
    "code_interpreter_file_map": {
      "abc123.png": "/mnt/data/generated/abc123.png",
      "data.csv": "/mnt/data/generated/def456_data.csv"
    }
  },
  "code_interpreter_file_map": {
    "abc123.png": "/mnt/data/generated/abc123.png",
    "data.csv": "/mnt/data/generated/def456_data.csv"
  }
}