Gymnasia: declaring reliable tools for OpenAI, Anthropic and Google

Gymnasia: declaring reliable tools for OpenAI, Anthropic and Google

A tool is a contractlink image

When a model decides to use a tool, it cannot see the TypeScript function that will eventually run. It receives only three pieces: name, description and inputSchema. That block determines whether it selects the right tool and whether it builds arguments the application can accept.

name

The stable identifier returned by the model and used by the executor to find the handler.

description

Explains when to use the tool, what it guarantees and which prior steps it needs. It is part of the agent prompt.

inputSchema

The JSON Schema that constrains fields, types and mandatory arguments before local code runs.

One canonical catalogue for 13 toolslink image

Gymnasia keeps all 13 definitions in apps/mobile/agent/toolDefinitions.ts. Personal memory, measurements, diet, exercises, routines and feature requests all start from that single array. There is no independent copy for each provider.

This is a real definition from the catalogue:

{
  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"]
  }
}

The current names are 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 and create_feature_issue.

The description is prompt toolink image

A weak description such as "Read measurements" documents the function for a person, but gives the model no decision criteria: it does not say which date to use, what comes back or when the call is appropriate.

The real version explains intent, result and context. Tools with dependencies also enforce ordering: add_meal_food says that search_foods must run first, while create_routine requires exact exercise names obtained through search_exercises. That information has more influence on selection accuracy than any internal comment because it is what the model can actually read.

Three providers, one definitionlink image

OpenAI, Anthropic and Google express the same contract through different wrappers. CHAT_TOOLS projects the catalogue when the request is built:

OpenAI

type: "function", with the schema under parameters.

Anthropic

Direct name and description, with the schema under input_schema.

Google

Declarations inside functionDeclarations, with the schema under 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
  })) }]
};

The syntax changes; the name, description and schema do not. Adding or fixing one tool in the catalogue updates all three formats.

Definition and local executionlink image

Declaring a tool does not implement it. apps/mobile/agent/toolExecutor.ts holds the AGENT_TOOL_HANDLERS map: every catalogue name must have exactly one handler, and no handler may be left orphaned.

The handlers read or change local state, AsyncStorage and the JSON repositories bundled with the app. They then return text to the agentic loop so the model can continue. There is no Gymnasia database or backend between the tool and the user's data; the mobile application remains in control of execution.

Tests that prevent driftlink image

The contract is verified in layers. Tests require unique, valid names, meaningful descriptions, object schemas and required fields declared in properties. Ajv compiles all 13 JSON Schemas to catch invalid constructs.

const ajv = new Ajv({ allErrors: true, strict: true });

for (const definition of AGENT_TOOL_DEFINITIONS) {
  expect(() => ajv.compile(definition.inputSchema)).not.toThrow();
}

Other tests compare the complete definition set with the handlers and with all three provider projections. validateToolInput is also exercised with arbitrary inputs through fast-check: it must never throw and must always return a structured validation result.

The result is straightforward to maintain: one source of truth, three mechanical adapters and tests that expose any drift before it reaches the agent.

Back to the Gymnasia agent series index.

Continue reading

Last posts -->

Have you seen these projects?

Gymnasia

Gymnasia Gymnasia
Expo
React Native
TypeScript
OpenAI
Anthropic

Fitness app with two agents that run entirely on the device, with no backend, so the user's data never leaves the phone. A BYOK conversational coach with adapters for OpenAI, Anthropic and Google, 12 local tools and a remote system prompt with offline fallback, plus a vision subagent that estimates macronutrients from food photos, with barcode scanning against OpenFoodFacts.

LangGraph Deep Researcher

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

Multi-agent research system built with LangGraph. A supervisor breaks your question down into topics and launches search sub-agents in parallel; each one compresses its findings before handing them to a writer agent that produces the final sourced markdown report. Live streaming over WebSockets, a configurable model per role and bring-your-own API keys that are never persisted server-side.

Tau

Tau Tau
Python
LangChain

Multi-agent tutoring system for secondary school students, with one agent per subject and course material written and validated by a team of teachers. It was used with real students at a private school in Spain and at a secondary school in Colombia.

View all projects -->
>_ Available for projects

Do you have an AI project?

Let's talk.

maximofn@gmail.com

Machine Learning and AI specialist. I develop solutions with generative AI, intelligent agents and custom models.

Do you want to watch any talk?

Last talks -->

Do you want to improve with these tips?

Last tips -->

Use this locally

Hugging Face spaces allow us to run models with very simple demos, but what if the demo breaks? Or if the user deletes it? That's why I've created docker containers with some interesting spaces, to be able to use them locally, whatever happens. In fact, if you click on any project view button, it may take you to a space that doesn't work.

Flow edit

Flow edit Flow edit

FLUX.1-RealismLora

FLUX.1-RealismLora FLUX.1-RealismLora
View all containers -->
>_ Available for projects

Do you have an AI project?

Let's talk.

maximofn@gmail.com

Machine Learning and AI specialist. I develop solutions with generative AI, intelligent agents and custom models.

Do you want to train your model with these datasets?

short-jokes-dataset

HuggingFace

Dataset with jokes in English

Use: Fine-tuning text generation models for humor

231K rows 2 columns 45 MB
View on HuggingFace →

opus100

HuggingFace

Dataset with translations from English to Spanish

Use: Training English-Spanish translation models

1M rows 2 columns 210 MB
View on HuggingFace →

netflix_titles

HuggingFace

Dataset with Netflix movies and series

Use: Netflix catalog analysis and recommendation systems

8.8K rows 12 columns 3.5 MB
View on HuggingFace →
View more datasets -->