摘要

前面几篇文章里,我们已经完成了 Ollama 的基础入门、Windows 安装、显卡模型选择、常用命令管理,以及 API 调用。

到这里,Ollama 已经不只是一个终端聊天工具了。
我们已经可以用 Python、JavaScript 或网页项目调用本地大模型。

但如果继续往下做,就会遇到一个更实用的问题:

能不能让本地 AI 回答我自己的资料?

比如:

  • 回答自己的学习笔记

  • 总结自己的 Markdown 文档

  • 查询项目 README

  • 分析本地技术资料

  • 做一个个人知识库

  • 做一个公司内部文档问答系统

这就涉及到一个非常重要的技术方向:RAG,也就是检索增强生成。

这一篇文章会用 Ollama 做一个简化版本地知识库问答系统,让本地模型不只是“凭记忆回答”,而是先从资料里找内容,再根据资料回答问题。


一、为什么普通聊天模型不够用?

我们直接问本地模型问题时,它的回答主要来自模型训练时学到的知识。

例如你问:

什么是 Ollama?

模型大概率可以回答。

但如果你问:

我昨天写的那篇 CSDN 草稿里,第五篇主要讲了什么?

模型就不知道了。

原因很简单:
你的个人文件、笔记、代码、草稿,并不在模型训练数据里。

所以如果你想让 AI 回答自己的资料,必须先把资料提供给它。

最简单的方式是直接复制粘贴:

这是我的文章内容:……
请根据上面的内容回答问题。

但这个方法有明显问题:

文档太长时放不进去
每次都要手动复制
上下文太长会变慢
模型可能抓不住重点
本地显存和内存压力变大

所以更合理的方法是:
先让程序从文档里找出最相关的几段,再交给大模型回答。

这就是 RAG 的基本思路。


二、什么是 RAG?

RAG 的全称是 Retrieval-Augmented Generation,中文一般叫:

检索增强生成

听起来很复杂,其实可以拆成两步:

Retrieval:先检索相关资料
Generation:再根据资料生成回答

普通大模型回答问题时是这样:

用户问题 → 大模型 → 回答

RAG 的流程是这样:

用户问题
↓
从知识库里检索相关内容
↓
把相关内容和问题一起交给大模型
↓
大模型根据资料回答

简单理解:

普通聊天:模型凭自己记忆回答
RAG 问答:模型先看资料,再回答

这样做有几个好处:

可以回答自己的文档内容
可以减少胡编乱造
可以让回答更贴近资料
可以处理比上下文窗口更大的资料库
可以搭建个人或项目知识库

三、本地知识库的基本流程

一个最基础的本地知识库系统,一般有下面几个步骤:

1. 准备文档
2. 把文档切成小段
3. 用 embedding 模型把每段文字转成向量
4. 保存这些向量
5. 用户提问时,把问题也转成向量
6. 找出最相似的几个文本段
7. 把这些文本段交给大模型
8. 大模型根据资料回答

可以画成这样:

文档
↓
切分 chunks
↓
生成 embeddings
↓
保存到本地
↓
用户问题
↓
问题 embedding
↓
相似度搜索
↓
取出相关 chunks
↓
Ollama 生成回答

这里面有两个模型角色:

Embedding 模型:负责把文字变成向量
Chat 模型:负责根据资料生成回答

这两个模型可以不同。

例如:

Embedding 模型:nomic-embed-text 或 embeddinggemma
回答模型:qwen2.5:7b 或 llama3.2

四、Embedding 是什么?

Embedding 可以理解为“文字的数字表示”。

人能直接理解文字,但计算机不能直接理解“意思”。
所以我们要把文字转换成一串数字向量。

比如一句话:

Ollama 可以在本地运行大语言模型。

经过 embedding 模型处理后,可能会变成类似这样的向量:

[0.12, -0.04, 0.88, 0.31, ...]

这串数字本身不适合人看,但适合计算机比较相似度。

比如下面两句话语义很接近:

Ollama 可以在本地运行大模型。
Ollama 能让电脑本地跑 AI 模型。

它们转换成向量后,距离会比较近。

而下面这句话语义就远一些:

今天晚上吃什么比较好?

