다크 모드
AI 서비스 통합
truloop-ai-server가 연동하는 AI 서비스 프로바이더들을 설명합니다.
프로바이더 개요
프로바이더 상세
OpenRouter (이미지 분석/이미지 선택 LLM Gateway)
| 항목 | 설명 |
|---|---|
| 역할 | 이미지 분석, 이미지 선택, 텍스트 생성 |
| 지원 모델 | Anthropic Claude (주력), OpenAI GPT 등 |
| 인증 | OPENROUTER_API_KEY |
| 용도 | 이미지 설명/태깅, 최적 이미지 선택, post message 생성 |
| 구현 | call_claude_with_retry() in app/services/common/utils.py |
OpenRouter는 다양한 LLM 프로바이더의 통합 게이트웨이 역할을 합니다. 단일 API Key로 여러 모델에 접근할 수 있어 프로바이더 전환이 용이합니다.
정보
OpenRouter 호출은 OpenAI Python SDK (AsyncOpenAI)를 사용하여 OpenRouter 엔드포인트(https://openrouter.ai/api/v1)로 요청합니다. 응답은 내부적으로 Anthropic 응답 형식(MockAnthropicResponse)으로 변환하여 기존 코드와 호환됩니다.
Junis AI (주력 콘텐츠 생성)
| 항목 | 설명 |
|---|---|
| 역할 | 하이라이트/포스터/스토리 콘텐츠 생성 |
| 인증 | JUNIS_API_KEY |
| 도메인 | JUNIS_DOMAIN (기본: https://content.junis.ai) |
| 통신 방식 | 비동기 웹훅 기반 — 요청 후 결과는 webhook으로 전달 |
| 구현 | app/services/content_template/junis_content_generator.py |
| 인터페이스 | ContentGeneratorInterface (ABC) |
Junis AI는 실제 콘텐츠 생성(하이라이트 비디오, 포스터 이미지, 스토리 텍스트)을 수행하는 핵심 프로바이더입니다. 요청 시 webhook_url을 포함하여 비동기로 결과를 받습니다.
Replicate / FAL
| 항목 | 설명 |
|---|---|
| 역할 | 특수 이미지/비디오 모델 실행 |
| 상태 | 의존성에 포함되어 있으나 현재 주요 콘텐츠 생성 흐름에서 직접 사용되지 않음 |
| 인증 | REPLICATE_API_TOKEN / FAL SDK |
| 특징 | GPU 기반 추론, 비동기 처리 |
콘텐츠 생성 흐름
이미지 분석 흐름
콘텐츠 생성 흐름
프롬프트 관리
버전 관리 시스템
AI 프롬프트는 DB에서 버전 관리됩니다:
| 모델 | 테이블 | 역할 |
|---|---|---|
AIPromptTemplate | ai_prompt_templates | 프롬프트 템플릿 정의 (카테고리별) |
AIPromptVersion | ai_prompt_versions | 프롬프트 버전 (모델명, 온도, 최대 토큰, 프롬프트 텍스트) |
AIPromptTemplate (1) ──→ (N) AIPromptVersion- 각
AIPromptTemplate은PromptCategory기반으로 분류 (image_description,image_tag,thumbnail_title,image_selection등) AIPromptVersion은model_name,temperature,max_tokens,prompt_text등을 포함- 활성 버전(
is_active=True)의 최신 버전을 사용 - 프롬프트 변경 시 새 버전을 생성하여 이력 유지
- 관리자 UI (SQLAdmin)에서 프롬프트 편집 가능
다국어 지원
콘텐츠 생성 시 lang_code 파라미터로 생성 언어를 지정합니다:
| 언어 코드 | 언어 |
|---|---|
ko | 한국어 |
en | 영어 |
ja | 일본어 |
최적 이미지 선택
BestImageSelector 서비스는 재귀적 토너먼트 방식으로 AI를 활용하여 룹의 이미지들 중 콘텐츠에 가장 적합한 이미지를 자동 선택합니다.
선택 방식
| 메서드 | 선택 수 | 설명 |
|---|---|---|
recursive_select() | 1개 | 단일 최적 이미지 선택 |
recursive_select_with_selected_image_count() | N개 (1, 3, 5) | 복수 최적 이미지 선택 |
토너먼트 흐름
| 단계 | 설명 |
|---|---|
| 1. 이미지 로드 | 병렬로 이미지를 base64로 변환 (thumbnail > optimized > original 우선순위) |
| 2. 그룹 분할 | group_size (기본: 10)개씩 그룹으로 분할 |
| 3. 그룹별 AI 선택 | Claude를 통해 각 그룹에서 최적 이미지 선택 |
| 4. 승자 재귀 | 각 그룹의 승자를 모아 다시 토너먼트 진행 |
| 5. 최종 선택 | 최종 승자 반환 |
정보
AI 선택 실패 시 random.sample()로 fallback합니다. 이미지 캐싱(_image_cache)을 지원하여 같은 이미지의 중복 다운로드를 방지합니다.
에러 처리
| 상황 | 처리 |
|---|---|
| OpenRouter API 호출 실패 | call_claude_with_retry()에서 최대 3회 재시도 |
| Junis API 타임아웃 | 120초 전체 타임아웃, 30초 연결 타임아웃 |
| Celery 태스크 실패 | 재시도 없음 (max_retries=0), 에러 로깅 |
| 모델 할당량 초과 | ClaudeRateLimiter로 사전 제어 (동시 3개, 분당 50개) |
| 잘못된 AI 응답 형식 | 파싱 에러 로깅, 태스크 실패 기록 (ContentStatus.FAILED) |
| 웹훅 에러 | Sentry에 상세 컨텍스트와 함께 보고, LoopGeneratedContent 상태를 FAILED로 업데이트 |
| 미디어 없음 | ValueError 발생, 콘텐츠 상태 FAILED로 업데이트 |
| 이미지 선택 실패 | random.sample()로 fallback 선택 |