mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-04 06:00:23 +00:00
refactor: move memory engine behind plugin adapters
This commit is contained in:
@@ -1,13 +1,64 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { afterEach, beforeEach, describe, expect, it } from "vitest";
|
||||
import type { OpenClawConfig } from "../config/config.js";
|
||||
import {
|
||||
clearMemoryEmbeddingProviders,
|
||||
registerMemoryEmbeddingProvider,
|
||||
} from "../plugins/memory-embedding-providers.js";
|
||||
import { resolveMemorySearchConfig } from "./memory-search.js";
|
||||
|
||||
const asConfig = (cfg: OpenClawConfig): OpenClawConfig => cfg;
|
||||
|
||||
describe("memory search config", () => {
|
||||
function configWithDefaultProvider(
|
||||
provider: "openai" | "local" | "gemini" | "mistral" | "ollama",
|
||||
): OpenClawConfig {
|
||||
beforeEach(() => {
|
||||
clearMemoryEmbeddingProviders();
|
||||
registerMemoryEmbeddingProvider({
|
||||
id: "openai",
|
||||
defaultModel: "text-embedding-3-small",
|
||||
transport: "remote",
|
||||
create: async () => ({ provider: null }),
|
||||
});
|
||||
registerMemoryEmbeddingProvider({
|
||||
id: "local",
|
||||
defaultModel: "local-default",
|
||||
transport: "local",
|
||||
create: async () => ({ provider: null }),
|
||||
});
|
||||
registerMemoryEmbeddingProvider({
|
||||
id: "gemini",
|
||||
defaultModel: "gemini-embedding-001",
|
||||
transport: "remote",
|
||||
supportsMultimodalEmbeddings: ({ model }) =>
|
||||
model
|
||||
.trim()
|
||||
.replace(/^models\//, "")
|
||||
.replace(/^(gemini|google)\//, "") === "gemini-embedding-2-preview",
|
||||
create: async () => ({ provider: null }),
|
||||
});
|
||||
registerMemoryEmbeddingProvider({
|
||||
id: "voyage",
|
||||
defaultModel: "voyage-4-large",
|
||||
transport: "remote",
|
||||
create: async () => ({ provider: null }),
|
||||
});
|
||||
registerMemoryEmbeddingProvider({
|
||||
id: "mistral",
|
||||
defaultModel: "mistral-embed",
|
||||
transport: "remote",
|
||||
create: async () => ({ provider: null }),
|
||||
});
|
||||
registerMemoryEmbeddingProvider({
|
||||
id: "ollama",
|
||||
defaultModel: "nomic-embed-text",
|
||||
transport: "remote",
|
||||
create: async () => ({ provider: null }),
|
||||
});
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
clearMemoryEmbeddingProviders();
|
||||
});
|
||||
|
||||
function configWithDefaultProvider(provider: string): OpenClawConfig {
|
||||
return asConfig({
|
||||
agents: {
|
||||
defaults: {
|
||||
@@ -258,7 +309,7 @@ describe("memory search config", () => {
|
||||
},
|
||||
});
|
||||
expect(() => resolveMemorySearchConfig(cfg, "main")).toThrow(
|
||||
/memorySearch\.multimodal requires memorySearch\.provider = "gemini"/,
|
||||
/memorySearch\.multimodal requires a provider adapter that supports multimodal embeddings/,
|
||||
);
|
||||
});
|
||||
|
||||
|
||||
@@ -3,12 +3,12 @@ import path from "node:path";
|
||||
import type { OpenClawConfig, MemorySearchConfig } from "../config/config.js";
|
||||
import { resolveStateDir } from "../config/paths.js";
|
||||
import type { SecretInput } from "../config/types.secrets.js";
|
||||
import { getMemoryEmbeddingProvider } from "../plugins/memory-embedding-providers.js";
|
||||
import {
|
||||
isMemoryMultimodalEnabled,
|
||||
normalizeMemoryMultimodalSettings,
|
||||
supportsMemoryMultimodalEmbeddings,
|
||||
type MemoryMultimodalSettings,
|
||||
} from "../memory/multimodal.js";
|
||||
} from "../plugins/memory-host/multimodal.js";
|
||||
import { clampInt, clampNumber, resolveUserPath } from "../utils.js";
|
||||
import { resolveAgentConfig } from "./agent-scope.js";
|
||||
|
||||
@@ -17,7 +17,7 @@ export type ResolvedMemorySearchConfig = {
|
||||
sources: Array<"memory" | "sessions">;
|
||||
extraPaths: string[];
|
||||
multimodal: MemoryMultimodalSettings;
|
||||
provider: "openai" | "local" | "gemini" | "voyage" | "mistral" | "ollama" | "auto";
|
||||
provider: string;
|
||||
remote?: {
|
||||
baseUrl?: string;
|
||||
apiKey?: SecretInput;
|
||||
@@ -33,7 +33,7 @@ export type ResolvedMemorySearchConfig = {
|
||||
experimental: {
|
||||
sessionMemory: boolean;
|
||||
};
|
||||
fallback: "openai" | "gemini" | "local" | "voyage" | "mistral" | "ollama" | "none";
|
||||
fallback: string;
|
||||
model: string;
|
||||
outputDimensionality?: number;
|
||||
local: {
|
||||
@@ -88,11 +88,6 @@ export type ResolvedMemorySearchConfig = {
|
||||
};
|
||||
};
|
||||
|
||||
const DEFAULT_OPENAI_MODEL = "text-embedding-3-small";
|
||||
const DEFAULT_GEMINI_MODEL = "gemini-embedding-001";
|
||||
const DEFAULT_VOYAGE_MODEL = "voyage-4-large";
|
||||
const DEFAULT_MISTRAL_MODEL = "mistral-embed";
|
||||
const DEFAULT_OLLAMA_MODEL = "nomic-embed-text";
|
||||
const DEFAULT_CHUNK_TOKENS = 400;
|
||||
const DEFAULT_CHUNK_OVERLAP = 80;
|
||||
const DEFAULT_WATCH_DEBOUNCE_MS = 1500;
|
||||
@@ -150,8 +145,12 @@ function mergeConfig(
|
||||
const sessionMemory =
|
||||
overrides?.experimental?.sessionMemory ?? defaults?.experimental?.sessionMemory ?? false;
|
||||
const provider = overrides?.provider ?? defaults?.provider ?? "auto";
|
||||
const primaryAdapter = provider === "auto" ? undefined : getMemoryEmbeddingProvider(provider);
|
||||
const defaultRemote = defaults?.remote;
|
||||
const overrideRemote = overrides?.remote;
|
||||
const fallback = overrides?.fallback ?? defaults?.fallback ?? "none";
|
||||
const fallbackAdapter =
|
||||
fallback && fallback !== "none" ? getMemoryEmbeddingProvider(fallback) : undefined;
|
||||
const hasRemoteConfig = Boolean(
|
||||
overrideRemote?.baseUrl ||
|
||||
overrideRemote?.apiKey ||
|
||||
@@ -162,12 +161,9 @@ function mergeConfig(
|
||||
);
|
||||
const includeRemote =
|
||||
hasRemoteConfig ||
|
||||
provider === "openai" ||
|
||||
provider === "gemini" ||
|
||||
provider === "voyage" ||
|
||||
provider === "mistral" ||
|
||||
provider === "ollama" ||
|
||||
provider === "auto";
|
||||
provider === "auto" ||
|
||||
primaryAdapter?.transport !== "local" ||
|
||||
fallbackAdapter?.transport === "remote";
|
||||
const batch = {
|
||||
enabled: overrideRemote?.batch?.enabled ?? defaultRemote?.batch?.enabled ?? false,
|
||||
wait: overrideRemote?.batch?.wait ?? defaultRemote?.batch?.wait ?? true,
|
||||
@@ -188,19 +184,7 @@ function mergeConfig(
|
||||
batch,
|
||||
}
|
||||
: undefined;
|
||||
const fallback = overrides?.fallback ?? defaults?.fallback ?? "none";
|
||||
const modelDefault =
|
||||
provider === "gemini"
|
||||
? DEFAULT_GEMINI_MODEL
|
||||
: provider === "openai"
|
||||
? DEFAULT_OPENAI_MODEL
|
||||
: provider === "voyage"
|
||||
? DEFAULT_VOYAGE_MODEL
|
||||
: provider === "mistral"
|
||||
? DEFAULT_MISTRAL_MODEL
|
||||
: provider === "ollama"
|
||||
? DEFAULT_OLLAMA_MODEL
|
||||
: undefined;
|
||||
const modelDefault = provider === "auto" ? undefined : primaryAdapter?.defaultModel;
|
||||
const model = overrides?.model ?? defaults?.model ?? modelDefault ?? "";
|
||||
const outputDimensionality = overrides?.outputDimensionality ?? defaults?.outputDimensionality;
|
||||
const local = {
|
||||
@@ -386,15 +370,16 @@ export function resolveMemorySearchConfig(
|
||||
return null;
|
||||
}
|
||||
const multimodalActive = isMemoryMultimodalEnabled(resolved.multimodal);
|
||||
const multimodalProvider =
|
||||
resolved.provider === "auto" ? undefined : getMemoryEmbeddingProvider(resolved.provider);
|
||||
if (
|
||||
multimodalActive &&
|
||||
!supportsMemoryMultimodalEmbeddings({
|
||||
provider: resolved.provider,
|
||||
!multimodalProvider?.supportsMultimodalEmbeddings?.({
|
||||
model: resolved.model,
|
||||
})
|
||||
) {
|
||||
throw new Error(
|
||||
'agents.*.memorySearch.multimodal requires memorySearch.provider = "gemini" and model = "gemini-embedding-2-preview".',
|
||||
"agents.*.memorySearch.multimodal requires a provider adapter that supports multimodal embeddings for the configured model.",
|
||||
);
|
||||
}
|
||||
if (multimodalActive && resolved.fallback !== "none") {
|
||||
|
||||
@@ -5,7 +5,10 @@ import type { ExtensionAPI, FileOperations } from "@mariozechner/pi-coding-agent
|
||||
import { extractSections } from "../../auto-reply/reply/post-compaction-context.js";
|
||||
import { openBoundaryFile } from "../../infra/boundary-file-read.js";
|
||||
import { createSubsystemLogger } from "../../logging/subsystem.js";
|
||||
import { extractKeywords, isQueryStopWordToken } from "../../memory/query-expansion.js";
|
||||
import {
|
||||
extractKeywords,
|
||||
isQueryStopWordToken,
|
||||
} from "../../plugins/memory-host/query-expansion.js";
|
||||
import {
|
||||
hasMeaningfulConversationContent,
|
||||
isRealConversationMessage,
|
||||
|
||||
Reference in New Issue
Block a user