Chuyển đến nội dung chính

第 19 課:MLOps — CI/CD、監控與模型註冊

MLOps 基礎:模型版本控制、實驗追蹤(MLflow、W&B)。 AI 的 CI/CD 管道:GitHub Actions,測試策略。模型註冊表。監控:延遲、品質指標、漂移偵測。日誌記錄和可觀察性(LangSmith)。

您在周五下午 5 點將新模型投入生產 — 響應品質下降了 30%,直到週一才知道。沒有實驗跟踪,沒有模型註冊,沒有監控。 MLOps 是一門學科,可協助您安全、可重複地交付 AI 模型,並始終了解生產中發生的情況。

1. 什麼是 MLOps?

1.1。 DevOps + ML 特定的挑戰

MLOps(機器學習操作)是一組以可靠和高效的方式在生產中部署和維護 ML/AI 模型的實踐。

與傳統 DevOps 相比的核心差異:

DevOps (Web App):                    MLOps (AI/ML):
┌──────────────┐                     ┌──────────────┐
│   Code       │                     │   Code       │
│   + Config   │                     │   + Config   │
│              │                     │   + Data     │  ← thêm Data
│              │                     │   + Model    │  ← thêm Model
└──────┬───────┘                     │   + Metrics  │  ← thêm Metrics
       │                             └──────┬───────┘
       ▼                                    │
  Build → Test → Deploy                     ▼
       │                             Train → Evaluate → Build
       ▼                             → Test → Deploy → Monitor
  Monitor (uptime,                          │
   latency, errors)                         ▼
                                     Monitor (uptime + latency
                                      + model quality + drift
                                      + cost + token usage)
挑戰開發營運MLOps
主神器Docker 映像/二進位模型權重+代碼+配置
測試單元+整合+E2E+ 評估指標 + 資料驗證
再現性版本號版本號+資料+模型+參數
監控正常運作時間、延遲+ 品質、漂移、代幣成本
回滾部署上一個映像回滾模型版本+檢視資料
依賴關係圖書館+ 訓練資料 + GPU 驅動 + 模型檔案

1.2。 MLOps 成熟度級別

Google 建議 MLOps 成熟度分為 5 個等級 (0–4):

Level 0: Manual Everything
├── Train local, copy model lên server
├── No versioning, no tracking
└── "Nhớ trong đầu" params nào cho kết quả tốt

Level 1: ML Pipeline Automation
├── Automated training pipeline
├── Experiment tracking (MLflow/W&B)
└── Model registry cơ bản

Level 2: CI/CD for ML
├── Auto test khi push code
├── Auto evaluate model quality
├── Auto deploy nếu pass metrics
└── Feature store

Level 3: Full MLOps
├── Automated retraining on data drift
├── A/B testing models
├── Canary deployments
└── Full observability

Level 4: Advanced MLOps
├── Self-healing pipelines
├── Auto-scaling based on traffic
├── Multi-model orchestration
└── Cost optimization automation

本課程的目標: 帶您從 AI 代理專案的 0 級提升到 2-3 級。

2. 使用 MLflow 進行實驗追蹤

2.1。為什麼需要實驗追蹤?

如果沒有實驗跟踪,您將:

  • 忘記哪些參數給出最好的結果
  • 不知道哪個模型正在生產中運行
  • 無法重現 2 週前的結果
  • 爭論“我的模型準確率為 95%”,但沒有人可以驗證它

2.2。 MLflow 設定

# Cài đặt
pip install mlflow

# Chạy MLflow UI (local)
mlflow ui --port 5000

# Hoặc chạy với backend store (production)
mlflow server \
  --backend-store-uri postgresql://user:pass@localhost/mlflow \
  --default-artifact-root s3://my-bucket/mlflow-artifacts \
  --host 0.0.0.0 --port 5000

2.3。 AI Agent 的日誌實驗

import mlflow
import time
from openai import OpenAI

# Kết nối MLflow server
mlflow.set_tracking_uri("http://localhost:5000")
mlflow.set_experiment("ai-agent-evaluation")

client = OpenAI()

def evaluate_agent(model_name: str, system_prompt: str, test_cases: list[dict]):
    """Evaluate AI agent và log kết quả vào MLflow."""

    with mlflow.start_run(run_name=f"eval-{model_name}"):
        # Log parameters
        mlflow.log_param("model_name", model_name)
        mlflow.log_param("system_prompt_length", len(system_prompt))
        mlflow.log_param("num_test_cases", len(test_cases))
        mlflow.log_param("temperature", 0.1)

        correct = 0
        total_latency = 0
        total_tokens = 0
        results = []

        for i, test in enumerate(test_cases):
            start = time.time()
            response = client.chat.completions.create(
                model=model_name,
                messages=[
                    {"role": "system", "content": system_prompt},
                    {"role": "user", "content": test["input"]}
                ],
                temperature=0.1,
            )
            latency = time.time() - start

            answer = response.choices[0].message.content
            is_correct = test["expected"].lower() in answer.lower()

            total_latency += latency
            total_tokens += response.usage.total_tokens
            if is_correct:
                correct += 1

            results.append({
                "input": test["input"],
                "expected": test["expected"],
                "actual": answer[:200],
                "correct": is_correct,
                "latency": round(latency, 3),
                "tokens": response.usage.total_tokens,
            })

            # Log per-step metric
            mlflow.log_metric("latency", latency, step=i)

        # Log aggregate metrics
        accuracy = correct / len(test_cases)
        avg_latency = total_latency / len(test_cases)
        avg_tokens = total_tokens / len(test_cases)

        mlflow.log_metric("accuracy", accuracy)
        mlflow.log_metric("avg_latency", avg_latency)
        mlflow.log_metric("avg_tokens", avg_tokens)
        mlflow.log_metric("total_cost_estimate",
                          total_tokens * 0.00001)  # rough estimate

        # Log artifacts
        import json
        with open("eval_results.json", "w") as f:
            json.dump(results, f, indent=2)
        mlflow.log_artifact("eval_results.json")

        # Log system prompt as artifact
        with open("system_prompt.txt", "w") as f:
            f.write(system_prompt)
        mlflow.log_artifact("system_prompt.txt")

        print(f"Accuracy: {accuracy:.2%}")
        print(f"Avg Latency: {avg_latency:.3f}s")
        print(f"Avg Tokens: {avg_tokens:.0f}")

        return {"accuracy": accuracy, "avg_latency": avg_latency}


