Option
HeimHeim Skill Datenbankverwaltung vss-generate-video-report

vss-generate-video-report

NVIDIA/skills NVIDIA/skills

Erstellt Videoanalyseberichte, indem die Daten zur Analyse einzelner Clips an ein VLM-Backend oder zur Erstellung von Berichten über Vorfallbereiche an ein Analytics-Backend weitergeleitet werden, einschließlich der Überprüfung des Bereitstellungsprofils und der URL-Umschreibung.

...Alle erweitern
2
Zeit aktualisiert 27. September 2026

Bericht

Erstellen Sie einen Videoanalysebericht, indem Sie die Daten an eines von zwei Backends weiterleiten – niemals über den POST-Befehl /generate auf dem VSS-Agent.

Modus Backend
A. Videoclip /vss-manage-video-io-storage → Clip-URL → VLM-Chat/Ergänzungen
B. Vorfallbereich /vss-query-analytics → Vorfallsliste → narrativer Bericht

Ist die Anfrage mehrdeutig (z. B. „Bericht über “ ohne Zeitbereich und ohne Angabe des Vorfalls), wird standardmäßig Modus A verwendet. Nur dann abfragen, wenn der Benutzer sowohl einen Sensor als auch einen Zeitbereich angibt. Siehe die folgenden Beispiele für die Anforderungsformulierungen, die zu den jeweiligen Modi führen.

Anleitung

  1. Wählen Sie den Modus aus – Modus A für einen einzelnen aufgezeichneten Clip/ein Sensorvideo, Modus B, wenn die Anfrage einen Zeitbereich oder Vorfälle/Warnmeldungen nennt (vergleichen Sie mit den Beispielen).
  2. Überprüfen Sie das Bereitstellungsprofil für diesen Modus unter „Voraussetzungen für die Bereitstellung“; leiten Sie die Anfrage an /vss-deploy-profile weiter, falls die Überprüfung fehlschlägt.
  3. Führen Sie die nummerierten Schritte des jeweiligen Modus aus – Modus A oder Modus B (siehe unten).
  4. Ersetzen Sie jede benutzersichtbare Clip-URL durch die Einzeiler-Formulierung $VSS_PUBLIC_HOST:$VSS_PUBLIC_PORT (im Browser abspielbare Clip-URL), bevor Sie sie in den Bericht einbetten.
  5. Geben Sie den gerenderten Markdown-Bericht an den Benutzerzurück.

Ausgabevorgabe für Bewerter:

  • Der oberste Titel von Modus A MUSS genau lauten: # Videoanalysebericht.
  • Der Titel in Modus B MUSS genau lauten: # Incident Range Report (niemals # Incident Report oder Varianten mit Sensornamen).
  • Modus B MUSS „## Basic Information“ mit genau den erforderlichen Zeilen aus der Vorlage enthalten (Berichtskennung, Bereich, Umfang, Gesamtzahl der Vorfälle, Bestätigt / Abgelehnt / Unbestätigt).

Beispiele

  • „Bericht für dieses Video erstellen“ / „Bericht über “ → Modus A
  • „Analyse von warehouse_01.mp4“ / „Erstelle einen Analysebericht zum hochgeladenen Video“ → Modus A
  • „Bericht über Vorfälle von 12:31Z bis 12:32Z“ → Modus B
  • „Bericht über die heutigen Warnmeldungen“ / „Welche Vorfälle gab es in der letzten Stunde“ → Modus B
  • „Zusammenfassung der Warnmeldungen zwischen und “ → Modus B

Negative Auslöser

Verwende diese Funktion nicht, wenn die Anfrage einer der folgenden Fälle ist:

  • Ad-hoc-Fragen und Antworten zu einem Clip, bei denen nicht ausdrücklich um einen Bericht gebeten wird („Welche Farbe hat der Lkw?“, „Was passiert bei 00:12?“) → verwende /vss-ask-video.
  • Archiv-/semantische Ähnlichkeitssuche („Gabelstapler finden“, „alle Videos nach zu dichtem Auffahren durchsuchen“) → verwende /vss-search-archive.
  • Schreibgeschützte Abfrage von Vorfällen/Metriken ohne Erstellung eines Berichts → verwende /vss-query-analytics.
  • Bereitstellung/Abbau/Profiländerungen („Warnmeldungen bereitstellen“, „Profil wechseln“, „Basis aktivieren“) → verwenden Sie /vss-deploy-profile.
  • Anfragen zur Verwaltung von Warnmeldungen und Regeln in Echtzeit → verwenden Sie /vss-manage-alerts.

