mirror of
https://github.com/hwanny1128/HGZero.git
synced 2026-06-13 14:09:10 +00:00
feat: rag 서비스 Event Hub 연동 및 연관 회의록 API 추가
This commit is contained in:
Binary file not shown.
+138
-1
@@ -22,7 +22,9 @@ from ..models.document import (
|
||||
)
|
||||
from ..models.minutes import (
|
||||
MinutesSearchRequest,
|
||||
MinutesSearchResult
|
||||
MinutesSearchResult,
|
||||
RelatedMinutesRequest,
|
||||
RelatedMinutesResponse
|
||||
)
|
||||
from ..db.postgres_vector import PostgresVectorDB
|
||||
from ..db.azure_search import AzureAISearchDB
|
||||
@@ -31,6 +33,7 @@ from ..services.claude_service import ClaudeService
|
||||
from ..utils.config import load_config, get_database_url
|
||||
from ..utils.embedding import EmbeddingGenerator
|
||||
from ..utils.text_processor import extract_nouns_as_query
|
||||
from ..utils.redis_cache import RedisCache
|
||||
|
||||
# 로깅 설정
|
||||
logging.basicConfig(
|
||||
@@ -62,6 +65,7 @@ _doc_db = None
|
||||
_rag_minutes_db = None
|
||||
_embedding_gen = None
|
||||
_claude_service = None
|
||||
_redis_cache = None
|
||||
|
||||
|
||||
def get_config():
|
||||
@@ -139,6 +143,22 @@ def get_claude_service():
|
||||
return _claude_service
|
||||
|
||||
|
||||
def get_redis_cache():
|
||||
"""Redis 캐시"""
|
||||
global _redis_cache
|
||||
if _redis_cache is None:
|
||||
config = get_config()
|
||||
redis_config = config["redis"]
|
||||
_redis_cache = RedisCache(
|
||||
host=redis_config["host"],
|
||||
port=redis_config["port"],
|
||||
db=redis_config["db"],
|
||||
password=redis_config.get("password"),
|
||||
decode_responses=redis_config.get("decode_responses", True)
|
||||
)
|
||||
return _redis_cache
|
||||
|
||||
|
||||
# ============================================================================
|
||||
# 용어집 API
|
||||
# ============================================================================
|
||||
@@ -501,6 +521,123 @@ async def get_minutes_stats(rag_minutes_db: RagMinutesDB = Depends(get_rag_minut
|
||||
raise HTTPException(status_code=500, detail=str(e))
|
||||
|
||||
|
||||
@app.post("/api/minutes/related", response_model=List[RelatedMinutesResponse])
|
||||
async def get_related_minutes(
|
||||
request: RelatedMinutesRequest,
|
||||
rag_minutes_db: RagMinutesDB = Depends(get_rag_minutes_db),
|
||||
embedding_gen: EmbeddingGenerator = Depends(get_embedding_gen),
|
||||
redis_cache: RedisCache = Depends(get_redis_cache)
|
||||
):
|
||||
"""
|
||||
연관 회의록 조회 (Option A: DB 조회 후 벡터 검색 + Redis 캐싱)
|
||||
|
||||
Args:
|
||||
request: 연관 회의록 조회 요청
|
||||
- minute_id: 기준 회의록 ID
|
||||
- meeting_title: 회의 제목 (미사용)
|
||||
- summary: 회의록 요약 (미사용)
|
||||
- top_k: 반환할 최대 결과 수
|
||||
- similarity_threshold: 최소 유사도 임계값
|
||||
|
||||
Returns:
|
||||
연관 회의록 리스트
|
||||
|
||||
Process:
|
||||
1. Redis 캐시에서 연관 회의록 결과 조회
|
||||
2. 캐시 MISS 시:
|
||||
a. minute_id로 rag_minutes 테이블에서 회의록 조회 (캐싱)
|
||||
b. full_content를 벡터 임베딩으로 변환
|
||||
c. 벡터 DB에서 유사도 검색 (자기 자신 제외)
|
||||
d. 결과를 Redis에 캐싱
|
||||
3. 연관 회의록 목록 반환
|
||||
"""
|
||||
try:
|
||||
config = get_config()
|
||||
cache_config = config.get("rag_minutes", {}).get("cache", {})
|
||||
cache_prefix = cache_config.get("prefix", "minutes:")
|
||||
minutes_ttl = cache_config.get("ttl", 1800)
|
||||
related_ttl = cache_config.get("related_ttl", 3600)
|
||||
|
||||
logger.info(f"연관 회의록 조회 시작: minute_id={request.minute_id}")
|
||||
|
||||
# 1. 캐시 키 생성
|
||||
related_cache_key = (
|
||||
f"{cache_prefix}related:{request.minute_id}:"
|
||||
f"{request.top_k}:{request.similarity_threshold}"
|
||||
)
|
||||
|
||||
# 2. 캐시 조회
|
||||
cached_results = redis_cache.get(related_cache_key)
|
||||
if cached_results:
|
||||
logger.info(f"연관 회의록 캐시 HIT: {related_cache_key}")
|
||||
return [
|
||||
RelatedMinutesResponse(**result)
|
||||
for result in cached_results
|
||||
]
|
||||
|
||||
# 3. 캐시 MISS - DB 조회
|
||||
logger.info(f"연관 회의록 캐시 MISS: {related_cache_key}")
|
||||
|
||||
# 3-1. 회의록 조회 (캐싱)
|
||||
minutes_cache_key = f"{cache_prefix}{request.minute_id}"
|
||||
base_minutes = redis_cache.get(minutes_cache_key)
|
||||
|
||||
if base_minutes:
|
||||
logger.info(f"회의록 캐시 HIT: {minutes_cache_key}")
|
||||
# RagMinutes 객체로 변환
|
||||
from ..models.minutes import RagMinutes
|
||||
base_minutes = RagMinutes(**base_minutes)
|
||||
else:
|
||||
logger.info(f"회의록 캐시 MISS: {minutes_cache_key}")
|
||||
base_minutes = rag_minutes_db.get_minutes_by_id(request.minute_id)
|
||||
if not base_minutes:
|
||||
raise HTTPException(
|
||||
status_code=404,
|
||||
detail=f"회의록을 찾을 수 없습니다: {request.minute_id}"
|
||||
)
|
||||
# 캐시 저장
|
||||
redis_cache.set(minutes_cache_key, base_minutes.dict(), minutes_ttl)
|
||||
|
||||
logger.info(f"기준 회의록 조회 완료: {base_minutes.title}")
|
||||
|
||||
# 3-2. full_content를 벡터 임베딩으로 변환
|
||||
query_embedding = embedding_gen.generate_embedding(base_minutes.full_content)
|
||||
logger.info(f"임베딩 생성 완료: {len(query_embedding)}차원")
|
||||
|
||||
# 3-3. 벡터 유사도 검색 (자기 자신 제외)
|
||||
results = rag_minutes_db.search_by_vector(
|
||||
query_embedding=query_embedding,
|
||||
top_k=request.top_k,
|
||||
similarity_threshold=request.similarity_threshold,
|
||||
exclude_minutes_id=request.minute_id
|
||||
)
|
||||
|
||||
# 4. 응답 형식으로 변환
|
||||
related_minutes = [
|
||||
RelatedMinutesResponse(
|
||||
minutes=result["minutes"],
|
||||
similarity_score=result["similarity_score"]
|
||||
)
|
||||
for result in results
|
||||
]
|
||||
|
||||
# 5. 결과 캐싱
|
||||
redis_cache.set(
|
||||
related_cache_key,
|
||||
[r.dict() for r in related_minutes],
|
||||
related_ttl
|
||||
)
|
||||
|
||||
logger.info(f"연관 회의록 조회 완료: {len(related_minutes)}개 결과")
|
||||
return related_minutes
|
||||
|
||||
except HTTPException:
|
||||
raise
|
||||
except Exception as e:
|
||||
logger.error(f"연관 회의록 조회 실패: {str(e)}")
|
||||
raise HTTPException(status_code=500, detail=str(e))
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
import uvicorn
|
||||
uvicorn.run(app, host="0.0.0.0", port=8000)
|
||||
|
||||
Reference in New Issue
Block a user