Menu
ESC

Nhập từ khóa để tìm kiếm

↑↓ Di chuyển
Enter Mở
ESC Đóng

Đang tải...

Bài 27 — Testing AI products — Unit, integration, eval

Building AI-Powered Products Bài 27/60

Mở đầu — vì sao bài này quan trọng

Nếu bạn từng viết phần mềm truyền thống, bạn quen với một sự thật an tâm: cùng một input sẽ luôn cho ra cùng một output. Hàm tinh_tong(2, 3) luôn trả về 5. Bạn viết một unit test khẳng định điều đó, test xanh, xong. Sản phẩm AI phá vỡ hoàn toàn niềm tin này. Cùng một câu hỏi gửi cho GPT-4 hay Claude hai lần liên tiếp có thể ra hai câu trả lời khác nhau về câu chữ, thậm chí khác nhau về nội dung. Model được nhà cung cấp cập nhật âm thầm, và bản gpt-4o tháng này có thể hành xử khác bản tháng trước. Dữ liệu người dùng nhập vào thì vô cùng đa dạng, không thể liệt kê hết.

Đây chính là lý do rất nhiều đội làm sản phẩm AI ở Việt Nam rơi vào một trong hai cái bẫy. Bẫy thứ nhất: không test gì cả, cứ "thấy chạy được là ship", rồi một ngày đẹp trời chatbot chăm sóc khách hàng bịa ra chính sách hoàn tiền không tồn tại, gây thiệt hại thật. Bẫy thứ hai: cố ép AI vào khuôn test truyền thống, viết assert output == "câu trả lời mong đợi", và test đỏ liên tục dù sản phẩm vẫn hoạt động tốt, khiến cả đội mất niềm tin vào bộ test.

Bài này dạy bạn con đường thứ ba: một tư duy testing phù hợp riêng cho sản phẩm AI. Bạn sẽ hiểu tháp testing dành cho AI, phân biệt rõ đâu là unit test, integration test, và eval — ba tầng khác nhau về mục đích, tốc độ và cách chấm điểm. Nắm được điều này, bạn sẽ ship AI tự tin hơn nhiều, và quan trọng nhất là bắt lỗi trước khi người dùng bắt được bạn.

Khái niệm cốt lõi

Tháp testing dành cho sản phẩm AI

Trong phần mềm truyền thống có "tháp testing" cổ điển: nhiều unit test ở dưới đáy (nhanh, rẻ), ít integration test ở giữa, và rất ít end-to-end test ở đỉnh (chậm, đắt). Với sản phẩm AI, chúng ta giữ nguyên tinh thần đó nhưng thêm một tầng đặc thù. Hãy hình dung tháp từ dưới lên:

  • Đáy — Unit tests: kiểm tra phần code tất định (deterministic) bao quanh model. Đây là phần không gọi tới AI: hàm cắt văn bản thành chunk, hàm ghép prompt template, hàm parse JSON mà model trả về, hàm tính toán logic nghiệp vụ. Chạy trong mili-giây, chạy hàng nghìn lần mỗi lần commit.
  • Giữa — Integration tests: kiểm tra sự phối hợp giữa các thành phần. Ví dụ pipeline RAG: từ câu hỏi → truy vấn vector database → lấy được đúng tài liệu → nhét vào prompt → gọi model → nhận kết quả. Ở tầng này ta thường mock (giả lập) lời gọi model để test luồng dữ liệu, và test riêng phần "kết nối thật" với số lượng ít hơn.
  • Trên — Eval suites: đây là tầng đặc thù của AI, không tồn tại trong test truyền thống. Eval là một bộ đề thi cho model: một tập các ví dụ đầu vào cùng tiêu chí chấm điểm, dùng để đo chất lượng đầu ra chứ không phải đúng/sai tuyệt đối. Chạy chậm hơn, tốn tiền hơn (mỗi lần chạy là gọi API thật), nên chạy theo lịch chứ không phải mỗi commit.
  • Đỉnh — User testing: con người thật dùng sản phẩm thật. Ít nhất về số lượng nhưng giá trị tín hiệu cao nhất, vì nó bắt được những vấn đề mà không bộ eval nào lường trước.
Điểm mấu chốt cần khắc cốt: unit và integration test hỏi "code có đúng không?", còn eval hỏi "model có tốt không?". Hai câu hỏi hoàn toàn khác nhau và cần công cụ khác nhau.

Unit tests — kiểm tra vỏ code quanh model

