Thiết kế hệ thống giới hạn token cho AI Coding Agent kiểu Codex

OpenAI Codex vừa chuyển sang token-based pricing từ tháng 4/2026. Bài viết này phân tích cách thiết kế hệ thống rate limiting và token quota tương tự — từ data model, đến sliding window counter, đến fair-use enforcement.

9 phút đọc

Podcast · 3 giọng đọc

8:51.03999999999985 Trang Sơn Linh
0:00 8:51.03999999999985

Trang

Host

Sơn

Curious Coder

Linh

Chuyên gia

Trang · Host · đoạn 1 / 32

Timeline · 32 lượt nói
Trang Host · đoạn 1
0:18.976
Trang Host · đoạn 2
0:21.76
Trang Host · đoạn 3
0:19.52
Linh Chuyên gia · đoạn 4
0:31.264
Sơn Curious Coder · đoạn 5
0:13.568
Linh Chuyên gia · đoạn 6
0:13.696
Trang Host · đoạn 7
0:5.568
Linh Chuyên gia · đoạn 8
0:22.592
Sơn Curious Coder · đoạn 9
0:6.816
Linh Chuyên gia · đoạn 10
0:21.056
Trang Host · đoạn 11
0:7.808
Sơn Curious Coder · đoạn 12
0:13.632
Linh Chuyên gia · đoạn 13
0:19.904
Sơn Curious Coder · đoạn 14
0:5.984
Linh Chuyên gia · đoạn 15
0:23.488
Trang Host · đoạn 16
0:11.712
Linh Chuyên gia · đoạn 17
0:21.472
Sơn Curious Coder · đoạn 18
0:4.352
Linh Chuyên gia · đoạn 19
0:21.44
Trang Host · đoạn 20
0:10.912
Linh Chuyên gia · đoạn 21
0:16.928
Sơn Curious Coder · đoạn 22
0:16.704
Linh Chuyên gia · đoạn 23
0:21.28
Trang Host · đoạn 24
0:16.736
Linh Chuyên gia · đoạn 25
0:19.776
Sơn Curious Coder · đoạn 26
0:3.328
Linh Chuyên gia · đoạn 27
0:17.92
Trang Host · đoạn 28
0:14.912
Linh Chuyên gia · đoạn 29
0:29.056
Sơn Curious Coder · đoạn 30
0:11.008
Linh Chuyên gia · đoạn 31
0:14.944
Trang Host · đoạn 32
0:32.928

Thiết kế hệ thống giới hạn token cho AI Coding Agent kiểu Codex

Từ ngày 2/4/2026, OpenAI chính thức chuyển Codex sang token-based pricing thay vì per-message như trước. Thay đổi này không chỉ ảnh hưởng đến người dùng — nó tiết lộ một bài toán thiết kế hệ thống cực kỳ thú vị mà bất kỳ ai đang xây dựng AI platform cũng sẽ phải đối mặt: làm thế nào để giới hạn và tính phí token usage một cách chính xác, công bằng và scale được?

Bài viết này không phải hướng dẫn dùng Codex. Đây là bài phân tích hệ thống: nếu bạn đang xây một AI SaaS tương tự — một coding agent, một chatbot có plan, một platform multi-tenant — bạn sẽ thiết kế token limit system như thế nào?

Bài toán cụ thể

Codex có một số đặc điểm thú vị từ góc độ hệ thống:

  • Multi-tier plans: Free, Plus, Pro, Business, Enterprise — mỗi tier có quota khác nhau.
  • Multi-model: GPT-5.3-Codex, GPT-5.4-Mini, GPT-5.3-Codex-Spark — mỗi model có credit rate per token khác nhau.
  • Token type khác nhau: Input tokens, cached input tokens, và output tokens có giá khác nhau.
  • Reset định kỳ: Quota reset theo tuần hoặc tháng tùy plan.
  • Credits overage: Khi hết quota, có thể dùng credits đã mua thêm để tiếp tục.
  • Fast mode: Tốn token nhanh hơn nhưng latency thấp hơn.

Đây là một bài toán rate limiting + billing kết hợp — phức tạp hơn nhiều so với rate limiting thông thường (kiểu "max 100 requests/phút").

Lớp 1: Data Model

