
에이전트를 위한 트레이스 샘플링 (Trace Sampling): 중요한 궤적(Trajectories) 유지하기
요약
에이전트 시스템의 관측성을 높이기 위해 무작위 헤드 샘플링 대신 궤적(Trajectory) 단위의 샘플링이 필요함을 설명합니다. 에이전트의 실행 과정은 복잡하여 시작 시점의 결정만으로는 유의미한 데이터를 선별하기 어렵기 때문입니다.
핵심 포인트
- 헤드 샘플링은 에이전트의 복잡한 실행 과정을 파편화하여 디버깅을 어렵게 만듦
- 샘플링 단위는 개별 스팬이 아닌 전체 궤적(Trajectory)이어야 함
- 의미 있는 학습 데이터와 실패 사례를 보존하기 위한 전략적 샘플링 필요
- 도서: LLM 애플리케이션을 위한 관측성 (Observability for LLM Applications) — 트레이싱 (Tracing), 평가 (Evals), 그리고 신뢰할 수 있는 AI 배포
- 저자의 다른 저서: 프로덕션 환경의 에이전트 (Agents in Production) — The AI Engineer's Library (2권 시리즈)의 동반 도서
- 내 프로젝트: Hermes IDE | GitHub — Claude Code 및 기타 AI 코딩 도구를 사용하여 작업하는 개발자를 위한 IDE
- 나: xgabriel.com | GitHub
페이지 호출(paged)을 받았습니다. 분류(triage) 에이전트가 잘못된 라벨을 작성하고 있습니다. 백엔드를 열고 지난 1시간으로 필터링하여 실패한 실행(run)을 찾아냅니다. 그것을 클릭합니다. 트레이스(trace)의 절반만 거기에 있습니다. 5개의 채팅 스팬(chat spans)은 있지만, 도구 스팬(tool spans)도 없고, invoke_agent 부모 스팬도 없습니다. 누군가 비용을 절감하기 위해 헤드 샘플링 (head sampling)을 켰고, 샘플러는 어떤 트레이스에 속하는지 상관하지 않고 무작위로 10%의 스팬(spans)만 유지했습니다. 당신이 읽어야 할 단 하나의 궤적(trajectory)은 파편화되어 있습니다. 볼 수 없는 것은 디버깅할 수 없으므로, 원래 15분이면 끝났을 일을 대신해 다음 한 시간 동안 가공되지 않은 스팬(raw spans)을 읽으며 시간을 보냅니다.
이것이 바로 에이전트를 위한 트레이스 샘플링 (trace sampling for agents)이 존재하는 이유이자 방지하고자 하는 실패 사례입니다. 목표는 "데이터를 적게 유지하는 것"이 아닙니다. 목표는 어떤 것 하나를 절반만 남기는 일 없이, 당신에게 무언가를 가르쳐 주는 모든 궤적(trajectory)은 유지하고 그렇지 않은 것들은 버리는 것입니다.
헤드 샘플링 (head sampling)이 에이전트에서 실패하는 이유
헤드 샘플링 (head sampling)은 아무 일도 일어나기 전, 트레이스의 시작 시점에 결정합니다. 첫 번째 스팬(span)에서 주사위를 던져 10%는 유지하고 나머지는 버립니다. 이는 하나의 요청이 다른 요청과 비슷해 보이고 실패 사례가 이미 에러 트래커(error tracker)에 의해 포착되는 대량의 웹 트래픽 환경에서는 효과적입니다.
에이전트는 두 가지 가정을 모두 깨뜨립니다. 단 한 번의 실행이 5초 동안 30개의 스팬(span)과 14개의 결정(decision)에 걸쳐 이어집니다. 첫 번째 스팬에서 유지(keep)할지 버릴지(drop) 결정할 때, 당신은 이 실행이 search_kb를 20번 반복할지, 토큰 예산(token budget)을 초과할지, 아니면 조용히 오답을 내놓을지 아직 알 수 없습니다. 샘플링의 근거가 될 정보는 궤적(trajectory)이 완료될 때까지 존재하지 않습니다.
따라서 앞부분에서 무작위로 10%를 추출하는 방식은 실패 사례의 90%를 버리고 지루한 성공 사례의 10%만 남기게 됩니다. 이는 정반대입니다. 당신은 그 반대의 비율을 원할 것입니다.
궤적을 샘플링하라, 트리의 절반을 샘플링하지 마라
두 가지 규칙이 있으며, 두 번째 규칙은 팀들이 자주 잊어버리는 것입니다.
첫째: 샘플링 결정을 chat 레벨이 아닌 invoke_agent 레벨에서 내리십시오. 디버깅의 단위는 궤적(trajectory)입니다. 샘플링의 단위도 이와 일치해야 합니다.
둘째: 궤적을 유지할 때는 전체 트리(tree)를 유지하십시오. 모든 채팅 스팬(chat span), 모든 도구 스팬(tool span), 모든 중첩된 핸드오프(nested handoff)를 포함해야 합니다. 15개 중 5개의 채팅 스팬만 남긴 절반만 샘플링된 궤적은 트레이스(trace)가 아예 없는 것보다 더 나쁩니다. 왜냐하면 그것은 완전해 보이며 당신을 속이기 때문입니다. 당신은 그것을 읽고 살아남은 스팬들로부터 이야기를 재구성하지만, 그 이야기는 틀린 것이 됩니다.
이것이 바로 헤드 샘플링(head sampling)이 할 수 없는 일을 테일 샘플링(tail sampling)이 제공하는 방식입니다. 테일 샘플링은 전체 트레이스를 버퍼링하고, 완료될 때까지 기다린 다음 결정합니다. 결정 시점에는 결과, 지연 시간(latency), 비용을 알 수 있으므로 전체 트리를 하나의 단위로 유지하거나 버릴 수 있습니다.
항상 유지해야 하는 네 가지
테일 샘플링을 사용하면 실제로 발생한 일을 기반으로 유지 목록(keep-list)을 작성할 수 있습니다. 에이전트의 경우, 다음 네 가지 카테고리는 '항상 유지' 대상입니다.
실패한 실행(Failed runs). 스팬이 에러 상태(error status)를 나타내는 모든 궤적입니다. 예산을 초과했거나, 도구가 오류를 던졌거나, 가드레일(guardrail)이 차단한 경우 등이 해당됩니다. 이것들은 당신이 페이지(page) 알림을 받게 되는 실행들입니다.
느린 실행(Slow runs). 루트 스팬(root span)의 실행 시간이 지연 시간 임계값(latency threshold)을 초과하는 모든 궤적입니다. 분류(triage) 에이전트에게 10초가 걸렸다는 것은 루프를 돌았거나 도구가 멈췄음을 의미합니다. 느린 테일(slow tail)은 통제 불능의 실행들이 존재하는 곳입니다.
비싼 실행 (Expensive runs). 총 토큰 수가 임계값을 넘는 모든 궤적 (trajectory)이 대상입니다. 지연 시간 (latency)이 항상 비용을 포착하는 것은 아닙니다. 매 턴마다 컨텍스트가 축적되어 실행이 빠르게 끝나더라도 여전히 200K의 입력 토큰을 소모할 수 있습니다. 토큰을 기준으로 직접 샘플링하십시오.
평가 트래픽 (Eval traffic). 평가 스위트 (eval suite) 또는 CI에서 발생한 것으로 태그된 모든 실행은 항상 100% 유지합니다. 이것은 나중에 궤적을 평가할 때 기준이 되는 트래픽입니다. 확률적 드롭 (probabilistic drop)으로 인해 평가 트레이스 (eval trace)를 놓치는 것은 회귀 데이터 (regression data)에 구멍이 생기는 것을 의미합니다. 태그를 지정하고, 절대 샘플링하여 버리지 마십시오.
그 외의 모든 것은 지루한 해피 패스 (happy path)입니다. 정상적인 상태가 어떤 모습인지 파악할 수 있도록 약 10~20% 정도의 베이스라인 슬라이스 (baseline slice)만 유지하고 나머지는 버리십시오.
수집기 설정 (The collector config)
OpenTelemetry Collector의 tail_sampling 프로세서가 이 기능을 기본적으로 수행합니다. 각 정책은 OR 조건으로 작동합니다. 즉, 어떤 정책이라도 유지를 결정하면 전체 트레이스 (trace)가 유지됩니다.
processors:
tail_sampling:
decision_wait: 30s
...
decision_wait는 수집기가 첫 번째 스팬 (span) 이후 트레이스를 버퍼링한 뒤 결정을 내리기까지 기다리는 시간입니다. 이 값을 p99 궤적 지속 시간보다 높게 설정하십시오. 그렇지 않으면 결정이 내려진 후 느린 실행이 완료되어 결국 잘려 나가게 됩니다. num_traces는 인메모리 (in-memory) 버퍼 크기이며, 처리량(throughput)에 대기 시간 창(wait window)을 곱한 값으로 크기를 설정하십시오.
latency 정책은 궤적의 실제 경과 시간(wall-clock)인 루트 스팬 (root span) 지속 시간을 읽습니다. 이를 위해 자식 스팬 (child spans)을 합산하려고 하지 마십시오. 도구 호출 (tool calls)은 때때로 동시에 실행되므로 합계는 부정확할 수 있습니다.
시그널을 가시화하기 (Make the signals visible)
이러한 정책 중 두 가지는 실행 프레임워크 (harness)가 종료되기 전에 루트 스팬에 시그널을 기록해야만 작동합니다. 수집기는 여러분이 방출 (emit)하는 데이터를 읽습니다.
status_code: ERROR를 사용하려면 실행이 실패하거나 예산 (budget)이 초과되었을 때 스팬 상태 (span status)를 설정해야 합니다. gen_ai.usage.total_tokens를 사용하려면 호출당 토큰 수를 합산하여 invoke_agent 루트에 총합을 찍어주어야 합니다.
from opentelemetry import trace
from opentelemetry.trace import StatusCode
...
루트(root)에서의 롤업(rollup)은 여러분의 금융 대시보드가 이미 각 궤적(trajectory)별 비용을 위해 요구하는 것과 동일한 집계 방식입니다. 이를 한 번 방출(emit)하면 비용 뷰(cost view)와 샘플러(sampler)라는 두 시스템이 이를 읽어갑니다.
소스 단계에서 평가(eval) 트래픽에 태그 지정하기
keep-eval-traffic 정책은 태그의 품질에 따라 성능이 결정됩니다. 평가 하네스(eval harness)나 CI에서 발생하는 모든 실행에 대해 루트 스팬(root span)에 agent.traffic.source를 설정하면, 해당 실행들은 확률적 드롭(probabilistic drop)을 우회할 수 있습니다. 평가 스위트(eval suites)는 종종 TypeScript로 작성되므로, 실행이 시작되는 모든 곳에 태그를 지정해야 합니다:
import { trace, SpanStatusCode } from "@opentelemetry/api";
const tracer = trace.getTracer("triage-agent");
...
운영(Production) 트래픽은 속성(attribute)이 설정되지 않은 상태로 남겨두어 15%의 기본값(baseline)을 따르게 합니다. 평가 및 CI 실행은 태그를 포함하므로 매번 기록됩니다.
아무도 언급하지 않는 주의사항: 트레이스 ID(trace ID)로 라우팅하기
테일 샘플링(Tail sampling)이 결정을 내리려면 전체 트레이스(trace)가 한곳에 모여 있어야 합니다. 로드 밸런서(load balancer) 뒤에서 하나 이상의 컬렉터(collector)를 실행하는 경우, 단일 궤적의 스팬(span)들이 여러 인스턴스에 흩어질 수 있으며, 어떤 단일 컬렉터도 전체 트리(tree)를 보지 못할 수 있습니다. 각 컬렉터가 파편화된 정보만 보고 결정하게 되면, 결국 절반만 샘플링된 궤적 문제로 되돌아가게 됩니다.
해결책은 2계층 컬렉터 레이아웃(two-tier collector layout)입니다. 첫 번째 계층은 스팬을 수신하고, 트레이스 ID(trace ID)를 키로 사용하는 loadbalancing exporter를 통해 라우팅합니다. 이렇게 하면 하나의 궤적에 속한 모든 스팬이 동일한 두 번째 계층 컬렉터에 도달하게 됩니다. tail_sampling 프로세서는 각 트레이스가 온전하게 모인 두 번째 계층에서만 실행됩니다. 이 단계를 건너뛰면, 부하가 걸릴 때 '전체 트리를 유지한다'는 규칙이 조용히 깨지게 됩니다.
유지된 데이터를 관찰하며 튜닝하기
4개의 '항상 유지(always-keeps)' 규칙을 적용한 15% 베이스라인(baseline)에서 시작하여, 하루가 지난 후 유지된 트레이스(trace)의 혼합 비율을 살펴보세요. 만약 저장된 데이터 중 실패 사례와 느린 실행(slow runs)이 지배적이라면 베이스라인은 적절합니다. 만약 지루한 해피 패스(happy path)가 여전히 저장 공간을 차지하고 있다면, 베이스라인을 10%로 낮추세요. 만약 회귀(regression)를 비교하기 위해 더 많은 정상 실행 데이터가 필요하다고 느껴진다면, 베이스라인을 높이세요.
보호해야 할 대상은 저장 비용이 아닙니다. 그것은 당신이 알람(page)을 받았을 때, 그 단 하나의 궤적(trajectory)이 당신이 찾아 나설 때 백엔드에 온전하게 남아 있을 확률입니다.
이 내용이 유용했다면
이러한 궤적(trajectories)을 생성하는 에이전트 루프(agent loop)를 구축하고 있다면, _Agents in Production_에서 트레이스(trace)를 샘플링할 가치가 있게 만드는 하네스(harness), 예산(budgets), 그리고 결정 범위(decision spans)를 다룹니다. 만약 트레이싱(tracing)과 평가(eval) 측면을 연결하고 있다면, _Observability for LLM Applications_에서 OpenTelemetry 속성(attributes), 테일 샘플링(tail sampling), 그리고 골드 시퀀스(gold sequence)에 대비한 궤적(trajectories) 등급 매기기(grading)를 심도 있게 다룹니다. _The AI Engineer's Library_의 이 두 권의 책은 바로 이러한 종류의 문제를 해결하기 위해 나란히 놓이도록 기획되었습니다.
AI 자동 생성 콘텐츠
본 콘텐츠는 Dev.to AI tag의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기