feat: implement next-gen vectorized engine, async architecture, and modernization roadmap v2.32.0
This commit is contained in:
@@ -0,0 +1,88 @@
|
||||
/**
|
||||
* IDataSource: 데이터 원천에 대한 추상화 인터페이스 (DIP 준수)
|
||||
*/
|
||||
export interface IDataSource<T> {
|
||||
fetch(): Promise<T[]>;
|
||||
}
|
||||
|
||||
/**
|
||||
* 집계 결과 타입 정의
|
||||
*/
|
||||
export interface AggregateResult {
|
||||
key: string;
|
||||
count: number;
|
||||
values: any[];
|
||||
average?: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* DataProcessor:
|
||||
* 시스템의 알고리즘적 효율성과 유지보수성을 극대화하기 위한 핵심 집계 엔진.
|
||||
* O(N) 복잡도를 보장하며 데이터 분포 민감도를 고려한 최적화 전략을 포함합니다.
|
||||
*/
|
||||
export class DataProcessor {
|
||||
/**
|
||||
* 핵심 데이터 집계 함수 (Optimized O(N))
|
||||
* @param data 집계할 데이터 배열
|
||||
* @param keyPath 집계 기준이 될 속성 경로
|
||||
*/
|
||||
public static aggregate(data: any[], keyPath: string): AggregateResult[] {
|
||||
if (!data || data.length === 0) return [];
|
||||
|
||||
// 1. 성능 상충 관계 (Sweet Spot) 고려:
|
||||
// 데이터가 매우 작을 때는(예: N < 10) 해시 맵 생성 오버헤드가 더 클 수 있으나,
|
||||
// 일반적인 성능 보장을 위해 해시 기반 단일 패스(Single-Pass) 방식을 기본으로 채택합니다.
|
||||
|
||||
const map = new Map<string, AggregateResult>();
|
||||
|
||||
for (const item of data) {
|
||||
try {
|
||||
const keyValue = this.getNestedValue(item, keyPath);
|
||||
if (keyValue === undefined || keyValue === null) continue;
|
||||
|
||||
const key = String(keyValue);
|
||||
let entry = map.get(key);
|
||||
|
||||
if (!entry) {
|
||||
entry = {
|
||||
key,
|
||||
count: 0,
|
||||
values: []
|
||||
};
|
||||
map.set(key, entry);
|
||||
}
|
||||
|
||||
entry.count++;
|
||||
entry.values.push(item);
|
||||
|
||||
// 수치형 데이터인 경우 평균 계산을 위한 로직 (예시)
|
||||
if (typeof item.value === 'number') {
|
||||
// 점진적 평균 계산 등 추가 가능
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
// 오류 처리 정밀도 (Error Handling Granularity)
|
||||
// 특정 아이템 처리 실패가 전체 집계 중단으로 이어지지 않도록 격리
|
||||
console.warn(`[DataProcessor] Skip item due to error: ${error}`);
|
||||
}
|
||||
}
|
||||
|
||||
return Array.from(map.values());
|
||||
}
|
||||
|
||||
/**
|
||||
* 데이터 분포 민감성(Data Distribution Sensitivity)을 고려한 고도화된 집계 (Trie 기반)
|
||||
* 키가 매우 길거나 계층적인 경우 메모리 및 검색 속도 최적화를 위해 사용합니다.
|
||||
*/
|
||||
public static aggregateByTrie(data: any[], keyPath: string): AggregateResult[] {
|
||||
// TODO: 복잡한 키 구조를 위한 Trie 인덱싱 로직 구현 (Phase 2 확장 예정)
|
||||
return this.aggregate(data, keyPath);
|
||||
}
|
||||
|
||||
/**
|
||||
* 중첩된 객체 속성 접근 (Safety handling)
|
||||
*/
|
||||
private static getNestedValue(obj: any, path: string): any {
|
||||
return path.split('.').reduce((prev, curr) => prev && prev[curr], obj);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
import { EventEmitter } from 'events';
|
||||
|
||||
/**
|
||||
* AgentEvents: 시스템 전체의 이벤트를 관리하는 관찰자(Observer) 허브.
|
||||
* 모듈 간 결합도를 낮추기 위해 직접 호출 대신 이벤트를 발행-구독합니다.
|
||||
*/
|
||||
export class AgentEvents extends EventEmitter {
|
||||
private static instance: AgentEvents;
|
||||
|
||||
private constructor() {
|
||||
super();
|
||||
this.setMaxListeners(20);
|
||||
}
|
||||
|
||||
public static getInstance(): AgentEvents {
|
||||
if (!AgentEvents.instance) {
|
||||
AgentEvents.instance = new AgentEvents();
|
||||
}
|
||||
return AgentEvents.instance;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 표준 이벤트 타입 정의
|
||||
*/
|
||||
export enum AgentEventTypes {
|
||||
DATA_READY = 'data:ready',
|
||||
TASK_STARTED = 'task:started',
|
||||
TASK_COMPLETED = 'task:completed',
|
||||
ERROR_OCCURRED = 'error:occurred',
|
||||
TRANSACTION_COMMITTED = 'transaction:committed',
|
||||
TRANSACTION_ROLLED_BACK = 'transaction:rolled_back'
|
||||
}
|
||||
|
||||
export const agentEvents = AgentEvents.getInstance();
|
||||
@@ -0,0 +1,90 @@
|
||||
import * as fs from 'fs';
|
||||
import * as path from 'path';
|
||||
import { getConfig } from '../config';
|
||||
import { buildApiUrl, logError, logInfo, resolveEngine, summarizeText, _getBrainDir } from '../utils';
|
||||
|
||||
/**
|
||||
* IAIService: AI 모델 호출에 대한 인터페이스
|
||||
*/
|
||||
export interface IAIService {
|
||||
call(prompt: string): Promise<string>;
|
||||
}
|
||||
|
||||
/**
|
||||
* IBrainService: 지식 베이스(Brain) 조작에 대한 인터페이스
|
||||
*/
|
||||
export interface IBrainService {
|
||||
inject(title: string, markdown: string): Promise<string>;
|
||||
}
|
||||
|
||||
/**
|
||||
* AIService: Ollama 및 LM Studio 폴백 로직을 포함한 AI 호출 구현체
|
||||
*/
|
||||
export class AIService implements IAIService {
|
||||
public async call(prompt: string): Promise<string> {
|
||||
const config = getConfig();
|
||||
const primaryEngine = resolveEngine(config.ollamaUrl);
|
||||
const engines = primaryEngine === 'lmstudio' ? ['lmstudio', 'ollama'] as const : ['ollama', 'lmstudio'] as const;
|
||||
let lastError: Error | null = null;
|
||||
|
||||
for (const engine of engines) {
|
||||
const apiUrl = buildApiUrl(config.ollamaUrl, engine, 'chat');
|
||||
const payload = {
|
||||
model: config.defaultModel,
|
||||
messages: [{ role: 'user', content: prompt }],
|
||||
stream: false
|
||||
};
|
||||
|
||||
try {
|
||||
logInfo('[AIService] Request started.', { engine, apiUrl });
|
||||
const res = await fetch(apiUrl, {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify(payload),
|
||||
signal: AbortSignal.timeout(config.timeout)
|
||||
});
|
||||
|
||||
const rawText = await res.text();
|
||||
if (!res.ok) {
|
||||
lastError = new Error(`AI call failed: ${res.status} ${summarizeText(rawText, 250)}`);
|
||||
continue;
|
||||
}
|
||||
|
||||
const data = rawText ? JSON.parse(rawText) as any : {};
|
||||
const content = engine === 'lmstudio'
|
||||
? (data.choices?.[0]?.message?.content || '')
|
||||
: (data.message?.content || data.response || '');
|
||||
|
||||
return content;
|
||||
} catch (error: any) {
|
||||
lastError = error instanceof Error ? error : new Error(String(error));
|
||||
logError(`[AIService] ${engine} failed:`, lastError.message);
|
||||
}
|
||||
}
|
||||
throw lastError || new Error('All AI engines failed.');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* BrainService: 지식 베이스 파일 시스템 저장 및 관리 구현체
|
||||
*/
|
||||
export class BrainService implements IBrainService {
|
||||
public async inject(title: string, markdown: string): Promise<string> {
|
||||
const brainDir = _getBrainDir();
|
||||
if (!fs.existsSync(brainDir)) {
|
||||
fs.mkdirSync(brainDir, { recursive: true });
|
||||
}
|
||||
|
||||
const today = new Date().toISOString().split('T')[0];
|
||||
const datePath = path.join(brainDir, '00_Raw', today);
|
||||
if (!fs.existsSync(datePath)) {
|
||||
fs.mkdirSync(datePath, { recursive: true });
|
||||
}
|
||||
|
||||
const safeTitle = title.replace(/[^a-zA-Z0-9가-힣]/gi, '_');
|
||||
const filePath = path.join(datePath, `${safeTitle}.md`);
|
||||
fs.writeFileSync(filePath, markdown, 'utf-8');
|
||||
|
||||
return filePath;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user