feat: cache token stat.

This commit is contained in:
2026-05-02 10:13:33 +08:00
parent 6ef84b7488
commit f81ee19bed
4 changed files with 91 additions and 33 deletions
+25 -3
View File
@@ -245,6 +245,8 @@ type aiAnalysisResult struct {
Content string Content string
InputTokens int InputTokens int
OutputTokens int OutputTokens int
CacheHitTokens int
CacheMissTokens int
InputSizeBytes int InputSizeBytes int
OutputSizeBytes int OutputSizeBytes int
} }
@@ -289,10 +291,16 @@ func callAIForAnalysis(prompt string) (*aiAnalysisResult, error) {
} }
content := resp.Choices[0].Message.Content content := resp.Choices[0].Message.Content
cacheHitTokens := 0
if resp.Usage.PromptTokensDetails != nil {
cacheHitTokens = resp.Usage.PromptTokensDetails.CachedTokens
}
return &aiAnalysisResult{ return &aiAnalysisResult{
Content: content, Content: content,
InputTokens: resp.Usage.PromptTokens, InputTokens: resp.Usage.PromptTokens,
OutputTokens: resp.Usage.CompletionTokens, OutputTokens: resp.Usage.CompletionTokens,
CacheHitTokens: cacheHitTokens,
CacheMissTokens: resp.Usage.PromptTokens - cacheHitTokens,
InputSizeBytes: len(prompt), InputSizeBytes: len(prompt),
OutputSizeBytes: len(content), OutputSizeBytes: len(content),
}, nil }, nil
@@ -397,16 +405,28 @@ func (tc *TrainingController) AnalyzeByAI(c *gin.Context) {
var costJSON string var costJSON string
var totalCost float64 var totalCost float64
if err := config.DB.First(&pricing).Error; err == nil { if err := config.DB.First(&pricing).Error; err == nil {
inputCost := float64(analysisResult.InputTokens) * pricing.InputPricePerMillion / 1_000_000 cacheMissPrice := pricing.CacheMissPricePerMillion
if cacheMissPrice == 0 {
cacheMissPrice = pricing.InputPricePerMillion
}
cacheHitPrice := pricing.CacheHitPricePerMillion
if cacheHitPrice == 0 {
cacheHitPrice = pricing.InputPricePerMillion
}
cacheHitCost := float64(analysisResult.CacheHitTokens) * cacheHitPrice / 1_000_000
cacheMissCost := float64(analysisResult.CacheMissTokens) * cacheMissPrice / 1_000_000
outputCost := float64(analysisResult.OutputTokens) * pricing.OutputPricePerMillion / 1_000_000 outputCost := float64(analysisResult.OutputTokens) * pricing.OutputPricePerMillion / 1_000_000
totalCost = inputCost + outputCost totalCost = cacheHitCost + cacheMissCost + outputCost
costInfo := map[string]interface{}{ costInfo := map[string]interface{}{
"pricingName": pricing.Name, "pricingName": pricing.Name,
"provider": pricing.Provider, "provider": pricing.Provider,
"inputPricePerMillion": pricing.InputPricePerMillion, "inputPricePerMillion": pricing.InputPricePerMillion,
"cacheHitPricePerMillion": cacheHitPrice,
"cacheMissPricePerMillion": cacheMissPrice,
"outputPricePerMillion": pricing.OutputPricePerMillion, "outputPricePerMillion": pricing.OutputPricePerMillion,
"inputCost": inputCost, "cacheHitCost": cacheHitCost,
"cacheMissCost": cacheMissCost,
"outputCost": outputCost, "outputCost": outputCost,
} }
if b, err := json.Marshal(costInfo); err == nil { if b, err := json.Marshal(costInfo); err == nil {
@@ -423,6 +443,8 @@ func (tc *TrainingController) AnalyzeByAI(c *gin.Context) {
TotalCost: totalCost, TotalCost: totalCost,
InputTokens: analysisResult.InputTokens, InputTokens: analysisResult.InputTokens,
OutputTokens: analysisResult.OutputTokens, OutputTokens: analysisResult.OutputTokens,
CacheHitTokens: analysisResult.CacheHitTokens,
CacheMissTokens: analysisResult.CacheMissTokens,
InputSizeBytes: analysisResult.InputSizeBytes, InputSizeBytes: analysisResult.InputSizeBytes,
OutputSizeBytes: analysisResult.OutputSizeBytes, OutputSizeBytes: analysisResult.OutputSizeBytes,
DurationMs: durationMs, DurationMs: durationMs,
+31 -1
View File
@@ -117,6 +117,8 @@ type regionStatisticsItem struct {
Count int64 `json:"count"` Count int64 `json:"count"`
TotalInputTokens int64 `json:"totalInputTokens"` TotalInputTokens int64 `json:"totalInputTokens"`
TotalOutputTokens int64 `json:"totalOutputTokens"` TotalOutputTokens int64 `json:"totalOutputTokens"`
TotalCacheHitTokens int64 `json:"totalCacheHitTokens"`
TotalCacheMissTokens int64 `json:"totalCacheMissTokens"`
TotalInputSizeBytes int64 `json:"totalInputSizeBytes"` TotalInputSizeBytes int64 `json:"totalInputSizeBytes"`
TotalOutputSizeBytes int64 `json:"totalOutputSizeBytes"` TotalOutputSizeBytes int64 `json:"totalOutputSizeBytes"`
TotalDurationMs int64 `json:"totalDurationMs"` TotalDurationMs int64 `json:"totalDurationMs"`
@@ -127,6 +129,8 @@ type regionStatisticsItem struct {
TotalCost float64 `json:"totalCost"` TotalCost float64 `json:"totalCost"`
TotalInputCost float64 `json:"totalInputCost"` TotalInputCost float64 `json:"totalInputCost"`
TotalOutputCost float64 `json:"totalOutputCost"` TotalOutputCost float64 `json:"totalOutputCost"`
TotalCacheHitCost float64 `json:"totalCacheHitCost"`
TotalCacheMissCost float64 `json:"totalCacheMissCost"`
AnalysisTypeCounts map[string]int64 `json:"analysisTypeCounts"` AnalysisTypeCounts map[string]int64 `json:"analysisTypeCounts"`
SourceTypeCounts map[string]int64 `json:"sourceTypeCounts"` SourceTypeCounts map[string]int64 `json:"sourceTypeCounts"`
FirstUsedAt *time.Time `json:"firstUsedAt"` FirstUsedAt *time.Time `json:"firstUsedAt"`
@@ -160,6 +164,8 @@ func (sc *StatisticsController) StatisticsByRegion(c *gin.Context) {
Count int64 Count int64
TotalInputTokens int64 TotalInputTokens int64
TotalOutputTokens int64 TotalOutputTokens int64
TotalCacheHitTokens int64
TotalCacheMissTokens int64
TotalInputSizeBytes int64 TotalInputSizeBytes int64
TotalOutputSizeBytes int64 TotalOutputSizeBytes int64
TotalDurationMs int64 TotalDurationMs int64
@@ -168,6 +174,8 @@ func (sc *StatisticsController) StatisticsByRegion(c *gin.Context) {
TotalCost float64 TotalCost float64
TotalInputCost float64 TotalInputCost float64
TotalOutputCost float64 TotalOutputCost float64
TotalCacheHitCost float64
TotalCacheMissCost float64
FirstUsedAt *int64 FirstUsedAt *int64
LastUsedAt *int64 LastUsedAt *int64
} }
@@ -178,14 +186,18 @@ func (sc *StatisticsController) StatisticsByRegion(c *gin.Context) {
COUNT(*) as count, COUNT(*) as count,
COALESCE(SUM(input_tokens), 0) as total_input_tokens, COALESCE(SUM(input_tokens), 0) as total_input_tokens,
COALESCE(SUM(output_tokens), 0) as total_output_tokens, COALESCE(SUM(output_tokens), 0) as total_output_tokens,
COALESCE(SUM(cache_hit_tokens), 0) as total_cache_hit_tokens,
COALESCE(SUM(cache_miss_tokens), 0) as total_cache_miss_tokens,
COALESCE(SUM(input_size_bytes), 0) as total_input_size_bytes, COALESCE(SUM(input_size_bytes), 0) as total_input_size_bytes,
COALESCE(SUM(output_size_bytes), 0) as total_output_size_bytes, COALESCE(SUM(output_size_bytes), 0) as total_output_size_bytes,
COALESCE(SUM(duration_ms), 0) as total_duration_ms, COALESCE(SUM(duration_ms), 0) as total_duration_ms,
COALESCE(SUM(original_file_size), 0) as total_original_file_size, COALESCE(SUM(original_file_size), 0) as total_original_file_size,
COALESCE(SUM(compressed_content_size), 0) as total_compressed_size, COALESCE(SUM(compressed_content_size), 0) as total_compressed_size,
COALESCE(SUM(total_cost), 0) as total_cost, COALESCE(SUM(total_cost), 0) as total_cost,
COALESCE(SUM((cost_json::jsonb->>'inputCost')::float8), 0) as total_input_cost, COALESCE(SUM((cost_json::jsonb->>'cacheHitCost')::float8 + (cost_json::jsonb->>'cacheMissCost')::float8), 0) as total_input_cost,
COALESCE(SUM((cost_json::jsonb->>'outputCost')::float8), 0) as total_output_cost, COALESCE(SUM((cost_json::jsonb->>'outputCost')::float8), 0) as total_output_cost,
COALESCE(SUM((cost_json::jsonb->>'cacheHitCost')::float8), 0) as total_cache_hit_cost,
COALESCE(SUM((cost_json::jsonb->>'cacheMissCost')::float8), 0) as total_cache_miss_cost,
MIN(upload_time) as first_used_at, MIN(upload_time) as first_used_at,
MAX(upload_time) as last_used_at MAX(upload_time) as last_used_at
`).Group("region_id").Scan(&rawResults).Error `).Group("region_id").Scan(&rawResults).Error
@@ -298,6 +310,8 @@ func (sc *StatisticsController) StatisticsByRegion(c *gin.Context) {
Count: r.Count, Count: r.Count,
TotalInputTokens: r.TotalInputTokens, TotalInputTokens: r.TotalInputTokens,
TotalOutputTokens: r.TotalOutputTokens, TotalOutputTokens: r.TotalOutputTokens,
TotalCacheHitTokens: r.TotalCacheHitTokens,
TotalCacheMissTokens: r.TotalCacheMissTokens,
TotalInputSizeBytes: r.TotalInputSizeBytes, TotalInputSizeBytes: r.TotalInputSizeBytes,
TotalOutputSizeBytes: r.TotalOutputSizeBytes, TotalOutputSizeBytes: r.TotalOutputSizeBytes,
TotalDurationMs: r.TotalDurationMs, TotalDurationMs: r.TotalDurationMs,
@@ -308,6 +322,8 @@ func (sc *StatisticsController) StatisticsByRegion(c *gin.Context) {
TotalCost: r.TotalCost, TotalCost: r.TotalCost,
TotalInputCost: r.TotalInputCost, TotalInputCost: r.TotalInputCost,
TotalOutputCost: r.TotalOutputCost, TotalOutputCost: r.TotalOutputCost,
TotalCacheHitCost: r.TotalCacheHitCost,
TotalCacheMissCost: r.TotalCacheMissCost,
AnalysisTypeCounts: analysisTypeMap[regionID], AnalysisTypeCounts: analysisTypeMap[regionID],
SourceTypeCounts: sourceTypeMap[regionID], SourceTypeCounts: sourceTypeMap[regionID],
FirstUsedAt: firstUsedAt, FirstUsedAt: firstUsedAt,
@@ -319,6 +335,8 @@ func (sc *StatisticsController) StatisticsByRegion(c *gin.Context) {
overall.Count += r.Count overall.Count += r.Count
overall.TotalInputTokens += r.TotalInputTokens overall.TotalInputTokens += r.TotalInputTokens
overall.TotalOutputTokens += r.TotalOutputTokens overall.TotalOutputTokens += r.TotalOutputTokens
overall.TotalCacheHitTokens += r.TotalCacheHitTokens
overall.TotalCacheMissTokens += r.TotalCacheMissTokens
overall.TotalInputSizeBytes += r.TotalInputSizeBytes overall.TotalInputSizeBytes += r.TotalInputSizeBytes
overall.TotalOutputSizeBytes += r.TotalOutputSizeBytes overall.TotalOutputSizeBytes += r.TotalOutputSizeBytes
overall.TotalDurationMs += r.TotalDurationMs overall.TotalDurationMs += r.TotalDurationMs
@@ -327,6 +345,8 @@ func (sc *StatisticsController) StatisticsByRegion(c *gin.Context) {
overall.TotalCost += r.TotalCost overall.TotalCost += r.TotalCost
overall.TotalInputCost += r.TotalInputCost overall.TotalInputCost += r.TotalInputCost
overall.TotalOutputCost += r.TotalOutputCost overall.TotalOutputCost += r.TotalOutputCost
overall.TotalCacheHitCost += r.TotalCacheHitCost
overall.TotalCacheMissCost += r.TotalCacheMissCost
if firstUsedAt != nil { if firstUsedAt != nil {
if overall.FirstUsedAt == nil || firstUsedAt.Before(*overall.FirstUsedAt) { if overall.FirstUsedAt == nil || firstUsedAt.Before(*overall.FirstUsedAt) {
@@ -631,6 +651,8 @@ func (sc *StatisticsController) TimelineStatistics(c *gin.Context) {
Count int64 `json:"count"` Count int64 `json:"count"`
InputTokens int64 `json:"inputTokens"` InputTokens int64 `json:"inputTokens"`
OutputTokens int64 `json:"outputTokens"` OutputTokens int64 `json:"outputTokens"`
CacheHitTokens int64 `json:"cacheHitTokens"`
CacheMissTokens int64 `json:"cacheMissTokens"`
TotalCost float64 `json:"totalCost"` TotalCost float64 `json:"totalCost"`
} }
@@ -640,6 +662,8 @@ func (sc *StatisticsController) TimelineStatistics(c *gin.Context) {
Count int64 Count int64
InputTokens int64 InputTokens int64
OutputTokens int64 OutputTokens int64
CacheHitTokens int64
CacheMissTokens int64
TotalCost float64 TotalCost float64
} }
@@ -650,6 +674,8 @@ func (sc *StatisticsController) TimelineStatistics(c *gin.Context) {
COUNT(*) as count, COUNT(*) as count,
COALESCE(SUM(input_tokens), 0) as input_tokens, COALESCE(SUM(input_tokens), 0) as input_tokens,
COALESCE(SUM(output_tokens), 0) as output_tokens, COALESCE(SUM(output_tokens), 0) as output_tokens,
COALESCE(SUM(cache_hit_tokens), 0) as cache_hit_tokens,
COALESCE(SUM(cache_miss_tokens), 0) as cache_miss_tokens,
COALESCE(SUM(total_cost), 0) as total_cost COALESCE(SUM(total_cost), 0) as total_cost
`).Group("region_id, DATE(TO_TIMESTAMP(upload_time / 1000.0))").Order("region_id, date ASC").Scan(&rawResults).Error `).Group("region_id, DATE(TO_TIMESTAMP(upload_time / 1000.0))").Order("region_id, date ASC").Scan(&rawResults).Error
@@ -670,6 +696,8 @@ func (sc *StatisticsController) TimelineStatistics(c *gin.Context) {
overallMap[r.Date].Count += r.Count overallMap[r.Date].Count += r.Count
overallMap[r.Date].InputTokens += r.InputTokens overallMap[r.Date].InputTokens += r.InputTokens
overallMap[r.Date].OutputTokens += r.OutputTokens overallMap[r.Date].OutputTokens += r.OutputTokens
overallMap[r.Date].CacheHitTokens += r.CacheHitTokens
overallMap[r.Date].CacheMissTokens += r.CacheMissTokens
overallMap[r.Date].TotalCost += r.TotalCost overallMap[r.Date].TotalCost += r.TotalCost
regionID := uint32(0) regionID := uint32(0)
@@ -682,6 +710,8 @@ func (sc *StatisticsController) TimelineStatistics(c *gin.Context) {
Count: r.Count, Count: r.Count,
InputTokens: r.InputTokens, InputTokens: r.InputTokens,
OutputTokens: r.OutputTokens, OutputTokens: r.OutputTokens,
CacheHitTokens: r.CacheHitTokens,
CacheMissTokens: r.CacheMissTokens,
TotalCost: r.TotalCost, TotalCost: r.TotalCost,
}) })
if _, ok := regionIDSet[regionID]; !ok && regionID > 0 { if _, ok := regionIDSet[regionID]; !ok && regionID > 0 {
+2
View File
@@ -16,6 +16,8 @@ type AIAnalysisRecord struct {
OutputTokens int `json:"outputTokens"` OutputTokens int `json:"outputTokens"`
InputSizeBytes int `json:"inputSizeBytes"` InputSizeBytes int `json:"inputSizeBytes"`
OutputSizeBytes int `json:"outputSizeBytes"` OutputSizeBytes int `json:"outputSizeBytes"`
CacheHitTokens int `json:"cacheHitTokens"`
CacheMissTokens int `json:"cacheMissTokens"`
DurationMs int64 `json:"durationMs"` DurationMs int64 `json:"durationMs"`
OriginalFileSize int64 `json:"originalFileSize"` OriginalFileSize int64 `json:"originalFileSize"`
CompressedContentSize int64 `json:"compressedContentSize"` CompressedContentSize int64 `json:"compressedContentSize"`
+4
View File
@@ -10,6 +10,8 @@ type AIPricingConfig struct {
Provider string `gorm:"size:64" json:"provider"` Provider string `gorm:"size:64" json:"provider"`
InputPricePerMillion float64 `json:"inputPricePerMillion"` InputPricePerMillion float64 `json:"inputPricePerMillion"`
OutputPricePerMillion float64 `json:"outputPricePerMillion"` OutputPricePerMillion float64 `json:"outputPricePerMillion"`
CacheHitPricePerMillion float64 `json:"cacheHitPricePerMillion"`
CacheMissPricePerMillion float64 `json:"cacheMissPricePerMillion"`
} }
func EnsureDefaultAIPricing(db *gorm.DB) error { func EnsureDefaultAIPricing(db *gorm.DB) error {
@@ -24,6 +26,8 @@ func EnsureDefaultAIPricing(db *gorm.DB) error {
Name: "deepseek-v4-flash", Name: "deepseek-v4-flash",
Provider: "tencentmaas", Provider: "tencentmaas",
InputPricePerMillion: 1, InputPricePerMillion: 1,
CacheHitPricePerMillion: 1,
CacheMissPricePerMillion: 0.02,
OutputPricePerMillion: 2, OutputPricePerMillion: 2,
}).Error }).Error
} }