메인 콘텐츠로 건너뛰기

Voice SDK

VoiceSDK는 자동 채팅 시스템 통합과 음성 활성화 애플리케이션 구축을 위한 React hooks를 포함한 고급 음성 대화 기능을 제공합니다. AI 에이전트와의 자연스러운 음성 상호작용을 만들고, 오디오 시각화, 전사 및 대화 관리를 완료하세요. 이 기사에서는 빠르게 시작하고 작동 방식을 이해하는 데 도움이 되는 빠른 시작 예시를 제공합니다. 나머지 기사에서는 사용할 수 있는 다양한 메서드, 용도 및 모범 사례를 자세히 설명합니다.

설치​

npm install @odin-ai-staging/sdk @elevenlabs/react

빠른 시작​

기본 음성 대화​

이 예시에서는 TypeScript와 React 애플리케이션 모두에서 VoiceSDK를 사용하여 실시간 음성 대화를 구현하는 방법을 알아봅니다. AI 음성 에이전트가 대화를 처리할 특정 agentId를 포함한 API 자격 증명으로 VoiceSDK를 초기화하는 것으로 시작합니다.

import { VoiceSDK } from '@odin-ai-staging/sdk';

// SDK 초기화
const voiceSDK = new VoiceSDK({
baseUrl: 'https://your-api-endpoint.com/',
projectId: 'your-project-id',
apiKey: 'your-api-key',
apiSecret: 'your-api-secret',
agentId: 'your-agent-id'
});

// 음성 대화 시작
async function startVoiceChat() {
const sessionId = await voiceSDK.startVoiceConversation({
saveToChat: true,
callbacks: {
onConnect: () => console.log('음성 연결됨'),
onMessage: (message) => console.log('음성 메시지:', message),
onDisconnect: () => console.log('음성 연결 해제됨'),
onTranscription: (text, isFinal) => {
if (isFinal) console.log('사용자 말:', text);
}
}
});

console.log('음성 세션 시작됨:', sessionId);
}

React Hook 사용​

import { useVoiceConversation } from '@odin-ai-staging/sdk';

function VoiceChat() {
const {
status,
startSession,
endSession,
setVolume,
getInputByteFrequencyData,
conversationState
} = useVoiceConversation({
sdkConfig: {
baseUrl: 'https://your-api-endpoint.com/',
projectId: 'your-project-id',
agentId: 'your-agent-id'
},
callbacks: {
onConnect: () => console.log('연결됨!'),
onMessage: (message) => console.log('메시지:', message)
}
});

return (
<div>
<button
onClick={() => startSession()}
disabled={status === 'connected'}
>
음성 채팅 시작
</button>

<button
onClick={() => endSession()}
disabled={status !== 'connected'}
>
채팅 종료
</button>

<div>상태: {status}</div>
<div>볼륨: {conversationState.volume}</div>
</div>
);
}

구성​

VoiceSDKConfig 인터페이스​

interface VoiceSDKConfig extends BaseClientConfig {
agentId?: string; // 대화의 기본 에이전트 ID
defaultVoiceSettings?: VoiceSettings; // 기본 음성 설정
}

VoiceSettings​

interface VoiceSettings {
stability?: number; // 음성 안정성 (0.0 ~ 1.0)
similarityBoost?: number; // 음성 유사도 부스트 (0.0 ~ 1.0)
style?: number; // 음성 스타일 (0.0 ~ 1.0)
useSpeakerBoost?: boolean; // 스피커 부스트 활성화
}

설정 예시:

const voiceSDK = new VoiceSDK({
baseUrl: 'https://api.example.com/',
projectId: 'proj_123',
apiKey: 'your-api-key',
apiSecret: 'your-api-secret',
agentId: 'agent_456',
defaultVoiceSettings: {
stability: 0.8,
similarityBoost: 0.7,
style: 0.3,
useSpeakerBoost: true
}
});

핵심 기능​

음성 대화 세션​

VoiceSDK는 자동 채팅 통합과 함께 음성 대화 세션을 관리합니다:

