LLM 엔지니어링20장 · LLM 평가와 모니터링12 / 22← 이전목차다음 →MetaContents

20장. LLM 평가와 모니터링

대시보드와 알림

화재경보기가 토스트 냄새에도 울리면, 사람들은 결국 경보기 건전지를 빼 버립니다. 알림은 꼭 볼 일에만 울려야 합니다. 대시보드는 늘 보는 계기판, 알림은 사람을 부르는 경보기입니다.

기준선 정하기, 문턱 정하기, 알림 울리기, 조치 기록하기의 네 단계 흐름도

1번에서 평소의 값(기준선)을 기록에서 잽니다. 2번에서 기준선보다 얼마나 나빠지면 부를지 문턱을 정합니다. 3번에서 문턱을 넘으면 알립니다. 4번에서 무엇을 했는지 적어, 다음에 문턱을 고칠 때 씁니다. 너무 예민한 경보기가 알림 피로입니다.

알림 규칙의 예

지표문턱(이 장)까닭
하루 지연 p956초 넘음평소 p95 는 3초 안팎
하루 오류율5% 넘음평소 1% 안팎
7일 이동 모름 비율0.12 넘음하루 값은 흔들려 이동 평균을 본다
7일 이동 싫어요 비율0.08 넘음같은 까닭
주간 PSI0.1 넘음질문 분포의 변화

서버에 계기를 다는 꼴

실제 서비스에서는 API 서버가 지표를 내보내고, Prometheus 가 모으고, Grafana 가 그립니다. 아래는 이 PC 에서 실행하지 않은 안내 코드입니다. FastAPI 서버에 prometheus_client 로 계기를 달고, Prometheus 의 알림 규칙을 적은 꼴입니다.

출력
# api.py: 요청마다 지연과 결과를 센다
from prometheus_client import Counter, Histogram, make_asgi_app
REQS = Counter("chatbot_requests_total", "요청 수", ["result"])      # result: ok, error, dont_know
LAT = Histogram("chatbot_latency_seconds", "응답 시간")
app.mount("/metrics", make_asgi_app())

# alert.rules.yml: 10분 동안 p95 가 6초를 넘으면 알린다
- alert: ChatbotSlow
  expr: histogram_quantile(0.95, rate(chatbot_latency_seconds_bucket[10m])) > 6
  for: 10m

서버를 띄운 뒤 /metrics 를 열었을 때의 예상 출력(실행하지 않고 유추한 예시)입니다. 숫자는 요청 수에 따라 다릅니다.

출력
curl http://127.0.0.1:8000/metrics
chatbot_requests_total{result="ok"} 약 수백
chatbot_requests_total{result="error"} 약 수
chatbot_latency_seconds_bucket{le="2.5"} 약 수백
chatbot_latency_seconds_count 약 수백

이어진 날을 하나의 알림으로

같은 문제로 날마다 알림이 울리면 피로가 쌓입니다. 이어진 날을 한 사건으로 묶습니다.

Python
import pandas as pd

days = pd.date_range("2026-10-18", periods=7, freq="D")
over = pd.Series([False, False, True, True, True, False, True], index=days)   # 문턱을 넘은 날
# 앞날과 상태가 바뀔 때마다 번호를 하나씩 올리면, 이어진 날은 같은 번호가 된다
group = (over != over.shift()).cumsum()
events = [(g.index[0].date(), g.index[-1].date()) for _, g in over[over].groupby(group[over])]
print("알림 사건:", events)

코드에 나오는 함수

코드쉬운 뜻
pd.date_range("2026-10-18", periods=7, freq="D")날짜 줄을 만든다. periods(개수)는 7개, freq(frequency, 간격)의 "D" 는 day(하루)다
pd.Series(values, index=days)값 목록에 날짜 이름표를 붙인 한 줄짜리 표를 만든다
over.shift()값을 한 칸 아래로 민다. 날마다 옆에 앞날의 값이 오게 된다
over != over.shift()앞날과 상태가 다르면 True 다
.cumsum()cumulative sum(누적 합)이다. 위에서부터 차례로 더해 간 값을 둔다
over[over]True 인 날, 곧 문턱을 넘은 날만 남긴다
.groupby(group[over])같은 번호끼리 묶는다
g.index[0].date(), g.index[-1].date()묶음의 첫날과 끝날에서 시각을 떼고 날짜만 꺼낸다

이 코드가 뜻하는 것

택배 회사가 날마다 "배송 지연" 여부를 달력에 적어 둔다고 생각해 봅니다. 사흘 이어서 지연되었다면 고객에게 사흘 내내 문자를 보내지 않고, "지연 사건 하나"로 묶어 한 번 알립니다. 여기서 달력의 지연 표시가 over, 사건 번호가 group 입니다.

줄하는 일쉽게 말하면
days = pd.date_range(...)10월 18일부터 7일일주일 달력
over = pd.Series([...], index=days)날마다 문턱을 넘었는지달력의 지연 표시
group = (over != over.shift()).cumsum()상태가 바뀔 때마다 번호를 올린다지연이 끊기거나 시작될 때 새 사건 번호
events = [...]넘은 날을 번호별로 묶어 첫날과 끝날을 꺼낸다사건마다 시작일과 끝일

결과의 사건은 둘입니다. 10월 20일부터 22일까지 사흘이 한 사건이고, 10월 24일 하루가 또 한 사건입니다. 알림을 날마다 울렸다면 네 번이었을 것을 두 번으로 줄인 셈입니다.

작게 줄여서 눈으로 보기

번호가 어떻게 매겨지는지 한 칸씩 표로 펼쳐 봅니다. 날짜 대신 0부터 6까지의 줄 번호를 씁니다.

Python
import pandas as pd

over = pd.Series([False, False, True, True, True, False, True])
table = pd.DataFrame({
    "넘음": over,
    "앞날": over.shift(),                        # 한 칸 아래로 밀어 앞날 값을 옆에 둔다
    "바뀜": over != over.shift(),                # 앞날과 다르면 True
    "번호": (over != over.shift()).cumsum(),     # True 를 차례로 더한다
})
print(table)
출력
      넘음     앞날     바뀜  번호
0  False    NaN   True   1
1  False  False  False   1
2   True  False   True   2
3   True   True  False   2
4   True   True  False   2
5  False   True   True   3
6   True  False   True   4
결과읽는 법
앞날 칸shift 로 한 칸 밀었다. 첫날은 앞날이 없어 NaN(Not a Number, 값 없음)이다
바뀜 칸앞날과 다르면 True 다. 첫날은 NaN 과 견주니 True 가 된다
번호 칸바뀜의 True 를 위에서부터 더해 1, 1, 2, 2, 2, 3, 4 가 된다
넘음이 True 인 줄2~4번 줄은 번호 2, 6번 줄은 번호 4 다. 번호가 둘이니 사건도 둘이다

이어진 날은 상태가 바뀌지 않으니 번호도 그대로입니다. 이 성질 하나로 "이어진 날 묶기"를 반복문 없이 풉니다. 문턱을 넘지 않은 날(번호 1, 3)은 over[over] 에서 빠집니다.

주의할 점

  • 처음에는 문턱을 느슨하게 잡고, 알림이 울릴 때마다 진짜 문제였는지 적어 문턱을 고칩니다.
  • 대시보드에는 지표마다 기준선을 함께 그립니다. 숫자 하나만 보면 좋은지 나쁜지 알 수 없습니다.
← 이전 절20장 · 모니터링 지표다음 절 →20장 · 실습 지도