Enforce a JSON schema¶
import anyinfer as ai
REVIEW = {
"type": "object",
"properties": {
"sentiment": {"type": "string", "enum": ["positive", "neutral", "negative"]},
"score": {"type": "integer", "minimum": 1, "maximum": 5},
"themes": {"type": "array", "items": {"type": "string"}},
},
"required": ["sentiment", "score", "themes"],
"additionalProperties": False,
}
result = client.generate(
"Analyze this review:\n" + review_text,
target="medium",
schema=REVIEW,
repair=ai.Repair(max_attempts=1),
)
analysis = result.structured # already validated against REVIEW
print(analysis["sentiment"], analysis["score"])
Handling failure¶
try:
result = client.generate(prompt, target="medium", schema=REVIEW)
except ai.SchemaViolationError as error:
log.warning("model produced: %s", error.raw_text)
for message in error.errors:
log.warning(" %s", message)
You get both the raw output and the specific validation errors, so you can salvage the response or tighten the prompt.
Pydantic models work¶
No pydantic dependency is added; the model is duck-typed through model_json_schema():
from pydantic import BaseModel
class Review(BaseModel):
sentiment: str
score: int
result = client.generate(prompt, target="medium", schema=Review)
parsed = Review.model_validate(result.structured)
Knowing what happened¶
result.structured_mechanism # "grammar" | "json_schema" | "json_mode" | "prompt"
result.repair_attempts # 0 if the model got it right first time
Both are worth logging in aggregate. A model that frequently needs repair is usually a
prompt problem; a target that unexpectedly reports "prompt" may not be the model you
thought you configured.
Repair costs a request¶
Repair(max_attempts=1) allows one corrective round trip against the same model — never
a different provider (why). Budget for it on
latency-sensitive paths.
Writing schemas that work everywhere¶
Grammar-based engines (llama.cpp, Ollama) compile your schema into a decoding grammar, where a few keywords are expensive:
minLength/maxLengthon strings are stripped for the wire.minItems/maxItemsof 2000 or more are stripped for the wire.
Both are still enforced by client-side validation, so nothing you asked for is lost. But if a local model keeps failing a length constraint, that is why — and clearer prompt wording will help more than a tighter constraint.
Two things that improve results under every mechanism: prefer enum over free-form strings,
and keep nesting shallow.
See structured output for how the mechanism is chosen.