v2.2.299~307: 아키텍처 수렴 + 채팅 정리 + 벤치마킹 강화 + Claude 구독 엔진
- v2.2.299 아키텍처 수렴: coreChat 통일(엔진 휴리스틱 3벌 제거), 기업 모드 검색 오케스트레이터 승격, lib/execUtil(실행 래퍼 6곳·Python 탐지 3벌 단일화), lib/kstSchedule(워처 4개 nowInKst 통합), estimateTokens 통합, 설정 접근 규칙 명문화 - v2.2.300 채팅 화면 정리: LiveReasoningFilter(스트리밍 중 <think>/Harmony 추론 토큰 단위 차단), 확신도·검토요청 footer 기본 숨김(계산·Reflection 은 유지) - v2.2.301 문맥·의도 이해: [답변 전 이해 원칙] 상시 주입, 워크플로우 의도 브리핑, Report QA 루프(규칙 레지스트리+실측치+회귀 게이트, 블로그_v3 개념 이식) - v2.2.302 /benchmark 비즈니스 렌즈(가격·수익·운영)+빌드 프롬프트 모드+QA 연계 - v2.2.303 handoff 모드(측정치 무손실 인수인계 문서)+/claude(Claude Code 터미널 위임) - v2.2.304 이식성: 지식 경로 두뇌-상대 규약(pickWikiDir 상대 해석), 이사 체크리스트 - v2.2.305 Claude 구독 엔진: claude: 프로바이더(CLI 위임, 모델 드롭다운 자동 노출, coreChat 지원 — 워크플로우·QA도 구독 모델 가능) - v2.2.306 Tone Guard: AI 상투어 금지 레지스트리(상담사 화법 실사례 8종+대조 예시) - v2.2.307 /benchmark 레이아웃 골격(sectionRoles 결정론 분석, 롤링 배너 즉답), 파트별 실패 격리, 합성 타임아웃 120→300초 검증: tsc 무오류 + jest 888 통과 + esbuild 정상 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
+14
-71
@@ -1,82 +1,37 @@
|
||||
import * as vscode from 'vscode';
|
||||
import { getConfig } from '../config';
|
||||
import { coreChat } from '../core/services';
|
||||
import { AgentExecuteOptions } from '../lib/engine';
|
||||
|
||||
export abstract class BaseAgent {
|
||||
constructor(protected readonly modelName: string) {}
|
||||
|
||||
protected async callLLM(persona: string, prompt: string, signal?: AbortSignal): Promise<string> {
|
||||
const { ollamaUrl } = getConfig();
|
||||
if (!ollamaUrl) {
|
||||
throw new Error('Ollama URL이 설정되지 않았습니다. 설정을 확인해주세요.');
|
||||
}
|
||||
|
||||
if (typeof fetch === 'undefined') {
|
||||
throw new Error('이 환경에서는 fetch 함수를 사용할 수 없습니다. Node.js 버전을 확인하거나 polyfill이 필요합니다.');
|
||||
}
|
||||
|
||||
const messages = [
|
||||
{ role: 'system', content: persona },
|
||||
{ role: 'user', content: prompt }
|
||||
];
|
||||
|
||||
// 엔진 자동 감지 (Ollama vs OpenAI/LM Studio)
|
||||
const isOllama = ollamaUrl.includes(':11434') || ollamaUrl.includes('ollama');
|
||||
const endpoint = isOllama ? `${ollamaUrl}/api/chat` : `${ollamaUrl}/v1/chat/completions`;
|
||||
|
||||
// 컨텍스트 초과 방지를 위해 출력 토큰 상한을 항상 명시한다 (서브에이전트 중간 산출물용).
|
||||
// [코어 수렴] 자체 fetch·엔진 휴리스틱을 제거하고 core/services 의 coreChat 으로 통일.
|
||||
// 이 레이어는 "시도당 45초 + 3회 재시도(지수 백오프) + Abort 즉시 중단" 정책만 담당하고,
|
||||
// 엔진 선택·폴백·빈응답 처리·로깅은 코어가 책임진다.
|
||||
const { contextLength, maxOutputTokens } = getConfig();
|
||||
const numCtx = Math.max(2048, contextLength);
|
||||
const outCap = Math.max(256, maxOutputTokens);
|
||||
|
||||
let lastError: any;
|
||||
for (let attempt = 1; attempt <= 3; attempt++) {
|
||||
const controller = new AbortController();
|
||||
const timeoutId = setTimeout(() => controller.abort(), 45000);
|
||||
const combinedSignal = signal ? anySignal([signal, controller.signal]) : controller.signal;
|
||||
|
||||
try {
|
||||
if (attempt > 1) await new Promise(resolve => setTimeout(resolve, 1000 * attempt));
|
||||
|
||||
const response = await fetch(endpoint, {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify(isOllama ? {
|
||||
const result = await coreChat({
|
||||
system: persona,
|
||||
user: prompt,
|
||||
model: this.modelName,
|
||||
messages,
|
||||
stream: false,
|
||||
options: { temperature: 0.3, num_ctx: numCtx, num_predict: outCap }
|
||||
} : {
|
||||
model: this.modelName,
|
||||
messages,
|
||||
stream: false,
|
||||
timeoutMs: 45000,
|
||||
signal,
|
||||
temperature: 0.3,
|
||||
max_tokens: outCap
|
||||
}),
|
||||
signal: combinedSignal
|
||||
});
|
||||
|
||||
clearTimeout(timeoutId);
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(`Agent API Error: ${response.statusText} (${response.status})`);
|
||||
}
|
||||
|
||||
const data = await response.json() as any;
|
||||
|
||||
// 강력한 응답 추출 (Multi-path parsing)
|
||||
let content = '';
|
||||
if (data.message?.content) content = data.message.content;
|
||||
else if (data.choices?.[0]?.message?.content) content = data.choices[0].message.content;
|
||||
else if (data.choices?.[0]?.text) content = data.choices[0].text;
|
||||
else if (data.response) content = data.response;
|
||||
else if (typeof data === 'string') content = data;
|
||||
|
||||
return content || '';
|
||||
maxTokens: outCap,
|
||||
numCtx,
|
||||
});
|
||||
return result.content || '';
|
||||
} catch (error: any) {
|
||||
clearTimeout(timeoutId);
|
||||
lastError = error;
|
||||
if (error.name === 'AbortError') break;
|
||||
if (error?.name === 'AbortError') break;
|
||||
if (attempt === 3) break;
|
||||
}
|
||||
}
|
||||
@@ -86,18 +41,6 @@ export abstract class BaseAgent {
|
||||
abstract execute(input: string, context?: string, signal?: AbortSignal, options?: AgentExecuteOptions): 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;
|
||||
}
|
||||
|
||||
/**
|
||||
* Section outline shape produced by ChunkedWriter in the 'outline' role.
|
||||
|
||||
Reference in New Issue
Block a user