본문으로 바로가기

Grok Voice Think Fast 2.0 API 튜토리얼: Python으로 실시간 보이스 에이전트 만들기

Grok Voice Think Fast 2.0으로 실시간 보이스 에이전트를 구축해 음성 대화 처리, 도구 호출, 끼어들기 처리, 끊긴 세션 재개를 구현하는 방법을 배워보세요.
업데이트됨 2026년 8월 8일  · 15분 읽다

AI로 탐색하기

ChatGPT에서 열기Claude에서 열기Perplexity에서 열기

SpaceXAI의 Grok Voice Think Fast 2.0은 음성-대-음성 모델입니다. WebSocket으로 오디오를 보내면 오디오로 응답하고, 그 사이에 스스로 결정한 함수 호출이 이미 실행 중인 동안에도 추론하며 계속 말할 수 있습니다. 별도의 음성-텍스트 변환도, 텍스트-음성 변환도 없습니다.

SpaceXAI는 2026년 7월 29일 Think Fast 2.0을 발표했습니다. 첫 음성 응답이 더 빨라졌고, 완전 양방향 동작이 더 안정적이며(엄격히 번갈아 말하지 않고 말하는 동안 듣기), 턴 초기에 도구 호출을 발화합니다. 튜토리얼에서 중요한 것은 코드의 변화이므로 벤치마크 얘기는 간단히 하겠습니다.

온라인 스토어용 고객 지원 보이스 에이전트를 만들어 보겠습니다. 발신자는 주문을 조회하고, 번호가 없을 때 이메일로 찾고, 배송 지시를 변경하고, 취소하고, 에이전트가 말하는 도중 끼어들며, 연결이 끊긴 뒤에도 대화를 이어갈 수 있습니다. 이는 API 경로이며, 우리 Grok Voice Agent Builder 튜토리얼에서 설명하는 노코드 빌더는 아닙니다. 콘솔 중심 버전은 그쪽부터 시작하세요.

Grok Voice Think Fast 2.0이란?

Grok Voice Think Fast 2.0은 SpaceXAI의 최신 Speech to Speech API용 모델입니다. 대부분의 사람들이 Grok Voice라고 부르는 그 제품의 정식 명칭이죠. 아직 회사를 xAI로 기억한다면 같은 조직입니다. 2026년 7월 6일 SpaceX에 합병되며 SpaceXAI로 리브랜딩되었습니다. API는 리브랜딩을 따르지 않았기 때문에 아래의 모든 식별자는 여전히 xai입니다. XAI_API_KEY 환경 변수부터 api.x.ai 호스트까지요.

전통적인 보이스 스택은 음성-텍스트, 언어 모델, 텍스트-음성의 세 가지 서비스를 연결합니다. 각 홉은 지연을 더하고 컨텍스트가 유실될 지점을 만듭니다. Think Fast 2.0은 이를 하나의 모델로 통합해 동일한 연결에서 오디오나 텍스트를 입력받아 오디오나 텍스트로 출력합니다.

모듈형 STT-LLM-TTS 파이프라인과 단일 Grok Voice WebSocket 연결 비교 다이어그램.

음성-대-음성 WebSocket 대 3-서비스 파이프라인 아키텍처. 이미지: 필자.

말만 하는 에이전트가 아니라 실제로 행동하는 에이전트에서 중요한 점은 추론과 발화가 병렬로 수행된다는 것입니다. SpaceXAI에 따르면 도구 호출은 에이전트가 첫 문장을 마치기 전에 "보통" 실행을 시작한다고 하며, 이 표현에는 실제 의미가 있습니다.

SpaceXAI가 Artificial Analysis의 벤치마크를 인용한 수치에서, Think Fast 2.0은 Speech to Speech Index에서 1.0의 75.7% 대비 82.9%를 기록했고, 첫 오디오까지의 시간을 1.25초에서 0.70초로 줄였습니다. 공급업체가 일반 벤치마크에 제시한 숫자는 귀하의 콜 플로우에 대한 가설이지, 테스트 계획이 아닙니다.

다음 세 가지 모델 문자열을 보게 될 것입니다: grok-voice-latest, grok-voice-think-fast-2.0, grok-voice-think-fast-1.0. 별칭은 프로토타이핑에는 편리하지만 그 외에는 안정적이지 않습니다.

