欢迎光临
我们一直在努力

用 Python 搭建本地 AI 问答系统:避开 90% 新手都会踩的环境坑

在这里插入图片描述

欢迎文末添加好友交流,共同进步!

“ 俺はモンキー・D・ルフィ。海贼王になる男だ!”

在这里插入图片描述

    • 前言
    • 一、整体架构概览
    • 二、新手踩坑分布图
    • 三、环境搭建:最容易翻车的第一步
      • 3.1 用虚拟环境隔离,别污染全局
      • 3.2 PyTorch 安装:版本对齐是关键
      • 3.3 依赖管理:用 requirements.txt 锁定版本
    • 四、模型下载:别让网络毁了你的心情
      • 4.1 使用 Ollama 管理本地模型(强烈推荐)
      • 4.2 用 Python 调用 Ollama
    • 五、搭建 RAG 问答系统
      • 5.1 RAG 完整流程
      • 5.2 完整代码实现
    • 六、内存/显存管理:别让 OOM 毁了你
      • 6.1 显存需求参考
      • 6.2 显存不够?用量化压缩模型
      • 6.3 流式输出,避免等待超时
    • 七、加一个 Web 界面(可选)
    • 八、常见报错速查表
    • 九、国内加速技巧
    • 十、总结:搭建清单
    • 参考资源


前言

想在本地跑一个 AI 问答系统?听起来很酷,但现实往往是这样的:

“为什么我的 CUDA 版本和 PyTorch 不兼容?” “为什么 pip install 装了半天,运行时还是报 ModuleNotFoundError?” “为什么模型加载到一半内存就爆了?”

这些问题,90% 的新手都踩过。本文将带你从零搭建一个本地 AI 问答系统,并系统性地帮你绕开那些"经典陷阱"。 在这里插入图片描述


一、整体架构概览

在动手之前,先看清楚我们要搭建的是什么:

#mermaid-svg-KpaNKAOgnwZ4LmK7{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-KpaNKAOgnwZ4LmK7 .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-KpaNKAOgnwZ4LmK7 .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-KpaNKAOgnwZ4LmK7 .error-icon{fill:#552222;}#mermaid-svg-KpaNKAOgnwZ4LmK7 .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-KpaNKAOgnwZ4LmK7 .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-KpaNKAOgnwZ4LmK7 .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-KpaNKAOgnwZ4LmK7 .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-KpaNKAOgnwZ4LmK7 .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-KpaNKAOgnwZ4LmK7 .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-KpaNKAOgnwZ4LmK7 .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-KpaNKAOgnwZ4LmK7 .marker{fill:#333333;stroke:#333333;}#mermaid-svg-KpaNKAOgnwZ4LmK7 .marker.cross{stroke:#333333;}#mermaid-svg-KpaNKAOgnwZ4LmK7 svg{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-KpaNKAOgnwZ4LmK7 p{margin:0;}#mermaid-svg-KpaNKAOgnwZ4LmK7 .label{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;color:#333;}#mermaid-svg-KpaNKAOgnwZ4LmK7 .cluster-label text{fill:#333;}#mermaid-svg-KpaNKAOgnwZ4LmK7 .cluster-label span{color:#333;}#mermaid-svg-KpaNKAOgnwZ4LmK7 .cluster-label span p{background-color:transparent;}#mermaid-svg-KpaNKAOgnwZ4LmK7 .label text,#mermaid-svg-KpaNKAOgnwZ4LmK7 span{fill:#333;color:#333;}#mermaid-svg-KpaNKAOgnwZ4LmK7 .node rect,#mermaid-svg-KpaNKAOgnwZ4LmK7 .node circle,#mermaid-svg-KpaNKAOgnwZ4LmK7 .node ellipse,#mermaid-svg-KpaNKAOgnwZ4LmK7 .node polygon,#mermaid-svg-KpaNKAOgnwZ4LmK7 .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-KpaNKAOgnwZ4LmK7 .rough-node .label text,#mermaid-svg-KpaNKAOgnwZ4LmK7 .node .label text,#mermaid-svg-KpaNKAOgnwZ4LmK7 .image-shape .label,#mermaid-svg-KpaNKAOgnwZ4LmK7 .icon-shape .label{text-anchor:middle;}#mermaid-svg-KpaNKAOgnwZ4LmK7 .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-KpaNKAOgnwZ4LmK7 .rough-node .label,#mermaid-svg-KpaNKAOgnwZ4LmK7 .node .label,#mermaid-svg-KpaNKAOgnwZ4LmK7 .image-shape .label,#mermaid-svg-KpaNKAOgnwZ4LmK7 .icon-shape .label{text-align:center;}#mermaid-svg-KpaNKAOgnwZ4LmK7 .node.clickable{cursor:pointer;}#mermaid-svg-KpaNKAOgnwZ4LmK7 .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-KpaNKAOgnwZ4LmK7 .arrowheadPath{fill:#333333;}#mermaid-svg-KpaNKAOgnwZ4LmK7 .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-KpaNKAOgnwZ4LmK7 .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-KpaNKAOgnwZ4LmK7 .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-KpaNKAOgnwZ4LmK7 .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-KpaNKAOgnwZ4LmK7 .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-KpaNKAOgnwZ4LmK7 .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-KpaNKAOgnwZ4LmK7 .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-KpaNKAOgnwZ4LmK7 .cluster text{fill:#333;}#mermaid-svg-KpaNKAOgnwZ4LmK7 .cluster span{color:#333;}#mermaid-svg-KpaNKAOgnwZ4LmK7 div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-KpaNKAOgnwZ4LmK7 .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-KpaNKAOgnwZ4LmK7 rect.text{fill:none;stroke-width:0;}#mermaid-svg-KpaNKAOgnwZ4LmK7 .icon-shape,#mermaid-svg-KpaNKAOgnwZ4LmK7 .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-KpaNKAOgnwZ4LmK7 .icon-shape p,#mermaid-svg-KpaNKAOgnwZ4LmK7 .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-KpaNKAOgnwZ4LmK7 .icon-shape rect,#mermaid-svg-KpaNKAOgnwZ4LmK7 .image-shape rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-KpaNKAOgnwZ4LmK7 .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-KpaNKAOgnwZ4LmK7 .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-KpaNKAOgnwZ4LmK7 :root{–mermaid-font-family:\”trebuchet ms\”,verdana,arial,sans-serif;}

