大規模言語モデル (LLM) は、リアルタイムデータや外部システムにアクセスできません。関数呼び出しを使用すると、モデルは API、データベース、ユーザー定義関数などの外部ツールを呼び出すことができます。これにより、モデルは組み込みの機能を超えて情報を取得したり、アクションを実行したりできます。
仕組み
関数呼び出しは、アプリケーションと LLM 間の複数ステップの対話を通じて機能します。
最初のモデル呼び出しの実行
アプリケーションは、ユーザーの質問と利用可能なツールのリストを LLM に送信します。
モデルからツール呼び出し命令の受信
モデルが外部ツールを呼び出すことを決定した場合、関数名と入力パラメーターを指定する JSON 命令を返します。
モデルがツールを呼び出さないと決定した場合、自然言語応答を返します。
アプリケーションでのツールの実行
アプリケーションは指定されたツールを実行し、出力を取得します。
2 回目のモデル呼び出しの実行
ツールの出力を `messages` 配列に追加し、モデルを再度呼び出します。
モデルから最終応答の受信
モデルは、ツールの出力とユーザーの質問を組み合わせて、自然言語応答を生成します。
次の図は、ワークフローを示しています。
サポートされているモデル
Qwen
テキスト生成モデル
Qwen-Max:Qwen3.7-Max シリーズ、Qwen3.6-Max シリーズ、Qwen3-Max シリーズ、および Qwen-Max シリーズ
Qwen-Plus:Qwen3.7-Plus、Qwen3.6-Plus、Qwen3.5-Plus、および Qwen-Plus シリーズ
Qwen-Flash:Qwen3.6-Flash シリーズ、Qwen3.5-Flash シリーズ、および Qwen-Flash シリーズ
Qwen-Coder:Qwen3-Coder シリーズ、Qwen2.5-Coder シリーズ、および Qwen-Coder シリーズ
Qwen-Turbo:Qwen-Turbo シリーズ
Qwen3.6 オープンソースシリーズ
Qwen3.5 オープンソースシリーズ
Qwen3 オープンソースシリーズ
Qwen2.5 オープンソースシリーズ
マルチモーダルモデル
Qwen-VL:Qwen3-VL-Plus シリーズおよび Qwen3-VL-Flash シリーズ
Qwen-Omni:Qwen3.5-Omni-Plus シリーズ、Qwen3.5-Omni-Flash シリーズ、および Qwen3-Omni-Flash シリーズ
Qwen-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 シリーズモデルで関数呼び出しを使用する場合、リクエストに `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 キーを取得し、環境変数として設定してください。OpenAI SDK または DashScope SDK を使用する場合は、SDK をインストールする必要もあります。
次の例は、天気クエリシナリオの完全な関数呼び出しフローを示しています。
OpenAI 互換
from openai import OpenAI
from datetime import datetime
import json
import os
import random
client = OpenAI(
# API キーはリージョンによって異なります。API キーを取得するには、https://www.alibabacloud.com/help/model-studio/get-api-key をご参照ください
# 環境変数を設定していない場合は、次の行を Model Studio API キーに置き換えてください: 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 = "What's the weather like in Singapore?"
# ツールリストを定義
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 = ["Sunny", "Cloudy", "Rainy"]
random_weather = random.choice(weather_conditions)
location = arguments["location"]
return f"The weather in {location} today is {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"No tool call needed. Direct response: {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"Calling tool [{func_name}], arguments: {arguments}")
# ツールを実行
tool_result = get_current_weather(arguments)
# ツールリターンメッセージを構築
tool_message = {
"role": "tool",
"tool_call_id": tool_call_id,
"content": tool_result, # 元のツール出力を保持
}
print(f"Tool returns: {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's final response: {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 = ["Sunny", "Cloudy", "Rainy"];
const randomWeather = weatherConditions[Math.floor(Math.random() * weatherConditions.length)];
const location = args.location;
return `The weather in ${location} today is ${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 = "What's the weather like in Singapore?";
let messages = [
{
role: "user",
content: input,
}
];
let response = await getResponse(messages);
let assistantOutput = response.choices[0].message;
// コンテンツが null でないことを確認
if (!assistantOutput.content) assistantOutput.content = "";
messages.push(assistantOutput);
// ツール呼び出しが必要かどうかを判断
if (!assistantOutput.tool_calls) {
console.log(`No tool call needed. Direct response: ${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(`Calling tool [${funcName}], arguments:`, funcArgs);
// ツールを実行
const toolResult = getCurrentWeather(funcArgs);
// ツールリターンメッセージを構築
const toolMessage = {
role: "tool",
tool_call_id: toolCallId,
content: toolResult,
};
console.log(`Tool returns: ${toolMessage.content}`);
messages.push(toolMessage);
// モデルを再度呼び出して自然言語の要約を取得
response = await getResponse(messages);
assistantOutput = response.choices[0].message;
if (!assistantOutput.content) assistantOutput.content = "";
messages.push(assistantOutput);
}
console.log(`Assistant's final response: ${assistantOutput.content}`);
}
};
// プログラムを開始
main().catch(console.error);DashScope
import os
from dashscope import MultiModalConversation
import dashscope
import json
import random
# 次の 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 = ["Sunny", "Cloudy", "Rainy"]
random_weather = random.choice(weather_conditions)
location = arguments["location"]
return f"The weather in {location} today is {random_weather}."
# 3. モデル応答関数をカプセル化
def get_response(messages):
response = MultiModalConversation.call(
# API キーはリージョンによって異なります。API キーを取得するには、https://www.alibabacloud.com/help/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 などのテキスト専用モデルを呼び出すには、テキスト専用モデル API を使用します。詳細については、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": "What's the weather like in Singapore?"}]
}
]
# 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"No tool call needed. Direct response: {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"Calling tool [{func_name}], arguments: {arguments}")
# 対応するツール関数を実行
tool_result = get_current_weather(arguments)
# ツールリターンメッセージを構築
tool_message = {
"role": "tool",
"content": tool_result,
"tool_call_id": tool_call_id
}
print(f"Tool returns: {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"Assistant's final response: {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 のコンテンツからプレーンテキストを抽出します。
* コンテンツのフォーマットは List<Map<String, String>> です。例: [{text=The weather is sunny}]
*/
@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("Sunny", "Cloudy", "Rainy");
String randomWeather = weatherConditions.get(new Random().nextInt(weatherConditions.size()));
return "The weather in " + location + " today is " + randomWeather + ".";
} catch (Exception e) {
// プログラムの堅牢性を確保するための例外処理。
return "Failed to parse location parameter.";
}
}
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();
// 次の URL は中国 (北京) リージョン用です。{WorkspaceId} を実際のワークスペース ID に置き換えてください。URL はリージョンによって異なります。
MultiModalConversation conv = new MultiModalConversation(Protocol.HTTP.getValue(), "https://{WorkspaceId}.cn-beijing.maas.aliyuncs.com/api/v1");
String userInput = "What's the weather like in Singapore?";
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 などのテキスト専用モデルを呼び出すには、テキスト専用モデル API を使用します。詳細については、https://www.alibabacloud.com/help/model-studio/qwen-api-via-dashscope をご参照ください
.enableThinking(false)
.apiKey(System.getenv("DASHSCOPE_API_KEY")) // 環境変数から API キーを取得します。API キーはリージョンによって異なります。API キーを取得するには、https://www.alibabacloud.com/help/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("No tool call needed. Direct response: " + 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("Calling tool [" + funcName + "], arguments: " + arguments);
// ツール名に基づいて、対応する Java メソッドをローカルで実行します。
String toolResult = getCurrentWeather(arguments);
// ツールの実行結果を含む "tool" ロールのメッセージを構築します。
MultiModalMessage toolMessage = MultiModalMessage.builder()
.role("tool")
.toolCallId(toolCall.getId())
.content(Arrays.asList(Collections.singletonMap("text", toolResult)))
.build();
System.out.println("Tool returns: " + 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("Assistant's final response: " + getTextContent(assistantOutput.getContent()));
}
} catch (NoApiKeyException | UploadFileException e) {
System.err.println("Error: " + e.getMessage());
} catch (Exception e) {
e.printStackTrace();
}
}
}コードを実行すると、次の出力が表示されます。
Calling tool [get_current_weather], arguments: {'location': 'Singapore'}
Tool returns: The weather in Singapore today is Cloudy.
Assistant's final response: The weather in Singapore today is cloudy.利用方法
関数呼び出しは、ツール情報を渡す 2 つの方法をサポートしています。
方法 1: tools パラメーターを介して情報を渡す (推奨)
詳細については、「利用方法」をご参照ください。手順に従って、ツールを定義し、`messages` 配列を作成し、関数呼び出しを実行し、ツール関数を実行し、LLM にツール関数の出力を要約させます。
方法 2: システムメッセージを介して情報を渡す
`tools` パラメーターを介して情報を渡すと、サーバーが最適なプロンプトテンプレートに自動的に適応するため、最良の結果が得られます。Qwen モデルを使用していて `tools` パラメーターを使用したくない場合は、「システムメッセージを介してツール情報を渡す」をご参照ください。
以下のセクションでは、OpenAI 互換 API を例として、`tools` パラメーターを使用した関数呼び出しの詳細な使用方法について説明します。
天気クエリと時刻クエリの 2 種類の質問を受け取るビジネスシナリオを想定します。
1. ツールの定義
ツールは LLM を外部サービスに接続します。まず、ツールを定義する必要があります。
1.1. ツール関数の作成
天気クエリツールと時刻クエリツールの 2 つのツール関数を作成します。
天気クエリツール
このツールは
argumentsパラメーターを受け取ります。argumentsのフォーマットは{"location": "queried location"}です。ツールの出力は、"{location} today is {weather}"というフォーマットの文字列です。デモンストレーションのため、ここで定義されている天気クエリツールは実際には天気を照会しません。晴れ、曇り、雨の中からランダムに選択します。実際のビジネスシナリオでは、これを Amap Weather などのツールに置き換えることができます。
時刻クエリツール
時刻クエリツールは入力パラメーターを必要としません。ツールの出力は、
"Current time: {queried time}."というフォーマットの文字列です。Node.js を使用する場合は、
npm install date-fnsを実行して、時刻を取得するための date-fns パッケージをインストールします。
## ステップ 1: ツール関数を定義する
# random モジュールのインポートを追加
import random
from datetime import datetime
# 天気クエリツールをシミュレートします。出力例: "The weather in Beijing today is rainy."
def get_current_weather(arguments):
# 代替の気象条件のリストを定義
weather_conditions = ["Sunny", "Cloudy", "Rainy"]
# 気象条件をランダムに選択
random_weather = random.choice(weather_conditions)
# JSON から場所情報を抽出
location = arguments["location"]
# フォーマットされた天気情報を返す
return f"The weather in {location} today is {random_weather}."
# 現在時刻を照会するツール。出力例: "Current time: 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"Current time: {formatted_time}."
# ツール関数をテストし、結果を出力します。後続のステップを実行する際には、次の 4 行のテストコードを削除できます。
print("Testing tool output:")
print(get_current_weather({"location": "Shanghai"}))
print(get_current_time())
print("\n")// ステップ 1: ツール関数を定義する
// 時刻クエリツールをインポート
import { format } from 'date-fns';
function getCurrentWeather(args) {
// 代替の気象条件のリストを定義
const weatherConditions = ["Sunny", "Cloudy", "Rainy"];
// 気象条件をランダムに選択
const randomWeather = weatherConditions[Math.floor(Math.random() * weatherConditions.length)];
// JSON から場所情報を抽出
const location = args.location;
// フォーマットされた天気情報を返す
return `The weather in ${location} today is ${randomWeather}.`;
}
function getCurrentTime() {
// 現在の日時を取得
const currentDatetime = new Date();
// 現在の日時をフォーマット
const formattedTime = format(currentDatetime, 'yyyy-MM-dd HH:mm:ss');
// フォーマットされた現在時刻を返す
return `Current time: ${formattedTime}.`;
}
// ツール関数をテストし、結果を出力します。後続のステップを実行する際には、次の 4 行のテストコードを削除できます。
console.log("Testing tool output:")
console.log(getCurrentWeather({location:"Shanghai"}));
console.log(getCurrentTime());
console.log("\n")ツールを実行すると、次の出力が表示されます。
Testing tool output:
The weather in Shanghai today is Cloudy.
Current time: 2025-01-08 20:21:45.1.2. tools 配列の作成
人間がツールを選択する前に、その機能、使用シナリオ、入力パラメーターを理解する必要があります。LLM にも同じことが言えます。モデルは、この情報に基づいて適切なツールを選択します。ツール情報を次の JSON 形式で提供します。
| 天気クエリツールの場合、ツール記述情報の形式は次のとおりです。 |
関数呼び出しを行う前に、コードでツール情報配列 (`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"Created {len(tools)} 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(`Created ${tools.length} tools: ${toolNames.join(', ')}\n`);2. messages 配列の作成
関数呼び出しは、`messages` 配列を介して命令とコンテキストを LLM に渡します。呼び出しを行う前に、`messages` 配列にはシステムメッセージとユーザーメッセージが含まれている必要があります。
システムメッセージ
tools 配列を作成したときに、ツールの機能と使用シナリオはすでに説明されていますが、システムメッセージでツールを呼び出すタイミングをさらに強調すると、通常、ツール呼び出しの精度が向上します。現在のシナリオでは、システムプロンプトを次のように設定できます。
You are a helpful assistant. If the user asks about the weather, call the 'get_current_weather' function;
if the user asks about the time, call the 'get_current_time' function.
Please answer the questions in a friendly tone.ユーザーメッセージ
ユーザーメッセージは、ユーザーの質問を渡すために使用されます。ユーザーが「上海の天気」と尋ねたと仮定すると、この時点での `messages` 配列は次のようになります。
# ステップ 3: messages 配列を作成する
# ステップ 2 のコードの後に次のコードを貼り付けます
# テキスト生成モデルのユーザーメッセージの例
messages = [
{
"role": "system",
"content": """あなたは役立つアシスタントです。ユーザーが天気について尋ねた場合は 'get_current_weather' 関数を呼び出します。
ユーザーが時刻について尋ねた場合は 'get_current_time' 関数を呼び出します。
質問にはフレンドリーな口調で答えてください。""",
},
{
"role": "user",
"content": "Weather in Shanghai"
}
]
# マルチモーダルモデルのユーザーメッセージの例
# 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 array created\n") // ステップ 3: messages 配列を作成する
// ステップ 2 のコードの後に次のコードを貼り付けます
const messages = [
{
role: "system",
content: "あなたは役立つアシスタントです。ユーザーが天気について尋ねた場合は 'get_current_weather' 関数を呼び出し、ユーザーが時刻について尋ねた場合は 'get_current_time' 関数を呼び出します。質問にはフレンドリーな口調で答えてください。",
},
{
role: "user",
content: "Weather in Shanghai"
}
];
// マルチモーダルモデルのユーザーメッセージの例,
// 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 array created\n");利用可能なツールには天気と時刻のクエリが含まれているため、現在時刻について尋ねることもできます。
3. 関数呼び出しの実行
作成した `tools` と `messages` を LLM に渡して、関数呼び出しを実行します。LLM はツールを呼び出すかどうかを判断します。呼び出す場合は、ツールの関数名とパラメーターを返します。
サポートされているモデルについては、「サポートされているモデル」をご参照ください。
# ステップ 4: 関数呼び出しを実行する
# ステップ 3 のコードの後に次のコードを貼り付けます
from openai import OpenAI
import os
client = OpenAI(
# API キーはリージョンによって異なります。API キーを取得するには、https://www.alibabacloud.com/help/model-studio/get-api-key をご参照ください
# 環境変数を設定していない場合は、次の行を Model Studio API キーに置き換えてください: 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/model-studio/getting-started/models をご参照ください
model="qwen3.6-plus",
extra_body={"enable_thinking": False},
messages=messages,
tools=tools
)
print("Returned object:")
print(completion.choices[0].message.model_dump_json())
print("\n")
return completion
print("Making a function calling...")
completion = function_calling()// ステップ 4: 関数呼び出しを実行する
// ステップ 3 のコードの後に次のコードを貼り付けます
import OpenAI from "openai";
const openai = new OpenAI(
{
// API キーはリージョンによって異なります。API キーを取得するには、https://www.alibabacloud.com/help/model-studio/get-api-key をご参照ください
// 環境変数を設定していない場合は、次の行を Model Studio API キーに置き換えてください: 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/model-studio/getting-started/models をご参照ください
enable_thinking: false,
messages: messages,
tools: tools
});
console.log("Returned object:");
console.log(JSON.stringify(completion.choices[0].message));
console.log("\n");
return completion;
}
const completion = await functionCalling();ユーザーが上海の天気について尋ねたため、LLM は使用するツール関数名を "get_current_weather" と指定し、関数の入力パラメーターを "{\"location\": \"Shanghai\"}" と指定します。
{
"content": "",
"refusal": null,
"role": "assistant",
"audio": null,
"function_call": null,
"tool_calls": [
{
"id": "call_6596dafa2a6a46f7a217da",
"function": {
"arguments": "{\"location\": \"Shanghai\"}",
"name": "get_current_weather"
},
"type": "function",
"index": 0
}
]
}LLM が質問にツールが必要ないと判断した場合、content パラメーターを介して直接応答することに注意してください。「こんにちは」と入力すると、tool_calls パラメーターは空になり、返されるオブジェクトの形式は次のようになります。
{
"content": "Hello! How can I help you? I'm particularly good at answering questions about the weather or time.",
"refusal": null,
"role": "assistant",
"audio": null,
"function_call": null,
"tool_calls": null
}tool_callsパラメーターが空の場合、プログラムは後続のステップを実行せずにcontentを直接返すことができます。
LLM に関数呼び出しを行うたびに特定のツールを選択させたい場合は、「強制ツール呼び出し」をご参照ください。
4. ツール関数の実行
ツール関数を実行すると、モデルの決定が実際の操作に変換されます。
ツール関数を実行するプロセスは、LLM ではなく、ご利用のコンピューティング環境によって完了します。
LLM は文字列のみを出力します。ツール関数を実行する前に、ツール関数名とその入力パラメーターを個別に解析する必要があります。
ツール関数
ツール関数名からツール関数エンティティへのマッピング
function_mapperを作成して、返されたツール関数文字列をツール関数エンティティにマッピングします。入力パラメーター
関数呼び出しによって返される入力パラメーターは JSON 文字列です。ツールを使用して JSON オブジェクトに解析し、入力パラメーター情報を抽出します。
解析後、パラメーターをツール関数に渡して実行し、出力結果を取得します。
# ステップ 5: ツール関数を実行する
# ステップ 4 のコードの後に次のコードを貼り付けます
import json
print("Running the tool function...")
# 返された結果から関数名と入力パラメーターを取得
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"Tool function output: {function_output}\n")// ステップ 5: ツール関数を実行する
// ステップ 4 のコードの後に次のコードを貼り付けます
console.log("Running the tool function...");
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(`Tool function output: ${functionOutput}\n`);コードを実行すると、次の出力が表示されます。
The weather in Shanghai today is Cloudy.実際のビジネスシナリオでは、多くのツールはデータのクエリではなく、特定のアクション (メールの送信やファイルのアップロードなど) を実行し、文字列を出力しません。LLM が実行ステータスを理解できるように、このようなツールにはステータス記述情報 (「メールが正常に送信されました」や「操作に失敗しました」など) を追加することを推奨します。
5. LLM によるツール関数出力の要約
ツール関数の出力形式は比較的固定されています。それを直接ユーザーに返すと、ロボットのように聞こえる可能性があります。ツール出力をモデルコンテキストに送信し、モデルを再度呼び出して自然言語スタイルの応答を生成します。
アシスタントメッセージの追加
関数呼び出しを実行した後、
completion.choices[0].messageを介してアシスタントメッセージを取得します。まず、それを `messages` 配列に追加します。ツールメッセージの追加
ツールの出力を
{"role": "tool", "content": "tool output", "tool_call_id": completion.choices[0].message.tool_calls[0].id}の形式で `messages` 配列に追加します。説明ツールの出力が文字列形式であることを確認してください。
tool_call_idは、各ツール呼び出しリクエストに対してシステムによって生成される一意の識別子です。モデルは一度に複数のツールを呼び出すようにリクエストする場合があります。複数のツール結果をモデルに返す場合、tool_call_idは、ツールの出力結果がその呼び出し意図と一致することを保証します。
# ステップ 6: ツール出力を LLM に送信する
# ステップ 5 のコードの後に次のコードを貼り付けます
messages.append(completion.choices[0].message)
print("Assistant message added")
messages.append({"role": "tool", "content": function_output, "tool_call_id": completion.choices[0].message.tool_calls[0].id})
print("Tool message added\n")// ステップ 6: ツール出力を LLM に送信する
// ステップ 5 のコードの後に次のコードを貼り付けます
messages.push(completion.choices[0].message);
console.log("Assistant message added")
messages.push({
"role": "tool",
"content": functionOutput,
"tool_call_id": completion.choices[0].message.tool_calls[0].id
});
console.log("Tool message added\n");この時点で、`messages` 配列は次のようになります。
[
システムメッセージ -- モデルのツール呼び出し戦略をガイドします
ユーザーメッセージ -- ユーザーの質問
アシスタントメッセージ -- モデルによって返されるツール呼び出し情報
ツールメッセージ -- ツールの出力情報 (並列ツール呼び出しが使用されている場合、複数のツールメッセージが存在する可能性があります。以下で説明します)
]`messages` 配列を更新した後、次のコードを実行します。
# ステップ 7: LLM にツール出力を要約させる
# ステップ 6 のコードの後に次のコードを貼り付けます
print("Summarizing the tool output...")
completion = function_calling()// ステップ 7: LLM にツール出力を要約させる
// ステップ 6 のコードの後に次のコードを貼り付けます
console.log("Summarizing the tool output...");
const completion_1 = await functionCalling();content から応答コンテンツを取得できます。「今日の上海の天気は曇りです。他に質問があれば、お気軽にお尋ねください。」
{
"content": "The weather in Shanghai today is cloudy. If you have any other questions, feel free to ask.",
"refusal": null,
"role": "assistant",
"audio": null,
"function_call": null,
"tool_calls": null
}これで、完全な関数呼び出しフローが完了しました。
高度な使用法
ツール呼び出し方法の指定
並列ツール呼び出し
単一の都市の天気クエリには、1 つのツール呼び出ししか必要ありません。質問に複数のツール呼び出しが必要な場合 (「北京と上海の天気は?」や「杭州の天気と今の時刻は?」など)、関数呼び出しを実行した後、ツール呼び出し情報は 1 つしか返されません。たとえば、「北京と上海の天気は?」と尋ねた場合:
{
"content": "",
"refusal": null,
"role": "assistant",
"audio": null,
"function_call": null,
"tool_calls": [
{
"id": "call_61a2bbd82a8042289f1ff2",
"function": {
"arguments": "{\"location\": \"Beijing\"}",
"name": "get_current_weather"
},
"type": "function",
"index": 0
}
]
}返された結果には、北京の入力パラメーターのみが含まれます。結果にすべてのツール関数と入力パラメーターが含まれるようにするには、関数呼び出しを実行するときに、parallel_tool_calls リクエストパラメーターを true に設定します。
並列ツール呼び出しは、依存関係のないタスクに適しています。タスク間に依存関係がある場合 (ツール A の入力がツール B の出力に関連している場合)、「クイックスタート」を参照して、`while` ループを介してシリアルツール呼び出し (一度に 1 つのツールを呼び出す) を実装してください。
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("Returned object:")
print(completion.choices[0].message.model_dump_json())
print("\n")
return completion
print("Making a 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("Returned object:");
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\": \"Beijing\"}"
},
"index": 0,
"id": "call_c2d8a3a24c4d4929b26ae2",
"type": "function"
},
{
"function": {
"name": "get_current_weather",
"arguments": "{\"location\": \"Shanghai\"}"
},
"index": 1,
"id": "call_dc7f2f678f1944da9194cd",
"type": "function"
}
]
}強制ツール呼び出し
LLM はある程度の不確実性を持ってコンテンツを生成し、間違ったツールを選択する可能性があります。特定の種類の質問に対して特定のツールの使用を強制または無効にするには、tool_choice パラメーターを変更できます。tool_choice パラメーターのデフォルト値は "auto" で、LLM がツール呼び出しの方法を自律的に決定することを意味します。
LLM がツール関数の出力を要約する場合、tool_choice パラメーターを削除してください。そうしないと、API は引き続きツール呼び出し情報を返します。特定のツールの使用を強制する
特定の種類の質問に対して関数呼び出しに特定のツールを強制的に呼び出させたい場合は、
tool_choiceパラメーターを{"type": "function", "function": {"name": "the_function_to_call"}}に設定できます。LLM はツール選択に参加せず、入力パラメーター情報のみを出力します。現在のシナリオが天気クエリの質問のみを扱うと仮定すると、`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("Returned object:"); console.log(JSON.stringify(response.choices[0].message)); console.log("\n"); return response; } const response = await functionCalling();どの質問が入力されても、返されるオブジェクトのツール関数は
get_current_weatherになります。この戦略を使用する前に、質問が選択したツールに関連していることを確認してください。そうしないと、予期しない結果が返される可能性があります。
少なくとも 1 つのツールの使用を強制する
ツールが必要な質問でも、LLM は呼び出しが不要だと判断する場合があります。関数呼び出しに常にツール呼び出しを行わせる (返されるオブジェクトの
tool_callsパラメーターが空でない) ようにするには、tool_choiceパラメーターを"required"に設定します。関数呼び出しは、常にツールと入力パラメーター情報を返します。現在のシナリオのすべての質問にツール呼び出しが必要であると仮定すると、`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("Returned object:"); console.log(JSON.stringify(completion.choices[0].message)); console.log("\n"); return completion; } const completion = await functionCalling();どの質問が入力されても、返されるオブジェクトの
tool_callsパラメーターは決して空になりません。この戦略を使用する前に、質問がツールに関連していることを確認してください。そうしないと、予期しない結果が返される可能性があります。
ツール不使用を強制
関数呼び出しにツール呼び出しを一切させないようにする必要がある場合 (返されるオブジェクトに
contentに応答コンテンツが含まれ、tool_callsパラメーターが空である)、tool_choiceパラメーターを"none"に設定するか、toolsパラメーターを渡さないようにします。関数呼び出しによって返される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("Returned object:"); console.log(JSON.stringify(completion.choices[0].message)); console.log("\n"); return completion; } const completion = await functionCalling();
マルチターン対話
ユーザーは、最初のターンで「北京の天気」と尋ね、2 番目のターンで「上海はどう?」と尋ねるかもしれません。モデルコンテキストに最初のターンの情報がない場合、モデルはどのツールを呼び出すかを判断できません。マルチターン対話シナリオでは、各ターンの後に `messages` 配列を完全に保ちます。この配列に新しいユーザーメッセージを追加してから、関数呼び出しを実行し、後続のステップを実行します。`messages` の構造は次のとおりです。
[
システムメッセージ -- モデルのツール呼び出し戦略をガイドします
ユーザーメッセージ -- ユーザーの質問
アシスタントメッセージ -- モデルによって返されるツール呼び出し情報
ツールメッセージ -- ツールの出力情報
アシスタントメッセージ -- ツール呼び出し情報のモデルの要約
ユーザーメッセージ -- ユーザーの 2 ターン目の質問
]ストリーミング出力
ストリーミング出力を使用すると、ツール関数名と入力パラメーター情報をリアルタイムで取得できるため、ユーザーエクスペリエンスが向上します。この場合:
ツール呼び出しのパラメーター情報は、データストリームとしてチャンクで返されます。
ツール関数名は、ストリーム応答の最初のデータチャンクで返されます。
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": "Weather in Hangzhou?"}],
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 キーはリージョンによって異なります。API キーを取得するには、https://www.alibabacloud.com/help/model-studio/get-api-key をご参照ください
// 環境変数を設定していない場合は、次の行を Model Studio API キーに置き換えてください: 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: "Weather in Beijing" }],
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=' "Hangzhou"}', 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\": \"Hangzhou\"}","name":"get_current_weather"},"type":"function"}LLM がツール関数の出力を要約するステップでは、追加されたアシスタントメッセージは以下の形式に準拠する必要があります。以下の 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 を使用する場合、全体的なプロセスは同じですが、API 形式には次の違いがあります。
ディメンション | Chat Completions | Responses API |
ツール定義形式 | | |
ツール呼び出し出力 | response.choices[0].message.tool_calls | `type` が `function_call` である `response.output` の項目 |
ツール結果のパスバック | | |
最終応答 | response.choices[0].message.content | response.output_text |
from openai import OpenAI
import json
import os
import random
# クライアントを初期化
client = OpenAI(
# 環境変数を設定していない場合は、次の行を Model Studio API キーに置き換えてください: api_key="sk-xxx",
# API キーはリージョンによって異なります。API キーを取得するには、https://www.alibabacloud.com/help/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 = "What's the weather like in Singapore?"
# ツールリストを定義
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 = ["Sunny", "Cloudy", "Rainy"]
random_weather = random.choice(weather_conditions)
location = arguments["location"]
return f"The weather in {location} today is {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"Assistant's final response: {response.output_text}")
else:
# ツール呼び出しループに入る
while function_calls:
for fc in function_calls:
func_name = fc.name
arguments = json.loads(fc.arguments)
print(f"Calling tool [{func_name}], arguments: {arguments}")
# ツールを実行
tool_result = get_current_weather(arguments)
print(f"Tool returns: {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"Assistant's final response: {response.output_text}")
import OpenAI from "openai";
// クライアントを初期化
const openai = new OpenAI({
// API キーはリージョンによって異なります。API キーを取得するには、https://www.alibabacloud.com/help/model-studio/get-api-key をご参照ください
// 環境変数を設定していない場合は、次の行を Model Studio API キーに置き換えてください: 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 = ["Sunny", "Cloudy", "Rainy"];
const randomWeather =
weatherConditions[Math.floor(Math.random() * weatherConditions.length)];
const location = args.location;
return `The weather in ${location} today is ${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 = "Weather in Singapore";
// 会話コンテキストを維持
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(`Assistant's final response: ${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(`Calling tool [${funcName}], arguments:`, args);
// ツールを実行
const toolResult = getCurrentWeather(args);
console.log(`Tool returns: ${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(`Assistant's final response: ${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 互換 API を介したツール呼び出しをサポートしています。ツール情報を取得する段階は、他のモデルとは次の点で異なります。
ストリーミング出力は必須です: Qwen-Omni はストリーミング出力のみをサポートしています。ツール情報を取得する際には、`
stream=True` も設定する必要があります。テキストのみの出力を推奨します: モデルはツール情報 (関数名とパラメーター) を取得する際にテキスト情報のみを必要とします。不要なオーディオの生成を避けるために、`
modalities=["text"]` を設定することを推奨します。出力にテキストとオーディオの両方のモダリティが含まれる場合、ツール情報を取得する際にオーディオデータチャンクをスキップする必要があります。
Qwen-Omni の詳細については、「非リアルタイム (Qwen-Omni)」をご参照ください。
from openai import OpenAI
import os
client = OpenAI(
# API キーはリージョンによって異なります。API キーを取得するには、https://www.alibabacloud.com/help/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": "Weather in Hangzhou?"}],
# 出力データのモダリティを設定します。有効な値: ["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 キーはリージョンによって異なります。API キーを取得するには、https://www.alibabacloud.com/help/model-studio/get-api-key をご参照ください
// 環境変数を設定していない場合は、次の行を Model Studio API キーに置き換えてください: 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": "Weather in Hangzhou"
}],
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": "Hangzhou"}', 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 を介してツール定義を渡し、次の対話フローに入ります。
フェーズ 1: 音声入力とツール呼び出し
ユーザーが音声で質問します。クライアントはオーディオを収集し、サーバーに送信します (
append_audio()メソッドに対応)。サーバーの VAD が音声の終了を検出した後、モデル推論を実行し、ツールを呼び出す必要があると判断します。サーバーは、関数名 (
name)、関数入力パラメーター (arguments)、および呼び出し識別子 (call_id) を含むツール呼び出し情報をクライアントに返します (response.function_call_arguments.doneイベントに対応)。例は次のとおりです。{ "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\": \"Hangzhou\"}" }クライアントは、関数名と入力パラメーターに基づいて、対応するツール関数をローカルで実行し、実行結果を取得します。
フェーズ 2: クライアントがツール結果を返信し、最終応答をトリガーする
クライアントは、呼び出し識別子 (
call_id) と実行結果 (output) を含むツール実行結果をサーバーに返信します (conversation.item.createイベントに対応)。例は次のとおりです。{ "type": "conversation.item.create", "item": { "type": "function_call_output", "call_id": "call_2be200f4cafe419b9530dd", "output": "The weather in Hangzhou today is sunny, with a temperature of 25°C and a light breeze." } }クライアントは、
response.createイベントを送信し続け、サーバーがツール実行結果に基づいて最終的な音声回答を生成するようにトリガーします。クライアントは、サーバーから返された音声とテキスト (
response.audio.deltaおよびresponse.audio_transcript.deltaイベントに対応) を受信し、音声応答をユーザーに再生します。
Qwen-Omni-Realtime シリーズは、tool_choiceおよびparallel_tool_callsパラメーターをサポートしていません。
Qwen-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 キーを設定します。環境変数を設定していない場合は、次の行を API キーに置き換えてください: 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"The train ticket price from {src} to {dst} is 100-200 CNY."
def get_flight_price(src: str, dst: str) -> str:
"""航空券の価格を照会"""
return f"The flight ticket price from {src} to {dst} is 200-300 USD."
def get_current_weather(location: str) -> str:
"""特定の都市の天気を照会"""
return f"The weather in {location} today is changing from haze to sunny, with a temperature of 4/-4°C and a light breeze."
# 統一された 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] Start processing: name={function_name}, args={tool_call_arguments}')
# 対応する関数を検索
if function_name not in TOOL_FUNCTIONS:
tool_call_response['output'] = f"Client did not find the tool: {function_name}"
print(f'[Tool Call] Error: Tool not found {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] Completed: {result}')
return tool_call_response
except Exception as e:
error_msg = f"Tool call failed: {str(e)}"
tool_call_response['output'] = error_msg
print(f'[Tool Call] Exception: {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)
# チャンクごとに生のオーディオデータをキューにプッシュ
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
# チャンクを pyaudio オーディオプレーヤーに書き込み、このチャンクの再生が完了するまで待機します。
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('Initializing audio devices...')
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('Audio devices initialized')
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] Failed to read audio data: {e}')
return None
def cleanup(self) -> None:
"""オーディオリソースをクリーンアップします"""
print('Cleaning up audio resources...')
if self.player:
self.player.shutdown()
if self.mic_stream:
self.mic_stream.close()
if self.pya:
self.pya.terminate()
print('Audio resources cleaned up')
# ==================== コールバックハンドラー ====================
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('Connection established')
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'Connection closed: 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'Session started: {response["session"]["id"]}')
# 音声テキスト変換完了
elif event_type == 'conversation.item.input_audio_transcription.completed':
print(f'User question: {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'Time to first token (VAD end): {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'Time to first audio (VAD end): {latency:.0f} ms')
audio_interval = time.time() * 1000 - self.last_package_time
print(f'Audio interval: {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 detected speech start ======')
if self.audio_manager.player:
self.audio_manager.player.cancel_playing()
# VAD が音声終了を検出
elif event_type == 'input_audio_buffer.speech_stopped':
print('====== VAD detected speech end ======')
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('====== Received tool call request ======')
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('====== Response completed ======')
print(f'Full response: {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] Response ID: {response_id}, '
f'Time to first token: {text_delay:.0f}ms, '
f'Time to first audio: {audio_delay:.0f}ms')
else:
print('[Metric] Metric information is temporarily unavailable (possibly a response after a tool call)')
self.all_response_text = ''
except Exception as e:
print(f'[Error] Exception handling event: {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('Initializing Omni real-time conversation...')
# オーディオマネージャーを作成
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('\nReceived Ctrl+C, stopping...')
conversation.close()
audio_manager.cleanup()
print('Omni real-time conversation stopped')
sys.exit(0)
signal.signal(signal.SIGINT, signal_handler)
print("Press Ctrl+C to stop the conversation...\n")
# メインループ:継続的にオーディオを送信し、ツール呼び出しをチェック
try:
while True:
# 保留中のツール呼び出しを処理
has_tool_calls = callback.process_pending_tool_calls()
if has_tool_calls:
print("*** Tool call completed, creating new response ***")
conversation.create_response(
instructions=None,
output_modalities=omni_output_modalities
)
print('====== Tool call processing completed ======\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] Main loop exception: {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 not found: Please set the DASHSCOPE_API_KEY environment variable");
} 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("Tool calling is enabled. Please start speaking (Press Ctrl+C to exit)...");
}
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, "Normal exit");
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 did not find this tool. Call failed.";
}
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 "The weather in " + location + " today is changing from haze to sunny, with a temperature of 4/-4°C and a light breeze.";
}
private String getFlightPrice(JsonObject args) {
String src = args.get("src").getAsString();
String dst = args.get("dst").getAsString();
return "The flight ticket price from " + src + " to " + dst + " is 200-300 USD.";
}
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("Connection established");
}
@Override
public void onClose(int code, String reason) {
System.out.println("Connection closed");
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"The weather in {location} today is changing from haze to sunny, with a temperature of 4/-4°C and a light breeze."
def get_flight_price(src, dst):
"""航空券の価格を照会"""
return f"The flight ticket price from {src} to {dst} is 200-300 USD."
def get_train_price(src, dst):
"""列車のチケット価格を照会"""
return f"The train ticket price from {src} to {dst} is 100-200 CNY."
# ツール名と関数のマッピング
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'[Tool Call] Start processing: name={name}, args={arguments}')
func = TOOL_FUNCTIONS.get(name)
if func is None:
result = f"Client did not find the tool: {name}"
print(f'[Tool Call] Error: {result}')
return result
result = func(**arguments)
print(f'[Tool Call] Completed: {result}')
return result
except Exception as e:
error_msg = f"Tool call failed: {str(e)}"
print(f'[Tool Call] Exception: {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": "You are a personal assistant named Xiaoyun",
"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("Tool calling is enabled. Speak into the microphone (Ctrl+C to exit)...")
# イベント処理ループ
async for raw in ws:
msg = json.loads(raw)
t = msg["type"]
# セッション作成
if t == "session.created":
print(f"Session started: {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"[User] {msg['transcript']}")
# VAD が音声開始を検出
elif t == "input_audio_buffer.speech_started":
print("====== VAD detected speech start ======")
# VAD が音声終了を検出
elif t == "input_audio_buffer.speech_stopped":
print("====== VAD detected speech end ======")
# ツール呼び出しリクエストを受信
elif t == "response.function_call_arguments.done":
print("====== Received tool call request ======")
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("====== Tool call processing completed ======")
else:
# 通常の応答が完了し、完全な応答を出力
if all_response_text:
print(f"[Model] {all_response_text}")
all_response_text = ""
send_task.cancel()
speaker.close()
pya.terminate()
asyncio.run(main())
ディープシンキングモデルのツール呼び出し
ディープシンキングモデルは、ツール呼び出し情報を出力する前に推論を実行し、決定の解釈可能性と信頼性を向上させます。
思考プロセス
モデルはユーザーの意図を分析し、必要なツールを特定し、パラメーターの正当性を検証し、呼び出し戦略を段階的に計画します。
ツール呼び出し
モデルは、構造化された形式で 1 つ以上の関数呼び出しリクエストを出力します。
並列ツール呼び出しがサポートされています。
以下は、ストリーミングディープシンキングモデルを使用したツール呼び出しの例です。
テキスト生成思考モデルの詳細については、「ディープシンキング」をご参照ください。マルチモーダル思考モデルの詳細については、「画像と動画の理解」および「非リアルタイム (Qwen-Omni)」をご参照ください。
tool_choiceパラメーターは、"auto"(デフォルト値、モデルが自律的にツールを選択することを意味します) または"none"(モデルにツールを選択させないように強制します) にのみ設定できます。
OpenAI 互換
Python
コード例
import os
from openai import OpenAI
# OpenAI クライアントを初期化し、Alibaba Cloud DashScope サービスを設定
client = OpenAI(
# API キーはリージョンによって異なります。API キーを取得するには、https://www.alibabacloud.com/help/model-studio/get-api-key をご参照ください
# 環境変数を設定していない場合は、次の行を Model Studio API キーに置き換えてください: 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("Please enter your question:")}]
# マルチモーダルモデルのメッセージ例
# 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": "Based on the location in the image, what is the current weather there?"}]
# }]
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,
# トークン消費情報を取得するにはコメントを解除
# stream_options={
# "include_usage": True
# }
)
reasoning_content = "" # 完全な思考プロセスを定義
answer_content = "" # 完全な応答を定義
tool_info = [] # ツール呼び出し情報を保存
is_answering = False # 思考プロセスが終了し、応答が開始されたかどうかを判断
print("="*20+"Thinking Process"+"="*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+"Response Content"+"="*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+"Tool Call Information"+"="*19)
if not tool_info:
print("No tool call")
else:
print(tool_info)戻り結果
「4 つの直轄市の天気」と入力すると、次の結果が得られます。
====================Thinking Process====================
Okay, the user is asking about the weather in the four municipalities. First, I need to clarify which four municipalities they are. According to China's administrative divisions, the municipalities include Beijing, Shanghai, Tianjin, and Chongqing. So the user wants to know the weather conditions in these four cities.
Next, I need to check the available tools. The provided tools include the get_current_weather function, which takes a location parameter of type string. Each city needs to be queried separately because the function can only query one location at a time. Therefore, I need to call this function once for each municipality.
Then, I need to consider how to generate the correct tool calls. Each call should include the city name as a parameter. For example, the first call is for Beijing, the second for Shanghai, and so on. I need to make sure the parameter name is `location` and the value is the correct city name.
Also, the user probably wants the weather information for each city, so I need to ensure each function call is correct. This might require making four consecutive calls, one for each city. However, based on the tool usage rules, it might need to be handled in multiple steps, or multiple calls might be generated at once. But according to the example, it seems only one function is called at a time, so it might need to be done step by step.
Finally, I need to confirm if there are any other factors to consider, such as whether the parameters are correct, the city names are accurate, and whether I need to handle possible error situations, like a city not existing or the API being unavailable. But for now, the four municipalities are clear, so it should be fine.
====================Response Content====================
===================Tool Call Information===================
[{'id': 'call_767af2834c12488a8fe6e3', 'name': 'get_current_weather', 'arguments': '{"location": "Beijing"}'}, {'id': 'call_2cb05a349c89437a947ada', 'name': 'get_current_weather', 'arguments': '{"location": "Shanghai"}'}, {'id': 'call_988dd180b2ca4b0a864ea7', 'name': 'get_current_weather', 'arguments': '{"location": "Tianjin"}'}, {'id': 'call_4e98c57ea96a40dba26d12', 'name': 'get_current_weather', 'arguments': '{"location": "Chongqing"}'}]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 キーはリージョンによって異なります。API キーを取得するには、https://www.alibabacloud.com/help/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("Please enter your question:");
rl.close();
const messages = [{ role: "user", content: question }];
// マルチモーダルモデルのメッセージ例
// 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: "What's the weather like in the location shown in the image?"}]
// }];
let reasoningContent = "";
let answerContent = "";
const toolInfo = [];
let isAnswering = false;
console.log("=".repeat(20) + "Thinking Process" + "=".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) + "Response Content" + "=".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) + "Tool Call Information" + "=".repeat(19));
console.log(toolInfo.length ? toolInfo : "No tool call");
} catch (error) {
console.error("An error occurred:", error);
}
}
main(); 戻り結果
「4 つの直轄市の天気」と入力すると、次の結果が得られます。
Please enter your question:Weather in the four municipalities
====================Thinking Process====================
Okay, the user is asking about the weather in the four municipalities. First, I need to clarify which are the four municipalities of China. Beijing, Shanghai, Tianjin, and Chongqing, right? Next, I need to call the weather query function for each city.
But the user's question might require me to get the weather conditions for these four cities separately. I need to call the get_current_weather function once for each city, with their respective city names as parameters. I need to make sure the parameters are correct, such as the full names of the municipalities, like "Beijing", "Shanghai", "Tianjin", and "Chongqing".
Then, I need to call the weather API for these four cities in order. Each call requires a separate tool_call. The user probably wants the current weather information for each city, so I need to ensure each call is correct. I might need to pay attention to the correct spelling and names of each city to avoid errors. For example, Chongqing is sometimes abbreviated, so the full name should be used in the parameter.
Now, I need to generate four tool_calls, one for each municipality. I'll check if each parameter is correct and then arrange them in order. This way, the user will get the weather data for all four municipalities.
====================Response Content====================
===================Tool Call Information===================
[
{
id: 'call_21dc802e717f491298d1b2',
name: 'get_current_weather',
arguments: '{"location": "Beijing"}'
},
{
id: 'call_2cd3be1d2f694c4eafd4e5',
name: 'get_current_weather',
arguments: '{"location": "Shanghai"}'
},
{
id: 'call_48cf3f78e02940bd9085e4',
name: 'get_current_weather',
arguments: '{"location": "Tianjin"}'
},
{
id: 'call_e230a2b4c64f4e658d223e',
name: 'get_current_weather',
arguments: '{"location": "Chongqing"}'
}
]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": "What's the weather like in Hangzhou?"
}
],
"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("Please enter your question:")}]}]
# マルチモーダルモデルのメッセージ例
# messages = [
# {
# "role": "user",
# "content": [
# {"image": "https://img.alicdn.com/imgextra/i2/O1CN01FbTJon1ErXVGMRdsN_!!6000000000405-0-tps-1024-683.jpg"},
# {"text": "What's the weather like in the location shown in the image?"}]
# }]
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+"Thinking Process"+"="*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+"Response Content"+"="*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+"Tool Call Information"+"="*19)
if not tool_info:
print("No tool call")
else:
print(tool_info)戻り結果
「4 つの直轄市の天気」と入力すると、次の結果が得られます。
Please enter your question:Weather in the four municipalities
====================Thinking Process====================
Okay, the user is asking about the weather in the four municipalities. First, I need to confirm which are the four municipalities of China. Beijing, Shanghai, Tianjin, and Chongqing, right? Next, the user needs the weather conditions for each city, so I need to call the weather query function.
However, the problem is that the user did not specify the city names, just "the four municipalities". I might need to explicitly state the name of each municipality and then query them separately. For example, Beijing, Shanghai, Tianjin, and Chongqing. I need to make sure each city is correct.
Then, I need to check the available tools. The user has provided the get_current_weather function, which takes a location parameter. Therefore, I need to call this function for each municipality, passing the corresponding city name as the parameter. For example, the first call's location is Beijing, the second is Shanghai, the third is Tianjin, and the fourth is Chongqing.
However, I might need to be careful. For a municipality like Chongqing, sometimes a more specific district is needed, but the user might only want the city-level weather. So using the municipality name directly should be fine. Next, I need to generate four separate function calls, one for each municipality. This way, the user will get the weather conditions for all four cities.
Finally, I need to make sure the parameters for each call are correct and that none are missed. This will ensure the user's question is fully answered.
===================Tool Call Information===================
[{'id': 'call_2f774ed97b0e4b24ab10ec', 'name': 'get_current_weather', 'arguments': '{"location": "Beijing"}'}, {'id': 'call_dc3b05b88baa48c58bc33a', 'name': 'get_current_weather', 'arguments': '{"location": "Shanghai"}}'}, {'id': 'call_249b2de2f73340cdb46cbc', 'name': 'get_current_weather', 'arguments': '{"location": "Tianjin"}'}, {'id': 'call_833333634fda49d1b39e87', 'name': 'get_current_weather', 'arguments': '{"location": "Chongqing"}}'}]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 "Current time: " + now.format(formatter) + ".";
}
}
static class WeatherTool {
private String location;
public WeatherTool(String location) {
this.location = location;
}
public String call() {
return location + " is sunny today";
}
}
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", "Please tell me the weather in Hangzhou"))).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("Please tell me the weather in Hangzhou")
// .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":"Okay, the user asked me"}}]}}
{"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":"to tell them the weather in Hangzhou. I"}}]}}
{"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":"need to first determine if there are"}}]}}
{"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":"any relevant tools available. Looking at the provided"}}]}}
{"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":"tools, I see there is a 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 function with a location parameter"}}]}}
{"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":". So I should call"}}]}}
{"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":"this function with the parameter"}}]}}
{"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":"set to Hangzhou. No other"}}]}}
{"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":"tools are needed because"}}]}}
{"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":"the user only"}}]}}
{"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":"asked about the weather. Next, I will construct"}}]}}
{"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":"the tool_call, filling"}}]}}
{"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":"in the name and parameters"}}]}}
{"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":". I need to make sure the parameter is a"}}]}}
{"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 object and location is a"}}]}}
{"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":"string. After checking for"}}]}}
{"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":"errors, I will return it."}}]}}
{"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\": \"Hangzhou"}}]}}]}}
{"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 キーはリージョンによって異なります。API キーを取得するには、https://www.alibabacloud.com/help/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": "Weather in Hangzhou"}]
}
]
},
"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` などのより強力なモデルバージョンにアップグレードすることが、最も直接的で効果的な方法です。
ツール数の動的制御
アプリケーションが数十、数百のツールを統合する場合、それらすべてをモデルに提供すると、次の問題が発生する可能性があります。
パフォーマンスの低下:モデルが多数のツールセットから正しいツールを選択する難易度が劇的に増加します。
コストとレイテンシ:多くのツールの説明は大量の入力トークンを消費し、コストの増加と応答の遅延につながります。
解決策:モデルを呼び出す前にツールルーティング/取得レイヤーを追加します。このレイヤーは、ユーザーのクエリに基づいてツールライブラリをフィルタリングし、関連性の高いツールの小さなサブセットをモデルに提供します。
ツールルーティングを実装するための主流の方法:
セマンティック検索
埋め込みモデルを使用してツールの説明 (
description) をベクトルに変換し、ベクトルデータベースに保存します。ユーザーがクエリを送信すると、クエリベクトルに対してベクトル類似検索を実行して、最も関連性の高い上位 K 個のツールを再現できます。ハイブリッド検索
この方法は、セマンティック検索のあいまい一致と、従来のキーワードまたはメタデータタグの完全一致を組み合わせたものです。これを行うには、`
tags` または `keywords` フィールドをツールに追加します。取得中に、ベクトル検索とキーワードフィルタリングの両方を実行すると、特に高頻度または特定のシナリオで、再現率が大幅に向上します。軽量 LLM ルーター
より複雑なルーティングロジックには、Qwen-Flash などのより小さく、高速で、安価なモデルをルーターモデルとして使用できます。このモデルのタスクは、ユーザーのクエリに基づいて関連するツール名のリストを出力することです。
実用的なアドバイス
候補セットを簡潔に保つ:使用する方法に関係なく、メインモデルには 20 個以下のツールを提供することを推奨します。これにより、モデルの認知負荷、コスト、レイテンシ、および精度の最適なバランスが提供されます。
階層型フィルタリング戦略:ファネルスタイルのルーティング戦略を構築できます。たとえば、最初に低コストのキーワードまたはルールマッチングを使用して、明らかに無関係なツールを除外できます。次に、残りのツールに対してセマンティック検索を実行して、効率と品質を向上させることができます。
ツールセキュリティの原則
LLM にツール実行機能を与える場合、セキュリティが最優先事項です。中心となる原則は、最小権限と人間による確認です。
最小権限の原則:モデルに提供されるツールセットは、最小権限の原則に厳密に従う必要があります。デフォルトでは、ツールは読み取り専用である必要があります (天気のクエリやドキュメントの検索などのツールなど)。状態の変更やリソース操作を伴う「書き込み」権限は提供しないでください。
危険なツールの隔離:任意のコードを実行するツール (
code interpreter)、ファイルシステムを操作するツール (fs.delete)、データベースの削除または更新操作を実行するツール (db.drop_table)、または金融取引を処理するツール (payment.transfer) など、危険なツールを LLM に直接提供しないでください。人間の関与:すべての高権限または元に戻せない操作には、手動レビューと確認プロセスが必要です。モデルは操作リクエストを生成できますが、最終的な「実行」ボタンは人間のユーザーがクリックする必要があります。たとえば、モデルはメールを作成できますが、ユーザーは送信操作を確認する必要があります。
ユーザーエクスペリエンスの最適化
関数呼び出しプロセスには複数のステップが含まれ、いずれかのステップで問題が発生すると、ユーザーエクスペリエンスに悪影響を及ぼす可能性があります。
ツール実行失敗の処理
ツールの実行失敗はよくあることです。次の戦略を採用できます。
最大リトライ回数:3 回など、合理的なリトライ制限を設定して、継続的な失敗による長時間のユーザー待機やシステムリソースの浪費を回避します。
フォールバック応答の提供:リトライが使い果たされた場合、または解決できないエラーが発生した場合は、「申し訳ありませんが、現時点では関連情報を見つけることができません。サービスが混み合っている可能性があります。後でもう一度お試しください。」など、明確でフレンドリーなプロンプトをユーザーに返します。
処理遅延への対処
高いレイテンシはユーザー満足度を低下させる可能性があります。フロントエンドとバックエンドの両方で最適化を実装できます。
タイムアウトの設定:関数呼び出しプロセスの各ステップに、独立した合理的なタイムアウトを設定します。タイムアウトが発生した場合、操作は直ちに中断され、ユーザーにフィードバックが提供される必要があります。
即時フィードバックの提供:関数呼び出しが開始されると、「天気を照会しています...」や「関連情報を検索しています...」など、インターフェイスにプロンプトを表示することを推奨します。これにより、ユーザーは進捗状況をリアルタイムでフィードバックされます。
課金
`messages` 配列のトークンに加えて、ツールの説明も入力トークンとして課金されます。
システムメッセージを介してツール情報を渡す
エラーコード
モデル呼び出しが失敗し、エラーメッセージが返された場合は、「エラーコード」を参照して問題を解決してください。