Skip to content

Latest commit

 

History

History
543 lines (419 loc) · 21.6 KB

File metadata and controls

543 lines (419 loc) · 21.6 KB

核心流程设计说明

Stage 4: Synthesis & Report Generation — Frontmatter 聚合 → Editor-in-Chief 日报合成 → 前端可视化


目录


1. 设计动机

前三阶段(采集、事实提取、深度分析)完成后,系统面临两个核心挑战:

  1. 数据碎片化:244 篇文章分散在 5 个数据源目录中,每篇是独立的 .md 文件,YAML Frontmatter 承载了全部结构化数据,但缺乏统一的聚合视图
  2. 信息过载:244 篇文章 × 32+ 字段 = 7800+ 数据点,人类无法直接消费,需要 AI 驱动的综合合成

Stage 4 的设计目标是将碎片化分析结果转化为一份即看即懂的日报:

  • 结构化输出:JSON 格式,可直接被 Next.js 前端消费
  • 人类可读:Markdown 格式,适合分享、审计、归档
  • 智能合成:不是简单汇总,而是发现跨文章关联、识别趋势、评估风险
  • 预计算可视化:前端零聚合逻辑,直接渲染预计算的图表数据

2. 数据流全景

data/03_analyzed/              data/04_structured/            data/05_reports/
                                               
├── arxiv/209.md ──┐           ├── arxiv.json (209篇)        
├── bensbites/4.md ─┤  Stage   ├── bensbites.json (4篇)      ┌─ daily-report.json
├── kdnuggets/8.md ─┼── 4a ──► ├── kdnuggets.json (8篇)      │  (→ Next.js 看板)
├── techcrunch/10.md─┤  聚合   ├── techcrunch.json (10篇)     │
├── tldrai/13.md ───┘           ├── tldrai.json (13篇)    Stage 4b
                                └── all_articles.json     Editor-in-Chief
                                     ├── articles[244]    (Claude Opus)
                                     └── sources 统计摘要 ──► └─ daily-report.md
                                                               (人类阅读/审计)

数据量级

  • Stage 4a 输出:6 个 JSON 文件,总计约 3.8MB
  • Stage 4b Prompt:~38K tokens(system 1.5K + user 36.5K)
  • Stage 4b 输出:~8K tokens(日报 JSON)

3. Stage 4a: Frontmatter 聚合

3.1 设计原则

  • 纯机械操作:不调用 LLM,确定性算法,< 1 秒完成
  • 保留完整字段:所有 Frontmatter 字段原样传递,不丢失任何数据
  • 分离输出:每个数据源独立 JSON + 全量合并 JSON,便于按源检查数据质量
  • 源级统计:合并 JSON 中预计算各源的 avg_impact,帮助判断数据源质量

3.2 核心实现

文件: pipeline/aggregation/aggregate_frontmatter.py
入口: aggregate_frontmatter(input_dir, output_dir, dry_run)
CLI:  python pipeline/run.py aggregate [--dry-run]
      注意:aggregate 会在 extract 和 analyze 完成后自动调用,日常无需手动执行。

处理流程

1. 递归 glob data/03_analyzed/**/*.md
                    │
2. 按父目录名分组 ──┼── arxiv/: 209 files
   (source 维度)    ├── bensbites/: 4 files
                    ├── kdnuggets/: 8 files
                    ├── techcrunch/: 10 files
                    └── tldrai/: 13 files
                    │
3. 逐文件提取 ──────► read_frontmatter() → 提取 YAML → 校验 id/title/body
   YAML Frontmatter   - 无 frontmatter → 跳过
                      - 缺少 id/title → 跳过
                      - 正文为空 → 跳过 (死文件)
                    │
4. _serialize_value() ──► 递归转换 datetime/嵌套结构为 JSON 兼容
                    │
5. 批量写入 ────────► per-source JSON:  {source}.json
                    └► 合并 JSON:       all_articles.json
                                         ├── generated_at
                                         ├── total_articles
                                         ├── sources (per-source avg_impact)
                                         └── articles (全量)

3.3 关键函数

