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
+30 -8
View File
@@ -245,6 +245,8 @@ type aiAnalysisResult struct {
Content string
InputTokens int
OutputTokens int
CacheHitTokens int
CacheMissTokens int
InputSizeBytes int
OutputSizeBytes int
}
@@ -289,10 +291,16 @@ func callAIForAnalysis(prompt string) (*aiAnalysisResult, error) {
}
content := resp.Choices[0].Message.Content
cacheHitTokens := 0
if resp.Usage.PromptTokensDetails != nil {
cacheHitTokens = resp.Usage.PromptTokensDetails.CachedTokens
}
return &aiAnalysisResult{
Content: content,
InputTokens: resp.Usage.PromptTokens,
OutputTokens: resp.Usage.CompletionTokens,
CacheHitTokens: cacheHitTokens,
CacheMissTokens: resp.Usage.PromptTokens - cacheHitTokens,
InputSizeBytes: len(prompt),
OutputSizeBytes: len(content),
}, nil
@@ -397,17 +405,29 @@ func (tc *TrainingController) AnalyzeByAI(c *gin.Context) {
var costJSON string
var totalCost float64
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
totalCost = inputCost + outputCost
totalCost = cacheHitCost + cacheMissCost + outputCost
costInfo := map[string]interface{}{
"pricingName": pricing.Name,
"provider": pricing.Provider,
"inputPricePerMillion": pricing.InputPricePerMillion,
"outputPricePerMillion": pricing.OutputPricePerMillion,
"inputCost": inputCost,
"outputCost": outputCost,
"pricingName": pricing.Name,
"provider": pricing.Provider,
"inputPricePerMillion": pricing.InputPricePerMillion,
"cacheHitPricePerMillion": cacheHitPrice,
"cacheMissPricePerMillion": cacheMissPrice,
"outputPricePerMillion": pricing.OutputPricePerMillion,
"cacheHitCost": cacheHitCost,
"cacheMissCost": cacheMissCost,
"outputCost": outputCost,
}
if b, err := json.Marshal(costInfo); err == nil {
costJSON = string(b)
@@ -423,6 +443,8 @@ func (tc *TrainingController) AnalyzeByAI(c *gin.Context) {
TotalCost: totalCost,
InputTokens: analysisResult.InputTokens,
OutputTokens: analysisResult.OutputTokens,
CacheHitTokens: analysisResult.CacheHitTokens,
CacheMissTokens: analysisResult.CacheMissTokens,
InputSizeBytes: analysisResult.InputSizeBytes,
OutputSizeBytes: analysisResult.OutputSizeBytes,
DurationMs: durationMs,
+47 -17
View File
@@ -117,6 +117,8 @@ type regionStatisticsItem struct {
Count int64 `json:"count"`
TotalInputTokens int64 `json:"totalInputTokens"`
TotalOutputTokens int64 `json:"totalOutputTokens"`
TotalCacheHitTokens int64 `json:"totalCacheHitTokens"`
TotalCacheMissTokens int64 `json:"totalCacheMissTokens"`
TotalInputSizeBytes int64 `json:"totalInputSizeBytes"`
TotalOutputSizeBytes int64 `json:"totalOutputSizeBytes"`
TotalDurationMs int64 `json:"totalDurationMs"`
@@ -127,6 +129,8 @@ type regionStatisticsItem struct {
TotalCost float64 `json:"totalCost"`
TotalInputCost float64 `json:"totalInputCost"`
TotalOutputCost float64 `json:"totalOutputCost"`
TotalCacheHitCost float64 `json:"totalCacheHitCost"`
TotalCacheMissCost float64 `json:"totalCacheMissCost"`
AnalysisTypeCounts map[string]int64 `json:"analysisTypeCounts"`
SourceTypeCounts map[string]int64 `json:"sourceTypeCounts"`
FirstUsedAt *time.Time `json:"firstUsedAt"`
@@ -160,6 +164,8 @@ func (sc *StatisticsController) StatisticsByRegion(c *gin.Context) {
Count int64
TotalInputTokens int64
TotalOutputTokens int64
TotalCacheHitTokens int64
TotalCacheMissTokens int64
TotalInputSizeBytes int64
TotalOutputSizeBytes int64
TotalDurationMs int64
@@ -168,6 +174,8 @@ func (sc *StatisticsController) StatisticsByRegion(c *gin.Context) {
TotalCost float64
TotalInputCost float64
TotalOutputCost float64
TotalCacheHitCost float64
TotalCacheMissCost float64
FirstUsedAt *int64
LastUsedAt *int64
}
@@ -178,14 +186,18 @@ func (sc *StatisticsController) StatisticsByRegion(c *gin.Context) {
COUNT(*) as count,
COALESCE(SUM(input_tokens), 0) as total_input_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(output_size_bytes), 0) as total_output_size_bytes,
COALESCE(SUM(duration_ms), 0) as total_duration_ms,
COALESCE(SUM(original_file_size), 0) as total_original_file_size,
COALESCE(SUM(compressed_content_size), 0) as total_compressed_size,
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->>'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,
MAX(upload_time) as last_used_at
`).Group("region_id").Scan(&rawResults).Error
@@ -298,6 +310,8 @@ func (sc *StatisticsController) StatisticsByRegion(c *gin.Context) {
Count: r.Count,
TotalInputTokens: r.TotalInputTokens,
TotalOutputTokens: r.TotalOutputTokens,
TotalCacheHitTokens: r.TotalCacheHitTokens,
TotalCacheMissTokens: r.TotalCacheMissTokens,
TotalInputSizeBytes: r.TotalInputSizeBytes,
TotalOutputSizeBytes: r.TotalOutputSizeBytes,
TotalDurationMs: r.TotalDurationMs,
@@ -308,6 +322,8 @@ func (sc *StatisticsController) StatisticsByRegion(c *gin.Context) {
TotalCost: r.TotalCost,
TotalInputCost: r.TotalInputCost,
TotalOutputCost: r.TotalOutputCost,
TotalCacheHitCost: r.TotalCacheHitCost,
TotalCacheMissCost: r.TotalCacheMissCost,
AnalysisTypeCounts: analysisTypeMap[regionID],
SourceTypeCounts: sourceTypeMap[regionID],
FirstUsedAt: firstUsedAt,
@@ -319,6 +335,8 @@ func (sc *StatisticsController) StatisticsByRegion(c *gin.Context) {
overall.Count += r.Count
overall.TotalInputTokens += r.TotalInputTokens
overall.TotalOutputTokens += r.TotalOutputTokens
overall.TotalCacheHitTokens += r.TotalCacheHitTokens
overall.TotalCacheMissTokens += r.TotalCacheMissTokens
overall.TotalInputSizeBytes += r.TotalInputSizeBytes
overall.TotalOutputSizeBytes += r.TotalOutputSizeBytes
overall.TotalDurationMs += r.TotalDurationMs
@@ -327,6 +345,8 @@ func (sc *StatisticsController) StatisticsByRegion(c *gin.Context) {
overall.TotalCost += r.TotalCost
overall.TotalInputCost += r.TotalInputCost
overall.TotalOutputCost += r.TotalOutputCost
overall.TotalCacheHitCost += r.TotalCacheHitCost
overall.TotalCacheMissCost += r.TotalCacheMissCost
if firstUsedAt != nil {
if overall.FirstUsedAt == nil || firstUsedAt.Before(*overall.FirstUsedAt) {
@@ -627,20 +647,24 @@ func (sc *StatisticsController) TimelineStatistics(c *gin.Context) {
}
type timelineItem struct {
Date string `json:"date"`
Count int64 `json:"count"`
InputTokens int64 `json:"inputTokens"`
OutputTokens int64 `json:"outputTokens"`
TotalCost float64 `json:"totalCost"`
Date string `json:"date"`
Count int64 `json:"count"`
InputTokens int64 `json:"inputTokens"`
OutputTokens int64 `json:"outputTokens"`
CacheHitTokens int64 `json:"cacheHitTokens"`
CacheMissTokens int64 `json:"cacheMissTokens"`
TotalCost float64 `json:"totalCost"`
}
type rawRegionTimeline struct {
RegionID *uint32
Date string
Count int64
InputTokens int64
OutputTokens int64
TotalCost float64
RegionID *uint32
Date string
Count int64
InputTokens int64
OutputTokens int64
CacheHitTokens int64
CacheMissTokens int64
TotalCost float64
}
var rawResults []rawRegionTimeline
@@ -650,6 +674,8 @@ func (sc *StatisticsController) TimelineStatistics(c *gin.Context) {
COUNT(*) as count,
COALESCE(SUM(input_tokens), 0) as input_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
`).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].InputTokens += r.InputTokens
overallMap[r.Date].OutputTokens += r.OutputTokens
overallMap[r.Date].CacheHitTokens += r.CacheHitTokens
overallMap[r.Date].CacheMissTokens += r.CacheMissTokens
overallMap[r.Date].TotalCost += r.TotalCost
regionID := uint32(0)
@@ -678,11 +706,13 @@ func (sc *StatisticsController) TimelineStatistics(c *gin.Context) {
}
regionIDStr := strconv.FormatUint(uint64(regionID), 10)
regionItemsMap[regionIDStr] = append(regionItemsMap[regionIDStr], timelineItem{
Date: r.Date,
Count: r.Count,
InputTokens: r.InputTokens,
OutputTokens: r.OutputTokens,
TotalCost: r.TotalCost,
Date: r.Date,
Count: r.Count,
InputTokens: r.InputTokens,
OutputTokens: r.OutputTokens,
CacheHitTokens: r.CacheHitTokens,
CacheMissTokens: r.CacheMissTokens,
TotalCost: r.TotalCost,
})
if _, ok := regionIDSet[regionID]; !ok && regionID > 0 {
regionIDSet[regionID] = struct{}{}