fix(deepinfra): remove deprecated embedding provider warning (#114727)

* fix(deepinfra): migrate embedding provider registration

* fix(deepinfra): preserve embedding index identity
This commit is contained in:
Jason (Json)
2026-07-27 14:53:34 -06:00
committed by GitHub
parent f4058528e5
commit 62baf451c1
14 changed files with 253 additions and 95 deletions
+1 -1
View File
@@ -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
+1 -1
View File
@@ -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";
@@ -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();
});
});
+85
View File
@@ -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();
+2 -1
View File
@@ -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"]);
});
+2 -4
View File
@@ -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 }),
@@ -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");
});
});
@@ -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();
+1 -1
View File
@@ -272,7 +272,7 @@
],
"contracts": {
"mediaUnderstandingProviders": ["deepinfra"],
"memoryEmbeddingProviders": ["deepinfra"],
"embeddingProviders": ["deepinfra"],
"imageGenerationProviders": ["deepinfra"],
"speechProviders": ["deepinfra"],
"videoGenerationProviders": ["deepinfra"]
@@ -468,7 +468,7 @@
"mediaUnderstandingProviders": [
"deepinfra"
],
"memoryEmbeddingProviders": [
"embeddingProviders": [
"deepinfra"
],
"imageGenerationProviders": [
@@ -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",
@@ -54,13 +54,13 @@ export function getRuntimeEmbeddingProviderAdapter<TAdapter extends { id: string
lookupIds: string[];
getRegisteredProvider: (id: string) => RegisteredAdapterEntry<TAdapter> | 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,
+23 -2
View File
@@ -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",
@@ -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({