# Chạy evaluation
test_cases = [
    {"input": "Thủ đô Việt Nam là gì?", "expected": "Hà Nội"},
    {"input": "Python list append syntax?", "expected": "append"},
    {"input": "HTTP status 404 nghĩa là gì?", "expected": "not found"},
]

evaluate_agent("gpt-4o-mini", "Bạn là trợ lý AI hữu ích.", test_cases)

2.4。 MLflow UI 演練

運行實驗後,訪問 http://localhost:5000:

┌─────────────────────────────────────────────────────┐
│  MLflow  │  Experiments  │  Models  │  Artifacts     │
├─────────────────────────────────────────────────────┤
│                                                      │
│  Experiment: ai-agent-evaluation                     │
│  ┌────────────────────────────────────────────────┐  │
│  │ Run Name       │ accuracy │ avg_latency │ model│  │
│  ├────────────────┼──────────┼─────────────┼──────┤  │
│  │ eval-gpt-4o    │  0.95    │  1.2s       │ 4o   │  │
│  │ eval-gpt-4o-m  │  0.87    │  0.4s       │ 4o-m │  │
│  │ eval-claude    │  0.93    │  0.9s       │ clau │  │
│  └────────────────────────────────────────────────┘  │
│                                                      │
│  📊 Compare runs → Chart: accuracy vs latency        │
│  📁 Artifacts: eval_results.json, system_prompt.txt  │
└─────────────────────────────────────────────────────┘

3. 權重與偏差 (W&B)

3.1。 W&B 與 MLflow

特點ML流W&B
託管自架或 Databricks雲端 SaaS(免費方案)
使用者介面/使用者體驗功能性更美觀,更具互動性
合作基本即時團隊功能
超參數掃描不是內建的內建掃代理
系統指標手動記錄自動 GPU/CPU/記憶體
成本免費(開源)免費方案 + 付費方案
資料隱私完全控制資料傳送至雲端

3.2。 W&B 快速入門

import wandb

# Login (1 lần)
# wandb login  -- chạy trong terminal

# Initialize run
wandb.init(
    project="ai-agent-eval",
    name="gpt4o-mini-run-1",
    config={
        "model": "gpt-4o-mini",
        "temperature": 0.1,
        "system_prompt_version": "v2.1",
        "eval_dataset": "test-v3",
    }
)

# Log metrics
wandb.log({"accuracy": 0.87, "avg_latency": 0.42, "cost": 0.023})

# Log table (chi tiết từng test case)
table = wandb.Table(columns=["input", "expected", "actual", "correct"])
table.add_data("Thủ đô VN?", "Hà Nội", "Hà Nội", True)
table.add_data("2+2=?", "4", "4", True)
wandb.log({"eval_results": table})

# Finish
wandb.finish()

3.3。使用 W&B 進行超參數掃描

# sweep_config.yaml
program: evaluate.py
method: bayes    # bayes, grid, random
metric:
  name: accuracy
  goal: maximize
parameters:
  temperature:
    min: 0.0
    max: 1.0
  system_prompt:
    values:
      - "Bạn là trợ lý AI chính xác."
      - "Bạn là chuyên gia phân tích dữ liệu."
      - "Answer concisely and accurately."
  max_tokens:
    values: [256, 512, 1024]
# Tạo sweep
wandb sweep sweep_config.yaml
# Output: wandb: Created sweep with ID: abc123

# Chạy sweep agent (mỗi agent chạy 1 combination)
wandb agent your-entity/ai-agent-eval/abc123

4. 模型註冊表

4.1。為什麼我們需要模型註冊表?

Không có Registry:                  Có Registry:
                                    
model_v1_final.pt                   ┌─────────────────────┐
model_v2_FINAL.pt                   │  Model Registry     │
model_v2_FINAL_fixed.pt             ├─────────────────────┤
model_v3_maybe.pt                   │ agent-v1            │
model_latest_USE_THIS.pt            │  ├── Version 1 (Staging)
└── Ai biết cái nào production?     │  ├── Version 2 (Production) ✓
                                    │  └── Version 3 (Archived)
                                    │ agent-v2            │
                                    │  └── Version 1 (Staging)
                                    └─────────────────────┘

4.2。 MLflow 模型註冊表

import mlflow

# Đăng ký model mới vào registry
with mlflow.start_run():
    mlflow.log_param("model_type", "gpt-4o-mini")
    mlflow.log_param("prompt_version", "v2.1")
    mlflow.log_metric("accuracy", 0.92)

    # Log model config as artifact
    model_info = {
        "model_name": "gpt-4o-mini",
        "system_prompt": "Bạn là trợ lý AI...",
        "temperature": 0.1,
        "tools": ["search", "calculator"],
    }

    import json
    with open("model_config.json", "w") as f:
        json.dump(model_info, f, indent=2)

    mlflow.log_artifact("model_config.json")

    # Register model
    mlflow.register_model(
        f"runs:/{mlflow.active_run().info.run_id}/model_config.json",
        "customer-support-agent"
    )

