feat: ConnectAI structural hardening and retrieval precision improvements

This commit is contained in:
g1nation
2026-05-05 21:37:45 +09:00
parent c2f17cfb03
commit 466e9e4d5f
17 changed files with 424 additions and 160 deletions
+3 -2
View File
@@ -21,6 +21,7 @@ import {
} from '../src/lib/engine';
import * as fs from 'fs';
import * as path from 'path';
import { createHash } from 'crypto';
// ─── Setup ───
const getBaseDir = () => {
@@ -622,12 +623,12 @@ describe('Concurrency & Stress Tests', () => {
// resilientExecute의 fallback 로직이 allowFallback 옵션에 반응하는지 테스트
const options: AgentExecuteOptions = {
config: { allowFallback: true },
config: { allowFallback: true, isSamePrompt: true },
priorResults: { previousValidData: expectedFallback }
};
const result = await (engine as any).resilientExecute(
new MissionState('fallback_test'),
new MissionState('fallback_test', createHash('sha256').update(testPrompt).digest('hex').slice(0, 16)),
failingAgent,
'FailingAgent',
testPrompt,
+84
View File
@@ -0,0 +1,84 @@
import {
AgentEngine,
IAgent,
ErrorClassifier,
ErrorType,
MissionState
} from '../src/lib/engine';
import { AgentDataValidator } from '../src/lib/diagnostics';
import * as path from 'path';
import * as fs from 'fs';
describe('ConnectAI Resilience v4.0 Validation', () => {
describe('Enhanced Error Classification', () => {
test('GPU OOM (out of memory) should be classified as TRANSIENT', () => {
const error = new Error('Ollama error: GPU out of memory, failed to allocate weights');
const result = ErrorClassifier.classify(error);
expect(result.type).toBe(ErrorType.TRANSIENT);
expect(result.rule.action).toBe('retry');
});
test('Context length exceeded should be classified as PERMANENT', () => {
const error = new Error('Validation failed: context_length_exceeded for model gemini-pro');
const result = ErrorClassifier.classify(error);
expect(result.type).toBe(ErrorType.PERMANENT);
expect(result.rule.action).toBe('fail_with_message');
});
test('Safety filter triggers should be classified as PERMANENT', () => {
const error = new Error('Response blocked by safety_filter: harmful content detected');
const result = ErrorClassifier.classify(error);
expect(result.type).toBe(ErrorType.PERMANENT);
});
test('500 Internal Server Error should be classified as TRANSIENT', () => {
const error = new Error('HTTP 500: Internal Server Error');
const result = ErrorClassifier.classify(error);
expect(result.type).toBe(ErrorType.TRANSIENT);
});
});
describe('Safe Pre-Failure Audit', () => {
class MockPermanentOOMAgent implements IAgent {
async execute(): Promise<string> {
// 이 에러는 패턴상 PERMANENT로 분류되도록 유도 (테스트용)
throw new Error('404: model not found');
}
}
test('Permanent error should trigger audit without crashing', async () => {
const engine = new AgentEngine(
new MockPermanentOOMAgent(),
{} as IAgent,
{} as IAgent
);
const state = new MissionState('audit_test_mission');
const input = 'This is a test input that should be audited upon failure.';
// audit 메서드가 에러를 던지지 않는지 확인
const auditResult = AgentDataValidator.audit('Planner', input);
expect(auditResult).toHaveProperty('score');
expect(auditResult).toHaveProperty('issues');
// 실제 resilientExecute 흐름에서 에러가 전파되는지 확인
await expect((engine as any).resilientExecute(
state,
new MockPermanentOOMAgent(),
'TestAgent',
input,
'context',
new AbortController().signal,
() => {}
)).rejects.toThrow(/TestAgent/);
});
test('Audit should handle empty input gracefully', () => {
const result = AgentDataValidator.audit('Tester', '');
expect(result.score).toBe(0);
expect(result.issues).toContain('데이터가 완전히 비어 있음');
});
});
});
+10 -2
View File
@@ -77,10 +77,18 @@ describe('Scoring Engine Unit Tests (v2.72.0)', () => {
// Language boundary split should handle alternating chars
expect(tokens).toContain('astra');
expect(tokens).toContain('v2');
expect(tokens).toContain('v2.0'); // [Structural Fix] 점(.)이 포함된 버전 번호 보존 확인
expect(tokens).toContain('한');
expect(tokens).toContain('글');
// Symbols should be filtered out
// [New Feature] 기술 기호 보존 확인 (C++, C#, .net)
const techText = 'I love C++ and C# programming on .net platform.';
const techTokens = tokenize(techText);
expect(techTokens).toContain('c++');
expect(techTokens).toContain('c#');
expect(techTokens).toContain('.net');
// Symbols should be filtered out (except the preserved ones)
expect(tokens.some(t => /^[!@#$]+$/.test(t))).toBe(false);
});