# AI API 통합 패턴 (AI API Integration) 이 프로젝트의 AI 모델 API 통합 패턴입니다. ## Claude API 통합 ### 클라이언트 초기화 ```python from anthropic import AsyncAnthropic class AIArticleGeneratorWorker: def __init__(self): self.claude_api_key = os.getenv("CLAUDE_API_KEY") self.claude_client = None async def start(self): if self.claude_api_key: self.claude_client = AsyncAnthropic(api_key=self.claude_api_key) else: logger.error("Claude API key not configured") return ``` ### API 호출 패턴 ```python async def _call_claude_api(self, prompt: str) -> str: """Claude API 호출""" try: response = await self.claude_client.messages.create( model="claude-sonnet-4-20250514", # 또는 claude-3-5-sonnet-latest max_tokens=8192, messages=[ { "role": "user", "content": prompt } ] ) return response.content[0].text except Exception as e: logger.error(f"Claude API error: {e}") raise ``` ### JSON 응답 파싱 ```python async def _generate_article(self, prompt: str) -> Dict[str, Any]: """기사 생성 및 JSON 파싱""" response_text = await self._call_claude_api(prompt) # JSON 블록 추출 json_match = re.search(r'```json\s*(.*?)\s*```', response_text, re.DOTALL) if json_match: json_str = json_match.group(1) else: json_str = response_text return json.loads(json_str) ``` ## 프롬프트 관리 ### MongoDB 기반 동적 프롬프트 ```python class AIArticleGeneratorWorker: def __init__(self): self._cached_prompt = None self._prompt_cache_time = None self._prompt_cache_ttl = 300 # 5분 캐시 self._default_prompt = """...""" async def _get_prompt_template(self) -> str: """MongoDB에서 프롬프트 템플릿을 가져옴 (캐시 적용)""" import time current_time = time.time() # 캐시가 유효하면 캐시된 프롬프트 반환 if (self._cached_prompt and self._prompt_cache_time and current_time - self._prompt_cache_time < self._prompt_cache_ttl): return self._cached_prompt try: prompts_collection = self.db.prompts custom_prompt = await prompts_collection.find_one({"service": "article_generator"}) if custom_prompt and custom_prompt.get("content"): self._cached_prompt = custom_prompt["content"] logger.info("Using custom prompt from database") else: self._cached_prompt = self._default_prompt logger.info("Using default prompt") self._prompt_cache_time = current_time return self._cached_prompt except Exception as e: logger.warning(f"Error fetching prompt from database: {e}, using default") return self._default_prompt ``` ### 프롬프트 템플릿 형식 ```python prompt_template = """Write a comprehensive article based on the following news information. Keyword: {keyword} News Information: Title: {title} Summary: {summary} Link: {link} {search_text} Please write in the following JSON format: {{ "title": "Article title", "summary": "One-line summary", "subtopics": [ {{ "title": "Subtopic 1", "content": ["Paragraph 1", "Paragraph 2", ...] }} ], "categories": ["Category1", "Category2"], "entities": {{ "people": [{{"name": "Name", "context": ["role", "company"]}}], "organizations": [{{"name": "Name", "context": ["industry", "type"]}}] }} }} Requirements: - Structure with 2-5 subtopics - Professional and objective tone - Write in English """ ``` ## OpenAI API 통합 (참고) ### 클라이언트 초기화 ```python from openai import AsyncOpenAI class OpenAIService: def __init__(self): self.api_key = os.getenv("OPENAI_API_KEY") self.client = AsyncOpenAI(api_key=self.api_key) async def generate(self, prompt: str) -> str: response = await self.client.chat.completions.create( model="gpt-4o", messages=[{"role": "user", "content": prompt}], max_tokens=4096 ) return response.choices[0].message.content ``` ## 에러 처리 및 재시도 ### 재시도 패턴 ```python import asyncio from typing import Optional async def _call_with_retry( self, func, max_retries: int = 3, initial_delay: float = 1.0 ) -> Optional[Any]: """지수 백오프 재시도""" delay = initial_delay for attempt in range(max_retries): try: return await func() except Exception as e: if attempt == max_retries - 1: logger.error(f"All {max_retries} attempts failed: {e}") raise logger.warning(f"Attempt {attempt + 1} failed: {e}, retrying in {delay}s") await asyncio.sleep(delay) delay *= 2 # 지수 백오프 ``` ## 환경 변수 ```bash # .env 파일 CLAUDE_API_KEY=sk-ant-... OPENAI_API_KEY=sk-... # docker-compose.yml environment: - CLAUDE_API_KEY=${CLAUDE_API_KEY} - OPENAI_API_KEY=${OPENAI_API_KEY} ``` ## 비용 최적화 ### 토큰 제한 ```python response = await self.claude_client.messages.create( model="claude-sonnet-4-20250514", max_tokens=8192, # 출력 토큰 제한 messages=[...] ) ``` ### 캐싱 전략 - MongoDB에 응답 캐시 저장 - TTL 기반 캐시 만료 - 동일 입력에 대한 중복 호출 방지