在前面的章節中,我們陸續解鎖了 Gemini 1.5 令人驚嘆的殺手級功能:吞下數十頁 PDF(Day 08)、直接辨識長篇影音與時間軸(Day 09)、嚴格約束的 JSON 結構化輸出(Day 10),以及具備自主調用外部 API 能力的 Agent(Day 11)。
此時,一個冷酷而現實的商業問題浮上檯面:「Token 帳單誰來買單?」
在 OmniVibe AI 的真實使用場景中,用戶上傳了一份 10 萬 Token 的年度財報或 45 分鐘的訪談影片後,絕不可能只問一個問題就離開。典型的用戶行為是:
如果每次追問,系統都把這 10 萬 Token 的原始影音與 PDF 重新打包、重新上傳給模型計算一次:
在商業模式上,這種架構會讓你的 SaaS 陷入「用戶用得越多,你虧得越慘」的窘境。
今天,我們將深入 Google Gemini 最具商業價值的省錢神器 —— Context Caching(上下文快取),用代碼實戰將重複分析的 API 成本直接砍掉 75%!
在傳統的大模型推論中,當你送出 Prompt 時,伺服器必須對所有文字與多模態 Token 重新計算 KV Cache(Key-Value Cache)。
graph TD
subgraph 傳統無快取模式 (每次重複計費與計算)
Q1[問題 1 + 10萬 Token 檔案] --> Engine1[計算 10萬 Token KV Cache] --> A1[產出摘要]
Q2[問題 2 + 10萬 Token 檔案] --> Engine2[重新計算 10萬 Token KV Cache] --> A2[產出貼文]
Q3[問題 3 + 10萬 Token 檔案] --> Engine3[重新計算 10萬 Token KV Cache] --> A3[產出腳本]
end
subgraph Google Context Caching (一次計算,重複讀取)
File[10萬 Token 影音/PDF] --> CacheEngine[建立雲端快取 (計算一次 KV Cache)]
CacheEngine --> CacheStore[(Google 記憶體快取實例 / TTL)]
CacheStore -->|極速快取讀取 (省 75% 成本)| Query1[問題 1] --> Out1[秒級產出]
CacheStore -->|極速快取讀取 (省 75% 成本)| Query2[問題 2] --> Out2[秒級產出]
CacheStore -->|極速快取讀取 (省 75% 成本)| Query3[問題 3] --> Out3[秒級產出]
end
Context Caching 允許開發者將龐大的上下文(長文本、書籍、高解析度 PDF、音訊、長影片或巨量 System Instructions)預先計算並快取在 Google 基礎架構的記憶體中:
以使用 Gemini 1.5 Flash 處理一份包含長影片與 PDF、合計 200,000 Tokens 的專案為例(假設用戶連續進行 5 次深入提問):
| 指標 | 無 Context Caching | 啟用 Context Caching | 效益提升 |
|---|---|---|---|
| 輸入 Token 總計費量 | 200k × 5 = 1,000,000 Tokens | 首次寫入:200k |
後續 4 次讀取:200k × 4 × 25% = 200k
合計:400,000 Tokens 等效費用 | 節省 60% 總成本 |
| 首字回傳延遲 | 每次提問需重新解析,約 6 ~ 9 秒 | 首次建立需數秒,後續查詢 約 1.2 秒 噴字 | 速度提升 5 倍以上 |
| 用戶操作體驗 | 追問等待時間長,容易跳離網頁 | 即問即答,符合即時生產力工具標準 | 留存率大幅提高 |
(註:快取會依照保留時間收取少許的儲存費用,通常以每小時每百萬 Token 幾美分計,對於頻繁互動的 SaaS 專案而言成本幾乎可以忽略)
在興奮地將代碼全部換成快取前,必須注意 Google 官方的兩大規則:
我們將在 src/lib/gemini/cache-manager.ts 中封裝快取管理邏輯,並在 API Route 中使用 GoogleAICacheManager。
src/lib/gemini/cache-manager.ts)// src/lib/gemini/cache-manager.ts
import { GoogleAICacheManager } from '@google/generative-ai/server';
import { GoogleGenerativeAI } from '@google/generative-ai';
import { OMNIVIBE_SYSTEM_INSTRUCTION } from './prompts';
const apiKey = process.env.GEMINI_API_KEY || '';
const cacheManager = new GoogleAICacheManager(apiKey);
const genAI = new GoogleGenerativeAI(apiKey);
export interface CreateProjectCacheParams {
projectId: string;
fileUris: Array<{ uri: string; mimeType: string }>;
ttlMinutes?: number; // 預設保留分鐘數
}
/**
* 為使用者的專案大檔案建立 Context Cache
*/
export async function createProjectContextCache({
projectId,
fileUris,
ttlMinutes = 60,
}: CreateProjectCacheParams) {
try {
const ttlSeconds = ttlMinutes * 60;
console.log(`[Cache Manager] 正在為專案 ${projectId} 建立快取,TTL: ${ttlMinutes} 分鐘...`);
const cache = await cacheManager.create({
model: 'models/gemini-1.5-flash-001',
displayName: `cache_project_${projectId}`,
systemInstruction: OMNIVIBE_SYSTEM_INSTRUCTION,
contents: [
{
role: 'user',
parts: fileUris.map((file) => ({
fileData: {
fileUri: file.uri,
mimeType: file.mimeType,
},
})),
},
],
ttlSeconds: ttlSeconds,
});
console.log(`[Cache Manager] 快取建立成功!Cache Name: ${cache.name}`);
return cache;
} catch (error: any) {
console.error('[Cache Manager Error] 建立快取失敗:', error);
throw error;
}
}
/**
* 取得支援快取的 Gemini 模型實例
*/
export function getModelWithCache(cacheName: string) {
return genAI.getGenerativeModelFromCachedContent({
name: cacheName,
});
}
src/app/api/ai/cached-chat/route.ts)這支 API 接收使用者針對該專案提出的「連續延伸問題」,完全不需要再次傳送原始的大型檔案,直接掛載 cacheName 進行推論:
// src/app/api/ai/cached-chat/route.ts
import { NextRequest, NextResponse } from 'next/server';
import { getModelWithCache } from '@/lib/gemini/cache-manager';
export async function POST(req: NextRequest) {
try {
const { cacheName, prompt } = await req.json();
if (!cacheName || !prompt) {
return NextResponse.json(
{ error: 'Bad Request', message: '必須提供 cacheName 與 prompt' },
{ status: 400 }
);
}
// 1. 直接以快取實例初始化模型(不再需要重複上傳大檔案或重複傳遞 System Prompt)
const model = getModelWithCache(cacheName);
// 2. 進行推論
const result = await model.generateContent(prompt);
const response = await result.response;
const usage = response.usageMetadata;
// 3. 輸出監控驗證:觀察 cachedContentTokenCount
console.log(`[Cache Hit Monitor] Token 消耗統計:`, {
promptTokens: usage?.promptTokenCount,
cachedTokens: usage?.cachedContentTokenCount, // 命中快取的 Token 數量
candidatesTokens: usage?.candidatesTokenCount,
totalTokens: usage?.totalTokenCount,
});
return NextResponse.json({
success: true,
data: {
output: response.text(),
usage: {
totalTokens: usage?.totalTokenCount,
cachedTokens: usage?.cachedContentTokenCount,
// 若 cachedContentTokenCount 大於 0,代表成功命中快取並享有極致折扣
isCacheHit: Boolean(usage?.cachedContentTokenCount && usage.cachedContentTokenCount > 0),
},
},
});
} catch (error: any) {
console.error('[Cached Chat Error]:', error);
return NextResponse.json(
{ error: '快取推論失敗', message: error.message },
{ status: 500 }
);
}
}
當我們上傳了一支 40 分鐘的演講影片(換算 Token 數約 120,000 Tokens)並建立快取後,連續發送追問請求:
curl -X POST http://localhost:3000/api/ai/cached-chat \
-H "Content-Type: application/json" \
-d '{
"cacheName": "cachedContents/ab12cd34ef56gh78",
"prompt": "請從這支演講中整理出 3 個最具啟發性的金句,並附上講者說出該句子的情境"
}'
usageMetadata:[Cache Hit Monitor] Token 消耗統計: {
promptTokens: 120140,
cachedTokens: 120000,
candidatesTokens: 380,
totalTokens: 120520
}
注意看這組數據:
cachedTokens: 120000:這 12 萬個 Token 全數從記憶體快取中讀取!這代表你的 SaaS 在每次用戶進行互動追問時,都能以不到四分之一的成本提供極速流暢的即時體驗。
今天我們完成了【第二篇:Google AI 核心引擎與 Prompt 工坊】(Day 06 - Day 12)的收官之作。讓我們回顧這 7 天建立起的堅實基礎:
後端大腦與 AI 引擎已經武裝到牙齒,但終究還停留在終端機與 API 測試工具裡。一個真正的 SaaS 必須擁有令人驚豔、絲滑流暢的使用者介面!
明天開始,我們將正式邁入【第三篇:介面疾速生成與全棧基礎建設(Day 13 - Day 19)】。
明天(Day 13),我們將進入【Google Stitch 介面生成篇】:認識 Google 次世代 AI 介面生成神器 Google Stitch!看我們如何用純自然語言,在數分鐘內生成出極具設計感、現代化且具備生產力質感的 SaaS Dashboard 完整前端原型!
敬請期待,我們明天見!🔥