重點摘要(TL;DR)
- 前 8 篇是藍圖。本篇是實作真實版:在 Mini PC(無 GPU、32GB RAM、Ryzen 7)用 364 行 FastAPI 跑通搬離方法論,真能接 Claude Code。
- 核心邏輯:Gateway 看 prompt 內容,命中 A 級字典 → 地端最強模型(14b);其他 → cloud Claude(若有 API key)或 fallback 地端。
- 關鍵設計原則(別搞錯):A 級資料用地端最強模型,不是最弱。敏感資料因為更重要,需要更可靠的回答。小模型只能當分類器或 fail-safe。
- 真接 CC 的關鍵:用 Anthropic 原生
/v1/messagesendpoint,不是 OpenAI 的/v1/chat/completions,並做完整翻譯層(request / response / tool use / SSE)。 - Harness 三 agent 永遠走 cloud(地端跑不動三 agent 並行 + long context),只是輸入經 Gateway 強脫敏 — 這是搬離後最關鍵的工作流保護。
- 本文是腦子系統九部曲實證篇。前八篇:Why / How / Scale / Tools / ERP / Self-Service / ISO / Execution。
一、為什麼寫這篇 — 從藍圖到實作真實版
前 8 篇腦子系統累積了大量「應該怎樣」的論述:Why / How / Scale / Tools / ERP / Self-Service / ISO / Execution。對真正要動手的人,這些都還是紙上的東西。
本篇是分水嶺 — 用一台 Mini PC(沒 GPU,32GB RAM,Ryzen 7 4700U,2020 年款)跑通可以真的接 Claude Code 的搬離 Gateway,證明:
- 不需要 GPU,純 CPU 也能 host gateway logic
- 不需要 LiteLLM / Portkey 等大框架,純 Python 364 行搞定
- 不需要 ANTHROPIC_API_KEY 也能跑(有 fallback 模式)
- CC + Agent Team + Harness 工作流不變,只改 BASE_URL
二、5 條設計原則(別搞錯)
原則 1:A 級資料地端,不可協商
A 級的定義是「送出去會出事」 — 客戶機密、財報、製程 know-how。這個層級不能因為 cloud 模型強就送出。地端是底線。
原則 2:A 級用地端最強模型,不是最弱
這條最容易搞錯。直覺是「敏感資料 = 風險高 = 用小模型」,但 logic 應該倒過來:敏感資料因為更重要,需要更可靠的回答。
| 情境 | 地端模型選擇 | 理由 |
|---|---|---|
| A 級主處理 | 地端最強(14b / 32b / 80B-A3B) | 資料越敏感,回答越要可靠 |
| 分級判斷器 | 小模型(0.5b / 1.7b)or regex | 分類本身不需要強能力 |
| Fail-safe 容錯 | 小模型保守路由 | 寧可路由保守不要錯放 |
原則 3:路由邏輯走字典 + regex,不靠 LLM
分級判斷不該交給 LLM(慢、不確定、可被 prompt injection 騙)。改用字典 + regex,毫秒級完成,可審計。
原則 4:Anthropic 原生 endpoint(/v1/messages),不是 OpenAI 的 /v1/chat/completions
CC 用 Anthropic Messages API,你 Gateway 必須 expose /v1/messages,不是 OpenAI 的 endpoint。並且做完整 Anthropic ↔ OpenAI 翻譯(因為地端 Ollama 用 OpenAI compatible 格式)。
原則 5:沒 API key 也能跑(fallback 地端)
Gateway 設計成:有 ANTHROPIC_API_KEY 就 C 級走真 cloud Claude;沒有就 fallback 走地端。讓你能純地端先驗證 logic,再加 cloud。
2.1 雙維度決策表(敏感度 × 可用性)— 別搞混
fallback 不只看「cloud 有沒有 key」,還要看「資料能不能上 cloud」。雙維度決策才完整:
| 分級 | 主路由 | Fallback | 關鍵保護 |
|---|---|---|---|
| A 級 | 地端最強(14b/32b/80B) | 沒 fallback — 地端跑不動 = 等 / 改題目 | 即使有 cloud key 也不走 cloud |
| B 級 | 地端優先 | 地端不可用 → 脫敏後 cloud | 能脫敏才 fallback,不能脫敏寧願報錯 |
| C 級 | cloud 優先 | 沒 key → 地端 | 純技術問題,無敏感度 |
常見誤解:有 cloud key 就什麼都走 cloud。錯。A 級即使有 key 也不該走 cloud — 因為「資料外洩風險 > 模型能力差異」。Gateway 的職責就是替你擋住這個誘惑:你 prompt 命中 A 級字典,Gateway 不問你「要不要送 cloud」,直接路由到地端。
本版實作狀態:A 級 + C 級已實作完整;B 級的「地端優先 + cloud fallback + 脫敏」是 TODO,本版 B 級 keyword 命中時邏輯等同 A 級(全地端)。完整 B 級實作見最末「待補的東西」章節。
三、364 行 Gateway 完整實作
結構:
gateway.py(364 行)
├─ Classifier (~30 行)— 抽 messages 文字 + 字典命中
├─ Anthropic→OpenAI Req (~80 行)— system / messages / tool_use / tool_result 翻譯
├─ OpenAI→Anthropic Resp (~40 行)— content blocks / stop_reason / usage
├─ SSE Streaming (~40 行)— 6 種 Anthropic 事件 from OpenAI delta
├─ Backend Forwarders (~80 行)— Ollama / Anthropic 雙路 forward + fallback
└─ Main Endpoint (~30 行)— /v1/messages,分類後派到對應 forward
3.1 核心邏輯(主要 dispatcher)
@app.post("/v1/messages")
async def messages(request: Request):
auth = request.headers.get("authorization", "")
if MASTER_KEY not in auth and not ANTHROPIC_API_KEY:
raise HTTPException(401, "bad master key")
body = await request.json()
original_model = body.get("model", "claude-opus-4-7")
decision, keyword = classify(body.get("messages", []), body.get("system"))
if decision == "A":
log.warning(f"[A-LEVEL] 命中 '{keyword}' → 地端 {MODEL_A_LEVEL}")
return await forward_to_ollama(body, MODEL_A_LEVEL, original_model)
else:
log.info(f"[C-LEVEL] → cloud {original_model}" if ANTHROPIC_API_KEY else f"[C-LEVEL] no key → local fallback")
return await forward_to_anthropic(body, request, original_model)
3.2 Anthropic ↔ OpenAI 翻譯的 4 個關鍵點
# 1. Anthropic system 是 top-level → OpenAI 是 system message
sys = body.get("system")
if isinstance(sys, str):
openai_messages.append({"role": "system", "content": sys})
# 2. Anthropic tool_use 是 content block → OpenAI 是 message 上的 tool_calls
if btype == "tool_use":
tool_calls.append({
"id": block["id"],
"type": "function",
"function": {"name": block["name"],
"arguments": json.dumps(block["input"])}
})
# 3. Anthropic tool_result 在 user message 內 → OpenAI 是 role:tool 獨立 message
if btype == "tool_result":
openai_messages.append({
"role": "tool",
"tool_call_id": block["tool_use_id"],
"content": str(result_content)
})
# 4. SSE 翻譯:OpenAI delta 累積 → Anthropic 6 種事件
# message_start → content_block_start → content_block_delta(每個 token)
# → content_block_stop → message_delta(stop_reason)→ message_stop
3.3 Forwarder(雙路 + fallback)
async def forward_to_ollama(body, target_model, original_model):
"""A 級 → 翻譯成 OpenAI format,forward to Ollama 地端強模型。"""
openai_body = anthropic_to_openai_request(body, target_model)
is_stream = openai_body.get("stream", False)
if is_stream:
return StreamingResponse(stream_anthropic_from_openai(...))
async with httpx.AsyncClient(timeout=600) as client:
r = await client.post(f"{OLLAMA_URL}/v1/chat/completions", json=openai_body)
return JSONResponse(openai_to_anthropic_response(r.json(), original_model))
async def forward_to_anthropic(body, request, original_model):
"""C 級 → 直接 proxy 到 api.anthropic.com,沒 key 就 fallback 地端。"""
if not ANTHROPIC_API_KEY:
return await forward_to_ollama(body, ANTHROPIC_FALLBACK_MODEL, original_model)
headers = {"x-api-key": ANTHROPIC_API_KEY, "anthropic-version": "2023-06-01"}
if body.get("stream"):
# SSE 直接透傳(Anthropic format,不用翻譯)
return StreamingResponse(...)
async with httpx.AsyncClient(timeout=600) as client:
r = await client.post("https://api.anthropic.com/v1/messages", json=body, headers=headers)
return JSONResponse(r.json())
v2.3 完整 Gist(674 行 gateway + 24 個 pytest + benchmark + demo + README,5 個檔案):
👉 https://gist.github.com/tm731531/c82c51ae2a73bfe640dec5b61e5a542a
Gist 含 README + 5 步驟啟動 + 測試 curl 範例 + 已知限制。clone 下來改字典即可用。
3.1 v2 → v2.1 changelog(review 後修)
v2 上 Gist 後又收到 review,點出 3 個有實際影響的 bug,其中 1 個是安全問題。**全修了**:
- 🔴 Bug 1(安全):Auth 邏輯反了 — 原本「沒設 cloud key 才檢查 master_key」意思是「接了 cloud 反而不檢查」,任何人能燒你 quota。修法:無條件檢查 master_key,並兼容
x-api-key+Authorization: Bearer兩種 header。實測 no-key/wrong-key 都回 401 - 🔴 Bug 2(功能):Streaming 模式 tool use 完全不工作 — 原本
stream_anthropic_from_openai只翻譯 text delta,沒處理delta.tool_calls。CC 的 Read/Edit/Bash 都是 tool use → A 級 + streaming 時 CC 卡住。修法:加 tool_calls delta 累積邏輯,追蹤tool_call_index → our_block_indexmapping,送content_block_start (tool_use)+input_json_delta事件序列。約 +60 行 - 🟡 Bug 3:streaming 模式
stop_reason寫死成end_turn,即使 OpenAI 端因 max_tokens 截斷或 tool_calls 收尾也誤標。修法:streaming 過程累積最後finish_reason,結束時用真實值映射(stop→end_turn / length→max_tokens / tool_calls→tool_use) - + 結構改進:content blocks 改 lazy open(只在真有內容時送
content_block_start),text 跟 tool 可正確交錯;dead import 清掉;docstring 改寫(原版誤稱用 sse-starlette)
從 v1(80 行,描述跟 code 矛盾) → v2(364 行,文字宣稱) → v2 Gist(394 行,實際存在但 3 bug) → v2.1(502 行,bug 修完)。三天四個版本,每一輪 review 都點出真實問題。這個迭代過程本身就是 brain 系統「review-driven development」的最佳示範。
四、CC + Agent Team + Harness 三件事的協作
4.1 CC 接 Gateway(0 行 code 改動)
# Terminal 設環境變數
export ANTHROPIC_BASE_URL=http://localhost:4000
export ANTHROPIC_AUTH_TOKEN=sk-walsin-test
# 跑 CC 跟原本一樣
claude
CC 完全不知道後面接的是 Gateway。所有 prompt 自動經分類 → 路由。
4.2 Agent Team 走 Gateway(子進程繼承 BASE_URL)
你在 CC 裡 spawn 7 個 opus agent 並行 — 每個 sub-agent 共用同一個 BASE_URL(從父 process 繼承)。Gateway 對每個 agent 的 prompt 獨立分類:
你 (CC main)
├─ Agent 1 (opus): "review 這份 SAP API 設計" → C 級 → cloud Claude
├─ Agent 2 (opus): "找 [client_alpha] 客訴 case" → A 級 → 地端 14b
├─ Agent 3 (opus): "寫 Kafka consumer" → C 級 → cloud Claude
├─ Agent 4 (opus): "看 [project_xxx] 的合約" → A 級 → 地端 14b
├─ Agent 5-7 (opus): 其他 C 級任務 → cloud Claude
大多數 Agent Team 任務不命中 A 級字典,99% 體感跟原本一樣。少數命中的會走地端,慢一點但隔離。
4.3 Harness 三 agent — 永遠走 cloud(關鍵保護)
Anthropic 2026/3 發布的三 agent harness(Planner / Generator / Evaluator)是給 cloud 設計的。地端 80B-A3B 跑三 agent 並行 = GPU 排隊,根本跑不動。
正解:Harness 永遠走 cloud,但輸入經 Gateway 強脫敏。
用戶: "幫我 refactor [project_xxx] 的支付模組"
↓
Gateway 偵測 [project_xxx](A 級字典)
↓
若強脫敏成功 → "幫我 refactor [PROJECT] 的支付模組" → cloud Claude(三 agent)
若無法脫敏 → 整個任務改地端 14b sequential 跑(慢但安全)
↓
Planner: 拆 task → Generator: 寫 code → Evaluator: 檢查
↓
結果經 Gateway 回到用戶
Harness 的價值在 long context + 複雜 reasoning,地端在這兩點本就弱。硬搬就是自虐。脫敏走 cloud 才是對的策略。
4.4 三件事的協作全景
你 (CC main session, ANTHROPIC_BASE_URL=gateway)
│
├─ 普通 prompt → Gateway → 路由 → 對應 backend
│
├─ Spawn Agent Team(7 個 opus 並行)
│ ├─ 每個 sub-agent 繼承 BASE_URL
│ ├─ Gateway 對每個 prompt 獨立分類
│ └─ A 級走地端 14b,C 級走 cloud Claude
│
└─ Spawn Harness(Planner / Generator / Evaluator)
├─ 三 agent 共用 BASE_URL
├─ Gateway 強制路由全 cloud(脫敏後)
└─ 因為地端跑不動三 agent 並行
五、Brain 系統整合(sensitivity_level frontmatter)
你的 brain markdown 系統(~/.claude/projects/.../memory/)是搬離的核心資產。整合方式:
5.1 brain frontmatter 加分級欄位
# 一般 brain(C 級,可上 cloud)
---
name: kafka_consumer_pattern
type: technical
sensitivity_level: C
---
Kafka consumer 群組 rebalance 機制...
# 敏感 brain(A 級,只地端 + 強模型)
---
name: client_alpha_oncall_pattern
type: business_incident
sensitivity_level: A
applies_to: [bu_xxx]
---
[client_alpha] 客訴流程,聯絡窗口...
5.2 build.sh 編譯時依分級過濾
#!/bin/bash
# 編譯雙版本 CLAUDE.md
# Cloud-bound CLAUDE.md(沒 A 級)
find brain/ -name "*.md" \
| xargs grep -L "sensitivity_level: A" \
| xargs cat > .claude/CLAUDE.md.cloud
# Local-bound CLAUDE.md(全部,A 級也進)
cat brain/**/*.md > .claude/CLAUDE.md.local
# Gateway 看員工任務目標選對應 CLAUDE.md
5.3 brain 的 A 級關鍵字自動同步到 Gateway 字典
# 從所有 A 級 brain 抽出 client name / project code 等
grep -h "sensitivity_level: A" -A 20 brain/**/*.md \
| grep -oP '\[client_\w+\]|\[project_\w+\]' \
| sort -u > /tmp/A_keywords.txt
# Gateway 啟動時 load
A_KEYWORDS = open("/tmp/A_keywords.txt").read().splitlines() + DEFAULT_A_KEYWORDS
5.4 公開版 brain repo 自動過濾
如果你的 brain 有公開版(教學分享 / 開源),build script 自動排除 sensitivity_level: A 條目,只發 B / C。不用手動審 brain 是否能公開。
這是brain 系統跟 Gateway 的接合點:你寫 brain 時標分級,Gateway 自動知道哪些字串該擋,公開版自動過濾。一個 frontmatter 欄位,三個地方用。
六、放大邏輯 — 個人 → 80 人 → 萬人
| 面向 | 個人(本文實證) | 80 人公司 | 萬人集團 |
|---|---|---|---|
| Gateway 實作 | 364 行 FastAPI | LiteLLM Docker | K8s HPA + Portkey |
| A 級字典 | 3-10 個關鍵字 | 100 個 | 1000+ 自動同步 brain |
| A 級 backend | Ollama Qwen3:14b(CPU) | Ollama Qwen3:32b(1x 4090) | 中央 GPU H100 跑 80B-A3B + 區域副本 |
| C 級 backend | cloud Claude(個人 API key)or fallback 地端 | Anthropic Enterprise | Anthropic Enterprise + Azure / Bedrock 多家 |
| 脫敏 | 無 | 字典 + regex | Microsoft Presidio + LLM 兜底 |
| 認證 | master key | 員工 SSO | SSO + Token Impersonation |
| Audit log | stdout | SQLite / OpenSearch | 三軌制 + WORM + HSM mapping |
| 治理 | 0 | Working Group | 三道防線 |
| 時程 | 30-60 分鐘 | 2-3 個月 | 12 個月 |
| 預算 | 0 | ~30 萬 NTD | 4000-6000 萬 NTD |
核心邏輯一模一樣(看 prompt → 字典分類 → 路由)。差的只是:
- 規模(字典條數、並發、儲存)
- 治理(Working Group、三道防線、ISO 認證)
- 合規(SOX / J-SOX / 個資法 / GDPR)
- 能力 backend(14b vs 80B-A3B)
七、能力降級補償策略
實際擔心:地端模型比 Claude Opus 4.7 弱,搬完會不會生產力崩?
實話:會降,看你會不會用補償工具。具體 benchmark 沒跑(個人 mini PC 沒 GPU 跑不了 32B+ 對比),但業界經驗的補償清單:
| 地端弱的地方 | 補償工具 | 效果 |
|---|---|---|
| Long context 弱 | RAG (Chroma / Qdrant) + chunking | context 不全進 LLM,只進 top-K |
| Reasoning 弱 | Chain-of-thought structured prompt | 強制分步,單步難度降 |
| Tool use 不穩 | function calling 限縮 5-10 個 tools | 減少選擇,提升正確率 |
| 並行 Agent 跑不動 | 改 sequential workflow | 一個跑完再下一個 |
| 跨檔 refactor 弱 | 限定 working set(≤ 5 檔) | 降低 context |
| Memory 弱 | brain markdown 強制 inject | 永遠帶 context |
而且這只用在 5% A 級任務,其他 95% 還是 cloud。整體生產力下降可控,具體百分比待 SWE-bench Lite 子集 + 真實工作流 case 量化。
八、5 步驟讓你今晚就跑起來
- 裝 Ollama + 拉模型:
ollama pull qwen3:14b # A 級主處理(地端最強) ollama pull qwen3:1.7b # 可選,當分類器 fail-safe - 裝 Python 套件:
pip install --user fastapi uvicorn httpx - 存 364 行 gateway.py(本文第三章 + 完整版見 GitHub Gist)
- 跑起來:
# 沒 API key 也能跑(fallback 地端) python3 gateway.py & curl -s http://localhost:4000/health # 確認 OK # 有 API key 完整版 ANTHROPIC_API_KEY=sk-ant-... python3 gateway.py & - CC 切過去:
export ANTHROPIC_BASE_URL=http://localhost:4000 export ANTHROPIC_AUTH_TOKEN=sk-walsin-test claude # 跟原本一樣寫 code
30-60 分鐘搞定。設定完後 99% 工作跟原本一樣,只有 prompt 命中 A 級字典時自動切地端。
九、跑不起來時會看到什麼(失敗模式排查)
Gist 證明能跑,失敗模式證明跑過。下面是實作過程實際踩過的 7 個錯誤:
| 錯誤訊息 / 症狀 | 根本原因 | 排查指令 |
|---|---|---|
connection reset by peer + log 完全空 |
Container 還在 init(LiteLLM 啟動慢 30s-1min),或 Python stdout buffering | docker exec <container> ps auxf 看 PID 1 是否還在跑;加 PYTHONUNBUFFERED=1 |
404 Not Found from CC |
Gateway 用 OpenAI /v1/chat/completions,CC 打 Anthropic /v1/messages |
看 Gateway log 有沒有「POST /v1/messages」;改用本文 Anthropic 原生 endpoint |
httpx.ReadTimeout 在 forward_to_ollama |
Ollama 模型在 CPU 第一次 load 太慢(超過 timeout) | ollama run <model> "warm" 先暖機;timeout 從 300 改 600 |
OCI runtime exec failed: "curl" not found |
LiteLLM image 沒裝 curl,內部 health check 工具有限 | 用 host 端 curl 測 http://localhost:4000/health 不要 docker exec |
{"detail": "bad master key"} |
CC 設了 ANTHROPIC_AUTH_TOKEN 但 Gateway 沒 match | echo $ANTHROPIC_AUTH_TOKEN 跟 Gateway 的 MASTER_KEY 對 |
| CC 卡住沒回應(streaming 不出來) | SSE 翻譯漏了 message_stop 事件,client 等不到結束 |
Gateway log 看最後送出的 event;確認 6 種事件全送(message_start → content_block_start/delta/stop → message_delta → message_stop) |
| A 級 prompt 沒命中字典(看到走 C 級) | 字典 keyword 是 case-sensitive 漏了 re.IGNORECASE,或字典裡沒這條 |
curl -s gateway.../health 看 keywords_count;echo $PROMPT | grep -i <keyword> |
十、Gist 上線前檢查清單(13 條)
從文章第一版到本版踩過的所有雷,清單化:
- Authorization header 兩種格式都要兼容:CC 可能送
x-api-key: xxx或Authorization: Bearer xxx,Gateway 都要認 - anthropic-version header 別漏:Anthropic API 要求
anthropic-version: 2023-06-01(或更新),proxy 過去要保留 - system 欄位三種型別都要處理:Anthropic 的
system可以是 string、list of {type:text,text:…},或 unset - tool_use ID 不能掉:翻譯後對應的 tool_calls 要保留同一個 ID,不然 client 對不上 tool_result
- tool_result 在 user message 內,翻譯後要拆成獨立 role:tool message
- SSE 6 個事件全送:
message_start → content_block_start → content_block_delta(每個 token)→ content_block_stop → message_delta → message_stop,漏一個 client 卡死 - SSE event 名稱要寫 event:,data: 兩行:不是只送 data,Anthropic SSE 格式有 event 名
- Ollama 連線斷掉時 fallback 邏輯不能 race:用
try/except包 forward_to_ollama,失敗才 fallback,不要兩個 task 同時跑 - timeout 要設 600 秒以上:CPU 跑 14b 慢,300 秒會 timeout
- master_key 預設值不要外洩:Gist 上的
sk-walsin-test是 placeholder,部署前換掉 - A 級字典不能放 secret:keyword 本身會出現在 log,別放真實 client name(用 placeholder 例如
[client_alpha]) - health endpoint 不檢查 master_key:不然 monitoring 工具會 401
- 關 Gateway 用 SIGTERM 不要 SIGKILL:
kill不加 -9 讓 uvicorn 優雅關閉,避免 streaming response 中斷
十一、TODO 全部 close(v2.2 update)
原本標的 4 個 TODO 全做完了,本版升 v2.2(620 行)。逐項說:
| 原 TODO | v2.2 處理 | 行數 |
|---|---|---|
| B 級「地端優先 + cloud fallback + 脫敏」 | ✅ 完整實作:ollama_alive() 健康檢查 → 失敗 sanitize_anthropic_body() → fallback cloud;sanitize 沒命中拒絕(503) |
+90 行 |
| Benchmark | ✅ benchmark_runner.py 獨立檔(258 行):跑 SWE-bench Lite 子集 + 自家 prompts × 多 model,輸出 markdown 報表。不打分,只跑數據(讓人類自己判斷,避免 premise drift) |
258 行新檔 |
| Asciinema 60 秒 demo | ✅ demo_record.sh:health → C 級 → A 級 → auth fail 4 個 step,可直接跑或 asciinema rec -c 包起來錄影 |
110 行新檔 |
| Token usage 真實計算 | ✅ 用 tiktoken 估算累積 text + tool args,取代原本的 chunk count(嚴重低估) | +20 行 |
11.1 v2.1 → v2.2 主要新邏輯
elif decision == "B":
# v2.2 完整 B 級實作
if await ollama_alive():
return await forward_to_ollama(body, MODEL_B_LEVEL, original_model)
# 地端死了,看能不能 fallback cloud
if not (ANTHROPIC_API_KEY and B_LEVEL_CLOUD_FALLBACK):
raise HTTPException(503, "B-level: local unavailable, cloud fallback disabled")
sanitized_body, hit = sanitize_anthropic_body(body)
if not hit:
# 地端死 + 脫敏沒命中 = B 字典跟脫敏字典不一致,寧願報錯
raise HTTPException(500, "B-level: local down + sanitization mismatch")
return await forward_to_anthropic(sanitized_body, request, original_model)
11.2 Sanitization 字典(v2.2 新增)
SANITIZE_MAP = {
r"\[internal_process\]": "[PROCESS]",
r"\[vendor_quote\]": "[QUOTE]",
r"\[employee_name\]": "[PERSON]",
# 通用 PII patterns
r"\b[\w.+-]+@[\w-]+\.[\w.-]+\b": "[EMAIL]",
r"\b(?:\d{1,3}\.){3}\d{1,3}\b": "[IP]",
r"\b\d{4}-\d{4}-\d{4}-\d{4}\b": "[CARD]",
}
實作策略:regex-based 簡單脫敏(快、可審計);生產環境建議升 Microsoft Presidio(NER + checksum + 多語言)。
11.3 Benchmark Runner 跑法
# 跑全部 prompts × 你已 pull 的 ollama 模型
python3 benchmark_runner.py
# 加 cloud Claude 對比(有 ANTHROPIC_API_KEY 才能)
ANTHROPIC_API_KEY=sk-ant-... python3 benchmark_runner.py \
--models qwen3:14b,qwen3:4b \
--anthropic-models claude-opus-4-7
# 只跑 SWE-bench Lite 子集
python3 benchmark_runner.py --suite swe --output report.md
跑出來是 markdown 報表,每 model × 每 prompt 的 latency / tokens / 截斷回應。故意不打分 — 因為「能力 = X%」這種宣稱本身就是 review 點過的 premise drift 風險。**跑數據給人看,人類自己判斷**,比 AI 講百分比有 integrity。
11.4 Demo 錄影
# 純跑(看 terminal output)
bash demo_record.sh
# 用 asciinema 錄影
asciinema rec -c "bash demo_record.sh" walsin-demo.cast
asciinema upload walsin-demo.cast # (可選)上傳分享
4 個 step:health check → C 級 prompt → A 級 prompt(命中字典)→ 沒帶 key 401。每一步都看到 x-gateway-decision + x-gateway-model headers。
11.5 v2.2 → v2.3 self-review 後再清 7 個漏洞
「考試不能邊改邊考」 — 我自己當最嚴格 reviewer 把 v2.2 從頭審一次,找到 7 個應修的(不是別人指出),全清:
| 優先 | 問題 | v2.3 修法 |
|---|---|---|
| 🔴 P0 | Auth substring match 漏洞 — MASTER_KEY not in auth 太寬,sk-test-extra 也通過 |
secrets.compare_digest 精確比對 + Bearer 解析 |
| 🔴 P0 | SSE 透傳格式錯 — aiter_lines + "\n" 會剝掉 \n\n event 結尾 |
改 aiter_bytes 直通,SSE 格式 byte-for-byte 完整 |
| 🔴 P0 | Sanitize 漏 tool_use input + tool_result content — 只處理 text block | 改遞迴 _sanitize_value 處理任意巢狀 dict / list / str |
| 🟡 P1 | MASTER_KEY 預設 hardcoded,生產環境壞習慣 | 沒設環境變數時 log warning,提示部署前必設 |
| 🟡 P1 | demo_record.sh 缺 pre-flight,gateway 沒啟動 script crash |
開頭加 curl /health,失敗給友善提示 + 啟動指令 |
| 🟡 P1 | /health 沒回報 ollama 狀態,monitoring 不夠 |
加 ollama: alive/down + b_level_model + b_cloud_fallback 配置 |
| 🟡 P1 | B 級走 cloud(脫敏後)client 不知道 | 回應加 X-Gateway-Sanitized: 1 header,透明度 |
11.6 24 個 pytest 全綠(v2.3 新)
$ pip install --user pytest pytest-asyncio
$ MASTER_KEY=sk-test-secret python3 -m pytest test_gateway.py -v
test_gateway.py::TestClassify::test_C_level_default PASSED
test_gateway.py::TestClassify::test_A_level_keyword_match PASSED
test_gateway.py::TestClassify::test_A_level_in_system PASSED
test_gateway.py::TestClassify::test_A_level_in_list_content PASSED
test_gateway.py::TestClassify::test_B_level_match PASSED
test_gateway.py::TestClassify::test_A_takes_precedence_over_B PASSED
test_gateway.py::TestMasterKey::test_correct_bearer PASSED
test_gateway.py::TestMasterKey::test_correct_bare PASSED
test_gateway.py::TestMasterKey::test_empty PASSED
test_gateway.py::TestMasterKey::test_wrong PASSED
test_gateway.py::TestMasterKey::test_substring_extra_suffix_blocked PASSED ← v2.3 修
test_gateway.py::TestMasterKey::test_substring_prefix_blocked PASSED ← v2.3 修
test_gateway.py::TestMasterKey::test_lower_case_bearer PASSED
test_gateway.py::TestSanitization::test_string_email PASSED
test_gateway.py::TestSanitization::test_string_ip PASSED
test_gateway.py::TestSanitization::test_string_no_hit PASSED
test_gateway.py::TestSanitization::test_recursive_dict PASSED
test_gateway.py::TestSanitization::test_recursive_list PASSED
test_gateway.py::TestSanitization::test_anthropic_body_text_block PASSED
test_gateway.py::TestSanitization::test_anthropic_body_tool_use_input_v23 PASSED ← v2.3 修
test_gateway.py::TestSanitization::test_anthropic_body_tool_result_v23 PASSED ← v2.3 修
test_gateway.py::TestRequestTranslation::test_system_string_to_message PASSED
test_gateway.py::TestRequestTranslation::test_tool_use_to_tool_calls PASSED
test_gateway.py::TestRequestTranslation::test_tool_result_becomes_separate_message PASSED
============================== 24 passed in 0.61s ==============================
4 個 v2.3 安全修正關鍵 test 全綠 — 證明 substring 攻擊擋下、tool_use input 真的會被 sanitize。
11.7 真的還剩什麼不會做(誠實)
- SWE-bench 完整跑數據:需要 GPU 跑 32B+,我這台 mini PC 不行。Runner 寫好了,你有 GPU 自己跑
- 真錄 asciinema 公開連結:script 寫好(含 v2.3 pre-flight check),你自己 run + upload
- Microsoft Presidio 升級:regex 已夠 demo,生產時換成 NER + checksum
- httpx async mock 整合測試:現在的 24 個 unit test 涵蓋純函式,async stream 整合測試還沒寫
策略:能在我環境做的全做,不能做的寫好工具讓你自己做。每一輪迭代都比上一輪誠實。
十二、5 個學到的事(實作後)
- Gateway 路由邏輯不複雜(364 行 Python 含完整翻譯層 + SSE),別被 LiteLLM / Portkey / Kong 這些大框架嚇到
- CC 工作流不用改(只改 BASE_URL),搬離成本低於想像。但要真接 CC 必須做 Anthropic 原生 endpoint + 完整翻譯層
- A 級資料用地端最強,不是最弱。敏感資料因為更重要,需要更可靠回答 — 這條最容易搞反
- Mini PC 雖弱但能跑(CPU 跑 14b 約 1-3 tok/s,慢但能用),證明搬離方法論不需要先投資 GPU
- Harness 不該硬搬地端(三 agent 並行 + 長 context 是 cloud 的價值,脫敏走 cloud 才是對的)
結語:從藍圖到可執行的搬離
前 8 篇腦子系統告訴你「應該怎樣」。本篇告訴你「實際怎樣」。
364 行 Python + Mini PC + Ollama + Claude Code = 搬離方法論的可執行實作。
這不是教你「怎麼蓋萬人企業 AI 治理」 — 那是另外 8 篇的事。
這是教你「怎麼今晚就在自己電腦上跑通搬離 logic」 — 證明你的方法論不只是紙上的。
有了這個實作,你才有立場跟集團 IT 提 PoC,跟 CFO 提預算,跟法遵提合規。
下一步:你的 mini PC 有沒有變慢?Agent Team 還能 spawn 嗎?Brain 還在嗎?都沒事 — 因為 Gateway 是個獨立 process,不影響任何沒設 BASE_URL 的工作流。你想停掉就 kill 一個 process,連配置都不用改。
這就是搬離方法論的真實樣子:低風險、可逆、漸進、實作在前、規模在後。
發佈留言