2026년 8월 4일 테스트 시점에 grok-voice-latest는 여전히 grok-voice-think-fast-1.0로 해석되었고, SpaceXAI의 릴리스 노트에는 다음 날 Think Fast 2.0으로 전환이 예정되어 있었습니다. 이 전환은 모델 변경이자 가격 변경입니다. 1.0이 분당 $0.05였던 데 비해 $0.08로 오르므로, 버전 고정 없이 별칭을 쓰면 코드 한 줄 바꾸지 않아도 비용이 올라갑니다. 실서비스에는 버전 문자열을 고정하세요.

무엇을 만들 것인가

에이전트는 지원 라인에서 자주 묻는 것들을 다룹니다. 주문 조회, 번호 없이 이메일로 찾기, 배송 지시 변경, 취소, 티켓 생성/확인, 사람 상담원으로 연결. 중간에 끼어들기와 연결 끊김도 다룹니다.

각 구성요소의 역할이 달라 개별 테스트를 원할 것이므로 하나의 스크립트가 아닌 작은 파일 여러 개로 나눕니다. 구조는 다음과 같습니다.

  • config.py가 API 키를 로드하고 모델 문자열, 샘플 레이트, 엔드포인트 URL을 보관합니다

  • voice_client.py 가 WebSocket을 래핑하고 과금 추적을 하며 송수신 헬퍼를 제공합니다

  • tools.py가 주문 관련 함수를 정의하고 실제 DB를 대신하는 소규모 인메모리 주문 저장소를 제공합니다

  • assistant.py 가 시스템 프롬프트, 세션 설정, 모든 것을 연결하는 이벤트 루프를 담습니다

  • token_server.py는 단명의 토큰을 발급하는 작은 FastAPI 엔드포인트입니다

  • app_streamlit.py는 동일한 클라이언트를 라이브 브라우저 통화 뒤에 배치합니다. 테스트 섹션 이후에 다시 다룹니다

학습 경로는 터미널에서 진행합니다. 데모는 마이크를 추가합니다.

사전 준비

API 키가 있는 SpaceXAI 계정, 충전된 결제 수단(영구 무료 티어는 없고 신규 계정 프로모션 크레딧은 충분하지 않습니다), 그리고 asyncio와 WebSocket에 대한 기본 이해가 필요합니다. await를 한 줄씩 설명하지 않아도 따라올 수 있는 정도로요.

SpaceXAI의 퀵스타트 예시는 전용 SDK 대신 순수 websockets 패키지를 사용하며, 우리도 동일하게 진행합니다. 문서에는 필요한 Python 버전이 명시되어 있지 않습니다. 저는 3.11에서 테스트했습니다.

API 키는 서버에 보관하세요. 브라우저나 모바일 앱이 Voice API와 직접 통신하는 경우, 아래 보안 섹션에서 다루는 단명의 토큰을 실제 키 대신 발급받아 사용합니다.

프로젝트 설정

아래의 모든 파일은 프로젝트 저장소에 있습니다. 조각을 복사하는 대신 클론하세요:

git clone https://github.com/KhalidAbdelaty/grok-voice-think-fast-2.0.git
cd grok-voice-think-fast-2.0
pip install -r requirements.txt

websockets는 실시간 연결을 담당하고, python-dotenv가 키를 읽습니다. 나머지는 토큰 엔드포인트와 브라우저 데모를 위한 것입니다. 키를 .env에 넣으세요:

XAI_API_KEY=xai-your-key-here

설정의 대부분은 끝났습니다. 핵심은 연결입니다.

Grok Voice 실시간 API 이해하기

Grok Voice는 제품명입니다. 실제로 코드가 상호작용하는 것은 wss://api.x.ai/v1/realtime의 WebSocket 엔드포인트이며, 대화 전체가 단일 소켓을 통한 JSON 이벤트 스트림으로 이루어집니다.

이벤트 라이프사이클

