工具
高级用法
本节介绍 Agent 工具调用的高级用法,包括:
使用 Client-side Tool - 将 Server-side Agent Tool 与你自己的 Client-side Tool 结合,实现需要在本地执行的专用功能。
多轮对话 - 在启用 Agent Tool 的对话中跨多个轮次保留上下文,让模型基于之前的研究和工具结果,迭代解决更复杂的问题
启用多个 Tool 的请求 - 发送同时启用多个 Server-side Tool 的请求,让 Web Search、X Search 和 Code Execution 协同完成综合分析
图像集成 - 在启用 Tool 的对话中加入图像,用于视觉分析和理解上下文的搜索
混合使用 Server-side 与 Client-side Tool
你可以将 Server-side Agent Tool(例如 Web Search 和 Code Execution)与自定义 Client-side Tool 结合,构建强大的混合工作流。这样既能利用模型通过 Server-side Tool 进行推理,又能加入在应用本地运行的专用功能。
工作原理
混合使用 Server-side 与 Client-side Tool 时,关键区别在于Server-side Tool 由 xAI 自动执行,而Client-side Tool 需要开发者介入:
在请求中同时包含 Server-side 与 Client-side Tool
xAI 会自动执行所有 Server-side Tool,只要模型决定使用它们(例如 Web Search、Code Execution)
模型调用 Client-side Tool 时,执行会暂停 - xAI 会将 Tool Call 返回给你,而不会代为执行
自行检测并执行 Client-side Tool Call,然后追加执行结果以继续对话
重复此过程,直到模型生成最终响应且不再发起 Client-side Tool Call
理解 max_turns 与 Client-side Tool
使用这个max_turns 参数 与 Server-side、Client-side Tool 混合使用时,需要注意max_turns仅限制单个请求中的 Assistant/Server-side Tool Call 轮次。
当模型决定调用 Client-side Tool 时,Agent 执行会暂停并将控制权交还给你的应用。这意味着:
当前请求结束,你会收到需要执行的 Client-side Tool Call
执行 Client-side Tool 并追加结果后,你需要发起一个新的后续请求
该后续请求会重新开始计算
max_turns次数
换言之,Client-side Tool 调用相当于重置轮次计数器的“检查点”。如果设置 max_turns=5,且 Agent 在请求 Client-side Tool 前执行了 3 轮 Server-side Tool Call,那么在你提供 Client-side Tool 结果后,后续请求仍可再次执行最多 5 轮 Server-side Tool Call。
实际示例
假设有一个本地 Client-side Function get_weather,用于获取指定城市的天气。模型可以结合此 Client-side Tool 与 Web Search Tool,查出 2025 年 NBA 总冠军球队所在城市的天气。
使用 xAI SDK
你可以使用 xai_sdk.tools.get_tool_call_type 检查 response.tool_calls list 中的 Tool Call,判断其是否为 Client-side Tool Call。
更多详情请参阅识别 Tool Call 类型。
导入依赖并定义 Client-side Tool。
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")) # Define client-side tool def get_weather(city: str) -> str: """Get the weather for a given city.""" # In a real app, this would query your database return f"The weather in {city} is sunny." # Tools array with both server-side and client-side tools 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"] }, ), ] model = "grok-4.5"执行 Tool Loop 并延续对话:
你可以使用
previous_response_id,从上一次响应继续对话。# Create chat with both server-side and client-side tools chat = client.chat.create( model=model, 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: {tool_call.function.name} " f"with arguments: {tool_call.function.arguments}" ) if not client_side_tool_calls: break chat = client.chat.create( model=model, tools=tools, store_messages=True, previous_response_id=response.id, ) for tool_call in client_side_tool_calls: print( f"Client-side tool call: {tool_call.function.name} " f"with arguments: {tool_call.function.arguments}" ) args = json.loads(tool_call.function.arguments) result = get_weather(args["city"]) chat.append(tool_result(result)) print(f"Final response: {response.content}")或者,也可以使用 encrypted content 继续对话。
# Create chat with both server-side and client-side tools chat = client.chat.create( model=model, tools=tools, use_encrypted_content=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: {tool_call.function.name} " f"with arguments: {tool_call.function.arguments}" ) chat.append(response) if not client_side_tool_calls: break for tool_call in client_side_tool_calls: print( f"Client-side tool call: {tool_call.function.name} " f"with arguments: {tool_call.function.arguments}" ) args = json.loads(tool_call.function.arguments) result = get_weather(args["city"]) chat.append(tool_result(result)) print(f"Final response: {response.content}")
你会看到类似以下内容的输出:
Server-side tool call: web_search with arguments: {"query":"Who won the 2025 NBA championship?","num_results":5}
Client-side tool call: get_weather with arguments: {"city":"Oklahoma City"}
Final response: The Oklahoma City Thunder won the 2025 NBA championship. The current weather in Oklahoma City is sunny.使用 OpenAI SDK
你可以检查 type 字段;该字段位于 response.output list 的 output entry 中,由此判断某个 Tool Call 是否为 Client-side Tool Call。
更多详情请参阅识别 Tool Call 类型。
导入依赖并定义 Client-side Tool。
import os import json from openai import OpenAI client = OpenAI( api_key=os.getenv("XAI_API_KEY"), base_url="https://api.x.ai/v1", ) # Define client-side tool def get_weather(city: str) -> str: """Get the weather for a given city.""" # In a real app, this would query your database return f"The weather in {city} is sunny." model = "grok-4.5" tools = [ { "type": "function", "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"], }, }, { "type": "web_search", }, ]执行 Tool Loop:
你可以使用
previous_response_id。response = client.responses.create( model=model, input=( "What is the weather in the base city of the team that won the " "2025 NBA championship?" ), tools=tools, ) while True: tool_outputs = [] for item in response.output: if item.type == "function_call": print(f"Client-side tool call: {item.name} with arguments: {item.arguments}") args = json.loads(item.arguments) weather = get_weather(args["city"]) tool_outputs.append( { "type": "function_call_output", "call_id": item.call_id, "output": weather, } ) elif item.type in ( "web_search_call", "x_search_call", "code_interpreter_call", "file_search_call", "mcp_call", ): # Server-side items expose type-specific fields (e.g. action), # not name/arguments like client-side function_call items. details = getattr(item, "action", None) or item.model_dump( exclude={"id", "type", "status"}, exclude_none=True ) print(f"Server-side tool call: {item.type} {details}") if not tool_outputs: break response = client.responses.create( model=model, tools=tools, input=tool_outputs, previous_response_id=response.id, ) print("Final response:", response.output[-1].content[0].text)或者使用 encrypted content
input_list = [ { "role": "user", "content": ( "What is the weather in the base city of the team that won the " "2025 NBA championship?" ), } ] response = client.responses.create( model=model, input=input_list, tools=tools, include=["reasoning.encrypted_content"], ) while True: input_list.extend(response.output) tool_outputs = [] for item in response.output: if item.type == "function_call": print(f"Client-side tool call: {item.name} with arguments: {item.arguments}") args = json.loads(item.arguments) weather = get_weather(args["city"]) tool_outputs.append( { "type": "function_call_output", "call_id": item.call_id, "output": weather, } ) elif item.type in ( "web_search_call", "x_search_call", "code_interpreter_call", "file_search_call", "mcp_call", ): # Server-side items expose type-specific fields (e.g. action), # not name/arguments like client-side function_call items. details = getattr(item, "action", None) or item.model_dump( exclude={"id", "type", "status"}, exclude_none=True ) print(f"Server-side tool call: {item.type} {details}") if not tool_outputs: break input_list.extend(tool_outputs) response = client.responses.create( model=model, input=input_list, tools=tools, include=["reasoning.encrypted_content"], ) print("Final response:", response.output[-1].content[0].text)
保留 Agent 状态的多轮对话
使用 Agent Tool 时,你可能希望后续 prompt 在多轮对话中保留全部 Agent 状态,包括完整的推理、Tool Call 和 Tool Response 历史。Stateful API 可以跨多次交互保留对话上下文。下面介绍两种方案。
远程存储对话历史
你可以选择将对话历史远程存储在 xAI Server 上。每次继续对话时,都可以从希望恢复的上一条响应接续。
只需额外执行两个步骤:
首次发起 Agent 请求时添加参数
store_messages=True。这会指示服务在 xAI Server 上存储完整对话历史,包括模型推理、Server-side Tool Call 及相应结果。创建后续对话时传入
previous_response_id=response.id,其中response是要继续的对话中由chat.sample()或chat.stream()返回的 response。
请注意,后续对话无需使用与初始对话相同的 Tool、模型参数或其他配置,它仍会完整恢复上一次交互的全部 Agent 状态。
import os
from xai_sdk import Client
from xai_sdk.chat import user
from xai_sdk.tools import web_search, x_search
client = Client(api_key=os.getenv("XAI_API_KEY"))
# First turn.
chat = client.chat.create(
model="grok-4.5", # reasoning model
tools=[web_search(), x_search()],
store_messages=True,
)
chat.append(user("What is xAI?"))
print("\\n\\n##### First turn #####\\n")
for response, chunk in chat.stream():
print(chunk.content, end="", flush=True)
print("\\n\\nUsage for first turn:", response.server_side_tool_usage)
# Second turn.
chat = client.chat.create(
model="grok-4.5", # reasoning model
tools=[web_search(), x_search()],
# pass the response id of the first turn to continue the conversation
previous_response_id=response.id,
)
chat.append(user("What is its latest mission?"))
print("\\n\\n##### Second turn #####\\n")
for response, chunk in chat.stream():
print(chunk.content, end="", flush=True)
print("\\n\\nUsage for second turn:", response.server_side_tool_usage)追加加密的 Agent Tool Calling 状态
对于 ZDR(Zero Data Retention)用户或不希望使用上述方案的用户,还有另一种选择:让 xAI Server 除最终内容外,同时向 Client 返回加密的推理和 Tool Output,并在下一轮对话中将这些加密内容作为上下文的一部分。
此方案需要额外执行以下步骤:
首次发起 Agent 请求时添加参数
use_encrypted_content=True。这会指示服务向 Client 返回完整对话历史,包括模型推理(已加密)、Server-side Tool Call 和相应结果(已加密)。将 response 追加到要继续的对话,然后调用
chat.sample()或chat.stream()。
import os
from xai_sdk import Client
from xai_sdk.chat import user
from xai_sdk.tools import web_search, x_search
client = Client(api_key=os.getenv("XAI_API_KEY"))
# First turn.
chat = client.chat.create(
model="grok-4.5", # reasoning model
tools=[web_search(), x_search()],
use_encrypted_content=True,
)
chat.append(user("What is xAI?"))
print("\\n\\n##### First turn #####\\n")
for response, chunk in chat.stream():
print(chunk.content, end="", flush=True)
print("\\n\\nUsage for first turn:", response.server_side_tool_usage)
chat.append(response)
print("\\n\\n##### Second turn #####\\n")
chat.append(user("What is its latest mission?"))
# Second turn.
for response, chunk in chat.stream():
print(chunk.content, end="", flush=True)
print("\\n\\nUsage for second turn:", response.server_side_tool_usage)有关 Stateful Response 的更多详情,请参阅此指南。
Tool 组合
为请求配置多个 Tool 很简单,只需将要启用的 Tool 放入请求的 tools array。模型会根据当前任务智能协调这些 Tool。
推荐的 Tool 组合
以下是针对不同使用场景的常见 Tool 组合:
| 如果你想…… | 可以启用…… | 原因 |
|---|---|---|
| 研究并分析数据 | Web Search + Code Execution | Web Search 收集信息,Code Execution 对其进行分析和可视化 |
| 汇总新闻与社交媒体信息 | Web Search + X Search | 同时覆盖传统网站与社交平台,获取更全面的信息 |
| 从多个 source 中提取洞察 | Web Search + X Search + Code Execution | 从不同 source 收集数据,再计算相关性和趋势 |
| 监测实时讨论 | X Search + Web Search | 结合权威信息追踪社交舆情 |
from xai_sdk.tools import web_search, x_search, code_execution
# Example tool combinations for different scenarios
research_setup = [web_search(), code_execution()]
news_setup = [web_search(), x_search()]
comprehensive_setup = [web_search(), x_search(), code_execution()]在不同场景中使用 Tool 组合
需要在 Internet 上搜索新闻时,可以启用全部 Search Tool:
Web Search Tool
X Search Tool
import os
from xai_sdk import Client
from xai_sdk.chat import user
from xai_sdk.tools import web_search, x_search
client = Client(api_key=os.getenv("XAI_API_KEY"))
chat = client.chat.create(
model="grok-4.5", # reasoning model
tools=[
web_search(),
x_search(),
],
include=["verbose_streaming"],
)
chat.append(user("what is the latest update from xAI?"))
is_thinking = True
for response, chunk in chat.stream():
# View the server-side tool calls as they are being made in real-time
for tool_call in chunk.tool_calls:
print(f"\\nCalling tool: {tool_call.function.name} with arguments: {tool_call.function.arguments}")
if response.usage.reasoning_tokens and is_thinking:
print(f"\\rThinking... ({response.usage.reasoning_tokens} tokens)", end="", flush=True)
if chunk.content and is_thinking:
print("\\n\\nFinal Response:")
is_thinking = False
if chunk.content and not is_thinking:
print(chunk.content, end="", flush=True)
print("\\n\\nCitations:")
print(response.citations)
print("\\n\\nUsage:")
print(response.usage)
print(response.server_side_tool_usage)
print("\\n\\nServer Side Tool Calls:")
print(response.tool_calls)需要从 Internet 收集最新数据并据此计算时,可以选择启用:
Web Search Tool
Code Execution Tool
import os
from xai_sdk import Client
from xai_sdk.chat import user
from xai_sdk.tools import web_search, code_execution
client = Client(api_key=os.getenv("XAI_API_KEY"))
chat = client.chat.create(
model="grok-4.5", # reasoning model
# research_tools
tools=[
web_search(),
code_execution(),
],
include=["verbose_streaming"],
)
chat.append(user("What is the average market cap of the companies with the top 5 market cap in the US stock market today?"))
# sample or stream the response...在上下文中使用图像
你可以使用包含图像的初始对话上下文来启动请求。
在下面的代码示例中,我们先将一张图像传入对话上下文,再发起 Agent 请求。
import os
from xai_sdk import Client
from xai_sdk.chat import image, user
from xai_sdk.tools import web_search, x_search
# Create the client and define the server-side tools to use
client = Client(api_key=os.getenv("XAI_API_KEY"))
chat = client.chat.create(
model="grok-4.5", # reasoning model
tools=[web_search(), x_search()],
include=["verbose_streaming"],
)
# Add an image to the conversation
chat.append(
user(
"Search the internet and tell me what kind of dog is in the image below.",
"And what is the typical lifespan of this dog breed?",
image(
"https://pbs.twimg.com/media/G3B7SweXsAAgv5N?format=jpg&name=900x900"
),
)
)
is_thinking = True
for response, chunk in chat.stream():
# View the server-side tool calls as they are being made in real-time
for tool_call in chunk.tool_calls:
print(f"\\nCalling tool: {tool_call.function.name} with arguments: {tool_call.function.arguments}")
if response.usage.reasoning_tokens and is_thinking:
print(f"\\rThinking... ({response.usage.reasoning_tokens} tokens)", end="", flush=True)
if chunk.content and is_thinking:
print("\\n\\nFinal Response:")
is_thinking = False
if chunk.content and not is_thinking:
print(chunk.content, end="", flush=True)
print("\\n\\nCitations:")
print(response.citations)
print("\\n\\nUsage:")
print(response.usage)
print(response.server_side_tool_usage)
print("\\n\\nServer Side Tool Calls:")
print(response.tool_calls)