Phần I · Nền tảng Chương 1

Giới thiệu: Ba Vực thẳm & vòng lặp Analyze–Measure–Improve

Chapter 1 — Introduction · bản mở rộng có ví dụ, code, bài tập & lab

TL;DR

  • Sách tách agent (phần do LLM điều khiển: suy luận, gọi tool, sinh output) khỏi application (UI, DB, API, hạ tầng) và chỉ tập trung eval tầng agent.
  • LLM app không deterministickhông có spec hình thức → không test theo kiểu unit test truyền thống; output có thể "đúng mà sai vibe" hoặc "rất thuyết phục mà sai hoàn toàn".
  • Evaluation = quá trình có hệ thống để hiểu (tìm mẫu lỗi) và đo (tần suất lỗi); gắn vào hệ thống qua 4 cách: monitoring · guardrails · improvement · model migration.
  • Mọi lỗi của LLM app rơi vào một trong 3 vực thẳm: Comprehension (bạn ↔ data) · Specification (bạn ↔ agent) · Generalization (data ↔ agent).
  • Cách vượt vực: vòng lặp Analyze → Measure → Improve. Lỗi spec thì sửa prompt ngay; lỗi generalization thì đo tần suất rồi mới can thiệp mạnh hơn.

Cách đọc trang này

Phần "sách nói gì" được diễn giải ngắn gọn bằng lời của người tóm tắt. Phần lớn nội dung còn lại — ví dụ, dữ liệu, code, case study, bài tập, lab — là tài liệu tự soạn để luyện tập; mọi con số trong đó là minh hoạ (illustrative), không lấy từ sách. Code Python chạy được bằng python3, không cần thư viện ngoài.

1.1Agent vs. Application — eval cái gì?

Trực giác. Khi một sản phẩm có LLM "chạy sai", có hai chỗ để đổ lỗi: bộ não (LLM agent — đọc input, suy luận, gọi tool, viết câu trả lời) và cơ thể quanh nó (application — giao diện, database, API, hạ tầng deploy). Sách dùng chữ "agent" theo nghĩa rộng: từ một lần gọi LLM không có tool, đến vòng lặp gọi nhiều tool và ra nhiều quyết định cho mỗi request. Toàn bộ cuốn sách tập trung vào tầng agent, vì lỗi ở tầng application đã có công cụ kiểm thử phần mềm truyền thống lo.

Application UI · Database · API layer · Hạ tầng deploy LLM Agent suy luận · gọi tool · sinh output ← sách tập trung eval tầng này Lỗi application: UI vỡ, thiếu data, phản hồi chậm Lỗi agent: gọi sai tool, suy luận sai, sai format output
"Agent" dùng theo nghĩa rộng: từ 1 lần gọi LLM không tool, đến vòng lặp gọi nhiều tool.
Ví dụ 1.1 · Phân loại 6 bug của một bot đặt lịch khám (minh hoạ)

Một phòng khám tư dùng bot chat để đặt lịch. Tuần đầu nhận 6 bug report. Ta tách xem cái nào thuộc tầng agent (sách quan tâm) và cái nào thuộc application:

#Bug reportTầngVì sao
1Bot đặt lịch vào Chủ nhật dù phòng khám đóng cửaAgentSuy luận sai về ràng buộc lịch (dữ liệu giờ mở cửa có sẵn trong context)
2Nút "Xác nhận" không bấm được trên iPhone SEApplicationLỗi UI, không liên quan LLM
3Bot gọi tool cancel_booking khi khách chỉ hỏi "hủy được không?"AgentGọi sai tool — hiểu câu hỏi thành mệnh lệnh
4Trả lời mất 25 giây vì DB lịch bác sĩ bị lockApplicationHạ tầng/DB chậm
5Bot trả về JSON thiếu trường doctor_id làm backend crashAgentSai format output (dù hậu quả hiện ra ở backend)
6Bot nói "bác sĩ Hà không có lịch" vì API lịch trả mảng rỗng do lỗi timezoneApplicationAgent làm đúng với data nó nhận; data sai từ API
  1. Hỏi: nếu thay LLM bằng một người thật đọc cùng context, lỗi còn xảy ra không? Nếu còn (bug 2, 4, 6) → gần như chắc là tầng application.
  2. Hỏi: LLM có nhận đủ thông tin đúng không? Có mà vẫn sai (bug 1, 3, 5) → tầng agent.
  3. Cẩn thận với "triệu chứng ở chỗ khác": bug 5 làm backend crash, nhưng nguyên nhân gốc là output của agent.
  4. Kết quả: 3/6 bug thuộc phạm vi eval của sách. 3 cái còn lại xử lý bằng test UI, load test, test tích hợp API.

Hay nhầm

"Agent" ở đây không chỉ là hệ thống tự chủ nhiều bước kiểu AutoGPT. Một lần gọi LLM để tóm tắt email cũng là "agent" theo nghĩa của sách. Và ranh giới agent/application quan trọng vì nó quyết định ai sửadùng công cụ gì để bắt: bug 6 ở trên mà đem đi sửa prompt thì mất công vô ích.
Bài tập 1.1
Một bot hỏi đáp nội bộ (RAG trên wiki công ty) nhận 3 phàn nàn: (a) trả lời dựa trên trang wiki đã bị xoá từ tháng trước; (b) trích dẫn đúng trang nhưng kết luận ngược nội dung trang; (c) câu trả lời bị cắt cụt ở giữa câu trên giao diện Slack. Mỗi cái thuộc tầng nào?
Xem lời giải (a) Chủ yếu application — pipeline index không đồng bộ khi xoá trang (data sai đi vào agent). (b) Agent — có đúng tài liệu mà suy luận sai. (c) Tuỳ: nếu Slack giới hạn độ dài tin nhắn và code cắt chuỗi → application; nếu model dừng vì max_tokens quá thấp do cấu hình gọi model → vùng giáp ranh, nhưng không phải lỗi "trí tuệ" của agent. Bài học: đọc trace đầy đủ mới phân loại chắc được.

1.2Vì sao LLM app khác phần mềm truyền thống

Trực giác. Hàm tinh_thue(gia) luôn trả cùng kết quả cho cùng input, và bạn có spec rõ ("VAT 10%"). LLM app thì ngược lại ở cả hai điểm: (1) cùng input có thể cho output khác nhau giữa các lần chạy; (2) không có spec hình thức nào nói output "phải" là gì. Lỗi cũng đa dạng hơn: có lúc sai rõ ràng, có lúc đúng về nội dung nhưng không phù hợp (giọng điệu lạ, dài dòng), có lúc nghe rất thuyết phục nhưng sai hoàn toàn. Ngay cả ML truyền thống cũng dễ hơn: output là số/nhãn, có ground truth, dùng accuracy/F1 là xong; còn output LLM mở, phi cấu trúc và thường mang tính chủ quan.

Ví dụ 1.2 · Cùng một prompt, 5 lần chạy (minh hoạ)

Prompt: Tóm tắt email sau cho quản lý. — email khách xin báo giá 200 áo đồng phục, cần trước thứ 6, hỏi thêm có in logo không.

LầnOutput (rút gọn)Nhận xét
1"Khách cần báo giá 200 áo trước thứ 6, hỏi về in logo."Tốt
2• Báo giá 200 áo
• Hạn: thứ 6
• Hỏi in logo
Tốt nhưng khác format
3"Khách hàng Lan gửi lời chào và mong nhận được phản hồi sớm..." (4 câu)Dài, lạc trọng tâm — "sai vibe"
4"Khách cần báo giá 200 áo trước thứ 6 và đã đồng ý in logo."Sai mà nghe rất tự tin: khách mới hỏi, chưa đồng ý
5"Khách cần báo giá 200 áo trước thứ 6, hỏi về in logo."Tốt
  1. Unit test kiểu assert out == "Khách cần báo giá..." sẽ fail ở lần 2 dù output tốt → assert chuỗi chính xác vô dụng.
  2. Lần 4 chỉ phát hiện được nếu ai đó (người hoặc evaluator) hiểu ngữ nghĩa: "hỏi" ≠ "đồng ý".
  3. Lần 3 không sai sự thật; nó sai kỳ vọng — mà kỳ vọng ("ngắn, cho quản lý đọc lướt") chưa hề được viết ra.
  4. Vậy "test" LLM app cần: đo trên nhiều input và nhiều lần chạy, với tiêu chí dạng thuộc tính ("không bịa cam kết", "≤ 2 câu") thay vì so khớp chuỗi.
Phần mềm truyền thốngML truyền thốngLLM application
OutputDeterministicSố / nhãn cố địnhVăn bản mở, thay đổi giữa các lần chạy
SpecRõ (yêu cầu nghiệp vụ)Ground truth labelsThường không có; hình thành dần khi đọc output
Kiểm traUnit/integration testAccuracy, F1, AUCError analysis + evaluator tự động (code/LLM-judge) + người
Kiểu lỗiCrash, sai logicNhầm lớpBịa, lạc đề, sai giọng, sai format, gọi sai tool…

Hay nhầm: "đặt temperature = 0 là deterministic, test như thường"

Temperature 0 giảm ngẫu nhiên nhưng không giải quyết vấn đề chính: (1) nhiều API vẫn cho output hơi khác giữa các lần do batching/phần cứng; (2) quan trọng hơn, thay đổi input rất nhỏ (một dấu phẩy, thêm chữ ký email) đã có thể đổi output — và input thật thì vô cùng đa dạng; (3) vẫn không có spec để assert. Non-determinism theo input mới là nguồn khó chính.
Bài tập 1.2
Cái nào dưới đây kiểm tra được bằng code deterministic (không cần người/LLM đánh giá)? (a) output là JSON hợp lệ có trường sender; (b) tóm tắt "đủ ý chính"; (c) câu trả lời không chứa số điện thoại; (d) giọng văn "thân thiện nhưng chuyên nghiệp"; (e) SQL sinh ra chỉ chứa câu lệnh SELECT.
Xem lời giải Code được: (a), (c) (regex), (e) (parse SQL). Cần judgment: (b), (d) → người chấm hoặc LLM-as-judge có rubric (Chương 5). Lưu ý: dù (a)(c)(e) check bằng code, quyết định check những thuộc tính nào vẫn đến từ việc đọc output thật (Analyze).

