本地部署大模型必备:Ollama Embeddings 使用指南与RAG语义检索实战!

从零上手 Ollama 嵌入向量:语义搜索 + RAG 落地代码教程

文章目录

Ollama 使用教程:本地大模型部署工具完全指南 中提到过 Embeddings,但写得比较简略。这篇文章单独展开说一下,因为我发现很多人在做 RAG(检索增强生成)时卡住的点其实并不在模型本身,而是对 Embeddings 的理解不够透,导致后面检索出来的东西质量很差。

什么是Embeddings(嵌入向量)

先讲一个简单概念。

计算机不认识「意思」这一说。你给它一句「猫趴在垫子上」,它看到的只是一堆字符。但如果你把这句话转成一个由几百个浮点数组成的数组(比如长度 384 或 768),两个句子的向量在空间里距离近,说明它们的语义相似。嵌入向量就是干这件事的。

Ollama 里跑 Embeddings 模型的时候,模型不生成自然语言回复,只吐出向量。输入「Man 爱吃 pizza」,输出就是类似 [0.023, -0.457, 0.112, ...] 这样的数组。后续你可以拿这个数组和别的句子向量做余弦相似度计算,找语义最接近的文本。

和普通的 LLM(重点是生成)不一样,Embeddings 模型只做「编码」这一半工作。

Ollama Embeddings的使用场景

1. 搜索引擎 / RAG(检索增强生成)

假设你有一堆文档(比如你的笔记、公司内部 Wiki、法律条款),用户问一个问题。你不能把整篇文档都塞进模型的上下文(太长了而且贵),但你可以:先把所有文档切块,每块生成一个 embedding → 存到向量数据库;用户问问题时,把问题也转成 embedding → 在数据库里找最相似的几块 → 把那几块原文 + 用户问题一起发给大模型,让模型基于资料回答。这个流程就是 RAG 的基础。

2. 去重 / 聚类

你想把一堆用户评论按主题分组,或者找重复的客服工单,靠人眼看不过来。可以把每条评论转成 embedding,然后用聚类算法(比如 k-means)自动分堆,或者两两算相似度,超过阈值就认为是重复。

3. 推荐系统 / 搜索排序

用户点击了一篇文章,你就用那篇文章的 embedding 去库里找「看过这篇文章的人还看了什么」——本质就是找最相似的向量。

Ollama 里怎么生成 Embeddings

命令行

最简单的用法:

ollama run embeddinggemma "Hello world"

这个会给出一长串浮点数,你可以直接重定向到文件或者用管道处理。

embeddinggemma 只是其中一种模型,也可以换成 nomic-embed-textall-minilm 等。

REST API

批量生成(比如你要一次性给 100 段文字做向量):

curl -X POST http://localhost:11434/api/embed \
  -H "Content-Type: application/json" \
  -d '{
    "model": "embeddinggemma",
    "input": ["第一段文本", "第二段文本"]
  }'

返回的 JSON 里 embeddings 字段是一个二维数组,每个元素对应一个输入的向量。

返回的 embeddings 是一个多维浮点数数组。注意不是所有模型都支持 embedding 功能,需要模型本身是专门训练来做 embedding 的,比如 all-minilm、nomic-embed-text、bge-m3 等。用普通的对话模型(像 qwen3.5)做 embedding 也不是不行,但效果一般,因为训练目标不同。

Python SDK

import ollama

response = ollama.embed(
    model='embeddinggemma',
    input=['天空是蓝色的', '太阳从东边升起']
)

vectors = response.embeddings
print(len(vectors[0]))  # 向量的维度,比如 384

需要注意:embed 函数是同步的,输入太多文本会等很久。如果你的文档集合很大,建议分批调用,或者换用异步方式自己控制并发。

一个易混淆的点:Ollama 有两个 embedding 相关的 API:

  • /api/embed(新版,推荐),支持批量输入和 dimensions 参数(截断维度)
  • /api/embeddings(旧版,仅单条输入,后续可能弃用)

两个都能用,但新项目直接用 /api/embed 就行。

和推理模型的关系:embedding 模型往往比对话模型小很多(几十 MB 到一两 GB),因为它们的任务简单——不需要生成自然语言,只需要输出固定维度的向量。所以跑 embedding 的硬件门槛低得多,甚至树莓派都能跑。

选哪个 Embeddings 模型

