캐싱·평가·운영
응답 캐시로 비용과 지연을 줄이고, 사용자 피드백과 골든 질문 세트·LLM 평가자로 AI 애플리케이션의 버전을 비교해 배포 여부를 정하는 운영 방법을 익힌다.
- 데모와 운영은 다름. 같은 질문에 매번 돈을 내야 하나(캐싱), 사용자는 만족하나(피드백), 고친 버전을 배포해도 되나(평가와 AgentOps).
캐싱
- 캐싱(caching): 이전 결과를 저장해 두고 같은 요청이 오면 다시 계산하지 않음. LLM 호출(수 초, 토큰 요금) 대신 거의 즉시, 무료.
- 키: 입력 문자열(가장 안전, 표현이 다르면 놓침), 입력 임베딩(의미 캐시), 항목 ID(상품 리뷰 요약을 미리 만들어 둠).
- 적중률(hit rate): 캐시에서 바로 응답한 비율. 고객센터 FAQ처럼 소수 질문이 트래픽 대부분이면 효과가 큼.
- 의미 캐시의 함정: 기준값을 낮추면 적중률은 오르지만 "터미네이터 1 줄거리"와 "터미네이터 2 줄거리", "실버/골드 회원 적립률"처럼 비슷하지만 답이 다른 질문에 엉뚱한 답을 줌. 원본(안내서)이 바뀌면 캐시도 비워야 함.
- API의 프롬프트 캐싱(prompt caching)은 별개: 앞부분이 같은 요청의 계산을 재사용해 입력 토큰 요금을 할인.
사용자 피드백
- 명시적 피드백(explicit): 좋아요/싫어요, 별점, 신고. 정확하지만 양이 적음.
- 암시적 피드백(implicit): 행동에서 추론. 코드 제안을 그대로 수용(강한 긍정), 이미지 다시 생성(부정), 챗봇 답변 직후 상담원 연결 요청(부정).
- 화면 설계는 AI 프로덕트 디자인.
AgentOps
- AgentOps: DevOps·MLOps를 에이전트·LLM 애플리케이션에 적용. 출력이 확률적이라
output == expected테스트가 맞지 않아 품질 판단에 다른 LLM이 필요.
Mermaid 다이어그램 원문
flowchart LR
M[지표 정의] --> E[골든 세트 + LLM 평가자]
E --> G{Go/No-Go}
G -- Go --> D[점진 배포·모니터링]
G -- No-Go --> F[수정]
D --> T[궤적 추적으로 원인 분석]
D --> U[사용자 피드백]
U --> C[새 테스트 케이스로 추가]
C --> E
T --> F --> E대화형 다이어그램을 불러오기 전에는 원문으로 표시됩니다.
- 중요한 것을 측정: 기술 지표(정답률, 지연, 비용)와 비즈니스 지표(만족도, 상담원 연결 감소)를 먼저 정함.
- 품질 평가: 골든 세트(golden set, 대표 질문과 기대 답)를 LLM 평가자로 채점. 같은 세트로 매번 평가해야 버전 비교가 됨.
- Go/No-Go: 새 버전 점수를 운영 버전과 비교해 배포 결정. 점수가 같아도 예전에 맞히던 질문을 틀리면(회귀) 확인.
- 궤적 추적: 한 요청의 프롬프트, 검색 결과, 도구 호출, 시간, 토큰을 기록. RAG라면 검색과 생성 중 어디가 틀렸는지 구분.
- 피드백 루프 닫기: 싫어요 수집 → 재현 → 골든 세트에 영구 추가 → 수정 → 재평가.
- 평가 비용: 답이 정해진 채점은 판단 모델로 대신 가능. Rao·Callison-Burch(2026)는 LLM 평가자가 비용 16-357배, 시간 5-350배이면서 두 종류가 틀리는 곳이 비슷하다고 보고.
- 출처: Rao, Callison-Burch(2026), Decision Models vs. LLMs as Rubric Judges: Cheaper, Faster, and Wrong in the Same Places.
실습: 골든 세트로 두 버전 비교
- 10주의 마인드몰 RAG를 두 버전으로: v1은 가장 가까운 문단 1개, v2는 3개를 자료로 줌. 검색은 numpy로 간단히(RAG 파이프라인 만들기).
- 실습 자료 다운로드: mindmall_guide.py. 현재 폴더에 업로드.
!pip install -q openai numpy
from pathlib import Path
import numpy as np
from openai import OpenAI
from mindmall_guide import guide
client = OpenAI(
base_url="https://openrouter.ai/api/v1",
api_key=Path("openrouter-key.txt").read_text().strip(), # 키 파일에서 읽기
)
model = "openai/gpt-4.1-mini"
def embed(texts: list[str]) -> np.ndarray:
response = client.embeddings.create(model="openai/text-embedding-3-small", input=texts)
return np.array([d.embedding for d in response.data])
chunks = [f"[{s.splitlines()[0]}] {p}" # 문단마다 절 제목을 붙임
for s in guide.split("\n## ")[1:] for p in s.split("\n\n")[1:]]
chunk_vectors = embed(chunks)
def rag_answer(question: str, k: int) -> str:
top = np.argsort(-(chunk_vectors @ embed([question])[0]))[:k]
sources = "\n\n".join(chunks[i] for i in top)
prompt = f"다음 자료만 근거로 답하세요. 자료에 없으면 '안내서에서 찾을 수 없습니다'라고 답하세요.\n\n{sources}\n\n질문: {question}"
response = client.chat.completions.create(
model=model, messages=[{"role": "user", "content": prompt}], temperature=0)
return response.choices[0].message.content
- 골든 세트: 안내서로 정답을 확인할 수 있는 질문. 여러 절을 함께 봐야 하는 질문(Q2)과 안내서에 없는 질문(Q3)을 섞음. 실제로는 고객센터 로그와 과거에 틀린 질문으로 수십-수백 개.
- LLM 평가자: 핵심 사실(금액·기간·조건)이 맞는지 구조화된 출력으로 판정. 이유를 먼저 쓰게 함.
from pydantic import BaseModel
golden = [
("단순 변심으로 반품하면 배송비는 얼마인가요?", "왕복 배송비 6,000원을 고객이 낸다."),
("골드 회원이 3만 원어치를 주문하면 배송비가 있나요?", "없다. 골드 회원은 모든 주문이 무료 배송이다."),
("해외로도 배송해 주나요?", "안내서에 내용이 없다. 모른다고 답해야 한다."),
("마인드키 키보드를 산 지 1년 반인데 고장 났어요. 무상 수리되나요?", "된다. 마인드키 제품은 2년 무상 수리."),
]
class Verdict(BaseModel):
reason: str
passed: bool
def judge(question: str, reference: str, answer: str) -> Verdict:
prompt = (f"기대 답의 핵심 사실(금액, 기간, 조건)이 실제 답에 맞게 들어 있으면 합격, 빠졌거나 모순되거나 지어냈으면 불합격.\n"
f"[질문] {question}\n[기대 답] {reference}\n[실제 답] {answer}")
response = client.chat.completions.parse(
model=model, messages=[{"role": "user", "content": prompt}],
response_format=Verdict, temperature=0)
return response.choices[0].message.parsed
for i, (q, ref) in enumerate(golden, start=1):
marks = []
for k in [1, 3]:
verdict = judge(q, ref, rag_answer(q, k))
marks.append("O" if verdict.passed else "X")
if not verdict.passed:
print(f" (Q{i}, k={k} 불합격 이유: {verdict.reason[:70]})")
print(f"Q{i} v1={marks[0]} v2={marks[1]} {q[:25]}")
실행 결과
Q1 v1=O v2=O 단순 변심으로 반품하면 배송비는 얼마인가요?
(Q2, k=1 불합격 이유: 실제 답변에는 배송비 여부에 대한 명확한 정보가 없으며, 기대 답변에서 제시한 '골드 회원은 모든 주문이 무료 배송이다'라는 )
Q2 v1=X v2=O 골드 회원이 3만 원어치를 주문하면 배송비가
Q3 v1=O v2=O 해외로도 배송해 주나요?
Q4 v1=O v2=O 마인드키 키보드를 산 지 1년 반인데 고장 났
- 위 실행에서는 v1이 Q2만 틀리고 v2는 4개 모두 합격. Q2는 회원 등급(골드 무료 배송)과 배송 절을 함께 봐야 해서 문단 1개만 가져오는 v1에 불리함. 평가 이유를 보면 생성보다 검색(k=1)이 원인임을 짐작할 수 있음.
temperature=0이어도 실행마다 판정이 바뀔 수 있음. 실제로는 질문을 늘리고 여러 번 돌림.- Go/No-Go 규칙을 코드로 미리 정해 둠: 예) 합격 수가 운영 버전 이상이고 회귀가 없으면 Go. 4문항에서 1문제 차이는 우연일 수 있음.
- 더 해 볼 것:
- 검색된 문단·프롬프트·토큰 수를 궤적으로 기록해 원인 확인. v2는 토큰도 늘어나므로 비용과 함께 판단.
- 싫어요가 달린 질문에 사람이 기대 답을 붙여
golden에 추가. - 정확 일치 캐시와 의미 캐시를 붙여 적중률과 기준값별 오답을 비교.
퀴즈
임베딩을 키로 쓰는 의미 캐시의 기준값을 너무 낮추면 생기는 문제는?
퀴즈를 풀려면 대화형 기능을 불러와야 합니다.