"""Claude API Service""" import anthropic import json import logging from typing import Dict, Any from app.config import get_settings logger = logging.getLogger(__name__) settings = get_settings() class ClaudeService: """Claude API 호출 서비스""" def __init__(self): self.client = anthropic.Anthropic(api_key=settings.claude_api_key) self.model = settings.claude_model self.max_tokens = settings.claude_max_tokens self.temperature = settings.claude_temperature async def generate_completion( self, prompt: str, system_prompt: str = None ) -> Dict[str, Any]: """ Claude API 호출하여 응답 생성 Args: prompt: 사용자 프롬프트 system_prompt: 시스템 프롬프트 (선택) Returns: Claude API 응답 (JSON 파싱) """ try: # 메시지 구성 messages = [ { "role": "user", "content": prompt } ] # API 호출 logger.info(f"Claude API 호출 시작 - Model: {self.model}") if system_prompt: response = self.client.messages.create( model=self.model, max_tokens=self.max_tokens, temperature=self.temperature, system=system_prompt, messages=messages ) else: response = self.client.messages.create( model=self.model, max_tokens=self.max_tokens, temperature=self.temperature, messages=messages ) # 응답 텍스트 추출 response_text = response.content[0].text logger.info(f"Claude API 응답 수신 완료 - Tokens: {response.usage.input_tokens + response.usage.output_tokens}") # JSON 파싱 # ```json ... ``` 블록 제거 if "```json" in response_text: response_text = response_text.split("```json")[1].split("```")[0].strip() elif "```" in response_text: response_text = response_text.split("```")[1].split("```")[0].strip() result = json.loads(response_text) return result except json.JSONDecodeError as e: logger.error(f"JSON 파싱 실패: {e}") logger.error(f"응답 텍스트: {response_text[:500]}...") raise ValueError(f"Claude API 응답을 JSON으로 파싱할 수 없습니다: {str(e)}") except Exception as e: logger.error(f"Claude API 호출 실패: {e}") raise async def analyze_suggestions(self, transcript_text: str): """ 회의 텍스트에서 AI 제안사항 추출 Args: transcript_text: 회의 텍스트 Returns: RealtimeSuggestionsResponse 객체 """ from app.models import RealtimeSuggestionsResponse, SimpleSuggestion from app.prompts.suggestions_prompt import get_suggestions_prompt from datetime import datetime import uuid try: # 프롬프트 생성 system_prompt, user_prompt = get_suggestions_prompt(transcript_text) # Claude API 호출 result = await self.generate_completion( prompt=user_prompt, system_prompt=system_prompt ) # 응답 파싱 suggestions_data = result.get("suggestions", []) # SimpleSuggestion 객체로 변환 suggestions = [ SimpleSuggestion( id=str(uuid.uuid4()), content=s["content"], timestamp=datetime.now().strftime("%H:%M:%S"), confidence=s.get("confidence", 0.85) ) for s in suggestions_data if s.get("confidence", 0) >= 0.65 # 신뢰도 0.65 이상 (0.7 → 0.65 낮춤) ] logger.info(f"AI 제안사항 {len(suggestions)}개 추출 완료") return RealtimeSuggestionsResponse(suggestions=suggestions) except Exception as e: logger.error(f"제안사항 분석 실패: {e}", exc_info=True) # 빈 응답 반환 return RealtimeSuggestionsResponse(suggestions=[]) async def generate_summary( self, text: str, language: str = "ko", style: str = "bullet", max_length: int = None ) -> Dict[str, Any]: """ 텍스트 요약 생성 Args: text: 요약할 텍스트 language: 요약 언어 (ko/en) style: 요약 스타일 (bullet/paragraph) max_length: 최대 요약 길이 Returns: 요약 결과 딕셔너리 """ from app.models.summary import SummaryResponse from app.prompts.summary_prompt import get_summary_prompt try: # 프롬프트 생성 system_prompt, user_prompt = get_summary_prompt( text=text, language=language, style=style, max_length=max_length ) # Claude API 호출 result = await self.generate_completion( prompt=user_prompt, system_prompt=system_prompt ) # 단어 수 계산 summary_text = result.get("summary", "") key_points = result.get("key_points", []) # 한국어와 영어의 단어 수 계산 방식 다르게 처리 if language == "ko": # 한국어: 공백으로 구분된 어절 수 original_word_count = len(text.split()) summary_word_count = len(summary_text.split()) else: # 영어: 공백으로 구분된 단어 수 original_word_count = len(text.split()) summary_word_count = len(summary_text.split()) compression_ratio = summary_word_count / original_word_count if original_word_count > 0 else 0 # 응답 생성 response = SummaryResponse( summary=summary_text, key_points=key_points, word_count=summary_word_count, original_word_count=original_word_count, compression_ratio=round(compression_ratio, 2) ) logger.info(f"요약 생성 완료 - 원본: {original_word_count}단어, 요약: {summary_word_count}단어") return response.model_dump() except Exception as e: logger.error(f"요약 생성 실패: {e}", exc_info=True) raise # 싱글톤 인스턴스 claude_service = ClaudeService()