hgzero/rag/docs/api-related-minutes.md

292 lines
7.6 KiB
Markdown

# 연관 회의록 조회 API 구현 문서
## 📋 개요
**준호** (Backend Developer)
회의록 ID를 기준으로 유사한 연관 회의록을 조회하는 API를 구현했습니다.
## 🎯 구현 방식: Option A (DB 조회 후 벡터 검색)
### 선택 이유
-**정확도 우선**: full_content 기반 검색으로 높은 정확도 보장
-**확장성**: 향후 full_content 기반 추가 기능 확장 용이
-**성능 최적화**: Redis 캐싱으로 DB 조회 오버헤드 최소화
### 처리 흐름
```
1. 요청 수신
2. Redis 캐시 조회 (연관 회의록 결과)
├─ HIT → 즉시 반환
└─ MISS → 3단계로 진행
3. Redis 캐시 조회 (회의록 full_content)
├─ HIT → 5단계로 진행
└─ MISS → 4단계로 진행
4. DB 조회 (rag_minutes 테이블)
├─ full_content 획득
└─ Redis에 캐싱 (TTL: 30분)
5. 벡터 임베딩 생성 (Azure OpenAI)
6. 벡터 유사도 검색 (자기 자신 제외)
7. 결과 Redis 캐싱 (TTL: 1시간)
8. 응답 반환
```
## 🔧 구현 상세
### 1. 데이터 모델 추가
**파일**: `rag/src/models/minutes.py`
```python
class RelatedMinutesRequest(BaseModel):
"""연관 회의록 조회 요청"""
minute_id: str # 기준 회의록 ID
meeting_title: str # 회의 제목 (현재 미사용)
summary: str # 회의록 요약 (현재 미사용)
top_k: int = 5 # 반환할 최대 결과 수
similarity_threshold: float = 0.7 # 최소 유사도 임계값
class RelatedMinutesResponse(BaseModel):
"""연관 회의록 조회 응답"""
minutes: RagMinutes # 회의록 정보
similarity_score: float # 유사도 점수 (0.0 ~ 1.0)
```
### 2. DB 쿼리 함수 개선
**파일**: `rag/src/db/rag_minutes_db.py`
**수정 내용**: `search_by_vector()` 함수에 `exclude_minutes_id` 파라미터 추가
```python
def search_by_vector(
self,
query_embedding: List[float],
top_k: int = 5,
similarity_threshold: float = 0.7,
exclude_minutes_id: Optional[str] = None # 자기 자신 제외
) -> List[Dict[str, Any]]:
"""벡터 유사도 검색"""
# WHERE 절에 minutes_id != %s 조건 추가
# 자기 자신을 결과에서 제외
```
### 3. Redis 캐싱 유틸리티 구현
**파일**: `rag/src/utils/redis_cache.py`
**주요 기능**:
- `get(key)`: 캐시 조회
- `set(key, value, ttl)`: 캐시 저장
- `delete(key)`: 캐시 삭제
- `delete_pattern(pattern)`: 패턴 매칭 일괄 삭제
**특징**:
- Redis 연결 실패 시 자동으로 캐싱 비활성화 (서비스 장애 방지)
- JSON 직렬화/역직렬화 자동 처리
### 4. 설정 추가
**파일**: `rag/config.yaml`
```yaml
rag_minutes:
search:
top_k: 5
similarity_threshold: 0.7
cache:
ttl: 1800 # 회의록 조회 결과: 30분
prefix: "minutes:"
related_ttl: 3600 # 연관 회의록 검색 결과: 1시간
```
### 5. API 엔드포인트 구현
**파일**: `rag/src/api/main.py`
**엔드포인트**: `POST /api/minutes/related`
**Request Body**:
```json
{
"minute_id": "MIN-2025-001",
"meeting_title": "2025 Q1 마케팅 전략 회의",
"summary": "2025년 1분기 마케팅 전략 수립을 위한 회의",
"top_k": 5,
"similarity_threshold": 0.7
}
```
**Response**:
```json
[
{
"minutes": {
"meeting_id": "MTG-2024-050",
"title": "2024 Q4 마케팅 성과 분석",
"minutes_id": "MIN-2024-050",
"full_content": "...",
...
},
"similarity_score": 0.85
},
...
]
```
## ⚡ 성능 최적화
### 2단계 캐싱 전략
1. **회의록 조회 결과 캐싱** (TTL: 30분)
- 키: `minutes:{minute_id}`
- DB 조회 횟수 감소
2. **연관 회의록 검색 결과 캐싱** (TTL: 1시간)
- 키: `minutes:related:{minute_id}:{top_k}:{threshold}`
- 벡터 검색 및 임베딩 생성 비용 절감
### 예상 성능 개선
| 케이스 | 처리 시간 | 비고 |
|--------|----------|------|
| 캐시 MISS (최초 요청) | ~2-3초 | DB 조회 + 임베딩 생성 + 벡터 검색 |
| 회의록 캐시 HIT | ~1-2초 | 임베딩 생성 + 벡터 검색 |
| 연관 회의록 캐시 HIT | ~50ms | 캐시에서 즉시 반환 |
## 🔍 사용 예시
### Python (requests)
```python
import requests
url = "http://localhost:8000/api/minutes/related"
data = {
"minute_id": "MIN-2025-001",
"meeting_title": "2025 Q1 마케팅 전략 회의",
"summary": "2025년 1분기 마케팅 전략 수립",
"top_k": 5,
"similarity_threshold": 0.7
}
response = requests.post(url, json=data)
related_minutes = response.json()
for item in related_minutes:
print(f"제목: {item['minutes']['title']}")
print(f"유사도: {item['similarity_score']:.2f}")
print("---")
```
### curl
```bash
curl -X POST "http://localhost:8000/api/minutes/related" \
-H "Content-Type: application/json" \
-d '{
"minute_id": "MIN-2025-001",
"meeting_title": "2025 Q1 마케팅 전략 회의",
"summary": "2025년 1분기 마케팅 전략 수립",
"top_k": 5,
"similarity_threshold": 0.7
}'
```
## 🧪 테스트 시나리오
### 1. 정상 케이스
- ✅ 존재하는 minute_id로 요청
- ✅ 유사한 회의록 5개 반환
- ✅ 자기 자신은 결과에서 제외
### 2. 캐싱 동작 확인
- ✅ 최초 요청: 캐시 MISS → DB 조회
- ✅ 2번째 요청: 캐시 HIT → 즉시 반환
- ✅ TTL 경과 후: 캐시 MISS → 재조회
### 3. 에러 케이스
- ❌ 존재하지 않는 minute_id → 404 오류
- ❌ 잘못된 요청 형식 → 422 오류
## 📊 데이터베이스 영향
### 쿼리 패턴
```sql
-- 1. 회의록 조회 (캐시 MISS 시)
SELECT * FROM rag_minutes WHERE minutes_id = 'MIN-2025-001';
-- 2. 벡터 유사도 검색 (캐시 MISS 시)
SELECT *,
1 - (embedding <=> '{vector}'::vector) as similarity_score
FROM rag_minutes
WHERE embedding IS NOT NULL
AND 1 - (embedding <=> '{vector}'::vector) >= 0.7
AND minutes_id != 'MIN-2025-001' -- 자기 자신 제외
ORDER BY embedding <=> '{vector}'::vector
LIMIT 5;
```
### 인덱스 활용
- `minutes_id`: Primary Key 인덱스 활용
- `embedding`: pgvector 인덱스 활용 (IVFFlat 또는 HNSW)
## 🔐 보안 고려사항
1. **SQL Injection 방지**: psycopg2 파라미터 바인딩 사용
2. **Rate Limiting**: 향후 추가 권장 (분당 요청 수 제한)
3. **인증/인가**: 향후 JWT 토큰 기반 인증 추가 권장
## 🚀 향후 개선 사항
### 1. Hybrid 검색 지원 (Option B 통합)
- summary를 활용한 빠른 1차 검색
- full_content로 정밀한 2차 검색
- 적응형 임계값 조정
### 2. 성능 모니터링
- 캐시 히트율 추적
- API 응답 시간 측정
- 벡터 검색 성능 분석
### 3. 검색 품질 개선
- 시간 가중치 적용 (최근 회의록 우선)
- 부서/팀별 필터링 옵션
- 주제별 카테고리 분류
## 📝 파일 변경 목록
| 파일 | 변경 유형 | 설명 |
|------|----------|------|
| `rag/src/models/minutes.py` | 수정 | Request/Response 모델 추가 |
| `rag/src/db/rag_minutes_db.py` | 수정 | exclude_minutes_id 파라미터 추가 |
| `rag/src/utils/redis_cache.py` | 신규 | Redis 캐싱 유틸리티 |
| `rag/src/api/main.py` | 수정 | API 엔드포인트 추가 |
| `rag/config.yaml` | 수정 | rag_minutes 설정 추가 |
## ✅ 구현 완료 체크리스트
- [x] Request/Response 모델 정의
- [x] DB 쿼리 함수 개선 (자기 자신 제외)
- [x] Redis 캐싱 유틸리티 구현
- [x] API 엔드포인트 구현
- [x] 2단계 캐싱 전략 적용
- [x] 설정 파일 업데이트
- [x] 문서 작성
---
**작성자**: 준호 (Backend Developer)
**작성일**: 2025-10-29
**버전**: 1.0.0