用户输入问题

文本预处理

是否需要检索?

向量数据库检索\\nFAISS / ChromaDB

直接送入 LLM

召回相关文档片段

构建 Prompt\\nRAG 增强

本地 LLM 推理\\nOllama / llama.cpp

生成回答

后处理 & 输出

用户看到答案

整个系统分为三层:

  • 输入层:用户问题 + 文本预处理
  • 检索层(可选):RAG(检索增强生成)
  • 推理层:本地 LLM 生成答案

二、新手踩坑分布图

根据社区反馈,新手遇到的问题主要集中在以下几类:

#mermaid-svg-bziwXUMwvZ5mqwQO{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-bziwXUMwvZ5mqwQO .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-bziwXUMwvZ5mqwQO .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-bziwXUMwvZ5mqwQO .error-icon{fill:#552222;}#mermaid-svg-bziwXUMwvZ5mqwQO .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-bziwXUMwvZ5mqwQO .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-bziwXUMwvZ5mqwQO .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-bziwXUMwvZ5mqwQO .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-bziwXUMwvZ5mqwQO .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-bziwXUMwvZ5mqwQO .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-bziwXUMwvZ5mqwQO .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-bziwXUMwvZ5mqwQO .marker{fill:#333333;stroke:#333333;}#mermaid-svg-bziwXUMwvZ5mqwQO .marker.cross{stroke:#333333;}#mermaid-svg-bziwXUMwvZ5mqwQO svg{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-bziwXUMwvZ5mqwQO p{margin:0;}#mermaid-svg-bziwXUMwvZ5mqwQO .pieCircle{stroke:#000000;stroke-width:2px;opacity:0.7;}#mermaid-svg-bziwXUMwvZ5mqwQO .pieOuterCircle{stroke:#000000;stroke-width:1px;fill:none;}#mermaid-svg-bziwXUMwvZ5mqwQO .pieTitleText{text-anchor:middle;font-size:25px;fill:#000000;font-family:\”trebuchet ms\”,verdana,arial,sans-serif;}#mermaid-svg-bziwXUMwvZ5mqwQO .slice{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;fill:#000000;font-size:17px;}#mermaid-svg-bziwXUMwvZ5mqwQO .legend text{fill:#000000;font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:17px;}#mermaid-svg-bziwXUMwvZ5mqwQO :root{–mermaid-font-family:\”trebuchet ms\”,verdana,arial,sans-serif;}

