Tools — fundamentals

🔵 Common mechanics of all tools: YAML fields, MiniJinja templates, visual blocks, built-in engines. For a summary table of all engines, see "Tools reference".

5.1 Common fields

Every tool YAML shares common top-level fields:

Field Description
kind engine discriminator (required)
name tool name that the LLM calls (defaults to the file name)
description text shown to the LLM for function-calling
parameters JSON Schema of the tool's arguments
ui rendering in the widget: icon, call_template, result_template, failure_template, plus result_blocks / chart / table
visual UI-only payload (ordered list of blocks) on top of the raw extracted — shown to the user, never enters the LLM context
warnings declarative warning rules that surface in traces
idempotency deduplication of side effects (auto / { window_secs }) for payment/CRM tools

ui

ui:
  icon: 🚗
  call_template: "Ищу {{ input.query }}..."      # до выполнения
  result_template: "{{ extracted.total }} найдено"  # после успеха
  failure_template: "⚠️ {{ error }}"                # после ошибки

Templates use MiniJinja. call_template sees input; result_template sees extracted. On failure (success: false), result_template is skipped, and failure_template (if set) is rendered with error (message), status (HTTP status, if any), extracted (parsed error body, if JSON), content (raw), and success: false. Without failure_template, the raw error text is shown.

Visual blocks (charts and tables)

A tool can return a visual layer — an ordered list of typed blocks (text, chart, table, file, map). Blocks are rendered in the web widget (Chart.js + Leaflet map + tables + file download link), Android (Vico + Compose + map link), and Telegram (PNG + table + document + link).

Two layers: data for the LLM and presentation for the UI

A tool's result serves two consumers, and they must not be mixed:

Layer For whom What it carries
content (string) LLM compact summary of the result
visual (optional) UI full data / ready-made blocks, bypassing the LLM
result_blocks client ordered blocks sent over the wire

content still goes into the model's context; visual goes only to the UI and never to the LLM. The client computes result_blocks either from visual or from content + ui templates.

Ways to define blocks

  1. ui.result_blocks / ui.chart / ui.table — declarative templates on top of extracted:

    ui:
      result_blocks:
        - type: text
          markdown: "Найдено **{{ extracted.count }}**"
        - type: table
          columns: [{ key: model, title: "Модель" }]
          rows: "{{ extracted.items | tojson }}"
    

    ui.chart: and ui.table: are shorthands for a single block; result_template: is one text block.

  2. visual — a top-level field, rendered on top of the raw extracted (not content), goes directly to the UI, while content remains a compact summary:

    output:
      count: "{{ extracted.items | length }}"   # компактная сводка → LLM
    visual:
      - type: table
        rows: "{{ extracted.items | tojson }}"  # полные данные → только UI
    
  3. Specialized engines — chart_tool / table_tool / visualize_tool themselves populate content (summary) and visual (full block). Script tools set visual with a key in run(args) (see 07-scripts).

  4. Fenced blocks in the model's response — ```chart {…} and ```table in the markdown response are parsed by the client.

Block format

A chart block carries spec:

{
  "type": "bar",            // bar | line | area | pie | scatter
  "title": "Цены",
  "labels": ["Q1", "Q2", "Q3", "Q4"],
  "series": [ { "name": "Цена", "values": [10, 20, 15, 30] } ],
  "options": { "stacked": false }
}

A table block carries columns + rows (array of objects), optionally title, page_info, and options (sortable/searchable/paged):

{
  "type": "table",
  "columns": [
    { "key": "model", "title": "Модель", "align": "left" },
    { "key": "price", "title": "Цена",  "align": "right" }
  ],
  "rows": [ { "model": "A", "price": 100 } ],
  "page_info": { "returned": 50, "total": 500, "has_more": true },
  "options": { "sortable": true, "searchable": true }
}

A file block carries a file for download — name (file name), mime, and data (base64 content). Created by the file_tool engine:

{
  "type": "file",
  "name": "report.html",
  "mime": "text/html",
  "data": "PGgxPkhpPC9oMT4="
}

