diff --git a/app/engine/legal_risk.py b/app/engine/legal_risk.py index d0df39a..947fb4b 100644 --- a/app/engine/legal_risk.py +++ b/app/engine/legal_risk.py @@ -203,8 +203,6 @@ class LegalRiskEngine: unknown = set(decision.matched_precedent_ids) - allowed if unknown: raise ValueError(f"LLM이 제공되지 않은 판례 ID를 반환함: {sorted(unknown)}") - if not decision.review_required: - raise ValueError("LLM legal judge는 사람 검토를 해제할 수 없습니다") if decision.verdict == "likely" and not decision.matched_precedent_ids: raise ValueError("침해 의심 의견에는 근거 판례 ID가 최소 1개 필요합니다") merged_missing = tuple(dict.fromkeys([ @@ -217,6 +215,7 @@ class LegalRiskEngine: "judgment_method": "llm", "llm_verdict": decision.verdict, "llm_confidence": decision.confidence, + # 모델 출력과 관계없이 서비스의 사람 검토 안전장치는 항상 유지한다. "llm_review_required": True, "llm_matched_precedent_ids": decision.matched_precedent_ids, "supporting_reasons": decision.supporting_reasons, diff --git a/app/engine/openai_legal_judge.py b/app/engine/openai_legal_judge.py index 78e6b18..60b3fef 100644 --- a/app/engine/openai_legal_judge.py +++ b/app/engine/openai_legal_judge.py @@ -10,7 +10,7 @@ from pydantic import BaseModel, ConfigDict, Field from app.engine.legal_risk import LegalJudgeDecision, LegalJudgeInput -PROMPT_VERSION = "legal-judge-v2" +PROMPT_VERSION = "legal-judge-v3" class _JudgeOutput(BaseModel): @@ -22,7 +22,7 @@ class _JudgeOutput(BaseModel): supporting_reasons: list[str] counter_reasons: list[str] missing_factors: list[str] - review_required: bool + review_required: Literal[True] SYSTEM_PROMPT = """당신은 대한민국 저작권 판례 비교 검토 보조자다. @@ -37,6 +37,7 @@ insufficient_evidence는 탐지된 표현 증거나 관련 판례가 부족해 유사도 점수는 침해 확률이 아니다. 보호되는 표현, 의거관계, 권리 귀속 및 이용허락 사실이 없으면 missing_factors에 명시한다. 근거와 반대 근거는 검토자가 확인할 수 있는 짧은 사실 문장으로 작성한다. +review_required는 항상 true로 반환한다. """ diff --git a/docs/LLM_LEGAL_JUDGE.md b/docs/LLM_LEGAL_JUDGE.md index 2a2456b..d38e2de 100644 --- a/docs/LLM_LEGAL_JUDGE.md +++ b/docs/LLM_LEGAL_JUDGE.md @@ -7,7 +7,7 @@ 1. 등록 코퍼스에서 유사 문서와 증거 구간을 찾는다. 2. `LegalRiskEngine`이 저작물 유형과 법적 태그로 등록 판례 Top 5를 고른다. 3. GPT Judge가 탐지 증거와 Top 5 판례만 비교한다. -4. 서버가 Structured Outputs 스키마, 판례 ID, `review_required=true`를 검증한다. +4. 서버가 Structured Outputs 스키마와 판례 ID를 검증하고, 사람 검토가 해제되지 않도록 `review_required=true`를 강제한다. 5. 호출 실패·형식 오류·미등록 판례 ID가 있으면 규칙 기반 결과로 복귀한다. GPT는 전체 판례 545건을 한 번에 받지 않으며 법률상 침해를 확정하지 않는다. diff --git a/tests/test_legal_risk.py b/tests/test_legal_risk.py index d6abf93..6b1dede 100644 --- a/tests/test_legal_risk.py +++ b/tests/test_legal_risk.py @@ -104,15 +104,15 @@ def test_llm_hallucinated_precedent_id_falls_back_to_rules(): assert "GPT" not in (result.judge_note or "") -def test_llm_cannot_disable_human_review(): +def test_server_keeps_human_review_even_if_judge_returns_false(): result = LegalRiskEngine([_case()], judge=_FakeJudge(review_required=False)).assess( max_similarity=0.9, coverage=0.5, longest_span=200, legal_tags=["reproduction"], ) - assert result.judgment_method == "rule_fallback" - assert result.llm_review_required is None + assert result.judgment_method == "llm" + assert result.llm_review_required is True def test_likely_judgment_without_precedent_id_falls_back():