Qué es Gymnasia
Gymnasia es una aplicación móvil de entrenamiento personal hecha con React Native y Expo. Dentro lleva dos agentes de IA: un coach conversacional y un subagente de visión que estima calorías y macronutrientes a partir de fotos de comida.
Lo interesante, y de lo que va este post, es que los dos se ejecutan enteros en el dispositivo. No hay backend propio: la orquestación, las herramientas y el almacenamiento viven en App.tsx y en AsyncStorage, y las claves de API las pone el usuario. Los datos del usuario nunca salen del móvil salvo por una única excepción que se explica más abajo.
Este es el post principal de la serie. Debajo están los dos grafos de arquitectura, y al final el índice de la serie con los posts que van saliendo sobre cada parte del desarrollo.
El agente general: Gymnasia Coach
Desliza el grafo en horizontal para verlo entero.
Gymnasia Coach es el agente conversacional general de la app, el de la pestaña Chat IA. Es un agente BYOK (Bring Your Own Key): el usuario configura su propia clave de API de OpenAI, Anthropic o Google, y la app llama directamente al proveedor elegido desde el dispositivo. No existe backend propio: toda la lógica de orquestación, las herramientas y el almacenamiento viven en App.tsx y en AsyncStorage local.
Configuración y proveedor activo
El usuario guarda su API key
Una de las 3 claves (OpenAI / Anthropic / Google) se marca como is_active. Solo esa se usa para el chat general.
prompts/AGENTS.md en GitHub
Se descarga desde raw.githubusercontent.com/maximofn/gymnasia, se cachea localmente y cae a un prompt por defecto embebido si falla la red.
Llamada al proveedor
Cada proveedor tiene su propio adaptador de request/response, porque los formatos de tools, de streaming y de bloques de contenido son distintos en los tres. Pero comparten el mismo bucle agentic que viene ahora.
Bucle agentic (tool use)
Modelo responde
Texto en streaming y/o bloques tool_use / function_call.
handleToolCall()
Se ejecuta 100% en local contra el estado de la app (AsyncStorage, repos JSON).
Resultado → modelo
Se reinyecta como tool_result / function_call_output. Se repite hasta que no pide más herramientas.
Las 12 herramientas
Almacenamiento y salida
LocalStore (AsyncStorage)
Dieta, rutinas, medidas y datos personales viven en el dispositivo. Los repos de alimentos y ejercicios son JSON estáticos empaquetados con la app.
GitHub Issues API
Único punto de salida a un servicio externo distinto del proveedor LLM: create_feature_issue.
apps/anthropic_proxy/cors-proxy.py) porque el navegador bloquea la llamada directa a api.anthropic.com. OpenAI y Google funcionan directo desde el navegador.El subagente de visión: Food Estimator
Desliza el grafo en horizontal para verlo entero.
El Food Estimator es el subagente de visión que estima calorías y macros a partir de fotos de comida (pestaña Dieta → Estimación IA). Es independiente del chat general: tiene su propio system prompt, su propia herramienta y su propia política de selección de proveedor.
Entrada
1–6 fotos de la comida
Cámara o galería. También admite texto (preguntas de seguimiento sobre la estimación) reutilizando el contexto de la conversación.
Selección de proveedor por prioridad
A diferencia del chat general, aquí no se usa el proveedor activo: se prueba en orden hasta encontrar el primero con clave de API configurada.
System prompt especializado
Nutricionista visual
Estima siempre kcal, proteína (g), carbohidratos (g), grasa (g) y peso total (g). Da rangos si hay incertidumbre.
Clasificación
Determina si es producto_comercial, receta o alimento base genérico.
Salida estructurada
Si el usuario pide "Devuelve json", responde solo con JSON: dish_name, calories_kcal, protein_g, carbs_g, fat_g.
Bucle agentic con el código de barras
¿Hay un código de barras en la foto?
El prompt obliga al modelo a usar la herramienta si detecta un EAN/UPC en cualquiera de las imágenes.
scan_barcode(barcode)
Llama a OpenFoodFacts (API pública) con el código leído y devuelve datos nutricionales exactos del producto.
Producto comercial confirmado
Si se usó scan_barcode, la clasificación es siempre producto_comercial, con datos exactos en vez de estimados.
Igual que en el agente general, el resultado de la herramienta se reinyecta al modelo y el bucle se repite, con un máximo de 5 rondas, hasta obtener una respuesta final.
Persistencia del resultado
Confirmación del usuario
Cuando el usuario acepta la estimación, se pide el bloque JSON final y se parsea.
add_meal_food
Se añade a la dieta local del día y comida seleccionados, en el mismo store que usa el agente general.
Variante: estimación manual
El usuario describe un alimento por texto
Flujo conversacional: el usuario nombra el alimento, el modelo pregunta ingredientes y cantidades si faltan, calcula valores por 100 g o unidad, el usuario confirma y devuelve el JSON para guardar en el repo de alimentos.
La serie
Este post es la portada. Cada parte del desarrollo del agente se cuenta en un post propio, y todos se van enlazando aquí.
- Todavía no hay posts publicados en la serie. Los primeros serán el de planificación y el de tool calling.
Mientras tanto, la ficha del proyecto está en maximofn.com/gymnasia y el código es abierto, en GitHub.