Files
connectai/src/retrieval/hierarchicalLevel.ts
T
koriweb 990ea0ae5f feat: v2.2.173-193 — 4인 팀 운영 슬래시 13개 + ASTRA 검증 엔진 6종
4인 팀 운영 슬래시 (v2.2.173~189):
- 일과 리듬: /morning, /evening, /weekly, /standup
- 트래커 (event-sourced .astra/*.jsonl): /runway, /customers, /hire
- 작업·결정: /task, /blocked, /onesie, /decisions
- 외부 출력: /draft, /feedback
- 분석: /cohort (MoM 추세)

ASTRA 추론·검색 엔진 (v2.2.183~192):
- v2.2.183 Conflict Surface — scoring.conflictSeverity 를 [CONFLICT WARNINGS] 블록으로
  서피스 + 교차-문서 발산(Jaccard) 감지
- v2.2.184 Chain-of-Verification — [VERIFICATION CHECKLIST] 답변 작성 전 그라운딩 자기 점검
  (instructional, strictMode 옵션)
- v2.2.185 Actionability Scoring — 최근 슬래시 명령 + 열린 파일 신호로 검색 결과 재가중
- v2.2.186 Temporal Markers + Distillation Loop — LongTerm/Episodic 만료 필터 +
  30일+ stale episode → LongTerm 'episode-digest' 승급 (수동 /memory distill + 세션 종료 자동)
- v2.2.187 Hierarchical Context Window + LLM Semantic Re-rank — 3-level 추상도 매칭
  + 토큰 예산 통과 후 LLM 1회로 의도-부합 재정렬 (opt-in)
- v2.2.190 Intent Clarification + Citation Trace — 모호 차원 감지 시 역질문 우선
  + 답변 끝 사용 출처 한 줄 정리
- v2.2.191 Post-hoc Self-Check — 답변 완료 후 별도 LLM 호출 1회로 답함/그라운딩/모순 평가,
  footer 한 줄로 표시 (opt-in, semantic re-rank 와 같은 안전 fallback 패턴)
- v2.2.192 Terminology Dictionary — .astra/glossary.md 사용자 편집 파일 + Term Check
  지침 통합 + /glossary init/path/reload
- v2.2.193 /help — 카테고리별 명령 목록 + 6종 verification 블록 현재 on/off

신규 모듈:
- src/retrieval/{conflictBlock,coveBlock,actionabilityScoring,hierarchicalLevel,
  semanticRerank,intentClarification,citationTrace,terminologyBlock}.ts
- src/memory/distillation.ts + types.ts 에 expiresAt/promoted/episode-digest 추가
- src/agent/postHocSelfCheck.ts
- src/features/{customers,feedback,hire,runway}/*.ts (event-sourced stores)

ASTRA 검증 5종 자동 주입 (buildAstraModeSystemPrompt, casual 모드 제외):
[INTENT CLARIFICATION GUIDANCE] (답변 시작 전) → [TERMINOLOGY DICTIONARY] +
[CONFLICT WARNINGS] + [VERIFICATION CHECKLIST] (작성 중) → [CITATION TRACE] (끝)
+ 6번째: Post-hoc Self-Check footer (답변 완료 후, opt-in)

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-29 16:05:30 +09:00

136 lines
5.8 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* Hierarchical Context Window — 질의·문서의 *추상도 레벨* 매칭으로 검색 노이즈 감소.
*
* 사용자 제안: "사용자가 '배포해줘' 라고 하면 L1(실행) 우선, '전략 검토' 라고 하면
* L3(전략) 우선". 같은 키워드 매치 점수여도 추상도가 안 맞으면 noise.
*
* v1 — 3-level 휴리스틱 (LLM 호출 없음, 결정적):
* - `concrete` — 코드, 로그, 디버그, 실행 명령
* - `operational` — 작업, 일정, 운영 절차, 회의록 (기본/기본값)
* - `strategic` — 전략, 비전, 의사결정 근거, 아키텍처 방향
*
* 매칭 정책:
* - 같은 레벨 → 보너스 (× 1.15)
* - 인접 레벨 (concrete↔operational, operational↔strategic) → 변화 없음
* - 양 끝 mismatch (concrete↔strategic) → 페널티 (× 0.7)
*
* 약한 시그널 — TF-IDF dominant 유지, 동점 깨기 역할. 검색 결과를 *제외* 하지 않음.
*/
import { RetrievalChunk } from './types';
export type AbstractionLevel = 'concrete' | 'operational' | 'strategic';
const QUERY_STRATEGIC_KEYWORDS = [
'전략', '방향', '비전', '미션', '목표', '의사결정', '아키텍처', '설계 방향',
'왜 이렇게', '왜 그렇게', '뭐가 맞', '어떤 게 좋', '어떻게 가야', '어떤 방향',
'판단', '결정', '관점', '평가', '검토',
'strategy', 'vision', 'mission', 'roadmap', 'direction', 'goal',
'why', 'rationale', 'pros and cons', 'tradeoff', 'evaluate',
];
const QUERY_CONCRETE_KEYWORDS = [
'코드', '함수', '버그', '에러', '로그', '실행', '명령어', '스크립트', '디버그',
'고쳐', '수정', '리팩토링', '리팩터', '커밋', '머지', '배포해', '돌려',
'에러 메시지', '스택 트레이스', 'syntax', 'compile',
'code', 'function', 'bug', 'error', 'log', 'execute', 'command', 'script',
'debug', 'fix', 'refactor', 'commit', 'merge', 'deploy', 'run',
];
const FOLDER_STRATEGIC_HINTS = ['strategy', 'vision', 'mission', 'roadmap', 'decision', 'principle', '전략', '비전'];
const FOLDER_OPERATIONAL_HINTS = ['playbook', 'runbook', 'operation', 'process', 'sop', '운영', '절차', 'meeting', '회의'];
const FOLDER_CONCRETE_HINTS = ['code', 'snippet', 'log', 'debug', 'fix', 'patch', '디버그', 'commit'];
const TITLE_STRATEGIC_HINTS = ['strategy', 'vision', 'rationale', 'direction', 'decision', 'plan', '전략', '계획', '방향', '결정', '평가'];
const TITLE_CONCRETE_HINTS = ['fix', 'bug', 'error', 'log', 'script', 'command', '버그', '에러', '로그', '커밋'];
function countMatches(text: string, keywords: string[]): number {
const lower = text.toLowerCase();
let n = 0;
for (const k of keywords) if (lower.includes(k.toLowerCase())) n++;
return n;
}
/**
* 질의 추상도 분류. 키워드 카운트 우열로 결정, 동률·없음이면 'operational' (기본).
*/
export function classifyQueryLevel(query: string): AbstractionLevel {
if (!query) return 'operational';
const s = countMatches(query, QUERY_STRATEGIC_KEYWORDS);
const c = countMatches(query, QUERY_CONCRETE_KEYWORDS);
if (s > c && s >= 1) return 'strategic';
if (c > s && c >= 1) return 'concrete';
return 'operational';
}
/**
* 한 chunk 의 추상도 분류 — 폴더 경로 → 파일명/제목 → 본문 순으로 강도 감소.
* 어느 신호도 없으면 'operational' (기본).
*/
export function classifyChunkLevel(chunk: RetrievalChunk): AbstractionLevel {
// 1. 폴더 경로 (가장 강함)
const fp = (chunk.metadata?.filePath || '').toLowerCase();
if (fp) {
for (const h of FOLDER_STRATEGIC_HINTS) if (fp.includes(`/${h}`) || fp.includes(`\\${h}`)) return 'strategic';
for (const h of FOLDER_CONCRETE_HINTS) if (fp.includes(`/${h}`) || fp.includes(`\\${h}`)) return 'concrete';
for (const h of FOLDER_OPERATIONAL_HINTS) if (fp.includes(`/${h}`) || fp.includes(`\\${h}`)) return 'operational';
}
// 2. 제목
const t = (chunk.title || '').toLowerCase();
if (t) {
let strat = 0, conc = 0;
for (const h of TITLE_STRATEGIC_HINTS) if (t.includes(h.toLowerCase())) strat++;
for (const h of TITLE_CONCRETE_HINTS) if (t.includes(h.toLowerCase())) conc++;
if (strat > conc && strat >= 1) return 'strategic';
if (conc > strat && conc >= 1) return 'concrete';
}
return 'operational';
}
const LEVEL_INDEX: Record<AbstractionLevel, number> = {
concrete: 0, operational: 1, strategic: 2,
};
export interface HierarchicalWeights {
/** 같은 레벨 매치 multiplier. 기본 1.15. */
sameLevelBonus: number;
/** 양 끝 mismatch (concrete↔strategic) multiplier. 기본 0.70. */
farMismatchPenalty: number;
}
export const DEFAULT_HIERARCHICAL_WEIGHTS: HierarchicalWeights = {
sameLevelBonus: 1.15,
farMismatchPenalty: 0.70,
};
/**
* 질의 레벨에 따라 chunks 의 score 를 hierarchical 매칭으로 재가중. in-place.
* metadata 에 분류 결과 기록 (debug/UI 노출).
*/
export function applyHierarchicalReweight(
chunks: RetrievalChunk[],
queryLevel: AbstractionLevel,
weights: HierarchicalWeights = DEFAULT_HIERARCHICAL_WEIGHTS,
): { sameLevel: number; farMismatch: number } {
let sameLevel = 0;
let farMismatch = 0;
const qi = LEVEL_INDEX[queryLevel];
for (const c of chunks) {
const cl = classifyChunkLevel(c);
(c.metadata as any).abstractionLevel = cl;
const ci = LEVEL_INDEX[cl];
const diff = Math.abs(qi - ci);
if (diff === 0) {
c.score *= weights.sameLevelBonus;
sameLevel++;
} else if (diff === 2) {
c.score *= weights.farMismatchPenalty;
farMismatch++;
}
// diff === 1: 인접 레벨 → 변화 없음
}
return { sameLevel, farMismatch };
}