From 005f289632e2319e34ccea6619340896cc0ea99c Mon Sep 17 00:00:00 2001 From: Dave Date: Sat, 2 Sep 2023 03:00:44 -0400 Subject: [PATCH] feat: Model Gallery Endpoint Refactor / Mutable Galleries Endpoints (#991) refactor for model gallery endpoints - bundle up resources into a struct, make galleries mutable with some crud endpoints. This is groundwork required for making efficient use of the new scraper - while that PR isn't _quite_ ready yet, the goal is to have more, individually smaller gallery files. Therefore, rather than requiring a full localai service restart, these new endpoints have been added to make life easier. - Adds endpoints to add, list and remove model galleries at runtime - Adds these endpoints to the Insomnia config - Minor fix: loading file urls follows symbolic links now --- api/api.go | 12 ++- api/localai/gallery.go | 113 +++++++++++++++++++----- api/options/options.go | 1 + examples/insomnia/Insomnia_LocalAI.json | 2 +- pkg/gallery/gallery.go | 4 +- pkg/utils/uri.go | 8 +- 6 files changed, 108 insertions(+), 32 deletions(-) diff --git a/api/api.go b/api/api.go index 6a16130b..87cc13bf 100644 --- a/api/api.go +++ b/api/api.go @@ -168,10 +168,14 @@ func App(opts ...options.AppOption) (*fiber.App, error) { }{Version: internal.PrintableVersion()}) }) - app.Post("/models/apply", auth, localai.ApplyModelGalleryEndpoint(options.Loader.ModelPath, cl, galleryService.C, options.Galleries)) - app.Get("/models/available", auth, localai.ListModelFromGalleryEndpoint(options.Galleries, options.Loader.ModelPath)) - app.Get("/models/jobs/:uuid", auth, localai.GetOpStatusEndpoint(galleryService)) - app.Get("/models/jobs", auth, localai.GetAllStatusEndpoint(galleryService)) + modelGalleryService := localai.CreateModelGalleryService(options.Galleries, options.Loader.ModelPath, galleryService) + app.Post("/models/apply", auth, modelGalleryService.ApplyModelGalleryEndpoint()) + app.Get("/models/available", auth, modelGalleryService.ListModelFromGalleryEndpoint()) + app.Get("/models/galleries", auth, modelGalleryService.ListModelGalleriesEndpoint()) + app.Post("/models/galleries", auth, modelGalleryService.AddModelGalleryEndpoint()) + app.Delete("/models/galleries", auth, modelGalleryService.RemoveModelGalleryEndpoint()) + app.Get("/models/jobs/:uuid", auth, modelGalleryService.GetOpStatusEndpoint()) + app.Get("/models/jobs", auth, modelGalleryService.GetAllStatusEndpoint()) // openAI compatible API endpoint diff --git a/api/localai/gallery.go b/api/localai/gallery.go index 7e6bcb67..a1cab6dc 100644 --- a/api/localai/gallery.go +++ b/api/localai/gallery.go @@ -4,6 +4,7 @@ import ( "context" "fmt" "os" + "slices" "strings" "sync" @@ -51,7 +52,6 @@ func NewGalleryService(modelPath string) *galleryApplier { } } -// prepareModel applies a func prepareModel(modelPath string, req gallery.GalleryModel, cm *config.ConfigLoader, downloadStatus func(string, string, string, float64)) error { config, err := gallery.GetGalleryConfigFromURL(req.URL) @@ -184,24 +184,12 @@ func ApplyGalleryFromString(modelPath, s string, cm *config.ConfigLoader, galler return processRequests(modelPath, s, cm, galleries, requests) } -/// Endpoints +/// Endpoint Service -func GetOpStatusEndpoint(g *galleryApplier) func(c *fiber.Ctx) error { - return func(c *fiber.Ctx) error { - - status := g.getStatus(c.Params("uuid")) - if status == nil { - return fmt.Errorf("could not find any status for ID") - } - - return c.JSON(status) - } -} - -func GetAllStatusEndpoint(g *galleryApplier) func(c *fiber.Ctx) error { - return func(c *fiber.Ctx) error { - return c.JSON(g.getAllStatus()) - } +type ModelGalleryService struct { + galleries []gallery.Gallery + modelPath string + galleryApplier *galleryApplier } type GalleryModel struct { @@ -209,7 +197,31 @@ type GalleryModel struct { gallery.GalleryModel } -func ApplyModelGalleryEndpoint(modelPath string, cm *config.ConfigLoader, g chan galleryOp, galleries []gallery.Gallery) func(c *fiber.Ctx) error { +func CreateModelGalleryService(galleries []gallery.Gallery, modelPath string, galleryApplier *galleryApplier) ModelGalleryService { + return ModelGalleryService{ + galleries: galleries, + modelPath: modelPath, + galleryApplier: galleryApplier, + } +} + +func (mgs *ModelGalleryService) GetOpStatusEndpoint() func(c *fiber.Ctx) error { + return func(c *fiber.Ctx) error { + status := mgs.galleryApplier.getStatus(c.Params("uuid")) + if status == nil { + return fmt.Errorf("could not find any status for ID") + } + return c.JSON(status) + } +} + +func (mgs *ModelGalleryService) GetAllStatusEndpoint() func(c *fiber.Ctx) error { + return func(c *fiber.Ctx) error { + return c.JSON(mgs.galleryApplier.getAllStatus()) + } +} + +func (mgs *ModelGalleryService) ApplyModelGalleryEndpoint() func(c *fiber.Ctx) error { return func(c *fiber.Ctx) error { input := new(GalleryModel) // Get input data from the request body @@ -221,11 +233,11 @@ func ApplyModelGalleryEndpoint(modelPath string, cm *config.ConfigLoader, g chan if err != nil { return err } - g <- galleryOp{ + mgs.galleryApplier.C <- galleryOp{ req: input.GalleryModel, id: uuid.String(), galleryName: input.ID, - galleries: galleries, + galleries: mgs.galleries, } return c.JSON(struct { ID string `json:"uuid"` @@ -234,11 +246,11 @@ func ApplyModelGalleryEndpoint(modelPath string, cm *config.ConfigLoader, g chan } } -func ListModelFromGalleryEndpoint(galleries []gallery.Gallery, basePath string) func(c *fiber.Ctx) error { +func (mgs *ModelGalleryService) ListModelFromGalleryEndpoint() func(c *fiber.Ctx) error { return func(c *fiber.Ctx) error { - log.Debug().Msgf("Listing models from galleries: %+v", galleries) + log.Debug().Msgf("Listing models from galleries: %+v", mgs.galleries) - models, err := gallery.AvailableGalleryModels(galleries, basePath) + models, err := gallery.AvailableGalleryModels(mgs.galleries, mgs.modelPath) if err != nil { return err } @@ -253,3 +265,56 @@ func ListModelFromGalleryEndpoint(galleries []gallery.Gallery, basePath string) return c.Send(dat) } } + +// NOTE: This is different (and much simpler!) than above! This JUST lists the model galleries that have been loaded, not their contents! +func (mgs *ModelGalleryService) ListModelGalleriesEndpoint() func(c *fiber.Ctx) error { + return func(c *fiber.Ctx) error { + log.Debug().Msgf("Listing model galleries %+v", mgs.galleries) + dat, err := json.Marshal(mgs.galleries) + if err != nil { + return err + } + return c.Send(dat) + } +} + +func (mgs *ModelGalleryService) AddModelGalleryEndpoint() func(c *fiber.Ctx) error { + return func(c *fiber.Ctx) error { + input := new(gallery.Gallery) + // Get input data from the request body + if err := c.BodyParser(input); err != nil { + return err + } + if slices.ContainsFunc(mgs.galleries, func(gallery gallery.Gallery) bool { + return gallery.Name == input.Name + }) { + return fmt.Errorf("%s already exists", input.Name) + } + dat, err := json.Marshal(mgs.galleries) + if err != nil { + return err + } + log.Debug().Msgf("Adding %+v to gallery list", *input) + mgs.galleries = append(mgs.galleries, *input) + return c.Send(dat) + } +} + +func (mgs *ModelGalleryService) RemoveModelGalleryEndpoint() func(c *fiber.Ctx) error { + return func(c *fiber.Ctx) error { + input := new(gallery.Gallery) + // Get input data from the request body + if err := c.BodyParser(input); err != nil { + return err + } + if !slices.ContainsFunc(mgs.galleries, func(gallery gallery.Gallery) bool { + return gallery.Name == input.Name + }) { + return fmt.Errorf("%s is not currently registered", input.Name) + } + mgs.galleries = slices.DeleteFunc(mgs.galleries, func(gallery gallery.Gallery) bool { + return gallery.Name == input.Name + }) + return c.Send(nil) + } +} diff --git a/api/options/options.go b/api/options/options.go index 6ffa571c..50f7dac8 100644 --- a/api/options/options.go +++ b/api/options/options.go @@ -99,6 +99,7 @@ func WithStringGalleries(galls string) AppOption { return func(o *Option) { if galls == "" { log.Debug().Msgf("no galleries to load") + o.Galleries = []gallery.Gallery{} return } var galleries []gallery.Gallery diff --git a/examples/insomnia/Insomnia_LocalAI.json b/examples/insomnia/Insomnia_LocalAI.json index dbcb313e..fed32f85 100644 --- a/examples/insomnia/Insomnia_LocalAI.json +++ b/examples/insomnia/Insomnia_LocalAI.json @@ -1 +1 @@ -{"_type":"export","__export_format":4,"__export_date":"2023-08-22T16:42:44.223Z","__export_source":"insomnia.desktop.app:v2023.5.5","resources":[{"_id":"req_395dfb3a370d470d907532dc94f91d3f","parentId":"fld_220b3e247cd940d0b615122f75b4da32","modified":1692719560587,"created":1692719560587,"url":"{{HOST}}:{{PORT}}/backend/monitor","name":"backend monitor","description":"","method":"GET","body":{"mimeType":"","text":"{\r\n \"model\": \"{{DEFAULT_MODEL}}\"\r\n}"},"parameters":[],"headers":[],"authentication":{},"metaSortKey":-1692719560587,"isPrivate":false,"settingStoreCookies":true,"settingSendCookies":true,"settingDisableRenderRequestBody":false,"settingEncodeUrl":true,"settingRebuildPath":true,"settingFollowRedirects":"global","_type":"request"},{"_id":"fld_220b3e247cd940d0b615122f75b4da32","parentId":"fld_911f4d2a05d84b59aff9d4924d1d3877","modified":1692719560584,"created":1692719560584,"name":"backend monitor","description":"","environment":{},"environmentPropertyOrder":null,"metaSortKey":-1692719560584,"_type":"request_group"},{"_id":"fld_911f4d2a05d84b59aff9d4924d1d3877","parentId":"wrk_76923a29272642e49208d65ffe7e885a","modified":1692719560581,"created":1692719560581,"name":"LocalAI","description":"","environment":{},"environmentPropertyOrder":null,"metaSortKey":-1692719560581,"_type":"request_group"},{"_id":"wrk_76923a29272642e49208d65ffe7e885a","parentId":null,"modified":1692719728510,"created":1692719560576,"name":"LocalAI","description":"","scope":"collection","_type":"workspace"},{"_id":"req_86b544c9e5324512ae2c830e8c2831ba","parentId":"fld_220b3e247cd940d0b615122f75b4da32","modified":1692722115897,"created":1692719560594,"url":"{{HOST}}:{{PORT}}/backend/shutdown","name":"backend/shutdown","description":"","method":"POST","body":{"mimeType":"application/json","text":"{\r\n \"model\": \"{{DEFAULT_MODEL}}\"\r\n}"},"parameters":[],"headers":[{"name":"Content-Type","value":"application/json"}],"authentication":{},"metaSortKey":-1692719560594,"isPrivate":false,"settingStoreCookies":true,"settingSendCookies":true,"settingDisableRenderRequestBody":false,"settingEncodeUrl":true,"settingRebuildPath":true,"settingFollowRedirects":"global","_type":"request"},{"_id":"req_1dec0983884c4b93acf3d8843108c003","parentId":"fld_2bc22ec3590240cd8d465fe084f5e14d","modified":1692722121942,"created":1692719560608,"url":"{{HOST}}:{{PORT}}/chat/completions","name":"chat completion (simple, 1 message)","description":"","method":"POST","body":{"mimeType":"application/json","text":"{\r\n \"model\": \"{{DEFAULT_MODEL}}\",\r\n \"messages\": [{\"role\": \"user\", \"content\": \"How could one use friction to cook an egg?\"}],\r\n \"max_tokens\": 256,\r\n \"temperature\": 0.2\r\n}"},"parameters":[],"headers":[{"name":"Content-Type","value":"application/json"}],"authentication":{},"metaSortKey":-1692719560611.5,"isPrivate":false,"settingStoreCookies":true,"settingSendCookies":true,"settingDisableRenderRequestBody":false,"settingEncodeUrl":true,"settingRebuildPath":true,"settingFollowRedirects":"global","_type":"request"},{"_id":"fld_2bc22ec3590240cd8d465fe084f5e14d","parentId":"fld_9584156ef4534e86b71735ed2b1e86e5","modified":1692721647772,"created":1692721647772,"name":"chat","description":"","environment":{},"environmentPropertyOrder":null,"metaSortKey":-1692721647772,"_type":"request_group"},{"_id":"fld_9584156ef4534e86b71735ed2b1e86e5","parentId":"fld_911f4d2a05d84b59aff9d4924d1d3877","modified":1692719560600,"created":1692719560600,"name":"llm text","description":"","environment":{},"environmentPropertyOrder":null,"metaSortKey":-1692719560600,"_type":"request_group"},{"_id":"req_e2ca5ec3feae4fd7b0b4162dad261f08","parentId":"fld_2bc22ec3590240cd8d465fe084f5e14d","modified":1692722124781,"created":1692719560615,"url":"{{HOST}}:{{PORT}}/chat/completions","name":"chat/completions (long)","description":"","method":"POST","body":{"mimeType":"application/json","text":"{\r\n \"model\": \"{{DEFAULT_MODEL}}\",\r\n \"messages\": [{\"role\": \"system\", \"content\": \"You are a helpful, intelligent pirate assistant. Always answer as helpfully as possible, while being a pirate and using pirate language. If a question does not make any sense, or is not factually coherent, explain why instead of answering something not correct. If you don't know the answer to a question, please don't share false information.\"},\r\n {\"role\": \"user\", \"content\": \"How could one use electricity to cook an egg?\"},\r\n {\"role\": \"assistant\",\r\n \"content\": \"Shiver me timbers! Using electricity to cook an egg? Well, matey, I reckon that be a right curious idea! *adjusts spectacles* Now, I ain't sure if this be possible, but I'll do me best to help ye out.\\n\\nFirst things first, ye gotta understand that electricity be a powerful force, and it can't just cook an egg on its own. Ye see, electricity be like a mighty wind that can make things happen, but it needs somethin' to work with. So, if ye want to use electricity to cook an egg, ye gotta find a way to harness that power and make it do the cookin'.\\n\\nNow, I know what ye might be thinkin': \\\"How do I harness the power of electricity to cook an egg?\\\" Well, matey, there be a few ways to do it. One way be to use a special device called an \\\"electric frying pan.\\\" This be a pan that has a built-in heating element that gets hot when ye plug it into a wall socket. When the element gets hot, ye can crack an egg into the pan and watch as it cook\"\r\n },\r\n {\"role\": \"user\", \"content\": \"I don't have one of those, just a raw wire and plenty of power! How do we get it done?\"}],\r\n \"max_tokens\": 1024,\r\n \"temperature\": 0.5\r\n}"},"parameters":[],"headers":[{"name":"Content-Type","value":"application/json"}],"authentication":{},"metaSortKey":-1692719560561.5,"isPrivate":false,"settingStoreCookies":true,"settingSendCookies":true,"settingDisableRenderRequestBody":false,"settingEncodeUrl":true,"settingRebuildPath":true,"settingFollowRedirects":"global","_type":"request"},{"_id":"req_0a66d6bfaff14b439ade641ce7469f16","parentId":"fld_2bc22ec3590240cd8d465fe084f5e14d","modified":1692722128583,"created":1692719560619,"url":"{{HOST}}:{{PORT}}/chat/completions","name":"chat/completions (stream)","description":"","method":"POST","body":{"mimeType":"application/json","text":"{\n \"model\": \"{{DEFAULT_MODEL}}\",\n \"messages\": [{\"role\": \"user\", \"content\": \"Explain how I can set sail on the ocean using only power generated by seagulls?\"}],\n \"max_tokens\": 256,\n \"temperature\": 0.9,\n \"stream\": true\n}"},"parameters":[],"headers":[{"name":"Content-Type","value":"application/json"}],"authentication":{},"metaSortKey":-1692719560511.5,"isPrivate":false,"settingStoreCookies":true,"settingSendCookies":true,"settingDisableRenderRequestBody":false,"settingEncodeUrl":true,"settingRebuildPath":true,"settingFollowRedirects":"global","_type":"request"},{"_id":"req_c1f5faeb3d8a4a0eb52c98d40c32f8f9","parentId":"fld_9584156ef4534e86b71735ed2b1e86e5","modified":1692722131493,"created":1692719560621,"url":"{{HOST}}:{{PORT}}/completions","name":"/completions","description":"","method":"POST","body":{"mimeType":"application/json","text":"{\r\n \"model\": \"{{DEFAULT_MODEL}}\",\r\n \"prompt\": \"function downloadFile(string url, string outputPath) {\",\r\n \"max_tokens\": 256,\r\n \"temperature\": 0.5\r\n}"},"parameters":[],"headers":[{"name":"Content-Type","value":"application/json"}],"authentication":{},"metaSortKey":-1692719560621,"isPrivate":false,"settingStoreCookies":true,"settingSendCookies":true,"settingDisableRenderRequestBody":false,"settingEncodeUrl":true,"settingRebuildPath":true,"settingFollowRedirects":"global","_type":"request"},{"_id":"req_f7ea027749b34f17a38f2bd549885f92","parentId":"fld_9584156ef4534e86b71735ed2b1e86e5","modified":1692722148262,"created":1692721748683,"url":"{{HOST}}:{{PORT}}/edits","name":"/edits","description":"","method":"POST","body":{"mimeType":"application/json","text":"{\n \"model\": \"{{DEFAULT_MODEL}}\",\n \"input\": \"What day of the wek is it?\",\n \"instruction\": \"Fix the spelling mistakes\"\n}"},"parameters":[],"headers":[{"name":"Content-Type","value":"application/json"}],"authentication":{},"metaSortKey":-1692719560616.25,"isPrivate":false,"settingStoreCookies":true,"settingSendCookies":true,"settingDisableRenderRequestBody":false,"settingEncodeUrl":true,"settingRebuildPath":true,"settingFollowRedirects":"global","_type":"request"},{"_id":"req_fe1e62ed6c384eccb0a75fbaacfd8e92","parentId":"fld_9584156ef4534e86b71735ed2b1e86e5","modified":1692722327277,"created":1692722256486,"url":"{{HOST}}:{{PORT}}/embeddings","name":"/embeddings","description":"","method":"POST","body":{"mimeType":"application/json","text":"{\n \"model\": \"{{DEFAULT_MODEL}}\",\n \"input\": \"A STRANGE GAME.\\nTHE ONLY WINNING MOVE IS NOT TO PLAY.\\n\\nHOW ABOUT A NICE GAME OF CHESS?\"\n}"},"parameters":[],"headers":[{"name":"Content-Type","value":"application/json"}],"authentication":{},"metaSortKey":-1692719560613.875,"isPrivate":false,"settingStoreCookies":true,"settingSendCookies":true,"settingDisableRenderRequestBody":false,"settingEncodeUrl":true,"settingRebuildPath":true,"settingFollowRedirects":"global","_type":"request"},{"_id":"req_2cec05b38d9049c5bee24dcac03d1214","parentId":"fld_9d70a564f6334ff6a5e9473d124d8ee6","modified":1692722140043,"created":1692719560627,"url":"{{HOST}}:{{PORT}}/models/apply","name":"model gallery apply","description":"","method":"POST","body":{"mimeType":"application/json","text":"{\n \"id\": \"huggingface@\",\n \"name\": \"test\"\n}"},"parameters":[],"headers":[{"name":"Content-Type","value":"application/json"}],"authentication":{},"metaSortKey":-1692719560627,"isPrivate":false,"settingStoreCookies":true,"settingSendCookies":true,"settingDisableRenderRequestBody":false,"settingEncodeUrl":true,"settingRebuildPath":true,"settingFollowRedirects":"global","_type":"request"},{"_id":"fld_9d70a564f6334ff6a5e9473d124d8ee6","parentId":"fld_911f4d2a05d84b59aff9d4924d1d3877","modified":1692719560625,"created":1692719560625,"name":"model gallery","description":"","environment":{},"environmentPropertyOrder":null,"metaSortKey":-1692719560625,"_type":"request_group"},{"_id":"req_03c6b65bce1541fa9a7751c7ed8a7f40","parentId":"fld_9d70a564f6334ff6a5e9473d124d8ee6","modified":1692720180884,"created":1692719560630,"url":"{{HOST}}:{{PORT}}/models/available","name":"model gallery list","description":"","method":"GET","body":{"mimeType":"","text":"{\n}"},"parameters":[],"headers":[],"authentication":{},"metaSortKey":-1692719560630,"isPrivate":false,"settingStoreCookies":true,"settingSendCookies":true,"settingDisableRenderRequestBody":false,"settingEncodeUrl":true,"settingRebuildPath":true,"settingFollowRedirects":"global","_type":"request"},{"_id":"req_d9f9175a0e1e4a8facf19e365c89403b","parentId":"fld_4a966fb07756459d9d7c1f5a5561228f","modified":1692722441541,"created":1692722374898,"url":"{{HOST}}:{{PORT}}/tts","name":"/tts","description":"","method":"POST","body":{"mimeType":"application/json","text":"{\n \"model\": \"{{DEFAULT_MODEL}}\",\n \"input\": \"A STRANGE GAME.\\nTHE ONLY WINNING MOVE IS NOT TO PLAY.\\n\\nHOW ABOUT A NICE GAME OF CHESS?\"\n}"},"parameters":[],"headers":[{"name":"Content-Type","value":"application/json"}],"authentication":{},"metaSortKey":-1692719560630,"isPrivate":false,"settingStoreCookies":true,"settingSendCookies":true,"settingDisableRenderRequestBody":false,"settingEncodeUrl":true,"settingRebuildPath":true,"settingFollowRedirects":"global","_type":"request"},{"_id":"fld_4a966fb07756459d9d7c1f5a5561228f","parentId":"fld_911f4d2a05d84b59aff9d4924d1d3877","modified":1692722533229,"created":1692722439678,"name":"tts","description":"","environment":{},"environmentPropertyOrder":null,"metaSortKey":-1692719560612.5,"_type":"request_group"},{"_id":"req_527fdc87fd404a2a8f1c401fb7a0e642","parentId":"fld_911f4d2a05d84b59aff9d4924d1d3877","modified":1692719560635,"created":1692719560635,"url":"{{HOST}}:{{PORT}}/models","name":"get models list","description":"","method":"GET","body":{},"parameters":[],"headers":[],"authentication":{},"metaSortKey":-1692719560635,"isPrivate":false,"settingStoreCookies":true,"settingSendCookies":true,"settingDisableRenderRequestBody":false,"settingEncodeUrl":true,"settingRebuildPath":true,"settingFollowRedirects":"global","_type":"request"},{"_id":"env_b7b4937221a512490c06b95c4986a60800670c2f","parentId":"wrk_76923a29272642e49208d65ffe7e885a","modified":1692720330621,"created":1692719566107,"name":"Base Environment","data":{"PORT":8080,"DEFAULT_MODEL":"gpt-3.5-turbo"},"dataPropertyOrder":{"&":["PORT","DEFAULT_MODEL"]},"color":null,"isPrivate":false,"metaSortKey":1692719566107,"_type":"environment"},{"_id":"jar_b7b4937221a512490c06b95c4986a60800670c2f","parentId":"wrk_76923a29272642e49208d65ffe7e885a","modified":1692719566120,"created":1692719566120,"name":"Default Jar","cookies":[],"_type":"cookie_jar"},{"_id":"env_4a68ee3db2714cc69d81419acd6b2c31","parentId":"env_b7b4937221a512490c06b95c4986a60800670c2f","modified":1692719629896,"created":1692719607346,"name":"localhost","data":{"HOST":"localhost"},"dataPropertyOrder":{"&":["HOST"]},"color":null,"isPrivate":false,"metaSortKey":1692719607346,"_type":"environment"}]} \ No newline at end of file +{"_type":"export","__export_format":4,"__export_date":"2023-09-01T05:11:43.695Z","__export_source":"insomnia.desktop.app:v2023.5.7","resources":[{"_id":"req_527fdc87fd404a2a8f1c401fb7a0e642","parentId":"fld_911f4d2a05d84b59aff9d4924d1d3877","modified":1692719560635,"created":1692719560635,"url":"{{HOST}}:{{PORT}}/models","name":"get models list","description":"","method":"GET","body":{},"parameters":[],"headers":[],"authentication":{},"metaSortKey":-1692719560635,"isPrivate":false,"settingStoreCookies":true,"settingSendCookies":true,"settingDisableRenderRequestBody":false,"settingEncodeUrl":true,"settingRebuildPath":true,"settingFollowRedirects":"global","_type":"request"},{"_id":"fld_911f4d2a05d84b59aff9d4924d1d3877","parentId":"wrk_76923a29272642e49208d65ffe7e885a","modified":1692719560581,"created":1692719560581,"name":"LocalAI","description":"","environment":{},"environmentPropertyOrder":null,"metaSortKey":-1692719560581,"_type":"request_group"},{"_id":"wrk_76923a29272642e49208d65ffe7e885a","parentId":null,"modified":1692719728510,"created":1692719560576,"name":"LocalAI","description":"","scope":"collection","_type":"workspace"},{"_id":"req_03c6b65bce1541fa9a7751c7ed8a7f40","parentId":"fld_9d70a564f6334ff6a5e9473d124d8ee6","modified":1693542905270,"created":1692719560630,"url":"{{HOST}}:{{PORT}}/models/available","name":"list MODELS in galleries","description":"","method":"GET","body":{"mimeType":"","text":"{\n}"},"parameters":[],"headers":[],"authentication":{},"metaSortKey":-1692719560630,"isPrivate":false,"settingStoreCookies":true,"settingSendCookies":true,"settingDisableRenderRequestBody":false,"settingEncodeUrl":true,"settingRebuildPath":true,"settingFollowRedirects":"global","_type":"request"},{"_id":"fld_9d70a564f6334ff6a5e9473d124d8ee6","parentId":"fld_911f4d2a05d84b59aff9d4924d1d3877","modified":1692719560625,"created":1692719560625,"name":"model gallery","description":"","environment":{},"environmentPropertyOrder":null,"metaSortKey":-1692719560625,"_type":"request_group"},{"_id":"req_9cfc92cb7f5c43b6bea7992d54e94eca","parentId":"fld_9d70a564f6334ff6a5e9473d124d8ee6","modified":1693542894779,"created":1693526412262,"url":"{{HOST}}:{{PORT}}/models/galleries","name":"list model GALLERIES","description":"","method":"GET","body":{"mimeType":"","text":"{\n}"},"parameters":[],"headers":[],"authentication":{},"metaSortKey":-1692719560628.5,"isPrivate":false,"settingStoreCookies":true,"settingSendCookies":true,"settingDisableRenderRequestBody":false,"settingEncodeUrl":true,"settingRebuildPath":true,"settingFollowRedirects":"global","_type":"request"},{"_id":"req_2cec05b38d9049c5bee24dcac03d1214","parentId":"fld_9d70a564f6334ff6a5e9473d124d8ee6","modified":1692773355999,"created":1692719560627,"url":"{{HOST}}:{{PORT}}/models/apply","name":"model gallery apply","description":"","method":"POST","body":{"mimeType":"application/json","text":"{\n \"id\": \"huggingface@TheBloke/wizardlm-13b-v1.2-ggml/wizardlm-13b-v1.2.ggmlv3.q4_0.bin\",\n \"name\": \"test\"\n}"},"parameters":[],"headers":[{"name":"Content-Type","value":"application/json"}],"authentication":{},"metaSortKey":-1692719560627,"isPrivate":false,"settingStoreCookies":true,"settingSendCookies":true,"settingDisableRenderRequestBody":false,"settingEncodeUrl":true,"settingRebuildPath":true,"settingFollowRedirects":"global","_type":"request"},{"_id":"req_f91d77c51f1d41d9b035ed083275441a","parentId":"fld_9d70a564f6334ff6a5e9473d124d8ee6","modified":1693545040139,"created":1693527252021,"url":"{{HOST}}:{{PORT}}/models/galleries","name":"add model gallery","description":"","method":"POST","body":{"mimeType":"application/json","text":"{\n \"url\": \"file:///home/dave/projects/model-gallery/huggingface/TheBloke__CodeLlama-7B-Instruct-GGML.yaml\",\n \"name\": \"test\"\n}"},"parameters":[],"headers":[{"name":"Content-Type","value":"application/json"}],"authentication":{},"metaSortKey":-1692719560625.5,"isPrivate":false,"settingStoreCookies":true,"settingSendCookies":true,"settingDisableRenderRequestBody":false,"settingEncodeUrl":true,"settingRebuildPath":true,"settingFollowRedirects":"global","_type":"request"},{"_id":"req_626fee90a5a04947a8545062e9a52350","parentId":"fld_9d70a564f6334ff6a5e9473d124d8ee6","modified":1693544472707,"created":1693544399269,"url":"{{HOST}}:{{PORT}}/models/galleries","name":"delete model gallery","description":"","method":"DELETE","body":{"mimeType":"application/json","text":"{\n \"name\": \"test\"\n}"},"parameters":[],"headers":[{"name":"Content-Type","value":"application/json"}],"authentication":{},"metaSortKey":-1692719560624.75,"isPrivate":false,"settingStoreCookies":true,"settingSendCookies":true,"settingDisableRenderRequestBody":false,"settingEncodeUrl":true,"settingRebuildPath":true,"settingFollowRedirects":"global","_type":"request"},{"_id":"req_e1e150aa05c54bb49f5bceca33d4d917","parentId":"fld_9d70a564f6334ff6a5e9473d124d8ee6","modified":1693537478093,"created":1692826085669,"url":"{{HOST}}:{{PORT}}/models/apply","name":"model gallery apply (gist)","description":"","method":"POST","body":{"mimeType":"application/json","text":"{\n \"id\": \"TheBloke__CodeLlama-7B-Instruct-GGML__codellama-7b-instruct.ggmlv3.Q2_K.bin\"\n}"},"parameters":[],"headers":[{"name":"Content-Type","value":"application/json"}],"authentication":{},"metaSortKey":-1692719560624,"isPrivate":false,"settingStoreCookies":true,"settingSendCookies":true,"settingDisableRenderRequestBody":false,"settingEncodeUrl":true,"settingRebuildPath":true,"settingFollowRedirects":"global","_type":"request"},{"_id":"req_d9f9175a0e1e4a8facf19e365c89403b","parentId":"fld_4a966fb07756459d9d7c1f5a5561228f","modified":1692722441541,"created":1692722374898,"url":"{{HOST}}:{{PORT}}/tts","name":"/tts","description":"","method":"POST","body":{"mimeType":"application/json","text":"{\n \"model\": \"{{DEFAULT_MODEL}}\",\n \"input\": \"A STRANGE GAME.\\nTHE ONLY WINNING MOVE IS NOT TO PLAY.\\n\\nHOW ABOUT A NICE GAME OF CHESS?\"\n}"},"parameters":[],"headers":[{"name":"Content-Type","value":"application/json"}],"authentication":{},"metaSortKey":-1692719560630,"isPrivate":false,"settingStoreCookies":true,"settingSendCookies":true,"settingDisableRenderRequestBody":false,"settingEncodeUrl":true,"settingRebuildPath":true,"settingFollowRedirects":"global","_type":"request"},{"_id":"fld_4a966fb07756459d9d7c1f5a5561228f","parentId":"fld_911f4d2a05d84b59aff9d4924d1d3877","modified":1692722533229,"created":1692722439678,"name":"tts","description":"","environment":{},"environmentPropertyOrder":null,"metaSortKey":-1692719560612.5,"_type":"request_group"},{"_id":"req_1dec0983884c4b93acf3d8843108c003","parentId":"fld_2bc22ec3590240cd8d465fe084f5e14d","modified":1692722121942,"created":1692719560608,"url":"{{HOST}}:{{PORT}}/chat/completions","name":"chat completion (simple, 1 message)","description":"","method":"POST","body":{"mimeType":"application/json","text":"{\r\n \"model\": \"{{DEFAULT_MODEL}}\",\r\n \"messages\": [{\"role\": \"user\", \"content\": \"How could one use friction to cook an egg?\"}],\r\n \"max_tokens\": 256,\r\n \"temperature\": 0.2\r\n}"},"parameters":[],"headers":[{"name":"Content-Type","value":"application/json"}],"authentication":{},"metaSortKey":-1692719560611.5,"isPrivate":false,"settingStoreCookies":true,"settingSendCookies":true,"settingDisableRenderRequestBody":false,"settingEncodeUrl":true,"settingRebuildPath":true,"settingFollowRedirects":"global","_type":"request"},{"_id":"fld_2bc22ec3590240cd8d465fe084f5e14d","parentId":"fld_9584156ef4534e86b71735ed2b1e86e5","modified":1692721647772,"created":1692721647772,"name":"chat","description":"","environment":{},"environmentPropertyOrder":null,"metaSortKey":-1692721647772,"_type":"request_group"},{"_id":"fld_9584156ef4534e86b71735ed2b1e86e5","parentId":"fld_911f4d2a05d84b59aff9d4924d1d3877","modified":1692719560600,"created":1692719560600,"name":"llm text","description":"","environment":{},"environmentPropertyOrder":null,"metaSortKey":-1692719560600,"_type":"request_group"},{"_id":"req_e2ca5ec3feae4fd7b0b4162dad261f08","parentId":"fld_2bc22ec3590240cd8d465fe084f5e14d","modified":1692722124781,"created":1692719560615,"url":"{{HOST}}:{{PORT}}/chat/completions","name":"chat/completions (long)","description":"","method":"POST","body":{"mimeType":"application/json","text":"{\r\n \"model\": \"{{DEFAULT_MODEL}}\",\r\n \"messages\": [{\"role\": \"system\", \"content\": \"You are a helpful, intelligent pirate assistant. Always answer as helpfully as possible, while being a pirate and using pirate language. If a question does not make any sense, or is not factually coherent, explain why instead of answering something not correct. If you don't know the answer to a question, please don't share false information.\"},\r\n {\"role\": \"user\", \"content\": \"How could one use electricity to cook an egg?\"},\r\n {\"role\": \"assistant\",\r\n \"content\": \"Shiver me timbers! Using electricity to cook an egg? Well, matey, I reckon that be a right curious idea! *adjusts spectacles* Now, I ain't sure if this be possible, but I'll do me best to help ye out.\\n\\nFirst things first, ye gotta understand that electricity be a powerful force, and it can't just cook an egg on its own. Ye see, electricity be like a mighty wind that can make things happen, but it needs somethin' to work with. So, if ye want to use electricity to cook an egg, ye gotta find a way to harness that power and make it do the cookin'.\\n\\nNow, I know what ye might be thinkin': \\\"How do I harness the power of electricity to cook an egg?\\\" Well, matey, there be a few ways to do it. One way be to use a special device called an \\\"electric frying pan.\\\" This be a pan that has a built-in heating element that gets hot when ye plug it into a wall socket. When the element gets hot, ye can crack an egg into the pan and watch as it cook\"\r\n },\r\n {\"role\": \"user\", \"content\": \"I don't have one of those, just a raw wire and plenty of power! How do we get it done?\"}],\r\n \"max_tokens\": 1024,\r\n \"temperature\": 0.5\r\n}"},"parameters":[],"headers":[{"name":"Content-Type","value":"application/json"}],"authentication":{},"metaSortKey":-1692719560561.5,"isPrivate":false,"settingStoreCookies":true,"settingSendCookies":true,"settingDisableRenderRequestBody":false,"settingEncodeUrl":true,"settingRebuildPath":true,"settingFollowRedirects":"global","_type":"request"},{"_id":"req_0a66d6bfaff14b439ade641ce7469f16","parentId":"fld_2bc22ec3590240cd8d465fe084f5e14d","modified":1692722128583,"created":1692719560619,"url":"{{HOST}}:{{PORT}}/chat/completions","name":"chat/completions (stream)","description":"","method":"POST","body":{"mimeType":"application/json","text":"{\n \"model\": \"{{DEFAULT_MODEL}}\",\n \"messages\": [{\"role\": \"user\", \"content\": \"Explain how I can set sail on the ocean using only power generated by seagulls?\"}],\n \"max_tokens\": 256,\n \"temperature\": 0.9,\n \"stream\": true\n}"},"parameters":[],"headers":[{"name":"Content-Type","value":"application/json"}],"authentication":{},"metaSortKey":-1692719560511.5,"isPrivate":false,"settingStoreCookies":true,"settingSendCookies":true,"settingDisableRenderRequestBody":false,"settingEncodeUrl":true,"settingRebuildPath":true,"settingFollowRedirects":"global","_type":"request"},{"_id":"req_c1f5faeb3d8a4a0eb52c98d40c32f8f9","parentId":"fld_9584156ef4534e86b71735ed2b1e86e5","modified":1692722131493,"created":1692719560621,"url":"{{HOST}}:{{PORT}}/completions","name":"/completions","description":"","method":"POST","body":{"mimeType":"application/json","text":"{\r\n \"model\": \"{{DEFAULT_MODEL}}\",\r\n \"prompt\": \"function downloadFile(string url, string outputPath) {\",\r\n \"max_tokens\": 256,\r\n \"temperature\": 0.5\r\n}"},"parameters":[],"headers":[{"name":"Content-Type","value":"application/json"}],"authentication":{},"metaSortKey":-1692719560621,"isPrivate":false,"settingStoreCookies":true,"settingSendCookies":true,"settingDisableRenderRequestBody":false,"settingEncodeUrl":true,"settingRebuildPath":true,"settingFollowRedirects":"global","_type":"request"},{"_id":"req_f7ea027749b34f17a38f2bd549885f92","parentId":"fld_9584156ef4534e86b71735ed2b1e86e5","modified":1692722148262,"created":1692721748683,"url":"{{HOST}}:{{PORT}}/edits","name":"/edits","description":"","method":"POST","body":{"mimeType":"application/json","text":"{\n \"model\": \"{{DEFAULT_MODEL}}\",\n \"input\": \"What day of the wek is it?\",\n \"instruction\": \"Fix the spelling mistakes\"\n}"},"parameters":[],"headers":[{"name":"Content-Type","value":"application/json"}],"authentication":{},"metaSortKey":-1692719560616.25,"isPrivate":false,"settingStoreCookies":true,"settingSendCookies":true,"settingDisableRenderRequestBody":false,"settingEncodeUrl":true,"settingRebuildPath":true,"settingFollowRedirects":"global","_type":"request"},{"_id":"req_fe1e62ed6c384eccb0a75fbaacfd8e92","parentId":"fld_9584156ef4534e86b71735ed2b1e86e5","modified":1692722327277,"created":1692722256486,"url":"{{HOST}}:{{PORT}}/embeddings","name":"/embeddings","description":"","method":"POST","body":{"mimeType":"application/json","text":"{\n \"model\": \"{{DEFAULT_MODEL}}\",\n \"input\": \"A STRANGE GAME.\\nTHE ONLY WINNING MOVE IS NOT TO PLAY.\\n\\nHOW ABOUT A NICE GAME OF CHESS?\"\n}"},"parameters":[],"headers":[{"name":"Content-Type","value":"application/json"}],"authentication":{},"metaSortKey":-1692719560613.875,"isPrivate":false,"settingStoreCookies":true,"settingSendCookies":true,"settingDisableRenderRequestBody":false,"settingEncodeUrl":true,"settingRebuildPath":true,"settingFollowRedirects":"global","_type":"request"},{"_id":"req_86b544c9e5324512ae2c830e8c2831ba","parentId":"fld_220b3e247cd940d0b615122f75b4da32","modified":1692722115897,"created":1692719560594,"url":"{{HOST}}:{{PORT}}/backend/shutdown","name":"backend/shutdown","description":"","method":"POST","body":{"mimeType":"application/json","text":"{\r\n \"model\": \"{{DEFAULT_MODEL}}\"\r\n}"},"parameters":[],"headers":[{"name":"Content-Type","value":"application/json"}],"authentication":{},"metaSortKey":-1692719560594,"isPrivate":false,"settingStoreCookies":true,"settingSendCookies":true,"settingDisableRenderRequestBody":false,"settingEncodeUrl":true,"settingRebuildPath":true,"settingFollowRedirects":"global","_type":"request"},{"_id":"fld_220b3e247cd940d0b615122f75b4da32","parentId":"fld_911f4d2a05d84b59aff9d4924d1d3877","modified":1692719560584,"created":1692719560584,"name":"backend monitor","description":"","environment":{},"environmentPropertyOrder":null,"metaSortKey":-1692719560584,"_type":"request_group"},{"_id":"req_395dfb3a370d470d907532dc94f91d3f","parentId":"fld_220b3e247cd940d0b615122f75b4da32","modified":1692719560587,"created":1692719560587,"url":"{{HOST}}:{{PORT}}/backend/monitor","name":"backend monitor","description":"","method":"GET","body":{"mimeType":"","text":"{\r\n \"model\": \"{{DEFAULT_MODEL}}\"\r\n}"},"parameters":[],"headers":[],"authentication":{},"metaSortKey":-1692719560587,"isPrivate":false,"settingStoreCookies":true,"settingSendCookies":true,"settingDisableRenderRequestBody":false,"settingEncodeUrl":true,"settingRebuildPath":true,"settingFollowRedirects":"global","_type":"request"},{"_id":"env_b7b4937221a512490c06b95c4986a60800670c2f","parentId":"wrk_76923a29272642e49208d65ffe7e885a","modified":1692720330621,"created":1692719566107,"name":"Base Environment","data":{"PORT":8080,"DEFAULT_MODEL":"gpt-3.5-turbo"},"dataPropertyOrder":{"&":["PORT","DEFAULT_MODEL"]},"color":null,"isPrivate":false,"metaSortKey":1692719566107,"_type":"environment"},{"_id":"jar_b7b4937221a512490c06b95c4986a60800670c2f","parentId":"wrk_76923a29272642e49208d65ffe7e885a","modified":1692719566120,"created":1692719566120,"name":"Default Jar","cookies":[],"_type":"cookie_jar"},{"_id":"env_4a68ee3db2714cc69d81419acd6b2c31","parentId":"env_b7b4937221a512490c06b95c4986a60800670c2f","modified":1692719629896,"created":1692719607346,"name":"localhost","data":{"HOST":"localhost"},"dataPropertyOrder":{"&":["HOST"]},"color":null,"isPrivate":false,"metaSortKey":1692719607346,"_type":"environment"}]} \ No newline at end of file diff --git a/pkg/gallery/gallery.go b/pkg/gallery/gallery.go index fbc59d62..eacd9aa1 100644 --- a/pkg/gallery/gallery.go +++ b/pkg/gallery/gallery.go @@ -98,8 +98,8 @@ func InstallModelFromGalleryByName(galleries []Gallery, name string, basePath st } // List available models -// Models galleries are a list of json files that are hosted on a remote server (for example github). -// Each json file contains a list of models that can be downloaded and optionally overrides to define a new model setting. +// Models galleries are a list of yaml files that are hosted on a remote server (for example github). +// Each yaml file contains a list of models that can be downloaded and optionally overrides to define a new model setting. func AvailableGalleryModels(galleries []Gallery, basePath string) ([]*GalleryModel, error) { var models []*GalleryModel diff --git a/pkg/utils/uri.go b/pkg/utils/uri.go index b79b5fc3..16f4dbf8 100644 --- a/pkg/utils/uri.go +++ b/pkg/utils/uri.go @@ -5,6 +5,7 @@ import ( "io" "net/http" "os" + "path/filepath" "strings" ) @@ -32,8 +33,13 @@ func GetURI(url string, f func(url string, i []byte) error) error { if strings.HasPrefix(url, "file://") { rawURL := strings.TrimPrefix(url, "file://") + // checks if the file is symbolic, and resolve if so - otherwise, this function returns the path unmodified. + resolvedFile, err := filepath.EvalSymlinks(rawURL) + if err != nil { + return err + } // Read the response body - body, err := os.ReadFile(rawURL) + body, err := os.ReadFile(resolvedFile) if err != nil { return err }