xAI’s function calling lets Grok models invoke external tools during a conversation. You define functions, Grok decides when to call them, and your code executes the actual logic. This is the foundation of how Grok Build’s agent capabilities work under the hood.
This guide covers the API directly. If you want to use function calling through Grok Build’s CLI, see the complete guide. For API setup basics, start with the xAI API setup guide.
How Function Calling Works
The flow is:
- You send a message to the API with a list of available tools
- Grok analyzes the message and decides if any tools should be called
- If yes, the API returns a
tool_callsresponse instead of a text response - Your code executes the function and sends the result back
- Grok uses the result to formulate its final response
This loop can repeat multiple times in a single conversation turn.
Basic Example
import requests
import json
API_KEY = "xai-your-key-here"
BASE_URL = "https://api.x.ai/v1"
# Define your tools
tools = [
{
"type": "function",
"function": {
"name": "get_weather",
"description": "Get the current weather for a location",
"parameters": {
"type": "object",
"properties": {
"location": {
"type": "string",
"description": "City name, e.g. 'San Francisco'"
},
"unit": {
"type": "string",
"enum": ["celsius", "fahrenheit"],
"description": "Temperature unit"
}
},
"required": ["location"]
}
}
}
]
# Send the request
response = requests.post(
f"{BASE_URL}/chat/completions",
headers={
"Authorization": f"Bearer {API_KEY}",
"Content-Type": "application/json"
},
json={
"model": "grok-3",
"messages": [
{"role": "user", "content": "What's the weather in Tokyo?"}
],
"tools": tools,
"tool_choice": "auto"
}
)
result = response.json()
message = result["choices"][0]["message"]
# Check if Grok wants to call a function
if message.get("tool_calls"):
tool_call = message["tool_calls"][0]
function_name = tool_call["function"]["name"]
arguments = json.loads(tool_call["function"]["arguments"])
print(f"Grok wants to call: {function_name}({arguments})")
# Execute the function (your implementation)
weather_result = get_weather_from_api(arguments["location"])
# Send the result back
follow_up = requests.post(
f"{BASE_URL}/chat/completions",
headers={
"Authorization": f"Bearer {API_KEY}",
"Content-Type": "application/json"
},
json={
"model": "grok-3",
"messages": [
{"role": "user", "content": "What's the weather in Tokyo?"},
message, # Include the assistant's tool_call message
{
"role": "tool",
"tool_call_id": tool_call["id"],
"content": json.dumps(weather_result)
}
],
"tools": tools
}
)
final_response = follow_up.json()
print(final_response["choices"][0]["message"]["content"])
Parallel Function Calling
Grok can request multiple function calls in a single response. This is critical for performance when tasks are independent:
# Grok might return multiple tool_calls at once
message = result["choices"][0]["message"]
if message.get("tool_calls"):
# Execute all calls (potentially in parallel)
tool_results = []
for tool_call in message["tool_calls"]:
function_name = tool_call["function"]["name"]
arguments = json.loads(tool_call["function"]["arguments"])
# Dispatch to the right function
if function_name == "get_weather":
result = get_weather(arguments["location"])
elif function_name == "get_time":
result = get_time(arguments["timezone"])
tool_results.append({
"role": "tool",
"tool_call_id": tool_call["id"],
"content": json.dumps(result)
})
# Send all results back at once
messages = [
{"role": "user", "content": original_query},
message,
*tool_results
]
Grok will call multiple functions in parallel when:
- The functions are independent (one doesn’t need the other’s result)
- The task benefits from gathering multiple pieces of information
- You haven’t restricted it with
tool_choice
The 200-Tool Limit
xAI’s API supports up to 200 tools per request. This is generous compared to most providers, but you can still hit it in complex agent systems.
Strategies for staying under the limit
1. Dynamic tool selection:
Only include tools relevant to the current task:
def get_relevant_tools(user_message, all_tools):
"""Select tools based on the message content."""
# Simple keyword matching (or use embeddings for better matching)
relevant = []
for tool in all_tools:
if any(keyword in user_message.lower()
for keyword in tool.get("keywords", [])):
relevant.append(tool)
return relevant[:200] # Hard cap
2. Tool namespacing:
Group related tools and only load the relevant group:
tool_groups = {
"database": [create_record, read_record, update_record, delete_record],
"filesystem": [read_file, write_file, list_dir, search_files],
"deployment": [deploy_staging, deploy_prod, rollback, check_status],
"testing": [run_tests, run_coverage, run_lint, run_typecheck]
}
# Load only needed groups
active_tools = tool_groups["database"] + tool_groups["filesystem"]
3. Meta-tools:
Create a tool that lists available tools, then load specific ones on demand:
tools = [
{
"type": "function",
"function": {
"name": "list_available_tools",
"description": "List tools available in a category",
"parameters": {
"type": "object",
"properties": {
"category": {
"type": "string",
"enum": ["database", "files", "deploy", "test"]
}
},
"required": ["category"]
}
}
},
{
"type": "function",
"function": {
"name": "load_tool_group",
"description": "Load a group of tools for use",
"parameters": {
"type": "object",
"properties": {
"group": {"type": "string"}
},
"required": ["group"]
}
}
}
]
Structured Output with Tool Calls
You can combine function calling with structured output to get predictable response formats:
response = requests.post(
f"{BASE_URL}/chat/completions",
headers=headers,
json={
"model": "grok-3",
"messages": messages,
"tools": tools,
"response_format": {
"type": "json_schema",
"json_schema": {
"name": "analysis_result",
"schema": {
"type": "object",
"properties": {
"summary": {"type": "string"},
"confidence": {"type": "number"},
"actions_taken": {
"type": "array",
"items": {"type": "string"}
}
},
"required": ["summary", "confidence", "actions_taken"]
}
}
}
}
)
Building an Agent Loop
A complete agent that uses tools iteratively:
import json
import requests
class GrokAgent:
def __init__(self, api_key, tools, system_prompt=""):
self.api_key = api_key
self.tools = tools
self.messages = []
if system_prompt:
self.messages.append({"role": "system", "content": system_prompt})
self.tool_handlers = {}
def register_handler(self, name, handler):
self.tool_handlers[name] = handler
def run(self, user_message, max_iterations=10):
self.messages.append({"role": "user", "content": user_message})
for _ in range(max_iterations):
response = self._call_api()
message = response["choices"][0]["message"]
self.messages.append(message)
# If no tool calls, we're done
if not message.get("tool_calls"):
return message["content"]
# Execute all tool calls
for tool_call in message["tool_calls"]:
name = tool_call["function"]["name"]
args = json.loads(tool_call["function"]["arguments"])
handler = self.tool_handlers.get(name)
if handler:
result = handler(**args)
else:
result = {"error": f"Unknown tool: {name}"}
self.messages.append({
"role": "tool",
"tool_call_id": tool_call["id"],
"content": json.dumps(result)
})
return "Max iterations reached"
def _call_api(self):
response = requests.post(
"https://api.x.ai/v1/chat/completions",
headers={
"Authorization": f"Bearer {self.api_key}",
"Content-Type": "application/json"
},
json={
"model": "grok-3",
"messages": self.messages,
"tools": self.tools
}
)
return response.json()
Usage:
agent = GrokAgent(
api_key="xai-...",
tools=my_tools,
system_prompt="You are a helpful coding assistant."
)
agent.register_handler("read_file", lambda path: open(path).read())
agent.register_handler("write_file", lambda path, content: write_and_confirm(path, content))
agent.register_handler("run_command", lambda cmd: subprocess.run(cmd, capture_output=True).stdout)
result = agent.run("Add input validation to the user registration endpoint")
print(result)
Error Handling
Handle tool execution failures gracefully:
try:
result = handler(**args)
except Exception as e:
result = {
"error": str(e),
"type": type(e).__name__
}
# Grok will see the error and can retry or adjust its approach
self.messages.append({
"role": "tool",
"tool_call_id": tool_call["id"],
"content": json.dumps(result)
})
Grok handles errors well. When a tool returns an error, it typically:
- Adjusts the arguments and retries
- Tries an alternative approach
- Reports the issue to the user with context
Pricing
Function calling uses the same token pricing as regular API calls:
- Input tokens: $1 per 1M tokens (includes tool definitions)
- Output tokens: $3 per 1M tokens (includes tool call arguments)
Tool definitions count as input tokens on every request. If you have 200 tools with detailed descriptions, that’s significant token overhead. Keep descriptions concise.
FAQ
How does xAI’s function calling compare to OpenAI’s?
The API format is nearly identical (OpenAI-compatible). The main differences: xAI supports up to 200 tools (vs OpenAI’s 128), and Grok tends to be more aggressive about parallel tool calling.
Can I force Grok to call a specific function?
Yes. Set tool_choice to a specific function:
{
"tool_choice": {
"type": "function",
"function": {"name": "get_weather"}
}
}
Or use "tool_choice": "required" to force at least one tool call without specifying which.
Does function calling work with grok-3-mini?
Yes. All Grok 3 models support function calling. Mini is faster but may make less optimal tool selection decisions on complex tasks.
What happens if my tool takes too long to execute?
The API doesn’t enforce a timeout on your tool execution. The timeout is on the API request itself. If your tool takes 30 seconds, that’s fine. Just make sure your HTTP client timeout is set high enough for the follow-up request.
Can I stream responses that include tool calls?
Yes. Tool calls are streamed as delta objects. You’ll receive the function name and arguments incrementally, then execute once the tool call is complete.
Is there a way to see which tools Grok considered but didn’t call?
No. The API only returns the tools Grok decided to call. There’s no “considered tools” field. If you need this for debugging, add a “think_aloud” tool that Grok can call to explain its reasoning.