函数 职责 特点
_serialize_value(obj) 递归将 Python 对象转为 JSON 安全格式 处理 datetime → ISO, list/dict 递归
_extract_article(filepath, source_dir) 从单 .md 提取 frontmatter 构造文章记录 跳过无 frontmatter、缺 id/title、空正文的文件
aggregate_frontmatter() 主函数:扫描 → 分组 → 提取 → 写入 返回汇总 dict

3.4 输出样例

// all_articles.json (结构)
{
  "generated_at": "2026-05-08T12:00:00Z",
  "source": "data/03_analyzed",
  "total_articles": 244,
  "sources": {
    "arxiv": { "count": 209, "avg_impact": 5.3 },
    "techcrunch": { "count": 10, "avg_impact": 5.0 }
  },
  "articles": [
    {
      "source_dir": "arxiv",
      "id": "a1b2c3d4e5f6",
      "title": "Hallucinations Undermine Trust; Metacognition is a Way Forward",
      "event_type": "infrastructure_update",
      "impact_score": { "score": 4.0, "reason": "..." },
      "entities": { "companies": ["Google"], "technologies": ["Metacognition"] },
      ...
    }
  ]
}

4. Stage 4b: Editor-in-Chief 合成

4.1 设计理念

Stage 4b 的核心理念是**"单次调用、完整输出"**——一次 Claude Opus 调用完成全部合成工作,而不是多轮对话或多 Agent 协作。理由:

  • 上下文完整性:LLM 需要同时看到统计概览 + 高影响力文章详情 + 全量标题列表,才能做出跨文章关联判断
  • 输出一致性:单次调用保证日报各部分的风格、术语、判断逻辑一致
  • 成本控制:一次 ~38K input + ~8K output 的调用,远低于多次中等规模调用的总和

4.2 核心实现

文件:
  pipeline/synthesis/editor_in_chief_agent.py  — Agent 调用 + Prompt 构造
  pipeline/synthesis/run_synthesis.py          — CLI 入口 + 编排
  pipeline/synthesis/report_generator.py        — 校验 + Markdown 生成
  pipeline/synthesis/prompts/system_prompt.py   — System Prompt
  pipeline/synthesis/prompts/user_prompt.py     — User Prompt Builder

处理流程

1. 读取 all_articles.json
        │
2. 构造 Prompt ─────────► System Prompt (1.5K chars)
        │                 User Prompt (36.5K chars)
        │                 ├── Section 1: 统计概览 (event/sentiment/source/entity 分布)
        │                 ├── Section 2: Top 30 文章完整 Frontmatter (按 impactScore 降序)
        │                 └── Section 3: 剩余 214 篇标题+关键指标列表
        │
3. 调用 Claude Opus ────► call_agent_with_retry()
        │                 model: claude-opus-4-7
        │                 max_turns: 1
        │                 max_retries: 3 (exponential backoff)
        │
4. 解析 JSON ───────────► parse_json_response() — 5 级回退策略
        │
5. 结构校验 ───────────► validate_report() — 顶层字段 + 子对象字段 + 枚举值校验
        │
6. 写入文件 ───────────► daily-report.json + daily-report.md

4.3 关键模块

模块 文件 职责
Agent 调用 editor_in_chief_agent.py 读取数据、构造 Prompt、调用 LLM、解析响应
CLI 编排 run_synthesis.py argparse 参数解析、dry-run 预估、进度输出
JSON 校验 report_generator.py::validate_report() 结构完整性检查、枚举值校验、错误/警告分类
Markdown 生成 report_generator.py::generate_markdown() JSON → 可读 Markdown(表格、列表、标题层级)

5. Prompt 工程设计

5.1 System Prompt 结构

文件: pipeline/synthesis/prompts/system_prompt.py
变量: EDITOR_IN_CHIEF_SYSTEM_PROMPT

System Prompt 定义 Agent 的角色、输出契约和质量约束,共三个部分:

第一部分:角色定义

"You are the Editor-in-Chief of the Daily AI Insight Engine, a specialized intelligence briefing for AI industry decision-makers."

明确受众(AI 投资人、产品负责人、工程管理者)、语调(分析性、循证、简洁、无营销话术)。

第二部分:输出 Schema

定义完整的 JSON 结构,含 11 个顶层字段、5 个子对象结构(topEvents, deepDives, trendInsights, riskSignals, opportunitySignals, visualizationData)。每个字段标注类型约束和语言要求(中文文本、英文枚举值)。

