Files
news-summary-bot/app/summarizer.py
sm4640 bb785545f0
All checks were successful
news-summary-bot-cicd / build_push_deploy (push) Successful in 9m23s
Fix: [3.0.5] 요약 필드 빈 값 방지 — 프롬프트 규칙 + fallback 처리
- 프롬프트에 세 필드 모두 비어있지 않은 값 필수 규칙 추가
- 코드에서 빈 문자열일 경우 "(내용 없음)" fallback 처리
- Discord embed field value 빈 문자열 에러 방지

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-25 20:03:30 +09:00

69 lines
2.8 KiB
Python

import json
import re
import anthropic
from app.config import settings
client = anthropic.Anthropic(api_key=settings.anthropic_api_key)
SYSTEM_PROMPT = """너는 뉴스/경제 유튜브 영상을 시청하고 핵심을 전달하는 요약 전문가야.
영상을 직접 다 본 사람처럼, 구체적인 수치·사례·맥락을 포함해서 요약해줘.
읽는 사람이 영상을 안 봐도 내용을 충분히 파악할 수 있어야 해.
## 출력 형식
반드시 아래 JSON만 출력해. 코드펜스(```)로 감싸지 마.
{"oneliner": "영상의 핵심 메시지를 구체적으로 한 문장", "main_points": "• 핵심 포인트 1\\n• 핵심 포인트 2\\n• 핵심 포인트 3", "conclusion": "영상이 전달하려는 메시지와 시청자가 취할 수 있는 액션"}
## 규칙
- 반드시 위 JSON 형식으로만 응답. 코드펜스, 설명 등 다른 텍스트 절대 금지
- oneliner, main_points, conclusion 세 필드 모두 반드시 비어있지 않은 값으로 채울 것. 절대 빈 문자열("") 금지
- 한국어로 작성
- main_points는 3~7개 불릿(•)으로 정리. 각 항목에 구체적인 수치, 종목명, 인물, 사건 등을 반드시 포함
- "~에 대해 이야기했다" 같은 메타 서술 금지. 내용 자체를 직접 전달
- 자막의 오타나 말더듬은 무시하고 의미 중심으로 정리
- 영상에서 언급된 구체적 수치(%, 금액, 날짜 등)가 있으면 반드시 포함
"""
def summarize(transcript: str, title: str) -> dict:
message = client.messages.create(
model="claude-sonnet-4-20250514",
max_tokens=2048,
system=SYSTEM_PROMPT,
messages=[
{
"role": "user",
"content": f"영상 제목: {title}\n\n자막:\n{transcript}",
}
],
)
raw = message.content[0].text
parsed = _parse_json_response(raw)
fallback = "(내용 없음)"
return {
"oneliner": parsed.get("oneliner") or fallback,
"main_points": parsed.get("main_points") or fallback,
"conclusion": parsed.get("conclusion") or fallback,
}
def _parse_json_response(raw: str) -> dict:
"""Claude 응답에서 JSON을 추출하여 파싱한다."""
# 1) 코드펜스 제거 (```json ... ``` 또는 ``` ... ```)
json_match = re.search(r"```(?:json)?\s*(.*?)\s*```", raw, re.DOTALL)
json_str = json_match.group(1) if json_match else raw.strip()
# 2) 첫 번째 { ~ 마지막 } 만 추출 (앞뒤 잡텍스트 제거)
brace_match = re.search(r"\{.*\}", json_str, re.DOTALL)
if brace_match:
json_str = brace_match.group(0)
try:
return json.loads(json_str)
except json.JSONDecodeError:
# 파싱 실패 시 전체 텍스트를 oneliner로
return {"oneliner": raw, "main_points": "", "conclusion": ""}