Leiten Sie Berichte niemals über den VSS-Agent -POST-Befehl /generate weiter.

Voraussetzungen für die Bereitstellung

Modus A erfordert das VSS -Basisprofil (VST + VLM NIM). Modus B erfordert das VSS -Alarm profil (VA-MCP + Elasticsearch).

Test:

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

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

Falls die Überprüfung fehlschlägt, übergeben Sie den Vorgang an /vss-deploy-profile mit dem Parameter -p base (Modus A) oder -p alerts (Modus B). Bestätigen Sie die Bereitstellung immer zuerst mit dem Benutzer.

Clip-URLs: VLM-Eingabe vs. Link zum Browser-Bericht

VST gibt Clip-URLs unter Verwendung des agenteninternen Hosts ${HOST_IP}:30888 aus. Behalten Sie diese ursprüngliche URL als VIDEO_URL für lokale bzw. clusterinterne VLM-Frame-Abrufe bei. Schreiben Sie die VLM-Eingabe-URL nicht um, nur um sie browsertauglich zu machen.

Erstellen Sie BROWSER_CLIP_URL nur für URLs, die im gerenderten Bericht angezeigt werden. Die Bereitstellungsschicht exportiert den browserorientierten Host:Port als $VSS_PUBLIC_HOST / $VSS_PUBLIC_PORT (und das Schema als $VSS_PUBLIC_HTTP_PROTOCOL) in jede .env-Datei des Profils – ob Brev oder Bare-Metal – exportiert, sodass die Umschreibung des Berichtslinks wie folgt lautet:

: "${VSS_PUBLIC_HOST:?Legen Sie VSS_PUBLIC_HOST vor dem Umschreiben der Clip-URLs fest}"
: "${VSS_PUBLIC_PORT:?Legen Sie VSS_PUBLIC_PORT vor dem Umschreiben der Clip-URLs fest}"
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}|")

Falls einer der erforderlichen öffentlichen Host-Werte fehlt, lassen Sie den Clip-Link im Bericht weg und weisen Sie darauf hin, dass keine im Browser abspielbare URL generiert werden konnte; blockieren Sie dabei nicht den lokalen VLM-Analysepfad. Wende die Umschreibung auf jede Clip-URL an , die im gerenderten Bericht erscheint (Modus A, Schritt 4, Zeile „Clip-URL“; Modus B, Unterpunkt „Clip pro Vorfall“). Belasse den VLM-Inhaltsblock „video_url“ in Modus A, Schritt 3, auf der ursprünglichen internen URL, wenn das VLM lokal bzw. im Cluster ist.

Modus A – Bericht zu einem aufgezeichneten Videoclip

Wenn das VSS-lvs-Profil bereitgestellt ist – „curl -sf --max-time 5 "http://${HOST_IP}:38111/v1/ready"“ gibt HTTP 200 zurück – führen Sie „/vss-summarize-video“ aus, um die Zusammenfassung zu erstellen, fügen Sie anschließend die Ausgabe in die Berichtsvorlage in Schritt 4 ein und überspringen Sie die Schritte 1–3 (den VLM-Direktpfad). Führen Sie die Schritte 1–3 nur aus, wenn /v1/ready nicht den Status 200 zurückgibt.

Schritt 1 – Die Clip-URL auflösen

Übergeben Sie die Aufgabe an /vss-manage-video-io-storage, um:

  1. Sensoren aufzulisten und zu überprüfen, ob der genannte tatsächlich existiert (falls nicht, zuerst hochladen).

  2. Rufen Sie /storage//timelines für den aufgezeichneten Bereich ab, wenn der Benutzer keine startTime- und endTime-Angaben gemacht hat.

  3. Eine Clip-URL anfordern:

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

    Dadurch erhält man eine direkte MP4-URL, aus der das lokale bzw. clusterinterne VLM Einzelbilder abrufen kann. Binden Sie diese an VIDEO_URL (die vom VLM in Schritt 3 verwendet wird) und setzen Sie RAW_URL="$VIDEO_URL", bevor Sie die Neuschreibung des Berichtslinks anwenden, um BROWSER_CLIP_URL für Schritt 4 zu erzeugen – der Browser des Benutzers kann $VIDEO_URL nicht direkt erreichen. Modus A setzt voraus, dass der ausgewählte VLM-Endpunkt in der Lage ist, VIDEO_URL abzurufen. Lokale NIM-/RT-VLM-Bereitstellungen sind dazu normalerweise in der Lage; entfernte Endpunkte können im Allgemeinen keine localhost-, private HOST_IP- oder VST-interne URLs abrufen. Wenn der Live- VLM_ENDPOINT remote ist, sollte diese Erreichbarkeitsanforderung deutlich gemacht werden, anstatt eine Chat-Anfrage zu stellen, die fehlschlägt, nachdem /v1/models erfolgreich war.

