Edit

Share via


Quickstart: Create a voice live real-time voice agent (Preview)

Note

This feature is currently in public preview. This preview is provided without a service-level agreement, and is not recommended for production workloads. Certain features might not be supported or might have constrained capabilities. For more information, see Supplemental Terms of Use for Microsoft Azure Previews.

In this article, you learn how to use voice live with generative AI and Azure AI Speech in the Azure AI Foundry portal.

You create and run an application to use voice live directly with generative AI models for real-time voice agents.

  • Using models directly allows specifying custom instructions (prompts) for each session, offering more flexibility for dynamic or experimental use cases.

  • Models may be preferable when you want fine-grained control over session parameters or need to frequently adjust the prompt or configuration without updating an agent in the portal.

  • The code for model-based sessions is simpler in some respects, as it does not require managing agent IDs or agent-specific setup.

  • Direct model use is suitable for scenarios where agent-level abstraction or built-in logic is unnecessary.

To instead use the voice live API with agents, see the voice live agents quickstart.

Prerequisites

Tip

To use voice live, you don't need to deploy an audio model with your Azure AI Foundry resource. Voice live is fully managed, and the model is automatically deployed for you. For more information about models availability, see the voice live overview documentation.

Try out voice live in the Speech playground

To try out the voice live demo, follow these steps:

  1. Go to your project in Azure AI Foundry.

  2. Select Playgrounds from the left pane.

  3. In the Speech playground tile, select Try the Speech playground.

  4. Select Speech capabilities by scenario > Voice live.

    Screenshot of filtering Speech service capabilities by scenario.

  5. Select a sample scenario, such as Casual chat.

    Screenshot of selecting the casual chat example scenario in the Speech playground.

  6. Select Start to start chatting with the chat agent.

  7. Select End to end the chat session.

  8. Select a new generative AI model from the drop-down list via Configuration > GenAI > Generative AI model.

    Note

    You can also select an agent that you configured in the Agents playground. For more information, see the voice live with Foundry agents quickstart.

    Screenshot of the casual chat example scenario in the Speech playground.

  9. Edit other settings as needed, such as the Response instructions, Voice, and Speaking rate.

  10. Select Start to start speaking again and select End to end the chat session.

In this article, you learn how to use Azure AI Speech voice live with Azure AI Foundry models using Python code.

You create and run an application to use voice live directly with generative AI models for real-time voice agents.

  • Using models directly allows specifying custom instructions (prompts) for each session, offering more flexibility for dynamic or experimental use cases.

  • Models may be preferable when you want fine-grained control over session parameters or need to frequently adjust the prompt or configuration without updating an agent in the portal.

  • The code for model-based sessions is simpler in some respects, as it does not require managing agent IDs or agent-specific setup.

  • Direct model use is suitable for scenarios where agent-level abstraction or built-in logic is unnecessary.

To instead use the voice live API with agents, see the voice live agents quickstart.

Prerequisites

Tip

To use voice live, you don't need to deploy an audio model with your Azure AI Foundry resource. Voice live is fully managed, and the model is automatically deployed for you. For more information about models availability, see the voice live overview documentation.

Microsoft Entra ID prerequisites

For the recommended keyless authentication with Microsoft Entra ID, you need to:

  • Install the Azure CLI used for keyless authentication with Microsoft Entra ID.
  • Assign the Cognitive Services User role to your user account. You can assign roles in the Azure portal under Access control (IAM) > Add role assignment.

