上一篇介绍了常见的 Embedding 模型、模型选型方法和应用场景。本文将从最小示例出发,使用 Ollama、
nomic-embed-text和 LangChain.js,把文本转换成向量。
前言
Embedding 的基本使用流程并不复杂:
text
文本
↓ Embedding 模型
向量
本文会完成以下内容:
- 使用 Ollama 在本地运行 Embedding 模型;
- 通过 Ollama HTTP API 生成向量;
- 使用 LangChain.js 调用 Embedding 模型;
- 理解
embedQuery()和embedDocuments()。
本文只关注最基础的向量生成过程,暂时不展示相似度计算和语义搜索的具体实现,也不引入向量数据库和完整 RAG 项目。
一、Embedding 的输入和输出
Embedding 模型接收文本,返回一个固定长度的数字数组。
例如:
text
"如何重置密码?"
↓ Embedding 模型
[0.12, -0.35, 0.81, ..., 0.27]
在 TypeScript 中,一条文本生成的向量可以表示为:
ts
const vector: number[] = [0.12, -0.35, 0.81, 0.27];
多条文本则会得到多个向量:
ts
const vectors: number[][] = [
[0.12, -0.35, 0.81, 0.27],
[0.09, -0.31, 0.77, 0.30],
];
真实模型输出的向量通常包含数百或数千个数字。为了便于阅读,示例中只展示前几个值。
向量中的单个数字通常不适合被单独解释。真正有用的是不同向量之间的整体关系:语义相近的文本,其向量通常也更接近。
二、准备 Ollama 和 Embedding 模型
1. Ollama 是什么?
Ollama 是一个本地模型运行工具。它负责下载、管理和运行模型,并向应用程序提供 HTTP API。
这里需要区分两个概念:
text
Ollama = 运行模型的服务
nomic-embed-text = 实际把文本转换成向量的模型
Ollama 本身不是 Embedding 模型。
2. 下载模型
安装并启动 Ollama 后,下载本文使用的模型:
bash
ollama pull nomic-embed-text
查看已经下载的模型:
bash
ollama list
正常情况下,可以在列表中看到 nomic-embed-text。
3. 确认 Ollama 服务地址
Ollama 默认提供本地服务:
text
http://localhost:11434
如果 Ollama 桌面程序没有自动启动服务,可以执行:
bash
ollama serve
三、通过 Ollama HTTP API 生成向量
在使用 LangChain.js 之前,可以先直接调用 Ollama API,观察 Embedding 模型的原始返回结果。
执行:
bash
curl http://localhost:11434/api/embed \
-H "Content-Type: application/json" \
-d '{
"model": "nomic-embed-text",
"input": "如何重置密码?"
}'
返回结果的结构类似:
json
{
"model": "nomic-embed-text",
"embeddings": [
[0.012, -0.035, 0.081, 0.027]
]
}
其中:
model表示使用的模型;input是需要转换的文本;embeddings是模型生成的向量列表。
即使只传入一条文本,embeddings 仍然是二维数组,因为接口也支持批量输入:
bash
curl http://localhost:11434/api/embed \
-H "Content-Type: application/json" \
-d '{
"model": "nomic-embed-text",
"input": [
"如何重置密码?",
"忘记密码后怎样重新设置?",
"今天天气怎么样?"
]
}'
三条文本会按输入顺序得到三个向量。
至此,我们已经完成了最基础的调用:
text
文本 → Ollama API → nomic-embed-text → 向量
四、使用 LangChain.js 调用 Embedding 模型
直接调用 HTTP API 没有问题,但在 LangChain.js 应用中,通常会使用 OllamaEmbeddings。
它是 LangChain 对 Ollama Embedding API 的客户端封装。
调用关系如下:
text
TypeScript 程序
↓
OllamaEmbeddings
↓ HTTP
Ollama 服务
↓
nomic-embed-text
↓
数值向量
1. 安装依赖
在 TypeScript 或 Node.js 项目中安装:
bash
npm install @langchain/ollama
2. 创建 Embedding 客户端
ts
import { OllamaEmbeddings } from "@langchain/ollama";
const embeddings = new OllamaEmbeddings({
model: "nomic-embed-text",
baseUrl: "http://localhost:11434",
});
两个主要参数分别是:
| 参数 | 作用 |
|---|---|
model |
指定 Ollama 中使用的 Embedding 模型 |
baseUrl |
指定 Ollama 服务地址 |
创建 OllamaEmbeddings 对象时,主要是在保存客户端配置。真正调用向量生成方法时,才会向 Ollama 发出请求。
五、使用 embedQuery() 生成单个向量
embedQuery() 接收一条文本,返回一个向量:
ts
const vector = await embeddings.embedQuery("如何重置密码?");
console.log(vector.length);
console.log(vector.slice(0, 5));
返回值类型可以理解为:
ts
number[]
完整示例:
ts
import { OllamaEmbeddings } from "@langchain/ollama";
const embeddings = new OllamaEmbeddings({
model: "nomic-embed-text",
baseUrl: "http://localhost:11434",
});
const vector = await embeddings.embedQuery("如何重置密码?");
console.log("向量维度:", vector.length);
console.log("前 5 个值:", vector.slice(0, 5));
embedQuery() 通常用于查询阶段,例如把用户输入的问题转换成查询向量。
需要注意,它只负责生成向量,不会自动搜索文档,也不会生成自然语言回答。
六、使用 embedDocuments() 批量生成向量
embedDocuments() 接收多条文本,返回多个向量:
ts
const vectors = await embeddings.embedDocuments([
"如何重置密码?",
"忘记密码后怎样重新设置?",
"今天天气怎么样?",
]);
返回值类型可以理解为:
ts
number[][]
可以检查返回数量和向量维度:
ts
console.log("向量数量:", vectors.length);
console.log("每个向量的维度:", vectors[0].length);
输入文本与输出向量按顺序对应:
text
texts[0] → vectors[0]
texts[1] → vectors[1]
texts[2] → vectors[2]
embedDocuments() 通常用于索引阶段,为多个文档片段批量生成向量。
两个方法可以这样记忆:
text
embedQuery(text) → 一条文本 → 一个向量
embedDocuments(texts) → 多条文本 → 多个向量
它们的区别主要体现在调用语义和输入数量上。部分模型还会针对"查询"和"文档"使用不同的编码方式,因此在实际项目中应按照用途选择正确的方法。
七、基础使用中的重要规则
1. 文档和查询必须使用同一个模型
正确方式:
text
文档 → nomic-embed-text → 文档向量
问题 → nomic-embed-text → 查询向量
错误方式:
text
文档 → 模型 A → 文档向量
问题 → 模型 B → 查询向量
不同模型生成的向量通常不在同一个语义空间中。即使两个模型输出的维度相同,也不代表它们可以直接比较。
因此,更换 Embedding 模型后,通常需要重新生成所有文档向量。
2. 向量维度必须一致
计算余弦相似度时,两个向量必须具有相同维度。
如果一个向量是 768 维,另一个是 1024 维,就不能直接进行对应位置的计算。
3. 相似度高不等于事实正确
Embedding 判断的是"语义是否接近",而不是"内容是否真实"。
两段内容可能表达相似,但同时包含错误信息。因此,语义相似度不能代替事实校验。
4. 不要直接照搬固定阈值
不能简单认为:
text
相似度大于 0.8 就一定相关
不同模型、文本长度、语言和业务数据的分数分布都可能不同。阈值应该使用真实样本进行评估后再确定。
5. 长文档通常需要先分块
虽然部分模型支持较长输入,但不建议把包含多个主题的整篇文档直接生成一个向量。
例如,一篇文档同时包含:
- 用户注册;
- 密码重置;
- 权限管理;
- 订单支付。
如果整篇文档只生成一个向量,具体主题可能被稀释。实际检索系统通常先把文档切成多个语义相对完整的片段,再分别生成向量。
6. 索引时尽量批量生成向量
为大量文档建立索引时,应优先使用 embedDocuments() 批量处理,而不是对每条文本单独调用一次 embedQuery()。
批量调用通常可以减少请求次数,提高索引效率。具体批次大小需要根据模型服务的输入限制和机器资源进行调整。
八、常见问题
1. 为什么提示连接不到 Ollama?
先确认 Ollama 服务正在运行,并检查地址是否正确:
text
http://localhost:11434
可以使用以下命令启动服务:
bash
ollama serve
如果应用运行在 Docker 容器或另一台机器中,localhost 指向的是应用自身所在的环境,需要改成应用能够访问的 Ollama 地址。
2. 为什么提示模型不存在?
通常是因为模型尚未下载。执行:
bash
ollama pull nomic-embed-text
然后通过 ollama list 确认模型名称与代码中的 model 配置一致。
3. OllamaEmbeddings 会保存向量吗?
不会。
它只负责请求模型并返回向量。向量需要由应用程序自行保存,或者交给向量数据库管理。
4. OllamaEmbeddings 能生成自然语言回答吗?
不能。
它调用的是 Embedding 模型,输出是数值向量。生成自然语言回答需要聊天模型或其他大语言模型客户端。
5. 为什么相似文本的分数没有想象中高?
可能原因包括:
- 模型对当前语言支持有限;
- 文本过短,缺少足够上下文;
- 两段文本属于特定专业领域;
- 使用了不适合当前任务的模型;
- 固定阈值并不适合当前模型的分数分布。
判断效果时,不要只检查一组文本。更可靠的方式是准备一批相关和不相关样本,观察整体排序表现。
6. 可以把向量打印出来直接分析吗?
可以打印,但通常很难从单个数字中直接理解模型学到了什么。
更有意义的检查方式是:
- 比较相似度;
- 查看检索排序;
- 检查正确结果是否出现在 Top-K 中;
- 使用真实问题构建评估集。
九、从基础示例到真实检索系统
本文的示例完成了从文本到向量的转换:
text
文本 → Embedding 模型 → 向量
在真实知识库中,这一步通常会扩展为:
text
原始文档
↓ 加载与清洗
文档分块
↓ embedDocuments()
文档向量
↓
写入向量数据库
用户问题
↓ embedQuery()
查询向量
↓
向量数据库相似度搜索
↓
返回 Top-K 个相关片段
如果继续构建 RAG,还会把检索到的片段与用户问题一起交给大语言模型:
text
相关片段 + 用户问题 → 大语言模型 → 最终回答
因此,本文完成的是 RAG 检索链路中最基础的一层:把文本转换成向量,为后续的向量存储和语义检索提供数据。
总结
Embedding 的基础使用可以归纳为两步:
- 准备并运行 Embedding 模型;
- 把查询和文档转换成向量。
使用 Ollama 和 LangChain.js 时,可以记住下面的关系:
text
Ollama:负责运行模型
nomic-embed-text:负责把文本转换成向量
OllamaEmbeddings:负责让 LangChain.js 调用 Ollama
还需要牢记三条规则:
- 索引和查询使用同一个 Embedding 模型;
- 相似度表示语义接近程度,不代表事实正确;
- 长文档通常需要分块后再生成向量。
掌握文本向量的生成方式后,就可以继续引入文档加载、文本分块和向量数据库,逐步搭建完整的语义搜索或 RAG 知识库。