Gymnasia: arquitectura de un agente de IA que corre en el móvil

Gymnasia: arquitectura de un agente de IA que corre en el móvil

Qué es Gymnasialink image

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 estimador de comidas que calcula calorías y macronutrientes a partir de fotos.

Van en paralelo, no uno dentro de otro: ninguno es subagente del otro, no se llaman entre ellos, viven en pantallas distintas y cada uno tiene su propio system prompt, sus propias tools y su propio proveedor de LLM. Compartir, comparten una sola cosa: los datos del usuario guardados en el móvil.

Lo interesante, y de lo que va este post, es que los dos se ejecutan enteros en el dispositivo. No hay backend propio: quien decide qué hacer, quien ejecuta las tools y quien guarda los datos es la propia app, dentro del teléfono. El modelo se llama directamente desde ahí con una clave de API que pone el usuario, lo que se suele llamar BYOK, Bring Your Own Key: cada usuario trae su propia clave, y con ella paga y controla su acceso al proveedor. 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 Coachlink image

Se lee de arriba abajo por la columna central: eso es lo que ocurre en el tiempo. La caja de la izquierda no son pasos: son las piezas que se suman unas a otras y entran juntas en cada llamada, de ahí la flecha ancha. La de la derecha es lo que pasa cuando el modelo pide una tool. La línea discontinua clara vuelve atrás: el resultado se añade al historial y se llama otra vez. La roja es lo único que sale del dispositivo. Y las cajas de borde discontinuo son la llamada al modelo: ahí manda el proveedor, no el código de la app.
LO QUE ENTRA EN CADA LLAMADA CUANDO EL MODELO PIDE UNA TOOL +++ no + tool_result vuelta n+1 (máx. 10)
System promptremoto, con caché
Catálogo de toolsqué puede hacer
Historiallo dicho hasta ahora
ProveedorBYOK, lo pone el usuario
Usuario (chat)escribe un mensaje
Petición al proveedortodo junto, en una sola llamada
Respuesta del modelotexto y/o petición de tool
¿Pide usar una tool?
Respuesta finalal usuario, en streaming
La app ejecuta la tooldentro del móvil
Datos localesdieta · rutinas · medidas · memoria
create_feature_issuela única tool que sale del móvil

Desliza el grafo en horizontal para verlo entero.

Paso siguiente Entra en la llamada Vuelve atrás (bucle) Sale del dispositivo Fuera de tu código

Gymnasia Coach es el agente conversacional general de la app. Es un agente BYOK: el usuario configura la clave de API del proveedor de LLM que prefiera, y la app llama a ese proveedor directamente desde el dispositivo. No existe backend propio: la orquestación, la ejecución de las tools y el almacenamiento ocurren dentro de la propia app.

Configuración y elección de proveedorlink image

Ajustes BYOK

El usuario guarda su API key

Guarda la clave de los proveedores que quiera y elige cuál atiende el chat. El chat y el estimador de comidas se configuran por separado: cada uno puede ir con un proveedor distinto.

Almacenamiento seguro del dispositivo
System prompt remoto

El prompt no viaja dentro de la app

El system prompt se descarga de un fichero público en el repositorio, se guarda en caché local y cae a una copia embebida si falla la red.

Editable sin publicar app

Esto último no es un capricho de arquitectura, es una válvula de seguridad. Si un día el agente está soltando barbaridades como «pasa una semana sin comer para bajar la grasa» y cosas así, quiero poder corregir las instrucciones, es decir, el system prompt, lo más rápido posible, no dentro de dos semanas, y desde luego no dependiendo de que cada usuario se acuerde de actualizar la app. Con el prompt en remoto, arreglarlo es editar un fichero de texto: el siguiente mensaje que envíe cualquiera ya va con la versión corregida. La copia embebida existe solo para que la app siga funcionando sin conexión.

El precio es que ese fichero pasa a ser una entrada de confianza para todos los usuarios a la vez, con lo que hay que tratarlo como código: revisado, versionado y con caché.

Llamada al proveedorlink image

La app no habla con un proveedor concreto, habla con «el proveedor que toque». Hoy hay tres soportados, y la lista está pensada para crecer:

OpenAIResponses API · streaming SSE/XHR
AnthropicMessages API · thinking habilitado
GoogleGemini generateContent

Cada proveedor tiene su propio adaptador de request/response, porque los formatos de tools, de streaming y de bloques de contenido son distintos en cada uno. Añadir un proveedor nuevo es escribir un adaptador más, no tocar el agente: todos comparten el mismo bucle agentic que viene ahora.

Bucle agentic (tool use)link image

Paso 1

Modelo responde

Texto en streaming y/o bloques tool_use / function_call.

Paso 2

La app ejecuta la tool

El modelo no ejecuta nada: solo pide. Quien ejecuta es la app, en local y contra sus propios datos.

Paso 3

Resultado → modelo

