← 개발 로그 목록

LearningCollector: Perplexity API 폴백 도입으로 초안 생성 안정성 향상

/ 18분 분량 / 개발 로그

GitHub API 호출을 4번에서 2번으로 줄여 성능을 개선했습니다. 백준 커밋 처리 시 중복 호출을 제거하여 API 한도를 효율적으로 사용할 수 있게 되었습니다.

LearningCollector: Perplexity API 폴백 도입으로 초안 생성 안정성 향상

GitHub API 호출을 4번에서 2번으로 줄여 성능을 개선했습니다. 백준 커밋 처리 시 중복 호출을 제거하여 API 한도를 효율적으로 사용할 수 있게 되었습니다.

요약

이번 커밋에서는 Gemini API의 불안정성 문제를 해결하기 위해 Perplexity API를 폴백(fallback)으로 도입했습니다. feat: Perplexity API 폴백 도입으로 초안 생성 안정성 향상이라는 커밋 메시지에 걸맞게, api/ai_client.py에서 Gemini API 호출에 실패할 경우 Perplexity API를 대신 사용하도록 로직을 수정했습니다. 또한, Perplexity API 클라이언트(api/perplexity_client.py)를 새로 구현하고, 기존 gemini_draft_generator.py에서는 GeminiClient 대신 새로 도입된 AIClient를 사용하도록 변경했습니다. requirements.txt에는 openai 라이브러리가 추가되었으며, .env.example 파일은 변경되지 않았습니다.

작업은 2026년 2월 6일에 이루어졌으며, 총 137 라인이 추가되고 11 라인이 삭제되었습니다.

배경 및 목적

기존 초안 생성 과정에서 Gemini API의 응답 지연 또는 오류로 인해 작업이 중단되는 경우가 발생했습니다. 특히 PERPLEXITY_API_KEY가 설정되지 않은 경우 Gemini API 단독으로 운영되었는데, 이 API의 불안정성은 사용자 경험 저하와 작업 효율성 감소로 직결되었습니다.

이번 작업의 목적은 다음과 같습니다.

  • 초안 생성 안정성 향상: Gemini API 호출이 실패하더라도 Perplexity API를 통해 초안 생성을 완료할 수 있도록 하여 안정성을 높입니다.
  • API 한도 효율적 사용: 두 가지 AI API를 활용하여 특정 API의 한도 초과 문제 발생 시에도 전체 서비스 중단을 방지합니다.
  • 유연한 AI 모델 운용: 향후 다른 AI 모델로의 전환 또는 확장을 용이하게 합니다.

구현 내용

이번 커밋에서는 크게 세 가지 파일에서 주요 변경이 있었습니다.

api/perplexity_client.py (신규 구현)

Perplexity API를 호출하기 위한 새로운 클라이언트 클래스가 구현되었습니다. OpenAI SDK와 호환되도록 설계되어 base_url만 변경하여 Perplexity API를 사용할 수 있습니다.

  • __init__: PERPLEXITY_API_KEY 환경 변수를 통해 API 키를 설정하고, OpenAI 클라이언트 인스턴스를 생성합니다. API 키가 없을 경우 None으로 처리됩니다.
  • is_available(): API 키가 설정되어 Perplexity API 사용이 가능한지 여부를 반환합니다.
  • generate_draft(prompt: str, json_content: str): Gemini API와 유사하게 프롬프트와 JSON 내용을 받아 블로그 초안을 생성합니다. 재시도 로직이 포함되어 있어 API 호출 실패 시 일정 횟수까지 재시도합니다.
# api/perplexity_client.py (일부 발췌)
import os
import time
from openai import OpenAI

class PerplexityClient:
    """Perplexity Sonar API 클라이언트"""

    def __init__(self):
        self.api_key = os.getenv("PERPLEXITY_API_KEY")
        self.client = None
        if self.api_key:
            self.client = OpenAI(
                api_key=self.api_key,
                base_url="https://api.perplexity.ai"
            )
        self.max_retries = 3
        self.retry_delay = 2  # 초

    def is_available(self) -> bool:
        """API 키가 설정되어 사용 가능한지 확인"""
        return self.client is not None

    def generate_draft(self, prompt: str, json_content: str) -> str:
        """
        Perplexity를 사용하여 블로그 초안 생성 (재시도 로직 포함)
        ...
        """
        if not self.is_available():
            return None

        full_prompt = f"""{prompt}

다음은 참조할 데이터입니다:

```json
{json_content}

위 데이터를 바탕으로 블로그 초안을 작성해주세요.
"""

    for attempt in range(self.max_retries):
        try:
            response = self.client.chat.completions.create(
                model="sonar",
                messages=[
                    {"role": "user", "content": full_prompt}
                ]
            )
            return response.choices[0].message.content
        except Exception as e:
            # ... 재시도 로직 ...
            print(f"      ❌ Perplexity API 호출 실패: {str(e)[:200]}")
            return None
    return None

