Product Management
Đăng nhập
ESC

Nhập từ khóa để tìm kiếm

↑↓ Di chuyển
Enter Mở
ESC Đóng

Bài 2 — Trace & Logging: nhìn thấy agent thực sự đang làm gì

Đã đọc 0/10 mục · còn khoảng 4 phút
Cách chúng tôi ghi nhận

Một mục được tính là đã đọc khi nó nằm giữa màn hình đủ lâu so với độ dài của nó (bảng và sơ đồ tính một nửa), bạn vẫn đang cuộn hoặc bấm phím, và bạn đã thấy tới cuối mục. Lướt nhanh hoặc để tab mở không được tính. Chúng tôi chỉ lưu thời gian đọc từng mục — không lưu gì khác.

Bạn không thể debug thứ bạn không nhìn thấy. Với agent, "nhìn thấy" nghĩa là trace: bản ghi đầy đủ từng bước suy nghĩ → hành động → kết quả trong một lần chạy. Không có trace, mỗi lần agent sai bạn chỉ biết "output xấu" mà không biết ở đâu và tại sao. Bài này dựng hệ thống logging thực dụng, bật được ngay hôm nay.

Trace là gì và cần chứa gì

Một trace ghi lại một phiên (session/run) của agent, gồm nhiều span (bước). Tối thiểu mỗi bước phải có:

  • step_id, timestamp
  • type: llm_call | tool_call | tool_result | final_answer
  • input: prompt/messages gửi vào (kèm system prompt)
  • output: agent quyết định gì (text hoặc lời gọi tool + tham số)
  • latency_ms, tokens (in/out), cost
  • error (nếu có)

Ví dụ: một trace tối thiểu ở dạng JSON Lines

{"run_id":"r-88","step":1,"type":"llm_call","output":{"tool":"lookup_order","args":{"id":"A123"}},"latency_ms":820,"tokens_in":540,"tokens_out":40}
{"run_id":"r-88","step":2,"type":"tool_result","tool":"lookup_order","output":{"eta":"2026-07-12"},"latency_ms":130}
{"run_id":"r-88","step":3,"type":"final_answer","output":"Đơn A123 sẽ giao ngày 12/07/2026.","tokens_out":22}

Nhìn trace này bạn biết ngay: agent gọi đúng tool, nhận đúng dữ liệu, trả lời khớp. Nếu ở bước 3 nó ghi ngày khác dữ liệu tool → phát hiện hallucination tức thì.

Cách gắn logging tự động (pattern decorator)

Đừng rải print khắp nơi. Bọc mỗi lời gọi LLM và tool bằng một lớp ghi log tập trung:

import time, json

def log_step(run_id, step_type, inp, out, err=None, t0=None):
    rec = {
        "run_id": run_id, "type": step_type,
        "input": inp, "output": out, "error": err,
        "latency_ms": int((time.time()-t0)*1000) if t0 else None,
    }
    with open("traces.jsonl", "a") as f:
        f.write(json.dumps(rec, ensure_ascii=False) + "\n")

def traced_tool(fn, run_id):
    def wrapper(**kwargs):
        t0 = time.time()
        try:
            out = fn(**kwargs)
            log_step(run_id, "tool_result", {"tool": fn.__name__, "args": kwargs}, out, t0=t0)
            return out
        except Exception as e:
            log_step(run_id, "tool_error", {"tool": fn.__name__, "args": kwargs}, None, str(e), t0)
            raise
    return wrapper

Các bước triển khai logging trong dự án của bạn

  • Gán run_id duy nhất cho mỗi phiên (dùng uuid4) — để gộp mọi bước của một lần chạy lại với nhau.
  • Bọc lời gọi LLM: log system prompt (hoặc hash của nó), messages, và output thô (bao gồm cả lời gọi tool).
  • Bọc mọi tool: log tên tool, tham số, kết quả và lỗi. Tool error thường là gốc rễ của loop.
  • Ghi ra JSON Lines (.jsonl), mỗi dòng một bước — dễ append, dễ đọc bằng jq, dễ đổ vào DataFrame.
  • Đừng log dữ liệu nhạy cảm dạng thô: che (mask) số điện thoại, token, mật khẩu trước khi ghi.
  • Thêm parent_run_id nếu có multi-agent, để dựng lại cây gọi giữa các agent.

Đọc trace nhanh bằng jq

# Đếm số lời gọi mỗi tool trong 1 run
grep '"run_id":"r-88"' traces.jsonl | jq -r '.output.tool // empty' | sort | uniq -c
# Tìm mọi bước có lỗi tool trong toàn bộ file
jq -c 'select(.type=="tool_error")' traces.jsonl

Ba cấp độ trace — chọn theo giai đoạn dự án

  • Cấp 1 (file JSONL local): đúng cho lúc phát triển. Rẻ, không phụ thuộc dịch vụ ngoài, grep/jq là đủ. Đây là nơi bạn nên bắt đầu hôm nay.
  • Cấp 2 (tự host DB/log store): khi có nhiều phiên, cần truy vấn theo thời gian, theo tool, theo lỗi. Đổ trace vào SQLite/Postgres hoặc một bảng phân tích.
  • Cấp 3 (nền tảng observability chuyên dụng cho LLM): khi lên production thật, cần dashboard, so phiên bản, và gắn feedback người dùng vào trace. Đừng vội lên cấp này khi chưa cần.
Nguyên tắc: hình dạng dữ liệu (run_id + từng bước) giống nhau ở cả 3 cấp — nên bắt đầu từ JSONL rồi nâng cấp mà không phải viết lại.

Khung tư duy: "5 câu hỏi mọi trace phải trả lời"

  • Agent quyết định làm gì ở mỗi bước? (WHAT)
  • Vì sao nó quyết vậy? (input/prompt nào dẫn tới)
  • Tool trả về gì — đúng hay lỗi?
  • Mất bao lâu / tốn bao nhiêu token?
  • Nó dừng ở đâu và vì sao dừng?

Sai lầm thường gặp

  • Chỉ log input & output cuối: Mất toàn bộ phần "ở giữa" — chính là nơi agent đi lạc. Phải log từng bước.
  • Không log lỗi tool: Agent nuốt lỗi rồi loop; không có log tool_error bạn sẽ mò kim đáy bể.
  • Không có run_id: Log của nhiều phiên trộn lẫn, không dựng lại được câu chuyện.
  • Log prompt khổng lồ dạng thô mỗi bước: Tốn dung lượng; nên log hash system prompt + phần thay đổi.
  • Quên che dữ liệu nhạy cảm: Rủi ro lộ PII trong file log.

Checklist kết bài

  • [ ] Mỗi phiên có run_id duy nhất.
  • [ ] Log đủ: llm_call, tool_call, tool_result, tool_error, final_answer.
  • [ ] Ghi JSON Lines, che PII.
  • [ ] Đọc lại được 1 trace và kể lại "câu chuyện" agent đã làm gì.
Bài sau: từ trace, ta xây evals — cách chấm điểm agent tự động, lặp lại được.

Học xong bài này rồi? Tạo tài khoản miễn phí để lưu lại — lần sau vào là biết ngay đang dở ở đâu, và học hết khóa thì có chứng chỉ. Lưu tiến độ của tôi