1. 准备环境:安装 Ollama 与 ChromaDB
本地知识库的核心是 向量数据库 ,用于存储文档的语义向量。ChromaDB 轻量、易用,适合个人开发者。首先确保已安装 Ollama,并拉取一个嵌入模型(如 nomic-embed-text),该模型将文档转为向量。
# 安装 ChromaDB
pip install chromadb
# 拉取嵌入模型
ollama pull nomic-embed-text
原因:嵌入模型负责将文本转化为数值向量,向量数据库根据向量相似度检索文档。没有向量化,语义搜索就无从谈起。
2. 加载文档并生成向量
假设你有一个 Markdown 文件 knowledge.md,内容为技术问答。首先读取文件,分割为段落(chunks),并利用 Ollama 生成每个段落的向量。
import chromadb
from chromadb.utils import embedding_functions
# 初始化 ChromaDB 客户端
client = chromadb.PersistentClient(path='./kb_chroma')
# 使用 Ollama 嵌入函数
ollama_ef = embedding_functions.OllamaEmbeddingFunction(
model_name="nomic-embed-text",
url="http://localhost:11434/api/embeddings"
)
# 创建或获取集合
collection = client.get_or_create_collection(
name="my_kb",
embedding_function=ollama_ef
)
# 加载文档并添加
with open('knowledge.md', 'r') as f:
text = f.read()
# 简单分段(每段200字符)
chunks = [text[i:i+200] for i in range(0, len(text), 200)]
ids = [f"chunk_{i}" for i in range(len(chunks))]
collection.add(
documents=chunks,
ids=ids
)
print(f"已添加 {len(chunks)} 个文档块")
原因 :OllamaEmbeddingFunction 封装了调用 Ollama API 的逻辑,避免了手动构造请求。分段是为了让每个向量对应一个语义连贯的片段,提升检索精度。
3. 实现语义检索
当用户提问时,将问题向量化后在 ChromaDB 中搜索最相似的文档块。以下示例查询"如何配置 Ollama 模型?"。
query = "如何配置 Ollama 模型?"
# 执行查询(返回前3个结果)
results = collection.query(
query_texts=[query],
n_results=3
)
print("检索结果:")
for doc, dist in zip(results['documents'][0], results['distances'][0]):
print(f"距离 {dist:.4f}: {doc[:50]}...")
原因 :ChromaDB 会自动调用 OllamaEmbeddingFunction 对查询文本进行向量化,然后计算余弦距离。距离越小,语义越相似。
4. 最佳实践清单
- 选择合适的嵌入模型 :
nomic-embed-text适合通用场景,若涉及代码,可选用mistral或llama3的嵌入版本。
原因:不同嵌入模型对领域语义的理解能力不同。 - 合理分段文档 :推荐 200-500 字符一段,段与段之间保留上下文重叠。
示例 :使用RecursiveCharacterTextSplitter实现智能分割。 - 持久化存储 :使用
PersistentClient而非临时内存,避免每次重启重新构建知识库。
原因:ChromaDB 会将向量数据写入磁盘,重启后自动加载。 - 元数据过滤 :为每个文档块添加元数据(如来源文件、章节),在查询时可按条件过滤。
示例 :collection.add(documents=..., ids=..., metadatas=[{"source": "knowledge.md"}])。 - 调整返回数量 :根据场景设置
n_results,一般 3-5 个足够,避免信息过载。
5. 集成到 Ollama 对话
将检索到的文档块作为上下文,结合用户问题发送给 Ollama 模型生成回答,实现 RAG(检索增强生成)。
import requests
# 取检索结果的前 1000 字作为上下文
context = "\n".join(results['documents'][0])[:1000]
prompt = f"基于以下知识回答:\n{context}\n\n问题:{query}"
response = requests.post("http://localhost:11434/api/generate",
json={"model": "llama3.2", "prompt": prompt, "stream": False})
print(response.json()['response'])
原因:仅靠大模型自身知识可能过时或缺失,本地知识库提供最新、私有的信息,大幅提升回答准确度。
通过以上步骤,你已成功搭建本地知识库,并实现语义检索与 RAG 对话。后续可以扩展为支持 PDF、Word 等多格式文档,或集成到前端聊天界面。