mcpField
V
vss-generate-video-report

vss-generate-video-report

github.com/promptingcompany数据与数据库039 次安装17 周前更新
代码仓库

关于 vss-generate-video-report

vss-generate-video-report 是用于生成视频分析报告的技能,面向 VSS 部署工作流。它根据请求选择单段视频分析或时间范围内的事件汇总,并将结果整理为 Markdown 报告。

主要功能

  1. 01按请求选择报告模式
  2. 02调用视频片段分析后端
  3. 03查询时间范围内的事件
  4. 04生成 Markdown 报告
  5. 05改写报告中的片段链接

适用场景

  1. 01为录制的视频片段生成分析报告
  2. 02汇总指定时间范围内的告警事件
  3. 03按传感器范围整理事件报告

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/promptingcompany/nv-skills --skill vss-generate-video-report

安装后在 Claude Code 中输入/vss-generate-video-report确认已加载。

包含的文件

除 SKILL.md 外,安装时一起下载的参考资料和脚本。

BENCHMARK.md
skill-card.md
skill.oms.sig
assets/
incident-range-report.md
video-analysis-report.md
evals/
base_profile_report.json
evals.json

详细介绍

报告

通过路由到以下两个后端之一来生成视频分析报告——绝不可通过 VSS Agent 上的 POST /generate。

模式后端
A. 视频片段/vss-manage-video-io-storage → 片段 URL → VLM chat/completions
B. 事件时间范围/vss-query-analytics → 事件列表 → 叙述式报告

如果请求含糊不清(例如“报告一下 <sensor>”,但未提供时间范围,也未提及事件),默认使用 模式 A。只有当用户同时提及传感器和时间范围时才询问。有关会路由到各模式的请求表述,请参阅下方的示例。


说明

  1. 选择模式——针对单个录制片段/传感器视频使用模式 A;请求中指定了时间范围或事件/警报时使用模式 B(参照示例)。
  2. 验证部署配置是否满足该模式的要求,参阅部署前提条件;如果探测失败,则交接给 /vss-deploy-profile。
  3. 执行该模式的编号步骤——参阅下方的模式 A或模式 B。
  4. 在报告中嵌入片段 URL 之前,使用 $VSS_PUBLIC_HOST:$VSS_PUBLIC_PORT 单行命令(浏览器可播放的片段 URL)重写所有面向用户的片段 URL。
  5. 将渲染后的报告 Markdown 返回给用户。

评估器的输出约定:

  • 模式 A 的顶层标题必须严格为 # Video Analysis Report。
  • 模式 B 的顶层标题必须严格为 # Incident Range Report(不得使用 # Incident Report 或包含传感器名称的变体)。
  • 模式 B 必须包含 ## Basic Information,并使用模板中要求的确切行(Report Identifier、Range、Scope、Total Incidents、Confirmed / Rejected / Unverified)。

示例

  • “为此视频生成报告” / “报告一下 <sensor-id>” → 模式 A
  • “分析 warehouse_01.mp4” / “为上传的视频创建分析报告” → 模式 A
  • “报告 12:31Z 到 12:32Z 期间的事件” → 模式 B
  • “报告今天的警报” / “<sensor> 上一小时发生了什么事件” → 模式 B
  • “汇总 <sensor> 在 <t1> 到 <t2> 之间的警报” → 模式 B

不适用的情形

如果请求属于以下任一情形,不要使用此技能:

  • 对片段进行临时视觉问答,且未明确要求生成报告(“卡车是什么颜色?”、“00:12 时发生了什么?”)→ 使用 /vss-ask-video。
  • 档案/语义相似度检索(“查找叉车”、“搜索所有视频中的尾随行为”)→ 使用 /vss-search-archive。
  • 只读查询事件/指标,无需渲染报告 → 使用 /vss-query-analytics。
  • 部署/拆除/配置变更(“部署警报”、“切换配置”、“启动基础配置”)→ 使用 /vss-deploy-profile。
  • 实时警报/规则管理请求 → 使用 /vss-manage-alerts。

绝不要通过 VSS Agent 的 POST /generate 路由报告。


部署前提条件

模式 A 需要 VSS base 配置(VST + VLM NIM)。 模式 B 需要 VSS alerts 配置(VA-MCP + Elasticsearch)。

探测:

# Mode A — VST + VLM reachability
curl -sf --max-time 5 "http://${HOST_IP}:30888/vst/api/v1/sensor/version" >/dev/null

# Mode B — VA-MCP
curl -sf --max-time 5 "http://${HOST_IP}:9901/" >/dev/null

