ailia LLM でマルチモーダルプロジェクタを読み込み、画像・音声を入力する方法を説明します。メッセージ構造体で入力する方法と、JSON履歴で入力する方法の2つがあります。
ailia LLM では、画像・音声のエンコーダを含むマルチモーダルプロジェクタ(mmproj)をモデルと一緒に読み込むことで、画像・音声を入力できます。どの言語でも次の流れで使用します。
プロジェクタは、使用するモデルに対応したものを指定してください。プロジェクタを開いていない状態や、対応していない種類のメディアを入力するとエラーになります。
画像・音声の入力方法は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"}]}
上記は概念を示す例です。各言語での書き方は、下のサンプルを参照してください。
画像・音声は、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)のどちらか一方を指定します。
| 言語 | プロジェクタの読み込み | 対応状況の確認 | メッセージ構造体で入力 | JSON履歴で入力 |
|---|---|---|---|---|
| C / C++ | ailiaLLMOpenMultimodalProjectorFileA / W | ailiaLLMGetMultimodalCapabilities | ailiaLLMSetMultimodalPrompt | ailiaLLMSetPromptJson |
| Kotlin | openMultimodalProjectorFile(path) | getMultimodalCapabilities() | setPrompt(Array<AiliaLLMMultimodalChatMessage>) | setPromptJson(messagesJson) |
| Flutter | openMultimodalProjectorFile(path) | getMultimodalCapabilities()(Map) | setPrompt(messages)(media_data付き) | setPromptJson(messages) |
| Python | open_multimodal_projector(path) | get_multimodal_capabilities()(dict) | generate(messages)(media_data付き) | generate_json(messages) |
| Unity | OpenMultimodalProjector(path) | GetMultimodalCapabilities(ref, ref) | SetMultimodalPrompt(messages) | SetPromptJson(messagesJson) |
モデルと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;
}
同じ処理を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"にします。
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()
}
}
メッセージの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();
}
}
メッセージの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()
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 / C++で使用する構造体とJSONの規則、エラーのステータスです。Kotlin・Flutter・Pythonではエラー時に例外が発生し、Unityではfalse(または空文字列)が返るため、GetErrorDetail()で詳細を取得します。
モデルを読み込んだ後、対応するprojectorをailiaLLMOpenMultimodalProjectorFileA / Wで読み込みます。画像と音声の対応状況はailiaLLMGetMultimodalCapabilitiesのvision_support / audio_supportで確認できます。
AILIALLMMultimodalChatMessageのcontentに<__media__>を含め、対応するメディアをmedia_dataに順番に設定し、media_countに個数を指定します。
メッセージの内容は内部でコピーされるため、呼び出し後に解放できます。生成結果はailiaLLMGetDeltaTextを連結します。
AILIALLMMediaDataのmedia_typeには"image"または"audio"を指定します。メディアは次のいずれかで指定します。
file_path(UTF-8)。dataを指定した場合は無視されます。dataとdata_sizeを指定し、width / heightは0にします。dataにwidth * height * 3バイトのRGBデータを指定し、width / heightを設定します。画像のみ使用できます。image/audioはfile_pathまたはdataを1つ指定します。dataは標準Base64(パディング付き)でエンコードしたファイル内容です。URL取得、動画、生RGB/PCMには対応しません。
画像・音声を含む履歴は毎回再評価します。生成結果はailiaLLMGetResponseJsonで取得します。
ailiaLLMSetMultimodalPromptを呼ぶとINVALID_STATEです。ailiaLLMSetPromptJsonで不正なBase64はINVALID_ARGUMENTです。