Add intervention data skill

This commit is contained in:
wuyang6
2026-05-19 11:59:49 +08:00
parent 201058dfb2
commit 5684df3caf
7 changed files with 1316 additions and 0 deletions
+136
View File
@@ -0,0 +1,136 @@
---
name: intervention-data
description: 生成和校验单句精确干预、正则干预候选 TSV,复用标签大师的 target 语法校验,并从历史干预表推断 code 到下发 domain 的固定映射。
when_to_use: 当用户希望新增、检查或整理干预规则,例如给某个 query 或多轮正则生成干预条目、确认设备和标签是否合法、推断下发 domain 时使用。
aliases: intervention, rule-intervention, 干预数据
allowed_tools: read_file, write_file, edit_file, grep_search, glob_search, ask_user_question, python_exec
---
使用这个 skill 处理“query/多轮表达 -> 干预 TSV 候选 -> 合法性校验”的任务。
干预数据目前有两类:
- **单句精确干预**:一行四列,格式为 `设备\tquery\tcode\t下发domain`
- **正则干预**:一行三列,格式为 `设备\t正则\t标签`
其中单句精确干预的 `code` 和正则干预的 `标签` 使用同一套 target 语法,和 `label-master` 的输出一致,但不包含复杂度判断。校验时必须复用 `skills/label-master/scripts/validate_label_output.py` 的逻辑。
## 关键约定
- `unify` 表示全部设备。
- 设备必须优先从历史干预文件中归纳,不要随手发明设备名。
- 单句精确干预如果用户没有提供 `下发domain`,必须从历史精确干预表中按 `code -> 下发domain` 的多数映射推断。
- 正则干预支持多轮。多轮正则使用 `beforeQuery#上一轮#query#当前轮` 这种片段表达,最终通常包在 `^...$` 中。
- 默认只生成候选 TSV 和校验结果,不直接追加到历史干预文件。只有用户明确要求“追加到某个文件末尾”或“修改某个文件”时,才使用通用文件编辑能力。
- 所有产物优先写到当前会话的 `output/``scratchpad/` 下,不要写到项目根目录的 `output/`
## 推荐流程
1. 先确认用户要哪种干预方式:
- 单句精确干预:query 必须完全命中。
- 正则干预:适合一类表达、多轮上下文或需要泛化的高频 case。
2. 确认设备。用户没说设备时,必须问设备;可以提示 `unify` 表示全部设备。
3. 确认目标标签/code。它必须是完整 target,例如 `Agent(tag="地图导航")``QA()``Chat()` 或合法 function 调用。
4. 如果是单句精确干预:
- 确认 `query`
- 如果没有 `下发domain`,调用脚本根据历史数据推断。
5. 如果是正则干预:
- 确认当前轮 query。
- 如果有多轮,确认前序 query 顺序。
- 用户给了原始正则就保留;没给时由脚本生成 `^query#...$``^beforeQuery#...#query#...$`
6. 调用 `scripts/generate_intervention_candidate.py` 生成候选 TSV 和校验报告。
7. 展示时只展示必要信息:干预类型、设备、候选 TSV、校验是否通过、需要用户确认的问题。
## 脚本能力
```text
skills/intervention-data/
SKILL.md
knowledge/
type_match_agent.json
scripts/
intervention_common.py
analyze_intervention_sources.py
audit_intervention_sources.py
generate_intervention_candidate.py
validate_intervention_records.py
```
### analyze_intervention_sources.py
读取历史干预文件,输出设备枚举、`code -> 下发domain` 多数映射、常见标签,以及 `knowledge/type_match_agent.json` 中的下发表。
示例:
```bash
python skills/intervention-data/scripts/analyze_intervention_sources.py
```
### generate_intervention_candidate.py
从 JSON 输入生成候选 TSV,并自动校验。输入可以来自文件或 stdin。
### audit_intervention_sources.py
审计现有两个历史干预文件,只输出摘要,不修改文件。适合验证当前 skill 的校验规则和存量数据是否贴合。
示例:
```bash
python skills/intervention-data/scripts/audit_intervention_sources.py
```
单句精确干预示例:
```json
{
"mode": "exact",
"device": "unify",
"query": "播放周杰伦的歌",
"target": "Agent(tag=\"音乐\")"
}
```
正则多轮干预示例:
```json
{
"mode": "regex",
"device": "miCar",
"before_queries": ["第一个"],
"query": "红绿灯少的路线",
"target": "Agent(tag=\"地图导航\")"
}
```
### validate_intervention_records.py
校验候选 TSV。支持 `--mode exact``--mode regex`
规则包括:
- 列数正确。
- 设备合法。
- `code` / `标签` 优先通过 label-master 校验;如果 label-master 未收录但历史干预表中已经稳定出现,允许作为候选通过,并给出 warning 让用户确认知识库是否需要补齐。
- 单句精确干预的 `下发domain` 与历史多数映射一致,默认不一致为 warning,`--strict-domain` 时为 error。
- 单句精确干预还会用 `type_match_agent.json` 做配置校对;配置值可以是更宽泛前缀,例如 `音乐 -> contentCopilot` 可以兼容历史里的 `contentCopilot|music`
- 正则可编译,且包含 `query#` 片段。
- 与历史文件重复或冲突时给出 warning/error。
## 展示格式
生成候选后,优先用这个格式回复:
```text
我生成了一条候选干预,先不写入历史文件。
- 类型:单句精确干预 / 正则干预
- 设备:xxx
- 标签:xxx
- 校验:通过 / 有问题
候选 TSV`设备 query code 下发domain`
需要确认:是否写入某个文件,或是否调整设备/标签/正则。
```
如果校验失败,先解释失败原因,不要要求用户直接落盘。
@@ -0,0 +1,211 @@
{
"音乐": "contentCopilot",
"音乐#tvV2": "contentCopilot|video",
"音乐#miCar": "contentCopilot|station",
"音乐#screenSoundbox": "contentCopilot|music",
"音乐#glass": "contentCopilot|ancientPoem",
"电台": "contentCopilot",
"电台#tvV2": "contentCopilot|video",
"视频": "contentCopilot",
"古诗": "contentCopilot",
"歌单": "contentCopilot|controlCopilot|soundboxControl|smartApp",
"应用播放": "contentCopilot|controlCopilot|soundboxControl|smartApp",
"新闻": "contentCopilot",
"笑话": "contentCopilot",
"内容控制": "contentCopilot|soundboxControl|controlCopilot",
"内容控制|系统控制": "contentCopilot|soundboxControl|controlCopilot",
"内容控制|应用控制": "contentCopilot|smartApp|controlCopilot",
"播放状态查询": "contentCopilot|soundboxControl|controlCopilot",
"内容问答": "contentCopilot|qabot|dialogCopilot",
"内容问答#tvV2": "contentCopilot|qabot|dialogCopilot|video",
"内容问答#soundbox": "contentCopilot|qabot|dialogCopilot|michat",
"赛事播放": "contentCopilot|qabot|dialogCopilot|sports",
"赛事预约": "contentCopilot|qabot|dialogCopilot|sports",
"声音": "contentCopilot",
"人物": "dialogCopilot",
"人物#tvV2": "dialogCopilot|video",
"问答": "dialogCopilot|open-class|shopping|productAgent",
"问答#tvV2": "dialogCopilot|open-class|shopping|productAgent|video",
"百科": "dialogCopilot",
"百科#tvV2": "dialogCopilot|video",
"医疗": "dialogCopilot",
"星座": "dialogCopilot",
"菜谱": "dialogCopilot",
"民俗": "dialogCopilot",
"运动": "contentCopilot|dialogCopilot|sports|qabot",
"体育赛事问答": "contentCopilot|dialogCopilot|sports|qabot",
"词典": "dialogCopilot",
"彩票": "dialogCopilot",
"投资": "dialogCopilot",
"股票": "dialogCopilot",
"闲聊": "dialogCopilot|feedback|scenes|fitnessHealth",
"闲聊#tvV2": "dialogCopilot|video|feedback|scenes|help|fitnessHealth|shopping|productAgent",
"用户反馈": "dialogCopilot|feedback",
"角色扮演": "dialogCopilot",
"重说": "dialogCopilot",
"图片问答": "dialogCopilot|imageLU|mapCopilot",
"屏幕问答": "dialogCopilot|imageLU|mapCopilot|smartApp|controlCopilot|productAgent|auto|lifeCopilot|lifeAgent",
"拍照问答": "dialogCopilot|imageLU|mapCopilot",
"搜索": "dialogCopilot|search",
"搜索#tvV2": "dialogCopilot|search|video|contentCopilot",
"搜索#soundbox": "dialogCopilot|search|music|contentCopilot",
"健康监控#glass": "dialogCopilot|toolsCopilot",
"地图导航": "mapCopilot|lifeCopilot|lifeAgent",
"地图导航#tvV2": "mapCopilot|lifeCopilot|lifeAgent|mapApp",
"地图问答": "mapCopilot|qabot|dialogCopilot",
"走哪问哪": "mapCopilot|qabot|dialogCopilot",
"地图设置": "mapCopilot|soundboxControl|controlCopilot|toolsCopilot|todolist",
"违章查询": "mapCopilot",
"限号": "mapCopilot",
"地址设置": "toolsCopilot|mapCopilot|mapApp",
"图片": "aicreativeCopilot|dialogCopilot|qabot",
"图像创作": "aicreativeCopilot|dialogCopilot|qabot",
"视频创作": "aicreativeCopilot|dialogCopilot|qabot",
"作文": "aicreativeCopilot",
"文本创作": "aicreativeCopilot|dialogCopilot|toolsCopilot",
"代码创作": "aicreativeCopilot",
"图像编辑": "aicreativeCopilot",
"时间": "toolsCopilot",
"天气": "toolsCopilot|dialogCopilot|qabot",
"数学": "toolsCopilot",
"数学应用题": "toolsCopilot",
"电话": "toolsCopilot",
"看图打电话": "toolsCopilot",
"翻译": "toolsCopilot",
"翻译#glass": "toolsCopilot|dialogCopilot|qabot",
"闹钟": "toolsCopilot",
"备忘录": "toolsCopilot|dialogCopilot|michat",
"记忆": "toolsCopilot",
"结构记忆": "toolsCopilot|dialogCopilot|michat",
"视频收藏": "toolsCopilot|controlCopilot|smartApp",
"电话问答": "toolsCopilot|dialogCopilot|qabot",
"课程表": "toolsCopilot",
"内容总结": "toolsCopilot|aicreativeCopilot|dialogCopilot|qabot",
"留言": "toolsCopilot|message",
"健康监控": "toolsCopilot|fitnessHealth|todolist|dialogCopilot|qabot",
"健康控制": "toolsCopilot|fitnessHealth|todolist|controlCopilot|soundboxControl|smartApp",
"声纹": "controlCopilot|voiceprint",
"家庭传声": "controlCopilot|iotCopilot|smartMiot|dialogCopilot|qabot|michat|contentCopilot|music|station",
"系统控制": "controlCopilot|iotCopilot|productAgent",
"萌宠控制": "controlCopilot|iotCopilot|productAgent",
"屏幕操作": "controlCopilot|iotCopilot|productAgent",
"音色切换": "controlCopilot|iotCopilot|productAgent",
"车载控制": "controlCopilot|iotCopilot|productAgent",
"车载设备状态查询": "controlCopilot|iotCopilot|productAgent",
"系统查询": "controlCopilot|iotCopilot|productAgent",
"设备控制": "controlCopilot|iotCopilot",
"家用设备状态查询": "controlCopilot|iotCopilot|productAgent",
"应用控制": "controlCopilot|iotCopilot|lifeCopilot|lifeAgent|fitnessHealth",
"生活缴费": "controlCopilot",
"应用窗口控制": "controlCopilot",
"应用控制#tvV2": "controlCopilot|iotCopilot|lifeCopilot|lifeAgent|fitnessHealth|video|soundboxControl|phonecall",
"应用控制#soundbox": "controlCopilot|contentCopilot|music|station|dialogCopilot|michat|qabot|video|fitnessHealth|lifeCopilot",
"应用控制搜索": "controlCopilot|smartApp",
"浏览器搜索": "controlCopilot|search|smartApp",
"相机": "controlCopilot",
"设备查找": "controlCopilot|smartMiot|michat",
"系统定时控制": "controlCopilot|iotCopilot|productAgent",
"设备定时控制": "controlCopilot|iotCopilot",
"应用定时控制": "controlCopilot|iotCopilot|lifeCopilot|lifeAgent|fitnessHealth",
"帮助": "productAgent|soundboxControl|controlCopilot",
"汽车手册": "productAgent|dialogCopilot|qabot",
"客服问答": "productAgent|soundboxControl|controlCopilot",
"购物": "productAgent|soundboxControl|controlCopilot",
"产品问答": "productAgent",
"手车互联": "productAgent|soundboxControl|controlCopilot",
"产品推荐": "productAgent",
"产品购买": "productAgent",
"外卖": "lifeCopilot",
"外卖|应用": "lifeCopilot|controlCopilot|smartApp",
"美食": "lifeCopilot",
"美食|应用": "lifeCopilot|controlCopilot|smartApp",
"景点": "lifeCopilot",
"景点|应用": "lifeCopilot|controlCopilot|smartApp",
"购票": "lifeCopilot",
"购票|应用": "lifeCopilot|controlCopilot|smartApp",
"电影": "lifeCopilot",
"电影|应用": "lifeCopilot|controlCopilot|smartApp",
"酒店": "lifeCopilot",
"酒店|应用": "lifeCopilot|controlCopilot|smartApp",
"生活休闲": "lifeCopilot",
"车服": "lifeCopilot",
"汽车服务": "lifeCopilot",
"团购": "lifeCopilot",
"打车": "lifeCopilot",
"航班查询": "lifeCopilot",
"通讯服务": "lifeCopilot",
"快递服务": "lifeCopilot|productAgent|shopping",
"产品推荐|参数对比": "lifeCopilot|productAgent|shopping",
"产品推荐|信息对比": "lifeCopilot|productAgent|shopping",
"平台比价": "productAgent|shopping",
"找同款": "lifeCopilot|productAgent|shopping",
"订单查询": "lifeCopilot|productAgent",
"订座排号": "lifeCopilot",
"前车识别": "dialogCopilot|imageLU|qabot",
"频道": "contentCopilot",
"频道#tvV2": "contentCopilot|controlCopilot|smartMiot|iotCopilot",
"频道#soundbox": "controlCopilot|smartMiot|iotCopilot",
"地图|搜索": "mapCopilot|controlCopilot|smartApp",
"生活服务|搜索": "lifeCopilot|lifeAgent|controlCopilot|smartApp",
"应用|购买": "controlCopilot|smartApp",
"地图控制": "mapCopilot|soundboxControl|controlCopilot|toolsCopilot|todolist",
"限行": "mapCopilot",
"音乐播放": "contentCopilot",
"音乐播放#tvV2": "contentCopilot|video",
"音乐播放#miCar": "contentCopilot|station",
"音乐播放#screenSoundbox": "contentCopilot|music",
"音乐播放#glass": "contentCopilot|ancientPoem",
"电台播放": "contentCopilot",
"电台播放#tvV2": "contentCopilot|video",
"视频播放": "contentCopilot",
"电视频道": "contentCopilot",
"电视频道#tvV2": "contentCopilot|controlCopilot|smartMiot|iotCopilot",
"电视频道#soundbox": "controlCopilot|smartMiot|iotCopilot",
"媒体资源问答": "contentCopilot|qabot|dialogCopilot",
"媒体资源问答#tvV2": "contentCopilot|qabot|dialogCopilot|video",
"媒体资源问答#soundbox": "contentCopilot|qabot|dialogCopilot|michat",
"媒体应用播放": "contentCopilot|controlCopilot|soundboxControl|smartApp",
"媒体资源切换": "contentCopilot|soundboxControl|controlCopilot",
"播放器控制": "contentCopilot|soundboxControl|controlCopilot",
"体育赛事播放": "contentCopilot|qabot|dialogCopilot|sports",
"体育赛事预约": "contentCopilot|qabot|dialogCopilot|sports",
"古诗词播放": "contentCopilot",
"古诗词问答": "contentCopilot",
"讲笑话": "contentCopilot",
"声音博物馆": "contentCopilot",
"闹钟计时器": "toolsCopilot",
"简单数学问题": "toolsCopilot",
"打电话": "toolsCopilot",
"发短信": "toolsCopilot",
"通讯录": "toolsCopilot",
"电话号码查询": "toolsCopilot|dialogCopilot|qabot",
"外语翻译": "toolsCopilot",
"翻译控制": "toolsCopilot",
"外语词典查询": "toolsCopilot",
"外语问答": "toolsCopilot",
"外语翻译#glass": "toolsCopilot|dialogCopilot|qabot",
"外语问答#glass": "toolsCopilot|dialogCopilot|qabot",
"外语词典查询#glass": "toolsCopilot|dialogCopilot|qabot",
"翻译控制#glass": "toolsCopilot|dialogCopilot|qabot",
"提醒": "toolsCopilot|dialogCopilot|michat",
"信息记忆": "toolsCopilot",
"记忆查询": "toolsCopilot|dialogCopilot|michat",
"个人信息": "toolsCopilot|dialogCopilot|michat",
"声纹设置": "controlCopilot|voiceprint",
"相机控制": "controlCopilot",
"系统状态查询": "controlCopilot|iotCopilot|productAgent",
"小爱帮助": "productAgent|soundboxControl|controlCopilot",
"小米产品帮助": "productAgent",
"商品购买": "productAgent",
"餐饮服务": "lifeCopilot",
"餐饮服务|应用": "lifeCopilot|controlCopilot|smartApp",
"旅游": "lifeCopilot",
"生活服务": "lifeCopilot",
"交通购票": "lifeCopilot",
"电影票购买": "lifeCopilot",
"电影票购买|应用": "lifeCopilot|controlCopilot|smartApp",
"航班信息查询": "lifeCopilot",
"话费流量服务": "lifeCopilot",
"购物订单查询": "lifeCopilot|productAgent",
"餐厅订座排号": "lifeCopilot"
}
@@ -0,0 +1,31 @@
#!/usr/bin/env python3
"""分析历史干预文件,归纳设备枚举和 code 到下发 domain 的映射。"""
from __future__ import annotations
import argparse
from pathlib import Path
from intervention_common import DEFAULT_EXACT_SOURCE, DEFAULT_REGEX_SOURCE, build_source_summary, json_dumps
def main() -> int:
parser = argparse.ArgumentParser(description="分析历史干预 TSV")
parser.add_argument("--exact-source", default=str(DEFAULT_EXACT_SOURCE), help="单句精确干预 TSV 路径")
parser.add_argument("--regex-source", default=str(DEFAULT_REGEX_SOURCE), help="正则干预 TSV 路径")
parser.add_argument("--top-mapping", type=int, default=30, help="输出前 N 个 code-domain 映射")
args = parser.parse_args()
summary = build_source_summary(Path(args.exact_source), Path(args.regex_source))
mapping_items = sorted(
summary["code_domain_mapping"].items(),
key=lambda item: item[1]["count"],
reverse=True,
)
summary["code_domain_mapping"] = dict(mapping_items[: args.top_mapping])
print(json_dumps(summary))
return 0
if __name__ == "__main__":
raise SystemExit(main())
@@ -0,0 +1,247 @@
#!/usr/bin/env python3
"""审计现有干预文件和当前规则的贴合度。
这个脚本用于回答两个问题:
1. 现有单句精确干预/正则干预文件自身是否存在明显格式问题。
2. 当前 label-master 校验、type_match_agent 下发表和历史数据是否匹配。
它不会修改任何文件,只输出 JSON 摘要和少量样例。
"""
from __future__ import annotations
import argparse
import re
from collections import Counter, defaultdict
from pathlib import Path
from typing import Any
from intervention_common import (
DEFAULT_EXACT_SOURCE,
DEFAULT_REGEX_SOURCE,
build_source_summary,
domain_compatible,
infer_configured_dispatch_domain,
json_dumps,
load_exact_records,
load_label_validator,
load_regex_records,
)
def sample_append(bucket: list[dict[str, Any]], item: dict[str, Any], limit: int) -> None:
if len(bucket) < limit:
bucket.append(item)
def validate_unique_targets(targets: set[str]) -> dict[str, dict[str, Any]]:
validator = load_label_validator()
return {target: validator.validate(target) for target in sorted(targets)}
def audit_exact_records(summary: dict[str, Any], target_results: dict[str, dict[str, Any]], sample_limit: int) -> dict[str, Any]:
records = load_exact_records(Path(summary["source_files"]["exact"]))
empty_fields = Counter()
target_invalid = Counter()
configured_mismatch = Counter()
duplicate_keys: dict[tuple[str, str], list[Any]] = defaultdict(list)
samples = {
"empty_fields": [],
"invalid_targets": [],
"configured_domain_mismatch": [],
"conflicts": [],
}
for record in records:
if not record.device:
empty_fields["device"] += 1
if not record.query:
empty_fields["query"] += 1
if not record.target:
empty_fields["target"] += 1
if not record.dispatch_domain:
empty_fields["dispatch_domain"] += 1
if any(not value for value in (record.device, record.query, record.target, record.dispatch_domain)):
sample_append(samples["empty_fields"], record.__dict__, sample_limit)
result = target_results.get(record.target)
if result and not result.get("valid"):
target_invalid[record.target] += 1
sample_append(
samples["invalid_targets"],
{
"line_no": record.line_no,
"target": record.target,
"errors": result.get("errors", []),
},
sample_limit,
)
configured = infer_configured_dispatch_domain(record.target, record.device, summary)
if configured and not domain_compatible(record.dispatch_domain, configured["dispatch_domain"]):
configured_mismatch[configured["matched_key"]] += 1
sample_append(
samples["configured_domain_mismatch"],
{
"line_no": record.line_no,
"device": record.device,
"query": record.query,
"target": record.target,
"dispatch_domain": record.dispatch_domain,
"configured": configured,
},
sample_limit,
)
duplicate_keys[(record.device, record.query)].append(record)
conflicts = []
duplicate_count = 0
for (device, query), items in duplicate_keys.items():
if len(items) <= 1:
continue
duplicate_count += len(items)
variants = {(item.target, item.dispatch_domain) for item in items}
if len(variants) > 1:
conflict = {
"device": device,
"query": query,
"lines": [item.line_no for item in items[:10]],
"variants": sorted([{"target": target, "dispatch_domain": domain} for target, domain in variants], key=str),
}
conflicts.append(conflict)
sample_append(samples["conflicts"], conflict, sample_limit)
return {
"total": len(records),
"empty_fields": dict(empty_fields),
"invalid_target_total": sum(target_invalid.values()),
"invalid_target_top": dict(target_invalid.most_common(20)),
"configured_domain_mismatch_total": sum(configured_mismatch.values()),
"configured_domain_mismatch_top": dict(configured_mismatch.most_common(20)),
"duplicate_row_count_by_device_query": duplicate_count,
"conflict_key_count": len(conflicts),
"samples": samples,
}
def audit_regex_records(target_results: dict[str, dict[str, Any]], summary: dict[str, Any], sample_limit: int) -> dict[str, Any]:
records = load_regex_records(Path(summary["source_files"]["regex"]))
empty_fields = Counter()
regex_errors = Counter()
anchor_warnings = 0
missing_query_fragment = 0
target_invalid = Counter()
duplicate_keys: dict[tuple[str, str], list[Any]] = defaultdict(list)
samples = {
"empty_fields": [],
"regex_errors": [],
"missing_query_fragment": [],
"invalid_targets": [],
"conflicts": [],
}
for record in records:
if not record.device:
empty_fields["device"] += 1
if not record.pattern:
empty_fields["pattern"] += 1
if not record.target:
empty_fields["target"] += 1
if any(not value for value in (record.device, record.pattern, record.target)):
sample_append(samples["empty_fields"], record.__dict__, sample_limit)
if record.pattern:
try:
re.compile(record.pattern)
except re.error as exc:
regex_errors[str(exc)] += 1
sample_append(
samples["regex_errors"],
{"line_no": record.line_no, "pattern": record.pattern, "error": str(exc)},
sample_limit,
)
if "query#" not in record.pattern:
missing_query_fragment += 1
sample_append(samples["missing_query_fragment"], record.__dict__, sample_limit)
if not record.pattern.startswith("^") or not record.pattern.endswith("$"):
anchor_warnings += 1
result = target_results.get(record.target)
if result and not result.get("valid"):
target_invalid[record.target] += 1
sample_append(
samples["invalid_targets"],
{
"line_no": record.line_no,
"target": record.target,
"errors": result.get("errors", []),
},
sample_limit,
)
duplicate_keys[(record.device, record.pattern)].append(record)
conflicts = []
duplicate_count = 0
for (device, pattern), items in duplicate_keys.items():
if len(items) <= 1:
continue
duplicate_count += len(items)
variants = {item.target for item in items}
if len(variants) > 1:
conflict = {
"device": device,
"pattern": pattern,
"lines": [item.line_no for item in items[:10]],
"variants": sorted(variants),
}
conflicts.append(conflict)
sample_append(samples["conflicts"], conflict, sample_limit)
return {
"total": len(records),
"empty_fields": dict(empty_fields),
"regex_error_total": sum(regex_errors.values()),
"regex_error_top": dict(regex_errors.most_common(20)),
"missing_query_fragment": missing_query_fragment,
"anchor_warning_count": anchor_warnings,
"invalid_target_total": sum(target_invalid.values()),
"invalid_target_top": dict(target_invalid.most_common(20)),
"duplicate_row_count_by_device_pattern": duplicate_count,
"conflict_key_count": len(conflicts),
"samples": samples,
}
def main() -> int:
parser = argparse.ArgumentParser(description="审计现有干预文件")
parser.add_argument("--exact-source", default=str(DEFAULT_EXACT_SOURCE), help="单句精确干预 TSV 路径")
parser.add_argument("--regex-source", default=str(DEFAULT_REGEX_SOURCE), help="正则干预 TSV 路径")
parser.add_argument("--sample-limit", type=int, default=5, help="每类问题最多输出样例数")
args = parser.parse_args()
summary = build_source_summary(Path(args.exact_source), Path(args.regex_source))
exact_records = load_exact_records(Path(args.exact_source))
regex_records = load_regex_records(Path(args.regex_source))
target_results = validate_unique_targets({record.target for record in exact_records + regex_records if record.target})
payload = {
"source_files": summary["source_files"],
"devices": summary["devices"],
"counts": summary["counts"],
"target_validation": {
"unique_targets": len(target_results),
"valid_unique_targets": sum(1 for item in target_results.values() if item.get("valid")),
"invalid_unique_targets": sum(1 for item in target_results.values() if not item.get("valid")),
},
"exact": audit_exact_records(summary, target_results, args.sample_limit),
"regex": audit_regex_records(target_results, summary, args.sample_limit),
}
print(json_dumps(payload))
has_hard_errors = bool(payload["exact"]["empty_fields"] or payload["regex"]["regex_error_total"])
return 1 if has_hard_errors else 0
if __name__ == "__main__":
raise SystemExit(main())
@@ -0,0 +1,141 @@
#!/usr/bin/env python3
"""生成单句精确干预或正则干预候选 TSV,并返回校验结果。"""
from __future__ import annotations
import argparse
import json
import subprocess
import sys
from pathlib import Path
from typing import Any
from intervention_common import (
DEFAULT_EXACT_SOURCE,
DEFAULT_REGEX_SOURCE,
build_query_pattern,
build_source_summary,
exact_tsv_line,
infer_configured_dispatch_domain,
infer_dispatch_domain_for_device,
json_dumps,
normalize_device,
normalize_target,
regex_tsv_line,
)
SCRIPT_DIR = Path(__file__).resolve().parent
VALIDATE_SCRIPT = SCRIPT_DIR / "validate_intervention_records.py"
def load_payload(path: str | None) -> dict[str, Any]:
text = Path(path).read_text(encoding="utf-8") if path else sys.stdin.read()
if not text.strip():
raise ValueError("输入 JSON 不能为空")
return json.loads(text)
def run_validator(mode: str, tsv: str, exact_source: str, regex_source: str) -> dict[str, Any]:
proc = subprocess.run(
[
sys.executable,
str(VALIDATE_SCRIPT),
"--mode",
mode,
"--exact-source",
exact_source,
"--regex-source",
regex_source,
],
input=tsv,
text=True,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
check=False,
)
if not proc.stdout.strip():
return {
"valid": False,
"errors": [proc.stderr.strip() or f"校验脚本退出:{proc.returncode}"],
}
return json.loads(proc.stdout)
def generate_exact(payload: dict[str, Any], exact_source: str, regex_source: str) -> dict[str, Any]:
device = normalize_device(str(payload.get("device", "")))
query = str(payload.get("query", "")).strip()
target = normalize_target(str(payload.get("target") or payload.get("code") or ""))
dispatch_domain = str(payload.get("dispatch_domain") or payload.get("domain") or "").strip()
summary = build_source_summary(Path(exact_source), Path(regex_source))
inferred = None
configured = infer_configured_dispatch_domain(target, device, summary)
if not dispatch_domain:
inferred = infer_dispatch_domain_for_device(target, device, summary)
if inferred:
dispatch_domain = inferred["dispatch_domain"]
tsv = exact_tsv_line(device, query, target, dispatch_domain)
validation = run_validator("exact", tsv, exact_source, regex_source)
return {
"mode": "exact",
"tsv": tsv,
"record": {
"device": device,
"query": query,
"target": target,
"dispatch_domain": dispatch_domain,
},
"inferred_dispatch_domain": inferred,
"configured_dispatch_domain": configured,
"validation": validation,
}
def generate_regex(payload: dict[str, Any], exact_source: str, regex_source: str) -> dict[str, Any]:
device = normalize_device(str(payload.get("device", "")))
target = normalize_target(str(payload.get("target") or payload.get("label") or ""))
raw_pattern = str(payload.get("pattern") or payload.get("regex") or "").strip()
query = str(payload.get("query") or payload.get("current_query") or "").strip()
before_queries = payload.get("before_queries") or []
if not isinstance(before_queries, list):
raise ValueError("before_queries 必须是字符串数组")
pattern = raw_pattern or build_query_pattern(query, [str(item) for item in before_queries])
tsv = regex_tsv_line(device, pattern, target)
validation = run_validator("regex", tsv, exact_source, regex_source)
return {
"mode": "regex",
"tsv": tsv,
"record": {
"device": device,
"pattern": pattern,
"target": target,
},
"validation": validation,
}
def main() -> int:
parser = argparse.ArgumentParser(description="生成干预候选 TSV")
parser.add_argument("--input", help="输入 JSON 文件;不传则读取 stdin")
parser.add_argument("--exact-source", default=str(DEFAULT_EXACT_SOURCE), help="历史单句精确干预 TSV")
parser.add_argument("--regex-source", default=str(DEFAULT_REGEX_SOURCE), help="历史正则干预 TSV")
args = parser.parse_args()
payload = load_payload(args.input)
mode = str(payload.get("mode", "")).strip().lower()
if mode == "exact":
result = generate_exact(payload, args.exact_source, args.regex_source)
elif mode == "regex":
result = generate_regex(payload, args.exact_source, args.regex_source)
else:
raise ValueError("mode 必须是 exact 或 regex")
print(json_dumps(result))
return 0 if result.get("validation", {}).get("valid") else 1
if __name__ == "__main__":
raise SystemExit(main())
@@ -0,0 +1,330 @@
#!/usr/bin/env python3
"""干预数据脚本的公共能力。
这里尽量只做确定性处理:
- 读取历史干预 TSV。
- 归纳设备枚举和 code 到下发 domain 的多数映射。
- 复用 label-master 校验 target 语法。
- 校验候选干预条目是否和历史数据重复或冲突。
"""
from __future__ import annotations
import csv
import importlib.util
import json
import re
import sys
from collections import Counter, defaultdict
from dataclasses import dataclass
from pathlib import Path
from typing import Any
SCRIPT_DIR = Path(__file__).resolve().parent
SKILL_ROOT = SCRIPT_DIR.parent
REPO_ROOT = SKILL_ROOT.parent.parent
DEFAULT_EXACT_SOURCE = REPO_ROOT / "干预skill" / "20260134"
DEFAULT_REGEX_SOURCE = REPO_ROOT / "干预skill" / "20260207"
LABEL_VALIDATOR_PATH = REPO_ROOT / "skills" / "label-master" / "scripts" / "validate_label_output.py"
CONFIG_MAPPING_PATH = SKILL_ROOT / "knowledge" / "type_match_agent.json"
AGENT_TARGET_RE = re.compile(r'^Agent\(\s*tag\s*=\s*["\'](.+?)["\']\s*\)$')
@dataclass(frozen=True)
class ExactRecord:
device: str
query: str
target: str
dispatch_domain: str
line_no: int
@dataclass(frozen=True)
class RegexRecord:
device: str
pattern: str
target: str
line_no: int
def normalize_device(device: str) -> str:
"""归一化历史数据里的设备写法,但保留常见大小写。"""
value = device.strip()
if value.startswith("#"):
value = value[1:].strip()
aliases = {
"micar": "miCar",
"miai": "miai",
"tVV2": "tvV2",
"tvv2": "tvV2",
"unify": "unify",
}
return aliases.get(value, value)
def normalize_target(target: str) -> str:
return target.replace("", '"').replace("", '"').replace("", "'").replace("", "'").strip()
def read_tsv_rows(path: Path) -> list[list[str]]:
with path.open("r", encoding="utf-8-sig", newline="") as handle:
return list(csv.reader(handle, delimiter="\t"))
def load_exact_records(path: Path = DEFAULT_EXACT_SOURCE) -> list[ExactRecord]:
rows = read_tsv_rows(path)
records: list[ExactRecord] = []
for index, row in enumerate(rows, start=1):
if index == 1 and row[:4] == ["#设备名", "query", "code", "下发domain"]:
continue
if not row or all(not cell.strip() for cell in row):
continue
padded = [*row, "", "", "", ""]
records.append(
ExactRecord(
device=normalize_device(padded[0]),
query=padded[1].strip(),
target=normalize_target(padded[2]),
dispatch_domain=padded[3].strip(),
line_no=index,
)
)
return records
def load_regex_records(path: Path = DEFAULT_REGEX_SOURCE) -> list[RegexRecord]:
rows = read_tsv_rows(path)
records: list[RegexRecord] = []
for index, row in enumerate(rows, start=1):
if not row or all(not cell.strip() for cell in row):
continue
padded = [*row, "", ""]
records.append(
RegexRecord(
device=normalize_device(padded[0]),
pattern=padded[1].strip(),
target=normalize_target(padded[2]),
line_no=index,
)
)
return records
def build_source_summary(
exact_path: Path = DEFAULT_EXACT_SOURCE,
regex_path: Path = DEFAULT_REGEX_SOURCE,
) -> dict[str, Any]:
exact_records = load_exact_records(exact_path) if exact_path.exists() else []
regex_records = load_regex_records(regex_path) if regex_path.exists() else []
exact_devices = Counter(record.device for record in exact_records)
regex_devices = Counter(record.device for record in regex_records)
devices = sorted(set(exact_devices) | set(regex_devices))
code_domain_counts: dict[str, Counter[str]] = defaultdict(Counter)
for record in exact_records:
if record.target and record.dispatch_domain:
code_domain_counts[record.target][record.dispatch_domain] += 1
code_domain_mapping: dict[str, dict[str, Any]] = {}
for target, counts in code_domain_counts.items():
domain, count = counts.most_common(1)[0]
total = sum(counts.values())
code_domain_mapping[target] = {
"dispatch_domain": domain,
"count": count,
"total": total,
"confidence": round(count / total, 4) if total else 0,
"alternatives": [
{"dispatch_domain": item_domain, "count": item_count}
for item_domain, item_count in counts.most_common(5)
],
}
config_mapping = load_type_match_agent_mapping()
return {
"source_files": {
"exact": str(exact_path),
"regex": str(regex_path),
},
"counts": {
"exact_records": len(exact_records),
"regex_records": len(regex_records),
},
"devices": devices,
"device_counts": {
"exact": dict(exact_devices.most_common()),
"regex": dict(regex_devices.most_common()),
},
"code_domain_mapping": code_domain_mapping,
"configured_code_domain_mapping": config_mapping,
"historical_targets": sorted({record.target for record in exact_records + regex_records if record.target}),
"top_exact_targets": dict(Counter(record.target for record in exact_records).most_common(50)),
"top_regex_targets": dict(Counter(record.target for record in regex_records).most_common(50)),
}
def historical_targets(summary: dict[str, Any]) -> set[str]:
targets = set(summary.get("top_exact_targets", {})) | set(summary.get("top_regex_targets", {}))
targets.update(summary.get("code_domain_mapping", {}))
targets.update(summary.get("historical_targets", []))
return {normalize_target(target) for target in targets if target}
def infer_dispatch_domain(target: str, summary: dict[str, Any]) -> dict[str, Any] | None:
normalized = normalize_target(target)
historical = summary.get("code_domain_mapping", {}).get(normalized)
if historical:
return {**historical, "source": "historical"}
configured = infer_configured_dispatch_domain(normalized, "unify", summary)
if configured:
return {
"dispatch_domain": configured["dispatch_domain"],
"count": 0,
"total": 0,
"confidence": None,
"alternatives": [],
"source": "type_match_agent",
"matched_key": configured["matched_key"],
}
return None
def infer_dispatch_domain_for_device(target: str, device: str, summary: dict[str, Any]) -> dict[str, Any] | None:
"""按设备推断推荐下发 domain。
优先级:
1. `标签#设备` 这种明确配置。
2. 历史精确干预表里的全局多数映射。
3. `标签` 通用配置。
"""
normalized = normalize_target(target)
configured = infer_configured_dispatch_domain(normalized, device, summary)
if configured and "#" in configured["matched_key"]:
return {
"dispatch_domain": configured["dispatch_domain"],
"count": 0,
"total": 0,
"confidence": None,
"alternatives": [],
"source": "type_match_agent",
"matched_key": configured["matched_key"],
}
historical = summary.get("code_domain_mapping", {}).get(normalized)
if historical:
return {**historical, "source": "historical"}
if configured:
return {
"dispatch_domain": configured["dispatch_domain"],
"count": 0,
"total": 0,
"confidence": None,
"alternatives": [],
"source": "type_match_agent",
"matched_key": configured["matched_key"],
}
return None
def load_type_match_agent_mapping(path: Path = CONFIG_MAPPING_PATH) -> dict[str, str]:
if not path.exists():
return {}
data = json.loads(path.read_text(encoding="utf-8"))
return {str(key): str(value) for key, value in data.items()}
def extract_agent_tag(target: str) -> str | None:
match = AGENT_TARGET_RE.match(normalize_target(target))
if not match:
return None
return match.group(1)
def infer_configured_dispatch_domain(target: str, device: str, summary: dict[str, Any]) -> dict[str, str] | None:
tag = extract_agent_tag(target)
if not tag:
return None
mapping = summary.get("configured_code_domain_mapping", {})
normalized_device = normalize_device(device)
for key in (f"{tag}#{normalized_device}", tag):
if key in mapping:
return {"matched_key": key, "dispatch_domain": mapping[key]}
return None
def split_domain(domain: str) -> list[str]:
return [part for part in domain.split("|") if part]
def domain_compatible(actual: str, expected: str) -> bool:
"""判断实际下发 domain 是否和配置基线兼容。
下发表里有些是宽泛入口,例如 `音乐 -> contentCopilot`
历史干预里常见更细路径,例如 `contentCopilot|music`。
只要二者一方的管道 token 是另一方的子集,就认为兼容。
"""
actual_parts = set(split_domain(actual))
expected_parts = set(split_domain(expected))
if not actual_parts or not expected_parts:
return False
return actual_parts.issubset(expected_parts) or expected_parts.issubset(actual_parts)
def load_label_validator() -> Any:
spec = importlib.util.spec_from_file_location("label_master_validate_label_output", LABEL_VALIDATOR_PATH)
if spec is None or spec.loader is None:
raise RuntimeError(f"无法加载 label-master 校验器:{LABEL_VALIDATOR_PATH}")
module = importlib.util.module_from_spec(spec)
sys.modules[spec.name] = module
spec.loader.exec_module(module)
manifest_path = module.DEFAULT_OUTPUT
validator = module.LabelOutputValidator(module.load_manifest(manifest_path))
return validator
def validate_target(target: str) -> dict[str, Any]:
validator = load_label_validator()
# 历史 TSV 中多行 function/Agent 目标常以字面量 `\n` 存在,
# label-master 校验器需要真实换行才能按多行程序解析。
return validator.validate(normalize_target(target).replace("\\n", "\n"))
def escape_regex_literal(text: str) -> str:
return re.escape(text.strip())
def build_query_pattern(query: str, before_queries: list[str] | None = None) -> str:
before_queries = before_queries or []
parts: list[str] = []
for before_query in before_queries:
if before_query.strip():
parts.append(f"beforeQuery#{escape_regex_literal(before_query)}")
parts.append(f"query#{escape_regex_literal(query)}")
return "^" + "#".join(parts) + "$"
def exact_tsv_line(device: str, query: str, target: str, dispatch_domain: str) -> str:
return "\t".join([normalize_device(device), query.strip(), normalize_target(target), dispatch_domain.strip()])
def regex_tsv_line(device: str, pattern: str, target: str) -> str:
return "\t".join([normalize_device(device), pattern.strip(), normalize_target(target)])
def parse_candidate_lines(text: str) -> list[list[str]]:
rows = []
for line in text.splitlines():
if line.strip():
rows.append(next(csv.reader([line], delimiter="\t")))
return rows
def json_dumps(payload: Any) -> str:
return json.dumps(payload, ensure_ascii=False, indent=2, sort_keys=True)
@@ -0,0 +1,220 @@
#!/usr/bin/env python3
"""校验单句精确干预和正则干预候选 TSV。"""
from __future__ import annotations
import argparse
import json
import re
import sys
from pathlib import Path
from typing import Any
from intervention_common import (
DEFAULT_EXACT_SOURCE,
DEFAULT_REGEX_SOURCE,
build_source_summary,
domain_compatible,
historical_targets,
infer_configured_dispatch_domain,
infer_dispatch_domain_for_device,
load_exact_records,
load_regex_records,
normalize_device,
normalize_target,
parse_candidate_lines,
validate_target,
json_dumps,
)
def load_input_text(path: str | None) -> str:
if path:
return Path(path).read_text(encoding="utf-8")
return sys.stdin.read()
def validate_exact_rows(rows: list[list[str]], args: argparse.Namespace) -> dict[str, Any]:
summary = build_source_summary(Path(args.exact_source), Path(args.regex_source))
devices = set(summary["devices"])
known_historical_targets = historical_targets(summary)
existing: dict[tuple[str, str], list[Any]] = {}
for record in load_exact_records(Path(args.exact_source)):
existing.setdefault((record.device, record.query), []).append(record)
results = []
for index, row in enumerate(rows, start=1):
errors: list[str] = []
warnings: list[str] = []
if len(row) != 4:
errors.append(f"单句精确干预必须是 4 列,当前 {len(row)}")
results.append({"line": index, "valid": False, "errors": errors, "warnings": warnings, "row": row})
continue
device, query, target, dispatch_domain = [cell.strip() for cell in row]
device = normalize_device(device)
target = normalize_target(target)
if device not in devices:
errors.append(f"设备不在历史枚举中:{device},可选:{', '.join(sorted(devices))}")
if not query:
errors.append("query 不能为空")
if "\t" in query or "\n" in query or "\r" in query:
errors.append("query 不能包含制表符或换行")
target_result = validate_target(target)
if not target_result["valid"]:
if target in known_historical_targets:
warnings.append("target 未通过 label-master 知识库校验,但历史干预文件中已存在;建议后续补齐标签知识库")
target_result["normalized_output"] = target
else:
errors.extend([f"target 不合法:{message}" for message in target_result.get("errors", [])])
if not dispatch_domain:
errors.append("下发domain 不能为空")
inferred = infer_dispatch_domain_for_device(target, device, summary)
if inferred and dispatch_domain and dispatch_domain != inferred["dispatch_domain"]:
message = (
f"下发domain 与推断映射不一致:当前 {dispatch_domain}"
f"推断为 {inferred['dispatch_domain']},来源 {inferred.get('source', 'historical')},置信度 {inferred.get('confidence')}"
)
if args.strict_domain:
errors.append(message)
else:
warnings.append(message)
if not inferred:
warnings.append("未在历史精确干预表中找到该 target 的下发domain 映射,需要人工确认")
configured = infer_configured_dispatch_domain(target, device, summary)
if configured and dispatch_domain and not domain_compatible(dispatch_domain, configured["dispatch_domain"]):
warnings.append(
f"下发domain 与 type_match_agent 配置不兼容:当前 {dispatch_domain}"
f"配置 {configured['matched_key']} -> {configured['dispatch_domain']}"
)
if not configured:
warnings.append("type_match_agent 下发表中未找到该 Agent tag 的设备/通用映射")
conflicts = existing.get((device, query), [])
for item in conflicts:
if item.target == target and item.dispatch_domain == dispatch_domain:
warnings.append(f"历史文件已存在相同单句干预:line {item.line_no}")
else:
errors.append(
f"历史文件存在同设备同 query 的不同干预:line {item.line_no}"
f"{item.target} / {item.dispatch_domain}"
)
results.append(
{
"line": index,
"valid": not errors,
"errors": errors,
"warnings": warnings,
"normalized": {
"device": device,
"query": query,
"target": target_result.get("normalized_output", target),
"dispatch_domain": dispatch_domain,
},
"configured_dispatch_domain": configured,
}
)
return summarize_results(results)
def validate_regex_rows(rows: list[list[str]], args: argparse.Namespace) -> dict[str, Any]:
summary = build_source_summary(Path(args.exact_source), Path(args.regex_source))
devices = set(summary["devices"])
known_historical_targets = historical_targets(summary)
existing: dict[tuple[str, str], list[Any]] = {}
for record in load_regex_records(Path(args.regex_source)):
existing.setdefault((record.device, record.pattern), []).append(record)
results = []
for index, row in enumerate(rows, start=1):
errors: list[str] = []
warnings: list[str] = []
if len(row) != 3:
errors.append(f"正则干预必须是 3 列,当前 {len(row)}")
results.append({"line": index, "valid": False, "errors": errors, "warnings": warnings, "row": row})
continue
device, pattern, target = [cell.strip() for cell in row]
device = normalize_device(device)
target = normalize_target(target)
if device not in devices:
errors.append(f"设备不在历史枚举中:{device},可选:{', '.join(sorted(devices))}")
if not pattern:
errors.append("正则不能为空")
else:
try:
re.compile(pattern)
except re.error as exc:
errors.append(f"正则无法编译:{exc}")
if "query#" not in pattern:
errors.append("正则必须包含 query# 片段")
if not pattern.startswith("^") or not pattern.endswith("$"):
warnings.append("历史常见正则通常使用 ^...$ 完整锚定,建议确认是否需要锚定")
target_result = validate_target(target)
if not target_result["valid"]:
if target in known_historical_targets:
warnings.append("target 未通过 label-master 知识库校验,但历史干预文件中已存在;建议后续补齐标签知识库")
target_result["normalized_output"] = target
else:
errors.extend([f"target 不合法:{message}" for message in target_result.get("errors", [])])
conflicts = existing.get((device, pattern), [])
for item in conflicts:
if item.target == target:
warnings.append(f"历史文件已存在相同正则干预:line {item.line_no}")
else:
errors.append(
f"历史文件存在同设备同正则的不同干预:line {item.line_no}{item.target}"
)
results.append(
{
"line": index,
"valid": not errors,
"errors": errors,
"warnings": warnings,
"normalized": {
"device": device,
"pattern": pattern,
"target": target_result.get("normalized_output", target),
},
}
)
return summarize_results(results)
def summarize_results(results: list[dict[str, Any]]) -> dict[str, Any]:
return {
"valid": all(item["valid"] for item in results),
"total": len(results),
"invalid": sum(1 for item in results if not item["valid"]),
"warning_count": sum(len(item.get("warnings", [])) for item in results),
"results": results,
}
def main() -> int:
parser = argparse.ArgumentParser(description="校验干预 TSV 候选")
parser.add_argument("--mode", choices=["exact", "regex"], required=True, help="干预类型")
parser.add_argument("--input", help="候选 TSV 文件路径;不传则读取 stdin")
parser.add_argument("--exact-source", default=str(DEFAULT_EXACT_SOURCE), help="历史单句精确干预 TSV")
parser.add_argument("--regex-source", default=str(DEFAULT_REGEX_SOURCE), help="历史正则干预 TSV")
parser.add_argument("--strict-domain", action="store_true", help="下发domain 和历史多数映射不一致时直接报错")
args = parser.parse_args()
text = load_input_text(args.input)
rows = parse_candidate_lines(text)
if args.mode == "exact":
payload = validate_exact_rows(rows, args)
else:
payload = validate_regex_rows(rows, args)
print(json_dumps(payload))
return 0 if payload["valid"] else 1
if __name__ == "__main__":
raise SystemExit(main())