全部產品
Search
文件中心

Vector Retrieval Service for Milvus:通過阿里雲Milvus構建短劇素材生產與多模態檢索流水線

更新時間:Aug 14, 2026

本文介紹如何用阿里雲 Milvus 的四個 AI Function 串聯一條「產生 → 檢索」流水線:從分鏡參考圖產生主要畫面格、由主要畫面格產生運動鏡頭、為素材自動產生檢索描述,再把圖片與視頻寫入同一個多模態 Collection,實現以文搜圖、以文搜視頻與以圖搜視頻。

方案概述

AIGC 短劇、豎屏微短劇、品牌短視頻的生產節奏很快:一個團隊一周要產出幾十條甚至上百條成片,每條成片背後又有大量主要畫面格、分鏡與候選素材需要產生和篩選。典型生產鏈路可以拆成四段:

  1. 分鏡文案:先寫出一段段分鏡指令碼,例如「雨夜,女主撐傘站在便利店門口,暖色燈光打在臉上」。

  2. 主要畫面格生圖:把分鏡文案轉成主要畫面格圖片,作為鏡頭的視覺錨點,也可在已有素材上做背景替換或風格調整。

  3. 圖生視頻:把選定的主要畫面格作為首幀,產生一段運動鏡頭。

  4. 素材沉澱與檢索複用:為每天產生的圖片與視頻自動產生描述並轉成向量入庫,之後就能以文搜圖、以圖搜視頻、以文搜視頻,讓素材從一次性消耗品變成可複用資產。

如果不藉助統一的向量資料庫平台,自己從零對接各家產生模型再拼一套檢索系統,通常會遇到以下問題:生圖與生視頻模型分散在不同服務、鑒權與返回格式各異;視頻產生是長耗時非同步任務,需要自己管理輪詢、終態判定與逾時兜底;圖片、視頻、描述文本與向量分散在四處,檢索時要跨系統拼裝;跨模態檢索需要把文本、圖片、視頻映射到同一向量空間,缺少語義檢索時只能靠檔案名稱和記憶找素材。

阿里雲 Milvus 用四個 AI Function 把這條鏈路串起來,分為「產生」與「檢索」兩個階段、五個步驟:

Function

作用

本文中的用途

AI_IMAGE_EDIT

映像產生與編輯,支援單圖編輯和多參考圖融合。

把分鏡參考圖按文案轉成主要畫面格圖片。

AI_VIDEO_EDIT

圖生視頻與文生視頻,非同步任務。

以主要畫面格為首幀產生運動鏡頭。

AI_MULTI_MODAL_GENERATE

多模態內容理解,為圖片與視頻產生標題、描述、標籤。

為素材產生用於檢索的客觀描述。

AI_EMBEDDING

寫入即向量化,把文本、圖片、視頻映射到同一向量空間。

素材入庫並支援跨模態檢索。

這條鏈路的關鍵在三點:產生即入庫,主要畫面格與運動鏡頭連同描述、標籤一起落庫,每個產生物都帶著結構化資訊和向量;同一向量空間,圖片和視頻共用同一個多模態模型與同一向量欄位,才能做以文搜圖、以圖搜視頻這類跨模態檢索;寫入即向量化、查詢即推理,應用側無需自己調用 embedding 模型。

前提條件

  • 已建立 Milvus 2.6 版本執行個體。AI Function 依賴 2.6 版本核心,建立後無需單獨綁定模型服務。

  • 如需從公網訪問執行個體,已在執行個體詳情頁的 安全配置 頁簽開啟 公網訪問 並將用戶端出口 IP 加入公網訪問白名單。

  • 已安裝 pymilvus。本文樣本基於 pymilvus 3.0.0 驗證。

說明

RESTful 介面與 gRPC 共用 19530 連接埠,調用時必須在地址中顯式帶上連接埠,例如 http://c-xxx.milvus.aliyuncs.com:19530;若省略連接埠將預設訪問 80 連接埠並導致連線逾時。

操作步驟

準備公用代碼

以下程式碼封裝含串連配置與 REST 調用工具,供步驟 1~3 共用。請將 MILVUS_ENDPOINT 與 MILVUS_TOKEN 替換為實際執行個體資訊。

from __future__ import annotations

import json
import time
from typing import Any
from urllib.error import HTTPError
from urllib.request import Request, urlopen

from pymilvus import DataType, Function, FunctionType, MilvusClient

