SigNoz와 OpenTelemetry를 활용한 자율성 에러 예산 게이트웨이(Autonomy Error Budget Gateway) 구축하기
요약
AI 에이전트의 자율적 행동 중 발생할 수 있는 오류를 제어하기 위해 SigNoz와 OpenTelemetry를 활용한 'LEASH' 시스템 구축 방법을 소개합니다. 에러 예산(Error Budget) 개념을 도입하여 에이전트의 도구 호출 성공률에 따라 권한을 단계별로 자동 조절합니다.
핵심 포인트
- AI 에이전트의 환각 및 도구 호출 실패에 대응하는 안전 장치 구축
- SRE의 에러 예산 개념을 에이전트 권한 제어에 적용
- SigNoz 텔레메트리를 기반으로 권한을 T3에서 T1까지 자동 강등
- 에이전트와 정책 집행 주체를 분리하여 시스템 안정성 확보
저는 스스로 데이터베이스 마이그레이션을 실행하고 스테이징 테이블을 정리할 수 있는 AI 에이전트를 구축했습니다. 그 다음, 상황이 잘못되기 시작하는 즉시 그 권한을 회수하는 것만을 목적으로 하는 두 번째 시스템을 만들었습니다. 그 두 번째 시스템이 바로 LEASH이며, 이것은 Agents of SigNoz 해커톤을 위해 약 하루 만에 이를 구축한 이야기입니다.
제가 실제로 관심을 가졌던 문제
모두가 AI 에이전트가 "자율적으로 행동(acting autonomously)"하는 것을 마치 이미 해결된, 안전한 일인 것처럼 이야기합니다. 하지만 그렇지 않습니다. 에이전트에게 데이터베이스 쓰기 권한을 주었는데 에이전트가 환각(hallucination)을 일으키거나, 에이전트의 도구 중 하나가 소리 없이 실패하기 시작한다면, 사람이 알아차리기도 전에 에이전트가 그 실수를 열 번 더 반복하는 것을 막을 방법이 없습니다.
사람들이 흔히 찾는 해결책은 "인간 참여형(human in the loop)" 방식입니다. 책임감 있게 들리지만, 실제로는 확장성이 없습니다. 새벽 3시에 에이전트가 수행하는 모든 개별 도구 호출(tool call)을 검토할 사람은 아무도 없습니다. 당신은 읽지도 않고 승인을 누르는 거수기가 되거나, 아니면 당신을 기다리느라 전체 파이프라인이 멈춰버리게 됩니다.
그래서 저는 대신 더 단순하고 기계적인 질문을 던졌습니다. 만약 에이전트의 권한이 고정되어 있지 않고, 최근의 행동이 실제로 잘 작동하는지에 따라 상하로 움직인다면 어떨까?
그것이 기본적으로 에러 예산(error budget)입니다. SRE 팀은 배포를 위해 수년 동안 이 개념을 사용해 왔습니다. 만약 서비스가 에러 예산을 빠르게 소진하고 있다면, 배포를 동결합니다. 저는 단지 이 동일한 아이디어를 배포가 아닌 AI 에이전트의 도구 호출(tool calls)에 적용했을 뿐입니다.
LEASH란 무엇인가
LEASH는 에이전트와 에이전트가 호출할 수 있는 도구들 사이에 위치합니다. 에이전트는 자신이 지금 신뢰할 수 있는지 스스로 결정할 수 없습니다. SigNoz가 실제 텔레메트리(telemetry)를 기반으로 결정하며, LEASH가 이를 집행합니다.
세 가지 단계가 있습니다:
- T3 - 완전한 권한. 읽기, 쓰기, 심지어 파괴적인 정리 호출까지 가능합니다.
- 그 아래로 점진적으로 더 제한되는 T2와 T1이 있습니다.
- T1 단계에서는 읽기 전용(read-only)입니다. 파괴적인 작업은 모두 엄격히 거부됩니다.
에이전트는 기본적으로 T3 단계에서 시작합니다. 도구 호출(tool calls)이 계속 성공하는 한 해당 단계에 머뭅니다. 5분 단위의 윈도우(window) 내에서 실패가 임계값(threshold)을 초과하는 순간, SigNoz의 알림(alert)이 발생하여 제 브로커(broker)의 웹훅(webhook)을 호출하며, 에이전트는 다른 파괴적인 작업을 수행하기 전에 권한이 강등(demoted)됩니다.
각 구성 요소가 실제로 통신하는 방식
저는 이 시스템을 하나의 거대한 서비스 대신 네 개의 작은 서비스로 나누었습니다. 그 이유는 주로 "에이전트"와 "에이전트에 정책을 강제하는 주체"가 단순히 같은 파일 내에서 분리된 것처럼 보이는 두 개의 함수가 아니라, 진정으로 분리된 프로세스이기를 원했기 때문입니다.
agent-runner- 가짜 릴리스 에이전트(fake release agent).read_release_notes,apply_migration,delete_staging_table과 같은 도구들을 호출합니다.leash-broker- 실제 게이트웨이(gateway). 에이전트의 모든 도구 호출은 먼저 이곳을 거칩니다. 현재 티어(tier)를 확인하고, 허용(allow) 또는 거부(deny)를 결정하며, SigNoz가 호출할 웹훅 엔드포인트(webhook endpoint)를 노출합니다.migration-tool및resource-tool- 실제 작업을 수행하는 단순한 다운스트림(downstream) 서비스들로, 필요에 따라 실패를 발생시킬 수 있도록 제가 제어할 수 있는 플래그(flag)를 가지고 있습니다.
이 모든 구성 요소는 OpenTelemetry Python SDK로 계측(instrumented)되어 있으며, 스팬(span), 메트릭(metrics), 로그(logs)를 OTLP를 통해 SigNoz로 직접 전송합니다.
제가 실제로 가장 많은 시간을 들여 제대로 구현한 부분은 정책 로직이 아니라, 스팬 이름과 속성(attributes)만으로도 그 자체로 하나의 이야기를 전달할 수 있도록 만드는 것이었습니다. leash.policy.decision은 현재 티어, 요구되는 티어, 그리고 판결(verdict)을 담고 있습니다. leash.tool.execute는 실제 호출 내용을 담습니다. 제 코드를 전혀 읽지 않고 SigNoz에서 트레이스(trace)를 열더라도, 스팬 속성만 보고 호출이 왜 허용되었는지 또는 거부되었는지를 정확히 알 수 있어야 합니다. 이것이 단순히 어딘가 로그 파일에 결정 사항을 기록하는 대신 SigNoz를 사용하는 핵심 이유이며, 트레이스 자체가 증거가 됩니다.
실제 작업을 수행하는 알림 설정하기
이 부분은 많은 해커톤 프로젝트들이 가짜로 구현하는 부분이라고 생각하기 때문에, 무엇이 실제인지 구체적으로 말씀드리고 싶습니다.
저는 leash_tool_calls_total에 대해 메트릭 알림 (metric alert)을 설정했습니다. tool_name = apply_migration 및 outcome = error로 필터링하고, 합계 (sum)로 집계하며, 5분 동안 임계값 (threshold)이 3 이상일 때 작동하도록 했습니다. 특별할 것 없는 설정입니다. 알림이 발생하면, SigNoz는 제가 엔드포인트에서 확인하는 토큰 헤더와 에이전트 ID 및 대상 티어 (target tier)가 포함된 페이로드 (payload)를 담아 http://<host>:18001/webhooks/signoz/demote로 직접 POST 요청을 보냅니다.
웹훅 페이로드 (webhook payload)의 형태를 한 번에 정확히 맞추는 것은 불가능했습니다. 핸들러 (handler)를 제대로 작성하기 위해서는 마이그레이션 도구에서 실제로 실패를 발생시키고, SigNoz UI에서 알림이 발생하는 것을 지켜보며, 제 엔드포인트에 실제로 어떤 데이터가 도달하는지 확인해야 했습니다. 문서를 읽어보니 알림이 웹훅을 호출할 수 있다는 점은 알 수 있었습니다. 하지만 제 특정 알림 유형에 대해 반대편에서 정확히 어떤 JSON을 받게 될지는 알려주지 않았습니다. 그런 세부 사항은 직접 해봐야만 알 수 있는 종류의 것입니다.
이것이 실감 나기 시작한 순간
실패 예산 (failure budget)이 소진되면, 에이전트는 T3 권한이 필요한 delete_staging_table을 호출하려고 시도합니다. LEASH는 이를 가로채고 다음과 같은 바디 (body)와 함께 403 에러를 반환합니다:
{
"error": "AUTONOMY_TIER_DENIED",
"agent_id": "release-agent-01",
...
저 trace_id는 단순히 장식용이 아닙니다. 이를 복사해서 SigNoz에 붙여넣으면, 티어 강등 (demotion)을 유발한 정확한 세 개의 실패한 마이그레이션 스팬 (migration spans)을 불러올 수 있습니다. 에이전트가 거부당하는 이유는 프롬프트 (prompt)가 그렇게 행동하라고 명령했기 때문이 아닙니다. 지금은 신뢰해서는 안 된다고 말하는 실제 실패의 기록(paper trail)이 SigNoz에 남아 있기 때문에 거부당하는 것입니다.
직접 시도해보려는 분들에게 해주고 싶은 말
유사한 것을 계측(instrumenting)하고 있다면, 자신의 결정 사항을 스팬(span)으로 방출(emitting)하는 과정을 생략하지 마세요. 단순히 허용/거부(allow/deny)를 stdout에 로그로 남기고 넘어가는 것도 유혹적이었을 것입니다. 하지만 leash.policy.decision이 고유한 속성(attributes)을 가진 적절한 스팬이 된 이후, SigNoz의 단일 트레이스 워터폴(trace waterfall)을 보는 것만으로도 에이전트 실행의 전체 티어(tier) 이력을 실제로 확인할 수 있었습니다. 이 단 하나의 선택이 제 자체 정책 로직의 디버깅 속도를 약 5배 정도 빠르게 만들었습니다. 호출이 왜 거부되었는지 추측하는 대신, 스팬에서 직접 읽을 수 있었기 때문입니다.
또한, 경보 임계값(alert threshold)을 신뢰하기 전에 실제 실패 호출로 테스트해 보세요. 처음에는 윈도우(window)를 60초로 설정했는데, 일반적인 지터(jitter) 상황에서도 너무 성급하게 경보가 발생했습니다. 이를 5분으로 늘리고 1번의 실패 대신 3번의 연속된 실패를 요구하도록 변경하자, 실제로 의미 있는 경보와 그저 소음(noisy)에 불과한 경보 사이의 차이를 만들 수 있었습니다.
다음에 바꿀 점
현재 티어(tiers)는 도구별로 하드코딩되어 있습니다. 실제 배포 환경에서는 브로커(broker)를 재배포하지 않고도 도구 호출의 위험 분류(risk classification)를 구성할 수 있어야 합니다. 또한 저는 하나의 실패 모드(마이그레이션 에러)에 대해서만 하나의 경보 규칙을 연결했습니다. 흥미로운 다음 단계는 이를 일반화하여, 특정 하드코딩된 경로뿐만 아니라 어떤 지표(metric)든 임계값을 넘으면 어떤 에이전트든 등급을 낮출(demote) 수 있도록 만드는 것입니다.
결론
에이전트에게 "에러가 발생하면 파괴적인 행동을 하지 마세요"라고 말하는 프롬프트 지침(prompt instructions)은 강제(enforcement)가 아닙니다. 그것은 모델이 무시하거나, 잘못 읽거나, 설득당해 어길 수 있는 제안일 뿐입니다. 실제로 무언가를 강제하는 것은 모델의 통제 밖에 있는 시스템이며, 실제 텔레메트리(telemetry)를 읽고 물리적으로 호출을 차단하는 것입니다. 이것이 바로 프롬프트로는 결코 얻을 수 없었던, SigNoz의 경보(alerts)와 OpenTelemetry의 트레이스(traces)가 저에게 준 가치입니다.
직접 살펴보길 원하신다면 코드는 여기에 있습니다: github.com/Vaibhav13Shukla/LEASH
SigNoz 해커톤, Track 1을 위해 제작되었습니다.
AI 자동 생성 콘텐츠
본 콘텐츠는 Dev.to AI tag의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기