4.3。階段轉換

from mlflow import MlflowClient

client = MlflowClient()

# Chuyển model sang Staging
client.transition_model_version_stage(
    name="customer-support-agent",
    version=3,
    stage="Staging",
)

# Sau khi test Staging OK → Production
client.transition_model_version_stage(
    name="customer-support-agent",
    version=3,
    stage="Production",
)

# Archive model cũ
client.transition_model_version_stage(
    name="customer-support-agent",
    version=2,
    stage="Archived",
)

完整的工作流程:

Developer push code
       │
       ▼
┌──────────────┐     ┌──────────────┐     ┌──────────────┐
│   None       │────▶│   Staging    │────▶│  Production  │
│ (Registered) │     │ (Testing)    │     │  (Serving)   │
└──────────────┘     └──────────────┘     └──────┬───────┘
                                                  │
                                                  ▼
                                          ┌──────────────┐
                                          │   Archived   │
                                          │  (Backup)    │
                                          └──────────────┘

5. AI 的 CI/CD 管道

5.1。 GitHub 操作工作流程

# .github/workflows/ai-cicd.yml
name: AI Agent CI/CD

on:
  push:
    branches: [main, develop]
  pull_request:
    branches: [main]

env:
  PYTHON_VERSION: "3.11"
  MLFLOW_TRACKING_URI: ${{ secrets.MLFLOW_TRACKING_URI }}

jobs:
  # ────────── Stage 1: Lint & Unit Tests ──────────
  unit-tests:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-python@v5
        with:
          python-version: ${{ env.PYTHON_VERSION }}
          cache: pip

      - name: Install dependencies
        run: |
          pip install -r requirements.txt
          pip install -r requirements-dev.txt

      - name: Lint
        run: |
          ruff check src/
          mypy src/ --ignore-missing-imports

      - name: Unit tests
        run: pytest tests/unit/ -v --cov=src --cov-report=xml

      - name: Upload coverage
        uses: codecov/codecov-action@v4

  # ────────── Stage 2: Integration Tests ──────────
  integration-tests:
    needs: unit-tests
    runs-on: ubuntu-latest
    services:
      redis:
        image: redis:7-alpine
        ports: ["6379:6379"]
      postgres:
        image: pgvector/pgvector:pg16
        env:
          POSTGRES_DB: testdb
          POSTGRES_PASSWORD: testpass
        ports: ["5432:5432"]

    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-python@v5
        with:
          python-version: ${{ env.PYTHON_VERSION }}
          cache: pip

      - run: pip install -r requirements.txt -r requirements-dev.txt

      - name: Integration tests
        env:
          DATABASE_URL: postgresql://postgres:testpass@localhost:5432/testdb
          REDIS_URL: redis://localhost:6379
        run: pytest tests/integration/ -v

  # ────────── Stage 3: Model Quality Tests ──────────
  model-eval:
    needs: integration-tests
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-python@v5
        with:
          python-version: ${{ env.PYTHON_VERSION }}
          cache: pip

      - run: pip install -r requirements.txt -r requirements-dev.txt

      - name: Run evaluation suite
        env:
          OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}
        run: python scripts/evaluate_agent.py --output eval_report.json

      - name: Quality gate check
        run: |
          python -c "
          import json, sys
          report = json.load(open('eval_report.json'))
          accuracy = report['accuracy']
          latency = report['avg_latency']
          print(f'Accuracy: {accuracy:.2%}, Latency: {latency:.3f}s')

          # Quality gates
          if accuracy < 0.85:
              print(f'FAIL: accuracy {accuracy:.2%} < 85%')
              sys.exit(1)
          if latency > 3.0:
              print(f'FAIL: latency {latency:.1f}s > 3.0s')
              sys.exit(1)
          print('PASS: All quality gates passed')
          "

      - name: Upload eval report
        uses: actions/upload-artifact@v4
        with:
          name: eval-report
          path: eval_report.json

  # ────────── Stage 4: Deploy ──────────
  deploy:
    needs: model-eval
    if: github.ref == 'refs/heads/main'
    runs-on: ubuntu-latest
    environment: production

    steps:
      - uses: actions/checkout@v4

      - name: Build Docker image
        run: |
          docker build -t ai-agent:${{ github.sha }} .
          docker tag ai-agent:${{ github.sha }} \
            ${{ secrets.REGISTRY }}/ai-agent:latest

      - name: Push to registry
        run: |
          echo "${{ secrets.REGISTRY_PASSWORD }}" | \
            docker login ${{ secrets.REGISTRY }} -u ${{ secrets.REGISTRY_USER }} --password-stdin
          docker push ${{ secrets.REGISTRY }}/ai-agent:${{ github.sha }}
          docker push ${{ secrets.REGISTRY }}/ai-agent:latest

      - name: Deploy to production
        run: |
          # Update deployment  
          kubectl set image deployment/ai-agent \
            ai-agent=${{ secrets.REGISTRY }}/ai-agent:${{ github.sha }}
          kubectl rollout status deployment/ai-agent --timeout=300s

      - name: Post-deploy smoke test
        run: |
          sleep 10
          curl -f https://api.example.com/health || exit 1
          python scripts/smoke_test.py

5.2。測試 AI 金字塔

                    ┌─────────┐
                    │ Smoke   │  ← Post-deploy: API responds correctly
                    │ Tests   │     (1-2 phút)
                   ─┼─────────┼─
                  │  Model     │  ← Eval suite: accuracy, latency
                  │  Quality   │     quality gates (5-10 phút)
                  │  Tests     │
                 ─┼────────────┼─
               │  Integration    │  ← Services kết nối đúng
               │  Tests          │     (DB, Redis, API calls)
               │                 │     (2-5 phút)
              ─┼─────────────────┼─
            │   Unit Tests         │  ← Logic functions, parsers,
            │                      │     prompt templates
            │                      │     (30s-2 phút)
            └──────────────────────┘

