Memory: extract embedding manager runtime

This commit is contained in:
Gustavo Madeira Santana 2026-03-15 20:06:23 +00:00
parent 4c88a7835c
commit bbe97e886f
2 changed files with 164 additions and 68 deletions

View File

@ -0,0 +1,122 @@
import { resolveAgentDir } from "../agents/agent-scope.js";
import type { ResolvedMemorySearchConfig } from "../agents/memory-search.js";
import type { OpenClawConfig } from "../config/config.js";
import { DEFAULT_GEMINI_EMBEDDING_MODEL } from "../memory/embeddings-gemini.js";
import { DEFAULT_MISTRAL_EMBEDDING_MODEL } from "../memory/embeddings-mistral.js";
import { DEFAULT_OLLAMA_EMBEDDING_MODEL } from "../memory/embeddings-ollama.js";
import { DEFAULT_OPENAI_EMBEDDING_MODEL } from "../memory/embeddings-openai.js";
import { DEFAULT_VOYAGE_EMBEDDING_MODEL } from "../memory/embeddings-voyage.js";
import {
createEmbeddingProvider,
type EmbeddingProvider,
type EmbeddingProviderId,
type GeminiEmbeddingClient,
type MistralEmbeddingClient,
type OllamaEmbeddingClient,
type OpenAiEmbeddingClient,
type VoyageEmbeddingClient,
} from "./embedding-runtime.js";
export type EmbeddingManagerBatchConfig = {
enabled: boolean;
wait: boolean;
concurrency: number;
pollIntervalMs: number;
timeoutMs: number;
};
export type EmbeddingManagerRuntimeState = {
provider: EmbeddingProvider | null;
fallbackFrom?: EmbeddingProviderId;
openAi?: OpenAiEmbeddingClient;
gemini?: GeminiEmbeddingClient;
voyage?: VoyageEmbeddingClient;
mistral?: MistralEmbeddingClient;
ollama?: OllamaEmbeddingClient;
};
export type EmbeddingManagerFallbackActivation = EmbeddingManagerRuntimeState & {
fallbackFrom: EmbeddingProviderId;
fallbackReason: string;
};
export function resolveEmbeddingManagerBatchConfig(params: {
settings: Pick<ResolvedMemorySearchConfig, "remote">;
state: EmbeddingManagerRuntimeState;
}): EmbeddingManagerBatchConfig {
const batch = params.settings.remote?.batch;
const { provider } = params.state;
const enabled = Boolean(
batch?.enabled &&
provider &&
((params.state.openAi && provider.id === "openai") ||
(params.state.gemini && provider.id === "gemini") ||
(params.state.voyage && provider.id === "voyage")),
);
return {
enabled,
wait: batch?.wait ?? true,
concurrency: Math.max(1, batch?.concurrency ?? 2),
pollIntervalMs: batch?.pollIntervalMs ?? 2000,
timeoutMs: (batch?.timeoutMinutes ?? 60) * 60 * 1000,
};
}
export async function activateEmbeddingManagerFallbackProvider(params: {
cfg: OpenClawConfig;
agentId: string;
settings: Pick<
ResolvedMemorySearchConfig,
"fallback" | "local" | "model" | "outputDimensionality" | "remote"
>;
state: EmbeddingManagerRuntimeState;
reason: string;
}): Promise<EmbeddingManagerFallbackActivation | null> {
const fallback = params.settings.fallback;
const { provider, fallbackFrom } = params.state;
if (!fallback || fallback === "none" || !provider || fallback === provider.id || fallbackFrom) {
return null;
}
const result = await createEmbeddingProvider({
config: params.cfg,
agentDir: resolveAgentDir(params.cfg, params.agentId),
provider: fallback,
remote: params.settings.remote,
model: resolveEmbeddingFallbackModel(fallback, params.settings.model),
outputDimensionality: params.settings.outputDimensionality,
fallback: "none",
local: params.settings.local,
});
return {
provider: result.provider,
fallbackFrom: provider.id as EmbeddingProviderId,
fallbackReason: params.reason,
openAi: result.openAi,
gemini: result.gemini,
voyage: result.voyage,
mistral: result.mistral,
ollama: result.ollama,
};
}
function resolveEmbeddingFallbackModel(
fallback: Exclude<ResolvedMemorySearchConfig["fallback"], undefined | "none">,
configuredModel: string,
): string {
switch (fallback) {
case "gemini":
return DEFAULT_GEMINI_EMBEDDING_MODEL;
case "openai":
return DEFAULT_OPENAI_EMBEDDING_MODEL;
case "voyage":
return DEFAULT_VOYAGE_EMBEDDING_MODEL;
case "mistral":
return DEFAULT_MISTRAL_EMBEDDING_MODEL;
case "ollama":
return DEFAULT_OLLAMA_EMBEDDING_MODEL;
case "local":
return configuredModel;
}
}

View File