Set up

  1. Create a new folder voice-live-quickstart and go to the quickstart folder with the following command:

    mkdir voice-live-quickstart && cd voice-live-quickstart
    
  2. Create a virtual environment. If you already have Python 3.10 or higher installed, you can create a virtual environment using the following commands:

    py -3 -m venv .venv
    .venv\scripts\activate
    

    Activating the Python environment means that when you run python or pip from the command line, you then use the Python interpreter contained in the .venv folder of your application. You can use the deactivate command to exit the python virtual environment, and can later reactivate it when needed.

    Tip

    We recommend that you create and activate a new Python environment to use to install the packages you need for this tutorial. Don't install packages into your global python installation. You should always use a virtual or conda environment when installing python packages, otherwise you can break your global installation of Python.

  3. Create a file named requirements.txt. Add the following packages to the file:

    aiohttp==3.11.18
    azure-core==1.34.0
    azure-identity==1.22.0
    certifi==2025.4.26
    cffi==1.17.1
    cryptography==44.0.3
    numpy==2.2.5
    pycparser==2.22
    python-dotenv==1.1.0
    requests==2.32.3
    sounddevice==0.5.1
    typing_extensions==4.13.2
    urllib3==2.4.0
    websocket-client==1.8.0
    
  4. Install the packages:

    pip install -r requirements.txt
    
  5. For the recommended keyless authentication with Microsoft Entra ID, install the azure-identity package with:

    pip install azure-identity
    

Retrieve resource information

Create a new file named .env in the folder where you want to run the code.

In the .env file, add the following environment variables for authentication:

AZURE_VOICE_LIVE_ENDPOINT=<your_endpoint>
VOICE_LIVE_MODEL=<your_model>
AZURE_VOICE_LIVE_API_VERSION=2025-05-01-preview
AZURE_VOICE_LIVE_API_KEY=<your_api_key> # Only required if using API key authentication

Replace the default values with your actual endpoint, model, API version, and API key.

Variable name Value
AZURE_VOICE_LIVE_ENDPOINT This value can be found in the Keys and Endpoint section when examining your resource from the Azure portal.
AZURE_VOICE_LIVE_MODEL The model you want to use. For example, gpt-4o or gpt-4o-mini-realtime-preview. For more information about models availability, see the Voice Live API overview documentation.
AZURE_VOICE_LIVE_API_VERSION The API version you want to use. For example, 2025-05-01-preview.

Learn more about keyless authentication and setting environment variables.

Start a conversation

The sample code in this quickstart uses Microsoft Entra ID for the recommended keyless authentication. If you prefer to use an API key, you can set the api_key variable instead of the token variable.

