文字だけの操作マニュアルを読み込ませ、漫画風の操作マニュアル(コマ割り画像+吹き出し) を 自動生成する実用サンプルです。3 種類の API を 1 つのパイプラインに組み合わせます。
ステップ API モデル例 base_url
- コマ割り設計 テキスト生成 qwen-plus /alibaba/compatible-mode/v1
- コマ画像生成 画像生成(text2image) qwen-image /alibaba/api/v1
- 画像点検・alt付与 ビジョン qwen-vl-max /alibaba/compatible-mode/v1
manual.txt ──(1 テキスト生成)──> コマ割りJSON ──(2 画像生成)──> PNG ──(3 ビジョン)──> manga.html
:::tip すべて Lykuro 透過プロキシ経由 テキスト生成・ビジョンは OpenAI 互換(/alibaba/compatible-mode/v1)、画像生成は DashScope ネイティブ(/alibaba/api/v1)を、base_url の差し替えだけで利用します。 上流キーは Lykuro 側が注入するため、クライアントは LYKURO_API_KEY(sk-jp-...)だけ持てば動きます。 :::
- コマ割りを設計する(テキスト生成)
マニュアル本文を渡し、各コマの「画像プロンプト・キャプション・吹き出し」を JSON で受け取ります。 response_format: json_object で JSON を強制します(構造化出力 参照)。
Python
cURL
import json, os
from openai import OpenAI
client = OpenAI(
api_key=os.environ["LYKURO_API_KEY"],
base_url="https://api.lykuro.ai/alibaba/compatible-mode/v1",
)
SYSTEM = """操作マニュアルを漫画のコマ列に分解する編集者です。
各コマに step_title / image_prompt(英語) / caption(日本語) / speech(日本語) を付け、
{"panels":[...]} の JSON だけを返してください。"""
resp = client.chat.completions.create(
model="qwen-plus",
response_format={"type": "json_object"},
messages=[
{"role": "system", "content": SYSTEM},
{"role": "user", "content": "次のマニュアルを最大6コマに:\n" + open("manga_manual.txt", encoding="utf-8").read()},
],
)
panels = json.loads(resp.choices[0].message.content)["panels"]
- コマ画像を生成する(画像生成 / text2image)
- 各コマの image_prompt から漫画風 PNG を生成します。DashScope の text2image は 非同期方式(タスク作成 → ポーリング → 画像URL取得)です。Lykuro は body を改変せず 透過するため、上流の契約どおりに呼べます。
- import time, requests
H = {"Authorization": f"Bearer {os.environ['LYKURO_API_KEY']}", "Content-Type": "application/json"}
BASE = "https://api.lykuro.ai/alibaba/api/v1"
タスク作成(X-DashScope-Async で非同期を要求)
task = requests.post(
f"{BASE}/services/aigc/text2image/image-synthesis",
headers={**H, "X-DashScope-Async": "enable"},
json={"model": "qwen-image",
"input": {"prompt": "manga panel, a user logging into a web dashboard, clean lineart"},
"parameters": {"size": "1024*1024", "n": 1}},
).json()
task_id = task["output"]["task_id"]
完了までポーリング
while True:
time.sleep(3)
out = requests.get(f"{BASE}/tasks/{task_id}", headers=H).json()["output"]
if out["task_status"] == "SUCCEEDED":
url = out["results"][0]["url"]
open("panel_01.png", "wb").write(requests.get(url).content)
break
if out["task_status"] == "FAILED":
raise RuntimeError(out.get("message"))
3. 生成画像を点検し alt を付ける(ビジョン)
3. 生成した PNG を qwen-vl-max に読み戻し、その手順を正しく描けているかを確認しつつ、 アクセシビリティ用の代替テキスト(alt)を 1 文で得ます(画像入力 参照)。
import base64
b64 = base64.b64encode(open("panel_01.png", "rb").read()).decode()
alt = client.chat.completions.create(
model="qwen-vl-max",
messages=[{"role": "user", "content": [
{"type": "image_url", "image_url": {"url": f"data:image/png;base64,{b64}"}},
{"type": "text", "text": "この漫画コマを1文の日本語altで説明して。"},
]}],
).choices[0].message.content