Sai lầm phổ biến nhất của người mới là nghĩ "AI thì không unit test được". Sai. Có rất nhiều thứ tất định quanh model, và chính chúng thường là nơi bug ẩn nấp. Bạn có thể và nên unit test:

  • Prompt assembly: khi bạn ghép biến người dùng vào template, có bị lỗi thiếu dấu, chèn sai chỗ, hay để lộ instruction hệ thống không?
  • Output parsing: model trả về text, bạn parse thành JSON hoặc trích số. Nếu model trả về `json {...} ` với dấu markdown, hàm parse của bạn có xử lý được không? Nếu model trả về JSON thiếu một trường, code có sập không?
  • Chunking & retrieval logic: hàm cắt tài liệu, hàm chọn top-k kết quả, hàm khử trùng lặp.
  • Guardrail logic: hàm kiểm tra output có chứa từ cấm, có vượt độ dài, có định dạng hợp lệ.
Những test này nhanh và tất định, nên chạy trong CI mỗi lần push.

Integration tests — kiểm tra luồng phối hợp

Ở tầng này bạn ghép các mảnh lại. Câu hỏi là: dữ liệu có chảy đúng qua toàn bộ pipeline không? Thủ thuật quan trọng là tách phần AI ra khỏi phần cần test. Ví dụ để test luồng RAG, bạn mock lời gọi LLM trả về một chuỗi cố định, rồi kiểm tra: retrieval có lấy đúng document không, prompt cuối có chứa đúng ngữ cảnh không, response có được lưu vào database không. Bạn cũng viết một số ít test "smoke" gọi model thật để chắc chắn API key, endpoint, format vẫn hoạt động.

Eval — trái tim của testing sản phẩm AI

Eval là thứ khiến sản phẩm AI khác biệt. Một bộ eval gồm ba phần: dataset (tập ví dụ đầu vào, lý tưởng là lấy từ log thật của người dùng), tiêu chí chấm (điều gì là "tốt"), và cách chấm. Có ba cách chấm phổ biến:

  • Exact/rule-based: dùng khi có đáp án rõ ràng, ví dụ phân loại email vào đúng nhãn, trích đúng số tiền. Chấm bằng so khớp hoặc regex.
  • LLM-as-judge: dùng một model khác để chấm điểm output theo rubric bạn định nghĩa, ví dụ "câu trả lời có bám sát tài liệu nguồn không, thang 1-5". Phù hợp cho câu trả lời tự do không có một đáp án đúng duy nhất.
  • Human eval: con người chấm, chậm và đắt nhất nhưng chuẩn nhất, thường dùng để hiệu chỉnh (calibrate) chính con LLM-judge.
Chỉ số bạn theo dõi không phải "pass/fail" mà là điểm số trên toàn tập — ví dụ accuracy 92%, hallucination rate 3%, average helpfulness 4.2/5. Bạn đặt ngưỡng (threshold) và cảnh báo khi tụt xuống dưới ngưỡng đó.

Tình huống thực tế

Tình huống 1 — Chatbot chăm sóc khách hàng của một sàn TMĐT Việt Nam

Một công ty thương mại điện tử tại TP.HCM (gọi là ShopViet) triển khai chatbot trả lời câu hỏi về đơn hàng, đổi trả, vận chuyển. Giai đoạn đầu họ không có eval, chỉ test tay bằng vài chục câu hỏi trước khi ship. Ba tuần sau, bộ phận vận hành phát hiện chatbot tự tin khẳng định "shop hỗ trợ đổi trả trong 30 ngày" trong khi chính sách thật là 7 ngày. Hàng chục khách khiếu nại, đội CSKH phải xin lỗi và xử lý thủ công.

Đội kỹ thuật ngồi lại và xây bộ eval. Họ lấy 300 câu hỏi thật từ log 3 tuần đó, gán nhãn đáp án đúng dựa trên tài liệu chính sách nội bộ, và dùng LLM-as-judge với rubric hai tiêu chí: tính chính xác so với tài liệucó bịa thông tin không (hallucination). Lần chạy đầu tiên cho ra hallucination rate 11% — cao đến giật mình. Họ chỉnh prompt để buộc model chỉ trả lời dựa trên tài liệu được cung cấp và nói "tôi chưa có thông tin này" khi không chắc, rồi thêm guardrail chặn câu trả lời không trích được nguồn. Hallucination rate xuống còn 2%. Quan trọng hơn, bộ eval này giờ chạy tự động mỗi khi họ đổi prompt hoặc nâng version model.

Bài học: không có eval nghĩa là bạn phó mặc chất lượng cho may rủi, và người dùng sẽ là người phát hiện bug thay bạn — với cái giá đắt hơn nhiều. Dataset eval tốt nhất đến từ chính log thật.