它的向量距离就会比较远。

所以 embedding 的作用就是:

把文本变成向量,让程序可以用数学方法搜索语义相似内容。

五、准备工作

开始之前,需要确认几个东西。

1. 安装 Ollama

先确认 Ollama 可用:

ollama --version

2. 准备回答模型

例如使用中文能力较好的模型:

ollama pull qwen2.5:7b

或者:

ollama pull llama3.2

3. 准备 embedding 模型

例如:

ollama pull nomic-embed-text

如果你想换其他 embedding 模型,也可以在 Ollama 模型库中选择合适的 embedding 模型。

4. 安装 Python 依赖

这篇文章用 Python 写一个最小可运行版本。

安装依赖:

pip install requests numpy

其中:

requests:用于请求 Ollama API
numpy:用于计算向量相似度

六、准备一份测试知识库

为了方便演示,我们先不用 PDF,也不用复杂数据库。
直接准备几段文本,模拟一个小型知识库。

新建文件:

rag_demo.py

先写入几段资料:

documents = [
    "Ollama 是一个本地大模型运行工具,可以帮助用户在自己的电脑上运行大语言模型。",
    "Ollama 默认会在本地启动 API 服务,地址通常是 http://localhost:11434。",
    "本地大模型运行速度和显卡、显存、内存、CPU 都有关系,其中显存非常关键。",
    "RAG 的意思是检索增强生成,它会先从知识库中检索相关内容,再让大模型根据资料回答。",
    "Embedding 可以把文本转换成数字向量,用来计算文本之间的语义相似度。"
]

这就是我们的“迷你知识库”。

真实项目里,这些内容可以来自:

Markdown 文件
TXT 文件
PDF 文档
网页内容
项目 README
课堂笔记
CSDN 草稿

七、调用 Ollama 生成 embedding

接下来写一个函数,把文本转换成向量。

import requests
import numpy as np

EMBED_MODEL = "nomic-embed-text"
CHAT_MODEL = "qwen2.5:7b"

def get_embedding(text):
    url = "http://localhost:11434/api/embed"

    payload = {
        "model": EMBED_MODEL,
        "input": text
    }

    response = requests.post(url, json=payload, timeout=120)
    response.raise_for_status()

    data = response.json()
    return np.array(data["embeddings"][0])

这里使用的是 Ollama 的 /api/embed 接口。

输入是一段文本,输出是一个向量。

注意:

data["embeddings"][0]

是因为 API 返回的是一个 embedding 数组。
我们这里每次只传入一段文本,所以取第一个结果。


八、计算文本相似度

有了向量之后,就要计算问题和文档之间的相似度。

常见方法是余弦相似度。

写一个函数:

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

余弦相似度越高,说明两个文本语义越接近。


九、构建知识库向量

接下来,我们把所有文档都转换成向量并保存。

documents = [
    "Ollama 是一个本地大模型运行工具,可以帮助用户在自己的电脑上运行大语言模型。",
    "Ollama 默认会在本地启动 API 服务,地址通常是 http://localhost:11434。",
    "本地大模型运行速度和显卡、显存、内存、CPU 都有关系,其中显存非常关键。",
    "RAG 的意思是检索增强生成,它会先从知识库中检索相关内容,再让大模型根据资料回答。",
    "Embedding 可以把文本转换成数字向量,用来计算文本之间的语义相似度。"
]

doc_embeddings = []

print("正在生成文档向量...")

for doc in documents:
    emb = get_embedding(doc)
    doc_embeddings.append(emb)

print("知识库向量生成完成。")

真实项目里,这一步通常不会每次都重新做。
因为 embedding 生成完后可以保存到本地文件或向量数据库。

比如保存到:

JSON
SQLite
Chroma
FAISS
LanceDB

这篇只是演示最小流程,所以先存在内存里。


十、根据问题检索相关资料

用户提问时,我们也把问题转成向量,然后和所有文档向量比较。

def retrieve(query, top_k=2):
    query_embedding = get_embedding(query)

    scores = []

    for index, doc_embedding in enumerate(doc_embeddings):
        score = cosine_similarity(query_embedding, doc_embedding)
        scores.append((score, documents[index]))

    scores.sort(reverse=True, key=lambda x: x[0])

    return scores[:top_k]

