Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
40 changes: 34 additions & 6 deletions proxy/proxymanager.go
Original file line number Diff line number Diff line change
Expand Up @@ -227,7 +227,7 @@ func (pm *ProxyManager) setupGinEngine() {
pm.ginEngine.GET("/upstream", func(c *gin.Context) {
c.Redirect(http.StatusFound, "/ui/models")
})
pm.ginEngine.Any("/upstream/:model_id/*upstreamPath", pm.proxyToUpstream)
pm.ginEngine.Any("/upstream/*upstreamPath", pm.proxyToUpstream)

pm.ginEngine.GET("/unload", pm.unloadAllModelsHandler)
pm.ginEngine.GET("/running", pm.listRunningProcessesHandler)
Expand Down Expand Up @@ -393,24 +393,52 @@ func (pm *ProxyManager) listModelsHandler(c *gin.Context) {
}

func (pm *ProxyManager) proxyToUpstream(c *gin.Context) {
requestedModel := c.Param("model_id")
upstreamPath := c.Param("upstreamPath")

if requestedModel == "" {
// split the upstream path by / and search for the model name
parts := strings.Split(strings.TrimSpace(upstreamPath), "/")
if len(parts) == 0 {
pm.sendErrorResponse(c, http.StatusBadRequest, "model id required in path")
return
}

processGroup, realModelName, err := pm.swapProcessGroup(requestedModel)
modelFound := false
searchModelName := ""
var modelName, remainingPath string
for i, part := range parts {
if parts[i] == "" {
continue
}

if searchModelName == "" {
searchModelName = part
} else {
searchModelName = searchModelName + "/" + parts[i]
}

if real, ok := pm.config.RealModelName(searchModelName); ok {
modelName = real
remainingPath = "/" + strings.Join(parts[i+1:], "/")
modelFound = true
break
}
}
Comment on lines +408 to +425
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Prefer longest prefix match for overlapping aliases (e.g., "aaa" vs "aaa/bbb")

Breaking on the first match can misroute requests intended for a longer alias (especially with “%2F” encoded slashes decoded by Gin). Keep scanning and use the last (longest) match.

-	for i, part := range parts {
-		if parts[i] == "" {
+	for i, part := range parts {
+		if part == "" {
 			continue
 		}
 
 		if searchModelName == "" {
-			searchModelName = part
+			searchModelName = part
 		} else {
-			searchModelName = searchModelName + "/" + parts[i]
+			searchModelName += "/" + part
 		}
 
 		if real, ok := pm.config.RealModelName(searchModelName); ok {
 			modelName = real
 			remainingPath = "/" + strings.Join(parts[i+1:], "/")
 			modelFound = true
-			break
+			// keep scanning to prefer the longest matching model alias
 		}
 	}
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
for i, part := range parts {
if parts[i] == "" {
continue
}
if searchModelName == "" {
searchModelName = part
} else {
searchModelName = searchModelName + "/" + parts[i]
}
if real, ok := pm.config.RealModelName(searchModelName); ok {
modelName = real
remainingPath = "/" + strings.Join(parts[i+1:], "/")
modelFound = true
break
}
}
for i, part := range parts {
if part == "" {
continue
}
if searchModelName == "" {
searchModelName = part
} else {
searchModelName += "/" + part
}
if real, ok := pm.config.RealModelName(searchModelName); ok {
modelName = real
remainingPath = "/" + strings.Join(parts[i+1:], "/")
modelFound = true
// keep scanning to prefer the longest matching model alias
}
}
🤖 Prompt for AI Agents
In proxy/proxymanager.go around lines 408-425, the loop currently breaks on the
first alias match which can pick a shorter alias (e.g., "aaa") over a longer
overlapping alias ("aaa/bbb"); instead, do not break on match — record the
matched real model name and the index where it was found (or compute
remainingPath from that index) and continue scanning all parts so the final
stored match will be the longest prefix; after the loop, use the last recorded
match to set modelName, remainingPath and modelFound (or leave them unchanged if
no match).


if !modelFound {
pm.sendErrorResponse(c, http.StatusBadRequest, "model id required in path")
return
}

processGroup, realModelName, err := pm.swapProcessGroup(modelName)
if err != nil {
pm.sendErrorResponse(c, http.StatusInternalServerError, fmt.Sprintf("error swapping process group: %s", err.Error()))
return
}

// rewrite the path
c.Request.URL.Path = c.Param("upstreamPath")
c.Request.URL.Path = remainingPath
processGroup.ProxyRequest(realModelName, c.Writer, c.Request)
}

func (pm *ProxyManager) proxyOAIHandler(c *gin.Context) {
bodyBytes, err := io.ReadAll(c.Request.Body)
if err != nil {
Expand Down
Loading