대화형 AI는 사람들이 인공 지능과 상호작용하는 방식을 혁신하고 있습니다. 정교하게 작성된 텍스트 프롬프트 대신 사용자는 AI 에이전트와 자연스럽고 실시간 음성 대화를 나눌 수 있습니다. 이는 직관적이고 효율적인 상호작용을 위한 흥미로운 기회를 열어줍니다.
많은 개발자들이 텍스트 기반 에이전트를 위해 맞춤형 LLM 워크플로우를 구축하는 데 상당한 시간을 투자해 왔습니다. Agora의 대화형 AI 엔진은 기존 워크플로우를 Agora 채널에 연결하여 현재 AI 인프라를 포기하지 않고 실시간 음성 대화를 가능하게 합니다.
이 가이드에서는 사용자와 Agora의 대화형 AI 사이의 연결을 처리하는 Express를 구축하는 방법을 단계별로 안내합니다. 완료 시에는 AI 에이전트와의 실시간 오디오 대화를 지원하는 완전한 기능의 백엔드를 갖게 됩니다.
필수 조건
시작하기 전에 다음을 확인하세요:
- Node.js (v18 이상)
- TypeScript 및 Express.js에 대한 기본 이해
- Agora 계정 - _매월 첫 10,000분은 무료_
- Conversational AI 서비스 AppID에 활성화됨
1. 프로젝트 설정
TypeScript 지원을 포함한 새로운 Express 프로젝트를 생성해 보겠습니다:
mkdir agora-convo-ai-express-server
cd agora-convo-ai-express-server
npm init -y
필요한 종속성을 설치하세요:
npm install express cors dotenv agora-token
npm install -D typescript ts-node @types/express @types/cors
TypeScript 초기화:
npx tsc --init
package.json
파일의 scripts
섹션을 수정하여 Typescript를 사용하도록 설정하세요:
"scripts": {
"start": "node dist/index.js",
"dev": "ts-node src/index.ts",
"build": "tsc"
}
이 가이드를 따라가면서 특정 디렉토리에 새로운 파일을 생성해야 합니다. 따라서 시작하기 전에 먼저 이 새로운 디렉토리를 생성해 두겠습니다.
프로젝트 루트 디렉토리에서 src/routes/
, components/
, 및 types/
디렉토리를 생성하고, .env
파일을 추가합니다:
mkdir -p src/routes src/types src/utils
touch .env
프로젝트 디렉토리는 이제 다음과 같은 구조를 가져야 합니다:
├── node_modules/
├── src/
│ ├── routes/
│ ├── types/
│ └── utils/
├── .env
├── package.json
├── package-lock.json
├── tsconfig.json
2. Express 서버 설정
먼저 주요 Express 애플리케이션을 설정해 보겠습니다.
입력 포인트 파일 index.ts
를 src/index.ts
에 생성합니다:
touch src/index.ts
현재는 기본적인 Express 애플리케이션을 생성하고, 가이드를 진행하면서 점차 기능을 추가해 나갈 것입니다. 코드 곳곳에 설명을 추가해 두었으니 참고하시기 바랍니다.
전체적으로 보면, 새로운 Express 애플리케이션을 설정하고 간단한 라우터 구조를 통해 요청을 처리하도록 구성합니다. 건강 점검을 위해 사용할 수 있는 ping
엔드포인트를 생성합니다.
다음 코드를 src/index.ts
에 추가하세요:
import express, { Request, Response } from 'express';
import type { Application } from 'express';
import cors from 'cors';
import dotenv from 'dotenv';
// Load environment variables from .env file
dotenv.config();
// Initialize Express application
const app: Application = express();
const port = process.env.PORT || 3000;
// Configure global middleware
app.use(cors());
app.use(express.json());
// Create a new router instance
const router = express.Router();
// Add health check endpoint
router.get('/ping', (req: Request, res: Response) => {
res.json({ message: 'pong' });
});
// Mount the router
app.use('/', router);
// Start the server
app.listen(port, () => {
console.log(`Server is running on port ${port}`);
});
참고: 환경 변수에서 PORT를 로드하고 있으며,.env
파일에 설정되지 않은 경우 기본값은3000
입니다.
기본 Express 앱을 테스트하려면 다음을 실행하세요:
npm run dev
콘솔에 “서버가 포트 3000에서 실행 중입니다”라는 메시지가 표시되어야 합니다. 이제 http://localhost:3000/ping
로 이동하여 서버가 정상적으로 작동하는지 확인할 수 있습니다.
3. Agora 대화형 AI 구현
먼저 번거로운 작업을 먼저 처리하겠습니다. Agora의 대화형 AI API와 작업하기 위해 필요한 유형의 파일을 생성합니다:
touch src/types/client-request-types.ts
touch src/types/agora-convo-ai-types.ts
다음 내용을 src/types/client-request-types.ts
에 추가하세요:
export interface InviteAgentRequest {
requester_id: string | number;
channel_name: string;
rtc_codec?: number;
input_modalities?: string[];
output_modalities?: string[];
}
export interface RemoveAgentRequest {
agent_id: string;
}
다음 내용을 src/types/agora-convo-ai-types.ts
에 추가하세요:
export enum TTSVendor {
Microsoft = 'microsoft',
ElevenLabs = 'elevenlabs',
}
// Agora API response body
export interface AgentResponse {
agent_id: string;
create_ts: number;
state: string;
}
// Agora API request body
export interface AgoraStartRequest {
name: string;
properties: {
channel: string;
token: string;
agent_rtc_uid: string;
remote_rtc_uids: string[];
enable_string_uid?: boolean;
idle_timeout?: number;
advanced_features?: {
enable_aivad?: boolean;
enable_bhvs?: boolean;
};
asr: {
language: string;
task?: string;
};
llm: {
url?: string;
api_key?: string;
system_messages: Array<{
role: string;
content: string;
}>;
greeting_message: string;
failure_message: string;
max_history?: number;
input_modalities?: string[];
output_modalities?: string[];
params: {
model: string;
max_tokens: number;
temperature?: number;
top_p?: number;
};
};
vad: {
silence_duration_ms: number;
speech_duration_ms?: number;
threshold?: number;
interrupt_duration_ms?: number;
prefix_padding_ms?: number;
};
tts: TTSConfig;
};
}
export interface TTSConfig {
vendor: TTSVendor;
params: MicrosoftTTSParams | ElevenLabsTTSParams;
}
export interface AgoraTokenData {
token: string;
uid: string;
channel: string;
agentId?: string;
}
interface MicrosoftTTSParams {
key: string;
region: string;
voice_name: string;
rate?: number;
volume?: number;
}
interface ElevenLabsTTSParams {
key: string;
voice_id: string;
model_id: string;
}
이 새로운 유형들은 다음 단계에서 조립할 모든 부품에 대한 이해를 제공합니다. 고객 요청을 받아 AgoraStartRequest를 구성하고 Agora의 대화형 AI 엔진으로 전송합니다. Agora의 Convo AI 엔진은 에이전트를 대화 흐름에 추가합니다.
에이전트 경로
대화형 AI 에이전트 경로를 구현해 보겠습니다. agent
경로를 생성합니다:
touch src/routes/agent.ts
먼저 express, 새로운 유형 및 `agora-token` 라이브러리를 가져옵니다. 이는 에이전트용 토큰을 생성해야 하기 때문입니다.
import express, { Request, Response } from 'express';
import { RtcTokenBuilder, RtcRole } from 'agora-token';
import {
AgoraStartRequest,
TTSConfig,
TTSVendor,
AgentResponse,
} from '../types/agora-convo-ai-types';
import {
InviteAgentRequest,
RemoveAgentRequest,
} from '../types/client-request-types';
에이전트 엔드포인트용 라우터를 정의합니다. 이 라우터는 나중에 이 에이전트들을 메인 앱의 라우터에 연결하는 데 사용됩니다.
// Router instance for handling AI agent-related endpoints
export const agentRouter = express.Router();
에이전트 초대
먼저 /agent/invite
엔드포인트를 구현합니다. 이 경로는 다음과 같은 주요 작업을 처리해야 합니다:
- 사용자 요청을 파싱하고 이를 사용하여 아고라 의 대화형 AI 엔진에 대한 시작 요청을 생성합니다.
- AI 에이전트가 RTC 채널에 액세스하기 위한 토큰을 생성합니다.
- 텍스트-음성 변환(Microsoft 또는 ElevenLabs)을 구성합니다.
- AI 에이전트의 프롬프트 및 인사 메시지를 정의합니다.
- 대화 흐름을 제어하는 음성 활동 감지(VAD)를 구성합니다.
- 아고라의 대화형 AI 엔진으로 시작 요청을 전송합니다.
- 아고라의 대화형 AI 엔진 응답에서 AgentID를 포함하여 클라이언트에게 응답을 반환합니다.
// POST /agent/invite - Start a conversation with an AI agent
agentRouter.post(
'/invite',
async (req: Request<{}, {}, InviteAgentRequest>, res: Response) => {
try {
// Extract required parameters from the request body
const {
requester_id,
channel_name,
input_modalities,
output_modalities,
} = req.body;
const agentUid = process.env.AGENT_UID || 'Agent';
// Generate a unique name for this conversation
const channelNameBase = 'conversation';
const timestamp = Date.now();
const random = Math.random().toString(36).substring(2, 8);
const uniqueName = `${channelNameBase}-${timestamp}-${random}`;
const expirationTime = Math.floor(timestamp / 1000) + 3600; // Token expires in 1 hour
// Generate a Publisher token for the agent
const token = RtcTokenBuilder.buildTokenWithUid(
process.env.AGORA_APP_ID!,
process.env.AGORA_APP_CERTIFICATE!,
channel_name,
agentUid,
RtcRole.PUBLISHER,
expirationTime,
expirationTime
);
// Configure Text-to-Speech settings based on environment configuration
const ttsVendor = process.env.TTS_VENDOR as TTSVendor;
if (!ttsVendor) {
throw new Error('TTS_VENDOR is not set');
}
const ttsConfig = getTTSConfig(ttsVendor);
// Prepare requester ID for Agora's API
const requesterUid = requester_id.toString();
// Define AI assistant behavior
const systemMessage =
'You are a helpful assistant. Pretend that the text input is audio, and you are responding to it. Speak fast, clearly, and concisely.';
// Prepare the request body for Agora Conversation AI service
const requestBody: AgoraStartRequest = {
name: uniqueName,
properties: {
channel: channel_name,
token: token,
agent_rtc_uid: agentUid,
remote_rtc_uids: [requesterUid],
enable_string_uid: /[a-zA-Z]/.test(requesterUid),
idle_timeout: 30,
asr: {
language: 'en-US',
task: 'conversation',
},
llm: {
url: process.env.LLM_URL,
api_key: process.env.LLM_TOKEN,
system_messages: [
{
role: 'system',
content: systemMessage,
},
],
greeting_message: 'Hello! How can I assist you today?',
failure_message: 'Please wait a moment.',
max_history: 10,
params: {
model: process.env.LLM_MODEL!,
max_tokens: 1024,
temperature: 0.7,
top_p: 0.95,
},
input_modalities: input_modalities ||
process.env.INPUT_MODALITIES?.split(',') || ['text'],
output_modalities: output_modalities ||
process.env.OUTPUT_MODALITIES?.split(',') || ['text', 'audio'],
},
tts: ttsConfig,
vad: {
silence_duration_ms: 480,
speech_duration_ms: 15000,
threshold: 0.5,
interrupt_duration_ms: 160,
prefix_padding_ms: 300,
},
advanced_features: {
enable_aivad: false,
enable_bhvs: false,
},
},
};
// Make API request to Agora Conversation AI service
const response = await fetch(
`${process.env.AGORA_CONVO_AI_BASE_URL}/${process.env.AGORA_APP_ID}/join`,
{
method: 'POST',
headers: {
'Content-Type': 'application/json',
Authorization: `Basic ${Buffer.from(
`${process.env.AGORA_CUSTOMER_ID}:${process.env.AGORA_CUSTOMER_SECRET}`
).toString('base64')}`,
},
body: JSON.stringify(requestBody),
}
);
if (!response.ok) {
throw new Error(`Failed to start conversation: ${response.status}`);
}
const data: AgentResponse = await response.json();
res.json(data);
} catch (error) {
console.error('Error starting conversation:', error);
res.status(500).json({
error:
error instanceof Error
? error.message
: 'Failed to start conversation',
});
}
}
);
진행하기 전에 VAD 설정을 잠시 검토해야 합니다. 이 설정은 대화의 흐름을 제어하기 때문입니다:
silence_duration_ms
- 말하기가 중단된 후 말하기 순서를 종료하기까지 기다리는 시간 (밀리초)speech_duration_ms
- 단일 말하기 세그먼트에 허용되는 최대 지속 시간 (밀리초)threshold
- 음성 활동에 대한 감도 (높은 값은 더 큰 음량에 반응) (더 높은 값은 더 큰 음량을 요구합니다)interrupt_duration_ms
- 방해가 감지되는 속도prefix_padding_ms
- 말의 시작 부분에 캡처되는 오디오 패딩
시작 경로에서는 변수 ttsConfig
를 사용하며, 이 변수는 getTTSConfig
를 호출합니다. 이 부분을 강조해야 하는 이유는 일반적으로 단일 TTS 구성만 사용하기 때문입니다. 데모 목적으로 모든 TTS 공급업체를 지원하는 Agora의 Convo AI 엔진에 구성 설정을 구현하는 방법을 보여주기 위해 이 방식으로 구현했습니다.
/**
* Generates TTS configuration based on the specified vendor
*/
function getTTSConfig(vendor: TTSVendor): TTSConfig {
if (vendor === TTSVendor.Microsoft) {
if (
!process.env.MICROSOFT_TTS_KEY ||
!process.env.MICROSOFT_TTS_REGION ||
!process.env.MICROSOFT_TTS_VOICE_NAME ||
!process.env.MICROSOFT_TTS_RATE ||
!process.env.MICROSOFT_TTS_VOLUME
) {
throw new Error('Missing Microsoft TTS environment variables');
}
return {
vendor: TTSVendor.Microsoft,
params: {
key: process.env.MICROSOFT_TTS_KEY,
region: process.env.MICROSOFT_TTS_REGION,
voice_name: process.env.MICROSOFT_TTS_VOICE_NAME,
rate: parseFloat(process.env.MICROSOFT_TTS_RATE),
volume: parseFloat(process.env.MICROSOFT_TTS_VOLUME),
},
};
}
if (vendor === TTSVendor.ElevenLabs) {
if (
!process.env.ELEVENLABS_API_KEY ||
!process.env.ELEVENLABS_VOICE_ID ||
!process.env.ELEVENLABS_MODEL_ID
) {
throw new Error('Missing ElevenLabs environment variables');
}
return {
vendor: TTSVendor.ElevenLabs,
params: {
key: process.env.ELEVENLABS_API_KEY,
model_id: process.env.ELEVENLABS_MODEL_ID,
voice_id: process.env.ELEVENLABS_VOICE_ID,
},
};
}
throw new Error(`Unsupported TTS vendor: ${vendor}`);
}
에이전트 제거
에이전트가 대화실에 참여한 후에는 해당 에이전트를 대화실에서 제거하는 방법이 필요합니다. 이때 /agent/remove
경로가 사용됩니다. 이 경로는 에이전트 ID를 받아 Agora의 대화형 AI 엔진에 요청을 전송하여 해당 에이전트를 채널에서 제거합니다.
// POST /agent/remove - Remove an AI agent from conversation
agentRouter.post(
'/remove',
async (req: Request<{}, {}, RemoveAgentRequest>, res: Response) => {
try {
const { agent_id } = req.body;
if (!agent_id) {
throw new Error('agent_id is required');
}
// Prepare authentication for Agora API
const plainCredential = `${process.env.AGORA_CUSTOMER_ID}:${process.env.AGORA_CUSTOMER_SECRET}`;
const encodedCredential = Buffer.from(plainCredential).toString('base64');
// Make API request to remove agent from conversation
const response = await fetch(
`${process.env.AGORA_CONVO_AI_BASE_URL}/${process.env.AGORA_APP_ID}/agents/${agent_id}/leave`,
{
method: 'POST',
headers: {
'Content-Type': 'application/json',
Authorization: `Basic ${encodedCredential}`,
},
}
);
if (!response.ok) {
throw new Error(`Failed to remove agent: ${response.status}`);
}
res.json({ success: true });
} catch (error) {
console.error('Error removing agent:', error);
res.status(500).json({
error:
error instanceof Error ? error.message : 'Failed to remove agent',
});
}
}
);
에이전트 라우터는 여러 환경 변수를 로드합니다. 이 변수들은 .env
파일에 설정해야 합니다. 이 가이드의 마지막 부분에 필요한 모든 환경 변수 목록을 포함시켰습니다.
Express 앱에 에이전트 라우터 추가
메인 애플리케이션 파일을 업데이트하여 에이전트 라우터를 포함시키겠습니다. `src/index.ts` 파일을 열고 다음을 추가합니다:
// Previous imports remain the same
import { agentRouter } from './routes/agent'; // Import the Agent router
// Rest of the file remains the same...
// Register route handlers
// - previous ping route remains the same.
router.use('/agent', agentRouter); // add Agent routes
// Rest of the code remains the same...
토큰 생성
이 가이드의 목적은 기존 Agora 클라이언트 앱과 함께 작동하는 독립형 마이크로 서비스를 구축하는 것이므로, 완전성을 위해 토큰 생성 경로를 구현할 것입니다.
토큰 경로 파일을 src/routes/token.ts
에 생성합니다.:
touch src/routes/token.ts
이 코드를 설명하는 것은 이 가이드의 범위를 약간 벗어나지만, 토큰에 익숙하지 않다면 제 가이드 아고라 애플리케이션을 위한 토큰 서버 구축를 참고하시기를 추천합니다.
토큰 경로의 독특한 요소 중 하나는 uid 또는 채널 이름이 제공되지 않을 경우 이 코드가 uid에 0을 사용하고 고유한 채널 이름을 생성한다는 점입니다. 채널 이름과 UID는 각 토큰과 함께 반환됩니다.
다음 코드를 src/routes/token.ts
파일에 추가하세요:
import express, { Request, Response, NextFunction } from 'express';
import { RtcTokenBuilder, RtcRole } from 'agora-token';
export const tokenRouter = express.Router();
/**
* GET /token - Generate Agora RTC PUBLISHER token for client
* Query Parameters:
* - uid (optional): Unique user identifier
* - channel (optional): Channel name for the communication
*/
tokenRouter.get('/', (req: Request, res: Response) => {
console.log('Generating Agora token...');
// Validate Agora credentials
if (!process.env.AGORA_APP_ID || !process.env.AGORA_APP_CERTIFICATE) {
console.error('Agora credentials are not set');
return res.status(500).json({ error: 'Agora credentials are not set' });
}
// Get query parameters
const { uid: uidStr, channel } = req.query;
// Validate UID if provided
if (uidStr && !/^\d+$/.test(uidStr as string)) {
return res
.status(400)
.json({ error: 'Invalid uid parameter. Must be a number' });
}
const uid = parseInt((uidStr as string) || '0');
const channelName = (channel as string) || generateChannelName();
const expirationTime = Math.floor(Date.now() / 1000) + 3600;
try {
console.log('Building token with UID:', uid, 'Channel:', channelName);
const token = RtcTokenBuilder.buildTokenWithUid(
process.env.AGORA_APP_ID!,
process.env.AGORA_APP_CERTIFICATE!,
channelName,
uid,
RtcRole.PUBLISHER,
expirationTime,
expirationTime
);
console.log('Token generated successfully');
res.json({
token,
uid: uid.toString(),
channel: channelName,
});
} catch (error) {
console.error('Error generating Agora token:', error);
res.status(500).json({
error: 'Failed to generate Agora token',
details: error instanceof Error ? error.message : 'Unknown error',
});
}
});
/**
* Generates a unique channel name
*/
function generateChannelName(): string {
const timestamp = Date.now();
const random = Math.random().toString(36).substring(2, 8);
return `ai-conversation-${timestamp}-${random}`;
}
이제 토큰 라우터를 포함하도록 주요 애플리케이션 파일을 업데이트합니다. src/index.ts
파일을 업데이트합니다::
// Previous imports remain the same
import { tokenRouter } from './routes/token'; // Import the Token router
// Rest of the file remains the same...
// Register route handlers
// - previous ping route remains the same.
// - previous agent routes remain the same.
router.use('/token', tokenRouter); // add token routes
// Rest of the code remains the same...
환경 및 요청 검증
요청 처리가 적절히 이루어지도록 검증 미들웨어를 추가해 보겠습니다.
validation.ts
파일을 생성합니다. src/utils/validation.ts
touch src/utils/validation.ts
이 코드는 환경 변수가 올바르게 설정되었는지 확인하고, 모든 들어오는 요청이 우리가 정의한 클라이언트 요청 유형과 일치하는지 확인합니다.
다음 코드를 src/utils/validation.ts
파일에 추가하세요:
import { Request, Response, NextFunction } from 'express';
import {
InviteAgentRequest,
RemoveAgentRequest,
} from '../types/client-request-types';
/**
* Validates required environment variables
*/
export function validateEnvironment(
req: Request,
res: Response,
next: NextFunction
) {
// Common Agora credentials
if (!process.env.AGORA_APP_ID || !process.env.AGORA_APP_CERTIFICATE) {
console.error('Agora credentials are not set');
return res.status(500).json({ error: 'Agora credentials are not set' });
}
// Validate based on route
if (req.path.startsWith('/agent')) {
// Additional validations for agent routes
if (
!process.env.AGORA_CONVO_AI_BASE_URL ||
!process.env.AGORA_CUSTOMER_ID ||
!process.env.AGORA_CUSTOMER_SECRET
) {
console.error('Agora Conversation AI credentials are not set');
return res.status(500).json({
error: 'Agora Conversation AI credentials are not set',
});
}
// LLM validations
if (!process.env.LLM_URL || !process.env.LLM_TOKEN) {
console.error('LLM configuration is not set');
return res.status(500).json({ error: 'LLM configuration is not set' });
}
// TTS validations
const ttsVendor = process.env.TTS_VENDOR;
if (!ttsVendor) {
console.error('TTS_VENDOR is not set');
return res.status(500).json({ error: 'TTS_VENDOR is not set' });
}
if (ttsVendor === 'microsoft') {
if (
!process.env.MICROSOFT_TTS_KEY ||
!process.env.MICROSOFT_TTS_REGION ||
!process.env.MICROSOFT_TTS_VOICE_NAME ||
!process.env.MICROSOFT_TTS_RATE ||
!process.env.MICROSOFT_TTS_VOLUME
) {
console.error('Microsoft TTS configuration is not set');
return res.status(500).json({
error: 'Microsoft TTS configuration is not set',
});
}
} else if (ttsVendor === 'elevenlabs') {
if (
!process.env.ELEVENLABS_API_KEY ||
!process.env.ELEVENLABS_VOICE_ID ||
!process.env.ELEVENLABS_MODEL_ID
) {
console.error('ElevenLabs TTS configuration is not set');
return res.status(500).json({
error: 'ElevenLabs TTS configuration is not set',
});
}
} else {
return res.status(500).json({
error: `Unsupported TTS vendor: ${ttsVendor}`,
});
}
}
next();
}
/**
* Validates Content-Type header for POST requests
*/
export function validateContentType(
req: Request,
res: Response,
next: NextFunction
) {
if (req.method === 'POST' && !req.is('application/json')) {
return res.status(415).json({
error: 'Unsupported Media Type. Content-Type must be application/json',
});
}
next();
}
/**
* Validates request body based on route
*/
export function validateRequestBody(
req: Request,
res: Response,
next: NextFunction
) {
// Skip validation for non-POST requests
if (req.method !== 'POST') {
return next();
}
// Ensure request body is not empty
if (!req.body || Object.keys(req.body).length === 0) {
return res.status(400).json({ error: 'Request body is required' });
}
// Route-specific validation
if (req.path === '/agent/invite') {
const { requester_id, channel_name } = req.body as InviteAgentRequest;
if (!requester_id) {
return res.status(400).json({ error: 'requester_id is required' });
}
if (!channel_name) {
return res.status(400).json({ error: 'channel_name is required' });
}
// Validate requester_id format
if (typeof requester_id === 'string') {
if (requester_id.trim() === '') {
return res.status(400).json({
error: 'requester_id cannot be empty',
});
}
} else if (typeof requester_id === 'number') {
if (!Number.isInteger(requester_id) || requester_id < 0) {
return res.status(400).json({
error:
'requester_id must be a positive integer when provided as a number',
});
}
} else {
return res.status(400).json({
error: 'requester_id must be a string or number',
});
}
// Validate channel_name
if (typeof channel_name !== 'string') {
return res.status(400).json({
error: 'channel_name must be a string',
});
}
if (channel_name.length < 3 || channel_name.length > 64) {
return res.status(400).json({
error: 'channel_name length must be between 3 and 64 characters',
});
}
} else if (req.path === '/agent/remove') {
const { agent_id } = req.body as RemoveAgentRequest;
if (!agent_id) {
return res.status(400).json({ error: 'agent_id is required' });
}
if (typeof agent_id !== 'string') {
return res.status(400).json({ error: 'agent_id must be a string' });
}
}
next();
}
이제 이 유효성 검사 미들웨어를 사용하도록 주요 애플리케이션 파일을 업데이트합니다. src/index.ts
를 업데이트합니다:
// Previous imports remain the same
import {
validateEnvironment,
validateContentType,
validateRequestBody,
} from './utils/validation'; // Import validation checks
// Rest of the file remains the same...
// Configure validation middleware for all routes
router.use(validateEnvironment as express.RequestHandler);
router.use(validateContentType as express.RequestHandler);
router.use(validateRequestBody as express.RequestHandler);
// Rest of the code remains the same ...
개발 및 테스트 환경 설정
핵심 기능이 구현되었으니 이제 적절한 개발 워크플로우를 설정해 보겠습니다. 파일 변경 시 서버를 자동으로 재시작하도록 nodemon을 구성하겠습니다.
프로젝트의 루트 디렉토리에서 nodemon.json
파일을 생성합니다:
touch nodemon.json
Add the following:
{
"watch": [
"src"
],
"ext": ".ts,.js",
"ignore": [],
"exec": "ts-node ./src/index.ts"
}
package.json
파일을 업데이트하세요. Nodemon을 사용하도록 스크립트를 업데이트하세요:
"scripts": {
"start": "node dist/index.js",
"dev": "nodemon",
"build": "tsc"
}
이제 개발 서버를 실행해 보겠습니다:
npm run dev
.env
파일이 필요한 모든 자격 증명과 함께 올바르게 구성되어 있는지 확인하세요. 이 가이드의 마지막 부분에 환경 변수의 전체 목록이 있습니다.
서버가 정상적으로 실행 중이라면 다음과 같은 출력이 표시되어야 합니다:
서버가 포트 3000에서 실행 중입니다
서버 테스트
엔드포인트를 테스트하기 전에 클라이언트 측 애플리케이션이 실행 중인지 확인하세요. 아고라의 비디오 SDK를 구현한 애플리케이션(웹, 모바일, 데스크톱)을 사용할 수 있습니다. 애플리케이션이 없다면 아고라의 Voice Demo를 사용하세요. 채널에 참여하기 전에 토큰 요청을 반드시 수행해야 합니다.
curl을 사용하여 API 엔드포인트를 테스트해 보겠습니다:
1. 토큰 생성
curl "http://localhost:3000/token"
예상 응답:
{
"token": "007eJxzYBB...",
"uid": "0",
"channel": "ai-conversation-1707654321-abc123"
}
2. 특정 매개변수를 사용하여 토큰 생성
curl "http://localhost:3000/token?channel=test-channel&uid=1234"
3. AI 에이전트를 초대하세요
curl -X POST "http://localhost:3000/agent/invite" \
-H "Content-Type: application/json" \
-d '{
"requester_id": "1234",
"channel_name": "YOUR_CHANNEL_NAME_FROM_PREVIOUS_STEP",
"input_modalities": ["text"],
"output_modalities": ["text", "audio"]
}'
Expected response:
{
"agent_id": "agent-123",
"create_ts": 1234567890,
"state": "active"
}
4. Remove an AI Agent
curl -X POST "http://localhost:3000/agent/remove" \
-H "Content-Type: application/json" \
-d '{
"agent_id": "agent-123"
}'
예상 응답:
{
"success": true
}
맞춤 설정
Agora Conversational AI 엔진은 다양한 맞춤 설정을 지원합니다.
에이전트 맞춤 설정
/agent/invite
엔드포인트에서 시스템 메시지를 수정하여 에이전트의 프로ンプ트를 맞춤 설정할 수 있습니다:
const systemMessage =
"You are a technical support specialist named Alex. Your responses should be friendly but concise, focused on helping users solve their technical problems. Use simple language but don't oversimplify technical concepts.";
또한 인사 메시지를 업데이트하여 채널에 처음 전송되는 메시지를 제어할 수 있습니다.
llm {
greeting_message: 'Hello! How can I assist you today?',
failure_message: 'Please wait a moment.',
}
음성 합성 맞춤 설정
응용 프로그램에 적합한 음성을 선택하려면 음성 라이브러리를 탐색하세요:
- Microsoft Azure TTS의 경우: Microsoft Azure TTS 음성 갤러리를 방문하세요
- ElevenLabs TTS의 경우: ElevenLabs 음성 라이브러리를 탐색하세요
음성 활동 감지(VAD) 조정
대화 흐름을 최적화하기 위해 VAD 설정을 조정하세요:
vad: {
silence_duration_ms: 600, // How long to wait after silence to end turn
speech_duration_ms: 10000, // Maximum duration for a single speech segment
threshold: 0.6, // Speech detection sensitivity
interrupt_duration_ms: 200, // How quickly interruptions are detected
prefix_padding_ms: 400, // Audio padding at the beginning of speech
}
환경 변수 참조 가이드
다음은 .env
파일용 환경 변수의 전체 목록입니다:
# Server Configuration
PORT=3000
# Agora Configuration
AGORA_APP_ID=your_app_id
AGORA_APP_CERTIFICATE=your_app_certificate
AGORA_CONVO_AI_BASE_URL=https://api.agora.io/api/conversational-ai-agent/v2/projects
AGORA_CUSTOMER_ID=your_customer_id
AGORA_CUSTOMER_SECRET=your_customer_secret
AGENT_UID=Agent
# LLM Configuration
LLM_URL=https://api.openai.com/v1/chat/completions
LLM_TOKEN=your_openai_api_key
LLM_MODEL=gpt-4o-mini
# Input/Output Modalities
INPUT_MODALITIES=text
OUTPUT_MODALITIES=text,audio
# TTS Configuration
TTS_VENDOR=microsoft # or elevenlabs
# Microsoft TTS Configuration
MICROSOFT_TTS_KEY=your_microsoft_tts_key
MICROSOFT_TTS_REGION=your_microsoft_tts_region
MICROSOFT_TTS_VOICE_NAME=en-US-GuyNeural
MICROSOFT_TTS_RATE=1.0
MICROSOFT_TTS_VOLUME=100.0
# ElevenLabs TTS Configuration
ELEVENLABS_API_KEY=your_elevenlabs_api_key
ELEVENLABS_VOICE_ID=your_elevenlabs_voice_id
ELEVENLABS_MODEL_ID=eleven_monolingual_v1
다음 단계
축하합니다! Agora의 Conversational AI Engine과 통합된 Express 서버를 구축하셨습니다. 이 마이크로서비스를 기존 Agora 백엔드와 통합하세요.
Agora의 Conversational AI Engine에 대한 자세한 내용은 공식 문서를 참고하세요.
개발을 즐겁게 진행하세요!