feat: cache token stat.
This commit is contained in:
+30
-8
@@ -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
@@ -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{}{}
|
||||
|
||||
@@ -16,6 +16,8 @@ type AIAnalysisRecord struct {
|
||||
OutputTokens int `json:"outputTokens"`
|
||||
InputSizeBytes int `json:"inputSizeBytes"`
|
||||
OutputSizeBytes int `json:"outputSizeBytes"`
|
||||
CacheHitTokens int `json:"cacheHitTokens"`
|
||||
CacheMissTokens int `json:"cacheMissTokens"`
|
||||
DurationMs int64 `json:"durationMs"`
|
||||
OriginalFileSize int64 `json:"originalFileSize"`
|
||||
CompressedContentSize int64 `json:"compressedContentSize"`
|
||||
|
||||
+12
-8
@@ -6,10 +6,12 @@ import (
|
||||
|
||||
type AIPricingConfig struct {
|
||||
gorm.Model
|
||||
Name string `gorm:"size:64;uniqueIndex" json:"name"`
|
||||
Provider string `gorm:"size:64" json:"provider"`
|
||||
InputPricePerMillion float64 `json:"inputPricePerMillion"`
|
||||
OutputPricePerMillion float64 `json:"outputPricePerMillion"`
|
||||
Name string `gorm:"size:64;uniqueIndex" json:"name"`
|
||||
Provider string `gorm:"size:64" json:"provider"`
|
||||
InputPricePerMillion float64 `json:"inputPricePerMillion"`
|
||||
OutputPricePerMillion float64 `json:"outputPricePerMillion"`
|
||||
CacheHitPricePerMillion float64 `json:"cacheHitPricePerMillion"`
|
||||
CacheMissPricePerMillion float64 `json:"cacheMissPricePerMillion"`
|
||||
}
|
||||
|
||||
func EnsureDefaultAIPricing(db *gorm.DB) error {
|
||||
@@ -21,9 +23,11 @@ func EnsureDefaultAIPricing(db *gorm.DB) error {
|
||||
return nil
|
||||
}
|
||||
return db.Create(&AIPricingConfig{
|
||||
Name: "deepseek-v4-flash",
|
||||
Provider: "tencentmaas",
|
||||
InputPricePerMillion: 1,
|
||||
OutputPricePerMillion: 2,
|
||||
Name: "deepseek-v4-flash",
|
||||
Provider: "tencentmaas",
|
||||
InputPricePerMillion: 1,
|
||||
CacheHitPricePerMillion: 1,
|
||||
CacheMissPricePerMillion: 0.02,
|
||||
OutputPricePerMillion: 2,
|
||||
}).Error
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user