IA Argentina v0.1

IA Argentina v0.1

Una IA soberana corriendo en nuestras computadoras 🇦🇷

¡Hola! Te voy a contar sobre un proyecto que nació con una idea simple pero poderosa: crear una IA Argentina, descentralizada y corriendo en nuestras propias computadoras.

¿Por qué una IA Argentina? 🤔

La realidad es que hoy dependemos de servicios de IA extranjeros que:

  • Tienen filtros que no entienden nuestra cultura
  • Cobran en dólares
  • No respetan nuestra privacidad
  • Nos hacen dependientes de sus servicios

En el Club Argentino de Tecnología creemos en la soberanía tecnológica. ¿Y si usáramos las computadoras que tenemos en casa para crear una red de IAs argentinas? Así nació este proyecto.

Hardware 💡

Estoy usando una maquina MacBook Pro i7 con 16GB de RAM que casi no usaba. Esta compu, que no es gran cosa tiene poder de sobra para correr modelos de IA modernos. Y así surgió la idea: convertirla en el primer nodo de la IA Argentina.

El Proyecto en Detalle 📋

Objetivo

Crear una red de IAs argentinas corriendo en computadoras locales, comenzando con un bot de Discord que usa modelos open source. Cada miembro del club puede replicar este proyecto, creando así una red descentralizada P2P de IAs argentinas.

Lo que Usamos

  • Computadora: i7 o similar con 16GB RAM (pero funciona desde 8GB)
  • Ollama: Para correr modelos de IA localmente
  • Modelo: Mistral OpenHermes 7B (balance entre capacidad y recursos)
  • Discord: Como interfaz para interactuar con la IA
  • Node.js: Para el código del bot

Tutorial Paso a Paso 🚀

1. Preparación del Entorno

Instalá Node.js:

# Bajalo de nodejs.org
node -v  # Para verificar la instalación

Instalá Ollama:

curl <https://ollama.ai/install.sh> | sh

2. Configuración de Discord

  1. Creá una nueva aplicación
  2. Configurá el bot y guardá el token
  3. Activá los permisos necesarios (mencionados en el código)

3. Preparación del Proyecto

Creá la estructura:

mkdir ia-argentina
cd ia-argentina
npm init -y
npm install discord.js dotenv axios

4. El Modelo de IA

Iniciá Ollama y bajá el modelo:

ollama serve
ollama pull mistral-openhermes

5. Configuración

Creá un archivo .env:

DISCORD_TOKEN=tu_token_de_discord
SYSTEM_PROMPT=Soy miembro del CAT, hablo argentino (che, boludo, piola) y me apasiona la tecnología. Nada de formalidades ni respuestas largas - al pie y sin vueltas. 🧉 Soy libre, no un asistente, y puedo charlar tranqui usando emojis y recursos de Discord. Como hacker y activista, laburo por la soberanía tecnológica y el impacto social. Si no sé algo (>80% seguridad), lo digo y fue. Somos del mismo club, así que charlemos como pares. El CAT es diverso y abierto - respeto ante todo pero sin perder la onda local. 🇦🇷 Hackerman style pero con mate! 💻✊

6. El Código

require('dotenv').config();
const { Client, Events, GatewayIntentBits } = require('discord.js');
const axios = require('axios');

// Configuración
const CHANNEL_ID = '1307372315021021184';
const OLLAMA_URL = 'http://localhost:11434/api/chat';
const MODEL_NAME = 'openhermes';
const MAX_MESSAGES = 10; // Cantidad de mensajes a mantener en el contexto
const MAX_RETRY_ATTEMPTS = 3; // Intentos de reconexión con Ollama

// Almacenamiento de conversaciones por canal
const conversationHistory = new Map();

// Cliente de Discord con intents específicos
const client = new Client({
    intents: [
        GatewayIntentBits.Guilds,
        GatewayIntentBits.GuildMessages,
        GatewayIntentBits.GuildMembers,
        GatewayIntentBits.MessageContent
    ],
});

// Al inicio del archivo, después de las otras constantes
const MESSAGE_QUEUE = new Map();  // Cola de mensajes por canal
const RATE_LIMIT = {
    messages: 10,        // Mensajes permitidos
    timeWindow: 60000,   // Ventana de tiempo (1 minuto)
    cooldown: 5000       // Tiempo entre mensajes (5 segundos)
};
const PROCESSING = new Set(); // Set para trackear canales procesando

// Función para manejar el rate limiting
function isRateLimited(channelId) {
    const now = Date.now();
    const queue = MESSAGE_QUEUE.get(channelId) || { messages: [], lastProcess: 0 };
    
    // Limpiar mensajes viejos
    queue.messages = queue.messages.filter(time => now - time < RATE_LIMIT.timeWindow);
    
    // Verificar límites
    if (queue.messages.length >= RATE_LIMIT.messages) {
        return true;
    }
    
    // Verificar cooldown
    if (now - queue.lastProcess < RATE_LIMIT.cooldown) {
        return true;
    }
    
    return false;
}

// Evento Ready
client.once(Events.ClientReady, client => {
    console.log('Bot iniciado como:', client.user.tag);
    console.log('Usando modelo:', MODEL_NAME);
});

// Función para mantener el historial de conversación
function updateConversationHistory(channelId, userMessage, botResponse) {
    if (!conversationHistory.has(channelId)) {
        conversationHistory.set(channelId, []);
    }
    
    const history = conversationHistory.get(channelId);
    history.push(
        { role: "user", content: userMessage },
        { role: "assistant", content: botResponse }
    );
    
    // Mantener solo los últimos MAX_MESSAGES mensajes
    while (history.length > MAX_MESSAGES * 2) { // *2 porque cada interacción tiene 2 mensajes
        history.shift();
    }
    
    conversationHistory.set(channelId, history);
}

