← 개발 로그 목록

LearningCollector: AI 초안 생성 로직 개선 및 폴백 기능 도입

/ 17분 분량 / 개발 로그

새로운 AI 클라이언트 통합 및 폴백 기능을 통해 안정성을 높이고, 중복 처리를 방지하여 데이터 처리 효율을 개선했습니다.

LearningCollector: AI 초안 생성 로직 개선 및 폴백 기능 도입

새로운 AI 클라이언트 통합 및 폴백 기능을 통해 안정성을 높이고, 중복 처리를 방지하여 데이터 처리 효율을 개선했습니다.

요약

이번 커밋은 LearningCollector 프로젝트의 AI 기반 초안 생성 로직을 대대적으로 개선하는 데 초점을 맞추었습니다. 주요 변경 사항으로는 AIClient라는 통합 AI 클라이언트 도입, Perplexity API를 Gemini의 폴백(fallback)으로 추가, 그리고 초안 생성 실패 시 데이터를 재처리하도록 로직을 수정했습니다. 이를 통해 AI API 호출의 안정성을 높이고, 잠재적인 API 한도 초과로 인한 데이터 누락을 방지하는 것을 목표로 합니다.

배경 및 목적

기존에는 Gemini API를 직접 호출하여 초안을 생성했습니다. 하지만 Gemini API의 응답 지연이나 일시적인 오류, 또는 API 한도 초과 문제 발생 시, 해당 데이터는 초안 생성에 실패하고 결국 누락될 위험이 있었습니다. 이러한 문제를 해결하기 위해 다음과 같은 목적을 가지고 이번 작업을 진행했습니다.

  • AI API 호출 안정성 강화: 단일 AI 서비스에 의존하는 대신, 여러 AI 서비스를 활용하여 시스템의 견고성을 높입니다.
  • 데이터 누락 방지: API 오류나 한도 초과로 인해 중요한 학습 데이터나 개발 진척 내용이 블로그 초안으로 작성되지 못하는 상황을 최소화합니다.
  • 효율적인 API 사용: 중복되는 API 호출을 방지하고, 실패한 작업은 다음 실행 시 재시도할 수 있도록 하여 API 사용량을 최적화합니다.

구현 내용

이번 작업에서는 총 6개의 파일이 수정되었으며, 약 235줄의 코드가 추가되고 45줄의 코드가 삭제되었습니다. 주요 변경 사항은 다음과 같습니다.

주요 변경 사항 상세 설명

  1. api/ai_client.py 신규 생성:

    • Gemini API를 1차 시도로 사용하고, 실패 시 Perplexity API로 폴백하는 AIClient 클래스를 구현했습니다.
    • Perplexity API를 사용하기 위한 PerplexityClient 클래스도 함께 추가되었습니다. (api/perplexity_client.py)
    • AIClient는 GeminiClient와 PerplexityClient를 내부적으로 관리하며, generate_draft 메서드를 통해 통합된 인터페이스를 제공합니다.
  2. core/gemini_draft_generator.py 수정:

    • 기존 GeminiClient 인스턴스 대신 새로 도입된 AIClient 인스턴스를 사용하도록 변경되었습니다.
    • generate_drafts 메서드의 반환 타입이 List[str]에서 Tuple[List[str], List[str]]로 변경되었습니다. 이는 생성된 초안 파일 경로 리스트와 함께, 초안 생성에 성공한 JSON 파일명 리스트를 반환하도록 하여 상태 업데이트 로직을 개선하기 위함입니다.
    • 백준, 개발 진척, AI 대화 공부 각각의 초안 생성 메서드(_generate_baekjoon_drafts, _generate_dev_drafts, _generate_study_drafts)에서도 동일하게 성공한 JSON 파일명 리스트를 반환하도록 수정되었습니다.
  3. core/orchestrator.py 수정:

    • _collect_all 메서드에서 수집된 JSON 파일 목록을 반환하기 전에, _merge_pending이라는 새로운 메서드를 호출하여 이전 실행에서 실패한 pending 항목들을 현재 처리 목록에 병합하도록 로직이 추가되었습니다. 이는 실패한 데이터의 재시도를 보장합니다.
    • _auto_process 및 _interactive_process 메서드에서 generate_drafts의 반환 값을 drafts, succeeded_jsons로 받도록 변경되었습니다.
    • 초안 생성 후, 성공적으로 생성된 JSON 파일만 update_status를 통해 draft_created 및 posted 상태를 True로 업데이트하도록 로직이 강화되었습니다. 실패한 항목은 pending 상태로 남아 다음 실행 시 재시도됩니다.
    • 실패한 항목 수를 명시적으로 출력하여 사용자에게 알립니다.
  4. api/perplexity_client.py 신규 생성:

    • Perplexity API를 호출하기 위한 클라이언트 클래스입니다.
    • OpenAI SDK를 재사용하며 base_url만 Perplexity API 엔드포인트로 설정합니다.
    • API 키 유무 확인, 재시도 로직 (rate limit 오류 처리 포함) 등을 구현했습니다.
  5. requirements.txt 수정:

    • Perplexity API 사용을 위해 openai 패키지가 추가되었습니다.

