MakeAI 딥러닝

에이전트 트레이싱 입문 — LLM 호출을 OpenTelemetry로 기록하기

에이전트가 왜 그런 답을 했는지 알려면 호출 하나하나를 추적해야 해요. 표준 OpenTelemetry 스팬에 LLM 입력·출력·도구 호출·토큰·비용을 어떻게 담는지 정리했어요.

4분 읽기

이번 주부터 에이전트 운영 이야기예요. 저희 Agent Ops 제품의 바탕이 되는 기술인데, 첫 글은 제일 기본인 트레이싱이에요. 에이전트가 이상한 답을 냈을 때 "왜?"를 알 수 있어야 고칠 수 있잖아요.

로그로는 부족했어요

처음엔 그냥 로그를 찍었어요. 프롬프트, 응답, 걸린 시간. 그런데 에이전트는 한 요청에 LLM을 여러 번 부르고, 중간에 도구를 호출하고, 그 결과로 또 LLM을 불러요. 로그 수십 줄 중에 어떤 게 어떤 요청에 속하는지, 어떤 순서로 일어났는지 다시 맞추는 게 일이었어요.

이건 분산 추적(distributed tracing)이 푸는 문제랑 똑같아요. 그래서 바퀴를 새로 만들지 않고 OpenTelemetry를 썼어요.

스팬 구조

OpenTelemetry에서 트레이스는 스팬(span)의 트리예요. 저희는 에이전트 실행을 이렇게 매핑했어요.

agent.run  (사용자 요청 하나)
├── llm.chat  (계획 세우기)
├── tool.call  (문서 검색)
│   └── http.request
├── llm.chat  (검색 결과로 답 만들기)
└── tool.call  (결과 전송)

루트 스팬 하나에 사용자 요청이 대응되고, 그 아래에 LLM 호출과 도구 호출이 자식으로 붙어요. 도구 안에서 또 외부 호출이 있으면 손자가 되고요.

LLM 스팬에 뭘 넣냐면요

OpenTelemetry에는 생성형 AI용 시맨틱 컨벤션(gen_ai.*)이 정리되고 있어요. 저희는 그 속성 이름을 따르되 몇 개 더 붙여요.

속성 예
gen_ai.system anthropic, openai, vllm
gen_ai.request.model 모델 이름
gen_ai.usage.input_tokens / output_tokens 토큰 수
gen_ai.request.temperature 등 생성 파라미터
makeai.prompt_version 프롬프트 템플릿 버전 (이게 중요해요)
makeai.cost_usd 토큰 × 단가
makeai.cache_hit 프롬프트 캐시 적중 여부

프롬프트 버전을 속성으로 넣는 게 핵심이에요. 다음 주에 쓸 회귀 평가가 이 속성으로 "버전 A와 B의 품질 차이"를 뽑거든요.

입력과 출력은 어디에 두나

프롬프트 전문과 응답 전문은 스팬 속성에 넣기엔 너무 커요. 그리고 문서 내용이 들어 있어서 폐쇄망 밖으로 보내면 안 돼요. 저희는 이렇게 해요.

  • 스팬 속성에는 해시와 길이, 앞 200자만 넣어요.
  • 전문은 별도 저장소(고객 환경 안)에 스팬 ID를 키로 저장해요.
  • 콘솔에서 스팬을 클릭하면 그 저장소에서 전문을 가져와 보여 줘요.

이렇게 하면 트레이스 자체는 가벼워서 오래 보관할 수 있고, 전문은 보존 기간을 따로 짧게 둘 수 있어요.

도구 호출 스팬

도구 호출은 tool.call 스팬이에요. 도구 이름, 인자(마찬가지로 요약), 결과 크기, 성공 여부를 넣어요. 중요한 건 LLM이 도구를 부르기로 결정한 스팬과 실제 실행 스팬을 연결하는 거예요. 모델이 도구를 두 번 부르려 했는데 한 번만 실행된 경우 같은 걸 찾을 수 있어요.

샘플링은 하지 마세요 (처음엔)

일반 웹 서비스 트레이싱은 1%만 샘플링하고 버려요. 에이전트는 달라요. 호출 수가 많지 않고, 문제는 항상 안 본 요청에서 나와요. 저희는 전부 기록하고 보존 기간으로 조절해요. 트래픽이 정말 커지면 "오류 난 것, 느린 것, 비용 큰 것"은 무조건 남기고 나머지만 샘플링하는 꼬리 샘플링(tail sampling)으로 가요.

실제로 잡은 것들

트레이싱을 넣고 나서 발견한 것들이에요.

  • 어떤 요청이 LLM을 같은 프롬프트로 세 번 부르고 있었어요. 재시도 로직 버그였어요. 비용 1/3 절감.
  • 도구 호출 중 하나가 평균 4초였는데 전체 지연의 70%였어요. 모델이 느린 줄 알았는데 아니었어요.
  • 특정 프롬프트 버전에서 출력 토큰이 두 배로 늘어 있었어요. "자세히 설명하라"는 한 줄이 원인이었어요.

이게 다 로그로는 못 찾던 것들이에요.

시작하는 분께

OpenTelemetry SDK를 쓰시면 수집기(collector)와 백엔드는 뭐든 붙일 수 있어요. 저희 Agent Ops도 표준 OTLP를 받아서, 이미 다른 도구를 쓰고 계셔도 같은 데이터를 보낼 수 있어요. 벤더 종속 없이 시작하실 수 있다는 게 OpenTelemetry를 고른 이유예요.

다음 주는 이 트레이스에서 프롬프트 변경이 품질을 깨뜨렸는지 아는 방법이에요.