Thực hành: dựng eval hồi quy bằng promptfoo trong GitHub Actions, tự chạy mỗi khi đổi prompt
Sửa vội một câu prompt có thể làm hỏng thứ đang chạy đúng, và một golden dataset gắn vào pull request sẽ bắt được lỗi đó trước khi khách hàng phát hiện.
Assertion có trọng số 2 khiến prompt mới bịa ngày giao rơi từ 1.0 xuống 0.5, dưới ngưỡng 0.75.
Đồ hoạ: FDE Times
Tóm tắt nhanh
- Output LLM có nhiều đáp án chấp nhận được, nên exact-match không dùng được; cần golden dataset cộng assertion có trọng số.
- Promptfoo action chạy so sánh trước/sau trên mỗi PR sửa prompt và comment kết quả, cần quyền pull-requests: write và secret API key.
- Bộ lọc paths cùng concurrency group giữ cho eval chỉ chạy khi cần và không đốt tiền API vì các lần chạy chồng nhau.
Thử hình dung: bạn sửa một câu trong system prompt để chatbot trả lời ngắn hơn. Demo trông ổn. Hai tuần sau, khách hàng báo rằng bot đã thôi nhắc mã đơn hàng trong câu trả lời, thứ mà đội vận hành của họ dựa vào mỗi ngày.
Unit test truyền thống không bắt được kiểu lỗi này. Với một hàm phần mềm thông thường, mỗi input có một output đúng; với LLM, cùng một input có thể có nhiều câu trả lời đều chấp nhận được, như Evidently AI lưu ý, nên so khớp tuyệt đối trở nên vô dụng.
Sản phẩm đã lên production cũng không có nghĩa là xong việc kiểm tra: bạn vẫn cần eval offline để chạy kiểm thử hồi quy sau mỗi thay đổi. Với một FDE, người trực tiếp đổi prompt và đổi model trên hệ thống của khách, đó là cái lưới bắt buộc phải có.
Hướng dẫn dưới đây dựng cái lưới ấy: một workflow GitHub Actions chạy promptfoo trên mỗi pull request sửa prompt.
Bạn sẽ dựng gì, và cần chuẩn bị gì?
Kết quả cuối cùng: mỗi khi ai đó mở PR có thay đổi trong thư mục prompts/, GitHub Actions chạy eval so sánh prompt cũ với prompt mới trên cùng một golden dataset, rồi comment kết quả thẳng vào PR. Nếu điểm thấp hơn ngưỡng, bạn có tín hiệu đỏ trước khi merge.
Chuẩn bị: một repo GitHub bạn có quyền admin, một API key của nhà cung cấp model (ví dụ dưới dùng OpenAI), và một thư mục chứa prompt. Kiến thức cần có: đọc được YAML và hiểu pull request hoạt động thế nào.
Bước 1: golden dataset là trái tim, không phải YAML
Tập input tham chiếu kèm output đã duyệt thường được gọi là “golden dataset”, và toàn bộ bộ test xoay quanh nó. Vì thế đừng vội viết workflow. Hãy bắt đầu bằng 15-20 câu hỏi thật, lấy từ log hoặc từ cuộc họp với khách, mỗi câu kèm những đặc điểm mà câu trả lời đúng phải có.
Thử hình dung một công ty giao hàng. Câu hỏi “Đơn DH-1042 của tôi đâu rồi?” không có một đáp án duy nhất. Nhưng mọi câu trả lời tốt đều phải nhắc lại mã đơn, không bịa ngày giao, và giữ giọng lịch sự. Ba đặc điểm đó sẽ thành ba assertion.
Kiểm tra sau bước này: mỗi test case có ít nhất một điều kiện kiểm được bằng máy (chứa chuỗi nào đó) và một điều kiện cần phán đoán (giọng văn, tính đúng sự thật).
Bước 2: viết config promptfoo với assertion có trọng số
Trong promptfoo, mỗi assertion so output của LLM với một giá trị hoặc điều kiện kỳ vọng; pass hay fail được suy ra từ điểm có trọng số so với một ngưỡng. Chính ngưỡng đó cho bạn tín hiệu pass/fail trong CI. File dưới đây là bản rút gọn để minh họa cấu trúc; hãy đối chiếu tên trường với trang Assertions and Metrics của promptfoo trước khi dùng.
# prompts/promptfooconfig.yaml (rút gọn)
prompts:
- file://support_prompt.txt
providers:
- openai:gpt-4o-mini
tests:
- vars:
question: "Đơn DH-1042 của tôi đâu rồi?"
threshold: 0.75
assert:
- type: contains
value: "DH-1042"
weight: 1
- type: llm-rubric
value: "Không bịa ngày giao hàng cụ thể"
weight: 2
- type: llm-rubric
value: "Giọng lịch sự, ngắn gọn"
weight: 1
Làm một phép tính để thấy ngưỡng hoạt động ra sao, giả sử điểm được tính như trung bình có trọng số. Tổng trọng số là 4. Prompt cũ trả lời đủ cả ba ý: 4/4 = 1.0, pass.
Giờ giả sử prompt mới, vì bị ép trả lời ngắn, bịa ra một ngày giao. Output vẫn có mã đơn (1) và vẫn lịch sự (1), nhưng mất 2 điểm của assertion nặng nhất: 2/4 = 0.5, dưới ngưỡng 0.75, test fail. Nếu prompt mới chỉ thiếu phần lịch sự, điểm là 3/4 = 0.75, vừa chạm ngưỡng.
| Assertion (trọng số) | Prompt cũ | Prompt mới bịa ngày giao |
|---|---|---|
| Chứa “DH-1042” (1) | Đạt: 1 | Đạt: 1 |
| Không bịa ngày giao (2) | Đạt: 2 | Trượt: 0 |
| Lịch sự, ngắn gọn (1) | Đạt: 1 | Đạt: 1 |
| Điểm so với ngưỡng 0.75 | 4/4 = 1.0, pass | 2/4 = 0.5, fail |
Bảng này cho thấy vì sao một thay đổi trông vô hại trên demo vẫn bị báo đỏ: chỉ một lỗi nặng đã đủ kéo điểm xuống dưới ngưỡng.
Kiểm tra: chạy thử một case bạn biết chắc là sai và xác nhận nó fail. Một bộ test không bao giờ đỏ là bộ test không kiểm gì.
Bước 3: workflow chỉ chạy khi prompt thay đổi
Mỗi lần eval chạy là một lần gọi API tính tiền, nên bạn không muốn nó chạy khi ai đó sửa README. Bộ lọc paths trên sự kiện pull_request giải quyết việc này: chỉ khi ít nhất một file thay đổi khớp pattern, workflow mới chạy.
# .github/workflows/prompt-eval.yml
name: Prompt regression eval
on:
pull_request:
paths:
- 'prompts/**'
concurrency:
group: prompt-eval-${{ github.ref }}
cancel-in-progress: true
permissions:
contents: read
pull-requests: write
Khối concurrency xử lý một nguồn tốn tiền khác. Trong cùng một concurrency group, tại mỗi thời điểm chỉ có tối đa một job hoặc workflow đang chạy. Khi bạn push ba commit liên tiếp vào cùng một PR, lần chạy cũ bị hủy thay vì cả ba cùng gọi API.
Quyền pull-requests: write không phải trang trí: promptfoo cần nó để đăng comment lên PR.
Bước 4: gắn promptfoo action và secret
Action của promptfoo làm phần nặng nhất: trên mỗi PR có sửa prompt, nó tự chạy một phép so sánh đầy đủ giữa trước và sau thay đổi, rồi đăng kết quả lên PR. Nếu dùng OpenAI, bạn cần đặt secret OPENAI_API_KEY trong repository (Settings, mục Secrets); nhà cung cấp khác thì dùng key tương ứng.
jobs:
evaluate:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
- uses: promptfoo/promptfoo-action@v1
with:
# tên input key/token: đối chiếu README của promptfoo-action
openai-api-key: ${{ secrets.OPENAI_API_KEY }}
github-token: ${{ secrets.GITHUB_TOKEN }}
prompts: 'prompts/**/*.txt'
config: 'prompts/promptfooconfig.yaml'
Hai input prompts (glob) và config (đường dẫn file config) là phần cốt lõi trong ví dụ của promptfoo. Phần checkout và cách truyền key được viết đơn giản hóa; hãy đọc trang Testing Prompts with GitHub Actions để lấy cấu hình đầy đủ cho phiên bản bạn dùng.
Kiểm tra: mở một PR chỉ sửa một từ trong support_prompt.txt. Tab Actions phải hiện workflow chạy, và PR phải có comment bảng so sánh. Sau đó mở một PR chỉ sửa README: workflow không được chạy.
Một lưu ý về phạm vi: hướng dẫn này dừng ở mức tín hiệu đỏ trên PR, chưa phải cơ chế khóa merge. Trước khi hứa với khách rằng prompt tệ “không thể lọt vào main”, hãy tự kiểm chứng trong repo của mình xem job có báo fail khi điểm dưới ngưỡng hay không.
Những lỗi khiến pipeline chạy mà vô nghĩa
Lỗi phổ biến nhất là golden dataset quá dễ. Nếu cả 20 câu đều là câu hỏi mẫu trong demo, eval sẽ xanh mãi trong khi người dùng thật hỏi những thứ khác. Hãy thêm vào dataset mọi câu từng gây sự cố.
Lỗi thứ hai: glob trong paths không khớp chỗ prompt thực sự nằm. Nhiều đội để prompt trong file Python, sửa xong thì workflow im lặng. Kiểm tra bằng cách sửa đúng file đó và xem Actions có chạy không.
Lỗi thứ ba là quên rằng đổi model cũng là thay đổi. Nếu tên model nằm trong promptfooconfig.yaml thuộc thư mục prompts/, việc đổi model sẽ tự kích hoạt eval. Nếu nó nằm ở file cấu hình khác, hãy thêm đường dẫn đó vào paths.
Kỹ năng này trông thế nào ở site khách hàng?
Mục tiêu của kiểm thử hồi quy là chứng minh thay đổi mới cải thiện hệ thống mà không làm hỏng những gì từng chạy, và chỗ hợp lý nhất để đặt nó là CI/CD, chạy sau mỗi thay đổi.
Giá trị đó lộ rõ khi, giả sử, khách muốn đổi sang một model rẻ hơn: thay vì tranh luận bằng cảm giác, bạn mở PR đổi model và cho họ xem comment so sánh.
Việc đầu tiên nên làm ở một khách hàng mới: ngồi với người vận hành, xin 20 câu hỏi khiến họ lo nhất, và hỏi lỗi nào là không thể chấp nhận. Câu trả lời đó quyết định trọng số. Danh sách câu hỏi ấy, cùng bộ trọng số đi kèm, là thứ đáng bàn giao nhất khi bạn rời dự án.
Viết vào CV và mang vào phỏng vấn ra sao?
Khi đọc JD FDE, hãy để ý các cụm như “evaluation”, “LLM testing”, “CI/CD”. Rồi so hai cách viết cùng một kinh nghiệm trong CV.
Cách viết chung chung
- Có kinh nghiệm đánh giá LLM, quen GitHub Actions.
- Tối ưu prompt cho chatbot.
Cách viết kiểm chứng được
- Dựng regression eval cho chatbot hỗ trợ đơn hàng trên golden dataset 20 case, chạy trong GitHub Actions, báo đỏ trên PR khi điểm dưới 0.75.
- Đặt trọng số assertion theo ưu tiên của đội vận hành: lỗi bịa ngày giao nặng gấp đôi lỗi văn phong.
Cách viết thứ hai thuyết phục hơn vì người tuyển dụng tự kiểm chứng được. Hãy kèm link một PR công khai có comment kết quả, và tốt nhất là một PR bị báo đỏ vì prompt mới tệ đi.
Trong phỏng vấn, chuẩn bị trả lời câu “Khi đổi model, làm sao bạn biết nó không làm hỏng gì?” bằng đúng ví dụ DH-1042 và phép tính 2/4 ở trên.
Và hãy hỏi ngược nhà tuyển dụng: “Đội đang kiểm tra hồi quy prompt thế nào trước khi deploy cho khách?” Câu trả lời cho bạn biết mình sẽ được dựng cái lưới này, hay sẽ phải đi dọn hậu quả khi nó vắng mặt.
Prompt sẽ còn thay đổi hàng chục lần sau khi bạn rời dự án. Thứ bạn để lại cho khách hàng không phải prompt hay nhất, mà là bài kiểm tra mà mọi prompt sau này phải vượt qua để chứng minh nó không làm hỏng điều đã chạy đúng.