RAG가 HTTP 200을 반환해도 망할 수 있는 이유 — SigNoz 기반으로 Goose를 구축한 과정
요약
RAG 시스템에서 HTTP 200 응답에도 불구하고 발생하는 '조용한 품질 실패'를 해결하기 위해 SigNoz 기반의 Goose를 구축한 사례를 소개합니다. OpenTelemetry를 활용해 답변의 근거(groundedness)와 라우팅 품질을 모니터링하고, 에이전트가 근본 원인을 분석하여 자동 복구하는 관측성 워크플로우를 다룹니다.
핵심 포인트
- RAG 시스템은 에러 없이도 잘못된 정보를 제공하는 '조용한 실패'가 발생할 수 있음
- 단순 RED 지표를 넘어 답변의 근거(groundedness)를 측정하는 품질 지표가 필수적임
- SigNoz와 MCP 에이전트를 결합하여 품질 저하 감지부터 RCA 작성까지 자동화 가능
- OpenTelemetry를 활용한 AI 에이전트 관측성(Observability) 구축의 중요성
자, 솔직하게 말해봅시다. 지연 시간(latency) 그래프는 깨끗해 보이고, 에러율(error rate)은 평온합니다. 모든 /chat 요청은 HTTP 200을 반환하죠.
그런데 답변이 그냥... 틀립니다. 아주 자신 있게 틀립니다. 형편없죠.
이것이 바로 HackRx RAG 6.0 이후 우리를 은근히 괴롭혔던 문제입니다. 약 1,000개의 팀 중 우리가 어떻게든 Top 30에 들었을 때, 우리는 평가(evals) / 검색(retrieval) / "이게 정말 근거(grounded)가 있는가"에 깊이 몰두하고 있었습니다. 하지만 그동안 우리의 "모니터링(monitoring)"은 기본적으로 느낌(vibes)과 배치 스크립트(batch scripts)에 의존하고 있었습니다. 모델이 잘못된 지식 베이스(KB)를 가져오고 있는 동안에도 RED 지표(Rate, Errors, Duration)는 계속 초록색을 유지했습니다. RAG를 출시하는 스타트업에게 이러한 격차는 치명적입니다. SLO(Service Level Objective)가 꿈쩍하기도 전에 사용자들이 떠나버리기 때문입니다.
그래서 우리는 Agents of SigNoz를 위해 Goose를 구축했습니다. 조용한 AI 품질 실패를 포착하여 SigNoz에 넣고, 임계치에 도달하면 알림을 보낸 뒤, MCP 에이전트가 근본 원인 분석(RCA, Root Cause Analysis)을 작성하고 문제를 자동으로 수정할 수 있게 합니다. 이 포스트는 문서의 재구성이 아니라, 우리가 실제로 무엇을 했고 무엇을 달성했는지에 대한 내용입니다. 여러분은 신호(signals), 주의해야 할 점(gotchas), 그리고 수치가 포함된 실제 엔드 투 엔드(e2e) 실행 과정을 보게 될 것입니다.
대시보드에 아무도 올리지 않는 실패 모드
일반적인 API: 잘못되면 = 5xx 에러 또는 타임아웃(timeout).
RAG 챗봇: 잘못되어도 = 성공(success).
- 리트리버(retriever)가 _무언가_를 반환함
- LLM(Large Language Model)이 매끄러운 답변을 작성함
- 핸들러(handler)가 200을 반환함
- 스팬(span) 상태: OK (진짜로)
하지만 질문한 카테고리와 쿼리된 카테고리가 다를 수 있습니다. 사용자가 특정 사실을 물었는데, 파이프라인이 조용히 결제 관련 문서를 가져오는 식입니다. 또는 도구(tool) 출력이 비어 있거나 유효하지 않은데 모델이 그냥 "일반 지식"을 바탕으로 즉흥적으로 답변(freestyle)을 내놓기도 합니다.
HackRx 당시, 이러한 조용한 실수(silent miss)는 "멋진 데모"와 "절대 출시해서는 안 될 제품" 사이의 차이를 만들었습니다. 우리에게 필요했던 것은 단순히 프로세스가 살아있는가가 아니라, 이 답변이 실제로 근거(grounded)가 있는가 / 제대로 라우팅(routed)되었는가를 나타내는 신호였습니다.
Goose란 무엇인가
요약하자면 다음과 같습니다:
- 데모 RAG가 매 턴마다 OTel(OpenTelemetry) 품질 저하 데이터를 방출합니다.
- SigNoz가 트레이스(traces), 메트릭(metrics), 로그(logs)를 수집하여 성능 저하 지점(the cliff)을 보여줍니다.
quality.score가 급락하면 알림을 보냅니다.- 에이전트가 SigNoz MCP에 접속하여 증거를 수집하고 마크다운(markdown) 형식의 RCA를 작성합니다.
- 선택적으로 자동 복구(auto-remediate)를 수행합니다 (우리가 주입한 오류의 경우
POST /admin/fix-retriever호출).
이 트랙은 **AI & 에이전트 관측성(Observability)**입니다.
우리가 사용한 데모 흐름: 약 100개의 커스텀 사실 질문을 정상적으로 실행 (답변을 의도적으로 이상하게 설정 — 예: "우리는 화성에 삽니다" — 이렇게 해야 Wikipedia가 아닌 우리의 Chroma store에 기반하고 있음을 알 수 있음). 그 다음 검색(Retrieval)을 고장 냅니다. 동일한 질문들을 던집니다. 여전히 HTTP 200 응답이 나옵니다. 하지만 점수(Scores)는 바닥으로 곤두박질칩니다. 답변이 뒤바뀝니다. SigNoz는 이 절벽을 보여줍니다. 에이전트가 조사하고 + 수정할 수 있습니다.
우리가 실제로 SigNoz를 사용한 방법
1. 모든 것을 OTLP로 한곳에 모으기
RAG 앱은 OTLP (OpenTelemetry Protocol)를 SigNoz로 전송합니다. 각 채팅 턴(Chat turn)은 단순한 요청 스팬(Request span)이 아닙니다. 우리는 여기에 품질 속성(Quality attrs)과 메트릭(Metrics)을 덧붙입니다:
quality.score— 주요 절벽 지표 (Cliff metric)quality.entity_match— 요청된 카테고리 매칭이 검색되었는가?tool.output_valid— 검색기(Retriever)/도구(Tool)의 출력이 사용 가능한 상태였는가?gen_ai.*+ 영역 속성 (asked/queried)- 동일한 혼란 상황에 대한 구조화된 품질 로그 (Structured quality logs)
조용한 실패(Silent fails)는 본질적으로 교차 신호(Cross-signal)의 특성을 가집니다. 트레이스(Trace)는 정상(OK)으로 보일 수 있지만, 메트릭 패널에서는 quality.score가 1.0에서 0.2로 떨어지는 것을 볼 수 있습니다. SigNoz에서 대시보드의 절벽을 열고, 여전히 OK라고 표시되지만 tool.output_valid=false인 스팬(Span)으로 점프합니다. 이 조합이 바로 이 논문의 핵심입니다.
[IMG:1] 여기에 대시보드 스크린샷을 넣으세요 —
quality.score가 정상이다가 절벽이 나타나는 모습
2. 허영심을 위한 차트가 아닌, 절벽을 위한 대시보드
우리는 단순히 "CPU가 올라간다"를 보여주는 또 다른 보드를 원하지 않았습니다. 우리의 대시보드는 기본적으로 다음과 같습니다:
- 최신 / 평균
quality.score - 엔티티 매칭(Entity match) / 잘못된 경로(Misroute)의 분위기
tool.output_valid비율- 품질은 죽어가는데 트래픽은 여전히 정상인 상태 (이것이 핵심입니다)
만약 트래픽 양(Volume)은 유지되는데 품질이 죽는다면, 그것은 "서버가 다운된 것"이 아니라 "답변이 멍청해진 것"임을 알 수 있습니다.
[IMG:2] 스크린샷: 정상적인 평균 ~1.0 vs 망가진 ~0.2
3. 알람 = CPU가 뜨거워진 것이 아니라, 답변이 멍청해진 것
알람: quality.score가 0.6 미만일 때. 채널 = :8020/webhook/alert에 있는 우리 에이전트로의 웹훅(Webhook).
이 부분이 시스템을 실감 나게 만들어준 지점입니다. SigNoz는 단순한 차트 박물관이 아닙니다. 품질을 위한 페이저(Pager, 호출기)입니다. 고전적인 장애 호출(Outage page)과 동일한 근육 기억을 사용하지만, 장애의 대상이 의미론적(Semantic)이라는 점이 다릅니다.
[IMG:3] 스크린샷: 알람 규칙 / 발생한 알람
4. 텔레메트리(Telemetry)를 읽는 온콜(On-call)로서의 MCP 에이전트
알람 발생 → goose는 허구의 이야기를 지어내지 않습니다. signoz mcp와 통신하여 최근의 품질 증거를 수집하고, 마크다운 형식의 RCA(무엇이 망가졌는지, asked vs queried 존과 같은 오라우팅(misroute) 힌트, 조치 사항)를 쏟아냅니다.
해커톤용 복구 도구(remediator)의 경우, 보고된/알람 값이 우리가 주입한 오라우팅(misroute)과 유사하다고 판단되면 POST /admin/fix-retriever를 호출합니다. 실제 상황이라면 이를 재인덱싱(reindex), 모델 롤백(model rollback), 기능 플래그(feature flag) 조절 등으로 대체하겠지만, 패턴은 동일합니다: signoz 증거 → 에이전트 결정 → 조치.
또한 솔직히 말씀드리면: 자동 수정(auto-fix)이 "임베딩(embeddings)을 바꿨더니 모든 것이 틀어졌다"와 같은 문제를 마법처럼 해결해주지는 않습니다. 우리의 복구 도구는 데모에서 보여준 라우팅 오류를 위한 것입니다. 이 부분을 과장하지 마세요. 심사위원들은 바로 알아챕니다.
screenshot: agent report markdown
우리가 실제로 실행한 E2E (단순한 느낌이 아닌 실제 테스트)
정상 트래픽 → 장애 발생 → 웹훅(webhook) → 복구. 에이전트가 직접 치유하도록 하기 위해 의도적으로 자동 치유(autoheal) 기능을 꺼두었습니다.
- 정상 (healthy) (~5분, 25개 질의) — 평균 점수 1.0, 리트리버(retriever) 정상
- 장애 (broken) (5개 질의) — 점수 0.2,
tool.output_valid=false, 답변이 지구/일반 상식 등으로 표류(drift) - 에이전트 (agent) — 웹훅(webhook) → MCP 조사 →
POST /admin/fix-retriever성공(ok) - 검증 (verify) — 화성(mars) 답변 복구, 점수 1.0, 리트리버 정상
이것은 말 그대로 우리가 HackRx 마감 직전(crunch)에 간절히 원했던 루프입니다. 단순히 "CSV 파일에서 배치 점수가 떨어졌는가"를 넘어, "운영 환경의 품질이 급락했을 때 내가 40분 동안 이곳저곳 클릭하지 않아도 무언가가 조사할 수 있는가"에 대한 답입니다.
인스트루멘테이션 (Instrumentation, 복사해서 사용할 수 있는 부분)
매 턴마다 우리는 대략 다음과 같은 작업을 수행한 후 OTLP로 내보냅니다:
span.set_attribute("quality.score", quality_score)
span.set_attribute("quality.entity_match", 1.0 if asked == queried else 0.0)
span.set_attribute("tool.output_valid", tool_output_valid)
...
OTEL_EXPORTER_OTLP_ENDPOINT를 signoz OTLP로 지정하세요.
우리가 대신 겪은 시행착오 (gotcha we ate so you don’t have to): 만약 SigNoz가 가동되기 전에 RAG를 시작하면, OTEL (OpenTelemetry)이 기본적으로 아무 작업도 수행하지 않거나 (no-op) 비활성화될 수 있습니다. 그러면 앱이 "정상 작동"하더라도 대시보드는 유령이 나온 것처럼 비어 보일 것입니다. SigNoz가 정상 상태(healthy)가 된 후에 RAG를 재시작하세요. 저희는 이 문제로 시간을 낭비했습니다. 자랑스럽지 않네요.
또 다른 바보 같은 실수: Mac에서의 localhost vs 127.0.0.1 — IPv6가 문제를 일으켰습니다. 127.0.0.1로 고정하기 전까지 저희의 E2E (End-to-End) 테스트는 연결 거부(connection refused)로 실패했습니다. 아주 사소하지만, 새벽 3시에 실제로 루프를 돌려봐야만 알 수 있는 종류의 디테일입니다.
잘 된 점 / 안 된 점
잘 된 점 (worked)
- 하나의 UI (SigNoz)에서 트레이스 (traces) 옆에 품질 지표 (quality metrics)를 배치함 = 컨텍스트 스위칭 (context switch)으로 인한 뇌 정지가 훨씬 줄어듦
quality.score에 대한 알림 (alerting) 설정은 적절해 보입니다. CPU 알림만 있었다면 이번 장애 상황을 인지하지 못하고 잠들었을 것입니다.- 근본 원인 분석 (RCA, Root Cause Analysis)을 위한 MCP (Model Context Protocol)는 증거가 무작위 스프레드시트가 아닌 SigNoz에 이미 들어있을 때 실제로 유용합니다.
안 된 점 / 거짓말하지 말 것 (didn’t / don’t lie about it)
- 앱 내 자동 복구 (in-app autoheal) 기능을 끄는 것을 잊으면 데모를 망칠 수 있습니다 (저희도 그랬고, 나중에 수정했습니다).
- 하나의 복구 도구 (remediator)가 모든 근본 원인을 해결할 수는 없습니다. 임베딩 (embedding) 변경이 경로 오류 (misroute break)를 해결해주지는 않습니다.
- 서비스를 잘못된 순서로 시작하면 계측 (instrumentation)이 고장 났다고 착각하게 만드는 가스라이팅을 당할 수 있습니다.
HackRx 시절의 저에게 해주고 싶은 말: 첫날부터 의미론적 품질 (semantic quality)을 계측하세요. 배치 평가 (batch evals)는 귀엽긴 하지만, 프로덕션 환경에서는 여전히 그래프 상의 급격한 절벽(cliff)이 필요합니다.
요약 (wrap)
Goose가 존재하는 이유는 HackRx 상위 30개 팀 중에서도 여전히 공백이 있었기 때문입니다 — 강력한 RAG, 하지만 조용히 실패할 때는 눈이 먼 상태. 이번 해커톤에서 저희는 그 공백을 메웠습니다: 품질 신호 (quality signals) 입력, SigNoz 내의 대시보드 + 알림, 그리고 RCA(+ 선택적 수정)를 수행하는 MCP 에이전트 출력. 저희는 스타트업의 RAG 작업에도 동일한 사고방식을 적용하고 있습니다. 왜냐하면 "HTTP 200이지만 틀린 답변"은 가설이 아니라, 기본 실패 모드이기 때문입니다.
여러분의 스택에 질문 하나를 던집니다: 답변은 틀렸는데 HTTP 상태 코드가 200일 때, 그래프 상의 무엇이 깨집니까?
만약 답변이 "아무것도 없습니다"라면... 네, 그것이 바로 버그입니다.
AI 자동 생성 콘텐츠
본 콘텐츠는 Dev.to AI tag의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기