Bedrock Runtime
boto3 클라이언트 구조, Runtime API 2종(InvokeModel vs Converse), 인증, VPC, 에러 핸들링
Bedrock Runtime
boto3 클라이언트 구조, Runtime API 2종(InvokeModel vs Converse), 인증, VPC, 에러 핸들링
모든 호출은 Runtime을 지난다
InvokeModel부터 Converse · 함수 호출 · 멀티모달까지
InvokeModel API
모델별 네이티브 포맷 · 저수준 호출
InvokeModel — 동기 vs 스트리밍
InvokeModel도 두 가지 — 한 번에 받는 invoke_model · 흘려받는 invoke_model_with_response_stream
케이션
0s · 요청 전송
6s · 전체 응답 한 번에
Runtime
케이션
0s · 같은 요청
0.5s · chunk — 첫 델타 "1."
2s · 델타 연속 수신 — 화면 갱신
6s · 마지막 chunk — stop_reason
Runtime
총 생성 시간은 같고 첫 글자 체감(6s vs 0.5s, 예시)이 다릅니다 — chunk 델타 JSON까지 모델별 포맷이라 모델을 바꾸면 파서도 다시 씁니다.
실물 요청·응답 — 네이티브 포맷
같은 회의록 요약 요청 — 필드 이름부터 응답 구조까지 전부 모델별 포맷
같은 요청, 같은 정보인데 필드 이름과 구조가 전부 모델별입니다 — 이 차이를 흡수하는 것이 Converse입니다.
요청 — 네이티브 JSON (json.dumps 필수)
body = json.dumps({
# Anthropic 전용 필수
'anthropic_version': 'bedrock-2023-05-31',
# 네이티브에선 필수 (Converse는 옵션)
'max_tokens': 512,
'messages': [{'role': 'user',
'content': '이 회의록을 3줄로 요약해 줘'}],
})
response = bedrock_runtime.invoke_model(
modelId='us.anthropic.claude-sonnet-4-6',
body=body)
응답 (예시 수치) — 이것도 모델별 포맷
{"content": [{"type": "text",
"text": "1. 3분기 예산 확정
2. 출시 일정 9월 합의
3. QA 담당 지정"}],
// 스네이크 케이스 — Converse는 stopReason
"stop_reason": "end_turn",
// 이름까지 다름 — Converse는 inputTokens
"usage": {"input_tokens": 1873,
"output_tokens": 96}}
InvokeModel 호출
모델별 JSON 포맷으로 요청 — 응답도 모델별 파싱 필요
import json
# Anthropic Claude - InvokeModel
body = json.dumps({
"anthropic_version": "bedrock-2023-05-31",
"max_tokens": 1024,
"messages": [
{"role": "user", "content": "Python으로 피보나치 함수 작성"}
]
})
response = bedrock_runtime.invoke_model(
modelId='us.anthropic.claude-sonnet-4-6',
body=body
)
# 응답 파싱 (Anthropic 포맷)
output = json.loads(response['body'].read())
code = output['content'][0]['text']
print(code)
- body는 모델별 네이티브 JSON 문자열 — anthropic_version 같은 모델 고유 필드 포함
- 응답도 모델별 포맷 — response['body'].read()를 직접 파싱
- 모델을 바꾸면 요청 본문과 파싱 코드를 함께 재작성
InvokeModel 스트리밍 호출
같은 body, 메서드만 교체 — chunk를 순회하며 모델별 델타를 파싱
response = bedrock_runtime.invoke_model_with_response_stream(
modelId='us.anthropic.claude-sonnet-4-6',
body=body # 동기 호출과 같은 네이티브 JSON
)
# response['body']는 EventStream — chunk 단위로 순회
for event in response['body']:
chunk = json.loads(event['chunk']['bytes'])
# Anthropic 스트리밍 포맷 — 타입별 분기
if chunk['type'] == 'content_block_delta':
print(chunk['delta']['text'], end='', flush=True)
elif chunk['type'] == 'message_delta':
stop_reason = chunk['delta']['stop_reason']
- 요청 body는 동기 호출과 동일 — 메서드만 _with_response_stream
- chunk['bytes'] 안의 델타 JSON도 모델별 포맷 — 타입 이름부터 Anthropic 전용
- content_block_delta에서 텍스트, message_delta에서 stop_reason
모델별 네이티브 포맷
같은 요청, 같은 512 토큰 상한 — JSON 모양은 네 가지
'max_tokens': 512,
'messages': [{'role': 'user',
'content': '회의록을 요약해 줘'}]}
'content': [{'text': '회의록을 요약해 줘'}]}],
'inferenceConfig': {'maxTokens': 512}}
'max_gen_len': 512}
// messages 배열 없이 단일 문자열
'content': '회의록을 요약해 줘'}],
'max_tokens': 512}
// OpenAI Chat Completions 스키마
같은 InvokeModel이라도 요청·응답 스키마가 프로바이더마다 다릅니다 — 모델을 바꿀 때마다 본문과 파싱 코드를 다시 쓰는 이 번거로움을 하나로 통합한 것이 Converse API입니다.
추론 파라미터
다음 토큰 선택 · Temperature · Top-P · Max Tokens
다음 토큰 선택 — 파라미터의 개입 지점
모델은 확률 분포에서 다음 토큰을 고른다 — Temperature·Top-P가 조절하는 대상
파라미터는 세 지점에 개입합니다 — ① Temperature 분포의 뾰족함(Softmax 전 로짓 나누기) · ② Top-P 후보 컷라인 · ③ Max Tokens · stopSequences 반복을 멈추는 조건. 컷라인 통과 후보 중 추첨(샘플링)은 모델의 몫입니다.
① Temperature — 분포의 뾰족함
Softmax 전에 로짓을 T로 나눕니다 — 낮으면 확정적, 높으면 다양 (0~1, 예시 수치)
같은 로짓, 다른 T — 분포 자체를 바꾸는 첫 개입 · 기본값은 모델별 상이라 명시 지정 권장
② Top-P — 후보 컷라인
확률 내림차순으로 누적 P에 도달할 때까지만 후보로 — 나머지는 제외
분포가 뾰족하면 후보가 줄고 평평하면 늘어나는 가변 컷 — 그래서 Temperature와 둘 중 하나만 조정하는 것이 예측 가능한 기본
③ Max Tokens · Stop Sequences — 멈추는 조건
생성이 끝나는 세 가지 길 — stopReason으로 어느 길이었는지 확인
maxTokens는 생성분만 제한합니다 — 입력 + 생성이 컨텍스트 윈도우를 넘을 수는 없음 · 어느 길로 끝났는지는 stopReason이 알려줌
InvokeModel · 추론 파라미터 · Converse · 함수 호출 · 멀티모달 — 운영 패턴은 추론 인프라 모듈에서