mirror of
https://github.com/abhigyanpatwari/GitNexus.git
synced 2026-08-28 05:25:25 +00:00
Merge pull request #2719 from voidfreud/fix/configurable-embedding-timeout
fix: allow slower remote embedding responses
This commit is contained in:
commit
ee9987fdc5
4 changed files with 37 additions and 3 deletions
|
|
@ -10,6 +10,7 @@
|
|||
# GITNEXUS_EMBEDDING_MAX_ATTEMPTS=3
|
||||
# GITNEXUS_EMBEDDING_RETRY_CAP_MS=5000
|
||||
# GITNEXUS_EMBEDDING_MIN_INTERVAL_MS=0
|
||||
# GITNEXUS_EMBEDDING_HTTP_TIMEOUT_MS=180000
|
||||
|
||||
# Works with Infinity, vLLM, TEI, llama.cpp, Ollama, LM Studio, or OpenAI.
|
||||
# See README for details.
|
||||
|
|
|
|||
|
|
@ -296,6 +296,7 @@ export GITNEXUS_EMBEDDING_API_KEY=your-key # optional, default: "unused"
|
|||
export GITNEXUS_EMBEDDING_MAX_ATTEMPTS=3 # optional, total attempts (1-20)
|
||||
export GITNEXUS_EMBEDDING_RETRY_CAP_MS=5000 # optional, maximum retry delay
|
||||
export GITNEXUS_EMBEDDING_MIN_INTERVAL_MS=0 # optional, minimum request spacing
|
||||
export GITNEXUS_EMBEDDING_HTTP_TIMEOUT_MS=180000 # optional, per-request timeout (max 300000)
|
||||
gitnexus analyze . --embeddings
|
||||
```
|
||||
|
||||
|
|
|
|||
|
|
@ -13,7 +13,8 @@
|
|||
|
||||
import { CircuitOpenError, ResilientFetchExhaustedError, resilientFetch } from 'gitnexus-shared';
|
||||
|
||||
const HTTP_TIMEOUT_MS = 30_000;
|
||||
const DEFAULT_HTTP_TIMEOUT_MS = 180_000;
|
||||
const MAX_HTTP_TIMEOUT_MS = 300_000;
|
||||
const HTTP_MAX_RETRIES = 2;
|
||||
const HTTP_RETRY_BACKOFF_MS = 1_000;
|
||||
const HTTP_RETRY_CAP_MS = 5_000;
|
||||
|
|
@ -21,6 +22,8 @@ const HTTP_BATCH_SIZE = 64;
|
|||
const DEFAULT_DIMS = 384;
|
||||
const HTTP_BREAKER_KEY = 'embeddings-http';
|
||||
|
||||
const HTTP_TIMEOUT_ENV = 'GITNEXUS_EMBEDDING_HTTP_TIMEOUT_MS';
|
||||
|
||||
interface HttpConfig {
|
||||
baseUrl: string;
|
||||
model: string;
|
||||
|
|
@ -29,6 +32,7 @@ interface HttpConfig {
|
|||
maxAttempts: number;
|
||||
retryCapMs: number;
|
||||
minIntervalMs: number;
|
||||
timeoutMs: number;
|
||||
requestDimensions?: number;
|
||||
}
|
||||
|
||||
|
|
@ -187,6 +191,11 @@ const readConfig = (): HttpConfig | null => {
|
|||
300_000,
|
||||
),
|
||||
minIntervalMs: parseNonNegativeIntegerEnv('GITNEXUS_EMBEDDING_MIN_INTERVAL_MS', 0, 300_000),
|
||||
timeoutMs: parsePositiveIntegerEnv(
|
||||
HTTP_TIMEOUT_ENV,
|
||||
DEFAULT_HTTP_TIMEOUT_MS,
|
||||
MAX_HTTP_TIMEOUT_MS,
|
||||
),
|
||||
requestDimensions,
|
||||
};
|
||||
};
|
||||
|
|
@ -209,6 +218,11 @@ export const isHttpMode = (): boolean =>
|
|||
*/
|
||||
export const getHttpDimensions = (): number | undefined => readConfig()?.dimensions;
|
||||
|
||||
/**
|
||||
* Return the configured per-request HTTP timeout for HTTP mode, or undefined
|
||||
* when HTTP mode is not active.
|
||||
*/
|
||||
export const getHttpTimeoutMs = (): number | undefined => readConfig()?.timeoutMs;
|
||||
/**
|
||||
* Return a safe representation of a URL for logs and error messages.
|
||||
* Strips query string (may contain tokens) and userinfo (may contain
|
||||
|
|
@ -323,6 +337,7 @@ const httpEmbedBatch = async (
|
|||
maxAttempts = HTTP_MAX_RETRIES + 1,
|
||||
retryCapMs = HTTP_RETRY_CAP_MS,
|
||||
minIntervalMs = 0,
|
||||
timeoutMs = DEFAULT_HTTP_TIMEOUT_MS,
|
||||
): Promise<EmbeddingItem[]> => {
|
||||
const requestBody: { input: string[]; model: string; dimensions?: number } = {
|
||||
input: batch,
|
||||
|
|
@ -349,7 +364,7 @@ const httpEmbedBatch = async (
|
|||
fetchImpl: async (input, init) => {
|
||||
await paceHttpRequest(minIntervalMs, requestOptions.signal);
|
||||
throwIfAborted(requestOptions.signal);
|
||||
const timeoutSignal = AbortSignal.timeout(HTTP_TIMEOUT_MS);
|
||||
const timeoutSignal = AbortSignal.timeout(timeoutMs);
|
||||
const signal = requestOptions.signal
|
||||
? AbortSignal.any([requestOptions.signal, timeoutSignal])
|
||||
: timeoutSignal;
|
||||
|
|
@ -383,7 +398,7 @@ const httpEmbedBatch = async (
|
|||
}
|
||||
if (err instanceof DOMException && err.name === 'TimeoutError') {
|
||||
throw new HttpEmbeddingError(
|
||||
`Embedding request timed out after ${HTTP_TIMEOUT_MS}ms (${safeUrl(url)}, batch ${batchIndex})`,
|
||||
`Embedding request timed out after ${timeoutMs}ms (${safeUrl(url)}, batch ${batchIndex})`,
|
||||
{ cause: err },
|
||||
);
|
||||
}
|
||||
|
|
@ -464,6 +479,7 @@ export const httpEmbed = async (
|
|||
config.maxAttempts,
|
||||
config.retryCapMs,
|
||||
config.minIntervalMs,
|
||||
config.timeoutMs,
|
||||
);
|
||||
|
||||
if (items.length !== batch.length) {
|
||||
|
|
@ -521,6 +537,7 @@ export const httpEmbedQuery = async (
|
|||
config.maxAttempts,
|
||||
config.retryCapMs,
|
||||
config.minIntervalMs,
|
||||
config.timeoutMs,
|
||||
);
|
||||
if (!items.length) {
|
||||
throw new HttpEmbeddingError(`Embedding endpoint returned empty response (${safeUrl(url)})`);
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ const ENV_KEYS = [
|
|||
'GITNEXUS_EMBEDDING_MODEL',
|
||||
'GITNEXUS_EMBEDDING_API_KEY',
|
||||
'GITNEXUS_EMBEDDING_DIMS',
|
||||
'GITNEXUS_EMBEDDING_HTTP_TIMEOUT_MS',
|
||||
'GITNEXUS_EMBEDDING_MAX_ATTEMPTS',
|
||||
'GITNEXUS_EMBEDDING_RETRY_CAP_MS',
|
||||
'GITNEXUS_EMBEDDING_MIN_INTERVAL_MS',
|
||||
|
|
@ -682,6 +683,19 @@ describe('HTTP embedding backend', () => {
|
|||
});
|
||||
|
||||
describe('timeout and network error handling', () => {
|
||||
it('uses a 180-second default timeout and accepts a bounded override', async () => {
|
||||
process.env.GITNEXUS_EMBEDDING_URL = 'http://test:8080/v1';
|
||||
process.env.GITNEXUS_EMBEDDING_MODEL = 'test-model';
|
||||
|
||||
const { getHttpTimeoutMs } = await import('../../src/core/embeddings/http-client.js');
|
||||
expect(getHttpTimeoutMs()).toBe(180_000);
|
||||
|
||||
process.env.GITNEXUS_EMBEDDING_HTTP_TIMEOUT_MS = '120000';
|
||||
expect(getHttpTimeoutMs()).toBe(120_000);
|
||||
process.env.GITNEXUS_EMBEDDING_HTTP_TIMEOUT_MS = '300001';
|
||||
expect(() => getHttpTimeoutMs()).toThrow('GITNEXUS_EMBEDDING_HTTP_TIMEOUT_MS');
|
||||
});
|
||||
|
||||
it('does not retry on timeout', async () => {
|
||||
process.env.GITNEXUS_EMBEDDING_URL = 'http://test:8080/v1';
|
||||
process.env.GITNEXUS_EMBEDDING_MODEL = 'test-model';
|
||||
|
|
@ -814,6 +828,7 @@ describe('HTTP embedding backend', () => {
|
|||
});
|
||||
|
||||
it.each([
|
||||
['GITNEXUS_EMBEDDING_HTTP_TIMEOUT_MS', '0'],
|
||||
['GITNEXUS_EMBEDDING_MAX_ATTEMPTS', '0'],
|
||||
['GITNEXUS_EMBEDDING_RETRY_CAP_MS', '-1'],
|
||||
['GITNEXUS_EMBEDDING_MIN_INTERVAL_MS', 'nope'],
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue