에이전트 루프 트레이싱: OpenTelemetry gen_ai
왜 에이전트 루프에는 특별한 트레이싱이 필요한가
섹션 제목: “왜 에이전트 루프에는 특별한 트레이싱이 필요한가”일반적인 HTTP 요청은 단순하다. 요청 하나가 들어오고, 처리되고, 응답이 나간다. 시작과 끝이 명확하고, 지연(latency)은 하나의 숫자로 표현된다. 에이전트 루프는 다르다. 루프 한 번 실행은 수십 번의 모델 호출, 여러 도구 실행, 서브에이전트 위임으로 구성될 수 있다. 어떤 스텝이 느렸는지, 어느 도구 호출에서 에러가 발생했는지, 서브에이전트는 몇 번의 이터레이션을 거쳤는지 — 이 모든 것이 분산 추적(distributed tracing) 없이는 블랙박스가 된다.
OpenTelemetry는 이 문제를 해결하기 위해 **gen_ai 시맨틱 컨벤션(semantic convention)**을 정의했다. 모델 공급자·프레임워크·도구 실행 레이어가 서로 다른 팀의 코드로 이뤄져 있더라도, 동일한 속성명을 사용하면 단일 추적(trace) 트리로 통합해 볼 수 있다.
gen_ai 스팬 계층 구조
섹션 제목: “gen_ai 스팬 계층 구조”OpenTelemetry gen_ai 컨벤션은 에이전트 루프를 세 가지 스팬 유형으로 모델링한다.
┌─────────────────────────────────────────────────────────────────┐│ invoke_agent (루트 스팬 — 에이전트 루프 전체) ││ gen_ai.operation.name = "invoke_agent" ││ gen_ai.agent.name = "research_agent" ││ ││ ├── chat (모델 호출 스팬 — 이터레이션마다 생성) ││ │ gen_ai.operation.name = "chat" ││ │ gen_ai.request.model = "claude-opus-4-5" ││ │ gen_ai.usage.input_tokens = 4200 ││ │ gen_ai.usage.output_tokens = 312 ││ │ ││ ├── execute_tool (도구 실행 스팬) ││ │ gen_ai.operation.name = "execute_tool" ││ │ gen_ai.tool.name = "web_search" ││ │ gen_ai.tool.call.id = "call_abc123" ││ │ ││ └── invoke_agent (서브에이전트 위임 — 재귀 가능) ││ gen_ai.agent.name = "summarizer_agent" │└─────────────────────────────────────────────────────────────────┘invoke_agent 스팬은 에이전트 루프의 생애 전체를 감싼다. 루프가 시작될 때 열리고, 종료 조건이 충족되거나 오류가 발생할 때 닫힌다. chat 스팬은 매 이터레이션마다 모델을 한 번 호출할 때 생성된다. gen_ai.usage.input_tokens와 gen_ai.usage.output_tokens를 기록해 이터레이션별 토큰 비용을 추적할 수 있다. execute_tool 스팬은 도구 실행의 시작과 끝을 기록한다. gen_ai.tool.call.id로 모델이 요청한 특정 도구 호출과 연결된다.
핵심 gen_ai 속성
섹션 제목: “핵심 gen_ai 속성”| 속성 | 스팬 유형 | 예시 값 |
|---|---|---|
gen_ai.system |
모두 | "anthropic", "openai" |
gen_ai.operation.name |
모두 | "invoke_agent", "chat", "execute_tool" |
gen_ai.request.model |
chat | "claude-sonnet-4-5" |
gen_ai.agent.name |
invoke_agent | "research_agent" |
gen_ai.agent.description |
invoke_agent | 에이전트의 목적 설명 |
gen_ai.tool.name |
execute_tool | "bash", "web_search" |
gen_ai.tool.call.id |
execute_tool | 모델이 생성한 도구 호출 ID |
gen_ai.usage.input_tokens |
chat | 이터레이션의 입력 토큰 수 |
gen_ai.usage.output_tokens |
chat | 이터레이션의 출력 토큰 수 |
error.type |
모두 | 오류 발생 시 예외 클래스명 |
W3C 트레이스 컨텍스트 전파
섹션 제목: “W3C 트레이스 컨텍스트 전파”에이전트 루프가 외부 API를 호출하거나, MCP(모델 컨텍스트 프로토콜, Model Context Protocol) 서버와 통신하거나, 서브에이전트를 HTTP로 위임할 때, 루트 트레이스 ID를 함께 전달해야 모든 스팬이 하나의 추적 트리에 묶인다. W3C가 정의한 traceparent 헤더가 이 역할을 한다.
traceparent 헤더 형식은 {version}-{trace_id}-{parent_span_id}-{flags} 구조다. 에이전트 루프에서 도구가 외부 HTTP를 호출할 때 이 헤더를 포함시키면, 도구 서버가 같은 trace_id를 부모로 하는 스팬을 생성할 수 있다.
개념 이해용 의사 코드이며 실제 API와 다를 수 있습니다.
from opentelemetry import tracefrom opentelemetry.trace.propagation.tracecontext import TraceContextTextMapPropagator
tracer = trace.get_tracer("agent.loop", "1.0.0")propagator = TraceContextTextMapPropagator()
def run_agent_loop(task: str, tools: list) -> str: with tracer.start_as_current_span( "invoke_agent", attributes={ "gen_ai.system": "anthropic", "gen_ai.operation.name": "invoke_agent", "gen_ai.agent.name": "research_agent", } ) as agent_span: messages = [{"role": "user", "content": task}] while True: with tracer.start_as_current_span( "chat", attributes={ "gen_ai.operation.name": "chat", "gen_ai.request.model": "claude-sonnet-4-5", } ) as chat_span: response = call_model(messages) chat_span.set_attribute( "gen_ai.usage.input_tokens", response.usage.input_tokens ) chat_span.set_attribute( "gen_ai.usage.output_tokens", response.usage.output_tokens )
if response.stop_reason == "end_turn": return response.content
for tool_call in response.tool_calls: with tracer.start_as_current_span( "execute_tool", attributes={ "gen_ai.operation.name": "execute_tool", "gen_ai.tool.name": tool_call.name, "gen_ai.tool.call.id": tool_call.id, } ): # W3C traceparent를 헤더에 주입해 외부 서비스로 전파 carrier = {} propagator.inject(carrier) result = execute_tool(tool_call, headers=carrier) messages.append(tool_result(tool_call.id, result))MCP 서버 트레이싱
섹션 제목: “MCP 서버 트레이싱”MCP 서버는 에이전트 루프와 별도 프로세스나 서비스로 실행되는 경우가 많다. 에이전트가 MCP 도구를 호출할 때 traceparent를 함께 전달하면, MCP 서버 내부의 처리 스팬도 같은 추적 트리에 포함된다. MCP 명세는 이를 위한 전파 포인트를 제공하며, 이를 활용하면 “어느 MCP 도구가 전체 루프에서 가장 느렸는가”를 정확히 알 수 있다.
트레이싱으로 얻을 수 있는 것
섹션 제목: “트레이싱으로 얻을 수 있는 것”분산 추적이 구축되면 다음 질문에 데이터로 답할 수 있다.
- 병목 탐지: 어느 이터레이션, 어느 도구 호출이 전체 지연의 대부분을 차지하는가?
- 토큰 비용 귀인: 이터레이션이 진행될수록
input_tokens가 얼마나 빠르게 증가하는가? - 오류 전파 추적: 하나의 도구 실패가 이후 어느 스팬에 영향을 미쳤는가?
- 서브에이전트 성능: 위임된 서브에이전트가 얼마나 많은 이터레이션을 소비했는가?
다음 챕터에서는 이 트레이스 데이터를 바탕으로, 최종 답변만 평가할 것인지 아니면 전체 **궤적(trajectory)**을 평가할 것인지를 살펴본다.
참고 자료
- OpenTelemetry — GenAI Agent Spans Semantic Conventions — 접속 2026-06-30
- OpenTelemetry — AI Agent Observability — 접속 2026-06-30
- Anthropic — How we built our multi-agent research system — 접속 2026-06-30