--- 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/多轮表达 -> 干预候选 -> 合并 + 上传 HDFS”的任务。 干预数据目前有两类,**文件无扩展名**(HDFS 上文件名是 `260610` 这种纯日期),内容是真实 TAB 字符分隔的纯文本,**绝不是字面 `\t` 两字符**。下文所有 `\t` 都表示一个真实的 TAB。 - **单句精确干预(codeTopQuery)**:一行 4 列,`设备\tquery\tcode\t下发domain` 示例: ``` unify 播放音乐 Agent(tag="音乐") contentCopilot|music unify 小爱同学 Chat(type="wakeup") dialogCopilot|michat ``` - **正则干预(codeIntervene)**:一行 3 列,`设备\t正则\t标签` 示例: ``` unify ^query#播放歌曲关机又关机$ Agent(tag="音乐") unify ^beforeQuery#播放歌曲#query#关机又关机$ Agent(tag="音乐") glass ^query#这是什么$ QA() ``` 精确干预的 `code` 和正则干预的 `标签` 使用同一套 target 语法,和 `label-master` 的输出一致,但不包含复杂度判断。校验时必须复用 `skills/label-master/scripts/validate_label_output.py` 的逻辑。 ## 关键约定 - `unify` 表示全部设备。 - 设备必须优先从下载下来的最新干预文件中归纳,不要随手发明设备名。 - **单句精确干预的 `下发domain` 必须由人拍板**:脚本基于最新干预文件按 `code → 下发domain` 频次给出 top-N 候选 + 计数,agent 列出来让用户从用户给的 tag 信息出发选一个,**不要默认用多数派直接落盘**。 - 正则干预支持多轮。多轮正则使用 `beforeQuery#上一轮#query#当前轮` 这种片段表达,最终通常包在 `^...$` 中。 - 默认只生成候选行 + 校验结果,不直接追加到历史干预文件。上传 HDFS 必须等用户明确确认。 - 所有产物优先写到当前会话的 `$AUTORESEARCH_CHAT_ROOT/output/` 或 `scratchpad/` 下,不要写到项目根目录的 `output/`。 ## HDFS 干预文件路径 线上干预的权威源在 HDFS。两类文件 × 四个环境(p4t = 测试,ptr = 灰度,preview = 预发,production = 生产): | 类型 | HDFS 目录 | |---|---| | 单句精确干预(codeTopQuery) | `hdfs://zjyprc-hadoop/user/s_ai_service/nlp/PlanningPredict/all//strategy/codeTopQuery` | | 正则干预(codeIntervene) | `hdfs://zjyprc-hadoop/user/s_ai_service/nlp/PlanningPredict/all//strategy/codeIntervene` | `` 取值:`p4t` / `ptr` / `preview` / `production`。每个目录下文件名"形式"按日期 `yymmdd` 命名(**纯数字、无扩展名**,例:`260609`),但实际值不要求严格等于当天日期——新增一份就是上一份**按一天 +1**(要正确处理月末/年末进位),最大值仍须落在合法 `yymmdd` 范围内(最大不超过今天)。**最新文件 = 文件名尾号最大的那一个**。 ## 推荐流程 0. **先确认链接到远端**(强制第一步,不通过就停下)。 - "远端"概念同 [model-iteration](../model-iteration/SKILL.md):bash 实际跑在远端 jupyter pod 工作区,`$AUTORESEARCH_CHAT_ROOT` 是后端注入的远端 chat 工作区路径(位于共享 NFS:`/mnt//autoresearch-zk-users//`)。系统提示里的"当前会话目录"是后端宿主机本地路径,**不是**远端,不能拿来跑脚本或读历史文件。 - 起手第一条 bash 必须是预检(同时验远端 + HDFS 工具): ```bash pwd && echo "AUTORESEARCH_CHAT_ROOT=$AUTORESEARCH_CHAT_ROOT" && uname -a && which hls hget hput ``` - 通过条件:`pwd` 返回远端绝对路径(一般 `/root/zk_agent_workspaces/LOCALID_xxx` 或类似 Linux 路径)、`$AUTORESEARCH_CHAT_ROOT` 非空、`hls/hget/hput` 都能找到。 - 不通过的情形(任一命中就停下问用户): - `$AUTORESEARCH_CHAT_ROOT` 为空 → 后端没注入,会话没绑定 chat。 - `pwd` 是 Windows 路径(如 `D:\...`、`/d/...`)或后端本地路径(如 `/home/mi/zk-data-agent-wsh/...`)→ 当前在本地,不是远端。 - `hls/hget/hput` 找不到 → 远端环境没装 HDFS 客户端,下一步拉文件直接卡死。 - bash 报命令找不到 / 没权限 → 远端 pod 没起来。 1. **获取最新的干预文件**(强制第二步,所有后续校验和上传都基于这一步拉下来的文件)。 - 先确认 (a) 干预类型:精确干预 → `codeTopQuery`,正则干预 → `codeIntervene`;(b) 目标环境(`p4t/ptr/preview/production`,用户没说就问,建议先在 `p4t` 验证)。 - 用 `hls` 列目录,按文件名尾号取最大的那个作为最新文件,然后用 `hget` 拉下来: ```bash ENV=p4t # 或 ptr / preview / production TYPE=codeTopQuery # 或 codeIntervene HDFS_DIR=hdfs://zjyprc-hadoop/user/s_ai_service/nlp/PlanningPredict/all/$ENV/strategy/$TYPE hls "$HDFS_DIR" LATEST=$(hls "$HDFS_DIR" | awk '{print $NF}' | grep -v '^$' | sort -V | tail -1) echo "LATEST=$LATEST" WORK="$AUTORESEARCH_CHAT_ROOT/intervention/${ENV}_${TYPE}" mkdir -p "$WORK" # base 不带扩展名(HDFS 上原文件名就是日期),保持原貌 hget "$LATEST" "$WORK/base" wc -l "$WORK/base" # 抽 1-2 行肉眼校验是不是真 TAB 分隔(cat -A 把 TAB 显成 ^I) head -2 "$WORK/base" | cat -A ``` - **设备枚举、`code → 下发domain` 频次、重复/冲突检查全部基于这份刚拉下来的 `base`**,不要再用本地历史快照(旧快照可能已过时)。文件是 TAB 分隔纯文本,无扩展名,不要当 csv/json 处理。 2. 先确认用户要哪种干预方式(如果第 1 步还没定就在这里定,确定后回到第 1 步拉对应文件): - 单句精确干预:query 必须完全命中。 - 正则干预:适合一类表达、多轮上下文或需要泛化的高频 case。 3. 确认设备。用户没说设备时,必须问设备;可以提示 `unify` 表示全部设备。设备枚举从 base.tsv 第一列归纳。 4. 确认目标标签/code。它必须是完整 target,例如 `Agent(tag="地图导航")`、`QA()`、`Chat()` 或合法 function 调用。 5. 如果是单句精确干预: - 确认 `query`。 - **下发domain 必须人选**:基于 base 文件统计该 `code`(target)历史上配过的 `下发domain` 频次,给出 top-N 候选 + 计数,让用户结合自己提的 tag 信息选一个。例: ```bash # 用 awk 抽该 target 的下发domain 分布 awk -F'\t' -v t='Agent(tag="音乐")' '$3==t {print $4}' "$WORK/base" \ | sort | uniq -c | sort -rn | head -5 ``` 把结果(如 `12345 contentCopilot|music / 87 contentCopilot|audio_book / ...`)作为候选给用户,**不要默认取第一个直接落盘**。 6. 如果是正则干预: - 确认当前轮 query。 - 如果有多轮,确认前序 query 顺序。 - 用户给了原始正则就保留;没给时由脚本生成 `^query#...$` 或 `^beforeQuery#...#query#...$`。 7. 调用 `scripts/generate_intervention_candidate.py` 生成候选 TSV 和校验报告(产物写到 `$AUTORESEARCH_CHAT_ROOT/output/` 或 `scratchpad/`)。 8. **合并 + diff 给人看**。把候选行追加进 `base` 副本,跑 unified diff: ```bash cp "$WORK/base" "$WORK/merged" cat candidate >> "$WORK/merged" # candidate 是上一步生成的候选行文件,纯 TAB 分隔 diff -u "$WORK/base" "$WORK/merged" | tee "$WORK/diff.patch" ``` 9. 展示给用户:干预类型、设备、候选行(原文 TAB 分隔)、校验结果、diff 摘要、目标 HDFS 路径。**用户没明确说"上传 / 推到 X 环境 / hput"前不要执行 hput**——这是写远端 + 影响线上的不可逆动作,必须等确认。 10. **用户确认后 hput 上传**。新文件名规则:**取 `LATEST` 的尾号「按一天」+1**——必须正确处理月末 / 年末进位(260131 +1 = 260201,261231 +1 = 270101,260228 在闰年 +1 = 260229、平年 +1 = 260301),**不是**数值 `+1`(那样 260131+1=260132 直接失效)。用 `date` 做日期算术: ```bash OLD_NUM=$(basename "$LATEST") # 例:260609 或 260131 NEW_NUM=$(date -d "20${OLD_NUM} + 1 day" +%y%m%d) TODAY=$(date +%y%m%d) # 校验:date 解析失败(OLD_NUM 本身就不合法)会直接报错;新尾号也必须是合法 yymmdd if ! [[ "$NEW_NUM" =~ ^[0-9]{2}(0[1-9]|1[0-2])(0[1-9]|[12][0-9]|3[01])$ ]]; then echo "ERR: $NEW_NUM 不符合 yymmdd 形式,停下问用户"; exit 1 fi if [ "$NEW_NUM" -gt "$TODAY" ]; then echo "ERR: $NEW_NUM=$NEW_NUM 超过今天 $TODAY(说明上一份就是今天的,已经发过一版),停下问用户"; exit 1 fi hput "$WORK/merged" "$HDFS_DIR/$NEW_NUM" hls "$HDFS_DIR" | tail -3 # 校验新文件已落、尾号最大 ``` 说明:HDFS 上文件名"形式"按日期来,实际值是「上一份按一天 +1」而不是严格当天日期;只要 +1 后仍是合法 yymmdd 且不超过今天就行。如果上一份已经是今天(OLD_NUM == TODAY),`date +1 day` 会算出明天 → 命中"超过今天"分支,停下问用户怎么处理(通常是当天已经发过一版,要不要并入现有版本,或推迟到明天再发)。 11. 展示最终结果:上传后的 HDFS 路径 + 新文件尾号。 ## 脚本能力 ```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` 需要确认:是否写入某个文件,或是否调整设备/标签/正则。 ``` 如果校验失败,先解释失败原因,不要要求用户直接落盘。