GLM 5.2 Structured Output: JSON Mode, Schema Validation, and Reliable Extraction

GLM 5.2 Structured Output: JSON Mode, Schema Validation, and Reliable Extraction

Master GLM 5.2 structured output: enable JSON mode, define output schemas, validate responses, and build reliable data extraction pipelines with Python.

If you have ever shipped an LLM-powered feature only to watch it break because the model returned prose instead of JSON, you know the structured output problem firsthand. GLM 5.2 (model name: glm-4-plus, released by Zhipu AI in May 2025) provides a JSON mode that guarantees valid JSON on every response, and its 1M-token context window means you can extract structure from documents that would break smaller models. This tutorial walks through every layer: enabling JSON mode, defining schemas in the system prompt, validating with Pydantic, and building a retry pipeline that handles edge cases gracefully.

If you want to test the API interactively before writing code, glm5.app lets you explore GLM 5.2 capabilities in a browser without any setup.

Why Unstructured LLM Output Is a Production Problem

A typical LLM call might return:

Sure! Here is the product information you asked for:

**Name:** Widget Pro  
**Price:** $29.99  
**In Stock:** Yes

That text is fine for a human. It is a disaster for a downstream system expecting {"name": "Widget Pro", "price": 29.99, "in_stock": true}. You cannot parse markdown headings reliably. You cannot pass freeform prose to a database insert. You cannot feed it to the next step in a data pipeline without an intermediate parsing layer that will eventually break on some edge case the model decided to be creative about.

This is the central pain point of LLM integration: models are trained to communicate with humans, and downstream systems need precise, machine-readable structure. Solving this reliably — not just "most of the time" — is what separates a prototype from a production feature.

GLM 5.2 addresses this with two mechanisms you can combine:

  1. JSON mode — a runtime flag that forces the model to return valid JSON
  2. System prompt schema definition — a description of exactly which fields you expect

Used together, they give you consistent, parseable output on every call.

Setting Up the Client

GLM 5.2's API is OpenAI-compatible. You use the standard openai Python package, pointing it at Zhipu's base URL. For a full walkthrough of authentication and endpoint details, see the GLM 5.2 API integration guide.

from openai import OpenAI

client = OpenAI(
    api_key="YOUR_ZHIPU_API_KEY",
    base_url="https://open.bigmodel.cn/api/paas/v4/"
)

Get your API key from the Zhipu AI open platform at open.bigmodel.cn. The openai package (any recent version) works without modification — no GLM-specific SDK is required.

Enabling JSON Mode

JSON mode is a single parameter: response_format={"type": "json_object"}. When set, the model guarantees that every response is valid JSON — no markdown code fences, no prose preamble, no trailing commentary.

response = client.chat.completions.create(
    model="glm-4-plus",
    response_format={"type": "json_object"},
    messages=[
        {
            "role": "system",
            "content": "Extract product information as JSON. Return only valid JSON."
        },
        {
            "role": "user",
            "content": "Widget Pro is priced at $29.99 and is currently in stock."
        }
    ]
)

import json
data = json.loads(response.choices[0].message.content)
print(data)
# {"name": "Widget Pro", "price": 29.99, "in_stock": true}

One important note: JSON mode guarantees syntactically valid JSON, but it does not guarantee that the JSON contains the fields you need. The model chooses which keys to include unless you tell it exactly what you want. That is where schema definition comes in.

Defining Your Output Schema in the System Prompt

GLM 5.2 does not yet support OpenAI's response_format.json_schema parameter (which passes a JSON Schema object directly in the API call). Instead, define your schema in the system prompt. This approach works reliably and gives you full control over the description alongside the structure.

SYSTEM_PROMPT = """
You are a data extraction assistant. Extract the following fields from the user's input and return them as a JSON object with exactly these keys:

{
  "product_name": "string — the full product name",
  "price_usd": "number — price in USD, numeric only (no currency symbols)",
  "in_stock": "boolean — true if available, false if not",
  "category": "string — product category if mentioned, otherwise null",
  "description": "string — brief product description if present, otherwise null"
}

Return only the JSON object. Do not include explanation, markdown, or any text outside the JSON.
"""

response = client.chat.completions.create(
    model="glm-4-plus",
    response_format={"type": "json_object"},
    messages=[
        {"role": "system", "content": SYSTEM_PROMPT},
        {"role": "user", "content": user_input}
    ]
)

The key practices here:

  • List every field with its type and a short description
  • Specify what to return when a field is absent (null rather than omitting it)
  • End the system prompt with an explicit instruction to return only JSON

Validating with Pydantic

JSON mode gets you valid JSON. A schema in the system prompt gets you the right keys most of the time. Pydantic validation catches the remaining edge cases — missing required fields, wrong types, values out of acceptable ranges — and gives you typed Python objects instead of raw dictionaries.

from pydantic import BaseModel, Field
from typing import Optional
import json

class ProductExtraction(BaseModel):
    product_name: str
    price_usd: float = Field(ge=0)
    in_stock: bool
    category: Optional[str] = None
    description: Optional[str] = None

def extract_product(text: str) -> ProductExtraction:
    response = client.chat.completions.create(
        model="glm-4-plus",
        response_format={"type": "json_object"},
        messages=[
            {"role": "system", "content": SYSTEM_PROMPT},
            {"role": "user", "content": text}
        ]
    )
    raw = response.choices[0].message.content
    data = json.loads(raw)
    return ProductExtraction(**data)  # raises ValidationError on schema mismatch

product = extract_product("Widget Pro — $29.99, electronics, in stock")
print(product.product_name)   # Widget Pro
print(product.price_usd)      # 29.99
print(product.in_stock)       # True

