Files
microdao-daarion/migrations/046_memory_service_full_schema.sql
Apple 5290287058 feat: implement TTS, Document processing, and Memory Service /facts API
- TTS: xtts-v2 integration with voice cloning support
- Document: docling integration for PDF/DOCX/PPTX processing
- Memory Service: added /facts/upsert, /facts/{key}, /facts endpoints
- Added required dependencies (TTS, docling)
2026-01-17 08:16:37 -08:00

449 lines
18 KiB
PL/PgSQL
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
-- Migration 046: Memory Service Full Schema
-- Повна схема пам'яті для агентів (episodic, semantic, group identity)
-- Дата: 2026-01-17
-- Автор: DAARION Team
-- ============================================================================
-- 1. GROUPS — Групові чати
-- ============================================================================
CREATE TABLE IF NOT EXISTS groups (
group_id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
platform VARCHAR(50) NOT NULL, -- telegram, discord, matrix, slack
platform_group_id VARCHAR(255) NOT NULL, -- ID групи на платформі
name VARCHAR(255),
description TEXT,
created_at TIMESTAMPTZ DEFAULT NOW(),
updated_at TIMESTAMPTZ DEFAULT NOW(),
metadata JSONB DEFAULT '{}',
UNIQUE(platform, platform_group_id)
);
CREATE INDEX idx_groups_platform ON groups(platform);
CREATE INDEX idx_groups_platform_id ON groups(platform, platform_group_id);
-- ============================================================================
-- 2. GROUP_MEMBERS — Учасники груп
-- ============================================================================
CREATE TABLE IF NOT EXISTS group_members (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
group_id UUID NOT NULL REFERENCES groups(group_id) ON DELETE CASCADE,
platform_user_id VARCHAR(255) NOT NULL, -- Стабільний ID користувача на платформі
nickname VARCHAR(255), -- Поточний нікнейм (може змінюватись)
first_seen_at TIMESTAMPTZ DEFAULT NOW(),
last_seen_at TIMESTAMPTZ DEFAULT NOW(),
last_message_at TIMESTAMPTZ,
message_count INTEGER DEFAULT 0,
no_memory_in_group BOOLEAN DEFAULT FALSE, -- Opt-out flag
status VARCHAR(20) DEFAULT 'active', -- active, left, banned
UNIQUE(group_id, platform_user_id)
);
CREATE INDEX idx_group_members_group ON group_members(group_id);
CREATE INDEX idx_group_members_user ON group_members(platform_user_id);
CREATE INDEX idx_group_members_no_memory ON group_members(no_memory_in_group) WHERE no_memory_in_group = TRUE;
-- ============================================================================
-- 3. GROUP_MEMBER_PROFILES — Профілі учасників у групах
-- ============================================================================
CREATE TABLE IF NOT EXISTS group_member_profiles (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
group_id UUID NOT NULL REFERENCES groups(group_id) ON DELETE CASCADE,
platform_user_id VARCHAR(255) NOT NULL,
-- Роль/контекст (виключно для цієї групи)
role_hint VARCHAR(100), -- investor, engineer, moderator, newcomer, etc.
language_preference VARCHAR(10) DEFAULT 'uk',
communication_style VARCHAR(50), -- formal, casual, technical
-- Інтереси/теми (в контексті групи)
topics_of_interest TEXT[], -- ['BioMiner', 'tokenomics', 'governance']
last_topics JSONB DEFAULT '[]', -- Останні обговорювані теми
-- Преференції спілкування
preferences_json JSONB DEFAULT '{}',
notes_short TEXT, -- Короткі нотатки агента про учасника
created_at TIMESTAMPTZ DEFAULT NOW(),
updated_at TIMESTAMPTZ DEFAULT NOW(),
UNIQUE(group_id, platform_user_id)
);
CREATE INDEX idx_group_profiles_group ON group_member_profiles(group_id);
CREATE INDEX idx_group_profiles_user ON group_member_profiles(platform_user_id);
CREATE INDEX idx_group_profiles_role ON group_member_profiles(role_hint);
-- ============================================================================
-- 4. USERS — Глобальні користувачі (для DM та cross-platform)
-- ============================================================================
CREATE TABLE IF NOT EXISTS memory_users (
user_id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
-- Платформенні ідентифікатори
telegram_id VARCHAR(50),
discord_id VARCHAR(50),
matrix_id VARCHAR(255),
email VARCHAR(255),
-- Глобальні налаштування
display_name VARCHAR(255),
global_memory_enabled BOOLEAN DEFAULT TRUE,
pii_allowed BOOLEAN DEFAULT FALSE, -- Дозвіл на збереження PII
created_at TIMESTAMPTZ DEFAULT NOW(),
updated_at TIMESTAMPTZ DEFAULT NOW(),
UNIQUE(telegram_id),
UNIQUE(discord_id),
UNIQUE(matrix_id)
);
CREATE INDEX idx_users_telegram ON memory_users(telegram_id) WHERE telegram_id IS NOT NULL;
CREATE INDEX idx_users_discord ON memory_users(discord_id) WHERE discord_id IS NOT NULL;
-- ============================================================================
-- 5. CONSENT — Згода на обробку пам'яті
-- ============================================================================
CREATE TABLE IF NOT EXISTS memory_consent (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
user_id UUID REFERENCES memory_users(user_id) ON DELETE CASCADE,
platform_user_id VARCHAR(255), -- Якщо user_id ще не створено
-- Типи згоди
memory_enabled BOOLEAN DEFAULT TRUE,
pii_allowed BOOLEAN DEFAULT FALSE,
cross_group_memory BOOLEAN DEFAULT FALSE, -- Дозвіл на перенос між групами
dm_memory_enabled BOOLEAN DEFAULT TRUE,
-- Retention policy
retention_policy VARCHAR(50) DEFAULT 'default', -- default, minimal, extended, forever
retention_days INTEGER DEFAULT 365,
updated_at TIMESTAMPTZ DEFAULT NOW(),
updated_by VARCHAR(100) DEFAULT 'user',
UNIQUE(user_id),
UNIQUE(platform_user_id)
);
-- ============================================================================
-- 6. CONVERSATIONS — Розмови/сесії
-- ============================================================================
CREATE TABLE IF NOT EXISTS conversations (
conversation_id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
-- Контекст
user_id UUID REFERENCES memory_users(user_id),
platform_user_id VARCHAR(255), -- Fallback якщо немає user_id
group_id UUID REFERENCES groups(group_id),
-- Метадані
channel VARCHAR(50) NOT NULL, -- dm, group, channel
platform VARCHAR(50) NOT NULL,
agent_id VARCHAR(100) DEFAULT 'helion',
-- Часові межі
started_at TIMESTAMPTZ DEFAULT NOW(),
ended_at TIMESTAMPTZ,
last_message_at TIMESTAMPTZ DEFAULT NOW(),
-- Статистика
message_count INTEGER DEFAULT 0,
token_count INTEGER DEFAULT 0,
-- Стан
status VARCHAR(20) DEFAULT 'active', -- active, ended, archived
summary TEXT, -- Автоматичне резюме після завершення
metadata JSONB DEFAULT '{}'
);
CREATE INDEX idx_conversations_user ON conversations(user_id);
CREATE INDEX idx_conversations_platform_user ON conversations(platform_user_id);
CREATE INDEX idx_conversations_group ON conversations(group_id);
CREATE INDEX idx_conversations_status ON conversations(status);
CREATE INDEX idx_conversations_started ON conversations(started_at DESC);
-- ============================================================================
-- 7. MESSAGES — Повідомлення
-- ============================================================================
CREATE TABLE IF NOT EXISTS messages (
message_id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
conversation_id UUID NOT NULL REFERENCES conversations(conversation_id) ON DELETE CASCADE,
-- Автор
role VARCHAR(20) NOT NULL, -- user, assistant, system, tool
platform_user_id VARCHAR(255), -- Для групових чатів
-- Контент
content TEXT NOT NULL,
content_type VARCHAR(50) DEFAULT 'text', -- text, image, audio, file
-- Часова мітка
created_at TIMESTAMPTZ DEFAULT NOW(),
-- PII/Redaction
has_pii BOOLEAN DEFAULT FALSE,
redaction_state VARCHAR(20) DEFAULT 'none', -- none, partial, full
original_content_hash VARCHAR(64), -- Для аудиту
-- Токени
token_count INTEGER,
model_used VARCHAR(100),
metadata JSONB DEFAULT '{}'
);
CREATE INDEX idx_messages_conversation ON messages(conversation_id);
CREATE INDEX idx_messages_created ON messages(created_at DESC);
CREATE INDEX idx_messages_role ON messages(role);
CREATE INDEX idx_messages_pii ON messages(has_pii) WHERE has_pii = TRUE;
-- ============================================================================
-- 8. MEMORIES — Довготривала пам'ять (episodic + semantic)
-- ============================================================================
CREATE TABLE IF NOT EXISTS memories (
memory_id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
-- Власник пам'яті
user_id UUID REFERENCES memory_users(user_id),
platform_user_id VARCHAR(255),
group_id UUID REFERENCES groups(group_id), -- NULL = глобальна/DM пам'ять
-- Тип пам'яті
memory_type VARCHAR(50) NOT NULL, -- episodic, semantic, procedural
category VARCHAR(100), -- preference, fact, interaction, topic_interest
-- Контент
content TEXT NOT NULL,
summary TEXT, -- Короткий опис для швидкого retrieval
-- Важливість та TTL
importance FLOAT DEFAULT 0.5, -- 0.0 - 1.0
confidence FLOAT DEFAULT 0.8,
ttl_days INTEGER, -- NULL = безстроково
expires_at TIMESTAMPTZ,
-- Джерело
source_message_ids UUID[],
source_conversation_id UUID REFERENCES conversations(conversation_id),
extraction_method VARCHAR(50) DEFAULT 'explicit', -- explicit, inferred, llm_extracted
-- Embedding (для vector search)
embedding_id VARCHAR(255), -- ID в Qdrant/pgvector
embedding_model VARCHAR(100),
-- Часові мітки
created_at TIMESTAMPTZ DEFAULT NOW(),
updated_at TIMESTAMPTZ DEFAULT NOW(),
last_accessed_at TIMESTAMPTZ,
access_count INTEGER DEFAULT 0,
-- Статус
is_active BOOLEAN DEFAULT TRUE,
is_verified BOOLEAN DEFAULT FALSE,
metadata JSONB DEFAULT '{}'
);
CREATE INDEX idx_memories_user ON memories(user_id);
CREATE INDEX idx_memories_platform_user ON memories(platform_user_id);
CREATE INDEX idx_memories_group ON memories(group_id);
CREATE INDEX idx_memories_type ON memories(memory_type);
CREATE INDEX idx_memories_category ON memories(category);
CREATE INDEX idx_memories_importance ON memories(importance DESC);
CREATE INDEX idx_memories_active ON memories(is_active) WHERE is_active = TRUE;
CREATE INDEX idx_memories_expires ON memories(expires_at) WHERE expires_at IS NOT NULL;
-- ============================================================================
-- 9. MEMORY_EVENTS — Аудит (хто/що/коли змінював пам'ять)
-- ============================================================================
CREATE TABLE IF NOT EXISTS memory_events (
event_id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
-- Що змінилось
memory_id UUID REFERENCES memories(memory_id) ON DELETE SET NULL,
user_id UUID REFERENCES memory_users(user_id),
group_id UUID REFERENCES groups(group_id),
-- Дія
action VARCHAR(50) NOT NULL, -- created, updated, deleted, accessed, opt_out, opt_in
-- Хто зробив
actor VARCHAR(100) NOT NULL, -- user, agent:helion, system, admin
actor_user_id VARCHAR(255),
-- Деталі
old_value JSONB,
new_value JSONB,
reason TEXT,
-- Час
created_at TIMESTAMPTZ DEFAULT NOW(),
-- Контекст
ip_address INET,
user_agent TEXT,
metadata JSONB DEFAULT '{}'
);
CREATE INDEX idx_memory_events_memory ON memory_events(memory_id);
CREATE INDEX idx_memory_events_user ON memory_events(user_id);
CREATE INDEX idx_memory_events_action ON memory_events(action);
CREATE INDEX idx_memory_events_created ON memory_events(created_at DESC);
-- ============================================================================
-- 10. GROUP_INTERACTIONS — Взаємодії в групах (опціонально)
-- ============================================================================
CREATE TABLE IF NOT EXISTS group_interactions (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
group_id UUID NOT NULL REFERENCES groups(group_id) ON DELETE CASCADE,
platform_user_id VARCHAR(255) NOT NULL,
-- Тема/контекст взаємодії
topic VARCHAR(255),
interaction_type VARCHAR(50), -- question, answer, discussion, feedback
-- Часові мітки
first_interaction_at TIMESTAMPTZ DEFAULT NOW(),
last_interaction_at TIMESTAMPTZ DEFAULT NOW(),
interaction_count INTEGER DEFAULT 1,
-- Якість взаємодії
sentiment_score FLOAT, -- -1.0 до 1.0
helpfulness_score FLOAT, -- 0.0 до 1.0
metadata JSONB DEFAULT '{}'
);
CREATE INDEX idx_group_interactions_group ON group_interactions(group_id);
CREATE INDEX idx_group_interactions_user ON group_interactions(platform_user_id);
CREATE INDEX idx_group_interactions_topic ON group_interactions(topic);
-- ============================================================================
-- 11. HELPER FUNCTIONS
-- ============================================================================
-- Функція для автоматичного оновлення updated_at
CREATE OR REPLACE FUNCTION update_updated_at_column()
RETURNS TRIGGER AS $$
BEGIN
NEW.updated_at = NOW();
RETURN NEW;
END;
$$ language 'plpgsql';
-- Тригери для updated_at
CREATE TRIGGER update_groups_updated_at BEFORE UPDATE ON groups
FOR EACH ROW EXECUTE FUNCTION update_updated_at_column();
CREATE TRIGGER update_group_member_profiles_updated_at BEFORE UPDATE ON group_member_profiles
FOR EACH ROW EXECUTE FUNCTION update_updated_at_column();
CREATE TRIGGER update_memory_users_updated_at BEFORE UPDATE ON memory_users
FOR EACH ROW EXECUTE FUNCTION update_updated_at_column();
CREATE TRIGGER update_memories_updated_at BEFORE UPDATE ON memories
FOR EACH ROW EXECUTE FUNCTION update_updated_at_column();
-- Функція для opt-out користувача з групи
CREATE OR REPLACE FUNCTION memory_opt_out_group(
p_group_id UUID,
p_platform_user_id VARCHAR(255)
) RETURNS VOID AS $$
BEGIN
-- Позначити учасника як no_memory
UPDATE group_members
SET no_memory_in_group = TRUE
WHERE group_id = p_group_id AND platform_user_id = p_platform_user_id;
-- Деактивувати всі пам'яті цього користувача в групі
UPDATE memories
SET is_active = FALSE
WHERE group_id = p_group_id AND platform_user_id = p_platform_user_id;
-- Видалити профіль
DELETE FROM group_member_profiles
WHERE group_id = p_group_id AND platform_user_id = p_platform_user_id;
-- Записати в аудит
INSERT INTO memory_events (user_id, group_id, action, actor, actor_user_id, reason)
VALUES (NULL, p_group_id, 'opt_out', 'user', p_platform_user_id, 'User requested opt-out from group memory');
END;
$$ LANGUAGE plpgsql;
-- Функція для повного видалення користувача з групи (forget)
CREATE OR REPLACE FUNCTION memory_forget_in_group(
p_group_id UUID,
p_platform_user_id VARCHAR(255)
) RETURNS VOID AS $$
BEGIN
-- Видалити всі пам'яті
DELETE FROM memories
WHERE group_id = p_group_id AND platform_user_id = p_platform_user_id;
-- Видалити профіль
DELETE FROM group_member_profiles
WHERE group_id = p_group_id AND platform_user_id = p_platform_user_id;
-- Очистити дані учасника (але залишити запис)
UPDATE group_members
SET no_memory_in_group = TRUE, nickname = NULL
WHERE group_id = p_group_id AND platform_user_id = p_platform_user_id;
-- Записати в аудит
INSERT INTO memory_events (group_id, action, actor, actor_user_id, reason)
VALUES (p_group_id, 'deleted', 'user', p_platform_user_id, 'User requested full memory deletion in group');
END;
$$ LANGUAGE plpgsql;
-- ============================================================================
-- 12. VIEWS для зручності
-- ============================================================================
-- View: Активні пам'яті користувача
CREATE OR REPLACE VIEW v_active_user_memories AS
SELECT
m.*,
u.display_name,
g.name as group_name
FROM memories m
LEFT JOIN memory_users u ON m.user_id = u.user_id
LEFT JOIN groups g ON m.group_id = g.group_id
WHERE m.is_active = TRUE
AND (m.expires_at IS NULL OR m.expires_at > NOW());
-- View: Учасники групи з профілями
CREATE OR REPLACE VIEW v_group_members_with_profiles AS
SELECT
gm.*,
gmp.role_hint,
gmp.language_preference,
gmp.topics_of_interest,
gmp.notes_short,
g.name as group_name,
g.platform
FROM group_members gm
LEFT JOIN group_member_profiles gmp
ON gm.group_id = gmp.group_id AND gm.platform_user_id = gmp.platform_user_id
JOIN groups g ON gm.group_id = g.group_id
WHERE gm.status = 'active';
-- ============================================================================
-- Result
-- ============================================================================
SELECT 'Migration 046 completed: Memory Service full schema created' AS result;