工具 — 基础

🔵 所有工具的通用机制: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。

设置块的方式

  1. 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 块。

  2. visual —— 顶层字段,基于原始 extracted 渲染(不是 content),直接进入 UI,而 content 保持为紧凑 摘要:

    output:
      count: "{{ extracted.items | length }}"   # компактная сводка → LLM
    visual:
      - type: table
        rows: "{{ extracted.items | tojson }}"  # полные данные → только UI
    
  3. 专用引擎 —— chart_tool / table_tool / visualize_tool 会自行填充 content(摘要)和 visual(完整块)。脚本类工具通过 run(args) 中的 visual 键来设置(见 07-scripts)。

  4. 模型响应中的 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 模式)