6부 26장. Tracing과 Observability
에이전트가 프로덕션에 올라간 뒤 가장 자주 듣는 말은 의외로 거창하지 않다. "방금 이상한 답을 했어요.", "왜 이 도구를 호출했죠?", "비용이 갑자기 왜 이렇게 뛰었죠?" 같은 말이다. 문제는 이런 질문 앞에서 시스템이 너무 태연하다는 데 있다. HTTP 상태 코드는 200이고, 서버 CPU도 멀쩡하고, 에러 로그도 없다. 겉으로는 아무 일도 없었다. 그런데 사용자 입장에서는 분명히 뭔가 잘못됐다.
바로 이 지점에서 Tracing과 Observability가 필요해진다. 에이전트는 전통적인 웹 요청보다 훨씬 더 많은 중간 과정을 거친다. 모델이 한 번 생각하고 끝나는 것이 아니라, 검색을 하고, 도구를 고르고, 상태를 읽고, 다시 판단하고, 때로는 같은 작업을 몇 번이나 반복한다. 최종 답변만 보면 그럴듯한데, 실제 내부에서는 엉뚱한 길로 한참 돌아갔을 수도 있다.
핵심은 단순하다.
보이지 않는 에이전트는 고칠 수 없고, 측정 가능한 에이전트만 개선할 수 있다.

