iT邦幫忙

2026 iThome 鐵人賽

DAY 11
0
Claude AI

從現場踩坑到 AI 工具 — IT Diagnostic Agent 開發實錄系列 第 11

Day 11 — Claude API 其實可以直接從瀏覽器呼叫

  • 分享至 

  • xImage
  •  

系列:從現場踩坑到 AI 工具 — IT Diagnostic Agent 開發實錄


一個大多數人沒質疑過的預設

如果你要做一個接 LLM 的網頁應用,幾乎所有教學文都會告訴你同一件事:

你需要一個後端。

前端把使用者的問題送到你的伺服器,伺服器帶著 API Key 去呼叫 Anthropic,拿到回應再轉回前端。

理由很明確:API Key 不能放在前端。 只要放進瀏覽器,任何人打開開發者工具就看得到,你的帳單就會被別人拿去用。

這個道理完全正確。但它有一個隱含前提,而那個前提在我的專案裡不成立。


前提是:Key 是誰的?

上面那套邏輯的前提是——Key 是你(服務提供者)的

在一個 SaaS 服務裡,使用者不需要自備 Key,他們用的是你的額度、你的帳單。所以你必須把 Key 藏在後端,否則就是把信用卡放在網路上。

但 IT Diagnostic Agent 不是 SaaS。它的模型是:

使用者自己去申請 API Key,填進自己瀏覽器裡的工具。

這個差別改變了整個安全計算:

SaaS 模式 IT Diagnostic Agent
Key 擁有者 服務提供者 使用者自己
Key 洩漏的受害者 服務提供者 使用者自己
誰有動機保護 Key 服務提供者 使用者自己
需要後端嗎 需要 不需要

當 Key 的擁有者、保管者、受害者是同一個人時,「把 Key 放在他自己的瀏覽器裡」不是安全漏洞,那就是他自己的鑰匙放在自己的抽屜裡。


技術上怎麼做

Anthropic API 預設會擋掉瀏覽器直接發出的請求(CORS 政策),但它提供了一個明確的開關:

async function callClaude(messages, systemPrompt){
  const p = providers.claude;
  if(!p.key) throw new Error('Claude API Key not set');
  const res = await fetch('https://api.anthropic.com/v1/messages', {
    method:'POST',
    headers:{
      'Content-Type':'application/json',
      'x-api-key':p.key,
      'anthropic-version':'2023-06-01',
      'anthropic-dangerous-direct-browser-access':'true'
    },
    body:JSON.stringify({model:p.model, max_tokens:1024, system:systemPrompt, messages})
  });
  const data = await res.json();
  if(data.error) throw new Error(data.error.message);
  return data.content[0].text;
}

注意那個 header 的名字:

anthropic-dangerous-direct-browser-access

Anthropic 直接把 "dangerous" 寫進 header 名稱裡。

這個命名我覺得非常誠實。它不是偷偷提供一個方便的後門,而是每次你寫下這行程式碼,都在提醒你自己:你知道你在做什麼嗎?

我知道。而且我認為在這個專案的情境下,這是對的選擇。


Key 的存放

使用者填進來的 Key 存在 localStorage。實際的實作是一個 provider 設定物件,每個供應商各自帶 key、model、endpoint:

const providers = {
  claude: {
    key: localStorage.getItem('it_key_claude') || '',
    model: localStorage.getItem('it_model_claude') || 'claude-sonnet-4-20250514',
    endpoint: 'https://api.anthropic.com'
  },
  gemini: {
    key: localStorage.getItem('it_key_gemini') || '',
    model: localStorage.getItem('it_model_gemini') || 'gemini-2.5-flash',
    endpoint: ''
  },
  openai: {
    key: localStorage.getItem('it_key_openai') || '',
    model: localStorage.getItem('it_model_openai') || 'gpt-4o-mini',
    endpoint: localStorage.getItem('it_ep_openai') || 'https://api.openai.com/v1'
  },
  ollama: {
    key: '',                              // ← 注意:不需要 Key
    model: localStorage.getItem('it_model_ollama') || 'gemma3:12b',
    endpoint: localStorage.getItem('it_ep_ollama') || 'http://localhost:11434'
  },
};

