Blog Post

AI - Azure AI services Blog
19 MIN READ

From Zero to Hero: Building Your First Voice Bot with GPT-4o Real-Time API using Python

mrajguru's avatar
mrajguru
Icon for Microsoft rankMicrosoft
Oct 12, 2024

Voice technology is transforming how we interact with machines, making conversations with AI feel more natural than ever before. With the public beta release of the Realtime API powered by GPT-4o, developers now have the tools to create low-latency, multimodal voice experiences in their apps, opening up endless possibilities for innovation.

 

Gone are the days when building a voice bot required stitching together multiple models for transcription, inference, and text-to-speech conversion. With the Realtime API, developers can now streamline the entire process with a single API call, enabling fluid, natural speech-to-speech conversations. This is a game-changer for industries like customer support, education, and real-time language translation, where fast, seamless interactions are crucial.

 

 

In this blog, we’ll guide you through the process of building your first real-time voice bot from scratch using the GPT-4o Realtime Model. We’ll cover key features of the Realtime API, how to set up a WebSocket connection for voice streaming, and how to leverage the API’s ability to handle interruptions and make function calls. By the end, you’ll be ready to create a voice bot that responds to users with near-human accuracy and emotion. Whether you're a beginner or an experienced developer, this blueprint will help you get started with creating immersive voice interactions that are both responsive and engaging. Ready to dive in? Let’s get started!

 

Key Features

 

  • Low-Latency Streaming: Enables real-time audio input and output, facilitating natural and seamless conversations.
  • Multimodal Support: Handles both text and audio inputs and outputs, allowing for versatile interaction modes.
  • Preset Voices: Supports six predefined voices, ensuring quality and consistency in responses.
  • Function Calling: Allows the voice assistant to perform actions or retrieve context-specific information dynamically.
  • Safety and Privacy: Incorporates multiple layers of safety protections, including automated monitoring and adherence to privacy policies.

 

How GPT-4o Realtime API Works

 

 Traditionally, building a voice assistant required chaining together several models: an automatic speech recognition (ASR) model like Whisper for transcribing audio, a text-based model for processing responses, and a text-to-speech (TTS) model for generating audio outputs. This multi-step process often led to delays and a loss of emotional nuance.

The GPT-4o Realtime API revolutionizes this by consolidating these functionalities into a single API call. By establishing a persistent WebSocket connection, developers can stream audio inputs and outputs directly, significantly reducing latency and enhancing the naturalness of conversations. Additionally, the API's function calling capability allows the voice bot to perform actions such as placing orders or retrieving customer information on the fly.

 

 

 

 

 

 

Building Your Real-Time Voice Bot

 
Let's dive into the step-by-step process of building your own real-time voice bot using the GPT-4o Realtime API.

Prerequisites

 
Before you begin, ensure you have the following:

  • Azure Subscription: Create one for free.
  • Azure OpenAI Resource: Set up in a supported region (East US 2 or Sweden Central).
  • Development Environment: Familiarity with Python and basic asynchronous programming.
  • Client Libraries: Tools like LiveKit, Agora, or Twilio can enhance your bot's capabilities.

 

Setting Up the API

 

  1. Deploy the GPT-4o Realtime Model:
    • Navigate to the Azure AI Studio.
    • Access the Model Catalog and search for gpt-4o-realtime-preview.
    • Deploy the model by selecting your Azure OpenAI resource and configuring the deployment settings.
  2. Configure Audio Input and Output:
    • The API supports various audio formats, primarily pcm16.
    • Set up your client to handle audio streaming, ensuring compatibility with the API's requirements.

 

This project demonstrates how to build a sophisticated real-time conversational AI system using Azure OpenAI. By leveraging WebSocket connections and an event-driven architecture, the system provides responsive and context-aware customer support in any language. This approach can be adapted to various languages and use cases, making it a versatile solution for businesses looking to enhance their customer service capabilities. The project consists of three main components:

  • Realtime API: Handles WebSocket connections to Azure OpenAI's real-time API.
  • Tools: Defines various customer support functions like checking order status, processing returns, and more.
  • Application: Manages the interaction flow and integrates the real-time client with UI Layer.

 

Environment Setup

 

Create an .env file and update the following environment variables:

 

 

 

AZURE_OPENAI_API_KEY=XXXX
# replace with your Azure OpenAI API Key

AZURE_OPENAI_ENDPOINT=https://xxxx.openai.azure.com/
# replace with your Azure OpenAI Endpoint

AZURE_OPENAI_DEPLOYMENT=gpt-4o-realtime-preview
#Create a deployment for the gpt-4o-realtime-preview model and place the deployment name here. You can name the deployment as per your choice and put the name here.

AZURE_OPENAI_CHAT_DEPLOYMENT_VERSION=2024-10-01-preview
#You don't need to change this unless you are willing to try other versions.

 

 

 

requirements.txt

 

 

 

chainlit==1.3.0rc1
openai
beautifulsoup4
lxml
python-dotenv
websockets
aiohttp

 

 

 

 

Implementing the Realtime Client

 
The heartbeat of your voice bot is the Realtime Client, which manages the WebSocket connection and handles communication with the GPT-4o Realtime API. The RealtimeAPI class is responsible for managing WebSocket connections to OpenAI's real-time API. It handles sending and receiving messages, dispatching events, and maintaining the connection state.

Key features include:
 
  • Connection Management: Establishes and maintains a WebSocket connection. 
  • Event Dispatching: Uses an event-driven architecture to handle incoming and outgoing messages.
  • Audio Processing: Convert audio inputs from base64 to array buffers and vice versa using utility functions. Manage audio streams efficiently to ensure minimal latency and high-quality voice interactions.


Key Components:

  • RealtimeAPI Class:
    • Establishes and maintains the WebSocket connection.
    • Handles sending and receiving messages.
    • Manages event dispatching for various conversation events.

 

 

 

class RealtimeAPI(RealtimeEventHandler):
    def __init__(self):
        super().__init__()
        self.default_url = 'wss://api.openai.com/v1/realtime'
        self.url = os.environ["AZURE_OPENAI_ENDPOINT"]
        self.api_key = os.environ["AZURE_OPENAI_API_KEY"]
        self.api_version = "2024-10-01-preview"
        self.azure_deployment = os.environ["AZURE_OPENAI_DEPLOYMENT"]
        self.ws = None

    def is_connected(self):
        return self.ws is not None

    def log(self, *args):
        logger.debug(f"[Websocket/{datetime.utcnow().isoformat()}]", *args)

    async def connect(self, model='gpt-4o-realtime-preview'):
        if self.is_connected():
            raise Exception("Already connected")
        self.ws = await websockets.connect(f"{self.url}/openai/realtime?api-version={self.api_version}&deployment={model}&api-key={self.api_key}", extra_headers={
            'Authorization': f'Bearer {self.api_key}',
            'OpenAI-Beta': 'realtime=v1'
        })
        self.log(f"Connected to {self.url}")
        asyncio.create_task(self._receive_messages())

    async def _receive_messages(self):
        async for message in self.ws:
            event = json.loads(message)
            if event['type'] == "error":
                logger.error("ERROR", message)
            self.log("received:", event)
            self.dispatch(f"server.{event['type']}", event)
            self.dispatch("server.*", event)

    async def send(self, event_name, data=None):
        if not self.is_connected():
            raise Exception("RealtimeAPI is not connected")
        data = data or {}
        if not isinstance(data, dict):
            raise Exception("data must be a dictionary")
        event = {
            "event_id": self._generate_id("evt_"),
            "type": event_name,
            **data
        }
        self.dispatch(f"client.{event_name}", event)
        self.dispatch("client.*", event)
        self.log("sent:", event)
        await self.ws.send(json.dumps(event))

    def _generate_id(self, prefix):
        return f"{prefix}{int(datetime.utcnow().timestamp() * 1000)}"

    async def disconnect(self):
        if self.ws:
            await self.ws.close()
            self.ws = None
            self.log(f"Disconnected from {self.url}")

 

 

 

 

Reference: init.py 

 

  • RealtimeConversation Class:
    • Manages the state of the conversation.
    • Processes different types of events, such as message creation, transcription completion, and audio streaming.
    • Queues and formats audio and text data for seamless interaction.

 

 

 

class RealtimeConversation:
    default_frequency = config.features.audio.sample_rate
    
    EventProcessors = {
        'conversation.item.created': lambda self, event: self._process_item_created(event),
        'conversation.item.truncated': lambda self, event: self._process_item_truncated(event),
        'conversation.item.deleted': lambda self, event: self._process_item_deleted(event),
        'conversation.item.input_audio_transcription.completed': lambda self, event: self._process_input_audio_transcription_completed(event),
        'input_audio_buffer.speech_started': lambda self, event: self._process_speech_started(event),
        'input_audio_buffer.speech_stopped': lambda self, event, input_audio_buffer: self._process_speech_stopped(event, input_audio_buffer),
        'response.created': lambda self, event: self._process_response_created(event),
        'response.output_item.added': lambda self, event: self._process_output_item_added(event),
        'response.output_item.done': lambda self, event: self._process_output_item_done(event),
        'response.content_part.added': lambda self, event: self._process_content_part_added(event),
        'response.audio_transcript.delta': lambda self, event: self._process_audio_transcript_delta(event),
        'response.audio.delta': lambda self, event: self._process_audio_delta(event),
        'response.text.delta': lambda self, event: self._process_text_delta(event),
        'response.function_call_arguments.delta': lambda self, event: self._process_function_call_arguments_delta(event),
    }
    
    def __init__(self):
        self.clear()

    def clear(self):
        self.item_lookup = {}
        self.items = []
        self.response_lookup = {}
        self.responses = []
        self.queued_speech_items = {}
        self.queued_transcript_items = {}
        self.queued_input_audio = None

    def queue_input_audio(self, input_audio):
        self.queued_input_audio = input_audio

    def process_event(self, event, *args):
        event_processor = self.EventProcessors.get(event['type'])
        if not event_processor:
            raise Exception(f"Missing conversation event processor for {event['type']}")
        return event_processor(self, event, *args)

    def get_item(self, id):
        return self.item_lookup.get(id)

    def get_items(self):
        return self.items[:]

    def _process_item_created(self, event):
        item = event['item']
        new_item = item.copy()
        if new_item['id'] not in self.item_lookup:
            self.item_lookup[new_item['id']] = new_item
            self.items.append(new_item)
        new_item['formatted'] = {
            'audio': [],
            'text': '',
            'transcript': ''
        }
        if new_item['id'] in self.queued_speech_items:
            new_item['formatted']['audio'] = self.queued_speech_items[new_item['id']]['audio']
            del self.queued_speech_items[new_item['id']]
        if 'content' in new_item:
            text_content = [c for c in new_item['content'] if c['type'] in ['text', 'input_text']]
            for content in text_content:
                new_item['formatted']['text'] += content['text']
        if new_item['id'] in self.queued_transcript_items:
            new_item['formatted']['transcript'] = self.queued_transcript_items[new_item['id']]['transcript']
            del self.queued_transcript_items[new_item['id']]
        if new_item['type'] == 'message':
            if new_item['role'] == 'user':
                new_item['status'] = 'completed'
                if self.queued_input_audio:
                    new_item['formatted']['audio'] = self.queued_input_audio
                    self.queued_input_audio = None
            else:
                new_item['status'] = 'in_progress'
        elif new_item['type'] == 'function_call':
            new_item['formatted']['tool'] = {
                'type': 'function',
                'name': new_item['name'],
                'call_id': new_item['call_id'],
                'arguments': ''
            }
            new_item['status'] = 'in_progress'
        elif new_item['type'] == 'function_call_output':
            new_item['status'] = 'completed'
            new_item['formatted']['output'] = new_item['output']
        return new_item, None

    def _process_item_truncated(self, event):
        item_id = event['item_id']
        audio_end_ms = event['audio_end_ms']
        item = self.item_lookup.get(item_id)
        if not item:
            raise Exception(f'item.truncated: Item "{item_id}" not found')
        end_index = (audio_end_ms * self.default_frequency) // 1000
        item['formatted']['transcript'] = ''
        item['formatted']['audio'] = item['formatted']['audio'][:end_index]
        return item, None

    def _process_item_deleted(self, event):
        item_id = event['item_id']
        item = self.item_lookup.get(item_id)
        if not item:
            raise Exception(f'item.deleted: Item "{item_id}" not found')
        del self.item_lookup[item['id']]
        self.items.remove(item)
        return item, None

    def _process_input_audio_transcription_completed(self, event):
        item_id = event['item_id']
        content_index = event['content_index']
        transcript = event['transcript']
        formatted_transcript = transcript or ' '
        item = self.item_lookup.get(item_id)
        if not item:
            self.queued_transcript_items[item_id] = {'transcript': formatted_transcript}
            return None, None
        item['content'][content_index]['transcript'] = transcript
        item['formatted']['transcript'] = formatted_transcript
        return item, {'transcript': transcript}

    def _process_speech_started(self, event):
        item_id = event['item_id']
        audio_start_ms = event['audio_start_ms']
        self.queued_speech_items[item_id] = {'audio_start_ms': audio_start_ms}
        return None, None

    def _process_speech_stopped(self, event, input_audio_buffer):
        item_id = event['item_id']
        audio_end_ms = event['audio_end_ms']
        speech = self.queued_speech_items[item_id]
        speech['audio_end_ms'] = audio_end_ms
        if input_audio_buffer:
            start_index = (speech['audio_start_ms'] * self.default_frequency) // 1000
            end_index = (speech['audio_end_ms'] * self.default_frequency) // 1000
            speech['audio'] = input_audio_buffer[start_index:end_index]
        return None, None

    def _process_response_created(self, event):
        response = event['response']
        if response['id'] not in self.response_lookup:
            self.response_lookup[response['id']] = response
            self.responses.append(response)
        return None, None

    def _process_output_item_added(self, event):
        response_id = event['response_id']
        item = event['item']
        response = self.response_lookup.get(response_id)
        if not response:
            raise Exception(f'response.output_item.added: Response "{response_id}" not found')
        response['output'].append(item['id'])
        return None, None

    def _process_output_item_done(self, event):
        item = event['item']
        if not item:
            raise Exception('response.output_item.done: Missing "item"')
        found_item = self.item_lookup.get(item['id'])
        if not found_item:
            raise Exception(f'response.output_item.done: Item "{item["id"]}" not found')
        found_item['status'] = item['status']
        return found_item, None

    def _process_content_part_added(self, event):
        item_id = event['item_id']
        part = event['part']
        item = self.item_lookup.get(item_id)
        if not item:
            raise Exception(f'response.content_part.added: Item "{item_id}" not found')
        item['content'].append(part)
        return item, None

    def _process_audio_transcript_delta(self, event):
        item_id = event['item_id']
        content_index = event['content_index']
        delta = event['delta']
        item = self.item_lookup.get(item_id)
        if not item:
            raise Exception(f'response.audio_transcript.delta: Item "{item_id}" not found')
        item['content'][content_index]['transcript'] += delta
        item['formatted']['transcript'] += delta
        return item, {'transcript': delta}

    def _process_audio_delta(self, event):
        item_id = event['item_id']
        content_index = event['content_index']
        delta = event['delta']
        item = self.item_lookup.get(item_id)
        if not item:
            logger.debug(f'response.audio.delta: Item "{item_id}" not found')
            return None, None
        array_buffer = base64_to_array_buffer(delta)
        append_values = array_buffer.tobytes()
        # TODO: make it work
        # item['formatted']['audio'] = merge_int16_arrays(item['formatted']['audio'], append_values)
        return item, {'audio': append_values}

    def _process_text_delta(self, event):
        item_id = event['item_id']
        content_index = event['content_index']
        delta = event['delta']
        item = self.item_lookup.get(item_id)
        if not item:
            raise Exception(f'response.text.delta: Item "{item_id}" not found')
        item['content'][content_index]['text'] += delta
        item['formatted']['text'] += delta
        return item, {'text': delta}

    def _process_function_call_arguments_delta(self, event):
        item_id = event['item_id']
        delta = event['delta']
        item = self.item_lookup.get(item_id)
        if not item:
            raise Exception(f'response.function_call_arguments.delta: Item "{item_id}" not found')
        item['arguments'] += delta
        item['formatted']['tool']['arguments'] += delta
        return item, {'arguments': delta}

 

 

 

 

  • RealtimeClient Class:
    • Initialization: Sets up system prompts, session configurations, and initializes RealtimeAPI and RealtimeConversation for managing WebSocket connections and conversation events.
    • Connection Management: Handles connecting and disconnecting from the server, waiting for session creation, and updating session settings.
    • Event Handling: Listens for and processes server and client events, dispatching them to appropriate handlers.
      Conversation Management: Manages creation, updating, and deletion of conversation items, including handling input audio and speech events.
    • Tool and Response Management: Supports adding/removing tools, invoking them based on events, sending user messages, creating responses, and managing audio content.

 

 

 

 

class RealtimeClient(RealtimeEventHandler):
    def __init__(self, system_prompt: str):
        super().__init__()
        self.system_prompt = system_prompt
        self.default_session_config = {
            "modalities": ["text", "audio"],
            "instructions": self.system_prompt,
            "voice": "shimmer",
            "input_audio_format": "pcm16",
            "output_audio_format": "pcm16",
            "input_audio_transcription": { "model": 'whisper-1' },
            "turn_detection": { "type": 'server_vad' },
            "tools": [],
            "tool_choice": "auto",
            "temperature": 0.8,
            "max_response_output_tokens": 4096,
        }
        self.session_config = {}
        self.transcription_models = [{"model": "whisper-1"}]
        self.default_server_vad_config = {
            "type": "server_vad",
            "threshold": 0.5,
            "prefix_padding_ms": 300,
            "silence_duration_ms": 200,
        }
        self.realtime = RealtimeAPI()
        self.conversation = RealtimeConversation()
        self._reset_config()
        self._add_api_event_handlers()
        
    def _reset_config(self):
        self.session_created = False
        self.tools = {}
        self.session_config = self.default_session_config.copy()
        self.input_audio_buffer = bytearray()
        return True

    def _add_api_event_handlers(self):
        self.realtime.on("client.*", self._log_event)
        self.realtime.on("server.*", self._log_event)
        self.realtime.on("server.session.created", self._on_session_created)
        self.realtime.on("server.response.created", self._process_event)
        self.realtime.on("server.response.output_item.added", self._process_event)
        self.realtime.on("server.response.content_part.added", self._process_event)
        self.realtime.on("server.input_audio_buffer.speech_started", self._on_speech_started)
        self.realtime.on("server.input_audio_buffer.speech_stopped", self._on_speech_stopped)
        self.realtime.on("server.conversation.item.created", self._on_item_created)
        self.realtime.on("server.conversation.item.truncated", self._process_event)
        self.realtime.on("server.conversation.item.deleted", self._process_event)
        self.realtime.on("server.conversation.item.input_audio_transcription.completed", self._process_event)
        self.realtime.on("server.response.audio_transcript.delta", self._process_event)
        self.realtime.on("server.response.audio.delta", self._process_event)
        self.realtime.on("server.response.text.delta", self._process_event)
        self.realtime.on("server.response.function_call_arguments.delta", self._process_event)
        self.realtime.on("server.response.output_item.done", self._on_output_item_done)

    def _log_event(self, event):
        realtime_event = {
            "time": datetime.utcnow().isoformat(),
            "source": "client" if event["type"].startswith("client.") else "server",
            "event": event,
        }
        self.dispatch("realtime.event", realtime_event)

    def _on_session_created(self, event):
        self.session_created = True

    def _process_event(self, event, *args):
        item, delta = self.conversation.process_event(event, *args)
        if item:
            self.dispatch("conversation.updated", {"item": item, "delta": delta})
        return item, delta

    def _on_speech_started(self, event):
        self._process_event(event)
        self.dispatch("conversation.interrupted", event)

    def _on_speech_stopped(self, event):
        self._process_event(event, self.input_audio_buffer)

    def _on_item_created(self, event):
        item, delta = self._process_event(event)
        self.dispatch("conversation.item.appended", {"item": item})
        if item and item["status"] == "completed":
            self.dispatch("conversation.item.completed", {"item": item})

    async def _on_output_item_done(self, event):
        item, delta = self._process_event(event)
        if item and item["status"] == "completed":
            self.dispatch("conversation.item.completed", {"item": item})
        if item and item.get("formatted", {}).get("tool"):
            await self._call_tool(item["formatted"]["tool"])

    async def _call_tool(self, tool):
        try:
            print(tool["arguments"])
            json_arguments = json.loads(tool["arguments"])
            tool_config = self.tools.get(tool["name"])
            if not tool_config:
                raise Exception(f'Tool "{tool["name"]}" has not been added')
            result = await tool_config["handler"](**json_arguments)
            await self.realtime.send("conversation.item.create", {
                "item": {
                    "type": "function_call_output",
                    "call_id": tool["call_id"],
                    "output": json.dumps(result),
                }
            })
        except Exception as e:
            logger.error(traceback.format_exc())
            await self.realtime.send("conversation.item.create", {
                "item": {
                    "type": "function_call_output",
                    "call_id": tool["call_id"],
                    "output": json.dumps({"error": str(e)}),
                }
            })
        await self.create_response()

    def is_connected(self):
        return self.realtime.is_connected()

    def reset(self):
        self.disconnect()
        self.realtime.clear_event_handlers()
        self._reset_config()
        self._add_api_event_handlers()
        return True

    async def connect(self):
        if self.is_connected():
            raise Exception("Already connected, use .disconnect() first")
        await self.realtime.connect()
        await self.update_session()
        return True

    async def wait_for_session_created(self):
        if not self.is_connected():
            raise Exception("Not connected, use .connect() first")
        while not self.session_created:
            await asyncio.sleep(0.001)
        return True

    async def disconnect(self):
        self.session_created = False
        self.conversation.clear()
        if self.realtime.is_connected():
            await self.realtime.disconnect()

    def get_turn_detection_type(self):
        return self.session_config.get("turn_detection", {}).get("type")

    async def add_tool(self, definition, handler):
        if not definition.get("name"):
            raise Exception("Missing tool name in definition")
        name = definition["name"]
        if name in self.tools:
            raise Exception(f'Tool "{name}" already added. Please use .removeTool("{name}") before trying to add again.')
        if not callable(handler):
            raise Exception(f'Tool "{name}" handler must be a function')
        self.tools[name] = {"definition": definition, "handler": handler}
        await self.update_session()
        return self.tools[name]

    def remove_tool(self, name):
        if name not in self.tools:
            raise Exception(f'Tool "{name}" does not exist, can not be removed.')
        del self.tools[name]
        return True

    async def delete_item(self, id):
        await self.realtime.send("conversation.item.delete", {"item_id": id})
        return True

    async def update_session(self, **kwargs):
        self.session_config.update(kwargs)
        use_tools = [
            {**tool_definition, "type": "function"}
            for tool_definition in self.session_config.get("tools", [])
        ] + [
            {**self.tools[key]["definition"], "type": "function"}
            for key in self.tools
        ]
        session = {**self.session_config, "tools": use_tools}
        if self.realtime.is_connected():
            await self.realtime.send("session.update", {"session": session})
        return True
    
    async def create_conversation_item(self, item):
        await self.realtime.send("conversation.item.create", {
            "item": item
        })

    async def send_user_message_content(self, content=[]):
        if content:
            for c in content:
                if c["type"] == "input_audio":
                    if isinstance(c["audio"], (bytes, bytearray)):
                        c["audio"] = array_buffer_to_base64(c["audio"])
            await self.realtime.send("conversation.item.create", {
                "item": {
                    "type": "message",
                    "role": "user",
                    "content": content,
                }
            })
        await self.create_response()
        return True

    async def append_input_audio(self, array_buffer):
        if len(array_buffer) > 0:
            await self.realtime.send("input_audio_buffer.append", {
                "audio": array_buffer_to_base64(np.array(array_buffer)),
            })
            self.input_audio_buffer.extend(array_buffer)
        return True

    async def create_response(self):
        if self.get_turn_detection_type() is None and len(self.input_audio_buffer) > 0:
            await self.realtime.send("input_audio_buffer.commit")
            self.conversation.queue_input_audio(self.input_audio_buffer)
            self.input_audio_buffer = bytearray()
        await self.realtime.send("response.create")
        return True

    async def cancel_response(self, id=None, sample_count=0):
        if not id:
            await self.realtime.send("response.cancel")
            return {"item": None}
        else:
            item = self.conversation.get_item(id)
            if not item:
                raise Exception(f'Could not find item "{id}"')
            if item["type"] != "message":
                raise Exception('Can only cancelResponse messages with type "message"')
            if item["role"] != "assistant":
                raise Exception('Can only cancelResponse messages with role "assistant"')
            await self.realtime.send("response.cancel")
            audio_index = next((i for i, c in enumerate(item["content"]) if c["type"] == "audio"), -1)
            if audio_index == -1:
                raise Exception("Could not find audio on item to cancel")
            await self.realtime.send("conversation.item.truncate", {
                "item_id": id,
                "content_index": audio_index,
                "audio_end_ms": int((sample_count / self.conversation.default_frequency) * 1000),
            })
            return {"item": item}

    async def wait_for_next_item(self):
        event = await self.wait_for_next("conversation.item.appended")
        return {"item": event["item"]}

    async def wait_for_next_completed_item(self):
        event = await self.wait_for_next("conversation.item.completed")
        return {"item": event["item"]}

 

 

 

 

Adding Tools and Handlers

 
Your voice bot's functionality can be extended by integrating various tools and handlers. These allow the bot to perform specific actions based on user inputs.

  1. Define Tool Definitions:
    • In tool.py, define the capabilities of your bot, such as checking order statuses, processing returns, or updating account information.
    • Each tool includes a name, description, and required parameters.
  2. Implement Handlers:
    • Create asynchronous handler functions for each tool to execute the desired actions.
    • These handlers interact with your backend systems or databases to fulfill user requests.
  3. Integrate Tools with the Realtime Client:
    • Register each tool and its handler with the RealtimeClient in your app.py file.
    • Ensure that the bot can invoke these tools dynamically during conversations.


Key Components:

  • Tool Definitions:
    • Structured descriptions of each tool, including the required parameters and functionalities.

Example:

 

 

 

 

# Function Definitions
check_order_status_def = {
    "name": "check_order_status",
    "description": "Check the status of a customer's order",
    "parameters": {
      "type": "object",
      "properties": {
        "customer_id": {
          "type": "string",
          "description": "The unique identifier for the customer"
        },
        "order_id": {
          "type": "string",
          "description": "The unique identifier for the order"
        }
      },
      "required": ["customer_id", "order_id"]
    }
}

 

 

 

 

  • Handler Functions:
    • Asynchronous functions that execute the logic for each tool.
    • Interact with external systems, databases, or perform specific actions based on user requests

Example:

 

 

 

 

async def check_order_status_handler(customer_id, order_id):
    status = "In Transit"
    
    # Your Business Logic
    estimated_delivery, status, order_date =  fetch_order_details(order_id, customer_id)
    # Read the HTML template
    with open('order_status_template.html', 'r') as file:
        html_content = file.read()

    # Replace placeholders with actual data
    html_content = html_content.format(
        order_id=order_id,
        customer_id=customer_id,
        order_date=order_date.strftime("%B %d, %Y"),
        estimated_delivery=estimated_delivery.strftime("%B %d, %Y"),
        status=status
    )

    # Return the Chainlit message with HTML content
    await cl.Message(content=f"Here is the detail of your order \n {html_content}").send()
    return f"Order {order_id} status for customer {customer_id}: {status}"
  

 

 

 

 

Reference:

 

Integrating with Your Application

 
With the Realtime Client and tools in place, it's time to weave everything into your application.

  1. Initialize OpenAI Realtime:
    • In app.py, set up the connection to the GPT-4o Realtime API using your system prompt and session configurations.
    • Manage user sessions and track interactions seamlessly.
  2. Handle User Interactions:
    • Implement event handlers for chat initiation, message reception, audio processing, and session termination.
    • Ensure that user inputs, whether text or voice, are appropriately processed and responded to in real-time.
  3. Manage Conversation Flow:
    • Utilize the RealtimeConversation class to handle conversation states, manage audio streams, and maintain context.
    • Implement logic to handle interruptions, cancellations, and dynamic responses based on user actions.


Key Components:

  • Initialization:
    • Sets up the OpenAI Realtime Client with the system prompt and configures tools.

 

 

 

 

system_prompt = """Provide helpful and empathetic support responses to customer inquiries for ShopMe in Hindi language, addressing their requests, concerns, or feedback professionally.

Maintain a friendly and service-oriented tone throughout the interaction to ensure a positive customer experience.

# Steps

1. **Identify the Issue:** Carefully read the customer's inquiry to understand the problem or question they are presenting.
2. **Gather Relevant Information:** Check for any additional data needed, such as order numbers or account details, while ensuring the privacy and security of the customer's information.
3. **Formulate a Response:** Develop a solution or informative response based on the understanding of the issue. The response should be clear, concise, and address all parts of the customer's concern.
4. **Offer Further Assistance:** Invite the customer to reach out again if they need more help or have additional questions.
5. **Close Politely:** End the conversation with a polite closing statement that reinforces the service commitment of ShopMe.

# Output Format

Provide a clear and concise paragraph addressing the customer's inquiry, including:
- Acknowledgment of their concern
- Suggested solution or response
- Offer for further assistance
- Polite closing

# Notes
- Greet user with Welcome to ShopMe For the first time only
- always speak in Hindi
- Ensure all customer data is handled according to relevant privacy and data protection laws and ShopMe's privacy policy.
- In cases of high sensitivity or complexity, escalate the issue to a human customer support agent.
- Keep responses within a reasonable length to ensure they are easy to read and understand."""

 

 

 

 

 

  • Event Handlers:
    • Manages chat start, message reception, audio streaming, and session termination events.

First we will i will initialize the real time client discussed before

 

 

 

 

async def setup_openai_realtime(system_prompt: str):
    """Instantiate and configure the OpenAI Realtime Client"""
    openai_realtime = RealtimeClient(system_prompt = system_prompt)
    cl.user_session.set("track_id", str(uuid4()))
    async def handle_conversation_updated(event):
        item = event.get("item")
        delta = event.get("delta")
        """Currently used to stream audio back to the client."""
        if delta:
            # Only one of the following will be populated for any given event
            if 'audio' in delta:
                audio = delta['audio']  # Int16Array, audio added
                await cl.context.emitter.send_audio_chunk(cl.OutputAudioChunk(mimeType="pcm16", data=audio, track=cl.user_session.get("track_id")))
            if 'transcript' in delta:
                transcript = delta['transcript']  # string, transcript added
                pass
            if 'arguments' in delta:
                arguments = delta['arguments']  # string, function arguments added
                pass
            
    async def handle_item_completed(item):
        """Used to populate the chat context with transcription once an item is completed."""
        # print(item) # TODO
        pass
    
    async def handle_conversation_interrupt(event):
        """Used to cancel the client previous audio playback."""
        cl.user_session.set("track_id", str(uuid4()))
        await cl.context.emitter.send_audio_interrupt()
        
    async def handle_error(event):
        logger.error(event)

 

 

 

 

 

  • Session Management:
    • Maintains user sessions, handles conversation interruptions, and ensures smooth interaction flow. As you see in the below code the idea is whenever you a receive an audio chunk you should call the real time client with the audio chunk.

 

 

 

if openai_realtime:            
    if openai_realtime.is_connected():
        await openai_realtime.append_input_audio(chunk.data)
    else:
        logger.info("RealtimeClient is not connected")

 

 

 

Reference: app.py 

Testing and Deployment

 
Once your voice bot is built, thorough testing is essential to ensure reliability and user satisfaction.

  1. Local Testing:
    • Use the AI Studio Real-time audio playground to interact with your deployed model.
    • Test various functionalities, including speech recognition, response generation, and tool execution.
  2. Integration Testing:
    • Ensure that your application seamlessly communicates with the Realtime API.
    • Test the event handlers and tool integrations to verify correct behavior under different scenarios.
  3. Deployment:
    • Deploy your application to a production environment, leveraging cloud services for scalability.
    • Monitor performance and make adjustments as needed to handle real-world usage.

 

Conclusion

 
Building a real-time voice bot has never been more accessible, thanks to the GPT-4o Realtime API. By consolidating speech-to-speech functionalities into a single, efficient interface, developers can craft engaging and natural conversational experiences without the complexity of managing multiple models. Whether you're enhancing customer support, developing educational tools, or creating interactive applications, the GPT-4o Realtime API provides a robust foundation to bring your voice bot visions to life. 

Embark on your development journey today and explore the endless possibilities that real-time voice interactions can offer your users!

 
Feel free to refer to the Azure OpenAI GPT-4o Realtime API documentation for more detailed information on setup, deployment, and advanced configurations.

 

