98 lines
4.0 KiB
Python
98 lines
4.0 KiB
Python
"""
|
|
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)}") |