目录
Ollama 文本嵌入与 RAG 教程:让模型根据你的文档回答
语言模型不了解你公司的规章制度、产品手册或个人笔记。RAG(Retrieval-Augmented Generation,检索增强生成) 的做法是:先从你的文档中找出与问题相关的段落,再把这些段落和问题一起交给模型,让它根据资料作答。
问题 ──▶ 找出最相关的文档段落 ──▶ “根据以下资料回答:……” ──▶ 模型 ──▶ 答案
找出“相关段落”的关键技术就是文本嵌入(embedding)。
什么是文本嵌入
嵌入模型会把一段文本转换为一串数字(向量)。意思相近的文本,向量也会相近,即使用词完全不同:
- “如何申请请假?”
- “休假要怎么申请?”
这两句话的向量会很接近,因此可以实现关键词搜索做不到的“语义搜索”。
下载嵌入模型
ollama pull bge-m3 # 多语言,中文表现好
ollama pull nomic-embed-text # 轻量,主要面向英文
嵌入模型不能用于聊天,只能生成向量。
生成嵌入
curl http://localhost:11434/api/embed -d '{
"model": "bge-m3",
"input": ["如何申请请假?", "休假要怎么申请?"]
}'
import ollama
result = ollama.embed(model="bge-m3", input=["如何申请请假?", "休假要怎么申请?"])
vectors = result.embeddings
print(len(vectors), len(vectors[0])) # 2 条,每条 1024 维
input 可以是单个字符串或字符串列表,批量处理比逐条发送快很多。
计算相似度
最常用的是余弦相似度,取值范围为 -1 到 1,越接近 1 越相似:
import math
def cosine(a, b):
dot = sum(x * y for x, y in zip(a, b))
return dot / (math.sqrt(sum(x * x for x in a)) * math.sqrt(sum(y * y for y in b)))
print(cosine(vectors[0], vectors[1]))
实战:本地知识库问答
假设有一份公司规章文件 handbook.txt。完整程序如下:
import math
import ollama
EMBED_MODEL = "bge-m3"
CHAT_MODEL = "qwen3:8b"
def cosine(a, b):
dot = sum(x * y for x, y in zip(a, b))
return dot / (math.sqrt(sum(x * x for x in a)) * math.sqrt(sum(y * y for y in b)))
# 1. 读取文档并切分成段落(按空行分段)
text = open("handbook.txt", encoding="utf-8").read()
chunks = [c.strip() for c in text.split("\n\n") if c.strip()]
# 2. 为每个段落生成嵌入(只需做一次,可以保存起来重复使用)
chunk_vectors = ollama.embed(model=EMBED_MODEL, input=chunks).embeddings
def ask(question: str, top_k: int = 3) -> str:
# 3. 找出与问题最相似的段落
q_vec = ollama.embed(model=EMBED_MODEL, input=question).embeddings[0]
ranked = sorted(zip(chunks, chunk_vectors), key=lambda p: cosine(q_vec, p[1]), reverse=True)
context = "\n\n".join(c for c, _ in ranked[:top_k])
# 4. 把段落和问题一起交给模型
response = ollama.chat(
model=CHAT_MODEL,
messages=[
{
"role": "system",
"content": "你是公司的内部助理。只根据提供的资料,用简体中文回答。"
"如果资料中没有答案,请回答‘资料中没有相关信息’,不要自行推测。",
},
{"role": "user", "content": f"资料:\n{context}\n\n问题:{question}"},
],
options={"temperature": 0.2},
)
return response.message.content
while True:
q = input("\n问题:").strip()
if not q:
break
print(ask(q))
这就是一个最基础但完整可用的 RAG 系统,所有数据都保留在本地。
改进方向
切块(chunking)
切得太大,会混入不相关的内容;切得太小,会丢失上下文。常见做法:
- 每块约 300~800 个字,相邻两块重叠 50~100 字。
- 按照文档结构(标题、段落)切分,比按固定字数切分更好。
- 在每块前面加上所属标题,例如“第三章 请假规定 > 年假”。
使用向量数据库
段落很多时,每次都与所有向量比对会很慢,而且每次重启都要重新计算。可以改用向量数据库来存储和搜索:
| 工具 | 特点 |
|---|---|
| Chroma | Python 中最容易上手,可存储在本地文件夹中 |
| SQLite + sqlite-vec | 单个文件,适合小型项目 |
| PostgreSQL + pgvector | 已在使用 PostgreSQL 时的首选 |
| Qdrant、Milvus | 海量数据、独立服务 |
混合搜索
语义搜索不擅长精确匹配(例如产品型号、人名)。结合关键词搜索(BM25)和语义搜索,通常效果更好。
注意上下文长度
把多个段落放进提示词时,总长度不能超过模型的上下文长度,否则前面的内容会被截断。必要时调高 num_ctx,参见环境变量与设置。
不想自己写程序?
Open WebUI 内置了文档上传和知识库功能,可以直接上传 PDF、Word 文件,让模型根据文档作答。
更新于 2026年10月12日