feat: implement next-gen vectorized engine, async architecture, and modernization roadmap v2.32.0
This commit is contained in:
+40
-106
@@ -1,19 +1,14 @@
|
||||
import * as http from 'http';
|
||||
import * as fs from 'fs';
|
||||
import * as path from 'path';
|
||||
// axios removed
|
||||
import {
|
||||
_getBrainDir,
|
||||
_isBrainDirExplicitlySet,
|
||||
findBrainFiles,
|
||||
buildApiUrl,
|
||||
logError,
|
||||
logInfo,
|
||||
logWarn,
|
||||
resolveEngine,
|
||||
summarizeText
|
||||
} from './utils';
|
||||
import { getConfig } from './config';
|
||||
import { IAIService, IBrainService, AIService, BrainService } from './core/services';
|
||||
|
||||
export interface BridgeInterface {
|
||||
injectSystemMessage(msg: string): void;
|
||||
@@ -23,10 +18,25 @@ export interface BridgeInterface {
|
||||
findBrainFiles(dir: string): string[];
|
||||
}
|
||||
|
||||
/**
|
||||
* BridgeServer:
|
||||
* 외부 툴(EZER, A.U 등)과 G1nation 확장을 연결하는 통신 브릿지.
|
||||
* 서비스 레이어(AIService, BrainService)를 통해 비즈니스 로직을 분리하여 유지보수성을 극대화했습니다.
|
||||
*/
|
||||
export class BridgeServer {
|
||||
private server: http.Server | null = null;
|
||||
private aiService: IAIService;
|
||||
private brainService: IBrainService;
|
||||
|
||||
constructor(private provider: BridgeInterface) {}
|
||||
constructor(
|
||||
private provider: BridgeInterface,
|
||||
aiService?: IAIService,
|
||||
brainService?: IBrainService
|
||||
) {
|
||||
// 의존성 주입 (DIP): 기본값 제공 및 외부 주입 허용
|
||||
this.aiService = aiService || new AIService();
|
||||
this.brainService = brainService || new BrainService();
|
||||
}
|
||||
|
||||
public start(port: number = 4825) {
|
||||
this.server = http.createServer((req, res) => {
|
||||
@@ -41,16 +51,18 @@ export class BridgeServer {
|
||||
}
|
||||
|
||||
const url = req.url || '';
|
||||
const method = req.method;
|
||||
|
||||
if (req.method === 'GET' && url === '/ping') {
|
||||
// 라우팅 로직 (SRP에 따라 비즈니스 로직은 서비스로 위임)
|
||||
if (method === 'GET' && url === '/ping') {
|
||||
this.handlePing(res);
|
||||
} else if (req.method === 'POST' && url === '/api/exam') {
|
||||
} else if (method === 'POST' && url === '/api/exam') {
|
||||
this.handlePost(req, res, this.processExam.bind(this));
|
||||
} else if (req.method === 'POST' && url === '/api/evaluate') {
|
||||
} else if (method === 'POST' && url === '/api/evaluate') {
|
||||
this.handlePost(req, res, this.processEvaluate.bind(this));
|
||||
} else if (req.method === 'GET' && url === '/api/evaluate-history') {
|
||||
} else if (method === 'GET' && url === '/api/evaluate-history') {
|
||||
this.processEvaluateHistory(res);
|
||||
} else if (req.method === 'POST' && url === '/api/brain-inject') {
|
||||
} else if (method === 'POST' && url === '/api/brain-inject') {
|
||||
this.handlePost(req, res, this.processBrainInject.bind(this));
|
||||
} else {
|
||||
res.writeHead(404);
|
||||
@@ -60,9 +72,9 @@ export class BridgeServer {
|
||||
|
||||
this.server.on('error', (err: any) => {
|
||||
if (err.code === 'EADDRINUSE') {
|
||||
logWarn(`Bridge server: Port ${port} is already in use. Another instance might be running.`);
|
||||
logError(`🚫 Bridge Port ${port} in use. Connection with EZER/A.U might fail.`);
|
||||
} else {
|
||||
logError('Bridge server error:', err);
|
||||
logError(`Bridge server error:`, err);
|
||||
}
|
||||
});
|
||||
|
||||
@@ -77,7 +89,6 @@ export class BridgeServer {
|
||||
res.writeHead(200, { 'Content-Type': 'application/json' });
|
||||
res.end(JSON.stringify({
|
||||
status: 'ok',
|
||||
msg: 'G1nation Bridge Ready',
|
||||
config: getConfig(),
|
||||
brain: { fileCount: brainCount, enabled: this.provider.brainEnabled }
|
||||
}));
|
||||
@@ -91,7 +102,7 @@ export class BridgeServer {
|
||||
const parsed = JSON.parse(body);
|
||||
await processor(parsed, res);
|
||||
} catch (e: any) {
|
||||
logError('Bridge request failed.', { url: req.url, method: req.method, body: summarizeText(body), error: e?.message || String(e) });
|
||||
logError('Bridge request processor failed.', { url: req.url, error: e.message });
|
||||
res.writeHead(500, { 'Content-Type': 'application/json' });
|
||||
res.end(JSON.stringify({ error: e.message }));
|
||||
}
|
||||
@@ -99,21 +110,18 @@ export class BridgeServer {
|
||||
}
|
||||
|
||||
private async processExam(data: any, res: http.ServerResponse) {
|
||||
const prompt = data.prompt || 'Automatic Prompt Received';
|
||||
this.provider.sendPromptFromExtension(`[Bridge Input] ${prompt}`);
|
||||
const result = await this.callAI(prompt);
|
||||
const prompt = data.prompt || 'Automatic Prompt';
|
||||
this.provider.sendPromptFromExtension(`[Bridge] ${prompt}`);
|
||||
const result = await this.aiService.call(prompt);
|
||||
res.writeHead(200, { 'Content-Type': 'application/json' });
|
||||
res.end(JSON.stringify({ success: true, rawOutput: result }));
|
||||
}
|
||||
|
||||
private async processEvaluate(data: any, res: http.ServerResponse) {
|
||||
const prompt = data.prompt || '';
|
||||
this.provider.injectSystemMessage(`**[A.U Evaluation Started]**\nAnalyzing input: _"${prompt.substring(0, 60)}..."_`);
|
||||
|
||||
const evaluationPrompt = `[EVALUATION REQUEST]\nPlease evaluate the following input and provide a score/reasoning:\n\n${prompt}`;
|
||||
const result = await this.callAI(evaluationPrompt);
|
||||
|
||||
this.provider.injectSystemMessage(`**[Evaluation Complete]**\n${result.substring(0, 300)}...`);
|
||||
this.provider.injectSystemMessage(`**[A.U Evaluation]** Analyzing input...`);
|
||||
const result = await this.aiService.call(`[EVALUATE] ${prompt}`);
|
||||
this.provider.injectSystemMessage(`**[Result]** ${summarizeText(result, 200)}`);
|
||||
res.writeHead(200, { 'Content-Type': 'application/json' });
|
||||
res.end(JSON.stringify({ rawOutput: result }));
|
||||
}
|
||||
@@ -122,96 +130,22 @@ export class BridgeServer {
|
||||
const historyText = this.provider.getHistoryText();
|
||||
if (!historyText || historyText.length < 50) {
|
||||
res.writeHead(400, { 'Content-Type': 'application/json' });
|
||||
res.end(JSON.stringify({ error: "Insufficient chat history for evaluation." }));
|
||||
res.end(JSON.stringify({ error: "Insufficient history" }));
|
||||
return;
|
||||
}
|
||||
|
||||
this.provider.injectSystemMessage(`**[History Evaluation]** Analyzing conversation flow...`);
|
||||
const historyPrompt = `Analyze this conversation history and return a JSON score for Math, Logic, Creative, and Code (0-100):\n\n${historyText.slice(-6000)}`;
|
||||
const result = await this.callAI(historyPrompt);
|
||||
|
||||
const result = await this.aiService.call(`Analyze chat history for metrics (JSON):\n${historyText.slice(-6000)}`);
|
||||
const jsonMatch = result.match(/\{[\s\S]*?\}/);
|
||||
if (jsonMatch) {
|
||||
res.writeHead(200, { 'Content-Type': 'application/json' });
|
||||
res.end(jsonMatch[0]);
|
||||
} else {
|
||||
res.writeHead(500, { 'Content-Type': 'application/json' });
|
||||
res.end(JSON.stringify({ error: "Failed to parse evaluation JSON", raw: result }));
|
||||
}
|
||||
res.writeHead(jsonMatch ? 200 : 500, { 'Content-Type': 'application/json' });
|
||||
res.end(jsonMatch ? jsonMatch[0] : JSON.stringify({ error: "Parse failed", raw: result }));
|
||||
}
|
||||
|
||||
private async processBrainInject(data: any, res: http.ServerResponse) {
|
||||
const { title, markdown, prompt } = data;
|
||||
let 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);
|
||||
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');
|
||||
|
||||
this.provider.injectSystemMessage(`**[Brain Inject]** Knowledge captured: ${title}`);
|
||||
|
||||
const result = await this.callAI(prompt || `Analyze this new knowledge: ${title}`);
|
||||
await this.brainService.inject(title, markdown);
|
||||
this.provider.injectSystemMessage(`**[Brain]** Knowledge captured: ${title}`);
|
||||
const result = await this.aiService.call(prompt || `Analyze: ${title}`);
|
||||
res.writeHead(200, { 'Content-Type': 'application/json' });
|
||||
res.end(JSON.stringify({ success: true, rawOutput: result }));
|
||||
}
|
||||
|
||||
private async callAI(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 = engine === 'lmstudio'
|
||||
? {
|
||||
model: config.defaultModel,
|
||||
messages: [{ role: 'user', content: prompt }],
|
||||
stream: false
|
||||
}
|
||||
: {
|
||||
model: config.defaultModel,
|
||||
messages: [{ role: 'user', content: prompt }],
|
||||
stream: false
|
||||
};
|
||||
|
||||
try {
|
||||
logInfo('Bridge AI request started.', { engine, apiUrl, model: config.defaultModel });
|
||||
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(`Bridge AI call failed: ${res.status} ${summarizeText(rawText, 250)}`);
|
||||
logError('Bridge AI request returned non-OK status.', { engine, apiUrl, status: res.status, body: summarizeText(rawText, 500) });
|
||||
continue;
|
||||
}
|
||||
|
||||
const data = rawText ? JSON.parse(rawText) as any : {};
|
||||
const content = engine === 'lmstudio'
|
||||
? (data.choices?.[0]?.message?.content || '')
|
||||
: (data.message?.content || data.response || '');
|
||||
|
||||
logInfo('Bridge AI request succeeded.', { engine, apiUrl, responsePreview: summarizeText(content, 200) });
|
||||
return content;
|
||||
} catch (error: any) {
|
||||
lastError = error instanceof Error ? error : new Error(String(error));
|
||||
logError('Bridge AI request failed.', { engine, apiUrl, error: lastError.message });
|
||||
}
|
||||
}
|
||||
|
||||
throw lastError || new Error('Bridge AI call failed.');
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user