연결은 정해진 흐름을 따릅니다. 연결 직후 서버가 session.created conversation.created를 보내고, 클라이언트가 session.update 로 음성/도구/오디오 포맷을 설정하면 서버가 session.updated로 확인합니다. 이후에는 대화 항목을 만들고 응답을 요청합니다. 실키로 테스트했을 때 문서와 순서가 정확히 일치했습니다.

  • session.update (클라이언트): 음성, 지침, 도구, 오디오 포맷 설정

  • conversation.item.create (클라이언트): 사용자 메시지, 어시스턴트 메시지, 도구 결과 추가

  • response.create (클라이언트): 모델에 발화를 요청합니다. 서버 VAD가 자동으로 이를 전송해 줍니다

  • response.output_audio.deltaresponse.output_audio_transcript.delta (서버): 생성되는 답변을 스트리밍

  • response.done (서버): 턴 종료

헷갈리기 쉬운 점이 둘 있습니다. 위에서 링크한 Speech to Speech 문서 페이지에는 세션 재개 중 conversation.item.created 이벤트가 언급되지만, 표준 이벤트 레퍼런스에는 conversation.item.added만 나옵니다. 제가 돌린 모든 테스트에서도 이것만 도착했으니 이에 맞춰 코드를 짜세요. 또한 대부분의 연결에서 몇 초 후 문서화되지 않은 ping 이벤트를 보게 될 텐데, 에러로 오해하지 않도록 언급해 둡니다.

오디오 포맷과 전송

코덱과 전송 방식은 별개의 선택입니다. 코덱은 audio.input.format audio.output.format에서 설정하며, audio/pcm(Linear16, 기본 24000 Hz), audio/pcmu 또는 audio/pcma (전화용 G.711, 8 kHz), 혹은 audio/opus(24 kHz)을 선택할 수 있습니다. 전송은 바이트를 전선 위로 어떻게 보낼지에 대한 방식입니다.

  • json(기본): 오디오를 input_audio_buffer.append response.output_audio.delta 내부의 base64 텍스트로 전송합니다. 로깅과 디버깅이 쉽습니다

  • binary : WebSocket 바이너리 프레임으로 원시 코덱 바이트를 전송해 base64 오버헤드를 제거합니다. 다만 수신 루프에서 메시지 타입에 따른 분기 처리가 필요합니다

JSON으로 시작하세요. 문서의 모든 예제가 이를 사용하고, 확인이 매우 쉽고, 지원 에이전트 빌드에서 병목은 base64 오버헤드가 아닙니다. 측정 결과가 있을 때만 바이너리로 옮기세요.

OpenAI Realtime API와의 호환성

OpenAI의 Realtime API를 써 본 적이 없다면 건너뛰세요. 그렇지 않다면, Speech to Speech API는 OpenAI Realtime API를 충분히 가깝게 따라가므로 대부분의 클라이언트 코드는 기본 URL과 키만 바꿔도 이식됩니다. 다만 완전한 드롭인 대체는 아닙니다.

여기서는 트랜스크립트가 OpenAI의 delta 대신 conversation.item.input_audio_transcription.updated로 옵니다. 몇몇 OpenAI 이벤트는 지원되지 않고, SpaceXAI만의 확장도 있습니다. 고지 멘트를 스크립트 그대로 재생하는 force_message, 재연결을 위한 resumption, 잘못 발음되는 브랜드명을 TTS 이전에 교정하는 replace 등이 그것입니다.

실시간 보이스 에이전트 구축

프로토콜 얘기는 충분합니다. 이제 이를 사용하는 클라이언트입니다.

세션 연결 및 구성

연결은 베어러 토큰과 모델 쿼리 파라미터로 열립니다. 처음 보내는 메시지가 에이전트 동작 방식을 모두 설정합니다.

import asyncio
import json
import os
import websockets

MODEL = "grok-voice-think-fast-2.0"  # pin the version, not grok-voice-latest

async def connect():
    url = f"wss://api.x.ai/v1/realtime?model={MODEL}"
    ws = await websockets.connect(
        url, additional_headers={"Authorization": f"Bearer {os.environ['XAI_API_KEY']}"}
    )
    await ws.send(json.dumps({
        "type": "session.update",
        "session": {
            "voice": "eve",
            "instructions": SYSTEM_PROMPT,
            "turn_detection": {"type": "server_vad"},
            "tools": ORDER_TOOLS,
            "resumption": {"enabled": True},
        }
    }))

return ws