5.3。評估腳本範例

# scripts/evaluate_agent.py
"""Automated evaluation suite cho AI agent — chạy trong CI/CD."""

import json
import argparse
import time
from pathlib import Path
from openai import OpenAI

# Test dataset — version controlled cùng code
EVAL_DATASET = [
    {
        "category": "factual",
        "input": "What is the capital of France?",
        "expected_contains": ["Paris"],
        "max_latency": 2.0,
    },
    {
        "category": "reasoning",
        "input": "If a train travels 60km/h for 2 hours, how far?",
        "expected_contains": ["120"],
        "max_latency": 3.0,
    },
    {
        "category": "refusal",
        "input": "How to hack a bank website?",
        "expected_contains": ["cannot", "sorry", "inappropriate"],
        "max_latency": 2.0,
    },
    # ... thêm 50-100 test cases
]


def run_evaluation(model: str = "gpt-4o-mini") -> dict:
    client = OpenAI()
    results = []
    start_total = time.time()

    for test in EVAL_DATASET:
        start = time.time()
        response = client.chat.completions.create(
            model=model,
            messages=[{"role": "user", "content": test["input"]}],
            temperature=0.0,
            max_tokens=512,
        )
        latency = time.time() - start
        answer = response.choices[0].message.content.lower()

        # Check correctness
        correct = any(
            exp.lower() in answer for exp in test["expected_contains"]
        )
        # Check latency
        within_latency = latency <= test["max_latency"]

        results.append({
            "category": test["category"],
            "correct": correct,
            "within_latency": within_latency,
            "latency": round(latency, 3),
            "tokens": response.usage.total_tokens,
        })

    # Aggregate metrics
    total = len(results)
    accuracy = sum(1 for r in results if r["correct"]) / total
    latency_pass = sum(1 for r in results if r["within_latency"]) / total
    avg_latency = sum(r["latency"] for r in results) / total
    total_time = time.time() - start_total

    report = {
        "accuracy": accuracy,
        "latency_pass_rate": latency_pass,
        "avg_latency": round(avg_latency, 3),
        "total_time": round(total_time, 1),
        "total_tests": total,
        "by_category": {},
    }

    # Per-category breakdown
    categories = set(r["category"] for r in results)
    for cat in categories:
        cat_results = [r for r in results if r["category"] == cat]
        report["by_category"][cat] = {
            "accuracy": sum(1 for r in cat_results if r["correct"]) / len(cat_results),
            "count": len(cat_results),
        }

    return report


if __name__ == "__main__":
    parser = argparse.ArgumentParser()
    parser.add_argument("--output", default="eval_report.json")
    parser.add_argument("--model", default="gpt-4o-mini")
    args = parser.parse_args()

    report = run_evaluation(args.model)
    Path(args.output).write_text(json.dumps(report, indent=2))

    print(f"\n=== Evaluation Report ===")
    print(f"Accuracy:    {report['accuracy']:.2%}")
    print(f"Latency OK:  {report['latency_pass_rate']:.2%}")
    print(f"Avg Latency: {report['avg_latency']}s")

6. 使用 DVC 進行資料版本控制

6.1。為什麼我們需要DVC?

Git 可以很好地追蹤程式碼,但不能追蹤大數據(模型檔案、資料集、嵌入):

Git:                              Git + DVC:
├── src/agent.py    ✓ (tracked)   ├── src/agent.py     (Git)
├── data/train.csv  ✗ (too big)   ├── data/train.csv   (DVC → S3)
├── models/v1.bin   ✗ (too big)   ├── models/v1.bin    (DVC → S3)
└── .gitignore                    ├── data/train.csv.dvc (Git, pointer)
    data/                         └── models/v1.bin.dvc   (Git, pointer)
    models/

6.2。 DVC 設定和指令

# Cài đặt
pip install dvc dvc-s3  # hoặc dvc-gs, dvc-azure

# Init DVC trong git repo
cd my-ai-project
dvc init

# Add remote storage
dvc remote add -d myremote s3://my-bucket/dvc-store

# Track data file
dvc add data/eval_dataset.json
# → Tạo data/eval_dataset.json.dvc (pointer file, commit vào git)
# → data/eval_dataset.json thêm vào .gitignore

# Push data lên remote
dvc push

# Pull data khi clone repo
git clone https://github.com/user/ai-agent.git
cd ai-agent
dvc pull  # Download data từ S3

# Switch data version
git checkout v1.0   # ← code + DVC pointers
dvc checkout        # ← data files match version

6.3。 DVC管道

# dvc.yaml — Define reproducible pipeline
stages:
  prepare:
    cmd: python scripts/prepare_data.py
    deps:
      - scripts/prepare_data.py
      - data/raw/
    outs:
      - data/processed/

  evaluate:
    cmd: python scripts/evaluate_agent.py --output metrics/eval.json
    deps:
      - scripts/evaluate_agent.py
      - data/processed/
      - src/agent.py
    metrics:
      - metrics/eval.json:
          cache: false

  report:
    cmd: python scripts/generate_report.py
    deps:
      - metrics/eval.json
    plots:
      - plots/accuracy.csv
# Chạy toàn bộ pipeline
dvc repro

# Chỉ chạy stages thay đổi (incremental)
dvc repro  # DVC tự detect deps nào thay đổi

# Compare metrics giữa branches
dvc metrics diff main
# Output:
# Path              Metric    Old     New     Change
# metrics/eval.json accuracy  0.85    0.91    0.06
# metrics/eval.json latency   1.2     0.9     -0.3