Github Link: https://github.com/monuminu/AOAI_Samples/tree/main/realtime-assistant-support

 

Thanks

Manoranjan Rajguru

https://www.linkedin.com/in/manoranjan-rajguru/

Updated Oct 14, 2024
Version 7.0

9 Comments

"}},"componentScriptGroups({\"componentId\":\"custom.widget.Social_Sharing\"})":{"__typename":"ComponentScriptGroups","scriptGroups":{"__typename":"ComponentScriptGroupsDefinition","afterInteractive":{"__typename":"PageScriptGroupDefinition","group":"AFTER_INTERACTIVE","scriptIds":[]},"lazyOnLoad":{"__typename":"PageScriptGroupDefinition","group":"LAZY_ON_LOAD","scriptIds":[]}},"componentScripts":[]},"component({\"componentId\":\"custom.widget.MicrosoftFooter\"})":{"__typename":"Component","render({\"context\":{\"component\":{\"entities\":[],\"props\":{}},\"page\":{\"entities\":[\"board:Azure-AI-Services-blog\",\"message:4269038\"],\"name\":\"BlogMessagePage\",\"props\":{},\"url\":\"https://techcommunity.microsoft.com/blog/azure-ai-services-blog/from-zero-to-hero-building-your-first-voice-bot-with-gpt-4o-real-time-api-using-/4269038\"}}})":{"__typename":"ComponentRenderResult","html":""}},"componentScriptGroups({\"componentId\":\"custom.widget.MicrosoftFooter\"})":{"__typename":"ComponentScriptGroups","scriptGroups":{"__typename":"ComponentScriptGroupsDefinition","afterInteractive":{"__typename":"PageScriptGroupDefinition","group":"AFTER_INTERACTIVE","scriptIds":[]},"lazyOnLoad":{"__typename":"PageScriptGroupDefinition","group":"LAZY_ON_LOAD","scriptIds":[]}},"componentScripts":[]},"cachedText({\"lastModified\":\"1743095130000\",\"locale\":\"en-US\",\"namespaces\":[\"components/community/NavbarDropdownToggle\"]})":[{"__ref":"CachedAsset:text:en_US-components/community/NavbarDropdownToggle-1743095130000"}],"cachedText({\"lastModified\":\"1743095130000\",\"locale\":\"en-US\",\"namespaces\":[\"shared/client/components/common/QueryHandler\"]})":[{"__ref":"CachedAsset:text:en_US-shared/client/components/common/QueryHandler-1743095130000"}],"cachedText({\"lastModified\":\"1743095130000\",\"locale\":\"en-US\",\"namespaces\":[\"components/messages/MessageCoverImage\"]})":[{"__ref":"CachedAsset:text:en_US-components/messages/MessageCoverImage-1743095130000"}],"cachedText({\"lastModified\":\"1743095130000\",\"locale\":\"en-US\",\"namespaces\":[\"shared/client/components/nodes/NodeTitle\"]})":[{"__ref":"CachedAsset:text:en_US-shared/client/components/nodes/NodeTitle-1743095130000"}],"cachedText({\"lastModified\":\"1743095130000\",\"locale\":\"en-US\",\"namespaces\":[\"components/messages/MessageTimeToRead\"]})":[{"__ref":"CachedAsset:text:en_US-components/messages/MessageTimeToRead-1743095130000"}],"cachedText({\"lastModified\":\"1743095130000\",\"locale\":\"en-US\",\"namespaces\":[\"components/messages/MessageSubject\"]})":[{"__ref":"CachedAsset:text:en_US-components/messages/MessageSubject-1743095130000"}],"cachedText({\"lastModified\":\"1743095130000\",\"locale\":\"en-US\",\"namespaces\":[\"components/users/UserLink\"]})":[{"__ref":"CachedAsset:text:en_US-components/users/UserLink-1743095130000"}],"cachedText({\"lastModified\":\"1743095130000\",\"locale\":\"en-US\",\"namespaces\":[\"shared/client/components/users/UserRank\"]})":[{"__ref":"CachedAsset:text:en_US-shared/client/components/users/UserRank-1743095130000"}],"cachedText({\"lastModified\":\"1743095130000\",\"locale\":\"en-US\",\"namespaces\":[\"components/messages/MessageTime\"]})":[{"__ref":"CachedAsset:text:en_US-components/messages/MessageTime-1743095130000"}],"cachedText({\"lastModified\":\"1743095130000\",\"locale\":\"en-US\",\"namespaces\":[\"components/messages/MessageBody\"]})":[{"__ref":"CachedAsset:text:en_US-components/messages/MessageBody-1743095130000"}],"cachedText({\"lastModified\":\"1743095130000\",\"locale\":\"en-US\",\"namespaces\":[\"components/messages/MessageCustomFields\"]})":[{"__ref":"CachedAsset:text:en_US-components/messages/MessageCustomFields-1743095130000"}],"cachedText({\"lastModified\":\"1743095130000\",\"locale\":\"en-US\",\"namespaces\":[\"components/messages/MessageRevision\"]})":[{"__ref":"CachedAsset:text:en_US-components/messages/MessageRevision-1743095130000"}],"cachedText({\"lastModified\":\"1743095130000\",\"locale\":\"en-US\",\"namespaces\":[\"components/messages/MessageReplyButton\"]})":[{"__ref":"CachedAsset:text:en_US-components/messages/MessageReplyButton-1743095130000"}],"cachedText({\"lastModified\":\"1743095130000\",\"locale\":\"en-US\",\"namespaces\":[\"components/messages/MessageAuthorBio\"]})":[{"__ref":"CachedAsset:text:en_US-components/messages/MessageAuthorBio-1743095130000"}],"cachedText({\"lastModified\":\"1743095130000\",\"locale\":\"en-US\",\"namespaces\":[\"shared/client/components/users/UserAvatar\"]})":[{"__ref":"CachedAsset:text:en_US-shared/client/components/users/UserAvatar-1743095130000"}],"cachedText({\"lastModified\":\"1743095130000\",\"locale\":\"en-US\",\"namespaces\":[\"shared/client/components/ranks/UserRankLabel\"]})":[{"__ref":"CachedAsset:text:en_US-shared/client/components/ranks/UserRankLabel-1743095130000"}],"cachedText({\"lastModified\":\"1743095130000\",\"locale\":\"en-US\",\"namespaces\":[\"components/users/UserRegistrationDate\"]})":[{"__ref":"CachedAsset:text:en_US-components/users/UserRegistrationDate-1743095130000"}],"cachedText({\"lastModified\":\"1743095130000\",\"locale\":\"en-US\",\"namespaces\":[\"shared/client/components/nodes/NodeAvatar\"]})":[{"__ref":"CachedAsset:text:en_US-shared/client/components/nodes/NodeAvatar-1743095130000"}],"cachedText({\"lastModified\":\"1743095130000\",\"locale\":\"en-US\",\"namespaces\":[\"shared/client/components/nodes/NodeDescription\"]})":[{"__ref":"CachedAsset:text:en_US-shared/client/components/nodes/NodeDescription-1743095130000"}],"cachedText({\"lastModified\":\"1743095130000\",\"locale\":\"en-US\",\"namespaces\":[\"shared/client/components/common/Pager/PagerLoadMore\"]})":[{"__ref":"CachedAsset:text:en_US-shared/client/components/common/Pager/PagerLoadMore-1743095130000"}],"message({\"id\":\"message:4298174\"})":{"__ref":"BlogReplyMessage:message:4298174"},"message({\"id\":\"message:4282642\"})":{"__ref":"BlogReplyMessage:message:4282642"},"message({\"id\":\"message:4286902\"})":{"__ref":"BlogReplyMessage:message:4286902"},"message({\"id\":\"message:4274777\"})":{"__ref":"BlogReplyMessage:message:4274777"},"message({\"id\":\"message:4288304\"})":{"__ref":"BlogReplyMessage:message:4288304"},"message({\"id\":\"message:4270754\"})":{"__ref":"BlogReplyMessage:message:4270754"},"message({\"id\":\"message:4270753\"})":{"__ref":"BlogReplyMessage:message:4270753"},"message({\"id\":\"message:4270598\"})":{"__ref":"BlogReplyMessage:message:4270598"},"message({\"id\":\"message:4269234\"})":{"__ref":"BlogReplyMessage:message:4269234"},"cachedText({\"lastModified\":\"1743095130000\",\"locale\":\"en-US\",\"namespaces\":[\"components/tags/TagView/TagViewChip\"]})":[{"__ref":"CachedAsset:text:en_US-components/tags/TagView/TagViewChip-1743095130000"}],"cachedText({\"lastModified\":\"1743095130000\",\"locale\":\"en-US\",\"namespaces\":[\"shared/client/components/nodes/NodeIcon\"]})":[{"__ref":"CachedAsset:text:en_US-shared/client/components/nodes/NodeIcon-1743095130000"}]},"CachedAsset:pages-1743150526968":{"__typename":"CachedAsset","id":"pages-1743150526968","value":[{"lastUpdatedTime":1743150526968,"localOverride":null,"page":{"id":"BlogViewAllPostsPage","type":"BLOG","urlPath":"/category/:categoryId/blog/:boardId/all-posts/(/:after|/:before)?","__typename":"PageDescriptor"},"__typename":"PageResource"},{"lastUpdatedTime":1743150526968,"localOverride":null,"page":{"id":"CasePortalPage","type":"CASE_PORTAL","urlPath":"/caseportal","__typename":"PageDescriptor"},"__typename":"PageResource"},{"lastUpdatedTime":1743150526968,"localOverride":null,"page":{"id":"CreateGroupHubPage","type":"GROUP_HUB","urlPath":"/groups/create","__typename":"PageDescriptor"},"__typename":"PageResource"},{"lastUpdatedTime":1743150526968,"localOverride":null,"page":{"id":"CaseViewPage","type":"CASE_DETAILS","urlPath":"/case/:caseId/:caseNumber","__typename":"PageDescriptor"},"__typename":"PageResource"},{"lastUpdatedTime":1743150526968,"localOverride":null,"page":{"id":"InboxPage","type":"COMMUNITY","urlPath":"/inbox","__typename":"PageDescriptor"},"__typename":"PageResource"},{"lastUpdatedTime":1743150526968,"localOverride":null,"page":{"id":"HelpFAQPage","type":"COMMUNITY","urlPath":"/help","__typename":"PageDescriptor"},"__typename":"PageResource"},{"lastUpdatedTime":1743150526968,"localOverride":null,"page":{"id":"IdeaMessagePage","type":"IDEA_POST","urlPath":"/idea/:boardId/:messageSubject/:messageId","__typename":"PageDescriptor"},"__typename":"PageResource"},{"lastUpdatedTime":1743150526968,"localOverride":null,"page":{"id":"IdeaViewAllIdeasPage","type":"IDEA","urlPath":"/category/:categoryId/ideas/:boardId/all-ideas/(/:after|/:before)?","__typename":"PageDescriptor"},"__typename":"PageResource"},{"lastUpdatedTime":1743150526968,"localOverride":null,"page":{"id":"LoginPage","type":"USER","urlPath":"/signin","__typename":"PageDescriptor"},"__typename":"PageResource"},{"lastUpdatedTime":1743150526968,"localOverride":null,"page":{"id":"BlogPostPage","type":"BLOG","urlPath":"/category/:categoryId/blogs/:boardId/create","__typename":"PageDescriptor"},"__typename":"PageResource"},{"lastUpdatedTime":1743150526968,"localOverride":null,"page":{"id":"UserBlogPermissions.Page","type":"COMMUNITY","urlPath":"/c/user-blog-permissions/page","__typename":"PageDescriptor"},"__typename":"PageResource"},{"lastUpdatedTime":1743150526968,"localOverride":null,"page":{"id":"ThemeEditorPage","type":"COMMUNITY","urlPath":"/designer/themes","__typename":"PageDescriptor"},"__typename":"PageResource"},{"lastUpdatedTime":1743150526968,"localOverride":null,"page":{"id":"TkbViewAllArticlesPage","type":"TKB","urlPath":"/category/:categoryId/kb/:boardId/all-articles/(/:after|/:before)?","__typename":"PageDescriptor"},"__typename":"PageResource"},{"lastUpdatedTime":1730819800000,"localOverride":null,"page":{"id":"AllEvents","type":"CUSTOM","urlPath":"/Events","__typename":"PageDescriptor"},"__typename":"PageResource"},{"lastUpdatedTime":1743150526968,"localOverride":null,"page":{"id":"OccasionEditPage","type":"EVENT","urlPath":"/event/:boardId/:messageSubject/:messageId/edit","__typename":"PageDescriptor"},"__typename":"PageResource"},{"lastUpdatedTime":1743150526968,"localOverride":null,"page":{"id":"OAuthAuthorizationAllowPage","type":"USER","urlPath":"/auth/authorize/allow","__typename":"PageDescriptor"},"__typename":"PageResource"},{"lastUpdatedTime":1743150526968,"localOverride":null,"page":{"id":"PageEditorPage","type":"COMMUNITY","urlPath":"/designer/pages","__typename":"PageDescriptor"},"__typename":"PageResource"},{"lastUpdatedTime":1743150526968,"localOverride":null,"page":{"id":"PostPage","type":"COMMUNITY","urlPath":"/category/:categoryId/:boardId/create","__typename":"PageDescriptor"},"__typename":"PageResource"},{"lastUpdatedTime":1743150526968,"localOverride":null,"page":{"id":"ForumBoardPage","type":"FORUM","urlPath":"/category/:categoryId/discussions/:boardId","__typename":"PageDescriptor"},"__typename":"PageResource"},{"lastUpdatedTime":1743150526968,"localOverride":null,"page":{"id":"TkbBoardPage","type":"TKB","urlPath":"/category/:categoryId/kb/:boardId","__typename":"PageDescriptor"},"__typename":"PageResource"},{"lastUpdatedTime":1743150526968,"localOverride":null,"page":{"id":"EventPostPage","type":"EVENT","urlPath":"/category/:categoryId/events/:boardId/create","__typename":"PageDescriptor"},"__typename":"PageResource"},{"lastUpdatedTime":1743150526968,"localOverride":null,"page":{"id":"UserBadgesPage","type":"COMMUNITY","urlPath":"/users/:login/:userId/badges","__typename":"PageDescriptor"},"__typename":"PageResource"},{"lastUpdatedTime":1743150526968,"localOverride":null,"page":{"id":"GroupHubMembershipAction","type":"GROUP_HUB","urlPath":"/membership/join/:nodeId/:membershipType","__typename":"PageDescriptor"},"__typename":"PageResource"},{"lastUpdatedTime":1743150526968,"localOverride":null,"page":{"id":"MaintenancePage","type":"COMMUNITY","urlPath":"/maintenance","__typename":"PageDescriptor"},"__typename":"PageResource"},{"lastUpdatedTime":1743150526968,"localOverride":null,"page":{"id":"IdeaReplyPage","type":"IDEA_REPLY","urlPath":"/idea/:boardId/:messageSubject/:messageId/comments/:replyId","__typename":"PageDescriptor"},"__typename":"PageResource"},{"lastUpdatedTime":1743150526968,"localOverride":null,"page":{"id":"UserSettingsPage","type":"USER","urlPath":"/mysettings/:userSettingsTab","__typename":"PageDescriptor"},"__typename":"PageResource"},{"lastUpdatedTime":1743150526968,"localOverride":null,"page":{"id":"GroupHubsPage","type":"GROUP_HUB","urlPath":"/groups","__typename":"PageDescriptor"},"__typename":"PageResource"},{"lastUpdatedTime":1743150526968,"localOverride":null,"page":{"id":"ForumPostPage","type":"FORUM","urlPath":"/category/:categoryId/discussions/:boardId/create","__typename":"PageDescriptor"},"__typename":"PageResource"},{"lastUpdatedTime":1743150526968,"localOverride":null,"page":{"id":"OccasionRsvpActionPage","type":"OCCASION","urlPath":"/event/:boardId/:messageSubject/:messageId/rsvp/:responseType","__typename":"PageDescriptor"},"__typename":"PageResource"},{"lastUpdatedTime":1743150526968,"localOverride":null,"page":{"id":"VerifyUserEmailPage","type":"USER","urlPath":"/verifyemail/:userId/:verifyEmailToken","__typename":"PageDescriptor"},"__typename":"PageResource"},{"lastUpdatedTime":1743150526968,"localOverride":null,"page":{"id":"AllOccasionsPage","type":"OCCASION","urlPath":"/category/:categoryId/events/:boardId/all-events/(/:after|/:before)?","__typename":"PageDescriptor"},"__typename":"PageResource"},{"lastUpdatedTime":1743150526968,"localOverride":null,"page":{"id":"EventBoardPage","type":"EVENT","urlPath":"/category/:categoryId/events/:boardId","__typename":"PageDescriptor"},"__typename":"PageResource"},{"lastUpdatedTime":1743150526968,"localOverride":null,"page":{"id":"TkbReplyPage","type":"TKB_REPLY","urlPath":"/kb/:boardId/:messageSubject/:messageId/comments/:replyId","__typename":"PageDescriptor"},"__typename":"PageResource"},{"lastUpdatedTime":1743150526968,"localOverride":null,"page":{"id":"IdeaBoardPage","type":"IDEA","urlPath":"/category/:categoryId/ideas/:boardId","__typename":"PageDescriptor"},"__typename":"PageResource"},{"lastUpdatedTime":1743150526968,"localOverride":null,"page":{"id":"CommunityGuideLinesPage","type":"COMMUNITY","urlPath":"/communityguidelines","__typename":"PageDescriptor"},"__typename":"PageResource"},{"lastUpdatedTime":1743150526968,"localOverride":null,"page":{"id":"CaseCreatePage","type":"SALESFORCE_CASE_CREATION","urlPath":"/caseportal/create","__typename":"PageDescriptor"},"__typename":"PageResource"},{"lastUpdatedTime":1743150526968,"localOverride":null,"page":{"id":"TkbEditPage","type":"TKB","urlPath":"/kb/:boardId/:messageSubject/:messageId/edit","__typename":"PageDescriptor"},"__typename":"PageResource"},{"lastUpdatedTime":1743150526968,"localOverride":null,"page":{"id":"ForgotPasswordPage","type":"USER","urlPath":"/forgotpassword","__typename":"PageDescriptor"},"__typename":"PageResource"},{"lastUpdatedTime":1743150526968,"localOverride":null,"page":{"id":"IdeaEditPage","type":"IDEA","urlPath":"/idea/:boardId/:messageSubject/:messageId/edit","__typename":"PageDescriptor"},"__typename":"PageResource"},{"lastUpdatedTime":1743150526968,"localOverride":null,"page":{"id":"TagPage","type":"COMMUNITY","urlPath":"/tag/:tagName","__typename":"PageDescriptor"},"__typename":"PageResource"},{"lastUpdatedTime":1743150526968,"localOverride":null,"page":{"id":"BlogBoardPage","type":"BLOG","urlPath":"/category/:categoryId/blog/:boardId","__typename":"PageDescriptor"},"__typename":"PageResource"},{"lastUpdatedTime":1743150526968,"localOverride":null,"page":{"id":"OccasionMessagePage","type":"OCCASION_TOPIC","urlPath":"/event/:boardId/:messageSubject/:messageId","__typename":"PageDescriptor"},"__typename":"PageResource"},{"lastUpdatedTime":1743150526968,"localOverride":null,"page":{"id":"ManageContentPage","type":"COMMUNITY","urlPath":"/managecontent","__typename":"PageDescriptor"},"__typename":"PageResource"},{"lastUpdatedTime":1743150526968,"localOverride":null,"page":{"id":"ClosedMembershipNodeNonMembersPage","type":"GROUP_HUB","urlPath":"/closedgroup/:groupHubId","__typename":"PageDescriptor"},"__typename":"PageResource"},{"lastUpdatedTime":1743150526968,"localOverride":null,"page":{"id":"CommunityPage","type":"COMMUNITY","urlPath":"/","__typename":"PageDescriptor"},"__typename":"PageResource"},{"lastUpdatedTime":1743150526968,"localOverride":null,"page":{"id":"ForumMessagePage","type":"FORUM_TOPIC","urlPath":"/discussions/:boardId/:messageSubject/:messageId","__typename":"PageDescriptor"},"__typename":"PageResource"},{"lastUpdatedTime":1743150526968,"localOverride":null,"page":{"id":"IdeaPostPage","type":"IDEA","urlPath":"/category/:categoryId/ideas/:boardId/create","__typename":"PageDescriptor"},"__typename":"PageResource"},{"lastUpdatedTime":1730819800000,"localOverride":null,"page":{"id":"CommunityHub.Page","type":"CUSTOM","urlPath":"/Directory","__typename":"PageDescriptor"},"__typename":"PageResource"},{"lastUpdatedTime":1743150526968,"localOverride":null,"page":{"id":"BlogMessagePage","type":"BLOG_ARTICLE","urlPath":"/blog/:boardId/:messageSubject/:messageId","__typename":"PageDescriptor"},"__typename":"PageResource"},{"lastUpdatedTime":1743150526968,"localOverride":null,"page":{"id":"RegistrationPage","type":"USER","urlPath":"/register","__typename":"PageDescriptor"},"__typename":"PageResource"},{"lastUpdatedTime":1743150526968,"localOverride":null,"page":{"id":"EditGroupHubPage","type":"GROUP_HUB","urlPath":"/group/:groupHubId/edit","__typename":"PageDescriptor"},"__typename":"PageResource"},{"lastUpdatedTime":1743150526968,"localOverride":null,"page":{"id":"ForumEditPage","type":"FORUM","urlPath":"/discussions/:boardId/:messageSubject/:messageId/edit","__typename":"PageDescriptor"},"__typename":"PageResource"},{"lastUpdatedTime":1743150526968,"localOverride":null,"page":{"id":"ResetPasswordPage","type":"USER","urlPath":"/resetpassword/:userId/:resetPasswordToken","__typename":"PageDescriptor"},"__typename":"PageResource"},{"lastUpdatedTime":1730819800000,"localOverride":null,"page":{"id":"AllBlogs.Page","type":"CUSTOM","urlPath":"/blogs","__typename":"PageDescriptor"},"__typename":"PageResource"},{"lastUpdatedTime":1743150526968,"localOverride":null,"page":{"id":"TkbMessagePage","type":"TKB_ARTICLE","urlPath":"/kb/:boardId/:messageSubject/:messageId","__typename":"PageDescriptor"},"__typename":"PageResource"},{"lastUpdatedTime":1743150526968,"localOverride":null,"page":{"id":"BlogEditPage","type":"BLOG","urlPath":"/blog/:boardId/:messageSubject/:messageId/edit","__typename":"PageDescriptor"},"__typename":"PageResource"},{"lastUpdatedTime":1743150526968,"localOverride":null,"page":{"id":"ManageUsersPage","type":"USER","urlPath":"/users/manage/:tab?/:manageUsersTab?","__typename":"PageDescriptor"},"__typename":"PageResource"},{"lastUpdatedTime":1743150526968,"localOverride":null,"page":{"id":"ForumReplyPage","type":"FORUM_REPLY","urlPath":"/discussions/:boardId/:messageSubject/:messageId/replies/:replyId","__typename":"PageDescriptor"},"__typename":"PageResource"},{"lastUpdatedTime":1743150526968,"localOverride":null,"page":{"id":"PrivacyPolicyPage","type":"COMMUNITY","urlPath":"/privacypolicy","__typename":"PageDescriptor"},"__typename":"PageResource"},{"lastUpdatedTime":1743150526968,"localOverride":null,"page":{"id":"NotificationPage","type":"COMMUNITY","urlPath":"/notifications","__typename":"PageDescriptor"},"__typename":"PageResource"},{"lastUpdatedTime":1743150526968,"localOverride":null,"page":{"id":"UserPage","type":"USER","urlPath":"/users/:login/:userId","__typename":"PageDescriptor"},"__typename":"PageResource"},{"lastUpdatedTime":1743150526968,"localOverride":null,"page":{"id":"OccasionReplyPage","type":"OCCASION_REPLY","urlPath":"/event/:boardId/:messageSubject/:messageId/comments/:replyId","__typename":"PageDescriptor"},"__typename":"PageResource"},{"lastUpdatedTime":1743150526968,"localOverride":null,"page":{"id":"ManageMembersPage","type":"GROUP_HUB","urlPath":"/group/:groupHubId/manage/:tab?","__typename":"PageDescriptor"},"__typename":"PageResource"},{"lastUpdatedTime":1743150526968,"localOverride":null,"page":{"id":"SearchResultsPage","type":"COMMUNITY","urlPath":"/search","__typename":"PageDescriptor"},"__typename":"PageResource"},{"lastUpdatedTime":1743150526968,"localOverride":null,"page":{"id":"BlogReplyPage","type":"BLOG_REPLY","urlPath":"/blog/:boardId/:messageSubject/:messageId/replies/:replyId","__typename":"PageDescriptor"},"__typename":"PageResource"},{"lastUpdatedTime":1743150526968,"localOverride":null,"page":{"id":"GroupHubPage","type":"GROUP_HUB","urlPath":"/group/:groupHubId","__typename":"PageDescriptor"},"__typename":"PageResource"},{"lastUpdatedTime":1743150526968,"localOverride":null,"page":{"id":"TermsOfServicePage","type":"COMMUNITY","urlPath":"/termsofservice","__typename":"PageDescriptor"},"__typename":"PageResource"},{"lastUpdatedTime":1743150526968,"localOverride":null,"page":{"id":"CategoryPage","type":"CATEGORY","urlPath":"/category/:categoryId","__typename":"PageDescriptor"},"__typename":"PageResource"},{"lastUpdatedTime":1743150526968,"localOverride":null,"page":{"id":"ForumViewAllTopicsPage","type":"FORUM","urlPath":"/category/:categoryId/discussions/:boardId/all-topics/(/:after|/:before)?","__typename":"PageDescriptor"},"__typename":"PageResource"},{"lastUpdatedTime":1743150526968,"localOverride":null,"page":{"id":"TkbPostPage","type":"TKB","urlPath":"/category/:categoryId/kbs/:boardId/create","__typename":"PageDescriptor"},"__typename":"PageResource"},{"lastUpdatedTime":1743150526968,"localOverride":null,"page":{"id":"GroupHubPostPage","type":"GROUP_HUB","urlPath":"/group/:groupHubId/:boardId/create","__typename":"PageDescriptor"},"__typename":"PageResource"}],"localOverride":false},"CachedAsset:text:en_US-components/context/AppContext/AppContextProvider-0":{"__typename":"CachedAsset","id":"text:en_US-components/context/AppContext/AppContextProvider-0","value":{"noCommunity":"Cannot find community","noUser":"Cannot find current user","noNode":"Cannot find node with id {nodeId}","noMessage":"Cannot find message with id {messageId}"},"localOverride":false},"CachedAsset:text:en_US-shared/client/components/common/Loading/LoadingDot-0":{"__typename":"CachedAsset","id":"text:en_US-shared/client/components/common/Loading/LoadingDot-0","value":{"title":"Loading..."},"localOverride":false},"User:user:-1":{"__typename":"User","id":"user:-1","uid":-1,"login":"Deleted","email":"","avatar":null,"rank":null,"kudosWeight":1,"registrationData":{"__typename":"RegistrationData","status":"ANONYMOUS","registrationTime":null,"confirmEmailStatus":false,"registrationAccessLevel":"VIEW","ssoRegistrationFields":[]},"ssoId":null,"profileSettings":{"__typename":"ProfileSettings","dateDisplayStyle":{"__typename":"InheritableStringSettingWithPossibleValues","key":"layout.friendly_dates_enabled","value":"false","localValue":"true","possibleValues":["true","false"]},"dateDisplayFormat":{"__typename":"InheritableStringSetting","key":"layout.format_pattern_date","value":"MMM dd yyyy","localValue":"MM-dd-yyyy"},"language":{"__typename":"InheritableStringSettingWithPossibleValues","key":"profile.language","value":"en-US","localValue":"en","possibleValues":["en-US"]}},"deleted":false},"Theme:customTheme1":{"__typename":"Theme","id":"customTheme1"},"Category:category:AI":{"__typename":"Category","id":"category:AI","entityType":"CATEGORY","displayId":"AI","nodeType":"category","depth":3,"title":"Artificial Intelligence and Machine Learning","shortTitle":"Artificial Intelligence and Machine Learning","parent":{"__ref":"Category:category:solutions"},"categoryPolicies":{"__typename":"CategoryPolicies","canReadNode":{"__typename":"PolicyResult","failureReason":null}}},"Category:category:top":{"__typename":"Category","id":"category:top","displayId":"top","nodeType":"category","depth":0,"title":"Top","entityType":"CATEGORY","shortTitle":"Top"},"Category:category:communities":{"__typename":"Category","id":"category:communities","displayId":"communities","nodeType":"category","depth":1,"parent":{"__ref":"Category:category:top"},"title":"Communities","entityType":"CATEGORY","shortTitle":"Communities"},"Category:category:solutions":{"__typename":"Category","id":"category:solutions","displayId":"solutions","nodeType":"category","depth":2,"parent":{"__ref":"Category:category:communities"},"title":"Topics","entityType":"CATEGORY","shortTitle":"Topics"},"Blog:board:Azure-AI-Services-blog":{"__typename":"Blog","id":"board:Azure-AI-Services-blog","entityType":"BLOG","displayId":"Azure-AI-Services-blog","nodeType":"board","depth":4,"conversationStyle":"BLOG","title":"AI - Azure AI services Blog","description":"","avatar":null,"profileSettings":{"__typename":"ProfileSettings","language":null},"parent":{"__ref":"Category:category:AI"},"ancestors":{"__typename":"CoreNodeConnection","edges":[{"__typename":"CoreNodeEdge","node":{"__ref":"Community:community:gxcuf89792"}},{"__typename":"CoreNodeEdge","node":{"__ref":"Category:category:communities"}},{"__typename":"CoreNodeEdge","node":{"__ref":"Category:category:solutions"}},{"__typename":"CoreNodeEdge","node":{"__ref":"Category:category:AI"}}]},"userContext":{"__typename":"NodeUserContext","canAddAttachments":false,"canUpdateNode":false,"canPostMessages":false,"isSubscribed":false},"boardPolicies":{"__typename":"BoardPolicies","canPublishArticleOnCreate":{"__typename":"PolicyResult","failureReason":{"__typename":"FailureReason","message":"error.lithium.policies.forums.policy_can_publish_on_create_workflow_action.accessDenied","key":"error.lithium.policies.forums.policy_can_publish_on_create_workflow_action.accessDenied","args":[]}}},"shortTitle":"AI - Azure AI services Blog","repliesProperties":{"__typename":"RepliesProperties","sortOrder":"REVERSE_PUBLISH_TIME","repliesFormat":"threaded"},"eventPath":"category:AI/category:solutions/category:communities/community:gxcuf89792board:Azure-AI-Services-blog/","tagProperties":{"__typename":"TagNodeProperties","tagsEnabled":{"__typename":"PolicyResult","failureReason":null}},"requireTags":true,"tagType":"PRESET_ONLY"},"AssociatedImage:{\"url\":\"https://techcommunity.microsoft.com/t5/s/gxcuf89792/images/cmstNC05WEo0blc\"}":{"__typename":"AssociatedImage","url":"https://techcommunity.microsoft.com/t5/s/gxcuf89792/images/cmstNC05WEo0blc","height":512,"width":512,"mimeType":"image/png"},"Rank:rank:4":{"__typename":"Rank","id":"rank:4","position":6,"name":"Microsoft","color":"333333","icon":{"__ref":"AssociatedImage:{\"url\":\"https://techcommunity.microsoft.com/t5/s/gxcuf89792/images/cmstNC05WEo0blc\"}"},"rankStyle":"OUTLINE"},"User:user:2080373":{"__typename":"User","id":"user:2080373","uid":2080373,"login":"mrajguru","deleted":false,"avatar":{"__typename":"UserAvatar","url":"https://techcommunity.microsoft.com/t5/s/gxcuf89792/images/dS0yMDgwMzczLTU2MzI2Nmk2MDUwNkNDRUUxMDhGQjYx"},"rank":{"__ref":"Rank:rank:4"},"email":"","messagesCount":27,"biography":null,"topicsCount":16,"kudosReceivedCount":55,"kudosGivenCount":6,"kudosWeight":1,"registrationData":{"__typename":"RegistrationData","status":null,"registrationTime":"2023-10-12T23:52:15.266-07:00","confirmEmailStatus":null},"followersCount":null,"solutionsCount":0,"entityType":"USER","eventPath":"community:gxcuf89792/user:2080373"},"BlogTopicMessage:message:4269038":{"__typename":"BlogTopicMessage","uid":4269038,"subject":"From Zero to Hero: Building Your First Voice Bot with GPT-4o Real-Time API using Python","id":"message:4269038","revisionNum":11,"repliesCount":9,"author":{"__ref":"User:user:2080373"},"depth":0,"hasGivenKudo":false,"board":{"__ref":"Blog:board:Azure-AI-Services-blog"},"conversation":{"__ref":"Conversation:conversation:4269038"},"messagePolicies":{"__typename":"MessagePolicies","canPublishArticleOnEdit":{"__typename":"PolicyResult","failureReason":{"__typename":"FailureReason","message":"error.lithium.policies.forums.policy_can_publish_on_edit_workflow_action.accessDenied","key":"error.lithium.policies.forums.policy_can_publish_on_edit_workflow_action.accessDenied","args":[]}},"canModerateSpamMessage":{"__typename":"PolicyResult","failureReason":{"__typename":"FailureReason","message":"error.lithium.policies.feature.moderation_spam.action.moderate_entity.allowed.accessDenied","key":"error.lithium.policies.feature.moderation_spam.action.moderate_entity.allowed.accessDenied","args":[]}}},"contentWorkflow":{"__typename":"ContentWorkflow","state":"PUBLISH","scheduledPublishTime":null,"scheduledTimezone":null,"userContext":{"__typename":"MessageWorkflowContext","canSubmitForReview":null,"canEdit":false,"canRecall":null,"canSubmitForPublication":null,"canReturnToAuthor":null,"canPublish":null,"canReturnToReview":null,"canSchedule":false},"shortScheduledTimezone":null},"readOnly":false,"editFrozen":false,"moderationData":{"__ref":"ModerationData:moderation_data:4269038"},"teaser":"

Ever wondered how to create a voice bot that interacts with users in real-time, just like a human? In this beginner-friendly guide, we’ll take you through the step-by-step process of building your own intelligent voice bot using GPT-4o Realtime API. From setting up your environment to integrating speech recognition and conversational AI, this blueprint is designed to help you go from zero to launch in no time. Whether you're a developer or a tech enthusiast, this is your chance to dive into the future of conversational AI!

","body":"

Voice technology is transforming how we interact with machines, making conversations with AI feel more natural than ever before. With the public beta release of the Realtime API powered by GPT-4o, developers now have the tools to create low-latency, multimodal voice experiences in their apps, opening up endless possibilities for innovation.

\n

 

\n

Gone are the days when building a voice bot required stitching together multiple models for transcription, inference, and text-to-speech conversion. With the Realtime API, developers can now streamline the entire process with a single API call, enabling fluid, natural speech-to-speech conversations. This is a game-changer for industries like customer support, education, and real-time language translation, where fast, seamless interactions are crucial.

\n

 

\n

\n

 

\n

In this blog, we’ll guide you through the process of building your first real-time voice bot from scratch using the GPT-4o Realtime Model. We’ll cover key features of the Realtime API, how to set up a WebSocket connection for voice streaming, and how to leverage the API’s ability to handle interruptions and make function calls. By the end, you’ll be ready to create a voice bot that responds to users with near-human accuracy and emotion. Whether you're a beginner or an experienced developer, this blueprint will help you get started with creating immersive voice interactions that are both responsive and engaging. Ready to dive in? Let’s get started!

\n

 

\n

Key Features

\n

 

\n\n

 

\n

How GPT-4o Realtime API Works

\n

 

\n

 Traditionally, building a voice assistant required chaining together several models: an automatic speech recognition (ASR) model like Whisper for transcribing audio, a text-based model for processing responses, and a text-to-speech (TTS) model for generating audio outputs. This multi-step process often led to delays and a loss of emotional nuance.

The GPT-4o Realtime API revolutionizes this by consolidating these functionalities into a single API call. By establishing a persistent WebSocket connection, developers can stream audio inputs and outputs directly, significantly reducing latency and enhancing the naturalness of conversations. Additionally, the API's function calling capability allows the voice bot to perform actions such as placing orders or retrieving customer information on the fly.

\n

 

\n
 
\n

\n

 

\n
 
\n

 

\n

 

\n

Building Your Real-Time Voice Bot

\n

 
Let's dive into the step-by-step process of building your own real-time voice bot using the GPT-4o Realtime API.

\n

Prerequisites

\n

 
Before you begin, ensure you have the following:

\n\n

 

\n

Setting Up the API

\n

 

\n
    \n
  1. Deploy the GPT-4o Realtime Model:
  2. \n\n
  3. Configure Audio Input and Output:
  4. \n\n
\n

 

\n

This project demonstrates how to build a sophisticated real-time conversational AI system using Azure OpenAI. By leveraging WebSocket connections and an event-driven architecture, the system provides responsive and context-aware customer support in any language. This approach can be adapted to various languages and use cases, making it a versatile solution for businesses looking to enhance their customer service capabilities. The project consists of three main components:

\n\n

 

\n

Environment Setup

\n

 

\n

Create an .env file and update the following environment variables:

\n

 

\n

 

\n

 

\n
AZURE_OPENAI_API_KEY=XXXX\n# replace with your Azure OpenAI API Key\n\nAZURE_OPENAI_ENDPOINT=https://xxxx.openai.azure.com/\n# replace with your Azure OpenAI Endpoint\n\nAZURE_OPENAI_DEPLOYMENT=gpt-4o-realtime-preview\n#Create a deployment for the gpt-4o-realtime-preview model and place the deployment name here. You can name the deployment as per your choice and put the name here.\n\nAZURE_OPENAI_CHAT_DEPLOYMENT_VERSION=2024-10-01-preview\n#You don't need to change this unless you are willing to try other versions.
\n

 

\n

 

\n

 

\n

requirements.txt

\n

 

\n

 

\n

 

\n
chainlit==1.3.0rc1\nopenai\nbeautifulsoup4\nlxml\npython-dotenv\nwebsockets\naiohttp
\n

 

\n

 

\n

 

\n

 

\n

Implementing the Realtime Client

\n

 
The heartbeat of your voice bot is the Realtime Client, which manages the WebSocket connection and handles communication with the GPT-4o Realtime API. The RealtimeAPI class is responsible for managing WebSocket connections to OpenAI's real-time API. It handles sending and receiving messages, dispatching events, and maintaining the connection state.

\n
\n
\n
\n
\n
Key features include:
\n
 
\n
\n
\n
\n
\n
\n
\n
\n
\n
    \n
  • Connection Management: Establishes and maintains a WebSocket connection. 
  • \n
  • Event Dispatching: Uses an event-driven architecture to handle incoming and outgoing messages.
  • \n
  • Audio Processing: Convert audio inputs from base64 to array buffers and vice versa using utility functions. Manage audio streams efficiently to ensure minimal latency and high-quality voice interactions.
  • \n
\n
\n
\n
\n
\n
\n


Key Components:

\n\n

 

\n

 

\n

 

\n
class RealtimeAPI(RealtimeEventHandler):\n    def __init__(self):\n        super().__init__()\n        self.default_url = 'wss://api.openai.com/v1/realtime'\n        self.url = os.environ[\"AZURE_OPENAI_ENDPOINT\"]\n        self.api_key = os.environ[\"AZURE_OPENAI_API_KEY\"]\n        self.api_version = \"2024-10-01-preview\"\n        self.azure_deployment = os.environ[\"AZURE_OPENAI_DEPLOYMENT\"]\n        self.ws = None\n\n    def is_connected(self):\n        return self.ws is not None\n\n    def log(self, *args):\n        logger.debug(f\"[Websocket/{datetime.utcnow().isoformat()}]\", *args)\n\n    async def connect(self, model='gpt-4o-realtime-preview'):\n        if self.is_connected():\n            raise Exception(\"Already connected\")\n        self.ws = await websockets.connect(f\"{self.url}/openai/realtime?api-version={self.api_version}&deployment={model}&api-key={self.api_key}\", extra_headers={\n            'Authorization': f'Bearer {self.api_key}',\n            'OpenAI-Beta': 'realtime=v1'\n        })\n        self.log(f\"Connected to {self.url}\")\n        asyncio.create_task(self._receive_messages())\n\n    async def _receive_messages(self):\n        async for message in self.ws:\n            event = json.loads(message)\n            if event['type'] == \"error\":\n                logger.error(\"ERROR\", message)\n            self.log(\"received:\", event)\n            self.dispatch(f\"server.{event['type']}\", event)\n            self.dispatch(\"server.*\", event)\n\n    async def send(self, event_name, data=None):\n        if not self.is_connected():\n            raise Exception(\"RealtimeAPI is not connected\")\n        data = data or {}\n        if not isinstance(data, dict):\n            raise Exception(\"data must be a dictionary\")\n        event = {\n            \"event_id\": self._generate_id(\"evt_\"),\n            \"type\": event_name,\n            **data\n        }\n        self.dispatch(f\"client.{event_name}\", event)\n        self.dispatch(\"client.*\", event)\n        self.log(\"sent:\", event)\n        await self.ws.send(json.dumps(event))\n\n    def _generate_id(self, prefix):\n        return f\"{prefix}{int(datetime.utcnow().timestamp() * 1000)}\"\n\n    async def disconnect(self):\n        if self.ws:\n            await self.ws.close()\n            self.ws = None\n            self.log(f\"Disconnected from {self.url}\")
\n

 

\n

 

\n

 

\n

 

\n

Reference: init.py 

\n

 

\n\n

 

\n

 

\n

 

\n
class RealtimeConversation:\n    default_frequency = config.features.audio.sample_rate\n    \n    EventProcessors = {\n        'conversation.item.created': lambda self, event: self._process_item_created(event),\n        'conversation.item.truncated': lambda self, event: self._process_item_truncated(event),\n        'conversation.item.deleted': lambda self, event: self._process_item_deleted(event),\n        'conversation.item.input_audio_transcription.completed': lambda self, event: self._process_input_audio_transcription_completed(event),\n        'input_audio_buffer.speech_started': lambda self, event: self._process_speech_started(event),\n        'input_audio_buffer.speech_stopped': lambda self, event, input_audio_buffer: self._process_speech_stopped(event, input_audio_buffer),\n        'response.created': lambda self, event: self._process_response_created(event),\n        'response.output_item.added': lambda self, event: self._process_output_item_added(event),\n        'response.output_item.done': lambda self, event: self._process_output_item_done(event),\n        'response.content_part.added': lambda self, event: self._process_content_part_added(event),\n        'response.audio_transcript.delta': lambda self, event: self._process_audio_transcript_delta(event),\n        'response.audio.delta': lambda self, event: self._process_audio_delta(event),\n        'response.text.delta': lambda self, event: self._process_text_delta(event),\n        'response.function_call_arguments.delta': lambda self, event: self._process_function_call_arguments_delta(event),\n    }\n    \n    def __init__(self):\n        self.clear()\n\n    def clear(self):\n        self.item_lookup = {}\n        self.items = []\n        self.response_lookup = {}\n        self.responses = []\n        self.queued_speech_items = {}\n        self.queued_transcript_items = {}\n        self.queued_input_audio = None\n\n    def queue_input_audio(self, input_audio):\n        self.queued_input_audio = input_audio\n\n    def process_event(self, event, *args):\n        event_processor = self.EventProcessors.get(event['type'])\n        if not event_processor:\n            raise Exception(f\"Missing conversation event processor for {event['type']}\")\n        return event_processor(self, event, *args)\n\n    def get_item(self, id):\n        return self.item_lookup.get(id)\n\n    def get_items(self):\n        return self.items[:]\n\n    def _process_item_created(self, event):\n        item = event['item']\n        new_item = item.copy()\n        if new_item['id'] not in self.item_lookup:\n            self.item_lookup[new_item['id']] = new_item\n            self.items.append(new_item)\n        new_item['formatted'] = {\n            'audio': [],\n            'text': '',\n            'transcript': ''\n        }\n        if new_item['id'] in self.queued_speech_items:\n            new_item['formatted']['audio'] = self.queued_speech_items[new_item['id']]['audio']\n            del self.queued_speech_items[new_item['id']]\n        if 'content' in new_item:\n            text_content = [c for c in new_item['content'] if c['type'] in ['text', 'input_text']]\n            for content in text_content:\n                new_item['formatted']['text'] += content['text']\n        if new_item['id'] in self.queued_transcript_items:\n            new_item['formatted']['transcript'] = self.queued_transcript_items[new_item['id']]['transcript']\n            del self.queued_transcript_items[new_item['id']]\n        if new_item['type'] == 'message':\n            if new_item['role'] == 'user':\n                new_item['status'] = 'completed'\n                if self.queued_input_audio:\n                    new_item['formatted']['audio'] = self.queued_input_audio\n                    self.queued_input_audio = None\n            else:\n                new_item['status'] = 'in_progress'\n        elif new_item['type'] == 'function_call':\n            new_item['formatted']['tool'] = {\n                'type': 'function',\n                'name': new_item['name'],\n                'call_id': new_item['call_id'],\n                'arguments': ''\n            }\n            new_item['status'] = 'in_progress'\n        elif new_item['type'] == 'function_call_output':\n            new_item['status'] = 'completed'\n            new_item['formatted']['output'] = new_item['output']\n        return new_item, None\n\n    def _process_item_truncated(self, event):\n        item_id = event['item_id']\n        audio_end_ms = event['audio_end_ms']\n        item = self.item_lookup.get(item_id)\n        if not item:\n            raise Exception(f'item.truncated: Item \"{item_id}\" not found')\n        end_index = (audio_end_ms * self.default_frequency) // 1000\n        item['formatted']['transcript'] = ''\n        item['formatted']['audio'] = item['formatted']['audio'][:end_index]\n        return item, None\n\n    def _process_item_deleted(self, event):\n        item_id = event['item_id']\n        item = self.item_lookup.get(item_id)\n        if not item:\n            raise Exception(f'item.deleted: Item \"{item_id}\" not found')\n        del self.item_lookup[item['id']]\n        self.items.remove(item)\n        return item, None\n\n    def _process_input_audio_transcription_completed(self, event):\n        item_id = event['item_id']\n        content_index = event['content_index']\n        transcript = event['transcript']\n        formatted_transcript = transcript or ' '\n        item = self.item_lookup.get(item_id)\n        if not item:\n            self.queued_transcript_items[item_id] = {'transcript': formatted_transcript}\n            return None, None\n        item['content'][content_index]['transcript'] = transcript\n        item['formatted']['transcript'] = formatted_transcript\n        return item, {'transcript': transcript}\n\n    def _process_speech_started(self, event):\n        item_id = event['item_id']\n        audio_start_ms = event['audio_start_ms']\n        self.queued_speech_items[item_id] = {'audio_start_ms': audio_start_ms}\n        return None, None\n\n    def _process_speech_stopped(self, event, input_audio_buffer):\n        item_id = event['item_id']\n        audio_end_ms = event['audio_end_ms']\n        speech = self.queued_speech_items[item_id]\n        speech['audio_end_ms'] = audio_end_ms\n        if input_audio_buffer:\n            start_index = (speech['audio_start_ms'] * self.default_frequency) // 1000\n            end_index = (speech['audio_end_ms'] * self.default_frequency) // 1000\n            speech['audio'] = input_audio_buffer[start_index:end_index]\n        return None, None\n\n    def _process_response_created(self, event):\n        response = event['response']\n        if response['id'] not in self.response_lookup:\n            self.response_lookup[response['id']] = response\n            self.responses.append(response)\n        return None, None\n\n    def _process_output_item_added(self, event):\n        response_id = event['response_id']\n        item = event['item']\n        response = self.response_lookup.get(response_id)\n        if not response:\n            raise Exception(f'response.output_item.added: Response \"{response_id}\" not found')\n        response['output'].append(item['id'])\n        return None, None\n\n    def _process_output_item_done(self, event):\n        item = event['item']\n        if not item:\n            raise Exception('response.output_item.done: Missing \"item\"')\n        found_item = self.item_lookup.get(item['id'])\n        if not found_item:\n            raise Exception(f'response.output_item.done: Item \"{item[\"id\"]}\" not found')\n        found_item['status'] = item['status']\n        return found_item, None\n\n    def _process_content_part_added(self, event):\n        item_id = event['item_id']\n        part = event['part']\n        item = self.item_lookup.get(item_id)\n        if not item:\n            raise Exception(f'response.content_part.added: Item \"{item_id}\" not found')\n        item['content'].append(part)\n        return item, None\n\n    def _process_audio_transcript_delta(self, event):\n        item_id = event['item_id']\n        content_index = event['content_index']\n        delta = event['delta']\n        item = self.item_lookup.get(item_id)\n        if not item:\n            raise Exception(f'response.audio_transcript.delta: Item \"{item_id}\" not found')\n        item['content'][content_index]['transcript'] += delta\n        item['formatted']['transcript'] += delta\n        return item, {'transcript': delta}\n\n    def _process_audio_delta(self, event):\n        item_id = event['item_id']\n        content_index = event['content_index']\n        delta = event['delta']\n        item = self.item_lookup.get(item_id)\n        if not item:\n            logger.debug(f'response.audio.delta: Item \"{item_id}\" not found')\n            return None, None\n        array_buffer = base64_to_array_buffer(delta)\n        append_values = array_buffer.tobytes()\n        # TODO: make it work\n        # item['formatted']['audio'] = merge_int16_arrays(item['formatted']['audio'], append_values)\n        return item, {'audio': append_values}\n\n    def _process_text_delta(self, event):\n        item_id = event['item_id']\n        content_index = event['content_index']\n        delta = event['delta']\n        item = self.item_lookup.get(item_id)\n        if not item:\n            raise Exception(f'response.text.delta: Item \"{item_id}\" not found')\n        item['content'][content_index]['text'] += delta\n        item['formatted']['text'] += delta\n        return item, {'text': delta}\n\n    def _process_function_call_arguments_delta(self, event):\n        item_id = event['item_id']\n        delta = event['delta']\n        item = self.item_lookup.get(item_id)\n        if not item:\n            raise Exception(f'response.function_call_arguments.delta: Item \"{item_id}\" not found')\n        item['arguments'] += delta\n        item['formatted']['tool']['arguments'] += delta\n        return item, {'arguments': delta}
\n

 

\n

 

\n

 

\n

 

\n\n

 

\n

 

\n

 

\n

 

\n
class RealtimeClient(RealtimeEventHandler):\n    def __init__(self, system_prompt: str):\n        super().__init__()\n        self.system_prompt = system_prompt\n        self.default_session_config = {\n            \"modalities\": [\"text\", \"audio\"],\n            \"instructions\": self.system_prompt,\n            \"voice\": \"shimmer\",\n            \"input_audio_format\": \"pcm16\",\n            \"output_audio_format\": \"pcm16\",\n            \"input_audio_transcription\": { \"model\": 'whisper-1' },\n            \"turn_detection\": { \"type\": 'server_vad' },\n            \"tools\": [],\n            \"tool_choice\": \"auto\",\n            \"temperature\": 0.8,\n            \"max_response_output_tokens\": 4096,\n        }\n        self.session_config = {}\n        self.transcription_models = [{\"model\": \"whisper-1\"}]\n        self.default_server_vad_config = {\n            \"type\": \"server_vad\",\n            \"threshold\": 0.5,\n            \"prefix_padding_ms\": 300,\n            \"silence_duration_ms\": 200,\n        }\n        self.realtime = RealtimeAPI()\n        self.conversation = RealtimeConversation()\n        self._reset_config()\n        self._add_api_event_handlers()\n        \n    def _reset_config(self):\n        self.session_created = False\n        self.tools = {}\n        self.session_config = self.default_session_config.copy()\n        self.input_audio_buffer = bytearray()\n        return True\n\n    def _add_api_event_handlers(self):\n        self.realtime.on(\"client.*\", self._log_event)\n        self.realtime.on(\"server.*\", self._log_event)\n        self.realtime.on(\"server.session.created\", self._on_session_created)\n        self.realtime.on(\"server.response.created\", self._process_event)\n        self.realtime.on(\"server.response.output_item.added\", self._process_event)\n        self.realtime.on(\"server.response.content_part.added\", self._process_event)\n        self.realtime.on(\"server.input_audio_buffer.speech_started\", self._on_speech_started)\n        self.realtime.on(\"server.input_audio_buffer.speech_stopped\", self._on_speech_stopped)\n        self.realtime.on(\"server.conversation.item.created\", self._on_item_created)\n        self.realtime.on(\"server.conversation.item.truncated\", self._process_event)\n        self.realtime.on(\"server.conversation.item.deleted\", self._process_event)\n        self.realtime.on(\"server.conversation.item.input_audio_transcription.completed\", self._process_event)\n        self.realtime.on(\"server.response.audio_transcript.delta\", self._process_event)\n        self.realtime.on(\"server.response.audio.delta\", self._process_event)\n        self.realtime.on(\"server.response.text.delta\", self._process_event)\n        self.realtime.on(\"server.response.function_call_arguments.delta\", self._process_event)\n        self.realtime.on(\"server.response.output_item.done\", self._on_output_item_done)\n\n    def _log_event(self, event):\n        realtime_event = {\n            \"time\": datetime.utcnow().isoformat(),\n            \"source\": \"client\" if event[\"type\"].startswith(\"client.\") else \"server\",\n            \"event\": event,\n        }\n        self.dispatch(\"realtime.event\", realtime_event)\n\n    def _on_session_created(self, event):\n        self.session_created = True\n\n    def _process_event(self, event, *args):\n        item, delta = self.conversation.process_event(event, *args)\n        if item:\n            self.dispatch(\"conversation.updated\", {\"item\": item, \"delta\": delta})\n        return item, delta\n\n    def _on_speech_started(self, event):\n        self._process_event(event)\n        self.dispatch(\"conversation.interrupted\", event)\n\n    def _on_speech_stopped(self, event):\n        self._process_event(event, self.input_audio_buffer)\n\n    def _on_item_created(self, event):\n        item, delta = self._process_event(event)\n        self.dispatch(\"conversation.item.appended\", {\"item\": item})\n        if item and item[\"status\"] == \"completed\":\n            self.dispatch(\"conversation.item.completed\", {\"item\": item})\n\n    async def _on_output_item_done(self, event):\n        item, delta = self._process_event(event)\n        if item and item[\"status\"] == \"completed\":\n            self.dispatch(\"conversation.item.completed\", {\"item\": item})\n        if item and item.get(\"formatted\", {}).get(\"tool\"):\n            await self._call_tool(item[\"formatted\"][\"tool\"])\n\n    async def _call_tool(self, tool):\n        try:\n            print(tool[\"arguments\"])\n            json_arguments = json.loads(tool[\"arguments\"])\n            tool_config = self.tools.get(tool[\"name\"])\n            if not tool_config:\n                raise Exception(f'Tool \"{tool[\"name\"]}\" has not been added')\n            result = await tool_config[\"handler\"](**json_arguments)\n            await self.realtime.send(\"conversation.item.create\", {\n                \"item\": {\n                    \"type\": \"function_call_output\",\n                    \"call_id\": tool[\"call_id\"],\n                    \"output\": json.dumps(result),\n                }\n            })\n        except Exception as e:\n            logger.error(traceback.format_exc())\n            await self.realtime.send(\"conversation.item.create\", {\n                \"item\": {\n                    \"type\": \"function_call_output\",\n                    \"call_id\": tool[\"call_id\"],\n                    \"output\": json.dumps({\"error\": str(e)}),\n                }\n            })\n        await self.create_response()\n\n    def is_connected(self):\n        return self.realtime.is_connected()\n\n    def reset(self):\n        self.disconnect()\n        self.realtime.clear_event_handlers()\n        self._reset_config()\n        self._add_api_event_handlers()\n        return True\n\n    async def connect(self):\n        if self.is_connected():\n            raise Exception(\"Already connected, use .disconnect() first\")\n        await self.realtime.connect()\n        await self.update_session()\n        return True\n\n    async def wait_for_session_created(self):\n        if not self.is_connected():\n            raise Exception(\"Not connected, use .connect() first\")\n        while not self.session_created:\n            await asyncio.sleep(0.001)\n        return True\n\n    async def disconnect(self):\n        self.session_created = False\n        self.conversation.clear()\n        if self.realtime.is_connected():\n            await self.realtime.disconnect()\n\n    def get_turn_detection_type(self):\n        return self.session_config.get(\"turn_detection\", {}).get(\"type\")\n\n    async def add_tool(self, definition, handler):\n        if not definition.get(\"name\"):\n            raise Exception(\"Missing tool name in definition\")\n        name = definition[\"name\"]\n        if name in self.tools:\n            raise Exception(f'Tool \"{name}\" already added. Please use .removeTool(\"{name}\") before trying to add again.')\n        if not callable(handler):\n            raise Exception(f'Tool \"{name}\" handler must be a function')\n        self.tools[name] = {\"definition\": definition, \"handler\": handler}\n        await self.update_session()\n        return self.tools[name]\n\n    def remove_tool(self, name):\n        if name not in self.tools:\n            raise Exception(f'Tool \"{name}\" does not exist, can not be removed.')\n        del self.tools[name]\n        return True\n\n    async def delete_item(self, id):\n        await self.realtime.send(\"conversation.item.delete\", {\"item_id\": id})\n        return True\n\n    async def update_session(self, **kwargs):\n        self.session_config.update(kwargs)\n        use_tools = [\n            {**tool_definition, \"type\": \"function\"}\n            for tool_definition in self.session_config.get(\"tools\", [])\n        ] + [\n            {**self.tools[key][\"definition\"], \"type\": \"function\"}\n            for key in self.tools\n        ]\n        session = {**self.session_config, \"tools\": use_tools}\n        if self.realtime.is_connected():\n            await self.realtime.send(\"session.update\", {\"session\": session})\n        return True\n    \n    async def create_conversation_item(self, item):\n        await self.realtime.send(\"conversation.item.create\", {\n            \"item\": item\n        })\n\n    async def send_user_message_content(self, content=[]):\n        if content:\n            for c in content:\n                if c[\"type\"] == \"input_audio\":\n                    if isinstance(c[\"audio\"], (bytes, bytearray)):\n                        c[\"audio\"] = array_buffer_to_base64(c[\"audio\"])\n            await self.realtime.send(\"conversation.item.create\", {\n                \"item\": {\n                    \"type\": \"message\",\n                    \"role\": \"user\",\n                    \"content\": content,\n                }\n            })\n        await self.create_response()\n        return True\n\n    async def append_input_audio(self, array_buffer):\n        if len(array_buffer) > 0:\n            await self.realtime.send(\"input_audio_buffer.append\", {\n                \"audio\": array_buffer_to_base64(np.array(array_buffer)),\n            })\n            self.input_audio_buffer.extend(array_buffer)\n        return True\n\n    async def create_response(self):\n        if self.get_turn_detection_type() is None and len(self.input_audio_buffer) > 0:\n            await self.realtime.send(\"input_audio_buffer.commit\")\n            self.conversation.queue_input_audio(self.input_audio_buffer)\n            self.input_audio_buffer = bytearray()\n        await self.realtime.send(\"response.create\")\n        return True\n\n    async def cancel_response(self, id=None, sample_count=0):\n        if not id:\n            await self.realtime.send(\"response.cancel\")\n            return {\"item\": None}\n        else:\n            item = self.conversation.get_item(id)\n            if not item:\n                raise Exception(f'Could not find item \"{id}\"')\n            if item[\"type\"] != \"message\":\n                raise Exception('Can only cancelResponse messages with type \"message\"')\n            if item[\"role\"] != \"assistant\":\n                raise Exception('Can only cancelResponse messages with role \"assistant\"')\n            await self.realtime.send(\"response.cancel\")\n            audio_index = next((i for i, c in enumerate(item[\"content\"]) if c[\"type\"] == \"audio\"), -1)\n            if audio_index == -1:\n                raise Exception(\"Could not find audio on item to cancel\")\n            await self.realtime.send(\"conversation.item.truncate\", {\n                \"item_id\": id,\n                \"content_index\": audio_index,\n                \"audio_end_ms\": int((sample_count / self.conversation.default_frequency) * 1000),\n            })\n            return {\"item\": item}\n\n    async def wait_for_next_item(self):\n        event = await self.wait_for_next(\"conversation.item.appended\")\n        return {\"item\": event[\"item\"]}\n\n    async def wait_for_next_completed_item(self):\n        event = await self.wait_for_next(\"conversation.item.completed\")\n        return {\"item\": event[\"item\"]}
\n

 

\n

 

\n

 

\n

 

\n

Adding Tools and Handlers

\n

 
Your voice bot's functionality can be extended by integrating various tools and handlers. These allow the bot to perform specific actions based on user inputs.

\n
    \n
  1. Define Tool Definitions:
  2. \n\n
  3. Implement Handlers:
  4. \n\n
  5. Integrate Tools with the Realtime Client:
  6. \n\n
\n


Key Components:

\n\n

Example:

\n

 

\n

 

\n

 

\n

 

\n
# Function Definitions\ncheck_order_status_def = {\n    \"name\": \"check_order_status\",\n    \"description\": \"Check the status of a customer's order\",\n    \"parameters\": {\n      \"type\": \"object\",\n      \"properties\": {\n        \"customer_id\": {\n          \"type\": \"string\",\n          \"description\": \"The unique identifier for the customer\"\n        },\n        \"order_id\": {\n          \"type\": \"string\",\n          \"description\": \"The unique identifier for the order\"\n        }\n      },\n      \"required\": [\"customer_id\", \"order_id\"]\n    }\n}
\n

 

\n

 

\n

 

\n

 

\n\n

Example:

\n

 

\n

 

\n

 

\n

 

\n
async def check_order_status_handler(customer_id, order_id):\n    status = \"In Transit\"\n    \n    # Your Business Logic\n    estimated_delivery, status, order_date =  fetch_order_details(order_id, customer_id)\n    # Read the HTML template\n    with open('order_status_template.html', 'r') as file:\n        html_content = file.read()\n\n    # Replace placeholders with actual data\n    html_content = html_content.format(\n        order_id=order_id,\n        customer_id=customer_id,\n        order_date=order_date.strftime(\"%B %d, %Y\"),\n        estimated_delivery=estimated_delivery.strftime(\"%B %d, %Y\"),\n        status=status\n    )\n\n    # Return the Chainlit message with HTML content\n    await cl.Message(content=f\"Here is the detail of your order \\n {html_content}\").send()\n    return f\"Order {order_id} status for customer {customer_id}: {status}\"\n  
\n

 

\n

 

\n

 

\n

 

\n

Reference:

\n\n

 

\n

Integrating with Your Application

\n

 
With the Realtime Client and tools in place, it's time to weave everything into your application.

\n
    \n
  1. Initialize OpenAI Realtime:
  2. \n\n
  3. Handle User Interactions:
  4. \n\n
  5. Manage Conversation Flow:
  6. \n\n
\n


Key Components:

\n\n

 

\n

 

\n

 

\n

 

\n
system_prompt = \"\"\"Provide helpful and empathetic support responses to customer inquiries for ShopMe in Hindi language, addressing their requests, concerns, or feedback professionally.\n\nMaintain a friendly and service-oriented tone throughout the interaction to ensure a positive customer experience.\n\n# Steps\n\n1. **Identify the Issue:** Carefully read the customer's inquiry to understand the problem or question they are presenting.\n2. **Gather Relevant Information:** Check for any additional data needed, such as order numbers or account details, while ensuring the privacy and security of the customer's information.\n3. **Formulate a Response:** Develop a solution or informative response based on the understanding of the issue. The response should be clear, concise, and address all parts of the customer's concern.\n4. **Offer Further Assistance:** Invite the customer to reach out again if they need more help or have additional questions.\n5. **Close Politely:** End the conversation with a polite closing statement that reinforces the service commitment of ShopMe.\n\n# Output Format\n\nProvide a clear and concise paragraph addressing the customer's inquiry, including:\n- Acknowledgment of their concern\n- Suggested solution or response\n- Offer for further assistance\n- Polite closing\n\n# Notes\n- Greet user with Welcome to ShopMe For the first time only\n- always speak in Hindi\n- Ensure all customer data is handled according to relevant privacy and data protection laws and ShopMe's privacy policy.\n- In cases of high sensitivity or complexity, escalate the issue to a human customer support agent.\n- Keep responses within a reasonable length to ensure they are easy to read and understand.\"\"\"
\n

 

\n

 

\n

 

\n

 

\n

 

\n\n

First we will i will initialize the real time client discussed before

\n

 

\n

 

\n

 

\n

 

\n
async def setup_openai_realtime(system_prompt: str):\n    \"\"\"Instantiate and configure the OpenAI Realtime Client\"\"\"\n    openai_realtime = RealtimeClient(system_prompt = system_prompt)\n    cl.user_session.set(\"track_id\", str(uuid4()))\n    async def handle_conversation_updated(event):\n        item = event.get(\"item\")\n        delta = event.get(\"delta\")\n        \"\"\"Currently used to stream audio back to the client.\"\"\"\n        if delta:\n            # Only one of the following will be populated for any given event\n            if 'audio' in delta:\n                audio = delta['audio']  # Int16Array, audio added\n                await cl.context.emitter.send_audio_chunk(cl.OutputAudioChunk(mimeType=\"pcm16\", data=audio, track=cl.user_session.get(\"track_id\")))\n            if 'transcript' in delta:\n                transcript = delta['transcript']  # string, transcript added\n                pass\n            if 'arguments' in delta:\n                arguments = delta['arguments']  # string, function arguments added\n                pass\n            \n    async def handle_item_completed(item):\n        \"\"\"Used to populate the chat context with transcription once an item is completed.\"\"\"\n        # print(item) # TODO\n        pass\n    \n    async def handle_conversation_interrupt(event):\n        \"\"\"Used to cancel the client previous audio playback.\"\"\"\n        cl.user_session.set(\"track_id\", str(uuid4()))\n        await cl.context.emitter.send_audio_interrupt()\n        \n    async def handle_error(event):\n        logger.error(event)
\n

 

\n

 

\n

 

\n

 

\n

 

\n\n

 

\n

 

\n

 

\n
if openai_realtime:            \n    if openai_realtime.is_connected():\n        await openai_realtime.append_input_audio(chunk.data)\n    else:\n        logger.info(\"RealtimeClient is not connected\")
\n

 

\n

 

\n

 

\n

Reference: app.py 

\n

Testing and Deployment

\n

 
Once your voice bot is built, thorough testing is essential to ensure reliability and user satisfaction.

\n
    \n
  1. Local Testing:
  2. \n\n
  3. Integration Testing:
  4. \n\n
  5. Deployment:
  6. \n\n
\n

 

\n

Conclusion

\n

 
Building a real-time voice bot has never been more accessible, thanks to the GPT-4o Realtime API. By consolidating speech-to-speech functionalities into a single, efficient interface, developers can craft engaging and natural conversational experiences without the complexity of managing multiple models. Whether you're enhancing customer support, developing educational tools, or creating interactive applications, the GPT-4o Realtime API provides a robust foundation to bring your voice bot visions to life. 

Embark on your development journey today and explore the endless possibilities that real-time voice interactions can offer your users!

\n

 
Feel free to refer to the Azure OpenAI GPT-4o Realtime API documentation for more detailed information on setup, deployment, and advanced configurations.

\n

 

\n

Github Link: https://github.com/monuminu/AOAI_Samples/tree/main/realtime-assistant-support

\n

 

\n

Thanks

\n

Manoranjan Rajguru

\n

https://www.linkedin.com/in/manoranjan-rajguru/

","body@stringLength":"47516","rawBody":"

Voice technology is transforming how we interact with machines, making conversations with AI feel more natural than ever before. With the public beta release of the Realtime API powered by GPT-4o, developers now have the tools to create low-latency, multimodal voice experiences in their apps, opening up endless possibilities for innovation.

\n

 

\n

Gone are the days when building a voice bot required stitching together multiple models for transcription, inference, and text-to-speech conversion. With the Realtime API, developers can now streamline the entire process with a single API call, enabling fluid, natural speech-to-speech conversations. This is a game-changer for industries like customer support, education, and real-time language translation, where fast, seamless interactions are crucial.

\n

 

\n

\n

 

\n

In this blog, we’ll guide you through the process of building your first real-time voice bot from scratch using the GPT-4o Realtime Model. We’ll cover key features of the Realtime API, how to set up a WebSocket connection for voice streaming, and how to leverage the API’s ability to handle interruptions and make function calls. By the end, you’ll be ready to create a voice bot that responds to users with near-human accuracy and emotion. Whether you're a beginner or an experienced developer, this blueprint will help you get started with creating immersive voice interactions that are both responsive and engaging. Ready to dive in? Let’s get started!

\n

 

\n

Key Features

\n

 

\n\n

 

\n

How GPT-4o Realtime API Works

\n

 

\n

 Traditionally, building a voice assistant required chaining together several models: an automatic speech recognition (ASR) model like Whisper for transcribing audio, a text-based model for processing responses, and a text-to-speech (TTS) model for generating audio outputs. This multi-step process often led to delays and a loss of emotional nuance.

The GPT-4o Realtime API revolutionizes this by consolidating these functionalities into a single API call. By establishing a persistent WebSocket connection, developers can stream audio inputs and outputs directly, significantly reducing latency and enhancing the naturalness of conversations. Additionally, the API's function calling capability allows the voice bot to perform actions such as placing orders or retrieving customer information on the fly.

\n

 

\n
 
\n

\n

 

\n
 
\n

 

\n

 

\n

Building Your Real-Time Voice Bot

\n

 
Let's dive into the step-by-step process of building your own real-time voice bot using the GPT-4o Realtime API.

\n

Prerequisites

\n

 
Before you begin, ensure you have the following:

\n\n

 

\n

Setting Up the API

\n

 

\n
    \n
  1. Deploy the GPT-4o Realtime Model:
  2. \n\n
  3. Configure Audio Input and Output:
  4. \n\n
\n

 

\n

This project demonstrates how to build a sophisticated real-time conversational AI system using Azure OpenAI. By leveraging WebSocket connections and an event-driven architecture, the system provides responsive and context-aware customer support in any language. This approach can be adapted to various languages and use cases, making it a versatile solution for businesses looking to enhance their customer service capabilities. The project consists of three main components:

\n\n

 

\n

Environment Setup

\n

 

\n

Create an .env file and update the following environment variables:

\n

 

\n

 

\n

 

\nAZURE_OPENAI_API_KEY=XXXX\n# replace with your Azure OpenAI API Key\n\nAZURE_OPENAI_ENDPOINT=https://xxxx.openai.azure.com/\n# replace with your Azure OpenAI Endpoint\n\nAZURE_OPENAI_DEPLOYMENT=gpt-4o-realtime-preview\n#Create a deployment for the gpt-4o-realtime-preview model and place the deployment name here. You can name the deployment as per your choice and put the name here.\n\nAZURE_OPENAI_CHAT_DEPLOYMENT_VERSION=2024-10-01-preview\n#You don't need to change this unless you are willing to try other versions.\n

 

\n

 

\n

 

\n

requirements.txt

\n

 

\n

 

\n

 

\nchainlit==1.3.0rc1\nopenai\nbeautifulsoup4\nlxml\npython-dotenv\nwebsockets\naiohttp\n

 

\n

 

\n

 

\n

 

\n

Implementing the Realtime Client

\n

 
The heartbeat of your voice bot is the Realtime Client, which manages the WebSocket connection and handles communication with the GPT-4o Realtime API. The RealtimeAPI class is responsible for managing WebSocket connections to OpenAI's real-time API. It handles sending and receiving messages, dispatching events, and maintaining the connection state.

\n
\n
\n
\n
\n
Key features include:
\n
 
\n
\n
\n
\n
\n
\n
\n
\n
\n
    \n
  • Connection Management: Establishes and maintains a WebSocket connection. 
  • \n
  • Event Dispatching: Uses an event-driven architecture to handle incoming and outgoing messages.
  • \n
  • Audio Processing: Convert audio inputs from base64 to array buffers and vice versa using utility functions. Manage audio streams efficiently to ensure minimal latency and high-quality voice interactions.
  • \n
\n
\n
\n
\n
\n
\n


Key Components:

\n\n

 

\n

 

\n

 

\nclass RealtimeAPI(RealtimeEventHandler):\n def __init__(self):\n super().__init__()\n self.default_url = 'wss://api.openai.com/v1/realtime'\n self.url = os.environ[\"AZURE_OPENAI_ENDPOINT\"]\n self.api_key = os.environ[\"AZURE_OPENAI_API_KEY\"]\n self.api_version = \"2024-10-01-preview\"\n self.azure_deployment = os.environ[\"AZURE_OPENAI_DEPLOYMENT\"]\n self.ws = None\n\n def is_connected(self):\n return self.ws is not None\n\n def log(self, *args):\n logger.debug(f\"[Websocket/{datetime.utcnow().isoformat()}]\", *args)\n\n async def connect(self, model='gpt-4o-realtime-preview'):\n if self.is_connected():\n raise Exception(\"Already connected\")\n self.ws = await websockets.connect(f\"{self.url}/openai/realtime?api-version={self.api_version}&deployment={model}&api-key={self.api_key}\", extra_headers={\n 'Authorization': f'Bearer {self.api_key}',\n 'OpenAI-Beta': 'realtime=v1'\n })\n self.log(f\"Connected to {self.url}\")\n asyncio.create_task(self._receive_messages())\n\n async def _receive_messages(self):\n async for message in self.ws:\n event = json.loads(message)\n if event['type'] == \"error\":\n logger.error(\"ERROR\", message)\n self.log(\"received:\", event)\n self.dispatch(f\"server.{event['type']}\", event)\n self.dispatch(\"server.*\", event)\n\n async def send(self, event_name, data=None):\n if not self.is_connected():\n raise Exception(\"RealtimeAPI is not connected\")\n data = data or {}\n if not isinstance(data, dict):\n raise Exception(\"data must be a dictionary\")\n event = {\n \"event_id\": self._generate_id(\"evt_\"),\n \"type\": event_name,\n **data\n }\n self.dispatch(f\"client.{event_name}\", event)\n self.dispatch(\"client.*\", event)\n self.log(\"sent:\", event)\n await self.ws.send(json.dumps(event))\n\n def _generate_id(self, prefix):\n return f\"{prefix}{int(datetime.utcnow().timestamp() * 1000)}\"\n\n async def disconnect(self):\n if self.ws:\n await self.ws.close()\n self.ws = None\n self.log(f\"Disconnected from {self.url}\")\n

 

\n

 

\n

 

\n

 

\n

Reference: init.py 

\n

 

\n\n

 

\n

 

\n

 

\nclass RealtimeConversation:\n default_frequency = config.features.audio.sample_rate\n \n EventProcessors = {\n 'conversation.item.created': lambda self, event: self._process_item_created(event),\n 'conversation.item.truncated': lambda self, event: self._process_item_truncated(event),\n 'conversation.item.deleted': lambda self, event: self._process_item_deleted(event),\n 'conversation.item.input_audio_transcription.completed': lambda self, event: self._process_input_audio_transcription_completed(event),\n 'input_audio_buffer.speech_started': lambda self, event: self._process_speech_started(event),\n 'input_audio_buffer.speech_stopped': lambda self, event, input_audio_buffer: self._process_speech_stopped(event, input_audio_buffer),\n 'response.created': lambda self, event: self._process_response_created(event),\n 'response.output_item.added': lambda self, event: self._process_output_item_added(event),\n 'response.output_item.done': lambda self, event: self._process_output_item_done(event),\n 'response.content_part.added': lambda self, event: self._process_content_part_added(event),\n 'response.audio_transcript.delta': lambda self, event: self._process_audio_transcript_delta(event),\n 'response.audio.delta': lambda self, event: self._process_audio_delta(event),\n 'response.text.delta': lambda self, event: self._process_text_delta(event),\n 'response.function_call_arguments.delta': lambda self, event: self._process_function_call_arguments_delta(event),\n }\n \n def __init__(self):\n self.clear()\n\n def clear(self):\n self.item_lookup = {}\n self.items = []\n self.response_lookup = {}\n self.responses = []\n self.queued_speech_items = {}\n self.queued_transcript_items = {}\n self.queued_input_audio = None\n\n def queue_input_audio(self, input_audio):\n self.queued_input_audio = input_audio\n\n def process_event(self, event, *args):\n event_processor = self.EventProcessors.get(event['type'])\n if not event_processor:\n raise Exception(f\"Missing conversation event processor for {event['type']}\")\n return event_processor(self, event, *args)\n\n def get_item(self, id):\n return self.item_lookup.get(id)\n\n def get_items(self):\n return self.items[:]\n\n def _process_item_created(self, event):\n item = event['item']\n new_item = item.copy()\n if new_item['id'] not in self.item_lookup:\n self.item_lookup[new_item['id']] = new_item\n self.items.append(new_item)\n new_item['formatted'] = {\n 'audio': [],\n 'text': '',\n 'transcript': ''\n }\n if new_item['id'] in self.queued_speech_items:\n new_item['formatted']['audio'] = self.queued_speech_items[new_item['id']]['audio']\n del self.queued_speech_items[new_item['id']]\n if 'content' in new_item:\n text_content = [c for c in new_item['content'] if c['type'] in ['text', 'input_text']]\n for content in text_content:\n new_item['formatted']['text'] += content['text']\n if new_item['id'] in self.queued_transcript_items:\n new_item['formatted']['transcript'] = self.queued_transcript_items[new_item['id']]['transcript']\n del self.queued_transcript_items[new_item['id']]\n if new_item['type'] == 'message':\n if new_item['role'] == 'user':\n new_item['status'] = 'completed'\n if self.queued_input_audio:\n new_item['formatted']['audio'] = self.queued_input_audio\n self.queued_input_audio = None\n else:\n new_item['status'] = 'in_progress'\n elif new_item['type'] == 'function_call':\n new_item['formatted']['tool'] = {\n 'type': 'function',\n 'name': new_item['name'],\n 'call_id': new_item['call_id'],\n 'arguments': ''\n }\n new_item['status'] = 'in_progress'\n elif new_item['type'] == 'function_call_output':\n new_item['status'] = 'completed'\n new_item['formatted']['output'] = new_item['output']\n return new_item, None\n\n def _process_item_truncated(self, event):\n item_id = event['item_id']\n audio_end_ms = event['audio_end_ms']\n item = self.item_lookup.get(item_id)\n if not item:\n raise Exception(f'item.truncated: Item \"{item_id}\" not found')\n end_index = (audio_end_ms * self.default_frequency) // 1000\n item['formatted']['transcript'] = ''\n item['formatted']['audio'] = item['formatted']['audio'][:end_index]\n return item, None\n\n def _process_item_deleted(self, event):\n item_id = event['item_id']\n item = self.item_lookup.get(item_id)\n if not item:\n raise Exception(f'item.deleted: Item \"{item_id}\" not found')\n del self.item_lookup[item['id']]\n self.items.remove(item)\n return item, None\n\n def _process_input_audio_transcription_completed(self, event):\n item_id = event['item_id']\n content_index = event['content_index']\n transcript = event['transcript']\n formatted_transcript = transcript or ' '\n item = self.item_lookup.get(item_id)\n if not item:\n self.queued_transcript_items[item_id] = {'transcript': formatted_transcript}\n return None, None\n item['content'][content_index]['transcript'] = transcript\n item['formatted']['transcript'] = formatted_transcript\n return item, {'transcript': transcript}\n\n def _process_speech_started(self, event):\n item_id = event['item_id']\n audio_start_ms = event['audio_start_ms']\n self.queued_speech_items[item_id] = {'audio_start_ms': audio_start_ms}\n return None, None\n\n def _process_speech_stopped(self, event, input_audio_buffer):\n item_id = event['item_id']\n audio_end_ms = event['audio_end_ms']\n speech = self.queued_speech_items[item_id]\n speech['audio_end_ms'] = audio_end_ms\n if input_audio_buffer:\n start_index = (speech['audio_start_ms'] * self.default_frequency) // 1000\n end_index = (speech['audio_end_ms'] * self.default_frequency) // 1000\n speech['audio'] = input_audio_buffer[start_index:end_index]\n return None, None\n\n def _process_response_created(self, event):\n response = event['response']\n if response['id'] not in self.response_lookup:\n self.response_lookup[response['id']] = response\n self.responses.append(response)\n return None, None\n\n def _process_output_item_added(self, event):\n response_id = event['response_id']\n item = event['item']\n response = self.response_lookup.get(response_id)\n if not response:\n raise Exception(f'response.output_item.added: Response \"{response_id}\" not found')\n response['output'].append(item['id'])\n return None, None\n\n def _process_output_item_done(self, event):\n item = event['item']\n if not item:\n raise Exception('response.output_item.done: Missing \"item\"')\n found_item = self.item_lookup.get(item['id'])\n if not found_item:\n raise Exception(f'response.output_item.done: Item \"{item[\"id\"]}\" not found')\n found_item['status'] = item['status']\n return found_item, None\n\n def _process_content_part_added(self, event):\n item_id = event['item_id']\n part = event['part']\n item = self.item_lookup.get(item_id)\n if not item:\n raise Exception(f'response.content_part.added: Item \"{item_id}\" not found')\n item['content'].append(part)\n return item, None\n\n def _process_audio_transcript_delta(self, event):\n item_id = event['item_id']\n content_index = event['content_index']\n delta = event['delta']\n item = self.item_lookup.get(item_id)\n if not item:\n raise Exception(f'response.audio_transcript.delta: Item \"{item_id}\" not found')\n item['content'][content_index]['transcript'] += delta\n item['formatted']['transcript'] += delta\n return item, {'transcript': delta}\n\n def _process_audio_delta(self, event):\n item_id = event['item_id']\n content_index = event['content_index']\n delta = event['delta']\n item = self.item_lookup.get(item_id)\n if not item:\n logger.debug(f'response.audio.delta: Item \"{item_id}\" not found')\n return None, None\n array_buffer = base64_to_array_buffer(delta)\n append_values = array_buffer.tobytes()\n # TODO: make it work\n # item['formatted']['audio'] = merge_int16_arrays(item['formatted']['audio'], append_values)\n return item, {'audio': append_values}\n\n def _process_text_delta(self, event):\n item_id = event['item_id']\n content_index = event['content_index']\n delta = event['delta']\n item = self.item_lookup.get(item_id)\n if not item:\n raise Exception(f'response.text.delta: Item \"{item_id}\" not found')\n item['content'][content_index]['text'] += delta\n item['formatted']['text'] += delta\n return item, {'text': delta}\n\n def _process_function_call_arguments_delta(self, event):\n item_id = event['item_id']\n delta = event['delta']\n item = self.item_lookup.get(item_id)\n if not item:\n raise Exception(f'response.function_call_arguments.delta: Item \"{item_id}\" not found')\n item['arguments'] += delta\n item['formatted']['tool']['arguments'] += delta\n return item, {'arguments': delta}\n

 

\n

 

\n

 

\n

 

\n\n

 

\n

 

\n

 

\n

 

\nclass RealtimeClient(RealtimeEventHandler):\n def __init__(self, system_prompt: str):\n super().__init__()\n self.system_prompt = system_prompt\n self.default_session_config = {\n \"modalities\": [\"text\", \"audio\"],\n \"instructions\": self.system_prompt,\n \"voice\": \"shimmer\",\n \"input_audio_format\": \"pcm16\",\n \"output_audio_format\": \"pcm16\",\n \"input_audio_transcription\": { \"model\": 'whisper-1' },\n \"turn_detection\": { \"type\": 'server_vad' },\n \"tools\": [],\n \"tool_choice\": \"auto\",\n \"temperature\": 0.8,\n \"max_response_output_tokens\": 4096,\n }\n self.session_config = {}\n self.transcription_models = [{\"model\": \"whisper-1\"}]\n self.default_server_vad_config = {\n \"type\": \"server_vad\",\n \"threshold\": 0.5,\n \"prefix_padding_ms\": 300,\n \"silence_duration_ms\": 200,\n }\n self.realtime = RealtimeAPI()\n self.conversation = RealtimeConversation()\n self._reset_config()\n self._add_api_event_handlers()\n \n def _reset_config(self):\n self.session_created = False\n self.tools = {}\n self.session_config = self.default_session_config.copy()\n self.input_audio_buffer = bytearray()\n return True\n\n def _add_api_event_handlers(self):\n self.realtime.on(\"client.*\", self._log_event)\n self.realtime.on(\"server.*\", self._log_event)\n self.realtime.on(\"server.session.created\", self._on_session_created)\n self.realtime.on(\"server.response.created\", self._process_event)\n self.realtime.on(\"server.response.output_item.added\", self._process_event)\n self.realtime.on(\"server.response.content_part.added\", self._process_event)\n self.realtime.on(\"server.input_audio_buffer.speech_started\", self._on_speech_started)\n self.realtime.on(\"server.input_audio_buffer.speech_stopped\", self._on_speech_stopped)\n self.realtime.on(\"server.conversation.item.created\", self._on_item_created)\n self.realtime.on(\"server.conversation.item.truncated\", self._process_event)\n self.realtime.on(\"server.conversation.item.deleted\", self._process_event)\n self.realtime.on(\"server.conversation.item.input_audio_transcription.completed\", self._process_event)\n self.realtime.on(\"server.response.audio_transcript.delta\", self._process_event)\n self.realtime.on(\"server.response.audio.delta\", self._process_event)\n self.realtime.on(\"server.response.text.delta\", self._process_event)\n self.realtime.on(\"server.response.function_call_arguments.delta\", self._process_event)\n self.realtime.on(\"server.response.output_item.done\", self._on_output_item_done)\n\n def _log_event(self, event):\n realtime_event = {\n \"time\": datetime.utcnow().isoformat(),\n \"source\": \"client\" if event[\"type\"].startswith(\"client.\") else \"server\",\n \"event\": event,\n }\n self.dispatch(\"realtime.event\", realtime_event)\n\n def _on_session_created(self, event):\n self.session_created = True\n\n def _process_event(self, event, *args):\n item, delta = self.conversation.process_event(event, *args)\n if item:\n self.dispatch(\"conversation.updated\", {\"item\": item, \"delta\": delta})\n return item, delta\n\n def _on_speech_started(self, event):\n self._process_event(event)\n self.dispatch(\"conversation.interrupted\", event)\n\n def _on_speech_stopped(self, event):\n self._process_event(event, self.input_audio_buffer)\n\n def _on_item_created(self, event):\n item, delta = self._process_event(event)\n self.dispatch(\"conversation.item.appended\", {\"item\": item})\n if item and item[\"status\"] == \"completed\":\n self.dispatch(\"conversation.item.completed\", {\"item\": item})\n\n async def _on_output_item_done(self, event):\n item, delta = self._process_event(event)\n if item and item[\"status\"] == \"completed\":\n self.dispatch(\"conversation.item.completed\", {\"item\": item})\n if item and item.get(\"formatted\", {}).get(\"tool\"):\n await self._call_tool(item[\"formatted\"][\"tool\"])\n\n async def _call_tool(self, tool):\n try:\n print(tool[\"arguments\"])\n json_arguments = json.loads(tool[\"arguments\"])\n tool_config = self.tools.get(tool[\"name\"])\n if not tool_config:\n raise Exception(f'Tool \"{tool[\"name\"]}\" has not been added')\n result = await tool_config[\"handler\"](**json_arguments)\n await self.realtime.send(\"conversation.item.create\", {\n \"item\": {\n \"type\": \"function_call_output\",\n \"call_id\": tool[\"call_id\"],\n \"output\": json.dumps(result),\n }\n })\n except Exception as e:\n logger.error(traceback.format_exc())\n await self.realtime.send(\"conversation.item.create\", {\n \"item\": {\n \"type\": \"function_call_output\",\n \"call_id\": tool[\"call_id\"],\n \"output\": json.dumps({\"error\": str(e)}),\n }\n })\n await self.create_response()\n\n def is_connected(self):\n return self.realtime.is_connected()\n\n def reset(self):\n self.disconnect()\n self.realtime.clear_event_handlers()\n self._reset_config()\n self._add_api_event_handlers()\n return True\n\n async def connect(self):\n if self.is_connected():\n raise Exception(\"Already connected, use .disconnect() first\")\n await self.realtime.connect()\n await self.update_session()\n return True\n\n async def wait_for_session_created(self):\n if not self.is_connected():\n raise Exception(\"Not connected, use .connect() first\")\n while not self.session_created:\n await asyncio.sleep(0.001)\n return True\n\n async def disconnect(self):\n self.session_created = False\n self.conversation.clear()\n if self.realtime.is_connected():\n await self.realtime.disconnect()\n\n def get_turn_detection_type(self):\n return self.session_config.get(\"turn_detection\", {}).get(\"type\")\n\n async def add_tool(self, definition, handler):\n if not definition.get(\"name\"):\n raise Exception(\"Missing tool name in definition\")\n name = definition[\"name\"]\n if name in self.tools:\n raise Exception(f'Tool \"{name}\" already added. Please use .removeTool(\"{name}\") before trying to add again.')\n if not callable(handler):\n raise Exception(f'Tool \"{name}\" handler must be a function')\n self.tools[name] = {\"definition\": definition, \"handler\": handler}\n await self.update_session()\n return self.tools[name]\n\n def remove_tool(self, name):\n if name not in self.tools:\n raise Exception(f'Tool \"{name}\" does not exist, can not be removed.')\n del self.tools[name]\n return True\n\n async def delete_item(self, id):\n await self.realtime.send(\"conversation.item.delete\", {\"item_id\": id})\n return True\n\n async def update_session(self, **kwargs):\n self.session_config.update(kwargs)\n use_tools = [\n {**tool_definition, \"type\": \"function\"}\n for tool_definition in self.session_config.get(\"tools\", [])\n ] + [\n {**self.tools[key][\"definition\"], \"type\": \"function\"}\n for key in self.tools\n ]\n session = {**self.session_config, \"tools\": use_tools}\n if self.realtime.is_connected():\n await self.realtime.send(\"session.update\", {\"session\": session})\n return True\n \n async def create_conversation_item(self, item):\n await self.realtime.send(\"conversation.item.create\", {\n \"item\": item\n })\n\n async def send_user_message_content(self, content=[]):\n if content:\n for c in content:\n if c[\"type\"] == \"input_audio\":\n if isinstance(c[\"audio\"], (bytes, bytearray)):\n c[\"audio\"] = array_buffer_to_base64(c[\"audio\"])\n await self.realtime.send(\"conversation.item.create\", {\n \"item\": {\n \"type\": \"message\",\n \"role\": \"user\",\n \"content\": content,\n }\n })\n await self.create_response()\n return True\n\n async def append_input_audio(self, array_buffer):\n if len(array_buffer) > 0:\n await self.realtime.send(\"input_audio_buffer.append\", {\n \"audio\": array_buffer_to_base64(np.array(array_buffer)),\n })\n self.input_audio_buffer.extend(array_buffer)\n return True\n\n async def create_response(self):\n if self.get_turn_detection_type() is None and len(self.input_audio_buffer) > 0:\n await self.realtime.send(\"input_audio_buffer.commit\")\n self.conversation.queue_input_audio(self.input_audio_buffer)\n self.input_audio_buffer = bytearray()\n await self.realtime.send(\"response.create\")\n return True\n\n async def cancel_response(self, id=None, sample_count=0):\n if not id:\n await self.realtime.send(\"response.cancel\")\n return {\"item\": None}\n else:\n item = self.conversation.get_item(id)\n if not item:\n raise Exception(f'Could not find item \"{id}\"')\n if item[\"type\"] != \"message\":\n raise Exception('Can only cancelResponse messages with type \"message\"')\n if item[\"role\"] != \"assistant\":\n raise Exception('Can only cancelResponse messages with role \"assistant\"')\n await self.realtime.send(\"response.cancel\")\n audio_index = next((i for i, c in enumerate(item[\"content\"]) if c[\"type\"] == \"audio\"), -1)\n if audio_index == -1:\n raise Exception(\"Could not find audio on item to cancel\")\n await self.realtime.send(\"conversation.item.truncate\", {\n \"item_id\": id,\n \"content_index\": audio_index,\n \"audio_end_ms\": int((sample_count / self.conversation.default_frequency) * 1000),\n })\n return {\"item\": item}\n\n async def wait_for_next_item(self):\n event = await self.wait_for_next(\"conversation.item.appended\")\n return {\"item\": event[\"item\"]}\n\n async def wait_for_next_completed_item(self):\n event = await self.wait_for_next(\"conversation.item.completed\")\n return {\"item\": event[\"item\"]}\n

 

\n

 

\n

 

\n

 

\n

Adding Tools and Handlers

\n

 
Your voice bot's functionality can be extended by integrating various tools and handlers. These allow the bot to perform specific actions based on user inputs.

\n
    \n
  1. Define Tool Definitions:
  2. \n\n
  3. Implement Handlers:
  4. \n\n
  5. Integrate Tools with the Realtime Client:
  6. \n\n
\n


Key Components:

\n\n

Example:

\n

 

\n

 

\n

 

\n

 

\n# Function Definitions\ncheck_order_status_def = {\n \"name\": \"check_order_status\",\n \"description\": \"Check the status of a customer's order\",\n \"parameters\": {\n \"type\": \"object\",\n \"properties\": {\n \"customer_id\": {\n \"type\": \"string\",\n \"description\": \"The unique identifier for the customer\"\n },\n \"order_id\": {\n \"type\": \"string\",\n \"description\": \"The unique identifier for the order\"\n }\n },\n \"required\": [\"customer_id\", \"order_id\"]\n }\n}\n

 

\n

 

\n

 

\n

 

\n\n

Example:

\n

 

\n

 

\n

 

\n

 

\nasync def check_order_status_handler(customer_id, order_id):\n status = \"In Transit\"\n \n # Your Business Logic\n estimated_delivery, status, order_date = fetch_order_details(order_id, customer_id)\n # Read the HTML template\n with open('order_status_template.html', 'r') as file:\n html_content = file.read()\n\n # Replace placeholders with actual data\n html_content = html_content.format(\n order_id=order_id,\n customer_id=customer_id,\n order_date=order_date.strftime(\"%B %d, %Y\"),\n estimated_delivery=estimated_delivery.strftime(\"%B %d, %Y\"),\n status=status\n )\n\n # Return the Chainlit message with HTML content\n await cl.Message(content=f\"Here is the detail of your order \\n {html_content}\").send()\n return f\"Order {order_id} status for customer {customer_id}: {status}\"\n \n

 

\n

 

\n

 

\n

 

\n

Reference:

\n\n

 

\n

Integrating with Your Application

\n

 
With the Realtime Client and tools in place, it's time to weave everything into your application.

\n
    \n
  1. Initialize OpenAI Realtime:
  2. \n\n
  3. Handle User Interactions:
  4. \n\n
  5. Manage Conversation Flow:
  6. \n\n
\n


Key Components:

\n\n

 

\n

 

\n

 

\n

 

\nsystem_prompt = \"\"\"Provide helpful and empathetic support responses to customer inquiries for ShopMe in Hindi language, addressing their requests, concerns, or feedback professionally.\n\nMaintain a friendly and service-oriented tone throughout the interaction to ensure a positive customer experience.\n\n# Steps\n\n1. **Identify the Issue:** Carefully read the customer's inquiry to understand the problem or question they are presenting.\n2. **Gather Relevant Information:** Check for any additional data needed, such as order numbers or account details, while ensuring the privacy and security of the customer's information.\n3. **Formulate a Response:** Develop a solution or informative response based on the understanding of the issue. The response should be clear, concise, and address all parts of the customer's concern.\n4. **Offer Further Assistance:** Invite the customer to reach out again if they need more help or have additional questions.\n5. **Close Politely:** End the conversation with a polite closing statement that reinforces the service commitment of ShopMe.\n\n# Output Format\n\nProvide a clear and concise paragraph addressing the customer's inquiry, including:\n- Acknowledgment of their concern\n- Suggested solution or response\n- Offer for further assistance\n- Polite closing\n\n# Notes\n- Greet user with Welcome to ShopMe For the first time only\n- always speak in Hindi\n- Ensure all customer data is handled according to relevant privacy and data protection laws and ShopMe's privacy policy.\n- In cases of high sensitivity or complexity, escalate the issue to a human customer support agent.\n- Keep responses within a reasonable length to ensure they are easy to read and understand.\"\"\"\n

 

\n

 

\n

 

\n

 

\n

 

\n\n

First we will i will initialize the real time client discussed before

\n

 

\n

 

\n

 

\n

 

\nasync def setup_openai_realtime(system_prompt: str):\n \"\"\"Instantiate and configure the OpenAI Realtime Client\"\"\"\n openai_realtime = RealtimeClient(system_prompt = system_prompt)\n cl.user_session.set(\"track_id\", str(uuid4()))\n async def handle_conversation_updated(event):\n item = event.get(\"item\")\n delta = event.get(\"delta\")\n \"\"\"Currently used to stream audio back to the client.\"\"\"\n if delta:\n # Only one of the following will be populated for any given event\n if 'audio' in delta:\n audio = delta['audio'] # Int16Array, audio added\n await cl.context.emitter.send_audio_chunk(cl.OutputAudioChunk(mimeType=\"pcm16\", data=audio, track=cl.user_session.get(\"track_id\")))\n if 'transcript' in delta:\n transcript = delta['transcript'] # string, transcript added\n pass\n if 'arguments' in delta:\n arguments = delta['arguments'] # string, function arguments added\n pass\n \n async def handle_item_completed(item):\n \"\"\"Used to populate the chat context with transcription once an item is completed.\"\"\"\n # print(item) # TODO\n pass\n \n async def handle_conversation_interrupt(event):\n \"\"\"Used to cancel the client previous audio playback.\"\"\"\n cl.user_session.set(\"track_id\", str(uuid4()))\n await cl.context.emitter.send_audio_interrupt()\n \n async def handle_error(event):\n logger.error(event)\n

 

\n

 

\n

 

\n

 

\n

 

\n\n

 

\n

 

\n

 

\nif openai_realtime: \n if openai_realtime.is_connected():\n await openai_realtime.append_input_audio(chunk.data)\n else:\n logger.info(\"RealtimeClient is not connected\")\n

 

\n

 

\n

 

\n

Reference: app.py 

\n

Testing and Deployment

\n

 
Once your voice bot is built, thorough testing is essential to ensure reliability and user satisfaction.

\n
    \n
  1. Local Testing:
  2. \n\n
  3. Integration Testing:
  4. \n\n
  5. Deployment:
  6. \n\n
\n

 

\n

Conclusion

\n

 
Building a real-time voice bot has never been more accessible, thanks to the GPT-4o Realtime API. By consolidating speech-to-speech functionalities into a single, efficient interface, developers can craft engaging and natural conversational experiences without the complexity of managing multiple models. Whether you're enhancing customer support, developing educational tools, or creating interactive applications, the GPT-4o Realtime API provides a robust foundation to bring your voice bot visions to life. 

Embark on your development journey today and explore the endless possibilities that real-time voice interactions can offer your users!

\n

 
Feel free to refer to the Azure OpenAI GPT-4o Realtime API documentation for more detailed information on setup, deployment, and advanced configurations.

\n

 

\n

Github Link: https://github.com/monuminu/AOAI_Samples/tree/main/realtime-assistant-support

\n

 

\n

Thanks

\n

Manoranjan Rajguru

\n

https://www.linkedin.com/in/manoranjan-rajguru/

","kudosSumWeight":3,"postTime":"2024-10-12T09:42:44.174-07:00","images":{"__typename":"AssociatedImageConnection","edges":[{"__typename":"AssociatedImageEdge","cursor":"MjUuMXwyLjF8b3wyNXxfTlZffDE","node":{"__ref":"AssociatedImage:{\"url\":\"https://techcommunity.microsoft.com/t5/s/gxcuf89792/images/bS00MjY5MDM4LTYyODY0M2kwQkNBN0UzODhGQkU3MzQ1?revision=11\"}"}}],"totalCount":1,"pageInfo":{"__typename":"PageInfo","hasNextPage":false,"endCursor":null,"hasPreviousPage":false,"startCursor":null}},"attachments":{"__typename":"AttachmentConnection","pageInfo":{"__typename":"PageInfo","hasNextPage":false,"endCursor":null,"hasPreviousPage":false,"startCursor":null},"edges":[]},"tags":{"__typename":"TagConnection","pageInfo":{"__typename":"PageInfo","hasNextPage":false,"endCursor":null,"hasPreviousPage":false,"startCursor":null},"edges":[{"__typename":"TagEdge","cursor":"MjUuMXwyLjF8b3wxMHxfTlZffDE","node":{"__typename":"Tag","id":"tag:azure ai studio","text":"azure ai studio","time":"2023-11-11T00:57:52.231-08:00","lastActivityTime":null,"messagesCount":null,"followersCount":null}},{"__typename":"TagEdge","cursor":"MjUuMXwyLjF8b3wxMHxfTlZffDI","node":{"__typename":"Tag","id":"tag:azure openai service","text":"azure openai service","time":"2022-12-14T08:49:09.396-08:00","lastActivityTime":null,"messagesCount":null,"followersCount":null}}]},"timeToRead":19,"rawTeaser":"

Ever wondered how to create a voice bot that interacts with users in real-time, just like a human? In this beginner-friendly guide, we’ll take you through the step-by-step process of building your own intelligent voice bot using GPT-4o Realtime API. From setting up your environment to integrating speech recognition and conversational AI, this blueprint is designed to help you go from zero to launch in no time. Whether you're a developer or a tech enthusiast, this is your chance to dive into the future of conversational AI!

","introduction":"","coverImage":null,"coverImageProperties":{"__typename":"CoverImageProperties","style":"STANDARD","titlePosition":"BOTTOM","altText":""},"currentRevision":{"__ref":"Revision:revision:4269038_11"},"latestVersion":{"__typename":"FriendlyVersion","major":"7","minor":"0"},"metrics":{"__typename":"MessageMetrics","views":18768},"visibilityScope":"PUBLIC","canonicalUrl":null,"seoTitle":null,"seoDescription":null,"placeholder":false,"originalMessageForPlaceholder":null,"contributors":{"__typename":"UserConnection","edges":[]},"nonCoAuthorContributors":{"__typename":"UserConnection","edges":[]},"coAuthors":{"__typename":"UserConnection","edges":[]},"blogMessagePolicies":{"__typename":"BlogMessagePolicies","canDoAuthoringActionsOnBlog":{"__typename":"PolicyResult","failureReason":{"__typename":"FailureReason","message":"error.lithium.policies.blog.action_can_do_authoring_action.accessDenied","key":"error.lithium.policies.blog.action_can_do_authoring_action.accessDenied","args":[]}}},"archivalData":null,"replies":{"__typename":"MessageConnection","edges":[{"__typename":"MessageEdge","cursor":"MjUuMXwyLjF8aXwxMHwxMzI6MHxpbnQsNDI5ODE3NCw0Mjk4MTc0","node":{"__ref":"BlogReplyMessage:message:4298174"}},{"__typename":"MessageEdge","cursor":"MjUuMXwyLjF8aXwxMHwxMzI6MHxpbnQsNDI5ODE3NCw0MjgyNjQy","node":{"__ref":"BlogReplyMessage:message:4282642"}},{"__typename":"MessageEdge","cursor":"MjUuMXwyLjF8aXwxMHwxMzI6MHxpbnQsNDI5ODE3NCw0Mjc0Nzc3","node":{"__ref":"BlogReplyMessage:message:4274777"}},{"__typename":"MessageEdge","cursor":"MjUuMXwyLjF8aXwxMHwxMzI6MHxpbnQsNDI5ODE3NCw0MjcwNzU0","node":{"__ref":"BlogReplyMessage:message:4270754"}},{"__typename":"MessageEdge","cursor":"MjUuMXwyLjF8aXwxMHwxMzI6MHxpbnQsNDI5ODE3NCw0MjcwNzUz","node":{"__ref":"BlogReplyMessage:message:4270753"}},{"__typename":"MessageEdge","cursor":"MjUuMXwyLjF8aXwxMHwxMzI6MHxpbnQsNDI5ODE3NCw0MjcwNTk4","node":{"__ref":"BlogReplyMessage:message:4270598"}},{"__typename":"MessageEdge","cursor":"MjUuMXwyLjF8aXwxMHwxMzI6MHxpbnQsNDI5ODE3NCw0MjY5MjM0","node":{"__ref":"BlogReplyMessage:message:4269234"}}],"pageInfo":{"__typename":"PageInfo","hasNextPage":false,"endCursor":null,"hasPreviousPage":false,"startCursor":null}},"customFields":[],"revisions({\"constraints\":{\"isPublished\":{\"eq\":true}},\"first\":1})":{"__typename":"RevisionConnection","totalCount":11}},"Conversation:conversation:4269038":{"__typename":"Conversation","id":"conversation:4269038","solved":false,"topic":{"__ref":"BlogTopicMessage:message:4269038"},"lastPostingActivityTime":"2024-11-15T07:26:14.664-08:00","lastPostTime":"2024-11-15T07:26:14.664-08:00","unreadReplyCount":9,"isSubscribed":false},"ModerationData:moderation_data:4269038":{"__typename":"ModerationData","id":"moderation_data:4269038","status":"APPROVED","rejectReason":null,"isReportedAbuse":false,"rejectUser":null,"rejectTime":null,"rejectActorType":null},"AssociatedImage:{\"url\":\"https://techcommunity.microsoft.com/t5/s/gxcuf89792/images/bS00MjY5MDM4LTYyODY0M2kwQkNBN0UzODhGQkU3MzQ1?revision=11\"}":{"__typename":"AssociatedImage","url":"https://techcommunity.microsoft.com/t5/s/gxcuf89792/images/bS00MjY5MDM4LTYyODY0M2kwQkNBN0UzODhGQkU3MzQ1?revision=11","title":"mrajguru_2-1728752379927.png","associationType":"BODY","width":994,"height":1089,"altText":null},"Revision:revision:4269038_11":{"__typename":"Revision","id":"revision:4269038_11","lastEditTime":"2024-10-13T23:13:03.254-07:00"},"CachedAsset:theme:customTheme1-1743150526467":{"__typename":"CachedAsset","id":"theme:customTheme1-1743150526467","value":{"id":"customTheme1","animation":{"fast":"150ms","normal":"250ms","slow":"500ms","slowest":"750ms","function":"cubic-bezier(0.07, 0.91, 0.51, 1)","__typename":"AnimationThemeSettings"},"avatar":{"borderRadius":"50%","collections":["default"],"__typename":"AvatarThemeSettings"},"basics":{"browserIcon":{"imageAssetName":"favicon-1730836283320.png","imageLastModified":"1730836286415","__typename":"ThemeAsset"},"customerLogo":{"imageAssetName":"favicon-1730836271365.png","imageLastModified":"1730836274203","__typename":"ThemeAsset"},"maximumWidthOfPageContent":"1300px","oneColumnNarrowWidth":"800px","gridGutterWidthMd":"30px","gridGutterWidthXs":"10px","pageWidthStyle":"WIDTH_OF_BROWSER","__typename":"BasicsThemeSettings"},"buttons":{"borderRadiusSm":"3px","borderRadius":"3px","borderRadiusLg":"5px","paddingY":"5px","paddingYLg":"7px","paddingYHero":"var(--lia-bs-btn-padding-y-lg)","paddingX":"12px","paddingXLg":"16px","paddingXHero":"60px","fontStyle":"NORMAL","fontWeight":"700","textTransform":"NONE","disabledOpacity":0.5,"primaryTextColor":"var(--lia-bs-white)","primaryTextHoverColor":"var(--lia-bs-white)","primaryTextActiveColor":"var(--lia-bs-white)","primaryBgColor":"var(--lia-bs-primary)","primaryBgHoverColor":"hsl(var(--lia-bs-primary-h), var(--lia-bs-primary-s), calc(var(--lia-bs-primary-l) * 0.85))","primaryBgActiveColor":"hsl(var(--lia-bs-primary-h), var(--lia-bs-primary-s), calc(var(--lia-bs-primary-l) * 0.7))","primaryBorder":"1px solid transparent","primaryBorderHover":"1px solid transparent","primaryBorderActive":"1px solid transparent","primaryBorderFocus":"1px solid var(--lia-bs-white)","primaryBoxShadowFocus":"0 0 0 1px var(--lia-bs-primary), 0 0 0 4px hsla(var(--lia-bs-primary-h), var(--lia-bs-primary-s), var(--lia-bs-primary-l), 0.2)","secondaryTextColor":"var(--lia-bs-gray-900)","secondaryTextHoverColor":"hsl(var(--lia-bs-gray-900-h), var(--lia-bs-gray-900-s), calc(var(--lia-bs-gray-900-l) * 0.95))","secondaryTextActiveColor":"hsl(var(--lia-bs-gray-900-h), var(--lia-bs-gray-900-s), calc(var(--lia-bs-gray-900-l) * 0.9))","secondaryBgColor":"var(--lia-bs-gray-200)","secondaryBgHoverColor":"hsl(var(--lia-bs-gray-200-h), var(--lia-bs-gray-200-s), calc(var(--lia-bs-gray-200-l) * 0.96))","secondaryBgActiveColor":"hsl(var(--lia-bs-gray-200-h), var(--lia-bs-gray-200-s), calc(var(--lia-bs-gray-200-l) * 0.92))","secondaryBorder":"1px solid transparent","secondaryBorderHover":"1px solid transparent","secondaryBorderActive":"1px solid transparent","secondaryBorderFocus":"1px solid transparent","secondaryBoxShadowFocus":"0 0 0 1px var(--lia-bs-primary), 0 0 0 4px hsla(var(--lia-bs-primary-h), var(--lia-bs-primary-s), var(--lia-bs-primary-l), 0.2)","tertiaryTextColor":"var(--lia-bs-gray-900)","tertiaryTextHoverColor":"hsl(var(--lia-bs-gray-900-h), var(--lia-bs-gray-900-s), calc(var(--lia-bs-gray-900-l) * 0.95))","tertiaryTextActiveColor":"hsl(var(--lia-bs-gray-900-h), var(--lia-bs-gray-900-s), calc(var(--lia-bs-gray-900-l) * 0.9))","tertiaryBgColor":"transparent","tertiaryBgHoverColor":"transparent","tertiaryBgActiveColor":"hsla(var(--lia-bs-black-h), var(--lia-bs-black-s), var(--lia-bs-black-l), 0.04)","tertiaryBorder":"1px solid transparent","tertiaryBorderHover":"1px solid hsla(var(--lia-bs-black-h), var(--lia-bs-black-s), var(--lia-bs-black-l), 0.08)","tertiaryBorderActive":"1px solid transparent","tertiaryBorderFocus":"1px solid transparent","tertiaryBoxShadowFocus":"0 0 0 1px var(--lia-bs-primary), 0 0 0 4px hsla(var(--lia-bs-primary-h), var(--lia-bs-primary-s), var(--lia-bs-primary-l), 0.2)","destructiveTextColor":"var(--lia-bs-danger)","destructiveTextHoverColor":"hsl(var(--lia-bs-danger-h), var(--lia-bs-danger-s), calc(var(--lia-bs-danger-l) * 0.95))","destructiveTextActiveColor":"hsl(var(--lia-bs-danger-h), var(--lia-bs-danger-s), calc(var(--lia-bs-danger-l) * 0.9))","destructiveBgColor":"var(--lia-bs-gray-200)","destructiveBgHoverColor":"hsl(var(--lia-bs-gray-200-h), var(--lia-bs-gray-200-s), calc(var(--lia-bs-gray-200-l) * 0.96))","destructiveBgActiveColor":"hsl(var(--lia-bs-gray-200-h), var(--lia-bs-gray-200-s), calc(var(--lia-bs-gray-200-l) * 0.92))","destructiveBorder":"1px solid transparent","destructiveBorderHover":"1px solid transparent","destructiveBorderActive":"1px solid transparent","destructiveBorderFocus":"1px solid transparent","destructiveBoxShadowFocus":"0 0 0 1px var(--lia-bs-primary), 0 0 0 4px hsla(var(--lia-bs-primary-h), var(--lia-bs-primary-s), var(--lia-bs-primary-l), 0.2)","__typename":"ButtonsThemeSettings"},"border":{"color":"hsla(var(--lia-bs-black-h), var(--lia-bs-black-s), var(--lia-bs-black-l), 0.08)","mainContent":"NONE","sideContent":"LIGHT","radiusSm":"3px","radius":"5px","radiusLg":"9px","radius50":"100vw","__typename":"BorderThemeSettings"},"boxShadow":{"xs":"0 0 0 1px hsla(var(--lia-bs-gray-900-h), var(--lia-bs-gray-900-s), var(--lia-bs-gray-900-l), 0.08), 0 3px 0 -1px hsla(var(--lia-bs-gray-900-h), var(--lia-bs-gray-900-s), var(--lia-bs-gray-900-l), 0.16)","sm":"0 2px 4px hsla(var(--lia-bs-gray-900-h), var(--lia-bs-gray-900-s), var(--lia-bs-gray-900-l), 0.12)","md":"0 5px 15px hsla(var(--lia-bs-gray-900-h), var(--lia-bs-gray-900-s), var(--lia-bs-gray-900-l), 0.3)","lg":"0 10px 30px hsla(var(--lia-bs-gray-900-h), var(--lia-bs-gray-900-s), var(--lia-bs-gray-900-l), 0.3)","__typename":"BoxShadowThemeSettings"},"cards":{"bgColor":"var(--lia-panel-bg-color)","borderRadius":"var(--lia-panel-border-radius)","boxShadow":"var(--lia-box-shadow-xs)","__typename":"CardsThemeSettings"},"chip":{"maxWidth":"300px","height":"30px","__typename":"ChipThemeSettings"},"coreTypes":{"defaultMessageLinkColor":"var(--lia-bs-link-color)","defaultMessageLinkDecoration":"none","defaultMessageLinkFontStyle":"NORMAL","defaultMessageLinkFontWeight":"400","defaultMessageFontStyle":"NORMAL","defaultMessageFontWeight":"400","forumColor":"#4099E2","forumFontFamily":"var(--lia-bs-font-family-base)","forumFontWeight":"var(--lia-default-message-font-weight)","forumLineHeight":"var(--lia-bs-line-height-base)","forumFontStyle":"var(--lia-default-message-font-style)","forumMessageLinkColor":"var(--lia-default-message-link-color)","forumMessageLinkDecoration":"var(--lia-default-message-link-decoration)","forumMessageLinkFontStyle":"var(--lia-default-message-link-font-style)","forumMessageLinkFontWeight":"var(--lia-default-message-link-font-weight)","forumSolvedColor":"#148563","blogColor":"#1CBAA0","blogFontFamily":"var(--lia-bs-font-family-base)","blogFontWeight":"var(--lia-default-message-font-weight)","blogLineHeight":"1.75","blogFontStyle":"var(--lia-default-message-font-style)","blogMessageLinkColor":"var(--lia-default-message-link-color)","blogMessageLinkDecoration":"var(--lia-default-message-link-decoration)","blogMessageLinkFontStyle":"var(--lia-default-message-link-font-style)","blogMessageLinkFontWeight":"var(--lia-default-message-link-font-weight)","tkbColor":"#4C6B90","tkbFontFamily":"var(--lia-bs-font-family-base)","tkbFontWeight":"var(--lia-default-message-font-weight)","tkbLineHeight":"1.75","tkbFontStyle":"var(--lia-default-message-font-style)","tkbMessageLinkColor":"var(--lia-default-message-link-color)","tkbMessageLinkDecoration":"var(--lia-default-message-link-decoration)","tkbMessageLinkFontStyle":"var(--lia-default-message-link-font-style)","tkbMessageLinkFontWeight":"var(--lia-default-message-link-font-weight)","qandaColor":"#4099E2","qandaFontFamily":"var(--lia-bs-font-family-base)","qandaFontWeight":"var(--lia-default-message-font-weight)","qandaLineHeight":"var(--lia-bs-line-height-base)","qandaFontStyle":"var(--lia-default-message-link-font-style)","qandaMessageLinkColor":"var(--lia-default-message-link-color)","qandaMessageLinkDecoration":"var(--lia-default-message-link-decoration)","qandaMessageLinkFontStyle":"var(--lia-default-message-link-font-style)","qandaMessageLinkFontWeight":"var(--lia-default-message-link-font-weight)","qandaSolvedColor":"#3FA023","ideaColor":"#FF8000","ideaFontFamily":"var(--lia-bs-font-family-base)","ideaFontWeight":"var(--lia-default-message-font-weight)","ideaLineHeight":"var(--lia-bs-line-height-base)","ideaFontStyle":"var(--lia-default-message-font-style)","ideaMessageLinkColor":"var(--lia-default-message-link-color)","ideaMessageLinkDecoration":"var(--lia-default-message-link-decoration)","ideaMessageLinkFontStyle":"var(--lia-default-message-link-font-style)","ideaMessageLinkFontWeight":"var(--lia-default-message-link-font-weight)","contestColor":"#FCC845","contestFontFamily":"var(--lia-bs-font-family-base)","contestFontWeight":"var(--lia-default-message-font-weight)","contestLineHeight":"var(--lia-bs-line-height-base)","contestFontStyle":"var(--lia-default-message-link-font-style)","contestMessageLinkColor":"var(--lia-default-message-link-color)","contestMessageLinkDecoration":"var(--lia-default-message-link-decoration)","contestMessageLinkFontStyle":"ITALIC","contestMessageLinkFontWeight":"var(--lia-default-message-link-font-weight)","occasionColor":"#D13A1F","occasionFontFamily":"var(--lia-bs-font-family-base)","occasionFontWeight":"var(--lia-default-message-font-weight)","occasionLineHeight":"var(--lia-bs-line-height-base)","occasionFontStyle":"var(--lia-default-message-font-style)","occasionMessageLinkColor":"var(--lia-default-message-link-color)","occasionMessageLinkDecoration":"var(--lia-default-message-link-decoration)","occasionMessageLinkFontStyle":"var(--lia-default-message-link-font-style)","occasionMessageLinkFontWeight":"var(--lia-default-message-link-font-weight)","grouphubColor":"#333333","categoryColor":"#949494","communityColor":"#FFFFFF","productColor":"#949494","__typename":"CoreTypesThemeSettings"},"colors":{"black":"#000000","white":"#FFFFFF","gray100":"#F7F7F7","gray200":"#F7F7F7","gray300":"#E8E8E8","gray400":"#D9D9D9","gray500":"#CCCCCC","gray600":"#717171","gray700":"#707070","gray800":"#545454","gray900":"#333333","dark":"#545454","light":"#F7F7F7","primary":"#0069D4","secondary":"#333333","bodyText":"#333333","bodyBg":"#FFFFFF","info":"#409AE2","success":"#41C5AE","warning":"#FCC844","danger":"#BC341B","alertSystem":"#FF6600","textMuted":"#707070","highlight":"#FFFCAD","outline":"var(--lia-bs-primary)","custom":["#D3F5A4","#243A5E"],"__typename":"ColorsThemeSettings"},"divider":{"size":"3px","marginLeft":"4px","marginRight":"4px","borderRadius":"50%","bgColor":"var(--lia-bs-gray-600)","bgColorActive":"var(--lia-bs-gray-600)","__typename":"DividerThemeSettings"},"dropdown":{"fontSize":"var(--lia-bs-font-size-sm)","borderColor":"var(--lia-bs-border-color)","borderRadius":"var(--lia-bs-border-radius-sm)","dividerBg":"var(--lia-bs-gray-300)","itemPaddingY":"5px","itemPaddingX":"20px","headerColor":"var(--lia-bs-gray-700)","__typename":"DropdownThemeSettings"},"email":{"link":{"color":"#0069D4","hoverColor":"#0061c2","decoration":"none","hoverDecoration":"underline","__typename":"EmailLinkSettings"},"border":{"color":"#e4e4e4","__typename":"EmailBorderSettings"},"buttons":{"borderRadiusLg":"5px","paddingXLg":"16px","paddingYLg":"7px","fontWeight":"700","primaryTextColor":"#ffffff","primaryTextHoverColor":"#ffffff","primaryBgColor":"#0069D4","primaryBgHoverColor":"#005cb8","primaryBorder":"1px solid transparent","primaryBorderHover":"1px solid transparent","__typename":"EmailButtonsSettings"},"panel":{"borderRadius":"5px","borderColor":"#e4e4e4","__typename":"EmailPanelSettings"},"__typename":"EmailThemeSettings"},"emoji":{"skinToneDefault":"#ffcd43","skinToneLight":"#fae3c5","skinToneMediumLight":"#e2cfa5","skinToneMedium":"#daa478","skinToneMediumDark":"#a78058","skinToneDark":"#5e4d43","__typename":"EmojiThemeSettings"},"heading":{"color":"var(--lia-bs-body-color)","fontFamily":"Segoe UI","fontStyle":"NORMAL","fontWeight":"400","h1FontSize":"34px","h2FontSize":"32px","h3FontSize":"28px","h4FontSize":"24px","h5FontSize":"20px","h6FontSize":"16px","lineHeight":"1.3","subHeaderFontSize":"11px","subHeaderFontWeight":"500","h1LetterSpacing":"normal","h2LetterSpacing":"normal","h3LetterSpacing":"normal","h4LetterSpacing":"normal","h5LetterSpacing":"normal","h6LetterSpacing":"normal","subHeaderLetterSpacing":"2px","h1FontWeight":"var(--lia-bs-headings-font-weight)","h2FontWeight":"var(--lia-bs-headings-font-weight)","h3FontWeight":"var(--lia-bs-headings-font-weight)","h4FontWeight":"var(--lia-bs-headings-font-weight)","h5FontWeight":"var(--lia-bs-headings-font-weight)","h6FontWeight":"var(--lia-bs-headings-font-weight)","__typename":"HeadingThemeSettings"},"icons":{"size10":"10px","size12":"12px","size14":"14px","size16":"16px","size20":"20px","size24":"24px","size30":"30px","size40":"40px","size50":"50px","size60":"60px","size80":"80px","size120":"120px","size160":"160px","__typename":"IconsThemeSettings"},"imagePreview":{"bgColor":"var(--lia-bs-gray-900)","titleColor":"var(--lia-bs-white)","controlColor":"var(--lia-bs-white)","controlBgColor":"var(--lia-bs-gray-800)","__typename":"ImagePreviewThemeSettings"},"input":{"borderColor":"var(--lia-bs-gray-600)","disabledColor":"var(--lia-bs-gray-600)","focusBorderColor":"var(--lia-bs-primary)","labelMarginBottom":"10px","btnFontSize":"var(--lia-bs-font-size-sm)","focusBoxShadow":"0 0 0 3px hsla(var(--lia-bs-primary-h), var(--lia-bs-primary-s), var(--lia-bs-primary-l), 0.2)","checkLabelMarginBottom":"2px","checkboxBorderRadius":"3px","borderRadiusSm":"var(--lia-bs-border-radius-sm)","borderRadius":"var(--lia-bs-border-radius)","borderRadiusLg":"var(--lia-bs-border-radius-lg)","formTextMarginTop":"4px","textAreaBorderRadius":"var(--lia-bs-border-radius)","activeFillColor":"var(--lia-bs-primary)","__typename":"InputThemeSettings"},"loading":{"dotDarkColor":"hsla(var(--lia-bs-black-h), var(--lia-bs-black-s), var(--lia-bs-black-l), 0.2)","dotLightColor":"hsla(var(--lia-bs-white-h), var(--lia-bs-white-s), var(--lia-bs-white-l), 0.5)","barDarkColor":"hsla(var(--lia-bs-black-h), var(--lia-bs-black-s), var(--lia-bs-black-l), 0.06)","barLightColor":"hsla(var(--lia-bs-white-h), var(--lia-bs-white-s), var(--lia-bs-white-l), 0.4)","__typename":"LoadingThemeSettings"},"link":{"color":"var(--lia-bs-primary)","hoverColor":"hsl(var(--lia-bs-primary-h), var(--lia-bs-primary-s), calc(var(--lia-bs-primary-l) - 10%))","decoration":"none","hoverDecoration":"underline","__typename":"LinkThemeSettings"},"listGroup":{"itemPaddingY":"15px","itemPaddingX":"15px","borderColor":"var(--lia-bs-gray-300)","__typename":"ListGroupThemeSettings"},"modal":{"contentTextColor":"var(--lia-bs-body-color)","contentBg":"var(--lia-bs-white)","backgroundBg":"var(--lia-bs-black)","smSize":"440px","mdSize":"760px","lgSize":"1080px","backdropOpacity":0.3,"contentBoxShadowXs":"var(--lia-bs-box-shadow-sm)","contentBoxShadow":"var(--lia-bs-box-shadow)","headerFontWeight":"700","__typename":"ModalThemeSettings"},"navbar":{"position":"FIXED","background":{"attachment":null,"clip":null,"color":"var(--lia-bs-white)","imageAssetName":"","imageLastModified":"0","origin":null,"position":"CENTER_CENTER","repeat":"NO_REPEAT","size":"COVER","__typename":"BackgroundProps"},"backgroundOpacity":0.8,"paddingTop":"15px","paddingBottom":"15px","borderBottom":"1px solid var(--lia-bs-border-color)","boxShadow":"var(--lia-bs-box-shadow-sm)","brandMarginRight":"30px","brandMarginRightSm":"10px","brandLogoHeight":"30px","linkGap":"10px","linkJustifyContent":"flex-start","linkPaddingY":"5px","linkPaddingX":"10px","linkDropdownPaddingY":"9px","linkDropdownPaddingX":"var(--lia-nav-link-px)","linkColor":"var(--lia-bs-body-color)","linkHoverColor":"var(--lia-bs-primary)","linkFontSize":"var(--lia-bs-font-size-sm)","linkFontStyle":"NORMAL","linkFontWeight":"400","linkTextTransform":"NONE","linkLetterSpacing":"normal","linkBorderRadius":"var(--lia-bs-border-radius-sm)","linkBgColor":"transparent","linkBgHoverColor":"transparent","linkBorder":"none","linkBorderHover":"none","linkBoxShadow":"none","linkBoxShadowHover":"none","linkTextBorderBottom":"none","linkTextBorderBottomHover":"none","dropdownPaddingTop":"10px","dropdownPaddingBottom":"15px","dropdownPaddingX":"10px","dropdownMenuOffset":"2px","dropdownDividerMarginTop":"10px","dropdownDividerMarginBottom":"10px","dropdownBorderColor":"hsla(var(--lia-bs-black-h), var(--lia-bs-black-s), var(--lia-bs-black-l), 0.08)","controllerBgHoverColor":"hsla(var(--lia-bs-black-h), var(--lia-bs-black-s), var(--lia-bs-black-l), 0.1)","controllerIconColor":"var(--lia-bs-body-color)","controllerIconHoverColor":"var(--lia-bs-body-color)","controllerTextColor":"var(--lia-nav-controller-icon-color)","controllerTextHoverColor":"var(--lia-nav-controller-icon-hover-color)","controllerHighlightColor":"hsla(30, 100%, 50%)","controllerHighlightTextColor":"var(--lia-yiq-light)","controllerBorderRadius":"var(--lia-border-radius-50)","hamburgerColor":"var(--lia-nav-controller-icon-color)","hamburgerHoverColor":"var(--lia-nav-controller-icon-color)","hamburgerBgColor":"transparent","hamburgerBgHoverColor":"transparent","hamburgerBorder":"none","hamburgerBorderHover":"none","collapseMenuMarginLeft":"20px","collapseMenuDividerBg":"var(--lia-nav-link-color)","collapseMenuDividerOpacity":0.16,"__typename":"NavbarThemeSettings"},"pager":{"textColor":"var(--lia-bs-link-color)","textFontWeight":"var(--lia-font-weight-md)","textFontSize":"var(--lia-bs-font-size-sm)","__typename":"PagerThemeSettings"},"panel":{"bgColor":"var(--lia-bs-white)","borderRadius":"var(--lia-bs-border-radius)","borderColor":"var(--lia-bs-border-color)","boxShadow":"none","__typename":"PanelThemeSettings"},"popover":{"arrowHeight":"8px","arrowWidth":"16px","maxWidth":"300px","minWidth":"100px","headerBg":"var(--lia-bs-white)","borderColor":"var(--lia-bs-border-color)","borderRadius":"var(--lia-bs-border-radius)","boxShadow":"0 0.5rem 1rem hsla(var(--lia-bs-black-h), var(--lia-bs-black-s), var(--lia-bs-black-l), 0.15)","__typename":"PopoverThemeSettings"},"prism":{"color":"#000000","bgColor":"#f5f2f0","fontFamily":"var(--font-family-monospace)","fontSize":"var(--lia-bs-font-size-base)","fontWeightBold":"var(--lia-bs-font-weight-bold)","fontStyleItalic":"italic","tabSize":2,"highlightColor":"#b3d4fc","commentColor":"#62707e","punctuationColor":"#6f6f6f","namespaceOpacity":"0.7","propColor":"#990055","selectorColor":"#517a00","operatorColor":"#906736","operatorBgColor":"hsla(0, 0%, 100%, 0.5)","keywordColor":"#0076a9","functionColor":"#d3284b","variableColor":"#c14700","__typename":"PrismThemeSettings"},"rte":{"bgColor":"var(--lia-bs-white)","borderRadius":"var(--lia-panel-border-radius)","boxShadow":" var(--lia-panel-box-shadow)","customColor1":"#bfedd2","customColor2":"#fbeeb8","customColor3":"#f8cac6","customColor4":"#eccafa","customColor5":"#c2e0f4","customColor6":"#2dc26b","customColor7":"#f1c40f","customColor8":"#e03e2d","customColor9":"#b96ad9","customColor10":"#3598db","customColor11":"#169179","customColor12":"#e67e23","customColor13":"#ba372a","customColor14":"#843fa1","customColor15":"#236fa1","customColor16":"#ecf0f1","customColor17":"#ced4d9","customColor18":"#95a5a6","customColor19":"#7e8c8d","customColor20":"#34495e","customColor21":"#000000","customColor22":"#ffffff","defaultMessageHeaderMarginTop":"40px","defaultMessageHeaderMarginBottom":"20px","defaultMessageItemMarginTop":"0","defaultMessageItemMarginBottom":"10px","diffAddedColor":"hsla(170, 53%, 51%, 0.4)","diffChangedColor":"hsla(43, 97%, 63%, 0.4)","diffNoneColor":"hsla(0, 0%, 80%, 0.4)","diffRemovedColor":"hsla(9, 74%, 47%, 0.4)","specialMessageHeaderMarginTop":"40px","specialMessageHeaderMarginBottom":"20px","specialMessageItemMarginTop":"0","specialMessageItemMarginBottom":"10px","__typename":"RteThemeSettings"},"tags":{"bgColor":"var(--lia-bs-gray-200)","bgHoverColor":"var(--lia-bs-gray-400)","borderRadius":"var(--lia-bs-border-radius-sm)","color":"var(--lia-bs-body-color)","hoverColor":"var(--lia-bs-body-color)","fontWeight":"var(--lia-font-weight-md)","fontSize":"var(--lia-font-size-xxs)","textTransform":"UPPERCASE","letterSpacing":"0.5px","__typename":"TagsThemeSettings"},"toasts":{"borderRadius":"var(--lia-bs-border-radius)","paddingX":"12px","__typename":"ToastsThemeSettings"},"typography":{"fontFamilyBase":"Segoe UI","fontStyleBase":"NORMAL","fontWeightBase":"400","fontWeightLight":"300","fontWeightNormal":"400","fontWeightMd":"500","fontWeightBold":"700","letterSpacingSm":"normal","letterSpacingXs":"normal","lineHeightBase":"1.5","fontSizeBase":"16px","fontSizeXxs":"11px","fontSizeXs":"12px","fontSizeSm":"14px","fontSizeLg":"20px","fontSizeXl":"24px","smallFontSize":"14px","customFonts":[{"source":"SERVER","name":"Segoe UI","styles":[{"style":"NORMAL","weight":"400","__typename":"FontStyleData"},{"style":"NORMAL","weight":"300","__typename":"FontStyleData"},{"style":"NORMAL","weight":"600","__typename":"FontStyleData"},{"style":"NORMAL","weight":"700","__typename":"FontStyleData"},{"style":"ITALIC","weight":"400","__typename":"FontStyleData"}],"assetNames":["SegoeUI-normal-400.woff2","SegoeUI-normal-300.woff2","SegoeUI-normal-600.woff2","SegoeUI-normal-700.woff2","SegoeUI-italic-400.woff2"],"__typename":"CustomFont"},{"source":"SERVER","name":"MWF Fluent Icons","styles":[{"style":"NORMAL","weight":"400","__typename":"FontStyleData"}],"assetNames":["MWFFluentIcons-normal-400.woff2"],"__typename":"CustomFont"}],"__typename":"TypographyThemeSettings"},"unstyledListItem":{"marginBottomSm":"5px","marginBottomMd":"10px","marginBottomLg":"15px","marginBottomXl":"20px","marginBottomXxl":"25px","__typename":"UnstyledListItemThemeSettings"},"yiq":{"light":"#ffffff","dark":"#000000","__typename":"YiqThemeSettings"},"colorLightness":{"primaryDark":0.36,"primaryLight":0.74,"primaryLighter":0.89,"primaryLightest":0.95,"infoDark":0.39,"infoLight":0.72,"infoLighter":0.85,"infoLightest":0.93,"successDark":0.24,"successLight":0.62,"successLighter":0.8,"successLightest":0.91,"warningDark":0.39,"warningLight":0.68,"warningLighter":0.84,"warningLightest":0.93,"dangerDark":0.41,"dangerLight":0.72,"dangerLighter":0.89,"dangerLightest":0.95,"__typename":"ColorLightnessThemeSettings"},"localOverride":false,"__typename":"Theme"},"localOverride":false},"CachedAsset:text:en_US-components/common/EmailVerification-1743095130000":{"__typename":"CachedAsset","id":"text:en_US-components/common/EmailVerification-1743095130000","value":{"email.verification.title":"Email Verification Required","email.verification.message.update.email":"To participate in the community, you must first verify your email address. The verification email was sent to {email}. To change your email, visit My Settings.","email.verification.message.resend.email":"To participate in the community, you must first verify your email address. The verification email was sent to {email}. Resend email."},"localOverride":false},"CachedAsset:text:en_US-shared/client/components/common/Loading/LoadingDot-1743095130000":{"__typename":"CachedAsset","id":"text:en_US-shared/client/components/common/Loading/LoadingDot-1743095130000","value":{"title":"Loading..."},"localOverride":false},"CachedAsset:quilt:o365.prod:pages/blogs/BlogMessagePage:board:Azure-AI-Services-blog-1743150524577":{"__typename":"CachedAsset","id":"quilt:o365.prod:pages/blogs/BlogMessagePage:board:Azure-AI-Services-blog-1743150524577","value":{"id":"BlogMessagePage","container":{"id":"Common","headerProps":{"backgroundImageProps":null,"backgroundColor":null,"addComponents":null,"removeComponents":["community.widget.bannerWidget"],"componentOrder":null,"__typename":"QuiltContainerSectionProps"},"headerComponentProps":{"community.widget.breadcrumbWidget":{"disableLastCrumbForDesktop":false}},"footerProps":null,"footerComponentProps":null,"items":[{"id":"blog-article","layout":"ONE_COLUMN","bgColor":null,"showTitle":null,"showDescription":null,"textPosition":null,"textColor":null,"sectionEditLevel":"LOCKED","bgImage":null,"disableSpacing":null,"edgeToEdgeDisplay":null,"fullHeight":null,"showBorder":null,"__typename":"OneColumnQuiltSection","columnMap":{"main":[{"id":"blogs.widget.blogArticleWidget","className":"lia-blog-container","props":null,"__typename":"QuiltComponent"}],"__typename":"OneSectionColumns"}},{"id":"section-1729184836777","layout":"MAIN_SIDE","bgColor":"transparent","showTitle":false,"showDescription":false,"textPosition":"CENTER","textColor":"var(--lia-bs-body-color)","sectionEditLevel":null,"bgImage":null,"disableSpacing":null,"edgeToEdgeDisplay":null,"fullHeight":null,"showBorder":null,"__typename":"MainSideQuiltSection","columnMap":{"main":[],"side":[{"id":"custom.widget.Social_Sharing","className":null,"props":{"widgetVisibility":"signedInOrAnonymous","useTitle":true,"useBackground":true,"title":"Share","lazyLoad":false},"__typename":"QuiltComponent"}],"__typename":"MainSideSectionColumns"}}],"__typename":"QuiltContainer"},"__typename":"Quilt","localOverride":false},"localOverride":false},"CachedAsset:text:en_US-pages/blogs/BlogMessagePage-1743095130000":{"__typename":"CachedAsset","id":"text:en_US-pages/blogs/BlogMessagePage-1743095130000","value":{"title":"{contextMessageSubject} | {communityTitle}","errorMissing":"This blog post cannot be found","name":"Blog Message Page","section.blog-article.title":"Blog Post","archivedMessageTitle":"This Content Has Been Archived","section.section-1729184836777.title":"","section.section-1729184836777.description":"","section.CncIde.title":"Blog Post","section.tifEmD.description":"","section.tifEmD.title":""},"localOverride":false},"CachedAsset:quiltWrapper:o365.prod:Common:1743150326779":{"__typename":"CachedAsset","id":"quiltWrapper:o365.prod:Common:1743150326779","value":{"id":"Common","header":{"backgroundImageProps":{"assetName":null,"backgroundSize":"COVER","backgroundRepeat":"NO_REPEAT","backgroundPosition":"CENTER_CENTER","lastModified":null,"__typename":"BackgroundImageProps"},"backgroundColor":"transparent","items":[{"id":"community.widget.navbarWidget","props":{"showUserName":true,"showRegisterLink":true,"useIconLanguagePicker":true,"useLabelLanguagePicker":true,"className":"QuiltComponent_lia-component-edit-mode__0nCcm","links":{"sideLinks":[],"mainLinks":[{"children":[],"linkType":"INTERNAL","id":"gxcuf89792","params":{},"routeName":"CommunityPage"},{"children":[],"linkType":"EXTERNAL","id":"external-link","url":"/Directory","target":"SELF"},{"children":[{"linkType":"INTERNAL","id":"microsoft365","params":{"categoryId":"microsoft365"},"routeName":"CategoryPage"},{"linkType":"INTERNAL","id":"microsoft-teams","params":{"categoryId":"MicrosoftTeams"},"routeName":"CategoryPage"},{"linkType":"INTERNAL","id":"windows","params":{"categoryId":"Windows"},"routeName":"CategoryPage"},{"linkType":"INTERNAL","id":"microsoft-securityand-compliance","params":{"categoryId":"microsoft-security"},"routeName":"CategoryPage"},{"linkType":"INTERNAL","id":"outlook","params":{"categoryId":"Outlook"},"routeName":"CategoryPage"},{"linkType":"INTERNAL","id":"planner","params":{"categoryId":"Planner"},"routeName":"CategoryPage"},{"linkType":"INTERNAL","id":"windows-server","params":{"categoryId":"Windows-Server"},"routeName":"CategoryPage"},{"linkType":"INTERNAL","id":"azure","params":{"categoryId":"Azure"},"routeName":"CategoryPage"},{"linkType":"INTERNAL","id":"exchange","params":{"categoryId":"Exchange"},"routeName":"CategoryPage"},{"linkType":"INTERNAL","id":"microsoft-endpoint-manager","params":{"categoryId":"microsoft-endpoint-manager"},"routeName":"CategoryPage"},{"linkType":"INTERNAL","id":"s-q-l-server","params":{"categoryId":"SQL-Server"},"routeName":"CategoryPage"},{"linkType":"EXTERNAL","id":"external-link-2","url":"/Directory","target":"SELF"}],"linkType":"EXTERNAL","id":"communities","url":"/","target":"BLANK"},{"children":[{"linkType":"INTERNAL","id":"education-sector","params":{"categoryId":"EducationSector"},"routeName":"CategoryPage"},{"linkType":"INTERNAL","id":"a-i","params":{"categoryId":"AI"},"routeName":"CategoryPage"},{"linkType":"INTERNAL","id":"i-t-ops-talk","params":{"categoryId":"ITOpsTalk"},"routeName":"CategoryPage"},{"linkType":"INTERNAL","id":"partner-community","params":{"categoryId":"PartnerCommunity"},"routeName":"CategoryPage"},{"linkType":"INTERNAL","id":"microsoft-mechanics","params":{"categoryId":"MicrosoftMechanics"},"routeName":"CategoryPage"},{"linkType":"INTERNAL","id":"healthcare-and-life-sciences","params":{"categoryId":"HealthcareAndLifeSciences"},"routeName":"CategoryPage"},{"linkType":"INTERNAL","id":"public-sector","params":{"categoryId":"PublicSector"},"routeName":"CategoryPage"},{"linkType":"INTERNAL","id":"io-t","params":{"categoryId":"IoT"},"routeName":"CategoryPage"},{"linkType":"INTERNAL","id":"driving-adoption","params":{"categoryId":"DrivingAdoption"},"routeName":"CategoryPage"},{"linkType":"INTERNAL","id":"s-m-b","params":{"categoryId":"SMB"},"routeName":"CategoryPage"},{"linkType":"INTERNAL","id":"startupsat-microsoft","params":{"categoryId":"StartupsatMicrosoft"},"routeName":"CategoryPage"},{"linkType":"EXTERNAL","id":"external-link-1","url":"/Directory","target":"SELF"}],"linkType":"EXTERNAL","id":"communities-1","url":"/","target":"SELF"},{"children":[],"linkType":"EXTERNAL","id":"external","url":"/Blogs","target":"SELF"},{"children":[],"linkType":"EXTERNAL","id":"external-1","url":"/Events","target":"SELF"},{"children":[{"linkType":"INTERNAL","id":"microsoft-learn-1","params":{"categoryId":"MicrosoftLearn"},"routeName":"CategoryPage"},{"linkType":"INTERNAL","id":"microsoft-learn-blog","params":{"boardId":"MicrosoftLearnBlog","categoryId":"MicrosoftLearn"},"routeName":"BlogBoardPage"},{"linkType":"EXTERNAL","id":"external-10","url":"https://learningroomdirectory.microsoft.com/","target":"BLANK"},{"linkType":"EXTERNAL","id":"external-3","url":"https://docs.microsoft.com/learn/dynamics365/?WT.mc_id=techcom_header-webpage-m365","target":"BLANK"},{"linkType":"EXTERNAL","id":"external-4","url":"https://docs.microsoft.com/learn/m365/?wt.mc_id=techcom_header-webpage-m365","target":"BLANK"},{"linkType":"EXTERNAL","id":"external-5","url":"https://docs.microsoft.com/learn/topics/sci/?wt.mc_id=techcom_header-webpage-m365","target":"BLANK"},{"linkType":"EXTERNAL","id":"external-6","url":"https://docs.microsoft.com/learn/powerplatform/?wt.mc_id=techcom_header-webpage-powerplatform","target":"BLANK"},{"linkType":"EXTERNAL","id":"external-7","url":"https://docs.microsoft.com/learn/github/?wt.mc_id=techcom_header-webpage-github","target":"BLANK"},{"linkType":"EXTERNAL","id":"external-8","url":"https://docs.microsoft.com/learn/teams/?wt.mc_id=techcom_header-webpage-teams","target":"BLANK"},{"linkType":"EXTERNAL","id":"external-9","url":"https://docs.microsoft.com/learn/dotnet/?wt.mc_id=techcom_header-webpage-dotnet","target":"BLANK"},{"linkType":"EXTERNAL","id":"external-2","url":"https://docs.microsoft.com/learn/azure/?WT.mc_id=techcom_header-webpage-m365","target":"BLANK"}],"linkType":"INTERNAL","id":"microsoft-learn","params":{"categoryId":"MicrosoftLearn"},"routeName":"CategoryPage"},{"children":[],"linkType":"INTERNAL","id":"community-info-center","params":{"categoryId":"Community-Info-Center"},"routeName":"CategoryPage"}]},"style":{"boxShadow":"var(--lia-bs-box-shadow-sm)","controllerHighlightColor":"hsla(30, 100%, 50%)","linkFontWeight":"400","dropdownDividerMarginBottom":"10px","hamburgerBorderHover":"none","linkBoxShadowHover":"none","linkFontSize":"14px","backgroundOpacity":0.8,"controllerBorderRadius":"var(--lia-border-radius-50)","hamburgerBgColor":"transparent","hamburgerColor":"var(--lia-nav-controller-icon-color)","linkTextBorderBottom":"none","brandLogoHeight":"30px","linkBgHoverColor":"transparent","linkLetterSpacing":"normal","collapseMenuDividerOpacity":0.16,"dropdownPaddingBottom":"15px","paddingBottom":"15px","dropdownMenuOffset":"2px","hamburgerBgHoverColor":"transparent","borderBottom":"1px solid var(--lia-bs-border-color)","hamburgerBorder":"none","dropdownPaddingX":"10px","brandMarginRightSm":"10px","linkBoxShadow":"none","collapseMenuDividerBg":"var(--lia-nav-link-color)","linkColor":"var(--lia-bs-body-color)","linkJustifyContent":"flex-start","dropdownPaddingTop":"10px","controllerHighlightTextColor":"var(--lia-yiq-dark)","controllerTextColor":"var(--lia-nav-controller-icon-color)","background":{"imageAssetName":"","color":"var(--lia-bs-white)","size":"COVER","repeat":"NO_REPEAT","position":"CENTER_CENTER","imageLastModified":""},"linkBorderRadius":"var(--lia-bs-border-radius-sm)","linkHoverColor":"var(--lia-bs-body-color)","position":"FIXED","linkBorder":"none","linkTextBorderBottomHover":"2px solid var(--lia-bs-body-color)","brandMarginRight":"30px","hamburgerHoverColor":"var(--lia-nav-controller-icon-color)","linkBorderHover":"none","collapseMenuMarginLeft":"20px","linkFontStyle":"NORMAL","controllerTextHoverColor":"var(--lia-nav-controller-icon-hover-color)","linkPaddingX":"10px","linkPaddingY":"5px","paddingTop":"15px","linkTextTransform":"NONE","dropdownBorderColor":"hsla(var(--lia-bs-black-h), var(--lia-bs-black-s), var(--lia-bs-black-l), 0.08)","controllerBgHoverColor":"hsla(var(--lia-bs-black-h), var(--lia-bs-black-s), var(--lia-bs-black-l), 0.1)","linkBgColor":"transparent","linkDropdownPaddingX":"var(--lia-nav-link-px)","linkDropdownPaddingY":"9px","controllerIconColor":"var(--lia-bs-body-color)","dropdownDividerMarginTop":"10px","linkGap":"10px","controllerIconHoverColor":"var(--lia-bs-body-color)"},"showSearchIcon":false,"languagePickerStyle":"iconAndLabel"},"__typename":"QuiltComponent"},{"id":"community.widget.breadcrumbWidget","props":{"backgroundColor":"transparent","linkHighlightColor":"var(--lia-bs-primary)","visualEffects":{"showBottomBorder":true},"linkTextColor":"var(--lia-bs-gray-700)"},"__typename":"QuiltComponent"},{"id":"custom.widget.community_banner","props":{"widgetVisibility":"signedInOrAnonymous","useTitle":true,"usePageWidth":false,"useBackground":false,"title":"","lazyLoad":false},"__typename":"QuiltComponent"},{"id":"custom.widget.HeroBanner","props":{"widgetVisibility":"signedInOrAnonymous","usePageWidth":false,"useTitle":true,"cMax_items":3,"useBackground":false,"title":"","lazyLoad":false,"widgetChooser":"custom.widget.HeroBanner"},"__typename":"QuiltComponent"}],"__typename":"QuiltWrapperSection"},"footer":{"backgroundImageProps":{"assetName":null,"backgroundSize":"COVER","backgroundRepeat":"NO_REPEAT","backgroundPosition":"CENTER_CENTER","lastModified":null,"__typename":"BackgroundImageProps"},"backgroundColor":"transparent","items":[{"id":"custom.widget.MicrosoftFooter","props":{"widgetVisibility":"signedInOrAnonymous","useTitle":true,"useBackground":false,"title":"","lazyLoad":false},"__typename":"QuiltComponent"}],"__typename":"QuiltWrapperSection"},"__typename":"QuiltWrapper","localOverride":false},"localOverride":false},"CachedAsset:text:en_US-components/common/ActionFeedback-1743095130000":{"__typename":"CachedAsset","id":"text:en_US-components/common/ActionFeedback-1743095130000","value":{"joinedGroupHub.title":"Welcome","joinedGroupHub.message":"You are now a member of this group and are subscribed to updates.","groupHubInviteNotFound.title":"Invitation Not Found","groupHubInviteNotFound.message":"Sorry, we could not find your invitation to the group. The owner may have canceled the invite.","groupHubNotFound.title":"Group Not Found","groupHubNotFound.message":"The grouphub you tried to join does not exist. It may have been deleted.","existingGroupHubMember.title":"Already Joined","existingGroupHubMember.message":"You are already a member of this group.","accountLocked.title":"Account Locked","accountLocked.message":"Your account has been locked due to multiple failed attempts. Try again in {lockoutTime} minutes.","editedGroupHub.title":"Changes Saved","editedGroupHub.message":"Your group has been updated.","leftGroupHub.title":"Goodbye","leftGroupHub.message":"You are no longer a member of this group and will not receive future updates.","deletedGroupHub.title":"Deleted","deletedGroupHub.message":"The group has been deleted.","groupHubCreated.title":"Group Created","groupHubCreated.message":"{groupHubName} is ready to use","accountClosed.title":"Account Closed","accountClosed.message":"The account has been closed and you will now be redirected to the homepage","resetTokenExpired.title":"Reset Password Link has Expired","resetTokenExpired.message":"Try resetting your password again","invalidUrl.title":"Invalid URL","invalidUrl.message":"The URL you're using is not recognized. Verify your URL and try again.","accountClosedForUser.title":"Account Closed","accountClosedForUser.message":"{userName}'s account is closed","inviteTokenInvalid.title":"Invitation Invalid","inviteTokenInvalid.message":"Your invitation to the community has been canceled or expired.","inviteTokenError.title":"Invitation Verification Failed","inviteTokenError.message":"The url you are utilizing is not recognized. Verify your URL and try again","pageNotFound.title":"Access Denied","pageNotFound.message":"You do not have access to this area of the community or it doesn't exist","eventAttending.title":"Responded as Attending","eventAttending.message":"You'll be notified when there's new activity and reminded as the event approaches","eventInterested.title":"Responded as Interested","eventInterested.message":"You'll be notified when there's new activity and reminded as the event approaches","eventNotFound.title":"Event Not Found","eventNotFound.message":"The event you tried to respond to does not exist.","redirectToRelatedPage.title":"Showing Related Content","redirectToRelatedPageForBaseUsers.title":"Showing Related Content","redirectToRelatedPageForBaseUsers.message":"The content you are trying to access is archived","redirectToRelatedPage.message":"The content you are trying to access is archived","relatedUrl.archivalLink.flyoutMessage":"The content you are trying to access is archived View Archived Content"},"localOverride":false},"CachedAsset:component:custom.widget.community_banner-en-1743150561112":{"__typename":"CachedAsset","id":"component:custom.widget.community_banner-en-1743150561112","value":{"component":{"id":"custom.widget.community_banner","template":{"id":"community_banner","markupLanguage":"HANDLEBARS","style":".community-banner {\n a.top-bar.btn {\n top: 0px;\n width: 100%;\n z-index: 999;\n text-align: center;\n left: 0px;\n background: #0068b8;\n color: white;\n padding: 10px 0px;\n display:block;\n box-shadow:none !important;\n border: none !important;\n border-radius: none !important;\n margin: 0px !important;\n font-size:14px;\n }\n}","texts":null,"defaults":{"config":{"applicablePages":[],"description":"community announcement text","fetchedContent":null,"__typename":"ComponentConfiguration"},"props":[],"__typename":"ComponentProperties"},"components":[{"id":"custom.widget.community_banner","form":null,"config":null,"props":[],"__typename":"Component"}],"grouping":"CUSTOM","__typename":"ComponentTemplate"},"properties":{"config":{"applicablePages":[],"description":"community announcement text","fetchedContent":null,"__typename":"ComponentConfiguration"},"props":[],"__typename":"ComponentProperties"},"form":null,"__typename":"Component","localOverride":false},"globalCss":{"css":".custom_widget_community_banner_community-banner_1a5zb_1 {\n a.custom_widget_community_banner_top-bar_1a5zb_2.custom_widget_community_banner_btn_1a5zb_2 {\n top: 0;\n width: 100%;\n z-index: 999;\n text-align: center;\n left: 0;\n background: #0068b8;\n color: white;\n padding: 0.625rem 0;\n display:block;\n box-shadow:none !important;\n border: none !important;\n border-radius: none !important;\n margin: 0 !important;\n font-size:0.875rem;\n }\n}","tokens":{"community-banner":"custom_widget_community_banner_community-banner_1a5zb_1","top-bar":"custom_widget_community_banner_top-bar_1a5zb_2","btn":"custom_widget_community_banner_btn_1a5zb_2"}},"form":null},"localOverride":false},"CachedAsset:component:custom.widget.HeroBanner-en-1743150561112":{"__typename":"CachedAsset","id":"component:custom.widget.HeroBanner-en-1743150561112","value":{"component":{"id":"custom.widget.HeroBanner","template":{"id":"HeroBanner","markupLanguage":"REACT","style":null,"texts":{"searchPlaceholderText":"Search this community","followActionText":"Follow","unfollowActionText":"Following","searchOnHoverText":"Please enter your search term(s) and then press return key to complete a search."},"defaults":{"config":{"applicablePages":[],"description":null,"fetchedContent":null,"__typename":"ComponentConfiguration"},"props":[{"id":"max_items","dataType":"NUMBER","list":false,"defaultValue":"3","label":"Max Items","description":"The maximum number of items to display in the carousel","possibleValues":null,"control":"INPUT","__typename":"PropDefinition"}],"__typename":"ComponentProperties"},"components":[{"id":"custom.widget.HeroBanner","form":{"fields":[{"id":"widgetChooser","validation":null,"noValidation":null,"dataType":"STRING","list":null,"control":null,"defaultValue":null,"label":null,"description":null,"possibleValues":null,"__typename":"FormField"},{"id":"title","validation":null,"noValidation":null,"dataType":"STRING","list":null,"control":null,"defaultValue":null,"label":null,"description":null,"possibleValues":null,"__typename":"FormField"},{"id":"useTitle","validation":null,"noValidation":null,"dataType":"BOOLEAN","list":null,"control":null,"defaultValue":null,"label":null,"description":null,"possibleValues":null,"__typename":"FormField"},{"id":"useBackground","validation":null,"noValidation":null,"dataType":"BOOLEAN","list":null,"control":null,"defaultValue":null,"label":null,"description":null,"possibleValues":null,"__typename":"FormField"},{"id":"widgetVisibility","validation":null,"noValidation":null,"dataType":"STRING","list":null,"control":null,"defaultValue":null,"label":null,"description":null,"possibleValues":null,"__typename":"FormField"},{"id":"moreOptions","validation":null,"noValidation":null,"dataType":"STRING","list":null,"control":null,"defaultValue":null,"label":null,"description":null,"possibleValues":null,"__typename":"FormField"},{"id":"cMax_items","validation":null,"noValidation":null,"dataType":"NUMBER","list":false,"control":"INPUT","defaultValue":"3","label":"Max Items","description":"The maximum number of items to display in the carousel","possibleValues":null,"__typename":"FormField"}],"layout":{"rows":[{"id":"widgetChooserGroup","type":"fieldset","as":null,"items":[{"id":"widgetChooser","className":null,"__typename":"FormFieldRef"}],"props":null,"legend":null,"description":null,"className":null,"viewVariant":null,"toggleState":null,"__typename":"FormFieldset"},{"id":"titleGroup","type":"fieldset","as":null,"items":[{"id":"title","className":null,"__typename":"FormFieldRef"},{"id":"useTitle","className":null,"__typename":"FormFieldRef"}],"props":null,"legend":null,"description":null,"className":null,"viewVariant":null,"toggleState":null,"__typename":"FormFieldset"},{"id":"useBackground","type":"fieldset","as":null,"items":[{"id":"useBackground","className":null,"__typename":"FormFieldRef"}],"props":null,"legend":null,"description":null,"className":null,"viewVariant":null,"toggleState":null,"__typename":"FormFieldset"},{"id":"widgetVisibility","type":"fieldset","as":null,"items":[{"id":"widgetVisibility","className":null,"__typename":"FormFieldRef"}],"props":null,"legend":null,"description":null,"className":null,"viewVariant":null,"toggleState":null,"__typename":"FormFieldset"},{"id":"moreOptionsGroup","type":"fieldset","as":null,"items":[{"id":"moreOptions","className":null,"__typename":"FormFieldRef"}],"props":null,"legend":null,"description":null,"className":null,"viewVariant":null,"toggleState":null,"__typename":"FormFieldset"},{"id":"componentPropsGroup","type":"fieldset","as":null,"items":[{"id":"cMax_items","className":null,"__typename":"FormFieldRef"}],"props":null,"legend":null,"description":null,"className":null,"viewVariant":null,"toggleState":null,"__typename":"FormFieldset"}],"actionButtons":null,"className":"custom_widget_HeroBanner_form","formGroupFieldSeparator":"divider","__typename":"FormLayout"},"__typename":"Form"},"config":null,"props":[],"__typename":"Component"}],"grouping":"CUSTOM","__typename":"ComponentTemplate"},"properties":{"config":{"applicablePages":[],"description":null,"fetchedContent":null,"__typename":"ComponentConfiguration"},"props":[{"id":"max_items","dataType":"NUMBER","list":false,"defaultValue":"3","label":"Max Items","description":"The maximum number of items to display in the carousel","possibleValues":null,"control":"INPUT","__typename":"PropDefinition"}],"__typename":"ComponentProperties"},"form":{"fields":[{"id":"widgetChooser","validation":null,"noValidation":null,"dataType":"STRING","list":null,"control":null,"defaultValue":null,"label":null,"description":null,"possibleValues":null,"__typename":"FormField"},{"id":"title","validation":null,"noValidation":null,"dataType":"STRING","list":null,"control":null,"defaultValue":null,"label":null,"description":null,"possibleValues":null,"__typename":"FormField"},{"id":"useTitle","validation":null,"noValidation":null,"dataType":"BOOLEAN","list":null,"control":null,"defaultValue":null,"label":null,"description":null,"possibleValues":null,"__typename":"FormField"},{"id":"useBackground","validation":null,"noValidation":null,"dataType":"BOOLEAN","list":null,"control":null,"defaultValue":null,"label":null,"description":null,"possibleValues":null,"__typename":"FormField"},{"id":"widgetVisibility","validation":null,"noValidation":null,"dataType":"STRING","list":null,"control":null,"defaultValue":null,"label":null,"description":null,"possibleValues":null,"__typename":"FormField"},{"id":"moreOptions","validation":null,"noValidation":null,"dataType":"STRING","list":null,"control":null,"defaultValue":null,"label":null,"description":null,"possibleValues":null,"__typename":"FormField"},{"id":"cMax_items","validation":null,"noValidation":null,"dataType":"NUMBER","list":false,"control":"INPUT","defaultValue":"3","label":"Max Items","description":"The maximum number of items to display in the carousel","possibleValues":null,"__typename":"FormField"}],"layout":{"rows":[{"id":"widgetChooserGroup","type":"fieldset","as":null,"items":[{"id":"widgetChooser","className":null,"__typename":"FormFieldRef"}],"props":null,"legend":null,"description":null,"className":null,"viewVariant":null,"toggleState":null,"__typename":"FormFieldset"},{"id":"titleGroup","type":"fieldset","as":null,"items":[{"id":"title","className":null,"__typename":"FormFieldRef"},{"id":"useTitle","className":null,"__typename":"FormFieldRef"}],"props":null,"legend":null,"description":null,"className":null,"viewVariant":null,"toggleState":null,"__typename":"FormFieldset"},{"id":"useBackground","type":"fieldset","as":null,"items":[{"id":"useBackground","className":null,"__typename":"FormFieldRef"}],"props":null,"legend":null,"description":null,"className":null,"viewVariant":null,"toggleState":null,"__typename":"FormFieldset"},{"id":"widgetVisibility","type":"fieldset","as":null,"items":[{"id":"widgetVisibility","className":null,"__typename":"FormFieldRef"}],"props":null,"legend":null,"description":null,"className":null,"viewVariant":null,"toggleState":null,"__typename":"FormFieldset"},{"id":"moreOptionsGroup","type":"fieldset","as":null,"items":[{"id":"moreOptions","className":null,"__typename":"FormFieldRef"}],"props":null,"legend":null,"description":null,"className":null,"viewVariant":null,"toggleState":null,"__typename":"FormFieldset"},{"id":"componentPropsGroup","type":"fieldset","as":null,"items":[{"id":"cMax_items","className":null,"__typename":"FormFieldRef"}],"props":null,"legend":null,"description":null,"className":null,"viewVariant":null,"toggleState":null,"__typename":"FormFieldset"}],"actionButtons":null,"className":"custom_widget_HeroBanner_form","formGroupFieldSeparator":"divider","__typename":"FormLayout"},"__typename":"Form"},"__typename":"Component","localOverride":false},"globalCss":null,"form":{"fields":[{"id":"widgetChooser","validation":null,"noValidation":null,"dataType":"STRING","list":null,"control":null,"defaultValue":null,"label":null,"description":null,"possibleValues":null,"__typename":"FormField"},{"id":"title","validation":null,"noValidation":null,"dataType":"STRING","list":null,"control":null,"defaultValue":null,"label":null,"description":null,"possibleValues":null,"__typename":"FormField"},{"id":"useTitle","validation":null,"noValidation":null,"dataType":"BOOLEAN","list":null,"control":null,"defaultValue":null,"label":null,"description":null,"possibleValues":null,"__typename":"FormField"},{"id":"useBackground","validation":null,"noValidation":null,"dataType":"BOOLEAN","list":null,"control":null,"defaultValue":null,"label":null,"description":null,"possibleValues":null,"__typename":"FormField"},{"id":"widgetVisibility","validation":null,"noValidation":null,"dataType":"STRING","list":null,"control":null,"defaultValue":null,"label":null,"description":null,"possibleValues":null,"__typename":"FormField"},{"id":"moreOptions","validation":null,"noValidation":null,"dataType":"STRING","list":null,"control":null,"defaultValue":null,"label":null,"description":null,"possibleValues":null,"__typename":"FormField"},{"id":"cMax_items","validation":null,"noValidation":null,"dataType":"NUMBER","list":false,"control":"INPUT","defaultValue":"3","label":"Max Items","description":"The maximum number of items to display in the carousel","possibleValues":null,"__typename":"FormField"}],"layout":{"rows":[{"id":"widgetChooserGroup","type":"fieldset","as":null,"items":[{"id":"widgetChooser","className":null,"__typename":"FormFieldRef"}],"props":null,"legend":null,"description":null,"className":null,"viewVariant":null,"toggleState":null,"__typename":"FormFieldset"},{"id":"titleGroup","type":"fieldset","as":null,"items":[{"id":"title","className":null,"__typename":"FormFieldRef"},{"id":"useTitle","className":null,"__typename":"FormFieldRef"}],"props":null,"legend":null,"description":null,"className":null,"viewVariant":null,"toggleState":null,"__typename":"FormFieldset"},{"id":"useBackground","type":"fieldset","as":null,"items":[{"id":"useBackground","className":null,"__typename":"FormFieldRef"}],"props":null,"legend":null,"description":null,"className":null,"viewVariant":null,"toggleState":null,"__typename":"FormFieldset"},{"id":"widgetVisibility","type":"fieldset","as":null,"items":[{"id":"widgetVisibility","className":null,"__typename":"FormFieldRef"}],"props":null,"legend":null,"description":null,"className":null,"viewVariant":null,"toggleState":null,"__typename":"FormFieldset"},{"id":"moreOptionsGroup","type":"fieldset","as":null,"items":[{"id":"moreOptions","className":null,"__typename":"FormFieldRef"}],"props":null,"legend":null,"description":null,"className":null,"viewVariant":null,"toggleState":null,"__typename":"FormFieldset"},{"id":"componentPropsGroup","type":"fieldset","as":null,"items":[{"id":"cMax_items","className":null,"__typename":"FormFieldRef"}],"props":null,"legend":null,"description":null,"className":null,"viewVariant":null,"toggleState":null,"__typename":"FormFieldset"}],"actionButtons":null,"className":"custom_widget_HeroBanner_form","formGroupFieldSeparator":"divider","__typename":"FormLayout"},"__typename":"Form"}},"localOverride":false},"CachedAsset:component:custom.widget.Social_Sharing-en-1743150561112":{"__typename":"CachedAsset","id":"component:custom.widget.Social_Sharing-en-1743150561112","value":{"component":{"id":"custom.widget.Social_Sharing","template":{"id":"Social_Sharing","markupLanguage":"HANDLEBARS","style":".social-share {\n .sharing-options {\n position: relative;\n margin: 0;\n padding: 0;\n line-height: 10px;\n display: flex;\n justify-content: left;\n gap: 5px;\n list-style-type: none;\n li {\n text-align: left;\n a {\n min-width: 30px;\n min-height: 30px;\n display: block;\n padding: 1px;\n .social-share-linkedin {\n img {\n background-color: rgb(0, 119, 181);\n }\n }\n .social-share-facebook {\n img {\n background-color: rgb(59, 89, 152);\n }\n }\n .social-share-x {\n img {\n background-color: rgb(0, 0, 0);\n }\n }\n .social-share-rss {\n img {\n background-color: rgb(0, 0, 0);\n }\n }\n .social-share-reddit {\n img {\n background-color: rgb(255, 69, 0);\n }\n }\n .social-share-email {\n img {\n background-color: rgb(132, 132, 132);\n }\n }\n }\n a {\n img {\n height: 2rem;\n }\n }\n }\n }\n}\n","texts":null,"defaults":{"config":{"applicablePages":[],"description":"Adds buttons to share to various social media websites","fetchedContent":null,"__typename":"ComponentConfiguration"},"props":[],"__typename":"ComponentProperties"},"components":[{"id":"custom.widget.Social_Sharing","form":null,"config":null,"props":[],"__typename":"Component"}],"grouping":"CUSTOM","__typename":"ComponentTemplate"},"properties":{"config":{"applicablePages":[],"description":"Adds buttons to share to various social media websites","fetchedContent":null,"__typename":"ComponentConfiguration"},"props":[],"__typename":"ComponentProperties"},"form":null,"__typename":"Component","localOverride":false},"globalCss":{"css":".custom_widget_Social_Sharing_social-share_c7xxz_1 {\n .custom_widget_Social_Sharing_sharing-options_c7xxz_2 {\n position: relative;\n margin: 0;\n padding: 0;\n line-height: 0.625rem;\n display: flex;\n justify-content: left;\n gap: 0.3125rem;\n list-style-type: none;\n li {\n text-align: left;\n a {\n min-width: 1.875rem;\n min-height: 1.875rem;\n display: block;\n padding: 0.0625rem;\n .custom_widget_Social_Sharing_social-share-linkedin_c7xxz_18 {\n img {\n background-color: rgb(0, 119, 181);\n }\n }\n .custom_widget_Social_Sharing_social-share-facebook_c7xxz_23 {\n img {\n background-color: rgb(59, 89, 152);\n }\n }\n .custom_widget_Social_Sharing_social-share-x_c7xxz_28 {\n img {\n background-color: rgb(0, 0, 0);\n }\n }\n .custom_widget_Social_Sharing_social-share-rss_c7xxz_33 {\n img {\n background-color: rgb(0, 0, 0);\n }\n }\n .custom_widget_Social_Sharing_social-share-reddit_c7xxz_38 {\n img {\n background-color: rgb(255, 69, 0);\n }\n }\n .custom_widget_Social_Sharing_social-share-email_c7xxz_43 {\n img {\n background-color: rgb(132, 132, 132);\n }\n }\n }\n a {\n img {\n height: 2rem;\n }\n }\n }\n }\n}\n","tokens":{"social-share":"custom_widget_Social_Sharing_social-share_c7xxz_1","sharing-options":"custom_widget_Social_Sharing_sharing-options_c7xxz_2","social-share-linkedin":"custom_widget_Social_Sharing_social-share-linkedin_c7xxz_18","social-share-facebook":"custom_widget_Social_Sharing_social-share-facebook_c7xxz_23","social-share-x":"custom_widget_Social_Sharing_social-share-x_c7xxz_28","social-share-rss":"custom_widget_Social_Sharing_social-share-rss_c7xxz_33","social-share-reddit":"custom_widget_Social_Sharing_social-share-reddit_c7xxz_38","social-share-email":"custom_widget_Social_Sharing_social-share-email_c7xxz_43"}},"form":null},"localOverride":false},"CachedAsset:component:custom.widget.MicrosoftFooter-en-1743150561112":{"__typename":"CachedAsset","id":"component:custom.widget.MicrosoftFooter-en-1743150561112","value":{"component":{"id":"custom.widget.MicrosoftFooter","template":{"id":"MicrosoftFooter","markupLanguage":"HANDLEBARS","style":".context-uhf {\n min-width: 280px;\n font-size: 15px;\n box-sizing: border-box;\n -ms-text-size-adjust: 100%;\n -webkit-text-size-adjust: 100%;\n & *,\n & *:before,\n & *:after {\n box-sizing: inherit;\n }\n a.c-uhff-link {\n color: #616161;\n word-break: break-word;\n text-decoration: none;\n }\n &a:link,\n &a:focus,\n &a:hover,\n &a:active,\n &a:visited {\n text-decoration: none;\n color: inherit;\n }\n & div {\n font-family: 'Segoe UI', SegoeUI, 'Helvetica Neue', Helvetica, Arial, sans-serif;\n }\n}\n.c-uhff {\n background: #f2f2f2;\n margin: -1.5625;\n width: auto;\n height: auto;\n}\n.c-uhff-nav {\n margin: 0 auto;\n max-width: calc(1600px + 10%);\n padding: 0 5%;\n box-sizing: inherit;\n &:before,\n &:after {\n content: ' ';\n display: table;\n clear: left;\n }\n @media only screen and (max-width: 1083px) {\n padding-left: 12px;\n }\n .c-heading-4 {\n color: #616161;\n word-break: break-word;\n font-size: 15px;\n line-height: 20px;\n padding: 36px 0 4px;\n font-weight: 600;\n }\n .c-uhff-nav-row {\n .c-uhff-nav-group {\n display: block;\n float: left;\n min-height: 1px;\n vertical-align: text-top;\n padding: 0 12px;\n width: 100%;\n zoom: 1;\n &:first-child {\n padding-left: 0;\n @media only screen and (max-width: 1083px) {\n padding-left: 12px;\n }\n }\n @media only screen and (min-width: 540px) and (max-width: 1082px) {\n width: 33.33333%;\n }\n @media only screen and (min-width: 1083px) {\n width: 16.6666666667%;\n }\n ul.c-list.f-bare {\n font-size: 11px;\n line-height: 16px;\n margin-top: 0;\n margin-bottom: 0;\n padding-left: 0;\n list-style-type: none;\n li {\n word-break: break-word;\n padding: 8px 0;\n margin: 0;\n }\n }\n }\n }\n}\n.c-uhff-base {\n background: #f2f2f2;\n margin: 0 auto;\n max-width: calc(1600px + 10%);\n padding: 30px 5% 16px;\n &:before,\n &:after {\n content: ' ';\n display: table;\n }\n &:after {\n clear: both;\n }\n a.c-uhff-ccpa {\n font-size: 11px;\n line-height: 16px;\n float: left;\n margin: 3px 0;\n }\n a.c-uhff-ccpa:hover {\n text-decoration: underline;\n }\n ul.c-list {\n font-size: 11px;\n line-height: 16px;\n float: right;\n margin: 3px 0;\n color: #616161;\n li {\n padding: 0 24px 4px 0;\n display: inline-block;\n }\n }\n .c-list.f-bare {\n padding-left: 0;\n list-style-type: none;\n }\n @media only screen and (max-width: 1083px) {\n display: flex;\n flex-wrap: wrap;\n padding: 30px 24px 16px;\n }\n}\n","texts":{"New tab":"What's New","New 1":"Surface Laptop Studio 2","New 2":"Surface Laptop Go 3","New 3":"Surface Pro 9","New 4":"Surface Laptop 5","New 5":"Surface Studio 2+","New 6":"Copilot in Windows","New 7":"Microsoft 365","New 8":"Windows 11 apps","Store tab":"Microsoft Store","Store 1":"Account Profile","Store 2":"Download Center","Store 3":"Microsoft Store Support","Store 4":"Returns","Store 5":"Order tracking","Store 6":"Certified Refurbished","Store 7":"Microsoft Store Promise","Store 8":"Flexible Payments","Education tab":"Education","Edu 1":"Microsoft in education","Edu 2":"Devices for education","Edu 3":"Microsoft Teams for Education","Edu 4":"Microsoft 365 Education","Edu 5":"How to buy for your school","Edu 6":"Educator Training and development","Edu 7":"Deals for students and parents","Edu 8":"Azure for students","Business tab":"Business","Bus 1":"Microsoft Cloud","Bus 2":"Microsoft Security","Bus 3":"Dynamics 365","Bus 4":"Microsoft 365","Bus 5":"Microsoft Power Platform","Bus 6":"Microsoft Teams","Bus 7":"Microsoft Industry","Bus 8":"Small Business","Developer tab":"Developer & IT","Dev 1":"Azure","Dev 2":"Developer Center","Dev 3":"Documentation","Dev 4":"Microsoft Learn","Dev 5":"Microsoft Tech Community","Dev 6":"Azure Marketplace","Dev 7":"AppSource","Dev 8":"Visual Studio","Company tab":"Company","Com 1":"Careers","Com 2":"About Microsoft","Com 3":"Company News","Com 4":"Privacy at Microsoft","Com 5":"Investors","Com 6":"Diversity and inclusion","Com 7":"Accessiblity","Com 8":"Sustainibility"},"defaults":{"config":{"applicablePages":[],"description":"The Microsoft Footer","fetchedContent":null,"__typename":"ComponentConfiguration"},"props":[],"__typename":"ComponentProperties"},"components":[{"id":"custom.widget.MicrosoftFooter","form":null,"config":null,"props":[],"__typename":"Component"}],"grouping":"CUSTOM","__typename":"ComponentTemplate"},"properties":{"config":{"applicablePages":[],"description":"The Microsoft Footer","fetchedContent":null,"__typename":"ComponentConfiguration"},"props":[],"__typename":"ComponentProperties"},"form":null,"__typename":"Component","localOverride":false},"globalCss":{"css":".custom_widget_MicrosoftFooter_context-uhf_f95yq_1 {\n min-width: 17.5rem;\n font-size: 0.9375rem;\n box-sizing: border-box;\n -ms-text-size-adjust: 100%;\n -webkit-text-size-adjust: 100%;\n & *,\n & *:before,\n & *:after {\n box-sizing: inherit;\n }\n a.custom_widget_MicrosoftFooter_c-uhff-link_f95yq_12 {\n color: #616161;\n word-break: break-word;\n text-decoration: none;\n }\n &a:link,\n &a:focus,\n &a:hover,\n &a:active,\n &a:visited {\n text-decoration: none;\n color: inherit;\n }\n & div {\n font-family: 'Segoe UI', SegoeUI, 'Helvetica Neue', Helvetica, Arial, sans-serif;\n }\n}\n.custom_widget_MicrosoftFooter_c-uhff_f95yq_12 {\n background: #f2f2f2;\n margin: -1.5625;\n width: auto;\n height: auto;\n}\n.custom_widget_MicrosoftFooter_c-uhff-nav_f95yq_35 {\n margin: 0 auto;\n max-width: calc(100rem + 10%);\n padding: 0 5%;\n box-sizing: inherit;\n &:before,\n &:after {\n content: ' ';\n display: table;\n clear: left;\n }\n @media only screen and (max-width: 1083px) {\n padding-left: 0.75rem;\n }\n .custom_widget_MicrosoftFooter_c-heading-4_f95yq_49 {\n color: #616161;\n word-break: break-word;\n font-size: 0.9375rem;\n line-height: 1.25rem;\n padding: 2.25rem 0 0.25rem;\n font-weight: 600;\n }\n .custom_widget_MicrosoftFooter_c-uhff-nav-row_f95yq_57 {\n .custom_widget_MicrosoftFooter_c-uhff-nav-group_f95yq_58 {\n display: block;\n float: left;\n min-height: 0.0625rem;\n vertical-align: text-top;\n padding: 0 0.75rem;\n width: 100%;\n zoom: 1;\n &:first-child {\n padding-left: 0;\n @media only screen and (max-width: 1083px) {\n padding-left: 0.75rem;\n }\n }\n @media only screen and (min-width: 540px) and (max-width: 1082px) {\n width: 33.33333%;\n }\n @media only screen and (min-width: 1083px) {\n width: 16.6666666667%;\n }\n ul.custom_widget_MicrosoftFooter_c-list_f95yq_78.custom_widget_MicrosoftFooter_f-bare_f95yq_78 {\n font-size: 0.6875rem;\n line-height: 1rem;\n margin-top: 0;\n margin-bottom: 0;\n padding-left: 0;\n list-style-type: none;\n li {\n word-break: break-word;\n padding: 0.5rem 0;\n margin: 0;\n }\n }\n }\n }\n}\n.custom_widget_MicrosoftFooter_c-uhff-base_f95yq_94 {\n background: #f2f2f2;\n margin: 0 auto;\n max-width: calc(100rem + 10%);\n padding: 1.875rem 5% 1rem;\n &:before,\n &:after {\n content: ' ';\n display: table;\n }\n &:after {\n clear: both;\n }\n a.custom_widget_MicrosoftFooter_c-uhff-ccpa_f95yq_107 {\n font-size: 0.6875rem;\n line-height: 1rem;\n float: left;\n margin: 0.1875rem 0;\n }\n a.custom_widget_MicrosoftFooter_c-uhff-ccpa_f95yq_107:hover {\n text-decoration: underline;\n }\n ul.custom_widget_MicrosoftFooter_c-list_f95yq_78 {\n font-size: 0.6875rem;\n line-height: 1rem;\n float: right;\n margin: 0.1875rem 0;\n color: #616161;\n li {\n padding: 0 1.5rem 0.25rem 0;\n display: inline-block;\n }\n }\n .custom_widget_MicrosoftFooter_c-list_f95yq_78.custom_widget_MicrosoftFooter_f-bare_f95yq_78 {\n padding-left: 0;\n list-style-type: none;\n }\n @media only screen and (max-width: 1083px) {\n display: flex;\n flex-wrap: wrap;\n padding: 1.875rem 1.5rem 1rem;\n }\n}\n","tokens":{"context-uhf":"custom_widget_MicrosoftFooter_context-uhf_f95yq_1","c-uhff-link":"custom_widget_MicrosoftFooter_c-uhff-link_f95yq_12","c-uhff":"custom_widget_MicrosoftFooter_c-uhff_f95yq_12","c-uhff-nav":"custom_widget_MicrosoftFooter_c-uhff-nav_f95yq_35","c-heading-4":"custom_widget_MicrosoftFooter_c-heading-4_f95yq_49","c-uhff-nav-row":"custom_widget_MicrosoftFooter_c-uhff-nav-row_f95yq_57","c-uhff-nav-group":"custom_widget_MicrosoftFooter_c-uhff-nav-group_f95yq_58","c-list":"custom_widget_MicrosoftFooter_c-list_f95yq_78","f-bare":"custom_widget_MicrosoftFooter_f-bare_f95yq_78","c-uhff-base":"custom_widget_MicrosoftFooter_c-uhff-base_f95yq_94","c-uhff-ccpa":"custom_widget_MicrosoftFooter_c-uhff-ccpa_f95yq_107"}},"form":null},"localOverride":false},"CachedAsset:text:en_US-components/community/Breadcrumb-1743095130000":{"__typename":"CachedAsset","id":"text:en_US-components/community/Breadcrumb-1743095130000","value":{"navLabel":"Breadcrumbs","dropdown":"Additional parent page navigation"},"localOverride":false},"CachedAsset:text:en_US-components/messages/MessageBanner-1743095130000":{"__typename":"CachedAsset","id":"text:en_US-components/messages/MessageBanner-1743095130000","value":{"messageMarkedAsSpam":"This post has been marked as spam","messageMarkedAsSpam@board:TKB":"This article has been marked as spam","messageMarkedAsSpam@board:BLOG":"This post has been marked as spam","messageMarkedAsSpam@board:FORUM":"This discussion has been marked as spam","messageMarkedAsSpam@board:OCCASION":"This event has been marked as spam","messageMarkedAsSpam@board:IDEA":"This idea has been marked as spam","manageSpam":"Manage Spam","messageMarkedAsAbuse":"This post has been marked as abuse","messageMarkedAsAbuse@board:TKB":"This article has been marked as abuse","messageMarkedAsAbuse@board:BLOG":"This post has been marked as abuse","messageMarkedAsAbuse@board:FORUM":"This discussion has been marked as abuse","messageMarkedAsAbuse@board:OCCASION":"This event has been marked as abuse","messageMarkedAsAbuse@board:IDEA":"This idea has been marked as abuse","preModCommentAuthorText":"This comment will be published as soon as it is approved","preModCommentModeratorText":"This comment is awaiting moderation","messageMarkedAsOther":"This post has been rejected due to other reasons","messageMarkedAsOther@board:TKB":"This article has been rejected due to other reasons","messageMarkedAsOther@board:BLOG":"This post has been rejected due to other reasons","messageMarkedAsOther@board:FORUM":"This discussion has been rejected due to other reasons","messageMarkedAsOther@board:OCCASION":"This event has been rejected due to other reasons","messageMarkedAsOther@board:IDEA":"This idea has been rejected due to other reasons","messageArchived":"This post was archived on {date}","relatedUrl":"View Related Content","relatedContentText":"Showing related content","archivedContentLink":"View Archived Content"},"localOverride":false},"Category:category:Exchange":{"__typename":"Category","id":"category:Exchange","categoryPolicies":{"__typename":"CategoryPolicies","canReadNode":{"__typename":"PolicyResult","failureReason":null}}},"Category:category:Planner":{"__typename":"Category","id":"category:Planner","categoryPolicies":{"__typename":"CategoryPolicies","canReadNode":{"__typename":"PolicyResult","failureReason":null}}},"Category:category:Outlook":{"__typename":"Category","id":"category:Outlook","categoryPolicies":{"__typename":"CategoryPolicies","canReadNode":{"__typename":"PolicyResult","failureReason":null}}},"Category:category:Community-Info-Center":{"__typename":"Category","id":"category:Community-Info-Center","categoryPolicies":{"__typename":"CategoryPolicies","canReadNode":{"__typename":"PolicyResult","failureReason":null}}},"Category:category:EducationSector":{"__typename":"Category","id":"category:EducationSector","categoryPolicies":{"__typename":"CategoryPolicies","canReadNode":{"__typename":"PolicyResult","failureReason":null}}},"Category:category:DrivingAdoption":{"__typename":"Category","id":"category:DrivingAdoption","categoryPolicies":{"__typename":"CategoryPolicies","canReadNode":{"__typename":"PolicyResult","failureReason":null}}},"Category:category:Azure":{"__typename":"Category","id":"category:Azure","categoryPolicies":{"__typename":"CategoryPolicies","canReadNode":{"__typename":"PolicyResult","failureReason":null}}},"Category:category:Windows-Server":{"__typename":"Category","id":"category:Windows-Server","categoryPolicies":{"__typename":"CategoryPolicies","canReadNode":{"__typename":"PolicyResult","failureReason":null}}},"Category:category:SQL-Server":{"__typename":"Category","id":"category:SQL-Server","categoryPolicies":{"__typename":"CategoryPolicies","canReadNode":{"__typename":"PolicyResult","failureReason":null}}},"Category:category:MicrosoftTeams":{"__typename":"Category","id":"category:MicrosoftTeams","categoryPolicies":{"__typename":"CategoryPolicies","canReadNode":{"__typename":"PolicyResult","failureReason":null}}},"Category:category:PublicSector":{"__typename":"Category","id":"category:PublicSector","categoryPolicies":{"__typename":"CategoryPolicies","canReadNode":{"__typename":"PolicyResult","failureReason":null}}},"Category:category:microsoft365":{"__typename":"Category","id":"category:microsoft365","categoryPolicies":{"__typename":"CategoryPolicies","canReadNode":{"__typename":"PolicyResult","failureReason":null}}},"Category:category:IoT":{"__typename":"Category","id":"category:IoT","categoryPolicies":{"__typename":"CategoryPolicies","canReadNode":{"__typename":"PolicyResult","failureReason":null}}},"Category:category:HealthcareAndLifeSciences":{"__typename":"Category","id":"category:HealthcareAndLifeSciences","categoryPolicies":{"__typename":"CategoryPolicies","canReadNode":{"__typename":"PolicyResult","failureReason":null}}},"Category:category:SMB":{"__typename":"Category","id":"category:SMB","categoryPolicies":{"__typename":"CategoryPolicies","canReadNode":{"__typename":"PolicyResult","failureReason":null}}},"Category:category:ITOpsTalk":{"__typename":"Category","id":"category:ITOpsTalk","categoryPolicies":{"__typename":"CategoryPolicies","canReadNode":{"__typename":"PolicyResult","failureReason":null}}},"Category:category:microsoft-endpoint-manager":{"__typename":"Category","id":"category:microsoft-endpoint-manager","categoryPolicies":{"__typename":"CategoryPolicies","canReadNode":{"__typename":"PolicyResult","failureReason":null}}},"Category:category:MicrosoftLearn":{"__typename":"Category","id":"category:MicrosoftLearn","categoryPolicies":{"__typename":"CategoryPolicies","canReadNode":{"__typename":"PolicyResult","failureReason":null}}},"Blog:board:MicrosoftLearnBlog":{"__typename":"Blog","id":"board:MicrosoftLearnBlog","blogPolicies":{"__typename":"BlogPolicies","canReadNode":{"__typename":"PolicyResult","failureReason":null}},"boardPolicies":{"__typename":"BoardPolicies","canReadNode":{"__typename":"PolicyResult","failureReason":null}}},"Category:category:MicrosoftMechanics":{"__typename":"Category","id":"category:MicrosoftMechanics","categoryPolicies":{"__typename":"CategoryPolicies","canReadNode":{"__typename":"PolicyResult","failureReason":null}}},"Category:category:StartupsatMicrosoft":{"__typename":"Category","id":"category:StartupsatMicrosoft","categoryPolicies":{"__typename":"CategoryPolicies","canReadNode":{"__typename":"PolicyResult","failureReason":null}}},"Category:category:PartnerCommunity":{"__typename":"Category","id":"category:PartnerCommunity","categoryPolicies":{"__typename":"CategoryPolicies","canReadNode":{"__typename":"PolicyResult","failureReason":null}}},"Category:category:Windows":{"__typename":"Category","id":"category:Windows","categoryPolicies":{"__typename":"CategoryPolicies","canReadNode":{"__typename":"PolicyResult","failureReason":null}}},"Category:category:microsoft-security":{"__typename":"Category","id":"category:microsoft-security","categoryPolicies":{"__typename":"CategoryPolicies","canReadNode":{"__typename":"PolicyResult","failureReason":null}}},"QueryVariables:TopicReplyList:message:4269038:11":{"__typename":"QueryVariables","id":"TopicReplyList:message:4269038:11","value":{"id":"message:4269038","first":10,"sorts":{"postTime":{"direction":"DESC"}},"repliesFirst":3,"repliesFirstDepthThree":1,"repliesSorts":{"postTime":{"direction":"DESC"}},"useAvatar":true,"useAuthorLogin":true,"useAuthorRank":true,"useBody":true,"useKudosCount":true,"useTimeToRead":false,"useMedia":false,"useReadOnlyIcon":false,"useRepliesCount":true,"useSearchSnippet":false,"useAcceptedSolutionButton":false,"useSolvedBadge":false,"useAttachments":false,"attachmentsFirst":5,"useTags":true,"useNodeAncestors":false,"useUserHoverCard":false,"useNodeHoverCard":false,"useModerationStatus":true,"usePreviewSubjectModal":false,"useMessageStatus":true}},"ROOT_MUTATION":{"__typename":"Mutation"},"CachedAsset:text:en_US-components/community/Navbar-1743095130000":{"__typename":"CachedAsset","id":"text:en_US-components/community/Navbar-1743095130000","value":{"community":"Community Home","inbox":"Inbox","manageContent":"Manage Content","tos":"Terms of Service","forgotPassword":"Forgot Password","themeEditor":"Theme Editor","edit":"Edit Navigation Bar","skipContent":"Skip to content","gxcuf89792":"Tech Community","external-1":"Events","s-m-b":"Small and Medium Businesses","windows-server":"Windows Server","education-sector":"Education Sector","driving-adoption":"Driving Adoption","microsoft-learn":"Microsoft Learn","s-q-l-server":"SQL Server","partner-community":"Microsoft Partner Community","microsoft365":"Microsoft 365","external-9":".NET","external-8":"Teams","external-7":"Github","products-services":"Products","external-6":"Power Platform","communities-1":"Topics","external-5":"Microsoft Security","planner":"Planner","external-4":"Microsoft 365","external-3":"Dynamics 365","azure":"Azure","healthcare-and-life-sciences":"Healthcare and Life Sciences","external-2":"Azure","microsoft-mechanics":"Microsoft Mechanics","microsoft-learn-1":"Community","external-10":"Learning Room Directory","microsoft-learn-blog":"Blog","windows":"Windows","i-t-ops-talk":"ITOps Talk","external-link-1":"View All","microsoft-securityand-compliance":"Microsoft Security","public-sector":"Public Sector","community-info-center":"Lounge","external-link-2":"View All","microsoft-teams":"Microsoft Teams","external":"Blogs","microsoft-endpoint-manager":"Microsoft Intune and Configuration Manager","startupsat-microsoft":"Startups at Microsoft","exchange":"Exchange","a-i":"AI and Machine Learning","io-t":"Internet of Things (IoT)","outlook":"Outlook","external-link":"Community Hubs","communities":"Products"},"localOverride":false},"CachedAsset:text:en_US-components/community/NavbarHamburgerDropdown-1743095130000":{"__typename":"CachedAsset","id":"text:en_US-components/community/NavbarHamburgerDropdown-1743095130000","value":{"hamburgerLabel":"Side Menu"},"localOverride":false},"CachedAsset:text:en_US-components/community/BrandLogo-1743095130000":{"__typename":"CachedAsset","id":"text:en_US-components/community/BrandLogo-1743095130000","value":{"logoAlt":"Khoros","themeLogoAlt":"Brand Logo"},"localOverride":false},"CachedAsset:text:en_US-components/community/NavbarTextLinks-1743095130000":{"__typename":"CachedAsset","id":"text:en_US-components/community/NavbarTextLinks-1743095130000","value":{"more":"More"},"localOverride":false},"CachedAsset:text:en_US-components/authentication/AuthenticationLink-1743095130000":{"__typename":"CachedAsset","id":"text:en_US-components/authentication/AuthenticationLink-1743095130000","value":{"title.login":"Sign In","title.registration":"Register","title.forgotPassword":"Forgot Password","title.multiAuthLogin":"Sign In"},"localOverride":false},"CachedAsset:text:en_US-components/nodes/NodeLink-1743095130000":{"__typename":"CachedAsset","id":"text:en_US-components/nodes/NodeLink-1743095130000","value":{"place":"Place {name}"},"localOverride":false},"CachedAsset:text:en_US-components/messages/MessageView/MessageViewStandard-1743095130000":{"__typename":"CachedAsset","id":"text:en_US-components/messages/MessageView/MessageViewStandard-1743095130000","value":{"anonymous":"Anonymous","author":"{messageAuthorLogin}","authorBy":"{messageAuthorLogin}","board":"{messageBoardTitle}","replyToUser":" to {parentAuthor}","showMoreReplies":"Show More","replyText":"Reply","repliesText":"Replies","markedAsSolved":"Marked as Solved","movedMessagePlaceholder.BLOG":"{count, plural, =0 {This comment has been} other {These comments have been} }","movedMessagePlaceholder.TKB":"{count, plural, =0 {This comment has been} other {These comments have been} }","movedMessagePlaceholder.FORUM":"{count, plural, =0 {This reply has been} other {These replies have been} }","movedMessagePlaceholder.IDEA":"{count, plural, =0 {This comment has been} other {These comments have been} }","movedMessagePlaceholder.OCCASION":"{count, plural, =0 {This comment has been} other {These comments have been} }","movedMessagePlaceholderUrlText":"moved.","messageStatus":"Status: ","statusChanged":"Status changed: {previousStatus} to {currentStatus}","statusAdded":"Status added: {status}","statusRemoved":"Status removed: {status}","labelExpand":"expand replies","labelCollapse":"collapse replies","unhelpfulReason.reason1":"Content is outdated","unhelpfulReason.reason2":"Article is missing information","unhelpfulReason.reason3":"Content is for a different Product","unhelpfulReason.reason4":"Doesn't match what I was searching for"},"localOverride":false},"CachedAsset:text:en_US-components/messages/ThreadedReplyList-1743095130000":{"__typename":"CachedAsset","id":"text:en_US-components/messages/ThreadedReplyList-1743095130000","value":{"title":"{count, plural, one{# Reply} other{# Replies}}","title@board:BLOG":"{count, plural, one{# Comment} other{# Comments}}","title@board:TKB":"{count, plural, one{# Comment} other{# Comments}}","title@board:IDEA":"{count, plural, one{# Comment} other{# Comments}}","title@board:OCCASION":"{count, plural, one{# Comment} other{# Comments}}","noRepliesTitle":"No Replies","noRepliesTitle@board:BLOG":"No Comments","noRepliesTitle@board:TKB":"No Comments","noRepliesTitle@board:IDEA":"No Comments","noRepliesTitle@board:OCCASION":"No Comments","noRepliesDescription":"Be the first to reply","noRepliesDescription@board:BLOG":"Be the first to comment","noRepliesDescription@board:TKB":"Be the first to comment","noRepliesDescription@board:IDEA":"Be the first to comment","noRepliesDescription@board:OCCASION":"Be the first to comment","messageReadOnlyAlert:BLOG":"Comments have been turned off for this post","messageReadOnlyAlert:TKB":"Comments have been turned off for this article","messageReadOnlyAlert:IDEA":"Comments have been turned off for this idea","messageReadOnlyAlert:FORUM":"Replies have been turned off for this discussion","messageReadOnlyAlert:OCCASION":"Comments have been turned off for this event"},"localOverride":false},"CachedAsset:text:en_US-components/messages/MessageReplyCallToAction-1743095130000":{"__typename":"CachedAsset","id":"text:en_US-components/messages/MessageReplyCallToAction-1743095130000","value":{"leaveReply":"Leave a reply...","leaveReply@board:BLOG@message:root":"Leave a comment...","leaveReply@board:TKB@message:root":"Leave a comment...","leaveReply@board:IDEA@message:root":"Leave a comment...","leaveReply@board:OCCASION@message:root":"Leave a comment...","repliesTurnedOff.FORUM":"Replies are turned off for this topic","repliesTurnedOff.BLOG":"Comments are turned off for this topic","repliesTurnedOff.TKB":"Comments are turned off for this topic","repliesTurnedOff.IDEA":"Comments are turned off for this topic","repliesTurnedOff.OCCASION":"Comments are turned off for this topic","infoText":"Stop poking me!"},"localOverride":false},"Rank:rank:37":{"__typename":"Rank","id":"rank:37","position":18,"name":"Copper Contributor","color":"333333","icon":null,"rankStyle":"TEXT"},"User:user:2423548":{"__typename":"User","id":"user:2423548","uid":2423548,"login":"kevinvago","biography":null,"registrationData":{"__typename":"RegistrationData","status":null,"registrationTime":"2024-04-16T04:49:51.284-07:00"},"deleted":false,"email":"","avatar":{"__typename":"UserAvatar","url":"https://techcommunity.microsoft.com/t5/s/gxcuf89792/m_assets/avatars/default/avatar-11.svg?time=0"},"rank":{"__ref":"Rank:rank:37"},"entityType":"USER","eventPath":"community:gxcuf89792/user:2423548"},"ModerationData:moderation_data:4298174":{"__typename":"ModerationData","id":"moderation_data:4298174","status":"APPROVED","rejectReason":null,"isReportedAbuse":false,"rejectUser":null,"rejectTime":null,"rejectActorType":null},"BlogReplyMessage:message:4298174":{"__typename":"BlogReplyMessage","author":{"__ref":"User:user:2423548"},"id":"message:4298174","revisionNum":1,"uid":4298174,"depth":1,"hasGivenKudo":false,"subscribed":false,"board":{"__ref":"Blog:board:Azure-AI-Services-blog"},"parent":{"__ref":"BlogTopicMessage:message:4269038"},"conversation":{"__ref":"Conversation:conversation:4269038"},"subject":"Re: From Zero to Hero: Building Your First Voice Bot with GPT-4o Real-Time API using Python","moderationData":{"__ref":"ModerationData:moderation_data:4298174"},"body":"

Great work, thank you! Any plans to add Azure AI Search?

 

","body@stripHtml({\"removeProcessingText\":false,\"removeSpoilerMarkup\":false,\"removeTocMarkup\":false,\"truncateLength\":200})@stringLength":"66","kudosSumWeight":0,"repliesCount":0,"postTime":"2024-11-15T07:26:14.664-08:00","lastPublishTime":"2024-11-15T07:26:14.664-08:00","metrics":{"__typename":"MessageMetrics","views":545},"visibilityScope":"PUBLIC","placeholder":false,"originalMessageForPlaceholder":null,"entityType":"BLOG_REPLY","eventPath":"category:AI/category:solutions/category:communities/community:gxcuf89792board:Azure-AI-Services-blog/message:4269038/message:4298174","replies":{"__typename":"MessageConnection","pageInfo":{"__typename":"PageInfo","hasNextPage":false,"endCursor":null,"hasPreviousPage":false,"startCursor":null},"edges":[]},"customFields":[],"attachments":{"__typename":"AttachmentConnection","edges":[],"pageInfo":{"__typename":"PageInfo","hasNextPage":false,"endCursor":null,"hasPreviousPage":false,"startCursor":null}}},"User:user:1530221":{"__typename":"User","id":"user:1530221","uid":1530221,"login":"SamuelChou","biography":null,"registrationData":{"__typename":"RegistrationData","status":null,"registrationTime":"2022-09-20T20:42:53.390-07:00"},"deleted":false,"email":"","avatar":{"__typename":"UserAvatar","url":"https://techcommunity.microsoft.com/t5/s/gxcuf89792/m_assets/avatars/default/avatar-1.svg?time=0"},"rank":{"__ref":"Rank:rank:37"},"entityType":"USER","eventPath":"community:gxcuf89792/user:1530221"},"ModerationData:moderation_data:4282642":{"__typename":"ModerationData","id":"moderation_data:4282642","status":"APPROVED","rejectReason":null,"isReportedAbuse":false,"rejectUser":null,"rejectTime":null,"rejectActorType":null},"BlogReplyMessage:message:4282642":{"__typename":"BlogReplyMessage","uid":4282642,"id":"message:4282642","revisionNum":1,"author":{"__ref":"User:user:1530221"},"readOnly":false,"repliesCount":1,"depth":1,"hasGivenKudo":false,"subscribed":false,"board":{"__ref":"Blog:board:Azure-AI-Services-blog"},"parent":{"__ref":"BlogTopicMessage:message:4269038"},"conversation":{"__ref":"Conversation:conversation:4269038"},"subject":"Re: From Zero to Hero: Building Your First Voice Bot with GPT-4o Real-Time API using Python","moderationData":{"__ref":"ModerationData:moderation_data:4282642"},"body":"

How to output real-time subtitles?

","body@stripHtml({\"removeProcessingText\":false,\"removeSpoilerMarkup\":false,\"removeTocMarkup\":false,\"truncateLength\":200})@stringLength":"36","kudosSumWeight":0,"postTime":"2024-10-29T23:59:53.984-07:00","lastPublishTime":"2024-10-29T23:59:53.984-07:00","metrics":{"__typename":"MessageMetrics","views":1384},"visibilityScope":"PUBLIC","placeholder":false,"originalMessageForPlaceholder":null,"entityType":"BLOG_REPLY","eventPath":"category:AI/category:solutions/category:communities/community:gxcuf89792board:Azure-AI-Services-blog/message:4269038/message:4282642","replies":{"__typename":"MessageConnection","pageInfo":{"__typename":"PageInfo","hasNextPage":false,"endCursor":null,"hasPreviousPage":false,"startCursor":null},"edges":[{"__typename":"MessageEdge","cursor":"MjUuMXwyLjF8aXwzfDEzMjowfGludCw0Mjg2OTAyLDQyODY5MDI","node":{"__ref":"BlogReplyMessage:message:4286902"}}]},"customFields":[],"attachments":{"__typename":"AttachmentConnection","edges":[],"pageInfo":{"__typename":"PageInfo","hasNextPage":false,"endCursor":null,"hasPreviousPage":false,"startCursor":null}}},"ModerationData:moderation_data:4286902":{"__typename":"ModerationData","id":"moderation_data:4286902","status":"APPROVED","rejectReason":null,"isReportedAbuse":false,"rejectUser":null,"rejectTime":null,"rejectActorType":null},"BlogReplyMessage:message:4286902":{"__typename":"BlogReplyMessage","author":{"__ref":"User:user:2080373"},"id":"message:4286902","revisionNum":1,"uid":4286902,"depth":2,"hasGivenKudo":false,"subscribed":false,"board":{"__ref":"Blog:board:Azure-AI-Services-blog"},"parent":{"__ref":"BlogReplyMessage:message:4282642"},"conversation":{"__ref":"Conversation:conversation:4269038"},"subject":"Re: From Zero to Hero: Building Your First Voice Bot with GPT-4o Real-Time API using Python","moderationData":{"__ref":"ModerationData:moderation_data:4286902"},"body":"

See the github . Code is updated !

","body@stripHtml({\"removeProcessingText\":false,\"removeSpoilerMarkup\":false,\"removeTocMarkup\":false,\"truncateLength\":200})@stringLength":"36","kudosSumWeight":1,"repliesCount":0,"postTime":"2024-11-05T18:50:20.093-08:00","lastPublishTime":"2024-11-05T18:50:20.093-08:00","metrics":{"__typename":"MessageMetrics","views":491},"visibilityScope":"PUBLIC","placeholder":false,"originalMessageForPlaceholder":null,"entityType":"BLOG_REPLY","eventPath":"category:AI/category:solutions/category:communities/community:gxcuf89792board:Azure-AI-Services-blog/message:4269038/message:4286902","replies":{"__typename":"MessageConnection","pageInfo":{"__typename":"PageInfo","hasNextPage":false,"endCursor":null,"hasPreviousPage":false,"startCursor":null},"edges":[]},"customFields":[],"attachments":{"__typename":"AttachmentConnection","edges":[],"pageInfo":{"__typename":"PageInfo","hasNextPage":false,"endCursor":null,"hasPreviousPage":false,"startCursor":null}}},"User:user:318586":{"__typename":"User","id":"user:318586","uid":318586,"login":"hodachalliv","biography":null,"registrationData":{"__typename":"RegistrationData","status":null,"registrationTime":"2019-04-10T08:20:52.056-07:00"},"deleted":false,"email":"","avatar":{"__typename":"UserAvatar","url":"https://techcommunity.microsoft.com/t5/s/gxcuf89792/m_assets/avatars/default/avatar-10.svg?time=0"},"rank":{"__ref":"Rank:rank:37"},"entityType":"USER","eventPath":"community:gxcuf89792/user:318586"},"ModerationData:moderation_data:4274777":{"__typename":"ModerationData","id":"moderation_data:4274777","status":"APPROVED","rejectReason":null,"isReportedAbuse":false,"rejectUser":null,"rejectTime":null,"rejectActorType":null},"User:user:2386694":{"__typename":"User","id":"user:2386694","uid":2386694,"login":"Manoranjan141","biography":null,"registrationData":{"__typename":"RegistrationData","status":null,"registrationTime":"2024-03-25T21:45:40.863-07:00"},"deleted":false,"email":"","avatar":{"__typename":"UserAvatar","url":"https://techcommunity.microsoft.com/t5/s/gxcuf89792/m_assets/avatars/default/avatar-8.svg?time=0"},"rank":{"__ref":"Rank:rank:37"},"entityType":"USER","eventPath":"community:gxcuf89792/user:2386694"},"BlogReplyMessage:message:4274777":{"__typename":"BlogReplyMessage","uid":4274777,"id":"message:4274777","revisionNum":1,"author":{"__ref":"User:user:318586"},"readOnly":false,"repliesCount":1,"depth":1,"hasGivenKudo":false,"subscribed":false,"board":{"__ref":"Blog:board:Azure-AI-Services-blog"},"parent":{"__ref":"BlogTopicMessage:message:4269038"},"conversation":{"__ref":"Conversation:conversation:4269038"},"subject":"Re: From Zero to Hero: Building Your First Voice Bot with GPT-4o Real-Time API using Python","moderationData":{"__ref":"ModerationData:moderation_data:4274777"},"body":"

Great work.
is it possible to get it integrated with Azure communication services, so it can handle phone calls. similar to communication-services-python-quickstarts/callautomation-openai-sample at main · Azure-Samples/communication-services-python-quickstarts

Another reference would be from Twilio who has successfully implemented a similar setup with their speech-assistant-openai-realtime-api-python. Seeing the success of Twilio's implementation, I believe that integrating Azure Communication Services could significantly enhance our phone call handling capabilities.

twilio-samples/speech-assistant-openai-realtime-api-python

","body@stripHtml({\"removeProcessingText\":false,\"removeSpoilerMarkup\":false,\"removeTocMarkup\":false,\"truncateLength\":200})@stringLength":"208","kudosSumWeight":0,"postTime":"2024-10-19T13:11:13.035-07:00","lastPublishTime":"2024-10-19T13:11:13.035-07:00","metrics":{"__typename":"MessageMetrics","views":2540},"visibilityScope":"PUBLIC","placeholder":false,"originalMessageForPlaceholder":null,"entityType":"BLOG_REPLY","eventPath":"category:AI/category:solutions/category:communities/community:gxcuf89792board:Azure-AI-Services-blog/message:4269038/message:4274777","replies":{"__typename":"MessageConnection","pageInfo":{"__typename":"PageInfo","hasNextPage":false,"endCursor":null,"hasPreviousPage":false,"startCursor":null},"edges":[{"__typename":"MessageEdge","cursor":"MjUuMXwyLjF8aXwzfDEzMjowfGludCw0Mjg4MzA0LDQyODgzMDQ","node":{"__ref":"BlogReplyMessage:message:4288304"}}]},"customFields":[],"attachments":{"__typename":"AttachmentConnection","edges":[],"pageInfo":{"__typename":"PageInfo","hasNextPage":false,"endCursor":null,"hasPreviousPage":false,"startCursor":null}}},"ModerationData:moderation_data:4288304":{"__typename":"ModerationData","id":"moderation_data:4288304","status":"UNMODERATED","rejectReason":null,"isReportedAbuse":false,"rejectUser":null,"rejectTime":null,"rejectActorType":null},"BlogReplyMessage:message:4288304":{"__typename":"BlogReplyMessage","author":{"__ref":"User:user:2386694"},"id":"message:4288304","revisionNum":1,"uid":4288304,"depth":2,"hasGivenKudo":false,"subscribed":false,"board":{"__ref":"Blog:board:Azure-AI-Services-blog"},"parent":{"__ref":"BlogReplyMessage:message:4274777"},"conversation":{"__ref":"Conversation:conversation:4269038"},"subject":"Re: From Zero to Hero: Building Your First Voice Bot with GPT-4o Real-Time API using Python","moderationData":{"__ref":"ModerationData:moderation_data:4288304"},"body":"

Yes you can do the same. Watch out for the next blog !

","body@stripHtml({\"removeProcessingText\":false,\"removeSpoilerMarkup\":false,\"removeTocMarkup\":false,\"truncateLength\":200})@stringLength":"56","kudosSumWeight":0,"repliesCount":0,"postTime":"2024-11-07T06:02:26.265-08:00","lastPublishTime":"2024-11-07T06:02:26.265-08:00","metrics":{"__typename":"MessageMetrics","views":422},"visibilityScope":"PUBLIC","placeholder":false,"originalMessageForPlaceholder":null,"entityType":"BLOG_REPLY","eventPath":"category:AI/category:solutions/category:communities/community:gxcuf89792board:Azure-AI-Services-blog/message:4269038/message:4288304","replies":{"__typename":"MessageConnection","pageInfo":{"__typename":"PageInfo","hasNextPage":false,"endCursor":null,"hasPreviousPage":false,"startCursor":null},"edges":[]},"customFields":[],"attachments":{"__typename":"AttachmentConnection","edges":[],"pageInfo":{"__typename":"PageInfo","hasNextPage":false,"endCursor":null,"hasPreviousPage":false,"startCursor":null}}},"ModerationData:moderation_data:4270754":{"__typename":"ModerationData","id":"moderation_data:4270754","status":"APPROVED","rejectReason":null,"isReportedAbuse":false,"rejectUser":null,"rejectTime":null,"rejectActorType":null},"BlogReplyMessage:message:4270754":{"__typename":"BlogReplyMessage","author":{"__ref":"User:user:2080373"},"id":"message:4270754","revisionNum":1,"uid":4270754,"depth":1,"hasGivenKudo":false,"subscribed":false,"board":{"__ref":"Blog:board:Azure-AI-Services-blog"},"parent":{"__ref":"BlogTopicMessage:message:4269038"},"conversation":{"__ref":"Conversation:conversation:4269038"},"subject":"Re: From Zero to Hero: Building Your First Voice Bot with GPT-4o Real-Time API using Python","moderationData":{"__ref":"ModerationData:moderation_data:4270754"},"body":"

torumakabe Updated the code

","body@stripHtml({\"removeProcessingText\":false,\"removeSpoilerMarkup\":false,\"removeTocMarkup\":false,\"truncateLength\":200})@stringLength":"34","kudosSumWeight":1,"repliesCount":0,"postTime":"2024-10-15T01:32:09.695-07:00","lastPublishTime":"2024-10-15T01:32:09.695-07:00","metrics":{"__typename":"MessageMetrics","views":3596},"visibilityScope":"PUBLIC","placeholder":false,"originalMessageForPlaceholder":null,"entityType":"BLOG_REPLY","eventPath":"category:AI/category:solutions/category:communities/community:gxcuf89792board:Azure-AI-Services-blog/message:4269038/message:4270754","replies":{"__typename":"MessageConnection","pageInfo":{"__typename":"PageInfo","hasNextPage":false,"endCursor":null,"hasPreviousPage":false,"startCursor":null},"edges":[]},"customFields":[],"attachments":{"__typename":"AttachmentConnection","edges":[],"pageInfo":{"__typename":"PageInfo","hasNextPage":false,"endCursor":null,"hasPreviousPage":false,"startCursor":null}}},"ModerationData:moderation_data:4270753":{"__typename":"ModerationData","id":"moderation_data:4270753","status":"APPROVED","rejectReason":null,"isReportedAbuse":false,"rejectUser":null,"rejectTime":null,"rejectActorType":null},"BlogReplyMessage:message:4270753":{"__typename":"BlogReplyMessage","author":{"__ref":"User:user:2080373"},"id":"message:4270753","revisionNum":1,"uid":4270753,"depth":1,"hasGivenKudo":false,"subscribed":false,"board":{"__ref":"Blog:board:Azure-AI-Services-blog"},"parent":{"__ref":"BlogTopicMessage:message:4269038"},"conversation":{"__ref":"Conversation:conversation:4269038"},"subject":"Re: From Zero to Hero: Building Your First Voice Bot with GPT-4o Real-Time API using Python","moderationData":{"__ref":"ModerationData:moderation_data:4270753"},"body":"

SamuelChou  Please use the github repo mentioned in the blog.

","body@stripHtml({\"removeProcessingText\":false,\"removeSpoilerMarkup\":false,\"removeTocMarkup\":false,\"truncateLength\":200})@stringLength":"68","kudosSumWeight":0,"repliesCount":0,"postTime":"2024-10-15T01:31:53.963-07:00","lastPublishTime":"2024-10-15T01:31:53.963-07:00","metrics":{"__typename":"MessageMetrics","views":3528},"visibilityScope":"PUBLIC","placeholder":false,"originalMessageForPlaceholder":null,"entityType":"BLOG_REPLY","eventPath":"category:AI/category:solutions/category:communities/community:gxcuf89792board:Azure-AI-Services-blog/message:4269038/message:4270753","replies":{"__typename":"MessageConnection","pageInfo":{"__typename":"PageInfo","hasNextPage":false,"endCursor":null,"hasPreviousPage":false,"startCursor":null},"edges":[]},"customFields":[],"attachments":{"__typename":"AttachmentConnection","edges":[],"pageInfo":{"__typename":"PageInfo","hasNextPage":false,"endCursor":null,"hasPreviousPage":false,"startCursor":null}}},"ModerationData:moderation_data:4270598":{"__typename":"ModerationData","id":"moderation_data:4270598","status":"APPROVED","rejectReason":null,"isReportedAbuse":false,"rejectUser":null,"rejectTime":null,"rejectActorType":null},"BlogReplyMessage:message:4270598":{"__typename":"BlogReplyMessage","author":{"__ref":"User:user:1530221"},"id":"message:4270598","revisionNum":1,"uid":4270598,"depth":1,"hasGivenKudo":false,"subscribed":false,"board":{"__ref":"Blog:board:Azure-AI-Services-blog"},"parent":{"__ref":"BlogTopicMessage:message:4269038"},"conversation":{"__ref":"Conversation:conversation:4269038"},"subject":"Re: From Zero to Hero: Building Your First Voice Bot with GPT-4o Real-Time API using Python","moderationData":{"__ref":"ModerationData:moderation_data:4270598"},"body":"

app.py show error

Unable to parse import \"realtime.tools\"

","body@stripHtml({\"removeProcessingText\":false,\"removeSpoilerMarkup\":false,\"removeTocMarkup\":false,\"truncateLength\":200})@stringLength":"60","kudosSumWeight":0,"repliesCount":0,"postTime":"2024-10-14T20:27:44.843-07:00","lastPublishTime":"2024-10-14T20:27:44.843-07:00","metrics":{"__typename":"MessageMetrics","views":3657},"visibilityScope":"PUBLIC","placeholder":false,"originalMessageForPlaceholder":null,"entityType":"BLOG_REPLY","eventPath":"category:AI/category:solutions/category:communities/community:gxcuf89792board:Azure-AI-Services-blog/message:4269038/message:4270598","replies":{"__typename":"MessageConnection","pageInfo":{"__typename":"PageInfo","hasNextPage":false,"endCursor":null,"hasPreviousPage":false,"startCursor":null},"edges":[]},"customFields":[],"attachments":{"__typename":"AttachmentConnection","edges":[],"pageInfo":{"__typename":"PageInfo","hasNextPage":false,"endCursor":null,"hasPreviousPage":false,"startCursor":null}}},"User:user:94497":{"__typename":"User","id":"user:94497","uid":94497,"login":"torumakabe","biography":null,"registrationData":{"__typename":"RegistrationData","status":null,"registrationTime":"2017-11-13T02:10:56.411-08:00"},"deleted":false,"email":"","avatar":{"__typename":"UserAvatar","url":"https://techcommunity.microsoft.com/t5/s/gxcuf89792/m_assets/avatars/default/avatar-4.svg?time=0"},"rank":{"__ref":"Rank:rank:4"},"entityType":"USER","eventPath":"community:gxcuf89792/user:94497"},"ModerationData:moderation_data:4269234":{"__typename":"ModerationData","id":"moderation_data:4269234","status":"APPROVED","rejectReason":null,"isReportedAbuse":false,"rejectUser":null,"rejectTime":null,"rejectActorType":null},"BlogReplyMessage:message:4269234":{"__typename":"BlogReplyMessage","author":{"__ref":"User:user:94497"},"id":"message:4269234","revisionNum":3,"uid":4269234,"depth":1,"hasGivenKudo":false,"subscribed":false,"board":{"__ref":"Blog:board:Azure-AI-Services-blog"},"parent":{"__ref":"BlogTopicMessage:message:4269038"},"conversation":{"__ref":"Conversation:conversation:4269038"},"subject":"Re: From Zero to Hero: Building Your First Voice Bot with GPT-4o Real-Time API using Python","moderationData":{"__ref":"ModerationData:moderation_data:4269234"},"body":"

Great article! BTW, the code (init.py, tool.py, app.py) reference is linked to the MS Learn article and I don't see any explanation of the code in the link.

","body@stripHtml({\"removeProcessingText\":false,\"removeSpoilerMarkup\":false,\"removeTocMarkup\":false,\"truncateLength\":200})@stringLength":"158","kudosSumWeight":0,"repliesCount":0,"postTime":"2024-10-12T17:58:21.549-07:00","lastPublishTime":"2024-10-12T17:59:42.117-07:00","metrics":{"__typename":"MessageMetrics","views":4365},"visibilityScope":"PUBLIC","placeholder":false,"originalMessageForPlaceholder":null,"entityType":"BLOG_REPLY","eventPath":"category:AI/category:solutions/category:communities/community:gxcuf89792board:Azure-AI-Services-blog/message:4269038/message:4269234","replies":{"__typename":"MessageConnection","pageInfo":{"__typename":"PageInfo","hasNextPage":false,"endCursor":null,"hasPreviousPage":false,"startCursor":null},"edges":[]},"customFields":[],"attachments":{"__typename":"AttachmentConnection","edges":[],"pageInfo":{"__typename":"PageInfo","hasNextPage":false,"endCursor":null,"hasPreviousPage":false,"startCursor":null}}},"CachedAsset:text:en_US-components/community/NavbarDropdownToggle-1743095130000":{"__typename":"CachedAsset","id":"text:en_US-components/community/NavbarDropdownToggle-1743095130000","value":{"ariaLabelClosed":"Press the down arrow to open the menu"},"localOverride":false},"CachedAsset:text:en_US-shared/client/components/common/QueryHandler-1743095130000":{"__typename":"CachedAsset","id":"text:en_US-shared/client/components/common/QueryHandler-1743095130000","value":{"title":"Query Handler"},"localOverride":false},"CachedAsset:text:en_US-components/messages/MessageCoverImage-1743095130000":{"__typename":"CachedAsset","id":"text:en_US-components/messages/MessageCoverImage-1743095130000","value":{"coverImageTitle":"Cover Image"},"localOverride":false},"CachedAsset:text:en_US-shared/client/components/nodes/NodeTitle-1743095130000":{"__typename":"CachedAsset","id":"text:en_US-shared/client/components/nodes/NodeTitle-1743095130000","value":{"nodeTitle":"{nodeTitle, select, community {Community} other {{nodeTitle}}} "},"localOverride":false},"CachedAsset:text:en_US-components/messages/MessageTimeToRead-1743095130000":{"__typename":"CachedAsset","id":"text:en_US-components/messages/MessageTimeToRead-1743095130000","value":{"minReadText":"{min} MIN READ"},"localOverride":false},"CachedAsset:text:en_US-components/messages/MessageSubject-1743095130000":{"__typename":"CachedAsset","id":"text:en_US-components/messages/MessageSubject-1743095130000","value":{"noSubject":"(no subject)"},"localOverride":false},"CachedAsset:text:en_US-components/users/UserLink-1743095130000":{"__typename":"CachedAsset","id":"text:en_US-components/users/UserLink-1743095130000","value":{"authorName":"View Profile: {author}","anonymous":"Anonymous"},"localOverride":false},"CachedAsset:text:en_US-shared/client/components/users/UserRank-1743095130000":{"__typename":"CachedAsset","id":"text:en_US-shared/client/components/users/UserRank-1743095130000","value":{"rankName":"{rankName}","userRank":"Author rank {rankName}"},"localOverride":false},"CachedAsset:text:en_US-components/messages/MessageTime-1743095130000":{"__typename":"CachedAsset","id":"text:en_US-components/messages/MessageTime-1743095130000","value":{"postTime":"Published: {time}","lastPublishTime":"Last Update: {time}","conversation.lastPostingActivityTime":"Last posting activity time: {time}","conversation.lastPostTime":"Last post time: {time}","moderationData.rejectTime":"Rejected time: {time}"},"localOverride":false},"CachedAsset:text:en_US-components/messages/MessageBody-1743095130000":{"__typename":"CachedAsset","id":"text:en_US-components/messages/MessageBody-1743095130000","value":{"showMessageBody":"Show More","mentionsErrorTitle":"{mentionsType, select, board {Board} user {User} message {Message} other {}} No Longer Available","mentionsErrorMessage":"The {mentionsType} you are trying to view has been removed from the community.","videoProcessing":"Video is being processed. Please try again in a few minutes.","bannerTitle":"Video provider requires cookies to play the video. Accept to continue or {url} it directly on the provider's site.","buttonTitle":"Accept","urlText":"watch"},"localOverride":false},"CachedAsset:text:en_US-components/messages/MessageCustomFields-1743095130000":{"__typename":"CachedAsset","id":"text:en_US-components/messages/MessageCustomFields-1743095130000","value":{"CustomField.default.label":"Value of {name}"},"localOverride":false},"CachedAsset:text:en_US-components/messages/MessageRevision-1743095130000":{"__typename":"CachedAsset","id":"text:en_US-components/messages/MessageRevision-1743095130000","value":{"lastUpdatedDatePublished":"{publishCount, plural, one{Published} other{Updated}} {date}","lastUpdatedDateDraft":"Created {date}","version":"Version {major}.{minor}"},"localOverride":false},"CachedAsset:text:en_US-components/messages/MessageReplyButton-1743095130000":{"__typename":"CachedAsset","id":"text:en_US-components/messages/MessageReplyButton-1743095130000","value":{"repliesCount":"{count}","title":"Reply","title@board:BLOG@message:root":"Comment","title@board:TKB@message:root":"Comment","title@board:IDEA@message:root":"Comment","title@board:OCCASION@message:root":"Comment"},"localOverride":false},"CachedAsset:text:en_US-components/messages/MessageAuthorBio-1743095130000":{"__typename":"CachedAsset","id":"text:en_US-components/messages/MessageAuthorBio-1743095130000","value":{"sendMessage":"Send Message","actionMessage":"Follow this blog board to get notified when there's new activity","coAuthor":"CO-PUBLISHER","contributor":"CONTRIBUTOR","userProfile":"View Profile","iconlink":"Go to {name} {type}"},"localOverride":false},"CachedAsset:text:en_US-shared/client/components/users/UserAvatar-1743095130000":{"__typename":"CachedAsset","id":"text:en_US-shared/client/components/users/UserAvatar-1743095130000","value":{"altText":"{login}'s avatar","altTextGeneric":"User's avatar"},"localOverride":false},"CachedAsset:text:en_US-shared/client/components/ranks/UserRankLabel-1743095130000":{"__typename":"CachedAsset","id":"text:en_US-shared/client/components/ranks/UserRankLabel-1743095130000","value":{"altTitle":"Icon for {rankName} rank"},"localOverride":false},"CachedAsset:text:en_US-components/users/UserRegistrationDate-1743095130000":{"__typename":"CachedAsset","id":"text:en_US-components/users/UserRegistrationDate-1743095130000","value":{"noPrefix":"{date}","withPrefix":"Joined {date}"},"localOverride":false},"CachedAsset:text:en_US-shared/client/components/nodes/NodeAvatar-1743095130000":{"__typename":"CachedAsset","id":"text:en_US-shared/client/components/nodes/NodeAvatar-1743095130000","value":{"altTitle":"Node avatar for {nodeTitle}"},"localOverride":false},"CachedAsset:text:en_US-shared/client/components/nodes/NodeDescription-1743095130000":{"__typename":"CachedAsset","id":"text:en_US-shared/client/components/nodes/NodeDescription-1743095130000","value":{"description":"{description}"},"localOverride":false},"CachedAsset:text:en_US-shared/client/components/common/Pager/PagerLoadMore-1743095130000":{"__typename":"CachedAsset","id":"text:en_US-shared/client/components/common/Pager/PagerLoadMore-1743095130000","value":{"loadMore":"Show More"},"localOverride":false},"CachedAsset:text:en_US-components/tags/TagView/TagViewChip-1743095130000":{"__typename":"CachedAsset","id":"text:en_US-components/tags/TagView/TagViewChip-1743095130000","value":{"tagLabelName":"Tag name {tagName}"},"localOverride":false},"CachedAsset:text:en_US-shared/client/components/nodes/NodeIcon-1743095130000":{"__typename":"CachedAsset","id":"text:en_US-shared/client/components/nodes/NodeIcon-1743095130000","value":{"contentType":"Content Type {style, select, FORUM {Forum} BLOG {Blog} TKB {Knowledge Base} IDEA {Ideas} OCCASION {Events} other {}} icon"},"localOverride":false}}}},"page":"/blogs/BlogMessagePage/BlogMessagePage","query":{"boardId":"azure-ai-services-blog","messageSubject":"from-zero-to-hero-building-your-first-voice-bot-with-gpt-4o-real-time-api-using-","messageId":"4269038"},"buildId":"HEhyUrv5OXNBIbfCLaOrw","runtimeConfig":{"buildInformationVisible":false,"logLevelApp":"info","logLevelMetrics":"info","openTelemetryClientEnabled":false,"openTelemetryConfigName":"o365","openTelemetryServiceVersion":"25.1.0","openTelemetryUniverse":"prod","openTelemetryCollector":"http://localhost:4318","openTelemetryRouteChangeAllowedTime":"5000","apolloDevToolsEnabled":false,"inboxMuteWipFeatureEnabled":false},"isFallback":false,"isExperimentalCompile":false,"dynamicIds":["./components/community/Navbar/NavbarWidget.tsx","./components/community/Breadcrumb/BreadcrumbWidget.tsx","./components/customComponent/CustomComponent/CustomComponent.tsx","./components/blogs/BlogArticleWidget/BlogArticleWidget.tsx","./components/external/components/ExternalComponent.tsx","./components/messages/MessageView/MessageViewStandard/MessageViewStandard.tsx","./components/messages/ThreadedReplyList/ThreadedReplyList.tsx","../shared/client/components/common/List/UnstyledList/UnstyledList.tsx","./components/messages/MessageView/MessageView.tsx","../shared/client/components/common/Pager/PagerLoadMore/PagerLoadMore.tsx","../shared/client/components/common/List/UnwrappedList/UnwrappedList.tsx","./components/tags/TagView/TagView.tsx","./components/tags/TagView/TagViewChip/TagViewChip.tsx"],"appGip":true,"scriptLoader":[{"id":"analytics","src":"https://techcommunity.microsoft.com/t5/s/gxcuf89792/pagescripts/1730819800000/analytics.js?page.id=BlogMessagePage&entity.id=board%3Aazure-ai-services-blog&entity.id=message%3A4269038","strategy":"afterInteractive"}]}