进阶 11 分钟Stack

RAG:使用 ChromaDB 和 Mistral

直接回答

使用Mistral搭建本地RAG时,最简单的技术栈是Ollama(负责生成,并通过bge-m3计算嵌入向量)搭配文件模式的ChromaDB,无需服务器或PyTorch。模型方面,对于配备8至12 GB显存的显卡,ministral-3:8b(6.0 GB,Apache 2.0许可证,公布的上下文长度为256K)是一个不错的默认选择;16 GB显存可选择ministral-3:14b,更大显存则可选择mistral-small3.2:24b(15 GB)。有一项设置不能忘:增大Ollama的上下文窗口,确保提示词能容纳这些文段。

本指南使用两个 Python 脚本和一个在您电脑上运行的 Mistral 模型,构建完整的文档助手:您的 PDF 和文本文件会被切分并索引到 ChromaDB 中,随后检索到的段落会传给模型,由模型作答并引用来源。本指南还说明如何根据您的显存选择 Mistral 模型,以及哪些陷阱会导致 RAG 答非所问。

作者: Mohamed Meguedmi·更新于 2026-09-30·已在 Windows、macOS 和 Linux 上测试

#我们要构建的系统:完全在本地运行的 Mistral RAG

RAG(检索增强生成)是指找出您的文档中与问题相关的片段,再将其放入模型的提示词中,让模型依据这些片段回答。这里预期得到的是一个小型命令行工具:一个脚本为文档文件夹建立索引;另一个脚本读取问题,在 ChromaDB 中检索最相近的五个片段,通过 Ollama 将这些片段连同问题发送给 Mistral 模型,然后显示回答,再列出查阅过的文件。任何数据都不会离开这台机器:Ollama 提供生成模型和嵌入模型的服务,ChromaDB 将向量存储在本地文件夹中。

“Mistral” 这个名称有两种用法:一是 Mistral AI 的开放权重模型,可以自行下载并运行(本指南讨论的对象);二是该公司托管的 API,会将您的文本片段发送到它的服务器。对于保密文档,只有前者符合“100% 本地”的要求。

#用于 RAG 时该选择哪个 Mistral 模型

本地 RAG 工具包

你的文档,你的 AI:基于你的 PDF、笔记和邮件的可靠本地 RAG——无需向云端发送任何内容。

  • 在线空间,终身可用
  • PDF + 文件
  • 30 天内退款

RAG 有其特殊需求:模型必须严格遵循指令(“仅根据这些文本片段回答”),能够阅读多个片段而不混淆信息,并用法语作答。与自由对话相比,模型规模没那么重要;但可用于上下文的内存则更加重要。下列大小均为 Ollama 模型库中显示的默认量化版本大小。

Ollama 模型库中的 Mistral 模型(2026 年 9 月统计)
模型Ollama 模型大小公布的上下文长度适用人群
ministral-3:3b3.0 GB256K没有独立 GPU 的机器;回答简单,对复杂指令的适应能力较弱
ministral-3:8b6.0 GB256K对于配备 8–12 GB 显存的显卡或 16 GB 内存的笔记本电脑,这是合理的默认选择
ministral-3:14b9.1 GB256K配备 16 GB 显存的显卡,或在上下文长度适中的情况下使用 12 GB 显存的显卡
mistral-nemo (12B)查看 Ollama 页面128K较早的替代方案,仍广泛使用
mistral-small3.2:24b15 GB128K显存 24 GB 或统一内存 32 GB 及以上;在格式指令方面表现最稳定
mistral(7B,0.3 版)4.4 GB32K旧模型:只建议用于资源极为有限的设备

Ministral 3 系列(3B、8B 和 14B)采用 Apache 2.0 许可证发布,Mistral 3 的公告对此有说明。Ollama 上的页面将该系列描述为面向边缘部署设计,能够在多种硬件上运行。Mistral Small 4 于 2026 年发布,根据其 Hugging Face 模型页面的名称,总参数量为 1190 亿,面向服务器硬件,不适合作为个人电脑上的模型选择。要大致估算所需内存,本站的显存计算器会给出模型本身加上上下文缓存所需的内存。

i
标称上下文长度与实际可用上下文长度
128K 或 256K 的上下文长度表示模型支持的最大容量,而不是实际设置值:Ollama 默认使用的上下文远小于这一上限,而且较大的上下文会消耗额外内存。对于包含五个文本片段的 RAG,8 000 个 token 就足够了。

