chore: import upstream snapshot with attribution
This commit is contained in:
@@ -0,0 +1,64 @@
|
||||
"""
|
||||
Strict JSON-schema normalization for ``@tool``-derived schemas.
|
||||
|
||||
Strict-mode schemas have two extra constraints beyond ordinary
|
||||
JSON Schema:
|
||||
|
||||
- Every object schema sets ``additionalProperties: false``.
|
||||
- Every property in an object is listed in ``required`` (Python
|
||||
defaults are still applied on the executor side after the LLM
|
||||
emits a value).
|
||||
|
||||
This is the form most major LLM providers accept for
|
||||
function-calling tool schemas without further coercion. Authors
|
||||
who hit a real schema that strict mode breaks can opt out via
|
||||
``@tool(strict=False)``.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import Any
|
||||
|
||||
|
||||
def ensure_strict_schema(schema: dict[str, Any]) -> dict[str, Any]:
|
||||
"""
|
||||
Recursively normalize a JSON schema to strict-mode rules.
|
||||
|
||||
Returns a new dict (does not mutate the input). Recurses into
|
||||
``properties``, ``items``, ``$defs``, and union variants
|
||||
(``anyOf`` / ``oneOf`` / ``allOf``).
|
||||
|
||||
:param schema: A JSON schema dict (typically produced by
|
||||
:func:`pydantic.BaseModel.model_json_schema`).
|
||||
:returns: A new dict with strict-mode constraints applied.
|
||||
"""
|
||||
if not isinstance(schema, dict):
|
||||
return schema
|
||||
|
||||
out: dict[str, Any] = dict(schema)
|
||||
|
||||
# Recurse into nested $defs FIRST so the recursion sees the
|
||||
# normalized definitions when it walks references.
|
||||
if "$defs" in out:
|
||||
out["$defs"] = {k: ensure_strict_schema(v) for k, v in out["$defs"].items()}
|
||||
|
||||
# Recurse into union variants.
|
||||
for union_key in ("anyOf", "oneOf", "allOf"):
|
||||
if union_key in out and isinstance(out[union_key], list):
|
||||
out[union_key] = [ensure_strict_schema(variant) for variant in out[union_key]]
|
||||
|
||||
# Recurse into array items.
|
||||
if "items" in out:
|
||||
out["items"] = ensure_strict_schema(out["items"])
|
||||
|
||||
if out.get("type") == "object":
|
||||
properties = out.get("properties", {}) or {}
|
||||
out["additionalProperties"] = False
|
||||
out["properties"] = {k: ensure_strict_schema(v) for k, v in properties.items()}
|
||||
# Strict mode requires every property to be listed as required.
|
||||
# The default value (if any) still applies on the executor side
|
||||
# via Pydantic — strict mode only governs what the LLM emits.
|
||||
if properties:
|
||||
out["required"] = list(properties.keys())
|
||||
|
||||
return out
|
||||
Reference in New Issue
Block a user