修复采集器流程顺序: 采集→分析→LLM总结
- _generate_topic_with_llm 改名为 _generate_topics_with_llm - 支持同时接收已分析的案例+搜索结果作为LLM上下文 - run()顺序改为: 采集→RSS提炼案例→LLM基于全部数据生成→降级回退 - LLM现在在流程末尾做总结生成,而非开头替代搜索
This commit is contained in:
+31
-33
@@ -377,13 +377,13 @@ class SustainabilityCollector:
|
|||||||
logger.warning(f"web_search失败 {source.name}: {e}")
|
logger.warning(f"web_search失败 {source.name}: {e}")
|
||||||
return []
|
return []
|
||||||
|
|
||||||
def _generate_topic_with_llm(self, search_results: Optional[List[Dict]] = None) -> Optional[SustainabilityTopic]:
|
def _generate_topics_with_llm(self, cases: List[SustainabilityCase] = None, search_results: List[Dict] = None) -> List[SustainabilityTopic]:
|
||||||
"""用LLM生成选题(有搜索结果时参考,无结果时直接生成)"""
|
"""用LLM基于采集数据生成选题(数据充分时精确生成,无数据时凭知识生成)"""
|
||||||
try:
|
try:
|
||||||
from app.core.nvidia_client import call_llm
|
from app.core.nvidia_client import call_llm
|
||||||
except ImportError:
|
except ImportError:
|
||||||
logger.warning("LLM不可用,跳过AI选题生成")
|
logger.warning("LLM不可用,跳过AI选题生成")
|
||||||
return None
|
return []
|
||||||
|
|
||||||
existing = self._get_existing_titles()
|
existing = self._get_existing_titles()
|
||||||
existing_hint = ""
|
existing_hint = ""
|
||||||
@@ -394,30 +394,28 @@ class SustainabilityCollector:
|
|||||||
day_idx = datetime.datetime.now().timetuple().tm_yday % len(categories)
|
day_idx = datetime.datetime.now().timetuple().tm_yday % len(categories)
|
||||||
target_category = categories[day_idx]
|
target_category = categories[day_idx]
|
||||||
|
|
||||||
search_section = ""
|
data_section = ""
|
||||||
if search_results:
|
if search_results:
|
||||||
summaries = [f"- {r.get('title','')}: {r.get('content','')[:120]}" for r in search_results[:4]]
|
summaries = [f"- {r.get('title','')}: {r.get('content','')[:100]}" for r in search_results[:5]]
|
||||||
search_section = "搜索结果参考:\n" + "\n".join(summaries) + "\n"
|
data_section += "搜索结果:\n" + "\n".join(summaries) + "\n"
|
||||||
|
if cases:
|
||||||
|
case_lines = [f"- {c.title[:40]}({c.category})" for c in cases[:5]]
|
||||||
|
data_section += "\n采集案例:\n" + "\n".join(case_lines) + "\n"
|
||||||
|
|
||||||
prompt = f"""你是一个内容策略师。生成一个面向中国年轻读者、有价值、适合传播的选题。
|
prompt = f"""你是一个内容策略师。基于以下信息,为「{target_category}」类别生成一个高质量选题。
|
||||||
|
|
||||||
目标类别:{target_category}
|
{data_section if data_section else "(当前无实时采集数据,请基于你对中文互联网趋势的了解直接生成)"}
|
||||||
{search_section}
|
|
||||||
{existing_hint}
|
{existing_hint}
|
||||||
|
|
||||||
生成一个选题,直接输出JSON(不要其他文字):
|
输出一个选题,格式JSON:
|
||||||
{{{{
|
{{{{
|
||||||
"title": "标题(20字内,含核心关键词,避免「新趋势」「指南」这类烂尾词)",
|
"title": "标题(20字内,含核心关键词)",
|
||||||
"core_concept": "核心观点(一句话说清独特价值)",
|
"core_concept": "核心观点(一句话)",
|
||||||
"audience_pain": "受众痛点(真实用户的困惑)",
|
"audience_pain": "受众痛点",
|
||||||
"unique_angle": "差异化切入点",
|
"unique_angle": "差异化切入点",
|
||||||
"format": "内容形式(趋势洞察/实操指南/对比分析/案例解读)"
|
"format": "内容形式(趋势洞察/实操指南/对比分析/案例解读)"
|
||||||
}}}}
|
}}}}
|
||||||
|
只输出JSON。"""
|
||||||
要求:
|
|
||||||
- 标题像普通人会搜索的
|
|
||||||
- 切入点具体,不泛泛而谈
|
|
||||||
- 优先考虑中国读者能实操的内容"""
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
resp = call_llm(prompt, temperature=0.7)
|
resp = call_llm(prompt, temperature=0.7)
|
||||||
@@ -431,7 +429,7 @@ class SustainabilityCollector:
|
|||||||
topic = SustainabilityTopic(
|
topic = SustainabilityTopic(
|
||||||
id=topic_id,
|
id=topic_id,
|
||||||
title=data.get("title", f"{target_category}新观察"),
|
title=data.get("title", f"{target_category}新观察"),
|
||||||
cases=[],
|
cases=[c.id for c in (cases or [])[:3]],
|
||||||
audience="城市焦虑青年(26-35岁)",
|
audience="城市焦虑青年(26-35岁)",
|
||||||
china_pain_points=data.get("audience_pain", ""),
|
china_pain_points=data.get("audience_pain", ""),
|
||||||
localization_solution="文章中将提供具体可执行的建议",
|
localization_solution="文章中将提供具体可执行的建议",
|
||||||
@@ -456,10 +454,10 @@ class SustainabilityCollector:
|
|||||||
platform_urls={}
|
platform_urls={}
|
||||||
)
|
)
|
||||||
logger.info(f"LLM生成选题: {topic.title}")
|
logger.info(f"LLM生成选题: {topic.title}")
|
||||||
return topic
|
return [topic]
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.warning(f"LLM选题生成失败: {e}")
|
logger.warning(f"LLM选题生成失败: {e}")
|
||||||
return None
|
return []
|
||||||
|
|
||||||
def analyze_article(self, article: Dict) -> Optional[SustainabilityCase]:
|
def analyze_article(self, article: Dict) -> Optional[SustainabilityCase]:
|
||||||
"""分析文章内容,提炼案例"""
|
"""分析文章内容,提炼案例"""
|
||||||
@@ -786,24 +784,24 @@ class SustainabilityCollector:
|
|||||||
logger.info(f"RSS采集 {sum(1 for a in all_articles if a.get('source_name','') not in [s.name for s in self.sources if s.type=='web_search'])} 篇, "
|
logger.info(f"RSS采集 {sum(1 for a in all_articles if a.get('source_name','') not in [s.name for s in self.sources if s.type=='web_search'])} 篇, "
|
||||||
f"搜索采集 {len(web_search_results)} 篇")
|
f"搜索采集 {len(web_search_results)} 篇")
|
||||||
|
|
||||||
# ---------------------- 第二阶段:LLM选题生成 ----------------------
|
# ---------------------- 第二阶段:RSS文章提炼案例 ----------------------
|
||||||
llm_topic = self._generate_topic_with_llm(web_search_results if web_search_results else None)
|
|
||||||
|
|
||||||
if llm_topic and not self._is_duplicate_topic(llm_topic.title, existing_titles):
|
|
||||||
llm_topic.created_at = datetime.datetime.now().isoformat()
|
|
||||||
llm_topic.lock_by = None
|
|
||||||
llm_topic.lock_at = None
|
|
||||||
llm_topic.status = "待处理"
|
|
||||||
self.new_topics.append(llm_topic)
|
|
||||||
logger.info(f"✅ LLM生成选题: {llm_topic.title}")
|
|
||||||
|
|
||||||
# ---------------------- 第三阶段:RSS文章提炼案例 ----------------------
|
|
||||||
rss_articles = [a for a in all_articles if a not in web_search_results]
|
rss_articles = [a for a in all_articles if a not in web_search_results]
|
||||||
for article in rss_articles[:15]:
|
for article in rss_articles[:15]:
|
||||||
case = self.analyze_article(article)
|
case = self.analyze_article(article)
|
||||||
if case:
|
if case:
|
||||||
self.new_cases.append(case)
|
self.new_cases.append(case)
|
||||||
|
|
||||||
|
# ---------------------- 第三阶段:LLM基于采集数据生成选题 ----------------------
|
||||||
|
llm_topics = self._generate_topics_with_llm(cases=self.new_cases, search_results=web_search_results)
|
||||||
|
for topic in llm_topics:
|
||||||
|
if not self._is_duplicate_topic(topic.title, existing_titles):
|
||||||
|
topic.created_at = datetime.datetime.now().isoformat()
|
||||||
|
topic.lock_by = None
|
||||||
|
topic.lock_at = None
|
||||||
|
topic.status = "待处理"
|
||||||
|
self.new_topics.append(topic)
|
||||||
|
logger.info(f"✅ LLM生成选题: {topic.title}")
|
||||||
|
|
||||||
# ---------------------- 第四阶段:降级策略 ----------------------
|
# ---------------------- 第四阶段:降级策略 ----------------------
|
||||||
if not self.new_topics and len(self.new_cases) < 2:
|
if not self.new_topics and len(self.new_cases) < 2:
|
||||||
logger.warning(f"LLM选题和RSS案例均不足,启动本地案例降级")
|
logger.warning(f"LLM选题和RSS案例均不足,启动本地案例降级")
|
||||||
|
|||||||
Reference in New Issue
Block a user