Syllabus Lesson 137 of 239 · Structured Outputs & Function/Tool Calling
Structured Outputs & Function/Tool Calling

Function -> Tool Schema (Introspection)

Tool calling (a.k.a. function calling) lets a model ask your program to run a function: you advertise a set of tools, the model replies with a tool name and arguments, and you execute it. But the model cannot see your Python -> it only sees a schema: a JSON description of each tool's name and parameters. Writing those schemas by hand for every function is tedious and drifts out of sync the moment you change a signature. The fix is introspection: generate the schema straight from the function.

Python's inspect module reads a function's signature at runtime. The schema the tool-calling APIs expect looks like this:

{
  "name": "get_weather",
  "parameters": {
    "type": "object",
    "properties": {"city": {"type": "string"}, "days": {"type": "integer"}},
    "required": ["city"]
  }
}

You are building tool_schema(func) that produces exactly that. Two ideas do all the work:

  • Map Python types to JSON-schema types. Read each parameter's annotation and translate: int -> "integer", float -> "number", str -> "string", bool -> "boolean". Default anything else to "string".
  • Derive required from defaults. A parameter with no default is required; a parameter with a default is optional. inspect.Parameter.empty is the sentinel for "no default".
import inspect
sig = inspect.signature(func)
for name, param in sig.parameters.items():
    is_required = param.default is inspect.Parameter.empty
    json_type = ...   # from param.annotation

Use func.__name__ for the tool name. Build it so two differently-annotated functions produce two clearly different schemas, an int param shows up as "integer" and a str as "string", a defaulted parameter is left out of required, and a non-defaulted one is in it. That is the contract every tool-calling integration relies on.

Your turn

Write tool_schema(func) that introspects a function with inspect.signature and returns {"name": func.__name__, "parameters": {"type": "object", "properties": {param: {"type": json_type}}, "required": [...]}}. Map int->"integer", float->"number", str->"string", bool->"boolean" (default "string"), and put a parameter in required only when it has no default.

Spotted a problem in this lesson? Report it

Code · runs in your browser
Output