diff --git a/CLAUDE.md b/CLAUDE.md index a955aaf..f0de8f5 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -35,13 +35,13 @@ n8n(YouTube API로 새 영상 감지) → `POST /api/news/summarize` → 자막 ```json // 성공 -{"status": "ok", "title": "...", "summary": "요약 텍스트"} +{"video_url": "...", "title": "...", "channel_name": "...", "status": "ok", "summary": "요약 텍스트"} // 스킵 (라이브/쇼츠) -{"status": "skipped", "title": "...", "reason": "라이브/예정 영상은 요약 대상이 아닙니다"} +{"video_url": "...", "title": "...", "channel_name": "...", "status": "skipped", "reason": "라이브/예정 영상은 요약 대상이 아닙니다"} // 에러 -{"status": "error", "title": "...", "error_type": "ValueError", "error_message": "..."} +{"video_url": "...", "title": "...", "channel_name": "...", "status": "error", "error_type": "ValueError", "error_message": "..."} ``` ## 환경변수 @@ -156,7 +156,8 @@ return $input.all().filter(item => { ```json { "video_url": "{{ $json.video_url }}", - "title": "{{ $json.title }}" + "title": "{{ $json.title }}", + "channel_name": "{{ $json.channel_name }}" } ``` - **Headers** (API_SECRET 사용 시): `X-Api-Secret: <시크릿값>` diff --git a/README.md b/README.md index a710e17..8904e83 100644 --- a/README.md +++ b/README.md @@ -74,13 +74,13 @@ OCI 등 클라우드 서버에서는 YouTube가 데이터센터 IP를 봇으로 ```json // 성공 -{"status": "ok", "title": "영상 제목", "summary": "요약 텍스트"} +{"video_url": "...", "title": "...", "channel_name": "...", "status": "ok", "summary": "요약 텍스트"} // 스킵 (라이브/쇼츠) -{"status": "skipped", "title": "영상 제목", "reason": "라이브/예정 영상은 요약 대상이 아닙니다"} +{"video_url": "...", "title": "...", "channel_name": "...", "status": "skipped", "reason": "라이브/예정 영상은 요약 대상이 아닙니다"} // 에러 -{"status": "error", "title": "영상 제목", "error_type": "ValueError", "error_message": "..."} +{"video_url": "...", "title": "...", "channel_name": "...", "status": "error", "error_type": "ValueError", "error_message": "..."} ``` ### `GET /api/news/health` (외부) / `GET /health` (내부) diff --git a/app/main.py b/app/main.py index 57dbc47..f0649c6 100644 --- a/app/main.py +++ b/app/main.py @@ -11,6 +11,7 @@ app = FastAPI(title="News Summary Bot") class SummarizeRequest(BaseModel): video_url: str title: str = "" + channel_name: str = "" @app.post("/summarize") @@ -23,21 +24,24 @@ async def summarize_video( title = req.title or "제목 없음" + channel_name = req.channel_name or "" + base = {"video_url": req.video_url, "title": title, "channel_name": channel_name} + try: video_id = extract_video_id(req.video_url) transcript = fetch_transcript(video_id) summary = summarize(transcript, title) except SkipVideo as e: - return {"status": "skipped", "title": title, "reason": str(e)} + return {**base, "status": "skipped", "reason": str(e)} except Exception as e: return { + **base, "status": "error", - "title": title, "error_type": type(e).__name__, "error_message": str(e), } - return {"status": "ok", "title": title, "summary": summary} + return {**base, "status": "ok", "summary": summary} @app.get("/health") diff --git a/app/summarizer.py b/app/summarizer.py index 9de06f7..42fe3ec 100644 --- a/app/summarizer.py +++ b/app/summarizer.py @@ -4,18 +4,20 @@ from app.config import settings client = anthropic.Anthropic(api_key=settings.anthropic_api_key) -SYSTEM_PROMPT = """너는 뉴스/경제 유튜브 영상 요약 전문가야. -영상 자막 텍스트를 받아서 아래 형식으로 요약해줘. +SYSTEM_PROMPT = """너는 뉴스/경제 유튜브 영상을 시청하고 핵심을 전달하는 요약 전문가야. +영상을 직접 다 본 사람처럼, 구체적인 수치·사례·맥락을 포함해서 요약해줘. +읽는 사람이 영상을 안 봐도 내용을 충분히 파악할 수 있어야 해. ## 형식 -- **한줄 요약**: 영상의 핵심을 한 문장으로 -- **주요 내용**: 핵심 포인트를 3~7개 불릿으로 정리 -- **결론/시사점**: 영상이 전달하려는 메시지나 시사점 +- **한줄 요약**: 영상의 핵심 메시지를 구체적으로 한 문장 +- **주요 내용**: 영상에서 다룬 핵심 포인트를 3~7개 불릿으로 정리. 각 항목에 구체적인 수치, 종목명, 인물, 사건 등을 반드시 포함 +- **결론/시사점**: 영상이 전달하려는 메시지와 시청자가 취할 수 있는 액션 ## 규칙 - 한국어로 작성 -- 간결하고 명확하게 +- "~에 대해 이야기했다" 같은 메타 서술 금지. 내용 자체를 직접 전달 - 자막의 오타나 말더듬은 무시하고 의미 중심으로 정리 +- 영상에서 언급된 구체적 수치(%, 금액, 날짜 등)가 있으면 반드시 포함 """ diff --git a/docs/development.md b/docs/development.md index d5b3dee..7f47476 100644 --- a/docs/development.md +++ b/docs/development.md @@ -52,13 +52,13 @@ YouTube 영상 URL을 받아 자막을 추출하고 Claude로 요약합니다. ```json // 성공 -{"status": "ok", "title": "...", "summary": "요약 텍스트"} +{"video_url": "...", "title": "...", "channel_name": "...", "status": "ok", "summary": "요약 텍스트"} // 스킵 (라이브/쇼츠) -{"status": "skipped", "title": "...", "reason": "..."} +{"video_url": "...", "title": "...", "channel_name": "...", "status": "skipped", "reason": "..."} // 에러 -{"status": "error", "title": "...", "error_type": "...", "error_message": "..."} +{"video_url": "...", "title": "...", "channel_name": "...", "status": "error", "error_type": "...", "error_message": "..."} ``` ### GET /health diff --git a/docs/n8n-setup.md b/docs/n8n-setup.md index a85b3f0..719d527 100644 --- a/docs/n8n-setup.md +++ b/docs/n8n-setup.md @@ -149,7 +149,8 @@ return $input.all().filter(item => { ```json { "video_url": "{{ $json.video_url }}", - "title": "{{ $json.title }}" + "title": "{{ $json.title }}", + "channel_name": "{{ $json.channel_name }}" } ```