1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Gemini 3.5 Transcribe入門、話者分離とカスタム語彙は同時に使えない

1
Posted at

はじめに

会議の録音を文字起こしするとき、欲しくなるのは「誰が話したか」「何秒目の発言か」「社内用語を正しく書けているか」の 3 点です。Google が 2026 年 8 月 26 日に発表した音声認識モデル Gemini 3.5 Transcribe は、この 3 つを 1 つのモデルで扱えます。ところが、3 つを 1 回のリクエストで同時に指定すると API はリクエストを受け付けません。

この記事では、Gemini API で Gemini 3.5 Transcribe と Gemini 3.5 Transcribe Live を使い始める手順を、設定の組み合わせ制約と合わせて解説します。

対象読者: Gemini API で音声の文字起こし(議事録・字幕・コールログ)を組み込みたい Python / Node.js 開発者です。

この記事で解決できること

  • 2 つのモデル(ファイル向けと WebSocket ストリーミング向け)の選び分けと料金感をつかむ
  • 話者分離・単語タイムスタンプ・カスタム語彙・Smart モードの指定方法を知る
  • 同時に指定できない設定の組み合わせを事前に把握し、リクエスト設計で詰まらない
  • SDK に残っている非推奨フィールドを避けて書く

前提条件

  • Gemini API の API キー(Google AI Studio で発行)
  • Python 3.9 以降、または Node.js 環境
  • 文字起こししたい音声ファイル(WAV / MP3 / FLAC / M4A / OGG / WebM など)

筆者が実機で確認した範囲

筆者は本記事の執筆にあたり、クラウド環境で Python SDK google-genai 2.25.0 と Node.js SDK @google/genai 2.24.0 を実際にインストールし、クライアントの公開属性と、文字起こし関連の型定義ファイルをソースコードから直接確認しました。一方、API キーを用意できなかったため、実際の音声ファイルを送って文字起こし結果を受け取る呼び出しと、Live 版の WebSocket 接続は実行していません。本文中のリクエスト例は公式ドキュメントのサンプルに基づくもので、出力例は公式ドキュメントの説明をもとに記載しています。

TL;DR

この記事の手順を終えると、次のことができるようになります。

  1. client.files.upload() で音声を上げ、client.interactions.create(model="gemini-3.5-transcribe") で文字起こしする
  2. transcription_config.mode 配下に diarization_mode と timestamp_granularities を書き、話者ラベルと単語ごとの時刻を取り出す
  3. カスタム語彙は 話者分離・タイムスタンプとは別リクエスト に分けて設計する

2 つのモデルと料金

Gemini 3.5 Transcribe には、用途の違う 2 つのモデル ID があります(モデルページ / changelog)。

項目 gemini-3.5-transcribe gemini-3.5-transcribe-live
処理方式 ファイル・長尺音声の一括処理 WebSocket による双方向ストリーミング
音声長の上限 最大 1 時間(話者分離・タイムスタンプ有効時は 30 分) 1 セッション最大 10 分
話者分離 対応(最大 8 話者) 非対応
単語タイムスタンプ 対応 非対応
途中結果(interim) なし あり(interim / finalized イベント)
入力単価 $2.00 / 1M トークン(約 $0.003 / 分) $3.50 / 1M トークン(約 $0.005 / 分)
出力単価 $12.00 / 1M トークン(約 $0.002 / 分) $21.00 / 1M トークン(約 $0.004 / 分)
1 分あたりの目安 約 $0.005 約 $0.009

料金は Gemini API 料金ページ の値です。分単価は、Google が同ページで示している「音声 25 トークン / 秒、出力 175 テキストトークン / 分」という前提での試算です。この前提で計算すると、1 時間の録音をファイル版で処理した場合の目安は約 $0.30、Live 版で 1 時間分をストリーミングした場合は約 $0.54 になります。

どちらのモデルも無料枠で試せますが、料金ページによると無料枠のデータは Google の製品改善に利用され、有料枠では利用されません。顧客の通話音声など機微なデータを扱う場合は、試験段階から有料枠を使うほうが安全です。

参考までに、OpenAI の 料金ページ では whisper-1 が $0.006 / 分、gpt-4o-mini-transcribe が約 $0.003 / 分です。ファイル版の Gemini 3.5 Transcribe は Whisper と同水準の価格帯にあり、話者分離と単語タイムスタンプを同じモデルで扱える点が選ぶ理由になります。

手順

全体の流れは次のとおりです。Live 版は手順 6 で別に扱います。

分岐が 3 本に分かれているのは、後述するとおり D・E・F を 1 回のリクエストに同居させられないためです。

1. SDK をインストールする