这个函数会返回最相关的几段资料。

例如:

results = retrieve("RAG 是什么?", top_k=2)

for score, doc in results:
    print(score, doc)

如果效果正常,它应该能找到和 RAG、Embedding 相关的内容。


十一、把检索结果交给大模型回答

现在我们已经能找到相关资料了。
下一步就是把资料和问题一起交给大模型。

def ask_ollama(query):
    retrieved_docs = retrieve(query, top_k=2)

    context = "\n".join([doc for score, doc in retrieved_docs])

    prompt = f"""
你是一个严谨的本地知识库助手。
请只根据下面提供的资料回答问题。
如果资料中没有答案,请直接说“资料中没有提到”。

资料:
{context}

问题:
{query}

回答:
"""

    url = "http://localhost:11434/api/generate"

    payload = {
        "model": CHAT_MODEL,
        "prompt": prompt,
        "stream": False
    }

    response = requests.post(url, json=payload, timeout=180)
    response.raise_for_status()

    data = response.json()
    return data["response"]

这里有一个很重要的提示词:

请只根据下面提供的资料回答问题。
如果资料中没有答案,请直接说“资料中没有提到”。

这样可以减少模型胡编乱造。


十二、完整代码

下面是一个完整可运行版本。

import requests
import numpy as np

EMBED_MODEL = "nomic-embed-text"
CHAT_MODEL = "qwen2.5:7b"

documents = [
    "Ollama 是一个本地大模型运行工具,可以帮助用户在自己的电脑上运行大语言模型。",
    "Ollama 默认会在本地启动 API 服务,地址通常是 http://localhost:11434。",
    "本地大模型运行速度和显卡、显存、内存、CPU 都有关系,其中显存非常关键。",
    "RAG 的意思是检索增强生成,它会先从知识库中检索相关内容,再让大模型根据资料回答。",
    "Embedding 可以把文本转换成数字向量,用来计算文本之间的语义相似度。"
]

def get_embedding(text):
    url = "http://localhost:11434/api/embed"

    payload = {
        "model": EMBED_MODEL,
        "input": text
    }

    response = requests.post(url, json=payload, timeout=120)
    response.raise_for_status()

    data = response.json()
    return np.array(data["embeddings"][0])

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

print("正在生成文档向量...")

doc_embeddings = []

for doc in documents:
    emb = get_embedding(doc)
    doc_embeddings.append(emb)

print("知识库向量生成完成。")

def retrieve(query, top_k=2):
    query_embedding = get_embedding(query)

    scores = []

    for index, doc_embedding in enumerate(doc_embeddings):
        score = cosine_similarity(query_embedding, doc_embedding)
        scores.append((score, documents[index]))

    scores.sort(reverse=True, key=lambda x: x[0])

    return scores[:top_k]

def ask_ollama(query):
    retrieved_docs = retrieve(query, top_k=2)

    context = "\n".join([doc for score, doc in retrieved_docs])

    prompt = f"""
你是一个严谨的本地知识库助手。
请只根据下面提供的资料回答问题。
如果资料中没有答案,请直接说“资料中没有提到”。

资料:
{context}

问题:
{query}

回答:
"""

    url = "http://localhost:11434/api/generate"

    payload = {
        "model": CHAT_MODEL,
        "prompt": prompt,
        "stream": False
    }

    response = requests.post(url, json=payload, timeout=180)
    response.raise_for_status()

    data = response.json()
    return data["response"]

if __name__ == "__main__":
    print("本地知识库问答系统已启动,输入 exit 退出。")

    while True:
        query = input("\n请输入问题:")

        if query.lower() in ["exit", "quit", "q"]:
            print("已退出。")
            break

        try:
            answer = ask_ollama(query)
            print("\nAI 回答:")
            print(answer)

        except Exception as e:
            print("出错了:", e)

运行:

python rag_demo.py

测试问题:

RAG 是什么?

或者:

Ollama 的 API 地址是什么?

如果一切正常,模型会先检索资料,再根据资料回答。


