Bạn vừa ship một tính năng AI. Không có lỗi build, không có exception, staging chạy mượt. Hai tuần sau, support ticket bắt đầu nhảy vào: “AI trả lời sai”, “câu trả lời không còn đúng như trước”. Bạn diff code — không có thay đổi gì liên quan. Hóa ra model provider vừa cập nhật model version, hoặc một thay đổi nhỏ trong system prompt từ sprint trước đã âm thầm làm giảm chất lượng response.

Đây là AI regression. Nó vô hình với test thông thường vì không có gì “crash” — chỉ là output kém hơn một chút, đủ để user nhận ra nhưng không đủ để alert fire.

Vấn Đề: AI Regression Thường Vô Hình

Khi tôi dẫn team xây tính năng summarization cho một nền tảng học tập, chúng tôi có full test coverage, có integration test, có staging environment. Nhưng ba tuần sau khi deploy, người dùng phản hồi rằng tóm tắt trở nên “lan man hơn” và “ít trọng tâm hơn”. Không ai trong team nhận ra vì không ai đọc từng response một — chúng tôi chỉ test xem API có trả về 200 không.

Vấn đề cốt lõi: unit test kiểm tra hành vi deterministic, còn AI output thì không deterministic và không binary. Không thể assert(response === expectedString). Cần một cơ chế đánh giá khác — đó là eval pipeline.

Eval Pipeline Là Gì?

Eval pipeline không phải là unit test viết lại cho AI. Đây là một hệ thống đo lường chất lượng AI output theo chu kỳ có cấu trúc:

  1. Xác định tiêu chí chất lượng — correctness, relevance, format, safety, tone
  2. Xây dựng eval dataset — tập hợp input + expected behavior
  3. Chạy eval tự động — so sánh output với tiêu chí
  4. Phân tích kết quả — track score theo thời gian
  5. Cải thiện — từ insight, điều chỉnh prompt hoặc pipeline

Điểm khác biệt với test thông thường: eval không assert exact match. Nó đo mức độ tốt theo các tiêu chí định nghĩa trước.

Framework 3 Tầng

Sau nhiều lần thử sai, tôi dùng kiến trúc 3 tầng cho hầu hết dự án AI production.

Tầng 1 — Deterministic Checks

Kiểm tra những thứ có thể assert chính xác: format JSON hợp lệ, độ dài response nằm trong ngưỡng, không có PII leak, response không rỗng, không chứa forbidden string. Tầng này chạy nhanh, chi phí thấp, fail rõ ràng.

def tier1_checks(response: str, expected_format: dict) -> dict:
    results = {}

    # Format check
    try:
        parsed = json.loads(response)
        results["valid_json"] = True
    except json.JSONDecodeError:
        results["valid_json"] = False
        return results  # Fail fast

    # Length check
    word_count = len(response.split())
    results["length_ok"] = 100 <= word_count <= 800

    # Required fields
    required_keys = expected_format.get("required_keys", [])
    results["has_required_fields"] = all(k in parsed for k in required_keys)

    # No forbidden content
    forbidden = ["[ERROR]", "I cannot", "As an AI language model"]
    results["no_forbidden"] = not any(f in response for f in forbidden)

    results["passed"] = all(results.values())
    return results

Tầng 2 — Model-as-Judge

Đây là nơi hầu hết team bỏ qua. Thay vì tự viết heuristic phức tạp để đánh giá relevance hay correctness, hãy dùng một LLM khác làm judge. Pattern này hoạt động tốt vì LLM hiểu ngữ cảnh theo cách regex không làm được.

async def tier2_judge(
    user_input: str,
    ai_response: str,
    criteria: list[str],
    judge_model: str = "gpt-4o-mini"
) -> dict:
    criteria_str = "\n".join(f"- {c}" for c in criteria)

    judge_prompt = f"""Đánh giá AI response sau theo từng tiêu chí. 
Trả về JSON với score 1-5 cho mỗi tiêu chí và reasoning ngắn.

USER INPUT: {user_input}

AI RESPONSE: {ai_response}

TIÊU CHÍ:
{criteria_str}

Format: {{"scores": {{"<criteria>": {{"score": int, "reason": str}}}}, "overall": int}}"""

    response = await llm_client.complete(judge_prompt, model=judge_model)
    result = json.loads(response)

    # Aggregate
    scores = [v["score"] for v in result["scores"].values()]
    result["avg_score"] = sum(scores) / len(scores)
    result["passed"] = result["avg_score"] >= 3.5

    return result

Lưu ý quan trọng: dùng model khác để judge (không phải model đang được eval) để tránh bias. Nếu production dùng Claude, judge bằng GPT-4o-mini — hoặc ngược lại.

Tầng 3 — Human Review

