mirror of
https://gitee.com/wanwujie/sub2api
synced 2026-04-21 23:24:46 +08:00
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.
This commit is contained in:
@@ -273,6 +273,7 @@ func (h *DashboardHandler) GetModelStats(c *gin.Context) {
|
|||||||
|
|
||||||
// Parse optional filter params
|
// Parse optional filter params
|
||||||
var userID, apiKeyID, accountID, groupID int64
|
var userID, apiKeyID, accountID, groupID int64
|
||||||
|
modelSource := usagestats.ModelSourceRequested
|
||||||
var requestType *int16
|
var requestType *int16
|
||||||
var stream *bool
|
var stream *bool
|
||||||
var billingType *int8
|
var billingType *int8
|
||||||
@@ -297,6 +298,13 @@ func (h *DashboardHandler) GetModelStats(c *gin.Context) {
|
|||||||
groupID = id
|
groupID = id
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if rawModelSource := strings.TrimSpace(c.Query("model_source")); rawModelSource != "" {
|
||||||
|
if !usagestats.IsValidModelSource(rawModelSource) {
|
||||||
|
response.BadRequest(c, "Invalid model_source, use requested/upstream/mapping")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
modelSource = rawModelSource
|
||||||
|
}
|
||||||
if requestTypeStr := strings.TrimSpace(c.Query("request_type")); requestTypeStr != "" {
|
if requestTypeStr := strings.TrimSpace(c.Query("request_type")); requestTypeStr != "" {
|
||||||
parsed, err := service.ParseUsageRequestType(requestTypeStr)
|
parsed, err := service.ParseUsageRequestType(requestTypeStr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -323,7 +331,7 @@ func (h *DashboardHandler) GetModelStats(c *gin.Context) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
stats, hit, err := h.getModelStatsCached(c.Request.Context(), startTime, endTime, userID, apiKeyID, accountID, groupID, requestType, stream, billingType)
|
stats, hit, err := h.getModelStatsCached(c.Request.Context(), startTime, endTime, userID, apiKeyID, accountID, groupID, modelSource, requestType, stream, billingType)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
response.Error(c, 500, "Failed to get model statistics")
|
response.Error(c, 500, "Failed to get model statistics")
|
||||||
return
|
return
|
||||||
@@ -619,6 +627,12 @@ func (h *DashboardHandler) GetUserBreakdown(c *gin.Context) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
dim.Model = c.Query("model")
|
dim.Model = c.Query("model")
|
||||||
|
rawModelSource := strings.TrimSpace(c.DefaultQuery("model_source", usagestats.ModelSourceRequested))
|
||||||
|
if !usagestats.IsValidModelSource(rawModelSource) {
|
||||||
|
response.BadRequest(c, "Invalid model_source, use requested/upstream/mapping")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
dim.ModelType = rawModelSource
|
||||||
dim.Endpoint = c.Query("endpoint")
|
dim.Endpoint = c.Query("endpoint")
|
||||||
dim.EndpointType = c.DefaultQuery("endpoint_type", "inbound")
|
dim.EndpointType = c.DefaultQuery("endpoint_type", "inbound")
|
||||||
|
|
||||||
|
|||||||
@@ -38,6 +38,7 @@ type dashboardModelGroupCacheKey struct {
|
|||||||
APIKeyID int64 `json:"api_key_id"`
|
APIKeyID int64 `json:"api_key_id"`
|
||||||
AccountID int64 `json:"account_id"`
|
AccountID int64 `json:"account_id"`
|
||||||
GroupID int64 `json:"group_id"`
|
GroupID int64 `json:"group_id"`
|
||||||
|
ModelSource string `json:"model_source,omitempty"`
|
||||||
RequestType *int16 `json:"request_type"`
|
RequestType *int16 `json:"request_type"`
|
||||||
Stream *bool `json:"stream"`
|
Stream *bool `json:"stream"`
|
||||||
BillingType *int8 `json:"billing_type"`
|
BillingType *int8 `json:"billing_type"`
|
||||||
@@ -111,6 +112,7 @@ func (h *DashboardHandler) getModelStatsCached(
|
|||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
startTime, endTime time.Time,
|
startTime, endTime time.Time,
|
||||||
userID, apiKeyID, accountID, groupID int64,
|
userID, apiKeyID, accountID, groupID int64,
|
||||||
|
modelSource string,
|
||||||
requestType *int16,
|
requestType *int16,
|
||||||
stream *bool,
|
stream *bool,
|
||||||
billingType *int8,
|
billingType *int8,
|
||||||
@@ -122,12 +124,13 @@ func (h *DashboardHandler) getModelStatsCached(
|
|||||||
APIKeyID: apiKeyID,
|
APIKeyID: apiKeyID,
|
||||||
AccountID: accountID,
|
AccountID: accountID,
|
||||||
GroupID: groupID,
|
GroupID: groupID,
|
||||||
|
ModelSource: usagestats.NormalizeModelSource(modelSource),
|
||||||
RequestType: requestType,
|
RequestType: requestType,
|
||||||
Stream: stream,
|
Stream: stream,
|
||||||
BillingType: billingType,
|
BillingType: billingType,
|
||||||
})
|
})
|
||||||
entry, hit, err := dashboardModelStatsCache.GetOrLoad(key, func() (any, error) {
|
entry, hit, err := dashboardModelStatsCache.GetOrLoad(key, func() (any, error) {
|
||||||
return h.dashboardService.GetModelStatsWithFilters(ctx, startTime, endTime, userID, apiKeyID, accountID, groupID, requestType, stream, billingType)
|
return h.dashboardService.GetModelStatsWithFiltersBySource(ctx, startTime, endTime, userID, apiKeyID, accountID, groupID, requestType, stream, billingType, modelSource)
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, hit, err
|
return nil, hit, err
|
||||||
|
|||||||
@@ -200,6 +200,7 @@ func (h *DashboardHandler) buildSnapshotV2Response(
|
|||||||
filters.APIKeyID,
|
filters.APIKeyID,
|
||||||
filters.AccountID,
|
filters.AccountID,
|
||||||
filters.GroupID,
|
filters.GroupID,
|
||||||
|
usagestats.ModelSourceRequested,
|
||||||
filters.RequestType,
|
filters.RequestType,
|
||||||
filters.Stream,
|
filters.Stream,
|
||||||
filters.BillingType,
|
filters.BillingType,
|
||||||
|
|||||||
@@ -521,6 +521,7 @@ func usageLogFromServiceUser(l *service.UsageLog) UsageLog {
|
|||||||
AccountID: l.AccountID,
|
AccountID: l.AccountID,
|
||||||
RequestID: l.RequestID,
|
RequestID: l.RequestID,
|
||||||
Model: l.Model,
|
Model: l.Model,
|
||||||
|
UpstreamModel: l.UpstreamModel,
|
||||||
ServiceTier: l.ServiceTier,
|
ServiceTier: l.ServiceTier,
|
||||||
ReasoningEffort: l.ReasoningEffort,
|
ReasoningEffort: l.ReasoningEffort,
|
||||||
InboundEndpoint: l.InboundEndpoint,
|
InboundEndpoint: l.InboundEndpoint,
|
||||||
|
|||||||
@@ -332,6 +332,9 @@ type UsageLog struct {
|
|||||||
AccountID int64 `json:"account_id"`
|
AccountID int64 `json:"account_id"`
|
||||||
RequestID string `json:"request_id"`
|
RequestID string `json:"request_id"`
|
||||||
Model string `json:"model"`
|
Model string `json:"model"`
|
||||||
|
// UpstreamModel is the actual model sent to the upstream provider after mapping.
|
||||||
|
// Omitted when no mapping was applied (requested model was used as-is).
|
||||||
|
UpstreamModel *string `json:"upstream_model,omitempty"`
|
||||||
// ServiceTier records the OpenAI service tier used for billing, e.g. "priority" / "flex".
|
// ServiceTier records the OpenAI service tier used for billing, e.g. "priority" / "flex".
|
||||||
ServiceTier *string `json:"service_tier,omitempty"`
|
ServiceTier *string `json:"service_tier,omitempty"`
|
||||||
// ReasoningEffort is the request's reasoning effort level.
|
// ReasoningEffort is the request's reasoning effort level.
|
||||||
|
|||||||
Reference in New Issue
Block a user