신뢰성과 관찰성과 모니터링
신뢰성은 에이전트가 실패할 때 어떻게 실패하는가의 문제다. 절대 멈추지 않는 시스템을 만드는 것이 아니라, 멈출 때 그 사실을 즉시 알아채고 안전하게 회복하거나 사람에게 넘기는 시스템을 만드는 것이 목표다. 관찰성(observability)은 그 판단의 전제 조건이다. 지금 무엇이 일어나는지 보이지 않으면 에스컬레이션 조건도, 회복 시점도 판정할 수 없다. 아키텍트 시험은 "어떤 신호를 어디서 읽어, 어떤 회복 전략과 연결하는가"를 의사결정 중심으로 묻는다.
응답 메타데이터가 1차 관찰 표면이다
Messages API 응답은 운영에 필요한 신호를 이미 담고 있다. stop_reason은 모델이 왜 멈췄는지를 말해주는 가장 중요한 단일 신호다(end_turn, max_tokens, tool_use, pause_turn, refusal). usage 객체는 비용과 캐시 효율을 알려준다. input_tokens는 캐시되지 않은 잔여 토큰만이며, 실제 프롬프트 크기는 input_tokens + cache_creation_input_tokens + cache_read_input_tokens의 합이라는 점이 중요하다. 캐시 히트율을 모니터링하려면 cache_read_input_tokens가 반복 요청에서 0이 아닌지 확인한다. 0이라면 시스템 프롬프트의 datetime.now()나 비결정적 직렬화 같은 조용한 무효화 요인이 있는 것이다.
장애 보고에는 응답의 request_id를 함께 남긴다(파이썬 SDK는 message._request_id, 헤더는 request-id). 이 ID가 있어야 Anthropic이 요청을 끝까지 추적할 수 있다.
재시도와 백오프는 무엇이 재시도 가능한가에 달려 있다
신뢰성의 핵심은 오류를 분류하는 것이다. 재시도 가능한 오류와 불가능한 오류를 구분하지 못하면, 영구 실패를 무한 재시도하거나 일시 장애를 사람에게 넘기는 잘못을 범한다. 429(rate_limit_error), 500(api_error), 529(overloaded_error)는 재시도 가능하다. 400(invalid_request_error), 401, 403, 404는 재시도 불가다. 요청 자체가 잘못됐으므로 다시 보내도 같은 결과다.
SDK는 429와 5xx를 지수 백오프로 자동 재시도한다(기본 max_retries=2). 따라서 직접 재시도 루프를 작성하기 전에 SDK 기본 동작으로 충분한지 먼저 따져야 한다. 시험 함정: 4xx(429 제외)를 재시도 대상에 넣은 선택지는 오답이다. 429 응답의 retry-after 헤더를 읽어 대기 시간을 정하는 것이 정석이며, 타입드 예외 클래스(anthropic.RateLimitError 등)로 분기해야지 오류 메시지 문자열 매칭으로 분류하면 안 된다.
import anthropictry: resp = client.messages.create(model="claude-opus-4-8", max_tokens=16000, messages=msgs)except anthropic.RateLimitError as e: wait = int(e.response.headers.get("retry-after", "60")) # 재시도 가능, 대기 후except anthropic.BadRequestError: alert_engineer() # 재시도 불가, 요청을 고쳐야 함
장기 실행 에이전트는 이벤트 스트림으로 관찰한다
Managed Agents 세션은 SSE 이벤트 스트림으로 내부 진행을 노출한다. agent.message(텍스트 출력), agent.tool_use/agent.tool_result(도구 사용), session.status_*(상태 전이), session.error(처리 중 오류)가 핵심이다. 오류는 상태값이 아니라 session.error 이벤트로 나타난다는 점을 기억한다. 세션 상태 머신은 rescheduling → running ↔ idle → terminated이며, terminated는 되돌릴 수 없는 종료다.
비용·효율 모니터링의 핵심 신호는 span.model_request_end 이벤트의 model_usage 필드다. 여기에 input_tokens, output_tokens, cache_read_input_tokens, cache_creation_input_tokens가 호출 단위로 담겨, 세션 한 번이 토큰을 얼마나 쓰는지 추적할 수 있다. 컨텍스트가 압축되면 agent.thread_context_compacted 이벤트가 나온다.
직접 HTTP로 폴링 루프를 짤 때의 함정: requests의 timeout=(c, r)이나 httpx.Timeout(n)은 청크 단위 읽기 타임아웃이라 바이트가 들어올 때마다 리셋된다. 느리게 흘러오는 응답은 무한 대기할 수 있다. 하드 데드라인이 필요하면 루프 수준에서 time.monotonic()을 추적해 직접 끊거나, SDK의 events.stream() / events.list()를 쓴다.
폴링 대신 웹훅으로 상태 전이를 받는다
스트림을 계속 붙들거나 반복 폴링하는 대신, 웹훅으로 세션 상태 전이를 받을 수 있다. Anthropic이 HTTPS 엔드포인트로 얇은 페이로드(이벤트 타입 + 리소스 ID만)를 HMAC 서명해 POST한다. session.status_idled, session.status_terminated, vault_credential.refresh_failed 같은 data.type을 구독한다. 검증은 손으로 하지 말고 SDK의 client.beta.webhooks.unwrap()을 쓴다. 이 함수가 서명을 검증하고 약 5분 넘은 페이로드를 거부한다. 재시도가 같은 event.id로 오므로 그 ID로 중복 제거한다. 페이로드가 얇은 것은 의도된 설계이므로, 상세 상태는 ID로 리소스를 조회해서 얻는다.
정리
- 신뢰성의 목표는 무중단이 아니라 안전한 실패다. 멈춤을 즉시 감지하고 회복하거나 에스컬레이션하는 것이 핵심이다.
- 1차 관찰 표면은 응답의
stop_reason과usage다. 실제 프롬프트 크기는 세 토큰 필드의 합이며, 장애 보고엔request_id를 남긴다. - 재시도는 오류 분류에 달려 있다. 429·500·529는 재시도 가능, 4xx(429 제외)는 불가. SDK가 자동 백오프하므로 타입드 예외로 분기한다.
- 장기 세션은 이벤트 스트림으로 관찰한다. 비용은
span.model_request_end의model_usage, 오류는session.error이벤트로 잡는다. - 시험은 재시도 가능/불가 오류 분류, 토큰 합산 오해, 청크 단위 타임아웃 함정, 웹훅 서명 검증과 ID 기반 중복 제거를 묻는다.