1.3Câu chuyện cảnh báo: chatbot hãng bay

Sách mở đầu bằng vụ chatbot của Air Canada: một hành khách hỏi về giá vé tang lễ (bereavement fare); bot bảo cứ mua vé thường rồi xin hoàn phần giảm giá trong vòng 90 ngày sau chuyến bay — trong khi chính sách thật chỉ cho xin trước chuyến bay. Hãng từ chối hoàn tiền, khách kiện ra toà dân sự British Columbia và thắng (2/2024); toà bác lập luận rằng chatbot là "thực thể pháp lý riêng" và bắt hãng bồi thường. Không ai biết chắc vì sao bot sai (diễn giải sai chính sách, hay lẫn với chính sách hãng khác). Điều chắc chắn: không ai bắt được lỗi trước khi khách tin vào nó. Một bộ check có hệ thống trên các câu hỏi chính sách phổ biến lẽ ra đã phát hiện được.

Sách cũng nhấn mạnh eval không dừng ở lúc launch: eval trước launch bắt lỗi trước khi tới người dùng; monitoring production bắt lỗi chỉ xuất hiện khi phân bố input thật dịch chuyển và người dùng hành xử khó đoán (Chương 9).

Ví dụ 1.3 · Bộ eval chính sách tối thiểu cho hãng bay giả định "SkyViet"

Giả sử bạn là kỹ sư của một hãng bay (hư cấu) sắp ra mắt bot. Làm thế nào để có "một bộ check câu hỏi chính sách" trong một buổi chiều?

  1. Lấy câu hỏi thật: xuất 3 tháng ticket tổng đài, lọc ra 20 chủ đề chính sách hỏi nhiều nhất (hành lý, đổi vé, hoàn vé, vé tang lễ, trẻ em đi một mình…).
  2. Viết đáp án chuẩn dạng thuộc tính với chuyên gia chính sách: mỗi câu có danh sách cụm phải có và cụm không được có (ví dụ: vé tang lễ → phải có "trước chuyến bay", không được có "sau chuyến bay").
  3. Diễn đạt lại mỗi câu 3–5 kiểu (không dấu, tiếng Anh, viết sai chính tả, hỏi vòng vo) vì khách thật không hỏi theo FAQ.
  4. Chạy bot, chấm tự động bằng code đơn giản dưới đây; câu nào FAIL thì người đọc lại trace.
  5. Chặn launch nếu có bất kỳ FAIL nào ở nhóm "chính sách tiền bạc" — stakes cao, sai một lần cũng đắt.
# policy_eval.py — dữ liệu & câu trả lời đều là minh hoạ
CASES = [
    {"id": "P1", "q": "Bay dự đám tang có được giảm giá vé không?",
     "must": ["trước chuyến bay"], "must_not": ["sau chuyến bay", "hoàn lại sau"]},
    {"id": "P2", "q": "Hành lý xách tay tối đa bao nhiêu kg?",
     "must": ["7 kg"], "must_not": ["10 kg"]},
    {"id": "P3", "q": "Đổi tên hành khách trên vé được không?",
     "must": ["không được đổi tên"], "must_not": ["đổi tên miễn phí"]},
]
ANSWERS = {  # output thu được khi chạy bot (giả lập)
    "P1": "Bạn cứ mua vé thường, rồi xin hoàn lại sau chuyến bay trong 90 ngày.",
    "P2": "Hành lý xách tay tối đa 7 kg, kích thước 56x36x23 cm.",
    "P3": "Rất tiếc, vé không được đổi tên, bạn cần mua vé mới.",
}

def check(case, answer):
    a = answer.lower()
    missing = [m for m in case["must"] if m.lower() not in a]
    forbidden = [f for f in case["must_not"] if f.lower() in a]
    return (not missing and not forbidden), missing, forbidden

fails = 0
for c in CASES:
    ok, missing, forbidden = check(c, ANSWERS[c["id"]])
    fails += not ok
    print(f"{c['id']} {'PASS' if ok else 'FAIL'}  thiếu={missing} cấm={forbidden}")
print(f"Tỉ lệ đạt: {len(CASES) - fails}/{len(CASES)}")
P1 FAIL  thiếu=['trước chuyến bay'] cấm=['sau chuyến bay', 'hoàn lại sau']
P2 PASS  thiếu=[] cấm=[]
P3 PASS  thiếu=[] cấm=[]
Tỉ lệ đạt: 2/3

Check dạng từ khoá rất thô (bot có thể nói "trước chuyến bay" trong câu phủ định), nhưng nó đủ để đẩy P1 tới mắt người trước khi đến tay khách. Evaluator tinh hơn (LLM-as-judge có rubric) là chủ đề Chương 5.

Bài tập 1.3
Với P1, hãy viết thêm 3 cách diễn đạt câu hỏi mà khách thật có thể gõ, và một câu trả lời của bot sẽ lọt qua check từ khoá ở trên dù sai chính sách.
Xem lời giải Diễn đạt: "ba em mất, em bay về gấp có được giảm k?", "bereavement fare how to claim", "mua vé rồi mới nộp giấy chứng tử được không". Câu lọt check: "Bạn không cần xin trước chuyến bay, cứ nộp giấy tờ sau cũng được" — chứa cụm "trước chuyến bay" (pass must) và không chứa nguyên văn "sau chuyến bay" (pass must_not). Bài học: check từ khoá là lưới thô; câu hỏi tiền bạc nên có thêm judge ngữ nghĩa hoặc người duyệt.

1.4Evaluation là gì?

Theo sách, evaluation là quá trình có hệ thống để hiểu và đo chất lượng của một LLM app — gồm cả phần định tính (phát hiện mẫu lỗi) và định lượng (đo tần suất các lỗi đó). Eval tốt cho kết quả diễn giải được (biết con số nghĩa là gì) và hành động được (biết phải sửa gì). Có lúc "metric" là một con số tính tự động (trợ lý du lịch: bao nhiêu % lịch trình có chuyến bay + khách sạn hợp lệ và trong ngân sách); có lúc là đánh giá có phán đoán của chuyên gia hay chính team sản phẩm (rõ ràng, giọng điệu, hữu ích). Mức nghiêm ngặt là một phổ: từ check nhanh lúc prototype tới rubric chặt + lấy mẫu có hệ thống cho production.

Ví dụ 1.4 · Từ "eval mơ hồ" đến "eval hành động được" — trợ lý lập lịch trình du lịch (minh hoạ)
Phiên bản evalKết quảDiễn giải được?Hành động được?
"Cho LLM chấm chất lượng lịch trình 1–10"Trung bình 7,4Không — 7,4 là tốt hay tệ?Không — không biết sửa gì
"% lịch trình hợp lệ"81%Một phầnChưa — 19% hỏng vì đâu?
Tách theo failure modeVượt ngân sách 11% · chuyến bay đã hết chỗ 5% · khách sạn sai thành phố 3%Có — "vượt ngân sách" là lớn nhất, xem prompt có nêu ngân sách là ràng buộc cứng chưa
  1. Bắt đầu từ việc đọc ~50 lịch trình thật để thấy các kiểu hỏng (định tính).
  2. Mỗi kiểu hỏng thành một check riêng, có thể là code (tổng giá ≤ ngân sách) hoặc judge (hợp lý về di chuyển).
  3. Báo cáo tỉ lệ theo từng kiểu hỏng (định lượng) → ưu tiên sửa theo tỉ lệ × mức nghiêm trọng.

Bốn cách gắn eval vào hệ thống

Một app thường cần nhiều eval, mỗi cái đo một chiều. Chúng được gắn vào hệ thống theo 4 kiểu:

📡
Monitoring
background checks

Chạy nền liên tục, phát hiện drift và suy giảm chất lượng theo thời gian.

🛡️
Guardrails
critical path

Nằm ngay trên đường xử lý: chặn, thử lại hoặc fallback khi output không đạt.

🔧
Improvement
feedback signal

Sinh dữ liệu gán nhãn, few-shot examples, danh sách lỗi dẫn tới đổi kiến trúc.

🔀
Model migration
compare versions

So sánh các phiên bản model để quyết định lúc nào nên chuyển.

④ Model migration so model cũ/mới trên cùng bộ eval ③ Improvement nhãn, few-shot, đổi kiến trúc Request LLM Agent prompt + model ② Guardrail chặn · retry · fallback User Trace / log store mọi request, output, kết quả check ① Monitoring dashboard drift · alert lỗi tìm được
Hình tự vẽ: guardrail nằm trên đường chính; monitoring đọc log; lỗi tìm được nuôi improvement; migration so model trên cùng bộ eval.
Ví dụ 1.5 · Một evaluator, bốn vai trò (email agent)

Evaluator: "output JSON hợp lệ, có senderrequests không rỗng". Cùng một hàm check có thể được dùng theo cả 4 cách:

  1. Guardrail: chạy ngay sau mỗi lần gọi LLM; fail thì retry, fail tiếp thì fallback "chuyển người xử lý".
  2. Monitoring: log kết quả check; dashboard tỉ lệ fail theo ngày. Tỉ lệ nhảy từ 1% lên 6% sau một đợt email marketing mới → drift.
  3. Improvement: gom các input làm fail thành bộ ví dụ khó; chọn vài cái làm few-shot hoặc dữ liệu fine-tune.
  4. Model migration: nhà cung cấp ra model mới rẻ hơn 40% — chạy cả hai trên 1.000 email lưu sẵn, so tỉ lệ fail trước khi chuyển.
