LLM tool calling needs a JSON Schema for each function. Frameworks generate it automatically from type hints. Do the same with inspect.
Implement tool_schema(fn) returning:
{"name": fn.__name__, "description": <first line of the docstring, or "">,
"parameters": {"type": "object", "properties": {...}, "required": [...]}}
str → "string", int → "integer", float → "number", bool → "boolean", list[X] → {"type": "array", "items": <schema of X>}, dict → "object". A missing annotation gives {} (any type).Literal["a", "b"] → {"type": "string", "enum": ["a", "b"]}.Optional[X] (or X | None) → the schema of X, and the parameter is not required."default" in their schema. Skip self, *args and **kwargs.