·

Nếu bạn từng bị sếp hỏi "tháng này chi phí AI agent tăng 40%, vì sao?" mà chỉ có thể trả lời "để em check lại" — nghĩa là team bạn đang thiếu một hệ thống token analytics đúng nghĩa. Không phải thiếu dữ liệu (hầu hết provider đều trả usage.total_tokens trong response), mà thiếu quy trình đo, lưu trữ và trực quan hóa để biến những con số rời rạc đó thành insight có thể hành động được. Bài này đi từ lý do vì sao token analytics phải là một hạng mục kỹ thuật nghiêm túc (first-class), đến cách instrument code, xây dashboard, đặt benchmark, và cuối cùng là cost attribution — quy chi phí AI về đúng team, đúng feature.

Vì Sao Token Analytics Là Vấn Đề Kỹ Thuật Nghiêm Túc

Nhiều team coi token usage là "chi phí vận hành" (operational cost) giống tiền điện, chỉ cần nhìn tổng hóa đơn cuối tháng. Đây là sai lầm phổ biến nhất khi vận hành AI agent ở quy mô production. Token không chỉ là tiền — token còn là latency (mỗi token sinh ra tốn thời gian decode), là chất lượng phản hồi (context quá dài làm model "lost in the middle", quá ngắn thì thiếu thông tin), và là rủi ro vận hành (một prompt lỗi có thể đẩy input context lên gấp 10 lần bình thường, gây timeout hoặc vượt context window).

Khi không có analytics, team chỉ phản ứng (reactive) — nhận hóa đơn cao, mới đi tìm nguyên nhân. Khi có analytics đúng cách, team chủ động (proactive) — phát hiện anomaly trong vài giờ, biết chính xác agent nào, prompt nào, user nào đang gây tốn kém, và có dữ liệu để đưa ra quyết định tối ưu (ví dụ: có nên chuyển từ GPT-4o sang một model rẻ hơn cho một số tác vụ cụ thể không).

Một lý do khác: token usage là early warning signal cho nhiều loại lỗi hệ thống. Một agent bị stuck trong loop gọi tool liên tục, một retrieval pipeline trả về quá nhiều document không liên quan, hay một prompt template bị leak dữ liệu debug vào context — tất cả đều thể hiện ngay lập tức qua biểu đồ token tăng đột biến, trước khi user report bug hoặc finance report vượt budget.

Bốn Chiều Đo Lường Token

Để phân tích token usage một cách có hệ thống, cần tách theo bốn chiều (dimension) độc lập, vì mỗi chiều trả lời một câu hỏi khác nhau:

  1. Input tokens vs Output tokens — Input là chi phí "nạp context" (system prompt, lịch sử hội thoại, tài liệu retrieval), output là chi phí "sinh nội dung". Hai loại này thường có đơn giá khác nhau (output token của hầu hết model đắt hơn input token 2-5 lần), nên gộp chung sẽ che mất nguyên nhân thực sự khi chi phí tăng.
  2. Theo agent / workflow / feature — Một hệ thống multi-agent có thể có agent "planner", agent "executor", agent "reviewer". Nếu không tách theo từng agent, bạn không biết agent nào đang "ăn" token nhiều nhất.
  3. Theo user / tenant / customer — Trong sản phẩm SaaS đa khách hàng (multi-tenant), cần biết token usage theo từng tenant để tính giá đúng, phát hiện customer nào đang dùng vượt gói, hoặc phát hiện abuse.
  4. Theo thời gian (trend theo giờ/ngày/tuần) — Token usage không phẳng theo thời gian. Cần biết peak hour, biết usage có đang tăng dần theo tính năng mới ra mắt hay đột biến do lỗi.

Mẹo: Đừng chỉ log total_tokens. Ngay từ ngày đầu, hãy log tối thiểu 4 field: input_tokens, output_tokens, cached_tokens (nếu provider hỗ trợ prompt caching), và model_name. Chi phí thực tế phụ thuộc vào cả bốn, và bạn không thể "truy hồi" (backfill) dữ liệu này nếu chỉ log tổng số sau này mới phát hiện ra cần tách.

