From 4cc59df94241d8435294f87dce6d65c415744fe9 Mon Sep 17 00:00:00 2001 From: thkim7 Date: Wed, 17 Sep 2025 10:52:04 +0900 Subject: [PATCH 1/4] =?UTF-8?q?feat:=20=EB=B8=94=EB=A1=9C=EA=B7=B8=20Rag?= =?UTF-8?q?=EA=B8=B0=EB=B0=98=20=EC=BD=98=ED=85=90=EC=B8=A0=20=EC=83=9D?= =?UTF-8?q?=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../app/service/blog/blog_create_service.py | 298 ++++++++++++++ .../service/product_blog_posting_service.py | 387 ------------------ 2 files changed, 298 insertions(+), 387 deletions(-) create mode 100644 apps/pre-processing-service/app/service/blog/blog_create_service.py delete mode 100644 apps/pre-processing-service/app/service/product_blog_posting_service.py diff --git a/apps/pre-processing-service/app/service/blog/blog_create_service.py b/apps/pre-processing-service/app/service/blog/blog_create_service.py new file mode 100644 index 00000000..b0e06907 --- /dev/null +++ b/apps/pre-processing-service/app/service/blog/blog_create_service.py @@ -0,0 +1,298 @@ +import json +import logging +import os +from datetime import datetime +from typing import Dict, List, Optional, Any + +from openai import OpenAI +from dotenv import load_dotenv + +from app.schemas import RequestBlogCreate +from app.errors.BlogPostingException import * + +# 환경변수 로드 +load_dotenv(".env.dev") + +client = OpenAI() + + +class BlogContentService: + """RAG를 사용한 블로그 콘텐츠 생성 전용 서비스""" + + def __init__(self): + # OpenAI API 키 설정 + self.openai_api_key = os.getenv("OPENAI_API_KEY") + if not self.openai_api_key: + raise ValueError("OPENAI_API_KEY가 .env.dev 파일에 설정되지 않았습니다.") + + client.api_key = self.openai_api_key + logging.basicConfig(level=logging.INFO) + self.logger = logging.getLogger(__name__) + + def generate_blog_content(self, request: RequestBlogCreate) -> Dict[str, Any]: + """ + 요청 데이터를 기반으로 블로그 콘텐츠 생성 + + Args: + request: RequestBlogCreate 객체 + + Returns: + Dict: {"title": str, "content": str, "tags": List[str]} 형태의 결과 + """ + try: + # 1. 콘텐츠 정보 정리 + content_context = self._prepare_content_context(request) + + # 2. 프롬프트 생성 + prompt = self._create_content_prompt(content_context, request) + + # 3. GPT를 통한 콘텐츠 생성 + generated_content = self._generate_with_openai(prompt) + + # 4. 콘텐츠 파싱 및 구조화 + return self._parse_generated_content(generated_content, request) + + except Exception as e: + self.logger.error(f"콘텐츠 생성 실패: {e}") + return self._create_fallback_content(request) + + def _prepare_content_context(self, request: RequestBlogCreate) -> str: + """요청 데이터를 콘텐츠 생성용 컨텍스트로 변환""" + context_parts = [] + + # 키워드 정보 추가 + if request.keyword: + context_parts.append(f"주요 키워드: {request.keyword}") + + # 상품 정보 추가 + if request.product_info: + context_parts.append("\n상품 정보:") + + # 상품 기본 정보 + if request.product_info.get("title"): + context_parts.append(f"- 상품명: {request.product_info['title']}") + + if request.product_info.get("price"): + context_parts.append(f"- 가격: {request.product_info['price']:,}원") + + if request.product_info.get("rating"): + context_parts.append(f"- 평점: {request.product_info['rating']}/5.0") + + # 상품 상세 정보 + if request.product_info.get("description"): + context_parts.append(f"- 설명: {request.product_info['description']}") + + # 상품 사양 (material_info 등) + if request.product_info.get("material_info"): + context_parts.append("- 주요 사양:") + specs = request.product_info["material_info"] + if isinstance(specs, dict): + for key, value in specs.items(): + context_parts.append(f" * {key}: {value}") + + # 상품 옵션 + if request.product_info.get("options"): + options = request.product_info["options"] + context_parts.append(f"- 구매 옵션 ({len(options)}개):") + for i, option in enumerate(options[:5], 1): # 최대 5개만 + if isinstance(option, dict): + option_name = option.get("name", f"옵션 {i}") + context_parts.append(f" {i}. {option_name}") + else: + context_parts.append(f" {i}. {option}") + + # 구매 링크 + if request.product_info.get("url") or request.product_info.get("product_url"): + url = request.product_info.get("url") or request.product_info.get("product_url") + context_parts.append(f"- 구매 링크: {url}") + + return "\n".join(context_parts) if context_parts else "키워드 기반 콘텐츠 생성" + + def _create_content_prompt(self, context: str, request: RequestBlogCreate) -> str: + """콘텐츠 생성용 프롬프트 생성""" + + # 기본 키워드가 없으면 상품 제목에서 추출 + main_keyword = request.keyword + if not main_keyword and request.product_info and request.product_info.get("title"): + main_keyword = request.product_info["title"] + + prompt = f""" +다음 정보를 바탕으로 매력적인 블로그 포스트를 작성해주세요. + +정보: +{context} + +작성 가이드라인: +- 스타일: 친근하면서도 신뢰할 수 있는, 정보 제공 중심 +- 길이: 1200자 내외의 적당한 길이 +- 톤: 독자의 관심을 끄는 자연스러운 어조 + +작성 요구사항: +1. SEO 친화적이고 클릭하고 싶은 매력적인 제목 +2. 독자의 관심을 끄는 도입부 +3. 핵심 특징과 장점을 구체적으로 설명 +4. 실제 사용 시나리오나 활용 팁 +5. 구매 결정에 도움이 되는 정보 + +⚠️ 주의: +- 절대로 마지막에 'HTML 구조는…' 같은 자기 평가 문장을 추가하지 마세요. +- 출력 시 ```나 ```html 같은 코드 블록 구문을 포함하지 마세요. +- 오직 HTML 태그만 사용하여 구조화된 콘텐츠를 작성해주세요. +(예:

,

,

,