feat(chat): Gemini 月費用上限 $10 USD + Redis 累計追蹤
All checks were successful
CD Pipeline / build-and-deploy (push) Successful in 6m55s

- 每次呼叫前檢查當月累計費用,超過 $10 USD 拒絕呼叫
- Redis key: gemini_cost:YYYY-MM,TTL 40 天
- 每次回覆顯示: token 數 | 本次費用 | 本月累計
- 超限時回傳警告訊息告知老闆

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
OG T
2026-04-03 19:01:21 +08:00
parent 2828cd897a
commit 08b02280f8

View File

@@ -90,6 +90,21 @@ class ChatManager:
logger.warning("openclaw_chat_failed", error="GEMINI_API_KEY not configured")
return None
# 月費用上限檢查 ($10 USD)
MONTHLY_LIMIT_USD = 10.0
from src.core.redis_client import get_redis
from src.utils.timezone import now_taipei
redis = get_redis()
month_key = f"gemini_cost:{now_taipei().strftime('%Y-%m')}"
try:
current_cost = float(await redis.get(month_key) or 0)
except Exception:
current_cost = 0.0
if current_cost >= MONTHLY_LIMIT_USD:
logger.warning("openclaw_gemini_monthly_limit_reached", current_usd=current_cost, limit_usd=MONTHLY_LIMIT_USD)
return f"🔴 OpenClaw 本月 Gemini 用量已達上限 ${MONTHLY_LIMIT_USD} USD已用 ${current_cost:.4f}"
# Gemini 1.5 Flash: 快速、便宜
model = "gemini-1.5-flash"
try:
@@ -106,14 +121,22 @@ class ChatManager:
data = resp.json()
text = data["candidates"][0]["content"]["parts"][0]["text"].strip()
# Token/費用統計
# Token/費用統計 + 累計到 Redis
usage = data.get("usageMetadata", {})
in_tok = usage.get("promptTokenCount", 0)
out_tok = usage.get("candidatesTokenCount", 0)
cost = (in_tok * 0.000000075) + (out_tok * 0.0000003)
logger.info("openclaw_gemini_usage", in_tokens=in_tok, out_tokens=out_tok, cost_usd=round(cost, 6))
new_total = current_cost + cost
return f"{text}\n\n<i>📊 {in_tok+out_tok} tokens | ${cost:.4f}</i>"
try:
await redis.set(month_key, str(round(new_total, 6)), ex=40 * 24 * 3600) # 40天 TTL
except Exception:
pass
logger.info("openclaw_gemini_usage", in_tokens=in_tok, out_tokens=out_tok,
cost_usd=round(cost, 6), monthly_total_usd=round(new_total, 4))
return f"{text}\n\n<i>📊 {in_tok+out_tok} tokens | ${cost:.4f} | 本月累計 ${new_total:.4f}</i>"
except Exception as e:
logger.warning("openclaw_chat_failed", error=str(e))
return None