Instrument LLM Calls Để Phục Vụ Analytics

Instrumentation (đưa logging/tracing vào code) là bước nền tảng — không có instrumentation đúng, mọi dashboard phía sau đều xây trên cát. Nguyên tắc quan trọng nhất: instrument tại một điểm duy nhất (ví dụ một wrapper function hoặc middleware bao quanh LLM client), không rải log token ở khắp nơi trong codebase — vì như vậy sẽ dễ thiếu, dễ sai, và khó maintain khi đổi provider.

Schema Instrumentation Tối Thiểu

Trước khi chọn công cụ (LangSmith, Helicone, hay tự dựng OpenTelemetry), cần thống nhất schema dữ liệu — tập field bắt buộc phải có trong mỗi bản ghi (record) của một lệnh gọi LLM. Đây là schema tối thiểu mà tôi khuyến nghị cho mọi team, độc lập với công cụ:

from dataclasses import dataclass, field
from datetime import datetime, timezone
from typing import Optional
import uuid


@dataclass
class LLMCallRecord:
    """Minimal schema for token usage analytics.

    Every LLM call in the system must produce one of these records,
    regardless of which vendor SDK or observability tool is used.
    """
    call_id: str = field(default_factory=lambda: str(uuid.uuid4()))
    trace_id: str = ""  # groups all calls in one workflow/request
    timestamp: datetime = field(default_factory=lambda: datetime.now(timezone.utc))

    # Attribution — who / what caused this call
    agent_name: str = ""
    workflow_name: str = ""
    tenant_id: Optional[str] = None
    user_id: Optional[str] = None

    # Model & cost
    provider: str = ""          # "openai" | "anthropic" | "google" | ...
    model_name: str = ""        # "claude-sonnet-5" | "gpt-4o" | ...
    input_tokens: int = 0
    output_tokens: int = 0
    cached_tokens: int = 0      # tokens served from prompt cache, if any
    estimated_cost_usd: float = 0.0

    # Performance
    latency_ms: int = 0
    time_to_first_token_ms: Optional[int] = None

    # Outcome — critical for "tokens per successful outcome" metrics
    status: str = "success"     # "success" | "error" | "timeout" | "retried"
    finish_reason: str = ""     # "stop" | "length" | "tool_calls" | ...

Điểm mấu chốt của schema này là trace_id và outcome (status, finish_reason). trace_id cho phép nhóm nhiều lệnh gọi LLM thuộc cùng một workflow (ví dụ agent gọi LLM 5 lần để hoàn tất một task) — nếu không có trace_id, bạn chỉ thấy "cây" (từng call) mà không thấy "rừng" (toàn bộ workflow tốn bao nhiêu token). status/finish_reason cho phép tính chỉ số cực kỳ quan trọng ở phần sau: token per successful outcome — vì một request retry 3 lần do lỗi sẽ tốn gấp 3 token nhưng dashboard đơn giản sẽ không phát hiện được nếu không có trường này.

Mẹo: Luôn tính estimated_cost_usd ngay tại thời điểm log, dùng bảng giá cứng (hardcoded pricing table) trong code, đừng để đến lúc query báo cáo mới tính giá — vì giá provider thay đổi theo thời gian, và nếu tính giá "muộn" bạn sẽ tính sai giá cho các record cũ khi giá mới được cập nhật.

Instrumentation với LangSmith

LangSmith (của LangChain) phù hợp nếu bạn đã dùng LangChain/LangGraph, vì nó tự động capture trace của toàn bộ chain, bao gồm cả token usage của từng bước con. Cách tích hợp nhanh nhất là qua decorator @traceable:

import os
from langsmith import traceable
from langsmith.wrappers import wrap_openai
from openai import OpenAI