instructions는 시스템 프롬프트입니다. 이 모델은 짧은 프롬프트를 선호합니다. SpaceXAI의 마이그레이션 노트는 GPT 시대 보이스 모델용으로 작성된 프롬프트를 그대로 옮기기보다 단순화하라고 합니다. 제 프롬프트는 답변을 짧게, 한 번에 한 질문만, 그리고 쓰기 작업 전에 다시 읽어 주라고 지시합니다. 음성으로 확인하는 것은 UX 개선일 뿐 보안 통제가 아닙니다. 실제 권한 검증은 애플리케이션이 쓰기 작업에서 강제해야 합니다.

의외였던 점 하나: 인식되지 않는 모델 문자열을 사용해도 연결 시점에 에러를 내지 않고 조용히 grok-voice-think-fast-1.0으로 폴백합니다. 오타 때문에 유료 요청을 다운그레이드하면서도 말해주지 않는 기본값은 이상합니다. 시작 시 한 번 session.createdsession.model 필드를 로깅해 요청한 모델이 맞는지 확인하세요.

WebSocket 연결 후 session.created 이벤트를 출력하는 터미널

연결 직후 session.created를 보여주는 터미널 출력. 이미지: 필자.

사용자 오디오 스트리밍

turn_detection.typeserver_vad로 설정하면 오디오만 계속 추가하면 됩니다. 서버가 발신자의 발화 종료 시점을 판단해 자동으로 응답을 트리거합니다. 이를 null로 두면 발화 종료 판단을 직접 맡아 버퍼를 명시적으로 커밋해야 합니다.

async def send_audio_chunk(ws, pcm_bytes: bytes):
    await ws.send(json.dumps({
        "type": "input_audio_buffer.append",
        "audio": base64.b64encode(pcm_bytes).decode(),
    }))

서버 VAD에는 세 가지 조정값이 있으며, 이를 잘못 설정하면 로그에 에러가 없는데도 에이전트가 망가진 것처럼 느껴지는 가장 흔한 원인이 됩니다. 기본적으로 session.updated의 에코에 이 값들이 나타나지 않으므로, 가정하지 말고 문서와 대조하세요.

  • threshold(0.1~0.9, 기본 0.85): 음성이 인식될 최소 음량. 소음이 큰 환경에서는 올리고, 조용한 화자가 누락되면 낮추세요

  • silence_duration_ms: 발신자가 얼마나 오래 조용해야 서버가 턴을 끝내는지. 너무 짧으면 생각 중인 화자의 말을 자르고, 너무 길면 굼뜹니다

  • prefix_padding_ms(기본 333): 음성 감지 직전의 오디오를 소량 보존해 첫 음절이 잘리지 않게 합니다

발신자가 생각하며 멈출 때 자꾸 끊긴다면 먼저 silence_duration_ms 를 조정하세요. 저는 다른 두 가지보다 이 값을 먼저 손댑니다.

응답 수신 및 재생

오디오는 response.output_audio.delta로 작은 조각 단위로 옵니다. 스트리밍의 목적은 response.done을 기다리지 않고 도착 즉시 각 조각을 재생하는 것입니다.

async def play_response(ws):
    async for message in ws:
        event = json.loads(message)
        if event["type"] == "response.output_audio.delta":
            chunk = base64.b64decode(event["delta"])
            speaker.write(chunk)  # your playback call goes here
        elif event["type"] == "response.output_audio_transcript.delta":
            print(event["delta"], end="", flush=True)

프로덕션에서도 트랜스크립트를 보관하세요. 발신자가 에이전트가 "이상한 말을 했다"고 할 때 가장 저렴한 디버깅 도구입니다.

보이스 에이전트에 도구 추가

말만 하는 보이스 에이전트는 마이크가 달린 챗봇일 뿐입니다.

주문 도구 만들기

각 도구는 JSON 스키마와 우리 쪽의 평범한 Python 함수 한 개로 구성됩니다. 모델은 DB에 직접 접근하지 않고, 오직 우리 함수가 반환한 결과만 봅니다.

ORDER_TOOLS = [
    {
        "type": "function",
        "name": "check_order_status",
        "description": "Look up the status, ETA, and delivery instructions for an order.",
        "parameters": {
            "type": "object",
            "properties": {
                "order_number": {"type": "string", "description": "e.g. ORD-1042"},
            },
            "required": ["order_number"],
        },
    },
    # find_orders, update_delivery_instructions, cancel_order,
    # create_support_ticket, check_ticket_status and transfer_to_human
    # all follow the same shape
]