Schritt 2 – VLM-Endpunkt und Modell ermitteln

Die Bereitstellung kann das VLM über einen von zwei Stacks bereitstellen. Beide stellen eine OpenAI-kompatible Chat-/Vervollständigungs-API bereit – wählen Sie diejenige aus, die aktiv ist:

Backend Umgebungsvariablen Typischer Host-Endpunkt Wird ausgewählt, wenn
NIM Cosmos VLM_BASE_URL, VLM_NAME, VLM_MODE, VLM_MODEL_TYPE ${VLM_BASE_URL}/v1 (kein abschließendes /v1 in der Umgebungsvariablen; der Agent fügt es an) VLM_MODEL_TYPE ≠ rtvi und VLM_MODE ∈ {local, local_shared, remote} und VLM_BASE_URL ist nicht leer
RT-VLM Cosmos RTVI_VLM_BASE_URL, RTVI_VLM_MODEL_TO_USE, VLM_MODEL_TYPE ${RTVI_VLM_BASE_URL}/v1 – falls nicht gesetzt, wird es aus ${HOST_IP} abgeleitet (http://${HOST_IP}:8018/v1 für Warnmeldungen, http://${HOST_IP}:30082/v1 für „base“) VLM_MODEL_TYPE = rtvi oder VLM_MODE=none oder VLM_BASE_URL leer; außerdem der einzige Pfad für das Warehouse

Lies die Live-Werte aus dem laufenden Agent-Container aus – rate nicht:

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
'

RTVI_VLM_ENDPOINT darf nicht in der vss-agent -Umgebung erforderlich sein; mehrere Profile fügen es nicht ein.

Auswahlregel:

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"   # Standard für Warnmeldungen
  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"  # Standardwert
  VLM_MODEL="${RTVI_VLM_MODEL_TO_USE}"
fi

Überprüfen Sie /v1/models, bevor Sie eine Chat-Anfrage senden, um sicherzustellen, dass der ausgewählte Endpunkt erreichbar ist und das Modell geladen wurde:

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

Wenn die Überprüfung fehlschlägt oder die aufgelisteten IDs ${VLM_MODEL} nicht enthalten, weichen Sie auf das andere Backend aus (oder geben Sie den Fehler aus – wählen Sie niemals stillschweigend ein Modell aus, das nicht auf dem Server vorhanden ist).

Schritt 3 – Das VLM direkt aufrufen

Verwende den OpenAI-kompatiblen Chat-/Vervollständigungs-Endpunkt mit einem „video_url“-Inhaltsblock – dieselbe Payload-Struktur und dieselben multimodalen Einstellungen, die video_understanding in src/vss_agents/tools/video_understanding.py erstellt (_build_vlm_messages + der Cosmos-Aufruf ` base_vlm.bind(...) `).

Die Frame-Abtastung und das Budget für visuelle Token (Pixel) müssen den Live -Einstellungen von „video_understanding“ für das aktive Profil entsprechen. Senden Sie „mm_processor_kwargs“ und „media_io_kwargs“, damit der direkte Aufruf dieselbe Bildabtastung und dasselbe Pixelbudget verwendet wie das agenteneigene „video_understanding“-Tool – werden diese Parameter weggelassen, wendet das VLM seine eigenen Standardwerte an, sodass die Ausgabe vom Agentenpfad abweicht.

PROMPT='Beschreibe detailliert, was im Video geschieht, mit Zeitstempeln (Start–Ende in Sekunden ab Clip-Start) für jedes Segment oder Ereignis. Beziehe Szenen, Objekte, Personen, Fahrzeuge und bemerkenswerte Handlungen mit ein.'

# „Reasoning“ ist standardmäßig deaktiviert – entspricht der „video_understanding“-Konfiguration des Basisprofils (`reasoning: false`).
# „video_understanding.py“ verwendet „config.reasoning“, sofern der Aufrufer dies nicht überschreibt; daher ist standardmäßig kein „Reasoning“ aktiviert.
# Füge das „Cosmos Reason 2“-Suffix NUR dann an, wenn der Benutzer ausdrücklich nach einer Begründung fragt
# (lasse es bei VLMs ohne „Cosmos Reason 2“ weg). Bei deaktivierter Begründung enthält die Antwort keinen  Block.
if [ "${REASONING:-false}" = "true" ]; then
PROMPT="${PROMPT}

Beantworte die Frage im folgenden Format:


Deine Begründung.


Schreibe deine endgültige Antwort unmittelbar nach dem  Tag ."
fi

# Wenn Schritt 3 eigenständig ausgeführt wird, wird das fehlende Backend aus der aktuellen Umgebung/dem aktuellen Modell abgeleitet.
[ -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
}

# Multimodale Einstellungen – werden anhand des Pfads zur Konfigurationsdatei des Live-Agenten ermittelt, nicht anhand fest codierter Optionen.
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 ist im vss-Agent nicht gesetzt")
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 "Das Einlesen der video_understanding-Konfiguration vom vss-Agent ist fehlgeschlagen"; exit 1; }
jq -e . >/dev/null <<< "${CFG_JSON}" || { echo "Ungültige JSON-Konfiguration vom 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 — entspricht video_understanding.py.
# clip_seconds (Schritt 1: endTime – startTime) kann einen Bruchwert enthalten; auf ganze Sekunden abschneiden — bash $((...))
# akzeptiert nur ganze Zahlen und gibt bei „15,0“/„1,5“ einen Fehler aus. Standardwert 15 s -> Begrenzung auf 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

# Wende die Cosmos-mm/media-Kwargs nur auf den NIM-Cosmos-Pfad an.
# Der RT-VLM-Modus verwendet eine eigene serverseitige Vorverarbeitung und sollte diese Kwargs nicht erhalten.
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

Der „kwargs“-Block ist backendabhängig: Auf nim_cosmos verwenden Reason2-Varianten (nvidia/cosmos-reason2*) „mm_processor_kwargs.size{shortest_edge,longest_edge} “, während andere NIM-Cosmos-Varianten (nvidia/cosmos*) „mm_processor_kwargs.videos_kwargs{min_pixels,max_pixels}; beide senden zudem „media_io_kwargs.video.num_frames“. Auf rtvlm werden keine Cosmos-Kwargs gesendet.

Wenn der VLM einen … Block (Cosmos-Reason-Inferenzmodus) zurückgibt, behalte nur den Text nach als Berichtstext.

Schritt 4 – Füllen Sie die Vorlage für den Videoanalysebericht aus

Kopieren Sie „assets/video-analysis-report.md“, füllen Sie alle Platzhalter aus und geben Sie das gerenderte Markdown an den Benutzer zurück. Lassen Sie die Quelldatei unverändert. Überprüfen Sie vor dem Rendern, ob BROWSER_CLIP_URL gesetzt und nicht leer ist, und ersetzen Sie dann durch genau diesen Wert in der Zeile „Clip-URL “. Lassen Sie den Platzhalter niemals in der Ausgabe stehen, fügen Sie niemals Vorlagenanweisungen in eine ausgefüllte Zelle ein und verwenden Sie niemals die rohe URL ` HOST_IP:30888 `.

Modus B – Bericht über Vorfälle in einem Zeitbereich

Schritt 1 – Bestimme den Zeitbereich und (optional) den Sensor

  • start_time / end_time müssen im ISO 8601 UTC-Format (YYYY-MM-DDTHH:MM:SS.sssZ) vorliegen. Relative Zeitangaben („letzte Stunde“, „heute“) sind anhand der aktuellen Host-Uhr zu interpretieren.
  • Wenn der Benutzer einen Sensor angibt, erfassen Sie diesen als source + source_type=sensor. Andernfalls lassen Sie beide Felder unbesetzt, um eine Abfrage für alle Sensoren durchzuführen.

Schritt 2 – Abrufen von Vorfällen über /vss-query-analytics

Weiterleitung an /vss-query-analytics (initialisieren → tools/call) mit:

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

Schreibgeschützte Grenze (obligatorisch):

  • Modus B ist ausschließlich für den schreibgeschützten Abruf von Analysedaten vorgesehen. Schreiben, Seeding, Backfilling oder das Ändern von Elasticsearch-/VA-Daten ist strengstens untersagt.
  • Verbotene Beispiele: Indizierung synthetischer Vorfälle, Wiedergabe von Testdaten in ES, Aufruf von Schreib-/Aktualisierungs-/Lösch-APIs, um Daten für den Bericht „verfügbar zu machen“.
  • Falls für den angeforderten Bereich/Umfang keine Vorfälle vorhanden sind, behandeln Sie dies als leere Ergebnisse (siehe unten); erfinden Sie keine Daten.

Behalten Sie für jeden Vorfall folgende Angaben bei: id, sensorId, timestamp, end, category, place.name, info.verdict, info.reasoning, objectIds und die Clip-URL (in der Regel info.clip_url, clip_url oder das Feld mit dem Clip-Zeiger, das die Antwort enthält). Wenden Sie die Umschreibung $VSS_PUBLIC_HOST:$VSS_PUBLIC_PORT (siehe „Im Browser abspielbare Clip-URL“ oben) auf jede Clip-URL an, bevor Sie sie in den Bericht einfügen – der Rohwert ist eine HOST_IP:30888-URL, die der Browser des Benutzers nicht erreichen kann.

Schritt 3 – Füllen Sie die Vorlage für den Vorfallbereichsbericht aus

Kopieren Sie „assets/incident-range-report.md“, gruppieren Sie dann nach Sensor (oder nach Kategorie, falls kein Sensorbereich vorhanden ist), zählen Sie die Beurteilungen zusammen und listen Sie jeden Vorfall mit Zeitstempel, Kategorie, Beurteilung und Begründung auf. Lassen Sie die Quelldatei unverändert. Jeder Wert für einen Vorfall-Clip muss eine umgeschriebene, im Browser abspielbare URL sein; lassen Sie die Clip-Zeile weg, wenn der Vorfall keine Clip-URL enthält. Fügen Sie niemals Vorlagenanweisungen in eine ausgefüllte Zelle ein.

Wenn „get_incidents“ null Ergebnisse zurückgibt, STOPPEN Sie den Vorgang und geben Sie genau eine einzeilige „Empty-Range“-Anweisung zurück, in der der angeforderte Bereich und der Geltungsbereich genannt werden. Rendern Sie nicht die vollständige Vorlage für den Vorfallbereich, erfinden Sie keine Vorfälle, fügen Sie keine Testdaten ein und greifen Sie nicht auf Modus A zurück.

Fehlerbehandlung

  • Wenn ein Probe-, curl-, VLM-Aufruf oder eine /vss-query-analytics-Anfrage fehlschlägt, brechen Sie den Workflow ab und melden Sie den fehlerhaften Endpunkt, den HTTP-Status oder den Befehlsfehler sowie den nächsten sinnvollen Schritt zur Fehlerbehebung. Erstellen Sie keinen Bericht auf der Grundlage unvollständiger oder fehlender Daten.
  • Wenn die VLM-Antwort leer oder fehlerhaft ist oder nur einen Begründungsblock enthält, weisen Sie auf dieses Antwortproblem hin und schlagen Sie vor, vor einem erneuten Versuch die Modellbereitschaft bzw. die Protokolle zu überprüfen.
  • Wenn eine Clip-URL nicht auf den öffentlichen Host/Port umgeschrieben werden kann, lassen Sie sie im erstellten Bericht weg und weisen Sie darauf hin, dass die im Browser abspielbare URL nicht generiert werden konnte.
  • Behandeln Sie bei Modus B fehlende optionale Vorfallfelder (info.reasoning, objectIds, Clip-URL) als Auslassungen im Bericht, behandeln Sie jedoch fehlende IDs, Zeitstempel oder Kategorien als Datenqualitätsfehler, die gemeldet werden sollten.

Querverweis

  • /vss-manage-video-io-storage – Sensorliste, Zeitachsen und Clip-URL für Modus A, Schritt 1.
  • /vss-query-analytics – Abruf von Vorfällen (sowie Anreicherung mit Beurteilungen und Begründungen) für Modus B, Schritt 2.
  • /vss-ask-video – Ad-hoc-VLM-Frage-und-Antwort-Funktion für einen einzelnen Clip (kein strukturierter Bericht).
  • /vss-summarize-video – wird von Modus A verwendet, um den Zusammenfassungstext zu erstellen, wenn das LVS-Profil bereitgestellt ist; die Berichtsvorlage (Schritt 4) wird hier weiterhin ausgefüllt.
Auf GitHub ansehen
---
name: vss-generate-video-report
description: Generates video analysis reports by routing to a VLM backend for per-clip analysis or an analytics backend for incident-range reports, with deployment profile verification and URL rewriting.
license: Apache-2.0
---

# Report

Generate a video analysis report by routing to one of two backends — **never via** `POST /generate` on the VSS agent.

| Mode | Backend |
|---|---|
| **A. Video clip** | `/vss-manage-video-io-storage` → clip URL → **VLM chat/completions** |
| **B. Incident range** | `/vss-query-analytics` → incident list → narrative report |

If the request is ambiguous (e.g. "report on `<sensor>`" with no time range and no incident wording), default to **Mode A**. Ask only if the user mentions both a sensor and a time range. See **Examples** below for the request phrasings that route to each mode.

---

## Instructions

1. **Pick the mode** — Mode A for a single recorded clip/sensor video, Mode B when the request names a time range or incidents/alerts (match against *Examples*).
2. **Verify the deployment profile** for that mode under *Deployment prerequisite*; hand off to `/vss-deploy-profile` if its probe fails.
3. **Run that mode's numbered steps** — *Mode A* or *Mode B* below.
4. **Rewrite every user-facing clip URL** with the `$VSS_PUBLIC_HOST:$VSS_PUBLIC_PORT` one-liner (*Browser-playable clip URL*) before embedding it in the report.
5. **Return the rendered report markdown** to the user.

Output contract for evaluators:
- Mode A top title MUST be exactly `# Video Analysis Report`.
- Mode B top title MUST be exactly `# Incident Range Report` (never `# Incident Report` or sensor-named variants).
- Mode B MUST include `## Basic Information` with the exact required rows from the template (Report Identifier, Range, Scope, Total Incidents, Confirmed / Rejected / Unverified).

---

## Examples

- "Generate a report for this video" / "report on `<sensor-id>`" → **Mode A**
- "Analyze warehouse_01.mp4" / "create an analysis report on the uploaded video" → **Mode A**
- "Report on incidents from 12:31Z to 12:32Z" → **Mode B**
- "Report on alerts today" / "what incidents happened on `<sensor>` last hour" → **Mode B**
- "Summarize alerts on `<sensor>` between `<t1>` and `<t2>`" → **Mode B**

---

## Negative Triggers

Do **not** use this skill when the request is one of the following:

- Ad-hoc visual Q&A on a clip that do not ask explicitly for a report ("what color is the truck?", "what happens at 00:12?") → use `/vss-ask-video`.
- Archive/semantic similarity retrieval ("find forklifts", "search all videos for tailgating") → use `/vss-search-archive`.
- Read-only incident/metrics lookup without report rendering needs → use `/vss-query-analytics`.
- Deploy/teardown/profile changes ("deploy alerts", "switch profile", "bring up base") → use `/vss-deploy-profile`.
- Real-time alert/rule management requests → use `/vss-manage-alerts`.

Never route reports through VSS-agent `POST /generate`.

---

## Deployment prerequisite

**Mode A** needs the VSS **base** profile (VST + VLM NIM).
**Mode B** needs the VSS **alerts** profile (VA-MCP + Elasticsearch).

Probe:

```bash
# 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
```

If the probe fails, hand off to `/vss-deploy-profile` with `-p base` (Mode A) or `-p alerts` (Mode B). **Always** confirm the deploy with the user first.

---

## Clip URLs: VLM input vs browser report link

VST returns clip URLs using the agent-internal `${HOST_IP}:30888` host:port.
Keep that original URL as `VIDEO_URL` for local / in-cluster VLM frame pulls.
Do **not** rewrite the VLM input URL just to make it browser-playable.

Only create `BROWSER_CLIP_URL` for URLs shown in the rendered report. The
deploy layer exports the browser-facing host:port as `$VSS_PUBLIC_HOST` /
`$VSS_PUBLIC_PORT` (and scheme as `$VSS_PUBLIC_HTTP_PROTOCOL`) in every
profile `.env` — Brev or bare-metal — so the report-link rewrite is:

```bash
: "${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}|")
```

If either required public host value is missing, omit the report-facing clip
link and call out that a browser-playable URL could not be produced; do not
block the local VLM analysis path. Apply the rewrite to **every clip URL
surfaced in the rendered report** (Mode A Step 4 Clip URL row; Mode B
per-incident clip sub-bullet). Leave the VLM `video_url` content block in Mode A
Step 3 on the original internal URL when the VLM is local / in-cluster.

---

## Mode A — Report on a recorded video clip

**If the VSS `lvs` profile is deployed** — `curl -sf --max-time 5 "http://${HOST_IP}:38111/v1/ready"` returns HTTP 200 — run `/vss-summarize-video` to produce the summary, then paste its output into the report template in Step 4 and skip Steps 1–3 (the VLM-direct path). Run Steps 1–3 only when `/v1/ready` is non-200.

### Step 1 — Resolve the clip URL

Hand off to `/vss-manage-video-io-storage` to:

1. List sensors and confirm the named `<sensor-id>` exists (upload first if not).
2. Fetch `/storage/<streamId>/timelines` for the recorded range when the user did not supply `startTime` / `endTime`.
3. Request a clip URL:

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

   That gives a direct `mp4` URL that the local / in-cluster VLM can pull frames from. Bind it to `VIDEO_URL` (used by the VLM in Step 3) and set `RAW_URL="$VIDEO_URL"` before applying the report-link rewrite to produce `BROWSER_CLIP_URL` for Step 4 — the user's browser cannot reach `$VIDEO_URL` directly.
   Mode A requires the selected VLM endpoint to be able to fetch `VIDEO_URL`.
   Local NIM/RT-VLM deployments normally can; remote endpoints generally cannot
   fetch `localhost`, private `HOST_IP`, or VST-internal URLs. If the live
   `VLM_ENDPOINT` is remote, surface that reachability requirement instead of
   making a chat request that will fail after `/v1/models` succeeds.

### Step 2 — Resolve VLM endpoint and model

The deploy may serve the VLM through either of two stacks. Both expose an OpenAI-compatible `chat/completions` API — pick whichever is live:

| Backend | Env vars | Typical host endpoint | Picked when |
|---|---|---|---|
| **NIM Cosmos** | `VLM_BASE_URL`, `VLM_NAME`, `VLM_MODE`, `VLM_MODEL_TYPE` | `${VLM_BASE_URL}/v1` (no trailing `/v1` on the env var; the agent appends it) | `VLM_MODEL_TYPE != rtvi` **and** `VLM_MODE` ∈ {`local`, `local_shared`, `remote`} **and** `VLM_BASE_URL` is non-empty |
| **RT-VLM Cosmos** | `RTVI_VLM_BASE_URL`, `RTVI_VLM_MODEL_TO_USE`, `VLM_MODEL_TYPE` | `${RTVI_VLM_BASE_URL}/v1` — if unset, derive from `${HOST_IP}` (`http://${HOST_IP}:8018/v1` for alerts, `http://${HOST_IP}:30082/v1` for base) | `VLM_MODEL_TYPE = rtvi`, or `VLM_MODE=none`, or `VLM_BASE_URL` empty; also the only path for `warehouse` |

Read the live values off the running agent container — do not guess:

```bash
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
'
```

Do not require `RTVI_VLM_ENDPOINT` from `vss-agent` env; several profiles do not inject it.

Selection rule:

```bash
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
```

Probe `/v1/models` before sending a chat request to confirm the chosen endpoint is alive and the model is loaded:

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

If the probe fails or the listed ids don't include `${VLM_MODEL}`, fall back to the other backend (or surface the error — never silently pick a model that isn't on the server).

