mirror of
https://github.com/hwanny1128/HGZero.git
synced 2025-12-06 11:26:25 +00:00
- 전체 팀원 토의를 통한 하이브리드형(단계별 확장) 최적안 도출 - 데이터 수집/정제/벡터라이징 전략 수립 - Claude API 호출 구조 설계 (프롬프트, JSON 요청/응답) - Phase 1-3 단계별 구현 로드맵 (2주/4주/6주) - 성능 목표: 응답시간 5초→3초→2초, 정확도 70%→85%→90% - 비용 최적화: Redis 캐싱으로 60% 절감 ($45→$18/월) - 기술 스택: OpenAI Embedding + Pinecone + Claude 3.5 + Redis - 하이브리드 검색: 벡터 70% + 키워드 30% - 품질 검증 기준 및 운영 모니터링 전략 포함 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
977 lines
30 KiB
Markdown
977 lines
30 KiB
Markdown
# 맥락기반 용어설명 구현방안
|
||
|
||
## 문서 정보
|
||
- **작성일**: 2025-01-20
|
||
- **작성자**: AI Specialist 박서연, Backend Developer 이준호/이동욱, Architect 홍길동
|
||
- **버전**: 2.0 (하이브리드형 - 단계별 확장 방식)
|
||
- **상태**: 최종 승인
|
||
|
||
---
|
||
|
||
## 목차
|
||
1. [개요](#개요)
|
||
2. [아키텍처 설계](#아키텍처-설계)
|
||
3. [데이터 수집 및 정제](#데이터-수집-및-정제)
|
||
4. [벡터라이징 전략](#벡터라이징-전략)
|
||
5. [Claude API 호출 구조](#claude-api-호출-구조)
|
||
6. [단계별 구현 로드맵](#단계별-구현-로드맵)
|
||
7. [성능 및 비용 최적화](#성능-및-비용-최적화)
|
||
8. [품질 검증 기준](#품질-검증-기준)
|
||
9. [운영 및 모니터링](#운영-및-모니터링)
|
||
|
||
---
|
||
|
||
## 개요
|
||
|
||
### 목적
|
||
회의록 작성자가 업무 지식이 없어도, AI가 **맥락에 맞는 실용적인 용어 설명**을 자동으로 제공하여 정확한 회의록 작성을 지원합니다.
|
||
|
||
### 핵심 차별화 포인트
|
||
- ❌ 단순 용어 정의 (Wikipedia 스타일)
|
||
- ✅ **조직 내 실제 사용 맥락** 제공
|
||
- ✅ **관련 회의록 및 프로젝트 연결**
|
||
- ✅ **과거 논의 요약** (언제, 누가, 어떻게 사용했는지)
|
||
|
||
### 관련 유저스토리
|
||
- **UFR-RAG-010**: 전문용어 자동 감지
|
||
- **UFR-RAG-020**: 맥락 기반 용어 설명 생성
|
||
|
||
---
|
||
|
||
## 아키텍처 설계
|
||
|
||
### 전체 아키텍처 (최종 목표)
|
||
|
||
```
|
||
┌─────────────────────────────────────────────────────────┐
|
||
│ 회의록 작성 중 │
|
||
│ (전문용어 "RAG" 감지) │
|
||
└──────────────────┬──────────────────────────────────────┘
|
||
│
|
||
▼
|
||
┌─────────────────────────────────────────────────────────┐
|
||
│ RAG 서비스 (Node.js) │
|
||
│ ┌───────────────────────────────────────────────────┐ │
|
||
│ │ 1. 용어 감지 엔진 │ │
|
||
│ │ - 용어 사전 매칭 (Trie 자료구조) │ │
|
||
│ │ - 신뢰도 계산 (0-100%) │ │
|
||
│ └───────────────────────────────────────────────────┘ │
|
||
│ ┌───────────────────────────────────────────────────┐ │
|
||
│ │ 2. Redis 캐시 조회 │ │
|
||
│ │ Key: term:{용어명}:{회의ID} │ │
|
||
│ │ TTL: 자주 쓰이는 용어 7일, 드문 용어 1일 │ │
|
||
│ └───────────────────────────────────────────────────┘ │
|
||
│ ▼ (캐시 미스) │
|
||
│ ┌───────────────────────────────────────────────────┐ │
|
||
│ │ 3. 벡터 검색 (Pinecone) │ │
|
||
│ │ - Query Embedding (OpenAI text-embedding-3) │ │
|
||
│ │ - 하이브리드 검색 (벡터 + 키워드) │ │
|
||
│ │ - Top 5 관련 문서 추출 │ │
|
||
│ └───────────────────────────────────────────────────┘ │
|
||
│ ▼ │
|
||
│ ┌───────────────────────────────────────────────────┐ │
|
||
│ │ 4. Claude API 호출 │ │
|
||
│ │ - 프롬프트: System + User + Few-shot │ │
|
||
│ │ - 응답: JSON {definition, context, related} │ │
|
||
│ └───────────────────────────────────────────────────┘ │
|
||
│ ▼ │
|
||
│ ┌───────────────────────────────────────────────────┐ │
|
||
│ │ 5. 응답 캐싱 (Redis) │ │
|
||
│ │ - 다음 요청 시 즉시 반환 │ │
|
||
│ └───────────────────────────────────────────────────┘ │
|
||
└─────────────────────────────────────────────────────────┘
|
||
│
|
||
▼
|
||
┌─────────────────────────────────────────────────────────┐
|
||
│ 사용자에게 설명 표시 │
|
||
│ - 간단한 정의 (1-2문장) │
|
||
│ - 맥락 기반 설명 │
|
||
│ - 관련 회의록 링크 (최대 3개) │
|
||
│ - 과거 사용 사례 │
|
||
└─────────────────────────────────────────────────────────┘
|
||
```
|
||
|
||
### 기술 스택
|
||
|
||
| 계층 | 기술 | 선택 이유 |
|
||
|------|------|-----------|
|
||
| **임베딩 모델** | OpenAI text-embedding-3-large | 높은 정확도 (1536 차원), 안정적 API |
|
||
| **벡터 DB** | Pinecone | 관리형 서비스, 빠른 검색, Kubernetes 호환 |
|
||
| **LLM** | Claude 3.5 Sonnet | 긴 컨텍스트, 한국어 성능 우수, JSON 응답 안정적 |
|
||
| **캐시** | Redis | 빠른 응답, TTL 지원, 분산 캐시 가능 |
|
||
| **메시지 큐** | RabbitMQ | 배치 작업 비동기 처리 |
|
||
| **오케스트레이션** | Kubernetes | 스케일링, 배포 자동화 |
|
||
|
||
---
|
||
|
||
## 데이터 수집 및 정제
|
||
|
||
### 1. 데이터 수집 범위
|
||
|
||
#### Phase 1 (2주): 회의록만
|
||
```
|
||
회의록 DB (Meeting 서비스)
|
||
├─ meeting_id
|
||
├─ title
|
||
├─ content (Markdown)
|
||
├─ participants
|
||
├─ date
|
||
└─ project_id
|
||
```
|
||
|
||
#### Phase 2 (4주): 위키 추가
|
||
```
|
||
사내 위키 (Confluence, Notion 등)
|
||
├─ page_id
|
||
├─ title
|
||
├─ content
|
||
├─ author
|
||
├─ last_updated
|
||
└─ tags
|
||
```
|
||
|
||
#### Phase 3 (6주): 프로젝트 문서 + 이메일
|
||
```
|
||
프로젝트 문서 (Google Drive, SharePoint)
|
||
├─ doc_id
|
||
├─ title
|
||
├─ content
|
||
├─ project_id
|
||
└─ created_at
|
||
|
||
이메일 (Outlook, Gmail)
|
||
├─ email_id
|
||
├─ subject
|
||
├─ body (HTML → Plain Text 변환)
|
||
├─ sender
|
||
└─ date
|
||
```
|
||
|
||
### 2. 데이터 정제 파이프라인
|
||
|
||
```mermaid
|
||
graph LR
|
||
A[원본 수집] --> B[전처리]
|
||
B --> C[메타데이터 추가]
|
||
C --> D[벡터화]
|
||
D --> E[Pinecone 저장]
|
||
|
||
B --> B1[불용어 제거]
|
||
B --> B2[토큰화]
|
||
B --> B3[정규화]
|
||
|
||
C --> C1[날짜]
|
||
C --> C2[참석자/작성자]
|
||
C --> C3[프로젝트]
|
||
C --> C4[부서]
|
||
```
|
||
|
||
#### 전처리 상세
|
||
|
||
**1) 불용어 제거**
|
||
```python
|
||
STOPWORDS = [
|
||
'그', '저', '것', '수', '등', '들', '및', '때문', '위해', '통해',
|
||
'하지만', '그러나', '따라서', '또한', '즉', '예를 들어'
|
||
]
|
||
|
||
def remove_stopwords(text):
|
||
tokens = text.split()
|
||
return ' '.join([t for t in tokens if t not in STOPWORDS])
|
||
```
|
||
|
||
**2) 토큰화 (한국어)**
|
||
```python
|
||
from konlpy.tag import Okt
|
||
|
||
okt = Okt()
|
||
|
||
def tokenize_korean(text):
|
||
return okt.morphs(text, stem=True)
|
||
```
|
||
|
||
**3) 정규화**
|
||
```python
|
||
import re
|
||
|
||
def normalize(text):
|
||
# 이메일 제거
|
||
text = re.sub(r'\S+@\S+', '[EMAIL]', text)
|
||
# URL 제거
|
||
text = re.sub(r'http\S+', '[URL]', text)
|
||
# 특수문자 제거 (단, -_ 유지)
|
||
text = re.sub(r'[^\w\s-_]', '', text)
|
||
# 공백 정리
|
||
text = re.sub(r'\s+', ' ', text)
|
||
return text.strip()
|
||
```
|
||
|
||
### 3. 메타데이터 설계
|
||
|
||
```json
|
||
{
|
||
"id": "doc_12345",
|
||
"content": "RAG 시스템은 Retrieval-Augmented Generation의 약자로...",
|
||
"metadata": {
|
||
"source": "meeting", // meeting | wiki | doc | email
|
||
"title": "프로젝트 회의",
|
||
"date": "2025-01-20T14:00:00Z",
|
||
"participants": ["김민준", "박서연", "이준호"],
|
||
"project_id": "proj_001",
|
||
"project_name": "회의록 시스템",
|
||
"department": "개발팀",
|
||
"tags": ["RAG", "AI", "회의록"],
|
||
"language": "ko"
|
||
}
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
## 벡터라이징 전략
|
||
|
||
### 1. Chunking 전략
|
||
|
||
**목표**: 회의록/문서를 의미 있는 단위로 분할하여 검색 정확도 향상
|
||
|
||
```python
|
||
def chunk_text(text, chunk_size=500, overlap=50):
|
||
"""
|
||
텍스트를 chunk로 분할
|
||
|
||
Args:
|
||
text: 원본 텍스트
|
||
chunk_size: 청크 크기 (토큰 수)
|
||
overlap: 청크 간 중복 크기
|
||
|
||
Returns:
|
||
List[str]: 청크 리스트
|
||
"""
|
||
tokens = tokenize_korean(text)
|
||
chunks = []
|
||
|
||
for i in range(0, len(tokens), chunk_size - overlap):
|
||
chunk = tokens[i:i + chunk_size]
|
||
chunks.append(' '.join(chunk))
|
||
|
||
return chunks
|
||
```
|
||
|
||
**Chunking 전략 비교**
|
||
|
||
| 방식 | 크기 | Overlap | 장점 | 단점 |
|
||
|------|------|---------|------|------|
|
||
| **고정 크기** | 500 토큰 | 50 토큰 | 단순, 빠름 | 문맥 끊김 가능 |
|
||
| **문단 기반** | 가변 | 0 | 자연스러운 구분 | 크기 불균등 |
|
||
| **문장 기반** | 가변 | 1 문장 | 의미 보존 | 너무 작을 수 있음 |
|
||
| **하이브리드** | 500 토큰 | 50 토큰 + 문단 경계 | 균형잡힘 | 복잡함 |
|
||
|
||
**선택**: **하이브리드 방식** (Phase 2 이후 적용)
|
||
|
||
### 2. Embedding 생성
|
||
|
||
```python
|
||
import openai
|
||
|
||
def generate_embedding(text, model="text-embedding-3-large"):
|
||
"""
|
||
OpenAI API로 임베딩 생성
|
||
|
||
Returns:
|
||
List[float]: 1536 차원 벡터
|
||
"""
|
||
response = openai.embeddings.create(
|
||
input=text,
|
||
model=model
|
||
)
|
||
return response.data[0].embedding
|
||
```
|
||
|
||
**비용 계산**:
|
||
- text-embedding-3-large: $0.00013 / 1K tokens
|
||
- 예상 월 비용: 500 회의록 × 2K tokens × $0.00013 = $0.13
|
||
|
||
### 3. Pinecone 저장
|
||
|
||
```python
|
||
import pinecone
|
||
|
||
# 초기화
|
||
pinecone.init(api_key="YOUR_API_KEY", environment="us-west1-gcp")
|
||
index = pinecone.Index("meeting-rag")
|
||
|
||
def upsert_to_pinecone(doc_id, embedding, metadata):
|
||
"""
|
||
Pinecone에 벡터 저장
|
||
"""
|
||
index.upsert(vectors=[{
|
||
"id": doc_id,
|
||
"values": embedding,
|
||
"metadata": metadata
|
||
}])
|
||
```
|
||
|
||
**Pinecone 설정**:
|
||
- Index: meeting-rag
|
||
- Dimension: 1536
|
||
- Metric: cosine
|
||
- Replicas: 1 (Phase 1), 2 (Phase 3, HA)
|
||
- Pods: p1.x1 (Phase 1), p1.x2 (Phase 2+)
|
||
|
||
---
|
||
|
||
## Claude API 호출 구조
|
||
|
||
### 1. 프롬프트 설계
|
||
|
||
#### System Prompt
|
||
```
|
||
당신은 조직 내 전문용어를 쉽게 설명하는 전문가입니다.
|
||
- 사내 회의록, 위키, 프로젝트 문서를 기반으로 실용적인 설명을 제공합니다.
|
||
- 단순 정의가 아닌, 조직에서 실제로 어떻게 사용되는지 맥락을 포함합니다.
|
||
- 과거 논의 내용을 요약하여 제공합니다.
|
||
|
||
응답 형식은 반드시 JSON으로 작성하세요:
|
||
{
|
||
"definition": "간단한 정의 (1-2문장)",
|
||
"context": "이 회의에서의 의미 (맥락 기반 설명)",
|
||
"usage_examples": ["실제 사용 사례 1", "사용 사례 2"],
|
||
"related_projects": ["관련 프로젝트 1", "프로젝트 2"],
|
||
"past_discussions": [
|
||
{"date": "2025-01-15", "meeting": "프로젝트 회의", "summary": "RAG 시스템 도입 결정"}
|
||
],
|
||
"references": ["doc_id_1", "doc_id_2", "doc_id_3"]
|
||
}
|
||
```
|
||
|
||
#### User Prompt (Few-shot Learning)
|
||
```
|
||
아래는 검색된 관련 문서들입니다:
|
||
|
||
---
|
||
문서 1 (회의록, 2025-01-15):
|
||
제목: 프로젝트 회의
|
||
내용: RAG 시스템을 도입하기로 결정했습니다. Retrieval-Augmented Generation은 문서 검색과 생성을 결합한 AI 기술입니다...
|
||
|
||
문서 2 (위키, 2025-01-10):
|
||
제목: AI 기술 가이드
|
||
내용: RAG는 벡터 DB를 활용하여 관련 문서를 찾고, LLM이 이를 기반으로 답변을 생성하는 방식입니다...
|
||
|
||
문서 3 (프로젝트 문서, 2025-01-05):
|
||
제목: 회의록 시스템 설계서
|
||
내용: 맥락 기반 용어 설명 기능에 RAG 시스템을 적용합니다...
|
||
---
|
||
|
||
현재 회의 맥락:
|
||
- 회의: "주간 스크럼"
|
||
- 날짜: 2025-01-20
|
||
- 참석자: 김민준, 박서연, 이준호
|
||
- 프로젝트: "회의록 시스템"
|
||
|
||
용어: "RAG"
|
||
|
||
위 정보를 바탕으로 "RAG"에 대한 설명을 JSON 형식으로 작성해주세요.
|
||
|
||
예시:
|
||
{
|
||
"definition": "Retrieval-Augmented Generation의 약자로, 문서 검색과 AI 생성을 결합한 기술입니다.",
|
||
"context": "우리 팀은 회의록 시스템에 RAG를 적용하여 과거 회의록과 사내 문서를 검색하고, 맥락에 맞는 용어 설명을 자동 생성합니다.",
|
||
"usage_examples": [
|
||
"회의 중 전문용어가 나오면 RAG 시스템이 관련 문서를 찾아 설명을 제공합니다",
|
||
"신입사원도 업무 지식 없이 정확한 회의록을 작성할 수 있습니다"
|
||
],
|
||
"related_projects": ["회의록 시스템", "AI 자동화 프로젝트"],
|
||
"past_discussions": [
|
||
{"date": "2025-01-15", "meeting": "프로젝트 회의", "summary": "RAG 시스템 도입 결정"},
|
||
{"date": "2025-01-10", "meeting": "기술 세미나", "summary": "RAG 아키텍처 소개"}
|
||
],
|
||
"references": ["doc_12345", "doc_12346", "doc_12347"]
|
||
}
|
||
```
|
||
|
||
### 2. API 호출 코드
|
||
|
||
```typescript
|
||
import Anthropic from '@anthropic-ai/sdk';
|
||
|
||
const anthropic = new Anthropic({
|
||
apiKey: process.env.CLAUDE_API_KEY,
|
||
});
|
||
|
||
interface TermExplanation {
|
||
definition: string;
|
||
context: string;
|
||
usage_examples: string[];
|
||
related_projects: string[];
|
||
past_discussions: Array<{
|
||
date: string;
|
||
meeting: string;
|
||
summary: string;
|
||
}>;
|
||
references: string[];
|
||
}
|
||
|
||
async function explainTerm(
|
||
term: string,
|
||
relatedDocs: any[],
|
||
meetingContext: any
|
||
): Promise<TermExplanation> {
|
||
|
||
const systemPrompt = `당신은 조직 내 전문용어를 쉽게 설명하는 전문가입니다...`;
|
||
|
||
const userPrompt = buildUserPrompt(term, relatedDocs, meetingContext);
|
||
|
||
const response = await anthropic.messages.create({
|
||
model: 'claude-3-5-sonnet-20241022',
|
||
max_tokens: 2000,
|
||
temperature: 0.3, // 일관된 응답을 위해 낮은 temperature
|
||
system: systemPrompt,
|
||
messages: [
|
||
{
|
||
role: 'user',
|
||
content: userPrompt
|
||
}
|
||
]
|
||
});
|
||
|
||
// JSON 파싱
|
||
const content = response.content[0].text;
|
||
const jsonMatch = content.match(/\{[\s\S]*\}/);
|
||
|
||
if (!jsonMatch) {
|
||
throw new Error('Invalid JSON response from Claude');
|
||
}
|
||
|
||
return JSON.parse(jsonMatch[0]);
|
||
}
|
||
|
||
function buildUserPrompt(term: string, docs: any[], context: any): string {
|
||
const docsSummary = docs.map((doc, idx) => {
|
||
return `문서 ${idx + 1} (${doc.metadata.source}, ${doc.metadata.date}):
|
||
제목: ${doc.metadata.title}
|
||
내용: ${doc.content.substring(0, 500)}...`;
|
||
}).join('\n\n');
|
||
|
||
return `아래는 검색된 관련 문서들입니다:
|
||
|
||
---
|
||
${docsSummary}
|
||
---
|
||
|
||
현재 회의 맥락:
|
||
- 회의: "${context.meeting_title}"
|
||
- 날짜: ${context.date}
|
||
- 참석자: ${context.participants.join(', ')}
|
||
- 프로젝트: "${context.project_name}"
|
||
|
||
용어: "${term}"
|
||
|
||
위 정보를 바탕으로 "${term}"에 대한 설명을 JSON 형식으로 작성해주세요.
|
||
|
||
예시:
|
||
{
|
||
"definition": "...",
|
||
"context": "...",
|
||
...
|
||
}`;
|
||
}
|
||
```
|
||
|
||
### 3. API 요청/응답 예시
|
||
|
||
#### 요청 (Request)
|
||
```json
|
||
{
|
||
"model": "claude-3-5-sonnet-20241022",
|
||
"max_tokens": 2000,
|
||
"temperature": 0.3,
|
||
"system": "당신은 조직 내 전문용어를 쉽게 설명하는 전문가입니다...",
|
||
"messages": [
|
||
{
|
||
"role": "user",
|
||
"content": "아래는 검색된 관련 문서들입니다...\n용어: \"RAG\""
|
||
}
|
||
]
|
||
}
|
||
```
|
||
|
||
#### 응답 (Response)
|
||
```json
|
||
{
|
||
"id": "msg_01ABC123",
|
||
"type": "message",
|
||
"role": "assistant",
|
||
"content": [
|
||
{
|
||
"type": "text",
|
||
"text": "{\n \"definition\": \"Retrieval-Augmented Generation의 약자로, 문서 검색과 AI 생성을 결합한 기술입니다.\",\n \"context\": \"우리 팀은 회의록 시스템에 RAG를 적용하여 과거 회의록과 사내 문서를 검색하고, 맥락에 맞는 용어 설명을 자동 생성합니다.\",\n \"usage_examples\": [\n \"회의 중 전문용어가 나오면 RAG 시스템이 관련 문서를 찾아 설명을 제공합니다\",\n \"신입사원도 업무 지식 없이 정확한 회의록을 작성할 수 있습니다\"\n ],\n \"related_projects\": [\"회의록 시스템\", \"AI 자동화 프로젝트\"],\n \"past_discussions\": [\n {\"date\": \"2025-01-15\", \"meeting\": \"프로젝트 회의\", \"summary\": \"RAG 시스템 도입 결정\"},\n {\"date\": \"2025-01-10\", \"meeting\": \"기술 세미나\", \"summary\": \"RAG 아키텍처 소개\"}\n ],\n \"references\": [\"doc_12345\", \"doc_12346\", \"doc_12347\"]\n}"
|
||
}
|
||
],
|
||
"model": "claude-3-5-sonnet-20241022",
|
||
"stop_reason": "end_turn",
|
||
"usage": {
|
||
"input_tokens": 1250,
|
||
"output_tokens": 320
|
||
}
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
## 단계별 구현 로드맵
|
||
|
||
### Phase 1: 기본 기능 (2주)
|
||
|
||
**목표**: 회의록 기반 최소 기능 구현 및 사용자 테스트
|
||
|
||
#### 구현 범위
|
||
- [x] 회의록 DB 연동
|
||
- [x] 용어 감지 엔진 (Trie 자료구조)
|
||
- [x] OpenAI Embedding API 연동
|
||
- [x] Pinecone 벡터 검색
|
||
- [x] Claude API 호출 (기본 프롬프트)
|
||
- [x] UI: 점선 밑줄 하이라이트, 바텀 시트 툴팁
|
||
|
||
#### 성능 목표
|
||
- 응답 시간: **5초 이내**
|
||
- 용어 감지 정확도: **70% 이상**
|
||
- 관련 문서 정확도: **60% 이상**
|
||
|
||
#### 제약 사항
|
||
- 캐시 없음 (모든 요청 실시간 처리)
|
||
- 회의록만 검색 (위키, 문서, 이메일 미포함)
|
||
|
||
#### 배포 전략
|
||
- Beta 테스트: 20명 (개발팀 10명, 기획팀 5명, 경영지원팀 5명)
|
||
- 피드백 수집: Google Forms 설문 + 주간 인터뷰
|
||
|
||
---
|
||
|
||
### Phase 2: 성능 개선 (4주)
|
||
|
||
**목표**: 캐시 도입 + 위키 추가 + 하이브리드 검색
|
||
|
||
#### 구현 범위
|
||
- [x] Redis 캐시 레이어
|
||
- TTL: 자주 쓰이는 용어 7일, 드문 용어 1일
|
||
- Cache Warming: 상위 50개 용어 사전 캐싱
|
||
- [x] 사내 위키 연동 (Confluence, Notion)
|
||
- [x] 하이브리드 검색 (벡터 + 키워드)
|
||
- 벡터 유사도: 70% 가중치
|
||
- 키워드 매칭: 30% 가중치
|
||
- [x] 프롬프트 최적화 (Few-shot learning)
|
||
|
||
#### 성능 목표
|
||
- 응답 시간: **3초 이내** (캐시 히트 시 0.5초)
|
||
- 용어 감지 정확도: **85% 이상**
|
||
- 관련 문서 정확도: **75% 이상**
|
||
- 캐시 히트율: **60% 이상**
|
||
|
||
#### 배포 전략
|
||
- Beta 테스트 확대: 50명
|
||
- A/B 테스트: 캐시 vs 캐시 없음, 하이브리드 vs 벡터 단독
|
||
|
||
---
|
||
|
||
### Phase 3: 고도화 (6주)
|
||
|
||
**목표**: 전체 데이터 통합 + 시맨틱 필터링 + 부서별 커스터마이징
|
||
|
||
#### 구현 범위
|
||
- [x] 프로젝트 문서 연동 (Google Drive, SharePoint)
|
||
- [x] 이메일 연동 (Outlook, Gmail)
|
||
- [x] 시맨틱 필터링
|
||
- 부서별 용어 우선순위
|
||
- 프로젝트별 문맥 가중치
|
||
- [x] 2단계 캐싱
|
||
- L1: Redis (Hot data, TTL 7일)
|
||
- L2: CDN (Static explanations, TTL 30일)
|
||
- [x] 용어 추천 시스템
|
||
- "이 용어를 사전에 추가할까요?" 제안
|
||
|
||
#### 성능 목표
|
||
- 응답 시간: **2초 이내** (캐시 히트 시 0.3초)
|
||
- 용어 감지 정확도: **90% 이상**
|
||
- 관련 문서 정확도: **80% 이상**
|
||
- 캐시 히트율: **80% 이상**
|
||
|
||
#### 배포 전략
|
||
- 전사 배포 (200명+)
|
||
- 부서별 용어 사전 큐레이션 워크숍
|
||
|
||
---
|
||
|
||
## 성능 및 비용 최적화
|
||
|
||
### 1. 캐싱 전략
|
||
|
||
#### Redis 캐시 구조
|
||
```
|
||
Key: term:{term_name}:{meeting_id}
|
||
Value: JSON (TermExplanation)
|
||
TTL:
|
||
- 자주 쓰이는 용어 (요청 >10회/월): 7일
|
||
- 드문 용어 (요청 <10회/월): 1일
|
||
```
|
||
|
||
#### Cache Warming
|
||
```python
|
||
# 매일 새벽 2시 실행
|
||
def cache_warming():
|
||
# 상위 50개 빈도 높은 용어
|
||
top_terms = get_top_terms(limit=50)
|
||
|
||
for term in top_terms:
|
||
# 최근 회의 맥락으로 미리 캐싱
|
||
recent_meetings = get_recent_meetings(limit=5)
|
||
for meeting in recent_meetings:
|
||
explanation = explain_term(term, meeting)
|
||
cache_key = f"term:{term}:{meeting.id}"
|
||
redis.setex(cache_key, ttl=7*24*3600, value=json.dumps(explanation))
|
||
```
|
||
|
||
### 2. 하이브리드 검색
|
||
|
||
```python
|
||
def hybrid_search(query, top_k=5):
|
||
"""
|
||
벡터 검색 + 키워드 검색 결합
|
||
"""
|
||
# 1. 벡터 검색 (70% 가중치)
|
||
query_embedding = generate_embedding(query)
|
||
vector_results = pinecone_index.query(
|
||
vector=query_embedding,
|
||
top_k=top_k * 2, # 2배수 조회
|
||
include_metadata=True
|
||
)
|
||
|
||
# 2. 키워드 검색 (30% 가중치)
|
||
keyword_results = elasticsearch.search(
|
||
index="meetings",
|
||
body={
|
||
"query": {
|
||
"multi_match": {
|
||
"query": query,
|
||
"fields": ["title^3", "content", "tags^2"]
|
||
}
|
||
}
|
||
},
|
||
size=top_k * 2
|
||
)
|
||
|
||
# 3. 점수 결합 (Normalized)
|
||
combined_scores = {}
|
||
|
||
for match in vector_results.matches:
|
||
combined_scores[match.id] = match.score * 0.7
|
||
|
||
for hit in keyword_results['hits']['hits']:
|
||
doc_id = hit['_id']
|
||
if doc_id in combined_scores:
|
||
combined_scores[doc_id] += hit['_score'] * 0.3
|
||
else:
|
||
combined_scores[doc_id] = hit['_score'] * 0.3
|
||
|
||
# 4. 상위 k개 반환
|
||
sorted_results = sorted(combined_scores.items(), key=lambda x: x[1], reverse=True)
|
||
return sorted_results[:top_k]
|
||
```
|
||
|
||
### 3. Claude API 비용 절감
|
||
|
||
#### 비용 구조
|
||
```
|
||
Claude 3.5 Sonnet:
|
||
- Input: $3 / 1M tokens
|
||
- Output: $15 / 1M tokens
|
||
|
||
월 예상 비용 (500 회의록, 평균 10회 용어 설명):
|
||
- 총 요청: 5,000회
|
||
- 평균 Input: 1,500 tokens/요청
|
||
- 평균 Output: 300 tokens/요청
|
||
|
||
비용 = (5000 * 1500 * $3 / 1M) + (5000 * 300 * $15 / 1M)
|
||
= $22.5 + $22.5
|
||
= $45/월
|
||
|
||
캐시 적용 시 (60% 히트율):
|
||
= $45 * 0.4 = $18/월
|
||
```
|
||
|
||
#### Rate Limiting
|
||
```typescript
|
||
import { RateLimiter } from 'limiter';
|
||
|
||
const limiter = new RateLimiter({
|
||
tokensPerInterval: 60, // 분당 60회
|
||
interval: 'minute'
|
||
});
|
||
|
||
async function callClaudeWithRateLimit(prompt: string) {
|
||
await limiter.removeTokens(1);
|
||
return await anthropic.messages.create({...});
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
## 품질 검증 기준
|
||
|
||
### 1. 자동 검증
|
||
|
||
#### 용어 감지 정확도
|
||
```python
|
||
def calculate_term_detection_accuracy():
|
||
"""
|
||
테스트 세트: 100개 회의록, 200개 용어
|
||
"""
|
||
test_data = load_test_data("term_detection_test.json")
|
||
|
||
correct = 0
|
||
total = len(test_data)
|
||
|
||
for sample in test_data:
|
||
detected_terms = detect_terms(sample.content)
|
||
expected_terms = sample.expected_terms
|
||
|
||
# F1 Score 계산
|
||
precision = len(set(detected_terms) & set(expected_terms)) / len(detected_terms)
|
||
recall = len(set(detected_terms) & set(expected_terms)) / len(expected_terms)
|
||
f1 = 2 * (precision * recall) / (precision + recall)
|
||
|
||
correct += f1
|
||
|
||
return (correct / total) * 100
|
||
```
|
||
|
||
#### 설명 품질 평가
|
||
```python
|
||
def evaluate_explanation_quality():
|
||
"""
|
||
사람 평가 + 자동 평가 결합
|
||
"""
|
||
test_cases = load_test_cases("explanation_quality.json")
|
||
|
||
scores = []
|
||
|
||
for case in test_cases:
|
||
explanation = explain_term(case.term, case.context)
|
||
|
||
# 자동 평가 (1-5점)
|
||
auto_score = 0
|
||
|
||
# 1) 정의 포함 여부 (1점)
|
||
if len(explanation.definition) > 10:
|
||
auto_score += 1
|
||
|
||
# 2) 맥락 설명 포함 여부 (1점)
|
||
if len(explanation.context) > 20:
|
||
auto_score += 1
|
||
|
||
# 3) 사용 사례 포함 여부 (1점)
|
||
if len(explanation.usage_examples) >= 1:
|
||
auto_score += 1
|
||
|
||
# 4) 관련 문서 연결 여부 (1점)
|
||
if len(explanation.references) >= 1:
|
||
auto_score += 1
|
||
|
||
# 5) 과거 논의 포함 여부 (1점)
|
||
if len(explanation.past_discussions) >= 1:
|
||
auto_score += 1
|
||
|
||
scores.append(auto_score)
|
||
|
||
return sum(scores) / len(scores)
|
||
```
|
||
|
||
### 2. 사람 평가
|
||
|
||
#### 평가 기준
|
||
| 항목 | 배점 | 기준 |
|
||
|------|------|------|
|
||
| **정확성** | 30점 | 용어 정의가 정확한가? |
|
||
| **맥락 적합성** | 30점 | 현재 회의 맥락과 관련성이 높은가? |
|
||
| **실용성** | 20점 | 실제 업무에 도움이 되는가? |
|
||
| **가독성** | 10점 | 이해하기 쉬운가? |
|
||
| **완성도** | 10점 | 관련 문서, 과거 논의 포함 여부 |
|
||
|
||
#### 평가 프로세스
|
||
1. 매 Sprint 종료 시 20개 샘플 평가
|
||
2. 평가자: Beta 테스터 10명 (무작위 선정)
|
||
3. 목표 점수: 80점 이상
|
||
|
||
---
|
||
|
||
## 운영 및 모니터링
|
||
|
||
### 1. 성능 메트릭
|
||
|
||
#### Prometheus 메트릭
|
||
```typescript
|
||
import { Counter, Histogram, Gauge } from 'prom-client';
|
||
|
||
// 요청 수
|
||
const requestCounter = new Counter({
|
||
name: 'rag_requests_total',
|
||
help: 'Total number of RAG requests',
|
||
labelNames: ['status', 'cache_hit']
|
||
});
|
||
|
||
// 응답 시간
|
||
const responseTimeHistogram = new Histogram({
|
||
name: 'rag_response_time_seconds',
|
||
help: 'RAG response time in seconds',
|
||
buckets: [0.5, 1, 2, 3, 5]
|
||
});
|
||
|
||
// 캐시 히트율
|
||
const cacheHitRate = new Gauge({
|
||
name: 'rag_cache_hit_rate',
|
||
help: 'Cache hit rate percentage'
|
||
});
|
||
|
||
// Claude API 비용
|
||
const apiCostCounter = new Counter({
|
||
name: 'claude_api_cost_usd',
|
||
help: 'Estimated Claude API cost in USD'
|
||
});
|
||
```
|
||
|
||
#### Grafana 대시보드
|
||
```
|
||
┌────────────────────────────────────────────┐
|
||
│ RAG 시스템 성능 모니터링 │
|
||
├────────────────────────────────────────────┤
|
||
│ [ 실시간 요청 수 ] [ 평균 응답 시간 ] │
|
||
│ 125 req/min 2.3s │
|
||
├────────────────────────────────────────────┤
|
||
│ [ 캐시 히트율 ] [ Claude API 비용 ] │
|
||
│ 65% $18/월 │
|
||
├────────────────────────────────────────────┤
|
||
│ [ 용어 감지 정확도 ] [ 설명 품질 점수 ] │
|
||
│ 88% 85/100 │
|
||
└────────────────────────────────────────────┘
|
||
```
|
||
|
||
### 2. 알림 설정
|
||
|
||
#### Alertmanager 규칙
|
||
```yaml
|
||
groups:
|
||
- name: rag_alerts
|
||
rules:
|
||
- alert: HighResponseTime
|
||
expr: rag_response_time_seconds > 5
|
||
for: 5m
|
||
labels:
|
||
severity: warning
|
||
annotations:
|
||
summary: "RAG 응답 시간 초과 (>5초)"
|
||
|
||
- alert: LowCacheHitRate
|
||
expr: rag_cache_hit_rate < 50
|
||
for: 10m
|
||
labels:
|
||
severity: info
|
||
annotations:
|
||
summary: "캐시 히트율 낮음 (<50%)"
|
||
|
||
- alert: ClaudeAPIError
|
||
expr: increase(rag_requests_total{status="error"}[5m]) > 10
|
||
labels:
|
||
severity: critical
|
||
annotations:
|
||
summary: "Claude API 오류 급증"
|
||
```
|
||
|
||
### 3. 로깅 전략
|
||
|
||
```typescript
|
||
import winston from 'winston';
|
||
|
||
const logger = winston.createLogger({
|
||
level: 'info',
|
||
format: winston.format.json(),
|
||
transports: [
|
||
new winston.transports.File({ filename: 'rag-error.log', level: 'error' }),
|
||
new winston.transports.File({ filename: 'rag-combined.log' })
|
||
]
|
||
});
|
||
|
||
// 로그 예시
|
||
logger.info('Term explanation generated', {
|
||
term: 'RAG',
|
||
meeting_id: 'meeting_12345',
|
||
response_time_ms: 2300,
|
||
cache_hit: false,
|
||
related_docs_count: 5,
|
||
claude_tokens: { input: 1500, output: 320 }
|
||
});
|
||
```
|
||
|
||
---
|
||
|
||
## 부록
|
||
|
||
### A. API 명세서
|
||
|
||
#### POST /api/rag/explain
|
||
**요청**:
|
||
```json
|
||
{
|
||
"term": "RAG",
|
||
"meeting_id": "meeting_12345",
|
||
"context": {
|
||
"meeting_title": "주간 스크럼",
|
||
"date": "2025-01-20T14:00:00Z",
|
||
"participants": ["김민준", "박서연"],
|
||
"project_name": "회의록 시스템"
|
||
}
|
||
}
|
||
```
|
||
|
||
**응답**:
|
||
```json
|
||
{
|
||
"term": "RAG",
|
||
"explanation": {
|
||
"definition": "Retrieval-Augmented Generation의 약자로...",
|
||
"context": "우리 팀은 회의록 시스템에 RAG를 적용하여...",
|
||
"usage_examples": ["..."],
|
||
"related_projects": ["..."],
|
||
"past_discussions": [...],
|
||
"references": ["doc_12345"]
|
||
},
|
||
"metadata": {
|
||
"response_time_ms": 2300,
|
||
"cache_hit": false,
|
||
"related_docs_count": 5,
|
||
"confidence_score": 0.92
|
||
}
|
||
}
|
||
```
|
||
|
||
### B. 배치 작업 스케줄
|
||
|
||
| 작업 | 주기 | 시간 | 설명 |
|
||
|------|------|------|------|
|
||
| 회의록 벡터화 | 실시간 | - | 회의 종료 후 즉시 |
|
||
| 위키 동기화 | 매일 | 새벽 2시 | Confluence API 호출 |
|
||
| 캐시 워밍 | 매일 | 새벽 3시 | 상위 50개 용어 캐싱 |
|
||
| 용어 사전 갱신 | 주간 | 일요일 새벽 1시 | 신규 용어 자동 추가 |
|
||
| 성능 리포트 생성 | 주간 | 월요일 오전 9시 | 주간 성능 분석 |
|
||
|
||
---
|
||
|
||
## 변경 이력
|
||
|
||
| 버전 | 날짜 | 변경 내용 | 작성자 |
|
||
|------|------|-----------|--------|
|
||
| 1.0 | 2025-01-20 | 초기 구현방안 작성 | 박서연, 이준호, 홍길동 |
|
||
| 2.0 | 2025-01-20 | 하이브리드형 로드맵으로 최종 승인 | 전체 팀 |
|
||
|
||
---
|
||
|
||
**문서 끝**
|