ailia LLM 1.5 の Tool Use API で Gemma 4 の Function Calling を使う方法を説明します。JSON履歴とtool_call_idによる結果の返却、画像・音声入力、GetResponseJsonによる結果取得を説明します。
Tool Useでは最初のuserメッセージからailiaLLMSetPromptJsonを使用します。
ailiaLLMSetToolsでツールが設定されている間、従来のailiaLLMSetPromptとailiaLLMSetMultimodalPromptは
AILIA_LLM_STATUS_INVALID_STATEを返します。ツールを解除すると再び利用できます。
ailiaLLMSetToolsにOpenAI互換のツール定義配列を渡します。ailiaLLMSetPromptJsonにmessages配列そのもの(UTF-8 JSON)を渡します。ailiaLLMGenerateを完了まで呼び出します。deltaはailiaLLMGetDeltaTextでプレビューできますが、取得・連結は必須ではありません。ailiaLLMGetResponseJsonSize / ailiaLLMGetResponseJsonで構造化assistant JSONを取得します。SDKが生成結果を蓄積するので、生の出力をアプリ側で引き回す必要はありません。 GetResponseJsonは読み取りで蓄積を消去しません。新しいプロンプトでリセットします。
[
{"role":"user","content":"東京の天気は?"},
{"role":"assistant","content":"","tool_calls":[
{"id":"call_0","type":"function","function":{
"name":"get_weather","arguments":"{\"city\":\"東京\"}"}}
]},
{"role":"tool","tool_call_id":"call_0","content":"雪、-3度"}
]
本文をツール構文として再解析しません。assistantのThinkingはreasoning_contentに指定します。
argumentsはJSONオブジェクトを表す文字列です。呼び出しのid/nameは空にできず、IDはassistant内で一意です。
別ターンではIDを再利用できます。連続するtool結果は直前のassistantの呼び出しにIDで対応付けます。
結果の順序は問いません。不明なID、重複結果、矛盾するツール名はINVALID_ARGUMENTです。
ツール結果のcontentには文字列を指定し、オブジェクトはJSON文字列に変換してください。
setTools(null)でツールを解除した後も、過去のtool_callsとtool結果をsetPromptJsonへ渡して要約・最終回答を生成できます。ID対応の検証は引き続き行います。解除すると出力パーサーが無効になるため、生成結果のJSONは解除前に取得してください。
対応するprojectorをailiaLLMOpenMultimodalProjectorFileAで読み込んだ後、userのcontent配列に順番に指定します。
メディアはtoolsの有無にかかわらずJSONで入力できます。
[{"role":"user","content":[
{"type":"text","text":"この画像と音声について説明してください。"},
{"type":"image","file_path":"/path/to/image.jpg"},
{"type":"audio","file_path":"/path/to/audio.wav"}
]}]
image/audioはfile_pathまたはdataをちょうど1つ指定します。dataは標準Base64(パディング付き)で
エンコードしたファイル内容です。JPEG/PNG、WAV/MP3/FLAC等を使用できます。URL取得、動画、生RGB/PCMには対応しません。
画像・音声を含む履歴は毎回再評価します。メディア非対応のモデル/projectorや未読込はINVALID_STATEです。
画像と音声の対応状況はailiaLLMGetMultimodalCapabilitiesで確認できます。
モデルの読み込み、ailiaLLMSetToolsによるツール設定から、生成、ツール結果の返却、ツールの解除までの一連の流れです。履歴の編集にはnlohmann/jsonを使用します。
#include "ailia_llm.h"
#include <nlohmann/json.hpp>
#include <cstdio>
#include <string>
#include <vector>
using json = nlohmann::json;
// SetPromptJson → Generate → GetResponseJsonを1ターン分実行します。
static int generate_turn(AILIALLM* llm, const json& messages, json& response) {
std::string input = messages.dump();
int status = ailiaLLMSetPromptJson(llm, input.c_str());
if (status != AILIA_LLM_STATUS_SUCCESS) return status;
unsigned int done = 0;
while (!done) {
status = ailiaLLMGenerate(llm, &done);
if (status != AILIA_LLM_STATUS_SUCCESS) return status;
// 必要な場合だけGetDeltaTextでプレビュー。履歴用の連結は不要。
}
unsigned int size = 0;
status = ailiaLLMGetResponseJsonSize(llm, &size);
if (status != AILIA_LLM_STATUS_SUCCESS) return status;
std::vector<char> output(size);
status = ailiaLLMGetResponseJson(llm, output.data(), size);
if (status != AILIA_LLM_STATUS_SUCCESS) return status;
response = json::parse(output.data());
return AILIA_LLM_STATUS_SUCCESS;
}
static int run_tool_use(AILIALLM* llm) {
const char* tools = R"([{"type":"function","function":{
"name":"get_weather","description":"現在の天気を取得します。",
"parameters":{"type":"object","properties":{"city":{"type":"string"}},
"required":["city"]}}}])";
int status = ailiaLLMSetTools(llm, tools);
if (status != AILIA_LLM_STATUS_SUCCESS) return status;
json messages = json::array({{{"role", "user"}, {"content", "東京の天気は?"}}});
for (int turn = 0; turn < 8; turn++) {
json response;
status = generate_turn(llm, messages, response);
if (status == AILIA_LLM_STATUS_PARSE_ERROR) {
// 失敗したassistantもtool結果も追加せず、既存履歴に再試行の指示を追加します。
messages.push_back({{"role", "user"},
{"content", "完全な応答またはツール呼び出しで再試行してください。"}});
continue;
}
if (status != AILIA_LLM_STATUS_SUCCESS) return status;
messages.push_back(response);
if (!response.contains("tool_calls") || response["tool_calls"].empty()) {
if (response["content"].is_string()) {
printf("%s\n", response["content"].get<std::string>().c_str());
}
break;
}
for (const auto& call : response["tool_calls"]) {
const auto& function = call["function"];
if (function["name"] != "get_weather") return AILIA_LLM_STATUS_INVALID_ARGUMENT;
json args = json::parse(function["arguments"].get<std::string>());
// 実際には引数を検証してツールを実行します。以下はモック結果。
json result = {{"city", args["city"]}, {"weather", "sunny"}};
messages.push_back({{"role", "tool"}, {"tool_call_id", call["id"]},
{"content", result.dump()}}); // 結果はJSON文字列
}
}
return AILIA_LLM_STATUS_SUCCESS;
}
int main() {
AILIALLM* llm = nullptr;
if (ailiaLLMCreate(&llm) != AILIA_LLM_STATUS_SUCCESS) return 1;
int status = ailiaLLMOpenModelFileA(llm, "gemma-4-E2B-it-Q4_K_M.gguf", 4096);
if (status == AILIA_LLM_STATUS_SUCCESS) {
status = run_tool_use(llm);
ailiaLLMSetTools(llm, nullptr); // エラー時もツール設定を解除します。
}
if (status != AILIA_LLM_STATUS_SUCCESS) {
fprintf(stderr, "error %d: %s\n", status, ailiaLLMGetErrorDetail(llm));
}
ailiaLLMDestroy(llm);
return status == AILIA_LLM_STATUS_SUCCESS ? 0 : 1;
}
| 言語 | JSON履歴の設定・生成 | SDK内の生成結果を取得 |
|---|---|---|
| C / C++ | ailiaLLMSetPromptJson → ailiaLLMGenerate |
ailiaLLMGetResponseJsonSize / ailiaLLMGetResponseJson |
| Python | generate_json(messages) |
get_response_json()(dict) |
| Unity | SetPromptJson(messagesJson) → Generate |
GetResponseJson()(JSON文字列、失敗時は空文字列) |
| Flutter | setPromptJson(messages) → generate |
getResponseJson()(Map) |
| Kotlin | setPromptJson(messagesJson) → generate |
getResponseJson()(JSON文字列) |
Python・Flutter・Kotlinはエラー時に例外を返します。UnityのSetPromptJsonはboolを返します。 Tool Useでは、最初のターンからJSON履歴を使用してください。
モデル読込済みの AiliaLLM を渡します。Androidの org.json を使用します。Android以外のJVM環境では対応するJSONライブラリを追加してください。
import axip.ailia_llm.AiliaLLM
import org.json.JSONArray
import org.json.JSONObject
// llmはモデル読込済み。JSON操作にはAndroidのorg.jsonを使用します。
fun runToolUse(llm: AiliaLLM) {
try {
llm.setTools("""[{"type":"function","function":{
"name":"get_weather","description":"現在の天気を取得します。",
"parameters":{"type":"object","properties":{"city":{"type":"string"}},
"required":["city"]}
}}]""")
val messages = JSONArray().put(
JSONObject().put("role", "user").put("content", "東京の天気は?")
)
for (turn in 0 until 8) {
llm.setPromptJson(messages.toString())
while (!llm.generate()) {
print(llm.getDeltaText()) // 任意のプレビュー。連結は不要。
}
// 不完全な出力の場合は例外。ツールを実行せず再試行を判断します。
val response = JSONObject(llm.getResponseJson())
messages.put(response)
val calls = response.optJSONArray("tool_calls")
if (calls == null || calls.length() == 0) {
println(response.optString("content"))
break
}
for (i in 0 until calls.length()) {
val call = calls.getJSONObject(i)
val function = call.getJSONObject("function")
require(function.getString("name") == "get_weather")
val args = JSONObject(function.getString("arguments"))
val city = args.getString("city")
// 実際には引数を検証してツールを実行します。以下はモック結果。
val result = JSONObject().put("city", city).put("weather", "sunny")
messages.put(JSONObject()
.put("role", "tool")
.put("tool_call_id", call.getString("id"))
.put("content", result.toString())) // 結果はJSON文字列
}
}
} finally {
llm.setTools(null)
}
}
モデル読込済みの AiliaLLMModel を渡します。tool結果の content はMapのままではなく、JSON文字列に変換します。
import 'dart:convert';
import 'dart:io';
import 'package:ailia_llm/ailia_llm_model.dart';
// llmはモデル読込済み。
void runToolUse(AiliaLLMModel llm) {
try {
llm.setTools([{
'type': 'function',
'function': {
'name': 'get_weather',
'description': '現在の天気を取得します。',
'parameters': {
'type': 'object',
'properties': {'city': {'type': 'string'}},
'required': ['city'],
},
},
}]);
final messages = <Map<String, dynamic>>[
{'role': 'user', 'content': '東京の天気は?'},
];
for (var turn = 0; turn < 8; turn++) {
llm.setPromptJson(messages);
String? delta;
while ((delta = llm.generate()) != null) {
stdout.write(delta); // 任意のプレビュー。連結は不要。
}
// 不完全な出力の場合は例外。ツールを実行せず再試行を判断します。
final response = llm.getResponseJson();
messages.add(response);
final calls = response['tool_calls'] as List? ?? [];
if (calls.isEmpty) {
print(response['content']);
break;
}
for (final call in calls) {
final function = call['function'];
if (function['name'] != 'get_weather') {
throw StateError('Unknown tool: ${function['name']}');
}
final args = jsonDecode(function['arguments'] as String);
final city = args['city'] as String;
// 実際には引数を検証してツールを実行します。以下はモック結果。
final result = {'city': city, 'weather': 'sunny'};
messages.add({
'role': 'tool',
'tool_call_id': call['id'],
'content': jsonEncode(result), // 結果はJSON文字列
});
}
}
} finally {
llm.setTools(null);
}
}
PARSE_ERRORだけを再試行します。失敗したassistantは履歴に追加せず、既存履歴にuserの再試行指示を追加します。各サンプルでは例外時もfinallyでツール設定を解除します。
# Tool Use with SDK-buffered responses. Deltas are optional previews.
import json
import ailia_llm
model = ailia_llm.AiliaLLM()
model.open("gemma-4-E2B-it-Q4_K_M.gguf", n_ctx=4096)
try:
model.set_tools([{"type":"function","function":{
"name":"get_weather", "description":"Get the current weather.",
"parameters":{"type":"object","properties":{"city":{"type":"string"}},"required":["city"]}
}}])
messages = [{"role":"user","content":"What is the weather in Tokyo?"}]
for turn in range(8):
for delta in model.generate_json(messages):
print(delta, end="", flush=True) # optional preview; no accumulation
try:
response = model.get_response_json()
except ailia_llm.AiliaLLMError as error:
if error.code != ailia_llm.AILIA_LLM_STATUS_PARSE_ERROR:
raise
# Keep existing history; never append the failed assistant or a tool result.
messages.append({"role": "user", "content": "Please retry with a complete response or tool call."})
continue
messages.append(response)
if not response.get("tool_calls"):
print(response["content"])
break
for call in response["tool_calls"]:
args = json.loads(call["function"]["arguments"])
# Validate and execute the tool in the application. Mock result here.
result = {"city": args["city"], "weather": "sunny"}
messages.append({"role":"tool", "tool_call_id":call["id"],
"content":json.dumps(result)})
finally:
model.set_tools(None)