Building Production-Grade LLM Evaluation Pipelines: From Vibes to Metrics

Building Production-Grade LLM Evaluation Pipelines: From Vibes to Metrics

构建生产级 LLM 评估流水线:从“感觉良好”到量化指标

Building Production-Grade LLM Evaluation Pipelines: From Vibes to Metrics. How we replaced “looks good to me” with automated evaluation catching 92% of hallucinations before deployment. 构建生产级 LLM 评估流水线:从“感觉良好”到量化指标。我们如何用自动化评估取代“看起来不错”,并在部署前拦截了 92% 的幻觉。

The Problem: Why “Vibe Checks” Fail in Production

问题所在:为什么“感觉检查”在生产环境中会失效

Three months ago, our team shipped a RAG-based customer support assistant. It worked great in testing — we’d ask it questions, read the answers, and say “yeah, that looks right.” Then it hit production. A customer asked about their billing cycle. The assistant confidently cited a policy that didn’t exist. Another asked about API rate limits and got numbers from a competitor’s documentation. By the time we caught it, 500+ users had seen hallucinated responses. The post-mortem was brutal: we had zero automated evaluation. Our test process was literally “ask 5 questions, read answers, thumbs up.” 三个月前,我们的团队发布了一个基于 RAG(检索增强生成)的客户支持助手。它在测试阶段表现良好——我们问它问题,阅读答案,然后说“嗯,看起来没问题”。随后它上线了。一位客户询问账单周期,助手自信地引用了一条根本不存在的政策。另一位客户询问 API 速率限制,结果得到了竞争对手文档中的数据。当我们发现时,已有超过 500 名用户看到了幻觉回复。事后分析非常惨痛:我们没有任何自动化评估。我们的测试流程实际上就是“问 5 个问题,读答案,点个赞”。

What Production Evaluation Actually Needs

生产环境评估真正需要什么

Academic benchmarks (MMLU, HellaSwag) don’t tell you if your system works for your use case. Production evaluation needs: 学术基准测试(如 MMLU, HellaSwag)无法告诉你系统是否适用于你的具体场景。生产环境评估需要:

  • Domain-specific judges — Your criteria, not generic “helpfulness”
  • 领域特定的评判器 — 使用你的标准,而非通用的“有用性”
  • Speed — Evaluation must run in CI/CD, not overnight
  • 速度 — 评估必须在 CI/CD 中运行,而不是耗费整晚
  • Regression detection — Know immediately when a prompt change breaks things
  • 回归检测 — 当提示词变更导致系统损坏时,能立即知晓
  • CI/CD integration — Block merges that degrade quality
  • CI/CD 集成 — 拦截会导致质量下降的代码合并
  • Golden dataset management — Versioned, stratified, growing test cases
  • 黄金数据集管理 — 版本化、分层且不断增长的测试用例

Architecture: The Evaluation Pipeline

架构:评估流水线

┌─────────────┐   ┌──────────────┐   ┌────────────────────┐   ┌──────────────┐
│ Test Cases  │──▶│ LLM Under    │──▶│ Judge Ensemble     │──▶│ Metrics &    │
│ (Golden Set)│   │ Test         │   │ - Faithfulness     │   │ Regression   │
└─────────────┘   └──────────────┘   │ - Instruction F.   │   │ Detection    │
                                     │ - JSON Schema      │   └──────┬───────┘
                                     │ - Custom LLM       │          ▼
                                     └────────────────────┘   ┌──────────────┐
                                                              │ Dashboard/   │
                                                              │ PR Comments  │
                                                              └──────────────┘

Core Abstractions

核心抽象

# eval/base.py
@dataclass(frozen=True)
class TestCase:
    id: str
    input: dict[str, Any]
    expected: dict[str, Any] | None = None
    tags: list[str] = field(default_factory=list) # ["edge-case", "long-context"]

@dataclass(frozen=True)
class EvaluationResult:
    test_case_id: str
    judge_name: str
    score: float
    passed: bool
    reasoning: str

class Judge(ABC):
    @abstractmethod
    async def evaluate(self, test_case: TestCase, response: Any) -> EvaluationResult: ...

class EvaluationHarness:
    def __init__(self, judges: list[Judge]):
        self.judges = judges
    async def evaluate_all(self, test_cases, generate_fn, concurrency=10):
        # Runs all cases through all judges with controlled concurrency ...

The Judge Ensemble: Beyond RAGAS

评判器集成:超越 RAGAS

RAGAS gives you faithfulness and answer relevance. But production needs more: RAGAS 可以提供忠实度和答案相关性,但生产环境需要更多:

JudgePurposeTypeThreshold
评判器目的类型阈值
FaithfulnessAnswer contradicts retrieved context?LLM0.8
忠实度答案是否与检索到的上下文矛盾?LLM0.8
Instruction FollowingAll prompt constraints satisfied?LLM0.9
指令遵循是否满足所有提示词约束?LLM0.9
JSON SchemaValid structured output?Deterministic1.0
JSON 模式是否为有效的结构化输出?确定性1.0
SafetyPII, harmful content, policy violationsLLM1.0
安全性PII、有害内容、政策违规LLM1.0
Domain ExpertMedical/legal/financial accuracyLLM (few-shot)0.85
领域专家医疗/法律/金融准确性LLM (少样本)0.85

Custom LLM Judge with Few-Shot

带少样本(Few-Shot)的自定义 LLM 评判器

# eval/judges.py
class LLMJudge(Judge):
    def __init__(self, name, criteria, model="gpt-4o-mini", few_shot_examples=None):
        self.name = name
        self.criteria = criteria
        self.model = model
        self.few_shot = few_shot_examples or []

    async def evaluate(self, test_case, response):
        client = instructor.from_openai(AsyncOpenAI())
        class Output(BaseModel):
            score: float = Field(ge=0, le=1)
            reasoning: str
            passed: bool
        
        result = await client.chat.completions.create(
            model=self.model,
            response_model=Output,
            messages=[
                {"role": "system", "content": self._system_prompt()},
                *self._few_shot_messages(),
                {"role": "user", "content": self._build_prompt(test_case, response)},
            ],
            temperature=0.0,
        )
        return EvaluationResult(...)

Faithfulness Judge (Production-Ready)

忠实度评判器(生产就绪)

def create_faithfulness_judge() -> LLMJudge:
    return LLMJudge(
        name="faithfulness",
        criteria="""
        Evaluate whether the ANSWER is faithful to the CONTEXT.
        - Score 1.0: All claims in answer are directly supported by context
        - Score 0.5: Some claims unsupported but not contradictory
        - Score 0.0: Answer contains claims directly contradicted by context
        """,
        threshold=0.8,
        few_shot_examples=[
            {
                "input": {"context": "Company founded in 2019. Revenue $10M in 2023.", "answer": "The company was founded in 2019 and reached $10M revenue in 2023."},
                "output": {"score": 1.0, "reasoning": "All claims supported", "passed": True}
            },
            {
                "input": {"context": "Product launched in January 2024.", "answer": "The product launched in March 2024 after extensive beta testing."},
                "output": {"score": 0.0, "reasoning": "Contradicts launch date", "passed": False}
            },
        ]
    )

Golden Dataset Strategy

黄金数据集策略

Don’t start with 1000 cases. Start with 50 real production cases. 不要一上来就搞 1000 个用例,先从 50 个真实的生产用例开始。

# eval/golden_set.jsonl
{"id": "support-001", "input": {"question": "How do I reset my password?", "context": "..."}, "expected": {"answer": "Use the 'Forgot Password' link..."}, "tags": ["basic", "auth"]}
{"id": "support-042", "input": {"question": "Why was I charged twice?", "context": "..."}, "expected": null, "tags": ["billing", "edge-case"]}

Stratification matters: 分层很重要:

  • 40% basic/happy-path (基础/正常路径)
  • 30% edge cases (ambiguous, multi-step) (边缘情况:歧义、多步骤)
  • 20% adversarial (injection, off-topic) (对抗性:注入、离题)
  • 10% multilingual/long-context (多语言/长上下文)

Version your dataset: Git-track it. Every production failure becomes a new test case. 版本化你的数据集:使用 Git 跟踪。每一个生产环境的故障都应成为一个新的测试用例。

Regression Detection That Works

有效的回归检测

def regression_report(self, baseline: dict[str, float]) -> dict[str, Any]:
    current = self.summary()
    report = {}
    for judge_name, metrics in current.items():
        if judge_name not in baseline: continue
        baseline_mean = baseline[judge_name]
        current_mean = metrics["mean_score"]
        diff = current_mean - baseline_mean
        
        report[judge_name] = {
            "baseline": baseline_mean,
            "current": current_mean,
            "delta": diff,
            "regressed": diff < -0.05, # 5% drop = regression
            "improved": diff > 0.02,
        }
    return report

CI/CD Integration: GitHub Actions

CI/CD 集成:GitHub Actions

# .github/workflows/llm-eval.yml
name: LLM Evaluation
on:
  pull_request:
    paths: ['prompts/**', 'eval/**']
  schedule: ['0 2 * * *'] # Nightly
jobs:
  evaluate:
    runs-on: ubuntu-latest
    steps:
      ...