LearningCollector: Perplexity API 폴백 도입으로 초안 생성 안정성 향상
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 키 설정 필요.
- Gemini + Perplexity 폴백:
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