| import {
|
| EMBEDDING_PROVIDERS,
|
| buildDynamicEmbeddingProvider,
|
| type EmbeddingProviderNodeRow,
|
| } from "@omniroute/open-sse/config/embeddingRegistry.ts";
|
| import { getProviderCredentials } from "@/sse/services/auth";
|
| import { getProviderNodes } from "@/lib/localDb";
|
| import type { MemorySettingsExtended } from "@/shared/schemas/memory";
|
| import type {
|
| EmbeddingResolution,
|
| EmbeddingResult,
|
| EmbeddingError,
|
| EmbeddingProviderListing,
|
| } from "./types";
|
| import { embedRemote } from "./remote";
|
| import { embedStatic } from "./staticPotion";
|
| import { embedTransformers } from "./transformersLocal";
|
| import {
|
| buildCacheKey,
|
| get as cacheGet,
|
| set as cacheSet,
|
| invalidate as cacheInvalidate,
|
| } from "./cache";
|
|
|
| const STATIC_MODEL = process.env.MEMORY_STATIC_MODEL || "minishlab/potion-base-8M";
|
| const TRANSFORMERS_MODEL =
|
| process.env.MEMORY_TRANSFORMERS_MODEL || "Xenova/all-MiniLM-L6-v2";
|
|
|
|
|
| function noSource(reason: string): EmbeddingResolution {
|
| return {
|
| source: null,
|
| model: null,
|
| dimensions: null,
|
| signature: "null:null:null",
|
| reason,
|
| };
|
| }
|
|
|
|
|
| function makeSignature(
|
| source: "remote" | "static" | "transformers" | null,
|
| model: string | null,
|
| dim: number | null
|
| ): string {
|
| return `${source ?? "null"}:${model ?? "null"}:${dim ?? "null"}`;
|
| }
|
|
|
| |
| |
| |
|
|
| export function resolveEmbeddingSource(settings: MemorySettingsExtended): EmbeddingResolution {
|
| const source = settings.embeddingSource ?? "auto";
|
|
|
| if (source === "remote") {
|
|
|
| const model = settings.embeddingProviderModel ?? null;
|
| if (!model) {
|
| return {
|
| source: null,
|
| model: null,
|
| dimensions: null,
|
| signature: makeSignature(null, null, null),
|
| reason: "no_key: embeddingProviderModel não configurado",
|
| };
|
| }
|
|
|
|
|
|
|
| return {
|
| source: "remote",
|
| model,
|
| dimensions: null,
|
| signature: makeSignature("remote", model, null),
|
| reason: `provider remoto configurado: ${model}`,
|
| };
|
| }
|
|
|
| if (source === "static") {
|
| if (settings.staticEnabled !== true) {
|
| return {
|
| source: null,
|
| model: null,
|
| dimensions: null,
|
| signature: makeSignature(null, null, null),
|
| reason: "static desabilitado nas configurações",
|
| };
|
| }
|
| return {
|
| source: "static",
|
| model: STATIC_MODEL,
|
| dimensions: 256,
|
| signature: makeSignature("static", STATIC_MODEL, 256),
|
| reason: "static (potion-base-8M) selecionado explicitamente",
|
| };
|
| }
|
|
|
| if (source === "transformers") {
|
| if (settings.transformersEnabled !== true) {
|
| return {
|
| source: null,
|
| model: null,
|
| dimensions: null,
|
| signature: makeSignature(null, null, null),
|
| reason: "transformers desabilitado nas configurações",
|
| };
|
| }
|
| return {
|
| source: "transformers",
|
| model: TRANSFORMERS_MODEL,
|
| dimensions: 384,
|
| signature: makeSignature("transformers", TRANSFORMERS_MODEL, 384),
|
| reason: "transformers.js (MiniLM-L6-v2) selecionado explicitamente",
|
| };
|
| }
|
|
|
|
|
|
|
|
|
|
|
| if (source === "auto") {
|
|
|
| const providerModel = settings.embeddingProviderModel ?? null;
|
| if (providerModel) {
|
| const slashIdx = providerModel.indexOf("/");
|
| const providerId = slashIdx > 0 ? providerModel.slice(0, slashIdx) : null;
|
| if (providerId && EMBEDDING_PROVIDERS[providerId]) {
|
|
|
|
|
|
|
| return {
|
| source: "remote",
|
| model: providerModel,
|
| dimensions: null,
|
| signature: makeSignature("remote", providerModel, null),
|
| reason: `auto: provider ${providerId} configurado`,
|
| };
|
| }
|
| }
|
|
|
| if (settings.staticEnabled === true) {
|
| return {
|
| source: "static",
|
| model: STATIC_MODEL,
|
| dimensions: 256,
|
| signature: makeSignature("static", STATIC_MODEL, 256),
|
| reason: "auto: potion-base-8M (static) disponível",
|
| };
|
| }
|
|
|
| if (settings.transformersEnabled === true) {
|
| return {
|
| source: "transformers",
|
| model: TRANSFORMERS_MODEL,
|
| dimensions: 384,
|
| signature: makeSignature("transformers", TRANSFORMERS_MODEL, 384),
|
| reason: "auto: transformers.js (MiniLM-L6-v2) disponível",
|
| };
|
| }
|
|
|
| return noSource("auto: nenhuma fonte de embedding disponível");
|
| }
|
|
|
| return noSource("fonte de embedding desconhecida");
|
| }
|
|
|
| |
| |
| |
|
|
| export async function embed(
|
| text: string,
|
| settings: MemorySettingsExtended
|
| ): Promise<EmbeddingResult | EmbeddingError> {
|
| const resolution = resolveEmbeddingSource(settings);
|
|
|
| if (!resolution.source) {
|
| return {
|
| source: "remote",
|
| model: null,
|
| reason: "unknown",
|
| message: resolution.reason,
|
| };
|
| }
|
|
|
| const cacheKey = buildCacheKey(
|
| resolution.source,
|
| resolution.model,
|
| resolution.dimensions,
|
| text
|
| );
|
|
|
| const cached = cacheGet(cacheKey);
|
| if (cached) {
|
| return {
|
| vector: cached,
|
| source: resolution.source,
|
| model: resolution.model ?? "",
|
| dimensions: cached.length,
|
| latencyMs: 0,
|
| cached: true,
|
| };
|
| }
|
|
|
| let result: EmbeddingResult | EmbeddingError;
|
|
|
| if (resolution.source === "remote") {
|
| result = await embedRemote(text, resolution.model ?? "");
|
| } else if (resolution.source === "static") {
|
| result = await embedStatic(text);
|
| } else {
|
| result = await embedTransformers(text);
|
| }
|
|
|
| if ("vector" in result) {
|
| cacheSet(cacheKey, result.vector);
|
| }
|
|
|
| return result;
|
| }
|
|
|
| |
| |
| |
|
|
| export async function listEmbeddingProviders(): Promise<EmbeddingProviderListing[]> {
|
|
|
| let dynamicProviders: ReturnType<typeof buildDynamicEmbeddingProvider>[] = [];
|
| try {
|
| const nodes = (await getProviderNodes()) as unknown as EmbeddingProviderNodeRow[];
|
| dynamicProviders = (Array.isArray(nodes) ? nodes : [])
|
| .filter((n) => {
|
| const validTypes = ["chat", "responses", "embeddings"];
|
| return validTypes.includes(n.apiType || "");
|
| })
|
| .map((n) => {
|
| try {
|
| return buildDynamicEmbeddingProvider(n);
|
| } catch {
|
| return null;
|
| }
|
| })
|
| .filter((p): p is NonNullable<typeof p> => p !== null);
|
| } catch {
|
|
|
| }
|
|
|
| const result: EmbeddingProviderListing[] = [];
|
|
|
|
|
| for (const [providerId, config] of Object.entries(EMBEDDING_PROVIDERS)) {
|
| let hasKey = false;
|
| try {
|
| const creds = await getProviderCredentials(providerId);
|
| hasKey = !!(
|
| creds &&
|
| !("allRateLimited" in creds && creds.allRateLimited) &&
|
| (("apiKey" in creds ? !!creds.apiKey : false) ||
|
| ("accessToken" in creds ? !!creds.accessToken : false))
|
| );
|
| } catch {
|
| hasKey = false;
|
| }
|
|
|
| result.push({
|
| provider: providerId,
|
| hasKey,
|
| models: config.models.map((m) => ({
|
| id: `${providerId}/${m.id}`,
|
| name: m.name,
|
| dimensions: m.dimensions ?? null,
|
| })),
|
| });
|
| }
|
|
|
|
|
| for (const dp of dynamicProviders) {
|
|
|
| result.push({
|
| provider: dp.id,
|
| hasKey: true,
|
| models: dp.models.map((m) => ({
|
| id: `${dp.id}/${m.id}`,
|
| name: m.name,
|
| dimensions: m.dimensions ?? null,
|
| })),
|
| });
|
| }
|
|
|
| return result;
|
| }
|
|
|
| |
| |
| |
|
|
| export function invalidateEmbeddingCache(): void {
|
| cacheInvalidate();
|
| }
|
|
|