mirror of
https://github.com/hwanny1128/HGZero.git
synced 2025-12-06 14:56:23 +00:00
주요 변경사항: - AI 서비스 Java → Python (FastAPI) 완전 마이그레이션 - 포트 변경: 8083 → 8086 - SSE 스트리밍 기능 구현 및 테스트 완료 - Claude API 연동 (claude-3-5-sonnet-20241022) - Redis 슬라이딩 윈도우 방식 텍스트 축적 - Azure Event Hub 연동 준비 (STT 텍스트 수신) 프론트엔드 연동 지원: - API 연동 가이드 업데이트 (Python 버전 반영) - Mock 데이터 개발 가이드 신규 작성 - STT 개발 완료 전까지 Mock 데이터로 UI 개발 가능 기술 스택: - Python 3.13 - FastAPI 0.104.1 - Anthropic Claude API 0.42.0 - Redis (asyncio) 5.0.1 - Azure Event Hub 5.11.4 - Pydantic 2.10.5 테스트 결과: - ✅ 서비스 시작 정상 - ✅ 헬스 체크 성공 - ✅ SSE 스트리밍 동작 확인 - ✅ Redis 연결 정상 다음 단계: - STT (Azure Speech) 서비스 연동 개발 - Event Hub를 통한 실시간 텍스트 수신 - E2E 통합 테스트 (STT → AI → Frontend) 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
118 lines
3.4 KiB
Python
118 lines
3.4 KiB
Python
"""Redis 서비스 - 실시간 텍스트 축적"""
|
|
import redis.asyncio as redis
|
|
import logging
|
|
from typing import List
|
|
from app.config import get_settings
|
|
|
|
logger = logging.getLogger(__name__)
|
|
settings = get_settings()
|
|
|
|
|
|
class RedisService:
|
|
"""Redis 서비스 (슬라이딩 윈도우 방식)"""
|
|
|
|
def __init__(self):
|
|
self.redis_client = None
|
|
|
|
async def connect(self):
|
|
"""Redis 연결"""
|
|
try:
|
|
self.redis_client = await redis.Redis(
|
|
host=settings.redis_host,
|
|
port=settings.redis_port,
|
|
password=settings.redis_password,
|
|
db=settings.redis_db,
|
|
decode_responses=True
|
|
)
|
|
await self.redis_client.ping()
|
|
logger.info("Redis 연결 성공")
|
|
except Exception as e:
|
|
logger.error(f"Redis 연결 실패: {e}")
|
|
raise
|
|
|
|
async def disconnect(self):
|
|
"""Redis 연결 종료"""
|
|
if self.redis_client:
|
|
await self.redis_client.close()
|
|
logger.info("Redis 연결 종료")
|
|
|
|
async def add_transcript_segment(
|
|
self,
|
|
meeting_id: str,
|
|
text: str,
|
|
timestamp: int
|
|
):
|
|
"""
|
|
실시간 텍스트 세그먼트 추가 (슬라이딩 윈도우)
|
|
|
|
Args:
|
|
meeting_id: 회의 ID
|
|
text: 텍스트 세그먼트
|
|
timestamp: 타임스탬프 (밀리초)
|
|
"""
|
|
key = f"meeting:{meeting_id}:transcript"
|
|
value = f"{timestamp}:{text}"
|
|
|
|
# Sorted Set에 추가 (타임스탬프를 스코어로)
|
|
await self.redis_client.zadd(key, {value: timestamp})
|
|
|
|
# 설정된 시간 이전 데이터 제거 (기본 5분)
|
|
retention_ms = settings.text_retention_seconds * 1000
|
|
cutoff_time = timestamp - retention_ms
|
|
await self.redis_client.zremrangebyscore(key, 0, cutoff_time)
|
|
|
|
logger.debug(f"텍스트 세그먼트 추가 - meetingId: {meeting_id}")
|
|
|
|
async def get_accumulated_text(self, meeting_id: str) -> str:
|
|
"""
|
|
누적된 텍스트 조회 (최근 5분)
|
|
|
|
Args:
|
|
meeting_id: 회의 ID
|
|
|
|
Returns:
|
|
누적된 텍스트 (시간순)
|
|
"""
|
|
key = f"meeting:{meeting_id}:transcript"
|
|
|
|
# 최신순으로 모든 세그먼트 조회
|
|
segments = await self.redis_client.zrevrange(key, 0, -1)
|
|
|
|
if not segments:
|
|
return ""
|
|
|
|
# 타임스탬프 제거하고 텍스트만 추출
|
|
texts = []
|
|
for seg in segments:
|
|
parts = seg.split(":", 1)
|
|
if len(parts) == 2:
|
|
texts.append(parts[1])
|
|
|
|
# 시간순으로 정렬 (역순으로 조회했으므로 다시 뒤집기)
|
|
return "\n".join(reversed(texts))
|
|
|
|
async def get_segment_count(self, meeting_id: str) -> int:
|
|
"""
|
|
누적된 세그먼트 개수
|
|
|
|
Args:
|
|
meeting_id: 회의 ID
|
|
|
|
Returns:
|
|
세그먼트 개수
|
|
"""
|
|
key = f"meeting:{meeting_id}:transcript"
|
|
count = await self.redis_client.zcard(key)
|
|
return count if count else 0
|
|
|
|
async def cleanup_meeting_data(self, meeting_id: str):
|
|
"""
|
|
회의 종료 시 데이터 정리
|
|
|
|
Args:
|
|
meeting_id: 회의 ID
|
|
"""
|
|
key = f"meeting:{meeting_id}:transcript"
|
|
await self.redis_client.delete(key)
|
|
logger.info(f"회의 데이터 정리 완료 - meetingId: {meeting_id}")
|