Function Calling
大模型無法訪問即時資料和外部系統。Function Calling 允許模型調用外部工具(API、資料庫、自訂函數等),擷取資訊或執行操作,突破模型自身能力的限制。
工作原理
Function Calling 通過應用程式與大模型之間的多步驟互動實現:
發起第一次模型調用
應用程式向大模型發送使用者問題和可用工具清單。
接收模型的工具調用指令(工具名稱與入參)
若模型判斷需要調用外部工具,返回JSON格式的指令,指定函數名稱與入參。
若模型判斷無需調用工具,會返回自然語言格式的回複。
在應用端運行工具
應用程式執行指定工具,擷取輸出結果。
發起第二次模型調用
將工具輸出結果添加到訊息數組(messages),再次調用模型。
接收來自模型的最終響應
模型綜合工具輸出與使用者問題,產生自然語言回複。
工作流程示意圖:
支援的模型
千問
文本產生模型
千問Max:Qwen3.7-Max系列、Qwen3.6-Max系列、Qwen3-Max系列、Qwen-Max系列
千問Plus:Qwen3.7-Plus系列、Qwen3.6-Plus系列、Qwen3.5-Plus系列、Qwen-Plus系列
千問Flash:Qwen3.6-Flash系列、Qwen3.5-Flash系列、Qwen-Flash系列
千問Coder:Qwen3-Coder系列、Qwen2.5-Coder系列、Qwen-Coder系列
千問Turbo:Qwen-Turbo系列
Qwen3.6開源系列
Qwen3.5開源系列
Qwen3開源系列
Qwen2.5開源系列
多模態模型
千問VL: Qwen3-VL-Plus系列、 Qwen3-VL-Flash系列
千問Omni:Qwen3.5-Omni-Plus系列、Qwen3.5-Omni-Flash系列、Qwen3-Omni-Flash系列
千問Omni-Realtime:Qwen3.5-Omni-Plus-Realtime系列、Qwen3.5-Omni-Flash-Realtime系列
Qwen3-VL 開源系列
DeepSeek
deepseek-v4-pro
deepseek-v4-flash
deepseek-v3.2
deepseek-v3.2-exp(非思考模式)
deepseek-v3.1(非思考模式)
deepseek-r1
deepseek-r1-0528
deepseek-v3
GLM
調用 GLM 系列模型的 Function Calling 時,需在請求中傳入 extra_body={"tool_stream": True},否則模型不會返回 tool_calls,工具調用不生效。
glm-5.2
glm-5.1
glm-5
glm-4.7
glm-4.6
Kimi
kimi-k2.6
kimi-k2.5
kimi-k2-thinking
Moonshot-Kimi-K2-Instruct
MiniMax
MiniMax-M2.5
快速開始
您需要已擷取API Key並配置API Key到環境變數。如果通過 OpenAI SDK或 DashScope SDK調用,還需安裝SDK。
以下樣本示範天氣查詢情境的完整 Function Calling 流程。
OpenAI 相容
from openai import OpenAI
from datetime import datetime
import json
import os
import random
client = OpenAI(
# 各地區的API Key不同。擷取API Key:https://www.alibabacloud.com/help/zh/model-studio/get-api-key
# 若沒有配置環境變數,請用百鍊API Key將下行替換為:api_key="sk-xxx",
api_key=os.getenv("DASHSCOPE_API_KEY"),
# 如果使用北京地區的模型,需要將base_url替換為:https://{WorkspaceId}.cn-beijing.maas.aliyuncs.com/compatible-mode/v1
base_url="https://{WorkspaceId}.ap-southeast-1.maas.aliyuncs.com/compatible-mode/v1",
)
# 類比使用者問題
USER_QUESTION = "新加坡天氣咋樣"
# 定義工具列表
tools = [
{
"type": "function",
"function": {
"name": "get_current_weather",
"description": "當你想查詢指定城市的天氣時非常有用。",
"parameters": {
"type": "object",
"properties": {
"location": {
"type": "string",
"description": "城市或縣區,比如新加坡、紐約等。",
}
},
"required": ["location"],
},
},
},
]
# 類比天氣查詢工具
def get_current_weather(arguments):
weather_conditions = ["晴天", "多雲", "雨天"]
random_weather = random.choice(weather_conditions)
location = arguments["location"]
return f"{location}今天是{random_weather}。"
# 封裝模型響應函數
def get_response(messages):
completion = client.chat.completions.create(
model="qwen3.6-plus",
extra_body={"enable_thinking": False},
messages=messages,
tools=tools,
)
return completion
messages = [{"role": "user", "content": USER_QUESTION}]
response = get_response(messages)
assistant_output = response.choices[0].message
if assistant_output.content is None:
assistant_output.content = ""
messages.append(assistant_output)
# 如果不需要調用工具,直接輸出內容
if assistant_output.tool_calls is None:
print(f"無需調用天氣查詢工具,直接回複:{assistant_output.content}")
else:
# 進入工具調用迴圈
while assistant_output.tool_calls is not None:
tool_call = assistant_output.tool_calls[0]
tool_call_id = tool_call.id
func_name = tool_call.function.name
arguments = json.loads(tool_call.function.arguments)
print(f"正在調用工具 [{func_name}],參數:{arguments}")
# 執行工具
tool_result = get_current_weather(arguments)
# 構造工具返回資訊
tool_message = {
"role": "tool",
"tool_call_id": tool_call_id,
"content": tool_result, # 保持原始工具輸出
}
print(f"工具返回:{tool_message['content']}")
messages.append(tool_message)
# 再次調用模型,擷取總結後的自然語言回複
response = get_response(messages)
assistant_output = response.choices[0].message
if assistant_output.content is None:
assistant_output.content = ""
messages.append(assistant_output)
print(f"助手最終回複:{assistant_output.content}")import OpenAI from 'openai';
// 初始化用戶端
const openai = new OpenAI({
apiKey: process.env.DASHSCOPE_API_KEY,
// 如果使用北京地區的模型,需要將baseURL替換為:https://{WorkspaceId}.cn-beijing.maas.aliyuncs.com/compatible-mode/v1
baseURL: "https://{WorkspaceId}.ap-southeast-1.maas.aliyuncs.com/compatible-mode/v1",
});
// 定義工具列表
const tools = [
{
type: "function",
function: {
name: "get_current_weather",
description: "當你想查詢指定城市的天氣時非常有用。",
parameters: {
type: "object",
properties: {
location: {
type: "string",
description: "城市或縣區,比如新加坡、紐約等。",
},
},
required: ["location"],
},
},
},
];
// 類比天氣查詢工具
const getCurrentWeather = (args) => {
const weatherConditions = ["晴天", "多雲", "雨天"];
const randomWeather = weatherConditions[Math.floor(Math.random() * weatherConditions.length)];
const location = args.location;
return `${location}今天是${randomWeather}。`;
};
// 封裝模型響應函數
const getResponse = async (messages) => {
const response = await openai.chat.completions.create({
model: "qwen3.6-plus",
enable_thinking: false,
messages: messages,
tools: tools,
});
return response;
};
const main = async () => {
const input = "新加坡天氣咋樣";
let messages = [
{
role: "user",
content: input,
}
];
let response = await getResponse(messages);
let assistantOutput = response.choices[0].message;
// 確保 content 不是 null
if (!assistantOutput.content) assistantOutput.content = "";
messages.push(assistantOutput);
// 判斷是否需要調用工具
if (!assistantOutput.tool_calls) {
console.log(`無需調用天氣查詢工具,直接回複:${assistantOutput.content}`);
} else {
// 進入工具調用迴圈
while (assistantOutput.tool_calls) {
const toolCall = assistantOutput.tool_calls[0];
const toolCallId = toolCall.id;
const funcName = toolCall.function.name;
const funcArgs = JSON.parse(toolCall.function.arguments);
console.log(`正在調用工具 [${funcName}],參數:`, funcArgs);
// 執行工具
const toolResult = getCurrentWeather(funcArgs);
// 構造工具返回資訊
const toolMessage = {
role: "tool",
tool_call_id: toolCallId,
content: toolResult,
};
console.log(`工具返回:${toolMessage.content}`);
messages.push(toolMessage);
// 再次調用模型擷取自然語言總結
response = await getResponse(messages);
assistantOutput = response.choices[0].message;
if (!assistantOutput.content) assistantOutput.content = "";
messages.push(assistantOutput);
}
console.log(`助手最終回複:${assistantOutput.content}`);
}
};
// 啟動程式
main().catch(console.error);DashScope
import os
from dashscope import MultiModalConversation
import dashscope
import json
import random
# 以下為華北2(北京)地區的URL,調用時請將WorkspaceId替換為真實的業務空間ID,各地區的URL不同。
dashscope.base_http_api_url = 'https://{WorkspaceId}.cn-beijing.maas.aliyuncs.com/api/v1'
# 1. 定義工具列表
tools = [
{
"type": "function",
"function": {
"name": "get_current_weather",
"description": "當你想查詢指定城市的天氣時非常有用。",
"parameters": {
"type": "object",
"properties": {
"location": {
"type": "string",
"description": "城市或縣區,比如新加坡、紐約等。",
}
},
"required": ["location"],
},
},
}
]
# 2. 類比天氣查詢工具
def get_current_weather(arguments):
weather_conditions = ["晴天", "多雲", "雨天"]
random_weather = random.choice(weather_conditions)
location = arguments["location"]
return f"{location}今天是{random_weather}。"
# 3. 封裝模型響應函數
def get_response(messages):
response = MultiModalConversation.call(
# 各地區的API Key不同。擷取API Key:https://www.alibabacloud.com/help/zh/model-studio/get-api-key
# 如果沒有配置環境變數,請將下行替換為api_key="sk-xxx"
api_key=os.getenv("DASHSCOPE_API_KEY"),
# 此處以多模態模型 qwen3.6-plus 為例。若調用 qwen3.6-max-preview、qwen-plus 等純文字模型需使用純文字模型介面,參見https://www.alibabacloud.com/help/model-studio/qwen-api-via-dashscope
model="qwen3.6-plus",
enable_thinking=False,
messages=messages,
tools=tools,
result_format="message",
)
return response
# 4. 初始化對話歷史
messages = [
{
"role": "user",
"content": [{"text": "新加坡天氣咋樣"}]
}
]
# 5. 第一次調用模型
response = get_response(messages)
assistant_output = response.output.choices[0].message
messages.append(assistant_output)
# 6. 判斷是否需要調用工具
if "tool_calls" not in assistant_output or not assistant_output["tool_calls"]:
print(f"無需調用工具,直接回複:{assistant_output['content']}")
else:
# 7. 進入工具調用迴圈
# 迴圈條件:只要最新的模型回複中包含工具調用請求
while "tool_calls" in assistant_output and assistant_output["tool_calls"]:
tool_call = assistant_output["tool_calls"][0]
# 解析工具調用的資訊
func_name = tool_call["function"]["name"]
arguments = json.loads(tool_call["function"]["arguments"])
tool_call_id = tool_call.get("id") # 擷取 tool_call_id
print(f"正在調用工具 [{func_name}],參數:{arguments}")
# 執行對應的工具函數
tool_result = get_current_weather(arguments)
# 構造工具返回資訊
tool_message = {
"role": "tool",
"content": tool_result,
"tool_call_id": tool_call_id
}
print(f"工具返回:{tool_message['content']}")
messages.append(tool_message)
# 再次調用模型,讓模型基於工具結果進行回複
response = get_response(messages)
assistant_output = response.output.choices[0].message
messages.append(assistant_output)
# 8. 輸出最終的自然語言回複
content = assistant_output["content"]
if isinstance(content, list) and content:
content = content[0].get("text", "") if isinstance(content[0], dict) else str(content[0])
print(f"助手最終回複:{content}")import com.alibaba.dashscope.aigc.multimodalconversation.MultiModalConversation;
import com.alibaba.dashscope.aigc.multimodalconversation.MultiModalConversationParam;
import com.alibaba.dashscope.aigc.multimodalconversation.MultiModalConversationResult;
import com.alibaba.dashscope.common.MultiModalMessage;
import com.alibaba.dashscope.common.Role;
import com.alibaba.dashscope.protocol.Protocol;
import com.alibaba.dashscope.exception.UploadFileException;
import com.alibaba.dashscope.exception.NoApiKeyException;
import com.alibaba.dashscope.tools.FunctionDefinition;
import com.alibaba.dashscope.tools.ToolCallBase;
import com.alibaba.dashscope.tools.ToolCallFunction;
import com.alibaba.dashscope.tools.ToolFunction;
import com.alibaba.dashscope.utils.JsonUtils;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Random;
public class Main {
/**
* 從 MultiModalMessage 的 content 中提取純文字。
* content 格式為 List<Map<String, String>>,例如:[{text=天氣是晴天}]
*/
@SuppressWarnings("unchecked")
public static String getTextContent(Object content) {
if (content instanceof List) {
for (Object item : (List<?>) content) {
if (item instanceof Map) {
Object text = ((Map<String, Object>) item).get("text");
if (text != null) return text.toString();
}
}
}
return content != null ? content.toString() : "";
}
/**
* 定義工具的本地實現。
* @param arguments 模型傳入的、包含工具所需參數的JSON字串。
* @return 工具執行後的結果字串。
*/
public static String getCurrentWeather(String arguments) {
try {
// 模型提供的參數是JSON格式的,需要我們手動解析。
ObjectMapper objectMapper = new ObjectMapper();
JsonNode argsNode = objectMapper.readTree(arguments);
String location = argsNode.get("location").asText();
// 用隨機結果來類比真實的API調用或商務邏輯。
List<String> weatherConditions = Arrays.asList("晴天", "多雲", "雨天");
String randomWeather = weatherConditions.get(new Random().nextInt(weatherConditions.size()));
return location + "今天是" + randomWeather + "。";
} catch (Exception e) {
// 異常處理,確保程式健壯性。
return "無法解析地點參數。";
}
}
public static void main(String[] args) {
try {
// 向模型描述(註冊)我們的工具。
String weatherParamsSchema =
"{\"type\":\"object\",\"properties\":{\"location\":{\"type\":\"string\",\"description\":\"城市或縣區,比如新加坡、紐約等。\"}},\"required\":[\"location\"]}";
FunctionDefinition weatherFunction = FunctionDefinition.builder()
.name("get_current_weather") // 工具的唯一標識名,必須與本地實現對應。
.description("當你想查詢指定城市的天氣時非常有用。") // 清晰的描述能協助模型更好地決定何時使用該工具。
.parameters(JsonUtils.parseString(weatherParamsSchema).getAsJsonObject())
.build();
// 以下為華北2(北京)地區的URL,調用時請將WorkspaceId替換為真實的業務空間ID,各地區的URL不同。
MultiModalConversation conv = new MultiModalConversation(Protocol.HTTP.getValue(), "https://{WorkspaceId}.cn-beijing.maas.aliyuncs.com/api/v1");
String userInput = "新加坡天氣咋樣";
List<MultiModalMessage> messages = new ArrayList<>();
messages.add(MultiModalMessage.builder().role(Role.USER.getValue())
.content(Arrays.asList(Collections.singletonMap("text", userInput))).build());
// 首次調用模型。將使用者的請求和我們定義好的工具列表一同發送給模型。
MultiModalConversationParam param = MultiModalConversationParam.builder()
.model("qwen3.6-plus") //此處以多模態模型 qwen3.6-plus 為例。若調用 qwen3.6-max-preview、qwen-plus 等純文字模型需使用純文字模型介面,參見https://www.alibabacloud.com/help/model-studio/qwen-api-via-dashscope
.enableThinking(false)
.apiKey(System.getenv("DASHSCOPE_API_KEY")) // 從環境變數中擷取API Key。各地區的API Key不同。擷取API Key:https://www.alibabacloud.com/help/zh/model-studio/get-api-key
.messages(messages) // 傳入當前的對話歷史。
.tools(Arrays.asList(ToolFunction.builder().function(weatherFunction).build())) // 傳入可用的工具列表。
.build();
MultiModalConversationResult result = conv.call(param);
MultiModalMessage assistantOutput = result.getOutput().getChoices().get(0).getMessage();
messages.add(assistantOutput); // 將模型的首次回複也加入到對話歷史中。
// 檢查模型的回複,判斷它是否請求調用工具。
if (assistantOutput.getToolCalls() == null || assistantOutput.getToolCalls().isEmpty()) {
// 情況A:模型沒有調用工具,而是直接給出了回答。
System.out.println("無需調用天氣查詢工具,直接回複:" + getTextContent(assistantOutput.getContent()));
} else {
// 情況B:模型決定調用工具。
// 使用 while 迴圈可以處理模型連續調用多次工具的情境。
while (assistantOutput.getToolCalls() != null && !assistantOutput.getToolCalls().isEmpty()) {
ToolCallBase toolCall = assistantOutput.getToolCalls().get(0);
// 從模型的回複中解析出工具調用的具體資訊(要調用的函數名、參數)。
ToolCallFunction functionCall = (ToolCallFunction) toolCall;
String funcName = functionCall.getFunction().getName();
String arguments = functionCall.getFunction().getArguments();
System.out.println("正在調用工具 [" + funcName + "],參數:" + arguments);
// 根據工具名,在本地執行對應的Java方法。
String toolResult = getCurrentWeather(arguments);
// 構造一個 role 為 "tool" 的訊息,其中包含工具的執行結果。
MultiModalMessage toolMessage = MultiModalMessage.builder()
.role("tool")
.toolCallId(toolCall.getId())
.content(Arrays.asList(Collections.singletonMap("text", toolResult)))
.build();
System.out.println("工具返回:" + toolResult);
messages.add(toolMessage); // 將工具的返回結果也加入到對話歷史中。
// 再次調用模型。
param.setMessages((List) messages);
result = conv.call(param);
assistantOutput = result.getOutput().getChoices().get(0).getMessage();
messages.add(assistantOutput);
}
// 列印模型經過總結後,產生的最終回複。
System.out.println("助手最終回複:" + getTextContent(assistantOutput.getContent()));
}
} catch (NoApiKeyException | UploadFileException e) {
System.err.println("錯誤: " + e.getMessage());
} catch (Exception e) {
e.printStackTrace();
}
}
}運行後得到如下輸出:
正在調用工具 [get_current_weather],參數:{'location': '新加坡'}
工具返回:新加坡今天是多雲。
助手最終回複:新加坡今天是多雲的天氣。如何使用
Function Calling 支援兩種傳入工具資訊的方式:
方式一:通過 tools 參數傳入(推薦)
參見如何使用,按照定義工具、建立 messages 數組、發起 Function Calling、運行工具函數、大模型總結工具函數輸出的步驟調用。
方式二:通過 System Message 傳入
通過 tools 參數傳入效果最佳,服務端會自動適配最優 prompt 模板。如使用 Qwen 模型且不期望使用 tools 參數,參見通過 System Message 傳入工具資訊。
以下以 OpenAI 相容介面為例,通過 tools 參數分步驟介紹 Function Calling 的詳細用法。
假設業務情境會收到天氣查詢與時間查詢兩類問題。
1. 定義工具
工具串連大模型與外部服務,首先需定義工具。
1.1. 建立工具函數
建立兩個工具函數:天氣查詢工具與時間查詢工具。
天氣查詢工具
接收
arguments參數,arguments格式為{"location": "查詢的地點"}。工具的輸出為字串,格式為:“{位置}今天是{天氣}”。為了便於示範,此處定義的天氣查詢工具並不真正查詢天氣,會從晴天、多雲、雨天隨機播放。在實際業務中可使用如 高德天氣查詢 等工具進行替換。
時間查詢工具
時間查詢工具不需要輸入參數。工具的輸出為字串,格式為:
“目前時間:{查詢到的時間}。”。如果使用 Node.js,請運行
npm install date-fns安裝擷取時間的工具包 date-fns:
## 步驟1:定義工具函數
# 添加匯入random模組
import random
from datetime import datetime
# 類比天氣查詢工具。返回結果樣本:“北京今天是雨天。”
def get_current_weather(arguments):
# 定義備選的天氣條件列表
weather_conditions = ["晴天", "多雲", "雨天"]
# 隨機播放一個天氣條件
random_weather = random.choice(weather_conditions)
# 從 JSON 中提取位置資訊
location = arguments["location"]
# 返回格式化的天氣資訊
return f"{location}今天是{random_weather}。"
# 查詢目前時間的工具。返回結果樣本:“目前時間:2024-04-15 17:15:18。“
def get_current_time():
# 擷取當前日期和時間
current_datetime = datetime.now()
# 格式化當前日期和時間
formatted_time = current_datetime.strftime('%Y-%m-%d %H:%M:%S')
# 返回格式化後的目前時間
return f"目前時間:{formatted_time}。"
# 測試載入器函數並輸出結果,運行後續步驟時可以去掉以下四句測試代碼
print("測試載入器輸出:")
print(get_current_weather({"location": "上海"}))
print(get_current_time())
print("\n")// 步驟1:定義工具函數
// 匯入時間查詢工具
import { format } from 'date-fns';
function getCurrentWeather(args) {
// 定義備選的天氣條件列表
const weatherConditions = ["晴天", "多雲", "雨天"];
// 隨機播放一個天氣條件
const randomWeather = weatherConditions[Math.floor(Math.random() * weatherConditions.length)];
// 從 JSON 中提取位置資訊
const location = args.location;
// 返回格式化的天氣資訊
return `${location}今天是${randomWeather}。`;
}
function getCurrentTime() {
// 擷取當前日期和時間
const currentDatetime = new Date();
// 格式化當前日期和時間
const formattedTime = format(currentDatetime, 'yyyy-MM-dd HH:mm:ss');
// 返回格式化後的目前時間
return `目前時間:${formattedTime}。`;
}
// 測試載入器函數並輸出結果,運行後續步驟時可以去掉以下四句測試代碼
console.log("測試載入器輸出:")
console.log(getCurrentWeather({location:"上海"}));
console.log(getCurrentTime());
console.log("\n")運行工具後,得到輸出:
測試載入器輸出:
上海今天是多雲。
目前時間:2025-01-08 20:21:45。1.2 建立 tools 數組
人類選擇工具前需瞭解工具的功能、使用情境和輸入參數。大模型同理——模型依據這些資訊選擇合適的工具。按以下JSON格式提供工具資訊。
| 對於天氣查詢工具來說,工具描述資訊的格式如下: |
發起 Function Calling 前,在代碼中定義工具資訊數組(tools),包含每個工具的函數名、描述和參數定義。該數組在後續請求時作為參數傳入。
# 請將以下代碼粘貼到步驟1代碼後
## 步驟2:建立 tools 數組
tools = [
{
"type": "function",
"function": {
"name": "get_current_time",
"description": "當你想知道現在的時間時非常有用。",
"parameters": {}
}
},
{
"type": "function",
"function": {
"name": "get_current_weather",
"description": "當你想查詢指定城市的天氣時非常有用。",
"parameters": {
"type": "object",
"properties": {
"location": {
"type": "string",
"description": "城市或縣區,比如北京市、杭州市、餘杭區等。",
}
},
"required": ["location"]
}
}
}
]
tool_name = [tool["function"]["name"] for tool in tools]
print(f"建立了{len(tools)}個工具,為:{tool_name}\n")// 請將以下代碼粘貼到步驟1代碼後
// 步驟2:建立 tools 數組
const tools = [
{
type: "function",
function: {
name: "get_current_time",
description: "當你想知道現在的時間時非常有用。",
parameters: {}
}
},
{
type: "function",
function: {
name: "get_current_weather",
description: "當你想查詢指定城市的天氣時非常有用。",
parameters: {
type: "object",
properties: {
location: {
type: "string",
description: "城市或縣區,比如北京市、杭州市、餘杭區等。",
}
},
required: ["location"]
}
}
}
];
const toolNames = tools.map(tool => tool.function.name);
console.log(`建立了${tools.length}個工具,為:${toolNames.join(', ')}\n`);2. 建立messages數組
Function Calling 通過 messages 數組向大模型傳入指令與上下文。發起調用前,messages 數組需包含 System Message 和 User Message。
System Message
儘管在建立 tools 數組時已描述了工具的功能和使用情境,但在 System Message 中進一步強調何時調用工具,通常能提高工具調用的準確率。當前情境可將 System Prompt 設定為:
你是一個很有協助的助手。如果使用者提問關於天氣的問題,請調用 ‘get_current_weather’ 函數;
如果使用者提問關於時間的問題,請調用‘get_current_time’函數。
請以友好的語氣回答問題。User Message
User Message 用於傳入使用者提問的問題。假設使用者提問“上海天氣”,此時的 messages 數組為:
# 步驟3:建立messages數組
# 請將以下代碼粘貼到步驟2 代碼後
# 文本產生模型的 User Message樣本
messages = [
{
"role": "system",
"content": """你是一個很有協助的助手。如果使用者提問關於天氣的問題,請調用 ‘get_current_weather’ 函數;
如果使用者提問關於時間的問題,請調用‘get_current_time’函數。
請以友好的語氣回答問題。""",
},
{
"role": "user",
"content": "上海天氣"
}
]
# 多模態模型的 User Message樣本
# messages=[
# {
# "role": "system",
# "content": """你是一個很有協助的助手。如果使用者提問關於天氣的問題,請調用 ‘get_current_weather’ 函數;
# 如果使用者提問關於時間的問題,請調用‘get_current_time’函數。
# 請以友好的語氣回答問題。""",
# },
# {"role": "user",
# "content": [{"type": "image_url","image_url": {"url": "https://img.alicdn.com/imgextra/i2/O1CN01FbTJon1ErXVGMRdsN_!!6000000000405-0-tps-1024-683.jpg"}},
# {"type": "text", "text": "根據映像上的地點,查詢該地點當前天氣"}]},
# ]
print("messages 數組建立完成\n") // 步驟3:建立messages數組
// 請將以下代碼粘貼到步驟2 代碼後
const messages = [
{
role: "system",
content: "你是一個很有協助的助手。如果使用者提問關於天氣的問題,請調用 ‘get_current_weather’ 函數; 如果使用者提問關於時間的問題,請調用‘get_current_time’函數。請以友好的語氣回答問題。",
},
{
role: "user",
content: "上海天氣"
}
];
// 多模態模型的 User Message樣本,
// const messages: [{
// role: "user",
// content: [{type: "image_url", image_url: {"url": "https://help-static-aliyun-doc.aliyuncs.com/file-manage-files/zh-CN/20241022/emyrja/dog_and_girl.jpeg"}},
// {type: "text", text: "圖中描繪的是什麼景象?"}]
// }];
console.log("messages 數組建立完成\n");由於備選工具包含天氣查詢與時間查詢,也可提問關於目前時間的問題。
3. 發起 Function Calling
將建立好的 tools 與 messages 傳入大模型,即可發起一次 Function Calling。大模型會判斷是否調用工具。若調用,則返回該工具的函數名與參數。
支援的模型參見 支援的模型 。
# 步驟4:發起 function calling
# 請將以下代碼粘貼到步驟3 代碼後
from openai import OpenAI
import os
client = OpenAI(
# 各地區的API Key不同。擷取API Key:https://www.alibabacloud.com/help/zh/model-studio/get-api-key
# 若沒有配置環境變數,請用百鍊API Key將下行替換為:api_key="sk-xxx",
api_key=os.getenv("DASHSCOPE_API_KEY"),
# 如果使用北京地區的模型,需要將base_url替換為:https://{WorkspaceId}.cn-beijing.maas.aliyuncs.com/compatible-mode/v1
base_url="https://{WorkspaceId}.ap-southeast-1.maas.aliyuncs.com/compatible-mode/v1",
)
def function_calling():
completion = client.chat.completions.create(
# 此處以qwen3.6-plus為例,可按需更換模型名稱。模型列表:https://www.alibabacloud.com/help/zh/model-studio/getting-started/models
model="qwen3.6-plus",
extra_body={"enable_thinking": False},
messages=messages,
tools=tools
)
print("返回對象:")
print(completion.choices[0].message.model_dump_json())
print("\n")
return completion
print("正在發起function calling...")
completion = function_calling()// 步驟4:發起 function calling
// 請將以下代碼粘貼到步驟3 代碼後
import OpenAI from "openai";
const openai = new OpenAI(
{
// 各地區的API Key不同。擷取API Key:https://www.alibabacloud.com/help/zh/model-studio/get-api-key
// 若沒有配置環境變數,請用百鍊API Key將下行替換為:apiKey: "sk-xxx",
apiKey: process.env.DASHSCOPE_API_KEY,
// 如果使用北京地區的模型,需要將baseURL替換為:https://{WorkspaceId}.cn-beijing.maas.aliyuncs.com/compatible-mode/v1
baseURL: "https://{WorkspaceId}.ap-southeast-1.maas.aliyuncs.com/compatible-mode/v1"
}
);
async function functionCalling() {
const completion = await openai.chat.completions.create({
model: "qwen3.6-plus", // 此處以qwen3.6-plus為例,可按需更換模型名稱。模型列表:https://www.alibabacloud.com/help/zh/model-studio/getting-started/models
enable_thinking: false,
messages: messages,
tools: tools
});
console.log("返回對象:");
console.log(JSON.stringify(completion.choices[0].message));
console.log("\n");
return completion;
}
const completion = await functionCalling();由於使用者提問為上海天氣,大模型指定需要使用的工具函數名稱為:"get_current_weather",函數的入參為:"{\"location\": \"上海\"}"。
{
"content": "",
"refusal": null,
"role": "assistant",
"audio": null,
"function_call": null,
"tool_calls": [
{
"id": "call_6596dafa2a6a46f7a217da",
"function": {
"arguments": "{\"location\": \"上海\"}",
"name": "get_current_weather"
},
"type": "function",
"index": 0
}
]
}需要注意,如果問題被大模型判斷為無需使用工具,會通過content參數直接回複。在輸入“你好”時,tool_calls參數為空白,返回對象格式為:
{
"content": "你好!有什麼可以協助你的嗎?如果你有關於天氣或者時間的問題,我特別擅長回答。",
"refusal": null,
"role": "assistant",
"audio": null,
"function_call": null,
"tool_calls": null
}如果tool_calls參數為空白,可使程式直接返回content,無需運行以下步驟。
若希望每次發起 Function Calling 後大模型都可以選擇指定工具,請參見 強制工具調用 。
4. 運行工具函數
運行工具函數將模型決策轉化為實際操作。
運行工具函數的過程由您的計算環境而非大模型來完成。
大模型僅輸出字串格式,運行工具函數前需對工具函數名稱與入參分別解析。
工具函數
建立一個工具函數名稱到工具函數實體的映射
function_mapper,將返回的工具函數字串映射到工具函數實體;入參
Function Calling 返回的入參為JSON字串,使用工具將其解析為JSON對象,提取入參資訊。
完成解析後,將參數傳入工具函數並執行,擷取輸出結果。
# 步驟5:運行工具函數
# 請將以下代碼粘貼到步驟4 代碼後
import json
print("正在執行工具函數...")
# 從返回的結果中擷取函數名稱和入參
function_name = completion.choices[0].message.tool_calls[0].function.name
arguments_string = completion.choices[0].message.tool_calls[0].function.arguments
# 使用json模組解析參數字串
arguments = json.loads(arguments_string)
# 建立一個函數映射表
function_mapper = {
"get_current_weather": get_current_weather,
"get_current_time": get_current_time
}
# 擷取函數實體
function = function_mapper[function_name]
# 如果入參為空白,則直接調用函數
if arguments == {}:
function_output = function()
# 否則,傳入參數後調用函數
else:
function_output = function(arguments)
# 列印工具的輸出
print(f"工具函數輸出:{function_output}\n")// 步驟5:運行工具函數
// 請將以下代碼粘貼到步驟4 代碼後
console.log("正在執行工具函數...");
const function_name = completion.choices[0].message.tool_calls[0].function.name;
const arguments_string = completion.choices[0].message.tool_calls[0].function.arguments;
// 使用JSON模組解析參數字串
const args = JSON.parse(arguments_string);
// 建立一個函數映射表
const functionMapper = {
"get_current_weather": getCurrentWeather,
"get_current_time": getCurrentTime
};
// 擷取函數實體
const func = functionMapper[function_name];
// 如果入參為空白,則直接調用函數
let functionOutput;
if (Object.keys(args).length === 0) {
functionOutput = func();
} else {
// 否則,傳入參數後調用函數
functionOutput = func(args);
}
// 列印工具的輸出
console.log(`工具函數輸出:${functionOutput}\n`);運行後得到如下輸出:
上海今天是多雲。實際業務中,許多工具執行具體操作(如郵件發送、檔案上傳),而非資料查詢,不會輸出字串。建議為此類工具添加狀態原因資訊(如“郵件發送完成”、“操作執行失敗”),協助大模型瞭解執行狀態。
5. 大模型總結工具函數輸出
工具函數的輸出格式較為固定,直接返回使用者可能語氣生硬。將工具輸出提交到模型上下文並再次調用模型,可產生自然語言風格的回複。
添加 Assistant Message
發起 Function Calling後,通過
completion.choices[0].message得到 Assistant Message,首先將它添加到 messages 數組中;添加 Tool Message
將工具的輸出通過
{"role": "tool", "content": "工具的輸出","tool_call_id": completion.choices[0].message.tool_calls[0].id}形式添加到 messages 數組。說明請確保工具的輸出為字串格式。
tool_call_id是系統為每一次的工具調用請求產生的唯一識別碼。模型可能一次性要求調用多個工具,將多個工具結果返回給模型時,tool_call_id可確保工具的輸出結果能夠與它的調用意圖對應。
# 步驟6:向大模型提交工具輸出
# 請將以下代碼粘貼到步驟5 代碼後
messages.append(completion.choices[0].message)
print("已添加assistant message")
messages.append({"role": "tool", "content": function_output, "tool_call_id": completion.choices[0].message.tool_calls[0].id})
print("已添加tool message\n")// 步驟6:向大模型提交工具輸出
// 請將以下代碼粘貼到步驟5 代碼後
messages.push(completion.choices[0].message);
console.log("已添加 assistant message")
messages.push({
"role": "tool",
"content": functionOutput,
"tool_call_id": completion.choices[0].message.tool_calls[0].id
});
console.log("已添加 tool message\n");此時的 messages 數組為:
[
System Message -- 指引模型調用工具的策略
User Message -- 使用者的問題
Assistant Message -- 模型返回的工具調用資訊
Tool Message -- 工具的輸出資訊(如果採用下文介紹的並行工具調用,可能有多個 Tool Message)
]更新 messages 數組後,運行以下代碼。
# 步驟7:大模型總結工具輸出
# 請將以下代碼粘貼到步驟6 代碼後
print("正在總結工具輸出...")
completion = function_calling()// 步驟7:大模型總結工具輸出
// 請將以下代碼粘貼到步驟6 代碼後
console.log("正在總結工具輸出...");
const completion_1 = await functionCalling();可從content得到回複內容:“上海今天的天氣是多雲。如果您有其他問題,歡迎繼續提問。”
{
"content": "上海今天的天氣是多雲。如果您有其他問題,歡迎繼續提問。",
"refusal": null,
"role": "assistant",
"audio": null,
"function_call": null,
"tool_calls": null
}至此,您已完成了一次完整的 Function Calling 流程。
進階用法
指定工具調用方式
並行工具調用
單一城市天氣查詢只需一次工具調用。若問題需要多次調用工具,如“北京上海的天氣如何”或“杭州天氣,以及現在幾點了”,發起 Function Calling 後只會返回一個工具調用資訊,以提問“北京上海的天氣如何”為例:
{
"content": "",
"refusal": null,
"role": "assistant",
"audio": null,
"function_call": null,
"tool_calls": [
{
"id": "call_61a2bbd82a8042289f1ff2",
"function": {
"arguments": "{\"location\": \"北京市\"}",
"name": "get_current_weather"
},
"type": "function",
"index": 0
}
]
}返回結果中只有北京市的入參資訊。為瞭解決這一問題,在發起 Function Calling時,可佈建要求參數parallel_tool_calls為true,這樣返回對象中將包含所有需要調用的工具函數與入參資訊。
並行工具調用適合任務之間無依賴的情況。若任務之間有依賴關係(工具A的輸入與工具B的輸出結果有關),請參見快速開始,通過while迴圈實現串列工具調用(一次調用一個工具)。
def function_calling():
completion = client.chat.completions.create(
model="qwen3.6-plus", # 此處以qwen3.6-plus為例,可按需更換模型名稱
extra_body={"enable_thinking": False},
messages=messages,
tools=tools,
# 新增參數
parallel_tool_calls=True
)
print("返回對象:")
print(completion.choices[0].message.model_dump_json())
print("\n")
return completion
print("正在發起function calling...")
completion = function_calling()async function functionCalling() {
const completion = await openai.chat.completions.create({
model: "qwen3.6-plus", // 此處以qwen3.6-plus為例,可按需更換模型名稱
enable_thinking: false,
messages: messages,
tools: tools,
parallel_tool_calls: true
});
console.log("返回對象:");
console.log(JSON.stringify(completion.choices[0].message));
console.log("\n");
return completion;
}
const completion = await functionCalling();在返回對象的tool_calls數組中包含了北京上海的入參資訊:
{
"content": "",
"role": "assistant",
"tool_calls": [
{
"function": {
"name": "get_current_weather",
"arguments": "{\"location\": \"北京市\"}"
},
"index": 0,
"id": "call_c2d8a3a24c4d4929b26ae2",
"type": "function"
},
{
"function": {
"name": "get_current_weather",
"arguments": "{\"location\": \"上海市\"}"
},
"index": 1,
"id": "call_dc7f2f678f1944da9194cd",
"type": "function"
}
]
}強制工具調用
大模型產生內容具有不確定性,可能選擇錯誤的工具。如需對某類問題強制使用或禁用特定工具,可修改tool_choice參數。tool_choice參數的預設值為"auto",表示由大模型自主判斷如何進行工具調用。
大模型總結工具函數輸出時,請將 tool_choice 參數去除,否則API仍會返回工具調用資訊。強制使用某個工具
如果您希望對於某一類問題,Function Calling 能強制調用某個工具,可設定
tool_choice參數為{"type": "function", "function": {"name": "the_function_to_call"}},大模型將不參與工具的選擇,只輸出入參資訊。假設當前情境中只包含天氣查詢的問題,可修改 function_calling 代碼為:
def function_calling(): completion = client.chat.completions.create( model="qwen3.6-plus", extra_body={"enable_thinking": False}, messages=messages, tools=tools, tool_choice={"type": "function", "function": {"name": "get_current_weather"}} ) print(completion.model_dump_json()) function_calling()async function functionCalling() { const response = await openai.chat.completions.create({ model: "qwen3.6-plus", enable_thinking: false, messages: messages, tools: tools, tool_choice: {"type": "function", "function": {"name": "get_current_weather"}} }); console.log("返回對象:"); console.log(JSON.stringify(response.choices[0].message)); console.log("\n"); return response; } const response = await functionCalling();無論輸入什麼問題,返回對象的工具函數都會是
get_current_weather。使用該策略前請確保問題與選擇的工具相關,否則可能返回不符合預期的結果。
強制使用至少一個工具
某些需要使用工具的問題,大模型可能判斷為無需調用。如需強制 Function Calling 始終進行工具調用(返回對象中
tool_calls參數不為空白),可以設定tool_choice參數為"required",Function Calling 將始終返回工具與入參資訊。假設當前情境中的問題均需要調用工具,您可以修改 function_calling 代碼為:
def function_calling(): completion = client.chat.completions.create( model="qwen3.6-plus", extra_body={"enable_thinking": False}, messages=messages, tools=tools, tool_choice="required" ) print(completion.model_dump_json()) function_calling()async function functionCalling() { const completion = await openai.chat.completions.create({ model: "qwen3.6-plus", enable_thinking: false, messages: messages, tools: tools, tool_choice: "required" }); console.log("返回對象:"); console.log(JSON.stringify(completion.choices[0].message)); console.log("\n"); return completion; } const completion = await functionCalling();無論輸入什麼問題,返回對象的
tool_calls參數將始終不為空白。使用該策略前請確保問題與工具相關,否則可能返回不符合預期的結果。
強制不使用工具
如需 Function Calling 始終不進行工具調用(返回對象中包含回複內容
content而tool_calls參數為空白),可設定tool_choice參數為"none",或不傳入tools參數,Function Calling 返回的tool_calls參數將始終為空白。假設當前情境中的問題均無需調用工具,可修改 function_calling 代碼為:
def function_calling(): completion = client.chat.completions.create( model="qwen3.6-plus", extra_body={"enable_thinking": False}, messages=messages, tools=tools, tool_choice="none" ) print(completion.model_dump_json()) function_calling()async function functionCalling() { const completion = await openai.chat.completions.create({ model: "qwen3.6-plus", enable_thinking: false, messages: messages, tools: tools, tool_choice: "none" }); console.log("返回對象:"); console.log(JSON.stringify(completion.choices[0].message)); console.log("\n"); return completion; } const completion = await functionCalling();
多輪對話
使用者可能第一輪提問“北京天氣”,第二輪提問“上海的呢?”。若模型上下文缺少第一輪資訊,模型無法判斷調用哪個工具。多輪對話情境中,每輪結束後保持 messages 數組完整,在此基礎上添加 User Message 並發起 Function Calling以及後續步驟。messages 結構如下所示:
[
System Message -- 指引模型調用工具的策略
User Message -- 使用者的問題
Assistant Message -- 模型返回的工具調用資訊
Tool Message -- 工具的輸出資訊
Assistant Message -- 模型總結的工具調用資訊
User Message -- 使用者第二輪的問題
]流式輸出
使用流式輸出可即時擷取工具函數名稱與入參資訊,提升使用者體驗。其中:
工具調用的參數資訊:以資料流的形式分塊返回。
工具函數名稱:在流式響應的第一個資料區塊中返回。
from openai import OpenAI
import os
client = OpenAI(
api_key=os.getenv("DASHSCOPE_API_KEY"),
# 如果使用北京地區的模型,需要將替換為:https://{WorkspaceId}.cn-beijing.maas.aliyuncs.com/compatible-mode/v1
base_url="https://{WorkspaceId}.ap-southeast-1.maas.aliyuncs.com/compatible-mode/v1",
)
tools = [
{
"type": "function",
"function": {
"name": "get_current_weather",
"description": "當你想查詢指定城市的天氣時非常有用。",
"parameters": {
"type": "object",
"properties": {
"location": {
"type": "string",
"description": "城市或縣區,比如北京市、杭州市、餘杭區等。",
}
},
"required": ["location"],
},
},
},
]
stream = client.chat.completions.create(
model="qwen3.6-plus",
extra_body={"enable_thinking": False},
messages=[{"role": "user", "content": "杭州天氣?"}],
tools=tools,
stream=True
)
for chunk in stream:
delta = chunk.choices[0].delta
print(delta.tool_calls)import { OpenAI } from "openai";
const openai = new OpenAI(
{
// 各地區的API Key不同。擷取API Key:https://www.alibabacloud.com/help/zh/model-studio/get-api-key
// 若沒有配置環境變數,請用百鍊API Key將下行替換為:apiKey: "sk-xxx",
apiKey: process.env.DASHSCOPE_API_KEY,
// 如果使用北京地區的模型,需要將baseURL替換為:https://{WorkspaceId}.cn-beijing.maas.aliyuncs.com/compatible-mode/v1
baseURL: "https://{WorkspaceId}.cn-beijing.maas.aliyuncs.com/compatible-mode/v1"
}
);
const tools = [
{
"type": "function",
"function": {
"name": "getCurrentWeather",
"description": "當你想查詢指定城市的天氣時非常有用。",
"parameters": {
"type": "object",
"properties": {
"location": {
"type": "string",
"description": "城市或縣區,比如北京市、杭州市、餘杭區等。"
}
},
"required": ["location"]
}
}
}
];
const stream = await openai.chat.completions.create({
model: "qwen3.6-plus",
enable_thinking: false,
messages: [{ role: "user", content: "北京天氣" }],
tools: tools,
stream: true,
});
for await (const chunk of stream) {
const delta = chunk.choices[0].delta;
console.log(delta.tool_calls);
}運行後得到如下輸出:
[ChoiceDeltaToolCall(index=0, id='call_8f08d2b0fc0c4d8fab7123', function=ChoiceDeltaToolCallFunction(arguments='{"location":', name='get_current_weather'), type='function')]
[ChoiceDeltaToolCall(index=0, id='', function=ChoiceDeltaToolCallFunction(arguments=' "杭州"}', name=None), type='function')]
None運行以下代碼拼接入參資訊(arguments):
tool_calls = {}
for response_chunk in stream:
delta_tool_calls = response_chunk.choices[0].delta.tool_calls
if delta_tool_calls:
for tool_call_chunk in delta_tool_calls:
call_index = tool_call_chunk.index
tool_call_chunk.function.arguments = tool_call_chunk.function.arguments or ""
if call_index not in tool_calls:
tool_calls[call_index] = tool_call_chunk
else:
tool_calls[call_index].function.arguments += tool_call_chunk.function.arguments
print(tool_calls[0].model_dump_json())const toolCalls = {};
for await (const responseChunk of stream) {
const deltaToolCalls = responseChunk.choices[0]?.delta?.tool_calls;
if (deltaToolCalls) {
for (const toolCallChunk of deltaToolCalls) {
const index = toolCallChunk.index;
toolCallChunk.function.arguments = toolCallChunk.function.arguments || "";
if (!toolCalls[index]) {
toolCalls[index] = { ...toolCallChunk };
if (!toolCalls[index].function) {
toolCalls[index].function = { name: '', arguments: '' };
}
}
else if (toolCallChunk.function?.arguments) {
toolCalls[index].function.arguments += toolCallChunk.function.arguments;
}
}
}
}
console.log(JSON.stringify(toolCalls[0]));獲得如下輸出:
{"index":0,"id":"call_16c72bef988a4c6c8cc662","function":{"arguments":"{\"location\": \"杭州\"}","name":"get_current_weather"},"type":"function"}在使用大模型總結工具函數輸出步驟,添加的 Assistant Message 需要符合下方格式。僅需將下方的tool_calls中的元素替換為以上內容即可。
{
"content": "",
"refusal": None,
"role": "assistant",
"audio": None,
"function_call": None,
"tool_calls": [
{
"id": "call_xxx",
"function": {
"arguments": '{"location": "xx"}',
"name": "get_current_weather",
},
"type": "function",
"index": 0,
}
],
}Responses API的工具調用
上述樣本基於 OpenAI Chat Completions 和 DashScope API。若使用 OpenAI Responses API,整體流程相同,但介面格式有以下區別:
維度 | Chat Completions | Responses API |
工具定義格式 | | |
工具調用輸出 | response.choices[0].message.tool_calls | response.output 中 type 為 function_call 的項 |
工具結果回傳 | | |
最終回複 | response.choices[0].message.content | response.output_text |
from openai import OpenAI
import json
import os
import random
# 初始化用戶端
client = OpenAI(
# 若沒有配置環境變數,請用阿里雲百鍊API Key將下行替換為:api_key="sk-xxx",
# 各地區的API Key不同。擷取API Key:https://www.alibabacloud.com/help/zh/model-studio/get-api-key
api_key=os.getenv("DASHSCOPE_API_KEY"),
base_url="https://{WorkspaceId}.ap-southeast-1.maas.aliyuncs.com/compatible-mode/v1",
)
# 類比使用者問題
USER_QUESTION = "新加坡天氣咋樣"
# 定義工具列表
tools = [
{
"type": "function",
"name": "get_current_weather",
"description": "當你想查詢指定城市的天氣時非常有用。",
"parameters": {
"type": "object",
"properties": {
"location": {
"type": "string",
"description": "城市或縣區,比如新加坡、倫敦等。",
}
},
"required": ["location"],
},
}
]
# 類比天氣查詢工具
def get_current_weather(arguments):
weather_conditions = ["晴天", "多雲", "雨天"]
random_weather = random.choice(weather_conditions)
location = arguments["location"]
return f"{location}今天是{random_weather}。"
# 封裝模型響應函數
def get_response(input_data):
response = client.responses.create(
model="qwen3.6-plus",
extra_body={"enable_thinking": False},
input=input_data,
tools=tools,
)
return response
# 維護對話上下文
conversation = [{"role": "user", "content": USER_QUESTION}]
response = get_response(conversation)
function_calls = [item for item in response.output if item.type == "function_call"]
# 如果不需要調用工具,直接輸出內容
if not function_calls:
print(f"助手最終回複:{response.output_text}")
else:
# 進入工具調用迴圈
while function_calls:
for fc in function_calls:
func_name = fc.name
arguments = json.loads(fc.arguments)
print(f"正在調用工具 [{func_name}],參數:{arguments}")
# 執行工具
tool_result = get_current_weather(arguments)
print(f"工具返回:{tool_result}")
# 將工具調用和結果成對追加到上下文中
conversation.append(
{
"type": "function_call",
"name": fc.name,
"arguments": fc.arguments,
"call_id": fc.call_id,
}
)
conversation.append(
{
"type": "function_call_output",
"call_id": fc.call_id,
"output": tool_result,
}
)
# 攜帶完整上下文再次調用模型
response = get_response(conversation)
function_calls = [
item for item in response.output if item.type == "function_call"
]
print(f"助手最終回複:{response.output_text}")
import OpenAI from "openai";
// 初始化用戶端
const openai = new OpenAI({
// 各地區的API Key不同。擷取API Key:https://www.alibabacloud.com/help/zh/model-studio/get-api-key
// 若沒有配置環境變數,請用阿里雲百鍊API Key將下行替換為:apiKey: "sk-xxx",
apiKey: process.env.DASHSCOPE_API_KEY,
baseURL:
"https://{WorkspaceId}.ap-southeast-1.maas.aliyuncs.com/compatible-mode/v1",
});
// 定義工具列表
const tools = [
{
type: "function",
name: "get_current_weather",
description: "當你想查詢指定城市的天氣時非常有用。",
parameters: {
type: "object",
properties: {
location: {
type: "string",
description: "城市或縣區,比如新加坡、倫敦等。",
},
},
required: ["location"],
},
},
];
// 類比天氣查詢工具
const getCurrentWeather = (args) => {
const weatherConditions = ["晴天", "多雲", "雨天"];
const randomWeather =
weatherConditions[Math.floor(Math.random() * weatherConditions.length)];
const location = args.location;
return `${location}今天是${randomWeather}。`;
};
// 封裝模型響應函數
const getResponse = async (inputData) => {
const response = await openai.responses.create({
model: "qwen3.6-plus",
enable_thinking: false,
input: inputData,
tools: tools,
});
return response;
};
const main = async () => {
const userQuestion = "新加坡天氣";
// 維護對話上下文
const conversation = [{ role: "user", content: userQuestion }];
let response = await getResponse(conversation);
let functionCalls = response.output.filter(
(item) => item.type === "function_call"
);
// 如果不需要調用工具,直接輸出內容
if (functionCalls.length === 0) {
console.log(`助手最終回複:${response.output_text}`);
} else {
// 進入工具調用迴圈
while (functionCalls.length > 0) {
for (const fc of functionCalls) {
const funcName = fc.name;
const args = JSON.parse(fc.arguments);
console.log(`正在調用工具 [${funcName}],參數:`, args);
// 執行工具
const toolResult = getCurrentWeather(args);
console.log(`工具返回:${toolResult}`);
// 將工具調用和結果成對追加到上下文中
conversation.push({
type: "function_call",
name: fc.name,
arguments: fc.arguments,
call_id: fc.call_id,
});
conversation.push({
type: "function_call_output",
call_id: fc.call_id,
output: toolResult,
});
}
// 攜帶完整上下文再次調用模型
response = await getResponse(conversation);
functionCalls = response.output.filter(
(item) => item.type === "function_call"
);
}
console.log(`助手最終回複:${response.output_text}`);
}
};
// 啟動程式
main().catch(console.error);
全模態模型的工具調用
全模態模型支援工具調用,Qwen-Omni 系列和 Qwen-Omni-Realtime 系列的調用方式不同。
Qwen-Omni 系列
Qwen3.5-Omni-Plus、Qwen3.5-Omni-Flash、Qwen3-Omni-Flash 系列支援工具調用,通過 OpenAI 相容介面調用。擷取工具資訊階段與其他模型有以下不同:
必須使用流式輸出:千問Omni僅支援流式輸出,在擷取工具資訊時也必須設定
stream=True。建議僅輸出文本:模型在擷取工具資訊(函數的名稱和參數)時僅需文本資訊,為避免產生不必要的音頻,建議設定
modalities=["text"]。當輸出包含文本和音頻兩種模態時,擷取工具資訊時需要跳過音頻資料區塊。
千問Omni詳情參見: 非即時(Qwen-Omni) 。
from openai import OpenAI
import os
client = OpenAI(
# 各地區的API Key不同。擷取API Key:https://www.alibabacloud.com/help/zh/model-studio/get-api-key
api_key=os.getenv("DASHSCOPE_API_KEY"),
# 如果使用北京地區的模型,需要將替換為:https://{WorkspaceId}.cn-beijing.maas.aliyuncs.com/compatible-mode/v1
base_url="https://{WorkspaceId}.ap-southeast-1.maas.aliyuncs.com/compatible-mode/v1",
)
tools = [
{
"type": "function",
"function": {
"name": "get_current_weather",
"description": "當你想查詢指定城市的天氣時非常有用。",
"parameters": {
"type": "object",
"properties": {
"location": {
"type": "string",
"description": "城市或縣區,比如北京市、杭州市、餘杭區等。",
}
},
"required": ["location"],
},
},
},
]
completion = client.chat.completions.create(
model="qwen3.5-omni-plus",
messages=[{"role": "user", "content": "杭州天氣?"}],
# 設定輸出資料的模態,可取值:["text"]、["text","audio"],建議設定為["text"]
modalities=["text"],
# stream 必須設定為 True,否則會報錯
stream=True,
tools=tools
)
for chunk in completion:
# 如果輸出包含音頻模態,請將下列條件改為:if chunk.choices and not hasattr(chunk.choices[0].delta, "audio"):
if chunk.choices:
delta = chunk.choices[0].delta
print(delta.tool_calls)import { OpenAI } from "openai";
const openai = new OpenAI(
{
// 各地區的API Key不同。擷取API Key:https://www.alibabacloud.com/help/zh/model-studio/get-api-key
// 若沒有配置環境變數,請用百鍊API Key將下行替換為:apiKey: "sk-xxx",
apiKey: process.env.DASHSCOPE_API_KEY,
// 如果使用北京地區的模型,需要將baseURL替換為:https://{WorkspaceId}.cn-beijing.maas.aliyuncs.com/compatible-mode/v1
baseURL: "https://{WorkspaceId}.ap-southeast-1.maas.aliyuncs.com/compatible-mode/v1"
}
);
const tools = [
{
"type": "function",
"function": {
"name": "getCurrentWeather",
"description": "當你想查詢指定城市的天氣時非常有用。",
"parameters": {
"type": "object",
"properties": {
"location": {
"type": "string",
"description": "城市或縣區,比如北京市、杭州市、餘杭區等。"
}
},
"required": ["location"]
}
}
}
];
const stream = await openai.chat.completions.create({
model: "qwen3-omni-flash",
messages: [
{
"role": "user",
"content": "杭州天氣"
}],
stream: true,
// 設定輸出資料的模態,可取值:["text"]、["text","audio"],建議設定為["text"]
modalities: ["text"],
tools:tools
});
for await (const chunk of stream) {
// 如果輸出包含音頻,請將條件陳述式替換為:if (chunk.choices?.length && chunk.choices[0].delta && !('audio' in chunk.choices[0].delta))
if (chunk.choices?.length){
const delta = chunk.choices[0].delta;
console.log(delta.tool_calls);
}}運行後得到如下輸出:
[ChoiceDeltaToolCall(index=0, id='call_391c8e5787bc4972a388aa', function=ChoiceDeltaToolCallFunction(arguments=None, name='get_current_weather'), type='function')]
[ChoiceDeltaToolCall(index=0, id='call_391c8e5787bc4972a388aa', function=ChoiceDeltaToolCallFunction(arguments=' {"location": "杭州市"}', name=None), type='function')]
None拼接入參資訊(arguments)的代碼請參見流式輸出。
Qwen-Omni-Realtime 系列
Qwen3.5-Omni-Plus-Realtime、Qwen3.5-Omni-Flash-Realtime 系列支援工具調用,適用於語音對話情境。可通過 DashScope SDK或 WebSocket 原生協議調用。
工作流程:
建立 WebSocket 串連後,通過 session.update 傳入工具定義,即可進入以下互動流程:
階段一:語音輸入與工具調用
使用者發起語音提問,用戶端採集音頻並發送至服務端(對應
append_audio()方法),服務端 VAD 檢測語音結束後進行模型推理,判斷需要調用工具。服務端將工具調用資訊返回給用戶端(對應
response.function_call_arguments.done事件),包含函數名(name)、函數入參(arguments)和調用標識(call_id),樣本如下:{ "type": "response.function_call_arguments.done", "response_id": "resp_JnTOsWXlFhKcFohZbtfz6", "item_id": "item_Rhcms7CauTNsQprV5S4Hr", "output_index": 0, "name": "get_current_weather", "call_id": "call_2be200f4cafe419b9530dd", "arguments": "{\"location\": \"杭州\"}" }用戶端根據函數名和入參,在本地執行對應的工具函數,獲得執行結果。
階段二:用戶端回傳工具結果並觸發最終響應
用戶端將工具執行結果發回服務端(對應
conversation.item.create事件),包含調用標識(call_id)和執行結果(output),樣本如下:{ "type": "conversation.item.create", "item": { "type": "function_call_output", "call_id": "call_2be200f4cafe419b9530dd", "output": "杭州今天天氣為晴,氣溫25℃,微風" } }用戶端繼續發送
response.create事件,觸發服務端基於工具執行結果產生最終語音回答。用戶端接收服務端返回的語音和文本(對應
response.audio.delta和response.audio_transcript.delta事件),播放語音回複給使用者。
Qwen-Omni-Realtime 系列不支援tool_choice和parallel_tool_calls參數。
千問Omni-Realtime詳情請參見: 即時(Qwen-Omni-Realtime) 、 用戶端事件 、 服務端事件 。
DashScope Python SDK
import os
import uuid
import threading
import traceback
import json
import base64
import signal
import sys
import time
from typing import Dict, Any, Optional, List
import pyaudio
import queue
import contextlib
import dashscope
from dashscope.audio.qwen_omni import *
# ==================== 常量定義 ====================
VOICE = 'Tina'
MODEL = "qwen3.5-omni-plus-realtime"
# 如果需要訪問北京地區,請WS_URL將替換為:wss://{WorkspaceId}.cn-beijing.maas.aliyuncs.com/api-ws/v1/realtime
WS_URL = "wss://{WorkspaceId}.ap-southeast-1.maas.aliyuncs.com/api-ws/v1/realtime"
# 配置 API Key,若沒有設定環境變數,請用 API Key 將下行替換為 dashscope.api_key = "sk-xxx"
dashscope.api_key = os.getenv('DASHSCOPE_API_KEY')
AUDIO_SAMPLE_RATE = 16000
AUDIO_CHUNK_SIZE = 3200
OUTPUT_AUDIO_SAMPLE_RATE = 24000
# ==================== 工具定義 ====================
def get_train_price(src: str, dst: str) -> str:
"""查詢火車票價格"""
return f"{src}到{dst}的火車票價格為100~200元。"
def get_flight_price(src: str, dst: str) -> str:
"""查詢飛機票價格"""
return f"{src}到{dst}的機票價格為200~300美元。"
def get_current_weather(location: str) -> str:
"""查詢指定城市天氣"""
return f"{location}今天天氣為霾轉晴,氣溫4/-4℃,微風"
# 統一的 OpenAI 格式工具定義
TOOLS = [
{
"type": "function",
"function": {
"name": "get_current_weather",
"description": "當你想查詢指定城市的天氣時非常有用。",
"parameters": {
"type": "object",
"properties": {
"location": {
"type": "string",
"description": "城市或縣區,比如北京市、杭州市、餘杭區等。",
}
},
"required": ["location"],
},
},
},
{
"type": "function",
"function": {
"name": "get_flight_price",
"description": "當你想查詢飛機票價格時非常有用。",
"parameters": {
"type": "object",
"properties": {
"src": {
"type": "string",
"description": "飛機起飛的城市,比如北京市、杭州市等。",
},
"dst": {
"type": "string",
"description": "飛機降落的城市,比如北京市、杭州市區等。",
},
},
"required": ["src", "dst"],
},
},
},
{
"type": "function",
"function": {
"name": "get_train_price",
"description": "當你想查詢火車票價格時非常有用。",
"parameters": {
"type": "object",
"properties": {
"src": {
"type": "string",
"description": "火車出發的城市,比如北京市、杭州市等。",
},
"dst": {
"type": "string",
"description": "火車到達的城市,比如北京市、杭州市區等。",
},
},
"required": ["src", "dst"],
},
},
},
]
# 工具名稱到函數的映射
TOOL_FUNCTIONS = {
"get_current_weather": get_current_weather,
"get_flight_price": get_flight_price,
"get_train_price": get_train_price,
}
# ==================== 工具調用處理 ====================
def handle_tool_call(tool_call_response: Dict[str, Any]) -> Dict[str, Any]:
"""
處理工具調用請求
Args:
tool_call_response: 包含 name, arguments, call_id 的工具調用資訊
Returns:
更新後的工具調用響應,包含 output 欄位
"""
try:
function_name = tool_call_response['name']
tool_call_arguments = json.loads(tool_call_response['arguments'])
print(f'[Tool Call] 開始處理: name={function_name}, args={tool_call_arguments}')
# 尋找對應的函數
if function_name not in TOOL_FUNCTIONS:
tool_call_response['output'] = f"用戶端未找到工具: {function_name}"
print(f'[Tool Call] 錯誤: 未找到工具 {function_name}')
return tool_call_response
# 調用函數
func = TOOL_FUNCTIONS[function_name]
result = func(**tool_call_arguments)
tool_call_response['output'] = result
print(f'[Tool Call] 完成: {result}')
return tool_call_response
except Exception as e:
error_msg = f"工具調用失敗: {str(e)}"
tool_call_response['output'] = error_msg
print(f'[Tool Call] 異常: {error_msg}')
traceback.print_exc()
return tool_call_response
def send_tool_call_response(conversation: OmniRealtimeConversation, response: Dict[str, Any]) -> None:
"""發送工具調用結果到服務端"""
conversation.create_item({
"id": 'item_' + uuid.uuid4().hex,
"type": "function_call_output",
"call_id": response['call_id'],
"output": response["output"],
})
# ==================== PCM 音頻播放器 ====================
class PCMPlayer:
"""
PCM 音頻播放器
使用雙線程架構實現即時音頻播放:
- 解碼線程:將 base64 編碼的音頻資料解碼為原始 PCM 資料
- 播放線程:將 PCM 資料寫入音訊輸出裝置
支援動態添加音頻資料、取消播放、儲存音頻檔案等功能。
"""
def __init__(self, pya: pyaudio.PyAudio, sample_rate=24000, chunk_size_ms=100, save_file=False):
"""
初始化 PCM 播放器
Args:
pya: pyaudio.PyAudio 執行個體
sample_rate: 音頻採樣率(Hz),預設 24000
chunk_size_ms: 音頻塊大小(毫秒),影響取消播放的延遲,預設 100ms
save_file: 是否儲存播放的音頻到檔案(result.pcm),預設 False
"""
self.pya = pya
self.sample_rate = sample_rate
self.chunk_size_bytes = chunk_size_ms * sample_rate * 2 // 1000
self.player_stream = pya.open(format=pyaudio.paInt16,
channels=1,
rate=sample_rate,
output=True)
self.raw_audio_buffer: queue.Queue = queue.Queue()
self.b64_audio_buffer: queue.Queue = queue.Queue()
self.status_lock = threading.Lock()
self.status = 'playing'
self.decoder_thread = threading.Thread(target=self.decoder_loop)
self.player_thread = threading.Thread(target=self.player_loop)
self.decoder_thread.start()
self.player_thread.start()
self.complete_event: threading.Event = None
self.save_file = save_file
if self.save_file:
self.out_file = open('result.pcm', 'wb')
def decoder_loop(self):
"""解碼線程:將 base64 音頻資料解碼為 PCM 未經處理資料"""
while self.status != 'stop':
recv_audio_b64 = None
with contextlib.suppress(queue.Empty):
recv_audio_b64 = self.b64_audio_buffer.get(timeout=0.1)
if recv_audio_b64 is None:
continue
recv_audio_raw = base64.b64decode(recv_audio_b64)
# push raw audio data into queue by chunk
for i in range(0, len(recv_audio_raw), self.chunk_size_bytes):
chunk = recv_audio_raw[i:i + self.chunk_size_bytes]
self.raw_audio_buffer.put(chunk)
if self.save_file:
self.out_file.write(chunk)
def player_loop(self):
"""播放線程:將 PCM 資料寫入音訊輸出裝置"""
while self.status != 'stop':
recv_audio_raw = None
with contextlib.suppress(queue.Empty):
recv_audio_raw = self.raw_audio_buffer.get(timeout=0.1)
if recv_audio_raw is None:
if self.complete_event:
self.complete_event.set()
continue
# write chunk to pyaudio audio player, wait until finish playing this chunk.
self.player_stream.write(recv_audio_raw)
def cancel_playing(self):
"""取消播放:清空所有緩衝隊列"""
self.b64_audio_buffer.queue.clear()
self.raw_audio_buffer.queue.clear()
def add_data(self, data):
"""添加 base64 編碼的音頻資料到播放隊列"""
self.b64_audio_buffer.put(data)
def wait_for_complete(self):
"""等待播放完成"""
self.complete_event = threading.Event()
self.complete_event.wait()
self.complete_event = None
def shutdown(self):
"""關閉播放器並釋放資源"""
self.status = 'stop'
self.decoder_thread.join()
self.player_thread.join()
self.player_stream.close()
if self.save_file:
self.out_file.close()
# ==================== 音頻管理器 ====================
class AudioManager:
"""管理音頻輸入輸出資源"""
def __init__(self):
self.pya: Optional[pyaudio.PyAudio] = None
self.mic_stream: Optional[pyaudio.Stream] = None
self.player: Optional[PCMPlayer] = None
def initialize(self) -> None:
"""初始化音訊裝置"""
print('初始化音訊裝置...')
self.pya = pyaudio.PyAudio()
self.mic_stream = self.pya.open(
format=pyaudio.paInt16,
channels=1,
rate=AUDIO_SAMPLE_RATE,
input=True
)
self.player = PCMPlayer(self.pya, sample_rate=OUTPUT_AUDIO_SAMPLE_RATE)
print('音訊裝置初始化完成')
def read_audio_chunk(self) -> Optional[bytes]:
"""讀取音頻資料區塊"""
if not self.mic_stream:
return None
try:
return self.mic_stream.read(AUDIO_CHUNK_SIZE, exception_on_overflow=False)
except Exception as e:
print(f'[Error] 讀取音頻資料失敗: {e}')
return None
def cleanup(self) -> None:
"""清理音頻資源"""
print('清理音頻資源...')
if self.player:
self.player.shutdown()
if self.mic_stream:
self.mic_stream.close()
if self.pya:
self.pya.terminate()
print('音頻資源清理完成')
# ==================== 回調處理器 ====================
class OmniCallback(OmniRealtimeCallback):
"""Omni 即時對話回調處理器"""
def __init__(self, audio_manager: AudioManager):
self.audio_manager = audio_manager
self.tool_calls: Dict[str, Dict[str, Any]] = {}
self.all_response_text: str = ''
self.last_package_time: float = 0
self.is_first_text: bool = True
self.is_first_audio: bool = True
self.conversation: Optional[OmniRealtimeConversation] = None
def set_conversation(self, conversation: OmniRealtimeConversation) -> None:
"""設定對話執行個體引用"""
self.conversation = conversation
def on_open(self) -> None:
"""串連建立時的回調"""
print('串連已建立')
self.audio_manager.initialize()
self.last_package_time = time.time() * 1000
self.is_first_text = True
self.is_first_audio = True
self.tool_calls = {}
self.all_response_text = ''
def on_close(self, close_status_code: int, close_msg: str) -> None:
"""串連關閉時的回調"""
print(f'串連已關閉: code={close_status_code}, msg={close_msg}')
self.audio_manager.cleanup()
sys.exit(0)
def on_event(self, response: Dict[str, Any]) -> None:
"""處理事件回調"""
try:
event_type = response.get('type', '')
# 會話建立
if event_type == 'session.created':
print(f'會話已啟動: {response["session"]["id"]}')
# 語音轉文本完成
elif event_type == 'conversation.item.input_audio_transcription.completed':
print(f'使用者問題: {response.get("transcript", "")}')
# 文本增量響應
elif event_type in ('response.audio_transcript.delta', 'response.text.delta'):
if self.is_first_text:
self.is_first_text = False
latency = time.time() * 1000 - self.last_package_time
print(f'首字延遲 (VAD結束): {latency:.0f} ms')
text = response.get('delta', '')
self.all_response_text += text
# 音頻增量響應
elif event_type == 'response.audio.delta':
if self.is_first_audio:
self.is_first_audio = False
latency = time.time() * 1000 - self.last_package_time
print(f'首音延遲 (VAD結束): {latency:.0f} ms')
audio_interval = time.time() * 1000 - self.last_package_time
print(f'音頻間隔: {audio_interval:.0f} ms')
self.last_package_time = time.time() * 1000
recv_audio_b64 = response.get('delta', '')
if self.audio_manager.player:
self.audio_manager.player.add_data(recv_audio_b64)
# VAD 檢測到語音開始
elif event_type == 'input_audio_buffer.speech_started':
print('====== VAD 檢測到語音開始 ======')
if self.audio_manager.player:
self.audio_manager.player.cancel_playing()
# VAD 檢測到語音結束
elif event_type == 'input_audio_buffer.speech_stopped':
print('====== VAD 檢測到語音結束 ======')
self.last_package_time = time.time() * 1000
self.is_first_text = True
self.is_first_audio = True
self.tool_calls = {}
# 函數調用參數完成
elif event_type == 'response.function_call_arguments.done':
print('====== 收到工具調用請求 ======')
call_id = response.get('call_id', '')
self.tool_calls[call_id] = response.copy()
self.tool_calls[call_id]['processed'] = False
# 響應完成
elif event_type == 'response.done':
print('====== 響應完成 ======')
print(f'完整回複: {self.all_response_text}')
if self.conversation:
response_id = self.conversation.get_last_response_id()
text_delay = self.conversation.get_last_first_text_delay()
audio_delay = self.conversation.get_last_first_audio_delay()
# 只有當所有指標都可用時才列印詳細指標
if response_id is not None and text_delay is not None and audio_delay is not None:
print(f'[Metric] 響應ID: {response_id}, '
f'首字延遲: {text_delay:.0f}ms, '
f'首音延遲: {audio_delay:.0f}ms')
else:
print('[Metric] 指標資訊暫不可用(可能是工具調用後的響應)')
self.all_response_text = ''
except Exception as e:
print(f'[Error] 處理事件異常: {e}')
traceback.print_exc()
def process_pending_tool_calls(self) -> bool:
"""
處理待處理的工具調用
Returns:
是否有新的工具調用需要響應
"""
has_pending = False
for call_id, tool_call in self.tool_calls.items():
if not tool_call.get('processed', False):
has_pending = True
tool_call['processed'] = True
# 處理工具調用
result = handle_tool_call(tool_call)
# 發送結果到服務端
if self.conversation:
send_tool_call_response(self.conversation, result)
return has_pending
# ==================== 主程式 ====================
def main():
"""主函數"""
print('正在初始化 Omni 即時對話...')
# 建立音頻管理器
audio_manager = AudioManager()
# 建立回調處理器
callback = OmniCallback(audio_manager)
# 建立對話執行個體
conversation = OmniRealtimeConversation(
api_key=dashscope.api_key,
url=WS_URL,
model=MODEL,
callback=callback,
)
# 設定回調中的對話引用
callback.set_conversation(conversation)
# 建立串連
conversation.connect()
# 配置會話參數
omni_output_modalities = [MultiModality.AUDIO, MultiModality.TEXT]
conversation.update_session(
output_modalities=omni_output_modalities,
voice=VOICE,
input_audio_format=AudioFormat.PCM_16000HZ_MONO_16BIT,
output_audio_format=AudioFormat.PCM_24000HZ_MONO_16BIT,
enable_input_audio_transcription=True,
enable_turn_detection=True,
turn_detection_type='server_vad',
tools=TOOLS,
)
# 設定訊號處理
def signal_handler(sig, frame):
print('\n接收到 Ctrl+C,正在停止...')
conversation.close()
audio_manager.cleanup()
print('Omni 即時對話已停止')
sys.exit(0)
signal.signal(signal.SIGINT, signal_handler)
print("按 Ctrl+C 停止對話...\n")
# 主迴圈:持續發送音頻並檢查工具調用
try:
while True:
# 處理待處理的工具調用
has_tool_calls = callback.process_pending_tool_calls()
if has_tool_calls:
print("*** 工具調用完成,建立新響應 ***")
conversation.create_response(
instructions=None,
output_modalities=omni_output_modalities
)
print('====== 工具調用處理完成 ======\n')
# 讀取並發送音頻資料
audio_data = audio_manager.read_audio_chunk()
if audio_data:
audio_b64 = base64.b64encode(audio_data).decode('ascii')
conversation.append_audio(audio_b64)
else:
break
except KeyboardInterrupt:
signal_handler(signal.SIGINT, None)
except Exception as e:
print(f'[Error] 主迴圈異常: {e}')
traceback.print_exc()
finally:
conversation.close()
audio_manager.cleanup()
if __name__ == '__main__':
main()DashScope Java SDK
import com.alibaba.dashscope.audio.omni.*;
import com.alibaba.dashscope.exception.NoApiKeyException;
import com.google.gson.Gson;
import com.google.gson.JsonObject;
import javax.sound.sampled.*;
import java.nio.ByteBuffer;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicReference;
import java.util.function.Function;
public class Main {
public static void main(String[] args) {
try {
// 初始化組件
AudioPlayer audioPlayer = new AudioPlayer();
ToolRegistry toolRegistry = new ToolRegistry();
ConversationHandler handler = new ConversationHandler(audioPlayer, toolRegistry);
// 建立並配置會話
OmniRealtimeParam param = OmniRealtimeParam.builder()
.model("qwen3.5-omni-plus-realtime")
.apikey(System.getenv("DASHSCOPE_API_KEY"))
// 如果需要訪問北京地區,請url將替換為:wss://{WorkspaceId}.cn-beijing.maas.aliyuncs.com/api-ws/v1/realtime
.url("wss://{WorkspaceId}.ap-southeast-1.maas.aliyuncs.com/api-ws/v1/realtime")
.build();
OmniRealtimeConversation conversation = new OmniRealtimeConversation(param, handler);
conversation.connect();
// 配置會話參數
configureSession(conversation, toolRegistry);
// 啟動音頻採集
startAudioCapture(conversation, handler);
// 清理資源
cleanup(conversation, audioPlayer);
} catch (NoApiKeyException e) {
System.err.println("未找到API KEY: 請設定環境變數 DASHSCOPE_API_KEY");
} catch (Exception e) {
e.printStackTrace();
}
}
private static void configureSession(OmniRealtimeConversation conversation, ToolRegistry toolRegistry) {
HashMap<String, Object> additionalConfig = new HashMap<>();
additionalConfig.put("tools", toolRegistry.buildToolsDefinition());
conversation.updateSession(OmniRealtimeConfig.builder()
.modalities(Arrays.asList(OmniRealtimeModality.AUDIO, OmniRealtimeModality.TEXT))
.voice("Tina")
.enableTurnDetection(true)
.enableInputAudioTranscription(true)
.parameters(additionalConfig)
.build());
System.out.println("工具調用已開啟,請開始說話(按Ctrl+C退出)...");
}
private static void startAudioCapture(OmniRealtimeConversation conversation, ConversationHandler handler)
throws LineUnavailableException {
AudioFormat format = new AudioFormat(16000, 16, 1, true, false);
TargetDataLine mic = AudioSystem.getTargetDataLine(format);
mic.open(format);
mic.start();
ByteBuffer buffer = ByteBuffer.allocate(3200);
while (!handler.getShouldStop().get()) {
int bytesRead = mic.read(buffer.array(), 0, buffer.capacity());
if (bytesRead > 0) {
conversation.appendAudio(Base64.getEncoder().encodeToString(buffer.array()));
// 檢查並處理待處理的工具調用
if (handler.hasPendingToolCalls()) {
System.out.println("*** create response after call tools");
handler.processPendingToolCalls(conversation);
conversation.createResponse(null, Arrays.asList(OmniRealtimeModality.AUDIO, OmniRealtimeModality.TEXT));
System.out.println("======TOOL CALL END======");
}
}
try {
Thread.sleep(20);
} catch (InterruptedException ignored) {}
}
mic.close();
}
private static void cleanup(OmniRealtimeConversation conversation, AudioPlayer audioPlayer) {
try {
conversation.close(1000, "正常結束");
audioPlayer.close();
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* 音頻播放器 - 負責音頻資料的順序播放
*/
static class AudioPlayer {
private final SourceDataLine line;
private final Queue<byte[]> audioQueue = new ConcurrentLinkedQueue<>();
private final Thread playerThread;
private final AtomicBoolean shouldStop = new AtomicBoolean(false);
public AudioPlayer() throws LineUnavailableException {
AudioFormat format = new AudioFormat(24000, 16, 1, true, false);
line = AudioSystem.getSourceDataLine(format);
line.open(format);
line.start();
playerThread = new Thread(this::playLoop, "AudioPlayer");
playerThread.start();
}
private void playLoop() {
while (!shouldStop.get()) {
byte[] audio = audioQueue.poll();
if (audio != null) {
line.write(audio, 0, audio.length);
} else {
try {
Thread.sleep(10);
} catch (InterruptedException ignored) {}
}
}
}
public void play(String base64Audio) {
audioQueue.add(Base64.getDecoder().decode(base64Audio));
}
public void close() {
shouldStop.set(true);
try {
playerThread.join(1000);
} catch (InterruptedException ignored) {}
line.drain();
line.close();
}
}
/**
* 工具註冊表 - 管理可用的工具及其實現
*/
static class ToolRegistry {
private final Map<String, Function<JsonObject, String>> tools = new ConcurrentHashMap<>();
private final Map<String, JsonObject> pendingToolCalls = new ConcurrentHashMap<>();
public ToolRegistry() {
registerDefaultTools();
}
private void registerDefaultTools() {
registerTool("get_current_weather", this::getCurrentWeather);
registerTool("get_flight_price", this::getFlightPrice);
registerTool("get_train_price", this::getTrainPrice);
}
public void registerTool(String name, Function<JsonObject, String> handler) {
tools.put(name, handler);
}
/**
* 構建工具定義(OpenAI 格式)
*/
public List<Map<String, Object>> buildToolsDefinition() {
List<Map<String, Object>> definitions = new ArrayList<>();
definitions.add(createFunctionDefinition(
"get_current_weather",
"當你想查詢指定城市的天氣時非常有用。",
createParamsSchema(
Collections.singletonMap("location",
createProperty("string", "城市或縣區,比如北京市、杭州市、餘杭區等。")),
Collections.singletonList("location")
)
));
Map<String, Object> flightProps = new HashMap<>();
flightProps.put("src", createProperty("string", "飛機起飛的城市,比如北京市、杭州市等。"));
flightProps.put("dst", createProperty("string", "飛機降落的城市,比如北京市、杭州市區等。"));
definitions.add(createFunctionDefinition(
"get_flight_price",
"當你想查詢飛機票價格時非常有用。",
createParamsSchema(flightProps, Arrays.asList("src", "dst"))
));
Map<String, Object> trainProps = new HashMap<>();
trainProps.put("src", createProperty("string", "火車出發的城市,比如北京市、杭州市等。"));
trainProps.put("dst", createProperty("string", "火車到達的城市,比如北京市、杭州市區等。"));
definitions.add(createFunctionDefinition(
"get_train_price",
"當你想查詢火車票價格時非常有用。",
createParamsSchema(trainProps, Arrays.asList("src", "dst"))
));
return definitions;
}
private Map<String, Object> createFunctionDefinition(String name, String description, Map<String, Object> parameters) {
Map<String, Object> function = new HashMap<>();
function.put("name", name);
function.put("description", description);
function.put("parameters", parameters);
Map<String, Object> tool = new HashMap<>();
tool.put("type", "function");
tool.put("function", function);
return tool;
}
private Map<String, Object> createParamsSchema(Map<String, Object> properties, List<String> required) {
Map<String, Object> schema = new HashMap<>();
schema.put("type", "object");
schema.put("properties", properties);
schema.put("required", required);
return schema;
}
private Map<String, Object> createProperty(String type, String description) {
Map<String, Object> prop = new HashMap<>();
prop.put("type", type);
prop.put("description", description);
return prop;
}
/**
* 添加工具調用到待處理隊列
*/
public void addPendingToolCall(String callId, JsonObject toolCall) {
pendingToolCalls.put(callId, toolCall);
}
/**
* 檢查是否有待處理的工具調用
*/
public boolean hasPendingToolCalls() {
return !pendingToolCalls.isEmpty();
}
/**
* 處理所有待處理的工具調用
*/
public void processPendingToolCalls(OmniRealtimeConversation conversation) {
if (pendingToolCalls.isEmpty()) {
return;
}
for (Map.Entry<String, JsonObject> entry : pendingToolCalls.entrySet()) {
String callId = entry.getKey();
JsonObject toolCall = entry.getValue();
String result = executeTool(toolCall);
sendToolResult(conversation, callId, result);
}
pendingToolCalls.clear();
}
private String executeTool(JsonObject toolCall) {
String functionName = toolCall.get("name").getAsString();
JsonObject arguments = new Gson().fromJson(
toolCall.get("arguments").getAsString(),
JsonObject.class
);
System.out.println("[Tool Call] start handling: " + functionName + ", args: " + arguments);
Function<JsonObject, String> handler = tools.get(functionName);
if (handler == null) {
return "client沒有找到這個工具,調用失敗。";
}
String result = handler.apply(arguments);
System.out.println("[Tool Call] response: " + result);
return result;
}
private void sendToolResult(OmniRealtimeConversation conversation, String callId, String output) {
JsonObject item = new JsonObject();
item.addProperty("id", "item_" + UUID.randomUUID().toString().replace("-", ""));
item.addProperty("type", "function_call_output");
item.addProperty("call_id", callId);
item.addProperty("output", output);
conversation.createItem(item);
}
// ===== 工具實現 =====
private String getCurrentWeather(JsonObject args) {
String location = args.get("location").getAsString();
return location + "今天天氣為霾轉晴,氣溫4/-4℃,微風";
}
private String getFlightPrice(JsonObject args) {
String src = args.get("src").getAsString();
String dst = args.get("dst").getAsString();
return src + "到" + dst + "的機票價格為200~300美元。";
}
private String getTrainPrice(JsonObject args) {
String src = args.get("src").getAsString();
String dst = args.get("dst").getAsString();
return "invalid apikey error";
}
}
/**
* 會話處理器 - 處理 WebSocket 事件
*/
static class ConversationHandler extends OmniRealtimeCallback {
private final AudioPlayer audioPlayer;
private final ToolRegistry toolRegistry;
private final AtomicBoolean shouldStop = new AtomicBoolean(false);
private final AtomicReference<StringBuilder> responseTextRef = new AtomicReference<>(new StringBuilder());
private long lastPackageTime = 0;
private boolean isFirstText = true;
private boolean isFirstAudio = true;
public ConversationHandler(AudioPlayer audioPlayer, ToolRegistry toolRegistry) {
this.audioPlayer = audioPlayer;
this.toolRegistry = toolRegistry;
}
public AtomicBoolean getShouldStop() {
return shouldStop;
}
@Override
public void onOpen() {
System.out.println("串連已建立");
}
@Override
public void onClose(int code, String reason) {
System.out.println("串連已關閉");
shouldStop.set(true);
}
@Override
public void onEvent(JsonObject message) {
String type = message.get("type").getAsString();
switch (type) {
case "session.created":
handleSessionCreated(message);
break;
case "conversation.item.input_audio_transcription.completed":
handleTranscriptionCompleted(message);
break;
case "response.audio_transcript.delta":
case "response.text.delta":
handleTextDelta(message);
break;
case "response.audio.delta":
handleAudioDelta(message);
break;
case "input_audio_buffer.speech_started":
handleSpeechStarted();
break;
case "input_audio_buffer.speech_stopped":
handleSpeechStopped();
break;
case "response.function_call_arguments.done":
handleFunctionCall(message);
break;
case "response.done":
handleResponseDone();
break;
default:
break;
}
}
private void handleSessionCreated(JsonObject message) {
String sessionId = message.get("session").getAsJsonObject().get("id").getAsString();
System.out.println("start session: " + sessionId);
}
private void handleTranscriptionCompleted(JsonObject message) {
System.out.println("question: " + message.get("transcript").getAsString());
}
private void handleTextDelta(JsonObject message) {
if (isFirstText) {
isFirstText = false;
System.out.println("first text latency from vad end: " +
(System.currentTimeMillis() - lastPackageTime) + " ms");
}
String text = message.get("delta").getAsString();
responseTextRef.get().append(text);
}
private void handleAudioDelta(JsonObject message) {
if (isFirstAudio) {
isFirstAudio = false;
System.out.println("first audio latency from vad end: " +
(System.currentTimeMillis() - lastPackageTime) + " ms");
}
System.out.println("audio interval: " + (System.currentTimeMillis() - lastPackageTime) + " ms");
lastPackageTime = System.currentTimeMillis();
audioPlayer.play(message.get("delta").getAsString());
}
private void handleSpeechStarted() {
System.out.println("======VAD Speech Start======");
}
private void handleSpeechStopped() {
System.out.println("======VAD Speech End======");
lastPackageTime = System.currentTimeMillis();
isFirstText = true;
isFirstAudio = true;
}
private void handleFunctionCall(JsonObject message) {
System.out.println("======TOOL CALL======");
String callId = message.get("call_id").getAsString();
toolRegistry.addPendingToolCall(callId, message);
}
private void handleResponseDone() {
System.out.println("======RESPONSE DONE======");
System.out.println("all response text: " + responseTextRef.get());
responseTextRef.set(new StringBuilder());
}
/**
* 檢查是否有待處理的工具調用
*/
public boolean hasPendingToolCalls() {
return toolRegistry.hasPendingToolCalls();
}
/**
* 處理所有待處理的工具調用
*/
public void processPendingToolCalls(OmniRealtimeConversation conversation) {
toolRegistry.processPendingToolCalls(conversation);
}
}
}WebSocket(Python)
import asyncio
import json
import base64
import os
import pyaudio
import websockets
# ==================== 常量定義 ====================
API_KEY = os.getenv("DASHSCOPE_API_KEY")
# 如果需要訪問北京地區,請替換為:
# wss://{WorkspaceId}.cn-beijing.maas.aliyuncs.com/api-ws/v1/realtime
URL = "wss://{WorkspaceId}.ap-southeast-1.maas.aliyuncs.com/api-ws/v1/realtime"
MODEL = "qwen3.5-omni-plus-realtime"
VOICE = "Ethan"
# ==================== 工具定義 ====================
def get_current_weather(location):
"""查詢指定城市天氣"""
return f"{location}今天天氣為霾轉晴,氣溫4/-4℃,微風"
def get_flight_price(src, dst):
"""查詢飛機票價格"""
return f"{src}到{dst}的機票價格為200~300美元。"
def get_train_price(src, dst):
"""查詢火車票價格"""
return f"{src}到{dst}的火車票價格為100~200元。"
# 工具名稱到函數的映射
TOOL_FUNCTIONS = {
"get_current_weather": get_current_weather,
"get_flight_price": get_flight_price,
"get_train_price": get_train_price,
}
TOOLS = [
{
"type": "function",
"function": {
"name": "get_current_weather",
"description": "當你想查詢指定城市的天氣時非常有用。",
"parameters": {
"type": "object",
"properties": {
"location": {
"type": "string",
"description": "城市或縣區,比如北京市、杭州市、餘杭區等。",
}
},
"required": ["location"],
},
},
},
{
"type": "function",
"function": {
"name": "get_flight_price",
"description": "當你想查詢飛機票價格時非常有用。",
"parameters": {
"type": "object",
"properties": {
"src": {
"type": "string",
"description": "飛機起飛的城市,比如北京市、杭州市等。",
},
"dst": {
"type": "string",
"description": "飛機降落的城市,比如北京市、杭州市區等。",
},
},
"required": ["src", "dst"],
},
},
},
{
"type": "function",
"function": {
"name": "get_train_price",
"description": "當你想查詢火車票價格時非常有用。",
"parameters": {
"type": "object",
"properties": {
"src": {
"type": "string",
"description": "火車出發的城市,比如北京市、杭州市等。",
},
"dst": {
"type": "string",
"description": "火車到達的城市,比如北京市、杭州市區等。",
},
},
"required": ["src", "dst"],
},
},
},
]
# ==================== 工具調用處理 ====================
def handle_tool_call(name, arguments_str):
"""
處理工具調用請求
Args:
name: 工具函數名稱
arguments_str: JSON 格式的入參字串
Returns:
工具執行結果字串
"""
try:
arguments = json.loads(arguments_str)
print(f'[工具調用] 開始處理: name={name}, args={arguments}')
func = TOOL_FUNCTIONS.get(name)
if func is None:
result = f"用戶端未找到工具: {name}"
print(f'[工具調用] 錯誤: {result}')
return result
result = func(**arguments)
print(f'[工具調用] 完成: {result}')
return result
except Exception as e:
error_msg = f"工具調用失敗: {str(e)}"
print(f'[工具調用] 異常: {error_msg}')
return error_msg
# ==================== 主程式 ====================
async def main():
"""主函數:建立 WebSocket 串連並進行語音對話"""
pya = pyaudio.PyAudio()
speaker = pya.open(format=pyaudio.paInt16, channels=1, rate=24000, output=True)
# 建立 WebSocket 串連
headers = {
"Authorization": f"bearer {API_KEY}",
"X-DashScope-OmniRealtime": "true",
}
async with websockets.connect(
f"{URL}?model={MODEL}", additional_headers=headers,
) as ws:
await ws.recv()
# 配置會話參數
await ws.send(json.dumps({
"type": "session.update",
"session": {
"modalities": ["text", "audio"],
"voice": VOICE,
"input_audio_format": "pcm16",
"output_audio_format": "pcm16",
"instructions": "你是個人助理小雲",
"turn_detection": {"type": "server_vad"},
"input_audio_transcription": {"model": "qwen3-asr-flash-realtime"},
"tools": TOOLS,
},
}))
await ws.recv()
# 音頻採集協程
async def send_audio():
mic = pya.open(format=pyaudio.paInt16, channels=1, rate=16000, input=True)
try:
while True:
data = mic.read(3200, exception_on_overflow=False)
await ws.send(json.dumps({
"type": "input_audio_buffer.append",
"audio": base64.b64encode(data).decode(),
}))
await asyncio.sleep(0.01)
except asyncio.CancelledError:
mic.close()
pending = {}
all_response_text = ""
send_task = asyncio.create_task(send_audio())
print("工具調用已啟用,對著麥克風說話 (Ctrl+C 退出)...")
# 事件處理迴圈
async for raw in ws:
msg = json.loads(raw)
t = msg["type"]
# 會話建立
if t == "session.created":
print(f"會話已啟動: {msg['session']['id']}")
# 播放音頻
elif t == "response.audio.delta":
speaker.write(base64.b64decode(msg["delta"]))
# 文本增量響應
elif t in ("response.audio_transcript.delta", "response.text.delta"):
all_response_text += msg.get("delta", "")
# 使用者語音轉文本
elif t == "conversation.item.input_audio_transcription.completed":
print(f"[使用者] {msg['transcript']}")
# VAD 檢測到語音開始
elif t == "input_audio_buffer.speech_started":
print("====== VAD 檢測到語音開始 ======")
# VAD 檢測到語音結束
elif t == "input_audio_buffer.speech_stopped":
print("====== VAD 檢測到語音結束 ======")
# 收到工具調用請求
elif t == "response.function_call_arguments.done":
print("====== 收到工具調用請求 ======")
pending[msg["call_id"]] = {
"name": msg["name"],
"arguments": msg["arguments"],
}
# 響應完成
elif t == "response.done":
if pending:
# 執行待處理的工具調用
for cid, info in pending.items():
result = handle_tool_call(info["name"], info["arguments"])
# 發送工具執行結果
await ws.send(json.dumps({
"type": "conversation.item.create",
"item": {
"type": "function_call_output",
"call_id": cid,
"output": result,
},
}))
pending.clear()
# 觸發服務端繼續產生回複
await ws.send(json.dumps({
"type": "response.create",
"response": {"modalities": ["text", "audio"]},
}))
print("====== 工具調用處理完成 ======")
else:
# 普通響應完成,列印完整回複
if all_response_text:
print(f"[模型] {all_response_text}")
all_response_text = ""
send_task.cancel()
speaker.close()
pya.terminate()
asyncio.run(main())
深度思考模型的工具調用
深度思考模型在輸出工具調用資訊前先進行推理,提升決策的可解釋性與可靠性。
思考過程
模型逐步分析使用者意圖、識別所需工具、驗證參數合法性,並規劃調用策略;
工具調用
模型以結構化格式輸出一個或多個函數調用請求。
支援並行工具調用。
以下展示流式調用深度思考模型的工具調用樣本。
文本產生思考模型請參見: 深度思考 ;多模態思考模型請參見: 映像與視頻理解 、 非即時(Qwen-Omni) 。
tool_choice參數只支援設定為"auto"(預設值,表示由模型自主選擇工具)或"none"(強制模型不選擇工具)。
OpenAI相容
Python
範例程式碼
import os
from openai import OpenAI
# 初始化OpenAI用戶端,配置阿里雲DashScope服務
client = OpenAI(
# 各地區的API Key不同。擷取API Key:https://www.alibabacloud.com/help/zh/model-studio/get-api-key
# 若沒有配置環境變數,請用阿里雲百鍊API Key將下行替換為:api_key="sk-xxx",
api_key=os.getenv("DASHSCOPE_API_KEY"), # 從環境變數讀取API密鑰
base_url="https://{WorkspaceId}.ap-southeast-1.maas.aliyuncs.com/compatible-mode/v1",
)
# 定義可用工具列表
tools = [
# 工具1 擷取當前時刻的時間
{
"type": "function",
"function": {
"name": "get_current_time",
"description": "當你想知道現在的時間時非常有用。",
"parameters": {} # 無需參數
}
},
# 工具2 擷取指定城市的天氣
{
"type": "function",
"function": {
"name": "get_current_weather",
"description": "當你想查詢指定城市的天氣時非常有用。",
"parameters": {
"type": "object",
"properties": {
"location": {
"type": "string",
"description": "城市或縣區,比如北京市、杭州市、餘杭區等。"
}
},
"required": ["location"] # 必填參數
}
}
}
]
messages = [{"role": "user", "content": input("請輸入問題:")}]
# 多模態模型的 message樣本
# messages = [{
# "role": "user",
# "content": [
# {"type": "image_url","image_url": {"url": "https://img.alicdn.com/imgextra/i4/O1CN014CJhzi20NOzo7atOC_!!6000000006837-2-tps-2048-1365.png"}},
# {"type": "text", "text": "根據映像上的地點,請問該地點當前的天氣"}]
# }]
completion = client.chat.completions.create(
# 此處以qwen3.6-plus為例,可更換為其它深度思考模型
model="qwen3.6-plus",
messages=messages,
extra_body={
# 開啟深度思考,該參數對qwen3-30b-a3b-thinking-2507、qwen3-235b-a22b-thinking-2507、QwQ 模型無效
"enable_thinking": True
},
tools=tools,
parallel_tool_calls=True,
stream=True,
# 解除注釋後,可以擷取到token消耗資訊
# stream_options={
# "include_usage": True
# }
)
reasoning_content = "" # 定義完整思考過程
answer_content = "" # 定義完整回複
tool_info = [] # 儲存工具調用資訊
is_answering = False # 判斷是否結束思考過程並開始回複
print("="*20+"思考過程"+"="*20)
for chunk in completion:
if not chunk.choices:
# 處理用量統計資訊
print("\n"+"="*20+"Usage"+"="*20)
print(chunk.usage)
else:
delta = chunk.choices[0].delta
# 處理AI的思考過程(鏈式推理)
if hasattr(delta, 'reasoning_content') and delta.reasoning_content is not None:
reasoning_content += delta.reasoning_content
print(delta.reasoning_content,end="",flush=True) # 即時輸出思考過程
# 處理最終回複內容
else:
if not is_answering: # 首次進入回複階段時列印標題
is_answering = True
print("\n"+"="*20+"回複內容"+"="*20)
if delta.content is not None:
answer_content += delta.content
print(delta.content,end="",flush=True) # 流式輸出回複內容
# 處理工具調用資訊(支援並行工具調用)
if delta.tool_calls is not None:
for tool_call in delta.tool_calls:
index = tool_call.index # 工具調用索引,用於並行調用
# 動態擴充工具資訊儲存列表
while len(tool_info) <= index:
tool_info.append({})
# 收集工具調用ID(用於後續函數調用)
if tool_call.id:
tool_info[index]['id'] = tool_info[index].get('id', '') + tool_call.id
# 收集合函式名稱(用於後續路由到具體函數)
if tool_call.function and tool_call.function.name:
tool_info[index]['name'] = tool_info[index].get('name', '') + tool_call.function.name
# 收集合函式參數(JSON字串格式,需要後續解析)
if tool_call.function and tool_call.function.arguments:
tool_info[index]['arguments'] = tool_info[index].get('arguments', '') + tool_call.function.arguments
print(f"\n"+"="*19+"工具調用資訊"+"="*19)
if not tool_info:
print("沒有工具調用")
else:
print(tool_info)返回結果
輸入“四個直轄市的天氣”,得到以下返回結果:
====================思考過程====================
好的,使用者問的是“四個直轄市的天氣”。首先,我需要明確四個直轄市是哪幾個。根據中國的行政區劃,直轄市包括北京、上海、天津和重慶。所以使用者想知道這四個城市的天氣情況。
接下來,我需要檢查可用的工具。提供的工具中有get_current_weather函數,參數是location,類型字串。每個城市需要單獨查詢,因為函數一次只能查一個地點。因此,我需要為每個直轄市調用一次這個函數。
然後,我需要考慮如何產生正確的工具調用。每個調用應該包含城市名稱作為參數。比如,第一個調用是北京,第二個是上海,依此類推。確保參數名稱是location,值是正確的城市名。
另外,使用者可能希望得到每個城市的天氣資訊,所以需要確保每個函數調用都正確無誤。可能需要連續調用四次,每次對應一個城市。不過,根據工具的使用規則,可能需要分多次處理,或者一次產生多個調用。但根據樣本,可能每次只調用一個函數,所以可能需要逐步進行。
最後,確認是否有其他需要考慮的因素,比如參數是否正確,城市名稱是否準確,以及是否需要處理可能的錯誤情況,比如城市不存在或API不可用。但目前看來,四個直轄市都是明確的,應該沒問題。
====================回複內容====================
===================工具調用資訊===================
[{'id': 'call_767af2834c12488a8fe6e3', 'name': 'get_current_weather', 'arguments': '{"location": "北京市"}'}, {'id': 'call_2cb05a349c89437a947ada', 'name': 'get_current_weather', 'arguments': '{"location": "上海市"}'}, {'id': 'call_988dd180b2ca4b0a864ea7', 'name': 'get_current_weather', 'arguments': '{"location": "天津市"}'}, {'id': 'call_4e98c57ea96a40dba26d12', 'name': 'get_current_weather', 'arguments': '{"location": "重慶市"}'}]Node.js
範例程式碼
import OpenAI from "openai";
import readline from 'node:readline/promises';
import { stdin as input, stdout as output } from 'node:process';
const openai = new OpenAI({
// 各地區的API Key不同。擷取API Key:https://www.alibabacloud.com/help/zh/model-studio/get-api-key
apiKey: process.env.DASHSCOPE_API_KEY,
baseURL: "https://{WorkspaceId}.ap-southeast-1.maas.aliyuncs.com/compatible-mode/v1"
});
const tools = [
{
type: "function",
function: {
name: "get_current_time",
description: "當你想知道現在的時間時非常有用。",
parameters: {}
}
},
{
type: "function",
function: {
name: "get_current_weather",
description: "當你想查詢指定城市的天氣時非常有用。",
parameters: {
type: "object",
properties: {
location: {
type: "string",
description: "城市或縣區,比如北京市、杭州市、餘杭區等。"
}
},
required: ["location"]
}
}
}
];
async function main() {
const rl = readline.createInterface({ input, output });
const question = await rl.question("請輸入您的問題:");
rl.close();
const messages = [{ role: "user", content: question }];
// 多模態模型的 message樣本
// const messages= [{
// role: "user",
// content: [{type: "image_url", image_url: {url: "https://img.alicdn.com/imgextra/i2/O1CN01FbTJon1ErXVGMRdsN_!!6000000000405-0-tps-1024-683.jpg"}},
// {type: "text", text: "圖中地點的天氣?"}]
// }];
let reasoningContent = "";
let answerContent = "";
const toolInfo = [];
let isAnswering = false;
console.log("=".repeat(20) + "思考過程" + "=".repeat(20));
try {
const stream = await openai.chat.completions.create({
// 此處以qwen3.6-plus為例,可更換為其它深度思考模型
model: "qwen3.6-plus",
messages,
// 開啟深度思考,該參數對qwen3-30b-a3b-thinking-2507、qwen3-235b-a22b-thinking-2507、QwQ 模型無效
enable_thinking: true,
tools,
stream: true,
parallel_tool_calls: true
});
for await (const chunk of stream) {
if (!chunk.choices?.length) {
console.log("\n" + "=".repeat(20) + "Usage" + "=".repeat(20));
console.log(chunk.usage);
continue;
}
const delta = chunk.choices[0]?.delta;
if (!delta) continue;
// 處理思考過程
if (delta.reasoning_content) {
reasoningContent += delta.reasoning_content;
process.stdout.write(delta.reasoning_content);
}
// 處理回複內容
else {
if (!isAnswering) {
isAnswering = true;
console.log("\n" + "=".repeat(20) + "回複內容" + "=".repeat(20));
}
if (delta.content) {
answerContent += delta.content;
process.stdout.write(delta.content);
}
// 處理工具調用
if (delta.tool_calls) {
for (const toolCall of delta.tool_calls) {
const index = toolCall.index;
// 確保數組長度足夠
while (toolInfo.length <= index) {
toolInfo.push({});
}
// 更新工具ID
if (toolCall.id) {
toolInfo[index].id = (toolInfo[index].id || "") + toolCall.id;
}
// 更新函數名稱
if (toolCall.function?.name) {
toolInfo[index].name = (toolInfo[index].name || "") + toolCall.function.name;
}
// 更新參數
if (toolCall.function?.arguments) {
toolInfo[index].arguments = (toolInfo[index].arguments || "") + toolCall.function.arguments;
}
}
}
}
}
console.log("\n" + "=".repeat(19) + "工具調用資訊" + "=".repeat(19));
console.log(toolInfo.length ? toolInfo : "沒有工具調用");
} catch (error) {
console.error("發生錯誤:", error);
}
}
main(); 返回結果
輸入“四個直轄市的天氣”,得到以下返回結果:
請輸入您的問題:四個直轄市的天氣
====================思考過程====================
好的,使用者問的是四個直轄市的天氣。首先,我需要明確中國的四個直轄市分別是哪幾個。北京、上海、天津和重慶,對吧?接下來,我需要為每個城市調用天氣查詢功能。
但是使用者的問題可能需要我分別擷取這四個城市的天氣情況。每個城市都需要調用一次get_current_weather函數,參數是各自的城市名稱。我需要確保參數正確,比如直轄市的全名,比如“北京市”、“上海市”、“天津市”和“重慶市”。
然後,我需要按照順序依次調用這四個城市的天氣介面。每個調用都需要單獨的tool_call。使用者可能希望得到每個城市的當前天氣資訊,所以需要確保每個調用都正確無誤。可能需要注意每個城市的正確拼字和名稱,避免出現錯誤。例如,重慶有時可能被簡稱為“重慶市”,所以參數裡應該用全稱。
現在,我需要產生四個tool_call,每個對應一個直轄市。檢查每個參數是否正確,然後按順序排列。這樣使用者就能得到四個直轄市的天氣資料了。
====================回複內容====================
===================工具調用資訊===================
[
{
id: 'call_21dc802e717f491298d1b2',
name: 'get_current_weather',
arguments: '{"location": "北京市"}'
},
{
id: 'call_2cd3be1d2f694c4eafd4e5',
name: 'get_current_weather',
arguments: '{"location": "上海市"}'
},
{
id: 'call_48cf3f78e02940bd9085e4',
name: 'get_current_weather',
arguments: '{"location": "天津市"}'
},
{
id: 'call_e230a2b4c64f4e658d223e',
name: 'get_current_weather',
arguments: '{"location": "重慶市"}'
}
]HTTP
範例程式碼
curl
curl -X POST https://{WorkspaceId}.ap-southeast-1.maas.aliyuncs.com/compatible-mode/v1/chat/completions \
-H "Authorization: Bearer $DASHSCOPE_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"model": "qwen3.6-plus",
"messages": [
{
"role": "user",
"content": "杭州天氣怎麼樣"
}
],
"tools": [
{
"type": "function",
"function": {
"name": "get_current_time",
"description": "當你想知道現在的時間時非常有用。",
"parameters": {}
}
},
{
"type": "function",
"function": {
"name": "get_current_weather",
"description": "當你想查詢指定城市的天氣時非常有用。",
"parameters": {
"type": "object",
"properties": {
"location":{
"type": "string",
"description": "城市或縣區,比如北京市、杭州市、餘杭區等。"
}
},
"required": ["location"]
}
}
}
],
"enable_thinking": true,
"stream": true
}'DashScope
Python
範例程式碼
import dashscope
from dashscope import MultiModalConversation
# 如果使用北京地區的模型,需要將base_http_api_url替換為:https://{WorkspaceId}.cn-beijing.maas.aliyuncs.com/api/v1
dashscope.base_http_api_url = "https://{WorkspaceId}.ap-southeast-1.maas.aliyuncs.com/api/v1/"
tools = [
# 工具1 擷取當前時刻的時間
{
"type": "function",
"function": {
"name": "get_current_time",
"description": "當你想知道現在的時間時非常有用。",
"parameters": {} # 因為擷取目前時間無需輸入參數,因此parameters為空白字典
}
},
# 工具2 擷取指定城市的天氣
{
"type": "function",
"function": {
"name": "get_current_weather",
"description": "當你想查詢指定城市的天氣時非常有用。",
"parameters": {
"type": "object",
"properties": {
# 查詢天氣時需要提供位置,因此參數設定為location
"location": {
"type": "string",
"description": "城市或縣區,比如北京市、杭州市、餘杭區等。"
}
},
"required": ["location"]
}
}
}
]
# 定義問題
messages = [{"role": "user", "content": [{"text": input("請輸入問題:")}]}]
# 多模態模型的 message樣本
# messages = [
# {
# "role": "user",
# "content": [
# {"image": "https://img.alicdn.com/imgextra/i2/O1CN01FbTJon1ErXVGMRdsN_!!6000000000405-0-tps-1024-683.jpg"},
# {"text": "映像地點天氣?"}]
# }]
completion = MultiModalConversation.call(
# 此處以qwen3.6-plus為例,可更換為其它深度思考模型
model="qwen3.6-plus",
messages=messages,
enable_thinking=True,
tools=tools,
parallel_tool_calls=True,
stream=True,
incremental_output=True,
result_format="message"
)
reasoning_content = ""
answer_content = ""
tool_info = []
is_answering = False
print("="*20+"思考過程"+"="*20)
for chunk in completion:
if chunk.status_code == 200:
msg = chunk.output.choices[0].message
# 處理思考過程
if 'reasoning_content' in msg and msg.reasoning_content:
reasoning_content += msg.reasoning_content
print(msg.reasoning_content, end="", flush=True)
# 處理回複內容
if 'content' in msg and msg.content:
if not is_answering:
is_answering = True
print("\n"+"="*20+"回複內容"+"="*20)
answer_content += msg.content
print(msg.content, end="", flush=True)
# 處理工具調用
if 'tool_calls' in msg and msg.tool_calls:
for tool_call in msg.tool_calls:
index = tool_call['index']
while len(tool_info) <= index:
tool_info.append({'id': '', 'name': '', 'arguments': ''}) # 初始化所有欄位
# 累加式更新工具ID
if 'id' in tool_call:
tool_info[index]['id'] += tool_call.get('id', '')
# 累加式更新函數資訊
if 'function' in tool_call:
func = tool_call['function']
# 累加式更新函數名稱
if 'name' in func:
tool_info[index]['name'] += func.get('name', '')
# 累加式更新參數
if 'arguments' in func:
tool_info[index]['arguments'] += func.get('arguments', '')
print(f"\n"+"="*19+"工具調用資訊"+"="*19)
if not tool_info:
print("沒有工具調用")
else:
print(tool_info)返回結果
輸入“四個直轄市的天氣”,得到以下返回結果:
請輸入問題:四個直轄市的天氣
====================思考過程====================
好的,使用者問的是四個直轄市的天氣。首先,我需要確認中國的四個直轄市分別是哪些。北京、上海、天津和重慶,對吧?接下來,使用者需要每個城市的天氣情況,所以我需要調用天氣查詢功能。
不過,問題來了,使用者沒有指定具體的城市名稱,只是說四個直轄市。可能需要我明確每個直轄市的名稱,然後分別查詢。比如,北京、上海、天津、重慶這四個直轄市。我需要確保每個城市都正確無誤。
然後,我要檢查可用的工具,使用者提供的函數是get_current_weather,參數是location。因此,我需要為每個直轄市調用這個函數,傳入對應的城市名稱作為參數。比如,第一次調用location是北京市,第二次是上海市,第三次是天津市,第四次是重慶市。
不過,可能需要注意,像重慶這樣的直轄市,有時候可能需要更具體的區,但使用者可能只需要市級的天氣。所以直接使用直轄市名稱應該沒問題。接下來,我需要產生四個獨立的函數調用,每個對應一個直轄市。這樣使用者就能得到四個城市的天氣情況了。
最後,確保每個調用的參數正確,並且沒有遺漏。這樣使用者的問題就能得到完整的回答了。
===================工具調用資訊===================
[{'id': 'call_2f774ed97b0e4b24ab10ec', 'name': 'get_current_weather', 'arguments': '{"location": "北京市"}'}, {'id': 'call_dc3b05b88baa48c58bc33a', 'name': 'get_current_weather', 'arguments': '{"location": "上海市"}}'}, {'id': 'call_249b2de2f73340cdb46cbc', 'name': 'get_current_weather', 'arguments': '{"location": "天津市"}'}, {'id': 'call_833333634fda49d1b39e87', 'name': 'get_current_weather', 'arguments': '{"location": "重慶市"}}'}]Java
範例程式碼
// dashscope SDK的版本 >= 2.19.4
import java.util.Arrays;
import com.alibaba.dashscope.exception.UploadFileException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.alibaba.dashscope.aigc.multimodalconversation.MultiModalConversation;
import com.alibaba.dashscope.aigc.multimodalconversation.MultiModalConversationParam;
import com.alibaba.dashscope.aigc.multimodalconversation.MultiModalConversationResult;
import com.alibaba.dashscope.common.MultiModalMessage;
import com.alibaba.dashscope.aigc.generation.Generation;
import com.alibaba.dashscope.aigc.generation.GenerationParam;
import com.alibaba.dashscope.aigc.generation.GenerationResult;
import com.alibaba.dashscope.common.Message;
import com.alibaba.dashscope.common.Role;
import com.alibaba.dashscope.exception.ApiException;
import com.alibaba.dashscope.exception.InputRequiredException;
import com.alibaba.dashscope.exception.NoApiKeyException;
import com.alibaba.dashscope.utils.Constants;
import com.alibaba.dashscope.utils.JsonUtils;
import com.alibaba.dashscope.tools.ToolFunction;
import com.alibaba.dashscope.tools.FunctionDefinition;
import io.reactivex.Flowable;
import com.fasterxml.jackson.databind.node.ObjectNode;
import java.lang.System;
import com.github.victools.jsonschema.generator.Option;
import com.github.victools.jsonschema.generator.OptionPreset;
import com.github.victools.jsonschema.generator.SchemaGenerator;
import com.github.victools.jsonschema.generator.SchemaGeneratorConfig;
import com.github.victools.jsonschema.generator.SchemaGeneratorConfigBuilder;
import com.github.victools.jsonschema.generator.SchemaVersion;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.Collections;
public class Main {
private static final Logger logger = LoggerFactory.getLogger(Main.class);
private static ObjectNode jsonSchemaWeather;
private static ObjectNode jsonSchemaTime;
static {Constants.baseHttpApiUrl="https://{WorkspaceId}.ap-southeast-1.maas.aliyuncs.com/api/v1";}
static class TimeTool {
public String call() {
LocalDateTime now = LocalDateTime.now();
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
return "目前時間:" + now.format(formatter) + "。";
}
}
static class WeatherTool {
private String location;
public WeatherTool(String location) {
this.location = location;
}
public String call() {
return location + "今天是晴天";
}
}
static {
SchemaGeneratorConfigBuilder configBuilder = new SchemaGeneratorConfigBuilder(
SchemaVersion.DRAFT_2020_12, OptionPreset.PLAIN_JSON);
SchemaGeneratorConfig config = configBuilder
.with(Option.EXTRA_OPEN_API_FORMAT_VALUES)
.without(Option.FLATTENED_ENUMS_FROM_TOSTRING)
.build();
SchemaGenerator generator = new SchemaGenerator(config);
jsonSchemaWeather = generator.generateSchema(WeatherTool.class);
jsonSchemaTime = generator.generateSchema(TimeTool.class);
}
private static void handleGenerationResult(GenerationResult message) {
System.out.println(JsonUtils.toJson(message));
}
// 為文本產生模型建立工具調用方法
public static void streamCallWithMessage(Generation gen, Message userMsg)
throws NoApiKeyException, ApiException, InputRequiredException {
GenerationParam param = buildGenerationParam(userMsg);
Flowable<GenerationResult> result = gen.streamCall(param);
result.blockingForEach(message -> handleGenerationResult(message));
}
// 構建文本產生模型參數,支援工具調用
private static GenerationParam buildGenerationParam(Message userMsg) {
FunctionDefinition fdWeather = buildFunctionDefinition(
"get_current_weather", "擷取指定地區的天氣", jsonSchemaWeather);
FunctionDefinition fdTime = buildFunctionDefinition(
"get_current_time", "擷取當前時刻的時間", jsonSchemaTime);
return GenerationParam.builder()
.apiKey(System.getenv("DASHSCOPE_API_KEY"))
.model("qwen3.6-plus")
.enableThinking(true)
.messages(Arrays.asList(userMsg))
.resultFormat(GenerationParam.ResultFormat.MESSAGE)
.incrementalOutput(true)
.tools(Arrays.asList(
ToolFunction.builder().function(fdWeather).build(),
ToolFunction.builder().function(fdTime).build()))
.build();
}
// 為多模態模型建立工具調用方法
public static void streamCallWithMultiModalMessage(MultiModalConversation conv, MultiModalMessage userMsg)
throws NoApiKeyException, ApiException, UploadFileException {
MultiModalConversationParam param = buildMultiModalConversationParam(userMsg);
Flowable<MultiModalConversationResult> result = conv.streamCall(param);
result.blockingForEach(message -> System.out.println(JsonUtils.toJson(message)));
}
// 構建多模態模型參數,支援工具調用
private static MultiModalConversationParam buildMultiModalConversationParam(MultiModalMessage userMsg) {
FunctionDefinition fdWeather = buildFunctionDefinition(
"get_current_weather", "擷取指定地區的天氣", jsonSchemaWeather);
FunctionDefinition fdTime = buildFunctionDefinition(
"get_current_time", "擷取當前時刻的時間", jsonSchemaTime);
return MultiModalConversationParam.builder()
.apiKey(System.getenv("DASHSCOPE_API_KEY"))
.model("qwen3-vl-plus") // 使用多模態模型 Qwen3-VL
.enableThinking(true)
.messages(Arrays.asList(userMsg))
.tools(Arrays.asList( // 組態工具列表
ToolFunction.builder().function(fdWeather).build(),
ToolFunction.builder().function(fdTime).build()))
.build();
}
private static FunctionDefinition buildFunctionDefinition(
String name, String description, ObjectNode schema) {
return FunctionDefinition.builder()
.name(name)
.description(description)
.parameters(JsonUtils.parseString(schema.toString()).getAsJsonObject())
.build();
}
public static void main(String[] args) {
try {
MultiModalConversation conv = new MultiModalConversation();
MultiModalMessage userMsg = MultiModalMessage.builder().role(Role.USER.getValue())
.content(Arrays.asList(Collections.singletonMap("text", "請告訴我杭州的天氣"))).build();
try {
streamCallWithMultiModalMessage(conv, userMsg);
} catch (UploadFileException e) {
throw new RuntimeException(e);
}
// 使用文本產生模型進行工具調用時,請解除下列注釋
// Generation gen = new Generation();
// Message userMessage = Message.builder()
// .role(Role.USER.getValue())
// .content("請告訴我杭州的天氣")
// .build();
// try {
// streamCallWithMessage(gen, userMessage);
// } catch (InputRequiredException e) {
// throw new RuntimeException(e);
// }
} catch (ApiException | NoApiKeyException e) {
logger.error("An exception occurred: {}", e.getMessage());
}
System.exit(0);
}
}
返回結果
{"requestId":"4edb81cd-4647-9d5d-88f9-a4f30bc6d8dd","usage":{"input_tokens":238,"output_tokens":6,"total_tokens":244},"output":{"choices":[{"finish_reason":"null","message":{"role":"assistant","content":"","reasoning_content":"好的,使用者讓我"}}]}}
{"requestId":"4edb81cd-4647-9d5d-88f9-a4f30bc6d8dd","usage":{"input_tokens":238,"output_tokens":12,"total_tokens":250},"output":{"choices":[{"finish_reason":"null","message":{"role":"assistant","content":"","reasoning_content":"告訴杭州的天氣。我"}}]}}
{"requestId":"4edb81cd-4647-9d5d-88f9-a4f30bc6d8dd","usage":{"input_tokens":238,"output_tokens":16,"total_tokens":254},"output":{"choices":[{"finish_reason":"null","message":{"role":"assistant","content":"","reasoning_content":"需要先確定是否有"}}]}}
{"requestId":"4edb81cd-4647-9d5d-88f9-a4f30bc6d8dd","usage":{"input_tokens":238,"output_tokens":22,"total_tokens":260},"output":{"choices":[{"finish_reason":"null","message":{"role":"assistant","content":"","reasoning_content":"相關的工具可用。查看提供的"}}]}}
{"requestId":"4edb81cd-4647-9d5d-88f9-a4f30bc6d8dd","usage":{"input_tokens":238,"output_tokens":28,"total_tokens":266},"output":{"choices":[{"finish_reason":"null","message":{"role":"assistant","content":"","reasoning_content":"工具,發現有一個get_current"}}]}}
{"requestId":"4edb81cd-4647-9d5d-88f9-a4f30bc6d8dd","usage":{"input_tokens":238,"output_tokens":34,"total_tokens":272},"output":{"choices":[{"finish_reason":"null","message":{"role":"assistant","content":"","reasoning_content":"_weather函數,參數是location"}}]}}
{"requestId":"4edb81cd-4647-9d5d-88f9-a4f30bc6d8dd","usage":{"input_tokens":238,"output_tokens":38,"total_tokens":276},"output":{"choices":[{"finish_reason":"null","message":{"role":"assistant","content":"","reasoning_content":"。所以應該調"}}]}}
{"requestId":"4edb81cd-4647-9d5d-88f9-a4f30bc6d8dd","usage":{"input_tokens":238,"output_tokens":43,"total_tokens":281},"output":{"choices":[{"finish_reason":"null","message":{"role":"assistant","content":"","reasoning_content":"用這個函數,參數"}}]}}
{"requestId":"4edb81cd-4647-9d5d-88f9-a4f30bc6d8dd","usage":{"input_tokens":238,"output_tokens":48,"total_tokens":286},"output":{"choices":[{"finish_reason":"null","message":{"role":"assistant","content":"","reasoning_content":"設為杭州。不需要"}}]}}
{"requestId":"4edb81cd-4647-9d5d-88f9-a4f30bc6d8dd","usage":{"input_tokens":238,"output_tokens":52,"total_tokens":290},"output":{"choices":[{"finish_reason":"null","message":{"role":"assistant","content":"","reasoning_content":"其他工具,因為"}}]}}
{"requestId":"4edb81cd-4647-9d5d-88f9-a4f30bc6d8dd","usage":{"input_tokens":238,"output_tokens":56,"total_tokens":294},"output":{"choices":[{"finish_reason":"null","message":{"role":"assistant","content":"","reasoning_content":"使用者只問了"}}]}}
{"requestId":"4edb81cd-4647-9d5d-88f9-a4f30bc6d8dd","usage":{"input_tokens":238,"output_tokens":60,"total_tokens":298},"output":{"choices":[{"finish_reason":"null","message":{"role":"assistant","content":"","reasoning_content":"天氣。接下來構造"}}]}}
{"requestId":"4edb81cd-4647-9d5d-88f9-a4f30bc6d8dd","usage":{"input_tokens":238,"output_tokens":64,"total_tokens":302},"output":{"choices":[{"finish_reason":"null","message":{"role":"assistant","content":"","reasoning_content":"tool_call,把"}}]}}
{"requestId":"4edb81cd-4647-9d5d-88f9-a4f30bc6d8dd","usage":{"input_tokens":238,"output_tokens":68,"total_tokens":306},"output":{"choices":[{"finish_reason":"null","message":{"role":"assistant","content":"","reasoning_content":"名稱和參數填"}}]}}
{"requestId":"4edb81cd-4647-9d5d-88f9-a4f30bc6d8dd","usage":{"input_tokens":238,"output_tokens":73,"total_tokens":311},"output":{"choices":[{"finish_reason":"null","message":{"role":"assistant","content":"","reasoning_content":"進去。確保參數是"}}]}}
{"requestId":"4edb81cd-4647-9d5d-88f9-a4f30bc6d8dd","usage":{"input_tokens":238,"output_tokens":78,"total_tokens":316},"output":{"choices":[{"finish_reason":"null","message":{"role":"assistant","content":"","reasoning_content":"JSON對象,location是"}}]}}
{"requestId":"4edb81cd-4647-9d5d-88f9-a4f30bc6d8dd","usage":{"input_tokens":238,"output_tokens":82,"total_tokens":320},"output":{"choices":[{"finish_reason":"null","message":{"role":"assistant","content":"","reasoning_content":"字串。檢查無"}}]}}
{"requestId":"4edb81cd-4647-9d5d-88f9-a4f30bc6d8dd","usage":{"input_tokens":238,"output_tokens":88,"total_tokens":326},"output":{"choices":[{"finish_reason":"null","message":{"role":"assistant","content":"","reasoning_content":"誤後返回。"}}]}}
{"requestId":"4edb81cd-4647-9d5d-88f9-a4f30bc6d8dd","usage":{"input_tokens":238,"output_tokens":106,"total_tokens":344},"output":{"choices":[{"finish_reason":"null","message":{"role":"assistant","content":"","reasoning_content":"","tool_calls":[{"type":"function","id":"call_ecc41296dccc47baa01567","function":{"name":"get_current_weather","arguments":"{\"location\": \"杭州"}}]}}]}}
{"requestId":"4edb81cd-4647-9d5d-88f9-a4f30bc6d8dd","usage":{"input_tokens":238,"output_tokens":108,"total_tokens":346},"output":{"choices":[{"finish_reason":"tool_calls","message":{"role":"assistant","content":"","reasoning_content":"","tool_calls":[{"type":"function","id":"","function":{"arguments":"\"}"}}]}}]}}HTTP
範例程式碼
curl
# ======= 重要提示 =======
# 如果使用純文字產生模型,請將url替換為 https://{WorkspaceId}.ap-southeast-1.maas.aliyuncs.com/api/v1/services/aigc/text-generation/generation
# 各地區的API Key不同。擷取API Key:https://www.alibabacloud.com/help/zh/model-studio/get-api-key
# 以下為新加坡地區URL,調用時請將WorkspaceId替換為真實的業務空間ID,各地區的URL不同。
# === 執行時請刪除該注釋 ===
curl -X POST "https://{WorkspaceId}.ap-southeast-1.maas.aliyuncs.com/api/v1/services/aigc/multimodal-generation/generation" \
-H "Authorization: Bearer $DASHSCOPE_API_KEY" \
-H "Content-Type: application/json" \
-H "X-DashScope-SSE: enable" \
-d '{
"model": "qwen3.6-plus",
"input":{
"messages":[
{
"role": "user",
"content": [{"text": "杭州天氣"}]
}
]
},
"parameters": {
"enable_thinking": true,
"incremental_output": true,
"result_format": "message",
"tools": [{
"type": "function",
"function": {
"name": "get_current_time",
"description": "當你想知道現在的時間時非常有用。",
"parameters": {}
}
},{
"type": "function",
"function": {
"name": "get_current_weather",
"description": "當你想查詢指定城市的天氣時非常有用。",
"parameters": {
"type": "object",
"properties": {
"location": {
"type": "string",
"description": "城市或縣區,比如北京市、杭州市、餘杭區等。"
}
},
"required": ["location"]
}
}
}]
}
}'應用於生產環境
測試載入器調用準確率
建立評估體系:
構建貼近真實業務的測試資料集,定義清晰的評估指標,如工具選擇準確率、參數提取準確率、端到端成功率等。
最佳化提示詞
根據測試暴露的具體問題(選錯工具、參數錯誤),針對性最佳化系統提示詞、工具描述和參數描述。
升級模型
提示詞調優無法提升效能時,升級到更強的模型版本(如
qwen3.6-plus)是最直接有效方法。
動態控制工具數量
應用整合的工具數量達到幾十甚至上百個時,將全部工具提供給模型會帶來以下問題:
效能下降:模型在龐大的工具集中選擇正確工具的難度劇增;
成本與延遲:大量的工具描述會消耗巨量的輸入 Token,導致費用上升和響應變慢;
解決方案:在調用模型前增加工具路由/檢索層,根據使用者查詢從工具庫中篩選出小而相關的工具子集,再提供給模型。
實現工具路由的幾種主流方法:
語義檢索
將工具描述資訊(
description)通過 Embedding 模型轉化為向量,並存入向量資料庫。當使用者查詢時,將查詢向量通過向量相似性搜尋,召回最相關的 Top-K 個工具。混合檢索
將語義檢索的“模糊比對”能力與傳統關鍵詞或中繼資料標籤的“精確匹配”能力相結合。為工具添加
tags或keywords欄位,檢索時同時進行向量搜尋和關鍵詞過濾,可以大幅提升高頻或特定情境下的召回精準度。輕量級 LLM 路由器
對於更複雜的路由邏輯,可以使用一個更小、更快、更便宜的模型(如 Qwen-Flash)作為前置“路由模型”。它的任務是根據使用者問題輸出相關的工具名稱列表。
實踐建議
保持候選集精簡:無論使用何種方法,最終提供給主模型的工具數量建議不超過 20 個。這是在模型認知負荷、成本、延遲和準確率之間的最佳平衡點。
分層過濾策略:可以構建一個漏鬥式的路由策略。例如,先用成本極低的關鍵詞/規則匹配進行第一輪篩選,過濾掉明顯不相關的工具,再對剩餘的工具進行語義檢索,從而提高效率和品質。
工具安全性原則
向大模型開放工具執行能力時,安全是首要考量。核心原則:“最小許可權”和“人類確認”。
最小許可權原則:為模型提供的工具集應嚴格遵守最小許可權原則。預設情況下,工具應是唯讀(如查詢天氣、搜尋文檔),避免直接提供任何涉及狀態變更或資源操作的“寫”許可權。
危險工具隔離:請勿向大模型直接提供危險工具,例如執行任意代碼(
code interpreter)、操作檔案系統(fs.delete)、執行資料庫刪除或更新操作(db.drop_table)或涉及資金流轉的工具(payment.transfer)。人類參與:對於所有高許可權或無法復原的操作,必須引入人工審核和確認環節。模型可以產生操作請求,但最終的執行“按鈕”必須由人類使用者點擊。例如,模型可以準備好一封郵件,但發送操作需要使用者確認。
使用者體驗最佳化
Function Calling 鏈路較長,任何環節出問題都可能影響使用者體驗。
處理工具運行失敗
工具運行失敗是常見情況,可採取以下策略:
最大重試次數:設定合理的重試上限(例如 3 次),避免因連續失敗導致使用者長時間等待或系統資源浪費。
提供兜底話術:當重試耗盡或遇到無法解決的錯誤時,應向使用者返回清晰、友好的提示資訊,例如:“抱歉,我暫時無法查詢到相關資訊,可能是服務有些繁忙,請您稍後再試。”
應對處理延遲
較高延遲會降低使用者滿意度,需從前端互動和後端兩方面最佳化。
設定逾時時間:為 Function Calling 的每一步設定獨立且合理的逾時時間。一旦逾時,應立即中斷操作並給出反饋。
提供即時反饋:開始執行 Function Calling 時,建議在介面上給出提示,如“正在為您查詢天氣...”、“正在搜尋相關資訊...”,向使用者即時反饋處理進度。
計費說明
除 messages 數組中的 Token 外,工具描述資訊也作為輸入 Token 計費。
通過 System Message 傳入工具資訊
錯誤碼
如果模型調用失敗並返回報錯資訊,請參見錯誤碼進行解決。