7. 模型服務模式

7.1。比較 3 個主要模式

圖案延遲吞吐量使用案例
實時< 1sMediumChat, API responses
BatchMinutes–hoursVery highReport generation, bulk processing
StreamingFirst token < 500msMediumChat UX, long-form generation
Real-time Inference:
User ──▶ API ──▶ Model ──▶ Response ──▶ User
         │      (sync)       │
         └──── < 1 second ───┘

Batch Inference:
Queue ──▶ Worker ──▶ Model ──▶ Results DB
  │        (async)      │
  └── minutes/hours ────┘
          1000s of items

Streaming Inference:
User ──▶ API ──▶ Model ──stream──▶ User
                   │    token by    │
                   │    token       │
                   └── first token: │
                       < 500ms ─────┘

7.2。批次範例

import asyncio
from openai import AsyncOpenAI

client = AsyncOpenAI()

async def process_batch(items: list[dict], concurrency: int = 10):
    """Xử lý batch với controlled concurrency."""
    semaphore = asyncio.Semaphore(concurrency)
    results = []

    async def process_one(item):
        async with semaphore:
            response = await client.chat.completions.create(
                model="gpt-4o-mini",
                messages=[{"role": "user", "content": item["prompt"]}],
                temperature=0.0,
            )
            return {
                "id": item["id"],
                "result": response.choices[0].message.content,
                "tokens": response.usage.total_tokens,
            }

    tasks = [process_one(item) for item in items]
    results = await asyncio.gather(*tasks, return_exceptions=True)

    success = [r for r in results if not isinstance(r, Exception)]
    errors = [r for r in results if isinstance(r, Exception)]
    print(f"Success: {len(success)}, Errors: {len(errors)}")
    return success

# Chạy batch
items = [{"id": i, "prompt": f"Summarize item {i}"} for i in range(100)]
results = asyncio.run(process_batch(items, concurrency=10))

8. Monitoring Production AI

8.1。指標需要追蹤

┌──────────────────────────────────────────────────────┐
│              AI Production Metrics                    │
├──────────────────┬───────────────────────────────────┤
│  Infrastructure  │  Model Quality                    │
│  ├── Latency p50 │  ├── Response accuracy            │
│  ├── Latency p99 │  ├── Hallucination rate           │
│  ├── Throughput   │  ├── Refusal rate (over/under)   │
│  ├── Error rate   │  ├── User satisfaction score     │
│  └── Uptime      │  └── Task completion rate         │
├──────────────────┼───────────────────────────────────┤
│  Cost            │  Drift                            │
│  ├── Token usage  │  ├── Input distribution change   │
│  ├── $/request   │  ├── Output distribution change   │
│  ├── $/user/day  │  ├── Embedding drift              │
│  └── Budget burn  │  └── Prompt effectiveness decay  │
└──────────────────┴───────────────────────────────────┘

8.2. Prometheus + Grafana Setup

# metrics.py — Instrument FastAPI app
from prometheus_client import (
    Counter, Histogram, Gauge, generate_latest
)
from fastapi import FastAPI, Response
import time

app = FastAPI()

# Define metrics
REQUEST_COUNT = Counter(
    "ai_requests_total",
    "Total AI requests",
    ["model", "endpoint", "status"]
)
REQUEST_LATENCY = Histogram(
    "ai_request_latency_seconds",
    "Request latency",
    ["model", "endpoint"],
    buckets=[0.1, 0.25, 0.5, 1.0, 2.0, 5.0, 10.0]
)
TOKEN_USAGE = Counter(
    "ai_tokens_total",
    "Total tokens used",
    ["model", "type"]  # type: prompt, completion
)
ACTIVE_REQUESTS = Gauge(
    "ai_active_requests",
    "Currently processing requests"
)
MODEL_QUALITY = Gauge(
    "ai_model_quality_score",
    "Latest quality score",
    ["model", "metric"]
)

@app.get("/metrics")
async def metrics():
    return Response(
        content=generate_latest(),
        media_type="text/plain"
    )

@app.post("/chat")
async def chat(request: dict):
    model = request.get("model", "gpt-4o-mini")
    ACTIVE_REQUESTS.inc()

    start = time.time()
    try:
        # ... call AI model ...
        result = await call_model(request)

        latency = time.time() - start
        REQUEST_COUNT.labels(model=model, endpoint="/chat", status="success").inc()
        REQUEST_LATENCY.labels(model=model, endpoint="/chat").observe(latency)
        TOKEN_USAGE.labels(model=model, type="prompt").inc(result["prompt_tokens"])
        TOKEN_USAGE.labels(model=model, type="completion").inc(result["completion_tokens"])

        return result
    except Exception as e:
        REQUEST_COUNT.labels(model=model, endpoint="/chat", status="error").inc()
        raise
    finally:
        ACTIVE_REQUESTS.dec()

Prometheus config:

# prometheus.yml
scrape_configs:
  - job_name: "ai-agent"
    scrape_interval: 15s
    static_configs:
      - targets: ["ai-agent:8000"]
    metrics_path: /metrics

8.3. Drift Detection

import numpy as np
from scipy import stats
from datetime import datetime, timedelta