32%

25%

18%

12%

8%

5%

新手搭建本地 AI 系统的踩坑分布

Python 环境/依赖冲突

CUDA/GPU 驱动不兼容

模型下载失败或损坏

内存/显存不足崩溃

API 调用姿势错误

其他配置问题

接下来,我们按照这个优先级,逐一击破。


三、环境搭建:最容易翻车的第一步

3.1 用虚拟环境隔离,别污染全局

❌ 新手常见错误:

pip install torch transformers langchain # 直接装到全局

✅ 正确做法:用 venv 或 conda 隔离环境

# 方式一:使用 venv(推荐,Python 内置)
python -m venv ai-qa-env
source ai-qa-env/bin/activate # Linux/macOS
ai-qa-env\\Scripts\\activate # Windows

# 方式二:使用 conda
conda create -n ai-qa python=3.11
conda activate ai-qa

💡 为什么要隔离? 不同项目依赖不同版本的库,全局安装会导致版本冲突,出了问题极难排查。


3.2 PyTorch 安装:版本对齐是关键

这是 最高频的踩坑点。PyTorch 的安装命令取决于你的 CUDA 版本,不能无脑 pip install torch。

第一步:查看你的 CUDA 版本

nvidia-smi # 查看 GPU 驱动支持的最高 CUDA 版本
nvcc –version # 查看已安装的 CUDA Toolkit 版本

第二步:去官网生成对应命令

# CUDA 12.1 对应的安装命令示例
pip install torch torchvision torchaudio –index-url https://download.pytorch.org/whl/cu121

# 没有 GPU,只用 CPU
pip install torch torchvision torchaudio –index-url https://download.pytorch.org/whl/cpu

第三步:验证安装是否成功

import torch

print(f"PyTorch 版本: {torch.__version__}")
print(f"CUDA 是否可用: {torch.cuda.is_available()}")
print(f"GPU 数量: {torch.cuda.device_count()}")

if torch.cuda.is_available():
print(f"当前 GPU: {torch.cuda.get_device_name(0)}")
print(f"显存总量: {torch.cuda.get_device_properties(0).total_memory / 1024**3:.1f} GB")


3.3 依赖管理:用 requirements.txt 锁定版本

# 生成当前环境的依赖快照
pip freeze > requirements.txt

# 在新环境中还原
pip install -r requirements.txt

推荐的 requirements.txt 示例:

torch==2.2.0
transformers==4.38.0
langchain==0.1.9
langchain-community==0.0.24
faiss-cpu==1.7.4
sentence-transformers==2.5.1
ollama==0.1.7
gradio==4.19.2


四、模型下载:别让网络毁了你的心情

4.1 使用 Ollama 管理本地模型(强烈推荐)

Ollama 是目前最省心的本地模型管理工具,一行命令搞定下载和运行:

# 安装 Ollama(macOS/Linux)
curl -fsSL https://ollama.com/install.sh | sh

# 下载并运行模型
ollama pull llama3.2 # Meta Llama 3.2 (3B)
ollama pull qwen2.5:7b # 阿里通义千问 2.5 (7B)
ollama pull deepseek-r1:7b # DeepSeek R1 (7B)

# 验证模型列表
ollama list

4.2 用 Python 调用 Ollama

import ollama

def ask_local_llm(question: str, model: str = "qwen2.5:7b") > str:
"""
调用本地 Ollama 模型进行问答

Args:
question: 用户问题
model: 模型名称

Returns:
模型回答
"""
response = ollama.chat(
model=model,
messages=[
{
"role": "system",
"content": "你是一个专业的 AI 助手,请用中文简洁准确地回答问题。"
},
{
"role": "user",
"content": question
}
]
)
return response["message"]["content"]

