# rb10508_micro 기억 개선 5단계 계획 ## 개요 rb10508_micro의 AI 응답 단조로움 문제를 해결하기 위한 캐시 기반 지능형 응답 시스템 구현 계획입니다. ### 핵심 목표 - 단조로운 템플릿 응답 제거 - Gemini API 비용 최적화 - 자연스러운 대화 경험 제공 - 기존 코드 최대한 재사용 ### 기본 원칙 - 최소한의 코드 수정으로 최대 효과 - skill-embedding 서비스 활용 (포트 8015) - 점진적 구현으로 리스크 최소화 --- ## 🏗️ 기본 설정 (모든 단계 공통) ```python # config.py에 추가 USE_GEMINI_CONVERSATION: bool = False # 1단계 토글 USE_CONVERSATION_CACHE: bool = False # 2-4단계 토글 USE_RESPONSE_VARIATION: bool = False # 5단계 토글 CACHE_DISTANCE_THRESHOLD: float = 0.3 # 동적 조정 가능 CACHE_MAX_ITEMS_PER_USER: int = 1000 # 사용자당 캐시 제한 CACHE_TTL_DAYS: int = 30 # 캐시 유효 기간 GEMINI_FALLBACK_MODEL: str = "gemini-2.5-flash-lite" # 쿼터 초과 시 대체 ``` --- ## 📌 1단계: Gemini 전면 도입 + 병렬화 + Fallback ### 목표 - 응답 품질 즉시 개선 - 안정성 확보 ### 구현 ```python async def _generate_conversational_response(self, message: str, memories: List[Dict]) -> str: if not settings.USE_GEMINI_CONVERSATION: return random.choice(self.responses) # 기존 방식 try: # 병렬 처리로 지연 최소화 tasks = [ self._generate_gemini_response(message, memories, EmotionState()), self.memory.check_novelty(message, self.current_user_id) # 동시 실행 ] response, novelty = await asyncio.gather(*tasks) return response except Exception as e: if "quota" in str(e).lower(): # 쿼터 초과 시 대체 모델 self.gemini_model = genai.GenerativeModel(settings.GEMINI_FALLBACK_MODEL) return await self._generate_gemini_response(message, memories, EmotionState()) else: # 완전 실패 시 기본 응답 logger.error(f"Gemini 호출 실패: {e}") return "죄송합니다. 잠시 후 다시 시도해주세요." ``` ### 효과 - 응답 다양성 100% 개선 - API 비용 증가 (임시 감수) - 쿼터 초과 시 자동 대체 --- ## 📌 2단계: 캐시 인프라 구축 + 중복 방지 ### 목표 - 안전한 캐시 구조 마련 - 동시성 제어 ### 구현 ```python # memory.py async def _init_collections(self): # 기존 컬렉션들... if settings.USE_CONVERSATION_CACHE: self.conversation_cache = self.client.get_or_create_collection( name=f"{settings.ROBING_ID}_conversation_cache", metadata={ "type": "conversation_cache", "version": "1.0", "max_items": settings.CACHE_MAX_ITEMS_PER_USER }, embedding_function=self.embedding_function ) # 동시성 제어를 위한 간단한 락 self._cache_locks = {} # user_id: asyncio.Lock() ``` ### 하드코딩 값 설명 - `"type": "conversation_cache"`: 다른 컬렉션과 구분하기 위한 메타데이터 - `"version": "1.0"`: 향후 마이그레이션을 위한 버전 관리 --- ## 📌 3단계: 지능형 캐시 저장 + TTL 관리 ### 목표 - 중복 방지 - 자동 정리 - 사용자별 캐시 제한 ### 구현 ```python async def store_conversation_cache(self, message: str, response: str, user_id: str, emotion_state: Dict): if not settings.USE_CONVERSATION_CACHE: return # 사용자별 락 획득 if user_id not in self._cache_locks: self._cache_locks[user_id] = asyncio.Lock() async with self._cache_locks[user_id]: # 중복 체크 existing = self.conversation_cache.query( query_texts=[message], n_results=1, where={"user_id": user_id} ) if existing['distances'][0] and existing['distances'][0][0] < 0.1: # 거의 동일 logger.debug(f"중복 캐시 스킵: {message[:50]}...") return # 사용자별 캐시 개수 체크 user_cache_count = self._get_user_cache_count(user_id) if user_cache_count >= settings.CACHE_MAX_ITEMS_PER_USER: # 오래된 것 삭제 await self._cleanup_old_cache(user_id) # 저장 cache_id = f"conv_{user_id}_{int(time.time())}_{hashlib.md5(message.encode()).hexdigest()[:8]}" self.conversation_cache.add( documents=[message], metadatas=[{ "user_id": user_id, "response": response, "emotion_valence": emotion_state.get('valence', 0), "timestamp": datetime.utcnow().isoformat(), "ttl_date": (datetime.utcnow() + timedelta(days=settings.CACHE_TTL_DAYS)).isoformat(), "usage_count": 0, "last_used": datetime.utcnow().isoformat() }], ids=[cache_id] ) ``` ### 하드코딩 값 설명 - `0.1`: 중복 판단 임계값 (90% 이상 유사 시 중복) - `cache_id` 형식: 가독성과 유일성 보장 --- ## 📌 4단계: 정밀 캐시 검색 + 다중 가중치 ### 목표 - 문맥에 맞는 캐시 활용 - 감정, 시간, 유사도 종합 고려 - memory_recall 인텐트의 기계적 응답 개선 (관련성 없는 기억 나열 문제 해결) ### 구현 ```python async def search_conversation_cache(self, query: str, user_id: str, emotion_valence: float = 0) -> List[Dict]: if not settings.USE_CONVERSATION_CACHE: return [] # 기본 유사도 검색 results = self.conversation_cache.query( query_texts=[query], n_results=10, # 더 많이 가져와서 필터링 where={ "$and": [ {"user_id": user_id}, {"ttl_date": {"$gte": datetime.utcnow().isoformat()}} # TTL 체크 ] } ) if not results['documents'][0]: return [] # 다중 가중치 적용 scored_results = [] for i, doc in enumerate(results['documents'][0]): distance = results['distances'][0][i] metadata = results['metadatas'][0][i] # 거리 기반 점수 (0-1, 낮을수록 좋음) distance_score = distance # 감정 유사도 점수 (0-1, 낮을수록 좋음) emotion_diff = abs(emotion_valence - metadata.get('emotion_valence', 0)) emotion_score = emotion_diff / 2 # 정규화 # 시간 가중치 (최근일수록 좋음) days_old = (datetime.utcnow() - datetime.fromisoformat(metadata['timestamp'])).days time_score = min(days_old / settings.CACHE_TTL_DAYS, 1.0) # 종합 점수 (가중 평균) final_score = ( 0.6 * distance_score + # 의미 유사도 60% 0.2 * emotion_score + # 감정 유사도 20% 0.2 * time_score # 시간 가중치 20% ) if final_score < settings.CACHE_DISTANCE_THRESHOLD: scored_results.append({ 'content': doc, 'metadata': metadata, 'distance': distance, 'final_score': final_score }) # 점수순 정렬 return sorted(scored_results, key=lambda x: x['final_score']) ``` ### 하드코딩 값 설명 - `n_results=10`: 충분한 후보군 확보 - 가중치 비율 (0.6, 0.2, 0.2): 의미 유사도 중심, 감정과 시간 보조 --- ## 📌 5단계: 자연스러운 응답 변형 + 모니터링 ### 목표 - 기계적이지 않은 변형 - 효과 측정 ### 구현 ```python async def _vary_cached_response(self, original: str, cache_metadata: Dict) -> str: if not settings.USE_RESPONSE_VARIATION: return original # 사용 횟수에 따른 변형 강도 결정 usage_count = cache_metadata.get('usage_count', 0) if usage_count < 3: # 3회 미만은 그대로 사용 return original elif usage_count < 10: # 간단한 템플릿 변형 return self._simple_variation(original) else: # 10회 이상 사용된 응답은 Gemini로 재생성 prompt = f"""다음 응답을 의미는 유지하되 자연스럽게 다시 표현해주세요. 원본: {original} 규칙: - 핵심 의미는 동일하게 - 어투나 표현만 살짝 변경 - 1-2문장으로 간결하게""" try: response = await self.gemini_model.generate_content_async(prompt) varied = response.text.strip() # 변형된 응답도 캐시에 저장 await self.store_conversation_cache( message=cache_metadata.get('original_message', ''), response=varied, user_id=cache_metadata.get('user_id', ''), emotion_state={'valence': cache_metadata.get('emotion_valence', 0)} ) return varied except: return self._simple_variation(original) def _simple_variation(self, text: str) -> str: """간단한 템플릿 기반 변형""" # 종결어미 변형 (더 자연스럽게) endings = { "입니다.": ["이에요.", "인 것 같아요.", "이네요."], "해요.": ["하는 편이에요.", "하고 있어요.", "해 보세요."], "네요.": ["는군요.", "네요!", "는 것 같아요."] } result = text for pattern, replacements in endings.items(): if pattern in result and random.random() < 0.3: # 30% 확률 result = result.replace(pattern, random.choice(replacements), 1) break # 한 번만 변형 return result ``` ### 하드코딩 값 설명 - `usage_count < 3`: 신선한 응답은 그대로 유지 - `usage_count < 10`: 적당한 사용 빈도는 간단 변형 - `0.3 (30%)`: 변형 확률 - 너무 자주 변형하면 부자연스러움 --- ## 📊 모니터링 지표 ```python # 각 단계에서 로깅 logger.info(f"[CONV_CACHE] action=search user={user_id} query_len={len(query)} hits={len(results)} cache_used={cache_hit}") logger.info(f"[CONV_CACHE] action=store user={user_id} msg_len={len(message)} cache_size={self._get_user_cache_count(user_id)}") logger.info(f"[GEMINI_API] model={self.gemini_model.model_name} response_time={elapsed} fallback={is_fallback}") ``` --- ## 📈 예상 효과 | 단계 | 기대 효과 | 구현 난이도 | 소요 시간 | API 비용 변화 | |------|-----------|------------|-----------|---------------| | 1단계 | 응답 다양성 100% 개선 | 매우 쉬움 | 5분 | +100% | | 2단계 | 캐시 구조 준비 | 쉬움 | 30분 | 변화 없음 | | 3단계 | 캐시 저장 시작 | 보통 | 1시간 | 변화 없음 | | 4단계 | API 비용 30% 절감 | 보통 | 2시간 | -30% | | 5단계 | API 비용 70% 절감 | 어려움 | 3시간 | -70% | --- ## 🔧 운영 고려사항 ### 저장 한도 - 사용자/팀 단위 max_items 지정으로 무제한 증가 방지 ### TTL 관리 - 30일 이상 미사용 캐시 자동 삭제 - 중요 대화는 요약 후 semantic 메모리로 이전 ### 동시성 - asyncio.Lock()으로 중복 저장 방지 ### 배포 전략 - 기능 토글로 단계별 롤아웃 - 문제 발생 시 즉시 롤백 가능 ### 성능 모니터링 - 캐시 히트율 목표: 30% → 70% (3개월) - API 호출 감소율 추적 - 응답 시간 개선 측정 --- ## 🚀 구현 로드맵 1. **Week 1**: 1단계 배포 및 모니터링 2. **Week 2**: 2-3단계 구현 및 테스트 3. **Week 3**: 4단계 배포 및 캐시 히트율 분석 4. **Week 4**: 5단계 구현 및 전체 최적화 --- 작성일: 2025-08-05 작성자: Claude & happybell80 프로젝트: rb10508_micro 응답 품질 개선