fix: v2.13.0 - stability fixes (model persistence & agent abort logic)

This commit is contained in:
Wonseok Jung
2026-04-30 00:31:27 +09:00
parent 326672cb93
commit 7430c91177
3 changed files with 69 additions and 27 deletions
+54 -23
View File
@@ -4,52 +4,83 @@ import { getConfig } from '../config';
export abstract class BaseAgent {
constructor(protected readonly modelName: string) {}
protected async callLLM(persona: string, prompt: string): Promise<string> {
protected async callLLM(persona: string, prompt: string, signal?: AbortSignal): Promise<string> {
const { ollamaUrl } = getConfig();
const messages = [
{ role: 'system', content: persona },
{ role: 'user', content: prompt }
];
// API 호출 로직 (Streaming 생략하고 결과만 반환하는 헬퍼)
const response = await fetch(`${ollamaUrl}/api/chat`, {
method: 'POST',
body: JSON.stringify({
model: this.modelName,
messages,
stream: false,
options: { temperature: 0.3 }
})
});
const controller = new AbortController();
const timeoutId = setTimeout(() => controller.abort(), 45000); // Increased to 45s for complex tasks
if (!response.ok) {
throw new Error(`Agent API Error: ${response.statusText}`);
// Combine external signal with local timeout
const combinedSignal = signal ?
anySignal([signal, controller.signal]) :
controller.signal;
try {
const response = await fetch(`${ollamaUrl}/api/chat`, {
method: 'POST',
body: JSON.stringify({
model: this.modelName,
messages,
stream: false,
options: { temperature: 0.3 }
}),
signal: combinedSignal
});
clearTimeout(timeoutId);
if (!response.ok) {
throw new Error(`Agent API Error: ${response.statusText} (${response.status})`);
}
const data = await response.json() as any;
return data.message?.content || data.choices?.[0]?.message?.content || '';
} catch (error: any) {
clearTimeout(timeoutId);
if (error.name === 'AbortError') {
throw new Error('Agent request was cancelled or timed out.');
}
throw error;
}
const data = await response.json() as any;
return data.message?.content || data.choices?.[0]?.message?.content || '';
}
abstract execute(input: string, context?: string): Promise<string>;
abstract execute(input: string, context?: string, signal?: AbortSignal): Promise<string>;
}
// Helper to combine signals (since AbortSignal.any is not always available in older Node)
function anySignal(signals: AbortSignal[]): AbortSignal {
const controller = new AbortController();
for (const signal of signals) {
if (signal.aborted) {
controller.abort();
return signal;
}
signal.addEventListener('abort', () => controller.abort(), { once: true });
}
return controller.signal;
}
export class PlannerAgent extends BaseAgent {
private readonly persona = `You are the [Planner Agent]. Analyze the request and output a structured <plan>.`;
async execute(input: string, brainContext?: string): Promise<string> {
return this.callLLM(this.persona, `Request: ${input}\nContext: ${brainContext}`);
async execute(input: string, brainContext?: string, signal?: AbortSignal): Promise<string> {
return this.callLLM(this.persona, `Request: ${input}\nContext: ${brainContext}`, signal);
}
}
export class ResearcherAgent extends BaseAgent {
private readonly persona = `You are the [Researcher Agent]. Gather facts based on the plan.`;
async execute(input: string, brainContext?: string): Promise<string> {
return this.callLLM(this.persona, `Plan: ${input}\nContext: ${brainContext}`);
async execute(input: string, brainContext?: string, signal?: AbortSignal): Promise<string> {
return this.callLLM(this.persona, `Plan: ${input}\nContext: ${brainContext}`, signal);
}
}
export class WriterAgent extends BaseAgent {
private readonly persona = `You are the [Writer Agent]. Synthesize research into a final report.`;
async execute(input: string, originalRequest?: string): Promise<string> {
return this.callLLM(this.persona, `Data: ${input}\nOriginal Request: ${originalRequest}`);
async execute(input: string, originalRequest?: string, signal?: AbortSignal): Promise<string> {
return this.callLLM(this.persona, `Data: ${input}\nOriginal Request: ${originalRequest}`, signal);
}
}