- 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>
249 lines
8.0 KiB
PL/PgSQL
249 lines
8.0 KiB
PL/PgSQL
-- ============================================================================
|
|
-- SCHEMA: llm
|
|
-- TABLE: conversation_messages
|
|
-- DESCRIPTION: Mensajes dentro de conversaciones LLM
|
|
-- VERSION: 1.0.0
|
|
-- CREATED: 2026-01-16
|
|
-- SPRINT: Sprint 5 - DDL Implementation Roadmap Q1-2026
|
|
-- ============================================================================
|
|
|
|
-- Enum para rol del mensaje
|
|
DO $$ BEGIN
|
|
CREATE TYPE llm.message_role AS ENUM (
|
|
'system', -- Mensaje de sistema
|
|
'user', -- Mensaje del usuario
|
|
'assistant', -- Respuesta del asistente
|
|
'tool', -- Resultado de herramienta
|
|
'function' -- Llamada a funcion (legacy)
|
|
);
|
|
EXCEPTION
|
|
WHEN duplicate_object THEN null;
|
|
END $$;
|
|
|
|
-- Enum para estado del mensaje
|
|
DO $$ BEGIN
|
|
CREATE TYPE llm.message_status AS ENUM (
|
|
'pending', -- Pendiente de procesar
|
|
'streaming', -- Streaming en progreso
|
|
'completed', -- Completado
|
|
'error', -- Error
|
|
'cancelled', -- Cancelado
|
|
'edited' -- Editado por usuario
|
|
);
|
|
EXCEPTION
|
|
WHEN duplicate_object THEN null;
|
|
END $$;
|
|
|
|
-- Tabla de Mensajes
|
|
CREATE TABLE IF NOT EXISTS llm.conversation_messages (
|
|
-- Identificadores
|
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
conversation_id UUID NOT NULL REFERENCES llm.conversations(id) ON DELETE CASCADE,
|
|
parent_message_id UUID REFERENCES llm.conversation_messages(id),
|
|
|
|
-- Rol y contenido
|
|
role llm.message_role NOT NULL,
|
|
content TEXT NOT NULL,
|
|
status llm.message_status NOT NULL DEFAULT 'completed',
|
|
|
|
-- Para mensajes de herramientas
|
|
tool_call_id VARCHAR(100),
|
|
tool_name VARCHAR(100),
|
|
tool_input JSONB,
|
|
tool_output JSONB,
|
|
|
|
-- Tokens
|
|
token_count INTEGER DEFAULT 0,
|
|
prompt_tokens INTEGER DEFAULT 0,
|
|
completion_tokens INTEGER DEFAULT 0,
|
|
|
|
-- Modelo usado
|
|
model VARCHAR(50),
|
|
|
|
-- Tiempos de respuesta
|
|
latency_ms INTEGER, -- Tiempo de respuesta
|
|
time_to_first_token_ms INTEGER, -- TTFT para streaming
|
|
|
|
-- Contenido estructurado
|
|
attachments JSONB DEFAULT '[]'::JSONB, -- Archivos adjuntos
|
|
citations JSONB DEFAULT '[]'::JSONB, -- Citas/referencias
|
|
code_blocks JSONB DEFAULT '[]'::JSONB, -- Bloques de codigo
|
|
|
|
-- Markdown y formato
|
|
is_markdown BOOLEAN NOT NULL DEFAULT TRUE,
|
|
rendered_html TEXT, -- HTML pre-renderizado
|
|
|
|
-- Feedback
|
|
thumbs_up BOOLEAN,
|
|
feedback_text TEXT,
|
|
feedback_at TIMESTAMPTZ,
|
|
|
|
-- Edicion
|
|
is_edited BOOLEAN NOT NULL DEFAULT FALSE,
|
|
original_content TEXT,
|
|
edited_at TIMESTAMPTZ,
|
|
|
|
-- Regeneracion
|
|
is_regenerated BOOLEAN NOT NULL DEFAULT FALSE,
|
|
regeneration_count INTEGER NOT NULL DEFAULT 0,
|
|
previous_version_id UUID,
|
|
|
|
-- Metadata
|
|
metadata JSONB DEFAULT '{}'::JSONB,
|
|
|
|
-- Timestamps
|
|
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
|
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
|
|
|
-- Indices para orden
|
|
sequence_number INTEGER NOT NULL DEFAULT 0 -- Orden en la conversacion
|
|
);
|
|
|
|
COMMENT ON TABLE llm.conversation_messages IS
|
|
'Mensajes individuales dentro de conversaciones LLM';
|
|
|
|
COMMENT ON COLUMN llm.conversation_messages.tool_call_id IS
|
|
'ID de llamada a herramienta para vincular request/response';
|
|
|
|
-- Indices
|
|
CREATE INDEX IF NOT EXISTS idx_messages_conversation
|
|
ON llm.conversation_messages(conversation_id, sequence_number);
|
|
|
|
CREATE INDEX IF NOT EXISTS idx_messages_parent
|
|
ON llm.conversation_messages(parent_message_id)
|
|
WHERE parent_message_id IS NOT NULL;
|
|
|
|
CREATE INDEX IF NOT EXISTS idx_messages_role
|
|
ON llm.conversation_messages(role);
|
|
|
|
CREATE INDEX IF NOT EXISTS idx_messages_tool
|
|
ON llm.conversation_messages(tool_call_id)
|
|
WHERE tool_call_id IS NOT NULL;
|
|
|
|
CREATE INDEX IF NOT EXISTS idx_messages_created
|
|
ON llm.conversation_messages(created_at DESC);
|
|
|
|
CREATE INDEX IF NOT EXISTS idx_messages_feedback
|
|
ON llm.conversation_messages(thumbs_up, feedback_at)
|
|
WHERE thumbs_up IS NOT NULL;
|
|
|
|
-- Trigger para updated_at
|
|
DROP TRIGGER IF EXISTS message_updated_at ON llm.conversation_messages;
|
|
CREATE TRIGGER message_updated_at
|
|
BEFORE UPDATE ON llm.conversation_messages
|
|
FOR EACH ROW
|
|
EXECUTE FUNCTION llm.update_llm_timestamp();
|
|
|
|
-- Trigger para actualizar estadisticas de conversacion
|
|
CREATE OR REPLACE FUNCTION llm.update_conversation_stats()
|
|
RETURNS TRIGGER AS $$
|
|
BEGIN
|
|
IF TG_OP = 'INSERT' THEN
|
|
UPDATE llm.conversations
|
|
SET message_count = message_count + 1,
|
|
token_count_input = token_count_input + COALESCE(NEW.prompt_tokens, 0),
|
|
token_count_output = token_count_output + COALESCE(NEW.completion_tokens, 0),
|
|
total_tokens = total_tokens + COALESCE(NEW.token_count, 0),
|
|
last_message_at = NEW.created_at,
|
|
last_user_message_at = CASE WHEN NEW.role = 'user' THEN NEW.created_at ELSE last_user_message_at END
|
|
WHERE id = NEW.conversation_id;
|
|
ELSIF TG_OP = 'DELETE' THEN
|
|
UPDATE llm.conversations
|
|
SET message_count = message_count - 1,
|
|
token_count_input = token_count_input - COALESCE(OLD.prompt_tokens, 0),
|
|
token_count_output = token_count_output - COALESCE(OLD.completion_tokens, 0),
|
|
total_tokens = total_tokens - COALESCE(OLD.token_count, 0)
|
|
WHERE id = OLD.conversation_id;
|
|
END IF;
|
|
|
|
RETURN COALESCE(NEW, OLD);
|
|
END;
|
|
$$ LANGUAGE plpgsql;
|
|
|
|
DROP TRIGGER IF EXISTS message_stats_update ON llm.conversation_messages;
|
|
CREATE TRIGGER message_stats_update
|
|
AFTER INSERT OR DELETE ON llm.conversation_messages
|
|
FOR EACH ROW
|
|
EXECUTE FUNCTION llm.update_conversation_stats();
|
|
|
|
-- Trigger para asignar sequence_number
|
|
CREATE OR REPLACE FUNCTION llm.assign_message_sequence()
|
|
RETURNS TRIGGER AS $$
|
|
BEGIN
|
|
SELECT COALESCE(MAX(sequence_number), 0) + 1 INTO NEW.sequence_number
|
|
FROM llm.conversation_messages
|
|
WHERE conversation_id = NEW.conversation_id;
|
|
|
|
RETURN NEW;
|
|
END;
|
|
$$ LANGUAGE plpgsql;
|
|
|
|
DROP TRIGGER IF EXISTS message_sequence ON llm.conversation_messages;
|
|
CREATE TRIGGER message_sequence
|
|
BEFORE INSERT ON llm.conversation_messages
|
|
FOR EACH ROW
|
|
WHEN (NEW.sequence_number = 0)
|
|
EXECUTE FUNCTION llm.assign_message_sequence();
|
|
|
|
-- Funcion para obtener historial de conversacion
|
|
CREATE OR REPLACE FUNCTION llm.get_conversation_history(
|
|
p_conversation_id UUID,
|
|
p_limit INTEGER DEFAULT 50
|
|
)
|
|
RETURNS TABLE (
|
|
role llm.message_role,
|
|
content TEXT,
|
|
tool_name VARCHAR,
|
|
tool_output JSONB,
|
|
created_at TIMESTAMPTZ
|
|
) AS $$
|
|
BEGIN
|
|
RETURN QUERY
|
|
SELECT
|
|
m.role,
|
|
m.content,
|
|
m.tool_name,
|
|
m.tool_output,
|
|
m.created_at
|
|
FROM llm.conversation_messages m
|
|
WHERE m.conversation_id = p_conversation_id
|
|
AND m.status = 'completed'
|
|
ORDER BY m.sequence_number DESC
|
|
LIMIT p_limit;
|
|
END;
|
|
$$ LANGUAGE plpgsql;
|
|
|
|
-- Vista de mensajes con feedback negativo (para mejorar)
|
|
CREATE OR REPLACE VIEW llm.v_messages_negative_feedback AS
|
|
SELECT
|
|
m.id,
|
|
m.conversation_id,
|
|
c.type AS conversation_type,
|
|
m.role,
|
|
m.content,
|
|
m.model,
|
|
m.feedback_text,
|
|
m.created_at
|
|
FROM llm.conversation_messages m
|
|
JOIN llm.conversations c ON m.conversation_id = c.id
|
|
WHERE m.thumbs_up = FALSE
|
|
ORDER BY m.feedback_at DESC;
|
|
|
|
-- RLS Policies (hereda de conversations via JOIN)
|
|
ALTER TABLE llm.conversation_messages ENABLE ROW LEVEL SECURITY;
|
|
|
|
CREATE POLICY messages_via_conversation ON llm.conversation_messages
|
|
FOR ALL
|
|
USING (
|
|
conversation_id IN (
|
|
SELECT id FROM llm.conversations
|
|
WHERE user_id = current_setting('app.current_user_id', true)::UUID
|
|
)
|
|
);
|
|
|
|
-- Grants
|
|
GRANT SELECT, INSERT, UPDATE, DELETE ON llm.conversation_messages TO trading_app;
|
|
GRANT SELECT ON llm.conversation_messages TO trading_readonly;
|
|
GRANT SELECT ON llm.v_messages_negative_feedback TO trading_app;
|
|
GRANT EXECUTE ON FUNCTION llm.get_conversation_history TO trading_app;
|