mirror of
https://github.com/openclaw/openclaw.git
synced 2026-08-12 21:53:00 -06:00
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:
@@ -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
|
||||
|
||||
|
||||
@@ -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();
|
||||
});
|
||||
});
|
||||
@@ -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();
|
||||
@@ -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"]);
|
||||
});
|
||||
|
||||
@@ -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();
|
||||
@@ -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,
|
||||
|
||||
@@ -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({
|
||||
|
||||
Reference in New Issue
Block a user