{
  "metadata": {
    "id": "ch29",
    "title": "第29章：内容生成流水线",
    "volume": "vol8",
    "volume_title": "实战案例集",
    "word_count": 2279,
    "difficulty": "advanced",
    "prerequisites": [
      "ch18"
    ],
    "key_concepts": [
      "需求分析与功能规划",
      "业务背景",
      "功能清单",
      "非功能需求",
      "架构设计",
      "项目结构",
      "核心类设计",
      "核心代码实现",
      "配置与 LLM 客户端",
      "选题策划 Agent",
      "内容撰写 Agent",
      "审校优化 Agent",
      "分发适配 Agent",
      "敏感词过滤服务",
      "FastAPI 入口"
    ],
    "learning_objectives": [],
    "estimated_tokens": 1367,
    "source_file": "vol8/ch29_内容生成流水线.md"
  },
  "overview": "",
  "sections": [
    {
      "id": "29.1",
      "title": "29.1 需求分析与功能规划",
      "level": 2,
      "content": "",
      "subsections": [
        {
          "id": "29.1.1",
          "title": "29.1.1 业务背景",
          "content": "内容营销是数字时代的核心增长引擎，但内容生产面临三大困境：\n\n1. **产能瓶颈**：一个优秀的内容创作者每周产出 3-5 篇高质量文章，远不够覆盖多渠道需求\n2. **质量不稳定**：不同创作者水平差异大，审校标准不统一，发布质量参差不齐\n3. **渠道适配难**：同一主题需要为公众号、知乎、小红书、抖音等不同平台定制不同格式和风格\n\n我们需要构建一个 AI 驱动的内容生成流水线，覆盖从选题策划到多渠道分发的全流程：\n\n- **智能选题策划**：基于热点趋势和用户画像推荐高价值选题\n- **AI 辅助写作**：基于大纲和参考资料自动生成高质量初稿\n- **自动审校优化**：语法检查、风格统一、SEO 优化、敏感词过滤\n- **多渠道适配**：一键将内容转换为不同平台的格式和风格"
        },
        {
          "id": "29.1.2",
          "title": "29.1.2 功能清单",
          "content": ""
        },
        {
          "id": "29.1.3",
          "title": "29.1.3 非功能需求",
          "content": "| 维度 | 指标 |\n|------|------|\n| 单篇文章生成时间 | < 30 秒（初稿） |\n| 内容原创度 | > 85%（相似度检测） |\n| 敏感词准确率 | > 95% |\n| 并发支持 | 50 QPS |\n| 支持渠道 | 微信公众号、知乎、小红书、头条号、抖音 |\n\n---"
        }
      ]
    },
    {
      "id": "29.2",
      "title": "29.2 架构设计",
      "level": 2,
      "content": "",
      "subsections": [
        {
          "id": "29.2.1",
          "title": "29.2.1 项目结构",
          "content": ""
        },
        {
          "id": "29.2.2",
          "title": "29.2.2 核心类设计",
          "content": "系统由四个 Agent 组成，形成完整的内容生产 Pipeline：\n\n- **TopicAgent**：分析热点趋势和用户需求，推荐高价值选题并生成内容大纲\n- **WritingAgent**：基于大纲和参考资料撰写内容初稿，支持多种写作风格\n- **ReviewAgent**：对初稿进行多维度审校——语法、SEO、敏感词、可读性\n- **DistributeAgent**：将审校后的内容适配为不同平台的格式和风格\n\n**设计决策**：采用**流水线 + 迭代反馈**模式。ReviewAgent 发现问题后反馈给 WritingAgent 修改，最多迭代 3 次。DistributeAgent 并行适配多渠道。\n\n---"
        }
      ]
    },
    {
      "id": "29.3",
      "title": "29.3 核心代码实现",
      "level": 2,
      "content": "",
      "subsections": [
        {
          "id": "29.3.1",
          "title": "29.3.1 配置与 LLM 客户端",
          "content": ""
        },
        {
          "id": "29.3.2",
          "title": "29.3.2 选题策划 Agent",
          "content": ""
        },
        {
          "id": "29.3.3",
          "title": "29.3.3 内容撰写 Agent",
          "content": ""
        },
        {
          "id": "29.3.4",
          "title": "29.3.4 审校优化 Agent",
          "content": ""
        },
        {
          "id": "29.3.5",
          "title": "29.3.5 分发适配 Agent",
          "content": ""
        },
        {
          "id": "29.3.6",
          "title": "29.3.6 敏感词过滤服务",
          "content": ""
        },
        {
          "id": "29.3.7",
          "title": "29.3.7 FastAPI 入口",
          "content": "---"
        }
      ]
    },
    {
      "id": "29.4",
      "title": "29.4 测试",
      "level": 2,
      "content": "",
      "subsections": [
        {
          "id": "29.4.1",
          "title": "29.4.1 选题分析测试",
          "content": ""
        },
        {
          "id": "29.4.2",
          "title": "29.4.2 敏感词过滤测试",
          "content": ""
        },
        {
          "id": "29.4.3",
          "title": "29.4.3 可读性评分测试",
          "content": "---"
        }
      ]
    },
    {
      "id": "29.5",
      "title": "29.5 部署",
      "level": 2,
      "content": "---",
      "subsections": []
    },
    {
      "id": "29.6",
      "title": "29.6 经验总结",
      "level": 2,
      "content": "",
      "subsections": [
        {
          "id": "29.6.1",
          "title": "29.6.1 踩坑记录",
          "content": "**坑1：AI 生成内容的\"AI 味\"**\n\n早期生成的文章有明显的人工智能痕迹——大量\"首先...其次...最后...\"、过于工整的排比、缺乏个人观点。解决方案是**注入风格 Persona**：在 Prompt 中定义明确的写作人格（语气、偏好用词、避讳表达），并在 Few-Shot 中提供同风格范文。对于品牌内容，还需注入**品牌调性词库**。\n\n**坑2：多渠道适配的质量不一致**\n\n小红书需要大量 Emoji 和口语化表达，知乎需要学术严谨，抖音需要视觉化脚本——用同一个 Prompt 适配所有平台效果很差。我们为每个渠道编写了**专属适配 Prompt 模板**，包含平台特有的格式规范和风格指南。\n\n**坑3：审校闭环的 Token 成本**\n\n审校发现→反馈→重写→再审校的循环可能消耗大量 Token。我们将**审校分级处理**：语法和格式问题用规则引擎直接修正（不消耗 LLM），只有逻辑和风格问题才交由 LLM 处理，成本降低 60%。\n\n**坑4：敏感词误判**\n\n\"最好\"在\"最好的实践\"语境中是正常的，但在广告语境中违规。解决方案是**上下文感知过滤**：结合文案类型（广告/教程/观点）和语境判断是否为违规使用，而非简单的字符串匹配。"
        },
        {
          "id": "29.6.2",
          "title": "29.6.2 性能优化经验",
          "content": "1. **并行审校**：语法检查、SEO 分析、敏感词过滤三路并行执行\n2. **分段审校**：不审校全文，而是逐段审校，发现 3 个问题后提前终止\n3. **流式输出**：文章生成使用流式输出，用户可实时看到生成进度\n4. **缓存机制**：相同选题的大纲可复用，避免重复生成"
        },
        {
          "id": "29.6.3",
          "title": "29.6.3 关键设计模式总结",
          "content": "| 模式 | 应用场景 | 效果 |\n|------|---------|------|\n| Style Persona | 内容风格控制 | AI味降低 80% |\n| 规则 + LLM 混合审校 | 内容质量控制 | 成本降低 60% |\n| Pipeline + 反馈循环 | 内容质量迭代 | 一次通过率 70%→90% |\n| 渠道模板隔离 | 多渠道适配 | 各平台质量达标率 95% |"
        },
        {
          "id": "29.6.4",
          "title": "29.6.4 未来演进方向",
          "content": "1. **品牌知识库**：为每个品牌构建专属风格库和术语库\n2. **A/B 测试集成**：自动生成多个标题版本，通过 A/B 测试选择最优\n3. **内容日历**：基于历史数据分析最佳发布时间和频率\n4. **多模态内容**：从图文扩展到短视频脚本、播客大纲、PPT 演讲稿\n\n---\n\n**本章小结**：内容生成流水线展示了 Agent 在内容生产领域的强大能力。核心在于**风格可控的生成**（Style Persona）和**质量闭环**（审校→反馈→重写）。通过 Pipeline 模式串联选题、撰写、审校、分发四个环节，并在每个环节注入专业规则和 LLM 能力，可以将内容生产效率提升 10 倍以上，同时保证质量的一致性。"
        }
      ]
    }
  ],
  "code_blocks": [
    {
      "id": "code-1",
      "language": "text",
      "description": "- 多渠道适配：一键将内容转换为不同平台的格式和风格",
      "code": "┌──────────────────────────────────────────────────────────┐\n│              内容生成流水线功能架构                         │\n├──────────────────────────────────────────────────────────┤\n│  ┌─────────────┐  ┌─────────────┐  ┌──────────────┐    │\n│  │  选题策划    │  │  内容撰写    │  │  审校优化    │    │\n│  │ • 热点追踪  │  │ • 大纲生成  │  │ • 语法检查  │    │\n│  │ • 竞品分析  │  │ • 段落扩写  │  │ • SEO优化   │    │\n│  │ • 关键词挖掘│  │ • 资料整合  │  │ • 敏感词过滤│    │\n│  │ • 用户画像  │  │ • 风格定制  │  │ • 可读性评分│    │\n│  └─────────────┘  └─────────────┘  └──────────────┘    │\n│  ┌─────────────────────────────────────────────────┐     │\n│  │                  多渠道分发层                     │     │\n│  │  • 微信公众号  • 知乎  • 小红书  • 头条号         │     │\n│  │  • 抖音脚本   • SEO文章  • 邮件营销              │     │\n│  └─────────────────────────────────────────────────┘     │\n└──────────────────────────────────────────────────────────┘",
      "section_ref": "29.1.2",
      "runnable": false,
      "dependencies": []
    },
    {
      "id": "code-2",
      "language": "text",
      "description": "",
      "code": "content-pipeline/\n├── app/\n│   ├── main.py                     # FastAPI 入口\n│   ├── config.py                   # 配置管理\n│   ├── models/                     # 数据模型\n│   │   ├── topic.py                # 选题模型\n│   │   ├── article.py              # 文章模型\n│   │   └── channel.py              # 渠道配置\n│   ├── agents/                     # Agent 核心\n│   │   ├── topic_agent.py          # 选题策划 Agent\n│   │   ├── writing_agent.py        # 内容撰写 Agent\n│   │   ├── review_agent.py         # 审校优化 Agent\n│   │   └── distribute_agent.py     # 分发适配 Agent\n│   ├── services/\n│   │   ├── plagiarism_checker.py   # 原创度检测\n│   │   ├── sensitive_filter.py     # 敏感词过滤\n│   │   └── seo_optimizer.py        # SEO 优化\n│   └── utils/\n│       └── llm_client.py           # LLM 客户端\n├── tests/\n└── requirements.txt",
      "section_ref": "29.2.1",
      "runnable": false,
      "dependencies": []
    },
    {
      "id": "code-3",
      "language": "python",
      "description": "",
      "code": "# app/config.py\n\"\"\"内容生成流水线配置\"\"\"\n\nfrom pydantic_settings import BaseSettings\nfrom enum import Enum\n\n\nclass WritingStyle(str, Enum):\n    PROFESSIONAL = \"professional\"      # 专业严谨\n    CASUAL = \"casual\"                  # 轻松活泼\n    STORYTELLING = \"storytelling\"      # 故事叙述\n    TUTORIAL = \"tutorial\"              # 教程指南\n    OPINION = \"opinion\"                # 观点评论\n\n\nclass Channel(str, Enum):\n    WECHAT = \"wechat\"                  # 微信公众号\n    ZHIHU = \"zhihu\"                    # 知乎\n    XIAOHONGSHU = \"xiaohongshu\"        # 小红书\n    TOUTIAO = \"toutiao\"                # 头条号\n    DOUYIN = \"douyin\"                  # 抖音脚本\n\n\nclass Settings(BaseSettings):\n    APP_NAME: str = \"内容生成流水线\"\n    APP_VERSION: str = \"1.0.0\"\n    DEBUG: bool = False\n\n    LLM_API_KEY: str = \"\"\n    LLM_BASE_URL: str = \"https://api.openai.com/v1\"\n    LLM_MODEL: str = \"gpt-4o\"\n    LLM_MAX_TOKENS: int = 4096\n\n    # 内容参数\n    MAX_REVIEW_ITERATIONS: int = 3\n    MIN_ORIGINALITY_SCORE: float = 0.85\n    SENSITIVE_WORD_STRICT: bool = True\n\n    # 渠道字数限制\n    CHANNEL_LIMITS: dict = {\n        \"wechat\": {\"min\": 1500, \"max\": 3000},\n        \"zhihu\": {\"min\": 1000, \"max\": 5000},\n        \"xiaohongshu\": {\"min\": 300, \"max\": 1000},\n        \"toutiao\": {\"min\": 800, \"max\": 2500},\n        \"douyin\": {\"min\": 100, \"max\": 500},\n    }\n\n    class Config:\n        env_file = \".env\"\n        env_prefix = \"CP_\"\n\n\nsettings = Settings()",
      "section_ref": "29.3.1",
      "runnable": true,
      "dependencies": [
        "pydantic_settings"
      ]
    },
    {
      "id": "code-4",
      "language": "python",
      "description": "settings = Settings()",
      "code": "# app/utils/llm_client.py\n\"\"\"LLM 客户端封装（同前章，略作简化）\"\"\"\n\nimport json\nfrom typing import Optional, List, Dict\nfrom openai import OpenAI\nfrom app.config import settings\n\n\nclass LLMClient:\n    _instance: Optional['LLMClient'] = None\n\n    def __new__(cls):\n        if cls._instance is None:\n            cls._instance = super().__new__(cls)\n            cls._instance._client = OpenAI(\n                api_key=settings.LLM_API_KEY,\n                base_url=settings.LLM_BASE_URL)\n        return cls._instance\n\n    async def chat(self, messages, system_prompt=None,\n                   temperature=0.7, max_tokens=None) -> str:\n        full = []\n        if system_prompt:\n            full.append({\"role\": \"system\", \"content\": system_prompt})\n        full.extend(messages)\n        resp = self._client.chat.completions.create(\n            model=settings.LLM_MODEL, messages=full,\n            temperature=temperature,\n            max_tokens=max_tokens or settings.LLM_MAX_TOKENS)\n        return resp.choices[0].message.content\n\n    async def chat_json(self, messages, system_prompt=None) -> dict:\n        content = await self.chat(messages, system_prompt,\n                                  temperature=0.1,\n                                  response_format={\"type\": \"json_object\"})\n        return json.loads(content)\n\n\nllm_client = LLMClient()",
      "section_ref": "29.3.1",
      "runnable": true,
      "dependencies": [
        "openai",
        "app"
      ]
    },
    {
      "id": "code-5",
      "language": "python",
      "description": "",
      "code": "# app/agents/topic_agent.py\n\"\"\"选题策划 Agent\"\"\"\n\nimport json\nfrom dataclasses import dataclass, field\nfrom typing import List, Dict, Optional\nfrom datetime import datetime\nfrom app.utils.llm_client import llm_client\n\n\n@dataclass\nclass TopicSuggestion:\n    title: str\n    description: str\n    target_audience: str\n    keywords: List[str]\n    estimated_read_time: int  # 分钟\n    trending_score: float     # 热度评分 0-100\n    competition_level: str    # low/medium/high\n    suggested_channels: List[str]\n    outline: List[str]        # 建议大纲\n\n\n@dataclass\nclass TopicAnalysis:\n    suggestions: List[TopicSuggestion]\n    trending_keywords: List[str]\n    content_gaps: List[str]\n    summary: str\n\n\nclass TopicAgent:\n    SYSTEM_PROMPT = \"\"\"你是一个资深内容策划专家，擅长选题分析和内容规划。\n\n你的任务：\n1. 分析给定的领域和关键词，推荐 5 个高价值选题\n2. 为每个选题生成详细的内容大纲\n3. 评估选题的热度、竞争度和目标受众\n4. 推荐适合的分发渠道\n\n返回 JSON：\n{{\n  \"suggestions\": [\n    {{\n      \"title\": \"文章标题\",\n      \"description\": \"一句话描述\",\n      \"target_audience\": \"目标受众\",\n      \"keywords\": [\"关键词1\", \"关键词2\"],\n      \"estimated_read_time\": 8,\n      \"trending_score\": 85,\n      \"competition_level\": \"medium\",\n      \"suggested_channels\": [\"wechat\", \"zhihu\"],\n      \"outline\": [\"一、引言：...\", \"二、核心观点：...\", ...]\n    }}\n  ],\n  \"trending_keywords\": [\"趋势关键词\"],\n  \"content_gaps\": [\"竞品尚未覆盖的内容方向\"],\n  \"summary\": \"选题分析总结\"\n}}\"\"\"\n\n    async def analyze(\n        self,\n        domain: str,\n        keywords: Optional[List[str]] = None,\n        target_channels: Optional[List[str]] = None,\n        count: int = 5,\n    ) -> TopicAnalysis:\n        \"\"\"分析领域并推荐选题\"\"\"\n        kw_text = f\"，关键词：{', '.join(keywords)}\" if keywords else \"\"\n        ch_text = (f\"，目标渠道：{', '.join(target_channels)}\"\n                   if target_channels else \"\")\n\n        messages = [{\n            \"role\": \"user\",\n            \"content\": (f\"请为以下领域推荐 {count} 个选题：\\n\"\n                        f\"领域：{domain}{kw_text}{ch_text}\\n\"\n                        f\"当前日期：{datetime.now().strftime('%Y-%m-%d')}\")\n        }]\n\n        try:\n            result = await llm_client.chat_json(\n                messages=messages, system_prompt=self.SYSTEM_PROMPT)\n            suggestions = []\n            for s in result.get(\"suggestions\", []):\n                suggestions.append(TopicSuggestion(\n                    title=s.get(\"title\", \"\"),\n                    description=s.get(\"description\", \"\"),\n                    target_audience=s.get(\"target_audience\", \"\"),\n                    keywords=s.get(\"keywords\", []),\n                    estimated_read_time=s.get(\"estimated_read_time\", 5),\n                    trending_score=float(s.get(\"trending_score\", 50)),\n                    competition_level=s.get(\"competition_level\", \"medium\"),\n                    suggested_channels=s.get(\"suggested_channels\", []),\n                    outline=s.get(\"outline\", []),\n                ))\n            return TopicAnalysis(\n                suggestions=suggestions,\n                trending_keywords=result.get(\"trending_keywords\", []),\n                content_gaps=result.get(\"content_gaps\", []),\n                summary=result.get(\"summary\", \"\"),\n            )\n        except Exception as e:\n            return TopicAnalysis(\n                suggestions=[], trending_keywords=[],\n                content_gaps=[], summary=f\"分析失败: {str(e)}\")\n\n    async def refine_outline(\n        self, topic: TopicSuggestion, feedback: str,\n    ) -> TopicSuggestion:\n        \"\"\"根据反馈优化大纲\"\"\"\n        messages = [\n            {\"role\": \"user\", \"content\": f\"选题: {topic.title}\\n\"\n                                        f\"当前大纲: {topic.outline}\"},\n            {\"role\": \"user\", \"content\": f\"优化反馈: {feedback}\\n\"\n                                        \"请优化大纲结构。\"},\n        ]\n        try:\n            result = await llm_client.chat_json(\n                messages=messages, system_prompt=self.SYSTEM_PROMPT)\n            if result.get(\"suggestions\"):\n                s = result[\"suggestions\"][0]\n                topic.outline = s.get(\"outline\", topic.outline)\n                topic.description = s.get(\"description\", topic.description)\n        except Exception:\n            pass\n        return topic",
      "section_ref": "29.3.2",
      "runnable": true,
      "dependencies": [
        "app"
      ]
    },
    {
      "id": "code-6",
      "language": "python",
      "description": "",
      "code": "# app/agents/writing_agent.py\n\"\"\"内容撰写 Agent\"\"\"\n\nfrom dataclasses import dataclass, field\nfrom typing import List, Dict, Optional\nfrom app.utils.llm_client import llm_client\nfrom app.agents.topic_agent import TopicSuggestion\nfrom app.config import WritingStyle\n\n\n@dataclass\nclass ArticleDraft:\n    title: str\n    subtitle: str = \"\"\n    content: str = \"\"\n    sections: List[Dict[str, str]] = field(default_factory=list)\n    # sections: [{\"heading\": \"...\", \"body\": \"...\"}]\n    word_count: int = 0\n    style: str = \"professional\"\n    target_channel: str = \"wechat\"\n    references: List[str] = field(default_factory=list)\n\n\nSTYLE_GUIDES = {\n    WritingStyle.PROFESSIONAL: {\n        \"tone\": \"专业、严谨、有深度\",\n        \"structure\": \"提出问题→分析原因→给出方案→总结展望\",\n        \"avoid\": [\"口语化表达\", \"过度感叹号\", \"网络流行语\"],\n    },\n    WritingStyle.CASUAL: {\n        \"tone\": \"轻松、有趣、接地气\",\n        \"structure\": \"吸引眼球开头→轻松展开→互动结尾\",\n        \"avoid\": [\"过于学术的术语\", \"长篇大论\", \"干巴巴的数据\"],\n    },\n    WritingStyle.STORYTELLING: {\n        \"tone\": \"故事化、场景化、有画面感\",\n        \"structure\": \"场景引入→人物/事件→冲突→解决→升华\",\n        \"avoid\": [\"空洞说教\", \"枯燥理论\", \"没有场景\"],\n    },\n    WritingStyle.TUTORIAL: {\n        \"tone\": \"清晰、步骤化、实用\",\n        \"structure\": \"需求场景→环境准备→步骤详解→常见问题\",\n        \"avoid\": [\"模糊描述\", \"跳过关键步骤\", \"缺少代码示例\"],\n    },\n    WritingStyle.OPINION: {\n        \"tone\": \"犀利、有观点、引发思考\",\n        \"structure\": \"热点切入→核心观点→论证→对立观点→结论\",\n        \"avoid\": [\"模棱两可\", \"人云亦云\", \"没有数据支撑\"],\n    },\n}\n\n\nclass WritingAgent:\n    def __init__(self):\n        pass\n\n    async def write(\n        self,\n        topic: TopicSuggestion,\n        style: WritingStyle = WritingStyle.PROFESSIONAL,\n        target_channel: str = \"wechat\",\n        reference_materials: Optional[List[str]] = None,\n        max_words: int = 2500,\n    ) -> ArticleDraft:\n        \"\"\"根据选题和大纲撰写内容\"\"\"\n        style_guide = STYLE_GUIDES[style]\n\n        system_prompt = f\"\"\"你是一个专业的内容创作者。\n\n写作风格要求：\n- 语气：{style_guide['tone']}\n- 结构：{style_guide['structure']}\n- 避免：{', '.join(style_guide['avoid'])}\n- 目标平台：{target_channel}\n\n写作规范：\n1. 每个段落控制在 100-200 字\n2. 使用小标题分割内容模块\n3. 适当使用数据、案例、引用增强说服力\n4. 结尾要有行动号召或互动引导\n5. 目标字数：{max_words}字左右\n\n返回 JSON：\n{{\n  \"title\": \"最终标题\",\n  \"subtitle\": \"副标题\",\n  \"sections\": [\n    {{\"heading\": \"小标题\", \"body\": \"正文内容\"}},\n    ...\n  ],\n  \"references\": [\"引用的资料来源\"]\n}}\"\"\"\n\n        # 构建参考资料\n        ref_text = \"\"\n        if reference_materials:\n            ref_text = \"\\n\\n参考资料：\\n\" + \"\\n---\\n\".join(reference_materials)\n\n        messages = [{\n            \"role\": \"user\",\n            \"content\": (f\"请撰写以下文章：\\n\\n\"\n                        f\"选题：{topic.title}\\n\"\n                        f\"描述：{topic.description}\\n\"\n                        f\"目标受众：{topic.target_audience}\\n\"\n                        f\"关键词：{', '.join(topic.keywords)}\\n\"\n                        f\"大纲：\\n\" +\n                        \"\\n\".join(f\"  {i+1}. {item}\"\n                                 for i, item in enumerate(topic.outline)) +\n                        ref_text)\n        }]\n\n        try:\n            import json\n            result = await llm_client.chat_json(\n                messages=messages, system_prompt=system_prompt,\n                temperature=0.7, max_tokens=4096)\n\n            sections = []\n            for s in result.get(\"sections\", []):\n                sections.append({\n                    \"heading\": s.get(\"heading\", \"\"),\n                    \"body\": s.get(\"body\", \"\"),\n                })\n\n            full_content = \"\\n\\n\".join(\n                f\"## {s['heading']}\\n\\n{s['body']}\" if s['heading']\n                else s['body']\n                for s in sections\n            )\n            word_count = len(full_content)\n\n            return ArticleDraft(\n                title=result.get(\"title\", topic.title),\n                subtitle=result.get(\"subtitle\", \"\"),\n                content=full_content,\n                sections=sections,\n                word_count=word_count,\n                style=style.value,\n                target_channel=target_channel,\n                references=result.get(\"references\", []),\n            )\n        except Exception as e:\n            return ArticleDraft(\n                title=topic.title, content=f\"撰写失败: {str(e)}\")\n\n    async def rewrite_section(\n        self, section: Dict[str, str], feedback: str,\n        style: WritingStyle = WritingStyle.PROFESSIONAL,\n    ) -> Dict[str, str]:\n        \"\"\"根据审校反馈重写某个段落\"\"\"\n        style_guide = STYLE_GUIDES[style]\n        system_prompt = f\"\"\"你是一个内容编辑，根据反馈优化段落。\n风格要求：{style_guide['tone']}\n\n返回 JSON：{{\"heading\": \"...\", \"body\": \"...\"}}\"\"\"\n\n        messages = [\n            {\"role\": \"user\", \"content\": f\"原段落标题: {section['heading']}\\n\"\n                                        f\"原段落内容: {section['body']}\"},\n            {\"role\": \"user\", \"content\": f\"修改意见: {feedback}\"},\n        ]\n\n        try:\n            import json\n            result = await llm_client.chat_json(\n                messages=messages, system_prompt=system_prompt,\n                temperature=0.7)\n            return {\n                \"heading\": result.get(\"heading\", section[\"heading\"]),\n                \"body\": result.get(\"body\", section[\"body\"]),\n            }\n        except Exception:\n            return section",
      "section_ref": "29.3.3",
      "runnable": true,
      "dependencies": [
        "app"
      ]
    },
    {
      "id": "code-7",
      "language": "python",
      "description": "",
      "code": "# app/agents/review_agent.py\n\"\"\"审校优化 Agent\"\"\"\n\nimport re\nfrom dataclasses import dataclass, field\nfrom typing import List, Dict, Optional, Tuple\nfrom app.utils.llm_client import llm_client\nfrom app.agents.writing_agent import ArticleDraft\n\n\n@dataclass\nclass ReviewIssue:\n    section_index: int\n    issue_type: str  # grammar/style/seo/sensitive/readability\n    severity: str    # info/warning/error\n    description: str\n    suggestion: str\n    auto_fixable: bool = False\n\n\n@dataclass\nclass ReviewResult:\n    overall_score: float  # 0-100\n    issues: List[ReviewIssue]\n    readability_score: float\n    seo_score: float\n    sensitivity_check: Dict[str, List[str]]  # {\"pass\": [], \"fail\": []}\n    word_count: int\n    keyword_density: Dict[str, float]\n    summary: str\n    needs_rewrite: bool = False\n\n\nclass ReviewAgent:\n    \"\"\"多维度内容审校\"\"\"\n\n    # 基础敏感词库（生产环境应使用专业词库）\n    SENSITIVE_WORDS = [\n        \"最\", \"第一\", \"首个\", \"独家\", \"国家级\", \"世界级\",\n        \"100%\", \"绝对\", \"保证\", \"包治\", \"根治\",\n    ]\n\n    def review(\n        self,\n        draft: ArticleDraft,\n        keywords: Optional[List[str]] = None,\n    ) -> ReviewResult:\n        \"\"\"执行多维度审校\"\"\"\n        issues = []\n\n        # 1. 敏感词检查\n        sensitivity = self._check_sensitive(draft.content)\n        for word in sensitivity[\"fail\"]:\n            issues.append(ReviewIssue(\n                section_index=-1, issue_type=\"sensitive\",\n                severity=\"error\",\n                description=f\"发现敏感词: {word}\",\n                suggestion=f\"替换或删除「{word}」\",\n                auto_fixable=False,\n            ))\n\n        # 2. 可读性评分\n        readability = self._score_readability(draft.content)\n\n        # 3. SEO 评分\n        seo_score, keyword_density = self._score_seo(\n            draft.content, draft.title, keywords or [])\n\n        # 4. LLM 深度审校（语法、风格、逻辑）\n        llm_issues = self._llm_review(draft)\n        issues.extend(llm_issues)\n\n        # 5. 字数检查\n        channel_limits = settings.CHANNEL_LIMITS.get(\n            draft.target_channel, {\"min\": 500, \"max\": 5000})\n        if draft.word_count < channel_limits[\"min\"]:\n            issues.append(ReviewIssue(\n                section_index=-1, issue_type=\"readability\",\n                severity=\"warning\",\n                description=f\"字数不足: {draft.word_count}字，\"\n                            f\"建议最少{channel_limits['min']}字\",\n                suggestion=\"增加更多论据和案例\",\n            ))\n        if draft.word_count > channel_limits[\"max\"]:\n            issues.append(ReviewIssue(\n                section_index=-1, issue_type=\"readability\",\n                severity=\"warning\",\n                description=f\"字数超标: {draft.word_count}字，\"\n                            f\"建议最多{channel_limits['max']}字\",\n                suggestion=\"精简内容，删减冗余段落\",\n            ))\n\n        # 计算总分\n        overall = self._calculate_overall(\n            readability, seo_score,\n            len(sensitivity[\"fail\"]) == 0,\n            len([i for i in issues if i.severity == \"error\"]) == 0)\n\n        needs_rewrite = any(i.severity == \"error\" for i in issues)\n\n        return ReviewResult(\n            overall_score=overall,\n            issues=issues,\n            readability_score=readability,\n            seo_score=seo_score,\n            sensitivity_check=sensitivity,\n            word_count=draft.word_count,\n            keyword_density=keyword_density,\n            summary=self._generate_summary(issues, overall),\n            needs_rewrite=needs_rewrite,\n        )\n\n    def _check_sensitive(self, text: str) -> Dict[str, List[str]]:\n        \"\"\"敏感词检查\"\"\"\n        found_pass = []\n        found_fail = []\n        for word in self.SENSITIVE_WORDS:\n            if word in text:\n                # 广告法极限词需要严格处理\n                if word in [\"最\", \"第一\", \"首个\", \"100%\", \"绝对\", \"国家级\"]:\n                    found_fail.append(word)\n                else:\n                    found_pass.append(word)\n        return {\"pass\": found_pass, \"fail\": found_fail}\n\n    def _score_readability(self, text: str) -> float:\n        \"\"\"可读性评分（0-100）\"\"\"\n        score = 70.0  # 基础分\n\n        # 段落长度适中（100-300字最佳）\n        paragraphs = [p.strip() for p in text.split(\"\\n\\n\") if p.strip()]\n        for p in paragraphs:\n            length = len(p)\n            if 100 <= length <= 300:\n                score += 2\n            elif length > 500:\n                score -= 5\n\n        # 句子长度适中（10-30字最佳）\n        sentences = re.split(r'[。！？；]', text)\n        avg_len = sum(len(s) for s in sentences) / max(len(sentences), 1)\n        if 10 <= avg_len <= 30:\n            score += 10\n        elif avg_len > 50:\n            score -= 10\n\n        # 小标题分布\n        headings = re.findall(r'^##?\\s+.+$', text, re.MULTILINE)\n        if len(headings) >= 3:\n            score += 5\n\n        # 互动元素（提问、感叹）\n        if re.search(r'[？?!]', text):\n            score += 3\n\n        return min(max(score, 0), 100)\n\n    def _score_seo(\n        self, text: str, title: str, keywords: List[str],\n    ) -> Tuple[float, Dict[str, float]]:\n        \"\"\"SEO 评分\"\"\"\n        score = 60.0\n        density = {}\n\n        if not keywords:\n            return score, density\n\n        total_words = len(text)\n        for kw in keywords:\n            count = text.count(kw)\n            d = count / max(total_words, 1) * 100\n            density[kw] = round(d, 2)\n            # 理想关键词密度 1%-3%\n            if 1.0 <= d <= 3.0:\n                score += 8\n            elif d > 5.0:\n                score -= 5  # 关键词堆砌扣分\n            # 标题包含关键词\n            if kw in title:\n                score += 5\n\n        return min(max(score, 0), 100), density\n\n    def _llm_review(self, draft: ArticleDraft) -> List[ReviewIssue]:\n        \"\"\"LLM 深度审校\"\"\"\n        # 逐段审校（限制段落数避免 Token 过多）\n        issues = []\n        for i, section in enumerate(draft.sections[:5]):\n            try:\n                import json\n                result = self._review_section(section)\n                issues.append(ReviewIssue(\n                    section_index=i,\n                    issue_type=result.get(\"type\", \"style\"),\n                    severity=result.get(\"severity\", \"info\"),\n                    description=result.get(\"description\", \"\"),\n                    suggestion=result.get(\"suggestion\", \"\"),\n                    auto_fixable=result.get(\"auto_fixable\", False),\n                ))\n            except Exception:\n                pass\n        return issues\n\n    def _review_section(self, section: Dict) -> Dict:\n        \"\"\"审校单个段落\"\"\"\n        import json\n        import asyncio\n        system_prompt = \"\"\"审校以下段落，检查：\n1. 语法错误\n2. 逻辑不通顺\n3. 表达不清晰\n4. 事实性错误风险\n\n返回 JSON：\n{\"type\": \"grammar|style|logic\", \"severity\": \"info|warning|error\",\n \"description\": \"问题描述\", \"suggestion\": \"修改建议\",\n \"auto_fixable\": false}\n如果没有问题返回：\n{\"type\": \"pass\", \"severity\": \"info\",\n \"description\": \"无问题\", \"suggestion\": \"\", \"auto_fixable\": false}\"\"\"\n\n        # 简化处理，实际可用 async\n        result = llm_client._client.chat.completions.create(\n            model=settings.LLM_MODEL,\n            messages=[\n                {\"role\": \"system\", \"content\": system_prompt},\n                {\"role\": \"user\",\n                 \"content\": f\"标题: {section['heading']}\\n\"\n                            f\"内容: {section['body'][:500]}\"},\n            ],\n            temperature=0.1,\n            max_tokens=500,\n        )\n        return json.loads(result.choices[0].message.content)\n\n    def _calculate_overall(\n        self, readability, seo, sensitive_pass, no_errors,\n    ) -> float:\n        \"\"\"计算综合评分\"\"\"\n        score = readability * 0.3 + seo * 0.3 + 40\n        if not sensitive_pass:\n            score -= 20\n        if not no_errors:\n            score -= 10\n        return min(max(round(score, 1), 0), 100)\n\n    def _generate_summary(\n        self, issues: List[ReviewIssue], score: float,\n    ) -> str:\n        errors = [i for i in issues if i.severity == \"error\"]\n        warnings = [i for i in issues if i.severity == \"warning\"]\n        parts = [f\"综合评分: {score}/100\"]\n        if errors:\n            parts.append(f\"❌ {len(errors)} 个严重问题需要修改\")\n        if warnings:\n            parts.append(f\"⚠️ {len(warnings)} 个建议优化项\")\n        if not errors and not warnings:\n            parts.append(\"✅ 审校通过，质量良好\")\n        return \"；\".join(parts)",
      "section_ref": "29.3.4",
      "runnable": true,
      "dependencies": [
        "app"
      ]
    },
    {
      "id": "code-8",
      "language": "python",
      "description": "",
      "code": "# app/agents/distribute_agent.py\n\"\"\"多渠道分发适配 Agent\"\"\"\n\nfrom dataclasses import dataclass, field\nfrom typing import List, Dict, Optional\nfrom app.utils.llm_client import llm_client\nfrom app.agents.writing_agent import ArticleDraft\nfrom app.config import Channel\n\n\n@dataclass\nclass ChannelContent:\n    channel: str\n    title: str\n    content: str\n    tags: List[str]\n    word_count: int\n    adaptation_notes: str\n\n\nCHANNEL_STYLES = {\n    Channel.WECHAT: {\n        \"name\": \"微信公众号\",\n        \"format\": \"Markdown → 排版\",\n        \"title_style\": \"吸引眼球但不过标题党，20字以内\",\n        \"content_style\": \"分段清晰、图文并茂、引用用引用框\",\n        \"tone\": \"专业但有温度\",\n        \"max_length\": 3000,\n        \"features\": [\"文末引导关注\", \"阅读原文链接\", \"分享提示\"],\n    },\n    Channel.ZHIHU: {\n        \"name\": \"知乎\",\n        \"format\": \"Markdown\",\n        \"title_style\": \"提问式或观点式，体现专业性\",\n        \"content_style\": \"逻辑严密、数据支撑、引用来源\",\n        \"tone\": \"理性、深度、有干货\",\n        \"max_length\": 5000,\n        \"features\": [\"开头直击痛点\", \"结构化论证\", \"评论区互动\"],\n    },\n    Channel.XIAOHONGSHU: {\n        \"name\": \"小红书\",\n        \"format\": \"短文 + Emoji\",\n        \"title_style\": \"20字以内，含关键词和Emoji\",\n        \"content_style\": \"要点式、多用Emoji、话题标签\",\n        \"tone\": \"种草感、闺蜜式分享\",\n        \"max_length\": 1000,\n        \"features\": [\"吸睛首图\", \"话题标签 #\", \"互动引导\"],\n    },\n    Channel.TOUTIAO: {\n        \"name\": \"头条号\",\n        \"format\": \"富文本\",\n        \"title_style\": \"信息量大、含数字和关键词\",\n        \"content_style\": \"新闻式开头、结构化内容\",\n        \"tone\": \"客观、信息量大\",\n        \"max_length\": 2500,\n        \"features\": [\"三段式标题\", \"摘要精炼\", \"标签分类\"],\n    },\n    Channel.DOUYIN: {\n        \"name\": \"抖音脚本\",\n        \"format\": \"视频脚本\",\n        \"title_style\": \"口语化、有悬念\",\n        \"content_style\": \"口语化表达、场景描述、时间标注\",\n        \"tone\": \"口语化、快节奏、有画面感\",\n        \"max_length\": 500,\n        \"features\": [\"前3秒吸引\", \"节奏标注\", \"BGM建议\"],\n    },\n}\n\n\nclass DistributeAgent:\n    SYSTEM_PROMPT = \"\"\"你是一个内容分发专家，擅长将一篇文章适配为不同平台的风格。\n\n规则：\n1. 保留核心观点和信息\n2. 调整标题、结构和语气以匹配目标平台\n3. 添加平台特有的元素（话题标签、引导语等）\n4. 控制字数在目标范围内\n\n返回 JSON：\n{{\n  \"title\": \"适配后的标题\",\n  \"content\": \"适配后的正文\",\n  \"tags\": [\"标签1\", \"标签2\"],\n  \"adaptation_notes\": \"适配说明\"\n}}\"\"\"\n\n    async def adapt(\n        self, draft: ArticleDraft, channels: List[str],\n    ) -> List[ChannelContent]:\n        \"\"\"并行适配多渠道内容\"\"\"\n        import asyncio\n        tasks = [\n            self._adapt_single(draft, ch) for ch in channels\n        ]\n        results = await asyncio.gather(*tasks, return_exceptions=True)\n        return [r for r in results if isinstance(r, ChannelContent)]\n\n    async def _adapt_single(\n        self, draft: ArticleDraft, channel: str,\n    ) -> ChannelContent:\n        \"\"\"适配单个渠道\"\"\"\n        style = CHANNEL_STYLES.get(Channel(channel), CHANNEL_STYLES[Channel.WECHAT])\n\n        messages = [{\n            \"role\": \"user\",\n            \"content\": (f\"请将以下文章适配为【{style['name']}】平台：\\n\\n\"\n                        f\"原标题: {draft.title}\\n\"\n                        f\"原内容: {draft.content[:2000]}\\n\\n\"\n                        f\"要求:\\n\"\n                        f\"- 标题风格: {style['title_style']}\\n\"\n                        f\"- 内容风格: {style['content_style']}\\n\"\n                        f\"- 语气: {style['tone']}\\n\"\n                        f\"- 最大字数: {style['max_length']}\\n\"\n                        f\"- 特色: {', '.join(style['features'])}\")\n        }]\n\n        try:\n            import json\n            result = await llm_client.chat_json(\n                messages=messages, system_prompt=self.SYSTEM_PROMPT,\n                temperature=0.6)\n            content = result.get(\"content\", \"\")\n            return ChannelContent(\n                channel=channel,\n                title=result.get(\"title\", draft.title),\n                content=content,\n                tags=result.get(\"tags\", []),\n                word_count=len(content),\n                adaptation_notes=result.get(\"adaptation_notes\", \"\"),\n            )\n        except Exception as e:\n            return ChannelContent(\n                channel=channel, title=draft.title,\n                content=draft.content, tags=[],\n                word_count=draft.word_count,\n                adaptation_notes=f\"适配失败: {str(e)}\")",
      "section_ref": "29.3.5",
      "runnable": true,
      "dependencies": [
        "app"
      ]
    },
    {
      "id": "code-9",
      "language": "python",
      "description": "",
      "code": "# app/services/sensitive_filter.py\n\"\"\"敏感词过滤服务\"\"\"\n\nimport re\nfrom dataclasses import dataclass, field\nfrom typing import List, Dict, Tuple\n\n\n# 广告法极限词（基础版）\nAD_LIMIT_WORDS = [\n    \"最\", \"最佳\", \"最好\", \"最优\", \"最强\", \"最先进\",\n    \"第一\", \"首个\", \"首款\", \"唯一\", \"独一无二\",\n    \"国家级\", \"世界级\", \"顶级\", \"极致\", \"绝无仅有\",\n    \"100%\", \"绝对\", \"保证治愈\", \"包治百病\", \"根治\",\n    \"永久\", \"万能\", \"特效\", \"高效\", \"超效\",\n]\n\n\n# 政治敏感词（基础版，生产环境需要完整词库）\nPOLITICAL_WORDS = [\n    # 生产环境请使用专业敏感词库\n]\n\n\n@dataclass\nclass FilterResult:\n    is_clean: bool\n    found_words: List[Tuple[str, int]]  # (word, position)\n    suggestions: List[str]\n    cleaned_text: str = \"\"\n\n\nclass SensitiveFilter:\n    def __init__(self, extra_words: List[str] = None):\n        self._words = set(AD_LIMIT_WORDS + POLITICAL_WORDS)\n        if extra_words:\n            self._words.update(extra_words)\n        # 按长度降序排列，优先匹配长词\n        self._sorted_words = sorted(self._words, key=len, reverse=True)\n\n    def check(self, text: str) -> FilterResult:\n        \"\"\"检查文本中的敏感词\"\"\"\n        found = []\n        for word in self._sorted_words:\n            idx = 0\n            while True:\n                pos = text.find(word, idx)\n                if pos == -1:\n                    break\n                found.append((word, pos))\n                idx = pos + len(word)\n\n        # 生成替换建议\n        suggestions = []\n        replacements = {\n            \"最\": \"非常\", \"最佳\": \"优秀\", \"第一\": \"领先\",\n            \"100%\": \"极高\", \"绝对\": \"非常\",\n        }\n        for word, pos in found:\n            replacement = replacements.get(word, f\"**{word[0]}{'*'*(len(word)-1)}\")\n            suggestions.append(f\"位置{pos}: 「{word}」→ 建议替换为「{replacement}」\")\n\n        # 自动替换\n        cleaned = text\n        for word, _ in found:\n            replacement = replacements.get(word, \"\")\n            if replacement:\n                cleaned = cleaned.replace(word, replacement)\n\n        return FilterResult(\n            is_clean=len(found) == 0,\n            found_words=found,\n            suggestions=suggestions,\n            cleaned_text=cleaned if found else text,\n        )\n\n    def check_title(self, title: str) -> FilterResult:\n        \"\"\"标题敏感词检查（更严格）\"\"\"\n        return self.check(title)",
      "section_ref": "29.3.6",
      "runnable": true,
      "dependencies": []
    },
    {
      "id": "code-10",
      "language": "python",
      "description": "",
      "code": "# app/main.py\n\"\"\"内容生成流水线 - FastAPI 入口\"\"\"\n\nfrom contextlib import asynccontextmanager\nfrom fastapi import FastAPI, HTTPException\nfrom fastapi.middleware.cors import CORSMiddleware\nfrom pydantic import BaseModel\nfrom typing import Optional, List\n\nfrom app.config import settings, WritingStyle\nfrom app.agents.topic_agent import TopicAgent\nfrom app.agents.writing_agent import WritingAgent\nfrom app.agents.review_agent import ReviewAgent\nfrom app.agents.distribute_agent import DistributeAgent\nfrom app.services.sensitive_filter import SensitiveFilter\n\ntopic_agent = TopicAgent()\nwriting_agent = WritingAgent()\nreview_agent = ReviewAgent()\ndistribute_agent = DistributeAgent()\nsensitive_filter = SensitiveFilter()\n\n\n@asynccontextmanager\nasync def lifespan(app: FastAPI):\n    print(f\"🚀 {settings.APP_NAME} v{settings.APP_VERSION} 启动\")\n    yield\n\n\napp = FastAPI(\n    title=settings.APP_NAME, version=settings.APP_VERSION,\n    lifespan=lifespan,\n    description=\"AI 驱动的内容生成流水线\")\napp.add_middleware(CORSMiddleware, allow_origins=[\"*\"],\n                   allow_credentials=True,\n                   allow_methods=[\"*\"], allow_headers=[\"*\"])\n\n\nclass TopicRequest(BaseModel):\n    domain: str\n    keywords: Optional[List[str]] = None\n    channels: Optional[List[str]] = None\n    count: int = 5\n\n\nclass WriteRequest(BaseModel):\n    topic_title: str\n    topic_description: str\n    topic_keywords: List[str]\n    outline: List[str]\n    target_audience: str = \"通用\"\n    style: str = \"professional\"\n    channel: str = \"wechat\"\n    max_words: int = 2500\n\n\nclass PipelineRequest(BaseModel):\n    domain: str\n    keywords: Optional[List[str]] = None\n    style: str = \"professional\"\n    channels: List[str] = [\"wechat\", \"zhihu\"]\n    max_words: int = 2500\n\n\n@app.get(\"/health\")\nasync def health():\n    return {\"status\": \"ok\", \"version\": settings.APP_VERSION}\n\n\n@app.post(\"/api/v1/topics\")\nasync def analyze_topics(req: TopicRequest):\n    \"\"\"分析领域并推荐选题\"\"\"\n    result = await topic_agent.analyze(\n        domain=req.domain,\n        keywords=req.keywords,\n        target_channels=req.channels,\n        count=req.count,\n    )\n    return {\n        \"suggestions\": [\n            {\"title\": s.title, \"description\": s.description,\n             \"trending_score\": s.trending_score,\n             \"competition_level\": s.competition_level,\n             \"keywords\": s.keywords,\n             \"suggested_channels\": s.suggested_channels,\n             \"outline\": s.outline}\n            for s in result.suggestions\n        ],\n        \"trending_keywords\": result.trending_keywords,\n        \"content_gaps\": result.content_gaps,\n        \"summary\": result.summary,\n    }\n\n\n@app.post(\"/api/v1/write\")\nasync def write_article(req: WriteRequest):\n    \"\"\"根据选题撰写文章\"\"\"\n    from app.agents.topic_agent import TopicSuggestion\n    topic = TopicSuggestion(\n        title=req.topic_title,\n        description=req.topic_description,\n        target_audience=req.target_audience,\n        keywords=req.topic_keywords,\n        outline=req.outline,\n    )\n    style = WritingStyle(req.style)\n    draft = await writing_agent.write(\n        topic=topic, style=style,\n        target_channel=req.channel,\n        max_words=req.max_words,\n    )\n    return {\n        \"title\": draft.title,\n        \"subtitle\": draft.subtitle,\n        \"content\": draft.content,\n        \"sections\": draft.sections,\n        \"word_count\": draft.word_count,\n        \"style\": draft.style,\n    }\n\n\n@app.post(\"/api/v1/review\")\nasync def review_article(\n    content: str,\n    title: str = \"\",\n    keywords: Optional[List[str]] = None,\n):\n    \"\"\"审校文章\"\"\"\n    draft = ArticleDraft(title=title, content=content,\n                         word_count=len(content))\n    result = review_agent.review(draft, keywords)\n    return {\n        \"overall_score\": result.overall_score,\n        \"readability_score\": result.readability_score,\n        \"seo_score\": result.seo_score,\n        \"needs_rewrite\": result.needs_rewrite,\n        \"issues\": [\n            {\"section\": i.issue_type,\n             \"severity\": i.severity,\n             \"description\": i.description,\n             \"suggestion\": i.suggestion}\n            for i in result.issues\n        ],\n        \"sensitive_check\": result.sensitivity_check,\n        \"summary\": result.summary,\n    }\n\n\n@app.post(\"/api/v1/pipeline\")\nasync def run_pipeline(req: PipelineRequest):\n    \"\"\"完整流水线：选题 → 撰写 → 审校 → 分发\"\"\"\n    # 1. 选题分析\n    topic_result = await topic_agent.analyze(\n        domain=req.domain,\n        keywords=req.keywords,\n        target_channels=req.channels,\n        count=1,\n    )\n    if not topic_result.suggestions:\n        raise HTTPException(400, detail=\"未能生成合适选题\")\n\n    topic = topic_result.suggestions[0]\n\n    # 2. 撰写初稿\n    style = WritingStyle(req.style)\n    draft = await writing_agent.write(\n        topic=topic, style=style,\n        target_channel=req.channels[0],\n        max_words=req.max_words,\n    )\n\n    # 3. 审校\n    review = review_agent.review(draft, topic.keywords)\n\n    # 4. 迭代修改（如有严重问题）\n    if review.needs_rewrite and settings.MAX_REVIEW_ITERATIONS > 0:\n        errors = [i for i in review.issues if i.severity == \"error\"]\n        for issue in errors[:2]:  # 最多修2个问题\n            if 0 <= issue.section_index < len(draft.sections):\n                draft.sections[issue.section_index] = \\\n                    await writing_agent.rewrite_section(\n                        draft.sections[issue.section_index],\n                        issue.suggestion, style)\n\n    # 5. 多渠道适配\n    adapted = await distribute_agent.adapt(draft, req.channels)\n\n    return {\n        \"topic\": {\n            \"title\": topic.title,\n            \"description\": topic.description,\n            \"trending_score\": topic.trending_score,\n        },\n        \"draft\": {\n            \"title\": draft.title,\n            \"content\": draft.content[:500] + \"...\",\n            \"word_count\": draft.word_count,\n        },\n        \"review\": {\n            \"overall_score\": review.overall_score,\n            \"readability\": review.readability_score,\n            \"seo\": review.seo_score,\n            \"summary\": review.summary,\n        },\n        \"distributed\": [\n            {\"channel\": a.channel, \"title\": a.title,\n             \"word_count\": a.word_count, \"tags\": a.tags}\n            for a in adapted\n        ],\n    }\n\n\nif __name__ == \"__main__\":\n    import uvicorn\n    uvicorn.run(\"app.main:app\", host=\"0.0.0.0\", port=8000,\n                reload=settings.DEBUG)",
      "section_ref": "29.3.7",
      "runnable": true,
      "dependencies": [
        "contextlib",
        "fastapi",
        "pydantic",
        "app"
      ]
    },
    {
      "id": "code-11",
      "language": "python",
      "description": "",
      "code": "# tests/test_topic.py\n\"\"\"选题分析测试\"\"\"\n\nimport pytest\nfrom app.agents.topic_agent import TopicAgent\n\n\n@pytest.mark.asyncio\nasync def test_topic_analysis():\n    agent = TopicAgent()\n    # 需要配置 LLM API_KEY\n    # result = await agent.analyze(\"AI技术\", [\"大模型\", \"Agent\"])\n    # assert len(result.suggestions) > 0\n    # assert result.suggestions[0].title\n    pass",
      "section_ref": "29.4.1",
      "runnable": true,
      "dependencies": [
        "pytest",
        "app"
      ]
    },
    {
      "id": "code-12",
      "language": "python",
      "description": "",
      "code": "# tests/test_sensitive.py\n\"\"\"敏感词过滤测试\"\"\"\n\nfrom app.services.sensitive_filter import SensitiveFilter\n\n\ndef test_ad_limit_words():\n    sf = SensitiveFilter()\n    text = \"这是全国最好用的产品，100%有效，绝对保证\"\n    result = sf.check(text)\n    assert not result.is_clean\n    assert len(result.found_words) >= 3\n\n\ndef test_clean_text():\n    sf = SensitiveFilter()\n    text = \"这是一篇关于技术分享的文章\"\n    result = sf.check(text)\n    assert result.is_clean\n    assert len(result.found_words) == 0\n\n\ndef test_title_check():\n    sf = SensitiveFilter()\n    title = \"全球第一款AI写作工具\"\n    result = sf.check_title(title)\n    assert not result.is_clean",
      "section_ref": "29.4.2",
      "runnable": true,
      "dependencies": [
        "app"
      ]
    },
    {
      "id": "code-13",
      "language": "python",
      "description": "",
      "code": "# tests/test_readability.py\n\"\"\"可读性评分测试\"\"\"\n\nfrom app.agents.review_agent import ReviewAgent\nfrom app.agents.writing_agent import ArticleDraft\n\n\ndef test_good_readability():\n    agent = ReviewAgent()\n    text = (\"## 引言\\n\\nAI技术的发展日新月异。\\n\\n\"\n            \"你知道Agent是什么吗？它可能改变我们的工作方式。\\n\\n\"\n            \"## 核心观点\\n\\n首先，Agent具备自主决策能力。\\n\"\n            \"其次，Agent可以调用外部工具。\\n\"\n            \"最后，Agent能够从反馈中学习。\")\n    draft = ArticleDraft(title=\"test\", content=text, word_count=len(text))\n    score = agent._score_readability(text)\n    assert score > 60\n\n\ndef test_long_paragraph_penalty():\n    agent = ReviewAgent()\n    text = \"这是一段非常长的文字\" * 100\n    score = agent._score_readability(text)\n    assert score < 70",
      "section_ref": "29.4.3",
      "runnable": true,
      "dependencies": [
        "app"
      ]
    },
    {
      "id": "code-14",
      "language": "dockerfile",
      "description": "---",
      "code": "FROM python:3.11-slim\nWORKDIR /app\nCOPY requirements.txt .\nRUN pip install --no-cache-dir -r requirements.txt\nCOPY app/ ./app/\nEXPOSE 8000\nCMD [\"uvicorn\", \"app.main:app\", \"--host\", \"0.0.0.0\", \"--port\", \"8000\"]",
      "section_ref": "29.5",
      "runnable": false,
      "dependencies": []
    },
    {
      "id": "code-15",
      "language": "yaml",
      "description": "CMD [\"uvicorn\", \"app.main:app\", \"--host\", \"0.0.0.0\", \"--port\", \"8000\"]",
      "code": "# docker-compose.yml\nversion: '3.8'\nservices:\n  content-pipeline:\n    build: .\n    ports: [\"8000:8000\"]\n    environment:\n      - CP_LLM_API_KEY=${LLM_API_KEY}",
      "section_ref": "29.5",
      "runnable": false,
      "dependencies": []
    }
  ],
  "tables": [
    {
      "headers": [
        "维度",
        "指标"
      ],
      "data": [
        [
          "单篇文章生成时间",
          "< 30 秒（初稿）"
        ],
        [
          "内容原创度",
          "> 85%（相似度检测）"
        ],
        [
          "敏感词准确率",
          "> 95%"
        ],
        [
          "并发支持",
          "50 QPS"
        ],
        [
          "支持渠道",
          "微信公众号、知乎、小红书、头条号、抖音"
        ]
      ]
    },
    {
      "headers": [
        "模式",
        "应用场景",
        "效果"
      ],
      "data": [
        [
          "Style Persona",
          "内容风格控制",
          "AI味降低 80%"
        ],
        [
          "规则 + LLM 混合审校",
          "内容质量控制",
          "成本降低 60%"
        ],
        [
          "Pipeline + 反馈循环",
          "内容质量迭代",
          "一次通过率 70%→90%"
        ],
        [
          "渠道模板隔离",
          "多渠道适配",
          "各平台质量达标率 95%"
        ]
      ]
    }
  ],
  "key_takeaways": [],
  "common_pitfalls": [],
  "related_chapters": [
    "ch18"
  ]
}