interface VoiceConversationSession {
id: string; // 세션 식별자
chatId?: string; // 연결된 채팅 ID
startTime: number; // 세션 시작 타임스탬프
endTime?: number; // 세션 종료 타임스탬프
messages: VoiceMessage[]; // 세션의 음성 메시지
metadata?: {
agentId?: string;
voiceSettings?: VoiceSettings;
totalDuration?: number;
userInfo?: { name: string; id: string };
};
}

음성 메시지​

interface VoiceMessage {
id: string; // 메시지 ID
type: 'user_speech' | 'ai_speech' | 'system';
text: string; // 전사/생성된 텍스트
audioUrl?: string; // 오디오 파일 URL
timestamp: number; // 메시지 타임스탬프
duration?: number; // 오디오 지속 시간(초)
voiceSettings?: VoiceSettings; // 사용된 음성 설정
saved?: boolean; // 데이터베이스에 저장되었는지 여부
}

세션 관리​

startVoiceConversation(options?)​

새 음성 대화 세션을 시작합니다.

async startVoiceConversation(
options?: StartVoiceConversationOptions
): Promise<string>

StartVoiceConversationOptions:

interface StartVoiceConversationOptions {
callbacks?: VoiceConversationCallbacks;
saveToChat?: boolean; // 대화 기록에 자동 저장
existingChatId?: string; // 기존 채팅 계속
agentId?: string; // 기본 에이전트 재정의
voiceSettings?: VoiceSettings; // 사용자 지정 음성 설정
userInfo?: { name: string; id: string };
}

예시:

const sessionId = await voiceSDK.startVoiceConversation({
saveToChat: true,
existingChatId: 'chat_123',
voiceSettings: {
stability: 0.9,
similarityBoost: 0.8
},
userInfo: {
name: '홍길동',
id: 'user_456'
},
callbacks: {
onConnect: () => console.log('음성 대화 시작됨'),
onMessage: (message) => handleVoiceMessage(message),
onTranscription: (text, isFinal) => {
if (isFinal) displayTranscription(text);
},
onConversationSaved: (chatId, messageId) => {
console.log(`대화가 채팅 ${chatId}에 저장됨`);
}
}
});

endVoiceSession(sessionId, reason?)​

음성 대화 세션을 종료합니다.

getVoiceState(sessionId)​

현재 음성 대화 상태를 가져옵니다.

const state = voiceSDK.getVoiceState(sessionId);
if (state) {
console.log('연결 상태:', state.connectionStatus);
console.log('말하고 있음:', state.isSpeaking);
console.log('볼륨:', state.volume);
}

React 통합​

useVoiceConversation Hook​

useVoiceConversation hook은 상태 관리가 포함된 React 통합을 제공합니다:

import React, { useState } from 'react';
import { useVoiceConversation } from '@odin-ai-staging/sdk';

function VoiceConversationComponent() {
const [messages, setMessages] = useState<string[]>([]);
const [isRecording, setIsRecording] = useState(false);

const {
status,
isSpeaking,
startSession,
endSession,
setVolume,
conversationState,
currentSessionId,
getInputByteFrequencyData
} = useVoiceConversation({
sdkConfig: {
baseUrl: process.env.REACT_APP_API_BASE_URL,
projectId: process.env.REACT_APP_PROJECT_ID,
agentId: process.env.REACT_APP_AGENT_ID
},
callbacks: {
onConnect: () => {
console.log('음성 채팅에 연결됨');
setIsRecording(true);
},
onDisconnect: () => {
console.log('음성 채팅 연결 해제됨');
setIsRecording(false);
},
onTranscription: (text, isFinal) => {
if (isFinal) {
setMessages(prev => [...prev, `사용자: ${text}`]);
}
},
onMessage: (message) => {
if (message.type === 'ai_speech') {
setMessages(prev => [...prev, `AI: ${message.text}`]);
}
},
onError: (error) => {
console.error('음성 오류:', error);
setIsRecording(false);
}
}
});

const handleStartConversation = async () => {
try {
await startSession({
saveToChat: true,
voiceSettings: {
stability: 0.8,
similarityBoost: 0.7
}
});
} catch (error) {
console.error('대화 시작 실패:', error);
}
};

return (
<div className="voice-conversation">
<div className="controls">
<button
onClick={handleStartConversation}
disabled={status === 'connected'}
>
음성 채팅 시작
</button>

<button
onClick={() => endSession()}
disabled={status !== 'connected'}
>
음성 채팅 종료
</button>
</div>

<div className="status">
<div>상태: {status}</div>
<div>말하기: {isSpeaking ? '예' : '아니오'}</div>
<div>녹음: {isRecording ? '예' : '아니오'}</div>
<div>볼륨: {conversationState.volume}</div>
</div>

<div className="volume-control">
<label>볼륨:</label>
<input
type="range"
min="0"
max="100"
value={conversationState.volume}
onChange={(e) => setVolume({ volume: parseInt(e.target.value) })}
/>
</div>

<div className="messages">
{messages.map((message, index) => (
<div key={index} className="message">
{message}
</div>
))}
</div>

{currentSessionId && (
<AudioVisualizer
getInputData={getInputByteFrequencyData}
isActive={status === 'connected'}
/>
)}
</div>
);
}

