Files
agents/contracts/coders/VerifyOutput.py
T
2026-09-22 23:21:59 +03:00

121 lines
3.8 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# contracts/verify_possibility.py
from __future__ import annotations
from dataclasses import dataclass, field
from typing import Literal, Protocol, Annotated, Union, Any
from pydantic import BaseModel, ConfigDict, Field
from contracts.base import _StrictModel
from contracts.coders.GatherOutput import GatherOutput
@dataclass(frozen=True)
class VerifyPossibilityInput:
gather: GatherOutput
def sanitize_for_openai(schema: dict[str, Any]) -> dict[str, Any]:
"""
Recursively rewrite a Pydantic-generated JSON schema into the subset
OpenAI strict mode accepts.
- oneOf -> anyOf (OpenAI rejects oneOf)
- discriminator -> removed (OpenAI rejects it; the const fields
already make branches disjoint)
- adds additionalProperties: false to every object missing it
- strips None defaults
"""
return _walk(schema)
def _walk(node: Any) -> Any:
if isinstance(node, list):
return [_walk(x) for x in node]
if not isinstance(node, dict):
return node
# Recurse into every nested schema container first.
for key in ("properties", "$defs", "definitions"):
if key in node and isinstance(node[key], dict):
node[key] = {k: _walk(v) for k, v in node[key].items()}
for key in ("items", "additionalProperties"):
if key in node and isinstance(node[key], (dict, list)):
node[key] = _walk(node[key])
for key in ("anyOf", "allOf"):
if key in node and isinstance(node[key], list):
node[key] = [_walk(v) for v in node[key]]
# ── the two rewrites that matter ──────────────────────────
if "oneOf" in node and isinstance(node["oneOf"], list):
existing = node.get("anyOf", [])
if not isinstance(existing, list):
existing = []
node["anyOf"] = existing + [_walk(v) for v in node["oneOf"]]
node.pop("oneOf")
node.pop("discriminator", None) # OpenAI doesn't accept it
# ── strict-mode hygiene ───────────────────────────────────
if node.get("type") == "object":
node.setdefault("additionalProperties", False)
props = node.get("properties")
if isinstance(props, dict):
# Strict mode: every property must be in required.
node["required"] = list(props.keys())
if node.get("default", object()) is None:
node.pop("default", None)
return node
class Option(_StrictModel):
label: str = Field(min_length=1, max_length=80)
description: str = Field(min_length=1)
tradeoff: str | None = Field(
description="Optional trade-off; null if none.",
)
class _BaseVerdict(_StrictModel):
reasoning: str = Field(
min_length=1,
description="2–5 sentences explaining the verdict.",
)
confidence: float = Field(
ge=0.0, le=1.0,
description="Your honest probability that the verdict is correct.",
)
class FeasibleVerdict(_BaseVerdict):
verdict: Literal["feasible"]
class AmbiguousVerdict(_BaseVerdict):
verdict: Literal["ambiguous"]
question: str = Field(
min_length=1,
description="Exactly one clarifying question that unblocks planning.",
)
class InfeasibleVerdict(_BaseVerdict):
verdict: Literal["infeasible"]
options: list[Option] = Field(
min_length=1, max_length=3,
description="1–3 concrete alternatives.",
)
Verdict = Annotated[
Union[FeasibleVerdict, AmbiguousVerdict, InfeasibleVerdict],
Field(discriminator="verdict"),
]
class VerifyPossibilityOutput(_StrictModel):
"""Top-level object required by OpenAI strict mode."""
result: Verdict = Field(
description="The feasibility verdict.",
)