# ==== 全域配置(REST 與 gRPC 同在 19530 連接埠)====
MILVUS_ENDPOINT = "c-xxx.milvus.aliyuncs.com:19530"
MILVUS_TOKEN = "root:xxx"
MILVUS_REST_BASE_URL = f"http://{MILVUS_ENDPOINT}"
MILVUS_URI = MILVUS_REST_BASE_URL


def post_json(path: str, body: dict[str, Any], timeout: int = 200) -> tuple[int, dict[str, Any]]:
    """步驟 1~3 共用的 REST 調用工具。"""
    request = Request(
        f"{MILVUS_REST_BASE_URL.rstrip('/')}{path}",
        data=json.dumps(body, ensure_ascii=False).encode("utf-8"),
        headers={"Authorization": f"Bearer {MILVUS_TOKEN}", "Content-Type": "application/json"},
        method="POST",
    )
    try:
        with urlopen(request, timeout=timeout) as response:
            return response.status, json.loads(response.read().decode("utf-8"))
    except HTTPError as exc:
        return exc.code, json.loads(exc.read().decode("utf-8"))

步驟一:主要畫面格生圖

拿一張分鏡參考圖,按分鏡文案做背景替換或風格調整,產出一張主要畫面格圖片,作為後續圖生視頻的首幀。

項

內容

Function / 模型

AI_IMAGE_EDIT / wan2.7-image

輸入

參考圖 URL + 分鏡文案

輸出

主要畫面格圖片 URL

調用方式

同步返回

# ========== 步驟 1:主要畫面格生圖 AI_IMAGE_EDIT (wan2.7-image) ==========
IMAGE_URL = "https://dashscope.oss-cn-beijing.aliyuncs.com/images/dog_and_girl.jpeg"
PROMPT = (
    "雨夜,一位女子撐著傘站在便利店門口,暖色的店內燈光打在她臉上,"
    "電影級構圖,保留畫面主體。"
)
status, data = post_json(
    "/v2/vectordb/ai/image_edit",
    {
        "model_name": "wan2.7-image",
        "texts": [IMAGE_URL],
        "params": {"prompt": PROMPT, "n": 1, "size": "1024*1024",
                   "watermark": False, "timeout_sec": 180},
    },
)
assert status == 200 and data.get("code") == 0, data
keyframe_url = data["data"]["output"]["outputs"][0]
print(f"主要畫面格圖片 URL:{keyframe_url}")
說明

單圖輸入用 texts 傳參考圖 URL,編輯指令寫在 params.prompt 中。n 表示產生數量,本例為 1。

步驟二:圖生視頻

把主要畫面格作為首幀產生一段運動鏡頭。視頻產生是長耗時非同步任務,需要先建立任務拿到 task_id,再輪詢查詢任務狀態。

項

內容

Function / 模型

AI_VIDEO_EDIT / happyhorse-1.1-i2v

輸入

首幀圖 + 運鏡文案

輸出

運動鏡頭 video_url

調用方式

非同步:/v2/vectordb/ai/video_edit 建立,/v2/vectordb/ai/tasks/describe 輪詢

# ========== 步驟 2:圖生視頻 AI_VIDEO_EDIT (happyhorse-1.1-i2v),非同步任務 ==========
VIDEO_MODEL = "happyhorse-1.1-i2v"  # 建立與查詢必須使用同一模型名

# 用步驟 1 的主要畫面格作首幀(media.type=first_frame)
status, data = post_json(
    "/v2/vectordb/ai/video_edit",
    {
        "model_name": VIDEO_MODEL,
        "prompt": "鏡頭緩緩推進,雨絲飄落,暖光在傘面上輕輕閃動,保留首幀圖中的主體。",
        "media": [{"type": "first_frame", "url": keyframe_url}],
        "params": {"resolution": "720P", "audio_setting": "none",
                   "watermark": False, "timeout_sec": 180},
    },
)
assert status == 200 and data.get("code") == 0, data
task_id = data["data"]["output"]["task_id"]
print(f"任務已建立  task_id = {task_id}")

