Capabilities
Structured outputs
When you need a machine-readable answer rather than prose — extracting fields, classifying, building an API response — structured outputs make the model return valid JSON every time, so there's no fragile string-parsing on your end. Pendra enforces this with a grammar at generation time, not just a prompt instruction, so the output is guaranteed to parse.
JSON mode
Set response_format to
{ "type": "json_object" } and the reply is guaranteed to
be syntactically valid JSON. Use this when you want JSON but don't need a
specific shape — describe the fields you want in your prompt.
JSON Schema
For a guaranteed shape, pass
{ "type": "json_schema", "json_schema": { ... } }. The
model is constrained to produce output matching your schema — the right
keys, the right types — so you can deserialise straight into your own
types.
Exact conformance with strict
Add "strict": true next to your schema (as in the example
below) and the reply matches it exactly: every field you list in
required is present, each key appears once, and nothing
outside the schema can appear. Nested objects, arrays and
enum values are held to the same standard, so the reply
deserialises into a typed struct or model without a validation step of
your own.
Without strict, the shape is best-effort: keys and value
types come from your schema, but an optional field the model skips — or a
required one it forgets — won't be corrected for you. Reach for
strict whenever you're parsing the result into a fixed type;
leave it off when you'd rather the model answer with whichever fields it
actually knows.
Enforced exactly: the object's shape and key set, which fields are
required, value types, enum and const values,
array item types and lengths (minItems /
maxItems), and schemas that reference their own definitions —
so a schema generated from a Pydantic model or a Zod object works as you'd
expect.
Some things can't be enforced: a union (anyOf,
oneOf, allOf — including an optional or nullable
field), a $ref to another document, and value constraints
like pattern, format, minLength or
minimum. Setting additionalProperties to a
schema (rather than false) is also not honoured — the reply
is held to exactly the keys you listed, so it still matches your schema,
but the extra keys you allowed won't appear. The rest of the schema is
enforced in every case, and because you asked for exact conformance and
didn't get all of it, the reply tells you so:
{
"choices": [ ... ],
"pendra": {
"notice": {
"code": "strict_schema_not_enforced",
"message": "Part of this schema could not be enforced, so the reply may not match it exactly ..."
}
}
}
The pendra field is an addition of ours, so OpenAI-compatible
clients ignore it — check for it when you want to know whether the
guarantee held. When streaming, it arrives as its own event near the end
of the stream.
If response_format is malformed — a type other
than text, json_object, or
json_schema, or { "type": "json_schema" }
without its json_schema object — the request is rejected with
a 422 before it runs, so a bad constraint fails fast instead
of quietly returning unconstrained text.
from pendra import Pendra
client = Pendra()
response = client.chat.completions.create(
model="qwen3.6:27b",
messages=[{"role": "user", "content": "Extract the name and age from: Sara is 34."}],
response_format={
"type": "json_schema",
"json_schema": {
"name": "person",
"strict": True,
"schema": {
"type": "object",
"properties": {
"name": {"type": "string"},
"age": {"type": "integer"},
},
"required": ["name", "age"],
"additionalProperties": False,
},
},
},
)
print(response.choices[0].message.content) # {"name": "Sara", "age": 34}
{ "name": "Sara", "age": 34 }