vss-generate-video-report

Par nvidia · skills

Utilisez cette skill lors de la production d'un rapport d'analyse VSS — Mode A par clip via VLM, Mode B sur plage d'incident via video-analytics. Non applicable pour les alertes temps réel ou les questions ad hoc.

npx skills add https://github.com/nvidia/skills --skill vss-generate-video-report

Rapport

Générez un rapport d'analyse vidéo en routant vers l'un des deux backends — jamais via POST /generate sur l'agent VSS.

Mode Déclencheur Backend
A. Clip vidéo "report on <sensor>", "report on this video", "analyze warehouse_01.mp4" /vss-manage-video-io-storage → URL du clip → VLM chat/completions
B. Plage d'incidents "report on incidents from <t1> to <t2>", "report on alerts today", "what incidents happened on <sensor> last hour" /vss-query-analytics → liste d'incidents → rapport narratif

Si la requête est ambiguë (par ex. "report on <sensor>" sans plage horaire et sans vocabulaire d'incident), utiliser par défaut le Mode A. Ne poser la question que si l'utilisateur mentionne à la fois un capteur et une plage horaire.


Quand utiliser

  • "Generate a report for this video" / "for <sensor-id>" — Mode A
  • "Create an analysis report on the uploaded video" — Mode A
  • "Report on incidents from 12:31Z to 12:32Z" — Mode B
  • "Summarize alerts on <sensor> between <t1> and <t2>" — Mode B

Prérequis de déploiement

Le Mode A nécessite le profil VSS base (VST + VLM NIM). Le Mode B nécessite le profil VSS alerts (VA-MCP + Elasticsearch).

Sondage :

# 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

Si le sondage échoue, rediriger vers /vss-deploy-profile avec -p base (Mode A) ou -p alerts (Mode B). Toujours confirmer le déploiement auprès de l'utilisateur d'abord.


URL de clip lisible dans le navigateur (toujours faire cela avant d'intégrer un clip au rapport)

VST retourne les URLs de clip en utilisant l'hôte:port interne à l'agent ${HOST_IP}:30888. Celles-ci fonctionnent dans le cluster (VLM frame pulls, agent backend) mais le navigateur de l'utilisateur ne peut pas les atteindre. La couche de déploiement exporte déjà l'hôte:port accessible au navigateur en tant que $VSS_PUBLIC_HOST / $VSS_PUBLIC_PORT (et le schéma en tant que $VSS_PUBLIC_HTTP_PROTOCOL) dans chaque profil .env — Brev ou bare-metal — donc la réécriture est une ligne :

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

L'appliquer à chaque URL de clip affichée dans le rapport généré (Mode A Step 4 Clip URL row ; Mode B par bullet de sous-incident). Laisser le bloc de contenu video_url du VLM en Mode A Step 3 sur l'URL interne d'origine — le VLM est dans le cluster.


Mode A — Rapport sur un clip vidéo enregistré

Si le profil VSS lvs est déployécurl -sf --max-time 5 "http://${HOST_IP}:38111/v1/ready" retourne HTTP 200 — exécuter /vss-summarize-video pour produire le résumé, puis coller sa sortie dans le modèle de rapport à l'étape 4 et sauter les étapes 1–3 (le chemin VLM direct). Exécuter les étapes 1–3 uniquement lorsque /v1/ready est non-200.

Étape 1 — Résoudre l'URL du clip

Rediriger vers /vss-manage-video-io-storage pour :

  1. Lister les capteurs et confirmer que le <sensor-id> nommé existe (télécharger d'abord s'il n'existe pas).

  2. Récupérer /storage/<streamId>/timelines pour la plage enregistrée quand l'utilisateur n'a pas fourni startTime / endTime.

  3. Demander une URL de clip :

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

    Cela donne une URL mp4 directe que le VLM peut extraire des images. La lier à VIDEO_URL (utilisée dans le cluster par le VLM à l'étape 3) et la réécrire en BROWSER_CLIP_URL pour le modèle de rapport de l'étape 4 en utilisant la ligne de commande de URL de clip lisible dans le navigateur ci-dessus — le navigateur de l'utilisateur ne peut pas atteindre $VIDEO_URL directement. Le Mode A exige que le endpoint VLM sélectionné soit capable de récupérer VIDEO_URL. Les déploiements locaux NIM/RT-VLM peuvent généralement le faire ; les endpoints distants généralement ne peuvent pas récupérer localhost, les URLs HOST_IP privées, ou les URLs internes à VST. Si le VLM_ENDPOINT actif est distant, afficher cette exigence de reachability au lieu de faire une requête chat qui échouera après que /v1/models réussisse.

Étape 2 — Résoudre le endpoint VLM et le modèle

Le déploiement peut servir le VLM à travers l'une de deux piles. Les deux exposent une API chat/completions compatible OpenAI — choisir celle qui est active :

Backend Variables d'env Endpoint d'hôte type Choisi quand
NIM Cosmos VLM_BASE_URL, VLM_NAME ${VLM_BASE_URL}/v1 (pas de /v1 à la fin sur la variable d'env ; l'agent l'ajoute) VLM_MODE ∈ {local, local_shared, remote} et VLM_BASE_URL est non-vide
RT-VLM Cosmos RTVI_VLM_BASE_URL, RTVI_VLM_MODEL_TO_USE (identifiant du modèle côté RT-VLM, par ex. cosmos-reason2) ${RTVI_VLM_BASE_URL}/v1 — les alertes par défaut http://${HOST_IP}:8018/v1, base par défaut http://${HOST_IP}:30082/v1 (RTVI_VLM_ENDPOINT) VLM_MODE=none ou VLM_BASE_URL vide ; aussi le seul chemin pour warehouse

Lire les valeurs actives du conteneur agent en cours d'exécution — ne pas deviner :

docker exec vss-agent env | grep -E '^(VLM_BASE_URL|VLM_NAME|VLM_MODE|RTVI_VLM_BASE_URL|RTVI_VLM_ENDPOINT|RTVI_VLM_MODEL_TO_USE)='

Règle de sélection :

if [ -n "${VLM_BASE_URL}" ] && [ "${VLM_MODE}" != "none" ]; then
  VLM_ENDPOINT="${VLM_BASE_URL%/}/v1"
  VLM_MODEL="${VLM_NAME}"
else
  VLM_ENDPOINT="${RTVI_VLM_ENDPOINT:-${RTVI_VLM_BASE_URL%/}/v1}"
  VLM_MODEL="${RTVI_VLM_MODEL_TO_USE}"
fi

Sonder /v1/models avant d'envoyer une requête chat pour confirmer que le endpoint choisi est actif et que le modèle est chargé :

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

Si le sondage échoue ou que les ids listés n'incluent pas ${VLM_MODEL}, basculer vers l'autre backend (ou afficher l'erreur — ne jamais silencieusement choisir un modèle qui n'est pas sur le serveur).

Étape 3 — Appeler le VLM directement

Utiliser le endpoint chat/completions compatible OpenAI avec un bloc de contenu video_url — le même format de payload que video_understanding construit dans src/vss_agents/tools/video_understanding.py (_build_vlm_messages) :

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.'

# Cosmos Reason 2 reasoning prompt suffix — matches video_understanding.py for is_cosmos_reason2 + reasoning=true.
# Drop this suffix for non-cosmos-reason2 VLMs.
PROMPT="${PROMPT}

Answer the question using the following format:

<think>
Your reasoning.
</think>

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

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

Si le VLM retourne un bloc <think>…</think> (mode reasoning Cosmos Reason), conserver uniquement le texte après </think> comme corps du rapport.

Étape 4 — Remplir le modèle de rapport d'analyse vidéo

# Video Analysis Report

## Basic Information

| Field | Value |
|-------|-------|
| **Report Identifier** | vss_report_<YYYYMMDD_HHMMSS> |
| **Date of Analysis** | <YYYY-MM-DD> |
| **Time of Analysis** | <HH:MM:SS> |
| **Video Source** | <sensor_id or filename> |
| **Clip Range** | <startTime> – <endTime> |
| **Clip URL** | `<BROWSER_CLIP_URL>` (apply the `$VSS_PUBLIC_HOST:$VSS_PUBLIC_PORT` rewrite — NEVER paste the raw `HOST_IP:30888` URL here) |
| **VLM** | <VLM_MODEL (NIM or RT-VLM)> |
| **Analysis Request** | <user's request> |

## Analysis Results

<VLM output: timestamped caption / summary>

Retourner le markdown généré à l'utilisateur.


Mode B — Rapport sur les incidents dans une plage horaire

Étape 1 — Résoudre la plage horaire et (optionnellement) le capteur

  • start_time / end_time doivent être au format ISO 8601 UTC (YYYY-MM-DDTHH:MM:SS.sssZ). Résoudre les expressions relatives ("last hour", "today") par rapport à l'horloge du host actuel.
  • Si l'utilisateur nomme un capteur, le capturer en tant que source + source_type=sensor. Sinon laisser les deux non définis pour une requête tous capteurs.

Étape 2 — Récupérer les incidents via /vss-query-analytics

Rediriger vers /vss-query-analytics (initialize → tools/call) avec :

{
  "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"]
  }
}

Pour chaque incident conserver : id, sensorId, timestamp, end, category, place.name, info.verdict, info.reasoning, objectIds, et l'URL du clip (généralement info.clip_url, clip_url, ou quel que soit le champ pointeur de clip que la réponse porte). Appliquer la réécriture $VSS_PUBLIC_HOST:$VSS_PUBLIC_PORT (voir URL de clip lisible dans le navigateur ci-dessus) à chaque URL de clip avant de la coller dans le rapport — la valeur brute est une URL HOST_IP:30888 que le navigateur de l'utilisateur ne peut pas atteindre.

Étape 3 — Remplir le modèle de rapport de plage d'incidents

Grouper par capteur (ou par catégorie si pas de scope capteur), compter les verdicts, lister chaque incident comme une bullet avec timestamp / catégorie / verdict / reasoning.

# Incident Range Report

## Basic Information

| Field | Value |
|-------|-------|
| **Report Identifier** | vss_report_<YYYYMMDD_HHMMSS> |
| **Range** | <start_time> – <end_time> |
| **Scope** | <sensor_id> | all sensors |
| **Total Incidents** | <N> |
| **Confirmed / Rejected / Unverified** | <c> / <r> / <u> |

## Incidents

### <sensor_id_or_category>

- **<timestamp>** — <category> — verdict: **<confirmed|rejected|unverified>**
  - <info.reasoning (1–2 lines)>
  - clip: `<rewritten URL>` (omit row when the incident carries no clip URL — never paste a raw `HOST_IP:30888` URL)
  - objects: <objectIds joined>
- …

## Summary

<2–4 sentences synthesizing what dominates the range — top categories, sensors with the most confirmed incidents, any clusters in time.>

Si get_incidents retourne zéro résultats, retourner un rapport d'une ligne indiquant que la plage et le scope n'ont produit aucun incident — ne pas inventer de contenu et ne pas basculer vers le Mode A.


Références croisées

  • /vss-manage-video-io-storage — liste des capteurs, timelines, et URL de clip pour Mode A Step 1.
  • /vss-query-analytics — récupération d'incidents (et enrichissement verdict / reasoning) pour Mode B Step 2.
  • /vss-ask-video — Q&A VLM ad-hoc sur un single clip (pas un rapport structuré).
  • /vss-summarize-video — utilisé par Mode A pour produire le corps du résumé quand le profil lvs est déployé ; le modèle de rapport (Step 4) est toujours rempli ici.

Skills similaires