// GUARDiA × Paperclip AI 에이전트 구현 보고서 PPT 생성 const pptxgen = require("pptxgenjs"); const pres = new pptxgen(); pres.layout = "LAYOUT_16x9"; pres.author = "GUARDiA Team"; pres.title = "GUARDiA × Paperclip AI 에이전트 구현 보고서"; // ─── 색상 팔레트 ───────────────────────────────────────── const C = { navyDark: "0D1B3E", // 타이틀 배경 navy: "1E2761", // 사이드바 blue: "2E5BBA", // 강조 파랑 lightBlue: "4A90D9", // 서브 강조 ice: "CADCFC", // 연한 파랑 (배경 요소) white: "FFFFFF", offWhite: "F4F7FF", // 콘텐츠 슬라이드 배경 gray: "64748B", lightGray: "E2E8F0", green: "059669", amber: "D97706", red: "DC2626", purple: "7C3AED", teal: "0891B2", orange: "EA580C", }; const makeShadow = () => ({ type: "outer", blur: 6, offset: 3, angle: 135, color: "000000", opacity: 0.12 }); // ─── 슬라이드 헬퍼 ─────────────────────────────────────── function addTitleSlide(bgColor, title, subtitle, badge) { const slide = pres.addSlide(); slide.background = { color: bgColor }; // 좌측 강조 바 slide.addShape(pres.shapes.RECTANGLE, { x: 0, y: 0, w: 0.12, h: 5.625, fill: { color: C.lightBlue }, line: { color: C.lightBlue } }); // 우측 장식 원 slide.addShape(pres.shapes.OVAL, { x: 7.5, y: -1.2, w: 4.5, h: 4.5, fill: { color: C.navy, transparency: 60 }, line: { color: C.navy, transparency: 60 } }); slide.addShape(pres.shapes.OVAL, { x: 8.5, y: 2.8, w: 2.5, h: 2.5, fill: { color: C.blue, transparency: 70 }, line: { color: C.blue, transparency: 70 } }); if (badge) { slide.addShape(pres.shapes.RECTANGLE, { x: 0.4, y: 1.3, w: 2.2, h: 0.38, fill: { color: C.lightBlue }, line: { color: C.lightBlue } }); slide.addText(badge, { x: 0.4, y: 1.3, w: 2.2, h: 0.38, fontSize: 11, color: C.white, bold: true, align: "center", valign: "middle", margin: 0 }); } slide.addText(title, { x: 0.4, y: 1.9, w: 8.5, h: 1.4, fontSize: 36, color: C.white, bold: true, align: "left", fontFace: "Calibri" }); if (subtitle) { slide.addText(subtitle, { x: 0.4, y: 3.45, w: 7.5, h: 0.7, fontSize: 16, color: C.ice, align: "left", fontFace: "Calibri" }); } // 하단 날짜 라인 slide.addShape(pres.shapes.LINE, { x: 0.4, y: 5.0, w: 9.2, h: 0, line: { color: C.lightBlue, width: 1 } }); slide.addText("2026.05.25 | GUARDiA ITSM", { x: 0.4, y: 5.1, w: 5, h: 0.35, fontSize: 10, color: "8FAADC", align: "left", fontFace: "Calibri" }); return slide; } function addContentSlide(title) { const slide = pres.addSlide(); slide.background = { color: C.offWhite }; // 상단 헤더 바 slide.addShape(pres.shapes.RECTANGLE, { x: 0, y: 0, w: 10, h: 0.72, fill: { color: C.navy }, line: { color: C.navy } }); // 좌측 포인트 바 slide.addShape(pres.shapes.RECTANGLE, { x: 0, y: 0.72, w: 0.08, h: 4.9, fill: { color: C.lightBlue }, line: { color: C.lightBlue } }); slide.addText(title, { x: 0.25, y: 0, w: 9.5, h: 0.72, fontSize: 20, color: C.white, bold: true, align: "left", valign: "middle", fontFace: "Calibri", margin: 0 }); return slide; } function addSectionSlide(number, title, subtitle) { const slide = pres.addSlide(); slide.background = { color: C.blue }; slide.addShape(pres.shapes.RECTANGLE, { x: 0, y: 0, w: 0.08, h: 5.625, fill: { color: C.lightBlue }, line: { color: C.lightBlue } }); slide.addText(`Phase ${number}`, { x: 0.4, y: 1.4, w: 9, h: 0.7, fontSize: 16, color: C.ice, bold: false, fontFace: "Calibri" }); slide.addText(title, { x: 0.4, y: 2.0, w: 9, h: 1.2, fontSize: 32, color: C.white, bold: true, fontFace: "Calibri" }); if (subtitle) { slide.addText(subtitle, { x: 0.4, y: 3.3, w: 8, h: 0.7, fontSize: 15, color: C.ice, fontFace: "Calibri" }); } return slide; } function card(slide, x, y, w, h, headerColor, headerText, bodyLines) { // 카드 본체 slide.addShape(pres.shapes.RECTANGLE, { x, y, w, h, fill: { color: C.white }, line: { color: C.lightGray, width: 1 }, shadow: makeShadow() }); // 카드 상단 컬러 바 slide.addShape(pres.shapes.RECTANGLE, { x, y, w, h: 0.32, fill: { color: headerColor }, line: { color: headerColor } }); slide.addText(headerText, { x, y, w, h: 0.32, fontSize: 11, color: C.white, bold: true, align: "center", valign: "middle", fontFace: "Calibri", margin: 0 }); const textArr = bodyLines.map((line, i) => ({ text: line, options: { fontSize: 10.5, color: i === 0 ? C.gray : "1E293B", bold: i === 0, breakLine: true } })); // 마지막 요소는 breakLine 제거 if (textArr.length > 0) textArr[textArr.length - 1].options.breakLine = false; slide.addText(textArr, { x: x + 0.1, y: y + 0.38, w: w - 0.2, h: h - 0.45, valign: "top", fontFace: "Calibri" }); } function infoBox(slide, x, y, w, h, accentColor, title, desc) { slide.addShape(pres.shapes.RECTANGLE, { x, y, w, h, fill: { color: C.white }, line: { color: accentColor, width: 2 }, shadow: makeShadow() }); slide.addShape(pres.shapes.RECTANGLE, { x, y, w: 0.1, h, fill: { color: accentColor }, line: { color: accentColor } }); slide.addText(title, { x: x + 0.18, y: y + 0.07, w: w - 0.25, h: 0.3, fontSize: 11.5, color: "1E293B", bold: true, fontFace: "Calibri" }); slide.addText(desc, { x: x + 0.18, y: y + 0.36, w: w - 0.25, h: h - 0.42, fontSize: 10.5, color: C.gray, fontFace: "Calibri", valign: "top" }); } // ═══════════════════════════════════════════════════════ // Slide 1: 타이틀 // ═══════════════════════════════════════════════════════ addTitleSlide( C.navyDark, "GUARDiA × Paperclip\nAI 에이전트 구현 보고서", "Phase 1~4 설계·구현·테스트 완료 보고", "2026.05.25 완료" ); // ═══════════════════════════════════════════════════════ // Slide 2: 목차 // ═══════════════════════════════════════════════════════ { const slide = addContentSlide("목 차"); const items = [ { n: "01", t: "GUARDiA ITSM 개요 & 배경", c: C.blue }, { n: "02", t: "Paperclip 프레임워크 소개", c: C.purple }, { n: "03", t: "Phase 1 — Paperclip 개발 도구 설정", c: C.teal }, { n: "04", t: "Phase 2 — Ollama 로컬 LLM 구성", c: C.green }, { n: "05", t: "Phase 3 — GUARDiA 에이전트 엔진", c: C.orange }, { n: "06", t: "Phase 4 — 자율 운영 대시보드", c: C.blue }, { n: "07", t: "테스트 결과 요약", c: C.green }, { n: "08", t: "보안 제약사항 & 향후 로드맵", c: C.navy }, ]; items.forEach((item, i) => { const col = i < 4 ? 0 : 1; const row = i % 4; const x = 0.3 + col * 4.85; const y = 0.88 + row * 1.1; slide.addShape(pres.shapes.RECTANGLE, { x, y, w: 4.5, h: 0.9, fill: { color: C.white }, line: { color: C.lightGray, width: 1 }, shadow: makeShadow() }); slide.addShape(pres.shapes.RECTANGLE, { x, y, w: 0.55, h: 0.9, fill: { color: item.c }, line: { color: item.c } }); slide.addText(item.n, { x, y, w: 0.55, h: 0.9, fontSize: 16, color: C.white, bold: true, align: "center", valign: "middle", margin: 0, fontFace: "Calibri" }); slide.addText(item.t, { x: x + 0.62, y, w: 3.8, h: 0.9, fontSize: 12.5, color: "1E293B", bold: false, align: "left", valign: "middle", fontFace: "Calibri" }); }); } // ═══════════════════════════════════════════════════════ // Slide 3: GUARDiA ITSM 개요 // ═══════════════════════════════════════════════════════ { const slide = addContentSlide("01 GUARDiA ITSM 개요"); // 왼쪽: 기존 기능 slide.addText("기존 GUARDiA 기능", { x: 0.25, y: 0.85, w: 4.5, h: 0.4, fontSize: 13, color: C.navy, bold: true, fontFace: "Calibri" }); const existingFeatures = [ ["SR 접수·처리", "서비스 요청 워크플로우"], ["SSL 인증서 관리", "만료 감시 & 갱신 알림"], ["PM 정기점검", "예방 유지보수 일정"], ["SI 프로젝트 관리", "WBS·이슈·리스크·테스트"], ["CMDB", "서버 자산 관리"], ["메신저·알림", "내부 커뮤니케이션"], ]; existingFeatures.forEach(([title, desc], i) => { const y = 1.3 + i * 0.63; slide.addShape(pres.shapes.RECTANGLE, { x: 0.25, y, w: 4.5, h: 0.55, fill: { color: C.offWhite }, line: { color: C.lightGray, width: 1 } }); slide.addShape(pres.shapes.RECTANGLE, { x: 0.25, y, w: 0.07, h: 0.55, fill: { color: C.blue }, line: { color: C.blue } }); slide.addText([ { text: title, options: { bold: true, color: "1E293B", breakLine: true } }, { text: desc, options: { color: C.gray } } ], { x: 0.4, y, w: 4.2, h: 0.55, fontSize: 10.5, valign: "middle", fontFace: "Calibri" }); }); // 화살표 slide.addShape(pres.shapes.RECTANGLE, { x: 4.85, y: 2.5, w: 0.35, h: 0.6, fill: { color: C.lightBlue }, line: { color: C.lightBlue } }); slide.addText("AI\n추가", { x: 4.82, y: 2.5, w: 0.41, h: 0.6, fontSize: 9, color: C.white, bold: true, align: "center", valign: "middle", fontFace: "Calibri", margin: 0 }); // 오른쪽: AI 추가 목표 slide.addText("AI 에이전트 추가 목표", { x: 5.3, y: 0.85, w: 4.4, h: 0.4, fontSize: 13, color: C.navy, bold: true, fontFace: "Calibri" }); const aiGoals = [ { icon: "⚡", title: "반복 업무 자동화", desc: "장애 분류, KB 등록, SR 자동 생성", color: C.orange }, { icon: "👁", title: "능동적 모니터링", desc: "SSL·WBS·PM 상태를 AI가 주기 감시", color: C.blue }, { icon: "🤝", title: "사람-AI 협업", desc: "고위험 액션은 사람 승인 게이트 통과", color: C.green }, { icon: "🔒", title: "완전 온프레미스", desc: "Ollama 로컬 LLM, 외부 API 완전 차단", color: C.purple }, ]; aiGoals.forEach((g, i) => { const y = 1.3 + i * 0.98; slide.addShape(pres.shapes.RECTANGLE, { x: 5.3, y, w: 4.4, h: 0.85, fill: { color: C.white }, line: { color: g.color, width: 2 }, shadow: makeShadow() }); slide.addShape(pres.shapes.RECTANGLE, { x: 5.3, y, w: 0.1, h: 0.85, fill: { color: g.color }, line: { color: g.color } }); slide.addText([ { text: `${g.icon} ${g.title}`, options: { bold: true, color: "1E293B", breakLine: true } }, { text: g.desc, options: { color: C.gray } } ], { x: 5.48, y, w: 4.1, h: 0.85, fontSize: 11, valign: "middle", fontFace: "Calibri" }); }); } // ═══════════════════════════════════════════════════════ // Slide 4: Paperclip 프레임워크 소개 // ═══════════════════════════════════════════════════════ { const slide = addContentSlide("02 Paperclip 프레임워크 소개"); // 메인 설명 slide.addShape(pres.shapes.RECTANGLE, { x: 0.25, y: 0.85, w: 9.5, h: 0.72, fill: { color: "EEF2FF" }, line: { color: C.lightBlue, width: 1 } }); slide.addText([ { text: "Paperclip", options: { bold: true, color: C.blue } }, { text: " — AI 에이전트 오케스트레이션 오픈소스 프레임워크 (github.com/paperclipai/paperclip)", options: { color: "1E293B" } } ], { x: 0.35, y: 0.85, w: 9.3, h: 0.72, fontSize: 13, valign: "middle", fontFace: "Calibri" }); const features = [ { title: "조직도 구조", desc: "CEO → CTO → 개발자/QA\n계층적 에이전트 관리", color: C.purple, icon: "🏢" }, { title: "하트비트 시스템", desc: "에이전트가 주기적으로\n깨어나 작업 수행 후 대기", color: C.blue, icon: "💓" }, { title: "이슈 추적", desc: "GitHub 스타일\n태스크/이슈 관리", color: C.teal, icon: "📋" }, { title: "거버넌스 게이트", desc: "위험 수준에 따른\n사람 승인 워크플로우", color: C.orange, icon: "🔐" }, ]; features.forEach((f, i) => { const x = 0.25 + i * 2.4; slide.addShape(pres.shapes.RECTANGLE, { x, y: 1.72, w: 2.25, h: 1.95, fill: { color: C.white }, line: { color: C.lightGray, width: 1 }, shadow: makeShadow() }); slide.addShape(pres.shapes.RECTANGLE, { x, y: 1.72, w: 2.25, h: 0.45, fill: { color: f.color }, line: { color: f.color } }); slide.addText(`${f.icon} ${f.title}`, { x, y: 1.72, w: 2.25, h: 0.45, fontSize: 11, color: C.white, bold: true, align: "center", valign: "middle", margin: 0, fontFace: "Calibri" }); slide.addText(f.desc, { x: x + 0.1, y: 2.22, w: 2.05, h: 1.4, fontSize: 11, color: "1E293B", align: "center", valign: "top", fontFace: "Calibri" }); }); // GUARDiA 적용 전략 slide.addText("GUARDiA 적용 전략", { x: 0.25, y: 3.8, w: 9.5, h: 0.35, fontSize: 13, color: C.navy, bold: true, fontFace: "Calibri" }); const strategies = [ ["개발 시", "Paperclip CLI로 에이전트 페르소나 정의 (CEO/CTO/Dev/QA)", C.purple], ["런타임", "AgentEngine (Python) 내장 — 외부 Paperclip 서버 불필요", C.blue], ["LLM", "Ollama localhost:11434 전용 — 외부 LLM API 완전 차단", C.red], ]; strategies.forEach(([label, desc, color], i) => { const x = 0.25 + i * 3.2; slide.addShape(pres.shapes.RECTANGLE, { x, y: 4.2, w: 3.0, h: 1.05, fill: { color: C.white }, line: { color: color, width: 2 }, shadow: makeShadow() }); slide.addText([ { text: label, options: { bold: true, color, breakLine: true } }, { text: desc, options: { color: "1E293B" } } ], { x: x + 0.1, y: 4.2, w: 2.8, h: 1.05, fontSize: 10.5, valign: "middle", fontFace: "Calibri" }); }); } // ═══════════════════════════════════════════════════════ // Slide 5: Phase 1 섹션 표지 // ═══════════════════════════════════════════════════════ addSectionSlide("1", "Paperclip 개발 도구 설정", "조직도 구성 · 에이전트 페르소나 · 거버넌스 규칙"); // ═══════════════════════════════════════════════════════ // Slide 6: Phase 1 상세 // ═══════════════════════════════════════════════════════ { const slide = addContentSlide("03 Phase 1 — Paperclip 개발 도구 설정"); // 조직도 트리 slide.addText("에이전트 조직도", { x: 0.25, y: 0.85, w: 4.5, h: 0.35, fontSize: 13, color: C.navy, bold: true, fontFace: "Calibri" }); const orgNodes = [ { label: "CEO", x: 1.9, y: 1.3, color: C.purple }, { label: "CTO", x: 0.5, y: 2.4, color: C.blue }, { label: "PM_AGENT", x: 3.3, y: 2.4, color: C.teal }, { label: "DEVELOPER", x: 0.0, y: 3.5, color: C.green }, { label: "QA", x: 1.2, y: 3.5, color: C.amber }, ]; // 연결선 slide.addShape(pres.shapes.LINE, { x: 2.4, y: 1.9, w: 0, h: 0.5, line: { color: C.gray, width: 1.5 } }); slide.addShape(pres.shapes.LINE, { x: 1.5, y: 2.25, w: 1.8, h: 0, line: { color: C.gray, width: 1.5 } }); slide.addShape(pres.shapes.LINE, { x: 1.5, y: 2.25, w: 0, h: 0.5, line: { color: C.gray, width: 1.5 } }); slide.addShape(pres.shapes.LINE, { x: 3.3, y: 2.25, w: 0, h: 0.5, line: { color: C.gray, width: 1.5 } }); slide.addShape(pres.shapes.LINE, { x: 0.65, y: 3.05, w: 1.55, h: 0, line: { color: C.gray, width: 1.5 } }); slide.addShape(pres.shapes.LINE, { x: 0.65, y: 3.05, w: 0, h: 0.5, line: { color: C.gray, width: 1.5 } }); slide.addShape(pres.shapes.LINE, { x: 1.7, y: 3.05, w: 0, h: 0.5, line: { color: C.gray, width: 1.5 } }); orgNodes.forEach(node => { slide.addShape(pres.shapes.RECTANGLE, { x: node.x, y: node.y, w: 1.2, h: 0.52, fill: { color: node.color }, line: { color: node.color }, shadow: makeShadow() }); slide.addText(node.label, { x: node.x, y: node.y, w: 1.2, h: 0.52, fontSize: 10, color: C.white, bold: true, align: "center", valign: "middle", margin: 0, fontFace: "Calibri" }); }); // 파일 구조 slide.addText("생성 파일 구조", { x: 5.0, y: 0.85, w: 4.7, h: 0.35, fontSize: 13, color: C.navy, bold: true, fontFace: "Calibri" }); slide.addShape(pres.shapes.RECTANGLE, { x: 5.0, y: 1.25, w: 4.7, h: 3.2, fill: { color: "1E293B" }, line: { color: "334155", width: 1 } }); slide.addText([ { text: "C:\\GUARDiA\\paperclip\\", options: { color: C.lightBlue, bold: true, breakLine: true } }, { text: "├─ paperclip.config.json", options: { color: "A3E635", breakLine: true } }, { text: "│ (조직도 · LLM 설정 · 거버넌스)", options: { color: "94A3B8", breakLine: true } }, { text: "├─ README.md", options: { color: "A3E635", breakLine: true } }, { text: "└─ agents/", options: { color: "FB923C", breakLine: true } }, { text: " ├─ ceo.md", options: { color: "A3E635", breakLine: true } }, { text: " ├─ cto.md", options: { color: "A3E635", breakLine: true } }, { text: " ├─ developer.md", options: { color: "A3E635", breakLine: true } }, { text: " └─ qa.md", options: { color: "A3E635" } }, ], { x: 5.15, y: 1.35, w: 4.4, h: 3.0, fontSize: 10.5, valign: "top", fontFace: "Courier New" }); // 거버넌스 표 slide.addText("거버넌스 규칙", { x: 0.25, y: 4.1, w: 4.5, h: 0.32, fontSize: 11, color: C.navy, bold: true, fontFace: "Calibri" }); const govRows = [ [{ text: "액션", options: { bold: true, fill: { color: C.navy }, color: C.white } }, { text: "승인 방식", options: { bold: true, fill: { color: C.navy }, color: C.white } }], ["code_commit / deploy", "사람 승인 필수"], ["delete_data", "CEO 승인 필수"], ["CRITICAL 장애 분류", "담당자 승인 필수"], ["KB 등록 / SSL SR", "자동 승인"], ]; slide.addTable(govRows, { x: 0.25, y: 4.45, w: 4.5, h: 1.0, colW: [2.5, 2.0], border: { pt: 1, color: C.lightGray }, fontSize: 10, fontFace: "Calibri", fill: { color: C.white } }); } // ═══════════════════════════════════════════════════════ // Slide 7: Phase 2 섹션 표지 // ═══════════════════════════════════════════════════════ addSectionSlide("2", "Ollama 로컬 LLM 설정", "guardia-agent 커스텀 모델 · 보안 온프레미스 추론"); // ═══════════════════════════════════════════════════════ // Slide 8: Phase 2 상세 // ═══════════════════════════════════════════════════════ { const slide = addContentSlide("04 Phase 2 — Ollama 로컬 LLM"); // 왼쪽: 아키텍처 slide.addText("추론 아키텍처", { x: 0.25, y: 0.85, w: 4.8, h: 0.35, fontSize: 13, color: C.navy, bold: true, fontFace: "Calibri" }); const archItems = [ { label: "GUARDiA AgentEngine", color: C.blue, y: 1.3 }, { label: "OllamaClient (HTTP)", color: C.teal, y: 2.0 }, { label: "Ollama Server :11434", color: C.green, y: 2.7 }, { label: "guardia-agent 모델", color: C.purple, y: 3.4 }, ]; archItems.forEach((item, i) => { slide.addShape(pres.shapes.RECTANGLE, { x: 0.5, y: item.y, w: 3.8, h: 0.55, fill: { color: item.color }, line: { color: item.color }, shadow: makeShadow() }); slide.addText(item.label, { x: 0.5, y: item.y, w: 3.8, h: 0.55, fontSize: 12, color: C.white, bold: true, align: "center", valign: "middle", margin: 0, fontFace: "Calibri" }); if (i < archItems.length - 1) { slide.addShape(pres.shapes.RECTANGLE, { x: 2.25, y: item.y + 0.55, w: 0.3, h: 0.15, fill: { color: C.gray }, line: { color: C.gray } }); } }); // 보안 배지 slide.addShape(pres.shapes.RECTANGLE, { x: 0.25, y: 4.1, w: 4.5, h: 0.9, fill: { color: "FEF2F2" }, line: { color: C.red, width: 2 } }); slide.addText([ { text: "🔒 외부 LLM API 완전 차단", options: { bold: true, color: C.red, breakLine: true } }, { text: "모든 AI 추론은 localhost:11434 (Ollama) 만 허용", options: { color: "7F1D1D" } } ], { x: 0.4, y: 4.1, w: 4.3, h: 0.9, fontSize: 12, valign: "middle", fontFace: "Calibri" }); // 오른쪽: 모델 설정 slide.addText("guardia-agent 모델 파라미터", { x: 5.05, y: 0.85, w: 4.7, h: 0.35, fontSize: 13, color: C.navy, bold: true, fontFace: "Calibri" }); slide.addShape(pres.shapes.RECTANGLE, { x: 5.05, y: 1.25, w: 4.7, h: 2.0, fill: { color: "1E293B" }, line: { color: "334155", width: 1 } }); slide.addText([ { text: "FROM llama3.1:8b", options: { color: "A3E635", breakLine: true } }, { text: "SYSTEM \"\"\"", options: { color: "FB923C", breakLine: true } }, { text: " GUARDiA ITSM AI 운영 에이전트", options: { color: "94A3B8", breakLine: true } }, { text: " 한국어 응답 · ITSM 전문화", options: { color: "94A3B8", breakLine: true } }, { text: " 외부 API 호출 금지", options: { color: C.red, breakLine: true } }, { text: "\"\"\"", options: { color: "FB923C", breakLine: true } }, { text: "PARAMETER temperature 0.2", options: { color: "38BDF8", breakLine: true } }, { text: "PARAMETER num_predict 2048", options: { color: "38BDF8" } }, ], { x: 5.2, y: 1.35, w: 4.4, h: 1.8, fontSize: 10.5, valign: "top", fontFace: "Courier New" }); // OllamaClient API slide.addText("OllamaClient API", { x: 5.05, y: 3.35, w: 4.7, h: 0.35, fontSize: 13, color: C.navy, bold: true, fontFace: "Calibri" }); const methods = [ ["health_check()", "서버 상태 확인"], ["resolve_model(preferred)", "fallback 모델 선택"], ["json_generate(prompt)", "JSON 추출 (코드블록 자동 제거)"], ["pull_model(model)", "모델 다운로드"], ]; methods.forEach(([method, desc], i) => { slide.addShape(pres.shapes.RECTANGLE, { x: 5.05, y: 3.75 + i * 0.43, w: 4.7, h: 0.38, fill: { color: i % 2 === 0 ? C.offWhite : C.white }, line: { color: C.lightGray, width: 1 } }); slide.addText([ { text: method, options: { bold: true, color: C.blue } }, { text: " → " + desc, options: { color: C.gray } } ], { x: 5.15, y: 3.75 + i * 0.43, w: 4.5, h: 0.38, fontSize: 10, valign: "middle", fontFace: "Calibri" }); }); } // ═══════════════════════════════════════════════════════ // Slide 9: Phase 3 섹션 표지 // ═══════════════════════════════════════════════════════ addSectionSlide("3", "GUARDiA 에이전트 엔진", "6종 자율 에이전트 · 하트비트 사이클 · 승인 게이트"); // ═══════════════════════════════════════════════════════ // Slide 10: Phase 3 — 에이전트 종류 // ═══════════════════════════════════════════════════════ { const slide = addContentSlide("05 Phase 3 — 6종 에이전트 역할"); const agents = [ { role: "INCIDENT_TRIAGE", schedule: "매 15분", color: C.orange, lines: ["미배정 장애 자동 분류", "severity / category / reason", "CRITICAL → 사람 승인"] }, { role: "KB_CURATOR", schedule: "매시간 정각", color: C.teal, lines: ["완료 SR → KB 자동 생성", "증상·원인·해결책 초안", "published=False (검토 대기)"] }, { role: "SSL_WATCHER", schedule: "매일 08:30", color: C.red, lines: ["SSL 만료 0~30일 서버 감시", "자동 갱신 SR 생성", "7일↓=CRITICAL, 30일↓=HIGH"] }, { role: "WBS_MONITOR", schedule: "매일 08:00", color: C.blue, lines: ["SI WBS 지연 감지", "3일+ 주의, 10일+ CRITICAL", "리스크 자동 등록"] }, { role: "PM_SUGGESTER", schedule: "매일 09:00", color: C.green, lines: ["PM 미등록 서버 탐지", "권장 점검 일정 제안", "AgentTask 기록"] }, { role: "DEVELOPER", schedule: "수동 트리거", color: C.purple, lines: ["PENDING 태스크 처리", "LLM 코드 생성", "CODE_CHANGE → 사람 승인"] }, ]; agents.forEach((a, i) => { const col = i % 3; const row = Math.floor(i / 3); const x = 0.25 + col * 3.25; const y = 0.88 + row * 2.35; slide.addShape(pres.shapes.RECTANGLE, { x, y, w: 3.1, h: 2.18, fill: { color: C.white }, line: { color: C.lightGray, width: 1 }, shadow: makeShadow() }); slide.addShape(pres.shapes.RECTANGLE, { x, y, w: 3.1, h: 0.5, fill: { color: a.color }, line: { color: a.color } }); slide.addText(a.role, { x, y, w: 2.3, h: 0.5, fontSize: 11, color: C.white, bold: true, align: "left", valign: "middle", fontFace: "Calibri", indent: 0.1, margin: 0 }); slide.addText(a.schedule, { x: x + 2.3, y, w: 0.8, h: 0.5, fontSize: 9, color: "FFEAA7", align: "center", valign: "middle", fontFace: "Calibri", margin: 0 }); a.lines.forEach((line, li) => { slide.addText([ { text: "▸ ", options: { color: a.color, bold: true } }, { text: line, options: { color: "1E293B" } } ], { x: x + 0.1, y: y + 0.55 + li * 0.52, w: 2.9, h: 0.48, fontSize: 10.5, valign: "top", fontFace: "Calibri" }); }); }); } // ═══════════════════════════════════════════════════════ // Slide 11: Phase 3 — 하트비트 & 승인 게이트 // ═══════════════════════════════════════════════════════ { const slide = addContentSlide("05 Phase 3 — 하트비트 & 승인 게이트"); // 하트비트 플로우 slide.addText("하트비트 사이클", { x: 0.25, y: 0.85, w: 4.7, h: 0.35, fontSize: 13, color: C.navy, bold: true, fontFace: "Calibri" }); const flowSteps = [ { label: "APScheduler\nCron 트리거", color: C.blue, y: 1.3 }, { label: "status = ACTIVE", color: C.teal, y: 2.05 }, { label: "Ollama\nhealth_check()", color: C.green, y: 2.8 }, { label: "_handler() 실행", color: C.orange, y: 3.55 }, { label: "status = IDLE\n(완료)", color: C.purple, y: 4.3 }, ]; flowSteps.forEach((step, i) => { slide.addShape(pres.shapes.RECTANGLE, { x: 0.5, y: step.y, w: 3.0, h: 0.6, fill: { color: step.color }, line: { color: step.color }, shadow: makeShadow() }); slide.addText(step.label, { x: 0.5, y: step.y, w: 3.0, h: 0.6, fontSize: 10.5, color: C.white, bold: true, align: "center", valign: "middle", margin: 0, fontFace: "Calibri" }); if (i < flowSteps.length - 1) { slide.addShape(pres.shapes.RECTANGLE, { x: 1.85, y: step.y + 0.6, w: 0.3, h: 0.15, fill: { color: C.gray }, line: { color: C.gray } }); } }); // 오른쪽: 승인 게이트 slide.addText("승인 게이트 설계", { x: 4.0, y: 0.85, w: 5.7, h: 0.35, fontSize: 13, color: C.navy, bold: true, fontFace: "Calibri" }); // AUTO_APPROVED slide.addShape(pres.shapes.RECTANGLE, { x: 4.0, y: 1.25, w: 5.7, h: 1.3, fill: { color: "F0FDF4" }, line: { color: C.green, width: 2 } }); slide.addShape(pres.shapes.RECTANGLE, { x: 4.0, y: 1.25, w: 0.1, h: 1.3, fill: { color: C.green }, line: { color: C.green } }); slide.addText([ { text: "✅ AUTO_APPROVED ", options: { bold: true, color: C.green } }, { text: "— 사람 개입 없이 즉시 실행", options: { color: "166534" } } ], { x: 4.18, y: 1.3, w: 5.4, h: 0.35, fontSize: 12, fontFace: "Calibri" }); slide.addText([ { text: "• KB 등록 초안 생성", options: { breakLine: true } }, { text: "• SSL 갱신 SR 생성 ", options: { breakLine: true } }, { text: "• PM 일정 제안 ", options: { breakLine: true } }, { text: "• WBS 리스크 등록 (일반)", options: {} }, ], { x: 4.18, y: 1.68, w: 5.4, h: 0.82, fontSize: 10.5, color: "166534", valign: "top", fontFace: "Calibri" }); // PENDING (사람 승인) slide.addShape(pres.shapes.RECTANGLE, { x: 4.0, y: 2.7, w: 5.7, h: 1.5, fill: { color: "FFF7ED" }, line: { color: C.red, width: 2 } }); slide.addShape(pres.shapes.RECTANGLE, { x: 4.0, y: 2.7, w: 0.1, h: 1.5, fill: { color: C.red }, line: { color: C.red } }); slide.addText([ { text: "⏳ PENDING ", options: { bold: true, color: C.red } }, { text: "— 담당자 승인 후 실행", options: { color: "7C2D12" } } ], { x: 4.18, y: 2.75, w: 5.4, h: 0.35, fontSize: 12, fontFace: "Calibri" }); slide.addText([ { text: "• CRITICAL 장애 분류 적용", options: { breakLine: true } }, { text: "• CODE_CHANGE (개발자 에이전트)", options: { breakLine: true } }, { text: "• CRITICAL 리스크 등록 (WBS 10일+ 지연)", options: { breakLine: true } }, { text: "• code_commit / deploy / delete_data", options: {} } ], { x: 4.18, y: 3.12, w: 5.4, h: 1.0, fontSize: 10.5, color: "7C2D12", valign: "top", fontFace: "Calibri" }); // 데이터 모델 요약 slide.addText("DB 테이블 구조", { x: 4.0, y: 4.35, w: 5.7, h: 0.32, fontSize: 11, color: C.navy, bold: true, fontFace: "Calibri" }); const tables = [ ["tb_agent_config", "에이전트 설정 · 상태 · 통계"], ["tb_agent_task", "실행된 태스크 · LLM 입출력"], ["tb_agent_approval", "승인 대기·완료 기록"], ]; tables.forEach(([t, d], i) => { slide.addShape(pres.shapes.RECTANGLE, { x: 4.0 + i * 1.9, y: 4.7, w: 1.8, h: 0.7, fill: { color: C.navy }, line: { color: C.navy }, shadow: makeShadow() }); slide.addText([ { text: t, options: { bold: true, color: C.ice, breakLine: true } }, { text: d, options: { color: "8FAADC" } } ], { x: 4.05 + i * 1.9, y: 4.72, w: 1.7, h: 0.66, fontSize: 9, align: "center", valign: "middle", fontFace: "Calibri" }); }); } // ═══════════════════════════════════════════════════════ // Slide 12: Phase 4 섹션 표지 // ═══════════════════════════════════════════════════════ addSectionSlide("4", "자율 운영 대시보드", "agents.html SPA · 실시간 상태 · 승인 워크플로우"); // ═══════════════════════════════════════════════════════ // Slide 13: Phase 4 상세 // ═══════════════════════════════════════════════════════ { const slide = addContentSlide("06 Phase 4 — 자율 운영 대시보드"); // 접근 경로 배너 slide.addShape(pres.shapes.RECTANGLE, { x: 0.25, y: 0.85, w: 9.5, h: 0.5, fill: { color: C.navy }, line: { color: C.navy } }); slide.addText([ { text: "접근: ", options: { color: C.ice } }, { text: "http://localhost:8001/agents", options: { bold: true, color: "A3E635" } }, { text: " | 자동 갱신: 30초 간격", options: { color: C.ice } } ], { x: 0.35, y: 0.85, w: 9.3, h: 0.5, fontSize: 12, valign: "middle", fontFace: "Calibri" }); // 대시보드 구성 카드 const panels = [ { title: "LLM 상태 배너", desc: "Ollama 온라인/오프라인\n실시간 연결 상태 표시", color: C.teal }, { title: "통계 카드 (5종)", desc: "총 에이전트·활성·오늘\n태스크·토큰·승인 대기", color: C.blue }, { title: "에이전트 탭", desc: "역할 배지 + 상태 펄스\n하트비트/정지/재개 버튼", color: C.purple }, { title: "조직도 탭", desc: "계층적 트리 렌더링\n에이전트 관계 시각화", color: C.orange }, { title: "승인 대기 탭", desc: "PENDING 승인 목록\nCRITICAL 강조 표시", color: C.red }, { title: "태스크 피드 탭", desc: "전체 에이전트 태스크\n실시간 피드 통합 뷰", color: C.green }, ]; panels.forEach((p, i) => { const col = i % 3; const row = Math.floor(i / 3); const x = 0.25 + col * 3.25; const y = 1.48 + row * 1.85; slide.addShape(pres.shapes.RECTANGLE, { x, y, w: 3.1, h: 1.65, fill: { color: C.white }, line: { color: C.lightGray, width: 1 }, shadow: makeShadow() }); slide.addShape(pres.shapes.RECTANGLE, { x, y, w: 3.1, h: 0.4, fill: { color: p.color }, line: { color: p.color } }); slide.addText(p.title, { x, y, w: 3.1, h: 0.4, fontSize: 11, color: C.white, bold: true, align: "center", valign: "middle", margin: 0, fontFace: "Calibri" }); slide.addText(p.desc, { x: x + 0.1, y: y + 0.45, w: 2.9, h: 1.15, fontSize: 11, color: "1E293B", align: "center", valign: "middle", fontFace: "Calibri" }); }); // API 엔드포인트 수 slide.addShape(pres.shapes.RECTANGLE, { x: 0.25, y: 5.15, w: 9.5, h: 0.35, fill: { color: "EEF2FF" }, line: { color: C.lightBlue, width: 1 } }); slide.addText("REST API: 16개 엔드포인트 | GET·POST·PATCH·DELETE | CUSTOMER 역할 전체 차단 | RBAC 기반 권한 제어", { x: 0.35, y: 5.15, w: 9.3, h: 0.35, fontSize: 10.5, color: C.blue, align: "center", valign: "middle", fontFace: "Calibri" }); } // ═══════════════════════════════════════════════════════ // Slide 14: 테스트 결과 // ═══════════════════════════════════════════════════════ { const slide = addContentSlide("07 테스트 결과 요약"); // 구문 검사 결과 slide.addText("구문 검사 (Python Syntax Check)", { x: 0.25, y: 0.85, w: 9.5, h: 0.35, fontSize: 13, color: C.navy, bold: true, fontFace: "Calibri" }); const syntaxFiles = [ "models.py", "main.py", "core/llm_client.py", "core/agents.py", "core/scheduler.py", "routers/agents.py" ]; syntaxFiles.forEach((f, i) => { const x = 0.25 + (i % 3) * 3.25; const y = 1.25 + Math.floor(i / 3) * 0.6; slide.addShape(pres.shapes.RECTANGLE, { x, y, w: 3.1, h: 0.5, fill: { color: "F0FDF4" }, line: { color: C.green, width: 1 } }); slide.addText([ { text: "✅ ", options: { color: C.green, bold: true } }, { text: f, options: { color: "1E293B" } } ], { x: x + 0.1, y, w: 2.9, h: 0.5, fontSize: 11, valign: "middle", fontFace: "Calibri" }); }); // 심볼 검증 slide.addText("심볼 검증", { x: 0.25, y: 2.55, w: 4.7, h: 0.35, fontSize: 13, color: C.navy, bold: true, fontFace: "Calibri" }); const symbols = [ ["AgentRole enum", "10가지 역할", C.purple], ["AgentEngine 핸들러", "6개 구현", C.orange], ["OllamaClient 메서드", "7개 구현", C.teal], ["API 엔드포인트", "16개 라우터", C.blue], ["스케줄러 잡", "9개 (기존4+신규5)", C.green], ["main.py 라우터", "34개 등록", C.navy], ]; symbols.forEach((s, i) => { const x = 0.25 + (i % 2) * 2.45; const y = 2.95 + Math.floor(i / 2) * 0.6; slide.addShape(pres.shapes.RECTANGLE, { x, y, w: 2.3, h: 0.52, fill: { color: C.white }, line: { color: s[2], width: 2 }, shadow: makeShadow() }); slide.addText([ { text: s[0] + "\n", options: { bold: true, color: "1E293B", fontSize: 10 } }, { text: s[1], options: { color: s[2], bold: true, fontSize: 11 } } ], { x: x + 0.08, y, w: 2.14, h: 0.52, valign: "middle", fontFace: "Calibri" }); }); // 스케줄러 잡 목록 slide.addText("스케줄러 잡 (총 9개)", { x: 5.2, y: 2.55, w: 4.5, h: 0.35, fontSize: 13, color: C.navy, bold: true, fontFace: "Calibri" }); const jobs = [ ["cert_check", "매일 09:00", C.gray], ["pm_check", "매일 09:05", C.gray], ["on_call_notify", "매일 08:55", C.gray], ["batch_cleanup", "매일 02:00", C.gray], ["agent_incident_triage", "매 15분", C.orange], ["agent_kb_curator", "매시간", C.teal], ["agent_ssl_watcher", "매일 08:30", C.red], ["agent_wbs_monitor", "매일 08:00", C.blue], ["agent_pm_suggester", "매일 09:00", C.green], ]; jobs.forEach((j, i) => { const y = 2.95 + i * 0.3; slide.addShape(pres.shapes.RECTANGLE, { x: 5.2, y, w: 4.5, h: 0.27, fill: { color: i < 4 ? C.offWhite : "F0FDF4" }, line: { color: C.lightGray, width: 1 } }); slide.addShape(pres.shapes.RECTANGLE, { x: 5.2, y, w: 0.07, h: 0.27, fill: { color: j[2] }, line: { color: j[2] } }); slide.addText([ { text: j[0], options: { bold: i >= 4, color: i >= 4 ? "1E293B" : C.gray } }, { text: " " + j[1], options: { color: j[2], bold: i >= 4 } } ], { x: 5.32, y, w: 4.3, h: 0.27, fontSize: 9.5, valign: "middle", fontFace: "Calibri" }); }); } // ═══════════════════════════════════════════════════════ // Slide 15: 보안 제약사항 // ═══════════════════════════════════════════════════════ { const slide = addContentSlide("08 보안 제약사항"); const secRules = [ { cat: "LLM 보안", color: C.red, rules: [ "외부 LLM API 완전 차단 (OpenAI/Claude 등)", "Ollama localhost:11434 전용 사용", "temperature 0.2 고정 (결정론적 응답)", "HTTP 요청 30초 타임아웃" ] }, { cat: "서버 정보 보호", color: C.orange, rules: [ "ip_addr — ServerOut 스키마 미포함", "ssh_user — API 응답 절대 미노출", "os_pw_enc — AES-256-GCM 암호화 필수", "file_path — API 응답 완전 제거" ] }, { cat: "에이전트 액션 제어", color: C.purple, rules: [ "CRITICAL 액션 → 사람 승인 후 실행", "위험 명령어 차단: rm -rf /, shutdown, mkfs", "fork bomb 패턴 감지 & 차단", "경로 순회 방지 (resolve().relative_to)" ] }, { cat: "접근 제어 (RBAC)", color: C.blue, rules: [ "CUSTOMER 역할 — 에이전트 API 전체 차단", "에이전트 생성/수정/삭제 — ADMIN만 허용", "승인 처리 — USER 이상 허용", "스택트레이스 — API 응답 노출 금지" ] } ]; secRules.forEach((sec, i) => { const x = 0.25 + (i % 2) * 4.9; const y = 0.88 + Math.floor(i / 2) * 2.35; slide.addShape(pres.shapes.RECTANGLE, { x, y, w: 4.65, h: 2.2, fill: { color: C.white }, line: { color: C.lightGray, width: 1 }, shadow: makeShadow() }); slide.addShape(pres.shapes.RECTANGLE, { x, y, w: 4.65, h: 0.42, fill: { color: sec.color }, line: { color: sec.color } }); slide.addText(sec.cat, { x, y, w: 4.65, h: 0.42, fontSize: 12, color: C.white, bold: true, align: "center", valign: "middle", margin: 0, fontFace: "Calibri" }); const textArr = sec.rules.map((r, ri) => ({ text: "▸ " + r, options: { color: ri === 0 ? "0F172A" : "1E293B", bold: ri === 0, fontSize: 10.5, breakLine: ri < sec.rules.length - 1 } })); slide.addText(textArr, { x: x + 0.12, y: y + 0.48, w: 4.4, h: 1.65, valign: "top", fontFace: "Calibri" }); }); } // ═══════════════════════════════════════════════════════ // Slide 16: 향후 로드맵 // ═══════════════════════════════════════════════════════ { const slide = addContentSlide("08 향후 로드맵"); const roadmap = [ { ver: "v1.1", period: "2026 Q3", title: "에이전트 간 메시지 전달", desc: "CEO→CTO 위임 워크플로우\n태스크 자동 위임 체계", color: C.blue }, { ver: "v1.2", period: "2026 Q3", title: "에이전트 학습", desc: "이전 태스크 기반 파인튜닝\n조직 맞춤형 응답 최적화", color: C.teal }, { ver: "v1.3", period: "2026 Q4", title: "멀티모달 지원", desc: "스크린샷·로그 이미지 분석\n시각 장애 자동 탐지", color: C.green }, { ver: "v2.0", period: "2027 Q1", title: "에이전트 마켓플레이스", desc: "커뮤니티 에이전트 공유\nPlug-in 방식 등록/사용", color: C.purple }, { ver: "v2.1", period: "2027 Q1", title: "엣지 배포", desc: "경량 모델 (llama3.2:1b)\n저사양 서버 지원", color: C.orange }, ]; roadmap.forEach((r, i) => { const x = 0.25 + i * 1.92; slide.addShape(pres.shapes.RECTANGLE, { x, y: 0.88, w: 1.75, h: 4.5, fill: { color: C.white }, line: { color: C.lightGray, width: 1 }, shadow: makeShadow() }); slide.addShape(pres.shapes.RECTANGLE, { x, y: 0.88, w: 1.75, h: 0.55, fill: { color: r.color }, line: { color: r.color } }); slide.addText(r.ver, { x, y: 0.88, w: 1.75, h: 0.55, fontSize: 16, color: C.white, bold: true, align: "center", valign: "middle", margin: 0, fontFace: "Calibri" }); slide.addShape(pres.shapes.RECTANGLE, { x: x + 0.1, y: 1.5, w: 1.55, h: 0.38, fill: { color: r.color, transparency: 80 }, line: { color: r.color, width: 1 } }); slide.addText(r.period, { x: x + 0.1, y: 1.5, w: 1.55, h: 0.38, fontSize: 10, color: "1E293B", bold: true, align: "center", valign: "middle", margin: 0, fontFace: "Calibri" }); slide.addText(r.title, { x: x + 0.1, y: 2.0, w: 1.55, h: 0.65, fontSize: 11, color: "0F172A", bold: true, align: "center", fontFace: "Calibri" }); slide.addShape(pres.shapes.LINE, { x: x + 0.2, y: 2.7, w: 1.35, h: 0, line: { color: C.lightGray, width: 1 } }); slide.addText(r.desc, { x: x + 0.1, y: 2.75, w: 1.55, h: 1.55, fontSize: 10.5, color: C.gray, align: "center", valign: "top", fontFace: "Calibri" }); }); // 하단 현재 완료 배너 slide.addShape(pres.shapes.RECTANGLE, { x: 0.25, y: 5.1, w: 9.5, h: 0.38, fill: { color: C.green }, line: { color: C.green } }); slide.addText("현재 완료: Phase 1~4 | 6종 에이전트 | 16개 API | 9개 스케줄러 잡 | 자율 운영 대시보드", { x: 0.35, y: 5.1, w: 9.3, h: 0.38, fontSize: 11, color: C.white, bold: true, align: "center", valign: "middle", fontFace: "Calibri" }); } // ═══════════════════════════════════════════════════════ // Slide 17: 마무리 // ═══════════════════════════════════════════════════════ { const slide = addTitleSlide( C.navyDark, "GUARDiA × Paperclip\n구현 완료", "Phase 1~4 완료 | 온프레미스 AI 자율 운영 체계 구축", "완료" ); // 요약 통계 카드 const stats = [ { num: "6", label: "자율 에이전트", color: C.orange }, { num: "16", label: "REST API 엔드포인트", color: C.blue }, { num: "9", label: "스케줄러 잡", color: C.green }, { num: "100%", label: "구문 검사 통과", color: C.teal }, ]; stats.forEach((s, i) => { const x = 0.4 + i * 2.3; slide.addShape(pres.shapes.RECTANGLE, { x, y: 4.05, w: 2.1, h: 1.3, fill: { color: s.color, transparency: 15 }, line: { color: s.color, width: 2 } }); slide.addText(s.num, { x, y: 4.08, w: 2.1, h: 0.7, fontSize: 28, color: C.white, bold: true, align: "center", valign: "middle", fontFace: "Calibri", margin: 0 }); slide.addText(s.label, { x, y: 4.75, w: 2.1, h: 0.55, fontSize: 10, color: C.ice, align: "center", valign: "top", fontFace: "Calibri", margin: 0 }); }); } // ─── 파일 저장 ─────────────────────────────────────── pres.writeFile({ fileName: "C:/GUARDiA/manual/GUARDiA_Paperclip_AI에이전트_구현보고서.pptx" }) .then(() => console.log("PPT 생성 완료: GUARDiA_Paperclip_AI에이전트_구현보고서.pptx")) .catch(err => { console.error("PPT 생성 실패:", err); process.exit(1); });