feat: Intelligent Resilience & Trust Reporting (v2.77.2)
This commit is contained in:
@@ -149,10 +149,12 @@ Analyze the provided report and suggest 3 high-impact next actions for the user.
|
||||
// Fix 3: Trim input if it's too long (Basic Context Diet)
|
||||
const trimmedData = input.length > 8000 ? input.substring(0, 8000) + '... [Data Trimmed for Performance]' : input;
|
||||
|
||||
const policy = options?.context || '';
|
||||
const wrappedInput = `### SYSTEM INSTRUCTION: FINAL SYNTHESIS
|
||||
1. Gathered Research Data: ${trimmedData}
|
||||
2. User's Original Objective & Policy: ${originalRequest}
|
||||
3. Mission: Write the definitive final report in KOREAN.`;
|
||||
2. User's Original Objective: ${originalRequest}
|
||||
3. Applied Knowledge & Filtering Policy: ${policy}
|
||||
4. Mission: Write the definitive final report in KOREAN.`;
|
||||
return this.callLLM(this.persona, wrappedInput, signal);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,69 @@
|
||||
import { logInfo, logError } from '../utils';
|
||||
|
||||
export enum ApiErrorType {
|
||||
AUTH_FAILURE = 'AUTH_FAILURE',
|
||||
RATE_LIMIT = 'RATE_LIMIT',
|
||||
NETWORK_TIMEOUT = 'NETWORK_TIMEOUT',
|
||||
SERVER_ERROR = 'SERVER_ERROR',
|
||||
UNKNOWN = 'UNKNOWN'
|
||||
}
|
||||
|
||||
export interface ApiResponse<T> {
|
||||
success: boolean;
|
||||
data?: T;
|
||||
error?: {
|
||||
type: ApiErrorType;
|
||||
message: string;
|
||||
isRecoverable: boolean;
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* ExternalApiHandler: 외부 서비스(Ollama, Gemini, Search API 등)와의
|
||||
* 모든 통신을 중앙 집중적으로 관리하며, 인증 복구 및 속도 제한 대응을 담당합니다.
|
||||
*/
|
||||
export class ExternalApiHandler {
|
||||
/**
|
||||
* 인증 오류 발생 시 복구 가능성을 판단하고 재시도 로직을 제어합니다.
|
||||
*/
|
||||
public static async handleAuthRecovery(error: any): Promise<boolean> {
|
||||
logInfo(`[ApiHandler] 인증 오류 감지. 복구 시도 중...`);
|
||||
// TODO: 실제 프로젝트의 인증 스키마(API Key, OAuth 등)에 따른 복구 로직 구현
|
||||
// 예: 환경 변수 재로드, 만료된 토큰 갱신 요청 등
|
||||
return false; // 현재는 기본적으로 수동 개입 필요로 처리
|
||||
}
|
||||
|
||||
/**
|
||||
* 지능형 요청 래퍼 (Resilient Fetch)
|
||||
*/
|
||||
public static async request<T>(
|
||||
call: () => Promise<T>,
|
||||
context: string
|
||||
): Promise<ApiResponse<T>> {
|
||||
try {
|
||||
const data = await call();
|
||||
return { success: true, data };
|
||||
} catch (error: any) {
|
||||
logError(`[ApiHandler] [${context}] 호출 실패:`, error);
|
||||
const errorType = this.classifyError(error);
|
||||
|
||||
return {
|
||||
success: false,
|
||||
error: {
|
||||
type: errorType,
|
||||
message: error.message,
|
||||
isRecoverable: errorType === ApiErrorType.NETWORK_TIMEOUT || errorType === ApiErrorType.RATE_LIMIT
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
private static classifyError(error: any): ApiErrorType {
|
||||
const msg = error.message || '';
|
||||
if (msg.includes('401') || msg.includes('unauthorized')) return ApiErrorType.AUTH_FAILURE;
|
||||
if (msg.includes('429') || msg.includes('rate limit')) return ApiErrorType.RATE_LIMIT;
|
||||
if (msg.includes('timeout') || msg.includes('ETIMEDOUT')) return ApiErrorType.NETWORK_TIMEOUT;
|
||||
if (msg.includes('500') || msg.includes('502') || msg.includes('503')) return ApiErrorType.SERVER_ERROR;
|
||||
return ApiErrorType.UNKNOWN;
|
||||
}
|
||||
}
|
||||
+59
-5
@@ -2,10 +2,9 @@ import { logInfo } from '../utils';
|
||||
|
||||
export class AgentDataValidator {
|
||||
/**
|
||||
* 에이전트 간 핸드오프(Handoff) 시 데이터 무결성을 검증합니다.
|
||||
* 데이터 누락이나 스키마 오류를 감지하여 파이프라인의 안정성을 높입니다.
|
||||
* 에이전트 간 핸드오프(Handoff) 시 데이터 무결성을 검증하고 품질 점수를 반환합니다.
|
||||
*/
|
||||
public static validateHandoff(stage: string, data: string): void {
|
||||
public static validateHandoff(stage: string, data: string): number {
|
||||
if (!data || data.trim().length === 0) {
|
||||
throw new Error(`[IntegrityError] 데이터 누락: ${stage} 에이전트의 출력이 비어 있습니다.`);
|
||||
}
|
||||
@@ -17,7 +16,6 @@ export class AgentDataValidator {
|
||||
if (data.length < minLength) {
|
||||
throw new Error(`[IntegrityError] Planner 출력 데이터가 비정상적으로 짧습니다 (${data.length} chars). 계획 누락 의심.`);
|
||||
}
|
||||
// 향후 JSON 스키마 검증(Zod 등)을 여기에 추가할 수 있습니다.
|
||||
break;
|
||||
case 'researcher':
|
||||
if (data.length < minLength) {
|
||||
@@ -36,11 +34,19 @@ export class AgentDataValidator {
|
||||
break;
|
||||
}
|
||||
|
||||
// 품질 점수화 (Quality Scoring) - Astra 피드백 반영
|
||||
// 품질 점수화 (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 * 0.5); // 충돌 위험이 높으면 전체 품질 점수를 감쇄함
|
||||
}
|
||||
}
|
||||
|
||||
@@ -94,6 +100,54 @@ export class PerformanceProfiler {
|
||||
}
|
||||
}
|
||||
|
||||
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)) {
|
||||
// 두 상충 용어가 너무 가까운 거리(예: 200자 이내)에 있으면 충돌 확률 높음
|
||||
const posIdx = data.indexOf(pos);
|
||||
const negIdx = data.indexOf(neg);
|
||||
if (Math.abs(posIdx - negIdx) < 300) {
|
||||
riskScore += 15;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 3. 명시적 경고 태그 확인
|
||||
if (data.includes('[CONFLICT WARNING]')) riskScore += 30;
|
||||
|
||||
return Math.min(100, riskScore);
|
||||
}
|
||||
}
|
||||
|
||||
export class CognitionAudit {
|
||||
/**
|
||||
* [Astra v4.0] 지능적 판단 및 정책 준수 여부를 감사(Audit)합니다.
|
||||
|
||||
+153
-97
@@ -11,6 +11,8 @@ import { WikiFormatter } from './formatter';
|
||||
// 1. 에이전트 인터페이스 확장 (Interface Extensibility)
|
||||
// ─────────────────────────────────────────────
|
||||
|
||||
export type AbstractionLevel = 'laser-focused' | 'balanced' | 'comprehensive';
|
||||
|
||||
/**
|
||||
* 에이전트 실행 시 전달되는 확장 옵션 객체.
|
||||
* 향후 에이전트별로 고유한 설정(temperature, maxTokens 등)을
|
||||
@@ -24,7 +26,9 @@ export interface AgentExecuteOptions {
|
||||
/** 에이전트별 커스텀 설정 (temperature, maxTokens 등) */
|
||||
config?: Record<string, unknown>;
|
||||
/** 이전 단계의 중간 결과물 (병렬 파이프라인용) */
|
||||
priorResults?: Record<string, string>;
|
||||
priorResults?: Record<string, string | undefined>;
|
||||
/** 에이전트 간 정보 전달 시의 추상화/필터링 수준 */
|
||||
abstractionLevel?: AbstractionLevel;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -69,6 +73,12 @@ export class MissionState {
|
||||
|
||||
public readonly missionId: string;
|
||||
public readonly startTime: number;
|
||||
public resilienceMetrics = {
|
||||
fallbacks: 0,
|
||||
retries: 0,
|
||||
maxConflictScore: 0,
|
||||
deduplications: 0
|
||||
};
|
||||
|
||||
constructor(missionId: string) {
|
||||
this.missionId = missionId;
|
||||
@@ -229,6 +239,8 @@ export enum ErrorType {
|
||||
TRANSIENT = 'TRANSIENT',
|
||||
/** 프롬프트 구조 문제, 모델 명백한 실패 등 수동 개입이 필요한 오류 */
|
||||
PERMANENT = 'PERMANENT',
|
||||
/** 인증 실패 (API Key 만료 등) */
|
||||
AUTH_FAILURE = 'AUTH_FAILURE',
|
||||
/** 사용자가 의도적으로 작업을 취소한 경우 */
|
||||
ABORT = 'ABORT'
|
||||
}
|
||||
@@ -242,8 +254,10 @@ export interface RecoveryRule {
|
||||
description: string;
|
||||
maxRetries: number;
|
||||
backoffBaseMs: number;
|
||||
action: 'retry' | 'abort' | 'fail_with_message';
|
||||
action: 'retry' | 'abort' | 'fail_with_message' | 'fallback';
|
||||
userMessage: string;
|
||||
/** 'fallback' 액션 시 실행할 대체 로직 (예: 캐시 반환) */
|
||||
fallbackDescription?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -423,8 +437,7 @@ export class AgentEngine {
|
||||
) {}
|
||||
|
||||
/**
|
||||
* 멀티 에이전트 워크플로우 실행
|
||||
* @param missionId 작업을 식별하기 위한 고유 ID (Mutex 락에 사용)
|
||||
* 멀티 에이전트 워크플로우 실행 (Refactored: Atomic State Machine)
|
||||
*/
|
||||
public async runMission(
|
||||
missionId: string,
|
||||
@@ -437,124 +450,135 @@ export class AgentEngine {
|
||||
|
||||
// 0. 상태 복원 시도 (Resumption)
|
||||
const existingState = MissionState.loadFromDisk(missionId);
|
||||
if (existingState && existingState.stage !== 'completed') {
|
||||
logInfo(`[AgentEngine] 기존 미션 발견. '${existingState.stage}' 단계부터 재개합니다.`);
|
||||
state = existingState;
|
||||
} else {
|
||||
state = new MissionState(missionId);
|
||||
}
|
||||
state = (existingState && existingState.stage !== 'completed') ? existingState : new MissionState(missionId);
|
||||
|
||||
// 1. 명시적 락 획득 (Mutex) - 동일 미션의 중복 실행 방지
|
||||
// 1. 명시적 락 획득 (Mutex)
|
||||
const release = await lockManager.acquire(`mission_${missionId}`);
|
||||
|
||||
try {
|
||||
// 2. 작업을 비동기 큐에 등록 (Producer-Consumer)
|
||||
return await actionQueue.enqueue(async () => {
|
||||
logInfo(`[AgentEngine] 미션 시작: ${missionId}`);
|
||||
|
||||
// --- Phase 1: Planner ---
|
||||
let plan = state.getResult('plan');
|
||||
if (!plan) {
|
||||
this.transition(state, 'planner', '전략 수립 중...', onProgress);
|
||||
this.checkAbort(signal);
|
||||
|
||||
// Deduplication: 동일 프롬프트 캐시 확인
|
||||
const cachedPlan = CacheManager.get(prompt, brainContext);
|
||||
if (cachedPlan) {
|
||||
logInfo(`[AgentEngine] [Deduplication] 기존 Planner 캐시를 사용합니다.`);
|
||||
plan = cachedPlan;
|
||||
} else {
|
||||
plan = await this.resilientExecute(
|
||||
state, this.planner, 'Planner', prompt, brainContext, signal, onProgress,
|
||||
{ context: brainContext, signal, config: { role: 'planner' } }
|
||||
);
|
||||
CacheManager.set(prompt, brainContext, plan);
|
||||
}
|
||||
state.setResult('plan', plan);
|
||||
|
||||
// [Blind Spot Check] 전역 중복 제거 (Global Deduplication)
|
||||
const globalCache = CacheManager.get(prompt, 'global_final_report');
|
||||
if (globalCache) {
|
||||
logInfo(`[AgentEngine] [Deduplication] 최종 리포트 캐시를 발견했습니다. 실행을 스킵합니다.`);
|
||||
this.transition(state, 'completed', '캐시된 결과 반환', onProgress);
|
||||
return globalCache;
|
||||
}
|
||||
this.validateResult(plan, 'Planner');
|
||||
|
||||
// --- Phase 2 & 3: Researcher ---
|
||||
let research = state.getResult('research');
|
||||
// --- Phase 1: Planner ---
|
||||
const plan = await this.executeStep(
|
||||
state, 'planner', '전략 수립 중...',
|
||||
() => this.resilientExecute(state, this.planner, 'Planner', prompt, brainContext, signal, onProgress, { context: brainContext, signal, config: { role: 'planner' } }),
|
||||
prompt, brainContext, signal, onProgress
|
||||
);
|
||||
|
||||
const plannerScore = this.validateResult(plan, 'Planner');
|
||||
const researcherLevel: AbstractionLevel = plannerScore < 70 ? 'laser-focused' : 'balanced';
|
||||
|
||||
// --- Phase 2: Researcher ---
|
||||
const research = await this.executeStep(
|
||||
state, 'researcher', '핵심 정보 수집 및 분석 중...',
|
||||
() => this.resilientExecute(state, this.researcher, 'Researcher', plan, brainContext, signal, onProgress, { context: brainContext, signal, config: { role: 'researcher' }, abstractionLevel: researcherLevel }),
|
||||
plan, brainContext, signal, onProgress
|
||||
);
|
||||
|
||||
// --- Phase 3: Context Preparation (Side Effect of Phase 2) ---
|
||||
let writerPrep = state.getResult('writerPrep');
|
||||
|
||||
if (!research || !writerPrep) {
|
||||
this.transition(state, 'researcher', '핵심 정보 수집 및 분석 중...', onProgress);
|
||||
this.checkAbort(signal);
|
||||
|
||||
const [res, prep] = await Promise.all([
|
||||
research || this.resilientExecute(
|
||||
state, this.researcher, 'Researcher', plan, brainContext, signal, onProgress,
|
||||
{ context: brainContext, signal, config: { role: 'researcher' } }
|
||||
),
|
||||
writerPrep || this.prepareWriterContext(prompt, plan, brainContext)
|
||||
]);
|
||||
|
||||
research = res;
|
||||
writerPrep = prep;
|
||||
state.setResult('research', research);
|
||||
if (!writerPrep) {
|
||||
writerPrep = await this.prepareWriterContext(prompt, plan, brainContext);
|
||||
state.setResult('writerPrep', writerPrep);
|
||||
}
|
||||
this.validateResult(research, 'Researcher');
|
||||
|
||||
// --- Phase 3: Writer ---
|
||||
this.transition(state, 'writer', '최종 리포트 작성 및 편집 중...', onProgress);
|
||||
this.checkAbort(signal);
|
||||
const finalReport = await this.resilientExecute(
|
||||
state, this.writer, 'Writer', research, prompt, signal, onProgress,
|
||||
{ context: brainContext, signal, config: { role: 'writer' }, priorResults: { plan, writerPrep } }
|
||||
const researchScore = this.validateResult(research, 'Researcher');
|
||||
const writerLevel: AbstractionLevel = researchScore < 65 ? 'laser-focused' : 'balanced';
|
||||
|
||||
// --- Phase 4: Writer ---
|
||||
const finalReport = await this.executeStep(
|
||||
state, 'writer', '최종 리포트 작성 및 편집 중...',
|
||||
() => this.resilientExecute(state, this.writer, 'Writer', research, prompt, signal, onProgress, {
|
||||
context: brainContext, signal, config: { role: 'writer', allowFallback: true },
|
||||
priorResults: { plan, writerPrep, previousValidData: state.getResult('finalReport') },
|
||||
abstractionLevel: writerLevel
|
||||
}),
|
||||
research, prompt, signal, onProgress
|
||||
);
|
||||
this.validateResult(finalReport, 'Writer');
|
||||
|
||||
state.setResult('finalReport', finalReport);
|
||||
|
||||
// --- Phase 4: Proactive Advisor (Astra v4.0) ---
|
||||
// 리포트 작성 후, 사용자의 다음 행동을 선제적으로 제안합니다.
|
||||
// --- Phase 5: Advice & Standardization ---
|
||||
const proactiveAdvice = await this.generateProactiveAdvice(finalReport, prompt, brainContext, signal);
|
||||
const enrichedReport = `${finalReport}\n\n---\n## 💡 Astra의 선제적 제안 (Proactive Next Actions)\n${proactiveAdvice}`;
|
||||
const standardizedReport = WikiFormatter.format(enrichedReport, state);
|
||||
|
||||
// 3. 지식 저장 포맷 표준화 (Standardization: Astra 피드백)
|
||||
const standardizedReport = WikiFormatter.format(enrichedReport, missionId);
|
||||
// 최종 결과 전역 캐싱 (Deduplication)
|
||||
CacheManager.set(prompt, 'global_final_report', standardizedReport);
|
||||
|
||||
// [Astra v4.0] 지능적 판단 감사 (Cognition Audit)
|
||||
CognitionAudit.auditPolicyCompliance('MissionComplete', standardizedReport);
|
||||
|
||||
this.transition(state, 'completed', '미션 완료', onProgress);
|
||||
logInfo(`[AgentEngine] 미션 완료: ${missionId} (총 ${state.getElapsedMs()}ms)`);
|
||||
return standardizedReport;
|
||||
});
|
||||
|
||||
|
||||
|
||||
} catch (error: any) {
|
||||
const { type, rule } = ErrorClassifier.classify(error);
|
||||
const stageName = (state!.stage || 'unknown').toUpperCase();
|
||||
|
||||
this.transition(state!, 'error', `오류 발생: ${error.message}`, onProgress);
|
||||
|
||||
// 실패 원인 명시적 기록 (Astra 피드백: Record failure reason)
|
||||
state!.setFailureReason(`[${type}] ${rule.description} - 세부원인: ${error.message}`);
|
||||
|
||||
// Error Recovery Matrix 기반 세분화된 로깅
|
||||
switch (type) {
|
||||
case ErrorType.ABORT:
|
||||
logInfo(`[AgentEngine] [ABORT] 미션 취소됨 (${missionId}) at ${stageName} stage.`);
|
||||
break;
|
||||
case ErrorType.TRANSIENT:
|
||||
logError(`[AgentEngine] [TRANSIENT] 재시도 소진 후 실패 (${missionId}) at ${stageName} stage — ${rule.description}:`, error);
|
||||
break;
|
||||
case ErrorType.PERMANENT:
|
||||
logError(`[AgentEngine] [PERMANENT] 복구 불가 오류 (${missionId}) at ${stageName} stage — ${rule.userMessage}:`, error);
|
||||
break;
|
||||
}
|
||||
|
||||
// 감사 이력 덤프 (디버깅용)
|
||||
logError(`[AgentEngine] Audit Trail for ${missionId}:\n${state!.summarizeAudit()}`);
|
||||
this.handleMissionFailure(missionId, state!, error, onProgress);
|
||||
throw error;
|
||||
} finally {
|
||||
// 3. 락 해제
|
||||
release();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* [Atomic Step Execution]
|
||||
* 개별 작업 단계를 캡슐화하여 상태 확인, 캐시 체크, 실행, 검증을 일관되게 처리합니다.
|
||||
*/
|
||||
private async executeStep(
|
||||
state: MissionState,
|
||||
stage: PipelineStage,
|
||||
progressMessage: string,
|
||||
action: () => Promise<string>,
|
||||
cacheKeyPrompt: string,
|
||||
cacheKeyContext: string,
|
||||
signal: AbortSignal,
|
||||
onProgress: (stage: PipelineStage, message: string) => void
|
||||
): Promise<string> {
|
||||
// 1. 기존 결과 확인 (Resumption)
|
||||
let result = state.getResult(stage);
|
||||
if (result) {
|
||||
logInfo(`[AgentEngine] [Resumption] '${stage}' 단계 결과가 이미 존재합니다.`);
|
||||
return result;
|
||||
}
|
||||
|
||||
this.transition(state, stage, progressMessage, onProgress);
|
||||
this.checkAbort(signal);
|
||||
|
||||
// 2. 캐시 확인 (Deduplication)
|
||||
const cached = CacheManager.get(cacheKeyPrompt, cacheKeyContext);
|
||||
if (cached) {
|
||||
logInfo(`[AgentEngine] [Deduplication] '${stage}' 단계 캐시를 사용합니다.`);
|
||||
state.resilienceMetrics.deduplications++;
|
||||
result = cached;
|
||||
} else {
|
||||
// 3. 실행
|
||||
result = await action();
|
||||
CacheManager.set(cacheKeyPrompt, cacheKeyContext, result);
|
||||
}
|
||||
|
||||
// 4. 저장
|
||||
state.setResult(stage, result);
|
||||
return result;
|
||||
}
|
||||
|
||||
private handleMissionFailure(missionId: string, state: MissionState, error: any, onProgress: (stage: PipelineStage, message: string) => void) {
|
||||
const { type, rule } = ErrorClassifier.classify(error);
|
||||
const stageName = (state.stage || 'unknown').toUpperCase();
|
||||
|
||||
this.transition(state, 'error', `오류 발생: ${error.message}`, onProgress);
|
||||
state.setFailureReason(`[${type}] ${rule.description} - 세부원인: ${error.message}`);
|
||||
|
||||
logError(`[AgentEngine] [${type}] ${missionId} 실패 at ${stageName} stage: ${error.message}`);
|
||||
logError(`[AgentEngine] Audit Trail:\n${state.summarizeAudit()}`);
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated 이제 미션 상태는 로컬 스코프에서 관리됩니다.
|
||||
*/
|
||||
@@ -586,6 +610,7 @@ export class AgentEngine {
|
||||
let lastError: any;
|
||||
|
||||
for (let attempt = 0; attempt <= transientRule.maxRetries; attempt++) {
|
||||
if (attempt > 0) state.resilienceMetrics.retries++;
|
||||
try {
|
||||
// 재시도 시 사용자에게 진행 상황 알림
|
||||
if (attempt > 0) {
|
||||
@@ -605,6 +630,10 @@ export class AgentEngine {
|
||||
const result = await agent.execute(input, amplifiedContext, signal, options);
|
||||
const durationMs = Date.now() - startTime;
|
||||
|
||||
// [Reliability Check] 충돌 위험도 추적
|
||||
const conflictScore = AgentDataValidator.validateHandoff(agentName, result);
|
||||
state.resilienceMetrics.maxConflictScore = Math.max(state.resilienceMetrics.maxConflictScore, conflictScore);
|
||||
|
||||
PerformanceProfiler.logLLMLatency(agentName, durationMs, result.length);
|
||||
|
||||
return result;
|
||||
@@ -629,6 +658,15 @@ export class AgentEngine {
|
||||
case ErrorType.TRANSIENT:
|
||||
// 일시적 오류 → 재시도 가능 여부 확인
|
||||
if (attempt >= transientRule.maxRetries) {
|
||||
// [Intelligent Resilience] 재시도 실패 시 대체 경로(Fallback) 시도
|
||||
if (transientRule.action === 'fallback' || options?.config?.allowFallback) {
|
||||
logInfo(`[AgentEngine] [FALLBACK] ${agentName} 재시도 소진. 대체 데이터(Cache/Stale) 경로를 가동합니다.`);
|
||||
state.resilienceMetrics.fallbacks++;
|
||||
const cached = CacheManager.get(input, context);
|
||||
if (cached) return cached;
|
||||
if (options?.priorResults?.['previousValidData']) return options.priorResults['previousValidData'];
|
||||
}
|
||||
|
||||
logError(`[AgentEngine] [TRANSIENT] ${agentName} 최대 재시도 횟수(${transientRule.maxRetries}) 소진.`);
|
||||
const exhaustedError = new Error(
|
||||
`[${agentName}] 일시적 연결 오류가 지속됩니다. ` +
|
||||
@@ -689,9 +727,9 @@ export class AgentEngine {
|
||||
return clean.length > length ? clean.substring(0, length) + '...' : clean;
|
||||
}
|
||||
|
||||
private validateResult(data: string, step: string) {
|
||||
private validateResult(data: string, step: string): number {
|
||||
// Error Recovery Matrix: Permanent 오류 발생을 방지하기 위한 선제적 핸드오프 검증
|
||||
AgentDataValidator.validateHandoff(step, data);
|
||||
return AgentDataValidator.validateHandoff(step, data);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -699,10 +737,28 @@ export class AgentEngine {
|
||||
* 지식 관리 정책 v4.0을 LLM 지시사항으로 변환하여 주입합니다.
|
||||
*/
|
||||
private amplifyContext(context: string, options?: AgentExecuteOptions): string {
|
||||
const level = options?.abstractionLevel || 'balanced';
|
||||
|
||||
const levelDirectives: Record<AbstractionLevel, string[]> = {
|
||||
'laser-focused': [
|
||||
"- [INTENSITY: HIGH] 불필요한 배경 설명과 중간 과정을 생략하고, 즉각적인 결론과 실행 가능한 To-Do 위주로 압축하십시오.",
|
||||
"- [QUALITY] 정보의 양보다 '추론 기여 밀도'를 극한으로 높여 핵심 위주로 서술하십시오."
|
||||
],
|
||||
'balanced': [
|
||||
"- [INTENSITY: MEDIUM] 핵심 논리를 중심으로 설명하되, 판단 근거가 되는 주요 데이터는 포함하십시오.",
|
||||
"- [QUALITY] 지식의 양보다 '추론 기여 밀도'를 중시하여 서술하십시오."
|
||||
],
|
||||
'comprehensive': [
|
||||
"- [INTENSITY: LOW] 전체 맥락을 파악할 수 있도록 배경 지식과 세부 데이터를 충분히 포함하십시오.",
|
||||
"- [QUALITY] 포괄적인 분석을 위해 가용한 모든 데이터를 구조화하여 제공하십시오."
|
||||
]
|
||||
};
|
||||
|
||||
const policyDirectives = [
|
||||
"\n### 🏛️ Knowledge Management Policy v4.0 Applied",
|
||||
"\n### 🏛️ Knowledge Management Policy v4.1 (Filtering Applied)",
|
||||
`- [ABSTRACTION LEVEL] 현재 에이전트 간 인터페이스 정책은 '${level}' 모드입니다.`,
|
||||
...levelDirectives[level],
|
||||
"- [CREDIBILITY] 정보 출처가 의도적으로 작성된 글인 경우 Medium 이상의 신뢰도를 부여하고 우선적으로 인용하십시오.",
|
||||
"- [QUALITY] 지식의 양보다 '추론 기여 밀도'를 중시하여 핵심 위주로 서술하십시오.",
|
||||
"- [CONFLICT] 상충되는 지식 발견 시 스스로 판단하지 말고 반드시 [CONFLICT WARNING] 플래그와 함께 두 관점을 모두 보고하십시오."
|
||||
].join('\n');
|
||||
|
||||
|
||||
+48
-38
@@ -1,60 +1,70 @@
|
||||
import * as vscode from 'vscode';
|
||||
import { MissionState } from './engine';
|
||||
|
||||
export class WikiFormatter {
|
||||
/**
|
||||
* 최종 에이전트 출력물을 P-Reinforce v3.0 표준 포맷으로 변환합니다.
|
||||
*/
|
||||
public static format(content: string, missionId: string): string {
|
||||
public static format(content: string, state: MissionState): string {
|
||||
const now = new Date().toISOString();
|
||||
const missionId = state.missionId;
|
||||
|
||||
// 1. Frontmatter가 없는 경우 주입
|
||||
let formatted = content;
|
||||
if (!content.trim().startsWith('---')) {
|
||||
const frontmatter = [
|
||||
'---',
|
||||
`id: ${missionId}`,
|
||||
`date: ${now}`,
|
||||
'type: knowledge_artifact',
|
||||
'standard: P-Reinforce v3.0',
|
||||
'tags: [automated, connect_ai, brain_sync]',
|
||||
'---',
|
||||
'',
|
||||
''
|
||||
].join('\n');
|
||||
formatted = frontmatter + content;
|
||||
}
|
||||
// 1. Frontmatter 주입 (항상 갱신)
|
||||
const frontmatter = [
|
||||
'---',
|
||||
`id: ${missionId}`,
|
||||
`date: ${now}`,
|
||||
'type: knowledge_artifact',
|
||||
'standard: P-Reinforce v3.0',
|
||||
'tags: [automated, connect_ai, brain_sync]',
|
||||
'---',
|
||||
'',
|
||||
''
|
||||
].join('\n');
|
||||
|
||||
// 2. 필수 헤더 보정 (예: Brief Summary가 없는 경우 상단에 자동 생성 시도)
|
||||
// 기존 frontmatter 제거 (있는 경우)
|
||||
let cleanContent = content.replace(/^---[\s\S]*?---\n*/, '');
|
||||
let formatted = frontmatter + cleanContent;
|
||||
|
||||
// 2. 필수 헤더 보정
|
||||
if (!formatted.includes('## 📌 Brief Summary')) {
|
||||
// 간단한 요약 추출 시도 (첫 문장 등)
|
||||
const summary = content.split('\n').find(l => l.trim().length > 10) || '요약이 생성되지 않았습니다.';
|
||||
const summary = cleanContent.split('\n').find(l => l.trim().length > 10) || '요약이 생성되지 않았습니다.';
|
||||
const summarySection = `## 📌 Brief Summary\n${summary.substring(0, 200)}...\n\n`;
|
||||
formatted = formatted.replace(/---\n\n/, `---\n\n${summarySection}`);
|
||||
}
|
||||
|
||||
// 3. 코드 스니펫 섹션 보정 (Astra 피드백: 실전 구현 코드 강제)
|
||||
const hasImplementationHeader = formatted.includes('## 💻 Practical Implementation') || formatted.includes('## 💻 실전 구현 코드');
|
||||
if (!hasImplementationHeader) {
|
||||
// 코드 블록이 이미 있다면 그 위에 헤더를 붙여줌
|
||||
// 3. 코드 스니펫 섹션 보정
|
||||
if (!formatted.includes('## 💻 Practical Implementation') && !formatted.includes('## 💻 실전 구현 코드')) {
|
||||
if (formatted.includes('```')) {
|
||||
// 첫 번째 코드 블록 앞에 헤더 삽입
|
||||
formatted = formatted.replace(/```/, '\n## 💻 실전 구현 코드\n\n```');
|
||||
} else {
|
||||
// 코드 블록이 전혀 없는 경우 하단에 플레이스홀더 추가 (추후 보강 유도)
|
||||
const boilerplatePlaceholder = [
|
||||
'',
|
||||
'---',
|
||||
'## 💻 실전 구현 코드 (Boilerplate)',
|
||||
'> [!TIP]',
|
||||
'> 이 문서의 개념을 즉시 적용할 수 있는 **실전 구현 코드**나 **보일러플레이트**가 아직 포함되지 않았습니다.',
|
||||
'> 에이전트에게 "React 예제 코드 포함해줘" 또는 "MSA 구조도 코드로 표현해줘"와 같이 구체적인 구현체 생성을 요청하세요.',
|
||||
'',
|
||||
].join('\n');
|
||||
formatted += boilerplatePlaceholder;
|
||||
}
|
||||
}
|
||||
|
||||
return formatted;
|
||||
// 4. [Astra v4.0] 신뢰성 보고서 (Reliability & Audit Summary)
|
||||
const metrics = state.resilienceMetrics;
|
||||
const totalDuration = Date.now() - state.startTime;
|
||||
const reliabilitySection = [
|
||||
'',
|
||||
'---',
|
||||
'## 🛡️ Reliability & Audit Summary',
|
||||
`> [!NOTE]`,
|
||||
`> 이 문서는 ConnectAI의 **Intelligent Resilience** 엔진에 의해 검증 및 정제되었습니다.`,
|
||||
'',
|
||||
'| Metric | Value | Status |',
|
||||
'| :--- | :--- | :--- |',
|
||||
`| **Conflict Risk** | \`${metrics.maxConflictScore}/100\` | ${metrics.maxConflictScore < 30 ? '✅ Low' : metrics.maxConflictScore < 70 ? '⚠️ Medium' : '🚨 High'} |`,
|
||||
`| **Fallbacks Used** | \`${metrics.fallbacks}\` | ${metrics.fallbacks === 0 ? '✅ None' : '💡 Recovered'} |`,
|
||||
`| **Auto Retries** | \`${metrics.retries}\` | ${metrics.retries < 3 ? '✅ Stable' : '⚠️ Unstable'} |`,
|
||||
`| **Deduplication** | \`${metrics.deduplications}\` | ${metrics.deduplications > 0 ? '🚀 Optimized' : 'Standard'} |`,
|
||||
`| **Processing Time** | \`${(totalDuration / 1000).toFixed(1)}s\` | ✅ Fast |`,
|
||||
'',
|
||||
'### 🔍 Decision Audit Trail',
|
||||
state.auditTrail.map(a => `- **[${a.to.toUpperCase()}]** ${a.message} (${a.durationFromPrev}ms)`).join('\n'),
|
||||
''
|
||||
].join('\n');
|
||||
|
||||
formatted += reliabilitySection;
|
||||
|
||||
return formatted;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user