모델 평가 및 Langfuse 기반 모델 고도화(Sagemaker) - 100-hours-a-week/9-team-Devths-WIKI GitHub Wiki

모델 평가 및 Langfuse 기반 모델 고도화

작성일: 2026-02-28 프로젝트: Devths AI - 면접 AI 서비스


목차

  1. 개요
  2. Phase 1: Gemini API 기반 초기 구현
  3. Phase 2: 프롬프트 엔지니어링 도입
  4. Phase 3: LangChain/LCEL 도입 후 파이프라인 구조화
  5. Phase 4: Langfuse 모니터링 및 관찰성 확보
  6. Phase 5: LLM-as-Judge 기반 자동 평가 시스템
  7. Phase 6: 평가 기준 고도화 (Faithfulness, Context Relevance)
  8. Phase 7: Multi-Judge 합의 시스템
  9. Phase 8: EXAONE 모델 평가 및 파인튜닝
  10. 전체 파이프라인 아키텍처
  11. 성과 및 배운 점

1. 개요

1.1 프로젝트 배경

면접 AI 서비스에서 LLM 응답 품질을 객관적으로 측정하고 지속적으로 개선하기 위한 평가 시스템을 구축했습니다. 단순 API 호출에서 시작하여 체계적인 평가 프레임워크까지 발전시킨 과정을 기록합니다.

1.2 해결하고자 한 문제

[문제 상황]
1. LLM 응답 품질을 주관적으로만 판단 → 개선 방향 불명확
2. 모델/프롬프트 변경 시 효과 측정 불가 → A/B 테스트 어려움
3. RAG 파이프라인에서 환각(Hallucination) 감지 불가
4. 프로덕션 품질 모니터링 부재 → 장애 사후 대응만 가능

1.3 목표

  • 정량적 평가: 동일 기준으로 모델/파이프라인 품질 비교
  • 자동화: 수동 검토 없이 CI/CD 파이프라인에서 품질 게이트 적용
  • 관찰성: 프로덕션 트래픽의 실시간 품질 모니터링
  • 환각 탐지: RAG 응답이 컨텍스트에 근거하는지 자동 검증

2. Phase 1: Gemini API 기반 초기 구현

2.1 초기 아키텍처

[사용자 질문] → [Gemini API 직접 호출] → [응답 반환]
                     ↑
              프롬프트 하드코딩

2.2 구현 방식

# 초기 구현 (단순 API 호출)
import google.generativeai as genai

genai.configure(api_key=GOOGLE_API_KEY)
model = genai.GenerativeModel('gemini-pro')

def generate_interview_question(resume_text: str) -> str:
    prompt = f"다음 이력서를 보고 면접 질문을 생성해주세요:\n{resume_text}"
    response = model.generate_content(prompt)
    return response.text

2.3 문제점

문제 영향
응답 품질 측정 불가 개선 방향 설정 불가
프롬프트 버전 관리 없음 변경 이력 추적 불가
에러 핸들링 미흡 API 장애 시 서비스 중단
토큰 사용량 미추적 비용 예측 불가

3. Phase 2: 프롬프트 엔지니어링 도입

3.1 프롬프트 템플릿 분리

app/prompts/
├── templates/
│   ├── interview/
│   │   ├── personality.md      # 인성 면접 프롬프트
│   │   ├── technical.md        # 기술 면접 프롬프트
│   │   └── followup.md         # 꼬리 질문 프롬프트
│   ├── analysis/
│   │   └── resume_analysis.md  # 이력서 분석 프롬프트
│   └── evaluation/
│       └── llm_judge.md        # LLM-as-Judge 프롬프트
└── loader.py                   # 프롬프트 로더

3.2 프롬프트 구조화

# llm_judge.md (실제 구현)

당신은 AI 채팅 응답 품질을 객관적으로 평가하는 전문가입니다.
주어진 질문에 대한 AI 응답을 4가지 기준으로 채점해주세요.