client = AzureVoiceLive(
    azure_endpoint = endpoint,
    api_version = api_version,
    token = token.token,
    # api_key = api_key,
)
  1. Create the voice-live-quickstart.py file with the following code:

    #Speech example to test the Azure Voice Live API
    import os
    import uuid
    import json
    import time
    import base64
    import logging
    import threading
    import numpy as np
    import sounddevice as sd
    import queue
    import signal
    import sys
    
    from collections import deque
    from dotenv import load_dotenv
    from azure.core.credentials import TokenCredential
    from azure.identity import DefaultAzureCredential
    from typing import Dict, Union, Literal, Set
    from typing_extensions import Iterator, TypedDict, Required
    import websocket
    from websocket import WebSocketApp
    from datetime import datetime
    
    # Global variables for thread coordination
    stop_event = threading.Event()
    connection_queue = queue.Queue()
    
    # This is the main function to run the Voice Live API client.
    def main() -> None: 
        # Set environment variables or edit the corresponding values here.
        endpoint = os.environ.get("AZURE_VOICE_LIVE_ENDPOINT") or "<https://your-endpoint.azure.com/>"
        model = os.environ.get("AZURE_VOICE_LIVE_MODEL") or "<your_model>"
        api_version = os.environ.get("AZURE_VOICE_LIVE_API_VERSION") or "2025-05-01-preview"
        api_key = os.environ.get("AZURE_VOICE_LIVE_API_KEY") or "<your_api_key>"
    
        # For the recommended keyless authentication, get and
        # use the Microsoft Entra token instead of api_key:
        credential = DefaultAzureCredential()
        scopes = "https://ai.azure.com/.default"
        token = credential.get_token(scopes)
    
        client = AzureVoiceLive(
            azure_endpoint = endpoint,
            api_version = api_version,
            token = token.token,
            # api_key = api_key,
        )
    
        connection = client.connect(model = model)
    
        session_update = {
            "type": "session.update",
            "session": {
                "instructions": "You are a helpful AI assistant responding in natural, engaging language.",
                "turn_detection": {
                    "type": "azure_semantic_vad",
                    "threshold": 0.3,
                    "prefix_padding_ms": 200,
                    "silence_duration_ms": 200,
                    "remove_filler_words": False,
                    "end_of_utterance_detection": {
                        "model": "semantic_detection_v1",
                        "threshold": 0.01,
                        "timeout": 2,
                    },
                },
                "input_audio_noise_reduction": {
                    "type": "azure_deep_noise_suppression"
                },
                "input_audio_echo_cancellation": {
                    "type": "server_echo_cancellation"
                },
                "voice": {
                    "name": "en-US-Ava:DragonHDLatestNeural",
                    "type": "azure-standard",
                    "temperature": 0.8,
                },
            },
            "event_id": ""
        }
        connection.send(json.dumps(session_update))
        print("Session created: ", json.dumps(session_update))
    
        # Create and start threads
        send_thread = threading.Thread(target=listen_and_send_audio, args=(connection,))
        receive_thread = threading.Thread(target=receive_audio_and_playback, args=(connection,))
        keyboard_thread = threading.Thread(target=read_keyboard_and_quit)
    
        print("Starting the chat ...")
    
        send_thread.start()
        receive_thread.start()
        keyboard_thread.start()
    
        # Wait for any thread to complete (usually the keyboard thread when user quits)
        keyboard_thread.join()
    
        # Signal other threads to stop
        stop_event.set()
    
        # Wait for other threads to finish
        send_thread.join(timeout=2)
        receive_thread.join(timeout=2)
    
        connection.close()
        print("Chat done.")
    
    # --- End of Main Function ---
    
    logger = logging.getLogger(__name__)
    AUDIO_SAMPLE_RATE = 24000
    
    class VoiceLiveConnection:
        def __init__(self, url: str, headers: dict) -> None:
            self._url = url
            self._headers = headers
            self._ws = None
            self._message_queue = queue.Queue()
            self._connected = False
    
        def connect(self) -> None:
            def on_message(ws, message):
                self._message_queue.put(message)
    
            def on_error(ws, error):
                logger.error(f"WebSocket error: {error}")
    
            def on_close(ws, close_status_code, close_msg):
                logger.info("WebSocket connection closed")
                self._connected = False
    
            def on_open(ws):
                logger.info("WebSocket connection opened")
                self._connected = True
    
            self._ws = websocket.WebSocketApp(
                self._url,
                header=self._headers,
                on_message=on_message,
                on_error=on_error,
                on_close=on_close,
                on_open=on_open
            )
    
            # Start WebSocket in a separate thread
            self._ws_thread = threading.Thread(target=self._ws.run_forever)
            self._ws_thread.daemon = True
            self._ws_thread.start()
    
            # Wait for connection to be established
            timeout = 10  # seconds
            start_time = time.time()
            while not self._connected and time.time() - start_time < timeout:
                time.sleep(0.1)
    
            if not self._connected:
                raise ConnectionError("Failed to establish WebSocket connection")
    
        def recv(self) -> str:
            try:
                return self._message_queue.get(timeout=1)
            except queue.Empty:
                return None
    
        def send(self, message: str) -> None:
            if self._ws and self._connected:
                self._ws.send(message)
    
        def close(self) -> None:
            if self._ws:
                self._ws.close()
                self._connected = False
    
    class AzureVoiceLive:
        def __init__(
            self,
            *,
            azure_endpoint: str | None = None,
            api_version: str | None = None,
            token: str | None = None,
            api_key: str | None = None,
        ) -> None:
    
            self._azure_endpoint = azure_endpoint
            self._api_version = api_version
            self._token = token
            self._api_key = api_key
            self._connection = None
    
        def connect(self, model: str) -> VoiceLiveConnection:
            if self._connection is not None:
                raise ValueError("Already connected to the Voice Live API.")
            if not model:
                raise ValueError("Model name is required.")
    
            azure_ws_endpoint = self._azure_endpoint.rstrip('/').replace("https://", "wss://")
    
            url = f"{azure_ws_endpoint}/voice-live/realtime?api-version={self._api_version}&model={model}"
    
            auth_header = {"Authorization": f"Bearer {self._token}"} if self._token else {"api-key": self._api_key}
            request_id = uuid.uuid4()
            headers = {"x-ms-client-request-id": str(request_id), **auth_header}
    
            self._connection = VoiceLiveConnection(url, headers)
            self._connection.connect()
            return self._connection
    
    class AudioPlayerAsync:
        def __init__(self):
            self.queue = deque()
            self.lock = threading.Lock()
            self.stream = sd.OutputStream(
                callback=self.callback,
                samplerate=AUDIO_SAMPLE_RATE,
                channels=1,
                dtype=np.int16,
                blocksize=2400,
            )
            self.playing = False
    
        def callback(self, outdata, frames, time, status):
            if status:
                logger.warning(f"Stream status: {status}")
            with self.lock:
                data = np.empty(0, dtype=np.int16)
                while len(data) < frames and len(self.queue) > 0:
                    item = self.queue.popleft()
                    frames_needed = frames - len(data)
                    data = np.concatenate((data, item[:frames_needed]))
                    if len(item) > frames_needed:
                        self.queue.appendleft(item[frames_needed:])
                if len(data) < frames:
                    data = np.concatenate((data, np.zeros(frames - len(data), dtype=np.int16)))
            outdata[:] = data.reshape(-1, 1)
    
        def add_data(self, data: bytes):
            with self.lock:
                np_data = np.frombuffer(data, dtype=np.int16)
                self.queue.append(np_data)
                if not self.playing and len(self.queue) > 0:
                    self.start()
    
        def start(self):
            if not self.playing:
                self.playing = True
                self.stream.start()
    
        def stop(self):
            with self.lock:
                self.queue.clear()
            self.playing = False
            self.stream.stop()
    
        def terminate(self):
            with self.lock:
                self.queue.clear()
            self.stream.stop()
            self.stream.close()
    
    def listen_and_send_audio(connection: VoiceLiveConnection) -> None:
        logger.info("Starting audio stream ...")
    
        stream = sd.InputStream(channels=1, samplerate=AUDIO_SAMPLE_RATE, dtype="int16")
        try:
            stream.start()
            read_size = int(AUDIO_SAMPLE_RATE * 0.02)
            while not stop_event.is_set():
                if stream.read_available >= read_size:
                    data, _ = stream.read(read_size)
                    audio = base64.b64encode(data).decode("utf-8")
                    param = {"type": "input_audio_buffer.append", "audio": audio, "event_id": ""}
                    # print("sending - ", param)
                    data_json = json.dumps(param)
                    connection.send(data_json)
                else:
                    time.sleep(0.001)  # Small sleep to prevent busy waiting
        except Exception as e:
            logger.error(f"Audio stream interrupted. {e}")
        finally:
            stream.stop()
            stream.close()
            logger.info("Audio stream closed.")
    
    def receive_audio_and_playback(connection: VoiceLiveConnection) -> None:
        last_audio_item_id = None
        audio_player = AudioPlayerAsync()
    
        logger.info("Starting audio playback ...")
        try:
            while not stop_event.is_set():
                raw_event = connection.recv()
                if raw_event is None:
                    continue
    
                try:
                    event = json.loads(raw_event)
                    print(f"Received event:", {event.get("type")})
    
                    if event.get("type") == "session.created":
                        session = event.get("session")
                        logger.info(f"Session created: {session.get('id')}")
    
                    elif event.get("type") == "response.audio.delta":
                        if event.get("item_id") != last_audio_item_id:
                            last_audio_item_id = event.get("item_id")
    
                        bytes_data = base64.b64decode(event.get("delta", ""))
                        if bytes_data:
                            logger.debug(f"Received audio data of length: {len(bytes_data)}")   
                        audio_player.add_data(bytes_data)
    
                    elif event.get("type") == "input_audio_buffer.speech_started":
                        print("Speech started")
                        audio_player.stop()
    
                    elif event.get("type") == "error":
                        error_details = event.get("error", {})
                        error_type = error_details.get("type", "Unknown")
                        error_code = error_details.get("code", "Unknown")
                        error_message = error_details.get("message", "No message provided")
                        raise ValueError(f"Error received: Type={error_type}, Code={error_code}, Message={error_message}")
                except json.JSONDecodeError as e:
                    logger.error(f"Failed to parse JSON event: {e}")
                    continue
    
        except Exception as e:
            logger.error(f"Error in audio playback: {e}")
        finally:
            audio_player.terminate()
            logger.info("Playback done.")
    
    def read_keyboard_and_quit() -> None:
        print("Press 'q' and Enter to quit the chat.")
        while not stop_event.is_set():
            try:
                user_input = input()
                if user_input.strip().lower() == 'q':
                    print("Quitting the chat...")
                    stop_event.set()
                    break
            except EOFError:
                # Handle case where input is interrupted
                break
    
    if __name__ == "__main__":
        try:
            # Change to the directory where this script is located
            os.chdir(os.path.dirname(os.path.abspath(__file__)))
            # Add folder for logging
            if not os.path.exists('logs'):
                os.makedirs('logs')
            # Add timestamp for logfiles
            timestamp = datetime.now().strftime("%Y-%m-%d_%H-%M-%S")
            # Set up logging
            logging.basicConfig(
                filename=f'logs/{timestamp}_voicelive.log',
                filemode="w",
                level=logging.DEBUG,
                format='%(asctime)s:%(name)s:%(levelname)s:%(message)s'
            )
            # Load environment variables from .env file
            load_dotenv("./.env", override=True)
    
            # Set up signal handler for graceful shutdown
            def signal_handler(signum, frame):
                print("\nReceived interrupt signal, shutting down...")
                stop_event.set()
                sys.exit(0)
    
            signal.signal(signal.SIGINT, signal_handler)
            signal.signal(signal.SIGTERM, signal_handler)
    
            main()
        except Exception as e:
            print(f"Error: {e}")
            stop_event.set()
    
  2. Sign in to Azure with the following command:

    az login
    
  3. Run the Python file.

    python voice-live-quickstart.py
    
  4. The Voice Live API starts to return audio with the model's initial response. You can interrupt the model by speaking. Enter "q" to quit the conversation.

