Bài 7: LLM Observability & Guardrails at Scale¶
Tổng quan¶
Module I, Bài 7 dạy guardrails cơ bản (prompt injection, PII, output validation) và setup LangSmith/LangFuse. Module II, Bài 5 dạy tracing agent và OpenTelemetry.
Bài này chuyển sang vận hành ở scale:
- Ba tầng observability và câu hỏi mỗi tầng trả lời.
- Tracing production: sampling, PII trong trace, chi phí của chính observability.
- Production dashboard: cost per feature, hallucination rate, user feedback, SLO.
- Guardrails ở scale: latency overhead, false positive, fail-open vs fail-closed.
- Incident response: 4 playbook (cost spike, hallucination spike, jailbreak, provider outage).
1. Ba tầng observability¶
graph TB
T[Trace<br/>1 request: mọi node, tool call, token, latency] --> M[Metric<br/>tổng hợp theo thời gian: cost, p95, hit rate]
M --> L[Log<br/>sự kiện rời rạc: lỗi, guardrail trigger, fallback]
| Tầng | Trả lời câu hỏi | Ví dụ công cụ |
|---|---|---|
| Trace | "Request này chậm/sai ở bước nào?" | LangSmith, LangFuse, OTel + Jaeger |
| Metric | "Xu hướng cost/latency/chất lượng đang đi đâu?" | Prometheus/Grafana, Cloud Monitoring, dashboard của LangSmith |
| Log | "Chuyện gì xảy ra lúc 02:14? Có bao nhiêu lần fallback?" | Cloud Logging, Loki, ELK |
Quy tắc: trace để debug một ca, metric để phát hiện xu hướng, log để điều tra sự kiện. Thiếu tầng nào là mù ở loại câu hỏi đó.
2. Tracing Production¶
Sampling - không trace 100%¶
Ở volume cao, lưu mọi trace tốn tiền và chậm. Chiến lược:
| Chiến lược | Cách | Dùng khi |
|---|---|---|
| Head sampling | Giữ ngẫu nhiên X% request ngay từ đầu | Volume rất cao, chỉ cần bức tranh thống kê |
| Tail sampling | Buffer rồi giữ trace nếu: có lỗi, latency > p99, guardrail trigger, chi phí cao | Muốn giữ 100% ca "thú vị" mà không lưu hết ca bình thường |
| Luôn giữ | Request bị đánh dấu (user báo lỗi, feedback âm) | Điều tra khiếu nại cụ thể |
def should_sample(ctx) -> bool:
if ctx.error or ctx.latency_ms > P99 or ctx.guardrail_triggered:
return True # tail: giữ ca bất thường
return random.random() < 0.05 # head: 5% ca bình thường
PII trong trace¶
Trace chứa prompt + output → có thể chứa PII người dùng nhập. Trước khi gửi lên dịch vụ ngoài:
- Redact (Presidio / regex tiếng Việt - xem M1, Bài 7) các trường
input,outputtrong hook trước khi export. - Hoặc self-host LangFuse để dữ liệu không rời hạ tầng của bạn.
- Đặt retention ngắn (14–30 ngày) cho trace có nội dung; giữ metric (không có nội dung) lâu hơn.
Chi phí của chính observability¶
- Trace đầy đủ + judge chấm hallucination trên mẫu = một khoản chi phí thật.
- Ngân sách hoá: "observability ≤ X% chi phí inference". Nếu vượt → giảm sampling, giảm tần suất chấm.
3. Token & Cost Analytics¶
Dùng lại record_cost() từ Bài 3, gắn tag đủ để cắt lát:
metrics.emit("llm_cost_usd", cost, tags={
"feature": feature, # rag_answer, summarize, classify...
"model": model,
"cache": "hit" | "miss",
"cascade": "cheap" | "escalated",
"prompt_version": pv,
})
Latency breakdown¶
Một request RAG chậm - chậm ở đâu?
graph LR
REQ[Request] --> RW[Query rewrite<br/>~120ms] --> RET[Retrieval<br/>~80ms] --> RERANK[Rerank<br/>~150ms] --> LLM[LLM generate<br/>~2400ms] --> GR[Output guardrail<br/>~90ms]
Trace phải tách được các span này. Thường phát hiện: rerank hoặc guardrail âm thầm cộng 200–400ms; hoặc retrieval chậm khi index lớn lên.
4. Production Dashboard¶
graph TB
subgraph "Dashboard production"
A[Cost per feature<br/>ngày / tuần]
B[Faithfulness / hallucination rate<br/>chấm judge trên mẫu]
C[Latency p50 / p95 / p99<br/>tổng + theo span]
D[User feedback<br/>thumbs + tín hiệu ngầm]
E[Cache hit rate<br/>tầng 1 + tầng 2]
F[Error rate + fallback count<br/>theo loại]
G[SLO / error budget<br/>còn lại bao nhiêu]
end
| Widget | Nguồn dữ liệu | Ngưỡng cảnh báo gợi ý |
|---|---|---|
| Cost per feature | llm_cost_usd tag feature |
Feature tăng > 50% tuần-trên-tuần; tổng > 120% dự phóng |
| Hallucination rate | Judge/RAGAS faithfulness trên mẫu 1–5% traffic | Faithfulness < baseline − 0.05 |
| Latency p95 | Span tổng | p95 > SLO (ví dụ 4s); p99 > 2× p95 |
| User feedback | Nút thumbs; tín hiệu ngầm: bỏ giữa chừng, hỏi lại ngay, copy đáp án | Tỉ lệ thumbs-down > X%; retry-rate tăng |
| Error / fallback | Log tag loại (429, timeout, guardrail block, provider fallback) | Bất kỳ loại nào tăng đột biến |
| SLO / error budget | availability + latency SLO | Error budget cháy nhanh hơn dự kiến |
Tín hiệu ngầm từ người dùng¶
Không phải ai cũng bấm thumbs. Tín hiệu rẻ mà giá trị:
- Regenerate / hỏi lại ngay trong < 10s → câu trả lời có thể tệ.
- Bỏ giữa stream → quá chậm hoặc lạc đề.
- Copy đoạn trả lời → có thể hữu ích.
- Rời phiên ngay sau 1 câu → không giải quyết được nhu cầu.
5. Guardrails at Scale¶
Guardrails cơ bản đã có ở M1, Bài 7. Ở production, câu hỏi chuyển từ "làm thế nào" sang "chịu được chi phí vận hành không".
graph LR
IN[Input] --> GI[Input guard<br/>injection, PII, off-topic]
GI -->|pass| LLM[LLM]
GI -->|block| REFUSE1[Từ chối / hỏi lại]
LLM --> GO[Output guard<br/>PII leak, groundedness, toxicity, format]
GO -->|pass| OUT[Trả người dùng]
GO -->|fail| ACT{Fail-open / Fail-closed?}
| Vấn đề vận hành | Chi tiết |
|---|---|
| Latency overhead | Mỗi guard là thêm 50–300ms (regex rẻ; LLM-based detector đắt). Đo và ngân sách hoá; chạy song song các guard độc lập |
| False positive rate | Guard quá gắt chặn cả câu hợp lệ → user bực. Đo FP trên traffic thật; tune ngưỡng; log mọi lần block để review |
| Fail-open vs fail-closed | Guard service lỗi/timeout thì làm gì? |
| Thứ tự & chi phí | Chạy guard rẻ trước (regex PII) rồi mới guard đắt (LLM injection detector); dừng sớm khi đã block |
Fail-open vs Fail-closed¶
| Fail-open (cho qua khi guard lỗi) | Fail-closed (chặn khi guard lỗi) | |
|---|---|---|
| Rủi ro | Nội dung xấu lọt khi guard down | Từ chối cả request hợp lệ khi guard down |
| Hợp với | Guard "nice-to-have" (ví dụ format check) | Guard bắt buộc (PII leak, nội dung nguy hiểm) |
| Bắt buộc kèm | Alert khi rơi vào fail-open + đếm số lần | Fallback nhẹ hơn (regex-only) trước khi từ chối hẳn |
Guardrail cũng là điểm lỗi
Một LLM-based injection detector gọi model ngoài: nó timeout thì cả request treo. Đặt timeout riêng, có nhánh fallback (regex), và log mỗi lần fallback → đưa lên dashboard (mục 4, "Error / fallback").
6. Incident Response¶
Bốn playbook. Mỗi playbook: tín hiệu phát hiện → khoanh vùng → giảm thiểu → sau sự cố.
6.1. Cost Spike¶
| Bước | Hành động |
|---|---|
| Phát hiện | Alert budget (Bài 3): chi phí giờ này > 3× trung bình; hoặc đạt 80% ngân sách sớm bất thường |
| Khoanh vùng | Dashboard cost per feature + per user: feature nào / user nào nhảy vọt? Cache hit rate có tụt (key bị đổi)? Prompt version mới có làm context phình? |
| Giảm thiểu | Bật/giảm ngưỡng hard limit per user; tạm route feature thủ phạm sang model rẻ; nếu do cache miss hàng loạt → rollback thay đổi làm hỏng cache key; nếu do loop agent → giảm max_iterations |
| Sau sự cố | Thêm alert sớm hơn; thêm test cache-key vào CI; ghi runbook |
6.2. Hallucination Spike¶
| Bước | Hành động |
|---|---|
| Phát hiện | Faithfulness (judge trên mẫu) tụt dưới baseline − 0.05; thumbs-down tăng; feedback "sai thông tin" |
| Khoanh vùng | Đổi gì gần đây: prompt version? model? retrieval (index rebuild, embedding đổi)? Slice nào tệ nhất (dùng eval theo slice Bài 2)? |
| Giảm thiểu | Rollback prompt version / model config về last-known-good (Bài 6); nếu do index → khôi phục snapshot index trước đó; siết guardrail groundedness tạm thời |
| Sau sự cố | Thêm case vào golden set từ đúng các câu bị sai; siết eval gate cho slice liên quan |
6.3. Jailbreak / Prompt Injection Attempt¶
| Bước | Hành động |
|---|---|
| Phát hiện | Guardrail injection log tăng đột biến; xuất hiện output rò rỉ system prompt / phá vai; một user gửi nhiều biến thể tấn công |
| Khoanh vùng | Mẫu tấn công nào đang lọt? Từ ít user hay lan rộng? Guard đang fail-open? |
| Giảm thiểu | Chuyển guard injection sang fail-closed cho traffic nghi ngờ; thêm pattern vào input filter; rate-limit / block user tấn công; nếu system prompt bị lộ → xoay các secret nhắc trong đó |
| Sau sự cố | Thêm mẫu tấn công vào golden set slice injection; red-team lại; xem M1, Bài 7 |
6.4. Provider Outage / Degradation¶
| Bước | Hành động |
|---|---|
| Phát hiện | Error rate 5xx / timeout từ provider tăng; latency p95 tăng gấp bội; status page provider |
| Khoanh vùng | Một model hay cả provider? Khu vực nào? Có phải quota bị vượt (429 chứ không phải 5xx)? |
| Giảm thiểu | Chuyển sang fallback model/provider đã cấu hình sẵn; bật retry backoff dài hơn (Bài 4); giảm tính năng không thiết yếu; hiện banner "đang chậm" |
| Sau sự cố | Kiểm tra fallback có thực sự chạy (diễn tập định kỳ); điều chỉnh timeout/retry; đa dạng hoá provider cho tính năng quan trọng |
# Fallback provider - cấu hình sẵn, diễn tập định kỳ
PROVIDERS = ["claude-sonnet-5", "gpt-4o"] # thứ tự ưu tiên
async def generate(messages) -> str:
last = None
for model in PROVIDERS:
try:
return await call_with_retry(model=model, messages=messages)
except (ProviderError, APITimeoutError) as e:
last = e
metrics.emit("provider_fallback", 1, tags={"from": model})
raise last
7. Hands-on: LangSmith tracing + cost dashboard cho Vietnamese RAG agent¶
Dùng RAG agent từ Capstone Module I đã deploy ở Bài 5.
Bước 1 - Tracing có sampling¶
- Bật LangSmith/LangFuse tracing (như M1, Bài 7).
- Thêm
should_sample(): 100% ca lỗi/guardrail/slow, 5% ca bình thường. - Redact PII ở hook trước export (regex tiếng Việt cho SĐT, email, CCCD).
- Đặt retention 30 ngày cho trace.
Bước 2 - Span breakdown¶
- Instrument các span:
query_rewrite,retrieve,rerank,llm_generate,output_guard. - Chạy 30 câu hỏi, xem trace: span nào chiếm nhiều thời gian nhất?
Bước 3 - Cost dashboard¶
record_cost()với tagfeature,model,cache,prompt_version.- Dashboard (Grafana/Cloud Monitoring/LangSmith) với widget: cost per feature (ngày), latency p50/p95/p99, cache hit rate, error/fallback count.
- Chấm faithfulness bằng judge trên 5% traffic → widget hallucination rate.
Bước 4 - Alert + một incident¶
- Alert: cost giờ này > 3× trung bình 7 ngày → gửi Slack/email.
- Diễn tập cost spike: tạm sửa prompt cho context phình gấp 3 (lấy top-30 chunk) → chạy tải → xác nhận alert kêu → theo playbook 6.1 khoanh vùng bằng dashboard → rollback prompt version.
Bước 5 (mở rộng) - Fallback provider¶
- Cấu hình
PROVIDERSvới 2 model khác nhà. - Giả lập outage (chặn network tới provider 1 / trả 503) → xác nhận tự chuyển provider 2,
provider_fallbackmetric tăng, người dùng vẫn có câu trả lời.
Tiêu chí hoàn thành¶
- Trace có sampling (100% ca bất thường, ~5% ca thường) + PII đã redact
- Trace tách được span; chỉ ra được bước chậm nhất
- Dashboard có ≥ 5 widget: cost/feature, latency p95, cache hit, error/fallback, hallucination rate
- Alert cost spike hoạt động (đã kích hoạt thật trong diễn tập)
- Thực hiện trọn 1 playbook incident: phát hiện → khoanh vùng bằng dashboard → mitigate
- (Mở rộng) fallback provider chạy khi provider chính lỗi
Tóm tắt¶
graph LR
APP[RAG / Agent] --> TRACE[Trace có sampling + redact PII]
APP --> METRIC[Metric: cost/feature, p95, hit rate, hallucination]
APP --> LOG[Log: lỗi, guardrail, fallback]
METRIC --> DASH[Dashboard + SLO]
DASH --> ALERT{Alert}
ALERT -->|cost spike| P1[Playbook: khoanh feature/user -> hard limit / route rẻ / rollback]
ALERT -->|hallucination| P2[Playbook: rollback prompt/model/index + thêm golden case]
ALERT -->|jailbreak| P3[Playbook: fail-closed + block user + thêm injection case]
ALERT -->|provider outage| P4[Playbook: fallback provider + retry dài + giảm tính năng]
| Chủ đề | Cốt lõi |
|---|---|
| Ba tầng | Trace (debug 1 ca) · Metric (xu hướng) · Log (điều tra sự kiện) |
| Tracing prod | Head/tail sampling; redact PII trước export; retention ngắn cho nội dung; ngân sách hoá chi phí observability |
| Dashboard | Cost/feature, faithfulness (judge trên mẫu), latency p95/p99, feedback + tín hiệu ngầm, error/fallback, SLO |
| Guardrails at scale | Đo latency overhead + false positive; guard rẻ trước; fail-open vs fail-closed có chủ đích; guard cũng là điểm lỗi |
| Incident response | 4 playbook chung khung: phát hiện → khoanh vùng → giảm thiểu → sau sự cố; fallback phải được diễn tập |