본 콘텐츠의 이미지 및 내용은 AI로 생성되었습니다.
본 콘텐츠의 이미지 및 내용을 무단으로 복제, 배포, 수정하여 사용할 경우 저작권법에 의해 법적 제재를 받을 수 있습니다.
이미지 로딩 중...
AI Generated
2025. 12. 18. · 8 Views
예약 관리 AI 에이전트 실전 구축 가이드
AWS Bedrock Agent를 활용하여 실시간 예약 관리 시스템을 구축하는 방법을 단계별로 배워봅니다. Lambda 함수, Action Groups, 에지 케이스 처리까지 실무에 필요한 모든 내용을 다룹니다.
목차
- 프로젝트_요구사항
- Action_Groups_설계
- Lambda_함수_개발
- 에이전트_생성_및_설정
- 예약 취소 - 예약 번호로 예약 삭제
- API Schema를 S3에서 가져오기
- 대화_흐름_테스트
- 예약 변경 기능도 있으면 좋겠다
- 에지_케이스_처리
1. 프로젝트 요구사항
어느 날 김개발 씨는 회사에서 흥미로운 프로젝트를 맡게 되었습니다. "고객이 자연어로 예약을 하고, 조회하고, 취소할 수 있는 AI 챗봇을 만들어보세요." 처음 듣는 순간 막막했지만, 선배 박시니어 씨가 귓속말로 "AWS Bedrock Agent를 써보세요"라고 조언했습니다.
예약 관리 AI 에이전트는 사용자의 자연어 질문을 이해하고 적절한 액션을 수행하는 지능형 시스템입니다. 마치 실제 예약 담당자가 고객의 요청을 듣고 처리하는 것처럼, AI가 예약 생성, 조회, 취소를 자동으로 수행합니다.
이를 통해 24시간 무인 예약 서비스를 구축할 수 있습니다.
다음 코드를 살펴봅시다.
# 프로젝트 요구사항 정의
requirements = {
"기능": [
"예약 생성 - 날짜, 시간, 고객 정보 입력",
"예약 조회 - 예약 ID로 상세 정보 확인",
"예약 취소 - 예약 ID로 삭제"
],
"기술스택": {
"AI모델": "AWS Bedrock (Claude)",
"백엔드": "AWS Lambda",
"통합": "Bedrock Agent Action Groups"
},
"사용자경험": "자연어 대화형 인터페이스"
}
김개발 씨는 프로젝트 미팅에 참석했습니다. PM이 말했습니다.
"고객들이 '내일 오후 3시에 예약하고 싶어요'라고 말하면 자동으로 처리되어야 해요." 회의가 끝나고 책상으로 돌아온 김개발 씨는 노트북을 펼쳤습니다. 어디서부터 시작해야 할까요?
박시니어 씨가 다가와 화이트보드에 그림을 그리기 시작했습니다. 요구사항 분석의 중요성 프로젝트를 시작하기 전에 가장 중요한 것은 정확한 요구사항 파악입니다.
마치 집을 지을 때 설계도를 먼저 그리는 것처럼, AI 에이전트 개발도 명확한 설계가 필요합니다. 우리가 만들 시스템은 세 가지 핵심 기능을 가져야 합니다.
첫째, 예약 생성 기능입니다. 고객이 "다음 주 월요일 오후 2시에 예약하고 싶어요"라고 말하면 시스템이 날짜를 계산하고 예약을 저장해야 합니다.
둘째, 예약 조회 기능입니다. "내 예약 확인해줘"라고 물으면 고객의 예약 정보를 찾아서 보여줘야 합니다.
셋째, 예약 취소 기능입니다. "예약 12345 취소할게요"라고 하면 해당 예약을 삭제해야 합니다.
왜 AI 에이전트인가 전통적인 방식이라면 어땠을까요? 웹 폼에 날짜 선택, 시간 선택, 이름 입력 칸을 만들어야 했을 겁니다.
고객은 여러 단계를 거쳐야 하고, 모바일에서는 더 불편했을 것입니다. 하지만 AI 에이전트를 사용하면 완전히 달라집니다.
고객은 그냥 말하기만 하면 됩니다. "내일 3시에 예약"이라고 짧게 말해도 AI가 문맥을 이해합니다.
날짜가 애매하면 "어떤 날짜를 원하세요?"라고 되묻습니다. 기술 스택 선정 박시니어 씨가 설명했습니다.
"AWS Bedrock Agent는 AI 모델과 백엔드 함수를 연결해주는 다리 역할을 해요. 사용자 입력을 이해하고, 적절한 Lambda 함수를 호출하고, 결과를 자연스러운 문장으로 돌려줍니다." 김개발 씨는 고개를 끄덕이며 메모했습니다.
Bedrock은 AI 모델을 제공하고, Lambda는 실제 비즈니스 로직을 실행하고, Action Groups는 이 둘을 연결합니다. 프로젝트 범위 설정 모든 프로젝트는 범위를 명확히 해야 합니다.
김개발 씨는 초기 버전에서는 세 가지 기능만 구현하기로 했습니다. 나중에 알림 기능, 결제 기능은 추가할 수 있습니다.
중요한 것은 MVP(Minimum Viable Product)를 빨리 만들어 테스트하는 것입니다. 완벽한 시스템을 한 번에 만들려다가 실패하는 것보다, 작게 시작해서 점진적으로 개선하는 게 훨씬 효과적입니다.
사용자 시나리오 작성 김개발 씨는 실제 대화 시나리오를 작성해봤습니다. 사용자: "내일 오후 3시에 예약하고 싶어요" AI: "네, 2025년 12월 19일 15시에 예약하시겠습니까?
성함과 연락처를 알려주세요" 사용자: "김철수, 010-1234-5678" AI: "예약이 완료되었습니다. 예약 번호는 12345입니다" 이런 시나리오를 미리 그려보면 어떤 데이터를 수집해야 하는지, 어떤 검증이 필요한지 명확해집니다.
데이터베이스 설계 예약 데이터를 어떻게 저장할까요? 일단 간단하게 시작하기로 했습니다.
예약 ID, 고객 이름, 전화번호, 예약 날짜, 예약 시간, 생성 시간 정도면 충분합니다. 나중에 복잡해지면 DynamoDB나 RDS를 사용하겠지만, 처음에는 Lambda 함수 내부의 딕셔너리로 메모리에 저장하기로 했습니다.
프로토타입에서는 간단함이 미덕입니다. 성공 기준 정의 프로젝트가 성공했는지 어떻게 알 수 있을까요?
김개발 씨는 세 가지 기준을 정했습니다. 첫째, 사용자 입력의 90% 이상을 정확히 이해할 것.
둘째, 평균 응답 시간이 3초 이내일 것. 셋째, 에러율이 5% 미만일 것.
명확한 기준이 있으면 개발 중에도 방향을 잃지 않습니다. 테스트할 때도 객관적으로 평가할 수 있습니다.
팀 역할 분담 실제 프로젝트라면 팀원과 역할을 나눠야 합니다. AI 모델 튜닝 담당, Lambda 함수 개발 담당, 프론트엔드 개발 담당 등으로 나눌 수 있습니다.
혼자 개발하더라도 작업을 단계별로 나누면 효율적입니다. 다음 단계로 요구사항 정의가 끝났으니 이제 본격적으로 설계를 시작할 차례입니다.
박시니어 씨가 말했습니다. "요구사항이 명확하니까 이제 Action Groups를 설계해봅시다." 김개발 씨는 자신감이 생겼습니다.
처음에는 막막했지만, 단계별로 정리하니 할 만해 보였습니다.
실전 팁
💡 - 프로젝트 시작 전 반드시 요구사항을 문서화하세요
- 사용자 시나리오를 3-5개 작성하면 숨은 요구사항을 발견할 수 있습니다
- MVP를 빨리 만들어 피드백을 받는 것이 완벽한 계획보다 중요합니다
2. Action Groups 설계
요구사항 정리가 끝나자 박시니어 씨가 새로운 파일을 열었습니다. "이제 가장 중요한 부분, Action Groups를 설계해야 해요.
이게 AI와 백엔드를 연결하는 핵심입니다." 김개발 씨는 노트를 준비하고 집중했습니다.
Action Groups는 AI 에이전트가 수행할 수 있는 행동의 집합입니다. 마치 식당 메뉴판처럼, AI가 할 수 있는 일들을 명확하게 정의해둡니다.
각 액션은 이름, 설명, 필요한 파라미터, 응답 형식을 가지며, 이를 통해 AI는 사용자 요청을 적절한 함수로 연결합니다.
다음 코드를 살펴봅시다.
# Action Groups OpenAPI 스키마 정의
action_schema = {
"createReservation": {
"description": "새로운 예약을 생성합니다",
"parameters": {
"customerName": {"type": "string", "required": True},
"phoneNumber": {"type": "string", "required": True},
"date": {"type": "string", "format": "YYYY-MM-DD"},
"time": {"type": "string", "format": "HH:MM"}
}
},
"getReservation": {
"description": "예약 ID로 예약 정보를 조회합니다",
"parameters": {
"reservationId": {"type": "string", "required": True}
}
}
}
박시니어 씨가 화이트보드에 그림을 그렸습니다. "사용자가 말을 하면, AI는 먼저 의도를 파악해요.
그 다음 어떤 액션을 실행할지 결정하죠. 이때 필요한 게 Action Groups입니다." 김개발 씨가 물었습니다.
"그럼 우리가 함수 이름이랑 파라미터를 정의해주면, AI가 알아서 맞는 함수를 호출한다는 건가요?" "정확합니다!" 박시니어 씨가 엄지를 들었습니다. Action Groups의 역할 Action Groups는 크게 세 가지 역할을 합니다.
첫째, AI에게 가능한 행동 목록을 알려줍니다. 둘째, 각 행동에 필요한 정보가 무엇인지 명시합니다.
셋째, Lambda 함수와 AI를 연결하는 계약서 역할을 합니다. 쉽게 비유하자면, 레스토랑 주방과 홀을 생각해보세요.
손님이 "스테이크 미디엄으로 하나"라고 주문하면, 웨이터는 주방에 정확한 주문서를 전달합니다. Action Groups는 바로 이 주문서 양식과 같습니다.
OpenAPI 스키마 작성 AWS Bedrock Agent는 OpenAPI 3.0 형식으로 Action Groups를 정의합니다. 이것은 업계 표준이라 익숙한 개발자도 많을 겁니다.
김개발 씨는 첫 번째 액션부터 작성하기 시작했습니다. "createReservation"이라는 이름으로 만들고, 설명을 추가했습니다.
"새로운 예약을 생성합니다." 간단하지만 명확합니다. 파라미터 정의의 중요성 다음은 파라미터입니다.
예약을 만들려면 무엇이 필요할까요? 고객 이름, 전화번호, 날짜, 시간이 필수입니다.
각각의 타입도 정확히 지정해야 합니다. 여기서 중요한 점이 있습니다.
required 필드를 제대로 설정해야 합니다. 필수 정보가 빠지면 AI가 사용자에게 되물어봅니다.
"성함을 알려주시겠어요?" 이런 식으로요. 박시니어 씨가 덧붙였습니다.
"날짜 형식도 명확히 해야 해요. YYYY-MM-DD로 통일하면 나중에 파싱할 때 편합니다.
시간도 24시간 형식인 HH:MM을 쓰세요." 액션별 설계 전략 세 가지 액션을 하나씩 설계해봅시다. createReservation은 가장 복잡합니다.
입력값이 많고, 검증도 필요합니다. 날짜가 과거면 안 되고, 영업 시간 내여야 하고, 중복 예약이 없어야 합니다.
getReservation은 상대적으로 간단합니다. 예약 ID만 받아서 정보를 반환하면 됩니다.
하지만 존재하지 않는 ID에 대한 처리가 중요합니다. cancelReservation도 마찬가지입니다.
예약 ID를 받아서 삭제하되, 이미 취소된 예약이나 지나간 예약은 어떻게 처리할지 정해야 합니다. 에러 응답 설계 성공 케이스만 생각하면 안 됩니다.
실패 케이스가 더 중요합니다. 김개발 씨는 각 액션마다 에러 코드를 정의했습니다.
400 Bad Request - 잘못된 입력값 404 Not Found - 예약을 찾을 수 없음 409 Conflict - 중복 예약 시도 500 Internal Server Error - 서버 오류 이렇게 표준 HTTP 상태 코드를 사용하면 프론트엔드 개발자도 이해하기 쉽습니다. 응답 형식 통일 모든 액션의 응답은 일관된 형식을 따라야 합니다.
김개발 씨는 이렇게 정했습니다. 성공 시: {"success": true, "data": {...}, "message": "예약이 완료되었습니다"} 실패 시: {"success": false, "error": "...", "message": "예약에 실패했습니다"} 이렇게 하면 AI가 응답을 파싱하기도 쉽고, 사용자에게 보여줄 메시지도 명확해집니다.
실제 작성 과정 김개발 씨는 VS Code를 열고 OpenAPI YAML 파일을 만들기 시작했습니다. 처음에는 오타도 나고, 인덴트도 틀렸지만, 온라인 검증 도구로 확인하며 수정했습니다.
박시니어 씨가 코드 리뷰를 해줬습니다. "여기 description이 너무 짧아요.
AI가 이해할 수 있도록 더 자세히 써주세요." 좋은 피드백이었습니다. 버전 관리 Action Groups도 코드처럼 버전 관리가 필요합니다.
나중에 기능을 추가하거나 파라미터를 변경할 때, 이전 버전과의 호환성을 고려해야 합니다. 김개발 씨는 파일명을 action-groups-v1.yaml로 저장했습니다.
앞으로 변경사항이 생기면 v2, v3로 관리할 계획입니다. 테스트 계획 설계가 끝났으니 테스트 계획도 세워야 합니다.
각 액션마다 정상 케이스 3개, 에러 케이스 3개씩 테스트 시나리오를 작성했습니다. "설계가 탄탄하면 개발은 순조롭습니다." 박시니어 씨의 말이 맞았습니다.
김개발 씨는 뿌듯한 마음으로 다음 단계로 넘어갔습니다.
실전 팁
💡 - OpenAPI 스키마는 온라인 에디터(Swagger Editor)로 검증하며 작성하세요
- 파라미터 설명은 AI가 읽는다고 생각하고 명확하게 작성하세요
- 에러 케이스를 먼저 정의하면 더 견고한 설계가 됩니다
3. Lambda 함수 개발
설계도가 완성되자 이제 진짜 코드를 작성할 차례입니다. 김개발 씨는 AWS Lambda 콘솔을 열었습니다.
"Python으로 작성하면 되나요?" 박시니어 씨가 고개를 끄덕였습니다. "네, Python이 간결하고 AWS SDK 지원도 좋아요."
Lambda 함수는 서버리스 환경에서 실행되는 백엔드 로직입니다. 마치 필요할 때만 작동하는 자동 응답 시스템처럼, 요청이 오면 즉시 실행되고 끝나면 자동으로 종료됩니다.
우리는 예약 생성, 조회, 취소 로직을 Lambda 함수로 구현하여 AI 에이전트와 연결합니다.
다음 코드를 살펴봅시다.
import json
from datetime import datetime
# 메모리 내 예약 저장소 (프로토타입용)
reservations = {}
reservation_counter = 1000
def lambda_handler(event, context):
# Bedrock Agent가 전달한 액션 정보 파싱
action = event.get('actionGroup')
api_path = event.get('apiPath')
parameters = event.get('parameters', [])
# 파라미터를 딕셔너리로 변환
params = {p['name']: p['value'] for p in parameters}
# 액션에 따라 적절한 함수 호출
if api_path == '/createReservation':
return create_reservation(params)
elif api_path == '/getReservation':
return get_reservation(params)
elif api_path == '/cancelReservation':
return cancel_reservation(params)
김개발 씨는 새 Lambda 함수를 생성했습니다. 이름은 "ReservationAgentHandler"로 지었습니다.
런타임은 Python 3.11을 선택했습니다. Lambda 기본 구조 이해 Lambda 함수의 진입점은 lambda_handler 함수입니다.
이 함수는 두 개의 인자를 받습니다. event에는 요청 정보가 담겨있고, context에는 실행 환경 정보가 있습니다.
박시니어 씨가 설명했습니다. "Bedrock Agent가 Lambda를 호출할 때는 특별한 형식으로 데이터를 보내요.
어떤 액션인지, 파라미터는 뭔지 다 들어있죠." 이벤트 파싱 첫 번째 할 일은 이벤트 데이터를 파싱하는 것입니다. event는 JSON 형태로 들어오는데, 여기서 필요한 정보를 추출해야 합니다.
김개발 씨는 코드를 작성했습니다. action = event.get('actionGroup')로 어떤 액션 그룹인지 확인하고, api_path로 구체적인 API를 식별합니다.
파라미터는 리스트 형태로 오기 때문에 딕셔너리로 변환하면 사용하기 편합니다. 라우팅 로직 다음은 라우팅입니다.
API 경로에 따라 적절한 함수를 호출해야 합니다. 간단하게 if-elif 문으로 처리할 수 있습니다.
실무에서는 더 복잡할 수 있습니다. 그럴 때는 딕셔너리에 함수를 매핑해두고 routes[api_path](params) 식으로 호출하면 깔끔합니다.
하지만 지금은 단순하게 가기로 했습니다. 예약 생성 함수 가장 복잡한 create_reservation 함수부터 작성했습니다.
python def create_reservation(params): global reservation_counter # 파라미터 검증 required = ['customerName', 'phoneNumber', 'date', 'time'] for field in required: if field not in params: return error_response(f"{field}는 필수입니다") # 날짜 검증 try: reservation_date = datetime.strptime(params['date'], '%Y-%m-%d') if reservation_date < datetime.now(): return error_response("과거 날짜로 예약할 수 없습니다") except ValueError: return error_response("날짜 형식이 올바르지 않습니다") # 예약 생성 reservation_id = str(reservation_counter) reservation_counter += 1 reservations[reservation_id] = { 'id': reservation_id, 'customerName': params['customerName'], 'phoneNumber': params['phoneNumber'], 'date': params['date'], 'time': params['time'], 'createdAt': datetime.now().isoformat() } return success_response({ 'reservationId': reservation_id, 'message': f"{params['customerName']}님의 예약이 완료되었습니다" }) 박시니어 씨가 코드를 보더니 고개를 끄덕였습니다. "좋아요.
검증 로직이 탄탄하네요." 예약 조회 함수 조회 함수는 상대적으로 간단합니다. python def get_reservation(params): reservation_id = params.get('reservationId') if not reservation_id: return error_response("예약 ID가 필요합니다") if reservation_id not in reservations: return error_response("예약을 찾을 수 없습니다") return success_response(reservations[reservation_id]) 하지만 간단하다고 대충 만들면 안 됩니다.
존재하지 않는 ID에 대한 처리가 중요합니다. 사용자가 잘못된 번호를 입력했을 때 친절한 메시지를 보여줘야 합니다.
예약 취소 함수 취소 함수도 비슷한 패턴입니다. python def cancel_reservation(params): reservation_id = params.get('reservationId') if not reservation_id: return error_response("예약 ID가 필요합니다") if reservation_id not in reservations: return error_response("예약을 찾을 수 없습니다") # 예약 삭제 deleted = reservations.pop(reservation_id) return success_response({ 'message': f"{deleted['customerName']}님의 예약이 취소되었습니다" }) 응답 헬퍼 함수 코드 중복을 줄이기 위해 응답 생성 함수를 만들었습니다.
python def success_response(data): return { 'messageVersion': '1.0', 'response': { 'actionGroup': 'ReservationActions', 'apiPath': event.get('apiPath'), 'httpMethod': event.get('httpMethod'), 'httpStatusCode': 200, 'responseBody': { 'application/json': { 'body': json.dumps({'success': True, 'data': data}) } } } } def error_response(message): return { 'messageVersion': '1.0', 'response': { 'actionGroup': 'ReservationActions', 'httpStatusCode': 400, 'responseBody': { 'application/json': { 'body': json.dumps({'success': False, 'error': message}) } } } } 이렇게 하면 매번 같은 구조를 반복해서 쓸 필요가 없습니다. 메모리 저장소의 한계 김개발 씨가 물었습니다.
"근데 메모리에 저장하면 Lambda가 종료되면 다 사라지지 않나요?" 박시니어 씨가 웃으며 대답했습니다. "맞아요.
실제로는 DynamoDB나 RDS를 써야죠. 하지만 프로토타입에서는 이게 더 빠르고 간단해요." 나중에 실제 데이터베이스로 바꾸는 것은 어렵지 않습니다.
함수 내부만 수정하면 되니까요. 인터페이스는 그대로 유지됩니다.
로깅과 디버깅 개발하면서 가장 답답한 게 뭘까요? 에러가 나는데 원인을 모를 때입니다.
그래서 로깅이 중요합니다. ```python import logging logger = logging.getLogger() logger.setLevel(logging.INFO) def lambda_handler(event, context): logger.info(f"Received event: {json.dumps(event)}") # ...
나머지 코드 ``` CloudWatch Logs에 이벤트 정보가 남으니 나중에 문제가 생기면 확인할 수 있습니다. 테스트 이벤트 작성 AWS Lambda 콘솔에서는 테스트 이벤트를 만들 수 있습니다.
김개발 씨는 각 API마다 샘플 이벤트를 만들어서 실행해봤습니다. json { "actionGroup": "ReservationActions", "apiPath": "/createReservation", "httpMethod": "POST", "parameters": [ {"name": "customerName", "value": "김철수"}, {"name": "phoneNumber", "value": "010-1234-5678"}, {"name": "date", "value": "2025-12-25"}, {"name": "time", "value": "14:00"} ] } 테스트 버튼을 누르니 성공 응답이 돌아왔습니다!
김개발 씨는 환호했습니다. IAM 권한 설정 Lambda 함수가 제대로 동작하려면 권한 설정이 필요합니다.
다행히 예약 관리는 외부 서비스 호출이 없어서 기본 권한으로 충분했습니다. 나중에 DynamoDB를 쓴다면 읽기/쓰기 권한을 추가해야겠죠.
배포와 버전 관리 코드를 저장하면 자동으로 배포됩니다. 하지만 중요한 변경사항이 있을 때는 버전을 발행하는 게 좋습니다.
잘못되면 이전 버전으로 롤백할 수 있으니까요. 김개발 씨는 첫 버전을 "v1-initial"이라고 태그했습니다.
앞으로의 여정이 기대됩니다.
실전 팁
💡 - 로깅은 개발 초기부터 넣어두세요. 나중에 디버깅이 훨씬 쉽습니다
- 에러 메시지는 사용자 친화적으로 작성하세요
- 프로토타입은 간단하게, 실제 배포는 견고하게 만드세요
4. 에이전트 생성 및 설정
Lambda 함수가 완성되자 박시니어 씨가 말했습니다. "이제 마지막 퍼즐 조각이에요.
Bedrock Agent를 만들고 Lambda와 연결해야 합니다." 김개발 씨는 AWS Bedrock 콘솔로 이동했습니다.
Bedrock Agent는 AI 모델과 백엔드 함수를 연결하는 오케스트레이터입니다. 마치 오케스트라 지휘자가 각 악기를 조율하듯이, 에이전트는 사용자 입력을 이해하고, 적절한 액션을 선택하고, Lambda 함수를 호출하고, 결과를 자연스러운 대화로 변환합니다.
다음 코드를 살펴봅시다.
# Bedrock Agent 설정 (AWS 콘솔에서 진행)
agent_config = {
"agentName": "ReservationAssistant",
"foundationModel": "anthropic.claude-3-sonnet-20240229-v1:0",
"instruction": """
당신은 친절한 예약 도우미입니다.
고객의 예약 요청을 받아 처리합니다.
주요 기능:
3. 예약 취소 - 예약 번호로 예약 삭제
AWS Bedrock 콘솔에 접속한 김개발 씨는 "Agents" 메뉴를 클릭했습니다. "Create Agent" 버튼이 보였습니다.
에이전트 기본 정보 설정 첫 화면에서 에이전트 이름을 입력합니다. "ReservationAssistant"라고 지었습니다.
설명도 추가했습니다. "고객 예약을 관리하는 AI 어시스턴트" 다음은 가장 중요한 선택, Foundation Model입니다.
여러 모델이 나열되어 있었습니다. Claude 3 Sonnet, Claude 3 Haiku, Claude 3 Opus...
박시니어 씨가 조언했습니다. "Sonnet이 가성비가 좋아요.
빠르고 정확하죠. Opus는 더 강력하지만 비싸고, Haiku는 저렴하지만 간단한 작업에 적합해요." 김개발 씨는 Sonnet을 선택했습니다.
anthropic.claude-3-sonnet-20240229-v1:0 지시사항 작성 다음 단계에서는 Instruction을 작성합니다. 이것은 AI의 정체성과 행동 방식을 정의하는 핵심입니다.
김개발 씨는 신중하게 작성했습니다. "당신은 친절한 예약 도우미입니다.
고객의 예약 요청을 받아 처리합니다." 이렇게 시작하면 AI가 자신의 역할을 명확히 이해합니다. 다음은 기능 설명입니다.
"주요 기능: 1. 예약 생성 2.
예약 조회 3. 예약 취소" 간단하지만 명확합니다.
마지막으로 행동 지침을 추가했습니다. "항상 정중하고 명확하게 응답하세요.
필수 정보가 부족하면 친절하게 요청하세요." 박시니어 씨가 엄지를 들었습니다. "좋아요.
이렇게 하면 AI가 빈 정보를 채우려고 스스로 노력할 거예요." Action Group 연결 이제 핵심 단계입니다. Action Group을 추가해야 합니다.
"Add Action Group" 버튼을 클릭했습니다. 이름은 "ReservationActions"로 지었습니다.
그 다음 두 가지 선택지가 나왔습니다.
2. API Schema를 S3에서 가져오기
실전 팁
💡 - Instruction은 구체적으로 작성하세요. AI의 행동 방식이 크게 달라집니다
- Lambda 권한 설정을 잊지 마세요. 가장 흔한 실수입니다
- 별칭을 사용하면 무중단 배포와 A/B 테스트가 가능합니다
5. 대화 흐름 테스트
에이전트가 완성되었지만 박시니어 씨는 말했습니다. "진짜 테스트는 지금부터예요.
실제 사용자처럼 대화해보면서 문제를 찾아야 합니다." 김개발 씨는 테스트 시나리오 목록을 꺼냈습니다.
대화 흐름 테스트는 AI 에이전트가 다양한 사용자 입력에 얼마나 자연스럽게 반응하는지 검증하는 과정입니다. 마치 연극 리허설처럼, 여러 시나리오를 반복해서 연습하며 문제를 발견하고 개선합니다.
완벽한 경로뿐 아니라 예외 상황, 애매한 입력, 잘못된 정보까지 모두 테스트해야 합니다.
다음 코드를 살펴봅시다.
# 테스트 시나리오 정의
test_scenarios = [
{
"name": "정상_예약_플로우",
"conversations": [
{"user": "예약하고 싶어요", "expected": "예약 정보 요청"},
{"user": "김철수, 010-1234-5678, 내일 오후 3시", "expected": "예약 완료 확인"},
{"user": "예약 확인해줘", "expected": "예약 번호 요청"},
{"user": "1000번", "expected": "예약 상세 정보 제공"}
]
},
{
"name": "정보_누락_시나리오",
"conversations": [
{"user": "예약할게요", "expected": "필수 정보 요청"},
{"user": "김철수", "expected": "나머지 정보 요청"},
{"user": "010-1234-5678", "expected": "날짜 시간 요청"}
]
}
]
김개발 씨는 노트북에 테스트 케이스를 정리했습니다. 단순히 작동하는 것만으로는 부족합니다.
실제 사용자가 만족할 수 있는 수준이어야 합니다. 해피 패스 테스트 먼저 가장 이상적인 시나리오부터 시작했습니다.
모든 정보를 한 번에 제공하는 경우입니다. 김개발 씨: "내일 오후 2시에 예약하고 싶어요.
이름은 김철수이고 번호는 010-1234-5678입니다." AI: "김철수님, 2025년 12월 19일 14시로 예약을 도와드리겠습니다. 예약을 진행할까요?" 김개발 씨: "네" AI: "예약이 완료되었습니다.
예약 번호는 1001입니다. 예약 시간 10분 전에 연락드리겠습니다." 완벽합니다!
한 번의 대화로 예약이 완료되었습니다. 정보 누락 시나리오 하지만 실제 사용자는 이렇게 친절하지 않습니다.
정보를 조금씩만 주는 경우가 많죠. 김개발 씨: "예약할게요" AI: "네, 예약을 도와드리겠습니다.
성함을 알려주시겠어요?" 김개발 씨: "김철수" AI: "감사합니다, 김철수님. 연락처를 알려주시겠어요?" 김개발 씨: "010-1234-5678" AI: "좋습니다.
원하시는 날짜와 시간을 말씀해주세요." 김개발 씨: "내일 3시" AI: "2025년 12월 19일 15시로 예약하시겠습니까?" 박시니어 씨가 감탄했습니다. "AI가 정보를 하나씩 수집하네요.
자연스러워요." 애매한 표현 처리 다음은 까다로운 테스트입니다. 애매한 시간 표현을 이해할 수 있을까요?
김개발 씨: "다음 주 월요일 오전에 예약하고 싶어요" AI: "다음 주 월요일은 2025년 12월 23일입니다. 오전 몇 시를 원하시나요?" 김개발 씨: "10시쯤" AI: "10시 00분으로 예약하시겠습니까?
아니면 다른 시간을 원하시나요?" 대단합니다! "오전"이라는 애매한 표현과 "10시쯤"이라는 불확실한 표현을 제대로 처리했습니다.
예약 조회 플로우 예약을 만들었으니 조회도 테스트해야겠죠. 김개발 씨: "내 예약 확인하고 싶어요" AI: "예약 번호를 알려주시겠어요?" 김개발 씨: "1001번" AI: "김철수님의 예약 정보입니다.
날짜: 2025년 12월 19일, 시간: 14시, 연락처: 010-1234-5678" 딱 필요한 정보만 간결하게 보여줍니다. 좋습니다.
잘못된 예약 번호 존재하지 않는 예약 번호를 입력하면 어떻게 될까요? 김개발 씨: "9999번 예약 확인해줘" AI: "죄송합니다.
예약 번호 9999를 찾을 수 없습니다. 예약 번호를 다시 확인해주시겠어요?" 에러 메시지도 친절합니다.
사용자를 탓하지 않고 확인을 요청합니다. 예약 취소 플로우 취소도 테스트해봅시다.
김개발 씨: "예약 취소하고 싶어요" AI: "어떤 예약을 취소하시겠어요? 예약 번호를 알려주세요." 김개발 씨: "1001번" AI: "김철수님의 2025년 12월 19일 14시 예약을 취소하시겠습니까?" 김개발 씨: "네" AI: "예약이 취소되었습니다.
다시 예약이 필요하시면 언제든 말씀해주세요." 확인 절차를 거치는 것이 좋습니다. 실수로 취소하는 것을 방지할 수 있습니다.
동시에 여러 요청 혼란스러운 상황을 만들어봅시다. 김개발 씨: "예약하고 싶은데 근데 기존 예약도 확인하고 싶어요" AI: "먼저 기존 예약을 확인해드릴까요, 아니면 새 예약을 진행할까요?" AI가 명확하게 선택을 요구합니다.
두 가지를 동시에 처리하려다 혼란스러워지는 것보다 훨씬 낫습니다. 문맥 이해 테스트 이전 대화를 기억하는지 확인해봅시다.
김개발 씨: "예약할게요" AI: "성함을 알려주세요" 김개발 씨: "김철수" AI: "연락처를 알려주세요" 김개발 씨: "아 잠깐, 날짜 먼저 확인하고 싶어요. 내일 3시 가능해요?" AI: "네, 2025년 12월 19일 15시는 예약 가능합니다.
김철수님, 연락처를 알려주시면 예약을 완료하겠습니다." AI가 이름을 기억하고 있습니다! 문맥을 유지하면서 유연하게 대응합니다.
성능 측정 박시니어 씨가 스톱워치를 꺼냈습니다. "응답 시간도 체크해봅시다." 평균적으로 사용자 입력 후 2-3초 내에 응답이 왔습니다.
복잡한 쿼리는 4-5초 걸렸지만 허용 범위 내입니다. 사용자 경험 평가 기술적으로는 완벽하지만, 사용자 경험은 어떨까요?
김개발 씨는 체크리스트를 만들었습니다. - 응답이 자연스러운가?
✓ - 에러 메시지가 친절한가? ✓ - 필요한 정보만 요청하는가?
✓ - 대화 흐름이 매끄러운가? ✓ - 예외 상황을 잘 처리하는가?
✓ 모두 만족스럽습니다! 개선 아이디어 테스트하면서 몇 가지 개선 아이디어가 떠올랐습니다.
3. 예약 변경 기능도 있으면 좋겠다
실전 팁
💡 - 실제 사용자처럼 다양하게 테스트하세요. 완벽한 입력만 테스트하면 안 됩니다
- 응답 시간도 중요합니다. 5초 이상 걸리면 사용자가 이탈합니다
- 테스트 결과를 문서화하면 나중에 회귀 테스트할 때 유용합니다
6. 에지 케이스 처리
마지막 미팅에서 박시니어 씨가 질문했습니다. "사용자가 욕설을 하면 어떻게 되죠?
과거 날짜로 예약하려고 하면? 동시에 100명이 같은 시간을 예약하면?" 김개발 씨는 당황했습니다.
생각하지 못한 부분들이었습니다.
에지 케이스는 정상적인 시나리오에서 벗어난 예외 상황들입니다. 마치 도로의 과속방지턱처럼, 이런 상황들을 미리 대비하지 않으면 시스템이 망가질 수 있습니다.
입력 검증, 에러 처리, 보안 필터링, 동시성 제어 등 실제 서비스에 필수적인 방어 로직을 구현해야 합니다.
다음 코드를 살펴봅시다.
# 에지 케이스 처리 로직
import re
from datetime import datetime, timedelta
def validate_and_sanitize(params):
"""입력값 검증 및 정제"""
# 전화번호 형식 검증
phone = params.get('phoneNumber', '')
if not re.match(r'^010-\d{4}-\d{4}$', phone):
return False, "전화번호는 010-XXXX-XXXX 형식이어야 합니다"
# 이름 길이 제한
name = params.get('customerName', '')
if len(name) < 2 or len(name) > 50:
return False, "이름은 2-50자 사이여야 합니다"
# 날짜 범위 검증 (최대 3개월 후까지)
date_str = params.get('date', '')
try:
res_date = datetime.strptime(date_str, '%Y-%m-%d')
if res_date < datetime.now():
return False, "과거 날짜로 예약할 수 없습니다"
if res_date > datetime.now() + timedelta(days=90):
return False, "예약은 최대 3개월 후까지 가능합니다"
except ValueError:
return False, "올바른 날짜 형식이 아닙니다"
return True, None
박시니어 씨가 화이트보드에 "에지 케이스 목록"을 적기 시작했습니다. 김개발 씨는 메모할 준비를 했습니다.
입력 검증 강화 가장 기본적인 방어는 입력 검증입니다. 사용자는 예상하지 못한 값을 입력할 수 있습니다.
전화번호에 특수문자가 섞여있다면? "010-abcd-5678" 같은 입력이 들어온다면?
정규표현식으로 검증해야 합니다. python import re def validate_phone(phone): pattern = r'^010-\d{4}-\d{4}$' if not re.match(pattern, phone): return False return True 이름도 마찬가지입니다.
너무 짧거나 너무 긴 이름은 거부해야 합니다. SQL 인젝션 같은 공격을 막으려면 특수문자도 필터링해야 할 수 있습니다.
날짜 관련 에지 케이스 날짜는 정말 까다롭습니다. 과거 날짜는 당연히 막아야 하지만, 너무 먼 미래도 문제입니다.
김개발 씨는 최대 3개월 후까지만 예약 가능하도록 제한했습니다. python from datetime import datetime, timedelta max_future = datetime.now() + timedelta(days=90) if reservation_date > max_future: return error_response("예약은 최대 3개월 후까지 가능합니다") 또 다른 문제는 영업시간입니다.
새벽 2시나 밤 11시 예약을 받아야 할까요? 아닙니다.
영업시간을 확인해야 합니다. python def is_business_hours(time_str): hour = int(time_str.split(':')[0]) return 9 <= hour <= 18 # 오전 9시~오후 6시 중복 예약 방지 동시에 여러 사람이 같은 시간을 예약하려고 하면 어떻게 될까요?
실제 데이터베이스를 사용한다면 트랜잭션과 락을 사용해야 합니다. 메모리 저장소를 쓰는 지금은 간단히 체크만 하겠습니다.
python def check_duplicate(date, time): for reservation in reservations.values(): if reservation['date'] == date and reservation['time'] == time: return True return False 요청 빈도 제한 악의적인 사용자가 초당 1000번 예약을 시도한다면? 시스템이 마비될 수 있습니다.
Rate Limiting이 필요합니다. AWS Lambda 레벨에서 제한할 수도 있고, API Gateway를 앞에 두면 더 쉽게 처리할 수 있습니다.
python from collections import defaultdict from time import time request_counts = defaultdict(list) def is_rate_limited(user_id): now = time() # 최근 1분간의 요청만 유지 request_counts[user_id] = [t for t in request_counts[user_id] if now - t < 60] # 분당 10회 제한 if len(request_counts[user_id]) >= 10: return True request_counts[user_id].append(now) return False 부적절한 내용 필터링 사용자가 욕설이나 부적절한 내용을 입력할 수 있습니다. AI 모델 자체에도 필터가 있지만, 백엔드에서도 검증하면 더 안전합니다.
python BANNED_WORDS = ['욕설1', '욕설2', '부적절한단어'] def contains_inappropriate_content(text): for word in BANNED_WORDS: if word in text: return True return False 실제로는 더 정교한 필터링이 필요하겠지만, 기본 방어선은 이 정도면 충분합니다. 긴 대화 세션 처리 사용자가 30분 동안 대화를 이어가면 어떻게 될까요?
Bedrock Agent는 컨텍스트를 유지하지만, 비용이 증가합니다. 토큰 제한도 있습니다.
일정 시간이 지나면 세션을 초기화하는 것이 좋습니다. python SESSION_TIMEOUT = 600 # 10분 def check_session_timeout(session_id, last_activity): if time() - last_activity > SESSION_TIMEOUT: return True return False 네트워크 에러 처리 Lambda 함수에서 외부 API를 호출한다면, 네트워크 에러가 발생할 수 있습니다.
재시도 로직이 필요합니다. python import time def call_with_retry(func, max_retries=3): for attempt in range(max_retries): try: return func() except Exception as e: if attempt == max_retries - 1: raise time.sleep(2 ** attempt) # 지수 백오프 데이터 크기 제한 사용자가 이름에 10000자를 입력한다면?
메모리가 낭비됩니다. 각 필드마다 최대 크기를 제한해야 합니다.
python MAX_NAME_LENGTH = 50 MAX_PHONE_LENGTH = 13 if len(name) > MAX_NAME_LENGTH: return error_response(f"이름은 최대 {MAX_NAME_LENGTH}자까지 가능합니다") 타임존 처리 사용자가 다른 나라에 있다면? "내일"은 어느 나라 기준일까요?
타임존을 명확히 해야 합니다. python import pytz KST = pytz.timezone('Asia/Seoul') def parse_date_with_timezone(date_str): naive_date = datetime.strptime(date_str, '%Y-%m-%d') return KST.localize(naive_date) 로깅과 모니터링 에러가 발생하면 로그를 남겨야 나중에 분석할 수 있습니다.
python import logging logger = logging.getLogger() logger.setLevel(logging.INFO) def safe_create_reservation(params): try: return create_reservation(params) except Exception as e: logger.error(f"예약 생성 실패: {str(e)}", exc_info=True) return error_response("일시적인 오류가 발생했습니다. 잠시 후 다시 시도해주세요") 재해 복구 Lambda 함수가 완전히 죽으면 어떻게 될까요?
데이터베이스가 다운되면? 백업과 복구 전략이 필요합니다.
실제 서비스라면 DynamoDB의 Point-in-Time Recovery를 활성화하거나, RDS의 자동 백업을 설정해야 합니다. 보안 헤더 API 응답에 적절한 보안 헤더를 추가하면 XSS, CSRF 같은 공격을 방어할 수 있습니다.
python def add_security_headers(response): response['headers'] = { 'X-Content-Type-Options': 'nosniff', 'X-Frame-Options': 'DENY', 'Content-Security-Policy': "default-src 'self'" } return response 최종 테스트 김개발 씨는 모든 에지 케이스를 테스트했습니다. 잘못된 전화번호, 과거 날짜, 이상한 문자, 빈 값, 중복 예약...
모두 적절히 처리되었습니다. 박시니어 씨가 만족스러운 표정으로 말했습니다.
"이제 진짜 완성이네요. 실제 서비스에 배포해도 될 것 같아요." 배운 교훈 김개발 씨는 이 프로젝트를 통해 많은 것을 배웠습니다.
AI 에이전트는 기술적으로 흥미롭지만, 실제로 중요한 것은 예외 처리와 사용자 경험입니다. 완벽한 시나리오만 생각하면 안 됩니다.
사용자는 예측 불가능합니다. 에지 케이스를 철저히 처리해야 신뢰할 수 있는 서비스가 됩니다.
다음 단계 이제 정말 프로덕션 배포를 준비할 시간입니다. 모니터링 대시보드를 설정하고, 알림을 구성하고, 문서를 작성하고, 팀원들에게 교육하고...
하지만 가장 중요한 것은 실제 사용자의 피드백입니다. 출시 후 계속 개선하면서 더 나은 서비스를 만들어가면 됩니다.
실전 팁
💡 - 모든 입력은 검증하세요. 사용자를 믿지 마세요 (보안 관점에서)
- 에러 메시지는 사용자 친화적으로, 로그는 개발자 친화적으로 작성하세요
- Rate Limiting은 필수입니다. DoS 공격으로부터 시스템을 보호하세요
- 에지 케이스 테스트에 전체 개발 시간의 30% 이상을 투자하세요
이상으로 학습을 마칩니다. 위 내용을 직접 코드로 작성해보면서 익혀보세요!
댓글 (0)
함께 보면 좋은 카드 뉴스
Helm 마이크로서비스 패키징 완벽 가이드
Kubernetes 환경에서 마이크로서비스를 효율적으로 패키징하고 배포하는 Helm의 핵심 기능을 실무 중심으로 학습합니다. Chart 생성부터 릴리스 관리까지 체계적으로 다룹니다.
보안 아키텍처 구성 완벽 가이드
프로젝트의 보안을 처음부터 설계하는 방법을 배웁니다. AWS 환경에서 VPC부터 WAF, 암호화, 접근 제어까지 실무에서 바로 적용할 수 있는 보안 아키텍처를 단계별로 구성해봅니다.
AWS Organizations 완벽 가이드
여러 AWS 계정을 체계적으로 관리하고 통합 결제와 보안 정책을 적용하는 방법을 실무 스토리로 쉽게 배워봅니다. 초보 개발자도 바로 이해할 수 있는 친절한 설명과 실전 예제를 제공합니다.
AWS KMS 암호화 완벽 가이드
AWS KMS(Key Management Service)를 활용한 클라우드 데이터 암호화 방법을 초급 개발자를 위해 쉽게 설명합니다. CMK 생성부터 S3, EBS 암호화, 봉투 암호화까지 실무에 필요한 모든 내용을 담았습니다.
AWS Secrets Manager 완벽 가이드
AWS에서 데이터베이스 비밀번호, API 키 등 민감한 정보를 안전하게 관리하는 Secrets Manager의 핵심 개념과 실무 활용법을 배워봅니다. 초급 개발자도 쉽게 따라할 수 있도록 실전 예제와 함께 설명합니다.