cyber-mysql-openai
Version:
Intelligent natural language to SQL translator with self-correction capabilities using OpenAI and MySQL
147 lines • 7.17 kB
JavaScript
"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) {
this.openai = new openai_1.OpenAI({
apiKey: apiKey,
});
this.model = model;
this.logger = logger || new index_1.default();
this.i18n = new i18n_1.I18n(language);
}
/**
* 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 prompt para respuestas simples y directas
*/
generateSimplePrompt(sql, resultData) {
const template = this.i18n.getMessage('prompts', 'generateNaturalResponse');
return template.replace('{sql}', sql).replace('{results}', resultData);
}
/**
* 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);
}
/**
* 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