工具 — 数据采集与搜索

🔵 用于从外部获取数据的引擎:HTTP 抓取、本地归档、JSON、RSS、 PDF、地理编码。所有引擎的汇总表见 《工具参考》。

5.4 site_tool

通用声明式 HTTP/API 客户端:请求 → 字段提取 → 输出 格式。尽管名称如此,它不仅仅是抓取器——还能处理任意 REST: method(GET/POST/PUT/PATCH/DELETE)、请求体 json/form/multipart/body、 自定义请求头、OAuth2、HMAC 签名、Cookie、分页。对于 REST 集成, 可以编写 kind: rest_tool——这是同一引擎的别名(语义更清晰)。

kind: site_tool                     # или rest_tool — алиас для REST/API
name: my_scraper
description: Парсит страницу товара.
parameters:
  type: object
  properties:
    url: { type: string }
  required: [url]

fetch:
  url: "{{ input.url }}"          # шаблон MiniJinja
  method: POST                    # GET (по умолч.) | POST | PUT | PATCH | DELETE
  json: { query: "{{ input.q }}" }  # JSON-тело (листья — шаблоны)
  # body: "q={{ input.q | urlencode }}"   # сырое тело (шаблон)
  # form: { search: "{{ input.q }}" }      # x-www-form-urlencoded
  # multipart: { fields: {...}, files: {...} }
  timeout_secs: 15
  cache_ttl_secs: 300
  follow_redirects: true
  cookies: true
  headers:
    User-Agent: "MyBot/1.0"
    Authorization: "Bearer {{ env_var('API_KEY') }}"
  # oauth2:                          # OAuth2 (refresh_token / client_credentials)
  #   token_url: "https://oauth.yandex.ru/token"
  #   grant: refresh_token
  #   client_id_env: X_CLIENT_ID
  #   client_secret_env: X_CLIENT_SECRET
  #   refresh_token_env: X_REFRESH_TOKEN
  #   prefix: "OAuth "               # префикс заголовка (Bearer по умолч.; Yandex — OAuth)

source:
  type: script_json               # JSON внутри <script>
  contains: productData
  # type: json                    # или тело само — JSON

extract: { ... }
output: { ... }

fetch — URL

两种风格:

  1. 直接 URL — url: "{{ input.url }}"。
  2. 路径构建器 — base + path_opt + path + query(示例 — drom_search.yaml):
fetch:
  base: "https://auto.drom.ru/"
  path_opt:
    - when: { has: region }
      value: "region{{ region | region_code }}"
    - when: { equals: [condition, "new"] }
      value: "new"
  path:
    - "{{ brand }}"
    - "{{ model | default('') | slugify }}"
    - "{% if page is defined and page > 1 %}page{{ page }}{% endif %}"
  query:
    unsold: "1"
    minprice: "{{ price_from }}"
  filters:
    slugify: _builtin_
    region_code:
      moscow: "77"
      nsk: "54"
    region_code_ci:
      case_insensitive: true
      default: "77"
      "Москва": "77"

filters 将过滤器名称与 _builtin_(例如 slugify)或 键→值表进行匹配。Lookup 表是普通映射,或者是带有 case_insensitive: true 和 default 的对象(值缺失时的回退)。

env_var('NAME') 读取环境变量——用于请求头中的密钥。

query 在直接 url 时也会应用(以 URL 编码方式添加)。

模板:命名空间与函数

在所有配置字段中(url、base/path/path_opt、query、headers、 body、json、form、multipart、output)都可访问同一个上下文:

也就是说,{{ org }} 和 {{ input.org }} 在任何字段中都可互换(此前 扁平名称仅在 path 中可见)。

可用的 MiniJinja 函数:

函数 作用
env_var('NAME') 环境变量的值(未设置时为 "")
state('scope', 'key') 来自调用方状态的值;scope = agent | user | session
template_var('name') 会话模板变量(template_vars),例如 admin_templates

state(...) 从调用方会话解析 scope,而不是从参数:这样 代理的设置(例如 state('agent', 'repo'),由 state_tool 写入) 无需作为参数传递即可代入请求。缺失的键渲染为空 (undefined);在工具调用之外或未启用 state-store 时调用 state(...) 是渲染错误,而不是静默的空值。

