DevOps AI 에이전트를 위한 관측성(Observability): 도구 호출(Tool Calls), 토큰, 그리고 폭주 루프(Runaway
요약
DevOps AI 에이전트를 프로덕션 서비스처럼 관리하기 위해 OpenTelemetry를 활용한 관측성(Observability) 구축 방법을 설명합니다. 에이전트의 실행 과정을 분산 추적(Distributed Tracing) 모델로 설계하여 도구 호출, 토큰 사용량, 비용 등을 체계적으로 모니터링하는 가이드를 제공합니다.
핵심 포인트
- 에이전트 실행을 하나의 트레이스로, 각 턴과 도구 호출을 스팬으로 모델링
- OpenTelemetry의 생성형 AI 시맨틱 컨벤션을 활용한 표준화된 데이터 수집
- 토큰, 비용, 지연 시간 및 가드레일 작동 여부의 실시간 쿼리 가능성 확보
- 에이전트의 행동 원인과 영향 범위를 파악하기 위한 계층적 스팬 트리 구조 설계
💡 devtocash.com에서 처음 게시되었습니다 — 이 가이드의 최신 정보가 유지되는 곳입니다. 저는 그곳에서 매주 실무 중심의 DevOps/SRE 심층 분석 글을 작성합니다.
자율 에이전트는 하나의 프로덕션 서비스입니다. 서비스처럼 계측(Instrument)하세요.
당신은 Ops 에이전트에게 범위가 제한된 MCP 서버를 부여하고, 감독 루프(supervised loop)로 감싸고, 프로덕션 투입 전 평가(evals)를 마쳤습니다. 이제 실행 중입니다. 새벽 3시에 에이전트가 어떤 조치를 취했고, 온콜(on-call) 담당자가 모든 자율 시스템에서 결국 유발하게 되는 질문을 던집니다: "무엇을 했고, 왜 그렇게 했는가?" 만약 당신의 답변이 "원시 모델 로그(raw model logs)를 스크롤해 볼게요"라면, 당신은 눈을 감고 프로덕션 서비스를 운영하고 있는 것입니다.
해결책은 당신이 다른 모든 서비스에 이미 적용하고 있는 것과 같습니다: 바로 **분산 추적(distributed tracing)**입니다. 각 에이전트 실행을 하나의 트레이스(trace)로 모델링하고, 각 턴(turn)과 각 도구 호출(tool call)을 스팬(span)으로 모델링하며, 중요한 수치들 — 토큰(tokens), 비용(cost), 지연 시간(latency), 그리고 어떤 가드레일(guardrail)이 작동했는지 — 를 연결하세요. 이 가이드는 DevOps 에이전트에 OpenTelemetry를 적용하여, "에이전트가 무엇을 했는가"가 고고학적 발굴이 아닌 쿼리(query)가 되도록 합니다.
에이전트의 실제 실행 방식을 반영하는 스팬 트리(span tree)
에이전트 실행은 자연스러운 계층 구조를 가지며, 이는 OpenTelemetry 스팬(span)과 일대일로 매핑됩니다:
agent.run (루트 스팬 — 하나의 인시던트 분류)
├── agent.turn 1
│ ├── llm.chat gen_ai.usage.* 토큰, 모델, 지연 시간
...
루트 스팬(root span)은 알람을 설정하는 단위입니다: 전체 실행에 대한 총 토큰, 총 비용, 실제 경과 시간(wall-clock), 그리고 턴 횟수입니다. 각 agent.turn은 이유→행동(reason→act)의 리듬을 보여줍니다. 각 tool.* 스팬은 실제 영향 범위(blast radius)가 있는 부분 — kubectl 또는 Terraform 프리뷰 — 이므로, 이곳에 속성(attributes)을 집중적으로 할당해야 합니다.
OpenTelemetry는 정확히 이 목적을 위한 **생성형 AI 시맨틱 컨벤션 (GenAI semantic conventions)**을 새롭게 도입하고 있습니다. 아직 안정화 단계에 있으므로 시맨틱 컨벤션(semconv) 버전을 고정(pin)하여 사용하되, 표준 이름(gen_ai.system, gen_ai.request.model, gen_ai.usage.input_tokens, gen_ai.usage.output_tokens)을 채택하면 별도의 커스텀 대시보드 없이도 모든 OTLP 백엔드에서 트레이스(trace)를 렌더링할 수 있습니다.
루프 계측 (Instrument the loop)
다음은 도구 호출(tool-calling) 루프를 최소한이지만 정직하게 계측(instrumentation)한 예시입니다. 트레이싱(tracing)은 이미 존재하는 루프를 감싸는 방식입니다. 에이전트의 로직을 변경하지 않으면서, 모든 결정 과정을 읽을 수 있게 만들어 줍니다.
# tracing.py — OTel 설정, 콜렉터(collector)로 OTLP 내보내기
from opentelemetry import trace, metrics
from opentelemetry.sdk.trace import TracerProvider
...
# agent.py — 이제 트레이싱이 적용된 루프
MAX_TURNS = 8
...
두 가지 세부 사항이 대부분의 가치를 제공합니다. 루트 스팬(root span)은 매 실행마다 turns_used와 tokens_total을 기록하며, MAX_TURNS에 의한 종료 시에는 agent.runaway=True 태그가 붙습니다. 따라서 통제 불능으로 치닫는 루프는 조용히 예산을 낭비하는 것이 아니라, 검색 가능한 이벤트가 됩니다. 이는 루프 엔지니어링 (loop engineering)에서 언급된 턴 제한(turn caps)을 런타임에서 강제하는 것입니다. 즉, 루프가 동작을 제한하고, 트레이스가 그 제한이 지켜졌음을 증명합니다.
도구 스팬(tool span)은 영향 범위(blast radius)가 존재하는 곳입니다
LLM 스팬은 모델이 무엇을 말했는지를 알려줍니다. 도구 스팬은 에이전트가 무엇을 했는지를 알려주며, 사후 분석(postmortem) 시에 실제로 확인하게 될 부분은 바로 이 스팬입니다.
def exec_tool_traced(call):
with tracer.start_as_current_span(f"tool.{call.name}") as span:
span.set_attribute("tool.name", call.name)
...
기록되는 항목을 주목하세요: 위험한 호출을 필터링할 수 있는 tool.mutating 플래그, 가드레일 (guardrail)이 작동했을 때의 tool.blocked 플래그, 그리고 모든 차단에 대한 **카운터 메트릭 (counter metric)**입니다. 해당 가드레일 카운터는 조기 경보 신호 역할을 합니다. 이는 evals 포스트에서 온라인 평가 (online evaluation)라고 부르는 것과 동일한 개념입니다. agent.guardrail.blocked 비율이 상승한다는 것은 모델이 해서는 안 될 행동을 반복적으로 시도하고 있음을 의미하며, 이는 모델이 취약점을 찾아내기 전에 반드시 확인해야 할 프롬프트 또는 모델 퇴보 (regression) 현상입니다. 그리고 인자 (arguments)를 기록하기 전에는 항상 redact()를 사용하세요. 트레이스 백엔드 (trace backend)는 클러스터 비밀 정보 (secrets)가 저장되는 곳이 아닙니다.
대시보드에 실제로 올려야 할 것들
트레이스 (Traces)는 단일 실행에 대한 심층 분석을 위한 것입니다. 메트릭 (Metrics)은 전체 플릿 (fleet) 관점의 뷰를 위한 것입니다. 다음 세 가지 패널은 여러분의 Prometheus + Grafana 설정 옆에 배치될 가치가 있습니다:
- 실행당 비용 (p50/p95).
agent.tokens_total× 모델 가격으로부터 도출됩니다. p95가 서서히 상승한다면 이는 모델이 더 수다스러워졌거나 컨텍스트 (context)가 비대해졌음을 의미합니다. 즉, 컨텍스트 엔지니어링 (context-engineering) 실패 모드가 가시화된 것입니다. 이는 실제 에이전트 지출에 대한 FinOps 관점을 구축하기 위한 기초 데이터입니다. - 실행당 턴 수 (histogram). 건강한 분류 (triage) 클러스터는 2~4턴 사이를 유지합니다.
MAX_TURNS쪽으로 꼬리가 두꺼워진다면(fat tail), 에이전트가 수렴하지 못하고 허우적거리고(thrashing) 있다는 뜻입니다. - 가드레일 차단율 (Guardrail-block rate). 정상 상태에서는 0에 가까워야 합니다. 지속적인 상승은 단순히 훑어보는 차트가 아니라 즉각적인 알람(alert) 대상입니다.
# 1시간 동안의 에이전트 실행당 p95 토큰 수 — 비용 조기 경보
histogram_quantile(0.95, sum by (le) (rate(agent_tokens_total_bucket[1h])))
...
트레이스는 실패 모드를 구체화합니다
사람들이 추상적으로 언급하는 모든 에이전트 실패 모드는, 계측 (instrumentation)이 완료되면 구체적인 트레이스 쿼리 (trace query)가 됩니다:
- 폭주 루프 (Runaway loop) →
agent.runaway=True, 또는agent.turns_used == MAX_TURNS로 트레이스 (trace) 추적. - 환각된 동작 (Hallucinated action) → 텍스트로는 수정을 주장하지만, 실제로 상태를 변경한
tool.*스팬 (span)이 없는llm.chat스팬. 모델은 "확장했습니다"라고 말했지만, 트레이스 상으로는 도구를 호출하지 않은 경우. - 조용한 비용 폭증 (Silent cost blowout) →
agent.tokens_total이 동일한 실행 그룹의 다른 사례들보다 훨씬 높은 경우로, 대개 하나의 거대한 도구 결과값이 컨텍스트 (context)에 다시 밀어 넣어졌을 때 발생. - 가드레일 탐색 (Guardrail probing) → 동일한 트리거로부터 여러 실행에 걸쳐 반복되는
tool.blocked=True스팬.
이것이 자율적인 작업이 실제로 의존하는 관측성 (observability)의 토대입니다. agent-harness-as-infrastructure 포스트에서는 이를 한 문장으로 요약합니다. "관측 불가능한 자동화는 아직 알아차리지 못한 장애일 뿐입니다." 트레이싱 (Tracing)은 바로 그것을 알아차리는 방법입니다. 이는 또한 AI-powered observability의 역방향이기도 합니다. 그곳에서는 AI를 여러분의 텔레메트리 (telemetry)에 적용하는 것이라면, 여기서는 AI로부터 텔레메트리를 생성하는 것입니다.
솔직한 한계
트레이싱은 에이전트가 무엇을 했고 비용이 얼마나 들었는지를 알려줍니다. 하지만 그 동작이 _정확했는지_는 알려주지 않습니다. 완벽하게 트레이싱된 실행이라도 매우 확신에 차서 잘못된 결정을 내릴 수 있습니다. 그 판단은 여러분의 평가 스위트 (eval suite)와 인간의 승인 단계 (human approval gate)에 맡겨져야 합니다. 데이터 마스킹 (Redaction) 또한 매우 중요하며 실수하기 쉽습니다. 트레이스 백엔드 (trace backend)는 장기간 보관되고 널리 읽히는 저장소이므로, 배포하기 전에 tool.args에 무엇이 담기는지 감사 (audit)하십시오. 또한 GenAI 시맨틱 컨벤션 (semantic conventions)은 여전히 변화 중이므로, 버전을 고정하고 속성 이름 (attribute names)이 안정화됨에 따라 조정할 준비를 하십시오.
작게 시작하십시오. 루프를 루트 스팬 (root span)으로 감싸고, 도구 호출마다 스팬을 추가하며, 토큰과 폭주 플래그 (runaway flag)를 기록하십시오. 그것만으로도 새벽 3시에 마주하는 "도대체 얘가 뭘 한 거지?"라는 대답 불가능한 질문을, 바로 열어볼 수 있는 단 하나의 트레이스로 바꿀 수 있습니다. 이것이 에이전트를 단순히 '실행'하는 것과 '운영'하는 것의 근본적인 차이입니다.
📌 이 가이드의 최신 버전 — 그리고 DevOps, SRE, Kubernetes, 관측성 (Observability) 및 클라우드 비용 (Cloud-cost) 가이드 전체 라이브러리 — 를 devtocash.com에서 확인하세요.
AI 자동 생성 콘텐츠
본 콘텐츠는 Dev.to AI tag의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기