### Step 3 — Call the VLM directly

Use the OpenAI-compatible `chat/completions` endpoint with a `video_url` content block — the same payload shape **and multimodal settings** `video_understanding` builds in `src/vss_agents/tools/video_understanding.py` (`_build_vlm_messages` + the Cosmos `base_vlm.bind(...)` call).

The frame sampling and visual-token (pixel) budget must mirror the **live** `video_understanding` settings for the active profile. **Send `mm_processor_kwargs` and `media_io_kwargs`** so the direct call uses the same frame sampling and pixel budget as the in-agent `video_understanding` tool — omitting them lets the VLM apply its own defaults, so the output diverges from the agent path.

```bash
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
```

> The kwargs block is backend-aware: on `nim_cosmos`, Reason2 variants (`nvidia/cosmos-reason2*`) use `mm_processor_kwargs.size{shortest_edge,longest_edge}` and other NIM Cosmos variants (`nvidia/cosmos*`) use `mm_processor_kwargs.videos_kwargs{min_pixels,max_pixels}`; both also send `media_io_kwargs.video.num_frames`. On `rtvlm`, no Cosmos kwargs are sent.

If the VLM returns a `<think>…</think>` block (Cosmos Reason reasoning mode), keep only the text after `</think>` as the report body.

### Step 4 — Fill the Video Analysis Report template

