Amazon Connect 음성 챗봇 구현기: LEX (CODEHOOK) 부터 Bedrock 연동까지
✨ Intro
목표
Amazon Connect를 기반으로 음성 챗봇 시스템을 구축하였다. 고객의 발화를 LEX로 처리하고, 의도를 인식하지 못한 fallback 상황에서는 Bedrock을 활용한 LLM을 통해 자연스러운 대화를 이어가는 것이 주 목적이었다.
기술 스택
Amazon Connect (음성 기반 콜센터 플랫폼)
Amazon Lex V2 (Intent 기반 NLU)
Amazon Bedrock (LLM 기반 Open Domain 질의 응답)
Lambda (Lex CodeHook 처리)
Claude 3 Haiku (LLM 모델)
🛠️ 전체 아키텍처 개요
음성 입력 → Connect Contact Flow → Lex → Lambda (Bedrock 호출) → 응답 생성 → Connect로 응답 전달
🎯 Contact Flow 구성
핵심 블록 흐름
Set Voice
Play Prompt (웰컴 메시지)
Get Customer Input (barge-in 허용)
Invoke Lex Bot
Resume & Redirect (Lambda 응답 기반 재흐름)
Play Prompt (LLM 응답 출력)
💡 포인트
FallbackIntent에서 Lambda를 호출
Lambda에서 Claude 호출 후 응답 생성
🧩 Lambda Code
import json
import boto3
# 서울 리전 Bedrock 클라이언트
bedrock = boto3.client(“bedrock-runtime”, region_name=“ap-northeast-2”)
def lambda_handler(event, context):
print(“[INFO] Full Lex Event:\n“, json.dumps(event, indent=2, ensure_ascii=False))
# 1. 사용자 발화 추출 (V2 구조 대응 포함)
user_input = (
event.get(“inputTranscript”) or
event.get(“sessionState”, {}).get(“intent”, {}).get(“slots”, {}).get(“utterance”, {}).get(“value”, {}).get(“interpretedValue”) or
“”
).strip()
print(f“[DEBUG] userInput = {user_input}“)
# 2. 발화 없으면 기본 응답
if not user_input:
return build_response(
message=“죄송합니다. 말씀을 정확히 인식하지 못했어요. 다시 말씀해 주세요.”,
intent_name=event.get(“sessionState”, {}).get(“intent”, {}).get(“name”, “FallbackIntent”)
)
# 3. Bedrock용 메시지 구성
messages = [{ “role”: “user”, “content”: user_input }]
payload = {
“anthropic_version”: “bedrock-2023-05-31”,
“max_tokens”: 512,
“temperature”: 0.7,
“messages”: messages
}
# 4. Bedrock 호출
try:
response = bedrock.invoke_model(
modelId=“anthropic.claude-3-haiku-20240307-v1:0”,
body=json.dumps(payload),
contentType=“application/json”,
accept=“application/json”
)
result = json.loads(response[“body”].read())
message = result.get(“content”, [{}])[0].get(“text”, “”)
except Exception as e:
print(f“[ERROR] Bedrock 호출 실패: {str(e)}“)
message = “죄송합니다. 지금은 답변할 수 없어요. 잠시 후 다시 시도해 주세요.”
# 5. 응답 반환
return build_response(
message=message,
intent_name=event.get(“sessionState”, {}).get(“intent”, {}).get(“name”, “FallbackIntent”)
)
# ✅ Lex 응답 포맷 생성 함수 (대화 유지)
def build_response(message: str, intent_name: str):
return {
“sessionState”: {
“dialogAction”: {
“type”: “ElicitIntent” # ← 핵심 변경: 대화 유지
},
“intent”: {
“name”: intent_name,
“state”: “Fulfilled”
}
},
“messages”: [
{
“contentType”: “PlainText”,
“content”: message
}
]
}
🧪 테스트 시나리오
“여기는 어디야?” → LEX 인식 실패 → Claude가 “여기는 AI 음성봇입니다” 등 자연어 응답
“배송 조회하고 싶어” → LEX가 TrackOrderIntent 인식 → 일반 시나리오 흐름