check_order_status 같은 조회 작업은 타임아웃 시 재시도해도 안전합니다. 쓰기 작업은 그렇지 않습니다. update_delivery_instructions를 애매한 타임아웃 후 재시도하면 동일 변경이 두 번 적용될 수 있습니다. 프롬프트에서 확인을 요구해도 이를 막지 못하므로, 쓰기에는 멱등 키나 중복 확인을 두세요.

거절 로직도 함수에 넣어두세요. cancel_order는 이미 배송된 주문의 취소를 수행하는 대신 사유와 대안을 반환합니다. "배송된 주문은 취소하지 말라"는 프롬프트는 제안에 불과하지만, 거부하는 함수는 그렇지 않기 때문입니다.

도구 호출 루프 처리

네 단계이며, 순서가 보기보다 중요합니다. 모델이 response.function_call_arguments.done를 보내면, 여러분의 코드가 함수를 실행하고, 결과를 function_call_output 항목으로 되돌려 보낸 다음, 그제야 모델에 계속하라고 요청합니다.

async def handle_tool_call(ws, event):
    args = json.loads(event["arguments"])
    result = execute(event["name"], args)  # never raises; errors come back as {"error": ...}
    await ws.send(json.dumps({
        "type": "conversation.item.create",
        "item": {
            "type": "function_call_output",
            "call_id": event["call_id"],
            "output": json.dumps(result),
        },
    }))

모델이 하나의 요청에 여러 도구가 필요하면, 어떤 오디오도 재생되기 전에 여러 개의 function_call_arguments.done 이벤트를 발행합니다. 이를 모두 처리하고 모든 결과를 보낸 뒤에야 response.create를 한 번 보내세요. 너무 일찍 보내면 아직 처리 중인 호출의 컨텍스트 없이 모델이 답합니다.

SpaceXAI 문서에 있는 함정이고 저도 처음에 겪었습니다. 도구 결과를 보내자마자 response.create를 전송하면 에이전트가 아직 서두 문장을 재생 중인 것과 겹칠 수 있습니다. 한 번은 "주문 ORD-1042의 상태를 바로 확인하겠습니다"라고 말하다가 문장 중간에 도구를 호출했는데, 즉시 응답을 보내면 자신의 서두를 덮어버리게 됩니다.

현재 턴의 오디오가 끝나기를 기다리고, 그 사이에 짧은 "생각 중" 상태를 보여주세요.

function_call_arguments.done에서 핸들러 실행, function_call_output 전송, 그 후 response.create로 이어지는 흐름.

응답 계속 이전의 도구 호출 흐름. 이미지: 필자.

중단과 대화 상태 관리

두 가지 문제가 있습니다. 에이전트의 응답 중간에 발신자가 말을 끼어드는 경우, 그리고 WebSocket이 끊겨 다시 이어야 하는 경우입니다.

자연스러운 끼어들기 지원

server_vad가 켜져 있으면 바지인은 서버 측에서 자동입니다. 발신자의 새 발화를 감지하는 즉시 input_audio_buffer.speech_started를 신호로 보내고, 이전 응답 생성을 중단합니다. 클라이언트의 역할은 큐에 대기 중인 오디오를 비워 에이전트가 아무도 원치 않는 문장을 마저 하지 않도록 하는 것입니다.

if event["type"] == "input_audio_buffer.speech_started":
    playback_queue.clear()

수동(비-VAD) 세션에서는 response.cancel이 요청 시 동일한 역할을 합니다. 또한

conversation.item.truncate 로 어시스턴트 항목을 실제로 들린 부분까지만 잘라낼 수 있습니다. 문서에 존재는 확인되지만 라이브 바지인 중 언제 발행해야 하는지는 명시돼 있지 않으니, 타이밍을 직접 테스트하세요.

중간에 배송 지시 변경으로 테스트했습니다. 요청을 시작하고, 에이전트의 확인 도중 다른 주소로 끼어듭니다. 중요한 것은 오디오가 멈췄는지가 아니라 에이전트가 조용히 이전 지시를 마무리하는 대신 수정된 지시를 실제로 적용했는지입니다. 침묵이 아니라 주문 레코드로 검증하세요. 마지막의 브라우저 데모에서 이를 직접 들을 수 있습니다.

끊긴 세션 재개

