OpenClaw Agent

Ответственность

Сервис AI-агента для админки франшизы. Принимает текстовые сообщения от пользователя, ведёт ReAct-цикл (Reason + Act) через LLM-провайдер (TBD), исполняет tool-вызовы как HTTP-запросы к downstream ERP-сервисам от имени юзера (с его JWT). Стримит ход рассуждения и результаты через SSE.

LLM-провайдер пересматривается

Самописный erp-llm-gateway отказан 2026-05-13 — не оправдал ожиданий. OpenClaw framework (см. ADR-022 rev 2) ходит в Ollama напрямую, но окончательный выбор LLM-провайдера для агента не зафиксирован — возможен переход на платный API или другой self-hosted стек.

Реализует BR 6.3

Это закрытие BR 6.3 (демо-сценарий 3 от 29.05.2026). Архитектура — ADR-022.

Ключевые функции

  • ReAct-циклLLM → tool_call → HTTP → result → LLM → ... до finish_reason: stop (модель сама решает когда хватит). Max 10 итераций за один user-message (safety bound).
  • Tool registry — внутренний JSON-каталог из 5 базовых функций (find_products, create_order, get_stoplist, add_to_stoplist, get_sales_summary). Каждая описывает service + HTTP-метод + path-template + JSON-schema аргументов.
  • JWT pass-through — каждый downstream вызов идёт с JWT юзера в Authorization. Сервис не имеет своих привилегий — что юзер может, то и агент.
  • SSE-стримингPOST /api/v1/admin/agent/chat возвращает text/event-stream. UI видит реальный поток: токены ассистента, начало/конец каждого tool_call, результат tool.
  • История сессий — каждая беседа сохраняется в PostgreSQL. CRUD сессий: список своих, открыть, удалить.
  • Системный промпт — фиксированный, в коде сервиса. Описывает роль, доступные tools, правила безопасности.
  • Лимиты — 20 ходов на сессию, 10 ReAct-итераций на ход, 2000 символов на user-message.
  • Graceful degradation — при 503 от LLM-провайдера возвращает понятный JSON с ошибкой, UI показывает «AI временно недоступен».

Зависимости

ЗависимостьРольПримечание
LLM-провайдер (TBD)Inference (стек не зафиксирован — кандидаты: OpenClaw + локальный Qwen, платный API)Главная зависимость. При падении сервис в degraded state
Auth ServiceВалидация JWT (HS256 local или introspection)Каждый запрос требует JWT
PostgreSQL (agent_db)История сессий: agent_conversations, agent_messagesСвоя БД (database-per-service)
RedisКэш active sessions, rate-limitОбщий
Catalog ServiceTools: find_products, get_stoplist, add_to_stoplistHTTP REST через JWT юзера
Order ServiceTools: create_order, get_sales_summaryHTTP REST через JWT юзера
User ServiceПроверка agent.use permission (через /auth/me или JWT-claim)На стороне OpenClaw Agent

Tool registry (статичный, в коде)

ToolServiceHTTPPath / Query templateБезопасность
find_productsCatalogGET/api/v1/admin/products?search={query}&store_id={store_id}&limit=20требует catalog.read у юзера
create_orderOrderPOST/api/v1/admin/ordersтребует orders.create у юзера
get_stoplistCatalogGET/api/v1/admin/stop-list/store/{store_id}требует catalog.read
add_to_stoplistCatalogPOST/api/v1/admin/stop-list body {product_id, store_id}требует catalog.stoplist.write
get_sales_summaryOrderGET/api/v1/admin/orders/summary?store_id={store_id}&from={date}&to={date}требует orders.read

Полная JSON Schema аргументов каждого tool — в API и в коде сервиса (src/tools/registry.ts).

Permissions проверяет downstream сервис

Если у юзера нет orders.create, Order Service вернёт 403 на create_order. Агент получит ошибку и сформулирует юзеру: «Не получилось — у тебя нет права создавать заказы».

Поток запроса

