# 연관 회의록 조회 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
