首页
学习
活动
专区
圈层
工具
发布
社区首页 >专栏 >一个字段,搜遍所有媒体:用 semantic 字段为微信打造多模态搜索

一个字段,搜遍所有媒体:用 semantic 字段为微信打造多模态搜索

原创
作者头像
点火三周
发布2026-08-18 17:56:04
发布2026-08-18 17:56:04
2951
举报

一个字段,搜遍所有媒体:用 semantic 字段为微信打造多模态搜索

Elasticsearch 9.5 · 实战案例

图片、视频、音频,一个 mapping,一条 match 查询。Elasticsearch 9.5 的 semantic 字段让多模态搜索像文本搜索一样简单。

🗓 2026 年 8 月 · 🏷 Elasticsearch 9.5 · Jina v5-omni · 多模态 · ⏱ 阅读约 15 分钟


微信里几年的聊天记录,藏着大量珍贵的照片、语音和视频——但它们一直是"存进去就找不到"的黑洞。本文展示如何用 Elasticsearch 9.5 的新 semantic 字段,配合 Jina Embeddings v5-omni-small 多模态模型,用不到 50 行核心代码,把图片、视频、音频统一纳入语义搜索。

目录

  1. 为什么需要多模态搜索
  2. 核心:semantic 字段
  3. 系统架构
  4. 5 步快速上手
  5. 4 种查询方式
  6. 完整 Web 应用
  7. 生产建议
  8. 总结

为什么需要多模态搜索

微信本地数据的特征很有代表性:大量媒体文件,却几乎没有任何文本标签。一张照片的文件名是 bd9de7d1.dat,一段视频是 1781612096.mp4,一条语音是 音乐疗愈.m4a——传统关键词搜索对这类数据几乎无能为力。

多模态语义搜索解决这个问题的方式,是把所有媒体映射到同一个向量空间:"海边风景"这个文本短语,和一张海滩照片,会落在空间里的相近位置。你可以用文字找图片,用图片找视频,用一段旋律找相似音频。

传统方案 vs ES 9.5 semantic 字段

传统方案

  • 手动维护 ingest pipeline
  • 为每种模态单独配置模型
  • 手动调用推理 API 生成向量
  • 查询时需手动计算 query embedding
  • 图片、视频、音频三套代码逻辑

ES 9.5 semantic 字段(新)

  • 一个字段声明,Ingest 自动生成 embedding
  • 一个模型处理所有模态
  • 无需手动调用推理 API
  • match 查询即可完成文字搜索
  • 图片/视频/音频统一格式

核心:semantic 字段

semantic 字段是 Elasticsearch 9.5 引入的新类型,是 semantic_text(文本专用)的多模态扩展版。它在 ingest 阶段自动调用配置好的推理端点,把传入的媒体内容转化为向量并存储。

💡 与 semantic_text 的关键区别 semantic_text 使用 text_embedding task type,只处理文本。semantic 字段使用 embedding task type,支持图片、视频、音频、PDF 及文本的混合输入。两者共存,各司其职。

模态

输入格式

MIME 示例

图片

{"type": "image", "value": "data:image/jpeg;base64,..."}

JPEG、PNG、WebP

视频

{"type": "video", "value": "data:video/mp4;base64,..."}

MP4(最多 32 帧均匀采样)

音频

{"type": "audio", "value": "data:audio/mpeg;base64,..."}

MP3、M4A、WAV

PDF

{"type": "pdf", "value": "data:application/pdf;base64,..."}

视觉化处理

文本

直接传字符串

⚠️ 1 MB 限制 Elasticsearch 默认对每个二进制输入设置 1 MB 上限(indices.inference.max_binary_input_size)。大文件必须在索引前压缩或缩小。本文后续会展示具体的压缩方案。


系统架构

整体数据流

代码语言:bash
复制
📁 微信本地文件          ⚙️ 预处理脚本           📤 PUT _doc            🧠 Elasticsearch 9.5
图片 / 视频 / 音频  →   压缩 · Base64 编码  →   结构化 JSON        →   semantic 字段自动 embedding

🔍 用户查询              🌐 搜索 Web App         match / knn 查询       📊 结果排序
文字 / 图片 / 视频 / 音频  →  Python HTTP Server  →  ES 自动生成 query vector  →  相似度 > 60% 高亮展示

图 1:Kibana 中 wechat-multimodal 索引概览 — 文档数、索引大小、字段列表


5 步快速上手

第 1 步:确认推理端点

Elasticsearch 9.5 内置了 .jina-embeddings-v5-omni-small 推理端点,支持 Elastic Cloud Hosted、Serverless 以及开启 Cloud Connected Mode 的自管集群,无需手动创建。

代码语言:bash
复制
# 验证推理端点已就绪
GET _inference/embedding/.jina-embeddings-v5-omni-small