If ProductExtraction(**data) raises a ValidationError, you know exactly which field is wrong and why, which makes debugging straightforward.

Building a Retry Pipeline

Even with JSON mode and a schema prompt, the model will occasionally return JSON that does not match your expected structure — an extra field, a missing optional that you needed, a price returned as a string instead of a number. A retry loop handles these cases without crashing the pipeline.

import json
from pydantic import ValidationError
import time

def extract_with_retry(text: str, max_attempts: int = 3) -> ProductExtraction:
    messages = [
        {"role": "system", "content": SYSTEM_PROMPT},
        {"role": "user", "content": text}
    ]

    for attempt in range(max_attempts):
        try:
            response = client.chat.completions.create(
                model="glm-4-plus",
                response_format={"type": "json_object"},
                messages=messages
            )
            raw = response.choices[0].message.content
            data = json.loads(raw)
            return ProductExtraction(**data)

        except json.JSONDecodeError as e:
            # JSON mode should prevent this, but handle defensively
            messages.append({"role": "assistant", "content": raw})
            messages.append({
                "role": "user",
                "content": f"Your response was not valid JSON: {e}. Please return only a JSON object."
            })

        except ValidationError as e:
            # Model returned JSON but with wrong structure
            messages.append({"role": "assistant", "content": raw})
            messages.append({
                "role": "user",
                "content": f"The JSON structure was incorrect: {e}. Please match the schema exactly."
            })

        if attempt < max_attempts - 1:
            time.sleep(1)

    raise RuntimeError(f"Failed to extract structured data after {max_attempts} attempts")

This pattern — append the bad response to the conversation, then append a correction prompt — works because the model has full context on what it returned and why it was wrong. Correction rates are high after one retry.

Long-Document Extraction: The 1M Context Advantage

Most models have context windows of 128K tokens or fewer. When your document exceeds that limit, you have to chunk it: split the document, run extraction on each chunk, then merge and deduplicate results. Chunking introduces errors at boundaries where information spans two chunks, adds latency from multiple API calls, and complicates your pipeline significantly.

GLM 5.2's 1,048,576-token context window (1M tokens, approximately 750,000 words) eliminates chunking for the vast majority of real-world documents. Financial reports, legal contracts, research papers, complete codebases, customer support ticket archives — these fit in a single API call. You write one extraction prompt, make one call, and get one structured result.

At $1.40 per million input tokens, processing a 500-page document costs roughly $0.50 in input tokens. For a pipeline that runs on thousands of documents, the cost of getting chunking wrong (missed extractions, duplicate records, boundary errors) typically exceeds the per-token cost of using a larger context.

The practical implementation is identical to the examples above — just pass the full document as the user message content. No special parameters are needed to access the 1M context.

Batch Extraction Pipeline

For processing many documents, combine the extraction function with Python's concurrent.futures to run multiple calls in parallel:

from concurrent.futures import ThreadPoolExecutor, as_completed

def batch_extract(documents: list[str], max_workers: int = 5) -> list[dict]:
    results = []
    failed = []

    with ThreadPoolExecutor(max_workers=max_workers) as executor:
        future_to_doc = {
            executor.submit(extract_with_retry, doc): i
            for i, doc in enumerate(documents)
        }

        for future in as_completed(future_to_doc):
            idx = future_to_doc[future]
            try:
                result = future.result()
                results.append({"index": idx, "data": result.model_dump()})
            except RuntimeError as e:
                failed.append({"index": idx, "error": str(e)})

    results.sort(key=lambda x: x["index"])
    return results, failed

documents = [
    "Widget Pro — $29.99, electronics, in stock",
    "Budget Desk — $149.00, furniture, limited availability",
    "Pro Camera Lens — $899, photography, backordered"
]

results, errors = batch_extract(documents)
for r in results:
    print(r["data"])

Keep max_workers moderate (5–10) to avoid hitting rate limits. The Zhipu API enforces per-minute token limits; if you hit them, add exponential backoff to extract_with_retry.

Common Patterns and Use Cases

Structured output is useful across a wide range of extraction tasks:

Document parsing: extract entities, dates, amounts, and parties from contracts or invoices in a single pass — especially valuable with GLM 5.2's 1M context for long documents.

Classification with metadata: instead of returning a label string, have the model return {"category": "billing", "confidence": "high", "reason": "mentions invoice number and payment terms"}. You get the classification and auditable reasoning in one call.

Form pre-filling: parse unstructured customer submissions and map them to structured form fields before database insertion.

API response normalization: when aggregating data from multiple sources with inconsistent schemas, use GLM 5.2 to normalize each response into your canonical structure.

Event extraction from logs: extract structured events (timestamp, severity, service, message, error code) from raw log lines at scale using batch mode.

For production use, store both the raw model output and the validated Pydantic object. The raw output helps you debug validation failures and audit what the model actually said.

Ready to run these examples against the live model? glm5.app gives you direct access to GLM 5.2 so you can test your prompts and schema definitions without writing any infrastructure code first.

Summary

GLM 5.2 structured output works in three layers:

  1. response_format={"type": "json_object"} — guarantees valid JSON syntax
  2. Schema definition in the system prompt — tells the model which fields to return
  3. Pydantic validation — catches structural mismatches and gives you typed Python objects

Add a retry loop for production reliability, and lean on the 1M context window when your documents are too large for other models. The combination makes GLM 5.2 a practical choice for data extraction pipelines that need consistent, machine-readable output at scale.

Sources

立即开始使用 GLM 5

免费试用 GLM 5 — 推理、编程、智能体和图像生成,一个平台全搞定。