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