O que é o Gymnasia
O Gymnasia é um aplicativo móvel de treino pessoal feito com React Native e Expo. Dentro dele há dois agentes de IA: um coach conversacional e um subagente de visão que estima calorias e macronutrientes a partir de fotos de comida.
O interessante, e o assunto deste post, é que os dois são executados inteiramente no dispositivo. Não existe backend próprio: a orquestração, as ferramentas e o armazenamento vivem em App.tsx e no AsyncStorage, e as chaves de API são fornecidas pelo usuário. Os dados do usuário nunca saem do celular, com uma única exceção explicada mais abaixo.
Este é o post principal da série. Abaixo estão os dois grafos de arquitetura e, no final, o índice da série com os posts que cobrem cada parte do desenvolvimento.
O agente geral: Gymnasia Coach
Deslize o grafo na horizontal para o ver inteiro.
O Gymnasia Coach é o agente conversacional geral do app, o da aba Chat IA. É um agente BYOK (Bring Your Own Key): o utilizador configura a sua própria chave de API da OpenAI, Anthropic ou Google, e o app chama diretamente o fornecedor escolhido a partir do dispositivo. Não existe backend próprio: toda a lógica de orquestração, ferramentas e armazenamento vive em App.tsx e no AsyncStorage local.
Configuração e fornecedor ativo
O utilizador guarda a sua API key
Uma das 3 chaves (OpenAI / Anthropic / Google) é marcada como is_active. Só essa é usada no chat geral.
prompts/AGENTS.md no GitHub
É descarregado de raw.githubusercontent.com/maximofn/gymnasia, guardado em cache local e recorre a um prompt por omissão embutido se a rede falhar.
Chamada ao fornecedor
Cada fornecedor tem o seu próprio adaptador de request/response, porque os formatos de tools, de streaming e de blocos de conteúdo são diferentes nos três. Mas partilham o mesmo laço agentic que vem a seguir.
Laço agentic (tool use)
O modelo responde
Texto em streaming e/ou blocos tool_use / function_call.
handleToolCall()
Executa 100% localmente sobre o estado do app (AsyncStorage, repositórios JSON).
Resultado → modelo
É reinjetado como tool_result / function_call_output. Repete-se até não pedir mais ferramentas.
As 12 ferramentas
Armazenamento e saída
LocalStore (AsyncStorage)
Dieta, rotinas, medidas e dados pessoais vivem no dispositivo. Os repositórios de alimentos e exercícios são JSON estáticos empacotados com o app.
GitHub Issues API
Único ponto de saída para um serviço externo diferente do fornecedor de LLM: create_feature_issue.
apps/anthropic_proxy/cors-proxy.py) porque o navegador bloqueia a chamada direta a api.anthropic.com. OpenAI e Google funcionam diretamente a partir do navegador.O subagente de visão: Food Estimator
Deslize o grafo na horizontal para o ver inteiro.
O Food Estimator é o subagente de visão que estima calorias e macros a partir de fotos de comida (aba Dieta → Estimativa IA). É independente do chat geral: tem o seu próprio system prompt, a sua própria ferramenta e a sua própria política de seleção de fornecedor.
Entrada
1–6 fotos da refeição
Câmara ou galeria. Também aceita texto (perguntas de seguimento sobre a estimativa), reutilizando o contexto da conversa.
Seleção de fornecedor por prioridade
Ao contrário do chat geral, aqui não se usa o fornecedor ativo: tenta-se por ordem até encontrar o primeiro com chave de API configurada.
System prompt especializado
Nutricionista visual
Estima sempre kcal, proteína (g), hidratos de carbono (g), gordura (g) e peso total (g). Dá intervalos se houver incerteza.
Classificação
Determina se é producto_comercial, receta ou alimento base genérico.
Saída estruturada
Se o utilizador pedir "Devuelve json", responde apenas com JSON: dish_name, calories_kcal, protein_g, carbs_g, fat_g.
Laço agentic com o código de barras
Há um código de barras na foto?
O prompt obriga o modelo a usar a ferramenta se detetar um EAN/UPC em qualquer das imagens.
scan_barcode(barcode)
Chama o OpenFoodFacts (API pública) com o código lido e devolve dados nutricionais exatos do produto.
Produto comercial confirmado
Se foi usado scan_barcode, a classificação é sempre producto_comercial, com dados exatos em vez de estimados.
Tal como no agente geral, o resultado da ferramenta é reinjetado no modelo e o laço repete-se, com um máximo de 5 rondas, até obter uma resposta final.
Persistência do resultado
Confirmação do utilizador
Quando o utilizador aceita a estimativa, é pedido o bloco JSON final e feito o parse.
add_meal_food
É adicionado à dieta local do dia e refeição selecionados, no mesmo store que o agente geral usa.
Variante: estimativa manual
O utilizador descreve um alimento por texto
Fluxo conversacional: o utilizador nomeia o alimento, o modelo pergunta ingredientes e quantidades em falta, calcula valores por 100 g ou unidade, o utilizador confirma e devolve o JSON para guardar no repositório de alimentos.
A série
Este post é a capa. Cada parte do desenvolvimento do agente tem o seu próprio post, e todos são ligados a partir daqui.
- Ainda não há posts publicados na série. Os primeiros serão o de planeamento e o de tool calling.
Entretanto, a página do projeto está em maximofn.com/pt-br/gymnasia e o código é aberto, no GitHub.