본문으로 건너뛰기

© 2026 Molayo

Dev.to헤드라인2026. 06. 15. 14:29

에이전트 로그가 당신을 속이고 있습니다: 에이전트 시스템(Agentic System)에서 실제로 추적해야 할 것

요약

에이전트 시스템의 디버깅 시 단순 애플리케이션 로그만으로는 모델의 의사결정 과정을 추적하기 어렵다는 문제를 지적합니다. 에이전트의 관측 가능성을 높이기 위해 '인지-결정-행동'으로 이어지는 단계(step) 단위의 트레이싱과 실제 렌더링된 입력값 캡처의 중요성을 강조합니다.

핵심 포인트

  • 전통적인 로그는 코드 실행 기록일 뿐, 모델의 결정 과정을 설명하지 못함
  • 에이전트 관측 가능성의 단위는 로그 한 줄이 아닌 '단계(step)'여야 함
  • 계층적 구조를 반영하기 위해 스트림 방식이 아닌 트리(tree) 구조의 트레이싱 필요
  • 프롬프트 템플릿이 아닌 실제 모델이 본 '완전히 해결된 입력'을 기록해야 함

저는 지금까지 네 곳의 서로 다른 회사에서 반복되는 디버깅 세션을 목격했습니다.

프로덕션 환경에서 에이전트가 멍청한 행동을 합니다. 사용자가 불만을 제기합니다. 엔지니어가 로그를 확인합니다. 그들은 다음과 같은 내용을 발견합니다:

[INFO] agent.run started
[INFO] calling tool: search
[INFO] calling tool: fetch_document
...

그게 전부입니다. 그게 끝입니다. 에이전트는 14초를 소모했고, 세 번의 모델 호출 (model calls)을 수행했으며, 잘못된 문서를 가져왔고, 사용자에게 틀린 정보를 자신 있게 말했습니다. 하지만 로그에는 그런 일이 일어났는지에 대해 아무런 말이 없습니다. 엔지니어는 어깨를 으쓱하며 티켓을 "재현 불가"로 표시하고 다음 업무로 넘어갑니다. 버그는 영원히 방치된 채 배포됩니다.

