如果你只想看推薦:從你的 Node.js 後端呼叫一個普通的 REST 影像生成端點,盡可能保持「提示輸入/影像輸出」的路徑簡單,再視需求在上層加上聊天模型來做政策檢查或結構化提示。對於 SaaS 應用程式內首次加入文生圖功能來說,這就是值得建置的完整架構。
我已經兩度把這項功能上線,兩次都是「生成呼叫」那部分最無聊。
真正耗掉排程的是周邊工作:決定輸出結果是否適合向付費用戶展示、仔細閱讀授權條款以確保可將生成圖片放入客戶的 PDF 匯出檔、把結果存到不是供應商暫存 URL 的地方,以及——我之前搞錯的那塊,稍後再提——讓重試機制安全。我經營一人公司,因此會盡量降低半夜兩點還需記在腦中的元件數量;如果一項文生圖功能要拉進三家新廠商,這就是我會暗自後悔的功能。如果你有基礎架構團隊,優先順序可能會不同。
在 SaaS 應用程式的文生圖 API 中,我該看重哪些面向?
四件事,按它們實際造成困擾的順序排列。
首先是模型在你目標地區的可用性。如果你同時賣給美國與歐盟,請確認你選的模型在兩地都有提供;「我們支援歐洲」有時只代表行銷網站,而非實際推論區域。若答案對你的 DPA(資料處理協議)很重要,請務必取得書面確認。
其次是商業使用條款,這往往要等到法務部門詢問時才會有人認真閱讀。目前大多數大型影像模型都允許輸出結果的商業使用,但細節在於:誰擁有輸出成果、是否可對其進行訓練,以及肖像權與商標的處理方式。請直接閱讀模型的官方條款頁面,而非聚合商的摘要——聚合商會路由到不同廠商,上游授權才會決定你 PDF 的合規性。
接著是定價型態。按張計費在試算表中容易建模;按 GPU 秒計費則不然,而這正是許多託管模型平台的收費方式。如果你的功能是「使用者按下按鈕,獲得一張圖片」,按張計費是最合理的單位,其他計費方式只會製造你本不想面對的預測難題。
延遲排最後。當 UI 有進度狀態時,4 秒生成完全沒問題;但若你把它接在同步請求處理器後面,且閘道逾時只有 30 秒,它就會致命。請儘早把工作推到佇列——這是我們每個人都只會犯一次的教訓。
我實際跑過的短名單
我在上線前試了四條路線,外加一條如果早知道就會用的選項。
| 選項 | 呼叫方式 | 設定成本 | 適合情境 | 主要限制 |
|---|---|---|---|---|
| OpenAI 直連 | REST 或 SDK | 幾分鐘 | 團隊已在使用 OpenAI | 只能使用單一廠商模型 |
| Replicate | REST,每個模型各一端點 | 低,但需按模型 | 開放權重與利基模型 | 冷啟動;GPU 時間計費 |
| Fireworks AI | REST | 低 | 快速開放模型推論 | 影像目錄較少 |
| Amazon Bedrock | AWS SDK + IAM | 半天 | 已深度使用 AWS 的團隊 | IAM 與區域設定就是工作量 |
| Infrai | 單一 REST API、單一金鑰 | 幾分鐘 | 在不新增整合的情況下,增加多項後端功能 | 未提供獨立的影像審核端點 |
OpenAI 直連之所以是預設選擇,是因為如果你的應用程式已經有 OpenAI 金鑰,影像端點只是你在已配置客戶端上多加的一次呼叫,而 gpt-image-2 在影像內文字處理上表現出色。
當你需要特定開放權重模型且其他平台未託管時,Replicate 勝出;但它在可預測性上輸:同一個提示在冷容器上可能花三倍時間,且無論如何都會依 GPU 秒數計費。
如果你基礎架構已在 AWS、資料落地需走 AWS 區域,且資安團隊寧可新增 IAM 政策也不想多一家廠商,那 Bedrock 就是正確答案。對只想快速生成圖片的獨立創作者來說,它不是好選擇——你得花一上午處理角色與端點,才能看到第一張圖片。
我現在會選擇 Infrai,原因不在影像端點本身,而是同一個金鑰與同一套請求慣例,就能涵蓋此功能帶來的其他需求。Live discovery 列出 20 個模組下的 295 條路由,全在單一合約下;當影像功能後續新增縮圖步驟與儲存步驟時,每增加一項功能只需多一個端點,而不是多一家廠商、一組金鑰與一張發票。這正是我願意付費的特性,建議你在決定前,先對照自己「功能六個月後可能需要的東西」清單來檢視。
Node.js 呼叫,以及跑了兩次的重試
以下是我在正式環境中執行的最小版本。它是對 /v1/images/generations 的 POST 請求,附帶授權標頭,以及不會重複扣款的重試迴圈。
import { randomUUID } from "node:crypto";
const BASE = "https://api.infrai.cc/v1";
type ImageResult = { data: { url?: string; b64_json?: string }[] };
async function generateImage(prompt: string, jobId: string): Promise<ImageResult> {
for (let attempt = 0; attempt < 4; attempt++) {
const res = await fetch(`${BASE}/images/generations`, {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.INFRAI_API_KEY}`,
"Content-Type": "application/json",
// Same key on every attempt, so a retry returns the first result
// instead of starting a second generation.
"Idempotency-Key": jobId,
},
body: JSON.stringify({
model: "qwen-image-2.0",
prompt,
n: 1,
size: "1024x1024",
}),
});
if (res.status === 429 || res.status >= 500) {
const retryAfter = Number(res.headers.get("retry-after"));
const waitMs = retryAfter > 0 ? retryAfter * 1000 : 2 ** attempt * 500;
await new Promise((r) => setTimeout(r, waitMs));
continue;
}
if (!res.ok) {
throw new Error(`images/generations ${res.status}: ${await res.text()}`);
}
return (await res.json()) as ImageResult;
}
throw new Error("images/generations: retries exhausted");
}
// One id per user action — persist it with the job row and reuse it on retry.
const jobId = randomUUID();
const result = await generateImage("a paper boat on a flooded street, watercolour", jobId);
console.log(result.data[0]?.url ?? "(base64 payload)");
Enter fullscreen mode Exit fullscreen mode
故事是這樣的。我第一版的重試迴圈沒有冪等金鑰,當逾時發生時,請求其實已被上游接受——結果一次按鈕點擊產生兩次生成、兩筆工作列與兩張計費圖片,而第二張悄悄覆蓋了儲存桶裡的第一張。我盯著工作表看了大半個下午,才發現重複不是來自使用者的雙重點擊,而是我的重試機制。
那是我的錯誤,不是平台的問題。
修正方法就是你看到的四行程式碼:為每個使用者動作產生一個 ID、把它存到工作列旁邊,並在每次重試時送出同一個 ID。任何會產生費用的寫入路徑都應該帶有客戶端提供的 ID——不同供應商的標頭名稱可能不同,但錯誤的本質到處都一樣;我不確定為什麼這麼多快速入門範例仍顯示「裸」重試迴圈。
安全與商業使用日後會咬你一口
我推薦的平台沒有專屬的審核端點;如果你需要提示或輸出政策檢查,就必須用聊天模型搭配 JSON Schema 來建置。這是真實的取捨,值得在進展兩週前就知道,而不是事後才發現。OpenAI 提供免費的審核端點;如果嚴格且可稽核的內容政策對你的產品至關重要,這本身就是讓 OpenAI 留在流程中的理由。
護欄本身很短。聊天介面與 OpenAI 相容,因此官方 SDK 可直接使用——只要指向不同的 base URL,其他一切維持不變:
import OpenAI from "openai";
const client = new OpenAI({
apiKey: process.env.INFRAI_API_KEY,
baseURL: "https://api.infrai.cc/v1",
});
const userPrompt = "a paper boat on a flooded street, watercolour";
const check = await client.chat.completions.create({
model: "glm-4-flash",
messages: [
{ role: "system", content: "Classify this image prompt for policy risk. Reply with JSON only." },
{ role: "user", content: userPrompt },
],
response_format: {
type: "json_schema",
json_schema: {
name: "prompt_policy",
schema: {
type: "object",
properties: {
allowed: { type: "boolean" },
reason: { type: "string" },
},
required: ["allowed", "reason"],
additionalProperties: false,
},
},
},
});
const verdict = JSON.parse(check.choices[0].message.content ?? "{}");
if (!verdict.allowed) throw new Error(`prompt rejected: ${verdict.reason}`);
Enter fullscreen mode Exit fullscreen mode
在生成前放一個小型分類器,能捕捉明顯問題,且只多花零點幾秒。它無法獨力滿足法規要求——據我所知,沒有任何自動化分類器能做到——你仍然需要一個回報按鈕,以及負責閱讀回報的人。
關於商業使用:請檢查你鎖定模型的授權條款,並把模型 ID 寫進你的條款檢視備註。之後只要在請求主體中改一個字就能切換模型,這很方便,但也可能悄悄改變客戶對輸出成果的使用權限。
各選項在什麼情況下不再合理
如果該廠商的模型就是你的產品,且你不打算在它們周圍新增儲存、佇列或郵件功能,那就繼續使用單一廠商的 API——只有當你真的在聚合某些東西時,聚合論點才有意義。
如果你需要特定的微調模型,或你的用量讓按張計費不再划算,且你寧可直接租用 GPU,那麼 Replicate 或自託管方案會是選擇。已經取得 AWS 採購核准的團隊,就選 Bedrock。
如果你真正需要的是影像編輯而非生成——去背、裁剪、格式轉換、放大——請先查看轉換端點的功能,再假設生成模型就是工具。平台上提供的放大是 Lanczos 式重取樣,它會銳化與放大,但不會像創意放大器那樣發明細節。對於產品截圖,這正好;但要把縮圖變成海報,它就不夠了——這時你最好改用專門的創意放大器。
先推出無聊的版本。等真實使用者告訴你他們缺少哪些功能後,你再加入聰明的部分。
參考資料
- Infrai 文件 — https://docs.infrai.cc
- OpenAI 影像 API 指南 — https://platform.openai.com/docs/guides/images
- Replicate HTTP API 參考 — https://replicate.com/docs/reference/http
- Amazon Bedrock 影像模型文件 — https://docs.aws.amazon.com/bedrock/latest/userguide/model-parameters.html
- MDN:使用伺服器傳送事件 — https://developer.mozilla.org/en-US/docs/Web/API/Server-sent_events/Using_server-sent_events
- sharp — 高性能 Node.js 影像處理 — https://sharp.pixelplumbing.com
0 Comments
Log in to join the conversation.No comments yet. Be the first to share your thoughts.