主题
LangChain 教程 35|项目实战:自动化内容创作系统
📖 本篇导读:这是 LangChain 系列教程的第 35 篇。本篇将构建一个智能内容创作平台,支持素材收集、多平台适配、SEO 优化、质量检查和一键发布。读完预计需要 20 分钟。
项目概述
简单来说
构建一个智能内容创作平台,用户只需输入一个主题或关键词,系统就能自动:
- 收集相关素材和资料
- 生成高质量的原创文章
- 针对不同平台(微信公众号、知乎、小红书、头条等)进行适配
- 自动优化 SEO 关键词
- 进行质量检查和原创性检测
- 一键发布到多个平台
核心功能
| 功能 | 描述 |
|---|---|
| 智能素材收集 | 自动搜索相关资料、热点话题、竞品文章 |
| 多平台适配 | 针对不同平台的风格和规范自动调整内容 |
| SEO 优化 | 自动提取关键词、优化标题、生成摘要 |
| 质量检查 | 原创性检测、事实核查、语法纠错 |
| 批量生产 | 支持批量生成系列文章 |
| 版本管理 | 保存修改历史,支持回滚 |

技术亮点
┌─────────────────────────────────────────────────────────────────┐
│ 自动化内容创作系统技术架构 │
├─────────────────────────────────────────────────────────────────┤
│ 前端:React 18 + TypeScript + Ant Design + Zustand │
│ 后端:Express + Prisma + MySQL + Redis │
│ AI:LangChain 1.x + Subagents + 中间件 + 结构化输出 │
│ 外部API:搜索引擎 + 各平台 Open API │
└─────────────────────────────────────────────────────────────────┘一、系统架构
1.1 整体架构图
┌─────────────────────────────────────────────────────────────────────────────┐
│ 前端层 │
│ ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐ │
│ │ 创作台 │ │ 素材库 │ │ 文章管理 │ │ 发布中心 │ │ 数据分析 │ │
│ └────┬─────┘ └────┬─────┘ └────┬─────┘ └────┬─────┘ └────┬─────┘ │
│ └──────────────┴──────────────┴──────────────┴──────────────┘ │
│ │ │
│ ┌──────┴───────┐ │
│ │ Zustand Store │ ← 状态管理 │
│ └──────┬────────┘ │
└─────────────────────────────────────┼────────────────────────────────────────┘
│ HTTP/SSE
┌─────────────────────────────────────┼────────────────────────────────────────┐
│ ▼ 后端层 │
│ ┌───────────────────────────────────────────────────────────────────────┐ │
│ │ Express API Server │ │
│ │ ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐ │ │
│ │ │/articles │ │/materials│ │/publish │ │/generate │ │ │
│ │ └────┬─────┘ └────┬─────┘ └────┬─────┘ └────┬─────┘ │ │
│ └───────┼──────────────┼──────────────┼──────────────┼──────────────────┘ │
│ │ │ │ │ │
│ ┌───────┴──────────────┴──────────────┴──────────────┴──────────────────┐ │
│ │ AI Content Pipeline │ │
│ │ │ │
│ │ ┌─────────────────────────────────────────────────────────────────┐ │ │
│ │ │ Orchestrator Agent (编排器) │ │ │
│ │ │ │ │ │
│ │ │ 调度以下 Sub-Agents: │ │ │
│ │ │ ┌───────────┐ ┌───────────┐ ┌───────────┐ ┌───────────┐ │ │ │
│ │ │ │ Researcher│ │ Writer │ │ Editor │ │ Publisher │ │ │ │
│ │ │ │ Sub-Agent │ │ Sub-Agent │ │ Sub-Agent │ │ Sub-Agent │ │ │ │
│ │ │ │ 素材收集 │ │ 内容生成 │ │ 编辑优化 │ │ 发布分发 │ │ │ │
│ │ │ └─────┬─────┘ └─────┬─────┘ └─────┬─────┘ └─────┬─────┘ │ │ │
│ │ └─────────┼─────────────┼─────────────┼─────────────┼────────────┘ │ │
│ │ │ │ │ │ │ │
│ │ ┌─────────┴─────────────┴─────────────┴─────────────┴────────────┐ │ │
│ │ │ Middleware Pipeline │ │ │
│ │ │ ┌─────────┐ ┌─────────┐ ┌─────────┐ ┌─────────┐ ┌─────────┐ │ │ │
│ │ │ │原创检测 │→│SEO优化 │→│格式转换 │→│质量评分 │→│敏感词过滤│ │ │ │
│ │ │ └─────────┘ └─────────┘ └─────────┘ └─────────┘ └─────────┘ │ │ │
│ │ └────────────────────────────────────────────────────────────────┘ │ │
│ └─────────────────────────────────────────────────────────────────────┘ │
└──────────────────────────────────────────────────────────────────────────┘
│
┌─────────────────────────────────────┼────────────────────────────────────────┐
│ ▼ 外部服务层 │
│ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │
│ │ 搜索引擎 API │ │ 平台 API │ │ 图片服务 │ │ 原创检测 │ │
│ │ (Bing/Google)│ │ (微信/知乎) │ │ (Unsplash) │ │ API │ │
│ └──────────────┘ └──────────────┘ └──────────────┘ └──────────────┘ │
└──────────────────────────────────────────────────────────────────────────────┘
│
┌─────────────────────────────────────┼────────────────────────────────────────┐
│ ▼ 数据层 │
│ ┌──────────────┐ ┌──────────────┐ │
│ │ MySQL │ │ Redis │ │
│ │ (Prisma) │ │ (缓存) │ │
│ └──────────────┘ └──────────────┘ │
└──────────────────────────────────────────────────────────────────────────────┘
1.2 内容创作流水线
用户输入:"写一篇关于 AI 编程助手的文章,面向开发者,发布到知乎"
│
▼
┌─────────────────────────────────────────────────────────────────────────────┐
│ Phase 1: 需求分析 │
│ ┌───────────────────────────────────────────────────────────────────────┐ │
│ │ Orchestrator Agent 解析: │ │
│ │ • 主题:AI 编程助手 │ │
│ │ • 受众:开发者 │ │
│ │ • 目标平台:知乎 │ │
│ │ • 内容类型:科普/评测 │ │
│ │ • 预估字数:2000-3000字 │ │
│ └───────────────────────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────────────────────┐
│ Phase 2: 素材收集 (Researcher Sub-Agent) │
│ ┌───────────────────────────────────────────────────────────────────────┐ │
│ │ 并行执行: │ │
│ │ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │ │
│ │ │ 搜索热点文章 │ │ 收集产品信息 │ │ 查找数据统计 │ │ 获取用户评价 │ │ │
│ │ └─────────────┘ └─────────────┘ └─────────────┘ └─────────────┘ │ │
│ │ │ │
│ │ 输出:MaterialCollection { articles, products, statistics, reviews } │ │
│ └───────────────────────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────────────────────┐
│ Phase 3: 大纲生成 (Writer Sub-Agent - Outline) │
│ ┌───────────────────────────────────────────────────────────────────────┐ │
│ │ 基于素材生成文章大纲: │ │
│ │ 1. 引言:AI 编程助手的崛起 │ │
│ │ 2. 主流 AI 编程助手盘点(GitHub Copilot、Cursor、通义灵码...) │ │
│ │ 3. 核心功能对比分析 │ │
│ │ 4. 实际使用体验 │ │
│ │ 5. 选型建议 │ │
│ │ 6. 总结与展望 │ │
│ └───────────────────────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────────────────────┐
│ Phase 4: 内容生成 (Writer Sub-Agent - Content) │
│ ┌───────────────────────────────────────────────────────────────────────┐ │
│ │ 按大纲逐章节生成: │ │
│ │ • 每个章节独立生成,保证质量 │ │
│ │ • 融入收集的素材和数据 │ │
│ │ • 保持语言风格一致 │ │
│ │ • 添加代码示例(针对开发者) │ │
│ │ │ │
│ │ 输出:RawArticle { title, sections[], wordCount } │ │
│ └───────────────────────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────────────────────┐
│ Phase 5: 编辑优化 (Editor Sub-Agent) │
│ ┌───────────────────────────────────────────────────────────────────────┐ │
│ │ 流水线处理: │ │
│ │ ┌─────────┐ ┌─────────┐ ┌─────────┐ ┌─────────┐ │ │
│ │ │ 语法纠错 │ → │ 风格润色 │ → │ 事实核查 │ → │ 原创检测 │ │ │
│ │ └─────────┘ └─────────┘ └─────────┘ └─────────┘ │ │
│ │ │ │
│ │ 输出:EditedArticle { content, corrections[], factChecks[] } │ │
│ └───────────────────────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────────────────────┐
│ Phase 6: 平台适配 (Publisher Sub-Agent) │
│ ┌───────────────────────────────────────────────────────────────────────┐ │
│ │ 针对知乎平台: │ │
│ │ • 标题优化(适合知乎的提问式/干货式标题) │ │
│ │ • 格式转换(Markdown → 知乎格式) │ │
│ │ • 封面图生成/选择 │ │
│ │ • SEO 关键词提取 │ │
│ │ • 话题标签推荐 │ │
│ │ │ │
│ │ 输出:PublishReadyArticle { platformContent, seo, coverImage, tags } │ │
│ └───────────────────────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────────────────────┐
│ Phase 7: 质量检查 (Middleware Pipeline) │
│ ┌───────────────────────────────────────────────────────────────────────┐ │
│ │ ┌─────────────┐ │ │
│ │ │ 原创性评分 │ → 相似度 < 15% ✓ │ │
│ │ └─────────────┘ │ │
│ │ ┌─────────────┐ │ │
│ │ │ 可读性评分 │ → Flesch Score > 60 ✓ │ │
│ │ └─────────────┘ │ │
│ │ ┌─────────────┐ │ │
│ │ │ SEO 评分 │ → 关键词密度 2-3% ✓ │ │
│ │ └─────────────┘ │ │
│ │ ┌─────────────┐ │ │
│ │ │ 敏感词检测 │ → 无违规内容 ✓ │ │
│ │ └─────────────┘ │ │
│ │ │ │
│ │ 输出:QualityReport { scores, passed, suggestions } │ │
│ └───────────────────────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────────────────────┐
│ Phase 8: 人工确认 & 发布 │
│ ┌───────────────────────────────────────────────────────────────────────┐ │
│ │ • 展示最终文章预览 │ │
│ │ • 显示质量检查报告 │ │
│ │ • 用户可手动编辑 │ │
│ │ • 确认后自动发布到目标平台 │ │
│ └───────────────────────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────────────────────┘
二、AI Agent 架构设计
2.1 整体架构模式
本项目采用 Subagents 模式,由一个 Orchestrator(编排器)调度多个专业 Sub-Agent:
┌─────────────────────────────────────────────────────────────────────────────┐
│ Subagents 架构 │
├─────────────────────────────────────────────────────────────────────────────┤
│ │
│ ┌────────────────────────┐ │
│ │ Orchestrator Agent │ │
│ │ (编排器) │ │
│ │ │ │
│ │ • 理解用户创作需求 │ │
│ │ • 分解任务给子Agent │ │
│ │ • 协调执行顺序 │ │
│ │ • 整合最终结果 │ │
│ └───────────┬────────────┘ │
│ │ │
│ ┌─────────────────────┼─────────────────────┐ │
│ │ │ │ │
│ ▼ ▼ ▼ │
│ ┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐ │
│ │ Researcher │ │ Writer │ │ Editor │ │
│ │ Sub-Agent │ │ Sub-Agent │ │ Sub-Agent │ │
│ │ │ │ │ │ │ │
│ │ 职责: │ │ 职责: │ │ 职责: │ │
│ │ • 搜索资料 │ │ • 生成大纲 │ │ • 语法纠错 │ │
│ │ • 收集素材 │ │ • 撰写内容 │ │ • 风格润色 │ │
│ │ • 分析热点 │ │ • 代码示例 │ │ • 事实核查 │ │
│ │ • 整理数据 │ │ • 保持一致性 │ │ • 原创优化 │ │
│ │ │ │ │ │ │ │
│ │ 工具: │ │ 工具: │ │ 工具: │ │
│ │ • web_search │ │ • (无工具) │ │ • grammar_check│ │
│ │ • fetch_url │ │ • 纯 LLM 推理 │ │ • fact_check │ │
│ │ • trend_search │ │ │ │ • plagiarism │ │
│ └─────────────────┘ └─────────────────┘ └─────────────────┘ │
│ │
│ │ │
│ ▼ │
│ ┌─────────────────┐ │
│ │ Publisher │ │
│ │ Sub-Agent │ │
│ │ │ │
│ │ 职责: │ │
│ │ • 平台适配 │ │
│ │ • SEO 优化 │ │
│ │ • 格式转换 │ │
│ │ • 封面生成 │ │
│ │ │ │
│ │ 工具: │ │
│ │ • seo_analyze │ │
│ │ • format_convert│ │
│ │ • image_search │ │
│ │ • platform_post│ │
│ └─────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────────────────┘
2.2 为什么选择 Subagents 模式?
┌─────────────────────────────────────────────────────────────────────────────┐
│ 架构选型分析 │
├─────────────────────────────────────────────────────────────────────────────┤
│ │
│ 内容创作的特点: │
│ ┌───────────────────────────────────────────────────────────────────────┐ │
│ │ 1. 多阶段流水线任务(研究→写作→编辑→发布) │ │
│ │ 2. 每个阶段需要不同的专业能力 │ │
│ │ 3. 阶段之间有明确的输入输出依赖 │ │
│ │ 4. 每个阶段可能需要迭代优化 │ │
│ └───────────────────────────────────────────────────────────────────────┘ │
│ │
│ 架构对比: │
│ │
│ ┌──────────────────┬───────────────────┬────────────────────────────────┐ │
│ │ 架构模式 │ 适用场景 │ 评估 │ │
│ ├──────────────────┼───────────────────┼────────────────────────────────┤ │
│ │ 单 Agent + 多工具 │ 简单任务 │ ❌ 内容创作太复杂 │ │
│ │ Router 模式 │ 独立任务分发 │ ❌ 任务间有依赖,不适合 │ │
│ │ Handoffs 模式 │ 对话式任务移交 │ ❌ 不需要对话式移交 │ │
│ │ Subagents 模式 ✅ │ 流水线式复杂任务 │ ✅ 完美匹配内容创作流程 │ │
│ │ Custom Workflow │ 需要精细控制的流程 │ ⚠️ 可以,但 Subagents 更简洁 │ │
│ └──────────────────┴───────────────────┴────────────────────────────────┘ │
│ │
│ Subagents 模式的优势: │
│ ┌───────────────────────────────────────────────────────────────────────┐ │
│ │ ✓ 任务分解清晰:每个 Sub-Agent 专注一个领域 │ │
│ │ ✓ 易于维护:修改某个阶段不影响其他阶段 │ │
│ │ ✓ 可扩展性强:轻松添加新的 Sub-Agent(如 Translator) │ │
│ │ ✓ 结果可控:每个阶段都可以输出中间结果供审核 │ │
│ │ ✓ 错误隔离:某个 Agent 失败不会影响已完成的工作 │ │
│ └───────────────────────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────────────────┘2.3 中间件设计
除了 Sub-Agents,本项目还使用 中间件模式 处理横切关注点:
┌─────────────────────────────────────────────────────────────────────────────┐
│ Middleware Pipeline(中间件流水线) │
├─────────────────────────────────────────────────────────────────────────────┤
│ │
│ 什么是中间件? │
│ ┌───────────────────────────────────────────────────────────────────────┐ │
│ │ 中间件是在主流程之外执行的"横切"处理逻辑。 │ │
│ │ 它们不改变核心业务流程,但对内容进行增强、检查、转换。 │ │
│ │ │ │
│ │ 类比: │ │
│ │ • 主流程 = 工厂生产线(研究→写作→编辑→发布) │ │
│ │ • 中间件 = 质检站(在各环节检查产品质量) │ │
│ └───────────────────────────────────────────────────────────────────────┘ │
│ │
│ 中间件列表: │
│ │
│ ┌─────────────────────────────────────────────────────────────────────┐ │
│ │ │ │
│ │ 输入内容 │ │
│ │ │ │ │
│ │ ▼ │ │
│ │ ┌─────────────────┐ │ │
│ │ │ 1. 敏感词过滤 │ 检测并处理违规内容 │ │
│ │ │ Middleware │ → 替换敏感词 / 标记警告 │ │
│ │ └────────┬────────┘ │ │
│ │ ▼ │ │
│ │ ┌─────────────────┐ │ │
│ │ │ 2. 原创性检测 │ 检测内容相似度 │ │
│ │ │ Middleware │ → 相似度 > 30% 时重写提示 │ │
│ │ └────────┬────────┘ │ │
│ │ ▼ │ │
│ │ ┌─────────────────┐ │ │
│ │ │ 3. SEO 优化 │ 关键词密度、标题优化 │ │
│ │ │ Middleware │ → 自动调整关键词分布 │ │
│ │ └────────┬────────┘ │ │
│ │ ▼ │ │
│ │ ┌─────────────────┐ │ │
│ │ │ 4. 格式规范化 │ 统一格式、修复排版 │ │
│ │ │ Middleware │ → Markdown 规范化 │ │
│ │ └────────┬────────┘ │ │
│ │ ▼ │ │
│ │ ┌─────────────────┐ │ │
│ │ │ 5. 质量评分 │ 综合评估内容质量 │ │
│ │ │ Middleware │ → 输出质量报告 │ │
│ │ └────────┬────────┘ │ │
│ │ ▼ │ │
│ │ 输出内容 + 质量报告 │ │
│ │ │ │
│ └─────────────────────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────────────────┘
2.4 Agent 间数据流
┌─────────────────────────────────────────────────────────────────────────────┐
│ Agent 间数据流设计 │
├─────────────────────────────────────────────────────────────────────────────┤
│ │
│ ┌─────────────────────────────────────────────────────────────────────┐ │
│ │ 用户输入 │ │
│ │ { │ │
│ │ topic: "AI 编程助手", │ │
│ │ targetAudience: "开发者", │ │
│ │ platforms: ["zhihu"], │ │
│ │ style: "professional", │ │
│ │ wordCount: { min: 2000, max: 3000 } │ │
│ │ } │ │
│ └────────────────────────────────┬────────────────────────────────────┘ │
│ │ │
│ ▼ │
│ ┌─────────────────────────────────────────────────────────────────────┐ │
│ │ Orchestrator → Researcher │ │
│ │ { │ │
│ │ task: "research", │ │
│ │ topic: "AI 编程助手", │ │
│ │ requirements: { │ │
│ │ collectCompetitors: true, // 收集竞品信息 │ │
│ │ collectStatistics: true, // 收集数据统计 │ │
│ │ collectUserReviews: true, // 收集用户评价 │ │
│ │ collectTrends: true // 收集热点趋势 │ │
│ │ } │ │
│ │ } │ │
│ └────────────────────────────────┬────────────────────────────────────┘ │
│ │ │
│ ▼ │
│ ┌─────────────────────────────────────────────────────────────────────┐ │
│ │ Researcher → Writer │ │
│ │ { │ │
│ │ materials: { │ │
│ │ competitors: [ │ │
│ │ { name: "GitHub Copilot", features: [...], pricing: "..." },│ │
│ │ { name: "Cursor", features: [...], pricing: "..." }, │ │
│ │ ... │ │
│ │ ], │ │
│ │ statistics: [ │ │
│ │ { source: "...", data: "开发者使用率增长 300%..." } │ │
│ │ ], │ │
│ │ trends: [ │ │
│ │ { topic: "AI 编程效率", heat: 95 } │ │
│ │ ], │ │
│ │ references: [...] │ │
│ │ } │ │
│ │ } │ │
│ └────────────────────────────────┬────────────────────────────────────┘ │
│ │ │
│ ▼ │
│ ┌─────────────────────────────────────────────────────────────────────┐ │
│ │ Writer → Editor │ │
│ │ { │ │
│ │ article: { │ │
│ │ title: "2024年 AI 编程助手终极指南:从入门到精通", │ │
│ │ outline: [...], │ │
│ │ sections: [ │ │
│ │ { heading: "引言", content: "..." }, │ │
│ │ { heading: "主流工具盘点", content: "..." }, │ │
│ │ ... │ │
│ │ ], │ │
│ │ wordCount: 2500, │ │
│ │ codeExamples: [...] │ │
│ │ } │ │
│ │ } │ │
│ └────────────────────────────────┬────────────────────────────────────┘ │
│ │ │
│ ▼ │
│ ┌─────────────────────────────────────────────────────────────────────┐ │
│ │ Editor → Publisher │ │
│ │ { │ │
│ │ editedArticle: { │ │
│ │ ...article, │ │
│ │ corrections: [ │ │
│ │ { type: "grammar", original: "...", fixed: "..." } │ │
│ │ ], │ │
│ │ factChecks: [ │ │
│ │ { claim: "...", verified: true, source: "..." } │ │
│ │ ], │ │
│ │ originalityScore: 0.92 │ │
│ │ } │ │
│ │ } │ │
│ └────────────────────────────────┬────────────────────────────────────┘ │
│ │ │
│ ▼ │
│ ┌─────────────────────────────────────────────────────────────────────┐ │
│ │ Publisher → 最终输出 │ │
│ │ { │ │
│ │ publishReady: { │ │
│ │ zhihu: { │ │
│ │ title: "...", │ │
│ │ content: "...", // 知乎格式 │ │
│ │ topics: ["AI", "编程", "效率工具"], │ │
│ │ coverImage: "https://...", │ │
│ │ seo: { keywords: [...], description: "..." } │ │
│ │ } │ │
│ │ }, │ │
│ │ qualityReport: { │ │
│ │ overallScore: 85, │ │
│ │ originalityScore: 92, │ │
│ │ readabilityScore: 78, │ │
│ │ seoScore: 88, │ │
│ │ suggestions: [...] │ │
│ │ } │ │
│ │ } │ │
│ └─────────────────────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────────────────┘三、数据库设计
3.1 ER 图
┌──────────────────┐ ┌──────────────────┐
│ users │ │ articles │
├──────────────────┤ ├──────────────────┤
│ id (PK) │───┐ │ id (PK) │
│ email │ │ │ userId (FK) │──┐
│ password │ └──→│ title │ │
│ nickname │ │ content │ │
│ avatar │ │ topic │ │
│ plan │ │ status │ │
│ createdAt │ │ wordCount │ │
│ updatedAt │ │ createdAt │ │
└──────────────────┘ │ updatedAt │ │
└────────┬─────────┘ │
│ │
┌──────────────────┐ │ │
│ materials │←───────────────┘ │
├──────────────────┤ │
│ id (PK) │ │
│ articleId (FK) │ │
│ type │ (link/text/image/data) │
│ title │ │
│ content │ │
│ source │ │
│ metadata │ (JSON) │
│ createdAt │ │
└──────────────────┘ │
│
┌──────────────────┐ ┌──────────────────┐ │
│ article_versions │ │ article_platforms│ │
├──────────────────┤ ├──────────────────┤ │
│ id (PK) │ │ id (PK) │ │
│ articleId (FK) │←──────│ articleId (FK) │←┘
│ version │ │ platform │
│ content │ │ platformContent │
│ title │ │ platformTitle │
│ changeLog │ │ seoData (JSON) │
│ createdAt │ │ coverImage │
└──────────────────┘ │ tags (JSON) │
│ publishStatus │
┌──────────────────┐ │ publishedAt │
│ quality_reports │ │ publishUrl │
├──────────────────┤ │ createdAt │
│ id (PK) │ └──────────────────┘
│ articleId (FK) │
│ overallScore │
│ originalityScore │
│ readabilityScore │
│ seoScore │
│ details (JSON) │
│ suggestions(JSON)│
│ createdAt │
└──────────────────┘
3.2 Prisma Schema
prisma
// prisma/schema.prisma
generator client {
provider = "prisma-client-js"
}
datasource db {
provider = "mysql"
url = env("DATABASE_URL")
}
model User {
id String @id @default(uuid())
email String @unique
password String
nickname String?
avatar String?
plan String @default("free") // free, pro, enterprise
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
articles Article[]
@@map("users")
}
model Article {
id String @id @default(uuid())
userId String
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
title String
content String @db.LongText
topic String
status String @default("draft") // draft, generating, editing, ready, published
style String @default("professional") // professional, casual, humorous
audience String? // 目标受众描述
wordCount Int @default(0)
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
materials Material[]
versions ArticleVersion[]
platforms ArticlePlatform[]
qualityReports QualityReport[]
@@index([userId])
@@index([status])
@@map("articles")
}
model Material {
id String @id @default(uuid())
articleId String
article Article @relation(fields: [articleId], references: [id], onDelete: Cascade)
type String // link, text, image, data, competitor, statistic
title String?
content String @db.Text
source String? // 来源 URL
metadata Json? // 额外元数据
createdAt DateTime @default(now())
@@index([articleId])
@@map("materials")
}
model ArticleVersion {
id String @id @default(uuid())
articleId String
article Article @relation(fields: [articleId], references: [id], onDelete: Cascade)
version Int
title String
content String @db.LongText
changeLog String? @db.Text
createdAt DateTime @default(now())
@@unique([articleId, version])
@@index([articleId])
@@map("article_versions")
}
model ArticlePlatform {
id String @id @default(uuid())
articleId String
article Article @relation(fields: [articleId], references: [id], onDelete: Cascade)
platform String // zhihu, wechat, xiaohongshu, toutiao, juejin
platformTitle String
platformContent String @db.LongText
seoData Json? // { keywords: [], description: "" }
coverImage String?
tags Json? // ["AI", "编程"]
publishStatus String @default("pending") // pending, published, failed
publishedAt DateTime?
publishUrl String?
errorMessage String?
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
@@unique([articleId, platform])
@@index([articleId])
@@map("article_platforms")
}
model QualityReport {
id String @id @default(uuid())
articleId String
article Article @relation(fields: [articleId], references: [id], onDelete: Cascade)
overallScore Int // 0-100
originalityScore Int // 原创性评分
readabilityScore Int // 可读性评分
seoScore Int // SEO评分
grammarScore Int // 语法评分
details Json? // 详细检查结果
suggestions Json? // 改进建议列表
createdAt DateTime @default(now())
@@index([articleId])
@@map("quality_reports")
}
// 素材库 - 用户收藏的素材
model MaterialLibrary {
id String @id @default(uuid())
userId String
category String // 分类
title String
content String @db.Text
source String?
tags Json?
createdAt DateTime @default(now())
@@index([userId])
@@index([category])
@@map("material_library")
}
// 创作模板
model Template {
id String @id @default(uuid())
userId String? // null 表示系统模板
name String
description String?
platform String // 适用平台
category String // 模板分类
structure Json // 文章结构模板
styleGuide String? @db.Text
isPublic Boolean @default(false)
usageCount Int @default(0)
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
@@index([userId])
@@index([platform])
@@map("templates")
}四、AI Agent 详细实现
4.1 状态定义
typescript
// src/agent/types.ts
import { Annotation } from "@langchain/langgraph";
import { BaseMessage } from "@langchain/core/messages";
// 创作请求
export interface ContentRequest {
topic: string;
targetAudience: string;
platforms: string[];
style: "professional" | "casual" | "humorous" | "storytelling";
wordCount: { min: number; max: number };
keywords?: string[];
references?: string[];
specialRequirements?: string;
}
// 素材
export interface Material {
type: "competitor" | "statistic" | "trend" | "reference" | "quote";
title: string;
content: string;
source?: string;
relevance: number;
metadata?: Record<string, any>;
}
// 文章大纲
export interface ArticleOutline {
title: string;
hook: string;
sections: Array<{
heading: string;
keyPoints: string[];
estimatedWords: number;
}>;
conclusion: string;
estimatedTotalWords: number;
}
// 文章章节
export interface ArticleSection {
heading: string;
content: string;
wordCount: number;
}
// 原始文章
export interface RawArticle {
title: string;
sections: ArticleSection[];
wordCount: number;
codeExamples?: Array<{
language: string;
code: string;
description: string;
}>;
}
// 编辑结果
export interface EditedArticle extends RawArticle {
corrections: Array<{
type: "grammar" | "style" | "fact" | "clarity";
original: string;
fixed: string;
explanation?: string;
}>;
factChecks: Array<{
claim: string;
verified: boolean;
source?: string;
note?: string;
}>;
originalityScore: number;
}
// 平台适配内容
export interface PlatformContent {
platform: string;
title: string;
content: string;
seo: {
keywords: string[];
description: string;
keywordDensity: number;
};
coverImage?: string;
tags: string[];
formatting: {
hasImages: boolean;
hasCode: boolean;
estimatedReadTime: number;
};
}
// 质量报告
export interface QualityReport {
overallScore: number;
originalityScore: number;
readabilityScore: number;
seoScore: number;
grammarScore: number;
passed: boolean;
issues: Array<{
severity: "error" | "warning" | "info";
category: string;
message: string;
suggestion?: string;
}>;
}
// Agent 状态
export const ContentCreatorState = Annotation.Root({
messages: Annotation<BaseMessage[]>({
reducer: (prev, next) => [...prev, ...next],
default: () => [],
}),
// 输入
request: Annotation<ContentRequest>(),
// 中间状态
currentPhase: Annotation<string>({
default: () => "init",
}),
// 各阶段输出
materials: Annotation<Material[]>({
default: () => [],
}),
outline: Annotation<ArticleOutline | null>({
default: () => null,
}),
rawArticle: Annotation<RawArticle | null>({
default: () => null,
}),
editedArticle: Annotation<EditedArticle | null>({
default: () => null,
}),
platformContents: Annotation<PlatformContent[]>({
default: () => [],
}),
qualityReport: Annotation<QualityReport | null>({
default: () => null,
}),
// 错误处理
errors: Annotation<string[]>({
reducer: (prev, next) => [...prev, ...next],
default: () => [],
}),
});
export type ContentCreatorStateType = typeof ContentCreatorState.State;4.2 Orchestrator Agent(编排器)
typescript
// src/agent/orchestrator.agent.ts
import { ChatOpenAI } from "@langchain/openai";
import { AIMessage } from "@langchain/core/messages";
import type { ContentCreatorStateType } from "./types";
const ORCHESTRATOR_PROMPT = `你是内容创作编排器,负责协调整个内容创作流程。
## 你的职责
1. 分析用户的创作需求
2. 决定任务执行顺序
3. 在各阶段之间传递上下文
4. 处理异常情况
## 创作流程
1. research - 素材收集(Researcher Sub-Agent)
2. outline - 大纲生成(Writer Sub-Agent)
3. write - 内容撰写(Writer Sub-Agent)
4. edit - 编辑优化(Editor Sub-Agent)
5. adapt - 平台适配(Publisher Sub-Agent)
6. quality - 质量检查(Middleware)
## 决策原则
- 如果素材不足,可以要求 Researcher 补充
- 如果大纲不满意,可以要求 Writer 重新生成
- 如果质量检查不通过,可以要求 Editor 修改
- 每个阶段完成后都要检查结果质量
## 输出格式
返回当前应该执行的阶段和原因。`;
export async function orchestratorAgent(state: ContentCreatorStateType) {
const model = new ChatOpenAI({
model: "gpt-4o",
temperature: 0,
});
const { request, currentPhase, materials, outline, rawArticle, editedArticle, qualityReport } = state;
// 构建当前状态描述
let statusDescription = `
当前阶段: ${currentPhase}
创作请求: ${JSON.stringify(request, null, 2)}
已收集素材数量: ${materials.length}
大纲状态: ${outline ? "已生成" : "未生成"}
文章状态: ${rawArticle ? `已生成 (${rawArticle.wordCount}字)` : "未生成"}
编辑状态: ${editedArticle ? "已完成" : "未完成"}
质量报告: ${qualityReport ? `得分 ${qualityReport.overallScore}` : "未检查"}
`;
const result = await model.invoke([
{ role: "system", content: ORCHESTRATOR_PROMPT },
{ role: "user", content: statusDescription },
]);
return {
messages: [new AIMessage({
content: `[编排器] ${result.content}`,
})],
};
}
// 路由决策函数
export function routeNextPhase(state: ContentCreatorStateType): string {
const { currentPhase, materials, outline, rawArticle, editedArticle, qualityReport, request } = state;
// 状态机逻辑
switch (currentPhase) {
case "init":
return "researcher"; // 开始收集素材
case "research":
if (materials.length < 5) {
return "researcher"; // 素材不足,继续收集
}
return "writer_outline"; // 素材足够,开始写大纲
case "outline":
if (!outline) {
return "writer_outline"; // 大纲未生成,重试
}
return "writer_content"; // 开始写内容
case "write":
if (!rawArticle) {
return "writer_content"; // 内容未生成,重试
}
return "editor"; // 开始编辑
case "edit":
if (!editedArticle) {
return "editor"; // 编辑未完成,重试
}
return "publisher"; // 开始平台适配
case "adapt":
return "quality_check"; // 进入质量检查
case "quality":
if (qualityReport && !qualityReport.passed) {
// 质量不达标,需要修改
if (qualityReport.originalityScore < 70) {
return "editor"; // 原创性问题,重新编辑
}
if (qualityReport.grammarScore < 70) {
return "editor"; // 语法问题,重新编辑
}
}
return "complete"; // 完成
default:
return "complete";
}
}4.3 Researcher Sub-Agent(素材收集器)
typescript
// src/agent/researcher.agent.ts
import { createReactAgent } from "@langchain/langgraph/prebuilt";
import { ChatOpenAI } from "@langchain/openai";
import { SystemMessage, AIMessage } from "@langchain/core/messages";
import { tool } from "@langchain/core/tools";
import { z } from "zod";
import type { ContentCreatorStateType, Material } from "./types";
// 工具定义
const webSearchTool = tool(
async ({ query, count }) => {
// 调用搜索 API(如 Bing Search API)
const searchService = new SearchService();
const results = await searchService.search(query, count);
return JSON.stringify({
query,
results: results.map((r: any) => ({
title: r.title,
snippet: r.snippet,
url: r.url,
date: r.date,
})),
});
},
{
name: "web_search",
description: "搜索网络获取相关资料,返回标题、摘要和链接",
schema: z.object({
query: z.string().describe("搜索关键词"),
count: z.number().default(10).describe("结果数量"),
}),
}
);
const fetchUrlTool = tool(
async ({ url }) => {
// 抓取网页内容
const fetcher = new WebFetcher();
const content = await fetcher.fetch(url);
return JSON.stringify({
url,
title: content.title,
content: content.text.slice(0, 5000), // 限制长度
publishDate: content.publishDate,
});
},
{
name: "fetch_url",
description: "获取指定 URL 的网页内容",
schema: z.object({
url: z.string().url().describe("要抓取的网页 URL"),
}),
}
);
const trendSearchTool = tool(
async ({ topic, platform }) => {
// 搜索热点趋势
const trendService = new TrendService();
const trends = await trendService.search(topic, platform);
return JSON.stringify({
topic,
platform,
trends: trends.map((t: any) => ({
title: t.title,
heat: t.heat,
url: t.url,
summary: t.summary,
})),
});
},
{
name: "trend_search",
description: "搜索特定主题在各平台的热点趋势",
schema: z.object({
topic: z.string().describe("主题关键词"),
platform: z.enum(["zhihu", "weibo", "toutiao", "all"]).default("all"),
}),
}
);
const competitorAnalysisTool = tool(
async ({ topic, count }) => {
// 分析竞品文章
const searchService = new SearchService();
const results = await searchService.search(`${topic} 评测 对比`, count);
// 抓取并分析前几篇文章
const analyses = [];
for (const r of results.slice(0, 3)) {
const fetcher = new WebFetcher();
const content = await fetcher.fetch(r.url);
analyses.push({
title: r.title,
url: r.url,
wordCount: content.text.length,
structure: extractStructure(content.text),
keyPoints: extractKeyPoints(content.text),
});
}
return JSON.stringify({
topic,
competitorArticles: analyses,
});
},
{
name: "competitor_analysis",
description: "分析同主题的竞品文章,了解他们的写法和结构",
schema: z.object({
topic: z.string().describe("主题"),
count: z.number().default(5).describe("分析数量"),
}),
}
);
const RESEARCHER_PROMPT = `你是内容研究专家,负责为文章创作收集全面的素材。
## 你的任务
根据创作主题,收集以下类型的素材:
1. **竞品/产品信息** - 相关产品的功能、价格、评价
2. **数据统计** - 行业数据、用户调研、市场分析
3. **热点趋势** - 当前热门话题、讨论焦点
4. **参考文章** - 高质量的相关文章
5. **权威引用** - 专家观点、官方声明
## 工作原则
1. 确保信息来源可靠(优先官方、权威媒体)
2. 收集多角度的观点,保持客观
3. 记录所有来源,便于引用
4. 标注信息的时效性
5. 评估每条素材的相关性(1-10分)
## 素材收集顺序
1. 先用 web_search 广泛搜索
2. 用 fetch_url 深入阅读重要文章
3. 用 trend_search 了解热点
4. 用 competitor_analysis 分析竞品文章
确保收集至少 10 条高质量素材。`;
export function createResearcherAgent() {
const model = new ChatOpenAI({
model: "gpt-4o",
temperature: 0.3,
});
const tools = [
webSearchTool,
fetchUrlTool,
trendSearchTool,
competitorAnalysisTool,
];
return createReactAgent({
llm: model,
tools,
messageModifier: new SystemMessage(RESEARCHER_PROMPT),
});
}
export async function researcherNode(state: ContentCreatorStateType) {
const agent = createResearcherAgent();
const { request } = state;
const instruction = `
请为以下创作需求收集素材:
主题:${request.topic}
目标受众:${request.targetAudience}
目标平台:${request.platforms.join(", ")}
风格:${request.style}
字数要求:${request.wordCount.min}-${request.wordCount.max}字
${request.keywords ? `关键词:${request.keywords.join(", ")}` : ""}
${request.specialRequirements ? `特殊要求:${request.specialRequirements}` : ""}
请全面收集素材,确保涵盖:
1. 该主题的核心产品/工具信息
2. 相关的数据和统计
3. 当前的热点讨论
4. 值得参考的优秀文章
最后,整理成结构化的素材列表。
`;
const result = await agent.invoke({
messages: [{ role: "user", content: instruction }],
});
// 解析收集到的素材
const materials = parseMaterials(result.messages);
return {
currentPhase: "research",
materials,
messages: [new AIMessage({
content: `[素材收集] 已收集 ${materials.length} 条素材`,
})],
};
}
function parseMaterials(messages: any[]): Material[] {
const materials: Material[] = [];
for (const msg of messages) {
if (msg._getType() === "tool") {
try {
const content = JSON.parse(msg.content);
// 解析搜索结果
if (content.results) {
for (const r of content.results) {
materials.push({
type: "reference",
title: r.title,
content: r.snippet,
source: r.url,
relevance: 7,
});
}
}
// 解析竞品分析
if (content.competitorArticles) {
for (const a of content.competitorArticles) {
materials.push({
type: "competitor",
title: a.title,
content: JSON.stringify(a.keyPoints),
source: a.url,
relevance: 8,
metadata: { structure: a.structure },
});
}
}
// 解析趋势
if (content.trends) {
for (const t of content.trends) {
materials.push({
type: "trend",
title: t.title,
content: t.summary,
source: t.url,
relevance: t.heat / 10,
metadata: { heat: t.heat },
});
}
}
} catch (e) {
// 忽略解析错误
}
}
}
return materials;
}4.4 Writer Sub-Agent(内容撰写器)
typescript
// src/agent/writer.agent.ts
import { ChatOpenAI } from "@langchain/openai";
import { AIMessage } from "@langchain/core/messages";
import { z } from "zod";
import type { ContentCreatorStateType, ArticleOutline, RawArticle } from "./types";
// 大纲 Schema
const OutlineSchema = z.object({
title: z.string().describe("文章标题,吸引人且准确"),
hook: z.string().describe("开篇钩子,引起读者兴趣"),
sections: z.array(z.object({
heading: z.string().describe("章节标题"),
keyPoints: z.array(z.string()).describe("该章节要覆盖的要点"),
estimatedWords: z.number().describe("预估字数"),
})).describe("文章各章节"),
conclusion: z.string().describe("结论要点"),
estimatedTotalWords: z.number().describe("预估总字数"),
});
// 章节内容 Schema
const SectionContentSchema = z.object({
heading: z.string(),
content: z.string().describe("章节完整内容,Markdown 格式"),
wordCount: z.number(),
});
const OUTLINE_PROMPT = `你是资深内容策划师,负责规划文章大纲。
## 大纲原则
### 1. 标题设计
- 数字型:《2024年 X 大 AI 编程工具盘点》
- 疑问型:《AI 编程助手真的能提升效率吗?》
- 干货型:《AI 编程助手完全指南:从入门到精通》
- 对比型:《GitHub Copilot vs Cursor:谁是最强 AI 编程助手》
### 2. 结构设计
- 金字塔结构:重要内容放前面
- 故事弧线:引入→发展→高潮→结尾
- 问题解决:痛点→方案→效果
### 3. 章节安排
- 引言(10%):建立话题,引起兴趣
- 主体(70%):分 3-5 个小节展开
- 结尾(20%):总结、建议、展望
### 4. 受众适配
- 开发者:可加入代码示例
- 普通用户:多用类比,少用术语
- 决策者:强调 ROI 和商业价值`;
const CONTENT_PROMPT = `你是专业内容创作者,负责撰写高质量文章。
## 写作原则
### 1. 开篇
- 用数据、故事或问题抓住注意力
- 明确文章价值,告诉读者能获得什么
- 控制在 200 字以内
### 2. 正文
- 每段一个核心观点
- 使用过渡句连接段落
- 适当使用小标题、列表、引用
- 用具体例子支撑观点
- 融入提供的素材和数据
### 3. 代码示例(如需)
- 简洁易懂
- 添加注释说明
- 展示实际效果
### 4. 结尾
- 总结核心观点
- 给出行动建议
- 可适当展望未来
### 5. 语言风格
- professional:专业严谨,数据支撑,逻辑清晰
- casual:轻松活泼,口语化表达,增加互动
- humorous:幽默风趣,加入段子,引人发笑
- storytelling:故事驱动,人物叙事,情感共鸣`;
export async function writerOutlineNode(state: ContentCreatorStateType) {
const model = new ChatOpenAI({
model: "gpt-4o",
temperature: 0.7,
}).withStructuredOutput(OutlineSchema);
const { request, materials } = state;
const prompt = `
## 创作需求
- 主题:${request.topic}
- 目标受众:${request.targetAudience}
- 目标平台:${request.platforms.join(", ")}
- 风格:${request.style}
- 字数:${request.wordCount.min}-${request.wordCount.max}字
## 已收集素材
${materials.map((m, i) => `
${i + 1}. [${m.type}] ${m.title}
${m.content.slice(0, 200)}...
来源:${m.source || "无"}
相关性:${m.relevance}/10
`).join("\n")}
请基于以上素材,生成一个吸引人的文章大纲。
`;
const outline = await model.invoke([
{ role: "system", content: OUTLINE_PROMPT },
{ role: "user", content: prompt },
]);
return {
currentPhase: "outline",
outline,
messages: [new AIMessage({
content: `[大纲生成] ${outline.title}\n\n章节:\n${outline.sections.map(s => `- ${s.heading}`).join("\n")}`,
})],
};
}
export async function writerContentNode(state: ContentCreatorStateType) {
const model = new ChatOpenAI({
model: "gpt-4o",
temperature: 0.7,
}).withStructuredOutput(SectionContentSchema);
const { request, materials, outline } = state;
if (!outline) {
throw new Error("大纲未生成,无法撰写内容");
}
// 逐章节生成内容
const sections = [];
for (const section of outline.sections) {
// 找出与该章节相关的素材
const relevantMaterials = findRelevantMaterials(materials, section.heading);
const prompt = `
## 任务
撰写文章章节:${section.heading}
## 文章信息
- 标题:${outline.title}
- 风格:${request.style}
- 目标受众:${request.targetAudience}
## 章节要点
${section.keyPoints.map(p => `- ${p}`).join("\n")}
## 可用素材
${relevantMaterials.map(m => `
[${m.type}] ${m.title}
${m.content}
来源:${m.source || "无"}
`).join("\n---\n")}
## 要求
- 字数约 ${section.estimatedWords} 字
- 融入提供的素材,但要自然
- 保持与其他章节的风格一致
- ${request.targetAudience === "开发者" ? "可包含代码示例" : ""}
- 使用 Markdown 格式
`;
const sectionContent = await model.invoke([
{ role: "system", content: CONTENT_PROMPT },
{ role: "user", content: prompt },
]);
sections.push(sectionContent);
}
// 生成引言和结尾
const introContent = await generateIntro(model, outline, request);
const conclusionContent = await generateConclusion(model, outline, request);
// 组装完整文章
const rawArticle: RawArticle = {
title: outline.title,
sections: [
{ heading: "引言", content: introContent, wordCount: countWords(introContent) },
...sections,
{ heading: "总结", content: conclusionContent, wordCount: countWords(conclusionContent) },
],
wordCount: sections.reduce((sum, s) => sum + s.wordCount, 0) +
countWords(introContent) + countWords(conclusionContent),
};
return {
currentPhase: "write",
rawArticle,
messages: [new AIMessage({
content: `[内容生成] 已生成 ${rawArticle.wordCount} 字文章`,
})],
};
}
function findRelevantMaterials(materials: any[], heading: string) {
// 简单的相关性匹配,实际可用更复杂的算法
return materials.filter(m => {
const headingWords = heading.toLowerCase().split(/\s+/);
const materialText = `${m.title} ${m.content}`.toLowerCase();
return headingWords.some(word => materialText.includes(word));
}).slice(0, 5);
}
async function generateIntro(model: any, outline: any, request: any) {
const result = await model.invoke([
{ role: "system", content: "生成文章引言,吸引读者,引出主题" },
{ role: "user", content: `
文章标题:${outline.title}
钩子:${outline.hook}
风格:${request.style}
字数:150-250字
` },
]);
return result.content;
}
async function generateConclusion(model: any, outline: any, request: any) {
const result = await model.invoke([
{ role: "system", content: "生成文章结尾,总结要点,给出建议" },
{ role: "user", content: `
文章标题:${outline.title}
结论要点:${outline.conclusion}
风格:${request.style}
字数:200-300字
` },
]);
return result.content;
}
function countWords(text: string): number {
return text.replace(/\s+/g, "").length;
}4.5 Editor Sub-Agent(编辑器)
typescript
// src/agent/editor.agent.ts
import { createReactAgent } from "@langchain/langgraph/prebuilt";
import { ChatOpenAI } from "@langchain/openai";
import { SystemMessage, AIMessage } from "@langchain/core/messages";
import { tool } from "@langchain/core/tools";
import { z } from "zod";
import type { ContentCreatorStateType, EditedArticle } from "./types";
// 语法检查工具
const grammarCheckTool = tool(
async ({ text }) => {
// 调用语法检查 API 或使用 LLM
const model = new ChatOpenAI({ model: "gpt-4o", temperature: 0 });
const result = await model.invoke([
{ role: "system", content: `你是语法检查专家。检查以下文本的语法错误、标点错误、用词不当等问题。
返回 JSON 格式:
{
"errors": [
{ "original": "错误原文", "corrected": "修正后", "type": "grammar/punctuation/wording", "explanation": "说明" }
],
"score": 0-100
}` },
{ role: "user", content: text },
]);
return result.content;
},
{
name: "grammar_check",
description: "检查文本的语法、标点和用词错误",
schema: z.object({
text: z.string().describe("要检查的文本"),
}),
}
);
// 事实核查工具
const factCheckTool = tool(
async ({ claims }) => {
// 对声明进行核查
const model = new ChatOpenAI({ model: "gpt-4o", temperature: 0 });
const searchService = new SearchService();
const results = [];
for (const claim of claims) {
// 搜索相关信息
const searchResults = await searchService.search(claim, 3);
// LLM 判断
const verification = await model.invoke([
{ role: "system", content: "你是事实核查专家。根据搜索结果判断声明是否属实。" },
{ role: "user", content: `
声明:${claim}
搜索结果:${JSON.stringify(searchResults)}
请判断该声明是否属实,返回 JSON:{ "verified": true/false, "confidence": 0-100, "source": "来源", "note": "备注" }
` },
]);
results.push({
claim,
...JSON.parse(verification.content as string),
});
}
return JSON.stringify(results);
},
{
name: "fact_check",
description: "核查文章中的事实声明是否准确",
schema: z.object({
claims: z.array(z.string()).describe("需要核查的声明列表"),
}),
}
);
// 原创性检测工具
const plagiarismCheckTool = tool(
async ({ text }) => {
// 检测原创性
// 实际项目中可调用专门的 API(如 Copyscape、Turnitin 等)
// 这里使用 LLM 模拟
const model = new ChatOpenAI({ model: "gpt-4o", temperature: 0 });
const searchService = new SearchService();
// 取文章的几个片段进行搜索
const segments = extractSegments(text, 5);
let similarCount = 0;
const matches = [];
for (const segment of segments) {
const results = await searchService.search(`"${segment}"`, 3);
if (results.length > 0) {
similarCount++;
matches.push({
segment,
matchedUrl: results[0].url,
});
}
}
const originalityScore = Math.round((1 - similarCount / segments.length) * 100);
return JSON.stringify({
originalityScore,
matches,
recommendation: originalityScore < 70 ? "建议重写部分内容" : "原创性良好",
});
},
{
name: "plagiarism_check",
description: "检测文章的原创性,识别可能的抄袭内容",
schema: z.object({
text: z.string().describe("要检测的文章内容"),
}),
}
);
const EDITOR_PROMPT = `你是资深编辑,负责优化和完善文章。
## 编辑任务
### 1. 语法纠错
使用 grammar_check 工具检查语法、标点、用词
### 2. 风格润色
- 保持语言流畅
- 消除冗余表达
- 增强可读性
- 确保风格一致
### 3. 事实核查
使用 fact_check 工具核查重要声明:
- 数据和统计
- 引用和来源
- 产品功能描述
### 4. 原创性检测
使用 plagiarism_check 工具检测原创性
- 原创性 < 70% 需要重写
- 标记相似内容
### 5. 整体优化
- 检查逻辑连贯性
- 优化段落过渡
- 确保论点支撑充分
## 输出要求
1. 修改后的完整文章
2. 修改记录(原文/修改后/原因)
3. 事实核查结果
4. 原创性得分`;
export function createEditorAgent() {
const model = new ChatOpenAI({
model: "gpt-4o",
temperature: 0.3,
});
const tools = [
grammarCheckTool,
factCheckTool,
plagiarismCheckTool,
];
return createReactAgent({
llm: model,
tools,
messageModifier: new SystemMessage(EDITOR_PROMPT),
});
}
export async function editorNode(state: ContentCreatorStateType) {
const { rawArticle, request } = state;
if (!rawArticle) {
throw new Error("文章未生成,无法编辑");
}
const agent = createEditorAgent();
// 将文章转为纯文本
const fullText = rawArticle.sections.map(s => `## ${s.heading}\n\n${s.content}`).join("\n\n");
const instruction = `
请编辑和优化以下文章:
## 文章标题
${rawArticle.title}
## 文章内容
${fullText}
## 编辑要求
1. 检查并修正语法错误
2. 按照 "${request.style}" 风格润色
3. 核查文中的事实声明
4. 检测原创性
请依次使用工具完成检查,然后提供:
1. 修改后的完整文章
2. 所有修改的详细记录
3. 事实核查结果
4. 原创性得分
`;
const result = await agent.invoke({
messages: [{ role: "user", content: instruction }],
});
// 解析编辑结果
const editedArticle = parseEditedArticle(result.messages, rawArticle);
return {
currentPhase: "edit",
editedArticle,
messages: [new AIMessage({
content: `[编辑完成] 原创性得分:${editedArticle.originalityScore},修正 ${editedArticle.corrections.length} 处`,
})],
};
}
function extractSegments(text: string, count: number): string[] {
const sentences = text.match(/[^。!?.!?]+[。!?.!?]/g) || [];
const step = Math.floor(sentences.length / count);
return sentences.filter((_, i) => i % step === 0).slice(0, count);
}
function parseEditedArticle(messages: any[], rawArticle: any): EditedArticle {
// 从消息中解析编辑结果
let corrections: any[] = [];
let factChecks: any[] = [];
let originalityScore = 85; // 默认值
for (const msg of messages) {
if (msg._getType() === "tool") {
try {
const content = JSON.parse(msg.content);
if (content.errors) {
corrections = content.errors.map((e: any) => ({
type: e.type,
original: e.original,
fixed: e.corrected,
explanation: e.explanation,
}));
}
if (Array.isArray(content) && content[0]?.claim) {
factChecks = content;
}
if (content.originalityScore !== undefined) {
originalityScore = content.originalityScore;
}
} catch (e) {
// 忽略解析错误
}
}
}
return {
...rawArticle,
corrections,
factChecks,
originalityScore,
};
}4.6 Publisher Sub-Agent(发布器)
typescript
// src/agent/publisher.agent.ts
import { createReactAgent } from "@langchain/langgraph/prebuilt";
import { ChatOpenAI } from "@langchain/openai";
import { SystemMessage, AIMessage } from "@langchain/core/messages";
import { tool } from "@langchain/core/tools";
import { z } from "zod";
import type { ContentCreatorStateType, PlatformContent } from "./types";
// SEO 分析工具
const seoAnalyzeTool = tool(
async ({ text, targetKeywords }) => {
const model = new ChatOpenAI({ model: "gpt-4o", temperature: 0 });
const result = await model.invoke([
{ role: "system", content: `你是 SEO 专家。分析文本的 SEO 质量,返回 JSON:
{
"keywords": ["提取的关键词"],
"keywordDensity": { "关键词": 密度百分比 },
"suggestions": ["优化建议"],
"score": 0-100,
"titleSuggestions": ["优化后的标题建议"],
"descriptionSuggestion": "meta description 建议"
}` },
{ role: "user", content: `
文本:${text.slice(0, 3000)}
目标关键词:${targetKeywords.join(", ")}
` },
]);
return result.content;
},
{
name: "seo_analyze",
description: "分析内容的 SEO 质量,提供优化建议",
schema: z.object({
text: z.string().describe("要分析的文本"),
targetKeywords: z.array(z.string()).describe("目标关键词"),
}),
}
);
// 格式转换工具
const formatConvertTool = tool(
async ({ content, fromFormat, toFormat, platform }) => {
// 不同平台的格式要求
const platformFormats: Record<string, any> = {
zhihu: {
maxImageWidth: 690,
codeBlockStyle: "highlight",
linkStyle: "inline",
supportedTags: ["h1", "h2", "h3", "p", "ul", "ol", "blockquote", "code", "img"],
},
wechat: {
maxImageWidth: 640,
codeBlockStyle: "screenshot",
linkStyle: "footnote", // 公众号链接只能放文末
supportedTags: ["h1", "h2", "h3", "p", "ul", "ol", "blockquote", "pre"],
},
xiaohongshu: {
maxImageWidth: 1080,
codeBlockStyle: "image", // 小红书不支持代码块
linkStyle: "none",
maxLength: 1000,
emojiRequired: true,
},
toutiao: {
maxImageWidth: 640,
codeBlockStyle: "highlight",
linkStyle: "card",
supportedTags: ["h1", "h2", "h3", "p", "ul", "ol", "blockquote", "code", "img"],
},
juejin: {
maxImageWidth: 800,
codeBlockStyle: "highlight",
linkStyle: "inline",
supportedTags: ["h1", "h2", "h3", "p", "ul", "ol", "blockquote", "code", "img"],
},
};
const format = platformFormats[platform];
// 根据平台规则转换内容
let convertedContent = content;
// 处理代码块
if (format.codeBlockStyle === "image" || format.codeBlockStyle === "screenshot") {
// 将代码块转为图片(实际实现中调用截图服务)
convertedContent = convertedContent.replace(/```[\s\S]*?```/g, "[代码图片]");
}
// 处理链接
if (format.linkStyle === "footnote") {
// 将内联链接转为脚注
const links: string[] = [];
convertedContent = convertedContent.replace(/\[([^\]]+)\]\(([^)]+)\)/g, (_, text, url) => {
links.push(url);
return `${text}[${links.length}]`;
});
if (links.length > 0) {
convertedContent += "\n\n---\n参考链接:\n" + links.map((l, i) => `[${i + 1}] ${l}`).join("\n");
}
} else if (format.linkStyle === "none") {
// 移除链接
convertedContent = convertedContent.replace(/\[([^\]]+)\]\([^)]+\)/g, "$1");
}
// 长度限制
if (format.maxLength && convertedContent.length > format.maxLength) {
convertedContent = convertedContent.slice(0, format.maxLength - 100) + "...\n\n(完整内容见评论区)";
}
return JSON.stringify({
platform,
originalLength: content.length,
convertedLength: convertedContent.length,
convertedContent,
notes: [],
});
},
{
name: "format_convert",
description: "将内容转换为目标平台的格式",
schema: z.object({
content: z.string().describe("原始内容(Markdown)"),
fromFormat: z.string().default("markdown"),
toFormat: z.string().default("platform"),
platform: z.enum(["zhihu", "wechat", "xiaohongshu", "toutiao", "juejin"]),
}),
}
);
// 图片搜索工具
const imageSearchTool = tool(
async ({ query, count }) => {
// 调用图片搜索 API(如 Unsplash、Pexels)
const imageService = new ImageService();
const images = await imageService.search(query, count);
return JSON.stringify({
query,
images: images.map((img: any) => ({
url: img.url,
thumbnailUrl: img.thumbnailUrl,
description: img.description,
author: img.author,
license: img.license,
})),
});
},
{
name: "image_search",
description: "搜索免费可商用的图片作为封面或配图",
schema: z.object({
query: z.string().describe("搜索关键词"),
count: z.number().default(5).describe("结果数量"),
}),
}
);
// 标签推荐工具
const tagRecommendTool = tool(
async ({ content, platform }) => {
const model = new ChatOpenAI({ model: "gpt-4o", temperature: 0.3 });
const result = await model.invoke([
{ role: "system", content: `你是${platform}平台的运营专家。根据内容推荐合适的标签/话题。
返回 JSON:{ "tags": ["标签1", "标签2", ...], "reasoning": "推荐理由" }` },
{ role: "user", content: content.slice(0, 2000) },
]);
return result.content;
},
{
name: "tag_recommend",
description: "根据内容推荐平台话题标签",
schema: z.object({
content: z.string().describe("文章内容"),
platform: z.string().describe("目标平台"),
}),
}
);
const PUBLISHER_PROMPT = `你是内容发布专家,负责将文章适配到不同平台。
## 各平台特点
### 知乎
- 标题:干货型、疑问型效果好
- 内容:深度长文,可带代码
- 标签:选择相关话题,增加曝光
- 封面:可选,建议信息图
### 微信公众号
- 标题:吸睛、引导点击
- 内容:图文并茂,链接放文末
- 代码:建议截图展示
- 封面:必须,尺寸 900x383
### 小红书
- 标题:emoji + 关键词
- 内容:口语化,分段短
- 图片:必须多图
- 长度:控制在 1000 字内
### 头条号
- 标题:数字、悬念效果好
- 内容:小标题分隔,易读
- 封面:三图或大图
### 掘金
- 标题:技术关键词 + 价值
- 内容:代码示例丰富
- 标签:技术栈相关
## 任务流程
1. 使用 seo_analyze 分析和优化 SEO
2. 使用 format_convert 转换平台格式
3. 使用 image_search 选择封面图
4. 使用 tag_recommend 推荐标签`;
export function createPublisherAgent() {
const model = new ChatOpenAI({
model: "gpt-4o",
temperature: 0.5,
});
const tools = [
seoAnalyzeTool,
formatConvertTool,
imageSearchTool,
tagRecommendTool,
];
return createReactAgent({
llm: model,
tools,
messageModifier: new SystemMessage(PUBLISHER_PROMPT),
});
}
export async function publisherNode(state: ContentCreatorStateType) {
const { editedArticle, request } = state;
if (!editedArticle) {
throw new Error("文章未编辑,无法发布");
}
const agent = createPublisherAgent();
const platformContents: PlatformContent[] = [];
// 为每个目标平台生成适配内容
for (const platform of request.platforms) {
const instruction = `
请将以下文章适配到 ${platform} 平台:
## 文章标题
${editedArticle.title}
## 文章内容
${editedArticle.sections.map(s => `## ${s.heading}\n\n${s.content}`).join("\n\n")}
## 关键词
${request.keywords?.join(", ") || "无指定"}
请执行以下操作:
1. SEO 分析和优化
2. 格式转换适配 ${platform}
3. 搜索合适的封面图
4. 推荐话题标签
最后输出 ${platform} 平台的:
1. 优化后的标题
2. 适配后的内容
3. SEO 数据
4. 封面图 URL
5. 推荐标签
`;
const result = await agent.invoke({
messages: [{ role: "user", content: instruction }],
});
// 解析平台内容
const platformContent = parsePlatformContent(result.messages, platform, editedArticle);
platformContents.push(platformContent);
}
return {
currentPhase: "adapt",
platformContents,
messages: [new AIMessage({
content: `[平台适配] 已适配 ${platformContents.length} 个平台`,
})],
};
}
function parsePlatformContent(messages: any[], platform: string, article: any): PlatformContent {
let seo = { keywords: [], description: "", keywordDensity: 0 };
let coverImage = "";
let tags: string[] = [];
let content = article.sections.map((s: any) => `## ${s.heading}\n\n${s.content}`).join("\n\n");
for (const msg of messages) {
if (msg._getType() === "tool") {
try {
const data = JSON.parse(msg.content);
if (data.keywords) {
seo = {
keywords: data.keywords,
description: data.descriptionSuggestion || "",
keywordDensity: Object.values(data.keywordDensity || {})[0] as number || 0,
};
}
if (data.convertedContent) {
content = data.convertedContent;
}
if (data.images) {
coverImage = data.images[0]?.url || "";
}
if (data.tags) {
tags = data.tags;
}
} catch (e) {
// 忽略
}
}
}
return {
platform,
title: article.title,
content,
seo,
coverImage,
tags,
formatting: {
hasImages: content.includes("[图片]") || content.includes("!["),
hasCode: content.includes("```"),
estimatedReadTime: Math.ceil(content.length / 500),
},
};
}4.7 Middleware(中间件)
typescript
// src/agent/middleware/index.ts
import type { PlatformContent, QualityReport } from "../types";
// 中间件接口
interface Middleware {
name: string;
process: (content: PlatformContent) => Promise<MiddlewareResult>;
}
interface MiddlewareResult {
passed: boolean;
score?: number;
issues?: Array<{
severity: "error" | "warning" | "info";
message: string;
suggestion?: string;
}>;
modifiedContent?: string;
}
// 敏感词过滤中间件
const sensitiveWordMiddleware: Middleware = {
name: "sensitiveWord",
async process(content) {
const sensitiveWords = await loadSensitiveWords();
const issues: MiddlewareResult["issues"] = [];
let modifiedContent = content.content;
for (const word of sensitiveWords) {
if (modifiedContent.includes(word)) {
issues.push({
severity: "error",
message: `检测到敏感词:${word}`,
suggestion: "建议替换或删除",
});
// 可选:自动替换
modifiedContent = modifiedContent.replace(new RegExp(word, "g"), "***");
}
}
return {
passed: issues.filter(i => i.severity === "error").length === 0,
issues,
modifiedContent,
};
},
};
// SEO 优化中间件
const seoOptimizeMiddleware: Middleware = {
name: "seoOptimize",
async process(content) {
const issues: MiddlewareResult["issues"] = [];
let score = 100;
// 检查标题长度
if (content.title.length < 10) {
issues.push({
severity: "warning",
message: "标题过短,建议 10-30 字",
suggestion: "增加描述性词语",
});
score -= 10;
}
if (content.title.length > 50) {
issues.push({
severity: "warning",
message: "标题过长,可能被截断",
suggestion: "精简标题,控制在 30 字内",
});
score -= 10;
}
// 检查关键词密度
const density = content.seo.keywordDensity;
if (density < 1) {
issues.push({
severity: "info",
message: "关键词密度偏低",
suggestion: "适当增加关键词出现频率",
});
score -= 5;
}
if (density > 5) {
issues.push({
severity: "warning",
message: "关键词密度过高,可能影响阅读",
suggestion: "适当减少关键词堆砌",
});
score -= 15;
}
// 检查 meta description
if (!content.seo.description || content.seo.description.length < 50) {
issues.push({
severity: "info",
message: "缺少或过短的描述",
suggestion: "添加 100-150 字的描述",
});
score -= 5;
}
return {
passed: true,
score: Math.max(0, score),
issues,
};
},
};
// 可读性评分中间件
const readabilityMiddleware: Middleware = {
name: "readability",
async process(content) {
const text = content.content;
const issues: MiddlewareResult["issues"] = [];
// 计算句子平均长度
const sentences = text.match(/[^。!?.!?]+[。!?.!?]/g) || [];
const avgSentenceLength = text.length / sentences.length;
// 计算段落平均长度
const paragraphs = text.split(/\n\n+/);
const avgParagraphLength = text.length / paragraphs.length;
let score = 80;
if (avgSentenceLength > 50) {
issues.push({
severity: "warning",
message: "句子平均长度过长",
suggestion: "拆分长句,每句控制在 30 字内",
});
score -= 15;
}
if (avgParagraphLength > 300) {
issues.push({
severity: "info",
message: "段落较长,建议适当分段",
suggestion: "每段 3-5 句话为宜",
});
score -= 5;
}
// 检查是否有小标题
const hasHeadings = /#{1,3}\s/.test(text);
if (!hasHeadings && text.length > 500) {
issues.push({
severity: "info",
message: "长文缺少小标题",
suggestion: "添加小标题帮助读者浏览",
});
score -= 10;
}
return {
passed: true,
score,
issues,
};
},
};
// 质量评分中间件
const qualityScoreMiddleware: Middleware = {
name: "qualityScore",
async process(content) {
// 综合各项指标评分
const metrics = {
length: content.content.length >= 800 ? 20 : Math.round(content.content.length / 40),
structure: /#{1,3}\s/.test(content.content) ? 20 : 10,
hasImage: content.formatting.hasImages ? 15 : 10,
hasList: /[-*]\s/.test(content.content) || /\d+\.\s/.test(content.content) ? 15 : 10,
seoScore: Math.round((content.seo.keywords.length > 0 ? 15 : 5) +
(content.seo.description ? 15 : 0)),
};
const totalScore = Object.values(metrics).reduce((a, b) => a + b, 0);
return {
passed: totalScore >= 60,
score: totalScore,
issues: totalScore < 60 ? [{
severity: "warning",
message: `质量评分 ${totalScore}/100,建议优化`,
suggestion: "参考各项指标进行改进",
}] : [],
};
},
};
// 中间件管道
export async function runMiddlewarePipeline(
content: PlatformContent
): Promise<QualityReport> {
const middlewares = [
sensitiveWordMiddleware,
seoOptimizeMiddleware,
readabilityMiddleware,
qualityScoreMiddleware,
];
const allIssues: QualityReport["issues"] = [];
let overallScore = 0;
let seoScore = 0;
let readabilityScore = 0;
let originalityScore = 85; // 从 Editor 获取
let grammarScore = 90; // 从 Editor 获取
let processedContent = content;
for (const middleware of middlewares) {
const result = await middleware.process(processedContent);
if (result.issues) {
allIssues.push(...result.issues);
}
if (result.modifiedContent) {
processedContent = { ...processedContent, content: result.modifiedContent };
}
// 收集各项得分
if (middleware.name === "seoOptimize") {
seoScore = result.score || 0;
}
if (middleware.name === "readability") {
readabilityScore = result.score || 0;
}
if (middleware.name === "qualityScore") {
overallScore = result.score || 0;
}
}
// 综合计算
const passed = allIssues.filter(i => i.severity === "error").length === 0 &&
overallScore >= 60;
return {
overallScore,
originalityScore,
readabilityScore,
seoScore,
grammarScore,
passed,
issues: allIssues,
};
}
async function loadSensitiveWords(): Promise<string[]> {
// 加载敏感词库
return ["敏感词1", "敏感词2"]; // 示例
}4.8 组装完整 Graph
typescript
// src/agent/content-creator.graph.ts
import { StateGraph, END, START } from "@langchain/langgraph";
import { MemorySaver } from "@langchain/langgraph";
import { AIMessage, HumanMessage } from "@langchain/core/messages";
import { ContentCreatorState, type ContentCreatorStateType } from "./types";
import { orchestratorAgent, routeNextPhase } from "./orchestrator.agent";
import { researcherNode } from "./researcher.agent";
import { writerOutlineNode, writerContentNode } from "./writer.agent";
import { editorNode } from "./editor.agent";
import { publisherNode } from "./publisher.agent";
import { runMiddlewarePipeline } from "./middleware";
// 质量检查节点
async function qualityCheckNode(state: ContentCreatorStateType) {
const { platformContents } = state;
if (!platformContents.length) {
return {
currentPhase: "quality",
qualityReport: null,
messages: [new AIMessage({ content: "[质量检查] 无内容可检查" })],
};
}
// 对第一个平台的内容进行检查(可扩展为全部检查)
const qualityReport = await runMiddlewarePipeline(platformContents[0]);
return {
currentPhase: "quality",
qualityReport,
messages: [new AIMessage({
content: `[质量检查] 总分:${qualityReport.overallScore},${qualityReport.passed ? "通过" : "未通过"}`,
})],
};
}
// 完成节点
async function completeNode(state: ContentCreatorStateType) {
const { platformContents, qualityReport } = state;
return {
messages: [new AIMessage({
content: `内容创作完成!
生成了 ${platformContents.length} 个平台的内容:
${platformContents.map(p => `- ${p.platform}: ${p.title}`).join("\n")}
质量报告:
- 总体评分:${qualityReport?.overallScore || "N/A"}
- 原创性:${qualityReport?.originalityScore || "N/A"}
- 可读性:${qualityReport?.readabilityScore || "N/A"}
- SEO:${qualityReport?.seoScore || "N/A"}
${qualityReport?.issues.length ? `
注意事项:
${qualityReport.issues.map(i => `- [${i.severity}] ${i.message}`).join("\n")}
` : ""}`,
})],
};
}
// 构建 Graph
export function createContentCreatorGraph() {
const workflow = new StateGraph(ContentCreatorState)
// 添加节点
.addNode("orchestrator", orchestratorAgent)
.addNode("researcher", researcherNode)
.addNode("writer_outline", writerOutlineNode)
.addNode("writer_content", writerContentNode)
.addNode("editor", editorNode)
.addNode("publisher", publisherNode)
.addNode("quality_check", qualityCheckNode)
.addNode("complete", completeNode)
// 添加边
.addEdge(START, "orchestrator")
.addConditionalEdges("orchestrator", routeNextPhase, {
researcher: "researcher",
writer_outline: "writer_outline",
writer_content: "writer_content",
editor: "editor",
publisher: "publisher",
quality_check: "quality_check",
complete: "complete",
})
.addEdge("researcher", "orchestrator")
.addEdge("writer_outline", "orchestrator")
.addEdge("writer_content", "orchestrator")
.addEdge("editor", "orchestrator")
.addEdge("publisher", "orchestrator")
.addEdge("quality_check", "orchestrator")
.addEdge("complete", END);
const checkpointer = new MemorySaver();
return workflow.compile({ checkpointer });
}
// 工作流可视化
export function visualizeWorkflow() {
return `
┌─────────────────────────────────────────────────────────────────────────────┐
│ Content Creator Workflow │
├─────────────────────────────────────────────────────────────────────────────┤
│ │
│ ┌─────────┐ │
│ │ START │ │
│ └────┬────┘ │
│ │ │
│ ▼ │
│ ┌─────────────┐ │
│ │ Orchestrator│◄───────────────────────────────────────┐ │
│ └──────┬──────┘ │ │
│ │ │ │
│ ┌─────┴─────────────────────────────────────────┐ │ │
│ │ │ │ │
│ ▼ ▼ ▼ │ │ │
│ ┌──────────┐ ┌────────────┐ ┌────────────┐ │ │ │
│ │Researcher│ │Writer │ │Writer │ │ │ │
│ │(素材收集)│ │(大纲生成) │ │(内容撰写) │ │ │ │
│ └────┬─────┘ └─────┬──────┘ └─────┬──────┘ │ │ │
│ │ │ │ │ │ │
│ └───────────────┴───────────────┘ │ │ │
│ │ │ │
│ ▼ ▼ ▼ │ │ │
│ ┌──────────┐ ┌────────────┐ ┌────────────┐│ │ │
│ │ Editor │ │ Publisher │ │ Quality ││ │ │
│ │(编辑优化)│ │(平台适配) │ │ Check ││ │ │
│ └────┬─────┘ └─────┬──────┘ └─────┬──────┘│ │ │
│ │ │ │ │ │ │
│ └───────────────┴───────────────┴────────┘ │ │
│ │ │
│ ┌────────────┘ │
│ │ │
│ ▼ │
│ ┌─────────┐ │
│ │Complete │ │
│ └────┬────┘ │
│ │ │
│ ▼ │
│ ┌─────────┐ │
│ │ END │ │
│ └─────────┘ │
│ │
└───────────────────────────────────────────────────────────────────────┘
`;
}
// 导出流式调用方法
export async function* streamContentCreator(
request: ContentRequest,
threadId: string
) {
const graph = createContentCreatorGraph();
const config = {
configurable: { thread_id: threadId },
streamMode: "messages" as const,
};
const stream = await graph.stream(
{
messages: [],
request,
currentPhase: "init",
},
config
);
for await (const event of stream) {
yield event;
}
}五、后端项目结构
content-creator-backend/
├── prisma/
│ ├── schema.prisma # 数据库模型定义
│ └── migrations/ # 数据库迁移
├── src/
│ ├── agent/ # LangChain Multi-Agent 系统
│ │ ├── types.ts # 类型定义
│ │ ├── orchestrator.agent.ts # 编排器 Agent
│ │ ├── researcher.agent.ts # 素材收集 Sub-Agent
│ │ ├── writer.agent.ts # 内容撰写 Sub-Agent
│ │ ├── editor.agent.ts # 编辑优化 Sub-Agent
│ │ ├── publisher.agent.ts # 发布适配 Sub-Agent
│ │ ├── middleware/ # 中间件
│ │ │ ├── index.ts
│ │ │ ├── sensitive.middleware.ts
│ │ │ ├── seo.middleware.ts
│ │ │ ├── readability.middleware.ts
│ │ │ └── quality.middleware.ts
│ │ └── content-creator.graph.ts # 完整 Graph 组装
│ ├── config/ # 配置
│ │ └── index.ts
│ ├── controllers/ # 控制器
│ │ ├── article.controller.ts
│ │ ├── generate.controller.ts
│ │ ├── material.controller.ts
│ │ └── publish.controller.ts
│ ├── middlewares/ # Express 中间件
│ │ ├── auth.middleware.ts
│ │ ├── error.middleware.ts
│ │ └── validate.middleware.ts
│ ├── routes/ # 路由
│ │ ├── article.routes.ts
│ │ ├── generate.routes.ts
│ │ ├── material.routes.ts
│ │ ├── publish.routes.ts
│ │ └── index.ts
│ ├── services/ # 服务层
│ │ ├── article.service.ts
│ │ ├── material.service.ts
│ │ ├── publish.service.ts
│ │ ├── search.service.ts # 搜索服务
│ │ ├── trend.service.ts # 热点服务
│ │ └── image.service.ts # 图片服务
│ ├── schemas/ # Zod Schema
│ │ ├── article.schema.ts
│ │ └── generate.schema.ts
│ ├── types/ # TypeScript 类型
│ │ └── index.ts
│ ├── utils/ # 工具函数
│ │ ├── jwt.ts
│ │ ├── logger.ts
│ │ └── response.ts
│ └── app.ts # 应用入口
├── .env
├── .env.example
├── package.json
├── tsconfig.json
└── README.md六、总结
技术要点回顾
| 模块 | 技术栈 | 说明 |
|---|---|---|
| 架构模式 | Subagents | Orchestrator 调度多个专业 Sub-Agent |
| Orchestrator | LangGraph StateGraph | 编排和路由决策 |
| Researcher | createReactAgent + Tools | 网络搜索、趋势分析、竞品研究 |
| Writer | withStructuredOutput | 大纲生成 + 分章节内容撰写 |
| Editor | createReactAgent + Tools | 语法检查、事实核查、原创检测 |
| Publisher | createReactAgent + Tools | SEO优化、格式转换、平台适配 |
| Middleware | 流水线模式 | 敏感词、SEO、可读性、质量评分 |
| 流式输出 | SSE | 实时展示创作进度 |

架构亮点
┌─────────────────────────────────────────────────────────────────────────────┐
│ 项目架构亮点 │
├─────────────────────────────────────────────────────────────────────────────┤
│ │
│ 1. Subagents 模式 - 完美匹配内容创作流水线 │
│ ┌───────────────────────────────────────────────────────────────────┐ │
│ │ Orchestrator → Researcher → Writer → Editor → Publisher │ │
│ │ ↑ ↓ ↓ ↓ ↓ │ │
│ │ └──────────────┴──────────┴─────────┴──────────┘ │ │
│ │ (每阶段完成后汇报给 Orchestrator) │ │
│ └───────────────────────────────────────────────────────────────────┘ │
│ │
│ 2. 中间件模式 - 横切关注点统一处理 │
│ ┌─────────┐ ┌─────────┐ ┌─────────┐ ┌─────────┐ ┌─────────┐ │
│ │敏感词过滤│→│SEO优化 │→│可读性检查│→│质量评分 │→│最终输出 │ │
│ └─────────┘ └─────────┘ └─────────┘ └─────────┘ └─────────┘ │
│ │
│ 3. 分章节生成 - 保证长文质量 │
│ • 先生成大纲,确定结构 │
│ • 逐章节撰写,融入相关素材 │
│ • 避免一次性生成导致的质量下降 │
│ │
│ 4. 多平台适配 - 一次创作,多平台分发 │
│ • 知乎:深度长文 + 代码高亮 │
│ • 公众号:图文并茂 + 链接脚注 │
│ • 小红书:口语化 + emoji + 短文 │
│ │
│ 5. 质量保障 - 多维度检查 │
│ • 原创性检测:防止抄袭 │
│ • 事实核查:确保准确性 │
│ • 语法检查:保证表达规范 │
│ • SEO 优化:提升搜索排名 │
│ │
└─────────────────────────────────────────────────────────────────────────────┘项目亮点
- Subagents 架构:Orchestrator 编排 4 个专业 Sub-Agent,职责分离清晰
- 中间件流水线:统一处理敏感词、SEO、质量检查等横切关注点
- 分章节生成:避免一次性生成长文导致的质量下降
- 多平台适配:一次创作,自动适配知乎、公众号、小红书等平台
- 素材驱动:先收集素材再写作,内容有据可依
- 质量保障:原创检测 + 事实核查 + 语法检查 + SEO 优化
- 结构化输出:大纲、文章、质量报告都是结构化数据
扩展方向
- 添加 Translator Sub-Agent:支持多语言翻译
- 接入 AI 配图生成:自动生成文章配图
- 支持 批量创作:基于关键词列表批量生成文章
- 添加 A/B 标题测试:生成多个标题供选择
- 接入 自动发布:通过平台 API 一键发布
- 支持 用户反馈学习:根据阅读数据优化内容策略
- 添加 模板系统:保存优秀文章结构作为模板复用