第三部分:9 条质控规则

# 规则 目的
1 Distributions 必须从统计概览聚合全部 244 篇文章 防止 LLM 编造分布数据
2 impactRanking 取 Top 10 前端 Impact 排名图的数据源
3 entityFrequency 聚合全部文章,合并近似实体 实体频次图的数据源
4 Top 5 事件需交叉参考、调整 hype/rumor 权重 事件排序的质控逻辑
5 Deep Dives 选择有战略深远影响的事件 防止选择仅热度高但无长期价值的事件
6 Trend Insights 4 维度各 2-4 个信号 保证四维覆盖完整
7 Risk/Opportunity Signals 必须扎根原文字段 防止 LLM 编造风险/机会
8 文本用中文,枚举值用英文 语言一致性
9 只输出纯 JSON,无 Markdown 包裹 确保可机器解析

5.2 User Prompt 结构

文件: pipeline/synthesis/prompts/user_prompt.py
函数: build_user_prompt(all_articles, max_detail=30)

User Prompt 分四部分,按信息密度递减排列:

Section 1: 统计概览(~2K tokens)

预计算的统计摘要,由 _compute_statistics() 从 244 篇文章中聚合:

  • Event Type 分布(5 类)
  • Sentiment 分布(4 类)
  • Source Type 分布(academic_paper / tech_blog / news_media / community_discussion)
  • Epistemic Status 分布(confirmed / disputed / rumor_leak / unknown)
  • 各数据源文章数
  • Top 50 实体频次(按类型分组:company / technology / person)

Section 2: Top 30 文章完整 Frontmatter(~20K tokens)

impactScore 降序排列的前 30 篇,每篇展开:

  • 基础信息:id, title, source_dir, source_type, published, tldr
  • 事实提取:event_type, epistemic_status, key_logic_flow(6 条)
  • 定性研判:impact_score, sentiment, developer_sentiment, hype_assessment, information_entropy, engineering_complexity
  • 价值评估:compound_value, value_capture_layer, moat_impact, key_beneficiaries, competitive_casualty
  • 前瞻预测:market_opportunities, risk_matrix, actionable_insight

约 22 个字段/篇 × 30 篇 = 660 个字段值。

Section 3: 剩余文章标题列表(~15K tokens)

214 篇文章,每篇仅一行:

[source_dir] title | impact=X | event=xxx | sentiment=xxx

让 LLM 感知全量数据分布,但不占用过多 token 预算。

Section 4: 指令(~0.5K tokens)

明确输出要求:必须聚合全量、语言规则、纯 JSON 输出。

5.3 文章排序策略

def _impact_score(a: dict) -> float:
    iscore = a.get("impact_score", {})
    if isinstance(iscore, dict):
        return float(iscore.get("score", 0))
    return float(iscore) if iscore else 0.0

sorted_articles = sorted(all_articles, key=_impact_score, reverse=True)

impact_score.score 降序排列,Top 30 获得完整展示,其余仅标题。这保证了 LLM 首先处理高价值文章。

5.4 Token 预算

Prompt 部分 估算 tokens 占比
System Prompt ~1,500 4%
统计概览 ~2,000 5%
Top 30 完整 Frontmatter ~20,000 52%
剩余文章标题 ~15,000 39%
总计 ~38,000 100%

Context window 占用率:~19%(Claude Opus 4 200K window),留有充足余量。


6. JSON 解析与校验

6.1 5 级 JSON 回退策略

文件: pipeline/core/agent.py
函数: parse_json_response(text)

LLM 输出可能被 Markdown 代码块包裹、截断、或混有解释文本。系统采用 5 级回退:

级别 策略 适用场景
1 json.loads() 直接解析 LLM 输出纯 JSON,无包裹
2 正则提取 ```json ... ``` 代码块 LLM 用 Markdown 包裹 JSON
3 查找第一个 { 和最后一个 } JSON 嵌入在解释文本中
4 去除 Markdown 代码块标记后重试 处理不规范的 code fence
5 截断 JSON 恢复(_close_json 补全括号 + 逐步修剪) LLM 输出被 token 限制截断

6.2 日报结构校验