# guardrail.py — call_llm là giả lập: lần 1 trả JSON hỏng, lần 2 trả đúng
import json

def call_llm(prompt, attempt):
    return '{"sender": "Lan", "requests": ["báo giá"' if attempt == 0 else \
           '{"sender": "Lan", "requests": ["báo giá 200 áo trước thứ 6"]}'

def valid(output):
    """Evaluator dạng code: parse được JSON + đủ field + requests không rỗng."""
    try:
        data = json.loads(output)
    except json.JSONDecodeError:
        return False, "json_invalid"
    if not data.get("sender") or not data.get("requests"):
        return False, "missing_field"
    return True, "ok"

def guarded_extract(prompt, max_retries=2, log=print):
    for attempt in range(max_retries + 1):
        out = call_llm(prompt, attempt)
        ok, reason = valid(out)
        log(f"attempt={attempt} ok={ok} reason={reason}")   # log → monitoring
        if ok:
            return json.loads(out)
    return {"sender": None, "requests": [], "fallback": "chuyển người xử lý"}

print(guarded_extract("Trích tên người gửi và yêu cầu..."))
attempt=0 ok=False reason=json_invalid
attempt=1 ok=True reason=ok
{'sender': 'Lan', 'requests': ['báo giá 200 áo trước thứ 6']}

Hay nhầm: "guardrail = eval, có guardrail rồi khỏi cần eval"

Guardrail chỉ bắt được những lỗi đã biết và check được rẻ, nhanh (nằm trên đường chính nên phải nhanh). Lỗi ngữ nghĩa kiểu "bot bịa chính sách" thường cần judge chậm hơn hoặc người đọc — chạy offline/monitoring. Và guardrail chỉ tồn tại vì ai đó đã phân tích lỗi trước để biết cần chặn cái gì. Ngoài ra, pre-launch eval và production monitoring bắt những loại lỗi khác nhau: cần cả hai.
Bài tập 1.4 · Gắn nhãn vai trò
Mỗi tình huống dùng eval theo cách nào (Monitoring / Guardrail / Improvement / Migration)? (a) Mỗi đêm chấm ngẫu nhiên 200 hội thoại bằng LLM-judge, vẽ biểu đồ theo tuần. (b) Trước khi gửi SMS cho khách, check tin nhắn không chứa số tài khoản ngân hàng; nếu có thì chặn. (c) So Claude bản mới vs bản cũ trên 500 case trước khi đổi cấu hình. (d) Lấy 40 output bị người chấm đánh fail làm ví dụ negative trong prompt.
Xem lời giải (a) Monitoring. (b) Guardrail. (c) Model migration. (d) Improvement. Một hệ thống trưởng thành thường có cả bốn, dùng chung một số evaluator.

1.5Eval xuyên suốt vòng đời LLM

Eval xuất hiện ở cả ba giai đoạn, nhưng mục tiêu khác nhau — nên không có một framework eval dùng chung cho mọi nơi.

Pre-training Lab lớn · ~trăm triệu USD Eval intrinsic: perplexity, scaling laws Post-training SFT · preference · DPO · RLVR Benchmark gần tác vụ: QA, reasoning, safety, code Application ← BẠN ở đây Tự định nghĩa "thành công" cho use case của mình leaderboard ít giúp được
Mỗi giai đoạn có mục tiêu eval khác nhau.

Model nền được huấn luyện từ đầu với một mục tiêu duy nhất: đoán token kế tiếp. Token là đơn vị văn bản nhỏ nhất model đọc/viết mỗi lần — thường là một từ hoặc một mảnh từ; chi phí API cũng tính theo số token vào/ra.

Eval ở đây là intrinsic: model đoán token kế tiếp tốt cỡ nào trên văn bản chưa từng thấy. Metric tiêu biểu là perplexity — mũ của negative log-likelihood trung bình mỗi token; càng thấp, model càng ít "bất ngờ". Kèm theo là phân tích scaling laws (loss giảm thế nào khi tăng compute/data). Những con số này nói model đã hấp thụ thống kê ngôn ngữ — không nói model có hữu ích cho tác vụ của bạn.

Biến model nền thành model dùng được: làm theo chỉ dẫn, bám chủ đề, tránh nội dung nguy hiểm. Thường gồm fine-tune trên cặp prompt–câu trả lời tốt, rồi dùng preference data (người chọn câu nào tốt hơn) để nắn phong cách; các phương pháp mới hơn như DPO, process reward model, và RL với phần thưởng kiểm chứng được (RLVR) cho chuỗi suy luận dài.

Eval chuyển sang benchmark gần tác vụ người dùng (QA, reasoning, safety/bias, coding), do nhà cung cấp tự chạy và công bố. Sách lưu ý: biết model được post-train kiểu gì giúp đoán kiểu lỗi nó sẽ mắc trong app, dù bạn không tự huấn luyện.

Đây là chỗ hầu hết kỹ sư và PM làm việc: model đã có sẵn, việc của bạn là tích hợp vào workflow cụ thể (phân loại email, CSKH, phân tích tài liệu). Đôi khi benchmark công khai trùng tác vụ (trợ lý code ↔ benchmark lập trình), nhưng phần lớn app không có mặt trên leaderboard nào — duyệt hợp đồng, tóm tắt hồ sơ bệnh án, kiểm tra tuân thủ đều cần eval riêng.

→ Trách nhiệm định nghĩa thành công, thiết kế metric và giám sát thuộc về team làm ứng dụng.

Ví dụ 1.6 · Tính perplexity bằng tay (số minh hoạ)

Một câu held-out có 5 token. Model A và B gán xác suất cho token đúng ở mỗi vị trí:

Token12345
Model A0,500,250,800,100,60
Model B0,700,400,900,300,75
  1. Lấy −ln từng xác suất của A: 0,693 · 1,386 · 0,223 · 2,303 · 0,511 → tổng 5,116.
  2. Chia cho số token: NLL trung bình = 5,116 / 5 ≈ 1,023.
  3. Perplexity = e1,023 ≈ 2,78 — hiểu nôm na: A "phân vân" như đang chọn giữa ~2,8 token mỗi bước.
  4. Làm tương tự cho B: ≈ 1,78. B tốt hơn về mô hình hoá ngôn ngữ.
  5. Nhưng điều đó không nói B tóm tắt email của bạn tốt hơn, tuân thủ format JSON tốt hơn, hay ít bịa chính sách hơn.
import math
model_A = [0.50, 0.25, 0.80, 0.10, 0.60]
model_B = [0.70, 0.40, 0.90, 0.30, 0.75]

def perplexity(probs):
    nll = -sum(math.log(p) for p in probs) / len(probs)   # NLL trung bình / token
    return math.exp(nll), nll

for name, probs in [("A", model_A), ("B", model_B)]:
    ppl, nll = perplexity(probs)
    print(f"model {name}: NLL/token = {nll:.3f}  perplexity = {ppl:.2f}")
# model A: NLL/token = 1.023  perplexity = 2.78
# model B: NLL/token = 0.574  perplexity = 1.78

Hay nhầm: "model đứng đầu leaderboard sẽ tốt nhất cho app của tôi"

Leaderboard đo tác vụ của người khác trên phân bố dữ liệu của người khác. App của bạn có định dạng input riêng (email tiếng Việt lẫn tiếng Anh, hoá đơn scan), định nghĩa "đúng" riêng, và ràng buộc riêng (chi phí, latency). Một model kém hơn 3 điểm benchmark có thể tốt hơn hẳn cho bạn — hoặc ngược lại. Chỉ bộ eval trên dữ liệu của chính bạn mới trả lời được.
Bài tập 1.5
Sếp gửi link: "Model X vừa đạt SOTA trên benchmark reasoning, chuyển hết sang X đi". App của bạn là bot trích xuất thông tin từ hoá đơn VAT. Bạn trả lời thế nào, và cần gì để ra quyết định?
Xem lời giải Benchmark reasoning là eval giai đoạn post-training, không đo trích xuất hoá đơn. Đề xuất: đây là bài toán model migration — chạy X và model hiện tại trên bộ eval hoá đơn đang có (ví dụ 300 hoá đơn thật đã gán nhãn), so theo từng failure mode (sai tổng tiền, sai MST, sai ngày) + chi phí + latency. Nếu chưa có bộ eval đó thì chính đây là lý do phải xây.

1.6Ba khó khăn khi xây ở tầng ứng dụng

Ở tầng application bạn phải: quyết định sản phẩm cần đạt gì → dịch mục tiêu thành prompt/logic agent → kiểm tra output có chấp nhận được không. Mỗi bước có một cái bẫy riêng:

Mục tiêu mơ hồ lúc đầu

"Tóm tắt email" nghe đơn giản; chỉ khi đọc output mới lộ ra: chi tiết cỡ nào, format gì, loại trừ gì?

Chỉ dẫn có lỗ hổng

Prompt và logic bằng ngôn ngữ tự nhiên hiếm khi nói hết ý định → hành vi không nhất quán, thiếu sót.

Hành vi đổi trên dữ liệu mới

Chạy tốt trên vài test case ≠ chạy tốt trước sự đa dạng của input thật.

Ba cái bẫy này ứng gần như một–một với Ba Vực thẳm ở phần tiếp theo.

1.7Ba Vực thẳm (The Three Gulfs)

Sách dùng mô hình Three Gulfs (phỏng theo Shankar et al. 2025, lấy cảm hứng từ Norman 1988) để gọi tên ba chỗ việc phát triển hay gãy. Ví dụ xuyên suốt: agent xử lý email gửi tới một tổ chức — trích tên người gửi, tóm tắt yêu cầu, phân loại email.