os.environ["LANGCHAIN_TRACING_V2"] = "true"
os.environ["LANGCHAIN_PROJECT"] = "token-analytics-demo"

client = wrap_openai(OpenAI())


@traceable(name="summarize_ticket", run_type="chain")
def summarize_ticket(ticket_text: str, tenant_id: str) -> str:
    response = client.chat.completions.create(
        model="gpt-4o-mini",
        messages=[
            {"role": "system", "content": "Summarize the support ticket in 2 sentences."},
            {"role": "user", "content": ticket_text},
        ],
        metadata={"tenant_id": tenant_id, "agent_name": "ticket_summarizer"},
    )
    return response.choices[0].message.content

Điểm mạnh của LangSmith là bạn gần như không cần tự viết code lưu token — mỗi lần client.chat.completions.create được gọi, wrap_openai sẽ tự bắt usage.prompt_tokens, usage.completion_tokens và gửi lên dashboard LangSmith kèm theo metadata bạn truyền vào (ở đây là tenant_id, agent_name) — đây chính là cách bạn "gắn nhãn" attribution mà không cần tự xây schema từ đầu.

Hạn chế: LangSmith gắn chặt với ecosystem LangChain, và dữ liệu nằm trên cloud của họ — nếu công ty có yêu cầu compliance nghiêm ngặt về việc không gửi nội dung prompt/response ra ngoài, cần cấu hình self-hosted hoặc cân nhắc phương án khác.

Instrumentation với Helicone

Helicone hoạt động theo mô hình proxy — bạn đổi base_url của client sang endpoint của Helicone, mọi request/response đi qua đó sẽ được log lại mà gần như không cần sửa code logic:

from openai import OpenAI

client = OpenAI(
    api_key="sk-...",
    base_url="https://oai.helicone.ai/v1",
    default_headers={
        "Helicone-Auth": f"Bearer {os.environ['HELICONE_API_KEY']}",
        "Helicone-User-Id": "tenant_9f21",          # for per-tenant analytics
        "Helicone-Property-Agent": "invoice_extractor",
        "Helicone-Property-Workflow": "monthly_close",
        "Helicone-Cache-Enabled": "true",           # dedupe identical requests
    },
)

response = client.chat.completions.create(
    model="gpt-4o",
    messages=[{"role": "user", "content": "Extract line items from this invoice."}],
)

Cách dùng header Helicone-Property-* để gắn custom dimension (agent, workflow, feature...) là điểm rất tiện — bạn không phải sửa logic nghiệp vụ, chỉ cần thêm header là có ngay breakdown theo agent/workflow trên dashboard Helicone. Mô hình proxy cũng có ưu điểm là hỗ trợ caching tại proxy layer (Helicone-Cache-Enabled), giúp giảm cả token thật lẫn latency cho các request trùng lặp — rất hữu ích với các tác vụ có tính lặp cao như FAQ bot.

Nhược điểm cần lưu ý: proxy thêm một network hop, có thể tăng latency vài chục ms, và toàn bộ traffic LLM của bạn đi qua hạ tầng bên thứ ba — cần review kỹ điều khoản bảo mật dữ liệu (data residency, retention policy) trước khi dùng cho dữ liệu nhạy cảm.

Instrument với OpenTelemetry để Tự Xây Analytics (Self-Hosted)

Nếu công ty yêu cầu dữ liệu không rời khỏi hạ tầng nội bộ, hoặc bạn muốn tích hợp token metrics vào cùng hệ thống observability sẵn có (Prometheus/Grafana, Datadog...), OpenTelemetry (OTel) là lựa chọn chuẩn hóa và không phụ thuộc vendor:

from opentelemetry import metrics
from opentelemetry.sdk.metrics import MeterProvider
from opentelemetry.sdk.metrics.export import PeriodicExportingMetricReader
from opentelemetry.exporter.otlp.proto.grpc.metric_exporter import OTLPMetricExporter

