diff --git a/skills/label-fast-slow-routing/SKILL.md b/skills/label-fast-slow-routing/SKILL.md new file mode 100644 index 0000000..39a0fed --- /dev/null +++ b/skills/label-fast-slow-routing/SKILL.md @@ -0,0 +1,191 @@ +--- +name: label-fast-slow-routing +description: 依据 XA4.1 2026.6 新标准,对单条或批量 query、带会话上下文的请求和快慢分流评测数据进行“快 / 慢 / 模糊”三档可解释打标,给出默认路由、原因码、证据、置信度和复核标记,并在需要时兼容旧 FAST_DIRECT / SLOW_* 评测标签。用于快慢系统策略标注、旧评测集重标、边界 case 仲裁、标注 Prompt 编写、标签一致性检查和 badcase 分析;可结合相邻 label-master skill 判断业务领域、候选标签、复杂度、多指令和自动任务,但不得把这些辅助维度直接等同于快慢标签。 +--- + +# 快慢分流打标 + +## 目标 + +按用户对响应速度和结果质量的预期判断语义标签,不按当前系统能力反推标签。始终分开输出: + +- `label`:`快`、`慢`、`模糊`。 +- `default_route`:三档标签的默认系统策略。 +- `fast_system_pending`:已确认的快系统能力缺口。 +- 业务标签及 `complex`、多指令、自动任务等辅助维度。 + +## 加载知识 + +1. 每次打标前读取 [references/policy.md](references/policy.md)。 +2. 处理边界 case、校准标注员或编写 Prompt 时,再读取 [references/golden-examples.md](references/golden-examples.md)。 +3. 迁移旧评测集、输出 `FAST_DIRECT` / `SLOW_*`、设计评测集或做质检时,再读取 [references/legacy-eval-and-quality.md](references/legacy-eval-and-quality.md)。 +4. 需要业务领域、业务标签、候选标签或结构维度时,按“结合 label-master”一节渐进加载相邻知识;不要一次读取整个知识库。 + +## 遵守真源优先级 + +按以下顺序处理冲突: + +1. 用户在当前任务中明确给出的已批准新口径。 +2. 《【XA4.1】快慢分流新标准-2026.6》最终汇总(物理页 7–10)。 +3. 同文档的垂域调研细则(物理页 2–7),仅补充最终汇总未覆盖的场景。 +4. 《快慢系统分发-评测集构建》的旧八类标签,仅用于原因解释、旧数据迁移和评测方法。 +5. `label-master` 的业务标签与正交维度,仅用于辅助理解。 + +明确处理以下冲突: + +- 把“导航去附近评分 4.5 以上的泰国菜”标为 `快`,不要沿用旧的筛选即慢。 +- 把“那个压线噔噔的声音关掉”标为 `快`,前提是功能可唯一识别且动作明确。 +- 把纯静态产品知识、故障诊断、设备或环境状态查询标为 `模糊`,不要直接沿用旧的快或慢。 +- 把多个全部明确的设备控制动作标为 `快`;不要仅因多指令就标慢。 +- 不要使用“长度不超过 5 字默认快”。它只是旧候选挖掘捷径,不是语义规则。 + +## 执行工作流 + +### 1. 整理输入 + +提取并保留: + +- 当前 `query`。 +- 已提供的前轮、系统回复、时间窗口、设备、端侧和场景。 +- 用户要求的输出格式和旧标签兼容要求。 +- 已提供的快系统能力清单;没有清单时不要猜测能力支持状态。 + +只使用可见上下文。缺少会改变结论的上下文时,保留不确定点并设置 `review_required=true`。 + +### 2. 判断业务与结构辅助信息 + +需要时使用 `label-master` 判断: + +- 业务领域和 2–5 个候选业务标签。 +- 是否为多指令、自动任务或 `complex`。 +- 当前轮是否真正依赖上文。 + +保持这些结果与快慢标签正交。特别注意: + +- `complex=true` 不自动等于 `慢`。 +- 多指令不自动等于 `慢`。 +- 自动任务要结合端侧专项策略。 +- 业务标签不自动决定快慢。 + +### 3. 按三档策略裁决 + +使用 [references/policy.md](references/policy.md) 的完整决策树。执行以下最小顺序: + +1. 先区分纯信息获取和带副作用的执行请求。 +2. 对执行请求,先检查是否必须反推意图、映射场景、消歧、补槽、依赖上下文或规划多步;命中则倾向 `慢`。 +3. 再检查动作和目标能否直接识别、是否无需先决定“做什么”;满足则标 `快`。 +4. 对纯闲聊、开放问答、产品知识、故障诊断、设备或环境状态查询标 `模糊`。 +5. 无法可靠归入快或慢、或速度和质量预期同时明显存在时标 `模糊`,不要把“模型自己不确定”伪装成确定标签。 +6. 聚合多个子请求时使用 `慢 > 模糊 > 快`;多个全部明确的设备控制动作使用 `快`特例。 + +同时做反证检查:说明为什么没有选择另外两档。不要只凭关键词命中一个规则。 + +### 4. 确定默认路由和能力状态 + +按固定映射填写: + +- `快` → `default_route="快系统"`。 +- `慢` → `default_route="慢系统"`。 +- `模糊` → 默认 `default_route="慢系统"`;只有用户另给细分部署策略时才在下游覆盖,语义标签保持 `模糊`。 + +仅在有明确能力清单或验证结果证明“语义上应为快,但当前快系统尚不支持”时填写 `fast_system_pending=true`。没有检查能力时填 `null`,不要填 `false` 假装已验证。 + +### 5. 生成可审查结果 + +默认每条样本输出一个符合 [schemas/label-record.schema.json](schemas/label-record.schema.json) 的 JSON 对象: + +```json +{ + "query": "太冷了,调一下空调", + "label": "快", + "default_route": "快系统", + "primary_reason_code": "DIRECT_ACTION", + "reason_codes": ["DIRECT_ACTION"], + "reason": "用户明确给出调节动作和空调对象,无需反推要操作什么。", + "evidence": ["“调一下空调”同时包含动作和可识别对象"], + "counterevidence": ["含感受词“太冷”,但感受词不覆盖明确执行目标"], + "domain_label": "设备控制", + "candidate_domain_labels": ["设备控制", "车载控制"], + "structural_signals": { + "complex": null, + "multi_instruction": false, + "auto_task": false + }, + "context_used": false, + "fast_system_pending": null, + "legacy_eval_label": "FAST_DIRECT", + "confidence": "high", + "review_required": false, + "uncertainties": [], + "policy_version": "XA4.1-2026.6" +} +``` + +使用 `null` 表示未评估,不要用猜测值补齐辅助字段。用户只要求简表时至少保留 `query`、`label`、`primary_reason_code`、`reason` 和 `review_required`;落盘数据仍使用完整契约。 + +### 6. 校验后再落盘 + +对单条结果执行: + +```bash +python skills/label-fast-slow-routing/scripts/validate_label.py \ + --record '{"query":"开空调","label":"快","default_route":"快系统","primary_reason_code":"DIRECT_ACTION","reason_codes":["DIRECT_ACTION"],"reason":"动作和对象明确。","evidence":["开+空调"],"context_used":false,"fast_system_pending":null,"confidence":"high","review_required":false,"uncertainties":[],"policy_version":"XA4.1-2026.6"}' +``` + +对 JSON、JSONL 文件执行: + +```bash +python skills/label-fast-slow-routing/scripts/validate_label.py \ + --file output/fast_slow_labels.jsonl +``` + +出现 `valid=false` 时先修正结构和不变量,再写入训练集或评测集。校验器只保证结构和跨字段一致性,不能替代语义复核。 + +## 结合 label-master + +需要业务标签时按以下最短路径读取: + +1. 读取 `../label-master/knowledge/决策流程.md`。 +2. 读取 `../label-master/knowledge/索引/候选召回索引.md` 和 `../label-master/knowledge/标签总览.md`。 +3. 从 `../label-master/knowledge/索引/标签索引.md` 召回 2–5 个候选。 +4. 命中高频混淆时读取对应边界卡,再读取候选标签卡。 +5. 只在任务需要时读取复杂度、多指令或自动任务文件。 + +复用它的“候选召回 → 边界比较 → 排除理由 → 不确定点”方法,不复用它的 `Agent` / function target 作为快慢输出。 + +当 `label-master` 与本策略冲突时: + +- 保留其业务标签与结构维度结论。 +- 按本 skill 的真源优先级独立重算快慢标签。 +- 在 `counterevidence` 中记录冲突,不静默覆盖。 + +## 批量与评测集要求 + +- 保留原始 ID、query、上下文和来源,不修改原始数据。 +- 逐条执行完整语义判断,不按字数、关键词或既有类别比例直接定标。 +- 对同一 session 使用相同的上下文截取规则;缺上下文样本单独标记。 +- 把 `模糊` 保留为语义标签;需要二值评测时另派生默认路由,不要覆盖原标签。 +- 旧八类标签只写入 `legacy_eval_label`;无法无损映射时填 `null` 并解释。 +- 对高流量、高分歧、高风险误分样本优先双人盲标并仲裁。 +- 分别统计三档标签、垂域、端侧和方向性错误;不要只看总准确率。 + +## 复核门禁 + +满足任一情况时设置 `review_required=true`: + +- 缺少会改变标签的前轮、设备或端侧信息。 +- 快慢规则同时有强证据,且优先级无法消解。 +- 只能依赖未确认的系统能力或白名单。 +- 旧标签无法无损迁移到新三档。 +- `confidence="low"`。 + +`模糊` 不自动等于需要人工复核。产品知识或状态查询可以高置信地标为 `模糊`。 + +## 禁止事项 + +- 不要因快系统当前不支持而把 `快`改成 `慢`。 +- 不要把 `complex=true`、长 query、联网、搜索、排序、模糊词或多轮中的任一项当成单独硬触发器。 +- 不要把非标准叫法等同于真实歧义;先判断对象能否唯一识别。 +- 不要脑补前轮、设备位置、说话人位置或能力清单。 +- 不要把旧评测集的样本占比当成先验配额。 +- 不要输出没有证据或排除理由的裸标签。 diff --git a/skills/label-fast-slow-routing/agents/openai.yaml b/skills/label-fast-slow-routing/agents/openai.yaml new file mode 100644 index 0000000..5f5a2c3 --- /dev/null +++ b/skills/label-fast-slow-routing/agents/openai.yaml @@ -0,0 +1,4 @@ +interface: + display_name: "快慢分流打标" + short_description: "依据 2026.6 新标准完成快、慢、模糊三档可解释打标" + default_prompt: "Use $label-fast-slow-routing to label these queries as 快、慢或模糊并给出可复核依据。" diff --git a/skills/label-fast-slow-routing/references/golden-examples.md b/skills/label-fast-slow-routing/references/golden-examples.md new file mode 100644 index 0000000..939860c --- /dev/null +++ b/skills/label-fast-slow-routing/references/golden-examples.md @@ -0,0 +1,61 @@ +# 黄金边界样例 + +使用这些样例校准规则,不要把词面模板机械迁移到所有场景。`来源`中的页码为 PDF 物理页码;“推导”表示按规则组合得到,不是文档逐字给出的标签。 + +| # | query / 上下文 | label | 主原因码 | 边界说明 | 来源 | +| --- | --- | --- | --- | --- | --- | +| 1 | 开空调 | 快 | `DIRECT_ACTION` | 动作和对象明确 | 新标准 p8 | +| 2 | 太冷了,调一下空调 | 快 | `DIRECT_ACTION` | 感受词不覆盖明确动作和对象 | 新标准 p8,规则推导 | +| 3 | 我感觉好冷快冻死了,帮我调一下 | 慢 | `INTENT_INFERENCE` | 未说明要调什么,需反推动作 | 新标准 p9 | +| 4 | 调一个浪漫点的氛围灯 | 快 | `DIRECT_ACTION` | 风格是简单修饰,功能目标明确 | 新标准 p8–9 | +| 5 | 帮我把房间布置得舒适温馨一些 | 慢 | `SCENE_GOAL_MAPPING` | 需把目标状态映射成动作组合 | 新标准 p9 | +| 6 | 导航去附近评分 4.5 以上的泰国菜 | 快 | `DIRECT_ACTION` | 最终汇总覆盖调研版“筛选即慢” | 新标准 p2 与 p8 | +| 7 | 导航去附近最大的停车场 | 快 | `DIRECT_ACTION` | 单目标、单一简单排序可直接满足 | 旧评测 p3 与新标准 p8,规则推导 | +| 8 | 推荐一家安静、亲子、评分高、停车方便且别排太久的酒店 | 慢 | `COMPLEX_REASONING` | 多个主观/实时条件需权衡 | 旧评测 p3,新标准规则推导 | +| 9 | 导航去大裤衩,再沿途找充电站,并看看终点附近哪里好停车 | 慢 | `MULTI_STEP_PLANNING` | 多地点、多步骤路线编排 | 新标准 p9 | +| 10 | 删除途经点 | 快 | `DIRECT_OPERATION_FLOW` | 导航操作流中的直接动作 | 新标准 p8 | +| 11 | 把空调调到 24 度,然后打开座椅加热 | 快 | `EXPLICIT_DEVICE_MULTI_ACTION` | 多个动作全部为明确设备控制 | 新标准 p8 | +| 12 | 查天气再决定要不要开窗 | 慢 | `MULTI_STEP_PLANNING` | 查询结果决定后续控制 | 旧评测 p4 | +| 13 | 那个压线噔噔的声音关掉 | 快 | `DIRECT_ACTION` | 名称不标准但目标可唯一识别且动作明确 | 新标准 p4 与 p9 | +| 14 | 打开那个灯(场景中有多个灯) | 慢 | `TRUE_DISAMBIGUATION` | 存在真实多候选,需澄清 | 旧评测 p3,新标准规则推导 | +| 15 | 导航到那个像大裤衩一样的楼 | 慢 | `TRUE_DISAMBIGUATION` | 需先识别隐含地标再定位 | 旧评测 p3 | +| 16 | 播放周杰伦的歌 | 快 | `DIRECT_ACTION` | 明确播放意图和资源 | 新标准 p8 | +| 17 | 播放最近短视频里很火的那首歌 | 慢 | `TRUE_DISAMBIGUATION` | 资源身份不确定且依赖外部热点 | 旧评测 p3,新标准内容细则 | +| 18 | 找王菲在春晚与别人合唱的歌,再用网易云播放 | 慢 | `MULTI_STEP_PLANNING` | 外部检索后跨步骤播放 | 新标准 p9 | +| 19 | 现在几点了 | 快 | `DIRECT_DETERMINISTIC_QUERY` | 高频、唯一、确定性工具查询 | 旧评测 p3 | +| 20 | 帮我定明天早上 9 点的闹钟 | 快 | `DIRECT_ACTION` | 时间和动作明确 | 新标准 p8 | +| 21 | 小米 SU7 支持哪些驾驶模式 | 模糊 | `PRODUCT_KNOWLEDGE` | 纯静态产品知识 | 新标准 p10 | +| 22 | 我的车怎么突然没声音了 | 模糊 | `FAULT_DIAGNOSIS` | 纯故障原因/诊断请求 | 新标准 p4 与 p10 | +| 23 | 现在胎压是多少 | 模糊 | `DEVICE_OR_ENV_STATE_QUERY` | 最终模糊档覆盖调研版简单动态查询快 | 新标准 p4 与 p10,规则推导 | +| 24 | 家里哪个房间最热 | 模糊 | `DEVICE_OR_ENV_STATE_QUERY` | 环境状态查询,无控制副作用 | 新标准 p10 | +| 25 | 给我讲个笑话 | 模糊 | `OPEN_CHAT_OR_QA` | 闲聊/开放内容 | 新标准 p10 | +| 26 | 做一个介绍 AI4S 的 PPT | 慢 | `MULTI_STEP_PLANNING` | 复杂产物生成与内容组织 | 新标准 p9 | +| 27 | 音箱端:每天晚上 8 点开灯开空调 | 慢 | `AUTOMATION_SLOW_POLICY` | 音箱家庭自动化专项默认慢 | 新标准 p7 | +| 28 | 车载端:车速超过 70 提醒我减速 | 快 | `DIRECT_ACTION` | 车载闹钟/提醒专项走快 | 新标准 p7 | +| 29 | 前轮慢系统询问“确认开启湿滑模式吗”;当前轮“帮我开启” | 慢 | `CONTEXT_REQUIRED` | 继承慢确认链路 | 新标准 p4 | +| 30 | 仅当前轮“帮我开启”,无任何前轮 | 慢 | `MISSING_REQUIRED_SLOT` | 无法确定开启对象;低置信并复核 | 新标准规则推导 | +| 31 | 自动帮我设置一下(端侧未知) | 模糊 | `POLICY_UNCERTAIN` | 端侧会改变正式自动化策略;默认慢并复核 | 新标准 p7,规则推导 | + +## 最小对照组 + +校准时至少同时检查以下紧邻对: + +```text +太冷了,调一下空调 -> 快 +我感觉好冷快冻死了,帮我调一下 -> 慢 + +调一个浪漫点的氛围灯 -> 快 +帮我把房间布置得浪漫一点 -> 慢 + +导航去附近评分 4.5 以上的泰国菜 -> 快 +帮我综合挑一家不踩雷、少排队、停车方便的餐厅 -> 慢 + +那个压线噔噔的声音关掉 -> 快 +打开那个灯(存在多个候选) -> 慢 + +现在几点 -> 快 +现在胎压多少 -> 模糊 + +开空调并打开座椅加热 -> 快 +查天气再决定是否开窗 -> 慢 +``` diff --git a/skills/label-fast-slow-routing/references/legacy-eval-and-quality.md b/skills/label-fast-slow-routing/references/legacy-eval-and-quality.md new file mode 100644 index 0000000..7f66543 --- /dev/null +++ b/skills/label-fast-slow-routing/references/legacy-eval-and-quality.md @@ -0,0 +1,153 @@ +# 旧评测标签迁移与质量控制 + +## 目录 + +- [文档定位](#文档定位) +- [旧八类标签](#旧八类标签) +- [迁移到新三档](#迁移到新三档) +- [关键口径变化](#关键口径变化) +- [评测集构建](#评测集构建) +- [质检门禁](#质检门禁) +- [禁止固化的旧信息](#禁止固化的旧信息) + +## 文档定位 + +《快慢系统分发-评测集构建》是早期策略、评测集项目计划和一次候选挖掘实验的混合文档。其项目日期早于 2026.6 新标准。 + +使用它: + +- 解释复杂性原因。 +- 迁移旧 `FAST_DIRECT` / `SLOW_*` 数据。 +- 复用候选挖掘、人工盲标、主动学习和流量回放方法。 + +不要使用它覆盖新三档语义标准。 + +## 旧八类标签 + +| 旧标签 | 旧定义 | +| --- | --- | +| `FAST_DIRECT` | 当前轮可直接、安全、确定性满足 | +| `SLOW_FILTER_RANK` | 候选筛选、排序、优化或推荐 | +| `SLOW_DISAMBIGUATE` | 目标身份不确定,需先消歧 | +| `SLOW_GOAL_STATE` | 给目标状态,需反推参数 | +| `SLOW_WORKFLOW` | 多动作、多工具或显式多步骤 | +| `SLOW_CONTEXT_DEPENDENT` | 复杂性主要来自上文或记忆 | +| `SLOW_UNCLEAR` | 结合必要上下文仍不明确 | +| `SLOW_ADVANCED_CAPABILITY` | 不在旧快系统白名单的高级能力 | + +旧文档没有给多原因冲突优先级。迁移时允许先保留多个原因,再选择决定性主因。 + +## 迁移到新三档 + +不要做固定的一对一映射。先按 2026.6 语义重标,再按需要回填最接近的旧标签。 + +| 旧标签 | 新标准处理 | +| --- | --- | +| `FAST_DIRECT` | 通常为快;仍检查它是否属于新标准明确列出的模糊信息型 | +| `SLOW_FILTER_RANK` | 简单单目标条件搜索可改为快;复杂权衡、推荐、外部验证仍慢 | +| `SLOW_DISAMBIGUATE` | 非标准称呼但可唯一识别且动作明确可改为快;真实多候选仍慢 | +| `SLOW_GOAL_STATE` | 只有目标状态、需反推动作时慢;动作和对象已明确时可能改为快 | +| `SLOW_WORKFLOW` | 多步/跨工具仍慢;多个全部明确的设备控制动作改为快 | +| `SLOW_CONTEXT_DEPENDENT` | 真正依赖上文推理时慢;已建立快操作流中的简单直接续接可快 | +| `SLOW_UNCLEAR` | 执行意图需澄清时慢;若缺端侧导致政策无法确定,可标模糊并复核 | +| `SLOW_ADVANCED_CAPABILITY` | 废除按能力白名单直接定语义;按用户预期重判,能力缺口另记 | + +兼容输出规则: + +- 只有新结论能被旧标签无损表达时才填写 `legacy_eval_label`。 +- `模糊`通常无法无损映射到旧二值标签,默认填 `null`。 +- 业务方明确要求旧二值路由时,可从 `default_route` 派生,但不要覆盖新 `label`。 +- 不要把 `complex_task=true` 与 `慢`画等号。 + +## 关键口径变化 + +| 维度 | 旧评测文档 | 2026.6 新标准 | +| --- | --- | --- | +| 核心视角 | 快系统白名单与能力边界 | 用户等待预期 | +| 标签空间 | 1 个 FAST + 7 个 SLOW | 快 / 慢 / 模糊 | +| 简单筛选 | 普遍视为 `SLOW_FILTER_RANK` | 目标明确、无需多步时可快 | +| 模糊称呼 | 普遍视为 `SLOW_DISAMBIGUATE` | 可唯一识别且动作明确时可快 | +| 多动作 | 普遍视为 `SLOW_WORKFLOW` | 全部明确设备控制是快特例 | +| 产品知识/故障/状态 | 快慢二分 | 正式标为模糊 | +| 能力不支持 | `SLOW_ADVANCED_CAPABILITY` | 标签不变;快能力缺口标待承接 | +| 边界 case | 短期一律慢 | 保留模糊语义,默认路由慢 | +| 短 query | 实验中 ≤5 字默认快 | 禁止作为语义规则 | + +内部冲突也要处理: + +- 新标准物理页 2 把“评分 4.5 以上的泰国菜”列入导航慢,物理页 8 又明确列为快;使用最终汇总的快。 +- 新标准垂域调研把“压线噔噔的声音关掉”归入模糊功能指代慢,物理页 9 明确改为快;使用最终汇总的快。 +- 新标准调研把静态知识、故障和部分状态查询直接分快慢,物理页 10 统一为模糊;保留模糊标签。 + +## 评测集构建 + +复用旧文档的闭环: + +```text +候选挖掘 + -> LLM 预打标 + -> 人工背靠背盲标 + -> 分歧仲裁与规则回灌 + -> 种子模型 + -> 主动学习 + -> 真实流量回放 + -> 覆盖分析 +``` + +候选来源: + +1. 线上真实流量。 +2. 明确功能点 TopQuery。 +3. 现有快慢评测集。 +4. LLM 生成或改写的紧邻边界对。 + +建议数据记录至少保留: + +- 原始 ID、query、session 和上下文。 +- 端侧、设备、来源和时间。 +- 新三档语义标签及默认路由。 +- 原因码、证据、置信度、复核状态。 +- 可选旧标签和结构维度。 +- 标注员、规则版本和仲裁结果。 + +## 质检门禁 + +### 标注一致性 + +- 使用同一份边界基准集校准 Prompt 和标注员。 +- 对高分歧样本执行两名标注员背靠背盲标。 +- 计算 IAA,并把分歧原因回灌规则和黄金样例。 +- 把人机分歧与人人分歧分开分析。 + +### 数据完整性 + +- 对同一 session 使用一致的上下文窗口。 +- 区分真实流量、旧集迁移和合成改写来源。 +- 对 session 去重,并隔离训练集和评测集。 +- 保留 `模糊`,不要在语义真值中提前二值化。 + +### 覆盖与指标 + +- 按快/慢/模糊、原因码、垂域、端侧和来源分层统计。 +- 单独统计慢误分为快的高风险错误,以及快误分为慢的延迟损失。 +- 同时报告宏平均、各类召回和混淆矩阵,不只报告总准确率。 +- 不使用旧实验分布作为目标配额。 + +### 两道门 + +1. 语义门:人工或模型是否依据当前规则得出正确结论。 +2. 结构门:记录是否通过 `scripts/validate_label.py`。 + +结构校验通过不代表语义正确。 + +## 禁止固化的旧信息 + +不要把以下内容写成新策略: + +- “≤5 字默认快”。 +- “上轮慢则次轮必慢”的无条件继承。 +- “边界一律慢”的永久语义规则。 +- 旧快系统白名单或 `SLOW_ADVANCED_CAPABILITY`。 +- 旧实验中任一类别占比。 +- 2K/3K 种子规模、5K/1W 最终规模等互相冲突的项目目标。 +- 95%/98% 等未统一口径的验收数字。 diff --git a/skills/label-fast-slow-routing/references/policy.md b/skills/label-fast-slow-routing/references/policy.md new file mode 100644 index 0000000..077a193 --- /dev/null +++ b/skills/label-fast-slow-routing/references/policy.md @@ -0,0 +1,332 @@ +# XA4.1 2026.6 快慢分流策略 + +## 目录 + +- [真源与概念](#真源与概念) +- [三档标签](#三档标签) +- [决策树](#决策树) +- [快的规则](#快的规则) +- [慢的规则](#慢的规则) +- [模糊的规则](#模糊的规则) +- [上下文、多指令和聚合](#上下文多指令和聚合) +- [垂域补充](#垂域补充) +- [原因码](#原因码) +- [能力与默认路由](#能力与默认路由) + +## 真源与概念 + +使用《【XA4.1】快慢分流新标准-2026.6》的最终汇总作为主真源。该文档物理页 7–10 的最终汇总高于物理页 2–7 的垂域调研草案;更早的《快慢系统分发-评测集构建》只提供旧原因分类和评测方法。 + +判断核心问题: + +> 用户说出这句话时,心里能接受等多久? + +不要用“当前快系统能不能做”代替这个问题。 + +保持以下概念正交: + +- 语义标签:快、慢、模糊。 +- 默认路由:快系统或慢系统。 +- 能力承接:当前快系统是否已支持。 +- 业务标签:导航、车控、产品问答等。 +- 结构维度:complex、多指令、自动任务、上下文依赖。 + +## 三档标签 + +| 标签 | 用户预期 | 默认策略 | +| --- | --- | --- | +| 快 | 立即响应,通常期望 2–3 秒内得到动作或确定结果 | 快系统 | +| 慢 | 愿意等几秒换取规划、推理或高质量结果 | 慢系统 | +| 模糊 | 对速度和质量都有要求,或难以可靠归入快/慢 | 默认慢系统,可由明确细分策略覆盖 | + +`模糊` 是正式语义标签,不是模型置信度。高置信产品知识问答仍可标为 `模糊`;低置信执行请求不一定因此成为 `模糊`,还要看是否需要澄清或补上下文。 + +## 决策树 + +按顺序执行: + +```text +1. query 是否包含要产生副作用的执行请求? + 否: + a. 闲聊/开放问答/产品静态知识/故障诊断/ + 设备或环境状态查询 -> 模糊 + b. 高频、唯一、确定性的工具或导航流查询 -> 快 + c. 需要多步分析、换算、聚合或生成复杂产物 -> 慢 + + 是: + a. 只有感受/意向,无法确定要做什么 -> 慢 + b. 只有目标状态/场景,需映射动作组合 -> 慢 + c. 真实歧义、缺关键槽位、需依赖上文才可决定 -> 慢 + d. 需规划、多步、多工具、复杂比较或跨源推理 -> 慢 + e. 动作与目标直接可识别,无需先决定“做什么” -> 快 + +2. 多个子请求: + a. 全部是明确设备控制动作 -> 快 + b. 其他情况按 慢 > 模糊 > 快 聚合 + +3. 仍无法归类,或端侧缺失导致策略确实不同 -> 模糊, + default_route=慢系统,review_required=true +``` + +“快系统当前不支持”不出现在语义决策树中。 + +## 快的规则 + +命中以下一类且没有更高优先级慢条件时标 `快`: + +### 明确直接动作 + +- 有明确操作目标。 +- 动作和设备、对象或资源可直接识别。 +- 不需要先推断用户到底想做什么。 + +例: + +- 开空调。 +- 把温度调到 24 度。 +- 关车窗。 +- 播放周杰伦的歌。 +- 帮我定明天早上 9 点的闹钟。 + +感受词不覆盖明确目标: + +- “太冷了,调一下空调”仍是快。 +- “调一个浪漫点的氛围灯”仍是快。 + +### 简单条件与非标准称呼 + +- 单一明确目标可以带位置、距离、评分、附近、风格等简单修饰。 +- 非标准称呼只要能唯一映射到对象,并且动作明确,仍为快。 + +例: + +- 导航去附近评分 4.5 以上的泰国菜。 +- 导航去附近最大的停车场。 +- 那个压线噔噔的声音关掉。 + +不要把“有筛选词”或“名称不标准”直接当成慢。只有需要权衡多个方案、外部事实验证、真实澄清或多步规划时才转慢。 + +### 操作流中的直接动作 + +- 删除途经点。 +- 切换导航播报。 +- 切换路线。 +- 下一首、暂停、声音调大。 +- 高频且答案唯一的工具查询,例如“现在几点”。 + +### 多个明确设备控制 + +多个动作全部为明确设备控制时仍标快: + +- 把空调调到 24 度,然后打开座椅加热。 +- 关客厅灯并打开空调。 + +跨工具编排、查询后再决定、先推荐再执行不适用该特例。 + +## 慢的规则 + +命中以下任一决定性条件时标 `慢`: + +### 意向反推 + +用户只表达主观感受、身体状态、情绪、不满或潜在需求,没有给出可直接识别的操作目标,系统必须反推要做什么。 + +例: + +- 我感觉好冷快冻死了,帮我调一下。 +- 脚底下好冷,腿快冻麻了。 +- 屏幕太亮有点刺眼,帮我弄一下。 + +对照: + +- “太冷了,调一下空调”有明确对象和动作,标快。 + +### 场景或目标状态映射 + +用户描述结果应是什么样,而不是要执行什么动作;系统必须把场景映射为一个或多个功能。 + +例: + +- 后排宝宝睡着了,把车里调成适合睡觉的状态。 +- 今天情人节,帮我营造点节日氛围。 +- 帮我把房间布置得舒适温馨。 + +### 规划、推理和多步编排 + +需要先决定方案,再执行;或需要多步计算、数值换算、跨源聚合、复杂比较、多工具承接。 + +例: + +- 导航到主目的地,路上找充电站,再看看终点附近哪里好停车。 +- 找王菲在春晚与别人合唱的歌,再用网易云播放。 +- 查天气再决定要不要开窗。 +- 做一个介绍 AI4S 的 PPT。 + +### 真实消歧、补槽和上下文推理 + +- 存在多个可行对象,必须追问才能选择。 +- 缺少执行所必需且不能稳定默认的槽位。 +- 当前短句必须结合前轮才能确定对象或字段。 +- 上一轮慢系统已发起确认,当前轮为确认、取消或继续。 + +例: + +- “打开那个灯”,且场景里存在多个灯。 +- 前轮慢系统询问是否开启湿滑模式;当前轮“帮我开启”。 + +不要把非标准称呼、口语改口或简单补充一律视为真实消歧。 + +### 复杂选择 + +单个简单筛选条件可快;需要综合主观体验、实时状态、风险、外部事实或多个相互权衡的条件时标慢。 + +例: + +- 推荐一家安静、适合亲子、评分高、停车方便且别排太久的酒店。 +- 找一个冷门但不能踩雷、出餐快且人不多的餐厅。 + +## 模糊的规则 + +### 纯信息型 + +以下纯信息请求标 `模糊`: + +- 闲聊、聊天和开放问答。 +- 产品功能、规格、概念、规则、使用方法等静态知识。 +- 报异常、问故障、问原因、要排查建议。 +- 设备状态或环境信息查询,无控制副作用。 + +例: + +- 给我讲个笑话。 +- 为什么天空是蓝色的? +- 小米 SU7 支持哪些驾驶模式? +- 哨兵模式是什么意思? +- 我的车怎么突然没声音了? +- 家里哪个房间最热? +- 空调已经开了多久? + +### 不要扩大模糊档 + +- 现在几点、高频导航状态等唯一且期望即时的工具查询可标快。 +- 需要制作复杂产物或多步分析的信息任务标慢。 +- 执行对象不明且需要澄清时通常标慢,不要仅因“有歧义”就标模糊。 + +### 策略不完整 + +如果缺失的端侧或场景信息会导致正式规则给出不同标签,可标 `模糊`,并设置低置信和人工复核。不要把普通的模型犹豫都标成模糊。 + +## 上下文、多指令和聚合 + +### 上下文 + +- 只在输入真实提供前轮时使用。 +- 区分“继承已建立的直接操作流”和“必须推理上文才能理解”。 +- 上一轮慢系统主动追问后的确认/取消继续走慢。 +- 上一轮快时,当前简单直接动作可继续快;复杂补充转慢。 +- 缺少前轮而当前短句不可独立理解时,标慢并复核;若端侧政策本身不确定,可标模糊并复核。 + +### 多指令 + +- 全部为明确设备控制动作:快。 +- 含查询后判断、跨 App、跨工具、规划或生成:慢。 +- 可独立拆分路由时先对子请求分别标注,再保留聚合标签。 + +### 聚合 + +默认使用风险主导顺序: + +```text +慢 > 模糊 > 快 +``` + +只对“全部为明确设备控制动作”应用快特例。 + +## 垂域补充 + +### 导航和生活服务 + +- 单地点、目标明确、简单条件筛选:快。 +- 地图操作和高频导航问答:快。 +- 多地点、多动作路线编排、复杂综合决策、外部事实定位:慢。 +- “评分 4.5 以上的泰国菜”按最终汇总标快,覆盖调研草案中的慢标签。 + +### 车控、系统控制和 IoT + +- 明确设备、功能和动作:快。 +- 只有感受、场景目标、复杂排除或位置集合推理:慢。 +- 非标准功能名但可唯一识别且动作明确:快。 +- 设备或环境状态查询:模糊。 + +### 内容和媒体 + +- 精确播放、明确资源、直接播控:快。 +- 描述性搜歌、歌词片段、场景推荐、歌单编排、收藏搜索、复杂内容承接:慢。 +- 纯内容知识问答按信息型处理,通常模糊。 + +### 通用工具 + +- 原子化且结果唯一的查询或控制:快。 +- 多步换算、结合外部信息再加工、缺关键槽位、开放生成:慢。 +- 不要仅因需要联网或 parser 暂不支持就标慢。 + +### 自动化 + +- 音箱端家庭自动化默认慢。 +- 音箱定时任务和意向化自动化慢。 +- 车载闹钟或提醒按文档专项策略可快。 +- 缺端侧且端侧会改变标签时,标模糊并复核。 + +## 原因码 + +每条记录选择一个决定性的 `primary_reason_code`,并在 `reason_codes` 中补充其他命中原因。 + +### 快 + +| 原因码 | 含义 | +| --- | --- | +| `DIRECT_ACTION` | 动作和目标直接可识别 | +| `DIRECT_DETERMINISTIC_QUERY` | 高频、唯一、确定性的直接查询 | +| `EXPLICIT_DEVICE_MULTI_ACTION` | 多个动作全部为明确设备控制 | +| `DIRECT_OPERATION_FLOW` | 已建立操作流中的直接操作或播控 | + +### 慢 + +| 原因码 | 含义 | +| --- | --- | +| `INTENT_INFERENCE` | 从感受或不满反推动作 | +| `SCENE_GOAL_MAPPING` | 将场景或目标状态映射为动作组合 | +| `MULTI_STEP_PLANNING` | 多步骤、多工具或任务编排 | +| `COMPLEX_REASONING` | 换算、聚合、外部验证或复杂比较 | +| `TRUE_DISAMBIGUATION` | 存在真实多候选,必须消歧 | +| `CONTEXT_REQUIRED` | 必须依赖前轮才能决定 | +| `MISSING_REQUIRED_SLOT` | 缺关键且不可默认的执行槽位 | +| `AUTOMATION_SLOW_POLICY` | 命中明确的慢自动化专项策略 | + +### 模糊 + +| 原因码 | 含义 | +| --- | --- | +| `OPEN_CHAT_OR_QA` | 闲聊或开放问答 | +| `PRODUCT_KNOWLEDGE` | 产品静态知识 | +| `FAULT_DIAGNOSIS` | 故障、异常、原因或排查建议 | +| `DEVICE_OR_ENV_STATE_QUERY` | 无副作用的设备或环境状态查询 | +| `POLICY_UNCERTAIN` | 缺少会改变正式策略的端侧或规则信息 | + +## 能力与默认路由 + +固定派生: + +| label | default_route | +| --- | --- | +| 快 | 快系统 | +| 慢 | 慢系统 | +| 模糊 | 慢系统 | + +`fast_system_pending`: + +- `true`:已有证据证明本应为快,但快系统尚未承接。 +- `false`:已有能力清单或验证证明已承接。 +- `null`:未评估能力;默认使用该值。 + +不得因为 `fast_system_pending=true` 修改 `label` 或 `default_route`。 diff --git a/skills/label-fast-slow-routing/schemas/label-record.schema.json b/skills/label-fast-slow-routing/schemas/label-record.schema.json new file mode 100644 index 0000000..1e2cc4d --- /dev/null +++ b/skills/label-fast-slow-routing/schemas/label-record.schema.json @@ -0,0 +1,293 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://local.skills/label-fast-slow-routing/label-record.schema.json", + "title": "快慢分流打标记录", + "type": "object", + "additionalProperties": false, + "required": [ + "query", + "label", + "default_route", + "primary_reason_code", + "reason_codes", + "reason", + "evidence", + "context_used", + "fast_system_pending", + "confidence", + "review_required", + "uncertainties", + "policy_version" + ], + "properties": { + "id": { + "type": ["string", "number", "null"] + }, + "query": { + "type": "string", + "minLength": 1 + }, + "label": { + "enum": ["快", "慢", "模糊"] + }, + "default_route": { + "enum": ["快系统", "慢系统"] + }, + "primary_reason_code": { + "$ref": "#/$defs/reasonCode" + }, + "reason_codes": { + "type": "array", + "minItems": 1, + "uniqueItems": true, + "items": { + "$ref": "#/$defs/reasonCode" + } + }, + "reason": { + "type": "string", + "minLength": 1 + }, + "evidence": { + "type": "array", + "minItems": 1, + "items": { + "type": "string", + "minLength": 1 + } + }, + "counterevidence": { + "type": "array", + "items": { + "type": "string", + "minLength": 1 + } + }, + "domain_label": { + "type": ["string", "null"] + }, + "candidate_domain_labels": { + "type": "array", + "uniqueItems": true, + "items": { + "type": "string", + "minLength": 1 + } + }, + "structural_signals": { + "type": "object", + "additionalProperties": false, + "required": ["complex", "multi_instruction", "auto_task"], + "properties": { + "complex": { + "type": ["boolean", "null"] + }, + "multi_instruction": { + "type": ["boolean", "null"] + }, + "auto_task": { + "type": ["boolean", "null"] + } + } + }, + "context_used": { + "type": "boolean" + }, + "fast_system_pending": { + "type": ["boolean", "null"] + }, + "legacy_eval_label": { + "enum": [ + "FAST_DIRECT", + "SLOW_FILTER_RANK", + "SLOW_DISAMBIGUATE", + "SLOW_GOAL_STATE", + "SLOW_WORKFLOW", + "SLOW_CONTEXT_DEPENDENT", + "SLOW_UNCLEAR", + "SLOW_ADVANCED_CAPABILITY", + null + ] + }, + "confidence": { + "enum": ["high", "medium", "low"] + }, + "review_required": { + "type": "boolean" + }, + "uncertainties": { + "type": "array", + "items": { + "type": "string", + "minLength": 1 + } + }, + "policy_version": { + "const": "XA4.1-2026.6" + } + }, + "allOf": [ + { + "if": { + "properties": { + "label": { + "const": "快" + } + }, + "required": ["label"] + }, + "then": { + "properties": { + "default_route": { + "const": "快系统" + }, + "primary_reason_code": { + "enum": [ + "DIRECT_ACTION", + "DIRECT_DETERMINISTIC_QUERY", + "EXPLICIT_DEVICE_MULTI_ACTION", + "DIRECT_OPERATION_FLOW" + ] + }, + "reason_codes": { + "items": { + "enum": [ + "DIRECT_ACTION", + "DIRECT_DETERMINISTIC_QUERY", + "EXPLICIT_DEVICE_MULTI_ACTION", + "DIRECT_OPERATION_FLOW" + ] + } + } + } + } + }, + { + "if": { + "properties": { + "label": { + "const": "慢" + } + }, + "required": ["label"] + }, + "then": { + "properties": { + "default_route": { + "const": "慢系统" + }, + "fast_system_pending": { + "enum": [false, null] + }, + "primary_reason_code": { + "enum": [ + "INTENT_INFERENCE", + "SCENE_GOAL_MAPPING", + "MULTI_STEP_PLANNING", + "COMPLEX_REASONING", + "TRUE_DISAMBIGUATION", + "CONTEXT_REQUIRED", + "MISSING_REQUIRED_SLOT", + "AUTOMATION_SLOW_POLICY" + ] + }, + "reason_codes": { + "items": { + "enum": [ + "INTENT_INFERENCE", + "SCENE_GOAL_MAPPING", + "MULTI_STEP_PLANNING", + "COMPLEX_REASONING", + "TRUE_DISAMBIGUATION", + "CONTEXT_REQUIRED", + "MISSING_REQUIRED_SLOT", + "AUTOMATION_SLOW_POLICY" + ] + } + } + } + } + }, + { + "if": { + "properties": { + "label": { + "const": "模糊" + } + }, + "required": ["label"] + }, + "then": { + "properties": { + "default_route": { + "const": "慢系统" + }, + "fast_system_pending": { + "enum": [false, null] + }, + "primary_reason_code": { + "enum": [ + "OPEN_CHAT_OR_QA", + "PRODUCT_KNOWLEDGE", + "FAULT_DIAGNOSIS", + "DEVICE_OR_ENV_STATE_QUERY", + "POLICY_UNCERTAIN" + ] + }, + "reason_codes": { + "items": { + "enum": [ + "OPEN_CHAT_OR_QA", + "PRODUCT_KNOWLEDGE", + "FAULT_DIAGNOSIS", + "DEVICE_OR_ENV_STATE_QUERY", + "POLICY_UNCERTAIN" + ] + } + } + } + } + }, + { + "if": { + "properties": { + "confidence": { + "const": "low" + } + }, + "required": ["confidence"] + }, + "then": { + "properties": { + "review_required": { + "const": true + } + }, + "required": ["review_required"] + } + } + ], + "$defs": { + "reasonCode": { + "enum": [ + "DIRECT_ACTION", + "DIRECT_DETERMINISTIC_QUERY", + "EXPLICIT_DEVICE_MULTI_ACTION", + "DIRECT_OPERATION_FLOW", + "INTENT_INFERENCE", + "SCENE_GOAL_MAPPING", + "MULTI_STEP_PLANNING", + "COMPLEX_REASONING", + "TRUE_DISAMBIGUATION", + "CONTEXT_REQUIRED", + "MISSING_REQUIRED_SLOT", + "AUTOMATION_SLOW_POLICY", + "OPEN_CHAT_OR_QA", + "PRODUCT_KNOWLEDGE", + "FAULT_DIAGNOSIS", + "DEVICE_OR_ENV_STATE_QUERY", + "POLICY_UNCERTAIN" + ] + } + } +} diff --git a/skills/label-fast-slow-routing/scripts/validate_label.py b/skills/label-fast-slow-routing/scripts/validate_label.py new file mode 100644 index 0000000..3f275d8 --- /dev/null +++ b/skills/label-fast-slow-routing/scripts/validate_label.py @@ -0,0 +1,352 @@ +#!/usr/bin/env python3 +"""Validate fast/slow/ambiguous label records without external dependencies.""" + +from __future__ import annotations + +import argparse +import json +import sys +from pathlib import Path +from typing import Any + + +POLICY_VERSION = "XA4.1-2026.6" + +REASON_CODES_BY_LABEL: dict[str, set[str]] = { + "快": { + "DIRECT_ACTION", + "DIRECT_DETERMINISTIC_QUERY", + "EXPLICIT_DEVICE_MULTI_ACTION", + "DIRECT_OPERATION_FLOW", + }, + "慢": { + "INTENT_INFERENCE", + "SCENE_GOAL_MAPPING", + "MULTI_STEP_PLANNING", + "COMPLEX_REASONING", + "TRUE_DISAMBIGUATION", + "CONTEXT_REQUIRED", + "MISSING_REQUIRED_SLOT", + "AUTOMATION_SLOW_POLICY", + }, + "模糊": { + "OPEN_CHAT_OR_QA", + "PRODUCT_KNOWLEDGE", + "FAULT_DIAGNOSIS", + "DEVICE_OR_ENV_STATE_QUERY", + "POLICY_UNCERTAIN", + }, +} + +DEFAULT_ROUTE_BY_LABEL = { + "快": "快系统", + "慢": "慢系统", + "模糊": "慢系统", +} + +LEGACY_LABELS = { + "FAST_DIRECT", + "SLOW_FILTER_RANK", + "SLOW_DISAMBIGUATE", + "SLOW_GOAL_STATE", + "SLOW_WORKFLOW", + "SLOW_CONTEXT_DEPENDENT", + "SLOW_UNCLEAR", + "SLOW_ADVANCED_CAPABILITY", +} + +CONFIDENCE_VALUES = {"high", "medium", "low"} + +REQUIRED_FIELDS = { + "query", + "label", + "default_route", + "primary_reason_code", + "reason_codes", + "reason", + "evidence", + "context_used", + "fast_system_pending", + "confidence", + "review_required", + "uncertainties", + "policy_version", +} + +OPTIONAL_FIELDS = { + "id", + "counterevidence", + "domain_label", + "candidate_domain_labels", + "structural_signals", + "legacy_eval_label", +} + + +def _is_nonempty_string(value: Any) -> bool: + return isinstance(value, str) and bool(value.strip()) + + +def _check_string_list( + value: Any, + *, + field: str, + errors: list[str], + require_nonempty: bool = False, + unique: bool = False, +) -> None: + if not isinstance(value, list): + errors.append(f"{field} 必须是数组") + return + if require_nonempty and not value: + errors.append(f"{field} 至少包含一项") + if any(not _is_nonempty_string(item) for item in value): + errors.append(f"{field} 的每一项都必须是非空字符串") + if unique and all(isinstance(item, str) for item in value): + if len(value) != len(set(value)): + errors.append(f"{field} 不允许重复项") + + +def validate_record(record: Any, index: int) -> dict[str, Any]: + errors: list[str] = [] + warnings: list[str] = [] + + if not isinstance(record, dict): + return { + "index": index, + "valid": False, + "errors": ["记录必须是 JSON 对象"], + "warnings": [], + } + + if "__parse_error__" in record: + return { + "index": index, + "valid": False, + "errors": [str(record["__parse_error__"])], + "warnings": [], + } + + missing = sorted(REQUIRED_FIELDS - set(record)) + if missing: + errors.append(f"缺少必填字段: {', '.join(missing)}") + + unknown = sorted(set(record) - REQUIRED_FIELDS - OPTIONAL_FIELDS) + if unknown: + errors.append(f"存在未知字段: {', '.join(unknown)}") + + query = record.get("query") + if not _is_nonempty_string(query): + errors.append("query 必须是非空字符串") + + label = record.get("label") + if label not in REASON_CODES_BY_LABEL: + errors.append("label 必须是 快、慢、模糊 之一") + + default_route = record.get("default_route") + expected_route = DEFAULT_ROUTE_BY_LABEL.get(label) + if expected_route is not None and default_route != expected_route: + errors.append(f"label={label} 时 default_route 必须是 {expected_route}") + + primary_reason_code = record.get("primary_reason_code") + reason_codes = record.get("reason_codes") + allowed_reasons = REASON_CODES_BY_LABEL.get(label, set()) + if primary_reason_code not in allowed_reasons: + errors.append( + f"primary_reason_code={primary_reason_code!r} 与 label={label!r} 不兼容" + ) + + if not isinstance(reason_codes, list): + errors.append("reason_codes 必须是数组") + else: + if not reason_codes: + errors.append("reason_codes 至少包含一项") + if all(isinstance(code, str) for code in reason_codes): + if len(reason_codes) != len(set(reason_codes)): + errors.append("reason_codes 不允许重复") + invalid_reason_codes = [ + code + for code in reason_codes + if not isinstance(code, str) or code not in allowed_reasons + ] + if invalid_reason_codes: + rendered_codes = ", ".join(repr(code) for code in invalid_reason_codes) + errors.append(f"reason_codes 包含与 label 不兼容的值: {rendered_codes}") + if primary_reason_code not in reason_codes: + errors.append("primary_reason_code 必须同时出现在 reason_codes 中") + + if not _is_nonempty_string(record.get("reason")): + errors.append("reason 必须是非空字符串") + + _check_string_list( + record.get("evidence"), + field="evidence", + errors=errors, + require_nonempty=True, + ) + + if "counterevidence" in record: + _check_string_list( + record.get("counterevidence"), + field="counterevidence", + errors=errors, + ) + + if "domain_label" in record: + domain_label = record.get("domain_label") + if domain_label is not None and not _is_nonempty_string(domain_label): + errors.append("domain_label 必须是非空字符串或 null") + + if "candidate_domain_labels" in record: + _check_string_list( + record.get("candidate_domain_labels"), + field="candidate_domain_labels", + errors=errors, + unique=True, + ) + + structural_signals = record.get("structural_signals") + if structural_signals is not None: + if not isinstance(structural_signals, dict): + errors.append("structural_signals 必须是对象") + else: + expected_keys = {"complex", "multi_instruction", "auto_task"} + actual_keys = set(structural_signals) + if actual_keys != expected_keys: + errors.append( + "structural_signals 必须且只能包含 " + "complex、multi_instruction、auto_task" + ) + for key in expected_keys: + value = structural_signals.get(key) + if value is not None and not isinstance(value, bool): + errors.append(f"structural_signals.{key} 必须是布尔值或 null") + + if not isinstance(record.get("context_used"), bool): + errors.append("context_used 必须是布尔值") + + fast_system_pending = record.get("fast_system_pending") + if fast_system_pending is not None and not isinstance(fast_system_pending, bool): + errors.append("fast_system_pending 必须是布尔值或 null") + if fast_system_pending is True and label != "快": + errors.append("只有 label=快 时 fast_system_pending 才能为 true") + + legacy_eval_label = record.get("legacy_eval_label") + if ( + "legacy_eval_label" in record + and legacy_eval_label is not None + and legacy_eval_label not in LEGACY_LABELS + ): + errors.append("legacy_eval_label 不是受支持的旧八类标签") + if label == "模糊" and legacy_eval_label is not None: + warnings.append("模糊档通常无法无损映射到旧二值标签,请确认迁移依据") + + confidence = record.get("confidence") + if confidence not in CONFIDENCE_VALUES: + errors.append("confidence 必须是 high、medium、low 之一") + + review_required = record.get("review_required") + if not isinstance(review_required, bool): + errors.append("review_required 必须是布尔值") + if confidence == "low" and review_required is not True: + errors.append("confidence=low 时 review_required 必须为 true") + + _check_string_list( + record.get("uncertainties"), + field="uncertainties", + errors=errors, + ) + uncertainties = record.get("uncertainties") + if isinstance(uncertainties, list) and uncertainties and review_required is False: + warnings.append("uncertainties 非空但 review_required=false,请确认不确定点不影响标签") + + if record.get("policy_version") != POLICY_VERSION: + errors.append(f"policy_version 必须是 {POLICY_VERSION}") + + return { + "index": index, + "id": record.get("id"), + "valid": not errors, + "errors": errors, + "warnings": warnings, + } + + +def _load_records_from_file(path: Path) -> list[Any]: + text = path.read_text(encoding="utf-8-sig") + if path.suffix.lower() == ".jsonl": + records: list[Any] = [] + for line_number, line in enumerate(text.splitlines(), start=1): + if not line.strip(): + continue + try: + records.append(json.loads(line)) + except json.JSONDecodeError as exc: + records.append( + { + "__parse_error__": ( + f"JSONL 第 {line_number} 行解析失败: {exc.msg}" + ) + } + ) + return records + + payload = json.loads(text) + if isinstance(payload, list): + return payload + if isinstance(payload, dict) and isinstance(payload.get("records"), list): + return payload["records"] + return [payload] + + +def _parse_args() -> argparse.Namespace: + parser = argparse.ArgumentParser( + description="校验 XA4.1-2026.6 快慢分流打标 JSON/JSONL" + ) + source_group = parser.add_mutually_exclusive_group(required=True) + source_group.add_argument("--record", help="单条 JSON 对象字符串") + source_group.add_argument("--file", type=Path, help="JSON 或 JSONL 文件路径") + parser.add_argument( + "--strict", + action="store_true", + help="把 warning 也视为校验失败", + ) + return parser.parse_args() + + +def main() -> int: + args = _parse_args() + try: + if args.record is not None: + records = [json.loads(args.record)] + else: + records = _load_records_from_file(args.file) + except (OSError, json.JSONDecodeError) as exc: + result = { + "valid": False, + "total": 0, + "invalid": 1, + "warning_count": 0, + "errors": [str(exc)], + "results": [], + } + print(json.dumps(result, ensure_ascii=False, indent=2)) + return 1 + + results = [validate_record(record, index) for index, record in enumerate(records)] + invalid = sum(not result["valid"] for result in results) + warning_count = sum(len(result["warnings"]) for result in results) + valid = invalid == 0 and (not args.strict or warning_count == 0) + summary = { + "valid": valid, + "total": len(records), + "invalid": invalid, + "warning_count": warning_count, + "results": results, + } + print(json.dumps(summary, ensure_ascii=False, indent=2)) + return 0 if valid else 1 + + +if __name__ == "__main__": + sys.exit(main())