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 :
-
Lister les capteurs et confirmer que le
<sensor-id>nommé existe (télécharger d'abord s'il n'existe pas). -
Récupérer
/storage/<streamId>/timelinespour la plage enregistrée quand l'utilisateur n'a pas fournistartTime/endTime. -
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 .videoUrlCela donne une URL
mp4directe 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 enBROWSER_CLIP_URLpour 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_URLdirectement. Le Mode A exige que le endpoint VLM sélectionné soit capable de récupérerVIDEO_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érerlocalhost, les URLsHOST_IPprivées, ou les URLs internes à VST. Si leVLM_ENDPOINTactif est distant, afficher cette exigence de reachability au lieu de faire une requête chat qui échouera après que/v1/modelsré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_timedoivent ê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 profillvsest déployé ; le modèle de rapport (Step 4) est toujours rempli ici.