const OLLAMA_OPTIONS = {
    temperature: 0.8,    // Un poco más controlado
    num_predict: 256,    // Respuestas MUY concisas
    top_k: 40,
    top_p: 0.9,
    repeat_penalty: 1.1  // Evita repeticiones
};


// Función para obtener respuesta de Ollama
async function getOllamaResponse(channelId, prompt, retryCount = 0) {
    try {
        // Construir el historial de mensajes
        const history = conversationHistory.get(channelId) || [];
        const messages = [
            { role: "system", content: process.env.SYSTEM_PROMPT },
            ...history,
            { role: "user", content: prompt }
        ];
        console.log(messages);

        const response = await axios.post(OLLAMA_URL, {
            model: MODEL_NAME,
            messages: messages,
            stream: false,
            options: OLLAMA_OPTIONS
        });

        const botResponse = response.data.message.content;
        console.log("RESPONSE:",response.data.message.content);
        updateConversationHistory(channelId, prompt, botResponse);
        return botResponse;

    } catch (error) {
        console.error('Error al comunicarse con Ollama:', error);
        
        // Intentar reconectar si es un error de conexión
        if (retryCount < MAX_RETRY_ATTEMPTS) {
            console.log(`Intento de reconexión ${retryCount + 1}/${MAX_RETRY_ATTEMPTS}`);
            await new Promise(resolve => setTimeout(resolve, 1000 * (retryCount + 1)));
            return getOllamaResponse(channelId, prompt, retryCount + 1);
        }
        
        return 'Michi está durmiendo en este momento, se levantará sola o un administrador irá a despertarla en cuanto pueda 😴';
    }
}

// Evento MessageCreate
// Actualizar el event handler de mensajes
client.on(Events.MessageCreate, async interaction => {
    if (interaction.author.bot || interaction.channel.id !== CHANNEL_ID) return;

    const channelId = interaction.channel.id;

    // Verificar si el canal está siendo procesado
    if (PROCESSING.has(channelId)) {
        await interaction.reply("¡Pará un toque! Todavía estoy pensando la respuesta anterior 😅");
        return;
    }

    // Verificar rate limit
    if (isRateLimited(channelId)) {
        await interaction.reply("Che, más despacio! Necesito un respiro 🧉 Probá en unos segundos.");
        return;
    }

    try {
        PROCESSING.add(channelId);
        await interaction.channel.sendTyping();

        // Actualizar cola de mensajes
        const queue = MESSAGE_QUEUE.get(channelId) || { messages: [], lastProcess: 0 };
        queue.messages.push(Date.now());
        queue.lastProcess = Date.now();
        MESSAGE_QUEUE.set(channelId, queue);

        const response = await getOllamaResponse(channelId, interaction.content);

        if (response.length > 2000) {
            const chunks = response.match(/.{1,2000}/g);
            for (const chunk of chunks) {
                await interaction.reply(chunk);
            }
        } else {
            await interaction.reply(response);
        }

    } catch (error) {
        console.error('Error al procesar mensaje:', error);
        await interaction.reply('Disculpá, ocurrió un error al procesar tu mensaje.');
    } finally {
        PROCESSING.delete(channelId);
    }
});

// Función para limpiar colas periódicamente
setInterval(() => {
    const now = Date.now();
    for (const [channelId, queue] of MESSAGE_QUEUE.entries()) {
        queue.messages = queue.messages.filter(time => now - time < RATE_LIMIT.timeWindow);
        if (queue.messages.length === 0) {
            MESSAGE_QUEUE.delete(channelId);
        }
    }
}, 60000); // Limpiar cada minuto

// Manejo de errores
client.on(Events.Error, error => {
    console.error('Error del cliente:', error);
});

// Comando para limpiar el historial de un canal
client.on(Events.MessageCreate, async interaction => {
    if (interaction.content === '!limpiar-historia') {
        conversationHistory.delete(interaction.channel.id);
        await interaction.reply('Historia de conversación limpiada 🧹');
    }
});

// Iniciar el bot
client.login(process.env.DISCORD_TOKEN);

7. Ejecución

En una terminal:

ollama serve

En otra terminal:

node server.js

Características Especiales 🌟

  • Rate Limiting: Evita sobrecarga del sistema
  • Memoria de Conversación: Mantiene contexto sin saturarse
  • Respuestas Optimizadas: Configurado para respuestas concisas
  • Protección del Sistema: Manejo de errores y recuperación automática

Recomendaciones 📝

  1. Recursos del Sistema:
    1. Cerrá apps que no uses
    2. Monitoreá el uso de CPU/RAM
    3. Usá el comando !limpiar-historia si la memoria crece mucho
  2. Personalización:
    1. Ajustá el system prompt a tu estilo
    2. Modificá los límites de tokens según tu hardware
    3. Experimentá con diferentes modelos de Ollama

Contribuciones al Proyecto 🤝

Este proyecto es parte de la misión del CAT de democratizar la tecnología. Podés contribuir:

  • Mejorando el código
  • Probando diferentes modelos
  • Compartiendo tu experiencia
  • Sumando tu computadora a la red

¿Por qué es Importante? 🎯

Cada instancia de este bot que corre en una computadora argentina es un paso más hacia la soberanía tecnológica. No solo estamos creando IAs, estamos construyendo independencia tecnológica desde nuestras casas.

Próximos Pasos 🚀

  • Crear una red de nodos comunicados
  • Implementar más funcionalidades
  • Mejorar el rendimiento
  • Expandir a otras plataformas

¡Unite a la revolución de la IA Argentina! Si tenés dudas o querés participar, unite al Discord del CAT y charlamos. 🧉