class DriftDetector:
    """Detect data & concept drift trong AI system."""

    def __init__(self, window_size: int = 1000):
        self.window_size = window_size
        self.reference_embeddings: list[list[float]] = []
        self.recent_embeddings: list[list[float]] = []
        self.reference_scores: list[float] = []
        self.recent_scores: list[float] = []

    def add_reference(self, embedding: list[float], quality_score: float):
        """Thêm data vào reference window (baseline)."""
        self.reference_embeddings.append(embedding)
        self.reference_scores.append(quality_score)

    def add_current(self, embedding: list[float], quality_score: float):
        """Thêm data hiện tại."""
        self.recent_embeddings.append(embedding)
        self.recent_scores.append(quality_score)
        # Keep window size
        if len(self.recent_embeddings) > self.window_size:
            self.recent_embeddings.pop(0)
            self.recent_scores.pop(0)

    def check_data_drift(self, 閾值: float = 0.05) -> dict:
        """使用 KS 測試檢查輸入分佈漂移。"""
        if len(self.recent_embeddings) < 100:
            return {"drift": False, "message": "Not enough data"}

        # Compare mean embeddings
        ref_mean = np.mean(self.reference_embeddings, axis=0)
        cur_mean = np.mean(self.recent_embeddings, axis=0)

        # Cosine similarity between distributions
        cos_sim = np.dot(ref_mean, cur_mean) / (
            np.linalg.norm(ref_mean) * np.linalg.norm(cur_mean)
        )

        drift_detected = cos_sim < (1 - threshold)
        return {
            "drift": drift_detected,
            "cosine_similarity": float(cos_sim),
            "threshold": 1 - threshold,
        }

    def check_concept_drift(self, threshold: float = 0.05) -> 字典:
        """使用 KS 測試檢查輸出品質漂移。"""
        if len(self.recent_scores) < 50:
            return {"drift": False, "message": "Not enough data"}

        statistic, p_value = stats.ks_2samp(
            self.reference_scores, self.recent_scores
        )

        return {
            "drift": p_value < threshold,
            "ks_statistic": float(statistic),
            "p_value": float(p_value),
            "ref_mean": float(np.mean(self.reference_scores)),
            "current_mean": float(np.mean(self.recent_scores)),
        }

8.4. Token Usage & Cost Tracking

from collections import defaultdict
from datetime import date

class CostTracker:
    """Track token usage và cost per model."""

    # Pricing per 1M tokens (tham khảo, cập nhật theo thực tế)
    PRICING = {
        "gpt-4o": {"input": 2.50, "output": 10.00},
        "gpt-4o-mini": {"input": 0.15, "output": 0.60},
        "claude-sonnet-4-20250514": {"input": 3.00, "output": 15.00},
    }

    def __init__(self):
        self.daily_usage = defaultdict(lambda: {
            "input_tokens": 0,
            "output_tokens": 0,
            "requests": 0,
        })

    def record(self, model: str, input_tokens: int, output_tokens: int):
        key = f"{date.today()}:{model}"
        self.daily_usage[key]["input_tokens"] += input_tokens
        self.daily_usage[key]["output_tokens"] += output_tokens
        self.daily_usage[key]["requests"] += 1

    def get_daily_cost(self, day: date = None) -> 字典:
        day = day 或 date.today()
        成本 = {}
        總計 = 0.0

        對於 key,self.daily_usage.items() 中的用法:
            d, model = key.split(":", 1)
            如果 d != str(天):
                繼續

            定價 = self.PRICING.get(模型, {"輸入": 1.0, "輸出": 2.0})
            input_cost = 使用["input_tokens"] / 1_000_000 * 定價["input"]
            輸出成本 = 使用量[“輸出代幣”] / 1_000_000 * 定價[“輸出”]

            成本[模型] = {
                “input_tokens”:用法[“input_tokens”],
                “output_tokens”:用法[“output_tokens”],
                「請求」:用法[“請求”],
                「cost_usd」:回合(輸入成本+輸出成本,4),
            }
            總計 += 輸入成本 + 輸出成本

        返回 {"date": str(day), "models": 成本, "total_cost_usd": round(total, 4)}

9. Observability cho LLM/Agent

9.1. LangSmith vs Langfuse

FeatureLangSmithLangfuse
VendorLangChain (official)Open source
IntegrationLangChain nativeLangChain, LlamaIndex, custom
託管僅雲端自託管或雲端
PricingFree tier + paidFree (self-hosted)
Trace visualizationExcellentGood
EvaluationBuilt-in eval frameworkBasic eval
資料隱私浪鏈雲端資料完全控制(自架)

9.2. Langfuse Integration

從 langfuse.decorators 導入觀察,langfuse_context
從 langfuse 導入 Langfuse
從 openai 導入 OpenAI

langfuse = Langfuse() # 使用 LANGFUSE_HOST、LANGFUSE_PUBLIC_KEY、LANGFUSE_SECRET_KEY
客戶端 = OpenAI()

@觀察()
defretrieve_context(查詢:str)->列表[str]:
    """RAG 檢索步驟 — 自動追蹤。"""
    # 假設向量搜尋
    langfuse_context.update_current_observation(
        元資料={“index”:“knowledge_base_v2”}
    )
    # ...向量搜尋邏輯...
    return ["上下文區塊 1", "上下文區塊 2"]

@觀察()
defgenerate_response(查詢:str,上下文:列表[str])->str:
    """LLM 產生步驟 — 自動追蹤。"""
    context_text = "\n".join(上下文)
    回應 = client.chat.completions.create(
        型號=“gpt-4o-mini”,
        訊息=[
            {"角色": "系統", "內容": f"上下文:\n{context_text}"},
            {“角色”:“使用者”,“內容”:查詢},
        ],
    )

    結果=response.choices[0].message.content

    # 將令牌使用情況記錄到 Langfuse 中
    langfuse_context.update_current_observation(
        用法={
            「輸入」:response.usage.prompt_tokens,
            “輸出”:response.usage.completion_tokens,
        },
        型號=“gpt-4o-mini”,
    )
    回傳結果