문제는 로그 기록을 잊어버린 것이 아닙니다. 로그는 충분히 남겼습니다. 문제는 잘못된 계층 (layer)을 기록했다는 점입니다. 애플리케이션 로그 (Application logs)는 당신의 _코드_가 무엇을 했는지에 대한 기록입니다. 에이전트의 행동은 코드 안에 존재하는 것이 아니라, 당신의 코드와 모델의 결정 (model's decisions) 사이의 간극에 존재합니다. 그 간극은 console.log로는 보이지 않습니다.

전통적인 로깅이 에이전트에게 실패하는 이유

일반적인 서비스에서 흥미로운 이벤트는 결정론적 (deterministic)입니다. 요청이 들어오면, 특정 조건에 따라 분기하고, 데이터베이스에 접근하며, 응답을 반환합니다. 분기 지점과 쿼리를 기록한다면 무슨 일이 일어났는지 재구성할 수 있습니다. 제어 흐름 (control flow) 자체가 곧 설명이 됩니다.

에이전트는 이를 뒤집습니다. 당신의 제어 흐름은 사소합니다. 보통 모델을 호출하고, 모델이 요청한 도구 (tool)를 실행하며, 그 결과를 다시 입력으로 넣는 while 루프일 뿐입니다. 실제 모든 의사결정은 당신이 작성하지 않은 토큰 (tokens)으로 표현되어 모델 내부에서 일어납니다. 에이전트가 잘못되었을 때, 답은 결코 "루프에 버그가 있었다"가 아닙니다. 답은 _콘텐츠 (content)_에 있습니다. 컨텍스트 윈도우 (context window)에 무엇이 있었는지, 모델이 무엇을 선택했는지, 도구가 무엇을 반환했는지, 그리고 모델이 그 반환값을 어떻게 해석했는지가 답입니다.

따라서 에이전트의 관측 가능성 (Observability) 단위는 로그 한 줄이 아닙니다. 그것은 바로 단계 (step), 즉 '인지(perceive) - 결정(decide) - 행동(act)'으로 이어지는 하나의 완전한 턴입니다. 그리고 단계는 중첩됩니다. 하위 에이전트 (sub-agent)의 단계는 상위 단계 내에 존재하며, 도구 호출 (tool call) 자체가 모델 호출을 트리거할 수도 있습니다. 당신에게 필요한 것은 스트림 (stream)이 아니라 트리 (tree)입니다. 이는 분산 트레이싱 (distributed tracing)의 트레이스-스팬 (trace-and-span) 모델과 정확히 일치하며, 에이전트에 놀라울 정도로 잘 들어맞습니다.

단계별로 실제로 캡처해야 할 것

모든 모델 호출 (model invocation)에 대해, 당신은 모델을 다시 실행하지 않고도 그 결정을 재현할 수 있게 해주는 요소들을 확보해야 합니다. 최소한 다음 항목들이 필요합니다:

  • 완전히 해결된 입력 (fully resolved input) — 프롬프트 템플릿 (prompt template)이 아니라, 검색된 컨텍스트 (retrieved context)와 이전 도구 출력값을 포함하여 실제로 렌더링된 메시지여야 합니다. 템플릿은 당신이 의도한 것이고, 해결된 입력은 모델이 것입니다. 버그는 이 차이 속에 숨어 있습니다.
  • 파싱 (parsing) 전의 도구 호출 인자 (tool-call arguments)를 포함한 가공되지 않은 출력 (raw output).
  • 입력과 출력에 대한 **토큰 수 (token counts)**를 각각 분리하여 기록하십시오. 이는 비용 및 지연 시간 (latency)에 대한 조기 경보 시스템 역할을 합니다.
  • 실제로 사용된 모델과 파라미터 (parameters). 트래픽의 절반이 조용히 더 저렴한 모델로 폴백 (fallback) 되었을 때,

parentId와 스택(stack)을 결합하는 것이 핵심 비결입니다. 이렇게 하면 트레이스(trace)를 통해 트리 구조를 공짜로 얻을 수 있으며, 하위 에이전트(sub-agent)는 동일한 트레이스에 더 많은 단계(steps)를 추가하기만 하면 됩니다. 모델 클라이언트(model client)와 도구 디스패처(tool dispatcher)를 래핑(wrap)하여 이 과정이 자동으로 일어나도록 만드세요. 만약 모든 호출 지점(call site)에서 일일이 계측(instrumenting)을 해야 한다면, 한 달도 안 되어 시스템은 부패할 것입니다.

async function tracedModelCall(trace: Trace, messages: Message[], model: string) {
  const id = trace.begin("model", model, messages, { model });
  try {
...

모두가 건너뛰는 부분: 트레이스를 쿼리 가능하게 만들기

트레이스를 캡처하는 것은 작업의 절반에 불과합니다. 실제로 가치를 발휘하는 나머지 절반은 트레이스 전반에 걸쳐 질문을 던질 수 있는 능력입니다. "도구가 빈 결과를 반환했음에도 최종 답변이 여전히 성공이라고 주장한 모든 실행 사례를 보여줘." "지난 화요일에 도구 호출(tool calls)이 3배로 늘어나기 시작한 모델 버전은 무엇인가?" "이번 주에 가장 낮은 점수를 받은 5개 응답의 컨텍스트 윈도우(context window) 상태는 어떠했는가?"와 같은 질문들 말입니다.

이 중 그 어떤 것도 로그 파일만으로는 답할 수 없습니다. 이를 위해서는 각 트레이스를 구조화되고 쿼리 가능한 데이터로 취급해야 합니다. 즉, 실제 스키마(schema), 인덱싱된 필드(indexed fields), 그리고 이상적으로는 평가 점수(evaluation scores)와 사용자 피드백을 동일한 트레이스에 부착할 수 있는 방법이 필요합니다. "이 트레이스는 우리의 평가(eval)를 통과하지 못했다"라는 사실을 "이 문제를 일으킨 정확한 해결된 입력값(resolved input)은 이것이다"라는 사실과 결합할 수 있는 순간, 디버깅은 고고학이 아닌 쿼리(query)가 됩니다.

이 지점에서 관측성(observability)과 평가(evaluation)는 더 이상 별개의 관심사가 아닙니다. 평가 실패는 단지 판정(verdict)이 부착된 트레이스일 뿐입니다. 운영 중 발생한 장애(production incident)는 좋지 않은 결과가 발생한 트레이스입니다. 이들은 두 방향에서 바라본 동일한 객체이며, 이를 하나로 취급하는 팀은 압도적으로 빠르게 움직입니다.

핵심 요약 (The takeaway)

이번 분기에 에이전트를 위해 단 한 가지를 구축해야 한다면, 트레이스 트리(trace tree)를 구축하십시오. 단순한 INFO 로그 라인을 늘리는 것이 아닙니다. 모든 모델 및 도구 단계에 대한 구조화되고 중첩된 기록을 만들고, 해결된 입력값(resolved inputs)과 가공되지 않은 출력값(raw outputs)을 온전히 보존하여 사후에 쿼리하고 점수를 매길 수 있도록 만드십시오. 에이전트 신뢰성(agent reliability)의 다른 모든 요소는 실제로 무슨 일이 일어났는지 볼 수 있게 되는 순간 훨씬 쉬워집니다.

이것이 제가 작업하는 툴링(tooling)의 이면에 깔린 철학입니다. agent-eval은 이러한 추적(traces)을 CI에서의 통과/실패 판정으로 전환하기 위한 것이며, AgentLens는 에이전트가 프로덕션(production) 환경에서 활성화된 후에도 동일한 추적을 검색 가능한 상태로 유지하기 위한 것입니다. 이 도구들을 채택하든 직접 구현하든 원칙은 동일합니다. 에이전트의 동작은 단계(steps) 속에 존재하므로, 바로 그 부분을 캡처해야 합니다. 함수 호출(function calls)이 아니라 결정(decisions)을 기록하십시오.

AI 자동 생성 콘텐츠

본 콘텐츠는 Dev.to AI tag의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.

원문 바로가기
0

댓글

0