Se reinyecta como tool_result / function_call_output. Se repite hasta que no pide más tools.

Las toolslink image

El coach sí toca los datos del usuario: los lee y los escribe. Puede apuntar el peso de hoy, añadir un alimento a la comida o crear una rutina entera. Lo que no puede es salirse de ahí: su capacidad de actuar es exactamente la lista de tools declaradas, ni una más. Están agrupadas en cuatro familias, y todas se ejecutan dentro del móvil.

🧠 Memoria personal

Leer y guardar lo que el usuario cuenta de sí mismo: objetivo, lesiones, preferencias. Es lo que hace que el coach recuerde de una conversación a la siguiente.

🍽️ Dieta

Buscar alimentos en el catálogo que viene con la app, leer lo que ya se ha comido en una fecha y añadir alimentos a una comida.

🏋️ Entrenamiento

Buscar ejercicios por músculo, equipamiento o dificultad, leer las rutinas del usuario y crear rutinas nuevas.

📏 Medidas

Leer y escribir medidas corporales por fecha: peso, porcentaje de grasa, perímetros.

La excepción

create_feature_issue

La única tool que sale del dispositivo: cuando el usuario pide una mejora de la app, abre un issue en el repositorio. Es el único punto por el que algo escrito en el chat acaba fuera del móvil, y por eso está separada del resto.

Externo

Cómo se declara cada una de estas tools, nombre, descripción, esquema de argumentos, y cómo se traduce ese catálogo al formato que espera cada proveedor da para un post entero, y lo tiene: Cómo declarar tools fiables para OpenAI, Anthropic y Google.

Almacenamiento y salidalink image

Sin base de datos

Todo en el dispositivo

Dieta, rutinas, medidas y datos personales viven en el almacenamiento local del teléfono. Los catálogos de alimentos y ejercicios son ficheros estáticos que se empaquetan con la app.

Excepción

La creación de issues

Único punto de salida a un servicio externo distinto del proveedor de LLM: create_feature_issue.

Externo
Caveat de plataforma: el mismo agente corriendo en navegador se topa con CORS. Algunos proveedores permiten la llamada directa desde el navegador y otros no, y para esos hace falta un proxy local en desarrollo. En el móvil el problema no existe. Es el tipo de detalle que no aparece en el diseño del agente y aparece el primer día que intentas depurarlo en web.

El segundo agente: el estimador de comidaslink image

Misma lectura y mismas cajas que el grafo del coach. Ojo al desvío del código de barras: no es una alternativa a la estimación, es una vuelta más del bucle. El dato exacto vuelve al modelo y la estimación se produce igual, solo que con datos reales en vez de a ojo. Las tres cajas de borde discontinuo son llamadas al modelo.
LO QUE ENTRA EN CADA LLAMADA CUANDO EL MODELO PIDE LA TOOL ++ no + tool_result vuelta n+1 (máx. 5)
System promptpropio del estimador
Su única toolscan_barcode
Proveedorse elige aparte del chat
Usuario1–6 fotos + texto opcional
Petición al proveedortodo junto, en una sola llamada
Respuesta del modelomira las imágenes
¿Pide usar scan_barcode?
Estimaciónkcal y macros, con rangos
Segunda llamada al modelosolo si el usuario acepta: «devuélvemelo en JSON»
add_meal_food()→ dieta local del día
La app ejecuta scan_barcodedentro del móvil
OpenFoodFactsAPI pública, fuera del móvil

Desliza el grafo en horizontal para verlo entero.

Paso siguiente Entra en la llamada Vuelve atrás (bucle) Sale del dispositivo Fuera de tu código

El estimador de comidas saca calorías y macros de una foto del plato. No es un subagente del coach: el coach no lo llama nunca, ni sabe que existe. Es un segundo agente, con su propio system prompt, su propia tool y su propia elección de proveedor, que se abre desde otra pantalla y escribe en los mismos datos locales.

Entradalink image

Input del usuario

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.

Su propio proveedorlink image

El estimador no hereda el proveedor del chat: se elige aparte, en ajustes. Y tiene sentido que así sea, porque los dos agentes no compiten por lo mismo. Al coach le pides razonamiento sobre texto; al estimador le pides mirar una foto.

Si el usuario no toca nada, el estimador arranca con el proveedor que hoy da mejor relación coste/calidad en visión, y solo si esa clave no está configurada busca otra. La lección general: la elección de modelo es por tarea, no por app. En cuanto un producto tiene dos usos de IA con perfiles de coste distintos, atarlos al mismo proveedor es pagar de más en el caro o rendir de menos en el barato.

System prompt especializadolink image

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 barraslink image

Detección

¿Hay un código de barras en la foto?

El prompt obliga al modelo a usar la tool si detecta un EAN/UPC en cualquiera de las imágenes.

Única tool

scan_barcode(barcode)

