오늘은 Developing Generative AI Applications on AWS 강의를 들었다. 생성형 AI(Generative AI)와 Amazon Bedrock을 활용해서 실제 애플리케이션을 만드는 방법을 배웠는데, 이론부터 실습까지 꽤 알차게 진행됐다. 특히 프롬프트 엔지니어링 기법들이나 RAG 아키텍처 같은 개념들이 실제로 어떻게 코드로 구현되는지 보니까 훨씬 이해가 잘 됐다.
새로운 콘텐츠 및 아이디어를 생성하는 AI 유형이다. 엄청난 양의 데이터에 대해 사전 훈련된 대규모 모델(Foundation Model, FM)에 의해 구동된다.
비지도 학습(Unsupervised Learning)을 기반으로 하며, 텍스트, 이미지, 오디오, 비디오, 구조화된 데이터, 소프트웨어 코드 등 다양한 콘텐츠 유형을 생성할 수 있다.
광범위한 일반화된 데이터와 레이블이 지정되지 않은 데이터를 기반으로 훈련된 ML 모델이다. 콘텐츠 이해, 변환, 생성을 비롯한 여러 가지 일반적인 태스크를 수행한다.
FM이 문장을 완성하는 방식은 크게 4단계로 진행된다.
추론은 프롬프트를 입력으로 사용하여 파운데이션 모델을 호출하고 응답을 출력으로 가져오는 프로세스다.
컨텍스트는 모델과의 일대일 세션이다. 중요한 점은 컨텍스트에 제한이 있다는 것이다.
| 이점 | 설명 |
|---|---|
| 간편한 통합 | API 하나로 다양한 AI 모델을 애플리케이션에 통합 |
| 사용자 지정 | 자체 데이터로 모델을 미세 조정하여 맞춤형 모델 생성 |
| 개인 정보 보호 | 데이터 및 사용자 지정 내용은 AWS 계정 내에서 비공개 유지 |
| 비용 효율성 | 선불 비용이나 약정 없이 사용한 항목에만 비용 지불 |
| 확장성 | 애플리케이션 수요에 맞게 자동 조정 |
| AWS 서비스 통합 | 포괄적인 AI/ML 워크플로를 위해 다른 AWS 서비스와 통합 |
서버리스 패턴이 일반적이다.
사용자 → 프론트엔드(S3 + CloudFront) → API Gateway → Lambda → Amazon Bedrock → FM
| 파라미터 | 설명 |
|---|---|
| temperature / top-p | 출력의 독창성과 예측 가능성 간의 균형 제어. 낮을수록 일관성, 높을수록 독창성 |
| 토큰 수 | 응답 길이의 하드 제한. 출력이 길수록 처리 시간과 비용 증가 |
| 중지 시퀀스 | 모델이 텍스트 생성을 중지하는 위치를 정밀하게 제어 |
| 결과 수 | 각 프롬프트에 대해 생성하는 대체 응답의 수 |
| 페널티 | 반복 텍스트, 특정 용어의 과도한 사용 방지 |
bedrock 클라이언트 (제어 영역)
ListFoundationModels: 사용 가능한 모델 검색CreateModelInvocationJob: 배치 처리 작업 설정CreateGuardrail: 안전 및 콘텐츠 필터링 정책 수립bedrock-runtime 클라이언트 (데이터 영역)
InvokeModel: 동기식 모델 호출 (실시간 애플리케이션)InvokeModelWithResponseStream: 응답 스트리밍 (긴 텍스트 생성)Converse: 다양한 FM에서 통합 인터페이스 제공ApplyGuardrail: 안전 정책 적용import boto3, json
# bedrock runtime 클라이언트 인스턴스화
bedrock_runtime = boto3.client("bedrock-runtime", region_name="us-east-1")
# 모델용 페이로드 구성
body = json.dumps({
"anthropic_version": "bedrock-2023-05-31",
"max_tokens": 5000,
"messages": [
{
"role": "user",
"content": "Create a script to resize images"
}
]
})
# Claude 모델 호출
response = bedrock_runtime.invoke_model(
body=body,
modelId="global.anthropic.claude-sonnet-4-5-20250929-v1:0",
accept="application/json",
contentType="application/json"
)
# 응답 출력
response_body = json.loads(response.get("body").read())
print(response_body["content"][0]["text"])
모델 공급자에 관계없이 동일한 메시지 구조를 사용하는 통합 인터페이스다. 모델 ID만 바꾸면 코드 변경 없이 다른 모델로 전환 가능하다.
import boto3
bedrock_client = boto3.client("bedrock-runtime")
response = bedrock_client.converse(
modelId="us.anthropic.claude-sonnet-4-5-20250929-v1:0",
messages=[{
"role": "user",
"content": [{"text": "이미지 크기를 조정하는 스크립트 생성."}]
}],
system=[{"text": "당신은 Python에 능숙한 앱 개발자입니다. 코딩 주제에 대한 논의에만 참여하세요."}],
inferenceConfig={"temperature": 0.7, "topP": 0.9, "maxTokens": 500},
additionalModelRequestFields={"top_k": 200}
)
LLM 사용 방식을 최적화하는 분야다. 모델 자체를 조정하지 않고도 LLM 응답을 개선할 수 있다. 모델 가중치나 파라미터에 영향을 주지 않는다.
| 요소 | 설명 |
|---|---|
| 지침 | LLM이 수행할 태스크 설명 |
| 컨텍스트 | 모델을 안내할 외부 정보 |
| 입력 데이터 | 응답을 원하는 입력 |
| 출력 지시 | 출력 유형 또는 형식 |
Zero-Shot 프롬프팅
추가 예제 없이 LLM에 태스크를 제시하는 방식이다. 모델이 추가 컨텍스트 없이 태스크를 수행할 것으로 기대한다.
다음 소셜 미디어 게시물의 감정을 알려주고 긍정, 부정 또는 중립으로 분류하십시오.
혁신적인 전기차를 놓치지 마십시오! AnyCompany는 EV용 머슬카를 버리고...
Few-Shot 프롬프팅
모델에 태스크의 예제와 원하는 출력을 함께 제공하는 방식이다. 컨텍스트를 제공하면 모델이 태스크 지침을 더 정확하게 준수한다.
다음 헤드라인의 감정을 분류합니다.
연구 기업은 신기술이 부적절하다는 주장을 차단합니다. → 부정
반대하는 소수 강경론자가 줄면서 해상 풍력 발전소는 계속 번창하고 있습니다. → 긍정
제조 공장은 최근에 주당국의 조사 대상이 되고 있습니다. → ?
사고 사슬(Chain of Thought, CoT)
복잡한 추론 태스크를 중간 단계로 구분하는 방식이다. "단계별로 생각하십시오(Think step by step)" 라는 구문으로 CoT 추론을 유도할 수 있다.
차량 A의 총 비용은 40,000 USD이고 계약금으로 원금의 30%가 있어야 합니다.
차량 B의 총 비용은 50,000 USD이고 계약금으로 원금의 20%가 있어야 합니다.
단계별로 생각해서 선납금이 더 필요한 차량은?
→ A 차량 계약금: 40,000 × 30% = 12,000 USD
→ B 차량 계약금: 50,000 × 20% = 10,000 USD
→ 차량 A가 더 많은 선납금 필요
| 기법 | 설명 |
|---|---|
| 사고 트리(ToT) | 하나의 순차적 경로 대신 여러 경로를 고려 |
| 자체 일관성 | 다양한 추론 경로를 샘플링하고 최종 답변을 집계 |
| RAG | 외부 지식 기반에서 관련 문서를 검색하여 프롬프트 증강 |
| ReAct | 추론과 액션을 결합. 외부 도구(위키피디아, SQL DB 등) 호출 가능 |
| 모범 사례 | 나쁜 예 | 좋은 예 |
|---|---|---|
| 구체적이고 명확하게 | "로그인 폼 코드 생성해줘" | "이메일 유효성 검사, 비밀번호 강도 표시기, 오류 처리 기능이 포함된 React 로그인 폼 생성해줘" |
| 역할 사용 | "이 함수에서 취약점 분석해줘" | "숙련된 보안 엔지니어라고 가정하고 이 인증 함수의 잠재적 취약점을 확인해줘" |
| 제약 조건 포함 | "React 컴포넌트 생성해줘" | "함수 컴포넌트, TypeScript, 접근성 가이드라인 준수, 인라인 주석 포함한 React 컴포넌트 생성해줘" |
복잡한 질문을 더 작은 하위 태스크로 나누는 기법이다. 모델이 각 구성 요소를 개별적으로 처리하여 더 정확한 응답을 제공한다.
예시: "이메일 인증 및 오류 처리 기능이 포함된 사용자 등록 REST API 엔드포인트 생성"을 아래처럼 분해한다.
/register POST 엔드포인트 구조 생성| 모드 | 설명 | 적합한 경우 |
|---|---|---|
| 배치 추론 | 대규모 데이터 세트에서 한 번 수행 | 일별 예측 분석, 임베딩 생성, 대규모 번역 |
| 온디맨드 추론 | 즉시 응답 제공 | AI 챗봇, RAG, 개인화 추천 |
| 프로비저닝된 처리량 | 시간당 성능 기준으로 모델 단위 구매 | 워크로드를 일정하게 실행하는 경우 |
파운데이션 모델은 대화 기록을 자동으로 유지하지 않는다. 애플리케이션에서 직접 구현해야 한다.
외부 지식 기반에서 관련 정보를 검색하여 LLM의 응답을 개선하는 기법이다.
| 유형 | 적합한 경우 |
|---|---|
| 벡터 저장소 | 구조화되지 않은 일반 텍스트, 시맨틱 유사성 기반 검색 |
| 그래프 데이터베이스 | 엔터티와 관계가 명확하게 정의된 도메인, 복잡한 다단계 질문 |
| 하이브리드 | 구조화된 도메인 + 폭넓은 시맨틱 검색이 모두 필요한 경우 |
Retrieve: 쿼리와 가장 관련성 높은 소스 청크 검색GenerateQuery: 자연어 쿼리를 구조화된 데이터 저장소 쿼리로 변환RetrieveAndGenerate: Retrieve + InvokeModel을 결합한 전체 RAG 프로세스 수행오케스트레이션 프레임워크
검색기(벡터 저장소)
생성기(LLM 액세스)
오픈 소스 프레임워크로 Python, TypeScript, JavaScript로 제공된다. LLM과 상호 작용할 때 컨텍스트 관리나 단계 순서 관리 같은 일반적인 태스크를 효율적으로 처리할 수 있는 구성 요소를 제공한다.
from langchain.chat_models import init_chat_model
from langchain_core.messages import HumanMessage, SystemMessage
# Amazon Bedrock 모델 초기화
model = init_chat_model(
model="bedrock/anthropic.claude-sonnet-4-5-20250929-v1:0",
model_provider="bedrock_converse"
)
# 메시지 전송
messages = [
SystemMessage("Translate the following from English into Italian"),
HumanMessage("hi!"),
]
response = model.invoke(messages)
LangChain은 내부적으로 Amazon Bedrock Converse API를 활용하여 요청을 처리한다. 응답은 AIMessage 객체로 래핑되어 반환된다.
from langchain_core.prompts import ChatPromptTemplate
system_template = "다음을 영어에서 {language}로 번역하세요"
prompt_template = ChatPromptTemplate.from_messages(
[("system", system_template), ("user", "{text}")]
)
prompt = prompt_template.invoke({"language": "Italian", "text": "hi!"})
response = model.invoke(prompt)
DynamoDB를 활용하여 대화 기록을 영구 저장하는 체인을 구성할 수 있다.
from langchain.chat_models import init_chat_model
from langchain_core.prompts import ChatPromptTemplate, MessagesPlaceholder
from langchain_core.runnables.history import RunnableWithMessageHistory
model = init_chat_model(
model="bedrock/anthropic.claude-sonnet-4-5-20250929-v1:0",
model_provider="bedrock_converse"
)
prompt_template = ChatPromptTemplate.from_messages([
("system", "You are a helpful assistant."),
MessagesPlaceholder(variable_name="history"),
("human", "{question}")
])
chain = prompt_template | model
# DynamoDB로 대화 기록 영구 저장
chain_with_history = RunnableWithMessageHistory(
chain,
lambda session_id: DynamoDBChatMessageHistory(
table_name="SessionTable",
session_id=session_id
),
input_messages_key="question",
history_messages_key="history"
)
고객 불만 이메일에 대한 응답을 Amazon Nova Lite 모델로 생성하는 실습이었다.
핵심 흐름:
1. boto3.client('bedrock-runtime') 으로 클라이언트 생성
2. 프롬프트 데이터 구성 (Zero-Shot 방식)
3. invoke_model() 또는 invoke_model_with_response_stream() 호출
4. 응답 파싱 및 출력
스트리밍 방식은 전체 응답이 생성될 때까지 기다리지 않고 청크 단위로 실시간 출력이 가능해서 사용자 경험이 훨씬 좋다.
AWS 블로그 게시물 텍스트를 Amazon Nova Lite 모델로 요약하는 실습이었다.
prompt_data = """
Please provide a summary of the following text:
AWS took all of that feedback from customers, and today we are excited to announce Amazon Bedrock...
"""
body = json.dumps({
"messages": [{"role": "user", "content": [{"text": prompt_data}]}],
"inferenceConfig": {"maxTokens": 2048, "temperature": 0, "topP": 0.9}
})
response = bedrock_client.invoke_model(
body=body,
modelId='amazon.nova-lite-v1:0',
accept='application/json',
contentType='application/json'
)
temperature=0으로 설정하면 일관성 있는 요약 결과를 얻을 수 있다.
오늘 핵심은 "Amazon Bedrock을 통해 다양한 FM을 API 하나로 활용할 수 있다" 는 것이다. 직접 모델을 구축하거나 인프라를 관리할 필요 없이 프롬프트만 잘 설계하면 강력한 AI 기능을 애플리케이션에 통합할 수 있다.
특히 프롬프트 엔지니어링 기법들이 인상적이었다. Zero-Shot, Few-Shot, CoT를 상황에 맞게 조합하면 모델 재훈련 없이도 응답 품질을 크게 개선할 수 있다. 그리고 RAG 아키텍처는 기업 내부 데이터를 LLM에 연결하는 현실적인 방법이라는 게 느껴졌다. 모델이 학습하지 않은 최신 정보나 사내 문서를 검색해서 답변에 활용할 수 있으니까.
LangChain 같은 오픈 소스 프레임워크를 쓰면 Bedrock API를 직접 다루는 것보다 훨씬 편하게 개발할 수 있다는 것도 알게 됐다. 특히 대화 기록 관리나 프롬프트 템플릿 같은 반복적인 작업을 추상화해주는 게 실용적이다.
이번 velog는 AWS Kiro를 통해 작성하였다.