세션 재개는 옵트인 기능이며, 메모리는 아닙니다. session.update에서 resumption.enabled: true로 설정하고, conversation.created 이벤트의 ID를 잡아두세요. 소켓이 끊기면 URL에 ?conversation_id=<id>를 붙여 재연결하고 새 연결에서도 다시 옵트인합니다.

async def reconnect(conversation_id):
    url = f"wss://api.x.ai/v1/realtime?model={MODEL}&conversation_id={conversation_id}"
    ws = await websockets.connect(url, additional_headers=auth_header)
    await ws.send(json.dumps({"type": "session.update", "session": {"resumption": {"enabled": True}}}))
    return ws

캐시된 턴, 트랜스크립트, 도구 호출 및 결과가 다음 질문 전에 재생되며, 비활성 30분 후 캐시는 사라집니다. 주문을 묻고 연결을 끊은 뒤, 반복 없이 후속 질문을 위해 재연결해 테스트했는데, 에이전트가 ETA를 정확히 이어받았습니다.

문서화되지 않은 함정 하나: 재생(replay)이 즉시 도착하지 않으므로, 소켓이 열리자마자 보낸 질문이 이를 앞질러 이전 턴을 기억하지 못한 상태로 돌아올 수 있습니다. 이를 재개의 문제로 탓하기 전에 잠시 여유를 주세요.

연결 끊김과 conversation_id로 재연결, 그리고 올바른 후속 답변이 있는 터미널 로그.

재개된 세션의 터미널 로그. 이미지: 필자.

이 기능을 자체 DB에 주문 상태를 저장하는 것의 대체로 쓰지 마세요. 캐시가 만료되거나 발신자가 내일 다시 전화하면 컨텍스트는 0부터 시작하며, 이는 설계된 동작입니다.

에이전트 보안 및 모니터링

영구 API 키를 브라우저나 모바일 코드에 절대 넣지 마세요. 클라이언트가 서버를 경유하지 않고 직접 연결한다면, 단명의 토큰을 발급하세요:

from fastapi import FastAPI
import httpx, os

app = FastAPI()

@app.post("/session")
async def create_session():
    async with httpx.AsyncClient() as client:
        response = await client.post(
            "https://api.x.ai/v1/realtime/client_secrets",
            headers={"Authorization": f"Bearer {os.environ['XAI_API_KEY']}"},
            json={"expires_after": {"seconds": 300}},
        )
    return response.json()  # {"value": "xai-realtime-client-secret-...", "expires_at": ...}

브라우저는 WebSocket 핸드셰이크 시 커스텀 Authorization 헤더를 설정할 수 없으므로, 토큰을 sec-websocket-protocol 헤더를 통해 전달합니다. xai-client-secret. 접두사를 붙여서요.

서버가 브라우저가 WebSocket을 열 수 있도록 단명의 클라이언트 시크릿을 발급하는 다이어그램.

서버가 토큰을 발급, 브라우저가 통화에 참여. 이미지: 필자.

과금은 두 개의 미터로 이뤄집니다. 송수신 오디오는 앞서 언급한 분당 $0.08(시간당 $4.80)이며, 오디오가 아니고 function_call_output도 아닌 모든 conversation.item.create가 건당 $0.004입니다. response.create는 과금되지 않습니다. 각 response.done에는 usage 객체가 포함되며, 제 테스트에서는 output_audio_seconds와 별도의 billable_audio_seconds가 보고되었습니다. 추정치가 아니라 이 값으로 청구를 계산하세요.

Speech to Speech API의 문서화된 제한은 팀당 동시 세션 10개, 세션당 120분 캡이며, 둘 다 us-east-1에 적용됩니다. 수용력 계획을 Voice Agent API의 수치로 세우지 마세요. 서로 다릅니다.

프라이버시에 관해서는 정확히 표현하세요. SpaceXAI의 보안 FAQ에 따르면 API 요청과 응답은 악용 모니터링을 위해 30일간 암호화되어 보관되며, 허가 없이 학습에 사용되지 않습니다. 팀은 Zero Data Retention을 켤 수 있지만, ZDR은 보이스 에이전트의 대화 이력을 저장하지 않으므로 재개 기능과는 함께 사용할 수 없습니다.

통화 녹음 또는 AI 처리 사실을 고지한다면, 앞서 언급한 force_message 확장을 사용하세요. 해당 문구가 모델의 의역 없이 그대로 재생됩니다.

