feat(agent-office): YouTubeResearchAgent + 스케줄러 + /youtube/research API
- db.py: youtube_research_jobs 테이블 추가 + CRUD 3종 (add/update/get_latest) - agents/youtube.py: YouTubeResearchAgent 신규 구현 (on_schedule/on_command/on_approval/_run_research/send_weekly_report) - agents/__init__.py: YouTubeResearchAgent 등록 - scheduler.py: youtube_research(매일 09:00) + youtube_weekly_report(월 08:00) cron 추가 - main.py: POST /api/agent-office/youtube/research + GET /api/agent-office/youtube/research/status 엔드포인트 추가 Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -3,6 +3,7 @@ from .music import MusicAgent
|
||||
from .blog import BlogAgent
|
||||
from .realestate import RealestateAgent
|
||||
from .lotto import LottoAgent
|
||||
from .youtube import YouTubeResearchAgent
|
||||
|
||||
AGENT_REGISTRY = {}
|
||||
|
||||
@@ -12,6 +13,7 @@ def init_agents():
|
||||
AGENT_REGISTRY["blog"] = BlogAgent()
|
||||
AGENT_REGISTRY["realestate"] = RealestateAgent()
|
||||
AGENT_REGISTRY["lotto"] = LottoAgent()
|
||||
AGENT_REGISTRY["youtube"] = YouTubeResearchAgent()
|
||||
|
||||
def get_agent(agent_id: str):
|
||||
return AGENT_REGISTRY.get(agent_id)
|
||||
|
||||
85
agent-office/app/agents/youtube.py
Normal file
85
agent-office/app/agents/youtube.py
Normal file
@@ -0,0 +1,85 @@
|
||||
# agent-office/app/agents/youtube.py
|
||||
import asyncio
|
||||
from datetime import date
|
||||
|
||||
from .base import BaseAgent
|
||||
from ..db import add_youtube_research_job, update_youtube_research_job
|
||||
from ..youtube_researcher import (
|
||||
TARGET_COUNTRIES, TREND_KEYWORDS,
|
||||
fetch_youtube_trending, fetch_google_trends, fetch_billboard_top20,
|
||||
push_to_music_lab,
|
||||
)
|
||||
|
||||
|
||||
class YouTubeResearchAgent(BaseAgent):
|
||||
agent_id = "youtube"
|
||||
display_name = "YouTube 리서치"
|
||||
|
||||
async def on_schedule(self) -> None:
|
||||
await self._run_research(TARGET_COUNTRIES)
|
||||
|
||||
async def on_command(self, command: str, params: dict) -> dict:
|
||||
if command == "research":
|
||||
countries = params.get("countries", TARGET_COUNTRIES)
|
||||
asyncio.create_task(self._run_research(countries))
|
||||
return {"ok": True, "message": f"리서치 시작: {countries}"}
|
||||
return {"ok": False, "message": f"Unknown command: {command}"}
|
||||
|
||||
async def on_approval(self, task_id: str, approved: bool, feedback: str = "") -> None:
|
||||
pass
|
||||
|
||||
async def _run_research(self, countries: list) -> None:
|
||||
job_id = add_youtube_research_job(countries)
|
||||
await self.transition("working", f"트렌드 수집 중 ({','.join(countries)})", str(job_id))
|
||||
|
||||
all_trends = []
|
||||
try:
|
||||
for country in countries:
|
||||
trends = await fetch_youtube_trending(country)
|
||||
all_trends.extend(trends)
|
||||
|
||||
gt = await fetch_google_trends(TREND_KEYWORDS, countries)
|
||||
all_trends.extend(gt)
|
||||
|
||||
bb = await fetch_billboard_top20()
|
||||
all_trends.extend(bb)
|
||||
|
||||
ok = await push_to_music_lab(all_trends, date.today().isoformat())
|
||||
if not ok:
|
||||
raise RuntimeError("music-lab push 실패")
|
||||
|
||||
update_youtube_research_job(job_id, "completed", len(all_trends))
|
||||
await self.transition("reporting", f"수집 완료: {len(all_trends)}건", str(job_id))
|
||||
except Exception as e:
|
||||
update_youtube_research_job(job_id, "failed", len(all_trends), str(e))
|
||||
await self.transition("idle", f"수집 실패: {e}")
|
||||
return
|
||||
|
||||
await self.transition("idle", "리서치 완료")
|
||||
|
||||
async def send_weekly_report(self) -> None:
|
||||
"""매주 월요일 08:00 — 주간 인사이트 텔레그램 발송."""
|
||||
import httpx
|
||||
from ..youtube_researcher import MUSIC_LAB_URL
|
||||
try:
|
||||
async with httpx.AsyncClient(timeout=10.0) as client:
|
||||
resp = await client.get(f"{MUSIC_LAB_URL}/api/music/market/report/latest")
|
||||
if resp.status_code != 200:
|
||||
return
|
||||
report = resp.json()
|
||||
except Exception:
|
||||
return
|
||||
|
||||
top = report.get("top_genres", [])[:3]
|
||||
insights = report.get("insights", "")
|
||||
text = "📊 *YouTube 시장 주간 리포트*\n\n🔥 인기 장르:\n"
|
||||
for g in top:
|
||||
text += f" • {g['genre']} (score: {g['score']:.2f})\n"
|
||||
if insights:
|
||||
text += f"\n💡 {insights[:300]}"
|
||||
|
||||
try:
|
||||
from ..telegram_bot import send_message
|
||||
await send_message(text)
|
||||
except (ImportError, Exception):
|
||||
pass
|
||||
Reference in New Issue
Block a user