0 · 背景:一次真實嘅封鎖事件
2026-08-01,Hermes Agent 嘅主力 OpenRouter 帳戶突然被 Google、OpenAI、Anthropic 三廠聯合封鎖(403 violation of provider Terms Of Service)。診斷結果:
| 測試 | 結果 | 結論 |
|---|---|---|
| OpenAI 系(gpt-image-1 / gpt-5-image-mini) | 403 TOS | 帳戶被永久標記(permanently flagged),與 IP / credits / 設定無關,retry 無效 |
| Google 系(gemini-3-pro-image / gemini-3.6-flash vision) | 403 TOS | |
| Anthropic(claude-sonnet-4) | 403 TOS | |
| z.ai / Qwen / xAI / Meta 系 | 正常 | 文字 + 讀圖仍然可用 |
關鍵教訓:唔好將全部 AI 能力押喺單一第三方平台。OpenRouter 係聚合商,帳戶一被標記就全線失效。與其開新帳戶(有同 IP 關聯風險),不如遷去基礎設施商 Cloudflare — 佢係 DNS/CDN 供應商,唔會因「AI 內容政策」封你。
1 · Cloudflare Workers AI 概覽
1.1 免費額度(重點記住)
| 項目 | 額度 | 備註 |
|---|---|---|
| Neurons | 10,000 / 日 | 每日 00:00 UTC 重置,毋須信用卡 |
| 並行請求 | 1 個 | 連環 call 會排隊,要設 sleep |
| 超過額度 | $0.011 / 1,000 neurons | 要 Workers Paid plan 先可以超 |
| R2 儲存 | 10 GB + 零 egress | 每月 100 萬次寫入 / 1000 萬次讀取 |
| Paid-only 模型 | 3 個 | kimi-k2.6 / kimi-k2.7-code / glm-5.2 免費 plan 用唔到 |
1.2 可用模型(61 個,實測)
gemma-4-26b 頂上task:query + question)都回傳空 {};llava-1.5 係 Beta 標記 + 400。唯一可靠嘅 vision 係 @cf/google/gemma-4-26b-a4b-it(Text Generation task 但支援圖片輸入)。2 · 生圖管線(免費,每日 ~170 張)
核心:CF flux-1-schnell 做主力,Pollinations.ai 做後備。Pollinations 完全免費無 key(unlimited-ish),質素中等;CF flux 質素高,有明確每日額度。
2.1 費用估算(實測 pricing 頁)
| 模型 | 成本 | 每日張數(10k neurons) | 用途 |
|---|---|---|---|
| flux-1-schnell | ~57.6 neurons/張(1024², 4 steps) | ~170 | 日常主力 |
| flux-2-klein-4b | ~104 neurons/張 | ~90 | 快 + 平 |
| flux-2-klein-9b | ~1,364 neurons/張 | ~7 | 圖生圖專用(貴,慳住用) |
| leonardo phoenix | ~540 neurons/張 | ~18 | 高質 |
2.2 核心 call(直連 API,唔使部署 Worker)
# 生圖 — POST /ai/run/<model>,JSON body
curl -X POST "https://api.cloudflare.com/client/v4/accounts/$CF_ACCOUNT_ID/ai/run/@cf/black-forest-labs/flux-1-schnell" \
-H "Authorization: Bearer $CF_WORKERS_AI_TOKEN" -H "Content-Type: application/json" \
-d '{"prompt":"a red apple on a wooden table","steps":4}'
# 回傳 {"result":{"image":"<base64>"}} → base64 解碼即係圖片
3 · 圖生圖(FLUX.2 嘅隱藏技巧)
flux-2-klein-9b 收圖生圖,但唔係 JSON — 用 JSON 傳 image 會出 400 Type mismatch。佢嘅 schema 係 multipart/form-data!# 正確做法:-F multipart,image 用檔案上傳
curl -X POST ".../ai/run/@cf/black-forest-labs/flux-2-klein-9b" \
-H "Authorization: Bearer $CF_WORKERS_AI_TOKEN" \
-F "prompt=同一個場景,但女仔而家伏喺沙灘上,golden hour" \
-F "image=@reference.jpg" # ← multipart 檔案,唔係 base64!
# → 直接回傳編輯後嘅圖(base64)
呢個係成個遷移最關鍵嘅發現:img2img 用 multipart,txt2img 用 JSON。用 Python requests 就係 files={"image": (name, f, "image/jpeg")} + data={"prompt": ...}。
4 · 讀圖管線(雙 gemma-4 通道)
Hermes 嘅 vision fallback 原本會跌返去主 model(deepseek-v4-pro),而 deepseek 係純文字 — 收咗圖但「睇唔到」(實測佢嘅 reasoning 話 I don't see any image attached)。解法:顯式設定 aux vision provider。
# config.yaml — 主力:OpenRouter gemma-4:free(26B,免費 :free 唔受三廠封鎖影響)
auxiliary:
vision:
provider: openrouter
model: google/gemma-4-26b-a4b-it:free
後備:CF 版同一個 model(@cf/google/gemma-4-26b-a4b-it,OpenAI-compatible messages 格式)— 寫成 cf_vision.py,gemma 主力失效時自動頂上。性能排名(實測):
| 排名 | 模型 | 規格 | 實測 |
|---|---|---|---|
| 🥇 主力 | gemma-4-26b-a4b-it:free(OpenRouter) | 26B(4B active MoE) | 詳細準確 |
| 🥈 後備 | gemma-4-26b-a4b-it(CF) | 同一 model 唔同 host | 正常 |
| ❌ | moondream3.1 / llava-1.5(CF) | 9B / 7B | 空回傳 / 400 |
4.1 實戰:表格圖 → 結構化數據(消委會《選擇》月刊)
vision 管線最實用嘅場景:直接讀表格圖,輸出結構化 JSON。消委會《選擇》測試報告嘅品牌表(牌子/型號/售價/評分)好多時只存在於 PDF 頁面圖,或者 OCR 出嚟錯晒 — 用 vision 讀圖最穩陣。方法係同一 OpenAI-compatible messages 格式(image_url data URL base64),CF gemma-4-26b 直連 /ai/run 就做到,唔使部署 Worker。
# 表格圖 → JSON(CF gemma-4 直連,無需 Worker)
import base64, urllib.request, json
b64 = base64.b64encode(open("p5.png", "rb").read()).decode()
body = {"messages": [{"role": "user", "content": [
{"type": "text", "text": PROMPT},
{"type": "image_url", "image_url": {"url": f"data:image/png;base64,{b64}"}}]}]}
# POST /accounts/$CF_ACCOUNT_ID/ai/run/@cf/google/gemma-4-26b-a4b-it
實測(2026-08-03,14 款雲端儲存服務真實表格頁):
| 模型 | 合成測試表 | 真實消委會頁 | 備註 |
|---|---|---|---|
| gemma-4-26b-a4b-it(CF) | ✅ 100% | ✅ OneDrive / SurDoc 等 | 免費 neurons 內,每日 10k |
| grok-4.5(OpenAI 兼容 API) | ✅ 100% | — | 收費 API 都得 |
| deepseek-v4-flash / pro | ❌ 收圖睇唔到 | ❌ | 純文字 model,忽略圖像 |
| qwen3-vl-30b(CF) | ❌ 400 | ❌ | list 有但 plan 用唔到(No route) |
提示詞關鍵:嚴格 JSON array、品牌名中文優先、價格只取數字(HK$1,299 → 1299)、is_recommended=總評最高(並列都要)、is_warning=致癌物/超標、冇品牌表輸出 []。表格橫跨多頁 → 每 3 頁一組 chunk,合併再去除重。
max_tokens=4000 會喺 JSON 中間截斷 → parse fail(flash 同 pro 都中);② vision 幻覺 — 讀表格會編造型號/分數,必須同文章敘述、樣本數交叉驗證先信;③ 表格頁要搵位 — 表通常喺文章中後段(p5/p6),逐頁試或者先用文字敘述定位。5 · 存檔系統(Drive + R2 雙軌)
AI-images/YYYY-MM/,並寫入 images_index.jsonl 記錄 prompt + Drive link。用現有 google-workspace OAuth,零新設定。ai-images bucket,S3-compatible API(boto3)。超過 50%(5GB)自動刪最舊到 4GB,每日 03:00 cron。# R2 = S3-compatible:endpoint 用 account ID
endpoint = f"https://{R2_ACCOUNT_ID}.r2.cloudflarestorage.com"
s3 = boto3.client("s3", endpoint_url=endpoint,
aws_access_key_id=R2_ACCESS_KEY_ID, # 32 字元
aws_secret_access_key=R2_SECRET_ACCESS_KEY) # 64 字元
s3.create_bucket(Bucket="ai-images") # 免費 plan 都得
s3.upload_file(path, "ai-images", name) # 零 egress 費用
6 · 監控(watchdog 模式)
兩個 cron 都用「靜默 unless 有事」模式(stdout 空 = 唔推 Telegram):
| Job | 頻率 | 行為 |
|---|---|---|
| cf-quota-monitor | 每小時 | 讀 cf_usage.log(每張圖自動記錄 neurons),>80%(8,000)先推警報 + model 分佈 + 剩餘張數 |
| r2-cleanup | 每日 03:00 | R2 用量 >5GB → 刪最舊到 4GB,有刪先推報告 |
# cf_usage.log 一行一個記錄(生圖時自動寫)
2026-08-01 12:45 flux-1-schnell 57.6
2026-08-01 12:58 flux-2-klein-9b 1545.0
7 · 文件清單(要準備啲咩)
7.1 Credentials(4 樣)
| 憑證 | 攞法 | 用途 |
|---|---|---|
| CF Account ID | Dashboard 右欄 | 所有 API 嘅 path 前綴 |
| CF Workers AI Token | My Profile → API Tokens → Workers AI 模板(Account → Workers AI → Edit) | 生圖 / 讀圖 / TTS / STT |
| R2 S3 Token(Access Key + Secret) | R2 → Manage R2 API Tokens(唔係 Profile API Tokens!) | 圖片存檔(Access=32字元 / Secret=64字元) |
| Google OAuth(client_secret + token) | Google Cloud Console → OAuth 2.0 Client | Drive 存檔(google-workspace skill 管理) |
Credential access key has length 64, should be 32。7.2 .env 變數(5 組)
CF_WORKERS_AI_TOKEN=cfut_xxxxxxxx... # Workers AI
CF_ACCOUNT_ID=your_account_id... # Account ID
R2_ACCOUNT_ID=your_account_id... # = Account ID(endpoint 用)
R2_ACCESS_KEY_ID=your_access_key... # 32 字元
R2_SECRET_ACCESS_KEY=your_secret_key... # 64 字元
OPENROUTER_API_KEY=sk-or-... # gemma-4:free 讀圖(保留)
7.3 Scripts(4 個,全部喺 ~/scripts/)
| Script | 功能 |
|---|---|
gen_image.py | 生圖(CF flux 主 → Pollinations 後備)+ img2img(--ref)+ 自動存 Drive/R2 + usage log |
cf_vision.py | CF gemma-4 讀圖(後備通道) |
r2_storage.py | R2 upload / usage / cleanup(>5GB 刪舊) |
cf_quota_monitor.py | 每日 neurons 用量統計 + 80% 警報 |
注意:gen_image.py 要用 hermes venv python(有 PIL 先可以 crop 比例),/usr/bin/python3 冇 PIL。
8 · 踩坑合集(全部親測)
- 403 TOS 唔一定係內容問題 — 連「a red apple」都被拒 = 帳戶層面封鎖,retry 冇用。用非 Google/OpenAI/Anthropic 模型(z.ai/qwen/xAI)即刻確認。
- OpenRouter :free 模型唔受三廠封鎖影響 —
gemma-4-26b-a4b-it:free係讀圖最佳免費選擇。 - CF flux-2-klein-9b 用 multipart 唔係 JSON — JSON 傳 image 會 400。
- CF 官方 model list 有 ≠ 用得 — moondream 空回傳、llava 400(Beta);gemma-4 頂上。
- Flux 固定輸出 1024×1024 — 要 4:5 / 16:9 就要用 PIL 中心裁剪(width/height 參數係無效嘅)。
- deepseek 收圖但睇唔到 — 純文字 model;aux vision 要顯式設 vision-capable model。
- curl 傳大 base64 圖會「Argument list too long」 — 寫 JSON body 落 file 再用
-d @file。 - 免費 tier 得 1 個並行請求 — batch 要加 sleep,否則排隊。
9 · 快速開始(3 步)
- 攞憑證:CF Workers AI token + R2 S3 token(見 §7.1),寫入
~/.hermes/.env - 放 Scripts:
gen_image.py/cf_vision.py/r2_storage.py入~/scripts/,用hermes venv python執行 - 試:
python gen_image.py "prompt" 1024 1024 /tmp/a.png --ref ref.jpg→ 自動生圖 + 存 Drive + 存 R2
# 一條 command 完成:生圖 + 雙備份 + 用量記錄
python ~/scripts/gen_image.py "a cyberpunk cat in neon Tokyo" 1024 1024 /tmp/cat.png
# [cloudflare-flux] /tmp/cat.png
# [drive] archived -> https://drive.google.com/file/d/...
# [r2] archived
10 · 補充建議(由可用變成 production-ready)
以下係工程師覆核後嘅補充 — 聚焦「可用 → production-ready」嘅細節:憑證最小權限、重試策略、成本控制、後備深度、雙軌對賬。
10.1 憑證安全:Token 最小權限 + Rotation
- Workers AI Token — 「Account → Workers AI → Edit」已經係最細權限;如有多個 project,建議開 Account-scoped(而唔係 User-scoped),方便 revoke 時唔影響其他用途。
- R2 S3 Token — 可以開「Object Read & Write only」(唔使 Admin),並且設指定 bucket(
ai-images)— 即使 token 洩漏都只係得一個 bucket 嘅權限。 - Rotation 策略 — 建議每 90 日 rotation 一次;CF API Token 可以開多個,新舊並行一陣再刪舊,唔會斷服務。
10.2 重試策略:唔係淨係 sleep
免費 tier 得 1 個並行,實際會遇到三種情況:
| 情況 | HTTP Status | 處理建議 |
|---|---|---|
| Rate limit | 429 | 讀 Retry-After header,跟佢等 |
| 排隊中(極慢) | 200 | 設 timeout=60s,超時即視為失敗 fallback |
| 模型暫時不可用 | 500 / 503 | 即時 fallback 去 Pollinations,唔使 retry |
# gen_image.py 加 retry decorator,但 429 唔應該 blind retry
@retry(
stop=stop_after_attempt(3),
wait=wait_exponential(multiplier=1, min=2, max=10),
retry=retry_if_exception_type((
requests.exceptions.Timeout,
requests.exceptions.HTTPError
))
)
重點:429 要讀 Retry-After,否則會越踩越重。
10.3 Flux steps × neurons 嘅非線性關係
§2.1 嘅 57.6 neurons 係 4 steps 數值 — steps 對成本影響好大:
| Steps | ~Neurons/張 | 質量 | 建議場景 |
|---|---|---|---|
| 1 | ~20 | 草稿級 | 快速 preview、測試 prompt |
| 4 | ~58 | 可用 | 日常主力(原指南建議) |
| 8 | ~110 | 較好 | 重要輸出 |
| 20 | ~260 | 最佳 | 極少需要,不如用 flux-2-dev |
慳 neurons 技巧:batch 生成(例如一次出 10 張變體),可以先用 step=1 做 preview,用戶揀中先出 step=4/8,慳大量 neurons。
10.4 Pollinations fallback 嘅隱藏限制
- 無 key ≠ 無限制 — 單一 IP 有隱性 rate limit(約 10 req/min),超過會 return 空或極慢。
- 無 SLA — 有時會 down 幾分鐘,fallback 邏輯要處理 timeout。
- Prompt 兼容性 — weight syntax(如
(...:1.2))可能唔支援;建議保持 prompt 簡潔,唔用進階語法。
建議 fallback 順序:CF flux → Pollinations → 本地 SD(如有)→ 文字描述代替。
10.5 R2 與 Drive 雙軌嘅一致性
- Drive 係「寫入後即忘」 — 上傳成功就完,冇 checksum。
- R2 可以驗證 — upload 後用
head_object讀 ETag,同本地 file 嘅 MD5 比對。
# gen_image.py 生成後寫 manifest.jsonl(每行一條,方便日後對賬)
{"timestamp":"...","prompt":"...","drive_id":"...","r2_key":"...","md5":"...","model":"flux-1-schnell","neurons":57.6}
R2 cleanup 刪舊檔時,最好連 manifest 一齊標記 deleted: true,而唔係真係刪 manifest entry,保留 audit trail。
10.6 圖片 metadata:EXIF 寫入 prompt
# 用 PIL 將 prompt、model、timestamp 寫入 PNG 嘅 tEXt chunk
from PIL import Image, PngImagePlugin
img = Image.open("/tmp/cat.png")
metadata = PngImagePlugin.PngInfo()
metadata.add_text("prompt", prompt)
metadata.add_text("model", "flux-1-schnell")
metadata.add_text("generated_at", datetime.utcnow().isoformat())
img.save("/tmp/cat.png", pnginfo=metadata)
咁樣即使 manifest 遺失,都可以從圖片本身讀返原始 prompt,方便 reproduce。
10.7 CF Workers AI 嘅「隱藏」endpoint:AI binding
原指南用直連 API(/ai/run/...)。如果已部署 Cloudflare Worker,可以用 AI binding:
// worker.js
const response = await env.AI.run(
'@cf/black-forest-labs/flux-1-schnell',
{ prompt: "..." }
)
好處:唔使管理 token(binding 用 account 權限)、可以做 caching、rate limiting、auth 層。壞處:要部署 Worker,多一層 infra。對於 Hermes 嘅 use case,直連 API 已經夠簡潔;但如果將來要開 API 畀多人用,binding 方式會更易管理 quota。