변경된 파일 목록

  • .env.example (변경 내용 없음, 포함된 파일 목록에 있었으나 실제 수정 없음)
  • api/ai_client.py
  • api/perplexity_client.py
  • core/gemini_draft_generator.py
  • core/orchestrator.py
  • requirements.txt

핵심 코드 설명

core/gemini_draft_generator.py 파일의 generate_drafts 메서드 수정 부분이 핵심입니다.

# ... (이전 import 문)
from api.ai_client import AIClient # GeminiClient 대신 AIClient 사용
# ... (이전 코드)

# ... (기존 반환 타입 List[str]에서 Tuple[List[str], List[str]]로 변경)
def generate_drafts(
    ai_chat_jsons: List[str],
    baekjoon_jsons: List[str],
    commit_jsons: List[str]
) -> Tuple[List[str], List[str]]:
    # ... (docstring 변경)
    all_drafts = []
    all_succeeded_jsons = [] # 성공한 JSON 파일명들을 저장할 리스트

    # ... (각 draft 생성 부분에서 반환 값을 Unpack)
    if baekjoon_jsons:
        print(f"  백준 풀이 초안 생성 중... ({len(baekjoon_jsons)}개)")
        baekjoon_drafts, succeeded = self._generate_baekjoon_drafts(baekjoon_jsons) # 이제 succeeded 리스트도 반환
        all_drafts.extend(baekjoon_drafts)
        all_succeeded_jsons.extend(succeeded) # 성공 목록에 추가
        print(f"    → {len(baekjoon_drafts)}개 생성 완료")

    # ... (다른 draft 생성 부분도 동일하게 수정)

    return all_drafts, all_succeeded_jsons # 두 개의 리스트 반환

또한, orchestrator.py의 _auto_process 메서드에서는 다음과 같이 성공한 JSON 파일만 상태를 업데이트합니다.

# ... (이전 코드)
        drafts, succeeded_jsons = self.draft_generator.generate_drafts(
            ai_chat_jsons,
            baekjoon_jsons,
            commit_jsons
        )

        # 상태 업데이트 (성공한 JSON만)
        succeeded_set = set(succeeded_jsons)
        for json_file in all_new:
            if json_file in succeeded_set: # 성공한 파일만 상태 업데이트
                self.json_saver.update_status(json_file, "draft_created", True)
                self.json_saver.update_status(json_file, "posted", True)

        failed_count = len(all_new) - len(succeeded_set)
        print(f"  → {len(drafts)}개의 초안 생성 완료")
        if failed_count > 0:
            print(f"  → {failed_count}개 항목 초안 생성 실패 (다음 실행에서 재시도)")
