跳至內容

從零開始學 AI 工程(四):
Phase 0-4 APIs & Keys

用 Python 第一次呼叫 AI 的 API,搞懂 SDK 背後其實就是一段 Raw HTTP 請求,學會用 .env 把 API key 跟程式碼分開存

本文作者是 AI 工程的初學者,正透過《AI Engineering from Scratch》課程自學,並全程搭配 AI 輔助:一步步照著課程開發者的進度動手執行,卡關時就向 AI 提問、請它引導。這篇文章,是把作者與 AI 互動的完整過程與學習歷程,先交由 AI 記錄、統整成初稿,再由作者親自逐段審視、修改、優化而成。AI 負責整理,最終判斷與文字由作者把關。

寫下它有兩個用意:替後來的讀者鋪一條能照著走的路徑,也幫作者自己複習、把學過的東西沉澱下來。

這篇怎麼讀

格式跟前幾課一樣,每一步分三塊:

  • 作者要你做什麼 — 原始指令
  • 背後的道理 — 我問 AI 之後理解的白話版
  • 走到這裡才會知道的事 — 按著課程走、但作者沒明說的補充

這一課的課程原文用 Anthropic(Claude)的 API 當範例。我手上有 OpenAI 的 API key,所以實作時改用 OpenAI 來跑。概念完全一樣,只是網址和套件名稱不同。

我的規則還是同一條:每一個指令,都要問到懂為止,才往下走。

這一課導覽框

  • 課名:Phase 0 · Lesson 4 — APIs & Keys
  • 目標:學會安全存放 API key、用 Python 發出第一次 API 呼叫、理解 SDK 背後的 Raw HTTP
  • 時間:課程標約 30 分鐘,我含理解和除錯大概花了一個多小時
  • 偏離課程的地方:課程用 Anthropic SDK,我用 OpenAI SDK。四個零件的邏輯完全相同
  • 完課驗證:用 SDK 呼叫 AI 拿到回答、用 Raw HTTP 呼叫同一個 API 拿到回答

先看懂作者的地圖:每次 API 呼叫都長一樣

課程開頭有個API概念可以記著:

Every AI API works the same way: send a request, get a response. The details change, the pattern doesn’t.

每個 AI API 的運作方式都一樣:送出請求,拿到回應。細節會變,模式不會。

課程用一張流程圖描述這個模式:

API 呼叫流程圖:Your Code 發出帶著 API Key 的 HTTP Request 給 API Server,API Server 回傳 JSON 格式的 HTTP Response

你的程式帶著 API key 發一個 HTTP 請求到 API 伺服器,伺服器處理完後回傳一個 JSON 格式的回應。每次都是這個來回,不會有第三種走法。

接著課程把每次 API 呼叫拆成四個零件:

零件做什麼實際長什麼樣
Endpoint(URL)你要把請求發到哪裡https://api.openai.com/v1/chat/completions
API Key證明你是誰、有權限呼叫sk-... 一長串字串
Request Body你的問題和設定JSON 格式:要哪個 model、問什麼、最多回幾個 token
Response BodyAI 的回答JSON 格式:AI 回的文字

這四個零件從頭到尾不會變。不管你呼叫 Claude、GPT、還是 Gemini,都是帶著 key、發一個 HTTP 請求到某個網址、收一個 JSON 回來。課程後面的 Phase 11 到 16 全部都在做這件事的各種變形。


步驟 1:安全存放 API Key

作者要你做什麼

課程給了兩條路:

路線 A — 直接在終端機設環境變數:

export ANTHROPIC_API_KEY="sk-ant-..."
export OPENAI_API_KEY="sk-..."

路線 B — 用 .env 檔存起來,然後加到 .gitignore

ANTHROPIC_API_KEY=sk-ant-...
OPENAI_API_KEY=sk-...

課程只說了這些,選一條做就好。

背後的道理

API key 就是密碼。程式碼可能會推到 GitHub 讓別人看到,所以 key 不能寫在程式碼裡面,要另外存。

兩條路的差別:

路線 A(export) 是直接把 key 放進終端機的記憶體裡。好處是一行搞定,程式馬上讀得到。壞處是關掉終端機就消失了,下次要重打。

路線 B(.env 檔) 是把 key 存成一個文字檔。關掉終端機也不會消失,因為是檔案。然後用 .gitignore 告訴 Git「假裝這個檔案不存在」,這樣推程式碼的時候 key 不會跟著上去。

我走了路線 B。

走到這裡才會知道的事

