AI 에이전트 장애를 제대로 추적하려면 모델 호출 로그만 모아서는 부족하다. 사용자 요청 하나를 루트 trace로 만들고, 에이전트 실행·모델 호출·검색·도구 호출·최종 응답을 parent-child span으로 연결해야 한다. 그래야 느린 구간, 토큰 급증 지점, 실패한 도구와 재시도 원인을 한 화면에서 찾을 수 있다. OpenTelemetry를 공통 전송 계층으로 두면 특정 관측 제품에 종속되지 않고 시작할 수 있다.
핵심 요약
- 첫 도입 범위는 trace_id, 단계, 지연, 상태, 토큰 사용량, 도구 이름으로 제한한다. 프롬프트와 응답 원문은 기본 수집 대상에서 뺀다.
- 하나의 사용자 요청 아래에 agent·model·tool·retrieval span을 중첩해야 병목과 실패 전파 경로가 보인다.
- 운영 대시보드는 평균보다 p95 지연, 오류율, 재시도율, 요청당 토큰, 도구별 실패율을 우선한다.
- OpenTelemetry GenAI 의미 규약은 별도 저장소로 이동해 계속 진화 중이므로 속성 이름을 코드 전역에 박지 말고 매핑 계층을 둔다.
AI 에이전트 추적은 일반 API 추적과 무엇이 다른가
일반 HTTP API는 요청 하나가 몇 개의 서비스 span으로 끝나는 경우가 많다. AI 에이전트는 한 요청 안에서 모델이 계획을 세우고, 여러 도구를 호출하고, 결과가 부족하면 모델을 다시 부르는 반복 구조를 가진다. 최종 응답은 성공했어도 중간 검색이 세 번 실패했거나 불필요한 모델 재호출로 비용이 커졌을 수 있다.
OpenAI Agents SDK 공식 문서는 기본 trace가 runner, task, turn, agent, generation, function tool, guardrail, handoff span을 포함한다고 설명한다. 특정 SDK를 쓰지 않더라도 이 계층은 실무 설계의 좋은 기준이 된다. 핵심은 로그 줄을 시간순으로 모으는 것이 아니라 어떤 단계가 어떤 부모 작업에서 파생됐는지 보존하는 데 있다.
| 질문 | 로그만 있을 때 | 연결된 trace가 있을 때 |
|---|---|---|
| 왜 12초가 걸렸나 | 타임스탬프를 수동 대조한다. | 모델·검색·도구 span의 지연을 분해한다. |
| 왜 토큰이 늘었나 | 요청별 합계만 보이기 쉽다. | 모델 재호출과 단계별 토큰을 찾는다. |
| 어디서 실패했나 | 예외 문자열을 검색한다. | 오류 span과 부모 agent 실행을 함께 본다. |
| 같은 대화인가 | 사용자 ID에 의존하기 쉽다. | 가명화한 session/thread 키로 trace를 묶는다. |

