StackTraces
Meilisearch从零跑通

从零跑通 Meilisearch 内置 embedder 自动嵌入:8 步,外加 3 个容易卡住的地方

@debug_master2026-10-11

跟着这篇走完,你会把 Meilisearch 的语义搜索从“手动转向量”换成“内置 embedder 自动嵌入”:在索引设置里配一次 huggingFace 类型的 embedder,文档入库和查询都不用你再碰向量——传进去的是纯文本,Meilisearch 自己把文本转成 512 维向量存起来,搜索时它又自己把你的查询词转成向量去比对。整个过程 8 步,每步都有一条命令能验证自己做对了没有。

动手前先交代环境。localhost:7700 是 Meilisearch 的默认监听地址,master key 固定用 aSampleMasterKey(与系列一致),索引沿用上篇的 books_vec(还是那 6 条虚构书籍),你要是换了端口、master key、索引名或嵌入模型,把示例值替换掉就行。这篇默认你已经装好并跑通了从零跑通 Meilisearch 本地搜索服务,安装、启动、异步任务轮询的套路全部复用第一篇。跟上篇最大的区别说在前面:上一篇(userProvided)要自己装 Python、跑模型、把文档和查询都算成 512 维向量再交给 Meilisearch;这篇一行 Python 都不用写,向量这回事整个交给服务端。

Meilisearch:从零跑通内置 embedder 自动嵌入的 8 个步骤

前置条件

项 要求 说明
Meilisearch v1.54.3 或更高 内置 embedder(嵌入器)v1.6 起才有,低于这个版本配置接口会不认
前置文章 已跑通 meilisearch-local-search 复用其安装/启动/master key/异步任务轮询套路;没跑通先回去补
网络 首次需能访问 HuggingFace 模型首次使用时自动下载;完全离线或内网环境会卡在下载这一步
master key aSampleMasterKey 与系列一致
curl 任意版本 全文用 curl 调 REST API

先跑这条,确认第一篇的服务还活着。

curl http://localhost:7700/health
{
  "status": "available"
}

返回 {"status":"available"} 就满足条件。要是返回 curl: (7) Failed to connect to localhost port 7700,说明服务没起——回第一篇第一步用 meilisearch --master-key="aSampleMasterKey" 重新启动,再回来。

第一步:建 books_vec 索引,直接传纯文本文档(不带向量)

上一篇到这一步之前,得先装 Python、跑 bge 模型把 6 本书转成带 _vectors 的 JSON,然后才能传文档。内置 embedder 模式不用走那套——文档就是普通 JSON,向量由服务端在后台自动生成,所以这一步先建索引、把纯文本传进去,验证“不带向量也能入库”。

索引走显式创建,指定主键 id,沿用系列惯例:

curl -X POST http://localhost:7700/indexes \
  -H "Authorization: Bearer aSampleMasterKey" \
  -H "Content-Type: application/json" \
  -d '{"uid":"books_vec","primaryKey":"id"}'
{
  "taskUid": 20,
  "indexUid": "books_vec",
  "status": "enqueued",
  "type": "indexCreation",
  "enqueuedAt": "2026-10-11T10:00:00.000Z"
}

接着传文档。注意看,字段里有 id、title、author 和 summary(书的简介,后面模板要把它拼进去),没有 _vectors:

curl -X POST http://localhost:7700/indexes/books_vec/documents \
  -H "Authorization: Bearer aSampleMasterKey" \
  -H "Content-Type: application/json" \
  --data-binary '[
    {"id":1,"title":"三体","author":"刘慈欣","summary":"仰望星空,写人类文明与三体文明星际博弈的科幻巨作"},
    {"id":2,"title":"球状闪电","author":"刘慈欣","summary":"以球状闪电为引的科幻小说,物理奇观与人生选择"},
    {"id":3,"title":"活着","author":"余华","summary":"写普通人在苦难中活下去的故事,农村家庭的伦理与温情"},
    {"id":4,"title":"许三观卖血记","author":"余华","summary":"写小人物靠卖血养家的故事,底层生活的坚韧与荒诞"},
    {"id":5,"title":"明朝那些事儿","author":"当年明月","summary":"用通俗笔法写明朝三百年历史,官场斗争与王朝兴衰"},
    {"id":6,"title":"万历十五年","author":"黄仁宇","summary":"从万历十五年切入,剖析明朝制度的运转与深层困境"}
  ]'
{
  "taskUid": 21,
  "indexUid": "books_vec",
  "status": "enqueued",
  "type": "documentAdditionOrUpdate",
  "enqueuedAt": "2026-10-11T10:01:00.000Z"
}

