文章目录
- 前言
- 环境准备
- 分步操作
- 第一步:文档加载与处理
- 第二步:构建向量数据库
- 第三步:加载本地大语言模型
- 第四步:组装RAG链并提问
- 完整代码与运行
- 踩坑提示
- 总结
前言
在AI项目落地的过程中,我踩过最大的一个坑就是:模型“一本正经地胡说八道”。你问它公司最新的产品政策,它能给你编出一套逻辑自洽但完全错误的答案。这背后的核心问题是,大语言模型(LLM)的知识存在“截止日期”,且无法记住你私有的、非公开的数据。为了解决这个问题,让AI真正成为你业务上的得力助手,检索增强生成(RAG)技术应运而生。今天,我就带大家从零开始,实战搭建一个基于私有数据的个人AI知识库与智能助理。这个系统能让你用自然语言查询自己的文档、笔记、聊天记录,并得到精准、可靠的回答。
环境准备
我们选择轻量、高效且生态成熟的工具链,确保大家能快速跑通并后续扩展。
- 编程语言:Python 3.9+
- 核心框架:LangChain。它是目前构建LLM应用最流行的框架之一,将RAG流程中的文档加载、切分、向量化、检索、生成等环节模块化,极大降低了开发复杂度。
- 向量数据库:Chroma。轻量级、易上手、可持久化,非常适合个人或小规模项目。生产环境可以考虑Qdrant、Milvus等。
- 嵌入模型:选用开源的
text-embedding-ada-002的平替版,如BAAI/bge-small-zh(中文效果好)或all-MiniLM-L6-v2。我们将通过Hugging Face使用它,避免调用OpenAI API产生费用。 - 大语言模型:为了完全私有化部署,我们使用开源模型。这里用ChatGLM3-6B的量化版,它对中文支持好,且对消费级显卡友好。你也可以换成Qwen、Llama等。
- 硬件:至少8GB内存。运行ChatGLM3-6B需要一张至少6GB显存的NVIDIA显卡(如RTX 2060/3060)。纯CPU推理也可行,但速度会慢很多。
首先,安装必要的库:
pipinstalllangchain langchain-community langchain-chroma pypdf sentence-transformers# 安装ChatGLM3需要额外的依赖pipinstallprotobuftransformers==4.36.2 cpm_kernels torch>=2.0gradio mdtex2html sentencepiece accelerate分步操作
我们的目标是构建一个完整的RAG流水线,流程如下:加载文档 -> 分割文本 -> 文本向量化 -> 存入向量库 -> 问句向量化 -> 检索相关片段 -> 组合成提示 -> LLM生成答案。
第一步:文档加载与处理
假设你的知识库是若干PDF和TXT文件,放在./data目录下。LangChain提供了大量的DocumentLoader。
# document_loader.pyfromlangchain_community.document_loadersimportTextLoader,PyPDFLoaderfromlangchain.text_splitterimportRecursiveCharacterTextSplitterimportosdefload_and_split_documents(data_dir="./data"):""" 加载并分割文档 """documents=[]forfilenameinos.listdir(data_dir):filepath=os.path.join(data_dir,filename)iffilename.endswith('.pdf'):loader=PyPDFLoader(filepath)eliffilename.endswith('.txt'):loader=TextLoader(filepath,encoding='utf-8')else:continue# 跳过不支持的文件类型loaded_docs=loader.load()# 每个文档被加载成一个Document对象列表documents.extend(loaded_docs)print(f"已加载:{filename}")# 分割文本。chunk_size控制每个片段的大小,chunk_overlap控制重叠部分,防止上下文断裂。text_splitter=RecursiveCharacterTextSplitter(chunk_size=500,# 每个片段约500字符chunk_overlap=50,# 片段间重叠50字符length_function=len,separators=["\n\n","\n","。","!","?",","," ",""]# 中文友好分隔符)split_docs=text_splitter.split_documents(documents)print(f"文档加载分割完毕,共得到{len(split_docs)}个文本片段。")returnsplit_docsif__name__=="__main__":docs=load_and_split_documents()第二步:构建向量数据库
我们需要一个嵌入模型将文本转换为向量( embeddings ),然后存入Chroma。
# build_vectorstore.pyfromlangchain_chromaimportChromafromlangchain_community.embeddingsimportHuggingFaceEmbeddingsimportosdefcreate_vector_store(split_docs,persist_directory="./chroma_db"):""" 创建并持久化向量数据库 """# 1. 初始化嵌入模型# 使用中文效果较好的开源模型model_name="BAAI/bge-small-zh"model_kwargs={'device':'cpu'}# 如果显卡够用,可改为 `{'device': 'cuda'}`encode_kwargs={'normalize_embeddings':True}# 归一化,提升检索效果embeddings=HuggingFaceEmbeddings(model_name=model_name,model_kwargs=model_kwargs,encode_kwargs=encode_kwargs)# 2. 创建向量库。split_docs会被自动嵌入并存储。# `persist_directory` 指定持久化目录,下次可直接加载,无需重新计算。vectordb=Chroma.from_documents(documents=split_docs,embedding=embeddings,persist_directory=persist_directory)# 显式持久化vectordb.persist()print(f"向量数据库已创建并保存至{persist_directory}")returnvectordbif__name__=="__main__":fromdocument_loaderimportload_and_split_documents split_docs=load_and_split_documents()vectordb=create_vector_store(split_docs)第三步:加载本地大语言模型
我们使用Transformers库加载本地的ChatGLM3-6B模型。
# local_llm.pyfromlangchain_community.llmsimportHuggingFacePipelinefromtransformersimportAutoTokenizer,AutoModelForCausalLM,pipelineimporttorchdefload_local_llm(model_path="THUDM/chatglm3-6b"):""" 加载本地LLM,这里以ChatGLM3为例 """tokenizer=AutoTokenizer.from_pretrained(model_path,trust_remote_code=True)# 量化加载,大幅降低显存需求。如果你的显卡显存>=12GB,可以去掉 `load_in_8bit=True`model=AutoModelForCausalLM.from_pretrained(model_path,trust_remote_code=True,torch_dtype=torch.float16,# 半精度device_map="auto",# 自动分配设备(CPU/GPU)load_in_8bit=True# 8位量化,RTX 3060 6G可跑)# 创建文本生成管道pipe=pipeline("text-generation",model=model,tokenizer=tokenizer,max_new_tokens=512,# 生成的最大token数temperature=0.1,# 较低的温度使输出更确定,更适合知识问答do_sample=True)# 包装成LangChain的LLM对象llm=HuggingFacePipeline(pipeline=pipe)print("本地LLM加载完成。")returnllmif__name__=="__main__":llm=load_local_llm()# 简单测试test_response=llm.invoke("你好,请介绍一下你自己。")print(test_response)第四步:组装RAG链并提问
这是最核心的一步,我们将检索器(Retriever)和语言模型(LLM)串联起来,形成一个“检索-增强-生成”的链条。
# rag_chain.pyfromlangchain.chainsimportRetrievalQAfromlangchain.promptsimportPromptTemplatefrombuild_vectorstoreimportcreate_vector_storefromlocal_llmimportload_local_llmdefcreate_rag_chain():""" 创建完整的RAG问答链 """# 1. 加载已存在的向量数据库(无需重新嵌入)fromlangchain_community.embeddingsimportHuggingFaceEmbeddings embeddings=HuggingFaceEmbeddings(model_name="BAAI/bge-small-zh")fromlangchain_chromaimportChroma persist_directory="./chroma_db"vectordb=Chroma(persist_directory=persist_directory,embedding_function=embeddings)# 2. 将向量数据库转换为检索器,设置`k=4`表示检索最相关的4个片段retriever=vectordb.as_retriever(search_kwargs={"k":4})# 3. 加载本地LLMllm=load_local_llm()# 4. 构建提示模板。这是提升回答质量的关键!# 模板告诉LLM:根据给定的上下文(context)来回答问题(question)。prompt_template="""基于以下已知信息,简洁和专业地回答用户的问题。 如果无法从已知信息中得到答案,请直接说"根据已知信息无法回答该问题",不要编造答案。 已知信息: {context} 问题: {question} 请用中文回答:"""PROMPT=PromptTemplate(template=prompt_template,input_variables=["context","question"])# 5. 创建检索问答链# `chain_type="stuff"` 是最简单的方式,将所有检索到的上下文塞进提示词。# `return_source_documents=True` 可以让我们看到检索到的源文档,便于调试。qa_chain=RetrievalQA.from_chain_type(llm=llm,chain_type="stuff",retriever=retriever,return_source_documents=True,chain_type_kwargs={"prompt":PROMPT}# 使用我们自定义的提示模板)returnqa_chaindefask_question(qa_chain,question):""" 向RAG链提问 """result=qa_chain.invoke({"query":question})answer=result["result"]source_docs=result["source_documents"]print(f"\n问题:{question}")print(f="答案:{answer}")print("\n--- 参考来源 ---")fori,docinenumerate(source_docs[:2]):# 显示前2个来源print(f"[来源{i+1}]{doc.page_content[:200]}...")# 截取片段内容预览print("-"*50)if__name__=="__main__":qa_chain=create_rag_chain()# 测试问题ask_question(qa_chain,"我文档中提到的核心产品是什么?")ask_question(qa_chain,"总结一下第三章节的主要内容。")完整代码与运行
将以上四个步骤的代码模块(document_loader.py,build_vectorstore.py,local_llm.py,rag_chain.py)放在同一目录下。按顺序执行:
- 准备数据:将你的PDF/TXT文档放入
./data文件夹。 - 构建知识库:运行
python build_vectorstore.py。这会完成文档加载、分割、向量化并存储。只需运行一次,除非文档有更新。 - 启动并提问:运行
python rag_chain.py。它会加载已有的向量库和模型,并等待你修改main函数中的问题进行测试。
你也可以将rag_chain.py改造成一个简单的Gradio Web界面,方便交互。
# app.py (可选,用于创建Web界面)importgradioasgrfromrag_chainimportcreate_rag_chain qa_chain=create_rag_chain()defanswer_question(question,history):result=qa_chain.invoke({"query":question})returnresult["result"]gr.ChatInterface(answer_question).launch(share=False,server_name="0.0.0.0")踩坑提示
- 文本分割是门艺术:
chunk_size设置过大,检索会不精准;过小,则上下文可能不完整。需要根据你的文档类型(技术手册、会议记录、小说)反复调整。我的经验是从500开始,观察检索出的片段是否是一个完整的语义单元。 - 嵌入模型的选择:如果知识库全是中文,务必选择中文优化的嵌入模型(如
BAAI/bge-*系列)。用英文模型处理中文,效果会大打折扣。 - 提示词工程:默认提示词可能不够强。务必在提示词中强调“根据上下文回答”和“不知道就说不知道”,这是减少模型幻觉的关键。可以尝试在提示词中加入“请引用原文中的句子”来让答案更精确。
- 本地LLM的显存瓶颈:如果遇到CUDA out of memory,可以尝试:a) 使用
load_in_8bit=True或load_in_4bit=True;b) 换更小的模型(如Qwen1.5-4B);c) 使用纯CPU模式(device_map="cpu"),但推理会慢10倍以上。 - 检索效果不佳:如果总是检索不到相关文档,检查:a) 嵌入模型是否匹配语种;b) 检索数量
k是否太小;c) 尝试使用vectordb.similarity_search_with_score(question, k=4)查看相似度分数,诊断问题。
总结
通过以上实战,我们成功搭建了一个完全本地化、私有的AI知识库与智能助理。这个系统的核心价值在于,它将LLM的通用推理能力与你独有的知识数据相结合,产出的答案既智能又精准。
这个基础框架有巨大的扩展空间:你可以接入企业微信/钉钉机器人,做成团队助手;可以增加多轮对话记忆;可以对接更多类型的文档(Word, Excel, 网页爬虫);甚至可以实现更复杂的“多路检索”或“重排序”来提升精度。
RAG是当前让大模型落地、产生商业价值的最实用路径之一。希望这个教程能帮你迈出第一步,亲手打造一个专属的AI大脑。
如有问题欢迎评论区交流,持续更新中…