Skip to content

Commit 56fcb20

Browse files
committed
feat(api): expose model_source filter in dashboard endpoints
Add model_source query parameter to GetModelStats and GetUserBreakdown handlers with explicit IsValidModelSource validation. Include model_source in cache key to prevent cross-source cache hits. Expose upstream_model in usage log DTO with omitempty semantics.
1 parent 7134266 commit 56fcb20

5 files changed

Lines changed: 24 additions & 2 deletions

File tree

backend/internal/handler/admin/dashboard_handler.go

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -273,6 +273,7 @@ func (h *DashboardHandler) GetModelStats(c *gin.Context) {
273273

274274
// Parse optional filter params
275275
var userID, apiKeyID, accountID, groupID int64
276+
modelSource := usagestats.ModelSourceRequested
276277
var requestType *int16
277278
var stream *bool
278279
var billingType *int8
@@ -297,6 +298,13 @@ func (h *DashboardHandler) GetModelStats(c *gin.Context) {
297298
groupID = id
298299
}
299300
}
301+
if rawModelSource := strings.TrimSpace(c.Query("model_source")); rawModelSource != "" {
302+
if !usagestats.IsValidModelSource(rawModelSource) {
303+
response.BadRequest(c, "Invalid model_source, use requested/upstream/mapping")
304+
return
305+
}
306+
modelSource = rawModelSource
307+
}
300308
if requestTypeStr := strings.TrimSpace(c.Query("request_type")); requestTypeStr != "" {
301309
parsed, err := service.ParseUsageRequestType(requestTypeStr)
302310
if err != nil {
@@ -323,7 +331,7 @@ func (h *DashboardHandler) GetModelStats(c *gin.Context) {
323331
}
324332
}
325333

326-
stats, hit, err := h.getModelStatsCached(c.Request.Context(), startTime, endTime, userID, apiKeyID, accountID, groupID, requestType, stream, billingType)
334+
stats, hit, err := h.getModelStatsCached(c.Request.Context(), startTime, endTime, userID, apiKeyID, accountID, groupID, modelSource, requestType, stream, billingType)
327335
if err != nil {
328336
response.Error(c, 500, "Failed to get model statistics")
329337
return
@@ -619,6 +627,12 @@ func (h *DashboardHandler) GetUserBreakdown(c *gin.Context) {
619627
}
620628
}
621629
dim.Model = c.Query("model")
630+
rawModelSource := strings.TrimSpace(c.DefaultQuery("model_source", usagestats.ModelSourceRequested))
631+
if !usagestats.IsValidModelSource(rawModelSource) {
632+
response.BadRequest(c, "Invalid model_source, use requested/upstream/mapping")
633+
return
634+
}
635+
dim.ModelType = rawModelSource
622636
dim.Endpoint = c.Query("endpoint")
623637
dim.EndpointType = c.DefaultQuery("endpoint_type", "inbound")
624638

backend/internal/handler/admin/dashboard_query_cache.go

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ type dashboardModelGroupCacheKey struct {
3838
APIKeyID int64 `json:"api_key_id"`
3939
AccountID int64 `json:"account_id"`
4040
GroupID int64 `json:"group_id"`
41+
ModelSource string `json:"model_source,omitempty"`
4142
RequestType *int16 `json:"request_type"`
4243
Stream *bool `json:"stream"`
4344
BillingType *int8 `json:"billing_type"`
@@ -111,6 +112,7 @@ func (h *DashboardHandler) getModelStatsCached(
111112
ctx context.Context,
112113
startTime, endTime time.Time,
113114
userID, apiKeyID, accountID, groupID int64,
115+
modelSource string,
114116
requestType *int16,
115117
stream *bool,
116118
billingType *int8,
@@ -122,12 +124,13 @@ func (h *DashboardHandler) getModelStatsCached(
122124
APIKeyID: apiKeyID,
123125
AccountID: accountID,
124126
GroupID: groupID,
127+
ModelSource: usagestats.NormalizeModelSource(modelSource),
125128
RequestType: requestType,
126129
Stream: stream,
127130
BillingType: billingType,
128131
})
129132
entry, hit, err := dashboardModelStatsCache.GetOrLoad(key, func() (any, error) {
130-
return h.dashboardService.GetModelStatsWithFilters(ctx, startTime, endTime, userID, apiKeyID, accountID, groupID, requestType, stream, billingType)
133+
return h.dashboardService.GetModelStatsWithFiltersBySource(ctx, startTime, endTime, userID, apiKeyID, accountID, groupID, requestType, stream, billingType, modelSource)
131134
})
132135
if err != nil {
133136
return nil, hit, err

backend/internal/handler/admin/dashboard_snapshot_v2_handler.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -200,6 +200,7 @@ func (h *DashboardHandler) buildSnapshotV2Response(
200200
filters.APIKeyID,
201201
filters.AccountID,
202202
filters.GroupID,
203+
usagestats.ModelSourceRequested,
203204
filters.RequestType,
204205
filters.Stream,
205206
filters.BillingType,

backend/internal/handler/dto/mappers.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -521,6 +521,7 @@ func usageLogFromServiceUser(l *service.UsageLog) UsageLog {
521521
AccountID: l.AccountID,
522522
RequestID: l.RequestID,
523523
Model: l.Model,
524+
UpstreamModel: l.UpstreamModel,
524525
ServiceTier: l.ServiceTier,
525526
ReasoningEffort: l.ReasoningEffort,
526527
InboundEndpoint: l.InboundEndpoint,

backend/internal/handler/dto/types.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -332,6 +332,9 @@ type UsageLog struct {
332332
AccountID int64 `json:"account_id"`
333333
RequestID string `json:"request_id"`
334334
Model string `json:"model"`
335+
// UpstreamModel is the actual model sent to the upstream provider after mapping.
336+
// Omitted when no mapping was applied (requested model was used as-is).
337+
UpstreamModel *string `json:"upstream_model,omitempty"`
335338
// ServiceTier records the OpenAI service tier used for billing, e.g. "priority" / "flex".
336339
ServiceTier *string `json:"service_tier,omitempty"`
337340
// ReasoningEffort is the request's reasoning effort level.

0 commit comments

Comments
 (0)