- 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>
252 lines
7.4 KiB
PL/PgSQL
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;
|