## 질문
{question}

## 평가할 응답
{answer}

## 채점 기준 (각 1~5점)

- **relevance** (관련성): 질문의 의도에 얼마나 정확히 답변했는가
  - 5: 질문 의도를 완전히 파악하고 핵심을 정확히 답변
  - 3: 관련은 있으나 핵심을 일부 벗어남
  - 1: 질문과 무관한 내용

- **accuracy** (정확성): 제공한 정보가 사실에 얼마나 부합하는가
- **fluency** (유창성): 답변이 자연스럽고 읽기 쉬운가
- **completeness** (완전성): 질문이 요구하는 내용을 충분히 다루었는가

## 출력 형식
반드시 JSON 형식으로만 응답해주세요.

3.3 개선 효과

  • 일관성: 동일 프롬프트로 재현 가능한 결과
  • 버전 관리: Git으로 프롬프트 변경 이력 추적
  • 재사용성: 템플릿 기반으로 다양한 시나리오 대응

4. Phase 3: LangChain/LCEL 도입 후 파이프라인 구조화

4.1 LCEL 기반 체인 구성

# app/services/rag_service.py
from langchain_core.runnables import RunnablePassthrough
from langchain_core.output_parsers import StrOutputParser

class RAGService:
    def _build_rag_chain(self):
        """LCEL 기반 RAG 체인 구성."""
        return (
            {
                "context": self._retriever | self._format_docs,
                "question": RunnablePassthrough(),
            }
            | self._prompt_template
            | self._llm
            | StrOutputParser()
        )

4.2 콜백 핸들러 통합

# Langfuse CallbackHandler 연동
from langfuse.callback import CallbackHandler

def get_langfuse_callback_handler(
    session_id: str | None = None,
    user_id: str | None = None,
    trace_name: str | None = None,
) -> CallbackHandler | None:
    """LangChain CallbackHandler 반환."""
    return CallbackHandler(
        public_key=LANGFUSE_PUBLIC_KEY,
        secret_key=LANGFUSE_SECRET_KEY,
        session_id=str(session_id) if session_id else None,
        user_id=str(user_id) if user_id else None,
        trace_name=trace_name or "langchain-trace",
    )

4.3 파이프라인 구조

[사용자 질문]
      ↓
[Retriever] ─→ ChromaDB 검색 ─→ [컨텍스트]
      ↓                              ↓
[Prompt Template] ←─────────────────┘
      ↓
[LLM (Gemini/EXAONE)]
      ↓
[Output Parser]
      ↓
[Langfuse Callback] ─→ 트레이스 기록
      ↓
[응답 반환]

5. Phase 4: Langfuse 모니터링 및 관찰성 확보

5.1 Langfuse 클라이언트 구현

# app/utils/langfuse_client.py (실제 구현)

from langfuse import Langfuse
from langfuse.decorators import observe

_langfuse_client: Langfuse | None = None

def get_langfuse_client() -> Langfuse | None:
    """Langfuse 클라이언트 싱글톤 인스턴스 반환."""
    global _langfuse_client
    
    if _langfuse_client is not None:
        return _langfuse_client
    
    public_key = os.getenv("LANGFUSE_PUBLIC_KEY")
    secret_key = os.getenv("LANGFUSE_SECRET_KEY")
    host = os.getenv("LANGFUSE_HOST", "https://cloud.langfuse.com")
    
    _langfuse_client = Langfuse(
        public_key=public_key, 
        secret_key=secret_key, 
        host=host
    )
    return _langfuse_client

def trace_llm_call(
    name: str, 
    user_id: str | None = None, 
    metadata: dict | None = None
) -> LangfuseTraceContext | None:
    """LLM 호출 추적을 위한 trace 생성."""
    client = get_langfuse_client()
    if client is None:
        return None
    
    trace_id = client.create_trace_id()
    return {
        "client": client,
        "trace_id": trace_id,
        "trace_name": name,
        "user_id": user_id,
        "metadata": dict(metadata or {}),
    }