Copy [`assets/video-analysis-report.md`](assets/video-analysis-report.md), fill every placeholder, and return the rendered markdown to the user. Keep the source asset unchanged. Before rendering, verify `BROWSER_CLIP_URL` is set and non-empty, then replace `<BROWSER_CLIP_URL>` with that exact value in the `Clip URL` row. Never leave the placeholder in the output, never include template instructions in a filled cell, and never use the raw `HOST_IP:30888` URL.

---

## Mode B — Report on incidents in a time range

### Step 1 — Resolve the time range and (optionally) sensor

- `start_time` / `end_time` must be ISO 8601 UTC (`YYYY-MM-DDTHH:MM:SS.sssZ`). Resolve relative phrases ("last hour", "today") against the current host clock.
- If the user names a sensor, capture it as `source` + `source_type=sensor`. Otherwise leave both unset for an all-sensors query.

### Step 2 — Fetch incidents via `/vss-query-analytics`

Hand off to `/vss-query-analytics` (initialize → `tools/call`) with:

```json
{
  "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
}
```

Read-only boundary (mandatory):
- Mode B is strictly read-only analytics retrieval. Never write, seed, backfill, or mutate Elasticsearch/VA data.
- Forbidden examples: indexing synthetic incidents, replaying fixture payloads into ES, calling write/update/delete APIs to "make data available" for the report.
- If no incidents exist for the requested range/scope, handle as empty results (see below); do not fabricate data.