Tình huống 2 — Startup fintech và cái bẫy "test đỏ giả"

Một startup fintech ở Singapore làm tính năng tóm tắt sao kê ngân hàng cho người dùng. Kỹ sư mới vào áp thói quen cũ, viết test kiểu assert summary == "Bạn đã chi 5 triệu cho ăn uống tháng này". Test đỏ liên tục vì model lúc thì viết "chi tiêu ăn uống của bạn là 5.000.000đ", lúc thì "Tổng chi ăn uống: 5 triệu đồng". Nội dung đúng, nhưng câu chữ khác nên assert luôn thất bại. Cả đội bắt đầu bỏ qua test đỏ, và rồi một bug thật (tính sai tổng tiền) lọt qua vì nằm lẫn trong đống đỏ giả.

Họ chuyển hướng: thay vì so khớp chuỗi, họ định nghĩa eval theo thuộc tính đầu ra. Tiêu chí trở thành: (1) con số tổng có khớp với dữ liệu gốc không — cái này chấm bằng rule, trích số bằng regex rồi so sánh; (2) văn phong có tự nhiên, dễ hiểu không — cái này chấm bằng LLM-judge. Test đỏ giả biến mất, và bug tính toán bị bắt ngay vì con số không khớp.

Bài học: đừng dùng so khớp chuỗi cứng cho đầu ra sinh văn bản. Hãy tách phần tất định có thể kiểm chứng chính xác (con số, nhãn) để chấm bằng rule, và phần chủ quan để chấm bằng judge.

Tình huống 3 — Đội copilot nội bộ và regression khi đổi version model

Một ngân hàng số ở Việt Nam xây copilot hỗ trợ nhân viên tra cứu quy trình nghiệp vụ. Mọi thứ ổn định cho tới khi nhà cung cấp phát hành version model mới, và đội nâng cấp để tận dụng chi phí rẻ hơn 20%. Vài ngày sau, nhân viên phàn nàn copilot bỗng trả lời dài dòng, hay lạc đề. Không ai đo lường được vì họ chưa có eval baseline.

Sau sự cố, họ thiết lập quy trình: mỗi khi định đổi model, chạy bộ eval 150 câu hỏi nghiệp vụ trên cả version cũ và version mới, so sánh điểm cạnh nhau (A/B trên eval set). Lần đổi tiếp theo, họ phát hiện version mới tuy rẻ hơn nhưng điểm "bám sát quy trình" tụt từ 4.4 xuống 3.9 — đủ để họ quyết định giữ lại version cũ cho một số luồng quan trọng.

Bài học: model là dependency bạn không kiểm soát. Eval baseline chính là tấm khiên chống regression khi model đổi, dù đổi do bạn nâng cấp hay do nhà cung cấp âm thầm cập nhật.

Hướng dẫn từng bước

Đây là lộ trình dựng hệ thống testing cho một sản phẩm AI từ con số 0.

  • Bắt đầu bằng unit test cho code tất định. Liệt kê mọi hàm không gọi AI: assemble prompt, parse output, chunk, retrieval, guardrail. Viết test cho từng hàm, đặc biệt các case lỗi (model trả JSON hỏng, trả rỗng, trả thừa markdown). Cho chạy trong CI mỗi push.
  • Thêm integration test cho pipeline, có mock. Mock lời gọi LLM bằng response cố định, kiểm tra dữ liệu chảy đúng qua toàn luồng. Thêm vài test smoke gọi API thật để bắt lỗi cấu hình.
  • Xây dataset eval từ log thật. Đừng ngồi nghĩ ví dụ trong đầu — hãy lấy 100–300 câu hỏi thật của người dùng. Nếu chưa có người dùng, tự sinh các case đa dạng phủ cả trường hợp thường gặp lẫn edge case (câu hỏi mơ hồ, ngoài phạm vi, có ý đồ xấu).
  • Định nghĩa tiêu chí và cách chấm. Với mỗi loại đầu ra, quyết định: chấm bằng rule (có đáp án rõ) hay LLM-judge (chủ quan)? Viết rubric cụ thể cho judge, ví dụ thang điểm và định nghĩa từng mức.
  • Chạy eval lấy baseline. Chạy lần đầu để biết điểm hiện tại. Đây là mốc so sánh cho mọi thay đổi sau này.
  • Hiệu chỉnh LLM-judge bằng human. Cho người thật chấm 20–30 mẫu, so với điểm của judge. Nếu lệch nhiều, sửa rubric cho tới khi judge đồng thuận với người.
  • Đưa eval vào quy trình. Chạy eval tự động trước mỗi lần đổi prompt, đổi model, hay release. Đặt ngưỡng cảnh báo (ví dụ hallucination rate không được vượt 5%).
  • Đóng vòng lặp với user testing và log production. Thu tín hiệu thật (thumbs up/down, tỉ lệ escalate lên người), đưa các case thất bại mới vào lại dataset eval. Bộ eval của bạn phải lớn dần theo thời gian.