fetch — 通用配置档案(extends)

为了避免重复 base_url 和 Authorization,将它们提取到配置档案中并指定 extends(相对于工具 YAML 的路径):

# tools/_shared.yaml
base_url: "https://api.example.com"
headers:
  Authorization: "Bearer {{ env_var('API_KEY') }}"
# tools/list.yaml
fetch:
  extends: _shared.yaml
  path: ["issues"]
  headers:
    Accept: "text/plain"      # перекрывает Accept из профиля

工具自身的 url/base/headers 优先于配置档案。

配置档案不是工具(它没有 kind):不要将其列入 tool_files,否则加载器无法识别该文件,工具也不会注册。 可以在 templates_list 中检查所有 tool_files 是否真正加载 (字段 tools_declared/tools_loaded/tools_failed)。

fetch — 请求体

body / json / form / multipart 中恰好一个(歧义配置 在加载时会被拒绝)。json: — JSON 请求体,每个字符串叶子都是模板。 form: — urlencoded。multipart: { fields, files } — files 是本地路径。

MiniJinja 中的布尔值/null 以 Python 风格渲染(True/False/None), 这不是有效的 JSON——在原始 body 中请将值通过 tojson 处理:

body: '{"completed": {{ input.completed | tojson }}}'

对于 json 请求体,omit_empty: true 会递归丢弃值为空的 键(""、null、[]、{})——便于 PATCH。

fetch — 重试 / backoff

临时故障会自动重试:HTTP 429、5xx、网络/超时错误。 retries — 尝试次数(默认 3,0 禁用);backoff_ms — 基础 延迟并加倍(默认 3000 → 3 秒、6 秒、12 秒)。

fetch — 分页(token 风格)

对于带有下一页 token 的列表,添加 pagination:

pagination:
  token_field: /next_page_token
  token_param: page_token
  items_field: /issues
  max_items: 100
  max_pages: 10

工具会自动翻页(聚合元素)并告知在哪里 继续。结果:{ items, page_info: { returned, total, has_more, next_token } }。 LLM 通过 page_token: <next_token> 继续调用。

source — 结构化数据所在位置

字段 取值
type script_json(<script> 中的 JSON 数据块)或 json(请求体即 JSON)
contains 脚本必须包含的子串(用于 script_json)
min_length 脚本最小长度(默认 5000)

其他 source 类型:

fetch — 两步 token(auth)

当需要先获取 token(匿名授权)时,添加 auth:

fetch:
  auth:
    token_url: "https://…/api/v1/auth/anonymous"
    method: POST
    json: { app: "mobile" }        # или body / form / headers
    field: token.accessToken       # JSON-указатель /a/b или точка a.b
    header: Authorization
    prefix: "Bearer "

fetch — OAuth2(oauth2)

对于 OAuth2 服务(Bitrix24、amoCRM、Google)—— oauth2。与 auth 不同,该引擎会缓存 access token 直到过期,轮换 refresh token,并在 HTTP 401 时自动刷新 token 并重试请求。密钥 从 env 获取(绝不写入 YAML):

fetch:
  oauth2:
    token_url: "https://…/oauth2/token"
    grant: refresh_token          # refresh_token (по умолч.) | client_credentials
    client_id_env: OAUTH_CLIENT_ID
    client_secret_env: OAUTH_CLIENT_SECRET
    refresh_token_env: OAUTH_REFRESH_TOKEN   # для grant: refresh_token
    # scope: "..."                # для grant: client_credentials
    header: Authorization         # по умолчанию
    prefix: "Bearer "             # по умолчанию

响应按标准 OAuth2 字段解析:access_token、expires_in (秒)以及可选的 refresh_token。返回的 refresh_token 会替换缓存中的值以供后续刷新。oauth2 和 auth 互斥。

fetch — 请求签名(sign)

用于需要签名的 API(ЮKassa、Тинькофф、Сбер)的 HMAC-SHA256。规范 字符串是 MiniJinja 模板,带有变量 method、url、body(规范 请求体)和 input:

fetch:
  sign:
    secret_env: SIGN_SECRET       # ключ подписи из env
    template: "{{ method }}{{ url }}{{ body }}"   # по умолчанию
    header: X-Signature           # либо header, либо query
    # query: sign
    encoding: hex                 # hex (по умолч.) | base64

header 和 query 互斥:签名要么放入请求头,要么 放入 query 参数。

extract — 字段类型

三种类型,按关键字段区分。

简单字段 — 按 JSON 指针导航:

extract:
  location:
    pointer: /geoInfo/0/text
    value: /sub/path           # доп. навигация вглубь
    fallback: "unknown"
    transform: int

指针可以是基于 input 的 MiniJinja 模板;段 *(或 ~first) 可跨越动态键。

数组字段 — 按判别器过滤数组:

  year:
    pointer: /fields
    array_match: { type: year }   # первый элемент с .type == "year"
    value: /payload
    transform: int
  photos:
    pointer: /gallery/images
    collect_all: true             # все элементы → массив
    value: /image/src
    transform: [ { skip: 5 }, { take: 20 } ]

计算字段 — 条件逻辑:

  status:
    compute:
      - when: { op: not_null, pointer: /soldNotification }   then: sold
      - when: { op: equals, pointer: /shouldShowDeleted, value: true }  then: deleted
    default: active

op 在发行版中:not_null、equals。

转换

按顺序应用(也可以列表链式):

转换 YAML 描述
Lookup { lookup: { "1": foo, "2": bar } } 按表取值(未知 → "?")
Truncate { truncate: 2000 } 截断到 N 个字符
Take { take: 20 } 取前 N 个
Skip { skip: 5 } 丢弃前 N 个
Len len 数组长度
NotNull not_null 若非 null 则为 true
Bool bool 转为布尔值
Int int 转为整数
Filter { filter: "not {{ item._sold }}" } 丢弃元素(数组)
Transliterate transliterate 转写为 slug
StripHtml strip_html 移除 HTML/XML 标签(<noindex>…</noindex> → 文本)

output — 格式

可选的 MiniJinja 模板。叶子节点基于 input 和 extracted 渲染。 当字段未能解析时,{{ extracted._warnings }} 可用。没有 output 时, 返回提取出的映射原样。

错误处理

引擎不会 panic:错误会返回带消息的 success: false(HTTP 403/404/429/5xx、网络错误、“未找到结构化数据”)。


5.5 archive_tool

在本地归档中搜索。

kind: archive_tool
name: ngu_search
description: Поиск по архиву НГУ.
parameters:
  type: object
  properties:
    query: { type: string }
  required: [query]

mode: search_grouped
archive:
  path: data/ngu
  default_limit: 10

filters:
  - { field: players, op: players_min, param: players_min }
  - { field: price,   op: gte,        param: price_min }

sort:
  field: price
  order: asc

mode

模式 用途
search 按关键词搜索
search_grouped 按页面分组的结果(matched_chunks)
lookup 按 URL 获取完整文档
read_chunks 分块范围(start_chunk、end_chunk)
outline 页面结构:标题、字符/分块数量、分块预览

归档是一个目录,包含 meta.json、docs.bin(按分块的文档)和 terms.bin (词项索引);通过命令 agent-os scrape 构建。


5.6 json_tool

在预加载的 JSON 中搜索。

kind: json_tool
name: find_brands
description: Поиск брендов по имени.
parameters:
  type: object
  properties:
    query: { type: string }
    section: { type: string, enum: [auto, moto, truck] }
  required: [query]

data_file: data/drom_brands.json

search:
  paths: ["{{ input.section | default('*') }}.top", "*.all"]
  field: name
  query_param: query
  mode: contains
  limit: 20
  dedupe_by: id
  source_field: section

computed:
  slug: "{{ item.url | path_segment(0) }}"
字段 描述
data_file 要加载的 JSON 文件
search.paths 搜索的 JSON 路径;* = 通配键
search.field 用于匹配的字段
search.query_param 携带查询的输入参数
search.mode contains(部分匹配)
search.limit 最大结果数
search.dedupe_by 按字段去重
computed 针对每个结果的 MiniJinja 模板(作用域中有 item)

5.7 rss_tool

在 RSS 订阅源中搜索。

