{ "cells": [ { "cell_type": "markdown", "id": "md0-c8662f64", "metadata": {}, "source": [ "# Migrate from Whisper to GPT-Transcribe and GPT-Live-Transcribe\n", "\n", "## Overview\n", "\n", "OpenAI’s new transcription models give existing Whisper users two focused upgrade paths. GPT-Transcribe (`gpt-transcribe`) is designed for accurate transcription of completed audio via uploaded files. GPT-Live-Transcribe (`gpt-live-transcribe`) is designed for low-latency, continuously streaming transcription.\n", "\n", "At a high level, the API endpoints are the same. File requests continue to use POST /v1/audio/transcriptions. Realtime integrations continue to configure a transcription session and handle transcription events. What’s different are the supported request fields, the output formats, and contextual/multilingual hints.\n", "\n", "### What stays the same\n", "\n", "- File transcription uses the existing Audio API transcription endpoint.\n", "- Realtime transcription uses the existing Realtime transcription session, WebSocket or WebRTC transport, and transcription events.\n", "- Applications still provide audio, receive transcription text, and handle API or connection errors.\n", "\n", "### What changes\n", "\n", "- Use `gpt-transcribe` for completed files, streamed file transcripts, or committed Realtime turns.\n", "- Use `gpt-live-transcribe` for continuously streaming, low-latency live transcription.\n", "- Replace the legacy `language` hint with the new-model `languages` array.\n", "- Use `prompt` for free-form context and `keywords` for literal terms expected in the audio.\n", "- Handle detected-language output for GPT-Transcribe, including an empty languages array.\n", "\n", "## 1. Choose your migration path\n", "\n", "- Recorded meetings, calls, or uploaded audio: migrate `whisper-1` to `gpt-transcribe` on POST /v1/audio/transcriptions.\n", "- Live captions or continuously arriving microphone audio: migrate `gpt-realtime-whisper` to `gpt-live-transcribe` in a Realtime transcription session.\n", "- Higher-accuracy transcription after a manually committed audio turn: use `gpt-transcribe` in a Realtime transcription session over WebSocket.\n", "Treat live audio and streaming output as separate decisions. A completed file can produce streamed transcript events, and a Realtime session can wait for an audio turn to be committed before producing deltas.\n", "\n", "## 2. Migrate recorded audio\n", "\n", "The smallest file migration preserves the endpoint, SDK call, and file upload. Replace the model identifier, then consume the JSON transcription response.\n" ] }, { "cell_type": "markdown", "id": "md1-d6f904cb", "metadata": {}, "source": [ "### Prerequisites\n", "\n", "- Python 3.11 or later.\n", "- The OpenAI Python SDK: `python -m pip install --upgrade openai`.\n", "- An `OPENAI_API_KEY` environment variable.\n", "- A completed recording named `meeting.wav`, or update `audio_path` to point to your own file.\n", "- Access to the transcription models used in the example.\n", "\n", "File examples make live API requests. Realtime examples construct the session payloads. Use WebSocket or WebRTC for `gpt-live-transcribe`, and use WebSocket for committed-turn `gpt-transcribe`.\n" ] }, { "cell_type": "code", "execution_count": null, "id": "code2-7bb29ad5", "metadata": {}, "outputs": [], "source": [ "import json\n", "from pathlib import Path\n", "\n", "from openai import OpenAI\n", "\n", "client = OpenAI()\n", "audio_path = Path(\"meeting.wav\")\n" ] }, { "cell_type": "markdown", "id": "md3-4afa0d58", "metadata": {}, "source": [ "**Before — Whisper file transcription**\n" ] }, { "cell_type": "code", "execution_count": null, "id": "code4-4e303bd7", "metadata": {}, "outputs": [], "source": [ "with audio_path.open(\"rb\") as audio:\n", " before = client.audio.transcriptions.create(\n", " model=\"whisper-1\",\n", " file=audio,\n", " )\n", "\n", "print(before.text)\n" ] }, { "cell_type": "markdown", "id": "md5-4cc39358", "metadata": {}, "source": [ "**After — GPT-Transcribe**\n" ] }, { "cell_type": "code", "execution_count": null, "id": "code6-7e0fa730", "metadata": {}, "outputs": [], "source": [ "with audio_path.open(\"rb\") as audio:\n", " after = client.audio.transcriptions.create(\n", " model=\"gpt-transcribe\",\n", " file=audio,\n", " response_format=\"json\",\n", " )\n", "\n", "print(after.text)\n" ] }, { "cell_type": "markdown", "id": "md7-9dcbab93", "metadata": {}, "source": [ "**Do’s and don’ts**\n", "\n", "- Note that the new model returns JSON. If the existing application requests `text`, `verbose_json`, `srt`, or `vtt`, do not assume the same response_format remains valid. Keep Whisper for those formats or explicitly build and test the downstream conversion your application requires.\n", "\n", "## 3. Update language hints and domain context\n", "\n", "Legacy Whisper integrations commonly send a single `language` value and place domain vocabulary in a free-form prompt. The new models accept multiple expected input languages and separate structured keyword hints from the general prompt.\n" ] }, { "cell_type": "markdown", "id": "md8-06de07c1", "metadata": {}, "source": [ "**Before — single-language Whisper request**\n" ] }, { "cell_type": "code", "execution_count": null, "id": "code9-336824aa", "metadata": {}, "outputs": [], "source": [ "from openai import OpenAI\n", "\n", "client = OpenAI()\n", "\n", "with open(\"meeting.wav\", \"rb\") as audio:\n", " result = client.audio.transcriptions.create(\n", " model=\"whisper-1\",\n", " file=audio,\n", " language=\"en\",\n", " prompt=\"A support call about AC-42.\",\n", " )\n", "\n", "print(result.text)\n" ] }, { "cell_type": "markdown", "id": "md10-8d904192", "metadata": {}, "source": [ "**After — multilingual GPT-Transcribe request**\n" ] }, { "cell_type": "code", "execution_count": null, "id": "code11-46992c50", "metadata": {}, "outputs": [], "source": [ "from openai import OpenAI\n", "\n", "client = OpenAI()\n", "\n", "with open(\"meeting.wav\", \"rb\") as audio:\n", " result = client.audio.transcriptions.create(\n", " model=\"gpt-transcribe\",\n", " file=audio,\n", " prompt=\"A customer support call about billing.\",\n", " extra_body={\n", " \"keywords\": [\"AC-42\", \"Premium Plus\"],\n", " \"languages\": [\"en\", \"fr\"],\n", " },\n", " )\n", "\n", "print(result.text)\n" ] }, { "cell_type": "markdown", "id": "md12-78363fec", "metadata": {}, "source": [ "**Do’s and don’ts**\n", "\n", "- Use `prompt` to describe the recording’s topic or setting.\n", "- Use `keywords` for literal product names, medication names, account identifiers, or other domain-specific terms that may actually be spoken.\n", "- Use `languages` to indicate expected spoken languages.\n", "- Do not send both `language` and `languages`.\n", "- Validate keyword input before making the request: each keyword must be a single-line literal and must not include `<`, `>`, a carriage return, or a line feed. Invalid keyword input rejects the whole request or session update.\n", "\n", "## 4. Stream the transcription of a completed file\n", "\n", "Streaming applies to the transcription output, not the audio input. The application first supplies a completed recording; the API then streams text as it transcribes that recording.\n" ] }, { "cell_type": "markdown", "id": "md13-da063a97", "metadata": {}, "source": [ "**Before — blocking Whisper file request**\n" ] }, { "cell_type": "code", "execution_count": null, "id": "code14-48114e38", "metadata": {}, "outputs": [], "source": [ "with audio_path.open(\"rb\") as audio:\n", " before = client.audio.transcriptions.create(\n", " model=\"whisper-1\",\n", " file=audio,\n", " )\n", "\n", "print(before.text)\n", "\n", "# whisper-1 does not provide transcript.text.delta events.\n" ] }, { "cell_type": "markdown", "id": "md15-5637d753", "metadata": {}, "source": [ "**After — streamed GPT-Transcribe file request**\n" ] }, { "cell_type": "code", "execution_count": null, "id": "code16-a33ae276", "metadata": {}, "outputs": [], "source": [ "with audio_path.open(\"rb\") as audio:\n", " stream = client.audio.transcriptions.create(\n", " model=\"gpt-transcribe\",\n", " file=audio,\n", " stream=True,\n", " )\n", "\n", " for event in stream:\n", " if event.type == \"transcript.text.delta\":\n", " print(event.delta, end=\"\", flush=True)\n", " elif event.type == \"transcript.text.done\":\n", " print(\"\\nFinal:\", event.text)\n" ] }, { "cell_type": "markdown", "id": "md17-0c31d92d", "metadata": {}, "source": [ "**Do’s and don’ts**\n", "\n", "- Use transcription events to distinguish in-progress from completed transcripts. Update your transcript or progress UI with `transcript.text.delta` events, then use `transcript.text.done` to mark the transcript as final.\n", "- Do not label file streaming as live audio capture: the file must already exist.\n", "\n", "## 5. Migrate continuous live transcription\n", "\n", "For a live captioning workflow, preserve the Realtime connection and transcription-session architecture. Change the transcription model and update the model-specific configuration.\n" ] }, { "cell_type": "markdown", "id": "md18-45f22aaa", "metadata": {}, "source": [ "**Before — GPT-Realtime-Whisper**\n" ] }, { "cell_type": "code", "execution_count": null, "id": "code19-bc07884b", "metadata": {}, "outputs": [], "source": [ "legacy_realtime_session = json.loads(\n", " r'''\n", "{\n", " \"type\": \"session.update\",\n", " \"session\": {\n", " \"type\": \"transcription\",\n", " \"audio\": {\n", " \"input\": {\n", " \"transcription\": {\n", " \"model\": \"gpt-realtime-whisper\",\n", " \"language\": \"en\"\n", " },\n", " \"turn_detection\": null\n", " }\n", " }\n", " }\n", "}\n", "'''\n", ")\n", "\n", "print(json.dumps(legacy_realtime_session, indent=2))\n" ] }, { "cell_type": "markdown", "id": "md20-03c1f03a", "metadata": {}, "source": [ "**After — GPT-Live-Transcribe**\n" ] }, { "cell_type": "code", "execution_count": null, "id": "code21-6a16eda3", "metadata": {}, "outputs": [], "source": [ "live_transcription_session = json.loads(\n", " r'''\n", "{\n", " \"type\": \"session.update\",\n", " \"session\": {\n", " \"type\": \"transcription\",\n", " \"audio\": {\n", " \"input\": {\n", " \"transcription\": {\n", " \"model\": \"gpt-live-transcribe\",\n", " \"prompt\": \"A support call about a plan.\",\n", " \"keywords\": [\"AC-42\", \"Premium Plus\"],\n", " \"languages\": [\"en\", \"fr\"],\n", " \"delay\": \"low\"\n", " },\n", " \"turn_detection\": null\n", " }\n", " }\n", " }\n", "}\n", "'''\n", ")\n", "\n", "print(json.dumps(live_transcription_session, indent=2))\n" ] }, { "cell_type": "markdown", "id": "md22-ade24c11", "metadata": {}, "source": [ "**Do’s and don’ts**\n", "\n", "- Continue handling `conversation.item.input_audio_transcription.delta` and `conversation.item.input_audio_transcription.completed`.\n", "- Preserve the existing item_id-based reconciliation logic.\n", "- Use the model’s `delay` setting to trade off faster partial transcripts against a more accurate final transcript.\n", "- Preserve the existing WebSocket or WebRTC transport; Realtime transcription supports both.\n", "\n", "## 6. Use GPT-Transcribe in a committed-turn Realtime session\n", "\n", "GPT-Transcribe is not limited to uploaded files. Use it as the input transcription model in a Realtime session over WebSocket when your application needs higher-accuracy transcription after a manually committed audio turn.\n" ] }, { "cell_type": "markdown", "id": "md23-e55b4514", "metadata": {}, "source": [ "**Before — legacy live-transcription session**\n" ] }, { "cell_type": "code", "execution_count": null, "id": "code24-f2e82f91", "metadata": {}, "outputs": [], "source": [ "legacy_live_session = json.loads(\n", " r'''\n", "{\n", " \"type\": \"session.update\",\n", " \"session\": {\n", " \"type\": \"transcription\",\n", " \"audio\": {\n", " \"input\": {\n", " \"transcription\": {\n", " \"model\": \"gpt-realtime-whisper\",\n", " \"language\": \"en\"\n", " }\n", " }\n", " }\n", " }\n", "}\n", "'''\n", ")\n", "\n", "print(json.dumps(legacy_live_session, indent=2))\n" ] }, { "cell_type": "markdown", "id": "md25-c6984fce", "metadata": {}, "source": [ "**After — committed-turn GPT-Transcribe session**\n" ] }, { "cell_type": "code", "execution_count": null, "id": "code26-86adc876", "metadata": {}, "outputs": [], "source": [ "committed_turn_session = json.loads(\n", " r'''\n", "{\n", " \"type\": \"session.update\",\n", " \"session\": {\n", " \"type\": \"transcription\",\n", " \"audio\": {\n", " \"input\": {\n", " \"transcription\": {\n", " \"model\": \"gpt-transcribe\",\n", " \"prompt\": \"A customer support call.\",\n", " \"keywords\": [\"AC-42\"],\n", " \"languages\": [\"en\", \"fr\"]\n", " },\n", " \"turn_detection\": null\n", " }\n", " }\n", " }\n", "}\n", "'''\n", ")\n", "\n", "print(json.dumps(committed_turn_session, indent=2))\n" ] }, { "cell_type": "markdown", "id": "md27-63bdbdea", "metadata": {}, "source": [ "**Do’s and don’ts**\n", "\n", "- Append audio to the input buffer, then send `input_audio_buffer.commit`. Transcription happens per committed chunk of audio, not continuous live captioning. GPT-Transcribe may emit transcript delta events before the completed event for that chunk.\n", "\n", "## 7. Update response and event handling\n", "\n", "GPT-Transcribe can include detected input languages in completed file and Realtime results. These are predictions returned by the model, not a restatement of the caller’s language hints.\n" ] }, { "cell_type": "markdown", "id": "md28-b02f456f", "metadata": {}, "source": [ "**Before — transcript without language predictions**\n" ] }, { "cell_type": "code", "execution_count": null, "id": "code29-4cfdd204", "metadata": {}, "outputs": [], "source": [ "legacy_transcription_response = json.loads(\n", " r'''\n", "{\n", " \"text\": \"Bonjour, pouvez-vous m'entendre ?\"\n", "}\n", "'''\n", ")\n", "\n", "print(json.dumps(legacy_transcription_response, indent=2))\n" ] }, { "cell_type": "markdown", "id": "md30-45e70d22", "metadata": {}, "source": [ "**After — GPT-Transcribe detected-language response**\n" ] }, { "cell_type": "code", "execution_count": null, "id": "code31-7b44a99b", "metadata": {}, "outputs": [], "source": [ "detected_language_response = json.loads(\n", " r'''\n", "{\n", " \"text\": \"Bonjour, pouvez-vous m'entendre ?\",\n", " \"languages\": [\n", " { \"code\": \"fr\" }\n", " ]\n", "}\n", "'''\n", ")\n", "\n", "print(json.dumps(detected_language_response, indent=2))\n", "\n", "language_codes = [\n", " item[\"code\"]\n", " for item in detected_language_response.get(\"languages\", [])\n", "]\n", "print(\"Detected languages:\", language_codes)\n" ] }, { "cell_type": "markdown", "id": "md32-2a2e27f3", "metadata": {}, "source": [ "**Do’s and don’ts**\n", "\n", "- Handle `languages: []` as a valid result when the model cannot make a reliable prediction.\n", "- Do not infer that no one spoke, that the request failed, or that a synthetic `unknown` language was returned. GPT-Live-Transcribe does not return detected-language predictions in its initial contract.\n", "- For Realtime, inspect `conversation.item.input_audio_transcription.completed`; GPT-Transcribe may attach the same language metadata to that event.\n", "\n", "## 8. Check compatibility before switching\n", "\n", "Some existing transcription features do not have a drop-in replacement in `gpt-transcribe` or `gpt-live-transcribe`.\n", "\n", "- Subtitles: retain `whisper-1` if the integration depends on native SRT or VTT output.\n", "- Timestamps: retain a model and response format that explicitly support word or segment timestamps.\n", "- Translation: retain `/v1/audio/translations` with a supported translation model if the application translates audio into English.\n", "- Speaker diarization: use `gpt-4o-transcribe-diarize` on `/v1/audio/transcriptions` with `response_format: \"diarized_json\"` when your application requires speaker labels. Set `chunking_strategy: \"auto\"` for recordings longer than 30 seconds. This model is not supported in the Realtime API.\n", "\n", "## 9. Evaluate before and after\n", "\n", "Use the same representative audio clips across both models. Compare the baseline, a direct model-only replacement, and a version that adds the new context fields. This isolates improvements from the model itself versus improvements caused by prompt, keywords, or language hints.\n", "\n", "### Evaluate transcription quality\n", "\n", "- Overall word error rate on representative recordings.\n", "- Exact-match accuracy for names, product names, medication names, account identifiers, numbers, and email addresses.\n", "- Performance with accents, background noise, telephony audio, and short utterances.\n", "- Accuracy during language switching and mixed-language speech.\n", "- Keyword hallucination: confirm the transcript does not insert hinted words that were not actually spoken.\n", "- Completeness of the final transcript and any expected language metadata.\n", "\n", "### Evaluate streaming and operational behavior\n", "\n", "- Time to first transcript delta, reported separately for file streaming and live audio.\n", "- Time from an explicit committed turn to the final transcript.\n", "- Median and tail latency, including p95 or the team’s existing service-level threshold.\n", "- How frequently partial transcript text changes before completion.\n", "- Event ordering and item_id reconciliation across overlapping turns.\n", "- Reconnect, retry, empty-audio, and interrupted-session behavior.\n", "- Actual request pricing and production rate limits from the released source of truth.\n", "\n", "## Common migration errors\n", "\n", "- Treating `gpt-transcribe` as incompatible with Realtime solely because it is optimized for completed audio.\n", "- Describing GPT-Transcribe as unable to stream because processing begins after a commit.\n", "- Assuming streamed file output means the endpoint accepts an ongoing microphone stream.\n", "- Sending both legacy `language` and new-model `languages`.\n", "- Expecting subtitle formats, timestamps, speaker labels, or detected-language output from a model that does not support them.\n", "- Treating an empty detected-language array as an API error.\n", "- Forcing keywords into the transcript instead of treating them as hints.\n", "- Assuming every Realtime model supports the same voice activity detection and latency controls.\n" ] }, { "cell_type": "markdown", "id": "md33-51355617", "metadata": {}, "source": [ "## Related resources\n", "\n", "- [Speech to text](https://developers.openai.com/api/docs/guides/speech-to-text)\n", "- [Realtime transcription](https://developers.openai.com/api/docs/guides/realtime-transcription)\n", "- [GPT-4o Transcribe Diarize](https://developers.openai.com/api/docs/models/gpt-4o-transcribe-diarize)\n" ] } ], "metadata": { "kernelspec": { "display_name": "Python 3", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.11.8" } }, "nbformat": 4, "nbformat_minor": 5 }