Claude Prompt Caching 長對話成本優化實戰

Claude Prompt Caching 長對話成本優化實戰

📌 本文重點

  • 只為「新算的 token」付錢才省成本
  • 穩定前綴(system、tools、長期記憶)要被快取
  • cache key 必須含模型、租戶與權限版本
  • 長對話可降 30–60% 推理成本

長對話裡真正燒錢的不是「整個 context 有幾個 token」,而是每一輪新算了多少 token。Claude 的 prompt caching 就是把那些每輪都重複出現的前綴(system prompt、tool schema、長期記憶等)標記成可重用的計算結果,只為「新出現的部分」付錢。實務上,你可以在 RAG、Agent、Code Assistant 裡大幅壓低長會話成本,同時讓模型保持完整上下文。

💡 關鍵: 掌握「每輪新算 token」才是控成本的核心,而不是只看整個 context 長度。


重點說明:為什麼「重用前綴」省錢

  1. 計費與計算模型:只對「新算過的 token」付錢

現代 LLM(包含 Claude)推理時,會對輸入序列做一次注意力與前向計算。若供應商支援 前綴快取,就能把已算過的 embedding / KV cache 重新使用,對於已快取的部分:

  • 計費:只收少量或不收額外費用(依供應商設計)
  • 計算:避免重跑 attention / matmul

  • 快取什麼:可穩定重複的 prefix

典型可快取區塊:

  • System prompt:角色、風格、平台規範
  • Tool schema:function calling / tools JSON schema
  • 長期記憶 / 專案上下文:repo 結構、domain handbook、RAG 長期摘要
  • 長期對話前半段:幾十輪以上的歷史,只要不會被頻繁重寫

  • API 層設計:cache key + 失效策略是核心

要讓 prompt caching 在多模型、多供應商環境可維護,必須明確管理:

  • cache key 組成:模型版本 + system prompt hash + tools hash + tenant + 權限版本
  • 失效策略
    • 模型升級(model_version 變更)
    • 工具清單或 schema 改動
    • tenant 權限/角色變更

💡 關鍵: 把模型 ID、租戶與權限版本都放進 cacheKey,才能避免快取錯配與資料洩漏。


實作範例:切分前綴與多層快取

1. Prompt 結構切分策略

我們先定義一個標準化的 prompt 結構:

// TypeScript / Node pseudo-code
interface PromptSegments {
  system: string;          // 系統指令
  toolsSchema: object[];   // 工具定義 (OpenAI / Claude tools 格式)
  longTermContext: string; // 長期記憶 / 專案說明 / RAG summary
  shortHistory: string;    // 近期對話 (例如最後 10 輪)
  userInput: string;       // 本輪使用者輸入
}

function buildMessages(segments: PromptSegments) {
  return [
    { role: "system", content: segments.system },
    { role: "system", content: `TOOLS_SCHEMA:\n${JSON.stringify(segments.toolsSchema)}` },
    { role: "system", content: `LONG_TERM_CONTEXT:\n${segments.longTermContext}` },
    { role: "assistant", content: segments.shortHistory },
    { role: "user", content: segments.userInput },
  ];
}

前 3 段(system / toolsSchema / longTermContext)就是要被 prompt caching 鎖定的 prefix;shortHistory 則保留可替換的空間,避免整個對話歷史變成難以管理的快取。

2. Node 版 cache middleware(多模型、多供應商共用)

假設你有一個抽象的 LLMClient,在呼叫前注入 cache metadata

// Node.js pseudo-code
import crypto from "crypto";

interface CacheMetadata {
  cacheKey: string;
  cacheTtlSec: number;
}

function buildCacheKey({
  provider,
  model,
  system,
  toolsSchema,
  tenantId,
  permissionsVersion,
}: {
  provider: string;
  model: string;
  system: string;
  toolsSchema: object[];
  tenantId: string;
  permissionsVersion: string;
}): string {
  const hashInput = JSON.stringify({ system, toolsSchema, tenantId, permissionsVersion });
  const hash = crypto.createHash("sha256").update(hashInput).digest("hex");
  return `${provider}:${model}:${hash}`; // 核心:模型 + 前綴內容 + 租戶/權限
}

async function withPromptCache(llmClient, segments: PromptSegments, ctx) {
  const cacheKey = buildCacheKey({
    provider: ctx.provider,          // "anthropic" / "openai" / "azure-openai" ...
    model: ctx.model,                // 例如 "claude-3.7-sonnet"
    system: segments.system,
    toolsSchema: segments.toolsSchema,
    tenantId: ctx.tenantId,
    permissionsVersion: ctx.permissionsVersion,
  });

  const messages = buildMessages(segments);

  const response = await llmClient.chat({
    messages,
    // 自定義或供應商原生字段
    metadata: {
      // 自家 caching layer 用
      promptCache: {
        cacheKey,
        segmentsCached: ["system", "toolsSchema", "longTermContext"],
        ttlSec: 3600, // 長期 context 一小時有效
      },
    },
  });

  return response;
}

在多供應商環境下的重點:

  • cacheKey 必須在抽象層就固定格式,不要依賴各家私有的 cache token。
  • 將「哪些 segment 可快取」「TTL 設定」寫在自家 metadata.promptCache,再由底層 adapter 映射到各家 API(例如 Anthropic 的 prompt caching 參數、OpenAI 未來的前綴重用機制等)。

3. Python 版:RAG + Agent + Code Assistant 共用策略

示範一個 Python middleware,處理三種場景:

# Python pseudo-code
import hashlib
from typing import List, Dict, Any