Output

The output of the script is printed to the console. You see messages indicating the status of the connection, audio stream, and playback. The audio is played back through your speakers or headphones.

Session created:  {"type": "session.update", "session": {"instructions": "You are a helpful AI assistant responding in natural, engaging language.","turn_detection": {"type": "azure_semantic_vad", "threshold": 0.3, "prefix_padding_ms": 200, "silence_duration_ms": 200, "remove_filler_words": false, "end_of_utterance_detection": {"model": "semantic_detection_v1", "threshold": 0.1, "timeout": 4}}, "input_audio_noise_reduction": {"type": "azure_deep_noise_suppression"}, "input_audio_echo_cancellation": {"type": "server_echo_cancellation"}, "voice": {"name": "en-US-Ava:DragonHDLatestNeural", "type": "azure-standard", "temperature": 0.8}}, "event_id": ""}
Starting the chat ...
Received event: {'session.created'}
Press 'q' and Enter to quit the chat.
Received event: {'session.updated'}
Received event: {'input_audio_buffer.speech_started'}
Received event: {'input_audio_buffer.speech_stopped'}
Received event: {'input_audio_buffer.committed'}
Received event: {'conversation.item.input_audio_transcription.completed'}
Received event: {'conversation.item.created'}
Received event: {'response.created'}
Received event: {'response.output_item.added'}
Received event: {'conversation.item.created'}
Received event: {'response.content_part.added'}
Received event: {'response.audio_transcript.delta'}
Received event: {'response.audio_transcript.delta'}
Received event: {'response.audio_transcript.delta'}
REDACTED FOR BREVITY
Received event: {'response.audio.delta'}
Received event: {'response.audio.delta'}
Received event: {'response.audio.delta'}
q
Received event: {'response.audio.delta'}
Received event: {'response.audio.delta'}
Received event: {'response.audio.delta'}
Received event: {'response.audio.delta'}
Received event: {'response.audio.delta'}
Quitting the chat...
Received event: {'response.audio.delta'}
Received event: {'response.audio.delta'}
REDACTED FOR BREVITY
Received event: {'response.audio.delta'}
Received event: {'response.audio.delta'}
Chat done.

