# AI ディレクター
> 音声合成の前に AI ディレクターが文章を読み解いて演出し、人が語っているような声に仕上げます。

音声合成の前に AI ディレクターが文章を読み解いて演出し、人が語っているような声に仕上げます。

AI ディレクターは原文とユーザーの要望を読み取り、対象のボイスが実際に表現できる書き方で、感情、演技、
効果音、間、読み方を加えた台本を返します。返された台本はそのまま音声合成に使えます。加えるのは演出だけで、
原文は一文字も変更しません。ボイスを指定しない場合は、合うボイスとモデルを推薦し、その理由も示します。

## Agent の主経路

Universal Skill から MCP ツール `beatra.speech.prepare_script` を使います。このツールは課金対象で、
非同期タスクを返します。

1. 原文を `input` に、仕上がりへの要望があれば `instructions` に渡します。ユーザーがボイスやモデルを
   指定していれば一緒に渡し、なければ省略して AI ディレクターに推薦させます。
2. 不透明な `client_request_id` を 1 つ作って一度だけ送信し、`beatra.tasks.get` で終端状態まで
   ポーリングします。
3. 返された各台本の `synthesis` をそのまま `beatra.speech.synthesize` の引数にし、自分の
   `client_request_id` を加えて呼び出します。`synthesis` が `null` の台本はそのままでは合成できません。
   理由は警告で確認できます。
4. ユーザーが調整を求めたら（例：「2 段落目をもっと緊迫させて」）、その文を `instructions` に
   加えて（前の要望は残したまま）もう一度呼び出し、合成し直します。

## AI ガイド

AI integration guide: https://docs.beatra.ai/llms/audio-ai-director.md

## ツール引数

```json
{
  "input": "夜は静まり返っていた。彼はドアを押し開けた。「誰かいますか？」返事はなかった。",
  "instructions": "少しサスペンス風に。問いかけはささやき声で。",
  "client_request_id": "script-opaque-1"
}
```

- `input`：原文。対応するどの言語でもよく、最大 50,000 文字です。原文にすでに書かれたマークアップ
  （`{pause 0.5}` など）はそのまま残し、AI ディレクターはその上に加えるだけです。指定したボイスや
  モデルが 1 回で読める長さ（ElevenLabs は 10,000 文字）を超える場合は、音声合成と同じ 422 エラーを
  返し、料金は発生しません。
- `instructions`：任意。自然な言葉で書いた要望で、言語は問わず最大 2,000 文字です。指定した場合は要望を
  優先し、要望が触れていない部分は文章の意味に沿って自然に演出します。省略すると、全体を文章の意味に沿って
  自然に演出します。
- `voice`、`model`：任意。意味と既定値は `beatra.speech.synthesize` と同じです。どちらも省略すると
  AI ディレクターが推薦し、ボイスだけ指定するとそのボイスのモデルから選び、モデルだけ指定するとその
  モデルで使えるボイスから推薦します。
- `language`：任意。通常は省略します（既定の `auto` で自動判定）。ユーザーが言語を指定した場合や、
  文章からは分からない方言（広東語 `yue` など）のときだけ渡します。
- `context`：任意の上級者向け設定です。長い文章を分けて演出するとき、その部分の前後の文章を
  `{"before": "…", "after": "…"}` の形で渡します（それぞれ最大 2,000 文字）。口調を理解するためだけに使い、
  マークアップの追加、読み上げ、返却はしません。対象モデルが対応していれば、`before` の末尾を前の文として
  `synthesis.previous_text` に入れ、前後の口調をつなげます。

## 結果

タスクが成功すると、`output` に台本のリストが入ります。

```json
{
  "type": "speech_script",
  "model": "ai-director",
  "directed": true,
  "scripts": [
    {
      "synthesis": {
        "input": "{emotion calm}夜は静まり返っていた。彼はドアを押し開けた。{emotion fearful}{whisper}「誰かいますか？」返事はなかった。",
        "voice": "voice_01JX...",
        "model": "elevenlabs-v4-turbo",
        "language": "ja"
      },
      "summary": "緊張感のある幕開け。問いかけはささやき声で",
      "voice_selection": {
        "source": "recommended",
        "voice": "voice_01JX...",
        "model": "elevenlabs-v4-turbo",
        "name": "Hiroki",
        "preview_url": "https://cdn.beatra.ai/voices/previews/voice_01JX....mp3",
        "reason": "若い男性の声。オーディオブック向けで、一文ごとに演じ分けられます。"
      },
      "alternatives": [
        {"voice": "voice_01JY...", "model": "elevenlabs-v4-turbo", "name": "Rin",
         "preview_url": "https://cdn.beatra.ai/voices/previews/voice_01JY....mp3", "reason": "若い女性の声。オーディオブック向けです。"}
      ],
      "estimate": {"weighted_characters": 72, "price_credits": "18"},
      "warnings": []
    }
  ],
  "warnings": []
}
```

