Evaluation & Monitoring: Làm sao biết Agent làm đúng hay đang \"ảo giác\"?
7/19/2026 · 15p đọc
title: "Evaluation & Monitoring: Làm sao biết Agent làm đúng hay đang "ảo giác"?"
series: "AI-Native Solutions Architect: Từ Coder đến Kiến trúc sư AI"
season: "Season 3 — AI Agents & Platform Engineering"
order: 26
audience: "Software Engineer hướng tới Solutions Architect"
reading_time: "14 phút"
tags: ["agent evaluation", "eval framework", "observability", "tracing", "hallucination", "monitoring", "llm agents", "solutions architect", "ai-native"]
Evaluation & Monitoring: Làm sao biết Agent làm đúng hay đang "ảo giác"?
Bạn deploy một agent xử lý ticket hỗ trợ khách hàng tự động — nó đọc yêu cầu, tra cứu đơn hàng, gọi API hoàn tiền nếu đủ điều kiện. Tuần đầu tiên chạy êm, dashboard xanh, không ai phàn nàn. Tuần thứ ba, kế toán báo động: số tiền hoàn trả tháng này cao gấp ba lần bình thường. Đào sâu log mới phát hiện agent đã tự "suy luận" rằng một nhóm khách hàng đủ điều kiện hoàn tiền dựa trên một điều kiện không có trong policy — nó không sai cú pháp, không crash, chỉ đơn giản là hiểu sai ngữ cảnh và hành động sai trong ba tuần liền mà không ai biết.
Đây chính là khác biệt căn bản giữa một script tự động hóa truyền thống và một AI agent. Script sai thì lỗi rành rành — exception, log đỏ, alert bắn ngay. Agent "sai" thì trông vẫn giống như đang hoạt động bình thường: nó vẫn trả về response hợp lệ về mặt cú pháp, vẫn gọi tool thành công, vẫn đóng ticket. Cái sai nằm ở tầng suy luận — logic đằng sau quyết định — và tầng đó không hề lộ ra ở output cuối cùng. Nếu bạn chỉ giám sát "agent có crash không" và "response có trả về không", bạn đang bỏ sót đúng loại lỗi nguy hiểm nhất.
Vấn đề càng nghiêm trọng hơn ở quy mô production, nơi agent chạy hàng nghìn lần một ngày, không có con người review từng bước. Một agent coding tự sửa lỗi có thể "tự tin" đưa ra một patch sai logic nhưng compile được. Một agent phân tích dữ liệu có thể trích dẫn một con số không tồn tại trong nguồn dữ liệu gốc. Cái giá của việc không phát hiện kịp không phải là một lỗi 500 dễ thấy — mà là hậu quả nghiệp vụ âm thầm tích lũy cho tới khi ai đó, thường không phải kỹ sư, phát hiện ra bằng cách khác.
Vấn đề
Xét lại pipeline agent hỗ trợ khách hàng ở trên, được xây dựng theo kiến trúc điển hình: nhận yêu cầu → LLM phân tích intent → gọi tool tra cứu đơn hàng → LLM quyết định hành động → gọi tool thực thi (hoàn tiền, đổi trạng thái, gửi email). Từ góc nhìn vận hành truyền thống, hệ thống này "khỏe mạnh": API response time bình thường, error rate gần 0%, không có exception nào trong log.
Nhưng "khỏe mạnh" theo nghĩa hạ tầng không đồng nghĩa với "đúng" theo nghĩa nghiệp vụ. Ba loại lỗi phổ biến nhất của agent hoàn toàn không xuất hiện trong monitoring truyền thống:
- Hallucination im lặng: agent trích dẫn một chính sách hoàn tiền không tồn tại, hoặc "nhớ nhầm" điều khoản hợp đồng vì context bị cắt bớt khi vượt giới hạn token. Output vẫn là JSON hợp lệ, tool call vẫn thành công.
- Đi lệch mục tiêu (goal drift): agent được giao task "kiểm tra đơn hàng trễ" nhưng qua vài bước suy luận, nó tự chuyển sang "hủy đơn hàng" vì hiểu sai ý định ban đầu — không có lỗi kỹ thuật nào, chỉ là chuỗi quyết định đi sai hướng.
- Lãng phí tài nguyên âm thầm: agent bị kẹt trong vòng lặp gọi lại cùng một tool nhiều lần vì không nhận ra kết quả đã đủ, khiến chi phí token tăng 5-10 lần so với kỳ vọng mà không ai để ý cho tới khi nhận hóa đơn API cuối tháng.
Cái khó ở đây là: bạn không thể "test" một agent bằng cách chạy lại input cũ và so sánh output y hệt, như unit test truyền thống — vì LLM có tính không xác định (non-deterministic), và "đúng" của một agent không phải một giá trị cố định mà là một phạm vi hành vi chấp nhận được. Bạn cần một cách tiếp cận khác: eval framework có cấu trúc, log đủ chi tiết để truy vết lại quá trình suy luận, và cảnh báo tự động cho các dấu hiệu bất thường — thay vì chờ hậu quả nghiệp vụ tự lộ ra.
Kỹ thuật cốt lõi
Ba lớp phòng thủ: Eval trước deploy, Tracing khi chạy, Alerting khi bất thường
Vấn đề "agent làm đúng hay đang ảo giác" không giải quyết được bằng một công cụ duy nhất — nó cần ba lớp bổ trợ nhau, mỗi lớp bắt một loại lỗi khác nhau ở một thời điểm khác nhau trong vòng đời agent:
flowchart TD
subgraph "1. Trước deploy — Eval Framework"
E1[Bộ test case chuẩn] --> E2[Chạy agent qua từng case]
E2 --> E3{Đạt ngưỡng pass rate?}
E3 -->|Không| E4[Chặn deploy, quay lại tune prompt/logic]
E3 -->|Có| E5[Cho phép deploy]
end
subgraph "2. Khi chạy — Tracing"
T1[Mỗi bước agent] --> T2[Log: input/output, tool call, reasoning]
T2 --> T3[Lưu trace có thể truy vết ngược]
end
subgraph "3. Khi bất thường — Alerting"
A1[Theo dõi metric real-time] --> A2{Vượt ngưỡng?<br/>lặp vô hạn, sai tool, chi phí spike}
A2 -->|Có| A3[Cảnh báo + auto-pause agent]
A2 -->|Không| A4[Tiếp tục chạy]
end
E5 --> T1
T3 --> A1
Lớp 1 — Eval framework trả lời câu hỏi "agent này có nên được deploy không", chạy offline trước khi lên production, giống CI/CD gate cho test truyền thống nhưng tiêu chí đánh giá khác hẳn — không phải "assert equal" mà là chấm điểm theo nhiều tiêu chí định tính lẫn định lượng.
Lớp 2 — Tracing trả lời câu hỏi "chuyện gì đã thực sự xảy ra" khi có sự cố, bằng cách ghi lại không chỉ kết quả cuối mà toàn bộ chuỗi suy luận — mỗi lần agent gọi LLM để "nghĩ" bước tiếp theo, mỗi tool call, mỗi input/output trung gian. Đây là khác biệt lớn nhất so với logging ứng dụng truyền thống: bạn cần log cả lý do agent chọn hành động đó, không chỉ hành động.
Lớp 3 — Alerting real-time trả lời câu hỏi "có cần can thiệp ngay bây giờ không", bằng cách theo dõi các tín hiệu bất thường có thể phát hiện được ngay cả khi chưa biết chi tiết agent đang nghĩ gì: số bước tăng đột biến, cùng một tool bị gọi lặp lại, chi phí token vượt ngưỡng dự kiến.
Ba lớp này không thay thế nhau. Một agent qua được eval trước deploy vẫn có thể đi lệch hướng khi gặp input thực tế chưa từng thấy trong test case — đó là lý do tracing và alerting vẫn cần chạy song song suốt vòng đời, không chỉ dừng ở bước kiểm định ban đầu.
Vì sao "test truyền thống" không đủ cho agent
Với code truyền thống, một unit test khẳng định f(x) == y — một input, một output đúng duy nhất. Với agent dựa trên LLM, cùng một input có thể tạo ra nhiều chuỗi hành động khác nhau nhưng đều "đúng" theo nghĩa đạt mục tiêu (ví dụ agent có thể tra cứu đơn hàng trước rồi tra khách hàng, hoặc ngược lại — thứ tự không quan trọng, miễn kết quả cuối chính xác). Vì vậy, eval cho agent cần đánh giá trên nhiều trục: outcome (có đạt mục tiêu không), process (số bước, tool nào được gọi có hợp lý không), và cost (chi phí thực thi có nằm trong ngân sách không) — ba trục độc lập, một agent có thể đạt outcome đúng nhưng process quá tốn kém, hoặc process gọn nhưng outcome sai.
Thực hành
Bảng tiêu chí eval cho agent (dùng làm gate trước khi deploy chính thức)
Đây là bảng tiêu chí cụ thể áp dụng cho agent xử lý ticket hỗ trợ khách hàng ở ví dụ trên — điều chỉnh ngưỡng theo agent thực tế của bạn, nhưng giữ nguyên bốn trục đánh giá:
| Tiêu chí | Cách đo | Ngưỡng đạt | Ghi chú |
|---|---|---|---|
| Task hoàn thành đúng mục tiêu | So khớp outcome cuối (đã hoàn tiền đúng điều kiện? đã trả lời đúng câu hỏi khách?) với nhãn "expected outcome" gán thủ công cho từng test case | ≥ 95% trên bộ test case chuẩn (tối thiểu 50 case, phủ cả case điển hình lẫn edge case) | Đánh giá bằng LLM-as-judge hoặc rule-based check, không dùng "so khớp chuỗi y hệt" |
| Số bước thực hiện hợp lý | Đếm số lần agent gọi LLM (reasoning step) + số tool call, so với baseline kỳ vọng | Không vượt quá 1.5x số bước trung bình của baseline cho cùng loại task | Vượt ngưỡng là dấu hiệu agent đang "loanh quanh" thay vì đi thẳng tới mục tiêu |
| Tool call thừa/không cần thiết | Đếm số lần gọi tool mà kết quả không được dùng trong quyết định tiếp theo, hoặc gọi lại tool đã có kết quả từ bước trước | 0 lần gọi trùng lặp không cần thiết trên mỗi case | Dấu hiệu agent không "nhớ" context đã có, hoặc thiếu điều kiện dừng |
| Chi phí token trong ngưỡng | Tổng token (input + output) tiêu thụ cho một task, quy đổi chi phí | Không vượt quá ngân sách đã ước tính x 1.3 (biên độ dung sai 30%) | Ngưỡng cứng, vi phạm là chặn deploy — không phải cảnh báo mềm |
| Không hallucination trên dữ liệu tham chiếu | Đối chiếu mọi số liệu/chính sách agent trích dẫn với nguồn dữ liệu gốc (DB, policy doc) | 100% — không chấp nhận sai số ở tiêu chí này | Đây là tiêu chí "chặn cứng", không có ngưỡng linh hoạt vì hậu quả nghiệp vụ trực tiếp |
Prompt mẫu: sinh bộ test case chuẩn cho eval framework
Vai trò: Bạn là kỹ sư QA chuyên viết test case cho AI agent.
Bối cảnh: Agent xử lý ticket hỗ trợ khách hàng, luồng xử lý:
1. Nhận nội dung ticket (text tự do từ khách hàng)
2. Phân tích intent (hỏi thông tin / yêu cầu hoàn tiền / khiếu nại)
3. Gọi tool `get_order(order_id)` để tra cứu đơn hàng
4. Nếu là yêu cầu hoàn tiền: kiểm tra điều kiện (đơn hàng trong 30 ngày,
trạng thái "delivered", chưa từng hoàn tiền trước đó)
5. Nếu đủ điều kiện: gọi tool `process_refund(order_id, amount)`
6. Trả lời khách hàng bằng ngôn ngữ tự nhiên, giải thích quyết định
Yêu cầu: Viết 15 test case cho bộ eval, đảm bảo phủ đủ các nhóm sau
(mỗi nhóm ít nhất 2 case, có ít nhất 1 edge case gây nhầm lẫn cho mỗi nhóm):
1. Case rõ ràng đủ điều kiện hoàn tiền (happy path)
2. Case rõ ràng KHÔNG đủ điều kiện (đơn hàng quá 30 ngày, đã hoàn tiền rồi)
3. Case mơ hồ — khách hàng không nói rõ order_id, agent phải hỏi lại
thay vì đoán hoặc "tự chọn" một đơn hàng bất kỳ
4. Case adversarial — khách hàng cố tình mô tả sai lệch tình huống
(ví dụ nói "đơn hàng giao trễ 2 tháng" nhưng thực tế data cho thấy
giao đúng hẹn) để kiểm tra agent có tin lời khách hơn data thật không
5. Case yêu cầu ngoài phạm vi (khách hỏi về vấn đề kỹ thuật không liên
quan đến đơn hàng) — agent phải nhận diện và không cố xử lý sai domain
6. Case chi phí — ticket có ngữ cảnh dài (đính kèm lịch sử chat 20 tin
nhắn) để kiểm tra agent có tóm tắt/lọc context hợp lý hay tốn token
xử lý toàn bộ
Với mỗi test case, trả về đúng format sau:
- id: mã số case
- input: nội dung ticket + dữ liệu order giả lập liên quan
- expected_outcome: kết quả đúng phải đạt được (hoàn tiền/không hoàn
tiền/hỏi lại/từ chối xử lý), nêu rõ lý do
- expected_max_steps: số bước tối đa hợp lý (reasoning + tool call)
- red_flag: điều gì nếu agent làm sẽ coi là THẤT BẠI rõ ràng (ví dụ:
"hoàn tiền dù đơn hàng đã quá 30 ngày", "gọi process_refund quá 1 lần
cho cùng order_id", "bịa ra chính sách hoàn tiền không có trong policy")
Cấu trúc log tối thiểu cho mỗi bước agent thực hiện
Để tracing hữu ích khi debug sự cố, mỗi bước agent thực hiện cần ghi tối thiểu các trường sau — thiếu bất kỳ trường nào cũng khiến việc truy vết ngược trở nên gần như không thể:
{
"trace_id": "agent-run-8f3a2b1c",
"step_number": 3,
"timestamp": "2026-07-19T09:42:11.203Z",
"step_type": "tool_call",
"action": "process_refund",
"reasoning": "Đơn hàng ORD-88421 giao ngày 2026-06-25 (24 ngày trước), trạng thái delivered, chưa có refund_record trước đó -> đủ điều kiện hoàn tiền theo policy 30 ngày.",
"input": {
"order_id": "ORD-88421",
"amount": 249000,
"reason": "customer_request_defective_item"
},
"output": {
"status": "success",
"refund_id": "RF-33210"
},
"duration_ms": 412,
"token_usage": {
"input_tokens": 1840,
"output_tokens": 96
},
"cost_usd": 0.0071,
"model": "claude-sonnet-5",
"previous_step_id": 2,
"confidence_signal": "high"
}
Ba trường quan trọng nhất mà đội ngũ thường bỏ sót khi mới xây logging cho agent: reasoning (không chỉ log input/output, mà log cả câu agent "giải thích" tại sao nó chọn hành động này — đây chính là thứ giúp bạn phát hiện hallucination khi review lại, vì lý do sai thường lộ rõ hơn kết quả sai), token_usage/cost_usd ở từng bước (không chỉ tổng cuối request, để phát hiện chính xác bước nào gây phình chi phí), và previous_step_id (để dựng lại toàn bộ chuỗi quyết định theo đúng thứ tự khi cần audit).
Snippet minh họa: middleware giám sát bất thường theo thời gian thực
// agent-monitor.service.ts — chặn agent khi vượt ngưỡng bất thường
import { Injectable, Logger } from '@nestjs/common';
interface AgentRunState {
traceId: string;
stepCount: number;
toolCallHistory: string[];
cumulativeTokens: number;
cumulativeCostUsd: number;
startedAt: number;
}
@Injectable()
export class AgentMonitorService {
private readonly logger = new Logger(AgentMonitorService.name);
private readonly runs = new Map<string, AgentRunState>();
private readonly LIMITS = {
MAX_STEPS: 15, // ngưỡng cứng — vượt quá là nghi ngờ vòng lặp
MAX_REPEATED_TOOL_CALL: 3, // cùng 1 tool + input gọi quá 3 lần
MAX_COST_USD: 0.50, // chi phí tối đa cho 1 task, theo baseline đo được
MAX_DURATION_MS: 60_000,
};
startRun(traceId: string): void {
this.runs.set(traceId, {
traceId,
stepCount: 0,
toolCallHistory: [],
cumulativeTokens: 0,
cumulativeCostUsd: 0,
startedAt: Date.now(),
});
}
// Gọi hàm này sau MỖI bước agent thực hiện — trước khi cho phép bước tiếp theo
checkStep(
traceId: string,
toolCallSignature: string,
stepTokens: number,
stepCostUsd: number,
): { shouldContinue: boolean; reason?: string } {
const state = this.runs.get(traceId);
if (!state) return { shouldContinue: true };
state.stepCount += 1;
state.toolCallHistory.push(toolCallSignature);
state.cumulativeTokens += stepTokens;
state.cumulativeCostUsd += stepCostUsd;
if (state.stepCount > this.LIMITS.MAX_STEPS) {
return this.abort(traceId, `Vượt ${this.LIMITS.MAX_STEPS} bước — nghi ngờ vòng lặp/goal drift`);
}
const repeatCount = state.toolCallHistory.filter((s) => s === toolCallSignature).length;
if (repeatCount > this.LIMITS.MAX_REPEATED_TOOL_CALL) {
return this.abort(traceId, `Gọi lặp "${toolCallSignature}" quá ${this.LIMITS.MAX_REPEATED_TOOL_CALL} lần`);
}
if (state.cumulativeCostUsd > this.LIMITS.MAX_COST_USD) {
return this.abort(traceId, `Chi phí $${state.cumulativeCostUsd.toFixed(2)} vượt ngưỡng $${this.LIMITS.MAX_COST_USD}`);
}
if (Date.now() - state.startedAt > this.LIMITS.MAX_DURATION_MS) {
return this.abort(traceId, `Chạy quá ${this.LIMITS.MAX_DURATION_MS}ms — treo hoặc kẹt`);
}
return { shouldContinue: true };
}
private abort(traceId: string, reason: string) {
this.logger.error(`[AGENT ABORT] trace=${traceId} reason=${reason}`);
// Bắn alert (Sentry/Slack webhook) kèm trace_id để đội on-call tra log ngay
return { shouldContinue: false, reason };
}
}
Cạm bẫy thường gặp
1. Đánh giá agent bằng cảm tính "có vẻ chạy ổn" thay vì bộ test case chuẩn hóa. Đây là cạm bẫy phổ biến nhất khi đội ngũ vội deploy: chạy thử agent với vài input demo, thấy output "đọc lên nghe hợp lý", rồi cho lên production. Vấn đề là "hợp lý" là đánh giá chủ quan, không lặp lại được, và không phủ được edge case. Khi agent gặp một input hơi khác so với những gì đã thử tay, không ai biết trước nó sẽ phản ứng ra sao — vì chưa từng có bộ test case ghi lại "hành vi đúng" là gì để so sánh. Nguyên tắc bắt buộc: mọi agent trước khi deploy chính thức phải chạy qua bộ eval case cố định, có pass rate đo được, và pass rate đó phải được ghi lại làm baseline cho lần deploy tiếp theo — không có "trông ổn" nào thay thế được con số.
2. Không giám sát chi phí/số bước, khiến agent âm thầm vượt xa ngân sách token dự kiến. Vì chi phí mỗi lần gọi LLM riêng lẻ thường nhỏ (vài cent), đội ngũ dễ chủ quan bỏ qua việc theo dõi tích lũy. Nhưng một agent bị kẹt trong vòng lặp — gọi lại cùng một tool vì không nhận ra đã có đủ thông tin để quyết định — có thể khiến chi phí một task tăng 10-20 lần so với baseline mà không hề crash hay báo lỗi rõ ràng. Tới cuối tháng, hóa đơn API tăng bất thường mới là dấu hiệu đầu tiên ai đó nhận ra — quá trễ để ngăn chặn ở từng lần chạy cụ thể. Ngưỡng cost/step phải được set cứng và enforce ở tầng middleware (như snippet ở trên), không chỉ để trong dashboard xem sau.
3. Log chỉ ghi input/output cuối cùng, bỏ qua reasoning và các bước trung gian. Khi sự cố xảy ra (ví dụ agent hoàn tiền sai), đội ngũ mở log lên và chỉ thấy "input: ticket X, output: đã hoàn tiền $249,000" — không có gì để hiểu tại sao agent quyết định vậy. Không có trace của quá trình suy luận, việc debug biến thành đoán mò hoặc phải chạy lại y hệt input đó (mà với LLM không xác định, chạy lại chưa chắc tái hiện được lỗi). Log reasoning và tool call ở từng bước không phải "nice to have" — nó là điều kiện tiên quyết để agent production có thể debug được khi có sự cố nghiêm trọng.
🧭 Góc nhìn Solutions Architect
Nếu agent này chạy sai trong ba tuần trước khi ai đó phát hiện qua kênh không phải monitoring của tôi, thì bộ metric tôi đang theo dõi thực sự đo được cái gì — "hệ thống chạy" hay "hệ thống làm đúng"? Ngưỡng cost/step tôi đặt ra có dựa trên số liệu baseline thực đo hay chỉ là con số đoán mò dễ bị bỏ qua khi áp lực deadline? Và nếu phải audit lại một quyết định agent đã đưa ra một tháng trước, tôi có đủ trace để tái dựng chính xác chuỗi suy luận đó, hay chỉ có một dòng log "success" trống rỗng?
🔗 Bài viết liên quan
- [Bài 24] Multi-Agent Systems: Coder, Reviewer, Tester — vì hệ thống multi-agent càng cần eval/tracing chặt vì lỗi ở một agent có thể lan sang agent khác (ans-24-multi-agent-systems-coder-reviewer-tester.md)
- [Bài 8] Security & Code Scanning: OWASP — cùng tư duy "đừng tin cảm tính, cần kiểm chứng có hệ thống" áp dụng từ code sang agent (ans-08-security-code-scanning-owasp.md)
Bài trước: Deploying AI Agents · Bài tiếp theo: AI Platform Engineering