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 하이라이팅 결과
- 사용자가
/devices페이지로 자동 이동 - "장비 등록" 버튼에 파란색 테두리 하이라이트
- 버튼 위에 "Step 1" 라벨 표시
- 사용자가 단계 완료 시 다음 단계로 하이라이트 이동
참고 사항
- 모든 컴포넌트 ID는
page-mapping.json에 정의된 셀렉터와 일치해야 합니다. - 시나리오 문서의
:::scenario블록은 자동으로 파싱됩니다. - UI 하이라이팅은 현재 페이지의 요소만 활성화됩니다.