
규정을 준수하는 BFSI 음성 에이전트 구축하기
요약
규제가 엄격한 금융 서비스(BFSI) 환경에서 프롬프트의 한계를 넘어 규정을 준수하는 음성 AI 에이전트를 구축하는 방법을 다룹니다. 단순 프롬프팅 대신 결정론적 동작을 보장하기 위한 3계층 아키텍처 설계 방식을 소개합니다.
핵심 포인트
- 금융 분야 음성 에이전트는 확률적 모델의 한계를 극복하고 결정론적 동작이 필요함
- 단일 프롬프트 방식은 규정 위반 위험이 높아 3계층 아키텍처 도입이 권장됨
- 신원 확인, 결제 안내, 분쟁 처리 등 복잡한 시나리오의 규정 준수 설계 방법 제시
- LLM이 프롬프트를 무시하더라도 규정을 지킬 수 있는 구조적 접근법 강조
빠른 링크.
코드: https://github.com/murf-ai/murf-cookbook/tree/main/examples/agents/payment-reminder
비디오 워크스루(Video Walkthrough):
금융 서비스(Financial services)는 음성 AI 에이전트를 도입하기에 가장 규제가 심한 분야 중 하나입니다. 결제 안내(payment reminder), KYC(Know Your Customer, 고객 확인 절차) 인증 전화, 또는 사기 경고(fraud alert) 등 무엇이든 대화는 규칙에 얽매여 있습니다.
이 모든 것의 공통점은 에이전트가 무엇을, 언제, 누구에게 말하느냐이며, 이는 더 이상 단순한 UX(사용자 경험)의 선택 문제가 아닙니다. 전화 상대방이 누구인지 확인하기 전에 계좌 정보를 노출하거나, 경제적 어려움을 방금 토로한 사람에게 압박을 가하거나, 부적절한 순간에 잘못된 말을 한다면, 당신은 실질적인 결과(consequences)를 초래하는 선을 넘게 되는 것입니다.
대부분의 사람들은 이를 해결하기 위해 더 나은 프롬프트(prompt)를 찾으며, 이는 대부분의 경우 효과가 있습니다. 하지만 전화 통화는 실시간으로 이루어지는 되돌릴 수 없는 대화이며, 규정 준수(compliance)의 문제에 있어서는 "대부분의 경우"라는 말은 충분하지 않습니다.
따라서 질문은 "어떻게 더 나은 프롬프트를 작성할 것인가?"가 아니라, **"모델이 프롬프트를 무시할 때조차 규정을 준수하는 음성 에이전트를 어떻게 구축할 것인가?"**가 되어야 합니다.
이 튜토리얼에서는 고객에게 전화를 걸어 신원을 확인하고 결제 링크를 제공하는 동시에, 규정이 요구하는 방식대로 분쟁, 경제적 어려움, 오연결 번호(wrong numbers)를 처리하는 아웃바운드 결제 안내 에이전트를 구축할 것입니다.
아키텍처 (Architecture)
대부분의 에이전트를 위한 기본적인 음성 아키텍처는 동일하게 유지됩니다. 전화 시스템(telephony)을 연결하고, LLM(대규모 언어 모델)을 연결한 뒤, 에이전트에게 "예의 바른 채권 추심원이 되어라"라고 지시하는 시스템 프롬프트(system prompt)를 작성하는 것입니다.
금융 서비스 분야에서, 아무런 제약 없이 방치된 단일 프롬프트는 규정 위반(compliance violation)이 발생하기를 기다리는 것과 같습니다. LLM은 확률적(probabilistic)입니다. 즉, 사용자를 만족시키고, 질문에 답하며, 대화를 계속 이어가려는 성향을 가집니다. 하지만 규제가 엄격한 영역에서는 에이전트가 **결정론적(deterministic)**이어야 합니다.
이를 달성하기 위해, 우리는 단순히 프롬프트를 작성하는 것이 아니라 **3계층 아키텍처 (3-Layer Architecture)**를 구축하고 있습니다.
계층 1: 기반 (기본 아웃바운드 콜 에이전트 + 시스템 프롬프트)
이것은 우리의 기준점(baseline)입니다. 이 계층에서는 Twilio, LiveKit, Murf, 그리고 LLM을 연결하고, 에이전트에게 정체성, 목소리, 그리고 핵심 컨텍스트(context)를 부여합니다. 이 계층은 듣고, 생각하고, 말하는 메커니즘을 처리하지만, 통화의 흐름을 제어하는 역할까지 신뢰할 수는 없습니다.
계층 2: 집행자 (상태 머신)
이것이 규정을 준수하는 BFSI 에이전트의 핵심 비결(secret sauce)입니다. LLM이 채무에 대해 이야기하기 전에 사용자의 신원을 확인하는 것을 기억하기를 바라는 대신, 우리는 상태 머신(state machine)을 하드코딩합니다. 대화는 다음과 같은 엄격한 단계로 나뉩니다.
- 인사 (Greeting)
- 인증 (Verification)
- 결제 논의 (Payment_Discussion)
- 결과 (Outcome)
에이전트는 인증(Verification) 상태가 완료될 때까지 결제를 논의하는 데 필요한 컨텍스트에 물리적으로 접근할 수 없도록 차단됩니다. 만약 사용자가 단계를 건너뛰려 하면, 상태 머신이 LLM을 현재 수행해야 할 필수 작업으로 다시 끌어다 놓음으로써, 예측 불가능한 AI를 엄격하고 규정을 준수하는 워크플로(workflow)로 전환합니다.
계층 3: 안전망 (가드레일 및 인간 에스컬레이션)
상태 머신(state machine)이 있더라도 현실 세계는 복잡합니다. 통화자가 화를 내거나, 파산을 언급하거나, 법적 조치를 위협할 수도 있습니다. 계층 3은 우리의 비상 브레이크 역할을 합니다. 우리는 사용자의 의도(intent)를 지속적으로 모니터링하는 의미론적 가드레일(semantic guardrails)을 구현합니다. 트리거 단어(trigger word)나 높은 스트레스 수치의 감정이 감지되면, 에이전트는 즉시 응답 생성을 중단하고 에스컬레이션 프로토콜(escalation protocol)을 실행하여 통화를 품위 있게 종료합니다.
설정 및 요구 사항
Twilio
Twilio는 글로벌 통신 네트워크를 단순한 소프트웨어 API로 전환하여 인터넷과 전화 네트워크 사이의 디지털 가교 역할을 하는 클라우드 커뮤니케이션 플랫폼입니다.
설정:
- console.twilio.com 또는
1console.twilio.com에서 계정을 생성합니다. 계정 SID, 인증 토큰(auth token) 및 전화번호를 복사하여.env파일에 붙여넣습니다. - 콘솔에서 TwiML Bins로 이동하여 빈(bin)을 생성합니다.
- 이름을 지정하고 TwiML 섹션에 다음 내용을 입력합니다:
<?xml version="1.0" encoding="UTF-8"?>
<Response>
<Dial>
...
<phone_number>를 Twilio 전화번호로, <sip_uri>를 SIP URI로 바꿉니다. 예를 들어, 전화번호가 +1234567이고 SIP URI가 sip:abc123.sip.livekit.cloud라면, TwiML 섹션은 다음과 같습니다:
<?xml version="1.0" encoding="UTF-8"?>
<Response>
<Dial>
...
1console.twilio.com에서 Products & Services → Numbers & Senders로 이동하여 전화번호를 클릭하고, Voice and emergency calling 섹션에서 Edit configuration details를 클릭합니다.- 구성 방법(configuration method)을 TwiML Bins가 포함된 것으로 선택하고, 기본 방법(primary method)을 TwiML Bins로 설정한 뒤 방금 생성한 빈을 선택합니다.
- Products & Services → Elastic SIP Trunking → Trunks로 이동하여 새로운 트렁크(trunk)를 생성합니다.
- 트렁크의 Termination 탭에서 종료 SIP URI(Termination SIP URI)를 설정하고 이를
.env파일에 붙여넣습니다. - 아래로 스크롤하여 Credential Lists로 이동해 새 리스트를 생성합니다. 사용자 이름(username)과 비밀번호(password)를 복사하여
.env파일에도 붙여넣습니다.
LiveKit
LiveKit은 WebRTC를 기반으로 구축된 스트리밍 인프라로, 오디오 버퍼링 (audio buffering)을 관리하고 네트워크 끊김 현상을 유연하게 처리합니다.
설정 (Setup):
- cloud.livekit.io에서 계정을 생성하고 프로젝트를 만듭니다.
- 프로젝트 설정에서 URL, API key, API secret 및 SIP URI를 복사하여
.env파일에 붙여넣습니다. - 콘솔에서 Telephony → SIP Trunks로 이동하여 새로운 트렁크 (trunk)를 생성합니다.
- 트렁크에 이름을 지정하고, outbound를 선택한 뒤, addresses 항목에 Twilio 트렁크의 종료 SIP URI (Termination SIP URI)를 입력합니다.
- Numbers에 Twilio 전화번호를 추가하고, Optional Settings 아래에 Twilio 트렁크에서 생성한 자격 증명 (credential) 목록의 사용자 이름 (username)과 비밀번호 (password)를 입력합니다.
JSON 에디터로 전환하면, JSON은 다음과 같은 형태가 됩니다:
{
"name": "payment reminder",
"address": "payment-reminder.pstn.twilio.com",
...
- Telephony → Dispatch Rules로 이동하여 새로운 디스패치 규칙 (dispatch rule)을 생성합니다.
- 규칙에 이름, 접두사 (prefix), 그리고 에이전트 이름 (agent name)을 지정합니다. 에이전트 이름을 반드시 기억하세요 — 이 이름은 코드에 작성된 이름과 일치해야 합니다. 그렇지 않으면 전화는 연결되지만 아무 소리도 들리지 않게 됩니다.
JSON 에디터로 전환하면, 디스패치 규칙은 다음과 같은 형태가 됩니다:
{
"sipDispatchRuleId": "SDR_abc123",
"rule": {
...
이 내용을 복사하여 프로젝트 루트 디렉토리에 dispatch-rule.json으로 저장하세요.
Speech-To-Text (STT) — Deepgram Nova-3
이 프로젝트에서는 STT (Speech-To-Text)로 Deepgram의 Nova-3를 사용합니다. Deepgram은 계정 생성 시 200달러의 무료 크레딧을 제공하며, 이는 이 프로젝트를 테스트하고 실행하기에 충분한 금액입니다. API key는 console.deepgram.com에서 받을 수 있습니다.
LLM
음성 에이전트의 중앙 추론 엔진 (reasoning engine) 역할을 할 대규모 언어 모델 (Large Language Model, LLM)이 필요합니다. 이 프로젝트는 두 가지 주요 제공업체를 지원하도록 구성되어 있습니다. 사용할 제공업체를 선택하려면 .env 파일의 LLM_PROVIDER 변수를 다음 중 하나로 설정하세요:
openai(유료 API 키 필요)gemini(테스트하기에 훌륭한 선택 — Google에서 무료 티어를 제공함)
다른 제공업체를 사용하려면, 어떤 커스텀 LLM이라도 연결할 수 있도록 코드가 구조화되어 있습니다.
Text-To-Speech (TTS) — Murf Falcon
LLM이 정확히 무엇을 말할지 결정하고 나면, 전화선을 통해 스트리밍할 수 있도록 해당 텍스트를 다시 자연스러운 인간의 음성으로 변환해야 합니다.
이 프로젝트에서는 실시간 대화형 애플리케이션을 위해 특별히 구축된, 지속적으로 가장 빠른 TTS 모델(첫 오디오 생성 시간 130ms)인 Murf Falcon을 사용합니다. Falcon은 실시간으로 생생하고 자연스러운 인간의 음성을 생성하도록 최적화되어 있습니다.
Falcon은 연속적인 청크 단위 오디오 스트리밍 (continuous chunked audio streaming)을 지원함으로써 이를 달성합니다. 즉, LLM이 첫 몇 단어를 스트리밍하는 순간, Falcon은 즉시 해당 토큰들을 오디오 패킷으로 변환하여 LiveKit 미디어 스트림으로 다시 밀어넣기 시작합니다. 이를 통해 발신자는 어색한 끊김 없이 매끄럽고 표현력이 풍부한 응답을 들을 수 있습니다.
단순한 속도를 넘어, Falcon은 인도 내 현지 데이터 거주성 (data residency)을 제공함으로써 금융 규정 준수(compliance)의 중요한 요구 사항을 해결하며, 민감한 고객 데이터가 국가 외부로 유출되지 않도록 보장합니다.
murf.ai/api로 이동하여 계정을 생성하고 API 키를 받으세요.
프로젝트 설정 (Project setup)
프로젝트 디렉토리를 생성하고 종속성을 격리하기 위해 표준 Python 가상 환경 (virtual environment)을 설정하세요:
mkdir payment-reminder-agent
cd payment-reminder-agent
python -m venv venv
...
프로젝트 루트에 requirements.txt 파일을 생성하세요:
livekit-agents>=1.0.0
livekit-plugins-deepgram>=0.7.0
livekit-plugins-openai>=0.10.0
...
그 다음 실행하세요:
pip install -r requirements.txt
고객 데이터 (Customer data)
고객 정보를 저장하는 두 가지 방법을 설정하겠습니다.
단일 고객의 경우, 프로젝트 루트에 scenario_config.json을 생성하세요:
{
"companyName": "NovaFin",
"agentName": "Asha",
...
여러 고객의 경우, 프로젝트 루트에 CSV 파일을 생성하세요:
name,phone,amount_due,due_date,account_ending,registered_mobile_last_four
Aria Sharma,+910000000001,10000,"June 21, 2026",4321,1234
Rahul Verma,+910000000002,5000,"July 1, 2026",8765,5678
...
이제 모든 준비가 완료되었으므로, 이 에이전트를 구축하는 과정으로 들어가 보겠습니다.
레이어 1: 기반 (The Foundation)
이 레이어에서는 핵심 전화 기술 (telephony)을 연결하고, 에이전트에게 엄격한 시스템 프롬프트 (system prompt)를 부여하며, 에이전트가 사용할 수 있는 도구 (tools)를 정의합니다. 로직을 전화 인프라와 완전히 분리(decoupled)하기 위해 이를 몇 개의 전용 파일로 나누어 구성할 것입니다.
상태 및 데이터 관리 (data.py)
실제 BFSI 환경에서는 감사 (auditing) 목적으로 통화 중에 정확히 어떤 일이 일어났는지 추적해야 합니다. 사용자가 신원을 확인했나요? 결제를 약속했나요? 분쟁 (dispute)이 제기되었나요?
사후에 방대한 텍스트 전사 (transcript)를 파싱하는 대신, 구조화된 데이터 로깅 (structured data logging)을 사용합니다. data.py 파일을 생성하세요. 이 파일은 환경 변수를 처리하고, 설정을 로드하며, 통화 상태를 엄격하게 추적하기 위한 OutcomeLog 데이터 클래스 (dataclass)를 정의합니다.
import contextvars
import json
import logging
...
에이전트에게 도구 부여하기 (tools.py)
LLM은 대화에는 뛰어나지만, 기본적으로 무언가를 직접 '수행'할 수는 없습니다. 대화와 행동 사이의 간극을 메우기 위해 함수 호출 (function calling)을 사용합니다.
사용자가 "네, 제 계좌 끝 번호는 4321이고 전화번호 끝 번호는 1234입니다"라고 말할 때, 우리는 LLM이 그것이 맞는지 단순히 추측하기를 원하지 않습니다. 대신 Python 함수를 호출하여 우리의 데이터와 대조 확인하기를 원합니다.
tools.py를 생성하고 에이전트가 수행할 수 있는 작업들을 정의하세요:
import logging
from typing import Annotated
from livekit.agents import RunContext, function_tool, get_job_context
...
두뇌 (prompt.py)
이제 시스템 프롬프트 (system prompt)를 정의합니다. 레이어 1 (Layer 1)에서 이것은 우리의 일차적인 방어 기제 (defense mechanism)입니다. 지침이 얼마나 명시적이고 엄격한지 주목하십시오. 우리는 단순히 무엇을 해야 하는지만 말하는 것이 아니라, 무엇을 해서는 안 되는지를 명시적으로 명령합니다 (예: "신원이 확인될 때까지 고객의 성함 전체, 계좌 번호, 납부 금액 또는 연체 상태를 공개하지 마십시오").
def build_payment_prompt(config: dict) -> str:
return (
f"당신은 {config['companyName']}의 자동 결제 지원 음성 에이전트인 {config['agentName']}입니다.\n\n"
...
모두 연결하기 (agent.py)
AI 자동 생성 콘텐츠
본 콘텐츠는 Dev.to AI tag의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기