# 测试
if __name__ == "__main__":
answer = ask_local_llm("Python 中的 GIL 是什么?")
print(answer)


五、搭建 RAG 问答系统

RAG(Retrieval-Augmented Generation)是让 AI 能回答你私有文档问题的核心技术。

5.1 RAG 完整流程

#mermaid-svg-aieE8WUOjZBOfqhr{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-aieE8WUOjZBOfqhr .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-aieE8WUOjZBOfqhr .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-aieE8WUOjZBOfqhr .error-icon{fill:#552222;}#mermaid-svg-aieE8WUOjZBOfqhr .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-aieE8WUOjZBOfqhr .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-aieE8WUOjZBOfqhr .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-aieE8WUOjZBOfqhr .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-aieE8WUOjZBOfqhr .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-aieE8WUOjZBOfqhr .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-aieE8WUOjZBOfqhr .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-aieE8WUOjZBOfqhr .marker{fill:#333333;stroke:#333333;}#mermaid-svg-aieE8WUOjZBOfqhr .marker.cross{stroke:#333333;}#mermaid-svg-aieE8WUOjZBOfqhr svg{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-aieE8WUOjZBOfqhr p{margin:0;}#mermaid-svg-aieE8WUOjZBOfqhr .label{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;color:#333;}#mermaid-svg-aieE8WUOjZBOfqhr .cluster-label text{fill:#333;}#mermaid-svg-aieE8WUOjZBOfqhr .cluster-label span{color:#333;}#mermaid-svg-aieE8WUOjZBOfqhr .cluster-label span p{background-color:transparent;}#mermaid-svg-aieE8WUOjZBOfqhr .label text,#mermaid-svg-aieE8WUOjZBOfqhr span{fill:#333;color:#333;}#mermaid-svg-aieE8WUOjZBOfqhr .node rect,#mermaid-svg-aieE8WUOjZBOfqhr .node circle,#mermaid-svg-aieE8WUOjZBOfqhr .node ellipse,#mermaid-svg-aieE8WUOjZBOfqhr .node polygon,#mermaid-svg-aieE8WUOjZBOfqhr .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-aieE8WUOjZBOfqhr .rough-node .label text,#mermaid-svg-aieE8WUOjZBOfqhr .node .label text,#mermaid-svg-aieE8WUOjZBOfqhr .image-shape .label,#mermaid-svg-aieE8WUOjZBOfqhr .icon-shape .label{text-anchor:middle;}#mermaid-svg-aieE8WUOjZBOfqhr .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-aieE8WUOjZBOfqhr .rough-node .label,#mermaid-svg-aieE8WUOjZBOfqhr .node .label,#mermaid-svg-aieE8WUOjZBOfqhr .image-shape .label,#mermaid-svg-aieE8WUOjZBOfqhr .icon-shape .label{text-align:center;}#mermaid-svg-aieE8WUOjZBOfqhr .node.clickable{cursor:pointer;}#mermaid-svg-aieE8WUOjZBOfqhr .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-aieE8WUOjZBOfqhr .arrowheadPath{fill:#333333;}#mermaid-svg-aieE8WUOjZBOfqhr .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-aieE8WUOjZBOfqhr .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-aieE8WUOjZBOfqhr .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-aieE8WUOjZBOfqhr .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-aieE8WUOjZBOfqhr .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-aieE8WUOjZBOfqhr .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-aieE8WUOjZBOfqhr .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-aieE8WUOjZBOfqhr .cluster text{fill:#333;}#mermaid-svg-aieE8WUOjZBOfqhr .cluster span{color:#333;}#mermaid-svg-aieE8WUOjZBOfqhr div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-aieE8WUOjZBOfqhr .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-aieE8WUOjZBOfqhr rect.text{fill:none;stroke-width:0;}#mermaid-svg-aieE8WUOjZBOfqhr .icon-shape,#mermaid-svg-aieE8WUOjZBOfqhr .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-aieE8WUOjZBOfqhr .icon-shape p,#mermaid-svg-aieE8WUOjZBOfqhr .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-aieE8WUOjZBOfqhr .icon-shape rect,#mermaid-svg-aieE8WUOjZBOfqhr .image-shape rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-aieE8WUOjZBOfqhr .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-aieE8WUOjZBOfqhr .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-aieE8WUOjZBOfqhr :root{–mermaid-font-family:\”trebuchet ms\”,verdana,arial,sans-serif;}