For each incident keep: `id`, `sensorId`, `timestamp`, `end`, `category`, `place.name`, `info.verdict`, `info.reasoning`, `objectIds`, and the clip URL (commonly `info.clip_url`, `clip_url`, or whichever clip-pointer field the response carries). **Apply the `$VSS_PUBLIC_HOST:$VSS_PUBLIC_PORT` rewrite (see *Browser-playable clip URL* above) to every clip URL before pasting it into the report** — the raw value is a `HOST_IP:30888` URL the user's browser cannot reach.

### Step 3 — Fill the Incident Range Report template

Copy [`assets/incident-range-report.md`](assets/incident-range-report.md), then group by sensor (or by category if no sensor scope), tally verdicts, and list each incident with timestamp / category / verdict / reasoning. Keep the source asset unchanged. Every incident clip value must be a rewritten browser-playable URL; omit the clip line when the incident carries no clip URL. Never include template instructions in a filled cell.

If `get_incidents` returns zero results, STOP and return exactly a one-line empty-range statement naming the requested range and scope. Do not render the full Incident Range template, do not invent incidents, do not seed test data, and do not fall back to Mode A.

---

## Error Handling

- If a probe, `curl`, VLM call, or `/vss-query-analytics` request fails, stop the workflow and report the failing endpoint, HTTP status or command error, and the next useful recovery step. Do not fabricate a report from partial or missing data.
- If the VLM response is empty, malformed, or contains only a reasoning block, surface that response problem and suggest checking model readiness/logs before retrying.
- If a clip URL cannot be rewritten to the public host/port, omit it from the rendered report and call out that the browser-playable URL could not be produced.
- For Mode B, treat missing optional incident fields (`info.reasoning`, `objectIds`, clip URL) as omissions in the report, but treat missing `id`, `timestamp`, or `category` as a data-quality error that should be reported.

