스트리밍과 재시도
Async Iterator, Callback Handler, SSE, ModelRetryStrategy
스트리밍과 재시도
Async Iterator, Callback Handler, SSE, ModelRetryStrategy
에이전트를 프로덕션에 올린다
프롬프트 캐싱·스트리밍·배포 타겟 — 비용과 속도 최적화
프롬프트 캐싱
SystemContentBlock · cache_tools · 비용 90% 절감
프롬프트 캐싱
매 호출 반복되는 prefix를 캐시해 입력 토큰 비용을 절감하는 메커니즘
프롬프트 캐싱
시스템 프롬프트·도구 정의처럼 매 호출 동일한 prefix를 캐시하여 입력 토큰 과금을 최소화합니다.
- cache_tools="default" — 도구 정의 전체 캐시 (5분 TTL)
- cache_config=CacheConfig(strategy="auto") — 멀티턴 메시지 자동 캐싱
- 시스템 프롬프트 명시 캐싱 — SystemContentBlock의 cachePoint 마커
- cache hit — 캐시 읽기 비용은 입력 토큰의 10%
첫 호출은 cache write 비용이 추가되지만, 이후 hit부터는 prefix 재처리 없이 절감된 비용으로 처리됩니다.
cache_tools로 도구 정의 캐싱
도구 정의는 한 줄 옵션 — 멀티턴 자동(CacheConfig)·시스템 프롬프트 명시(cachePoint)까지 대상별 선언
from strands import Agent
from strands.models import BedrockModel, CacheConfig
from strands.types.content import SystemContentBlock
model = BedrockModel(
model_id="us.anthropic.claude-sonnet-4-6",
cache_tools="default", # 도구 정의 전체를 캐시 (5분 TTL)
# 멀티턴 메시지 자동 캐싱 — 마지막 user 메시지 끝에 캐시 포인트 자동 배치
cache_config=CacheConfig(strategy="auto"),
)
agent = Agent(
model=model,
# 시스템 프롬프트 명시 캐싱 — cachePoint 마커까지가 캐시 prefix
system_prompt=[
SystemContentBlock(text="""You are a production data analyst.
You have access to SQL databases, S3 data lakes, and monitoring dashboards.
Always validate queries before execution and explain your reasoning."""),
SystemContentBlock(cachePoint={"type": "default"}),
],
tools=[query_database, read_s3, check_metrics, generate_report],
)
# 첫 호출: cache write (약간의 추가 비용)
# 이후 호출: cache hit → 시스템 프롬프트 + 도구 정의 비용 90% 절감
result = agent("지난주 매출 리포트를 생성해줘")
캐싱 동작 원리
Cache Point 마커 기준으로 prefix 일치 여부를 판단하는 hit/miss 흐름
-
1
Cache Point 선언
SystemContentBlock에 cachePoint: { type: "default" }를 삽입. 이 마커까지의 토큰이 캐시 키의 prefix가 됨.
-
2
Prefix 해싱
프로바이더가 시스템 프롬프트 + 도구 정의를 해싱하여 캐시 키 생성. 동일 prefix면 같은 키로 매핑.
-
3
Cache Hit 판정
TTL 내에 동일 키가 존재하면 hit — 캐시된 KV 상태를 재사용하여 prefix 토큰 재처리 생략.
-
4
비용 정산
Hit 시 캐시 읽기 비용만 과금 (일반 입력 토큰의 10%). Miss 시 cache write 비용 25% 추가 후 다음 호출부터 절감.
hit이면 캐시 읽기 비용 10%만 과금되고, miss면 write 비용 25%가 추가됩니다 — TTL 안에 같은 prefix로 다시 호출하는 것이 절감의 조건입니다.
캐싱 조건 비교
Anthropic API 직접 호출과 Bedrock 경유 시 캐싱 동작 차이
| 조건 | Anthropic API | Amazon Bedrock |
|---|---|---|
| 최소 토큰 | 1,024 이상 (Sonnet 4.6 기준) | 모델별 최소 토큰 존재 (1,024/4,096 — Bedrock 문서 기준) |
| TTL | 5분 (사용 시 갱신) | 5분~1시간 (모델별 상이) |
| cache_tools="default" | ✓ 지원 (5분 TTL) | ✓ 지원 |
| cache_config (멀티턴 메시지 자동) | ✓ CacheConfig(strategy="auto") | ✓ 동일 인터페이스 |
| 시스템 프롬프트 (수동 cachePoint) | ✓ SystemContentBlock | ✓ 동일 인터페이스 |
| Cache Write 비용 | 입력 토큰의 25% 추가 (5분 TTL 기준 — 1시간 TTL은 +100%) | 입력 토큰의 25% 추가 (5분 TTL 기준) |
| Cache Read 비용 | 입력 토큰의 10% | 입력 토큰의 10% |
| 교차 세션 공유 | 동일 workspace 내 공유 (2026-02 변경) | 동일 계정 내 공유 |
Strands 캐싱 인터페이스는 Anthropic API와 Bedrock에서 동일합니다 — 최소 토큰과 TTL 같은 캐싱 조건만 프로바이더별로 다릅니다.
스트리밍
AsyncIterator · Callback · SSE · Retry
스트리밍
토큰 단위 수신 인터페이스부터 브라우저 전달까지의 실시간 경로
스트리밍
전체 응답 완료를 기다리지 않고 생성 즉시 토큰을 전달하여 체감 지연을 제거합니다.
- Callback Handler — callable 하나로 이벤트 수신
- stream_async — async for 루프의 AsyncIterator
- SSE 파이프라인 — Agent → Callback → Transport → 브라우저
- TTFT 최소화 — 첫 토큰 즉시 표시
AsyncIterator와 Callback 이중 인터페이스로 토큰을 받고, SSE 파이프라인이 브라우저까지 실시간 전달합니다.
AsyncIterator + Callback 스트리밍
토큰 단위 수신을 위한 두 가지 인터페이스 — 이터레이터와 이벤트 콜백
import asyncio
from strands import Agent
queue = asyncio.Queue()
# 방법 1: Callback Handler — callable 하나로 이벤트 수신
def sse_handler(**kwargs):
"""토큰을 SSE 형식으로 클라이언트에 푸시"""
if "data" in kwargs: # 텍스트 델타
queue.put_nowait(f"data: {kwargs['data']}\n\n")
tool_use = kwargs.get("current_tool_use")
if tool_use: # 도구 호출 이벤트
queue.put_nowait(f"event: tool\ndata: {tool_use['name']}\n\n")
agent = Agent(callback_handler=sse_handler)
# 방법 2: AsyncIterator — async for 루프 (이벤트는 dict)
async def stream_response(agent: Agent, prompt: str):
async for event in agent.stream_async(prompt):
if "data" in event:
yield f"data: {event['data']}\n\n"
elif event.get("current_tool_use"):
name = event["current_tool_use"]["name"]
yield f"event: tool\ndata: {name}\n\n"
yield "data: [DONE]\n\n"
Polling vs Streaming 비교
응답 수신 방식에 따른 지연·리소스·UX 차이
Polling (동기 대기)
전체 응답 완료 후 반환
- TTFT = 전체 생성 시간 (수 초~수십 초)
- 클라이언트가 빈 화면에서 대기
- 구현 단순 — request/response 한 쌍
- 네트워크 타임아웃 위험 (30초+ 응답)
Streaming (토큰 단위)
생성 즉시 토큰 전달
- TTFT 최소화 — 첫 토큰 즉시 표시
- 타이핑 효과로 체감 지연 제거
- SSE/WebSocket 연결 관리 필요
- 긴 응답도 타임아웃 없이 안정 수신
스트리밍은 SSE/WebSocket 연결 관리 비용을 치르는 대신 첫 토큰 지연과 타임아웃 위험을 동시에 제거합니다.
SSE 파이프라인 구조
Agent에서 브라우저까지 토큰이 전달되는 4단계 흐름
-
1
Agent 추론
Strands Agent가 모델 API를 스트리밍 모드로 호출. 모델이 토큰을 생성할 때마다 chunk 이벤트 발생.
-
2
Token Callback
callback_handler로 전달한 함수가 "data" 키의 텍스트 델타를 수신. SSE 형식(data: ...)으로 변환하여 응답 큐에 적재.
-
3
SSE Transport
FastAPI StreamingResponse / Lambda Function URL이 Content-Type: text/event-stream으로 청크 전송. Keep-alive 유지.
-
4
Client Render
EventSource API가 토큰을 수신할 때마다 DOM에 append. 타이핑 효과로 실시간 표시. 연결 끊김 시 자동 재연결.
네 단계 중 개발자가 작성하는 것은 callback 함수와 SSE 엔드포인트뿐입니다 — 나머지는 모델과 브라우저가 처리합니다.
SSE 장면 — 토큰이 흐르는 화면
같은 순간의 양쪽 모습 — 와이어의 델타 이벤트와 브라우저의 부분 출력
와이어 — GET /chat/stream
app.example.com/chat
캐싱 · 스트리밍 · 배포 — 프로덕션 에이전트의 3대 요소