AI 에이전트 관측 데이터 경계 설계
- 1 사용자 요청이 끝날 때까지의 의사결정 경로
이 글에서 먼저 가져갈 세 가지
AI 에이전트의 지연·비용·보안 문제를 함께 읽기 위한 관측 데이터의 경계 설계입니다.
-
01
병목을 찾으려면 요청 전체를 나눠 기록한다.
모델·검색·도구 중 어디가 막혔는지 구분해야 엉뚱한 곳을 고치지 않는다. 본문 2절
-
02
비용은 한 곳에서만 합산한다.
같은 토큰을 여러 계층에서 중복 계산하면 숫자는 커져도 원인을 알 수 없다. 본문 4절
-
03
원문은 적게, 판단에 필요한 단서는 충분히 남긴다.
고객 입력을 복제하지 않고도 장애를 분석할 수 있는 안전한 경계를 만든다. 본문 3절
1. 모델 호출 시간만 보면 에이전트 장애를 놓친다
AI가 답을 만들기 위해 여러 일을 거칠 때는, “AI가 느리다”는 한 줄짜리 기록만으로 고장 난 곳과 새는 비용을 찾을 수 없다. 주문 배송이 늦을 때 결제·창고·배송 중 어디에서 멈췄는지 따로 보듯, AI가 모델에게 묻는 일과 사내 도구를 실행하는 일을 구분해 기록해야 한다.
AI 기능을 처음 운영할 때는 모델 API의 응답 시간과 실패율만 봐도 꽤 많은 문제가 설명된다. 요청 하나가 프롬프트를 보내고 답을 받는 구조라면, 모델 이름·입력 토큰·출력 토큰·응답 코드 정도로도 비용과 지연을 이해할 수 있다. 그러나 에이전트가 검색을 하고, 내부 API를 호출하고, 도구 결과를 다시 모델에 넣어 다음 행동을 고르는 순간 이야기가 달라진다. 사용자에게는 하나의 질문이지만 시스템에는 여러 서비스와 재시도가 얽힌 작은 워크플로가 된다.
이때 “평균 모델 지연은 정상입니다”라는 대시보드는 별 도움이 되지 않는다. 실제 문제는 도구 호출이 타임아웃 난 뒤 모델이 같은 작업을 다른 경로로 반복했을 수 있고, 검색 결과가 비어 재시도 횟수만 늘었을 수 있으며, 권한 오류가 모델의 장황한 재질문으로 바뀌었을 수도 있다. 모델 span, 즉 요청 처리의 한 구간 하나가 녹색이라고 해서 요청 전체가 건강한 것은 아니다. 관측의 단위는 모델이 아니라 사용자 요청이 끝날 때까지의 의사결정 경로가 되어야 한다.
OpenTelemetry의 GenAI semantic conventions는 이런 기록에 붙일 공통 이름표와 형식을 정하려는 규약이다. 예를 들어 ‘모델 호출’, ‘도구 실행’, ‘검색’처럼 서로 다른 일을 같은 의미로 기록하게 돕는다. 현재 관련 규약은 별도 GenAI 저장소에서 개발 상태로 다뤄지고 있으므로, 이름을 도입했다는 이유만으로 추적 구조가 자동으로 완성되지는 않는다. 팀이 어떤 장애 질문에 답하려는지 먼저 정하지 않으면, 기록 구간만 많고 판단은 어려운 상태가 된다. “어느 고객의 요청이 실패했나”와 “도구가 실패할 때 모델 비용이 얼마나 더 발생하나”는 서로 다른 데이터 경계와 집계 방식을 요구한다.
2. 요청 하나를 네 개의 책임 구간으로 나눠라
에이전트 관측에서 가장 먼저 분리할 것은 호출 주체다. 애플리케이션은 사용자의 요청을 시작하고, 에이전트는 다음 행동을 선택하고, 도구는 외부 세계를 바꾸거나 읽고, 모델 서비스는 추론을 수행한다. 여기서 span은 이 긴 과정 안의 ‘한 구간 영수증’이라고 생각하면 된다. 이 네 가지를 같은 종류의 기록으로 다루면, 장애 원인과 비용 책임이 섞인다. 특히 애플리케이션이 기록한 토큰 수와 모델 게이트웨이가 기록한 토큰 수를 같은 지표로 합산하면 사용량이 두 번 계산될 수 있다.
text
사용자 요청
└─ app.request 요청 성공·실패와 사용자 체감 시간
└─ gen_ai.agent.invoke 계획 수립, 단계 수, 종료 이유
├─ gen_ai.inference.client 모델 호출의 실제 비용·지연
├─ gen_ai.tool.execute 검색·사내 API·쓰기 작업의 결과
└─ gen_ai.retrieval.client 문서 탐색과 후보 수, 실패 원인
집계 원칙
- 비용: inference.client에서만 청구 기준으로 합산
- 사용자 지연: app.request의 전체 기간으로 측정
- 도구 품질: tool.execute의 오류·재시도·결과 유형으로 분석
이 구조에서 중요한 것은 완벽한 트리 모양이 아니다. 비동기 큐나 백그라운드 작업 때문에 부모 자식 관계가 끊길 수 있다. 그럴 때는 요청 ID, 작업 ID, 도구 실행 ID 같은 상관관계 키를 남기되, 무작정 모든 ID를 span 속성으로 복제하지 않는다. 조회에 필요한 키와 장기 보관이 가능한 키를 구분해야 한다. 한 번 유출되면 바꾸기 어려운 고객 식별자나 원문 입력은 특히 그렇다.
운영 화면에서는 다음 세 질문이 바로 답되어야 한다. 첫째, 사용자가 느린 이유가 모델 대기인지 도구 대기인지. 둘째, 실패가 계획 단계의 잘못된 선택인지 도구의 일시 오류인지. 셋째, 같은 요청을 다시 시도하면서 비용이 어디에서 불어났는지다. 이 질문에 답하지 못하는 속성은 일단 수집하지 않아도 된다. 관측 데이터는 많이 모을수록 좋은 원본 로그가 아니라, 사고 때 빨리 줄일 수 있어야 하는 탐색 공간이다.
3. 프롬프트와 도구 인자는 ‘기록 여부’보다 ‘복구 가능성’으로 결정한다
에이전트 추적을 붙인 뒤 가장 흔한 유혹은 입력과 출력을 전부 span에 넣는 것이다. 실패한 요청을 한 화면에서 재현할 수 있으니 당장은 편하다. 하지만 고객 문의, 내부 문서 조각, 접근 토큰이 섞일 수 있는 시스템에서는 이 편의가 오래가지 않는다. 고카디널리티 속성, 즉 요청마다 값이 거의 달라져 끝없이 새로운 분류가 생기는 값은 저장 비용과 검색 성능을 악화시키고, 민감한 원문은 접근 제어가 약한 관측 도구로 새 복사본을 만든다.
그래서 데이터 등급을 나눈다. 운영 대시보드에 항상 남길 값, 제한된 기간 동안 원본과 연결할 값, 기본적으로 수집하지 않을 값을 정한다. 원문을 없애라는 뜻은 아니다. 장애 재현에 반드시 필요하다면 별도 저장소에 암호화해 보관하고, trace에는 참조 ID와 안전한 요약만 남긴다. 반대로 사용자가 이미 공개한 짧은 상태 코드나 도구의 실행 결과 유형처럼 민감하지 않은 값은 span 속성으로 남겨도 유용하다.
| 데이터 종류 | 기본 처리 | 장애 분석에 남길 최소 정보 |
|---|---|---|
| 모델·도구 이름, 응답 상태 | 항상 수집 | 공급자, 작업 종류, 종료 사유, 지연 시간 |
| 토큰 수와 재시도 횟수 | 집계용으로 수집 | 청구 경계를 가진 모델 호출의 입력·출력 사용량 |
| 프롬프트·도구 인자 원문 | 기본 비수집 또는 별도 보관 | 길이, 해시, 분류 결과, 보안 참조 ID |
| 검색 문서와 고객 데이터 | 접근 제어된 저장소에 분리 | 문서 개수, 점수 범위, 테넌트 없는 내부 참조 |
이 결정은 보안팀에만 넘길 일이 아니다. 운영자가 어떤 문맥 없이 장애를 해결할 수 없는지, 어느 정도의 문맥이면 충분한지를 서비스 팀이 알아야 한다. 예를 들어 도구 호출이 실패했을 때 전체 JSON 인자가 필요할 수도 있지만, 대부분의 초기 분류에는 도구 이름, 인자 스키마 버전, 응답 상태, 크기, 안전한 오류 분류면 충분하다. 원문을 보지 못해 막힌 사례가 실제로 생긴 뒤에만 보존 범위를 넓혀도 늦지 않다.
▲ 관측 설계는 모든 선을 기록하는 일이 아니라, 나중에 따라가야 할 선을 의도적으로 남기는 일이다.
4. 재시도와 토큰 비용의 이중 계산을 막는 규칙
에이전트 시스템에서 비용 보고가 어긋나는 이유는 대개 모델 가격 계산식이 틀려서가 아니다. 같은 사용량을 여러 계층이 각각 ‘내 비용’으로 기록했기 때문이다. 애플리케이션은 요청 단위 합계를, 게이트웨이는 공급자 호출 단위 합계를, 모델 서버는 배치 처리 단위 합계를 볼 수 있다. 이 값들은 모두 유효하지만 같은 그래프에 더하면 안 된다.
가장 단순한 원칙은 비용의 공식 장부를 하나 정하는 것이다. 외부 모델 API를 사용한다면 클라이언트 또는 게이트웨이의 실제 공급자 응답을 청구 기준으로 잡는다. 자체 추론 서버라면 모델 엔진의 사용량을 별도 용량 지표로 보고, 애플리케이션의 추정 토큰과 섞지 않는다. 다른 계층에는 비용이 아니라 attempt, retryreason, fallbackmodel처럼 경로를 설명하는 정보를 남긴다.
typescript
type AgentAttempt = {
traceId: string;
step: number;
operation: 'model' | 'tool' | 'retrieval';
retryReason?: 'timeout' | 'rate_limit' | 'invalid_tool_result';
billableUsage?: { inputTokens: number; outputTokens: number };
};
// 청구용 사용량은 실제 모델 호출을 감싼 한 계층에서만 기록한다.
function recordAttempt(attempt: AgentAttempt) {
if (attempt.operation !== 'model') delete attempt.billableUsage;
return attempt;
}
이 규칙의 트레이드오프도 분명하다. 한 곳만 비용을 기록하면 일부 팀이 익숙한 대시보드에서는 합계를 바로 볼 수 없다. 그러나 편의 때문에 중복 합계를 허용하면 비용 급증을 발견한 뒤에도 어느 계층을 믿어야 할지 알 수 없다. 처음에는 추적 화면이 조금 불편하더라도, 집계의 소유자와 정의를 문서로 고정하는 편이 훨씬 싸다.
실무 원칙: 오류 유형을 모델·도구·검색·정책 경계로 분류하고, 각 경계에 별도의 재시도 예산을 둔다.
5. 표준은 고정된 계약이 아니라 버전이 있는 의존성이다
GenAI 관측 규약은 아직 빠르게 변하는 영역이다. 이름이 바뀌거나, 기존 속성이 더 좁은 의미의 새 속성으로 분리되거나, 제공자별 값이 추가될 수 있다. 따라서 코드 곳곳에서 문자열 속성 이름을 직접 조립하는 방식은 피하는 편이 좋다. 수집 라이브러리를 감싼 작은 어댑터를 두고, 어떤 schema URL, 즉 어떤 버전의 이름표 규칙을 사용했는지 리소스 정보 또는 배포 메타데이터로 남긴다. 그래야 대시보드의 선이 갑자기 끊겼을 때 데이터가 사라진 것인지, 이름이 옮겨간 것인지부터 구분할 수 있다.
이 어댑터는 거대한 추상화일 필요가 없다. 예를 들어 recordModelUsage, recordToolOutcome, recordAgentStep 세 함수가 각각 허용된 속성만 받게 해도 충분하다. 기능 팀은 비즈니스 문맥을 전달하고, 플랫폼 팀은 민감 데이터 제거와 스키마 호환성을 책임진다. 이 경계가 없으면 새로운 도구를 붙일 때마다 각 팀이 서로 다른 이름으로 토큰 수, 모델 이름, 오류 사유를 기록하게 된다. 그 순간 공통 대시보드는 정밀한 진단 도구가 아니라 보기 좋은 평균값이 된다.
변경을 배포할 때는 이전 속성을 한꺼번에 지우지 않는 편이 안전하다. 짧은 전환 기간 동안 새 속성과 기존 속성을 비교해 값의 의미가 같은지 확인하고, 집계 쿼리와 경보 규칙을 먼저 바꾼다. 이후에만 이전 필드를 제거한다. 이 과정은 번거로워 보이지만, 관측 데이터는 한번 버전이 섞이면 과거 장애와 현재 장애를 같은 질문으로 비교할 수 없게 된다. 특히 비용이나 성공률처럼 경영 지표에도 쓰이는 값은 스키마 변경 기록을 코드 변경 기록만큼 신중하게 남겨야 한다.
현실적으로는 모든 팀이 같은 속도에 맞춰 옮길 수 없다. 그래서 전환 기간에는 경보의 기준을 새 값 하나에 걸기보다, 이전·새 값의 차이가 예상 범위인지 확인하는 검증 경보를 둔다. 수집기가 바뀐 날부터 오류율이 갑자기 반으로 보인다면 서비스가 좋아진 것이 아니라 분류 규칙이 달라진 것일 수 있다. 관측의 변화 자체도 운영 대상이라는 사실을 잊지 않는 편이 좋다.
6. 2주 안에 시작하는 관측 도입 순서
처음부터 모든 semantic convention을 구현하려 하면 표준 용어를 배우는 데 시간을 쓰고 실제 장애는 계속 놓친다. 우선 최근에 실제로 겪었거나 충분히 우려되는 실패 시나리오 세 개를 고른다. 예를 들어 ‘도구 타임아웃 뒤 반복 호출’, ‘검색 결과 없음 뒤 장황한 답변’, ‘권한 오류가 사용자 오류처럼 보이는 경우’가 될 수 있다. 각 시나리오에 필요한 span과 속성만 붙여서 trace를 한 번 끝까지 따라가 본다.
| 기간 | 실행 과제 | 통과 기준 |
|---|---|---|
| 1~2일 | 요청·에이전트·모델·도구의 책임 경계와 비용 장부 소유자를 정한다. | 중복 합산하지 않을 사용량 지표가 문서화됨 |
| 1주 | 실패 시나리오 세 개에 필요한 최소 span과 오류 분류를 구현한다. | trace 하나로 느린 구간과 실패 경계를 찾을 수 있음 |
| 2주 | 프롬프트·도구 인자의 보존 범위를 점검하고 접근 권한을 검토한다. | 원문 없이도 대부분의 장애를 분류할 수 있음 |
좋은 에이전트 관측은 멋진 trace 화면을 만드는 일이 아니다. 장애가 났을 때 누가 무엇을 다시 실행했고, 왜 비용이 늘었으며, 어떤 데이터는 보지 않아도 되는지를 빠르게 말할 수 있게 하는 일이다. 모델, 도구, 검색, 정책의 경계를 먼저 정하면 표준은 그 경계를 공유하는 언어가 된다. 반대로 경계 없이 속성부터 쌓으면, 추적 시스템은 또 하나의 이해하기 어려운 시스템이 된다.
댓글 0