Merge pull request #7001 from SavitarC/feat/ops-token-stats-all-platforms

feat(ops): 扩展 Token 请求统计至所有平台
This commit is contained in:
Wesley Liddick
2026-09-12 19:34:45 +08:00
committed by GitHub
7 changed files with 169 additions and 13 deletions
@@ -219,7 +219,8 @@ func (h *OpsHandler) GetDashboardErrorDistribution(c *gin.Context) {
response.Success(c, data)
}
// GetDashboardOpenAITokenStats returns OpenAI token efficiency stats grouped by model.
// GetDashboardOpenAITokenStats returns token efficiency stats for all platforms, grouped by model.
// The endpoint name is retained for compatibility; platform and group_id optionally narrow the scope.
// GET /api/v1/admin/ops/dashboard/openai-token-stats
func (h *OpsHandler) GetDashboardOpenAITokenStats(c *gin.Context) {
if h.opsService == nil {
@@ -32,7 +32,6 @@ func (r *opsRepository) GetOpenAITokenStats(ctx context.Context, filter *service
}
join, where, baseArgs, next := buildUsageWhere(dashboardFilter, dashboardFilter.StartTime, dashboardFilter.EndTime, 1)
where += " AND ul.model LIKE 'gpt%'"
baseCTE := `
WITH stats AS (
@@ -2,6 +2,8 @@ package repository
import (
"context"
"database/sql/driver"
"regexp"
"testing"
"time"
@@ -10,6 +12,81 @@ import (
"github.com/stretchr/testify/require"
)
func TestOpsRepositoryGetOpenAITokenStats_PlatformScope(t *testing.T) {
start := time.Date(2026, 1, 1, 0, 0, 0, 0, time.UTC)
end := start.Add(time.Hour)
groupID := int64(9)
tests := []struct {
name string
platform string
groupID *int64
models []string
}{
{name: "all platforms", models: []string{"claude-sonnet-4", "deepseek-chat", "gemini-2.5-pro", "gpt-4o", "o3"}},
{name: "anthropic group", platform: "anthropic", groupID: &groupID, models: []string{"claude-sonnet-4"}},
{name: "gemini", platform: "gemini", models: []string{"gemini-2.5-pro"}},
{name: "openai reasoning model", platform: "openai", models: []string{"o3"}},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
db, mock := newSQLMock(t)
repo := &opsRepository{db: db}
filter := &service.OpsOpenAITokenStatsFilter{
TimeRange: "1h",
StartTime: start,
EndTime: end,
Platform: tt.platform,
GroupID: tt.groupID,
TopN: 20,
}
// Match the complete filtering clause in both queries so an implicit
// model restriction cannot silently narrow the selected platform scope.
where := "WHERE ul.created_at >= $1 AND ul.created_at < $2"
args := []driver.Value{start, end}
if tt.groupID != nil {
where += " AND ul.group_id = $3"
args = append(args, *tt.groupID)
}
if tt.platform != "" {
placeholder := "$3"
if tt.groupID != nil {
placeholder = "$4"
}
where += " AND COALESCE(NULLIF(g.platform,''), a.platform) = " + placeholder
args = append(args, tt.platform)
}
queryPrefix := regexp.QuoteMeta(where) + `\s+GROUP BY ul.model\s+\)`
mock.ExpectQuery(queryPrefix + `\s+SELECT COUNT\(\*\) FROM stats`).
WithArgs(args...).
WillReturnRows(sqlmock.NewRows([]string{"count"}).AddRow(len(tt.models)))
rows := sqlmock.NewRows([]string{
"model", "request_count", "avg_tokens_per_sec", "avg_first_token_ms",
"total_output_tokens", "avg_duration_ms", "requests_with_first_token",
})
for _, model := range tt.models {
rows.AddRow(model, int64(2), 10.0, 100.0, int64(20), int64(1000), int64(2))
}
mock.ExpectQuery(queryPrefix + `\s+SELECT model,`).
WithArgs(append(args, 20)...).
WillReturnRows(rows)
resp, err := repo.GetOpenAITokenStats(context.Background(), filter)
require.NoError(t, err)
require.Equal(t, tt.platform, resp.Platform)
require.Equal(t, tt.groupID, resp.GroupID)
require.Equal(t, int64(len(tt.models)), resp.Total)
require.Len(t, resp.Items, len(tt.models))
for i, model := range tt.models {
require.Equal(t, model, resp.Items[i].Model)
}
require.NoError(t, mock.ExpectationsWereMet())
})
}
}
func TestOpsRepositoryGetOpenAITokenStats_PaginationMode(t *testing.T) {
db, mock := newSQLMock(t)
repo := &opsRepository{db: db}