trading-platform-database-v2/ddl/schemas/llm/tables/001_conversations.sql
rckrdmrd 62c811be45 [DDL] feat: Sprint 5 - Add llm schema with 5 tables
- conversations: Conversaciones con agente LLM
- conversation_messages: Mensajes individuales
- llm_tools_usage: Registro de uso de herramientas
- llm_proactive_notifications: Notificaciones proactivas
- llm_usage_limits: Limites de uso por usuario/plan VIP

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-16 20:13:33 -06:00

252 lines
7.4 KiB
PL/PgSQL

-- ============================================================================
-- SCHEMA: llm
-- TABLE: conversations
-- DESCRIPTION: Conversaciones con el agente LLM
-- VERSION: 1.0.0
-- CREATED: 2026-01-16
-- SPRINT: Sprint 5 - DDL Implementation Roadmap Q1-2026
-- ============================================================================
-- Crear schema si no existe
CREATE SCHEMA IF NOT EXISTS llm;
COMMENT ON SCHEMA llm IS
'Sistema de agente LLM con conversaciones, herramientas y limites de uso';
-- Enum para tipo de conversacion
DO $$ BEGIN
CREATE TYPE llm.conversation_type AS ENUM (
'chat', -- Chat general
'trading_assistant', -- Asistente de trading
'market_analysis', -- Analisis de mercado
'education', -- Educacion/tutoria
'support', -- Soporte tecnico
'portfolio_review', -- Revision de portafolio
'strategy_builder', -- Constructor de estrategias
'code_assistant' -- Asistente de codigo
);
EXCEPTION
WHEN duplicate_object THEN null;
END $$;
-- Enum para estado de conversacion
DO $$ BEGIN
CREATE TYPE llm.conversation_status AS ENUM (
'active', -- Activa
'archived', -- Archivada
'deleted', -- Eliminada (soft delete)
'expired' -- Expirada por inactividad
);
EXCEPTION
WHEN duplicate_object THEN null;
END $$;
-- Enum para modelo LLM
DO $$ BEGIN
CREATE TYPE llm.llm_model AS ENUM (
'gpt-4o',
'gpt-4o-mini',
'gpt-4-turbo',
'claude-3-5-sonnet',
'claude-3-opus',
'claude-3-5-haiku',
'gemini-pro',
'gemini-ultra',
'llama-3-70b',
'mixtral-8x7b',
'custom'
);
EXCEPTION
WHEN duplicate_object THEN null;
END $$;
-- Tabla de Conversaciones
CREATE TABLE IF NOT EXISTS llm.conversations (
-- Identificadores
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
tenant_id UUID NOT NULL REFERENCES tenants.tenants(id) ON DELETE CASCADE,
user_id UUID NOT NULL REFERENCES users.users(id) ON DELETE CASCADE,
-- Tipo y estado
type llm.conversation_type NOT NULL DEFAULT 'chat',
status llm.conversation_status NOT NULL DEFAULT 'active',
-- Titulo y descripcion
title VARCHAR(255),
description TEXT,
auto_title BOOLEAN NOT NULL DEFAULT TRUE, -- Generar titulo automaticamente
-- Modelo
model llm.llm_model NOT NULL DEFAULT 'gpt-4o-mini',
model_config JSONB DEFAULT '{}'::JSONB, -- Temperatura, max_tokens, etc.
-- System prompt
system_prompt TEXT,
custom_instructions TEXT,
-- Contexto
context_type VARCHAR(50), -- 'symbol', 'portfolio', 'position'
context_id UUID, -- ID del contexto
context_data JSONB DEFAULT '{}'::JSONB, -- Datos de contexto
-- Estadisticas
message_count INTEGER NOT NULL DEFAULT 0,
token_count_input INTEGER NOT NULL DEFAULT 0,
token_count_output INTEGER NOT NULL DEFAULT 0,
total_tokens INTEGER NOT NULL DEFAULT 0,
-- Costo estimado
estimated_cost_usd DECIMAL(10, 6) NOT NULL DEFAULT 0,
-- Herramientas habilitadas
tools_enabled VARCHAR(100)[], -- Herramientas disponibles
tools_auto_execute BOOLEAN NOT NULL DEFAULT FALSE, -- Ejecutar herramientas automaticamente
-- Feedback
rating INTEGER CHECK (rating BETWEEN 1 AND 5),
feedback TEXT,
rated_at TIMESTAMPTZ,
-- Compartir
is_shared BOOLEAN NOT NULL DEFAULT FALSE,
share_code VARCHAR(20),
shared_at TIMESTAMPTZ,
-- Ultima actividad
last_message_at TIMESTAMPTZ,
last_user_message_at TIMESTAMPTZ,
-- Metadata
tags VARCHAR(50)[],
metadata JSONB DEFAULT '{}'::JSONB,
-- Timestamps
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
archived_at TIMESTAMPTZ,
expires_at TIMESTAMPTZ, -- Para limpiar conversaciones antiguas
-- Constraints
CONSTRAINT conversations_share_code_unique UNIQUE (share_code)
);
COMMENT ON TABLE llm.conversations IS
'Conversaciones con el agente LLM por usuario';
COMMENT ON COLUMN llm.conversations.tools_enabled IS
'Herramientas habilitadas: market_data, portfolio, trading, charts, etc.';
-- Indices
CREATE INDEX IF NOT EXISTS idx_conversations_tenant
ON llm.conversations(tenant_id);
CREATE INDEX IF NOT EXISTS idx_conversations_user
ON llm.conversations(user_id);
CREATE INDEX IF NOT EXISTS idx_conversations_status
ON llm.conversations(status);
CREATE INDEX IF NOT EXISTS idx_conversations_active
ON llm.conversations(user_id, status, updated_at DESC)
WHERE status = 'active';
CREATE INDEX IF NOT EXISTS idx_conversations_type
ON llm.conversations(type);
CREATE INDEX IF NOT EXISTS idx_conversations_last_message
ON llm.conversations(last_message_at DESC)
WHERE status = 'active';
CREATE INDEX IF NOT EXISTS idx_conversations_share
ON llm.conversations(share_code)
WHERE share_code IS NOT NULL;
CREATE INDEX IF NOT EXISTS idx_conversations_context
ON llm.conversations(context_type, context_id)
WHERE context_id IS NOT NULL;
-- Funcion de timestamp
CREATE OR REPLACE FUNCTION llm.update_llm_timestamp()
RETURNS TRIGGER AS $$
BEGIN
NEW.updated_at := NOW();
RETURN NEW;
END;
$$ LANGUAGE plpgsql;
-- Trigger para updated_at
DROP TRIGGER IF EXISTS conversation_updated_at ON llm.conversations;
CREATE TRIGGER conversation_updated_at
BEFORE UPDATE ON llm.conversations
FOR EACH ROW
EXECUTE FUNCTION llm.update_llm_timestamp();
-- Funcion para generar share code
CREATE OR REPLACE FUNCTION llm.generate_share_code()
RETURNS VARCHAR AS $$
DECLARE
v_code VARCHAR;
v_exists BOOLEAN;
BEGIN
LOOP
v_code := UPPER(SUBSTRING(MD5(RANDOM()::TEXT || NOW()::TEXT) FROM 1 FOR 8));
SELECT EXISTS (SELECT 1 FROM llm.conversations WHERE share_code = v_code) INTO v_exists;
EXIT WHEN NOT v_exists;
END LOOP;
RETURN v_code;
END;
$$ LANGUAGE plpgsql;
-- Funcion para compartir conversacion
CREATE OR REPLACE FUNCTION llm.share_conversation(p_conversation_id UUID)
RETURNS VARCHAR AS $$
DECLARE
v_code VARCHAR;
BEGIN
v_code := llm.generate_share_code();
UPDATE llm.conversations
SET is_shared = TRUE,
share_code = v_code,
shared_at = NOW()
WHERE id = p_conversation_id;
RETURN v_code;
END;
$$ LANGUAGE plpgsql;
-- Vista de conversaciones recientes
CREATE OR REPLACE VIEW llm.v_recent_conversations AS
SELECT
id,
user_id,
type,
title,
model,
message_count,
total_tokens,
estimated_cost_usd,
last_message_at,
created_at
FROM llm.conversations
WHERE status = 'active'
ORDER BY last_message_at DESC NULLS LAST;
-- RLS Policies
ALTER TABLE llm.conversations ENABLE ROW LEVEL SECURITY;
CREATE POLICY conversations_tenant_isolation ON llm.conversations
FOR ALL
USING (tenant_id = current_setting('app.current_tenant_id', true)::UUID);
CREATE POLICY conversations_user_isolation ON llm.conversations
FOR ALL
USING (user_id = current_setting('app.current_user_id', true)::UUID);
-- Grants
GRANT USAGE ON SCHEMA llm TO trading_app;
GRANT SELECT, INSERT, UPDATE, DELETE ON llm.conversations TO trading_app;
GRANT SELECT ON llm.conversations TO trading_readonly;
GRANT SELECT ON llm.v_recent_conversations TO trading_app;
GRANT EXECUTE ON FUNCTION llm.share_conversation TO trading_app;