LLM 환각 감지와 그라운딩: 사실 기반 AI 응답 보장

AI 기술

LLM 환각사실 검증그라운딩RAGAI 신뢰성

이 글은 누구를 위한 것인가

  • AI가 존재하지 않는 사실을 만들어내는 문제를 겪는 팀
  • 의료·법률·금융 도메인에서 AI 응답의 정확성이 중요한 팀
  • RAG 시스템에서 환각을 최소화하고 싶은 엔지니어

들어가며

LLM은 자신감 있게 틀린 정보를 말한다. 의료 AI가 존재하지 않는 약물 상호작용을 말한다면 심각한 문제다. 환각을 100% 막을 수는 없지만, 탐지하고 최소화할 수 있다.

이 글은 bluefoxdev.kr의 AI 신뢰성 설계 를 참고하여 작성했습니다.


1. 환각 유형과 탐지 전략

[LLM 환각 유형]

사실 환각:
  존재하지 않는 인물, 논문, 법률 인용
  날짜·수치 오류
  지식 컷오프 이후 정보를 아는 척

논리 환각:
  전제에서 결론이 논리적으로 따르지 않음
  자기 모순 (같은 대화에서 다른 답)

충실도 환각 (RAG):
  제공된 문서에 없는 내용을 답변에 포함
  문서 내용을 왜곡하여 인용

[탐지 전략]

1. 인용 강제화:
   "근거 문장을 원문 그대로 인용하세요"
   인용 없는 주장 = 환각 위험

2. 자기 일관성 (Self-Consistency):
   같은 질문 N번 → 답변 불일치 = 환각 가능성

3. 역질문 (Reverse QA):
   답변에서 질문 역생성 → 원 질문과 다르면 환각

4. NLI 검증:
   답변이 소스 문서에 의해 지지되는지 확인

2. 환각 감지 및 방지 구현

import anthropic
import json
from dataclasses import dataclass

client = anthropic.Anthropic()

@dataclass
class GroundedResponse:
    answer: str
    citations: list[dict]
    confidence: float
    hallucination_risk: str  # 'low', 'medium', 'high'

async def grounded_rag_response(
    question: str,
    source_documents: list[dict],
) -> GroundedResponse:
    """RAG 기반 그라운딩된 응답 생성"""
    
    # 문서 번호 부여
    numbered_docs = "\n\n".join(
        f"[문서 {i+1}]: {doc['content'][:800]}"
        for i, doc in enumerate(source_documents)
    )
    
    response = client.messages.create(
        model="claude-sonnet-4-6",
        max_tokens=1500,
        messages=[{
            "role": "user",
            "content": f"""다음 문서들만을 근거로 질문에 답하세요.
문서에 없는 내용은 절대 추가하지 마세요.
확신할 수 없으면 "이 문서에는 해당 정보가 없습니다"라고 하세요.

문서:
{numbered_docs}

질문: {question}

JSON 형식으로 답하세요:
{{
  "answer": "답변",
  "citations": [
    {{"doc_number": 1, "quoted_text": "원문 인용", "supports_claim": "지지하는 주장"}}
  ],
  "confidence": 0.0-1.0,
  "has_sufficient_info": true/false
}}"""
        }]
    )
    
    result = json.loads(response.content[0].text)
    
    hallucination_risk = "low" if result["confidence"] > 0.8 else \
                         "medium" if result["confidence"] > 0.5 else "high"
    
    return GroundedResponse(
        answer=result["answer"],
        citations=result["citations"],
        confidence=result["confidence"],
        hallucination_risk=hallucination_risk,
    )

async def self_consistency_check(
    question: str,
    n_samples: int = 5,
) -> dict:
    """자기 일관성으로 환각 위험 측정"""
    
    responses = []
    
    for _ in range(n_samples):
        response = client.messages.create(
            model="claude-haiku-4-5-20251001",
            max_tokens=300,
            messages=[{"role": "user", "content": question}],
        )
        responses.append(response.content[0].text)
    
    # 핵심 사실 추출 및 일관성 비교
    judge_response = client.messages.create(
        model="claude-opus-4-7",
        max_tokens=400,
        messages=[{
            "role": "user",
            "content": f"""다음 5개 응답의 사실적 일관성을 분석하세요.

질문: {question}

{"".join(f"응답 {i+1}: {r[:200]}" + chr(10) for i, r in enumerate(responses))}

JSON으로:
{{
  "consistent_facts": ["모든 응답이 동의하는 사실"],
  "inconsistent_facts": ["응답마다 다른 사실"],
  "consistency_score": 0.0-1.0,
  "hallucination_risk": "low/medium/high"
}}"""
        }]
    )
    
    result = json.loads(judge_response.content[0].text)
    result["sample_responses"] = responses
    return result

async def verify_factual_claim(
    claim: str,
    context: str = "",
) -> dict:
    """사실 주장 검증"""
    
    response = client.messages.create(
        model="claude-opus-4-7",
        max_tokens=300,
        messages=[{
            "role": "user",
            "content": f"""다음 주장의 사실 여부를 검증하세요.
{"맥락: " + context if context else ""}

주장: {claim}

JSON으로:
{{
  "verdict": "supported/refuted/unknown",
  "confidence": 0.0-1.0,
  "explanation": "이유",
  "suggested_verification": "확인 방법 (URL, 기관 등)"
}}"""
        }]
    )
    
    return json.loads(response.content[0].text)

def add_uncertainty_to_prompt(base_prompt: str) -> str:
    """불확실성 표현을 유도하는 프롬프트 래퍼"""
    
    return f"""{base_prompt}

중요 지침:
- 확실하지 않은 내용은 "~인 것으로 알려져 있습니다" 또는 "~일 수 있습니다"로 표현
- 모르는 것은 솔직하게 "이 정보를 확인할 수 없습니다"라고 말하세요
- 숫자/날짜는 출처와 함께 제시하세요
- 추측은 명확히 "추측하건대"로 표시하세요"""

마무리

환각 방지의 최선은 RAG + 인용 강제화다. LLM이 소스 문서의 원문을 인용하게 하면 자의적 창작을 막는다. 고위험 도메인(의료, 법률)에서는 자기 일관성 체크와 사람 검토를 반드시 추가하라. "모른다"고 말하는 AI가 틀린 정보를 자신있게 말하는 AI보다 훨씬 안전하다.