Structured Data

Send a JSON schema with response_format and the reply conforms to it, so your code can parse the answer instead of pulling values back out of prose. On /v1/responses the same schema goes in text.format, covered below.

Quick start with the OpenAI SDK

With the OpenAI SDK, response_format takes the schema directly:

$curl -X POST https://api.asi1.ai/v1/chat/completions \
> -H "Content-Type: application/json" \
> -H "Authorization: Bearer $ASI_ONE_API_KEY" \
> -d '{
> "model": "asi1",
> "messages": [
> {"role": "system", "content": "Extract the requested information as JSON."},
> {"role": "user", "content": "Extract: John Smith is a 32 year old software engineer from San Francisco."}
> ],
> "response_format": {
> "type": "json_schema",
> "json_schema": {
> "name": "person_info",
> "strict": true,
> "schema": {
> "type": "object",
> "properties": {
> "name": {"type": "string"},
> "age": {"type": "integer"},
> "occupation": {"type": "string"},
> "city": {"type": "string"}
> },
> "required": ["name", "age", "occupation", "city"],
> "additionalProperties": false
> }
> }
> }
> }'

If you already use LangChain, with_structured_output takes a Pydantic model and returns a typed object, which is often cleaner than handling response_format yourself. See Using ASI:One with LangChain.


Complex schema example

Nested objects and arrays work the same way, and additionalProperties: false applies to every object in the schema rather than only the outermost one:

1import os
2import json
3from openai import OpenAI
4
5client = OpenAI(
6 api_key=os.getenv("ASI_ONE_API_KEY"),
7 base_url="https://api.asi1.ai/v1"
8)
9
10# Complex schema with nested objects
11response_format = {
12 "type": "json_schema",
13 "json_schema": {
14 "name": "order_summary",
15 "strict": True,
16 "schema": {
17 "type": "object",
18 "additionalProperties": False,
19 "properties": {
20 "order_id": {"type": "string"},
21 "customer": {
22 "type": "object",
23 "additionalProperties": False,
24 "properties": {
25 "name": {"type": "string"},
26 "email": {"type": "string"}
27 },
28 "required": ["name", "email"]
29 },
30 "items": {
31 "type": "array",
32 "items": {
33 "type": "object",
34 "additionalProperties": False,
35 "properties": {
36 "sku": {"type": "string"},
37 "name": {"type": "string"},
38 "quantity": {"type": "integer"},
39 "unit_price": {"type": "number"}
40 },
41 "required": ["sku", "name", "quantity", "unit_price"]
42 }
43 },
44 "total": {"type": "number"},
45 "currency": {"type": "string"}
46 },
47 "required": ["order_id", "customer", "items", "total", "currency"]
48 }
49 }
50}
51
52response = client.chat.completions.create(
53 model="asi1",
54 messages=[
55 {"role": "system", "content": "Generate order data matching the schema."},
56 {"role": "user", "content": "Create a sample order for customer Jane Doe (jane@example.com) with 2 items totaling $150."}
57 ],
58 response_format=response_format
59)
60
61order = json.loads(response.choices[0].message.content)
62print(json.dumps(order, indent=2))

Best practices

Schema design

Use strict: true so the model follows your schema exactly rather than treating it as a strong suggestion. Two things are required alongside it:

  1. additionalProperties must be false on every object in the schema.
  2. Every key in properties must be listed in required. To make a field optional, keep it in required and add "null" to its type.

The same rules apply to tool schemas. See Strict mode for a worked example.

Beyond that:

PracticeWhy
Add description to propertiesTells the model what each field is for
Give examples in the promptImproves accuracy on complex extraction tasks

Validation

1import json
2from pydantic import BaseModel, ValidationError
3
4class Person(BaseModel):
5 name: str
6 age: int
7 city: str
8
9# Always validate model output
10try:
11 content = response.choices[0].message.content
12 data = json.loads(content)
13 person = Person(**data) # Pydantic validation
14except json.JSONDecodeError:
15 print("Model returned invalid JSON")
16except ValidationError as e:
17 print(f"Schema validation failed: {e}")

Always validate and sanitize structured output before using it in production. A schema constrains the shape of the reply, not the truth of its contents.

On the Responses API

Everything above uses response_format on /v1/chat/completions. On /v1/responses the equivalent is text.format, which takes the same JSON schema in a flat shape rather than nesting it under json_schema:

$curl -X POST https://api.asi1.ai/v1/responses \
> -H "Content-Type: application/json" \
> -H "Authorization: Bearer $ASI_ONE_API_KEY" \
> -d '{
> "model": "asi1",
> "input": "Extract the order details from this email: ...",
> "text": {
> "format": {
> "type": "json_schema",
> "name": "order_summary",
> "strict": true,
> "schema": {
> "type": "object",
> "additionalProperties": false,
> "properties": {
> "order_id": {"type": "string"},
> "total": {"type": "number"}
> },
> "required": ["order_id", "total"]
> }
> }
> }
> }'

Both reuse the client built in the quick start above, and name is required on text.format. The schema itself, including strict and additionalProperties, is identical to the Chat Completions version, so you can reuse it as-is.

Next steps

  1. Tool Calling - Let the model call your own functions
  2. Responses API - Where text.format applies, and the rest of that endpoint
  3. Using ASI:One with LangChain - with_structured_output instead of raw JSON schemas
  4. OpenAI Compatibility - Which parameters apply on which endpoint