
Sonnet 비용의 1/30 수준으로 구현하는 LLM 모니터링용 Trace Digest
요약
LLM 에이전트 모니터링을 위한 트레이스 다이제스트(Trace Digest) 생성 시, Claude Sonnet 대비 비용을 1/30 수준으로 절감하는 최적의 모델 선정 과정을 다룹니다. 단순히 저렴한 모델이 아닌, 검색 결과의 정확도를 유지할 수 있는 모델을 검증하는 방법론을 제시합니다.
핵심 포인트
- 에이전트 트레이스를 검색 가능한 다이제스트로 축소하여 모니터링 효율화
- Claude Sonnet 대비 비용을 약 30분의 1로 절감하는 모델 구현 성공
- 단순 요약 성능이 아닌 검색 결과의 일관성을 기준으로 모델 검증
- 비용 효율성과 검색 정확도 사이의 트레이드오프 최적화
우리는 수집하는 모든 에이전트 트레이스(trace)에 대해 하나의 LLM 호출을 실행합니다. 이를 통해 트레이스를 짧고 검색 가능한 다이제스트(digest)로 축소합니다. 모든 고객의 모든 트레이스에 대해 실행되기 때문에, 이는 우리가 수행하는 가장 빈번한 LLM 호출이며, 사용량에 따라 비용이 가장 빠르게 증가하는 항목입니다.
그래서 우리는 더 저렴한 모델이 이 작업을 수행할 수 있는지 테스트했습니다. 현재 Sonnet 4.6은 트레이스 1,000개당 약 3.45달러의 비용으로 해당 다이제스트를 작성하며, 많은 모델이 이 비용의 아주 일부분만 소모합니다. 우리는 50개의 고정된 트레이스 세트를 대상으로 4개의 모델을 Sonnet과 비교했습니다. 우리가 실제로 배포한 모델은 검색 결과가 동일한 트레이스를 반환하면서도 비용은 약 30분의 1 수준으로 실행되었지만, 가격표상 가장 저렴해 보였던 두 모델은 모두 실패했습니다.
더 어려운 부분은 이를 검증하는 것이었습니다. 저렴한 모델은 완벽하게 읽히는 요약문을 작성할 수 있지만, 검색 결과는 여전히 바꿀 수 있기 때문에, 우리는 각 모델의 다이제스트가 제공하는 검색 결과에 대해 점수를 매겼으며, 동일한 쿼리가 동일한 트레이스를 반환하는지 확인했습니다.
복잡한 트레이스는 하나의 구조화된 다이제스트 호출이 되며, 이 다이제스트가 바로 검색 대상이 됩니다.
동기 (Motivation)
우리는 프로덕션 환경의 AI 에이전트를 위한 평가(eval) 플랫폼인 Glassray를 구축합니다. 에이전트가 업무를 제대로 수행했는지 알기 위해서는 트레이스를 읽어야 하므로, 트레이스 데이터는 우리가 하루 종일 다루는 데이터입니다. 다이제스트는 해당 데이터를 사용 가능하게 만드는 방법입니다. 우리의 검색, 클러스터링(clustering), 모니터링(monitoring)은 모두 원시 스팬(raw spans)이 아닌 다이제스트를 기반으로 실행됩니다. 따라서 다이제스트는 정확해야 하며, 모든 트레이스에 대해 실행되므로 저렴해야 합니다.
결과 (Results)
모든 모델은 동일한 입력을 받았습니다: 50개의 고정된 트레이스 입력 세트(실제 트레이스 및 다양한 언어, 코드, SQL, 멀티 툴 실행, 충돌 실행, 그리고 우리의 절단 제한(truncation caps)을 초과하는 대규모 트레이스를 포함하여 합성된 것들)를 각각 3회씩 실행했습니다. 네 개의 더 저렴한 모델이 후보군이었으며, 현재 우리가 사용 중인 Claude Sonnet 4.6 모델이 기준점(bar)을 설정하는 대조군(control) 역할을 합니다. 통과 기준은 후보 모델이 보정된 노이즈 범위(noise band) 내에서 Sonnet의 규칙별 통과율(pass rates)을 유지하고, 검색(search) 결과가 동일한 트레이스를 반환해야 한다는 것이었습니다. 보정 및 규칙별 증거는 아래와 같으며, 왼쪽에는 한 줄 요약, 비용 및 신뢰성이, 오른쪽에는 Sonnet 베이스라인(baseline) 대비 품질이 정리되어 있습니다:
| 모델 (Model) | 비용 ($/1k) | 지연 시간 (Latency) | 유효한 JSON (Valid JSON) | 품질 규칙 (Quality rules) | 언어 (Language) | 주제 (Topic) | 검색 중첩 (Search overlap) | 판정 (Verdict) |
|---|---|---|---|---|---|---|---|---|
| Claude Sonnet 4.6 (현재) | $3.45 | ~3s | 99.3% | baseline | 100% | 0.96 | 0.79-0.84 | reference |
| ... |
열(column) 설명:
- 품질 규칙 (Quality rules): ±4포인트의 노이즈 범위(noise band)를 적용한 후, 다이제스트가 약속하는 7가지 트레이스 기반 규칙에 대해 후보 모델이 Sonnet 대비 기록한 점수입니다. "Held"는 어떤 규칙도 범위를 벗어나 하락하지 않았음을 의미하며, "N regressions"는 범위를 벗어난 규칙의 개수를 나타냅니다.
- 언어 (Language) 및 주제 (Topic): 후보 모델의 언어 태그와 주제 레이블을 베이스라인과 비교한 결과입니다. Kimi의 언어 점수가 급락한 이유는 스키마(schema)에서
en을 요구하는 곳에 "English"라고 작성했기 때문입니다. - 검색 중첩 (Search overlap): 운영 환경과 동일한 임베딩 모델(embedding model)을 사용하여, 기존 코퍼스(corpus)에 대한 상위 3개 최근접 이웃(top-3 nearest-neighbor) 중첩도를 나타냅니다. Sonnet 자체의 0.79-0.84(두 번째 Sonnet 실행 결과와 비교)는 어떤 후보 모델도 도달할 수 있는 천장(ceiling)이며, 범위는 3회의 실행을 포괄합니다.
다이제스트 작동 원리
가공되지 않은 에이전트 트레이스(raw agent trace)는 대규모의 비구조화된 호출(calls) 집합입니다. 하나의 사용자 요청은 수십 개의 스팬(spans)(플래너 호출, 툴 호출, 재시도, 서브 에이전트 등)으로 확산되며, 각 스팬은 프롬프트(prompts), 페이로드(payloads), 프레임워크 메타데이터(framework metadata)를 포함합니다. 다음은 실제 요청의 예시입니다:

하나의 요청은 검색이 닿을 수 없는 수십 개의 중첩된 스팬 (spans)이 됩니다.
가공되지 않은 스팬 (raw spans)을 기반으로 구축된 대시보드는 "이 실행(run)에서 무슨 일이 일어났는가"에 대해 답합니다. 하지만 무언가 잘못되었다고 느껴질 때 실제로 던지는 질문에는 답할 수 없습니다. 예를 들어, 어떤 실행이 환불에 관한 것인지, 혹은 어떤 트레이스 (traces)가 오류가 발생한 트레이스와 유사한지 같은 질문 말입니다. 이를 해결하기 위해서는 각 트레이스를 다음과 같이 구조화된 무언가로 축약해야 합니다:
- 요약 (summary): 사용자가 무엇을 요청했고 시스템이 무엇을 수행했는지를 설명하는 두세 개의 사실적인 문장,
- 주제 (topic):
refund(환불) 또는data deletion(데이터 삭제)와 같은 짧은 레이블, - 언어 (language) 태그: 대화가 실제로 진행된 언어,
- 임베딩 (embedding): 의미론적 검색 (semantic search)과 클러스터링 (clustering)을 가능하게 하는 벡터.
다이제스트 (digest)는 트레이스당 하나의 구조화된 LLM 호출과 요약에 대한 임베딩으로 구성됩니다. 이 호출은 트레이스 행에 이미 존재하는 내용(사용자 요청, 단계 이름, 그리고 쓰기 시점에 각각 제한된 최종 답변)만을 읽습니다. 원본 페이로드 (raw payload)를 다시 가져오지 않습니다. 덕분에 원래 트레이스의 크기에 상관없이 호출 규모가 작게 유지되며 비용을 예측할 수 있습니다. 설계는 간단합니다. 세 가지 선택 사항이 이 방식이 프로덕션 환경에서 견딜 수 있을지를 결정합니다.
출력을 구조화하기
모든 다이제스트는 출력 언어를 고정하여 고정된 스키마 (summary, language, topic)를 반환합니다. 즉, 요약 (summaries)과 주제 (topics)는 항상 대시보드의 언어인 영어로 작성되는 반면, language 필드는 트레이스가 실제로 어떤 언어로 이루어졌는지를 보고합니다. 이를 고정하지 않으면, 스페인어 고객 지원 대화가 스페인어 요약을 생성하게 되고, 클러스터가 주제가 아닌 언어별로 나뉘게 됩니다.
큰 트레이스는 잘립니다
텍스트의 제한(Bounded text)은 곧 잘림(truncation)을 의미하며, 우리는 이를 해결하려 하기보다 제한을 두는 방식을 택했습니다. 수집(ingest) 단계에서 각 저장 필드(사용자 요청, 최종 답변)는 8,000자로 제한되며, 조립된 다이제스트(digest) 입력값은 12,000자로 제한됩니다. 조립 순서는 트레이스(trace)의 골격을 보호합니다. 사용자 요청을 가장 먼저 배치하고, 그다음 단계 이름(step names)을 배치하여(모델이 어떤 에이전트와 도구가 실행되었는지 항상 볼 수 있도록 함), 마지막으로 최종 답변을 배치합니다. 단일 필드가 8,000자를 초과할 수 없으므로, 요청이 최대치에 도달하더라도 단계(steps)와 답변의 시작 부분을 위한 공간이 남습니다. 가장 먼저 잘려 나가는 부분은 매우 긴 답변의 뒷부분(tail)이므로, 답변 자체는 온전하게 유지되며 잘림은 요약기(summarizer)가 읽는 내용에만 영향을 미칩니다. 즉, 저장된 답변은 트레이스에 그대로 남아 있으며, 검색을 통해 도달했을 때도 확인할 수 있습니다. 우리가 요구하는 것은 잘림에 대한 정직함입니다. 모델은 자신이 볼 수 있는 부분에 대해 사실적이어야 하며, 문장 중간에 끊긴 텍스트에 대해 임의로 결말을 지어내서는 안 됩니다. 판단이 많이 필요한 작업의 경우, 우리는 크기가 너무 큰 트레이스에서 관련 단계들을 골라내는 저렴한 선택 패스(selection pass)를 실행합니다. 하지만 다이제스트는 의도적으로 '멍청하지만 저렴한' 잘림 방식을 유지합니다. 이 정도의 규모에서는 무엇을 읽을지 선택하기 위해 두 번째 LLM 호출을 수행하는 비용이 다이제스트 자체의 비용보다 더 많이 들기 때문입니다. 우리는 이 잘림을 명시적인 테스트 항목으로 만들었습니다. 우리의 평가 세트(evaluation set)에는 두 제한치를 모두 초과하도록 구축된 트레이스들이 포함되어 있으며, 전용 규칙("잘린 부분 이후를 지어내지 말 것")에 따라 점수가 매겨집니다.
큰 트레이스에서 다이제스트 모델이 보는 모습. 긴 답변의 뒷부분이 가장 먼저 잘려 나가며, 이를 보호하는 규칙은 보이는 부분에 대해 사실을 유지하고 결말을 절대 지어내지 않는 것입니다.
일관성이 어려운 부분입니다
다이제스트의 역할은 매 실행마다 동일한 종류의 요약을 생성하는 것입니다. 왜냐하면 그 이후의 모든 과정(트레이스에서 다이제스트로, 다시 임베딩(embedding)으로, 검색 및 클러스터링(clusters)으로 이어지는 과정)이 다이제스트를 기반으로 구축되기 때문입니다.
임베딩(embedding)은 요약 텍스트로부터 계산되므로, 유사한 텍스트는 벡터 공간(vector space) 내의 인접한 지점에 위치하게 됩니다. 만약 다이제스트(digest)가 매번 동일한 방식으로 트레이스(trace)를 설명한다면, 그 임베딩은 동일한 위치에 놓이게 되며, 동일한 쿼리(query)는 계속해서 동일한 트레이스를 반환하게 됩니다. 목표는 오늘 실행하든 다음 주에 실행하든, 혹은 이 모델의 다이제스트를 사용하든 더 저렴한 모델의 다이제스트를 사용하든 관계없이 동일한 쿼리가 동일한 결과를 반환하는 것입니다.
이러한 종류의 드리프트(drift)는 에러를 발생시키지 않습니다. 실행 사이에 요약의 스타일이나 초점이 변하면 임베딩도 함께 이동하므로, 검색 이웃(search neighborhoods)이 재배치되고 클러스터(clusters)의 레이블이 다시 지정됩니다. 검색 결과가 더 이상 유효하지 않더라도 요약문 자체는 여전히 잘 읽힐 수 있습니다. 따라서 요약문을 읽는 것만으로는 충분하지 않습니다. 다이제스트가 제공하는 검색을 실제로 실행해 보고, 매 실행마다 동일한 쿼리가 동일한 상위 트레이스들을 반환하는지 확인해야 합니다.
모델 교체는 다이제스트 단계에서 일어나지만, 그 영향은 다이제스트를 소비하는 단계에서만 나타나므로, 바로 그 단계에서 검증을 수행해야 합니다.
실험: 기준을 충족하는 가장 저렴한 모델
다이제스트는 우리가 수행하는 LLM 호출 중 가장 빈도가 높은 작업으로, 모든 고객의 모든 트레이스에 대해 매번 한 번씩 실행됩니다. 현재는 Claude Sonnet 4.6에서 실행되고 있으며, 이는 판단 집약적인 작업에 사용하는 더 큰 모델보다 이미 한 단계 낮은 수준입니다. 질문은 검색 결과가 변하기 전까지 얼마나 더 저렴한 모델로 내려갈 수 있느냐는 것입니다.
150회의 측정된 호출을 통해 측정한 현재의 기준점(baseline)은 다음과 같습니다:
- 비용: 다이제스트 1,000개당 $3.45 (트레이스당 $0.0035)
- 지연 시간(Latency): 다이제스트당 약 3초
- 신뢰성: 99.3% 유효한 구조화된 출력 (150회 호출 중 149회)
- 품질: 원본 트레이스를 기준으로 판단하며, 이것이 기준(reference)이 됩니다. 고정된 판정 모델(pinned judge)이 Sonnet을 포함한 모든 다이제스트를 7가지 트레이스 기반 규칙에 따라 평가하며, Sonnet의 통과율이 각 후보 모델과 비교되는 기준점이 됩니다.
대량의 작업을 더 작은 모델로 라우팅(Routing)하는 것은 잘 알려진 패턴입니다. 모델 교체를 검증하기 위해서는 네 가지 확인 절차가 필요합니다: 각 다이제스트(digest)가 여전히 원본 트레이스(trace)에 대해 정확한지, 모델이 모든 호출에서 여전히 유효한 구조화된 출력(structured output)을 반환하는지, 소비자(검색 및 클러스터링)가 여전히 동일한 결과를 반환하는지, 그리고 실제 성능 저하(regression)와 실행 간의 노이즈(run-to-run noise)를 구분하기 위한 보정(calibration)입니다.
설정 (The setup)
우리는 로컬 오픈 소스 실험 하네스(experiment harness)인 Glassray Coach에서 이 사다리(ladder) 테스트를 실행했으며, 이 포스트의 보고서는 수정되지 않은 그 결과물입니다. 50개의 고정된 트레이스 입력 세트를 사용하였으며, 여기에는 실제 트레이스와 19개 언어, 코드, SQL, 멀티 툴 실행(multi-tool runs), 충돌 실행(crashed runs), 단계 전용 트레이스(steps-only traces), 그리고 절단 제한(truncation caps)을 초과하는 세 개의 대형 트레이스가 포함된 합성 트레이스가 포함됩니다. 5개의 모델을 대상으로 각각 3회씩 전체 실행(총 750개 다이제스트)을 수행하였으며, 모두 실제 호출당 비용이 측정되는 프로덕션 코드 경로(production code path)에서 진행되었습니다. 각 측정값은 필요한 슬라이스(slice)를 사용합니다: 비용, 지연 시간(latency), 신뢰성은 세 번의 실행 전체(모델당 150회 호출)에 대해 집계됩니다. 검색 확인(각 트레이스에 대해, 임베딩 공간에서의 최근접 이웃이 기존 모델의 다이제스트와 동일하게 유지되는지 여부)은 세 번의 실행에 걸친 범위로 보고되며, 7가지 고정 품질 규칙은 각 모델의 첫 번째 실행(250개 다이제스트, 점수가 변하지 않도록 하나의 고정된 판정 모델 사용)을 기준으로 판단됩니다. 7가지 규칙은 다이제스트의 프롬프트와 스키마(schema)에서 가져온 다이제스트 자체의 약속입니다:
- 요약(summary)과 주제(topic)는 항상 영어로 작성할 것;
- 세부 사항을 절대 지어내지 말 것;
- 트레이스의 실제 언어를 보고할 것;
- 텍스트가 비어 있는 경우 명확하게 말할 것;
- 평이한 일상 영어를 사용할 것 (유일한 주관적 스타일 규칙);
- 절단된 트레이스(truncated traces)에서 잘린 부분 이후의 내용을 지어내지 말 것;
- 요약은 요청된 내용과 발생한 일을 다루는 2~3개의 문장으로 작성할 것.
AI 자동 생성 콘텐츠
본 콘텐츠는 Dev.to AI tag의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기