.env 檔本身不會自動生效。 課程只說「用 .env 存 key」,但沒有教你怎麼把 .env 的內容載入到環境變數。.env 就是一個純文字檔,Python 程式不會自己去讀它。你要自己把裡面的內容載入到終端機的記憶體裡,程式才讀得到。

我的做法是在終端機打 export $(cat .env),這行指令會讀出 .env 的內容,然後設成環境變數。效果等同於你自己手打 export OPENAI_API_KEY="sk-..."

環境變數是什麼? 就是終端機在記憶體裡存的一組「名稱=值」的資料。你用 export 把 key 放進去之後,在這個終端機裡跑的任何程式都可以讀到它。Python 用 os.environ["OPENAI_API_KEY"] 讀,Node.js 用 process.env.OPENAI_API_KEY 讀。關掉終端機,記憶體清空,下次開要重新載入。

.env 不只存 API key。 任何不想寫死在程式碼裡的設定值都可以放:資料庫帳號密碼、伺服器網址、開發模式的開關。共同點是這些值會因為環境不同而改變(你的電腦用一組,公司伺服器用另一組),而且有些是機密。

AI 帶我多做了幾步課程沒要求的事。 比如用 grep '.env' .gitignore 去驗證 .gitignore 有沒有寫好、用 echo '...' > .env 建檔。這些不是課程的步驟,是 AI 補充的操作細節。課程假設你知道怎麼建檔案、怎麼確認設定,如果你跟我一樣是初學者,可能需要這些額外的步驟。但要知道哪些是課程要求的、哪些是額外補的,才不會搞混。


步驟 2:第一次 API 呼叫(Python)

作者要你做什麼

課程原文用 Anthropic SDK,我改用 OpenAI SDK。結構一樣:

import openai

client = openai.OpenAI()

response = client.chat.completions.create(
    model="gpt-4o",
    max_tokens=256,
    messages=[{"role": "user", "content": "What is a neural network in one sentence?"}]
)

print(response.choices[0].message.content)

每行在做什麼

import openai — 載入 openai 這個套件。之前用 uv pip install openai 裝進 .venv 的就是它。裝是裝在資料夾裡,import 是載入到目前這次 Python 的執行環境。兩步,缺一不可。

client = openai.OpenAI() — 建立一個客戶端物件。這行做了一件重要的事:自動去環境變數裡找 OPENAI_API_KEY。所以你不需要在程式碼裡寫出 key,它自己去記憶體找。為什麼要先建一個 client 而不是直接呼叫?因為你可能同時用兩組不同的 API key,或連不同的伺服器。建成物件,每個物件有自己的設定,互不干擾。

client.chat.completions.create(...) — 發出請求。三個參數:

  • model="gpt-4o" — 指定要哪個 AI 模型回答。不同 model 能力不同、收費不同
  • max_tokens=256 — 回答最多用 256 個 token。token 是計費單位,也控制回答長度
  • messages=[{"role": "user", "content": "..."}] — 你要問的問題。是一個 list([]),裡面放 dict({})。"role": "user" 代表這是你說的話,"content" 是內容

print(response.choices[0].message.content) — 從回應裡取出 AI 回的文字。choices 是一個 list,因為 API 設計上可以一次回多個答案。[0] 取第一個,.message.content 是那個答案的文字。

我的實測

跑完之後 GPT-4o 回了一段英文解釋 neural network 的句子。看到回應印出來的那一刻,才意識到這跟平常在 ChatGPT 網頁上打字聊天走的是同一條路,只是這次是我的程式碼在發請求。

走到這裡才會知道的事

不是每個 model 你都有權限用。 我一開始照 AI 的建議用 gpt-4o-mini,結果報了 403 錯誤,說我的 project 沒有權限存取這個 model。換成 gpt-4o 就通了。如果你遇到 model_not_found 的錯誤,不一定是程式寫錯,可能是你的 OpenAI 帳號或 project 沒有開通那個 model 的權限。


步驟 3:TypeScript SDK 呼叫

課程提供了 Python 和 TypeScript 兩個版本,做的事完全一樣,只是語法不同。我目前走 Python 路線,所以跳過 TypeScript 版。不是用不到,是對我來說重複了。


步驟 4:Raw HTTP 呼叫(不用 SDK)

作者要你做什麼

課程原文用 Anthropic 的 API,我改用 OpenAI:

import os, urllib.request, json

