Cómo construir un chatbot con IA usando Go y React
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:
- Go proporciona performance y concurrencia
- React ofrece una UX moderna y fluida
- RAG mejora significativamente la calidad de las respuestas
- 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.