Files
connectai/tests/integration_retrieval.test.ts
T
g1nation ded3eea7ce feat: v2.2.74 → v2.2.82 — chunked writer + 코드 리뷰 패치 + /youtube 확장
주요 변경:

[chunked writer 아키텍처 (v2.2.74~v2.2.75)]
- 5-stage 다중 에이전트(planner/researcher/reflector/writer/synthesizer)
  파이프라인 제거 → 단일 ChunkedWriter 의 outline → section[N] → polish
  3-step 으로 교체. 본문 분석에서 추상화 손실 / 토큰 폭증 문제 해소
- 답변 길이 자동 분기: 짧은 prompt 는 fast-path direct 1회 호출,
  본문 분석은 chunked. outline 빈 배열도 direct 폴백

[코드 리뷰 9개 항목 일괄 패치 (v2.2.76)]
- /research polling hang 방어 (heartbeat + status 정규화 + 연속 실패 abort)
- 회사 모드 dispatcher abort 신호를 AIService.chat 까지 전달
- bridgeFetch 에 onHeartbeat 콜백 도입 (slow endpoint 사용자 친화적)
- dead code 정리: reflectionPersister.ts 제거 + enableReflection 등 좀비 config 키
- parseOutline 의 empty vs fallback reason 명시적 분리
- chatHandlers 의 회사 모드 케이스 ~325줄을 src/sidebar/companyHandlers.ts 로 분리
- Intent Alignment 라운드 한도 도달 시 smart 모드 자동 진행
- LM Studio doSwitch unload 실패 시 currentModel 정리 + load 강행
- retrieval informationDensity → queryCoverage 정합화

[/youtube 채널 지원 (v2.2.77~v2.2.82)]
- 채널/플레이리스트 URL 자동 감지 + n:N 으로 영상 개수 지정 (최대 50)
- 채널 루트 URL 에 /videos 탭 자동 append (yt-dlp enumeration 정상화)
- 영상별 순차 처리 (queue 패턴) + i/N 진행 표시 + 마지막 통계 요약
- mode:info / mode:benchmark / mode:both 분석 모드 분기
  - info: 영상 내용을 지식 카드로 추출 (튜토리얼·강의·뉴스용)
  - benchmark: 4-렌즈 대본 역기획서 (콘텐츠 제작 벤치마크용)
  - both: 둘 다 (기본)
  - bare keyword 도 허용: /youtube <url> n:1 info
- bridge 에러 메시지 [object Object] 깨짐 수정 (구조화 에러 추출)
- "패키지 없음" 등 환경 의존성 에러에 자동 가이드 첨부

[Astra: Setup Datacollect Dependencies 명령 추가 (v2.2.80)]
- Python 자동 감지 + yt-dlp / youtube-transcript-api 자동 설치
- macOS PEP 668 환경 자동 폴백 (--user --break-system-packages)
- /youtube 등에서 패키지 미설치 감지 시 "Install Now" 버튼 notification

[테스트]
- tests/agentEngine.test.ts 를 chunked flow 에 맞춰 전체 재작성
- tests/resilience_stress.test.ts Scenario B/D 를 role-aware mock 으로 갱신
- 399/399 통과

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-23 23:13:21 +09:00

92 lines
3.9 KiB
TypeScript

import { RetrievalOrchestrator } from '../src/retrieval/index';
import * as fs from 'fs';
import * as utils from '../src/utils';
// Mocking dependencies
jest.mock('fs');
jest.mock('../src/utils');
describe('Retrieval Orchestrator Phase 5 Integration Tests', () => {
let orchestrator: RetrievalOrchestrator;
const mockBrainPath = '/mock/brain';
beforeEach(() => {
jest.clearAllMocks();
orchestrator = new RetrievalOrchestrator();
});
test('End-to-End Brain Search: should populate advanced scoring metadata in results', () => {
// 1. Mock Brain Files
const mockFiles = [
'/mock/brain/doc1.md',
'/mock/brain/doc2.md'
];
(utils.findBrainFiles as jest.Mock).mockReturnValue(mockFiles);
// 2. Mock File Content
const fileContents: Record<string, string> = {
'/mock/brain/doc1.md': 'Astra 성능 최적화 전략에 대한 문서입니다.',
'/mock/brain/doc2.md': '이 설계는 기존 아키텍처와 충돌하며 오류가 많고 논란이 있는 반대 의견입니다.'
};
(fs.readFileSync as jest.Mock).mockImplementation((path: string) => fileContents[path]);
(fs.statSync as jest.Mock).mockReturnValue({ mtimeMs: Date.now() });
// 3. Perform Retrieval
const brain = { localBrainPath: mockBrainPath };
const result = orchestrator.retrieve('최적화 충돌', {
brain: brain as any,
memoryManager: {
getLongTermMemory: () => ({ buildContext: () => null }),
getProjectMemory: () => ({ buildContext: () => null }),
getProceduralMemory: () => ({ buildContext: () => null }),
getEpisodicMemory: () => ({ buildContext: () => null })
} as any,
contextBudget: { totalBudget: 2000 }
});
// 4. Verify Intelligence Metadata
expect(result.selectedChunks.length).toBeGreaterThan(0);
// Find doc2 (the conflicting one)
const conflictChunk = result.selectedChunks.find(c => c.title.includes('doc2'));
expect(conflictChunk).toBeDefined();
if (conflictChunk) {
expect(conflictChunk.metadata.conflictDetected).toBe(true);
expect(conflictChunk.metadata.conflictSeverity).toBe('HIGH'); // '충돌', '오류', '논란', '반대' -> 4 indicators
}
// Find doc1 (the dense one)
const denseChunk = result.selectedChunks.find(c => c.title.includes('doc1'));
expect(denseChunk).toBeDefined();
if (denseChunk) {
expect(denseChunk.metadata.queryCoverage).toBeGreaterThan(0);
expect(denseChunk.metadata.conflictDetected).toBe(false);
}
// 5. Verify Assembled Context String
const contextString = orchestrator.buildContextString(result);
expect(contextString).toContain('[⚠️ CONFLICT: HIGH]');
expect(contextString).toContain('(Coverage:');
});
test('Score Normalization: should normalize scores across brain sources', () => {
(utils.findBrainFiles as jest.Mock).mockReturnValue(['/mock/brain/test.md']);
(fs.readFileSync as jest.Mock).mockReturnValue('테스트 내용');
(fs.statSync as jest.Mock).mockReturnValue({ mtimeMs: Date.now() });
const result = orchestrator.retrieve('테스트', {
brain: { localBrainPath: mockBrainPath } as any,
memoryManager: {
getLongTermMemory: () => ({ buildContext: () => null }),
getProjectMemory: () => ({ buildContext: () => null }),
getProceduralMemory: () => ({ buildContext: () => null }),
getEpisodicMemory: () => ({ buildContext: () => null })
} as any
});
// Scores should be boosted by source priority (brain-memory boost is 0.9)
const chunk = result.selectedChunks[0];
expect(chunk.score).toBeCloseTo(0.9, 1);
});
});