如果探测失败,则将请求交接给 /vss-deploy-profile,并使用 -p base(模式 A)或 -p alerts(模式 B)。务必先向用户确认部署。


Clip URL:VLM 输入与浏览器报告链接

VST 返回的 clip URL 使用 Agent 内部的 ${HOST_IP}:30888 主机和端口。 请将原始 URL 保留为 VIDEO_URL,供本地/集群内 VLM 拉取帧使用。 不要仅为了让浏览器能够播放而改写 VLM 输入 URL。

只为渲染报告中显示的 URL 创建 BROWSER_CLIP_URL。部署层会在每个配置文件的 .env 中导出浏览器端的主机和端口 $VSS_PUBLIC_HOST / $VSS_PUBLIC_PORT(以及协议 $VSS_PUBLIC_HTTP_PROTOCOL)——无论是 Brev 还是裸机,因此报告链接的改写方式为:

: "${VSS_PUBLIC_HOST:?Set VSS_PUBLIC_HOST before rewriting clip URLs}"
: "${VSS_PUBLIC_PORT:?Set VSS_PUBLIC_PORT before rewriting clip URLs}"
VSS_PUBLIC_HTTP_PROTOCOL="${VSS_PUBLIC_HTTP_PROTOCOL:-http}"
BROWSER_CLIP_URL=$(echo "$RAW_URL" | sed -E "s|^https?://[^/]+|${VSS_PUBLIC_HTTP_PROTOCOL}://${VSS_PUBLIC_HOST}:${VSS_PUBLIC_PORT}|")

如果缺少任一必需的公共主机值,请省略面向报告的 clip 链接,并指出无法生成可供浏览器播放的 URL;不要阻断本地 VLM 分析路径。对渲染报告中显示的每个 clip URL应用改写(Mode A 的 Step 4 Clip URL 行;Mode B 每个事件的 clip 子项目)。当 VLM 在本地/集群内运行时,Mode A Step 3 中 VLM 的 video_url 内容块应保留原始内部 URL。


Mode A — 生成录制视频 clip 的报告

如果已部署 VSS lvs 配置文件——curl -sf --max-time 5 "http://${HOST_IP}:38111/v1/ready" 返回 HTTP 200——运行 /vss-summarize-video 生成摘要,然后将其输出粘贴到 Step 4 的报告模板中,并跳过 Steps 1–3(直接调用 VLM 的路径)。仅当 /v1/ready 返回非 200 时,才运行 Steps 1–3。

Step 1 — 解析 clip URL

将任务交给 /vss-manage-video-io-storage,以便:

  1. 列出传感器并确认指定的 <sensor-id> 存在(如果不存在,请先上传)。

  2. 如果用户未提供 startTime/endTime,则获取录制时间范围的 /storage/<streamId>/timelines。

  3. 请求 clip URL:

    curl -s "http://${HOST_IP}:30888/vst/api/v1/storage/file/<streamId>/url?startTime=<startTime>&endTime=<endTime>&container=mp4&disableAudio=true" | jq -r .videoUrl
    

    这会生成一个直接的 mp4 URL,本地/集群内 VLM 可以从中拉取帧。将其绑定到 VIDEO_URL(供 Step 3 中的 VLM 使用),并在应用报告链接改写之前设置 RAW_URL="$VIDEO_URL",以生成 Step 4 使用的 BROWSER_CLIP_URL——用户的浏览器无法直接访问 $VIDEO_URL。 Mode A 要求所选 VLM 端点能够获取 VIDEO_URL。 本地 NIM/RT-VLM 部署通常可以;远程端点通常无法获取 localhost、私有 HOST_IP 或 VST 内部 URL。如果当前的 VLM_ENDPOINT 是远程端点,请说明这一可访问性要求,不要发起必然会在 /v1/models 成功后失败的聊天请求。

Step 2 — 解析 VLM 端点和模型

部署可以通过以下两种技术栈之一提供 VLM 服务。两者都提供与 OpenAI 兼容的 chat/completions API——选择当前正在运行的一个:

后端环境变量典型主机端点选择条件
NIM CosmosVLM_BASE_URL、VLM_NAME、VLM_MODE、VLM_MODEL_TYPE${VLM_BASE_URL}/v1(环境变量末尾不带 /v1;Agent 会追加它)VLM_MODEL_TYPE != rtvi 且 VLM_MODE ∈ {local、local_shared、remote} 且 VLM_BASE_URL 非空
RT-VLM CosmosRTVI_VLM_BASE_URL、RTVI_VLM_MODEL_TO_USE、VLM_MODEL_TYPE${RTVI_VLM_BASE_URL}/v1——如果未设置,则根据 ${HOST_IP} 推导(alerts 使用 http://${HOST_IP}:8018/v1,base 使用 http://${HOST_IP}:30082/v1)VLM_MODEL_TYPE = rtvi,或 VLM_MODE=none,或 VLM_BASE_URL 为空;也是 warehouse 的唯一途径

从正在运行的 Agent 容器中读取实际值——不要猜测:

docker exec vss-agent sh -lc '
for k in HOST_IP VLM_MODE VLM_MODEL_TYPE VLM_BASE_URL VLM_NAME RTVI_VLM_BASE_URL RTVI_VLM_MODEL_TO_USE; do
  v="$(printenv "$k")"
  [ -n "$v" ] && printf "%s=%s\n" "$k" "$v"
done
'

不要要求从 vss-agent 环境变量中读取 RTVI_VLM_ENDPOINT;若干配置文件不会注入该变量。

选择规则:

if [ "${VLM_MODEL_TYPE:-}" = "rtvi" ]; then
  VLM_BACKEND="rtvlm"
  VLM_ENDPOINT="${RTVI_VLM_BASE_URL:+${RTVI_VLM_BASE_URL%/}/v1}"
  [ -z "${VLM_ENDPOINT}" ] && VLM_ENDPOINT="http://${HOST_IP}:8018/v1"   # alerts default
  VLM_MODEL="${RTVI_VLM_MODEL_TO_USE}"
elif [ -n "${VLM_BASE_URL}" ] && [ "${VLM_MODE}" != "none" ]; then
  VLM_BACKEND="nim_cosmos"
  VLM_ENDPOINT="${VLM_BASE_URL%/}/v1"
  VLM_MODEL="${VLM_NAME}"
else
  VLM_BACKEND="rtvlm"
  VLM_ENDPOINT="${RTVI_VLM_BASE_URL:+${RTVI_VLM_BASE_URL%/}/v1}"
  [ -z "${VLM_ENDPOINT}" ] && VLM_ENDPOINT="http://${HOST_IP}:30082/v1"  # base default
  VLM_MODEL="${RTVI_VLM_MODEL_TO_USE}"
fi

发送聊天请求前,先探测 /v1/models,确认所选端点正常运行且模型已加载:

curl -sf --max-time 5 "${VLM_ENDPOINT}/models" | jq -r '.data[].id'

如果探测失败,或列出的 ID 不包含 ${VLM_MODEL},则回退到另一个后端(或说明错误——绝不能悄悄选择服务器上不存在的模型)。

Step 3 — 直接调用 VLM

使用兼容 OpenAI 的 chat/completions 端点,并传入 video_url 内容块——其负载结构和多模态设置与 src/vss_agents/tools/video_understanding.py 中 video_understanding 构建的相同(_build_vlm_messages + Cosmos base_vlm.bind(...) 调用)。

帧采样和视觉令牌(像素)预算必须与当前配置的实时 video_understanding 设置保持一致。发送 mm_processor_kwargs 和 media_io_kwargs,确保直接调用使用与 Agent 内部 video_understanding 工具相同的帧采样和像素预算——省略这些参数会让 VLM 使用自身默认值,导致输出偏离 Agent 路径。

PROMPT='Describe in detail what happens in the video, with timestamps (start–end in seconds from clip start) for each segment or event. Cover scenes, objects, people, vehicles, and notable actions.'

# Reasoning is OFF by default — matches the base-profile video_understanding config (`reasoning: false`).
# video_understanding.py uses config.reasoning unless the caller overrides it, so default to non-reasoning.
# Append the Cosmos Reason 2 reasoning suffix ONLY when the user explicitly asks for reasoning
# (drop it for non-cosmos-reason2 VLMs). With reasoning off, the response has no <think> block.
if [ "${REASONING:-false}" = "true" ]; then
PROMPT="${PROMPT}

Answer the question using the following format:

<think>
Your reasoning.
</think>

Write your final answer immediately after the </think> tag."
fi

# If Step 3 is run standalone, derive missing backend from current env/model.
[ -z "${VLM_BACKEND:-}" ] && {
  if [ "${VLM_MODEL_TYPE:-}" = "rtvi" ]; then
    VLM_BACKEND="rtvlm"
  elif [[ "${VLM_MODEL:-}" == nvidia/cosmos* ]]; then
    VLM_BACKEND="nim_cosmos"
  else
    VLM_BACKEND="rtvlm"
  fi
}

# Multimodal settings — resolve from the live agent config file path, not hardcoded candidates.
CFG_JSON=$(
docker exec vss-agent python3 -c '
import json, os, yaml
p = os.getenv("VSS_AGENT_CONFIG_FILE")
if not p:
    raise SystemExit("VSS_AGENT_CONFIG_FILE is not set in vss-agent")
if not os.path.isabs(p):
    p = os.path.join("/vss-agent", p.lstrip("./"))
with open(p, encoding="utf-8") as f:
    cfg = yaml.safe_load(f) or {}
vu = (cfg.get("functions", {}) or {}).get("video_understanding", {}) or {}
print(json.dumps({
    "max_fps": int(vu.get("max_fps", 2)),
    "max_frames": int(vu.get("max_frames", 30)),
    "min_pixels": int(vu.get("min_pixels", 3136)),
    "max_pixels": int(vu.get("max_pixels", 8388608)),
}))
')
)
[ -n "${CFG_JSON}" ] || { echo "Failed to read video_understanding config from vss-agent"; exit 1; }
jq -e . >/dev/null <<< "${CFG_JSON}" || { echo "Invalid config JSON from vss-agent"; exit 1; }
MAX_FPS="$(jq -r '.max_fps' <<< "${CFG_JSON}")"
MAX_FRAMES="$(jq -r '.max_frames' <<< "${CFG_JSON}")"
MIN_PIXELS="$(jq -r '.min_pixels' <<< "${CFG_JSON}")"
MAX_PIXELS="$(jq -r '.max_pixels' <<< "${CFG_JSON}")"

# num_frames = min(int(clip_seconds) * max_fps, max_frames), min 1 — matches video_understanding.py.
# clip_seconds (Step 1 endTime-startTime) may be fractional; truncate to integer seconds — bash $((...))
# is integer-only and errors on "15.0"/"1.5". Default 15s -> caps at MAX_FRAMES.
CLIP_SECONDS=$(awk -v s="${CLIP_SECONDS:-15}" 'BEGIN{printf "%d", s}')
NUM_FRAMES=$(( CLIP_SECONDS * MAX_FPS ))
[ "$NUM_FRAMES" -gt "$MAX_FRAMES" ] && NUM_FRAMES=$MAX_FRAMES
[ "$NUM_FRAMES" -lt 1 ] && NUM_FRAMES=1

# Only apply Cosmos mm/media kwargs on the NIM Cosmos path.
# RT-VLM mode uses its own server-side preprocessing and should not receive these kwargs.
MM_KWARGS=""
if [ "${VLM_BACKEND}" = "nim_cosmos" ]; then
  case "$VLM_MODEL" in
    *cosmos-reason2*) MM_KWARGS=", \"mm_processor_kwargs\": {\"size\": {\"shortest_edge\": ${MIN_PIXELS}, \"longest_edge\": ${MAX_PIXELS}}}, \"media_io_kwargs\": {\"video\": {\"num_frames\": ${NUM_FRAMES}}}" ;;
    *cosmos*)         MM_KWARGS=", \"mm_processor_kwargs\": {\"videos_kwargs\": {\"min_pixels\": ${MIN_PIXELS}, \"max_pixels\": ${MAX_PIXELS}}}, \"media_io_kwargs\": {\"video\": {\"num_frames\": ${NUM_FRAMES}}}" ;;
    *)                      MM_KWARGS="" ;;
  esac