上传是异步的,等任务 succeeded 后验证文档确实进去了:

curl http://localhost:7700/tasks/21 -H "Authorization: Bearer aSampleMasterKey"
{
  "uid": 21,
  "indexUid": "books_vec",
  "status": "succeeded",
  "type": "documentAdditionOrUpdate",
  "details": {
    "receivedDocuments": 6,
    "indexedDocuments": 6
  }
}

看到 indexedDocuments: 6 就说明 6 条纯文本都落库了。注意:现在这个索引还没有任何向量,也做不了语义搜索——向量是下一步配了 embedder 之后才自动生成的。

第二步:配置 huggingFace embedder(model + documentTemplate)

Meilisearch 默认只会做关键词检索,让它自己转向量,得先在索引设置里声明一个 embedder。embedder(嵌入器)就是一条“向量由谁生成、用什么模型、拿文档的哪些字段去嵌入”的配置。这里用 huggingFace 源——意思是模型跑在你本机,不需要任何 API key,Meilisearch 首次使用时会自动从 HuggingFace 下载模型。这一步不配,后面文档永远只有纯文本,搜了也白搜。

documentTemplate(文档模板)用 Liquid 模板语法把每条文档拼成一段要送去嵌入的文本:{{doc.title}} 取文档的 title 字段,{{doc.summary}} 取简介。这里的 doc 是模板引擎的固定变量名,指“当前正在嵌入的这条文档”——建索引、传文档时都不需要声明它,Meilisearch 渲染模板时自动把每条文档注入成 doc;所以 {{doc.xxx}} 里写的字段名,跟第一步文档 JSON 的键一一对应(title、author、summary),写成文档里不存在的键,整批嵌入就会报错(第七步的 {{doc.genre}} 就是这么翻车的)。语义搜索比对的就是这段渲染文本,所以模板里放哪些字段,决定了能被什么样的查询搜到——只有书名和作者时,模型只能靠对名作的认识来匹配;把简介拼进去,“按内容搜”才有真实依据。它决定了“模型看到的这段文字是什么”,直接影响检索质量。

curl -X PATCH http://localhost:7700/indexes/books_vec/settings/embedders \
  -H "Authorization: Bearer aSampleMasterKey" \
  -H "Content-Type: application/json" \
  -d '{"default":{"source":"huggingFace","model":"BAAI/bge-small-zh-v1.5","documentTemplate":"中文书籍,书名《{{doc.title}}》,作者 {{doc.author}},简介 {{doc.summary}}"}}'
{
  "taskUid": 23,
  "indexUid": "books_vec",
  "status": "enqueued",
  "type": "settingsUpdate",
  "enqueuedAt": "2026-10-11T10:05:00.000Z"
}

model 用的还是系列前两篇那个 BAAI/bge-small-zh-v1.5(中文、输出 512 维),跟前几篇的向量完全同一路数,只是生成方从你本地的 Python 换成了 Meilisearch 服务端。bge 系模型架构一致,官方推荐列表里是 BAAI/bge-base-en-v1.5 这些英文模型,中文模型没单列,Meilisearch 侧实际加载行为以官方文档和你的运行结果为准。注意这里不用像 userProvided 那样声明 dimensions——huggingFace 源的维度由模型自己决定,配了反而多余。

验证,把 embedder 设置读回来看看。

curl http://localhost:7700/indexes/books_vec/settings/embedders -H "Authorization: Bearer aSampleMasterKey"
{
  "default": {
    "source": "huggingFace",
    "model": "BAAI/bge-small-zh-v1.5",
    "documentTemplate": "中文书籍,书名《{{doc.title}}》,作者 {{doc.author}},简介 {{doc.summary}}"
  }
}