sequenceDiagram
    autonumber
    actor U as User (Admin BFF)
    participant A as OpenClaw Agent:3031
    participant DB as PostgreSQL
    participant G as LLM-провайдер
    participant C as Catalog Service
    participant O as Order Service

    U->>A: POST /api/v1/admin/agent/chat<br/>{conversation_id?, message}<br/>JWT
    A->>A: validate JWT, check agent.use
    A->>DB: BEGIN<br/>INSERT INTO agent_conversations (...) если нет<br/>INSERT INTO agent_messages (role=user)
    
    Note over A,G: ReAct loop start
    A->>G: POST /v1/chat/completions<br/>{messages, tools[5], stream:true}
    G-->>A: SSE stream → tool_calls=[{name:"find_products", args:{...}}]
    A-->>U: SSE: 🔍 ищу товары "маргарита"
    A->>C: GET /api/v1/admin/products?search=маргарита<br/>(пробрасывает JWT юзера)
    C-->>A: 200 [{id: prd_abc, name: "Пицца Маргарита", ...}]
    A->>DB: INSERT agent_messages (role=tool, content=...)
    A->>G: POST /v1/chat/completions<br/>(добавили tool result в messages)
    G-->>A: SSE stream → tool_calls=[{name:"create_order", args:{...}}]
    A-->>U: SSE: 📝 создаю заказ
    A->>O: POST /api/v1/admin/orders<br/>(JWT юзера)
    O-->>A: 201 {id: ord_xyz, total: 850}
    A->>DB: INSERT agent_messages (role=tool)
    A->>G: POST /v1/chat/completions
    G-->>A: SSE stream → content="Заказ #1234 создан..."<br/>finish_reason=stop
    A->>DB: INSERT agent_messages (role=assistant)<br/>COMMIT
    A-->>U: SSE: токены ответа → [DONE]

Авторизация

ЭндпоинтAuthPermission
/api/v1/admin/agent/*Bearer JWT юзераagent.use
/internal/agent/healthБез auth
/internal/agent/admin/sessions/{user_id}Service-tokenagent.config (на стороне вызывающего)

JWT юзера прокидывается во все downstream сервисы — никаких суперправ.

Конфигурация

База

VariableDescriptionDefault
PORTHTTP-порт3031
DATABASE_URLPostgreSQL connection string
REDIS_URLRedis

LLM-провайдер

VariableDescriptionDefault
LLM_PROVIDER_URLEndpoint провайдера (см. блок «LLM-провайдер пересматривается» выше)TBD
LLM_MODELМодельqwen2.5:14b-instruct-q5_k_m
LLM_TEMPERATUREКреативность (0.0 — детерминизм для agent)0.2
LLM_MAX_TOKENSЛимит на один ответ1024
LLM_TIMEOUT_MSTimeout одного inference60000

Downstream services

VariableDescriptionDefault
CATALOG_SERVICE_URLCatalog Service base URLhttp://catalog-service:3004
ORDER_SERVICE_URLOrder Service base URLhttp://order-service:3005

Лимиты

VariableDescriptionDefault
MAX_TURNS_PER_CONVERSATIONЛимит на сессию (user→assistant пар)20
MAX_REACT_ITERATIONSMax iterations of LLM→tool→LLM в одном turn10
MAX_USER_MESSAGE_CHARSЛимит длины user-сообщения2000
CONVERSATION_TTL_DAYSTTL для closed conversations до удаления30

Auth

VariableDescriptionDefault
AUTH_MODElocal (HS256) / introspectionlocal
JWT_LOCAL_SECRETHS256 secret
AUTH_USERINFO_URLДля проверки agent.use через /auth/mehttp://user-service:3002/auth/me

Не-функциональные требования

  • Latency — первое токен от ассистента ≤ 3 сек (включая LLM + parsing). Полный ответ простого запроса (1 tool_call) ≤ 15 сек.
  • Параллельность — поддерживает ≥ 20 одновременных SSE-стримов на один инстанс (бутылочное горло — LLM, не Node.js).
  • Восстановление — при рестарте сервиса активные стримы прерываются. Клиент должен переоткрыть чат — последнее user-сообщение остаётся в БД, можно нажать «Повторить».
  • Безопасность — никогда не логировать содержимое messages (PII). Логи: request_id, user_id, conversation_id, tool_name, tool_status, duration_ms.
  • Аудит — все tool_calls пишутся в agent_messages с timestamp + результатом. Это и есть аудит-трейл.

Что НЕ делает

  • ❌ Не имеет своего fallback-LLM — если провайдер упал, агент тоже падает (graceful 503)
  • ❌ Не добавляет своих tools на лету — статичный registry
  • ❌ Не делает повторный inference при ошибке tool — ошибка попадает в messages, LLM сам решает что делать
  • ❌ Не отвечает голосом, не понимает голос — только текст

Ссылки