Subir archivos a "/"
This commit is contained in:
commit
3d7564b822
|
|
@ -0,0 +1,8 @@
|
||||||
|
# URL del servidor Ollama (por defecto localhost)
|
||||||
|
OLLAMA_URL=http://localhost:11434/api/generate
|
||||||
|
|
||||||
|
# Modelo a utilizar (debe estar instalado en Ollama)
|
||||||
|
MODEL=openchat
|
||||||
|
|
||||||
|
# Timeout en milisegundos (por defecto 120000 = 2 minutos)
|
||||||
|
TIMEOUT=120000
|
||||||
|
|
@ -0,0 +1,87 @@
|
||||||
|
// ai_client.js
|
||||||
|
const OLLAMA_URL = process.env.OLLAMA_URL || "http://localhost:11434/api/generate";
|
||||||
|
const MODEL = process.env.MODEL || "openchat";
|
||||||
|
const TIMEOUT = parseInt(process.env.TIMEOUT || "120000"); // 2 minutos por defecto
|
||||||
|
|
||||||
|
// Rate limiting
|
||||||
|
let lastCallTime = 0;
|
||||||
|
const MIN_CALL_INTERVAL = 2000; // 2 segundos
|
||||||
|
|
||||||
|
// Caché básica
|
||||||
|
const cache = new Map();
|
||||||
|
|
||||||
|
async function callLLM(prompt, maxTokens = 1000) {
|
||||||
|
// Rate limiting
|
||||||
|
const now = Date.now();
|
||||||
|
const timeSinceLastCall = now - lastCallTime;
|
||||||
|
if (timeSinceLastCall < MIN_CALL_INTERVAL) {
|
||||||
|
const waitTime = MIN_CALL_INTERVAL - timeSinceLastCall;
|
||||||
|
console.log(`⏳ Esperando ${waitTime}ms por rate limit...`);
|
||||||
|
await new Promise(resolve => setTimeout(resolve, waitTime));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verificar caché
|
||||||
|
const cacheKey = `${prompt}_${maxTokens}`;
|
||||||
|
if (cache.has(cacheKey)) {
|
||||||
|
console.log("✅ Respuesta obtenida de caché");
|
||||||
|
return cache.get(cacheKey);
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Crear un AbortController manual para timeout
|
||||||
|
const controller = new AbortController();
|
||||||
|
const timeoutId = setTimeout(() => controller.abort(), TIMEOUT);
|
||||||
|
|
||||||
|
const response = await fetch(OLLAMA_URL, {
|
||||||
|
method: "POST",
|
||||||
|
headers: {
|
||||||
|
"Content-Type": "application/json"
|
||||||
|
},
|
||||||
|
body: JSON.stringify({
|
||||||
|
model: MODEL,
|
||||||
|
prompt: prompt,
|
||||||
|
stream: false,
|
||||||
|
options: {
|
||||||
|
num_predict: maxTokens,
|
||||||
|
temperature: 0.7
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
signal: controller.signal
|
||||||
|
});
|
||||||
|
|
||||||
|
clearTimeout(timeoutId);
|
||||||
|
lastCallTime = Date.now();
|
||||||
|
|
||||||
|
if (response.status === 429) {
|
||||||
|
console.error("⚠️ Error 429: Demasiadas peticiones. Esperando 5 segundos...");
|
||||||
|
await new Promise(resolve => setTimeout(resolve, 5000));
|
||||||
|
return callLLM(prompt, maxTokens); // Reintentar
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!response.ok) {
|
||||||
|
const errorText = await response.text();
|
||||||
|
throw new Error(`Error Ollama: ${response.status} - ${errorText}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
const data = await response.json();
|
||||||
|
|
||||||
|
if (!data.response) {
|
||||||
|
throw new Error("La respuesta de Ollama no contiene el campo 'response'");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Guardar en caché
|
||||||
|
cache.set(cacheKey, data.response);
|
||||||
|
|
||||||
|
return data.response;
|
||||||
|
} catch (error) {
|
||||||
|
if (error.name === 'AbortError') {
|
||||||
|
throw new Error(`⏱️ Timeout: La petición tardó más de ${TIMEOUT/1000} segundos. Intenta con un prompt más corto o aumenta TIMEOUT en .env`);
|
||||||
|
}
|
||||||
|
if (error.code === 'ECONNREFUSED') {
|
||||||
|
throw new Error("🔌 No se puede conectar a Ollama. Asegúrate de que está ejecutándose con 'ollama serve'");
|
||||||
|
}
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = { callLLM };
|
||||||
|
|
@ -0,0 +1,22 @@
|
||||||
|
// logger.js
|
||||||
|
const fs = require('fs');
|
||||||
|
const path = require('path');
|
||||||
|
|
||||||
|
const LOG_FILE = path.join(__dirname, 'logs.jsonl');
|
||||||
|
|
||||||
|
function log(entry) {
|
||||||
|
const logEntry = {
|
||||||
|
timestamp: new Date().toISOString(),
|
||||||
|
...entry
|
||||||
|
};
|
||||||
|
|
||||||
|
const logLine = JSON.stringify(logEntry) + '\n';
|
||||||
|
|
||||||
|
try {
|
||||||
|
fs.appendFileSync(LOG_FILE, logLine, 'utf8');
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error escribiendo en logs:", error.message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = { log };
|
||||||
|
|
@ -0,0 +1,6 @@
|
||||||
|
{"timestamp":"2026-01-26T18:24:51.723Z","opcion":"respuesta_guiada","error":"⏱️ Timeout: La petición tardó demasiado","solicitud":"Dime que sabes sobre javascript"}
|
||||||
|
{"timestamp":"2026-01-26T18:28:08.737Z","opcion":"respuesta_guiada","proveedor":"ollama","modelo":"openchat","solicitud":"Dime que es javascript","parametros":{"max_tokens":1000},"respuesta_length":2034,"duracion_ms":61085}
|
||||||
|
{"timestamp":"2026-01-26T18:31:54.756Z","opcion":"checklist_tecnico","proveedor":"ollama","modelo":"openchat","tema":"Hardening SSH Debian","prompt_length":390,"parametros":{"max_tokens":1500},"respuesta_length":3428,"duracion_ms":96414}
|
||||||
|
{"timestamp":"2026-01-26T18:34:25.287Z","opcion":"transformacion_itil","proveedor":"ollama","modelo":"openchat","texto_original_length":27,"parametros":{"max_tokens":1200},"respuesta_length":1356,"duracion_ms":38090}
|
||||||
|
{"timestamp":"2026-01-26T18:42:05.237Z","opcion":"checklist_tecnico","proveedor":"ollama","modelo":"openchat","tema":"Que es el Kernel?","prompt_length":384,"parametros":{"max_tokens":1500},"respuesta_length":3499,"duracion_ms":104562}
|
||||||
|
{"timestamp":"2026-01-26T18:46:59.464Z","opcion":"respuesta_guiada","proveedor":"ollama","modelo":"openchat","solicitud":"Que necesita una VPN Para funcionar?","parametros":{"max_tokens":1000},"respuesta_length":1340,"duracion_ms":43898}
|
||||||
|
|
@ -0,0 +1,262 @@
|
||||||
|
// main.js
|
||||||
|
const readline = require('readline');
|
||||||
|
const { callLLM } = require('./ai_client');
|
||||||
|
const { log } = require('./logger');
|
||||||
|
require('dotenv').config();
|
||||||
|
|
||||||
|
const rl = readline.createInterface({
|
||||||
|
input: process.stdin,
|
||||||
|
output: process.stdout
|
||||||
|
});
|
||||||
|
|
||||||
|
function question(prompt) {
|
||||||
|
return new Promise(resolve => {
|
||||||
|
rl.question(prompt, resolve);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function mostrarMenu() {
|
||||||
|
console.log("\n" + "=".repeat(60));
|
||||||
|
console.log("🤖 ASISTENTE IA - GESTIÓN TÉCNICA IT");
|
||||||
|
console.log("=".repeat(60));
|
||||||
|
console.log("1. Checklist técnico (salida estructurada)");
|
||||||
|
console.log("2. Transformación a plantilla ITIL");
|
||||||
|
console.log("3. Respuesta guiada (preguntas + respuesta final)");
|
||||||
|
console.log("4. Salir");
|
||||||
|
console.log("=".repeat(60));
|
||||||
|
}
|
||||||
|
|
||||||
|
async function checklistTecnico() {
|
||||||
|
console.log("\n📋 CHECKLIST TÉCNICO");
|
||||||
|
console.log("-".repeat(60));
|
||||||
|
|
||||||
|
const tema = await question("Introduce el tema técnico (ej: hardening SSH Debian): ");
|
||||||
|
|
||||||
|
const prompt = `Genera un checklist técnico detallado sobre "${tema}" en formato Markdown.
|
||||||
|
|
||||||
|
Estructura requerida:
|
||||||
|
# Checklist: ${tema}
|
||||||
|
|
||||||
|
## Items
|
||||||
|
Para cada item incluye:
|
||||||
|
- [ ] **Nombre del item**: Breve descripción
|
||||||
|
- **Justificación**: Por qué es importante
|
||||||
|
- **Riesgo si no se aplica**: Consecuencias de omitirlo
|
||||||
|
|
||||||
|
Genera entre 5 y 8 items relevantes. Sé específico y práctico.`;
|
||||||
|
|
||||||
|
console.log("\n⏳ Generando checklist...\n");
|
||||||
|
|
||||||
|
const startTime = Date.now();
|
||||||
|
try {
|
||||||
|
const respuesta = await callLLM(prompt, 1500);
|
||||||
|
const duration = Date.now() - startTime;
|
||||||
|
|
||||||
|
console.log(respuesta);
|
||||||
|
|
||||||
|
// Logging
|
||||||
|
log({
|
||||||
|
opcion: "checklist_tecnico",
|
||||||
|
proveedor: "ollama",
|
||||||
|
modelo: process.env.MODEL || "openchat",
|
||||||
|
tema: tema,
|
||||||
|
prompt_length: prompt.length,
|
||||||
|
parametros: { max_tokens: 1500 },
|
||||||
|
respuesta_length: respuesta.length,
|
||||||
|
duracion_ms: duration
|
||||||
|
});
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
console.error("\n❌ Error:", error.message);
|
||||||
|
log({
|
||||||
|
opcion: "checklist_tecnico",
|
||||||
|
error: error.message,
|
||||||
|
tema: tema
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function transformacionITIL() {
|
||||||
|
console.log("\n📄 TRANSFORMACIÓN A PLANTILLA ITIL");
|
||||||
|
console.log("-".repeat(60));
|
||||||
|
|
||||||
|
console.log("Introduce el texto de la incidencia (escribe 'FIN' en una línea aparte para terminar):");
|
||||||
|
|
||||||
|
let textoIncidencia = "";
|
||||||
|
let linea;
|
||||||
|
while ((linea = await question("")) !== "FIN") {
|
||||||
|
textoIncidencia += linea + "\n";
|
||||||
|
}
|
||||||
|
|
||||||
|
const prompt = `Transforma el siguiente texto de incidencia en una plantilla ITIL estructurada:
|
||||||
|
|
||||||
|
TEXTO ORIGINAL:
|
||||||
|
${textoIncidencia}
|
||||||
|
|
||||||
|
PLANTILLA ITIL REQUERIDA:
|
||||||
|
|
||||||
|
## Resumen
|
||||||
|
[Descripción breve y clara del problema]
|
||||||
|
|
||||||
|
## Impacto
|
||||||
|
[Alto/Medio/Bajo - Afectación al negocio]
|
||||||
|
|
||||||
|
## Urgencia
|
||||||
|
[Alta/Media/Baja - Tiempo crítico de resolución]
|
||||||
|
|
||||||
|
## Acciones Realizadas
|
||||||
|
- [Lista de acciones ya tomadas]
|
||||||
|
|
||||||
|
## Evidencias
|
||||||
|
- [Logs, capturas, datos técnicos relevantes]
|
||||||
|
|
||||||
|
## Diagnóstico Preliminar
|
||||||
|
[Análisis de la causa probable]
|
||||||
|
|
||||||
|
## Próximos Pasos
|
||||||
|
1. [Primer paso]
|
||||||
|
2. [Segundo paso]
|
||||||
|
3. [Tercer paso]
|
||||||
|
|
||||||
|
Completa la plantilla de forma profesional y técnica.`;
|
||||||
|
|
||||||
|
console.log("\n⏳ Transformando a formato ITIL...\n");
|
||||||
|
|
||||||
|
const startTime = Date.now();
|
||||||
|
try {
|
||||||
|
const respuesta = await callLLM(prompt, 1200);
|
||||||
|
const duration = Date.now() - startTime;
|
||||||
|
|
||||||
|
console.log(respuesta);
|
||||||
|
|
||||||
|
// Logging
|
||||||
|
log({
|
||||||
|
opcion: "transformacion_itil",
|
||||||
|
proveedor: "ollama",
|
||||||
|
modelo: process.env.MODEL || "openchat",
|
||||||
|
texto_original_length: textoIncidencia.length,
|
||||||
|
parametros: { max_tokens: 1200 },
|
||||||
|
respuesta_length: respuesta.length,
|
||||||
|
duracion_ms: duration
|
||||||
|
});
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
console.error("\n❌ Error:", error.message);
|
||||||
|
log({
|
||||||
|
opcion: "transformacion_itil",
|
||||||
|
error: error.message
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function respuestaGuiada() {
|
||||||
|
console.log("\n❓ RESPUESTA GUIADA");
|
||||||
|
console.log("-".repeat(60));
|
||||||
|
|
||||||
|
const solicitud = await question("Describe el problema (ej: no funciona la VPN): ");
|
||||||
|
|
||||||
|
const prompt = `Un usuario reporta: "${solicitud}"
|
||||||
|
|
||||||
|
Como técnico IT experto, necesitas:
|
||||||
|
|
||||||
|
1. **PREGUNTAS DE ACLARACIÓN** (3-5 preguntas específicas):
|
||||||
|
- Pregunta 1: [pregunta técnica específica]
|
||||||
|
- Pregunta 2: [pregunta sobre contexto]
|
||||||
|
- Pregunta 3: [pregunta sobre síntomas]
|
||||||
|
- [2 preguntas más si es necesario]
|
||||||
|
|
||||||
|
2. **HIPÓTESIS INICIAL**:
|
||||||
|
Basándote en la información limitada, cuál es la causa más probable.
|
||||||
|
|
||||||
|
3. **PLAN DE DIAGNÓSTICO** (pasos numerados):
|
||||||
|
1. [Primer paso de verificación]
|
||||||
|
2. [Segundo paso]
|
||||||
|
3. [Tercer paso]
|
||||||
|
4. [Cuarto paso]
|
||||||
|
5. [Quinto paso si es necesario]
|
||||||
|
|
||||||
|
Proporciona una respuesta estructurada y profesional.`;
|
||||||
|
|
||||||
|
console.log("\n⏳ Analizando y generando preguntas...\n");
|
||||||
|
|
||||||
|
const startTime = Date.now();
|
||||||
|
try {
|
||||||
|
const respuesta = await callLLM(prompt, 1000);
|
||||||
|
const duration = Date.now() - startTime;
|
||||||
|
|
||||||
|
console.log(respuesta);
|
||||||
|
|
||||||
|
// Logging
|
||||||
|
log({
|
||||||
|
opcion: "respuesta_guiada",
|
||||||
|
proveedor: "ollama",
|
||||||
|
modelo: process.env.MODEL || "openchat",
|
||||||
|
solicitud: solicitud,
|
||||||
|
parametros: { max_tokens: 1000 },
|
||||||
|
respuesta_length: respuesta.length,
|
||||||
|
duracion_ms: duration
|
||||||
|
});
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
console.error("\n❌ Error:", error.message);
|
||||||
|
log({
|
||||||
|
opcion: "respuesta_guiada",
|
||||||
|
error: error.message,
|
||||||
|
solicitud: solicitud
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function main() {
|
||||||
|
// Verificar variables de entorno
|
||||||
|
if (!process.env.OLLAMA_URL) {
|
||||||
|
console.log("⚠️ Usando URL por defecto de Ollama: http://localhost:11434/api/generate");
|
||||||
|
}
|
||||||
|
|
||||||
|
let continuar = true;
|
||||||
|
|
||||||
|
while (continuar) {
|
||||||
|
mostrarMenu();
|
||||||
|
const opcion = await question("\nSelecciona una opción (1-4): ");
|
||||||
|
|
||||||
|
switch (opcion.trim()) {
|
||||||
|
case "1":
|
||||||
|
await checklistTecnico();
|
||||||
|
break;
|
||||||
|
case "2":
|
||||||
|
await transformacionITIL();
|
||||||
|
break;
|
||||||
|
case "3":
|
||||||
|
await respuestaGuiada();
|
||||||
|
break;
|
||||||
|
case "4":
|
||||||
|
console.log("\n👋 ¡Hasta pronto!");
|
||||||
|
continuar = false;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
console.log("\n❌ Opción no válida. Por favor, selecciona 1-4.");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (continuar) {
|
||||||
|
await question("\nPresiona ENTER para continuar...");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
rl.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Manejo de errores global
|
||||||
|
process.on('unhandledRejection', (error) => {
|
||||||
|
console.error('\n❌ Error no manejado:', error.message);
|
||||||
|
log({
|
||||||
|
tipo: "error_no_manejado",
|
||||||
|
error: error.message,
|
||||||
|
stack: error.stack
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// Ejecutar
|
||||||
|
main().catch(error => {
|
||||||
|
console.error("❌ Error fatal:", error.message);
|
||||||
|
process.exit(1);
|
||||||
|
});
|
||||||
Loading…
Reference in New Issue