工具 — 状态、数据库与可视化

🔵 状态存储(scoped store、DuckDB SQL 数据库)与可视化块(图表/表格)的生成。所有引擎的汇总表见 《工具参考》。

5.11 state_tool

带作用域的状态存储。kind: state_tool 为 agent 打开内核的 scoped state store。每个 YAML 将一个操作(op)绑定到一个作用域 + 键——从而赋予 agent 一个狭窄、有针对性的能力(例如「只 读取 user/name」)。

具体的作用域 id(session/user/template)在运行时从调用工具的上下文中解析—— LLM 永远看不到他人的 id。

字段 必填 描述
kind 是 state_tool
name 是 给 LLM 的名称
description 是 给 LLM 的描述
op 是 一个操作(见下表)
scope 是 user | session | agent | team:<id>
key 是 存储键
key_param 否 覆盖 key 的输入参数
default 否 未命中时的 get 值
ttl_secs 否 记录的 TTL
limit / offset 否 集合读取的分页
filter 否 针对集合的 JSON 过滤器
parameters 否 覆盖自动 schema

作用域

作用域 存储于 解析自
user user:<user_id> 会话的 X-User-Id
session session:<session_id> 当前会话
agent agent:<template> agent 模板名称
team:<id> team:<id> 显式指定的 id;调用方必须是团队成员(见 §45)

值类型

类型 写入 读取
标量 set get
列表 push list、range
集合 sadd、srem smembers、scard

delete 删除任意键;keys 列出作用域的所有键。

在模板中读取状态

同一个 scoped store 可在 rest_tool/site_tool 模板中通过函数 state('scope', 'key') 访问。将设置写入 agent 作用域——它们就会自动代入 请求,而无需作为参数传递:

# tools/my_api.yaml
fetch:
  url: "https://api.example.com/repos/{{ state('agent', 'repo') }}/issues"

state(...) 从调用方会话解析作用域(与 state_tool 相同);这样 「agent 的设置」就会应用到它自己的工具上。

op — 参考

op 描述 自动参数 返回
get 读取标量(或 default/null) — 值
set 标量的 upsert value {"ok":true}
delete 删除键 — {"deleted":true|false}
keys 作用域的键列表 limit、offset [{key,kind,size}]
push 添加到列表 value {"seq":N}
list 读取列表 limit、offset ["a", …]
range 列表切片 [from..=to] from、to ["a", …]
sadd 添加到集合 value {"added":true|false}
smembers 集合的成员 limit、offset ["x", …]
srem 从集合中删除 value {"removed":true|false}
scard 集合的大小 — {"count":N}

集合读取有上限(默认 1000 个元素)。

filter

filter:
  path: meta.priority
  op: gte               # eq | ne | contains | prefix | gt | gte | lt | lte | exists
  value: 3

字符串操作不区分大小写;gt/gte/lt/lte 按数值比较。


5.17 duckdb_tool

为 agent 提供专属的 DuckDB 数据库。对 DuckDB 的通用 SQL 访问:agent 获得 自己的数据库(或对共享数据库的访问),而连接与 schema 的管理则留给 引擎。适用于 key/value state_tool 不够用的场景——需要表、JOIN、 聚合、索引。

kind: duckdb_tool
name: memory
description: SQL-база фактов пользователя.
path: data/dbs/{user}.db     # файл, ":memory:" или шаблон пути
operation: query             # необязательно: query | execute | schema
init:                        # необязательно: выполняются ОДИН раз (идемпотентно)
  - |
    CREATE TABLE IF NOT EXISTS facts (
      id BIGINT PRIMARY KEY,
      topic TEXT NOT NULL,
      created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
    );
readonly: false              # запретить execute
limit: 100                   # страница по умолчанию
max_rows: 1000               # жёсткий кап строк
emit_visual: false           # не рисовать авто-таблицу в чате (по умолч. true)
sandbox: true                # запретить доступ к ФС/ATTACH + lock_configuration
字段 必填 描述
kind 是 duckdb_tool
name 是 给 LLM 的名称
description 否 给 LLM 的描述
path 是 数据库文件或 :memory:。支持 {user}、{session}、{template}/{agent}
operation 否 绑定到单个操作;没有它时操作由参数 operation 选择(默认 query)
init 否 字符串或 SQL 列表;在首次打开数据库时执行一次
readonly 否 true 禁止 execute
limit / max_rows 否 默认页大小 / 行数上限
emit_visual 否 false — 不将自动表格 query 输出到聊天可视化(默认 true)
sandbox 否 true — 关闭对文件/ATTACH/扩展的访问并锁定 DuckDB 配置
operations 否 声明式操作(见下文);存在时禁用自由 sql
parameters 否 覆盖自动 schema

声明式操作(operations:)

如果 agent 不需要任意 SQL,就描述一组固定的操作—— 每个操作有自己的 SQL 模板。对外只暴露 operation + 参数;sql 不会 出现在 schema 中,也不会进入查询。

