ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

DeepSeek+RAG本地知识库实战:从文档切分到检索生成全链路拆解

DeepSeek+RAG本地知识库实战:从文档切分到检索生成全链路拆解 简介这份PDF文档面向希望将大模型落地到垂直业务场景的开发者与技术支持人员系统讲解如何用DeepSeek大模型结合RAG技术搭建本地知识库。内容以CST/ABAQUS官方文档为语料构建“虚拟技术支持工程师”智能体验证AI模型与行业知识库在真实业务中的响应效果。资源包共1个PDF文件约2.9MB篇幅紧凑涵盖整体架构、RAG检索增强生成流程、Embedding向量化、RAGFlow智能检索以及Ollama容器化本地部署等关键环节并给出在线与本地部署方式对比。读者可从中获得从知识库解析、向量存储到问答生成的完整方法论理解RAG如何降低微调成本、缓解模型幻觉并掌握敏感数据全程驻留内网的离线部署思路。目前已有897人学习适合关注DeepSeek、RAG与本地知识库实践的技术人员参考。1. 从一份 PDF 说起DeepSeek 加 RAG 到底能解决什么你可能也遇到过这种场景公司内部攒了一堆产品手册、运维文档、合同模板想用大模型直接问答结果模型要么一本正经地胡说要么根本不知道你私有的那点业务细节。把文档全塞进上下文token 烧得心疼长文档还会被截断。这份《DeepSeek模型RAG技术构建本地知识库.pdf》讲的正是这条落地路径——用 DeepSeek 作为生成侧的大模型用 RAG检索增强生成把私有文档变成可检索的知识源最终搭出一个能本地跑、数据不出内网的知识库问答系统。它适合三类人一是手里有大量非结构化文档、想快速做内部问答的工程师二是想搞明白 RAG 检索链路每一环怎么调、为什么召回不准的开发者三是已经在用 DeepSeek API 或本地部署想把它接进自己知识库的从业者。这份资料的价值不在于教你调 API而在于把「文档切分、向量化、检索、重排、拼 prompt」这条链路拆开讲清楚让你知道每一环的参数怎么设、坑在哪。下面我按自己复现的顺序把这份资料里的关键点重新捋一遍。2. RAG 链路拆解从文档切分到向量入库的每一步RAG 听起来玄学拆开就是一条流水线文档进来先切块切完转成向量存进向量库用户提问时把问题也转成向量去库里捞相似的块捞出来的块拼进 prompt 交给 DeepSeek 生成答案。这条链路里任何一环出问题最终答案都会翻车。这一章先把「入库」这半段讲透下一章讲「检索和生成」那半段。2.1 文档加载与切分chunk_size 和 overlap 怎么定切分是 RAG 里最容易被低估的一步。切太大检索出来的块里一半是无关内容噪声干扰生成切太小一个完整语义被切断模型拿到半句话也答不对。常见做法是按字符数切配合重叠窗口保证跨块语义连续。from langchain.text_splitter import RecursiveCharacterTextSplitter # 中文文档按字符切分隔符优先级从段落降到句子 splitter RecursiveCharacterTextSplitter( chunk_size500, # 每块目标字符数 chunk_overlap80, # 相邻块重叠字符数防止语义断裂 separators[\n\n, \n, 。, , , , , , ], length_functionlen, ) with open(handbook.md, encodingutf-8) as f: raw f.read() chunks splitter.split_text(raw) print(f切出 {len(chunks)} 块首块长度 {len(chunks[0])})这段代码的关键在separators的顺序。RecursiveCharacterTextSplitter会优先用靠前的分隔符切切完还超长就降级到下一个分隔符。中文文档一定要把中文标点加进去否则默认按空格切一整段中文会被当成一个词切出来全是超长块。chunk_size我一般从 500 起步技术文档可以到 800FAQ 类可以降到 300。chunk_overlap取 chunk_size 的 10% 到 20%太小起不到衔接作用太大又会让检索结果重复。提示切分前先做一次清洗把页眉页脚、连续空行、PDF 转换残留的乱码去掉。这些噪声进了向量库检索时会被当成有效内容召回。2.2 向量化与入库embedding 模型和向量库选型切完的块要转成向量。DeepSeek 本身不提供 embedding 接口所以向量化这一步得另找模型。常见做法是用开源的 BGE 系列或者 m3e本地跑不花钱中文效果也够用。向量库选 Chroma 最省事单机持久化几行代码就能建库。from langchain_community.embeddings import HuggingFaceEmbeddings from langchain_community.vectorstores import Chroma # 本地 embedding 模型首次运行会自动下载权重 embedding HuggingFaceEmbeddings( model_nameBAAI/bge-base-zh-v1.5, model_kwargs{device: cpu}, # 有 GPU 改成 cuda encode_kwargs{normalize_embeddings: True}, # 归一化后余弦相似度更稳 ) vectordb Chroma.from_texts( textschunks, embeddingembedding, persist_directory./chroma_db, # 持久化目录重启不丢 collection_namehandbook, ) vectordb.persist() print(入库完成当前集合向量数, vectordb._collection.count())normalize_embeddingsTrue这行别省。BGE 模型归一化之后内积等价于余弦相似度检索打分更稳定。persist_directory指定了持久化路径Chroma 会把向量和原文一起落盘下次直接Chroma(persist_directory..., embedding_function...)加载不用重新算。如果你文档量大比如上万块CPU 算 embedding 会很慢这时候要么上 GPU要么换成批量接口。入库完成后一定打印一下集合里的向量数确认和切块数对得上我见过因为编码问题静默丢块的。2.3 元数据设计让检索结果能溯源光存文本不够每块还得带上来源信息否则检索出来你都不知道这段话出自哪份文档哪一页。Chroma 支持给每个块挂 metadata检索时一起返回。metadatas [ {source: handbook.md, chunk_id: i, section: 运维规范} for i in range(len(chunks)) ] vectordb Chroma.from_texts( textschunks, embeddingembedding, metadatasmetadatas, persist_directory./chroma_db, collection_namehandbook, )source和chunk_id是最基本的两个字段前者用于展示引用来源后者用于定位原文。如果文档有章节结构把章节名也塞进 metadata检索时可以按章节过滤比如只在「故障处理」章节里搜。元数据设计得好后面做引用展示和结果过滤会省很多事。这一步在资料里往往一笔带过但实际项目里它是能不能做「可溯源问答」的分水岭。3. 检索与生成把召回结果喂给 DeepSeek 的正确姿势入库只是上半场真正决定答案质量的是检索和生成这半段。检索召回不准DeepSeek 再强也只能拿着错误材料编prompt 拼得不好模型会把检索内容当耳旁风。这一章把检索策略、重排、prompt 模板和 DeepSeek 调用串起来讲。3.1 相似度检索与 top_k 的取舍最基础的检索就是拿问题向量去库里找最相似的 k 个块。k 太小可能漏掉关键信息k 太大无关内容稀释了有效信息还推高 token 成本。query 服务器磁盘满了怎么处理 # 相似度检索返回 top 4 docs vectordb.similarity_search_with_score(query, k4) for doc, score in docs: print(fscore{score:.4f} source{doc.metadata[source]}) print(doc.page_content[:80]) print(- * 40)similarity_search_with_score会返回距离分数Chroma 默认用 L2 距离分数越小越相似。这里有个容易踩的坑不同向量库、不同距离度量分数的含义和范围都不一样别拿一个库的阈值去套另一个库。我一般先跑一批典型问题看召回块的分数分布再决定要不要设阈值过滤。k的取值从 3 到 6 起步配合后面的重排再精筛。3.2 重排用 rerank 把真正相关的块顶上来向量检索是「粗筛」它看的是语义相似但相似不等于相关。比如你问「磁盘满了怎么办」它可能召回一段讲「磁盘类型」的内容语义很近但答非所问。重排模型rerank专门解决这个问题它把问题和每个候选块放一起做精细打分把真正相关的顶到前面。from FlagEmbedding import FlagReranker reranker FlagReranker(BAAI/bge-reranker-base, use_fp16True) # 先粗筛 10 个再重排取前 3 candidates vectordb.similarity_search(query, k10) pairs [[query, doc.page_content] for doc in candidates] scores reranker.compute_score(pairs) ranked sorted(zip(candidates, scores), keylambda x: x[1], reverseTrue) top_docs [doc for doc, _ in ranked[:3]]重排的代价是慢每个候选块都要过一次模型所以流程是「粗筛多召回、重排精筛」。k10粗筛再取前 3是我在文档量几千块时比较稳的配置。如果文档量特别大粗筛阶段可以先用向量库的近似检索压到几十个再重排。重排模型和 embedding 模型最好选同一系列的比如都用 BGE打分尺度更一致。3.3 拼 prompt 调 DeepSeek上下文模板与参数设置检索出来的块要拼成 prompt 交给 DeepSeek。模板设计的原则是明确告诉模型「只根据下面材料回答材料里没有就说不知道」并且把材料编号方便模型引用。import os from openai import OpenAI client OpenAI( api_keyos.getenv(DEEPSEEK_API_KEY), base_urlhttps://api.deepseek.com, # DeepSeek 兼容 OpenAI 接口 ) context \n\n.join( f[{i1}] {doc.page_content} for i, doc in enumerate(top_docs) ) prompt f你是一个严谨的技术助手。请只根据下面提供的资料回答问题。 如果资料中没有相关信息直接回答「资料中未提及」不要编造。 资料 {context} 问题{query} resp client.chat.completions.create( modeldeepseek-chat, messages[{role: user, content: prompt}], temperature0.2, # 知识库问答要稳温度调低 max_tokens800, ) print(resp.choices[0].message.content)base_url指向 DeepSeek 的兼容接口SDK 用法和 OpenAI 一致。temperature设 0.2 左右知识库问答不需要发挥越低越稳。max_tokens按答案长度预期设别设太大浪费。模板里那句「资料中未提及」很关键它是防幻觉的第一道闸。如果你用的是本地部署的 DeepSeek把base_url换成你本地服务的地址即可其余不变。注意拼 prompt 时给每块材料加编号生成答案后可以让模型标注引用了哪几号材料前端就能做溯源展示。这个编号习惯我从第一次做知识库就强制保留后面加引用功能几乎零改动。4. 避坑与排查RAG 效果差的五个真实原因RAG 搭起来容易调好难。下面这五条是我复现过程中真实踩过的每条按「现象 → 原因 → 解决」写你对着排查能省不少时间。4.1 召回内容对但答案还是错现象检索出来的块明明包含正确答案DeepSeek 却答偏了或者答成别的。原因通常是 prompt 里材料太多太杂模型注意力被无关块分散或者材料顺序把关键块埋在了中间。解决先上重排把 top_k 压到 3 以内再把最相关的块放在 prompt 最前面。模型对开头和结尾的内容注意力更高中间容易被忽略这是血泪经验。4.2 中文文档切出来全是超长块现象切分后每块长度远超 chunk_size检索时一块里混了好几个主题。原因分隔符列表里没加中文标点切分器找不到切点只能整段保留。解决把。加进 separators并且把\n\n放在最前面优先按段落切。切完打印长度分布超过 chunk_size 两倍的块要单独看。4.3 相似度分数没法设阈值现象想用分数过滤低质量召回结果发现分数范围飘忽设了阈值要么全滤掉要么全留下。原因不同向量库距离度量不同Chroma 默认 L2 距离分数是距离不是相似度且和向量归一化与否强相关。解决统一开启normalize_embeddingsTrue改用余弦距离然后跑一批标注问题看分数分布取一个能分开正负样本的值。别照搬网上的阈值。4.4 更新文档后检索还是旧内容现象文档改了重新入库检索出来的还是老版本。原因Chroma 的 collection 是追加写入同名的块不会自动覆盖旧向量还在库里。解决更新时先按 metadata 里的 source 删除旧块再插入新块或者干脆换一个 collection 名重建。我一般给 collection 名带上日期或版本号重建最干净。4.5 本地部署 DeepSeek 显存不够现象本地跑 DeepSeek 时显存爆了或者响应慢到没法用。原因模型量化等级、上下文长度、并发数都会吃显存长 prompt 尤其明显。解决优先用量化版本把检索回来的上下文块数压下来max_tokens调小。如果还是不够生成侧走 API、检索侧本地跑是性价比最高的折中。这块资料里没细讲但实际部署时绕不开。5. 进阶技巧把命中率和可维护性再抬一档基础链路跑通之后真正拉开差距的是检索命中率和长期可维护性。这一章讲三个我常用的进阶手段都是在这份资料基础上往外延的实操。5.1 混合检索向量加关键词双路召回纯向量检索对专有名词、型号、错误码这类精确匹配不敏感。比如你问「ERR-5021 怎么解」向量检索可能召回一堆讲「错误处理」的泛泛内容就是命不中那个具体错误码。混合检索的做法是同时跑一路关键词检索BM25把两路结果合并去重再重排。from rank_bm25 import BM25Okapi import jieba # 对切好的块建 BM25 索引中文先分词 tokenized [list(jieba.cut(c)) for c in chunks] bm25 BM25Okapi(tokenized) query_tokens list(jieba.cut(query)) bm25_scores bm25.get_scores(query_tokens) bm25_top [chunks[i] for i in bm25_scores.argsort()[-5:][::-1]] # 向量路召回 vec_top [doc.page_content for doc in vectordb.similarity_search(query, k5)] # 两路合并去重再交给 rerank 精排 merged list(dict.fromkeys(bm25_top vec_top))BM25 那一路负责精确命中向量那一路负责语义泛化合并去重后候选集更全。jieba分词对中文 BM25 是必须的不分词直接按字算效果会差一截。合并时用dict.fromkeys去重同时保序把两路的高分项都留住。这套组合在专有名词多的技术文档上命中率提升很明显。5.2 查询改写让用户的口语问题对上文档的书面表达用户提问往往很口语文档却是书面语两者向量距离可能很远。查询改写就是在检索前先把问题改写成更接近文档表达的多个版本分别检索再合并。rewrite_prompt f把下面的问题改写成 3 个适合检索技术文档的查询语句 每行一个不要编号不要解释 {query} resp client.chat.completions.create( modeldeepseek-chat, messages[{role: user, content: rewrite_prompt}], temperature0.3, ) rewritten resp.choices[0].message.content.strip().split(\n) # 每个改写版本各召回一批合并去重 all_docs [] for q in [query] rewritten: all_docs.extend(vectordb.similarity_search(q, k3))用 DeepSeek 自己做查询改写成本很低但效果立竿见影。改写出来的多个查询覆盖了不同表达角度召回面更广。注意改写结果要去空行、去编号否则会污染检索。这一步会增加一次模型调用延迟上去了适合对准确率要求高于响应速度的场景。5.3 效果验证用固定问题集量化命中率调参不能靠感觉得有一组固定问题来量化。我一般从真实文档里挑 20 到 30 个有明确答案的问题人工标注每个问题的正确块然后跑检索看正确块有没有进 top_k。指标含义目标Recall3正确块进前三的比例技术文档 0.85 以上MRR正确块排名的倒数均值越接近 1 越好答案准确率人工判断答案是否正确抽样 30 题评估def recall_at_k(questions, vectordb, k3): hit 0 for q, gold_chunk_id in questions: docs vectordb.similarity_search(q, kk) ids [d.metadata[chunk_id] for d in docs] if gold_chunk_id in ids: hit 1 return hit / len(questions) print(Recall3 , recall_at_k(eval_set, vectordb, k3))有了这个数字你调 chunk_size、换 embedding 模型、加重排都能看到明确涨跌而不是凭感觉。我每次改检索链路都强制先跑一遍这个评估集涨了才留下跌了就回滚。从那以后我每次动 RAG 参数都强制走一遍评估再也没出现过「感觉变好了其实变差了」的情况。希望帮到你。本文还有配套的精品资源点击获取
返回列表