ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

DeepSeek RAG环境搭建:避开Embedding与向量库的坑

DeepSeek RAG环境搭建:避开Embedding与向量库的坑 简介这份基于DeepSeek搭建RAG系统的实战教程面向需要落地检索增强生成应用的深度学习开发与运维人员重点解决模型环境配置繁杂、GPU与容器工具链协调不畅等问题核心技术栈涵盖CUDA、vLLM与Docker。文档从技术栈概要说起按Dify服务器ECS-1、Rerank/Embedding模型服务器ECS-2、DeepSeek模型服务器ECS-3三部分展开部署路径覆盖Ubuntu/CentOS环境配置、Tesla驱动与CUDA版本选择、xinference安装、bge-reranker-large与bge-large-zh-v1.5部署、vLLM安装以及Python相关依赖处理等环节。资源为单个docx文档约580KB以图文步骤和命令说明为主便于对照操作与回溯检查。已有324人学习下载适合刚接触RAG或需要快速搭建DeepSeek检索增强环境的读者作为入门到实操的参考。1. DeepSeek RAG 环境搭建卡人的不是 DeepSeek 而是 Embedding基于 DeepSeek 搭建 RAG 系统环境搭建这一步真正卡住人的往往不是 DeepSeek 本身而是它没有开放 Embedding 接口。很多人照着“调用 API”的教程走聊得很顺一到检索问答就发现文档拼不进去。这篇教程讲的是从一台干净机器开始把 Python 虚拟环境、文本拆分工具、本地 Embedding 模型、向量库和 DeepSeek 接口一次性装齐跑通一个能回答私有文档问题的最小 RAG 系统。适合零基础想本地跑知识库的读者也适合要快速做技术验证的一线开发者。先说预期按这套走30 到 60 分钟能把环境搭完后面每一层我都给了参数和踩坑记录。2. 先选路线再装包DeepSeek 访问方式与 RAG 依赖环境2.1 API 还是本地部署两条路线怎么选环境搭建的第一步不是敲命令而是决定 DeepSeek 以什么形态出现在你的 RAG 系统里。这决定了后续依赖装什么、显存要多大、接口怎么配。市面上常见的做法是分两条路线。第一条是官方 API 路线直接调用 DeepSeek 的在线接口模型是 deepseek-chat 或 deepseek-reasoner你的机器只需要跑检索和 Embedding负担很小普通笔记本就能撑起来。第二条是本地部署路线用 Ollama 或 vLLM 把开源模型跑在自己机器上适合隐私敏感或需要离线运行的场景代价是显存和推理速度。两者对 RAG 框架本身没有区别因为 DeepSeek 的 API 兼容 OpenAI 协议本地部署也几乎都暴露成同一套接口。我给你的建议是如果是第一次搭或者只是做技术验证直接走官方 API。原因很实在——环境搭建的不可控因素已经够多了别再让大模型推理层添乱。等 RAG 链路跑通、确认检索质量没问题再考虑把底层模型换成本地部署这时候改动只是换一个 base_url 和 model 名的事。下面的依赖清单也是按“API 本地检索”的组合来配的。2.2 Python 虚拟环境与依赖清单一把装齐RAG 环境对 Python 版本有硬性要求我建议用 3.10 到 3.12 之间的版本太老或太新都会碰到依赖兼容问题。装包之前先建虚拟环境别直接装进系统 Python否则 LangChain、Chroma、SentenceTransformer 这几个库的依赖打架时你会连项目目录都不敢删。mkdir -p rag-workspace cd rag-workspace python -m venv .venv source .venv/bin/activate # Windows 下用 .venv\Scripts\activate pip install --upgrade pip pip install langchain0.3,0.4 \ langchain-community0.3,0.4 \ langchain-openai0.3,0.4 \ langchain-text-splitters0.3,0.4 \ chromadb0.5,0.6 \ faiss-cpu1.9,2.0 \ sentence-transformers3.0,4.0 \ pypdf python-docx openpyxl \ python-dotenv这里每个包都不是随手装的。langchain 是 RAG 编排框架langchain-openai 是 DeepSeek 接口的接入层因为 DeepSeek 兼容 OpenAI 协议直接用这个包就能调langchain-text-splitters 在 0.3 版本被独立拆出来了不装会报缺失chromadb 负责向量存储faiss-cpu 是备选的向量检索库sentence-transformers 用来跑本地 Embedding 模型pypdf、python-docx、openpyxl 分别处理 PDF、Word 和 Excel 三种最常见的知识库文件格式。最后那个 python-dotenv 是给 API Key 用的不建议把 Key 写死在代码里。装完验证一下环境是否完整跑一个最简导入python -c from langchain_openai import ChatOpenAI; from langchain_community.vectorstores import Chroma; from sentence_transformers import SentenceTransformer; print(ok)这条命令能一次确认三个核心组件都可用。如果报错优先看是什么包导入失败再针对那个包单独重装不要一上来就全部卸载重来。2.3 配置 DeepSeek 接口Key、base_url 与 .env环境装完先把 DeepSeek 接口配好这样后面每一步调试都能确认“大模型侧是通的”。在 rag-workspace 下创建一个 .env 文件内容如下DEEPSEEK_API_KEYsk-你的密钥 DEEPSEEK_BASE_URLhttps://api.deepseek.com DEEPSEEK_MODELdeepseek-chat注意 base_url 不要随手加 /v1 后缀。DeepSeek 官方文档给的地址是 https://api.deepseek.com加了 /v1 在部分 SDK 版本里也能通但没必要给自己留一个可以避免的不确定因素。model 建议先用 deepseek-chat它是通用对话模型RAG 问答场景足够deepseek-reasoner 是推理模型回答更慢适合后面做复杂问题进阶时再换。写一段代码验证接口连通性顺便把读取 .env 的习惯建立起来import os from dotenv import load_dotenv from langchain_openai import ChatOpenAI load_dotenv() llm ChatOpenAI( modelos.getenv(DEEPSEEK_MODEL, deepseek-chat), api_keyos.getenv(DEEPSEEK_API_KEY), base_urlos.getenv(DEEPSEEK_BASE_URL, https://api.deepseek.com), temperature0.3, ) resp llm.invoke(用一句话说明什么是 RAG) print(resp.content)这里重点说明两个参数。temperature 我习惯设 0.3RAG 场景要求回答紧贴知识库内容温度太高容易让模型自由发挥编出知识库里没有的东西base_url 是这次环境搭建里最容易翻车的点后面避坑章节会专门展开。把能跑通这一小段作为环境搭建的第一里程碑比直接冲 RAG 全链路稳妥得多。3. 给 RAG 配好“检索引擎”Embedding 模型与向量库的选型落地3.1 为什么必须自备 EmbeddingDeepSeek 不提供文本向量接口做 RAG 环境搭建时很多人会默认“DeepSeek 既然是大模型向量化肯定也一并搞定”。这是最大的误解。DeepSeek 官方 API 目前不提供 Embedding 接口你没法把一段文本丢给 DeepSeek 换回一串向量。这个缺口必须用本地 Embedding 模型补上。RAG 的检索链路本质上是“文本向量化 相似度计算”。知识库里的文档先被切块每一块转换成向量存进向量库用户提问时问题也被转成向量到向量库里找最相似的几个片段拿这些片段当上下文交给大模型。换句话说Embedding 模型决定了“什么内容算相似”大模型只负责“基于相似内容组织回答”。Embedding 这层选不好RAG 的瓶颈会直接卡在召回上上下文拼错后面大模型再强也白搭。选择 Embedding 模型时中文场景我第一推荐是 BAAI/bge-m3。它的优势有三个中文效果稳定、支持最长 8192 token 的输入、输出 1024 维向量在检索精度和资源消耗之间比较均衡。如果机器配置较差可以退一步用 bge-small-zh-v1.5速度快很多精度损失在可接受范围内。千万不要用 word2vec 或者 TF-IDF 来做 RAG 检索那两种方法处理不了语义匹配换个说法问同一个问题就查不到。3.2 用 bge-m3 提供本地向量能力加载与调用bge-m3 的加载方式有两种。走 Hugging Face 的缓存路径第一次需要下载模型文件走 ModelScope魔搭的路径则适合国内网络环境。我先给 Hugging Face 的标准做法后面避坑章节会讲国内下载的替换方案。from sentence_transformers import SentenceTransformer import torch model_name BAAI/bge-m3 device cuda if torch.cuda.is_available() else cpu model SentenceTransformer(model_name, devicedevice) texts [ 环境搭建需要哪些组件, RAG 的瓶颈通常在检索召回而不是生成, ] embeddings model.encode(texts, normalize_embeddingsTrue) print(embeddings.shape)这段代码里有几个参数是必须注意的。第一device 的判断要放在加载模型之前bge-m3 在 CPU 上也能跑但第一次加载和推理会明显偏慢有 NVIDIA 显卡就优先走 cuda。第二encode 时 normalize_embeddingsTrue 必须开这决定了向量是否归一化后面向量库做内积计算时归一化过的向量等价于余弦相似度否则检索结果会和预期差很远。第三bge-m3 默认输出 1024 维向量打印 shape 是 (2, 1024) 就说明模型加载成功。如果你走 ModelScope 下载代码要稍作调整先下载再指定本地路径加载from modelscope import snapshot_download model_dir snapshot_download(BAAI/bge-m3, local_dir./models/bge-m3) model SentenceTransformer(./models/bge-m3, devicedevice)ModelScope 在国内访问稳定第一次下载时优先考虑这条路。下载完成后以后启动项目直接指定本地路径不再触发网络请求离线也能跑。这个细节很重要因为 Embedding 模型加载失败是 RAG 环境搭建里最高频的卡点之一。3.3 向量库选型Chroma 起步FAISS 提速向量库是 RAG 环境的存储层负责存放文档向量并提供相似度检索。选型不需要纠结太久我直接给你一个判断框架个人项目和技术验证用 Chroma数据量大、要上生产再考虑 Qdrant 或 Milvus。向量库启动方式适合阶段持久化形式Chroma进程内运行零部署个人项目、原型验证本地目录文件FAISSPython 库直接调用单机百万级向量以内需自行管理索引文件QdrantDocker 容器生产级、需要 HTTP 服务磁盘映射MilvusDocker Compose 集群企业级、亿级向量分布式存储我个人的偏好是刚开始一律 Chroma没有之一。它不需要单独起服务代码里指定一个目录就能持久化重启不丢数据对“把 RAG 跑起来”这个目标来说性价比最高。FAISS 更适合批量检索、对速度和内存占用敏感的场景但它没有开箱即用的持久化方案索引文件要自己存自己读入门阶段容易搞乱。Chroma 的持久化初始化方式如下import chromadb client chromadb.PersistentClient(path./chroma_db) collection client.get_or_create_collection( nameknowledge_base, metadata{hnsw:space: ip}, ) print(collection.count())这里两个参数要解释。PersistentClient 的 path 是向量数据落盘目录我习惯放在项目根目录下的 chroma_db 文件夹里这样备份整个项目时向量库也跟着走。collection 的 metadata 里 hnsw:space 设成 ip内积空间配合前面 bge-m3 做过的向量归一化检索时算的就是余弦相似度这是官方推荐的中文检索组合。count() 返回 0 说明 collection 建好了可以开始灌数据。如果你的知识库文档量级在一万份以内Chromapath 完全够用等涨上去再迁 Qdrant迁移逻辑在后面章节的代码里本来就是抽象出来的改动成本不大。不要在环境搭建阶段就上一套分布式向量库那是把复杂度提前预支给自己。4. 跑通最小 RAG 闭环DeepSeek API 本地知识库的完整链路4.1 RAG 链路拆解与目录规划环境搭好、Embedding 就绪接下来把整个 RAG 链路串起来。完整的 RAG 系统包含五个环节加载文档、切分文本、向量化入库、检索命中、拼接上下文交给大模型问答。前三个属于离线流程后两个属于在线流程。环境搭建阶段要做的是让这五个环节能在一台机器上顺序跑通不需要一开始就追求工程化先把链路走通再谈优化.我习惯按下面的目录结构组织一个最小项目方便区分数据和代码rag-workspace/ ├── .env ├── docs/ # 原始知识库文档放 PDF / TXT / Word ├── data/chroma_db/ # Chroma 持久化目录 ├── models/bge-m3/ # 本地 Embedding 模型 └── src/ ├── ingest.py # 离线加载、拆分、入库 └── query.py # 在线检索 问答docs 目录放你的知识库原始文件建议先放一两份真实的业务文档测试别用网上的通用文本——检索效果好不好只有用自己领域的内容才能看出来。ingest.py 跑一次把文档内容变成向量存进 chroma_dbquery.py 负责接收问题、查库、调 DeepSeek。两个脚本一个数据目录这是最小但五脏俱全的形态。4.2 文档加载与拆分先解决文本入口文档加载是环境搭建里最容易被低估的一步。很多项目搭完环境、入库跑通一查检索结果全是乱序问题就出在拆分参数上。我先给加载和拆分的完整代码from pathlib import Path from langchain_community.document_loaders import PyPDFLoader, TextLoader from langchain_text_splitters import RecursiveCharacterTextSplitter docs_dir Path(docs) raw_docs [] for path in docs_dir.iterdir(): if path.suffix.lower() .txt: raw_docs.extend(TextLoader(str(path)).load()) elif path.suffix.lower() .pdf: raw_docs.extend(PyPDFLoader(str(path)).load()) print(f加载了 {len(raw_docs)} 份文档) splitter RecursiveCharacterTextSplitter( chunk_size800, chunk_overlap120, separators[\n\n, \n, 。, , , , ], keep_separatorTrue, ) chunks splitter.split_documents(raw_docs) print(f拆分出 {len(chunks)} 个片段)这段代码解决了“文本入口”问题我逐个参数说明。chunk_size800 表示每个片段最多 800 字符这是中文 RAG 的常用起步值既能保住语义完整又不会让上下文太长chunk_overlap120 让相邻片段有 120 字符重叠防止答案正好被切在片段边界上separators 里的顺序很关键从“段落换行”到“句号”再到“空格”递归拆分时会按这个优先级寻找断开点中文文本里把“。”放在“\n”之后能有效避免把一句话腰斩。keep_separatorTrue 保留分隔符在片段末尾对后续检索拼接有好处。顺带解决一个很多人纠结的问题知识库能不能存图片文本 RAG 链路接不了图片本身PDF 里的插图如果不做 OCR 转文本检索时只会跳过。环境搭建阶段先规划好图片类知识要么先转成文本描述要么等后续接入多模态管线这不是当前这套环境能覆盖的范围。4.3 检索与问答把命中片段交给 DeepSeek文档入库后查询脚本要做三件事给问题向量化、到向量库取 Top-K 片段、把片段拼进 Prompt 交给 DeepSeek。先说入库代码这是 ingest.py 的后半段from langchain_core.embeddings import Embeddings from langchain_community.vectorstores import Chroma class BgeM3Embeddings(Embeddings): def __init__(self, model): self._model model def embed_documents(self, texts): return self._model.encode(texts, normalize_embeddingsTrue).tolist() def embed_query(self, text): return self._model.encode([text], normalize_embeddingsTrue).tolist()[0] embedder BgeM3Embeddings(model) vectorstore Chroma.from_documents( documentschunks, embeddingembedder, persist_directory./data/chroma_db, )为什么需要这个 BgeM3Embeddings 包装类因为 LangChain 的 Chroma 接口要求 Embedding 对象实现 embed_documents 和 embed_query 两个方法而 sentence-transformers 的 SentenceTransformer 本身不兼容这个协议所以必须包一层。注意 embed_query 和 embed_documents 的处理路径略有不同查询向量只需要对单条文本编码返回一维向量文档向量则按批量编码。代码里 normalize_embeddingsTrue 保持和前面一致保证向量空间相同。查询端代码同样简洁import os from dotenv import load_dotenv from langchain_openai import ChatOpenAI from langchain_core.prompts import ChatPromptTemplate load_dotenv() retriever vectorstore.as_retriever(search_kwargs{k: 4}) llm ChatOpenAI( modelos.getenv(DEEPSEEK_MODEL, deepseek-chat), api_keyos.getenv(DEEPSEEK_API_KEY), base_urlos.getenv(DEEPSEEK_BASE_URL, https://api.deepseek.com), temperature0.3, ) prompt ChatPromptTemplate.from_template( 请根据以下知识库片段回答问题。如果片段中没有答案就明确说不知道。\n 片段如下\n{context}\n\n问题{question} ) def answer(question: str): hits retriever.invoke(question) context \n\n.join([hit.page_content for hit in hits]) chain prompt | llm return chain.invoke({context: context, question: question}).contentsearch_kwargs{k: 4} 表示每次检索取 4 个片段这是环境验证阶段的合理值。片段太少容易漏答案太多会把无关内容塞给模型还浪费 token。Prompt 里那句“没有答案就明确说不知道”必须加它能抑制大模型在信息不足时自由发挥这是 RAG 问答和普通聊天最重要的区别。跑一遍 answer(你的知识库里某份文档的核心结论是什么)如果能引用到对应片段并给出符合文档原意的回答说明环境搭建已经完整走通。5. 环境搭建避坑清单5 个高频翻车现场与排查方法5.1 langchain 版本冲突导致 import 崩溃现象是 pip install 一切顺利但一 import langchain 就抛 AttributeError 或者 pydantic 相关的报错错误栈往往指向 langchain_core 内部。原因是 LangChain 0.3 要求 pydantic 2.x而某些第三方包或者旧缓存会把 pydantic 锁在 1.x两个版本共存时直接炸掉。解决方法是先看错误栈最后一行确认是哪个包冲突然后执行 pip install -U pydantic2.0 langchain0.3,0.4 强制对齐版本。如果还不行直接把虚拟环境删了重建重新按第 2 章的依赖清单装别在原环境里硬修。这个坑几乎每个新项目都会遇到一次不值得花太多时间。5.2 没写 base_url请求默认打到了 OpenAI现象是调用 DeepSeek 时返回 401 unauthorized或者明确提示 model 不存在。原因是 ChatOpenAI 这个类默认 base_url 指向 OpenAI 的地址你如果忘了传 base_url 参数SDK 会把 deepseek-chat 当成 OpenAI 的模型名去请求自然被拒。解决方法是每次初始化时显式传 base_urlos.getenv(DEEPSEEK_BASE_URL)不要依赖默认值。我踩过一次之后养成了习惯所有兼容 OpenAI 协议的国产模型base_url 一律写进 .env代码里显式读取。这个坑不会报错提示你“地址错了”它只会给你一个让人摸不着头脑的 401。5.3 Embedding 模型下载卡死现象是第一次执行 SentenceTransformer(BAAI/bge-m3) 时进度条永远停在某个百分比或者直接报连接超时。原因是模型权重托管在 Hugging Face国内直连不稳定。解决方法是优先切换到 ModelScope 下载按第 3 章的 snapshot_download 方式拉取到本地目录然后直接用 SentenceTransformer(./models/bge-m3) 加载本地路径。如果必须走 Hugging Face可以设置环境变量 HF_ENDPOINT 指向国内可访问的镜像站但镜像的可用性会变化ModelScope 更稳定。记住一点模型下载成功后加载路径就固定用本地目录别再依赖在线缓存。5.4 入库“成功”但检索永远为空现象是 Chroma 的 add 没有报错count() 也显示有数据但检索时 hits 为空或者返回完全不相关的内容。原因有两类一类是 encode 时忘了 normalize_embeddingsTrue向量没有归一化而 collection 的 hnsw:space 设的是 ip相似度计算全乱另一类是混合用了两套 Embedding 模型入库时用一个模型检索时换了另一个两个模型的向量空间根本不兼容。解决方法是先确认代码里所有 encode 调用都带 normalize_embeddingsTrue再确认入库和查询用的是同一个模型实例或同一路径。排查时可以随手打印一条检索回来的片段原文比对是否和文档内容沾边别只看有没有返回结果。5.5 上下文超长DeepSeek 返回空响应现象是检索正常、拼接正常但调用 DeepSeek 时返回空字符串或者报输入超限。原因是 k 值太大、chunk_size 又设得高拼出来的 context 超出了模型上下文窗口或者正好顶到边界被截断。解决方法是先打印 len(context) 看实际 token 规模把 k 从 4 降到 3或者把 chunk_size 从 800 调到 500。我见过最极端的案例是 k10、chunk_size1200拼出来的上下文快两万字符模型直接罢工。RAG 不是给模型喂越多越好控制在刚好能覆盖答案的范围内才是正解。6. 验证 RAG 检索效果并把 DeepSeek 换成本地推理先做效果验证再做部署升级这个顺序别颠倒。最快的验证方法是拿知识库里一段原话去提问打印检索命中的片段原文看它是否真的包含答案内容hits retriever.invoke(你的测试问题) for idx, hit in enumerate(hits): print(idx, hit.page_content[:80])如果命中的片段和问题语义对得上再进行第二步同一个问题分别用不带上下文的直答和带上下文的 RAG 回答做对比。直答版本经常会给一个“看起来合理但细节错误”的答案RAG 版本必须能引用到知识库里的具体表述。第三步是调参验证chunk_size 分别试 400、800、1200k 试 3 和 5记录哪组参数下答案最稳定。调参时只动一个变量别同时改两处。验证通过后如果你有足够显存想换成完全本地部署两条路都行。Ollama 适合快速替换一条命令拉模型再起服务ollama pull deepseek-r1:7b ollama serve然后把 ChatOpenAI 的 base_url 换成 http://localhost:11434/v1model 换成 deepseek-r1:7b其余代码不用动。要更高吞吐就上 vLLMvllm serve deepseek-ai/DeepSeek-R1-Distill-Qwen-7B --host 0.0.0.0 --port 8000同样只改 base_url 和 modelRAG 链路完全复用。本地部署 7B 级模型建议至少 16GB 显存显卡不够就别硬上API 路线在验证阶段其实更划算。我现在的习惯是每次新项目都把 chunk_size800、k4、temperature0.3 当作基线跑一遍再按验证结果调整。这套环境搭建流程我重复过很多次最深的教训是一切异常先查 Embedding再查向量库最后才查大模型接口——绝大多数翻车都发生在检索侧。希望帮到你。本文还有配套的精品资源点击获取
返回列表