
Project 4: 고객 서비스 AI 챗봇 (Lambda + Gemini/Bedrock + DynamoDB)
Terraform으로 구축한 고객 서비스 AI 챗봇 인프라입니다. 사용자가 웹 UI 또는 REST API로 메시지를 보내면 Lambda가 DynamoDB에서 최근 대화 이력을 불러와 Google Gemini 또는 AWS Bedrock Claude에 전달하고, AI 응답을 반환합니다. 대화 이력은 TTL 24시간으로 자동 만료되어 세션별 다중 턴(Multi-turn) 대화가 자연스럽게 이어집니다.
상담원 연결 요청이나 욕설이 감지되면 SNS를 통해 담당자 이메일로 즉시 에스컬레이션 알림이 발송됩니다. 웹 UI는 S3(비공개 버킷) + OAC + CloudFront HTTPS로 서빙되어 안전하게 브라우저에서 접근할 수 있습니다. AI 엔진은 variables.tf 한 줄로 Gemini와 Bedrock 간 전환이 가능하여, 무료 할당량 소진 시 유연하게 대응할 수 있습니다.
아키텍처
[웹 UI] [REST API]
브라우저 curl / 외부 앱
│ │
└──────────┬───────────────┘
▼
API Gateway (HTTP v2)
▼
Lambda (챗봇 로직)
┌──────────────────────────────────────────────┐
│ 1. DynamoDB 대화 이력 조회 (최근 10턴) │
│ 2. System Prompt + 이력 조립 │
│ 3. AI 호출 (Gemini / Bedrock) │
│ └─ Gemini 키: SSM Parameter Store │
│ (Cold Start 시 1회 조회 후 캐시) │
│ 4. 응답 라우팅 │
│ (정상 · 에스컬레이션 · 욕설 · fallback) │
│ 5. DynamoDB 대화 이력 저장 (TTL 24h) │
└──────────────────────────────────────────────┘
│ │
DynamoDB Gemini API
(대화 이력) or Bedrock
│
▼ (에스컬레이션 / 욕설 감지 시)
SNS → 이메일 알림
웹 UI: S3 (비공개) ─── OAC ───▶ CloudFront (HTTPS)
주요 구성 요소
| 리소스 | 설명 | 비용 |
|---|---|---|
| Lambda (챗봇) | API Gateway 요청 수신 → DynamoDB 이력 조회 → AI 호출 → 응답 라우팅 → 이력 저장. 5단계 로직을 단일 함수에서 처리 | 프리 티어: 월 1,000,000 요청 |
| DynamoDB | 세션 ID 기반 대화 이력 저장. TTL 24시간 자동 만료로 스토리지 누적 없음 | 프리 티어: 25GB / 월 |
| API Gateway (HTTP v2) | 웹 UI·curl 요청을 Lambda에 프록시 통합으로 전달하는 HTTP API(v2). /v1/chat 엔드포인트 |
프리 티어: 월 1,000,000 호출 |
| S3 + CloudFront (OAC) | S3 버킷을 비공개로 유지하고 OAC(Origin Access Control)를 통해 CloudFront에서만 접근. HTTPS 강제 제공 | 프리 티어: S3 5GB / 월 |
| SSM Parameter Store | Gemini API 키를 SecureString으로 안전하게 저장. Lambda는 Cold Start 시 1회 조회 후 컨테이너 수명 동안 메모리에 캐시 |
표준 파라미터: 무료 |
| SNS | 에스컬레이션·욕설 감지 시 등록 이메일로 즉시 알림 발송 | 프리 티어: 월 1,000건 이메일 |
| CloudWatch | Lambda 실행 로그, 호출 수, 응답 시간 대시보드 제공 | 프리 티어: 5GB 로그 수집 |
사전 준비
IAM 사용자 생성
AWS CLI와 Terraform이 AWS 리소스를 생성할 수 있도록 프로그래밍 방식 액세스 권한을 가진 IAM 사용자가 필요합니다.
1단계: IAM 사용자 생성
AWS 콘솔에서 IAM → 사용자 → 사용자 생성으로 이동합니다. 사용자 이름(예: terraform-admin)을 입력하고, AWS Management Console 액세스는 체크하지 않습니다 (CLI 전용 사용자이므로 콘솔 로그인 권한 불필요).
2단계: AdministratorAccess 권한 부여
권한 설정 단계에서 직접 정책 연결을 선택하고 AdministratorAccess 정책을 검색하여 체크합니다.
⚠️ AdministratorAccess는 실습/테스트용 편의 설정입니다. 실제 운영 환경에서는 최소 권한 원칙에 따라 필요한 서비스만 허용하는 커스텀 정책을 사용하세요.
3단계: 액세스 키(Access Key) 발급 및 저장
사용자 생성 후 해당 사용자 → 보안 자격 증명 → 액세스 키 만들기로 이동합니다. 사용 사례로 **Command Line Interface(CLI)**를 선택합니다. 발급된 액세스 키 ID와 시크릿 액세스 키는 이 화면에서만 확인 가능하므로, CSV를 다운로드하거나 별도로 메모해 두세요. 화면을 닫으면 시크릿 키는 다시 볼 수 없습니다.
AWS CLI v2 설치
# Linux / macOS의 경우
brew install awscli
aws --version
# Windows의 경우
winget install Amazon.AWSCLI
aws --version
Terraform 설치
# Linux / macOS의 경우
# 1. HashiCorp 저장소 추가
brew tap hashicorp/tap
# 2. Terraform 설치
brew install hashicorp/tap/terraform
terraform --version
# Windows의 경우
winget install Hashicorp.Terraform
terraform --version
설치 후 주의사항
Windows winget 약관 동의: winget 명령어를 처음 사용하면 중간에 ‘소스 약관에 동의하십니까?’ 라는 질문(Y/N)이 뜰 수 있습니다. Y를 입력하고 엔터를 누르면 계속 진행됩니다.
버전 확인 에러 발생 시: 설치가 완료된 직후
aws --version이나terraform --version을 입력했을 때 *‘명령어를 찾을 수 없다’*고 나온다면, 터미널(또는 PowerShell) 창을 완전히 닫고 새로 열어 다시 입력해 보세요. 환경 변수가 새로고침되어 정상 동작합니다.VSCode 사용 시: 터미널 탭만 닫는 것이 아니라 VSCode 창 전체를 완전히 껐다가 다시 켜야 합니다.
Google Gemini API 키 발급
이 프로젝트의 기본 AI 엔진은 Google Gemini입니다. 배포 전에 API 키를 먼저 발급받아야 합니다.
- https://aistudio.google.com/apikey 에 접속합니다.
- “Create API Key” 버튼을 클릭합니다.
- 생성된 키(
AIzaSy...형태)를 복사해 두세요.
Google 계정으로 로그인되어 있어야 합니다. API 키는 이 화면에서 언제든 재확인할 수 있습니다.
배포 가이드
Step 0. AWS CLI 자격증명 등록
aws configure
# AWS Access Key ID: <발급받은 Access Key ID>
# AWS Secret Access Key: <발급받은 Secret Access Key>
# Default region name: ap-northeast-2
# Default output format: json
연결 확인:
aws sts get-caller-identity
Step 1. Gemini API 키 발급
https://aistudio.google.com/apikey 에 접속하여 “Create API Key” 를 클릭하고 키를 복사합니다.
Step 2. Gemini API 키를 SSM Parameter Store에 저장
aws ssm put-parameter `
--name "/cloud-portfolio/gemini-api-key" `
--value "<your-gemini-api-key>" `
--type SecureString `
--region ap-northeast-2
- 이미 파라미터가 존재하는 경우
--overwrite플래그를 추가하세요.- Lambda는 Cold Start 시 이 경로에서 키를 읽고 컨테이너 수명 동안 메모리에 캐시합니다. 재배포 없이 키를 교체하면 다음 Cold Start 시 자동으로 새 키가 로드됩니다.
Step 3. variables.tf 수정
project4-ai-chatbot/variables.tf 파일을 열어 아래 세 변수를 수정합니다.
| 변수 | 설명 | 예시 |
|---|---|---|
suffix |
S3 버킷 이름 뒤에 붙는 고유 식별자. 전 세계에서 유일해야 하므로 이름+날짜 조합 권장 | <your-name>-<date> |
alert_email |
에스컬레이션·욕설 감지 시 알림을 받을 이메일 주소 | your@email.com |
company_name |
챗봇 System Prompt에 표시될 서비스 이름 (선택) | 내 쇼핑몰 |
gemini_api_key는variables.tf에 없습니다 — Step 2에서 SSM Parameter Store에 저장합니다.
Step 4. 인프라 배포
프로젝트 폴더로 이동 후 순서대로 실행합니다.
cd project4-ai-chatbot
terraform init # Provider 플러그인 다운로드 (최초 1회)
terraform plan # 생성될 리소스 미리보기
terraform apply # 실제 배포, 확인 프롬프트에 'yes' 입력
배포가 완료되면 터미널에 Outputs이 출력됩니다. 아래 값들을 메모해 두세요.
chat_api_endpoint = "https://<api-id>.execute-api.ap-northeast-2.amazonaws.com/v1/chat"
chatbot_ui_url = "https://<distribution-id>.cloudfront.net"
ui_upload_command = "aws s3 sync ./website/ s3://p4-chatbot-ui-<suffix>/ --delete"
cache_invalidation_command = "aws cloudfront create-invalidation --distribution-id <distribution-id> --paths '/*'"
dynamodb_table = "p4-chatbot-sessions"



