""" HealthSync AI Claude API 클라이언트 (공식 라이브러리 사용) """ import json import logging import asyncio from typing import Dict, Any import anthropic from app.config.settings import settings logger = logging.getLogger(__name__) class ClaudeClient: """Claude API 호출 클라이언트 (공식 라이브러리 사용)""" def __init__(self): self.api_key = settings.claude_api_key # API 키 검증 if not self.api_key or self.api_key == "" or self.api_key == "your_claude_api_key_here": raise ValueError("Claude API 키가 설정되지 않았습니다. .env 파일에 CLAUDE_API_KEY를 설정해주세요.") # 동기 클라이언트 초기화 self.client = anthropic.Anthropic(api_key=self.api_key) logger.info(f"✅ Claude API 클라이언트 초기화 완료") async def call_claude_api(self, prompt: str) -> str: """Claude API 호출 (비동기 래퍼)""" try: logger.info(f"🚀 Claude API 호출 시작 (모델: {settings.claude_model})") # 동기 함수를 비동기로 실행 def sync_call(): return self.client.messages.create( model=settings.claude_model, max_tokens=settings.claude_max_tokens, temperature=settings.claude_temperature, messages=[ { "role": "user", "content": prompt } ] ) # 스레드 풀에서 동기 함수 실행 message = await asyncio.get_event_loop().run_in_executor(None, sync_call) logger.info("✅ Claude API 호출 성공") return message.content[0].text except anthropic.AuthenticationError as e: logger.error("❌ Claude API 인증 실패 - API 키 확인 필요") raise Exception(f"Claude API 인증 실패: {str(e)}") except anthropic.NotFoundError as e: logger.error("❌ Claude API 엔드포인트 또는 모델을 찾을 수 없음") raise Exception(f"Claude API 모델 또는 엔드포인트 오류: {str(e)}") except anthropic.RateLimitError as e: logger.error("❌ Claude API 요청 한도 초과") raise Exception(f"Claude API 요청 한도 초과: {str(e)}") except anthropic.APITimeoutError as e: logger.error("⏰ Claude API 타임아웃") raise Exception(f"Claude API 타임아웃: {str(e)}") except Exception as e: logger.error(f"❌ Claude API 호출 중 예상치 못한 오류: {str(e)}") raise Exception(f"Claude API 호출 실패: {str(e)}") def parse_json_response(self, response: str) -> Dict[str, Any]: """Claude 응답을 JSON으로 파싱""" try: # JSON 부분만 추출 (```json ... ``` 형태로 올 수 있음) if "```json" in response: start = response.find("```json") + 7 end = response.find("```", start) json_str = response[start:end].strip() elif "{" in response and "}" in response: start = response.find("{") end = response.rfind("}") + 1 json_str = response[start:end] else: json_str = response parsed_json = json.loads(json_str) logger.info(f"✅ Claude 응답 파싱 성공: {len(parsed_json.get('missions', []))}개 미션") return parsed_json except json.JSONDecodeError as e: logger.error(f"❌ Claude 응답 JSON 파싱 실패: {str(e)}") logger.error(f"파싱 대상 응답: {response}") raise Exception(f"Claude 응답 파싱 실패: {str(e)}") except Exception as e: logger.error(f"❌ Claude 응답 처리 중 오류: {str(e)}") raise Exception(f"Claude 응답 처리 실패: {str(e)}")