#技术栈

生成
由 Ollama 通过本地 HTTP API 端口 11434 提供的 Mistral 模型。
Embeddings
通过 Ollama 运行 bge-m3:模型库页面将其描述为 BAAI 的通用、多语言、多粒度模型,拥有 5.67 亿参数。这种方式无需安装 PyTorch 和 sentence-transformers。
向量数据库
ChromaDB 本地模式(PersistentClient):一个文件夹,无需服务器。Chroma 提供了名为 OllamaEmbeddingFunction 的封装,用于调用 Ollama 的嵌入 API。
文件读取
pypdf用于包含文本的PDF文件,Markdown和纯文本则使用直接读取。扫描件PDF是图像:首先需要进行字符识别。

#准备环境

  1. 01
    安装 Ollama 并下载模型
    安装Ollama,然后使用以下两条命令分别下载生成模型和嵌入模型。
  2. 02
    创建 Python 环境
    Python 3.10 或更高版本。虚拟环境可隔离项目依赖。
  3. 03
    放置文档
    将您的 PDF、Markdown 文件和文本文件复制到与脚本位于同一目录的 docs/ 文件夹中。
模型与依赖项
ollama pull ministral-3:8b
ollama pull bge-m3

mkdir mon-rag && cd mon-rag
python3 -m venv venv
source venv/bin/activate   # .\venv\Scripts\activate sous Windows
pip install chromadb pypdf requests

#2. 在 ChromaDB 中索引文档

脚本读取每个文件,在段落边界处将文本切分为约 1,800 个字符的片段,然后交给 Chroma,由 Chroma 通过 Ollama 调用 bge-m3 来计算向量。有两个重要细节:每个片段都会在元数据中保留文件名(用于引用来源),而且内容按批次添加,而不是一次添加一个片段。

index.py
from pathlib import Path
import chromadb
from chromadb.utils.embedding_functions.ollama_embedding_function import OllamaEmbeddingFunction
from pypdf import PdfReader

ef = OllamaEmbeddingFunction(url="http://localhost:11434", model_name="bge-m3")
coll = chromadb.PersistentClient(path="./chroma_db").get_or_create_collection("mes_docs", embedding_function=ef)

def lire(path: Path) -> str:
    if path.suffix.lower() == ".pdf":
        return "\n\n".join(p.extract_text() or "" for p in PdfReader(str(path)).pages)
    return path.read_text(encoding="utf-8", errors="ignore")

def decouper(texte: str, max_chars=1800):
    """Regroupe des paragraphes entiers jusqu'à max_chars ; un paragraphe trop long est coupé."""
    chunks, courant = [], ""
    for para in (p.strip() for p in texte.split("\n\n")):
        if not para:
            continue
        while len(para) > max_chars:
            if courant:
                chunks.append(courant); courant = ""
            chunks.append(para[:max_chars]); para = para[max_chars:]
        if len(courant) + len(para) + 2 > max_chars and courant:
            chunks.append(courant); courant = ""
        courant = (courant + "\n\n" + para).strip()
    if courant:
        chunks.append(courant)
    return chunks

n = 0
for path in sorted(Path("docs").rglob("*")):
    if path.suffix.lower() not in {".pdf", ".md", ".txt"}:
        continue
    chunks = decouper(lire(path))
    if not chunks:
        print(f"  ! {path.name} : aucun texte extrait (PDF scanné ?)")
        continue
    for i in range(0, len(chunks), 32):  # par lots de 32
        lot = chunks[i:i + 32]
        coll.upsert(
            ids=[f"{path.name}-{i + j}" for j in range(len(lot))],
            documents=lot,
            metadatas=[{"source": path.name}] * len(lot),
        )
    n += len(chunks)
    print(f"  + {path.name} : {len(chunks)} passages")
print(f"Terminé : {n} passages indexés")

使用 upsert,并以文件名和文本片段编号构建标识符,可以让脚本重复运行:重新索引同一文件夹时,会更新文本片段,而不是生成重复项。不过请注意:如果文档变短,多余的旧片段仍会留在数据库中;如果发生重大变更,请删除 chroma_db 文件夹并重新索引。文本片段大小的选择详见分块策略指南。

#3. 提问:检索后生成

