mirror of
https://github.com/mudler/LocalAI.git
synced 2026-09-29 17:44:30 -04:00
fix(localai-proxy): treat upstream_url as the server root, ignoring /v1
The failover prober cuts upstream_url at /v1, but Load kept the path, so an upstream_url ending in /v1 probed healthy while every request went to /v1/v1/... and got a 404, which never trips the target. Cut the path at /v1 in Load too, with a warning. Assisted-by: Claude:claude-opus-5-5 Signed-off-by: Ettore Di Giacinto <mudler@localai.io>
This commit is contained in:
1 parent
e61bd2d78e
commit
fe8519d7b5
2 files changed
+28
-2
No files matched your search
@@ -74,6 +74,17 @@ func (p *LocalAIProxy) Load(opts *pb.ModelOptions) error {
|
||||
return fmt.Errorf("localai-proxy: proxy.upstream_url %q must be an http(s) URL with a host", raw)
|
||||
}
|
||||
|
||||
// Every request path starts with /v1, so upstream_url is the server's
|
||||
// root. A URL copied from an OpenAI-style config ends in /v1 (or a full
|
||||
// endpoint): cut the path at /v1 as the failover prober does, or the
|
||||
// prober reports the target healthy while every request 404s.
|
||||
base := strings.TrimRight(raw, "/")
|
||||
if i := strings.Index(u.Path, "/v1"); i >= 0 {
|
||||
base = strings.TrimRight(u.Scheme+"://"+u.Host+u.Path[:i], "/")
|
||||
xlog.Warn("localai-proxy: proxy.upstream_url should be the server root; ignoring its /v1 path",
|
||||
"upstream_url", raw, "using", base)
|
||||
}
|
||||
|
||||
// There is no translate mode: the upstream always speaks LocalAI's API.
|
||||
if po.GetMode() != "" || po.GetProvider() != "" {
|
||||
xlog.Warn("localai-proxy: proxy.mode and proxy.provider are ignored",
|
||||
@@ -106,13 +117,13 @@ func (p *LocalAIProxy) Load(opts *pb.ModelOptions) error {
|
||||
}
|
||||
|
||||
p.cfg.Store(&proxyConfig{
|
||||
base: strings.TrimRight(raw, "/"),
|
||||
base: base,
|
||||
upstreamModel: model,
|
||||
apiKey: key,
|
||||
realtimePipeline: pipeline,
|
||||
timeout: timeout,
|
||||
})
|
||||
xlog.Info("localai-proxy: ready", "upstream", raw, "upstream_model", model,
|
||||
xlog.Info("localai-proxy: ready", "upstream", base, "upstream_model", model,
|
||||
"has_key", key != "", "realtime_pipeline", pipeline)
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -63,6 +63,21 @@ var _ = Describe("localai-proxy", func() {
|
||||
Expect(cfg.timeout).To(Equal(7 * time.Second))
|
||||
})
|
||||
|
||||
DescribeTable("strips an OpenAI-style /v1 suffix, as the failover prober does",
|
||||
func(suffix string) {
|
||||
p := loadProxy(up, func(o *pb.ModelOptions) { o.Proxy.UpstreamUrl = up.URL + suffix })
|
||||
Expect(p.cfg.Load().base).To(Equal(up.URL))
|
||||
},
|
||||
Entry("/v1", "/v1"),
|
||||
Entry("/v1/", "/v1/"),
|
||||
Entry("a full endpoint path", "/v1/chat/completions"),
|
||||
)
|
||||
|
||||
It("keeps a path prefix in front of /v1", func() {
|
||||
p := loadProxy(up, func(o *pb.ModelOptions) { o.Proxy.UpstreamUrl = up.URL + "/localai/v1" })
|
||||
Expect(p.cfg.Load().base).To(Equal(up.URL + "/localai"))
|
||||
})
|
||||
|
||||
It("falls back to the model name when upstream_model is unset", func() {
|
||||
p := loadProxy(up, func(o *pb.ModelOptions) { o.Proxy.UpstreamModel = "" })
|
||||
Expect(p.model("")).To(Equal("local-name"))
|
||||
|
||||
Reference in new issue
Block a user