The script that you ran creates a log file named <timestamp>_voicelive.log in the logs folder.

logging.basicConfig(
    filename=f'logs/{timestamp}_voicelive.log',
    filemode="w",
    level=logging.DEBUG,
    format='%(asctime)s:%(name)s:%(levelname)s:%(message)s'
)

The log file contains information about the connection to the Voice Live API, including the request and response data. You can view the log file to see the details of the conversation.

2025-05-09 06:56:06,821:websockets.client:DEBUG:= connection is CONNECTING
2025-05-09 06:56:07,101:websockets.client:DEBUG:> GET /voice-live/realtime?api-version=2025-05-01-preview&model=gpt-4o HTTP/1.1
<REDACTED FOR BREVITY>
2025-05-09 06:56:07,551:websockets.client:DEBUG:= connection is OPEN
2025-05-09 06:56:07,551:websockets.client:DEBUG:< TEXT '{"event_id":"event_5a7NVdtNBVX9JZVuPc9nYK","typ...es":null,"agent":null}}' [1475 bytes]
2025-05-09 06:56:07,552:websockets.client:DEBUG:> TEXT '{"type": "session.update", "session": {"turn_de....8}}, "event_id": null}' [551 bytes]
2025-05-09 06:56:07,557:__main__:INFO:Starting audio stream ...
2025-05-09 06:56:07,810:websockets.client:DEBUG:> TEXT '{"type": "input_audio_buffer.append", "audio": ...AAAEA", "event_id": ""}' [1346 bytes]
2025-05-09 06:56:07,824:websockets.client:DEBUG:> TEXT '{"type": "input_audio_buffer.append", "audio": ...AAAAA", "event_id": ""}' [1346 bytes]
2025-05-09 06:56:07,844:websockets.client:DEBUG:> TEXT '{"type": "input_audio_buffer.append", "audio": ...AAAAA", "event_id": ""}' [1346 bytes]
2025-05-09 06:56:07,874:websockets.client:DEBUG:> TEXT '{"type": "input_audio_buffer.append", "audio": ...AAAAA", "event_id": ""}' [1346 bytes]
2025-05-09 06:56:07,874:websockets.client:DEBUG:> TEXT '{"type": "input_audio_buffer.append", "audio": ...AAAEA", "event_id": ""}' [1346 bytes]
2025-05-09 06:56:07,905:websockets.client:DEBUG:> TEXT '{"type": "input_audio_buffer.append", "audio": ...BAAAA", "event_id": ""}' [1346 bytes]
2025-05-09 06:56:07,926:websockets.client:DEBUG:> TEXT '{"type": "input_audio_buffer.append", "audio": ...AAAAA", "event_id": ""}' [1346 bytes]
2025-05-09 06:56:07,954:websockets.client:DEBUG:> TEXT '{"type": "input_audio_buffer.append", "audio": ...AAAAA", "event_id": ""}' [1346 bytes]
2025-05-09 06:56:07,954:websockets.client:DEBUG:> TEXT '{"type": "input_audio_buffer.append", "audio": ...///7/", "event_id": ""}' [1346 bytes]
2025-05-09 06:56:07,974:websockets.client:DEBUG:> TEXT '{"type": "input_audio_buffer.append", "audio": ...BAAAA", "event_id": ""}' [1346 bytes]
2025-05-09 06:56:08,004:websockets.client:DEBUG:> TEXT '{"type": "input_audio_buffer.append", "audio": ...AAAAA", "event_id": ""}' [1346 bytes]
2025-05-09 06:56:08,035:websockets.client:DEBUG:> TEXT '{"type": "input_audio_buffer.append", "audio": ...AAAAA", "event_id": ""}' [1346 bytes]
2025-05-09 06:56:08,035:websockets.client:DEBUG:> TEXT '{"type": "input_audio_buffer.append", "audio": ...AAAAA", "event_id": ""}' [1346 bytes]
<REDACTED FOR BREVITY>
2025-05-09 06:56:42,957:websockets.client:DEBUG:> TEXT '{"type": "input_audio_buffer.append", "audio": ...AAP//", "event_id": ""}' [1346 bytes]
2025-05-09 06:56:42,984:websockets.client:DEBUG:> TEXT '{"type": "input_audio_buffer.append", "audio": ...+/wAA", "event_id": ""}' [1346 bytes]
2025-05-09 06:56:43,005:websockets.client:DEBUG:> TEXT '{"type": "input_audio_buffer.append", "audio": .../////", "event_id": ""}' [1346 bytes]
2025-05-09 06:56:43,034:websockets.client:DEBUG:> TEXT '{"type": "input_audio_buffer.append", "audio": ...+////", "event_id": ""}' [1346 bytes]
2025-05-09 06:56:43,034:websockets.client:DEBUG:> TEXT '{"type": "input_audio_buffer.append", "audio": ...CAAMA", "event_id": ""}' [1346 bytes]
2025-05-09 06:56:43,055:websockets.client:DEBUG:> TEXT '{"type": "input_audio_buffer.append", "audio": ...CAAIA", "event_id": ""}' [1346 bytes]
2025-05-09 06:56:43,084:websockets.client:DEBUG:> TEXT '{"type": "input_audio_buffer.append", "audio": ...BAAEA", "event_id": ""}' [1346 bytes]
2025-05-09 06:56:43,114:websockets.client:DEBUG:> TEXT '{"type": "input_audio_buffer.append", "audio": ...9//3/", "event_id": ""}' [1346 bytes]
2025-05-09 06:56:43,114:websockets.client:DEBUG:> TEXT '{"type": "input_audio_buffer.append", "audio": ...DAAMA", "event_id": ""}' [1346 bytes]
2025-05-09 06:56:43,134:websockets.client:DEBUG:> TEXT '{"type": "input_audio_buffer.append", "audio": ...BAAIA", "event_id": ""}' [1346 bytes]
2025-05-09 06:56:43,165:websockets.client:DEBUG:> TEXT '{"type": "input_audio_buffer.append", "audio": ...AAAAA", "event_id": ""}' [1346 bytes]
2025-05-09 06:56:43,184:websockets.client:DEBUG:> TEXT '{"type": "input_audio_buffer.append", "audio": ...+//7/", "event_id": ""}' [1346 bytes]
2025-05-09 06:56:43,214:websockets.client:DEBUG:> TEXT '{"type": "input_audio_buffer.append", "audio": .../////", "event_id": ""}' [1346 bytes]
2025-05-09 06:56:43,214:websockets.client:DEBUG:> TEXT '{"type": "input_audio_buffer.append", "audio": ...+/wAA", "event_id": ""}' [1346 bytes]
2025-05-09 06:56:43,245:websockets.client:DEBUG:> TEXT '{"type": "input_audio_buffer.append", "audio": ...BAAIA", "event_id": ""}' [1346 bytes]
2025-05-09 06:56:43,264:websockets.client:DEBUG:> TEXT '{"type": "input_audio_buffer.append", "audio": ...AAP//", "event_id": ""}' [1346 bytes]
2025-05-09 06:56:43,295:websockets.client:DEBUG:> TEXT '{"type": "input_audio_buffer.append", "audio": ...BAAEA", "event_id": ""}' [1346 bytes]
2025-05-09 06:56:43,295:websockets.client:DEBUG:> CLOSE 1000 (OK) [2 bytes]
2025-05-09 06:56:43,297:websockets.client:DEBUG:= connection is CLOSING
2025-05-09 06:56:43,346:__main__:INFO:Audio stream closed.
2025-05-09 06:56:43,388:__main__:INFO:Playback done.
2025-05-09 06:56:44,512:websockets.client:DEBUG:< CLOSE 1000 (OK) [2 bytes]
2025-05-09 06:56:44,514:websockets.client:DEBUG:< EOF
2025-05-09 06:56:44,514:websockets.client:DEBUG:> EOF
2025-05-09 06:56:44,514:websockets.client:DEBUG:= connection is CLOSED
2025-05-09 06:56:44,514:websockets.client:DEBUG:x closing TCP connection
2025-05-09 06:56:44,514:asyncio:ERROR:Unclosed client session
client_session: <aiohttp.client.ClientSession object at 0x00000266DD8E5400>