Files
drama-studio/.claude/skills/ai-api-integration.md
jungwoo choi cc547372c0 feat: Drama Studio 프로젝트 초기 구조 설정
- FastAPI 백엔드 (audio-studio-api)
- Next.js 프론트엔드 (audio-studio-ui)
- Qwen3-TTS 엔진 (audio-studio-tts)
- MusicGen 서비스 (audio-studio-musicgen)
- Docker Compose 개발/운영 환경

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-26 11:39:38 +09:00

5.6 KiB

AI API 통합 패턴 (AI API Integration)

이 프로젝트의 AI 모델 API 통합 패턴입니다.

Claude API 통합

클라이언트 초기화

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 호출 패턴

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 응답 파싱

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 기반 동적 프롬프트

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

프롬프트 템플릿 형식

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 통합 (참고)

클라이언트 초기화

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

에러 처리 및 재시도

재시도 패턴

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  # 지수 백오프

환경 변수

# .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}

비용 최적화

토큰 제한

response = await self.claude_client.messages.create(
    model="claude-sonnet-4-20250514",
    max_tokens=8192,  # 출력 토큰 제한
    messages=[...]
)

캐싱 전략

  • MongoDB에 응답 캐시 저장
  • TTL 기반 캐시 만료
  • 동일 입력에 대한 중복 호출 방지