5.2 @observe 데코레이터 활용

from langfuse.decorators import observe

@observe(name="rag_chat")
async def chat(self, question: str, session_id: str) -> str:
    """RAG 기반 채팅 - 자동 트레이스 생성."""
    context = await self.retrieve_context(question, session_id)
    answer = await self._generate_answer(question, context)
    return answer

5.3 모니터링 대시보드 지표

지표 설명 활용
Trace Count API 호출 횟수 사용량 추적
Latency (P50/P95) 응답 시간 분포 성능 병목 파악
Token Usage 입출력 토큰 수 비용 최적화
Error Rate 실패율 안정성 모니터링
Score Distribution Judge 점수 분포 품질 추이 확인

6. Phase 5: LLM-as-Judge 기반 자동 평가 시스템

6.1 JudgeService 구현

# app/services/judge_service.py (실제 구현)

class JudgeService:
    """LLM-as-Judge — RAG 파이프라인 응답 품질 채점.
    
    Judge 모델: gemini-3.1-pro-preview
    채점 기준: relevance / accuracy / fluency / completeness (각 1~5점)
    """
    
    JUDGE_CRITERIA: list[str] = [
        "relevance", 
        "accuracy", 
        "fluency", 
        "completeness"
    ]
    
    async def score(
        self,
        question: str,
        answer: str,
        pipeline_stage: str,
        reference_answer: str | None = None,
        user_id: str | None = None,
    ) -> JudgeResult:
        """Judge LLM으로 응답 품질 채점 후 Langfuse에 점수 기록."""
        
        # 1. 프롬프트 생성
        prompt = self._build_prompt(question, answer, reference_answer)
        
        # 2. Langfuse trace 생성
        trace = trace_llm_call(
            name=f"llm_judge_{pipeline_stage}",
            user_id=user_id,
            metadata={"pipeline_stage": pipeline_stage},
        )
        
        # 3. Judge LLM 호출
        raw_json = await self._call_judge(prompt)
        
        # 4. 결과 파싱
        result = self._parse_result(raw_json, question, answer, pipeline_stage)
        
        # 5. Langfuse에 점수 기록
        self._record_to_langfuse(trace, result)
        
        return result

6.2 채점 기준 상세

# 4가지 채점 기준 (1~5점)

CRITERIA_DEFINITIONS = {
    "relevance": {
        "name": "관련성",
        "description": "질문의 의도에 얼마나 정확히 답변했는가",
        "rubric": {
            5: "질문 의도를 완전히 파악하고 핵심을 정확히 답변",
            3: "관련은 있으나 핵심을 일부 벗어남",
            1: "질문과 무관한 내용"
        }
    },
    "accuracy": {
        "name": "정확성",
        "description": "제공한 정보가 사실에 얼마나 부합하는가",
        "rubric": {
            5: "모든 정보가 정확하고 신뢰할 수 있음",
            3: "대체로 정확하나 일부 부정확한 부분 있음",
            1: "명백히 틀리거나 근거 없는 정보 포함"
        }
    },
    "fluency": {
        "name": "유창성",
        "description": "답변이 자연스럽고 읽기 쉬운가",
        "rubric": {
            5: "매우 자연스럽고 명확하며 잘 구조화됨",
            3: "읽을 수 있으나 어색한 부분 있음",
            1: "이해하기 어렵거나 매우 어색함"
        }
    },
    "completeness": {
        "name": "완전성",
        "description": "질문이 요구하는 내용을 충분히 다루었는가",
        "rubric": {
            5: "질문의 모든 측면을 충분히 다룸",
            3: "주요 내용은 다루었으나 일부 누락",
            1: "핵심 내용을 거의 다루지 못함"
        }
    }
}

6.3 Langfuse Score 기록