@ -4,13 +4,16 @@ import fs from "node:fs/promises";
import path from "node:path";
import type { DatabaseSync } from "node:sqlite";
import chokidar, { FSWatcher } from "chokidar";
import { resolveAgentDir } from "../agents/agent-scope.js";
import { ResolvedMemorySearchConfig } from "../agents/memory-search.js";
import { type OpenClawConfig } from "../config/config.js";
import { resolveSessionTranscriptsDirForAgent } from "../config/sessions/paths.js";
import {
createEmbeddingProvider,
activateEmbeddingManagerFallbackProvider,
resolveEmbeddingManagerBatchConfig,
} from "../extension-host/embedding-manager-runtime.js";
import {
type EmbeddingProvider,
type EmbeddingProviderId,
type GeminiEmbeddingClient,
type MistralEmbeddingClient,
type OllamaEmbeddingClient,
@ -20,11 +23,6 @@ import {
import { createSubsystemLogger } from "../logging/subsystem.js";
import { onSessionTranscriptUpdate } from "../sessions/transcript-events.js";
import { resolveUserPath } from "../utils.js";
import { DEFAULT_GEMINI_EMBEDDING_MODEL } from "./embeddings-gemini.js";
import { DEFAULT_MISTRAL_EMBEDDING_MODEL } from "./embeddings-mistral.js";
import { DEFAULT_OLLAMA_EMBEDDING_MODEL } from "./embeddings-ollama.js";
import { DEFAULT_OPENAI_EMBEDDING_MODEL } from "./embeddings-openai.js";
import { DEFAULT_VOYAGE_EMBEDDING_MODEL } from "./embeddings-voyage.js";
import { isFileMissingError } from "./fs-utils.js";
import {
buildFileEntry,
@ -1071,74 +1069,50 @@ export abstract class MemoryManagerSyncOps {
pollIntervalMs: number;
timeoutMs: number;
} {
const batch = this.settings.remote?.batch;
const enabled = Boolean(
batch?.enabled &&
this.provider &&
((this.openAi && this.provider.id === "openai") ||
(this.gemini && this.provider.id === "gemini") ||
(this.voyage && this.provider.id === "voyage")),
);
return {
enabled,
wait: batch?.wait ?? true,
concurrency: Math.max(1, batch?.concurrency ?? 2),
pollIntervalMs: batch?.pollIntervalMs ?? 2000,
timeoutMs: (batch?.timeoutMinutes ?? 60) * 60 * 1000,
};
return resolveEmbeddingManagerBatchConfig({
settings: this.settings,
state: {
provider: this.provider,
openAi: this.openAi,
gemini: this.gemini,
voyage: this.voyage,
},
});
}
private async activateFallbackProvider(reason: string): Promise<boolean> {
const fallback = this.settings.fallback;
if (!fallback || fallback === "none" || !this.provider || fallback === this.provider.id) {
return false;
}
if (this.fallbackFrom) {
return false;
}
const fallbackFrom = this.provider.id as
| "openai"
| "gemini"
| "local"
| "voyage"
| "mistral"
| "ollama";
const fallbackModel =
fallback === "gemini"
? DEFAULT_GEMINI_EMBEDDING_MODEL
: fallback === "openai"
? DEFAULT_OPENAI_EMBEDDING_MODEL
: fallback === "voyage"
? DEFAULT_VOYAGE_EMBEDDING_MODEL
: fallback === "mistral"
? DEFAULT_MISTRAL_EMBEDDING_MODEL
: fallback === "ollama"
? DEFAULT_OLLAMA_EMBEDDING_MODEL
: this.settings.model;
const fallbackResult = await createEmbeddingProvider({
config: this.cfg,
agentDir: resolveAgentDir(this.cfg, this.agentId),
provider: fallback,
remote: this.settings.remote,
model: fallbackModel,
outputDimensionality: this.settings.outputDimensionality,
fallback: "none",
local: this.settings.local,
const activation = await activateEmbeddingManagerFallbackProvider({
cfg: this.cfg,
agentId: this.agentId,
settings: this.settings,
state: {
provider: this.provider,
fallbackFrom: this.fallbackFrom as EmbeddingProviderId | undefined,
openAi: this.openAi,
gemini: this.gemini,
voyage: this.voyage,
mistral: this.mistral,
ollama: this.ollama,
},
reason,
});
if (!activation) {
return false;
}
this.fallbackFrom = fallbackFrom;
this.fallbackReason = reason;
this.provider = fallbackResult.provider;
this.openAi = fallbackResult.openAi;
this.gemini = fallbackResult.gemini;
this.voyage = fallbackResult.voyage;
this.mistral = fallbackResult.mistral;
this.ollama = fallbackResult.ollama;
this.fallbackFrom = activation.fallbackFrom;
this.fallbackReason = activation.fallbackReason;
this.provider = activation.provider;
this.openAi = activation.openAi;
this.gemini = activation.gemini;
this.voyage = activation.voyage;
this.mistral = activation.mistral;
this.ollama = activation.ollama;
this.providerKey = this.computeProviderKey();
this.batch = this.resolveBatchConfig();
log.warn(`memory embeddings: switched to fallback provider (${fallback})`, { reason });
log.warn(`memory embeddings: switched to fallback provider (${this.provider?.id ?? "none"})`, {
reason,
});
return true;
}