음성 제어​

볼륨 제어​

// 볼륨 설정 (0-100)
await voiceSDK.setVolume(sessionId, 75);

마이크 제어​

// 마이크 음소거/음소거 해제
await voiceSDK.setMicrophoneMuted(sessionId, true); // 음소거
await voiceSDK.setMicrophoneMuted(sessionId, false); // 음소거 해제

음성 설정 업데이트​

// 대화 중 음성 설정 업데이트
await voiceSDK.updateVoiceSettings(sessionId, {
stability: 0.9,
similarityBoost: 0.8,
style: 0.4
});

오디오 시각화​

실시간 오디오 데이터​

// 시각화를 위한 오디오 주파수 데이터 가져오기
const audioData = voiceSDK.getAudioFrequencyData(sessionId);

if (audioData) {
const inputData = audioData.input; // 사용자 오디오 입력
const outputData = audioData.output; // AI 오디오 출력

// 오디오 시각화에 사용
renderAudioVisualization(inputData, outputData);
}

오디오 시각화 컴포넌트​

import React, { useRef, useEffect } from 'react';

interface AudioVisualizerProps {
getInputData: () => Uint8Array | null;
isActive: boolean;
}

function AudioVisualizer({ getInputData, isActive }: AudioVisualizerProps) {
const canvasRef = useRef<HTMLCanvasElement>(null);

useEffect(() => {
if (!isActive) return;

const canvas = canvasRef.current;
if (!canvas) return;

const ctx = canvas.getContext('2d');
if (!ctx) return;

const animate = () => {
const data = getInputData();

if (data) {
ctx.clearRect(0, 0, canvas.width, canvas.height);

const barWidth = canvas.width / data.length;

for (let i = 0; i < data.length; i++) {
const barHeight = (data[i] / 255) * canvas.height;

ctx.fillStyle = `hsl(${i * 2}, 100%, 50%)`;
ctx.fillRect(
i * barWidth,
canvas.height - barHeight,
barWidth - 1,
barHeight
);
}
}

requestAnimationFrame(animate);
};

animate();
}, [isActive, getInputData]);

return (
<canvas
ref={canvasRef}
width={400}
height={100}
className="audio-visualizer"
/>
);
}

채팅 통합​

자동 채팅 저장​

음성 대화는 채팅 시스템에 자동으로 저장될 수 있습니다:

const sessionId = await voiceSDK.startVoiceConversation({
saveToChat: true, // 자동 저장 활성화
existingChatId: 'chat_123', // 선택 사항: 기존 채팅 계속
callbacks: {
onConversationSaved: (chatId, messageId) => {
console.log(`음성 대화가 채팅 ${chatId}에 저장됨`);
refreshChatHistory(chatId);
}
}
});

오류 처리​