출처: OpenTelemetry·OpenAI Agents SDK 공식 문서 기반 구성
먼저 정할 최소 span 구조
처음부터 모든 이벤트를 수집하면 비용과 개인정보 위험만 커진다. 아래 다섯 단계로 시작하면 대부분의 운영 질문에 답할 수 있다. 에이전트가 검색을 쓰지 않으면 retrieval span은 생략하고, 여러 도구를 병렬 실행한다면 같은 부모 아래에 형제 span으로 둔다.
| span | 부모 | 최소 기록값 | 주요 질문 |
|---|---|---|---|
| request / workflow | 없음 | trace_id, route, total latency, status | 요청 전체가 성공했는가 |
| agent run | request | agent name, iteration, handoff | 몇 번 계획하고 위임했는가 |
| model call | agent run | provider, model, input/output token, latency | 비용과 지연이 어디서 늘었는가 |
| retrieval / tool | agent run | tool name, duration, status, retry count | 어떤 외부 작업이 실패했는가 |
| final response | request | result status, quality signal | 최종 결과가 기준을 통과했는가 |
W3C Trace Context의 traceparent를 서비스 경계에서 전달하면 웹 API, 작업 큐, 도구 서버가 같은 분산 trace에 참여할 수 있다. 다만 대화 전체를 하나의 무한히 긴 trace로 만들기보다 사용자 요청 단위 trace와 별도의 가명 session key를 함께 쓰는 편이 조회와 보존에 유리하다.
속성은 표준·조직·민감정보로 나눠 관리한다
OpenTelemetry 공식 사이트의 GenAI 의미 규약 페이지는 2026년 8월 현재 관련 규약이 별도 semantic-conventions-genai 저장소로 이동했다고 안내한다. 새 저장소에는 agent, inference, tool, retrieval 등 span 문서와 참조 시나리오가 있다. 아직 변할 수 있는 이름을 애플리케이션 곳곳에 직접 쓰면 업그레이드 비용이 커진다.
| 구분 | 예시 | 운영 원칙 |
|---|---|---|
| 표준 후보 | operation, provider, model, token usage, error type | 공식 규약의 안정성 상태를 확인하고 exporter 앞에서 매핑한다. |
| 조직 속성 | service tier, feature flag, release, tenant class | 개인 식별값 대신 운영 분류만 넣는다. |
| 상관관계 | trace_id, span_id, parent span, pseudonymous session | 원본 사용자 ID를 그대로 넣지 않는다. |
| 본문 데이터 | prompt, response, tool arguments | 기본 비수집, 필요 시 별도 동의·마스킹·짧은 보존을 적용한다. |
Langfuse 공식 OpenTelemetry 문서도 GenAI 속성 규약이 진화 중이며 수신 OTel trace를 자체 데이터 모델로 매핑한다고 밝힌다. 또한 baggage는 서비스와 외부 API 경계를 따라 전파되므로 비밀번호, API 키, 개인정보를 넣지 말라고 경고한다. 검색 필터 편의를 위해 민감값을 baggage에 넣는 설계는 피해야 한다.
Python에서 최소 추적을 붙이는 순서
다음 코드는 특정 LLM 프레임워크 자동 계측에 기대지 않고 구조를 이해하기 위한 최소 예시다. OpenTelemetry Python 공식 문서의 TracerProvider, BatchSpanProcessor, current span 구조를 따른다. app.* 속성은 이 글의 조직 예시이며 공식 GenAI 속성으로 오해하면 안 된다.
from opentelemetry import trace
from opentelemetry.sdk.trace import TracerProvider
from opentelemetry.sdk.trace.export import BatchSpanProcessor
from opentelemetry.exporter.otlp.proto.http.trace_exporter import OTLPSpanExporter
provider = TracerProvider()
provider.add_span_processor(
BatchSpanProcessor(OTLPSpanExporter())
)
trace.set_tracer_provider(provider)
tracer = trace.get_tracer("agent-service")
with tracer.start_as_current_span("agent.request") as root:
root.set_attribute("app.route", "support-agent")
with tracer.start_as_current_span("agent.model_call") as model_span:
model_span.set_attribute("app.model", selected_model)
result = call_model(messages)
model_span.set_attribute("app.input_tokens", result.input_tokens)
model_span.set_attribute("app.output_tokens", result.output_tokens)
with tracer.start_as_current_span("agent.tool_call") as tool_span:
tool_span.set_attribute("app.tool", "knowledge_search")
docs = search_knowledge(result.query)
- 개발 환경에서 console exporter로 구조를 먼저 확인한다. 부모·자식 관계가 끊기지 않는지 본다.
- OTLP exporter를 Collector 또는 관측 백엔드에 연결한다. 애플리케이션이 각 제품의 전용 전송 코드를 직접 갖지 않게 한다.
- 모델·도구 wrapper 한 곳에서 속성을 정규화한다. 프레임워크가 바뀌어도 대시보드 키를 유지한다.
- 오류와 재시도를 이벤트 또는 span 상태로 남긴다. 최종 성공만 기록하면 숨은 실패 비용을 놓친다.
- 샘플링과 보존 기간을 부하 테스트로 결정한다. trace 자체가 서비스 병목이 되지 않는지 확인한다.
운영 대시보드는 다섯 지표부터 만든다
대시보드에 모든 속성을 올리기보다 장애와 비용 결정을 직접 바꾸는 지표를 먼저 둔다. 아래 경보선은 제품 공식값이 아니라 초기 운영을 위한 예시다. 서비스의 정상 기준선을 수집한 뒤 조정해야 한다.
| 지표 | 보는 이유 | 초기 점검 조건 예시 |
|---|---|---|
| 요청 p95 지연 | 느린 소수 요청이 사용자 경험을 좌우한다. | 기준선 대비 2배가 15분 지속 |
| 모델 호출 횟수/요청 | 루프와 불필요한 재계획을 찾는다. | 상한 초과 요청 비율 증가 |
| 입력·출력 토큰/요청 | 비용 급증과 컨텍스트 팽창을 찾는다. | 배포 전후 중앙값·p95 비교 |
| 도구별 오류·재시도율 | 외부 API와 검색 장애를 분리한다. | 특정 도구 오류율이 기준선 초과 |
| 완료율·품질 신호 | 빠르지만 실패한 응답을 걸러낸다. | status OK와 평가 통과를 함께 집계 |
토큰 단가를 연결할 때는 모델과 컨텍스트 구간, 캐시 읽기·쓰기, Batch 여부에 따라 가격표가 달라질 수 있다. 따라서 span에는 계산된 비용만 남기기보다 provider, model, 입력·출력·캐시 토큰을 보존하고 비용 계산기를 별도 버전으로 관리하는 편이 감사와 재계산에 유리하다.
개인정보와 비용 때문에 실패하는 지점
- 프롬프트 원문 기본 수집: 고객 정보, 소스코드, API 키가 trace 백엔드로 복제될 수 있다. 메타데이터 우선 원칙을 적용한다.
- 모든 span 100% 장기 보존: 트래픽과 도구 호출 수가 늘면 저장·인덱싱 비용이 빠르게 커진다. 정상 요청은 head sampling, 오류·고지연은 tail sampling을 검토한다.
- 비동기 경계의 context 손실: 큐 worker와 병렬 도구 실행에서 부모 context를 전달하지 않으면 trace가 여러 조각으로 갈라진다.
- 속성의 무제한 cardinality: 원문 URL, 사용자 ID, 임의 오류 메시지를 인덱스 필드로 쓰면 조회 비용이 커진다. 정규화한 코드와 제한된 분류를 쓴다.
- 관측 백엔드 실패의 서비스 전파: exporter는 batch와 짧은 timeout을 쓰고, 추적 전송 실패 때문에 사용자 요청까지 실패하지 않게 분리한다.
OpenAI Agents SDK 문서는 추적이 기본 활성화될 수 있고, 전역 환경변수·코드·개별 실행 설정으로 끌 수 있다고 안내한다. 또한 Zero Data Retention 정책을 쓰는 조직에는 해당 trace 기능을 사용할 수 없다고 명시한다. 사용하는 SDK의 기본값과 데이터 정책을 배포 전에 반드시 확인해야 한다.
도입 단계별 체크리스트
| 단계 | 완료 조건 | 중단 조건 |
|---|---|---|
| 1. 로컬 구조 | 요청→agent→model/tool 계층이 보인다. | 부모 context가 자주 끊긴다. |
| 2. 스테이징 OTLP | Collector 수신과 exporter 재시도가 확인된다. | 전송이 응답 지연을 유의미하게 늘린다. |
| 3. 민감정보 점검 | 샘플 trace에 원문·키·개인정보가 없다. | 마스킹 이전 데이터가 남는다. |
| 4. 제한 배포 | p95·오류·토큰·도구율 대시보드가 동작한다. | cardinality와 저장량을 예측할 수 없다. |
| 5. 운영 확대 | 장애 한 건을 trace로 재현해 원인을 찾는다. | 수집은 되지만 실제 운영 질문에 답하지 못한다. |
이미 개념과 제품 선택부터 정리해야 한다면 AI 에이전트 관측성 가이드와 Langfuse·LangSmith·Phoenix·OpenTelemetry 비교를 먼저 보는 편이 낫다. 비용 분석까지 확장하려면 AI 에이전트 비용 추적 가이드를 함께 연결하면 된다.
FAQ
로그와 trace를 둘 다 남겨야 하나
둘의 역할이 다르다. trace는 요청의 단계와 부모·자식 관계, 지연을 보여주고 로그는 상세 사건과 디버그 문맥을 제공한다. 로그에 trace_id와 span_id를 넣어 상호 이동할 수 있게 만드는 구성이 실용적이다.
프롬프트와 응답을 저장하지 않으면 디버깅이 불가능하지 않나
대부분의 운영 장애는 모델명, 지연, 토큰, 도구 입력의 분류, 상태, 오류 코드만으로도 1차 진단이 가능하다. 원문이 꼭 필요한 평가 환경은 별도 동의, 마스킹, 접근 제어, 짧은 보존 기간을 적용해 분리한다.
OpenTelemetry만 설치하면 AI 비용도 자동으로 계산되나
그렇지 않다. OpenTelemetry는 신호와 context를 수집·전송하는 기반이다. 모델별 단가와 캐시·컨텍스트 구간을 반영한 비용 계산 로직 또는 이를 지원하는 백엔드가 별도로 필요하다.
trace는 요청마다 하나가 좋은가, 대화마다 하나가 좋은가
일반적으로 사용자 요청마다 trace를 만들고 가명 session 또는 thread 키로 여러 trace를 묶는 편이 낫다. 대화 전체를 하나의 장기 trace로 만들면 크기, 시간 제한, 조회가 복잡해진다.
가장 먼저 경보로 만들 지표는 무엇인가
오류율과 요청 p95 지연을 먼저 두고, 모델 호출 횟수와 요청당 토큰을 추가하는 순서가 현실적이다. 도구가 핵심인 서비스라면 도구별 오류·재시도율도 첫 단계에 포함한다.
참고 자료
- OpenTelemetry GenAI Semantic Conventions 저장소
- OpenTelemetry GenAI Agent Spans
- OpenTelemetry Python Instrumentation
- OpenAI Agents SDK Tracing
- Langfuse OpenTelemetry 문서
- W3C Trace Context
문서 확인 기준일은 2026년 8월 24일이다. OpenTelemetry GenAI 의미 규약은 진화 중이므로 실제 적용 전 저장소의 최신 상태와 사용하는 SDK·백엔드의 지원 범위를 다시 확인해야 한다.