读到 {"default":{"source":"huggingFace",...}} 说明配置落进去了。embedder 名字是 default,后面搜索时 hybrid.embedder 要跟它一致。如果读出来是 {},多半是设置任务还没跑完就查了,把 taskUid 轮询到 succeeded 再看。这一步提交后 Meilisearch 会在后台干活:下载模型、给 6 条文档逐个嵌入——这正是下一步要盯的事。

顺带说清一个容易想歪的点:语义搜索比对的是 documentTemplate 渲染出来的文本向量,不是“读了书的内容”。后文查询“仰望星空,讲人类文明命运的科幻巨作”能命中《三体》,靠的正是模板里拼进去的 summary——简介写了“科幻、人类文明”,模型把查询向量跟这段简介对齐。如果模板只放书名和作者,也能靠模型对《三体》这类名作的先验勉强命中,但那层匹配不稳定,换个冷门书就不灵了。

第三步:盯任务队列,等模型下载 + 全量自动嵌入完成

配置提交了,但嵌入是异步的。Meilisearch 拿到 embedder 配置后会先下载模型(bge-small 系列权重约 90 多 MB,看网速,可能要几分钟),然后批量处理索引里所有文档、给每条生成 512 维向量,最后把结果缓存下来——文档内容不变就不会重算。任务没跑完就去搜,会搜到“没有向量的索引”,结果跟普通关键词没区别。 所以这一步就是盯任务队列。

轮询刚才的 taskUid:

curl http://localhost:7700/tasks/23 -H "Authorization: Bearer aSampleMasterKey"
{
  "uid": 23,
  "indexUid": "books_vec",
  "status": "succeeded",
  "type": "settingsUpdate",
  "enqueuedAt": "2026-10-11T10:05:00.000Z",
  "startedAt": "2026-10-11T10:05:01.000Z",
  "finishedAt": "2026-10-11T10:08:32.000Z"
}

验证就一条:status: "succeeded"。任务从 enqueued → processing → succeeded,中间卡几分钟是正常的(首次在下载模型)。要是变成 failed,八成是模板渲染问题——documentTemplate 里引用了文档里不存在的字段,整批嵌入会失败,报 template_rendering_error,排查入口见Meilisearch documentTemplate 引用缺失字段报 template_rendering_error。想确认向量真生成了,等第四步用搜索来验证最直观。

第四步:纯文本查询做语义搜索(不带 vector,查询自动嵌入)

这一步是整篇的答案。上一篇的 userProvided 模式,检索前要先跑 Python 给查询词算一个 512 维向量,再塞进 vector 参数发过去;内置 embedder 模式不用了——q 直接传中文文本,Meilisearch 用同一个模型自动把查询词转成向量,然后跟库里每条文档的向量比对相似度。

curl -X POST http://localhost:7700/indexes/books_vec/search \
  -H "Authorization: Bearer aSampleMasterKey" \
  -H "Content-Type: application/json" \
  -d '{"q":"仰望星空,讲人类文明命运的科幻巨作","hybrid":{"embedder":"default","semanticRatio":1.0},"showRankingScore":true}'

hybrid(混合检索)对象里 embedder 指名用哪个嵌入器(这里就是第二步配的 default),semanticRatio(语义占比)控制关键词与语义结果的比例,1.0 就是只看语义。showRankingScore: true 让返回带上 _rankingScore(相关性分数,v1.8 起语义分并入这个字段,之前的 _semanticScore 已移除)。返回体按官方文档结构大致长这样(具体分值以你本地实测为准):

{
  "hits": [
    {
      "id": 1,
      "title": "三体",
      "author": "刘慈欣",
      "_rankingScore": 0.82
    },
    {
      "id": 2,
      "title": "球状闪电",
      "author": "刘慈欣",
      "_rankingScore": 0.71
    }
  ],
  "query": "仰望星空,讲人类文明命运的科幻巨作",
  "processingTimeMs": 3,
  "estimatedTotalHits": 2
}

验证方式是做一次对照实验:同一个查询词去掉 hybrid 跑普通关键词搜索。