# 輪詢查詢:tasks/describe 一次只查一個 task_id,帶 provider 與相同 model_name
video_url = None
for attempt in range(60):  # 本地輪詢上限,達到上限只記錄 task_id,不判定服務端失敗
    status, data = post_json(
        "/v2/vectordb/ai/tasks/describe",
        {"provider": "aliyun_milvus", "model_name": VIDEO_MODEL, "task_id": task_id},
    )
    task_status = data.get("data", {}).get("output", {}).get("task_status")
    print(f"  輪詢 #{attempt:<2} ->  {task_status}")
    if task_status == "SUCCEEDED":
        video_url = data["data"]["output"]["video_url"]
        break
    if task_status in ("FAILED", "CANCELED"):
        raise RuntimeError(f"task ended in {task_status}: {json.dumps(data, ensure_ascii=False)}")
    time.sleep(10)
assert video_url, f"輪詢達到本地上限,請稍後憑 task_id={task_id} 繼續查詢"
print(f"運動鏡頭 video_url:{video_url}")

輪詢返回的 task_status 取值說明:

task_status

含義

處理方式

UNKNOWN

任務進行中,尚未完成。這是任務執行期間的正常傳回值。

繼續輪詢,不要判定為異常。

SUCCEEDED

任務成功,可從 video_url 取結果。

結束輪詢。

FAILED

任務失敗。

終態,結束輪詢並排查。

CANCELED

任務被取消。

終態,結束輪詢。

警告

實測一次 720P、5 秒的圖生視頻任務約需 2~3 分鐘,期間 task_status 會持續返回 UNKNOWN。tasks/describe 的返回體包含 task_id、task_status、video_url 三個欄位,且必須攜帶與建立時相同的 model_name 和 provider。

說明

params.resolution 指定的是像素量檔位,不是固定的寬高。實際輸出尺寸由首幀圖的寬高比決定:本例首幀為 1024×1024 正方形,設定 720P 後實際輸出為 960×960(像素總量與 1280×720 相當),時間長度約 5 秒。如需 16:9 橫屏輸出,請使用對應寬高比的首幀圖。

步驟三:素材理解,產生描述與標籤

在素材入庫前,用多模態大模型給圖片和視頻各產生一句客觀的檢索描述,作為標題、簡介或標籤來源,隨素材一起在步驟四入庫。

項

內容

Function / 模型

AI_MULTI_MODAL_GENERATE / qwen3.7-plus

輸入

圖片或視頻 URL

輸出

一句中文描述,可作為標題、描述或標籤來源

# ========== 步驟 3:素材理解 AI_MULTI_MODAL_GENERATE (qwen3.7-plus) ==========
def describe_media(url: str, media_type: str, prompt: str) -> str:
    status, data = post_json(
        "/v2/vectordb/ai/multi_modal_generate",
        {
            "model_name": "qwen3.7-plus",
            "texts": [url],
            "params": {"media_type": media_type, "prompt": prompt, "temperature": 0},
        },
    )
    assert status == 200 and data.get("code") == 0, data
    return data["data"]["output"]["outputs"][0]


image_caption = describe_media(
    keyframe_url, "image",
    "用一句中文客觀描述這張主要畫面格圖片的主體、情境和色調,便於以文搜圖。",
)
video_caption = describe_media(
    video_url, "video",
    "用一句中文客觀描述這段視頻的主體、動作和畫面氛圍,便於按鏡頭檢索。",
)
print(f"[圖片描述] {image_caption}")
print(f"[視頻描述] {video_caption}")

圖片用 media_type=image,視頻用 media_type=video,prompt 為必填項。實測產生的描述樣本:

[圖片描述] 雨夜街頭,一位穿格子襯衫的女子撐著傘站在便利店門口,身旁坐著一隻金毛犬,
          整體色調偏冷藍,燈光溫暖映襯濕漉地面,營造靜謐而略帶憂鬱的氛圍。
[視頻描述] 這段視頻呈現了雨夜街頭,一名身穿格子襯衫的女子撐著透明雨傘與一隻黃狗站在亮燈的
          店鋪外,鏡頭由全景逐漸推近至女子面部的特寫,整體畫面色調偏冷且帶有濕潤的反光。
說明

描述文案的品質直接決定後續以文搜圖的召回效果,建議在 prompt 中明確要求「客觀描述主體、情境、色調、動作」等檢索時會用到的維度,避免產生帶主觀評價的文案。

步驟四:多模態向量入庫

建一個「寫入即向量化」的多模態素材 Collection,把圖片 URL、視頻 URL 連同描述、標籤寫入,Milvus 會自動為素材產生向量並落庫。

項

內容

Function / 模型

AI_EMBEDDING / qwen3-vl-embedding

關鍵參數

