模型能力
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,例如Pydantic或Zod。
JSON Schema 支持
我们支持 JSON Schema 的实用子集。按照 Draft 2020-12 编写的 schema 效果最佳,同时也接受 Draft-07 schema。
支持的类型
stringnumberintegerbooleannullenumconstarrayobjectanyOfoneOf(行为与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 / maxLength | 2,048 |
minItems / maxItems | 256 |
minProperties / maxProperties | 64 |
Best-effort keywords
这些 keyword 会被接受,但不会在结构上强制执行;模型会处理它们,并且实际表现可靠,但不能保证输出满足这些约束。如果需要严格符合约束,建议自行验证。
notif/then/elseallOf包含多个 subschemaformat值未列在String formats超出上述限制的约束
被拒绝的 schema
以下情况会返回400错误:
enum或anyOf不包含任何 variantproperty 的 schema 为
true或falsemaxContains/minContainsitemsarray(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
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 中所需的字段。
Given a raw invoice, carefully analyze the text and extract the relevant invoice data into JSON format.示例发票文本
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。
{
"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 会自动:
将 Pydantic model 转换为 JSON schema
约束模型输出,使其符合该 schema
在
response.content
然后,你需要手动将 JSON string 解析为 Pydantic model instance。
主要区别
| 方式 | Method | 返回值 | 解析 |
|---|---|---|---|
使用parse() | chat.parse(Model) | 由以下内容组成的 tuple:(Response, Model) | 自动,SDK 会为你解析 |
使用response_format | chat.sample()或chat.stream() | Response,其中包含 JSON string | 手动,需要自行解析response.content |
何时使用每种方式
在以下情况使用
parse():希望获得最简单、最便捷的自动解析体验在以下情况使用
response_format+sample()或stream()适用于以下情况:希望对解析过程拥有更多控制
需要在解析前处理原始 JSON string
希望将 streaming 与 structured outputs 结合使用
正在与需要使用
sample()或stream()
使用response_format
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:
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}")