Uma tool é um contrato
Quando um modelo decide usar uma ferramenta, ele não vê a função TypeScript que será executada. Recebe apenas três elementos: name, description e inputSchema. Esse bloco determina se ele escolhe a ferramenta certa e se constrói argumentos que o aplicativo pode aceitar.
O identificador estável devolvido pelo modelo e usado pelo executor para encontrar o handler.
Explica quando usar a tool, o que ela garante e quais passos anteriores exige. Faz parte do prompt do agente.
O JSON Schema que limita campos, tipos e argumentos obrigatórios antes de executar o código local.
Um catálogo canônico para 13 ferramentas
O Gymnasia mantém as 13 definições em apps/mobile/agent/toolDefinitions.ts. Memória pessoal, medidas, dieta, exercícios, rotinas e pedidos de melhorias partem desse único array. Não existe uma cópia independente para cada provedor.
Esta é uma definição real do catálogo:
{
name: "read_measurement",
description:
"Lee las medidas corporales del usuario para una fecha específica. " +
"Devuelve el registro de medidas de ese día si existe. " +
"Usa esta herramienta cuando el usuario pregunte por sus medidas de un día concreto.",
inputSchema: {
type: "object",
properties: {
date: stringProperty("Fecha en formato YYYY-MM-DD")
},
required: ["date"]
}
}Os nomes atuais são save_personal_data, list_personal_data_keys, read_field_description, read_field_value, read_measurement, write_measurement, read_meal_foods, search_foods, add_meal_food, search_exercises, read_routines, create_routine e create_feature_issue.
A descrição também é prompt
Uma descrição fraca como "Lê medidas" documenta a função para uma pessoa, mas deixa o modelo sem critério: não diz qual data usar, o que retorna nem quando a chamada é apropriada.
A versão real explica intenção, resultado e contexto. Ferramentas com dependências também impõem a ordem: add_meal_food informa que search_foods deve ser executada antes, enquanto create_routine exige nomes exatos obtidos por search_exercises. Essa informação influencia mais a precisão da escolha do que qualquer comentário interno, porque é o que o modelo realmente consegue ler.
Três provedores, uma definição
OpenAI, Anthropic e Google expressam o mesmo contrato com invólucros diferentes. CHAT_TOOLS projeta o catálogo no momento de montar a requisição:
type: "function", com o schema em parameters.
Nome e descrição diretos, com o schema em input_schema.
Declarações dentro de functionDeclarations, com o schema em parameters.
const CHAT_TOOLS = {
openai: definitions.map(tool => ({
type: "function", name: tool.name,
description: tool.description, parameters: tool.inputSchema
})),
anthropic: definitions.map(tool => ({
name: tool.name, description: tool.description,
input_schema: tool.inputSchema
})),
google: [{ functionDeclarations: definitions.map(tool => ({
name: tool.name, description: tool.description,
parameters: tool.inputSchema
})) }]
};A sintaxe muda; o nome, a descrição e o schema não. Adicionar ou corrigir uma ferramenta no catálogo atualiza os três formatos.
Definição e execução local
Declarar uma tool não significa implementá-la. apps/mobile/agent/toolExecutor.ts mantém o mapa AGENT_TOOL_HANDLERS: cada nome do catálogo deve ter exatamente um handler, e nenhum handler pode ficar órfão.
Os handlers leem ou alteram o estado local, o AsyncStorage e os repositórios JSON incluídos no aplicativo. Depois devolvem texto ao laço agentic para que o modelo continue. Não há banco de dados nem backend do Gymnasia entre a tool e os dados do usuário; o aplicativo móvel mantém o controle da execução.
Testes contra divergências
O contrato é verificado em várias camadas. Os testes exigem nomes únicos e válidos, descrições significativas, schemas de objeto e campos required declarados em properties. O Ajv compila os 13 JSON Schemas para detectar construções inválidas.
const ajv = new Ajv({ allErrors: true, strict: true });
for (const definition of AGENT_TOOL_DEFINITIONS) {
expect(() => ajv.compile(definition.inputSchema)).not.toThrow();
}Outros testes comparam o conjunto completo de definições com os handlers e com as projeções dos três provedores. validateToolInput também recebe entradas arbitrárias por meio do fast-check: nunca deve lançar uma exceção e sempre deve produzir um resultado de validação estruturado.
O resultado é simples de manter: uma única fonte de verdade, três adaptadores mecânicos e testes que tornam qualquer divergência visível antes de chegar ao agente.