ailia LLM Tool Use ガイド

ailia LLM 1.5 の Tool Use API で Gemma 4 の Function Calling を使う方法を説明します。JSON履歴とtool_call_idによる結果の返却、画像・音声入力、GetResponseJsonによる結果取得を説明します。

Tool Useの流れ

Tool Useでは最初のuserメッセージからailiaLLMSetPromptJsonを使用します。 ailiaLLMSetToolsでツールが設定されている間、従来のailiaLLMSetPromptとailiaLLMSetMultimodalPromptは AILIA_LLM_STATUS_INVALID_STATEを返します。ツールを解除すると再び利用できます。

  1. ailiaLLMSetToolsにOpenAI互換のツール定義配列を渡します。
  2. ailiaLLMSetPromptJsonにmessages配列そのもの(UTF-8 JSON)を渡します。
  3. ailiaLLMGenerateを完了まで呼び出します。deltaはailiaLLMGetDeltaTextでプレビューできますが、取得・連結は必須ではありません。
  4. ailiaLLMGetResponseJsonSize / ailiaLLMGetResponseJsonで構造化assistant JSONを取得します。
  5. 成功したオブジェクトをそのまま履歴に追加し、tool_callsがあれば実行して、ID付きのtool結果を追加し、次のプロンプトを設定します。

SDKが生成結果を蓄積するので、生の出力をアプリ側で引き回す必要はありません。 GetResponseJsonは読み取りで蓄積を消去しません。新しいプロンプトでリセットします。

JSON履歴

[
  {"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で確認できます。

不完全な出力・エラー

C++サンプル

モデルの読み込み、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;
}

各言語のAPI

言語 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履歴を使用してください。

Kotlinサンプル

モデル読込済みの 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)
    }
}

Flutterサンプル

モデル読込済みの 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);
  }
}

Pythonサンプル

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)