第二个脚本包含问题,检索最接近的五个段落并构建提示词。其中的指令至关重要:它要求仅根据这些段落作答,缺少信息时明确承认,并引用文件。num_ctx 参数用于增大上下文窗口:Ollama 文档指出,默认窗口为 4096 个 token,可通过 OLLAMA_CONTEXT_LENGTH 变量或 num_ctx 参数修改。五个段落各有 400 至 500 个 token,再加上指令和回答,4096 个 token 已接近极限:上下文窗口过小时,内容会被静默截断,模型会在没有读到您所提供段落末尾的情况下作答。

ask.py
import sys, requests
import chromadb
from chromadb.utils.embedding_functions.ollama_embedding_function import OllamaEmbeddingFunction

MODELE = "ministral-3:8b"
ef = OllamaEmbeddingFunction(url="http://localhost:11434", model_name="bge-m3")
coll = chromadb.PersistentClient(path="./chroma_db").get_collection("mes_docs", embedding_function=ef)

SYSTEME = (
    "Tu réponds en français, uniquement à partir des passages fournis. "
    "Si la réponse n'y figure pas, dis-le clairement au lieu de deviner. "
    "Termine chaque affirmation par le nom du fichier source entre crochets."
)

def repondre(question: str, k: int = 5):
    res = coll.query(query_texts=[question], n_results=k)
    passages = list(zip(res["documents"][0], res["metadatas"][0]))
    contexte = "\n\n---\n\n".join(f"[{m['source']}]\n{p}" for p, m in passages)
    r = requests.post("http://localhost:11434/api/chat", json={
        "model": MODELE,
        "stream": False,
        "options": {"temperature": 0.2, "num_ctx": 8192},
        "messages": [
            {"role": "system", "content": SYSTEME},
            {"role": "user", "content": f"PASSAGES :\n{contexte}\n\nQUESTION : {question}"},
        ],
    }, timeout=300)
    r.raise_for_status()
    return r.json()["message"]["content"], sorted({m["source"] for _, m in passages})

if __name__ == "__main__":
    q = " ".join(sys.argv[1:]) or input("Question : ")
    reponse, sources = repondre(q)
    print("\n" + reponse)
    print("\nSources consultées :", ", ".join(sources))
启动
python index.py
python ask.py "Quel est le délai de préavis prévu au contrat ?"

#在归咎于模型之前,先检查 ChromaDB 返回的内容

当回答质量不佳时,原因出在两个环节之一:检索没有找回正确的文本片段,或者模型没有正确使用该片段。无需调用模型,只要显示检索到的片段及其距离值,就能区分这两种情况。如果正确片段不在前五个结果中,请调整切分方式,增加关键词搜索或重排序器。如果正确片段在其中,但回答仍然错误,问题就出在提示词、被截断的上下文或模型上:在下结论之前,先尝试大一档的模型。

debug.py:显示段落及其距离
import sys
import chromadb
from chromadb.utils.embedding_functions.ollama_embedding_function import OllamaEmbeddingFunction

ef = OllamaEmbeddingFunction(url="http://localhost:11434", model_name="bge-m3")
coll = chromadb.PersistentClient(path="./chroma_db").get_collection("mes_docs", embedding_function=ef)
res = coll.query(query_texts=[" ".join(sys.argv[1:])], n_results=8)
for doc, meta, dist in zip(res["documents"][0], res["metadatas"][0], res["distances"][0]):
    print(f"{dist:.3f}  {meta['source']}  {doc[:120]!r}")

#内存预算:同时需容纳的内容

RAG 需要同时使用两个模型:一个负责生成,另一个负责计算向量,此外还需要第一个模型的上下文缓存。Ollama 按需加载各个模型,也可以卸载其中一个,为另一个腾出空间;如果内存余量不足,每次切换都会增加延迟。表格给出了三种配置的粗略估算;模型大小来自 Ollama 模型库,其余部分是计算所得的估值,可借助本站的 VRAM 计算器进一步细化。

需预留的内存(Ollama 模型权重,8 192 token 上下文)
配置生成模型的大小待添加目标显卡
ministral-3:8b + bge-m36.0 GB上下文缓存、嵌入模型(5.67 亿参数,半精度下略大于一个 Go)、系统余量8至12 GB
ministral-3:14b + bge-m39.1 GB同上;在 12 GB 显存下,长上下文成为限制因素12 到 16 GB
mistral-small3.2:24b + bge-m315 GB相同;需预留充足的余量24 GB 或以上
→
如果内存不足
先减小 num_ctx(对于五段文本,8,192 已经很宽裕),再换用小一档的模型。同时避免增加文本片段的数量:片段过多不仅会占满内存,还会冲淡回答的重点。