💬 在线阶段(每次查询)

📚 离线阶段(一次性)

加载文档\\nPDF/TXT/MD

文本分块\\nChunk Splitting

向量化\\nEmbedding

存入向量库\\nFAISS/Chroma

用户提问

问题向量化

相似度检索

召回 Top-K 文档

构建增强 Prompt

LLM 生成回答

返回答案

5.2 完整代码实现

"""
本地 RAG 问答系统
依赖: pip install langchain langchain-community faiss-cpu sentence-transformers ollama
"""

from langchain.text_splitter import RecursiveCharacterTextSplitter
from langchain_community.vectorstores import FAISS
from langchain_community.embeddings import HuggingFaceEmbeddings
from langchain_community.document_loaders import TextLoader, DirectoryLoader
from langchain.chains import RetrievalQA
from langchain_community.llms import Ollama
import os

class LocalRAGSystem:
"""本地 RAG 问答系统"""

def __init__(
self,
docs_dir: str = "./docs",
model_name: str = "qwen2.5:7b",
embedding_model: str = "BAAI/bge-small-zh-v1.5",
chunk_size: int = 500,
chunk_overlap: int = 50,
):
self.docs_dir = docs_dir
self.model_name = model_name
self.chunk_size = chunk_size
self.chunk_overlap = chunk_overlap

print("🔧 初始化 Embedding 模型…")
# 使用本地 Embedding 模型,避免调用外部 API
self.embeddings = HuggingFaceEmbeddings(
model_name=embedding_model,
model_kwargs={"device": "cpu"}, # 改为 "cuda" 可用 GPU 加速
encode_kwargs={"normalize_embeddings": True},
)

self.vectorstore = None
self.qa_chain = None

def load_and_index(self):
"""加载文档并建立向量索引"""
print(f"📂 加载文档目录: {self.docs_dir}")

# 支持多种文档格式
loader = DirectoryLoader(
self.docs_dir,
glob="**/*.txt",
loader_cls=TextLoader,
loader_kwargs={"encoding": "utf-8"},
)
documents = loader.load()
print(f"✅ 加载了 {len(documents)} 个文档")

# 文本分块
splitter = RecursiveCharacterTextSplitter(
chunk_size=self.chunk_size,
chunk_overlap=self.chunk_overlap,
separators=["\\n\\n", "\\n", "。", "!", "?", " ", ""],
)
chunks = splitter.split_documents(documents)
print(f"✅ 分割为 {len(chunks)} 个文本块")

# 建立向量索引
print("🔍 建立向量索引(首次较慢,请耐心等待)…")
self.vectorstore = FAISS.from_documents(chunks, self.embeddings)
print("✅ 向量索引建立完成")

# 保存索引到本地(下次直接加载,无需重建)
self.vectorstore.save_local("./faiss_index")
print("💾 索引已保存到 ./faiss_index")

def load_existing_index(self):
"""加载已有的向量索引"""
if os.path.exists("./faiss_index"):
print("📦 加载已有向量索引…")
self.vectorstore = FAISS.load_local(
"./faiss_index",
self.embeddings,
allow_dangerous_deserialization=True,
)
print("✅ 索引加载完成")
else:
print("⚠️ 未找到已有索引,请先调用 load_and_index()")

def build_qa_chain(self):
"""构建问答链"""
if self.vectorstore is None:
raise ValueError("请先调用 load_and_index() 或 load_existing_index()")

print(f"🤖 连接本地 LLM: {self.model_name}")
llm = Ollama(
model=self.model_name,
temperature=0.1, # 降低随机性,让回答更稳定
)

retriever = self.vectorstore.as_retriever(
search_type="similarity",
search_kwargs={"k": 3}, # 召回最相关的 3 个文档块
)

self.qa_chain = RetrievalQA.from_chain_type(
llm=llm,
chain_type="stuff",
retriever=retriever,
return_source_documents=True,
)
print("✅ 问答系统就绪!")