Trước khi nghĩ đến algorithm, hãy thiết kế data model cho bài toán này.

-- Plan definitions
CREATE TABLE plans (
  id          VARCHAR PRIMARY KEY,         -- 'plus', 'pro', 'business'
  name        VARCHAR NOT NULL,
  reset_period VARCHAR NOT NULL,           -- 'weekly', 'monthly'
  base_quota_credits BIGINT NOT NULL       -- credits included per period
);

-- Model rate card
CREATE TABLE model_rates (
  model_id    VARCHAR NOT NULL,            -- 'gpt-5.3-codex', 'gpt-5.4-mini'
  token_type  VARCHAR NOT NULL,            -- 'input', 'cached_input', 'output'
  credits_per_million_tokens NUMERIC NOT NULL,
  PRIMARY KEY (model_id, token_type)
);

-- User subscriptions
CREATE TABLE subscriptions (
  user_id     UUID NOT NULL,
  plan_id     VARCHAR REFERENCES plans(id),
  period_start TIMESTAMPTZ NOT NULL,
  period_end   TIMESTAMPTZ NOT NULL,
  quota_used_credits BIGINT DEFAULT 0,
  purchased_credits  BIGINT DEFAULT 0,     -- credits mua thêm
  PRIMARY KEY (user_id, period_start)
);

-- Usage log (append-only)
CREATE TABLE token_usage_log (
  id          UUID DEFAULT gen_random_uuid() PRIMARY KEY,
  user_id     UUID NOT NULL,
  session_id  UUID NOT NULL,
  model_id    VARCHAR NOT NULL,
  input_tokens      INTEGER NOT NULL,
  cached_input_tokens INTEGER DEFAULT 0,
  output_tokens     INTEGER NOT NULL,
  credits_consumed  NUMERIC NOT NULL,
  fast_mode   BOOLEAN DEFAULT false,
  created_at  TIMESTAMPTZ DEFAULT NOW()
);

CREATE INDEX ON token_usage_log (user_id, created_at DESC);

Tại sao append-only log? Vì billing data phải auditable — bạn không được xóa hay update. Mọi thứ đều là event, quota_used_credits được tính từ log hoặc cập nhật qua atomic increment.

Lớp 2: Token → Credits Conversion

Codex tính giá theo credits per million tokens, và mỗi token type có rate khác nhau. Đây là business logic quan trọng nhất:

interface TokenUsage {
  inputTokens: number;
  cachedInputTokens: number;
  outputTokens: number;
  modelId: string;
  fastMode: boolean;
}

interface ModelRate {
  inputCreditsPerMillion: number;
  cachedInputCreditsPerMillion: number;
  outputCreditsPerMillion: number;
  fastModeMultiplier: number; // e.g. 1.5x for fast mode
}

function calculateCredits(usage: TokenUsage, rate: ModelRate): number {
  const inputCost = (usage.inputTokens / 1_000_000) * rate.inputCreditsPerMillion;
  const cachedCost = (usage.cachedInputTokens / 1_000_000) * rate.cachedInputCreditsPerMillion;
  const outputCost = (usage.outputTokens / 1_000_000) * rate.outputCreditsPerMillion;

  const base = inputCost + cachedCost + outputCost;
  return usage.fastMode ? base * rate.fastModeMultiplier : base;
}

Tách biệt việc tính credits ra khỏi việc enforce limit — hai concerns khác nhau, dễ test độc lập hơn.

Lớp 3: Quota Enforcement — Sliding Window vs Fixed Window

Đây là phần nhiều người hay bỏ qua. Có 3 strategy phổ biến:

Fixed Window Counter

|---- period 1 ----|---- period 2 ----|
   [used: 800/1000]    [used: 0/1000]

Đơn giản nhất. Nhưng có "boundary burst problem": user có thể dùng 1000 credits cuối period 1 và 1000 credits đầu period 2 — tổng 2000 credits trong 1 khoảng thời gian ngắn.

Codex dùng fixed window (weekly/monthly reset) — chấp nhận boundary burst vì đây là developer tool, không phải financial system.

Sliding Window Log

Lưu timestamp của mỗi request, count trong window [now - period, now]:

async function checkQuota(userId: string, creditsNeeded: number): Promise<boolean> {
  const windowStart = subWeeks(new Date(), 1); // weekly window
  
  const { data } = await db
    .from('token_usage_log')
    .select('credits_consumed')
    .eq('user_id', userId)
    .gte('created_at', windowStart.toISOString());

  const usedCredits = data.reduce((sum, row) => sum + row.credits_consumed, 0);
  const quota = await getUserQuota(userId);
  
  return usedCredits + creditsNeeded <= quota;
}

Chính xác hơn nhưng expensive — query toàn bộ log mỗi request. Không scale tốt.

Sliding Window Counter (Hybrid — Khuyến nghị)

Kết hợp tốt nhất của cả hai: chia window thành nhiều bucket nhỏ, giữ approximate sliding count trong Redis:

// Redis key pattern: quota:{userId}:{bucketTimestamp}
// Mỗi bucket = 1 giờ, window = 7 ngày = 168 buckets

async function consumeCredits(
  userId: string,
  credits: number,
  quotaLimit: number
): Promise<{ allowed: boolean; remaining: number }> {
  const now = Date.now();
  const bucketSize = 3600_000; // 1 hour in ms
  const windowSize = 7 * 24 * 3600_000; // 7 days
  const currentBucket = Math.floor(now / bucketSize) * bucketSize;

  const pipeline = redis.pipeline();

  // Tính tổng usage trong sliding window (168 buckets)
  const buckets: string[] = [];
  for (let i = 0; i < 168; i++) {
    const bucket = currentBucket - i * bucketSize;
    buckets.push(`quota:${userId}:${bucket}`);
  }

  // Dùng Lua script để atomic check-and-increment
  const luaScript = `
    local total = 0
    for i, key in ipairs(KEYS) do
      local val = redis.call('GET', key)
      if val then total = total + tonumber(val) end
    end
    
    local limit = tonumber(ARGV[1])
    local credits = tonumber(ARGV[2])
    local currentKey = KEYS[1]
    local ttl = tonumber(ARGV[3])
    
    if total + credits > limit then
      return {0, limit - total}
    end
    
    redis.call('INCRBYFLOAT', currentKey, credits)
    redis.call('EXPIRE', currentKey, ttl)
    return {1, limit - total - credits}
  `;

  const result = await redis.eval(
    luaScript,
    buckets,
    [quotaLimit.toString(), credits.toString(), (windowSize / 1000).toString()]
  );

  return {
    allowed: result[0] === 1,
    remaining: result[1],
  };
}

Tại sao Lua script? Vì check-then-increment phải atomic — nếu không, race condition sẽ cho phép user vượt quota khi nhiều requests đến đồng thời.

Lớp 4: Pre-flight Check vs Post-flight Billing

Đây là một tension thú vị trong thiết kế hệ thống AI:

Vấn đề: Bạn không biết trước output tokens sẽ là bao nhiêu — model mới generate xong mới biết.

Hai approach:

Option A: Pre-flight estimate

1. User gửi request
2. System ước tính max tokens dựa trên input + max_tokens parameter
3. "Reserve" estimated credits
4. Gọi LLM
5. Actual credits = credits thật sự dùng
6. Hoàn trả phần reserve dư

Option B: Post-flight billing

1. User gửi request
2. Kiểm tra user còn quota không (rough check)
3. Gọi LLM
4. Tính credits thật từ response
5. Deduct credits
6. Nếu vượt quota → flag, charge vào credits/overage

Codex dùng Option B với overage protection — nếu bạn hết quota giữa chừng một task dài, Codex sẽ deduct vào purchased credits. Đây là UX tốt hơn cho developer workflow (không muốn task bị cắt ngang), nhưng phức tạp hơn về billing.

Lớp 5: Multi-tier Fair Use

Codex có một cơ chế thú vị: credits "multiplier" theo plan. Plus = baseline, Pro = 20x Plus, Business = custom. Điều này có nghĩa là cùng một "credit unit" nhưng giá trị thực tế khác nhau.

Cách implement đơn giản nhất: normalize về một đơn vị chung.

// Thay vì lưu "raw tokens", lưu "normalized credits"
// Mỗi plan có conversion_factor
const planMultipliers = {
  'free':     1,
  'plus':     1,
  'pro':      20,
  'business': 50,  // example
};