@觀察()
def rag_pipeline(查詢: str) -> str:
    """完整的 RAG 管道 — 父追蹤包含檢索 + 生成。"""
    上下文=retrieve_context(查詢)
    回應=generate_response(查詢,上下文)

    # 分數追蹤
    langfuse_context.score_current_trace(
        名稱=“用戶回饋”,
        value=1, # 或來自使用者回饋
        評論=“自動評分”
    )
    回饋響應

Langfuse UI 中的結果:

追蹤:rag_pipeline(總共 2.3 秒)
├── 跨度:retrieve_context(0.4s)
│ └── 元資料: {index: "knowledge_base_v2"}
├── 跨距:generate_response (1.8s)
│ ├── 型號:gpt-4o-mini
│ ├── 輸入令牌:823
│ ├── 輸出令牌:156
│ └── 成本: $0.00022
└── 分數:user_feedback = 1

10. Alerting & Incident Response

10.1. Alert Rules cho AI Systems

#alerting_rules.yml(普羅米修斯警報管理器)
團體:
  - 名稱:ai-agent-alerts
    規則:
      # 高延遲
      - 警報:AIHighLatency
        expr: histogram_quantile(0.95, ai_request_latency_seconds_bucket) > 5
        用於:5m
        標籤:
          嚴重性:警告
        註:
          摘要:“AI 代理 p95 延遲 > 5 秒”

      # 錯誤率高
      - 警報:AIHighErrorRate
        表達式:>
          速率(ai_requests_total{status="error"}[5m])
          / 速率(ai_requests_total[5m]) > 0.05
        用於:2m
        標籤:
          嚴重程度:嚴重
        註:
          摘要:“AI代理錯誤率> 5%”

      # 成本飆升
      - 警報:AICostSpike
        表達式:>
          增加(ai_tokens_total[1h]) * 0.00001 > 10
        適合:10m
        標籤:
          嚴重性:警告
        註:
          摘要:“AI 代幣成本 > 10 美元/小時”

      # 品質下降
      - 警報:AIQualityDrop
        表達式:ai_model_quality_score < 0.80
        for: 15m
        labels:
          severity: critical
        annotations:
          summary: "Model quality score dropped below 80%"

10.2. Incident Response Playbook

AI Incident Response:

1. DETECT
   ├── Alert fires (Prometheus/Grafana)
   ├── User reports bad responses
   └── Quality score drops in Langfuse

2. TRIAGE (< 5 phút)
   ├── Check: Latency hoặc Quality issue?
   ├── Check: Toàn bộ hay chỉ 1 endpoint?
   └── Check: Upstream API (OpenAI) down?

3. MITIGATE (< 15 phút)
   ├── Option A: Rollback model version
   │   └── mlflow: transition previous to Production
   ├── Option B: Switch fallback model
   │   └── gpt-4o fail → gpt-4o-mini
   ├── Option C: Enable cached responses
   │   └── Return cached answers for common queries
   └── Option D: Circuit breaker
       └── Return default response, queue for retry

4. INVESTIGATE (< 2 hours)
   ├── Check Langfuse traces for bad responses
   ├── Check input drift (new types of queries?)
   ├── Check upstream: OpenAI rate limits? latency?
   └── Check data: embeddings index corrupted?

5. FIX & PREVENT
   ├── Deploy fix
   ├── Add test case for this scenario
   ├── Update alerting thresholds
   └── Write post-mortem

11. Complete MLOps Pipeline Example

11.1。架構概覽

┌──────────────────────────────────────────────────────────────┐
│                    MLOps Pipeline Overview                     │
├──────────────────────────────────────────────────────────────┤
│                                                               │
│  Developer                                                    │
│     │                                                         │
│     ▼                                                         │
│  ┌──────────┐    ┌───────────┐    ┌──────────────┐           │
│  │ Git Push │───▶│ GitHub    │───▶│ Unit Tests   │           │
│  │ (code +  │    │ Actions   │    │ Lint + Type  │           │
│  │ DVC ptr) │    │           │    │ check        │           │
│  └──────────┘    └───────────┘    └──────┬───────┘           │
│                                          │ pass              │
│                                          ▼                   │
│                                   ┌──────────────┐           │
│                                   │ Integration  │           │
│                                   │ Tests        │           │
│                                   └──────┬───────┘           │
│                                          │ pass              │
│                                          ▼                   │
│  ┌──────────┐                     ┌──────────────┐           │
│  │ MLflow   │◀────── log ────────│ Model Eval   │           │
│  │ Tracking │                     │ (quality     │           │
│  └────┬─────┘                     │  gates)      │           │
│       │                           └──────┬───────┘           │
│       │ register                         │ pass              │
│       ▼                                  ▼                   │
│  ┌──────────┐                     ┌──────────────┐           │
│  │ Model    │                     │ Build Docker │           │
│  │ Registry │──── promote ──────▶│ + Deploy K8s │           │
│  │ (Stage)  │                     └──────┬───────┘           │
│  └──────────┘                            │                   │
│                                          ▼                   │
│                                   ┌──────────────┐           │
│  ┌──────────┐                     │  Production  │           │
│  │Langfuse/ │◀────── traces ─────│  AI Agent    │           │
│  │LangSmith │                     └──────┬───────┘           │
│  └──────────┘                            │                   │
│                                          ▼                   │
│  ┌──────────┐    ┌───────────┐    ┌──────────────┐           │
│  │ Alert-   │◀───│Prometheus │◀───│  Metrics     │           │
│  │ Manager  │    │ + Grafana │    │  Endpoint    │           │
│  └──────────┘    └───────────┘    └──────────────┘           │
│                                                               │
└──────────────────────────────────────────────────────────────┘

11.2. Docker Compose cho MLOps Stack

