Pydantic and Tool Definition with the xAI SDK
On this page
Updated November 3, 2026 based on xAI Function calling documentation and
xai-sdk1.21.0: the SDK offers neither a@client.tool()decorator nor automatic function execution; you build tools withtool()(using a Pydantic schema, for example) and return results withtool_result. Source: Function calling.
Simplify Tool Definition
Writing JSON schemas by hand is verbose and error-prone. Two approaches let you generate tool definitions from your Python code: Pydantic models, which the xAI SDK transforms into tools with its tool() function, and a simple custom decorator that reads your function signatures.
Pydantic Approach
Pydantic is a Python library that lets you define data models with automatic validation. The Grok API can use these models to generate JSON schemas for tools.
from pydantic import BaseModel, Field
from typing import Optional
class WeatherParams(BaseModel):
city: str = Field(description="City name")
unit: Optional[str] = Field(
default="celsius",
description="Temperature unit",
enum=["celsius", "fahrenheit"]
)
This Pydantic model automatically generates the JSON schema equivalent to what you would have written manually. Benefits include:
- Type checking: Python checks types at runtime
- Validation: Pydantic validates data automatically
- Documentation:
Field(description=...)entries serve as documentation - Reusability: the same model works for validation and API documentation
Complex Models
Pydantic handles nested structures, lists, and advanced types:
from typing import List, Optional
from pydantic import BaseModel, Field
class Address(BaseModel):
street: str = Field(description="Street number and name")
city: str = Field(description="City")
zip_code: str = Field(description="ZIP code")
country: str = Field(default="FR", description="ISO country code")
class CreateOrderParams(BaseModel):
product_id: str = Field(description="Product identifier")
quantity: int = Field(description="Ordered quantity", ge=1, le=100)
shipping_address: Address = Field(description="Shipping address")
notes: Optional[str] = Field(
default=None,
description="Special delivery instructions"
)
tags: List[str] = Field(
default=[],
description="Tags for order tracking"
)
The ge (greater or equal) and le (less or equal) constraints translate into JSON Schema constraints that the model enforces.
The xAI SDK tool() Function
The xAI SDK does not take a Pydantic class as-is: it expects a tool definition, which its tool() function builds from a name, a description, and a JSON schema. Pydantic provides exactly this schema via model_json_schema():
from xai_sdk.chat import tool
tools = [
tool(
name="get_weather",
description="Get the current weather for a city",
parameters=WeatherParams.model_json_schema(),
),
]
Using with the SDK
You declare the tool when creating the conversation. The SDK does not execute your functions: when Grok requests a call, your code executes it, adds the result with tool_result, then resumes generation.
import json
from xai_sdk import Client
from xai_sdk.chat import tool_result, user
client = Client(api_key="your-api-key")
chat = client.chat.create(model="grok-4.5", tools=tools)
chat.append(user("Weather in Bordeaux?"))
response = chat.sample()
if response.tool_calls:
chat.append(response)
for call in response.tool_calls:
args = WeatherParams.model_validate_json(call.function.arguments)
result = {"temp": 22, "unit": args.unit, "city": args.city} # your implementation
chat.append(tool_result(json.dumps(result)))
response = chat.sample()
print(response.content)
model_validate_json also checks that the arguments produced by the model match the schema: a missing argument or wrong type raises an error before your function runs.
A Custom Decorator for Speed
The SDK does not provide a @tool decorator. Nothing stops you from writing one in a few lines that reads a function’s signature and docstring:
import inspect
from pydantic import create_model
from xai_sdk.chat import tool
def custom_tool(func):
"""Builds the xAI tool definition from a function's signature and docstring."""
fields = {
name: (p.annotation, ... if p.default is inspect.Parameter.empty else p.default)
for name, p in inspect.signature(func).parameters.items()
}
schema = create_model(func.__name__, **fields).model_json_schema()
description = (inspect.getdoc(func) or func.__name__).split("\n\n")[0]
return tool(name=func.__name__, description=description, parameters=schema)
def get_weather(city: str, unit: str = "celsius") -> dict:
"""Get the current weather for a city (e.g., Paris, Lyon)."""
return {"temp": 22, "unit": unit, "city": city}
tools = [custom_tool(get_weather)]
The function name becomes the tool name, the first paragraph of the docstring becomes the description, and parameters without default values become required. Parameter descriptions, however, are not extracted: for ambiguous parameters, revert to a Pydantic model with Field(description=...).
Comparing the Three Approaches
| Approach | Benefits | Limitations |
|---|---|---|
| Manual JSON schema | Total control, no dependencies | Verbose, typos go undetected |
Pydantic model + tool() | Argument validation, per-field descriptions | One class per tool |
| Custom decorator | Most concise | No per-parameter descriptions, code to maintain |
Best Practices for Descriptions
Regardless of the approach, the description is what the model reads to decide whether to call the tool. State when to use it, what it returns, and the parameter format:
class SearchProductsParams(BaseModel):
query: str = Field(description="Search terms: product name or keywords")
category: str = Field(default="all", description="Category: electronics, clothing, food, or all")
max_results: int = Field(default=10, ge=1, le=50, description="Maximum number of results")
search = tool(
name="search_products",
description=(
"Search for products in the e-commerce catalog. Use when "
"the user looks for a product by name, description, or category. "
"Returns a list of products with price, availability, and link."
),
parameters=SearchProductsParams.model_json_schema(),
)
Key Takeaways
- Pydantic generates JSON schemas from Python models, with argument validation
- The xAI SDK builds tools with
tool(name=..., description=..., parameters=...) - The SDK does not execute your functions: your code does, then returns the result with
tool_result - The SDK has no
@tooldecorator; a custom decorator is possible, at the cost of per-parameter descriptions - Polish your descriptions: the model reads them to choose a tool