EastPay v1 → v2: Техническое задание
Детальный разбор логики legacy-приложения, Supabase-архитектура и стратегия интеграции AI Chat-Agent
1. Обзор системы
EastPay v1 — Telegram Mini App для обмена валют и зарубежных платежей, построенная на связке PHP + MySQL + Telegram Bot API.
Технологический стек (старый)
| Компонент | Технология |
|---|---|
| Frontend | Vanilla JS, HTML5, CSS3, Chart.js |
| Backend | PHP (native, без фреймворков) |
| БД | MySQL / MariaDB (PDO) |
| Интеграция | Telegram Bot API (Webhook) |
| Хостинг | Классический shared hosting (eastpaycripto.ru) |
Технологический стек (новый — v2)
| Компонент | Технология |
|---|---|
| Frontend | React, Vite, Tailwind v4, Shadcn/UI |
| Backend | Supabase (Auth, DB, Edge Functions, Realtime) |
| AI Orchestration | n8n (Telegram AI Agent workflow) |
| LLM | OpenRouter (GPT-4o-mini) + Google Gemini (image/video) |
| Монорепа | Nx |
| Интеграция | Telegram Bot API (через n8n Webhook) |
| Хостинг | Netlify + Supabase Cloud |
2. Текущая Supabase-архитектура (проект cvzsgjksswowqgfxvrsb)
2.1. Схема базы данных — 12 таблиц
erDiagram
clients ||--o{ orders : creates
clients ||--o{ chat_logs : has_chat
clients ||--o| clients : "referrer_id"
profiles ||--o{ orders : manages
locations ||--o{ orders : located_in
locations ||--o{ market_rates : has_rates
locations ||--o{ profiles : assigned_to
instruments ||--o{ orders : uses
instruments ||--o{ market_rates : priced_in
orders ||--o{ chat_logs : has_messages
clients {
bigint id PK
text telegram_id UK
text telegram_username
text full_name
text first_name
text last_name
text phone
text email
text verification_status "unverified"
text risk_level "low"
numeric total_volume_usd "0"
int deals_count "0"
text notes
boolean is_ai_paused "false"
text passport_series
text passport_number
text_arr passport_files
bigint referrer_id FK
boolean is_ambassador "false"
timestamptz created_at
}
profiles {
uuid id PK "FK auth.users"
text email
text full_name
user_role role "pending"
bigint assigned_location_id FK
text telegram_username
text status "active"
text avatar_url
timestamptz created_at
}
orders {
bigint id PK
bigint client_id FK
uuid manager_id FK
bigint location_id FK
bigint instrument_id FK
deal_direction direction "buy|sell"
numeric amount_give
text currency_give
numeric amount_get
text currency_get
numeric exchange_rate
numeric cost_price_rate
numeric service_fee_amount
numeric estimated_profit_usdt
order_status status "new"
text delivery_method
text meeting_address
text client_wallet_address
text client_bank_details
text manager_comment
text payment_link
timestamptz created_at
timestamptz updated_at
}
locations {
bigint id PK
text name
text country
text currency_local
text timezone "UTC"
boolean is_active "true"
numeric surcharge_percent "0"
text partner_tag
}
instruments {
bigint id PK
text name
text description
text base_currency
boolean is_active "true"
text cost_rate_expression
numeric fixed_fee_usdt "0"
}
market_rates {
bigint id PK
bigint location_id FK
bigint instrument_id FK
numeric cost_price_percent "0"
numeric selling_rate_percent "0"
numeric min_amount "0"
timestamptz updated_at
}
daily_rates {
text symbol PK
numeric rate "0"
boolean is_active "true"
timestamptz updated_at
}
chat_logs {
bigint id PK
bigint client_id FK
bigint order_id FK
text sender
text message_content
boolean is_read "false"
timestamptz created_at
}
message_templates {
bigint id PK
text title
text category
text content
text language "ru"
text_arr trigger_keywords
boolean is_active "true"
}
audit_logs {
bigint id PK
uuid user_id FK
text action
text table_name
text record_id
jsonb old_data
jsonb new_data
text method
jsonb payload
text ip_address
text user_agent
text severity "info"
text entity_type
timestamptz created_at
}
documents {
bigint id PK
text content
jsonb metadata
vector embedding
}
project_activity {
bigint id PK
text stat_name
jsonb stat_value
text description
timestamptz timestamp
}
2.2. Enums (типы данных)
| Enum | Значения |
|---|---|
order_status |
new, calculated, payment_pending, processing, delivery, office, done, cancelled, dispute |
deal_direction |
buy, sell |
user_role |
admin, operator, city_manager, pending, super_admin |
2.3. RLS-политики (Row Level Security)
| Таблица | RLS | Примечание |
|---|---|---|
clients |
✅ Вкл | Менеджеры видят клиентов |
orders |
✅ Вкл | Менеджеры — свои заявки; клиенты — через API |
chat_logs |
✅ Вкл | Привязка к client_id / order_id |
market_rates |
✅ Вкл | Чтение для всех, запись для админов |
daily_rates |
✅ Вкл | Чтение для всех, запись для админов |
audit_logs |
✅ Вкл | Только super_admin |
profiles |
✅ Вкл | Каждый видит свой профиль |
locations |
❌ Откл | Публичные данные |
instruments |
❌ Откл | Публичные данные |
documents |
❌ Откл | Внутреннее использование (RAG) |
message_templates |
❌ Откл | Публичные шаблоны |
project_activity |
❌ Откл | Системный мониторинг |
2.4. Существующие RPC-функции (Supabase Functions)
| Функция | Возврат | Назначение |
|---|---|---|
bot_create_order(p_tg_id, p_city_name, p_amount, p_currency_pair) |
json |
Создание заявки из Telegram бота — находит/создаёт пользователя, рассчитывает сделку, вставляет в orders |
bot_log_chat_message(p_tg_id, p_content, p_sender) |
void |
Логирование сообщения в chat_logs по telegram_id клиента |
calculate_deal(in_city_name, in_amount, in_base_pair) |
json |
Расчёт сделки: base rate × (1 + city surcharge), возвращает {rate, amount_get, currency_get} |
get_active_cities_for_bot() |
TABLE(name) |
Список активных городов для бота |
handle_new_user() |
trigger |
Автоматическое создание profiles при регистрации в auth.users |
process_audit_log() |
trigger |
Автоматическая запись в audit_logs при INSERT/UPDATE/DELETE |
is_admin() |
boolean |
Проверка роли admin/super_admin |
is_operator_level() |
boolean |
Проверка роли operator и выше |
is_staff() |
boolean |
Проверка, что пользователь — сотрудник |
record_project_heartbeat() |
void |
Записывает heartbeat в project_activity (cron) |
match_documents(query_embedding, match_threshold, match_count, filter_category) |
TABLE(id, content, metadata, similarity) |
Семантический поиск по векторной БД (RAG). См. Раздел 12.5 |
2.5. Edge Functions (развёрнутые)
| Slug | JWT | Назначение |
|---|---|---|
invite-user |
❌ Нет | Приглашение пользователя в систему (отправка email) |
logger-service |
✅ Да | Сервис логирования |
2.6. Cron-задачи (pg_cron)
| Задача | Расписание | SQL |
|---|---|---|
supabase-keep-alive-job |
Каждые 12 ч (0 */12 * * *) |
SELECT public.record_project_heartbeat() |
cleanup-keep-alive-logs |
Еженедельно (0 0 * * 0) |
DELETE FROM project_activity WHERE timestamp < now() - interval '90 days' |
2.7. Расширения PostgreSQL
Ключевые установленные расширения:
- pgvector — Векторный поиск (для RAG / embeddings)
- pg_cron — Cron-задачи внутри PostgreSQL
- uuid-ossp — Генерация UUID
- pgjwt — JWT-токены в PostgreSQL
3. Текущая реализация AI-Агента в n8n
3.1. Обзор Workflow «Telegram AI Agent Base»
[!IMPORTANT] Текущая архитектура: AI-агент реализован и работает через n8n (workflow ID:
YiKrHeitj7K100Vd). Это ключевой компонент системы, который координирует Telegram ↔ AI ↔ Supabase.
flowchart LR
TG["📱 Telegram Trigger"]
SW{"🔀 Switch"}
WELCOME["👋 Send Welcome"]
AUDIO["🎵 Get Audio File"]
PHOTO["📷 Get Photo"]
VIDEO["🎬 Get Video"]
TRANSCRIBE["🗣️ OpenAI Transcribe"]
ANALYZE_IMG["🧠 Gemini: Analyze Image"]
ANALYZE_VID["🧠 Gemini: Analyze Video"]
MERGE["🔗 Merge"]
FIELDS["📝 Edit Fields"]
AGENT["🤖 AI Agent"]
SEND["📤 Send to Telegram"]
TG --> SW
SW -->|"/start"| WELCOME
SW -->|"voice"| AUDIO --> TRANSCRIBE --> MERGE
SW -->|"photo"| PHOTO --> ANALYZE_IMG --> MERGE
SW -->|"video"| VIDEO --> ANALYZE_VID --> MERGE
SW -->|"text"| MERGE
MERGE --> FIELDS --> AGENT --> SEND
subgraph "AI Agent Tools"
T1["📊 get_rates_tool"]
T2["🏙️ get_locations_tool"]
T3["🧮 calculate_deal_tool"]
T4["📋 create_order_tool"]
T5["💬 log_chat_tool"]
T6["📅 Date & Time"]
end
AGENT -.-> T1 & T2 & T3 & T4 & T5 & T6
subgraph "LLM & Memory"
LLM["OpenRouter GPT-4o-mini"]
MEM["Simple Memory (50 msg)"]
end
LLM -.-> AGENT
MEM -.-> AGENT
3.2. Компоненты Workflow
| Нода | Тип | Конфигурация |
|---|---|---|
| Telegram Trigger | telegramTrigger |
Webhook, слушает message events, credentials: EastPay |
| Switch | switch |
5 веток: /start, voice, photo, video, text |
| Send Welcome | telegram |
HTML-сообщение + 2 inline-кнопки: «Совершить обмен» (URL) и «Чат с поддержкой» (URL) |
| Get Audio/Photo/Video | telegram |
Скачивание файлов через Bot API file_id |
| Transcribe | openAi |
OpenAI Whisper для транскрипции голосовых |
| Analyze Image/Video | googleGemini |
Gemini 2.5 Flash для анализа медиа (с caption) |
| Merge | merge |
4 входа → унифицированный поток |
| Edit Fields | set |
Формирует User Input (текст) и telegram_id (из from.id) |
| AI Agent | agent |
OpenRouter LLM + 6 инструментов + Simple Memory (50 сообщений) |
| Send Regular Message | telegram |
Отправляет AI Agent.output обратно в чат |
3.3. System Prompt AI-агента (текущая версия)
Вы — старший менеджер EastPay. Вы общаетесь с клиентами в Telegram.
Ваши задачи:
1. Квалифицировать клиента: Город, Сумма, Валютная пара.
2. Предоставлять актуальные курсы: get_rates_tool / calculate_deal_tool.
3. Создавать заявки: create_order_tool (когда клиент согласен).
4. Поддержка: log_chat_tool для сохранения сообщений менеджеру.
Правила:
- Язык: СТРОГО Русский.
- Стиль: Профессиональный, деловой, но дружелюбный.
- Никаких выдуманных данных. Только инструменты.
- Голосовые транскрибированы — работать как с текстом.
3.4. Инструменты AI-агента (Tool Bindings → Supabase)
| n8n Tool | Supabase RPC / Table | Параметры | Описание |
|---|---|---|---|
get_rates_tool |
daily_rates (getAll) |
— | Получить все актуальные курсы |
get_locations_tool |
locations (getAll) |
— | Получить все доступные города |
calculate_deal_tool |
calculate_deal() RPC |
in_city_name, in_amount, in_base_pair |
Рассчитать сделку с наценкой города |
create_order_tool |
bot_create_order() RPC |
p_tg_id, p_city_name, p_amount, p_currency_pair |
Создать заявку от клиента |
log_chat_tool |
bot_log_chat_message() RPC |
p_tg_id, p_content |
Записать сообщение для менеджера |
Date & Time |
(встроенный) | — | Текущая дата/время |
[!WARNING] Текущие ограничения n8n-реализации:
Simple Memory(50 сообщений) хранится в оперативной памяти n8n, не в Supabase — при перезапуске n8n контекст теряется.bot_create_orderссылается на таблицуusers, которой нет в текущей Supabase-схеме (естьclients). Нужна синхронизация.- Нет привязки
order_idк чат-логам —log_chat_toolпишет вchat_logsбезorder_id.- Нет обратной связи: если менеджер ответил из админки — ответ не доставляется в Telegram.
4. Путь клиента (Customer Journey) — Детальный разбор
4.1. Точка входа: Telegram бот
Команда /start:
- Пользователь находит бота (ссылка, реферал, поиск).
- n8n Switch → ветка «/start» → Send Welcome Message (HTML).
- Сообщение: «EAST PAY — Обмен валют и зарубежные платежи» + 2 кнопки:
[Совершить обмен]→ URL на Mini App[Чат с поддержкой]→ URL на @eastpay_support
Любое другое текстовое сообщение:
- n8n Switch → ветка «text» → Merge → Edit Fields → AI Agent.
- AI-агент обрабатывает, вызывает tools, отвечает.
[!IMPORTANT] Проблема v1: Нет промежуточных шагов между
/startи кнопкой Mini App. Нет онбординга, нет FAQ.
4.2. Mini App: Навигация (6 табов)
| # | Таб | Функция | Обязательность |
|---|---|---|---|
| 1 | Курсы | Просмотр актуальных курсов + график динамики | Обязательный первый экран |
| 2 | Обмен | Калькулятор и создание заявки на обмен | Основная функция |
| 3 | WeChat / Alipay | Пополнение китайских кошельков | Дополнительная функция |
| 4 | Профиль | Контакты, KYC, история заявок, чат по заявкам | Личный кабинет |
| 5 | Реферальная система | Ссылка, статистика, заработок | Монетизация |
| 6 | Поддержка | Чат с менеджером | Сервис |
4.3. Сценарий «Обмен валюты» (Core Flow)
Шаг 1: Просмотр курсов
- Курсы из
daily_rates(v2) /rates(legacy). - Обновление через Supabase Realtime (v2) вместо polling (v1).
Шаг 2: Заполнение формы
- Тип операции:
cash/noncash. - Город (из
locationsчерез RPCget_active_cities_for_bot()). - Направление (валютная пара из
daily_rates). - Сумма + единица.
Шаг 3: Калькулятор
finalRate = daily_rates.rate × (1 + locations.surcharge_percent / 100)
amount_get = amount_give / finalRate
В v2 реализовано через RPC calculate_deal().
Шаг 4: Отправка заявки
- Mini App → Supabase
ordersINSERT (или через n8ncreate_order_tool). - Заявка создаётся со статусом
new. - n8n должен уведомить менеджеров через Telegram Bot API.
Шаг 5–7: Обработка, чат, завершение
Аналогично legacy (описано выше), но с Supabase Realtime вместо polling.
5. Архитектура чата (JSONB + Realtime)
5.1. Проблема текущей реализации
Текущая таблица chat_logs хранит сообщения как простой текст:
-- Текущая структура (упрощённая)
chat_logs (
id BIGINT,
client_id BIGINT, -- FK → clients
order_id BIGINT, -- FK → orders (nullable)
sender TEXT, -- 'client' | 'manager' | 'ai_agent'
message_content TEXT,-- Простой текст
is_read BOOLEAN,
created_at TIMESTAMPTZ
)
Проблемы:
- Нет структуры сообщения (фото, голосовые, кнопки, файлы).
- Нет metadata (intent, tool_calls, token usage).
- Нет thread/conversation_id для группировки диалогов.
- Нет синхронизации между Telegram ↔ Личный кабинет.
5.2. Целевая архитектура: таблица conversations + messages
-- Таблица диалогов (sessions)
CREATE TABLE conversations (
id UUID DEFAULT gen_random_uuid() PRIMARY KEY,
client_id BIGINT NOT NULL REFERENCES clients(id),
order_id BIGINT REFERENCES orders(id), -- NULL = общий чат, NOT NULL = чат по заявке
type TEXT NOT NULL DEFAULT 'support'
CHECK (type IN ('support', 'order', 'ai_consultation', 'onboarding')),
status TEXT NOT NULL DEFAULT 'active'
CHECK (status IN ('active', 'resolved', 'escalated', 'archived')),
assigned_manager_id UUID REFERENCES profiles(id),
ai_enabled BOOLEAN DEFAULT true, -- AI-агент включён для этого чата
metadata JSONB DEFAULT '{}', -- Произвольные данные: {source, campaign, etc}
created_at TIMESTAMPTZ DEFAULT now(),
updated_at TIMESTAMPTZ DEFAULT now(),
last_message_at TIMESTAMPTZ DEFAULT now()
);
-- Таблица сообщений (JSONB-формат, как ChatGPT)
CREATE TABLE messages (
id UUID DEFAULT gen_random_uuid() PRIMARY KEY,
conversation_id UUID NOT NULL REFERENCES conversations(id) ON DELETE CASCADE,
-- Роль отправителя (как в OpenAI Chat API)
role TEXT NOT NULL CHECK (role IN ('user', 'assistant', 'system', 'manager', 'tool')),
-- Основной контент (JSONB — поддержка multimodal)
content JSONB NOT NULL,
-- Формат content для разных типов:
-- Текст: {"type": "text", "text": "Привет!"}
-- Фото: {"type": "image", "url": "https://...", "caption": "Чек", "file_id": "..."}
-- Голос: {"type": "voice", "url": "https://...", "transcript": "...", "duration": 12}
-- Видео: {"type": "video", "url": "https://...", "analysis": "...", "file_id": "..."}
-- Документ: {"type": "document", "url": "https://...", "filename": "passport.pdf"}
-- Tool: {"type": "tool_result", "tool_name": "calculate_deal", "result": {...}}
-- System: {"type": "system", "text": "Заявка #42 создана"}
-- Multi: {"type": "multi", "parts": [{"type":"text","text":"..."}, {"type":"image","url":"..."}]}
-- AI-метаданные
metadata JSONB DEFAULT '{}',
-- Формат metadata:
-- {
-- "intent": "exchange_request",
-- "model": "gpt-4o-mini",
-- "tokens": {"input": 150, "output": 80},
-- "tool_calls": [{"name": "calculate_deal", "args": {...}}],
-- "telegram_message_id": 12345,
-- "source": "telegram" | "webapp" | "admin_panel",
-- "n8n_execution_id": "abc-123"
-- }
-- Статус доставки
is_read BOOLEAN DEFAULT false,
delivered_to_telegram BOOLEAN DEFAULT false,
delivered_to_webapp BOOLEAN DEFAULT false,
created_at TIMESTAMPTZ DEFAULT now()
);
-- Индексы для быстрого доступа
CREATE INDEX idx_messages_conversation ON messages(conversation_id, created_at DESC);
CREATE INDEX idx_messages_role ON messages(role) WHERE role = 'user';
CREATE INDEX idx_conversations_client ON conversations(client_id);
CREATE INDEX idx_conversations_order ON conversations(order_id);
CREATE INDEX idx_messages_unread ON messages(conversation_id, is_read) WHERE is_read = false;
5.3. Синхронизация Telegram ↔ Личный кабинет ↔ Админ-панель
sequenceDiagram
participant TG as 📱 Telegram
participant N8N as ⚙️ n8n
participant SB as 🗄️ Supabase
participant RT as 🔴 Realtime
participant WEB as 🖥️ Личный кабинет
participant ADM as 👨💼 Админ-панель
Note over TG,ADM: === Клиент пишет в Telegram ===
TG->>N8N: Webhook: message
N8N->>N8N: AI Agent обрабатывает
N8N->>SB: INSERT messages (role='user', content=JSONB)
N8N->>SB: INSERT messages (role='assistant', content=JSONB)
SB->>RT: postgres_changes event
RT-->>WEB: Subscription: new message
RT-->>ADM: Subscription: new message
N8N->>TG: sendMessage (AI ответ)
Note over TG,ADM: === Менеджер пишет из Админки ===
ADM->>SB: INSERT messages (role='manager', source='admin_panel')
SB->>RT: postgres_changes event
RT-->>WEB: Subscription: new message
SB-->>N8N: Webhook / Edge Function trigger
N8N->>TG: sendMessage (ответ менеджера)
Note over TG,ADM: === Клиент пишет из ЛК ===
WEB->>SB: INSERT messages (role='user', source='webapp')
SB->>RT: postgres_changes event
RT-->>ADM: Subscription: new message
SB-->>N8N: Webhook / Edge Function trigger
N8N->>TG: Mirror message to Telegram (если нужно)
5.4. Supabase Realtime — конфигурация каналов
// Подписка на сообщения конкретного чата (Личный кабинет / Админка)
const channel = supabase
.channel(`conversation:${conversationId}`)
.on(
'postgres_changes',
{
event: 'INSERT',
schema: 'public',
table: 'messages',
filter: `conversation_id=eq.${conversationId}`
},
(payload) => {
// Мгновенно отобразить новое сообщение в UI
appendMessage(payload.new);
}
)
.on(
'postgres_changes',
{
event: 'UPDATE',
schema: 'public',
table: 'messages',
filter: `conversation_id=eq.${conversationId}`
},
(payload) => {
// Обновить статус прочтения, доставки
updateMessageStatus(payload.new);
}
)
.subscribe();
// Подписка на все чаты менеджера (Админ-панель — лента)
const managerChannel = supabase
.channel('manager-feed')
.on(
'postgres_changes',
{
event: 'INSERT',
schema: 'public',
table: 'messages',
filter: `role=in.(user,assistant)`
},
(payload) => {
updateChatList(payload.new);
}
)
.subscribe();
6. Интеграция курсов: daily_rates ↔ Telegram ↔ Mini App
6.1. Текущая схема курсов
daily_rates (symbol PK, rate, is_active, updated_at)
Данные (7 записей):
- RUB/THB, RUB/CNY, RUB/USD, RUB/AED, RUB/USDT и т.д.
6.2. Цепочка обновления курсов
flowchart TD
ADMIN["👨💼 Админ-панель\n(обновляет daily_rates)"]
SB["🗄️ Supabase\n(daily_rates)"]
RT["🔴 Realtime\n(postgres_changes)"]
MINIAPP["📱 Mini App\n(Realtime subscription)"]
CRON["⏰ pg_cron\n(проверка изменений)"]
N8N["⚙️ n8n\n(get_rates_tool)"]
TG["📱 Telegram\n(ответ AI-агента)"]
ALERT["📢 Rate Alert\n(Edge Function)"]
ADMIN -->|"UPDATE rate"| SB
SB -->|"Мгновенно"| RT
RT -->|"ws: INSERT/UPDATE"| MINIAPP
SB -->|"Каждые 5 мин"| CRON
CRON -->|"Если изменение > 3%"| ALERT
ALERT -->|"sendMessage"| TG
N8N -->|"SELECT * FROM daily_rates"| SB
N8N -->|"AI-ответ с курсом"| TG
6.3. Подписка Mini App на курсы (Realtime)
// Mini App: подписка на актуальные курсы
const ratesChannel = supabase
.channel('rates-live')
.on(
'postgres_changes',
{
event: '*', // INSERT, UPDATE, DELETE
schema: 'public',
table: 'daily_rates'
},
(payload) => {
updateRatesUI(payload.new); // Мгновенное обновление UI
}
)
.subscribe();
7. Edge Functions и Backend-функции: план реализации
7.1. Существующие Edge Functions
| Функция | Статус | Описание |
|---|---|---|
invite-user |
✅ Active | Приглашение пользователя |
logger-service |
✅ Active | Логирование |
7.2. Необходимые новые Edge Functions
| # | Edge Function | Триггер | Назначение |
|---|---|---|---|
| 1 | telegram-webhook |
HTTP POST (Telegram Webhook) | Принимает Telegram updates → пересылает в n8n ИЛИ обрабатывает самостоятельно |
| 2 | send-telegram-message |
HTTP POST (internal) | Отправка сообщений в Telegram от имени бота. Вызывается из админки/ЛК при ответе менеджера |
| 3 | rate-alert-notifier |
pg_cron (каждые 5 мин) | Проверяет изменение курсов > threshold%, отправляет уведомления подписавшимся клиентам |
| 4 | retention-scheduler |
pg_cron (ежечасно) | Проверяет «замёрзших» пользователей, отправляет follow-up сообщения |
| 5 | order-status-notifier |
Database Webhook (trigger на orders.status) |
При смене статуса заявки — уведомляет клиента в Telegram |
| 6 | sync-chat-to-telegram |
Database Webhook (trigger на INSERT в messages) |
Когда менеджер пишет из админки → доставляет в Telegram |
| 7 | process-media-upload |
Storage trigger (новый файл в bucket) | Обработка загруженных файлов (KYC, чеки) — OCR, валидация |
7.3. Необходимые новые RPC-функции (PostgreSQL)
| # | Функция | Параметры | Назначение |
|---|---|---|---|
| 1 | get_or_create_conversation(p_client_id, p_order_id, p_type) |
client_id, order_id?, type | Найти или создать диалог |
| 2 | save_chat_message(p_conversation_id, p_role, p_content_jsonb, p_metadata_jsonb) |
conversation_id, role, content, metadata | Сохранить сообщение в JSONB-формате |
| 3 | get_conversation_history(p_conversation_id, p_limit, p_offset) |
conversation_id, limit, offset | Получить историю чата с пагинацией |
| 4 | mark_messages_read(p_conversation_id, p_reader_role) |
conversation_id, reader_role | Пометить сообщения как прочитанные |
| 5 | get_client_conversations(p_client_id) |
client_id | Все диалоги клиента с last_message preview |
| 6 | get_unread_count(p_conversation_id, p_reader_role) |
conversation_id, reader_role | Количество непрочитанных сообщений |
| 7 | check_rate_changes() |
— | Сравнить текущие курсы с предыдущими, вернуть changed > threshold |
| 8 | get_stale_sessions(p_timeout_minutes) |
timeout_minutes | Найти клиентов без активности дольше N минут |
7.4. Необходимые новые pg_cron задачи
| # | Задача | Расписание | SQL |
|---|---|---|---|
| 1 | check-rate-alerts |
*/5 * * * * (каждые 5 мин) |
SELECT net.http_post('EDGE_FN_URL/rate-alert-notifier', ...) |
| 2 | run-retention |
0 10 * * * (ежедневно в 10:00) |
SELECT net.http_post('EDGE_FN_URL/retention-scheduler', ...) |
| 3 | archive-old-conversations |
0 3 * * 0 (раз в неделю) |
UPDATE conversations SET status='archived' WHERE last_message_at < now() - interval '90 days' |
8. Стратегия интеграции AI Chat-Agent
8.1. Концепция: «Невидимый сопроводитель»
AI Chat-Agent НЕ заменяет кнопочный флоу. Он:
- Молчит, когда клиент справляется сам.
- Включается при «замерзании», вопросах или необходимости помощи.
- Выполняет функции менеджера первой линии.
8.2. Точки включения
graph TD
A["🟢 /start"] --> B{"Действие?"}
B -->|"Нажал «Обмен»"| C["Mini App"]
B -->|"❄️ Молчит 3 мин"| D["🤖 AI: Приветствие"]
B -->|"Пишет вопрос"| E["🤖 AI: Консультация"]
C --> F{"Действия?"}
F -->|"Заполняет форму"| G["✅ Бот молчит"]
F -->|"❄️ Закрыл Mini App"| H["🤖 Follow-up 5 мин"]
G --> I["Заявка создана"]
I --> J{"Статус?"}
J -->|"new > 10 мин"| K["🤖 «Менеджер скоро возьмёт»"]
J -->|"processing"| L["🤖 Мониторит"]
J -->|"done"| M["🤖 Запрос отзыва"]
J -->|"cancelled"| N["🤖 «Новая заявка?»"]
M --> O["🤖 Ретеншн D+7/14/30"]
8.3. Функции Chat-Agent
Функция 1: Онбординг — Таймаут 3 мин после /start без действия.
Функция 2: Консультация — Intent detection через LLM: question → AI отвечает, exchange_request → Mini App, complaint → эскалация менеджеру.
Функция 3: Follow-up — 5 триггеров с таймаутами (от 5 до 30 минут).
Функция 4: Deal Escort — Уведомления при смене статуса заявки (new → processing → done → ретеншн).
Функция 5: Ретеншн — Периодические напоминания (D+1, D+7, D+14, D+30), курсовые алерты (>3%), реферальные пуши.
9. Принцип «Кнопки vs AI»
| Ситуация | Инструмент | Почему |
|---|---|---|
| Выбор валюты, города, суммы | 🔘 Кнопки / Mini App | Быстрее, точнее |
| Расчёт суммы | 🔘 Калькулятор | Мгновенно |
| Смена статуса | 🔘 Inline-кнопки | Одно нажатие |
| «Какой курс бата?» | 🤖 AI Agent | Естественный язык |
| Клиент молчит 5 мин | 🤖 AI Agent | Follow-up |
| Клиент не был 2 недели | 🤖 AI Agent | Ретеншн |
| Жалоба | 🤖 AI → 👨💼 Менеджер | Эскалация |
10. Необходимые доработки (Action Items)
[!CAUTION] Критические проблемы для исправления:
10.1. Синхронизация n8n ↔ Supabase
| # | Проблема | Решение |
|---|---|---|
| 1 | bot_create_order ссылается на таблицу users (не существует) |
Обновить RPC: заменить users → clients, tg_id → telegram_id (text) |
| 2 | Simple Memory в n8n хранится в RAM |
Мигрировать на Supabase-backed memory: сохранять в messages таблицу |
| 3 | log_chat_tool не привязывает к conversation/order |
Обновить RPC: добавить p_conversation_id, p_order_id параметры |
| 4 | Нет обратной связи Admin → Telegram | Добавить Edge Function sync-chat-to-telegram + trigger на INSERT в messages |
10.2. Миграция структуры чата
| # | Действие | Приоритет |
|---|---|---|
| 1 | Создать таблицы conversations + messages |
🔴 Высокий |
| 2 | Мигрировать данные из chat_logs → messages |
🟡 Средний |
| 3 | Обновить n8n workflow: записывать в messages (JSONB) |
🔴 Высокий |
| 4 | Настроить Supabase Realtime на messages |
🔴 Высокий |
| 5 | Обновить RLS-политики для conversations и messages |
🔴 Высокий |
10.3. Новые Edge Functions (приоритизация)
| Приоритет | Edge Function | Когда |
|---|---|---|
| 🔴 P0 | send-telegram-message |
Фаза 1 — без этого менеджер не ответит в TG |
| 🔴 P0 | order-status-notifier |
Фаза 1 — клиент должен знать про смену статуса |
| 🟡 P1 | sync-chat-to-telegram |
Фаза 2 — двусторонний чат |
| 🟡 P1 | rate-alert-notifier |
Фаза 2 — повышение вовлечённости |
| 🟢 P2 | retention-scheduler |
Фаза 4 — ретеншн |
| 🟢 P2 | process-media-upload |
Фаза 5 — KYC automation |
11. Следующие шаги реализации
Фаза 1: Фундамент (DB + Chat)
- Создать таблицы
conversations+messages(миграция) - Обновить RPC
bot_create_order(users → clients) - Создать RPC
save_chat_message,get_conversation_history - Настроить Supabase Realtime на
messages - Развернуть Edge Function
send-telegram-message - Развернуть Edge Function
order-status-notifier
Фаза 2: Синхронизация n8n ↔ Supabase ↔ Telegram + RAG
- Обновить n8n workflow: писать в
messages(JSONB-формат) - Реализовать Supabase-backed memory (вместо Simple Memory)
- Edge Function
sync-chat-to-telegram(ответы менеджера → TG) - Подписка на
daily_ratesв Mini App (Realtime) - Edge Function
rate-alert-notifier+ pg_cron задача - RAG: Создать RPC
match_documents+ HNSW индекс наdocuments.embedding - RAG: Edge Function
embed(автоматическая генерация embeddings) - RAG: Наполнить
documentsбазовыми знаниями (FAQ, услуги, правила) - RAG: Интегрировать RAG-поиск в n8n AI Agent workflow
Фаза 3: Mini App v2 (React)
- React Mini App с Vite + Tailwind v4 + Shadcn/UI
- Чат в ЛК с Realtime (подписка на
messages) - Возможность отвечать из ЛК → доставка в Telegram
- Таб «Курсы» с Realtime подписками
- Таб «Обмен» (калькулятор + создание заявки)
Фаза 4: AI Agent v2 (Follow-up + Retention)
- pg_cron для проверки «замёрзших» пользователей
- Ретеншн-логика (D+1, D+7, D+14, D+30)
- Курсовые нотификации (>3% изменение)
- Антиспам (лимиты,
/stop) -
user_preferencesтаблица
Фаза 5: Продвинутые функции
- KYC через OCR (Ainoflow Convert)
- Реферальная система v2
-
RAG-контекст для AI Agent→ Перенесено в Фазу 2 (см. Раздел 12) - Панель аналитики для менеджеров v2
- Гибридный поиск (vector + full-text) для AI Agent
- Автоматическое резюмирование длинных диалогов →
documents(conversation_summary)
12. RAG & Vector Database архитектура
[!IMPORTANT] RAG (Retrieval-Augmented Generation) — ключевая технология для AI Agent'а EastPay. Она позволяет агенту обращаться к актуальной базе знаний компании перед генерацией ответа, снижая галлюцинации и обеспечивая точность информации о курсах, услугах и правилах.
12.1. Обзор RAG-архитектуры
flowchart LR
subgraph INPUT["📥 Входящий запрос"]
USER_MSG["💬 Сообщение клиента"]
end
subgraph EMBED["🧮 Embedding"]
EMBED_Q["OpenAI text-embedding-3-small<br/>→ vector(1536)"]
end
subgraph SEARCH["🔍 Vector Search"]
MATCH["match_documents()<br/>cosine similarity ≥ 0.78"]
DOCS["📄 Релевантные документы<br/>(top 5)"]
end
subgraph GENERATE["🤖 Генерация ответа"]
CONTEXT["📋 System prompt +<br/>RAG-контекст +<br/>История чата"]
LLM["GPT-4o-mini<br/>(OpenRouter)"]
RESPONSE["📨 Ответ клиенту"]
end
USER_MSG --> EMBED_Q
EMBED_Q --> MATCH
MATCH --> DOCS
DOCS --> CONTEXT
USER_MSG --> CONTEXT
CONTEXT --> LLM
LLM --> RESPONSE
Зачем RAG для EastPay:
| Проблема без RAG | Решение с RAG |
|---|---|
| AI не знает текущие курсы | Подтягивает данные из daily_rates + контексты из documents |
| AI выдумывает условия обмена | Берёт реальные правила из базы знаний |
| Нет информации о городах/офисах | Ищет по категории location_info |
| Забывает историю клиента | Суммарии прошлых диалогов в conversation_summary |
| Не знает AML/KYC правила | Ищет по категории compliance |
12.2. Supabase как Vector Database
pgvector — конфигурация
-- Расширение уже установлено в проекте cvzsgjksswowqgfxvrsb
-- Поддерживаемые типы: vector, halfvec, sparsevec
-- Текущая таблица documents (существует, 0 строк):
-- id bigint PK (identity)
-- content text
-- metadata jsonb
-- embedding vector (без ограничения размерности)
Целевая схема таблицы documents
-- Обновление таблицы documents (добавление ограничений и колонок)
ALTER TABLE documents
ADD COLUMN IF NOT EXISTS created_at TIMESTAMPTZ DEFAULT now(),
ADD COLUMN IF NOT EXISTS updated_at TIMESTAMPTZ DEFAULT now(),
ADD COLUMN IF NOT EXISTS source TEXT DEFAULT 'manual',
ADD COLUMN IF NOT EXISTS is_active BOOLEAN DEFAULT true;
-- Ограничение размерности embedding (1536 = text-embedding-3-small)
-- Примечание: ALTER COLUMN для vector dimension делается через пересоздание
-- при первом заполнении — INSERT с vector(1536)
-- Комментарий к таблице
COMMENT ON TABLE documents IS 'RAG knowledge base — векторная база знаний AI Agent';
COMMENT ON COLUMN documents.content IS 'Текстовый контент документа (чанк)';
COMMENT ON COLUMN documents.metadata IS 'JSONB: {category, title, source_url, chunk_index, total_chunks, language}';
COMMENT ON COLUMN documents.embedding IS 'vector(1536) — OpenAI text-embedding-3-small';
Модель эмбеддингов
| Параметр | Значение |
|---|---|
| Модель | text-embedding-3-small (OpenAI) |
| Размерность | 1536 |
| Метрика расстояния | Cosine similarity (<=> оператор) |
| Тип индекса | HNSW (рекомендуется для < 100k записей) |
| Стоимость | ~$0.02 / 1M tokens |
HNSW-индекс
-- Создать HNSW-индекс для косинусного расстояния
CREATE INDEX idx_documents_embedding_hnsw
ON documents
USING hnsw (embedding vector_cosine_ops)
WITH (m = 16, ef_construction = 64);
-- m = 16 — кол-во связей на узел (больше = точнее, больше RAM)
-- ef_construction = 64 — точность построения (больше = медленнее build, точнее поиск)
-- Для < 10k документов эти значения оптимальны
12.3. Knowledge Base Categories (категории знаний)
Все документы хранятся в таблице documents, категоризованные через metadata->>'category':
mindmap
root((📚 Knowledge Base))
company_info
FAQ
Условия обслуживания
О компании
Лицензии
service_docs
Инструкции по обмену
Лимиты и комиссии
KYC требования
Способы доставки
rate_context
Рыночные комментарии
Прогнозы курсов
Волатильность
location_info
Адреса офисов
Часы работы
Контакты по городам
Доступные валюты
compliance
AML правила
Ограничения по суммам
Запрещённые операции
Верификация
conversation_summary
AI-резюме длинных чатов
Профиль предпочтений клиента
Формат metadata JSONB
{
"category": "company_info",
"title": "FAQ — Часто задаваемые вопросы",
"language": "ru",
"source": "manual",
"source_url": null,
"chunk_index": 0,
"total_chunks": 1,
"tags": ["faq", "general"],
"version": "1.0",
"last_reviewed": "2026-02-10"
}
Пример начальных документов
| # | metadata.category |
metadata.title |
content (чанк) |
|---|---|---|---|
| 1 | company_info |
FAQ — Что такое EastPay? | EastPay — сервис обмена валют и зарубежных платежей. Работаем в Таиланде, Китае, ОАЭ... |
| 2 | service_docs |
Инструкция — Обмен RUB → THB | Шаг 1: Выберите город. Шаг 2: Укажите сумму в рублях... |
| 3 | service_docs |
Лимиты и комиссии | Минимальная сумма обмена: от $100. Комиссия: от 1.5%... |
| 4 | location_info |
Бангкок — информация | Офис: Sukhumvit Rd. Часы работы: 10:00-20:00 (ICT)... |
| 5 | compliance |
AML/KYC правила | При сумме свыше $1000 требуется верификация паспорта... |
| 6 | rate_context |
Текущая ситуация RUB/THB | Курс рубля к бату стабилен на уровне... (обновляется ежедневно) |
12.4. Embedding Pipeline (автоматическая генерация)
sequenceDiagram
participant ADMIN as 👨💼 Админ / n8n
participant DB as 🗄️ Supabase DB
participant QUEUE as 📬 pgmq Queue
participant CRON as ⏰ pg_cron
participant EDGE as ⚡ Edge Function 'embed'
participant OPENAI as 🧠 OpenAI API
ADMIN->>DB: INSERT INTO documents (content, metadata)
DB->>QUEUE: Trigger → pgmq.send('embed_queue', ...)
Note over QUEUE: Сообщение в очереди:<br/>{document_id: 42}
CRON->>QUEUE: Каждые 5 сек: pgmq.read('embed_queue')
QUEUE-->>CRON: {document_id: 42}
CRON->>EDGE: HTTP POST /embed {document_id: 42}
EDGE->>DB: SELECT content FROM documents WHERE id = 42
DB-->>EDGE: "EastPay — сервис обмена..."
EDGE->>OPENAI: POST /v1/embeddings<br/>{model: "text-embedding-3-small", input: "..."}
OPENAI-->>EDGE: [0.0023, -0.0145, 0.0391, ...]
EDGE->>DB: UPDATE documents SET embedding = '[...]' WHERE id = 42
EDGE->>QUEUE: pgmq.delete('embed_queue', msg_id)
SQL: Триггер для очереди
-- Создать очередь для embeddings
SELECT pgmq.create('embed_queue');
-- Триггер: при INSERT в documents — добавить в очередь
CREATE OR REPLACE FUNCTION queue_document_for_embedding()
RETURNS TRIGGER AS $$
BEGIN
-- Только если embedding ещё не сгенерирован
IF NEW.embedding IS NULL THEN
PERFORM pgmq.send(
'embed_queue',
jsonb_build_object(
'document_id', NEW.id,
'retry_count', 0
)
);
END IF;
RETURN NEW;
END;
$$ LANGUAGE plpgsql;
CREATE TRIGGER trg_queue_embedding
AFTER INSERT OR UPDATE OF content ON documents
FOR EACH ROW
EXECUTE FUNCTION queue_document_for_embedding();
Edge Function embed (псевдокод)
// supabase/functions/embed/index.ts
import "jsr:@supabase/functions-js/edge-runtime.d.ts";
import { createClient } from "jsr:@supabase/supabase-js@2";
Deno.serve(async (req: Request) => {
const { document_id } = await req.json();
const supabase = createClient(
Deno.env.get('SUPABASE_URL')!,
Deno.env.get('SUPABASE_SERVICE_ROLE_KEY')!
);
// 1. Получить контент документа
const { data: doc } = await supabase
.from('documents')
.select('content')
.eq('id', document_id)
.single();
if (!doc) return new Response('Not found', { status: 404 });
// 2. Сгенерировать embedding через OpenAI
const embeddingResponse = await fetch('https://api.openai.com/v1/embeddings', {
method: 'POST',
headers: {
'Authorization': `Bearer ${Deno.env.get('OPENAI_API_KEY')}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
model: 'text-embedding-3-small',
input: doc.content,
}),
});
const { data: [{ embedding }] } = await embeddingResponse.json();
// 3. Сохранить embedding в документ
await supabase
.from('documents')
.update({
embedding,
updated_at: new Date().toISOString()
})
.eq('id', document_id);
return new Response(JSON.stringify({ success: true }), {
headers: { 'Content-Type': 'application/json' },
});
});
12.5. Функция семантического поиска match_documents
-- RPC для семантического поиска по базе знаний
CREATE OR REPLACE FUNCTION match_documents(
query_embedding vector(1536),
match_threshold float DEFAULT 0.78,
match_count int DEFAULT 5,
filter_category text DEFAULT NULL
)
RETURNS TABLE (
id bigint,
content text,
metadata jsonb,
similarity float
)
LANGUAGE plpgsql
AS $$
BEGIN
RETURN QUERY
SELECT
d.id,
d.content,
d.metadata,
1 - (d.embedding <=> query_embedding) AS similarity
FROM documents d
WHERE
d.is_active = true
AND d.embedding IS NOT NULL
AND 1 - (d.embedding <=> query_embedding) > match_threshold
AND (filter_category IS NULL OR d.metadata->>'category' = filter_category)
ORDER BY d.embedding <=> query_embedding
LIMIT match_count;
END;
$$;
-- Разрешить вызов через Supabase API
GRANT EXECUTE ON FUNCTION match_documents TO anon, authenticated, service_role;
Пример вызова из n8n / Edge Function
// Шаг 1: Получить embedding запроса пользователя
const embeddingResponse = await fetch('https://api.openai.com/v1/embeddings', {
method: 'POST',
headers: {
'Authorization': `Bearer ${OPENAI_API_KEY}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
model: 'text-embedding-3-small',
input: 'Какой курс рубля к бату в Бангкоке?',
}),
});
const { data: [{ embedding }] } = await embeddingResponse.json();
// Шаг 2: Семантический поиск по базе знаний
const { data: docs } = await supabase
.rpc('match_documents', {
query_embedding: embedding,
match_threshold: 0.78,
match_count: 5,
filter_category: null, // null = искать по всем категориям
});
// Шаг 3: Инжектировать контекст в prompt
const ragContext = docs
.map((d: any) => `[${d.metadata.category}] ${d.content}`)
.join('\n---\n');
const systemPrompt = `
Вы — AI-ассистент EastPay. Используйте следующий контекст для ответа:
${ragContext}
Если информация не найдена в контексте — честно скажите, что не знаете.
`;
12.6. Интеграция RAG с AI Agent (n8n)
flowchart TD
subgraph TELEGRAM["📱 Telegram"]
MSG["Сообщение клиента"]
end
subgraph N8N["⚙️ n8n Workflow"]
TRIGGER["Telegram Trigger"]
SWITCH{{"Switch: тип сообщения"}}
subgraph RAG_PIPELINE["🔍 RAG Pipeline"]
EMBED_NODE["HTTP Request:<br/>OpenAI Embeddings API"]
SEARCH_NODE["Supabase RPC:<br/>match_documents()"]
FORMAT["Code Node:<br/>Форматирование контекста"]
end
subgraph AI_AGENT["🤖 AI Agent"]
MEMORY["Supabase Memory Store<br/>(conversations + messages)"]
TOOLS["Tools:<br/>get_rates, calculate_deal,<br/>create_order, log_chat"]
AGENT["OpenAI Chat Agent<br/>(GPT-4o-mini)"]
end
end
subgraph SUPABASE["🗄️ Supabase"]
DOCS_TABLE[("documents<br/>(vector search)")]
CONV_TABLE[("conversations +<br/>messages")]
BIZ_DATA[("orders, clients,<br/>daily_rates")]
end
MSG --> TRIGGER
TRIGGER --> SWITCH
SWITCH -->|text| EMBED_NODE
EMBED_NODE --> SEARCH_NODE
SEARCH_NODE --> DOCS_TABLE
DOCS_TABLE --> FORMAT
FORMAT --> AGENT
MEMORY <--> CONV_TABLE
TOOLS <--> BIZ_DATA
MEMORY --> AGENT
TOOLS --> AGENT
AGENT -->|Ответ| MSG
Supabase Memory Store (замена Simple Memory)
[!WARNING] Текущий n8n workflow использует Simple Memory (хранение в RAM). При перезапуске n8n вся история диалогов теряется. Необходимо мигрировать на Supabase-backed storage.
Архитектура памяти:
| Уровень | Хранилище | Содержимое | TTL |
|---|---|---|---|
| Short-term | messages таблица |
Последние 20 сообщений диалога | Бессрочно |
| Long-term | documents (category: conversation_summary) |
AI-сжатое резюме длинных разговоров | Бессрочно |
| Knowledge | documents (все категории) |
Статические знания компании | Ручное обновление |
| Tools | orders, clients, daily_rates |
Актуальные бизнес-данные | Realtime |
12.7. Гибридный поиск (Vector + Full-Text)
Для повышения точности поиска используется комбинация векторного сходства и полнотекстового поиска (tsvector):
-- Гибридный поиск: vector similarity + full-text search
CREATE OR REPLACE FUNCTION hybrid_search(
query_text text,
query_embedding vector(1536),
match_count int DEFAULT 5,
full_text_weight float DEFAULT 0.3,
semantic_weight float DEFAULT 0.7,
filter_category text DEFAULT NULL
)
RETURNS TABLE (
id bigint,
content text,
metadata jsonb,
similarity float,
fts_rank float,
combined_score float
)
LANGUAGE plpgsql
AS $$
BEGIN
RETURN QUERY
SELECT
d.id,
d.content,
d.metadata,
1 - (d.embedding <=> query_embedding) AS similarity,
ts_rank(to_tsvector('russian', d.content), plainto_tsquery('russian', query_text)) AS fts_rank,
(
semantic_weight * (1 - (d.embedding <=> query_embedding)) +
full_text_weight * ts_rank(to_tsvector('russian', d.content), plainto_tsquery('russian', query_text))
) AS combined_score
FROM documents d
WHERE
d.is_active = true
AND d.embedding IS NOT NULL
AND (filter_category IS NULL OR d.metadata->>'category' = filter_category)
ORDER BY combined_score DESC
LIMIT match_count;
END;
$$;
Когда использовать какой тип поиска:
| Сценарий | Метод | Почему |
|---|---|---|
| «Какой курс бата?» | 🧮 Vector (semantic) | Смысловой поиск, не точное совпадение слов |
| «AML правила для суммы $5000» | 🔍 Hybrid | Нужен смысл + точный термин |
| «FAQ» | 📝 Full-text | Точное совпадение ключевого слова |
| «Расскажи про ваш сервис» | 🧮 Vector | Широкий семантический запрос |
12.8. Action Items для RAG (приоритизация)
| # | Действие | Приоритет | Фаза | Зависимости |
|---|---|---|---|---|
| 1 | Добавить колонки created_at, updated_at, source, is_active в documents |
🔴 P0 | 2 | — |
| 2 | Создать HNSW-индекс на documents.embedding |
🔴 P0 | 2 | #1 |
| 3 | Создать RPC match_documents() |
🔴 P0 | 2 | #2 |
| 4 | Развернуть Edge Function embed |
🔴 P0 | 2 | #1 |
| 5 | Настроить pgmq + триггер для auto-embedding | 🟡 P1 | 2 | #4 |
| 6 | Наполнить documents базовыми знаниями (≥20 документов) |
🔴 P0 | 2 | #4 |
| 7 | Добавить RAG-retrieval ноду в n8n workflow | 🔴 P0 | 2 | #3, #6 |
| 8 | Мигрировать n8n Simple Memory → Supabase messages |
🟡 P1 | 2 | conversations таблица |
| 9 | Создать RPC hybrid_search() |
🟢 P2 | 5 | #3 |
| 10 | Автоматическое резюмирование длинных диалогов → documents |
🟢 P2 | 5 | #4 |
[!NOTE] Оценка объёма: При ≤ 1000 документах и HNSW-индексе — поиск занимает < 10ms. OpenAI embedding стоит ~$0.02 / 1M токенов. Для EastPay с ~100 документами базы знаний стоимость пренебрежимо мала.
12.9. Supabase AI Chat Agent Memory для n8n
[!TIP] Контекст задачи: Мы заменяем "Simple Memory" (которая хранит данные в RAM и стирается при перезагрузке n8n) на надежную базу данных Supabase. Это позволит агенту помнить диалоги даже спустя дни.
12.9.1. Концепция памяти
В реализации AI Agent есть два типа памяти:
- Short-Term Memory (Conversation History): То, что мы реализуем здесь. Хранение текущего диалога (последние N сообщений).
- Long-Term Memory (RAG Vector Store): То, что мы описали выше (поиск по базе знаний).
12.9.2. Подготовка базы данных (SQL)
n8n использует стандартную структуру таблицы для хранения истории чатов langchain. Нам нужно создать её в Supabase.
-- 1. Создаем таблицу для истории чатов n8n
CREATE TABLE IF NOT EXISTS n8n_chat_histories (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
session_id TEXT NOT NULL,
message JSONB NOT NULL,
created_at TIMESTAMPTZ DEFAULT now()
);
-- 2. Индекс для быстрого поиска по сессии (chat_id)
CREATE INDEX IF NOT EXISTS idx_n8n_chat_histories_session_id ON n8n_chat_histories(session_id);
-- 3. (Опционально) RLS, если нужно, но для сервисного n8n подключения можно full access
ALTER TABLE n8n_chat_histories ENABLE ROW LEVEL SECURITY;
CREATE POLICY "Enable all access for service role" ON n8n_chat_histories
FOR ALL
TO service_role
USING (true)
WITH CHECK (true);
12.9.3. Настройка n8n Workflow
В n8n нам нужно заменить ноду "Simple Memory" на Postgres Chat Memory (так как Supabase — это Postgres).
Шаги настройки:
- Добавить ноду: Найдите
@n8n/n8n-nodes-langchain.memoryPostgresChat(или просто "Postgres Chat Memory"). - Подключить к AI Agent: Соедините выход Memory ноды с входом
Memoryу AI Agent. - Настройка Credentials:
- Создайте новое Credential: Postgres.
- Host:
db.cvzsgjksswowqgfxvrsb.supabase.co(из настроек Supabase). - Database:
postgres. - User:
postgres(или ваш пользователь бд). - Password: (ваш пароль от БД).
- Port:
5432. - SSL: Отметьте, если требуется (обычно для Supabase нужно).
- Параметры ноды:
- Table Name:
n8n_chat_histories(как создали выше). - Session ID: ОЧЕНЬ ВАЖНО! Здесь должен быть динамический ID чата из Telegram.
- Expression:
{{ $('Telegram Trigger').item.json.message.chat.id }} - Или если используете node "Edit Fields" перед агентом:
{{ $json.telegram_id }}.
- Expression:
- Context Window Length: Установите
20(помнить последние 20 сообщений). Не ставьте слишком много, чтобы не переплачивать за токены.
- Table Name:
12.9.4. Как это работает вместе
- Приходит сообщение в Telegram Trigger.
- Извлекается
chat_id. - AI Agent перед генерацией ответа "смотрит" в таблицу
n8n_chat_historiesпо этомуsession_id. - Он видит контекст: "А, мы только что обсуждали курс USDT".
- Генерирует ответ.
- Новое сообщение (вопрос юзера и ответ агента) автоматически записывается обратно в
n8n_chat_histories.
[!IMPORTANT] Отличие от таблицы
messages(раздел 1.2 plan-11):
n8n_chat_histories— это "сырая" память для работы LangChain внутри n8n. Она хранит JSON-структуры.messages— это наша бизнесовая таблица для отображения истории в Личном Кабинете (Mini App) и админке менеджера. Рекомендация: Используйте пока обе.n8n_chat_historiesдля работы "мозгов" бота, аmessagesдля отображения в UI (черезlog_chat_toolили save_message RPC).