Ollama 嵌入向量(Embeddings)
搜索有两种做法。一种是找字面匹配——搜"退款"就只出现含"退款"的段落;另一种是找意思相近——搜"钱能退回来吗",也能命中写着"申请返还货款"的文档。后者靠的就是嵌入向量(Embeddings)。
嵌入向量把一段文本压成一串数字(向量的每一维是一个浮点数)。语义相近的文本,向量在空间里的距离也近。有了这个性质,语义检索、去重、聚类、RAG 全都成立。
版本基线 v0.40.12,官方
docs.ollama.com/capabilities/embeddings。向量维度由模型决定,常见在 384–1024 维之间。
一、命令行先跑一次
## 拉一个嵌入模型
ollama pull embeddinggemma
## 直接给文本
ollama run embeddinggemma "Hello world"
也可以用管道把文本喂进去,适合脚本里批量处理:
echo "Hello world" | ollama run embeddinggemma
输出是一个 JSON 数组,那就是向量本体。肉眼看不出意义,但可以拿来算距离。
二、REST 接口:/api/embed
真正的集成入口是 /api/embed。单条文本:
## 单条输入:input 传字符串
curl -X POST http://localhost:11434/api/embed \
-H "Content-Type: application/json" \
-d '{
"model": "embeddinggemma",
"input": "The quick brown fox jumps over the lazy dog."
}'
Python 和 JavaScript 拿到的是同一个结构,embeddings 是二维数组——即使只传了一条文本,也是最外层包一层的形态:
## pip install ollama
import ollama
single = ollama.embed(
model='embeddinggemma',
input='The quick brown fox jumps over the lazy dog.'
)
print(len(single['embeddings'][0])) # 向量维度(第一层是数组,第二层是具体那条)
// npm install ollama
import ollama from 'ollama'
const single = await ollama.embed({
model: 'embeddinggemma',
input: 'The quick brown fox jumps over the lazy dog.',
})
console.log(single.embeddings[0].length) // 向量维度
注意 single['embeddings'][0] 这个下标。新手常写成 single['embeddings'] 然后奇怪为什么长度是 1——因为它拿到的是"装着一条向量的数组",不是向量本身。
三、批量:把 input 换成数组
建索引时一条一条发请求太慢。input 直接传字符串数组,一次编码一批:
## 批量输入:input 传字符串数组,返回数组与输入一一对应
curl -X POST http://localhost:11434/api/embed \
-H "Content-Type: application/json" \
-d '{
"model": "embeddinggemma",
"input": [
"First sentence",
"Second sentence",
"Third sentence"
]
}'
import ollama
batch = ollama.embed(
model='embeddinggemma',
input=[
'The quick brown fox jumps over the lazy dog.',
'The five boxing wizards jump quickly.',
'Jackdaws love my big sphinx of quartz.',
]
)
print(len(batch['embeddings'])) # 3 —— 向量条数 = 输入条数
import ollama from 'ollama'
const batch = await ollama.embed({
model: 'embeddinggemma',
input: [
'The quick brown fox jumps over the lazy dog.',
'The five boxing wizards jump quickly.',
'Jackdaws love my big sphinx of quartz.',
],
})
console.log(batch.embeddings.length) // 3
返回顺序与输入顺序严格一致,可以直接按下标对应回原文。这一点很关键:建索引时务必把向量和原文、以及原文的唯一 ID 一起存起来,否则后面检索到向量却找不到对应内容。
四、/api/embed 的向量已经归一化
这是官方文档里一句容易被跳过、但很影响实现的说明:/api/embed 返回的是 L2 归一化(单位长度)向量。
什么意思?每个向量的模长都是 1。带来的直接好处是:计算余弦相似度时,分母恒等于 1,你可以直接用点积代替余弦相似度,省掉两次开方和一次除法。向量库里的 dot_product 和 cosine 在这批数据上结果完全一致,选更快的那个就行。
反过来说,如果你拿到的是自己另算的向量(或者换了别的模型来源),就不能默认它已归一化,该按余弦算还得按余弦算。
五、两条官方建议,值得当规则用
规则一:语义检索优先用余弦相似度。 官方明确推荐大多数场景用 cosine。欧氏距离在归一化向量上也等价(单调对应),但余弦的取值范围更好解释——1 表示完全同向,0 表示无关。
规则二:索引和查询必须用同一个模型。 这条比第一条更重要。不同模型的向量空间完全不通用,embeddinggemma 生成的向量拿去和另一个模型的向量比距离,结果毫无意义(哪怕维度碰巧一样)。所以:
- 换嵌入模型 = 整个索引重建,不能只换查询侧
- 模型版本升级也要留意,有些模型的版本迭代会改动向量空间
- 项目里把"嵌入模型名 + 维度"写进索引元数据,加载时校验,能提前发现不匹配
六、一个最小可用的语义检索骨架
把上面几点串起来,一个不依赖任何向量库的最小实现长这样:
import ollama
MODEL = 'embeddinggemma'
## ① 建索引:文本 -> 向量,同时保留原文
docs = [
'申请退还货款需要三个工作日到账。',
'如何修改账户绑定的手机号。',
'订单发货后不支持修改收货地址。',
]
index = ollama.embed(model=MODEL, input=docs)['embeddings']
## ② 查询:用同一个模型编码问题
question = '钱什么时候能退回来?'
q_vec = ollama.embed(model=MODEL, input=question)['embeddings'][0]
## ③ 向量已归一化,点积即余弦相似度
def dot(a, b):
return sum(x * y for x, y in zip(a, b))
scored = sorted(
((dot(q_vec, vec), text) for vec, text in zip(index, docs)),
reverse=True,
)
for score, text in scored:
print(f'{score:.4f} {text}')
跑一遍会发现,语义最接近"钱什么时候能退"的那条"申请退还货款…"排在第一位,尽管两者没有一个字面词重合。这就是嵌入向量相对于关键词搜索的价值。
生产环境当然要用真正的向量数据库(存向量、建近似最近邻索引、支持过滤条件),但上面的骨架解释了全部原理:编码、存储、用同模型编码查询、算余弦、排序。剩下的都是工程优化。
想把检索结果直接喂给模型生成答案(也就是 RAG 的"G"),下一步看 3.9 Ollama 网络搜索(Web search) 了解联网补充信息,或看 3.8 Ollama 工具调用(Tool calling) 把检索做成模型可自主调用的工具。