Bài kiểm tra của bạn đã vượt qua vào thứ Hai với cùng đầu vào, cùng mã và temperature=0. Đến thứ Ba, nó thất bại dù bạn không thay đổi gì: mô hình vẫn trả lời đúng ý, nhưng diễn đạt khác đi một chút. Nếu bạn đang dùng assert response == "...", bạn sẽ phải gỡ lỗi bộ kiểm thử thay vì sản phẩm.
Đây là cái giá khi kiểm thử bất kỳ luồng nào gọi mô hình ngôn ngữ. Đầu ra có thể dịch chuyển dù bạn đặt temperature=0, vì vậy không nên kỳ vọng phản hồi giống hệt từng byte qua nhiều lần chạy. Bài viết này tập trung vào cách thay thế các khẳng định chuỗi chính xác bằng các kiểm tra hợp đồng bền vững hơn. Đây là phần tìm hiểu sâu về chế độ lỗi thứ ba trong hướng dẫn về lý do tác nhân AI thất bại trong môi trường production.
Tại sao temperature=0 không có nghĩa là xác định
temperature kiểm soát cách mô hình lấy mẫu token tiếp theo. Khi bằng 0, mô hình ưu tiên token có xác suất cao nhất. Điều này giúp giảm biến thiên, nhưng không đảm bảo tái tạo hoàn toàn đầu ra.
Nguyên nhân nằm ở toàn bộ ngăn xếp suy luận:
- Phép toán dấu phẩy động trên GPU không có tính kết hợp: cùng các số nhưng khác thứ tự cộng có thể tạo chênh lệch nhỏ.
- Chênh lệch nhỏ có thể thay đổi token đứng đầu, kéo theo toàn bộ phần sinh sau đó.
- Nhà cung cấp có thể thay đổi cách batch request, phần cứng, kernel, thư viện suy luận, lượng tử hóa trọng số hoặc khu vực định tuyến.
- Seed cố định cũng không đảm bảo đầu ra giống từng bit.
Thảo luận này từ vLLM giải thích chi tiết vì sao seed cố định và temperature=0 vẫn chưa đủ để tái tạo tuyệt đối.
Kết luận: tính xác định là thuộc tính của toàn bộ hệ thống phục vụ mô hình, không phải một cờ trong request. Vì vậy, hãy kiểm tra hợp đồng của phản hồi, thay vì kiểm tra từng câu chữ.
Vì sao khẳng định chuỗi chính xác tạo flaky test
Ví dụ dễ gặp:
assert.equal(response, "Your order total is $42.00.");
Lần sau, mô hình trả về:
Your total comes to $42.00.
Câu trả lời vẫn đúng, nhưng test thất bại.
Một test thất bại trên kết quả đúng còn nguy hiểm hơn không có test. Theo thời gian, team sẽ:
- Chạy lại pipeline cho đến khi xanh.
- Bỏ qua lỗi vì cho rằng test chỉ “báo động giả”.
- Bỏ lỡ hồi quy thật bị lẫn trong nhiễu.
Đó là cách flaky test làm suy giảm niềm tin vào toàn bộ CI. Chúng tôi đã phân tích thêm về nguyên nhân tạo ra flaky test và vì sao chúng có xu hướng lan rộng.
Thay vì ghim chặt output bằng snapshot văn bản hoặc so sánh chuỗi, hãy kiểm tra những thuộc tính không đổi khi mô hình diễn đạt lại câu trả lời.
Kiểm tra cấu trúc và ý nghĩa, không kiểm tra câu chữ
Một phản hồi hỗ trợ hoàn tiền có thể được viết theo hàng trăm cách. Nhưng phản hồi hợp lệ vẫn cần giữ các thông tin quan trọng:
- Số tiền hoàn.
- ID đơn hàng.
- Trạng thái xử lý.
- Các trường không được phép lộ ra.
Câu hỏi nên chuyển từ:
“Mô hình có nói chính xác câu này không?”
sang:
“Phản hồi có đúng cấu trúc, đúng trường, đúng kiểu dữ liệu và nằm trong phạm vi hợp lệ không?”
Dưới đây là các cách triển khai.
1. Xác thực phản hồi bằng JSON Schema
Nếu tác nhân trả về dữ liệu có cấu trúc, hãy định nghĩa JSON Schema và xác thực mọi phản hồi dựa trên schema đó.
Ví dụ schema cho phản hồi hoàn tiền:
{
"type": "object",
"required": ["order_id", "status", "amount"],
"properties": {
"order_id": {
"type": "string",
"pattern": "^ORD-[0-9]+$"
},
"status": {
"type": "string",
"enum": ["refunded", "pending", "denied"]
},
"amount": {
"type": "number",
"minimum": 0
}
},
"additionalProperties": false
}
Schema này bắt được các lỗi thực tế:
- Thiếu trường bắt buộc.
-
amountlà chuỗi thay vì số. -
statuscó giá trị ngoài enum. - Mô hình trả về văn xuôi khi API yêu cầu JSON.
- Object bị lồng sai cấp.
Bạn có thể tải schema phản hồi vào Apidog để xác thực phản hồi của tác nhân. Khi lỗi xảy ra, bạn sẽ biết chính xác trường nào không hợp lệ thay vì nhận một diff văn bản dài.
2. Kiểm tra tool call thay vì lời giải thích của tác nhân
Khi tác nhân gọi công cụ, phần cần kiểm tra là request gửi đi, không phải câu dẫn đến request đó.
Ví dụ, tác nhân đặt chỗ gọi:
POST /reservations
Bạn nên xác nhận:
- Đúng endpoint và HTTP method.
- Đúng công cụ được chọn.
- Các tham số bắt buộc có mặt.
- Payload đúng schema.
- Không có trường dư hoặc trường được bịa ra.
Ví dụ payload:
{
"guests": 2,
"date": "2025-06-20"
}
Các kiểm tra cần có:
assert.equal(request.method, "POST");
assert.equal(request.path, "/reservations");
assert.isInteger(request.body.guests);
assert.match(request.body.date, /^\d{4}-\d{2}-\d{2}$/);
Cách tiếp cận này kiểm tra hợp đồng của tool call, vốn không thay đổi dù tác nhân diễn đạt lý do theo nhiều cách khác nhau. Xem thêm hướng dẫn kiểm thử API call của tác nhân AI từ đầu đến cuối.
3. Dùng khoảng giá trị cho số thay vì giá trị tuyệt đối
Với số do mô hình tạo hoặc chuyển tiếp, hãy kiểm tra khoảng hợp lệ thay vì ép khớp một giá trị tuyệt đối.
Ví dụ với tổng tiền giỏ hàng:
assert.isAtLeast(response.total, 0);
assert.isAtMost(response.total, cartSubtotal + maxShippingFee + maxTax);
Kiểm tra này vẫn bắt được các lỗi quan trọng:
- Tổng tiền âm.
- Tổng tiền lớn bất thường.
- Tổng tiền bằng
0với giỏ hàng không rỗng. - Giá trị không phải số.
Phạm vi cũng phù hợp cho:
- Confidence score.
- Số lượng item.
- Token usage.
- Độ trễ.
- Ngân sách.
- Giá trị tính toán dẫn xuất.
Nguyên tắc: chọn giới hạn rộng nhất vẫn có thể phát hiện lỗi thật.
4. Kiểm tra trường bắt buộc và trường bị cấm
Đây là hai kiểm tra đơn giản nhưng có giá trị cao.
Ví dụ, phản hồi hỗ trợ phải luôn có resolution:
assert.exists(response.resolution);
assert.isNotEmpty(response.resolution);
Đồng thời, phản hồi gửi cho khách hàng không được lộ thông tin nội bộ:
assert.notProperty(response, "internal_notes");
assert.notProperty(response, "raw_prompt");
Các kiểm tra này không bị ảnh hưởng bởi cách diễn đạt. Chúng cũng giúp ngăn các lỗi rò rỉ dữ liệu như prompt nội bộ, ghi chú vận hành hoặc thông tin không dành cho người dùng.
5. Dùng kiểm tra ngữ nghĩa cho văn bản tự do
Đôi khi payload bắt buộc là văn bản tự do. Khi đó, thay vì so sánh toàn bộ chuỗi, hãy kiểm tra các thuộc tính cụ thể:
assert.include(response.message, input.orderId);
assert.isBelow(response.message.length, 1000);
assert.notMatch(response.message, /internal system prompt/i);
Bạn có thể kiểm tra:
- Có chứa ID đơn hàng hoặc dữ liệu đầu vào quan trọng không.
- Có vượt giới hạn độ dài không.
- Có chứa cụm từ cấm không.
- Có đúng ngôn ngữ hoặc định dạng yêu cầu không.
Nếu cần kiểm tra ý nghĩa, hãy dùng embedding similarity với một câu trả lời tham chiếu và đặt ngưỡng tối thiểu. Tuy nhiên, hãy coi đây là cổng kiểm tra thô, không phải nguồn chân lý duy nhất.
assert.isAtLeast(similarityScore, 0.8);
Kiểm tra ngữ nghĩa có thể phát hiện câu trả lời lạc đề, nhưng không thay thế schema validation, kiểm tra kiểu dữ liệu hoặc kiểm tra ràng buộc nghiệp vụ.
6. Snapshot phần ổn định, không snapshot toàn bộ văn bản
Snapshot test vẫn hữu ích nếu bạn chỉ snapshot các phần bền vững:
- Tập hợp key.
- Kiểu dữ liệu.
- Enum.
- Cấu trúc object.
- Khoảng giá trị hợp lệ.
Thay vì snapshot thế này:
"Your order total is $42.00."
Hãy snapshot ở mức hợp đồng:
{
"requiredKeys": ["order_id", "status", "amount"],
"statusEnum": ["refunded", "pending", "denied"],
"amountRange": [0, 10000]
}
Khi snapshot hỏng, đó sẽ là thay đổi cấu trúc đáng xem xét, không phải chỉ vì mô hình dùng từ đồng nghĩa.
Tác nhân có trạng thái khiến việc kiểm thử khó hơn
Các ví dụ trên giả định một request tạo ra một response. Nhưng tác nhân AI thường có bộ nhớ, context và trạng thái qua nhiều lượt.
Output của tác nhân có thể thay đổi do:
- Tài liệu truy xuất được xếp hạng khác nhau.
- Bộ nhớ trước đó khác nhau.
- Thứ tự lượt hội thoại thay đổi.
- Một bản tóm tắt ở lượt trước ảnh hưởng đến lý luận ở lượt sau.
Nói cách khác, bạn đang xử lý đồng thời:
- Tính không xác định của mô hình.
- Khác biệt ở trạng thái đầu vào.
Để hiểu thêm về nguồn gốc của trạng thái này, xem bài viết về cách bộ nhớ của tác nhân AI hoạt động.
Có hai thói quen nên áp dụng:
Seed trạng thái về điểm bắt đầu đã biết
Trước mỗi test, thiết lập bộ nhớ tác nhân về cùng một trạng thái:
await agent.resetMemory();
await agent.seedMemory({
user_id: "test-user-001",
preferences: {
language: "vi"
}
});
Điều này giúp test chỉ quan sát một biến thay đổi thay vì nhiều nguồn biến thể cùng lúc.
Kiểm tra bất biến không phụ thuộc đường đi
Với luồng nhiều bước, hãy kiểm tra kết quả cuối và các invariant nghiệp vụ:
assert.isAtLeast(account.balance, 0);
assert.equal(finalState.flightBookings.length, 1);
Ví dụ: cuộc hội thoại có thể mất 3 hoặc 8 lượt để đặt chuyến bay, nhưng kết quả hợp lệ vẫn phải có đúng một booking. Đây là loại assertion bền vững hơn trong hệ thống stateful.
Giả lập dependency để test có thể lặp lại
Không nên chạy toàn bộ test với API bên thứ ba trực tiếp. Các dependency đó có thể:
- Rate limit request.
- Thay đổi dữ liệu.
- Gặp lỗi mạng.
- Trả về kết quả theo thời gian thực.
- Tạo thêm nguồn ngẫu nhiên ngoài mô hình.
Để test có thể lặp lại, hãy mock mọi thứ không phải hành vi bạn đang kiểm tra.
Ví dụ:
- Payment API luôn trả về cùng một biên lai.
- Search API luôn trả về cùng ba kết quả.
- Inventory API luôn trả về cùng trạng thái hàng tồn.
- Weather API luôn trả về một payload cố định.
Khi đó, phần biến thiên còn lại chủ yếu là hành vi của tác nhân.
Mock cũng giúp bạn ép các edge case mà API thật khó tạo theo yêu cầu:
{
"status": "success",
"data": null
}
Hoặc:
{
"error": "UPSTREAM_TIMEOUT"
}
Sau đó, bạn kiểm tra tác nhân xử lý tình huống đó đúng cách hay không.
Dùng Apidog để thiết lập mock server với response ổn định cho các dependency của tác nhân, sau đó kết hợp với schema validation và assertion về payload. Đây là một phần quan trọng của kiểm thử AI tác nhân, nơi mock và assertion cần hoạt động cùng nhau.
Apidog phù hợp ở đâu và không phù hợp ở đâu
Cần phân biệt rõ vai trò của công cụ.
Apidog là nền tảng thiết kế, kiểm thử và giả lập API. Nó không phải:
- Framework xây dựng tác nhân.
- Model host.
- Agent runtime.
- Hệ thống điều phối reasoning.
- Nền tảng đánh giá hoặc quan sát chất lượng mô hình.
Apidog không xây dựng hay chạy tác nhân của bạn. Điểm phù hợp của nó là lớp API mà tác nhân giao tiếp.
Bạn có thể dùng Apidog để:
- Xác thực phản hồi API của tác nhân bằng schema.
- Kiểm tra response shape, kiểu dữ liệu, enum và range.
- Kiểm tra required field và forbidden field.
- Xác thực payload của tool call.
- Giả lập API dependency bằng response ổn định.
- Chạy regression test dựa trên hợp đồng API.
Đây là ranh giới đúng: kiểm tra request/response contract, không cố kiểm tra từng token mà mô hình sinh ra.
Kiểm tra hợp đồng, không kiểm tra cách diễn đạt
Tính không xác định không phải lỗi có thể loại bỏ chỉ bằng temperature=0. Đó là thuộc tính thực tế của việc chạy mô hình ngôn ngữ trong một hệ thống phục vụ thay đổi theo thời gian.
Để xây dựng bộ kiểm thử đáng tin cậy cho tác nhân AI:
- Dùng JSON Schema cho output có cấu trúc.
- Kiểm tra tool call payload thay vì lý do bằng ngôn ngữ tự nhiên.
- Dùng range cho số.
- Kiểm tra trường bắt buộc và trường bị cấm.
- Dùng semantic check như một lớp bổ sung cho văn bản tự do.
- Snapshot cấu trúc, không snapshot từng câu.
- Reset hoặc seed state trước mỗi test.
- Mock dependency bên ngoài.
Hãy chọn một assertion flaky trong bộ test của bạn tuần này và viết lại nó thành schema validation hoặc range check. Khi test chỉ đỏ vì hợp đồng thực sự bị phá vỡ, CI mới trở thành tín hiệu đáng tin cậy.
Top comments (0)