"""Claude API 기반 콘텐츠 생성 — 트렌드 브리프 + 블로그 글 작성.""" import json import logging from typing import Any, Dict, Optional import anthropic from .config import ANTHROPIC_API_KEY, CLAUDE_MODEL from .db import get_template logger = logging.getLogger(__name__) _client: Optional[anthropic.Anthropic] = None def _get_client() -> anthropic.Anthropic: global _client if _client is None: _client = anthropic.Anthropic(api_key=ANTHROPIC_API_KEY) return _client def _call_claude(prompt: str, max_tokens: int = 4096) -> str: """Claude API 호출. 단일 user 메시지.""" client = _get_client() resp = client.messages.create( model=CLAUDE_MODEL, max_tokens=max_tokens, messages=[{"role": "user", "content": prompt}], ) return resp.content[0].text def generate_trend_brief(analysis: Dict[str, Any]) -> str: """키워드 분석 데이터를 바탕으로 트렌드 브리프 생성.""" template = get_template("trend_brief") if not template: raise RuntimeError("trend_brief 템플릿이 없습니다") top_blogs_text = "\n".join( f"- {b.get('title', '')}" for b in analysis.get("top_blogs", []) ) or "없음" top_products_text = "\n".join( f"- {p.get('title', '')} ({p.get('lprice', '?')}원, {p.get('mallName', '')})" for p in analysis.get("top_products", []) ) or "없음" prompt = template.format( keyword=analysis.get("keyword", ""), competition=analysis.get("competition", 0), opportunity=analysis.get("opportunity", 0), top_blogs=top_blogs_text, top_products=top_products_text, ) return _call_claude(prompt) def generate_blog_post(analysis: Dict[str, Any], trend_brief: str) -> Dict[str, str]: """트렌드 브리프를 바탕으로 블로그 글 작성. Returns: {"title": str, "body": str, "excerpt": str, "tags": [...]} """ template = get_template("blog_write") if not template: raise RuntimeError("blog_write 템플릿이 없습니다") top_products_text = "\n".join( f"- {p.get('title', '')} ({p.get('lprice', '?')}원, {p.get('mallName', '')})" for p in analysis.get("top_products", []) ) or "없음" prompt = template.format( keyword=analysis.get("keyword", ""), trend_brief=trend_brief, top_products=top_products_text, ) # 구조화된 응답을 위한 추가 지시 prompt += ( "\n\n---\n" "응답은 반드시 아래 JSON 형식으로 해주세요 (JSON만 출력, 다른 텍스트 없이):\n" '{"title": "블로그 제목", "body": "HTML 본문", "excerpt": "2줄 요약", ' '"tags": ["태그1", "태그2", ...]}' ) raw = _call_claude(prompt, max_tokens=8192) # JSON 파싱 시도 try: # ```json ... ``` 블록 제거 text = raw.strip() if text.startswith("```"): lines = text.split("\n") lines = [l for l in lines if not l.strip().startswith("```")] text = "\n".join(lines) result = json.loads(text) return { "title": result.get("title", ""), "body": result.get("body", ""), "excerpt": result.get("excerpt", ""), "tags": result.get("tags", []), } except (json.JSONDecodeError, KeyError): # JSON 파싱 실패 시 원본 텍스트를 body로 logger.warning("Blog post JSON parse failed, using raw text") return { "title": f"{analysis.get('keyword', '')} 추천 리뷰", "body": raw, "excerpt": raw[:200], "tags": [analysis.get("keyword", "")], } def regenerate_blog_post( analysis: Dict[str, Any], trend_brief: str, previous_body: str, feedback: str, ) -> Dict[str, str]: """피드백을 반영하여 블로그 글 재생성.""" prompt = ( "당신은 네이버 블로그에서 월 100만 이상 수익을 올리는 전문 블로거입니다.\n" f"키워드: {analysis.get('keyword', '')}\n\n" f"이전에 작성한 글:\n{previous_body[:3000]}\n\n" f"리뷰어 피드백:\n{feedback}\n\n" "위 피드백을 반영하여 글을 개선해주세요.\n" "작성 규칙: 1인칭 체험기, 1,500자 이상, 자연스러운 구어체, " "제품 비교표 포함, 광고 고지 문구 포함.\n" "HTML 형식으로 작성하되, 네이버 블로그에서 바로 붙여넣기 가능한 형태로.\n\n" "---\n" "응답은 반드시 아래 JSON 형식으로 해주세요 (JSON만 출력):\n" '{"title": "블로그 제목", "body": "HTML 본문", "excerpt": "2줄 요약", ' '"tags": ["태그1", "태그2", ...]}' ) raw = _call_claude(prompt, max_tokens=8192) try: text = raw.strip() if text.startswith("```"): lines = text.split("\n") lines = [l for l in lines if not l.strip().startswith("```")] text = "\n".join(lines) result = json.loads(text) return { "title": result.get("title", ""), "body": result.get("body", ""), "excerpt": result.get("excerpt", ""), "tags": result.get("tags", []), } except (json.JSONDecodeError, KeyError): logger.warning("Regenerate JSON parse failed, using raw text") return { "title": f"{analysis.get('keyword', '')} 추천 리뷰 (개선)", "body": raw, "excerpt": raw[:200], "tags": [analysis.get("keyword", "")], }