Không thể automate 100%. Tầng 3 dành cho edge cases, safety evaluation, và những trường hợp tinh tế mà model-as-judge không đủ tin cậy. Thiết lập một queue nhỏ — khoảng 5-10% sample production traffic — để human review định kỳ. Đây cũng là nguồn cung cấp adversarial cases mới cho dataset.

Xây Dựng Eval Dataset

Bắt đầu nhỏ: 50 cases là đủ để bắt đầu, không cần nghìn cases ngay từ đầu.

Chia dataset thành 3 loại:

  • Golden examples (60%): input điển hình + expected output rõ ràng. Lấy từ production logs khi AI đang làm tốt.
  • Adversarial cases (25%): input edge case, ambiguous, hoặc tricky — những thứ bạn biết AI hay fail.
  • Regression cases (15%): mỗi khi bắt được một bug mới, thêm nó vào đây. Dataset tự lớn lên theo thời gian.

Quan trọng: gắn tag cho mỗi case theo category (format, reasoning, safety, domain-specific). Khi eval score giảm, bạn biết giảm ở dimension nào.

Tích Hợp Evals Vào CI/CD

Tier 1 và Tier 2 phải chạy tự động trong mỗi PR. Đây là GitHub Actions workflow cơ bản:

name: AI Eval Pipeline

on:
  pull_request:
    paths:
      - 'src/prompts/**'
      - 'src/ai/**'

jobs:
  eval:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - name: Run Tier 1 Checks
        run: python evals/run_tier1.py --dataset evals/dataset.json
        id: tier1

      - name: Run Tier 2 Judge Eval
        run: python evals/run_tier2.py --dataset evals/dataset.json
        id: tier2
        env:
          OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}

      - name: Check Score Threshold
        run: |
          SCORE=$(cat evals/results/tier2_score.txt)
          BASELINE=$(cat evals/baseline_score.txt)
          python -c "
          score, baseline = $SCORE, $BASELINE
          if score < baseline - 0.15:
              print(f'FAIL: Score {score:.2f} dropped more than 15% from baseline {baseline:.2f}')
              exit(1)
          print(f'PASS: Score {score:.2f} (baseline: {baseline:.2f})')
          "

Ngưỡng quan trọng: đừng hard-code absolute score (ví dụ “phải >= 4.0”). Thay vào đó, so sánh với baseline của main branch. Nếu score giảm hơn 10-15% so với baseline thì block merge. Cách này linh hoạt hơn và ít false positive hơn.

Bài Học Từ Production

1. Prompt change nhỏ gây regression lớn. Thêm một câu vào system prompt, đổi tone từ “formal” sang “friendly” — những thứ tưởng vô hại này thường là nguyên nhân số một. Eval pipeline bắt được điều này ngay lập tức.

2. Model-as-judge cũng có bias. Judge model có xu hướng đánh giá cao những response dài, formal, dùng nhiều bullet point — không hẳn là response tốt hơn. Calibrate judge bằng cách so sánh kết quả với human review định kỳ.

3. “Eval dead zone” là thật. Nếu eval dataset không được cập nhật, nó sẽ lỗi thời. AI học cách “pass” dataset cũ mà không thực sự tốt hơn. Đặt lịch review dataset mỗi sprint, thêm cases từ production bugs mới nhất.

4. Chi phí Tier 2 cần kiểm soát. Chạy model-as-judge trên 200 cases mỗi PR tốn tiền thật. Giải pháp: chỉ chạy full Tier 2 khi Tier 1 pass, và giới hạn dataset CI/CD ở 30-50 cases critical nhất. Full dataset chạy hàng đêm.

Bắt Đầu Ngay Ngày Mai

Đừng thiết kế hệ thống hoàn hảo trước. Làm theo thứ tự này:

Bước 1: Lấy 20 production inputs mà AI đang làm tốt. Đây là golden dataset đầu tiên. Viết Tier 1 checks — format, độ dài, required fields. Chạy thủ công. Mất khoảng nửa ngày.

Bước 2: Thêm model-as-judge với 3 tiêu chí đơn giản nhất cho use case của bạn (relevance, accuracy, tone). Record baseline score. Mất thêm một ngày.

Bước 3: Kết nối vào CI/CD. Chỉ cần block PR nếu Tier 1 fail hoặc Tier 2 giảm hơn 20% so với baseline. Đừng đặt threshold quá nghiêm ngay từ đầu.

Bỏ qua trước: human review queue, dashboard, alerting, dataset versioning. Những thứ này quan trọng nhưng không cần ngay. Có eval pipeline cơ bản chạy trong CI/CD còn hơn có kế hoạch eval pipeline hoàn hảo nằm trong Confluence.

AI regression không phải câu hỏi “có xảy ra không” mà là “bao giờ xảy ra”. Eval pipeline là hệ thống sớm cảnh báo duy nhất thực sự hiệu quả — và nó bắt đầu đơn giản hơn bạn nghĩ.

Xuất nội dung

Bình luận