メインコンテンツまでスキップ

Speech to Text Streaming API

Streaming APIは、WebSocketで音声chunkを受信し、transcript deltaと完了eventを 返します。event名はOpenAI Realtime transcription互換の形式を採用していますが、 接続先はKitsch Labs専用です。

接続情報

wss://api.kitschlabs.com/v1/speech-to-text/stream

サーバー環境からのWebSocket handshakeに、次の認証ヘッダーを含めます。

Authorization: Bearer <API_KEY>

xi-api-keyも利用できます。一般的なブラウザWebSocket APIでは任意の認証 ヘッダーを設定できません。APIキーはサーバーに保管し、サーバーから接続してください。

認証後、サーバーは最初にsession.createdを送信します。

Session設定

音声を送る前にsession.updateを1回送信できます。

{
"type": "session.update",
"session": {
"type": "transcription",
"audio": {
"input": {
"format": {"type": "audio/pcm", "rate": 24000},
"transcription": {
"model": "kitsch-stt-v1",
"language": "ja",
"prompt": "Kitsch Labs"
},
"turn_detection": {"type": "server_vad"}
}
}
}
}

サーバーは適用した設定をsession.updatedで返します。最初の音声chunkを送信した 後はsessionを変更できません。

対応する音声形式

形式入力条件
audio/pcm24 kHz、mono、signed 16-bit little-endian PCM
audio/pcmu8 kHz、mono、G.711 μ-law
audio/pcma8 kHz、mono、G.711 A-law

デフォルトはaudio/pcm、24 kHzです。

音声の送信方法

各chunkをbase64でエンコードして送信します。デコード後のchunk 1つの最大サイズは 256 KiBです。

{"type":"input_audio_buffer.append","audio":"BASE64_AUDIO"}

デフォルトのserver_vadは発話終了を自動検出します。手動で終了する場合は turn_detectionnullに設定し、次のeventを送信します。

{"type":"input_audio_buffer.commit"}

受信するイベント

イベント説明
session.createdtranscription sessionの作成完了
session.updatedsession設定の反映完了
input_audio_buffer.committed1つの発話入力が確定
conversation.item.input_audio_transcription.delta確定したtranscript断片
conversation.item.input_audio_transcription.completed発話全体のtranscriptと使用量
errorリクエストまたは処理エラー

Delta event:

{
"type": "conversation.item.input_audio_transcription.delta",
"event_id": "event_...",
"item_id": "item_...",
"content_index": 0,
"delta": "こんにちは"
}

完了event:

{
"type": "conversation.item.input_audio_transcription.completed",
"event_id": "event_...",
"item_id": "item_...",
"content_index": 0,
"transcript": "こんにちは。",
"usage": {"type": "duration", "seconds": 1.25}
}

item_idcontent_indexを基準にdeltaを順番に連結し、completed eventの transcriptをその発話の最終結果として使用してください。

接続例

次の例では、24 kHz mono signed 16-bit little-endian raw PCMファイルを送信します。

import asyncio
import base64
import json
import os

from websockets.asyncio.client import connect


async def transcribe() -> None:
headers = {"Authorization": f"Bearer {os.environ['KITSCH_API_KEY']}"}
async with connect(
"wss://api.kitschlabs.com/v1/speech-to-text/stream",
additional_headers=headers,
) as websocket:
print(await websocket.recv()) # session.created

await websocket.send(json.dumps({
"type": "session.update",
"session": {
"type": "transcription",
"audio": {
"input": {
"format": {"type": "audio/pcm", "rate": 24000},
"transcription": {"model": "kitsch-stt-v1", "language": "ja"},
"turn_detection": None,
}
},
},
}))

with open("speech.pcm", "rb") as audio:
while chunk := audio.read(64 * 1024):
await websocket.send(json.dumps({
"type": "input_audio_buffer.append",
"audio": base64.b64encode(chunk).decode("ascii"),
}))

await websocket.send(json.dumps({"type": "input_audio_buffer.commit"}))

async for raw_event in websocket:
event = json.loads(raw_event)
if event["type"] == "conversation.item.input_audio_transcription.delta":
print(event["delta"], end="", flush=True)
elif event["type"] == "conversation.item.input_audio_transcription.completed":
print()
break
elif event["type"] == "error":
raise RuntimeError(event["error"]["message"])


asyncio.run(transcribe())

制限と課金

  • 1接続の最大session時間は30秒です。
  • 接続時間を開始済みの1秒単位に切り上げ、1秒あたり0.1 creditで計算します。
  • 1接続で複数の発話を処理できますが、30秒に達する前に新しいsessionへ接続する ことを推奨します。
  • 一時的な接続エラーの後は、新しいsessionとして再接続してください。以前の sessionの音声を自動再送しないでください。

エラー処理

未対応のeventや無効な設定は、接続を維持したままerror eventとして返される 場合があります。

{
"type": "error",
"event_id": "event_...",
"error": {
"type": "invalid_request_error",
"code": "invalid_audio",
"message": "The audio field must be valid base64.",
"param": null,
"event_id": "<REQUEST_ID>"
}
}
Close code意味推奨対応
1000正常完了またはsession上限必要に応じて新しいsessionへ接続
1008認証、権限、リクエストポリシーのエラーキー、credit、設定を確認
1011一時的な処理エラー限定的に再接続
1013同時処理容量の上限backoff後に再接続