Skip to content

Commit 3b6ded2

Browse files
feat: configurable embed/rerank client timeouts via env vars (#29)
Add EMBED_TIMEOUT_MS and RERANK_TIMEOUT_MS so operators can raise the fixed 10s HTTP timeout per deployment. Ollama embed calls have been observed taking 6-9s in production, leaving little margin against the hardcoded default.
1 parent 0001fbf commit 3b6ded2

5 files changed

Lines changed: 75 additions & 0 deletions

File tree

‎src/config.ts‎

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,9 @@ export type EngineConfig = {
2626
model: string;
2727
apiStyle: string;
2828
apiKey: string | undefined;
29+
// Per-request timeout override (EMBED_TIMEOUT_MS). `undefined` means
30+
// "use embed-client.ts's own default" — must NOT be defaulted here.
31+
timeoutMs: number | undefined;
2932
};
3033
rerank: {
3134
baseUrl: string | undefined;
@@ -40,5 +43,8 @@ export type EngineConfig = {
4043
// single constant is exactly how a small-model budget got applied to
4144
// the default model's much larger context window.
4245
maxDocChars: number | undefined;
46+
// Per-request timeout override (RERANK_TIMEOUT_MS). `undefined` means
47+
// "use rerank-client.ts's own default" — must NOT be defaulted here.
48+
timeoutMs: number | undefined;
4349
};
4450
};

‎src/memory.test.ts‎

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -113,12 +113,14 @@ const wiringConfig: MemoryConfig = {
113113
model: "m",
114114
apiStyle: "openai",
115115
apiKey: undefined,
116+
timeoutMs: undefined,
116117
},
117118
rerank: {
118119
baseUrl: undefined,
119120
model: undefined,
120121
apiKey: undefined,
121122
maxDocChars: undefined,
123+
timeoutMs: undefined,
122124
},
123125
},
124126
};
@@ -145,6 +147,7 @@ function baseConfig(
145147
model: "m",
146148
apiStyle: "openai",
147149
apiKey: undefined,
150+
timeoutMs: undefined,
148151
},
149152
rerank,
150153
},
@@ -163,6 +166,7 @@ describe("createMemory — construction validation", () => {
163166
model: "bge-reranker-base",
164167
apiKey: undefined,
165168
maxDocChars: 5_000,
169+
timeoutMs: undefined,
166170
}),
167171
}),
168172
).toThrow(RerankConfigError);

‎src/mount-config.test.ts‎

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
import { afterEach, beforeEach, describe, expect, it } from "bun:test";
2+
import { loadMemoryConfig } from "./mount-config.ts";
3+
4+
const REQUIRED_ENV = {
5+
KNOWLEDGE_DATABASE_URL: "postgres://localhost:5432/test",
6+
EMBED_BASE_URL: "http://embed.example",
7+
EMBED_MODEL: "test-model",
8+
};
9+
10+
const ENV_KEYS = [
11+
...Object.keys(REQUIRED_ENV),
12+
"EMBED_TIMEOUT_MS",
13+
"RERANK_TIMEOUT_MS",
14+
];
15+
16+
let savedEnv: Record<string, string | undefined>;
17+
18+
beforeEach(() => {
19+
savedEnv = {};
20+
for (const key of ENV_KEYS) {
21+
savedEnv[key] = process.env[key];
22+
delete process.env[key];
23+
}
24+
for (const [key, value] of Object.entries(REQUIRED_ENV)) {
25+
process.env[key] = value;
26+
}
27+
});
28+
29+
afterEach(() => {
30+
for (const key of ENV_KEYS) {
31+
if (savedEnv[key] === undefined) delete process.env[key];
32+
else process.env[key] = savedEnv[key];
33+
}
34+
});
35+
36+
describe("loadMemoryConfig — EMBED_TIMEOUT_MS / RERANK_TIMEOUT_MS", () => {
37+
it("leaves embed.timeoutMs and rerank.timeoutMs undefined when unset, so the clients' own defaults apply", () => {
38+
const config = loadMemoryConfig();
39+
expect(config.memory.embed.timeoutMs).toBeUndefined();
40+
expect(config.memory.rerank.timeoutMs).toBeUndefined();
41+
});
42+
43+
it("flows EMBED_TIMEOUT_MS through to embed.timeoutMs", () => {
44+
process.env.EMBED_TIMEOUT_MS = "20000";
45+
const config = loadMemoryConfig();
46+
expect(config.memory.embed.timeoutMs).toBe(20_000);
47+
});
48+
49+
it("flows RERANK_TIMEOUT_MS through to rerank.timeoutMs", () => {
50+
process.env.RERANK_TIMEOUT_MS = "15000";
51+
const config = loadMemoryConfig();
52+
expect(config.memory.rerank.timeoutMs).toBe(15_000);
53+
});
54+
55+
it("rejects a non-positive-integer EMBED_TIMEOUT_MS", () => {
56+
process.env.EMBED_TIMEOUT_MS = "not-a-number";
57+
expect(() => loadMemoryConfig()).toThrow("EMBED_TIMEOUT_MS must be a positive integer");
58+
});
59+
});

‎src/mount-config.ts‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,12 +64,14 @@ export function loadMemoryConfig(): MemoryConfig {
6464
model: requireEnv("EMBED_MODEL"),
6565
apiStyle: optionalEnv("EMBED_API_STYLE") ?? "openai",
6666
apiKey: optionalEnv("EMBED_API_KEY"),
67+
timeoutMs: optionalIntEnv("EMBED_TIMEOUT_MS"),
6768
},
6869
rerank: {
6970
baseUrl: optionalEnv("RERANK_BASE_URL"),
7071
model: optionalEnv("RERANK_MODEL"),
7172
apiKey: optionalEnv("RERANK_API_KEY"),
7273
maxDocChars: optionalIntEnv("RERANK_MAX_DOC_CHARS"),
74+
timeoutMs: optionalIntEnv("RERANK_TIMEOUT_MS"),
7375
},
7476
},
7577
};

‎src/services/search.ts‎

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -672,6 +672,7 @@ function toEmbedClientConfig(embed: EngineConfig["embed"]): EmbedClientConfig {
672672
modelId: embed.model,
673673
apiStyle: embed.apiStyle as EmbedClientConfig["apiStyle"],
674674
...(embed.apiKey !== undefined ? { apiKey: embed.apiKey } : {}),
675+
...(embed.timeoutMs !== undefined ? { timeoutMs: embed.timeoutMs } : {}),
675676
};
676677
}
677678

@@ -694,6 +695,9 @@ export function toRerankClientConfig(
694695
...(rerank.maxDocChars !== undefined
695696
? { maxDocChars: rerank.maxDocChars }
696697
: {}),
698+
...(rerank.timeoutMs !== undefined
699+
? { timeoutMs: rerank.timeoutMs }
700+
: {}),
697701
};
698702
}
699703

0 commit comments

Comments
 (0)