工具 — 基础
🔵 所有工具的通用机制:YAML 字段、MiniJinja 模板、可视化 块、内置引擎。所有引擎的汇总表见 «工具参考»。
5.1 通用字段
每个工具 YAML 都共享通用的顶层字段:
| 字段 | 描述 |
|---|---|
kind |
引擎判别符(必填) |
name |
LLM 调用的工具名称(默认为文件名) |
description |
向 LLM 展示的用于 function-calling 的文本 |
parameters |
工具参数的 JSON Schema |
ui |
在组件中渲染:icon、call_template、result_template、failure_template,外加 result_blocks / chart / table |
visual |
基于原始 extracted 的 UI-only 载荷(有序块列表)——展示给用户,永远不会进入 LLM 上下文 |
warnings |
声明式告警规则,在追踪中弹出 |
idempotency |
副作用去重(auto / { window_secs }),用于支付/CRM 类工具 |
ui
ui:
icon: 🚗
call_template: "Ищу {{ input.query }}..." # до выполнения
result_template: "{{ extracted.total }} найдено" # после успеха
failure_template: "⚠️ {{ error }}" # после ошибки
模板为 MiniJinja。call_template
看到 input;result_template 看到 extracted。失败时
(success: false)会跳过 result_template,而 failure_template(如果
已设置)会以 error(消息)、status(HTTP 状态码,如果有)、
extracted(解析后的错误体,如果是 JSON)、content(原始内容)和
success: false 进行渲染。没有 failure_template 时显示原始错误文本。
可视化块(图表和表格)
工具可以返回可视化层——一个有序的类型化
块列表(text、chart、table、file、map)。这些块会在 Web 组件
(Chart.js + Leaflet 地图 + 表格 + 文件下载链接)、Android
(Vico + Compose + 地图链接)和 Telegram(PNG + 表格 + 文档 + 链接)中渲染。
两个层:给 LLM 的数据和给 UI 的展示
工具结果服务于两个消费者,不能将它们混在一起:
| 层 | 给谁 | 承载什么 |
|---|---|---|
content(字符串) |
LLM | 结果的紧凑摘要 |
visual(可选) |
UI | 完整数据 / 现成块,绕过 LLM |
result_blocks |
客户端 | 通过网络发送的有序块 |
content 仍然会进入模型上下文;visual 只进入 UI,
永远不会进入 LLM。客户端从 visual 或从
content + ui 模板计算 result_blocks。
设置块的方式
-
ui.result_blocks/ui.chart/ui.table—— 基于extracted的声明式 模板:ui: result_blocks: - type: text markdown: "Найдено **{{ extracted.count }}**" - type: table columns: [{ key: model, title: "Модель" }] rows: "{{ extracted.items | tojson }}"ui.chart:和ui.table:是单个块的简写;result_template:是 一个text块。 -
visual—— 顶层字段,基于原始extracted渲染(不是content),直接进入 UI,而content保持为紧凑 摘要:output: count: "{{ extracted.items | length }}" # компактная сводка → LLM visual: - type: table rows: "{{ extracted.items | tojson }}" # полные данные → только UI -
专用引擎 ——
chart_tool/table_tool/visualize_tool会自行填充content(摘要)和visual(完整块)。脚本类工具通过run(args)中的visual键来设置(见 07-scripts)。 -
模型响应中的 Fenced 块 —— markdown 响应中的
```chart {…}和```table会被客户端解析。
块格式
chart 块承载 spec:
{
"type": "bar", // bar | line | area | pie | scatter
"title": "Цены",
"labels": ["Q1", "Q2", "Q3", "Q4"],
"series": [ { "name": "Цена", "values": [10, 20, 15, 30] } ],
"options": { "stacked": false }
}
table 块承载 columns + rows(对象数组),可选 title、
page_info 和 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 }
}
file 块承载用于下载的文件 —— name(文件名)、mime 和 data
(base64 内容)。由引擎 file_tool 创建:
{
"type": "file",
"name": "report.html",
"mime": "text/html",
"data": "PGgxPkhpPC9oMT4="
}
map 块承载带标记的地图 —— center(中心),可选 zoom、
markers(lat/lon/label)、瓦片源 tile_url(+ attribution、
subdomains)以及为没有交互式地图的渠道准备的现成 link。由
引擎 map_tool 创建:
{
"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"
}
Web 组件渲染交互式 Leaflet 地图;Telegram / VK / Android
显示链接(link)。
懒加载分页(continuation)
table 块可以承载 page_info 和 continuation —— 客户端通过
POST /v1/continuations 携带 id 请求下一页,服务器会用保存的参数重新调用
工具(不经过 LLM 轮次)。Web —— 无限滚动,
Telegram —— «Load more» 按钮。第一页始终渲染;失效的 id
只会禁用后续加载。
warnings
warnings:
- contains: '"total_server": 0' # подстрока в сериализованном выводе
message: "Нет результатов"
- failed: true # срабатывает при success=false
message: "Инструмент упал"
触发的告警在追踪中可见(trace_sessions、trace_search)。
idempotency
对于有副作用的工具(支付、写入 CRM、提交文档), 失败后重复执行会产生重复。只读和生成型 工具不需要去重,甚至有害,因此它是可选的。
idempotency: auto # дедуп по hash аргументов + session
# или с явным окном (сек):
idempotency:
window_secs: 86400
当 auto 时,内核从 auto:<tool>:<session>:<args_hash> 推导出键,并在
window_secs 范围内返回保存的结果,而不重新执行工具。
显式键可以通过调用传递:call_tool(name, args, idempotency_key) в скриптах или полем idempotency_key у tool 步骤
的工作流。存储在 server.yaml 中配置(logging.side_effect_db_path,
空 = 禁用)。
5.2 模板和变量
工具在模板字段中使用 MiniJinja(Jinja2 的子集):
url、请求头、请求体、call_template / result_template /
failure_template、output、extract 中的指针、summary、computed。
上下文变量
| 变量 | 在哪里可用 | 值 |
|---|---|---|
input |
fetch(url/headers/body)、extract(指针)、call_template、output |
工具调用的参数 |
extracted |
result_template、output、visual |
提取的字段 |
item |
json_tool.computed |
结果的当前元素 |
count |
summary(chart/table/visualize) |
元素数量 |
error / status / content / extracted |
failure_template |
错误详情(消息、HTTP 状态码、原始响应体、解析后的响应体) |
env_var('NAME') |
到处(函数) | 进程的环境变量值 |
MiniJinja 内置过滤器
| 过滤器 | 描述 |
|---|---|
slugify |
转为小写,非字母数字字符 → _ |
transliterate |
西里尔字母 → 拉丁字母(用于 slug) |
urlencode |
URL 的 percent-encoding |
tojson |
将值序列化为正确的 JSON(true/false/null,引号) |
strip_html |
移除 HTML/XML 标签 |
fromunixtime |
unix 时间 → 日期字符串 |
来自 filters: 的自定义过滤器 |
键→值表(lookup),带 case_insensitive 和 default |
渲染后:MiniJinja 将 null/布尔值输出为 None/True/False —— 引擎
会将它们转换回正确的 JSON null/true/false。
智能体指令中的变量
在智能体的 instructions: 中,可以使用由内核在创建
会话时填充的替换项(以大写形式写在双下划线中):
| 占位符 | 值 |
|---|---|
__DATE__ / __WEEKDAY__ / __TIME__ |
当前日期 / 星期几 / 时间(服务器本地时间) |
__GEOIP__ |
用户所在城市(通过 IP 解析;如果无法确定则为 unknown) |
__COUNTRY__ / __COUNTRY_CODE__ |
国家 / 国家代码 |
__USER_ID__ |
用户 id |
__ADMIN_TEMPLATES__ |
管理员可见范围(* 或模板列表) |
来自 drom-agent 的示例:User's city: __GEOIP__. If user doesn't specify region, use this city.
5.3 内置工具
实现已在核心中,无需 YAML —— 只需在 agent.yaml 的 tools: 中列出名称。
| 名称 | 用途 | 关键参数 |
|---|---|---|
http_fetch |
下载 URL | url、max_chars(默认 8000,0=全部)、start_char |
fs_read |
读取文件 | path、max_chars、start_char |
job_done |
完成信号 | reason(或 output_schema 模式) |