Lỗi thường gặp & mẹo

  • Dùng assert so khớp chuỗi cho output sinh văn bản. Đây là lỗi số một. Hãy tách phần kiểm chứng được (con số, nhãn, định dạng) chấm bằng rule, phần còn lại chấm bằng judge.
  • Không có baseline. Không có mốc gốc thì không biết một thay đổi làm tốt lên hay tệ đi. Luôn lưu điểm baseline trước khi sửa.
  • Dataset eval tự nghĩ trong đầu. Ví dụ do bạn tưởng tượng thường quá "sạch" và không giống câu hỏi thật lộn xộn của người dùng. Ưu tiên log thật.
  • Tin LLM-judge một cách mù quáng. Judge cũng là model, cũng sai. Phải hiệu chỉnh bằng human trước khi tin, và định kỳ kiểm tra lại.
  • Eval quá đắt nên bỏ chạy. Nếu bộ eval tốn kém, đừng chạy full mỗi commit. Chạy tập nhỏ (smoke eval) thường xuyên, chạy full theo lịch hoặc trước release.
  • Quên test tính không tất định. Chạy cùng một input vài lần để đo độ dao động của output. Nếu quá dao động, cân nhắc hạ temperature.
  • Mẹo: đặt temperature=0 khi chạy eval để giảm nhiễu, giúp so sánh giữa các lần chạy công bằng hơn. Nhưng nhớ rằng production có thể chạy temperature cao hơn, nên vẫn cần một eval phản ánh cấu hình thật.
  • Mẹo: gắn version của prompt và model vào mỗi kết quả eval để về sau truy ngược được thay đổi nào gây ra tụt điểm.

Bài tập thực hành

  • Vẽ tháp testing cho sản phẩm của bạn. Lấy một tính năng AI bạn đang làm hoặc muốn làm (chatbot, tóm tắt, phân loại). Liệt kê cụ thể: những hàm nào sẽ unit test, luồng nào sẽ integration test, và eval sẽ đo cái gì.
  • Xây một bộ eval mini. Chuẩn bị 15 ví dụ đầu vào cho tính năng đó. Với mỗi ví dụ, ghi rõ tiêu chí "tốt" là gì. Phân loại: case nào chấm bằng rule, case nào cần LLM-judge.
  • Viết một rubric cho LLM-judge. Chọn một tiêu chí chủ quan (ví dụ "độ hữu ích" hoặc "bám sát nguồn"), định nghĩa thang 1–5 với mô tả rõ ràng cho từng mức để một model khác có thể chấm nhất quán.
  • Thử phá vỡ test giả. Lấy 3 câu trả lời "đúng nội dung nhưng khác câu chữ" và chứng minh rằng assert so khớp chuỗi sẽ báo đỏ sai. Sau đó viết lại tiêu chí chấm để chúng pass đúng.

Tóm tắt

Testing sản phẩm AI đòi hỏi một tư duy khác phần mềm truyền thống, vì đầu ra không tất định và model là dependency bạn không kiểm soát. Tháp testing gồm bốn tầng: unit test kiểm tra code tất định quanh model, integration test kiểm tra luồng phối hợp, eval đo chất lượng đầu ra của model, và user testing bắt những gì eval bỏ sót. Điều cốt lõi cần nhớ: unit/integration hỏi "code có đúng không", còn eval hỏi "model có tốt không" — hai câu hỏi khác nhau cần công cụ khác nhau.

Đừng bao giờ dùng so khớp chuỗi cứng cho đầu ra sinh văn bản; hãy tách phần kiểm chứng được để chấm bằng rule và phần chủ quan để chấm bằng LLM-judge đã được hiệu chỉnh bằng human. Xây dataset eval từ log thật, lấy baseline, đặt ngưỡng cảnh báo, và đưa eval vào quy trình trước mỗi lần đổi prompt hay model. Ba tình huống trong bài đều dạy chung một điều: đội nào không đo lường chất lượng thì người dùng sẽ đo giúp — và cái giá luôn đắt hơn. Bộ eval của bạn là tài sản sống, phải lớn dần theo mỗi thất bại bạn học được từ production.