文件: pipeline/synthesis/report_generator.py
函数: validate_report(report) → { valid: bool, errors: [], warnings: [] }

校验分层进行:

  • 顶层字段(11 个必需 key):date, generatedAt, reportTitle, executiveSummary, dataSourceSummary, topEvents, deepDives, trendInsights, riskSignals, opportunitySignals, visualizationData
  • 子对象字段(逐项检查):topEvents[i]title/articleIds/eventType/impactScore/whyItMatters/evidence
  • 枚举值校验eventType 必须是 5 个合法值之一、severity 必须是 low/medium/highdimension 必须是 technology/application/policy/capital

返回值区分 errors(阻断性)和 warnings(提醒性),校验失败不阻断文件写入,但会在日志中醒目提示。


7. Markdown 报告生成

文件: pipeline/synthesis/report_generator.py
函数: generate_markdown(report) → str

7.1 输出结构

段落 格式 内容
YAML Frontmatter --- 包裹 title, date, generated
执行摘要 纯文本段落 executiveSummary
数据概览 Markdown 表格 样本总量 / 信源数 / 语言覆盖
Top 事件 三级标题 + 列表 5 事件 × (类型/评分/重要性/证据)
深度分析 三级标题 + 子段落 3 篇 × (背景/影响/后续关注)
趋势判断 三级标题 + 列表 4 维度 × (判断/支撑信号)
风险提示 Markdown 表格 严重程度 / 信号 / 判断依据
机会提示 Markdown 表格 严重程度 / 信号 / 判断依据
信源说明 纯文本段落 selectionRationale

7.2 标签映射

Markdown 中的标签文本与前端 labels.ts 保持一致:

字段 中文标签
infrastructure_update 基建更新
framework_tools 框架工具
capital_movement 资本动向
application_landing 应用落地
policy_and_safety 政策与安全
low / medium / high 低 / 中 / 高
technology / application / policy / capital 技术 / 应用 / 政策 / 资本

8. 前端数据消费

8.1 数据桥接路径

Dashboard 页 (/dashboard)

data/05_reports/daily-report.json
        │
        ▼
src/app/dashboard/page.tsx
    getReport() {
        readFile("data/05_reports/daily-report.json")
        → JSON.parse()
        → dailyReportSchema.parse()  ← Zod 校验
        → 传递给组件树
    }

Sources 页 (/, 首页)