def ask(self, question: str) > dict:
"""
提问并获取答案

Returns:
dict: {"answer": str, "sources": list}
"""
if self.qa_chain is None:
raise ValueError("请先调用 build_qa_chain()")

result = self.qa_chain.invoke({"query": question})

return {
"answer": result["result"],
"sources": [
doc.metadata.get("source", "未知来源")
for doc in result["source_documents"]
],
}

# ============ 使用示例 ============
if __name__ == "__main__":
# 初始化系统
rag = LocalRAGSystem(
docs_dir="./my_docs",
model_name="qwen2.5:7b",
)

# 首次使用:加载文档并建立索引
rag.load_and_index()

# 后续使用:直接加载已有索引(更快)
# rag.load_existing_index()

# 构建问答链
rag.build_qa_chain()

# 开始问答
while True:
question = input("\\n❓ 请输入问题(输入 q 退出): ").strip()
if question.lower() == "q":
break

result = rag.ask(question)
print(f"\\n💡 回答:\\n{result['answer']}")
print(f"\\n📎 参考来源: {', '.join(result['sources'])}")


六、内存/显存管理:别让 OOM 毁了你

6.1 显存需求参考

#mermaid-svg-r9d6qqdCyQZ4bOnG{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-r9d6qqdCyQZ4bOnG .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-r9d6qqdCyQZ4bOnG .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-r9d6qqdCyQZ4bOnG .error-icon{fill:#552222;}#mermaid-svg-r9d6qqdCyQZ4bOnG .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-r9d6qqdCyQZ4bOnG .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-r9d6qqdCyQZ4bOnG .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-r9d6qqdCyQZ4bOnG .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-r9d6qqdCyQZ4bOnG .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-r9d6qqdCyQZ4bOnG .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-r9d6qqdCyQZ4bOnG .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-r9d6qqdCyQZ4bOnG .marker{fill:#333333;stroke:#333333;}#mermaid-svg-r9d6qqdCyQZ4bOnG .marker.cross{stroke:#333333;}#mermaid-svg-r9d6qqdCyQZ4bOnG svg{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-r9d6qqdCyQZ4bOnG p{margin:0;}#mermaid-svg-r9d6qqdCyQZ4bOnG .pieCircle{stroke:#000000;stroke-width:2px;opacity:0.7;}#mermaid-svg-r9d6qqdCyQZ4bOnG .pieOuterCircle{stroke:#000000;stroke-width:1px;fill:none;}#mermaid-svg-r9d6qqdCyQZ4bOnG .pieTitleText{text-anchor:middle;font-size:25px;fill:#000000;font-family:\”trebuchet ms\”,verdana,arial,sans-serif;}#mermaid-svg-r9d6qqdCyQZ4bOnG .slice{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;fill:#000000;font-size:17px;}#mermaid-svg-r9d6qqdCyQZ4bOnG .legend text{fill:#000000;font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:17px;}#mermaid-svg-r9d6qqdCyQZ4bOnG :root{–mermaid-font-family:\”trebuchet ms\”,verdana,arial,sans-serif;}

50%

27%

12%

8%

4%

不同规模模型的显存需求(FP16 精度)

7B 模型 (~14GB)

13B 模型 (~26GB)

3B 模型 (~6GB)

1B 模型 (~2GB)

量化 4bit 7B (~4GB)

6.2 显存不够?用量化压缩模型

from transformers import AutoModelForCausalLM, AutoTokenizer, BitsAndBytesConfig
import torch

# 4-bit 量化配置(显存减少约 75%)
quantization_config = BitsAndBytesConfig(
load_in_4bit=True,
bnb_4bit_compute_dtype=torch.float16,
bnb_4bit_use_double_quant=True,
bnb_4bit_quant_type="nf4",
)

model_id = "Qwen/Qwen2.5-7B-Instruct"

tokenizer = AutoTokenizer.from_pretrained(model_id)
model = AutoModelForCausalLM.from_pretrained(
model_id,
quantization_config=quantization_config,
device_map="auto", # 自动分配到 GPU/CPU
)

print(f"模型加载完成,占用显存: {torch.cuda.memory_allocated() / 1024**3:.2f} GB")

6.3 流式输出,避免等待超时

import ollama

def stream_answer(question: str, model: str = "qwen2.5:7b"):
"""流式输出,边生成边显示"""
print("💬 ", end="", flush=True)