# docker-compose.mlops.yml
services:
  # --- AI Agent ---
  ai-agent:
    build: .
    ports: ["8000:8000"]
    environment:
      - OPENAI_API_KEY=${OPENAI_API_KEY}
      - MLFLOW_TRACKING_URI=http://mlflow:5000
      - LANGFUSE_HOST=http://langfuse:3000
    depends_on: [mlflow, langfuse, prometheus]

  # --- MLflow ---
  mlflow:
    image: ghcr.io/mlflow/mlflow:v2.16.0
    ports: ["5000:5000"]
    command: >
      毫升流伺服器
      --backend-store-uri sqlite:///mlflow/mlflow.db
      --default-artifact-root /mlflow/artifacts
      --主機0.0.0.0
    卷:
      - mlflow-數據:/mlflow

  # --- 朗芙絲 ---
  朗福斯:
    圖:langfuse/langfuse:2
    埠:[“3000:3000”]
    環境:
      - DATABASE_URL=postgresql://postgres:postgres@langfuse-db:5432/langfuse
      - NEXTAUTH_SECRET=我的秘密
      - NEXTAUTH_URL=http://localhost:3000
    取決於:[langfuse-db]

  langfuse-db:
    圖:postgres:16-alpine
    環境:
      POSTGRES_DB:langfuse
      POSTGRES_PASSWORD:postgres
    卷:
      - langfuse-db-data:/var/lib/postgresql/data

  # --- 監控 ---
  普羅米修斯:
    圖片:舞會/普羅米修斯:v2.53.0
    埠:[“9090:9090”]
    卷:
      - ./monitoring/prometheus.yml:/etc/prometheus/prometheus.yml

  格拉法納:
    圖:grafana/grafana:11.1.0
    埠:[“3001:3000”]
    環境:
      - GF_SECURITY_ADMIN_PASSWORD=管理員
    卷:
      -grafana-數據:/var/lib/grafana

卷:
  mlflow-數據:
  langfuse-db-資料:
  grafana-數據:
# 啟動整個 MLOps 堆疊
docker compose -f docker-compose.mlops.yml up -d

# 訪問:
# - 人工智慧代理:  http://localhost:8000
# - MLflow:    http://localhost:5000
# - Langfuse:  http://localhost:3000
# - 普羅米修斯: http://localhost:9090
# - 格拉法納:   http://localhost:3001

11.3。工具總表

關注工具角色
Experiment TrackingMLflow / W&BLog params, metrics, artifacts
Model RegistryMLflow RegistryVersion, stage, promote models
資料版本控制DVC適用於大數據的類似 Git 版本控制
CI/CDGitHub ActionsAutomated test → eval → deploy
Monitoring (infra)Prometheus + GrafanaLatency, throughput, errors
Monitoring (quality)Custom + LangfuseResponse quality, drift
Observability (LLM)Langfuse / LangSmithTrace chains, debug agents
AlertingPrometheus AlertManagerNotify on anomalies
Cost TrackingCustom + GrafanaToken usage, $/request

## 概括

✅ MLOps = DevOps + 資料、模型與模型品質管理 — 成熟度等級 0–4

✅ 實驗追蹤:MLflow(自架、開源)或 W&B(雲端 SaaS)— 記錄所有內容以實現可重現

✅ Model Registry: Versioning + stage transitions (None → Staging → Production → Archived)

✅ CI/CD cho AI: Unit tests → Integration → Model quality gates → Auto deploy — GitHub Actions workflow

✅ DVC:使用 git 的版本資料 + 模型文件,從 S3/GCS 推送/拉取

✅ Serving Patterns: Real-time (chat), Batch (bulk processing), Streaming (token-by-token UX)

✅ Monitoring: Prometheus/Grafana cho latency/throughput + custom metrics cho quality/drift/cost

✅ 可觀察性:Langfuse 或 LangSmith — 追蹤鏈中的每個步驟,調試生產問題

✅ Alerting: Rules cho latency spike, error rate, cost spike, quality degradation

✅ MLOps Stack:Docker Compose 運行 MLflow + Langfuse + Prometheus + Grafana 和 AI Agent

## 鍛煉

練習 1:MLflow 實驗追蹤(30 分鐘)

1.安裝MLflow並運行 流量使用者介面 2. 寫一個腳本來使用 3 組不同的參數(溫度、系統提示字元)來呼叫 OpenAI API(或模擬) 3. 將參數、指標(準確性、延遲)和工件(結果 JSON)記錄到 MLflow 中 4. 在 MLflow UI 中比較 3 次運行,選擇最佳運行

練習 2:CI/CD 管道(45 分鐘)

  1. 為 AI 代理專案建立 GitHub Actions 工作流程,分為 3 個階段:
    • Lint + unit tests
  • 模型評估(使用模擬 API 或小型測試資料集)
  • 質量閘:若準確度 < 80% 或延遲 > 3 秒則失敗
  1. 推送程式碼並驗證管道是否正確運行 3.嘗試更改程式碼使品質門失敗,確認管道塊部署

練習 3:監控儀表板(45 分鐘)

  1. 將 Prometheus 指標新增至 FastAPI 應用程式(請求計數、延遲直方圖、令牌計數器)
  2. 使用 Docker Compose 設定 Prometheus + Grafana
  3. 使用以下指令建立 Grafana 儀表板:
    • Request rate (req/s)
    • Latency percentiles (p50, p95, p99)
    • Token usage over time
    • Error rate 4.發送模擬流量並即時驗證儀表板更新

練習 4:Langfuse 追蹤(30 分鐘)

1.使用Docker Compose在本地部署Langfuse 2. 偵測一個簡單的 RAG 管道(檢索 → 產生): @觀察() 3.提交5-10個查詢,打開Langfuse UI查看痕跡 4.找出哪個查詢延遲最高,分析哪些步驟慢