feat(ollama): LocalOllamaProvider — abort() + AbortController instance field (v0.2.3.1)
- abortController 가 method-local 에서 private instance field 로 이동 - public abort() 메서드 — 외부에서 in-flight generate 강제 중단 - ProviderHolder.replace() 시 호출되어 endpoint 변경 즉시 반영 - 단위 +2 cases (abort cancellation, model 파라미터) Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -18,6 +18,7 @@ export class LocalOllamaProvider implements InferenceProvider {
|
||||
private timeoutMs: number;
|
||||
private temperature: number;
|
||||
private numPredict: number;
|
||||
private abortController: AbortController | null = null;
|
||||
|
||||
constructor(opts: LocalOllamaOptions = {}) {
|
||||
this.endpoint = opts.endpoint ?? 'http://localhost:11434';
|
||||
@@ -29,8 +30,8 @@ export class LocalOllamaProvider implements InferenceProvider {
|
||||
}
|
||||
|
||||
async generate(input: GenerateInput): Promise<AiResponse> {
|
||||
const controller = new AbortController();
|
||||
const timer = setTimeout(() => controller.abort(), this.timeoutMs);
|
||||
this.abortController = new AbortController();
|
||||
const timer = setTimeout(() => this.abortController?.abort(), this.timeoutMs);
|
||||
try {
|
||||
const res = await request(`${this.endpoint}/api/generate`, {
|
||||
method: 'POST',
|
||||
@@ -42,7 +43,7 @@ export class LocalOllamaProvider implements InferenceProvider {
|
||||
stream: false,
|
||||
options: { temperature: this.temperature, num_predict: this.numPredict }
|
||||
}),
|
||||
signal: controller.signal
|
||||
signal: this.abortController.signal
|
||||
});
|
||||
if (res.statusCode < 200 || res.statusCode >= 300) {
|
||||
throw new Error(`ollama http ${res.statusCode}`);
|
||||
@@ -55,9 +56,15 @@ export class LocalOllamaProvider implements InferenceProvider {
|
||||
return parseAiResponse(parsed);
|
||||
} finally {
|
||||
clearTimeout(timer);
|
||||
this.abortController = null;
|
||||
}
|
||||
}
|
||||
|
||||
/** v0.2.3.1 — 외부에서 in-flight generate 강제 중단. ProviderHolder.replace 시 사용. */
|
||||
abort(): void {
|
||||
this.abortController?.abort();
|
||||
}
|
||||
|
||||
async healthCheck(): Promise<HealthResult> {
|
||||
try {
|
||||
const res = await request(`${this.endpoint}/api/tags`, { method: 'GET' });
|
||||
|
||||
@@ -89,4 +89,24 @@ describe('LocalOllamaProvider', () => {
|
||||
expect(h.ok).toBe(false);
|
||||
expect(h.reason).toMatch(/connect|refused|unreachable/i);
|
||||
});
|
||||
|
||||
it('abort() cancels in-flight generate (rejects with AbortError)', async () => {
|
||||
mock.get('http://localhost:11434').intercept({
|
||||
path: '/api/generate', method: 'POST'
|
||||
}).reply((async () => {
|
||||
await new Promise<void>((r) => setTimeout(r, 5000)); // long-running
|
||||
return { statusCode: 200, data: '{}' };
|
||||
}) as never);
|
||||
const provider = new LocalOllamaProvider({ timeoutMs: 30_000 });
|
||||
const generatePromise = provider.generate({
|
||||
text: 'x', todayKst: '2026-05-04', dueDateCandidates: []
|
||||
});
|
||||
setTimeout(() => provider.abort(), 50);
|
||||
await expect(generatePromise).rejects.toThrow();
|
||||
});
|
||||
|
||||
it('constructor uses provided model param (not just default)', () => {
|
||||
const provider = new LocalOllamaProvider({ model: 'gemma4:26b' });
|
||||
expect(provider.name).toBe('local-ollama/gemma4:26b');
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user