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)
This commit is contained in:
Apple
2026-01-17 08:16:37 -08:00
parent a9fcadc6e2
commit 5290287058
121 changed files with 17071 additions and 436 deletions

View File

@@ -0,0 +1,448 @@
-- 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;