Strands 관측성
StrandsTelemetry·OTEL 자동 계측·result.metrics — Strands 에이전트 관측성 구현
Strands 관측성
Telemetry · Tracing · Metrics · ADOT
에이전트의 동작을 투명하게 관찰한다
OpenTelemetry 기반 트레이싱·메트릭·로깅으로 에이전트 동작 가시성 확보
관측성 개요
StrandsTelemetry · OpenTelemetry · 3 Pillars
관측성 파이프라인 전체 흐름
SDK 계측부터 백엔드 시각화까지 — 데이터가 흐르는 경로
-
1
SDK 계측
StrandsTelemetry가 Agent Loop, Model Invoke, Tool Execute 각 단계에 자동으로 스팬과 메트릭을 생성. 코드 수정 없이 1줄 설정으로 활성화.
-
2
Exporter 전송
OTel SDK의 OTLP Exporter가 스팬·메트릭·로그를 gRPC 또는 HTTP/protobuf로 Collector에 전송 — StrandsTelemetry의 setup_otlp_exporter는 HTTP/protobuf(4318) 고정. 배치 처리로 네트워크 효율 확보.
-
3
Collector 처리
ADOT Collector가 수신된 텔레메트리를 배치·필터·샘플링 처리. 민감 정보 제거, 어트리뷰트 보강 등 전처리 수행.
-
4
Backend 저장/시각화
X-Ray에 트레이스, CloudWatch에 메트릭과 로그 저장. 서비스 맵, 대시보드, 알람으로 운영자에게 가시성 제공.
네 단계 중 개발자가 만지는 것은 SDK 계측 1줄과 Collector 설정뿐 — 나머지는 파이프라인이 알아서 흘려보냅니다.
관측성 3 Pillars
세 축이 결합되어야 "어디서, 무엇이, 왜" 발생했는지 파악 가능
Traces (추적)
요청의 전체 실행 경로를 스팬 계층으로 기록 — "어디가 문제인가?"
Metrics (지표)
토큰 수, 지연, 에러율을 시계열로 집계 — "이상이 있는가?"
Logs (로그)
프롬프트 입출력, 도구 파라미터, 에러 상세를 이벤트로 기록 — "왜 발생했나?"
Traces가 어디, Metrics가 이상 여부, Logs가 왜를 답합니다 — 하나만으로는 근본 원인에 도달하지 못합니다.
StrandsTelemetry 초기화
1줄 설정으로 에이전트 전체 자동 계측 활성화
핵심 포인트
- StrandsTelemetry()
- 텔레메트리 진입점 — setup_* 메서드 체이닝으로 활성화
- setup_otlp_exporter()
- OTLP 전송 — HTTP/protobuf 전용(4318 포트), gRPC 아님
- setup_console_exporter()
- 스팬 콘솔 출력 — Collector 없이 로컬 디버그
- setup_meter()
- OTel Meter 활성화 — 커스텀 메트릭의 기반
- OTEL_TRACES_SAMPLER
- SDK 측 샘플링(traceidratio) — 비율은 SAMPLER_ARG=0.5
- gen_ai_span_attributes_only
- gen_ai.* 어트리뷰트만 내보내는 환경 변수 옵션
- 자동 계측
- invoke_agent · chat · execute_tool 스팬 + 토큰·지연 메트릭 자동 생성
코드
from strands import Agent
from strands.telemetry import StrandsTelemetry
from strands.models import BedrockModel
# Exporter + Meter 활성화 — 메서드 체이닝, HTTP/protobuf 전용
# 전송 대상: export OTEL_EXPORTER_OTLP_ENDPOINT=http://localhost:4318
StrandsTelemetry().setup_otlp_exporter() \
.setup_meter(enable_otlp_exporter=True)
# 에이전트 조립·실행 — 이후 모든 호출이 자동 계측됨
agent = Agent(
model=BedrockModel(model_id="us.anthropic.claude-sonnet-4-6"),
tools=[search, calculate],
)
result = agent("최근 3개월 AWS 비용 추이를 분석해줘")
# → invoke_agent / execute_event_loop_cycle / chat / execute_tool
# 스팬 자동 생성 + 토큰·지연·도구 호출 횟수 메트릭 자동 발행
트레이싱
trace_attributes · Custom Spans · Span Hierarchy
트레이싱
자동 계측이 만든 스팬 트리 위에 커스텀 스팬·속성을 얹는 확장
트레이싱
하나의 에이전트 호출이 스팬 트리로 분해되어 실행 경로 전체가 기록됩니다
- 커스텀 스팬 — OTel tracer API로 도구 내부 계측
- trace_attributes — 트레이스 공통 비즈니스 속성
- Span 계층 — Agent → Cycle → Model Invoke · Tool Execute
- gen_ai.* — GenAI Semantic Conventions 표준 속성
자동 계측이 뼈대를 만들고, 커스텀 스팬과 어트리뷰트가 비즈니스 맥락을 입힙니다.
커스텀 스팬과 어트리뷰트
OTel tracer API로 도구 내부에 커스텀 스팬과 속성을 추가
핵심 포인트
- trace.get_tracer
- OTel tracer 획득 — 커스텀 스팬 생성의 진입점
- start_as_current_span
- with 블록으로 자식 스팬 생성 — 도구 내부 구간을 독립 계측
- set_attribute
- 질의 · top_k · 결과 수 같은 비즈니스 속성을 스팬에 기록
- trace_attributes
- Agent 트레이스 전체에 공통 속성 부여 — session.id 등
코드
from strands import Agent, tool
from opentelemetry import trace
tracer = trace.get_tracer("my-agent") # 커스텀 스팬의 진입점
@tool
def knowledge_search(query: str, top_k: int = 5) -> list:
"""지식 베이스에서 관련 문서를 검색합니다."""
with tracer.start_as_current_span("kb_retrieve") as span:
span.set_attribute("search.query", query)
span.set_attribute("search.top_k", top_k)
results = bedrock_kb.retrieve(query=query, top_k=top_k)
span.set_attribute("search.result_count", len(results))
return results
# Agent에 트레이스 공통 속성 전달
agent = Agent(tools=[knowledge_search],
trace_attributes={"session.id": "abc-123"})
Span 계층 구조
하나의 에이전트 호출이 스팬 트리로 분해되는 구조 — 부모가 자식을 포함
병목이 보이면 트리를 따라 내려갑니다 — 루트에서 External Call까지 어느 층에서 시간이 새는지 드러납니다.
트레이스 워터폴 — 실물
요청 하나가 남긴 스팬 계단 — cost-analyzer 에이전트의 3사이클, 4,200ms
TRACE WATERFALL
gen_ai.agent.name = cost-analyzer · session.id = abc-123 · agent.loop.iterations = 3
Agent Loop Span
├ Model Invoke #1
├ Tool Execute · knowledge_search
│ └ External Call · POST → 200
├ Model Invoke #2
├ Tool Execute · knowledge_search
└ Model Invoke #3 — 최종 응답
Trace Attributes 스키마
GenAI Semantic Conventions 표준(gen_ai.*) + 커스텀 비즈니스 속성 — 두 층이 합쳐져 검색 가능한 트레이스
| 스팬 | 어트리뷰트 | 설명 | 예시 값 |
|---|---|---|---|
| 공통 | gen_ai.system | 기본 legacy 스키마 — OTEL_SEMCONV_STABILITY_OPT_IN=gen_ai_latest_experimental로 최신 semconv 전환 | strands-agents |
| Agent Loop | gen_ai.agent.name | 에이전트 식별자 | cost-analyzer |
| Agent Loop | agent.loop.iterations | 루프 반복 수 (커스텀 속성 예시) | 3 |
| Model Invoke | gen_ai.request.model | 요청 모델 ID | us.anthropic.claude-sonnet-4-6 |
| Model Invoke | gen_ai.usage.input_tokens | 입력 토큰 수 | 1,234 |
| Model Invoke | gen_ai.usage.output_tokens | 출력 토큰 수 | 567 |
| Tool Execute | gen_ai.tool.name | 도구 이름 | knowledge_search |
| Tool Execute | tool.duration_ms | 실행 시간(ms) (커스텀 속성 예시) | 320 |
| External Call | http.request.method | HTTP 메서드 | POST |
| External Call | http.response.status_code | 응답 코드 | 200 |
스팬 리댁션 — 민감 정보 마스킹
허용목록 밖 프롬프트·응답 원문을 SDK가 스팬 생성 시점에 차단 — python v1.47.0부터 지원
핵심 포인트
- [REDACTED]
- 허용목록에 없으면 gen_ai.input.messages · gen_ai.output.messages · gen_ai.system_instructions 값이 이 문자열로 치환
- 허용목록(glob)
- semconv 전환과 같은 환경 변수에 gen_ai_unredacted_attributes= 토큰 추가 — glob 패턴으로 원문 유지 대상 지정
- SDK 원천 차단
- 스팬 생성 시점에 마스킹 — 원문이 Exporter · Collector · 백엔드 어디에도 도달하지 않음
- Collector 필터와 대비
- attributes · filter processor는 수집 후 파이프라인에서 제거 — 전송 구간에는 원문 노출. 리댁션은 SDK에서 먼저 자름
코드
# 최신 semconv 모드 — 메시지 본문 어트리뷰트는 기본 리댁션
export OTEL_SEMCONV_STABILITY_OPT_IN=gen_ai_latest_experimental
# span: gen_ai.input.messages = [REDACTED]
# gen_ai.output.messages = [REDACTED]
# gen_ai.system_instructions = [REDACTED]
# 허용목록(glob) 지정 — 출력 메시지만 원문 유지
export OTEL_SEMCONV_STABILITY_OPT_IN="gen_ai_latest_experimental,\
gen_ai_unredacted_attributes=gen_ai.output.*"
# span: gen_ai.input.messages = [REDACTED]
# gen_ai.output.messages = [{"role": "assistant", ...}]
메트릭과 비용
result.metrics · Custom Meter · CloudWatch EMF
메트릭과 비용
호출 결과에서 바로 꺼내는 수치와, 그 위에 쌓는 운영 메트릭
result.metrics
에이전트 호출 결과에 토큰 · 지연 · 사이클 메트릭이 담겨 즉시 확인할 수 있습니다
- accumulated_usage["inputTokens"] — 키는 camelCase
- accumulated_metrics["latencyMs"] — 누적 지연
- cycle_count · tool_metrics — 루프와 도구별 통계
- get_summary() — 사람이 읽기 좋은 집계 dict
별도 설정 없이 호출 즉시 확인 — 비용은 토큰 수 × 단가 한 줄 계산이고, 시계열 추적은 CloudWatch EMF로 얹습니다.
result.metrics 즉시 확인
에이전트 호출 결과에서 토큰·지연·비용 메트릭을 즉시 추출
from strands import Agent
# 에이전트 조립·실행 — 별도 설정 없이 메트릭 자동 수집
agent = Agent(model="us.anthropic.claude-sonnet-4-6", tools=[search])
result = agent("최근 보안 취약점 요약해줘")
# result.metrics — EventLoopMetrics 객체, 호출 즉시 확인 가능
usage = result.metrics.accumulated_usage # 키는 camelCase
print(usage["inputTokens"]) # 2340
print(usage["outputTokens"]) # 891
print(usage["totalTokens"]) # 3231
# accumulated_*는 에이전트 재사용 시 전 요청 누적 —
# 요청 단위는 latest_agent_invocation·agent_invocations로 추출
# 누적 지연·사이클 수·도구별 메트릭·요약 dict
print(result.metrics.accumulated_metrics["latencyMs"]) # 4520
print(result.metrics.cycle_count) # 2
print(result.metrics.tool_metrics) # 도구별 호출 통계
print(result.metrics.get_summary()) # 사람이 읽기 좋은 집계
# 비용 계산 (Claude Sonnet 기준)
cost = (usage["inputTokens"] * 3 + usage["outputTokens"] * 15) / 1_000_000
print(f"이번 호출 비용: ${cost:.4f}") # $0.0204
핵심 메트릭 지표
에이전트 운영에서 반드시 추적해야 하는 수치
메트릭 수집 위치별 분류
SDK 자동 수집 vs CloudWatch 자동 발행 vs 커스텀 정의 구분
| 메트릭 | 수집 위치 | 타입 | 용도 |
|---|---|---|---|
| inputTokens / outputTokens | SDK (metrics.accumulated_usage) | Counter | 비용 산출, 프롬프트 최적화 |
| latencyMs | SDK (metrics.accumulated_metrics) | Histogram | P95 모니터링, SLA 준수 |
| cycle_count | SDK (result.metrics) | Gauge | 무한루프 감지, 효율 측정 |
| Invocations | CloudWatch (Bedrock) | Counter | 트래픽 파악, 용량 계획 |
| InvocationThrottles | CloudWatch (Bedrock) | Counter | 쿼터 한계 감지 |
| session_cost_usd | 커스텀 (EMF) | Gauge | 세션별 비용 추적, 예산 알람 |
| goal_completion_rate | 커스텀 (EMF) | Gauge | 품질 모니터링, 프롬프트 개선 |
SDK · CloudWatch · 커스텀 EMF 세 수집 위치를 구분하면, 이미 있는 메트릭을 다시 만드는 낭비를 막습니다.
ADOT 파이프라인
ADOT Collector · X-Ray · CloudWatch · 대시보드
ADOT Collector 아키텍처
Agent에서 X-Ray·CloudWatch까지 텔레메트리가 흐르는 경로
-
1
Strands Agent
StrandsTelemetry가 계측한 스팬·메트릭을 OTel SDK가 수집. OTLP 프로토콜로 직렬화하여 Collector에 전송 준비.
-
2
OTel SDK (Exporter)
BatchSpanProcessor가 스팬을 배치로 모아 전송. StrandsTelemetry의 OTLPSpanExporter는 HTTP/protobuf(4318)로 Collector에 push — Collector 수신부는 gRPC 4317도 함께 열 수 있음.
-
3
ADOT Collector
Receiver(OTLP) → Processor(batch, filter, attributes) → Exporter(awsxray, awsemf) 3단 파이프라인. ECS Sidecar 또는 EC2 데몬으로 배포.
-
4
X-Ray + CloudWatch
X-Ray가 트레이스를 저장하고 서비스 맵을 생성. CloudWatch가 메트릭을 저장하고 대시보드·알람을 구동.
Collector가 중간에 서 있어 백엔드 교체 · 필터링 · 배치를 애플리케이션 코드 수정 없이 처리합니다. 백엔드는 X-Ray 외에 Jaeger · Zipkin · Langfuse도 공식 문서에 등재되어 있습니다.
ADOT Collector 설정
otel-collector-config.yaml — Receiver, Processor, Exporter 3단 구성
핵심 포인트
- receivers.otlp
- SDK가 보내는 텔레메트리 수신 — gRPC 4317 · HTTP 4318
- processors.batch
- 5초 · 256개 배치 묶음 — filter를 추가하면 헬스체크 스팬 제외 같은 전처리 가능
- exporters
- awsxray는 트레이스를 X-Ray로, awsemf는 메트릭을 CloudWatch EMF로 발행
- service.pipelines
- traces · metrics 경로를 각각 Receiver → Processor → Exporter로 조립
코드
receivers:
otlp:
protocols:
grpc: { endpoint: 0.0.0.0:4317 }
http: { endpoint: 0.0.0.0:4318 }
processors:
batch: { timeout: 5s, send_batch_size: 256 }
exporters:
awsxray: { region: us-east-1 }
awsemf:
region: us-east-1
namespace: StrandsAgent
log_group_name: /strands/agent/metrics
service:
pipelines:
traces:
{receivers: [otlp], processors: [batch], exporters: [awsxray]}
metrics:
{receivers: [otlp], processors: [batch], exporters: [awsemf]}
대시보드 구성요소
운영팀이 에이전트 상태를 한눈에 파악하는 4패널 구성
Overview 패널
전체 요청 수, 성공률, P95 지연 — 현재 상태 한 줄 요약
Model 패널
모델별 토큰 사용량, 비용 추이, 스로틀링 횟수 추적
Tool 패널
도구별 호출 수, 실패율, 평균 지연 — 병목 도구 식별
Cost 패널
일간/주간 비용 추이, 에이전트별 비용 분배 — 예산 초과 조기 감지
Overview → Model → Tool → Cost — 넓게 보다가 좁혀 들어가는 4패널이 운영 화면의 기본형입니다.
메트릭 대시보드 — 실물
Strands가 내보낸 gen_ai.* · strands.* 데이터가 CloudWatch 4패널에서 읽히는 모습 (예시 수치)
CLOUDWATCH DASHBOARD
namespace = StrandsAgent · gen_ai.agent.name = cost-analyzer · 지난 3시간
Telemetry · Tracing · Metrics · ADOT — 에이전트 가시성의 4요소