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:
@@ -21,6 +21,10 @@ import * as vscode from 'vscode';
|
||||
* LM 서버(OpenAI 호환 `/v1/chat/completions`)를 확장에서 직접 호출. LM Studio/Ollama 는
|
||||
* 인증이 없으므로 토큰 불필요. 타임아웃 가드 포함, 비정상 응답이면 throw.
|
||||
*/
|
||||
// [코어 수렴 예외 — 의도적] 이 모듈은 core/services.coreChat 으로 수렴하지 않는다:
|
||||
// finish_reason 기반 이어쓰기(continuation), repeat_penalty/top_k 커스텀 샘플링,
|
||||
// degeneration 감지·재시도 등 '장문 생성 엔진' 요구가 코어 채팅 API 범위를 벗어난다.
|
||||
// 단순 단발 완성이 필요한 새 코드는 이 파일이 아니라 coreChat 을 사용할 것.
|
||||
async function lmChat(lmUrl: string, payload: unknown, timeoutMs = 120_000): Promise<any> {
|
||||
const controller = new AbortController();
|
||||
const timer = setTimeout(() => controller.abort(), timeoutMs);
|
||||
@@ -70,6 +74,9 @@ interface LmOpts {
|
||||
/** 출력이 length(토큰 상한)로 잘렸을 때 끊긴 지점부터 이어쓰기 최대 횟수(기본 3). 0이면 이어쓰기 안 함.
|
||||
* 회의록 "누락" 의 실제 원인(출력 잘림)을 보완 — 잘린 부분을 이어붙여 완결성을 확보. */
|
||||
maxContinuations?: number;
|
||||
/** 호출당 타임아웃(ms). 기본 120초 — /benchmark 4파트 합성처럼 입력 JSON 이 큰
|
||||
* 호출은 e4b 급 모델에서 120초를 넘길 수 있어 (실사례: 파트 2 abort) 크게 지정. */
|
||||
timeoutMs?: number;
|
||||
}
|
||||
|
||||
/** 단발 호출 결과 — 이어쓰기 판단을 위해 finish_reason 을 함께 반환. */
|
||||
@@ -82,6 +89,7 @@ async function callLmOnce(
|
||||
lmUrl: string, model: string, sys: string, prompt: string,
|
||||
sampling: { temperature: number; repeat_penalty: number; top_k: number },
|
||||
maxTokens?: number,
|
||||
timeoutMs = 120_000,
|
||||
): Promise<LmResult> {
|
||||
const payload: Record<string, unknown> = {
|
||||
model,
|
||||
@@ -96,7 +104,7 @@ async function callLmOnce(
|
||||
};
|
||||
// max_tokens 는 지정됐을 때만 넣는다 — 미지정 caller(벤치마크 등)의 기존 동작(서버 기본값)을 바꾸지 않기 위해.
|
||||
if (maxTokens && maxTokens > 0) payload.max_tokens = maxTokens;
|
||||
const res = await lmChat(lmUrl, payload, 120_000);
|
||||
const res = await lmChat(lmUrl, payload, timeoutMs);
|
||||
const choice = res?.choices?.[0];
|
||||
const content = choice?.message?.content ?? choice?.text ?? res?.answer ?? res?.response ?? '';
|
||||
const finish = String(choice?.finish_reason ?? choice?.native_finish_reason ?? '').toLowerCase();
|
||||
@@ -146,7 +154,7 @@ export async function callLmSynthesis(prompt: string, systemPrompt?: string, opt
|
||||
top_k: Math.max(10, 20 - attempt * 5), // 20 → 15 → 10
|
||||
};
|
||||
try {
|
||||
const first = await callLmOnce(lmUrl, model, sys, prompt, sampling, opts?.maxTokens);
|
||||
const first = await callLmOnce(lmUrl, model, sys, prompt, sampling, opts?.maxTokens, opts?.timeoutMs);
|
||||
let out = first.text;
|
||||
if (!out) { lastErr = new Error('LLM 응답이 비어 있습니다.'); continue; }
|
||||
if (looksDegenerate(out)) {
|
||||
@@ -159,7 +167,7 @@ export async function callLmSynthesis(prompt: string, systemPrompt?: string, opt
|
||||
for (let c = 0; c < maxCont && finish === 'length'; c++) {
|
||||
let next: LmResult;
|
||||
try {
|
||||
next = await callLmOnce(lmUrl, model, sys, buildContinuePrompt(out.slice(-1600)), sampling, opts?.maxTokens);
|
||||
next = await callLmOnce(lmUrl, model, sys, buildContinuePrompt(out.slice(-1600)), sampling, opts?.maxTokens, opts?.timeoutMs);
|
||||
} catch { break; } // 이어쓰기 호출 실패 시 지금까지분으로 마감
|
||||
if (!next.text || looksDegenerate(next.text)) break; // 이어쓰기가 붕괴하면 중단
|
||||
out = `${out}\n${next.text}`.trim();
|
||||
|
||||
Reference in New Issue
Block a user