UNPKG

cyber-mysql-openai

Version:

Intelligent natural language to SQL translator with self-correction capabilities using OpenAI and MySQL

229 lines 10.7 kB
"use strict"; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.ResponseFormatter = void 0; // src/utils/responseFormatter.ts const openai_1 = require("openai"); const uuid_1 = require("uuid"); const index_1 = __importDefault(require("./index")); const i18n_1 = require("./i18n"); /** * Clase ResponseFormatter que maneja la generación de respuestas naturales * a partir de resultados SQL */ class ResponseFormatter { constructor(apiKey, model = "gpt-4", language = "en", logger, businessContext, responseStyleInstruction, lightModel) { this.openai = new openai_1.OpenAI({ apiKey: apiKey, }); this.model = lightModel || model; // Usar lightModel si está disponible this.logger = logger || new index_1.default(); this.i18n = new i18n_1.I18n(language); this.businessContext = businessContext || ""; this.responseStyleInstruction = responseStyleInstruction || ""; } /** * Cambia el idioma del formateador */ setLanguage(language) { this.i18n.setLanguage(language); } /** * Genera una explicación elaborada en lenguaje natural de los resultados SQL * @param sql - Consulta SQL ejecutada * @param results - Resultados de la consulta SQL * @param options - Opciones de configuración * @returns Explicación en lenguaje natural con formato markdown */ async generateNaturalResponse(sql, results, options = {}) { const requestId = (0, uuid_1.v4)(); const detailed = options.detailed || false; try { // Si no hay resultados, proporcionar una respuesta adecuada if (!results || (Array.isArray(results) && results.length === 0)) { return this.i18n.getMessage("responses", "noResultsFound"); } // Preparar el contexto para el modelo const resultData = typeof results === "string" ? results : JSON.stringify(results, null, 2); // Detectar características de los resultados para contextualizar mejor const isMultipleRows = Array.isArray(results) && results.length > 1; const numberOfRows = Array.isArray(results) ? results.length : 1; const fields = Array.isArray(results) && results.length > 0 ? Object.keys(results[0]).join(", ") : results && typeof results === "object" ? Object.keys(results).join(", ") : ""; // Seleccionar el prompt según el nivel de detalle solicitado const prompt = detailed ? this.generateDetailedPrompt(sql, resultData, isMultipleRows, numberOfRows, fields) : this.generateSimplePrompt(sql, resultData); this.logger.debug("Generating natural response", { sql, detailed, language: this.i18n.getLanguage(), }); try { const response = await this.openai.chat.completions.create({ model: this.model, messages: [{ role: "user", content: prompt }], }); const explanation = response.choices[0]?.message?.content?.trim() || this.i18n.getMessage("responses", "queryCompleted"); // Registrar uso de tokens if (response.usage && this.logger) { this.logger.logTokenUsage(requestId, "format-response", response.usage.prompt_tokens, response.usage.completion_tokens, response.usage.total_tokens, this.model); } // Registrar prompt y respuesta if (this.logger) { this.logger.logPromptAndResponse(requestId, "format-response", prompt, explanation, true, detailed ? 2 : 1, // Nivel de complejidad this.model); } return explanation; } catch (error) { this.logger.error("Error calling OpenAI to generate natural response:", error); return detailed ? this.i18n.getMessage("errors", "openaiError") : this.i18n.getMessage("responses", "queryCompleted"); } } catch (error) { this.logger.error("General error generating natural response:", error); // En caso de error, devolver una respuesta genérica return this.i18n.getMessage("responses", "queryCompleted"); } } /** * Genera un stream de explicación en lenguaje natural a partir de los resultados de base de datos */ async *generateNaturalResponseStream(sql, results, options = {}) { const requestId = (0, uuid_1.v4)(); const detailed = options.detailed || false; try { if (!results || (Array.isArray(results) && results.length === 0)) { yield this.i18n.getMessage("responses", "noResultsFound"); return; } const resultData = typeof results === "string" ? results : JSON.stringify(results, null, 2); const isMultipleRows = Array.isArray(results) && results.length > 1; const numberOfRows = Array.isArray(results) ? results.length : 1; const fields = Array.isArray(results) && results.length > 0 ? Object.keys(results[0]).join(", ") : results && typeof results === "object" ? Object.keys(results).join(", ") : ""; const prompt = detailed ? this.generateDetailedPrompt(sql, resultData, isMultipleRows, numberOfRows, fields) : this.generateSimplePrompt(sql, resultData); this.logger.debug("Generating natural response stream", { sql, detailed, language: this.i18n.getLanguage(), }); try { const stream = await this.openai.chat.completions.create({ model: this.model, messages: [{ role: "user", content: prompt }], stream: true, }); let fullText = ""; for await (const chunk of stream) { const content = chunk.choices[0]?.delta?.content || ""; if (content) { fullText += content; yield content; } } // Registrar prompt y respuesta si hay logger if (this.logger) { this.logger.logPromptAndResponse(requestId, "format-response-stream", prompt, fullText, true, detailed ? 2 : 1, this.model); } } catch (error) { this.logger.error("Error calling OpenAI to generate natural response stream:", error); yield detailed ? this.i18n.getMessage("errors", "openaiError") : this.i18n.getMessage("responses", "queryCompleted"); } } catch (error) { this.logger.error("General error generating natural response stream:", error); yield this.i18n.getMessage("responses", "queryCompleted"); } } /** * Genera un prompt para respuestas simples y directas */ generateSimplePrompt(sql, resultData) { const template = this.i18n.getMessage("prompts", "generateNaturalResponse"); return template .replace("{sql}", sql) .replace("{results}", resultData) .replace("{businessContext}", this.businessContext) .replace("{responseStyleInstruction}", this.responseStyleInstruction); } /** * Genera un prompt para respuestas elaboradas y analíticas */ generateDetailedPrompt(sql, resultData, _isMultipleRows, _numberOfRows, _fields) { const template = this.i18n.getMessage("prompts", "generateDetailedResponse"); return template .replace("{sql}", sql) .replace("{results}", resultData) .replace("{businessContext}", this.businessContext); } /** * Genera respuestas simples para casos comunes sin usar el modelo */ generateSimpleResponse(sql, results) { // Asegurar que results es un array const resultsArray = Array.isArray(results) ? results : [results]; if (!resultsArray.length) return null; const firstResult = resultsArray[0]; // Caso: consulta de precio if (sql.toLowerCase().includes("price") && firstResult.price_public) { const product = firstResult.product_name || firstResult.name || this.i18n.getMessage("labels", "sqlGenerated"); return `${product} tiene un precio de **$${firstResult.price_public}**.`; } // Caso: consulta de stock/inventario if (sql.toLowerCase().includes("stock") && firstResult.stock !== undefined) { const product = firstResult.product_name || firstResult.name || this.i18n.getMessage("labels", "sqlGenerated"); return `${product} tiene **${firstResult.stock}** unidades disponibles.`; } // Caso: consulta de beneficios/ingresos if ((sql.toLowerCase().includes("profit") || sql.toLowerCase().includes("income") || sql.toLowerCase().includes("revenue")) && firstResult) { const month = firstResult.month || firstResult.fecha || firstResult.date; const profit = firstResult.profit || firstResult.benefits || firstResult.beneficios; const revenue = firstResult.revenue || firstResult.income || firstResult.ingresos || firstResult.ingresos_totales; if (month && profit && revenue) { return `El periodo ${month} tuvo **$${profit}** en beneficios y **$${revenue}** en ingresos totales.`; } else if (profit && revenue) { return `Se registraron **$${profit}** en beneficios y **$${revenue}** en ingresos totales.`; } } // Para otros casos, usaremos el modelo return null; } } exports.ResponseFormatter = ResponseFormatter; exports.default = ResponseFormatter; //# sourceMappingURL=responseFormatter.js.map