Skip to content
上新Nano Banana 2.1 已上線:Google 新一代生圖模型,單張低至 $0.0336,原生 4K,約上一代半價查看模型
HopBase
← 返回 Blog

多租戶大型語言模型用量依客戶計費:一租戶一 Key 與真實扣款

先講結論:多租戶 AI 產品要依客戶結算模型用量,最簡單可靠的做法是給每個租戶(或每個租戶方案)一把獨立的 API Key。在這把 Key 上設定美元消費上限與併發上限,並記錄閘道為每次請求回傳的實際扣款,而不是拿 token 數自己估算。在 HopBase 上,每個計費的非串流回應都帶 X-HopBase-Cost-USD 和 usage.cost_usd;每個完成的影片或圖片任務都回傳 usage.cost_usd;GET /v1/usage 回傳某把 Key 還能花多少。某個租戶的額度用完後,它的請求會收到 402 insufficient_quota,其他租戶照常使用。Key 超出自身併發上限時收到 429 apikey_concurrency_limit,一個用量暴衝的客戶不會耗盡你的容量。串流回應沒有扣款標頭,用按 Key 匯出的 CSV 對帳即可,其中每一列都能重新驗算。

問題:一張帳單,很多客戶

如果你經營的是 AI 代理平台、帶 AI 功能的垂直 SaaS,或白牌 AI 服務,模型服務商只會給你一張帳單,而你的每個客戶都期待拿到自己的那一張。兩者之間,幾乎每個團隊最後都要回答這些問題:

  • 租戶 A 這個月花了我們多少錢,依模型拆分是多少?
  • 怎麼避免某個租戶失控的代理迴圈把所有人的預算花光?
  • 怎麼在我們自己的後台向客戶顯示剩餘額度?
  • 財務問起時,能不能證明發票上的每一列?

只靠 token 數回答不了這些問題。不同模型價格不同,快取輸入比較便宜,有些模型把推理 token 當作輸出計費,有些超過某個上下文長度就漲價,影片則按秒或按影片 token 計費。如果你自己拿 token 數乘以價格表,每次有新模型上線,這張表就會過時一次。

三種架構比較

做法租戶如何隔離費用數字從哪裡來由誰執行限制
請求加標籤,自己記帳應用程式中的租戶 ID;共用一把服務商 Key自己維護的價格表 × token 數你的程式,在每次呼叫前
自架代理代理中的虛擬 Key 或客戶 ID 欄位代理的價格表代理
在會回傳實際扣款的閘道上,每個租戶一把 Key每個租戶或方案一把閘道 Key閘道對每次請求的扣款閘道:依 Key 的消費上限與併發上限

三種都可行。本文接下來介紹第三種,因為它省掉了最難的兩件事:維護一張正確的價格表,以及在請求送出前執行限額。

第一步:每個租戶一把 Key,並設定上限

在 HopBase 主控台打開 API 金鑰,為每個租戶建立一把 Key。Key 在主控台中建立與修改,沒有公開的 API 可用,所以請把建立 Key 當作租戶開通流程中的一步。每把 Key 上有三項設定最關鍵:

  • 分組決定這把 Key 能呼叫哪些模型。一把 Key 可以跨家族加入 1 到 10 個分組,所以「基礎版」租戶可以只給 DeepSeek 和千問,「專業版」租戶再加上 Claude 和影片。呼叫 Key 分組以外的模型會回傳 404 model_not_found。
  • 額度是這把 Key 的美元消費上限。用完後,該 Key 的請求回傳 402 insufficient_quota,訊息為「API key quota exhausted」。帳戶下的其他 Key 不受影響。
  • 併發是這把 Key 上同時處理中的請求數。超出上限的請求收到 429 apikey_concurrency_limit,並帶 Retry-After: 1。留空代表不設 Key 層級上限。帳戶層級的上限仍會疊加生效:預設每個帳戶同時處理 5 個請求,可申請提高。

把 Key 加密存進租戶資料表,絕不要暴露在租戶的瀏覽器中。由你的後端代表租戶呼叫模型。

第二步:每次請求都記下實際扣款

同步呼叫(對話、生圖、語音)在計費後的回應中回傳從你餘額扣除的金額:

  • 回應標頭 X-HopBase-Cost-USD,例如 0.012。
  • 非串流 JSON 中的 usage.cost_usd,值相同,和一般 token 計數放在一起。

寫一個輕量包裝,把它變成一筆帳本紀錄:

import os, sqlite3
from openai import OpenAI

db = sqlite3.connect("ledger.db")
db.execute("""CREATE TABLE IF NOT EXISTS usage_ledger (
  ts TEXT DEFAULT CURRENT_TIMESTAMP, tenant_id TEXT, model TEXT,
  prompt_tokens INT, completion_tokens INT, cost_usd REAL, request_id TEXT)""")

def client_for(tenant):
    # tenant["hopbase_key"] 是該租戶自己的 Key,在伺服器端解密
    return OpenAI(base_url="https://api.hop-base.com/v1", api_key=tenant["hopbase_key"])

def chat(tenant, model, messages, **kw):
    raw = client_for(tenant).chat.completions.with_raw_response.create(
        model=model, messages=messages, **kw)
    resp = raw.parse()
    cost = float(raw.headers.get("X-HopBase-Cost-USD") or 0)
    db.execute("INSERT INTO usage_ledger (tenant_id, model, prompt_tokens, completion_tokens, cost_usd, request_id) VALUES (?,?,?,?,?,?)",
               (tenant["id"], model, resp.usage.prompt_tokens,
                resp.usage.completion_tokens, cost, resp.id))
    db.commit()
    return resp