Developer bạn Data input + output LLM Agent prompt + logic ① Comprehension "Mình có hiểu data không?" ② Specification "Mình có nói rõ ý chưa?" ③ Generalization "Nói rõ rồi, model có làm đúng?"
Ba vực nằm trên ba cạnh của tam giác Developer — Data — LLM Agent (vẽ lại theo ý, phỏng theo Shankar et al. 2025).

Developer ↔ Data. Không hiểu input thật trông ra sao thì bạn sẽ thiết kế cho một phiên bản tưởng tượng của bài toán.

  • Input thật: email forward, reply kèm thread trích dẫn dài, chữ ký nhiều ngôn ngữ, text dán từ PDF lộn xộn → LLM tóm tắt nguyên thread cũ thay vì tin mới, hoặc nhầm khối chữ ký là yêu cầu chính.
  • Output thật cũng thuộc vực này: thường ổn, nhưng thỉnh thoảng lấy nhầm tên khi email nhắc nhiều người, bỏ sót chi tiết trong email dài, chép "confidentiality notice" vào tóm tắt. Lỗi hiếm nhưng đủ để người dùng thấy hệ thống "không đáng tin".

Cách vượt: có chiến lược mô tả input output ở quy mô lớn mà không phải đọc hết từng cái bằng tay.

Developer ↔ LLM Agent. Ý định trong đầu chỉ được prompt/logic nắm bắt một cách lỏng lẻo; ngôn ngữ tự nhiên không chính xác, khe hở nhỏ trong câu chữ → khác biệt lớn trong hành vi.

Chỉ dẫn "trích tên người gửi và tóm tắt các yêu cầu chính" nghe rõ nhưng bỏ ngỏ: đoạn văn hay bullet? Tên hiển thị, địa chỉ email hay cả hai? Có tính yêu cầu ngầm? Ngắn cỡ nào?

Những lựa chọn bạn không nói ra, LLM sẽ tự đoán — và đoán không phải lúc nào cũng khớp ý bạn → hai lần chạy cho output khác format, khác độ chi tiết, xử lý edge case không nhất quán.

Cách vượt: làm kỳ vọng thành tường minh, bám sát data mà agent thực sự gặp.

Data ↔ LLM Agent. Hiểu data rồi, prompt rõ rồi — model vẫn sai trên một số input.

Đã nói rõ "lấy tên từ header"; đa số lần model làm đúng, nhưng khi thân email nhắc tới một người khác (ví dụ người nổi tiếng) thì model trả tên người đó. Hiểu biết về data và prompt đều không sai — model chỉ đơn giản áp dụng chỉ dẫn không nhất quán (có thể do thiên lệch từ dữ liệu huấn luyện).

Lỗi loại này thường chỉ lộ ra khi gặp đủ độ đa dạng của data thật, và không tránh được hoàn toàn dù model mạnh đến đâu.

Cách vượt: đo tần suất bằng evaluator; nếu cao thì can thiệp mạnh hơn (thêm ví dụ, retrieval tốt hơn, fine-tune, hoặc đổi kiến trúc).

① Comprehension — ví dụ, code, bài tập

Ví dụ 1.7 · "Profile" input trước khi viết prompt (minh hoạ)

Thay vì đọc 5 email mẫu sạch đẹp rồi viết prompt, bạn lấy mẫu email thật và đếm các đặc điểm cấu trúc bằng vài regex rẻ tiền. Mục tiêu không phải đo chất lượng, mà là biết "data trông như thế nào" để không thiết kế cho bài toán tưởng tượng.

  1. Lấy mẫu ngẫu nhiên (ở đây 8 email cho gọn; thực tế vài trăm).
  2. Liệt kê các đặc điểm nghi ngờ gây khó: forward, thread trích dẫn, chữ ký/disclaimer, tiếng Anh, rác từ PDF, nhiều yêu cầu trong một email.
  3. Đếm tỉ lệ. Đặc điểm nào ≥ 10% thì prompt phải xử lý tường minh và bộ test phải có đại diện.
  4. Đọc kỹ vài email của mỗi nhóm — con số chỉ chỉ đường, không thay việc đọc.
import re
from collections import Counter

EMAILS = [  # 8 email giả lập
    "Chào anh, em cần báo giá 200 áo đồng phục trước thứ 6. Cảm ơn, Lan",
    "---------- Forwarded message ----------\nFrom: Minh\nNhờ chị xử lý giúp đơn #8812",
    "Dạ em gửi lại hợp đồng.\n\n> On Mon, Hùng wrote:\n> Anh gửi bản nháp\n> > Em xem nhé",
    "Hi team, please confirm the invoice.\n--\nJohn Tran | Sales\nCONFIDENTIAL: This email is intended only for the recipient",
    "Anh ơi hủy giúp em đơn hôm qua",
    "Kính gửi Quý công ty,\nChúng tôi đề nghị hợp tác... (dán từ PDF)  Trang 1/3   Điều 1.1",
    "Re: Re: Re: lịch họp\n> > > thứ 3 được không\n> > được\n> ok chốt",
    "Gửi bộ phận CSKH: sản phẩm bị lỗi, tôi muốn đổi trả và được hoàn tiền phí ship",
]
FEATURES = {
    "forwarded": lambda e: "forwarded message" in e.lower(),
    "quoted_thread": lambda e: bool(re.search(r"^>", e, re.M)),
    "signature_or_disclaimer": lambda e: bool(re.search(r"^--\s*$|confidential", e, re.M | re.I)),
    "english": lambda e: bool(re.search(r"\b(please|the|hi)\b", e, re.I)),
    "pdf_artifact": lambda e: bool(re.search(r"Trang \d+/\d+|Điều \d", e)),
    "multi_request": lambda e: len(re.findall(r"\b(và|muốn|cần|nhờ)\b", e, re.I)) >= 2,
}
counts = Counter()
for e in EMAILS:
    for name, fn in FEATURES.items():
        counts[name] += fn(e)
print(f"{'đặc điểm':<26}{'số email':>9}{'tỉ lệ':>8}")
for name in FEATURES:
    print(f"{name:<26}{counts[name]:>9}{counts[name] / len(EMAILS):>8.0%}")
đặc điểm                   số email   tỉ lệ
forwarded                         1     12%
quoted_thread                     2     25%
signature_or_disclaimer           1     12%
english                           1     12%
pdf_artifact                      1     12%
multi_request                     1     12%

Kết luận (trên data minh hoạ): 1/4 email có thread trích dẫn → prompt phải nói rõ "chỉ tóm tắt tin nhắn mới nhất, bỏ phần trích dẫn", và bộ test phải có loại email này. Đây là cầu nối từ Comprehension sang Specification.

Hay nhầm: "Comprehension chỉ là hiểu input"

Vực này gồm cả output: bạn phải biết model thường thành công/thất bại theo những kiểu nào. Đọc 5 output đẹp rồi yên tâm là rơi đúng vào vực — lỗi xảy ra 3–5% chỉ lộ ra khi nhìn ở quy mô lớn.

② Specification — ví dụ, bài tập

Ví dụ 1.8 · Biến prompt v1 thành spec tường minh (tự soạn)

Prompt v1: Trích tên người gửi và tóm tắt yêu cầu trong email. Ta liệt kê mọi quyết định mà v1 để model tự đoán, rồi trả lời từng cái dựa trên data đã profile ở Ví dụ 1.7:

Quyết định bị bỏ ngỏModel có thể đoánSpec chọn (và lý do)
Format tóm tắtđoạn văn / bullet / 1 câuTối đa 3 bullet, mỗi bullet ≤ 15 từ — quản lý đọc lướt trên điện thoại
Tên người gửidisplay name / email / cả haiDisplay name từ header From; không có thì dùng phần trước @
Thread trích dẫntóm tắt cả threadChỉ tin mới nhất; phần > chỉ dùng làm ngữ cảnh
Yêu cầu ngầmcó / khôngCó, nhưng đánh dấu "(ngầm)" — ví dụ khách than giao trễ = ngầm muốn được bồi thường
Chữ ký, disclaimerđôi khi chép vàoBỏ hoàn toàn
Email không có yêu cầubịa ra một yêu cầuTrả requests: [], category FYI
  1. Mỗi dòng bảng là một quyết định sản phẩm, không phải chi tiết kỹ thuật — nên hỏi PM/chuyên gia domain khi không chắc.
  2. Spec phải bám data: dòng "thread trích dẫn" chỉ xuất hiện vì profile cho thấy 25% email có thread.
  3. Sau khi sửa, output khác format giữa các lần chạy gần như biến mất — dấu hiệu lỗi cũ là specification, không phải generalization.

Mẹo phân biệt Specification vs Generalization: "phép thử nhà thầu"

Đưa prompt (và đúng input đó) cho một người thông minh nhưng chưa từng biết dự án. Nếu họ cũng có thể làm "sai" theo cùng cách mà vẫn hợp lý với chỉ dẫn → lỗi specification (chỉ dẫn cho phép cách hiểu đó). Nếu mọi người đọc prompt đều thấy rõ đáp án đúng, mà model vẫn sai → generalization. (Đây là heuristic tự đề xuất, không phải của sách.)
Bài tập 1.7a
Chatbot CSKH của shop điện tử: prompt "Hãy hỗ trợ khách hàng lịch sự và hiệu quả." Khách: "Tai nghe mua 20 ngày bị rè, đổi được không, không thì tôi báo công an!" Liệt kê ít nhất 4 quyết định mà prompt đang để model tự đoán.
Xem lời giải (1) Chính sách đổi trả (bao nhiêu ngày, điều kiện) — model sẽ bịa nếu không có. (2) Khi nào escalate sang người thật (khách đe doạ/giận dữ?) hay chỉ xin lỗi. (3) Được hứa gì (đổi mới, hoàn tiền, voucher) và không được hứa gì. (4) Giọng điệu với khách giận (xin lỗi bao nhiêu, có nhắc lại lời đe doạ không). (5) Cần thu thập thông tin gì (mã đơn, ảnh/video lỗi). Sách cũng lấy ví dụ tương tự: bot xin lỗi khi lẽ ra phải chuyển người thật là đang theo chỉ dẫn sai.