class PromptCacheMiddleware:
    def __init__(self, backend):
        self.backend = backend  # Redis / in-memory / provider-native

    def _hash(self, payload: Dict[str, Any]) -> str:
        raw = repr(payload).encode("utf-8")
        return hashlib.sha256(raw).hexdigest()

    def build_cache_key(self, provider: str, model: str, tenant: str,
                         system: str, tools: List[Dict[str, Any]],
                         perm_version: str) -> str:
        hash_part = self._hash({"system": system, "tools": tools,
                                "tenant": tenant, "perm": perm_version})
        return f"{provider}:{model}:{hash_part}"

    def call(self, llm_client, segments, ctx, scenario: str):
        # scenario: "rag" | "agent" | "code"
        cache_key = self.build_cache_key(
            ctx["provider"], ctx["model"], ctx["tenant"],
            segments.system, segments.tools_schema,
            ctx["permissions_version"],
        )

        ttl = 3600 if scenario in ("rag", "code") else 600

        messages = build_messages(segments)

        # 自家 cache backend,也可以是 provider 的 prompt cache
        cached_prefix = self.backend.get(cache_key)
        if cached_prefix:
            # 若使用 provider-native KV cache,可在這裡直接標記使用
            pass

        resp = llm_client.chat(
            messages=messages,
            metadata={
                "prompt_cache": {
                    "cache_key": cache_key,
                    "ttl_sec": ttl,
                    "segments_cached": ["system", "toolsSchema", "longTermContext"],
                }
            },
        )
        self.backend.set(cache_key, "USED", ttl)
        return resp

這樣你就可以在同一層 middleware 裡:

  • RAG 保留穩定的 long-term summary 前綴
  • Agent 快取工具清單與角色設定
  • Code Assistant 快取 repo / 專案上下文

建議與注意事項:幾個常見坑

  1. 工具清單動態變化 → cache 大量失效

  2. 問題:Agent 工具列表若每次請求都依據情境動態調整,toolsSchema hash 會頻繁變動,導致 cacheKey 不可重用

  3. 建議:

    • 將工具分成「核心必備工具」與「情境工具」,只對核心工具段做 caching。
    • 使用 mid-conversation tool changes(例如 Claude Opus 5 的 beta 功能)讓工具權限在對話中調整,而不觸發整個 prefix 重算。
  4. 模型升級 → 快取錯配

  5. 問題:模型從 claude-3.6 換到 claude-3.7,如果 cacheKey 沒把 model / version 納入,就會拿舊模型算出的 prefix 餵給新模型,出現行為差異。

  6. 建議:

    • 必須把 model id / version 放在 cacheKey 開頭(前面範例已包含)。
    • 建多供應商兼容測試(對齊「同一前綴 + 不同模型」在工具呼叫、輸出格式上的差異),參考 LLM Provider Quirks 文章的思路。
  7. 多租戶 SaaS:tenant 隔離

  8. 問題:如果 cacheKey 沒包含 tenant / 權限版本,在多租戶環境下可能把 A 客戶的長期記憶前綴給 B 客戶用,造成嚴重資料洩漏

  9. 建議:

    • tenantIdpermissionsVersion 必須是 cacheKey 的一部分
    • 權限變更(role / scope 調整)時,明確 bump permissionsVersion,強制快取失效。
    • 對於敏感工具(例如能查詢客戶資料庫的 tool),可直接標記為 不參與 prompt caching,或使用獨立 cache 空間。
  10. 避免 cache 污染:RAG + Agent + Code Assistant

  11. 污染範例:

    • RAG summary 中意外混入使用者敏感資料,然後被快取成長期前綴。
    • Agent 工具列表在某次測試中加入 dev-only 工具,被 cache,之後所有 production 對話都看得到。
  12. 建議:
    • 在產生長期 context / tools schema 前,做一次 安全與敏感資料清洗
    • 長期前綴內容最好由後端控制(例如固定的 RAG summary service),而不是讓使用者直接寫入。
    • 為 dev / staging / prod 分別建立獨立 cache namespace。

實際好處:你專案會得到什麼

引入 Claude Code Prompt Caching 這類前綴快取機制後,對典型專案有幾個直接好處:

  • 長對話成本顯著下降:像 code assistant 或長期顧問型 Agent,一整天會話的 token 數看起來驚人,但前 70–90% 的前綴計算可以重用。實務上常見是 30–60% 推理成本下降
  • 維持完整上下文又不必瘋狂裁剪:有快取後,你可以保留更多長期記憶與工具說明,而不是每次都為了成本把 context 切到只剩最近幾輪。
  • 跨供應商、多模型快速試錯:抽象層設計好 cacheKey 與前綴切分後,就能在不同模型間切換時,維持一致的成本控制策略,不怕「換模型就打回重算」。

💡 關鍵: 只要一開始就設計好前綴切分與快取策略,prompt caching 就能變成穩定的基礎設施,而不是事後補救。

只要在專案一開始就規劃好 prompt 結構、cache key、失效策略,你就能把 prompt caching 當成基礎設施來使用,而不是事後補上去的微調。

🚀 你現在可以做的事

  • 在現有專案裡明確切分 systemtoolsSchemalongTermContext 等前綴區塊
  • 為你的 LLM 抽象層加入統一的 cacheKeymetadata.promptCache 設計
  • 在開發環境先測試 RAG、Agent、Code Assistant 三種場景的快取策略與失效機制

留言

發佈留言

發佈留言必須填寫的電子郵件地址不會公開。 必填欄位標示為 *