Skip to main content

RAG 시스템 연동 가이드

이 문서는 KIWI Guide 문서를 RAG(Retrieval-Augmented Generation) 시스템에 연동하고, KIWI 플랫폼의 챗봇 및 UI 하이라이팅 기능과 통합하는 방법을 설명합니다.


문서 구조

1. 페이지 매핑 파일

docs/page-mapping.json 파일은 KIWI 플랫폼의 페이지-라우트 매핑 정보를 포함합니다.

{
"pages": {
"장비 관리": {
"route": "/devices",
"permission": "device",
"components": {
"장비목록테이블": "#device-list-table",
"장비등록버튼": "#add-device-btn"
}
}
}
}

2. RAG 최적화 문서

각 문서에는 RAG 검색을 위한 메타데이터가 포함되어 있습니다:

---
title: 문서 제목
description: 문서 설명
tags:
- 태그1
- 태그2
rag_keywords:
- 검색 키워드1
- 검색 키워드2
---

3. 시나리오 문서 구조

시나리오 문서는 다음 구조를 따릅니다:

## 시나리오 N: 시나리오 제목

### Q. 사용자 질문 형태

:::scenario
### Step N: 단계 제목
- **이동 경로**: [페이지명](/route)
- **UI 요소**: `컴포넌트ID` - 설명
- **동작**: 수행할 동작 설명
:::

RAG 시스템 연동 방법

1. 문서 인덱싱

import json
import os
from pathlib import Path

def index_kiwi_guide_docs(docs_path: str) -> list:
"""KIWI Guide 문서를 RAG용으로 인덱싱"""
documents = []

# Markdown 파일 파싱
for md_file in Path(docs_path).rglob("*.md"):
content = md_file.read_text(encoding="utf-8")

# frontmatter 파싱
metadata = parse_frontmatter(content)

# 본문 추출
body = extract_body(content)

documents.append({
"id": str(md_file),
"title": metadata.get("title", ""),
"description": metadata.get("description", ""),
"tags": metadata.get("tags", []),
"rag_keywords": metadata.get("rag_keywords", []),
"content": body,
"file_path": str(md_file)
})

return documents

2. 페이지 매핑 로드

def load_page_mapping(mapping_file: str) -> dict:
"""페이지 매핑 정보 로드"""
with open(mapping_file, "r", encoding="utf-8") as f:
return json.load(f)

# 사용 예시
page_mapping = load_page_mapping("docs/page-mapping.json")

# 페이지 이름으로 라우트 조회
def get_route_by_page_name(page_name: str) -> str:
page_info = page_mapping["pages"].get(page_name)
if page_info:
return page_info["route"]
return None

3. RAG 응답 처리

def process_rag_response(response: str, page_mapping: dict) -> dict:
"""RAG 응답에서 페이지/컴포넌트 정보 추출"""

result = {
"answer": response,
"pages": [],
"components": [],
"steps": []
}

# [페이지명] 패턴 추출
import re
page_pattern = r'\[([^\]]+)\]'
pages_found = re.findall(page_pattern, response)

for page_name in pages_found:
if page_name in page_mapping["pages"]:
page_info = page_mapping["pages"][page_name]
result["pages"].append({
"name": page_name,
"route": page_info["route"],
"components": page_info.get("components", {})
})

return result

KIWI UI 하이라이팅 연동

1. 하이라이팅 CSS 클래스

KIWI 프론트엔드에 다음 CSS 클래스를 추가합니다:

/* 가이드 하이라이팅 스타일 */
.kiwi-guide-highlight {
outline: 3px solid var(--color-primary);
outline-offset: 2px;
animation: kiwi-guide-pulse 2s infinite;
position: relative;
z-index: 1000;
}

.kiwi-guide-highlight::before {
content: attr(data-guide-step);
position: absolute;
top: -24px;
left: 0;
background: var(--color-primary);
color: white;
padding: 2px 8px;
border-radius: 4px;
font-size: 12px;
font-weight: bold;
}

@keyframes kiwi-guide-pulse {
0%, 100% {
outline-color: var(--color-primary);
}
50% {
outline-color: var(--color-info);
}
}

/* 단계별 색상 */
.kiwi-step-current {
outline-color: #3475E6; /* 현재 단계 - 파랑 */
}

.kiwi-step-next {
outline-color: #63CA29; /* 다음 단계 - 초록 */
}

.kiwi-step-completed {
outline-color: #A7A7A7; /* 완료 단계 - 회색 */
}

2. 하이라이팅 Context

// GuideHighlightContext.tsx
import React, { createContext, useContext, useState } from 'react';

interface HighlightStep {
pageRoute: string;
componentId: string;
stepNumber: number;
description: string;
}

