feat: v2.2.74 → v2.2.82 — chunked writer + 코드 리뷰 패치 + /youtube 확장
주요 변경: [chunked writer 아키텍처 (v2.2.74~v2.2.75)] - 5-stage 다중 에이전트(planner/researcher/reflector/writer/synthesizer) 파이프라인 제거 → 단일 ChunkedWriter 의 outline → section[N] → polish 3-step 으로 교체. 본문 분석에서 추상화 손실 / 토큰 폭증 문제 해소 - 답변 길이 자동 분기: 짧은 prompt 는 fast-path direct 1회 호출, 본문 분석은 chunked. outline 빈 배열도 direct 폴백 [코드 리뷰 9개 항목 일괄 패치 (v2.2.76)] - /research polling hang 방어 (heartbeat + status 정규화 + 연속 실패 abort) - 회사 모드 dispatcher abort 신호를 AIService.chat 까지 전달 - bridgeFetch 에 onHeartbeat 콜백 도입 (slow endpoint 사용자 친화적) - dead code 정리: reflectionPersister.ts 제거 + enableReflection 등 좀비 config 키 - parseOutline 의 empty vs fallback reason 명시적 분리 - chatHandlers 의 회사 모드 케이스 ~325줄을 src/sidebar/companyHandlers.ts 로 분리 - Intent Alignment 라운드 한도 도달 시 smart 모드 자동 진행 - LM Studio doSwitch unload 실패 시 currentModel 정리 + load 강행 - retrieval informationDensity → queryCoverage 정합화 [/youtube 채널 지원 (v2.2.77~v2.2.82)] - 채널/플레이리스트 URL 자동 감지 + n:N 으로 영상 개수 지정 (최대 50) - 채널 루트 URL 에 /videos 탭 자동 append (yt-dlp enumeration 정상화) - 영상별 순차 처리 (queue 패턴) + i/N 진행 표시 + 마지막 통계 요약 - mode:info / mode:benchmark / mode:both 분석 모드 분기 - info: 영상 내용을 지식 카드로 추출 (튜토리얼·강의·뉴스용) - benchmark: 4-렌즈 대본 역기획서 (콘텐츠 제작 벤치마크용) - both: 둘 다 (기본) - bare keyword 도 허용: /youtube <url> n:1 info - bridge 에러 메시지 [object Object] 깨짐 수정 (구조화 에러 추출) - "패키지 없음" 등 환경 의존성 에러에 자동 가이드 첨부 [Astra: Setup Datacollect Dependencies 명령 추가 (v2.2.80)] - Python 자동 감지 + yt-dlp / youtube-transcript-api 자동 설치 - macOS PEP 668 환경 자동 폴백 (--user --break-system-packages) - /youtube 등에서 패키지 미설치 감지 시 "Install Now" 버튼 notification [테스트] - tests/agentEngine.test.ts 를 chunked flow 에 맞춰 전체 재작성 - tests/resilience_stress.test.ts Scenario B/D 를 role-aware mock 으로 갱신 - 399/399 통과 Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
@@ -124,8 +124,8 @@ export function assembleContext(chunks: RetrievalChunk[]): string {
|
||||
.map((c) => {
|
||||
const metadata = c.metadata;
|
||||
const conflictTag = metadata.conflictDetected ? ` [⚠️ CONFLICT: ${metadata.conflictSeverity}]` : '';
|
||||
const densityTag = metadata.informationDensity !== undefined ? ` (Density: ${metadata.informationDensity.toFixed(2)})` : '';
|
||||
return `- ${c.title}${conflictTag}${densityTag}: ${c.content}`;
|
||||
const coverageTag = metadata.queryCoverage !== undefined ? ` (Coverage: ${metadata.queryCoverage.toFixed(2)})` : '';
|
||||
return `- ${c.title}${conflictTag}${coverageTag}: ${c.content}`;
|
||||
})
|
||||
.join('\n');
|
||||
sections.push(`### ${label}\n${items}`);
|
||||
|
||||
@@ -297,7 +297,7 @@ export class RetrievalOrchestrator {
|
||||
// Phase 5: Scoring Intelligence Integration
|
||||
conflictDetected: s.conflictDetected,
|
||||
conflictSeverity: s.conflictSeverity,
|
||||
informationDensity: s.informationDensity,
|
||||
queryCoverage: s.queryCoverage,
|
||||
...(isLesson ? { isLesson: true, lessonKind: doc.kind } : {}),
|
||||
},
|
||||
});
|
||||
|
||||
@@ -205,7 +205,13 @@ export interface ScoredDocument {
|
||||
matchedTerms: string[];
|
||||
conflictDetected: boolean;
|
||||
conflictSeverity: ConflictSeverity;
|
||||
informationDensity: number;
|
||||
/**
|
||||
* Query Coverage = |matchedTermsSet| / |expandedQuery|.
|
||||
* 즉 "이 문서가 쿼리의 몇 % 를 다루고 있는가". 옛 이름은 `informationDensity`
|
||||
* 였는데 코드는 *문서 내 토큰 밀도* 가 아니라 *쿼리 커버리지* 를 계산하고 있어서
|
||||
* 호출자에게 의도 혼동을 줬다. 이름을 의미와 맞춰 통일.
|
||||
*/
|
||||
queryCoverage: number;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -293,9 +299,6 @@ export function scoreTfIdfPreTokenized(
|
||||
score += tfidf * titleMultiplier;
|
||||
}
|
||||
|
||||
// Information Density: 쿼리 관련 토큰의 밀도 측정
|
||||
const informationDensity = docTokens.length > 0 ? matchedTerms.length / docTokens.length : 0;
|
||||
|
||||
// Recency boost
|
||||
let recencyBoost = 0;
|
||||
if (doc.lastModified) {
|
||||
@@ -316,9 +319,11 @@ export function scoreTfIdfPreTokenized(
|
||||
|
||||
const finalScore = (score + recencyBoost + titleBoost) * conflictMultiplier;
|
||||
|
||||
// [Structural Fix] Information Density: 쿼리 커버리지 기반으로 계산 방식 정상화
|
||||
const queryCoverage = expandedQuery.length > 0
|
||||
? new Set(matchedTerms).size / expandedQuery.length
|
||||
// Query Coverage — 이 문서가 expanded query 의 몇 % 를 cover 했는지.
|
||||
// 옛날에 `informationDensity` 라는 이름으로 노출됐는데 이름과 계산이 어긋나 있어
|
||||
// 호출자가 "문서 내 밀도" 로 잘못 해석할 위험이 있었다. 이름·의미 통일.
|
||||
const queryCoverage = expandedQuery.length > 0
|
||||
? new Set(matchedTerms).size / expandedQuery.length
|
||||
: 0;
|
||||
|
||||
return {
|
||||
@@ -329,7 +334,7 @@ export function scoreTfIdfPreTokenized(
|
||||
matchedTerms: [...new Set(matchedTerms)],
|
||||
conflictDetected,
|
||||
conflictSeverity,
|
||||
informationDensity: queryCoverage // 밀도를 쿼리 커버리지로 대체
|
||||
queryCoverage,
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
@@ -36,7 +36,7 @@ export interface RetrievalChunk {
|
||||
// --- Scoring Intelligence (v2.75.0+) ---
|
||||
conflictDetected?: boolean;
|
||||
conflictSeverity?: ConflictSeverity;
|
||||
informationDensity?: number;
|
||||
queryCoverage?: number;
|
||||
|
||||
// --- Experience Memory ---
|
||||
/** True when this chunk comes from a lesson / playbook / qa-finding card in the brain. */
|
||||
|
||||
Reference in New Issue
Block a user