def record_score(
    trace: LangfuseTraceContext | None,
    name: str,
    value: float,
    comment: str | None = None,
) -> None:
    """Judge LLM이 채점한 점수를 Langfuse trace에 기록."""
    if trace is None:
        return
    
    client = trace["client"]
    client.score(
        trace_id=trace["trace_id"],
        name=name,
        value=value,
        comment=comment,
    )

6.4 4단계 파이프라인 비교 평가

[테스트셋 JSON] ────────────────────────────────────────────────┐
                                                                │
  stage=langchain_only → LLMService (Gemini, RAG OFF)          │
  stage=rag            → RAGService.retrieve_context() + LLM   ├→ [JudgeService]
  stage=vllm           → VLLMService (ExaONE 8B)               │       ↓
  stage=sagemaker      → HTTP → SAGEMAKER_ENDPOINT             │  [JudgeResult]
                                                                │       ↓
                                                                └→ Langfuse score
                                                                        ↓
                                                              data/eval/results.json

6.5 평가 스크립트 실행

# RAG 단계만 평가
python scripts/llm_judge_eval.py --stage rag

# 4단계 전체 비교 평가
python scripts/llm_judge_eval.py --stage all

# 단건 API 채점
curl -X POST /ai/evaluation/llm-judge \
  -H "Content-Type: application/json" \
  -d '{"question":"...", "answer":"...", "pipeline_stage":"rag"}'

7. Phase 6: 평가 기준 고도화 (Faithfulness, Context Relevance)

7.1 RAG Triad 평가 체계

Chip Huyen의 AI Engineering Chapter 4와 RAGAS 프레임워크를 참고하여 RAG 파이프라인 전용 평가 기준을 추가했습니다.

┌─────────────────────────────────────────────┐
│               RAG Triad 평가                 │
├─────────────────────────────────────────────┤
│                                             │
│    질문 ←──── Answer Relevance ────→ 응답   │
│      │                                │     │
│      │                                │     │
│  Context                          Faithful- │
│  Relevance                           ness   │
│      │                                │     │
│      ▼                                ▼     │
│              컨텍스트 (검색 결과)             │
│                                             │
└─────────────────────────────────────────────┘

1. Context Relevance: 검색 결과가 질문에 관련 있는가? (리트리버 품질)
2. Faithfulness: 응답이 컨텍스트에 근거하는가? (환각 탐지)
3. Answer Relevance: 응답이 질문에 답하는가? (기존 relevance)

7.2 Faithfulness (환각 탐지) 기준 추가

# 기존 4기준 → 6기준 확장
JUDGE_CRITERIA = [
    "relevance",          # Answer Relevance (기존)
    "accuracy",           # 기존
    "fluency",            # 기존
    "completeness",       # 기존
    "faithfulness",       # 신규: 환각 탐지
    "context_relevance",  # 신규: 리트리버 품질
]

7.3 환각 탐지 예시

[환각 탐지 시나리오]

사용자 질문: "이 지원자의 AWS 경험을 알려줘"
RAG 컨텍스트: "FastAPI와 Docker로 배포 경험이 있습니다."
AI 응답: "지원자는 AWS EC2와 S3를 활용한 경험이 있습니다."  ← 환각!

[기존 채점 결과]
  relevance: 5 (질문 의도에 잘 답변)
  accuracy: 4 (기술적으로 가능한 내용)
  → 환각을 감지하지 못함!

[개선된 채점 결과]
  relevance: 5
  accuracy: 4
  faithfulness: 1 (컨텍스트에 없는 정보 생성)  ← 환각 감지!
  → 문제 파악 가능

7.4 진단 매트릭스

Context Relevance Faithfulness Answer Relevance 진단
✅ 높음 ✅ 높음 ✅ 높음 정상 — 파이프라인 최적
❌ 낮음 ✅ 높음 ❌ 낮음 리트리버 문제 — 검색 개선 필요
✅ 높음 ❌ 낮음 ❌ 낮음 LLM 환각 — 프롬프트/모델 개선
✅ 높음 ✅ 높음 ❌ 낮음 프롬프트 문제 — 지시사항 개선