exporter = OTLPMetricExporter(endpoint="otel-collector:4317", insecure=True)
reader = PeriodicExportingMetricReader(exporter, export_interval_millis=15000)
provider = MeterProvider(metric_readers=[reader])
metrics.set_meter_provider(provider)
meter = metrics.get_meter("llm.token_usage")

token_counter = meter.create_counter(
    name="llm.tokens.total",
    description="Total tokens consumed per LLM call",
    unit="token",
)
cost_histogram = meter.create_histogram(
    name="llm.cost.usd",
    description="Estimated cost per LLM call in USD",
    unit="usd",
)
latency_histogram = meter.create_histogram(
    name="llm.latency.ms",
    description="LLM call latency",
    unit="ms",
)


def record_llm_call(usage, model_name, agent_name, tenant_id, cost_usd, latency_ms):
    attrs = {"model": model_name, "agent": agent_name, "tenant": tenant_id}
    token_counter.add(usage.prompt_tokens, {**attrs, "token_type": "input"})
    token_counter.add(usage.completion_tokens, {**attrs, "token_type": "output"})
    cost_histogram.record(cost_usd, attrs)
    latency_histogram.record(latency_ms, attrs)

Với cách này, token_countercost_histogram sẽ được scrape bởi Prometheus (qua OTel Collector) và bạn có toàn quyền kiểm soát retention, aggregation, và không phụ thuộc vào bất kỳ SaaS bên ngoài. Đánh đổi là bạn phải tự vận hành pipeline (Collector, Prometheus, Grafana) — chi phí vận hành ban đầu cao hơn nhưng về lâu dài rẻ hơn và linh hoạt hơn khi quy mô lớn.

Mẹo: Khi dùng OTel, luôn giới hạn cardinality của attribute (nhãn). Đừng gắn user_id trực tiếp làm label nếu có hàng chục nghìn user — Prometheus sẽ "nổ" cardinality (mỗi giá trị label tạo ra một time series riêng), làm chậm và tốn RAM khủng khiếp. Với user_id/tenant_id số lượng lớn, hãy log vào một hệ log riêng (ví dụ Loki, hoặc bảng SQL) để query theo nhu cầu, chỉ giữ label có cardinality thấp (model, agent, status) trong metrics.

Xây Dashboard Token Analytics

Có dữ liệu instrumentation tốt mới chỉ là điều kiện cần — điều kiện đủ là biến dữ liệu đó thành dashboard mà một kỹ sư on-call lúc 2 giờ sáng cũng có thể nhìn vào và hiểu ngay vấn đề trong 30 giây.

Kiến Trúc Dashboard Grafana

Một kiến trúc phổ biến và đã được kiểm chứng: ứng dụng expose metrics dạng Prometheus (qua OTel Collector như trên, hoặc trực tiếp qua prometheus_client), Prometheus scrape định kỳ (thường 15-30s), và Grafana query Prometheus làm datasource để vẽ dashboard.

apiVersion: 1
datasources:
  - name: Prometheus-TokenMetrics
    type: prometheus
    access: proxy
    url: http://prometheus:9090
    isDefault: false
    jsonData:
      timeInterval: "15s"
      httpMethod: POST

Cấu trúc dashboard nên chia thành 3 tầng, theo đúng ba nhóm câu hỏi mà các stakeholder khác nhau sẽ hỏi:

  • Tầng Executive (tổng quan) — 4-6 panel dạng "stat" hiển thị: tổng chi phí hôm nay/tháng này, xu hướng so với tháng trước (%), top 5 agent tốn nhất, dự báo chi phí cuối tháng (linear projection từ usage hiện tại).
  • Tầng Engineering (chi tiết theo agent/workflow) — time series chia theo agent_name, breakdown input/output tokens, P50/P95/P99 latency, error rate kèm token đã tốn cho các call lỗi (token bị "phí" do lỗi).
  • Tầng Tenant/Customer (attribution) — bảng (table panel) top tenant theo chi phí, cảnh báo tenant vượt ngưỡng gói dịch vụ (quota).