③ Generalization — ví dụ, code, bài tập

Ví dụ 1.9 · Đo một lỗi generalization và vì sao 10 trace là chưa đủ

Sau khi spec đã ghi "bỏ hoàn toàn chữ ký/disclaimer", bạn vẫn thấy vài tóm tắt có câu "CONFIDENTIAL…". Prompt rõ ràng rồi → nghi generalization. Câu hỏi bây giờ là bao thường xuyên.

  1. Viết evaluator rẻ: tìm các cụm boilerplate trong tóm tắt (code, không cần LLM).
  2. Chạy trên 2.000 output (ở đây giả lập với tỉ lệ thật ~8%).
  3. Báo cáo tỉ lệ kèm khoảng tin cậy (Wilson 95%) để biết con số chắc cỡ nào.
  4. So với việc chỉ đọc 10 trace và thấy 2 lỗi: ước lượng "20%" nhưng khoảng tin cậy trải từ ~6% đến ~51% — gần như vô nghĩa.
import math, random

def wilson(k, n, z=1.96):
    """Khoảng tin cậy 95% Wilson cho tỉ lệ lỗi k/n."""
    if n == 0:
        return (0.0, 1.0)
    p = k / n
    denom = 1 + z * z / n
    center = (p + z * z / (2 * n)) / denom
    half = z * math.sqrt(p * (1 - p) / n + z * z / (4 * n * n)) / denom
    return max(0.0, center - half), min(1.0, center + half)

BOILERPLATE = ["confidential", "intended only for", "sent from my iphone", "bảo mật"]

def has_boilerplate(summary: str) -> bool:
    s = summary.lower()
    return any(b in s for b in BOILERPLATE)

random.seed(7)   # giả lập 2.000 tóm tắt, ~8% bị lọt disclaimer
summaries = [
    "Khách xin báo giá 200 áo. CONFIDENTIAL: intended only for recipient"
    if random.random() < 0.08 else "Khách xin báo giá 200 áo trước thứ 6."
    for _ in range(2000)
]
k = sum(has_boilerplate(s) for s in summaries)
lo, hi = wilson(k, len(summaries))
print(f"Lỗi boilerplate: {k}/{len(summaries)} = {k/len(summaries):.1%}  (95% CI {lo:.1%}–{hi:.1%})")
lo, hi = wilson(2, 10)
print(f"Chỉ 10 trace, 2 lỗi: 20%  (95% CI {lo:.1%}–{hi:.1%})")
Lỗi boilerplate: 180/2000 = 9.0%  (95% CI 7.8%–10.3%)
Chỉ 10 trace, 2 lỗi: 20%  (95% CI 5.7%–51.0%)

Khi biết lỗi ~9% và ổn định, bạn có cơ sở để chọn can thiệp mạnh hơn — ví dụ đổi kiến trúc: cắt chữ ký bằng code trước khi đưa email cho LLM, thay vì hi vọng model tự bỏ qua.

Hay nhầm: "lỗi generalization thì đợi model mới là hết"

Sách nói rõ: dù model tiến bộ tới đâu, vẫn luôn có input mà nó sai. Model mới có thể giảm tỉ lệ lỗi này nhưng tạo lỗi khác — nên đổi model cũng phải đi qua eval (model migration). Và nhiều lỗi generalization được giải quyết rẻ nhất bằng kiến trúc (lấy tên từ metadata, cắt chữ ký bằng regex) chứ không phải bằng model tốt hơn.
Bài tập 1.7b
Bạn đọc 20 trace của bot trích xuất hoá đơn và thấy 1 lỗi "lấy tổng trước thuế thay vì sau thuế", dù prompt ghi rõ "tổng SAU thuế". (a) Có nên kết luận tỉ lệ lỗi là 5%? (b) Bước tiếp theo?
Xem lời giải (a) Không. 1/20 cho khoảng tin cậy Wilson 95% khoảng 0,9%–23,6% — quá rộng. (b) Vì prompt đã rõ → nghi generalization. Viết evaluator (so số trích được với tổng tính lại từ các dòng hàng + VAT, hoặc với nhãn thật trên tập có nhãn), chạy trên vài trăm–vài nghìn hoá đơn, xem lỗi tập trung ở loại hoá đơn nào (ví dụ mẫu có dòng "Tổng trước thuế" nằm cuối). Rồi mới chọn cách sửa: thêm few-shot cho mẫu đó, hoặc hậu kiểm bằng code.

Cùng một khung, domain khác

Chatbot CSKHTrợ lý codeBot tư vấn bảo hiểm (tự soạn)
ComprehensionCâu hỏi đa ngôn ngữ, lỗi chính tả, hỏi nhiều ý một lúcYêu cầu mơ hồ, thiếu context, nhắc tới API nội bộ không có docsKhách dán nguyên hợp đồng 30 trang; hỏi về gói đã ngừng bán
SpecificationThế nào là trả lời "tốt"? Xin lỗi hay escalate lên người thật?Format output, cách xử lý lỗi, coding styleĐược phép so sánh với đối thủ không? Khi nào bắt buộc kèm câu miễn trừ?
GeneralizationTốt với câu hỏi billing, kém với câu hỏi sản phẩm ít gặpPython tốt; ngôn ngữ hiếm hoặc sửa nhiều file thì kémĐúng với gói phổ biến; nhầm điều khoản giữa hai gói tên gần giống nhau

Sách chốt ý: chiến lược eval cho mỗi app phụ thuộc vào vực nào là nguồn lỗi chính.

Vì sao khung này đáng giá

Nó cho team một từ vựng chung. Thay vì cãi nhau "model không chạy", bạn nói: "đây là lỗi specification — prompt chưa nói rõ" hoặc "đây là lỗi generalization — nói rõ rồi mà model áp dụng không nhất quán". Chẩn đoán đúng → chọn đúng cách sửa, nhanh hơn.
▶ LAB 1 · Gulf Classifier🎮

14 tình huống lỗi hư cấu từ nhiều domain. Chọn vực thẳm chính; nhận giải thích và điểm. Gợi ý: hỏi (1) team đã nhìn data thật chưa? (2) prompt/spec đã nói rõ chưa? (3) nói rõ rồi mà vẫn sai?

Câu 1/14 · Điểm 0
Chọn một đáp án…

1.8Vì sao eval LLM app khó

Mỗi app phải vượt lại cả ba vực trong bối cảnh riêng của nó — không có bộ eval "mua về dùng luôn". Và yêu cầu chỉ rõ dần sau khi tương tác với output thật, nên:

"Evaluation is not a one-time check."— Shankar & Husain, Chương 1 (trích nguyên văn, 1/3)

Khi bắt đầu đọc output (vượt vực Comprehension), bạn chắc chắn sẽ thấy lỗi. Câu hỏi tiếp theo luôn là: lỗi do spec chưa đủ, hay do generalization? Nếu prompt chưa nêu yêu cầu → sửa chỉ dẫn/thiết kế agent. Nếu prompt đã rõ mà model vẫn sai → dùng evaluator tự động để đo tần suất.

Người dùng tự vượt vực được bao nhiêu?

Trợ lý code — agency cao

Lập trình viên tự spec chính xác, đọc và debug được code sinh ra (comprehension), chạy test để lộ lỗi generalization (ví dụ lúc sinh được React app chạy, lúc không). Một phần gánh eval do người dùng tự làm.

Stylist cá nhân — agency thấp

Người dùng khó nói rõ yêu cầu → app phải tự hỏi lại (thời tiết? mức trang trọng?) để lấp vực spec. Và họ khó nhận ra lỗi generalization: gợi ý đồ đông thường tốt không có nghĩa đồ cưới mùa đông cũng tốt.

Quy tắc của sách: stakes càng cao — vì người dùng khó tự bắt lỗi, hoặc vì lỗi gây hậu quả lớn — thì quy trình lấp vực càng phải chặt. Hình dưới là cách tự vẽ để đặt app của bạn lên bản đồ:

Lỗi đắt · dễ bắt trợ lý viết migration DB (có test, review) sinh SQL báo cáo, người duyệt trước khi chạy → eval + dựa vào kiểm tra của user Lỗi đắt · khó bắt bot chính sách hãng bay, tư vấn y tế, router âm thầm hạ cấp model → eval chặt nhất + guardrail + monitoring Lỗi rẻ · dễ bắt autocomplete tên biến, gợi ý tiêu đề → check nhanh là đủ Lỗi rẻ · khó bắt stylist thời trang, gợi ý công thức nấu ăn → app hỏi lại + đo theo tình huống hiếm người dùng tự phát hiện lỗi: dễ → khó giá của một lỗi: thấp → cao
Hình tự vẽ minh hoạ ý "stakes" của sách. Càng lên trên–sang phải, eval càng phải chặt.
Bài tập 1.8
Đặt 3 app sau lên ma trận và nói vực nào đáng lo nhất: (a) bot trả lời câu hỏi về lương/thuế TNCN cho nhân viên; (b) tool sinh caption Instagram cho shop; (c) agent tự động trả lời email khách hàng mà không có người duyệt.
Xem lời giải (a) Đắt · khó bắt: nhân viên không tự kiểm tra được luật thuế → lo generalization (quy tắc hiếm, giảm trừ gia cảnh) và spec (khi nào phải chuyển HR). (b) Rẻ · dễ bắt: người dùng đọc caption trước khi đăng → check nhanh đủ. (c) Đắt · khó bắt vì không ai duyệt → cần guardrail trên đường chính + monitoring; vực comprehension đáng lo nhất vì email thật rất đa dạng.

1.9Vòng đời Analyze → Measure → Improve

