fix: log resolved model name instead of group name in Recent Activity
CI / Lint (push) Has been cancelled
CI / Test (push) Has been cancelled
CI / Build (push) Has been cancelled

When using model groups (e.g. 'deepseek-auto'), the dashboard logged the
group name instead of the concrete resolved model (e.g. 'deepseek-reasoner').

Now:
- logRequest passes the resolved modelID (concrete) + modelGroup (group name)
- RequestLog struct has a new ModelGroup field (omitempty)
- Dashboard displays resolved model (via group) when a group was used

Files changed:
  internal/server/logging.go  - add ModelGroup field
  internal/server/server.go   - pass resolved modelID, capture modelGroup
  static/js/websocket.js      - show group annotation in Recent Activity
  static/js/pages/overview.js - show group annotation in overview table
  static/js/pages/monitoring.js - show group annotation in stream
This commit is contained in:
2026-05-07 11:16:36 -04:00
parent 14de7e9ebf
commit 3021e4b2b4
5 changed files with 23 additions and 19 deletions
+1
View File
@@ -12,6 +12,7 @@ type RequestLog struct {
ClientID string `json:"client_id"` ClientID string `json:"client_id"`
Provider string `json:"provider"` Provider string `json:"provider"`
Model string `json:"model"` Model string `json:"model"`
ModelGroup string `json:"model_group,omitempty"`
PromptTokens uint32 `json:"prompt_tokens"` PromptTokens uint32 `json:"prompt_tokens"`
CompletionTokens uint32 `json:"completion_tokens"` CompletionTokens uint32 `json:"completion_tokens"`
ReasoningTokens uint32 `json:"reasoning_tokens"` ReasoningTokens uint32 `json:"reasoning_tokens"`
+17 -14
View File
@@ -362,7 +362,7 @@ func (s *Server) handleResponses(c *gin.Context) {
if stream { if stream {
ch, err := provider.ResponsesStream(c.Request.Context(), &req) ch, err := provider.ResponsesStream(c.Request.Context(), &req)
if err != nil { if err != nil {
s.logRequest(startTime, clientID, providerName, req.Model, nil, err, false) s.logRequest(startTime, clientID, providerName, req.Model, "", nil, err, false)
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
return return
} }
@@ -377,9 +377,9 @@ func (s *Server) handleResponses(c *gin.Context) {
if !ok { if !ok {
fmt.Fprintf(w, "data: [DONE]\n\n") fmt.Fprintf(w, "data: [DONE]\n\n")
if lastUsage != nil { if lastUsage != nil {
s.logRequest(startTime, clientID, providerName, req.Model, lastUsage.ToUsage(), nil, false) s.logRequest(startTime, clientID, providerName, req.Model, "", lastUsage.ToUsage(), nil, false)
} else { } else {
s.logRequest(startTime, clientID, providerName, req.Model, nil, nil, false) s.logRequest(startTime, clientID, providerName, req.Model, "", nil, nil, false)
} }
return false return false
} }
@@ -399,15 +399,15 @@ func (s *Server) handleResponses(c *gin.Context) {
resp, err := provider.Responses(c.Request.Context(), &req) resp, err := provider.Responses(c.Request.Context(), &req)
if err != nil { if err != nil {
s.logRequest(startTime, clientID, providerName, req.Model, nil, err, false) s.logRequest(startTime, clientID, providerName, req.Model, "", nil, err, false)
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
return return
} }
if resp.Usage != nil { if resp.Usage != nil {
s.logRequest(startTime, clientID, providerName, req.Model, resp.Usage.ToUsage(), nil, false) s.logRequest(startTime, clientID, providerName, req.Model, "", resp.Usage.ToUsage(), nil, false)
} else { } else {
s.logRequest(startTime, clientID, providerName, req.Model, nil, nil, false) s.logRequest(startTime, clientID, providerName, req.Model, "", nil, nil, false)
} }
c.JSON(http.StatusOK, resp) c.JSON(http.StatusOK, resp)
} }
@@ -528,7 +528,9 @@ func (s *Server) handleChatCompletions(c *gin.Context) {
} }
// Check if model is a group and route to a concrete model // Check if model is a group and route to a concrete model
modelGroup := ""
if s.modelRouter != nil && s.modelRouter.IsGroup(modelID) { if s.modelRouter != nil && s.modelRouter.IsGroup(modelID) {
modelGroup = modelID
userMessage := extractUserMessage(req.Messages) userMessage := extractUserMessage(req.Messages)
decision, err := s.modelRouter.Route(c.Request.Context(), modelID, userMessage) decision, err := s.modelRouter.Route(c.Request.Context(), modelID, userMessage)
if err != nil { if err != nil {
@@ -536,7 +538,7 @@ func (s *Server) handleChatCompletions(c *gin.Context) {
return return
} }
modelID = decision.SelectedModel modelID = decision.SelectedModel
log.Printf("[ROUTER] %s -> %s (%s: %s)", req.Model, modelID, decision.Strategy, decision.Reason) log.Printf("[ROUTER] %s -> %s (%s: %s)", modelGroup, modelID, decision.Strategy, decision.Reason)
} }
// Convert ChatCompletionRequest to UnifiedRequest // Convert ChatCompletionRequest to UnifiedRequest
@@ -657,7 +659,7 @@ if unifiedReq.MaxTokens == nil {
if unifiedReq.Stream { if unifiedReq.Stream {
ch, err := provider.ChatCompletionStream(c.Request.Context(), unifiedReq) ch, err := provider.ChatCompletionStream(c.Request.Context(), unifiedReq)
if err != nil { if err != nil {
s.logRequest(startTime, clientID, providerName, req.Model, nil, err, unifiedReq.HasImages) s.logRequest(startTime, clientID, providerName, modelID, modelGroup, nil, err, unifiedReq.HasImages)
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
return return
} }
@@ -671,7 +673,7 @@ if unifiedReq.MaxTokens == nil {
chunk, ok := <-ch chunk, ok := <-ch
if !ok { if !ok {
fmt.Fprintf(w, "data: [DONE]\n\n") fmt.Fprintf(w, "data: [DONE]\n\n")
s.logRequest(startTime, clientID, providerName, req.Model, lastUsage, nil, unifiedReq.HasImages) s.logRequest(startTime, clientID, providerName, modelID, modelGroup, lastUsage, nil, unifiedReq.HasImages)
return false return false
} }
if chunk.Usage != nil { if chunk.Usage != nil {
@@ -689,12 +691,12 @@ if unifiedReq.MaxTokens == nil {
resp, err := provider.ChatCompletion(c.Request.Context(), unifiedReq) resp, err := provider.ChatCompletion(c.Request.Context(), unifiedReq)
if err != nil { if err != nil {
s.logRequest(startTime, clientID, providerName, req.Model, nil, err, unifiedReq.HasImages) s.logRequest(startTime, clientID, providerName, modelID, modelGroup, nil, err, unifiedReq.HasImages)
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
return return
} }
s.logRequest(startTime, clientID, providerName, req.Model, resp.Usage, nil, unifiedReq.HasImages) s.logRequest(startTime, clientID, providerName, modelID, modelGroup, resp.Usage, nil, unifiedReq.HasImages)
c.JSON(http.StatusOK, resp) c.JSON(http.StatusOK, resp)
} }
@@ -763,7 +765,7 @@ func (s *Server) handleImageGenerations(c *gin.Context) {
resp, err := provider.ImageGeneration(c.Request.Context(), &req) resp, err := provider.ImageGeneration(c.Request.Context(), &req)
if err != nil { if err != nil {
s.logRequest(startTime, clientID, providerName, req.Model, nil, err, false) s.logRequest(startTime, clientID, providerName, req.Model, "", nil, err, false)
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
return return
} }
@@ -777,7 +779,7 @@ func (s *Server) handleImageGenerations(c *gin.Context) {
// Calculate per-image cost (not per-token like chat) // Calculate per-image cost (not per-token like chat)
cost := imageGenCost(providerName, req.Model, req.Size, uint32(len(resp.Data))) cost := imageGenCost(providerName, req.Model, req.Size, uint32(len(resp.Data)))
s.logRequest(startTime, clientID, providerName, req.Model, &models.Usage{ s.logRequest(startTime, clientID, providerName, req.Model, "", &models.Usage{
PromptTokens: promptTokens, PromptTokens: promptTokens,
CompletionTokens: uint32(len(resp.Data)), CompletionTokens: uint32(len(resp.Data)),
TotalTokens: promptTokens + uint32(len(resp.Data)), TotalTokens: promptTokens + uint32(len(resp.Data)),
@@ -819,12 +821,13 @@ func imageGenCost(provider, model string, size *string, n uint32) float64 {
return perImage * float64(n) return perImage * float64(n)
} }
func (s *Server) logRequest(start time.Time, clientID, provider, model string, usage *models.Usage, err error, hasImages bool) { func (s *Server) logRequest(start time.Time, clientID, provider, model, modelGroup string, usage *models.Usage, err error, hasImages bool) {
entry := RequestLog{ entry := RequestLog{
Timestamp: start, Timestamp: start,
ClientID: clientID, ClientID: clientID,
Provider: provider, Provider: provider,
Model: model, Model: model,
ModelGroup: modelGroup,
Status: "success", Status: "success",
DurationMS: time.Since(start).Milliseconds(), DurationMS: time.Since(start).Milliseconds(),
HasImages: hasImages, HasImages: hasImages,
+1 -1
View File
@@ -392,7 +392,7 @@ class MonitoringPage {
</div> </div>
<div class="stream-entry-content"> <div class="stream-entry-content">
<strong>${request.client_id || 'Unknown'}</strong> → <strong>${request.client_id || 'Unknown'}</strong> →
${request.provider || 'Unknown'} (${request.model || 'Unknown'}) ${request.provider || 'Unknown'} (${request.model || 'Unknown'}${request.model_group ? ` via ${request.model_group}` : ''})
<div class="stream-entry-details"> <div class="stream-entry-details">
${request.total_tokens || request.tokens || 0} tokens • ${request.duration_ms || request.duration || 0}ms ${request.total_tokens || request.tokens || 0} tokens • ${request.duration_ms || request.duration || 0}ms
</div> </div>
+2 -2
View File
@@ -252,7 +252,7 @@ class OverviewPage {
<td>${time}</td> <td>${time}</td>
<td><span class="badge-client">${request.client_id}</span></td> <td><span class="badge-client">${request.client_id}</span></td>
<td>${request.provider}</td> <td>${request.provider}</td>
<td><code class="code-sm">${request.model}</code></td> <td><code class="code-sm">${request.model}${request.model_group ? ` (via ${request.model_group})` : ''}</code></td>
<td>${request.tokens.toLocaleString()}</td> <td>${request.tokens.toLocaleString()}</td>
<td> <td>
<span class="status-badge ${statusClass}"> <span class="status-badge ${statusClass}">
@@ -313,7 +313,7 @@ class OverviewPage {
<td>${time}</td> <td>${time}</td>
<td><span class="badge-client">${request.client_id}</span></td> <td><span class="badge-client">${request.client_id}</span></td>
<td>${request.provider}</td> <td>${request.provider}</td>
<td><code class="code-sm">${request.model}</code></td> <td><code class="code-sm">${request.model}${request.model_group ? ` (via ${request.model_group})` : ''}</code></td>
<td>${(request.total_tokens || request.tokens || 0).toLocaleString()}</td> <td>${(request.total_tokens || request.tokens || 0).toLocaleString()}</td>
<td> <td>
<span class="status-badge ${statusClass}"> <span class="status-badge ${statusClass}">
+2 -2
View File
@@ -309,7 +309,7 @@ class WebSocketManager {
<td>${time}</td> <td>${time}</td>
<td>${request.client_id || 'Unknown'}</td> <td>${request.client_id || 'Unknown'}</td>
<td>${request.provider || 'Unknown'}</td> <td>${request.provider || 'Unknown'}</td>
<td>${request.model || 'Unknown'}</td> <td>${request.model || 'Unknown'}${request.model_group ? ` (via ${request.model_group})` : ''}</td>
<td>${(request.total_tokens || request.tokens || 0)}</td> <td>${(request.total_tokens || request.tokens || 0)}</td>
<td> <td>
<span class="status-badge ${statusClass}"> <span class="status-badge ${statusClass}">
@@ -358,7 +358,7 @@ class WebSocketManager {
</div> </div>
<div class="stream-entry-content"> <div class="stream-entry-content">
<strong>${request.client_id || 'Unknown'}</strong> → <strong>${request.client_id || 'Unknown'}</strong> →
${request.provider || 'Unknown'} (${request.model || 'Unknown'}) ${request.provider || 'Unknown'} (${request.model || 'Unknown'}${request.model_group ? ` via ${request.model_group}` : ''})
<div class="stream-entry-details"> <div class="stream-entry-details">
${(request.total_tokens || request.tokens || 0)} tokens • ${(request.duration_ms || request.duration || 0)}ms ${(request.total_tokens || request.tokens || 0)} tokens • ${(request.duration_ms || request.duration || 0)}ms
</div> </div>