kind: rss_tool
name: rss_search
description: Поиск новостей по ключевому слову.
parameters:
  type: object
  properties:
    site: { type: string }
    query: { type: string }
  required: [site, query]

feeds:
  lenta: "https://lenta.ru/rss"
  ria: "https://ria.ru/export/rss2/archive/index.xml"
  rbc: "https://rssexport.rbc.ru/rbcnews/news/30/full.rss"

site — feeds 中的键。结果包含 count、标题、摘要、URL。


5.8 pdf_tool

按页读取 PDF。

kind: pdf_tool
name: pdf_read
description: Скачать и прочитать PDF.
parameters:
  type: object
  properties:
    url: { type: string }
    page: { type: integer, default: 1 }
  required: [url]

fetch:
  url: "{{ input.url }}"
  timeout_secs: 30
  cache_ttl_secs: 3600

pdf:
  chars_per_page: 3000

output:
  page: "{{ extracted.page }}"
  total_pages: "{{ extracted.total_pages }}"
  text: "{{ extracted.text }}"
  next_page: "{{ extracted.next_page }}"

5.15 geo_tool

地理编码和地点(OpenStreetMap)。免费、无需密钥,地理编码和 POI 搜索 (Nominatim + Overpass API)——智能体不会编造地址。

kind: geo_tool
name: geo_places
description: Найти места категории рядом.
operation: places
operation 用途 参数
locate 地点地理编码 → 坐标 + bbox query
places 附近 / bbox 内的 POI 类别 category、place/bbox、limit
distance 按距离对地址排序 place、addresses

类别(俄语/英语同义词):сто/автосервис/car_repair、шиномонтаж、 аптека/pharmacy、банк/атм、кафе/ресторан、школа、магазин/ продукты/супермаркет、заправка/азс、парковка。未知类别 → Overpass 搜索 shop=<value>。

可选:nominatim_url / overpass_url、user_agent、ui。


5.15.1 rasp_tool

交通时刻表(火车、通勤列车、飞机、巴士、轮船、 渡轮),通过免费的 Yandex.Raspisaniya API。授权使用免费密钥 (apikey query 参数),在开发者后台 developer.tech.yandex.ru 发放(API“Yandex.Raspisaniya”)。

kind: rasp_tool
name: rasp
description: Расписания транспорта Яндекса.
api_key_env: YANDEX_RASP_API_KEY   # или api_key: "..." (не рекомендуется)
lang: ru_RU                        # опц., язык ответов
字段 描述
api_key / api_key_env 免费密钥(内联或环境变量)
lang 响应语言(默认 ru_RU)

操作(参数 action):

操作 API 方法 作用
station_list stations_list 按名称搜索车站(query)→ 名称 + 代码
search search A→B 路线(from、to、date、transport_types)
schedule schedule 车站时刻板(station、date、direction)
thread thread 班次详情(uid)
nearest_stations nearest_stations 距离某点最近的车站(lat、lng)
nearest_settlement nearest_settlement 最近的居民点(lat、lng)

车站既可以用名称指定(会自动解析为代码),也可以用现成的 代码 yandex_code(例如 s2000006)。对于 search,城市会解析为 settlement 代码(城市→城市),对于 schedule —— 解析为具体车站(优先 真正的火车站)。

{ "action": "station_list", "query": "Москва" }
{ "action": "search", "from": "Москва", "to": "Санкт-Петербург", "date": "2026-09-15", "transport_types": ["train"] }
{ "action": "schedule", "station": "Москва", "date": "2026-09-15", "direction": "departure" }
{ "action": "thread", "uid": "122S_15_2" }
{ "action": "nearest_stations", "lat": 55.75, "lng": 37.61 }

transport_types:plane、train、suburban、bus、water(默认——全部)。 API 的强制要求——注明 Yandex 为数据来源。


5.23 browser_tool

无头浏览器。对于通过 JavaScript 渲染的网站(SPA、 无限滚动、登录)——普通的 HTTP 抓取 site_tool 不够。browser_tool 在 Chromium 中加载页面,执行交互并返回结果—— 要么通过在页面内部执行的 JS 脚本(“内部脚本编写”), 要么通过渲染后的文本。