fi

curl -s --connect-timeout 5 --max-time 120 -X POST "${VLM_ENDPOINT}/chat/completions" \
  -H "Content-Type: application/json" \
  -d @- <<EOF | jq -r '.choices[0].message.content'
{
  "model": $(jq -Rs . <<< "${VLM_MODEL}"),
  "messages": [
    {
      "role": "user",
      "content": [
        {"type": "text", "text": $(jq -Rs . <<< "${PROMPT}")},
        {"type": "video_url", "video_url": {"url": $(jq -Rs . <<< "${VIDEO_URL}")}}
      ]
    }
  ],
  "max_tokens": 1024,
  "temperature": 0.0${MM_KWARGS}
}
EOF

kwargs 块会根据后端选择参数:对于 nim_cosmos,Reason2 变体(nvidia/cosmos-reason2*)使用 mm_processor_kwargs.size{shortest_edge,longest_edge},其他 NIM Cosmos 变体(nvidia/cosmos*)使用 mm_processor_kwargs.videos_kwargs{min_pixels,max_pixels};两者都会发送 media_io_kwargs.video.num_frames。对于 rtvlm,不发送 Cosmos kwargs。

如果 VLM 返回 `` 块(Cosmos Reason 推理模式),报告正文只保留 </think> 之后的文本。