보이스 에이전트 테스트

WebSocket 핸드셰이크 200 상태는 에이전트가 올바르게 동작했는지에 대해 아무것도 말해주지 않습니다. 연결이 아니라 결과를 테스트하세요.

  • 정상 주문 조회: 응답 도착 여부가 아니라 발화 내용이 레코드와 일치하는지 확인
  • 중단된 응답: 재생이 멈추고 에이전트가 새 요청을 다루는지 확인
  • 확인이 필요한 배송 업데이트: 주문 레코드와 대조
  • 거절 사례: 배송된 주문 취소처럼 규칙을 설명해야지 사과로 끝내지 않도록
  • 알 수 없는 주문 번호: 상태를 지어내지 말고 모른다고 말하는지 확인
  • 에러를 반환하는 도구: 에이전트가 멈추지 않고 이를 말하는지 확인
  • 재연결과 재개: 앞서 겪은 재생 윈도우 포함
  • 소음 많은 오디오, 빠른 발화, 숫자와 주소를 철자대로 말하는 발신자

이 글을 쓰며 대부분을 실키로 돌려봤습니다. 흥미로운 실패는 에러가 아니라 행태였습니다. 위의 재개 타이밍, 그리고 범위를 벗어난 VAD 임계값이 거부되지 않고 받아들여지는 문제 등입니다. 해피 패스만 테스트하면 조용히 망가진 채로 배포될 수 있는 종류죠. 다국어 테스트도 추가하고, 언어 명명법의 주의점은 FAQ를 참고하세요.

이 중 두 가지는 타이핑만으로는 테스트할 수 없습니다. app_streamlit.py는 브라우저에서 라이브 통화를 제공하는 Streamlit 페이지입니다. 마이크가 동일한 WebSocket으로 WebRTC를 통해 스트리밍되고, 에이전트의 음성이 되돌아오며, 소켓은 내내 열려 있습니다.

streamlit run app_streamlit.py
문장 중간에 에이전트를 끼어들기. 영상: 필자.

에이전트가 말하는 도중에 말을 시작하면 speech_started가 도착하고 페이지가 큐에 쌓인 오디오를 비우므로, 에이전트는 멈춥니다. 중단 섹션의 핸드셰이크가 실제로 동작하는 모습입니다.

트랜스크립트보다 주문 레코드를 보세요. 에이전트는 배송 변경 사항을 다시 읽어주고 완료됐다고 말합니다. 레코드는 바뀌었을 수도 있고 아닐 수도 있습니다. 헤드폰을 사용하세요. 스피커를 통해 틀면 에이전트가 자기 목소리를 듣고 이를 바지인으로 간주해 자신의 문장을 끊습니다. 스피커폰 발신자가 유발하는 상황의 미리 보기입니다.

Grok Voice Think Fast 2.0의 한계와 배포 고려사항

다음 상황을 대비하세요. 턴 도중 실패하는 도구 호출, 행동의 성공 여부보다 더 자신감 있게 말해버리는 확인 멘트, 조용한 사무실에 맞춘 VAD가 전화선에서 무력해지는 문제, 문장 중간에 마음을 바꾸는 발신자.

결제, 계정 접근, 혼란스럽거나 화가 난 듯한 발신자는 사람에게 라우팅하세요. 이를 위해 모델에 transfer_to_human 도구를 제공하세요. 없으면 사과로 땜질하고 상향 조치를 하지 않을 것입니다.

모듈형 음성-텍스트, 언어 모델, 텍스트-음성 스택도 여전히 쓸모가 있습니다. 각 구성요소를 개별 제어하고, 어떤 추론도 시작되기 전 결정적 트랜스크립트를 얻을 수 있습니다. 통합 작업이 더 든다는 대가를 치르고요. 그리고 워크로드에 라이브 상호 대화가 전혀 필요 없다면, 텍스트 챗봇이나 배치 전사 작업이 실시간 파이프라인보다 단순하고 저렴합니다.

결론

이 글의 테스트 전반에서 grok-voice-think-fast-2.0 은 대체로 문서가 말하는 대로 동작했습니다. 이벤트 라이프사이클이 유지됐고, 끊긴 연결은 이전 턴을 보존한 채 복구됐으며, 모델은 오프닝 라인을 말하는 중에 도구를 호출했습니다.

