先講結論:多租戶 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:租戶請求了方案以外的模型。可以把它對應成升級提示。
上線前檢查清單
- 每把租戶 Key 都設定了額度和併發上限。
- 帳本寫入的是回傳的扣款,絕不是本機價格表。
- 串流請求和中途斷線的請求,用 CSV 匯出來結算。
- 非同步任務的扣款在任務進入最終狀態時寫入,並依各租戶存下的任務 ID 歸屬。
- 每月對帳時,依 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。