{
  "name": "Qwen3.5 Captioner",
  "description": "Applies a Qwen3.5 vision-language model to video frames selected by input TimeFrame annotations for prompt-driven captioning / scene description. Per-TimeFrame captioning is composite: every frame sampled from a TF is fed to the model in a single prompt and yields one caption per TF. A `model` runtime parameter selects the Qwen3.5 variant (default `Qwen/Qwen3.5-2B`); larger variants need substantially more VRAM (see metadata.py for the GPU/VRAM sketch). Qwen3.5 is a reasoning model -- this app runs it for captions and strips any `<think>` reasoning block from the output.",
  "app_version": "v0.1",
  "mmif_version": "1.2.0",
  "analyzer_versions": {
    "Qwen/Qwen3.5-397B-A17B": "8472618",
    "Qwen/Qwen3.5-397B-A17B-FP8": "ea5b4f8",
    "Qwen/Qwen3.5-122B-A10B": "dc4d348",
    "Qwen/Qwen3.5-122B-A10B-FP8": "a099dee",
    "Qwen/Qwen3.5-35B-A3B": "59d61f3",
    "Qwen/Qwen3.5-35B-A3B-FP8": "9d1823d",
    "Qwen/Qwen3.5-27B": "fc05dae",
    "Qwen/Qwen3.5-27B-FP8": "97f5941",
    "Qwen/Qwen3.5-9B": "c202236",
    "Qwen/Qwen3.5-4B": "851bf6e",
    "Qwen/Qwen3.5-2B": "15852e8",
    "Qwen/Qwen3.5-0.8B": "2fc0636",
    "Qwen/Qwen3.5-397B-A17B-GPTQ-Int4": "df333de",
    "Qwen/Qwen3.5-122B-A10B-GPTQ-Int4": "30cd92c",
    "Qwen/Qwen3.5-35B-A3B-GPTQ-Int4": "3af5ca2",
    "Qwen/Qwen3.5-27B-GPTQ-Int4": "8f0c09f"
  },
  "app_license": "Apache 2.0",
  "analyzer_license": "Apache 2.0",
  "identifier": "http://apps.clams.ai/qwen35-timeframe-captioner/v0.1",
  "url": "https://github.com/clamsproject/app-qwen35-timeframe-captioner",
  "input": [
    {
      "@type": "http://clams.ai/vocabulary/type/VideoDocument/v2",
      "required": true
    },
    {
      "@type": "http://clams.ai/vocabulary/type/TimeFrame/v6",
      "description": "Labeled TimeFrame annotations selecting which video segments to caption. Frame selection within each segment is controlled by the universal `tfSamplingMode` parameter (see SDK docs). Filter by label with the `tfLabels` parameter.",
      "properties": {
        "representatives": "?",
        "label": "*"
      },
      "required": true
    }
  ],
  "output": [
    {
      "@type": "http://clams.ai/vocabulary/type/TextDocument/v2",
      "description": "Caption text generated by the Qwen3.5 model for each processed TimeFrame. The `origins` property points to the `TimePoint`(s) anchoring the sampled image(s).",
      "properties": {
        "origins": "*",
        "origination": "derived"
      }
    },
    {
      "@type": "http://clams.ai/vocabulary/type/Alignment/v1",
      "description": "Alignment between each parent TimeFrame and the TextDocument(s) derived from it."
    },
    {
      "@type": "http://clams.ai/vocabulary/type/TimePoint/v5",
      "description": "Optional output. Newly-created TimePoint annotations for images sampled from a TimeFrame interval without an existing backing TimePoint (see `tfSamplingMode`).",
      "properties": {
        "timeUnit": "milliseconds",
        "timePoint": "*"
      }
    }
  ],
  "parameters": [
    {
      "name": "tfLabels",
      "description": "Label(s) of input TimeFrame annotations to caption. By default (`[]`), all TimeFrames are processed regardless of label.",
      "type": "string",
      "default": [],
      "multivalued": true
    },
    {
      "name": "prompt",
      "description": "User prompt(s) sent to the model. A single value runs as a one-shot generation. A multi-value list is interpreted as a multi-turn static prompt; see ``promptMode`` for how turns are assembled.",
      "type": "string",
      "default": "You are looking at one or more frames sampled from a single segment of a video. Describe what is shown, transcribe any visible text, and produce one consolidated caption across all provided frames.",
      "multivalued": true
    },
    {
      "name": "systemPrompt",
      "description": "Optional system-role text prepended to the conversation. Empty by default.",
      "type": "string",
      "default": "",
      "multivalued": false
    },
    {
      "name": "promptMode",
      "description": "How to interpret a multi-value ``prompt`` list. Has no effect when ``prompt`` has a single value. For semantics of each choice and worked examples, see https://clams.ai/clams-python/app-baseclasses.html#promptable-multiturn",
      "type": "string",
      "choices": [
        "user-only",
        "turn-taking"
      ],
      "default": "turn-taking",
      "multivalued": false
    },
    {
      "name": "maxNewTokens",
      "description": "Maximum number of new tokens generated per inference call. Forwarded to the backend's ``generate``-equivalent. Larger values grow the KV cache linearly and increase GPU memory usage; reduce if VRAM is constrained.",
      "type": "integer",
      "default": 512,
      "multivalued": false
    },
    {
      "name": "useReasoning",
      "description": "Request the model's reasoning (\"thinking\") mode. Off by default. Honored only by apps whose backing model has a distinct reasoning mode; apps without one ignore it. When honored and enabled, the reasoning trace is split from the answer and stored in the ``modelReasoningTrace`` property of the output ``TextDocument`` (kept out of the document text). Reasoning is markedly slower and far more token-hungry: the whole trace is generated before the answer and drawn from the same budget capped by ``maxNewTokens``, so raise ``maxNewTokens`` substantially (thousands of tokens, not hundreds) when enabling this, or the trace may consume the entire budget and the answer be truncated or empty. Small reasoning models (as a rule of thumb, roughly 4B parameters and under) are especially prone to non-terminating \"thinking loops\" that exhaust the budget without producing an answer; validate termination per model before relying on it.",
      "type": "boolean",
      "default": false,
      "multivalued": false
    },
    {
      "name": "temperature",
      "description": "Sampling temperature. The default ``0.0`` selects deterministic / greedy decoding for maximum reproducibility; override for sampled generation.",
      "type": "number",
      "default": 0,
      "multivalued": false
    },
    {
      "name": "topP",
      "description": "Nucleus-sampling cumulative probability cutoff. Only meaningful when ``temperature`` is greater than 0.",
      "type": "number",
      "default": 1,
      "multivalued": false
    },
    {
      "name": "topK",
      "description": "Top-K sampling cutoff. Only meaningful when ``temperature`` is greater than 0.",
      "type": "integer",
      "default": 50,
      "multivalued": false
    },
    {
      "name": "parallelPrompts",
      "description": "Number of independent prompts the app runs in parallel (stacks into a single forward pass). The *size* of each prompt (how many images, how long the system/user text is, etc.) is NOT regulated by this parameter; that is each app's responsibility. Prompt count and per-prompt content size combine multiplicatively for GPU memory, so the two can blow up together. Catastrophic example: ``tfSamplingMode=all`` on a TimeFrame without ``targets`` expands that TF into one image per native-FPS frame (300 images for a 10-second TF at 30fps); ``parallelPrompts=4`` then runs 4 such prompts in one forward pass (~1200 images), guaranteed OOM. Keep at ``1`` on memory-tight setups; raise only when per-prompt content is small and bounded.",
      "type": "integer",
      "default": 1,
      "multivalued": false
    },
    {
      "name": "model",
      "description": "HuggingFace model identifier to use for this request. Must be one of the model ids declared in this app's ``analyzer_versions``; the SDK pins the corresponding commit hash at load time. When the app ships a single model (the typical case), this parameter defaults to that one model and can be omitted. Pass the full HF model id (e.g. ``org/repo-name``); URL-encoding the ``/`` is optional.",
      "type": "string",
      "choices": [
        "Qwen/Qwen3.5-397B-A17B",
        "Qwen/Qwen3.5-397B-A17B-FP8",
        "Qwen/Qwen3.5-122B-A10B",
        "Qwen/Qwen3.5-122B-A10B-FP8",
        "Qwen/Qwen3.5-35B-A3B",
        "Qwen/Qwen3.5-35B-A3B-FP8",
        "Qwen/Qwen3.5-27B",
        "Qwen/Qwen3.5-27B-FP8",
        "Qwen/Qwen3.5-9B",
        "Qwen/Qwen3.5-4B",
        "Qwen/Qwen3.5-2B",
        "Qwen/Qwen3.5-0.8B",
        "Qwen/Qwen3.5-397B-A17B-GPTQ-Int4",
        "Qwen/Qwen3.5-122B-A10B-GPTQ-Int4",
        "Qwen/Qwen3.5-35B-A3B-GPTQ-Int4",
        "Qwen/Qwen3.5-27B-GPTQ-Int4"
      ],
      "default": "Qwen/Qwen3.5-2B",
      "multivalued": false
    },
    {
      "name": "pretty",
      "description": "The JSON body of the HTTP response will be re-formatted with 2-space indentation",
      "type": "boolean",
      "default": false,
      "multivalued": false
    },
    {
      "name": "runningTime",
      "description": "The running time of the app will be recorded in the view metadata",
      "type": "boolean",
      "default": true,
      "multivalued": false
    },
    {
      "name": "hwFetch",
      "description": "The hardware information (architecture, GPU and vRAM) will be recorded in the view metadata",
      "type": "boolean",
      "default": false,
      "multivalued": false
    },
    {
      "name": "tfSamplingMode",
      "description": "Sampling mode for TimeFrame annotations. Has no effect when the app does not process TimeFrames. \"representatives\" uses all representative timepoints if present, otherwise skips the TimeFrame. \"single\" uses the middle representative if present, otherwise extracts an image from the midpoint of the start/end interval (midpoint is calculated by floor division of the sum of start and end). \"all\" uses all target timepoints if present, otherwise extracts all images from the time interval.",
      "type": "string",
      "choices": [
        "representatives",
        "single",
        "all"
      ],
      "default": "representatives",
      "multivalued": false
    }
  ],
  "est_gpu_mem_min": 2000,
  "est_gpu_mem_typ": 5000
}