네이버 검색 API 키워드 분석 + Claude AI 글 생성 + 품질 리뷰 + 수익 추적 - blog-lab/ 서비스 전체 (FastAPI, SQLite 5테이블, 18 엔드포인트) - docker-compose.yml: blog-lab 서비스 (port 18700) - nginx: /api/blog-marketing/ 라우팅 추가 - .env.example: NAVER_CLIENT_ID/SECRET 추가 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
77 lines
2.2 KiB
Python
77 lines
2.2 KiB
Python
"""Claude API 기반 블로그 글 품질 리뷰 — 5기준 × 10점, 35/50 통과."""
|
||
|
||
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__)
|
||
|
||
PASS_THRESHOLD = 35 # 50점 만점 중 35점 이상이면 통과
|
||
|
||
_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 review_post(title: str, body: str) -> Dict[str, Any]:
|
||
"""블로그 글 품질 리뷰.
|
||
|
||
Returns:
|
||
{
|
||
"scores": {"empathy": N, "click_appeal": N, "conversion": N, "seo": N, "format": N},
|
||
"total": N,
|
||
"pass": bool,
|
||
"feedback": str
|
||
}
|
||
"""
|
||
template = get_template("quality_review")
|
||
if not template:
|
||
raise RuntimeError("quality_review 템플릿이 없습니다")
|
||
|
||
prompt = template.format(title=title, body=body[:6000])
|
||
|
||
client = _get_client()
|
||
resp = client.messages.create(
|
||
model=CLAUDE_MODEL,
|
||
max_tokens=2048,
|
||
messages=[{"role": "user", "content": prompt}],
|
||
)
|
||
raw = resp.content[0].text
|
||
|
||
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)
|
||
|
||
scores = result.get("scores", {})
|
||
total = sum(scores.values())
|
||
passed = total >= PASS_THRESHOLD
|
||
|
||
return {
|
||
"scores": scores,
|
||
"total": total,
|
||
"pass": passed,
|
||
"feedback": result.get("feedback", ""),
|
||
}
|
||
except (json.JSONDecodeError, KeyError, TypeError) as e:
|
||
logger.warning("Quality review JSON parse failed: %s", e)
|
||
return {
|
||
"scores": {"empathy": 0, "click_appeal": 0, "conversion": 0, "seo": 0, "format": 0},
|
||
"total": 0,
|
||
"pass": False,
|
||
"feedback": f"리뷰 파싱 실패. 원본 응답:\n{raw[:500]}",
|
||
}
|