From 62baf451c14c98dc8ab70e5050083a8efb18823b Mon Sep 17 00:00:00 2001 From: "Jason (Json)" <263060202+fuller-stack-dev@users.noreply.github.com> Date: Mon, 27 Jul 2026 14:53:34 -0600 Subject: [PATCH] fix(deepinfra): remove deprecated embedding provider warning (#114727) * fix(deepinfra): migrate embedding provider registration * fix(deepinfra): preserve embedding index identity --- docs/plugins/reference/deepinfra.md | 2 +- extensions/deepinfra/api.ts | 2 +- .../deepinfra/embedding-adapter.test.ts | 127 ++++++++++++++++++ extensions/deepinfra/embedding-adapter.ts | 85 ++++++++++++ extensions/deepinfra/index.test.ts | 3 +- extensions/deepinfra/index.ts | 6 +- .../memory-embedding-adapter.test.ts | 32 ----- .../deepinfra/memory-embedding-adapter.ts | 48 ------- extensions/deepinfra/openclaw.plugin.json | 2 +- .../official-external-provider-catalog.json | 2 +- ...in-sdk-package-contract-guardrails.test.ts | 2 - .../embedding-provider-runtime-shared.ts | 4 +- .../embedding-provider-runtime.test.ts | 25 +++- .../official-external-plugin-catalog.test.ts | 8 ++ 14 files changed, 253 insertions(+), 95 deletions(-) create mode 100644 extensions/deepinfra/embedding-adapter.test.ts create mode 100644 extensions/deepinfra/embedding-adapter.ts delete mode 100644 extensions/deepinfra/memory-embedding-adapter.test.ts delete mode 100644 extensions/deepinfra/memory-embedding-adapter.ts diff --git a/docs/plugins/reference/deepinfra.md b/docs/plugins/reference/deepinfra.md index d652927007c4..fb44d74f1467 100644 --- a/docs/plugins/reference/deepinfra.md +++ b/docs/plugins/reference/deepinfra.md @@ -16,7 +16,7 @@ Adds DeepInfra model provider support to OpenClaw. ## Surface -providers: `deepinfra`; contracts: `imageGenerationProviders`, `mediaUnderstandingProviders`, `memoryEmbeddingProviders`, `speechProviders`, `videoGenerationProviders` +providers: `deepinfra`; contracts: `embeddingProviders`, `imageGenerationProviders`, `mediaUnderstandingProviders`, `speechProviders`, `videoGenerationProviders` ## Related docs diff --git a/extensions/deepinfra/api.ts b/extensions/deepinfra/api.ts index e90565f80154..13c1e14e0737 100644 --- a/extensions/deepinfra/api.ts +++ b/extensions/deepinfra/api.ts @@ -4,6 +4,6 @@ export { applyDeepInfraConfig } from "./onboard.js"; export { DEEPINFRA_DEFAULT_MODEL_REF } from "./provider-models.js"; export { buildDeepInfraImageGenerationProvider } from "./image-generation-provider.js"; export { deepinfraMediaUnderstandingProvider } from "./media-understanding-provider.js"; -export { deepinfraMemoryEmbeddingProviderAdapter } from "./memory-embedding-adapter.js"; +export { deepinfraEmbeddingProviderAdapter } from "./embedding-adapter.js"; export { buildDeepInfraSpeechProvider } from "./speech-provider.js"; export { buildDeepInfraVideoGenerationProvider } from "./video-generation-provider.js"; diff --git a/extensions/deepinfra/embedding-adapter.test.ts b/extensions/deepinfra/embedding-adapter.test.ts new file mode 100644 index 000000000000..87d952261a52 --- /dev/null +++ b/extensions/deepinfra/embedding-adapter.test.ts @@ -0,0 +1,127 @@ +// Deepinfra tests cover its generic embedding adapter behavior. +import type { MemoryEmbeddingProvider } from "openclaw/plugin-sdk/memory-core-host-engine-embeddings"; +import { beforeEach, describe, expect, it, vi } from "vitest"; + +const mocks = vi.hoisted(() => ({ + createDeepInfraEmbeddingProvider: vi.fn(), +})); + +vi.mock("./embedding-provider.js", () => ({ + createDeepInfraEmbeddingProvider: mocks.createDeepInfraEmbeddingProvider, + DEFAULT_DEEPINFRA_EMBEDDING_MODEL: "BAAI/bge-m3", +})); + +import { deepinfraEmbeddingProviderAdapter } from "./embedding-adapter.js"; + +const memoryProvider: MemoryEmbeddingProvider = { + id: "deepinfra", + model: "BAAI/bge-m3", + maxInputTokens: 8192, + embedQuery: vi.fn(async () => [1, 0]), + embedBatch: vi.fn(async (texts) => texts.map(() => [0, 1])), + close: vi.fn(), +}; + +describe("DeepInfra generic embedding adapter", () => { + beforeEach(() => { + vi.clearAllMocks(); + mocks.createDeepInfraEmbeddingProvider.mockResolvedValue({ + provider: memoryProvider, + client: { model: "BAAI/bge-m3-resolved" }, + }); + }); + + it("declares the existing provider id, default model, transport, and auth owner", () => { + expect(deepinfraEmbeddingProviderAdapter).toMatchObject({ + id: "deepinfra", + defaultModel: "BAAI/bge-m3", + transport: "remote", + authProviderId: "deepinfra", + create: expect.any(Function), + }); + }); + + it("preserves model, dimensions, input types, and runtime identity when creating", async () => { + const result = await deepinfraEmbeddingProviderAdapter.create({ + config: {}, + agentDir: "/tmp/openclaw-agent", + provider: "deepinfra", + remote: { + baseUrl: "https://api.deepinfra.com/v1/openai", + apiKey: "fixture-key", + headers: { "x-deployment": "tenant-a" }, + }, + model: "BAAI/bge-m3", + inputType: "semantic", + queryInputType: "query", + documentInputType: "document", + dimensions: 1024, + taskType: "SEMANTIC_SIMILARITY", + }); + + expect(mocks.createDeepInfraEmbeddingProvider).toHaveBeenCalledWith({ + config: {}, + agentDir: "/tmp/openclaw-agent", + provider: "deepinfra", + fallback: "none", + remote: { + baseUrl: "https://api.deepinfra.com/v1/openai", + apiKey: "fixture-key", + headers: { "x-deployment": "tenant-a" }, + }, + model: "BAAI/bge-m3", + inputType: "semantic", + queryInputType: "query", + documentInputType: "document", + outputDimensionality: 1024, + taskType: "SEMANTIC_SIMILARITY", + defaultModel: "BAAI/bge-m3", + }); + expect(result.runtime).toEqual({ + id: "deepinfra", + cacheKeyData: { provider: "deepinfra", model: "BAAI/bge-m3-resolved" }, + }); + expect(result.provider).toMatchObject({ + id: "deepinfra", + model: "BAAI/bge-m3", + maxInputTokens: 8192, + }); + }); + + it("adapts generic query and batch calls without changing text or cancellation", async () => { + const result = await deepinfraEmbeddingProviderAdapter.create({ + config: {}, + model: "BAAI/bge-m3", + }); + const provider = result.provider; + if (!provider) { + throw new Error("expected DeepInfra embedding provider"); + } + const abortController = new AbortController(); + + await expect( + provider.embed( + { text: "query text" }, + { signal: abortController.signal, inputType: "query" }, + ), + ).resolves.toEqual([1, 0]); + await expect( + provider.embedBatch(["document one", { text: "document two" }], { + signal: abortController.signal, + inputType: "document", + }), + ).resolves.toEqual([ + [0, 1], + [0, 1], + ]); + await provider.close?.(); + + expect(memoryProvider.embedQuery).toHaveBeenCalledWith("query text", { + signal: abortController.signal, + }); + expect(memoryProvider.embedBatch).toHaveBeenCalledWith(["document one", "document two"], { + signal: abortController.signal, + }); + expect(memoryProvider.close).toHaveBeenCalledOnce(); + }); +}); diff --git a/extensions/deepinfra/embedding-adapter.ts b/extensions/deepinfra/embedding-adapter.ts new file mode 100644 index 000000000000..549ea8106689 --- /dev/null +++ b/extensions/deepinfra/embedding-adapter.ts @@ -0,0 +1,85 @@ +// Deepinfra plugin module adapts its text embedding runtime to the generic provider contract. +import type { + EmbeddingInput, + EmbeddingProvider, + EmbeddingProviderAdapter, + EmbeddingProviderCreateOptions, +} from "openclaw/plugin-sdk/embedding-providers"; +import type { + MemoryEmbeddingProvider, + MemoryEmbeddingProviderCreateOptions, +} from "openclaw/plugin-sdk/memory-core-host-engine-embeddings"; +import { + createDeepInfraEmbeddingProvider, + DEFAULT_DEEPINFRA_EMBEDDING_MODEL, +} from "./embedding-provider.js"; +import type { DeepInfraSurfaceModel } from "./provider-models.js"; + +function textFromEmbeddingInput(input: EmbeddingInput): string { + return typeof input === "string" ? input : input.text; +} + +function adaptMemoryEmbeddingProvider(provider: MemoryEmbeddingProvider): EmbeddingProvider { + return { + id: provider.id, + model: provider.model, + ...(typeof provider.maxInputTokens === "number" + ? { maxInputTokens: provider.maxInputTokens } + : {}), + embed: async (input, options) => + await provider.embedQuery(textFromEmbeddingInput(input), { signal: options?.signal }), + embedBatch: async (inputs, options) => + await provider.embedBatch(inputs.map(textFromEmbeddingInput), { signal: options?.signal }), + ...(provider.close ? { close: async () => await provider.close?.() } : {}), + }; +} + +function buildMemoryCreateOptions( + options: EmbeddingProviderCreateOptions, +): MemoryEmbeddingProviderCreateOptions { + return { + config: options.config, + agentDir: options.agentDir, + provider: "deepinfra", + fallback: "none", + remote: options.remote, + model: options.model, + inputType: options.inputType, + queryInputType: options.queryInputType, + documentInputType: options.documentInputType, + outputDimensionality: options.dimensions, + taskType: options.taskType as MemoryEmbeddingProviderCreateOptions["taskType"], + }; +} + +// First entry of embedModels becomes the default embedding model. +export function buildDeepInfraEmbeddingAdapter(options?: { + embedModels?: readonly DeepInfraSurfaceModel[]; +}): EmbeddingProviderAdapter { + const defaultModel = options?.embedModels?.[0]?.id ?? DEFAULT_DEEPINFRA_EMBEDDING_MODEL; + return { + id: "deepinfra", + defaultModel, + transport: "remote", + authProviderId: "deepinfra", + create: async (createOptions) => { + const { provider, client } = await createDeepInfraEmbeddingProvider({ + ...buildMemoryCreateOptions(createOptions), + defaultModel, + }); + return { + provider: provider ? adaptMemoryEmbeddingProvider(provider) : null, + runtime: { + id: "deepinfra", + cacheKeyData: { + provider: "deepinfra", + model: client.model, + }, + }, + }; + }, + }; +} + +export const deepinfraEmbeddingProviderAdapter: EmbeddingProviderAdapter = + buildDeepInfraEmbeddingAdapter(); diff --git a/extensions/deepinfra/index.test.ts b/extensions/deepinfra/index.test.ts index 87f84c75f4c9..df53b2601123 100644 --- a/extensions/deepinfra/index.test.ts +++ b/extensions/deepinfra/index.test.ts @@ -229,7 +229,8 @@ describe("deepinfra capability registration", () => { expect(captured.mediaUnderstandingProviders.map((provider) => provider.id)).toEqual([ "deepinfra", ]); - expect(captured.memoryEmbeddingProviders.map((provider) => provider.id)).toEqual(["deepinfra"]); + expect(captured.embeddingProviders.map((provider) => provider.id)).toEqual(["deepinfra"]); + expect(captured.memoryEmbeddingProviders).toEqual([]); expect(captured.speechProviders.map((provider) => provider.id)).toEqual(["deepinfra"]); expect(captured.videoGenerationProviders.map((provider) => provider.id)).toEqual(["deepinfra"]); }); diff --git a/extensions/deepinfra/index.ts b/extensions/deepinfra/index.ts index c1b0385a28c4..6ec05013144d 100644 --- a/extensions/deepinfra/index.ts +++ b/extensions/deepinfra/index.ts @@ -11,9 +11,9 @@ import { isProxyReasoningUnsupported, } from "openclaw/plugin-sdk/provider-stream"; import { createDeepInfraAnthropicCacheWrapper } from "./cache-wrapper.js"; +import { buildDeepInfraEmbeddingAdapter } from "./embedding-adapter.js"; import { buildDeepInfraImageGenerationProvider } from "./image-generation-provider.js"; import { buildDeepInfraMediaUnderstandingProvider } from "./media-understanding-provider.js"; -import { buildDeepInfraMemoryEmbeddingAdapter } from "./memory-embedding-adapter.js"; import { applyDeepInfraConfig } from "./onboard.js"; import { buildDeepInfraApiKeyCatalog, buildStaticDeepInfraProvider } from "./provider-catalog.js"; import { @@ -140,9 +140,7 @@ export default defineSingleProviderPluginEntry({ sttModels: catalog.stt, }), ); - api.registerMemoryEmbeddingProvider( - buildDeepInfraMemoryEmbeddingAdapter({ embedModels: catalog.embed }), - ); + api.registerEmbeddingProvider(buildDeepInfraEmbeddingAdapter({ embedModels: catalog.embed })); api.registerSpeechProvider(buildDeepInfraSpeechProvider({ ttsModels: catalog.tts })); api.registerVideoGenerationProvider( buildDeepInfraVideoGenerationProvider({ videoGenModels: catalog.videoGen }), diff --git a/extensions/deepinfra/memory-embedding-adapter.test.ts b/extensions/deepinfra/memory-embedding-adapter.test.ts deleted file mode 100644 index 1ed0ee7435e0..000000000000 --- a/extensions/deepinfra/memory-embedding-adapter.test.ts +++ /dev/null @@ -1,32 +0,0 @@ -// Deepinfra tests cover memory embedding adapter plugin behavior. -import { isMissingEmbeddingApiKeyError } from "openclaw/plugin-sdk/memory-core-host-engine-embeddings"; -import { describe, expect, it } from "vitest"; -import { DEFAULT_DEEPINFRA_EMBEDDING_MODEL } from "./embedding-provider.js"; -import { deepinfraMemoryEmbeddingProviderAdapter } from "./memory-embedding-adapter.js"; - -describe("deepinfra memory embedding adapter", () => { - it("declares a remote auth-backed embedding provider", () => { - expect(Object.keys(deepinfraMemoryEmbeddingProviderAdapter)).toEqual([ - "id", - "defaultModel", - "transport", - "authProviderId", - "autoSelectPriority", - "allowExplicitWhenConfiguredAuto", - "shouldContinueAutoSelection", - "create", - ]); - expect(deepinfraMemoryEmbeddingProviderAdapter.id).toBe("deepinfra"); - expect(deepinfraMemoryEmbeddingProviderAdapter.defaultModel).toBe( - DEFAULT_DEEPINFRA_EMBEDDING_MODEL, - ); - expect(deepinfraMemoryEmbeddingProviderAdapter.transport).toBe("remote"); - expect(deepinfraMemoryEmbeddingProviderAdapter.authProviderId).toBe("deepinfra"); - expect(deepinfraMemoryEmbeddingProviderAdapter.autoSelectPriority).toBe(55); - expect(deepinfraMemoryEmbeddingProviderAdapter.allowExplicitWhenConfiguredAuto).toBe(true); - expect(deepinfraMemoryEmbeddingProviderAdapter.shouldContinueAutoSelection).toBe( - isMissingEmbeddingApiKeyError, - ); - expect(deepinfraMemoryEmbeddingProviderAdapter.create).toBeTypeOf("function"); - }); -}); diff --git a/extensions/deepinfra/memory-embedding-adapter.ts b/extensions/deepinfra/memory-embedding-adapter.ts deleted file mode 100644 index 6121f6efc781..000000000000 --- a/extensions/deepinfra/memory-embedding-adapter.ts +++ /dev/null @@ -1,48 +0,0 @@ -// Deepinfra plugin module implements memory embedding adapter behavior. -import { - isMissingEmbeddingApiKeyError, - type MemoryEmbeddingProviderAdapter, -} from "openclaw/plugin-sdk/memory-core-host-engine-embeddings"; -import { - createDeepInfraEmbeddingProvider, - DEFAULT_DEEPINFRA_EMBEDDING_MODEL, -} from "./embedding-provider.js"; -import type { DeepInfraSurfaceModel } from "./provider-models.js"; - -// First entry of embedModels becomes the default embedding model. -export function buildDeepInfraMemoryEmbeddingAdapter(options?: { - embedModels?: readonly DeepInfraSurfaceModel[]; -}): MemoryEmbeddingProviderAdapter { - const defaultModel = options?.embedModels?.[0]?.id ?? DEFAULT_DEEPINFRA_EMBEDDING_MODEL; - return { - id: "deepinfra", - defaultModel, - transport: "remote", - authProviderId: "deepinfra", - autoSelectPriority: 55, - allowExplicitWhenConfiguredAuto: true, - shouldContinueAutoSelection: isMissingEmbeddingApiKeyError, - create: async (createOptions) => { - const { provider, client } = await createDeepInfraEmbeddingProvider({ - ...createOptions, - provider: "deepinfra", - fallback: "none", - defaultModel, - }); - return { - provider, - runtime: { - id: "deepinfra", - cacheKeyData: { - provider: "deepinfra", - model: client.model, - }, - }, - }; - }, - }; -} - -// Back-compat const for callers not yet on the builder. -export const deepinfraMemoryEmbeddingProviderAdapter: MemoryEmbeddingProviderAdapter = - buildDeepInfraMemoryEmbeddingAdapter(); diff --git a/extensions/deepinfra/openclaw.plugin.json b/extensions/deepinfra/openclaw.plugin.json index 1f1da75173d3..d035d9a98505 100644 --- a/extensions/deepinfra/openclaw.plugin.json +++ b/extensions/deepinfra/openclaw.plugin.json @@ -272,7 +272,7 @@ ], "contracts": { "mediaUnderstandingProviders": ["deepinfra"], - "memoryEmbeddingProviders": ["deepinfra"], + "embeddingProviders": ["deepinfra"], "imageGenerationProviders": ["deepinfra"], "speechProviders": ["deepinfra"], "videoGenerationProviders": ["deepinfra"] diff --git a/scripts/lib/official-external-provider-catalog.json b/scripts/lib/official-external-provider-catalog.json index c417faa8926c..6590b6abd679 100644 --- a/scripts/lib/official-external-provider-catalog.json +++ b/scripts/lib/official-external-provider-catalog.json @@ -468,7 +468,7 @@ "mediaUnderstandingProviders": [ "deepinfra" ], - "memoryEmbeddingProviders": [ + "embeddingProviders": [ "deepinfra" ], "imageGenerationProviders": [ diff --git a/src/plugins/contracts/plugin-sdk-package-contract-guardrails.test.ts b/src/plugins/contracts/plugin-sdk-package-contract-guardrails.test.ts index c1675168b11e..df47b37a6442 100644 --- a/src/plugins/contracts/plugin-sdk-package-contract-guardrails.test.ts +++ b/src/plugins/contracts/plugin-sdk-package-contract-guardrails.test.ts @@ -52,7 +52,6 @@ const DEPRECATED_TEST_ALIAS_ALLOWED_REFERENCE_FILES = new Set([ ]); const LEGACY_MEMORY_EMBEDDING_PROVIDER_API_FILES = new Set([ "extensions/amazon-bedrock/register.sync.runtime.ts", - "extensions/deepinfra/index.ts", "extensions/github-copilot/index.ts", "extensions/google/index.ts", "extensions/lmstudio/index.ts", @@ -64,7 +63,6 @@ const LEGACY_MEMORY_EMBEDDING_PROVIDER_API_FILES = new Set([ ]); const LEGACY_MEMORY_EMBEDDING_PROVIDER_MANIFEST_FILES = new Set([ "extensions/amazon-bedrock/openclaw.plugin.json", - "extensions/deepinfra/openclaw.plugin.json", "extensions/github-copilot/openclaw.plugin.json", "extensions/google/openclaw.plugin.json", "extensions/lmstudio/openclaw.plugin.json", diff --git a/src/plugins/embedding-provider-runtime-shared.ts b/src/plugins/embedding-provider-runtime-shared.ts index d46508b2545d..aed0e7308fa0 100644 --- a/src/plugins/embedding-provider-runtime-shared.ts +++ b/src/plugins/embedding-provider-runtime-shared.ts @@ -54,13 +54,13 @@ export function getRuntimeEmbeddingProviderAdapter RegisteredAdapterEntry | undefined; }): TAdapter | undefined { + // Resolve each exact id before trying the next configured alias. Otherwise a + // registered alias can shadow a plugin-owned adapter for the requested id. for (const candidateId of params.lookupIds) { const registered = params.getRegisteredProvider(candidateId); if (registered) { return registered.adapter; } - } - for (const candidateId of params.lookupIds) { const provider = resolvePluginCapabilityProvider({ key: params.key, providerId: candidateId, diff --git a/src/plugins/embedding-provider-runtime.test.ts b/src/plugins/embedding-provider-runtime.test.ts index 41f71a259b31..3756cf76ff93 100644 --- a/src/plugins/embedding-provider-runtime.test.ts +++ b/src/plugins/embedding-provider-runtime.test.ts @@ -81,7 +81,7 @@ describe("embedding provider runtime resolution", () => { expect(runtimeModule.getEmbeddingProvider("openai-compatible")?.id).toBe("openai-compatible"); }); - it("maps configured OpenAI-compatible provider ids to the core adapter", () => { + it("maps configured OpenAI-compatible provider ids to the core adapter when no exact provider exists", () => { const cfg = { models: { providers: { @@ -97,13 +97,34 @@ describe("embedding provider runtime resolution", () => { expect(runtimeModule.getEmbeddingProvider("tenant-embeddings", cfg)?.id).toBe( "openai-compatible", ); - expect(mocks.resolvePluginCapabilityProvider).not.toHaveBeenCalledWith({ + expect(mocks.resolvePluginCapabilityProvider).toHaveBeenCalledWith({ key: "embeddingProviders", providerId: "tenant-embeddings", cfg, }); }); + it("prefers an exact plugin adapter over a configured OpenAI-compatible alias", () => { + const adapter = createCapabilityAdapter("tenant-embeddings"); + const cfg = { + models: { + providers: { + "tenant-embeddings": { + api: "openai-responses", + baseUrl: "http://127.0.0.1:11434/v1", + models: [], + }, + }, + }, + } satisfies OpenClawConfig; + mocks.resolvePluginCapabilityProvider.mockImplementation(({ providerId }) => + providerId === "tenant-embeddings" ? adapter : undefined, + ); + + expect(runtimeModule.getEmbeddingProvider("tenant-embeddings", cfg)).toBe(adapter); + expect(mocks.resolvePluginCapabilityProvider).toHaveBeenCalledOnce(); + }); + it("prefers registered adapters over declared capability fallback adapters with the same id", () => { const registered = { id: "openai", diff --git a/src/plugins/official-external-plugin-catalog.test.ts b/src/plugins/official-external-plugin-catalog.test.ts index 2753c7c5b2e6..7e3d5e9600d4 100644 --- a/src/plugins/official-external-plugin-catalog.test.ts +++ b/src/plugins/official-external-plugin-catalog.test.ts @@ -289,6 +289,14 @@ describe("official external plugin catalog", () => { } }); + it("advertises DeepInfra through the generic embedding provider contract", () => { + const entry = expectCatalogEntry("deepinfra"); + const contracts = getOfficialExternalPluginCatalogManifest(entry)?.contracts; + + expect(contracts?.embeddingProviders).toEqual(["deepinfra"]); + expect(contracts?.memoryEmbeddingProviders).toBeUndefined(); + }); + it("does not allow malformed feed wrappers to count as feed documents", () => { expect( isOfficialExternalPluginCatalogFeed({