Step 5. SNS 구독 확인 이메일 클릭
terraform apply 완료 후 alert_email로 SNS Subscription Confirmation 이메일이 옵니다.
메일 안의 “Confirm subscription” 링크를 클릭해야 에스컬레이션 알림이 실제로 수신됩니다.
이 단계를 건너뛰면 테스트 시나리오 ③ (에스컬레이션 트리거)에서 이메일이 오지 않습니다.
Step 6. 웹 UI API 엔드포인트 설정
website/index.html을 열어 아래 줄을 Step 4 출력의 chat_api_endpoint 값으로 교체합니다.
// 수정 전
const API_ENDPOINT = "https://xxxxx.execute-api.ap-northeast-2.amazonaws.com/v1/chat";
// 수정 후 (terraform output chat_api_endpoint 값으로 교체)
const API_ENDPOINT = "https://abc12345de.execute-api.ap-northeast-2.amazonaws.com/v1/chat";
Step 7. 웹 UI S3 업로드 + CloudFront 캐시 무효화
# Step 4 출력의 ui_upload_command 실행
aws s3 sync ./website/ s3://p4-chatbot-ui-<suffix>/ --delete
# Step 4 출력의 cache_invalidation_command 실행
aws cloudfront create-invalidation --distribution-id <distribution-id> --paths "/*"
Step 8. 브라우저 접속
Step 4 출력의 chatbot_ui_url을 브라우저에서 열면 채팅 UI가 표시됩니다.
예: https://<distribution-id>.cloudfront.net
테스트 시나리오
테스트 전 아래 명령으로 chat_api_endpoint 값을 확인하고 메모해 두세요. 아래 예시의 <api-id> 자리에 실제 값을 넣어 사용합니다.
cd project4-ai-chatbot
terraform output -raw chat_api_endpoint
Windows PowerShell 참고:
curl.exe에-d인자로 JSON을 전달할 때 내부 따옴표는\"로 이스케이프해야 합니다. 아래 예시는 모두 이 방식을 사용합니다.
① 기본 대화
챗봇이 정상 응답하는지 확인합니다.
curl.exe -X POST "https://<api-id>.execute-api.ap-northeast-2.amazonaws.com/v1/chat" `
-H "Content-Type: application/json" `
-d '{\"message\": \"반품은 어떻게 하나요?\", \"session_id\": \"test-001\"}'
예상 응답:
{"response": "반품 정책 안내 메시지...", "session_id": "test-001", "escalated": false}
escalated: false 가 포함되어 있으면 정상입니다.
② 대화 이력 연속성 확인
같은 session_id로 두 번 연속 요청하여 두 번째 응답이 첫 번째 메시지 내용을 기억하는지 확인합니다.
# 1번째 메시지 — 주문 번호 전달
curl.exe -X POST "https://<api-id>.execute-api.ap-northeast-2.amazonaws.com/v1/chat" `
-H "Content-Type: application/json" `
-d '{\"message\": \"제 주문 번호는 12345입니다.\", \"session_id\": \"test-003\"}'
# 2번째 메시지 — 이전 대화 기억 여부 확인
curl.exe -X POST "https://<api-id>.execute-api.ap-northeast-2.amazonaws.com/v1/chat" `
-H "Content-Type: application/json" `
-d '{\"message\": \"제 주문 번호가 뭐였죠?\", \"session_id\": \"test-003\"}'
2번째 응답에서 “12345” 를 언급하면 DynamoDB 대화 이력이 정상 동작하는 것입니다.
③ 에스컬레이션 트리거
상담원 연결 요청 문구를 보내 escalated: true 응답과 이메일 알림을 확인합니다.
curl.exe -X POST "https://<api-id>.execute-api.ap-northeast-2.amazonaws.com/v1/chat" `
-H "Content-Type: application/json" `
-d '{\"message\": \"상담원 연결해 주세요.\", \"session_id\": \"test-002\"}'
예상 응답:
{"response": "상담원에게 연결 중입니다...", "session_id": "test-002", "escalated": true}
alert_email로 설정한 주소로 에스컬레이션 알림 이메일이 수신되는지 함께 확인하세요.
④ DynamoDB 대화 이력 직접 확인
DynamoDB에 대화 이력이 실제로 저장되었는지 CLI로 직접 조회합니다.
aws dynamodb query `
--table-name p4-chatbot-sessions `
--key-condition-expression "session_id = :sid" `
--expression-attribute-values "{\":sid\":{\"S\":\"test-003\"}}" `
--region ap-northeast-2
test-003 세션의 대화 항목들이 출력되면 정상입니다.
검증 체크리스트
-
terraform output에chat_api_endpoint,chatbot_ui_url값이 출력됨 - curl 기본 대화 테스트에서
escalated: false응답 확인 - 같은
session_id로 연속 호출 시 이전 대화 내용(주문 번호 등) 기억 확인 - “상담원 연결해 주세요” 요청 시
escalated: true응답 + 이메일 알림 수신 확인 - DynamoDB
query명령으로 대화 이력 레코드 저장 확인 - 브라우저에서 CloudFront URL 접속 시 웹 UI 채팅 정상 동작 확인
⚠️ 비용 관리
서비스별 프리 티어 범위
| 서비스 | 프리 티어 한도 | 초과 시 단가 |
|---|---|---|
| Lambda | 월 1,000,000 요청 / 400,000 GB-초 | $0.20 / 백만 요청 |
| DynamoDB | 25GB 스토리지 / 월 200만 읽기·쓰기 | $0.25 / WCU, $0.10 / RCU |
| API Gateway (HTTP v2) | 월 1,000,000 호출 (12개월) | $1.00 / 백만 호출 |
| S3 (UI 버킷) | 5GB 스토리지 / 월 20,000 GET, 2,000 PUT | $0.023 / GB |
| CloudFront | 월 1TB 전송 / 10,000,000 요청 (12개월) | $0.0085 / GB |
| SSM Parameter Store | 표준 파라미터 무제한 | 고급 파라미터: $0.05 / 파라미터-월 |
| SNS | 월 1,000건 이메일 | $2.00 / 100,000건 |
| CloudWatch | 5GB 로그 수집 / 3개 대시보드 | $0.50 / GB |
| Gemini API | 무료 할당량 제공 (aistudio.google.com 에서 확인) | 모델별 상이 |
| Bedrock Claude (전환 시) | 프리 티어 없음 | 테스트 규모 기준 약 $1~3/월 |
Gemini 무료 할당량: Google AI Studio의 Gemini API는 일정 수준의 무료 호출 할당량을 제공합니다. 현재 한도는 aistudio.google.com/apikey 에서 확인할 수 있습니다. 소규모 테스트 기준으로는 무료 범위 내에서 충분히 사용 가능합니다.
Bedrock 전환 시 주의: Bedrock Claude는 프리 티어가 없으므로 토큰 사용량에 따라 비용이 발생합니다. 테스트 규모(수십
수백 회 호출)에서는 $13/월 수준입니다.
트러블슈팅 노트
1. Gemini API 키가 로드되지 않음 — ParameterNotFound
증상: Lambda가 실행되나 AI 응답 대신 오류가 반환되고, CloudWatch Logs에 ParameterNotFound 에러가 찍힘.
원인: SSM Parameter Store에 /cloud-portfolio/gemini-api-key 경로로 파라미터가 없거나 오타가 있습니다.
해결: Step 2의 명령어를 재실행하여 파라미터를 저장합니다. 이미 존재하는 경우 --overwrite를 추가하세요.
aws ssm put-parameter `
--name "/cloud-portfolio/gemini-api-key" `
--value "<your-gemini-api-key>" `
--type SecureString `
--region ap-northeast-2 `
--overwrite
2. 웹 UI가 이전 버전으로 표시됨
증상: website/index.html을 수정하고 S3에 업로드했으나 브라우저에 반영되지 않음.
원인: CloudFront 엣지 캐시가 살아있어 이전 파일을 계속 제공합니다.
해결: 캐시 무효화 명령을 실행합니다.
aws cloudfront create-invalidation --distribution-id <distribution-id> --paths "/*"
무효화는 보통 1~3분 내에 완료됩니다.
3. Bedrock AccessDeniedException: subscription required
증상: ai_provider = "bedrock"으로 전환 후 Lambda가 AccessDeniedException 오류를 반환함.
원인: AWS Bedrock에서 사용할 모델을 계정 레벨에서 먼저 활성화(구독)해야 합니다.
해결:
- AWS 콘솔 → Amazon Bedrock 서비스로 이동합니다.
- 왼쪽 메뉴에서 Model catalog (또는 Model access) 를 선택합니다.
- 사용할 모델(예:
Claude 3 Haiku) 옆 Request access 버튼을 클릭하고 승인을 기다립니다. - 승인 완료 후 Lambda를 다시 호출합니다.
모델 접근 승인은 즉시 또는 수 분 내에 완료되는 경우가 많습니다.
4. PowerShell curl.exe JSON 이스케이프 오류
증상: curl.exe -d 에 JSON을 직접 입력하면 Invalid JSON 또는 400 Bad Request 응답이 돌아옴.
원인: PowerShell에서 -d 인자 안의 큰따옴표를 이스케이프하지 않으면 JSON이 깨집니다.
해결: -d 인자 안의 모든 내부 큰따옴표를 \" 로 이스케이프합니다.
# ❌ 문제가 생기는 방식
curl.exe -X POST $endpoint -d '{"message": "안녕하세요", "session_id": "test-001"}'
# ✅ 올바른 방식 — 내부 따옴표를 \" 로 이스케이프
curl.exe -X POST $endpoint `
-H "Content-Type: application/json" `
-d '{\"message\": \"안녕하세요\", \"session_id\": \"test-001\"}'
Bedrock으로 전환 (선택 사항)
기본값은 Google Gemini입니다. AWS Bedrock Claude로 전환하려면 variables.tf를 수정 후 재배포합니다.
# project4-ai-chatbot/variables.tf
variable "ai_provider" {
default = "bedrock" # "gemini" → "bedrock" 으로 변경
}
cd project4-ai-chatbot
terraform apply
서울 리전 미지원 주의: AWS 서울 리전(ap-northeast-2)은 Claude 모델을 지원하지 않습니다.
variables.tf의bedrock_region은 기본값"us-east-1"(버지니아 북부)을 유지하세요.variable "bedrock_region" { default = "us-east-1" # 서울 미지원 → 버지니아 북부 사용 }Bedrock 전환 전 반드시 AWS Bedrock 콘솔에서 해당 모델의 Model access(구독)를 활성화해야 합니다.
인프라 삭제
# 1. S3 UI 버킷 비우기 (오브젝트가 남아있으면 terraform destroy 실패)
aws s3 rm s3://p4-chatbot-ui-<suffix> --recursive
# 2. Terraform 리소스 전체 삭제
cd project4-ai-chatbot
terraform destroy
SSM Parameter Store 수동 삭제: Gemini API 키는 Terraform이 관리하지 않으므로
terraform destroy이후에도 남아 있습니다. 아래 경로에서 수동으로 삭제하세요.AWS 콘솔 → Systems Manager → Parameter Store →
/cloud-portfolio/gemini-api-key→ 삭제
Lambda 함수, DynamoDB 테이블, API Gateway, S3 버킷, CloudFront 배포, SNS 토픽, CloudWatch 대시보드 등 Terraform이 생성한 모든 리소스가 삭제됩니다.
기술 스택
Terraform · AWS Lambda · AWS DynamoDB · AWS API Gateway · AWS S3 · AWS CloudFront · AWS SSM Parameter Store · AWS SNS · AWS CloudWatch · AWS IAM · Amazon Bedrock · Google Gemini API · Python