Cómo construir un chatbot con IA usando Go y React

Francisco Parra
15 min de lectura
#Go#React#IA#Chatbot#RAG#OpenRouter

Cómo construir un chatbot con IA usando Go y React

En este artículo te mostraré cómo construí un chatbot inteligente para la Universidad de Santiago de Chile que maneja 1,770 preguntas frecuentes usando Go, React y OpenRouter API.

El Desafío

La Universidad necesitaba un sistema que pudiera:

  • Responder preguntas sobre admisión, carreras y becas
  • Manejar más de 1,700 FAQs de manera eficiente
  • Proporcionar información actualizada sobre 71 carreras
  • Mantener contexto conversacional entre mensajes
  • Funcionar en español con búsqueda semántica precisa

Arquitectura del Sistema

┌─────────────┐      REST API       ┌─────────────┐    OpenRouter API   ┌─────────────┐
│   Frontend  │ ◄─────────────────► │   Backend   │ ◄─────────────────► │     GPT     │
│  React 19   │                      │   Go + Gin  │                     │   Models    │
└─────────────┘                      └─────────────┘                     └─────────────┘
                                             │
                                             │ MongoDB Driver
                                             ▼
                                     ┌─────────────┐
                                     │   MongoDB   │
                                     │  FAQs + DB  │
                                     └─────────────┘

Stack Tecnológico

Backend (Go)

  • Gin Framework: Web framework ligero y rápido
  • MongoDB Driver: Para almacenar FAQs y sesiones
  • Snowball Stemmer: Normalización de palabras en español
  • OpenRouter Client: Acceso a múltiples modelos LLM

Frontend (React)

  • React 19: Con las últimas características
  • Vite 7: Build tool ultra-rápido
  • Tailwind CSS 4: Styling moderno
  • React Markdown: Renderizado de respuestas

Implementación del Sistema RAG

El corazón del sistema es el Retrieval-Augmented Generation (RAG). Aquí está cómo funciona:

1. Indexación de FAQs con Stemming

func processFAQ(faq *FAQ) {
    // Tokenizar la pregunta
    words := strings.Fields(strings.ToLower(faq.Question))

    // Aplicar stemming Snowball
    stemmed := make([]string, 0, len(words))
    for _, word := range words {
        stem := snowball.Stem(word, "spanish", true)
        stemmed = append(stemmed, stem)
    }

    faq.KeywordsStemmed = stemmed
}

2. Búsqueda Semántica

func searchFAQs(query string, db *MongoDB) []FAQ {
    // Procesar query con stemming
    queryStems := processQuery(query)

    // Buscar en MongoDB
    filter := bson.M{
        "keywords_stemmed": bson.M{
            "$in": queryStems,
        },
    }

    cursor, _ := db.Collection("faqs").Find(ctx, filter)
    // Calcular scores y rankear resultados
    return rankedFAQs
}

3. Generación con OpenRouter

func generateResponse(query string, faqs []FAQ) (string, error) {
    // Construir contexto con FAQs relevantes
    context := buildContext(faqs)

    // Llamar a OpenRouter
    request := OpenRouterRequest{
        Model: "openai/gpt-5-nano",
        Messages: []Message{
            {Role: "system", Content: systemPrompt},
            {Role: "user", Content: query},
        },
        Context: context,
    }

    response, err := client.Post("/chat/completions", request)
    return response.Choices[0].Message.Content, err
}

Frontend con React

Hook Personalizado para el Chat

function useChat() {
    const [messages, setMessages] = useState([]);
    const [isLoading, setIsLoading] = useState(false);

    const sendMessage = async (query: string) => {
        setIsLoading(true);

        try {
            const response = await fetch('/api/chat', {
                method: 'POST',
                headers: { 'Content-Type': 'application/json' },
                body: JSON.stringify({ query })
            });

            const data = await response.json();
            setMessages(prev => [...prev, data]);
        } finally {
            setIsLoading(false);
        }
    };

    return { messages, sendMessage, isLoading };
}

Efecto Typewriter

function useTyp ingEffect(text: string, speed: number = 4) {
    const [displayedText, setDisplayedText] = useState('');

    useEffect(() => {
        let index = 0;
        const interval = setInterval(() => {
            if (index < text.length) {
                setDisplayedText(text.slice(0, index + 1));
                index++;
            } else {
                clearInterval(interval);
            }
        }, speed);

        return () => clearInterval(interval);
    }, [text, speed]);

    return displayedText;
}

Optimizaciones Clave

1. Caché de Sesiones

type SessionCache struct {
    sessions map[string]*ChatSession
    mu       sync.RWMutex
}

func (c *SessionCache) Get(id string) (*ChatSession, bool) {
    c.mu.RLock()
    defer c.mu.RUnlock()
    session, ok := c.sessions[id]
    return session, ok
}

2. Rate Limiting

limiter := rate.NewLimiter(rate.Limit(10), 20) // 10 req/s, burst 20

if !limiter.Allow() {
    c.JSON(429, gin.H{"error": "Too many requests"})
    return
}

3. Timeout Configurables

client := &http.Client{
    Timeout: 60 * time.Second, // Para modelos lentos
}

Resultados

Después de implementar el sistema:

  • 1,770 FAQs indexadas y buscables
  • 71 carreras con información detallada
  • Latencia < 2s para respuestas
  • 95% precisión en búsqueda semántica
  • Escalable a miles de usuarios concurrentes

Código Fuente

El código completo está disponible en GitHub: https://github.com/citiaps/proyecto-chatbot-admision-usach

Conclusiones

Construir un chatbot con IA no tiene por qué ser complejo. Con las herramientas adecuadas:

  1. Go proporciona performance y concurrencia
  2. React ofrece una UX moderna y fluida
  3. RAG mejora significativamente la calidad de las respuestas
  4. OpenRouter da acceso a múltiples modelos LLM

¿Tienes preguntas? ¡Escríbeme en los comentarios o contáctame directamente!


Sobre el autor: Francisco Parra es Doctor en Ingeniería Informática por la USACH, especializado en desarrollo web, data science y análisis geoespacial.