文字起こしは、Gemini API の Interactions API(client.interactions)経由で呼び出します。Python と Node.js の SDK を入れて、クライアントに interactions が生えているか確認しました。

$ python3 -m venv venv && source venv/bin/activate
$ pip install --quiet google-genai
$ pip show google-genai
Name: google-genai
Version: 2.25.0
Summary: GenAI Python SDK

クライアントの公開属性は、ダミーの API キーでも初期化すれば一覧を取得できます(認証はリクエスト送信時に初めて行われるため)。

from google import genai
client = genai.Client(api_key='dummy')
print([a for a in dir(client) if not a.startswith('_')])

実行結果です。

['agents', 'aio', 'auth_tokens', 'batches', 'caches', 'chats', 'close', 'credentials',
 'environments', 'file_search_stores', 'files', 'interactions', 'models', 'operations',
 'triggers', 'tunings', 'vertexai', 'voices', 'webhooks']

interactions と files が並んでいることが確認できます。古い SDK で interactions が見当たらない場合は、まずバージョンを上げてください。

Node.js の場合は次のとおりです。

$ npm init -y && npm install --silent @google/genai
$ grep version node_modules/@google/genai/package.json
  "version": "2.24.0"

型定義 dist/node/node.d.ts を検索すると、Interactions API 用の TranscriptionConfig 型が見つかりました。こちらは diarization_mode / timestamp_granularities / custom_vocabulary と、フィールド名まで Python 版と完全に一致しています(@deprecated タグの付き方も同じ)。一方、同じファイルには AudioTranscriptionConfig という別の型もあり、こちらは diarization?: boolean / wordTimestamp?: boolean という camelCase かつ真偽値のフィールドで、Python 側とは名前も型も異なります。同じ SDK に「Interactions API 用(snake_case)」と「別 API 用(camelCase)」の 2 種類の設定型が並んでいるため、Node.js で書くときは検索でヒットした型がどちらの API 向けかを確認してから使う必要があります。

2. 最小構成で文字起こしする

公式ドキュメント(Transcribe ガイド)のサンプルによると、最小構成は「ファイルをアップロードして input に音声 URI を渡す」だけです。

from google import genai

client = genai.Client()  # 環境変数 GEMINI_API_KEY を参照
audio_file = client.files.upload(file="path/to/meeting.mp3")

interaction = client.interactions.create(
    model="gemini-3.5-transcribe",
    input=[
        {
            "type": "audio",
            "uri": audio_file.uri,
            "mime_type": audio_file.mime_type,
        }
    ],
)
print(interaction.output_text)

言語は発話単位で自動判定されます。公式ドキュメントでは 85 以上の言語に対応し、1 つの発話の中で言語が切り替わる code-switching(日本語の会話に英語の技術用語が混ざるようなケース)にも対応するとされています。

3. 話者分離と単語タイムスタンプを付ける

議事録で「誰が」「いつ」を取りたいときは、generation_config.transcription_config.mode に type: "verbatim" を指定し、その配下に 2 つのオプションを書きます。

interaction = client.interactions.create(
    model="gemini-3.5-transcribe",
    input=[{"type": "audio", "uri": audio_file.uri, "mime_type": audio_file.mime_type}],
    generation_config={
        "transcription_config": {
            "mode": {
                "type": "verbatim",
                "diarization_mode": "speaker",        # 話者分離
                "timestamp_granularities": ["word"],  # 単語ごとの時刻
            },
        }
    },
)

公式ドキュメントの注意点は 3 つあります。

  • 話者は最大 8 人まで識別できますが、3 人以上の話者帰属は実験的(experimental) と明記されています
  • 話者分離かタイムスタンプを有効にすると、音声長の上限が 1 時間から 30 分に下がります
  • どちらも Live 版では使えません

1 時間の会議を話者付きで書き起こしたい場合は、30 分以下に分割してから送る設計が必要です。

4. カスタム語彙と言語を指定する

製品名や社内用語の表記ゆれを抑えたいときは custom_vocabulary を使います。最大 1,000 語まで登録できますが、公式ドキュメントでは 100 語以下のときに最良の結果になる とされています。

interaction = client.interactions.create(
    model="gemini-3.5-transcribe",
    input=[{"type": "audio", "uri": audio_file.uri, "mime_type": audio_file.mime_type}],
    generation_config={
        "transcription_config": {
            "custom_vocabulary": ["Gemini", "Kubernetes", "BigQuery"],
            "language_codes": ["ja-JP", "en-US"],  # BCP-47 形式
        }
    },
)

language_codes は SDK の型定義に存在するフィールドで、BCP-47 形式(ja-JP、en-US、cmn-Hans-CN など)で指定します。旧名の language_hints は SDK 上で「Deprecated: use language_codes」とされているため、新規コードでは使わないでください。

