Files
connectai/src/intelligence/requirementGraph.ts
T
koriweb 2afd1ac589 feat: Self-Evolving Digital Employee OS P0~P6 + 캘린더 충돌 게이트
신뢰성 코어 (P1~P2):
- Requirement Graph: 업무 유형(회의록/시장조사/업무조사/일정) 필수 요소 주입 + 커버리지 hook
- Confidence Engine(0~100 결정론적) / Escalation Engine(검토 요청) / Epistemic Guard(모름·추정·확실 3분류)
- Provenance: citationTrace 에 출처 수정일·오래됨 경고
- Critic Loop: 문제 신호 turn 만 LLM 검수 1회 + 보완 카드

성장 루프 (P3):
- Gap Detector(Requirement-Knowledge) / Need Engine(30/25/20/15/10 공식) / Knowledge Inventory
- Learning Queue(proposed 전용 병합 — 승인은 사람만) / Decision Journal / Reflection 기록
- 반복 누락 요소(3회+)는 다음 turn 체크리스트에 자동 강조 (T5 루프)

지식 운영 (P4) + 기억 (P5) + 학습 실행 (P6):
- Knowledge Validation + Belief Revision(중복 reject·충돌 시 update/add 권고)
- Knowledge Decay(분야별 반감기 감사) / Knowledge Debt(blocked x impact)
- Organizational Memory(.astra/organization.md 상시 주입)
- Research Agent(approved 큐 -> 조사 브리프+추정 라벨 초안+Validation 게이트 -> proposals/)
- Skill Score(전/후반 추세) + Success Pattern DB(전요소충족+확신도90+ 자동 적재)

병렬 트랙:
- 캘린더 충돌 게이트: conflictCheck + 구조화 이벤트 캐시 + create_calendar_event 차단(force 는 사용자 승인 후)
- Task Eval Harness: 회의록 골든셋 자동 채점 명령 + 성장 리포트/학습 큐/노후 점검 명령

신규 모듈 17종(src/intelligence/), VS Code 명령 5종, 설정 11종, 테스트 +89건(전체 508 통과).
설계 문서: docs/SELF_EVOLVING_OS_MASTER_PLAN.md

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-11 13:42:09 +09:00

