- 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>
264 lines
8.1 KiB
PL/PgSQL
264 lines
8.1 KiB
PL/PgSQL
-- ============================================================================
|
|
-- SCHEMA: llm
|
|
-- TABLE: llm_tools_usage
|
|
-- DESCRIPTION: Registro de uso de herramientas por el agente LLM
|
|
-- VERSION: 1.0.0
|
|
-- CREATED: 2026-01-16
|
|
-- SPRINT: Sprint 5 - DDL Implementation Roadmap Q1-2026
|
|
-- ============================================================================
|
|
|
|
-- Enum para categoria de herramienta
|
|
DO $$ BEGIN
|
|
CREATE TYPE llm.tool_category AS ENUM (
|
|
'market_data', -- Datos de mercado
|
|
'trading', -- Operaciones de trading
|
|
'portfolio', -- Gestion de portafolio
|
|
'analysis', -- Analisis tecnico/fundamental
|
|
'charts', -- Graficos
|
|
'alerts', -- Alertas
|
|
'education', -- Educacion
|
|
'news', -- Noticias
|
|
'calendar', -- Calendario economico
|
|
'calculations', -- Calculos financieros
|
|
'external_api', -- APIs externas
|
|
'system' -- Sistema
|
|
);
|
|
EXCEPTION
|
|
WHEN duplicate_object THEN null;
|
|
END $$;
|
|
|
|
-- Enum para estado de ejecucion
|
|
DO $$ BEGIN
|
|
CREATE TYPE llm.tool_execution_status AS ENUM (
|
|
'pending', -- Pendiente
|
|
'executing', -- Ejecutando
|
|
'success', -- Exitoso
|
|
'error', -- Error
|
|
'timeout', -- Timeout
|
|
'cancelled', -- Cancelado
|
|
'rate_limited', -- Rate limited
|
|
'permission_denied' -- Sin permisos
|
|
);
|
|
EXCEPTION
|
|
WHEN duplicate_object THEN null;
|
|
END $$;
|
|
|
|
-- Tabla de Uso de Herramientas
|
|
CREATE TABLE IF NOT EXISTS llm.llm_tools_usage (
|
|
-- 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,
|
|
|
|
-- Referencias
|
|
conversation_id UUID REFERENCES llm.conversations(id) ON DELETE SET NULL,
|
|
message_id UUID REFERENCES llm.conversation_messages(id) ON DELETE SET NULL,
|
|
|
|
-- Herramienta
|
|
tool_name VARCHAR(100) NOT NULL,
|
|
tool_category llm.tool_category NOT NULL,
|
|
tool_version VARCHAR(20),
|
|
|
|
-- Ejecucion
|
|
status llm.tool_execution_status NOT NULL DEFAULT 'pending',
|
|
execution_time_ms INTEGER,
|
|
|
|
-- Input/Output
|
|
input_params JSONB NOT NULL DEFAULT '{}'::JSONB,
|
|
output_result JSONB,
|
|
output_size_bytes INTEGER,
|
|
|
|
-- Error
|
|
error_code VARCHAR(50),
|
|
error_message TEXT,
|
|
error_details JSONB,
|
|
|
|
-- Contexto
|
|
context_symbol VARCHAR(20),
|
|
context_timeframe VARCHAR(10),
|
|
context_data JSONB DEFAULT '{}'::JSONB,
|
|
|
|
-- Rate limiting
|
|
rate_limit_bucket VARCHAR(50),
|
|
rate_limit_remaining INTEGER,
|
|
|
|
-- Costo
|
|
cost_units DECIMAL(10, 4) DEFAULT 0, -- Unidades de costo (API calls, etc)
|
|
cost_usd DECIMAL(10, 6) DEFAULT 0, -- Costo en USD
|
|
|
|
-- Auditoria
|
|
ip_address INET,
|
|
user_agent TEXT,
|
|
|
|
-- Metadata
|
|
metadata JSONB DEFAULT '{}'::JSONB,
|
|
|
|
-- Timestamps
|
|
requested_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
|
started_at TIMESTAMPTZ,
|
|
completed_at TIMESTAMPTZ,
|
|
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
|
);
|
|
|
|
COMMENT ON TABLE llm.llm_tools_usage IS
|
|
'Registro de todas las ejecuciones de herramientas por el agente LLM';
|
|
|
|
COMMENT ON COLUMN llm.llm_tools_usage.cost_units IS
|
|
'Unidades de costo internas (ej: 1 API call = 1 unit)';
|
|
|
|
-- Indices
|
|
CREATE INDEX IF NOT EXISTS idx_tools_usage_tenant
|
|
ON llm.llm_tools_usage(tenant_id);
|
|
|
|
CREATE INDEX IF NOT EXISTS idx_tools_usage_user
|
|
ON llm.llm_tools_usage(user_id);
|
|
|
|
CREATE INDEX IF NOT EXISTS idx_tools_usage_conversation
|
|
ON llm.llm_tools_usage(conversation_id)
|
|
WHERE conversation_id IS NOT NULL;
|
|
|
|
CREATE INDEX IF NOT EXISTS idx_tools_usage_tool
|
|
ON llm.llm_tools_usage(tool_name, requested_at DESC);
|
|
|
|
CREATE INDEX IF NOT EXISTS idx_tools_usage_category
|
|
ON llm.llm_tools_usage(tool_category, requested_at DESC);
|
|
|
|
CREATE INDEX IF NOT EXISTS idx_tools_usage_status
|
|
ON llm.llm_tools_usage(status);
|
|
|
|
CREATE INDEX IF NOT EXISTS idx_tools_usage_errors
|
|
ON llm.llm_tools_usage(status, tool_name)
|
|
WHERE status IN ('error', 'timeout');
|
|
|
|
CREATE INDEX IF NOT EXISTS idx_tools_usage_date
|
|
ON llm.llm_tools_usage(requested_at DESC);
|
|
|
|
-- Indice BRIN para datos temporales
|
|
CREATE INDEX IF NOT EXISTS idx_tools_usage_brin
|
|
ON llm.llm_tools_usage USING BRIN (requested_at);
|
|
|
|
-- Funcion para registrar inicio de ejecucion
|
|
CREATE OR REPLACE FUNCTION llm.start_tool_execution(
|
|
p_tool_usage_id UUID
|
|
)
|
|
RETURNS VOID AS $$
|
|
BEGIN
|
|
UPDATE llm.llm_tools_usage
|
|
SET status = 'executing',
|
|
started_at = NOW()
|
|
WHERE id = p_tool_usage_id;
|
|
END;
|
|
$$ LANGUAGE plpgsql;
|
|
|
|
-- Funcion para registrar fin exitoso
|
|
CREATE OR REPLACE FUNCTION llm.complete_tool_execution(
|
|
p_tool_usage_id UUID,
|
|
p_output JSONB
|
|
)
|
|
RETURNS VOID AS $$
|
|
DECLARE
|
|
v_started_at TIMESTAMPTZ;
|
|
BEGIN
|
|
SELECT started_at INTO v_started_at
|
|
FROM llm.llm_tools_usage
|
|
WHERE id = p_tool_usage_id;
|
|
|
|
UPDATE llm.llm_tools_usage
|
|
SET status = 'success',
|
|
completed_at = NOW(),
|
|
execution_time_ms = EXTRACT(EPOCH FROM (NOW() - v_started_at)) * 1000,
|
|
output_result = p_output,
|
|
output_size_bytes = LENGTH(p_output::TEXT)
|
|
WHERE id = p_tool_usage_id;
|
|
END;
|
|
$$ LANGUAGE plpgsql;
|
|
|
|
-- Funcion para registrar error
|
|
CREATE OR REPLACE FUNCTION llm.fail_tool_execution(
|
|
p_tool_usage_id UUID,
|
|
p_error_code VARCHAR,
|
|
p_error_message TEXT,
|
|
p_error_details JSONB DEFAULT NULL
|
|
)
|
|
RETURNS VOID AS $$
|
|
BEGIN
|
|
UPDATE llm.llm_tools_usage
|
|
SET status = 'error',
|
|
completed_at = NOW(),
|
|
error_code = p_error_code,
|
|
error_message = p_error_message,
|
|
error_details = p_error_details
|
|
WHERE id = p_tool_usage_id;
|
|
END;
|
|
$$ LANGUAGE plpgsql;
|
|
|
|
-- Vista de estadisticas de herramientas
|
|
CREATE OR REPLACE VIEW llm.v_tool_usage_stats AS
|
|
SELECT
|
|
tool_name,
|
|
tool_category,
|
|
COUNT(*) AS total_calls,
|
|
COUNT(*) FILTER (WHERE status = 'success') AS successful_calls,
|
|
COUNT(*) FILTER (WHERE status = 'error') AS failed_calls,
|
|
ROUND((COUNT(*) FILTER (WHERE status = 'success')::DECIMAL
|
|
/ NULLIF(COUNT(*), 0) * 100), 2) AS success_rate,
|
|
ROUND(AVG(execution_time_ms)::NUMERIC, 2) AS avg_execution_ms,
|
|
ROUND(PERCENTILE_CONT(0.95) WITHIN GROUP (ORDER BY execution_time_ms)::NUMERIC, 2) AS p95_execution_ms,
|
|
SUM(cost_units) AS total_cost_units,
|
|
SUM(cost_usd) AS total_cost_usd
|
|
FROM llm.llm_tools_usage
|
|
WHERE completed_at IS NOT NULL
|
|
GROUP BY tool_name, tool_category
|
|
ORDER BY total_calls DESC;
|
|
|
|
-- Vista de errores recientes
|
|
CREATE OR REPLACE VIEW llm.v_tool_errors AS
|
|
SELECT
|
|
id,
|
|
user_id,
|
|
tool_name,
|
|
tool_category,
|
|
error_code,
|
|
error_message,
|
|
input_params,
|
|
requested_at
|
|
FROM llm.llm_tools_usage
|
|
WHERE status IN ('error', 'timeout')
|
|
ORDER BY requested_at DESC
|
|
LIMIT 100;
|
|
|
|
-- Vista de uso por usuario
|
|
CREATE OR REPLACE VIEW llm.v_user_tool_usage AS
|
|
SELECT
|
|
user_id,
|
|
tool_category,
|
|
COUNT(*) AS total_calls,
|
|
SUM(cost_units) AS total_cost_units,
|
|
SUM(cost_usd) AS total_cost_usd,
|
|
MAX(requested_at) AS last_used_at
|
|
FROM llm.llm_tools_usage
|
|
WHERE requested_at >= NOW() - INTERVAL '30 days'
|
|
GROUP BY user_id, tool_category
|
|
ORDER BY user_id, total_calls DESC;
|
|
|
|
-- RLS Policies
|
|
ALTER TABLE llm.llm_tools_usage ENABLE ROW LEVEL SECURITY;
|
|
|
|
CREATE POLICY tools_usage_tenant_isolation ON llm.llm_tools_usage
|
|
FOR ALL
|
|
USING (tenant_id = current_setting('app.current_tenant_id', true)::UUID);
|
|
|
|
CREATE POLICY tools_usage_user_isolation ON llm.llm_tools_usage
|
|
FOR SELECT
|
|
USING (user_id = current_setting('app.current_user_id', true)::UUID);
|
|
|
|
-- Grants
|
|
GRANT SELECT, INSERT, UPDATE ON llm.llm_tools_usage TO trading_app;
|
|
GRANT SELECT ON llm.llm_tools_usage TO trading_readonly;
|
|
GRANT SELECT ON llm.v_tool_usage_stats TO trading_app;
|
|
GRANT SELECT ON llm.v_tool_errors TO trading_app;
|
|
GRANT SELECT ON llm.v_user_tool_usage TO trading_app;
|
|
GRANT EXECUTE ON FUNCTION llm.start_tool_execution TO trading_app;
|
|
GRANT EXECUTE ON FUNCTION llm.complete_tool_execution TO trading_app;
|
|
GRANT EXECUTE ON FUNCTION llm.fail_tool_execution TO trading_app;
|