Przeglądaj źródła

feat: claude 1h cache (#2155)

* feat: claude 1h cache

* feat: claude 1h cache

* fix price
Seefs 1 miesiąc temu
rodzic
commit
df2ee649ab

+ 38 - 5
dto/claude.go

@@ -510,11 +510,44 @@ func (c *ClaudeResponse) GetClaudeError() *types.ClaudeError {
 }
 
 type ClaudeUsage struct {
-	InputTokens              int                  `json:"input_tokens"`
-	CacheCreationInputTokens int                  `json:"cache_creation_input_tokens"`
-	CacheReadInputTokens     int                  `json:"cache_read_input_tokens"`
-	OutputTokens             int                  `json:"output_tokens"`
-	ServerToolUse            *ClaudeServerToolUse `json:"server_tool_use,omitempty"`
+	InputTokens              int                       `json:"input_tokens"`
+	CacheCreationInputTokens int                       `json:"cache_creation_input_tokens"`
+	CacheReadInputTokens     int                       `json:"cache_read_input_tokens"`
+	OutputTokens             int                       `json:"output_tokens"`
+	CacheCreation            *ClaudeCacheCreationUsage `json:"cache_creation,omitempty"`
+	// claude cache 1h
+	ClaudeCacheCreation5mTokens int                  `json:"claude_cache_creation_5_m_tokens"`
+	ClaudeCacheCreation1hTokens int                  `json:"claude_cache_creation_1_h_tokens"`
+	ServerToolUse               *ClaudeServerToolUse `json:"server_tool_use,omitempty"`
+}
+
+type ClaudeCacheCreationUsage struct {
+	Ephemeral5mInputTokens int `json:"ephemeral_5m_input_tokens,omitempty"`
+	Ephemeral1hInputTokens int `json:"ephemeral_1h_input_tokens,omitempty"`
+}
+
+func (u *ClaudeUsage) GetCacheCreation5mTokens() int {
+	if u == nil || u.CacheCreation == nil {
+		return 0
+	}
+	return u.CacheCreation.Ephemeral5mInputTokens
+}
+
+func (u *ClaudeUsage) GetCacheCreation1hTokens() int {
+	if u == nil || u.CacheCreation == nil {
+		return 0
+	}
+	return u.CacheCreation.Ephemeral1hInputTokens
+}
+
+func (u *ClaudeUsage) GetCacheCreationTotalTokens() int {
+	if u == nil {
+		return 0
+	}
+	if u.CacheCreationInputTokens > 0 {
+		return u.CacheCreationInputTokens
+	}
+	return u.GetCacheCreation5mTokens() + u.GetCacheCreation1hTokens()
 }
 
 type ClaudeServerToolUse struct {

+ 5 - 0
dto/openai_response.go

@@ -230,6 +230,11 @@ type Usage struct {
 	InputTokens            int                `json:"input_tokens"`
 	OutputTokens           int                `json:"output_tokens"`
 	InputTokensDetails     *InputTokenDetails `json:"input_tokens_details"`
+
+	// claude cache 1h
+	ClaudeCacheCreation5mTokens int `json:"claude_cache_creation_5_m_tokens"`
+	ClaudeCacheCreation1hTokens int `json:"claude_cache_creation_1_h_tokens"`
+
 	// OpenRouter Params
 	Cost any `json:"cost,omitempty"`
 }

+ 4 - 0
relay/channel/claude/relay-claude.go

@@ -596,6 +596,8 @@ func FormatClaudeResponseInfo(requestMode int, claudeResponse *dto.ClaudeRespons
 			claudeInfo.Usage.PromptTokens = claudeResponse.Message.Usage.InputTokens
 			claudeInfo.Usage.PromptTokensDetails.CachedTokens = claudeResponse.Message.Usage.CacheReadInputTokens
 			claudeInfo.Usage.PromptTokensDetails.CachedCreationTokens = claudeResponse.Message.Usage.CacheCreationInputTokens
+			claudeInfo.Usage.ClaudeCacheCreation5mTokens = claudeResponse.Message.Usage.GetCacheCreation5mTokens()
+			claudeInfo.Usage.ClaudeCacheCreation1hTokens = claudeResponse.Message.Usage.GetCacheCreation1hTokens()
 			claudeInfo.Usage.CompletionTokens = claudeResponse.Message.Usage.OutputTokens
 		} else if claudeResponse.Type == "content_block_delta" {
 			if claudeResponse.Delta.Text != nil {
@@ -740,6 +742,8 @@ func HandleClaudeResponseData(c *gin.Context, info *relaycommon.RelayInfo, claud
 		claudeInfo.Usage.TotalTokens = claudeResponse.Usage.InputTokens + claudeResponse.Usage.OutputTokens
 		claudeInfo.Usage.PromptTokensDetails.CachedTokens = claudeResponse.Usage.CacheReadInputTokens
 		claudeInfo.Usage.PromptTokensDetails.CachedCreationTokens = claudeResponse.Usage.CacheCreationInputTokens
+		claudeInfo.Usage.ClaudeCacheCreation5mTokens = claudeResponse.Usage.GetCacheCreation5mTokens()
+		claudeInfo.Usage.ClaudeCacheCreation1hTokens = claudeResponse.Usage.GetCacheCreation1hTokens()
 	}
 	var responseData []byte
 	switch info.RelayFormat {

+ 10 - 0
relay/helper/price.go

@@ -13,6 +13,9 @@ import (
 	"github.com/gin-gonic/gin"
 )
 
+// https://docs.claude.com/en/docs/build-with-claude/prompt-caching#1-hour-cache-duration
+const claudeCacheCreation1hMultiplier = 6 / 3.75
+
 // HandleGroupRatio checks for "auto_group" in the context and updates the group ratio and relayInfo.UsingGroup if present
 func HandleGroupRatio(ctx *gin.Context, relayInfo *relaycommon.RelayInfo) types.GroupRatioInfo {
 	groupRatioInfo := types.GroupRatioInfo{
@@ -53,6 +56,8 @@ func ModelPriceHelper(c *gin.Context, info *relaycommon.RelayInfo, promptTokens
 	var cacheRatio float64
 	var imageRatio float64
 	var cacheCreationRatio float64
+	var cacheCreationRatio5m float64
+	var cacheCreationRatio1h float64
 	var audioRatio float64
 	var audioCompletionRatio float64
 	var freeModel bool
@@ -76,6 +81,9 @@ func ModelPriceHelper(c *gin.Context, info *relaycommon.RelayInfo, promptTokens
 		completionRatio = ratio_setting.GetCompletionRatio(info.OriginModelName)
 		cacheRatio, _ = ratio_setting.GetCacheRatio(info.OriginModelName)
 		cacheCreationRatio, _ = ratio_setting.GetCreateCacheRatio(info.OriginModelName)
+		cacheCreationRatio5m = cacheCreationRatio
+		// 固定1h和5min缓存写入价格的比例
+		cacheCreationRatio1h = cacheCreationRatio * claudeCacheCreation1hMultiplier
 		imageRatio, _ = ratio_setting.GetImageRatio(info.OriginModelName)
 		audioRatio = ratio_setting.GetAudioRatio(info.OriginModelName)
 		audioCompletionRatio = ratio_setting.GetAudioCompletionRatio(info.OriginModelName)
@@ -116,6 +124,8 @@ func ModelPriceHelper(c *gin.Context, info *relaycommon.RelayInfo, promptTokens
 		AudioRatio:           audioRatio,
 		AudioCompletionRatio: audioCompletionRatio,
 		CacheCreationRatio:   cacheCreationRatio,
+		CacheCreation5mRatio: cacheCreationRatio5m,
+		CacheCreation1hRatio: cacheCreationRatio1h,
 		QuotaToPreConsume:    preConsumedQuota,
 	}
 

+ 13 - 1
service/log_info_generate.go

@@ -92,11 +92,23 @@ func GenerateAudioOtherInfo(ctx *gin.Context, relayInfo *relaycommon.RelayInfo,
 }
 
 func GenerateClaudeOtherInfo(ctx *gin.Context, relayInfo *relaycommon.RelayInfo, modelRatio, groupRatio, completionRatio float64,
-	cacheTokens int, cacheRatio float64, cacheCreationTokens int, cacheCreationRatio float64, modelPrice float64, userGroupRatio float64) map[string]interface{} {
+	cacheTokens int, cacheRatio float64,
+	cacheCreationTokens int, cacheCreationRatio float64,
+	cacheCreationTokens5m int, cacheCreationRatio5m float64,
+	cacheCreationTokens1h int, cacheCreationRatio1h float64,
+	modelPrice float64, userGroupRatio float64) map[string]interface{} {
 	info := GenerateTextOtherInfo(ctx, relayInfo, modelRatio, groupRatio, completionRatio, cacheTokens, cacheRatio, modelPrice, userGroupRatio)
 	info["claude"] = true
 	info["cache_creation_tokens"] = cacheCreationTokens
 	info["cache_creation_ratio"] = cacheCreationRatio
+	if cacheCreationTokens5m != 0 {
+		info["cache_creation_tokens_5m"] = cacheCreationTokens5m
+		info["cache_creation_ratio_5m"] = cacheCreationRatio5m
+	}
+	if cacheCreationTokens1h != 0 {
+		info["cache_creation_tokens_1h"] = cacheCreationTokens1h
+		info["cache_creation_ratio_1h"] = cacheCreationRatio1h
+	}
 	return info
 }
 

+ 15 - 2
service/quota.go

@@ -251,7 +251,11 @@ func PostClaudeConsumeQuota(ctx *gin.Context, relayInfo *relaycommon.RelayInfo,
 	cacheTokens := usage.PromptTokensDetails.CachedTokens
 
 	cacheCreationRatio := relayInfo.PriceData.CacheCreationRatio
+	cacheCreationRatio5m := relayInfo.PriceData.CacheCreation5mRatio
+	cacheCreationRatio1h := relayInfo.PriceData.CacheCreation1hRatio
 	cacheCreationTokens := usage.PromptTokensDetails.CachedCreationTokens
+	cacheCreationTokens5m := usage.ClaudeCacheCreation5mTokens
+	cacheCreationTokens1h := usage.ClaudeCacheCreation1hTokens
 
 	if relayInfo.ChannelType == constant.ChannelTypeOpenRouter {
 		promptTokens -= cacheTokens
@@ -269,7 +273,12 @@ func PostClaudeConsumeQuota(ctx *gin.Context, relayInfo *relaycommon.RelayInfo,
 	if !relayInfo.PriceData.UsePrice {
 		calculateQuota = float64(promptTokens)
 		calculateQuota += float64(cacheTokens) * cacheRatio
-		calculateQuota += float64(cacheCreationTokens) * cacheCreationRatio
+		calculateQuota += float64(cacheCreationTokens5m) * cacheCreationRatio5m
+		calculateQuota += float64(cacheCreationTokens1h) * cacheCreationRatio1h
+		remainingCacheCreationTokens := cacheCreationTokens - cacheCreationTokens5m - cacheCreationTokens1h
+		if remainingCacheCreationTokens > 0 {
+			calculateQuota += float64(remainingCacheCreationTokens) * cacheCreationRatio
+		}
 		calculateQuota += float64(completionTokens) * completionRatio
 		calculateQuota = calculateQuota * groupRatio * modelRatio
 	} else {
@@ -322,7 +331,11 @@ func PostClaudeConsumeQuota(ctx *gin.Context, relayInfo *relaycommon.RelayInfo,
 	}
 
 	other := GenerateClaudeOtherInfo(ctx, relayInfo, modelRatio, groupRatio, completionRatio,
-		cacheTokens, cacheRatio, cacheCreationTokens, cacheCreationRatio, modelPrice, relayInfo.PriceData.GroupRatioInfo.GroupSpecialRatio)
+		cacheTokens, cacheRatio,
+		cacheCreationTokens, cacheCreationRatio,
+		cacheCreationTokens5m, cacheCreationRatio5m,
+		cacheCreationTokens1h, cacheCreationRatio1h,
+		modelPrice, relayInfo.PriceData.GroupRatioInfo.GroupSpecialRatio)
 	model.RecordConsumeLog(ctx, relayInfo.UserId, model.RecordConsumeLogParams{
 		ChannelId:        relayInfo.ChannelId,
 		PromptTokens:     promptTokens,

+ 3 - 1
types/price_data.go

@@ -15,6 +15,8 @@ type PriceData struct {
 	CompletionRatio      float64
 	CacheRatio           float64
 	CacheCreationRatio   float64
+	CacheCreation5mRatio float64
+	CacheCreation1hRatio float64
 	ImageRatio           float64
 	AudioRatio           float64
 	AudioCompletionRatio float64
@@ -31,5 +33,5 @@ type PerCallPriceData struct {
 }
 
 func (p PriceData) ToSetting() string {
-	return fmt.Sprintf("ModelPrice: %f, ModelRatio: %f, CompletionRatio: %f, CacheRatio: %f, GroupRatio: %f, UsePrice: %t, CacheCreationRatio: %f, QuotaToPreConsume: %d, ImageRatio: %f, AudioRatio: %f, AudioCompletionRatio: %f", p.ModelPrice, p.ModelRatio, p.CompletionRatio, p.CacheRatio, p.GroupRatioInfo.GroupRatio, p.UsePrice, p.CacheCreationRatio, p.QuotaToPreConsume, p.ImageRatio, p.AudioRatio, p.AudioCompletionRatio)
+	return fmt.Sprintf("ModelPrice: %f, ModelRatio: %f, CompletionRatio: %f, CacheRatio: %f, GroupRatio: %f, UsePrice: %t, CacheCreationRatio: %f, CacheCreation5mRatio: %f, CacheCreation1hRatio: %f, QuotaToPreConsume: %d, ImageRatio: %f, AudioRatio: %f, AudioCompletionRatio: %f", p.ModelPrice, p.ModelRatio, p.CompletionRatio, p.CacheRatio, p.GroupRatioInfo.GroupRatio, p.UsePrice, p.CacheCreationRatio, p.CacheCreation5mRatio, p.CacheCreation1hRatio, p.QuotaToPreConsume, p.ImageRatio, p.AudioRatio, p.AudioCompletionRatio)
 }

+ 8 - 0
web/src/components/table/usage-logs/UsageLogsColumnDefs.jsx

@@ -551,6 +551,10 @@ export const getLogsColumns = ({
               other.cache_ratio || 1.0,
               other.cache_creation_tokens || 0,
               other.cache_creation_ratio || 1.0,
+              other.cache_creation_tokens_5m || 0,
+              other.cache_creation_ratio_5m || other.cache_creation_ratio || 1.0,
+              other.cache_creation_tokens_1h || 0,
+              other.cache_creation_ratio_1h || other.cache_creation_ratio || 1.0,
               false,
               1.0,
               other?.is_system_prompt_overwritten,
@@ -565,6 +569,10 @@ export const getLogsColumns = ({
               other.cache_ratio || 1.0,
               0,
               1.0,
+              0,
+              1.0,
+              0,
+              1.0,
               false,
               1.0,
               other?.is_system_prompt_overwritten,

+ 257 - 54
web/src/helpers/render.jsx

@@ -1046,6 +1046,10 @@ function renderPriceSimpleCore({
   cacheRatio = 1.0,
   cacheCreationTokens = 0,
   cacheCreationRatio = 1.0,
+  cacheCreationTokens5m = 0,
+  cacheCreationRatio5m = 1.0,
+  cacheCreationTokens1h = 0,
+  cacheCreationRatio1h = 1.0,
   image = false,
   imageRatio = 1.0,
   isSystemPromptOverride = false,
@@ -1064,17 +1068,40 @@ function renderPriceSimpleCore({
     });
   }
 
+  const hasSplitCacheCreation =
+    cacheCreationTokens5m > 0 || cacheCreationTokens1h > 0;
+
+  const shouldShowLegacyCacheCreation =
+    !hasSplitCacheCreation && cacheCreationTokens !== 0;
+
+  const shouldShowCache = cacheTokens !== 0;
+  const shouldShowCacheCreation5m =
+    hasSplitCacheCreation && cacheCreationTokens5m > 0;
+  const shouldShowCacheCreation1h =
+    hasSplitCacheCreation && cacheCreationTokens1h > 0;
+
   const parts = [];
   // base: model ratio
   parts.push(i18next.t('模型: {{ratio}}'));
 
   // cache part (label differs when with image)
-  if (cacheTokens !== 0) {
+  if (shouldShowCache) {
     parts.push(i18next.t('缓存: {{cacheRatio}}'));
   }
 
-  // cache creation part (Claude specific if passed)
-  if (cacheCreationTokens !== 0) {
+  if (hasSplitCacheCreation) {
+    if (shouldShowCacheCreation5m && shouldShowCacheCreation1h) {
+      parts.push(
+        i18next.t(
+          '缓存创建: 5m {{cacheCreationRatio5m}} / 1h {{cacheCreationRatio1h}}',
+        ),
+      );
+    } else if (shouldShowCacheCreation5m) {
+      parts.push(i18next.t('缓存创建: 5m {{cacheCreationRatio5m}}'));
+    } else if (shouldShowCacheCreation1h) {
+      parts.push(i18next.t('缓存创建: 1h {{cacheCreationRatio1h}}'));
+    }
+  } else if (shouldShowLegacyCacheCreation) {
     parts.push(i18next.t('缓存创建: {{cacheCreationRatio}}'));
   }
 
@@ -1091,6 +1118,8 @@ function renderPriceSimpleCore({
     groupRatio: finalGroupRatio,
     cacheRatio: cacheRatio,
     cacheCreationRatio: cacheCreationRatio,
+    cacheCreationRatio5m: cacheCreationRatio5m,
+    cacheCreationRatio1h: cacheCreationRatio1h,
     imageRatio: imageRatio,
   });
 
@@ -1450,6 +1479,10 @@ export function renderModelPriceSimple(
   cacheRatio = 1.0,
   cacheCreationTokens = 0,
   cacheCreationRatio = 1.0,
+  cacheCreationTokens5m = 0,
+  cacheCreationRatio5m = 1.0,
+  cacheCreationTokens1h = 0,
+  cacheCreationRatio1h = 1.0,
   image = false,
   imageRatio = 1.0,
   isSystemPromptOverride = false,
@@ -1464,6 +1497,10 @@ export function renderModelPriceSimple(
     cacheRatio,
     cacheCreationTokens,
     cacheCreationRatio,
+    cacheCreationTokens5m,
+    cacheCreationRatio5m,
+    cacheCreationTokens1h,
+    cacheCreationRatio1h,
     image,
     imageRatio,
     isSystemPromptOverride,
@@ -1681,6 +1718,10 @@ export function renderClaudeModelPrice(
   cacheRatio = 1.0,
   cacheCreationTokens = 0,
   cacheCreationRatio = 1.0,
+  cacheCreationTokens5m = 0,
+  cacheCreationRatio5m = 1.0,
+  cacheCreationTokens1h = 0,
+  cacheCreationRatio1h = 1.0,
 ) {
   const { ratio: effectiveGroupRatio, label: ratioLabel } = getEffectiveRatio(
     groupRatio,
@@ -1710,20 +1751,121 @@ export function renderClaudeModelPrice(
     const completionRatioValue = completionRatio || 0;
     const inputRatioPrice = modelRatio * 2.0;
     const completionRatioPrice = modelRatio * 2.0 * completionRatioValue;
-    let cacheRatioPrice = (modelRatio * 2.0 * cacheRatio).toFixed(2);
-    let cacheCreationRatioPrice = modelRatio * 2.0 * cacheCreationRatio;
+    const cacheRatioPrice = modelRatio * 2.0 * cacheRatio;
+    const cacheCreationRatioPrice = modelRatio * 2.0 * cacheCreationRatio;
+    const cacheCreationRatioPrice5m = modelRatio * 2.0 * cacheCreationRatio5m;
+    const cacheCreationRatioPrice1h = modelRatio * 2.0 * cacheCreationRatio1h;
+
+    const hasSplitCacheCreation =
+      cacheCreationTokens5m > 0 || cacheCreationTokens1h > 0;
+
+    const shouldShowCache = cacheTokens > 0;
+    const shouldShowLegacyCacheCreation =
+      !hasSplitCacheCreation && cacheCreationTokens > 0;
+    const shouldShowCacheCreation5m =
+      hasSplitCacheCreation && cacheCreationTokens5m > 0;
+    const shouldShowCacheCreation1h =
+      hasSplitCacheCreation && cacheCreationTokens1h > 0;
 
     // Calculate effective input tokens (non-cached + cached with ratio applied + cache creation with ratio applied)
     const nonCachedTokens = inputTokens;
     const effectiveInputTokens =
       nonCachedTokens +
       cacheTokens * cacheRatio +
-      cacheCreationTokens * cacheCreationRatio;
+      cacheCreationTokens * cacheCreationRatio +
+      cacheCreationTokens5m * cacheCreationRatio5m +
+      cacheCreationTokens1h * cacheCreationRatio1h;
 
     let price =
       (effectiveInputTokens / 1000000) * inputRatioPrice * groupRatio +
       (completionTokens / 1000000) * completionRatioPrice * groupRatio;
 
+    const inputUnitPrice = inputRatioPrice * rate;
+    const completionUnitPrice = completionRatioPrice * rate;
+    const cacheUnitPrice = cacheRatioPrice * rate;
+    const cacheCreationUnitPrice = cacheCreationRatioPrice * rate;
+    const cacheCreationUnitPrice5m = cacheCreationRatioPrice5m * rate;
+    const cacheCreationUnitPrice1h = cacheCreationRatioPrice1h * rate;
+    const cacheCreationUnitPriceTotal =
+      cacheCreationUnitPrice5m + cacheCreationUnitPrice1h;
+
+    const breakdownSegments = [
+      i18next.t('提示 {{input}} tokens / 1M tokens * {{symbol}}{{price}}', {
+        input: inputTokens,
+        symbol,
+        price: inputUnitPrice.toFixed(6),
+      }),
+    ];
+
+    if (shouldShowCache) {
+      breakdownSegments.push(
+        i18next.t(
+          '缓存 {{tokens}} tokens / 1M tokens * {{symbol}}{{price}} (倍率: {{ratio}})',
+          {
+            tokens: cacheTokens,
+            symbol,
+            price: cacheUnitPrice.toFixed(6),
+            ratio: cacheRatio,
+          },
+        ),
+      );
+    }
+
+    if (shouldShowLegacyCacheCreation) {
+      breakdownSegments.push(
+        i18next.t(
+          '缓存创建 {{tokens}} tokens / 1M tokens * {{symbol}}{{price}} (倍率: {{ratio}})',
+          {
+            tokens: cacheCreationTokens,
+            symbol,
+            price: cacheCreationUnitPrice.toFixed(6),
+            ratio: cacheCreationRatio,
+          },
+        ),
+      );
+    }
+
+    if (shouldShowCacheCreation5m) {
+      breakdownSegments.push(
+        i18next.t(
+          '5m缓存创建 {{tokens}} tokens / 1M tokens * {{symbol}}{{price}} (倍率: {{ratio}})',
+          {
+            tokens: cacheCreationTokens5m,
+            symbol,
+            price: cacheCreationUnitPrice5m.toFixed(6),
+            ratio: cacheCreationRatio5m,
+          },
+        ),
+      );
+    }
+
+    if (shouldShowCacheCreation1h) {
+      breakdownSegments.push(
+        i18next.t(
+          '1h缓存创建 {{tokens}} tokens / 1M tokens * {{symbol}}{{price}} (倍率: {{ratio}})',
+          {
+            tokens: cacheCreationTokens1h,
+            symbol,
+            price: cacheCreationUnitPrice1h.toFixed(6),
+            ratio: cacheCreationRatio1h,
+          },
+        ),
+      );
+    }
+
+    breakdownSegments.push(
+      i18next.t(
+        '补全 {{completion}} tokens / 1M tokens * {{symbol}}{{price}}',
+        {
+          completion: completionTokens,
+          symbol,
+          price: completionUnitPrice.toFixed(6),
+        },
+      ),
+    );
+
+    const breakdownText = breakdownSegments.join(' + ');
+
     return (
       <>
         <article>
@@ -1744,7 +1886,7 @@ export function renderClaudeModelPrice(
               },
             )}
           </p>
-          {cacheTokens > 0 && (
+          {shouldShowCache && (
             <p>
               {i18next.t(
                 '缓存价格:{{symbol}}{{price}} * {{ratio}} = {{symbol}}{{total}} / 1M tokens (缓存倍率: {{cacheRatio}})',
@@ -1752,13 +1894,13 @@ export function renderClaudeModelPrice(
                   symbol: symbol,
                   price: (inputRatioPrice * rate).toFixed(6),
                   ratio: cacheRatio,
-                  total: (cacheRatioPrice * rate).toFixed(2),
+                  total: cacheUnitPrice.toFixed(6),
                   cacheRatio: cacheRatio,
                 },
               )}
             </p>
           )}
-          {cacheCreationTokens > 0 && (
+          {shouldShowLegacyCacheCreation && (
             <p>
               {i18next.t(
                 '缓存创建价格:{{symbol}}{{price}} * {{ratio}} = {{symbol}}{{total}} / 1M tokens (缓存创建倍率: {{cacheCreationRatio}})',
@@ -1766,49 +1908,65 @@ export function renderClaudeModelPrice(
                   symbol: symbol,
                   price: (inputRatioPrice * rate).toFixed(6),
                   ratio: cacheCreationRatio,
-                  total: (cacheCreationRatioPrice * rate).toFixed(6),
+                  total: cacheCreationUnitPrice.toFixed(6),
                   cacheCreationRatio: cacheCreationRatio,
                 },
               )}
             </p>
           )}
+          {shouldShowCacheCreation5m && (
+            <p>
+              {i18next.t(
+                '5m缓存创建价格:{{symbol}}{{price}} * {{ratio}} = {{symbol}}{{total}} / 1M tokens (5m缓存创建倍率: {{cacheCreationRatio5m}})',
+                {
+                  symbol: symbol,
+                  price: (inputRatioPrice * rate).toFixed(6),
+                  ratio: cacheCreationRatio5m,
+                  total: cacheCreationUnitPrice5m.toFixed(6),
+                  cacheCreationRatio5m: cacheCreationRatio5m,
+                },
+              )}
+            </p>
+          )}
+          {shouldShowCacheCreation1h && (
+            <p>
+              {i18next.t(
+                '1h缓存创建价格:{{symbol}}{{price}} * {{ratio}} = {{symbol}}{{total}} / 1M tokens (1h缓存创建倍率: {{cacheCreationRatio1h}})',
+                {
+                  symbol: symbol,
+                  price: (inputRatioPrice * rate).toFixed(6),
+                  ratio: cacheCreationRatio1h,
+                  total: cacheCreationUnitPrice1h.toFixed(6),
+                  cacheCreationRatio1h: cacheCreationRatio1h,
+                },
+              )}
+            </p>
+          )}
+          {shouldShowCacheCreation5m && shouldShowCacheCreation1h && (
+            <p>
+              {i18next.t(
+                '缓存创建价格合计:5m {{symbol}}{{five}} + 1h {{symbol}}{{one}} = {{symbol}}{{total}} / 1M tokens',
+                {
+                  symbol: symbol,
+                  five: cacheCreationUnitPrice5m.toFixed(6),
+                  one: cacheCreationUnitPrice1h.toFixed(6),
+                  total: cacheCreationUnitPriceTotal.toFixed(6),
+                },
+              )}
+            </p>
+          )}
           <p></p>
           <p>
-            {cacheTokens > 0 || cacheCreationTokens > 0
-              ? i18next.t(
-                  '提示 {{nonCacheInput}} tokens / 1M tokens * {{symbol}}{{price}} + 缓存 {{cacheInput}} tokens / 1M tokens * {{symbol}}{{cachePrice}} + 缓存创建 {{cacheCreationInput}} tokens / 1M tokens * {{symbol}}{{cacheCreationPrice}} + 补全 {{completion}} tokens / 1M tokens * {{symbol}}{{compPrice}} * {{ratioType}} {{ratio}} = {{symbol}}{{total}}',
-                  {
-                    nonCacheInput: nonCachedTokens,
-                    cacheInput: cacheTokens,
-                    cacheRatio: cacheRatio,
-                    cacheCreationInput: cacheCreationTokens,
-                    cacheCreationRatio: cacheCreationRatio,
-                    symbol: symbol,
-                    cachePrice: (cacheRatioPrice * rate).toFixed(2),
-                    cacheCreationPrice: (
-                      cacheCreationRatioPrice * rate
-                    ).toFixed(6),
-                    price: (inputRatioPrice * rate).toFixed(6),
-                    completion: completionTokens,
-                    compPrice: (completionRatioPrice * rate).toFixed(6),
-                    ratio: groupRatio,
-                    ratioType: ratioLabel,
-                    total: (price * rate).toFixed(6),
-                  },
-                )
-              : i18next.t(
-                  '提示 {{input}} tokens / 1M tokens * {{symbol}}{{price}} + 补全 {{completion}} tokens / 1M tokens * {{symbol}}{{compPrice}} * {{ratioType}} {{ratio}} = {{symbol}}{{total}}',
-                  {
-                    input: inputTokens,
-                    symbol: symbol,
-                    price: (inputRatioPrice * rate).toFixed(6),
-                    completion: completionTokens,
-                    compPrice: (completionRatioPrice * rate).toFixed(6),
-                    ratio: groupRatio,
-                    ratioType: ratioLabel,
-                    total: (price * rate).toFixed(6),
-                  },
-                )}
+            {i18next.t(
+              '{{breakdown}} * {{ratioType}} {{ratio}} = {{symbol}}{{total}}',
+              {
+                breakdown: breakdownText,
+                ratioType: ratioLabel,
+                ratio: groupRatio,
+                symbol: symbol,
+                total: (price * rate).toFixed(6),
+              },
+            )}
           </p>
           <p>{i18next.t('仅供参考,以实际扣费为准')}</p>
         </article>
@@ -1825,6 +1983,10 @@ export function renderClaudeLogContent(
   user_group_ratio,
   cacheRatio = 1.0,
   cacheCreationRatio = 1.0,
+  cacheCreationTokens5m = 0,
+  cacheCreationRatio5m = 1.0,
+  cacheCreationTokens1h = 0,
+  cacheCreationRatio1h = 1.0,
 ) {
   const { ratio: effectiveGroupRatio, label: ratioLabel } = getEffectiveRatio(
     groupRatio,
@@ -1843,17 +2005,58 @@ export function renderClaudeLogContent(
       ratio: groupRatio,
     });
   } else {
-    return i18next.t(
-      '模型倍率 {{modelRatio}},输出倍率 {{completionRatio}},缓存倍率 {{cacheRatio}},缓存创建倍率 {{cacheCreationRatio}},{{ratioType}} {{ratio}}',
-      {
-        modelRatio: modelRatio,
-        completionRatio: completionRatio,
-        cacheRatio: cacheRatio,
-        cacheCreationRatio: cacheCreationRatio,
+    const hasSplitCacheCreation =
+      cacheCreationTokens5m > 0 || cacheCreationTokens1h > 0;
+    const shouldShowCacheCreation5m =
+      hasSplitCacheCreation && cacheCreationTokens5m > 0;
+    const shouldShowCacheCreation1h =
+      hasSplitCacheCreation && cacheCreationTokens1h > 0;
+
+    let cacheCreationPart = null;
+    if (hasSplitCacheCreation) {
+      if (shouldShowCacheCreation5m && shouldShowCacheCreation1h) {
+        cacheCreationPart = i18next.t(
+          '缓存创建倍率 5m {{cacheCreationRatio5m}} / 1h {{cacheCreationRatio1h}}',
+          {
+            cacheCreationRatio5m,
+            cacheCreationRatio1h,
+          },
+        );
+      } else if (shouldShowCacheCreation5m) {
+        cacheCreationPart = i18next.t(
+          '缓存创建倍率 5m {{cacheCreationRatio5m}}',
+          {
+            cacheCreationRatio5m,
+          },
+        );
+      } else if (shouldShowCacheCreation1h) {
+        cacheCreationPart = i18next.t(
+          '缓存创建倍率 1h {{cacheCreationRatio1h}}',
+          {
+            cacheCreationRatio1h,
+          },
+        );
+      }
+    }
+
+    if (!cacheCreationPart) {
+      cacheCreationPart = i18next.t('缓存创建倍率 {{cacheCreationRatio}}', {
+        cacheCreationRatio,
+      });
+    }
+
+    const parts = [
+      i18next.t('模型倍率 {{modelRatio}}', { modelRatio }),
+      i18next.t('输出倍率 {{completionRatio}}', { completionRatio }),
+      i18next.t('缓存倍率 {{cacheRatio}}', { cacheRatio }),
+      cacheCreationPart,
+      i18next.t('{{ratioType}} {{ratio}}', {
         ratioType: ratioLabel,
         ratio: groupRatio,
-      },
-    );
+      }),
+    ];
+
+    return parts.join(',');
   }
 }
 

+ 8 - 0
web/src/hooks/usage-logs/useUsageLogsData.jsx

@@ -361,6 +361,10 @@ export const useLogsData = () => {
                 other?.user_group_ratio,
                 other.cache_ratio || 1.0,
                 other.cache_creation_ratio || 1.0,
+                other.cache_creation_tokens_5m || 0,
+                other.cache_creation_ratio_5m || other.cache_creation_ratio || 1.0,
+                other.cache_creation_tokens_1h || 0,
+                other.cache_creation_ratio_1h || other.cache_creation_ratio || 1.0,
               )
             : renderLogContent(
                 other?.model_ratio,
@@ -429,6 +433,10 @@ export const useLogsData = () => {
             other.cache_ratio || 1.0,
             other.cache_creation_tokens || 0,
             other.cache_creation_ratio || 1.0,
+            other.cache_creation_tokens_5m || 0,
+            other.cache_creation_ratio_5m || other.cache_creation_ratio || 1.0,
+            other.cache_creation_tokens_1h || 0,
+            other.cache_creation_ratio_1h || other.cache_creation_ratio || 1.0,
           );
         } else {
           content = renderModelPrice(

+ 5 - 1
web/src/i18n/locales/en.json

@@ -1516,6 +1516,10 @@
     "缓存倍率": "Cache ratio",
     "缓存创建 Tokens": "Cache Creation Tokens",
     "缓存创建: {{cacheCreationRatio}}": "Cache creation: {{cacheCreationRatio}}",
+    "缓存创建: 5m {{cacheCreationRatio5m}}": "Cache creation: 5m {{cacheCreationRatio5m}}",
+    "缓存创建: 1h {{cacheCreationRatio1h}}": "Cache creation: 1h {{cacheCreationRatio1h}}",
+    "缓存创建倍率 5m {{cacheCreationRatio5m}}": "Cache creation multiplier 5m {{cacheCreationRatio5m}}",
+    "缓存创建倍率 1h {{cacheCreationRatio1h}}": "Cache creation multiplier 1h {{cacheCreationRatio1h}}",
     "缓存创建价格:{{symbol}}{{price}} * {{ratio}} = {{symbol}}{{total}} / 1M tokens (缓存创建倍率: {{cacheCreationRatio}})": "Cache creation price: {{symbol}}{{price}} * {{ratio}} = {{symbol}}{{total}} / 1M tokens (Cache creation ratio: {{cacheCreationRatio}})",
     "编辑": "Edit",
     "编辑API": "Edit API",
@@ -2104,4 +2108,4 @@
     "统一的": "The Unified",
     "大模型接口网关": "LLM API Gateway"
   }
-}
+}

+ 4 - 0
web/src/i18n/locales/fr.json

@@ -1525,6 +1525,10 @@
     "缓存倍率": "Ratio de cache",
     "缓存创建 Tokens": "Jetons de création de cache",
     "缓存创建: {{cacheCreationRatio}}": "Création de cache : {{cacheCreationRatio}}",
+    "缓存创建: 5m {{cacheCreationRatio5m}}": "Création de cache : 5m {{cacheCreationRatio5m}}",
+    "缓存创建: 1h {{cacheCreationRatio1h}}": "Création de cache : 1h {{cacheCreationRatio1h}}",
+    "缓存创建倍率 5m {{cacheCreationRatio5m}}": "Multiplicateur de création de cache 5m {{cacheCreationRatio5m}}",
+    "缓存创建倍率 1h {{cacheCreationRatio1h}}": "Multiplicateur de création de cache 1h {{cacheCreationRatio1h}}",
     "缓存创建价格:{{symbol}}{{price}} * {{ratio}} = {{symbol}}{{total}} / 1M tokens (缓存创建倍率: {{cacheCreationRatio}})": "Prix de création du cache : {{symbol}}{{price}} * {{ratio}} = {{symbol}}{{total}} / 1M tokens (taux de création de cache : {{cacheCreationRatio}})",
     "编辑": "Modifier",
     "编辑API": "Modifier l'API",

+ 5 - 1
web/src/i18n/locales/ja.json

@@ -1516,6 +1516,10 @@
     "缓存倍率": "キャッシュ倍率",
     "缓存创建 Tokens": "キャッシュ作成トークン",
     "缓存创建: {{cacheCreationRatio}}": "キャッシュ作成:{{cacheCreationRatio}}",
+    "缓存创建: 5m {{cacheCreationRatio5m}}": "キャッシュ作成:5m {{cacheCreationRatio5m}}",
+    "缓存创建: 1h {{cacheCreationRatio1h}}": "キャッシュ作成:1h {{cacheCreationRatio1h}}",
+    "缓存创建倍率 5m {{cacheCreationRatio5m}}": "キャッシュ作成倍率 5m {{cacheCreationRatio5m}}",
+    "缓存创建倍率 1h {{cacheCreationRatio1h}}": "キャッシュ作成倍率 1h {{cacheCreationRatio1h}}",
     "缓存创建价格:{{symbol}}{{price}} * {{ratio}} = {{symbol}}{{total}} / 1M tokens (缓存创建倍率: {{cacheCreationRatio}})": "キャッシュ作成料金:{{symbol}}{{price}} * {{ratio}} = {{symbol}}{{total}} / 1Mtokens(キャッシュ作成倍率:{{cacheCreationRatio}})",
     "编辑": "編集",
     "编辑API": "API編集",
@@ -2075,4 +2079,4 @@
     "统一的": "統合型",
     "大模型接口网关": "LLM APIゲートウェイ"
   }
-}
+}

+ 4 - 0
web/src/i18n/locales/ru.json

@@ -1534,6 +1534,10 @@
     "缓存倍率": "Коэффициент кэширования",
     "缓存创建 Tokens": "Создание кэша токенов",
     "缓存创建: {{cacheCreationRatio}}": "Создание кэша: {{cacheCreationRatio}}",
+    "缓存创建: 5m {{cacheCreationRatio5m}}": "Создание кэша: 5m {{cacheCreationRatio5m}}",
+    "缓存创建: 1h {{cacheCreationRatio1h}}": "Создание кэша: 1h {{cacheCreationRatio1h}}",
+    "缓存创建倍率 5m {{cacheCreationRatio5m}}": "Множитель создания кэша 5m {{cacheCreationRatio5m}}",
+    "缓存创建倍率 1h {{cacheCreationRatio1h}}": "Множитель создания кэша 1h {{cacheCreationRatio1h}}",
     "缓存创建价格:{{symbol}}{{price}} * {{ratio}} = {{symbol}}{{total}} / 1M tokens (缓存创建倍率: {{cacheCreationRatio}})": "Цена создания кэша: {{symbol}}{{price}} * {{ratio}} = {{symbol}}{{total}} / 1M токенов (коэффициент создания кэша: {{cacheCreationRatio}})",
     "编辑": "Редактировать",
     "编辑API": "Редактировать API",

+ 5 - 1
web/src/i18n/locales/zh.json

@@ -1507,6 +1507,10 @@
     "缓存倍率": "缓存倍率",
     "缓存创建 Tokens": "缓存创建 Tokens",
     "缓存创建: {{cacheCreationRatio}}": "缓存创建: {{cacheCreationRatio}}",
+    "缓存创建: 5m {{cacheCreationRatio5m}}": "缓存创建: 5m {{cacheCreationRatio5m}}",
+    "缓存创建: 1h {{cacheCreationRatio1h}}": "缓存创建: 1h {{cacheCreationRatio1h}}",
+    "缓存创建倍率 5m {{cacheCreationRatio5m}}": "缓存创建倍率 5m {{cacheCreationRatio5m}}",
+    "缓存创建倍率 1h {{cacheCreationRatio1h}}": "缓存创建倍率 1h {{cacheCreationRatio1h}}",
     "缓存创建价格:{{symbol}}{{price}} * {{ratio}} = {{symbol}}{{total}} / 1M tokens (缓存创建倍率: {{cacheCreationRatio}})": "缓存创建价格:{{symbol}}{{price}} * {{ratio}} = {{symbol}}{{total}} / 1M tokens (缓存创建倍率: {{cacheCreationRatio}})",
     "编辑": "编辑",
     "编辑API": "编辑API",
@@ -2066,4 +2070,4 @@
     "Creem 介绍": "Creem 是一个简单的支付处理平台,支持固定金额产品销售,以及订阅销售。",
     "Creem Setting Tips": "Creem 只支持预设的固定金额产品,这产品以及价格需要提前在Creem网站内创建配置,所以不支持自定义动态金额充值。在Creem端配置产品的名字以及价格,获取Product Id 后填到下面的产品,在new-api为该产品设置充值额度,以及展示价格。"
   }
-}
+}