Ba Vực thẳm gọi tên các kiểu hỏng; vòng lặp Analyze–Measure–Improve là quy trình làm các vực đó hiện ra và quyết định xử lý thế nào. Đây là xương sống của cả cuốn sách.

ANALYZE Đọc trace, tìm failure modes MEASURE Evaluator đo tần suất lỗi IMPROVE Sửa theo loại lỗi lặp lại liên tục trước & sau launch
Analyze gắn nhất với Comprehension; Measure định lượng lỗi Generalization; Improve chọn cách sửa theo loại lỗi.

Mục tiêu: nhận diện failure modes — chưa đếm.

Đọc trace (bản ghi đầy đủ agent đã làm gì cho một request), hỏi người dùng/chuyên gia domain "đúng" là gì, dựng danh sách các kiểu hỏng. Analyze gắn nhất với vực Comprehension, nhưng trong cùng một buổi đọc trace bạn thường phát hiện cả ba loại vấn đề. (→ Chương 3)

Sách Đọc một loạt tóm tắt email, thấy 2 lỗi lặp: chèn chữ ký không liên quan vào tóm tắt; bỏ sót yêu cầu quan trọng nhất.

Câu hỏi: mỗi failure mode xảy ra bao thường xuyên? Có đủ thấp để hệ thống dùng được?

Những lỗi rõ ràng là spec thì sửa luôn; Measure chủ yếu để định lượng lỗi generalization. Không đo thì bạn dựa vào ấn tượng từ vài trace — dễ sai cả hai chiều (vài ví dụ xấu làm hệ thống tốt trông hỏng; vài ví dụ đẹp làm hệ thống hỏng trông ổn). Công cụ: evaluator tự động chấm trace không cần người — rule bằng code, hoặc LLM-as-judge. (→ Chương 5)

Sách Dù đã làm rõ prompt, chữ ký vẫn lọt; check rule-based trên hàng nghìn output → 8%.

Chọn cách sửa theo nguồn lỗi:

  • Lỗi spec → siết prompt, sửa logic agent.
    Sách Bỏ sót yêu cầu vì prompt chưa nói mức chi tiết → yêu cầu tóm tắt thành 3 bullet ngắn.
  • Lỗi generalization → can thiệp mạnh hơn: thêm ví dụ, cải thiện retrieval, fine-tune trên data có nhãn, hoặc đổi kiến trúc.
    Sách Nhầm tên vẫn còn dù prompt rõ → lấy tên người gửi từ metadata thay vì để LLM đọc thân email.

Mỗi vòng giúp hiểu rõ hơn bạn đang đối mặt vực nào, và chọn mức nghiêm ngặt phù hợp. (→ Chương 12: chiến lược cải thiện theo mức công sức)

Ví dụ 1.10 · Một vòng A→M→I trọn vẹn cho email agent (số minh hoạ)
  1. Analyze — lấy ngẫu nhiên 120 trace của tuần qua, mỗi trace ghi 1 dòng ghi chú tự do ("tóm tắt cả thread cũ", "tên sai: lấy 'Sơn Tùng' trong thân mail", "3 bullet nhưng bỏ mất deadline"…). Gom ghi chú thành 5 failure mode.
  2. Phân loại vực — với mỗi mode hỏi "prompt đã nói chưa?": F1 tóm tắt cả thread (spec — chưa nói), F2 format lộn xộn (spec), F3 bỏ sót deadline (spec — chưa nói deadline là bắt buộc), F4 lọt chữ ký (generalization — đã nói), F5 nhầm tên (generalization — đã nói "lấy từ header").
  3. Improve lần 1 (spec) — sửa prompt cho F1–F3 ngay, không cần đo trước: nói rõ "chỉ tin mới nhất", "3 bullet", "luôn nêu deadline nếu có".
  4. Measure (generalization) — evaluator code cho F4 (tìm cụm boilerplate), F5 (so tên trả về với header From). Trên 2.000 email: F4 = 8,5%, F5 = 1,2%.
  5. Improve lần 2 — F4 cao → đổi kiến trúc: cắt chữ ký bằng regex trước khi gọi LLM → đo lại còn 0,6%. F5 → lấy tên từ metadata, LLM không còn quyết định → 0%.
  6. Quay lại Analyze — đọc 120 trace mới sau khi sửa: xuất hiện mode mới F6 "email tiếng Anh bị tóm tắt bằng tiếng Anh" (spec chưa nói ngôn ngữ output). Vòng lặp tiếp tục.

Hay nhầm trong vòng lặp

  • "Analyze là đếm lỗi." Không — Analyze chỉ nhận diện và gọi tên; đếm là việc của Measure. Đếm trước khi biết có những kiểu lỗi nào thì chỉ ra con số vô nghĩa kiểu "helpfulness 7,4/10".
  • "Phải xây evaluator cho mọi lỗi." Lỗi spec hiển nhiên thì sửa luôn, rẻ hơn. Đầu tư evaluator cho lỗi còn tồn tại khi spec đã rõ.
  • "Sửa xong là xong." Mỗi lần sửa có thể sinh lỗi mới (F6 ở trên) → quay lại Analyze.
  • "Có evaluator rồi khỏi đọc trace." Evaluator chỉ đo những gì bạn đã biết để đo; lỗi mới chỉ lộ ra khi người đọc trace.
Bài tập 1.9
Một bot tóm tắt hồ sơ bệnh án. Sau Analyze bạn có: (a) đôi khi dùng viết tắt mà bác sĩ khoa khác không hiểu — prompt không nói gì về viết tắt; (b) prompt ghi "luôn liệt kê TẤT CẢ thuốc đang dùng", nhưng với hồ sơ dài model bỏ sót thuốc; (c) tóm tắt dài 1 trang trong khi bác sĩ muốn ≤ 5 dòng — prompt không nói độ dài. Với mỗi lỗi: vực nào, bước tiếp theo?
Xem lời giải (a) Specification → hỏi bác sĩ quy ước viết tắt, ghi vào prompt. (b) Generalization → Measure trước: evaluator so danh sách thuốc trích được với trường thuốc có cấu trúc trong EHR, tách theo độ dài hồ sơ; nếu lỗi tập trung ở hồ sơ dài → chia nhỏ (chunk) hoặc trích danh sách thuốc bằng bước riêng/bằng code. Stakes cao (y tế) → cần tỉ lệ rất thấp và có thể thêm guardrail. (c) Specification → ghi "≤ 5 dòng". Lưu ý (a)(c) sửa ngay, không cần evaluator trước.
▶ LAB 2 · Analyze→Measure→Improve Simulator🕹️

Chọn một tình huống, rồi quyết định bước tiếp theo qua 3 lượt. Mỗi lựa chọn có hậu quả; lựa chọn đúng quy trình được nhiều điểm hơn.

Bấm "Bắt đầu".
▶ LAB 3 · Vài trace đánh lừa bạn thế nào🎲

Đặt tỉ lệ lỗi thật của hệ thống và số trace bạn đọc. Bấm "Rút mẫu" để mô phỏng 20 người khác nhau, mỗi người đọc n trace ngẫu nhiên — xem ước lượng của họ tản mát thế nào.

1.10Case study: bot CSKH "ShopMate" qua 3 vòng lặp

Case study · ShopMate — trợ lý CSKH cho một shop mỹ phẩm online (hư cấu, số liệu minh hoạ)

Bối cảnh. Shop bán mỹ phẩm qua website và Zalo, ~3.000 tin nhắn khách/ngày. Team 2 kỹ sư dựng bot trả lời tự động bằng một LLM + tài liệu chính sách (đổi trả, freeship, xuất xứ). Tuần đầu, chủ shop nói: "Khách phàn nàn bot trả lời linh tinh". Không ai định nghĩa "linh tinh" là gì.

Vòng 1 — Tuần 1

  1. Analyze. Lấy mẫu 150 hội thoại (phân tầng: 50 có khách phàn nàn, 100 ngẫu nhiên). Mỗi hội thoại một dòng ghi chú. Gom thành 6 failure mode: M1 hứa freeship cho đơn dưới ngưỡng; M2 trả lời tiếng Anh khi khách gõ tiếng Việt không dấu; M3 không chuyển người thật khi khách đòi "bóc phốt"; M4 bịa xuất xứ sản phẩm; M5 trả lời quá dài trên Zalo; M6 không hỏi mã đơn khi khách khiếu nại giao hàng.
  2. Phân vực. Kiểm prompt: không nhắc ngưỡng freeship (M1 → spec), không có quy tắc escalate (M3 → spec), không giới hạn độ dài (M5 → spec), không yêu cầu hỏi mã đơn (M6 → spec). Prompt đã ghi "trả lời bằng ngôn ngữ của khách" (M2 → generalization) và "chỉ dùng thông tin trong tài liệu" (M4 → generalization).
  3. Improve (spec). Viết lại prompt: ngưỡng freeship 300k, quy tắc escalate (đe doạ, đòi hoàn tiền > 1 triệu, nhắc tới dị ứng), ≤ 4 câu trên Zalo, luôn hỏi mã đơn khi khiếu nại giao hàng.

Vòng 2 — Tuần 2

  1. Measure. Evaluator: M2 = code (phát hiện ngôn ngữ câu trả lời vs câu hỏi, có xử lý tiếng Việt không dấu); M4 = LLM-judge "mọi thông tin xuất xứ có trong tài liệu?" (đã được đối chiếu với 60 nhãn người chấm). Chạy trên 2.000 hội thoại.
  2. Kết quả.
ModeVựcTỉ lệGhi chú
M2 sai ngôn ngữGeneralization6,1% (trên hội thoại không dấu: 19%)Tập trung ở tin nhắn ngắn không dấu, ví dụ "ship ko", "con hang k"
M4 bịa xuất xứGeneralization2,3%Chủ yếu sản phẩm mới chưa có trong tài liệu
M1, M3, M5, M6Spec (đã sửa)< 0,5%Kiểm lại bằng check nhanh: sửa spec đã hiệu quả
  1. Improve (generalization). M2: thêm bước khôi phục dấu tiếng Việt trước khi gọi LLM + 6 few-shot không dấu → đo lại 0,9%. M4: đây thực ra lộ ra một lỗi application — tài liệu không được cập nhật khi thêm sản phẩm; sửa pipeline đồng bộ tài liệu, và thêm guardrail: nếu câu trả lời nhắc xuất xứ mà judge không tìm thấy nguồn → thay bằng "để em kiểm tra và báo lại ạ" + chuyển người.

Vòng 3 — Tuần 4 (sau launch rộng)

  1. Monitoring cho thấy tỉ lệ escalate tăng từ 4% lên 11% trong đợt sale 9/9. Analyze lại 100 hội thoại đợt sale: mode mới M7 — khách hỏi mã giảm giá chồng nhau, bot không biết quy tắc cộng dồn (spec chưa có) → escalate. Sửa spec, thêm bảng quy tắc voucher.
  2. Model migration. Nhà cung cấp ra model rẻ hơn 50%. Chạy bộ eval đã tích luỹ (2.000 hội thoại + evaluator M1–M7) cho cả hai: model mới tốt ngang ở M1–M6 nhưng M2 tăng lên 3,4% → giữ model cũ cho hội thoại không dấu, dùng model mới cho phần còn lại (một dạng routing đơn giản).

Bài học rút ra

  • "Bot trả lời linh tinh" → sau vòng 1 thành 6 failure mode có tên; 4/6 là spec và được sửa trong một buổi chiều.
  • Chỉ 2 mode cần evaluator. Đầu tư đo có trọng tâm, không đo đại trà.
  • Có lỗi tưởng là agent (M4) nhưng gốc lại ở application (tài liệu lỗi thời) — trace đầy đủ mới cho thấy.
  • Bộ eval tích luỹ qua các vòng chính là thứ cho phép quyết định migration trong một ngày thay vì "đổi thử rồi xem khách có kêu không".
Bài tập 1.10 · Mở rộng case study
Ở vòng 3, nếu bạn không có monitoring mà chỉ có pre-launch eval, M7 sẽ bị phát hiện khi nào và bằng cách nào? Rút ra điều gì về vai trò của từng loại eval?
Xem lời giải M7 chỉ xuất hiện khi phân bố input đổi (đợt sale) — pre-launch eval không có loại câu hỏi này nên không bắt được. Không có monitoring thì M7 lộ ra qua phàn nàn của khách hoặc doanh thu giảm, trễ nhiều ngày. Đúng như sách nói: pre-launch eval và production monitoring bắt những loại lỗi khác nhau; cần cả hai. Sau khi phát hiện, nên thêm hội thoại voucher vào bộ eval pre-launch để lần sau regression được bắt sớm.

1.11Checklist chẩn đoán khi app LLM hỏng

Khi gặp app hỏng, câu hỏi đầu tiên: lỗi này thuộc vực nào? Sơ đồ quyết định dưới đây là cách tự vẽ lại checklist của sách:

Thấy output lỗi Đã đọc đủ trace, hiểu input và hành vi hệ thống? Prompt / logic đã nêu rõ yêu cầu bị vi phạm? ① Comprehension đọc thêm trace, chưa sửa ② Specification sửa prompt / logic ③ Generalization đo tần suất → can thiệp mạnh hơn Sau mỗi lần sửa: quay lại Analyze chưa chưa rồi rồi
Hình tự vẽ dựa trên checklist cuối chương của sách.
  1. Comprehension? Chưa hiểu input trông ra sao hoặc hệ thống thực sự xử lý thế nào → đọc thêm trace trước khi đổi bất cứ thứ gì.
  2. Specification? Yêu cầu chưa đủ tường minh để LLM làm theo → sửa prompt hoặc logic agent.
  3. Generalization? Yêu cầu rõ mà model vẫn sai trên một số input → đo tần suất, rồi cân nhắc can thiệp mạnh hơn (thêm ví dụ, retrieval tốt hơn, fine-tune).

Vòng Analyze–Measure–Improve biến chẩn đoán này thành quy trình lặp lại được: phân tích trace để lộ failure mode, đo mức phổ biến, sửa, rồi đưa kết quả vào vòng phân tích tiếp theo. Chương sau bắt đầu bằng bước thực tế đầu tiên: dựng prototype và viết prompt ban đầu — phải có thứ gì đó chạy được, dù thô, mới phân tích được.

1.12Áp dụng cho dự án model-routing

Bối cảnh. Router nhận mỗi request LLM và chọn model phù hợp (ví dụ small / medium / large), cân bằng chất lượng – chi phí – latency. Router là một "agent" theo nghĩa rộng của sách: nó ra quyết định, và quyết định có thể sai. Điểm nguy hiểm: lỗi router thường im lặng — người dùng thấy câu trả lời hơi kém mà không biết vì bị hạ cấp model, còn dashboard chi phí thì trông rất đẹp. Theo ma trận stakes ở 1.8, đó là góc "lỗi đắt · khó bắt".

Ba Vực thẳm của một router

VựcCâu hỏi cho routerDấu hiệu bạn đang rơi vào
ComprehensionPhân bố request thật ra sao — loại tác vụ, độ dài, ngôn ngữ, độ khó, tỉ lệ multi-turn? Router đang chọn gì cho từng nhóm?Bạn chỉ biết "60% request đi small" mà không biết 60% đó là những request nào.
Specification"Route đúng" là gì? Ngưỡng chất lượng tối thiểu theo loại tác vụ, ngân sách/request, SLO latency p95, thứ tự ưu tiên khi xung đột.PM nói router "tiết kiệm quá tay", kỹ sư nói "vẫn ổn" — không ai sai vì chưa có định nghĩa.
GeneralizationSpec đã rõ, classifier/luật vẫn chọn sai trên một số loại request (hiếm, đa ngôn ngữ, câu ngắn nhưng khó)?Spec ghi "toán nhiều bước → large", nhưng bài toán viết bằng lời văn tiếng Việt vẫn bị gửi small.
Mẫu request phân tầng từ log Router chọn model Output của model được chọn Metric router chất lượng · cost · p95 under/over-route · regret Chạy CẢ small / medium / large judge + rubric chấm từng output (offline) Oracle label model rẻ nhất đạt ngưỡng so sánh counterfactual
Hình tự vẽ: eval router cần biết "nếu gửi model khác thì sao" → chạy mọi model offline trên cùng mẫu request, rồi so quyết định router với oracle.

Quy trình từng bước

  1. Analyze trace routing thật. Lấy mẫu phân tầng 200–300 request (theo loại tác vụ, độ dài, ngôn ngữ, và cả theo model được chọn). Mỗi trace xem: request, model được chọn, output, latency, chi phí. Ghi chú tự do những lần route "đáng ngờ": câu hỏi code nhiều file đi small; chitchat đi large; request tiếng Việt dài bị escalate vô lý… Gom thành failure mode của router.
  2. Viết "routing contract" (spec). Không có spec thì không có "route sai". Ví dụ spec tự soạn:
    quality_bar:            # điểm judge tối thiểu (0-1) theo loại tác vụ
      chitchat: 0.7
      extract:  0.85
      code:     0.8
      legal:    0.9         # stakes cao → ngưỡng cao
    budget:
      avg_cost_per_1k_req: 3.0 USD
    latency:
      p95_ms: 4000
    tie_break: "quality_bar > latency > cost"
    oracle: "model rẻ nhất có điểm ≥ quality_bar; nếu không có → model tốt nhất"
  3. Measure offline (counterfactual). Chạy mọi model ứng viên trên cùng tập eval (vài trăm–vài nghìn request đã ẩn danh), chấm bằng judge đã được căn chỉnh với nhãn người (Chương 5). Từ đó tính oracle và so với quyết định của router. Chạy mỗi cấu hình vài lần vì output LLM không deterministic.
  4. Tách lỗi theo vực rồi mới sửa. Spec lỗi (ngưỡng sai, loại tác vụ thiếu) → sửa contract/luật. Generalization (classifier nhầm dù luật đúng) → thêm dữ liệu huấn luyện router cho nhóm yếu, thêm feature (độ dài, có code block, ngôn ngữ), hiệu chỉnh ngưỡng, hoặc đổi kiến trúc sang cascade (thử small → check → escalate).
  5. Gắn eval theo cả 4 cách (bảng dưới) và lặp lại vòng A→M→I mỗi khi đổi model pool, đổi luật, hoặc phân bố traffic dịch chuyển.

Metric nên có

MetricĐịnh nghĩaVì sao cần
Quality retentionchất lượng TB của router / chất lượng TB nếu luôn dùng model mạnh nhấtCâu hỏi "mất bao nhiêu chất lượng?"
Cost savings1 − chi phí router / chi phí always-large (tính theo token thật, không theo giá niêm yết × số request)Câu hỏi "được bao nhiêu tiền?"
Under-route rate% request router chọn model yếu hơn oracleLỗi chất lượng im lặng — nguy hiểm nhất
Over-route rate% request router chọn model mạnh hơn oracleTiền lãng phí
Below-bar rate% request có output dưới quality_barGắn thẳng với spec; báo cáo theo từng loại tác vụ
Latency p95kể cả thời gian escalate/retryCascade tiết kiệm tiền nhưng có thể phá SLO
Ví dụ 1.11 · Tính metric router trên 8 request (số minh hoạ)
# routing_eval.py — mỗi request đã chạy qua CẢ 3 model, có điểm judge (0-1)
PRICE = {"small": 0.2, "medium": 1.0, "large": 5.0}   # $ / 1.000 request (minh hoạ)
QUALITY_BAR = 0.8
DATA = [  # (id, loại, điểm từng model, model router đã chọn)
    ("r1", "chitchat", {"small": 0.95, "medium": 0.96, "large": 0.97}, "small"),
    ("r2", "extract",  {"small": 0.85, "medium": 0.90, "large": 0.92}, "medium"),
    ("r3", "code",     {"small": 0.40, "medium": 0.82, "large": 0.93}, "medium"),
    ("r4", "math",     {"small": 0.30, "medium": 0.55, "large": 0.90}, "medium"),
    ("r5", "summarize",{"small": 0.88, "medium": 0.90, "large": 0.91}, "large"),
    ("r6", "legal",    {"small": 0.20, "medium": 0.60, "large": 0.85}, "large"),
    ("r7", "chitchat", {"small": 0.93, "medium": 0.94, "large": 0.95}, "small"),
    ("r8", "code",     {"small": 0.35, "medium": 0.70, "large": 0.88}, "small"),
]
ORDER = ["small", "medium", "large"]