Llama a OpenFoodFacts (API pública) con el código leído y devuelve datos nutricionales exactos del producto.

Externo · world.openfoodfacts.org

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 coach, el resultado de la tool se reinyecta al modelo y el bucle se repite, con un máximo de 5 rondas, hasta obtener una respuesta final. Ese tope no es decorativo: sin él, un modelo que se empeñe en volver a llamar a la misma tool se queda dando vueltas y gastando tokens del usuario.

Persistencia del resultadolink image

Nada se guarda solo

Confirmación del usuario

La estimación se enseña primero en lenguaje natural. Solo cuando el usuario la acepta se pide una segunda respuesta, esta vez en JSON, y se parsea.

add_meal_food

Se añade a la dieta local del día y la comida seleccionados, en los mismos datos que usa el coach.

Variante: estimación manuallink image

Sin cámara

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 catálogo de alimentos.

Sin tools · sin imágenes
Diseño clave: el estimador y el coach no comparten proveedor, ni prompt, ni tools. Solo comparten los datos. Mantenerlos separados es lo que permite ajustar el coste de cada uno por su cuenta y cambiar uno sin romper el otro.

La serielink image

Este post es la portada. Cada parte del desarrollo del agente se cuenta en un post propio, y todos se van enlazando aquí.

Mientras tanto, la ficha del proyecto está en maximofn.com/gymnasia y el código es abierto, en GitHub.

Seguir leyendo

Últimos posts -->

¿Has visto estos proyectos?

Gymnasia

Gymnasia Gymnasia
Expo
React Native
TypeScript
OpenAI
Anthropic

App de fitness con dos agentes que se ejecutan íntegramente en el dispositivo, sin backend, de forma que los datos del usuario nunca salen del móvil. Un coach conversacional BYOK con tools locales y system prompt remoto con fallback offline, y un estimador de comidas que saca los macronutrientes de una foto del plato, con lectura de códigos de barras contra OpenFoodFacts.

LangGraph Deep Researcher

LangGraph Deep Researcher LangGraph Deep Researcher
Python
LangGraph
FastAPI
React
TypeScript
Docker

Sistema multiagente de investigación construido con LangGraph. Un supervisor descompone tu pregunta en temas y lanza subagentes de búsqueda en paralelo; cada uno comprime sus hallazgos antes de pasarlos a un agente redactor que escribe el informe final en markdown con sus fuentes. Streaming en vivo por WebSockets, modelo configurable por rol y claves de API propias que nunca se guardan en el servidor.

Tau

Tau Tau
Python
LangChain

Sistema multiagente de tutoría para estudiantes de secundaria, con un agente por asignatura y material de curso elaborado y validado por un equipo de profesores. Llegó a usarse con alumnos reales en un colegio privado en España y en un instituto en Colombia.

Ver todos los proyectos -->
>_ Disponible para proyectos

¿Tienes un proyecto con IA?

Hablemos.

maximofn@gmail.com

Especialista en Machine Learning e Inteligencia Artificial. Desarrollo soluciones con IA generativa, agentes inteligentes y modelos personalizados.

¿Quieres ver alguna charla?

Últimas charlas -->

¿Quieres mejorar con estos tips?

Últimos tips -->

Usa esto en local

Los espacios de Hugging Face nos permite ejecutar modelos con demos muy sencillas, pero ¿qué pasa si la demo se rompe? O si el usuario la elimina? Por ello he creado contenedores docker con algunos espacios interesantes, para poder usarlos de manera local, pase lo que pase. De hecho, es posible que si pinchas en alún botón de ver proyecto te lleve a un espacio que no funciona.

Flow edit

Flow edit Flow edit

Edita imágenes con este modelo de Flow. Basándose en SD3 o FLUX puedes editar cualquier imagen y generar nuevas

FLUX.1-RealismLora

FLUX.1-RealismLora FLUX.1-RealismLora
Ver todos los contenedores -->
>_ Disponible para proyectos

¿Tienes un proyecto con IA?

Hablemos.

maximofn@gmail.com

Especialista en Machine Learning e Inteligencia Artificial. Desarrollo soluciones con IA generativa, agentes inteligentes y modelos personalizados.

¿Quieres entrenar tu modelo con estos datasets?

short-jokes-dataset

HuggingFace

Dataset de chistes en inglés

Uso: Fine-tuning de modelos de generación de texto humorístico

231K filas 2 columnas 45 MB
Ver en HuggingFace →

opus100

HuggingFace

Dataset con traducciones de inglés a español

Uso: Entrenamiento de modelos de traducción inglés-español

1M filas 2 columnas 210 MB
Ver en HuggingFace →

netflix_titles

HuggingFace

Dataset con películas y series de Netflix

Uso: Análisis de catálogo de Netflix y sistemas de recomendación

8.8K filas 12 columnas 3.5 MB
Ver en HuggingFace →
Ver más datasets -->