I enforce strict static analysis and runtime type validation across the entire codebase to catch bugs early and guarantee schema compliance during LLM tool calling.
I target Python 3.10+ and utilize native built-in generics (PEP 585) and standard union syntax (PEP 604). I avoid deprecated imports from typing like List, Dict, or Optional, relying instead on lower-case built-ins and the | operator.
Deprecated (typing) |
Modern Built-in | Purpose |
|---|---|---|
List[str] |
list[str] |
Typed list collections |
Dict[str, Any] |
dict[str, Any] |
Dictionary mappings |
Tuple[int, str] |
tuple[int, str] |
Fixed-length tuple signatures |
Optional[str] |
`str | None` |
Note: from typing import Any is retained because Any is a static type construct rather than a runtime class object.
I configure Ruff to enforce PEP 8 guidelines and handle isort import ordering (Ruff(I001)). Imports are automatically grouped into standard library, third-party, and local module blocks.
To automatically format code and reorder imports across the project:
uv run ruff check --fix .
uv run ruff format .
LLMs frequently output raw JSON values that mismatch expected function signatures (e.g., passing an integer 1 instead of a float 1.0).
I implemented enforce_arg_types to sanitize arguments against tool definitions at runtime. The function includes Google-style PEP 257 docstrings and type guards (isinstance, .get()) to pass mypy checks without warnings:
from typing import Any
def enforce_arg_types(
fn_name: str,
args: dict[str, Any],
functions_def: list[dict[str, Any]],
) -> dict[str, Any]:
"""Converts argument values to their defined types based on function definitions.
Args:
fn_name: The name of the target function to look up.
args: Dictionary of extracted argument names and raw LLM outputs.
functions_def: List of tool definition dictionaries.
Returns:
Dictionary with argument values converted to their expected types.
"""
fn_def = next((f for f in functions_def if f.get("fn_name") == fn_name), None)
if not fn_def or "args_types" not in fn_def:
return args
args_types = fn_def["args_types"]
if not isinstance(args_types, dict):
return args
for arg_name, arg_type in args_types.items():
if arg_name in args:
try:
if arg_type == "float":
args[arg_name] = float(args[arg_name])
elif arg_type == "int":
args[arg_name] = int(args[arg_name])
elif arg_type == "str":
args[arg_name] = str(args[arg_name])
except (ValueError, TypeError):
pass # Keep original value if conversion fails
return argsI manage mypy flags centrally inside pyproject.toml rather than passing long CLI flag strings. This ensures identical static analysis enforcement across IDEs, terminal runs, and CI checks.
[tool.mypy]
python_version = "3.10"
warn_return_any = true
warn_unused_ignores = true
ignore_missing_imports = true
disallow_untyped_defs = true
check_untyped_defs = true
- Explicit Return Annotations: Every constructor ends with
-> None:. - Explicit Null Checks:
Optionalor| Nonevariables must undergo explicitif val is not None:validation before usage. - Empty Collections: Empty lists or dicts are explicitly typed at declaration (e.g.,
results: list[str] = []).
I centralize static analysis execution in the project Makefile:
lint:
uv run ruff check .
uv run mypy .
To run the complete quality check suite:
make lint