如果你想在網站上顯示 NVIDIA 免費模型清單,第一件事不是做頁面,而是先把資料流設計好
很多人一開始會直覺地想:做一個頁面、打 NVIDIA API、把模型列出來就好。但只要這個功能準備上線,你很快就會發現問題沒有那麼簡單。哪些模型真的可用?哪些屬於免費端點?哪些只是 catalog 上看得到、但 API key 當下不一定可用?如果上游一時變慢、資料不完整,前台是不是就跟著壞掉?
所以這篇文章真正要教的,不是怎麼「列出模型」,而是怎麼把 NVIDIA 免費模型清單做成一個穩定的網站功能:有 API 串接、有本地快取、有手動刷新、有背景同步,而且前台永遠不直接依賴上游。
先搞懂來源:為什麼只打一個 API 還不夠?
如果你的目標是做「NVIDIA 免費模型清單」,你至少會碰到兩種資訊來源:
可見模型: 也就是你的 NVIDIA API key 當下看得到、理論上能呼叫的模型
免費端點標記: 也就是平台層面哪些模型屬於 Free Endpoint
真正的網站產品,不應該把這兩件事混成同一件事。因為「API 可見」不代表「一定在你的免費清單邏輯裡」,而「catalog 上看起來免費」也不代表「你這把 key 當下真的能打」。所以比較穩的方式是:先抓可見模型,再補上免費端點資訊,最後把結果存進自己的 catalog。
產品架構先決定:前台只讀本地,刷新交給背景任務
這種功能最穩定的設計原則只有一句話:Public read path 只查本地資料,不在頁面載入時直接打上游。
實務上你可以把它拆成這樣:
背景同步程式去抓 NVIDIA 上游資料
把原始資料保存成 snapshot
整理成網站用的 normalized catalog
前台 API 只查 catalog
管理端需要時再手動刷新
這樣做的好處是,網站速度、可用性與資料一致性都會穩很多。使用者永遠拿到的是「最近一次成功同步的結果」,而不是跟著上游 API 一起承受波動。
建議資料設計:至少分成兩層
第一層:raw snapshot
這一層的目的不是讓前台直接讀,而是保留現場,方便審計與除錯。建議至少存:
providersourceUrlfetchedAthttpStatuspayloadJsonrefreshRunId
只要你未來遇到「這次同步為什麼少了 5 個模型」這種問題,這層就會救你。
第二層:normalized catalog
這才是前台要讀的清單。建議至少存:
providermodelIddisplayNamedescriptionfreeTierAvailableagentCapablevisibledeprecatedlastSeenAtlastSyncedAtmissingSincerawSnapshotId
其中 freeTierAvailable 是這篇主題最關鍵的欄位。因為你最後要顯示的,正是它。
教學開始:用 TypeScript 做最小可用同步服務
下面的示範用 Node.js + TypeScript,因為這是網站後端最常見的技術棧之一。你不一定要用 Next.js,但這種寫法很容易接進 Next.js、Express 或任何 Node API 專案。
第一步:定義資料型別
type RawSnapshot = {
provider: 'nvidia'
sourceUrl: string
fetchedAt: string
httpStatus: number
payloadJson: unknown
refreshRunId: string
}
type CatalogRow = {
provider: 'nvidia'
modelId: string
displayName: string
description?: string
freeTierAvailable: boolean
visible: boolean
deprecated: boolean
lastSeenAt: string
lastSyncedAt: string
missingSince: string | null
rawSnapshotId?: string
}第二步:先實作上游 API 抓取
如果你的網站要先從 NVIDIA API 取得模型列表,程式應該至少長這樣:
const NVIDIA_API_BASE = 'https://integrate.api.nvidia.com/v1'
async function fetchVisibleModels(apiKey: string) {
const res = await fetch(NVIDIA_API_BASE + '/models', {
headers: {
Authorization: 'Bearer ' + apiKey
}
})
if (!res.ok) {
throw new Error('Failed to fetch NVIDIA models: ' + res.status)
}
return res.json()
}這一步只解決「目前這把 key 能看到哪些模型」。它還沒告訴你哪些是免費端點,所以接下來你要把 free endpoint 資訊補進來。
第三步:把免費端點資訊交給 provider adapter 處理
這篇不展開爬站與 enrichment 細節,因為重點是「刷新機制設計」,不是「特定頁面的抓取技巧」。實務上你應該把 NVIDIA 的免費端點判斷封裝在 adapter 裡,不要散在業務邏輯裡。
type NormalizedModel = {
modelId: string
displayName: string
description?: string
freeTierAvailable: boolean
}
interface CatalogProviderAdapter {
providerName(): string
fetchRawCatalog(apiKey: string): Promise<unknown>
normalizeModels(raw: unknown): Promise<NormalizedModel[]>
}這樣做的好處是,你現在先做 NVIDIA,未來要加別的 provider 時,不必重寫整套同步邏輯。
第四步:保存 raw snapshot,不要只留處理後結果
這一步很多人會偷懶,但我非常建議做。因為只要同步錯一次,你就會慶幸自己有留原始資料。
async function saveRawSnapshot(snapshot: RawSnapshot) {
await db.rawCatalogSnapshot.create({
data: snapshot
})
}實務上你可以存到資料庫,也可以先存到 JSON 檔,只要能查回來就行。
第五步:normalize 之後做 upsert,不要暴力覆蓋
真正的同步核心在這裡。你不應該每次都 delete all 再 insert all,而應該比對現有資料後做 upsert。
async function upsertCatalogRows(rows: CatalogRow[]) {
for (const row of rows) {
await db.llmCatalog.upsert({
where: {
provider_modelId: {
provider: row.provider,
modelId: row.modelId
}
},
create: row,
update: {
displayName: row.displayName,
description: row.description,
freeTierAvailable: row.freeTierAvailable,
visible: row.visible,
deprecated: row.deprecated,
lastSeenAt: row.lastSeenAt,
lastSyncedAt: row.lastSyncedAt,
missingSince: null,
rawSnapshotId: row.rawSnapshotId
}
})
}
}這段 code pan 的重點只有一個:同步是增量更新,不是重灌。
第六步:這次沒看到的模型,先標 missing,不要立刻刪
這是安全同步裡最關鍵的一條原則。因為模型消失不一定代表它真的下架,也可能只是這次同步異常。
async function markMissingModels(provider: string, seenModelIds: string[]) {
const existing = await db.llmCatalog.findMany({
where: { provider }
})
const now = new Date().toISOString()
for (const row of existing) {
if (!seenModelIds.includes(row.modelId)) {
await db.llmCatalog.update({
where: { id: row.id },
data: {
missingSince: row.missingSince ?? now
}
})
}
}
}這樣就算某次上游資料不完整,你的網站也不會立刻少掉一大批模型。
第七步:組成完整 refresh service
現在把前面幾步串起來,就會得到一個真正能跑的同步服務:
export async function refreshNvidiaFreeCatalog() {
const provider = 'nvidia'
const apiKey = process.env.NVIDIA_API_KEY
if (!apiKey) {
throw new Error('Missing NVIDIA_API_KEY')
}
const refreshRunId = crypto.randomUUID()
const fetchedAt = new Date().toISOString()
const raw = await nvidiaAdapter.fetchRawCatalog(apiKey)
await saveRawSnapshot({
provider,
sourceUrl: 'nvidia:/v1/models',
fetchedAt,
httpStatus: 200,
payloadJson: raw,
refreshRunId
})
const normalized = await nvidiaAdapter.normalizeModels(raw)
const rows: CatalogRow[] = normalized.map((model) => ({
provider: 'nvidia',
modelId: model.modelId,
displayName: model.displayName,
description: model.description,
freeTierAvailable: model.freeTierAvailable,
visible: true,
deprecated: false,
lastSeenAt: fetchedAt,
lastSyncedAt: fetchedAt,
missingSince: null
}))
await upsertCatalogRows(rows)
await markMissingModels(provider, rows.map((r) => r.modelId))
return {
provider,
refreshedAt: fetchedAt,
count: rows.length
}
}第八步:做 public read API
前台只查本地 catalog,這是整篇文章最重要的產品原則之一。
// GET /api/llm-catalog?provider=nvidia&free=true
export async function GET(req: Request) {
const url = new URL(req.url)
const provider = url.searchParams.get('provider') ?? 'nvidia'
const free = url.searchParams.get('free') === 'true'
const rows = await db.llmCatalog.findMany({
where: {
provider,
...(free ? { freeTierAvailable: true } : {}),
visible: true,
deprecated: false
},
orderBy: { displayName: 'asc' }
})
return Response.json({ rows })
}這樣你的網站頁面、搜尋頁、模型篩選器,全都不需要直接連到 NVIDIA 上游。
第九步:做 admin refresh API
如果你想讓內部人員手動刷新資料,可以做一個受保護的 refresh endpoint。
// POST /api/admin/llm-catalog/refresh
export async function POST(req: Request) {
const auth = req.headers.get('x-admin-key')
if (auth !== process.env.ADMIN_SYNC_KEY) {
return new Response('forbidden', { status: 403 })
}
const result = await refreshNvidiaFreeCatalog()
return Response.json({ ok: true, result })
}這通常很適合搭配內部 admin console 的「Refresh now」按鈕。
第十步:背景排程刷新
正式上線後,這件事不應該只靠人手按。你可以用 cron、queue worker、GitHub Actions、Vercel Cron 或其他 scheduler 每 6 到 24 小時跑一次。
最簡單的做法,是把 refreshNvidiaFreeCatalog() 做成一個可被 cron job 呼叫的腳本。像這樣:
async function main() {
try {
const result = await refreshNvidiaFreeCatalog()
console.log('refresh ok', result)
} catch (err) {
console.error('refresh failed', err)
process.exit(1)
}
}
main()這樣你就能在排程器裡安全執行它。
如果今天只想做最小可用版本,應該先做哪四件事?
先把
/v1/models串起來先存本地 catalog,不要做前台即時查詢
先做 manual refresh API
先做 upsert + missing tracking,不要用 destructive overwrite
只要這四件先成立,你的 NVIDIA 免費模型清單就已經比多數「抓到就直接顯示」的做法穩很多。
總結
要做好一個 NVIDIA 免費模型清單功能,重點不是頁面,而是同步架構。真正穩定的做法,是把這件事當成 catalog sync 系統來設計:上游抓取、raw snapshot、normalized catalog、upsert、missing tracking、public read API、admin refresh API、背景排程,缺一不可。
一句話收尾:你不是在寫一個模型列表頁,你是在寫一個可維護的 NVIDIA free model sync service。