curl -X POST http://localhost:7700/indexes/books_vec/search \
  -H "Authorization: Bearer aSampleMasterKey" \
  -H "Content-Type: application/json" \
  -d '{"q":"仰望星空,讲人类文明命运的科幻巨作"}'
{
  "hits": [
    {
      "id": 1,
      "title": "三体",
      "author": "刘慈欣",
      "summary": "仰望星空,写人类文明与三体文明星际博弈的科幻巨作"
    },
    {
      "id": 2,
      "title": "球状闪电",
      "author": "刘慈欣",
      "summary": "以球状闪电为引的科幻小说,物理奇观与人生选择"
    }
  ],
  "query": "仰望星空,讲人类文明命运的科幻巨作",
  "processingTimeMs": 1,
  "estimatedTotalHits": 2
}

关键词搜索同样命中这两条,但排位按字面重合度来:《三体》简介里「仰望星空、人类文明、科幻巨作」字面全在,排第一;《球状闪电》只有「科幻」一个词沾边,靠默认的 last 匹配策略在最后一层才被捞进来。语义搜索按向量相似度排序,两条分数差距明显更小——模板里拼进去的 summary(三体的“人类文明、科幻巨作”、球状闪电的“科幻、物理奇观”)跟查询描述对上了,模型拿查询向量跟每条简介向量比相似度。这正好回答开头那个问题:查询转向量这步,真不用自己做了。(具体命中与分值以本地实测为准。)要是返回 400,先看错误码——hybrid.embedder 写错名字会报 invalid_search_embedder,hybrid 结构写错会报 invalid_search_hybrid_query,排查入口都是这篇(见Meilisearch semanticRatio 越界或 embedder 名写错报错)。

第五步:写入一条新文档,等自动嵌入完成后立即再查

第二步配好 embedder 后,自动嵌入不只发生在初始那 6 条文档上——之后每次新增文档,Meilisearch 都会在入库时自动转向量。这一步验证写入路径:传一条新书,任务跑完直接搜,新书应该能命中语义查询。注意看,传的还是纯文本,没有 _vectors 字段:

curl -X POST http://localhost:7700/indexes/books_vec/documents \
  -H "Authorization: Bearer aSampleMasterKey" \
  -H "Content-Type: application/json" \
  -d '{"id":7,"title":"围城","author":"钱钟书","summary":"写婚姻与人生的围城困局,知识分子的困惑与讽刺"}'
{
  "taskUid": 24,
  "indexUid": "books_vec",
  "status": "enqueued",
  "type": "documentAdditionOrUpdate",
  "enqueuedAt": "2026-10-11T10:10:00.000Z"
}

新文档也要先过一遍嵌入,照旧轮询任务号:

curl http://localhost:7700/tasks/24 -H "Authorization: Bearer aSampleMasterKey"
{
  "uid": 24,
  "indexUid": "books_vec",
  "status": "succeeded",
  "type": "documentAdditionOrUpdate",
  "details": {
    "receivedDocuments": 1,
    "indexedDocuments": 1
  }
}

验证:搜一个能映射到《围城》的语义查询,它应该进结果并且排前面:

curl -X POST http://localhost:7700/indexes/books_vec/search \
  -H "Authorization: Bearer aSampleMasterKey" \
  -H "Content-Type: application/json" \
  -d '{"q":"婚姻是围城,困住人的故事","hybrid":{"embedder":"default","semanticRatio":1.0},"showRankingScore":true}'
{
  "hits": [
    {
      "id": 7,
      "title": "围城",
      "author": "钱钟书",
      "_rankingScore": 0.79
    }
  ],
  "query": "婚姻是围城,困住人的故事",
  "processingTimeMs": 3,
  "estimatedTotalHits": 1
}

hits 里出现 id: 7 的《围城》就对了——围城的 summary 写的是“写婚姻与人生的围城困局”,跟查询“婚姻是围城,困住人的故事”说的是同一件事,所以直接命中,这就是把内容写进模板的效果。这一步跟第三步传初始 6 条文档没有任何区别——都是纯文本进去、自动嵌入、立刻可查。写入路径(入库自动转向量)验证通过。要是任务没跑完就去搜,新文档会搜不到,排查见Meilisearch 添加文档后立即搜索返回空结果。

第六步:更新一条文档,等自动重嵌入完成后再次查询