url = "https://api.openai.com/v1/chat/completions"
headers = {
    "Content-Type": "application/json",
    "Authorization": "Bearer " + os.environ["OPENAI_API_KEY"],
}
body = json.dumps({
    "model": "gpt-4o",
    "max_tokens": 256,
    "messages": [{"role": "user", "content": "What is a neural network in one sentence?"}],
}).encode()

req = urllib.request.Request(url, data=body, headers=headers, method="POST")
with urllib.request.urlopen(req) as resp:
    result = json.loads(resp.read())
    print(result["choices"][0]["message"]["content"])

每行在做什麼

url = "https://api.openai.com/v1/chat/completions" — API 的地址。https://api.openai.com 是 OpenAI 伺服器的位置,/v1/chat/completions 是伺服器上「處理聊天」的那個入口。

headers = {...} — 附在請求上的標頭資訊,告訴伺服器兩件事:

  • "Content-Type": "application/json" — 我送過去的資料格式是 JSON
  • "Authorization": "Bearer " + os.environ["OPENAI_API_KEY"] — 我的身份證明。Bearer 是 HTTP 認證的一種標準格式,後面接你的 API key。os.environ["OPENAI_API_KEY"] 從環境變數讀出 key

body = json.dumps({...}).encode() — 這裡有三層操作:

  1. 最裡面的 {...} 是一個 Python dict,內容跟步驟 2 一樣:model、max_tokens、messages
  2. json.dumps(...) 把 Python dict 轉成 JSON 字串。HTTP 傳輸不認得 Python 的 dict,只認純文字。dumps 是 dump string 的意思
  3. .encode() 把字串再轉成 bytes。網路傳輸的最底層是 bytes

req = urllib.request.Request(url, data=body, headers=headers, method="POST") — 把 URL、headers、body 組裝成一個完整的 HTTP 請求。method="POST" 是 HTTP 的動詞,表示「我要送資料給你處理」。相對的 GET 是「我要拿資料」。呼叫 AI 是送問題過去,所以用 POST。

with urllib.request.urlopen(req) as resp: — 把請求發出去,拿到回應。with ... as resp: 是 Python 的寫法,意思是「開這個連線,用完自動關掉」。

result = json.loads(resp.read())resp.read() 讀回應的原始 bytes,json.loads(...) 把它解析回 Python dict。loads 是 load string,跟前面的 dumps 相反。

print(result["choices"][0]["message"]["content"]) — 從 dict 裡取出 AI 回的文字,跟步驟 2 一樣。

步驟 2 跟步驟 4 的差別

做的事完全一樣。差別在於步驟 2 用 SDK(openai 套件),四行搞定;步驟 4 自己處理所有細節,寫了十幾行。

*SDK 做的事就是把 Raw HTTP 的那些步驟(組 URL、帶 header、打包 JSON、發請求、解析回應)包成幾個函式。除此之外,SDK 還會幫你處理錯誤重試(網路斷了自動重試)、型別檢查(參數寫錯提早告訴你)、版本管理(API 改版時處理相容問題)。

課程說了一句話:Understanding the raw HTTP call helps when debugging. 懂原始 HTTP 呼叫,之後 debug 的時候才知道問題出在哪一層。


什麼時候需要哪個 API

課程列了一張表,重點是不用現在全部申請,到了那個 Phase 再弄就好:

API什麼時候用免費額度
Anthropic(Claude)Phase 11-16(Agent、工具呼叫)註冊送 $5
OpenAIPhase 11(比較不同 LLM)註冊送 $5
Hugging FacePhase 4-10(模型、資料集)免費

四個關鍵詞

術語一般怎麼講白話意思
API key「API 的密碼」一段獨特字串,用來認出你的帳號、授權你的請求
Rate limit「被限流了」每分鐘或每小時最多能發幾個請求,防止濫用
Token「一個字」(API 語境下)計費單位。輸入 token 和輸出 token 分開計算、分開收費
Streaming「即時回應」一個字一個字收回應,不用等全部生完。ChatGPT 打字的效果就是這個

這一課做完之後,我覺得收穫最大是理解了一件事:所有 AI API 都是同一個模式。送一個帶著身份證明的 HTTP 請求到某個網址,收一個 JSON 回來,而SDK 就是把這件事打包起來方便API呼叫。

另一個體會是關於環境變數和 .env 檔。課程寫得很簡潔,對有經驗的人來說夠了,但對我這種從零開始的人,「建好 .env 檔之後要怎麼讓程式讀到裡面的 key」這細節課程並沒有明確說明,但可以利用AI協助了解一下。