diff --git a/core/application/router_factories.go b/core/application/router_factories.go index dff2d5798..0ef663019 100644 --- a/core/application/router_factories.go +++ b/core/application/router_factories.go @@ -13,6 +13,7 @@ import ( "github.com/mudler/LocalAI/core/backend" "github.com/mudler/LocalAI/core/config" "github.com/mudler/LocalAI/core/services/routing/corpus" + "github.com/mudler/LocalAI/pkg/reasoning" "gopkg.in/yaml.v3" ) @@ -119,7 +120,14 @@ func (a *Application) EmbedderFingerprint(modelName string) (string, error) { if cfg == nil { return "", fmt.Errorf("embedding model %q not available", modelName) } - raw, err := yaml.Marshal(cfg) + // Reasoning never changes the embedding space, but a chat request against + // this model may persist probed reasoning slots into the shared config + // (persistProbedReasoning). Hashing them would move the fingerprint on + // such a request and leave every knn router built on this embedder failing + // with ErrLiveEmbeddingMismatch until restart. + hashed := *cfg + hashed.ReasoningConfig = reasoning.Config{} + raw, err := yaml.Marshal(&hashed) if err != nil { return "", fmt.Errorf("fingerprint embedding model %q config: %w", modelName, err) } diff --git a/core/application/router_factories_test.go b/core/application/router_factories_test.go index 7f50d47c2..6e20332d8 100644 --- a/core/application/router_factories_test.go +++ b/core/application/router_factories_test.go @@ -137,6 +137,27 @@ var _ = Describe("router_factories lazy config resolution", func() { Expect(updated).NotTo(Equal(replaced)) }) + It("stays stable when a chat request persists probed reasoning slots", func() { + // A chat completion sent to an embedding model whose config sets + // use_tokenizer_template runs the thinking probe, and + // persistProbedReasoning writes the probed reasoning slots back into + // the shared config. Reasoning never changes the embedding space, so + // the fingerprint must not move — otherwise every knn router built + // on this embedder fails with ErrLiveEmbeddingMismatch until restart. + writeCfg("emb-probe", "llama-cpp") + before, err := app.EmbedderFingerprint("emb-probe") + Expect(err).NotTo(HaveOccurred()) + + disable, prefill := true, false + app.backendLoader.UpdateModelConfig("emb-probe", func(c *config.ModelConfig) { + c.ReasoningConfig.DisableReasoning = &disable + c.ReasoningConfig.DisableReasoningTagPrefill = &prefill + }) + after, err := app.EmbedderFingerprint("emb-probe") + Expect(err).NotTo(HaveOccurred()) + Expect(after).To(Equal(before)) + }) + It("rejects an unknown model", func() { _, err := app.EmbedderFingerprint("missing") Expect(err).To(HaveOccurred())