117 lines
4.7 KiB
Python
117 lines
4.7 KiB
Python
from __future__ import annotations
|
|
|
|
import logging
|
|
from datetime import date
|
|
from typing import Optional
|
|
|
|
from sqlalchemy.orm import Session
|
|
|
|
from app.agent.report_writer import ReportWriter
|
|
from app.core.config import Settings
|
|
from app.core.timezone import beijing_today
|
|
from app.db.repository import AgentRepository
|
|
from app.integrations.github_client import GitHubHotProjectClient
|
|
from app.integrations.news_search_client import NewsSearchClient
|
|
from app.llm.grok_client import GrokIntelligenceClient
|
|
from app.schemas.agent import RunResponse, SignalItem
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
|
|
class AgentRunner:
|
|
"""Coordinates source collection, persistence, and report writing."""
|
|
|
|
def __init__(self, db: Session, settings: Settings) -> None:
|
|
self.repo = AgentRepository(db)
|
|
self.db = db
|
|
self.github = GitHubHotProjectClient(settings)
|
|
self.news = NewsSearchClient(settings)
|
|
self.grok = GrokIntelligenceClient(settings)
|
|
self.writer = ReportWriter(settings.report_dir)
|
|
|
|
def run_daily(self, run_date: Optional[date] = None) -> RunResponse:
|
|
# Runner统一编排候选采集、模型研判和持久化,保证一次运行只有一种业务路径。
|
|
current_date = run_date or beijing_today()
|
|
request_payload = {
|
|
"run_date": current_date.isoformat(),
|
|
"mode": "grok_agent",
|
|
}
|
|
run = self.repo.create_run(request_payload=request_payload)
|
|
logger.info("agent_run_started run_id=%s run_date=%s mode=grok_agent", run.id, current_date)
|
|
|
|
try:
|
|
topics = self.repo.list_enabled_topics()
|
|
news_candidates = self.news.collect_ai_news(run_date=current_date)
|
|
github_projects = self.github.collect_hot_projects(run_date=current_date)
|
|
result = self.grok.collect_daily_intelligence(
|
|
topics=topics,
|
|
news_candidates=news_candidates,
|
|
github_projects=github_projects,
|
|
run_date=current_date,
|
|
)
|
|
signal_items = self._compose_run_signals(
|
|
news_candidates=news_candidates,
|
|
github_projects=github_projects,
|
|
model_signals=result.signals,
|
|
)
|
|
signals = self.repo.save_signals(run=run, items=signal_items)
|
|
report_path = self.writer.write_daily_report(current_date, result.report_markdown)
|
|
self.repo.save_report(
|
|
run=run,
|
|
title=result.title,
|
|
content_md=result.report_markdown,
|
|
file_path=str(report_path),
|
|
)
|
|
self.repo.mark_run_complete(
|
|
run=run,
|
|
raw_response={
|
|
"mode": "grok_agent",
|
|
"topic_count": len(topics),
|
|
"news_candidate_count": len(news_candidates),
|
|
"github_project_count": len(github_projects),
|
|
"model_signal_count": len(result.signals),
|
|
"saved_signal_count": len(signal_items),
|
|
"model": self.grok.settings.llm_model,
|
|
"model_response": result.raw_response,
|
|
},
|
|
)
|
|
self.db.commit()
|
|
logger.info(
|
|
"agent_run_completed run_id=%s signals=%s report_path=%s",
|
|
run.id,
|
|
len(signals),
|
|
report_path,
|
|
)
|
|
return RunResponse(run_id=run.id, status=run.status, report_path=str(report_path))
|
|
except Exception as exc:
|
|
self.repo.mark_run_failed(run=run, error=str(exc))
|
|
self.db.commit()
|
|
logger.exception("agent_run_failed run_id=%s error=%s", run.id, exc)
|
|
raise
|
|
|
|
def _compose_run_signals(
|
|
self,
|
|
news_candidates: list[SignalItem],
|
|
github_projects: list[SignalItem],
|
|
model_signals: list[SignalItem],
|
|
) -> list[SignalItem]:
|
|
# 入库清单以采集候选为准,模型同链接结果只增强摘要和重要度,避免日报模型漏选导致信号消失。
|
|
model_by_url = {item.source_url: item for item in model_signals}
|
|
composed = []
|
|
for candidate in [*news_candidates, *github_projects]:
|
|
model_item = model_by_url.get(candidate.source_url)
|
|
if model_item is None:
|
|
composed.append(candidate)
|
|
continue
|
|
composed.append(
|
|
candidate.model_copy(
|
|
update={
|
|
"topic": model_item.topic,
|
|
"summary": model_item.summary,
|
|
"importance": model_item.importance,
|
|
"entities": model_item.entities or candidate.entities,
|
|
}
|
|
)
|
|
)
|
|
return composed
|