8. Phase 7: Multi-Judge 합의 시스템

8.1 단일 Judge의 한계

Chip Huyen이 강조하는 LLM-as-Judge 한계:

[현재 — 단일 Judge]
Gemini Pro → 채점 → 최종 점수
  ↑ 모델 편향 누적 (Gemini 특유의 관대함/엄격함)
  ↑ 위치 편향 (첫 번째 기준에 높은 점수)

[개선 — Multi-Judge 합의]
Gemini Pro → 채점A ─┐
                      ├→ 불일치 감지 → 재평가 or 평균
GPT-4o     → 채점B ─┘
  ↑ 두 모델의 편향이 상쇄
  ↑ 불일치 항목 = 주관적/모호한 기준 → 개선 대상

8.2 Multi-Judge 설계

class MultiJudgeService:
    """2-Judge 합의 채점."""
    
    async def score_with_consensus(
        self, question, answer, context, pipeline_stage,
    ) -> JudgeResult:
        # 1. Gemini Judge 채점
        gemini_result = await self._gemini_judge.score(...)
        
        # 2. GPT-4o Judge 채점
        gpt4o_result = await self._gpt4o_judge.score(...)
        
        # 3. 불일치 감지 (점수 차이 >= 1.5)
        disagreements = self._find_disagreements(
            gemini_result, gpt4o_result
        )
        
        # 4. 합의: 점수 평균 or 불일치 항목 재평가
        return self._consensus(
            gemini_result, gpt4o_result, disagreements
        )

8.3 기대 효과

장점 단점
모델 편향 상쇄 → 채점 신뢰도 향상 GPT-4o 호출 비용 추가
불일치 항목 → 평가 기준 개선 인사이트 레이턴시 2배 (병렬 실행으로 완화)
기존 Debate 패턴 재활용 가능 OpenAI API 의존성

9. Phase 8: EXAONE 모델 평가 및 파인튜닝

9.1 EXAONE 모델 평가 계획

[평가 대상]
1. EXAONE 7.8B 기본 모델 (vLLM 서빙)
2. EXAONE 7.8B LoRA 파인튜닝 모델
3. EXAONE 32B 기본 모델 (GPU 확보 시)

[평가 방법]
- 동일 테스트셋 20개 (data/eval/rag_test_dataset.json)
- 동일 Judge (Gemini Pro)
- 동일 6기준 채점

9.2 LoRA 파인튜닝

# 학습 데이터: interview_feedback 컬렉션 + 공개 한국어 IT 면접 Q&A
# 방식: LoRA (Low-Rank Adaptation) — 전체 파라미터의 1% 미만만 학습

from peft import LoraConfig, get_peft_model

lora_config = LoraConfig(
    r=16,            # LoRA rank
    lora_alpha=32,
    target_modules=["q_proj", "v_proj"],
    lora_dropout=0.1,
)

# 파인튜닝 목적
# 1. 한국 IT 기업 면접 스타일 학습 (카카오, 네이버, 삼성 SDS 등)
# 2. STAR 기법 답변 생성 능력 향상
# 3. 기술 면접 + 인성 면접 스타일 분리

9.3 SageMaker 기반 학습 파이프라인 구현

9.3.1 데이터 전처리 (Processing Job)

  • 입력: s3://devths-storage-dev/uploads/training-data/.../interview_feedback.jsonl
  • 이미지/리소스: sagemaker-scikit-learn:1.2-1-cpu-py3 (ml.t3.medium 1대)
  • 스크립트: /opt/ml/processing/input/code/preprocess.py
    • JSONL 인터뷰 피드백을 DataFrame으로 로드
    • train/val 8:2 분할(random_state=42)/opt/ml/processing/output/{train,val} 경로에 train.jsonl, val.jsonl 저장
  • 출력 S3 경로:
    • s3://devths-storage-dev/processed-data/train/
    • s3://devths-storage-dev/processed-data/val/

