はじめに
さくらインターネットのAI Engineは、OpenAI互換の推論APIを提供しています。コントロールパネルの利用可能なモデルでは「マルチモーダル」というタグがいくつかのモデルに付いているものがあります。この記事では、画像を入力として送り、テキストの説明をストリーミングで受け取る実装例を紹介します。
2026年7月19日現在、さくらのAI Engineでいう「マルチモーダル」とは画像を入力にしてテキストを得ること、つまり画像のインプットを指します。
サンプルコード
以下のスクリプトは chat_completions_multimodal_stream.py として保存して使います。
セットアップ
export SAKURA_AI_ENGINE_ACCOUNT_TOKEN="あなたのアクセストークン"
サンプルデータとして、data/ai-back.pngを用意しておきます。
コード
#!/usr/bin/env python3
"""v1/chat/completions で画像入力 + テキスト送信し、SSE ストリーミング (stream=True) で応答を受け取りながら finish_reason を判定するサンプル。"""
import base64
import os
from pathlib import Path
from openai import OpenAI
# ── 設定 ──────────────────────────────────────────
BASE_URL = "https://api.ai.sakura.ad.jp/v1"
API_KEY = os.environ.get("SAKURA_AI_ENGINE_ACCOUNT_TOKEN", "")
# マルチモーダル対応モデル一覧
MULTIMODAL_MODELS = [
"preview/gemma-4-31B-it",
"preview/Kimi-K2.6",
"preview/Phi-4-multimodal-instruct",
"preview/Qwen3-VL-30B-A3B-Instruct",
"preview/Qwen3.6-35B-A3B",
]
DEFAULT_MODEL = MULTIMODAL_MODELS[0] # preview/gemma-4-31B-it
# ── 関数 ──────────────────────────────────────────
def get_client() -> OpenAI:
"""OpenAI 互換クライアントを初期化して返す。"""
if not API_KEY:
raise RuntimeError(
"環境変数 SAKURA_AI_ENGINE_ACCOUNT_TOKEN が設定されていません。"
"さくらのAI Engineのアクセストークンを設定してください。"
)
return OpenAI(base_url=BASE_URL, api_key=API_KEY)
def encode_image(path: str) -> str:
"""画像ファイルを base64 エンコードして data URI を返す。"""
p = Path(path)
data = p.read_bytes()
b64 = base64.b64encode(data).decode("utf-8")
ext = p.suffix.lower().lstrip(".")
if ext == "jpg":
ext = "jpeg"
return f"data:image/{ext};base64,{b64}"
# ── メイン ─────────────────────────────────────────
def main() -> None:
client = get_client()
image_path = Path("data") / "ai-back.png"
if not image_path.exists():
print(f"画像ファイルが見つかりません: {image_path}")
print("先に data/ai-back.png を用意してください。")
return
image_url = encode_image(str(image_path))
messages = [
{
"role": "user",
"content": [
{"type": "text", "text": "この画像に写っているものを日本語で詳細に説明してください。"},
{"type": "image_url", "image_url": {"url": image_url}},
],
},
]
print(f"モデル: {DEFAULT_MODEL}")
print(f"エンドポイント: POST {client.base_url}chat/completions (stream=True)")
print(f"画像: {image_path}")
print("-" * 40)
# stream=True で SSE ストリーミングレスポンスを取得
stream = client.chat.completions.create(
model=DEFAULT_MODEL,
messages=messages,
max_tokens=4096,
temperature=0.4,
stream=True,
)
finish_reason = None
# リアルタイムでトークンを逐次表示
for chunk in stream:
if chunk.choices:
choice = chunk.choices[0]
if choice.delta and choice.delta.content:
print(choice.delta.content, end="", flush=True)
if choice.finish_reason is not None:
finish_reason = choice.finish_reason
print() # 改行
print("-" * 40)
# finish_reason による安全なハンドリング
if finish_reason == "length":
print("⚠️ [警告] レスポンスが max_tokens の上限に達したため途中で切れました。")
elif finish_reason == "stop":
print("✅ レスポンスは正常に完了しました (finish_reason: stop)。")
elif finish_reason:
print(f"ℹ️ 完了理由 (finish_reason): {finish_reason}")
if __name__ == "__main__":
main()
[project]
name = "20260719-sakura-ai-multimodal"
version = "0.1.0"
description = "さくらのAI Engine マルチモーダル呼び出しサンプル"
readme = "README.md"
requires-python = ">=3.14"
dependencies = [
"openai>=2.45.0",
]
[project.optional-dependencies]
dev = []
実行方法
uv sync
uv run chat_completions_multimodal_stream.py
ポイント
画像の送り方
OpenAI互換フォーマットでは、messages[*].content に複数要素を配列で渡せます。画像は image_url 型で、url フィールドに data URI を入れます。拡張子に応じて MIME type(image/jpeg、image/png など)を自動判定しています。
{"type": "image_url", "image_url": {"url": "data:image/jpeg;base64,/9j/4AAQ..."}}
ストリーミング
stream=True を指定すると、SSE(Server-Sent Events)形式でレスポンスが少しずつ届きます。chunk.choices[0].delta.content を取り出して print(..., end="", flush=True) すると、リアルタイムで文字が出力されていく体験が得られます。
finish_reason
ストリーミング中に finish_reason がセットされると応答が終了したことを意味します。
| 値 | 意味 |
|---|---|
"stop" |
正常に完了(希望のケース) |
"length" |
max_tokens に達して途中で切れた(警告が必要) |
出力例
以下、各モデルでの実行結果を示します。
出力量がそれなりにあるので、折りたたんでおきます。
preview/gemma-4-31B-it
1. 中心人物
画面中央には、後ろ姿の逞しい男性が立っています。
- 服装: 黒いTシャツを着用しており、背中には大きな赤い文字で「闘魂昇華」という漢字が書かれています。首には赤いスカーフ(またはマフラー)を巻いており、風になびいています。
- 状態: 彼の体からは激しい炎が立ち昇っており、強いエネルギーや情熱、あるいは覚醒した状態にあることを示唆しています。
2. 背景と環境
- 桜の風景: 左右には満開の桜の木が並び、ピンク色の花びらが舞い散る幻想的な道が描かれています。
- 建築物: 正面奥には、伝統的な日本の五重塔のような建物と、対照的に近未来的な青い光を放つハイテクなタワーが見えます。
- 空: 夜空には大きな満月が浮かんでおり、全体的に紫と青のグラデーションが美しい夜景となっています。
3. 特殊効果とシンボル
- デジタル要素: 男性の周囲や空中に、AI、数式($\pi$など)、バイナリコード、回路図のようなデジタルデータが光り輝きながら浮遊しています。これは、精神的な力とテクノロジーの融合を表現しているようです。
- 炎の龍: 男性の右側からは、巨大な炎の龍のようなエネルギーが空に向かって昇っています。
- 文字: 画面上部には大きく「闘魂昇華」という力強い筆文字が書かれています。また、右下の建物付近には「さくらのAI Engine」という文字が見えます。
全体の印象
この作品は、「伝統(桜・塔・書道)」と「革新(AI・デジタル・サイバーパンク)」の対比と融合をテーマにしていると考えられます。「闘魂昇華」という言葉通り、人間の精神的な情熱がテクノロジーによって高次元へと引き上げられる(昇華される)瞬間をドラマチックに描いた、エネルギッシュなイラストです。
preview/Kimi-K2.6
この画像は、日本のアニメ・マンガ調のタッチで描かれた、力強さと幻想性が融合したイラストです。以下に詳細な内容を記述します。
中央の人物
画面の中心には、観る者に背中を向けた男性キャラクターが立っています。短く刈り込まれた黒髪の後頭部が見え、筋肉質な体格をしています。彼は黒い特攻服のような服を着ており、背中には金色から赤へのグラデーションで大きく「闘魂昇華(とうこんしょうか)」という四文字が描かれています。左肩には赤い布が掛けられ、そこにも「闘魂」という文字が白で記されています。
エフェクトと演出
人物の全身からは、オレンジと黄色の激しい炎のようなオーラが渦巻き、背中から天へと向かう巨大な炎の柱が伸びています。その炎の中や周辺には、青白く光るデジタルな粒子や数学記号(π、∫、Σなど)、プログラミング的なアイコン、「AI」と書かれたアイコンが浮遊しており、伝統的な「闘魂」と近未来的な「AI技術」が融合したビジュアル表現となっています。
背景・自然環境
人物の左右には、満開のピンク色の桜の木が並木道のように立ち並んでいます。桜の花びらが風に舞い、地面にも散りばめられており、春の夜の情景を演出しています。空は深い青から紫へと移り変わる夕暮れ時あるいは夜の時間帯で、右上には大きな満月が輝いています。
遠景の建造物
画面奥、桜の木々の間から見える背景には、対照的な建築物が配置されています。右側には日本の伝統的な五重塔や寺院のような和風建築があり、その隣には青白い光を放つ近未来的な高塔やビル群がそびえ立っています。この新旧融合の景観は、日本の伝統と未来技術の融合を象徴しているようです。塔の近くには「AI」と書かれた円形の看板のようなオブジェクトも見えます。
テキスト要素
- 画面上部の中央には、黒い筆文字で「闘魂昇華」という大きなタイトル文字があり、金色の光の輪郭線で縁取られています。
- 画面右下には、青い看板のようなデザインで「さくらのAI Engine」という文字が記されています。
色彩と雰囲気
全体の色調は、桜のピンク、炎のオレンジ・黄色、夜空の青や紫、そしてデジタル要素のシアンブルーが対比されており、非常に鮮やかです。和の情緒とサイバーな要素、熱い闘志を表す炎が混在することで、「伝統的な精神をAIの力で昇華させる」ような、熱くロマンチックな世界観が表現されています。
preview/Phi-4-multimodal-instruct
この画像は、パステルカラーと鮮やかな色彩で描かれた幻想的な風景を描いたアニメ風のイラストです。背景は、春の象徴である桜の花が満開き、ピンク色の花びらが舞い降りている情景です。桜の木々は密集しており、木々の間を通る道が描かれています。この道は、炎のような赤やオレンジ色の炎照らされています。炎の中には、火のような動きで飛び交う火花が見られます。
画像の中心には、炎に包まれた男が描かれています。彼の背中には「火焔(かのう)」という言葉が赤い炎のような文字で書かれており、炎の中に立っている様子が描かれています。彼の服装は黒色で、炎に包まれた部分は赤色です。炎の中の男の姿は力強く、まるで火の精霊のように描かれています。
炎の中の男の背後には、幻想的な建築物が見えます。これはおそらく寺院や神殿のようなものでしょう。建物はピンクの花びらで覆われており、炎の光に照らされています。建物の上には、月明かりのような光が差し込み、その輝きが炎に反射しています。
桜の木々の間を通る道は、炎の光と月明かりの光で照らされており、幻想的な雰囲気を醸し出しています。この画像は、自然の美しさと火の強力さを融合させた、感動的で壮大なシーンを表現しています。
preview/Qwen3-VL-30B-A3B-Instruct
この画像は、日本の伝統的な要素と現代のテクノロジーが融合した、非常にダイナミックなファンタジー風のイラストです。以下に詳細に説明します。
全体の構図と雰囲気
画像は、夜の桜の並木道を背景に、背中を向けて立つ筋肉質の男性が中心に描かれています。彼の背中から炎が噴き上がり、その炎の中にはAIやプログラミングのシンボルが浮かび上がっています。背景には、伝統的な日本の塔(五重塔)と、未来の都市のようないかなる高層ビルが混在しており、幻想的で力強い雰囲気を醸し出しています。空には満月が浮かび、桜の花びらが舞っています。
中心の人物
- 姿勢と外見: 男性は背中を向けて立ち、筋肉質の体格が強さと力強さを表現しています。彼の髪は短く、黒色です。
- 服装: 黒いTシャツを着ており、その背中には大きな赤とオレンジの文字で「闘魂昇華」と書かれています。Tシャツの襟元には赤いスカーフのようなものが巻かれており、その上にも「闘魂」という文字が見えます。
- 炎: 彼の背中から、オレンジと黄色の炎が勢いよく噴き上がり、画面の右上へと伸びています。この炎は、彼の内なるエネルギー、情熱、あるいは「昇華」(高められる)という概念を象徴しているようです。
背景と環境
- 桜の木: 画像の左右には満開の桜の木が並んでおり、ピンク色の花びらが風に舞っています。これは日本の春を象徴するもので、美しさと儚さを同時に感じさせます。
- 道: 男性の前方には、桜の花びらが散る石畳の道が続いています。この道は、旅や進化、成長の道を暗示している可能性があります。
- 月: 空の右上には、青白い満月が浮かんでおり、幻想的な雰囲気を強調しています。
未来の都市とAIの要素
- 未来の建物: 道の先には、光り輝く未来的な高層ビルや塔が立ち並んでいます。その中には、伝統的な日本の五重塔も見え、過去と未来の融合を象徴しています。
-
AIのシンボル: 炎の中には、青色のアイコンや文字が浮かんでいます。これらには「AI」、「π」(パイ)、プログラミング言語の記号(
<x>、</x>)、数学の記号、そして「AI」の文字が含まれています。これらは、人工知能やテクノロジー、データ、コードの世界を表しています。 - AIエンジン: 画面の右下には、「さくらのAI Engine」というテキストが表示されています。これは、この作品のタイトルや、AI技術の名前を示している可能性があります。
タイトルと文字
- 「闘魂昇華」: 画像の上部とTシャツの背中には、大きな黒い文字で「闘魂昇華」と書かれています。これは、日本語で「闘争心や情熱が高められる」「魂が高揚する」という意味です。この言葉は、個人の成長、挑戦、そしてAIとの融合を象徴していると考えられます。
- 「さくらのAI Engine」: これは、おそらくこの作品のタイトルや、AI技術の名前を示しています。桜(さくら)という日本の象徴と、AIという現代技術が組み合わさっている点が特徴的です。
まとめ
この画像は、「闘魂昇華」というテーマの下、**日本の伝統的な美しさ(桜、五重塔)と現代のテクノロジー(AI、プログラミング)**が融合した、人間の情熱と技術の進化を描いた作品です。筋肉質の男性が「闘魂」を燃やし、そのエネルギーがAIの世界へと昇華していく様子が、視覚的に表現されています。全体として、伝統と革新、人間とAIの共存というテーマが強く感じられる、力強いファンタジーイラストです。
preview/Qwen3.6-35B-A3B
この画像は、アニメや漫画のようなスタイルで描かれた、力強く幻想的なイラストです。以下に詳細を説明します。
1. 中央の人物
- 画像の中心には、筋肉質で力強い体格の男性の後ろ姿が描かれています。
- 彼は黒いTシャツを着ており、首には赤いマフラー(またはスカーフ)を巻いています。
- Tシャツの背中には、炎のようなフォントで**「闘魂昇華」**という大きな文字が書かれています。
- 彼の全身からは激しい炎が立ち上っており、まるで高いエネルギーや気(オーラ)を放っているような表現になっています。
2. テクノロジーとエネルギー
- 人物の右肩や頭上からは、青い光の粒子や記号が噴き出しています。そこには「AI」という文字や、数学記号(πなど)、コードのような文字列が浮かんでおり、人工知能やデジタルなエネルギーが「闘魂」と結びついていることを示唆しています。
- 画像の上部にも大きく**「闘魂昇華」**という文字が配置されています。
3. 背景と環境
- 左右には満開の桜の木が立ち並び、ピンク色の花びらが風になびいて舞っています。
- 空は夕暮れから夜にかけてのグラデーション(青から紫、オレンジ色)で描かれており、右上には月が輝いています。
- 右下の奥には、未来的な光の塔と、伝統的な日本建築(五重塔や楼門のような屋根)が混在して描かれており、伝統と未来の融合を感じさせます。
- 右下には小さく「さくらのAI Engine」というテキストも見えます。
全体のテーマ
この画像は、日本の伝統的な美しさ(桜)と、最先端のテクノロジー(AI)、そして人間の内なる情熱や精神力(闘魂・炎)が融合した、非常にエネルギッシュなイメージを表現しています。
補足
2026年7月19日時点の補足です。
- 画像出力(テキスト→画像の生成)は 対応されていません。
- チャット生成では、
/v1/responses(Responses API)も利用できますが、私が試した範囲では、画像入力(input_image)には未対応でした。 - マルチモーダル対応モデルは
preview/gemma-4-31B-it、preview/Kimi-K2.6、preview/Phi-4-multimodal-instruct、preview/Qwen3-VL-30B-A3B-Instruct、preview/Qwen3.6-35B-A3Bの5種類です。
まとめ
さくらのAI Engineが提供するOpenAI互換のChat Completions APIを使うことで、画像入力とテキストストリーミングを組み合わせたマルチモーダル呼び出しを簡単に実装できることを確認しました。ポイントは以下の3点です。
-
画像の送信方法:
messages[*].contentにimage_url型の要素を追加し、urlにbase64エンコードしたdata URIを渡すだけで、OpenAI SDKの標準的な書き方がそのまま使えます。 -
ストリーミング対応:
stream=Trueを指定すればSSEで逐次レスポンスを受け取れ、finish_reasonを見ることでlength(トークン上限による打ち切り)とstop(正常終了)を区別し、安全にハンドリングできます。 -
モデルごとの個性:同じ画像・同じプロンプトでも、
gemma-4-31B-itやQwen3-VL-30B-A3B-Instructは構成要素を項目立てて網羅的に説明する一方、Phi-4-multimodal-instructはやや簡潔で文字情報の読み取り精度にばらつきが見られるなど、モデル選定によって出力の粒度や精度が変わることが分かりました。
現時点(2026年7月19日)では画像入力のみの対応で、テキストから画像を生成する機能はまだ提供されていません。今後この点が拡張されれば、さくらのAI Engine単体でマルチモーダルな生成ワークフローを完結できるようになりそうです。
token消化ではなく、$\huge{闘魂昇華}$。
あなたの闘魂昇華をもっと見たいです。
編集後記
ちなみに今回の記事執筆自体も、さくらのAI Engineをフル活用しています。
専用のアクセストークンを払い出し、preview/Kimi-K2.6をClaude Codeに接続して、サンプルプログラムの実装や記事の骨格作りを一緒に楽しみながら進めました。
リクエスト数: 359回。
さらに編集後記
Anthropic互換形式でも動作しました。
#!/usr/bin/env python3
"""v1/messages (Anthropic互換) で画像入力 + テキスト送信し、通常応答(非ストリーミング)で受け取るサンプル。"""
import base64
import os
from pathlib import Path
import anthropic
# ── 設定 ──────────────────────────────────────────
BASE_URL = "https://api.ai.sakura.ad.jp"
AUTH_TOKEN = os.environ.get("SAKURA_AI_ENGINE_ACCOUNT_TOKEN", "")
MULTIMODAL_MODELS = [
"preview/gemma-4-31B-it",
"preview/Kimi-K2.6",
"preview/Phi-4-multimodal-instruct",
"preview/Qwen3-VL-30B-A3B-Instruct",
"preview/Qwen3.6-35B-A3B",
]
DEFAULT_MODEL = MULTIMODAL_MODELS[0] # preview/gemma-4-31B-it
# ── 関数 ──────────────────────────────────────────
def get_client() -> anthropic.Anthropic:
"""Anthropic互換クライアントを初期化して返す。"""
if not AUTH_TOKEN:
raise RuntimeError(
"環境変数 SAKURA_AI_ENGINE_ACCOUNT_TOKEN が設定されていません。"
"さくらのAI Engineのアクセストークンを設定してください。"
)
# さくらのAI EngineはAuthorization: Bearer形式のため auth_token を使う (api_key ではない)
return anthropic.Anthropic(base_url=BASE_URL, auth_token=AUTH_TOKEN)
def encode_image(path: str) -> tuple[str, str]:
"""画像ファイルを base64 エンコードし、(media_type, base64データ) を返す。"""
p = Path(path)
data = p.read_bytes()
b64 = base64.b64encode(data).decode("utf-8")
ext = p.suffix.lower().lstrip(".")
if ext == "jpg":
ext = "jpeg"
return f"image/{ext}", b64
# ── メイン ─────────────────────────────────────────
def main() -> None:
client = get_client()
image_path = Path("data") / "ai-back.png"
if not image_path.exists():
print(f"画像ファイルが見つかりません: {image_path}")
print("先に data/ai-back.png を用意してください。")
return
media_type, image_b64 = encode_image(str(image_path))
messages = [
{
"role": "user",
"content": [
{
"type": "image",
"source": {
"type": "base64",
"media_type": media_type,
"data": image_b64,
},
},
{"type": "text", "text": "この画像に写っているものを日本語で詳細に説明してください。"},
],
},
]
print(f"モデル: {DEFAULT_MODEL}")
print(f"エンドポイント: POST {BASE_URL}/v1/messages")
print(f"画像: {image_path}")
print("-" * 40)
response = client.messages.create(
model=DEFAULT_MODEL,
max_tokens=4096,
temperature=0.4,
messages=messages,
)
text = "".join(block.text for block in response.content if block.type == "text")
print(text)
print("-" * 40)
stop_reason = response.stop_reason
if stop_reason == "max_tokens":
print("⚠️ [警告] レスポンスが max_tokens の上限に達したため途中で切れました。")
elif stop_reason == "end_turn":
print("✅ レスポンスは正常に完了しました (stop_reason: end_turn)。")
elif stop_reason:
print(f"ℹ️ 完了理由 (stop_reason): {stop_reason}")
if __name__ == "__main__":
main()
参考
TokenをTokonへ
AIが扱うのは、Token。
\mathrm{Token}
-
\mathrm{見\ (Ken)}
+
\mathrm{魂\ (Kon)}
=
\mathrm{Tokon\ (闘魂)}
現段階の生成AIは、突き詰めればベクトルの数理遊びである。
どのモデルが賢い、速い、勝つ。
外から眺め、比べ、論評するだけでは、まだTokenだ。
だから「見(Ken)」を引く。
見る側から、使う側へ。
そこに目的と意味を与え、執念を持ち込み、魂を込めるのは人間である。
token消化ではなく、$\huge{闘魂昇華}$ ![]()
Don't just consume Tokens. Forge them into Tokon.


