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
-
ui.result_blocks/ui.chart/ui.table— declarative templates on top ofextracted:ui: result_blocks: - type: text markdown: "Найдено **{{ extracted.count }}**" - type: table columns: [{ key: model, title: "Модель" }] rows: "{{ extracted.items | tojson }}"ui.chart:andui.table:are shorthands for a single block;result_template:is onetextblock. -
visual— a top-level field, rendered on top of the rawextracted(notcontent), goes directly to the UI, whilecontentremains a compact summary:output: count: "{{ extracted.items | length }}" # компактная сводка → LLM visual: - type: table rows: "{{ extracted.items | tojson }}" # полные данные → только UI -
Specialized engines —
chart_tool/table_tool/visualize_toolthemselves populatecontent(summary) andvisual(full block). Script tools setvisualwith a key inrun(args)(see 07-scripts). -
Fenced blocks in the model's response —
```chart {…}and```tablein 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) |