package runtime_test import ( "strings" "testing" "github.com/krow/krow-backend/go-api/internal/config" "github.com/krow/krow-backend/go-api/internal/runtime" ) // Which embedder a deployment actually gets. // // The reason this is worth testing at all: all three providers return vectors, // and retrieval works with any of them. A deployment running the stand-in looks // exactly like one running a real model — same shape of result, same citations, // same confidence — until somebody phrases a question differently. There is no // symptom to notice, so the choice has to be asserted rather than observed. func embedderFor(k config.KnowledgeConfig, env string) string { e := runtime.NewEmbedder(config.Config{AppEnv: env, Knowledge: k}) if e == nil { return "none" } return e.Model() } func TestTheNamedProviderWins(t *testing.T) { // Explicit beats inferred. A deployment that names ollama gets ollama even // with a Voyage key sitting in the environment — otherwise a leftover // credential silently decides where tenant text goes. got := embedderFor(config.KnowledgeConfig{ EmbedProvider: "ollama", EmbedAPIKey: "pa-a-real-looking-key", }, "development") if !strings.HasPrefix(got, "ollama/") { t.Errorf("named ollama and got %q; a stray credential overrode an explicit choice", got) } got = embedderFor(config.KnowledgeConfig{ EmbedProvider: "voyage", EmbedAPIKey: "pa-key", EmbedBaseURL: "http://localhost:11434", }, "development") if strings.HasPrefix(got, "ollama/") { t.Errorf("named voyage and got %q", got) } } func TestWithNothingConfiguredThereIsNoEmbedder(t *testing.T) { // Nil, not a hosted client with an empty key. Both end up keyword-only, but // nil says so once at wiring time instead of failing one HTTP call per // query to learn the same thing. if got := embedderFor(config.KnowledgeConfig{}, "development"); got != "none" { t.Errorf("an unconfigured deployment got %q, want no embedder", got) } } func TestNamingVoyageWithoutAKeyIsNotAnEmbedder(t *testing.T) { // Named but unusable. Degrading honestly beats failing a request per query // on a credential nobody set. if got := embedderFor(config.KnowledgeConfig{EmbedProvider: "voyage"}, "development"); got != "none" { t.Errorf("voyage with no key produced %q", got) } } func TestInferencePrefersTheLocalModel(t *testing.T) { // With nothing named, a configured local model wins over a hosted one: it // costs nothing and keeps tenant text on the host, and both are real // semantic embedders. got := embedderFor(config.KnowledgeConfig{ EmbedBaseURL: "http://localhost:11434", EmbedAPIKey: "pa-key", }, "development") if !strings.HasPrefix(got, "ollama/") { t.Errorf("inferred %q; a local model should win when both are available", got) } } func TestTheStandInRefusesToRunInProduction(t *testing.T) { // It is not semantic. A production corpus indexed with it retrieves on word // overlap alone — which looks like working retrieval and is not, which is // exactly why the refusal is in the code and not in a comment. e := runtime.NewEmbedder(config.Config{ AppEnv: "production", Knowledge: config.KnowledgeConfig{EmbedProvider: "lexical"}, }) if e == nil { t.Fatal("expected the stand-in, refusing at use rather than at wiring") } if _, err := e.Embed(t.Context(), []string{"x"}, "document"); err == nil { t.Error("the stand-in embedded text in production") } // And in development it works, because that is what it is for. dev := runtime.NewEmbedder(config.Config{ AppEnv: "development", Knowledge: config.KnowledgeConfig{EmbedProvider: "lexical"}, }) if _, err := dev.Embed(t.Context(), []string{"x"}, "document"); err != nil { t.Errorf("the stand-in refused in development: %v", err) } }