try {
const sessionId = await voiceSDK.startVoiceConversation({
callbacks: {
onError: (error) => {
console.error('음성 대화 오류:', error);

if (error.message.includes('microphone')) {
showMicrophonePermissionDialog();
} else if (error.message.includes('network')) {
showNetworkErrorMessage();
}
},
onDisconnect: (details) => {
console.log('연결 해제됨:', details?.reason);

if (details?.reason === 'user_ended') {
showConversationSummary();
} else if (details?.reason === 'error') {
showReconnectOption();
}
}
}
});
} catch (error) {
console.error('음성 대화 시작 실패:', error);
}

예시​

음성 활성화 고객 지원​

import { VoiceSDK, ChatSDK } from '@odin-ai-staging/sdk';

class VoiceCustomerSupport {
private voiceSDK: VoiceSDK;
private chatSDK: ChatSDK;
private activeSession?: string;

constructor() {
const config = {
baseUrl: process.env.API_BASE_URL,
projectId: process.env.PROJECT_ID,
apiKey: process.env.API_KEY,
apiSecret: process.env.API_SECRET,
};

this.voiceSDK = new VoiceSDK(config);
this.chatSDK = new ChatSDK(config);
}

async startSupportSession(customerId: string, issueType: string) {
try {
const chat = await this.chatSDK.createChat(
`음성 지원 - ${issueType}`,
[]
);

this.activeSession = await this.voiceSDK.startVoiceConversation({
saveToChat: true,
existingChatId: chat.chat_id,
agentId: this.getAgentForIssueType(issueType),
userInfo: {
name: `고객 ${customerId}`,
id: customerId
},
callbacks: {
onConnect: () => {
console.log('지원 세션 시작됨');
},
onTranscription: (text, isFinal) => {
if (isFinal) {
console.log('고객 말:', text.substring(0, 100));
}
},
onMessage: (message) => {
if (message.type === 'ai_speech') {
console.log('에이전트 응답:', message.text.length);
}
},
onConversationSaved: (chatId, messageId) => {
console.log(`지원 대화가 채팅 ${chatId}에 저장됨`);
}
}
});

return {
sessionId: this.activeSession,
chatId: chat.chat_id
};
} catch (error) {
console.error('지원 세션 시작 실패:', error);
throw error;
}
}

async endSupportSession() {
if (this.activeSession) {
await this.voiceSDK.endVoiceSession(this.activeSession);
this.activeSession = undefined;
}
}

private getAgentForIssueType(issueType: string): string {
const agentMap: Record<string, string> = {
'technical': 'agent_technical_support',
'billing': 'agent_billing_support',
'general': 'agent_general_support'
};
return agentMap[issueType] || agentMap['general'];
}
}

모범 사례​

오류 처리 및 대체​

const voiceSupport = {
async startWithFallback() {
try {
return await this.voiceSDK.startVoiceConversation(options);
} catch (error) {
console.warn('음성 실패, 텍스트 채팅으로 대체:', error);
return await this.chatSDK.createChat('지원 채팅 (텍스트)');
}
}
};

리소스 관리​

class VoiceManager {
private activeSessions = new Set<string>();

async startSession(options: any) {
const sessionId = await this.voiceSDK.startVoiceConversation(options);
this.activeSessions.add(sessionId);
return sessionId;
}

async cleanup() {
for (const sessionId of this.activeSessions) {
try {
await this.voiceSDK.endVoiceSession(sessionId);
} catch (error) {
console.warn('세션 종료 실패:', sessionId, error);
}
}
this.activeSessions.clear();
}
}

접근성​

function VoiceAccessibleChat() {
const [transcript, setTranscript] = useState('');

const { startSession } = useVoiceConversation({
callbacks: {
onTranscription: (text, isFinal) => {
setTranscript(text);
if (isFinal) {
announceToScreenReader(`말씀하신 내용: ${text}`);
}
}
}
});

return (
<div>
<button
aria-label="음성 대화 시작"
onClick={startSession}
>
🎤 음성 채팅 시작
</button>

<div
aria-live="polite"
aria-label="음성 전사"
>
{transcript}
</div>
</div>
);
}