このリクエストには diarization_mode も timestamp_granularities も入っていません。入れると拒否されるためです(次章で詳しく扱います)。

5. Smart モードで読みやすいテキストにする

「えー」「あのー」といった言い淀みを取り除き、言い直しを解消し、箇条書きなどに整形したテキストが欲しい場合は Smart モードを使います。

generation_config = {
    "transcription_config": {
        "mode": "smart",
    }
}

verbatim(発話をそのまま書き起こす)と smart(整形する)は二択です。Smart モードもタイムスタンプ・話者分離とは併用できません。整形後のテキストは元の発話と単語単位で対応しなくなるため、時刻や話者を付けられないのは仕様として筋が通っています。

6. 単語ごとの注釈を取り出す

手順 3 で話者分離やタイムスタンプを有効にした場合、単語ごとの情報は word_info 型の注釈として返ります。公式ドキュメントのサンプルでは、次のようにたどって取り出します。

def extract_word_annotations(interaction):
    words = []
    for step in getattr(interaction, "steps", []) or []:
        for content in getattr(step, "content", []) or []:
            for annotation in getattr(content, "annotations", []) or []:
                if getattr(annotation, "type", None) == "word_info":
                    words.append(annotation)
    return words

SDK のソース google/genai/_gaos/types/interactions/wordinfo.py を読むと、注釈が持つフィールドは次の 5 つでした。

class WordInfoParam(TypedDict):
    end_offset: NotRequired[str]
    speaker: NotRequired[str]
    start_offset: NotRequired[str]
    text: NotRequired[str]
    type: Literal["word_info"]

start_offset / end_offset はタイムスタンプ指定時だけ、speaker(spk_1 のような値)は話者分離指定時だけ入ります。すべて NotRequired なので、取り出す側は getattr(word, "speaker", None) のように欠落前提で書くのが安全です。

7. Live 版(ストリーミング)を使う場合

Live 版は、公式ドキュメントによると WebSocket で音声を送り続け、途中結果(interim)と確定結果(finalized)の文字起こしイベントを受け取る方式です。無音検出(VAD: Voice Activity Detection)の戦略も複数から選べます。

設計上の注意は次の 2 点です。

  • 1 セッションは最大 10 分のため、会議のように長時間続く用途では セッションの張り直し を前提にしたクライアント設計が必要です
  • 話者分離と単語タイムスタンプは使えません。リアルタイム字幕は Live 版、会議後の議事録は録音ファイルをファイル版で処理し直す、という 2 段構えが現実的です

Live 版の接続コードは公式の Transcribe ガイドの Python サンプルには含まれていなかったため、ここでは掲載しません。実装時は Live API のドキュメントを参照してください。

同時に指定できない組み合わせ

ここまでの制約を 1 枚にすると次のようになります。

組み合わせ 可否 理由・影響
話者分離 + 単語タイムスタンプ 可 音声長の上限が 30 分になる
カスタム語彙 + 話者分離 不可 API がリクエストを拒否する
カスタム語彙 + 単語タイムスタンプ 不可 同上
Smart モード + 話者分離 / タイムスタンプ 不可 整形後テキストは単語と対応しない
カスタム語彙 + Smart モード 型定義上は制約なし custom_vocabulary は mode と別の独立フィールドのため型は通る。実際に API が受け付けるかは未実行(本記事では未検証)
Live 版 + 話者分離 / タイムスタンプ 不可 非ストリーミング限定の機能

「話者付きで、しかも固有名詞も正しい議事録」が欲しい場合は、1 回で済ませる方法はありません。話者分離付きの verbatim 結果と、カスタム語彙付きの結果を別々に取得し、アプリケーション側で突き合わせる設計になります。その分、API 呼び出しと料金も 2 回分かかる点を見積もりに入れておく必要があります。

SDK ソースで見つけた非推奨フィールド

公式ドキュメントの説明文には出てこない情報として、SDK のソースコードに非推奨の指定方法が残っていました。google-genai 2.25.0 の google/genai/_gaos/types/interactions/transcriptionconfig.py にある TranscriptionConfigParam のフィールドは次のとおりです。

class TranscriptionConfigParam(TypedDict):
    adaptation_phrases: NotRequired[List[str]]
    custom_vocabulary: NotRequired[List[str]]
    diarization_mode: NotRequired[str]
    language_codes: NotRequired[List[str]]
    language_hints: NotRequired[List[str]]   # Deprecated: use language_codes.
    mode: NotRequired[TranscriptionConfigModeParam]
    timestamp_granularities: NotRequired[List[str]]