Mẹo: Đặt time range mặc định của dashboard là "Last 24 hours" với auto-refresh 1 phút cho tầng Engineering, nhưng "Last 30 days" không auto-refresh cho tầng Executive — dashboard chi phí tháng mà refresh liên tục chỉ gây nhiễu và tốn tài nguyên query không cần thiết.

Prometheus Queries cho Token Analytics

Dưới đây là các câu PromQL cốt lõi nên có trong mọi dashboard token analytics, dựa trên metric llm_tokens_total (counter) và llm_latency_ms (histogram) đã định nghĩa ở phần instrumentation:

sum(increase(llm_tokens_total{token_type="input"}[24h])) by (agent)
  /
sum(increase(llm_calls_total[24h])) by (agent)

topk(10,
  sum(increase(llm_cost_usd_sum[7d])) by (workflow)
)

histogram_quantile(0.95,
  sum(rate(llm_tokens_bucket[1h])) by (le, agent)
)

sum(increase(llm_tokens_total[1h]))
  /
sum(increase(llm_calls_total{status="success"}[1h]))

Query cuối cùng — "token efficiency trend" — là query tôi cho là quan trọng nhất nhưng bị bỏ qua nhiều nhất. Nếu error rate tăng và hệ thống tự động retry, tổng token tiêu thụ tăng nhưng số outcome thành công không tăng tương ứng — chỉ query này mới phát hiện ra vấn đề "hiệu suất token" đang xấu đi, trong khi query "tổng token" đơn thuần có thể trông vẫn "bình thường" vì traffic tăng đồng thời.

Mẹo: Luôn tạo alert rule dựa trên tốc độ tăng (rate of change) chứ không chỉ ngưỡng tuyệt đối. Ví dụ: increase(llm_cost_usd_sum[1h]) > 3 * increase(llm_cost_usd_sum[1h] offset 1d) — cảnh báo khi chi phí giờ hiện tại cao hơn 3 lần so với cùng giờ hôm trước, sẽ bắt được anomaly sớm hơn nhiều so với alert "chi phí giờ này > $50".

Truy Vấn Analytics trên LangSmith

Nếu dùng LangSmith, bạn không cần tự viết PromQL — LangSmith cung cấp UI filter và cả API để truy vấn trace theo điều kiện. Hai truy vấn hữu ích nhất trong vận hành thực tế:

from langsmith import Client

client = Client()

expensive_runs = client.list_runs(
    project_name="token-analytics-demo",
    filter='and(gt(total_tokens, 5000), gt(start_time, "2026-07-02"))',
    order_by=["-total_tokens"],
    limit=20,
)
for run in expensive_runs:
    print(run.name, run.total_tokens, run.total_cost)

verbose_runs = client.list_runs(
    project_name="token-analytics-demo",
    filter='gt(outputs.usage_metadata.output_tokens, 2000)',
)

Hai truy vấn này giải quyết hai câu hỏi vận hành khác nhau: "run nào tốn tiền nhất" (để review xem có cần optimize prompt hay retrieval) và "run nào output dài bất thường" (thường là dấu hiệu model đang "lải nhải" — verbose — do thiếu constraint rõ trong system prompt, ví dụ thiếu câu "trả lời tối đa 3 câu").

Mẹo: Lưu các câu query này thành "Saved Views" trong LangSmith và review định kỳ hàng tuần (weekly review), đừng chỉ chạy khi có sự cố — phần lớn giá trị của analytics nằm ở việc phát hiện xu hướng xấu đi từ từ (gradual degradation), thứ mà alert threshold khó bắt được.

Đặt Benchmark Token Có Ý Nghĩa

Có dashboard rồi, câu hỏi tiếp theo là: "bao nhiêu token là bình thường, bao nhiêu là bất thường?" Nếu không có benchmark, mọi con số trên dashboard chỉ là dữ liệu thô, không phải insight — team sẽ không biết khi nào cần hành động.

Quy Trình Đặt Benchmark