즉, Langfuse에서 수집한 인터뷰 피드백을 SageMaker 학습용 배치 데이터로 표준화하는 단계이다.

9.3.2 AMT 하이퍼파라미터 튜닝 (Tuning Job)

  • Tuning Job: exaone-tuning-job-54
  • 목표 메트릭: 학습 로그에서 'eval_loss': ([0-9\.]+) 패턴을 정규식으로 파싱해 eval_loss 최소화
  • 탐색 범위:
    • learning_rate ∈ [1e-5, 1e-4] (Logarithmic 스케일)
  • 리소스/전략:
    • Strategy: Bayesian
    • MaxNumberOfTrainingJobs: 2, MaxParallelTrainingJobs: 1
    • 인스턴스: ml.g5.xlarge GPU 1대
  • 공통 환경:
    • MLFLOW_TRACKING_URI, MLFLOW_EXPERIMENT_NAME 를 주입해 각 시도를 MLflow 실험으로 기록

AMT는 동일한 train.py를 사용하되 learning_rate만 바꿔 실행하고,
가장 낮은 eval_loss를 낸 조합이 이후 학습·배포 후보가 된다.

9.3.3 EXAONE 7.8B LoRA 학습 (Training Job)

  • 학습 스크립트: sagemaker_src/train.py
  • 베이스 모델: LGAI-EXAONE/EXAONE-3.0-7.8B-Instruct
  • 주요 설정:
    • 4-bit NF4 양자화 + torch.bfloat16 계산 (BitsAndBytesConfig)
    • LoRA(r=8, α=16, target_modules=["q_proj","k_proj","v_proj","o_proj"], dropout=0.05)
    • num_train_epochs=3, per_device_train_batch_size=1, gradient_accumulation_steps=8
    • optim="paged_adamw_8bit", gradient_checkpointing_enable()
    • eval_strategy="steps", eval_steps=50, save_steps=50
  • 입력 채널:
    • train: s3://devths-storage-dev/processed-data/train/
    • val: s3://devths-storage-dev/processed-data/val/
  • 출력:
    • SM_MODEL_DIR 에 LoRA 어댑터 가중치 + 토크나이저 저장
    • s3://devths-storage-dev/models/exaone-tuned/ 경로로 모델 아티팩트 업로드

Airflow DAG(KTB-Final)에서는 data(Processing) → AMT(Tuning) → train(Training) 순으로
위 세 단계를 하나의 엔드투엔드 SageMaker 파이프라인으로 실행할 수 있도록 정의하였다.

9.3.4 SageMaker 학습 결과 평가 (Langfuse + Gemini)

  • sagemaker_src/evaluate.py 에서 Langfuse Observations API로 특정 run_id 태그의 Generation 로그를 조회
  • gemini-2.5-flash 를 Judge로 사용해 1~100점 단일 스코어를 산출
    • 여러 개의 GEMINI_API_KEYS 를 라운드로빈 + backoff 로 사용해 429/RESOURCE_EXHAUSTED 에러 대응
  • 채점 결과는 Langfuse scores 엔드포인트에 gemini_eval_score 이름으로 기록
  • 평균 점수가 threshold(기본 85점) 미만이면 Exception을 발생시켜 해당 학습 결과를 배포 후보에서 제외

이를 통해 데이터 전처리 → 하이퍼파라미터 튜닝 → LoRA 학습 → Langfuse 기반 자동 평가 까지가
SageMaker + Langfuse 조합으로 하나의 실험 파이프라인으로 구현되었다.

9.4 파인튜닝 전후 비교 평가

[평가 파이프라인]

테스트셋 (20개 Q&A)
        ↓
┌───────────────────────────────────────┐
│  EXAONE 7.8B 기본 모델                │
│  → 응답 생성 → Judge 채점 → 점수A    │
└───────────────────────────────────────┘
        ↓