kind: duckdb_tool
name: content
path: data/dbs/{template}.db
emit_visual: false
sandbox: true
operations:
  plan_list:
    sql: "SELECT * FROM content_plan WHERE ({{status}} IS NULL OR status = {{status}}) ORDER BY updated_at DESC"
  plan_upsert:
    mode: execute          # query (по умолчанию) | execute
    sql: |
      INSERT INTO content_plan (id, title, status) VALUES ({{id}}, {{title}}, COALESCE({{status}}, 'idea'))
      ON CONFLICT (id) DO UPDATE SET title = EXCLUDED.title, status = EXCLUDED.status

{{param}} 的替换规则:

如果 parameters 未指定,schema 会自动推导:operation(名称的 enum)+ 为每个找到的占位符生成一个字符串参数。mode 默认 query;execute 用于 DDL/DML(遵循 readonly)。

当 operations 已指定时,参数 sql 会被忽略——引擎只执行所选操作的 SQL。

操作

没有 operations: 时(raw 模式)有三个带自由 sql 的操作可用:

operation 用途 参数
query 执行 SQL 并返回 columns + rows sql、limit
execute DDL/DML(CREATE、INSERT、UPDATE、DELETE……) sql
schema 表/视图及其列 table(可选)

query 物化最多 limit 行,并在超过时设置 has_more—— 代理会用自己的 LIMIT/OFFSET 重复请求。结果 query 还会 放入 data(用于通过 visualize_tool source 进行组合)以及 visual (供 UI 使用的表格);content — 供 LLM 使用的 JSON。时间类型(TIMESTAMP、DATE、 TIME)以 ISO 8601 字符串返回。

通过 user / session / agent 使用自定义数据库

path 中的占位符会从调用上下文中解析:

path: data/dbs/{user}.db       # одна база на пользователя
path: data/dbs/{template}.db   # одна база на шаблон агента
path: data/dbs/{session}.db    # одна база на сессию

无法解析的占位符(例如没有 user id 时的 {user})是显式错误,而不是 静默回退到公共文件。替换会经过清理(不能逃逸出 目录)。

多个工具和实例共享的公共数据库

DuckDB 允许每个文件只有一个写入者,因此引擎按规范路径维护 进程级连接池:多个指向同一 文件的 duckdb_tool 定义(以及并行的代理会话)共享同一个受互斥锁保护的连接。此时 init 只会执行 一次。示例 — memory + memory_schema 在 config/agents/db-demo/ 中共享 {user}.db。


5.22 sql_tool

对外部 PostgreSQL 或 MySQL 的 SQL 访问——是 duckdb_tool 的网络版同类工具,具有相同的 query/execute/schema 接口, 但用于连接现有的账户 / CRM / ERP 数据库。

kind: sql_tool
name: crm_db
description: Чтение из базы CRM.
dialect: postgres            # postgres | mysql
url_env: CRM_DATABASE_URL    # URL подключения из env (postgres://… / mysql://…)
operation: query             # необязательно: query | execute | schema
readonly: false              # запретить execute
limit: 100                   # страница по умолчанию
max_rows: 1000               # жёсткий кап строк
init:                        # необязательно: выполняется один раз при создании пула
  - SET NAMES utf8mb4;
字段 必填 描述
kind 是 sql_tool
name 是 供 LLM 使用的名称
description 否 供 LLM 使用的描述
dialect 是 postgres | mysql
url_env 是¹ 连接 URL 的环境变量名(密钥不放在 YAML 中)
url 否 字面 URL(仅用于开发;优先使用 url_env)
operation 否 绑定到单个操作;没有它时,操作由参数 operation 选择(默认 query)
readonly 否 true 禁止 execute
limit / max_rows 否 默认页 / 行数上限
parameters 否 覆盖自动 schema

¹ url_env 或 url 二者必填其一。

操作

operation 用途 参数
query 执行 SQL 并返回 columns + rows sql、limit
execute DDL/DML(CREATE、INSERT、UPDATE、DELETE、……) sql
schema 表/视图及其列 table(可选)

query 会物化最多 limit 行并设置 has_more;结果会放入 data(通过 visualize_tool source 组合)以及 visual(表格)。 连接通过按 URL 的进程级连接池共享:多个 sql_tool 定义指向同一数据库时使用同一个池,init 只执行一次。 时间类型、uuid 等以字符串返回。


5.12 chart_tool 和 table_tool

静态可视化数据。用于预计算/静态数据的引擎: 紧凑的 content(供 LLM 使用的摘要)+ visual(供 UI 使用的完整块)。

kind: table_tool
name: price_list
description: Показать прайс.
parameters: { type: object, properties: {} }
data_file: data/prices.json
title: "Цены"
summary: "{{ count }} позиций"
columns:
  - { key: model, title: "Модель" }
  - { key: price, title: "Цена", align: right }
