mirror of
https://github.com/hwanny1128/HGZero.git
synced 2026-06-13 09:29:10 +00:00
feat: rag 서비스 Event Hub 연동 및 연관 회의록 API 추가
This commit is contained in:
Binary file not shown.
@@ -1,6 +1,6 @@
|
||||
"""
|
||||
Azure Event Hub Consumer 서비스
|
||||
회의록 확정 이벤트를 consume하여 RAG 저장소에 저장
|
||||
회의록 확정 이벤트 및 세그먼트 생성 이벤트를 consume
|
||||
"""
|
||||
import asyncio
|
||||
import json
|
||||
@@ -13,7 +13,9 @@ from azure.eventhub.extensions.checkpointstoreblobaio import BlobCheckpointStore
|
||||
|
||||
from ..models.minutes import RagMinutes, MinutesSection
|
||||
from ..db.rag_minutes_db import RagMinutesDB
|
||||
from ..db.postgres_vector import PostgresVectorDB
|
||||
from ..utils.embedding import EmbeddingGenerator
|
||||
from ..utils.text_processor import extract_nouns_as_query
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
@@ -29,7 +31,9 @@ class EventHubConsumer:
|
||||
storage_connection_string: str,
|
||||
storage_container_name: str,
|
||||
rag_minutes_db: RagMinutesDB,
|
||||
embedding_gen: EmbeddingGenerator
|
||||
embedding_gen: EmbeddingGenerator,
|
||||
term_db: Optional[PostgresVectorDB] = None,
|
||||
config: Optional[Dict[str, Any]] = None
|
||||
):
|
||||
"""
|
||||
초기화
|
||||
@@ -42,6 +46,8 @@ class EventHubConsumer:
|
||||
storage_container_name: Checkpoint 저장 컨테이너 이름
|
||||
rag_minutes_db: RAG Minutes 데이터베이스
|
||||
embedding_gen: Embedding 생성기
|
||||
term_db: 용어집 데이터베이스 (선택)
|
||||
config: 설정 딕셔너리 (선택)
|
||||
"""
|
||||
self.connection_string = connection_string
|
||||
self.eventhub_name = eventhub_name
|
||||
@@ -50,6 +56,8 @@ class EventHubConsumer:
|
||||
self.storage_container_name = storage_container_name
|
||||
self.rag_minutes_db = rag_minutes_db
|
||||
self.embedding_gen = embedding_gen
|
||||
self.term_db = term_db
|
||||
self.config = config or {}
|
||||
self.client: Optional[EventHubConsumerClient] = None
|
||||
self.is_running = False
|
||||
|
||||
@@ -106,13 +114,18 @@ class EventHubConsumer:
|
||||
event_body = event.body_as_str()
|
||||
event_data = json.loads(event_body)
|
||||
|
||||
logger.info(f"이벤트 수신: {event_data.get('eventType', 'unknown')}")
|
||||
logger.info(f"이벤트 수신: {event_data.get('data', 'unknown')}")
|
||||
event_type = event_data.get('eventType', 'unknown')
|
||||
logger.info(f"이벤트 수신: {event_type}")
|
||||
|
||||
# 회의록 확정 이벤트 처리
|
||||
if event_data.get("eventType") == "MINUTES_FINALIZED":
|
||||
# 이벤트 타입별 처리
|
||||
if event_type == "MINUTES_FINALIZED":
|
||||
# 회의록 확정 이벤트
|
||||
await self._process_minutes_event(event_data)
|
||||
|
||||
elif event_type == "SegmentCreated":
|
||||
# 세그먼트 생성 이벤트 - 용어검색 실행
|
||||
await self._process_segment_event(event_data)
|
||||
|
||||
# Checkpoint 업데이트
|
||||
await partition_context.update_checkpoint(event)
|
||||
|
||||
@@ -131,6 +144,110 @@ class EventHubConsumer:
|
||||
"""
|
||||
logger.error(f"Event Hub 에러 (Partition {partition_context.partition_id}): {str(error)}")
|
||||
|
||||
async def _process_segment_event(self, event_data: Dict[str, Any]):
|
||||
"""
|
||||
세그먼트 생성 이벤트 처리 - 용어검색 실행
|
||||
|
||||
Args:
|
||||
event_data: 이벤트 데이터
|
||||
"""
|
||||
try:
|
||||
# 용어집 DB가 없으면 스킵
|
||||
if not self.term_db:
|
||||
logger.debug("용어집 DB가 설정되지 않아 용어검색을 스킵합니다")
|
||||
return
|
||||
|
||||
# 세그먼트 데이터 추출
|
||||
segment_id = event_data.get("segmentId")
|
||||
text = event_data.get("text", "")
|
||||
meeting_id = event_data.get("meetingId")
|
||||
|
||||
if not text:
|
||||
logger.warning(f"세그먼트 {segment_id}에 텍스트가 없습니다")
|
||||
return
|
||||
|
||||
logger.info(f"세그먼트 용어검색 시작: {segment_id} (회의: {meeting_id})")
|
||||
logger.info(f"텍스트: {text[:100]}...")
|
||||
|
||||
# 1. 명사 추출하여 검색 쿼리 생성
|
||||
search_query = extract_nouns_as_query(text)
|
||||
logger.info(f"검색 쿼리 변환: '{text[:30]}...' → '{search_query}'")
|
||||
|
||||
# 2. 용어검색 설정
|
||||
config = self.config.get("term_glossary", {})
|
||||
search_config = config.get("search", {})
|
||||
|
||||
top_k = search_config.get("top_k", 5)
|
||||
confidence_threshold = search_config.get("confidence_threshold", 0.7)
|
||||
keyword_weight = search_config.get("keyword_weight", 0.4)
|
||||
vector_weight = search_config.get("vector_weight", 0.6)
|
||||
|
||||
# 3. 키워드 검색
|
||||
keyword_results = self.term_db.search_by_keyword(
|
||||
query=search_query,
|
||||
top_k=top_k,
|
||||
confidence_threshold=confidence_threshold
|
||||
)
|
||||
|
||||
# 4. 벡터 검색
|
||||
query_embedding = self.embedding_gen.generate_embedding(search_query)
|
||||
vector_results = self.term_db.search_by_vector(
|
||||
query_embedding=query_embedding,
|
||||
top_k=top_k,
|
||||
confidence_threshold=confidence_threshold
|
||||
)
|
||||
|
||||
# 5. 하이브리드 검색 결과 통합 (RRF)
|
||||
results = []
|
||||
seen_ids = set()
|
||||
|
||||
# 키워드 결과 가중치 적용
|
||||
for result in keyword_results:
|
||||
term_id = result["term"].term_id
|
||||
if term_id not in seen_ids:
|
||||
result["relevance_score"] *= keyword_weight
|
||||
result["match_type"] = "hybrid"
|
||||
results.append(result)
|
||||
seen_ids.add(term_id)
|
||||
|
||||
# 벡터 결과 가중치 적용
|
||||
for result in vector_results:
|
||||
term_id = result["term"].term_id
|
||||
if term_id not in seen_ids:
|
||||
result["relevance_score"] *= vector_weight
|
||||
result["match_type"] = "hybrid"
|
||||
results.append(result)
|
||||
seen_ids.add(term_id)
|
||||
else:
|
||||
# 이미 있는 경우 점수 합산
|
||||
for r in results:
|
||||
if r["term"].term_id == term_id:
|
||||
r["relevance_score"] += result["relevance_score"] * vector_weight
|
||||
break
|
||||
|
||||
# 점수 기준 재정렬
|
||||
results.sort(key=lambda x: x["relevance_score"], reverse=True)
|
||||
results = results[:top_k]
|
||||
|
||||
# 6. 검색 결과 로깅
|
||||
if results:
|
||||
logger.info(f"세그먼트 {segment_id} 용어검색 완료: {len(results)}개 발견")
|
||||
for idx, result in enumerate(results, 1):
|
||||
term = result["term"]
|
||||
score = result["relevance_score"]
|
||||
logger.info(
|
||||
f" [{idx}] {term.term_name} "
|
||||
f"(카테고리: {term.category}, 점수: {score:.3f})"
|
||||
)
|
||||
else:
|
||||
logger.info(f"세그먼트 {segment_id}에서 매칭되는 용어를 찾지 못했습니다")
|
||||
|
||||
# 7. 선택적: 검색 결과를 별도 테이블에 저장하거나 Event Hub로 발행
|
||||
# TODO: 필요시 검색 결과를 저장하거나 downstream 서비스로 전달
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"세그먼트 이벤트 처리 실패: {str(e)}", exc_info=True)
|
||||
|
||||
def _convert_datetime_array_to_string(self, value: Union[str, List, None]) -> Optional[str]:
|
||||
"""
|
||||
Java LocalDateTime 배열을 ISO 8601 문자열로 변환
|
||||
@@ -302,7 +419,8 @@ class EventHubConsumer:
|
||||
async def start_consumer(
|
||||
config: Dict[str, Any],
|
||||
rag_minutes_db: RagMinutesDB,
|
||||
embedding_gen: EmbeddingGenerator
|
||||
embedding_gen: EmbeddingGenerator,
|
||||
term_db: Optional[PostgresVectorDB] = None
|
||||
):
|
||||
"""
|
||||
Event Hub Consumer 시작 (비동기)
|
||||
@@ -311,6 +429,7 @@ async def start_consumer(
|
||||
config: 설정 딕셔너리
|
||||
rag_minutes_db: RAG Minutes 데이터베이스
|
||||
embedding_gen: Embedding 생성기
|
||||
term_db: 용어집 데이터베이스 (선택)
|
||||
"""
|
||||
eventhub_config = config["eventhub"]
|
||||
|
||||
@@ -321,7 +440,9 @@ async def start_consumer(
|
||||
storage_connection_string=eventhub_config["storage"]["connection_string"],
|
||||
storage_container_name=eventhub_config["storage"]["container_name"],
|
||||
rag_minutes_db=rag_minutes_db,
|
||||
embedding_gen=embedding_gen
|
||||
embedding_gen=embedding_gen,
|
||||
term_db=term_db,
|
||||
config=config
|
||||
)
|
||||
|
||||
try:
|
||||
|
||||
Reference in New Issue
Block a user