RAG2026-5일26차시 · Verifier Node 설계8 / 19← 이전목차다음 →MetaContents

26차시. Verifier Node 설계

5. 실습 ② — Verifier Node 완성

출처: 원본 PDF 26차시_VerifierNode설계.pdf 11쪽 · 쪽 그림 11쪽

📝 src/nodes/verifier.py

Python
import json, re
import config
from prompts import JUDGE_PROMPT
from nodes.generator import build_context, _llm
from langchain_core.output_parsers import StrOutputParser

_judge = JUDGE_PROMPT | _llm | StrOutputParser()

def _parse_json(raw: str) -> dict:
    # 모델이 앞뒤에 말을 붙여도 { } 블록만 뽑아낸다
    m = re.search(r"\{.*\}", raw, re.S)
    if not m:
        raise ValueError("JSON 블록을 찾을 수 없음")
    return json.loads(m.group())

def verifier_node(state) -> dict:
    docs   = state.get("documents") or []
    answer = state.get("answer", "")

    # ── 1차: 규칙 기반 (비용 0) ──
    if state.get("gen_error"):
        return {"grade": "giveup", "reason": "생성 오류 발생",
                "log": ["검증: 생성 오류로 중단"]}

    if state.get("insufficient"):
        return {"grade": "research", "reason": "근거 부족 자가신고",
                "log": ["검증(규칙): 근거 부족"]}

    if not state.get("has_citation"):
        return {"grade": "retry", "reason": "출처 번호가 없거나 유효하지 않음",
                "log": ["검증(규칙): 인용 불량"]}

    if len(answer.strip()) < 10:
        return {"grade": "retry", "reason": "답변이 너무 짧음",
                "log": ["검증(규칙): 답변 길이 부족"]}

    # ── 2차: LLM 심판 ──
    try:
        raw = _judge.invoke({
            "context":  build_context(docs),
            "question": state["question"],
            "answer":   answer,
        })
        v = _parse_json(raw)
    except Exception as e:
        # 심판이 실패하면 통과시킨다 (검증 때문에 서비스가 막히면 안 됨)
        return {"grade": "pass",
                "reason": f"검증 불가({type(e).__name__}) - 통과 처리",
                "log": [f"검증 오류: {type(e).__name__}"]}

    grounded = bool(v.get("grounded"))
    relevant = bool(v.get("relevant"))
    reason   = str(v.get("reason", ""))[:200]

    if grounded and relevant:
        grade = "pass"
    elif not grounded:
        grade = "retry"        # 근거 이탈 → 다시 생성
    else:
        grade = "research"     # 관련성 부족 → 다시 검색

    return {"grade": grade, "reason": reason,
            "log": [f"검증(LLM): g={grounded} r={relevant} -> {grade}"]}
← 이전 절26차시 · 4. 실습 ① — LLM 심판 프롬프트다음 절 →26차시 · 6. 판정 결과를 세 가지로 나눈 이유