// Quota limit của mỗi plan (normalized credits)
const planQuotas = {
  'free':     100_000,   // 100K normalized credits/month
  'plus':     1_000_000, // 1M normalized credits/week
  'pro':      20_000_000,
};

Normalized thì dễ so sánh, dễ migrate khi thay đổi pricing.

Lớp 6: Observability — Dashboard & Alerts

Hệ thống token limit mà không có observability là hệ thống chết. Những metrics cần track:

// Metrics để expose qua /status hoặc dashboard
interface UsageMetrics {
  userId: string;
  currentPeriod: {
    start: Date;
    end: Date;
    quotaLimit: number;
    quotaUsed: number;
    quotaRemaining: number;
    percentUsed: number;
  };
  purchasedCredits: number;
  topModels: Array<{
    modelId: string;
    creditsConsumed: number;
    percentOfTotal: number;
  }>;
  dailyBurnRate: number;    // avg credits/day
  projectedOverage: number; // ước tính thiếu bao nhiêu cuối kỳ
}

Codex CLI expose điều này qua lệnh /status — một feature nhỏ nhưng cực kỳ hữu ích cho developer.

Alert thresholds nên implement:

  • 80% quota used → warning notification
  • 95% quota used → urgent warning
  • 100% quota → block hoặc charge overage
  • Burn rate bất thường → anomaly alert (tránh bug vô tình loop gọi API)

Kiến trúc tổng thể

┌─────────────────────────────────────────────────────┐
│                   API Gateway                        │
│  (auth, basic rate limit: req/sec per user)          │
└────────────────────┬────────────────────────────────┘
                     │
┌────────────────────▼────────────────────────────────┐
│              Token Quota Service                     │
│                                                      │
│  ┌─────────────┐  ┌──────────────┐  ┌────────────┐  │
│  │ Quota Check │  │Credit Calc   │  │Usage Logger│  │
│  │ (Redis)     │  │(Rate Card DB)│  │(Postgres)  │  │
│  └─────────────┘  └──────────────┘  └────────────┘  │
└────────────────────┬────────────────────────────────┘
                     │
┌────────────────────▼────────────────────────────────┐
│               LLM Proxy Layer                        │
│  (gọi OpenAI/Anthropic, stream response)             │
│  (đếm tokens từ response headers/usage field)        │
└────────────────────┬────────────────────────────────┘
                     │
┌────────────────────▼────────────────────────────────┐
│              Billing & Credits Service               │
│  (deduct credits, handle overage, invoice)           │
└─────────────────────────────────────────────────────┘

Những edge case cần xử lý

1. Stream interrupted: User đóng connection giữa chừng — vẫn phải charge tokens đã generate.

2. Model fallback: Nếu model A overloaded, fallback sang model B có giá khác — billing phải dùng model thật sự served.

3. Cached tokens: OpenAI trả về cached_tokens trong usage field — cần parse đúng để charge giá thấp hơn.

4. Concurrent sessions: User mở 3 tab đồng thời — tất cả cùng đọc Redis, cần Lua script atomic (như đã đề cập ở trên).

5. Clock skew: Distributed system với nhiều node — dùng server-side timestamp, không bao giờ trust client clock.

Tổng kết

Thiết kế hệ thống token limit cho AI platform không phải chỉ là "thêm một middleware check quota". Nó đòi hỏi:

Layer Công nghệ Mục đích
Data model PostgreSQL Audit trail, billing accuracy
Real-time quota Redis + Lua Atomic check, low latency
Credit calculation Business logic layer Rate card, multi-model
Enforcement strategy Sliding window counter Balance accuracy vs performance
Observability Metrics + alerts Transparency, anomaly detection

Codex đã giải quyết bài toán này ở production scale với hàng triệu developer — việc phân tích cách họ thiết kế pricing model cho ta nhiều gợi ý valuable khi xây platform tương tự.

Nếu bạn đang xây AI SaaS với subscription model, đây là những quyết định thiết kế mà bạn sẽ phải đưa ra sớm hay muộn. Tốt hơn là nghĩ kỹ từ đầu hơn là refactor lại billing system sau khi đã có khách hàng thật.