有兩個細節值得說。

第一,Ollama 那筆的 key 是空字串。 它不需要 Key,因為模型跑在本機。這一行就是整個工具唯一一條「零成本、零註冊」的路徑,我在後面會回來講它。

第二,這段程式碼還留著一段向下相容的遷移邏輯:

// Backward compat: migrate old single apiKey
if(!providers.claude.key && localStorage.getItem('it_key')) {
  providers.claude.key = localStorage.getItem('it_key');
  ...
}

早期版本只支援 Claude,Key 存在一個叫 it_key 的欄位裡。後來改成多供應商架構之後,這些早期使用者的 Key 不能就這樣消失,所以留了一段搬遷程式碼。

這五行是這個工具已經有真實使用者的證據。 如果沒人用,我大可以直接改掉 key 名稱,誰在乎。

這樣的設計代表:

  • Key 只存在使用者自己的瀏覽器
  • 沒有任何伺服器收到它(因為根本沒有伺服器)
  • 清除瀏覽器資料就等於刪除 Key
  • 換一台電腦要重新填

最後一點是缺點,但也是特性——沒有帳號系統,就沒有跨裝置同步,也就沒有任何我需要保管的東西。


這個決定的真實代價

純前端呼叫 API 不是沒有代價的。有兩個是真的痛。

代價 1:不可能有「打開就能試用」的 Demo

任何人第一次打開這個工具,AI 功能都是不能用的。他必須:

  1. 知道自己需要一個 API Key
  2. 去供應商註冊帳號
  3. 綁定付款方式
  4. 產生 Key
  5. 複製貼回工具裡

這是五個步驟的門檻。相較於「打開就能用」的 SaaS,轉換率一定差很多。

我沒有解法,只能接受。這是「零後端」這個選擇的直接後果——我不能替使用者承擔他們的 API 成本,因為我沒有任何機制去承擔。

也正是這個代價,讓後來的 Ollama 本地模型支援變得特別重要。那是唯一一條「不需要 Key、不需要付費」的路徑。這件事我會在 Day 19–20 講。

代價 2:Key 在瀏覽器裡,就會被瀏覽器的問題影響

瀏覽器擴充套件理論上讀得到 localStorage。共用電腦上,前一個人的 Key 可能留在那裡。

我在 README 裡加了警告,但這不是能靠程式碼解決的事,只能靠告知。


結構化地看這個決定

可控的

  • 架構選擇(要不要後端)
  • Key 的存放方式與告知義務
  • 是否提供本地模型作為替代路徑

不可控的

  • 使用者會不會願意花五個步驟去申請 Key
  • 使用者的瀏覽器環境有多乾淨
  • API 供應商的定價與政策變動

我能做的是把可控的部分做對,並且為不可控的部分留退路。這也是為什麼 Adapter Pattern 從第一版就存在——那是 Day 16 的主題。


今天的反思

「API Key 不能放在前端」是一條正確的規則。

每一條正確的規則都有它的適用前提,而工程師最容易犯的錯誤,是把規則背下來卻沒有背下前提。

當你的專案不符合那個前提時,照搬規則反而會讓你做出過度複雜的架構——為了保護一把根本不屬於你的鑰匙,架一台你不需要的伺服器。


明天預告: API 接好了,工具可以呼叫 Claude 了。但接下來我花最多時間的地方不是程式碼,是那段 systemPrompt。而關於那段 Prompt 該怎麼寫,我必須先誠實交代一件事:核心的五條規則,不是我想出來的。


作者:Rich Chang | IT 基礎建設工程師 | 越南・柬埔寨・台灣


上一篇
Day 10 — 手機版看起來正常,直到朋友傳來截圖
下一篇
Day 12 — Prompt 不重要?我花最多時間其實在這裡
系列文
從現場踩坑到 AI 工具 — IT Diagnostic Agent 開發實錄13
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言