┌───────────────────────────────────────┐
│  EXAONE 7.8B LoRA 파인튜닝 모델       │
│  → 응답 생성 → Judge 채점 → 점수B    │
└───────────────────────────────────────┘
        ↓
[점수A vs 점수B 비교]
  - relevance: +0.3 향상
  - accuracy: +0.5 향상
  - faithfulness: +0.2 향상
  → 파인튜닝 효과 정량화

10. 전체 파이프라인 아키텍처

10.1 현재 구현 상태

┌──────────────────────────────────────────────────────────────────┐
│                    Devths AI 평가 파이프라인                       │
├──────────────────────────────────────────────────────────────────┤
│                                                                  │
│  [사용자 요청]                                                    │
│       ↓                                                          │
│  [FastAPI Endpoint]                                              │
│       ↓                                                          │
│  [RAG Pipeline] ─────────────────────────────────────────────┐   │
│       │                                                      │   │
│       ├─→ [ChromaDB] ─→ 컨텍스트 검색                        │   │
│       │                                                      │   │
│       ├─→ [LangChain/LCEL] ─→ 프롬프트 구성                  │   │
│       │                                                      │   │
│       └─→ [LLM (Gemini/EXAONE)] ─→ 응답 생성                 │   │
│                                                              │   │
│  [Langfuse @observe] ←───────────────────────────────────────┘   │
│       │                                                          │
│       ├─→ Trace 자동 생성                                        │
│       ├─→ 토큰 사용량 기록                                       │
│       └─→ 레이턴시 측정                                          │
│                                                                  │
│  [JudgeService] ←── 비동기 채점 (샘플링 10~20%)                  │
│       │                                                          │
│       ├─→ relevance / accuracy / fluency / completeness         │
│       ├─→ faithfulness (환각 탐지)                               │
│       └─→ context_relevance (리트리버 품질)                      │
│                                                                  │
│  [Langfuse Score] ←── 점수 기록                                  │
│       │                                                          │
│       └─→ 대시보드 시계열 그래프                                 │
│                                                                  │
└──────────────────────────────────────────────────────────────────┘

10.2 구현 현황

단계 기능 상태
Phase 1 Gemini API 직접 호출 ✅ 완료
Phase 2 프롬프트 템플릿 분리 ✅ 완료
Phase 3 LangChain/LCEL 도입 ✅ 완료
Phase 4 Langfuse 모니터링 ✅ 완료
Phase 5 LLM-as-Judge 4기준 ✅ 완료
Phase 6 Faithfulness/Context Relevance ✅ 완료
Phase 7 Multi-Judge 합의 ✅ 완료
Phase 8 EXAONE 파인튜닝 평가 ✅ 완료

11. 성과 및 배운 점

11.1 정량적 성과

지표 개선 전 개선 후 변화
품질 측정 가능 여부 ❌ 불가 ✅ 6기준 자동 채점 -
평가 자동화율 0% 80% (수동 샘플링 20%) +80%p
환각 탐지율 0% ~70% (Faithfulness) +70%p
모델 비교 시간 수 시간 (수동) 10분 (자동) -95%
프로덕션 모니터링 ❌ 없음 ✅ 실시간 대시보드 -

11.2 기술적 배운 점

  1. LLM-as-Judge의 한계 인식

    • 단일 Judge는 모델 고유 편향 누적
    • Multi-Judge 합의로 신뢰도 향상 필요
    • Human evaluation과 병행 필수
  2. RAG 평가의 분리 필요성

    • 최종 응답만 평가하면 원인 파악 불가
    • Context Relevance로 리트리버 vs LLM 분리 평가
    • Faithfulness로 환각 탐지
  3. 비용 vs 품질 트레이드오프

    • 전수 평가는 비용 부담
    • 샘플링 10~20%로 비용 최적화
    • Heuristic Scorer로 LLM 호출 최소화