關於這個數字,有幾條對計費很重要的規則:

  • 沒有產生輸出的失敗請求不計費,也不帶扣款標頭。
  • 如果閘道在內部重試某個請求,只有最後完成的那次嘗試計費,回傳的就是那次的金額。加入多個分組的 Key,依實際服務該請求的分組計費。
  • 如果你的用戶端提早斷線,已產生的輸出照常計費。即使你沒看到標頭,這筆扣款也會出現在主控台的使用記錄中。
  • 串流(SSE)回應不帶扣款標頭,因為標頭在生成開始前就已送出;串流最後一個 usage 事件只有 token 數,沒有金額。把 token 數(傳送 stream_options: {"include_usage": true})記為一筆待結算紀錄,再用 CSV 匯出(第四步)補上金額。

第三步:非同步影片與圖片任務

影片任務用 POST /v1/video/generate 送出,用 GET /v1/video/tasks/{task_id} 輪詢。任務進入最終狀態後,單一任務查詢的回應頂層會帶一個 usage 物件:

{
  "id": "vt-1",
  "status": "completed",
  "usage": { "cost_usd": 0.5 }
}

任務結束時把 usage.cost_usd 寫進帳本。未扣款的失敗任務回傳 0。查詢單一任務時,只有建立它的那把 Key 看得到這個欄位。任務清單(不帶 ID 的 GET /v1/video/tasks)涵蓋整個帳戶,包括其他 Key 建立的任務,所以請把每個任務 ID 和對應租戶一起存起來,不要把原始清單直接轉給租戶。影片送出時還會預扣預估費用,所以接近上限的租戶會在送出時就收到 402,而不是跑到一半才失敗。

第四步:對帳與開立帳單

到了結算週期末,從主控台使用記錄頁匯出 CSV,可依 Key 和日期範圍篩選(每個檔案最多 50,000 列、400 天)。每一列都有時間、模型、用量、官方定價幣別、官方費用、折扣、匯率、實扣金額和備註,所以每一列都能重新驗算:

官方費用 × 折扣 ÷ 匯率 = 實扣金額

失敗請求會標示為未扣款,中斷的請求會標示為已依產生的輸出計費。依 Key 加總實扣金額,和你的帳本比對,再把第二步中的串流待結算紀錄補齊。主控台也依 Key 提供消費與請求趨勢圖。如果你透過 MCP 用戶端操作,唯讀的 get_usage 工具會依模型回傳目前這把 Key 的請求數、token 數和費用合計,預設最近 7 天,最長 92 天。

向租戶收多少由你決定。常見做法是成本加上一定利潤,或以高於預期成本的價格銷售額度包。既然帳本裡記的是真實扣款,哪種方式都只是一次乘法。

第五步:向租戶顯示剩餘額度

想在你的後台顯示即時的「剩餘額度」,用租戶的 Key 呼叫 GET /v1/usage:

curl https://api.hop-base.com/v1/usage \
  -H "Authorization: Bearer sk-tenant-key"

一把額度 $10、已用 $2 的 Key,balance 為 8,quota.total 為 10,quota.used 為 2,單位都是美元。如果 Key 沒有單獨設定額度,balance 就是帳戶餘額,所以打算顯示這個數字的話,請為每把租戶 Key 都設定額度。即使 Key 還有剩餘額度,帳戶餘額先用完時也會回傳 402。這個介面本身不計費。

尊重租戶邊界的錯誤處理

  • 402 insufficient_quota:該租戶的額度已用完,或你的帳戶餘額為零。用 GET /v1/usage 判斷是哪一種,然後只對該租戶顯示「額度已用完」。
  • 429 apikey_concurrency_limit:該租戶超出了自己的併發上限。只對這個租戶排隊或退避,不影響其他租戶。
  • 429 user_concurrency_limit:你整個帳戶到了上限。這需要討論容量,而不是針對某個租戶修正。
  • 404 model_not_found:租戶請求了方案以外的模型。可以把它對應成升級提示。

上線前檢查清單

  1. 每把租戶 Key 都設定了額度和併發上限。
  2. 帳本寫入的是回傳的扣款,絕不是本機價格表。
  3. 串流請求和中途斷線的請求,用 CSV 匯出來結算。
  4. 非同步任務的扣款在任務進入最終狀態時寫入,並依各租戶存下的任務 ID 歸屬。
  5. 每月對帳時,依 Key 比對帳本合計和匯出檔案。

常見問題

不維護價格表,怎麼依客戶追蹤大型語言模型成本?

讀取閘道回傳的扣款。在 HopBase 上,同步呼叫看 X-HopBase-Cost-USD 標頭或 usage.cost_usd,非同步任務完成後看 usage.cost_usd。它們都是該請求實際扣除的美元金額。

能限制某個客戶最多花多少嗎?

可以。為這個客戶的 API Key 設定額度。用完後,該 Key 的請求回傳 402 insufficient_quota,其他 Key 照常運作。

為什麼串流回應沒有扣款標頭?

標頭在生成開始前就已送出,那時還不知道最終扣款。把串流最後一個 usage 事件中的 token 數記為待結算紀錄,再依主控台使用記錄或 CSV 匯出補上金額。

不同客戶可以使用不同的模型嗎?

可以。每把 Key 只加入你選擇的分組(1 到 10 個),方案由 Key 本身執行。呼叫 Key 分組以外的模型會回傳 404。

詳細說明在哪裡?

扣款欄位和 /v1/usage 見帳戶與目錄 API,上限見併發、逾時與計費,CSV 對帳方法見我們的用量對帳指南。定價見 hop-base.com/pricing。