Unify skills under repository root
This commit is contained in:
@@ -0,0 +1,20 @@
|
||||
# Local override only — committed profiles.json holds shared team creds
|
||||
.env
|
||||
.env.*
|
||||
|
||||
# Ad-hoc probes / local experiments
|
||||
_probe_*.py
|
||||
scratch_*.py
|
||||
|
||||
# Python
|
||||
__pycache__/
|
||||
*.py[cod]
|
||||
*.egg-info/
|
||||
.venv/
|
||||
venv/
|
||||
|
||||
# IDE / OS
|
||||
.idea/
|
||||
.vscode/
|
||||
.DS_Store
|
||||
Thumbs.db
|
||||
@@ -0,0 +1,140 @@
|
||||
# ELK 索引目录(schema reference)
|
||||
|
||||
> 每张索引一节:**集群 / 账号 / 关键字段 / JSON 字符串字段的内部结构**。纯 schema 参考。
|
||||
>
|
||||
> 业务侧"为什么这么查 / 怎么排查问题"在 [`business/`](business/) 目录:
|
||||
>
|
||||
> - [`business/keyword-free.md`](business/keyword-free.md) — 免唤醒判决(拒识 / kwfree 两张表的视角差异)
|
||||
> - [`business/intent-arbitrator.md`](business/intent-arbitrator.md) — 中控仲裁
|
||||
> - [`business/micar-onetrack.md`](business/micar-onetrack.md) — 小米汽车 OneTrack 端侧埋点(端到端可用性 / 全离线日志 / 离线NLP结果)
|
||||
|
||||
## 集群 & 账号
|
||||
|
||||
⚠️ **不止一个 ES 集群**。每个 profile 自带 `host` 字段指向所在集群。账号密码(团队共用)已 commit 在仓库的 `profiles.json` 里,clone 即可用。
|
||||
|
||||
| Profile Key | ES 集群 | 账号 | 对应 Kibana 前端 |
|
||||
| ----------- | ------ | --- | ---------------- |
|
||||
| `default` | `akaiservice.api.es.srv:80` | `ai_service_kibana` | `aiservice.ak.kibana.cloud.mioffice.cn` / `akelk.pt.ai.srv` |
|
||||
| `reject` | `akaiservice.api.es.srv:80` | `ai_service_kibana` | `akaiservice.kibana.pt.xiaomi.com` |
|
||||
| `micar` | `c3log.api.es.srv:80` | `xiaoai_micar_kibana` | `c3log.kibana.pt.xiaomi.com` |
|
||||
|
||||
`default` / `reject` 共用一个集群和账号;`micar` 是**完全独立的 c3log 集群**,跨集群账号互相不通(403)。
|
||||
|
||||
## 索引清单
|
||||
|
||||
| preset | 索引 pattern | profile | request id 字段 | 时间字段 | 内置过滤 | 主要业务 |
|
||||
| ------ | ----------- | ------- | -------------- | ------- | ------- | -------- |
|
||||
| `main` | `arch-flat-nlp-log-f-*` | default | `request_id` | `timestamp` | — | 主 NLP 日志 / 中控仲裁 |
|
||||
| `reject` | `aiservice_duplex_rejection_lcs_log*` | reject | `requestId` | `time` | — | 后置拒识 |
|
||||
| `kwfree` | `nlp_post_processing_lcs-*` | default | `requestId` | `timestamp` | `moduleName=keyword-free-log` | 后处理免唤醒 |
|
||||
| `micar` | `onetrack_xiaoai_micar*` | micar | `request_id` | `timestamp` | — | 小米汽车 OneTrack 端侧埋点 |
|
||||
|
||||
⚠️ **字段命名各表不统一**:snake (`request_id`) vs camel (`requestId`);时间字段 `timestamp` vs `time`。Kibana 显示的字段名不一定等于 ES 字段名。
|
||||
|
||||
---
|
||||
|
||||
## `arch-flat-nlp-log-f-*`(preset=`main`)
|
||||
|
||||
主 NLP 服务端日志,每次请求一条。
|
||||
|
||||
**顶层字段**(节选):
|
||||
`request_id`, `query`, `query_origin`, `domain`, `func`, `intention`, `code`, `latency`,
|
||||
`user_agent`, `session_id`, `app_id`, `app_name`, `device_id`, `mask_did`,
|
||||
`arbitrator_dialog_status`, `offline_arbitrate_domains`,
|
||||
`is_llm`, `is_llm_classify_enable`, `is_llm_classify_success`,
|
||||
`large_model_info`, `large_model_traceid`,
|
||||
`llm_intent`, `llm_intent_model_name`, `llm_intent_model_version`, `llm_content`, `llm_knowledge`, `llm_system_prompt`, `llm_history_size`, `llm_reject_intent`, `llm_access_control`, `llm_description`,
|
||||
`is_continuous_dialog`, `exit_continuous_dialog`, `is_multi_turn`, `is_multi_rewrite`, `multi_rewrite_method`,
|
||||
`reject_type`, `rejection_hint`, `rejection_info`, `is_shumei_reject`, `is_filtered`, `is_user_cancelled`,
|
||||
`requestInfo`, `responseInfo`, `text`, `display_text`, `to_read`, `to_speak`, `tts_speaker`, `tts_vendor`,
|
||||
`car_type`, `car_config`, `car_category`, `vehicle_driving_status`, `vehicle_wakeup_zone`, `is_real_vehicle`, `is_bench_vehicle`, `is_internal`
|
||||
|
||||
**关键嵌套对象**:
|
||||
|
||||
- `intention` (dict) — 意图判决总集,含:
|
||||
- `intention.intent_arbitrator_info` ⭐ — **中控仲裁对象**(顶层同名字段通常 null,**真东西在这里**),子结构详见 [`business/intent-arbitrator.md`](business/intent-arbitrator.md)
|
||||
- `intention.score` / `intention.func` / `intention.domain` / `intention.query`
|
||||
- `intention.dialog_status`, `intention.rewrite_infos`, `intention.domain_judge`, `intention.provider_domains`
|
||||
|
||||
---
|
||||
|
||||
## `aiservice_duplex_rejection_lcs_log*`(preset=`reject`)
|
||||
|
||||
后置拒识模块日志。**同一 requestId 通常多条(多轮)**,按 `time` 倒序取最新。
|
||||
|
||||
**顶层字段**:
|
||||
`requestId`, `time`, `query`, `appId`, `deviceId`, `sessionId`, `env`,
|
||||
`isDuplex`, `isExit`, `isWakeup`, `costInfo`, `exitInfo`, `instructionPriority`,
|
||||
`rejectHint`, `rejectInfo`, `rejectType`, `requestInfo`, `strategy_info`
|
||||
|
||||
**JSON 字符串字段**(脚本自动深度解析):
|
||||
- `rejectInfo` — 完整拒识判决,关键路径:
|
||||
- `rejectInfo.reject_reason` / `rejectInfo.rejection_info` / `rejectInfo.debug_info`
|
||||
- `rejectInfo.strategy_info.reject.<策略名>` — 各子策略
|
||||
- 常用子策略 `KEY_WORD_FREE_RESTRIC`(免唤醒):`process` / `skipped_reason` / `keywordFreeType` / `isCodeMatch` / `getProcessedQuery` / `Function List` —— 业务深入见 [`business/keyword-free.md`](business/keyword-free.md)
|
||||
|
||||
---
|
||||
|
||||
## `nlp_post_processing_lcs-*`(preset=`kwfree`)
|
||||
|
||||
后处理通用日志表。`kwfree` preset 内置 `moduleName=keyword-free-log` 过滤,锁定到免唤醒模块那条。
|
||||
|
||||
**顶层字段**:
|
||||
`requestId`, `timestamp`, `moduleName`, `appId`, `env`, `machine`, `maskUid`, `message`
|
||||
|
||||
**JSON 字符串字段**(脚本自动深度解析):
|
||||
|
||||
- `message` — 免唤醒模块本次处理的入参出参,关键路径:
|
||||
- `message.process` / `message.skipReason`
|
||||
- `message.route.flow` / `route.enableFullMigration` / `route.clawEnabled`
|
||||
- `message.request.query` / `keywordFreeType` / `zone` / `appId` / `maskUid` / `maskDeviceId`
|
||||
- `message.fallback.oldRejectType` / `fallback.oldDomain`
|
||||
- 业务深入见 [`business/keyword-free.md`](business/keyword-free.md)
|
||||
|
||||
---
|
||||
|
||||
## `onetrack_xiaoai_micar*`(preset=`micar`)
|
||||
|
||||
小米汽车小爱端侧 OneTrack 埋点。**c3log 独立集群**。按日 rolling,单日 ~300GB——**强烈建议 `--date YYYYMMDD`**。
|
||||
|
||||
⚠️ **同一 request_id 多条记录**,按 `tip` 区分业务点位。**不同 tip 的负载 schema 不统一**。
|
||||
|
||||
**顶层公共字段**:
|
||||
`request_id`, `tip`, `tip_id`, `tip_name`, `tip_module_name`, `tip_page_id`, `tip_pos`,
|
||||
`event_name` (`execute` / `state` / `start`),
|
||||
`device_id`, `vid`, `instance_id`, `app_id`, `app_package_name`, `pkg`,
|
||||
`path_id`, `clientTime`, `serverTime`, `timestamp`,
|
||||
`network`, `region`, `mfrs`, `model`, `car_type`, `car_config`,
|
||||
`voice_position` (`driver` / `passenger`), `wakeup_origin`,
|
||||
`srv_env`, `app_ver`, `micar_ver`, `os_ver`,
|
||||
`is_internal`, `is_bench_vehicle`, `sender`, `distinct_id`, `plugin_id`
|
||||
|
||||
**主要 tip 与负载位置**:
|
||||
|
||||
| tip | tip_name | 负载在 |
|
||||
| --- | -------- | ------ |
|
||||
| `1024.1.1.1.23177` | 小爱端到端可用性 | 顶层 `key_value`(性能时序) |
|
||||
| `1024.1.2.1.23176` | 小爱全离线日志 | 顶层 `request_infos[]` / `response_infos[]` / `other_infos`(**不在 `key_value`**) |
|
||||
| `1024.1.2.1.34496` | 离线NLP结果 | `key_value.instructions` |
|
||||
| `1024.4.1.1.25631` | ASR识别状态 | `key_value` 为 null,看 `event_name` / `other_infos` |
|
||||
| `1024.1.1.1.33610` | 第三方接口调用 | — |
|
||||
| `1024.1.3.1.23520` / `23521` | 小爱指令开始 / 结束 | — |
|
||||
|
||||
**JSON 字符串字段**(脚本自动深度解析):
|
||||
|
||||
- `key_value`(大多数 tip)
|
||||
- `request_infos[]` / `response_infos[]` / `other_infos[]`(23176)
|
||||
|
||||
各 tip 字段语义、模型相关字段(`nlp_debug_info` / `Arbitrate` / `nlp_model_version` 等)业务深入见 [`business/micar-onetrack.md`](business/micar-onetrack.md)。
|
||||
|
||||
---
|
||||
|
||||
## 新增索引流程
|
||||
|
||||
1. 从 Kibana URL 拿:Kibana index UUID、KQL 字段、filter 里的 `match_phrase` 条件
|
||||
2. 必要时跑临时 `_probe_*.py`(命名已 gitignore):用 `match_phrase` 条件 + `*` 索引跨库搜,从命中文档的 `_index` 反推真实索引名
|
||||
3. 确认 **request id 字段名**(`request_id` / `requestId`)和**时间字段**(`timestamp` / `time`)
|
||||
4. 如 `AuthorizationException(403)`,换 profile
|
||||
5. 字段为 JSON 字符串时(如 `rejectInfo` / `message` / `key_value`)脚本已自动深度解析,把业务路径记到本文档对应索引一节
|
||||
6. 在 `elk_query.py` 的 `PRESETS` 加一条,`extra_filters` 写清楚(如 `moduleName=...`)
|
||||
7. 如果是新业务主题,在 `business/` 下加一份业务深入文档
|
||||
@@ -0,0 +1,37 @@
|
||||
# elk-fetch
|
||||
|
||||
按 request id 查 ELK / Elasticsearch 日志的小工具 + Claude Code Skill。
|
||||
|
||||
**核心能力**
|
||||
- 绕开 Kibana CAS 登录,直接走 ES HTTP API
|
||||
- 多账号 profile(不同业务用不同 ES 账号,支持多 ES 集群)
|
||||
- 多业务 preset(业务语义 → 索引/字段/过滤条件,一键套用)
|
||||
- 深度 JSON 字段自动解析(如 `rejectInfo` / `message` / `key_value` / `response_infos` 里的嵌套 JSON)
|
||||
- 按点号路径(`a.b.0.c`)提取嵌套字段 / 仅输出指定字段
|
||||
|
||||
## 快速开始
|
||||
|
||||
```bash
|
||||
git clone git@git.n.xiaomi.com:zhongsiyao/elk-fetch.git
|
||||
cd elk-fetch
|
||||
|
||||
pip install "elasticsearch<8" urllib3
|
||||
|
||||
# 默认查主 NLP 日志
|
||||
python elk_query.py <your_request_id>
|
||||
```
|
||||
|
||||
账号密码(团队共用)已经在仓库里的 `profiles.json` 中,clone 完即可用。想用自己的账号则 `ELK_FETCH_PROFILES=/path/to/profiles.json` 指向别处。
|
||||
|
||||
## 完整用法
|
||||
|
||||
- **怎么选 preset / Kibana URL 转参数 / 命令模板** → [`SKILL.md`](SKILL.md)
|
||||
- **每张表的字段 schema / JSON 字符串内部结构 / 新增索引流程** → [`INDEX_CATALOG.md`](INDEX_CATALOG.md)
|
||||
- **业务深入**(每个主题一份排查 playbook)→ [`business/`](business/):
|
||||
- [`business/keyword-free.md`](business/keyword-free.md) — 免唤醒判决(拒识 / kwfree 两张表三个视角)
|
||||
- [`business/intent-arbitrator.md`](business/intent-arbitrator.md) — 中控仲裁
|
||||
- [`business/micar-onetrack.md`](business/micar-onetrack.md) — 小米汽车 OneTrack 端侧埋点
|
||||
|
||||
## 作为 Claude Code Skill 使用
|
||||
|
||||
把本仓库 clone / 软链到 `~/.claude/skills/elk-fetch/`,Claude Code 会根据 `SKILL.md` 的触发条件(给出 Kibana 链接、提到 requestId / 某类业务日志等)自动调用。
|
||||
@@ -0,0 +1,200 @@
|
||||
---
|
||||
name: elk-fetch
|
||||
description: 从小米内网 ELK 按 request id 拉取日志。支持多账号 profile、多 ES 集群、多业务 preset(主 NLP / 拒识表 / 免唤醒后处理日志 / 小米汽车 OneTrack 端侧埋点)、深度 JSON 字段自动解析。绕开 Kibana CAS 登录,直接走 ES HTTP API。触发条件:用户给出 Kibana 链接、提到 requestId / request_id 查日志、想看某次请求的 NLP 日志 / 中控仲裁日志(intent_arbitrator_info / llm_agent_info / hit_rules / score_domains)/ 拒识 rejectInfo / KEY_WORD_FREE_RESTRIC / 免唤醒判决(keyword-free-log)/ 小爱端到端可用性 / 小爱全离线日志 / OneTrack 埋点 / micar tip。
|
||||
when_to_use: 用户给出 Kibana 链接、request_id/requestId,或要求查询 NLP 日志、中控仲裁日志、拒识日志、免唤醒日志、小米汽车 OneTrack 埋点日志时使用。
|
||||
aliases: elk, elk-query, log-fetch, request-log
|
||||
allowed_tools: python_exec, python_package, read_file
|
||||
---
|
||||
|
||||
# ELK Fetch Skill
|
||||
|
||||
按 request id 查小米内网 ELK。**多个 ES 集群**:`default`/`reject` 走 `akaiservice.api.es.srv:80`,`micar` 走独立的 `c3log.api.es.srv:80`。每个 profile 自带 `host`,脚本会自动选集群。
|
||||
|
||||
账号密码(团队共用)已 commit 进仓库的 `profiles.json`,无需配置。需要换账号时用 `ELK_FETCH_PROFILES=/abs/path/to/profiles.json` 指向自己的文件。
|
||||
|
||||
## 在本数据 Agent 中使用
|
||||
|
||||
本 skill 已安装在项目目录 `skills/elk-fetch/`。在本项目中不要用 `bash` 执行 `python elk_query.py`,也不要用 `pip` 直接安装依赖。
|
||||
|
||||
实际查询必须使用 `python_exec`:
|
||||
|
||||
```json
|
||||
{
|
||||
"script_path": "skills/elk-fetch/elk_query.py",
|
||||
"args": ["<request_id>", "--preset", "main"],
|
||||
"timeout_seconds": 90,
|
||||
"max_output_chars": 20000
|
||||
}
|
||||
```
|
||||
|
||||
如果 `python_exec` 返回缺少 `elasticsearch` 或 `urllib3`,先使用 `python_package` 安装:
|
||||
|
||||
```json
|
||||
{
|
||||
"action": "install",
|
||||
"packages": ["elasticsearch<8", "urllib3"],
|
||||
"timeout_seconds": 120
|
||||
}
|
||||
```
|
||||
|
||||
下面所有 `python elk_query.py ...` 命令模板只表示参数选择;实际执行时都要转换为 `python_exec.script_path = "skills/elk-fetch/elk_query.py"` 和对应的 `args`。
|
||||
|
||||
## 关键文件
|
||||
|
||||
| 文件 | 用途 |
|
||||
|------|------|
|
||||
| `elk_query.py` | CLI 入口 |
|
||||
| `INDEX_CATALOG.md` | **每张表的字段 schema reference**(顶层字段 / JSON 字符串字段内部结构) |
|
||||
| `business/keyword-free.md` | 免唤醒判决 — 拒识 vs kwfree 两张表三个视角的区分 + 排查 playbook |
|
||||
| `business/intent-arbitrator.md` | 中控仲裁 — `intention.intent_arbitrator_info` 子字段语义 |
|
||||
| `business/micar-onetrack.md` | 小米汽车 OneTrack — 23177 端到端可用性 / 23176 全离线日志 / 34496 离线NLP结果 |
|
||||
|
||||
## Preset 速查
|
||||
|
||||
| preset | profile | 集群 | 索引 pattern | 字段名 | 时间字段 | 内置过滤 |
|
||||
|--------|---------|------|-------------|--------|---------|---------|
|
||||
| `main` (默认) | default | akaiservice | `arch-flat-nlp-log-f-*` | `request_id` | `timestamp` | 无 |
|
||||
| `reject` | reject | akaiservice | `aiservice_duplex_rejection_lcs_log*` | **`requestId`** | **`time`** | 无 |
|
||||
| `kwfree` | default | akaiservice | `nlp_post_processing_lcs-*` | `requestId` | `timestamp` | `moduleName = keyword-free-log` |
|
||||
| `micar` | micar | **c3log** | `onetrack_xiaoai_micar*` | `request_id` | `timestamp` | 无(一个 rid 多条 tip) |
|
||||
|
||||
## 选 preset 的决策表
|
||||
|
||||
| 用户想看 | preset | 额外参数 | 业务深入 |
|
||||
|---------|--------|---------|---------|
|
||||
| query / domain / func / 设备信息 | `main` | — | — |
|
||||
| **中控仲裁日志**(agent 路由 / score_domains / llm_agent_info / hit_rules) | `main` | `--json-path intention.intent_arbitrator_info` | [`business/intent-arbitrator.md`](business/intent-arbitrator.md) |
|
||||
| **拒识表整体**(rejectInfo 全部 / reject_reason / 各子策略) | `reject` | 不加 `--json-path` | [`business/keyword-free.md`](business/keyword-free.md) |
|
||||
| **拒识里的免唤醒**(后置拒识调到免唤醒那次的出入参) | `reject` | `--json-path rejectInfo.strategy_info.reject.KEY_WORD_FREE_RESTRIC` | [`business/keyword-free.md`](business/keyword-free.md) |
|
||||
| **后处理的免唤醒**(免唤醒模块被后处理调用时自己记的日志) | `kwfree` | — | [`business/keyword-free.md`](business/keyword-free.md) |
|
||||
| **小米汽车端侧埋点**(端到端可用性 / 全离线日志 / ASR 状态等 OneTrack tip) | `micar` | **`--date YYYYMMDD` 强烈建议** | [`business/micar-onetrack.md`](business/micar-onetrack.md) |
|
||||
|
||||
## 从 Kibana 链接提取参数
|
||||
|
||||
Kibana URL 形如:
|
||||
|
||||
```text
|
||||
...discover#/?...
|
||||
filters:(...match_phrase:(moduleName:keyword-free-log))
|
||||
index:d7ff4d65-... ← Kibana index pattern UUID(非 ES 索引名)
|
||||
query:(language:kuery,query:'requestId : 67f41dff...')
|
||||
time:(from:now-1d,to:now)
|
||||
```
|
||||
|
||||
反推方法:
|
||||
|
||||
- `requestId : xxx` / `request_id : xxx` → request id
|
||||
- `filters` 里的 `match_phrase:(moduleName:keyword-free-log)` → **`kwfree` preset**
|
||||
- 主机 `aiservice.ak.kibana.cloud.mioffice.cn` → `default` profile;主机 `akaiservice.kibana.pt.xiaomi.com` → `reject` profile;主机 `c3log.kibana.pt.xiaomi.com` → **`micar` profile(独立集群)**;主机 `akelk.pt.ai.srv` → `default` profile + `main` preset(中控仲裁的 Kibana 前端,底层就是主表)
|
||||
- index 名为 `onetrack_xiaoai_micar` 直接走 `micar` preset;filter 里有 `tip` / `tip_name` 也走 `micar`
|
||||
- `time:(from:now-1d,...)` → 默认近 48 小时,`from:'2026-04-22...'` 这种具体日期 → 传 `--date YYYYMMDD`
|
||||
|
||||
Kibana index UUID 不能直接反查 ES 索引名;如遇未知 UUID 跑一次 preset 试,打不中再 probe。
|
||||
|
||||
## 命令模板
|
||||
|
||||
> 路径形如 `python "C:\Users\Sivan\.claude\skills\elk-fetch\elk_query.py" ...`,下方为简写。每个场景的字段语义和排查思路见 `business/` 对应文档。
|
||||
|
||||
### 主 NLP 日志(默认)
|
||||
|
||||
```bash
|
||||
python elk_query.py <request_id>
|
||||
```
|
||||
|
||||
### 中控仲裁日志(→ [`business/intent-arbitrator.md`](business/intent-arbitrator.md))
|
||||
|
||||
```bash
|
||||
# 整个仲裁对象
|
||||
python elk_query.py <rid> --json-path intention.intent_arbitrator_info
|
||||
|
||||
# 命中的 agent + 召回规则 + 仲裁等级
|
||||
python elk_query.py <rid> \
|
||||
--fields request_id,query,domain,intention.intent_arbitrator_info.llm_agent_info,intention.intent_arbitrator_info.hit_rules,intention.intent_arbitrator_info.arbitrator_level
|
||||
|
||||
# 各候选 domain 的仲裁打分
|
||||
python elk_query.py <rid> --json-path intention.intent_arbitrator_info.score_domains
|
||||
```
|
||||
|
||||
⚠️ 真东西在 `intention.intent_arbitrator_info`,**顶层同名字段通常 null**。
|
||||
|
||||
### 拒识表 / `KEY_WORD_FREE_RESTRIC`(→ [`business/keyword-free.md`](business/keyword-free.md))
|
||||
|
||||
```bash
|
||||
# 整个拒识 rejectInfo
|
||||
python elk_query.py <rid> --preset reject
|
||||
|
||||
# 拒识里的免唤醒子字段
|
||||
python elk_query.py <rid> --preset reject \
|
||||
--json-path rejectInfo.strategy_info.reject.KEY_WORD_FREE_RESTRIC
|
||||
```
|
||||
|
||||
同一 requestId 在此索引常有多条(多轮),按 `time` 倒序返回。
|
||||
|
||||
### 后处理免唤醒(→ [`business/keyword-free.md`](business/keyword-free.md))
|
||||
|
||||
```bash
|
||||
# 整个 message
|
||||
python elk_query.py <rid> --preset kwfree --json-path message
|
||||
|
||||
# 是否实际处理 + 跳过原因
|
||||
python elk_query.py <rid> --preset kwfree \
|
||||
--fields requestId,message.process,message.skipReason,message.request.keywordFreeType
|
||||
```
|
||||
|
||||
### 小米汽车 OneTrack(→ [`business/micar-onetrack.md`](business/micar-onetrack.md))
|
||||
|
||||
⚠️ 必须传 `--date YYYYMMDD`(按日 rolling,单日 ~300GB)。
|
||||
|
||||
```bash
|
||||
# 这个 request 上报了哪些 tip
|
||||
python elk_query.py <rid> --preset micar --date 20260424 \
|
||||
--fields request_id,tip,tip_name,tip_module_name,event_name
|
||||
|
||||
# 端到端可用性(23177)的性能时序
|
||||
python elk_query.py <rid> --preset micar --date 20260424 \
|
||||
--fields request_id,tip,key_value.state_result_type,key_value.state_exec_result,key_value.wakeup_received_event,key_value.asr_final,key_value.nlp_finish_answer
|
||||
|
||||
# 全离线日志(23176)的模型 debug 输出
|
||||
python elk_query.py <rid> --preset micar --date 20260424 \
|
||||
--json-path response_infos.0.nlp_debug_info
|
||||
```
|
||||
|
||||
⚠️ 23176 schema 与其他 tip 不同:负载在 `request_infos[]` / `response_infos[]`,**不在 `key_value`**。`response_infos[0]` 含 `instructions` + `nlp_debug_info` 两块,研究模型行为时都要看,别只取 nlp_debug_info。
|
||||
|
||||
### 指定日期 / 自定义索引
|
||||
|
||||
```bash
|
||||
# 老请求超出 48h 窗口
|
||||
python elk_query.py <rid> --date 20260422
|
||||
|
||||
# probe 未知索引
|
||||
python elk_query.py <rid> \
|
||||
--index "some-other-*" --field requestId --time-field time --profile reject
|
||||
```
|
||||
|
||||
## 输出格式
|
||||
|
||||
始终单个 JSON 对象到 stdout:
|
||||
|
||||
```json
|
||||
{
|
||||
"request_id": "...",
|
||||
"preset": "reject",
|
||||
"profile": "reject",
|
||||
"index": "aiservice_duplex_rejection_lcs_log*",
|
||||
"field": "requestId",
|
||||
"time_field": "time",
|
||||
"date": "past-48h",
|
||||
"total": 2,
|
||||
"hits": [ { "...": "_source(嵌套 JSON 字符串已自动解开)" } ]
|
||||
}
|
||||
```
|
||||
|
||||
出错时 JSON 里会有 `error` 键,退出码非零。
|
||||
|
||||
## 常见坑
|
||||
|
||||
- **请求 id 过期**:默认查近 48h,老请求必须 `--date YYYYMMDD`。
|
||||
- **字段名驼峰 vs 下划线**:`main` / `micar` 是 `request_id`,`reject` / `kwfree` 是 `requestId`;Kibana 界面显示的字段名不一定等于 ES 存储字段名。
|
||||
- **返回大量字段刷屏**:用 `--fields` 或 `--json-path` 精简。
|
||||
- **不要用 WebFetch 抓 Kibana URL**:会被 CAS 拦到登录页,永远走这个 skill。
|
||||
@@ -0,0 +1,60 @@
|
||||
# 中控仲裁日志
|
||||
|
||||
> "中控"= central intent arbitrator,决定一条 query 路由到哪个 domain/agent(`controlCopilot` / `iotCopilot` / `productAgent` / 闲聊 / ...)。
|
||||
|
||||
## 在哪查
|
||||
|
||||
主 NLP 表 `arch-flat-nlp-log-f-*` 里的 **`intention.intent_arbitrator_info`** 嵌套对象。`main` preset 直接能读,**不需要新 preset**。
|
||||
|
||||
⚠️ **坑**:顶层 `intent_arbitrator_info`(不带 `intention.`)通常是 `null`——同名但不同位置,别取错。
|
||||
|
||||
> Kibana URL 形如 `akelk.pt.ai.srv/app/discover#/?...index:b0bd62cb-...&query:(...request_id:...)`,这个 Kibana 前端虽然走 akelk 域名,底层 ES 就是 akaiservice 主表。
|
||||
|
||||
## 关键字段
|
||||
|
||||
路径前缀 `intention.intent_arbitrator_info`:
|
||||
|
||||
**仲裁结果**
|
||||
- `predict_result_domain` / `l2_domain` / `l2_func` / `before_final_rule_domain` — 各阶段判出的 domain
|
||||
- `single_result_domain` / `multi_result_domain` — 单轮 / 多轮结果
|
||||
- `dialog_status` — 对话状态(`FINISH` / ...)
|
||||
|
||||
**仲裁打分**
|
||||
- `score_domains` (dict) — 各候选 domain 的 `score` + `func`,仲裁打分原始数据
|
||||
- 例:`controlCopilot:1.0`, `soundboxControl:0.97`, `default:0.05`
|
||||
|
||||
**仲裁等级**
|
||||
- `arbitrator_level` — 仲裁等级(如 `Agent_LLM` 表示走 LLM agent 路径)
|
||||
- `is_llm_allowed` / `is_new_agent_logic`
|
||||
|
||||
**LLM agent 命中**
|
||||
- `llm_agent_info`:
|
||||
- `isEffective` — 是否生效
|
||||
- `agentType` — 类型(如 `车载控制`)
|
||||
- `agentName` — 名称(如 `controlCopilot|iotCopilot|productAgent`)
|
||||
- `agentSubType`
|
||||
- `hit_rules` — 命中的召回规则(如 `[agent_llm_recall_controlCopilot]`)
|
||||
|
||||
**LLM 仲裁子流程**
|
||||
- `llm_strategy_info`:`llm_domain` / `llm_intent` / `llm_strategy_stage` / `before_llm_*`
|
||||
|
||||
**confidence**
|
||||
- `intent_confidence_information`
|
||||
|
||||
## 命令模板
|
||||
|
||||
```bash
|
||||
# 整个仲裁对象
|
||||
python elk_query.py <rid> --json-path intention.intent_arbitrator_info
|
||||
|
||||
# 命中的 agent + 召回规则 + 仲裁等级(速览)
|
||||
python elk_query.py <rid> \
|
||||
--fields request_id,query,domain,intention.intent_arbitrator_info.llm_agent_info,intention.intent_arbitrator_info.hit_rules,intention.intent_arbitrator_info.arbitrator_level
|
||||
|
||||
# 各候选 domain 的仲裁打分
|
||||
python elk_query.py <rid> --json-path intention.intent_arbitrator_info.score_domains
|
||||
```
|
||||
|
||||
## 字段 schema
|
||||
|
||||
完整 schema 见 [`../INDEX_CATALOG.md`](../INDEX_CATALOG.md) 中 `arch-flat-nlp-log-f-*` 一节。
|
||||
@@ -0,0 +1,61 @@
|
||||
# 免唤醒判决日志
|
||||
|
||||
> "免唤醒"指车机上不需要每次说"小爱同学"的免唤醒指令模式(如开/关空调、调温度)。每次请求都会经过免唤醒判决——是否让这条 query 直接执行而不是走闲聊/拒识。
|
||||
|
||||
## 三个视角,两张表
|
||||
|
||||
| # | 业务视角 | 日志位置 | 命令 |
|
||||
| - | ------- | ------- | ---- |
|
||||
| 1 | **拒识模块视角**:每次请求拒识模块整体输出(含所有子策略,含调用免唤醒的结果) | `aiservice_duplex_rejection_lcs_log*` 整条 `rejectInfo` | `--preset reject` |
|
||||
| 2 | **拒识里的免唤醒子字段**:拒识调免唤醒那一次的入参出参 | 同表,`rejectInfo.strategy_info.reject.KEY_WORD_FREE_RESTRIC` | `--preset reject --json-path rejectInfo.strategy_info.reject.KEY_WORD_FREE_RESTRIC` |
|
||||
| 3 | **免唤醒模块视角**:免唤醒模块**被后处理调到时**自己写的日志(独立的另一张表) | `nlp_post_processing_lcs-*` 中 `moduleName=keyword-free-log` | `--preset kwfree` |
|
||||
|
||||
1 和 2 同表同条记录,2 只是取子字段;3 是完全独立的另一张表。
|
||||
|
||||
## 关键字段
|
||||
|
||||
### 视角 2:拒识里的免唤醒子字段
|
||||
|
||||
路径前缀 `rejectInfo.strategy_info.reject.KEY_WORD_FREE_RESTRIC`:
|
||||
- `process` — 是否走到免唤醒判决(true / false)
|
||||
- `skipped_reason` — 被跳过原因(如 `post_processing_takeover` 表示让后处理接管,进入视角 3)
|
||||
- `keywordFreeType`
|
||||
- `isCodeMatch`
|
||||
- `getProcessedQuery`
|
||||
- `Function List`
|
||||
|
||||
### 视角 3:后处理免唤醒模块自身
|
||||
|
||||
JSON 字段 `message` 顶层:
|
||||
- `process` — 是否实际走到处理
|
||||
- `skipReason` — 未处理原因(如 `rejectedByOtherStrategy` 已被其他策略先拒)
|
||||
- `route.flow` / `route.enableFullMigration` / `route.clawEnabled`
|
||||
- `request.query`
|
||||
- `request.keywordFreeType`(如 `INSTRUCTION_KEY_WORD_FREE`)
|
||||
- `request.zone`(如 `DRIVER`)
|
||||
- `request.appId` / `maskUid` / `maskDeviceId`
|
||||
- `fallback.oldRejectType` / `fallback.oldDomain`
|
||||
|
||||
## 排查 Playbook
|
||||
|
||||
**问题:免唤醒为什么没生效?**
|
||||
|
||||
1. 先查视角 2,看拒识有没有调到免唤醒:
|
||||
```bash
|
||||
python elk_query.py <rid> --preset reject \
|
||||
--json-path rejectInfo.strategy_info.reject.KEY_WORD_FREE_RESTRIC
|
||||
```
|
||||
- `process=true` → 走到免唤醒判决了,看 `keywordFreeType` 等子字段
|
||||
- `process=false` → 没走到,看 `skipped_reason`:
|
||||
- `post_processing_takeover` → 进视角 3 看后处理免唤醒
|
||||
- 其他原因 → 拒识自己挡了
|
||||
|
||||
2. 视角 3——后处理免唤醒模块自身:
|
||||
```bash
|
||||
python elk_query.py <rid> --preset kwfree --json-path message
|
||||
```
|
||||
- `process=false` 时看 `skipReason`(`rejectedByOtherStrategy` 表示已被先拒)
|
||||
|
||||
## 字段 schema
|
||||
|
||||
完整 schema 见 [`../INDEX_CATALOG.md`](../INDEX_CATALOG.md) 中 `aiservice_duplex_rejection_lcs_log*` 和 `nlp_post_processing_lcs-*` 两节。
|
||||
@@ -0,0 +1,127 @@
|
||||
# 小米汽车 OneTrack 端侧埋点
|
||||
|
||||
> 小米汽车小爱端侧上报的全量埋点(端到端可用性、ASR 状态、全离线日志、性能指标等)。与 NLP 服务端日志正交:从车端上报的客户端事件流。
|
||||
>
|
||||
> 索引 `onetrack_xiaoai_micar*`(**c3log 独立集群**),单日 ~300GB——**必须传 `--date YYYYMMDD`**。
|
||||
|
||||
## tip 体系
|
||||
|
||||
一个 request_id 对应多条记录,每条对应一个 `tip` 埋点点位。先 `--fields tip,tip_name` 看一眼有哪些 tip:
|
||||
|
||||
```bash
|
||||
python elk_query.py <rid> --preset micar --date 20260424 \
|
||||
--fields request_id,tip,tip_name,tip_module_name,event_name
|
||||
```
|
||||
|
||||
主要点位:
|
||||
|
||||
| tip | tip_name | tip_module_name | event_name | 含义 |
|
||||
| --- | -------- | --------------- | ---------- | ---- |
|
||||
| `1024.1.1.1.23177` | 小爱端到端可用性 | 性能类 | execute | 全链路时序埋点(wakeup → asr → nlp → exec) |
|
||||
| `1024.1.2.1.23176` | 小爱全离线日志 | 日志类 | execute | **离线模型相关日志** |
|
||||
| `1024.1.2.1.34496` | 离线NLP结果 | — | state | 离线 NLP 出参 |
|
||||
| `1024.4.1.1.25631` | ASR识别状态 | ASR识别 | state | ASR 中间状态 |
|
||||
| `1024.1.1.1.33610` | 第三方接口调用 | — | execute | — |
|
||||
| `1024.1.3.1.23520` / `23521` | 小爱指令开始 / 结束 | — | start / — | — |
|
||||
|
||||
⚠️ **不同 tip 的负载 schema 不统一**:
|
||||
- 23177 → 负载在顶层 `key_value`
|
||||
- 23176 → 负载在顶层 `request_infos[]` / `response_infos[]` / `other_infos`(**不在 `key_value`**)
|
||||
- 34496 → 负载在 `key_value.instructions`
|
||||
- 25631 → `key_value` 为 null,看 `event_name` / `other_infos`
|
||||
|
||||
---
|
||||
|
||||
## 23177 — 端到端可用性
|
||||
|
||||
负载在顶层 `key_value`(脚本自动解析)。常用字段:
|
||||
|
||||
**唤醒**:`wakeup_received_event` / `wakeup_ball_appear`
|
||||
|
||||
**ASR 时序**:`asr_first_partial` / `asr_first_text` / `asr_first_same_final` / `asr_final` / `asr_first_pack_sent`
|
||||
|
||||
**NLP 时序**:`nlp_start_answer` / `nlp_finish_answer`
|
||||
|
||||
**结果**:`state_result_type`(`success` / ...)/ `state_exec_result` / `state_cancel_msg`
|
||||
|
||||
**指令统计**:`state_exec_ins_total` / `state_exec_ins_success` / `state_exec_ins_failed` / `state_exec_ins_filtered`
|
||||
|
||||
**异常**:`state_nlp_unknown_instructions` (list) / `state_nlp_exceptions` / `state_vad_end_type`
|
||||
|
||||
**链路**:`state_duplex` / `state_request_id` / `state_asr_final_size`
|
||||
|
||||
```bash
|
||||
python elk_query.py <rid> --preset micar --date 20260424 \
|
||||
--fields request_id,tip,key_value.state_result_type,key_value.state_exec_result,key_value.wakeup_received_event,key_value.asr_final,key_value.nlp_finish_answer
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 23176 — 全离线日志 ⭐ 离线模型核心
|
||||
|
||||
⚠️ schema 与其他 tip 不同:负载在顶层 `request_infos[]` / `response_infos[]` / `other_infos`(list,长度通常为 1,但每个 list 都有大量信息),**不在 `key_value`**。
|
||||
|
||||
### `request_infos[0]` — 送给离线引擎的入参
|
||||
|
||||
- 引擎/模型版本:`engine_id` / `engine_model`(如 `3.2.30-claw`)/ `offline_model_ver`(如 `2026012201`)/ `miai_ver` / `app_version`
|
||||
- 请求形态:`request_type` / `request_state_type` / `is_wakeup_req` / `is_llm` / `duplex` / `origin`
|
||||
- 链路环境:`srv_env` / `network` / `network_nlp` / `network_type` / `off_asr_avail`
|
||||
- 设备:`device_id` / `car_type` / `car_config` / `is_driving` / `zone_info` / `is_login` / `has_token` / `isInternal`
|
||||
- **`context_offline`** (list) — 离线引擎拿到的全部 context payload(含 `Nlp.OfflineSession` / `Map.MapState` / `UIController.InteractionInfoList` 等 namespace)。**研究模型行为时务必看这一段**
|
||||
- 其他:`ua` / `transaction_id` / `request_id` / `timeout_reason`
|
||||
|
||||
### `response_infos[0]` — 引擎返回的产物
|
||||
|
||||
⚠️ 不止 `nlp_debug_info`,三块都要看:
|
||||
|
||||
**`instructions[]`** — 引擎下发的全部指令序列。每条 `header.namespace.name` + `header.is_offline`(true/false 标识离/在线判决)+ `header.dialog_id` / `id` / `transaction_id` + `payload`。常见 namespace.name:
|
||||
- `System.Heartbeat` / `System.Abort`
|
||||
- `Offline.CloudStop`(`current_round_used_audio_duration` / `next_round_rid` / `stop_audio_duration`)
|
||||
- `SpeechRecognizer.RecognizeResult`(`is_final` / `results[].text` / `confidence` / `is_nlp_request` / `is_key_word_free_request`)
|
||||
- `Nlp.StartAnswer` / `Nlp.FinishAnswer` / `Nlp.IntentsWithRelation`(`payload.intent`)
|
||||
- `Template.Query`(`text`)
|
||||
- `Dialog.Reject`(`query` / `reject_type` 如 `NON_HUMAN`) / `Dialog.Finish`
|
||||
|
||||
**`nlp_debug_info`** ⭐ 离线 NLP 模型自身的调试输出(模型日志打点)。研究模型决策为什么这样做的核心:
|
||||
- **`Arbitrate`** — 仲裁器决策:`query` / **`logits`** (list[float], 各类目原始打分) / `label_id` / `category`(如 `Info`)/ `predict` / `label`(如 `baike#person`)/ `domains`(候选 domain)/ `time`
|
||||
- 各阶段耗时:`Rewrite.time` / `PreTrain.time`(`bertAvailable`)/ `EdgeIntentExecutor.time` / `GeneralParse.time` / `lookAndTalk.time`
|
||||
- 候选 domain:`domain_confidence` (list) / `domain_parsers` (list)
|
||||
- 各 domain parser 子模块(如 `music` / `phonecall` / `mapApp`):每个 parser 块下含 `time` / `score` / `parser_version` + 子模块耗时(如 `music.MusicKvParserCost` / `MusicJsgfParserCost` / `MusicIcsfModelParserCost` / `MusicGlobalCost`)
|
||||
- **`nlp_model_version`** (dict) — 各模型 checkpoint 版本(如 `arbitrator-l1: 20241205_offline` / `phonecall-icsf: 240205_offline` / `mapapp-icsf-single: 241224_offline`)
|
||||
- `nlp_edge_version` — 边端 NLP 引擎整体版本
|
||||
- `edge_track` — 上下文/能力 capability 追踪:`capabilitiesVersion` / `contexts`(用到的 context namespace list)/ `arbitrator_priority`
|
||||
- `llm_wait` — 是否等待 LLM
|
||||
|
||||
**`other_infos`** — 其他辅助信息(list)
|
||||
|
||||
### 命令模板
|
||||
|
||||
```bash
|
||||
# 整条全离线日志(含 request_infos[0]、response_infos[0]、other_infos)
|
||||
python elk_query.py <rid> --preset micar --date 20260424 \
|
||||
--fields request_id,tip,tip_name,request_infos,response_infos,other_infos
|
||||
|
||||
# 只看模型自身打的 debug 日志
|
||||
python elk_query.py <rid> --preset micar --date 20260424 \
|
||||
--json-path response_infos.0.nlp_debug_info
|
||||
|
||||
# 模型版本 + 仲裁结果速览
|
||||
python elk_query.py <rid> --preset micar --date 20260424 \
|
||||
--fields request_id,tip,response_infos.0.nlp_debug_info.Arbitrate,response_infos.0.nlp_debug_info.nlp_model_version,response_infos.0.nlp_debug_info.nlp_edge_version,request_infos.0.engine_model,request_infos.0.offline_model_ver
|
||||
|
||||
# 引擎全部指令(看 is_offline 区分离/在线)
|
||||
python elk_query.py <rid> --preset micar --date 20260424 \
|
||||
--json-path response_infos.0.instructions
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 34496 — 离线NLP结果
|
||||
|
||||
`key_value` 顶层就是 `{instructions: [...]}`,结构和 23176 的 `response_infos[0].instructions` 一致,但**只是 NLP 部分的指令子集**(不含 SpeechRecognizer / System / Offline 等)。没有 `nlp_debug_info`、没有 `request_infos`——想看模型决策细节去 23176。
|
||||
|
||||
---
|
||||
|
||||
## 字段 schema
|
||||
|
||||
完整 schema 见 [`../INDEX_CATALOG.md`](../INDEX_CATALOG.md) 中 `onetrack_xiaoai_micar*` 一节。
|
||||
@@ -0,0 +1,300 @@
|
||||
"""
|
||||
ELK / Elasticsearch 日志查询工具。
|
||||
|
||||
按 request id 查 ELK。支持:
|
||||
- 多账号 profile(不同业务用不同 ES 账号)
|
||||
- 多业务 preset(业务语义 → 索引/字段/过滤条件的映射)
|
||||
- 深度 JSON 字符串自动解析(rejectInfo / message 等场景)
|
||||
- 按点号路径提取嵌套字段(--json-path)
|
||||
|
||||
账号密码从同目录 profiles.json 加载(团队共用账号已 commit 进仓库;可用 ELK_FETCH_PROFILES 指向别处覆盖)。
|
||||
业务→索引→路径的详细说明见同目录 INDEX_CATALOG.md。
|
||||
|
||||
用法:
|
||||
python elk_query.py <request_id>
|
||||
[--preset main|reject|kwfree|micar]
|
||||
[--date YYYYMMDD]
|
||||
[--index PATTERN] [--field FIELD] [--time-field NAME] [--profile KEY]
|
||||
[--size N]
|
||||
[--fields f1,f2,...]
|
||||
[--json-path a.b.c] # 提取嵌套字段,自动解 JSON 字符串
|
||||
[--raw] # 关闭自动 JSON 字符串解析
|
||||
"""
|
||||
import argparse
|
||||
import json
|
||||
import os
|
||||
import sys
|
||||
import warnings
|
||||
from copy import deepcopy
|
||||
from datetime import datetime, timedelta
|
||||
from pathlib import Path
|
||||
from typing import Any, Dict, List, Optional
|
||||
|
||||
import urllib3
|
||||
from elasticsearch import Elasticsearch
|
||||
from elasticsearch.exceptions import (
|
||||
ConnectionError as ESConnectionError,
|
||||
ElasticsearchWarning,
|
||||
NotFoundError,
|
||||
RequestError,
|
||||
AuthorizationException,
|
||||
AuthenticationException,
|
||||
)
|
||||
|
||||
urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
|
||||
warnings.filterwarnings("ignore", category=ElasticsearchWarning)
|
||||
warnings.filterwarnings("ignore", category=DeprecationWarning)
|
||||
|
||||
if hasattr(sys.stdout, "reconfigure"):
|
||||
sys.stdout.reconfigure(encoding="utf-8")
|
||||
|
||||
ES_PORT = int(os.getenv("ES_PORT", "80"))
|
||||
ES_HOST_FALLBACK = os.getenv("ES_HOST", "")
|
||||
|
||||
SCRIPT_DIR = Path(__file__).resolve().parent
|
||||
PROFILES_PATH = Path(os.getenv("ELK_FETCH_PROFILES", SCRIPT_DIR / "profiles.json"))
|
||||
|
||||
|
||||
def load_profiles() -> Dict[str, Dict[str, str]]:
|
||||
"""从 profiles.json 读取账号配置。缺文件 / 格式错误时给出可操作的错误信息。"""
|
||||
if not PROFILES_PATH.exists():
|
||||
raise FileNotFoundError(
|
||||
f"profiles.json not found at {PROFILES_PATH}. "
|
||||
f"Override path via ELK_FETCH_PROFILES=/path/to/profiles.json"
|
||||
)
|
||||
try:
|
||||
with PROFILES_PATH.open("r", encoding="utf-8") as f:
|
||||
data = json.load(f)
|
||||
except json.JSONDecodeError as e:
|
||||
raise ValueError(f"profiles.json is not valid JSON: {e}")
|
||||
if not isinstance(data, dict) or not data:
|
||||
raise ValueError("profiles.json must be a non-empty JSON object keyed by profile name")
|
||||
for key, prof in data.items():
|
||||
if not isinstance(prof, dict) or "user" not in prof or "password" not in prof:
|
||||
raise ValueError(f"profile '{key}' missing required fields 'user' / 'password'")
|
||||
return data
|
||||
|
||||
|
||||
# 业务→索引/字段/额外过滤 的映射。time_field 为毫秒时间戳字段名。
|
||||
# 每条 preset 对应一个明确的业务语义,详见 INDEX_CATALOG.md。
|
||||
PRESETS: Dict[str, Dict[str, Any]] = {
|
||||
"main": {
|
||||
"profile": "default",
|
||||
"index": "arch-flat-nlp-log-f-*",
|
||||
"field": "request_id",
|
||||
"time_field": "timestamp",
|
||||
"extra_filters": [],
|
||||
},
|
||||
"reject": {
|
||||
# 后置拒识模块的日志,内含调用免唤醒判决(KEY_WORD_FREE_RESTRIC)的结果
|
||||
"profile": "reject",
|
||||
"index": "aiservice_duplex_rejection_lcs_log*",
|
||||
"field": "requestId",
|
||||
"time_field": "time",
|
||||
"extra_filters": [],
|
||||
},
|
||||
"kwfree": {
|
||||
# 后处理阶段调用免唤醒模块自身的日志。message 是 JSON 字符串
|
||||
"profile": "default",
|
||||
"index": "nlp_post_processing_lcs-*",
|
||||
"field": "requestId",
|
||||
"time_field": "timestamp",
|
||||
"extra_filters": [{"match_phrase": {"moduleName": "keyword-free-log"}}],
|
||||
},
|
||||
"micar": {
|
||||
# 小米汽车小爱 OneTrack 埋点(端到端可用性 / 性能埋点等 tip)。
|
||||
# 索引按日 rolling 且非常大(~300GB/天),强烈建议传 --date 收敛日期。
|
||||
# key_value 是序列化 JSON 字符串,脚本会自动深度解析。
|
||||
"profile": "micar",
|
||||
"index": "onetrack_xiaoai_micar*",
|
||||
"field": "request_id",
|
||||
"time_field": "timestamp",
|
||||
"extra_filters": [],
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
def build_client(profiles: Dict[str, Dict[str, str]], profile_key: str) -> Elasticsearch:
|
||||
if profile_key not in profiles:
|
||||
available = ", ".join(sorted(profiles.keys())) or "(none)"
|
||||
raise KeyError(f"profile '{profile_key}' not in profiles.json (available: {available})")
|
||||
prof = profiles[profile_key]
|
||||
host = prof.get("host") or ES_HOST_FALLBACK
|
||||
if not host:
|
||||
raise ValueError(
|
||||
f"profile '{profile_key}' has no 'host' and ES_HOST env var is not set"
|
||||
)
|
||||
return Elasticsearch(
|
||||
hosts=[f"http://{host}:{ES_PORT}"],
|
||||
http_auth=(prof["user"], prof["password"]),
|
||||
timeout=60,
|
||||
max_retries=3,
|
||||
)
|
||||
|
||||
|
||||
def build_query(
|
||||
request_id: str,
|
||||
field: str,
|
||||
time_field: str,
|
||||
date_str: Optional[str],
|
||||
size: int,
|
||||
extra_filters: List[Dict[str, Any]],
|
||||
) -> Dict[str, Any]:
|
||||
if date_str:
|
||||
date_obj = datetime.strptime(date_str, "%Y%m%d")
|
||||
start_ms = int(date_obj.timestamp() * 1000)
|
||||
end_ms = int((date_obj + timedelta(days=1)).timestamp() * 1000) - 1
|
||||
else:
|
||||
end_ms = int(datetime.now().timestamp() * 1000)
|
||||
start_ms = int((datetime.now() - timedelta(hours=48)).timestamp() * 1000)
|
||||
|
||||
filters: List[Dict[str, Any]] = [
|
||||
{"wildcard": {field: {"value": f"{request_id}*"}}},
|
||||
]
|
||||
filters.extend(deepcopy(extra_filters))
|
||||
|
||||
return {
|
||||
"query": {
|
||||
"bool": {
|
||||
"must": [{"range": {time_field: {"gte": start_ms, "lte": end_ms}}}],
|
||||
"filter": filters,
|
||||
}
|
||||
},
|
||||
"size": size,
|
||||
"sort": [{time_field: {"order": "desc"}}],
|
||||
}
|
||||
|
||||
|
||||
def deep_parse(v: Any) -> Any:
|
||||
"""递归把看起来是 JSON 的字符串展开成 dict/list。"""
|
||||
if isinstance(v, str):
|
||||
s = v.strip()
|
||||
if (s.startswith("{") and s.endswith("}")) or (s.startswith("[") and s.endswith("]")):
|
||||
try:
|
||||
return deep_parse(json.loads(s))
|
||||
except Exception:
|
||||
return v
|
||||
return v
|
||||
if isinstance(v, dict):
|
||||
return {k: deep_parse(val) for k, val in v.items()}
|
||||
if isinstance(v, list):
|
||||
return [deep_parse(x) for x in v]
|
||||
return v
|
||||
|
||||
|
||||
def get_path(obj: Any, dotted: str) -> Any:
|
||||
cur = obj
|
||||
for part in dotted.split("."):
|
||||
if not part:
|
||||
continue
|
||||
if isinstance(cur, dict):
|
||||
cur = cur.get(part)
|
||||
elif isinstance(cur, list):
|
||||
try:
|
||||
cur = cur[int(part)]
|
||||
except (ValueError, IndexError):
|
||||
return None
|
||||
else:
|
||||
return None
|
||||
return cur
|
||||
|
||||
|
||||
def project(
|
||||
source: Dict[str, Any],
|
||||
fields: Optional[List[str]],
|
||||
json_path: Optional[str],
|
||||
) -> Any:
|
||||
if json_path:
|
||||
return {json_path: get_path(source, json_path)}
|
||||
if fields:
|
||||
return {k: get_path(source, k) for k in fields}
|
||||
return source
|
||||
|
||||
|
||||
def run(args: argparse.Namespace, profiles: Dict[str, Dict[str, str]]) -> Dict[str, Any]:
|
||||
preset = PRESETS[args.preset]
|
||||
profile_key = args.profile or preset["profile"]
|
||||
index_pattern = args.index or preset["index"]
|
||||
field_name = args.field or preset["field"]
|
||||
time_field = args.time_field or preset["time_field"]
|
||||
extra_filters = preset.get("extra_filters", [])
|
||||
|
||||
body = build_query(
|
||||
args.request_id, field_name, time_field, args.date, args.size, extra_filters
|
||||
)
|
||||
try:
|
||||
client = build_client(profiles, profile_key)
|
||||
except (KeyError, ValueError) as e:
|
||||
return {"error": "ProfileError", "message": str(e)}
|
||||
|
||||
try:
|
||||
resp = client.search(index=index_pattern, body=body)
|
||||
except AuthenticationException as e:
|
||||
return {"error": "AuthenticationException", "profile": profile_key, "message": str(e)}
|
||||
except AuthorizationException as e:
|
||||
return {"error": "AuthorizationException", "profile": profile_key, "index": index_pattern, "message": str(e)}
|
||||
except NotFoundError as e:
|
||||
return {"error": "IndexNotFound", "index": index_pattern, "message": str(e)}
|
||||
except RequestError as e:
|
||||
return {"error": "QueryError", "message": str(e)}
|
||||
except ESConnectionError as e:
|
||||
return {"error": "ConnectionError", "message": str(e)}
|
||||
|
||||
hits_raw = resp.get("hits", {}).get("hits", [])
|
||||
fields = [f.strip() for f in args.fields.split(",")] if args.fields else None
|
||||
|
||||
records = []
|
||||
for h in hits_raw:
|
||||
src = h.get("_source", {})
|
||||
if not args.raw:
|
||||
src = deep_parse(src)
|
||||
records.append(project(src, fields, args.json_path))
|
||||
|
||||
return {
|
||||
"request_id": args.request_id,
|
||||
"preset": args.preset,
|
||||
"profile": profile_key,
|
||||
"index": index_pattern,
|
||||
"field": field_name,
|
||||
"time_field": time_field,
|
||||
"date": args.date or "past-48h",
|
||||
"total": len(records),
|
||||
"hits": records,
|
||||
}
|
||||
|
||||
|
||||
def parse_args() -> argparse.Namespace:
|
||||
p = argparse.ArgumentParser(
|
||||
description="Query ELK by request id (multi-profile, deep-JSON aware)",
|
||||
formatter_class=argparse.RawDescriptionHelpFormatter,
|
||||
epilog="业务→索引映射详见同目录 INDEX_CATALOG.md",
|
||||
)
|
||||
p.add_argument("request_id", help="目标 request id(支持前缀通配)")
|
||||
p.add_argument("--preset", choices=list(PRESETS.keys()), default="main",
|
||||
help="预置方案:" + " | ".join(PRESETS.keys()))
|
||||
p.add_argument("--profile", help="覆盖账号 profile(key 来自 profiles.json)")
|
||||
p.add_argument("--index", help="覆盖索引 pattern")
|
||||
p.add_argument("--field", help="覆盖 request id 字段名")
|
||||
p.add_argument("--time-field", help="覆盖时间字段名")
|
||||
p.add_argument("--date", help="指定日期 YYYYMMDD,不填默认近 48 小时")
|
||||
p.add_argument("--size", type=int, default=20, help="最多返回记录数(默认 20)")
|
||||
p.add_argument("--fields", help="只输出指定字段,逗号分隔,支持 a.b.c 嵌套")
|
||||
p.add_argument("--json-path", help="只提取某条嵌套路径的值")
|
||||
p.add_argument("--raw", action="store_true", help="关闭自动 JSON 字符串解析")
|
||||
return p.parse_args()
|
||||
|
||||
|
||||
def main() -> int:
|
||||
args = parse_args()
|
||||
try:
|
||||
profiles = load_profiles()
|
||||
except (FileNotFoundError, ValueError) as e:
|
||||
print(json.dumps({"error": "ConfigError", "message": str(e)}, indent=2, ensure_ascii=False))
|
||||
return 2
|
||||
result = run(args, profiles)
|
||||
print(json.dumps(result, indent=2, ensure_ascii=False))
|
||||
return 0 if "error" not in result else 1
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
sys.exit(main())
|
||||
@@ -0,0 +1,17 @@
|
||||
{
|
||||
"default": {
|
||||
"user": "ai_service_kibana",
|
||||
"password": "Ox7tLXSorOo5t5KG",
|
||||
"host": "akaiservice.api.es.srv"
|
||||
},
|
||||
"reject": {
|
||||
"user": "ai_service_kibana",
|
||||
"password": "Ox7tLXSorOo5t5KG",
|
||||
"host": "akaiservice.api.es.srv"
|
||||
},
|
||||
"micar": {
|
||||
"user": "xiaoai_micar_kibana",
|
||||
"password": "aF6pi9XfzXq2h543",
|
||||
"host": "c3log.api.es.srv"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,71 @@
|
||||
---
|
||||
name: eval-repair
|
||||
description: 分析评测错误,并为针对性训练集/评测集补充生成可 review 的数据计划。
|
||||
when_to_use: 当用户提供评测结果、模型判错样本或带标签错误 case,并希望补充训练/评测数据时使用。
|
||||
aliases: error-repair, eval-data
|
||||
allowed_tools: read_file, write_file, edit_file, grep_search, glob_search, ask_user_question
|
||||
---
|
||||
|
||||
使用这个 skill 处理“已有评测错误 -> 错误分析 -> 人工 review 问题类别 -> 数据生成计划 -> canonical records -> 导出”的工作流。
|
||||
|
||||
## 输入假设
|
||||
|
||||
用户可能会提供一个或多个文件,里面包含:
|
||||
|
||||
- query 或多轮对话
|
||||
- 期望/正确 label
|
||||
- 模型预测 label
|
||||
- 可选的 tts
|
||||
- 可选的原因、垂域、模型版本、设备或其他元数据
|
||||
|
||||
输入格式可能每次不同。不要在检查文件前假设列名。
|
||||
|
||||
## 必要工作流
|
||||
|
||||
1. 读取用户提供的评测/错误文件;如果没有路径,先询问。
|
||||
2. 识别已有字段,以及缺失的 canonical record 必填字段。
|
||||
3. 生成错误分析笔记,至少包含:
|
||||
- top 混淆对
|
||||
- 反复出现的 query 模式
|
||||
- 代表性样例
|
||||
- 缺失或不明确的字段
|
||||
4. 提出可供人工 review 的问题类别。
|
||||
5. 如果类别边界不清,先请用户确认,再制定生成计划。
|
||||
6. 创建生成计划,说明:
|
||||
- 目标问题类别
|
||||
- 目标 label
|
||||
- 计划生成数量
|
||||
- 要生成的正例、反例、边界例
|
||||
- 必要元数据
|
||||
7. 只有在计划清楚后,才生成或请求 canonical records。
|
||||
8. TODO:工具实现后,在预览或导出前运行 `data_agent_validate_dataset_records`。
|
||||
9. TODO:工具实现后,运行 `render_dataset_preview` 和 `export_dataset`。
|
||||
|
||||
## 当前工具状态
|
||||
|
||||
当前先使用已有工具完成文件检查和产物写入:
|
||||
|
||||
- `read_file`
|
||||
- `write_file`
|
||||
- `edit_file`
|
||||
- `grep_search`
|
||||
- `glob_search`
|
||||
- `ask_user_question`
|
||||
|
||||
规划中的数据工具不一定已经实现。除非用户确认当前分支已经实现,否则不要直接调用不存在的工具。
|
||||
|
||||
## 推荐产物
|
||||
|
||||
```text
|
||||
tasks/{task_id}/context/eval_errors.*
|
||||
tasks/{task_id}/artifacts/error_analysis.md
|
||||
tasks/{task_id}/artifacts/generation_plan.md
|
||||
tasks/{task_id}/artifacts/generated_candidates.jsonl
|
||||
tasks/{task_id}/memory/open_questions.md
|
||||
```
|
||||
|
||||
## 约束
|
||||
|
||||
- 在 canonical records 达成一致并通过校验前,不要直接导出最终训练/评测格式。
|
||||
- 不要把自动聚类当成最终事实,聚类和类别命名必须可 review。
|
||||
- 面向人的总结要简洁,并尽量用样例支撑。
|
||||
@@ -0,0 +1,125 @@
|
||||
---
|
||||
name: online-mining
|
||||
description: 分析 badcase,并通过可 review 的策略迭代挖掘线上相似 case。
|
||||
when_to_use: 当用户提供线上 badcase 或产品/标签定义,并希望查找相似线上问题或构建专项集时使用。
|
||||
aliases: badcase-mining, router-mining
|
||||
allowed_tools: read_file, write_file, edit_file, grep_search, glob_search, ask_user_question, data_agent_profile_router_sessions, data_agent_search_router_sessions, data_agent_sample_router_candidates, data_agent_convert_router_candidates_to_records, data_agent_export_dataset_records
|
||||
---
|
||||
|
||||
使用这个 skill 处理“badcase 或标签定义 -> 挖掘策略 -> 候选召回 -> 抽样 review -> 策略迭代 -> mined dataset”的工作流。
|
||||
|
||||
所有线上挖掘产物必须放在当前用户当前会话的 output 目录下。不要把 records 或 review 结果写到项目根目录的 `output/`、`tasks/` 或源码目录。canonical records 的稳定输出文件名固定为 `output/records.jsonl`,不要按数据集名创建子目录,不要自定义时间戳、中文专题名或随机文件名。工具会把相对 `output_path` 自动路由到会话 output 目录,并把 records 路径归一到当前会话 `output/records.jsonl`;展示给用户时以工具返回的实际路径为准。
|
||||
|
||||
## 两条分支
|
||||
|
||||
线上挖掘后必须先判断用户要的是哪条分支。
|
||||
|
||||
### 分支 A:线上候选直接作为数据样本
|
||||
|
||||
当用户说“把筛选出的数据变成样本”“拿这些线上数据做评测集”“保留这批线上 case”“导出候选样本”时,走这个分支。
|
||||
|
||||
流程:
|
||||
|
||||
1. 用 `data_agent_profile_router_sessions` 查看数据概貌。
|
||||
2. 用 `data_agent_search_router_sessions` 按策略召回线上候选。
|
||||
3. 用 `data_agent_sample_router_candidates` 抽样展示给用户 review。
|
||||
4. 根据用户 review 意见形成 include/exclude/uncertain 决策和目标标签。
|
||||
5. 用 `data_agent_convert_router_candidates_to_records` 把线上候选直接转换为 canonical records。
|
||||
6. 如果用户要求落盘,用 `data_agent_export_dataset_records` 导出紧凑 JSONL,`output_path` 固定传 `output/records.jsonl`。
|
||||
|
||||
这个分支不生成新 query,不调用数据生成计划工具,不调用 dataset draft 归一化工具。
|
||||
|
||||
### 分支 B:基于线上问题再生成补充数据
|
||||
|
||||
当用户明确说“生成/扩写/构造/造一批类似 case/补充训练数据”时,才走这个分支。
|
||||
|
||||
流程:
|
||||
|
||||
1. 先完成线上召回、抽样和 review。
|
||||
2. 总结线上问题模式和需要覆盖的边界。
|
||||
3. 再切换到数据生成流程,创建待 review 的 generation goal 和 generation plan。
|
||||
4. 用户确认后才生成 draft,并转换为 canonical records。
|
||||
|
||||
如果用户只是要求“把线上数据作为样本”,不要进入这个分支。
|
||||
|
||||
## 输入假设
|
||||
|
||||
用户可能会提供:
|
||||
|
||||
- 产品或标签定义
|
||||
- 线上 badcase 样例
|
||||
- 期望/正确 label
|
||||
- 线上预测 label
|
||||
- query、tts、agent type、function name、垂域、设备、日期、模型版本或其他元数据
|
||||
|
||||
线上字段和允许使用的筛选条件还没有最终确定。
|
||||
|
||||
## 必要工作流
|
||||
|
||||
1. 读取用户提供的 badcase 或定义文件。
|
||||
2. 分析 badcase 共性:
|
||||
- query 模式
|
||||
- label 混淆
|
||||
- agent/function 类型
|
||||
- 垂域
|
||||
- 如存在,分析设备/日期/模型等元数据
|
||||
3. 起草带明确筛选条件的挖掘策略。
|
||||
4. 当筛选条件不清或影响较大时,先请用户 review,再做宽泛线上召回。
|
||||
5. 只使用批准的工具检索或请求候选数据。
|
||||
6. 先抽取小批 review 样本,通常约 100 条。
|
||||
7. 总结样本命中率和主要 false positive 模式。
|
||||
8. 根据结果调整策略,并按需重复。
|
||||
9. 最终产出以下一种或多种:
|
||||
- 线上问题评估报告
|
||||
- 专项评测集候选
|
||||
- 专项 badcase 集合
|
||||
- 训练候选数据
|
||||
|
||||
## 当前工具状态
|
||||
|
||||
当前先使用已有工具完成本地分析和策略起草:
|
||||
|
||||
- `read_file`
|
||||
- `write_file`
|
||||
- `edit_file`
|
||||
- `grep_search`
|
||||
- `glob_search`
|
||||
- `ask_user_question`
|
||||
- `data_agent_profile_router_sessions`:读取 `router_session_parquet/date=YYYYMMDD/` 的小样本概貌,查看 schema、设备、轮次、domain、intent、func 等分布。
|
||||
- `data_agent_search_router_sessions`:按日期、设备、domain、intent、func、query 关键词/正则、轮次数等条件召回线上 session 候选;输出命中 turn、前文 turn、req_id 和 action_json 解析结果。
|
||||
- `data_agent_sample_router_candidates`:对召回候选做稳定抽样,并输出 review 需要的基础统计。
|
||||
- `data_agent_convert_router_candidates_to_records`:把 review 后的线上候选直接转换为 canonical records;用于“线上数据作为样本”的分支。
|
||||
- `data_agent_export_dataset_records`:后续把 review 后的线上候选转换为 canonical records 后落盘;默认紧凑 JSONL,固定传 `output/records.jsonl`。
|
||||
|
||||
TODO:后续规划中的专用工具:
|
||||
|
||||
- `analyze_badcases`
|
||||
- `build_mining_strategy`
|
||||
- `create_annotation_batch`
|
||||
- `read_annotation_result`
|
||||
- `evaluate_mining_precision`
|
||||
- `refine_mining_strategy`
|
||||
- `export_mined_dataset`
|
||||
|
||||
不要手写 raw SQL,也不要用宽泛 shell 命令进行数据检索。
|
||||
|
||||
## 推荐产物
|
||||
|
||||
```text
|
||||
tasks/{task_id}/context/badcases.*
|
||||
tasks/{task_id}/artifacts/badcase_analysis.md
|
||||
tasks/{task_id}/artifacts/mining_strategy.md
|
||||
tasks/{task_id}/artifacts/candidate_sample.jsonl
|
||||
tasks/{task_id}/artifacts/review_report.md
|
||||
tasks/{task_id}/artifacts/mined_candidates.jsonl
|
||||
tasks/{task_id}/memory/open_questions.md
|
||||
tasks/{task_id}/memory/failed_attempts.md
|
||||
```
|
||||
|
||||
## 约束
|
||||
|
||||
- 除非批准的工具输出已经脱敏,否则不要导出敏感线上原始字段。
|
||||
- 不要把第一版挖掘策略当成最终策略。
|
||||
- 始终让筛选条件和抽样决策可 review。
|
||||
- 只有用户明确要求生成、扩写或构造新数据时,才进入数据生成分支。
|
||||
- 如果用户要求把筛选出的线上候选变成样本,必须先使用 `data_agent_convert_router_candidates_to_records`,不要改走 generation goal/plan。
|
||||
@@ -0,0 +1,319 @@
|
||||
---
|
||||
name: product-data
|
||||
description: 从产品/标签定义、手写边界规则或示例 query 中提取标签边界,并生成可 review 的数据计划、dataset draft text 和 canonical metadata records。
|
||||
when_to_use: 当用户提供产品定义、标签规则、路由边界文档、示例 query、手写标签边界,并希望生成训练/评测/专项数据时使用。
|
||||
aliases: definition-data, label-data
|
||||
allowed_tools: read_file, write_file, edit_file, grep_search, glob_search, ask_user_question, data_agent_load_input_sources, data_agent_render_source_context, data_agent_extract_case_evidence, data_agent_prepare_generation_goal, data_agent_confirm_generation_goal, data_agent_prepare_generation_plan, data_agent_show_generation_plan, data_agent_update_generation_plan, data_agent_confirm_generation_plan, data_agent_normalize_dataset_draft, data_agent_validate_dataset_records, data_agent_export_dataset_records
|
||||
---
|
||||
|
||||
使用这个 skill 作为“产品/标签定义/手写规则/示例 query -> 输入文本化 -> generation goal 草案 -> 人工 review -> 生成计划 review -> dataset draft text -> canonical metadata records”的统一入口。
|
||||
|
||||
本 skill 内置数据记录生成协议。其他数据开发 skill 如果需要生成或整理标准数据,可以复用这里的“交互门禁、dataset draft text v1、canonical record v1”规则。
|
||||
|
||||
## 输入假设
|
||||
|
||||
用户可能会提供:
|
||||
|
||||
- 产品定义文档、标签定义文档、路由规则文档。
|
||||
- 表格、Markdown、JSON、CSV 或普通文本里的标签定义。
|
||||
- 手写的标签边界规则。
|
||||
- 一组 example query、badcase、正例或反例。
|
||||
- 已知边界冲突,例如某类 query 应该进入哪个 Agent/function。
|
||||
|
||||
输入可能不完整或存在歧义。保留不确定性,不要自行发明隐藏规则。
|
||||
|
||||
## 任务定位
|
||||
|
||||
先判断用户输入属于哪一类:
|
||||
|
||||
1. **文件定义型**:用户提供文件路径、文档、表格或粘贴的大段定义内容。
|
||||
2. **手写规则型**:用户直接描述标签边界,例如“找附近美食是餐饮服务,导航去某地是地图导航”。
|
||||
3. **示例归纳型**:用户只给 query/example/badcase,需要先归纳边界和标签倾向。
|
||||
|
||||
三类输入最后都要统一产出:
|
||||
|
||||
```text
|
||||
dataset_label:
|
||||
target 或 target_definitions:
|
||||
plan_hint:
|
||||
coverage:
|
||||
exclusions:
|
||||
open_questions:
|
||||
source_refs:
|
||||
```
|
||||
|
||||
这一步称为 `generation_goal`。它是模型基于输入资料整理出的数据生成目标草案,不是最终生成计划。
|
||||
|
||||
`generation_goal` 必须先展示给用户 review。只有用户确认 generation goal 后,才能进入本 skill 内置的 generation plan review 流程。
|
||||
|
||||
## 交互门禁
|
||||
|
||||
默认不要一步到位生成数据。除非用户已经明确说“开始生成”“确认计划”“按这个计划生成”或同义表达,否则只能做目标对齐、计划草案和问题确认。
|
||||
|
||||
为了减少重复确认,优先按下面两种门禁模式选择:
|
||||
|
||||
- **一次确认模式**:用户直接给出手写规则、完整 target 表达,并且明确希望生成数据时,直接调用 `data_agent_prepare_generation_plan`,传 `direct_review=true` 和 `target_definitions`。这一次 review 同时确认目标、数量、轮次和路径;用户回复“确认,开始生成”后即可调用 `data_agent_confirm_generation_plan`。
|
||||
- **两段确认模式**:用户提供文件、表格、badcase、长文档,或者标签/边界/字段含义有歧义时,先用 `data_agent_prepare_generation_goal` 做目标 review;目标确认后再做 plan review。
|
||||
|
||||
所有数据产物必须放在当前用户当前会话的 output 目录下。不要把 records、draft 或 validation 写到项目根目录的 `output/`、`tasks/` 或其他源码目录。canonical records 的稳定输出文件名固定为 `output/records.jsonl`,不要按数据集名创建子目录,不要自定义时间戳、中文专题名或随机文件名。工具会把相对 `output_path` 自动路由到会话 output 目录,并把 records 路径归一到当前会话 `output/records.jsonl`;展示给用户时以工具返回的实际路径为准。
|
||||
|
||||
开始生成前必须确认这些信息:
|
||||
|
||||
- `dataset_label`:数据集或专题名称。
|
||||
- `target` / `target_definitions`:最终监督标签。单标签任务用 `target`,多标签边界任务必须用 `target_definitions` 列出每个标签和判定规则。
|
||||
- 生成数量:总条数,以及单轮/多轮数量或比例。
|
||||
- 覆盖范围:需要覆盖哪些 query 类型、意图边界或错误类型。
|
||||
- 负例/排除项:哪些表达不要生成,或哪些边界容易误判。
|
||||
- 落盘路径:canonical records 固定使用 `output/records.jsonl`,由工具路由到当前会话 output 目录。
|
||||
|
||||
如果任一信息缺失,不要生成数据,不要调用 `data_agent_prepare_generation_plan`,不要调用 `data_agent_normalize_dataset_draft`,不要调用 `data_agent_validate_dataset_records`,只向用户提出需要确认的问题。
|
||||
|
||||
两段确认模式下,信息完整后,调用 `data_agent_prepare_generation_goal` 创建 pending goal。这个工具会暂停本轮,必须把返回的 `generation_goal` 展示给用户 review。用户确认 goal 之后,调用 `data_agent_confirm_generation_goal` 获取 `confirmed_goal_id`,再调用 `data_agent_prepare_generation_plan` 创建 pending plan。创建 plan 后也会暂停本轮,必须等待用户 review。
|
||||
|
||||
一次确认模式下,不要先创建 generation goal;直接创建 pending plan,并在 plan 里包含 `target_definitions`、`total_count`、`turn_mix`、`coverage`、`exclusions`、`output_path`。`output_path` 固定传 `output/records.jsonl`。不要让用户先确认目标再确认计划。
|
||||
|
||||
用户确认后,拿到 `confirmed_plan_id`,才能生成 dataset draft text,并继续调用工具。
|
||||
|
||||
`data_agent_normalize_dataset_draft` 对生成数据有代码级门禁:没有 `confirmed_plan_id`,或者计划未确认,会拒绝执行。
|
||||
|
||||
如果用户在原始需求里已经写出 `Agent(tag="xxx")`、function 调用或其他完整标签表达,`target` 必须原样保留这个完整表达,不要简化成纯标签名。例如用户说 `Agent(tag="餐饮服务")`,则 `target_definitions[*].target` 和后续 draft 的 `target:` 都必须写 `Agent(tag="餐饮服务")`,不要写成 `餐饮服务`。
|
||||
|
||||
review 展示必须简短清晰,不要重复解释工具和流程。每次 review 最多展示 6 行,格式优先如下:
|
||||
|
||||
```text
|
||||
我先把生成目标整理好了,先确认边界,暂时不生成数据。
|
||||
- 数据集:xxx
|
||||
- 标签:A -> Agent(tag="A");B -> Agent(tag="B")
|
||||
- 边界:一句话说明核心判定规则
|
||||
- 覆盖:一句话说明主要 case 类型
|
||||
- 内部:goal_id `...`,revision `...`
|
||||
|
||||
你看这个目标是否准确?没问题就回“确认目标”;想改的话直接说哪里不对。
|
||||
```
|
||||
|
||||
计划 review 也最多展示 6 行,只展示数量、轮次、覆盖、输出路径和确认口令。不要把 goal 的完整内容再次复制到 plan review 中,开头必须说明“目标已确认,现在只补充生成参数,标签边界沿用上一步”。确认口令可以自然一点,例如“如果这个数量和路径可以,就回‘确认,开始生成’;想调整就直接说,比如‘改成 20 条,全单轮’。”
|
||||
|
||||
多标签边界数据不要拆成多个互不相关的单标签计划。应该创建一个计划,并在 `target_definitions` 中列出所有候选标签。例如:
|
||||
|
||||
```json
|
||||
[
|
||||
{
|
||||
"name": "餐饮服务",
|
||||
"target": "Agent(tag=\"餐饮服务\")",
|
||||
"rule": "找附近的美食、奶茶、餐厅等,没有明确要求导航。"
|
||||
},
|
||||
{
|
||||
"name": "地图导航",
|
||||
"target": "Agent(tag=\"地图导航\")",
|
||||
"rule": "明确出现导航去某地点、带我去某地点、路线规划等。"
|
||||
}
|
||||
]
|
||||
```
|
||||
|
||||
生成 draft text 时,每条 case 的 `target:` 必须从已确认计划的 `target` 或 `target_definitions[*].target` 中选择。不要临时发明新 target。
|
||||
|
||||
## 必须遵守的数据生成协议
|
||||
|
||||
不要直接生成 canonical JSON,不要直接导出最终训练/评测格式,不要绕过人类 review。
|
||||
|
||||
如果需要生成数据,必须按顺序执行:
|
||||
|
||||
1. 用 `data_agent_load_input_sources` 读取文件。
|
||||
2. 用 `data_agent_render_source_context` 把输入渲染成大模型可读 evidence text。
|
||||
3. 大模型只基于 evidence text 抽取 `generation_goal`,包括 `dataset_label`、`target_definitions`、`plan_hint`、`coverage`、`exclusions`、`open_questions`、`source_refs`。
|
||||
4. 如果目标标签、边界或字段含义不清楚,先用普通回复向用户提问并停止。
|
||||
5. 如果是手写规则且信息完整,调用 `data_agent_prepare_generation_plan`,设置 `direct_review=true`,创建一次确认的 pending 计划,并停止等待用户 review。
|
||||
6. 如果是文件/示例归纳/歧义场景,调用 `data_agent_prepare_generation_goal` 创建 pending goal,并停止等待用户 review。
|
||||
7. 用户确认 generation goal 后,调用 `data_agent_confirm_generation_goal` 获取 `confirmed_goal_id`,再调用 `data_agent_prepare_generation_plan` 创建 pending 计划。
|
||||
8. 展示计划后停止本轮,等待用户 review。
|
||||
9. 用户提出修改意见时,调用 `data_agent_update_generation_plan`,再展示计划。
|
||||
10. 用户明确确认当前计划版本后,调用 `data_agent_confirm_generation_plan`。
|
||||
11. 生成 dataset draft text v1。
|
||||
12. 调用 `data_agent_normalize_dataset_draft`,必须传入 `confirmed_plan_id`。
|
||||
13. 调用 `data_agent_validate_dataset_records`。
|
||||
14. 如果用户要求落盘 canonical records,调用 `data_agent_export_dataset_records`,`output_path` 固定传 `output/records.jsonl`,默认导出紧凑 JSONL,不要用 `write_file` 手写 JSON。
|
||||
15. 本阶段默认只推进到 canonical metadata records;除非用户另行要求,不做最终训练/评测格式导出。
|
||||
|
||||
## 数据生成输出格式
|
||||
|
||||
当需要生成数据样本时,默认使用 dataset draft text v1,不要直接输出 JSON、JSONL、CSV 或最终表格格式。除非用户明确要求机器可读格式,否则优先输出便于人工 review 的文本格式。
|
||||
|
||||
### 格式
|
||||
|
||||
```text
|
||||
# dataset_label: 数据集或专题名称
|
||||
|
||||
### case: case名称
|
||||
用户: 本轮 query
|
||||
target: Agent(tag="xxx")
|
||||
notes: 可选,说明覆盖的问题或边界
|
||||
|
||||
### case: 多轮 case 名称
|
||||
用户: 前一轮 query
|
||||
小爱: 前一轮 tts
|
||||
用户: 本轮 query
|
||||
target: Agent(tag="xxx")
|
||||
notes: 可选,说明覆盖的问题或边界
|
||||
```
|
||||
|
||||
### 规则
|
||||
|
||||
- 每条数据用一个 `### case:` 开始。
|
||||
- `用户:` 表示用户 query。
|
||||
- `小爱:` 表示小爱回复 tts。
|
||||
- 最后一个 `用户:` 是本轮 query。
|
||||
- `target:` 是本轮 query 对应的监督标签,必须存在。
|
||||
- 单轮数据只需要写一行 `用户:`,然后写 `target:`。
|
||||
- 多轮数据需要按照时间顺序写多组 `用户:` / `小爱:`。
|
||||
- 多轮数据的最后一轮只写 `用户:` 和 `target:`,不要写最后一轮 `小爱:`,因为本轮 query 不包含 tts。
|
||||
- 如果 `target` 无法确定,不要编造,必须向用户确认。
|
||||
- 不要手写 `record_id`、`request_id`、`timestamp`、`context`。
|
||||
- 线上挖掘数据如有真实 `request_id` 和 `timestamp`,可以附加在 case 中;没有则不写。
|
||||
|
||||
## Canonical Record
|
||||
|
||||
`data_agent_normalize_dataset_draft` 会把 dataset draft text 转成 canonical records,并统一补齐 `record_id`、`source`、`timestamp`、`context`、`target_type` 等机械字段。
|
||||
|
||||
canonical records 落盘必须使用 `data_agent_export_dataset_records`,默认格式是紧凑 JSONL:一行一个 canonical record,不带外层数组,不手写缩进 JSON。默认 `output_path` 固定传 `output/records.jsonl`;不要传 `output/<数据集名>/records.jsonl`,不要传 `tasks/...`,不要自定义文件名。只有用户明确要求兼容旧文件时,才使用 `output_format="json"` 导出紧凑 JSON 数组,此时工具会归一为 `output/records.json`。
|
||||
|
||||
当前 canonical record v1 工作格式:
|
||||
|
||||
```json
|
||||
{
|
||||
"record_id": "gen_aabbccdd_000001",
|
||||
"source": {
|
||||
"type": "generated",
|
||||
"request_id": "aabbccdd",
|
||||
"timestamp": 1755567930500
|
||||
},
|
||||
"turn": {
|
||||
"query": "本轮 query",
|
||||
"timestamp": 1755567930500
|
||||
},
|
||||
"prev_session": [
|
||||
{
|
||||
"query": "前一轮 query",
|
||||
"tts": "前一轮 tts",
|
||||
"timestamp": 1755567870500
|
||||
}
|
||||
],
|
||||
"context": {},
|
||||
"label": {
|
||||
"dataset_label": "数据集或专题名称",
|
||||
"target": "Agent(tag=\"xxx\")",
|
||||
"target_type": "agent"
|
||||
},
|
||||
"meta": {
|
||||
"case_name": "case名称",
|
||||
"notes": ""
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 场景工作流
|
||||
|
||||
### 文件定义型
|
||||
|
||||
1. 使用 `data_agent_load_input_sources` 读取用户提供的文件。
|
||||
2. 如果用户只描述了文件名或主题但没给路径,先询问路径,不要猜。
|
||||
3. 使用 `data_agent_render_source_context` 文本化输入资料,必要时用 `focus_keywords` 缩小到 query、功能点、标签、badcase 相关内容。
|
||||
4. 大模型基于 evidence text 总结 query 语义、功能点、边界、正例、反例、冲突点和缺失假设。
|
||||
5. 把标签边界整理为 `generation_goal.target_definitions`。
|
||||
6. 如果文档里没有明确 target 格式,向用户确认,例如 `Agent(tag="xxx")` 还是 function 调用格式。
|
||||
7. 调用 `data_agent_prepare_generation_goal`,由工具暂停等待用户 review;用户确认后再调用 `data_agent_confirm_generation_goal` 和 `data_agent_prepare_generation_plan`。
|
||||
|
||||
### 手写规则型
|
||||
|
||||
1. 直接从用户描述里抽取标签边界。
|
||||
2. 多标签边界必须使用 `target_definitions`,不要拆成多个无关单标签计划。
|
||||
3. 识别规则中的冲突词、优先级和反例。
|
||||
4. 如果用户已经给出完整 target 表达,直接写入 `target_definitions`;如果 target 表达不明确,先提出问题。
|
||||
5. 如果数量、轮次或输出路径未指定,可以由模型给出保守建议,走一次确认模式;不要为了这些默认参数单独多问一轮。
|
||||
6. 调用 `data_agent_prepare_generation_plan`,传入 `direct_review=true`,让用户一次确认目标和生成参数。
|
||||
|
||||
### 示例归纳型
|
||||
|
||||
1. 先把 example query / badcase 按意图和可能标签分组。
|
||||
2. 输出边界归纳和不确定点,不要马上生成数据。
|
||||
3. 如果 query 没有明确正确标签,必须向用户确认标签或允许的 target 集合。
|
||||
4. 用户确认后,整理为 `generation_goal.target_definitions` 和 `generation_goal.coverage`。
|
||||
5. 调用 `data_agent_prepare_generation_goal` 展示 `generation_goal` 给用户 review;用户确认后再调用 `data_agent_confirm_generation_goal` 和 `data_agent_prepare_generation_plan`。
|
||||
|
||||
## Generation Goal 草案格式
|
||||
|
||||
大模型完成产品定义或 badcase 分析后,先输出下面的草案给用户 review:
|
||||
|
||||
```json
|
||||
{
|
||||
"dataset_label": "数据集或专题名称",
|
||||
"goal_summary": "这批数据要解决什么问题",
|
||||
"target_definitions": [
|
||||
{
|
||||
"name": "标签名称",
|
||||
"target": "Agent(tag=\"xxx\") 或 function 调用",
|
||||
"rule": "哪些 query 应该进入这个标签",
|
||||
"positive_examples": [],
|
||||
"negative_examples": [],
|
||||
"boundary_notes": [],
|
||||
"source_refs": []
|
||||
}
|
||||
],
|
||||
"plan_hint": "建议先生成 50 条单轮,输出到 output/records.jsonl;具体数量和轮次在 generation plan 中确认。",
|
||||
"coverage": "需要覆盖的 query 语义、功能点、错误类型",
|
||||
"exclusions": "不要生成或需要排除的表达",
|
||||
"open_questions": [],
|
||||
"source_refs": []
|
||||
}
|
||||
```
|
||||
|
||||
`generation_goal` 只确认“做什么数据、为什么做、标签边界是什么”。不要在 goal 中维护结构化的 `total_count`、`turn_mix` 或 `output_path`;这些字段属于后续 `generation_plan`。如果需要在 goal review 阶段提示执行方向,只写一句 `plan_hint`,例如“建议先生成 50 条单轮,输出到 output/records.jsonl;具体数量和轮次在 generation plan 中确认”。
|
||||
|
||||
如果未来增加 `data_agent_validate_generation_goal`,它只做结构校验和缺失字段提示,不做语义判断,不替代用户 review,也不替代 `data_agent_prepare_generation_plan`。
|
||||
|
||||
现在已经有代码级 goal review 门禁:不要手写 goal 后直接进入 plan,必须先调用 `data_agent_prepare_generation_goal`,并在用户确认后用 `data_agent_confirm_generation_goal` 取得 `confirmed_goal_id`。
|
||||
|
||||
## 计划中的专用工具
|
||||
|
||||
当前前链路只保留三个工具。不要再假设有 `data_agent_load_definition_source`、`data_agent_extract_target_definitions` 这类更细工具。
|
||||
|
||||
### `data_agent_load_input_sources`
|
||||
|
||||
读取目录或文件,统一抽取 `xlsx/csv/docx/pdf/txt/md/json/jsonl` 的段落、表格预览、行数据样例和 source refs。
|
||||
|
||||
### `data_agent_render_source_context`
|
||||
|
||||
把 `data_agent_load_input_sources` 的结构化结果渲染成大模型可读的 evidence text。产品定义/PRD/走查文档的语义理解应该基于这个文本由大模型完成,不要依赖程序规则直接抽语义。
|
||||
|
||||
### `data_agent_extract_case_evidence`
|
||||
|
||||
从 badcase、评测表、走查表里识别 query、上下文、预期标签、模型预测、类型和备注。字段不明确时,它会返回 `required_questions`,此时必须向用户确认字段含义。
|
||||
|
||||
## 当前可用工具
|
||||
|
||||
- `read_file`:读取用户提供的产品定义、标签定义、样例 query 文件。
|
||||
- `write_file`:在用户确认后落盘 draft、校验结果或说明文档;不要用它手写 canonical records 文件。
|
||||
- `edit_file`:修改已有的计划、说明文档或生成结果文件。
|
||||
- `grep_search`:在项目中搜索已有标签定义、历史数据样例或相关文档。
|
||||
- `glob_search`:按路径模式查找定义文件、样例文件或历史产物。
|
||||
- `ask_user_question`:需要用户明确选择或补充关键信息时使用;如果不可用,就用普通回复提问并停止。
|
||||
- `data_agent_load_input_sources`:读取用户给的目录或文件,把 docx/xlsx/pdf 等输入统一抽成段落、表格和 source refs。
|
||||
- `data_agent_render_source_context`:把结构化输入渲染成大模型可读文本,支持 `max_chars`、表格行数和关键词过滤,用于后续模型语义抽取。
|
||||
- `data_agent_extract_case_evidence`:从 badcase/评测/走查表中抽取 query、预期标签、模型预测、上下文和备注;字段歧义会返回需要确认的问题。
|
||||
- `data_agent_prepare_generation_goal`:在已经整理出 `dataset_label`、`target_definitions`、`plan_hint`、`coverage`、`exclusions`、`source_refs` 后,创建待 review 的 generation goal;调用后本轮会暂停等待用户 review。
|
||||
- `data_agent_confirm_generation_goal`:用户明确确认 generation goal 后使用,获取 `confirmed_goal_id`。
|
||||
- `data_agent_prepare_generation_plan`:在 generation goal 已确认后创建待 review 的生成计划;必须传入 `confirmed_goal_id`,调用后本轮会暂停等待用户 review。
|
||||
- `data_agent_show_generation_plan`:用户要求查看当前计划,或继续上下文时需要恢复计划详情时使用。
|
||||
- `data_agent_update_generation_plan`:用户对计划提出修改意见后使用,更新计划并重新展示。
|
||||
- `data_agent_confirm_generation_plan`:用户明确确认当前计划版本后使用,获取 `confirmed_plan_id`。
|
||||
- `data_agent_normalize_dataset_draft`:用户确认计划后,把 dataset draft text v1 转成 canonical records;必须传入 `confirmed_plan_id`。
|
||||
- `data_agent_validate_dataset_records`:对 canonical records 做结构、标签、时间戳和多轮上下文校验。
|
||||
- `data_agent_export_dataset_records`:校验 canonical records 并落盘;默认写紧凑 JSONL,一行一条,固定传 `output/records.jsonl`,不要再用 `write_file` 手写 records 文件。
|
||||
|
||||
## 约束
|
||||
|
||||
- 不要静默解决产品或标签歧义。
|
||||
- 如果 `ask_user_question` 不可用,使用普通回复向用户提问并停止,不要自己替用户确认。
|
||||
- canonical records 通过校验前,不要生成最终导出格式。
|
||||
- canonical records 需要落盘时,必须用 `data_agent_export_dataset_records`;不要自己拼接 JSON/JSONL;不要创建数据集子目录或自定义 records 文件名。
|
||||
- 除非用户明确要求,否则不要把“修改标签定义”和“生成数据”混在一起做。
|
||||
- 不要因为用户说“生成一些数据”就跳过边界总结和 generation plan review。
|
||||
@@ -0,0 +1,68 @@
|
||||
---
|
||||
name: update-config
|
||||
description: Configure settings via settings.json - hooks, permissions, env vars.
|
||||
when_to_use: When the user wants to configure hooks, permissions, or settings.
|
||||
aliases: config-help
|
||||
allowed_tools: read_file
|
||||
---
|
||||
|
||||
Help configure the agent settings.
|
||||
|
||||
## Settings File Locations
|
||||
|
||||
- Global: `~/.claude/settings.json` applies to all projects.
|
||||
- Project: `.claude/settings.json` is project-specific and committed to git.
|
||||
- Local: `.claude/settings.local.json` is project-specific and gitignored.
|
||||
|
||||
## Configurable Settings
|
||||
|
||||
### Hooks
|
||||
|
||||
Event-driven shell commands that run on tool use or lifecycle events:
|
||||
|
||||
- `PreToolUse` runs before a tool executes and can block with exit code 2.
|
||||
- `PostToolUse` runs after a tool completes.
|
||||
- `PreCompact` runs before conversation compaction.
|
||||
|
||||
Hook format:
|
||||
|
||||
```json
|
||||
{
|
||||
"hooks": {
|
||||
"PreToolUse": [
|
||||
{
|
||||
"matcher": "Bash",
|
||||
"hooks": [
|
||||
{
|
||||
"type": "command",
|
||||
"command": "echo 'tool: $TOOL_NAME'"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Permissions
|
||||
|
||||
Tool permission rules:
|
||||
|
||||
```json
|
||||
{
|
||||
"permissions": {
|
||||
"allow": ["Read", "Grep", "Glob"],
|
||||
"deny": ["Bash(rm:*)"]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Environment Variables
|
||||
|
||||
```json
|
||||
{
|
||||
"env": {
|
||||
"MY_VAR": "value"
|
||||
}
|
||||
}
|
||||
```
|
||||
@@ -0,0 +1,28 @@
|
||||
---
|
||||
name: verify
|
||||
description: Verify a code change works by running the app and tests.
|
||||
when_to_use: When the user asks to verify, test, or check that recent changes work.
|
||||
allowed_tools: read_file, bash, grep_search, glob_search
|
||||
---
|
||||
|
||||
Verify that the recent code changes work correctly.
|
||||
|
||||
## Instructions
|
||||
|
||||
1. Identify what was changed by checking `git diff` and `git status`.
|
||||
2. Determine the appropriate verification strategy:
|
||||
- Unit tests: run existing tests and check for failures.
|
||||
- Integration tests: run broader test suites if available.
|
||||
- Manual verification: start the app/server and test the feature when needed.
|
||||
3. Run the verification.
|
||||
4. Report the result clearly:
|
||||
- PASS: all checks passed and the feature works as expected.
|
||||
- FAIL: describe what failed and why.
|
||||
- PARTIAL: some checks passed and some still need attention.
|
||||
|
||||
## Verification Strategy
|
||||
|
||||
- For CLI tools: run the command with test inputs.
|
||||
- For servers: start the server and make test requests.
|
||||
- For libraries: run the test suite.
|
||||
- For config changes: validate that the config loads correctly.
|
||||
Reference in New Issue
Block a user