BYOKchat Blog

How to Design Good AI Tool Schemas

Design tool names, descriptions, JSON schemas, enums, required fields, error contracts, and side-effect boundaries that models can use reliably and applications can validate safely.

· 5 min read

On this page
  1. Start with one clear responsibility
  2. Tool names should be boring and explicit
  3. Descriptions should explain when to use the tool
  4. Prefer structured fields over overloaded text
  5. Use required fields deliberately
  6. Enums are better than “be reasonable” strings
  7. Avoid giant enums when IDs can be retrieved first
  8. Prefer stable IDs over names for mutation
  9. Do not ask the model to repeat trusted identity
  10. Keep side effects obvious
  11. Two-phase tools can improve safety
  12. Schema validation is necessary but not sufficient
  13. Use additionalProperties: false where supported and appropriate
  14. Keep schemas as small as the operation allows
  15. Separate read tools from write tools
  16. Descriptions should not contain secrets
  17. Defaults belong in the executor when possible
  18. Use bounded strings and arrays
  19. Avoid polymorphic schemas unless they add real value
  20. Tool result shape matters too
  21. Include explicit error results
  22. Make approval previews from parsed arguments
  23. Tool descriptions are part of the prompt-injection surface
  24. Version tool schemas
  25. Test model behavior, not only JSON validation
  26. A schema review checklist
  27. Where BYOKchat fits
  28. Further reading

A tool schema is a miniature API designed for a probabilistic caller.

That makes it different from an ordinary internal function signature.

A model needs enough information to answer:

Should I use this tool?
Which tool should I choose?
What arguments are required?
What values are valid?
What should I not invent?

Your application then needs a schema strict enough to validate the model’s proposal before execution.

Start with one clear responsibility

Bad tool:

manage_workspace

Possible meanings:

create project
delete project
rename project
invite user
list files
archive project

That is too broad.

Prefer narrow actions:

create_project
rename_project
archive_project
list_project_files

Narrow tools improve model selection and make permission policy easier.

Tool names should be boring and explicit

Good names describe the operation:

get_weather
search_documents
create_calendar_event
send_email

Avoid vague names:

do_it
helper
process
manage
smart_action

The model sees the name repeatedly in its tool context. Clarity beats branding.

Descriptions should explain when to use the tool

A weak description:

Searches documents.

A stronger description:

Search the user's indexed project documents for passages relevant to a factual question. Use this before answering questions that depend on project files. Do not use it for public web search.

Useful descriptions include:

  • purpose;
  • scope;
  • when it is appropriate;
  • important exclusions.

Do not bury authorization policy in the description. The application must enforce that separately.

Prefer structured fields over overloaded text

Bad:

{
  "query": "email alex@example.com subject hello body hi"
}

Better:

{
  "to": ["alex@example.com"],
  "subject": "Hello",
  "body": "Hi"
}

Structured fields improve validation, previews, logging, and future migration.

Use required fields deliberately

If a field is necessary for execution, mark it required.

Do not rely on the model to infer that an omitted field is mandatory.

Example:

{
  "type": "object",
  "properties": {
    "city": { "type": "string" },
    "unit": {
      "type": "string",
      "enum": ["celsius", "fahrenheit"]
    }
  },
  "required": ["city"],
  "additionalProperties": false
}

If unit has a safe application default, it can remain optional.

Enums are better than “be reasonable” strings

If the application supports exactly three modes:

fast
balanced
accurate

encode them as an enum.

Do not ask the model for an arbitrary string and then maintain a fuzzy parser for:

quick
speedy
high quality
normal

Enums turn an ambiguous language problem into a deterministic validation problem.

Avoid giant enums when IDs can be retrieved first

A tool with 2,000 customer IDs embedded in its schema is inefficient and brittle.

Use a retrieval step:

search_customers(query)
→ customer IDs

get_customer(customer_id)

This keeps schemas small and lets the model ground later calls in real identifiers.

Prefer stable IDs over names for mutation

Names can be ambiguous:

Project Alpha
Alex
Budget

For destructive or state-changing tools, use stable IDs when possible:

{
  "project_id": "proj_123"
}

The model can discover the ID using a read-only search/list tool first.

This reduces accidental operations on the wrong object.

Do not ask the model to repeat trusted identity

If the user is already authenticated to tenant tenant_42, avoid a tool schema like:

{
  "tenant_id": "...",
  "query": "..."
}

when the model is not supposed to choose the tenant.

Bind trusted context in application code:

authenticated tenant from session
+ model-provided query

This prevents the model from crossing authorization boundaries by inventing another ID.

Keep side effects obvious

A tool named:

preview_email

should not send email.

A tool named:

send_email

should have a permission policy appropriate for a real side effect.

Avoid tools whose effect depends on a hidden flag such as:

{
  "dry_run": false
}

when a model could accidentally switch it.

