feat: cache token stat.
This commit is contained in:
+30
-8
@@ -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,17 +405,29 @@ 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,
|
||||||
"outputPricePerMillion": pricing.OutputPricePerMillion,
|
"cacheHitPricePerMillion": cacheHitPrice,
|
||||||
"inputCost": inputCost,
|
"cacheMissPricePerMillion": cacheMissPrice,
|
||||||
"outputCost": outputCost,
|
"outputPricePerMillion": pricing.OutputPricePerMillion,
|
||||||
|
"cacheHitCost": cacheHitCost,
|
||||||
|
"cacheMissCost": cacheMissCost,
|
||||||
|
"outputCost": outputCost,
|
||||||
}
|
}
|
||||||
if b, err := json.Marshal(costInfo); err == nil {
|
if b, err := json.Marshal(costInfo); err == nil {
|
||||||
costJSON = string(b)
|
costJSON = string(b)
|
||||||
@@ -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,
|
||||||
|
|||||||
+47
-17
@@ -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) {
|
||||||
@@ -627,20 +647,24 @@ func (sc *StatisticsController) TimelineStatistics(c *gin.Context) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type timelineItem struct {
|
type timelineItem struct {
|
||||||
Date string `json:"date"`
|
Date string `json:"date"`
|
||||||
Count int64 `json:"count"`
|
Count int64 `json:"count"`
|
||||||
InputTokens int64 `json:"inputTokens"`
|
InputTokens int64 `json:"inputTokens"`
|
||||||
OutputTokens int64 `json:"outputTokens"`
|
OutputTokens int64 `json:"outputTokens"`
|
||||||
TotalCost float64 `json:"totalCost"`
|
CacheHitTokens int64 `json:"cacheHitTokens"`
|
||||||
|
CacheMissTokens int64 `json:"cacheMissTokens"`
|
||||||
|
TotalCost float64 `json:"totalCost"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type rawRegionTimeline struct {
|
type rawRegionTimeline struct {
|
||||||
RegionID *uint32
|
RegionID *uint32
|
||||||
Date string
|
Date string
|
||||||
Count int64
|
Count int64
|
||||||
InputTokens int64
|
InputTokens int64
|
||||||
OutputTokens int64
|
OutputTokens int64
|
||||||
TotalCost float64
|
CacheHitTokens int64
|
||||||
|
CacheMissTokens int64
|
||||||
|
TotalCost float64
|
||||||
}
|
}
|
||||||
|
|
||||||
var rawResults []rawRegionTimeline
|
var rawResults []rawRegionTimeline
|
||||||
@@ -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)
|
||||||
@@ -678,11 +706,13 @@ func (sc *StatisticsController) TimelineStatistics(c *gin.Context) {
|
|||||||
}
|
}
|
||||||
regionIDStr := strconv.FormatUint(uint64(regionID), 10)
|
regionIDStr := strconv.FormatUint(uint64(regionID), 10)
|
||||||
regionItemsMap[regionIDStr] = append(regionItemsMap[regionIDStr], timelineItem{
|
regionItemsMap[regionIDStr] = append(regionItemsMap[regionIDStr], timelineItem{
|
||||||
Date: r.Date,
|
Date: r.Date,
|
||||||
Count: r.Count,
|
Count: r.Count,
|
||||||
InputTokens: r.InputTokens,
|
InputTokens: r.InputTokens,
|
||||||
OutputTokens: r.OutputTokens,
|
OutputTokens: r.OutputTokens,
|
||||||
TotalCost: r.TotalCost,
|
CacheHitTokens: r.CacheHitTokens,
|
||||||
|
CacheMissTokens: r.CacheMissTokens,
|
||||||
|
TotalCost: r.TotalCost,
|
||||||
})
|
})
|
||||||
if _, ok := regionIDSet[regionID]; !ok && regionID > 0 {
|
if _, ok := regionIDSet[regionID]; !ok && regionID > 0 {
|
||||||
regionIDSet[regionID] = struct{}{}
|
regionIDSet[regionID] = struct{}{}
|
||||||
|
|||||||
@@ -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"`
|
||||||
|
|||||||
+12
-8
@@ -6,10 +6,12 @@ import (
|
|||||||
|
|
||||||
type AIPricingConfig struct {
|
type AIPricingConfig struct {
|
||||||
gorm.Model
|
gorm.Model
|
||||||
Name string `gorm:"size:64;uniqueIndex" json:"name"`
|
Name string `gorm:"size:64;uniqueIndex" json:"name"`
|
||||||
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 {
|
||||||
@@ -21,9 +23,11 @@ func EnsureDefaultAIPricing(db *gorm.DB) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
return db.Create(&AIPricingConfig{
|
return db.Create(&AIPricingConfig{
|
||||||
Name: "deepseek-v4-flash",
|
Name: "deepseek-v4-flash",
|
||||||
Provider: "tencentmaas",
|
Provider: "tencentmaas",
|
||||||
InputPricePerMillion: 1,
|
InputPricePerMillion: 1,
|
||||||
OutputPricePerMillion: 2,
|
CacheHitPricePerMillion: 1,
|
||||||
|
CacheMissPricePerMillion: 0.02,
|
||||||
|
OutputPricePerMillion: 2,
|
||||||
}).Error
|
}).Error
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user