@remnic/core 9.3.563 → 9.3.564
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/access-cli.js +40 -39
- package/dist/access-cli.js.map +1 -1
- package/dist/access-http.js +16 -16
- package/dist/access-mcp.js +13 -13
- package/dist/access-schema.js +3 -3
- package/dist/access-service.js +11 -11
- package/dist/active-recall.js +1 -1
- package/dist/adapters/index.js +4 -4
- package/dist/adapters/registry.js +2 -2
- package/dist/briefing.js +4 -4
- package/dist/causal-consolidation.js +5 -5
- package/dist/{chunk-I2K6KCVC.js → chunk-2FHLI4U6.js} +49 -49
- package/dist/chunk-3ONXXHQO.js +57 -0
- package/dist/chunk-3ONXXHQO.js.map +1 -0
- package/dist/{chunk-5GX5MUQ2.js → chunk-574MU2Y3.js} +3 -3
- package/dist/{chunk-65OLPXBU.js → chunk-5WB4C7KM.js} +6 -6
- package/dist/chunk-6PTSXBPE.js +483 -0
- package/dist/chunk-6PTSXBPE.js.map +1 -0
- package/dist/{chunk-Z56KAZQL.js → chunk-74VA26CT.js} +2 -2
- package/dist/{chunk-CC2ESOOG.js → chunk-7X7TBJRX.js} +2 -2
- package/dist/{chunk-O4M4WH6V.js → chunk-ARY5OOLG.js} +2 -2
- package/dist/{chunk-JBPKEARU.js → chunk-AU7Q3LSC.js} +4 -4
- package/dist/{chunk-PM3QHTFT.js → chunk-CF3ZF2YU.js} +3 -3
- package/dist/{chunk-SI3QCHWF.js → chunk-DARLGSFX.js} +5 -5
- package/dist/chunk-EWLQPEO6.js +308 -0
- package/dist/chunk-EWLQPEO6.js.map +1 -0
- package/dist/{chunk-FVCZINOF.js → chunk-FHBEL473.js} +2 -2
- package/dist/{chunk-7Q3RCKAQ.js → chunk-FXKPZ3H6.js} +2 -2
- package/dist/{chunk-5WLYNZPC.js → chunk-GBXGCFRH.js} +2 -2
- package/dist/{chunk-ILJXM3FV.js → chunk-HQO5EBUC.js} +10 -10
- package/dist/{chunk-FK556DDH.js → chunk-I4UNL747.js} +4 -4
- package/dist/{chunk-RLPIT4YI.js → chunk-IOTTZLFF.js} +38 -38
- package/dist/{chunk-TVZ6LKKS.js → chunk-IRFF6LSF.js} +8 -8
- package/dist/{chunk-M5T4Q2ZU.js → chunk-KGK2QKWL.js} +1 -1
- package/dist/chunk-KGK2QKWL.js.map +1 -0
- package/dist/{chunk-IPLYGWQF.js → chunk-KQAFEZQX.js} +5 -5
- package/dist/chunk-M46RYSMW.js +597 -0
- package/dist/chunk-M46RYSMW.js.map +1 -0
- package/dist/{chunk-KXULCVOC.js → chunk-M6I5Z4SR.js} +4 -2
- package/dist/chunk-M6I5Z4SR.js.map +1 -0
- package/dist/{chunk-JFN6K74Q.js → chunk-MQEIWDYW.js} +2 -2
- package/dist/{chunk-7H6CFEBJ.js → chunk-NZPF2SYV.js} +8 -1
- package/dist/{chunk-7H6CFEBJ.js.map → chunk-NZPF2SYV.js.map} +1 -1
- package/dist/{chunk-SML26KED.js → chunk-OB6353F7.js} +16 -12
- package/dist/chunk-OB6353F7.js.map +1 -0
- package/dist/{chunk-SOTR74FK.js → chunk-OPYFD6PD.js} +2 -2
- package/dist/{chunk-3C5RPJAX.js → chunk-OXJBNGBK.js} +2 -2
- package/dist/{chunk-BD5LHQWD.js → chunk-PPPZY2EU.js} +2 -2
- package/dist/{chunk-25BY3HHZ.js → chunk-SUTSSOYU.js} +2 -2
- package/dist/{chunk-KS7WO6EQ.js → chunk-VFB2G5YL.js} +20 -20
- package/dist/{chunk-BUUYY2H2.js → chunk-WP5OWVLZ.js} +4 -4
- package/dist/{chunk-6URPAY2D.js → chunk-XCAZF7KQ.js} +207 -53
- package/dist/chunk-XCAZF7KQ.js.map +1 -0
- package/dist/{chunk-S53PAX2V.js → chunk-XM7BYXT7.js} +2 -2
- package/dist/{chunk-FADZBOR4.js → chunk-XRWTAEZM.js} +2 -2
- package/dist/{chunk-E5OECWZ5.js → chunk-XT7XVA53.js} +2 -2
- package/dist/{chunk-R3PS27B4.js → chunk-Z4R6RI2N.js} +2 -2
- package/dist/cli.js +44 -43
- package/dist/compounding/engine.js +4 -4
- package/dist/config.js +1 -1
- package/dist/connectors/codex-materialize-runner.js +4 -4
- package/dist/connectors/index.js +4 -4
- package/dist/embedding-fallback.d.ts +12 -1
- package/dist/embedding-fallback.js +4 -1
- package/dist/entity-retrieval.js +4 -4
- package/dist/host-embedding-provider.d.ts +21 -0
- package/dist/host-embedding-provider.js +14 -0
- package/dist/host-embedding-provider.js.map +1 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +71 -63
- package/dist/index.js.map +1 -1
- package/dist/lcm/index.js +3 -3
- package/dist/maintenance/memory-governance.js +4 -4
- package/dist/maintenance/rebuild-memory-lifecycle-ledger.js +4 -4
- package/dist/maintenance/rebuild-memory-projection.js +5 -5
- package/dist/namespaces/migrate.js +14 -13
- package/dist/namespaces/search.js +9 -8
- package/dist/namespaces/storage.js +4 -4
- package/dist/operator-toolkit.js +17 -16
- package/dist/orchestrator.js +32 -31
- package/dist/recall-explain-renderer.js +3 -3
- package/dist/recall-xray-cli.js +4 -4
- package/dist/recall-xray-renderer.js +3 -3
- package/dist/recall-xray.js +2 -2
- package/dist/resume-bundles.js +2 -2
- package/dist/search/embed-helper.d.ts +48 -4
- package/dist/search/embed-helper.js +2 -1
- package/dist/search/factory.js +8 -7
- package/dist/search/index.d.ts +1 -0
- package/dist/search/index.js +12 -11
- package/dist/search/lancedb-backend.d.ts +11 -0
- package/dist/search/lancedb-backend.js +2 -2
- package/dist/search/meilisearch-backend.js +2 -2
- package/dist/search/orama-backend.d.ts +16 -0
- package/dist/search/orama-backend.js +2 -2
- package/dist/semantic-consolidation.js +5 -5
- package/dist/semantic-rule-promotion.js +4 -4
- package/dist/semantic-rule-verifier.js +4 -4
- package/dist/storage.js +3 -3
- package/dist/transfer/autodetect.js +1 -1
- package/dist/transfer/backup.js +1 -1
- package/dist/transfer/capsule-export.js +2 -2
- package/dist/transfer/types.d.ts +6 -6
- package/dist/types.d.ts +17 -0
- package/dist/types.js +1 -1
- package/dist/verified-recall.js +4 -4
- package/package.json +11 -1
- package/src/config.ts +18 -0
- package/src/embedding-fallback.ts +293 -61
- package/src/host-embedding-provider.ts +84 -0
- package/src/index.ts +7 -0
- package/src/namespaces/search.ts +9 -1
- package/src/qmd.test.ts +28 -0
- package/src/search/embed-helper.ts +319 -51
- package/src/search/factory.ts +6 -2
- package/src/search/lancedb-backend.ts +297 -41
- package/src/search/orama-backend.ts +418 -47
- package/src/types.ts +17 -0
- package/dist/chunk-6URPAY2D.js.map +0 -1
- package/dist/chunk-FUC4LZMD.js +0 -301
- package/dist/chunk-FUC4LZMD.js.map +0 -1
- package/dist/chunk-KXULCVOC.js.map +0 -1
- package/dist/chunk-M5T4Q2ZU.js.map +0 -1
- package/dist/chunk-ONPLNAPX.js +0 -133
- package/dist/chunk-ONPLNAPX.js.map +0 -1
- package/dist/chunk-QVJ4NWL2.js +0 -335
- package/dist/chunk-QVJ4NWL2.js.map +0 -1
- package/dist/chunk-SML26KED.js.map +0 -1
- /package/dist/{chunk-I2K6KCVC.js.map → chunk-2FHLI4U6.js.map} +0 -0
- /package/dist/{chunk-5GX5MUQ2.js.map → chunk-574MU2Y3.js.map} +0 -0
- /package/dist/{chunk-65OLPXBU.js.map → chunk-5WB4C7KM.js.map} +0 -0
- /package/dist/{chunk-Z56KAZQL.js.map → chunk-74VA26CT.js.map} +0 -0
- /package/dist/{chunk-CC2ESOOG.js.map → chunk-7X7TBJRX.js.map} +0 -0
- /package/dist/{chunk-O4M4WH6V.js.map → chunk-ARY5OOLG.js.map} +0 -0
- /package/dist/{chunk-JBPKEARU.js.map → chunk-AU7Q3LSC.js.map} +0 -0
- /package/dist/{chunk-PM3QHTFT.js.map → chunk-CF3ZF2YU.js.map} +0 -0
- /package/dist/{chunk-SI3QCHWF.js.map → chunk-DARLGSFX.js.map} +0 -0
- /package/dist/{chunk-FVCZINOF.js.map → chunk-FHBEL473.js.map} +0 -0
- /package/dist/{chunk-7Q3RCKAQ.js.map → chunk-FXKPZ3H6.js.map} +0 -0
- /package/dist/{chunk-5WLYNZPC.js.map → chunk-GBXGCFRH.js.map} +0 -0
- /package/dist/{chunk-ILJXM3FV.js.map → chunk-HQO5EBUC.js.map} +0 -0
- /package/dist/{chunk-FK556DDH.js.map → chunk-I4UNL747.js.map} +0 -0
- /package/dist/{chunk-RLPIT4YI.js.map → chunk-IOTTZLFF.js.map} +0 -0
- /package/dist/{chunk-TVZ6LKKS.js.map → chunk-IRFF6LSF.js.map} +0 -0
- /package/dist/{chunk-IPLYGWQF.js.map → chunk-KQAFEZQX.js.map} +0 -0
- /package/dist/{chunk-JFN6K74Q.js.map → chunk-MQEIWDYW.js.map} +0 -0
- /package/dist/{chunk-SOTR74FK.js.map → chunk-OPYFD6PD.js.map} +0 -0
- /package/dist/{chunk-3C5RPJAX.js.map → chunk-OXJBNGBK.js.map} +0 -0
- /package/dist/{chunk-BD5LHQWD.js.map → chunk-PPPZY2EU.js.map} +0 -0
- /package/dist/{chunk-25BY3HHZ.js.map → chunk-SUTSSOYU.js.map} +0 -0
- /package/dist/{chunk-KS7WO6EQ.js.map → chunk-VFB2G5YL.js.map} +0 -0
- /package/dist/{chunk-BUUYY2H2.js.map → chunk-WP5OWVLZ.js.map} +0 -0
- /package/dist/{chunk-S53PAX2V.js.map → chunk-XM7BYXT7.js.map} +0 -0
- /package/dist/{chunk-FADZBOR4.js.map → chunk-XRWTAEZM.js.map} +0 -0
- /package/dist/{chunk-E5OECWZ5.js.map → chunk-XT7XVA53.js.map} +0 -0
- /package/dist/{chunk-R3PS27B4.js.map → chunk-Z4R6RI2N.js.map} +0 -0
|
@@ -2,15 +2,48 @@ import { log } from "../logger.js";
|
|
|
2
2
|
import type { PluginConfig } from "../types.js";
|
|
3
3
|
import { isAbortError } from "../abort-error.js";
|
|
4
4
|
import { withTimeoutSignal } from "./abort.js";
|
|
5
|
+
import {
|
|
6
|
+
getHostEmbeddingProvider,
|
|
7
|
+
type HostEmbeddingProvider,
|
|
8
|
+
type HostEmbeddingInputType,
|
|
9
|
+
normalizeHostEmbeddingVector,
|
|
10
|
+
} from "../host-embedding-provider.js";
|
|
5
11
|
|
|
6
12
|
type ProviderConfig = {
|
|
7
|
-
type: "openai" | "local";
|
|
13
|
+
type: "openai" | "local" | "host";
|
|
8
14
|
model: string;
|
|
9
|
-
endpoint
|
|
10
|
-
headers
|
|
15
|
+
endpoint?: string;
|
|
16
|
+
headers?: Record<string, string>;
|
|
17
|
+
hostProvider?: HostEmbeddingProvider;
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
type HostEmbeddingScopeConfig = PluginConfig & {
|
|
21
|
+
/**
|
|
22
|
+
* Internal namespace-router metadata. Host adapters register providers at
|
|
23
|
+
* the root memoryDir while namespace backends operate under scoped dirs.
|
|
24
|
+
*/
|
|
25
|
+
hostEmbeddingProviderScope?: string;
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
type EmbedHelperOptions = {
|
|
29
|
+
/** Backend-local vector schema dimension for host-provider validation. */
|
|
30
|
+
hostEmbeddingExpectedDimension?: number;
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
export type EmbedProviderIdentity = `${ProviderConfig["type"]}:${string}`;
|
|
34
|
+
|
|
35
|
+
export type EmbedWithProviderResult = {
|
|
36
|
+
vector: number[];
|
|
37
|
+
providerIdentity: EmbedProviderIdentity;
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
export type EmbedBatchWithProviderResult = {
|
|
41
|
+
vectors: (number[] | null)[];
|
|
42
|
+
providerIdentity: EmbedProviderIdentity;
|
|
11
43
|
};
|
|
12
44
|
|
|
13
45
|
const DEFAULT_OPENAI_MODEL = "text-embedding-3-small";
|
|
46
|
+
const DEFAULT_PROVIDER_CACHE_TTL_MS = 250;
|
|
14
47
|
|
|
15
48
|
/**
|
|
16
49
|
* Standalone embedding helper for search backend adapters.
|
|
@@ -22,28 +55,70 @@ const DEFAULT_OPENAI_MODEL = "text-embedding-3-small";
|
|
|
22
55
|
* Merging them would break the port/adapter separation between search and plugin layers.
|
|
23
56
|
*/
|
|
24
57
|
export class EmbedHelper {
|
|
25
|
-
private
|
|
58
|
+
private cachedProvider: ProviderConfig | null | undefined;
|
|
59
|
+
private cachedProviderAt = 0;
|
|
60
|
+
private providerCacheTtlMs = DEFAULT_PROVIDER_CACHE_TTL_MS;
|
|
26
61
|
|
|
27
|
-
constructor(
|
|
62
|
+
constructor(
|
|
63
|
+
private readonly config: PluginConfig,
|
|
64
|
+
private readonly options: EmbedHelperOptions = {},
|
|
65
|
+
) {}
|
|
28
66
|
|
|
29
67
|
/**
|
|
30
68
|
* Whether an embedding provider is available.
|
|
31
|
-
*
|
|
69
|
+
* Re-resolves periodically so late host-provider registration is visible
|
|
70
|
+
* without repeatedly probing provider state on every hot-path call.
|
|
32
71
|
*/
|
|
33
72
|
isAvailable(): boolean {
|
|
34
|
-
|
|
35
|
-
this.provider = this.resolveProvider();
|
|
36
|
-
}
|
|
37
|
-
return this.provider !== null;
|
|
73
|
+
return this.getProvider() !== null;
|
|
38
74
|
}
|
|
39
75
|
|
|
40
76
|
/**
|
|
41
77
|
* Embed a single text string. Returns null if no provider is available.
|
|
42
78
|
*/
|
|
43
79
|
async embed(text: string, options: { signal?: AbortSignal } = {}): Promise<number[] | null> {
|
|
80
|
+
return (await this.embedWithProvider(text, options))?.vector ?? null;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
async embedWithProvider(
|
|
84
|
+
text: string,
|
|
85
|
+
options: { signal?: AbortSignal } = {},
|
|
86
|
+
): Promise<EmbedWithProviderResult | null> {
|
|
44
87
|
const provider = this.getProvider();
|
|
45
88
|
if (!provider) return null;
|
|
46
|
-
|
|
89
|
+
const result = await this.callEmbed(text, provider, options.signal, "query");
|
|
90
|
+
if (result) {
|
|
91
|
+
return {
|
|
92
|
+
vector: result,
|
|
93
|
+
providerIdentity: providerIdentity(provider),
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
if (provider.type !== "host") return null;
|
|
97
|
+
const fallbackProvider = this.resolveProvider({ includeHost: false });
|
|
98
|
+
if (!fallbackProvider) return null;
|
|
99
|
+
const fallbackResult = await this.callEmbed(text, fallbackProvider, options.signal, "query");
|
|
100
|
+
return fallbackResult
|
|
101
|
+
? {
|
|
102
|
+
vector: fallbackResult,
|
|
103
|
+
providerIdentity: providerIdentity(fallbackProvider),
|
|
104
|
+
}
|
|
105
|
+
: null;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
async embedWithFallbackProviderIdentity(
|
|
109
|
+
text: string,
|
|
110
|
+
identity: EmbedProviderIdentity,
|
|
111
|
+
options: { signal?: AbortSignal } = {},
|
|
112
|
+
): Promise<EmbedWithProviderResult | null> {
|
|
113
|
+
const provider = this.resolveFallbackProviderForIdentity(identity);
|
|
114
|
+
if (!provider) return null;
|
|
115
|
+
const result = await this.callEmbed(text, provider, options.signal, "query");
|
|
116
|
+
return result
|
|
117
|
+
? {
|
|
118
|
+
vector: result,
|
|
119
|
+
providerIdentity: providerIdentity(provider),
|
|
120
|
+
}
|
|
121
|
+
: null;
|
|
47
122
|
}
|
|
48
123
|
|
|
49
124
|
/**
|
|
@@ -54,15 +129,65 @@ export class EmbedHelper {
|
|
|
54
129
|
batchSize = 32,
|
|
55
130
|
options: { signal?: AbortSignal } = {},
|
|
56
131
|
): Promise<(number[] | null)[]> {
|
|
132
|
+
return (await this.embedBatchWithProvider(texts, batchSize, options))?.vectors ?? texts.map(() => null);
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
async embedBatchWithProvider(
|
|
136
|
+
texts: string[],
|
|
137
|
+
batchSize = 32,
|
|
138
|
+
options: { signal?: AbortSignal } = {},
|
|
139
|
+
): Promise<EmbedBatchWithProviderResult | null> {
|
|
57
140
|
const provider = this.getProvider();
|
|
58
|
-
if (!provider) return
|
|
141
|
+
if (!provider) return null;
|
|
59
142
|
|
|
143
|
+
if (provider.type === "host") {
|
|
144
|
+
const hostResults = await this.embedAllWithProvider(texts, batchSize, provider, options);
|
|
145
|
+
if (!hostResults.some((result) => result === null)) {
|
|
146
|
+
return {
|
|
147
|
+
vectors: hostResults,
|
|
148
|
+
providerIdentity: providerIdentity(provider),
|
|
149
|
+
};
|
|
150
|
+
}
|
|
151
|
+
const fallbackProvider = this.resolveProvider({ includeHost: false });
|
|
152
|
+
if (!fallbackProvider) {
|
|
153
|
+
return {
|
|
154
|
+
vectors: hostResults,
|
|
155
|
+
providerIdentity: providerIdentity(provider),
|
|
156
|
+
};
|
|
157
|
+
}
|
|
158
|
+
const fallbackResults = await this.embedAllWithProvider(texts, batchSize, fallbackProvider, options);
|
|
159
|
+
return {
|
|
160
|
+
vectors: fallbackResults,
|
|
161
|
+
providerIdentity: providerIdentity(fallbackProvider),
|
|
162
|
+
};
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
return {
|
|
166
|
+
vectors: await this.embedAllWithProvider(texts, batchSize, provider, options),
|
|
167
|
+
providerIdentity: providerIdentity(provider),
|
|
168
|
+
};
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
getProviderIdentity(): EmbedProviderIdentity | null {
|
|
172
|
+
const provider = this.getProvider();
|
|
173
|
+
return provider ? providerIdentity(provider) : null;
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
private async embedAllWithProvider(
|
|
177
|
+
texts: string[],
|
|
178
|
+
batchSize: number,
|
|
179
|
+
provider: ProviderConfig,
|
|
180
|
+
options: { signal?: AbortSignal } = {},
|
|
181
|
+
): Promise<(number[] | null)[]> {
|
|
60
182
|
const results: (number[] | null)[] = new Array(texts.length).fill(null);
|
|
61
183
|
for (let i = 0; i < texts.length; i += batchSize) {
|
|
62
184
|
const batch = texts.slice(i, i + batchSize);
|
|
63
|
-
const batchResults =
|
|
64
|
-
|
|
65
|
-
|
|
185
|
+
const batchResults =
|
|
186
|
+
provider.type === "host" && provider.hostProvider?.embedBatch
|
|
187
|
+
? await this.callHostEmbedBatch(batch, provider.hostProvider, options.signal)
|
|
188
|
+
: await Promise.all(
|
|
189
|
+
batch.map((t) => this.callEmbed(t, provider, options.signal, "document")),
|
|
190
|
+
);
|
|
66
191
|
for (let j = 0; j < batchResults.length; j++) {
|
|
67
192
|
results[i + j] = batchResults[j];
|
|
68
193
|
}
|
|
@@ -71,62 +196,131 @@ export class EmbedHelper {
|
|
|
71
196
|
}
|
|
72
197
|
|
|
73
198
|
private getProvider(): ProviderConfig | null {
|
|
74
|
-
|
|
75
|
-
|
|
199
|
+
const now = Date.now();
|
|
200
|
+
if (
|
|
201
|
+
this.cachedProvider !== undefined &&
|
|
202
|
+
now - this.cachedProviderAt < this.providerCacheTtlMs
|
|
203
|
+
) {
|
|
204
|
+
return this.cachedProvider;
|
|
76
205
|
}
|
|
77
|
-
|
|
206
|
+
this.cachedProvider = this.resolveProvider();
|
|
207
|
+
this.cachedProviderAt = now;
|
|
208
|
+
return this.cachedProvider;
|
|
78
209
|
}
|
|
79
210
|
|
|
80
|
-
private resolveProvider(): ProviderConfig | null {
|
|
211
|
+
private resolveProvider(options: { includeHost?: boolean } = {}): ProviderConfig | null {
|
|
81
212
|
if (!this.config.embeddingFallbackEnabled) return null;
|
|
82
213
|
|
|
214
|
+
if (
|
|
215
|
+
options.includeHost !== false &&
|
|
216
|
+
this.config.hostEmbeddingProviderEnabled !== false
|
|
217
|
+
) {
|
|
218
|
+
const hostProvider = this.resolveHostEmbeddingProvider();
|
|
219
|
+
if (hostProvider) {
|
|
220
|
+
return {
|
|
221
|
+
type: "host",
|
|
222
|
+
model: hostProvider.model || hostProvider.id,
|
|
223
|
+
hostProvider,
|
|
224
|
+
};
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
|
|
83
228
|
const preferred = this.config.embeddingFallbackProvider;
|
|
84
229
|
const providers = preferred === "auto" ? ["openai", "local"] : [preferred];
|
|
85
230
|
|
|
86
231
|
for (const p of providers) {
|
|
87
|
-
if (p === "openai"
|
|
88
|
-
const
|
|
89
|
-
return
|
|
90
|
-
type: "openai",
|
|
91
|
-
model: DEFAULT_OPENAI_MODEL,
|
|
92
|
-
endpoint: `${baseUrl.replace(/\/$/, "")}/embeddings`,
|
|
93
|
-
headers: {
|
|
94
|
-
"Content-Type": "application/json",
|
|
95
|
-
Authorization: `Bearer ${this.config.openaiApiKey}`,
|
|
96
|
-
},
|
|
97
|
-
};
|
|
232
|
+
if (p === "openai") {
|
|
233
|
+
const provider = this.createOpenAiProvider();
|
|
234
|
+
if (provider) return provider;
|
|
98
235
|
}
|
|
99
236
|
|
|
100
|
-
if (p === "local"
|
|
101
|
-
const
|
|
102
|
-
|
|
103
|
-
const headers: Record<string, string> = {
|
|
104
|
-
"Content-Type": "application/json",
|
|
105
|
-
...(this.config.localLlmHeaders ?? {}),
|
|
106
|
-
};
|
|
107
|
-
if (this.config.localLlmApiKey && this.config.localLlmAuthHeader !== false) {
|
|
108
|
-
headers.Authorization = `Bearer ${this.config.localLlmApiKey}`;
|
|
109
|
-
}
|
|
110
|
-
return {
|
|
111
|
-
type: "local",
|
|
112
|
-
model:
|
|
113
|
-
this.config.embeddingFallbackModel ||
|
|
114
|
-
this.config.localLlmModel ||
|
|
115
|
-
DEFAULT_OPENAI_MODEL,
|
|
116
|
-
endpoint,
|
|
117
|
-
headers,
|
|
118
|
-
};
|
|
237
|
+
if (p === "local") {
|
|
238
|
+
const provider = this.createLocalProvider();
|
|
239
|
+
if (provider) return provider;
|
|
119
240
|
}
|
|
120
241
|
}
|
|
121
242
|
|
|
122
243
|
return null;
|
|
123
244
|
}
|
|
124
245
|
|
|
246
|
+
private resolveFallbackProviderForIdentity(identity: EmbedProviderIdentity): ProviderConfig | null {
|
|
247
|
+
if (!this.config.embeddingFallbackEnabled) return null;
|
|
248
|
+
const separator = identity.indexOf(":");
|
|
249
|
+
if (separator <= 0 || separator === identity.length - 1) return null;
|
|
250
|
+
const type = identity.slice(0, separator);
|
|
251
|
+
const model = identity.slice(separator + 1);
|
|
252
|
+
|
|
253
|
+
if (type === "openai") {
|
|
254
|
+
const provider = this.createOpenAiProvider();
|
|
255
|
+
return provider && provider.model === model ? provider : null;
|
|
256
|
+
}
|
|
257
|
+
if (type === "local") {
|
|
258
|
+
const provider = this.createLocalProvider();
|
|
259
|
+
return provider && provider.model === model ? provider : null;
|
|
260
|
+
}
|
|
261
|
+
return null;
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
private createOpenAiProvider(): ProviderConfig | null {
|
|
265
|
+
if (!this.config.openaiApiKey) return null;
|
|
266
|
+
const baseUrl = this.config.openaiBaseUrl ?? "https://api.openai.com/v1";
|
|
267
|
+
return {
|
|
268
|
+
type: "openai",
|
|
269
|
+
model: DEFAULT_OPENAI_MODEL,
|
|
270
|
+
endpoint: `${baseUrl.replace(/\/$/, "")}/embeddings`,
|
|
271
|
+
headers: {
|
|
272
|
+
"Content-Type": "application/json",
|
|
273
|
+
Authorization: `Bearer ${this.config.openaiApiKey}`,
|
|
274
|
+
},
|
|
275
|
+
};
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
private createLocalProvider(): ProviderConfig | null {
|
|
279
|
+
if (!this.config.localLlmEnabled || !this.config.localLlmUrl) return null;
|
|
280
|
+
const base = this.config.localLlmUrl.replace(/\/$/, "");
|
|
281
|
+
const endpoint = /\/v1$/i.test(base) ? `${base}/embeddings` : `${base}/v1/embeddings`;
|
|
282
|
+
const headers: Record<string, string> = {
|
|
283
|
+
"Content-Type": "application/json",
|
|
284
|
+
...(this.config.localLlmHeaders ?? {}),
|
|
285
|
+
};
|
|
286
|
+
if (this.config.localLlmApiKey && this.config.localLlmAuthHeader !== false) {
|
|
287
|
+
headers.Authorization = `Bearer ${this.config.localLlmApiKey}`;
|
|
288
|
+
}
|
|
289
|
+
return {
|
|
290
|
+
type: "local",
|
|
291
|
+
model:
|
|
292
|
+
this.config.embeddingFallbackModel ||
|
|
293
|
+
this.config.localLlmModel ||
|
|
294
|
+
DEFAULT_OPENAI_MODEL,
|
|
295
|
+
endpoint,
|
|
296
|
+
headers,
|
|
297
|
+
};
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
private resolveHostEmbeddingProvider(): HostEmbeddingProvider | undefined {
|
|
301
|
+
const scopedConfig = this.config as HostEmbeddingScopeConfig;
|
|
302
|
+
const scopes = [
|
|
303
|
+
scopedConfig.memoryDir,
|
|
304
|
+
scopedConfig.hostEmbeddingProviderScope,
|
|
305
|
+
].filter((scope): scope is string => typeof scope === "string" && scope.trim().length > 0);
|
|
306
|
+
|
|
307
|
+
for (const scope of new Set(scopes)) {
|
|
308
|
+
const provider = getHostEmbeddingProvider(scope);
|
|
309
|
+
if (provider) return provider;
|
|
310
|
+
}
|
|
311
|
+
return undefined;
|
|
312
|
+
}
|
|
313
|
+
|
|
125
314
|
private async callEmbed(
|
|
126
315
|
input: string,
|
|
127
316
|
provider: ProviderConfig,
|
|
128
317
|
signal?: AbortSignal,
|
|
318
|
+
inputType: HostEmbeddingInputType = "document",
|
|
129
319
|
): Promise<number[] | null> {
|
|
320
|
+
if (provider.type === "host") {
|
|
321
|
+
return this.callHostEmbed(input, provider.hostProvider, signal, inputType);
|
|
322
|
+
}
|
|
323
|
+
if (!provider.endpoint || !provider.headers) return null;
|
|
130
324
|
try {
|
|
131
325
|
const res = await fetch(provider.endpoint, {
|
|
132
326
|
method: "POST",
|
|
@@ -144,12 +338,86 @@ export class EmbedHelper {
|
|
|
144
338
|
}
|
|
145
339
|
const payload = (await res.json()) as any;
|
|
146
340
|
const vector = payload?.data?.[0]?.embedding;
|
|
147
|
-
|
|
148
|
-
return vector.map((n: unknown) => { const v = Number(n); return Number.isFinite(v) ? v : 0; });
|
|
341
|
+
return normalizeHttpEmbeddingVector(vector);
|
|
149
342
|
} catch (err) {
|
|
150
343
|
if (isAbortError(err)) throw err;
|
|
151
344
|
log.debug(`EmbedHelper error: ${err}`);
|
|
152
345
|
return null;
|
|
153
346
|
}
|
|
154
347
|
}
|
|
348
|
+
|
|
349
|
+
private async callHostEmbed(
|
|
350
|
+
input: string,
|
|
351
|
+
provider: HostEmbeddingProvider | undefined,
|
|
352
|
+
signal?: AbortSignal,
|
|
353
|
+
inputType: HostEmbeddingInputType = "document",
|
|
354
|
+
): Promise<number[] | null> {
|
|
355
|
+
if (!provider) return null;
|
|
356
|
+
try {
|
|
357
|
+
const vector = await provider.embed(input.slice(0, 8000), {
|
|
358
|
+
signal: withTimeoutSignal(signal, 30_000),
|
|
359
|
+
inputType,
|
|
360
|
+
});
|
|
361
|
+
return this.normalizeHostEmbeddingVector(vector);
|
|
362
|
+
} catch (err) {
|
|
363
|
+
if (isAbortError(err)) throw err;
|
|
364
|
+
log.debug(`EmbedHelper host provider error: ${err}`);
|
|
365
|
+
return null;
|
|
366
|
+
}
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
private async callHostEmbedBatch(
|
|
370
|
+
inputs: string[],
|
|
371
|
+
provider: HostEmbeddingProvider,
|
|
372
|
+
signal?: AbortSignal,
|
|
373
|
+
): Promise<(number[] | null)[]> {
|
|
374
|
+
try {
|
|
375
|
+
const vectors = await provider.embedBatch?.(
|
|
376
|
+
inputs.map((input) => input.slice(0, 8000)),
|
|
377
|
+
{
|
|
378
|
+
signal: withTimeoutSignal(signal, 30_000),
|
|
379
|
+
inputType: "document",
|
|
380
|
+
},
|
|
381
|
+
);
|
|
382
|
+
if (!Array.isArray(vectors)) return inputs.map(() => null);
|
|
383
|
+
return inputs.map((_, index) => this.normalizeHostEmbeddingVector(vectors[index]));
|
|
384
|
+
} catch (err) {
|
|
385
|
+
if (isAbortError(err)) throw err;
|
|
386
|
+
log.debug(`EmbedHelper host provider batch error: ${err}`);
|
|
387
|
+
return inputs.map(() => null);
|
|
388
|
+
}
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
private normalizeHostEmbeddingVector(value: unknown): number[] | null {
|
|
392
|
+
const vector = normalizeHostEmbeddingVector(value);
|
|
393
|
+
if (!vector) return null;
|
|
394
|
+
const expectedDimension = this.resolveHostEmbeddingExpectedDimension();
|
|
395
|
+
if (expectedDimension !== null && vector.length !== expectedDimension) {
|
|
396
|
+
return null;
|
|
397
|
+
}
|
|
398
|
+
return vector;
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
private resolveHostEmbeddingExpectedDimension(): number | null {
|
|
402
|
+
const value = this.options.hostEmbeddingExpectedDimension;
|
|
403
|
+
return typeof value === "number" && Number.isInteger(value) && value > 0
|
|
404
|
+
? value
|
|
405
|
+
: null;
|
|
406
|
+
}
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
function providerIdentity(provider: ProviderConfig): EmbedProviderIdentity {
|
|
410
|
+
return `${provider.type}:${provider.model}`;
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
function normalizeHttpEmbeddingVector(vector: unknown): number[] | null {
|
|
414
|
+
if (!Array.isArray(vector)) return null;
|
|
415
|
+
const normalized: number[] = [];
|
|
416
|
+
for (const component of vector) {
|
|
417
|
+
if (typeof component !== "number" || !Number.isFinite(component)) {
|
|
418
|
+
return null;
|
|
419
|
+
}
|
|
420
|
+
normalized.push(component);
|
|
421
|
+
}
|
|
422
|
+
return normalized;
|
|
155
423
|
}
|
package/src/search/factory.ts
CHANGED
|
@@ -41,7 +41,9 @@ function resolveNonQmdBackend(config: PluginConfig): SearchBackend | undefined {
|
|
|
41
41
|
}
|
|
42
42
|
|
|
43
43
|
if (backend === "lancedb") {
|
|
44
|
-
const embedHelper = new EmbedHelper(config
|
|
44
|
+
const embedHelper = new EmbedHelper(config, {
|
|
45
|
+
hostEmbeddingExpectedDimension: config.lanceEmbeddingDimension,
|
|
46
|
+
});
|
|
45
47
|
return new LanceDbBackend({
|
|
46
48
|
dbPath: config.lanceDbPath!,
|
|
47
49
|
collection,
|
|
@@ -63,7 +65,9 @@ function resolveNonQmdBackend(config: PluginConfig): SearchBackend | undefined {
|
|
|
63
65
|
}
|
|
64
66
|
|
|
65
67
|
if (backend === "orama") {
|
|
66
|
-
const embedHelper = new EmbedHelper(config
|
|
68
|
+
const embedHelper = new EmbedHelper(config, {
|
|
69
|
+
hostEmbeddingExpectedDimension: config.oramaEmbeddingDimension,
|
|
70
|
+
});
|
|
67
71
|
return new OramaBackend({
|
|
68
72
|
dbPath: config.oramaDbPath!,
|
|
69
73
|
collection,
|