🤖 AI-агенты для бизнеса

5. Инструменты — сбор и поиск данных

🔵 Движки для получения данных извне: HTTP-скрап, локальный архив, JSON, RSS, PDF, геокодинг. Сводную таблицу всех движков см. в «Справочник инструментов».

5.4 site_tool

Универсальный скрапер: HTTP-запрос → извлечение полей → формат вывода.

kind: site_tool
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') }}"

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

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

fetch — URL

Два стиля:

  1. Прямой URLurl: "{{ 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-кодированием).

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 инструмента выигрывают у профиля.

fetch — тело запроса

Ровно одно из body / json / form / multipart (неоднозначный конфиг отвергается при загрузке). json: — JSON-тело, каждый строковый лист — шаблон. form: — urlencoded. multipart: { fields, files }files это локальные пути.

Булевы/null в MiniJinja рендерятся 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-стиль)

Для списков с токеном следующей страницы добавь 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 (JSON-блоб в <script>) или json (тело — JSON)
contains подстрока, которую должен содержать скрипт (для script_json)
min_length мин. длина скрипта (по умолчанию 5000)

Другие типы source:

fetch — двухшаговый токен (auth)

Когда сначала нужен токен (анонимная авторизация), добавь 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 "

extract — типы полей

Три вида, различаются по ключам.

Простое поле — навигация по JSON-указателю:

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

Указатели могут быть MiniJinja-шаблонами от input; сегмент * (или ~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 true, если не null
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 возвращается извлечённая карта как есть.

Обработка ошибок

Движок не паникует: ошибки возвращают 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 (индекс терминов); собирается scraper-ом.


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-пути поиска; * = wildcard-ключ
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 POI категории рядом / внутри bbox category, place/bbox, limit
distance сортировка адресов по расстоянию place, addresses

Категории (RU/EN синонимы): сто/автосервис/car_repair, шиномонтаж, аптека/pharmacy, банк/атм, кафе/ресторан, школа, магазин/ продукты/супермаркет, заправка/азс, парковка. Неизвестная категория → поиск Overpass shop=<value>.

Опционально: nominatim_url / overpass_url, user_agent, ui.