- `scripts`：台本 1 つにつき合成 1 回です。現在は常に 1 つですが、今後は複数になることがあります
  （話者ごとに分ける場合など）。1 つずつ処理してください。
- `synthesis`：この台本を合成するための引数で、そのまま `beatra.speech.synthesize` に渡します
  （`client_request_id` は自分で加えます）。`input` はマークアップ入りの台本で、ほかに `voice`、`model`、
  `language` と、対象のボイスが実際に従う全体設定 `emotion`、`direction`、`previous_text` が入ります
  （従わない設定は入りません）。そのままでは合成できない場合は `null` です。使えるボイスがない、
  ボイスがこの台本を読めない、または今回は事前チェックを実行できなかった（後で再試行してください）場合で、
  理由は警告で確認できます。
- `summary`：演出を 1 行で説明したもので、パラメーター名やマークアップを含まず、そのままユーザーに
  伝えられます。`instructions` の言語、要望がなければ原文の言語で書かれます。
- `voice_selection`：この台本のボイスで、`voice`、`model`、ボイスの名前 `name`、試聴リンク `preview_url`
  が入ります。`source` が `recommended` なら AI ディレクターの推薦で、`reason` に理由が入ります。`given`
  なら指定されたボイスで、`reason` は `null` です。ボイスがない場合はフィールド全体が `null` です。
- `alternatives`：推薦した場合は最大 3 個の候補が付き、項目は `voice_selection` と同じです（`source` は
  ありません）。ボイスを指定した場合は空のリストです。
- `estimate`：この台本を合成したときの正確な価格で、無料の事前チェックと同じ値です。`synthesis` が
  `null` のときは `estimate` も `null` です。
- `directed`：`false` の場合、今回は AI ディレクターが演出を完了できませんでした。台本は原文のままで、
  演出の分は課金されません。`synthesis` があれば、そのまま合成できます。
- 推薦できるボイスがない場合（警告 `no_voice_available`）、`synthesis`、`voice_selection`、`estimate` は
  すべて `null` です。`beatra.voices.list` でボイスを選び、その `voice` を付けてもう一度呼び出してください。

候補のボイスに替えたいときは、2 日以内に `voice`（必要なら `model` も）だけを変え、ほかの引数は
同じまま、新しい `client_request_id` でもう一度呼び出します。AI ディレクターは前回の読み解きを
再利用し、同じ読み解きに二重に課金することはありません。新しいボイスに合わせて書き直すだけです。この呼び出しにも
残高の見積もりと同時 2 件までの上限が適用されますが、再利用した読み解きは課金されません。

## モデルごとの演出

AI ディレクターは、対象モデルが表現できる書き方だけを使います。

- ElevenLabs：一文ごとの感情の切り替え、ささやきや叫びなどの演技、笑い声やため息などの効果音。
- MiniMax：台本全体で 1 つの感情。間、読み方の指定、効果音に対応します。
- Doubao：台本全体で 1 つの感情。間と読み方の指定に対応します。感情、演技の指示、前の文に従うかは
  ボイスによって異なります。

ユーザーが求めた効果を選んだボイスで表現できない場合、AI ディレクターはそれ以外を演出し、表現できな
かったことと代わりに使ったものを警告で伝えます。一文ごとの演技が必要な場合は、一文ごとに演じ分け
られるボイスを選んでください。

## 警告

最上位の `warnings` は AI ディレクターからユーザーへの説明で、ユーザーに伝えてください。各台本の
`warnings` はその台本に対する無料の事前チェックの警告で、通常は空です。

