Перейти к содержимому

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 — это внешний AI-агент (его код вне репозитория) и одноимённая сквозная модель диалога с ним. В бэкенде Walrider представлен:

  • набором Prisma-моделей WalriderThread / WalriderMessage / WalriderThreadState (в общей схеме apps/core-api/prisma/schema.prisma);
  • интеграцией через RabbitMQ (запрос/ответ) и WebSocket (реалтайм-доставка ответов клиенту).

Идея: у каждого проекта есть ровно один тред диалога с AI-агентом (связь Project 1—1 WalriderThread). Пользователь общается с агентом в контексте проекта; переписка и состояние сохраняются, ответы приходят асинхронно.

Prisma-модели (apps/core-api/prisma/schema.prisma, секция «Walrider Agent Integration Models»):

ПолеТипПримечание
iduuid PKидентификатор треда (используется как threadId в RabbitMQ)
customerIduuidвладелец, FK → Customer (onDelete: Cascade)
projectIduuid @uniqueFK → Project (onDelete: Cascade). Уникальность = 1 тред на проект
externalThreadIdtext?id треда на стороне внешнего агента (маппинг), пока не заполняется в найденном коде
createdAt / updatedAtDateTime
связиmessages: WalriderMessage[], states: WalriderThreadState[]
ПолеТипПримечание
iduuid PK
messageIdInt autoincrement (SERIAL)человекочитаемый/последовательный id; именно он летит в RabbitMQ
threadIduuidFK → WalriderThread
roleenum WalriderMessageRoleUSER | ASSISTANT
contenttextтекст сообщения
externalMessageIdtext?id сообщения на стороне агента (пока не заполняется)
metadataJson?для ASSISTANT-сообщений сюда пишется { phase, isComplete, plan }
createdAt / updatedAtDateTime
связиthread, states: WalriderThreadState[]
ПолеТипПримечание
iduuid PK
threadIduuidFK → WalriderThread
messageIduuid?FK → WalriderMessage.id (не messageId, а id), onDelete: Cascade
statusvarchar(255)?берётся из response.phase (фаза диалога)
dataJson?сюда кладётся plan из ответа агента
createdAtDateTime(только createdAt — состояние иммутабельно, растёт историей)

enum WalriderMessageRole { USER, ASSISTANT }.

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/агенту, + свои репозитории. Сейчас такого модуля нет (см. предупреждение вверху).
Внешний агент WalriderAI-«мозг»Консьюмит requestQueue, продюсит в responsesExchange. Кода в репозитории нет.

Важно: project-api и ai-agent-service имеют свои копии WalriderThreadRepository (тонкие Prisma-обёртки через TransactionHost) — они не шарят репозиторий, а работают с одними и теми же таблицами общей схемы Prisma. В project-api репозиторий минимален (create + findUnique), в ai-agent-service — полнее (create/update/find*/delete/count).

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
end

⚠️ Отсутствует. В 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/historypage, 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, enum WalriderMessageRole, связь 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.tscreate() создаёт тред вместе с проектом (в транзакции).
  • .../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.tssendMessage, 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.tsRabbitRequestMessage / 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) расширяется миграцией.