#!/usr/bin/env python3 """ 大纲阶段:基于选题和研究笔记,用 LLM 动态生成结构化大纲 """ import json, datetime, logging, sys from pathlib import Path from typing import Dict PROJECT_ROOT = Path(__file__).parent.parent sys.path.insert(0, str(PROJECT_ROOT)) sys.path.insert(0, str(PROJECT_ROOT / "platform" / "backend")) from db_helper import get_topic_by_id try: from app.core.nvidia_client import call_llm HAVE_LLM = True except ImportError: HAVE_LLM = False DATA_DIR = PROJECT_ROOT / "automation" / "data" RESEARCH_DIR = DATA_DIR / "research" OUTPUT_DIR = DATA_DIR / "outlines" LOGS_DIR = PROJECT_ROOT / "automation" / "logs" TODAY = datetime.datetime.now().strftime("%Y-%m-%d") logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s', handlers=[logging.FileHandler(LOGS_DIR / f"outline_{TODAY}.log"), logging.StreamHandler()]) logger = logging.getLogger(__name__) class Outliner: def __init__(self, topic_id: str): self.topic_id = topic_id self.topic = self._load_topic() research_file = RESEARCH_DIR / TODAY / f"{topic_id}_research.md" self.research_notes = research_file.read_text(encoding='utf-8') if research_file.exists() else "" self.output_dir = OUTPUT_DIR / TODAY self.output_dir.mkdir(parents=True, exist_ok=True) def _load_topic(self) -> Dict: topic = get_topic_by_id(self.topic_id) if not topic: raise ValueError(f"Topic {self.topic_id} not found") return topic def generate_outline(self) -> str: title = self.topic['title'] field = self.topic.get('field', '') core = self.topic.get('core_concept', '') pain = self.topic.get('audience_pain', '') angle = self.topic.get('unique_angle', '') cases_summary = self.research_notes[:2000] if self.research_notes else "暂无研究笔记" if HAVE_LLM: _now = datetime.datetime.now() prompt = f"""你是一个资深内容编辑,擅长设计读者爱看+搜索引擎友好+平台愿意推荐+有市场传播力的文章结构。 ⚠️ 今天日期:{_now.strftime('%Y年%m月%d日')}。当前年份:{_now.year}年。 ## 选题信息 标题:{title} 领域:{field} 核心观点:{core} 受众痛点:{pain} 独特视角:{angle} ## 研究笔记 {cases_summary} ## 大纲设计要求 ### 结构 - 5-8章,每章2-4个要点 - 结构要有递进:要么认知升级型,要么问题解决型 - 把独特视角和受众痛点融入各章,不单独列 - 每章标题自带信息量+好奇心,不要「引言」「总结」这类通用标题 - 开头要有"钩子"(hook)抓住读者,结尾要有可转发/收藏的总结 ### 数据与热点 - **全文必须使用{_now.year-1}-{_now.year}年最新数据**,禁用一切过时数据 - 每个观点必须配最新的国内外热点事件/数据/政策来佐证 - 体现当前行业正在讨论的核心议题,拒绝泛泛而谈 ### 独特风格 - 有自己的判断和立场,不是搬运观点 - 每章至少一个"反常识"或"很少有人提"的洞察 - 避免同质化表达、老生常谈 ### SEO - H2/H3自然包含用户搜索时会用的短语 - 确保大纲覆盖2-3个高价值搜索词,包含1个长尾词 - 每章标题对用户搜索意图有回应 ### 市场价值 - 读完每章读者能拿走一个实际有用的东西(方法/清单/思维框架/判断标准) - 避免「信息增量为零」的空洞章节 - 思考:这篇文章对读者职业/生活/认知有什么用? ### 平台推荐优化 - 知乎:偏硬核数据分析和深度逻辑,结构要有论证链 - 小红书:偏实操步骤/清单/对比,结构要一目了然 - 公众号:偏故事化开头+情感共鸣+金句结尾,段落节奏快 - 一个框架适应三平台,各平台可裁剪侧重点 直接输出大纲,不要输出思考过程。""" try: outline = call_llm(prompt, temperature=0.6, system_prompt="你是一个有经验的内容编辑,擅长为不同选题设计差异化的文章结构。") logger.info(f"LLM 大纲生成成功,长度:{len(outline)}") return f"# 文章大纲:{title}\n\n{outline}\n\n---\n*大纲生成时间:{TODAY}*" except Exception as e: logger.warning(f"LLM 大纲生成失败: {e},使用模板") return self._template_outline(title, field, core, pain, angle) def _template_outline(self, title, field, core, pain, angle) -> str: case_count = self.research_notes.count('### 案例') if self.research_notes else 0 case_section = f"""## 四、全球/行业趋势与案例 - 引用研究笔记中的 {case_count} 个案例,精选 2-3 个详述 - 数据支撑:提取研究笔记中的关键数据 - 趋势分析""" if case_count > 0 else "" return f"""# 文章大纲:{title} ## 一、引言 - 场景切入:{title} - 点明文章价值 ## 二、核心观点 {core} ## 三、受众痛点分析 {pain} {case_section} ## {"五" if case_section else "四"}、本土落地建议 - 结合{field}领域特点 - 提供可执行的步骤 - 注意事项 ## {"六" if case_section else "五"}、独特视角:{angle} ## {"七" if case_section else "六"}、行动指南 1. 了解现状 2. 制定方案 3. 小范围验证 4. 持续优化 ## {"八" if case_section else "七"}、总结与鼓励 --- *大纲生成时间:{TODAY}*""" def save(self): outline_text = self.generate_outline() out_path = self.output_dir / f"{self.topic_id}_outline.md" out_path.write_text(outline_text, encoding='utf-8') logger.info(f"大纲已保存: {out_path}") return out_path def main(): import argparse parser = argparse.ArgumentParser() parser.add_argument('--topic-id', required=True) args = parser.parse_args() o = Outliner(args.topic_id) o.save() print(f"SUCCESS: Outline created for {args.topic_id}") sys.exit(0) if __name__ == "__main__": main()