@@ -20,6 +20,7 @@ import (
2020
2121 "github.com/LM4eu/goinfer/conf"
2222 "github.com/LM4eu/goinfer/event"
23+ "github.com/LM4eu/goinfer/gie"
2324 "github.com/LM4eu/goinfer/proxy/config"
2425 "github.com/gin-gonic/gin"
2526 "github.com/tidwall/gjson"
@@ -242,30 +243,61 @@ func (pm *ProxyManager) setupGinEngine() {
242243 })
243244
244245 // Set up routes using the Gin engine
245- pm .ginEngine .POST ("/v1/chat/completions" , pm .ProxyOAIHandler )
246+ pm .ginEngine .POST ("/v1/chat/completions" , func (c * gin.Context ) { pm .ProxyOAIHandler (c , false , false ) })
247+ pm .ginEngine .POST ("/d1/chat/completions" , func (c * gin.Context ) { pm .ProxyOAIHandler (c , true , false ) })
248+ pm .ginEngine .POST ("/a1/chat/completions" , func (c * gin.Context ) { pm .ProxyOAIHandler (c , false , true ) })
249+ pm .ginEngine .POST ("/A1/chat/completions" , func (c * gin.Context ) { pm .ProxyOAIHandler (c , true , true ) })
246250 // Support legacy /v1/completions api, see issue #12
247- pm .ginEngine .POST ("/v1/completions" , pm .ProxyOAIHandler )
251+ pm .ginEngine .POST ("/v1/completions" , func (c * gin.Context ) { pm .ProxyOAIHandler (c , false , false ) })
252+ pm .ginEngine .POST ("/d1/completions" , func (c * gin.Context ) { pm .ProxyOAIHandler (c , true , false ) })
253+ pm .ginEngine .POST ("/a1/completions" , func (c * gin.Context ) { pm .ProxyOAIHandler (c , false , true ) })
254+ pm .ginEngine .POST ("/A1/completions" , func (c * gin.Context ) { pm .ProxyOAIHandler (c , true , true ) })
248255
249256 // Support embeddings and reranking
250- pm .ginEngine .POST ("/v1/embeddings" , pm .ProxyOAIHandler )
257+ pm .ginEngine .POST ("/v1/embeddings" , func (c * gin.Context ) { pm .ProxyOAIHandler (c , false , false ) })
258+ pm .ginEngine .POST ("/d1/embeddings" , func (c * gin.Context ) { pm .ProxyOAIHandler (c , true , false ) })
259+ pm .ginEngine .POST ("/a1/embeddings" , func (c * gin.Context ) { pm .ProxyOAIHandler (c , false , true ) })
260+ pm .ginEngine .POST ("/A1/embeddings" , func (c * gin.Context ) { pm .ProxyOAIHandler (c , true , true ) })
251261
252262 // llama-server's /reranking endpoint + aliases
253- pm .ginEngine .POST ("/reranking" , pm .ProxyOAIHandler )
254- pm .ginEngine .POST ("/rerank" , pm .ProxyOAIHandler )
255- pm .ginEngine .POST ("/v1/rerank" , pm .ProxyOAIHandler )
256- pm .ginEngine .POST ("/v1/reranking" , pm .ProxyOAIHandler )
263+ pm .ginEngine .POST ("/reranking" , func (c * gin.Context ) { pm .ProxyOAIHandler (c , false , false ) })
264+ pm .ginEngine .POST ("/rerank" , func (c * gin.Context ) { pm .ProxyOAIHandler (c , false , false ) })
265+ pm .ginEngine .POST ("/v1/rerank" , func (c * gin.Context ) { pm .ProxyOAIHandler (c , false , false ) })
266+ pm .ginEngine .POST ("/d1/rerank" , func (c * gin.Context ) { pm .ProxyOAIHandler (c , true , false ) })
267+ pm .ginEngine .POST ("/a1/rerank" , func (c * gin.Context ) { pm .ProxyOAIHandler (c , false , true ) })
268+ pm .ginEngine .POST ("/A1/rerank" , func (c * gin.Context ) { pm .ProxyOAIHandler (c , true , true ) })
269+ pm .ginEngine .POST ("/v1/reranking" , func (c * gin.Context ) { pm .ProxyOAIHandler (c , false , false ) })
270+ pm .ginEngine .POST ("/d1/reranking" , func (c * gin.Context ) { pm .ProxyOAIHandler (c , true , false ) })
271+ pm .ginEngine .POST ("/a1/reranking" , func (c * gin.Context ) { pm .ProxyOAIHandler (c , false , true ) })
272+ pm .ginEngine .POST ("/A1/reranking" , func (c * gin.Context ) { pm .ProxyOAIHandler (c , true , true ) })
257273
258274 // llama-server's /infill endpoint for code infilling
259- pm .ginEngine .POST ("/infill" , pm .ProxyOAIHandler )
275+ pm .ginEngine .POST ("/v1/infill" , func (c * gin.Context ) { pm .ProxyOAIHandler (c , false , false ) })
276+ pm .ginEngine .POST ("/d1/infill" , func (c * gin.Context ) { pm .ProxyOAIHandler (c , true , false ) })
277+ pm .ginEngine .POST ("/a1/infill" , func (c * gin.Context ) { pm .ProxyOAIHandler (c , false , true ) })
278+ pm .ginEngine .POST ("/A1/infill" , func (c * gin.Context ) { pm .ProxyOAIHandler (c , true , true ) })
260279
261280 // llama-server's /completion endpoint
262- pm .ginEngine .POST ("/completion" , pm .ProxyOAIHandler )
281+ pm .ginEngine .POST ("/completion" , func (c * gin.Context ) { pm .ProxyOAIHandler (c , false , false ) })
282+ pm .ginEngine .POST ("/d/completion" , func (c * gin.Context ) { pm .ProxyOAIHandler (c , true , false ) })
283+ pm .ginEngine .POST ("/a/completion" , func (c * gin.Context ) { pm .ProxyOAIHandler (c , false , true ) })
284+ pm .ginEngine .POST ("/A/completion" , func (c * gin.Context ) { pm .ProxyOAIHandler (c , true , true ) })
263285
264286 // Support audio/speech endpoint
265- pm .ginEngine .POST ("/v1/audio/speech" , pm .ProxyOAIHandler )
266- pm .ginEngine .POST ("/v1/audio/transcriptions" , pm .ProxyOAIPostFormHandler )
287+ pm .ginEngine .POST ("/v1/audio/speech" , func (c * gin.Context ) { pm .ProxyOAIHandler (c , false , false ) })
288+ pm .ginEngine .POST ("/d1/audio/speech" , func (c * gin.Context ) { pm .ProxyOAIHandler (c , true , false ) })
289+ pm .ginEngine .POST ("/a1/audio/speech" , func (c * gin.Context ) { pm .ProxyOAIHandler (c , false , true ) })
290+ pm .ginEngine .POST ("/A1/audio/speech" , func (c * gin.Context ) { pm .ProxyOAIHandler (c , true , true ) })
291+ pm .ginEngine .POST ("/v1/audio/transcriptions" , func (c * gin.Context ) { pm .ProxyOAIPostFormHandler (c , false , false ) })
292+ pm .ginEngine .POST ("/d1/audio/transcriptions" , func (c * gin.Context ) { pm .ProxyOAIPostFormHandler (c , true , false ) })
293+ pm .ginEngine .POST ("/a1/audio/transcriptions" , func (c * gin.Context ) { pm .ProxyOAIPostFormHandler (c , false , true ) })
294+ pm .ginEngine .POST ("/A1/audio/transcriptions" , func (c * gin.Context ) { pm .ProxyOAIPostFormHandler (c , true , true ) })
267295
268296 pm .ginEngine .GET ("/v1/models" , pm .ListModelsHandler )
297+ pm .ginEngine .GET ("/d1/models" , pm .ListModelsHandler )
298+ pm .ginEngine .GET ("/a1/models" , pm .ListModelsHandler )
299+ pm .ginEngine .GET ("/A1/models" , pm .ListModelsHandler )
300+ pm .ginEngine .GET ("/gi/models" , pm .GiListModelsHandler )
269301
270302 // in proxymanager_loghandlers.go
271303 pm .ginEngine .GET ("/logs" , pm .sendLogsHandlers )
@@ -470,6 +502,18 @@ func (pm *ProxyManager) ListModelsHandler(c *gin.Context) {
470502 })
471503}
472504
505+ func (pm * ProxyManager ) GiListModelsHandler (c * gin.Context ) {
506+ models := pm .cfg .ListModels ()
507+
508+ if len (models ) == 0 {
509+ c .String (http .StatusNoContent , `{"count":0, "models":[]}` )
510+ return
511+ }
512+
513+ // Use gin's JSON method which handles content-type and encoding
514+ c .JSON (http .StatusOK , gin.H {"count" : len (models ), "models" : models })
515+ }
516+
473517func (pm * ProxyManager ) proxyToUpstream (c * gin.Context ) {
474518 upstreamPath := c .Param ("upstreamPath" )
475519
@@ -553,55 +597,35 @@ func (pm *ProxyManager) proxyToUpstream(c *gin.Context) {
553597 }
554598}
555599
556- func (pm * ProxyManager ) ProxyOAIHandler (c * gin.Context ) {
557- bodyBytes , err := io .ReadAll (c .Request .Body )
558- if err != nil {
559- pm .sendErrorResponse (c , http .StatusBadRequest , "could not ready request body" )
560- return
561- }
562-
563- requestedModel := gjson .GetBytes (bodyBytes , "model" ).String ()
564- if requestedModel == "" {
565- // fallback: the first running process we find
566- for _ , processGroup := range pm .processGroups {
567- for _ , process := range processGroup .processes {
568- if process .CurrentState () == StateReady {
569- requestedModel = process .ID
570- }
571- }
572- }
573- if requestedModel == "" {
574- pm .sendErrorResponse (c , http .StatusBadRequest , "missing or invalid 'model' key" )
575- return
600+ func (pm * ProxyManager ) ProxyOAIHandler (c * gin.Context , download , agentSmith bool ) {
601+ if download || agentSmith {
602+ if c .Request .URL .Path [2 ] == '/' {
603+ c .Request .URL .Path = c .Request .URL .Path [2 :]
604+ } else {
605+ c .Request .URL .Path = "/v" + c .Request .URL .Path [2 :]
576606 }
577607 }
578608
579- realModelName , found := pm .config . RealModelName ( requestedModel )
580- if ! found {
581- pm . sendErrorResponse ( c , http .StatusBadRequest , "could not find real modelID for " + requestedModel )
609+ bodyBytes , realModelName , download , agentSmith , err := pm .getSetModel ( c . Request . Body , download , agentSmith )
610+ if err != nil {
611+ c . JSON ( http .StatusBadRequest , err )
582612 return
583613 }
584-
585- processGroup , _ , err := pm .swapProcessGroup (realModelName )
586- if err != nil {
587- pm .sendErrorResponse (c , http .StatusInternalServerError , "error swapping process group: " + err .Error ())
614+ if realModelName == "" {
615+ c .JSON (http .StatusBadRequest , gie .New (gie .Invalid , "no model provided and no model loaded" ))
588616 return
589617 }
590618
591- // issue #69 allow custom model names to be sent to upstream
592- useModelName := pm .config .Models [realModelName ].UseModelName
593- if useModelName != "" {
594- bodyBytes , err = sjson .SetBytes (bodyBytes , "model" , useModelName )
595- if err != nil {
596- pm .sendErrorResponse (c , http .StatusInternalServerError , "error rewriting model name in JSON: " + err .Error ())
597- return
598- }
619+ processGroup , realModelName , err := pm .swapProcessGroup (realModelName )
620+ if err != nil {
621+ c .JSON (http .StatusInternalServerError , gie .Wrap (err , gie .Invalid , "error swapping process group" , "model" , realModelName ))
622+ return
599623 }
600624
601625 // issue #174 strip parameters from the JSON body
602626 stripParams , err := pm .config .Models [realModelName ].Filters .SanitizedStripParams ()
603627 if err != nil { // just log it and continue
604- pm .proxyLogger .Errorf ("Error sanitizing strip params string: %s, %s" , pm .config .Models [realModelName ].Filters .StripParams , err .Error ())
628+ pm .proxyLogger .Warnf ("Error sanitizing strip params string: %s, %s" , pm .config .Models [realModelName ].Filters .StripParams , err .Error ())
605629 } else {
606630 for _ , param := range stripParams {
607631 pm .proxyLogger .Debugf ("<%s> stripping param: %s" , realModelName , param )
@@ -631,19 +655,19 @@ func (pm *ProxyManager) ProxyOAIHandler(c *gin.Context) {
631655 if err != nil {
632656 pm .sendErrorResponse (c , http .StatusInternalServerError , "error proxying metrics wrapped request: " + err .Error ())
633657 pm .proxyLogger .Errorf ("Error Proxying Metrics Wrapped Request for processGroup %s and model %s" , processGroup .id , realModelName )
634- return
635- }
636- } else {
637- err := processGroup .ProxyRequest (realModelName , c .Writer , c .Request )
638- if err != nil {
639- pm .sendErrorResponse (c , http .StatusInternalServerError , "error proxying request: " + err .Error ())
640- pm .proxyLogger .Errorf ("Error Proxying Request for processGroup %s and model %s" , processGroup .id , realModelName )
641- return
642658 }
659+ return
660+ }
661+
662+ err = processGroup .ProxyRequest (realModelName , c .Writer , c .Request )
663+ if err != nil {
664+ pm .sendErrorResponse (c , http .StatusInternalServerError , "error proxying request: " + err .Error ())
665+ pm .proxyLogger .Errorf ("Error Proxying Request for processGroup %s and model %s" , processGroup .id , realModelName )
666+ return
643667 }
644668}
645669
646- func (pm * ProxyManager ) ProxyOAIPostFormHandler (c * gin.Context ) {
670+ func (pm * ProxyManager ) ProxyOAIPostFormHandler (c * gin.Context , download , agentSmith bool ) {
647671 // Parse multipart form
648672 if err := c .Request .ParseMultipartForm (32 << 20 ); err != nil { // 32MB max memory, larger files go to tmp disk
649673 pm .sendErrorResponse (c , http .StatusBadRequest , "error parsing multipart form: " + err .Error ())
@@ -652,22 +676,9 @@ func (pm *ProxyManager) ProxyOAIPostFormHandler(c *gin.Context) {
652676
653677 // Get model parameter from the form
654678 requestedModel := c .Request .FormValue ("model" )
655- if requestedModel == "" {
656- // fallback: the first running process we find
657- for _ , processGroup := range pm .processGroups {
658- for _ , process := range processGroup .processes {
659- if process .CurrentState () == StateReady {
660- requestedModel = process .ID
661- }
662- }
663- }
664- if requestedModel == "" {
665- pm .sendErrorResponse (c , http .StatusBadRequest , "missing or invalid 'model' parameter in form data" )
666- return
667- }
668- }
679+ fixed , download , agentSmith := pm .fixModelName (requestedModel , download , agentSmith )
669680
670- processGroup , realModelName , err := pm .swapProcessGroup (requestedModel )
681+ processGroup , realModelName , err := pm .swapProcessGroup (fixed )
671682 if err != nil {
672683 pm .sendErrorResponse (c , http .StatusInternalServerError , "error swapping process group: " + err .Error ())
673684 return
@@ -780,7 +791,18 @@ func (pm *ProxyManager) UnloadAllModelsHandler(c *gin.Context) {
780791
781792func (pm * ProxyManager ) ListRunningProcessesHandler (context * gin.Context ) {
782793 context .Header ("Content-Type" , "application/json" )
783- runningProcesses := make ([]gin.H , 0 ) // Default to an empty response.
794+
795+ // Put the results under the `running` key.
796+ response := gin.H {
797+ "running" : pm .listRunningProcesses (),
798+ }
799+
800+ context .JSON (http .StatusOK , response ) // Always return 200 OK
801+ }
802+
803+ func (pm * ProxyManager ) listRunningProcesses () []gin.H {
804+ count := pm .countRunningProcesses ()
805+ runningProcesses := make ([]gin.H , count ) // Default to an empty response.
784806
785807 for _ , processGroup := range pm .processGroups {
786808 for _ , process := range processGroup .processes {
@@ -792,13 +814,21 @@ func (pm *ProxyManager) ListRunningProcessesHandler(context *gin.Context) {
792814 }
793815 }
794816 }
817+ return runningProcesses
818+ }
795819
796- // Put the results under the `running` key.
797- response := gin.H {
798- "running" : runningProcesses ,
820+ func (pm * ProxyManager ) countRunningProcesses () int {
821+ count := 0
822+
823+ for _ , processGroup := range pm .processGroups {
824+ for _ , process := range processGroup .processes {
825+ if process .CurrentState () == StateReady {
826+ count ++
827+ }
828+ }
799829 }
800830
801- context . JSON ( http . StatusOK , response ) // Always return 200 OK
831+ return count
802832}
803833
804834func (pm * ProxyManager ) findGroupByModelName (modelName string ) * ProcessGroup {
@@ -818,15 +848,37 @@ func (pm *ProxyManager) SetVersion(buildDate, commit, version string) {
818848 pm .version = version
819849}
820850
821- // ProxyToFirstRunningProcess forwards the request to the any running process (llama-server).
851+ func (pm * ProxyManager ) firstRunningProcess () string {
852+ var starting string
853+ for _ , processGroup := range pm .processGroups {
854+ for _ , process := range processGroup .processes {
855+ if process .state == StateReady {
856+ return process .ID
857+ }
858+ if process .state == StateStarting {
859+ starting = process .ID
860+ }
861+ }
862+ }
863+ return starting
864+ }
865+
866+ // ProxyToFirstRunningProcess forwards the request to a running process (llama-server).
822867func (pm * ProxyManager ) ProxyToFirstRunningProcess (c * gin.Context ) {
868+ var starting * Process
823869 for _ , processGroup := range pm .processGroups {
824870 for _ , process := range processGroup .processes {
825- if process .CurrentState () == StateReady {
871+ if process .state == StateReady {
826872 process .ProxyRequest (c .Writer , c .Request )
827873 return
828874 }
875+ if process .state == StateStarting {
876+ starting = process
877+ }
829878 }
830879 }
880+ if starting != nil {
881+ starting .ProxyRequest (c .Writer , c .Request )
882+ }
831883 pm .sendErrorResponse (c , http .StatusInternalServerError , "No model currently running. Please select a model." )
832884}
0 commit comments