浏览器会在系统中查找,如果没有——则自动下载 (chrome-headless-shell 来自 Chrome-for-Testing;与 OfficeCLI 的 PDF 导出 逻辑相同)。

浏览器进程会被复用(池):每次调用都会在存活的 Chromium 中打开一个全新的 隔离标签页(CDP browser context),并在完成后将浏览器归还到池中——重复调用无需支付 约 1–2 秒的启动开销。数量 进程数量受变量 AGENT_OS_MAX_BROWSERS 限制(默认 4); 多余的调用会等待空闲槽位(最多 60 秒,之后报错)。

kind: browser_tool
name: scrape_spa
description: Извлечь товары из JS-листинга.
parameters:
  type: object
  properties:
    url: { type: string }
  required: [url]

steps:                          # необязательные действия, по порядку
  - { type: goto, url: "{{ input.url }}" }
  - { type: wait, selector: ".product-card" }
  - { type: click, selector: "button.load-more" }
  - { type: type, selector: "#q", text: "{{ input.q }}" }
  - { type: check, selector: "input[name=agree]" }
  - { type: eval, script: "return document.title" }
  - { type: screenshot, name: "page.png" }
  - { type: pdf, name: "page.pdf" }

script: |                       # JS внутри страницы → итоговый результат (JSON)
  [...document.querySelectorAll('.product-card')].map(c => ({
    title: c.querySelector('h2')?.innerText,
    price: c.querySelector('.price')?.innerText,
  }))

timeout_secs: 30

步骤

type 字段 操作
goto url 跳转并等待 readyState
wait ms 或 selector 暂停或等待选择器
click selector 点击第一个匹配的元素
type selector, text 填充 <input>/<textarea>/<select> 并触发事件
check selector, checked? 设置复选框/单选框的 checked(默认 true)
eval script 在页面中执行 JS(结果会被保存)
screenshot name PNG 截图 → 文件
pdf name 打印为 PDF → 文件

脚本与结果

步骤中的字符串字段(url、selector、text)是基于 input 的 MiniJinja 模板。script / eval.script 中的 JS 在页面上下文中执行,调用参数 可作为全局变量 input 访问(例如 input.url)。

结果:如果 script(或最后一个 eval)返回了值——它会进入 data 和 content(字符串或 pretty-JSON);否则返回页面文本。 screenshot/pdf 会生成可下载的块 file 到 visual 中;对象数组 还会额外渲染为表格。

5.24 web_search

🔵 通过提供商进行互联网搜索(默认 FireCrawl)。引擎 提供模块 searchers(linked-crate 或 DLL);内核注册 kind: web_search автоматически, когда модуль включён. Демо-агент searchers-demo 随附提供。

支持的提供商:firecrawl(默认)、tavily、brave、 serper、serpapi、exa、google(CSE)、searxng(自托管,无需密钥)。 每个都有自己的密钥/环境变量——完整表格见页面 模块 searchers。

调用参数(由 LLM 指定):

参数 必填 描述
query 是 搜索查询
limit 否 结果数量,1–20(默认 5)
provider 否 提供商(默认 firecrawl)

配置:

kind: web_search
name: web_search
description: Поиск в интернете (title, url, snippet).
# provider: firecrawl            # firecrawl|tavily|brave|serper|serpapi|exa|google|searxng
# api_key: fc-...                # явный ключ (не хранить в git)
# api_key_env: FIRECRAWL_API_KEY # по умолчанию — своя для провайдера
# base_url: https://api.firecrawl.dev
# cx: <google-cse-id>            # только для provider: google
# limit: 5

密钥按以下顺序解析:配置中的 api_key → 变量 api_key_env (默认每个提供商各有自己的:FIRECRAWL_API_KEY、TAVILY_API_KEY、 BRAVE_API_KEY、SERPER_API_KEY、SERPAPI_API_KEY、EXA_API_KEY、 GOOGLE_API_KEY)。searxng 不需要密钥。密钥也可以通过模块 设置 指定——值会投射到相应的变量中, 无需重启。

结果——JSON:{ provider, query, count, results: [ { title, url, description } ] }。

模块、演示代理和详细信息——见页面 模块 searchers。