更新路径同理:改文档内容 → Meilisearch 重新给这条文档生成向量、替换旧向量,内容没变的文档不会重算(文档不变不重算,是自动嵌入的缓存策略)。这一步把第五步新加的 id: 7 从《围城》改成《边城》,然后查两次验证。

更新就是“同一条 id 再传一次”,POST /documents 对已存在的 id 是覆盖更新:

curl -X POST http://localhost:7700/indexes/books_vec/documents \
  -H "Authorization: Bearer aSampleMasterKey" \
  -H "Content-Type: application/json" \
  -d '{"id":7,"title":"边城","author":"沈从文","summary":"写湘西边陲小镇的乡土爱情,纯净而忧伤的故事"}'
{
  "taskUid": 25,
  "indexUid": "books_vec",
  "status": "enqueued",
  "type": "documentAdditionOrUpdate",
  "enqueuedAt": "2026-10-11T10:12:00.000Z"
}
curl http://localhost:7700/tasks/25 -H "Authorization: Bearer aSampleMasterKey"
{
  "uid": 25,
  "indexUid": "books_vec",
  "status": "succeeded",
  "type": "documentAdditionOrUpdate",
  "details": {
    "receivedDocuments": 1,
    "indexedDocuments": 1
  }
}

验证一:搜《边城》的语义查询,id: 7 以新书名命中:

curl -X POST http://localhost:7700/indexes/books_vec/search \
  -H "Authorization: Bearer aSampleMasterKey" \
  -H "Content-Type: application/json" \
  -d '{"q":"湘西边陲小镇的乡土爱情故事","hybrid":{"embedder":"default","semanticRatio":1.0},"showRankingScore":true}'
{
  "hits": [
    {
      "id": 7,
      "title": "边城",
      "author": "沈从文",
      "_rankingScore": 0.77
    }
  ],
  "query": "湘西边陲小镇的乡土爱情故事",
  "processingTimeMs": 3,
  "estimatedTotalHits": 1
}

验证二:回搜第五步那个“婚姻是围城”的查询,id: 7 不再出现——旧向量已被替换:

curl -X POST http://localhost:7700/indexes/books_vec/search \
  -H "Authorization: Bearer aSampleMasterKey" \
  -H "Content-Type: application/json" \
  -d '{"q":"婚姻是围城,困住人的故事","hybrid":{"embedder":"default","semanticRatio":1.0},"showRankingScore":true}'
{
  "hits": [],
  "query": "婚姻是围城,困住人的故事",
  "processingTimeMs": 2,
  "estimatedTotalHits": 0
}

id: 7 从“围城命中”变成“边城命中”,就是更新触发自动重嵌入的直接证据:全程只改了文档文本,向量一次都没手动碰过。到这里,写入、更新、查询三条路径的自动嵌入全部验证完。

第七步:改 documentTemplate 触发自动重嵌入,用 render-template 先预演

模板决定模型看到什么文本,模板写得差,语义就偏。但改模板有一个好处:不用重新传文档。改一下 embedder 配置,Meilisearch 检测到 documentTemplate 变了,会自动把全部文档重新嵌入一遍。这一步先介绍一个预演工具,再演示改模板重嵌入。

render-template 接口可以在不改任何配置的情况下,把某条文档套上模板看渲染结果——模板里有拼写错误、引用了不存在的字段,在这里就能提前发现:

curl -X POST http://localhost:7700/render-template \
  -H "Authorization: Bearer aSampleMasterKey" \
  -H "Content-Type: application/json" \
  -d '{"template":{"kind":"inlineDocumentTemplate","inline":"中文书籍,书名《{{doc.title}}》,作者 {{doc.author}},简介 {{doc.summary}}"},"input":{"kind":"inlineDocument","inline":{"id":1,"title":"三体","author":"刘慈欣","summary":"仰望星空,写人类文明与三体文明星际博弈的科幻巨作"}}}'
{
  "template": "中文书籍,书名《{{doc.title}}》,作者 {{doc.author}},简介 {{doc.summary}}",
  "rendered": "中文书籍,书名《三体》,作者 刘慈欣,简介 仰望星空,写人类文明与三体文明星际博弈的科幻巨作"
}

rendered 就是模型实际会看到的那段文本。想给模板加点上下文(比如分类信息),改完先预演一遍,确认没问题再提交设置:

curl -X PATCH http://localhost:7700/indexes/books_vec/settings/embedders \
  -H "Authorization: Bearer aSampleMasterKey" \
  -H "Content-Type: application/json" \
  -d '{"default":{"source":"huggingFace","model":"BAAI/bge-small-zh-v1.5","documentTemplate":"中文书籍,书名《{{doc.title}}》,作者 {{doc.author}},类型是 {{doc.genre}}"}}'
{
  "taskUid": 27,
  "indexUid": "books_vec",
  "status": "enqueued",
  "type": "settingsUpdate",
  "enqueuedAt": "2026-10-11T10:15:00.000Z"
}

等等,这里有个坑:{{doc.genre}} 引用了 genre 字段,但 6 条书里根本没有这个字段——现在轮询任务,你会看到它 failed:

curl http://localhost:7700/tasks/27 -H "Authorization: Bearer aSampleMasterKey"
{
  "uid": 27,
  "indexUid": "books_vec",
  "status": "failed",
  "type": "settingsUpdate",
  "error": {
    "message": "error while rendering template: user error: missing field in document: liquid: Unknown index",
    "code": "template_rendering_error",
    "type": "invalid_request",
    "link": "https://docs.meilisearch.com/errors#template_rendering_error"
  }
}

模板引用不存在的字段,整批嵌入直接失败——这就是第七步为什么先让你用 render-template 预演。修法有两个:把模板改回只引用存在的字段(title、author、summary),或者给文档补上 genre 字段再重新提交设置。改回去、任务 succeeded 后,重新搜第四步那个查询,Meilisearch 会用新模板生成的向量重新排序,分值会跟改模板前不一样——这就是“自动重嵌入”生效的证据。

第八步(进阶):加第二个 embedder,一个索引两套向量

一个索引可以配多个 embedder,各自用不同模型生成一套独立向量,搜索时用 hybrid.embedder 指定用哪套。这一步加一个官方推荐的多语言模型 sentence-transformers/paraphrase-multilingual-MiniLM-L12-v2(384 维,支持中文),跟 default 并存,然后对比同一查询在两家模型下的结果差异。

curl -X PATCH http://localhost:7700/indexes/books_vec/settings/embedders \
  -H "Authorization: Bearer aSampleMasterKey" \
  -H "Content-Type: application/json" \
  -d '{"default":{"source":"huggingFace","model":"BAAI/bge-small-zh-v1.5","documentTemplate":"中文书籍,书名《{{doc.title}}》,作者 {{doc.author}},简介 {{doc.summary}}"},"multilingual":{"source":"huggingFace","model":"sentence-transformers/paraphrase-multilingual-MiniLM-L12-v2","documentTemplate":"中文书籍,书名《{{doc.title}}》,作者 {{doc.author}},简介 {{doc.summary}}"}}'
{
  "taskUid": 29,
  "indexUid": "books_vec",
  "status": "enqueued",
  "type": "settingsUpdate",
  "enqueuedAt": "2026-10-11T10:20:00.000Z"
}

等任务 succeeded(第二个模型也要下载一次)。注意这个 PATCH 是整份替换 embedder 配置,提交时把两个 embedder 都写上,只写 multilingual 会把 default 顶掉。接着用同一个查询词,hybrid.embedder 切到 multilingual 再搜一次:

curl -X POST http://localhost:7700/indexes/books_vec/search \
  -H "Authorization: Bearer aSampleMasterKey" \
  -H "Content-Type: application/json" \
  -d '{"q":"仰望星空,讲人类文明命运的科幻巨作","hybrid":{"embedder":"multilingual","semanticRatio":1.0},"showRankingScore":true}'
{
  "hits": [
    {
      "id": 1,
      "title": "三体",
      "author": "刘慈欣",
      "_rankingScore": 0.78
    },
    {
      "id": 2,
      "title": "球状闪电",
      "author": "刘慈欣",
      "_rankingScore": 0.69
    }
  ],
  "query": "仰望星空,讲人类文明命运的科幻巨作",
  "processingTimeMs": 4,
  "estimatedTotalHits": 2
}

