import { logInfo } from '../utils'; export class AgentDataValidator { /** * 에이전트 간 핸드오프(Handoff) 시 데이터 무결성을 검증하고 품질 점수를 반환합니다. */ public static validateHandoff(stage: string, data: string): { score: number, conflictRisk: number } { if (!data || data.trim().length === 0) { throw new Error(`[IntegrityError] 데이터 누락: ${stage} 에이전트의 출력이 비어 있습니다.`); } const minLength = process.env.NODE_ENV === 'test' ? 5 : 50; switch (stage.toLowerCase()) { case 'planner': if (data.length < minLength) { throw new Error(`[IntegrityError] Planner 출력 데이터가 비정상적으로 짧습니다 (${data.length} chars). 계획 누락 의심.`); } break; case 'researcher': if (data.length < minLength) { throw new Error(`[IntegrityError] Researcher 출력 데이터가 부족합니다 (${data.length} chars). 근거 데이터 누락 의심.`); } break; case 'writer': if (data.length < minLength) { throw new Error(`[IntegrityError] Writer 결과물이 불완전합니다 (${data.length} chars). 최종 보고서 작성 실패 의심.`); } break; case 'outline': // Outline 응답은 JSON 배열 — 빈 배열 `[]` (single-pass 신호) 도 유효. // 길이 검증을 우회하고, 형식 검사는 호출자(`parseOutline`) 가 별도로 한다. break; case 'direct': // 짧은 질문에 대한 짧은 답을 허용. 한 문장(≈ 10자) 까지 OK. if (data.length < 4) { throw new Error(`[IntegrityError] Direct 답변이 비정상적으로 짧습니다 (${data.length} chars).`); } break; default: if (data.length < 10) { throw new Error(`[IntegrityError] ${stage} 에이전트로부터 유효한 응답을 받지 못했습니다.`); } break; } // 품질 점수화 (Quality Scoring) const score = QualityScorer.evaluate(data); if (score < 50) { logInfo(`[QualityWarning] ${stage} 결과 품질 낮음 (Score: ${score}). 내용 보강이 필요할 수 있습니다.`); } // [New] 지능형 충돌 감지 및 위험도 평가 const conflictRisk = ConflictDetector.analyzeSemanticDivergence(data); if (conflictRisk > 40) { logInfo(`[ConflictAlert] ${stage} 단계에서 지식 충돌 위험 감지 (Risk: ${conflictRisk}).`); } return { score, conflictRisk }; } /** * [New] 오류 발생 시 안전하게 데이터를 진단합니다 (에러를 던지지 않음). */ public static audit(stage: string, data: string): { score: number, conflictRisk: number, issues: string[] } { const issues: string[] = []; if (!data || data.trim().length === 0) { issues.push('데이터가 완전히 비어 있음'); return { score: 0, conflictRisk: 100, issues }; } if (data.length < 50) issues.push('데이터 길이가 너무 짧음 (잠재적 누락)'); const score = QualityScorer.evaluate(data); const conflictRisk = ConflictDetector.analyzeSemanticDivergence(data); if (score < 40) issues.push('품질 점수 매우 낮음'); if (conflictRisk > 60) issues.push('심각한 지식 충돌 감지'); return { score, conflictRisk, issues }; } } export class QualityScorer { /** * 수집된 데이터의 품질을 휴리스틱하게 점수화합니다 (0-100). */ public static evaluate(data: string): number { if (!data) return 0; let score = 0; // 1. 길이 기반 점수 (최대 40점) score += Math.min(40, data.length / 50); // 2. 구조화 기반 점수 (최대 30점) if (data.includes('##') || data.includes('**') || data.includes('- ')) { score += 30; } // 3. 밀도/정보량 기반 점수 (최대 30점) // 코드 블록이나 구체적 링크가 있으면 가산점 if (data.includes('```')) score += 15; if (data.includes('## 💻 실전 구현') || data.includes('Implementation')) score += 10; if (data.includes('[[') || data.includes('http')) score += 5; // 감점 요소: Placeholder 나 Empty 상태 if (data.includes('[Placeholder]') || data.includes('TODO')) { score -= 20; } return Math.max(0, Math.min(100, score)); } } export class PerformanceProfiler { /** * LLM 호출 시 발생하는 비동기 오버헤드와 생성 시간을 측정합니다. * 이 지표를 통해 병목(Bottleneck) 현상을 정밀하게 추적할 수 있습니다. */ public static logLLMLatency(agentName: string, durationMs: number, outputLength: number): void { // 영어권 기준 대략적인 토큰 수 산정 (1 token ≈ 4 characters) const tokensApprox = Math.floor(outputLength / 4); const tokensPerSecond = durationMs > 0 ? tokensApprox / (durationMs / 1000) : 0; logInfo( `[Profiler] [${agentName}] LLM Latency: ${durationMs}ms | ` + `Output: ${outputLength} chars (~${tokensApprox} tokens) | ` + `Speed: ${tokensPerSecond.toFixed(2)} tok/s` ); } } export class ConflictDetector { /** * [Astra v4.1] 도메인 맥락 기반의 의미적 괴리(Semantic Divergence)를 분석합니다. * 데이터 내의 논리적 모순이나 상충하는 지표를 탐지하여 위험도를 산출합니다. */ public static analyzeSemanticDivergence(data: string): number { if (!data) return 0; let riskScore = 0; // 1. 수치적 모순 탐지 (Metric Contradiction) // 동일 문맥 내에서 서로 다른 수치가 발견되는 패턴 탐지 const metricPatterns = [ /(\d+%)\s+vs\s+(\d+%)/g, /(\d+ms)\s+대비\s+(\d+ms)/g, /상충되는\s+(데이터|정보|결과)/i ]; for (const pattern of metricPatterns) { if (pattern.test(data)) riskScore += 25; } // 2. 논리적 상충 용어 탐지 (Logical Divergence) const conflictTerms = [ ['최적화', '성능 저하'], ['안정적', '불안정'], ['증가', '감소'], ['승인', '반려/경고'], ['임상 등급', '비승인/소비자용'] ]; for (const [pos, neg] of conflictTerms) { if (data.includes(pos) && data.includes(neg)) { const posIdx = data.indexOf(pos); const negIdx = data.indexOf(neg); if (Math.abs(posIdx - negIdx) < 400) { riskScore += 15; } } } // 3. 기술적 상충 (Technical Divergence) const technicalConflicts = [ /동기(식)?.*비동기(식)?/g, /로컬.*클라우드/g, /정적.*동적/g, /Success.*Fail/i ]; for (const pattern of technicalConflicts) { if (pattern.test(data)) riskScore += 10; } // 4. 명시적 경고 태그 확인 if (data.includes('[CONFLICT WARNING]') || data.includes('[ERROR]')) riskScore += 30; return Math.min(100, riskScore); } } export class CognitionAudit { /** * [Astra v4.0] 지능적 판단 및 정책 준수 여부를 감사(Audit)합니다. * 시스템이 얼마나 '생각'하고 '경고'했는지 정량적으로 측정합니다. */ public static auditPolicyCompliance(stage: string, content: string): void { const findings = []; // 1. 충돌 경고 감지 (Conflict Warning) if (content.includes('[CONFLICT WARNING]')) { findings.push('Conflict Detected & Warning Issued'); } // 2. 선제적 제안 감지 (Proactive Advice) if (content.includes('## 💡 Astra의 선제적 제안')) { findings.push('Proactive Advice Generated'); } // 3. 신뢰도 인용 패턴 확인 (Credibility Usage) const highTrustCitationCount = (content.match(/\[\[/g) || []).length; if (highTrustCitationCount > 0) { findings.push(`Knowledge Density: ${highTrustCitationCount} connections used`); } if (findings.length > 0) { logInfo(`[CognitionAudit] [${stage}] ${findings.join(' | ')}`); } } }