wiki: Topic_Blog 신규 문서 일괄 추가 + ASTRA 성장 자산 동기화

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Antigravity Agent
2026-06-16 09:55:38 +09:00
parent d77ff5c625
commit e2c5471046
444 changed files with 88916 additions and 231 deletions
@@ -0,0 +1,110 @@
---
id: five-layer-memory-system
title: "5계층 메모리 시스템"
category: "AI_and_ML"
status: "draft"
verification_status: "applied"
canonical_id: ""
aliases: ["cognitive memory", "메모리 시스템", "short-term", "long-term", "episodic", "procedural", "distillation", "memory layers"]
duplicate_of: ""
source_trust_level: "A"
confidence_score: 0.93
created_at: 2026-06-13
updated_at: 2026-06-13
review_reason: ""
merge_history: []
tags: ["memory", "ai", "agent", "cognitive-architecture", "astraai"]
raw_sources: ["AstraAI/src/memory/index.ts", "AstraAI/src/memory/types.ts", "AstraAI/src/retrieval/index.ts"]
applied_in: ["AstraAI"]
github_commit: ""
---
# [[5계층 메모리 시스템]]
## 🎯 한 줄 통찰 (One-line insight)
LLM 에이전트의 "기억"은 인간 인지처럼 **시간 범위·용도가 다른 5개 계층(단기·장기·프로젝트·절차·일화)** 으로 나누고, 각 계층이 질의에 대해 관련도 점수를 매겨 컨텍스트에 합치는 것이 AstraAI 의 설계다 [S1][S2].
## 🧠 핵심 개념 (Core concepts)
1. **① 단기(Short-Term):** 현재 대화 흐름. 최근 N개 메시지를 FIFO 로 유지(`shortTermLimit: 8`) [S1].
2. **② 장기(Long-Term):** 사용자 취향·규칙·결정·목표. category(`preference`/`rule`/`decision`/`goal`/`episode-digest`)와 confidence, 참조 횟수, 만료시각(`expiresAt`)을 가진 엔트리 [S2].
3. **③ 프로젝트(Project):** 워크스페이스별 지식 — 아키텍처 결정(ADR), 버그 기록, 요구사항, 기술스택, 코드 컨벤션. workspace 경로 hash 로 식별 [S2].
4. **④ 절차(Procedural):** 반복 작업의 절차서(skill.md). triggerPatterns 로 매칭(`["wiki화","위키","wikify"]`)해 steps 를 제공 [S2].
5. **⑤ 일화(Episodic):** 과거 세션의 요약·주요 결정·토픽. 시간이 지나면 distillation 으로 장기 digest 로 승급 [S2].
## 🧩 추출된 패턴 (Extracted patterns)
- **통합 매니저 + 계층 위임:** `MemoryManager` 가 5개 계층 객체를 보유하고 `buildContext()` 에서 각 계층의 `buildContext(query)` 를 호출해 결과를 모은다 — 계층은 독립, 매니저는 조립 [S1].
- **관련도 점수 + 정렬:** 각 계층이 `MemoryContextResult { layer, label, content, relevance }` 를 반환, 매니저가 `relevance` 내림차순 정렬 후 합침 [S1].
- **lazy 프로젝트 메모리:** `projectMemories: Map<string, ProjectMemory>` 로 워크스페이스별 지연 생성·캐시 [S1].
- **temporal marker 로 자동 만료:** `expiresAt < now` 인 엔트리는 검색·컨텍스트 구성에서 자동 제외 — "Q3 계획은 9/30까지만 유효" 같은 시한부 지식 [S2].
- **증류(Distillation) 폐루프:** 세션 종료 시 stale 일화를 장기 digest 로 승급하고 `promoted=true` 로 표시해 이후 검색에서 제외(중복 방지) [S1].
## 📖 세부 내용 (Details)
### 컨텍스트 조립 (핵심 API)
`buildContext(currentPrompt, visibleHistory, summarize, workspacePath)` 가 5계층에서 관련 컨텍스트를 모아 하나의 `[MEMORY CONTEXT]` 블록으로 만든다. 각 계층은 자기 데이터를 query 와 비교해 relevance 를 매기고, 빈 결과면 제외된다. 마지막에 "관련될 때만 사용하고, 충돌 시 현재 요청을 우선하라"는 지침을 덧붙인다 [S1].
### 세션 종료 시 추출·영속화
`onSessionEnd(sessionId, messages, workspacePath, distillationOpts)`:
1. `MemoryExtractor` 가 대화에서 장기/일화/프로젝트 메모리를 추출 (실패해도 본 흐름 안 깨짐 — 빈 catch).
2. 장기 메모리 `save()`.
3. distillation 이 enabled 이고 interval 충족 시 stale 일화를 장기 digest 로 승급 [S1].
### RAG 와의 결합
검색 오케스트레이터는 메모리 계층을 RAG 소스 중 하나로 끌어온다 — 장기/프로젝트/절차/일화 각각을 `RetrievalChunk` 로 변환하고, source 별 가중치(procedural 0.95, project 0.85, episodic 0.7)로 정규화한다 [S3]. → [[RAG 검색 파이프라인]].
### 설정 가능성
`MemoryConfig` 로 계층별 on/off 와 한도(`longTermMaxEntries: 100`, `episodicMaxEpisodes: 50`)를 조절. 생성자에서 `{ ...defaults, ...config }` 로 부분 오버라이드 [S1].
## ⚖️ 모순 및 업데이트 (Contradictions & updates)
- **계층 경계의 모호성:** "프로젝트 결정"이 장기 메모리(decision)인지 프로젝트 메모리(ADR)인지 겹칠 수 있다. AstraAI 는 워크스페이스 종속성 유무로 가른다(프로젝트 종속이면 ③, 사용자 보편이면 ②).
- **만료 vs 보존:** `expiresAt` 로 시한부 지식을 자동 제외하지만, 만료된 지식이 "역사적 맥락"으로 필요할 때도 있다 — 검색은 제외하되 distillation 이 digest 로 보존하는 식으로 절충.
## 🛠️ 적용 사례 (Applied in summary)
- `AstraAI/src/memory/index.ts` — MemoryManager 의 buildContext/onSessionEnd 전체 흐름 [S1].
- `AstraAI/src/memory/types.ts` — 5계층 데이터 형태, temporal marker, distillation 승급 필드 [S2].
- `AstraAI/src/retrieval/index.ts``searchMemoryLayers` 가 계층을 RAG 청크로 변환 [S3].
## 💻 코드 패턴 (Code patterns)
```typescript
// 1) 통합 매니저가 계층에 위임 + 관련도 정렬 (src/memory/index.ts)
public buildContext(prompt, history, summarize, workspacePath?): string {
const layers: MemoryContextResult[] = [];
const stm = this.shortTerm.buildContext(history, this.config.shortTermLimit, summarize);
if (stm) layers.push(stm);
const ltm = this.longTerm.buildContext(prompt);
if (ltm) layers.push(ltm);
// ... project / procedural / episodic 동일 패턴
if (!layers.length) return '';
layers.sort((a, b) => b.relevance - a.relevance); // 관련도 내림차순
return ['', '[MEMORY CONTEXT]', /* 지침 */, layers.map(l => `### ${l.label}\n${l.content}`).join('\n\n')].join('\n');
}
// 2) lazy 프로젝트 메모리 (src/memory/index.ts)
public getProjectMemory(workspacePath: string): ProjectMemory {
if (!this.projectMemories.has(workspacePath))
this.projectMemories.set(workspacePath, new ProjectMemory(workspacePath));
return this.projectMemories.get(workspacePath)!;
}
// 3) 부가 작업은 본 흐름을 깨지 않음 (src/memory/index.ts)
try { this.extractor.extractFromSession(...); } catch { /* never break main flow */ }
```
## ✅ 검증 상태 및 신뢰도
- **상태:** draft
- **검증 단계:** applied
- **출처 신뢰도:** A
- **신뢰 점수:** 0.93
- **중복 검사 결과:** 신규 생성 (New discovery)
## 🔗 지식 그래프 (Knowledge Graph)
- **상위/루트:** [[AstraAI 아키텍처 개요]]
- **관련 개념:** [[RAG 검색 파이프라인]], [[이벤트 소싱 스토어 패턴]], [[Agent 오케스트레이터 분해]]
- **참조 맥락:** 로컬 LLM 이 에이전트의 기억/컨텍스트 시스템을 설계하거나 메모리 계층 코드를 다룰 때 참조.
## 📚 출처 (Sources)
- [S1] AstraAI/src/memory/index.ts — MemoryManager, buildContext, onSessionEnd, distillation 호출
- [S2] AstraAI/src/memory/types.ts — 5계층 타입, temporal marker, 승급 필드
- [S3] AstraAI/src/retrieval/index.ts — searchMemoryLayers(메모리→RAG 청크)
## 📝 변경 이력 (Change history)
- 2026-06-13: AstraAI 코드 분석 기반 초안 생성.
@@ -0,0 +1,113 @@
---
id: agent-orchestrator-decomposition
title: "Agent 오케스트레이터 분해"
category: "AI_and_ML"
status: "draft"
verification_status: "applied"
canonical_id: ""
aliases: ["agent executor", "orchestrator", "god class 분해", "multi-agent", "ChunkedWriter", "sequential dispatch", "에이전트 파이프라인"]
duplicate_of: ""
source_trust_level: "A"
confidence_score: 0.9
created_at: 2026-06-13
updated_at: 2026-06-13
review_reason: ""
merge_history: []
tags: ["agent", "orchestrator", "multi-agent", "architecture", "ai", "astraai"]
raw_sources: ["AstraAI/src/agent.ts", "AstraAI/src/agents/AgentWorkflowManager.ts", "AstraAI/src/agent/multiAgent/workflow.ts", "AstraAI/src/features/company/dispatcher.ts"]
applied_in: ["AstraAI"]
github_commit: ""
---
# [[Agent 오케스트레이터 분해]]
## 🎯 한 줄 통찰 (One-line insight)
한 턴의 복잡한 처리(컨텍스트 조립→라우팅→스트리밍→후처리→학습)는 거대 orchestrator 하나가 *흐름의 골격만 쥐고 세부는 추출된 모듈에 위임*하는 구조가 유지보수에 유리하며, 멀티에이전트는 "병렬 persona 줄세우기"보다 **자원 제약에 맞춘 순차 실행 + 단일 작성자 다중 역할**이 로컬 환경에서 더 견고하다 [S1][S2][S4].
## 🧠 핵심 개념 (Core concepts)
1. **얇은 골격 + 추출 위임:** `agent.ts`(orchestrator)는 한 턴의 흐름을 읽을 수 있게 유지하고, 세부는 `handlePrompt/`, `llm/`, `actions/`, `sessions/`, `multiAgent/` 로 추출 [S1].
2. **Action tag 실행:** 모델 출력의 `<create_file>`, `<run_command>` 등 태그를 액션 실행기로 라우팅해 도구를 수행 [S1][S4].
3. **단일 작성자 다중 역할(ChunkedWriter):** outline → section[N] → polish 를 같은 모델이 번갈아 수행 — hop 마다 컨텍스트 폭증·본문 손실을 피함 [S2].
4. **순차 디스패치(company):** CEO 플래너 → 전문가들 순차 실행(peer-context 전달) → CEO 리포터 합성 [S4].
5. **mission 락:** 무거운 LLM 작업은 `missionId` 단위로 직렬화 [S2][참조: [[동시성 제어 Lock Queue Transaction]]].
## 🧩 추출된 패턴 (Extracted patterns)
- **God-class 분해:** orchestrator 의 import 가 100줄을 넘지만, 이는 "기능을 작은 모듈로 추출하고 흐름에서 다시 끌어모은" 결과 — 흐름은 한 곳, 구현은 분산 [S1].
- **메시지 프로토콜 UI:** webview 와 `streamStart`/`streamChunk`/`streamEnd`/`workflowStage` 메시지로 통신 — 진행 단계는 본문이 아니라 상단 인디케이터로 [S3].
- **모든 종료 경로에서 인디케이터 닫기:** 성공·취소·에러 어디서든 `workflowStage{done:true}` 를 보내 "영원히 도는 스피너" 방지 [S3].
- **peer-context 버퍼:** 앞 에이전트 출력을 잘라(`PEER_OUTPUT_BUDGET 1500`) 다음 에이전트 프롬프트에 전달 [S4].
- **raw 출력 → 공용 액션 실행기 재사용:** 전문가도 action tag 를 낼 수 있고, 채팅과 같은 실행기를 통과시켜 도구 동작 일관성 유지 [S4].
## 📖 세부 내용 (Details)
### 멀티에이전트 설계의 진화 (post-mortem)
초기엔 planner/researcher/reflector/writer/synthesizer 5개 persona 를 줄세웠다. 문제: 각 hop 마다 컨텍스트가 누적되고 *원본 본문이 추상화로 손실* 돼, 사용자가 본문 분석을 요청해도 "분석 방법론" 만 만들어내는 사고가 났다. → 현재는 단일 `ChunkedWriter` 가 outline/section/polish 세 역할을 같은 모델에서 번갈아 수행 — 각 호출이 작고 본문은 매 호출에 직접 전달돼 손실이 없다 [S2].
### 왜 순차 디스패치인가 (company 모드)
사용자는 단일 GPU/제한된 RAM 에서 Astra 를 돌린다. 병렬 에이전트는 여러 모델을 동시에 메모리에 상주시켜야 한다. 순차 실행은 "정확히 한 번에 하나의 모델만 상주" 를 보장하고, LM Studio lifecycle 매니저가 이전 모델을 unload 하고 다음을 load 한다 [S4]. → 이 위키의 sub-agent 제약(병렬 fanout 금지)과도 같은 원리.
### 왜 handlePrompt 를 재사용하지 않는가
`handlePrompt`*대화형* 경로용이라 대화 이력·스트리밍 UI·에이전트 모드 주입 등 12가지를 떠안는다. company 턴은 "system 1개 + user 1개 → 문자열 1개" 의 깨끗한 primitive 가 필요하므로 `AIService.chat()` 을 쓴다 — 책임이 다른 경로는 다른 primitive [S4].
## ⚖️ 비교 및 선택 기준 (Comparison & decision criteria)
| 멀티에이전트 방식 | 장점 | 단점 | 언제 |
|---|---|---|---|
| 병렬 persona N개 | 빠름(자원 충분 시) | 모델 다중 상주, 컨텍스트 누적/본문 손실 | RAM/GPU 넉넉한 서버 |
| 단일 작성자 다중 역할 | 컨텍스트 작고 본문 보존 | 한 모델 품질에 의존 | 로컬 단일 모델 |
| 순차 디스패치 | 한 번에 한 모델, 자원 안전 | 총 시간 김 | 단일 GPU/제한 RAM |
## ⚖️ 모순 및 업데이트 (Contradictions & updates)
- **orchestrator 크기:** 분해했어도 agent.ts 가 크다 — "흐름 가독성" 을 위해 의도적으로 골격을 남긴 트레이드오프(완전 분해 시 흐름 추적이 파일 사이를 떠돈다).
- **순차의 비용:** 응답이 느리다. 사용자 경험을 위해 진행 단계를 webview 인디케이터로 보여 체감 지연을 완화한다.
## 🛠️ 적용 사례 (Applied in summary)
- `AstraAI/src/agent.ts` — orchestrator 골격 + 추출 모듈 import [S1].
- `AstraAI/src/agents/AgentWorkflowManager.ts` — ChunkedWriter 단일 작성자 다중 역할 [S2].
- `AstraAI/src/agent/multiAgent/workflow.ts` — webview 메시지 프로토콜, 모든 경로 인디케이터 닫기 [S3].
- `AstraAI/src/features/company/dispatcher.ts` — 순차 디스패치, peer-context, 설계 근거 주석 [S4].
## 💻 코드 패턴 (Code patterns)
```typescript
// 1) 단일 작성자 다중 역할 — 스테이지를 UI 라벨로 (src/agents/AgentWorkflowManager.ts)
const writer = new ChunkedWriter(modelName, overrides); // outline → section → polish
const engine = new AgentEngine(writer);
return await engine.runMission(missionId, prompt, brainContext, signal,
(stage, msg) => onProgress(this.mapStageToUI(stage), msg)); // ① 구조 → ② 본문 → ③ 다듬기
// 2) 모든 종료 경로에서 인디케이터 닫기 (src/agent/multiAgent/workflow.ts)
} catch (error: any) {
deps.getWebview()?.postMessage({ type: 'workflowStage', value: { step: '완료', done: true } });
if (error.name === 'AbortError') { /* 취소 */ return; }
// ... 에러 카드
} finally { deps.options.onStreamLifecycle?.end(); }
// 3) 순차 디스패치 + peer-context 버퍼 (src/features/company/dispatcher.ts, 개념)
let peerContext = '';
for (const task of plan.tasks) {
const prompt = buildSpecialistPrompt(task, peerContext); // 앞 에이전트 맥락 포함
const out = await aiService.chat({ system, user: prompt }); // 한 번에 한 모델만 상주
writeAgentOutput(sessionDir, task, out.content);
peerContext += '\n' + out.content.slice(0, PEER_OUTPUT_BUDGET); // 다음 에이전트에 전달
}
```
## ✅ 검증 상태 및 신뢰도
- **상태:** draft
- **검증 단계:** applied
- **출처 신뢰도:** A
- **신뢰 점수:** 0.90
- **중복 검사 결과:** 신규 생성 (New discovery)
## 🔗 지식 그래프 (Knowledge Graph)
- **상위/루트:** [[AstraAI 아키텍처 개요]]
- **관련 개념:** [[Intelligence 검증 레이어]], [[LLM 프로바이더 추상화]], [[동시성 제어 Lock Queue Transaction]], [[Agent Orchestration Pattern]]
- **참조 맥락:** 로컬 LLM 이 에이전트 실행 파이프라인·멀티에이전트 구조를 자원 제약 하에서 설계할 때 참조.
## 📚 출처 (Sources)
- [S1] AstraAI/src/agent.ts — orchestrator 골격, 추출 모듈 import 구조
- [S2] AstraAI/src/agents/AgentWorkflowManager.ts — ChunkedWriter, 멀티에이전트 진화 post-mortem
- [S3] AstraAI/src/agent/multiAgent/workflow.ts — 메시지 프로토콜, 인디케이터 닫기
- [S4] AstraAI/src/features/company/dispatcher.ts — 순차 디스패치 근거, peer-context, primitive 분리
## 📝 변경 이력 (Change history)
- 2026-06-13: AstraAI 코드 분석 기반 초안 생성.
@@ -0,0 +1,127 @@
---
id: intelligence-verification-layer
title: "Intelligence 검증 레이어"
category: "AI_and_ML"
status: "draft"
verification_status: "applied"
canonical_id: ""
aliases: ["self-evolving", "critic", "confidence engine", "epistemic guard", "correction loop", "anti-hallucination", "자기검증", "환각 방지"]
duplicate_of: ""
source_trust_level: "A"
confidence_score: 0.93
created_at: 2026-06-13
updated_at: 2026-06-13
review_reason: ""
merge_history: []
tags: ["intelligence", "verification", "hallucination", "self-evolving", "ai", "astraai"]
raw_sources: ["AstraAI/src/intelligence/criticAgent.ts", "AstraAI/src/intelligence/confidenceEngine.ts", "AstraAI/src/intelligence/epistemicGuardBlock.ts", "AstraAI/src/intelligence/correctionLoop.ts"]
applied_in: ["AstraAI"]
github_commit: ""
---
# [[Intelligence 검증 레이어]]
## 🎯 한 줄 통찰 (One-line insight)
작은 로컬 LLM 의 환각·오류를 시스템이 잡으려면 "**저렴한 결정론적 검사를 항상, 비싼 LLM 검사는 조건부로**" 돌리고, 사용자 정정 한 번을 시스템 성장으로 환원하는 폐루프가 핵심이며, AstraAI 는 Epistemic Guard(사전 억제)·Confidence Engine(측정)·Critic(사후 검수)·Correction Loop(학습)로 이를 계층화한다 [S1][S2][S3][S4].
## 🧠 핵심 개념 (Core concepts)
1. **Epistemic Guard (사전):** 시스템 프롬프트에 "확실/추정/모름 3등급" 을 강제하는 블록을 주입. 검색 근거가 없을수록 지시가 강해진다("지어내지 말고 질문하라") [S3].
2. **Confidence Engine (측정):** LLM 호출 없이 검색 그라운딩 신호 + 답변 텍스트 신호(정규식)만으로 0~100 확신도를 *결정론적* 산출. latency 0 [S2].
3. **Critic Agent (사후):** 결정론적 검사가 문제를 신호할 때만 LLM 검수 1회 — 조건부 1-pass. 누락·근거 없는 단정·미결 구분을 검사 [S1].
4. **Correction Loop (학습):** 사용자 정정을 Ground Truth 로 보고 레슨 저장 + 회귀 케이스 적립 + 약점 프로필 → 다음 턴 프롬프트에 자동 주입 [S4].
5. **CoVe vs Epistemic Guard 분업:** CoVe 는 출처가 *있을 때* 주장-출처 매핑 검증, Epistemic Guard 는 출처 *유무 무관* 등급 표시 강제 [S3].
## 🧩 추출된 패턴 (Extracted patterns)
- **저렴한 것 항상, 비싼 것 조건부:** 결정론적(정규식/카운트) 검사는 매 턴, LLM 검수는 신호가 있을 때만 — 로컬 모델 latency 비용 통제 [S1][S2].
- **"확신도는 모델 자신감이 아니라 검증 가능성":** 모델이 솔직히 "(확인 필요)" 를 쓰면 점수가 *내려가는* 게 올바른 동작 — 사용자 검토를 유도해야 하므로 [S2].
- **가중치 합산 + factor 기록:** 확신도를 요인별 delta 합으로 계산하고 각 factor 를 footer 에 노출 — 점수가 왜 그렇게 나왔는지 설명가능 [S2].
- **정답지를 사람이 안 만든다:** 정정 발화 자체가 Ground Truth. 회귀 테스트가 "같은 실수 반복?" 을 LLM-judge 로 판정 [S4].
- **통찰→행동의 기계화:** 태그 통계가 리포트에 머물지 않고 `buildSelfReviewBlock` 으로 다음 턴 시스템 프롬프트를 바꾼다(같은 태그 2회+일 때만) [S4].
- **JSON 강건 파싱:** LLM 의 JSON 출력에 잡설이 섞여도 첫 균형 `{}` 블록을 스캔해 추출 [S1].
- **fire-and-forget 캡처:** 정정 캡처는 비동기로 흘려 응답 속도에 영향 없음 [S4].
## 📖 세부 내용 (Details)
### Confidence Engine — 결정론적 가중 합산
중립 출발점 55에서 시작해 요인별로 가감한다 [S2]:
- 그라운딩: 청크 3건+ & topScore 0.5+ → +25, 1건+ → +12, 없음 → -15.
- 출처 인용 +8 / 모델 지식만 -5.
- 출처 충돌 건당 -8(최대 -16), 요청 모호성 -10.
- Requirement 커버리지 전부 충족 +10 / 누락당 -6(최대 -18).
- 헤지 표현 개당 -4(최대 -12).
구간: 90+ 높음 / 70-89 보통 / 50-69 낮음 / <50 매우 낮음(에스컬레이션).
### Critic — 조건부 1-pass 검수
완전한 작성→비판→재작성 debate 는 로컬 Gemma latency 때문에 미룬다. v1 은 "커버리지 누락 또는 확신도 <70" 일 때만 Critic LLM 1회 호출, 결과를 답변 아래 보완 카드로 표시. `maxRounds` knob 만 준비해 두고 다회전은 후속 [S1].
### Correction Loop — 정정 1회가 3곳을 성장
```
사용자 정정 → ① 감지(보수적 정규식) + LLM 분류(6개 오류 태그)
├→ 태깅된 레슨 저장 (lessons/)
└→ 회귀 케이스 적립 (.astra/eval/corrections.jsonl)
② 주간 성장 사이클: 회귀 재검사 + 태그 통계 → 약점 프로필
③ 약점 프로필 → 시스템 프롬프트 자기검토 블록 자동 주입
```
설계 원칙: 정답지는 정정 자체, 통찰→행동은 기계적, 모든 산출물은 사람이 열어 수정/삭제 가능(Permission Based Learning), 캡처는 fire-and-forget [S4].
### 폭주 방지 장치들
- 자기검토 블록은 같은 태그 2회+일 때만(1회성 실수로 프롬프트 어지럽힘 방지) [S4].
- 지식 공백 학습 큐는 같은 질문 1회만 등록(해시 id) + proposed 20건 쌓이면 사람이 정리할 때까지 중단 [S4].
## ⚖️ 모순 및 업데이트 (Contradictions & updates)
- **휴리스틱 가중치의 임시성:** Confidence 가중치는 "휴리스틱 v1" 으로, 골든셋이 쌓이면 사람 평가와의 상관으로 보정 예정 — 현재 절대 점수보다 *상대 비교* 와 추세에 의미가 있다.
- **정규식 정정 감지의 한계:** 보수적이라 미묘한 정정("음 그건 좀…")은 놓친다. 오탐이 레슨 노이즈를 만들기에 의도적으로 보수적.
- **Critic 의 1-pass 한계:** 다회전 debate 가 더 정확하지만 비용 때문에 보류 — 정확도 vs latency 트레이드오프의 현실적 타협.
## 🛠️ 적용 사례 (Applied in summary)
- `AstraAI/src/intelligence/confidenceEngine.ts` — 결정론적 확신도 + footer [S2].
- `AstraAI/src/intelligence/criticAgent.ts` — 조건부 검수 + 균형 JSON 파싱 [S1].
- `AstraAI/src/intelligence/epistemicGuardBlock.ts` — 사전 억제 프롬프트 [S3].
- `AstraAI/src/intelligence/correctionLoop.ts` — 정정→레슨→약점프로필→프롬프트 폐루프 [S4].
## 💻 코드 패턴 (Code patterns)
```typescript
// 1) 결정론적 확신도 — 요인별 delta 합산 + 설명가능 (src/intelligence/confidenceEngine.ts)
let score = 55; // 중립 출발점
if (retrieval.chunkCount >= 3 && retrieval.topScore >= 0.5) factors.push({ label: '검색 근거 강', delta: +25 });
else if (retrieval.chunkCount >= 1) factors.push({ label: '검색 근거 있음', delta: +12 });
else factors.push({ label: '검색 근거 없음', delta: -15 });
if (answer.hedgeCount > 0) factors.push({ label: '불확실 표시', delta: -Math.min(12, answer.hedgeCount * 4) });
for (const f of factors) score += f.delta;
score = Math.max(0, Math.min(100, Math.round(score))); // 0~100 clamp
// 2) 균형 괄호 JSON 추출 — LLM 잡설 내성 (src/intelligence/criticAgent.ts)
let depth = 0, end = -1, inString = false, escaped = false;
for (let i = start; i < raw.length; i++) {
const ch = raw[i];
if (escaped) { escaped = false; continue; }
if (ch === '\\') { escaped = true; continue; }
if (ch === '"') { inString = !inString; continue; }
if (inString) continue;
if (ch === '{') depth++; else if (ch === '}' && --depth === 0) { end = i; break; }
}
// 3) 통찰→행동 — 약점 통계를 다음 턴 프롬프트로 (src/intelligence/correctionLoop.ts)
const significant = profile.tagCounts.filter(t => t.count >= 2).slice(0, 2); // 2회+만
// → "너는 최근 '사실오류' 정정을 N회 받았다. 수치·날짜는 근거 없으면 '확인 필요'로 표시하라."
```
## ✅ 검증 상태 및 신뢰도
- **상태:** draft
- **검증 단계:** applied
- **출처 신뢰도:** A
- **신뢰 점수:** 0.93
- **중복 검사 결과:** 신규 생성 (New discovery)
## 🔗 지식 그래프 (Knowledge Graph)
- **상위/루트:** [[AstraAI 아키텍처 개요]]
- **관련 개념:** [[의존성 주입과 서비스 인터페이스]], [[프롬프트 엔지니어링 패턴]], [[Agent 오케스트레이터 분해]]
- **참조 맥락:** 로컬 LLM 이 자기검증·환각 억제·피드백 학습 루프를 설계할 때 참조.
## 📚 출처 (Sources)
- [S1] AstraAI/src/intelligence/criticAgent.ts — 조건부 검수, 균형 JSON 파싱, 함수 주입
- [S2] AstraAI/src/intelligence/confidenceEngine.ts — 결정론적 확신도 산출
- [S3] AstraAI/src/intelligence/epistemicGuardBlock.ts — 3등급 인식론 가드
- [S4] AstraAI/src/intelligence/correctionLoop.ts — 정정 폐루프, 약점 프로필, 폭주 방지
## 📝 변경 이력 (Change history)
- 2026-06-13: AstraAI 코드 분석 기반 초안 생성.
@@ -0,0 +1,124 @@
---
id: llm-provider-abstraction
title: "LLM 프로바이더 추상화"
category: "AI_and_ML"
status: "draft"
verification_status: "applied"
canonical_id: ""
aliases: ["provider abstraction", "adapter pattern", "LLM 라우팅", "prefix routing", "SSE", "스트리밍", "엔진 폴백"]
duplicate_of: ""
source_trust_level: "A"
confidence_score: 0.92
created_at: 2026-06-13
updated_at: 2026-06-13
review_reason: ""
merge_history: []
tags: ["llm", "provider", "adapter", "streaming", "sse", "astraai"]
raw_sources: ["AstraAI/src/features/providers/types.ts", "AstraAI/src/features/providers/index.ts", "AstraAI/src/features/providers/anthropic.ts", "AstraAI/src/core/services.ts"]
applied_in: ["AstraAI"]
github_commit: ""
---
# [[LLM 프로바이더 추상화]]
## 🎯 한 줄 통찰 (One-line insight)
여러 LLM 공급자(로컬 LM Studio/Ollama, 클라우드 OpenRouter/Anthropic/Gemini)를 한 코드에서 쓰려면 "**model id prefix 로 라우팅 + 공급자별 어댑터가 차이를 흡수 + 모두 같은 SSE 포맷으로 정규화**"가 핵심이며, AstraAI 는 이 어댑터 패턴으로 호출부를 공급자 무관하게 유지한다 [S1][S2].
## 🧠 핵심 개념 (Core concepts)
1. **Prefix 라우팅:** `anthropic:claude-...`, `gemini:...`, `openrouter:...` 처럼 model id 의 접두사로 공급자를 결정. 접두사 없으면 로컬 엔진 [S1].
2. **어댑터 패턴:** 공급자마다 `streamX(context, params)` 함수가 그 API 의 차이(인증·바디·스트림 형식)를 흡수하고 표준 인터페이스(`StreamParams`)를 받는다 [S2][S3].
3. **출력 정규화(SSE):** 각 어댑터가 응답 스트림을 *OpenAI 호환 SSE* 로 변환해 반환 → 기존 SSE 파서 하나로 모두 소비 [S2][S3].
4. **로컬 엔진 폴백:** 로컬은 LM Studio↔Ollama 간 자동 폴백(전송 오류/5xx/빈 응답 시) [S4].
5. **에러 응답 passthrough:** 인증 실패·4xx·5xx 는 `.ok=false` Response 로 그대로 반환, 호출부가 `.text()` 로 메시지 추출 [S2][S3].
## 🧩 추출된 패턴 (Extracted patterns)
- **양방향 prefix 변환:** `parseModelPrefix(id)`(분해) ↔ `makeModelId(provider, model)`(조립) — UI/config 저장과 라우팅이 1:1 [S1].
- **switch dispatch:** `streamCloudCompletion``switch (hit.provider)` 로 어댑터 선택 — 공급자 추가는 case 하나 [S2].
- **입력 정규화(공급자 제약 흡수):** Anthropic 어댑터는 (1) system 을 top-level 로 분리, (2) 연속 같은 role 병합(교대 강제), (3) 첫 메시지 user 강제(dummy 삽입) [S3].
- **활성 공급자만 병렬 조회:** `listAllCloudModels` 가 enabled+apiKey 인 공급자만 `Promise.all` 로 모델 목록 수집 [S2].
- **하드코딩 fallback 목록:** 모델 list API 가 없는 공급자(Anthropic)는 알려진 모델 목록을 반환하되 사용자 직접 입력도 허용(validate 안 함) [S3].
## 📖 세부 내용 (Details)
### prefix 를 왜 쓰는가
같은 model name 이 OpenRouter 와 직통에 동시 존재할 수 있어 출처를 명시해야 라우팅이 모호하지 않다. 또 UI 드롭다운 그룹화('OpenRouter · ...')와, 접두사 없는 옛 설정('gemma4:e2b')의 자동 로컬 경로 처리에 유리하다 [S1].
### 어댑터가 흡수하는 차이 (Anthropic 예)
- base URL `https://api.anthropic.com/v1`, 인증 `x-api-key` + `anthropic-version` 헤더.
- system 은 messages 가 아니라 top-level `system` 필드 → 어댑터가 분리·병합.
- role 교대 강제 → 연속 같은 role 병합.
- 응답 스트림이 OpenAI 와 다른 event 형식 → `transformAnthropicStream` 으로 변환 후 새 `Response` 로 wrap [S3].
이 정규화 덕분에 상위 `agent.ts` 의 스트림 파서는 공급자를 전혀 모른다 — "차이는 가장자리(어댑터)에서 흡수, 중심은 단일 포맷".
### 로컬 엔진 폴백 (services.ts)
`AIService.chat` 은 사용자 설정 엔진을 먼저 시도하고, 전송 오류/HTTP 실패/빈 응답이면 다른 엔진으로 폴백한다. 빈 응답은 soft failure 로 취급해 재시도, 두 엔진 모두 빈 응답이면 `empty: true` 결과를 반환해 호출부가 사용자에게 안내하게 한다(예외 삼키지 않음) [S4].
## ⚖️ 비교 및 선택 기준 (Comparison & decision criteria)
| 접근 | 장점 | 단점 | 언제 |
|---|---|---|---|
| prefix 라우팅 | 모호성 없음, UI 그룹화 | id 규칙 학습 필요 | 다중 공급자/중복 모델명 |
| 어댑터별 함수 | 차이 격리, 추가 쉬움 | 공급자마다 구현 | 공급자 API 가 제각각일 때 |
| 공통 SSE 정규화 | 파서 1개로 통일 | 변환 레이어 비용 | 스트리밍 다공급자 |
| 로컬↔로컬 폴백 | 가용성↑ | 지연 증가 가능 | 로컬 엔진 불안정 환경 |
## ⚖️ 모순 및 업데이트 (Contradictions & updates)
- **하드코딩 모델 목록의 노후화:** Anthropic 어댑터의 모델 목록은 작성 시점 기준이라 시간이 지나면 낡는다. 사용자 직접 입력을 허용해 완화하지만, 최신 모델 사용 시 id 를 직접 넣어야 한다.
- **prompt caching/tool use 미구현:** 어댑터 주석이 "향후 확장 여지(prompt caching, tool use)"를 명시 — 현재는 단순 streaming 만. 비용 최적화·구조화 호출은 후속 과제.
## 🛠️ 적용 사례 (Applied in summary)
- `AstraAI/src/features/providers/index.ts` — streamCloudCompletion switch dispatch, listAllCloudModels 병렬 [S2].
- `AstraAI/src/features/providers/anthropic.ts` — 입력 정규화 + SSE 변환 + 에러 passthrough [S3].
- `AstraAI/src/features/providers/types.ts` — parseModelPrefix/makeModelId [S1].
- `AstraAI/src/core/services.ts` — 로컬 엔진 폴백 [S4].
## 💻 코드 패턴 (Code patterns)
```typescript
// 1) prefix 라우팅 (src/features/providers/types.ts)
export function parseModelPrefix(id: string): { provider: ProviderId; model: string } | null {
for (const { prefix, id: pid } of PROVIDER_PREFIXES)
if (id.startsWith(prefix)) return { provider: pid, model: id.slice(prefix.length) };
return null; // null = 로컬 엔진 경로
}
// 2) switch dispatch — 공급자 추가는 case 하나 (src/features/providers/index.ts)
switch (hit.provider) {
case 'openrouter': return streamOpenRouter(context, fullParams);
case 'anthropic': return streamAnthropic(context, fullParams);
case 'gemini': return streamGemini(context, fullParams);
}
// 3) 입력 정규화로 공급자 제약 흡수 (src/features/providers/anthropic.ts)
for (const m of params.messages) {
if (m.role === 'system') systemPrompt += (systemPrompt ? '\n\n' : '') + m.content; // top-level 분리
else { const last = messages.at(-1);
if (last?.role === m.role) last.content += '\n\n' + m.content; // 같은 role 병합
else messages.push({ role: m.role, content: m.content }); }
}
if (messages[0]?.role !== 'user') messages.unshift({ role: 'user', content: '(continue)' }); // 첫 user 강제
// 4) 응답 스트림을 공통 SSE 로 정규화
const transformed = transformAnthropicStream(upstream.body);
return new Response(transformed, { status: 200, headers: { 'Content-Type': 'text/event-stream' } });
```
## ✅ 검증 상태 및 신뢰도
- **상태:** draft
- **검증 단계:** applied
- **출처 신뢰도:** A
- **신뢰 점수:** 0.92
- **중복 검사 결과:** 신규 생성 (New discovery)
## 🔗 지식 그래프 (Knowledge Graph)
- **상위/루트:** [[AstraAI 아키텍처 개요]]
- **관련 개념:** [[비동기 프로그래밍 Promise async await]], [[의존성 주입과 서비스 인터페이스]], [[Agent 오케스트레이터 분해]]
- **참조 맥락:** 로컬 LLM 이 다중 LLM 공급자/외부 API 를 어댑터로 통합하는 코드를 작성할 때 참조.
## 📚 출처 (Sources)
- [S1] AstraAI/src/features/providers/types.ts — prefix 라우팅, makeModelId
- [S2] AstraAI/src/features/providers/index.ts — switch dispatch, 병렬 모델 조회
- [S3] AstraAI/src/features/providers/anthropic.ts — 입력 정규화, SSE 변환, 에러 passthrough
- [S4] AstraAI/src/core/services.ts — 로컬 엔진 폴백
## 📝 변경 이력 (Change history)
- 2026-06-13: AstraAI 코드 분석 기반 초안 생성.
@@ -0,0 +1,115 @@
---
id: rag-retrieval-pipeline
title: "RAG 검색 파이프라인"
category: "AI_and_ML"
status: "draft"
verification_status: "applied"
canonical_id: ""
aliases: ["RAG", "retrieval", "검색 증강 생성", "하이브리드 검색", "context budget", "rerank", "chunking", "orchestrator"]
duplicate_of: ""
source_trust_level: "A"
confidence_score: 0.92
created_at: 2026-06-13
updated_at: 2026-06-13
review_reason: ""
merge_history: []
tags: ["rag", "retrieval", "ai", "search", "embedding", "astraai"]
raw_sources: ["AstraAI/src/retrieval/index.ts", "AstraAI/src/retrieval/chunker.ts", "AstraAI/src/retrieval/scoring.ts"]
applied_in: ["AstraAI"]
github_commit: ""
---
# [[RAG 검색 파이프라인]]
## 🎯 한 줄 통찰 (One-line insight)
RAG 는 "질문에 답하기 전에 관련 지식을 찾아 컨텍스트에 넣는" 기법이며, AstraAI 의 오케스트레이터는 **질의 계획 → 다중 소스 병렬 검색 → 점수 정규화·재가중 → 토큰 예산 내 선택** 의 4단계로 brain 파일·5계층 메모리·최근 세션을 하나의 컨텍스트로 융합한다 [S1].
## 🧠 핵심 개념 (Core concepts)
1. **소스 융합(Fusion):** brain 파일(TF-IDF/하이브리드), 메모리 계층, 중기(최근 세션)를 각각 검색해 `RetrievalChunk[]` 로 모은다 [S1].
2. **점수 정규화:** 소스마다 점수 스케일이 달라, 소스별로 0~1 정규화 후 source 우선순위 가중치를 곱한다 [S1].
3. **재가중(Re-rank):** Actionability(현재 작업 상태 신호)와 Hierarchical(질의·문서 추상도 매칭)로 점수를 조정 [S1].
4. **토큰 예산(Context budget):** 모델 컨텍스트 한도 내에서 점수 높은 청크만 선택, 나머지는 drop [S1].
5. **섹션 청킹:** 긴 문서를 헤딩 경계로 쪼개 정밀도를 높이는 선택적 경로 [S2]. → [[TF-IDF 이중언어 스코어링]].
## 🧩 추출된 패턴 (Extracted patterns)
- **단계별 fusionLog:** 각 단계가 `fusionLog.push(...)` 로 무엇을 했는지 기록 — 검색이 왜 그 결과를 냈는지 추적·디버깅 가능 [S1].
- **`??` 로 의미 있는 0 보존:** `brainFileLimit ?? 8` — Knowledge Mix "모델 지식만" 모드가 명시적 0 을 보내면 검색을 건너뛴다(`|| 8` 이면 0이 8로 둔갑) [S1].
- **하이브리드 blend(sparse+dense):** TF-IDF 점수를 top 값으로 정규화하고 임베딩 cosine 과 `(1-α)·sparse + α·dense` 로 혼합. 벡터 없는 문서는 순수 TF-IDF 유지 [S1].
- **per-file cap:** 한 문서가 상위 슬롯을 독식하지 않게 파일당 청크 수를 3개로 제한 [S1].
- **운영 로그 제외:** 세션/메모리/회사 로그 폴더는 "지식"이 아니므로 검색에서 제외(`isOperationalPath`) — 노이즈·토큰 낭비 차단 [S1].
- **레슨 카드 가산:** 짧고 신호 강한 레슨 카드는 top-limit 밖이어도 추가로 끌어오고 1.4× 가중 [S1].
## 📖 세부 내용 (Details)
### 4단계 흐름 (`retrieve`)
```
① Query Planning : tokenize → expandQuery(동의어 확장)
② Parallel Search : Brain(TF-IDF/하이브리드) + Memory 계층 + Medium-term(최근 세션)
③ Result Fusion : normalizeScores(소스별 0~1 + source boost) → actionability/hierarchical re-rank
④ Context Budget : selectWithinBudget(점수순, 토큰 한도 내) → lesson 청크 분리 추출
```
반환에는 선택/탈락 청크, 레슨 청크, 사용 토큰, fusionLog 가 포함된다 [S1].
### 하이브리드 검색의 스케일 함정 (주석에 기록된 실측 버그)
1. *모든* 후보를 maxTfidf 로 정규화해야 한다 — 벡터 있는 것만 0..1 로 줄이면 벡터 없는 후보의 raw 점수(≫1)가 상위를 독식해 blend 가 무효가 된다.
2. cosine 은 후보군 내 min-max 정규화 — 임베딩 모델은 무관 문서끼리도 cos 0.5~0.7 이 나와, 절대값 가산이 균일 노이즈로 sparse 정밀도를 흐린다 [S1].
### 성능 — mtime 인덱스
`getBrainTokenIndex` 는 파일 mtime 기준 영속 인덱스라, 변경 없는 파일은 재읽기·재토큰화하지 않는다. 큰 brain 에서 질의당 작업량이 O(전체 내용) → O(파일 수)로 떨어진다. 실제 디스크 읽기는 *선택된 파일* 의 발췌 추출 때만 발생 [S1].
### 평가 무결성
`rankBrainForEval` 은 프로덕션 `retrieve`*동일한 scoring 경로*(`searchBrainFiles`)를 재사용한다 — recall@k/MRR 측정이 실제 검색 동작을 반영하도록 [S1].
## ⚖️ 비교 및 선택 기준 (Comparison & decision criteria)
| 검색 방식 | 장점 | 단점 | 언제 |
|---|---|---|---|
| TF-IDF (sparse) | 키워드 정확, 인덱스 가벼움, 설명가능 | 의미 유사 놓침 | 임베딩 엔진 없을 때 기본 |
| 임베딩 (dense) | 의미 유사 포착 | 무관 문서도 높은 cos, 비용 | 동의·환언 많은 질의 |
| 하이브리드 blend | 둘의 장점 결합 | 스케일 정규화 까다로움 | 임베딩 가용 시 권장 |
| 섹션 청크 | 긴 문서 정밀도↑ | 인덱스 크기↑ | 다주제 장문 brain |
## ⚖️ 모순 및 업데이트 (Contradictions & updates)
- **청크 vs 파일 단위:** 섹션 청킹은 정밀도를 높이지만 회귀 위험이 있어 파일 단위 경로와 분리(`chunkLevelRetrieval` 플래그)해 격리했다 — 점진 도입 전략.
- **재가중의 위험:** actionability/hierarchical 가중이 과하면 키워드 정합이 약한 문서가 떠오를 수 있다. 그래서 정규화 *후, 예산 선택 전* 에 적용해 영향 범위를 통제한다.
## 🛠️ 적용 사례 (Applied in summary)
- `AstraAI/src/retrieval/index.ts` — RetrievalOrchestrator 의 retrieve/searchBrainFiles/searchBrainChunks/normalizeScores 전체 [S1].
- `AstraAI/src/retrieval/chunker.ts` — 헤딩 경계 섹션 청킹(순수 함수) [S2].
## 💻 코드 패턴 (Code patterns)
```typescript
// 1) 의미 있는 0 보존 — Knowledge Mix "모델 지식만" 모드 (src/retrieval/index.ts)
const brainFileLimit = options.brainFileLimit ?? 8; // ?? : 명시적 0 을 0으로 존중
const brainChunks = brainFileLimit > 0 ? this.searchBrainFiles(...) : [];
// 2) 소스별 정규화 + 우선순위 가중 (src/retrieval/index.ts)
for (const [, group] of groups) {
const maxScore = Math.max(...group.map(c => c.score), 0.001);
for (const c of group) c.score /= maxScore; // 소스 내 0~1 정규화
}
const sourceBoost = { 'procedural-memory': 0.95, 'brain-memory': 0.9, 'episodic-memory': 0.7 /* ... */ };
for (const c of chunks) { c.score *= (sourceBoost[c.source] ?? 0.5); if (c.metadata.isLesson) c.score *= 1.4; }
// 3) 하이브리드 blend — 모든 후보를 같은 스케일로 (src/retrieval/index.ts)
const sparse = s.score / maxTfidf;
s.score = cos === null ? sparse : (1 - alpha) * sparse + alpha * ((cos - minCos) / span);
```
## ✅ 검증 상태 및 신뢰도
- **상태:** draft
- **검증 단계:** applied
- **출처 신뢰도:** A
- **신뢰 점수:** 0.92
- **중복 검사 결과:** 신규 생성 (New discovery)
## 🔗 지식 그래프 (Knowledge Graph)
- **상위/루트:** [[AstraAI 아키텍처 개요]]
- **관련 개념:** [[TF-IDF 이중언어 스코어링]], [[5계층 메모리 시스템]], [[Agent 오케스트레이터 분해]]
- **참조 맥락:** 로컬 LLM 이 검색 증강·컨텍스트 조립·점수 융합 코드를 설계할 때 참조.
## 📚 출처 (Sources)
- [S1] AstraAI/src/retrieval/index.ts — RetrievalOrchestrator(4단계, fusion, 하이브리드, 예산, 평가)
- [S2] AstraAI/src/retrieval/chunker.ts — 섹션 청킹 순수 함수
## 📝 변경 이력 (Change history)
- 2026-06-13: AstraAI 코드 분석 기반 초안 생성.
@@ -0,0 +1,121 @@
---
id: tfidf-bilingual-scoring
title: "TF-IDF 이중언어 스코어링"
category: "AI_and_ML"
status: "draft"
verification_status: "applied"
canonical_id: ""
aliases: ["TF-IDF", "토크나이저", "tokenizer", "한국어 영어 토큰화", "동의어 확장", "검색 점수", "stop words", "섹션 청킹"]
duplicate_of: ""
source_trust_level: "A"
confidence_score: 0.92
created_at: 2026-06-13
updated_at: 2026-06-13
review_reason: ""
merge_history: []
tags: ["tfidf", "tokenizer", "search", "nlp", "korean", "astraai"]
raw_sources: ["AstraAI/src/retrieval/scoring.ts", "AstraAI/src/retrieval/chunker.ts"]
applied_in: ["AstraAI"]
github_commit: ""
---
# [[TF-IDF 이중언어 스코어링]]
## 🎯 한 줄 통찰 (One-line insight)
임베딩 엔진 없이도 쓸 수 있는 가벼운 검색의 핵심은 "좋은 토크나이저 + TF-IDF 가중"이며, AstraAI 는 **한국어/영어 혼합 토크나이저·불용어·동의어 확장·제목 가중·충돌 신호** 를 더해 단순 `includes()` 매칭을 넘어선 점수를 낸다 [S1].
## 🧠 핵심 개념 (Core concepts)
1. **TF-IDF:** 용어 빈도(TF, 문서 내 흔함) × 역문서빈도(IDF, 전체에서 희소함). 흔하면서 그 문서에만 자주 나오는 단어가 고득점 [S1].
2. **이중언어 토큰화:** 한글-영문 경계를 분리(`성능optimization``성능` `optimization`), 특수기호 보존(C++, C#, .net) [S1].
3. **불용어(Stop words):** 검색에 무의미한 단어(영/한 각각 집합)를 제거 [S1].
4. **동의어 확장:** 질의 토큰을 관련어로 확장(`성능``performance`, `optimization`, `최적화`) [S1].
5. **제목 가중:** 제목 일치는 본문보다 3배 가중(`TITLE_MULTIPLIER: 3.0`) [S1].
6. **토큰 캐시:** 같은 텍스트의 토큰화를 Map 으로 캐시(한도 초과 시 전체 clear) [S1].
## 🧩 추출된 패턴 (Extracted patterns)
- **중앙 설정 객체:** `SCORING_CONFIG` 에 불용어·동의어·임계값·가중치를 모아 한 곳에서 조정 [S1].
- **경계 분리 정규식:** `replace(/([a-z0-9]+)([가-힣]+)/gi, '$1 $2')` 로 언어 경계 분할, `split(/[^a-z0-9가-힣+#.-]+/g)` 로 특수기호(C++) 보존 [S1].
- **TF 계산 1회화:** `buildTermCounts` 로 문서당 용어 빈도 맵을 한 번 만들고 질의 용어마다 재사용 — O(질의×문서) 재스캔 회피 [S1].
- **IDF smoothing:** 문서 수가 적을 때도 안정적이도록 평활화 적용 [S1].
- **충돌 신호 탐지:** "반대/충돌/conflict/vs" 등 지표 단어 수로 conflictSeverity 산출 — 지식 충돌 가능 문서를 표시 [S1].
- **순수 함수 분리:** chunker/scoring 은 fs·네트워크 의존 없는 순수 함수라 단위 테스트·재현이 쉽다 [S2].
## 📖 세부 내용 (Details)
### 토크나이저 (가장 중요한 부품)
```typescript
const normalized = text.toLowerCase()
.replace(/[-]/g, '') // zero-width 제거
.replace(/[^\w\s가-힣_+#.-]/g, ' '); // 의미 없는 기호 → 공백
const splitText = normalized
.replace(/([a-z0-9]+)([가-힣]+)/gi, '$1 $2') // 영→한 경계 분리
.replace(/([가-힣]+)([a-z0-9]+)/gi, '$1 $2');// 한→영 경계 분리
const tokens = splitText.split(/[^a-z0-9가-힣+#.-]+/g) // C++, C#, .net 보존
.map(t => t.trim().replace(/[.,]$/g, ''))
.filter(t => /[가-힣]/.test(t) ? t.length >= 1 : t.length >= 2) // 한글 1자+, 영문 2자+
.filter(t => !STOP_EN.has(t) && !STOP_KO.has(t));
```
한국어는 한 글자도 의미를 가질 수 있어 1자 이상 허용, 영문은 2자 이상으로 노이즈를 줄인다 [S1].
### 동의어 확장
질의 `[성능]``[성능, performance, optimization, 최적화, speed]`. Set 으로 중복 제거 후 반환. brain 문서가 영어로, 질의가 한국어로 와도(또는 반대) 매칭되게 하는 양국어 다리 [S1].
### 섹션 청킹과의 결합
긴 문서를 통째 색인하면 5000자 다주제 문서가 흐릿한 한 단위가 되어 정밀도가 떨어진다. `chunker.ts` 가 헤딩(`#`~`######`) 경계로 섹션을 나누고, 짧은 섹션은 병합·긴 섹션은 문단 경계로 재분할한다. fenced code block(```) 안의 `#` 는 헤딩으로 보지 않는다. 헤딩 breadcrumb 을 보존해 청크가 문맥을 잃지 않게 한다 [S2].
## ⚖️ 모순 및 업데이트 (Contradictions & updates)
- **TF-IDF 의 한계:** 어휘가 다르면(동의어 사전에 없는 환언) 못 잡는다. 그래서 임베딩 하이브리드로 보완한다 → [[RAG 검색 파이프라인]].
- **동의어 사전의 유지보수:** 수작업 사전이라 도메인이 늘면 누락이 생긴다. 핵심 도메인 용어 위주로 관리하는 절충.
- **형태소 분석 부재:** 한국어 조사/어미를 정밀 분해하지 않는다(경량 우선). 정밀도가 더 필요하면 형태소 분석기 도입 여지.
## 🛠️ 적용 사례 (Applied in summary)
- `AstraAI/src/retrieval/scoring.ts` — tokenize/expandQuery/TF-IDF/충돌 탐지 [S1].
- `AstraAI/src/retrieval/chunker.ts` — splitIntoSections 섹션 청킹 [S2].
## 💻 코드 패턴 (Code patterns)
```typescript
// 1) 중앙 설정 객체 — 가중치/임계값 한 곳에서 (src/retrieval/scoring.ts)
const SCORING_CONFIG = {
STOP_WORDS_EN: new Set(['the','a','and',/* ... */]),
STOP_WORDS_KO: new Set(['그리고','그런데',/* ... */]),
SYNONYM_DATA: [['성능', ['performance','optimization','최적화','speed']], /* ... */],
TITLE_MULTIPLIER: 3.0,
GLOBAL_CACHE_LIMIT: 2000,
};
// 2) TF 계산 1회화 (src/retrieval/scoring.ts)
function buildTermCounts(tokens: string[]): Map<string, number> {
const counts = new Map<string, number>();
for (const t of tokens) counts.set(t, (counts.get(t) || 0) + 1);
return counts; // 질의 용어마다 재스캔 대신 이 맵을 조회
}
// 3) 동의어 확장 (src/retrieval/scoring.ts)
export function expandQuery(tokens: string[]): string[] {
const expanded = new Set(tokens);
for (const t of tokens) (synonymMap.get(t) ?? []).forEach(s => expanded.add(s));
return Array.from(expanded);
}
// 4) 헤딩 경계 섹션 청킹 — fence 안의 # 무시 (src/retrieval/chunker.ts)
const fence = line.trimStart().startsWith('```'); if (fence) inFence = !inFence;
const m = !inFence ? line.match(HEADING_RE) : null; // 코드블록 내 #는 헤딩 아님
```
## ✅ 검증 상태 및 신뢰도
- **상태:** draft
- **검증 단계:** applied
- **출처 신뢰도:** A
- **신뢰 점수:** 0.92
- **중복 검사 결과:** 신규 생성 (New discovery)
## 🔗 지식 그래프 (Knowledge Graph)
- **상위/루트:** [[AstraAI 아키텍처 개요]]
- **관련 개념:** [[RAG 검색 파이프라인]], [[5계층 메모리 시스템]], [[TypeScript 기초와 타입 시스템]]
- **참조 맥락:** 로컬 LLM 이 가벼운 텍스트 검색·토큰화·점수 함수를 작성할 때(특히 한/영 혼용) 참조.
## 📚 출처 (Sources)
- [S1] AstraAI/src/retrieval/scoring.ts — 토크나이저, TF-IDF, 동의어, 불용어, 충돌 탐지, 캐시
- [S2] AstraAI/src/retrieval/chunker.ts — 섹션 청킹(순수 함수)
## 📝 변경 이력 (Change history)
- 2026-06-13: AstraAI 코드 분석 기반 초안 생성.