-- ============================================================================ -- 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;