# Qwen-TTS / Qwen3-TTS-Flash 本番運用ガイド：49音色・10言語・中国語方言・ボイスクローンを DashScope API と OSS で使い分ける音声合成設計

> Qwen-TTS / Qwen3-TTS を本番品質で使う実装ガイド。公式ドキュメントに忠実なモデル一覧（qwen3-tts-flash / instruct-flash / realtime / qwen-tts）、49音色・10言語・9つの中国語方言、DashScope API（Python・HTTP・ストリーミング）とOSS版（Apache-2.0・3秒ボイスクローン・ボイスデザイン）の使い分け、料金・冪等・回復性・可観測性・倫理まで実コードで解説します。

- 公開日: 2026-06-25
- 著者: 友田 陽大
- タグ: Python, 音声合成, 生成AI, Qwen, アーキテクチャ設計, 可観測性
- URL: https://tomodahinata.com/blog/qwen-tts-qwen3-tts-flash-production-guide

## 要点

- 「Qwen-TTS」にはマネージドAPI（DashScope / Model Studio）とOSS版（GitHub・Apache-2.0）の2実体があり、最初にどちらを使うか確定させる
- 主力の Qwen3-TTS-Flash は49以上の音色・10言語・9つの中国語方言に対応し、seed-tts-eval で中国語・英語の安定性SOTAを公称
- APIは MultiModalConversation.call（または HTTP の multimodal-generation/generation）で voice・language_type・stream を指定。生成URLは24時間で失効するため即座に自前ストレージへ退避する
- instruct モデルは自然言語の instructions で話速・抑揚・感情を制御でき、realtime は WebSocket で「話しながら出す」逐次再生に対応する
- OSS版は3秒音声からのボイスクローンと自然言語ボイスデザインが可能。本人同意のない声の複製を禁止する倫理境界を設計に組み込む

---

「テキストを自然な音声にしたい」——要件としては一行です。けれど本番に載せようとした瞬間、判断すべきことが一気に増えます。**マネージドAPIを叩くのか、OSS版を自前で回すのか。どの音色（声）を選ぶのか。日本語だけか、10言語の多言語ナレーションか。リアルタイムで話しながら返すのか、バッチで音声ファイルを量産するのか。生成URLが24時間で消える落とし穴をどう塞ぐのか。そして——他人の声を勝手に複製しないという一線をどう守るのか。**

この記事は、Alibaba Cloud（通義/Qwen）の音声合成モデル **Qwen-TTS / Qwen3-TTS** を**本番品質**で運用するための実装ガイドです。文字起こし（STT）側の設計は[OpenAI Whisper 本番運用ガイド](/blog/openai-whisper-production-guide-selfhost-vs-api)で扱いました。本記事はその対になる**音声生成（TTS）側**の設計を、私が実際に多言語吹き替えパイプライン（[AI動画ローカライズ基盤](/case-studies/ai-video-localization-lipsync)）で組んだ知見も交えて解説します。音声AI全体（STT・TTS・音声エージェント）の地図と技術選定は[音声AI 本番実装ガイド](/blog/voice-ai-production-guide-stt-tts-voice-agents)にまとめています。