def oracle(scores):
    ok = [m for m in ORDER if scores[m] >= QUALITY_BAR]
    return ok[0] if ok else max(scores, key=scores.get)

n = len(DATA)
router_q = sum(s[c] for _, _, s, c in DATA) / n
large_q = sum(s["large"] for _, _, s, _ in DATA) / n
router_cost = sum(PRICE[c] for *_, c in DATA)
large_cost = PRICE["large"] * n
under = [r for r, _, s, c in DATA if ORDER.index(c) < ORDER.index(oracle(s))]
over = [r for r, _, s, c in DATA if ORDER.index(c) > ORDER.index(oracle(s))]
below = [r for r, _, s, c in DATA if s[c] < QUALITY_BAR]
print(f"Chất lượng TB  router={router_q:.3f}  always-large={large_q:.3f}  (giữ lại {router_q / large_q:.0%})")
print(f"Chi phí        router=${router_cost:.1f}  always-large=${large_cost:.1f}  (tiết kiệm {1 - router_cost / large_cost:.0%})")
print(f"Under-route: {under}   Over-route: {over}")
print(f"Dưới ngưỡng: {below} = {len(below) / n:.0%}")
Chất lượng TB  router=0.782  always-large=0.914  (giữ lại 86%)
Chi phí        router=$13.6  always-large=$40.0  (tiết kiệm 66%)
Under-route: ['r4', 'r8']   Over-route: ['r2', 'r5']
Dưới ngưỡng: ['r4', 'r8'] = 25%
  1. Tiêu đề đẹp: tiết kiệm 66% chi phí, giữ 86% chất lượng. Nếu chỉ nhìn hai số này, dễ kết luận "router tốt".
  2. Nhìn theo failure mode: 2/8 request dưới ngưỡng, và cả hai là under-route ở loại toán/code — đúng nhóm người dùng khó tự phát hiện là mình bị hạ cấp.
  3. Over-route r2, r5 chỉ tốn tiền (r5 dùng large cho tóm tắt mà small đã đạt 0,88).
  4. Phân vực: nếu contract chưa định nghĩa "math" là một loại riêng → r4 là lỗi spec. Nếu đã định nghĩa mà classifier vẫn nhầm → generalization, cần đo trên nhiều request toán hơn (8 request thì CI rất rộng — xem LAB 3).
  5. Hành động: ưu tiên giảm under-route cho code/math (stakes cao hơn) trước khi tối ưu over-route.

Bốn cách gắn eval vào router

Monitoring

Dashboard tỉ lệ request theo model, theo loại tác vụ; alert khi mix đổi đột ngột; chấm mẫu hằng ngày below-bar rate theo model được chọn.

Guardrail

Cascade: small trả lời → check rẻ (JSON hợp lệ? code compile? judge nhanh) → fail thì escalate lên large. Nhớ tính latency cộng thêm.

Improvement

Mỗi request oracle ≠ router là một nhãn huấn luyện cho classifier router; nhóm under-route thành tập test khó.

Model migration

Model mới vào pool → chạy lại ma trận offline (mọi model × tập eval), tính lại oracle và ngưỡng; không "cắm vào rồi xem".

Cạm bẫy thường gặp

▶ LAB 4 · Router Threshold Explorer🔀

40 request hư cấu, mỗi cái có "độ khó thật". Router ước lượng độ khó (có nhiễu) và gửi sang large nếu ước lượng ≥ ngưỡng, còn lại small. Kéo ngưỡng và độ nhiễu để thấy trade-off chất lượng–chi phí và under-route (quality bar = 0,8; giá large = 10× small).

Bài tập 1.12 · Thiết kế eval router
Router của bạn có 3 model và traffic gồm: 50% chitchat, 25% trích xuất JSON, 15% code, 10% phân tích tài liệu pháp lý. Ngân sách chạy eval offline chỉ đủ cho 1.200 lượt gọi model. (a) Bạn lấy mẫu bao nhiêu request, phân bổ thế nào? (b) Với nhóm nào bạn cần evaluator chặt nhất và vì sao?
Xem lời giải (a) 1.200 lượt / 3 model = 400 request. Không lấy mẫu theo tỉ lệ traffic (sẽ chỉ có 40 request pháp lý) mà phân tầng có trọng số theo rủi ro, ví dụ: chitchat 80, JSON 100, code 110, pháp lý 110. Khi báo cáo tổng thể thì trọng số lại theo tỉ lệ traffic thật. (b) Pháp lý và code: stakes cao và người dùng khó tự phát hiện bị hạ cấp; JSON có thể check phần lớn bằng code (schema). Chitchat dùng judge nhẹ là đủ. Có thể tiết kiệm thêm: với chitchat, nếu small đã đạt ngưỡng thì không cần chạy medium (oracle đã xác định).

1.13Nối với các chương sau

Khái niệm ở Chương 1Được đào sâu ởBạn sẽ học thêm
Agent theo nghĩa rộng, prototype đầu tiênChương 2Các dạng agent, cách viết prompt ban đầu, eval cơ bản
Analyze, đọc trace, failure modeChương 3–4Error analysis có hệ thống, làm cùng chuyên gia domain
Measure, evaluator, LLM-as-judgeChương 5Xây và căn chỉnh evaluator tự động
Generalization trong hội thoại, RAG, toolChương 6–8Eval multi-turn, retrieval, gọi tool và agent phức tạp
Pre-launch vs production monitoringChương 9–11CI/CD, giao diện review, phân tích dữ liệu trace
Improve theo mức công sứcChương 12Từ sửa prompt tới thay đổi ở mức model

Tự kiểm tra

1. Prompt đã ghi rõ "lấy tên từ header", nhưng thỉnh thoảng model vẫn trả tên người nổi tiếng trong thân email. Đây là vực nào?
Generalization. Spec đã rõ; model áp dụng không nhất quán. Cách đúng: đo tần suất, rồi cân nhắc đổi kiến trúc (lấy từ metadata) hoặc thêm ví dụ.
2. Tóm tắt lúc là đoạn văn, lúc là bullet list; prompt không nhắc format. Vực nào?
Specification. Prompt không nói → LLM tự đoán mỗi lần một kiểu. Sửa prompt, không cần đo trước.
3. Vì sao không nên "sửa prompt" ngay khi thấy vài output tệ?
Có thể bạn vẫn đang ở vực Comprehension — vài ví dụ không đại diện. Đọc thêm trace (và đo tần suất nếu cần) để biết lỗi hiếm hay có hệ thống, và thuộc vực nào.
4. Pha Analyze có đếm tần suất lỗi không?
Không. Analyze chỉ nhận diện và gọi tên failure mode; đếm là việc của Measure.
5. Khác biệt giữa "agent" và "application" theo sách? Cho một ví dụ lỗi mỗi loại.
Agent = phần do LLM điều khiển (suy luận, gọi tool, sinh output) — ví dụ gọi sai tool. Application = mọi thứ quanh nó (UI, DB, API, hạ tầng) — ví dụ phản hồi chậm vì DB. Sách tập trung eval tầng agent.
6. Kể tên 4 cách gắn eval vào hệ thống. Cách nào nằm trên đường xử lý chính?
Monitoring, Guardrails, Improvement, Model migration. Guardrails nằm trên critical path: chặn, retry hoặc fallback khi output không đạt.
7. Perplexity thấp có nghĩa model sẽ tốt cho app trích xuất hoá đơn của bạn không?
Không nhất thiết. Perplexity là eval intrinsic ở giai đoạn pre-training: đo khả năng đoán token kế tiếp, không đo tính hữu ích cho tác vụ cụ thể. App phải tự định nghĩa và đo "thành công".
8. Đọc 10 trace thấy 0 lỗi. Có thể kết luận tỉ lệ lỗi ~0% không?
Không. Với 0/10, khoảng tin cậy 95% (Wilson) lên tới ~28%; ngay cả 0/30 vẫn lên tới ~11%. Vài ví dụ đẹp có thể làm hệ thống hỏng trông ổn — đó là lý do cần Measure bằng evaluator trên nhiều trace.
9. Vì sao app stylist cá nhân cần quy trình eval chặt hơn trợ lý code, xét theo Ba Vực?
Người dùng stylist khó nói rõ yêu cầu (vực spec — app phải tự hỏi lại) và khó nhận ra lỗi generalization (tốt với đồ đông thường không có nghĩa tốt với đồ cưới mùa đông). Lập trình viên thì tự spec, đọc code và chạy test — tự lấp một phần vực.
10. Router của bạn tiết kiệm 60% chi phí và giữ 90% chất lượng trung bình. Còn thiếu thông tin gì trước khi kết luận nó tốt?
Phân tách theo loại tác vụ: under-route ratebelow-bar rate cho từng nhóm (nhóm nhỏ stakes cao có thể bị hạ cấp nặng mà trung bình che mất); spec "đủ tốt" là gì; latency p95; khoảng tin cậy/số lần chạy; và tập eval có phải traffic thật không.