

























@@ -111,6 +111,34 @@ export function resolveMemoryIndexConcurrency(params: {
111111return params.providerId === "ollama" ? 1 : EMBEDDING_INDEX_CONCURRENCY;
112112}
113113114+export async function runEmbeddingOperationWithTimeout<T>(params: {
115+timeoutMs: number;
116+message: string;
117+run: (signal: AbortSignal) => Promise<T>;
118+}): Promise<T> {
119+const controller = new AbortController();
120+if (!Number.isFinite(params.timeoutMs) || params.timeoutMs <= 0) {
121+return await params.run(controller.signal);
122+}
123+let timer: NodeJS.Timeout | null = null;
124+const timeoutPromise = new Promise<never>((_, reject) => {
125+timer = setTimeout(() => {
126+const error = new Error(params.message);
127+reject(error);
128+controller.abort(error);
129+}, params.timeoutMs);
130+timer.unref?.();
131+});
132+try {
133+const operation = params.run(controller.signal);
134+return (await Promise.race([operation, timeoutPromise])) as T;
135+} finally {
136+if (timer) {
137+clearTimeout(timer);
138+}
139+}
140+}
141+114142export abstract class MemoryManagerEmbeddingOps extends MemoryManagerSyncOps {
115143protected abstract batchFailureCount: number;
116144protected abstract batchFailureLastError?: string;
@@ -304,11 +332,11 @@ export abstract class MemoryManagerEmbeddingOps extends MemoryManagerSyncOps {
304332items: texts.length,
305333 timeoutMs,
306334});
307-return await this.withTimeout(
308-provider.embedBatch(texts),
335+return await runEmbeddingOperationWithTimeout({
309336 timeoutMs,
310-`memory embeddings batch timed out after ${Math.round(timeoutMs / 1000)}s`,
311-);
337+message: `memory embeddings batch timed out after ${Math.round(timeoutMs / 1000)}s`,
338+run: async (signal) => await provider.embedBatch(texts, { signal }),
339+});
312340},
313341isRetryable: isRetryableMemoryEmbeddingError,
314342waitForRetry: async (delayMs) => {
@@ -336,11 +364,11 @@ export abstract class MemoryManagerEmbeddingOps extends MemoryManagerSyncOps {
336364items: inputs.length,
337365 timeoutMs,
338366});
339-return await this.withTimeout(
340-embedBatchInputs(inputs),
367+return await runEmbeddingOperationWithTimeout({
341368 timeoutMs,
342-`memory embeddings batch timed out after ${Math.round(timeoutMs / 1000)}s`,
343-);
369+message: `memory embeddings batch timed out after ${Math.round(timeoutMs / 1000)}s`,
370+run: async (signal) => await embedBatchInputs(inputs, { signal }),
371+});
344372},
345373isRetryable: isRetryableMemoryEmbeddingError,
346374waitForRetry: async (delayMs) => {
@@ -371,16 +399,17 @@ export abstract class MemoryManagerEmbeddingOps extends MemoryManagerSyncOps {
371399}
372400373401protected async embedQueryWithTimeout(text: string): Promise<number[]> {
374-if (!this.provider) {
402+const provider = this.provider;
403+if (!provider) {
375404throw new Error("Cannot embed query in FTS-only mode (no embedding provider)");
376405}
377406const timeoutMs = this.resolveEmbeddingTimeout("query");
378-log.debug("memory embeddings: query start", { provider: this.provider.id, timeoutMs });
379-return await this.withTimeout(
380-this.provider.embedQuery(text),
407+log.debug("memory embeddings: query start", { provider: provider.id, timeoutMs });
408+return await runEmbeddingOperationWithTimeout({
381409 timeoutMs,
382-`memory embeddings query timed out after ${Math.round(timeoutMs / 1000)}s`,
383-);
410+message: `memory embeddings query timed out after ${Math.round(timeoutMs / 1000)}s`,
411+run: async (signal) => await provider.embedQuery(text, { signal }),
412+});
384413}
385414386415protected async withTimeout<T>(
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。