Tools
Function Calling
Define custom tools that the model can invoke during a conversation. The model requests the call, you execute it locally, and return the result. This enables integration with databases, APIs, and any external system.
With streaming, the function call is returned in whole in a single chunk, not streamed across chunks.
How It Works

- Define tools with a name, description, and JSON schema for parameters
- Include tools in your request
- Model returns a
tool_callwhen it needs external data - Execute the function locally and return the result
- Model continues with your result

Quick Start
import os
import json
from xai_sdk import Client
from xai_sdk.chat import user, tool, tool_result
client = Client(api_key=os.getenv("XAI_API_KEY"))
# Define tools
tools = [
tool(
name="get_temperature",
description="Get current temperature for a location",
parameters={
"type": "object",
"properties": {
"location": {"type": "string", "description": "City name"},
"unit": {"type": "string", "enum": ["celsius", "fahrenheit"], "default": "fahrenheit"}
},
"required": ["location"]
},
),
]
chat = client.chat.create(
model="grok-4-1-fast-reasoning",
tools=tools,
)
chat.append(user("What is the temperature in San Francisco?"))
response = chat.sample()
# Handle tool calls
if response.tool_calls:
chat.append(response)
for tc in response.tool_calls:
args = json.loads(tc.function.arguments)
# Execute your function
result = {"location": args["location"], "temperature": 59, "unit": args.get("unit", "fahrenheit")}
chat.append(tool_result(result))
response = chat.sample()
print(response.content)
Defining Tools with Pydantic
Use Pydantic models for type-safe parameter schemas:
from typing import Literal
from pydantic import BaseModel, Field
from xai_sdk.chat import tool
class TemperatureRequest(BaseModel):
location: str = Field(description="City and state, e.g. San Francisco, CA")
unit: Literal["celsius", "fahrenheit"] = Field("fahrenheit", description="Temperature unit")
class CeilingRequest(BaseModel):
location: str = Field(description="City and state, e.g. San Francisco, CA")
# Generate JSON schema from Pydantic models
tools = [
tool(
name="get_temperature",
description="Get current temperature for a location",
parameters=TemperatureRequest.model_json_schema(),
),
tool(
name="get_ceiling",
description="Get current cloud ceiling for a location",
parameters=CeilingRequest.model_json_schema(),
),
]
Handling Tool Calls
When the model wants to use your tool, execute the function and return the result:
import json
def get_temperature(location: str, unit: str = "fahrenheit") -> dict:
# In production, call a real weather API
temp = 59 if unit == "fahrenheit" else 15
return {"location": location, "temperature": temp, "unit": unit}
def get_ceiling(location: str) -> dict:
return {"location": location, "ceiling": 15000, "unit": "ft"}
tools_map = {
"get_temperature": get_temperature,
"get_ceiling": get_ceiling,
}
chat.append(user("What's the weather in Denver?"))
response = chat.sample()
# Process tool calls
if response.tool_calls:
chat.append(response)
for tool_call in response.tool_calls:
name = tool_call.function.name
args = json.loads(tool_call.function.arguments)
result = tools_map[name](**args)
chat.append(tool_result(result))
response = chat.sample()
print(response.content)
Combining with Built-in Tools
Function calling works alongside built-in agentic tools. The model can use web search, then call your custom function:
from xai_sdk.chat import tool
from xai_sdk.tools import web_search, x_search
tools = [
web_search(), # Built-in: runs on xAI servers
x_search(), # Built-in: runs on xAI servers
tool( # Custom: runs on your side
name="save_to_database",
description="Save research results to the database",
parameters={
"type": "object",
"properties": {
"data": {"type": "string", "description": "Data to save"}
},
"required": ["data"]
},
),
]
chat = client.chat.create(
model="grok-4-1-fast-reasoning",
tools=tools,
)
When mixing tools:
- Built-in tools execute automatically on xAI servers
- Custom tools pause execution and return to you for handling
See Advanced Usage for complete examples with tool loops.
Tool Choice
Control when the model uses tools:
| Value | Behavior |
|---|---|
"auto" | Model decides whether to call a tool (default) |
"required" | Model must call at least one tool |
"none" | Disable tool calling |
{"type": "function", "function": {"name": "..."}} | Force a specific tool |
Parallel Function Calling
By default, parallel function calling is enabled — the model can request multiple tool calls in a single response. Process all of them before continuing:
Python
# response.tool_calls may contain multiple calls
for tool_call in response.tool_calls:
result = tools_map[tool_call.function.name](**json.loads(tool_call.function.arguments))
# Append each result...
Disable with parallel_tool_calls: false in your request.
Tool Schema Reference
| Field | Required | Description |
|---|---|---|
name | Yes | Unique identifier (max 200 tools per request) |
description | Yes | What the tool does — helps the model decide when to use it |
parameters | Yes | JSON Schema defining function inputs |
Parameter Schema
JSON
{
"type": "object",
"properties": {
"location": {
"type": "string",
"description": "City name"
},
"unit": {
"type": "string",
"enum": ["celsius", "fahrenheit"],
"default": "celsius"
}
},
"required": ["location"]
}
Complete Vercel AI SDK Example
The Vercel AI SDK handles tool definition, execution, and the request/response loop automatically:
Javascript
import { xai } from '@ai-sdk/xai';
import { streamText, tool, stepCountIs } from 'ai';
import { z } from 'zod';
const result = streamText({
model: xai.responses('grok-4-1-fast-reasoning'),
tools: {
getCurrentTemperature: tool({
description: 'Get current temperature for a location',
parameters: z.object({
location: z.string().describe('City and state, e.g. San Francisco, CA'),
unit: z.enum(['celsius', 'fahrenheit']).default('fahrenheit'),
}),
execute: async ({ location, unit }) => ({
location,
temperature: unit === 'fahrenheit' ? 59 : 15,
unit,
}),
}),
getCurrentCeiling: tool({
description: 'Get current cloud ceiling for a location',
parameters: z.object({
location: z.string().describe('City and state'),
}),
execute: async ({ location }) => ({
location,
ceiling: 15000,
ceiling_type: 'broken',
unit: 'ft',
}),
}),
},
stopWhen: stepCountIs(5),
prompt: "What's the temperature and cloud ceiling in San Francisco?",
});
for await (const chunk of result.fullStream) {
switch (chunk.type) {
case 'text-delta':
process.stdout.write(chunk.text);
break;
case 'tool-call':
console.log(`Tool call: ${chunk.toolName}`, chunk.args);
break;
case 'tool-result':
console.log(`Tool result: ${chunk.toolName}`, chunk.result);
break;
}
}