Walrider — сквозная модель диалога с AI
Сквозная фича: используется в
project-apiиai-agent-service(и, по замыслу, вuser-api).
⚠️ Важное расхождение с ТЗ. На момент документирования (ветки
feat/user-apiиmain) вuser-apiфичиwalriderНЕТ: файловwalrider.controller.ts,walrider-http.service.ts, репозиториев иwalrider.dto.tsвlibs/apis/providers/user-api/**не существует (провереноfind/grep). Реальная реализация Walrider распределена междуai-agent-service(шлюз + консьюмер + репозитории) иproject-api(создание треда). Раздел про HTTP-API user-api ниже помечен как ожидаемый/отсутствующий.
Что такое Walrider
Заголовок раздела «Что такое Walrider»Walrider — это внешний AI-агент (его код вне репозитория) и одноимённая сквозная модель диалога с ним. В бэкенде Walrider представлен:
- набором Prisma-моделей
WalriderThread/WalriderMessage/WalriderThreadState(в общей схемеapps/core-api/prisma/schema.prisma); - интеграцией через RabbitMQ (запрос/ответ) и WebSocket (реалтайм-доставка ответов клиенту).
Идея: у каждого проекта есть ровно один тред диалога с AI-агентом (связь Project 1—1 WalriderThread). Пользователь общается с агентом в контексте проекта; переписка и состояние сохраняются, ответы приходят асинхронно.
Сущности: Thread / Message / State (+ поле plan)
Заголовок раздела «Сущности: Thread / Message / State (+ поле plan)»Prisma-модели (apps/core-api/prisma/schema.prisma, секция «Walrider Agent Integration Models»):
WalriderThread → таблица walrider_threads
Заголовок раздела «WalriderThread → таблица walrider_threads»| Поле | Тип | Примечание |
|---|---|---|
id | uuid PK | идентификатор треда (используется как threadId в RabbitMQ) |
customerId | uuid | владелец, FK → Customer (onDelete: Cascade) |
projectId | uuid @unique | FK → Project (onDelete: Cascade). Уникальность = 1 тред на проект |
externalThreadId | text? | id треда на стороне внешнего агента (маппинг), пока не заполняется в найденном коде |
createdAt / updatedAt | DateTime | |
| связи | messages: WalriderMessage[], states: WalriderThreadState[] |
WalriderMessage → таблица walrider_messages
Заголовок раздела «WalriderMessage → таблица walrider_messages»| Поле | Тип | Примечание |
|---|---|---|
id | uuid PK | |
messageId | Int autoincrement (SERIAL) | человекочитаемый/последовательный id; именно он летит в RabbitMQ |
threadId | uuid | FK → WalriderThread |
role | enum WalriderMessageRole | USER | ASSISTANT |
content | text | текст сообщения |
externalMessageId | text? | id сообщения на стороне агента (пока не заполняется) |
metadata | Json? | для ASSISTANT-сообщений сюда пишется { phase, isComplete, plan } |
createdAt / updatedAt | DateTime | |
| связи | thread, states: WalriderThreadState[] |
WalriderThreadState → таблица walrider_thread_states
Заголовок раздела «WalriderThreadState → таблица walrider_thread_states»| Поле | Тип | Примечание |
|---|---|---|
id | uuid PK | |
threadId | uuid | FK → WalriderThread |
messageId | uuid? | FK → WalriderMessage.id (не messageId, а id), onDelete: Cascade |
status | varchar(255)? | берётся из response.phase (фаза диалога) |
data | Json? | сюда кладётся plan из ответа агента |
createdAt | DateTime | (только createdAt — состояние иммутабельно, растёт историей) |
enum WalriderMessageRole { USER, ASSISTANT }.
Поле plan
Заголовок раздела «Поле plan»plan — это структурированный «план», который возвращает AI-агент в ответном сообщении (RabbitResponseMessage.plan: Record<string, unknown> | null). В коде ai-agent-service он:
- сохраняется в
WalriderMessage.metadata.plan(у ASSISTANT-сообщения); - сохраняется в
WalriderThreadState.data; - пушится клиенту в WS-событии
message.
Из-за типизации Prisma plan приводится к Prisma.InputJsonValue (см. недавние коммиты: «cast plan field to Prisma.InputJsonValue for type safety»). То есть plan — это произвольный JSON-объект (например, план работ/шагов, который агент строит по диалогу) (содержимое схемы plan в бэкенде не типизировано — это «чёрный ящик» от внешнего агента, предположительно).
Как связаны сервисы
Заголовок раздела «Как связаны сервисы»| Сервис | Роль по отношению к Walrider | Что пишет / читает |
|---|---|---|
| project-api | Создаёт тред при создании проекта | В ProjectService.create() в одной транзакции создаётся Project и затем WalriderThread (walriderThreadRepository.create) с привязкой customer + project. Пишет: WalriderThread. Больше ничего из Walrider не читает. |
| ai-agent-service | Основной рантайм диалога | Читает тред по projectId; создаёт WalriderMessage(USER) и публикует запрос в RabbitMQ; консьюмит ответы, создаёт WalriderMessage(ASSISTANT) + WalriderThreadState; пушит по WS. Пишет: messages, states; читает: thread, messages, state. |
| user-api | (по ТЗ — HTTP-фасад к Walrider) | В коде отсутствует. Ожидался walrider-http.service, ходящий по HTTP к ai-agent-service/агенту, + свои репозитории. Сейчас такого модуля нет (см. предупреждение вверху). |
| Внешний агент Walrider | AI-«мозг» | Консьюмит requestQueue, продюсит в responsesExchange. Кода в репозитории нет. |
Важно: project-api и ai-agent-service имеют свои копии WalriderThreadRepository (тонкие Prisma-обёртки через TransactionHost) — они не шарят репозиторий, а работают с одними и теми же таблицами общей схемы Prisma. В project-api репозиторий минимален (create + findUnique), в ai-agent-service — полнее (create/update/find*/delete/count).
Полный поток сообщения (end-to-end)
Заголовок раздела «Полный поток сообщения (end-to-end)»sequenceDiagram autonumber actor U as Клиент (заказчик) participant P as project-api participant DB as PostgreSQL (Prisma) participant G as ai-agent-service<br/>Gateway (/agent-chat) participant S as ai-agent-service<br/>Service participant R as Redis participant MQ as RabbitMQ participant W as Внешний агент WALRIDER participant C as ai-agent-service<br/>RabbitConsumer
Note over U,P: 0. Подготовка — создание проекта U->>P: POST /projects (создать проект) P->>DB: tx: create Project + create WalriderThread (projectId unique) DB-->>P: project + thread
Note over U,G: 1. Подключение к чату U->>G: WS connect /agent-chat ?token&projectId G->>DB: verify JWT customer + project.customerId G->>R: SET ws:project:{projectId} = socketId G-->>U: connected (комната project:{projectId})
Note over U,MQ: 2. Отправка сообщения U->>G: emit "sendMessage" { content } G->>S: sendMessage(projectId, customerId, content) S->>DB: create WalriderMessage(role=USER) S->>MQ: publish(requestExchange,'', {messageId,threadId,content}) S-->>G: message G-->>U: ack "messageSent" { messageId } MQ->>W: consume requestQueue
Note over W,U: 3. Ответ агента W->>W: обработка + построение plan W->>MQ: publish(responsesExchange, {messageId,threadId,response,phase,isComplete,plan}) MQ->>C: consume responsesQueue C->>S: handleAgentResponse(response) S->>DB: create WalriderMessage(role=ASSISTANT, metadata={phase,isComplete,plan}) S->>DB: create WalriderThreadState(status=phase, data=plan) C->>R: GET ws:project:{projectId} -> socketId? alt сокет активен C->>G: emitToProject(projectId,'message',{messageId,response,phase,isComplete,plan}) G-->>U: emit "message" (ответ агента) else нет активного сокета Note over C: ответ только сохранён в БД,<br/>клиент заберёт через REST history/state endHTTP API walrider в user-api
Заголовок раздела «HTTP API walrider в user-api»⚠️ Отсутствует. В
user-apiнетwalrider.controller.ts— таблицу эндпоинтов построить не из чего (ожидаемая по ТЗ фича не реализована на текущих ветках).
Фактический HTTP-API диалога живёт в ai-agent-service (AgentChatController, @Controller({ path: 'agents', version: '1' }), guard CustomerGuard, префикс /api):
| Метод | Путь | Query/Params | Описание |
|---|---|---|---|
GET | /api/v1/agents/:projectId/history | page, perPage (AgentHistoryQueryDto) | Пагинированная история сообщений треда проекта. Проверяется владение проектом; ответ мапится в AgentMessageDto, порядок — по возрастанию времени. |
GET | /api/v1/agents/:projectId/state | — | Последнее состояние диалога (WalriderThreadState, orderBy createdAt desc), мапится в AgentStateDto ({ id, status, data, createdAt }) или null. |
Реалтайм-часть — по WebSocket (см. ai-agent-service.md, ns /agent-chat, события sendMessage / messageSent / message).
Ключевые файлы (по всем трём сервисам)
Заголовок раздела «Ключевые файлы (по всем трём сервисам)»Общая схема (Prisma):
apps/core-api/prisma/schema.prisma— моделиWalriderThread,WalriderMessage,WalriderThreadState, enumWalriderMessageRole, связьProject 1—1 WalriderThread.apps/core-api/prisma/migrations/20260214131711_add_project_thread_link/migration.sql— добавлениеmessage_id(SERIAL),project_id(unique) и FK наprojects.
project-api:
libs/apis/providers/project-api/features/project/src/lib/walrider-thread.repository.ts— минимальный репозиторий (create,findUnique)..../project/src/lib/project.service.ts—create()создаёт тред вместе с проектом (в транзакции)..../project/src/lib/project.module.ts— регистрацияWalriderThreadRepository.
ai-agent-service:
apps/ai-agent-service/src/main.ts,.../app/app.module.ts— bootstrap сервиса.libs/apis/providers/ai-agent-service/features/agent-chat/src/lib/agent-chat.gateway.ts— WebSocket..../agent-chat.service.ts—sendMessage,handleAgentResponse,getHistory,getState..../rabbit-consumer.service.ts— топология + консьюмер ответов..../agent-chat.controller.ts— REST history/state..../walrider-thread.repository.ts,.../walrider-message.repository.ts,.../walrider-state.repository.ts.libs/apis/providers/ai-agent-service/data-access/src/lib/interfaces/rabbit-messages.interface.ts—RabbitRequestMessage/RabbitResponseMessage.libs/apis/providers/ai-agent-service/data-access/src/lib/dtos/agent-chat.dto.ts— DTO.
Инфраструктура:
libs/apis/utils/rabbit-client/**— клиент RabbitMQ (amqplib).libs/apis/configs/shared/rabbit/**— env-конфиг очередей/обменников.libs/apis/utils/redis-client/**— трекинг активного WS-сокета на проект.
user-api:
- (нет файлов Walrider — фича не реализована на текущих ветках).
Точки расширения
Заголовок раздела «Точки расширения»externalThreadId/externalMessageId: поля под маппинг на сущности внешнего агента заведены в схеме, но в найденном коде не заполняются — их можно начать сохранять при интеграции с реальным Walrider (предположительно, задел на будущее).- HTTP-фасад в user-api: если по продуктовому замыслу нужен REST-доступ к диалогу из user-api, его следует добавить как отдельную фичу, ходящую в
ai-agent-service(сейчас отсутствует). - Схема
plan: сейчас произвольный JSON вmetadata/state.data; можно типизировать под конкретный контракт агента. - Мультиинстансность WS: активный сокет хранится один на проект в Redis; для горизонтального масштабирования нужен Socket.IO-adapter поверх Redis (предположительно).
- Роли сообщений: enum
WalriderMessageRoleпокаUSER/ASSISTANT; при необходимости (system/tool) расширяется миграцией.