# 精选与校准 ## 一条资料怎么变成精选 1. **收进来**:同一网址、同一内容只留一份。普通文章只有标题或摘要时,按信源规则补取正文;视频播放页使用已提供的文字材料,不把播放器页面当正文。没有可信发布时间的资料先不公开,读到原文日期后再判断新旧(见 [信源](sources.md#规则旧文不刷屏) 的“旧文不刷屏”)。 2. **预筛**(`prefilter.md`):这是不是本行业的事。宽进,只拦明显无关的:`BLOCK` 的资料不出现在任何公开页面;`PASS` 和拿不准的 `UNKNOWN` 继续往下走。只有标题、没有正文、订阅摘要或帖子文字的普通文章先等待材料,不评分、不凭标题写文案,也不进入公开池;材料补齐产生新修订后再判断。可展示的空文字或纯链接原帖沿用原有评分与原样展示规则。 3. **评分**(`selection-score.md`):同一份评分标准独立打两次分(0–100)。**两次之和 ≥ 2 × 门槛**就够分,门槛按信源分级不同;够分的还要过第 6 步的去重才进精选。页面上显示的分数是两次的平均(向下取整)。 4. **结构化**(`structure.md`):和评分同时进行。给出分类和标签(页面上显示的就是这一份)、主体公司,判断这篇是一条具体新闻、讲多件事的综合稿,还是材料不够、说不清;是具体新闻的,抽出它的事实(谁、做了什么、对什么、什么时候)和原文里的出处、前提条件。主题页、事件归组和日报都靠它。 5. **写标题摘要**:等结构化完成再写。入选的和差一点入选的(平均分高于 `understandFloor`),按 `content-understanding.md` 写中文标题、答案先行的摘要和推荐理由;其余的按 `summarize-*.md` 写简短的标题摘要,进“全部动态”。X 帖子没有正文或只含链接时,保留原帖的链接、图片和引用,不调用写作或翻译模型,也不生成“请补充正文”的文案;已经取得实际正文的 X 长文照常处理。写好的文字用了 `industry/wording.ts` 里列出的词时,再让模型只改这些词一次(`mend-wording.md`)。 6. **归组与去重**(`group-*.md`):不同来源报道的同一件事归成一个事件,事件页有综述(`story-digest.md`),“热门”按事件排。修改综述提示词前,可用 [综述评测](story-digest-evaluation.md) 在同一批事件上把现有提示词和候选提示词并排比较。分数够了的资料要等归组完成才进精选:模型同时判断它和精选里已有的报道是不是同一条新闻,以及它有没有精选还没给过的具体信息。 - 同一条新闻的几篇报道都可以入选:网页上折成一个阅读组;API、RSS 和同步接口每条新闻只出一条,由代表报道出面:官方一手优先;其次是核实过的官方账号(填了 `publisherRole` 和 `owner_entity_id` 的 `T1_5` 账号,新闻的主体正是这家公司,机构先于个人);再往后是有全文的、分数高的、发得早的。 - 不是同一条新闻、但没有新信息的(换个说法再介绍一遍已经披露的能力、只是上架到别的平台、只重复已选要点的汇总),不进精选,留在“全部动态”。讲多件事的综合稿不单独成事件,只挂到它提到的事件上,有精选没覆盖的重要内容才入选;材料不够、确认不了是哪条新闻的,不进精选。 - 显式撤回的报道保留历史归属,但不再参与之后的归组召回、候选代表、事件起点或自动事件比较;同网址及 X 回复/引用也不能借它提供身份依据。同一事实仍有有效报道时使用有效稿,全撤回则暂不召回;恢复公开后可重新参与。只展示摘要或尚未发布的记录不等于撤回,保留原有资格。已有关系和判断不删除,不批量重新归组。 - 确认之前资料只在“全部动态”里,等得再久也不会自动放行。模型调用失败或回答不合格式,这条资料就停在那里:等待超过 10 分钟会告警,后台“运行”页列出这些资料,可以在后台“内容诊断”里打开这条资料,点“重新归组”。所以归组用的模型要能稳定按格式回答。 7. **日报、周报、月报**: - **日报**每天 08:00 出(日报、周报、月报的出刊时间在 `site/site.ts` 的 `EDITION_TIMES`,下面按默认值写),覆盖前一天 08:00 到当天 08:00,按规则编排,不调用模型。一件事一条,取报道最多的那条新闻里最权威的一篇,同一事件当天的其他进展列在下面,讲多件事的综合稿挂到它提到的最重要那件事下面。最近 7 期报过的不再出现;报过的事件再有进展,只有当事方自己有新动作(教程、观点这类 `commentary` 类别不算)、或这条进展有 4 家以上信源报道时才进正文,其余进快讯。没有报道入选、但有官方原帖并且已有 3 个以上独立参与方的事件也会补进来(只进日报,不进精选、不推送)。按重要性排序(入选报道的最高分、当天讨论它的独立参与方、有没有官方原帖,跟进的减分),前 12 条进正文,同一信源最多 2 条,再往后最多 10 条一行快讯。第一条是头条,导语就是它自己的标题和摘要;第 2–4 条是今日看点。 - **周报**(每周一 10:00)和**月报**(每月 1 日 10:30)由这一周、这个月的日报汇编:同一事件跨天只算一件,按日报给它的位置(头条、看点、上了几天)、报道面和评分排序,周报取前 20 件、月报取前 30 件,按日报的分节分栏目。模型(`report-period.md`、`report-period-sections.md`)只写总述和三件以上栏目的一句导读,不增删条目;它写出了条目里没有的名字或数字(拉丁字母的名称、三位以上或带小数点、百分号的数字、词表里的公司),这段话就不用:总述换成按模板生成的一句,栏目不放导读。行业通用词(`taxonomy.ts` 的 `PLAIN_TERMS`)和站名不受这个限制。 - 每条精选只进一期:按它的时间算,确认入选比它晚的按确认时间;截止以后才确认的进下一期。 - 每半小时检查一次:到点的刊期出一次,已经出过的不重写;停机错过的、失败的,在之后的检查里补上。窗口里没有可收的条目时这一期不出,之后每半小时再试,到 10 点还没有当天的日报会告警。 每一步的提示词都在 `industry/prompts/`,改提示词不用改代码。提示词的版本就是它内容的哈希:改了提示词,之后的新资料按新版判断,已经判过的不会重算。 ## 门槛:`industry/selection.ts` ```ts export const SELECTION = { thresholds: { T1: 60, T1_5: 65, T2: 76 }, // 两次评分的平均至少要到这个数 understandFloor: 50, // 平均分高于它的未入选资料,也按入选的写法写 }; ``` 官方一手信源(T1)门槛低一些,媒体和个人(T2)门槛高一些:同样一件事,官方原文更值得先看。没有门槛的分级(`EXCLUDE_MP`)不参与精选。 这组数是 AIHOT 在 AI 领域一直在用的门槛,偏严:宁可少选几条,也不让噪声进精选。换了行业、改了评分标准,一定要按下面的办法重新校准。 ## 校准 ### 1. 准备样本集 从你自己的信源里挑 100–200 条资料,一条一条标“该选 / 不该选”,存成 `.data/gold.jsonl`(`.data/` 不进 Git)。每行一条: ```json {"caseId":"law-001","material":{"title":"原文标题","originalTitle":null,"publishedAt":"2026-10-01T09:00:00+08:00","sourceName":"信源名称","bodyZh":null,"bodyOriginal":"正文……"},"sourceFacts":{"sourceKind":"rss","sourceTier":"T1","firstParty":true,"language":"zh"},"samplingContext":{"benchmarkSplit":"development","samplingStratum":"regulation"},"gold":{"decision":"select"}} ``` | 字段 | 说明 | |---|---| | `caseId` | 唯一编号 | | `material` | 标题、原标题、发布时间、信源名、正文(中文正文放 `bodyZh`,原文放 `bodyOriginal`,有一个就行) | | `sourceFacts` | 信源类型、分级、是否一手(线上只有 `T1` 算一手,标注时保持一致)、语言。分级决定用哪个门槛 | | `samplingContext` | 可选。`benchmarkSplit` 分开发集和留出集,`samplingStratum` 是你自己的分组(比如“新规”“判决”“营销”),看错在哪一类 | | `gold.decision` | `select` 该选,`reject` 不该选,`either` 两可(不计入准确率) | `industry/gold.example.jsonl` 有两条示例。 几条建议: - 多放**难例**:差一点就该选、差一点就不该选的。一眼就能判断的放太多,准确率会虚高。 - 分出一部分做**留出集**(`benchmarkSplit: "holdout"`),调提示词只看开发集,最后再用留出集检查一遍,免得把提示词调成只会做这几道题。 - 标注的人最好就是以后读这个站的人,或者和他们口味一致的人。 - 评测只跑预筛和两次评分,不含第 6 步的去重:按这条资料本身该不该选来标,不要因为同一件事已经选过另一篇就标“不该选”。 ### 2. 跑评测 ```bash node --env-file=.env scripts/eval-selection.ts --gold .data/gold.jsonl --split development --label "第一版评分标准" ``` 对每条样本跑一遍预筛和两次评分,输出: - **有效输出准确率、查准率、查全率和 F1**:只看成功得到入选决定的非 `either` 样本,用来判断模型答出来以后有多准; - **覆盖率**:成功得到入选决定的样本占全部非 `either` 金标样本的比例;**完整准确率**则用全部非 `either` 金标样本作分母,把模型失败或回答不合格式也算作没有正确完成; - `either` 始终不进入上述质量指标的分母;报告另外记录决定样本失败数与 `either` 失败数。`goldSelectRate` 按全部非 `either` 金标计算,因此同一批样本在不同模型间应保持一致; - 门槛从 40 到 90 每隔 2 分(范围可以在下面说的 `DEPLOYMENT.selectionGold` 里改),各自会得到什么结果;门槛扫描同样同时报告有效输出准确率、覆盖率和完整准确率; - 判错的条目,完整报告写到 `.data/eval/`,同时导入后台 SelectBench(加 `--no-import` 不导入)。 不传 `--models` 时,评测用“精选评分”这一步线上正在用的模型:后台“模型与评测”页切换过的优先,其次是环境变量 `SCORE_MODEL`,再其次是 `site/models.ts` 的 `DEFAULTS.score`,都没有就用默认模型(`.env` 里的 `LLM_*`)。`--models default,deepseek-flash` 可以在同一批样本上比较几个模型。其他常用参数:`--n 200` 最多抽多少条,`--split holdout` 只跑留出集,`--no-import` 不导入 SelectBench。在 `site/site.ts` 的 `DEPLOYMENT.selectionGold` 里写好样本文件、抽样条数、只抽哪一份和门槛扫描范围,以后不带参数运行就用这一套。 同一次评测里,不同样本渲染出完全相同的评分输入时,只共享模型的评分结果,各样本仍按自己的预筛结果、信源分级门槛和标注独立计分;失败的结果也在这次运行里共享,这样第一次跑和用已有结果重跑覆盖的样本一样。重复运行会复用已有回执。报告里的 token 用量和平均耗时,按相关回执的全部请求尝试汇总,解析失败后的重试也算在内;用已有结果重跑时,显示的是这些回执累计的用量,不代表这次新增的费用。 ### 3. 看错例,改标准,再跑 在后台 SelectBench 里逐条看判错的资料和模型给的理由: 上传多模型报告时,同一 `caseId` 在各模型下须使用相同的 `gold` 标注,模型的 `decision` 可以不同。标注冲突的报告会在写入前被拒绝,避免同一条资料在并排比较时出现不同的人工答案;不提供逐例 `cases` 的报告仍可导入。 - 该选没选上,多半是评分标准里没说清它为什么重要:在 `selection-score.md` 里把这类价值写进“必须正常评价”的部分,给出例子。 - 不该选却选上了,多半是噪声没压住:写进“必须压住”的部分。 - 整体偏松或偏紧,而判错的条目分数都贴着门槛,再调 `industry/selection.ts` 的门槛。 先改标准,再动门槛:门槛只能整体移动,解决不了“哪一类判错了”。每改一次跑一遍,SelectBench 里能看到每一版的对比。比较模型时不要只看有效输出 F1:如果覆盖率明显不同,还要一起看完整准确率;高分但经常无法完成结构化判断的模型,不应被失败样本从分母里“隐藏”掉。 ## 换模型 后台“模型与评测”页能看到每一步当前用哪个模型、近期的成功率、耗时和 token 用量,也能直接切换(只影响之后的新任务)。换评分模型之前,先用 `--models` 在同一批样本上比一比。