274 lines
12 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* Requirement Graph — 업무 유형별 필수 요소 정의 + 감지 + 커버리지 검사.
*
* Self-Evolving Digital Employee OS 마스터 플랜(docs/SELF_EVOLVING_OS_MASTER_PLAN.md)
* Phase 1 / Track 2-1. 신뢰 조건 T3 "품질이 일관적이다 — 필수 요소 누락 없음" 담당.
*
* 동작 2단계:
* 1. *Instructional* — 사용자 요청에서 업무 유형(회의록/시장조사/업무조사/일정) 감지 시
* [TASK REQUIREMENTS] 블록을 시스템 프롬프트에 주입 → 모델이 필수 요소를 빠짐없이 작성.
* 정보가 없어 채울 수 없는 요소는 "(확인 필요)" 로 명시하게 강제 — 조용한 생략 금지
* (Anti-Hallucination T1 과 연결).
* 2. *Deterministic* — 답변 완료 후 post-answer hook 이 필수 요소 커버리지를 정규식으로
* 스캔, 누락 가능 요소를 footer 로 표시 (termValidator 와 같은 패턴, LLM 호출 없음).
*
* Gap Detector (Phase 3) 가 이 모듈의 Requirement 정의를 입력으로 사용한다:
* Gap = Requirement Knowledge.
*/
export interface RequirementElement {
/** 안정적 식별자 (Failure Pattern DB 가 누락 카운트 키로 사용 예정). */
id: string;
/** 사람이 읽는 요소명 — 블록·footer 에 표시. */
label: string;
/** 모델에게 주는 작성 힌트. */
hint: string;
/** 커버리지 검사용 정규식 소스 (OR 결합, i+u 플래그). */
detectPatterns: string[];
}
export interface TaskRequirement {
/** 업무 유형 ID (예: 'meeting-minutes'). */
id: string;
/** 사람이 읽는 업무명 (예: '회의록'). */
label: string;
/** 사용자 요청에서 업무 유형을 감지하는 정규식 소스 (OR). */
detectKeywords: string[];
/**
* 답변 커버리지 검사 여부. 일정 등 짧은 확인형 응답이 정상인 업무는 false —
* footer 노이즈(false-positive) 방지. 블록 주입은 항상 수행.
*/
coverageCheck: boolean;
elements: RequirementElement[];
}
export interface CoverageResult {
ran: boolean;
taskId?: string;
taskLabel?: string;
covered: string[]; // element labels
missing: string[]; // element labels
}
/**
* 기본 업무 정의 4종. 배열 순서 = 감지 우선순위 (구체적 유형 먼저, 범용 '업무조사' 마지막 —
* "조사" 류 키워드가 시장조사를 가로채지 않도록).
*/
export const DEFAULT_TASK_REQUIREMENTS: TaskRequirement[] = [
{
id: 'meeting-minutes',
label: '회의록',
detectKeywords: ['회의록', '회의 ?(내용|결과)? ?정리', '미팅 ?(노트|정리)', 'meeting (minutes|notes)'],
coverageCheck: true,
elements: [
{
id: 'attendees', label: '참석자',
hint: '회의 참석 인원 전원. 불명확하면 "(확인 필요)".',
detectPatterns: ['참석자', '참석 ?인원', 'attendees?'],
},
{
id: 'decisions', label: '결정사항',
hint: '회의에서 합의·확정된 사항. 논의만 되고 미결인 항목과 구분.',
detectPatterns: ['결정 ?사항', '결정된', '합의', '확정', 'decisions?'],
},
{
id: 'action-items', label: '액션 아이템',
hint: '후속 실행 항목. 각 항목에 담당자·기한 연결.',
detectPatterns: ['액션 ?아이템', 'action ?items?', '할 ?일', '후속 ?(조치|작업)', 'to-?do'],
},
{
id: 'owners', label: '담당자',
hint: '액션 아이템별 책임자. 미정이면 "(담당자 미정)" 명시.',
detectPatterns: ['담당자?', '책임자', 'owner'],
},
{
id: 'due-dates', label: '기한',
hint: '액션 아이템별 마감일. 미정이면 "(기한 미정)" 명시.',
detectPatterns: ['기한', '마감', '까지', 'due', '\\d{1,2}\\s*월\\s*\\d{1,2}\\s*일'],
},
],
},
{
id: 'market-research',
label: '시장조사',
detectKeywords: ['시장 ?조사', '시장 ?분석', '시장 ?(규모|동향|현황)', 'market (research|analysis)'],
coverageCheck: true,
elements: [
{
id: 'market-size', label: '시장 규모',
hint: '금액/수량 기준 규모. 수치 출처 필수, 없으면 "(확인 필요)".',
detectPatterns: ['시장 ?규모', 'market ?size', '\\d+\\s*(억|조|만\\s*달러|billion|million)'],
},
{
id: 'growth', label: '성장률',
hint: '연 성장률(CAGR 등) 또는 성장 추세.',
detectPatterns: ['성장률', '성장세', 'CAGR', 'growth', '연평균'],
},
{
id: 'competitors', label: '경쟁사',
hint: '주요 플레이어와 각자의 포지션.',
detectPatterns: ['경쟁사', '경쟁 ?업체', '주요 ?(업체|기업|플레이어)', 'competitors?'],
},
{
id: 'pricing', label: '가격',
hint: '가격대·요금 구조.',
detectPatterns: ['가격', '요금', '단가', 'pricing', '원대', '달러'],
},
{
id: 'customer-needs', label: '고객 니즈',
hint: '고객 요구·페인 포인트.',
detectPatterns: ['니즈', '고객 ?(요구|수요)', '페인 ?포인트', 'needs', 'pain ?points?'],
},
{
id: 'trends', label: '트렌드',
hint: '시장 동향·변화 방향.',
detectPatterns: ['트렌드', '동향', '추세', 'trends?'],
},
{
id: 'sources', label: '출처',
hint: '핵심 수치·주장의 출처. 모델 일반 지식이면 그렇게 명시.',
detectPatterns: ['출처', '근거', 'source', '자료:', '참고'],
},
],
},
{
id: 'schedule',
label: '일정 관리',
detectKeywords: ['일정 ?(등록|추가|확인|조회|정리|관리)', '스케줄', '캘린더', '미팅 ?잡', '약속 ?(등록|추가|잡)'],
coverageCheck: false, // 짧은 확인형 응답이 정상 — footer 검사는 노이즈
elements: [
{
id: 'datetime', label: '일시',
hint: '날짜와 시간을 명시. 모호하면 되묻기.',
detectPatterns: ['\\d{1,2}\\s*[:시]', '날짜', '일시'],
},
{
id: 'title', label: '일정 제목',
hint: '무엇을 위한 일정인지.',
detectPatterns: ['제목', '일정명', '건명'],
},
{
id: 'conflict-check', label: '충돌 확인',
hint: '기존 일정과 겹침 여부 확인 결과 명시.',
detectPatterns: ['충돌', '겹치', '겹침'],
},
],
},
{
id: 'work-research',
label: '업무조사',
detectKeywords: ['업무 ?조사', '조사해', '리서치', '알아봐\\s*줘?', '서치해', 'research'],
coverageCheck: true,
elements: [
{
id: 'purpose', label: '조사 목적',
hint: '무엇을 알기 위한 조사인지 한 줄 명시.',
detectPatterns: ['목적', '배경', '알아보기 위해'],
},
{
id: 'summary', label: '핵심 요약',
hint: '결론 먼저 — 3줄 이내 요약.',
detectPatterns: ['요약', '핵심', '결론부터', 'TL;?DR', 'summary'],
},
{
id: 'details', label: '세부 내용',
hint: '요약을 뒷받침하는 상세 조사 내용.',
detectPatterns: ['상세', '세부', '구체적', '자세히'],
},
{
id: 'sources', label: '출처',
hint: '핵심 주장의 출처. 모델 일반 지식이면 그렇게 명시.',
detectPatterns: ['출처', '근거', 'source', '참고'],
},
{
id: 'implications', label: '시사점·다음 단계',
hint: '조사 결과가 의미하는 것과 권장 다음 행동.',
detectPatterns: ['시사점', '다음 ?단계', '권장', '제안', '결론'],
},
],
},
];
function toRegex(sources: string[]): RegExp {
return new RegExp(sources.join('|'), 'iu');
}
/**
* 사용자 요청에서 업무 유형 감지. 배열 순서대로 첫 매치 반환, 없으면 null.
* 짧은 인사·일반 잡담은 키워드 미매치로 자연스럽게 제외.
*/
export function detectTaskType(
userPrompt: string,
requirements: TaskRequirement[] = DEFAULT_TASK_REQUIREMENTS,
): TaskRequirement | null {
if (!userPrompt || !userPrompt.trim()) return null;
for (const req of requirements) {
if (toRegex(req.detectKeywords).test(userPrompt)) return req;
}
return null;
}
/**
* [TASK REQUIREMENTS] 시스템 프롬프트 블록 생성. 업무 유형 미감지 시 빈 문자열 —
* memoryContext 의 dynamicBlocks join 에서 자동 제외.
*/
export function buildRequirementGraphBlock(
userPrompt: string,
requirements: TaskRequirement[] = DEFAULT_TASK_REQUIREMENTS,
/** 과거 자주 누락된 요소 label — Reflection/Failure Pattern 이 공급 (T5: 같은 실수 반복 방지). */
emphasizeLabels: string[] = [],
): string {
const req = detectTaskType(userPrompt, requirements);
if (!req) return '';
const emphasize = new Set(emphasizeLabels);
const lines: string[] = [];
lines.push(`[TASK REQUIREMENTS — ${req.label}]`);
lines.push(`이 요청은 '${req.label}' 업무로 감지됨. 아래 필수 요소를 *모두* 포함해 작성할 것.`);
lines.push('정보가 없어 채울 수 없는 요소는 조용히 생략하지 말고 "(확인 필요)" 로 명시 후 사용자에게 질문.');
lines.push('');
for (const el of req.elements) {
const mark = emphasize.has(el.label) ? ' ⚠️ *과거에 자주 누락된 요소 — 특히 주의*' : '';
lines.push(`- [ ] **${el.label}** — ${el.hint}${mark}`);
}
lines.push('');
lines.push('제출 전 위 체크리스트를 스스로 점검하고, 누락 요소가 있으면 보완 후 답변할 것.');
lines.push('[/TASK REQUIREMENTS]');
return lines.join('\n');
}
/**
* 답변 커버리지 결정론적 검사 — 각 필수 요소의 detectPatterns 가 답변에 하나도 안 나타나면
* missing. LLM 호출 없음 (정규식), 매 turn 안전.
*
* 한계(의도된 보수성): 패턴 매치 = "요소가 언급됨" 이지 "내용이 충실함" 이 아님.
* 내용 충실도 평가는 Phase 3 Self Evaluation 담당.
*/
export function checkRequirementCoverage(
userPrompt: string,
assistantAnswer: string,
requirements: TaskRequirement[] = DEFAULT_TASK_REQUIREMENTS,
): CoverageResult {
const req = detectTaskType(userPrompt, requirements);
if (!req || !req.coverageCheck || !assistantAnswer || !assistantAnswer.trim()) {
return { ran: false, covered: [], missing: [] };
}
const covered: string[] = [];
const missing: string[] = [];
for (const el of req.elements) {
if (toRegex(el.detectPatterns).test(assistantAnswer)) covered.push(el.label);
else missing.push(el.label);
}
return { ran: true, taskId: req.id, taskLabel: req.label, covered, missing };
}
/**
* 커버리지 footer — 누락 있을 때만 문자열 반환 (전부 충족 시 빈 문자열, 노이즈 방지).
* termValidator footer 와 같은 위치(답변 아래 streamChunk)에 표시.
*/
export function formatRequirementCoverageFooter(result: CoverageResult): string {
if (!result.ran || result.missing.length === 0) return '';
const miss = result.missing.join(', ');
return `\n\n> ⚠️ **Requirement Check (${result.taskLabel})** — 누락 가능 요소: ${miss}. 해당 내용이 없었다면 "(확인 필요)" 로 표시하거나 추가 정보를 요청하세요.`;
}