Amazon Bedrock 챗봇 핸즈온
실습 개요
Amazon Bedrock으로 Claude 모델을 호출하여 챗봇을 만드는 실습입니다. 가장 원초적인 저수준 호출(invoke_model)에서 출발해, 단계마다 한 가지 불편함을 직접 겪고 그것을 해소하면서, 마지막에는 모든 번거로움을 표준 형식 하나로 통일하는 고수준 API(Converse)에 도달합니다.
핵심 학습 원리는 “먼저 불편을 느끼고, 그다음 도구의 가치를 이해한다”입니다. 처음부터 편한 API를 쓰면 그것이 왜 편한지 모릅니다. 그래서 우리는 일부러 손이 많이 가는 방식부터 시작합니다.
| Lab | 제목 | 핵심 학습 |
|---|---|---|
| 01 | invoke_model 단일 턴 | body(JSON) 직접 작성, 응답 파싱, 저수준 호출의 구조 |
| 02 | 멀티턴 대화 | 히스토리를 직접 쌓아 맥락 유지, 토큰/비용 감각 |
| 03 | 스트리밍 | 이벤트 청크 직접 분기, 실시간 글자 출력(제너레이터) |
| 04 | Converse | 표준 형식, system 분리, 페르소나 부여, 모델 교체 0줄 |
| 05 | 세 API 비교 | 같은 질문을 세 방식으로 호출해 응답·지연 비교, 선택 기준 정리 |
Amazon Bedrock이란
Amazon Bedrock은 여러 회사가 만든 생성형 AI 기반 모델(Foundation Model)을 하나의 API로 호출할 수 있게 해 주는 AWS의 완전관리형 서비스입니다. Anthropic의 Claude, Amazon의 Titan 등 서로 다른 제공사의 모델을 같은 방식으로 불러 쓸 수 있습니다.
직접 GPU 서버를 띄우거나 모델 가중치를 내려받을 필요가 없습니다. boto3(파이썬 AWS SDK)로 API를 호출하면, AWS가 모델 실행을 대신 처리하고 결과만 돌려줍니다. 우리는 “모델을 운영하는 일”이 아니라 “모델에게 무엇을 시킬지”에만 집중하면 됩니다.
이 과정에서 알아 둘 두 가지 호출 계층이 있습니다.
- 저수준 API (
invoke_model) — 모델마다 다른 요청 형식(body)을 우리가 직접 만들어 보내고, 응답 JSON도 직접 파싱합니다. 자유롭지만 그만큼 우리가 형식을 책임집니다. - 고수준 API (
Converse) — 모델이 달라도 똑같은 표준 형식으로 호출합니다. AWS가 모델별 차이를 내부에서 흡수해 주므로, 모델을 바꿔도 코드를 고칠 필요가 거의 없습니다.
이 과정은 저수준에서 시작해 고수준으로 올라가며, 두 계층의 차이를 손으로 직접 느끼는 것을 목표로 합니다.
학습 흐름: 저수준에서 고수준으로
이 과정은 네 단계의 챗봇을 차례로 만들고, 마지막에 세 가지 호출 방식을 비교합니다. 각 단계는 이전 단계의 “불편함”을 출발점으로 삼습니다.
graph TD A["Lab 01: invoke_model 단일 턴<br/>body를 직접 만든다"] --> B["Lab 02: 멀티턴<br/>히스토리를 직접 쌓는다"] B --> C["Lab 03: 스트리밍<br/>청크를 직접 분기한다"] C --> D["Lab 04: Converse<br/>모든 걸 표준 형식으로 통일"] D --> E["Lab 05: 세 API 비교<br/>언제 무엇을 쓸지 정리"]
Lab 01 — invoke_model 단일 턴
모델 호출의 가장 기본 형태입니다. “호출 = body(JSON)를 만들어 보내고, 응답(JSON)을 파싱한다”를 손으로 익힙니다. Anthropic body의 세 요소(anthropic_version, max_tokens, messages)를 직접 작성합니다.
이 단계가 끝나면 모델을 한 번 부르고 답을 받을 수 있습니다. 하지만 단일 턴이라 이전 대화를 기억하지 못합니다. “내 이름은 ○○야”라고 알려준 뒤 “내 이름 뭐야?”라고 물으면 모델은 모릅니다.
Lab 02 — 멀티턴 대화
모델은 본질적으로 기억이 없습니다. 맥락을 유지하려면 매번 대화 전체를 다시 보내야 합니다. 이전 질문과 답변을 messages 배열에 차곡차곡 쌓아 함께 전송함으로써 대화의 맥락을 유지합니다.
이 단계가 끝나면 이름을 기억하는 챗봇이 됩니다. 하지만 히스토리를 우리가 직접 관리해야 하고, 대화가 길어질수록 매번 보내는 토큰(=비용)도 늘어난다는 것을 체감합니다.
Lab 03 — 스트리밍
ChatGPT처럼 글자가 한 자씩 흘러나오게 만듭니다. 응답이 한 덩어리로 오는 게 아니라 이벤트 스트림으로 옵니다. 청크마다 붙은 type(message_start, content_block_delta, message_stop 등)을 보고 우리가 원하는 delta.text만 골라냅니다. 파이썬 제너레이터(yield)로 구현합니다.
이 단계가 끝나면 실시간 출력으로 체감 응답 속도를 크게 줄일 수 있습니다. 하지만 raw 청크를 우리가 직접 분기·추출해야 한다는 또 하나의 번거로움을 만납니다.
Lab 04 — Converse
지금까지 쌓인 번거로움(모델별 body, 수동 히스토리, raw 청크 처리)을 한 번에 푸는 고수준 API입니다. 표준 형식으로 호출하고, 시스템 프롬프트는 system= 별도 인자로 분리하며, 페르소나를 부여합니다. 핵심 데모는 modelId만 Haiku에서 Amazon Nova로 바꿔도 코드 변경 0줄로 모델이 교체된다는 점입니다.
Lab 05 — 세 API 비교
같은 질문을 세 가지 방식으로 호출해 응답 내용과 지연을 한눈에 비교합니다. “새 프로젝트는 Converse를 기본으로, 필요할 때만 저수준으로 내려간다”는 선택 기준을 직접 정리합니다.
핵심 설계: 챗봇 UI는 제공 모듈, 학습자는 호출 함수만
이 과정에서 학습자가 만드는 것은 챗봇의 UI가 아니라 모델을 호출하는 함수입니다.
채팅 화면(입력을 받고 답을 출력하고 대화를 이어가는 루프)은 넥스트클라우드가 제공하는 콘솔 모듈 chat_ui.py가 담당합니다. 이 모듈은 블랙박스입니다. 학습자는 열어 볼 필요도, 수정할 필요도 없습니다. 마찬가지로 리전·모델 ID·클라이언트 생성을 모아 둔 config.py도 제공되는 블랙박스이며 수정하지 않습니다.
학습자가 채우는 것은 각 Lab 파일의 respond()(또는 respond_stream()) 함수 안 TODO, 즉 boto3로 Bedrock을 호출하고 응답을 파싱하는 부분뿐입니다. 이렇게 책임을 분리하는 이유는 분명합니다. UI 코드에 시간을 쓰지 않고, 이 과정의 진짜 학습 목표인 “Bedrock을 어떻게 호출하고 응답을 어떻게 다루는가”에만 집중하기 위해서입니다.
이 과정은 순수 파이썬 콘솔로 진행합니다. UI는 제공 모듈이 처리하고, 학습자는 호출 로직만 다룹니다.
config.py에서 제공되는 클라이언트 생성 함수는 다음과 같습니다. 실습 환경에서는 EC2에 부여된 IAM Role 자격증명을 boto3가 자동으로 사용하므로, 키를 코드에 적을 필요가 없습니다.
import boto3
REGION = "us-east-1"
def get_runtime():
# 실습 환경(EC2 + code-server): 부여된 IAM Role 자격증명을 boto3가 자동 사용
return boto3.client("bedrock-runtime", region_name=REGION) 사용 모델 정책
이 과정은 Anthropic Claude(Haiku 4.5) 를 주력으로 사용하고, Converse 스왑 데모에서는 제공사가 다른 Amazon Nova도 사용합니다(고수준 API가 제공사 차이를 추상화함을 보여주기 위함). 모델 ID는 config.py에 상수로 정의되어 있습니다.
| 용도 | 모델 ID | 비고 |
|---|---|---|
| 주력 실습 (전 Lab) | us.anthropic.claude-haiku-4-5-20251001-v1:0 | 최신 Haiku. invoke_model·Converse 모두 이 ID |
| Converse 모델 스왑 데모 | us.amazon.nova-lite-v1:0 | Amazon Nova(제공사 다름). 코드 0줄 변경으로 교체되는지 확인용 |
| (참고) 임베딩 | amazon.titan-embed-text-v2:0 | 텍스트 → 벡터 변환 (후속 과정에서 사용) |
인퍼런스 프로파일 ID (us.)
이 과정의 모든 Lab은 최신 모델을 us.anthropic.claude-haiku-4-5-...처럼 us.로 시작하는 인퍼런스 프로파일 ID로 호출합니다. 신형 모델은 온디맨드 호출 시 모델 ID를 그대로가 아니라 인퍼런스 프로파일 ID로 modelId에 넘겨야 하기 때문입니다. (과거 Claude 3 Haiku 같은 모델은 bare ID로도 호출됐지만 현재 Bedrock LEGACY입니다.)
주의: 사용할 수 없는 모델
- Claude 3 Sonnet은 Bedrock에서 Legacy로 분류되어 사용할 수 없습니다. 호출하면
ResourceNotFoundException ... Legacy오류가 납니다. 이 과정은 Claude 3 계열 대신 최신 Haiku 4.5(us.anthropic.claude-haiku-4-5-...)만 사용합니다. - 이 과정에서는 Sonnet, Opus 계열을 사용하지 않습니다. 실습 비용과 속도를 고려해 Haiku를 주력으로, 제공사가 다른 Amazon Nova를 모델 스왑 비교용으로 사용합니다.
실습 환경
실습은 브라우저에서 동작하는 code-server(웹 VS Code) 위에서 진행합니다. 개인 노트북에 파이썬이나 AWS CLI를 설치할 필요가 없습니다. 브라우저로 접속해 바로 코드를 작성하고 터미널을 실행할 수 있습니다.
실습 환경은 넥스트클라우드가 미리 만들어 둔 AMI(머신 이미지) 로 제공됩니다. 이 이미지에는 다음이 사전 설치되어 있습니다.
- 파이썬 실행 환경
- boto3 (파이썬 AWS SDK)
- AWS CLI
다만 실습 코드는 AMI에 들어 있지 않습니다. 첫 실습(Lab 01)에서 깃 저장소를 홈 디렉터리에 직접 내려받습니다. config.py·chat_ui.py 같은 제공 모듈도 이 저장소 안에 함께 들어 있습니다.
git clone https://github.com/nxtcloud-org/nxt-workshop-code.git자격증명은 인스턴스에 부여된 IAM Role로 자동 처리됩니다. boto3가 이 Role을 자동으로 사용하므로, 액세스 키를 코드나 파일에 적지 않습니다. 이는 키 유출 위험이 없는 안전한 방식이자, 실무에서 권장되는 표준 패턴입니다.
만약 호출이 막힌다면(
AccessDenied, 인퍼런스 프로파일 권한 오류 등) 리전이us-east-1인지, 그리고 신형 모델 호출 권한이 IAM Role에 부여되어 있는지 확인합니다.
다음 단계
이제 가장 원초적인 호출부터 시작합니다. Lab 01에서 invoke_model로 body(JSON)를 직접 만들어 보내고 응답을 파싱하면서, 모델 호출의 가장 기본적인 구조를 손에 익힙니다. 단일 턴 챗봇을 완성한 뒤, “이 챗봇은 왜 내 이름을 기억하지 못할까?”라는 질문을 안고 Lab 02 멀티턴으로 넘어가게 됩니다.
NxtCloud Workshop