is_multimodal 必須為 true,dim 為 2560

輸入

步驟 1~3 產出的 URL + 描述 + 標籤

輸出

落庫的 2560 維向量

# ========== 步驟 4:建多模態 Collection 並向量入庫 AI_EMBEDDING (qwen3-vl-embedding) ==========
EMBED_MODEL = "qwen3-vl-embedding"
VECTOR_DIM = 2560  # qwen3-vl-embedding 多模態維度,向量欄位維度必須與 dim 一致

client = MilvusClient(uri=MILVUS_URI, token=MILVUS_TOKEN)

collection_name = "aigc_assets_multimodal"
if client.has_collection(collection_name):
    client.drop_collection(collection_name)

schema = MilvusClient.create_schema(auto_id=True, enable_dynamic_field=False)
schema.add_field("id", DataType.INT64, is_primary=True)
schema.add_field("media_type", DataType.VARCHAR, max_length=16)     # image / video
schema.add_field("media_ref", DataType.VARCHAR, max_length=4096)    # 用於向量化的圖片/視頻 URL
schema.add_field("caption", DataType.VARCHAR, max_length=2048)      # 步驟 3 產生的描述
schema.add_field("tags", DataType.VARCHAR, max_length=512)
schema.add_field("embedding", DataType.FLOAT_VECTOR, dim=VECTOR_DIM)

# 寫入 media_ref 時自動調用 qwen3-vl-embedding 產生 2560 維向量
schema.add_function(
    Function(
        name="embed_media",
        function_type=FunctionType.TEXTEMBEDDING,
        input_field_names=["media_ref"],
        output_field_names=["embedding"],
        params={
            "provider": "aliyun_milvus",
            "model_name": EMBED_MODEL,
            "dim": VECTOR_DIM,
            "is_multimodal": "true",
        },
    )
)

index_params = client.prepare_index_params()
index_params.add_index(field_name="embedding", index_type="AUTOINDEX", metric_type="COSINE")
client.create_collection(collection_name=collection_name, schema=schema,
                        index_params=index_params)

# 把步驟 1~3 的產出(url + caption + tags)向量化入庫
client.insert(
    collection_name,
    [
        {"media_type": "image", "media_ref": keyframe_url,
         "caption": image_caption, "tags": "主要畫面格,產生圖"},
        {"media_type": "video", "media_ref": video_url,
         "caption": video_caption, "tags": "運動鏡頭,產生視頻"},
    ],
)
client.flush(collection_name)
client.load_collection(collection_name)
print(f"圖片與視頻已向量化入庫,Collection:{collection_name}")
警告

多模態 Function 必須帶 "is_multimodal": "true",否則在 create_collection 階段即報 multimodal=false ... 400 InvalidParameter url error。向量欄位的 dim 必須與 Function 參數中的 dim 一致(2560)。

說明

寫入後必須調用 flush(),否則緊接著執行檢索可能返回空結果。圖片和視頻要落在同一個向量欄位、使用同一個模型,才能在同一向量空間裡做跨模態檢索。

產生物 URL 是臨時簽名地址,入庫前建議轉存。生圖與生視頻返回的 URL 均帶 OSS 簽名,實測有效期間約 24 小時。這會帶來兩類影響:

情境

影響

庫中 media_ref 的 URL 到期後做文本檢索

仍能正常召回(向量在寫入時已產生並落庫),但返回的 URL 已無法訪問,業務側展示素材會失敗。

用已失效的 URL 作為查詢做以圖搜圖

整個 search 請求報錯 code 65535 ... download form url error,不會降級或跳過該條查詢。

警告

因此生產環境中應在入庫前把產生物轉存到自己的 OSS,並把長期有效地址寫入 media_ref;同時保證查詢側傳入的媒體 URL 可訪問。

步驟五:內容搜尋

素材入庫後,用同一個多模態模型把查詢映射到同一向量空間,檢索最相似的素材實現複用。由於 Collection 已綁定 qwen3-vl-embedding 的 Function,search 的 data 裡直接傳原始文本或媒體 URL 即可,Milvus 會自動向量化查詢。