pipeline/config.yaml  +  data/00_manifest/*.json
        │
        ▼
src/app/page.tsx
    getSourcesViewData() {
        getSourceConfigs()  →  解析 config.yaml (enabled: true 源 + tiers_meta)
        loadManifests()     →  读取所有 manifest JSON
        configToStatus()    →  合并为 SourceStatus[]
        → { tiersMeta, sources, totalSources, totalArticles, latestDate }
    }

Source Detail 页 (/sources/[name])

pipeline/config.yaml  +  data/00_manifest/{source}_*.json
        │
        ▼
src/app/sources/[name]/page.tsx
    getSourceDetail(name)  →  SourceStatus | null
    getSourceDetailEnriched(name)  →  EnrichedSourceDetail (含 articles)

8.2 关键设计决策

决策 理由
服务端直接读文件 无网络请求、无 API 路由、无中间层,零延迟
force-dynamic 禁用 Next.js 静态优化,每次请求重新读取文件,确保数据最新
Zod 校验前置 在服务端渲染前校验,损坏的 JSON 不会渲染到页面,而是抛出清晰的 ZodError
可视化数据预计算 visualizationData 由 LLM 在合成阶段生成,前端零聚合。eventTypeDistribution 和 sentimentDistribution 的总计数必须等于 totalArticles
Markdown fallback /report 页优先读取预生成的 .md,文件缺失时从 JSON 动态生成(generateMarkdown()
配置驱动 Sources UI Sources 页面的展示文案由 config.yamltiers_metadisplay_namedisplay_description 驱动,新增数据源无需改前端代码

8.3 前端组件数据依赖

Dashboard 页

ReportHeader          ← reportTitle, date, generatedAt, executiveSummary
KPISection            ← dataSourceSummary { totalArticles, sources, languages }
DistributionSection   ← visualizationData { eventTypeDistribution, sentimentDistribution }
TopEventsSection      ← topEvents[]
RankingsSection       ← visualizationData { impactRanking, entityFrequency }
TrendInsightsSection  ← trendInsights[]
DeepDivesSection      ← deepDives[]
SignalList ×2         ← riskSignals[], opportunitySignals[]
ReportFooter          ← dataSourceSummary.selectionRationale

Sources 页

SourcesHero           ← tiersMeta, totalSources, totalArticles, latestDate, tier counts
SourcesGrid           ← sources[], tiersMeta
  TierSection         ← tier, source[], tierMeta (彩色竖条 + 标题 + 副标题)
    SourceCard        ← SourceStatus (display_name, display_description, keywords, ...)

9. CLI 集成

9.1 统一入口

文件: pipeline/run.py

所有阶段通过 python pipeline/run.py <subcommand> 调用:

命令 阶段 说明
scout Stage 1a 生成 URL 清单
ingest Stage 1b 下载清洗正文
extract Stage 2 事实提取
analyze Stage 3 深度分析
aggregate Stage 4a Frontmatter 聚合(extract/analyze 后自动执行,独立运行用于配置变更或日期回溯)
synthesize Stage 4b 日报合成(支持 --target-date 回溯历史日报)
backfill-ids 工具 为已有 .md 补充 article.id

9.2 synthesize 子命令参数

python pipeline/run.py synthesize [options]

Options:
  --input, -i      all_articles.json 路径 (默认: data/04_structured/all_articles.json)
  --output, -o     输出目录 (默认: data/05_reports/)
  --model, -m      LLM 模型名称 (默认: claude-opus-4-7)
  --max-detail     完整展示的文章数 (默认: 30)
  --dry-run        仅显示 prompt 预估,不调用 LLM
  --lookback-days  先按 N 天窗口重新聚合,再合成 (默认: 跳过)
  --target-date    精确日期过滤,先按目标日期聚合再合成 (与 --lookback-days 互斥)
  --verbose, -v    显示详细日志

9.3 dry-run 模式

$ python pipeline/run.py synthesize --dry-run

=== Stage 4b: Editor-in-Chief 合成 ===
  文章总数: 244
  数据源: ['arxiv', 'bensbites', 'kdnuggets', 'techcrunch', 'tldrai']
  完整展示: 前 30 篇

  >>> DRY RUN — 不调用 LLM <<<
  System prompt:  4678 chars
  User prompt:    115705 chars
  估算 tokens:    ~38568 tokens (rough estimate)

dry-run 模式构造完整 User Prompt 但跳过 LLM 调用,用于:

  • 评估 token 消耗
  • 检查 Top 30 文章选择是否正确
  • 调试 prompt 结构

10. 关键设计决策

10.1 为什么 Stage 4 拆分为 4a + 4b?

对比维度 合并为一步 拆分 4a + 4b
调试体验 看不到中间 JSON,问题定位困难 可先检查 4a 产物,确认数据完整性后再跑 4b
LLM 成本 每次调 prompt 都要重新聚合 4a 一次聚合后可反复跑 4b,无需重复
失败恢复 LLM 调用失败需从 4a 重来 4a 纯机械,几乎不会失败;4b 失败可直接重试

10.2 为什么 Top 30 而不是全量详细?

  • Token 预算:244 篇全量详细 = ~160K tokens,超过 context window 的合理使用比例
  • 信息收益递减:低 impactScore 文章(< 3)的详细分析对日报质量贡献有限
  • 统计概览兜底:Section 1 的全量统计保证了低分文章的信息不被完全忽略

10.3 为什么单次调用而不是多 Agent 协作?

  • 上下文完整性:LLM 需要同时看到所有数据才能做出跨文章关联判断
  • 成本经济性:一次 ~38K input 调用 < 多次分散调用
  • Schema 一致性:单次调用保证日报各部分风格、术语、判断逻辑统一

10.4 为什么前端直接读文件而不是 API 路由?

  • 零延迟:无网络请求、无中间层、无序列化开销
  • 简化部署:前端可以直接从 data/05_reports/ 静态读取,适合 Vercel / 静态托管
  • 可审计性:生成的 JSON 即是前端数据源,可以版本控制、diff、归档

相关文档: