本文作者是 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 key 發一個 HTTP 請求到 API 伺服器,伺服器處理完後回傳一個 JSON 格式的回應。每次都是這個來回,不會有第三種走法。
接著課程把每次 API 呼叫拆成四個零件:
| 零件 | 做什麼 | 實際長什麼樣 |
|---|---|---|
| Endpoint(URL) | 你要把請求發到哪裡 | https://api.openai.com/v1/chat/completions |
| API Key | 證明你是誰、有權限呼叫 | sk-... 一長串字串 |
| Request Body | 你的問題和設定 | JSON 格式:要哪個 model、問什麼、最多回幾個 token |
| Response Body | AI 的回答 | 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() — 這裡有三層操作:
- 最裡面的
{...}是一個 Python dict,內容跟步驟 2 一樣:model、max_tokens、messages json.dumps(...)把 Python dict 轉成 JSON 字串。HTTP 傳輸不認得 Python 的 dict,只認純文字。dumps是 dump string 的意思.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 |
| OpenAI | Phase 11(比較不同 LLM) | 註冊送 $5 |
| Hugging Face | Phase 4-10(模型、資料集) | 免費 |
四個關鍵詞
| 術語 | 一般怎麼講 | 白話意思 |
|---|---|---|
| API key | 「API 的密碼」 | 一段獨特字串,用來認出你的帳號、授權你的請求 |
| Rate limit | 「被限流了」 | 每分鐘或每小時最多能發幾個請求,防止濫用 |
| Token | 「一個字」(API 語境下) | 計費單位。輸入 token 和輸出 token 分開計算、分開收費 |
| Streaming | 「即時回應」 | 一個字一個字收回應,不用等全部生完。ChatGPT 打字的效果就是這個 |
這一課做完之後,我覺得收穫最大是理解了一件事:所有 AI API 都是同一個模式。送一個帶著身份證明的 HTTP 請求到某個網址,收一個 JSON 回來,而SDK 就是把這件事打包起來方便API呼叫。
另一個體會是關於環境變數和 .env 檔。課程寫得很簡潔,對有經驗的人來說夠了,但對我這種從零開始的人,「建好 .env 檔之後要怎麼讓程式讀到裡面的 key」這細節課程並沒有明確說明,但可以利用AI協助了解一下。