
Combine structured outputs with tools on the Grok API
Combine structured outputs with tools on the Grok API
On supported Grok 4 family models, you can let the model call tools and still return a typed JSON object that matches your schema. That covers server-side tools (web_search, x_search, code execution) and your own client-side functions. Plain structured output without tools lives in Return structured JSON from the Grok API.
Agentic tools + typed result
Ask for a search, then parse into a Pydantic (or Zod) model. Responses API:
curl https://api.x.ai/v1/responses \
-H "Content-Type: application/json" \
-H "Authorization: Bearer $XAI_API_KEY" \
-d '{
"model": "grok-4.6",
"input": "Find the latest machine-checked proof of the four color theorem.",
"tools": [{ "type": "web_search" }],
"text": {
"format": {
"type": "json_schema",
"name": "proof_info",
"strict": true,
"schema": {
"type": "object",
"properties": {
"name": { "type": "string" },
"authors": { "type": "string" },
"year": { "type": "string" },
"summary": { "type": "string" }
},
"required": ["name", "authors", "year", "summary"],
"additionalProperties": false
}
}
}
}'
Python SDK (chat.parse after tools are enabled):
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
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")
client = Client(api_key=os.getenv("XAI_API_KEY"))
chat = client.chat.create(model="grok-4.6", tools=[web_search()])
chat.append(user("Find the latest machine-checked proof of the four color theorem."))
response, proof = chat.parse(ProofInfo)
print(proof.name, proof.year, proof.summary)
OpenAI SDK on https://api.x.ai/v1: client.responses.parse(..., tools=[{"type": "web_search"}], text_format=ProofInfo).
Client-side functions + typed result
Run your function loop until there are no more tool_calls, then request the structured parse on the final turn:
import os, json
from pydantic import BaseModel, Field
from xai_sdk import Client
from xai_sdk.chat import tool, tool_result, user
class CollatzResult(BaseModel):
starting_number: int
steps: int
def collatz_steps(n: int) -> int:
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 Collatz steps to reach 1",
parameters={
"type": "object",
"properties": {"n": {"type": "integer"}},
"required": ["n"],
},
)
client = Client(api_key=os.getenv("XAI_API_KEY"))
chat = client.chat.create(model="grok-4.6", tools=[collatz_tool])
chat.append(user("Use collatz_steps for 20250709."))
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)
chat.append(tool_result(str(collatz_steps(args["n"]))))
response, result = chat.parse(CollatzResult)
print(result.starting_number, result.steps)
Tool argument objects always follow the tool’s input JSON Schema (strict is always on for tools). Your final answer schema is separate — that is what parse / json_schema constrains.
Pitfalls
- Structured outputs with tools need a supported Grok 4 family model (use
grok-4.6unless a model page says otherwise). additionalPropertiesdefaults tofalseon schemas. Set ittrueonly when you need extra keys.- Streaming structured JSON: pass the Pydantic model as
response_formatand usestream(), thenModel.model_validate_json(response.content)after the stream finishes — see the structured-outputs guide.