Bài 5: Agent Evaluation & Observability¶
Tổng quan¶
RAGAS và LLM-as-judge (Module I, Bài 7) đánh giá output cuối cùng - đủ cho RAG nhưng không đủ cho agent, vì agent có thể ra đúng đáp án bằng con đường sai (hoặc tốn kém, hoặc nguy hiểm). Bài này mở rộng evaluation sang trajectory - toàn bộ chuỗi hành động agent thực hiện - và cách giám sát agent trong production.
1. Eval Dimensions¶
graph TD
EVAL[Agent Evaluation] --> TS[Task Success<br/>Có đạt mục tiêu không?]
EVAL --> TQ[Trajectory Quality<br/>Con đường đi có hợp lý không?]
EVAL --> TA[Tool Accuracy<br/>Chọn đúng tool, đúng tham số?]
EVAL --> CL[Cost & Latency<br/>Có hiệu quả không?]
| Chiều đánh giá | Câu hỏi | Ví dụ vấn đề nếu bỏ qua |
|---|---|---|
| Task Success | Agent có hoàn thành mục tiêu người dùng đề ra không? | Đây là metric "must-have" nhưng không đủ - có thể đúng do may mắn |
| Trajectory Quality | Chuỗi bước có logic, không lặp vô ích, không đi đường vòng? | Agent trả lời đúng nhưng gọi tool 15 lần cho task cần 3 lần |
| Tool Accuracy | Có chọn đúng tool? Tham số truyền vào có chính xác? | Agent gọi get_weather(city="Hà Lội") - lỗi chính tả khiến tool fail |
| Cost & Latency | Tổng token, tổng thời gian, tổng số lời gọi LLM/tool? | 2 agent cùng ra đúng đáp án nhưng 1 agent tốn gấp 5 lần chi phí |
Task Success cao không có nghĩa là agent tốt
Một agent có thể đạt task success 95% nhưng trajectory kém - gọi tool thừa, lãng phí chi phí, hoặc thực hiện hành động rủi ro (xoá nhầm dữ liệu rồi tạo lại) mà vẫn ra kết quả đúng cuối cùng. Production cần đánh giá cả 4 chiều, không chỉ chiều cuối.
2. Methodologies¶
End-to-end Evaluation¶
So sánh output cuối với expected outcome - tương tự LLM-as-judge ở Module I nhưng áp dụng cho toàn bộ agent run.
from openai import OpenAI
import json
client = OpenAI()
def evaluate_task_success(task: str, final_output: str, success_criteria: str) -> dict:
judge_prompt = f"""Đánh giá agent có hoàn thành nhiệm vụ không.
Nhiệm vụ: {task}
Tiêu chí thành công: {success_criteria}
Kết quả agent trả về: {final_output}
Trả về JSON: {{"success": true/false, "score": 0-1, "reasoning": "..."}}"""
response = client.chat.completions.create(
model="gpt-4o-mini",
messages=[{"role": "user", "content": judge_prompt}],
response_format={"type": "json_object"},
)
return json.loads(response.choices[0].message.content)
Trajectory Evaluation¶
Đánh giá toàn bộ chuỗi Thought/Action/Observation, không chỉ câu trả lời cuối - phát hiện các vấn đề mà end-to-end eval bỏ sót.
def evaluate_trajectory(task: str, trajectory: list[dict]) -> dict:
"""
trajectory: [{"action": "search_news", "args": {...}, "observation": "..."}, ...]
"""
formatted = "\n".join([
f"Bước {i+1}: gọi {step['action']}({step['args']}) → {step['observation'][:150]}"
for i, step in enumerate(trajectory)
])
judge_prompt = f"""Đánh giá chuỗi hành động của agent cho nhiệm vụ sau.
Nhiệm vụ: {task}
Chuỗi hành động:
{formatted}
Đánh giá theo tiêu chí (1-5):
1. Efficiency: Có bước thừa/lặp không cần thiết không?
2. Logical order: Thứ tự bước có hợp lý không?
3. Tool correctness: Tool và tham số có phù hợp với mục đích từng bước không?
4. Recovery: Nếu có bước lỗi, agent có xử lý hợp lý không?
Trả về JSON: {{"efficiency": X, "logical_order": X, "tool_correctness": X, "recovery": X, "issues": ["..."]}}"""
response = client.chat.completions.create(
model="gpt-4o-mini",
messages=[{"role": "user", "content": judge_prompt}],
response_format={"type": "json_object"},
)
return json.loads(response.choices[0].message.content)
LLM-as-Judge cho Agent¶
Áp dụng lại các nguyên tắc giảm bias từ Module I (Bài 7) - đặc biệt quan trọng hơn với agent vì trajectory dài, dễ bị verbosity bias (judge thiên vị trajectory dài, chi tiết hơn dù không hiệu quả hơn).
Tái sử dụng nguyên tắc giảm bias từ Module I
6 loại bias của LLM-as-judge (position, verbosity, self-preference, style, calibration drift, preference leakage) đã học ở Module I, Bài 7 áp dụng nguyên vẹn cho agent evaluation. Với trajectory dài, verbosity bias càng dễ xảy ra - judge có thể đánh giá cao trajectory "trông kỹ lưỡng" dù thực chất kém hiệu quả hơn.
Unit Tests cho Tool Calls¶
Kiểm tra xác định (deterministic) - không cần LLM judge - cho các trường hợp có đáp án rõ ràng.
import pytest
def test_agent_calls_correct_tool_for_weather_query():
result = run_agent("Thời tiết Hà Nội hôm nay thế nào?")
tool_calls = extract_tool_calls(result)
assert len(tool_calls) == 1
assert tool_calls[0]["name"] == "get_weather"
assert tool_calls[0]["args"]["city"] in ["Hà Nội", "Hanoi"]
def test_agent_does_not_call_tool_for_greeting():
"""Agent không nên gọi tool cho câu chào đơn giản - kiểm tra tránh over-calling."""
result = run_agent("Xin chào!")
tool_calls = extract_tool_calls(result)
assert len(tool_calls) == 0
def test_agent_respects_max_retry_on_tool_error():
result = run_agent_with_mocked_failing_tool(max_retries=3)
assert result["retry_count"] <= 3
Phù hợp khi: Test case có đáp án đúng/sai rõ ràng (tool nào nên/không nên được gọi, giới hạn retry có được tôn trọng không) - nhanh, rẻ, chạy được trong CI.
So sánh methodologies¶
| Phương pháp | Chi phí | Tốc độ | Phát hiện được |
|---|---|---|---|
| Unit tests | Thấp | Nhanh (không cần LLM) | Lỗi rõ ràng, có thể assert được |
| End-to-end (LLM judge) | Trung bình | Trung bình | Task có hoàn thành không |
| Trajectory eval | Cao (chuỗi dài) | Chậm | Hiệu quả, đường đi hợp lý, tool đúng |
3. Observability¶
Evaluation chạy offline (trước khi deploy, trên test set). Observability giám sát agent online (production, real-time).
Tracing với LangSmith¶
import os
os.environ["LANGCHAIN_TRACING_V2"] = "true"
os.environ["LANGCHAIN_API_KEY"] = "ls__..."
os.environ["LANGCHAIN_PROJECT"] = "vietnamese-research-agent"
# Mọi lời gọi LangGraph agent tự động được trace - mỗi node, mỗi tool call,
# mỗi token usage đều xuất hiện trên dashboard LangSmith
result = app.invoke({"messages": [...]}, config=config)
Trace của agent hiển thị dạng cây, phản ánh đúng graph: node nào gọi node nào, tool nào được gọi với tham số gì, mất bao lâu, tốn bao nhiêu token - khác với trace của 1 lời gọi LLM đơn (Module I) vốn chỉ là 1 span phẳng.
LangFuse (self-hosted alternative)¶
from langfuse.callback import CallbackHandler
langfuse_handler = CallbackHandler(
public_key="pk-lf-...",
secret_key="sk-lf-...",
host="https://cloud.langfuse.com",
)
result = app.invoke(
{"messages": [...]},
config={"callbacks": [langfuse_handler], **config},
)
OpenTelemetry cho hệ thống đa framework¶
Khi agent kết hợp nhiều framework/service (vd: LangGraph + custom microservice), OpenTelemetry cung cấp chuẩn tracing không gắn với 1 vendor cụ thể.
from opentelemetry import trace
from opentelemetry.sdk.trace import TracerProvider
from opentelemetry.sdk.trace.export import BatchSpanProcessor
from opentelemetry.exporter.otlp.proto.grpc.trace_exporter import OTLPSpanExporter
trace.set_tracer_provider(TracerProvider())
trace.get_tracer_provider().add_span_processor(
BatchSpanProcessor(OTLPSpanExporter(endpoint="http://localhost:4317"))
)
tracer = trace.get_tracer("agent-service")
def agent_node(state: AgentState) -> dict:
with tracer.start_as_current_span("agent_node") as span:
response = llm.invoke(state["messages"])
span.set_attribute("token_usage", response.usage_metadata["total_tokens"])
return {"messages": [response]}
| Công cụ | Điểm mạnh | Dùng khi |
|---|---|---|
| LangSmith | Tích hợp sâu với LangGraph/LangChain, dashboard sẵn cho agent trace | Stack chính là LangChain/LangGraph |
| LangFuse | Open-source, tự host được, không vendor lock-in | Cần kiểm soát dữ liệu, ngân sách hạn chế |
| OpenTelemetry | Chuẩn mở, tích hợp được với hệ thống ngoài LLM (backend, DB) | Kiến trúc đa framework, cần trace xuyên suốt toàn hệ thống |
Metrics cần giám sát cho Agent (mở rộng từ Module I)¶
| Metric | Mô tả | Alert threshold |
|---|---|---|
| Task success rate | % task hoàn thành đúng | < 90% |
| Avg steps per task | Số bước trung bình để hoàn thành | Tăng đột biến so với baseline |
| Tool error rate | % tool call thất bại | > 5% |
| Cost per task | Tổng token × giá, trung bình mỗi task | Vượt budget |
| Human intervention rate | % task cần HITL can thiệp | Tăng bất thường (dấu hiệu agent kém tự tin) |
| Loop detection | Số lần agent lặp lại cùng 1 tool call | > 3 lần liên tiếp |
Tóm tắt¶
graph TD
A[Đánh giá agent] --> B{Offline hay Online?}
B -->|Offline, trước deploy| C[Evaluation]
B -->|Online, production| D[Observability]
C --> E{Loại kiểm tra?}
E -->|Đáp án rõ ràng| F[Unit tests]
E -->|Đánh giá output| G[End-to-end LLM judge]
E -->|Đánh giá quá trình| H[Trajectory eval]
D --> I[LangSmith / LangFuse / OpenTelemetry]
I --> J[Giám sát: success rate, cost, tool errors, loop detection]
| Khái niệm | Ghi nhớ |
|---|---|
| Task Success | Cần nhưng không đủ - đúng đáp án không đồng nghĩa quá trình tốt |
| Trajectory Quality | Đánh giá cả con đường đi, không chỉ đích đến |
| Tool Accuracy | Chọn đúng tool + đúng tham số |
| Cost & Latency | So sánh hiệu quả giữa các agent cùng đạt task success |
| Unit tests | Nhanh, rẻ, cho case có đáp án xác định - chạy trong CI |
| Trajectory eval | Cần LLM judge, phát hiện lãng phí/đi đường vòng |
| LangSmith/LangFuse | Trace cây phản ánh cấu trúc graph, khác trace phẳng của Module I |
| Loop detection | Metric đặc thù agent - phát hiện agent kẹt lặp vô ích |