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
Ключевые функции
- 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 Service | Tools: find_products, get_stoplist, add_to_stoplist | HTTP REST через JWT юзера |
| Order Service | Tools: create_order, get_sales_summary | HTTP REST через JWT юзера |
| User Service | Проверка agent.use permission (через /auth/me или JWT-claim) | На стороне OpenClaw Agent |
Tool registry (статичный, в коде)
| Tool | Service | HTTP | Path / Query template | Безопасность |
|---|---|---|---|---|
find_products | Catalog | GET | /api/v1/admin/products?search={query}&store_id={store_id}&limit=20 | требует catalog.read у юзера |
create_order | Order | POST | /api/v1/admin/orders | требует orders.create у юзера |
get_stoplist | Catalog | GET | /api/v1/admin/stop-list/store/{store_id} | требует catalog.read |
add_to_stoplist | Catalog | POST | /api/v1/admin/stop-list body {product_id, store_id} | требует catalog.stoplist.write |
get_sales_summary | Order | GET | /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]
Авторизация
| Эндпоинт | Auth | Permission |
|---|---|---|
/api/v1/admin/agent/* | Bearer JWT юзера | agent.use |
/internal/agent/health | Без auth | — |
/internal/agent/admin/sessions/{user_id} | Service-token | agent.config (на стороне вызывающего) |
JWT юзера прокидывается во все downstream сервисы — никаких суперправ.
Конфигурация
База
| Variable | Description | Default |
|---|---|---|
PORT | HTTP-порт | 3031 |
DATABASE_URL | PostgreSQL connection string | — |
REDIS_URL | Redis | — |
LLM-провайдер
| Variable | Description | Default |
|---|---|---|
LLM_PROVIDER_URL | Endpoint провайдера (см. блок «LLM-провайдер пересматривается» выше) | TBD |
LLM_MODEL | Модель | qwen2.5:14b-instruct-q5_k_m |
LLM_TEMPERATURE | Креативность (0.0 — детерминизм для agent) | 0.2 |
LLM_MAX_TOKENS | Лимит на один ответ | 1024 |
LLM_TIMEOUT_MS | Timeout одного inference | 60000 |
Downstream services
| Variable | Description | Default |
|---|---|---|
CATALOG_SERVICE_URL | Catalog Service base URL | http://catalog-service:3004 |
ORDER_SERVICE_URL | Order Service base URL | http://order-service:3005 |
Лимиты
| Variable | Description | Default |
|---|---|---|
MAX_TURNS_PER_CONVERSATION | Лимит на сессию (user→assistant пар) | 20 |
MAX_REACT_ITERATIONS | Max iterations of LLM→tool→LLM в одном turn | 10 |
MAX_USER_MESSAGE_CHARS | Лимит длины user-сообщения | 2000 |
CONVERSATION_TTL_DAYS | TTL для closed conversations до удаления | 30 |
Auth
| Variable | Description | Default |
|---|---|---|
AUTH_MODE | local (HS256) / introspection | local |
JWT_LOCAL_SECRET | HS256 secret | — |
AUTH_USERINFO_URL | Для проверки agent.use через /auth/me | http://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 сам решает что делать
- ❌ Не отвечает голосом, не понимает голос — только текст