# ... (이후 코드)

기술적 의사결정

AI 서비스 선택: Gemini + Perplexity 폴백

  • 선택: 1차 AI 모델로 Gemini를 사용하고, Gemini 호출 실패 시 Perplexity API로 폴백하는 AIClient를 구현했습니다.
  • 이유:
    • Gemini의 성능: Gemini는 강력한 언어 모델로서, 다양한 텍스트 생성 작업에 대해 우수한 성능을 보여줍니다.
    • 폴백의 필요성: 단일 AI 서비스에만 의존하면 API의 일시적인 장애, 응답 지연, 혹은 할당량 초과로 인해 서비스가 중단될 수 있습니다. Perplexity는 Gemini와는 다른 인프라를 사용하므로, Gemini가 실패하더라도 Perplexity는 정상적으로 작동할 가능성이 높습니다. 이를 통해 서비스의 가용성과 안정성을 크게 향상시킬 수 있습니다.
    • OpenAI SDK 호환성: Perplexity API가 OpenAI SDK와 호환되는 방식으로 제공된다는 점을 활용하여, 기존 OpenAI SDK를 사용하는 코드를 최소한으로 수정하면서 Perplexity를 쉽게 통합할 수 있었습니다. 이는 개발 시간을 단축하고 유지보수성을 높이는 데 기여했습니다.
  • 다른 대안:
    • GPT 시리즈 (OpenAI): GPT-3.5, GPT-4 등도 좋은 대안이지만, Gemini와 유사하게 API 한도나 비용 측면에서 고려해야 할 사항이 많습니다. Perplexity와 Gemini를 조합하는 것과 동일한 수준의 폴백 전략을 구축하려면 추가적인 설정이 필요할 수 있습니다.
    • 단일 서비스 강화: Gemini API의 재시도 로직을 더 강화하거나, 에러 처리 메커니즘을 고도화하는 방법도 고려할 수 있었습니다. 하지만 이는 근본적으로 단일 서비스의 제약을 완전히 극복하기 어렵다는 판단하에, 다중 서비스 도입을 결정했습니다.
  • 장단점 분석:
    • 장점:
      • 높은 안정성 및 가용성: 단일 장애점(SPOF)을 제거하여 서비스 중단 위험을 크게 줄였습니다.
      • 데이터 누락 최소화: API 호출 실패 시에도 다른 서비스로 대체하여 초안 생성을 시도하므로, 데이터가 누락될 가능성이 현저히 낮아집니다.
      • 유지보수 용이성: AIClient라는 추상화 계층을 통해 각 AI 클라이언트를 독립적으로 관리할 수 있습니다.
    • 단점:
      • 복잡성 증가: 두 개 이상의 API 서비스를 관리해야 하므로, 초기 설정 및 디버깅 과정이 다소 복잡해질 수 있습니다.
      • API 키 관리: Gemini와 Perplexity 두 서비스의 API 키를 모두 안전하게 관리해야 합니다.
      • 비용 증가 가능성: 두 API를 모두 사용하게 되므로, 호출 빈도에 따라 전체적인 API 사용 비용이 증가할 수 있습니다.

