Validate a Tool Call Against Its Schema
Problem Statement
The model does not call your tool - it emits a JSON object claiming to call your tool. Everything between that claim and your code is your responsibility. A validator sitting on the boundary turns a malformed call into a precise, machine-readable error the agent can actually recover from, instead of a stack trace inside your business logic.
Background
A tool schema is a JSON-Schema-shaped object:
{
"parameters": {
"type": "object",
"properties": {
"city": {"type": "string"},
"days": {"type": "integer"},
"units": {"type": "string", "enum": ["c", "f"]}
},
"required": ["city"]
}
}
Validation checks four things, in this order per argument: the tool exists, every required parameter is present, no undeclared parameters were passed, and every declared parameter has the right type (and, if an enum is declared, an allowed value).
The subtle one: in Python bool is a subclass of int, so a naive isinstance(True, int) accepts True for an integer parameter. It is not one. A boolean value is valid only for "type": "boolean".
Your Task
Implement:
def validate_tool_call(schemas, call):
- schemas: dict mapping tool name -> schema dict as above.
- call: dict {"name": str, "arguments": dict}.
Return {"ok": bool, "errors": list[str]} where errors use these exact formats:
| Situation | Error string | |---|---| | tool name not in schemas | unknown_tool:<name> | | required parameter absent | missing:<param> | | argument not declared in properties | unexpected:<param> | | declared parameter, wrong type | type:<param> | | right type, value not in enum | enum:<param> |
Rules: an unknown tool short-circuits - return that single error and nothing else. Otherwise collect every error, and report at most one error per argument (a type error suppresses the enum check for that argument). Sort the final error list ascending before returning. ok is True only when the list is empty.
Type names map to Python as: string->str, integer->int, number->int or float, boolean->bool, array->list, object->dict. A parameter spec with no "type" accepts anything.
Input/Output Format
Returns a dict with keys ok (bool) and errors (sorted list of strings).
Sample
schemas = {"get_weather": {"parameters": {
"type": "object",
"properties": {"city": {"type": "string"}, "days": {"type": "integer"}},
"required": ["city"]}}}
print(validate_tool_call(schemas, {"name": "get_weather",
"arguments": {"days": True, "tz": "UTC"}}))
# {'ok': False, 'errors': ['missing:city', 'type:days', 'unexpected:tz']}
Example
schemas = {'get_weather': {'parameters': {'type':'object','properties': {'city': {'type':'string'}, 'days': {'type':'integer'}}, 'required': ['city']}}}
print(validate_tool_call(schemas, {'name': 'get_weather', 'arguments': {'days': True, 'tz': 'UTC'}})){'ok': False, 'errors': ['missing:city', 'type:days', 'unexpected:tz']}- Inputs:
schemasdeclares one tool,get_weather, with two parameters:city(a string, required) anddays(an integer, optional). Thecallnames that tool and passes two arguments:daysset toTrueandtzset to'UTC'. - Tool check:
get_weatherexists, so there is no short circuit and every argument problem gets reported. - Required: the required set is and the supplied set is . Their difference is , giving
missing:city. - Declared:
tzis not among the declared properties, so it earnsunexpected:tzand nothing else is judged about it (one error per argument). - Type:
daysis declared as an integer, butTrueis a boolean. Python counts a boolean as an integer, but here booleans are valid only for a boolean parameter, so this istype:days. - Output: the three strings in ascending order compare on their first letters, m < t < u, and since the list is non-empty the result is
{'ok': False, 'errors': ['missing:city', 'type:days', 'unexpected:tz']}. - Notice that with
citypresent anddaysequal to 3, onlyunexpected:tzwould remain.
Constraints
1 <= len(schemas) <= 50; each schema has at most 20 properties- An unknown tool returns exactly
["unknown_tool:<name>"]and stops - At most one error per argument:
type:suppresses theenum:check True/Falseare valid only for"type": "boolean"- never forintegerornumberintis an acceptablenumber;floatis not an acceptableinteger- The
errorslist must be sorted ascending