for chunk in ollama.chat(
model=model,
messages=[{"role": "user", "content": question}],
stream=True, # 开启流式输出
):
content = chunk["message"]["content"]
print(content, end="", flush=True)

print() # 换行

stream_answer("用一句话解释什么是 Transformer 架构")


七、加一个 Web 界面(可选)

用 Gradio 5 分钟搭一个好看的 Web 界面:

import gradio as gr
from local_rag import LocalRAGSystem # 引用上面的代码

# 初始化 RAG 系统
rag = LocalRAGSystem()
rag.load_existing_index()
rag.build_qa_chain()

def chat(message: str, history: list) > str:
"""Gradio 聊天回调函数"""
if not message.strip():
return "请输入问题"

result = rag.ask(message)
answer = result["answer"]
sources = result["sources"]

if sources:
answer += f"\\n\\n—\\n📎 **参考来源**: {', '.join(set(sources))}"

return answer

# 创建 Gradio 界面
demo = gr.ChatInterface(
fn=chat,
title="🤖 本地 AI 问答系统",
description="基于本地 LLM + RAG 的私有知识库问答",
examples=[
"这个系统是如何工作的?",
"请总结一下主要内容",
],
theme=gr.themes.Soft(),
)

if __name__ == "__main__":
demo.launch(
server_name="0.0.0.0",
server_port=7860,
share=False, # 改为 True 可生成公网链接
)

运行后访问 http://localhost:7860 即可使用。


八、常见报错速查表

报错信息原因解决方案
CUDA out of memory 显存不足 使用量化模型或减小 batch_size
ModuleNotFoundError: No module named 'torch' 虚拟环境未激活 激活对应的 venv/conda 环境
RuntimeError: CUDA error: no kernel image is available PyTorch 与 CUDA 版本不匹配 重新安装对应 CUDA 版本的 PyTorch
ConnectionRefusedError: [Errno 111] Ollama 服务未启动 运行 ollama serve
OSError: [Errno 28] No space left on device 磁盘空间不足 清理磁盘或更换存储路径
ValueError: Tokenizer class … not found transformers 版本过低 pip install -U transformers
huggingface_hub.utils._errors.EntryNotFoundError 模型名称错误或网络问题 检查模型 ID 或使用镜像源

九、国内加速技巧

# 设置 HuggingFace 镜像(国内访问加速)
export HF_ENDPOINT=https://hf-mirror.com

# pip 使用清华镜像
pip install -i https://pypi.tuna.tsinghua.edu.cn/simple transformers

# conda 使用清华镜像
conda config –add channels https://mirrors.tuna.tsinghua.edu.cn/anaconda/pkgs/main

# 在代码中指定镜像
import os
os.environ["HF_ENDPOINT"] = "https://hf-mirror.com"

from transformers import AutoTokenizer
# 之后的下载会自动走镜像
tokenizer = AutoTokenizer.from_pretrained("Qwen/Qwen2.5-7B-Instruct")


十、总结:搭建清单

