# RF-MGN-018-004: Acciones y Herramientas **Módulo:** MGN-018 - AI Agents & Chatbots **Prioridad:** P2 **Story Points:** 13 **Estado:** Definido **Fecha:** 2025-12-05 ## Descripción El sistema debe permitir que los agentes de IA ejecuten acciones en el sistema ERP mediante herramientas (tools/functions). Las herramientas permiten al agente consultar información (pedidos, productos), crear registros (leads, tickets), y ejecutar operaciones específicas del negocio. Los tenants pueden habilitar herramientas predefinidas y crear herramientas personalizadas. ## Actores - **Actor Principal:** AI Agent (invoca herramientas) - **Actores Secundarios:** - Sistema (ejecuta acciones) - Tenant Admin (configura herramientas) - APIs externas (integraciones) ## Precondiciones 1. Agente configurado con herramientas habilitadas 2. Permisos adecuados para cada acción 3. Feature flags correspondientes activos ## Flujo Principal - Ejecutar Herramienta 1. LLM determina que necesita ejecutar una herramienta 2. LLM genera tool call con argumentos 3. Sistema valida que herramienta está habilitada para el agente 4. Sistema valida argumentos según schema 5. Sistema verifica permisos 6. Sistema ejecuta herramienta 7. Sistema captura resultado o error 8. Sistema retorna resultado al LLM 9. LLM genera respuesta final para usuario 10. Sistema registra ejecución en logs ## Herramientas Predefinidas ### 1. Consulta de Pedidos ```typescript interface CheckOrderStatusTool { name: 'check_order_status'; description: 'Consulta el estado de un pedido por número de orden o datos del cliente'; parameters: { order_number?: string; // Número de orden customer_phone?: string; // Teléfono del cliente customer_email?: string; // Email del cliente }; returns: { found: boolean; order?: { order_number: string; status: string; status_description: string; created_at: string; total: number; items: Array<{ name: string; quantity: number; }>; shipping?: { carrier: string; tracking_number: string; estimated_delivery: string; }; }; message: string; }; } ``` ### 2. Consulta de Productos ```typescript interface SearchProductsTool { name: 'search_products'; description: 'Busca productos en el catálogo por nombre, categoría o características'; parameters: { query: string; // Búsqueda de texto category?: string; // Filtro por categoría min_price?: number; max_price?: number; in_stock_only?: boolean; limit?: number; // Default: 5 }; returns: { products: Array<{ id: string; name: string; description: string; price: number; stock_available: number; image_url?: string; category: string; }>; total_count: number; }; } ``` ### 3. Crear Lead ```typescript interface CreateLeadTool { name: 'create_lead'; description: 'Crea un nuevo lead/prospecto en el CRM'; parameters: { name: string; // Nombre del prospecto phone?: string; email?: string; company?: string; interest?: string; // Producto/servicio de interés notes?: string; // Notas adicionales source?: string; // Default: 'ai_agent' }; returns: { success: boolean; lead_id?: string; message: string; }; } ``` ### 4. Crear Ticket de Soporte ```typescript interface CreateTicketTool { name: 'create_support_ticket'; description: 'Crea un ticket de soporte para seguimiento por el equipo'; parameters: { subject: string; description: string; priority?: 'low' | 'medium' | 'high'; category?: string; customer_name?: string; customer_contact?: string; }; returns: { success: boolean; ticket_id?: string; ticket_number?: string; message: string; }; } ``` ### 5. Agendar Cita/Llamada ```typescript interface ScheduleAppointmentTool { name: 'schedule_appointment'; description: 'Agenda una cita o llamada con el equipo de ventas'; parameters: { customer_name: string; customer_phone?: string; customer_email?: string; preferred_date?: string; // ISO date preferred_time?: string; // HH:mm appointment_type: 'call' | 'meeting' | 'demo'; notes?: string; }; returns: { success: boolean; appointment_id?: string; confirmed_datetime?: string; message: string; }; } ``` ### 6. Consultar Disponibilidad ```typescript interface CheckAvailabilityTool { name: 'check_availability'; description: 'Verifica disponibilidad de productos o slots de citas'; parameters: { type: 'product' | 'appointment'; product_id?: string; date_from?: string; date_to?: string; }; returns: { available: boolean; details: any; message: string; }; } ``` ### 7. Enviar Notificación Interna ```typescript interface NotifyTeamTool { name: 'notify_team'; description: 'Envía notificación al equipo para casos urgentes'; parameters: { team: string; // 'sales', 'support', 'management' message: string; priority: 'normal' | 'high' | 'urgent'; context?: object; }; returns: { success: boolean; notification_id?: string; }; } ``` ## Herramientas Personalizadas ### Definición de Tool Personalizada ```typescript interface CustomToolDefinition { id: string; tenant_id: string; name: string; description: string; // Schema de parámetros (JSON Schema) parameters_schema: { type: 'object'; properties: Record; required?: string[]; }; // Tipo de ejecución execution_type: 'api_call' | 'webhook' | 'internal_function'; // Configuración según tipo execution_config: { // Para api_call url?: string; method?: 'GET' | 'POST' | 'PUT'; headers?: Record; body_template?: string; // Para webhook webhook_url?: string; webhook_secret?: string; // Para internal_function function_name?: string; }; // Transformación de respuesta response_transform?: string; // Template o JMESPath // Límites timeout_ms: number; rate_limit_per_minute: number; is_active: boolean; } ``` ### Ejemplo: Tool de API Externa ```json { "id": "custom_shipping_check", "name": "check_shipping_rate", "description": "Consulta tarifas de envío con proveedor externo", "parameters_schema": { "type": "object", "properties": { "postal_code": { "type": "string", "description": "Código postal de destino" }, "weight_kg": { "type": "number", "description": "Peso del paquete en kg" } }, "required": ["postal_code", "weight_kg"] }, "execution_type": "api_call", "execution_config": { "url": "https://api.shipping-provider.com/rates", "method": "POST", "headers": { "Authorization": "Bearer {{SHIPPING_API_KEY}}", "Content-Type": "application/json" }, "body_template": "{\"destination\": \"{{postal_code}}\", \"weight\": {{weight_kg}}}" }, "response_transform": "{rate: response.rates[0].price, carrier: response.rates[0].carrier, days: response.rates[0].delivery_days}", "timeout_ms": 5000, "rate_limit_per_minute": 10 } ``` ## Tool Call Flow (OpenAI Format) ```typescript // Request al LLM con tools const completion = await openai.chat.completions.create({ model: "gpt-4o", messages: messages, tools: [ { type: "function", function: { name: "check_order_status", description: "Consulta el estado de un pedido", parameters: { type: "object", properties: { order_number: { type: "string", description: "Número de orden" } }, required: ["order_number"] } } } ] }); // Si el LLM decide usar tool if (completion.choices[0].message.tool_calls) { for (const toolCall of completion.choices[0].message.tool_calls) { // Ejecutar tool const result = await executeToolCall(toolCall); // Agregar resultado a mensajes messages.push({ role: "tool", tool_call_id: toolCall.id, content: JSON.stringify(result) }); } // Segunda llamada al LLM con resultados const finalResponse = await openai.chat.completions.create({ model: "gpt-4o", messages: messages }); } ``` ## Reglas de Negocio - **RN-1:** Solo herramientas habilitadas por agente pueden ejecutarse - **RN-2:** Herramientas personalizadas requieren plan Enterprise - **RN-3:** Timeout máximo por herramienta: 30 segundos - **RN-4:** Máximo 5 tool calls por turno de conversación - **RN-5:** Resultados de tools se loguean (sin datos sensibles) - **RN-6:** Webhooks deben responder en máximo 10 segundos - **RN-7:** APIs externas usan rate limiting por tenant ## Criterios de Aceptación - [ ] Todas las herramientas predefinidas funcionan - [ ] LLM puede invocar herramientas según contexto - [ ] Validación de parámetros antes de ejecución - [ ] Manejo de errores y timeouts - [ ] Herramientas personalizadas configurables - [ ] Ejecución de webhooks externos funciona - [ ] Logs de todas las ejecuciones - [ ] Rate limiting por herramienta - [ ] Admin puede habilitar/deshabilitar por agente ## Entidades Involucradas ### ai_agents.tool_definitions ```sql CREATE TABLE ai_agents.tool_definitions ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), tenant_id UUID, -- NULL para tools del sistema -- Identificación name VARCHAR(100) NOT NULL, display_name VARCHAR(200), description TEXT NOT NULL, category VARCHAR(50), -- orders, products, crm, support, custom -- Schema parameters_schema JSONB NOT NULL, -- JSON Schema para validación -- Ejecución execution_type VARCHAR(50) NOT NULL, -- internal, api_call, webhook execution_config JSONB NOT NULL, -- Respuesta response_schema JSONB, response_transform TEXT, -- Configuración timeout_ms INT DEFAULT 10000, rate_limit_per_minute INT DEFAULT 60, requires_confirmation BOOLEAN DEFAULT false, -- Estado is_system BOOLEAN DEFAULT false, is_active BOOLEAN DEFAULT true, created_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP, updated_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP, CONSTRAINT uq_tool_name_tenant UNIQUE (tenant_id, name) ); -- Tools del sistema INSERT INTO ai_agents.tool_definitions (name, description, category, parameters_schema, execution_type, execution_config, is_system) VALUES ('check_order_status', 'Consulta el estado de un pedido', 'orders', '{ "type": "object", "properties": { "order_number": {"type": "string", "description": "Número de orden"}, "customer_phone": {"type": "string", "description": "Teléfono del cliente"} } }', 'internal', '{"function": "orders.getStatus"}', true), ('search_products', 'Busca productos en el catálogo', 'products', '{ "type": "object", "properties": { "query": {"type": "string", "description": "Texto de búsqueda"}, "category": {"type": "string"}, "limit": {"type": "integer", "default": 5} }, "required": ["query"] }', 'internal', '{"function": "products.search"}', true), ('create_lead', 'Crea un lead en el CRM', 'crm', '{ "type": "object", "properties": { "name": {"type": "string"}, "phone": {"type": "string"}, "email": {"type": "string"}, "interest": {"type": "string"} }, "required": ["name"] }', 'internal', '{"function": "crm.createLead"}', true), ('create_support_ticket', 'Crea un ticket de soporte', 'support', '{ "type": "object", "properties": { "subject": {"type": "string"}, "description": {"type": "string"}, "priority": {"type": "string", "enum": ["low", "medium", "high"]} }, "required": ["subject", "description"] }', 'internal', '{"function": "support.createTicket"}', true); ``` ### ai_agents.agent_tools ```sql CREATE TABLE ai_agents.agent_tools ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), agent_id UUID NOT NULL REFERENCES ai_agents.agents(id) ON DELETE CASCADE, tool_id UUID NOT NULL REFERENCES ai_agents.tool_definitions(id), -- Configuración específica para este agente is_enabled BOOLEAN DEFAULT true, custom_config JSONB DEFAULT '{}', -- Override de parámetros, límites, etc. created_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP, CONSTRAINT uq_agent_tool UNIQUE (agent_id, tool_id) ); ``` ### ai_agents.tool_executions ```sql CREATE TABLE ai_agents.tool_executions ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), tenant_id UUID NOT NULL, agent_id UUID NOT NULL, conversation_id UUID NOT NULL, message_id UUID NOT NULL, tool_id UUID NOT NULL, -- Invocación tool_name VARCHAR(100) NOT NULL, input_params JSONB NOT NULL, -- Resultado success BOOLEAN NOT NULL, output JSONB, error_message TEXT, -- Timing started_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP, completed_at TIMESTAMPTZ, duration_ms INT, -- Metadata execution_type VARCHAR(50), external_request_id VARCHAR(100) ); CREATE INDEX idx_tool_exec_tenant ON ai_agents.tool_executions(tenant_id); CREATE INDEX idx_tool_exec_conversation ON ai_agents.tool_executions(conversation_id); CREATE INDEX idx_tool_exec_created ON ai_agents.tool_executions(started_at); ``` ## Implementación de Tool Executor ```typescript class ToolExecutor { async execute( toolCall: ToolCall, context: ExecutionContext ): Promise { const tool = await this.getToolDefinition(toolCall.name); // 1. Validar que tool está habilitada para el agente if (!await this.isToolEnabled(context.agent_id, tool.id)) { throw new ToolNotEnabledError(toolCall.name); } // 2. Validar parámetros this.validateParams(toolCall.arguments, tool.parameters_schema); // 3. Rate limiting await this.checkRateLimit(context.tenant_id, tool.id); // 4. Ejecutar según tipo const startTime = Date.now(); let result: any; try { switch (tool.execution_type) { case 'internal': result = await this.executeInternal(tool, toolCall.arguments, context); break; case 'api_call': result = await this.executeApiCall(tool, toolCall.arguments, context); break; case 'webhook': result = await this.executeWebhook(tool, toolCall.arguments, context); break; } // 5. Transformar respuesta si necesario if (tool.response_transform) { result = this.transformResponse(result, tool.response_transform); } // 6. Log ejecución await this.logExecution({ tenant_id: context.tenant_id, agent_id: context.agent_id, conversation_id: context.conversation_id, message_id: context.message_id, tool_id: tool.id, tool_name: tool.name, input_params: toolCall.arguments, success: true, output: result, duration_ms: Date.now() - startTime }); return { success: true, data: result }; } catch (error) { await this.logExecution({ // ... campos success: false, error_message: error.message, duration_ms: Date.now() - startTime }); return { success: false, error: error.message }; } } private async executeInternal( tool: ToolDefinition, params: any, context: ExecutionContext ): Promise { const functionName = tool.execution_config.function; // Map de funciones internas const internalFunctions: Record = { 'orders.getStatus': this.ordersService.getStatus.bind(this.ordersService), 'products.search': this.productsService.search.bind(this.productsService), 'crm.createLead': this.crmService.createLead.bind(this.crmService), 'support.createTicket': this.supportService.createTicket.bind(this.supportService), }; const fn = internalFunctions[functionName]; if (!fn) throw new Error(`Unknown function: ${functionName}`); return fn(params, context); } private async executeApiCall( tool: ToolDefinition, params: any, context: ExecutionContext ): Promise { const config = tool.execution_config; // Interpolate template let url = this.interpolate(config.url, params); let body = config.body_template ? this.interpolate(config.body_template, params) : JSON.stringify(params); // Headers con secrets const headers = await this.resolveHeaders(config.headers, context.tenant_id); const response = await fetch(url, { method: config.method || 'POST', headers, body: config.method !== 'GET' ? body : undefined, signal: AbortSignal.timeout(tool.timeout_ms) }); if (!response.ok) { throw new Error(`API call failed: ${response.status}`); } return response.json(); } } ``` ## Interfaz de Configuración ``` ┌─────────────────────────────────────────────────────────────────┐ │ ⚙️ Herramientas del Agente: Asistente de Ventas │ ├─────────────────────────────────────────────────────────────────┤ │ │ │ Herramientas del Sistema: │ │ ┌─────────────────────────────────────────────────────────────┐│ │ │ ☑️ check_order_status Consultar estado de pedidos ││ │ │ ☑️ search_products Buscar en catálogo de productos ││ │ │ ☑️ create_lead Crear prospectos en CRM ││ │ │ ☐ create_support_ticket Crear tickets de soporte ││ │ │ ☐ schedule_appointment Agendar citas/llamadas ││ │ └─────────────────────────────────────────────────────────────┘│ │ │ │ Herramientas Personalizadas: │ │ ┌─────────────────────────────────────────────────────────────┐│ │ │ ☑️ check_shipping_rate Consultar tarifas de envío ││ │ │ ☐ verify_inventory Verificar inventario en tiempo real││ │ └─────────────────────────────────────────────────────────────┘│ │ │ │ [+ Crear herramienta personalizada] │ │ │ │ [Cancelar] [Guardar] │ └─────────────────────────────────────────────────────────────────┘ ``` ## Referencias - [OpenAI Function Calling](https://platform.openai.com/docs/guides/function-calling) - [Anthropic Tool Use](https://docs.anthropic.com/en/docs/build-with-claude/tool-use) - [JSON Schema](https://json-schema.org/) ## Dependencias - **RF Requeridos:** RF-018-003 (Procesamiento de Mensajes) - **Bloqueante para:** RF-018-005 (Entrenamiento)