### `api/ai_client.py` (신규 구현)
AI 모델 호출을 통합 관리하는 `AIClient`가 새로 구현되었습니다. 이 클래스는 Gemini API를 먼저 시도하고, 실패 시 Perplexity API로 폴백하는 로직을 담당합니다.
*   `__init__`: `GeminiClient`와 `PerplexityClient` 인스턴스를 생성하고, Perplexity API 사용 가능 여부에 따라 초기 메시지를 출력합니다.
*   `generate_draft(prompt: str, json_content: str)`: 먼저 `self.gemini.generate_draft`를 호출하고, 결과가 `None`이 아니면 해당 결과를 반환합니다. 만약 Gemini 호출이 실패하면, `self.perplexity.is_available()`을 확인하여 Perplexity API가 사용 가능하다면 이를 호출하여 결과를 반환합니다.

```python
# api/ai_client.py (일부 발췌)
from api.gemini_client import GeminiClient
from api.perplexity_client import PerplexityClient

class AIClient:
    """Gemini → Perplexity 폴백을 가진 통합 AI 클라이언트"""

    def __init__(self):
        self.gemini = GeminiClient()
        self.perplexity = PerplexityClient()

        if self.perplexity.is_available():
            print("  [AI] Gemini + Perplexity 폴백 활성화")
        else:
            print("  [AI] Gemini 단독 모드 (PERPLEXITY_API_KEY 미설정)")

    def generate_draft(self, prompt: str, json_content: str) -> str:
        """
        블로그 초안 생성 (Gemini 우선, 실패 시 Perplexity 폴백)
        ...
        """
        # 1차: Gemini 시도
        result = self.gemini.generate_draft(prompt, json_content)
        if result is not None:
            return result

        # 2차: Perplexity 폴백
        if not self.perplexity.is_available():
            return None

        print("      🔄 Gemini 실패 → Perplexity로 전환")
        return self.perplexity.generate_draft(prompt, json_content)

core/gemini_draft_generator.py (수정)

기존 GeminiDraftGenerator 클래스에서 GeminiClient 대신 새로 구현된 AIClient를 사용하도록 변경했습니다.

  • GeminiClient 임포트 문이 AIClient로 변경되었습니다.
  • __init__ 메서드에서 self.gemini_client 대신 self.ai_client로 인스턴스를 생성합니다.
  • _generate_baekjoon_drafts, _generate_dev_drafts, _generate_study_drafts 메서드 내에서 self.gemini_client.generate_draft 호출 부분이 self.ai_client.generate_draft로 변경되었습니다.
  • AI API 호출 실패 시 self.quota_exhausted를 True로 설정하고 "API 한도 초과" 대신 "AI API 모두 실패"라는 메시지를 출력하도록 수정했습니다.
# core/gemini_draft_generator.py (일부 발췌)
# ...
# from api.gemini_client import GeminiClient # 이전
from api.ai_client import AIClient # 변경
# ...
class GeminiDraftGenerator:
    # ...
    def __init__(self):
        # self.gemini_client = GeminiClient() # 이전
        self.ai_client = AIClient() # 변경
        # ...

    # ...
    def _generate_baekjoon_drafts(self, json_files: List[str]) -> Tuple[List[str], List[str]]:
        # ...
        # draft_content = self.gemini_client.generate_draft(prompt, json_content) # 이전
        draft_content = self.ai_client.generate_draft(prompt, json_content) # 변경

        if draft_content is None:
            self.quota_exhausted = True
            # print(f"    ⚠️  API 한도 초과. 나머지 백준 draft 생성 중단") # 이전
            print(f"    ⚠️  AI API 모두 실패. 나머지 백준 draft 생성 중단") # 변경
            break
        # ...

requirements.txt (수정)

Perplexity API 사용을 위해 openai 라이브러리가 추가되었습니다.

--- a/requirements.txt
+++ b/requirements.txt
@@ -11,6 +11,9 @@
 # Gemini AI (최신 패키지)
 google-genai>=0.2.0

+# Perplexity AI (OpenAI SDK 호환, 폴백용)
+openai>=1.0.0
+
 # Scheduling (Linux)
 python-crontab>=3.0.0; platform_system == "Linux"

기술적 의사결정

AI 모델 선택: Gemini + Perplexity 폴백

  • 선택: Gemini API를 1차 시도로 사용하고, 실패 시 Perplexity API를 폴백으로 사용하는 방식을 선택했습니다.
  • 이유:
    • Gemini: 초기에는 Gemini API를 중심으로 개발되었으며, 충분한 성능을 보여주었습니다.
    • Perplexity: Perplexity API는 OpenAI SDK와 호환되는 인터페이스를 제공하여 기존 코드베이스에 비교적 쉽게 통합할 수 있었습니다. 또한, 다양한 모델을 지원하며, 특히 sonar 모델은 코드 생성 및 정보 요약에 강점을 보여 초안 생성에 적합하다고 판단했습니다.
    • 폴백 전략: 단일 AI API의 불안정성이나 한도 초과 문제를 완화하고, 서비스 연속성을 보장하기 위한 필수적인 전략입니다.
  • 다른 대안:
    • 다른 GPT 모델 (e.g., GPT-4): 비용 및 API 사용 편의성을 고려했을 때, Perplexity가 더 나은 대안이라고 판단했습니다.
    • 자체 호스팅 LLM: 모델 관리 및 운영 부담이 커서 현재 단계에서는 적합하지 않다고 판단했습니다.
  • 장단점 분석:
    • Gemini + Perplexity 폴백:
      • 장점: 초안 생성 안정성 향상, 단일 API 의존성 감소, 유연한 모델 운용 가능.
      • 단점: API 호출 로직 복잡성 증가, 두 API 모두 비용 발생, Perplexity API 키 설정 필요.

OpenAI SDK 호환성 활용

  • 선택: Perplexity API 클라이언트를 구현할 때, OpenAI SDK와 호환되는 base_url 설정 방식을 채택했습니다.
  • 이유:
    • 개발 편의성: 이미 openai 라이브러리가 설치되어 있거나 익숙한 개발자에게 익숙한 방식으로 Perplexity API를 사용할 수 있습니다.
    • 코드 재사용: openai 라이브러리의 기능(예: 재시도 로직, 스트리밍 등)을 활용하거나, 향후 다른 OpenAI 호환 API로 전환할 때 유리합니다.
    • 간결한 구현: 복잡한 HTTP 요청 로직을 직접 구현할 필요 없이, OpenAI 클라이언트 객체에 base_url만 변경하여 Perplexity API 엔드포인트로 요청을 보낼 수 있습니다.

배운 점 및 개선점

배운 점

  • AI API의 내재적 불안정성: 클라우드 기반 AI 서비스는 네트워크 문제, API 자체의 오류, 또는 사용량 제한 등으로 인해 불안정할 수 있음을 다시 한번 확인했습니다. 이러한 불안정성에 대비한 폴백 전략의 중요성을 실감했습니다.
  • OpenAI SDK 호환성의 유용성: Perplexity API가 OpenAI SDK와 호환되도록 설계된 덕분에, 복잡한 API 연동 작업을 훨씬 효율적으로 수행할 수 있었습니다. 이는 API 설계 시 호환성을 고려하는 것의 중요성을 보여줍니다.
  • 환경 변수 관리의 중요성: API 키 관리를 위해 PERPLEXITY_API_KEY와 같은 환경 변수를 사용하는 것이 얼마나 중요한지 다시 한번 깨달았습니다.

개선점 및 향후 계획

  • Perplexity API 재시도 로직 강화: 현재는 단순 재시도 로직만 구현되어 있습니다. API 응답 코드별 상세 에러 메시지를 분석하여 더욱 정교한 재시도 전략을 구현하면 안정성을 더욱 높일 수 있을 것입니다.
  • API 사용량 모니터링: Gemini와 Perplexity API의 사용량 및 비용을 지속적으로 모니터링하여 예산을 관리하고, 비효율적인 API 호출을 개선해야 합니다.
  • AI 모델 성능 비교 및 선택 로직 개선: 두 AI 모델의 생성 결과물에 대한 정량적, 정성적 평가를 통해 어떤 상황에서 어떤 모델이 더 적합한지 판단하는 로직을 추가하거나, 사용자에게 선택권을 줄 수 있는 기능을 고려해볼 수 있습니다.
  • PERPLEXITY_API_KEY 미설정 시 처리: 현재는 Perplexity API가 없을 경우 Gemini 단독 모드로 작동하지만, 이 경우 Gemini API의 불안정성으로 작업이 중단될 수 있습니다. 향후에는 Gemini API 실패 시에도 명확한 실패 메시지와 함께 gracefully하게 종료되도록 처리하는 것이 좋습니다.
  • 테스트 커버리지 확대: 새로 구현된 AIClient 및 PerplexityClient에 대한 유닛 테스트 및 통합 테스트를 추가하여 코드의 신뢰성을 높여야 합니다.

참고 자료

  • Perplexity API 문서 (OpenAI SDK 호환 관련 내용) - (직접적인 링크는 제공되지 않았으나, 해당 API를 활용하기 위한 내부 문서 또는 일반적인 Perplexity API 연동 가이드라인을 참고했을 것으로 추정)
  • OpenAI Python SDK 문서 - https://github.com/openai/openai-python