222 lines
12 KiB
Markdown
222 lines
12 KiB
Markdown
---
|
||
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/<env>/strategy/codeTopQuery` |
|
||
| 正则干预(codeIntervene) | `hdfs://zjyprc-hadoop/user/s_ai_service/nlp/PlanningPredict/all/<env>/strategy/codeIntervene` |
|
||
|
||
`<env>` 取值:`p4t` / `ptr` / `preview` / `production`。每个目录下文件名"形式"按日期 `yymmdd` 命名(**纯数字、无扩展名**,例:`260609`),但实际值不要求严格等于当天日期——新增一份就是上一份**按一天 +1**(要正确处理月末/年末进位),最大值仍须落在合法 `yymmdd` 范围内(最大不超过今天)。**最新文件 = 文件名尾号最大的那一个**。
|
||
|
||
## 推荐流程
|
||
|
||
0. **先确认链接到远端**(强制第一步,不通过就停下)。
|
||
- "远端"概念同 [model-iteration](../model-iteration/SKILL.md):bash 实际跑在远端 jupyter pod 工作区,`$AUTORESEARCH_CHAT_ROOT` 是后端注入的远端 chat 工作区路径(位于共享 NFS:`/mnt/<email_prefix>/autoresearch-zk-users/<chat_session_id>/`)。系统提示里的"当前会话目录"是后端宿主机本地路径,**不是**远端,不能拿来跑脚本或读历史文件。
|
||
- 起手第一条 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`
|
||
|
||
需要确认:是否写入某个文件,或是否调整设备/标签/正则。
|
||
```
|
||
|
||
如果校验失败,先解释失败原因,不要要求用户直接落盘。
|