cyber-mysql-openai
Version:
Intelligent natural language to SQL translator with self-correction capabilities using OpenAI and MySQL
229 lines • 10.7 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, 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