20장. LLM 평가와 모니터링
실습 6 — 운영 지표와 알림
같은 운영 기록에서 날마다의 지표(질문 수, 지연 p95, 오류율, 모름·싫어요 비율)를 뽑고, 본문 "대시보드와 알림" 절의 문턱으로 알림을 겁니다. 이어진 날은 하나의 알림 사건으로 묶고, 네 지표를 문턱과 함께 그립니다.
1단계. 날마다의 지표
import matplotlib.pyplot as plt
import pandas as pd
plt.rcParams["font.family"] = "Malgun Gothic" # 한글 글꼴(4단계의 그래프)
plt.rcParams["axes.unicode_minus"] = False
log = pd.read_csv("chat_log.csv", parse_dates=["시각"])
log["날"] = log["시각"].dt.normalize()
daily = log.groupby("날").agg(
질문=("질문", "size"),
p95=("지연초", lambda s: s.quantile(0.95)),
오류율=("오류", "mean"),
모름=("모름", "mean"),
싫어요=("피드백", lambda s: (s == "싫어요").mean()),
)
# 하루 값은 흔들리므로 모름·싫어요는 7일 이동 평균도 만든다(첫 6일은 있는 날만으로)
daily["모름7"] = daily["모름"].rolling(7, min_periods=1).mean()
daily["싫어요7"] = daily["싫어요"].rolling(7, min_periods=1).mean()
# 결과: 기준 기간(9월 7일~10월 4일)의 평소 값
print(daily.loc[:"2026-10-04"][["질문", "p95", "오류율", "모름", "싫어요"]].mean().round(3).to_dict()){'질문': 30.714, 'p95': 3.031, '오류율': 0.012, '모름': 0.05, '싫어요': 0.038}2단계. 알림 규칙 걸기
RULES = {"p95": 6.0, "오류율": 0.05, "모름7": 0.12, "싫어요7": 0.08} # 이 값을 넘으면 알린다
over = pd.DataFrame({k: daily[k] > v for k, v in RULES.items()})
# 결과: 지표마다 문턱을 넘은 날의 수
print(over.sum().to_dict()){'p95': 3, '오류율': 3, '모름7': 15, '싫어요7': 14}3단계. 이어진 날을 사건으로 묶기
events = []
for metric in RULES:
s = over[metric]
group = (s != s.shift()).cumsum() # 상태가 바뀔 때마다 번호가 하나 오른다
for _, g in s[s].groupby(group[s]):
peak = daily.loc[g.index, metric].max()
events.append({"지표": metric, "시작": g.index[0].date(), "끝": g.index[-1].date(), "날수": len(g),
"가장 나쁜 값": round(peak, 3), "문턱": RULES[metric]})
events = pd.DataFrame(events).sort_values("시작")
# 결과: 알림 사건 목록
print(events.to_string(index=False)) 지표 시작 끝 날수 가장 나쁜 값 문턱
싫어요7 2026-09-08 2026-09-08 1 0.081 0.08
오류율 2026-09-13 2026-09-14 2 0.071 0.05
모름7 2026-10-18 2026-11-01 15 0.287 0.12
p95 2026-10-20 2026-10-22 3 8.922 6.00
싫어요7 2026-10-20 2026-11-01 13 0.147 0.08
오류율 2026-10-21 2026-10-21 1 0.069 0.05알림 사건은 여섯입니다. 앞의 둘은 헛알림입니다. 9월 8일의 싫어요 알림은 이동 평균의 첫 이틀이라 표본이 적어서 났고, 9월 13~14일의 오류율 알림은 하루 30개 남짓 가운데 2개가 오류여서 났습니다. 나머지 넷이 실제 사건입니다. 모름(10월 18일부터)과 싫어요(10월 20일부터)는 끝날까지 이어졌고, 지연 p95 는 10월 20~22일에 8.9초까지 올랐고, 오류율은 10월 21일에 0.069 였습니다. 헛알림을 줄이려면 하루 질문 수가 적은 날은 알리지 않거나, 이틀 이어질 때만 알리는 규칙을 더합니다.
4단계. 계기판 그리기
fig, axes = plt.subplots(2, 2, figsize=(11, 6), sharex=True)
for ax, (col, title) in zip(axes.flat, [("p95", "지연 p95(초)"), ("오류율", "오류율"),
("모름7", "모름 비율(7일 이동)"), ("싫어요7", "싫어요 비율(7일 이동)")]):
daily[col].plot(ax=ax, title=title)
ax.axhline(RULES[col], linestyle="--", color="gray") # 문턱
ax.set_xlabel("")
plt.tight_layout()
plt.show()
이 실습이 뜻하는 것
이 실습은 가게 계기판에 경보등을 다는 일과 같습니다. 날마다 손님 수, 기다린 시간, 실수, 불만을 적고(1단계), 칸마다 경보선을 그어 넘은 날을 세고(2단계), 며칠 이어서 울린 경보를 한 사건으로 묶은 뒤(3단계), 계기판 그림을 그립니다(4단계). 여기서 기다린 시간이 지연 p95(95번째 백분위수, 100번 가운데 느린 쪽 다섯 번을 뺀 가장 긴 값), 경보선이 문턱(RULES), 이어진 경보의 묶음이 알림 사건입니다. 비유와 다른 점은, 경보등은 그 순간만 보지만 여기의 모름·싫어요는 7일 이동 평균이라 지난 일주일을 함께 본다는 것입니다.
| 단계 | 출력 | 뜻 |
|---|---|---|
| 1. 평소 값 | 질문 30.714, p95 3.031, 오류율 0.012, 모름 0.05, 싫어요 0.038 | 기준 기간의 하루 평균입니다. 하루 질문이 31개쯤, 느린 쪽 답도 3초 남짓, 오류는 100번에 1번쯤입니다 |
| 2. 넘은 날 | p95 3, 오류율 3, 모름7 15, 싫어요7 14 | 지표마다 문턱을 넘은 날의 수입니다. 모름과 싫어요는 두 주 안팎으로 넘었습니다 |
| 3. 알림 사건 | 여섯 줄 | 문턱을 넘은 날 35개가 사건 6건으로 묶였습니다. 날마다 울렸다면 35번 알렸을 것입니다 |
| 3. 헛알림 | 9월 8일 싫어요7 0.081, 9월 13~14일 오류율 0.071 | 이동 평균의 첫머리라 표본이 적었거나, 하루 30개 남짓 가운데 2개가 오류여서 난 헛알림입니다 |
| 3. 실제 사건 | 모름7 10월 18일~11월 1일 최고 0.287, p95 10월 20~22일 최고 8.922 | 10월 중순부터 모름과 싫어요가 끝날까지 이어졌고, 그 사이 지연이 9초 가까이 올랐습니다 |
| 4. 계기판 | 네 칸 그래프 | 네 지표를 날마다 선으로 그리고 문턱을 회색 점선으로 그었습니다. 선이 점선 위로 올라간 구간이 위의 사건입니다 |
한 줄로 줄이면, 날마다의 지표에 문턱을 걸고 이어진 날을 묶으면 35번 울릴 알림이 6건의 사건으로 줄어든다는 것을 보인 실습입니다. 그 가운데 2건은 헛알림이라, 하루 질문이 적은 날은 빼는 식으로 규칙을 더 다듬어야 합니다. 알림은 "언제 나빠졌나"를 알려 줄 뿐 "왜"는 알려 주지 않으니, 원인은 서버 기록과 맞춰 찾아야 합니다. 이 지표들을 주 단위로 묶어 화면에 띄우는 일은 프로젝트에서 합니다.
코드 설명
groupby("날").agg(이름=(칸, 함수))는 날마다 여러 지표를 한 번에 냅니다. p95 는s.quantile(0.95)입니다.rolling(7, min_periods=1).mean()은 7일 이동 평균입니다. 하루 표본이 30개 안팎이라 흔들리는 비율 지표를 고르게 합니다.RULES에 지표와 문턱을 모아 두면, 문턱을 고칠 때 한 곳만 바꿉니다.(s != s.shift()).cumsum()은 상태가 바뀔 때마다 번호를 올려, 이어진 날을 같은 묶음으로 만듭니다. 알림 피로를 줄이는 가장 쉬운 방법입니다.