Language models are good at producing text, and less reliable at producing exactly formatted data. Ask a model for a date, and it may return "next Tuesday," "2026-12-05," or "Tuesday the 5th," depending on its mood, and each variation breaks a program that expects one format. Function calling solves this by giving the model a structured choice: instead of inventing a format, the model fills in a declared function, with named parameters of declared types, and the program receives data it can validate and use. The schema is the contract between the model and the program, and writing it well is the difference between a system that works reliably and one that fails in subtle, hard-to-debug ways.
The Function Calling Schema Builder turns a function outline into a JSON schema. You enter a function name, a description, and parameters as name:type:yes or name:type:no lines, where yes means required and no means optional, and the tool returns a JSON schema for function calling. It does not run the function, connect to any service, or validate the schema against a particular model's requirements. Its value is in producing a clean, consistent schema from a plain outline, so you can focus on the design decisions that matter: what the function does, what it needs, and what it must not be allowed to assume.
Designing a function before writing its schema
Start with a single, clear purpose. A function should do one thing well, because a model choosing between functions needs distinct options. A function called get_weather that returns weather for a city is a good candidate. A function called handle_everything that takes a vague task description and returns whatever seems relevant is not, because the model cannot know what it is supposed to do, and the program cannot validate what it returns. If a task seems to need several purposes, split it into several functions, each with a name that states its purpose in plain words, using verbs and nouns a model can distinguish, such as get_weather, book_appointment, and search_products, rather than process_data or do_thing.
Next, write the description as if explaining the function to a careful colleague who has never seen it. State when to use it, what it returns, and any important limits. "Get the current weather for a city" is a start, but "Get the current weather for a city, including temperature and conditions, for planning outdoor activities. Use only when the user asks about weather in a specific place" is better, because it tells the model when the function is appropriate and when it is not. A vague description invites misuse: a model may call a weather function for a question about climate history, or avoid calling it when it should, because the description did not make the intended use clear.
Then list parameters, and for each one decide three things: its name, its type, and whether it is required. Names should be clear and consistent, using the same term the rest of your system uses: city, not location_or_place, if your code calls it city. Types should be as specific as the data allows: a string for a city name, a number for a temperature threshold, a boolean for a yes-or-no flag, and an enumeration where only certain values are valid. Required parameters are those without which the function cannot do its job. Making too many parameters required forces the model to guess values it does not know, which produces confident-looking but invented data. Making too few required allows calls that lack essential information, which then fail downstream. The builder's yes/no marker forces this decision for every parameter, which is itself a useful discipline.
A worked example
Consider a sample function for weather, named get_weather, with the description "Get the current weather for a named city, for planning outdoor activities." Its parameters, entered as name:type:yes or name:type:no lines, are city:string:yes and units:string:no. The city parameter is required, because weather without a place is meaningless. The units parameter is optional, because the function can default to a sensible unit when none is given, and forcing the model to choose a unit on every call would add a guess where none is needed.
The resulting schema, in outline form, declares a function named get_weather, with a description as above, and a parameters object containing city as a string and units as a string, with a required list containing only city. In JSON form it looks like this:
{ "name": "get_weather", "description": "Get the current weather for a named city, for planning outdoor activities.", "parameters": { "type": "object", "properties": { "city": { "type": "string" }, "units": { "type": "string" } }, "required": ["city"] } }
That schema is small, but it encodes several decisions that would otherwise be implicit: the function name, its purpose, the two parameters, their types, and the fact that only city is required. A model given this schema can produce a call such as get_weather with city set to "Lahore" and units omitted, and the program can validate that call mechanically: city is present and is a string, units is absent but optional, so the call is valid. If the model omitted city, validation would fail clearly, rather than the program proceeding with a guessed place. That clarity is the point of the schema. It turns a model's output from prose that must be interpreted into data that can be checked.
A second example shows why enumerations matter. Suppose units may only be "metric" or "imperial" in your system. Declaring units as a plain string allows a model to return "celsius," "fahrenheit," "standard," or any other plausible word, and your program must then handle each variant. Declaring units with an explicit list of allowed values, where the schema format supports it, constrains the model to the two values your code understands. The builder's simple outline does not cover every advanced schema feature, and for enumerations or nested objects you may need to extend the generated schema by hand, but the principle remains: the more precisely the schema states what is allowed, the fewer surprises the program must handle.
Common mistakes
The most common mistake is writing a description that describes the implementation rather than the use. "Calls the weather API endpoint with authentication" tells the model nothing about when to use the function, while "Get current weather for a city when the user asks about conditions for outdoor plans" tells it exactly when. Write descriptions for the caller, which in this case is the model deciding whether to call the function, not for the developer maintaining the code.
A second mistake is making every parameter required. Required parameters should be the minimum needed for the function to work. Optional parameters with sensible defaults reduce guessing, because the model can omit what it does not know instead of inventing it. When a model must guess a required value, it usually does so confidently, and the resulting call looks valid while being wrong, which is harder to debug than a clearly incomplete call.
A third mistake is using vague parameter names or types. A parameter named data of type string can contain anything, which means the program cannot validate it meaningfully, and the model cannot know what format is expected. A parameter named city of type string, with a description stating that it should be a city name rather than a country or a postcode, is far more usable. Where a parameter has a known format, such as a date in year-month-day form, state that format in the description, because the schema type alone cannot express it.
A fourth mistake is trusting the model's output without validation. A schema constrains the shape of the output, but it does not guarantee that the values are true, current, or authorised. A model may call get_weather with a city that does not exist, or with a plausible but wrong city for the user's actual location. Always validate parameter values against your own data, check authorisation before acting on a function call, and treat the model's arguments as untrusted input, just as you would treat any user input. A schema is a contract about shape, not a guarantee about truth.
Honest limitations
A schema builder produces a schema from an outline. It cannot design the function for you, judge whether the function is safe to expose to a model, or ensure that the schema matches a particular provider's exact format, which can vary in details such as how the function name and parameters are wrapped. Always check the generated schema against the documentation of the model provider you are using, and test it with real prompts before relying on it, because a schema that looks correct in isolation may behave differently when a model interprets it. The builder also cannot enforce business rules, such as rate limits, permissions, or side effects, which must be handled by the code that executes the function, not by the schema that describes it.
Companion tools help with the surrounding JSON work. The JSON Studio formats and validates the generated schema, so syntax errors are caught before testing, and the JSON Schema Validator checks sample data against a schema, which is useful for verifying that a model's arguments actually match what the function expects. For the discipline of validating structured data before trusting it, How to Validate JSON Against a Schema covers the same habit in the broader JSON setting.
Closing
A good function schema is a clear contract: one purpose, a description written for the caller, parameters with clear names and types, and only the truly necessary parameters marked required. Outline the function, build the schema with the Function Calling Schema Builder, check the result in the JSON Studio, and validate sample calls with the JSON Schema Validator before connecting it to real actions. A model given a precise schema will still make mistakes, but those mistakes will be visible as validation failures rather than hidden as plausible prose, and visible failures are the ones you can fix.