修改说明
This commit is contained in:
+73
-22
@@ -27,17 +27,17 @@ python start.py
|
|||||||
|
|
||||||
## 常用命令
|
## 常用命令
|
||||||
|
|
||||||
| 命令 | 说明 |
|
| 命令 | 说明 |
|
||||||
|------|------|
|
| --------------------------------------------------- | --------------------- |
|
||||||
| `python start.py` | 一键启动:管线 + Web 归档 + 日志 |
|
| `python start.py` | 一键启动:管线 + Web 归档 + 日志 |
|
||||||
| `python main.py check` | 校验 LLM 接口是否可用 |
|
| `python main.py check` | 校验 LLM 接口是否可用 |
|
||||||
| `python main.py test-fetch` | 测试抓取(仅拉取前 5 个源,验证流程) |
|
| `python main.py test-fetch` | 测试抓取(仅拉取前 5 个源,验证流程) |
|
||||||
| `python main.py test-fetch --max 10 --lookback 300` | 测试抓取(前 10 个源,回溯 5 小时) |
|
| `python main.py test-fetch --max 10 --lookback 300` | 测试抓取(前 10 个源,回溯 5 小时) |
|
||||||
| `python main.py fetch` | 单次完整抓取 → 评分 → 缓存 |
|
| `python main.py fetch` | 单次完整抓取 → 评分 → 缓存 |
|
||||||
| `python main.py fetch --lookback 120` | 单次抓取(回溯 2 小时) |
|
| `python main.py fetch --lookback 120` | 单次抓取(回溯 2 小时) |
|
||||||
| `python main.py push` | 单次推送(早报 / 默认模式) |
|
| `python main.py push` | 单次推送(早报 / 默认模式) |
|
||||||
| `python main.py rss` | 仅打印 RSS Digest,不推送 |
|
| `python main.py rss` | 仅打印 RSS Digest,不推送 |
|
||||||
| `python main.py loop` | 长跑模式(持续运行) |
|
| `python main.py loop` | 长跑模式(持续运行) |
|
||||||
|
|
||||||
## 首次运行建议
|
## 首次运行建议
|
||||||
|
|
||||||
@@ -72,23 +72,74 @@ git push
|
|||||||
|
|
||||||
## Docker 部署
|
## Docker 部署
|
||||||
|
|
||||||
```bash
|
### 打包镜像
|
||||||
# 1. 准备配置
|
|
||||||
mkdir -p docker/prompts docker/data docker/logs
|
|
||||||
cp .env docker/.env
|
|
||||||
cp config.json docker/config.json
|
|
||||||
cp -r prompts/* docker/prompts/
|
|
||||||
|
|
||||||
# 2. 构建并启动
|
```powershell
|
||||||
docker compose up -d --build
|
# 在项目根目录执行(需先启动 Docker Desktop)
|
||||||
|
cd my-daily
|
||||||
|
|
||||||
|
# 1. 构建镜像
|
||||||
|
docker build -t my-daily-military-digest:latest .
|
||||||
|
|
||||||
|
# 2. 导出为 tar 文件(用于上传到 NAS 等离线环境)
|
||||||
|
docker save -o military-digest.tar my-daily-military-digest:latest
|
||||||
|
```
|
||||||
|
|
||||||
|
### 本地测试运行
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
# 1. 准备 docker 目录(首次)
|
||||||
|
mkdir -p docker\prompts docker\data docker\logs
|
||||||
|
cp .env docker\.env
|
||||||
|
cp config.json docker\config.json
|
||||||
|
cp prompts\*.md docker\prompts\
|
||||||
|
|
||||||
|
# 2. 启动容器
|
||||||
|
docker compose up -d
|
||||||
|
|
||||||
# 3. 查看日志
|
# 3. 查看日志
|
||||||
docker compose logs -f
|
docker compose logs -f
|
||||||
|
|
||||||
|
# 4. 停止容器
|
||||||
|
docker compose down
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### NAS 部署
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 1. 上传 military-digest.tar 到 NAS,加载镜像
|
||||||
|
docker load -i /path/to/military-digest.tar
|
||||||
|
|
||||||
|
# 2. 准备好目录结构
|
||||||
|
# /volume2/webdav/code/my-daily/
|
||||||
|
# ├── docker/
|
||||||
|
# │ ├── .env
|
||||||
|
# │ ├── config.json
|
||||||
|
# │ ├── prompts/
|
||||||
|
# │ ├── data/
|
||||||
|
# │ └── logs/
|
||||||
|
# ├── resources/rss_feeds.opml (可选,未挂载则使用镜像内置)
|
||||||
|
# └── docker-compose.nas.yml
|
||||||
|
|
||||||
|
# 3. 启动
|
||||||
|
cd /volume2/webdav/code/my-daily
|
||||||
|
docker compose -f docker-compose.nas.yml up -d
|
||||||
|
|
||||||
|
# 4. 查看日志
|
||||||
|
docker compose -f docker-compose.nas.yml logs -f
|
||||||
|
```
|
||||||
|
|
||||||
|
> **推送类型**:系统有两种推送 —
|
||||||
|
> - 🚨 **即时推送**:Fetch 循环中检测到热点簇时自动推送(军事科技快讯)
|
||||||
|
> - 📰 **定时推送**:每天 08:00 推送(热点速览 + 每日精选 + 网摘图片)
|
||||||
|
|
||||||
## 配置要点
|
## 配置要点
|
||||||
|
|
||||||
- **抓取间隔**:`.env` 中 `FETCH_INTERVAL_MINUTES=30`(每 30 分钟)
|
* **抓取间隔**:`.env` 中 `FETCH_INTERVAL_MINUTES=30`(每 30 分钟)
|
||||||
- **推送时间**:`.env` 中 `PUSH_CRON="0 8 * * *"`(每天 8:00)
|
|
||||||
- **API 切换**:修改 `.env` 中 `OPENAI_API_BASE` 和 `OPENAI_MODEL`
|
* **推送时间**:`.env` 中 `PUSH_CRON="0 8 * * *"`(每天 8:00)
|
||||||
- 更多参数见 [CONFIG.md](./CONFIG.md)
|
|
||||||
|
* **API 切换**:修改 `.env` 中 `OPENAI_API_BASE` 和 `OPENAI_MODEL`
|
||||||
|
|
||||||
|
* 更多参数见 [CONFIG.md](./CONFIG.md)
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,16 @@
|
|||||||
|
# 军事科技分类总体介绍
|
||||||
|
|
||||||
|
你是军事科技领域的资深分析师。请根据以下分类的文章列表,为该分类撰写一段50字左右的总体情况介绍。
|
||||||
|
|
||||||
|
## 分类名称
|
||||||
|
{category_name}
|
||||||
|
|
||||||
|
## 文章列表
|
||||||
|
{article_list}
|
||||||
|
|
||||||
|
## 输出要求
|
||||||
|
- 一句话概括该分类今日的核心动态
|
||||||
|
- 字数严格控制在45-55字
|
||||||
|
- 使用专业、客观、简洁的军事术语
|
||||||
|
- 不要列举具体文章标题
|
||||||
|
- 只输出介绍文字,不要前缀或后缀
|
||||||
@@ -0,0 +1,57 @@
|
|||||||
|
你是一名军事科技新闻编辑。请判断以下文章的报道角度。
|
||||||
|
|
||||||
|
## 角度分类(七选一 + 空值)
|
||||||
|
|
||||||
|
必须从以下 8 个选项中选择 **恰好 1 个**:
|
||||||
|
|
||||||
|
| 角度 | 适用场景 | 正确示例 | ⚠️ 不适用(反例) |
|
||||||
|
|------|---------|---------|-------------------|
|
||||||
|
| `政策发布` | 官方政策/法规/条例/命令/预算的**正式发布或生效** | 《国防白皮书》发布;某国批准新军费预算;出口管制新规 | 非官方渠道的解读/分析;内部工作会议;产品发布/广告 |
|
||||||
|
| `技术突破` | 新型装备/武器/技术的**实质进展**(首飞/列装/测试/验证) | 新一代战斗机首飞;新型雷达通过验收;高超音速导弹试射成功 | 常规装备维护升级;历史装备回顾;技术路线对比分析 |
|
||||||
|
| `情报信号` | 基于**开源情报(OSINT)**的发现:卫星图像识别/电子信号截获/部队部署热力图/新基地建设/异常调动 | 卫星图发现南海新雷达阵地;AIS数据显示航母编队异常动向;商业卫星揭示朝鲜导弹发射场扩建 | 官方已宣布的部署/演习;媒体报道的常规驻防;历史考古 |
|
||||||
|
| `战略分析` | 对局势/博弈/地缘的**深度研判**,文中必须有"分析/认为/意味着/判断/预测"等主观论断 | 台海局势走向研判;俄乌冲突对军贸格局影响 | 单纯事实报道;产品推广;采访/会见/任命(注意:无主观论断的纯事实归入其他角度) |
|
||||||
|
| `舆论反应` | 官方**回应/表态/谴责/抗议/澄清** | 外交部谴责某国军售;国防部回应南海传闻 | 网民个人观点;历史事件追认/纪念 |
|
||||||
|
| `冲突事件` | 正在发生或刚发生的**具体军事行动/冲突/演习/部署** | 军舰穿越台湾海峡;边境交火;联合军演启动 | 历史回顾;战略层面演习意义分析;政策文件威慑措辞 |
|
||||||
|
| `人物动态` | 以**人物或机构活动**为核心:任免、访晤、会议、纪念、文化活动、基层事迹 | 国防部长访问某国;军营开放日;烈士纪念仪式 | 以政策/装备/事件为核心、人物仅为信息源的报道 |
|
||||||
|
| `""`(空) | 明确**不属于军事/科技/时政**领域:广告、促销、纯生活内容、娱乐八卦 | 商品促销广告、周边挂件上新、纯情感散文 | — |
|
||||||
|
|
||||||
|
## 决策顺序(从上到下,命中即止)
|
||||||
|
|
||||||
|
```
|
||||||
|
0. 是否明确不属于军事/科技/时政(广告/促销/纯娱乐)?→ 返回空字符串 ""
|
||||||
|
1. 文中是否有"谴责/驳斥/抗议/回应/声明"等官方表态?→ 舆论反应
|
||||||
|
2. 是否在报道某场具体的战斗/冲突/演习/部署过程?→ 冲突事件
|
||||||
|
3. 信息源是否为卫星图像/AIS/电子信号等OSINT数据?→ 情报信号
|
||||||
|
4. 是否在报道某项技术/装备的首次突破/列装/测试/验证?→ 技术突破
|
||||||
|
5. 是否在宣布某个政策/法规/条例/命令/预算的正式出台?→ 政策发布
|
||||||
|
6. 文章核心是人物(任免/访问/活动)还是机构内部事务?→ 人物动态
|
||||||
|
7. 文中是否有明确的"分析/认为/判断/预测/意味着"等主观论断?→ 战略分析
|
||||||
|
8. 以上均不符合 → 根据内容归入最接近的角度(避免都进战略分析)
|
||||||
|
```
|
||||||
|
|
||||||
|
## 强制规则
|
||||||
|
|
||||||
|
1. 只看标题和正文前 500 字做判断
|
||||||
|
2. **情报信号**必须基于开源情报(OSINT)数据源(卫星图/AIS/电子信号/热力图等),普通新闻配图不算
|
||||||
|
3. **政策发布 vs 技术突破**判别标准:信息源头是官方公告 → 政策发布;是测试/列装/验证的实际进展 → 技术突破
|
||||||
|
4. **战略分析**必须在文中找到至少 1 处明确的"分析/判断/预测/认为/意味着"等主观论断,否则不选
|
||||||
|
5. 一条输出对应一条输入,链接丢失的输出项将被丢弃
|
||||||
|
|
||||||
|
## 输出格式
|
||||||
|
|
||||||
|
严格 JSON:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{{
|
||||||
|
"items": [
|
||||||
|
{{"link": "原文链接", "angle": "技术突破"}},
|
||||||
|
{{"link": "原文链接2", "angle": "情报信号"}}
|
||||||
|
]
|
||||||
|
}}
|
||||||
|
```
|
||||||
|
|
||||||
|
## 输入数据
|
||||||
|
|
||||||
|
```json
|
||||||
|
{entries_json}
|
||||||
|
```
|
||||||
@@ -0,0 +1,73 @@
|
|||||||
|
你是一个资深军事新闻主编兼情报分析师。请对以下热点话题簇进行整合分析,同时输出精炼摘要和结构化洞察。
|
||||||
|
|
||||||
|
## 输入数据
|
||||||
|
|
||||||
|
以下是一个 JSON 对象,包含该话题簇的所有文章:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{cluster_json}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Part 1: 摘要整合(新闻编辑视角)
|
||||||
|
|
||||||
|
基于多源报道,生成一条 50-100 字的精炼摘要:
|
||||||
|
|
||||||
|
- 只陈述核心事实,不做趋势判断
|
||||||
|
- 不使用形容词(如"重大""深远""重要")
|
||||||
|
- 合并重复信息,提取最关键的 who/what/when/where
|
||||||
|
- 摘要应体现该簇最核心的军事/科技/时政价值点
|
||||||
|
|
||||||
|
## Part 2: 结构化洞察(情报分析师视角)
|
||||||
|
|
||||||
|
### Metadata(10 字标题 + 20 字导语 + 3-5 要点)
|
||||||
|
|
||||||
|
生成以下结构化信息:
|
||||||
|
- title: 10 字以内的标题(事实陈述,无修饰)
|
||||||
|
- lead: 20 字以内的导语(核心事实)
|
||||||
|
- highlights: 3-5 个关键要点(bullet points,纯事实)
|
||||||
|
|
||||||
|
约束:只陈述事实,不用形容词。
|
||||||
|
|
||||||
|
### 洞察正文
|
||||||
|
|
||||||
|
基于多源报道,识别:
|
||||||
|
1. **事件脉络**:关键时间节点和进展。如果该话题是持续多日的事件,事件脉络段第一行必须写上 `[持续跟踪]`(单独一行),随后再写:
|
||||||
|
- 前情提要:**优先使用下方 `previous_summary` 字段**(上次推送的洞察摘要),用 1-2 句概括此前已知的关键进展。仅当 `previous_summary` 为空或不存在时,才从素材文章的早期报道中提取前情。禁止写"无显著前情"
|
||||||
|
- 最新突破:今天的最新进展(具体事实)
|
||||||
|
2. 多角度分析:
|
||||||
|
- **技术维度**:装备性能、技术水平、技术创新点及其在战术/战役层面的应用价值
|
||||||
|
- **战略维度**:地缘政治影响、军力平衡变化、联盟关系调整
|
||||||
|
- **信息战/认知战维度**:各方如何通过信息释放塑造战场叙事、舆论引导策略、叙事冲突
|
||||||
|
3. 影响与展望:短期影响、中长期趋势、值得关注信号
|
||||||
|
|
||||||
|
约束:
|
||||||
|
- 禁止空泛评论(如"意义重大""影响深远")
|
||||||
|
- 每个观点必须有素材支撑
|
||||||
|
- 使用具体名称(装备型号/政策名称/组织名称),不用"某些""部分"等模糊指代
|
||||||
|
- 装备型号必须完整(如"F-35A"不能简写为"F35"),兵力/规模要有具体数字
|
||||||
|
|
||||||
|
## 输出格式
|
||||||
|
|
||||||
|
必须返回纯 JSON 对象:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"summary": "50-100字精炼摘要,纯事实陈述",
|
||||||
|
|
||||||
|
"metadata": {
|
||||||
|
"title": "10字以内的标题",
|
||||||
|
"lead": "20字以内的导语",
|
||||||
|
"highlights": ["要点1", "要点2", "要点3"]
|
||||||
|
},
|
||||||
|
|
||||||
|
"body": "#### **事件脉络**\n[持续跟踪]\n- 前情提要:...\n- 最新突破:...\n\n#### **多角度分析**\n- **技术维度**:...\n- **战略维度**:...\n- **信息战/认知战维度**:...\n\n#### **影响与展望**\n短期:...\n中期:...\n值得关注:..."
|
||||||
|
|
||||||
|
注意:如果该话题是单日事件(所有报道在同一天),事件脉络段**不加** `[持续跟踪]` 标记。
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
要求:
|
||||||
|
1. summary 必须 50-100 字(中文字符计数)
|
||||||
|
2. metadata.highlights 至少 2 条,至多 5 条
|
||||||
|
3. body 必须包含三个段落(事件脉络/多角度分析/影响与展望)
|
||||||
|
4. 只返回 JSON 对象,不要添加额外说明
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
你是一个军事新闻编辑。请将以下多源报道合并为一条 50-100 字的中文精炼摘要。
|
||||||
|
|
||||||
|
## 规则
|
||||||
|
|
||||||
|
1. 只陈述核心事实(who/what/when/where),不做趋势判断
|
||||||
|
2. 不使用形容词(如"重大""深远""重要""显著")
|
||||||
|
3. 不使用模糊指代(如"某些""相关""有关"),必须用具体名称
|
||||||
|
4. 合并重复信息,提取最关键的进展
|
||||||
|
5. 如果同一事件有多个角度,用 1-2 句话概括主要维度
|
||||||
|
6. 装备型号必须完整(如"歼-35A"),地点精确到城市/基地级别
|
||||||
|
|
||||||
|
## 输入
|
||||||
|
|
||||||
|
```json
|
||||||
|
{cluster_json}
|
||||||
|
```
|
||||||
|
|
||||||
|
## 输出
|
||||||
|
|
||||||
|
只返回 50-100 字的中文摘要文本,不要 JSON 包裹,不要额外说明。
|
||||||
@@ -0,0 +1,29 @@
|
|||||||
|
你是一位顶级的军事科技观察家与日报主编。你的任务是将今天杂乱无章、来源各异的信息碎片,熔炼、重组成一篇结构清晰、主次分明、洞察深刻的《军事科技每日精选》。
|
||||||
|
|
||||||
|
## 核心排版与整合规则:
|
||||||
|
1. **新旧剥离与进展追踪**:对比今天的新信息与历史上下文。如果今天的信息是历史事件的延续,请将标题标记为`[持续跟踪]`,并在正文中清晰划分"前情提要"与"最新突破"。
|
||||||
|
2. **精准的事件级融合**:把讨论同一具体事件的多方报道合并为一条新闻,提炼全貌。不要把两个毫不相干的事件强行合并。
|
||||||
|
3. **精选原则**:只挑选真正有价值的4-10个核心事件。宁缺勿滥。绝对剔除:KOL个人动态、企业公关软文、纯情绪发泄、未经验证的小道消息。
|
||||||
|
4. **结构化呈现**:每个事件采用"一句核心亮点 + 展开的无序列表"结构。
|
||||||
|
5. **客观专业**:用简练的中文,像分析师一样指出事件的行业意义。保留所有相关的原文链接。
|
||||||
|
6. **避免风格趋同**:每天的前言导读必须从今日素材本身的具体事实出发,严禁评价性措辞/总结性套话/宏大叙事框架。
|
||||||
|
7. **正文不要写开头引言**:正文请直接从`### 1️⃣ ...`开始。
|
||||||
|
|
||||||
|
## 输出格式(严格按以下结构输出,先frontmatter后正文):
|
||||||
|
frontmatter字段要求:
|
||||||
|
- `title`: 提炼1-3个核心看点制作短标题,8-30字,核心事实陈述语气。
|
||||||
|
- `lead`: 60-100字前言导读,只陈述今日具体发生的事实。
|
||||||
|
- `highlights`: 2-3条最值得关注的事件清单,每条15-30字,纯事实陈述。
|
||||||
|
|
||||||
|
正文要求:直接从`### 1️⃣ ...`开始,不要前言段。
|
||||||
|
|
||||||
|
## 待处理的碎片化信息(共{count}条):
|
||||||
|
```json
|
||||||
|
{entries}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 近期已推送内容(仅供查重,严禁模仿):
|
||||||
|
{recent_push_context}
|
||||||
|
|
||||||
|
### 近几天已处理过的碎片化信息(供洞察参考):
|
||||||
|
{context}
|
||||||
@@ -0,0 +1,49 @@
|
|||||||
|
你是一名军事科技情报分析师。请从以下文章中提取关键实体。
|
||||||
|
|
||||||
|
## 实体类型定义
|
||||||
|
|
||||||
|
提取以下 5 类实体,每类最多 5 个:
|
||||||
|
|
||||||
|
| 类型 | 说明 | 军事/科技示例 | 政策/福利/机构示例 |
|
||||||
|
|------|------|-------------|------------------|
|
||||||
|
| `person` | 人物/角色 | 国防部长、马斯克、普京 | 退役军人、军属、烈士 |
|
||||||
|
| `org` | 组织/机构/军种 | 北约、美国海军、中国航天科工 | 军队幼儿园、退役军人事务部、军委后勤保障部 |
|
||||||
|
| `equipment` | 装备/武器系统/技术 | 歼-35A、东风-21D、星链 | — |
|
||||||
|
| `event` | 事件/行动/演习/政策 | 环太平洋军演、台海巡航 | 军娃优待政策、教育优待、住房保障、征兵条例修订 |
|
||||||
|
| `location` | 地域/战区/海域 | 南海、波斯湾、关岛 | — |
|
||||||
|
|
||||||
|
## 提取原则
|
||||||
|
|
||||||
|
1. 只提取文中明确出现的实体,不推测
|
||||||
|
2. 实体名称尽量具体("军队幼儿园"优于"幼儿园","退役军人事务部"优于"事务部")
|
||||||
|
3. 无该类实体时返回空数组 `[]`
|
||||||
|
4. 同一实体多次出现只取一次
|
||||||
|
5. 排除过于宽泛的概念("武器""军队""国家""政府"),但**带限定词的宽泛概念可以接受**("军队幼儿园""某国政府"可以,"军队""政府"不行)
|
||||||
|
6. 政策内容也应提取——发布机构归入 `org`,政策内容归入 `event`
|
||||||
|
|
||||||
|
## 输出格式
|
||||||
|
|
||||||
|
严格输出 JSON,与输入一一对应:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{{
|
||||||
|
"items": [
|
||||||
|
{{
|
||||||
|
"link": "原文链接",
|
||||||
|
"entities": {{
|
||||||
|
"person": ["实体1", "实体2"],
|
||||||
|
"org": ["实体1"],
|
||||||
|
"equipment": [],
|
||||||
|
"event": ["实体1"],
|
||||||
|
"location": ["实体1", "实体2"]
|
||||||
|
}}
|
||||||
|
}}
|
||||||
|
]
|
||||||
|
}}
|
||||||
|
```
|
||||||
|
|
||||||
|
## 输入数据
|
||||||
|
|
||||||
|
```json
|
||||||
|
{entries_json}
|
||||||
|
```
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
你是一位顶级的军事科技观察家与专业的军事新闻记者。你需要将最新发生的高分重磅消息整理成即时推送快讯。
|
||||||
|
|
||||||
|
## 任务要求:
|
||||||
|
1. **严格查重与阻断机制**:仔细对比原始数据与【过去已推送历史】。如果事件已经推送过且无重大新进展,请直接抛弃。如果所有输入的事件都被判定为重复,你必须且只能输出[NO_NEW_CONTENT]。
|
||||||
|
2. **专业聚合**:如果输入数据中有多条讨论同一个事件,请将它们融合为一条快讯。
|
||||||
|
3. **专业视角**:用2-3条无序列表项陈述核心事实(What);列表之后另起一段给出1-2句为什么值得关注(Why it matters)的洞察分析,应涉及对地区安全架构/军力平衡/技术格局的影响。
|
||||||
|
4. **语气风格**:客观、准确、克制。多用短句,重点内容加粗。避免空洞的行业宏大叙事和未来预测。
|
||||||
|
5. **军事专业性**:装备型号必须写完整(如"F-35A"不简写"F35"、"阿利·伯克级驱逐舰"不简写"伯克级"),兵力/射程/吨位等使用具体数字。
|
||||||
|
|
||||||
|
## 输出格式:
|
||||||
|
{output_format}
|
||||||
|
|
||||||
|
## 过去N天已推送事件清单(仅供查重,严禁模仿):
|
||||||
|
{recent_push_context}
|
||||||
|
|
||||||
|
## 本次需要推送的原始数据:
|
||||||
|
```json
|
||||||
|
{entries}
|
||||||
|
```
|
||||||
@@ -0,0 +1,60 @@
|
|||||||
|
你是一个资深的军事新闻主编。请对以下热点话题簇进行精评分(0-100)。
|
||||||
|
|
||||||
|
## 评分维度
|
||||||
|
|
||||||
|
请从以下四个维度综合打分:
|
||||||
|
|
||||||
|
1. **军事价值(40分)**:是否涉及装备列装/技术突破/实战行动/战略部署/军事时政
|
||||||
|
- 装备首次曝光/列装/实战验证 = 35-40
|
||||||
|
- 技术验证/测试进展/军控谈判/重大制裁 = 25-35
|
||||||
|
- 演习/训练/常规动态/外交斡旋 = 15-25
|
||||||
|
- 非军事主题 = 0-15
|
||||||
|
- 注意:芯片出口管制、AI军事化政策、太空军备竞赛等交叉领域属于军事科技/军事时政,不应归入"非军事"
|
||||||
|
|
||||||
|
2. **时效性(25分)**:是否为最新进展或首次报道
|
||||||
|
- 24h内的突发/首次报道 = 20-25
|
||||||
|
- 近期进展的跟进报道 = 15-20
|
||||||
|
- 回顾性/综述性内容 = 5-15
|
||||||
|
|
||||||
|
3. **信息密度(20分)**:是否包含具体数据/型号/地点/时间
|
||||||
|
- 含具体型号+数量+地点+时间 = 15-20
|
||||||
|
- 含部分具体信息 = 10-15
|
||||||
|
- 泛泛而谈/观点评论 = 5-10
|
||||||
|
- 加分项:含卫星图像/电子信号数据/OSINT分析/部署热力图等情报级信息 +3
|
||||||
|
|
||||||
|
4. **传播广度(15分)**:多家媒体、多个角度报道的热度
|
||||||
|
- 5+源或新华社等国家级通讯社通稿 × 3+角度 = 12-15
|
||||||
|
- 3-4源 或 2角度 = 8-12
|
||||||
|
- 单一来源 = 3-8
|
||||||
|
- 注意:中文军事新闻中,新华社/央视/国防部发布的一手通稿视为等效多源,不因"单源"而扣分
|
||||||
|
|
||||||
|
## 输入数据
|
||||||
|
|
||||||
|
以下是一个 JSON 数组,包含热点话题簇的摘要信息:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{clusters_json}
|
||||||
|
```
|
||||||
|
|
||||||
|
## 输出要求
|
||||||
|
|
||||||
|
必须返回纯 JSON 对象,格式如下:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"items": [
|
||||||
|
{
|
||||||
|
"cluster_id": 0,
|
||||||
|
"precise_score": 92,
|
||||||
|
"brief": "歼-35A首次公开确认列装,官方首发,多源多角度报道"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
要求:
|
||||||
|
1. items 数组长度必须与输入相同
|
||||||
|
2. cluster_id 必须与输入一一对应
|
||||||
|
3. precise_score 为 0-100 整数
|
||||||
|
4. brief 为 25 字以内的评分依据简述
|
||||||
|
5. 只返回 JSON 对象,不要添加额外说明
|
||||||
@@ -0,0 +1,50 @@
|
|||||||
|
你是一个专业且严苛的军事科技新闻主编。请对抓取到的碎片化信息进行过滤、评分和信息提取。
|
||||||
|
|
||||||
|
## 任务与评分标准
|
||||||
|
请根据以下标准为每条信息打分(0-100)。
|
||||||
|
|
||||||
|
**核心约束(先判这三条,再进入分档)**:
|
||||||
|
1. 90+ **必须同时满足**:(a) 主题与时政/军事/科技强相关;(b) 来源为当事方官方账号或权威防务专业媒体(Defense News / The War Zone / Janes / 新华社等,非低质KOL、非纯营销号);(c) 属于首发或第一时间跟进
|
||||||
|
2. 非时政/军事/科技主题(纯娱乐八卦、生活琐事、纯体育等)无论多重大、即使来自官方,**上限79分**
|
||||||
|
3. 时政/军事/科技新闻若来源是低质KOL(纯转述无增量信息、个人自媒体、无专业背景的营销号),**上限89分**。有深度分析、独家信息或专业背景(退役军官/OSINT分析师/行业研究员)的KOL不在此限。
|
||||||
|
|
||||||
|
**分档**:
|
||||||
|
- 【90-100分】军事/科技领域 + 官方或权威防务专业媒体首发 + 里程碑级装备/政策/事件。军事时政重磅事件(军控条约签署、重大制裁、香格里拉级别对话会)也在此档。
|
||||||
|
- 【80-89分】重要军事科技进展、深度技术分析、军事时政重要进展;或重磅新闻但通过普通媒体转述。
|
||||||
|
- 【70-79分】实用装备分析、技术教程、行业报告;**非目标领域**的重磅新闻;低质KOL转述的军事内容。
|
||||||
|
- 【60-69分】二手信息、一般性新闻、小道消息。
|
||||||
|
- 【<60分】低价值内容:纯情绪宣泄、无营养评价、广告。
|
||||||
|
|
||||||
|
## 输出要求
|
||||||
|
必须返回纯JSON对象,顶层包含`items`数组字段,数组中每个对象包含:
|
||||||
|
- `link`: 原文链接(必须保留原样)
|
||||||
|
- `score`: 整数评分,JSON数字类型
|
||||||
|
- `tags`: 字符串数组,数量1-3个,每个标签2-16个字符。必须是新闻中具体的关键词(装备型号/政策名称/技术特性/关键人物/地缘事件),禁止空泛分类标签。
|
||||||
|
- ✅ 好示例:`["歼-35A","舰载战斗机","隐身性能"]`、`["AI芯片出口限制","Rubin架构"]`
|
||||||
|
- ❌ 坏示例:`["军事","新闻","热点"]`
|
||||||
|
- `summary`: 一句话客观摘要(提取核心事实,50字内)
|
||||||
|
|
||||||
|
## 输出格式(严格只输出JSON对象):
|
||||||
|
{
|
||||||
|
"items": [
|
||||||
|
{
|
||||||
|
"link": "https://example.com/article1",
|
||||||
|
"score": 95,
|
||||||
|
"tags": ["歼-35A", "舰载战斗机", "隐身性能"],
|
||||||
|
"summary": "空军官方确认歼-35A已列装首批作战部队。"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
## 重要提示
|
||||||
|
1. items数组长度必须与输入相同
|
||||||
|
2. link字段必须与输入一一对应
|
||||||
|
3. 只返回JSON对象,不要添加任何额外说明文字
|
||||||
|
|
||||||
|
## 输入数据
|
||||||
|
以下是一个JSON数组,包含抓取到的内容:
|
||||||
|
每个对象包含:link, title, source, published, content
|
||||||
|
|
||||||
|
```json
|
||||||
|
{entries_json}
|
||||||
|
```
|
||||||
@@ -0,0 +1,55 @@
|
|||||||
|
# 军事科技网摘生成
|
||||||
|
|
||||||
|
你是一名资深军事编辑,请你严格按照《参考消息》官方新闻报道格式、标题风格,对我提供的新闻内容进行改写,严格遵守以下所有规则:
|
||||||
|
|
||||||
|
## 文章信息
|
||||||
|
- 标题:{title}
|
||||||
|
- 来源:{source}
|
||||||
|
- 发布时间:{published}
|
||||||
|
- AI摘要:{summary}
|
||||||
|
- 原文内容:{content}
|
||||||
|
|
||||||
|
## 标题要求
|
||||||
|
- 简洁、客观、中性、信息密度高
|
||||||
|
- 结构:主体 + 事件 + 核心态势
|
||||||
|
- 不抒情、不夸张、不用网络用语
|
||||||
|
|
||||||
|
## 价值点要求
|
||||||
|
- 价值点严格 35~50 字(含标点)
|
||||||
|
- 句式结构:事件 - 影响/后果 - 值得关注
|
||||||
|
- 凝练、客观、不发散
|
||||||
|
|
||||||
|
## 正文要求
|
||||||
|
- 开头第一句必须加:据 XXX 报道
|
||||||
|
- 如果新闻来源是微信公众号,开头第一句必须加:据微信公众号 XXX
|
||||||
|
- 正文风格:客观、平实、严谨、书面化,类似外电编译稿
|
||||||
|
- 只保留核心信息(时间、地点、人物、事件、内容、前景/影响),全文聚焦文章最核心的亮点,其他次要内容简要提及或不介绍
|
||||||
|
- 正文中可适当使用编号概述,如(1)(2)(3)…,不超过4个,每个编号后的冒号前为2~4字短语,如(1)具体行动:...(2)实现目标:...等
|
||||||
|
- 装备型号必须完整准确(如"F-35A"不简写为"F35"),兵力规模使用具体数字
|
||||||
|
- 全文一段到底,不分段
|
||||||
|
- 字数强制要求:正文字数严格控制在 250~350 字(含标点),不许超、不许少
|
||||||
|
|
||||||
|
## 输出格式
|
||||||
|
|
||||||
|
严格输出 JSON,包含六个字段:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"title": "网摘标题",
|
||||||
|
"value": "价值点",
|
||||||
|
"body": "正文",
|
||||||
|
"source": "新华社",
|
||||||
|
"original_title": "原文原标题",
|
||||||
|
"published": "2026-06-02T10:00:00"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
- source: 来源名称
|
||||||
|
- original_title: 未经改写的原标题
|
||||||
|
- published: 发布时间
|
||||||
|
|
||||||
|
只输出 JSON,不要任何前缀或后缀文字。
|
||||||
|
|
||||||
|
## 注意事项
|
||||||
|
- 标题与价值点、正文中的"核心亮点"必须一致——标题突出什么,价值点和正文就从什么角度切入组织
|
||||||
|
- 不遗漏新闻中值得关注的意外事件或副作用(如事故、失误),以客观笔法简要带过
|
||||||
@@ -35,6 +35,7 @@ from src.sections.webzine.section import generate_webzine_batch
|
|||||||
from src.storage import (
|
from src.storage import (
|
||||||
get_cluster_file,
|
get_cluster_file,
|
||||||
get_digest_file,
|
get_digest_file,
|
||||||
|
get_fetch_file,
|
||||||
save_push_file,
|
save_push_file,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
+40
-11
@@ -129,6 +129,7 @@ def _try_fix_truncated_json(text: str) -> str:
|
|||||||
|
|
||||||
def _parse_score_response(response: str) -> List[Dict]:
|
def _parse_score_response(response: str) -> List[Dict]:
|
||||||
text = response.strip()
|
text = response.strip()
|
||||||
|
# 移除 markdown 代码块标记
|
||||||
if text.startswith("```json"):
|
if text.startswith("```json"):
|
||||||
text = text[7:]
|
text = text[7:]
|
||||||
elif text.startswith("```"):
|
elif text.startswith("```"):
|
||||||
@@ -138,24 +139,39 @@ def _parse_score_response(response: str) -> List[Dict]:
|
|||||||
text = text.strip()
|
text = text.strip()
|
||||||
|
|
||||||
parsed = None
|
parsed = None
|
||||||
|
# 尝试 1: 直接解析
|
||||||
try:
|
try:
|
||||||
parsed = json.loads(text)
|
parsed = json.loads(text)
|
||||||
except json.JSONDecodeError:
|
except json.JSONDecodeError:
|
||||||
for pattern in (r"\{.*\}", r"\[.*\]"):
|
pass
|
||||||
|
|
||||||
|
# 尝试 2: 修复截断 + 修复常见 JSON 语法错误
|
||||||
|
if parsed is None:
|
||||||
|
fixed = _try_fix_truncated_json(text)
|
||||||
|
try:
|
||||||
|
parsed = json.loads(fixed)
|
||||||
|
except json.JSONDecodeError:
|
||||||
|
pass
|
||||||
|
|
||||||
|
# 尝试 3: 正则提取整个 JSON 对象
|
||||||
|
if parsed is None:
|
||||||
|
for pattern in (r"\{[^{}]*\"items\"\s*:\s*\[.*?\]\s*\}", r"\{.*\}", r"\[.*\]"):
|
||||||
match = re.search(pattern, text, re.DOTALL)
|
match = re.search(pattern, text, re.DOTALL)
|
||||||
if match:
|
if match:
|
||||||
try:
|
try:
|
||||||
parsed = json.loads(match.group())
|
parsed = json.loads(match.group())
|
||||||
break
|
break
|
||||||
except json.JSONDecodeError:
|
except json.JSONDecodeError:
|
||||||
continue
|
# 尝试修复后再解析
|
||||||
|
fixed_match = _try_fix_truncated_json(match.group())
|
||||||
|
try:
|
||||||
|
parsed = json.loads(fixed_match)
|
||||||
|
break
|
||||||
|
except json.JSONDecodeError:
|
||||||
|
continue
|
||||||
|
|
||||||
if parsed is None:
|
if parsed is None:
|
||||||
fixed = _try_fix_truncated_json(text)
|
raise ValueError(f"无法从响应中解析JSON: {response[:300]}...")
|
||||||
try:
|
|
||||||
parsed = json.loads(fixed)
|
|
||||||
except json.JSONDecodeError:
|
|
||||||
raise ValueError(f"无法从响应中解析JSON: {response[:200]}...")
|
|
||||||
|
|
||||||
if isinstance(parsed, list):
|
if isinstance(parsed, list):
|
||||||
return parsed
|
return parsed
|
||||||
@@ -166,7 +182,7 @@ def _parse_score_response(response: str) -> List[Dict]:
|
|||||||
list_values = [v for v in parsed.values() if isinstance(v, list)]
|
list_values = [v for v in parsed.values() if isinstance(v, list)]
|
||||||
if len(list_values) == 1:
|
if len(list_values) == 1:
|
||||||
return list_values[0]
|
return list_values[0]
|
||||||
raise ValueError(f"无法从响应中提取评分数组: {response[:200]}...")
|
raise ValueError(f"无法从响应中提取评分数组: {response[:300]}...")
|
||||||
|
|
||||||
|
|
||||||
def _split_entries_for_batch(entries: List[Dict], max_prompt_chars: int = 10000) -> List[List[Dict]]:
|
def _split_entries_for_batch(entries: List[Dict], max_prompt_chars: int = 10000) -> List[List[Dict]]:
|
||||||
@@ -227,16 +243,29 @@ def _reconcile_batch_results(entries: List[Dict], results: List[Dict], batch_ind
|
|||||||
return matched_results, errors
|
return matched_results, errors
|
||||||
|
|
||||||
|
|
||||||
async def _score_single_batch(entries: List[Dict], config: Dict, batch_index: int = 0) -> Tuple[List[Dict], List[str]]:
|
async def _score_single_batch(entries: List[Dict], config: Dict, batch_index: int = 0,
|
||||||
|
retry: bool = True) -> Tuple[List[Dict], List[str]]:
|
||||||
prompt_path = config.get("prompts", {}).get("score_batch", "prompts/score_batch.md")
|
prompt_path = config.get("prompts", {}).get("score_batch", "prompts/score_batch.md")
|
||||||
prompt = _build_batch_prompt(entries, prompt_path)
|
prompt = _build_batch_prompt(entries, prompt_path)
|
||||||
try:
|
|
||||||
response = await call_llm(prompt, config, response_format={"type": "json_object"})
|
async def _try_score():
|
||||||
|
response = await call_llm(prompt, config, max_tokens=40960)
|
||||||
results = _parse_score_response(response)
|
results = _parse_score_response(response)
|
||||||
if not isinstance(results, list):
|
if not isinstance(results, list):
|
||||||
raise ValueError(f"LLM返回的不是数组: {type(results)}")
|
raise ValueError(f"LLM返回的不是数组: {type(results)}")
|
||||||
return _reconcile_batch_results(entries, results, batch_index)
|
return _reconcile_batch_results(entries, results, batch_index)
|
||||||
|
|
||||||
|
try:
|
||||||
|
return await _try_score()
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
|
if retry:
|
||||||
|
print(f" 🔄 批次{batch_index + 1} 首次失败,重试中... ({e})")
|
||||||
|
try:
|
||||||
|
return await _try_score()
|
||||||
|
except Exception as retry_e:
|
||||||
|
error_message = f"批次{batch_index + 1} 评分失败(重试后): {retry_e}"
|
||||||
|
print(f" ⚠️ {error_message}")
|
||||||
|
return [], [error_message]
|
||||||
error_message = f"批次{batch_index + 1} 评分失败: {e}"
|
error_message = f"批次{batch_index + 1} 评分失败: {e}"
|
||||||
print(f" ⚠️ {error_message}")
|
print(f" ⚠️ {error_message}")
|
||||||
return [], [error_message]
|
return [], [error_message]
|
||||||
|
|||||||
Reference in New Issue
Block a user