ailia LLM マルチモーダル ガイド

ailia LLM でマルチモーダルプロジェクタを読み込み、画像・音声を入力する方法を説明します。メッセージ構造体で入力する方法と、JSON履歴で入力する方法の2つがあります。

概要と流れ

ailia LLM では、画像・音声のエンコーダを含むマルチモーダルプロジェクタ(mmproj)をモデルと一緒に読み込むことで、画像・音声を入力できます。どの言語でも次の流れで使用します。

モデルを開く
GGUF または QNN Model
↓
プロジェクタ(mmproj)を開く
モデルに対応する画像・音声エンコーダ
↓
対応状況を確認する
画像入力・音声入力に対応しているか
↓
画像・音声を含むメッセージを入力する
メッセージ構造体、または JSON 履歴
↓
生成する
テキスト入力と同じ生成 API

プロジェクタは、使用するモデルに対応したものを指定してください。プロジェクタを開いていない状態や、対応していない種類のメディアを入力するとエラーになります。

入力方法

画像・音声の入力方法は2つあります。

メッセージ構造体で入力JSON履歴で入力
メッセージrole・contentと、メディアの一覧messages配列のJSON(UTF-8)
メディアの位置contentの<__media__>の位置content配列での順序
メディアの指定ファイルパス、エンコード済みデータ、Raw RGB(画像のみ)ファイルパス、Base64(エンコード済みファイル)
生成結果生成の差分テキストを連結生成後に応答をJSONで取得
Tool Use併用できません併用できます

画像・音声を入力するだけであればどちらの方法でも使用できます。Raw RGBを渡す場合はメッセージ構造体で入力し、Tool Useと組み合わせる場合はJSON履歴で入力してください。 Tool Useの詳細はTool Use ガイドを参照してください。画像はJPG、PNG、TGA、BMP、PSD、GIF、HDR、PIC、音声はWAV、MP3、FLAC等のファイルに対応します。

メッセージ構造体で入力

メッセージのcontentに<__media__>を書き、その位置に入れる画像・音声を、メッセージのメディア一覧に同じ順番で指定します。メディアの種類は"image"または"audio"です。

{"role": "user", "content": "この画像について説明してください。 <__media__>",
 "media_data": [{"media_type": "image", "file_path": "/path/to/image.jpg"}]}

上記は概念を示す例です。各言語での書き方は、下のサンプルを参照してください。

JSON履歴で入力

画像・音声は、userのcontent配列にtextと並べて順番に指定します。各言語とも、この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(Base64)のどちらか一方を指定します。

各言語のAPI

言語プロジェクタの読み込み対応状況の確認メッセージ構造体で入力JSON履歴で入力
C / C++ailiaLLMOpenMultimodalProjectorFileA / WailiaLLMGetMultimodalCapabilitiesailiaLLMSetMultimodalPromptailiaLLMSetPromptJson
KotlinopenMultimodalProjectorFile(path)getMultimodalCapabilities()setPrompt(Array<AiliaLLMMultimodalChatMessage>)setPromptJson(messagesJson)
FlutteropenMultimodalProjectorFile(path)getMultimodalCapabilities()(Map)setPrompt(messages)(media_data付き)setPromptJson(messages)
Pythonopen_multimodal_projector(path)get_multimodal_capabilities()(dict)generate(messages)(media_data付き)generate_json(messages)
UnityOpenMultimodalProjector(path)GetMultimodalCapabilities(ref, ref)SetMultimodalPrompt(messages)SetPromptJson(messagesJson)

C++サンプル(SetMultimodalPrompt)

モデルとprojectorを読み込み、メッセージ構造体で画像を渡して応答を生成します。生成結果はGetDeltaTextで連結します。

#include "ailia_llm.h"
#include <cstdio>
#include <string>
#include <vector>

static int open_projector(AILIALLM* llm) {
    int status = ailiaLLMOpenMultimodalProjectorFileA(llm, "/path/to/mmproj.gguf");
    if (status != AILIA_LLM_STATUS_SUCCESS) return status;
    unsigned int vision = 0, audio = 0;
    status = ailiaLLMGetMultimodalCapabilities(llm, &vision, &audio);
    if (status != AILIA_LLM_STATUS_SUCCESS) return status;
    // vision_support / audio_support で画像・音声の対応を確認します。
    return vision ? AILIA_LLM_STATUS_SUCCESS : AILIA_LLM_STATUS_INVALID_STATE;
}

static int describe_image(AILIALLM* llm) {
    AILIALLMMediaData media = {};
    media.media_type = "image";
    media.file_path = "/path/to/image.jpg"; // dataとdata_sizeでバッファも指定可
    AILIALLMMultimodalChatMessage message = {};
    message.role = "user";
    message.content = "この画像について説明してください。 <__media__>";
    message.media_data = &media;
    message.media_count = 1;
    int status = ailiaLLMSetMultimodalPrompt(llm, &message, 1);
    if (status != AILIA_LLM_STATUS_SUCCESS) return status;
    std::string text;
    unsigned int done = 0;
    while (!done) {
        status = ailiaLLMGenerate(llm, &done);
        if (status != AILIA_LLM_STATUS_SUCCESS) return status;
        if (done) break;
        unsigned int size = 0;
        status = ailiaLLMGetDeltaTextSize(llm, &size);
        if (status != AILIA_LLM_STATUS_SUCCESS) return status;
        std::vector<char> delta(size);
        status = ailiaLLMGetDeltaText(llm, delta.data(), size);
        if (status != AILIA_LLM_STATUS_SUCCESS) return status;
        text += delta.data();
    }
    printf("%s\n", text.c_str());
    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 = open_projector(llm);
    if (status == AILIA_LLM_STATUS_SUCCESS) status = describe_image(llm);
    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;
}

C++サンプル(SetPromptJson)

同じ処理をJSON履歴で行います。履歴の編集にはnlohmann/jsonを使用します。

#include "ailia_llm.h"
#include <nlohmann/json.hpp>
#include <cstdio>
#include <string>
#include <vector>
using json = nlohmann::json;

static int open_projector(AILIALLM* llm) {
    int status = ailiaLLMOpenMultimodalProjectorFileA(llm, "/path/to/mmproj.gguf");
    if (status != AILIA_LLM_STATUS_SUCCESS) return status;
    unsigned int vision = 0, audio = 0;
    status = ailiaLLMGetMultimodalCapabilities(llm, &vision, &audio);
    if (status != AILIA_LLM_STATUS_SUCCESS) return status;
    // vision_support / audio_support で画像・音声の対応を確認します。
    return vision ? AILIA_LLM_STATUS_SUCCESS : AILIA_LLM_STATUS_INVALID_STATE;
}

static int describe_image_json(AILIALLM* llm) {
    json messages = json::array({{{"role", "user"}, {"content", json::array({
        {{"type", "text"}, {"text", "この画像について説明してください。"}},
        {{"type", "image"}, {"file_path", "/path/to/image.jpg"}}
    })}}});
    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;
    }
    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;
    try {
        json response = json::parse(output.data());
        printf("%s\n", response.at("content").get<std::string>().c_str());
    } catch (const json::exception&) {
        return AILIA_LLM_STATUS_INVALID_ARGUMENT;
    }
    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 = open_projector(llm);
    if (status == AILIA_LLM_STATUS_SUCCESS) status = describe_image_json(llm);
    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;
}

以下のKotlin・Flutter・Python・Unityのサンプルは、メッセージ構造体で画像ファイルを1枚入力します。contentの<__media__>の位置にメディアが入ります。音声を入力する場合はmedia_typeを"audio"にします。

Kotlinサンプル

AiliaLLMMultimodalChatMessageにAiliaLLMMediaDataを添えてsetPromptで入力し、generate()がtrueを返すまでgetDeltaText()を連結します。

import axip.ailia_llm.AiliaLLM
import axip.ailia_llm.AiliaLLMMediaData
import axip.ailia_llm.AiliaLLMMultimodalChatMessage

fun describeImage(modelPath: String, mmprojPath: String, imagePath: String): String {
    AiliaLLM().use { llm ->
        llm.openModelFile(modelPath, 4096)
        llm.openMultimodalProjectorFile(mmprojPath)
        check(llm.getMultimodalCapabilities().visionSupport) { "画像入力に対応していません" }
        val message = AiliaLLMMultimodalChatMessage(
            "user",
            "この画像について説明してください。 <__media__>",
            AiliaLLMMediaData("image", filePath = imagePath) // 音声は "audio"
        )
        llm.setPrompt(arrayOf(message))
        val text = StringBuilder()
        while (!llm.generate()) {
            text.append(llm.getDeltaText())
        }
        return text.toString()
    }
}

Flutterサンプル

メッセージのMapにmedia_data(List<Map<String, dynamic>>)を付けてsetPromptで入力し、generate()がnullを返すまで差分を連結します。

import 'package:ailia_llm/ailia_llm_model.dart';

String describeImage(String modelPath, String mmprojPath, String imagePath) {
  final llm = AiliaLLMModel();
  try {
    llm.open(modelPath, 4096);
    llm.openMultimodalProjectorFile(mmprojPath);
    if (llm.getMultimodalCapabilities()['vision'] != true) {
      throw StateError('画像入力に対応していません');
    }
    llm.setPrompt([
      {
        'role': 'user',
        'content': 'この画像について説明してください。 <__media__>',
        'media_data': <Map<String, dynamic>>[
          {'media_type': 'image', 'file_path': imagePath}, // 音声は 'audio'
        ],
      },
    ]);
    final text = StringBuffer();
    String? delta;
    while ((delta = llm.generate()) != null) {
      text.write(delta);
    }
    return text.toString();
  } finally {
    llm.close();
  }
}

Pythonサンプル

メッセージのdictにmedia_dataを付けてgenerateに渡すと、メッセージ構造体での入力になります。media_dataはfile_pathで指定します。

import ailia_llm

model = ailia_llm.AiliaLLM()
model.open("gemma-4-E2B-it-Q4_K_M.gguf", n_ctx=4096)
model.open_multimodal_projector("/path/to/mmproj.gguf")
if not model.get_multimodal_capabilities()["vision"]:
    raise RuntimeError("画像入力に対応していません")

messages = [{
    "role": "user",
    "content": "この画像について説明してください。 <__media__>",
    "media_data": [{"media_type": "image", "file_path": "/path/to/image.jpg"}],  # 音声は "audio"
}]
for delta in model.generate(messages):
    print(delta, end="", flush=True)
print()

Unityサンプル

AiliaLLMMultimodalChatMessageにAiliaLLMMediaDataのリストを設定してSetMultimodalPromptで入力します。失敗時はfalseを返すため、GetErrorDetail()で詳細を取得します。

using System;
using System.Collections.Generic;
using System.Text;
using ailiaLLM;

public static class MultimodalSample
{
    public static string DescribeImage(string modelPath, string mmprojPath, string imagePath)
    {
        var llm = new AiliaLLMModel();
        try
        {
            if (!llm.Create()) throw new Exception("ailiaLLMCreate failed");
            if (!llm.Open(modelPath, 4096)) throw new Exception(llm.GetErrorDetail());
            if (!llm.OpenMultimodalProjector(mmprojPath)) throw new Exception(llm.GetErrorDetail());
            bool vision = false, audio = false;
            if (!llm.GetMultimodalCapabilities(ref vision, ref audio) || !vision)
                throw new Exception("画像入力に対応していません");
            var messages = new List<AiliaLLMMultimodalChatMessage>
            {
                new AiliaLLMMultimodalChatMessage
                {
                    role = "user",
                    content = "この画像について説明してください。 <__media__>",
                    media_data = new List<AiliaLLMMediaData>
                    {
                        new AiliaLLMMediaData { media_type = "image", file_path = imagePath } // 音声は "audio"
                    }
                }
            };
            if (!llm.SetMultimodalPrompt(messages)) throw new Exception(llm.GetErrorDetail());
            var text = new StringBuilder();
            bool done = false;
            while (true)
            {
                if (!llm.Generate(ref done)) throw new Exception(llm.GetErrorDetail());
                if (done) break;
                text.Append(llm.GetDeltaText());
            }
            return text.ToString();
        }
        finally
        {
            llm.Dispose();
        }
    }
}

C APIの詳細

C / C++で使用する構造体とJSONの規則、エラーのステータスです。Kotlin・Flutter・Pythonではエラー時に例外が発生し、Unityではfalse(または空文字列)が返るため、GetErrorDetail()で詳細を取得します。

プロジェクタと対応状況

モデルを読み込んだ後、対応するprojectorをailiaLLMOpenMultimodalProjectorFileA / Wで読み込みます。画像と音声の対応状況はailiaLLMGetMultimodalCapabilitiesのvision_support / audio_supportで確認できます。

ailiaLLMSetMultimodalPrompt(メッセージ構造体)

AILIALLMMultimodalChatMessageのcontentに<__media__>を含め、対応するメディアをmedia_dataに順番に設定し、media_countに個数を指定します。 メッセージの内容は内部でコピーされるため、呼び出し後に解放できます。生成結果はailiaLLMGetDeltaTextを連結します。

AILIALLMMediaDataのmedia_typeには"image"または"audio"を指定します。メディアは次のいずれかで指定します。

ailiaLLMSetPromptJson(JSON履歴)

image/audioはfile_pathまたはdataを1つ指定します。dataは標準Base64(パディング付き)でエンコードしたファイル内容です。URL取得、動画、生RGB/PCMには対応しません。 画像・音声を含む履歴は毎回再評価します。生成結果はailiaLLMGetResponseJsonで取得します。

エラー