#容易导致答非所问的陷阱

默认上下文长度过短
参见上文:如果没有调高 num_ctx,最后几个段落就会被截断。典型症状是:ChromaDB 确实检索到了正确答案,但模型却说找不到。
扫描的PDF文件
pypdf 仅能读取已存在的文本。扫描文件将返回空内容:脚本会将其显示出来。请先使用 OCR 工具处理文档,相关说明见 Tesseract 指南。
缺乏上下文的文本片段
从文档中截取的段落(如「期限为30天」)未说明具体内容。每个段落前应添加文档或章节标题。
文档中无答案的问题
如果没有‘明确说明’这一指令,模型会凭借自身知识填补空白。请始终测试那些答案不在您文件中的问题。
精确标识与术语
仅靠嵌入向量很难准确检索到合同编号或档案编号:请按照混合检索指南中的说明,加入关键词检索。
!
先核实,再信任
RAG会引用其来源,但这并不能证明回答的正确性:需打开所引用的文件以验证关键决策(如合同、数据、截止日期)。

#深入了解

按投入与效果之比排序的改进措施
优化努力适用场景
将检索返回的文本片段数量(k)从 5 增加到 8一行答案分散在多个段落中
按标题而非段落进行分块中等结构化文档(说明文档、按条款划分的合同)
BM25 + 向量混合搜索中等按标识符、缩写或专有名词提问
重排序模型(bge-reranker-v2-m3)中等正确答案被检索到,但排名位于第 5 位之后
聊天界面(Open WebUI,FastAPI API)视情况而定其他用户需要使用该工具
计划的备份与重新索引较低文档目录每周都会更新

每项改进都有对应的指南:请用 30 到 50 个真实问题,分别测量改进前后的召回率,而不是不断堆叠技术。如果您更喜欢无需编写代码的现成界面,无代码 RAG 指南介绍了 Open WebUI 和 AnythingLLM。

#使用 Mistral 实现 RAG 的常见问题

FAQ
本地 RAG 应选用哪个 Mistral 模型?+
对于显存为 8–12 GB 的显卡,ministral-3:8b(Ollama 版本为 6.0 GB,采用 Apache 2.0 许可证)是不错的起点;16 GB 显存可选 ministral-3:14b(9.1 GB);24 GB 及以上显存可选 mistral-small3.2:24b(15 GB)。请根据扣除模型占用后剩余的显存来选择:还需要为上下文留出空间。
Ollama 是否可以代替 sentence-transformers 计算嵌入向量?+
可以:Ollama 提供嵌入 API,并提供 bge-m3,这是一个拥有 5.67 亿参数的多语言模型。ChromaDB 提供 OllamaEmbeddingFunction 封装来调用该 API。优点是只需安装一个引擎,无需 PyTorch;缺点是在建立索引期间必须保持 Ollama 运行。
为什么模型表示未找到答案,而答案实际上存在于我的文档中?+
有两种常见原因。一是 Ollama 的上下文窗口太短,导致文本片段被截断:请将 num_ctx 提高到 8192。二是检索到的片段不包含答案:请在生成前检查 ChromaDB 返回的内容,然后调整文本切分或检索方式。
能否用 Mistral API 替代 Ollama?+
技术上可以,但您的文本会发送到企业服务器,这违背了敏感文档的隐私要求。为保持本地运行,请使用 Ollama 执行开放权重模型。对于非敏感文档,可使用 API;具体数据处理条款需查阅供应商说明。
如何添加新文档而无需重新索引全部内容?+
将它们复制到 docs/ 目录并重新运行 index.py:通过 upsert 和稳定的标识符,现有段落将被更新,新增段落将被添加。如果修改了段落长度或嵌入模型,删除 chroma_db 文件夹并重新索引所有内容:旧向量将不再可比。
运行这个 RAG 需要 GPU 吗?+
并非必须:ministral-3:3b 可在配备 8 GB 内存的现代处理器上运行,但响应较慢。索引操作仅需执行一次。GPU 主要提升响应速度,同时允许使用更大模型。在决定投入前,请先在自己的设备上测量响应时间。
这份指南对您有帮助吗?

有反馈、发现了错误,或想补充说明?请告诉我们,让这份指南对每个人都更有帮助。