왜 에이전트는 특히 보이지 않기 쉬운가
기존 소프트웨어는 대체로 결정론적이다. 같은 입력이면 비슷한 코드 경로를 탄다. 물론 예외는 있지만, 개발자는 보통 함수 호출 흐름을 머릿속에 그릴 수 있다. 반면 에이전트는 같은 요청에도 다른 경로를 택할 수 있다. 어떤 날은 검색을 먼저 하고, 어떤 날은 바로 답하려 들고, 어떤 날은 불필요한 도구를 한 번 더 부를 수도 있다. 그래서 최종 출력만으로는 실패 원인을 추정하기 어렵다.3
더 까다로운 점은 실패가 겉으로 드러나지 않는다는 것이다. 에이전트는 틀려도 유창하다. 잘못된 문서를 읽고도 자연스럽게 요약하고, 적절한 도구를 골랐지만 인자를 틀리게 넣고도 아무렇지 않은 척 응답한다. 전통적인 APM은 이런 상황에서 응답 시간과 에러율은 보여 줄 수 있어도, 왜 그런 판단이 나왔는지는 설명하지 못한다.3
즉, 에이전트 운영의 진짜 문제는 장애가 아니라 불투명성이다. 서버가 죽으면 누구나 안다. 하지만 에이전트가 살아 있는 채로 틀리기 시작하면, 모르면 끝이다.
Tracing과 Observability는 어떻게 다른가
두 용어는 자주 같이 쓰이지만 완전히 같은 뜻은 아니다. Observability는 시스템이 왜 그렇게 행동했는지, 바깥에서 수집한 신호를 통해 설명할 수 있는 능력에 가깝다. 반면 Tracing은 그 능력을 만드는 핵심 기록 장치다. 한 번의 실행이 어떤 단계들을 거쳐 흘러갔는지를 이어 붙인 실행 지도라고 생각하면 쉽다.12
쉽게 비유해 보자. Tracing은 한 경기의 풀영상이고, Observability는 풀영상에 통계판, 선수 기록, 하이라이트, 판정 리플레이까지 붙인 운영실이다. 풀영상만 있어도 어느 정도는 볼 수 있지만, 추세를 보려면 통계가 필요하고, 품질을 따지려면 평가 기준이 필요하다.
그래서 실무에서는 보통 이렇게 나눈다.12
Traces: 한 요청의 전체 실행 경로
Logs: 각 단계에서 남긴 구조화된 사건 기록
Metrics: 많은 실행을 요약한 집계 수치
Evals와 Feedback: 결과가 실제로 좋았는지 판단하는 품질 신호
이 네 가지가 연결될 때 비로소 "무슨 일이 있었는가"를 넘어 "무엇을 고쳐야 하는가"까지 말할 수 있게 된다.
한 번의 실행을 어떻게 보아야 하는가
좋은 trace는 한 요청을 나무처럼 보여 준다. 맨 위에는 사용자 요청 또는 작업 시작이 있고, 그 아래에 모델 호출, 검색, 도구 실행, 상태 읽기와 쓰기, 승인 대기, 재시도 같은 하위 단계가 가지처럼 붙는다. 이를 보통 trace와 span으로 표현한다.
trace는 한 번의 실행 전체다. span은 그 안의 한 단계다. 예를 들어 사용자가 "이번 환불 요청 처리 가능 여부를 알려줘"라고 묻는다면, 하나의 trace 안에 다음과 같은 span들이 생길 수 있다.
정책 문서 검색
주문 상태 조회
결제 수단 조회
모델 판단
최종 설명 생성
여기서 중요한 것은 순서만 남기는 것이 아니다. 각 span마다 무엇을 함께 남길지가 더 중요하다. 최소한 시작 시각과 종료 시각, 성공 여부, 입력 요약, 출력 요약, 사용한 모델이나 도구 이름, 토큰 수, 비용, 부모 span과의 관계는 붙어 있어야 한다. 그래야 나중에 "느린 이유", "비싼 이유", "틀린 이유"를 서로 다른 관점에서 읽을 수 있다.
멀티턴 대화나 장기 실행 작업에서는 session도 함께 봐야 한다. 사용자의 한 세션 안에서 여러 trace가 이어질 수 있기 때문이다. 단발성 요청은 trace 하나로 충분할 수 있지만, 에이전트는 종종 세션 전체 문맥 속에서 의미가 생긴다. 어제 읽은 메모리 때문에 오늘 다른 결정을 했을 수도 있으니, trace와 session은 같이 묶여야 한다.
반드시 남겨야 할 다섯 가지 흔적
무엇을 추적할지 헷갈릴 때는 다섯 가지 흔적부터 생각하면 된다.
모델 호출
도구 호출
검색과 컨텍스트 조립
상태 변화
제어 흐름 변화
모델 호출에는 어떤 모델을 썼는지, 입력 토큰과 출력 토큰이 얼마였는지, 온도 같은 주요 파라미터가 어땠는지, 응답이 어느 정도 걸렸는지가 들어가야 한다. 에이전트 문제의 상당수는 사실 모델 문제가 아니라 모델 호출 방식의 문제이므로, 이 층을 빼먹으면 나중에 손댈 수 있는 레버가 사라진다.3
도구 호출에는 어떤 도구를 왜 선택했는지, 어떤 인자를 넘겼는지, 결과가 어땠는지, 실패했다면 어떤 유형의 실패였는지를 남겨야 한다. 올바른 도구를 잘못된 인자로 호출하는 경우는 생각보다 흔하다. 사람 눈에는 "도구는 썼네"로 보이지만, 실제로는 완전히 다른 요청을 보낸 셈이다.
검색과 컨텍스트 조립은 RAG 계열에서 특히 중요하다. 어떤 질의로 무엇을 찾았는지, 어떤 문서가 반환됐는지, 최종 프롬프트에 어떤 문맥이 들어갔는지를 봐야 한다. 검색이 엉뚱하면 생성이 아무리 좋아도 결과는 흔들린다. 프롬프트를 계속 고치는데 품질이 안 오르는 팀은, 종종 검색을 한 번도 제대로 들여다보지 않은 팀이다.
상태 변화는 더 중요하면서도 자주 빠진다. 세션 상태를 읽었는지, 작업 상태가 바뀌었는지, 메모리를 저장했는지, 승인 대기에서 실행 가능으로 바뀌었는지 같은 변화가 보여야 한다. 상태를 안 남기면 "왜 이번엔 다르게 행동했지?"라는 질문에 답할 수 없다.
마지막으로 제어 흐름 변화가 필요하다. 라우팅, 재시도, fallback, loop 진입, loop 종료, 사람 승인 요청, 조기 중단 같은 사건을 구조화해 남겨야 한다. 에이전트는 겉보기에 자연스러운 문장을 말하지만, 운영자는 문장이 아니라 흐름을 봐야 한다.
한 가지 주의할 점도 있다. 중간 판단을 남긴다고 해서 내부 추론 전문을 무조건 전부 저장하라는 뜻은 아니다. 실무에서는 구조화된 결정 기록이 더 유용한 경우가 많다. 예를 들어 "도구 A 선택", "근거: 최신 주문 정보 필요", "종료 이유: 필요한 정보 확보"처럼 요약된 판단 이벤트를 남기면 디버깅에는 충분하면서도 민감한 내용 노출을 줄일 수 있다.
비용 흐름까지 trace에 붙여라
에이전트 운영에서 비용은 나중 문제가 아니다. 성능과 함께 처음부터 봐야 하는 핵심 신호다. 이유는 간단하다. 에이전트의 비용은 요청 수만으로 결정되지 않기 때문이다. 같은 한 건의 요청도 어떤 경로를 탔느냐에 따라 토큰 사용량과 도구 호출 수가 크게 달라진다. 어떤 trace에서는 첫 번째 모델 호출이 거의 다 먹고, 어떤 trace에서는 재시도와 루프가 비용을 잡아먹는다.3
그래서 비용은 루트 trace에만 달면 늦다. span 단위까지 내려가야 한다. 최소한 다음은 붙여 두는 편이 좋다.
입력 토큰, 출력 토큰, 총 토큰
호출 모델과 가격 기준
span별 추정 비용
캐시 적중 여부 또는 캐시 절감량
최종 요청 단가와 세션 누적 비용
이렇게 해 두면 "왜 이번 주 예산이 튀었는가?"라는 질문을 감이 아니라 데이터로 답할 수 있다. 예를 들어 특정 하위 작업이 전체 비용의 70%를 먹는다거나, 특정 사용자 세그먼트에서만 불필요하게 긴 컨텍스트가 붙는다거나, 특정 모델 버전 교체 이후 출력 토큰이 급증했다는 식의 사실이 보인다.
비용은 성능 튜닝과도 바로 이어진다. 토큰을 많이 먹는 span은 대개 느리기도 하다. 긴 컨텍스트, 과도한 검색 결과, 쓸데없이 장황한 응답, 반복되는 재시도는 비용과 지연을 함께 끌어올린다. 운영자는 늘 "느린데 비싸기까지 한 경로"를 먼저 잡아야 한다.
로그와 메트릭은 trace의 친구다
trace만 있으면 모든 것이 해결될 것 같지만, 실제 운영에서는 그것만으로 부족하다. trace는 한 건을 깊게 보는 데 강하고, 메트릭은 수천 건을 넓게 보는 데 강하다. 로그는 그 사이를 메운다.
구조화 로그에는 적어도 trace_id와 span_id가 붙어야 한다. 그래야 특정 trace를 열었을 때 관련 로그를 함께 볼 수 있다. 자유 텍스트 로그만 잔뜩 쌓아 두면 결국 grep으로 숲을 헤매게 된다. 반면 JSON 형태의 구조화 로그는 필터링, 집계, 상관분석이 쉽다.4
메트릭은 대시보드와 알림의 언어다. 에이전트 운영에서 자주 보는 메트릭은 다음과 같다.5
p50, p95, p99 지연 시간
요청당 토큰 수와 비용
요청당 단계 수
도구 성공률과 타임아웃 비율
재시도율과 loop 발생률
사람 승인 전환율
guardrail 또는 정책 차단 비율
여기서 중요한 것은 평균보다 꼬리다. 평균 응답 시간은 멀쩡한데 p95가 갑자기 늘었다면, 일부 경로가 이상하게 길어졌다는 뜻일 수 있다. 평균 비용은 안정적인데 p99가 치솟는다면, 드물지만 매우 비싼 runaway run이 있다는 뜻이다. 에이전트 운영은 늘 긴 꼬리와 싸우는 일이다.
좋은 대시보드는 그래서 두 층을 함께 보여 준다. 위에서는 집계 추세를 보고, 아래에서는 문제 구간을 trace로 바로 타고 내려갈 수 있어야 한다. 메트릭이 경보를 울리고, trace가 원인을 보여 주는 구조다.
상태 변화가 보이지 않으면 절반만 본 것이다
에이전트는 상태를 읽고 쓰는 순간부터 비로소 에이전트다. 그런데 많은 팀이 관찰 가능성을 모델 호출과 도구 호출에만 한정한다. 그러면 시스템의 절반만 보는 셈이다.
예를 들어 사용자에게 이미 한번 거절된 작업을 다시 제안하는 문제가 발생했다고 해 보자. 겉으로는 모델이 멍청해 보인다. 하지만 실제 원인은 장기 메모리에 저장된 선호 정보가 갱신되지 않았거나, 작업 상태가 실패에서 대기로 잘못 되돌아갔거나, 사람 승인 결과가 다음 step에 전달되지 않은 것일 수 있다. 이런 문제는 상태 read와 write의 기록이 없으면 거의 추적할 수 없다.
그래서 상태 관찰의 기본 단위는 전체 스냅샷보다 변화량이어야 한다. 언제 어떤 키를 읽었고, 무엇이 무엇으로 바뀌었고, 어떤 span이 그 변화를 만들었는지를 남겨야 한다. 마치 계좌 잔액 전체보다 입출금 내역이 더 유용한 것과 같다.
특히 아래 항목들은 꼭 남기는 편이 좋다.
세션 상태의 주요 필드 변화
작업 상태 전이와 전이 이유
메모리 저장과 조회 이력
승인 요청, 승인 완료, 거절, 만료 같은 사람 개입 이벤트
외부 시스템에 대한 쓰기 시도와 최종 확정 결과
상태가 보이면 이상 행동의 원인이 비로소 드러난다. 모델이 바보여서가 아니라, 낡은 상태를 믿었기 때문에 틀린 것인지 구분할 수 있게 되는 것이다.
멀티에이전트와 백그라운드 작업에서는 컨텍스트 전파가 생명이다
단일 에이전트에서도 tracing은 중요하지만, 멀티에이전트와 비동기 구조로 가면 사실상 필수다. Planner가 일을 나누고, Worker들이 도구를 호출하고, 결과를 다시 Orchestrator가 합치는 구조에서는 한 요청이 여러 프로세스, 여러 서비스, 여러 큐를 건너다닌다. 이때 trace context를 전파하지 않으면, 운영 화면에는 서로 무관한 로그와 span들만 흩어져 보인다.7
핵심은 trace ID를 요청 헤더, 메시지 메타데이터, 작업 큐 payload 등에 실어 함께 보내는 것이다. 그래야 Agent A가 Agent B에게 일을 넘기고, Agent B가 도구 서비스에 API를 호출하고, 그 결과가 다시 돌아오는 전체 경로가 하나의 trace tree로 이어진다.7
이 구조는 fan-out과 fan-in에서도 특히 중요하다. 예를 들어 세 개의 서브에이전트를 병렬로 실행했다면, trace에서는 세 갈래가 동시에 펼쳐졌다가 마지막에 다시 합쳐져야 한다. 그래야 전체 지연의 병목이 누구였는지, 어떤 서브에이전트가 유난히 비용이 컸는지, 실패가 어디서 처음 시작됐는지가 한눈에 보인다.
백그라운드 작업도 마찬가지다. 웹 요청이 끝난 뒤 큐로 넘어간 작업은 trace가 끊기기 쉽다. 하지만 사용자 입장에서는 여전히 같은 일의 연장선이다. 따라서 동기 요청 trace와 비동기 후속 trace를 상위 작업 ID나 부모 trace ID로 연결해야 한다. 그래야 "버튼을 눌렀는데 왜 두 시간 뒤 잘못된 메일이 나갔는가"를 끝까지 추적할 수 있다.
OpenTelemetry와 프레임워크 내장 추적을 어떻게 볼 것인가
관찰 가능성 도구를 고를 때 가장 자주 나오는 질문은 이것이다. 프레임워크가 제공하는 내장 tracing을 쓸까, 아니면 OpenTelemetry 같은 표준을 쓸까?
최근 흐름은 OpenTelemetry가 공통 기반이 되는 쪽으로 가고 있다. 모델명, 토큰 수, 오류 여부, span 관계 같은 공통 의미를 표준화하면, 에이전트 실행을 기존 백엔드 서비스의 추적과 같은 파이프라인에서 볼 수 있기 때문이다.4 특히 이미 분산 추적 인프라가 있는 조직이라면, 에이전트만 따로 다른 섬으로 두는 것보다 같은 관찰 체계 안에 넣는 편이 운영이 훨씬 편하다.
다만 시작 속도만 놓고 보면 프레임워크 내장 SDK가 더 빠른 경우가 많다. 몇 줄 설정으로 모델 호출과 도구 호출이 자동으로 잡히고, 프레임워크가 아는 내부 구조까지 더 깊게 볼 수 있기 때문이다.3
그래서 실무적으로는 이렇게 생각하면 좋다. 초기에 빠르게 가시성을 얻고 싶다면 프레임워크 내장 추적부터 붙이고, 장기적으로는 OTel 호환 스키마와 trace context 전파를 맞춰 가는 식이다. 중요한 것은 특정 벤더 이름이 아니라, 나중에 다른 시스템과 연결될 수 있는 일관된 span 모델을 가지는 것이다.
저장은 많이, 노출은 적게
에이전트 관찰 가능성은 금방 욕심이 커진다. 프롬프트도 다 저장하고 싶고, 응답도 다 남기고 싶고, 검색 결과도 전부 기록하고 싶다. 물론 디버깅에는 도움이 된다. 하지만 이렇게 가면 곧바로 세 가지 문제가 온다. 저장 비용, 개인정보 노출, 그리고 운영 복잡도다.
그래서 프로덕션에서는 원칙이 필요하다. span 속성에는 전체 payload를 다 넣지 말고, 디버깅에 필요한 미리보기만 잘라 넣는 편이 낫다. 대략 200자에서 500자 수준의 요약만 넣고, 전문이 필요하면 별도의 재현 저장소나 보안이 강화된 원문 저장소에서 가져오는 방식이 흔하다.5
또한 모든 메타데이터는 100% 수집하되, 프롬프트와 완성문 전문은 샘플링하는 전략이 실용적이다. 예를 들어 토큰 수, 모델명, 지연 시간, 비용, 성공 여부는 전부 남기고, 실제 텍스트 본문은 10~20%만 샘플링하거나 특정 사용자, 특정 오류 유형에서만 강화 수집하는 식이다.8
민감정보는 더 조심해야 한다. 사용자 입력 안에 개인정보가 있을 수 있고, 검색 결과 안에 내부 문서가 섞일 수 있고, 도구 인자 안에 비밀 토큰이 들어갈 수 있다. 따라서 관찰 가능성 파이프라인에도 다음 원칙이 필요하다.
수집 전 또는 export 전 redaction
역할 기반 접근 제어
보존 기간 분리
재현용 원문 저장소의 암호화
규정상 저장 불가 데이터의 즉시 마스킹
쉽게 말해, 잘 보는 것과 많이 노출하는 것은 다르다. 관찰 가능성은 CCTV가 아니라 블랙박스에 가깝게 설계해야 한다.
관찰에서 개선으로 이어지는 루프
관찰 가능성의 진짜 목적은 예쁜 대시보드가 아니다. 개선이다. 가장 빠른 팀들은 프로덕션 trace를 단순 보관하지 않고, 개선 루프로 연결한다. 실제 실패 사례를 모으고, 패턴을 찾고, 그것을 테스트셋으로 바꾸고, 평가를 돌리고, 수정 전후를 비교한다.6
이 흐름은 대체로 다음과 같다.
프로덕션에서 이상한 trace를 수집한다.
실패 유형별로 묶는다. 예를 들어 검색 실패, 잘못된 도구 인자, 불필요한 loop, 비용 폭주 같은 식이다.
대표 사례를 골라 회귀 테스트셋을 만든다.
프롬프트, 라우팅, 도구 스키마, 상태 정책을 수정한다.
수정 전후 eval과 운영 메트릭을 비교한다.
다시 프로덕션에 반영한다.
이 과정을 거치면 trace는 단순한 사후 기록이 아니라 학습 재료가 된다. 한 번의 사고가 다음 배포를 더 안전하게 만든다. 좋은 팀일수록 장애 보고서를 쓰는 데서 끝나지 않고, 그 사례를 자동 평가와 회귀 검증에 편입시킨다.
이 점에서 tracing은 다음 장의 evals와 자연스럽게 이어진다. trace가 사실을 모으고, eval이 그 사실에 점수를 붙이며, 운영 메트릭이 개선 효과를 검증한다. 셋은 따로 놀면 힘이 약하고, 연결되면 시스템을 바꾼다.
좋은 운영 화면은 무엇을 보여 주는가
에이전트 운영 화면은 단순히 예쁜 그래프가 많다고 좋은 것이 아니다. 실제로 문제를 찾아서 고칠 수 있어야 한다. 좋은 운영 화면에는 보통 네 가지 관점이 함께 있다.
첫째, 실행 관점이다. 한 trace를 열었을 때 span tree와 시간 흐름이 보여야 한다. 무엇이 순차였고 무엇이 병렬이었는지, 어디에서 지연이 컸는지, 어느 span이 실패했는지가 바로 보여야 한다.
둘째, 비용 관점이다. 요청별, 에이전트별, 모델별, 사용자 세그먼트별 비용이 보여야 한다. 특히 상위 1% 고비용 trace를 바로 열어 볼 수 있어야 한다.
셋째, 품질 관점이다. thumbs up/down, 사람 검토 결과, 정책 위반 여부, 자동 평가 점수 같은 신호가 trace와 연결되어야 한다. "이 답변은 나빴다"에서 멈추지 않고 "어느 step 때문에 나빴는가"로 내려갈 수 있어야 한다.
넷째, 버전 관점이다. 프롬프트 버전, 모델 버전, 도구 스키마 버전, 배포 환경 태그가 붙어 있어야 한다. 에이전트의 회귀는 코드 배포 없이도 일어난다. 프롬프트 한 줄, 검색 인덱스 한 번, 모델 버전 변경 한 번이 행동을 바꾼다. 버전 태그가 없으면 원인 추적은 거의 운에 맡기는 셈이다.
도입은 작게, 그러나 구조는 제대로
처음부터 모든 것을 완벽히 계측하려고 들면 오히려 시작을 못 한다. 그렇다고 print문 몇 개로 오래 버티면 어느 순간 손에 잡히는 것이 하나도 없어지는 날이 온다. 현실적인 도입 순서는 대개 이렇다.6
첫 단계에서는 구조화 로그를 붙인다. 최소한 요청 ID, trace ID, 모델명, 도구명, 지연 시간, 토큰 수, 상태 코드는 남긴다.
둘째 단계에서는 span 기반 tracing을 붙인다. 모델 호출과 도구 호출에 부모-자식 관계를 만든다.
셋째 단계에서는 핵심 메트릭과 알림을 붙인다. p95 지연, 비용 급증, 도구 실패율, loop 증가를 본다.
넷째 단계에서는 replay와 회귀 검증으로 연결한다. 문제가 된 trace를 다시 돌려 보거나, 테스트셋으로 변환한다.
핵심은 작게 시작하되, 나중에 연결될 구조로 시작하는 것이다. 오늘은 로그 한 줄이어도, 내일 span이 되고, 다음 달에는 eval 데이터셋이 될 수 있어야 한다.
이번 장의 결론
에이전트를 운영한다는 것은 단지 모델을 호출하는 것이 아니다. 그 판단 경로를 보고, 도구 사용을 추적하고, 상태 변화를 설명하고, 비용 흐름을 통제하는 일이다. 눈에 보이지 않으면 디버깅은 추측이 되고, 추측에 기대는 팀은 개선 속도가 느릴 수밖에 없다.
Tracing은 한 번의 실행을 해부하는 도구다. Observability는 그 해부 결과를 운영, 품질, 비용, 보안의 언어로 연결하는 체계다. 좋은 에이전트 팀은 둘을 함께 만든다. trace로 원인을 찾고, 메트릭으로 추세를 보고, 로그로 디테일을 확인하고, eval로 개선 효과를 검증한다.
결국 운영 가능한 에이전트의 기준은 똑똑함만이 아니다.
무엇을 했는지 설명할 수 있어야 하고, 왜 그랬는지 추적할 수 있어야 하며, 다음에는 더 낫게 만들 수 있어야 한다.
그때부터 에이전트는 더 이상 신기한 블랙박스가 아니다. 관리 가능한 소프트웨어가 된다.
참고
3Agent Observability와 step-level tracing의 필요성
4OpenTelemetry 기반 AI Agent Observability 설명
5프로덕션 에이전트 추적 시 span payload 절단과 핵심 메트릭
6프로덕션 trace를 replay와 회귀 테스트로 연결하는 방법
이 노트는 보도·비평·교육·연구 목적으로 작성되었습니다. 저작권 문의가 있으시면 에서 알려주세요.