음성 통화 (플러그인)
플러그인을 통한 OpenClaw용 음성 통화 기능입니다. 발신 알림 및 수신 정책을 통한 멀티턴 대화를 지원합니다.
현재 프로바이더:
twilio(Programmable Voice + Media Streams)telnyx(Call Control v2)plivo(Voice API + XML transfer + GetInput speech)mock(개발/네트워크 없음)
핵심 개념:
- 플러그인 설치
- Gateway 재시작
plugins.entries.voice-call.config아래에 설정openclaw voicecall ...또는voice_call도구 사용
실행 위치 (로컬 vs 원격)
음성 통화 플러그인은 Gateway 프로세스 내부에서 실행됩니다.
원격 Gateway를 사용하는 경우, Gateway를 실행하는 머신에서 플러그인을 설치/설정한 다음 Gateway를 재시작하세요.
설치
옵션 A: npm에서 설치 (권장)
openclaw plugins install @openclaw/voice-call
이후 Gateway를 재시작하세요.
옵션 B: 로컬 폴더에서 설치 (개발용, 복사 없음)
openclaw plugins install ./extensions/voice-call
cd ./extensions/voice-call && pnpm install
이후 Gateway를 재시작하세요.
설정
plugins.entries.voice-call.config 아래에 설정하세요:
{
plugins: {
entries: {
"voice-call": {
enabled: true,
config: {
provider: "twilio", // 또는 "telnyx" | "plivo" | "mock"
fromNumber: "+15550001234",
toNumber: "+15550005678",
twilio: {
accountSid: "ACxxxxxxxx",
authToken: "...",
},
telnyx: {
apiKey: "...",
connectionId: "...",
// Telnyx Mission Control Portal의 Telnyx 웹훅 공개 키
// (Base64 문자열; TELNYX_PUBLIC_KEY로도 설정 가능).
publicKey: "...",
},
plivo: {
authId: "MAxxxxxxxxxxxxxxxxxxxx",
authToken: "...",
},
// 웹훅 서버
serve: {
port: 3334,
path: "/voice/webhook",
},
// 웹훅 보안 (터널/프록시에 권장)
webhookSecurity: {
allowedHosts: ["voice.example.com"],
trustedProxyIPs: ["100.64.0.1"],
},
// 공개 노출 (하나 선택)
// publicUrl: "https://example.ngrok.app/voice/webhook",
// tunnel: { provider: "ngrok" },
// tailscale: { mode: "funnel", path: "/voice/webhook" }
outbound: {
defaultMode: "notify", // notify | conversation
},
streaming: {
enabled: true,
streamPath: "/voice/stream",
preStartTimeoutMs: 5000,
maxPendingConnections: 32,
maxPendingConnectionsPerIp: 4,
maxConnections: 128,
},
},
},
},
},
}
참고:
- Twilio/Telnyx는 공개적으로 접근 가능한 웹훅 URL이 필요합니다.
- Plivo는 공개적으로 접근 가능한 웹훅 URL이 필요합니다.
mock은 로컬 개발용 프로바이더입니다 (네트워크 호출 없음).- Telnyx는
skipSignatureVerification이 true가 아닌 한telnyx.publicKey(또는TELNYX_PUBLIC_KEY)가 필요합니다. skipSignatureVerification은 로컬 테스트 전용입니다.- ngrok 무료 계층을 사용하는 경우,
publicUrl을 정확한 ngrok URL로 설정하세요. 서명 검증은 항상 적용됩니다. tunnel.allowNgrokFreeTierLoopbackBypass: true는tunnel.provider="ngrok"이고serve.bind가 루프백(ngrok 로컬 에이전트)일 때만 유효하지 않은 서명의 Twilio 웹훅을 허용합니다. 로컬 개발에만 사용하세요.- ngrok 무료 계층 URL은 변경되거나 중간 페이지가 추가될 수 있습니다.
publicUrl이 변경되면 Twilio 서명이 실패합니다. 프로덕션에서는 안정적인 도메인이나 Tailscale funnel을 사용하세요. - 스트리밍 보안 기본값:
streaming.preStartTimeoutMs는 유효한start프레임을 보내지 않는 소켓을 닫습니다.streaming.maxPendingConnections는 인증되지 않은 시작 전 소켓 총수를 제한합니다.streaming.maxPendingConnectionsPerIp는 소스 IP당 인증되지 않은 시작 전 소켓을 제한합니다.streaming.maxConnections는 총 오픈 미디어 스트림 소켓(대기 + 활성)을 제한합니다.
비활성 통화 정리기
staleCallReaperSeconds를 사용하여 종료 웹훅을 받지 못한 통화를
종료할 수 있습니다 (예: 완료되지 않은 알림 모드 통화). 기본값은 0
(비활성화)입니다.
권장 범위:
- 프로덕션: 알림 스타일 흐름에
120-300초. - 이 값은 정상 통화가 완료될 수 있도록
maxDurationSeconds보다 높게 유지하세요. 좋은 시작점은maxDurationSeconds + 30-60초입니다.
예시:
{
plugins: {
entries: {
"voice-call": {
config: {
maxDurationSeconds: 300,
staleCallReaperSeconds: 360,
},
},
},
},
}
웹훅 보안
프록시나 터널이 Gateway 앞에 있을 때, 플러그인은 서명 검증을 위해 공개 URL을 재구성합니다. 이 옵션은 어떤 전달 헤더를 신뢰할지 제어합니다.
webhookSecurity.allowedHosts는 전달 헤더에서 허용되는 호스트를 화이트리스트합니다.
webhookSecurity.trustForwardingHeaders는 허용 목록 없이 전달 헤더를 신뢰합니다.
webhookSecurity.trustedProxyIPs는 요청 원격 IP가 목록에 일치할 때만
전달 헤더를 신뢰합니다.
Twilio와 Plivo에는 웹훅 재생 방지가 활성화되어 있습니다. 재생된 유효한 웹훅 요청은 확인 응답되지만 부작용은 건너뛰어집니다.
Twilio 대화 턴에는 <Gather> 콜백에 턴별 토큰이 포함되어 있어,
오래된/재생된 음성 콜백이 새로운 대기 중인 텍스트 변환 턴을 충족할 수 없습니다.
안정적인 공개 호스트를 사용한 예시:
{
plugins: {
entries: {
"voice-call": {
config: {
publicUrl: "https://voice.example.com/voice/webhook",
webhookSecurity: {
allowedHosts: ["voice.example.com"],
},
},
},
},
},
}
통화용 TTS
음성 통화는 통화 중 스트리밍 음성을 위해 코어 messages.tts 설정(OpenAI 또는 ElevenLabs)을
사용합니다. 동일한 구조로 플러그인 설정에서 재정의할 수 있으며,
messages.tts와 딥 머지됩니다.
{
tts: {
provider: "elevenlabs",
elevenlabs: {
voiceId: "pMsXgVXv3BLzUgSXRplE",
modelId: "eleven_multilingual_v2",
},
},
}
참고:
- Edge TTS는 음성 통화에서 무시됩니다 (전화 오디오에는 PCM이 필요하며, Edge 출력은 불안정합니다).
- Twilio 미디어 스트리밍이 활성화되면 코어 TTS가 사용됩니다. 그렇지 않으면 프로바이더 네이티브 음성으로 폴백합니다.
추가 예시
코어 TTS만 사용 (재정의 없음):
{
messages: {
tts: {
provider: "openai",
openai: { voice: "alloy" },
},
},
}
통화에만 ElevenLabs로 재정의 (다른 곳은 코어 기본값 유지):
{
plugins: {
entries: {
"voice-call": {
config: {
tts: {
provider: "elevenlabs",
elevenlabs: {
apiKey: "elevenlabs_key",
voiceId: "pMsXgVXv3BLzUgSXRplE",
modelId: "eleven_multilingual_v2",
},
},
},
},
},
},
}
통화용 OpenAI 모델만 재정의 (딥 머지 예시):
{
plugins: {
entries: {
"voice-call": {
config: {
tts: {
openai: {
model: "gpt-4o-mini-tts",
voice: "marin",
},
},
},
},
},
},
}
수신 통화
수신 정책은 기본적으로 disabled입니다. 수신 통화를 활성화하려면:
{
inboundPolicy: "allowlist",
allowFrom: ["+15550001234"],
inboundGreeting: "Hello! How can I help?",
}
inboundPolicy: "allowlist"는 낮은 보증의 발신자 ID 필터링입니다. 플러그인은
프로바이더가 제공한 From 값을 정규화하고 allowFrom과 비교합니다.
웹훅 검증은 프로바이더 전달 및 페이로드 무결성을 인증하지만,
PSTN/VoIP 발신 번호 소유권을 증명하지는 않습니다. allowFrom을
강력한 발신자 신원이 아닌 발신자 ID 필터링으로 취급하세요.
자동 응답은 에이전트 시스템을 사용합니다. 다음으로 조정하세요:
responseModelresponseSystemPromptresponseTimeoutMs
CLI
openclaw voicecall call --to "+15555550123" --message "Hello from OpenClaw"
openclaw voicecall continue --call-id <id> --message "Any questions?"
openclaw voicecall speak --call-id <id> --message "One moment"
openclaw voicecall end --call-id <id>
openclaw voicecall status --call-id <id>
openclaw voicecall tail
openclaw voicecall expose --mode funnel
에이전트 도구
도구 이름: voice_call
액션:
initiate_call(message, to?, mode?)continue_call(callId, message)speak_to_user(callId, message)end_call(callId)get_status(callId)
이 저장소에는 skills/voice-call/SKILL.md에 매칭되는 스킬 문서가 포함되어 있습니다.
Gateway RPC
voicecall.initiate(to?,message,mode?)voicecall.continue(callId,message)voicecall.speak(callId,message)voicecall.end(callId)voicecall.status(callId)