Benchmark không nên là số bạn "đoán" hay copy từ blog người khác — nó phải được rút ra từ chính dữ liệu hệ thống của bạn, qua một quy trình có thể lặp lại:

  1. Thu thập baseline — Chạy production (hoặc staging với traffic mô phỏng thực tế) tối thiểu 1-2 tuần, đủ để trải qua các pattern usage khác nhau (ngày thường, cuối tuần, giờ cao điểm).
  2. Tính phân phối (distribution), không chỉ trung bình — Trung bình (mean) dễ bị "kéo" lệch bởi vài outlier. Hãy tính P50 (median), P90, P95, P99 cho từng metric (token/request, cost/workflow). P50 cho biết case điển hình, P95/P99 cho biết "cái đuôi" (tail) cần quan tâm — thường đây là nơi phát sinh chi phí bất thường.
  3. Đặt benchmark theo outcome, không theo request — So sánh "token trung bình để hoàn tất một task thành công" quan trọng hơn "token trung bình mỗi request", vì hai agent có cùng token/request nhưng agent nào cần ít request hơn để xong việc mới thực sự hiệu quả.
  4. Phân tầng benchmark theo độ phức tạp task — Một benchmark chung cho "tất cả support ticket" là vô nghĩa nếu ticket có độ phức tạp rất khác nhau. Hãy phân nhóm task (ví dụ: đơn giản/trung bình/phức tạp dựa trên số bước xử lý) và đặt benchmark riêng cho từng nhóm.
  5. Review và điều chỉnh benchmark định kỳ (mỗi quý) — Model mới ra, prompt được optimize, benchmark cũ có thể không còn phù hợp. Benchmark là "sống" (living), không phải con số cố định vĩnh viễn.

Ví dụ cụ thể: giả sử sau 2 tuần thu thập dữ liệu cho agent "trích xuất thông tin hóa đơn" (invoice extraction), bạn có P50 = 1.200 token/hóa đơn, P95 = 3.800 token/hóa đơn. Từ đó đặt benchmark: "bình thường" là dưới 1.500 token, "cần review" là 1.500-4.000 token, "bất thường — cần điều tra ngay" là trên 4.000 token. Đây là benchmark có căn cứ, khác hẳn với việc đặt tùy tiện "dưới 2.000 token là ổn".

Benchmark Tham Khảo Từ Ngành

Vì không có "chuẩn ngành" chính thức cho token usage (khác với các chỉ số như Core Web Vitals), các số dưới đây chỉ nên dùng làm điểm khởi đầu tham khảo, không phải mục tiêu tuyệt đối — luôn ưu tiên benchmark tự đo theo quy trình ở trên:

Loại tác vụ Input tokens điển hình Output tokens điển hình Ghi chú
Chatbot hỏi-đáp đơn giản (FAQ) 200-800 50-300 Nên tối ưu qua caching nếu câu hỏi lặp lại nhiều
RAG (retrieval-augmented generation) 1.500-6.000 100-500 Input phụ thuộc số chunk retrieval; đây là nơi dễ "phình" token nhất
Agent nhiều bước (multi-step, dùng tool) 3.000-15.000 (cộng dồn cả trace) 500-2.000 (cộng dồn) Cần đo theo trace_id, không theo từng call riêng lẻ
Tóm tắt tài liệu dài 5.000-30.000 200-800 Output thường nhỏ hơn input rất nhiều — nếu ngược lại, cần review prompt
Code generation/review 2.000-10.000 500-3.000 Output tokens cao là bình thường với tác vụ sinh code dài

Mẹo: Khi so sánh với benchmark ngành, luôn tự hỏi "workload của tôi có tương đương không?" — một agent RAG với knowledge base tiếng Việt và tokenizer không tối ưu cho tiếng Việt (một số model sinh nhiều token hơn cho văn bản có dấu) sẽ có input tokens cao hơn benchmark tiếng Anh 20-40%, đó không phải là dấu hiệu tối ưu kém, mà là đặc thù ngôn ngữ cần tính vào benchmark riêng của bạn.

