mirror of
https://github.com/diegosouzapw/OmniRoute.git
synced 2026-09-13 18:32:12 +03:00
On dashboard/memory?tab=engine the Embedding Model quick-select (and the rerank selector) built their lists from a keyword heuristic over the CHAT catalog (AI_MODELS) plus OpenRouter live discovery. Providers whose embedding models are not in that catalog never appeared — mistral, gemini, nvidia nim, groq, vercel-ai-gateway and others that serve embeddings on a standard OpenAI-compatible /embeddings endpoint — and typing such a model by hand failed at runtime with "Unknown embedding provider". The fix is one generic mechanism rather than a list of per-provider patches: deriveEmbeddingProviderForChatProvider() turns any chat-registry entry with a /chat/completions base into an OpenAI-compatible /embeddings config, with curated EMBEDDING_PROVIDERS entries always winning; the embeddings service resolves a derived config for unknown-but-configured providers instead of rejecting them; deriveRerankProviderForChatProvider() does the same for Cohere-compatible /rerank; and both memory selectors fall back to a free-text provider/model input when no static catalog exists. No provider is special-cased by name, so adding one to the chat registry now makes it embedding- and rerank-capable here automatically. Verified on the current release tip: merged clean, typecheck:core clean, check:cycles OK across 417 files, and 35/35 across the PR's five new suites (qdrant-quick-select-catalog, memory-provider-listings, rerank-provider-listings, embedding-generic-provider-fallback, rerank-generic-provider-fallback) plus the updated hard-session-lease-bypass-inventory and embeddings-handler. Note: the base-red disclaimer in the description referenced #9985 against release/v3.8.50 — that window is closed and the current tip carries no open base-red, so nothing was inherited here. Thanks @rqzbeh — deriving the capability instead of enumerating providers is the version of this that stays correct as the registry grows.
78 lines
3.0 KiB
TypeScript
78 lines
3.0 KiB
TypeScript
import { describe, it } from "node:test";
|
|
import assert from "node:assert/strict";
|
|
import {
|
|
getEmbeddingProvider,
|
|
parseEmbeddingModel,
|
|
deriveEmbeddingProviderForChatProvider,
|
|
type EmbeddingProvider,
|
|
} from "@omniroute/open-sse/config/embeddingRegistry.ts";
|
|
|
|
describe("deriveEmbeddingProviderForChatProvider (global OpenAI-compatible fallback)", () => {
|
|
it("derives an embeddings endpoint from a chat-completions base URL", () => {
|
|
const derived = deriveEmbeddingProviderForChatProvider("groq", {
|
|
id: "groq",
|
|
baseUrl: "https://api.groq.com/openai/v1/chat/completions",
|
|
});
|
|
assert.ok(derived, "groq should derive an embedding provider");
|
|
assert.equal(derived.baseUrl, "https://api.groq.com/openai/v1/embeddings");
|
|
assert.equal(derived.authType, "apikey");
|
|
assert.equal(derived.authHeader, "bearer");
|
|
assert.deepEqual(derived.models, []);
|
|
});
|
|
|
|
it("returns null for providers without a usable static base URL", () => {
|
|
assert.equal(deriveEmbeddingProviderForChatProvider("x", null), null);
|
|
assert.equal(
|
|
deriveEmbeddingProviderForChatProvider("dynamic-provider", {
|
|
id: "dynamic-provider",
|
|
baseUrl: "https://host.example/accounts",
|
|
}),
|
|
null,
|
|
"non chat/completions bases must not derive a bogus /embeddings endpoint"
|
|
);
|
|
});
|
|
|
|
it("is a fallback only: curated registry entries stay authoritative", () => {
|
|
const derived = deriveEmbeddingProviderForChatProvider("deepinfra", {
|
|
id: "deepinfra",
|
|
baseUrl: "https://api.deepinfra.com/v1/openai/chat/completions",
|
|
});
|
|
// deepinfra IS in EMBEDDING_PROVIDERS — the helper still derives mechanically;
|
|
// callers must check getEmbeddingProvider() first.
|
|
assert.ok(derived);
|
|
assert.ok(getEmbeddingProvider("deepinfra"), "curated entry remains authoritative");
|
|
});
|
|
|
|
it("covers known embedding-capable chat providers with derivable endpoints", () => {
|
|
for (const id of ["mistral", "together", "upstage", "fireworks", "nvidia"]) {
|
|
const derived = deriveEmbeddingProviderForChatProvider(id, {
|
|
id,
|
|
baseUrl: `https://${id}.example.com/v1/chat/completions`,
|
|
});
|
|
assert.ok(derived, `${id} should derive`);
|
|
assert.equal(derived?.baseUrl, `https://${id}.example.com/v1/embeddings`);
|
|
}
|
|
});
|
|
});
|
|
|
|
describe("parseEmbeddingModel precedence (provider_node vs registry)", () => {
|
|
it("a configured provider_node prefix wins over alias resolution", () => {
|
|
const dynamic: EmbeddingProvider[] = [
|
|
{
|
|
id: "jina-ai",
|
|
baseUrl: "http://127.0.0.1:9/embeddings",
|
|
authType: "none",
|
|
authHeader: "none",
|
|
models: [],
|
|
},
|
|
];
|
|
const parsed = parseEmbeddingModel("jina-ai/my-local-model", dynamic);
|
|
assert.deepEqual(parsed, { provider: "jina-ai", model: "my-local-model" });
|
|
});
|
|
|
|
it("unknown prefixes fall through to the generic provider segment", () => {
|
|
const parsed = parseEmbeddingModel("totally-unknown/model-id");
|
|
assert.deepEqual(parsed, { provider: "totally-unknown", model: "model-id" });
|
|
});
|
|
});
|