| `code` | 意味 |
|---|---|
| `direction_unavailable` | AI ディレクターが文章の全部または一部を演出できませんでした。その文は原文のまま返され、その部分は課金されません。`field` が `voice` の場合は、指定したボイスがこの台本を読めないこと（文章の言語がボイスと合わないなど）を表します。別のボイスを選んでください。 |
| `effect_unavailable` | 選んだボイスでは、ユーザーが求めた効果を表現できないか、文章が 1 回の長さの上限に近いため効果を残せませんでした。`effect` に該当の効果、`substitute` に代わりに使ったもの（ない場合は `null`）を示します。`effect` が `voice_gender` の場合は、指定された性別のボイスがこの言語にまだないため、最も適した別のボイスを推薦したことを表します。 |
| `instruction_unsupported` | 指示に、まだ対応していない操作（翻訳、原文の書き換え、複数の話者の割り当てなど）が含まれています。その部分は無視されます。 |
| `no_voice_available` | 推薦できるボイスがありません。この言語を読めるボイスがない、指定したモデルにこの言語を読めるボイスがない、文章がすべてのボイスの上限を超えている、または読めるボイスが一時的に使えない、のいずれかです。台本は通常どおり返されます。 |

## 課金とタスクの状態

AI ディレクターは推論してから演出を書き込み、完了後に実際の入力・出力・推論トークン数で課金されます。
価格と 1,000 文字あたりの目安は、機能カタログ `GET /v1/catalog/capabilities/speech_script` に載っています。
開始するには、送信する文章の見積もり額（1,000 文字未満は 1,000 文字として数えます）を残高でまかなえる必要が
あります。同じアカウントで進行中の別の AI ディレクタータスクがあれば、その見積もり額も合わせて必要です。
1,000 文字あたり 146 クレジットなら、10,000 文字の文章には 1,460 クレジットが必要です。足りない場合は
402 `insufficient_balance`（`details.reason` は `director_estimate`）が返り、タスクは作成されません。
課金額は実際の使用量に基づき、通常は見積もりを下回ります。`directed` が `false` の場合と、2 日以内の
同じ読み解きを再利用した場合、演出の分は課金されません。`directed` が `true` の場合は実際のトークン数で
課金されます。AI ディレクターが原文に演出を加える必要がないと判断し、原文のまま返した場合も同様です。
合成は音声合成として別に課金され、価格は各台本の `estimate` で確認できます。

1 アカウントあたり同時に進行できる AI ディレクタータスクは 2 件までで、プラットフォーム全体で同時に進行できる
数にも上限があります。3 件目を送信すると 429 `rate_limited`（`details.reason` は `director_inflight_limit`）が
返り（MCP 経由ではプランの同時実行数の上限が先に適用されることがあります）、AI ディレクターが混み合っている
ときは 429（`details.reason` は `director_busy`）が返ります。どちらの場合もタスクは開始されず、課金もありません。
`director_inflight_limit` の場合は、このアカウントの AI ディレクタータスクが 1 件終わってから送信し直して
ください。`director_busy` の場合は、`details.retry_after_seconds` の秒数が経ってから再試行してください（REST
では `Retry-After` ヘッダーに同じ秒数が入ります）。AI ディレクターが一時的に使えないときは 503
`model_unavailable` が返ります。モデルを選ぶ機能ではないため、時間をおいて再試行してください。

`queued` と `running` は失敗ではありません。同じ `task_id` をポーリングし続けてください。作成時の
レスポンスを受け取れなかった場合は、同じ `client_request_id` でまったく同じ引数を再送し、入力が
変わったら新しい ID を使います。タスク一覧（`GET /v1/tasks`）には結果の要約だけが表示されます。
`truncated: true` が付き、`synthesis` は含まれません。合成に使える台本は、タスク ID で取得してください
（`beatra.tasks.get` または `GET /v1/tasks/{task_id}`）。AI ディレクターの結果は台本であって作品ではないため、
作品一覧には表示されません。コンソールでは、アクティビティと請求で「AI ディレクター」と表示されます。

## REST で呼び出す

プロトコルを直接扱う連携では、`POST /v1/audio/speech/scripts` と共通のタスクエンドポイントを使い、
各台本の `synthesis` をそのまま `POST /v1/audio/speech` のリクエスト本文として送ります。生成された
[API 定義](https://docs.beatra.ai/ja/docs/api-reference/operations/audio/prepare_speech_script_v1_audio_speech_scripts_post)
を参照してください。Skill + MCP での連携をおすすめします。REST はエージェント以外の独自アプリ向けです。
