
Mix client-side and server-side tools on Grok
Mix client-side and server-side tools on Grok
Put server-side agentic tools (web_search, x_search, code_execution, and the rest) in the same tools array as your own functions. xAI runs the server-side calls. When the model picks a client-side function, the request pauses and returns those calls to you — execute them locally, append the results, and continue. Advanced mixed-tool patterns are not supported in the Vercel AI SDK yet; use the xAI SDK or OpenAI SDK.
Loop shape (xAI SDK)
- Define client tools with
tool(...)and include server tools likeweb_search(). - Stream or sample. Classify each call with
get_tool_call_type. - On
client_side_tool, run your function, appendtool_result, and start a follow-up (eitherprevious_response_idwithstore_messages=True, or append encrypted content withuse_encrypted_content=True). - Stop when a turn returns no client-side calls.
import os
import json
from xai_sdk import Client
from xai_sdk.chat import user, tool, tool_result
from xai_sdk.tools import web_search, get_tool_call_type
client = Client(api_key=os.getenv("XAI_API_KEY"))
def get_weather(city: str) -> str:
return f"The weather in {city} is sunny."
tools = [
web_search(),
tool(
name="get_weather",
description="Get the weather for a given city.",
parameters={
"type": "object",
"properties": {
"city": {"type": "string", "description": "The name of the city"},
},
"required": ["city"],
},
),
]
chat = client.chat.create(model="grok-4.6", tools=tools, store_messages=True)
chat.append(
user(
"What is the weather in the base city of the team that won the "
"2025 NBA championship?"
)
)
while True:
client_side_tool_calls = []
for response, chunk in chat.stream():
for tool_call in chunk.tool_calls:
if get_tool_call_type(tool_call) == "client_side_tool":
client_side_tool_calls.append(tool_call)
else:
print(
f"Server-side: {tool_call.function.name} "
f"{tool_call.function.arguments}"
)
if not client_side_tool_calls:
break
chat = client.chat.create(
model="grok-4.6",
tools=tools,
store_messages=True,
previous_response_id=response.id,
)
for tool_call in client_side_tool_calls:
args = json.loads(tool_call.function.arguments)
chat.append(tool_result(get_weather(args["city"])))
print(response.content)
On Responses API, client-side items use type: "function_call"; server-side items use types like web_search_call, x_search_call, code_interpreter_call, file_search_call, mcp_call. Return outputs as function_call_output with the matching call_id.
max_turns with client tools
max_turns caps assistant/server-side tool turns inside one request. A client-side call ends that request and resets the counter on your follow-up. If you set max_turns=5 and the agent uses 3 server-side turns before asking for weather, the next request again allows up to 5 server-side turns.
Recommended ranges when you only use server-side tools: 1–2 for quick lookups, 3–5 for balanced research, 10+ or unset for deep research. Unset uses the server’s global default; at the cap the model answers from what it has so far.
Identify call types
xAI SDK get_tool_call_type |
Who runs it |
|---|---|
client_side_tool |
You |
web_search_tool / x_search_tool / code_execution_tool / collections_search_tool / mcp_tool |
xAI server |
Pitfalls
- Treating every
tool_callsentry as something you must execute — server-side types are already handled. - Reusing the same
max_turnsbudget across a client-side pause — each new request starts fresh. - Expecting Vercel AI SDK parity for this loop — use xAI or OpenAI SDK until that SDK catches up.