从 RAG to Riches:用 Meilisearch 混合搜索构建更可靠的检索增强生成

2026/9/13·2 views

如果你正在构建基于大语言模型的应用,大概率绕不开 RAG。但很多团队在完成第一个 demo 后会发现一个尴尬的事实:检索环节经常拖后腿。用户问“怎么快速做一顿高蛋白早餐”,向量检索可能返回一堆讲“蛋白质粉溶解度”的文章——语义上好像沾边,实际上答非所问。反过来,纯关键词检索又搞不定用户用自然语言描述需求时产生的词汇鸿沟。

这就是混合搜索的用武之地。而 Meilisearch 的混合搜索有一个关键区别:它不做简单的排名拼接。

为什么传统混合搜索不够好

很多混合搜索的实现方式是“分别检索,然后用 Reciprocal Rank Fusion 合并排名”。这种做法有一个根本缺陷:它假设两个检索结果列表中的文档具有可比性。但现实是,当一种检索方法效果很差时,它返回的“第 1 名”可能根本不该出现在最终结果里。

Meilisearch 的做法不同。它对两种检索的分数进行归一化和统一评分,让系统根据查询性质动态调整权重,而不是依赖排名融合。当关键词检索对一个查询效果很好时,它的高分文档会自然排在前面;当语义检索更懂用户意图时,它的高分文档会胜出。

快速上手:TypeScript 示例

1. 安装与连接

bash 复制代码
npm install meilisearch
# 或
yarn add meilisearch
typescript 复制代码
import { Meilisearch } from 'meilisearch'

const client = new Meilisearch({
  host: process.env.MEILISEARCH_URL,  // 例如 http://localhost:7700
  apiKey: process.env.MEILISEARCH_KEY
})

const index = client.index('movies')

2. 配置嵌入器(Embedder)

Meilisearch 会自动为文档生成向量嵌入,你只需要配置好 provider。以下以 OpenAI 为例:

typescript 复制代码
// 配置 embedder,指定使用 OpenAI 的 embedding 模型
await index.updateEmbedders({
  movies: {
    source: 'openAi',
    apiKey: process.env.OPENAI_API_KEY,
    model: 'text-embedding-3-small',
    // 使用 Liquid 模板聚焦最有检索价值的字段
    documentTemplate: "{{doc.title}}:{{doc.overview}}。类型:{{doc.genre}}"
  }
})

documentTemplate 决定了哪些字段进入嵌入模型。不要直接把整个文档 JSON 丢进去——用模板聚焦标题、摘要等核心字段,能显著提升嵌入质量,还能节省 token 成本。

3. 添加文档

typescript 复制代码
const movies = [
  { id: 1, title: '盗梦空间', overview: '一个关于梦境窃取和植入的科幻惊悚片', genre: '科幻' },
  { id: 2, title: '星际穿越', overview: '宇航员穿越虫洞寻找人类新家园', genre: '科幻' },
  { id: 3, title: '当幸福来敲门', overview: '一位父亲在困境中追求幸福的励志故事', genre: '剧情' },
]

const task = await index.addDocuments(movies)
await client.waitForTask(task.taskUid)

console.log('文档已添加并完成嵌入生成')

Meilisearch 会自动批处理文档、调用嵌入 API、缓存结果,文档更新时只重新嵌入变化的部分。

4. 执行混合搜索

typescript 复制代码
// 混合搜索:semanticRatio=0.5 表示关键词和语义各占一半
const results = await index.search('适合全家一起看的科幻电影', {
  hybrid: {
    embedder: 'movies',
    semanticRatio: 0.5  // 0.0 = 纯关键词, 1.0 = 纯语义
  },
  limit: 5
})

for (const hit of results.hits) {
  console.log(`${hit.title} (Score: ${hit._rankingScore?.toFixed(4) ?? 'N/A'})`)
}

semanticRatio 控制混合权重。0 是纯关键词,1 是纯语义,0.5 是均衡。

用 LangChain.js 集成

如果你已经在用 LangChain.js 构建 RAG pipeline,Meilisearch 有现成的 vector store 集成。以下是一个完整的检索流程:

typescript 复制代码
import { Meilisearch } from 'meilisearch'
import { OpenAIEmbeddings } from '@langchain/openai'
import { MeilisearchVectorStore } from '@langchain/community/vectorstores/meilisearch'

const embeddings = new OpenAIEmbeddings()
const client = new Meilisearch({
  host: process.env.MEILISEARCH_URL,
  apiKey: process.env.MEILISEARCH_KEY
})

// 创建 vector store 并索引文档
const vectorStore = await MeilisearchVectorStore.fromTexts(
  ['盗梦空间是一部关于梦境窃取的科幻片', '星际穿越讲述宇航员穿越虫洞的故事'],
  [{ genre: '科幻' }, { genre: '科幻' }],
  embeddings,
  {
    client,
    indexName: 'movies_rag'
  }
)

// 相似度搜索
const docs = await vectorStore.similaritySearch('适合全家看的科幻电影', 3)
docs.forEach(doc => console.log(doc.pageContent))

不同 semanticRatio 的对比演示

以下 TypeScript 示例展示同一查询在不同 semanticRatio 下的检索结果差异:

typescript 复制代码
const query = 'wireless ergonomic keyboard'

const ratios = [
  { ratio: 0.0, desc: '纯关键词搜索' },
  { ratio: 0.5, desc: '均衡混合' },
  { ratio: 1.0, desc: '纯语义搜索' },
]

for (const { ratio, desc } of ratios) {
  console.log(`\n--- ${desc} (semanticRatio: ${ratio}) ---`)
  
  const results = await index.search(query, {
    hybrid: {
      embedder: 'products',
      semanticRatio: ratio
    },
    limit: 5
  })
  
  results.hits.forEach((hit, i) => {
    console.log(`${i + 1}. ${hit.title} (${hit._rankingScore?.toFixed(4)})`)
  })
}

纯关键词搜索会优先返回标题或描述中包含 “wireless”、“ergonomic”、“keyboard” 精确词的文档;纯语义搜索会返回概念相关的文档(比如提到“打字舒适”“人体工学设计”但没出现这些精确词的产品);混合搜索则两者兼顾。

实践建议

语义比例怎么调:官方给的起点参考——电商产品搜索 0.5–0.7,文档/知识库 0.5–0.8,代码搜索 0.0–0.3,FAQ/工单 0.7–1.0。中文知识库建议从 0.5 起步,根据查询日志微调。

文档模板决定嵌入质量:用 documentTemplate 聚焦标题、摘要等核心字段。标题 + 简短描述的嵌入效果通常比全文嵌入好得多,还能省 token。

先多召回再精排:让 Meilisearch 返回 Top 20–30,用重排序模型精排后取 Top 5 送入 LLM。混合检索负责“别漏”,重排序负责“别错”。

什么时候不该用混合搜索

如果你的数据完全没有文本内容(纯图像目录),混合搜索退化成纯语义检索,不如直接用 semanticRatio: 1.0。如果你做的是相似物品推荐,用 /similar 端点就够了。


混合搜索的价值在于它用一个相对简单的抽象(semanticRatio)封装了两种检索策略的协同,让开发者不需要在“精确但死板”和“灵活但漂移”之间二选一。对于任何需要同时处理实体查询和自然语言提问的 RAG 应用,这大概率是当前最务实的检索层方案。