资讯中心

LangChain+ChatGLM-6B实现本地知识库自动问答:RAG全流程实战

📅 2026/9/28 13:07:44
LangChain+ChatGLM-6B实现本地知识库自动问答:RAG全流程实战
简介面向计算机、通信、人工智能、自动化等相关专业的学生、老师或从业者这是一份基于LangChain与ChatGLM-6B等系列LLM构建针对本地知识库自动问答系统的毕业设计项目适合作为期末课程设计、课程大作业或毕业设计参考。压缩包共76个文件除12个Python源码、39个pickle数据缓存外还包含文本切分、向量化嵌入、模型调用等关键模块以及说明文档、手册、Docker配置等资料整体约17.96MB目录结构清晰便于按模块阅读和二次改造。项目已完成调试测试答辩评审分达到98分从文本加载、切分、向量化到模型接入与问答链路均有完整实现既适合小白系统学习也为基础较好的读者预留了扩展空间。目前已有251人学习下载可作为理解本地知识库自动问答系统落地方式的实用样例。1. 给本地知识库做自动问答LangChain和ChatGLM-6B要解决的三个问题做企业内部知识库问答最常见的误区是第一时间想到调用在线大模型API。但试过的人很快会发现三个现实问题数据不能出内网、单次问答成本随调用量线性上涨、回答内容不受你控制。这个标题给的路线是把LangChain和ChatGLM-6B这类LLM直接跑在本地再把文档切块、向量化、检索、生成串成一条自动问答流水线让本地知识库拥有一个能说自然语言的接口。它适合手里有几十到几万份文档、想让同事用中文提问就能拿到带依据回答的团队也适合个人开发者想在消费级显卡上复现一个完整RAG项目。先说明白这是一条工程链路不是装个依赖就能跑的玩具下面的每个环节都会决定回答质量。2. 先定技术路线RAG链路、ChatGLM-6B的选型理由与LangChain/LangGraph关系2.1 把“本地知识库自动问答”拆成五段RAG流水线自动问答系统的核心不是让模型“记住”知识库内容而是把知识库变成模型在回答时可以查阅的资料。这套方案的通用名字是RAG检索增强生成。它的流程固定为五段文档加载、文本切分、向量化入库、相似度检索、LLM生成回答。第一段处理的是原始文件txt、md、PDF、Word都可能出现加载后要清理掉页眉页脚和多余换行。第二段把长文档切成固定大小的文本块这是后面检索的基本单位。第三段用embedding模型把每个文本块转成向量写入向量数据库。第四段在用户提问时把问题向量化在库里找相似度最高的几个文本块。第五段把“问题检索到的文本块”拼成提示词交给ChatGLM-6B生成最终回答。为什么要绕这么一圈而不是直接把整个知识库塞给模型因为ChatGLM-6B这类LLM的上下文窗口有限真正能影响回答质量的往往只是与问题相关的几段内容。RAG把“大海捞针”变成“先捞针再让模型读”这也是当前本地知识库项目的主流做法。理解这一点后再去选具体的组件就不会被框架牵着走。2.2 为什么第一版LLM选ChatGLM-6B而不是更大参数的模型模型选型是这个项目里最影响交付周期的一步。ChatGLM-6B是清华大学开源的中英双语对话模型参数规模约60亿核心优势在于中文理解和生成能力在同等量级的开源模型里表现稳定而且官方提供了int4量化版本让8GB到12GB显存的消费级显卡也能跑。对比一下常见选型会更直观模型参数量FP16推理显存下限int4量化显存下限中文能力ChatGLM-6B约6B约13GB约6GB强Qwen-7B约7B约15GB约7GB强Llama-2-7B约7B约14GB约6GB中需中文微调ChatGLM3-6B约6B约14GB约6GB更强但依赖更新如果你的显卡是RTX 3060 12GB或RTX 4060 8GBChatGLM-6B的int4版本就是那个“刚好能跑”的选项。这里的边界要算清楚FP16版本加载后权重就要13GB左右加上中间激活值和向量检索的占用12GB显卡基本会直接OOM。int4量化之后权重降到6GB左右才给生成阶段留出余量。还有一个常被忽略的选型因素生态成熟度。ChatGLM-6B发布早网上能搜到大量踩坑记录和示例代码遇到问题容易找到对照。换一个更新但小众的模型你可能要自己面对tokenizer兼容性和量化脚本报错。第一版项目的目标是跑通链路不是追最新权重稳定性优先。2.3 LangChain足够LangGraph在什么时候才值得引入LangChain在本地知识库问答里的角色是编排框架它把文档加载器、文本分割器、embedding模型、向量库、LLM这些组件用统一接口串起来。早期版本里所有东西都在langchain一个包里但从0.1.x开始官方做了拆分langchain-core放核心抽象langchain-community放社区维护的集成向量库和文本分割器也独立成包。这个拆分是好事安装时知道自己在装什么报错时就知道去哪查。很多人纠结要不要直接上LangGraph。LangGraph把流程建模成状态机节点做事情边做状态转移适合需要多步推理、动态决定调用哪个工具的场景比如“先查知识库不够再查数据库最后生成报表”的Agent应用。但本标题这个项目的RAG链路是固定五段每一步都是确定的LangChain的RetrievalQA链加上自定义prompt已经能覆盖全部需求。用了LangGraph反而多个状态定义和流转调试成本。我给的建议是第一版用LangChain把RAG链路跑通当你的需求出现分叉时再迁移到LangGraph。LangGraph不排斥LangChain组件retriever、embedding、vectorstore都可以直接复用迁移成本不是白扔。比如以后要做多个知识库的自动路由或者让模型自己决定是检索还是直接回答那时候LangGraph的价值才体现出来。3. 环境准备与模型部署在消费级显卡上把ChatGLM-6B跑起来3.1 依赖安装与conda选择先把LangChain相关库装齐环境搭建阶段最大的坑是依赖版本互相打架。我一般用conda隔离一个干净环境Python选3.10这个版本对当前大多数LLM相关库的兼容性最好。先装基础依赖再装模型库顺序不能反因为transformers和torch的版本组合会在模型加载时才暴露问题。conda create -n langchain-qa python3.10 -y conda activate langchain-qa pip install langchain langchain-community langchain-text-splitters langchain-chroma chromadb pip install torch2.1.2 --index-url https://download.pytorch.org/whl/cu121 pip install transformers accelerate sentence-transformers这段命令里最容易翻车的是torch的安装。如果直接pip install torch在很多机器上装到的是CPU版本模型能加载但推理慢到无法接受而且不会报错属于典型“黑匣子”故障。用--index-url指定PyTorch官方CUDA版本能确保拿到带CUDA支持的wheel。注意CUDA版本要和显卡驱动兼容RTX 30系和40系显卡用cu121这个tag基本没问题。LangChain相关包的拆分在这里体现得很直接langchain-community提供DirectoryLoader和HuggingFaceEmbeddings等实现langchain-text-splitters提供RecursiveCharacterTextSplitterlangchain-chroma是对Chroma向量库的LangChain适配层。少任何一个都会在import阶段报No module named。sentence-transformers则是加载bge系列embedding模型的底层依赖。3.2 模型加载的两条路线FP16与int4量化显卡显存超过14GB可以跑FP16版本12GB及以下老老实实用int4量化版。两条路线的加载代码只有模型名不同但要注意量化版不能再用.half().cuda()否则会报类型不匹配。下面是封装成一个LangChain可调用LLM的最小实现from langchain_core.language_models.llms import LLM from transformers import AutoModel, AutoTokenizer class ChatGLM6B(LLM): model: AutoModel tokenizer: AutoTokenizer property def _llm_type(self) - str: return chatglm-6b def _call(self, prompt: str, stopNone, run_managerNone, **kwargs) - str: resp, _ self.model.chat(self.tokenizer, prompt, history[]) return resp property def _identifying_params(self) - dict: return {model: chatglm-6b} def load_chatglm(model_path: str THUDM/chatglm-6b): tokenizer AutoTokenizer.from_pretrained(model_path, trust_remote_codeTrue) model AutoModel.from_pretrained(model_path, trust_remote_codeTrue).half().cuda() model.eval() return ChatGLM6B(modelmodel, tokenizertokenizer)trust_remote_codeTrue这个参数必须带上ChatGLM-6B的模型定义依赖仓库内的自定义Python代码不信任它就无法加载。建议首次加载前自己翻一遍这个模型的代码文件确认没有可疑逻辑再执行。显存紧张时把model_path换成THUDM/chatglm-6b-int4同时删掉.half()直接.cuda()即可。int4版本加载后占用约6GB显存但生成速度会比FP16慢一些这是量化必须接受的代价。如果你的显卡只有6GB显存这个方案也紧张建议把检索用的embedding模型放到CPU上跑把GPU显存全部留给ChatGLM-6B。3.3 向量库与embedding模型选型Chroma、FAISS和bge向量库承担“从几万条文本块里快速找到最相关的几条”这个任务。三个常见选项各有适用边界向量库部署方式适合场景优缺点Chroma嵌入式单机即可原型验证、中小规模知识库配置简单支持持久化到本地目录FAISS嵌入式/内存索引百万级向量检索检索性能高但没有内置持久化管理Milvus独立服务团队协作、海量知识库功能完整资源占用高运维成本在前期偏高第一次做这个项目选Chroma最省心。chromadb直接pip安装数据持久化在一句persist_directory参数里完成不需要额外启动服务。等到文档量超过几十万条或者需要多人并发检索时再迁移Milvus不迟。embedding模型的选择直接影响检索质量。中文场景推荐BAAI/bge-small-zh-v1.5它是一个512维的中文嵌入模型模型体积小加载快检索效果在中文文档上比通用多语言模型稳定得多。加载方式用HuggingFaceBgeEmbeddings封装from langchain_community.embeddings import HuggingFaceBgeEmbeddings embedding HuggingFaceBgeEmbeddings( model_nameBAAI/bge-small-zh-v1.5, model_kwargs{device: cpu}, encode_kwargs{normalize_embeddings: True}, )normalize_embeddings设为True做相似度计算时用余弦距离会更稳定这个参数容易被漏掉但漏掉后检索排序会明显变差。embedding模型放CPU跑开销不大把显卡让给生成模型是更合理的资源分配。4. 自动问答链路的落地代码加载、切分、向量化、检索生成4.1 文档加载txt与PDF统一入库的事前清洗加载不是简单的读文件清洗环节决定了后面检索的干净程度。直接读PDF经常遇到换行符把一句话切成两半、页眉页码混进正文的情况这些噪音会在向量化时拉低检索精度。先看代码from langchain_community.document_loaders import DirectoryLoader, TextLoader, PyPDFLoader txt_loader DirectoryLoader( ./knowledge_base/, glob**/*.txt, loader_clsTextLoader, loader_kwargs{encoding: utf-8}, ) txt_docs txt_loader.load() pdf_loader PyPDFLoader(./knowledge_base/server_guide.pdf) pdf_docs pdf_loader.load()DirectoryLoader支持用glob模式批量加载指定扩展名的文件loader_cls指定每种文件的解析器loader_kwargs里可以传入编码参数。这里把txt和PDF分开处理因为PDF的解析逻辑差异大混在一个目录加载器里容易出现报错。PyPDFLoader直接实例化传入单个文件路径即可。清洗这一步我一般会写一个简单函数把文本里的空行压缩、去掉首尾空白、过滤掉明显的页眉页脚。要注意不要过度清洗把正文里的换行符全部删掉反而会让句子粘连。经验是只处理“连续三个以上换行符”和“纯数字页码”这两类典型噪音。4.2 文本切分chunk_size和chunk_overlap怎么调切分是整个RAG链路里最玄学也最影响效果的环节。切大了一个chunk里混入多个主题检索召回的内容不精准切小了语义被切断模型得不到完整上下文。LangChain提供的RecursiveCharacterTextSplitter是默认选择from langchain_text_splitters import RecursiveCharacterTextSplitter splitter RecursiveCharacterTextSplitter( chunk_size400, chunk_overlap80, separators[\n\n, \n, 。, , , , ], ) chunks splitter.split_documents(txt_docs pdf_docs)chunk_size400表示每个文本块最多约400个字符这个值适合大多数中文技术文档和FAQ。chunk_overlap80表示相邻文本块重叠80个字符目的是避免一个完整句子刚好被切分线截断保证关键信息不出现在边界盲区。separators指定切分优先级先从段落分隔符切再到换行、句号、逗号最后才按空格和字符硬切。这个顺序保证尽量在语义自然边界处切割。经验参考代码类文档chunk_size可以降到200因为代码行与行之间本身有结构长篇幅制度文档可以提到500。调参时记住一个原则——先保证每个chunk讲清楚一件事再考虑上下文长度。4.3 构建向量库与检索器embedding入库和相似度检索细节切好的文本块要转成向量写入Chroma。这里复用前面定义的embedding模型整个入库过程如下from langchain_chroma import Chroma vectorstore Chroma.from_documents( documentschunks, embeddingembedding, persist_directory./kb_store, ) retriever vectorstore.as_retriever( search_typesimilarity, search_kwargs{k: 4}, )Chroma.from_documents会自动调用embedding模型把每个chunk转成向量并写入持久化目录./kb_store。第二次运行时不用重新embedding所有文档直接Chroma(persist_directory./kb_store, embedding_functionembedding)就能恢复已有向量库。检索器的search_typesimilarity表示用向量的余弦相似度做召回k4表示每次取最相似的4个文本块。这个数字的调节空间很大k太小可能漏掉关键信息k太大则会把不相关的内容塞进上下文干扰模型判断。我第一次做项目时设k4后来在几百条测试query上的经验是4到6是平衡点知识库噪音大就调小文档分段碎就调大。4.4 组装RetrievalQA链提示词控制“不编造”边界检索器就绪后最后一步是把检索结果和用户问题一起交给ChatGLM-6B。这里要用提示词明确约束模型的行为边界否则它会在知识库内容不足时自行脑补。代码实现from langchain_core.prompts import PromptTemplate from langchain.chains import RetrievalQA prompt PromptTemplate.from_template( 你是一个企业内部知识库问答助手。请只依据以下资料片段回答用户问题不要编造。 如果资料中没有答案请明确回答“资料中未找到相关信息”。 资料片段 {context} 用户问题 {question} ) qa_chain RetrievalQA.from_chain_type( llmllm, retrieverretriever, chain_typestuff, chain_type_kwargs{prompt: prompt}, ) answer qa_chain.invoke({query: 服务台系统的默认超时时间是多少}) print(answer[result])chain_typestuff表示把所有检索到的文本块一次性塞给LLM适合chunk数量少的场景。当知识库规模很大、单次需要检索超过8个文本块时要考虑map_rerank或refine方式但那会明显增加生成耗时本地模型上不推荐。RetrievalQA内部会自动把检索到的文本块拼接成{context}把用户输入填充到{query}。这里有个容易混淆的点invoke传入的key是query而不是question写错会直接报找不到模板变量。打印answer[result]拿到的是最终生成文本要调试检索中间结果时单独调用retriever.get_relevant_documents(question)。5. 常见问题排查本地知识库问答翻车的几个典型现场5.1 回答和知识库无关先查检索召回而不是生成现象用户问“报销流程”模型回答却像在介绍考勤制度内容通顺但完全对不上。原因这个问题90%出在检索阶段不是模型不会答。要么embedding模型不擅长中文语义匹配要么切分后的chunk本身就不包含清晰答案要么k值太小导致正确内容没进候选。解决先在链路中间加一个调试输出打印每次检索命中的chunk内容docs retriever.get_relevant_documents(报销流程是什么) for d in docs: print(d.page_content[:200])如果打印出来的内容确实与问题无关按顺序检查三件事embedding模型换成bge中文版、chunk_size调小到300到400、k值从3递增到6。如果打印内容相关但最终回答跑偏再检查提示词里是否明确写了“只依据资料片段回答”。5.2 一生成就OOM显存边界被同一份模型加载两次撞穿现象程序启动时正常但第一次调用llm.chat后就报CUDA out of memory进程直接退出。原因最常见的是复用代码时不小心加载了两份模型。比如先加载模型做一次测试输出又新建了一个对象走RetrievalQA链路两份ChatGLM-6B同时占显存12GB显卡立刻爆掉。另外max_new_tokens设得过大也会在长文本生成时撑爆显存。解决把模型加载封装成单例整个进程只保留一个llm对象。同时把max_new_tokens压到512以内生成过程中的KV Cache和激活值会随生成长度线性增长。用torch.cuda.memory_summary()观察显存占用确认模型加载后剩余显存至少2GB再继续。5.3 LangChain导入报错No module named版本拆分导致的老代码翻车现象照着旧教程写from langchain.document_loaders import DirectoryLoader报ModuleNotFoundError。原因LangChain在0.1.x版本把非核心集成拆到了langchain-community包0.2.x后更进一步拆分出langchain-chroma、langchain-text-splitters。旧教程里的导入路径全部失效。解决按新路径导入。文档加载器在langchain_community.document_loaders文本分割器在langchain_text_splittersChroma在langchain_chroma。如果你的项目里还有大量老代码可以先在代码开头统一做一层兼容别名但长期维护还是建议逐步迁到新路径。5.4 输出截断在句子中间答到一半就停车现象模型回答“报销流程是员工填写申请表然后提交到部门经”最后一个“经”字后就停了。原因max_new_tokens设得太小生成到上限被强制截断。ChatGLM-6B虽然能识别中文字符但默认生成参数在长回答场景下需要显式放宽长度限制。解决在transformers加载时设置max_new_tokens512同时把do_sampleFalse关闭随机采样避免长文本生成时概率漂移导致重复。如果关闭采样后质量下降再改为temperature0.7配合repetition_penalty1.1。注意这两组参数在model.chat内部传递时通过generation_config传入不要只写在外层pipeline配置里。5.5 明明知识库有答案模型却回答“资料中未找到相关信息”现象人工搜索能定位到答案所在的文档段落但问答系统直接给出否定回答。原因检索结果虽然召回了正确chunk但该chunk恰好只包含了表格内容的前半部分答案关键字在chunk之外。或者chunk_size设得很大答案被埋在一大段无关文字中间模型的注意力被噪声干扰。解决先打印检索到的chunk内容确认正确答案确实在context里。如果在就把chunk_size从400降到200到250同时增大chunk_overlap到120让答案即使在切分边界也能被完整保留。还有一种情况是chunk内容在“资料片段”占位符里的格式与提示词模板不匹配需要检查拼接后的完整prompt确认{context}确实被填充了内容。6. 收尾验证与调优从“能答”到“能交付”的检查方法链路跑通只是开始真正能不能交付要看验证和调优。我的做法是准备一份带标准答案的验收集大概20到30条真实业务问题每条都用手工能从知识库中找到的答案做标定。然后逐个跑一遍QA链路记录回答是否正确、是否引用了知识库内容、是否出现幻觉。判断标准就一条回答里的每个关键事实都能在检索到的chunk里找到出处。调优顺序固定为先调检索再调生成。检索阶段看两个指标一个是“命中率”标准答案所在chunk有没有被召回另一个是“前k命中率”标准答案有没有排在前4位。命中率低就改chunk_size和embedding模型前k命中率低就增大k。检索稳定后再调生成阶段这时候才动temperature和repetition_penalty。参考参数经验temperature0.2保证稳定输出max_new_tokens512覆盖绝大多数业务答案。最后列一个我的习惯做法每次调参后把chunk_size、chunk_overlap、k、模型名四个参数记在配置里验证集跑一遍后记录正确率。现在做新知识库项目第一天就先搭这个验证集不花两个小时把验证集建好后面调参全靠感觉就是血泪教训。这个方向值得投入但投入的第一件事不是优化效果而是建好衡量效果的尺子。希望帮到你。本文还有配套的精品资源点击获取

看完文章,想为自己的企业也做一次专业网站诊断?

尧图顾问免费为您评估现有网站,并给出建站/改版建议与报价方案。

免费获取方案