模型能力

Structured Outputs

Structured Outputs 允许 API 以特定格式返回响应,例如返回与你定义的 schema 匹配的 JSON object,而不是自由格式文本。此功能特别适合文档解析、实体提取和报告生成等任务。

有两种方式可以要求模型返回 structured outputs。

主要且最灵活的方法是使用response_format参数。将response_format.type设置为"json_schema",并在response_format.json_schema下提供 schema,即可精确定义模型应返回的 structured output。该参数还接受"json_object",用于不需要特定结构时返回任意格式正确的 JSON;也接受"text"(默认值),用于返回自由格式文本。

第二种方式是通过 tool calling。定义工具后,xAI 模型始终会生成严格符合工具 input JSON Schema 的 tool call arguments(strict flag 会隐式地始终设为true)。

你可以使用以下库定义 schema,例如PydanticZod

JSON Schema 支持

我们支持 JSON Schema 的实用子集。按照 Draft 2020-12 编写的 schema 效果最佳,同时也接受 Draft-07 schema。

支持的类型

  • string

  • number

  • integer

  • boolean

  • null

  • enum

  • const

  • array

  • object

  • anyOf

  • oneOf(行为与anyOf

  • allOf(仅支持单个 subschema;多个 subschema 请参阅Best-effort keywords

  • $ref / $defs(仅支持非循环引用)

要使字段可为 null,请使用 type array({"type": ["string", "null"]})或anyOf variant,其中包含null。未列在required中的字段会被视为 optional。

String formats

format keyword 会对以下值强制执行:

date · time · date-time · email · uuid · ipv4 · ipv6 · uri

其他format值会被接受但不会强制执行(请参阅Best-effort keywords)。

约束限制

output engine 会在以下阈值范围内强制执行这些约束。超过限制的 schema 仍会被接受,但是否符合约束取决于模型行为。

Keyword保证上限
minimum / maximum / exclusiveMinimum / exclusiveMaximum无限制
minLength / maxLength2,048
minItems / maxItems256
minProperties / maxProperties64

Best-effort keywords

这些 keyword 会被接受,但不会在结构上强制执行;模型会处理它们,并且实际表现可靠,但不能保证输出满足这些约束。如果需要严格符合约束,建议自行验证。

  • not

  • if / then / else

  • allOf 包含多个 subschema

  • format值未列在String formats

  • 超出上述限制的约束

被拒绝的 schema

以下情况会返回400错误:

  • enumanyOf不包含任何 variant

  • property 的 schema 为truefalse

  • maxContains / minContains

  • itemsarray(tuple validation 请使用prefixItems

Regex 支持(pattern

在 string field 上使用pattern keyword 时,我们支持 ECMAScript Regular Expressions(ECMA-262)的实用子集。

支持:

  • 字面量和 character class([abc][a-z][^abc]

  • .(匹配任何 Unicode codepoint,包括换行符)

  • Alternation |、grouping (...)以及 non-capturing group (?:...)

  • Quantifier *+?和 repetition range {n}{n,}{n,m}

  • Shorthand class \d\w\s(以及它们的否定形式\D\W\S

  • 常见 escape:\n\t\r\f\xHH\uHHHH\u{HHHHHH}

不支持:

  • Backreference(\1\k<name>等)

  • Unicode property escape(\p{L}\P{Letter}

  • Word boundary(\b\B

  • Lookahead 和 lookbehind((?=...)(?<=...)等)

  • Inline modifier((?i)(?m)等)

  • Conditional expression 和其他高级构造

与标准 JavaScript RegExp 的语义差异:

  • .会匹配换行符

  • ^$隐式的,pattern 始终匹配整个字符串(无需手动添加)

  • Capturing group (...)没有语义效果(其行为与 non-capturing group 相同)

  • regex 会在支持 Unicode 的情况下求值

示例:发票解析

Structured Outputs 的常见使用场景是解析原始文档。例如,发票包含供应商详情、金额和日期等结构化数据,但从原始文本中提取这些数据容易出错。Structured Outputs 可确保提取的数据与预定义 schema 匹配。

假设你希望从发票中提取以下数据:

  • 供应商名称和地址

  • 发票编号和日期

  • 明细项(描述、数量、价格)

  • 总金额和货币

我们将使用 structured outputs,让 Grok 为这些数据生成 strongly typed JSON。

步骤 1:定义 Schema

可以使用PydanticZod定义 schema。

from datetime import date
from enum import Enum

from pydantic import BaseModel, Field

class Currency(str, Enum):
    USD = "USD"
    EUR = "EUR"
    GBP = "GBP"

class LineItem(BaseModel):
    description: str = Field(description="Description of the item or service")
    quantity: int = Field(description="Number of units", ge=1)
    unit_price: float = Field(description="Price per unit", ge=0)

class Address(BaseModel):
    street: str = Field(description="Street address")
    city: str = Field(description="City")
    postal_code: str = Field(description="Postal/ZIP code")
    country: str = Field(description="Country")

class Invoice(BaseModel):
    vendor_name: str = Field(description="Name of the vendor")
    vendor_address: Address = Field(description="Vendor's address")
    invoice_number: str = Field(description="Unique invoice identifier")
    invoice_date: date = Field(description="Date the invoice was issued")
    line_items: list[LineItem] = Field(description="List of purchased items/services")
    total_amount: float = Field(description="Total amount due", ge=0)
    currency: Currency = Field(description="Currency of the invoice")

步骤 2:准备 Prompts

System Prompt

system prompt 指示模型从文本中提取发票数据。由于 schema 已单独定义,prompt 可以专注于任务,无需显式指定输出 JSON 中所需的字段。

Text

Given a raw invoice, carefully analyze the text and extract the relevant invoice data into JSON format.

示例发票文本

Text

Vendor: Acme Corp, 123 Main St, Springfield, IL 62704
Invoice Number: INV-2025-001
Date: 2025-02-10
Items:
- Widget A, 5 units, $10.00 each
- Widget B, 2 units, $15.00 each
Total: $80.00 USD

步骤 3:最终代码

使用 SDK 的 structured outputs 功能解析发票。

import os
from datetime import date
from enum import Enum

from pydantic import BaseModel, Field

from xai_sdk import Client
from xai_sdk.chat import system, user

# Pydantic Schemas

class Currency(str, Enum):
    USD = "USD"
    EUR = "EUR"
    GBP = "GBP"

class LineItem(BaseModel):
    description: str = Field(description="Description of the item or service")
    quantity: int = Field(description="Number of units", ge=1)
    unit_price: float = Field(description="Price per unit", ge=0)

class Address(BaseModel):
    street: str = Field(description="Street address")
    city: str = Field(description="City")
    postal_code: str = Field(description="Postal/ZIP code")
    country: str = Field(description="Country")

class Invoice(BaseModel):
    vendor_name: str = Field(description="Name of the vendor")
    vendor_address: Address = Field(description="Vendor's address")
    invoice_number: str = Field(description="Unique invoice identifier")
    invoice_date: date = Field(description="Date the invoice was issued")
    line_items: list[LineItem] = Field(description="List of purchased items/services")
    total_amount: float = Field(description="Total amount due", ge=0)
    currency: Currency = Field(description="Currency of the invoice")

client = Client(api_key=os.getenv("XAI_API_KEY"))
chat = client.chat.create(model="grok-4.5")

chat.append(system("Given a raw invoice, carefully analyze the text and extract the invoice data into JSON format."))
chat.append(
user("""
Vendor: Acme Corp, 123 Main St, Springfield, IL 62704
Invoice Number: INV-2025-001
Date: 2025-02-10
Items: - Widget A, 5 units, $10.00 each - Widget B, 2 units, $15.00 each
Total: $80.00 USD
""")
)

# The parse method returns a tuple of the full response object as well as the parsed pydantic object.

response, invoice = chat.parse(Invoice)
assert isinstance(invoice, Invoice)

# Can access fields of the parsed invoice object directly

print(invoice.vendor_name)
print(invoice.invoice_number)
print(invoice.invoice_date)
print(invoice.line_items)
print(invoice.total_amount)
print(invoice.currency)

# Can also access fields from the raw response object such as the content.

# In this case, the content is the JSON schema representation of the parsed invoice object

print(response.content)

步骤 4:Type-safe Output

使用受支持的 schema 功能时,输出将是 type-safe 的,并遵循 input schema。

JSON

{
  "vendor_name": "Acme Corp",
  "vendor_address": {
    "street": "123 Main St",
    "city": "Springfield",
    "postal_code": "62704",
    "country": "IL"
  },
  "invoice_number": "INV-2025-001",
  "invoice_date": "2025-02-10",
  "line_items": [
    { "description": "Widget A", "quantity": 5, "unit_price": 10.0 },
    { "description": "Widget B", "quantity": 2, "unit_price": 15.0 }
  ],
  "total_amount": 80.0,
  "currency": "USD"
}

结合 Tools 使用 Structured Outputs

可以将 structured outputs 与 tool calling 结合使用,从工具增强的 query 中获得 type-safe 响应。此方式同时适用于:

  • Agentic tool calling:由模型自主编排的服务器端工具,例如 web search、X search 和 code execution。

  • Function calling:由用户提供的工具,你可以定义自定义函数并自行处理工具执行。

这种组合支持模型使用工具收集信息,并以可预测的 strongly typed 格式返回结果。

示例:结合 Structured Output 的 Agentic Tools

此示例使用 web search 查找某个主题的最新研究,并将结构化数据提取到 schema 中:

from pydantic import BaseModel, Field

class ProofInfo(BaseModel):
    name: str = Field(description="Name of the proof or paper")
    authors: str = Field(description="Authors of the proof")
    year: str = Field(description="Year published")
    summary: str = Field(description="Brief summary of the approach")
import os
from pydantic import BaseModel, Field

from xai_sdk import Client
from xai_sdk.chat import user
from xai_sdk.tools import web_search

# ProofInfo schema defined above

client = Client(api_key=os.getenv("XAI_API_KEY"))
chat = client.chat.create(
    model="grok-4.5",
    tools=[web_search()],
)

chat.append(user("Find the latest machine-checked proof of the four color theorem."))

response, proof = chat.parse(ProofInfo)

print(f"Name: {proof.name}")
print(f"Authors: {proof.authors}")
print(f"Year: {proof.year}")
print(f"Summary: {proof.summary}")

示例:结合 Structured Output 的客户端 Tools

此示例使用客户端 function tool 计算 Collatz sequence 的步骤,并以结构化格式返回结果:

from pydantic import BaseModel, Field

class CollatzResult(BaseModel):
    starting_number: int = Field(description="The input number")
    steps: int = Field(description="Number of steps to reach 1")
import os
import json
from pydantic import BaseModel, Field

from xai_sdk import Client
from xai_sdk.chat import tool, tool_result, user

# CollatzResult schema defined above

def collatz_steps(n: int) -> int:
    """Returns the number of steps for n to reach 1 in the Collatz sequence."""
    steps = 0
    while n != 1:
        n = n // 2 if n % 2 == 0 else 3 * n + 1
        steps += 1
    return steps

collatz_tool = tool(
    name="collatz_steps",
    description="Compute the number of steps for a number to reach 1 in the Collatz sequence",
    parameters={
        "type": "object",
        "properties": {
            "n": {"type": "integer", "description": "The starting number"},
        },
        "required": ["n"],
    },
)

client = Client(api_key=os.getenv("XAI_API_KEY"))
chat = client.chat.create(
    model="grok-4.5",
    tools=[collatz_tool],
)

chat.append(user("Use the collatz_steps tool to find how many steps it takes for 20250709 to reach 1."))

# Handle tool calls until we get a final response
while True:
    response = chat.sample()
    
    if not response.tool_calls:
        break
    
    chat.append(response)
    for tc in response.tool_calls:
        args = json.loads(tc.function.arguments)
        result = collatz_steps(args["n"])
        chat.append(tool_result(str(result)))

# Parse the final response into structured output
response, result = chat.parse(CollatzResult)

print(f"Starting number: {result.starting_number}")
print(f"Steps to reach 1: {result.steps}")

替代方案:结合response_format使用sample()stream()

使用 xAI Python SDK 时,还有另一种检索 structured outputs 的方式。无需使用parse()method,可以在创建 chat 时将 Pydantic model 直接传给response_format参数,然后使用sample()stream()获取响应。

工作原理

将 Pydantic model 传给response_format时,SDK 会自动:

  1. 将 Pydantic model 转换为 JSON schema

  2. 约束模型输出,使其符合该 schema

  3. response.content

然后,你需要手动将 JSON string 解析为 Pydantic model instance。

主要区别

方式Method返回值解析
使用parse()chat.parse(Model)由以下内容组成的 tuple:(Response, Model)自动,SDK 会为你解析
使用response_formatchat.sample()chat.stream()Response,其中包含 JSON string手动,需要自行解析response.content

何时使用每种方式

  • 在以下情况使用parse():希望获得最简单、最便捷的自动解析体验

  • 在以下情况使用response_format + sample()stream()适用于以下情况:

    • 希望对解析过程拥有更多控制

    • 需要在解析前处理原始 JSON string

    • 希望将 streaming 与 structured outputs 结合使用

    • 正在与需要使用sample()stream()

使用response_format

Python

import os
from datetime import date
from enum import Enum

from pydantic import BaseModel, Field
from xai_sdk import Client
from xai_sdk.chat import system, user

# Pydantic Schemas
class Currency(str, Enum):
    USD = "USD"
    EUR = "EUR"
    GBP = "GBP"


class LineItem(BaseModel):
    description: str = Field(description="Description of the item or service")
    quantity: int = Field(description="Number of units", ge=1)
    unit_price: float = Field(description="Price per unit", ge=0)


class Address(BaseModel):
    street: str = Field(description="Street address")
    city: str = Field(description="City")
    postal_code: str = Field(description="Postal/ZIP code")
    country: str = Field(description="Country")


class Invoice(BaseModel):
    vendor_name: str = Field(description="Name of the vendor")
    vendor_address: Address = Field(description="Vendor's address")
    invoice_number: str = Field(description="Unique invoice identifier")
    invoice_date: date = Field(description="Date the invoice was issued")
    line_items: list[LineItem] = Field(description="List of purchased items/services")
    total_amount: float = Field(description="Total amount due", ge=0)
    currency: Currency = Field(description="Currency of the invoice")


client = Client(api_key=os.getenv("XAI_API_KEY"))

# Pass the Pydantic model to response_format instead of using parse()
chat = client.chat.create(
    model="grok-4.5",
    response_format=Invoice,  # Pass the Pydantic model here
)

chat.append(system("Given a raw invoice, carefully analyze the text and extract the invoice data into JSON format."))
chat.append(
    user("""
Vendor: Acme Corp, 123 Main St, Springfield, IL 62704
Invoice Number: INV-2025-001
Date: 2025-02-10
Items: - Widget A, 5 units, $10.00 each - Widget B, 2 units, $15.00 each
Total: $80.00 USD
""")
)

# Use sample() instead of parse() - returns Response object
response = chat.sample()

# The response.content is a valid JSON string conforming to your schema
print(response.content)
# Output: {"vendor_name": "Acme Corp", "vendor_address": {...}, ...}

# Manually parse the JSON string into your Pydantic model
invoice = Invoice.model_validate_json(response.content)
assert isinstance(invoice, Invoice)

# Access fields of the parsed invoice object
print(invoice.vendor_name)
print(invoice.invoice_number)
print(invoice.total_amount)

结合 Structured Outputs 使用 Streaming

还可以使用stream()使用response_format获取 streaming structured output。各个 chunk 会逐步构建 JSON string:

Python

import os

from pydantic import BaseModel, Field
from xai_sdk import Client
from xai_sdk.chat import system, user


class Summary(BaseModel):
    title: str = Field(description="A brief title")
    key_points: list[str] = Field(description="Main points from the text")
    sentiment: str = Field(description="Overall sentiment: positive, negative, or neutral")


client = Client(api_key=os.getenv("XAI_API_KEY"))

chat = client.chat.create(
    model="grok-4.5",
    response_format=Summary,  # Pass the Pydantic model here
)

chat.append(system("Analyze the following text and provide a structured summary."))
chat.append(user("The new product launch exceeded expectations with record sales..."))


# Stream the response - chunks contain partial JSON
for response, chunk in chat.stream():
    print(chunk.content, end="", flush=True)


# Parse the complete JSON string into your model
summary = Summary.model_validate_json(response.content)
print(f"Title: {summary.title}")
print(f"Sentiment: {summary.sentiment}")