#mermaid-svg-fW9HHHijjFcEwyVM{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-fW9HHHijjFcEwyVM .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-fW9HHHijjFcEwyVM .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-fW9HHHijjFcEwyVM .error-icon{fill:#552222;}#mermaid-svg-fW9HHHijjFcEwyVM .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-fW9HHHijjFcEwyVM .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-fW9HHHijjFcEwyVM .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-fW9HHHijjFcEwyVM .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-fW9HHHijjFcEwyVM .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-fW9HHHijjFcEwyVM .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-fW9HHHijjFcEwyVM .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-fW9HHHijjFcEwyVM .marker{fill:#333333;stroke:#333333;}#mermaid-svg-fW9HHHijjFcEwyVM .marker.cross{stroke:#333333;}#mermaid-svg-fW9HHHijjFcEwyVM svg{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-fW9HHHijjFcEwyVM p{margin:0;}#mermaid-svg-fW9HHHijjFcEwyVM .label{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;color:#333;}#mermaid-svg-fW9HHHijjFcEwyVM .cluster-label text{fill:#333;}#mermaid-svg-fW9HHHijjFcEwyVM .cluster-label span{color:#333;}#mermaid-svg-fW9HHHijjFcEwyVM .cluster-label span p{background-color:transparent;}#mermaid-svg-fW9HHHijjFcEwyVM .label text,#mermaid-svg-fW9HHHijjFcEwyVM span{fill:#333;color:#333;}#mermaid-svg-fW9HHHijjFcEwyVM .node rect,#mermaid-svg-fW9HHHijjFcEwyVM .node circle,#mermaid-svg-fW9HHHijjFcEwyVM .node ellipse,#mermaid-svg-fW9HHHijjFcEwyVM .node polygon,#mermaid-svg-fW9HHHijjFcEwyVM .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-fW9HHHijjFcEwyVM .rough-node .label text,#mermaid-svg-fW9HHHijjFcEwyVM .node .label text,#mermaid-svg-fW9HHHijjFcEwyVM .image-shape .label,#mermaid-svg-fW9HHHijjFcEwyVM .icon-shape .label{text-anchor:middle;}#mermaid-svg-fW9HHHijjFcEwyVM .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-fW9HHHijjFcEwyVM .rough-node .label,#mermaid-svg-fW9HHHijjFcEwyVM .node .label,#mermaid-svg-fW9HHHijjFcEwyVM .image-shape .label,#mermaid-svg-fW9HHHijjFcEwyVM .icon-shape .label{text-align:center;}#mermaid-svg-fW9HHHijjFcEwyVM .node.clickable{cursor:pointer;}#mermaid-svg-fW9HHHijjFcEwyVM .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-fW9HHHijjFcEwyVM .arrowheadPath{fill:#333333;}#mermaid-svg-fW9HHHijjFcEwyVM .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-fW9HHHijjFcEwyVM .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-fW9HHHijjFcEwyVM .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-fW9HHHijjFcEwyVM .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-fW9HHHijjFcEwyVM .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-fW9HHHijjFcEwyVM .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-fW9HHHijjFcEwyVM .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-fW9HHHijjFcEwyVM .cluster text{fill:#333;}#mermaid-svg-fW9HHHijjFcEwyVM .cluster span{color:#333;}#mermaid-svg-fW9HHHijjFcEwyVM div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-fW9HHHijjFcEwyVM .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-fW9HHHijjFcEwyVM rect.text{fill:none;stroke-width:0;}#mermaid-svg-fW9HHHijjFcEwyVM .icon-shape,#mermaid-svg-fW9HHHijjFcEwyVM .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-fW9HHHijjFcEwyVM .icon-shape p,#mermaid-svg-fW9HHHijjFcEwyVM .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-fW9HHHijjFcEwyVM .icon-shape rect,#mermaid-svg-fW9HHHijjFcEwyVM .image-shape rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-fW9HHHijjFcEwyVM .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-fW9HHHijjFcEwyVM .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-fW9HHHijjFcEwyVM :root{–mermaid-font-family:\”trebuchet ms\”,verdana,arial,sans-serif;}

开始

✅ 创建虚拟环境\\nvenv / conda

✅ 查 CUDA 版本\\nnvidia-smi

✅ 安装对应 PyTorch\\n官网生成命令

✅ 安装 Ollama\\n管理本地模型

✅ 下载模型\\nollama pull

需要 RAG?

✅ 准备文档\\n放入 docs 目录

✅ 建立向量索引\\nFAISS / Chroma

✅ 构建问答链\\nLangChain

✅ 直接调用 LLM\\nollama.chat

✅ 可选:Gradio Web UI

🎉 系统就绪!


参考资源

  • Ollama 官网 — 本地模型管理
  • LangChain 文档 — RAG 框架
  • HuggingFace 镜像站 — 国内模型下载
  • PyTorch 安装向导 — 版本对齐工具
  • FAISS 文档 — 向量检索库

✍️ 坚持用 清晰易懂的图解 + 可落地的代码,让每个知识点都 简单直观

💡 座右铭:“道路是曲折的,前途是光明的!”

在这里插入图片描述

赞(0)
未经允许不得转载:171主机测评 » 用 Python 搭建本地 AI 问答系统:避开 90% 新手都会踩的环境坑
分享到: 更多 (0)

评论 抢沙发

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址