一見すると、diarization_mode を transcription_config の直下に書いてもよさそうに見えます。ところが同じパッケージのソースでは、adaptation_phrases・トップレベルの diarization_mode・トップレベルの timestamp_granularities の 3 つに次の非推奨指定が付いていました。

deprecated="warning: ** DEPRECATED ** - This will be removed in a future release, please migrate away from it as soon as possible."

一方、verbatimtranscriptionmode.py の VerbatimTranscriptionModeParam は diarization_mode と timestamp_granularities を持っています。つまり現行の書き方は「mode の中(type: "verbatim")に話者分離とタイムスタンプを書く」で、トップレベルの指定は移行期間の後方互換用です。手順 3 のコードがわざわざ mode の中にネストしているのはこのためです。

書き換えの対応は次のとおりです。

非推奨・旧名 現行の書き方
transcription_config.diarization_mode transcription_config.mode.diarization_mode(type: "verbatim")
transcription_config.timestamp_granularities transcription_config.mode.timestamp_granularities(type: "verbatim")
transcription_config.adaptation_phrases transcription_config.custom_vocabulary
transcription_config.language_hints transcription_config.language_codes

この非推奨指定は Node.js SDK(@google/genai 2.24.0)の型定義でも @deprecated タグとして同じ 3 フィールドに付いており、Python・Node.js 両方の SDK で一致しています。

手順を実際に試した所見

SDK を入れて型定義を読んだ範囲で、筆者が気づいた点は 3 つあります。

1 つ目は、型定義だけ見ると組み合わせ制約が見えない ことです。TranscriptionConfigParam には custom_vocabulary と diarization_mode と timestamp_granularities が同じ階層に並んでおり、型チェックは 3 つ同時の指定を通します。拒否されるのは API 側で、エディタ補完に従って書いただけでは実行するまで気づけません。設定をアプリケーションの UI から組み立てる場合は、送信前に自前で組み合わせを検証する層を置くほうが、ユーザーにエラーを返すより親切です。

2 つ目は、旧フィールドの非推奨が Python の型ヒントからは分からない ことです。非推奨の印は pydantic の Field(deprecated=...) として付いていて、TypedDict 側のフィールド一覧を眺めても判別できません。手元のコードで adaptation_phrases を使っていないか、grep で確認しておくと移行漏れを防げます。

3 つ目は、同じ SDK パッケージの中に、命名規則が異なる 2 つの設定型が並んでいる ことです。Interactions API 用の TranscriptionConfig(snake_case・Python と同一フィールド名)と、別 API 用の AudioTranscriptionConfig(camelCase・diarization / wordTimestamp は真偽値)が同じ node.d.ts に共存しています。エディタの補完で最初に出てきた型がどちらか確認せずに書くと、文字起こしのリクエストに真偽値の diarization: true を渡して型エラーになる、といった詰まり方をします。

トラブルシューティング

Q1: 話者分離を指定したらリクエストが拒否される

同じリクエストに custom_vocabulary が入っていないか確認してください。カスタム語彙は話者分離・単語タイムスタンプと併用できません。Smart モード(mode: "smart")を指定している場合も同様です。

Q2: 1 時間以内の音声なのにエラーになる

話者分離かタイムスタンプを有効にしていると、上限は 30 分に下がります。音声を 30 分以下に分割するか、話者分離・タイムスタンプを外して送ってください。

Q3: 非推奨の警告が出る

transcription_config 直下の diarization_mode や adaptation_phrases、language_hints を使っていないか確認してください。前章の対応表のとおり書き換えれば解消します。

Q4: Live 版の接続が 10 分で切れる

仕様です。1 セッションは最大 10 分のため、切断を検知して新しいセッションを張り直す処理をクライアント側に実装してください。

Q5: client.interactions が存在しない

SDK が古い可能性があります。pip show google-genai でバージョンを確認し、更新してください。本記事の確認は google-genai 2.25.0、@google/genai 2.24.0 で行っています。

おわりに

Gemini 3.5 Transcribe は、話者分離・単語タイムスタンプ・カスタム語彙・85 以上の言語の自動判定を 1 つのモデルに収めた音声認識モデルで、ファイル版なら 1 分あたり約 $0.005 で使えます。使い始める前に押さえておきたいのは次の 3 点です。

  • 話者分離とタイムスタンプは mode(verbatim)の中に書く。トップレベル指定は非推奨
  • カスタム語彙は話者分離・タイムスタンプと別リクエストに分ける
  • 話者付きの処理は 30 分、Live 版は 10 分で区切る設計にする

議事録ツールに組み込むなら、「話者付き verbatim」と「語彙付き」の 2 回呼び出しを前提に、料金と処理時間を見積もるところから始めるのがよいと考えます。

関連記事

参考リンク

1
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?