SOL(Sleep Onset Latency) Chain of Debate (CoD) 시스템 설계 문서
📋 프로젝트 개요
목표: Microsoft AI MAI-DxO 논문에서 영감을 받아 4개의 전문가 에이전트가 Chain of Debate를 통해 사용자의 SOL(Sleep Onset Latency, 0-120분)을 예측하는 시스템을 DTA Wide 플랫폼에 구축
기술 스택: TypeScript + LangGraph.js + NestJS + CQRS + Event-Driven Architecture
🎯 MAI-DxO 핵심 개념 분석
Sequential Diagnosis Benchmark (SDBench)
- 304개의 NEJM CPC 케이스를 순차적 진단 과제로 변환
- Gatekeeper 모델이 정보를 제어하여 실제 임상 환경 시뮬레이션
- 진단 정확도와 비용을 동시에 평가하는 Pareto 프론티어 접근법
- o3 모델 기준: 기본 78.6% → MAI-DxO 79.9% 정확도, 비용 70% 절감
MAI Diagnostic Orchestrator 아키텍처
- 가상 의료진 패널: 5개의 전문화된 역할을 가진 AI 에이전트
- Chain of Debate: 구조화된 토론을 통한 합의 도출
- 모델 불가지론적 설계: 다양한 LLM에서 일관된 성능 향상 (+11% 평균)
MAI-DxO 5개 전문가 에이전트
- Dr. Hypothesis: 베이지안 방식으로 상위 3개 진단 가설 관리
- Dr. Test-Chooser: 진단 가설을 최대로 구별하는 3개 검사 선택
- Dr. Challenger: 편향 식별 및 반박 증거 제시
- Dr. Stewardship: 비용 효율적 대안 옹호
- Dr. Checklist: 품질 관리 및 일관성 유지
👥 SOL CoD - 4개 전문가 에이전트 설계
1. 수면잠복기 분석 전문가 (Sleep Latency Analyst)
역할: 수면 데이터의 시계열 패턴과 트렌드를 분석하여 SOL 예측
핵심 책임:
- 과거 SOL 패턴 분석 (7일, 14일, 30일 추세)
- 침상 시간(lot)과 실제 수면 시작(ast) 간격 분석
- 주중/주말 SOL 차이 분석
- 수면 부채와 SOL 상관관계 분석
- 개인별 SOL 기준선(baseline) 설정
- 계절적/환경적 요인과 SOL 연관성 분석
전문성: 수면의학, 수면잠복기 병리학, 시계열 분석
2. 심리상태 분석 전문가 (Psychological State Analyst)
역할: SOL에 영향을 미치는 심리적 요인 분석
핵심 책임:
- ISI: 불면증 중증도와 SOL 연관성 분석
- PHQ-9: 우울 증상이 수면잠복기에 미치는 영향
- GAD-7: 불안 수준과 SOL 상관관계
- PSS: 스트레스와 수면잠복기 연관성
- DBAS: 수면에 대한 역기능적 인지가 SOL에 미치는 영향
- 과각성(hyperarousal) 상태 평가
- 수면 불안과 SOL 연관성 분석
전문성: 임상심리학, 수면 심리학, 불안-각성 이론
3. CBT-I 수면행동 전문가 (CBT-I Sleep Behavior Specialist)
역할: SOL 개선을 위한 행동 개입 전문가
핵심 책임:
- 침실 환경이 SOL에 미치는 영향 분석
- 잠자리에서의 활동 패턴 평가 (스크린 타임, 독서 등)
- 수면 위생 요소와 SOL 연관성
- 자극 조절 치료 관점에서 SOL 분석
- 이완 기법 효과성 평가
- 수면 제한 치료와 SOL 개선 가능성
- 카페인, 알코올 등 물질이 SOL에 미치는 영향
전문성: CBT-I, 수면 위생, 자극 조절 치료, 이완 요법
4. 디지털 수면환경 전문가 (Digital Sleep Environment Analyst)
역할: 디지털 환경이 SOL에 미치는 영향 분석
핵심 책임:
- 잠자리 전 스크린 타임과 SOL 상관관계
- 블루라이트 노출 시간과 수면잠복기 영향
- 수면 추적 앱 사용이 SOL에 미치는 역설적 효과 분석
- 소셜미디어, 뉴스 앱 사용과 각성 상태 연관성
- 알림 설정과 수면잠복기 방해 요인 분석
- 디지털 웰빙 지표와 SOL 개선 연관성
전문성: 디지털 수면 건강, 일주기 리듬과 기술 상호작용
🔄 Chain of Debate 워크플로우
1단계: 개별 SOL 분석 (Individual SOL Analysis)
- 각 전문가가 자신의 전문 영역에서 SOL 예측
- 예측 범위: 0-120분
- 신뢰도 및 불확실성 평가
- 주요 영향 요인 식별
2단계: 교차 요인 분석 토론 (Cross-Factor Analysis Debate)
- 생리적 요인 vs 심리적 요인 대조
- 행동적 요인 vs 환경적 요인 연관성 검토
- 급성 요인 vs 만성 요인 구분 분석
3단계: SOL 예측 검증 토론 (SOL Prediction Validation)
- 각 전문가의 예측값에 대한 비판적 검토
- 상충하는 요인들의 우선순위 결정
- 개인별 특성을 고려한 예측값 조정
4단계: 최종 SOL 예측 및 개선 방안 도출
- 가중평균을 통한 최종 SOL 예측값 (분 단위)
- 신뢰구간 및 예측 정확도 제시
- 개인 맞춤형 SOL 개선 권장사항
- 후속 모니터링 계획
🏗️ DTA Wide 플랫폼 통합 아키텍처
시스템 요구사항
핵심 입력 데이터:
- 수면 기록: lot(lights out time), ast(asleep start time) 포함 DTA Wide sleep domain 데이터
- 설문 응답: questionnaire domain의 ISI, PHQ-9, DBAS, GAD-7, PSS
- 사용자 행동: user domain의 앱 사용 패턴 및 디지털 행동 데이터
- TimeMachine 데이터: 시간 기반 분석을 위한 가상 시간 정보
출력:
- SOL 예측값 (0-120분)
- 각 전문가의 SOL 분석 근거
- Chain of Debate 토론 로그
- SOL 개선 권장사항
- 예측 신뢰도 지표 및 불확실성 범위
Feature Library 구조
libs/feature/sleep-onset-latency-cod/
├── src/lib/
│ ├── application/
│ │ ├── commands/handlers/
│ │ │ └── predict-sol.handler.ts
│ │ └── queries/handlers/
│ │ └── get-sol-analysis.handler.ts
│ ├── domain/
│ │ ├── entities/
│ │ │ ├── sol-prediction.entity.ts
│ │ │ ├── expert-sol-analysis.entity.ts
│ │ │ └── sol-chain-of-debate.entity.ts
│ │ ├── services/
│ │ │ ├── sleep-latency-analyst.service.ts
│ │ │ ├── psychological-analyst.service.ts
│ │ │ ├── behavior-specialist.service.ts
│ │ │ ├── digital-environment-analyst.service.ts
│ │ │ └── sol-consensus-builder.service.ts
│ │ ├── repositories/
│ │ │ └── sol-cod.repository.interface.ts
│ │ └── exceptions/
│ └── infrastructure/
├── langgraph/
│ ├── sol-cod-workflow.ts
│ ├── expert-agents/
│ │ ├── base-expert-agent.ts
│ │ ├── sleep-latency-analyst.agent.ts
│ │ ├── psychological-analyst.agent.ts
│ │ ├── behavior-specialist.agent.ts
│ │ └── digital-environment-analyst.agent.ts
│ └── prompts/
│ ├── sol-latency-prompts.ts
│ ├── psychological-prompts.ts
│ ├── behavior-prompts.ts
│ └── digital-environment-prompts.ts
└── repositories/
└── prisma-sol-cod.repository.ts
CQRS 패턴 구현
Command Handler:
@CommandHandler(PredictSOLCommand)
export class PredictSOLHandler {
constructor(
private readonly unitOfWork: UnitOfWork,
private readonly solCoDWorkflow: SOLCoDWorkflow,
private readonly timeMachineService: TimeMachineService,
private readonly eventBus: EventBus
) {}
async execute(command: PredictSOLCommand): Promise<SOLPredictionResultDto> {
return await this.unitOfWork.execute(async (repositories) => {
const currentTime = await this.timeMachineService.getCurrentTime(command.userId);
// Execute SOL Chain of Debate workflow
const prediction = await this.solCoDWorkflow.executeChainOfDebate({
userId: command.userId,
sleepData: command.sleepData,
questionnaireResponses: command.questionnaireResponses,
mobileUsageData: command.mobileUsageData,
analysisTime: currentTime
});
// Save SOL prediction
const savedPrediction = await repositories.solCoDRepository.save(prediction);
// Publish domain event
this.eventBus.publish(new SOLPredictionCompletedEvent(
savedPrediction.id,
savedPrediction.userId,
savedPrediction.solValue,
savedPrediction.confidenceLevel,
currentTime
));
return new SOLPredictionResultDto(savedPrediction);
});
}
}
Query Handler:
@QueryHandler(GetSOLAnalysisQuery)
export class GetSOLAnalysisHandler {
constructor(
private readonly solCoDRepository: SOLCoDRepository,
private readonly timeMachineService: TimeMachineService
) {}
async execute(query: GetSOLAnalysisQuery): Promise<SOLAnalysisDto[]> {
const userCurrentTime = await this.timeMachineService.getCurrentTime(query.userId);
const analyses = await this.solCoDRepository.findAnalysesByUserAndDateRange(
query.userId,
query.dateRange || { start: subDays(userCurrentTime, 30), end: userCurrentTime }
);
return analyses.map(analysis => new SOLAnalysisDto(
analysis.id,
analysis.solValue,
analysis.expertAnalyses,
analysis.confidenceLevel,
analysis.recommendations,
analysis.createdAt.toISOString()
));
}
}
Event-Driven Architecture 통합
Domain Events:
export class SOLPredictionCompletedEvent extends DomainEvent {
constructor(
public readonly predictionId: string,
public readonly userId: string,
public readonly solValue: number,
public readonly confidenceLevel: number,
public readonly analysisTime: Date
) {
super('sol.prediction.completed', predictionId);
}
}
export class SOLExpertAnalysisGeneratedEvent extends DomainEvent {
constructor(
public readonly predictionId: string,
public readonly expertType: string,
public readonly analysis: SOLExpertAnalysis,
public readonly userId: string
) {
super('sol.expert.analysis.generated', predictionId);
}
}
export class SOLTrendDetectedEvent extends DomainEvent {
constructor(
public readonly userId: string,
public readonly trendType: 'improving' | 'declining' | 'stable',
public readonly trendStrength: number,
public readonly timeRange: DateRange
) {
super('sol.trend.detected', userId);
}
}
Cross-Domain Event Flow:
- Sleep Domain →
SleepDataRecordedEvent→ SOL CoD 실행 트리거 - Questionnaire Domain →
QuestionnaireCompletedEvent→ 심리 상태 업데이트 - SOL CoD →
SOLPredictionCompletedEvent→ 결과 저장 및 알림 - User Domain → SOL 개선 권장사항 제공
- Notification Domain → 사용자 알림 발송
💻 TypeScript/LangGraph.js 구현 상세
기술 스택 및 의존성
핵심 기술:
- LangGraph.js: Chain of Debate 워크플로우 구현
- TypeScript: 타입 안전성 및 코드 품질 보장
- NestJS: DI Container 및 CQRS 패턴 지원
- Prisma: 데이터베이스 ORM
- Redis: 캐싱 및 세션 관리
AI/ML 통합:
- OpenAI GPT-4: 주요 언어 모델
- Claude/Gemini: 백업 및 앙상블 모델
- LangChain: 프롬프트 관리 및 체이닝
LangGraph.js 워크플로우 구현
// sol-cod-workflow.ts
export class SOLCoDWorkflow {
private sleepLatencyAnalyst: SleepLatencyAnalystAgent;
private psychologicalAnalyst: PsychologicalAnalystAgent;
private behaviorSpecialist: BehaviorSpecialistAgent;
private digitalEnvironmentAnalyst: DigitalEnvironmentAnalystAgent;
private solConsensusBuilder: SOLConsensusBuilder;
constructor(
@Inject('ChatOpenAI') private readonly llm: ChatOpenAI,
@Inject('TimeMachineService') private readonly timeMachine: TimeMachineService
) {
this.initializeAgents();
}
async executeChainOfDebate(input: SOLAnalysisInput): Promise<SOLPrediction> {
try {
// Phase 1: Individual Analysis
const individualAnalyses = await this.conductIndividualAnalysis(input);
// Phase 2: Cross-Expert Review
const debateResults = await this.conductCrossExpertDebate(individualAnalyses);
// Phase 3: Consensus Building
const consensus = await this.buildConsensus(debateResults);
// Phase 4: Final Validation
const finalPrediction = await this.validateAndFinalize(consensus, input);
return finalPrediction;
} catch (error) {
throw new SOLCoDException('SOL Chain of Debate execution failed', error);
}
}
private async conductIndividualAnalysis(input: SOLAnalysisInput): Promise<SOLExpertAnalysis[]> {
const analyses = await Promise.allSettled([
this.sleepLatencyAnalyst.analyze(input.sleepData, input.analysisTime),
this.psychologicalAnalyst.analyze(input.questionnaireResponses, input.analysisTime),
this.behaviorSpecialist.analyze(input.behaviorData, input.analysisTime),
this.digitalEnvironmentAnalyst.analyze(input.mobileUsageData, input.analysisTime)
]);
return analyses
.filter(result => result.status === 'fulfilled')
.map(result => (result as PromiseFulfilledResult<SOLExpertAnalysis>).value);
}
private async conductCrossExpertDebate(analyses: ExpertAnalysis[]): Promise<DebateResult[]> {
const debates: DebateResult[] = [];
// Each expert challenges others' analyses
for (const expert of [this.sleepLatencyAnalyst, this.psychologicalAnalyst, this.behaviorSpecialist, this.digitalEnvironmentAnalyst]) {
const otherAnalyses = analyses.filter(a => a.expertType !== expert.getExpertType());
const debate = await expert.challengeOtherAnalyses(otherAnalyses);
debates.push(debate);
}
return debates;
}
private async buildConsensus(debates: DebateResult[]): Promise<ConsensusResult> {
return await this.solConsensusBuilder.buildConsensus(debates);
}
private async validateAndFinalize(consensus: ConsensusResult, input: SOLAnalysisInput): Promise<SOLPrediction> {
// Additional validation logic
const confidenceLevel = this.calculateConfidenceLevel(consensus);
const recommendations = await this.generateRecommendations(consensus, input);
return new SOLPrediction(
generateId(),
input.userId,
consensus.finalSOLValue,
consensus.expertAnalyses,
confidenceLevel,
recommendations,
consensus.reasoning,
input.analysisTime
);
}
}
전문가 에이전트 구현 구조
Base Expert Agent:
export abstract class BaseExpertAgent {
protected llm: ChatOpenAI;
protected promptManager: PromptManager;
protected tools: Tool[];
constructor(
llm: ChatOpenAI,
promptManager: PromptManager
) {
this.llm = llm;
this.promptManager = promptManager;
this.tools = this.initializeTools();
}
abstract getExpertType(): string;
abstract analyze(data: any, analysisTime: Date): Promise<ExpertAnalysis>;
abstract challengeOtherAnalyses(analyses: ExpertAnalysis[]): Promise<DebateResult>;
abstract buildConsensusView(debates: DebateResult[]): Promise<ConsensusView>;
protected abstract initializeTools(): Tool[];
protected async executeAnalysis(prompt: string, context: any): Promise<string> {
try {
const response = await this.llm.invoke([
{ role: 'system', content: this.getSystemPrompt() },
{ role: 'user', content: prompt }
]);
return response.content as string;
} catch (error) {
throw new ExpertAnalysisException(`${this.getExpertType()} analysis failed`, error);
}
}
protected abstract getSystemPrompt(): string;
}
구체적 전문가 구현:
export class SleepLatencyAnalystAgent extends BaseExpertAgent {
getExpertType(): string {
return 'sleep_latency_analyst';
}
async analyze(sleepData: SleepDataDto, analysisTime: Date): Promise<SOLLatencyAnalysis> {
const prompt = await this.promptManager.buildSOLLatencyAnalysisPrompt({
sleepData,
analysisTime,
historicalData: await this.getHistoricalSleepData(sleepData.userId)
});
const response = await this.executeAnalysis(prompt, sleepData);
return this.parseSOLLatencyResponse(response);
}
async challengeOtherAnalyses(analyses: SOLExpertAnalysis[]): Promise<DebateResult> {
const challengePrompt = await this.promptManager.buildChallengePrompt({
myExpertise: this.getExpertType(),
otherAnalyses: analyses,
challengeFocus: 'physiological_sol_vs_psychological_factors'
});
const response = await this.executeAnalysis(challengePrompt, analyses);
return this.parseChallengeResponse(response);
}
protected getSystemPrompt(): string {
return `당신은 수면의학 전문의이자 수면 패턴 분석가입니다.
전문 영역:
- 수면 구조 및 아키텍처 분석
- 일주기 리듬 평가
- 수면 효율성 및 질 지표 해석
- 수면 장애 패턴 식별
분석 접근법:
1. 객관적 수면 데이터를 우선시
2. 시계열 트렌드 분석 수행
3. 개인별 수면 기준선 고려
4. 생리학적 수면 요구사항 평가
항상 근거 기반 분석을 제공하고, 불확실성이 있는 경우 명시하세요.`;
}
protected initializeTools(): Tool[] {
return [
new SleepDataAnalysisTool(),
new CircadianRhythmTool(),
new SleepEfficiencyCalculatorTool(),
new HistoricalTrendAnalysisTool()
];
}
private async getHistoricalSleepData(userId: string): Promise<HistoricalSleepData[]> {
// Implementation to fetch historical sleep data
return [];
}
private parseSleepPatternResponse(response: string): SleepPatternAnalysis {
// Parse LLM response into structured analysis
const parsed = JSON.parse(response);
return new SleepPatternAnalysis({
qualityScore: parsed.qualityScore,
sleepEfficiency: parsed.sleepEfficiency,
sleepLatency: parsed.sleepLatency,
awakeLengths: parsed.awakeLengths,
deepSleepPercentage: parsed.deepSleepPercentage,
remSleepPercentage: parsed.remSleepPercentage,
circadianAlignment: parsed.circadianAlignment,
trendAnalysis: parsed.trendAnalysis,
confidenceLevel: parsed.confidenceLevel,
reasoning: parsed.reasoning,
recommendations: parsed.recommendations
});
}
private parseChallengeResponse(response: string): DebateResult {
// Parse challenge response
const parsed = JSON.parse(response);
return new DebateResult({
challengingExpert: this.getExpertType(),
challenges: parsed.challenges,
supportingEvidence: parsed.supportingEvidence,
conflictingFindings: parsed.conflictingFindings,
proposedResolution: parsed.proposedResolution
});
}
}
개발 단계별 마일스톤
Phase 1 (Week 1-2): 기반 구조 구축
- NestJS Feature Module 생성 및 DI 설정
- LangGraph.js 워크플로우 기본 구조 구현
- BaseExpertAgent 추상 클래스 구현
- 프롬프트 관리 시스템 구축
- 기본 도메인 엔티티 정의
- CQRS Command/Query 구조 설정
Phase 2 (Week 3-4): 전문가 에이전트 구현
- SleepPatternAnalystAgent 구현 및 테스트
- PsychometricExpertAgent 구현 및 테스트
- CBTISpecialistAgent 구현 및 테스트
- MobileAnalyticsExpertAgent 구현 및 테스트
- 각 에이전트별 프롬프트 템플릿 최적화
- 에이전트별 도구(Tools) 구현
Phase 3 (Week 5-6): Chain of Debate 구현
- 에이전트 간 토론 로직 구현
- ConsensusBuilder 서비스 구현
- 합의 도출 알고리즘 구현 (가중평균, 앙상블)
- 결과 신뢰도 계산 시스템 구축
- 토론 로그 저장 및 추적 시스템
Phase 4 (Week 7-8): 통합 및 최적화
- DTA Wide CQRS 패턴 완전 통합
- Event-Driven Architecture 이벤트 연동
- TimeMachine 서비스 통합
- Redis 캐싱 전략 구현
- 성능 최적화 및 병렬 처리 개선
- 에러 핸들링 및 복구 로직 구현
Phase 5 (Week 9-10): 테스트 및 검증
- 단위 테스트 (각 에이전트 및 워크플로우)
- 통합 테스트 (전체 시스템)
- E2E 테스트 (API 엔드포인트)
- 의료진 피드백 수집 및 반영
- 성능 벤치마킹 및 부하 테스트
- 프로덕션 배포 준비
📈 성공 지표 및 평가 기준
정량적 지표
- SOL 예측 정확도: 실제 SOL 대비 예측 정확도 (목표: MAPE < 20%)
- 응답 시간: SOL Chain of Debate 실행 시간 (목표: 25초 이내)
- 합의 일관성: 동일 데이터에 대한 반복 실행시 SOL 예측 일관성 (목표: 90% 이상)
- SOL 개선 효과: 권장사항 적용 후 SOL 개선률 (목표: 30% 이상)
정성적 지표
- 의료진 신뢰도: 의료 전문가의 SOL 예측 결과 신뢰도 평가
- 사용자 만족도: SOL 개선 권장사항의 실용성 및 수용도
- 시스템 안정성: 오류 발생률 및 복구 시간
- 확장 가능성: 새로운 전문가 에이전트 추가 용이성
⚠️ 리스크 및 완화 방안
기술적 리스크
-
LLM 응답 불안정성
- 완화: 여러 모델 앙상블, 응답 검증 로직, 재시도 메커니즘
-
토큰 비용 증가
- 완화: 프롬프트 최적화, 캐싱 전략, 응답 길이 제한
-
응답 시간 지연
- 완화: 병렬 처리, 응답 캐싱, 비동기 처리
의료 안전성 리스크
-
잘못된 권장사항 제공
- 완화: 의료진 검토 프로세스, 면책 조항, 추가 상담 권유
-
개인정보 보호
- 완화: 데이터 암호화, GDPR 준수, 최소 데이터 수집
비즈니스 리스크
-
사용자 수용도 저조
- 완화: 점진적 롤아웃, 사용자 피드백 반영, UI/UX 개선
-
의료 규제 변경
- 완화: 규제 모니터링, 유연한 아키텍처 설계, 컴플라이언스 검토
📅 일정 및 마일스톤 요약
| Phase | 기간 | 주요 산출물 | 완료 기준 |
|---|---|---|---|
| Phase 1 | Week 1-2 | 기반 아키텍처 | 기본 구조 및 DI 완료 |
| Phase 2 | Week 3-4 | 4개 전문가 에이전트 | 모든 에이전트 단위 테스트 통과 |
| Phase 3 | Week 5-6 | Chain of Debate 로직 | 전체 워크플로우 동작 |
| Phase 4 | Week 7-8 | 시스템 통합 | DTA Wide 플랫폼 완전 통합 |
| Phase 5 | Week 9-10 | 테스트 및 최적화 | 프로덕션 배포 준비 완료 |
문서 버전: v1.0
작성일: 2025-08-31
다음 업데이트 예정: Phase 1 완료 후
담당자: Sleep Quality CoD Team