중복 처리 및 실패 데이터 재시도 로직

  • 선택: 초안 생성에 실패한 JSON 파일들을 pending 목록으로 관리하고, 다음 실행 시 이를 병합하여 재시도하도록 로직을 수정했습니다. 또한, 초안 생성 후 성공한 파일만 상태를 업데이트하도록 변경했습니다.
  • 이유:
    • 데이터 무결성: API 호출 오류 등으로 인해 초안 생성이 실패했을 때, 해당 데이터를 누락시키지 않고 다음 기회에 다시 처리할 수 있도록 보장합니다.
    • 자동화 강화: 수동 개입 없이도 실패한 작업이 자동으로 재처리되므로, 전체적인 워크플로우의 자동화를 유지하고 효율성을 높입니다.
    • 명확한 상태 관리: 성공한 작업과 실패한 작업(재시도 대상)을 명확히 구분하여 관리함으로써, 시스템의 상태를 투명하게 파악하고 디버깅을 용이하게 합니다.
  • 다른 대안:
    • 실패 시 바로 제거: 실패한 데이터는 즉시 무시하고 다음 데이터로 넘어가는 방식입니다. 이는 가장 간단하지만, 중요한 데이터를 영구적으로 잃을 위험이 있습니다.
    • 실패 시 즉시 재시도: 한 번 실패했을 때, 몇 번 더 즉시 재시도하는 로직을 추가하는 방식입니다. 일시적인 네트워크 문제 등에는 효과적일 수 있으나, API 자체의 문제나 할당량 초과 등에는 근본적인 해결책이 되지 못합니다.
  • 장단점 분석:
    • 장점:
      • 데이터 누락 방지 및 복구 가능성: 실패한 데이터의 재처리 기회를 제공합니다.
      • 안정적인 처리 흐름: 일시적인 문제에도 워크플로우가 중단되지 않고 지속될 수 있습니다.
      • 투명한 상태 추적: 성공/실패/재시도 상태가 명확하게 관리됩니다.
    • 단점:
      • 로직 복잡성 증가: pending 목록 관리 및 병합 로직이 추가되어 코드의 복잡성이 약간 증가합니다.
      • 실행 시간 증가 가능성: 재시도 대상이 많을 경우, 다음 실행 시 처리 시간이 길어질 수 있습니다.

배운 점 및 개선점

배운 점

  • 폴백(Fallback) 전략의 중요성: 서비스의 안정성과 가용성을 높이기 위해 단일 의존성보다는 여러 대안을 준비하는 폴백 전략이 얼마나 중요한지 다시 한번 깨달았습니다. 특히 API 기반 서비스에서는 예상치 못한 오류나 제약사항이 발생할 수 있으므로, 이러한 대비책이 필수적입니다.
  • OpenAI SDK의 유연성: OpenAI SDK가 base_url 변경을 통해 다양한 API 엔드포인트에 쉽게 연결될 수 있다는 점은 매우 유용한 팁이었습니다. 이를 통해 별도의 클라이언트 라이브러리 없이도 새로운 API를 쉽게 통합할 수 있었습니다.
  • 상태 관리의 명확성: 작업의 성공/실패 여부를 명확하게 기록하고 관리하는 것이 시스템의 안정성과 디버깅 효율성에 얼마나 큰 영향을 미치는지 체감했습니다. 특히 succeeded_jsons 리스트를 통해 성공한 항목만 명확히 구분하여 상태를 업데이트하는 방식은 매우 효과적이었습니다.

개선점 및 다음 단계 계획

  • Perplexity API 호출 시 모델 선택: 현재는 sonar 모델을 고정하여 사용하고 있습니다. 하지만 Perplexity는 다른 모델들도 제공하므로, 특정 목적에 더 적합한 모델을 선택하거나 동적으로 변경하는 기능을 추가할 수 있습니다.
  • AI 서비스별 응답 시간 및 비용 분석: Gemini와 Perplexity의 응답 시간, 생성 품질, 비용 등을 비교 분석하여, 어떤 상황에서 어떤 모델을 우선적으로 사용할지 결정하는 더욱 정교한 로직을 구현할 수 있습니다.
  • 실패 사유 로깅 강화: orchestrator.py에서 실패한 항목을 처리할 때, 실패 원인(예: API 에러 메시지, 타임아웃 등)을 더 자세히 로깅하여 추후 디버깅에 활용할 수 있도록 개선해야 합니다.
  • pending 데이터 처리 로직 고도화: _merge_pending에서 단순히 no_draft 목록을 병합하는 것 외에, 재시도 횟수 제한이나 특정 에러 유형에 대한 처리 방식 등을 추가하여 더욱 견고하게 만들 수 있습니다.

참고 자료