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

  • Andá a Discord Developer Portal
  • Creá una nueva aplicación
  • Configurá el bot y guardá el token
  • 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 📝

  • Recursos del Sistema:
  • Personalización:
  • 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. 🧉