에이전트가 무언가를 수행했지만, 아무도 무엇을 했는지 말할 수 없다. 에이전트 도구 호출을 위한 실용적인 감사 추적 패턴
요약
본 글은 에이전트가 수행한 복잡한 작업 과정(도구 호출, 재시도, 승인 요청 등)을 완벽하게 추적할 수 있는 실용적인 감사 추적 패턴을 제시합니다. 별도의 프레임워크 없이 Node.js의 이벤트 에미터와 파일 싱크만으로 모든 결정 체인을 기록하는 방법을 안내하며, 사후 분석에 필수적인 구조화된 로그를 만드는 것이 핵심입니다.
핵심 포인트
- 별도 프레임워크 없이 구현 가능한 감사 추적 패턴을 제공합니다.
- 모든 도구 호출, 재시도, 승인 요청 등 '결정 체인' 기록이 중요합니다.
- 최상위 에이전트 호출당 고유한 `run_id`를 반드시 포함해야 합니다.
- 사후 분석에 필요한 핵심 필드는 타임스탬프, run_id, 도구 이름, 인자 등으로 요약됩니다.
당신의 에이전트가 방금 작업을 완료했습니다. 다섯 개의 도구를 호출했고, 하나를 재시도했으며, 다른 하나에 대한 인간의 승인을 요청했고, 당신이 요청하지 않은 파일을 생성했습니다. 당신은 다음을 알고 싶습니다: 어떤 도구가 언제, 어떤 인자와 함께 호출되었으며, 무엇을 반환했습니까?
만약 당신의 에이전트 런타임(agent runtime)이 장난감이 아니라면, 솔직한 대답은 '모르겠다. 그리고 알아내기 쉽지 않다'입니다.
이 게시물은 바로 그 격차를 다룹니다. 저는 Node.js에서 구조화된 감사 추적(audit trail)을 위한 최소한의, 복사-붙여넣기가 가능한 패턴을 안내할 것입니다. 이 패턴은 모든 도구 호출, 승인, 재시도 및 최종 결과를 기록합니다. 프레임워크나 SDK 없이, 단지 이벤트 에미터(event emitter)와 나중에 grep 할 수 있는 파일 싱크(file sink)만 사용합니다. 끝날 무렵에는 약 한 시간 만에 구현했지만, 이미 하나의 사후 분석(post-mortem)에서 비용을 회수한 패턴을 갖게 될 것입니다.
문제점을 한 문장으로 요약하면
오늘날 대부분의 에이전트 런타임은 사용자에게 보이는 상태만 기록합니다: '에이전트가 생각 중', '도구 X 호출 중', '완료'. 그들은 _결정 체인(decision chain)_을 기록하지 않습니다: 모델이 왜 해당 도구를 선택했는지, 어떤 인자를 전달했는지, 승인 게이트(approval gate)가 막았는지, 도구가 무엇을 반환했는지, 재시도가 있었는지 여부입니다. 새벽 3시에 무언가 잘못되면, 당신은 '에이전트가 생각 중'이라는 줄들로 이루어진 벽을 읽게 되며, '다섯 개의 도구 호출 중 실제로 나쁜 상태를 유발한 것이 무엇인지?'에 답할 방법이 없습니다.
감사 이벤트(audit event)가 실제로 필요로 하는 것
사후 분석의 90%는 다섯 가지 필드로 커버됩니다:
{
ts: ISO-8601 타임스탬프
run_id: 최상위 에이전트 호출당 하나의 ID
...
}
그게 전부입니다. run_id는 사람들이 보통 잊어버리는 것이며, 이것이 '나의 세 개의 동시 에이전트 중 어느 것이 이것을 했지?'라는 질문에 답할 수 없게 만드는 이유입니다. 아래 전체 패턴은 바로 그 하나의 필드에 달려 있습니다.
30줄로 구현하는 패턴
const fs = require("node:fs");
const path = require("node:path");
...
재시도 및 승인의 경우, 구별되는 필드를 가진 동일한 이벤트 유형을 방출합니다:
audit(runId, "retry", tool.name, { reason: "timeout", attempt: 2 });
audit(runId, "approval", "human", { blocked: true, policy: "no-write-prod" });
이것이 전체 패턴입니다. 추상화 계층도, 프레임워크도 없습니다. 이 패턴은 어떤 런타임(hand-rolled loop, LangChain, OpenAI Assistants 등 무엇이든)에도 적용할 수 있는데, 오직 '도구 이름(tool name)'과 '페이로드(payload)'만 알기 때문이지, 사용자가 실제로 어떤 도구를 사용하는지는 알지 못합니다.
실제 에이전트 루프에 연결하기
재시도 루프에서 이벤트가 실제로 어디에 기록되는지를 보여주는 더 완전한 버전입니다:
async function runAgentWithAudit(runId, tool, initialArgs, { maxRetries = 2 } = {}) {
const t0 = Date.now();
audit(runId, "tool_call", tool.name, { args: initialArgs, attempt: 1 });
...
결과에 있는 attempt 필드를 주목하세요. 이것이 없으면 재시도 호출과 최종 성공 기록이 로그에서 동일하게 보여서, '이 도구가 이번 실행에서 불안정했다(flaky)'는 신호를 놓치게 됩니다. 이 신호야말로 재시도 한도를 높일지 아니면 근본적인 도구를 수정할지를 결정할 때 실제로 필요한 것입니다.
기록 읽어내기
로그가 생기면, 사후 분석은 grep 명령어로 가능해집니다:
# 한 실행에서 실패한 모든 도구 호출을 가져오기
grep '"run_id":"run-abc-123"' audit.log | grep -E '"event":"retry"|"event":"outcome"' \
| jq -r 'select(.event=="outcome") | .payload.result'
...
세 번째가 놀라운 부분입니다. 우리는 허용적으로 조정했기 때문에 게이트(gate)가 거의 아무것도 차단하지 않을 것이라고 예상했습니다. 하지만 약 40번 중 1번의 실행에서 차단되었고, 그 모든 차단 사례는 에이전트가 환각(hallucinated)을 일으킨 경로에 대해 rm -rf를 시도하는 경우였습니다. 로그 덕분에 우리는 이 사실을 알았지만, 로그가 없었다면 이는 조용한 프로덕션 위험으로 남았을 것입니다.
제가 하지 않을 두 가지
args에 비밀 정보를 기록하지 마세요. 도구가 토큰(token)을 필요로 한다면, 페이로드에 쓰기 전에 이를 비식별화(redact)하세요. 감사 로그는 프로세스보다 오래 지속되므로, 이곳을 자격 증명(credentials)의 두 번째 사본으로 만들지 마십시오. 한 줄짜리 헬퍼 함수만 있으면 충분합니다:
function redact(obj, keys = ["token", "apiKey", "secret"]) {
const out = structuredClone(obj);
for (const k of keys) if (k in out) out[k] = "***";
...
- 에이전트 프레임워크에 감사(audit) 형식을 의존하지 마세요. 위에 제시된 스키마는 의도적으로 지루합니다: 다섯 개의 필드, JSON 라인 형식입니다. 이는 로그를 읽는 사람들을 다시 교육할 필요 없이 런타임을 전환할 수 있다는 의미입니다. 저는 팀들이 풍부한 프레임워크별 감사 형식을 구축했다가, 프레임워크를 변경하는 날 그 형식이 쓸모없어지는 것을 본 적이 있습니다. 지루한 형식이야말로 살아남는 것입니다.
네 가지 게이트(Four Gates) 프레이밍에서 이 위치
만약 '네 가지 게이트' 프레이밍—접근(access), 행동(action), 승인(approval), 감사(audit)—에 대해 읽어보셨다면, 제가 설명하는 감사 이벤트는 게이트 4이며, 사후에 무슨 일이 일어났는지 재구성할 수 있게 해주는 부분입니다. 나머지 세 개의 게이트는 에이전트가 프로덕션 환경에 들어간 후에 개조하기 어렵습니다. 반면 감사는 파일에 라인을 쓰는 것이기 때문에 개조 비용이 저렴합니다. 따라서 가장 높은 투자 대비 효과(ROI)를 가진 게이트부터 추가하는 것이 좋습니다. 거기서 시작하여 역으로 작업하세요.
승인 게이트는 로그가 스스로 값을 증명해주는 곳입니다
approval 이벤트 유형은 대부분의 사람들이 건너뛰고, 나중에 추가하기 가장 어려운 부분입니다. 에이전트가 어떤 것에 대한 쓰기 접근 권한을 갖게 되면, 결국
로컬 파일에 JSON-lines를 사용하는 것은 몇천 개 이상의 이벤트를 분당 생성할 때는 괜찮습니다. 그 이상부터는 동일한 이벤트 형태(event shape)를 실제 싱크(S3, CloudWatch, 로그 스토어 등)로 전송해야 합니다. 하지만 _형태_는 동일하게 유지하세요. 이 패턴의 전체 가치는 스키마가 전송 방식보다 오래 지속된다는 것입니다.
이 게시물은 AI의 도움을 받아 작성되었습니다. 내용은 저자가 책임집니다.
AI 자동 생성 콘텐츠
본 콘텐츠는 Dev.to AI tag의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기