十三、这个 Demo 的本质是什么?

这个 Demo 虽然很小,但已经包含了 RAG 的核心流程:

文档 → embedding → 问题 embedding → 相似度检索 → 拼接上下文 → 大模型回答

也就是说,这已经不是普通聊天机器人了,而是一个最小版知识库问答系统。

它和真正项目的区别主要在于:

真实项目文档更多
需要更好的文本切分
需要保存向量
需要向量数据库
需要更好的前端界面
需要更严格的引用来源
需要处理 PDF、Word、网页等格式

但底层思路是一样的。


十四、真实项目里应该怎么升级?

如果你想把这个 Demo 升级成真正可用的知识库,可以按下面路线来。

1. 支持读取文件

先支持 TXT 和 Markdown:

notes/
  ollama.md
  api.md
  rag.md

程序遍历文件夹,读取所有文档。

2. 文档切分

不要把整篇文章直接当成一个 chunk。
应该按段落或长度切分。

例如:

每 300 到 800 字切一段
段落之间保留少量重叠
标题和正文尽量放在一起

切分太短,信息不完整。
切分太长,检索不精准,也会增加上下文压力。

3. 保存向量

不要每次启动都重新生成 embedding。
可以把文档内容和向量保存起来。

简单方案:

JSON 文件
SQLite 数据库

进阶方案:

FAISS
Chroma
LanceDB
Milvus

4. 增加来源引用

真实知识库最好告诉用户答案来自哪里。

例如:

回答:
Ollama 默认 API 地址是 localhost:11434。

来源:
api_notes.md,第 3 段

这样用户可以回到原文核对。

5. 做一个前端界面

可以用:

HTML + JavaScript
React
Vue
Electron
Tauri

做一个本地网页或桌面应用。


十五、性能优化建议

本地知识库比普通聊天更吃资源,因为它多了 embedding、检索、上下文拼接等步骤。

下面是一些实用优化建议。


1. 文档向量不要重复生成

Embedding 生成是有成本的。
如果每次启动程序都重新给所有文档生成向量,文档多了之后会很慢。

更好的做法是:

第一次生成向量
保存到本地
下次直接读取
只有文档变化时才重新生成

2. 控制 top_k

top_k 表示取最相关的几段资料。

如果设置太小:

可能漏掉关键资料

如果设置太大:

上下文变长
速度变慢
模型更容易混乱

新手可以从:

top_k = 3

开始测试。


3. 控制 chunk 大小

chunk 太小,语义不完整。
chunk 太大,检索不精准。

建议先从:

300 到 800 字左右

开始测试。

如果是代码文档,可以按函数或标题切分。
如果是学习笔记,可以按小标题切分。
如果是文章,可以按段落切分。


4. 不要一次塞太多资料给模型

即使检索出了很多内容,也不要全部塞进 prompt。
本地模型上下文越长,占用资源越高,速度越慢。

建议只传最相关的几段。


5. 使用合适的回答模型

如果只是简单知识库问答,7B 模型通常可以先试。
如果需要复杂推理,可以尝试更强模型,但速度会下降。

本地知识库不是模型越大越好。
更重要的是:

检索是否准确
资料是否干净
prompt 是否限制清楚
模型是否能稳定按资料回答

6. 使用 keep_alive 减少重复加载

如果频繁请求同一个模型,可以设置 keep_alive

例如:

payload = {
    "model": CHAT_MODEL,
    "prompt": prompt,
    "stream": False,
    "keep_alive": "10m"
}

这样模型会在一段时间内保留在内存中,下次调用可能更快。

但如果显存比较小,不建议让模型一直占着资源。


7. 合理设置上下文长度

如果你需要处理更长上下文,可以设置 num_ctx

例如:

payload = {
    "model": CHAT_MODEL,
    "prompt": prompt,
    "stream": False,
    "options": {
        "num_ctx": 4096
    }
}

上下文不是越大越好。
上下文越大,对显存和内存的压力也越大。

本地知识库应该优先靠检索减少上下文,而不是把所有资料都塞给模型。


十六、常见问题

问题 1:Embedding 模型没下载

如果报错提示模型不存在,可以先下载:

ollama pull nomic-embed-text

然后再运行程序。


问题 2:回答模型没下载

如果你代码里写的是:

CHAT_MODEL = "qwen2.5:7b"

就要确保本地有这个模型:

ollama list

如果没有,下载:

ollama pull qwen2.5:7b

问题 3:程序很慢

可能原因:

第一次加载模型
模型太大
文档太多
每次都重新生成 embedding
top_k 太大
prompt 太长
显存或内存不足

解决方法:

换小一点的模型
缓存文档向量
减少 top_k
减少 chunk 长度
提前加载模型
关闭占显存的软件

问题 4:检索结果不准

可能原因:

chunk 切分不好
embedding 模型不适合
问题表达和文档表达差异太大
文档里噪声太多
top_k 太小

解决方法:

重新切分文档
保留标题信息
增加 top_k
清洗文档内容
换 embedding 模型测试

问题 5:模型开始胡说

RAG 可以减少幻觉,但不能完全消除幻觉。

建议在 prompt 里明确限制:

只能根据资料回答。
资料没有提到时,必须说“资料中没有提到”。
不要编造资料外的信息。

同时最好让回答带来源,方便人工核查。


问题 6:上下文太长导致变慢

解决方法不是无限增加上下文,而是优化检索。

更好的做法是:

减少 chunk 数量
提高检索准确率
只取最相关内容
避免把整篇文档塞给模型

十七、本地知识库适合什么场景?

适合:

个人笔记问答
课程资料整理
项目文档查询
代码仓库说明
CSDN 草稿管理
PDF 摘要
本地学习助手
小型团队内部文档问答

不太适合:

高风险医疗判断
严肃法律结论
金融投资决策
完全无人审核的企业关键系统
超大规模文档检索

本地知识库很适合学习和个人项目。
但如果用于严肃场景,必须加入人工核查、来源引用、权限管理和日志审计。


十八、Ollama 做知识库的优缺点

优点

本地运行,隐私更可控
适合个人资料和项目资料
API 调用简单
可以降低云端 API 成本
适合学习 RAG 原理
可以接入 Python、网页、Electron 项目

缺点

本地模型能力受硬件限制
大文档处理需要优化
小模型仍然可能幻觉
向量检索效果取决于 embedding 模型
复杂项目需要数据库和前端支持
性能不如专业服务器方案

所以 Ollama 很适合做本地知识库入门和原型开发。
但如果是大型企业级系统,还需要更完整的架构。


十九、这个系列到这里学到了什么?

从第一篇到第六篇,我们其实完成了一条完整路线:

第一篇:认识 Ollama
第二篇:Windows 安装并运行第一个模型
第三篇:理解显卡、显存和模型大小
第四篇:学会模型管理和常用命令
第五篇:用 API 调用本地大模型
第六篇:用 Ollama 搭建简单本地知识库

这条路线走完之后,你对 Ollama 的理解就不只是“会打开一个模型聊天”。

你已经知道:

模型怎么运行
硬件怎么影响速度
模型怎么管理
API 怎么调用
Python 怎么接入
知识库怎么搭建
RAG 的基本流程是什么

这才是 Ollama 真正有价值的地方。

它不仅是一个本地 AI 工具,也是一个学习大模型应用开发的入口。


二十、总结

这篇文章用 Ollama 做了一个最小版本的本地知识库问答系统。

核心流程是:

文档切分
↓
生成 embedding
↓
保存向量
↓
用户提问
↓
问题向量化
↓
相似度检索
↓
拼接相关资料
↓
调用本地大模型回答

虽然这个 Demo 很简单,但它已经包含了 RAG 的核心思想。

如果后续继续升级,可以加入:

文件读取
PDF 解析
向量数据库
前端页面
历史对话
来源引用
模型切换
权限控制

到这里,Ollama 就已经从一个“本地聊天工具”升级成了一个“本地 AI 应用开发平台”。

如果你有一张还不错的显卡,又想学习 AI 应用开发、本地知识库、低成本 AI 项目,那么 Ollama 是非常值得长期折腾的工具。

Logo

Agent 垂直技术社区,欢迎活跃、内容共建。

更多推荐