@untangle-code/embeddings-ollama 0.0.0-stage → 0.1.0
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/LICENSE +661 -0
- package/NOTICE +90 -0
- package/README.md +187 -2
- package/THIRD_PARTY_LICENSES.md +24 -0
- package/dist/backoff.d.ts +43 -0
- package/dist/backoff.d.ts.map +1 -0
- package/dist/backoff.js +38 -0
- package/dist/backoff.js.map +1 -0
- package/dist/cache.d.ts +17 -0
- package/dist/cache.d.ts.map +1 -0
- package/dist/cache.js +17 -0
- package/dist/cache.js.map +1 -0
- package/dist/concurrency.d.ts +11 -0
- package/dist/concurrency.d.ts.map +1 -0
- package/dist/concurrency.js +31 -0
- package/dist/concurrency.js.map +1 -0
- package/dist/endpoint.d.ts +16 -0
- package/dist/endpoint.d.ts.map +1 -0
- package/dist/endpoint.js +32 -0
- package/dist/endpoint.js.map +1 -0
- package/dist/http.d.ts +54 -0
- package/dist/http.d.ts.map +1 -0
- package/dist/http.js +136 -0
- package/dist/http.js.map +1 -0
- package/dist/index.d.ts +13 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +18 -0
- package/dist/index.js.map +1 -0
- package/dist/inflight.d.ts +49 -0
- package/dist/inflight.d.ts.map +1 -0
- package/dist/inflight.js +87 -0
- package/dist/inflight.js.map +1 -0
- package/dist/ollama-api.d.ts +45 -0
- package/dist/ollama-api.d.ts.map +1 -0
- package/dist/ollama-api.js +40 -0
- package/dist/ollama-api.js.map +1 -0
- package/dist/provider.d.ts +111 -0
- package/dist/provider.d.ts.map +1 -0
- package/dist/provider.js +374 -0
- package/dist/provider.js.map +1 -0
- package/dist/readiness.d.ts +31 -0
- package/dist/readiness.d.ts.map +1 -0
- package/dist/readiness.js +105 -0
- package/dist/readiness.js.map +1 -0
- package/dist/sleep.d.ts +8 -0
- package/dist/sleep.d.ts.map +1 -0
- package/dist/sleep.js +20 -0
- package/dist/sleep.js.map +1 -0
- package/dist/truncation.d.ts +30 -0
- package/dist/truncation.d.ts.map +1 -0
- package/dist/truncation.js +39 -0
- package/dist/truncation.js.map +1 -0
- package/dist/types.d.ts +108 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +7 -0
- package/dist/types.js.map +1 -0
- package/dist/validation.d.ts +2 -0
- package/dist/validation.d.ts.map +1 -0
- package/dist/validation.js +16 -0
- package/dist/validation.js.map +1 -0
- package/package.json +62 -3
- package/src/backoff.ts +73 -0
- package/src/cache.ts +30 -0
- package/src/concurrency.ts +37 -0
- package/src/endpoint.ts +31 -0
- package/src/http.ts +168 -0
- package/src/index.ts +21 -0
- package/src/inflight.ts +102 -0
- package/src/ollama-api.ts +93 -0
- package/src/provider.ts +477 -0
- package/src/readiness.ts +203 -0
- package/src/sleep.ts +27 -0
- package/src/truncation.ts +41 -0
- package/src/types.ts +132 -0
- package/src/validation.ts +23 -0
package/dist/provider.js
ADDED
|
@@ -0,0 +1,374 @@
|
|
|
1
|
+
import { buildEmbeddingProfileId, isUntangleError, sha256Hex } from "@untangle-code/domain";
|
|
2
|
+
import { embeddingUnavailableError, jobCancelledError } from "@untangle-code/observability";
|
|
3
|
+
import { withRetry } from "./backoff.js";
|
|
4
|
+
import { InMemoryEmbeddingCache } from "./cache.js";
|
|
5
|
+
import { mapWithConcurrency } from "./concurrency.js";
|
|
6
|
+
import { isRequestCancellation, OllamaHttpClient, OllamaHttpError } from "./http.js";
|
|
7
|
+
import { createDeferred, InFlightRegistry, raceWithSignal } from "./inflight.js";
|
|
8
|
+
import { embed } from "./ollama-api.js";
|
|
9
|
+
import { runReadinessSequence } from "./readiness.js";
|
|
10
|
+
import { realSleep } from "./sleep.js";
|
|
11
|
+
import { wouldProviderTruncate } from "./truncation.js";
|
|
12
|
+
import { DEFAULT_BASE_BACKOFF_MS, DEFAULT_MAX_BACKOFF_MS, DEFAULT_MAX_RETRIES } from "./types.js";
|
|
13
|
+
import { assertVectorDimension } from "./validation.js";
|
|
14
|
+
function isRetryableEmbedError(error) {
|
|
15
|
+
return error instanceof OllamaHttpError && error.retryable;
|
|
16
|
+
}
|
|
17
|
+
/** Same shape as `cache.ts`'s internal key string — local to this module, used only for in-flight bookkeeping. */
|
|
18
|
+
function cacheKeyString(key) {
|
|
19
|
+
return `${key.embeddingProfileId}:${key.preparedTextHash}`;
|
|
20
|
+
}
|
|
21
|
+
/** Splits `items` into consecutive slices of at most `size` (the last one possibly shorter). */
|
|
22
|
+
function chunkIntoSlices(items, size) {
|
|
23
|
+
if (items.length === 0) {
|
|
24
|
+
return [];
|
|
25
|
+
}
|
|
26
|
+
const slices = [];
|
|
27
|
+
for (let start = 0; start < items.length; start += size) {
|
|
28
|
+
slices.push(items.slice(start, start + size));
|
|
29
|
+
}
|
|
30
|
+
return slices;
|
|
31
|
+
}
|
|
32
|
+
/** Normalizes any thrown value into the `UntangleError` this package's public API promises (rule 8). */
|
|
33
|
+
function normalizeError(error, signal) {
|
|
34
|
+
if (isUntangleError(error)) {
|
|
35
|
+
return error;
|
|
36
|
+
}
|
|
37
|
+
if (isRequestCancellation(error, signal)) {
|
|
38
|
+
return jobCancelledError("Ollama embedding operation was cancelled.", {
|
|
39
|
+
cause: error,
|
|
40
|
+
retryable: false,
|
|
41
|
+
});
|
|
42
|
+
}
|
|
43
|
+
if (error instanceof OllamaHttpError) {
|
|
44
|
+
return embeddingUnavailableError(error.message, {
|
|
45
|
+
cause: error,
|
|
46
|
+
retryable: false,
|
|
47
|
+
context: error.status === undefined ? {} : { status: error.status },
|
|
48
|
+
});
|
|
49
|
+
}
|
|
50
|
+
return embeddingUnavailableError(`Unexpected Ollama embedding provider error: ${String(error)}`, {
|
|
51
|
+
cause: error,
|
|
52
|
+
retryable: false,
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
async function guarded(signal, fn) {
|
|
56
|
+
try {
|
|
57
|
+
signal?.throwIfAborted();
|
|
58
|
+
return await fn();
|
|
59
|
+
}
|
|
60
|
+
catch (error) {
|
|
61
|
+
throw normalizeError(error, signal);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
export class OllamaEmbeddingProvider {
|
|
65
|
+
profile;
|
|
66
|
+
profileId;
|
|
67
|
+
config;
|
|
68
|
+
clock;
|
|
69
|
+
random;
|
|
70
|
+
cache;
|
|
71
|
+
sleep;
|
|
72
|
+
httpClient;
|
|
73
|
+
backoffConfig;
|
|
74
|
+
cachedReadiness;
|
|
75
|
+
resolvedEndpointUrl;
|
|
76
|
+
readyPromise;
|
|
77
|
+
/**
|
|
78
|
+
* One shared fetch-and-cache operation per still-uncached cache key
|
|
79
|
+
* (spec.md §9.4, WP-78): concurrent `embedDocuments`/`embedQuery` calls
|
|
80
|
+
* that race for the same not-yet-cached text join the SAME `/api/embed`
|
|
81
|
+
* request instead of each starting their own.
|
|
82
|
+
*/
|
|
83
|
+
inFlightEmbeds = new InFlightRegistry();
|
|
84
|
+
constructor(config, deps) {
|
|
85
|
+
this.config = config;
|
|
86
|
+
this.profile = config.profile;
|
|
87
|
+
this.profileId = buildEmbeddingProfileId(config.profile);
|
|
88
|
+
this.clock = deps.clock;
|
|
89
|
+
this.random = deps.random;
|
|
90
|
+
this.cache = deps.cache ?? new InMemoryEmbeddingCache();
|
|
91
|
+
this.sleep = deps.sleep ?? realSleep;
|
|
92
|
+
this.httpClient = new OllamaHttpClient({
|
|
93
|
+
requestTimeoutMs: config.requestTimeoutMs,
|
|
94
|
+
maxConnections: config.maxConnections,
|
|
95
|
+
apiKeyEnv: config.apiKeyEnv,
|
|
96
|
+
fetchImpl: deps.fetchImpl,
|
|
97
|
+
});
|
|
98
|
+
this.backoffConfig = {
|
|
99
|
+
maxRetries: config.maxRetries ?? DEFAULT_MAX_RETRIES,
|
|
100
|
+
baseDelayMs: config.baseBackoffMs ?? DEFAULT_BASE_BACKOFF_MS,
|
|
101
|
+
maxDelayMs: config.maxBackoffMs ?? DEFAULT_MAX_BACKOFF_MS,
|
|
102
|
+
};
|
|
103
|
+
}
|
|
104
|
+
async ensureReady(options) {
|
|
105
|
+
return guarded(options?.signal, async () => {
|
|
106
|
+
await this.ensureReadyOnce(options?.signal);
|
|
107
|
+
// Non-null: ensureReadyOnce either already resolved it or just did.
|
|
108
|
+
return this.cachedReadiness;
|
|
109
|
+
});
|
|
110
|
+
}
|
|
111
|
+
async embedDocuments(preparedTexts, options) {
|
|
112
|
+
return guarded(options?.signal, async () => {
|
|
113
|
+
if (preparedTexts.length === 0) {
|
|
114
|
+
return { vectors: [], truncated: [] };
|
|
115
|
+
}
|
|
116
|
+
await this.ensureReadyOnce(options?.signal);
|
|
117
|
+
return this.embedTexts(preparedTexts, options?.signal, options?.onBatchProgress);
|
|
118
|
+
});
|
|
119
|
+
}
|
|
120
|
+
async embedQuery(preparedText, options) {
|
|
121
|
+
return guarded(options?.signal, async () => {
|
|
122
|
+
await this.ensureReadyOnce(options?.signal);
|
|
123
|
+
const { vectors } = await this.embedTexts([preparedText], options?.signal);
|
|
124
|
+
return vectors[0];
|
|
125
|
+
});
|
|
126
|
+
}
|
|
127
|
+
/**
|
|
128
|
+
* Runs the readiness sequence exactly once per instance; concurrent
|
|
129
|
+
* callers (including through model-pull, spec.md §9.1 step 4) await the
|
|
130
|
+
* SAME run rather than each starting their own (WP-78 fehlerklasse: the
|
|
131
|
+
* same in-flight-dedup requirement applies here, not just to `/api/embed`).
|
|
132
|
+
*
|
|
133
|
+
* The shared run itself is started with NO caller's `signal` (it is
|
|
134
|
+
* intentionally not tied to whichever caller happened to trigger it) —
|
|
135
|
+
* `raceWithSignal` below still lets THIS caller's own signal reject its
|
|
136
|
+
* own wait promptly, without cancelling the readiness sequence still
|
|
137
|
+
* running for other, still-waiting callers.
|
|
138
|
+
*/
|
|
139
|
+
async ensureReadyOnce(signal) {
|
|
140
|
+
if (this.cachedReadiness) {
|
|
141
|
+
return;
|
|
142
|
+
}
|
|
143
|
+
if (!this.readyPromise) {
|
|
144
|
+
this.readyPromise = this.runReadiness().catch((error) => {
|
|
145
|
+
// A failed attempt must not be cached — the next call retries from scratch.
|
|
146
|
+
this.readyPromise = undefined;
|
|
147
|
+
throw error;
|
|
148
|
+
});
|
|
149
|
+
}
|
|
150
|
+
await raceWithSignal(this.readyPromise, signal);
|
|
151
|
+
}
|
|
152
|
+
async runReadiness() {
|
|
153
|
+
const outcome = await runReadinessSequence(this.config, {
|
|
154
|
+
httpClient: this.httpClient,
|
|
155
|
+
clock: this.clock,
|
|
156
|
+
random: this.random,
|
|
157
|
+
sleep: this.sleep,
|
|
158
|
+
backoffConfig: this.backoffConfig,
|
|
159
|
+
}, undefined);
|
|
160
|
+
this.cachedReadiness = outcome.readiness;
|
|
161
|
+
this.resolvedEndpointUrl = outcome.endpointUrl;
|
|
162
|
+
}
|
|
163
|
+
async embedTexts(texts, signal, onBatchProgress) {
|
|
164
|
+
const endpointUrl = this.resolvedEndpointUrl;
|
|
165
|
+
if (!endpointUrl) {
|
|
166
|
+
throw embeddingUnavailableError("Ollama embedding provider is not ready (no resolved endpoint).", {
|
|
167
|
+
retryable: false,
|
|
168
|
+
});
|
|
169
|
+
}
|
|
170
|
+
const slices = chunkIntoSlices(texts, Math.max(1, this.config.batchSize));
|
|
171
|
+
const batchCount = slices.length;
|
|
172
|
+
let embeddedCount = 0;
|
|
173
|
+
const sliceResults = await mapWithConcurrency(slices, this.config.maxConnections, async (slice, sliceIndex) => {
|
|
174
|
+
signal?.throwIfAborted();
|
|
175
|
+
const result = await this.embedSlice(slice, endpointUrl, signal);
|
|
176
|
+
embeddedCount += slice.length;
|
|
177
|
+
onBatchProgress?.({
|
|
178
|
+
batchIndex: sliceIndex,
|
|
179
|
+
batchCount,
|
|
180
|
+
embeddedCount,
|
|
181
|
+
totalCount: texts.length,
|
|
182
|
+
});
|
|
183
|
+
return result;
|
|
184
|
+
});
|
|
185
|
+
const vectors = sliceResults.flatMap((result) => result.vectors);
|
|
186
|
+
const truncated = sliceResults.flatMap((result) => result.truncated);
|
|
187
|
+
return { vectors, truncated };
|
|
188
|
+
}
|
|
189
|
+
async embedSlice(texts, endpointUrl, signal) {
|
|
190
|
+
const keys = texts.map((text) => ({
|
|
191
|
+
embeddingProfileId: this.profileId,
|
|
192
|
+
preparedTextHash: sha256Hex(text),
|
|
193
|
+
}));
|
|
194
|
+
const keyStrings = keys.map(cacheKeyString);
|
|
195
|
+
const cached = await Promise.all(keys.map((key) => this.cache.get(key, { signal })));
|
|
196
|
+
const vectors = new Array(texts.length);
|
|
197
|
+
const truncated = new Array(texts.length);
|
|
198
|
+
// Group the still-missing positions by cache key: (a) the SAME prepared
|
|
199
|
+
// text appearing more than once in this slice must be embedded only
|
|
200
|
+
// once (spec.md §9.4, WP-78 "Batch-interne Deduplizierung"), and (b) a
|
|
201
|
+
// key already being fetched by ANOTHER concurrent call is joined below
|
|
202
|
+
// instead of triggering a second `/api/embed` request.
|
|
203
|
+
const missGroups = new Map();
|
|
204
|
+
for (let index = 0; index < texts.length; index += 1) {
|
|
205
|
+
const hit = cached[index];
|
|
206
|
+
if (hit) {
|
|
207
|
+
vectors[index] = hit.vector;
|
|
208
|
+
truncated[index] = hit.truncated;
|
|
209
|
+
continue;
|
|
210
|
+
}
|
|
211
|
+
const keyString = keyStrings[index];
|
|
212
|
+
const group = missGroups.get(keyString);
|
|
213
|
+
if (group) {
|
|
214
|
+
group.push(index);
|
|
215
|
+
}
|
|
216
|
+
else {
|
|
217
|
+
missGroups.set(keyString, [index]);
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
if (missGroups.size === 0) {
|
|
221
|
+
return { vectors, truncated };
|
|
222
|
+
}
|
|
223
|
+
signal?.throwIfAborted();
|
|
224
|
+
// Claim/join in-flight fetches. This loop MUST NOT `await` anything: two
|
|
225
|
+
// concurrent `embedSlice` calls that both read a cache miss for the same
|
|
226
|
+
// key resume as two separate, atomically-run continuations (JS never
|
|
227
|
+
// interleaves them mid-loop), so whichever runs this claiming loop first
|
|
228
|
+
// wins the key and the other necessarily observes and joins it instead
|
|
229
|
+
// of starting its own fetch (WP-78 "Nebenläufigkeit").
|
|
230
|
+
const toFetch = [];
|
|
231
|
+
const pending = [];
|
|
232
|
+
for (const [keyString, indexes] of missGroups) {
|
|
233
|
+
const existing = this.inFlightEmbeds.get(keyString);
|
|
234
|
+
if (existing) {
|
|
235
|
+
pending.push({ indexes, promise: existing });
|
|
236
|
+
continue;
|
|
237
|
+
}
|
|
238
|
+
const deferred = createDeferred();
|
|
239
|
+
this.inFlightEmbeds.register(keyString, deferred.promise);
|
|
240
|
+
const firstIndex = indexes[0];
|
|
241
|
+
toFetch.push({
|
|
242
|
+
keyString,
|
|
243
|
+
key: keys[firstIndex],
|
|
244
|
+
text: texts[firstIndex],
|
|
245
|
+
deferred,
|
|
246
|
+
});
|
|
247
|
+
pending.push({ indexes, promise: deferred.promise });
|
|
248
|
+
}
|
|
249
|
+
if (toFetch.length > 0) {
|
|
250
|
+
// Deliberately not awaited here: the underlying request must survive
|
|
251
|
+
// (and keep serving every joined caller) even if THIS caller — the
|
|
252
|
+
// one that happened to claim the keys — is the one whose own signal
|
|
253
|
+
// aborts. Errors are routed to each `deferred` inside the method, so
|
|
254
|
+
// this can never become an unhandled rejection.
|
|
255
|
+
void this.fetchAndSettle(toFetch, endpointUrl);
|
|
256
|
+
}
|
|
257
|
+
await Promise.all(pending.map(async ({ indexes, promise }) => {
|
|
258
|
+
// `raceWithSignal`, not `promise` directly: THIS call's own signal
|
|
259
|
+
// must be able to reject THIS call without cancelling the shared
|
|
260
|
+
// fetch for any other caller still awaiting the same key.
|
|
261
|
+
const entry = await raceWithSignal(promise, signal);
|
|
262
|
+
for (const index of indexes) {
|
|
263
|
+
vectors[index] = entry.vector;
|
|
264
|
+
truncated[index] = entry.truncated;
|
|
265
|
+
}
|
|
266
|
+
}));
|
|
267
|
+
return { vectors, truncated };
|
|
268
|
+
}
|
|
269
|
+
/**
|
|
270
|
+
* Fetches every text in `items` in a SINGLE `/api/embed` request and
|
|
271
|
+
* settles each item's `deferred` with its own resulting cache entry, or
|
|
272
|
+
* rejects all of them on failure — which also frees their in-flight slots
|
|
273
|
+
* (`InFlightRegistry.register`'s cleanup), so the next call retries from
|
|
274
|
+
* scratch rather than being stuck on a broken promise (WP-78).
|
|
275
|
+
*
|
|
276
|
+
* Vor dem Absetzen des Requests wird der Cache ein ZWEITES Mal gelesen
|
|
277
|
+
* (WP-117, siehe {@link revalidateBeforeFetch}): die Miss-Entscheidung in
|
|
278
|
+
* `embedSlice` kann veraltet sein, sobald `cache.get()` verzögert auflöst.
|
|
279
|
+
*
|
|
280
|
+
* Intentionally takes no `AbortSignal`: this operation is shared by
|
|
281
|
+
* however many callers joined it, so it is not tied to any single one of
|
|
282
|
+
* their lifecycles (see `embedSlice`/`raceWithSignal`).
|
|
283
|
+
*/
|
|
284
|
+
async fetchAndSettle(items, endpointUrl) {
|
|
285
|
+
try {
|
|
286
|
+
// WP-117: erst jetzt, mit den bereits beanspruchten In-flight-Plätzen
|
|
287
|
+
// in der Hand, steht fest, was wirklich über die Leitung muss.
|
|
288
|
+
const outstanding = await this.revalidateBeforeFetch(items);
|
|
289
|
+
if (outstanding.length === 0) {
|
|
290
|
+
return;
|
|
291
|
+
}
|
|
292
|
+
const texts = outstanding.map((item) => item.text);
|
|
293
|
+
const response = await withRetry(() => embed(this.httpClient, endpointUrl, this.profile.model, texts), this.backoffConfig, { random: this.random, sleep: this.sleep }, { isRetryable: isRetryableEmbedError });
|
|
294
|
+
if (response.embeddings.length !== outstanding.length) {
|
|
295
|
+
throw embeddingUnavailableError(`Ollama /api/embed returned ${response.embeddings.length} vectors for ${outstanding.length} inputs.`, { retryable: false });
|
|
296
|
+
}
|
|
297
|
+
for (let j = 0; j < outstanding.length; j += 1) {
|
|
298
|
+
const item = outstanding[j];
|
|
299
|
+
const vector = response.embeddings[j];
|
|
300
|
+
assertVectorDimension(vector, this.profile.dimensions, {
|
|
301
|
+
endpoint: endpointUrl,
|
|
302
|
+
model: this.profile.model,
|
|
303
|
+
stage: "embed",
|
|
304
|
+
});
|
|
305
|
+
const isTruncated = wouldProviderTruncate(item.text, this.config.contextLength);
|
|
306
|
+
const entry = { vector, truncated: isTruncated };
|
|
307
|
+
await this.cache.set(item.key, entry);
|
|
308
|
+
item.deferred.resolve(entry);
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
catch (error) {
|
|
312
|
+
// Bewusst über `items` und nicht über `outstanding`: schlägt schon die
|
|
313
|
+
// Revalidierung fehl, ist `outstanding` gar nicht bestimmt. Ein bereits
|
|
314
|
+
// aus dem Cache aufgelöstes Deferred ignoriert das spätere `reject`
|
|
315
|
+
// (die erste Festlegung gewinnt), ein noch offenes wird sicher
|
|
316
|
+
// abgewiesen — kein Aufrufer bleibt hängen.
|
|
317
|
+
for (const item of items) {
|
|
318
|
+
item.deferred.reject(error);
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
/**
|
|
323
|
+
* Zweite Cache-Prüfung unmittelbar vor dem Request (WP-117).
|
|
324
|
+
*
|
|
325
|
+
* `embedSlice` trifft die Miss-/Join-Entscheidung EINMAL, aus einem
|
|
326
|
+
* einzigen `Promise.all` über `cache.get()`. Ein Cache, dessen `get()` den
|
|
327
|
+
* Zustand zum Aufrufzeitpunkt liest, aber erst später auflöst — also jeder
|
|
328
|
+
* netzgebundene Cache und damit genau der vorgesehene Erweiterungspunkt
|
|
329
|
+
* für einen persistenten Adapter —, öffnet ein Fenster: Aufrufer A kann
|
|
330
|
+
* zwischen der Anfrage und dem Auflösen von B's `get()` vollständig fertig
|
|
331
|
+
* werden, seinen Eintrag schreiben und seinen In-flight-Platz wieder
|
|
332
|
+
* freigeben. B fände dann weder Cache-Treffer noch In-flight-Eintrag und
|
|
333
|
+
* würde denselben Text ein zweites Mal einbetten.
|
|
334
|
+
*
|
|
335
|
+
* Diese Prüfung schließt das Fenster, weil sie STRIKT NACH der Beanspruchung
|
|
336
|
+
* des In-flight-Platzes liegt: der Platz wird erst freigegeben, nachdem
|
|
337
|
+
* `cache.set()` aufgelöst hat (siehe unten), also sieht ein `get()`, das
|
|
338
|
+
* danach abgesetzt wird, den geschriebenen Eintrag.
|
|
339
|
+
*
|
|
340
|
+
* Kein Deadlock und keine Wiederholungsschleife: genau EIN zusätzliches
|
|
341
|
+
* `cache.get()` je beanspruchtem Schlüssel, kein Warten auf fremde
|
|
342
|
+
* Deferreds (die eigenen Plätze hält dieser Aufruf selbst, ein anderer
|
|
343
|
+
* Aufrufer kann sie also nicht beanspruchen), und jedes Deferred wird auf
|
|
344
|
+
* jedem Pfad festgelegt — aus dem Cache, aus der Antwort oder per
|
|
345
|
+
* `reject` im `catch`.
|
|
346
|
+
*
|
|
347
|
+
* Kein `AbortSignal`: dieser Lauf gehört keinem einzelnen Aufrufer
|
|
348
|
+
* (dieselbe Begründung wie bei {@link fetchAndSettle}).
|
|
349
|
+
*
|
|
350
|
+
* Ein Fehler des Caches wird NICHT verschluckt (E4), sondern nach oben
|
|
351
|
+
* gereicht und weist damit die beteiligten Deferreds ab — dasselbe
|
|
352
|
+
* sichtbare Verhalten wie bei einem Fehlschlag der ERSTEN Cache-Lesung in
|
|
353
|
+
* `embedSlice`. Preis dieser Prüfung: eine zusätzliche Cache-Lesung je
|
|
354
|
+
* echtem Miss (dokumentiert in der README).
|
|
355
|
+
*/
|
|
356
|
+
async revalidateBeforeFetch(items) {
|
|
357
|
+
// Erst alle Lesungen abschließen, dann festlegen: so ist bei einem Fehler
|
|
358
|
+
// noch kein Deferred aufgelöst und der `catch` in `fetchAndSettle` weist
|
|
359
|
+
// wirklich alle ab.
|
|
360
|
+
const lateHits = await Promise.all(items.map(async (item) => this.cache.get(item.key)));
|
|
361
|
+
const outstanding = [];
|
|
362
|
+
for (let index = 0; index < items.length; index += 1) {
|
|
363
|
+
const item = items[index];
|
|
364
|
+
const hit = lateHits[index];
|
|
365
|
+
if (hit) {
|
|
366
|
+
item.deferred.resolve(hit);
|
|
367
|
+
continue;
|
|
368
|
+
}
|
|
369
|
+
outstanding.push(item);
|
|
370
|
+
}
|
|
371
|
+
return outstanding;
|
|
372
|
+
}
|
|
373
|
+
}
|
|
374
|
+
//# sourceMappingURL=provider.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"provider.js","sourceRoot":"","sources":["../src/provider.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Readiness sequence (spec.md §9.1), run exactly in the spec's order:
|
|
3
|
+
*
|
|
4
|
+
* 1. determine the effective endpoint;
|
|
5
|
+
* 2. check reachability;
|
|
6
|
+
* 3. check model availability;
|
|
7
|
+
* 4. pull the model in managed mode if needed;
|
|
8
|
+
* 5. produce a probe embedding;
|
|
9
|
+
* 6. validate the vector dimension;
|
|
10
|
+
* 7. cache profile and probe timestamp (done by the caller, `provider.ts`,
|
|
11
|
+
* which owns the cached `EmbeddingReadinessV1`).
|
|
12
|
+
*/
|
|
13
|
+
import type { Clock, EmbeddingReadinessV1, Random } from "@untangle-code/domain";
|
|
14
|
+
import type { BackoffConfig } from "./backoff.js";
|
|
15
|
+
import { type OllamaHttpClient } from "./http.js";
|
|
16
|
+
import type { SleepFn, OllamaEmbeddingProviderConfig } from "./types.js";
|
|
17
|
+
export declare const READINESS_PROBE_TEXT = "untangle embedding readiness probe";
|
|
18
|
+
export interface ReadinessDeps {
|
|
19
|
+
httpClient: OllamaHttpClient;
|
|
20
|
+
clock: Clock;
|
|
21
|
+
random: Random;
|
|
22
|
+
sleep: SleepFn;
|
|
23
|
+
backoffConfig: BackoffConfig;
|
|
24
|
+
}
|
|
25
|
+
export interface ReadinessOutcome {
|
|
26
|
+
readiness: EmbeddingReadinessV1;
|
|
27
|
+
/** The resolved, non-redacted endpoint — used internally for subsequent embed calls. */
|
|
28
|
+
endpointUrl: string;
|
|
29
|
+
}
|
|
30
|
+
export declare function runReadinessSequence(config: OllamaEmbeddingProviderConfig, deps: ReadinessDeps, signal?: AbortSignal): Promise<ReadinessOutcome>;
|
|
31
|
+
//# sourceMappingURL=readiness.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"readiness.d.ts","sourceRoot":"","sources":["../src/readiness.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AACH,OAAO,KAAK,EAAE,KAAK,EAAE,oBAAoB,EAAE,MAAM,EAAE,MAAM,uBAAuB,CAAC;AAGjF,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,cAAc,CAAC;AAGlD,OAAO,EAA0C,KAAK,gBAAgB,EAAE,MAAM,WAAW,CAAC;AAQ1F,OAAO,KAAK,EAAE,OAAO,EAAE,6BAA6B,EAAE,MAAM,YAAY,CAAC;AAIzE,eAAO,MAAM,oBAAoB,uCAAuC,CAAC;AAEzE,MAAM,WAAW,aAAa;IAC5B,UAAU,EAAE,gBAAgB,CAAC;IAC7B,KAAK,EAAE,KAAK,CAAC;IACb,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,EAAE,OAAO,CAAC;IACf,aAAa,EAAE,aAAa,CAAC;CAC9B;AAED,MAAM,WAAW,gBAAgB;IAC/B,SAAS,EAAE,oBAAoB,CAAC;IAChC,wFAAwF;IACxF,WAAW,EAAE,MAAM,CAAC;CACrB;AA6FD,wBAAsB,oBAAoB,CACxC,MAAM,EAAE,6BAA6B,EACrC,IAAI,EAAE,aAAa,EACnB,MAAM,CAAC,EAAE,WAAW,GACnB,OAAO,CAAC,gBAAgB,CAAC,CA6D3B"}
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
import { configInvalidError, embeddingUnavailableError } from "@untangle-code/observability";
|
|
2
|
+
import { withRetry } from "./backoff.js";
|
|
3
|
+
import { redactEndpointUrl } from "./endpoint.js";
|
|
4
|
+
import { isRequestCancellation, OllamaHttpError } from "./http.js";
|
|
5
|
+
import { embed, listTags, pullModel, tagsContainModel, } from "./ollama-api.js";
|
|
6
|
+
import { DEFAULT_MANAGED_URL, DEFAULT_NATIVE_PROBE_URL } from "./types.js";
|
|
7
|
+
import { assertVectorDimension } from "./validation.js";
|
|
8
|
+
export const READINESS_PROBE_TEXT = "untangle embedding readiness probe";
|
|
9
|
+
function isRetryableOllamaError(error) {
|
|
10
|
+
return error instanceof OllamaHttpError && error.retryable;
|
|
11
|
+
}
|
|
12
|
+
async function requestWithRetry(fn, deps, signal) {
|
|
13
|
+
return withRetry(fn, deps.backoffConfig, { random: deps.random, sleep: deps.sleep }, {
|
|
14
|
+
signal,
|
|
15
|
+
isRetryable: isRetryableOllamaError,
|
|
16
|
+
});
|
|
17
|
+
}
|
|
18
|
+
/** `GET /api/tags`, treating any failure as "not reachable" — EXCEPT cancellation, which propagates. */
|
|
19
|
+
async function tryListTags(url, deps, signal) {
|
|
20
|
+
try {
|
|
21
|
+
return await requestWithRetry(() => listTags(deps.httpClient, url, signal), deps, signal);
|
|
22
|
+
}
|
|
23
|
+
catch (error) {
|
|
24
|
+
if (isRequestCancellation(error, signal)) {
|
|
25
|
+
throw error;
|
|
26
|
+
}
|
|
27
|
+
return null;
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
async function checkReachable(url, deps, signal) {
|
|
31
|
+
const tags = await tryListTags(url, deps, signal);
|
|
32
|
+
if (!tags) {
|
|
33
|
+
throw embeddingUnavailableError(`Ollama endpoint ${redactEndpointUrl(url)} is not reachable.`, {
|
|
34
|
+
retryable: true,
|
|
35
|
+
context: { endpoint: redactEndpointUrl(url) },
|
|
36
|
+
});
|
|
37
|
+
}
|
|
38
|
+
return tags;
|
|
39
|
+
}
|
|
40
|
+
/** spec.md §9.1 steps 1-2: determine the effective endpoint and confirm it is reachable. */
|
|
41
|
+
async function resolveEndpoint(config, deps, signal) {
|
|
42
|
+
if (config.mode === "external") {
|
|
43
|
+
if (!config.url) {
|
|
44
|
+
throw configInvalidError('EmbeddingConfig.url is required when embeddings.mode is "external" (spec.md §4.5).', { retryable: false });
|
|
45
|
+
}
|
|
46
|
+
return {
|
|
47
|
+
url: config.url,
|
|
48
|
+
managed: false,
|
|
49
|
+
tags: await checkReachable(config.url, deps, signal),
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
if (config.mode === "managed") {
|
|
53
|
+
const url = config.url ?? config.managedUrl ?? DEFAULT_MANAGED_URL;
|
|
54
|
+
return { url, managed: true, tags: await checkReachable(url, deps, signal) };
|
|
55
|
+
}
|
|
56
|
+
// "auto" (spec.md §3.1): native probe first, managed endpoint as fallback.
|
|
57
|
+
const nativeUrl = config.nativeProbeUrl ?? DEFAULT_NATIVE_PROBE_URL;
|
|
58
|
+
const nativeTags = await tryListTags(nativeUrl, deps, signal);
|
|
59
|
+
if (nativeTags) {
|
|
60
|
+
return { url: nativeUrl, managed: false, tags: nativeTags };
|
|
61
|
+
}
|
|
62
|
+
const managedUrl = config.url ?? config.managedUrl ?? DEFAULT_MANAGED_URL;
|
|
63
|
+
return { url: managedUrl, managed: true, tags: await checkReachable(managedUrl, deps, signal) };
|
|
64
|
+
}
|
|
65
|
+
export async function runReadinessSequence(config, deps, signal) {
|
|
66
|
+
const { url, managed, tags } = await resolveEndpoint(config, deps, signal);
|
|
67
|
+
const modelName = config.profile.model;
|
|
68
|
+
// Step 3: model availability.
|
|
69
|
+
let available = tagsContainModel(tags.models, modelName);
|
|
70
|
+
// Step 4: pull in managed mode if needed (spec.md §3.2 forbids touching an external endpoint).
|
|
71
|
+
if (!available) {
|
|
72
|
+
if (!managed) {
|
|
73
|
+
throw embeddingUnavailableError(`Ollama model "${modelName}" is not available at ${redactEndpointUrl(url)}, and this endpoint is not managed by Untangle — pull it manually (spec.md §3.2).`, { retryable: false, context: { endpoint: redactEndpointUrl(url), model: modelName } });
|
|
74
|
+
}
|
|
75
|
+
await requestWithRetry(() => pullModel(deps.httpClient, url, modelName, signal), deps, signal);
|
|
76
|
+
const refreshed = await requestWithRetry(() => listTags(deps.httpClient, url, signal), deps, signal);
|
|
77
|
+
available = tagsContainModel(refreshed.models, modelName);
|
|
78
|
+
if (!available) {
|
|
79
|
+
throw embeddingUnavailableError(`Ollama model "${modelName}" was still not available after pulling it from ${redactEndpointUrl(url)}.`, { retryable: false, context: { endpoint: redactEndpointUrl(url), model: modelName } });
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
// Step 5: probe embedding.
|
|
83
|
+
const probeResponse = await requestWithRetry(() => embed(deps.httpClient, url, modelName, [READINESS_PROBE_TEXT], signal), deps, signal);
|
|
84
|
+
const probeVector = probeResponse.embeddings[0];
|
|
85
|
+
if (!probeVector) {
|
|
86
|
+
throw embeddingUnavailableError(`Ollama probe embedding returned no vector for model "${modelName}".`, { retryable: false, context: { endpoint: redactEndpointUrl(url), model: modelName } });
|
|
87
|
+
}
|
|
88
|
+
// Step 6: dimension validation (must fail BEFORE the first upsert, spec.md §4.8).
|
|
89
|
+
assertVectorDimension(probeVector, config.profile.dimensions, {
|
|
90
|
+
endpoint: redactEndpointUrl(url),
|
|
91
|
+
model: modelName,
|
|
92
|
+
stage: "readiness_probe",
|
|
93
|
+
});
|
|
94
|
+
// Step 7: profile + probe timestamp are cached by the caller from this result.
|
|
95
|
+
return {
|
|
96
|
+
readiness: {
|
|
97
|
+
endpoint: redactEndpointUrl(url),
|
|
98
|
+
model: modelName,
|
|
99
|
+
dimensions: probeVector.length,
|
|
100
|
+
probedAt: deps.clock.nowIso(),
|
|
101
|
+
},
|
|
102
|
+
endpointUrl: url,
|
|
103
|
+
};
|
|
104
|
+
}
|
|
105
|
+
//# sourceMappingURL=readiness.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"readiness.js","sourceRoot":"","sources":["../src/readiness.ts"],"names":[],"mappings":"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"}
|
package/dist/sleep.d.ts
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Real, abortable {@link SleepFn} implementation. The only production
|
|
3
|
+
* implementation of the port declared in `types.ts`; tests inject a fake
|
|
4
|
+
* that resolves immediately instead of waiting for real time to pass.
|
|
5
|
+
*/
|
|
6
|
+
import type { SleepFn } from "./types.js";
|
|
7
|
+
export declare const realSleep: SleepFn;
|
|
8
|
+
//# sourceMappingURL=sleep.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sleep.d.ts","sourceRoot":"","sources":["../src/sleep.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,YAAY,CAAC;AAE1C,eAAO,MAAM,SAAS,EAAE,OAmBpB,CAAC"}
|
package/dist/sleep.js
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
export const realSleep = (ms, signal) => new Promise((resolve, reject) => {
|
|
2
|
+
if (signal?.aborted) {
|
|
3
|
+
reject(signal.reason);
|
|
4
|
+
return;
|
|
5
|
+
}
|
|
6
|
+
const timer = setTimeout(() => {
|
|
7
|
+
cleanup();
|
|
8
|
+
resolve();
|
|
9
|
+
}, ms);
|
|
10
|
+
const onAbort = () => {
|
|
11
|
+
cleanup();
|
|
12
|
+
reject(signal?.reason);
|
|
13
|
+
};
|
|
14
|
+
function cleanup() {
|
|
15
|
+
clearTimeout(timer);
|
|
16
|
+
signal?.removeEventListener("abort", onAbort);
|
|
17
|
+
}
|
|
18
|
+
signal?.addEventListener("abort", onAbort, { once: true });
|
|
19
|
+
});
|
|
20
|
+
//# sourceMappingURL=sleep.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sleep.js","sourceRoot":"","sources":["../src/sleep.ts"],"names":[],"mappings":"AAOA,MAAM,CAAC,MAAM,SAAS,GAAY,CAAC,EAAE,EAAE,MAAM,EAAE,EAAE,CAC/C,IAAI,OAAO,CAAO,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE;IACpC,IAAI,MAAM,EAAE,OAAO,EAAE,CAAC;QACpB,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC;QACtB,OAAO;IACT,CAAC;IACD,MAAM,KAAK,GAAG,UAAU,CAAC,GAAG,EAAE;QAC5B,OAAO,EAAE,CAAC;QACV,OAAO,EAAE,CAAC;IACZ,CAAC,EAAE,EAAE,CAAC,CAAC;IACP,MAAM,OAAO,GAAG,GAAS,EAAE;QACzB,OAAO,EAAE,CAAC;QACV,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACzB,CAAC,CAAC;IACF,SAAS,OAAO;QACd,YAAY,CAAC,KAAK,CAAC,CAAC;QACpB,MAAM,EAAE,mBAAmB,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC;IAChD,CAAC;IACD,MAAM,EAAE,gBAAgB,CAAC,OAAO,EAAE,OAAO,EAAE,EAAE,IAAI,EAAE,IAAI,EAAE,CAAC,CAAC;AAC7D,CAAC,CAAC,CAAC"}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Truncation guard (spec.md §9.3).
|
|
3
|
+
*
|
|
4
|
+
* Ollama's `/api/embed` truncates silently to the model's context window by
|
|
5
|
+
* default (`truncate: true`) and does not report, per input, whether
|
|
6
|
+
* truncation actually happened. The chunker (a separate package) is already
|
|
7
|
+
* responsible for keeping prepared text within budget in the normal case;
|
|
8
|
+
* this guard exists purely to catch the exceptional case where it did not
|
|
9
|
+
* and flag the resulting point honestly rather than silently accepting a
|
|
10
|
+
* vector computed from a truncated text (vision.md 8.4 "no silent data
|
|
11
|
+
* loss").
|
|
12
|
+
*
|
|
13
|
+
* Detection is a conservative, offline token estimate compared against the
|
|
14
|
+
* configured `contextLength` — the same trade-off `@untangle-code/chunker`
|
|
15
|
+
* makes for its own budget checks (over-estimating real BPE token counts is
|
|
16
|
+
* far safer than under-estimating, which would hide a real truncation).
|
|
17
|
+
* The estimator is intentionally duplicated rather than imported: chunker
|
|
18
|
+
* uses it to fit a budget ahead of time, this module uses it to flag a
|
|
19
|
+
* budget miss after the fact, and importing chunker here would add a
|
|
20
|
+
* runtime dependency between packages that otherwise have no reason to
|
|
21
|
+
* know about each other (rule 3 spirit).
|
|
22
|
+
*/
|
|
23
|
+
/** Estimates a conservative (over-)approximation of `text`'s token count. */
|
|
24
|
+
export declare function estimateTokenCount(text: string): number;
|
|
25
|
+
/**
|
|
26
|
+
* True when `text`'s estimated token count exceeds `contextLength` — the
|
|
27
|
+
* point at which Ollama's provider-side truncation actually applies.
|
|
28
|
+
*/
|
|
29
|
+
export declare function wouldProviderTruncate(text: string, contextLength: number): boolean;
|
|
30
|
+
//# sourceMappingURL=truncation.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"truncation.d.ts","sourceRoot":"","sources":["../src/truncation.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;GAqBG;AAKH,6EAA6E;AAC7E,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAKvD;AAED;;;GAGG;AACH,wBAAgB,qBAAqB,CAAC,IAAI,EAAE,MAAM,EAAE,aAAa,EAAE,MAAM,GAAG,OAAO,CAElF"}
|