# ========== 步驟 5:內容搜尋 以文搜圖 / 以文搜視頻 / 以圖搜視頻 ==========
def search_assets(query: str, top_k: int = 5, media_type: str | None = None,
                  min_score: float = 0.0):
    """query 可以是一段中文/英文文案,也可以是一張查詢圖片的 URL。
    min_score:相似性閾值,低於該值的結果視為不相關,直接丟棄。"""
    filter_expr = f'media_type == "{media_type}"' if media_type else ""
    results = client.search(
        collection_name=collection_name,
        data=[query],                 # 文本或圖片 URL,均由 qwen3-vl-embedding 向量化
        anns_field="embedding",
        limit=top_k,
        filter=filter_expr,
        output_fields=["media_type", "media_ref", "caption", "tags"],
    )
    for rank, hit in enumerate(results[0], 1):
        if hit["distance"] < min_score:
            continue
        e = hit["entity"]
        url = e["media_ref"].split("?")[0]   # 去掉 OSS 簽名參數,展示更乾淨
        print(f"  {rank}. [相似性 {hit['distance']:.4f}] [{e['media_type']}] {e['caption'][:42]}")
        print(f"       {url}")


# 以文搜圖:限定只搜圖片
print("[5.1] 以文搜圖:")
search_assets("雨夜撐傘的女子站在便利店門口", media_type="image")

# 以文搜視頻:限定只搜視頻
print("[5.2] 以文搜視頻:")
search_assets("雨夜街頭鏡頭緩慢推進的運動鏡頭", media_type="video")

# 以圖搜視頻:用一張主要畫面格圖片 URL 去召迴風格相近的視頻素材
print("[5.3] 以圖搜視頻(用主要畫面格圖召回同源視頻):")
search_assets(keyframe_url, media_type="video")

# 跨模態混合檢索:去掉 filter,在圖片與視頻混合庫中檢索
print("[5.4] 跨模態混合檢索:")
search_assets("雨夜便利店暖光鏡頭")

實測三路檢索的召回結果與相似性:

檢索方式

查詢

命中

相似性

以文搜圖

雨夜撐傘的女子站在便利店門口

image

0.4834

以文搜視頻

雨夜街頭鏡頭緩慢推進的運動鏡頭

video

0.4967

以圖搜視頻

主要畫面格圖片 URL

video

0.8715

跨模態混合(無 filter)

雨夜便利店暖光鏡頭

video / image

0.4967 / 0.3816

其中以圖搜視頻達到 0.8715,是四種方式中最高的——該視頻正是由這張主要畫面格產生的,同源素材在同一向量空間下相似性極高。這也直觀體現了「圖片和視頻共用同一向量空間」的價值:把查詢文案換成一張圖片 URL,同一個 search 調用即可完成以圖搜圖與以圖搜視頻;去掉 filter 則在圖片與視頻混合庫中跨模態檢索。

查詢文案要與素材主題相關,並配合相似性閾值。多模態檢索按相似性排序返回 Top-K,即使查詢與素材完全無關也會返回結果。實測對比:

查詢文案

與素材的關係

相似性

雨夜撐傘的女子站在便利店門口

主題匹配

0.4834

室內暖光下的條紋毛衣造型

與素材無關

0.0715

角色近景,鏡頭緩慢推進的概念片

與素材無關

0.0694

說明

相差近 7 倍。因此應用側應結合相似性閾值過濾(本例 search_assets 的 min_score 參數),把明顯不相關的長尾結果丟棄,避免把 0.07 這類結果當作有效召回展示給使用者。具體閾值需按業務素材分布調參。

方案價值

與自己從零對接各家產生模型、再拼一套檢索系統相比:

維度

傳統自建方案

阿里雲 Milvus

產生模型接入

文生圖、圖生圖、文生視頻各家 SDK、鑒權與返回格式分散對接

AI Function 統一封裝生圖、生視頻與內容理解

非同步任務管理

自建任務隊列、輪詢退避、終態判定與逾時兜底

tasks/describe 標準輪詢介面,終態判定清晰

素材理解

自建或外接標題、描述、標籤服務

AI_MULTI_MODAL_GENERATE 內建多模態理解

向量化鏈路

應用側先調 embedding 再寫入

寫入即向量化,由 Collection Function 自動產生

檢索入口

圖片、視頻、文本、向量四份資料割裂,跨系統拼裝

單庫單次 search 完成跨模態檢索

這套方案的價值不只是產生快,更在於把每一次產生都沉澱為可檢索、可複用的素材資產,讓內容團隊從一次性消耗走向資產化經營。新劇本出現相似鏡頭需求時,先在歷史素材庫裡語義檢索一遍,能複用的直接複用,不能複用的再走產生,邊際成本持續下降。