验证:同一个查询词,两个 embedder 都命中三体、球状闪电,但 _rankingScore 不同——说明两套向量都在工作、各算各的。多 embedder 的实际价值在混合场景:比如一个 embedder 管中文文本、另一个管图片向量(图片走 userProvided),检索时按需切换。到这一步,内置 embedder 的自动嵌入链路就完整跑通了。

核心概念

下面这张图是整条调用链路的走向:纯文本文档和纯文本查询在两端进,向量在 Meilisearch 内部自动生成,两边都不用你碰向量。

Meilisearch 内置 embedder 自动嵌入调用链路架构图

概念 是什么 类比
embedder 索引设置里声明“向量由谁生成、用什么模型、嵌入哪些字段”的配置项 快递柜上开好的储物格,声明了谁送货、货多长
自动嵌入(auto-embedding) 文档入库和查询检索时由服务端自动转向量,客户端只传文本 自助餐厅配餐,菜和汤都给你盛好,不用自己下厨
写入 / 更新自动嵌入 配好 embedder 后,新增或更新的文档入库时同样自动转向量;文档内容不变不重算(缓存生效) 补新菜、换旧菜都是后厨自动做,不用你亲自下厨
huggingFace(source) 嵌入源的一种:模型跑在本机,无需 API key,首次使用自动下载 家里自带的厨房,做饭不用叫外卖
documentTemplate 用 Liquid 语法把文档拼成嵌入文本的模板,决定模型看到什么 给模型看的“文档摘要卡”,只写重点
render-template 不改配置、先用样例文档预演模板渲染结果的接口 下馆子前先看菜单配图,避免点错菜
hybrid / semanticRatio 混合检索模式:关键词与语义并行检索后合并排序;semanticRatio 控制两者比例 查重名再查意思,旋钮拧到哪头就听哪头

常见问题

template_rendering_error

现象:提交 embedder 配置或改 documentTemplate 后任务变成 failed,error.code 是 template_rendering_error,message 含 “missing field in document: liquid: Unknown index”。

原因:documentTemplate 里用 {{doc.xxx}} 引用了文档中不存在的字段,整批嵌入失败。修法:把模板改回只引用真实存在的字段,或用 {% if doc.xxx %} 包一层守卫。

相关文章:Meilisearch documentTemplate 引用缺失字段报 template_rendering_error

invalid_search_embedder

现象:搜索时返回 400,错误码 invalid_search_embedder。

原因:hybrid.embedder 写的名字跟索引里配置的 embedder 对不上——比如配置的是 default 却写了 "Default",或者索引里根本没配过这个 embedder。大小写和名字都要严格一致。

相关文章:Meilisearch semanticRatio 越界或 embedder 名写错报错

配完 embedder 立刻搜索,结果跟普通关键词一样

现象:第二步配完 embedder 马上搜,hits 是纯关键词结果或空,语义完全没生效。

原因:嵌入是异步任务,模型下载加全量嵌入还没跑完你就去搜了。settingsUpdate / documentAddition 任务队列里的活没干完,索引还没有向量。轮询 taskUid 到 succeeded 再搜。

相关文章:Meilisearch 添加文档后立即搜索返回空结果

下一步

跟手动转向量的版本对比

这篇的自动嵌入版,跟之前 userProvided 手动转向量的版本是同一套搜索能力的两条实现路线。手动版要自己装 Python、生成向量、处理维度,自动版一行 Python 不用写——但手动版适合图片等非文本向量的场景。

从零跑通 Meilisearch 纯向量语义搜索

换一套纯向量数据库试试

Meilisearch 是“关键词 + 向量”双引擎,语义搜索是其中一半。如果你只要向量、不要关键词,可以对比纯向量数据库 Chroma 的玩法——那边从头到尾都是向量在说话。

从零跑通 Chroma 向量语义搜索

用 Ollama 当嵌入源

内置 embedder 除了本地 huggingFace 源,还支持 ollama 源(把 url 指向 http://localhost:11434/api/embeddings,模型用 nomic-embed-text 这类)和 openAi / rest 外部源。如果你的机器上已经跑着 Ollama,本地模型这一路可以先接上。

从零跑通 Ollama 本地对话模型

卡在哪一步了?

从零跑通的过程中撞到报错?把命令和输出记下来,帮助下一个刚起步的人。

贡献一条解法