fix: enable FTS fallback when no embedding provider available (#17725)

When no embedding provider is available (e.g., OAuth mode without API keys),
memory_search now falls back to FTS-only mode instead of returning disabled: true.

Changes:
- embeddings.ts: return null provider with reason instead of throwing
- manager.ts: handle null provider, use FTS-only search mode
- manager-search.ts: allow searching all models when provider is undefined
- memory-tool.ts: expose search mode in results

The search results now include a 'mode' field indicating 'hybrid' or 'fts-only'.
This commit is contained in:
康熙
2026-02-16 14:37:32 +08:00
committed by Peter Steinberger
parent 153794080e
commit 65aedac20e
6 changed files with 150 additions and 17 deletions

View File

@@ -432,3 +432,63 @@ describe("local embedding normalization", () => {
}
});
});
describe("FTS-only fallback when no provider available", () => {
afterEach(() => {
vi.resetAllMocks();
vi.unstubAllGlobals();
});
it("returns null provider with reason when auto mode finds no providers", async () => {
vi.mocked(authModule.resolveApiKeyForProvider).mockRejectedValue(
new Error('No API key found for provider "openai"'),
);
const result = await createEmbeddingProvider({
config: {} as never,
provider: "auto",
model: "",
fallback: "none",
});
expect(result.provider).toBeNull();
expect(result.requestedProvider).toBe("auto");
expect(result.providerUnavailableReason).toBeDefined();
expect(result.providerUnavailableReason).toContain("No API key");
});
it("returns null provider when explicit provider fails with missing API key", async () => {
vi.mocked(authModule.resolveApiKeyForProvider).mockRejectedValue(
new Error('No API key found for provider "openai"'),
);
const result = await createEmbeddingProvider({
config: {} as never,
provider: "openai",
model: "text-embedding-3-small",
fallback: "none",
});
expect(result.provider).toBeNull();
expect(result.requestedProvider).toBe("openai");
expect(result.providerUnavailableReason).toBeDefined();
});
it("returns null provider when both primary and fallback fail with missing API keys", async () => {
vi.mocked(authModule.resolveApiKeyForProvider).mockRejectedValue(
new Error("No API key found for provider"),
);
const result = await createEmbeddingProvider({
config: {} as never,
provider: "openai",
model: "text-embedding-3-small",
fallback: "gemini",
});
expect(result.provider).toBeNull();
expect(result.requestedProvider).toBe("openai");
expect(result.fallbackFrom).toBe("openai");
expect(result.providerUnavailableReason).toContain("Fallback to gemini failed");
});
});