RAG2026-5일37차시 · 루브릭 설계10 / 18← 이전목차다음 →MetaContents

37차시. 루브릭 설계

7. 실습 ③ — 루브릭 자동 채점

출처: 원본 PDF 37차시_루브릭설계.pdf 15쪽 · 쪽 그림 15쪽

사람이 매번 채점할 수는 없습니다. LLM 심판에 루브릭을 넘깁니다.

📝 eval/rubric.py

Python
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
← 이전 절37차시 · 6. 실습 ② — 채점자 간 일치도 확보다음 절 →37차시 · 8. 루브릭 채점 결과 활용