Skip to main content
Lesson 3 of 14 · Function Calling and Tool Use

Define Functions with JSON Schema

Lesson 3 of 14 Updated July 29, 2026 Developer
On this page
  1. The contract between your code and the model
  2. Anatomy of a tool specification
  3. Supported JSON Schema types
  4. Complete example: two complementary functions
  5. Required vs. optional parameters
  6. Best practices for descriptions
  7. Key takeaways

The contract between your code and the model

For the Mistral model to call your functions, it needs to know what exists and how to use it. This is the role of JSON Schema: a standardized format that describes each function—its name, what it does, what parameters it expects, and which ones are mandatory.

Keep one thing in mind throughout this lesson: the model never sees your Python code. It only sees this specification. If your function handles lowercase IDs gracefully but the schema doesn’t say so, the model ignores that capability. The more precise and well-documented your schema is, the more accurate the generated arguments will be.

Anatomy of a tool specification

Every tool sent to the Mistral API follows the same structure:

tool = {
    "type": "function",
    "function": {
        "name": "function_name",
        "description": "Clear description of what the function does",
        "parameters": {
            "type": "object",
            "properties": {
                "param1": {
                    "type": "string",
                    "description": "Parameter description"
                },
                "param2": {
                    "type": "integer",
                    "description": "Parameter description"
                }
            },
            "required": ["param1"]
        }
    }
}
FieldRole
typeAlways "function" (other types may exist in the future)
function.nameThe exact name of your Python function—this is what the model returns in tool_calls
function.descriptionA clear sentence that helps the model choose the right function
function.parametersA JSON Schema object describing the expected parameters
requiredThe list of mandatory parameters

Supported JSON Schema types

The type vocabulary available for describing parameters is intentionally restricted, and each type guides the model differently. An enum, in particular, does more than document: it shuts the door on invented values.

# String — free text or constrained
{"type": "string", "description": "Transaction ID"}

# String with enum — limited values
{"type": "string", "enum": ["asc", "desc"], "description": "Sort order"}

# Integer — whole number
{"type": "integer", "description": "Number of results to return"}

# Number — decimal number
{"type": "number", "description": "Amount in euros"}

# Boolean — true/false
{"type": "boolean", "description": "Include canceled transactions"}

# Array — list of values
{
    "type": "array",
    "items": {"type": "string"},
    "description": "List of transaction IDs"
}

# Object — nested object
{
    "type": "object",
    "properties": {
        "city": {"type": "string"},
        "country": {"type": "string"}
    },
    "description": "Shipping address"
}

Distinguish clearly between integer and number: an amount in euros like 125.50 requires a number, while a count of results to return is an integer. Declaring an amount as an integer will cause the model to round it.

Complete example: two complementary functions

Here is a realistic example with two payment-related functions, inspired by the official Mistral documentation:

tools = [
    {
        "type": "function",
        "function": {
            "name": "retrieve_payment_status",
            "description": "Retrieves the status of a payment (paid, pending, failed) "
                           "based on the transaction ID.",
            "parameters": {
                "type": "object",
                "properties": {
                    "transaction_id": {
                        "type": "string",
                        "description": "The unique transaction ID (e.g., T1001)"
                    }
                },
                "required": ["transaction_id"]
            }
        }
    },
    {
        "type": "function",
        "function": {
            "name": "retrieve_payment_date",
            "description": "Retrieves the date when a payment was made "
                           "based on the transaction ID.",
            "parameters": {
                "type": "object",
                "properties": {
                    "transaction_id": {
                        "type": "string",
                        "description": "The unique transaction ID (e.g., T1001)"
                    }
                },
                "required": ["transaction_id"]
            }
        }
    }
]

Both functions expect exactly the same parameter, transaction_id, yet the model must choose between them. This choice relies entirely on the function description. “Has this payment gone through?” maps to retrieve_payment_status; “When was it?” maps to retrieve_payment_date. If you had written two vague, interchangeable descriptions, the model would get it wrong half the time—and you would blame the model when the fault lies in the schema.

Required vs. optional parameters

The distinction between required and optional parameters determines what the model is allowed to omit.

{
    "type": "function",
    "function": {
        "name": "search_transactions",
        "description": "Searches for transactions based on various criteria",
        "parameters": {
            "type": "object",
            "properties": {
                "status": {
                    "type": "string",
                    "enum": ["paid", "pending", "failed"],
                    "description": "Filter by payment status"
                },
                "date_from": {
                    "type": "string",
                    "description": "Start date in YYYY-MM-DD format"
                },
                "date_to": {
                    "type": "string",
                    "description": "End date in YYYY-MM-DD format"
                },
                "min_amount": {
                    "type": "number",
                    "description": "Minimum amount in euros"
                }
            },
            "required": ["status"]
        }
    }
}

Here, only status is required; the other parameters are included only if the user mentions them. Compare two formulations. For “Show me failed payments,” the model generates simply {"status": "failed"}. For “Show me failed payments from March 2026,” it adds date_from and date_to on its own. Making those dates required would have broken the first query, or worse, pushed the model to invent a date range.

Best practices for descriptions

Description quality directly impacts model accuracy. The counter-examples below are more instructive than any abstract rule.

# Bad — too vague
{"description": "Retrieves data"}

# Good — precise and contextual
{"description": "Retrieves the status of a payment (paid, pending, failed) "
                "based on the unique transaction ID"}

# Bad — no description for the parameter
{"transaction_id": {"type": "string"}}

# Good — description with example
{"transaction_id": {
    "type": "string",
    "description": "Unique transaction ID (format: T followed by 4 digits, e.g., T1001)"
}}

A good description states in one sentence what the function does, specifies the expected format for each parameter, and provides an example whenever that format isn’t obvious—T1001 is better than “transaction ID.” When possible values are limited, don’t describe them in prose: declare them with enum. That is the only way to make them binding.

Key takeaways

  • JSON Schema is the contract between your code and the model—it describes your functions without exposing the code
  • Every tool has a name, a description, and typed parameters
  • Supported types include string, integer, number, boolean, array, and object
  • The required list distinguishes mandatory parameters from optional ones
  • Precise descriptions with examples significantly improve model accuracy