Ollama 官方库( https://ollama.com/library?q=embed )里带 embedding 标签的模型不少。我自己试过几个,简单做个对比:

模型 维度 中文支持 速度 推荐场景
embeddinggemma 768 一般 较快 通用英文,小项目
nomic-embed-text 768 还凑合 中等 长文档(8192 token 上下文)
all-minilm 384 极快 快速原型,对精度要求不高
bge-m3 1024 较慢 多语言 + 稠密+稀疏混合检索

中文内容多的话,bge-m3 虽然慢但效果好。如果只处理技术文档(术语偏英文),nomic-embed-text 性价比不错。all-minilm 维度低,存储和计算都快,但语义区分度弱一些。

经典用法:通过Ollama Embedding实现一个最简单的语义搜索

假设你有一堆自己的笔记或者博客文章,想通过问题找到最相关的那几段。

步骤 1:预计算所有笔记内容的向量

import ollama
import json

notes = [
    "今天用 Ollama 跑了下 qwen3,速度还行",
    "买了新的机械键盘,青轴声音太大了",
    "Embeddings 可以用来做相似度计算",
    "RAG 需要先把文档切成块,再生成向量",
]

# 存储 (文本, 向量)
corpus = []
for text in notes:
    resp = ollama.embed(model='nomic-embed-text', input=[text])
    vec = resp.embeddings[0]
    corpus.append((text, vec))

步骤 2:用户输入 query,转为向量

query = "怎么用 Ollama 做向量搜索"
query_vec = ollama.embed(model='nomic-embed-text', input=[query]).embeddings[0]

步骤 3:计算余弦相似度,排序

import numpy as np

def cosine_similarity(a, b):
    return np.dot(a, b) / (np.linalg.norm(a) * np.linalg.norm(b))

results = []
for text, vec in corpus:
    sim = cosine_similarity(query_vec, vec)
    results.append((sim, text))

results.sort(reverse=True)
for sim, text in results[:2]:
    print(f"{sim:.4f}: {text}")

你会看到和「向量搜索」语义最接近的那条笔记排在前面。这套东西跑通了,后面就能搭 RAG。

进阶:配合生成模型做 RAG

RAG 的基本流程:

  1. 用户提问
  2. 把问题转成向量,去向量库里检索相关文档片段
  3. 把检索到的片段 + 原始问题一起喂给普通 LLM(比如 qwen3)生成最终答案

伪代码:

# 假设已经有了 corpus 向量库(上面 notes 的例子)
def rag_answer(question):
    q_vec = ollama.embed(model='nomic-embed-text', input=[question]).embeddings[0]
    # 找到最相似的 top-2 文本
    scores = [(cosine_similarity(q_vec, vec), text) for text, vec in corpus]
    scores.sort(reverse=True)
    context = "\n".join([text for _, text in scores[:2]])

    prompt = f"基于以下信息回答用户的问题:\n{context}\n\n问题:{question}\n回答:"
    response = ollama.generate(model='qwen3:4b', prompt=prompt)
    return response['response']

注意:这里为了简单演示,每来一个问题都会重新算一次相似度。工程上应该把向量存到数据库里(如 Chroma、Qdrant、FAISS),然后建索引,才能支撑大量文档。

一些容易踩的坑

维度不匹配

不同模型输出的向量维度不一样。比如 all-minilm 是 384 维,bge-m3 是 1024 维。你把两种模型生成的向量混在一个库里,相似度计算会出错(即使强行 dot 也能算,但语义空间不一致)。要么统一用一个模型,要么做模型对齐(很麻烦,不推荐)。

上下文长度

Embeddings 模型也有输入长度限制。nomic-embed-text 支持 8192 token,embeddinggemma 只有 2048。如果某个文本块超过限制,要么截断,要么切得更碎。一般做法是把文档按句子或段落切块,每块控制在 500 token 以内比较安全。

归一化

余弦相似度计算前,很多实现会先把向量 L2 归一化。ollama 返回的原始向量已经是归一化过的吗?我没在文档里找到明确说明。保险的做法还是自己 normalise 一下,或者直接用 np.dot(vec1, vec2) / (norm1 * norm2)

中文 token 计数

Ollama 的 token izer 对不同模型不同。一个中文字可能占 2-3 token,自己估算长度的时候别按字符数来,最好实际 ollama show <embedding-model> --parameters 看看详细信息。稳妥做法是写一小段代码测试超长输入会报什么错误。

什么时候用嵌入向量,什么时候不用

  • 需要找「语义相似」的内容 → 用嵌入向量
  • 需要找完全相同的关键词(比如搜索「ollama 报错 404」) → 用传统倒排索引(BM25)更好,Embeddings 对罕见词不敏感
  • 需要做聚类、分类、异常检测 → 嵌入向量做特征输入到下游模型
  • 只需要生成一句话 → 直接 ollama run 就行,别绕一圈嵌入

实践中很多 RAG 系统会把 Embedding 检索和关键词检索混合(HyDE、RRF 等)。初期先把纯 Embedding 跑通,后面再慢慢优化。

总结

微信公众号后台自动回复中AI回复其实就是一种RAG,根据你的文章来回复粉丝的私信。相信你看完这篇文章之后也懂了它的实现原理。

如果是自己跑数据,要处理大规模数据(百万级以上),建议上专门的向量数据库 + GPU 加速版 FAISS。Ollama 这边负责生成向量,剩下的检索部分交给别的工具。


也可以看看