Separate preview and commit actions if the risk justifies it.

Two-phase tools can improve safety

For high-impact actions:

prepare_transfer
→ returns preview + operation token

confirm_transfer
→ requires explicit confirmation token

The application can require human approval between phases.

This makes the irreversible boundary visible.

Schema validation is necessary but not sufficient

A valid schema can still request something unauthorized:

{
  "account_id": "someone-else",
  "amount": 1000
}

Validation layers are:

JSON parse
→ schema validation
→ semantic validation
→ authorization
→ side-effect policy

Do all of them.

Use additionalProperties: false where supported and appropriate

Unexpected arguments can signal hallucination or prompt injection.

Rejecting them makes behavior more predictable.

But JSON Schema support differs by provider/tool protocol, so verify the supported subset.

The application should still ignore/reject unknown fields locally even if the provider does not enforce strict schema generation.

Keep schemas as small as the operation allows

Every tool definition consumes model context.

Large schemas can:

  • increase token usage;
  • reduce prompt-cache stability;
  • make tool selection harder;
  • expose irrelevant implementation details.

If the model never needs a field, do not include it.

Separate read tools from write tools

A useful permission taxonomy:

read-only
reversible write
irreversible/high-impact write

Schema design can reinforce this by making operations separate tools.

This is easier to audit than one database_action tool with an operation enum containing select, update, and delete.

Descriptions should not contain secrets

Tool definitions can be sent to the model provider.

Do not put:

  • API keys;
  • private credentials;
  • hidden database passwords;
  • sensitive internal tokens

inside tool descriptions or example values.

Secrets should be resolved inside the executor.

Defaults belong in the executor when possible

Suppose limit defaults to 20.

You can document it:

limit: optional integer, defaults to 20, max 100

but the executor should enforce:

let limit = min(args.limit ?? 20, 100)

Do not trust the model to honor prose-only limits.

Use bounded strings and arrays

A schema/executor should constrain:

maximum query length
maximum recipient count
maximum result count
maximum file count

This protects downstream systems from oversized model-generated requests.

Even if schema keywords for limits are unsupported by a provider, enforce them locally.

Avoid polymorphic schemas unless they add real value

Complex oneOf/nested variants can be harder for models and providers to handle consistently.

Instead of:

one tool with 12 operation variants

multiple simpler tools are often easier to reason about.

Use polymorphism when the domain genuinely requires it, not to reduce the number of tool names.

Tool result shape matters too

The model also consumes the result.

Return structured, concise data where possible:

{
  "customer_id": "cus_123",
  "name": "Alex",
  "status": "active"
}

Avoid dumping a 5 MB internal object containing irrelevant fields.

Tool-result minimization improves context efficiency and reduces accidental sensitive-data exposure.

Include explicit error results

Do not turn every tool failure into an unstructured exception that kills the model loop.

A recoverable tool result can be:

{
  "ok": false,
  "error": {
    "code": "not_found",
    "message": "No project matched that ID."
  }
}

The model can then decide whether to ask the user or try a different read-only lookup.

Keep internal stack traces out of model-visible error messages.

Make approval previews from parsed arguments

A schema with explicit fields enables a useful human approval card:

Send email
To: alex@example.com
Subject: Release schedule
Body: 824 characters

That is much safer than displaying raw JSON or only the tool name.

See How to Build Human Approval Into AI Tool Calls.

Tool descriptions are part of the prompt-injection surface

Untrusted external content should never be concatenated into tool descriptions dynamically.

For example, do not build:

Tool description = "Use this API. Current page says: <untrusted webpage>"

Tool definitions should come from trusted application/server configuration.

Version tool schemas

If a durable conversation stores tool calls, schema evolution matters.

A tool can keep a stable external name and add a version internally, or expose versioned names when changes are incompatible.

Persist enough metadata to interpret old calls during backup/restore/debugging.

Test model behavior, not only JSON validation

A technically valid schema may still be confusing to models.

Evaluate:

Does the model choose the right tool?
Does it avoid the tool when unnecessary?
Does it fill required arguments correctly?
Does it ask the user when information is missing?
Does it confuse similar tools?

Use representative prompts and multiple supported models.

A schema review checklist

Before shipping a tool:

  • one clear responsibility;
  • explicit name;
  • description explains when/when not to use;
  • required fields truly required;
  • enums for closed domains;
  • stable IDs for mutations;
  • no trusted identity delegated to model;
  • bounded inputs;
  • unknown fields rejected;
  • side effect obvious;
  • result minimized;
  • error contract defined;
  • approval preview possible;
  • authorization implemented outside schema.

Where BYOKchat fits

A provider-neutral tool layer can keep one portable schema intent while adapters translate it to provider/MCP formats. The application then validates the returned arguments against the same trusted schema and applies per-tool permission policy before execution.

Further reading

Keep reading