Analytics Nâng Cao: Cost Attribution và Chargeback

Khi hệ thống AI phục vụ nhiều team, nhiều sản phẩm, hoặc nhiều khách hàng, câu hỏi "ai đang tốn bao nhiêu" trở thành câu hỏi tài chính, không chỉ kỹ thuật. Cost attribution (quy chi phí về đúng nguồn) là nền tảng để làm chargeback (tính phí nội bộ giữa các team) hoặc billing (tính phí khách hàng) một cách công bằng và minh bạch.

Triển Khai Cost Attribution

Nguyên tắc cốt lõi: mọi lệnh gọi LLM phải mang theo đủ metadata để trả lời "chi phí này thuộc về ai" ngay tại thời điểm phát sinh — không đi "suy luận ngược" (reverse-engineer) sau này, vì lúc đó thông tin context đã mất.

from dataclasses import dataclass
from decimal import Decimal

PRICING_PER_MILLION = {
    "gpt-4o":            {"input": Decimal("2.50"), "output": Decimal("10.00")},
    "gpt-4o-mini":       {"input": Decimal("0.15"), "output": Decimal("0.60")},
    "claude-sonnet-5": {"input": Decimal("3.00"), "output": Decimal("15.00")},
}


@dataclass
class CostAttribution:
    tenant_id: str
    cost_center: str      # e.g. "engineering", "customer_success"
    product_feature: str  # e.g. "invoice_extraction", "chat_support"


def compute_cost(model: str, input_tokens: int, output_tokens: int) -> Decimal:
    rates = PRICING_PER_MILLION[model]
    input_cost = rates["input"] * input_tokens / Decimal("1_000_000")
    output_cost = rates["output"] * output_tokens / Decimal("1_000_000")
    return (input_cost + output_cost).quantize(Decimal("0.000001"))


def record_and_attribute(model, input_tokens, output_tokens, attribution: CostAttribution):
    cost = compute_cost(model, input_tokens, output_tokens)
    # Write to a dedicated cost ledger table — append-only, never mutated,
    # so month-end reconciliation always matches what was actually charged.
    insert_cost_ledger_row(
        tenant_id=attribution.tenant_id,
        cost_center=attribution.cost_center,
        product_feature=attribution.product_feature,
        model=model,
        input_tokens=input_tokens,
        output_tokens=output_tokens,
        cost_usd=cost,
    )
    return cost

Có hai chi tiết thiết kế quan trọng ở đây. Thứ nhất, dùng Decimal thay vì float cho tính toán tiền — sai số dấu phẩy động (floating-point) tích lũy qua hàng triệu lệnh gọi có thể tạo ra chênh lệch đáng kể khi reconcile với hóa đơn thật của provider cuối tháng. Thứ hai, bảng cost_ledger phải là append-only (chỉ thêm, không sửa/xóa) — đây là nguyên tắc kế toán cơ bản, giúp việc đối soát (reconciliation) với hóa đơn provider luôn có "sổ cái" gốc để tham chiếu, và giúp trả lời được câu hỏi kiểm toán (audit) như "chi phí ngày 15/6 của tenant X là bao nhiêu" bất kỳ lúc nào.

Với chargeback nội bộ, cần thêm bước hòa giải (reconciliation) hàng tháng: so tổng cost_usd tính từ ledger với hóa đơn thật từ OpenAI/Anthropic. Nếu lệch quá 5%, thường do: (a) pricing table chưa cập nhật giá mới, (b) có request không đi qua lớp attribution (bị bỏ log), hoặc (c) có model/endpoint mới chưa được thêm vào PRICING_PER_MILLION.

Mẹo: Đặt một alert riêng khi có request LLM với model không tồn tại trong PRICING_PER_MILLION — đây là dấu hiệu sớm nhất của "chi phí ma" (phantom cost) không được attribute đúng, thường xảy ra khi team thêm model mới vào code mà quên cập nhật bảng giá.

