LLM 에이전트 관측성: 트레이스와 세션으로 재현한 10가지 문제
목차
LLM 에이전트의 관측성은 모델 호출을 두 가지 단위로 묶는 데서 시작합니다. 트레이스는 에이전트의 실행 한 번을 타임라인으로 보여주고, 세션은 대화 하나와 그 전체 비용을 보여줍니다. 각 호출에 요청 헤더 3개를 넣으면 LLM 게이트웨이가 요청 로그에서 둘 다 구성합니다. 별도의 tracing SDK는 필요 없습니다. LLM 게이트웨이는 코드와 모델 제공업체 사이에서 모든 요청을 기록하는 프록시입니다. 이 구성을 작은 고객 지원 에이전트와 Claude Code에 적용해 흔한 문제 10가지를 재현했습니다. 속도를 늦추는 문제 3개, 비용을 늘리는 문제 4개, 동작을 망가뜨리는 문제 3개입니다.
TL;DR
- 요청 헤더 3개(
X-Trace-Id,X-Span-Id,X-Session-Id)만으로 Synthorai 게이트웨이의 요청 로그를 트레이스와 세션으로 묶을 수 있습니다. tracing SDK는 필요 없습니다. - 서로 독립적인 하위 에이전트 검사 3개를 동시에 보내자 비용은 그대로였고, 소요 시간 중앙값은 3.7 s에서 1.8 s로 줄었습니다.
- GPT-6 Luna 프롬프트의 첫 줄에서 타임스탬프를 옮기자, 빈 캐시에서 시작한 캐시 적중률이 0%에서 73%로 올랐습니다.
- Claude Code 실행 한 번에 $0.1728이 들었고, 에이전트 3개의 첫 호출이 전체 비용의 72%를 차지했습니다.
이 글은 총 7편으로 구성된 가이드 시리즈의 개요입니다. 아래 각 절에서는 가이드 하나의 측정 결과를 요약하고, 코드와 스크린샷이 있는 본문으로 연결합니다. 모든 실행은 2026년 10월 10일과 11일에 진행했습니다. 지연 시간은 5회 실행의 중앙값이며, 나머지 수치는 모두 단일 실행에서 얻었습니다. 코드는 github.com/synthorai-io/use-cases에 있습니다.
LLM 에이전트에서 트레이스, 스팬, 세션이란?
트레이스는 사용자 메시지 하나나 작업 하나를 처리하는 동안 에이전트가 한 모든 일입니다. 스팬은 그 안의 개별 단계이고, 세션은 여러 트레이스로 이루어진 대화 전체입니다. 고객 지원 에이전트 예제에서 두 턴으로 이루어진 고객 대화는 트레이스 2개를 포함한 세션 하나입니다. 각 트레이스에는 모델 호출이 5개 또는 6개 들어갑니다. 일부 호출은 하위 에이전트가 보냅니다. 하위 에이전트는 주 에이전트가 작업 일부를 맡기려고 실행하는 별도 에이전트로, 자체 프롬프트를 쓰며 더 저렴한 모델을 쓰는 경우가 많습니다.
| 용어 | 범위 | 헤더 | 같은 값을 보내는 범위 |
|---|---|---|---|
| 트레이스 | 에이전트 실행 한 번 | X-Trace-Id | 해당 실행의 모든 호출 |
| 스팬 | 실행 내 모델 호출 하나 | X-Span-Id | 해당 호출만 |
| 세션 | 대화 하나 | X-Session-Id | 해당 대화의 모든 호출 |
| 에이전트 | 호출을 보낸 하위 에이전트 | X-Agent-Id | 해당 하위 에이전트의 모든 호출 |
| 라벨 | 사용자, 기능, 릴리스 | X-Synthorai-Metadata | 묶거나 필터링할 모든 호출 |
ID는 직접 정하는 문자열이며, 각각 출력 가능한 ASCII 문자 최대 128자까지 사용할 수 있습니다. 라벨은 문자열 값으로 이루어진 평면 JSON 객체입니다. 요청당 최대 10개이며, 각 값은 최대 128바이트입니다. 게이트웨이는 응답에 ID를 반환하며, 이 헤더들은 모델 제공업체에 전달하지 않습니다. OpenAI Python SDK에서는 요청별 헤더를 extra_headers에 넣습니다.
resp = client.chat.completions.create(
model="gpt-6-luna",
messages=messages,
extra_headers={
"X-Session-Id": session_id, # one per conversation
"X-Trace-Id": trace_id, # one per user message
"X-Span-Id": "plan", # one per call
"X-Synthorai-Metadata": '{"feature":"support-agent","_user":"u_1002"}',
},
)
에이전트가 이미 다른 도구를 통해 트레이스 ID를 보내고 있다면 아무것도 추가하지 않아도 될 수 있습니다. 요청에 세 ID 중 빠진 값이 있으면 게이트웨이는 OpenTelemetry, LiteLLM, Langfuse, Open WebUI, Claude Code 또는 OpenAI Codex CLI의 대응 헤더에서 값을 읽습니다. OpenTelemetry는 트레이싱을 위한 개방형 표준이며, traceparent 헤더에 트레이스 ID를 담습니다. HTTP 헤더 3개로 LLM 에이전트 추적하기에서는 예제 에이전트를 단계별로 만듭니다. GPT-6 Luna의 계획 호출, 로컬 도구 2개, DeepSeek V4.1 Flash의 병렬 검사 3개, 답변 호출로 구성됩니다. 예제 실행에서는 모델을 21번 호출했고 $0.0017이 들었습니다.
질문에 따라 어느 화면을 봐야 할까?
시리즈에서 다루는 각 질문은 콘솔의 특정 화면이나 콘솔 밖에서 로그를 읽는 방식으로 답할 수 있습니다. 콘솔은 각 트레이스를 워터폴로 표시합니다. 같은 시간축에 모델 호출마다 막대 하나를 그리므로, 병렬 호출은 겹치고 앞선 호출을 기다린 호출은 계단 모양으로 나타납니다.
| 질문 | 확인할 곳 | 가이드 |
|---|---|---|
| 이번 실행은 왜 8초나 걸렸을까? | 트레이스 요약의 게이트웨이 시간과 앱 시간 | 에이전트 지연 시간 |
| 이 대화에서 어느 턴의 비용이 가장 컸을까? | 트레이스별로 한 행씩 표시하는 세션 페이지 | 에이전트 비용 |
| 캐시 적중률이 왜 0%일까? | 세션 페이지의 캐시 적중 열 | 에이전트 비용 |
| 어제 이 사용자의 요청에 무슨 일이 있었을까? | 트레이스 목록의 라벨 검색 | 에이전트 디버깅 |
| Claude Code의 어느 하위 에이전트가 비용을 썼을까? | 세션 페이지의 에이전트 표 | Claude Code 하위 에이전트별 비용 |
| 채팅창에서 이런 질문을 할 수 있을까? | Claude의 MCP 커넥터 | Claude에 LLM 로그 질문하기 |
| 직접 만든 프로그램으로 로그를 조사할 수 있을까? | MCP 도구 또는 REST API | 에이전트에 로그 도구 제공하기 |
모든 트레이스 페이지에는 Compare 버튼도 있습니다. 두 트레이스를 한 페이지에 놓고 호출 이름으로 짝을 맞춰, 같은 시간축에 두 워터폴을 표시합니다. 시리즈의 모든 수정은 이 방식으로 수정 전후 실행을 비교해 확인했습니다.
LLM 에이전트가 느린 이유는?
먼저 트레이스 상단의 시간 구분을 봅니다. 첫 요청부터 마지막 응답까지 사용자가 기다린 실제 경과 시간을 게이트웨이 시간과 앱 시간으로 나눠 보여줍니다. 모델 요청이 하나라도 진행 중이면 게이트웨이 시간이고, 자체 코드만 실행 중이면 앱 시간입니다. 예제 트레이스에서는 전체 8.5 s 중 게이트웨이가 7.0 s, 앱이 1.6 s였으므로 모델 호출부터 살펴봐야 했습니다. 지연 시간 가이드에서는 원인 3가지를 재현하고 각각 5회 실행했습니다.
| 원인 | 트레이스에 나타나는 모습 | 수정 방법 | 수정 전 중앙값 | 수정 후 중앙값 |
|---|---|---|---|---|
| 독립적인 호출을 하나씩 전송 | 계단 모양으로 늘어선 막대 | Python에서 여러 호출을 동시에 기다리는 asyncio.gather로 함께 전송 | 3.7 s | 1.8 s |
| 호출 사이의 느린 코드 | 긴 your app 행 | 조회 4개를 동시에 실행 | 11.0 s | 9.2 s |
| 한 줄 답변에도 추론 사용 | 실제 답변보다 훨씬 많은 출력 토큰 | reasoning_effort="none" | 답변 5개에 25.6 s | 10.2 s |
느린 코드 사례에서는 두 실행의 모델 호출 시간이 거의 같았고, 앱 시간이 달라졌습니다. 가이드에 나온 실행 쌍에서 your app 행은 4.6 s에서 1.3 s로 줄었습니다. 모델을 바꿔도 이 구간은 줄일 수 없으므로 자체 프로파일러나 OpenTelemetry 스팬부터 확인해야 합니다. 세 번째 사례는 GPT-6 Luna로 실행했습니다. reasoning_effort는 답변 전에 모델이 얼마나 추론할지 정하는 요청 매개변수입니다. 이를 none으로 설정하자 답변 5개의 출력 토큰이 1,150에서 190으로 줄었습니다. LLM 에이전트가 느린 이유는?에서 세 사례의 코드와 트레이스 비교를 볼 수 있습니다.
LLM 에이전트 비용이 많이 드는 이유는?
트레이스 목록을 비용순으로 정렬한 뒤, 가장 비싼 세션을 열어 턴별 캐시 적중 열과 입력 열을 확인합니다. 캐시 적중률은 입력 토큰 중 제공업체의 프롬프트 캐시에서 읽은 비율입니다. 제공업체는 전에 본 프롬프트 접두부에 대해 일반 입력 가격의 일부만 청구합니다. 비용 가이드에서는 원인 4가지를 재현했습니다. 작은 예제라 금액은 1센트 미만입니다. 다른 환경에 적용할 때는 비율을 보면 됩니다.
| 원인 | 드러나는 위치 | 수정 전 | 수정 후 |
|---|---|---|---|
| 프롬프트 첫 줄의 타임스탬프 | 캐시 적중률이 0%로 유지 | 캐시 적중 0% | 캐시 적중 73%, 입력 비용 66% 감소 |
| 모든 도구 결과를 매번 다시 보내는 이력 | 턴마다 증가하는 입력 | 입력 토큰 24,546개 | 입력 토큰 8,053개 |
| 재시도 루프 | 한 트레이스에 과도하게 많은 호출, 상태는 모두 200 | 호출 10회 | 호출 2회 |
| 단순한 단계에 비싼 모델 사용 | 세션 페이지의 에이전트 표 | $0.0012 | $0.0003 |
첫 행은 GPT-6 Luna에서 빈 캐시로 시작한 결과입니다. 가이드에서는 캐시를 놓치는 또 다른 경우도 다룹니다. 사용자 메시지를 보낼 때는 지침을 덧붙이지만 저장할 때는 빼는 경우입니다. 그러면 매 턴 재구성한 프롬프트가 이전 턴에 실제로 보낸 프롬프트와 달라집니다. 보낸 그대로 메시지를 저장하자 해당 대화의 적중률이 0%에서 76%로 올랐습니다. 이력 사례에서는 입력 토큰이 3배였지만 71%를 캐시에서 읽었기 때문에 입력 비용은 21%만 늘었습니다. 마지막 행에서는 GPT-6.1 Sol로 수행한 하위 에이전트 검사 3개가 세션 비용의 81%를 차지했습니다. 같은 검사를 DeepSeek V4.1 Flash로 수행하니 비용이 10분의 1이었습니다. LLM 에이전트 비용이 많이 드는 이유는?에서 각 사례를 살펴봅니다.
사용자 제보로 LLM 에이전트를 디버깅하려면?
라벨로 사용자의 실행을 찾은 뒤, 문제가 난 호출이 들어 있는 트레이스를 읽습니다. 문제가 난 호출도 상태 코드가 200인 경우가 많습니다. 모든 요청에 _user 라벨을 붙였다면 트레이스 목록 검색에 metadata._user = u_1002를 입력해 해당 사용자의 세션과 트레이스를 찾을 수 있습니다. 디버깅 가이드에서는 다음 장애 3가지를 재현했습니다.
- 404 오류와 재시도. 존재하지 않는 모델 ID로 보낸 호출은 95 ms 만에 실패했고 비용은 $0이었습니다. 재시도에서 트레이스 ID를 유지하고 새 스팬 이름(
policy-check.retry)을 썼기 때문에, 실패한 호출과 재시도가 워터폴에 나란히 나타납니다. - 상태 코드 200으로 반환된 잘린 답변.
max_tokens=40으로 설정하고 추론을 켜둔 상태에서 DeepSeek V4.1 Flash를 연속 3번 호출했습니다. 모두 상태 코드 200, 출력 토큰 정확히 40개, 빈 답변을 반환했습니다. 추론이 토큰 한도를 모두 쓴 탓입니다. 트레이스에는 같은 스팬이 3번 나타나며 출력 토큰 수가 한도와 같습니다. 한도를 2,000으로 올리고 추론을 끄자 호출 한 번으로 49토큰의 답변을 받았습니다. 한도만 올려도 해결됐지만, 그 호출은 추론에 1,876토큰을 쓰고 22 s가 걸렸습니다. - 두 번째 턴의 400 오류. 저장된 이력에서 도구 호출 하나가 누락되자, 첫 턴은 정상 처리했던 GPT-6 Luna가 두 번째 턴을 0.5 s 만에 거부했습니다. 비용은 청구되지 않았습니다.
두 번째 장애 때문에 모든 호출에서 finish_reason을 기록해야 합니다. 모델이 생성을 멈춘 이유를 나타내는 응답 필드입니다. 값이 length라면 답변이 max_tokens 한도에 도달했다는 뜻이며, 상태 코드가 200이어도 실패로 집계해야 합니다. LLM 에이전트 디버깅하기에서는 모든 에이전트가 기록해야 할 필드 5개를 정리합니다. 이 글 후반에도 같은 목록을 실었습니다.
직접 만들지 않은 코딩 에이전트도 추적할 수 있을까?
Claude Code는 모든 요청에 이미 세션 ID와 에이전트 ID를 보내므로 수정 없이 추적할 수 있습니다. 환경 변수 2개로 게이트웨이를 사용하도록 설정하고, 세 번째 변수로 하위 에이전트 유형까지 추가할 수 있습니다.
export ANTHROPIC_BASE_URL="https://synthorai.io" # no /v1: Claude Code adds /v1/messages itself
export ANTHROPIC_API_KEY="<your gateway key>"
export CLAUDE_CODE_GATEWAY_HINT_HEADERS=1 # optional: names sub-agent types such as Explore
Claude Sonnet 5.5를 쓰는 Claude Code에서 하위 에이전트 2개로 실패한 테스트 하나를 고쳤습니다. 요청은 11번, 비용은 $0.1728이었습니다. 세션 페이지에서 에이전트별로 나눠 보니 하위 에이전트 2개가 비용의 39%, 캐시 쓰기가 77%, 에이전트 3개의 첫 호출이 72%를 차지했습니다. 30초 뒤 캐시가 채워진 상태에서 같은 실행을 반복하자 비용은 $0.0812였습니다. Claude Code 하위 에이전트별 비용에서는 팀 비용을 개발자와 저장소별로 나누는 방법도 다룹니다.
모델이 대신 트레이스를 읽을 수 있을까?
게이트웨이가 요청 로그를 MCP 서버로 제공하므로 모델도 MCP를 통해 로그를 읽을 수 있습니다. MCP는 Model Context Protocol의 약자로, 모델에 도구를 제공하는 표준 방식입니다. 로그를 읽는 방식은 누가 질문하느냐에 따라 달라집니다.
| 질문 주체 | 사용할 방식 | 모델 토큰 | 측정 결과 |
|---|---|---|---|
| 질문이 있는 사람 | 게이트웨이를 커넥터로 추가한 Claude | 채팅 요금제에 포함 | 질문 3개에 도구 호출 14회, 모든 수치가 청구 기록과 일치 |
| 고정된 쿼리를 실행하는 프로그램 | REST API | 없음 | 비용 상위 3개 세션 조회에 $0 |
| 조사 대상을 스스로 정하는 자체 에이전트 | 프로그램 내 MCP 도구 | 별도 비용 발생 | 비용 조사 한 번에 $0.0015 |
채팅 사례에서는 Claude Fable 5.1이 OAuth로 연결했고 요청 로그에 대한 읽기 권한만 받았습니다. OAuth는 API 키를 붙여 넣는 대신 브라우저에서 로그인하는 방식입니다. 이 연결로 읽기 전용 도구 4개와 7일 뒤 만료되는 키가 발급됐습니다. 도구는 비용, 토큰, 상태를 반환하며 프롬프트나 답변 본문은 반환하지 않습니다. 프로그램 사례에서 GPT-6 Luna는 세션 26개 중 캐시, 잘못된 모델 선택, 재시도 루프 문제가 있는 세션을 도구 호출 한 번으로 찾아냈고 $0.0015이 들었습니다. Claude Sonnet 5.5는 가격을 조회한 뒤 도구 호출 3번으로 같은 세션을 찾았고 $0.0631이 들었습니다. Claude에 LLM 로그 질문하기와 에이전트에 로그 도구 제공하기에서 두 구성을 다룹니다.
모든 에이전트가 기록해야 할 항목은?
필드 5개로 이 시리즈의 모든 문제를 확인할 수 있습니다.
- 실행별 트레이스 ID와 대화별 세션 ID: 헤더로 보내고 자체 로그에도 기록합니다.
- 단계와 시도를 나타내는 호출별 스팬 이름:
plan-2나policy-check.retry처럼 지정합니다. - 사용자, 기능, 릴리스 라벨: 사용자 제보나 청구 금액에 해당하는 실행을 좁혀 찾을 수 있어야 합니다.
- 모든 호출의
x-request-id응답 헤더: 나중에 해당 호출의 청구 비용을 조회할 때 사용합니다. - 모든 호출의
finish_reason과 출력 토큰 수:length는 실패로 처리합니다.
모든 호출에 클라이언트 타임아웃과 max_tokens도 설정하되, 둘 다 여유 있게 잡으세요. 위에서 나온 빈 200 응답은 눈에 보이는 답변 길이에만 맞춰 한도를 정해서 발생했습니다. 추론 모델은 긴 호출 하나에 몇 분이 걸릴 수도 있습니다.
게이트웨이 측 트레이싱으로 볼 수 없는 것은?
요청 로그 기반 트레이싱은 모델 호출만 다루며 자체 코드 내부는 보여주지 못합니다. 느린 코드 사례에서 봤듯이 게이트웨이는 요청 사이의 간격으로 코드 실행 시간을 추정합니다. 도구, 검색, 큐의 스팬을 기록하려면 OpenTelemetry SDK가 필요합니다. 게이트웨이는 X-Trace-Id가 없으면 traceparent를 읽으므로 두 화면에서 같은 트레이스 ID를 사용할 수 있습니다. 트레이스에는 프롬프트와 응답 본문도 포함되지 않으며, 비용은 사후에 표시됩니다. 실시간 예산을 관리하려면 자체 에이전트 루프에서 각 응답이 도착할 때마다 usage에 가격을 적용해 계산해야 합니다.
자주 묻는 질문
LLM 에이전트 관측성이란?
LLM 에이전트 관측성은 실행마다 에이전트가 무엇을 했는지 파악할 수 있는 능력입니다. 어떤 모델을 어떤 순서로 호출했는지, 각 호출에 얼마나 걸리고 얼마가 들었는지, 무엇이 실패했는지를 확인합니다. 실제로는 호출을 트레이스(실행 한 번)와 세션(대화 하나)으로 묶고, 사용자와 기능 라벨을 붙이는 방식으로 구현합니다.
함께 읽을 글
- HTTP 헤더 3개로 LLM 에이전트 추적하기: 예제 에이전트와 헤더를 단계별로 구성합니다.
- 요청 헤더 레퍼런스: 이 글의 모든 헤더와 다른 도구에서 읽는 대체 헤더를 정리했습니다.
- 콘솔의 트레이스와 세션: 목록, 워터폴, 세션 페이지를 설명합니다.
- LLM 토큰 사용량 해부: 각 기록의 토큰 수가 무엇을 뜻하는지 제공업체별로 설명합니다.
- 프롬프트 캐싱 완벽 가이드: 프롬프트 접두부가 캐시에 적중하거나 적중하지 않는 이유를 설명합니다.