A map block carries a map with markers — center (center), optionally zoom, markers (lat/lon/label), tile source tile_url (+ attribution, subdomains), and a ready-made link for channels without an interactive map. Created by the map_tool engine:

{
  "type": "map",
  "title": "Академгородок",
  "center": { "lat": 54.85, "lon": 83.10 },
  "zoom": 14,
  "markers": [ { "lat": 54.85, "lon": 83.10, "label": "Академ" } ],
  "tile_url": "https://tile.openstreetmap.org/{z}/{x}/{y}.png",
  "attribution": "© OpenStreetMap contributors",
  "link": "https://www.openstreetmap.org/?mlat=54.85&mlon=83.10#map=14/54.85/83.10"
}

The web widget renders an interactive Leaflet map; Telegram / VK / Android show a link (link).

Lazy pagination (continuation)

A table block can carry page_info and continuation — the client requests the next page via POST /v1/continuations with an id, and the server re-invokes the tool with the saved arguments (without an LLM round). Web — infinite scroll, Telegram — a "Load more" button. The first page is always rendered; a dead id simply disables loading more.

warnings

warnings:
  - contains: '"total_server": 0'   # подстрока в сериализованном выводе
    message: "Нет результатов"
  - failed: true                    # срабатывает при success=false
    message: "Инструмент упал"

Triggered warnings are visible in traces (trace_sessions, trace_search).

idempotency

For side-effecting tools (payment, CRM write, document submission), re-execution after a failure produces a duplicate. Read-only and generative tools don't need deduplication and it's even harmful, so it's optional.

idempotency: auto                # дедуп по hash аргументов + session
# или с явным окном (сек):
idempotency:
  window_secs: 86400

With auto, the core derives a key from auto:<tool>:<session>:<args_hash> and, within window_secs, returns the saved result without executing the tool again. An explicit key can be passed by calling: call_tool(name, args, idempotency_key) в скриптах или полем idempotency_key у tool step of a workflow. The store is configured in server.yaml (logging.side_effect_db_path, empty = disabled).


5.2 Templates and variables

Tools use MiniJinja (a subset of Jinja2) in template fields: url, headers, request body, call_template / result_template / failure_template, output, pointers in extract, summary, computed.

Context variables

Variable Where available Value
input fetch (url/headers/body), extract (pointers), call_template, output tool call arguments
extracted result_template, output, visual extracted fields
item json_tool.computed current result item
count summary (chart/table/visualize) number of items
error / status / content / extracted failure_template error details (message, HTTP status, raw body, parsed body)
env_var('NAME') everywhere (function) process environment variable value

Built-in MiniJinja filters

Filter Description
slugify to lowercase, non-alphanumeric → _
transliterate Cyrillic → Latin (for slug)
urlencode percent-encoding for URLs
tojson serialize value to valid JSON (true/false/null, quotes)
strip_html strip HTML/XML tags
fromunixtime unix time → date string
custom from filters: key→value table (lookup) with case_insensitive and default

Post-render: MiniJinja outputs null/booleans as None/True/False — the engine converts them back to valid JSON null/true/false.

Variables in agent instructions

In instructions: the agent has access to substitutions filled in by the core when creating a session (written in uppercase with double underscores):

Placeholder Value
__DATE__ / __WEEKDAY__ / __TIME__ current date / day of week / time (server local)
__GEOIP__ user's city (resolved by IP; unknown if not determined)
__COUNTRY__ / __COUNTRY_CODE__ country / country code
__USER_ID__ user id
__ADMIN_TEMPLATES__ admin visibility scope (* or list of templates)

Example from drom-agent: User's city: __GEOIP__. If user doesn't specify region, use this city.


5.3 Built-in tools

The implementation is already in the core, no YAML needed — just list the name in tools: in agent.yaml.

Name Purpose Key parameters
http_fetch download URL url, max_chars (default 8000, 0=all), start_char
fs_read read file path, max_chars, start_char
job_done completion signal reason (or output_schema schema)