El reto
El planteamiento era construir un agente de IA en Python en cuatro niveles: chat simple contra un LLM, conversación con memoria, uso de herramientas y un proyecto completo sobre un caso real.
Me puse una condición extra: nada de frameworks de agentes. Quería entender cada pieza (el bucle de herramientas, la memoria, el enrutado entre proveedores) antes de dejar que una librería me la escondiera.
Cómo lo construí, nivel a nivel
- Básico: chat en bucle, clave en
.env, errores manejados y logging. Primera lección: los catálogos de modelos que citan las IA están caducados, así que siempre verifico contra la documentación oficial. - Intermedio: memoria completa, system prompt configurable,
/resumeny persistencia en JSON con índice SQLite. Comprobé con un experimento sobre tres modelos que el system prompt cambia el comportamiento, no solo el estilo. - Avanzado: tool calling real en Groq, Ollama y Gemini, con el JSON Schema de cada herramienta derivado de Pydantic. Aquí encontré un bug real: Gemini puede devolver la llamada a función en cualquier parte de la respuesta, no siempre en la primera.
- Experto: antes de exponerlo a Telegram lo audité a propósito y lo endurecí. Unifiqué el bucle de herramientas (estaba triplicado), añadí errores tipados, timeouts y un router de failover. Después vinieron el bot, el cifrado y la memoria semántica.
Qué hace hoy
- Chat por terminal y bot de Telegram con memoria compartida: lo que hablo en uno lo recuerda el otro.
- Failover automático entre Groq, Ollama y Gemini: si el proveedor principal falla por límite de peticiones, timeout o red, reintenta con espera creciente y pasa solo al siguiente.
- Memoria semántica local con ChromaDB y embeddings locales, y conversaciones cifradas en reposo con Fernet.
- 7 herramientas en 6 categorías, que se encienden y apagan con
/tools: calculadora, tiempo (Open-Meteo), lectura de PDF y Word, resumen de páginas web con guardián anti-SSRF, foto astronómica de la NASA… /condensarresume documentos largos por bloques, y/noticiaslee ocho medios en inglés y escribe un informe en español (118 candidatas → 10 noticias, 2 llamadas al modelo, 73 segundos).
Canales
Lo que hablo en uno lo recuerda el otro.
Calidad: más código de test que código
| Métrica | Valor |
|---|---|
| Código fuente | 11.205 líneas en 18 módulos |
| Tests | 831 tests, 12.505 líneas (1,12 líneas de test por cada línea de código) |
| Cobertura de rama | 79,86 %, con umbral en CI |
| Mutación | 167 mutantes sobre los módulos críticos; uno vivo destapó un caso sin test en el guardián anti-SSRF, y lo cerré |
| CI | GitHub Actions: lint, tests, cobertura y pip-audit sobre el lockfile |
| Dependencias | 110 paquetes fijados con hash SHA-256 |
| Auditorías de seguridad | 3, más una autoevaluación contra 4 familias de normas ISO |
Documentado como un proyecto profesional
Requisitos, arquitectura, estado del arte y entrega final; 6 ADR con sus alternativas descartadas, y un diario con 181 decisiones en 25 días. La entrega final incluye un veredicto honesto: es seguro frente a terceros, privado a medias por diseño (el texto viaja al proveedor del LLM) y no confidencial (la clave de cifrado vive junto a los datos). Decirlo con precisión vale más que prometer una seguridad que no se sostiene.
Stack desglosado
- Lenguaje: Python, sin frameworks de agentes.
- Modelos: Groq, Ollama y Gemini con router de failover.
- Memoria: JSON + SQLite, ChromaDB embebido y cifrado Fernet.
- Canal: CLI con menús y
python-telegram-bot. - Validación: Pydantic para los argumentos de las herramientas.
- Calidad: pytest, ruff, cobertura de rama, mutación y
pip-auditen GitHub Actions.
Resultados y aprendizajes
Lo más valioso: endurecer antes de exponer. La fase de endurecimiento no estaba planificada; salió de una auditoría que hice a propósito antes de conectar el bot a Telegram, y me ahorró llevar fallos a producción.
Lo que me llevo: construir sin frameworks me dio el criterio para usarlos después. Hoy trabajo con LangGraph sabiendo exactamente qué hace por mí, y este proyecto fue la base de mi asistente de Telegram.