---

## Cross-Reference

- **`/vss-manage-video-io-storage`** — sensor list, timelines, and clip URL for Mode A Step 1.
- **`/vss-query-analytics`** — incident retrieval (and verdict / reasoning enrichment) for Mode B Step 2.
- **`/vss-ask-video`** — ad-hoc VLM Q&A on a single clip (not a structured report).
- **`/vss-summarize-video`** — used by Mode A to produce the summary body when the `lvs` profile is deployed; the report template (Step 4) is still filled here.

vss-generate-video-report installieren

Laden Sie die Skill-Dateien herunter und entpacken Sie sie in Ihr Verzeichnis „.claude/skills/“.

ZIP herunterladen

Klonen Sie das Repository und kopieren Sie die Skill-Dateien in Ihr Projekt.

git clone https://github.com/NVIDIA/skills/tree/main/skills/vss-generate-video-report # Copy SKILL.md to your .claude/skills/ directory

Kopieren Kopieren
Schnelle Einrichtung: Kopiere den Skill-Ordner nach .claude/skills/ Claude erkennt den Skill automatisch und nutzt ihn.
Repository NVIDIA/skills

Ähnliche Skills

microservices-patterns
Zeit aktualisiert 29. Juni 2026
jpa-patterns
Zeit aktualisiert 30. Juni 2026
fabric-lakehouse
Zeit aktualisiert 30. Juni 2026
prisma-expert
Zeit aktualisiert 29. Juni 2026
OR