第 2 步:创建索引 Mapping

核心只有一个字段:把 embedding 声明为 semantic 类型,绑定推理端点。其他字段存储元数据(文件路径、媒体类型、时间)。

代码语言:json
复制
{
  "mappings": {
    "properties": {
      "embedding": {
        "type": "semantic",
        "inference_id": ".jina-embeddings-v5-omni-small"
      },
      "file_path":   { "type": "keyword" },
      "file_name":   { "type": "keyword" },
      "media_type":  { "type": "keyword" },
      "date_folder": { "type": "keyword" },
      "file_size":   { "type": "long"    },
      "indexed_at":  { "type": "date"    }
    }
  }
}

就这些。Elasticsearch 会在每次文档写入时自动调用推理端点并存储向量。

第 3 步:索引图片

把图片读为字节,Base64 编码后放入结构化对象。大图片先用 PIL 缩小到 900 KB 以内。

代码语言:python
复制
def process_image(path: Path) -> tuple[bool, str]:
    raw  = path.read_bytes()
    # 超过 900 KB 自动缩小,保持语义不变
    data = raw if len(raw) <= MAX_BINARY else resize_image(path)
    b64  = base64.b64encode(data).decode()
    mime = "image/png" if path.suffix.lower() == ".png" else "image/jpeg"

    doc = {
        "embedding": {          # ← ES 自动生成向量,无需手动调推理 API
            "type":  "image",
            "value": f"data:{mime};base64,{b64}"
        },
        "file_path":  str(path),
        "file_name":  path.name,
        "media_type": "image",
        "indexed_at": datetime.utcnow().isoformat() + "Z",
    }
    es_request("PUT", f"/{INDEX}/_doc/{path.stem}", doc)

第 4 步:索引视频

视频超过 900 KB 时用 ffmpeg 压缩:降分辨率、提高 CRF、截取前 30 秒。模型内部会均匀抽取最多 32 帧,自动理解视频整体内容。

代码语言:python
复制
def compress_video(path: Path) -> bytes:
    tmp = tempfile.NamedTemporaryFile(suffix=".mp4", delete=False)
    subprocess.run([
        "ffmpeg", "-y", "-i", str(path),
        "-vf", "scale='min(320,iw)':-2",
        "-crf", "40", "-preset", "fast",
        "-t", "30", "-an",     # 去音频,取前 30 秒
        tmp.name,
    ], capture_output=True, check=True)
    return Path(tmp.name).read_bytes()

# 索引时结构与图片完全一致,只改 type
doc["embedding"] = {
    "type":  "video",
    "value": f"data:video/mp4;base64,{b64}"
}

第 5 步:索引音频

音频用 ffmpeg 转为 16 kHz 单声道 MP3,码率 32 kbps。语音、音乐、环境声都可以索引。

代码语言:python
复制
def compress_audio(path: Path) -> bytes:
    tmp = tempfile.NamedTemporaryFile(suffix=".mp3", delete=False)
    subprocess.run([
        "ffmpeg", "-y", "-i", str(path),
        "-ar", "16000",   # 16 kHz 采样率
        "-ac", "1",       # 单声道
        "-b:a", "32k",    # 32 kbps 码率
        "-t", "120",      # 最长 2 分钟
        tmp.name,
    ], capture_output=True, check=True)
    return Path(tmp.name).read_bytes()

💡 更少代码,更多模态 三种媒体的索引代码结构完全一致——只有 type 字段不同。这是 semantic 字段设计的核心优势:统一的数据模型,统一的 ingest 路径。


4 种查询方式

semantic 字段最大的易用性提升体现在查询端:文字查询只需一条普通的 match,Elasticsearch 在服务端自动生成 query embedding;媒体查询使用 knn + query_vector_builder,格式与索引时完全对称。

① 文字查询(最常用)

用自然语言描述想找的内容,ES 自动调用推理端点将文字转为向量并执行语义搜索。

代码语言:json
复制
GET wechat-multimodal/_search
{
  "query": {
    "match": {
      "embedding": "海边风景,傍晚日落"
    }
  },
  "_source": { "excludes": ["embedding"] }
}

② 图片查询(以图搜图)

上传一张图片,找语义相似的内容。格式与索引时完全一致。

代码语言:json
复制
GET wechat-multimodal/_search
{
  "query": {
    "knn": {
      "field": "embedding",
      "query_vector_builder": {
        "embedding": {
          "input": {
            "type":  "image",
            "value": "data:image/jpeg;base64,<base64-encoded-bytes>"
          }
        }
      },
      "num_candidates": 100,
      "k": 20
    }
  }
}

③ 视频查询

用一段视频片段搜索语义相似的视频——只需把 type 改为 "video",结构完全相同。