conversation.item.added의 명칭 불일치를 제외하면, 나머지 작업의 상당 부분이 소켓의 우리 쪽에 있다는 점을 강조하고 싶습니다. 재생 큐, 침묵 유지 시점, 아직 다음 질문을 하지 말아야 할 때 등입니다.

오늘 프로젝트를 시작한다면, 별칭 대신 버전이 명시된 모델 문자열, server_vad(조정값 셋 중 silence_duration_ms를 우선 튜닝), 측정된 이유가 생길 때까지 JSON 전송 유지, 최초 session.update에서 resumption.enabled 켜기, 시작 시 session.model 로깅을 기본값으로 두겠습니다.

어떤 보이스 에이전트에도 적용할 습관: 발화 확인이 아니라 레코드에 대한 쓰기 결과를 검증하기, 거절 로직을 프롬프트가 아닌 도구에 두기, 다음 response.create 전에는 재생이 비워지도록 하기, 실제 악센트/실제 소음/실패하는 도구를 현실적으로 테스트하기.

명백한 확장은 전화( SpaceXAI가 SIP 지원을 문서화), 단명의 토큰을 쓰는 브라우저 클라이언트, 실제 CRM으로의 MCP 연결, 제대로 된 다국어 버전입니다. 그리고 앞서 비교한 세션 제한이 있는 Voice Agent API가 요구사항에 더 가깝다면, 우리 Grok Voice Agent API 튜토리얼을 참고하세요.

FAQs

grok-voice-latest를 운영 환경에서 써도 안전한가요?

엄밀히 말하면 아닙니다. 위 버전 관리 섹션에서 언급했듯, 전환 시점은 SpaceXAI가 정하며 비용도 함께 바뀝니다. 운영 환경에서는 grok-voice-think-fast-2.0을 고정하고, 예기치 않은 전환이 실고객 통화에 영향을 주지 않는 로컬 실험에만 별칭을 사용하세요.

Grok Voice Think Fast 2.0은 영어 외 언어도 지원하나요?

네, 자동 감지와 함께 20개 이상이 문서화되어 있습니다. language_hint로 특정 언어에 치우치도록 설정할 수도 있습니다. 스페인어와 포르투갈어는 es-MXpt-BR처럼 지역 코드를 필요로 합니다. espt만으로는 허용되지 않습니다. 인식되지 않는 코드는 조용히 무시되고 자동 감지로 폴백하므로, 오타가 비용을 유발하지는 않지만 아무 효과도 없습니다.

음성을 바꿀 수 있나요? 몇 가지가 있나요?

eve가 문서와 본문 예시에서 사용한 음성입니다. 이외에 ara, rex, sal, leo, 그리고 커스텀 음성 ID도 사용할 수 있습니다. 현재 목록은 GET /v1/tts/voices에서 확인하세요. 속도가 거슬린다면 audio.output.speed에 0.7~1.5를 설정할 수 있습니다.

에이전트가 지금보다 더 빨리 답하게 할 수 있나요?

워크스루에서는 기본값이 대체로 적절해 건너뛴 reasoning.effort를 시도해 보세요. 기본은 "high"이며, 턴당 계획량을 줄이는 "none"도 있습니다. 단순 조회 플로우에는 괜찮습니다. 도구 간 선택이 필요한 경우에는 조정하지 않겠습니다.

이걸 만들려면 SpaceXAI 공식 SDK가 꼭 필요한가요?

아니요. 사전 준비 섹션에서 말했듯, 공식 SDK는 필요하지 않습니다. 순수 websockets 패키지나 OpenAI 호환 클라이언트를 api.x.ai 기본 URL에 맞춰 사용하면 됩니다. 유의할 점 하나: 공식 xai-sdk는 이 WebSocket과 통신하지 않는 별도의 gRPC 클라이언트입니다. 실시간 메서드는 거기에 없습니다. 다른 출발점을 원한다면 xai-cookbook에 iOS, 웹, WebRTC, 텔레포니 샘플이 있습니다.

주제

DataCamp와 함께 배우세요

courses

인공 지능 이해하기

2
411.5K
머신 러닝, 딥러닝, NLP, 생성형 AI 등 인공 지능의 기본 개념을 학습합니다.
자세히 보기Right Arrow
강좌 시작
더 보기Right Arrow