Step 4 — 填写视频分析报告模板

复制 assets/video-analysis-report.md,填写所有占位符,并将渲染后的 Markdown 返回给用户。保持源资源文件不变。渲染前,确认 BROWSER_CLIP_URL 已设置且非空,然后在 Clip URL 行中将 <BROWSER_CLIP_URL> 替换为该精确值。输出中绝不能保留占位符,已填写的单元格中不得包含模板说明,也不得使用原始 HOST_IP:30888 URL。


模式 B — 报告某一时间范围内的事件

Step 1 — 确定时间范围和(可选的)传感器
  • start_time / end_time 必须是 ISO 8601 UTC 格式(YYYY-MM-DDTHH:MM:SS.sssZ)。根据当前主机时钟解析相对时间表述(“过去一小时”“今天”)。
  • 如果用户指定了传感器,将其作为 source,并设置 source_type=sensor。否则两者都不设置,查询所有传感器。
Step 2 — 通过 /vss-query-analytics 获取事件

将请求交给 /vss-query-analytics(initialize → tools/call),并传入:

{
  "jsonrpc": "2.0",
  "method": "tools/call",
  "params": {
    "name": "video_analytics__get_incidents",
    "arguments": {
      "source": "<sensor-id-or-omit>",
      "source_type": "sensor",
      "start_time": "<ISO>",
      "end_time": "<ISO>",
      "max_count": 100,
      "includes": ["objectIds", "info"]
    }
  },
  "id": 1
}

只读边界(强制):

  • 模式 B 严格限定为只读分析数据检索。绝不能写入、播种、回填或修改 Elasticsearch/VA 数据。
  • 禁止的示例:编入合成事件、将 fixture 负载重放到 ES,或调用写入/更新/删除 API 来“确保报告有数据”。
  • 如果请求的时间范围/范围内没有事件,将其作为空结果处理(见下文);不得伪造数据。

对于每个事件,保留:id、sensorId、timestamp、end、category、place.name、info.verdict、info.reasoning、objectIds 以及剪辑 URL(通常为 info.clip_url、clip_url,或响应中携带的其他剪辑指针字段)。将 $VSS_PUBLIC_HOST:$VSS_PUBLIC_PORT 改写(见上文 浏览器可播放的剪辑 URL)应用于每个剪辑 URL,再粘贴到报告中——原始值是 HOST_IP:30888 URL,用户的浏览器无法访问。

Step 3 — 填写事件范围报告模板

复制 assets/incident-range-report.md,然后按传感器分组(如果未限定传感器,则按类别分组)、统计判定结果,并列出每个事件的时间戳 / 类别 / 判定结果 / 推理。保持源资源文件不变。每个事件的剪辑值都必须是改写后的浏览器可播放 URL;如果事件不包含剪辑 URL,则省略剪辑行。已填写的单元格中不得包含模板说明。

如果 get_incidents 返回零条结果,立即停止,并仅返回一行空范围说明,其中列出请求的时间范围和范围条件。不要渲染完整的事件范围模板,不要编造事件,不要播种测试数据,也不要回退到模式 A。

错误处理

  • 如果探测、curl、VLM 调用或 /vss-query-analytics 请求失败,请停止工作流,并报告失败的端点、HTTP 状态码或命令错误,以及下一步有用的恢复措施。不要根据不完整或缺失的数据编造报告。
  • 如果 VLM 响应为空、格式错误或仅包含推理内容,请指出响应问题,并建议检查模型是否就绪及相关日志,然后重试。
  • 如果无法将片段 URL 改写为公共主机/端口,请从生成的报告中省略该 URL,并说明无法生成可在浏览器中播放的 URL。
  • 对于 Mode B,请将缺失的可选事件字段(info.reasoning、objectIds、片段 URL)视为报告中的省略项;但缺少 id、timestamp 或 category 应视为数据质量错误并报告。

交叉引用

  • /vss-manage-video-io-storage — 用于 Mode A 第 1 步的传感器列表、时间线和片段 URL。
  • /vss-query-analytics — 用于 Mode B 第 2 步的事件检索(以及 verdict / reasoning 补充信息)。
  • /vss-ask-video — 针对单个片段进行临时 VLM 问答(不是结构化报告)。
  • /vss-summarize-video — 部署 lvs 配置时,Mode A 使用此项生成摘要正文;报告模板(第 4 步)仍在此处填写。

评论

支持 Markdown