interface GuideHighlightContextType {
activeSteps: HighlightStep[];
currentStep: number;
setGuideSteps: (steps: HighlightStep[]) => void;
nextStep: () => void;
clearGuide: () => void;
}

const GuideHighlightContext = createContext<GuideHighlightContextType | null>(null);

export const GuideHighlightProvider: React.FC<{children: React.ReactNode}> = ({ children }) => {
const [activeSteps, setActiveSteps] = useState<HighlightStep[]>([]);
const [currentStep, setCurrentStep] = useState(0);

const setGuideSteps = (steps: HighlightStep[]) => {
setActiveSteps(steps);
setCurrentStep(0);
};

const nextStep = () => {
if (currentStep < activeSteps.length - 1) {
setCurrentStep(prev => prev + 1);
}
};

const clearGuide = () => {
setActiveSteps([]);
setCurrentStep(0);
};

return (
<GuideHighlightContext.Provider value={{
activeSteps,
currentStep,
setGuideSteps,
nextStep,
clearGuide
}}>
{children}
</GuideHighlightContext.Provider>
);
};

export const useGuideHighlight = () => {
const context = useContext(GuideHighlightContext);
if (!context) {
throw new Error('useGuideHighlight must be used within GuideHighlightProvider');
}
return context;
};

3. 하이라이팅 Hook

// useGuideHighlightElement.ts
import { useEffect } from 'react';
import { useGuideHighlight } from './GuideHighlightContext';
import { useLocation } from 'react-router-dom';

export const useGuideHighlightElement = (componentId: string) => {
const { activeSteps, currentStep } = useGuideHighlight();
const location = useLocation();

useEffect(() => {
const currentStepInfo = activeSteps[currentStep];

if (!currentStepInfo) return;

// 현재 페이지가 가이드 페이지와 일치하는지 확인
if (location.pathname !== currentStepInfo.pageRoute) return;

// 현재 컴포넌트가 하이라이트 대상인지 확인
if (currentStepInfo.componentId !== componentId) return;

// 하이라이트 클래스 추가
const element = document.querySelector(componentId);
if (element) {
element.classList.add('kiwi-guide-highlight', 'kiwi-step-current');
element.setAttribute('data-guide-step', `Step ${currentStepInfo.stepNumber}`);

// 화면에 보이도록 스크롤
element.scrollIntoView({ behavior: 'smooth', block: 'center' });
}

return () => {
if (element) {
element.classList.remove('kiwi-guide-highlight', 'kiwi-step-current');
element.removeAttribute('data-guide-step');
}
};
}, [activeSteps, currentStep, componentId, location]);
};

4. 챗봇 응답 처리

// ChatbotGuideHandler.ts
import { useGuideHighlight } from './GuideHighlightContext';
import { useNavigate } from 'react-router-dom';

interface ChatbotResponse {
answer: string;
pages: Array<{
name: string;
route: string;
components: Record<string, string>;
}>;
steps: Array<{
stepNumber: number;
pageRoute: string;
componentId: string;
description: string;
}>;
}

export const useChatbotGuideHandler = () => {
const { setGuideSteps } = useGuideHighlight();
const navigate = useNavigate();

const handleChatbotResponse = (response: ChatbotResponse) => {
if (response.steps && response.steps.length > 0) {
// 가이드 단계 설정
setGuideSteps(response.steps);

// 첫 번째 단계 페이지로 이동
const firstStep = response.steps[0];
navigate(firstStep.pageRoute);
}
};

return { handleChatbotResponse };
};

사용 예시

1. 사용자 질문

사용자: 회사 서버를 KIWI에 등록하고 싶어요

2. RAG 응답

{
"answer": "[장비 관리] 페이지에서 서버를 등록할 수 있습니다...",
"pages": [
{
"name": "장비 관리",
"route": "/devices",
"components": {
"장비등록버튼": "#add-device-btn",
"장비목록테이블": "#device-list-table"
}
}
],
"steps": [
{
"stepNumber": 1,
"pageRoute": "/devices",
"componentId": "#add-device-btn",
"description": "장비 등록 버튼을 클릭하세요"
},
{
"stepNumber": 2,
"pageRoute": "/devices",
"componentId": "#device-detail-modal",
"description": "장비 정보를 입력하세요"
}
]
}

3. UI 하이라이팅 결과

  1. 사용자가 /devices 페이지로 자동 이동
  2. "장비 등록" 버튼에 파란색 테두리 하이라이트
  3. 버튼 위에 "Step 1" 라벨 표시
  4. 사용자가 단계 완료 시 다음 단계로 하이라이트 이동

참고 사항

  • 모든 컴포넌트 ID는 page-mapping.json에 정의된 셀렉터와 일치해야 합니다.
  • 시나리오 문서의 :::scenario 블록은 자동으로 파싱됩니다.
  • UI 하이라이팅은 현재 페이지의 요소만 활성화됩니다.