> **この記事のルール**：モデル仕様・API仕様は **Qwen公式ブログ・Alibaba Cloud Model Studio ドキュメント・GitHub（QwenLM/Qwen3-TTS）の2026年6月時点の内容**に基づきます。料金・モデル名・対応言語は改定されるため、本番投入前に必ず[公式ドキュメント](https://www.alibabacloud.com/help/en/model-studio/qwen-tts)で最新値を確認してください。コードは実運用で使える形に整えていますが、APIキーは環境変数前提です（ハードコード厳禁）。

---

## 0. 最初の地図：「Qwen-TTS」には2つの実体がある

ここを混同したまま設計を始めると、後で必ず詰まります。「Qwen-TTS」という言葉は、**性質の異なる2つの提供形態**を指します。

| | ① マネージドAPI（DashScope / Model Studio） | ② OSS版（GitHub・オープンウェイト） |
| --- | --- | --- |
| 実体 | `dashscope` SDK / HTTP エンドポイント | `Qwen3-TTS-12Hz-*`（Hugging Face の重み） |
| 代表モデル | `qwen3-tts-flash` / `qwen3-tts-instruct-flash` / `*-realtime` | `1.7B-Base` / `1.7B-CustomVoice` / `1.7B-VoiceDesign` |
| 実行場所 | Alibaba Cloud のサーバー（テキストを送信する） | 自前の GPU（テキストが外に出ない） |
| ライセンス | 商用利用は Model Studio の規約に従う | **Apache-2.0**（重みを所有・改造できる） |
| 課金 | 文字数ベースの従量課金 | 計算リソース（GPU時間）の固定費 |
| 強み | 運用ゼロ・即日導入・49+音色 | データ主権・3秒ボイスクローン・無制限 |

**「Qwen-TTS を使う」と言ったとき、①と②のどちらを指すのか**を最初に確定させてください。本記事は両方を扱い、選定基準（第7章）まで示します。多くの案件では「まず①のAPIで価値検証 → 独自の声やデータ主権が要件化したら②を検討」が正しい順序です（YAGNI）。

---

## 1. Qwen3-TTS-Flash とは何か（公式の数値）

現在の主力は **Qwen3-TTS-Flash** です。公式が公表している数値は次のとおりです。

- **49以上の音色（timbre）**：性別・年齢・地域性・キャラクター性を網羅。
- **10言語**：中国語・英語・ドイツ語・イタリア語・ポルトガル語・スペイン語・日本語・韓国語・フランス語・ロシア語。
- **9つの中国語方言**：Mandarin（普通話）・Hokkien（閩南語）・Wu（呉語/上海）・Cantonese（広東語）・Sichuanese（四川語）・Beijing（北京）・Nanjing（南京）・Tianjin（天津）・Shaanxi（陝西）。
- **5百万時間超の音声データ**で学習。
- **ベンチマーク**：seed-tts-eval テストセットで中国語・英語の安定性 SOTA（SeedTTS・MiniMax・GPT-4o-Audio-Preview を上回ると公称）。MiniMax の多言語テストセットでも MiniMax・ElevenLabs・GPT-4o-Audio-Preview より平均 WER（単語誤り率）が低いと公称。

参考までに、初代 Qwen-TTS は7音色（Cherry・Ethan・Chelsie・Serena・Dylan・Jada・Sunny）で公開され、SeedTTS-Eval で WER 1.209〜2.206、話者類似度（SIM）0.473〜0.804 を記録していました。Qwen3 世代はここから音色・言語・方言・安定性を大きく拡張した系譜だと捉えると、選定の見通しが立ちます。

### 1.1 モデル系統とリージョン（ここが落とし穴）

Model Studio のモデル名は**リージョンで異なります**。国際（シンガポール）と中国本土（北京）でラインナップが違う点に注意してください。

| モデル | 役割 | 国際（Singapore） | 中国本土（Beijing） |
| --- | --- | --- | --- |
| `qwen3-tts-flash` | 標準の高速合成（49+音色） | ✅ | ✅ |
| `qwen3-tts-instruct-flash` | 自然言語で話し方を指示制御 | ✅ | ✅ |
| `qwen3-tts-flash-realtime` | WebSocketで逐次合成 | ✅ | ✅ |
| `qwen3-tts-instruct-flash-realtime` | 指示制御つきリアルタイム | ✅ | ✅ |
| `qwen-tts` / `qwen-tts-latest` | 初代（旧称） | — | ✅ のみ |

- **`qwen3-tts-flash` は安定版エイリアス**で、執筆時点ではスナップショット `qwen3-tts-flash-2025-11-27` に対応します（`-2025-09-18` などの過去版も指定可）。本番では**スナップショット名を固定**すると、エイリアス更新による音色の挙動変化を避けられます（再現性・テスト容易性）。
- **エンドポイントのホストもリージョンで違います**。国際は `dashscope-intl.aliyuncs.com`、中国本土は `dashscope.aliyuncs.com`。**初代 `qwen-tts` は中国本土リージョンでのみ提供**されるため、国際リージョンしか使えない要件では `qwen3-tts-flash` を選びます。
- **データ所在（residency）の観点**でも、どちらのリージョンにテキストが送られるかは設計判断です。社外秘テキストの越境が問題になるなら、リージョン選定かOSS版（第6章）の検討が要ります。

---

## 2. 5分で動かす：DashScope API（Python）

まずは最短で音声を1本生成します。SDK は `dashscope`、呼び出しは `MultiModalConversation.call` です。

```bash
pip install -U dashscope
```

```python
import os
import dashscope

# 国際リージョン（シンガポール）を使う。中国本土なら .aliyuncs.com 側を指定。
dashscope.base_http_api_url = "https://dashscope-intl.aliyuncs.com/api/v1"

response = dashscope.MultiModalConversation.call(
    model="qwen3-tts-flash",
    api_key=os.getenv("DASHSCOPE_API_KEY"),  # キーは環境変数から（ハードコード禁止）
    text="本日はお集まりいただき、ありがとうございます。",
    voice="Cherry",            # 音色（声）。第4章の一覧から選ぶ
    language_type="Japanese",  # テキストの言語に合わせる（後述）
    stream=False,              # 一括生成（URLで受け取る）
)

# 非ストリーミングでは「音声ファイルのURL」が返る（生成から24時間で失効）
audio_url = response.output.audio.url
print(audio_url)
```

ここで**最大の落とし穴**は、`response.output.audio.url` が**生成から24時間で失効する一時URL**だという点です。本番では、受け取った直後に自前のストレージ（S3 / Vercel Blob 等）へ保存してから業務で使います。

```python
import requests

def fetch_and_store(audio_url: str, dest: str) -> None:
    """一時URLは24時間で消える。受領直後に自前ストレージへ退避するのが鉄則。"""
    res = requests.get(audio_url, timeout=30)
    res.raise_for_status()
    with open(dest, "wb") as f:
        f.write(res.content)  # 実運用では S3/Blob の put に置き換える
```

> **`language_type` のコツ**：公式は「テキストの言語に合わせることを推奨」しています。日本語テキストなら `"Japanese"`、英語なら `"English"`。同じ音色（例：Cherry）でも `language_type` を切り替えるだけで、**同一原稿を10言語にナレーションし分けられる**のが Qwen3-TTS の強みです（多言語eラーニング・吹き替えに直結。第10章）。

---

## 3. HTTP API：TypeScript / Next.js のサーバーから叩く

SDK が無い言語、あるいはこのサイトと同じ TypeScript / Next.js から使うなら、HTTP を直接叩きます。**ブラウザから叩かず、必ずサーバー側（Route Handler / Server Action）で**実行してキーを秘匿します。

エンドポイント（国際リージョン）：

```text
POST https://dashscope-intl.aliyuncs.com/api/v1/services/aigc/multimodal-generation/generation
```

Next.js の Route Handler 例。**入力は境界で Zod 検証**し、音色・言語は許可リストで縛ります（ユーザー由来の値を素通ししない）。

```ts
// app/api/tts/route.ts — サーバー側でのみ実行（APIキーはブラウザに出さない）
import { z } from "zod";

// 許可する音色・言語を型で固定（不正値をAPIに渡さない＝コスト事故と例外を防ぐ）
const VOICES = ["Cherry", "Ethan", "Serena", "Jennifer", "Ryan"] as const;
const LANGS = ["Japanese", "English", "Chinese", "Korean"] as const;

const Body = z.object({
  text: z.string().min(1).max(2000), // 上限は要件に合わせる（課金は文字数ベース）
  voice: z.enum(VOICES),
  language_type: z.enum(LANGS),
});

export async function POST(req: Request) {
  const parsed = Body.safeParse(await req.json());
  if (!parsed.success) {
    return Response.json({ error: parsed.error.flatten() }, { status: 400 });
  }

  const upstream = await fetch(
    "https://dashscope-intl.aliyuncs.com/api/v1/services/aigc/multimodal-generation/generation",
    {
      method: "POST",
      headers: {
        Authorization: `Bearer ${process.env.DASHSCOPE_API_KEY!}`,
        "Content-Type": "application/json",
      },
      body: JSON.stringify({
        model: "qwen3-tts-flash",
        input: parsed.data, // { text, voice, language_type }
      }),
    },
  );

  if (!upstream.ok) {
    // 上流のエラーはそのまま返さず、こちらで分類してログ（内部詳細は漏らさない）
    return Response.json({ error: "tts_upstream_failed" }, { status: 502 });
  }

  const json = (await upstream.json()) as { output: { audio: { url: string } } };
  return Response.json({ url: json.output.audio.url }); // 受領後は自前ストレージへ
}
```

リクエストボディの形は `{ "model": ..., "input": { "text", "voice", "language_type" } }`、レスポンスは `output.audio.url` です。ストリーミングを使う場合のみ、ヘッダに `X-DashScope-SSE: enable` を付けます（次章）。

---

## 4. 声を選ぶ・操る：音色・言語・方言・指示制御

### 4.1 音色（voice）の選び方

49以上の音色から、**用途で代表音色を決め打ち**するのが実務的です。代表例（公式の説明を要約）：

| 用途 | 代表音色 | 公式の説明（要約） |
| --- | --- | --- |
| 汎用ナレーション（女性・明るい） | `Cherry` | 太陽のように明るく親しみやすい若い女性 |
| 汎用ナレーション（男性・標準） | `Ethan` | やや北方訛りの標準中国語。元気で温かい |
| 英語シネマ品質（女性） | `Jennifer` | 高級感のある映画品質の米国英語女性 |
| ドラマ・予告（男性） | `Ryan` | リズム感があり、ドラマチックな抑揚 |
| 落ち着いた学術・解説 | `Elias` | ストーリーテリング技法を用いた学術的厳密さ |

すべての標準音色が**10言語に対応**します。つまり「日本語ナレーションは Cherry、同じ原稿の英語版も Cherry」と、**音色を固定したまま言語だけ切り替える**運用ができます。声のブランド一貫性を保ったまま多言語展開できるのは、ローカライズ案件で効きます。

### 4.2 中国語の方言を出す（方言は専用音色）

方言は**音色側に紐づいています**。狙った地域性を出すには、対応する音色を選びます。

| 方言 | 代表音色 | 方言 | 代表音色 |
| --- | --- | --- | --- |
| 北京 | `Dylan` | 上海（呉語） | `Jada` |
| 四川 | `Sunny` / `Eric` | 広東語 | `Rocky` / `Kiki` |
| 天津 | `Peter` | 南京 | `Li` |
| 陝西 | `Marcus` | 閩南語（台湾） | `Roy` |

中華圏向けのキャラクターボイス・ローカルCM・エンタメ用途では、この方言音色が差別化になります。

### 4.3 話し方を「言葉で」制御する：instruct モデル

速度・抑揚・感情を細かいパラメータで触るのではなく、**自然言語の指示文（instructions）で制御**できるのが `qwen3-tts-instruct-flash` です。

```python
response = dashscope.MultiModalConversation.call(
    model="qwen3-tts-instruct-flash",
    api_key=os.getenv("DASHSCOPE_API_KEY"),
    text="期間限定セール、本日スタートです！",
    voice="Cherry",
    language_type="Japanese",
    # 話し方を自然言語で指定（速度・抑揚・感情）
    instructions="やや速めのテンポで、明るく弾むような上がり調子。ファッション商品の紹介向け。",
    optimize_instructions=True,  # 指示文をモデル側で最適化処理する
    stream=False,
)
```

公式によれば `instructions` は**最大1,600トークン**、対応言語は**中国語・英語**です（指示文自体の言語。日本語の本文を読み上げつつ指示は英語で書く、といった運用も可）。想定用途として、オーディオブック・ラジオドラマ・広告・ゲームキャラのボイスオーバー・音声アシスタント・ドキュメンタリーのナレーションが挙げられています。

> **設計判断**：話し方を**コードのパラメータ**ではなく**プロンプト（自然言語）**で持つと、ディレクター（非エンジニア）が演出を直接調整できます。これは「演出＝データ」として外部化する設計で、修正のたびにデプロイが要らなくなる（ETC：変更しやすさ）。一方、再現性が要る用途ではスナップショットモデル＋固定 instructions を版管理してください。

---

## 5. リアルタイム音声合成：話しながら出す（WebSocket）

音声アシスタントや対話UIのように「全文の生成完了を待たせたくない」場面では、`*-realtime` モデルを **WebSocket** で使い、生成しながら逐次再生します。

- エンドポイント（国際）：`wss://dashscope-intl.aliyuncs.com/api-ws/v1/realtime`
- モデル：`qwen3-tts-flash-realtime` / `qwen3-tts-instruct-flash-realtime`

Python SDK は、音声チャンクをコールバックで受け取る形です。

```python
from dashscope.audio.qwen_tts_realtime import QwenTtsRealtime, AudioFormat

rt = QwenTtsRealtime(
    model="qwen3-tts-flash-realtime",
    callback=callback,  # response.audio.delta（base64音声）を逐次受け取る
    url="wss://dashscope-intl.aliyuncs.com/api-ws/v1/realtime",
)
rt.connect()
rt.update_session(
    voice="Cherry",
    response_format=AudioFormat.PCM_24000HZ_MONO_16BIT,  # 24kHz/モノ/16bit
    mode="server_commit",  # サーバーがテキストを賢く区切る（後述）
)
# 以降、テキストを流し込むと response.audio.delta イベントで音声断片が届く
```

### 5.1 2つのモード：`server_commit` と `commit`

リアルタイムには**テキストの区切り方が違う2モード**があります。要件で選びます。

- **`server_commit`（サーバー主導）**：サーバー側がテキストをインテリジェントに分割。**長い文章を連続合成**するのに向く（記事の読み上げ・ナレーション）。
- **`commit`（クライアント主導）**：クライアントがテキストバッファを手動でコミットして合成をトリガー。**対話シナリオで精密に制御**したいとき（チャットの一発話ごとに区切る）。

### 5.2 レイテンシの考え方

SDK は `first_audio_delay`（リクエスト送信〜最初の音声断片受信までの時間）を計測できます。公式は「**最初のテキスト送信は WebSocket 接続確立を含むため、初回の first-packet レイテンシには接続セットアップ時間が含まれる**」と明記しています。つまり**接続は使い回し、ウォームに保つ**のが定石です。なお、OSS版（第6章）はエンドツーエンド合成レイテンシ **97ms** を公称しており、対話の体感品質を決めるのはこの初動です。

> 録音済みテキストの「バッチ生成」（第2章）と、対話の「ライブ合成」（本章）は別物です。要件を取り違えると、無駄に WebSocket を張ったり、逆に待たせるUXになったりします。

WebSocketプロトコルの詳細・ブラウザでのPCMギャップレス再生・バージイン（割り込み）・LLM→TTSのパイプライン化は、[Qwen-TTS リアルタイム音声エージェント実装ガイド](/blog/qwen-tts-realtime-voice-agent-websocket-streaming-guide)で深掘りしています。

---

## 6. 独自の声を作る：OSS版（ボイスクローン & ボイスデザイン）

「自分（or 自社タレント）の声で読ませたい」「データを外に出せない」「無制限に固定費で回したい」——その要件では、Apache-2.0 で公開された **OSS版（QwenLM/Qwen3-TTS）** が選択肢になります。

公開されている主なチェックポイント：

| モデル | 特徴 |
| --- | --- |
| `Qwen3-TTS-12Hz-1.7B-Base` | **3秒の参照音声**からボイスクローン |
| `Qwen3-TTS-12Hz-1.7B-CustomVoice` | 9つのプリセット話者（Vivian / Serena / Uncle_Fu / Dylan / Eric / Ryan / Aiden / Ono_Anna / Sohee） |
| `Qwen3-TTS-12Hz-1.7B-VoiceDesign` | **自然言語で声そのものを設計** |
| `*-0.6B-Base` / `*-0.6B-CustomVoice` | 軽量版（省メモリ） |

```bash
pip install -U qwen-tts   # FlashAttention 2 併用を推奨。GPU(bf16/fp16)前提
```

**ボイスクローン**（3秒の参照音声＋その書き起こしから、同じ声で任意テキストを読ませる）：

```python
import torch
from qwen_tts import Qwen3TTSModel

model = Qwen3TTSModel.from_pretrained(
    "Qwen/Qwen3-TTS-12Hz-1.7B-Base",
    device_map="cuda:0",
    dtype=torch.bfloat16,
)

wavs, sr = model.generate_voice_clone(
    text="この声で、好きな原稿を読み上げます。",
    language="Japanese",
    ref_audio="ref.wav",          # 3秒程度の参照音声
    ref_text="参照音声の書き起こし", # 参照音声に対応するテキスト
)
```

**ボイスデザイン**（声の質感を自然言語で“発注”する）：

```python
wavs, sr = model.generate_voice_design(
    text="ようこそ、いらっしゃいませ。",
    language="Japanese",
    instruct="落ち着いた中低音の男性。ホテルのコンシェルジュのように丁寧で温かい。",
)
```

OSS版のベンチマーク（公式 README）は、`1.7B-Base` が SEED test-en で **1.24 WER**、中国語の話者類似度 **0.811**、long-zh で **2.356 WER**。10言語に対応します。

> **倫理と法務は“機能”ではなく“前提”**：ボイスクローンは**本人の同意がない声の複製を可能にしてしまう**技術です。なりすまし・詐欺・名誉毀損のリスクがあるため、本番では「①声の提供者の書面同意」「②用途の明示と限定」「③生成物がAI音声であることの開示」を**設計と契約の両方に組み込む**のが必須条件です。これは Qwen-TTS に限らず、すべてのボイスAIの本番化で外せない一線です。

OSS版のセルフホスト実装（FastAPI推論サーバー）と、同意台帳・用途限定・失効・開示・来歴（プロベナンス）・監査ログまでのガバナンス設計は、[Qwen-TTS ボイスクローン本番実装ガイド](/blog/qwen-tts-voice-cloning-self-hosting-consent-governance-guide)で詳述しています。

---

## 7. 選定フレームワーク：API vs OSS をどう決めるか

「どっちが正解」ではなく、**4つの軸**で要件に合う方を選びます。

| 判断軸 | OSS版（セルフホスト）が有利 | マネージドAPI が有利 |
| --- | --- | --- |
| **プライバシー / データ主権** | テキスト・声を**外に出せない**（医療・行政・社外秘） | 外部送信が許容される |
| **コスト構造** | 大量・常時稼働で**固定費**にしたい（GPU償却） | 少量・スパイク的で**変動費**が合理的 |
| **カスタム性** | **独自の声**（クローン/デザイン）が要件 | 49+の既製音色で足りる |
| **運用体制** | GPU・モデル更新・スケールを**運用できる** | 運用したくない／即日で精度が欲しい |

### コストの直感（要・公式確認）

Model Studio の課金は**文字数ベース**です。公開情報による概算では `qwen3-tts-flash` がおおむね **1,000文字あたり約 $0.013（≒ 100万文字で約 $13）**、realtime は別料金、新規ユーザーには無料枠（おおむね100万文字程度）があるとされています。**ただしこれらの金額は変動し、リージョンでも異なるため、必ず[公式の料金ページ](https://www.alibabacloud.com/help/en/model-studio/model-pricing)で最新値を確認してください。**

判断の型はこうです：**月に数万〜数十万文字程度ならAPIが圧倒的に安く・運用ゼロ**。逆に、**毎日大量のナレーションを常時生成する／独自の声が要る／テキストを外に出せない**なら、OSS版をGPUで回す固定費の方が単価と要件適合で勝つ損益分岐点が現れます。「まずAPIで価値検証 → 量とカスタム要件が固まってからOSS移行を検討」が多くの案件で正しい順序です（KISS / コスト効率）。

他社TTS（ElevenLabs・OpenAI・Google・Azure）とのコスト・多言語・セルフホスト可否・声の複製・遅延の横並び比較は、[TTS徹底比較ガイド](/blog/qwen-tts-vs-elevenlabs-openai-google-azure-tts-comparison)で要件から逆算する意思決定フレームワークとして整理しました。

---

## 8. 本番運用設計：冪等・回復性・可観測性

音声生成は「外部API・課金あり・大量バッチ」のジョブです。**素朴に呼ぶと、途中失敗で全部やり直し＆二重課金**になります。文字起こし側（[Whisperガイド](/blog/openai-whisper-production-guide-selfhost-vs-api)）と対になる、生成側の最小装備を示します。

### 8.1 内容ハッシュによる冪等キャッシュ

同じ原稿・同じ音色・同じ言語・同じモデルの組み合わせは、**何度生成しても同じ音声**です。ならば**内容ハッシュをキー**にキャッシュすれば、再実行で生成済みをスキップでき、リトライが冪等になり、課金も減ります。

```python
import hashlib
from pathlib import Path

def tts_key(text: str, voice: str, language: str, model: str) -> str:
    """入力で決まる決定的キー。同入力 → 同キー → 生成をスキップ（冪等・節約）。"""
    h = hashlib.sha256()
    h.update(f"{model}\x00{voice}\x00{language}\x00{text}".encode("utf-8"))
    return h.hexdigest()

def synthesize_idempotent(text: str, voice: str, language: str, model: str, cache_dir: Path) -> Path:
    key = tts_key(text, voice, language, model)
    out = cache_dir / f"{key}.wav"
    if out.exists():
        return out  # 再実行はAPIを叩かない（冪等・コスト削減）

    resp = dashscope.MultiModalConversation.call(
        model=model, api_key=os.getenv("DASHSCOPE_API_KEY"),
        text=text, voice=voice, language_type=language, stream=False,
    )
    fetch_and_store(resp.output.audio.url, str(out))  # 24時間で消える前に退避
    return out
```

### 8.2 指数バックオフ付きリトライ（対象を限定する）

外部APIはレート制限・一時障害で必ず失敗します。**冪等な操作にだけ**、かつ**4xx（入力不正）はリトライしない**でリトライを掛けます。

```python
import time

def with_retry(fn, *, max_attempts: int = 4, base: float = 1.0):
    """指数バックオフ。一時障害だけ再試行し、入力不正は即失敗させる（fail fast）。"""
    for attempt in range(1, max_attempts + 1):
        try:
            return fn()
        except Exception as e:  # 実運用ではdashscopeの例外型で分類する
            transient = _is_transient(e)  # 429/5xx/接続断 → True
            if not transient or attempt == max_attempts:
                raise
            time.sleep(base * (2 ** (attempt - 1)))  # 1s, 2s, 4s, ...
```

### 8.3 可観測性：何を必ず記録するか

音声生成ジョブでは、**本文（PIIになり得る原稿）ではなくメタデータ**を記録します。

- ジョブID / 入力ハッシュ / モデル（スナップショット名）/ 音色 / 言語
- 文字数（＝課金量の主因）と推定コスト
- 処理時間・realtime なら `first_audio_delay`
- 失敗種別（レート制限 / タイムアウト / 入力不正）とリトライ回数

これらを構造化ログ（[OpenTelemetry](/blog/opentelemetry-observability-production-tracing-metrics-logs) 等）で出すと、「どの生成で詰まったか」「コストが妥当か」が一目で追えます。**読み上げ原稿に個人情報が含まれ得る**案件では、本文をログに残さないことが内部統制の絶対条件です。

---

## 9. UX / アクセシビリティ：生成音声をプロダクトに載せる

音声合成は**アクセシビリティの強力な味方**になり得ますが、実装を誤ると逆にWCAG違反やUX劣化を生みます。プロダクトに載せる際の必須項目：

- **自動再生しない**：音声を勝手に鳴らすとスクリーンリーダー利用者の妨げになり、WCAG 1.4.2（音声の制御）にも抵触します。**再生は必ずユーザー操作起点**にし、再生/一時停止/音量のコントロールを用意する。
- **テキスト代替を必ず併記**：生成音声は元テキストがあるはずです。**読み上げと同じテキストを画面にも提示**すれば、聴覚に頼れない利用者もアクセスできます（音声“だけ”にしない）。
- **AI生成の明示**：合成音声であることを明示する（信頼性・倫理・将来の規制対応）。
- **プレイヤーのa11y**：操作はキーボードで完結、`aria-label` を付与、`prefers-reduced-motion` を尊重した波形アニメーション。
- **体感速度はキャッシュで**：第8章の冪等キャッシュは、同じフレーズの再生成を消すことで**初回以降の体感を即時化**します（コストとUXの一石二鳥）。

フロントの実装規範は[Next.js × React のアクセシビリティ（WCAG 2.2）ガイド](/blog/react-nextjs-web-accessibility-wcag22-guide)に揃えてください。アクセシブルな「記事読み上げ」プレイヤーの完全な実装（サーバー側生成＋WCAG準拠のReactプレイヤー）は、[Next.js × Qwen-TTS 読み上げプレイヤー実装ガイド](/blog/nextjs-qwen-tts-accessible-audio-player-text-to-speech)にまとめています。

---

## 10. ユースケース別レシピ（応用）

公式の機能を、実際の案件にどう落とすか。代表的な4パターンです。

### 10.1 多言語eラーニング / ナレーションの一括生成

**同一原稿を音色固定で10言語化**します。`language_type` を回すだけで、声のブランドを保ったまま多言語展開できます。

```python
SCRIPT = {"Japanese": "ようこそ。", "English": "Welcome.", "Korean": "환영합니다."}

for lang, text in SCRIPT.items():
    path = synthesize_idempotent(text, voice="Cherry", language=lang,
                                 model="qwen3-tts-flash", cache_dir=Path("./out"))
    print(lang, path)  # 同一音色・多言語のナレーションが冪等に揃う
```

### 10.2 動画ローカライズ / 多言語吹き替え

これは私が[AI動画ローカライズ・リップシンク基盤](/case-studies/ai-video-localization-lipsync)で実際に組んだパイプラインの“声”の部分です。**音声分離 →（Whisperで）文字起こし → 翻訳 →（Qwen-TTSで）多言語吹き替え → 口元同期**という流れの「多言語吹き替え」を TTS が担います。Qwen3-TTS の10言語対応は、この吹き替え工程をモデル1本で賄えるのが利点です。リップシンク側の設計は[LatentSyncガイド](/blog/latentsync-lip-sync-diffusion-model-production-guide)を参照。

### 10.3 音声アシスタント / IVR（リアルタイム）

対話の応答生成（LLM）→ その出力を `*-realtime` に流し、`commit` モードで一発話ごとに合成して逐次再生します。LLM 側のストリーミング設計は[Vercel AI SDK 本番ガイド](/blog/vercel-ai-sdk-production-llm-apps-streaming-tools-rag)と組み合わせると、**「考えながら話す」体感**が作れます。RAG接客の文脈は[生成AI音声チャットボット](/case-studies/ai-voice-chatbot)が近い事例です。

### 10.4 キャラクターボイス / 広告

`qwen3-tts-instruct-flash` で演出を自然言語指定（10.3の instruct 例）、または OSS の VoiceDesign で**世界に1つの声**を設計します。方言音色（4.2）と組み合わせると、地域性のあるキャラクターも作れます。

---

## 11. セキュリティ・コンプライアンス・倫理

- **APIキーはサーバー側に**：ブラウザから DashScope を直接叩かない（キー漏洩）。Next.js なら Route Handler / Server Action 経由、キーは環境変数（第3章）。
- **入力は境界で検証**：`text` の長さ上限（課金とDoS対策）、`voice` / `language_type` は許可リスト（enum）で縛る。ユーザー由来の値を素通ししない。
- **データ所在**：どのリージョンにテキストが送られるかを把握し、越境が問題なら OSS版かリージョン選定で対応（1.1）。
- **生成URLの寿命**：`output.audio.url` は24時間で失効。受領直後に自前ストレージへ退避し、署名付きURLで配信する（第2章）。
- **ボイスクローンの同意**：本人同意・用途限定・AI生成の開示を契約と実装の両方に（第6章）。これは妥協できない一線です。

---

## 12. まとめ：選定チートシート

最後に、迷ったときの早見表です。

- **とりあえず日本語/多言語ナレーションを即日で**：`qwen3-tts-flash`（API）。`language_type` を本文言語に合わせ、音色は用途で固定。
- **演出（速度・抑揚・感情）を細かく出したい**：`qwen3-tts-instruct-flash` + `instructions`（自然言語）。
- **対話・音声アシスタントで待たせたくない**：`qwen3-tts-flash-realtime`（WebSocket、`commit` モード）。接続はウォームに保つ。
- **独自の声 / データを外に出せない / 無制限に固定費で**：OSS版（Apache-2.0）。3秒クローン or VoiceDesign。GPU運用と倫理ゲート前提。
- **中国語の方言が要る**：方言は音色側。北京=Dylan・上海=Jada・四川=Sunny/Eric・広東=Rocky/Kiki ほか。
- **本番化の共通装備**：内容ハッシュの冪等キャッシュ・対象限定の指数バックオフ・24時間URLの即時退避・PIIを出さない可観測性・a11y（自動再生しない＋テキスト代替）。

音声合成は「一行の要件」に見えて、**音色・言語・コスト・リアルタイム性・プライバシー・倫理のトレードオフを設計する仕事**です。私は多言語吹き替えパイプラインで、TTS を「ローカライズの声」として本番運用し、冪等・回復性・可観測性を担保したジョブとして組み上げました（[本案件はクラウドワークス契約ランキング1位](/case-studies/ai-video-localization-lipsync)）。

**「自社のテキスト/動画をどう多言語の音声にし、どう業務やプロダクトに組み込むか」——その設計から実装・運用・倫理ガードまで、一気通貫で伴走できます。** 要件の整理段階からでも、お気軽にご相談ください。

---

### 参考（公式ドキュメント）

- [Qwen-TTS 音声合成（Alibaba Cloud Model Studio）](https://www.alibabacloud.com/help/en/model-studio/qwen-tts) — モデル一覧・音色・`MultiModalConversation` / HTTP API・パラメータ
- [Qwen-TTS リアルタイム音声合成（Model Studio）](https://www.alibabacloud.com/help/en/model-studio/qwen-tts-realtime) — WebSocket・`server_commit` / `commit`・`first_audio_delay`
- [QwenLM/Qwen3-TTS（GitHub・Apache-2.0）](https://github.com/QwenLM/Qwen3-TTS) — OSS版の重み・ボイスクローン/デザイン・ベンチ（97ms / WER / 話者類似度）
- [Qwen3-TTS アップデート（Qwen公式ブログ）](https://qwen.ai/blog?id=qwen3-tts-1128) — 49音色・10言語・9方言・seed-tts-eval の公称値
- [Time to Speak Some Dialects, Qwen-TTS!（初代の解説）](https://qwenlm.github.io/blog/qwen-tts/) — 初代の7音色・方言・SeedTTS-Eval の数値
- [Model Studio 料金](https://www.alibabacloud.com/help/en/model-studio/model-pricing) — 文字数課金・無料枠の最新値（本番投入前に要確認）
