GPU_GUARD_MONOREPO/docs/superpowers/specs/2026-05-04-vehicle-damage-inspection-skill-design.md
2026-05-20 21:39:12 +08:00

680 lines
25 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 汽车环车视频旧伤检测 Skill 设计
> 日期2026-05-04
> 状态Draft
> 范围:新增一个业务级 compute-entry Skill用于从环车视频中抽帧、检测漆面旧伤、定位标注损伤区域并筛选最佳证据帧。
> 部署约束:生产形态是 Windows 安装包,本设计必须遵守后端 `dataDir`、bundled skills 复制、可写目录和托盘运行时边界。
## 1. 背景与目标
RZYX_AI_MONOREPO 当前以 NetaClaw Skill Runtime 为核心承载可扩展业务能力。Skill 已支持 `SKILL.md``skill.config.yaml`、skill scoped secrets、`execute_skill` 和 stdin/stdout JSON 协议。
本设计新增一个独立业务 Skill**汽车环车视频旧伤检测**。它不依赖旧 AI_flow 后端的 `BaseSkill`、LangChain tool、Midway service 注入或旧 progress emitter而是按新项目 Skill Runtime 的 compute-entry 机制实现。
目标:
- 输入一个环车视频文件,输出结构化旧伤检测结果。
- 自动抽帧并保留每帧时间戳。
- 分批调用多模态模型识别车身漆面旧伤。
- 对候选损伤调用支持 grounding/bbox 的视觉模型定位区域。
- 在图片上绘制标注框,筛选每处损伤的最佳证据帧。
- 所有模型、API Key、API URL、workspace 根目录等运行配置都从 `skill.config.yaml` env/schema 或输入参数读取,不在代码里硬编码密钥。
- Windows 安装包生产环境下,所有运行产物只能写入后端 `dataDir` 下的可写目录,不能写入安装目录、源码目录或 bundled skill 原始目录。
非目标:
- 第一版不做前端专用结果卡片。
- 第一版不做实时进度推送。
- 第一版不训练 YOLO/SAM 等专用模型。
- 第一版不生成正式检测报告 PDF/HTML。
- 第一版不做人工复核工作流。
## 2. 总体形态
采用 **一个对外大 Skill + 内部模块化流水线** 的结构。
Agent 只调用一次:
```json
{
"name": "vehicle-damage-inspection",
"input": {
"videoUrl": "/upload/20260504/car.mp4",
"fps": 3,
"topN": 1
}
}
```
Skill 内部固定按协议执行:
```text
输入视频
-> frame_extractor 抽帧
-> damage_detector 环车旧伤候选检测
-> damage_grounding 损伤 bbox 定位和画框
-> best_frame_selector 去重和最佳帧筛选
-> 输出最终 JSON
```
选择大 Skill 的原因:
- 视频旧伤检测是垂直业务流程,不是通用 Agent 工具。
- 让 Agent 连续调用四个 Skill 并传递 `taskId` 会增加失败点。
- 固定流水线更稳定,便于测试、重跑和后续替换局部模块。
- 内部模块保持独立,未来仍可拆出单独 Skill。
## 3. Skill 目录结构
参考 `packages/backend/skills/minimax-pdf` 的组织方式,但本 Skill 以 compute-entry 为主,额外保留 `make.sh` 方便人工调试。
```text
packages/backend/skills/vehicle-damage-inspection/
├── SKILL.md
├── skill.config.yaml
├── README.md
├── prompts/
│ ├── damage_detect.md
│ ├── grounding.md
│ ├── dedup.md
│ └── best_frame.md
└── scripts/
├── index.cjs
├── make.sh
├── setup.ps1
└── lib/
├── workspace.cjs
├── frame_extractor.cjs
├── vision_client.cjs
├── damage_detector.cjs
├── damage_grounding.cjs
├── best_frame_selector.cjs
├── image_marker.cjs
└── json_utils.cjs
```
职责边界:
| 文件 | 职责 |
| --- | --- |
| `SKILL.md` | 面向 Agent 的能力说明、触发场景、输入输出摘要 |
| `skill.config.yaml` | runtime、entrypoint、依赖、env、接口 schema |
| `scripts/index.cjs` | stdin/stdout JSON 入口,编排完整流水线 |
| `scripts/make.sh` | Git Bash / POSIX 本地 `check/run/demo` 调试入口 |
| `scripts/setup.ps1` | Windows 安装器 setup 入口;当前 installer 在 win32 下使用 `powershell -File` 执行 setup |
| `workspace.cjs` | 创建 task workspace、读写 JSON、解析 `/upload/` 路径 |
| `frame_extractor.cjs` | ffmpeg/ffprobe 抽帧与视频元信息 |
| `vision_client.cjs` | OpenAI-compatible vision API 调用、重试、JSON/bbox 解析 |
| `damage_detector.cjs` | 分批读取帧,识别候选旧伤 |
| `damage_grounding.cjs` | 读取候选旧伤,找邻近帧,调用 grounding 模型 |
| `image_marker.cjs` | 用 sharp 绘制 bbox 和标签 |
| `best_frame_selector.cjs` | 损伤去重、最佳帧模型筛选、启发式兜底 |
## 4. skill.config.yaml
第一版使用 Node runtime避免 Python venv 要求;依赖通过 skill 的 `dependencies.node.packages` 声明。生产 Windows 安装包应在打包或安装阶段预置 Node 依赖,不把现场 `npm install` 作为正常运行路径;`setup` 只作为安装/修复入口。
```yaml
runtime: node
entrypoint: scripts/index.cjs
timeout: 900000
dependencies:
system:
- name: ffmpeg
check: "ffmpeg -version"
- name: ffprobe
check: "ffprobe -version"
- name: node
check: "node --version"
node:
packages:
- axios
- sharp
- fluent-ffmpeg
- "@ffmpeg-installer/ffmpeg"
- "@ffprobe-installer/ffprobe"
setup:
posix: scripts/make.sh fix
win32: scripts/setup.ps1
env:
- name: ARK_API_KEY
required: true
description: 火山方舟/豆包 OpenAI-compatible API Key
- name: ARK_API_URL
required: false
default: https://ark.cn-beijing.volces.com/api/v3/chat/completions
description: OpenAI-compatible chat completions endpoint
- name: DAMAGE_DETECT_MODEL
required: true
default: doubao-seed-2-0-pro-260215
description: 用于环车旧伤候选检测的多模态模型 ID
- name: DAMAGE_GROUNDING_MODEL
required: true
default: doubao-seed-2-0-pro-260215
description: 支持 bbox/grounding 输出的视觉模型 ID当前默认豆包模型已支持 bbox 和定位
- name: BEST_FRAME_MODEL
required: false
default: doubao-seed-2-0-pro-260215
description: 用于损伤去重和最佳帧筛选的模型 ID未配置时复用 DAMAGE_DETECT_MODEL
- name: RZYX_AI_WORKSPACE_ROOT
required: false
description: 检测产物 workspace 根目录;生产环境由宿主注入,默认应为 dataDir/workspace/vehicle-damage-inspection
- name: RZYX_AI_UPLOAD_ROOT
required: false
description: 后端 /upload 路径对应的本地 uploads 根目录;生产环境由宿主注入,默认应为 dataDir/uploads
- name: RZYX_AI_DATA_DIR
required: false
description: 后端运行时 dataDir由 Windows 安装包/runtime 注入skill 可据此推导 workspace/uploads 默认路径
interface:
input:
videoUrl:
type: string
required: true
description: 视频本地路径或 /upload/... 路径;第一版不支持 http(s) URL
taskId:
type: string
required: false
description: 可选任务 ID未传则自动生成
fps:
type: number
required: false
default: "3"
description: 抽帧帧率
quality:
type: number
required: false
default: "90"
description: 输出 JPG 质量,范围 1-100
batchSize:
type: number
required: false
default: "12"
description: 每批发送给检测模型的帧数
concurrency:
type: number
required: false
default: "2"
description: 多模态 API 并发数
groundingWindow:
type: number
required: false
default: "1.5"
description: 每个损伤时间点前后取帧窗口,单位秒
topN:
type: number
required: false
default: "1"
description: 每处损伤返回的最佳证据帧数量
mode:
type: string
required: false
default: full
description: full | frames-only | detect-only用于调试或重跑部分流程
output:
success:
type: boolean
taskId:
type: string
workspacePath:
type: string
summary:
type: object
damages:
type: array
artifacts:
type: object
```
模型配置原则:
- 代码不写死 API Key、模型 ID 或业务密钥。
- `skill.config.yaml env` 是模型和密钥 schema 的单一来源。宿主需要把 `skill.config.yaml` 中的 env 声明同步到 `NetaClawSkillEntity.envSchema`,这样管理页、默认模型和 `SkillSecretService.resolveEnv()` 使用同一份配置;不要要求在 `SKILL.md metadata.env` 中重复声明。
- 当前默认模型为 `doubao-seed-2-0-pro-260215`。该豆包模型支持多模态理解、bbox 和定位能力,因此第一版可同时作为候选旧伤检测、损伤 grounding 和最佳帧筛选模型使用。
- `DAMAGE_DETECT_MODEL` 负责从视频帧中识别候选旧伤,默认 `doubao-seed-2-0-pro-260215`
- `DAMAGE_GROUNDING_MODEL` 负责输出 bbox默认 `doubao-seed-2-0-pro-260215`;它应返回 `<bbox>x1 y1 x2 y2</bbox>` 或等价结构化定位。
- `BEST_FRAME_MODEL` 可选,未配置时复用检测模型。
- 所有模型调用使用同一个 `ARK_API_URL``ARK_API_KEY`。第一版用一个豆包模型即可跑通;后续如需要按阶段切换模型,再通过 env 覆盖模型 ID。
运行目录配置原则:
- 生产环境必须由宿主运行时注入 `RZYX_AI_DATA_DIR`,并由 Skill 推导:
- `RZYX_AI_WORKSPACE_ROOT = {RZYX_AI_DATA_DIR}/workspace/vehicle-damage-inspection`
- `RZYX_AI_UPLOAD_ROOT = {RZYX_AI_DATA_DIR}/uploads`
- 管理端仍允许用 skill secrets 覆盖这些路径,但默认不要求用户手填。
- 开发态没有 `RZYX_AI_DATA_DIR` 时,才允许 fallback 到 skill 目录下 `.workspace`
## 5. 输入输出协议
### 5.1 输入
`scripts/index.cjs` 从 stdin 读取 JSON
```json
{
"videoUrl": "/upload/20260504/car.mp4",
"taskId": "optional-task-id",
"fps": 3,
"quality": 90,
"batchSize": 12,
"concurrency": 2,
"groundingWindow": 1.5,
"topN": 1,
"mode": "full"
}
```
### 5.2 输出
stdout 只输出一个 JSON 对象:
```json
{
"success": true,
"taskId": "20260504-abc",
"workspacePath": "C:/ProgramData/RZYX-AI/data/workspace/vehicle-damage-inspection/20260504-abc",
"summary": {
"duration": 42.3,
"resolution": "1920x1080",
"frameCount": 127,
"candidateDamageCount": 5,
"mergedDamageCount": 3,
"bestFrameCount": 3
},
"damages": [
{
"id": "dmg_001",
"part": "左前门",
"type": "划痕",
"severity": "轻微",
"description": "左前门中部可见细长白色划痕",
"timestamps": [12.4, 12.7],
"bestFrames": [
{
"timestamp": 12.4,
"path": "C:/.../marked_frames/dmg_001_12.40s.jpg",
"relativePath": "marked_frames/dmg_001_12.40s.jpg",
"bbox": { "x1": 120, "y1": 340, "x2": 280, "y2": 390 }
}
]
}
],
"artifacts": {
"videoInfo": "video_info.json",
"damageCandidates": "damage_candidates.json",
"damageAnnotations": "damage_annotations.json",
"bestFrames": "best_frames.json"
}
}
```
失败时:
```json
{
"success": false,
"error": "视频文件不存在: ...",
"taskId": "..."
}
```
stderr 可输出人类可读日志,但不能输出最终 JSON以免 `SkillExecutorService` 解析 stdout 失败。
## 6. Workspace 协议
每次运行创建或复用一个 task workspace
```text
workspace/{taskId}/
├── source/
│ └── input.mp4
├── frames/
│ └── frame_000001_0.33s.jpg
├── marked_frames/
│ └── dmg_001_12.40s.jpg
├── video_info.json
├── damage_candidates.json
├── damage_annotations.json
├── best_frames.json
└── run_summary.json
```
生产环境 workspace 根目录必须位于后端 dataDir 下:
```text
{dataDir}/workspace/vehicle-damage-inspection/{taskId}/
```
Windows 安装包场景下,后端 dataDir 解析顺序由 `comm/data-dir.ts` 决定:外部 `config.yaml``data.dir``NETA_DATA_DIR`、pkg 模式 `<exe-dir>/data`。本 Skill 不直接 import 后端 `pDataPath()`,而是通过宿主注入的 env 使用该目录。
开发态 fallback
```text
packages/backend/skills/vehicle-damage-inspection/.workspace
```
该 fallback 仅用于本地调试。生产运行如果既没有 `RZYX_AI_DATA_DIR`,也没有 `RZYX_AI_WORKSPACE_ROOT`Skill 应返回配置错误,而不是默默写入 skill 目录。
`/upload/...` 路径解析策略:
- 如果 `videoUrl` 是存在的本地路径,直接使用。
- 如果 `videoUrl` 匹配 `/upload/<relative>`,生产环境使用 `RZYX_AI_UPLOAD_ROOT/<relative>` 解析。
- 开发态未配置 `RZYX_AI_UPLOAD_ROOT` 时,可从 `RZYX_AI_DATA_DIR/uploads``RZYX_AI_WORKSPACE_ROOT/../../uploads` 推导;生产环境不能依赖推导。
- 第一版明确不支持 http(s) URL。遇到 http(s) 输入时返回 `success:false` 和明确错误;如果后续要支持远程 URL必须新增下载模块把视频下载到 `source/input.mp4`,并实现协议白名单、大小限制、超时、重定向限制和审计日志。
### 6.1 Windows 安装包运行时约束
Windows 安装包部署时,后端 `backend.exe`、托盘程序、配置文件和 bundled skills 都在安装目录附近;真正可写的业务数据必须在 dataDir。
本 Skill 需要遵守以下约束:
- bundled skill 目录只作为模板来源。首次启动时,后端会把 bundled skills 复制到 `{dataDir}/skills`;运行时加载和执行的是 `{dataDir}/skills/vehicle-damage-inspection`
- Skill 代码、prompt、脚本可以位于 `{dataDir}/skills/...`,但检测产物不得写入该目录,避免升级、重装、依赖安装或清理时误删业务数据。
- workspace、下载视频副本、抽帧图片、标注图片、JSON artifact 都写入 `{dataDir}/workspace/vehicle-damage-inspection/{taskId}`
- 上传视频来源优先解析为 `{dataDir}/uploads/...`
- 日志只写 stderr 或 `{dataDir}/logs` 下由宿主管理的日志,不在安装目录创建日志。
- 如需清理过期 workspace应按 dataDir 下的 workspace mtime/manifest 执行,不扫描 skill 代码目录。
- `sharp``fluent-ffmpeg``@ffmpeg-installer/ffmpeg``@ffprobe-installer/ffprobe` 等运行依赖应在打包或安装阶段准备好。生产运行时不依赖联网执行 `npm install``setup.ps1` 只用于安装器或人工修复。
### 6.2 宿主运行时需要补充的 env 注入
当前 `SkillExecutorService` 只注入 env 白名单和 skill secrets。为减少 Windows 安装包配置成本,宿主应在执行 compute-entry skill 时额外注入:
```text
RZYX_AI_DATA_DIR={pDataPath()}
RZYX_AI_WORKSPACE_ROOT={pDataPath()}/workspace/vehicle-damage-inspection
RZYX_AI_UPLOAD_ROOT={pUploadPath()}
```
这些不是密钥,不应要求管理员在 Skill secrets 中手动配置。Skill secrets 只负责模型 API Key、模型 ID 和可选覆盖项。
如果暂时不改 `SkillExecutorService`MVP 实现必须把 `RZYX_AI_WORKSPACE_ROOT``RZYX_AI_UPLOAD_ROOT` 标记为部署必填配置,并在 Windows 安装文档里写清楚。
## 7. 流水线设计
### 7.1 抽帧
`frame_extractor.cjs` 负责:
- 检查视频存在。
- 用 ffprobe 获取 duration、fps、分辨率、视频流信息。
- 用 ffmpeg 按 `fps` 抽帧到 `frames/`
- 文件名包含序号和时间戳。
- 写出 `video_info.json`
抽帧输出:
```json
{
"videoPath": "...",
"videoInfo": {
"duration": 42.3,
"videoFps": 29.97,
"resolution": "1920x1080",
"extractFps": 3,
"extractedFrames": 127
},
"frames": [
{
"index": 0,
"timestamp": 0,
"relativePath": "frames/frame_000001_0.00s.jpg",
"path": "..."
}
]
}
```
第一版不做复杂视频质量过滤。可以保留轻量重复帧/模糊帧过滤接口,但默认关闭,避免误删证据帧。
### 7.2 环车旧伤候选检测
`damage_detector.cjs` 负责:
- 读取 `video_info.json``frames/`
-`batchSize` 分批组装多模态请求。
- 每张图前插入时间戳文本。
- 调用 `DAMAGE_DETECT_MODEL`
- 要求模型只返回 JSON。
- 合并各批结果并写出 `damage_candidates.json`
检测 prompt 重点:
- 只检测车身漆面/外观旧伤:划痕、凹陷、掉漆、裂纹、锈蚀等。
- 反光、污渍、阴影、压缩噪声不应直接判定为损伤。
- 不确定结果标记 `confidence: "low"``uncertain: true`
- 输出位置、类型、严重程度、时间点和证据描述。
候选输出:
```json
{
"totalFrames": 127,
"batches": [
{
"batch": 1,
"frameStart": 0,
"frameEnd": 11,
"status": "success"
}
],
"candidates": [
{
"id": "cand_001",
"timestamp": 12.4,
"part": "左前门",
"type": "划痕",
"severity": "轻微",
"description": "左前门中部可见细长白色划痕",
"confidence": "medium"
}
]
}
```
并发控制:
- 默认 `concurrency=2`,避免视觉模型请求体过大导致限流。
- 对 429/TooManyRequests 做指数退避重试。
- API 请求超时默认 10 分钟。
### 7.3 损伤定位标注
`damage_grounding.cjs` 负责:
- 读取 `damage_candidates.json``video_info.json`
- 对每个 candidate 取 `timestamp ± groundingWindow` 的帧。
- 调用 `DAMAGE_GROUNDING_MODEL`
- 解析 bbox。
-`image_marker.cjs` 画红框和标签。
- 写出 `damage_annotations.json`
bbox 坐标约定:
- 模型输出优先使用 0-1000 归一化坐标。
- 内部统一转换为图片像素坐标后画框。
- 输出 JSON 同时保存 normalized 和 pixel 坐标。
标注输出:
```json
{
"annotations": [
{
"candidateId": "cand_001",
"damageId": "dmg_001",
"part": "左前门",
"type": "划痕",
"severity": "轻微",
"markedFrames": [
{
"timestamp": 12.4,
"sourceRelativePath": "frames/frame_000038_12.40s.jpg",
"markedRelativePath": "marked_frames/dmg_001_12.40s.jpg",
"bbox": {
"normalized": { "x1": 120, "y1": 315, "x2": 280, "y2": 380 },
"pixel": { "x1": 230, "y1": 340, "x2": 538, "y2": 410 }
},
"groundingRaw": "<bbox>120 315 280 380</bbox>"
}
]
}
]
}
```
如果 grounding 模型未返回 bbox
- 仍复制原图到 `marked_frames/`
- `bbox` 为空。
- 标记 `groundingStatus: "no_bbox"`,后续最佳帧筛选降低评分。
### 7.4 最佳帧筛选
`best_frame_selector.cjs` 负责:
- 读取 `damage_annotations.json`
- 对候选损伤做去重合并。
- 对每处损伤的标注帧选择最佳 `topN`
- 写出 `best_frames.json` 和最终 summary。
去重策略:
- 第一优先:调用 `BEST_FRAME_MODEL`,输入候选损伤摘要,让模型输出合并分组。
- 兜底:位置文本相似、损伤类型一致、时间相近的候选合并。
- 去重后生成稳定 `dmg_001``dmg_002` 编号。
最佳帧策略:
- 如果某处损伤候选独立帧数小于等于 3按启发式评分选择每个时间点最好的标注版本。
- 如果候选帧较多,调用模型选择 `topN * 3` 候选,再按原始时间戳去重,最终保留 `topN`
- 模型失败时使用启发式评分。
- 第一版实现必须真实接入 `BEST_FRAME_MODEL` 的去重/筛选调用,不能只保留 env 而完全不用;启发式逻辑只作为失败兜底和少量候选快速路径。
启发式评分因素:
- 有 bbox 的帧优先。
- bbox 面积适中优先。
- bbox 不贴边优先。
- 同一时间戳只保留评分最高的标注版本。
## 8. 错误处理
| 场景 | 行为 |
| --- | --- |
| 输入视频不存在 | 返回 `success:false`,不创建后续 artifact |
| 输入 http(s) URL | 第一版返回 `success:false`,提示仅支持本地路径和 `/upload/...` |
| ffmpeg/ffprobe 缺失 | `make.sh check` 报错;运行时返回可诊断错误 |
| 抽帧为空 | 返回失败,提示视频无法抽帧 |
| 模型配置缺失 | 返回失败,明确指出缺少 env 名称 |
| 生产环境缺少 dataDir/workspace/upload 运行目录 | 返回失败,提示宿主未注入 `RZYX_AI_DATA_DIR` 或必要路径 |
| 某批检测失败 | 记录 batch error继续处理其他批次 |
| 全部检测批次失败 | 返回失败,保留 `video_info.json` 和错误摘要 |
| 未发现损伤 | 返回 `success:true`damageCount 为 0 |
| grounding 部分失败 | 保留无 bbox 标注帧,后续筛选降权 |
| best frame 模型失败 | 使用启发式兜底 |
| stdout JSON 生成异常 | `index.cjs` 捕获后输出标准失败 JSON |
## 9. 与 Agent Runtime 的关系
本 Skill 通过当前 Skill Runtime 执行:
```text
Agent -> execute_skill({ name, input })
-> SkillExecutorService
-> node scripts/index.cjs
-> stdout JSON
-> tool_result
```
ToolResolver 只需要在 Agent 绑定了 `vehicle-damage-inspection` 且工具治理允许 `execute_skill` 时注入执行工具。Agent 不需要看到内部四个模块,也不需要调用 `bash`
为适配 Windows 安装包生产形态,`SkillExecutorService` 需要在执行该 Skill 时提供运行时目录 env。该改动属于宿主能力补强不改变 `execute_skill` 对 Agent 暴露的 schema。
第一版不修改 `SkillExecutorService` 的流式协议。由于视频检测耗时较长,后续可以扩展:
- 子进程 stderr 输出 JSONL progress。
- `SkillExecutorService` 识别 progress 事件并转成 `tool_result_streamed`
- 前端对 `execute_skill` 增加视频检测进度展示。
该扩展不阻塞 MVP。
## 10. 测试策略
### 10.1 单元测试
优先测试纯函数和模块边界:
- `parseJSON()`:兼容 markdown code fence 和文本包裹 JSON。
- `parseBboxes()`:解析 `<bbox>x1 y1 x2 y2</bbox>`
- `workspace`:创建 task、写读 JSON、路径安全。
- `syncSkillConfigEnvSchema()`:从 `skill.config.yaml env` 同步默认模型和密钥 schema 到 DB而不是依赖 `SKILL.md metadata.env`
- `best_frame_selector`:去重兜底、启发式评分、同时间戳去重。
- `image_marker`:给一张 fixture 图片画框,确认输出文件存在。
### 10.2 集成测试
使用小型测试视频:
- `make.sh check` 验证依赖。
- `make.sh run --video fixtures/car.mp4 --mode frames-only` 验证抽帧。
- mock `vision_client` 验证完整流水线不依赖真实 API。
- 有真实模型配置时,手动运行完整视频检测。
### 10.3 验收标准
MVP 完成的判断:
- 后端 skill loader 能识别该 Skill 为 `compute-entry`
- Agent 绑定该 Skill 后能通过 `execute_skill` 调起。
- 输入视频后能生成 `video_info.json``damage_candidates.json``damage_annotations.json``best_frames.json`
- 有损伤视频能输出至少一张红框标注图。
- 无损伤视频能返回 `success:true` 且损伤数为 0。
- 未配置模型密钥时返回明确错误,不出现硬编码密钥。
## 11. 实施顺序
1. 补强 Skill 配置加载:从 `skill.config.yaml env` 同步 `envSchema`,确保默认模型和管理页一致。
2. 创建 Skill 目录、`SKILL.md``skill.config.yaml``README.md`
3. 补强 `SkillExecutorService`:注入 `RZYX_AI_DATA_DIR``RZYX_AI_WORKSPACE_ROOT``RZYX_AI_UPLOAD_ROOT` 这三个非密钥运行时 env。
4. 实现 `scripts/index.cjs` stdin/stdout 协议和统一错误包装。
5. 实现 workspace 和路径解析,生产路径固定落在 dataDir 下,并明确拒绝 http(s) URL。
6. 实现 ffmpeg 抽帧。
7. 实现 vision client、prompt 文件和模型配置读取。
8. 实现候选损伤检测。
9. 实现 grounding 和图片标注。
10. 实现 `BEST_FRAME_MODEL` 参与的去重/最佳帧筛选,以及启发式兜底。
11. 增加 `make.sh check/run/demo` 和 Windows `setup.ps1`
12. 增加必要测试或 mock 验证脚本。
13. 用 pkg/Windows 安装包路径模拟验证:确认 workspace/uploads/logs 都不写入安装目录或 skill 代码目录。
14. 在技能管理页配置模型 env secrets 后进行真实视频手测。
## 12. 风险与后续扩展
### 12.1 主要风险
- 多模态模型容易把反光、污渍、阴影误判为旧伤。
- 一次发送过多帧会降低模型注意力并增加请求失败概率。
- grounding 模型能力不稳定时bbox 可能偏移或为空。
- 长视频会导致抽帧和模型调用成本高。
- 当前 `execute_skill` 不支持流式进度,用户体验可能是长时间等待。
### 12.2 缓解策略
- 默认低 fps 和小 batch。
- prompt 明确要求不确定就标记 uncertain不强行报损伤。
- 对 bbox 失败保留原图和状态,避免流程中断。
- 保留 `mode` 参数支持分阶段调试。
- 输出完整 artifact方便人工复核和后续重跑。
### 12.3 后续扩展
- 增加 progress JSONL 协议。
- 增加前端专用结果卡片。
- 增加人工复核和修正 bbox。
- 增加报告生成 Skill 或复用文档生成 Skill。
- 累积标注数据后训练专用漆面损伤检测模型。