"Không đo được thì không tối ưu được." Bài này hướng dẫn dựng lớp observability tối thiểu nhưng đủ dùng, để mọi quyết định tối ưu về sau đều dựa trên số liệu chứ không cảm tính.
Ba tầng cần đo
- Tầng lệnh gọi LLM (span): mỗi request tới model.
- Tầng bước agent (step): một vòng reasoning/tool.
- Tầng phiên (trace/session): toàn bộ một yêu cầu người dùng, gồm nhiều bước.
Bọc client bằng một decorator đo lường
Cách đơn giản và bền nhất: bọc mọi lệnh gọi LLM qua một hàm trung gian.
import time, uuid
def call_llm(model, messages, use_case, trace_id):
t0 = time.perf_counter()
resp = provider.chat(model=model, messages=messages)
dt = (time.perf_counter() - t0) * 1000
u = resp.usage
emit_metric({
"trace_id": trace_id,
"span_id": str(uuid.uuid4()),
"model": model,
"use_case": use_case,
"in_tok": u.input_tokens,
"out_tok": u.output_tokens,
"cached_tok": getattr(u, "cache_read_tokens", 0),
"latency_ms": round(dt, 1),
"ttft_ms": resp.ttft_ms, # nếu stream
})
return resp
Điểm mấu chốt: trace_id xuyên suốt một phiên để ghép các span lại thành cây.
Các bước triển khai
- Chọn nơi lưu: đơn giản thì log JSON + một bảng SQL; bài bản thì dùng công cụ tracing LLM (đo token, cost, latency theo trace).
- Sinh
trace_idở điểm vào (HTTP handler / message queue) và truyền xuống mọi lệnh gọi. - Chuẩn hóa schema metric (như trên) để mọi service ghi giống nhau.
- Tách
ttft_mskhỏilatency_mskhi stream — TTFT quyết định cảm giác nhanh, tổng latency quyết định chi phí thời gian. - Ghi
cached_tokđể đo hiệu quả cache (Bài 3). - Dựng 4 dashboard cốt lõi: cost/ngày theo use-case, p50/p95/p99 latency, tỉ lệ cache hit, phân bố số bước/phiên.
Chỉ số nên theo dõi
| Chỉ số | Ý nghĩa | Ngưỡng cảnh báo (ví dụ) |
|---|---|---|
| Cost/1.000 phiên | Kinh tế đơn vị | Tăng >20% tuần |
| p95 latency | Trải nghiệm đuôi | > SLA đặt ra |
| Cache hit rate | Hiệu quả cache | < 30% |
| Bước/phiên (p95) | Agent lặp vòng | > 8 bước |
| Tỉ lệ output rỗng/lỗi | Chất lượng | > 1% |
Khung tư duy: USE + RED
- RED (Rate, Errors, Duration) cho từng use-case: bao nhiêu lượt, bao nhiêu lỗi, mất bao lâu.
- USE ánh xạ sang chi phí: mỗi lượt tiêu bao nhiêu token → tiền.
Sai lầm thường gặp
- Chỉ log tổng, không log theo trace: không truy được phiên nào lặp vòng.
- Bỏ qua token đã cache: tưởng cache hoạt động nhưng không có số để chứng minh.
- Đo latency trung bình: che giấu đuôi p95/p99.
- Sampling quá tay từ đầu: mất dữ liệu hiếm nhưng đắt (phiên 40 token-triệu). Log 100% trước, sampling sau khi hiểu pattern.
- Không gắn version prompt/model vào metric: đổi prompt xong không so sánh được trước/sau.
Ví dụ đọc số liệu ra hành động
Giả sử sau 3 ngày bạn thấy: use-case answer chiếm 72% tổng chi phí, cache hit rate chỉ 8%, và p95 số bước/phiên là 11. Ba con số này lập tức chỉ đường: (1) answer là nơi phải nhắm routing/nén; (2) cache gần như chưa hoạt động, cần kiểm tra prefix có bị xáo trộn không; (3) 11 bước/phiên là dấu hiệu agent lặp vòng, cần đặt trần bước. Không có lớp đo này, cả ba vấn đề đều vô hình và bạn sẽ tối ưu theo cảm tính.
Một mẹo vận hành: thêm cột prompt_version và model_version vào mỗi metric. Khi bạn đổi prompt hay đổi model, dashboard tự tách được 'trước' và 'sau', giúp bạn khẳng định thay đổi thực sự cải thiện chứ không phải trùng hợp do traffic dao động.
Khi lớp đo này chạy vài ngày, bạn sẽ có đủ số liệu để chọn đòn bẩy đúng ở các bài sau.