Skip to main content
Perceptron supports constrained decoding so replies always adhere to the shape you expect. Provide a Pydantic class, JSON Schema or regex pattern and the inference server ensures the outputs adhere to the structure without additional prompt engineering.

Helpers overview

  • pydantic_format(MyModel, name=None, strict=None): Generate the schema from a Pydantic v2 model; optionally override the schema name; set strict=True for strict enforcement.
  • json_schema_format(schema, name="response", strict=None): Wrap a JSON Schema dict to enforce keys, enums, and structure—set strict=True for strict enforcement.
  • regex_format(pattern): Constrain short outputs (yes/no, IDs, emails) with a regex instead of a full schema.
All helpers feed into the same response_format argument available on perceive, async_perceive, Client.generate, and Client.stream.

Pydantic-backed responses

Use Pydantic models to define the target shape, then pass pydantic_format so the response is guaranteed to match. You can parse the result directly into the model for type-safe handling.
from perceptron import image, perceive, pydantic_format, text
from pydantic import BaseModel, Field
from typing import Literal


class SceneAnalysis(BaseModel):
  scene_type: str = Field(description="outdoor, indoor, urban, nature")
  main_subjects: list[str]
  mood: Literal["calm", "energetic", "dramatic", "peaceful", "tense"]
  time_of_day: Literal["morning", "afternoon", "evening", "night", "unknown"]


@perceive(response_format=pydantic_format(SceneAnalysis))
def analyze_scene(path: str):
  return image(path) + text("Analyze this scene and return JSON.")


result = analyze_scene("scene.jpg")
analysis = SceneAnalysis.model_validate_json(result.text)
print(analysis.time_of_day)
Strict mode (strict=True) pushes providers to reject extra fields and invalid enums. When omitted, provider defaults apply.

Raw JSON Schema

If you already have a schema, pass it directly via json_schema_format.
from perceptron import image, json_schema_format, perceive, text

schema = {
  "type": "object",
  "properties": {
    "title": {"type": "string"},
    "keywords": {"type": "array", "items": {"type": "string"}},
    "confidence": {"type": "number"}
  },
  "required": ["title", "keywords", "confidence"],
  "additionalProperties": False
}

@perceive(response_format=json_schema_format(schema, name="summary", strict=True))
def summarize(path: str):
  return image(path) + text("Summarize the image and include keywords.")


resp = summarize("frame.png")
print(resp.text)  # Valid JSON matching the schema

Regex constraints for short answers

Use regex_format when a compact pattern is enough (e.g., binary decisions, IDs, or numeric ranges).
from perceptron import image, perceive, regex_format, text

@perceive(response_format=regex_format(r"yes|no"))
def quick_check(path: str):
  return image(path) + text("Is there a stop sign in this photo? Respond yes or no.")


print(quick_check("frame.jpg").text)

Streaming with structure

Client.stream and async_perceive(stream=True) support the same response_format. Streamed deltas arrive as text; the final event still respects your schema so you can parse once the stream completes.
Schema compilation can add latency the first time you use a new shape. Reuse the same schema object or Pydantic model to benefit from provider-side caching.