Vị giám khảo AI luôn đồng ý với chính mình: Bài học về thiên vị trong đánh giá LLM
Một sự cố sản xuất khiến một pipeline đa tác tử phê duyệt câu truy vấn SQL sai lầm đã hé lộ hiện tượng 'thiên vị tự ưu' (self-preference bias) trong các mô hình ngôn ngữ lớn dùng làm giám khảo. Bài viết phân tích nguyên nhân gốc rễ, các dạng thiên vị phổ biến, và giải pháp thực tế để hiệu chỉnh LLM judge trước khi dùng nó để kiểm soát các hành động tự động trong sản xuất.

Trong một pipeline đa tác tử (multi-agent), một agent sinh ra câu truy vấn SQL từ ngôn ngữ tự nhiên của người dùng, và một agent thứ hai — đóng vai "giám khảo" — quyết định xem truy vấn đó có an toàn và chính xác để tự động thực thi hay không, hay cần dừng lại chờ con người xác nhận.
Trong vài tuần đầu, hệ thống hoạt động đủ tốt đến mức chúng tôi ngừng theo dõi sát sao. Rồi một truy vấn lẽ ra phải bị gắn cờ lại được phê duyệt và chạy. Không có gì thảm khốc xảy ra: không mất dữ liệu, không ghi sai bảng, nhưng kết quả trả về hoàn toàn sai. Lý do là truy vấn đã âm thầm bỏ qua một điều kiện lọc (filter clause) mà câu hỏi của người dùng hiển nhiên ngụ ý. Chúng tôi mất nửa giờ đồng hồ bối rối với một nhà phân tích để nhận ra con số họ nhận được đơn giản là không chính xác. Sai và tự tin, được trình bày như một kết quả đúng đắn bởi một giám khảo đã phê duyệt nó mà không do dự.
Đó là sự cố khiến chúng tôi ngừng coi "đã được LLM judge phê duyệt" tương đương với "điều này đúng", và bắt đầu xem bản thân giám khảo như một thành phần cần được kiểm thử riêng.
Sự cố bắt đầu từ giả định sai lầm
Giả định đầu tiên của tôi là giám khảo chỉ mắc một lỗi cá biệt. LLM không hoàn hảo, một quyết định sai có thể xảy ra, nhưng rồi bạn sẽ bỏ qua. Vì vậy, tôi chạy lại cùng truy vấn và cùng prompt giám khảo trong môi trường cô lập, hy vọng sẽ thấy hoặc là lỗi lặp lại, hoặc là sự sửa sai.
Nó lại phê duyệt truy vấn đó với cùng thiếu sót về filter và cùng mức độ tự tin. Vấn đề không phải là một sai sót ngẫu nhiên — đó là một khuôn mẫu mà giám khảo sẽ tái lập một cách đáng tin cậy, nghĩa là có điều gì đó mang tính cấu trúc đang đi sai hướng.
Để truy tận gốc, tôi kéo một loạt các quyết định đánh giá trước đây và so sánh chúng với nhận xét của một người đánh giá con người về cùng các truy vấn. Đó là lúc khuôn mẫu trở nên đủ rõ ràng để gọi tên.
Điều giám khảo thực sự đang làm
Agent sinh truy vấn và agent giám khảo được xây dựng trên cùng một mô hình nền tảng. Đó không phải là một lựa chọn thiết kế — chỉ là mô hình mà chúng tôi đã chuẩn hóa vì lý do chi phí cho toàn pipeline. Khi tôi thử thay thế bằng truy vấn được sinh ra từ một mô hình khác, cùng nhiệm vụ và schema, chất lượng tương đương khi kiểm tra thủ công, giám khảo đột nhiên trở nên khắt khe hơn hẳn. Nó phát hiện ra các lỗi trong truy vấn của mô hình kia mà trước đây nó đã bỏ qua trong truy vấn của chính nó.
Đó là thiên vị tự ưu (self-preference bias) — một hành vi đã được ghi nhận trong các nghiên cứu về LLM-as-judge, không phải điều gì độc nhất của hệ thống chúng tôi. Điều tôi không ngờ tới cho đến khi nó gây ra một sự cố thực tế là mức độ nhất quán của hiệu ứng này. Đây không phải một giám khảo hào phóng một cách ngẫu nhiên. Nó đặc biệt hào phóng với những đầu ra được viết theo phong cách và cấu trúc mà nó nhận ra là của chính mình.
Đây là phiên bản rút gọn của prompt giám khảo chúng tôi dùng lúc đó, chỉ để minh họa:
JUDGE_PROMPT = """
You are reviewing a SQL query generated for the following user question.
Approve it for automatic execution, or flag it for human review.
User question: {question}
Generated SQL: {sql}
Schema: {schema}
Return JSON only:
{{"decision": "approve" | "flag_for_review", "reasoning": str}}
"""
def judge_query(question, sql, schema, client, model="gpt-4o"):
response = client.chat.completions.create(
model=model,
messages=[{"role": "user", "content": JUDGE_PROMPT.format(
question=question, sql=sql, schema=schema
)}],
temperature=0,
response_format={"type": "json_object"},
)
return json.loads(response.choices[0].message.content)
Không có gì trong prompt này yêu cầu giám khảo nới lỏng với bất kỳ điều gì. Thiên vị không đến từ chỉ dẫn, mà đến từ việc mô hình giám khảo nhận diện "một truy vấn đúng được viết như thế nào". Nếu bộ sinh viết các truy vấn theo đúng phong cách dễ nhận biết đó, xác suất giám khảo phê duyệt sẽ tăng lên, bất kể truy vấn có thực sự đúng hay không.
Cách một giám khảo "nói dối" bạn
Sau khi phát hiện thiên vị tự ưu, tôi bắt đầu tìm kiếm những cách sai lệch có hệ thống khác và tìm ra ba dạng chính:
Thiên vị tự ưu (Self-preference bias) – Một LLM đóng vai giám khảo có thể đánh giá cao các đầu ra của chính nó, hoặc từ cùng một dòng mô hình, cao hơn các đầu ra khác. Nguyên nhân cốt lõi là sự quen thuộc về độ khó dự đoán (perplexity familiarity) — LLM thường chấm điểm cao hơn cho văn bản dễ dự đoán hơn (perplexity thấp hơn), thường là do chính dòng mô hình của nó tạo ra. Các mô hình mạnh hơn có thể thể hiện điều này rõ rệt hơn vì chúng giỏi nhận ra "dấu vân tay" phong cách của mình.
Thiên vị về độ dài (Verbosity bias) – Khi đưa cho giám khảo hai câu trả lời để so sánh — một đúng và ngắn gọn, một đúng nhưng có thêm ngữ cảnh thừa — câu dài hơn thắng nhiều hơn câu ngắn. Chúng tôi quan sát thấy giám khảo chấm điểm SQL có nhiều comment chi tiết cao hơn SQL chỉ chứa đúng truy vấn. Thiên vị này đặc biệt tái diễn nếu rubric của bạn có bất kỳ tiêu chí nào gần với "sự kỹ lưỡng".
Thiên vị vị trí (Position bias) – Với so sánh cặp đôi "cái nào tốt hơn", thứ tự trình bày hai đầu ra có thể thay đổi kết quả đánh giá. Đổi chỗ chúng, giữ nguyên mọi thứ khác, phán quyết có thể lật ngược. Cách phát hiện rất dễ: chạy cả hai thứ tự và xem kết quả có giữ nguyên không.
Không một trong ba dạng thiên vị này phủ nhận giá trị của phương pháp. Chúng chỉ đơn giản loại bỏ việc coi điểm số của giám khảo là một phép đo khách quan, như cách bạn xem một unit test pass hay fail. Đó chỉ là ý kiến của một người đánh giá nhất quán nhưng thiên vị — và bạn cần biết những điểm mù của nó, giống như bạn học cách hiểu điểm mù của một đồng nghiệp trước khi coi trọng nhận xét của họ như những người khác.
Giải pháp thực sự hiệu quả
Giải pháp đầu tiên là chuyển việc đánh giá sang một mô hình thuộc một dòng khác với mô hình sinh — một bên thứ ba trung lập, thay vì một người đánh giá có chung "huyết thống" với thứ nó đang xem xét. Điều này trực tiếp giải quyết vấn đề tự ưu và là thay đổi đầu tiên tôi khuyên bạn nên làm nếu gặp tình huống tương tự, vì nó loại bỏ cơ chế gây lỗi thay vì cố gắng làm mờ nó đi bằng thống kê.
# Đơn giản hóa cho minh họa
def judge_query_neutral(question, sql, schema, generator_model, client):
# Không bao giờ để giám khảo chung dòng mô hình với bộ sinh.
judge_model = "gemini-2-5-pro" if "gpt" in generator_model else "gpt-4o"
return judge_query(question, sql, schema, client, model=judge_model)
Thay đổi này không giúp gì cho thiên vị về độ dài, vì thiên vị đó không liên quan đến việc ai huấn luyện giám khảo, mà nằm ở những gì rubric ngầm khuyến khích. Một giám khảo trung lập với rubric ưa chuộng đầu ra nghe có vẻ kỹ lưỡng vẫn sẽ ưa chuộng đầu ra như vậy. Tôi từng nghĩ "chọn một giám khảo không liên quan đến cả hai bên" sẽ dọn sạch mọi thiên vị. Nó chỉ sửa một thiên vị cụ thể và để các thiên vị khác nguyên vẹn. Bị sai theo cách này thực sự hữu ích, vì nó buộc tôi phải coi mỗi thiên vị là một vấn đề riêng với giải pháp riêng, thay vì một vấn đề duy nhất với một miếng vá duy nhất.
Điều giúp khắc phục thiên vị độ dài là viết lại rubric để chỉ dẫn giám khảo một cách tường minh: phạt sự dài dòng không cần thiết và thưởng cùng một kết quả đúng với ít từ hơn. Thêm một ví dụ cụ thể ngay trong prompt — cho thấy một truy vấn ngắn đúng đánh bại một truy vấn dài thừa thãi — hiệu quả hơn nhiều so với bất kỳ chỉ dẫn "hãy khách quan" chung chung nào. Tôi đã biết điều này về việc prompt các mô hình, nhưng lại quên áp dụng nó vào chính prompt giám khảo của mình.
Hiệu chỉnh giám khảo với con người
Không một thay đổi nào ở trên có ý nghĩa nếu bạn không kiểm chứng nó với đánh giá thực tế của con người trên một mẫu dữ liệu riêng — và phải kiểm tra liên tục, không phải một lần rồi thôi.
# Đơn giản hóa cho minh họa
def measure_agreement(judge_decisions, human_decisions):
agreed = sum(
1 for j, h in zip(judge_decisions, human_decisions) if j == h
)
return agreed / len(judge_decisions)
Chúng tôi chọn một mẫu các truy vấn đã được giám khảo chấm điểm, yêu cầu một người đánh giá có hiểu biết về schema chấm lại cùng mẫu đó mà không nhìn kết quả của giám khảo trước, rồi đo độ đồng thuận. Kết quả đạt khoảng 80–85% — một con số thô từ pipeline riêng của chúng tôi tại thời điểm đó, không phải để làm chuẩn cho các tác vụ hoặc rubric khác.
Bản thân con số không phải là điều quan trọng. Nó dùng để quyết định nên chuyển loại truy vấn nào tự động cho con người xử lý, bất kể giám khảo nói gì. Các truy vấn "có vẻ hợp lý nhưng sai tinh vi" — chính xác là nhóm gây ra sự cố ban đầu — luôn là nơi đồng thuận giữa giám khảo và người thấp nhất. Những truy vấn đó giờ đây mặc định được gắn cờ để xem xét thủ công, không phụ thuộc vào độ tự tin của giám khảo, vì độ tự tin của giám khảo trong nhóm này đã chứng minh là không đáng tin.
Đây cũng là luận điểm thực sự cho việc dùng LLM judge trong pipeline kiểu này — điều ít khi được nói ra bên cạnh các cảnh báo về thiên vị: nó thực sự nhanh cho một quyết định sơ bộ, và thực sự hữu ích để tạo một bộ dữ liệu nhãn khi bạn chưa có — chấm một loạt thô, để người kiểm tra các điểm bất đồng, và xây dựng bộ đánh giá thật từ vòng lặp đó, thay vì viết từng nhãn bằng tay từ con số không. Các vấn đề thiên vị là có thật, và chúng là lý do để ngừng coi đầu ra của giám khảo như một sự thật, thay vào đó xem nó như một ý kiến đầu tiên cần một ý kiến thứ hai theo định kỳ — nhưng không phải lý do để vứt bỏ cả phương pháp.
Kết luận
Truy vấn bắt đầu mọi chuyện không lọt qua vì prompt giám khảo kém; nó lọt qua vì chúng tôi đã xây một bước kiểm duyệt và âm thầm coi nó là trung lập, trong khi nó có cấu trúc nghiêng về việc phê duyệt hình ảnh phản chiếu của chính nó. Đó là sửa đổi thực sự quan trọng, và là điều đầu tiên tôi khuyên bất kỳ ai đang chạy LLM judge trong sản xuất nên làm trước khi chạm vào một từ nào của rubric. Thay đổi đó khép lại đúng chế độ lỗi đã gây ra sự cố, và chẳng tốn gì ngoài việc chọn một mô hình khác cho một lần gọi API.
Việc viết lại rubric và vòng lặp hiệu chỉnh với con người chính là thứ biến giám khảo từ "thường ổn" thành thứ chúng tôi sẵn sàng dùng làm cửa ngõ cho các hành động tự động. Ba điều giờ đây luôn đúng mà trước sự cố không hề đúng: giám khảo và bộ sinh không bao giờ cùng dòng mô hình; rubric phạt sự dài dòng một cách tường minh thay vì vô tình thưởng nó; và bất kỳ nhóm truy vấn nào có lịch sử đồng thuận giám khảo–người yếu sẽ mặc định chuyển cho con người, bất kể giám khảo nói gì. Không điều nào trong số đó làm cho giám khảo hoàn hảo. Nó làm cho các chế độ lỗi của nó trở nên được biết đến, được giới hạn, và cụ thể là những thứ chúng tôi đã quyết định có thể chấp nhận sống chung.
Nếu có một điều đáng rút ra từ câu chuyện này, đó là: một LLM judge giành được quyền kiểm soát các hành động trong sản xuất giống như cách một thành viên mới trong đội giành quyền tự phê duyệt các pull request của chính họ — không phải vào ngày đầu tiên, và không phải chỉ vì nó nghe có vẻ tự tin khi giải thích lý do, mà sau khi bạn đã quan sát nó bất đồng với con người đủ thường xuyên và đủ cụ thể để biết chính xác nơi nào có thể tin và nơi nào không.