options: { sortable: true, searchable: true }
kind: chart_tool
name: sales_chart
description: Продажи по кварталам.
parameters: { type: object, properties: {} }
data_file: data/sales.json       # { type, title, labels, series }
summary: "{{ count }} точек"

公共字段:kind、name、description、parameters、data_file(相对于 YAML 的路径) 或内联 data、summary(MiniJinja,上下文为 count)、 ui。table_tool 额外还有:title、columns、options(sortable / searchable / paged)。chart_tool 将 data 作为 spec(type = bar | line | area | pie | scatter)。

两个引擎在调用时都接受动态参数 data——它会覆盖 静态的 data/data_file。


5.13 visualize_tool

从任意数据生成图表和表格。是 chart_tool/table_tool 的通用同类工具:将通过参数传入的数据转换为 图表或表格,并带有转换流水线。LLM 提供行(或 spec), 引擎以确定性方式聚合。

kind: visualize_tool
name: make_chart
description: Построить график/таблицу из переданных строк.
参数 描述
data 行对象数组,或完整的表格/图表 spec
source 引用上一次工具调用的结果:{ tool, index, field, path, pages }
group_by 分组字段
aggregate { count, sum, avg, min, max } — 带前缀的结果(sum_cost、……)
filter { field: { op: value } }
sort { by, dir: asc|desc }
limit 最大行数
as table | bar | line | area | pie | scatter
title、x、series、columns、options 样式

工具组合(source)

无需复制数据,可以引用同一会话中上一次调用的结果:

{ "source": { "tool": "trace_sessions" }, "group_by": "agent",
  "aggregate": { "sum": ["errors"] }, "as": "bar" }

trace_tool 的 sessions 会将 结构化行返回到 data 中——这是现成的数据源。显式的 data 优先于 source。


5.14 map_tool

显示带一个或多个标记的地图。引擎会解析地点 (通过 Photon 进行地理编码——快速的 OSM 地理编码器),或接受显式 坐标,并返回类型为 map 的 visual 块。在 Web 组件中会渲染 交互式地图(Leaflet),Telegram / VK 会收到静态图片 + 链接,Android 会收到“在地图中打开” / “在浏览器中打开”按钮。

kind: map_tool
name: show_map
description: Показать карту с маркером.
provider: osm        # osm (по умолчанию) | google | yandex | 2gis | custom
字段 描述
provider 瓦片预设:osm(无需密钥)、google、yandex、2gis、custom
tile_url 自定义瓦片 URL({z}/{x}/{y}/{s})——覆盖 provider
attribution 来源署名(覆盖预设)
subdomains 用于 {s} 的子域数组
geocode_url 地理编码端点(默认 Photon photon.komoot.io/api/)
zoom 默认缩放级别(1–20,默认 14)

调用参数(任意一个来源):

{ "place": "Академгородок, Новосибирск" }                 // геокодинг
{ "lat": 54.85, "lon": 83.10, "label": "Академ" }         // координаты
{ "markers": [ {"lat": 54.85, "lon": 83.10, "label": "A"}, {"lat": 55.0, "lon": 83.5} ] }

label / title / zoom 可选。Google/Yandex/2GIS 瓦片是 非官方预设;如有需要,请指定自己的 tile_url 和 attribution (遵守提供商的使用条款)。

传输中的 map 块:{ type, title?, center{lat,lon}, zoom?, markers[], tile_url?, attribution?, subdomains?, link } — 参见块格式。

5.14 scratch_tool

会话草稿区(ToolContext.temp_dir)中的文件——这是内核为每个会话创建的临时目录。Agent 可以在那里存放中间结果、笔记和补丁,分块读取并修改它们,而无需访问宿主文件系统。引擎提供模块 scratchpad;一个 YAML 文件描述一个操作(op)。

kind: scratch_tool
name: scratch_read
description: Прочитать файл из черновика сессии (с постраничным чтением)
op: read                    # list | read | write | edit | delete | mkdir | move
op 参数 作用
list path?, recursive? 列出 {path, dir, size} 条目(最多 1000 条)
read path, start_char?, max_chars?, start_line?, line_count?, line_numbers? 按窗口读取(按字符或按行)
write path, content, append? 创建/覆盖/追加(目录会自动创建)
edit path, old_string, new_string?, replace_all? 或 path, start_char, end_char, content 替换片段(唯一)或字符范围
delete path 删除文件或目录(递归)
mkdir path 创建目录及其父目录
move path, to 在草稿区内重命名/移动

所有路径均为相对于草稿区根目录;绝对路径和 .. 会被拒绝。大文件按窗口读取:响应中包含带有偏移量的提示(start_char=),以便继续。工程师 Agent 从 config/agents/admin/tools/ 接入 scratch_list/read/write/edit/delete/mkdir/move。

现成示例见 config/agents/admin/tools/scratch_*.yaml;类型参考——平台中的 tools/scratch_tool(“文档”选项卡)。