代码语言:json
复制
GET wechat-multimodal/_search
{
  "query": {
    "knn": {
      "field": "embedding",
      "query_vector_builder": {
        "embedding": {
          "input": {
            "type":  "video",
            "value": "data:video/mp4;base64,<base64-encoded-bytes>"
          }
        }
      },
      "k": 10
    }
  }
}

④ 音频查询

以音频片段搜索相似音频——同样只改 type 字段。

代码语言:json
复制
GET wechat-multimodal/_search
{
  "query": {
    "knn": {
      "field": "embedding",
      "query_vector_builder": {
        "embedding": {
          "input": {
            "type":  "audio",
            "value": "data:audio/mpeg;base64,<base64-encoded-bytes>"
          }
        }
      },
      "k": 10
    }
  }
}

🔑 设计一致性 注意索引格式和查询格式完全对称:索引时 {"type": "image", "value": "data:..."},查询时 input: {"type": "image", "value": "data:..."}。切换模态只需改 type,其余代码不动。


完整 Web 应用

在上述 API 基础上,我们用 Python 标准库搭建了一个本地搜索 Web App,无需额外框架依赖。

后端搜索逻辑(Python)

代码语言:python
复制
def text_search(query: str, size=20, filter_type=None):
    """文字查询:match 即可,ES 自动生成 embedding"""
    body = {
        "query": { "match": { "embedding": query } },
        "_source": SOURCE_FIELDS,
        "size": size,
    }
    if filter_type:
        body["query"] = {
            "bool": {
                "must":   { "match": { "embedding": query } },
                "filter": { "term": { "media_type": filter_type } }
            }
        }
    return es_request("POST", f"/{INDEX}/_search", body)


def media_search(media_type: str, data_uri: str, size=20):
    """媒体查询:knn + query_vector_builder,type 动态传入"""
    return es_request("POST", f"/{INDEX}/_search", {
        "query": {
            "knn": {
                "field": "embedding",
                "query_vector_builder": {
                    "embedding": {
                        "input": { "type": media_type, "value": data_uri }
                    }
                },
                "num_candidates": size * 5,
                "k": size,
            }
        },
        "_source": SOURCE_FIELDS,
        "size": size,
    })

结果展示:按相似度分组

搜索结果按相似度阈值分两组:≥ 60% 的直接展示,< 60% 的折叠显示,让用户一眼区分高相关和低相关结果。

图 3:搜索 Web App 界面 — 文字查询 tab,输入框,筛选下拉,高相关区 + 折叠低相关区

图 4:图片模态查询结果 — 上传一张图片,返回语义相似的图片/视频结果网格


生产建议

问题

建议

二进制数据超过 1 MB

图片用 PIL 缩小质量;视频用 ffmpeg 降分辨率 + 截短;音频降码率到 32 kbps

响应体积过大

搜索时用 "_source": {"excludes": ["embedding"]},排除存储的 base64 数据

索引速度慢

推理端点有速率限制,并发 Worker 控制在 2,失败后指数退避重试

向量存储空间大

使用 index_options.dense_vector.type: "int8_hnsw" 开启 int8 量化,节省 ~75% 空间

混合搜索(关键词 + 语义)

linearrrf retriever,fields 列表同时包含文本字段和 semantic 字段


总结

Elasticsearch 9.5 的 semantic 字段把多模态搜索从一个需要大量工程投入的方向,变成了和普通文本搜索同等难度的事情:

  • Mapping 就一行:声明 "type": "semantic" 和 inference_id,其余交给 ES
  • 索引格式统一:图片、视频、音频都是 {"type": "...", "value": "data:..."}
  • 查询极简:文字用 match,媒体用 knn,无需手动生成 embedding
  • 跨模态天然支持:所有内容落在同一向量空间,文字找图片、图片找视频开箱即用

对于微信数据这样"有媒体无标签"的场景,semantic 字段几乎是零阻力的解决方案。唯一需要关注的工程细节是 1 MB 的二进制上限——做好文件压缩,其余基本是"能跑就能搜"。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

目录
  • 一个字段,搜遍所有媒体:用 semantic 字段为微信打造多模态搜索
    • 目录
    • 为什么需要多模态搜索
      • 传统方案 vs ES 9.5 semantic 字段
    • 核心:semantic 字段
    • 系统架构
    • 5 步快速上手
      • 第 1 步:确认推理端点
      • 第 2 步:创建索引 Mapping
      • 第 3 步:索引图片
      • 第 4 步:索引视频
      • 第 5 步:索引音频
    • 4 种查询方式
      • ① 文字查询(最常用)
      • ② 图片查询(以图搜图)
      • ③ 视频查询
      • ④ 音频查询
    • 完整 Web 应用
      • 后端搜索逻辑(Python)
      • 结果展示:按相似度分组
    • 生产建议
    • 总结
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档