release
This commit is contained in:
parent
c06d453cf4
commit
d0b59725df
@ -301,6 +301,114 @@ class ClaudeService:
|
||||
"""API 응답용 프롬프트를 생성합니다. (호환성용)"""
|
||||
return self._build_action_prompt(context, additional_context)
|
||||
|
||||
async def generate_action_recommendations_optimized(
|
||||
self,
|
||||
context: str,
|
||||
additional_context: Optional[str] = None
|
||||
) -> Tuple[Optional[str], Optional[Dict[str, Any]]]:
|
||||
"""
|
||||
최적화된 액션 추천 생성
|
||||
- 더 명확한 JSON 지시사항
|
||||
- 토큰 효율성 개선
|
||||
- 파싱 안정성 향상
|
||||
"""
|
||||
if not self.is_ready():
|
||||
return None, None
|
||||
|
||||
try:
|
||||
# 최적화된 프롬프트 구성
|
||||
prompt = self._build_optimized_prompt(context, additional_context)
|
||||
|
||||
response = self.client.messages.create(
|
||||
model=self.model,
|
||||
max_tokens=3000, # 토큰 수 최적화
|
||||
temperature=0.3, # 일관성 향상
|
||||
messages=[{"role": "user", "content": prompt}]
|
||||
)
|
||||
|
||||
if response.content and len(response.content) > 0:
|
||||
raw_response = response.content[0].text
|
||||
|
||||
# 즉시 JSON 파싱 시도
|
||||
parsed_json = self._parse_json_response_enhanced(raw_response)
|
||||
|
||||
return raw_response, parsed_json
|
||||
|
||||
return None, None
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Claude AI 호출 실패: {e}")
|
||||
return None, None
|
||||
|
||||
def _build_optimized_prompt(self, context: str, additional_context: Optional[str] = None) -> str:
|
||||
"""최적화된 프롬프트 구성"""
|
||||
|
||||
prompt_parts = [
|
||||
"당신은 소상공인 경영 컨설턴트입니다.",
|
||||
f"분석 요청: {context}",
|
||||
]
|
||||
|
||||
if additional_context:
|
||||
prompt_parts.extend([
|
||||
"\n=== 참고 데이터 ===",
|
||||
additional_context,
|
||||
"==================\n"
|
||||
])
|
||||
|
||||
prompt_parts.extend([
|
||||
"위 정보를 바탕으로 실행 가능한 액션을 추천해주세요.",
|
||||
"",
|
||||
"⚠️ 응답은 반드시 아래 JSON 형식으로만 작성하세요:",
|
||||
"다른 텍스트는 포함하지 마세요.",
|
||||
"",
|
||||
"```json",
|
||||
"{",
|
||||
' "summary": {',
|
||||
' "current_situation": "현재 상황 요약 (50자 이내)",',
|
||||
' "key_insights": ["핵심 포인트1", "핵심 포인트2"],',
|
||||
' "priority": "high|medium|low"',
|
||||
' },',
|
||||
' "actions": [',
|
||||
' {',
|
||||
' "title": "액션명",',
|
||||
' "description": "구체적 실행방법",',
|
||||
' "timeline": "실행기간",',
|
||||
' "cost": "예상비용",',
|
||||
' "impact": "예상효과"',
|
||||
' }',
|
||||
' ],',
|
||||
' "quick_tips": ["즉시 실행 팁1", "즉시 실행 팁2"]',
|
||||
"}",
|
||||
"```"
|
||||
])
|
||||
|
||||
return "\n".join(prompt_parts)
|
||||
|
||||
def _parse_json_response_enhanced(self, raw_response: str) -> Optional[Dict[str, Any]]:
|
||||
"""향상된 JSON 파싱"""
|
||||
try:
|
||||
# 1. JSON 블록 추출
|
||||
json_match = re.search(r'```json\s*(\{.*?\})\s*```', raw_response, re.DOTALL)
|
||||
if json_match:
|
||||
json_str = json_match.group(1)
|
||||
else:
|
||||
# 2. 직접 JSON 찾기
|
||||
json_match = re.search(r'(\{.*\})', raw_response, re.DOTALL)
|
||||
if json_match:
|
||||
json_str = json_match.group(1)
|
||||
else:
|
||||
return None
|
||||
|
||||
# 3. JSON 파싱
|
||||
return json.loads(json_str)
|
||||
|
||||
except json.JSONDecodeError as e:
|
||||
logger.error(f"JSON 파싱 오류: {e}")
|
||||
return None
|
||||
except Exception as e:
|
||||
logger.error(f"JSON 추출 실패: {e}")
|
||||
return None
|
||||
|
||||
# =============================================================================
|
||||
# 헬스체크 및 상태 확인 메서드들
|
||||
# =============================================================================
|
||||
|
||||
@ -726,3 +726,97 @@ class VectorService:
|
||||
except Exception as e:
|
||||
logger.error(f"유사 케이스 검색 실패: {e}")
|
||||
return None
|
||||
|
||||
def search_similar_cases_improved(self, store_id: str, context: str) -> Optional[str]:
|
||||
"""
|
||||
개선된 유사 케이스 검색
|
||||
1. store_id 기반 필터링 우선 적용
|
||||
2. 동종 업체 우선 검색
|
||||
3. 캐싱 및 성능 최적화
|
||||
"""
|
||||
try:
|
||||
if not self.is_ready():
|
||||
logger.warning("VectorService가 준비되지 않음")
|
||||
return None
|
||||
|
||||
# 1단계: 해당 가게의 정보 먼저 확인
|
||||
store_context = self.get_store_context(store_id)
|
||||
food_category = store_context.get('food_category', '') if store_context else ''
|
||||
|
||||
# 2단계: 검색 쿼리 구성 (가게 정보 + 컨텍스트)
|
||||
enhanced_query = f"{food_category} {context}"
|
||||
query_embedding = self.embedding_model.encode(enhanced_query).tolist()
|
||||
|
||||
# 3단계: 동종 업체 우선 검색 (메타데이터 필터링)
|
||||
results = self.collection.query(
|
||||
query_embeddings=[query_embedding],
|
||||
n_results=10, # 더 많은 결과에서 필터링
|
||||
include=['documents', 'metadatas', 'distances'],
|
||||
where={"food_category": {"$eq": food_category}} if food_category else None
|
||||
)
|
||||
|
||||
if not results or not results.get('documents') or not results['documents'][0]:
|
||||
# 4단계: 동종 업체가 없으면 전체 검색
|
||||
logger.info("동종 업체 없음 - 전체 검색으로 전환")
|
||||
results = self.collection.query(
|
||||
query_embeddings=[query_embedding],
|
||||
n_results=5,
|
||||
include=['documents', 'metadatas', 'distances']
|
||||
)
|
||||
|
||||
if not results or not results.get('documents') or not results['documents'][0]:
|
||||
logger.info("유사 케이스를 찾을 수 없음")
|
||||
return None
|
||||
|
||||
# 5단계: 결과 조합 (관련성 높은 순서로)
|
||||
context_parts = []
|
||||
documents = results['documents'][0]
|
||||
metadatas = results.get('metadatas', [[]])[0]
|
||||
distances = results.get('distances', [[]])[0]
|
||||
|
||||
# 거리(유사도) 기준으로 필터링 (너무 관련성 낮은 것 제외)
|
||||
filtered_results = []
|
||||
for i, (doc, metadata, distance) in enumerate(zip(documents, metadatas, distances)):
|
||||
if distance < 0.8: # 유사도 임계값
|
||||
filtered_results.append((doc, metadata, distance))
|
||||
|
||||
if not filtered_results:
|
||||
return None
|
||||
|
||||
# 최대 3개의 가장 관련성 높은 케이스만 사용
|
||||
for doc, metadata, distance in filtered_results[:3]:
|
||||
store_name = metadata.get('store_name', 'Unknown')
|
||||
food_cat = metadata.get('food_category', 'Unknown')
|
||||
|
||||
context_parts.append(f"[{food_cat} - {store_name}] (유사도: {1-distance:.2f})")
|
||||
# 문서 길이 제한으로 토큰 수 최적화
|
||||
context_parts.append(doc[:300] + "..." if len(doc) > 300 else doc)
|
||||
context_parts.append("---")
|
||||
|
||||
return "\n".join(context_parts)
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"유사 케이스 검색 실패: {e}")
|
||||
return None
|
||||
|
||||
def get_store_context(self, store_id: str) -> Optional[Dict[str, Any]]:
|
||||
"""해당 가게의 컨텍스트 정보 조회 (캐싱 적용)"""
|
||||
try:
|
||||
if not self.is_ready():
|
||||
return None
|
||||
|
||||
# 메타데이터에서 해당 store_id 검색
|
||||
results = self.collection.get(
|
||||
where={"store_id": {"$eq": store_id}},
|
||||
limit=1,
|
||||
include=['metadatas']
|
||||
)
|
||||
|
||||
if results and results.get('metadatas') and len(results['metadatas']) > 0:
|
||||
return results['metadatas'][0]
|
||||
|
||||
return None
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"가게 컨텍스트 조회 실패: {e}")
|
||||
return None
|
||||
Loading…
x
Reference in New Issue
Block a user