Weights & Biases cho Team Gần Với ML

Với các team đã có nền tảng ML Ops sẵn (đặc biệt là team vừa làm fine-tuning/prompt engineering vừa vận hành production), Weights & Biases (W&B) là lựa chọn tự nhiên để gộp token analytics vào cùng nơi theo dõi experiment, vì nó cho phép so sánh token usage giữa các phiên bản prompt/model như so sánh giữa các training run:

import wandb

wandb.init(project="agent-token-tracking", job_type="production-monitoring")

def log_llm_call(prompt_version, model, input_tokens, output_tokens, cost_usd, success):
    wandb.log({
        "prompt_version": prompt_version,
        "model": model,
        "input_tokens": input_tokens,
        "output_tokens": output_tokens,
        "total_tokens": input_tokens + output_tokens,
        "cost_usd": cost_usd,
        "success": int(success),
        "tokens_per_success": (input_tokens + output_tokens) if success else None,
    })

log_llm_call("v3_condensed_system_prompt", "gpt-4o-mini", 850, 210, 0.000339, True)
log_llm_call("v2_verbose_system_prompt", "gpt-4o-mini", 1600, 240, 0.000384, True)

Giá trị thực sự của W&B trong bối cảnh này không nằm ở việc log số liệu (Prometheus hay LangSmith đều làm được), mà ở khả năng so sánh trực quan giữa các phiên bản (prompt_version) trên cùng một biểu đồ, giống cách bạn so sánh loss curve giữa các lần training. Khi team vừa optimize prompt (rút gọn từ v2 sang v3), việc thấy ngay biểu đồ token giảm 45% mà success rate không đổi trên cùng dashboard W&B tạo động lực và bằng chứng rõ ràng cho việc tiếp tục đầu tư vào tối ưu prompt.

Mẹo: Nếu team đã dùng W&B cho việc khác (theo dõi fine-tuning, đánh giá model), tận dụng lại cùng project thay vì tạo project riêng cho token monitoring — việc này giúp data scientist và engineer nhìn thấy token cost ngay cạnh accuracy/quality metrics khi ra quyết định chọn model, tránh tình trạng "team ML chọn model tốt nhất về chất lượng, team platform mới phát hiện ra nó đắt gấp 3 lần" sau khi đã lên production.

Tổng Kết

Token analytics không phải là việc "thêm một dòng log() ghi lại total_tokens" — đó là một hệ thống có chủ đích, gồm bốn lớp: instrumentation đúng schema (input/output/cached tokens, gắn kèm attribution agent/tenant/workflow), dashboard phân tầng theo đối tượng xem (executive/engineering/tenant), benchmark được rút ra từ chính dữ liệu của bạn (không copy từ nơi khác), và cost attribution có sổ cái (ledger) minh bạch để phục vụ chargeback hoặc billing.

Điểm chung xuyên suốt cả bốn lớp này: đo lường phải gắn với outcome (kết quả thành công), không chỉ với volume (khối lượng request/token thô). Một hệ thống tốn nhiều token hơn nhưng tạo ra nhiều kết quả thành công hơn có thể vẫn hiệu quả hơn một hệ thống tốn ít token nhưng phải retry liên tục. Ngược lại, một hệ thống trông "rẻ" trên tổng chi phí nhưng có tỷ lệ token/outcome-thành-công đang tăng dần theo thời gian là dấu hiệu sớm của một vấn đề đang âm ỉ — và chỉ dashboard đúng, với benchmark đúng, mới giúp bạn phát hiện ra nó trước khi nó trở thành hóa đơn bất ngờ cuối tháng.

Với nền tảng analytics này đã sẵn sàng, bước tiếp theo tự nhiên là dùng chính dữ liệu này làm cơ sở cho A/B testing giữa các phiên bản prompt/context — chủ đề sẽ được khai thác ở bài kế tiếp.