37차시. 루브릭 설계
7. 실습 ③ — 루브릭 자동 채점
출처: 원본 PDF 37차시_루브릭설계.pdf 15쪽 · 쪽 그림 15쪽
사람이 매번 채점할 수는 없습니다. LLM 심판에 루브릭을 넘깁니다.
📝 eval/rubric.py
import json, re
from langchain_core.prompts import ChatPromptTemplate
from langchain_openai import ChatOpenAI
from langchain_core.output_parsers import StrOutputParser
import config
RUBRIC_PROMPT = ChatPromptTemplate.from_template(
"당신은 채점자입니다. 아래 루브릭에 따라 답변을 채점하고 "
"JSON만 출력하십시오.\n\n"
"[루브릭]\n"
"accuracy(정확성) — 0: 사실 오류 있음 / "
"1: 핵심은 맞으나 일부 부정확 / 2: 모든 사실 정확\n"
"grounding(근거성) — 0: 근거 밖 내용 포함 또는 출처 없음 / "
"1: 근거 안이나 출처 표시가 일부 문장에만 / "
"2: 모두 근거 기반이며 각 사실 문장에 출처 표시\n"
"completeness(완결성) — 0: 질문의 절반 이하만 답함 / "
"1: 주요 부분은 답했으나 일부 누락 / "
"2: 질문의 모든 부분에 답함\n"
"clarity(명료성) — 0: 장황하거나 이해 어려움 / "
"1: 읽을 만하나 늘어짐 / 2: 간결하고 명확함\n\n"
"[근거 자료]\n{context}\n\n"
"[질문]\n{question}\n\n"
"[모범 답안]\n{gold}\n\n"
"[채점할 답변]\n{answer}\n\n"
'{{"accuracy": 2, "grounding": 2, "completeness": 1, '
'"clarity": 2, "note": "환불 조건 일부 누락"}}'
)
WEIGHTS = {"accuracy": 0.35, "grounding": 0.30,
"completeness": 0.20, "clarity": 0.15}
GATES = {"accuracy": 1, "grounding": 1} # 최소 기준
_scorer = (RUBRIC_PROMPT
| ChatOpenAI(model=config.EVAL_MODEL, temperature=0)
| StrOutputParser())
def score_by_rubric(question, gold, answer, docs):
raw = _scorer.invoke({
"context": build_context(docs) if docs else "(근거 없음)",
"question": question, "gold": gold, "answer": answer})
m = re.search(r"\{.*\}", raw, re.S)
if not m:
return {"total": 0.0, "passed": False, "note": "채점 실패"}
v = json.loads(m.group())
total = sum(v.get(k, 0) * w for k, w in WEIGHTS.items())
# 모든 항목이 기준(GATES)을 넘었는지 확인
passed = True
for k, g in GATES.items():
if v.get(k, 0) < g:
passed = False
result = {k: v.get(k, 0) for k in WEIGHTS}
result["total"] = round(total, 2)
result["passed"] = passed
result["note"] = v.get("note", "")
return result