尧图建网站 尧图建网站 YAOTU WEB BUILD 免费咨询
ARTICLE DETAIL

资讯详情

深耕网站建设与建站编程的一线实战洞察。

基于InternLM与LangChain构建本地RAG知识库:从原理到实践

基于InternLM与LangChain构建本地RAG知识库:从原理到实践 1. 项目缘起从“玩具”到“助手”的必经之路去年年底我花了整整一个周末用开源的InternLM-7B模型和LangChain框架给自己搭建了一个本地化的个人知识库。起因很简单我手头积累了大量的技术文档、项目笔记和行业报告每次想找点东西要么是记不清文件名要么是记得关键词但搜出来一堆无关结果。传统的全文搜索在面对“帮我找一下关于微服务熔断机制但不要讲Hystrix要讲新一点方案”这类问题时显得力不从心。大模型的出现让我看到了希望它能不能真正理解我的问题然后从我的资料库里找到最相关的内容并组织成清晰的答案这就是RAG检索增强生成技术的核心价值。它不像让大模型凭空“编造”答案而是让它学会“查资料”。我的这个项目就是一次完整的实践利用上海人工智能实验室开源的书生·浦语InternLM大模型作为“大脑”用LangChain这套工具链作为“手脚”去连接和处理我的本地文档最终构建一个能问答、能总结的智能知识库。这不仅仅是技术拼装更是一次对当前AI应用开发范式的深度体验。你会发现让大模型“靠谱”起来关键不在于模型本身有多庞大而在于你如何为它构建一个高效、准确的“外部记忆系统”。2. 技术栈选型为什么是InternLM LangChain搭建一个知识库核心组件无非三块大模型LLM、框架Framework和向量数据库Vector Database。市面上选择很多我的选型思路主要基于“可控性”、“成本”和“生态”三个维度。2.1 大模型书生·浦语InternLM的务实之选为什么不直接用ChatGPT的API原因有三数据隐私、长期成本和定制化需求。我的技术笔记很多涉及未公开的项目细节上传到第三方云服务存在风险。其次API调用是按量计费的对于一个需要频繁查询的个人知识库长期来看是一笔不小的开销。最后我希望模型能更贴合我的技术语境比如对某些专有名词的理解更深。InternLM-7B是一个70亿参数的中英双语模型完全开源可以部署在消费级显卡如RTX 3090/4090上。它的性能在同等尺寸模型中属于第一梯队特别是在中文理解和生成任务上表现优异。选择它意味着我拥有完全的控制权数据不出本地、零调用成本、并且可以后续针对我的知识领域进行微调虽然本项目未涉及微调。部署也相对简单通过Hugging Face的transformers库或者lmdeploy等工具可以在半小时内让模型在本地跑起来。2.2 框架LangChain——AI应用的“粘合剂”LangChain不是一个具体的工具而是一个开发框架。它的价值在于它把大模型应用开发中那些繁琐、重复的环节标准化、模块化了。想象一下如果没有LangChain你需要自己写代码去加载各种格式的文档PDF、Word、Markdown、对文档进行分块、调用嵌入模型将文本转为向量、设计检索逻辑、拼接提示词Prompt、调用大模型、解析输出……这个过程极其容易出错且难以维护。LangChain提供了“链Chain”的概念让你可以像搭积木一样组合这些模块。对于知识库场景它提供了现成的RetrievalQA链你只需要配置好文档加载器、文本分割器、向量存储检索器和大模型它就能自动完成“检索-生成”的全流程。这大大降低了开发门槛让我能把精力集中在业务逻辑和效果优化上而不是底层通信和错误处理。2.3 向量数据库Milvus与Chroma的轻量级对决向量数据库负责存储和快速检索我们文档的向量化表示。当用户提问时问题也会被转化为向量并在数据库中找到最相似的文本块即知识片段。Milvus功能强大的专业向量数据库支持分布式部署、多种索引算法和丰富的查询功能。但它相对重量级对于个人知识库这种单机、数据量在万级以下的应用来说有点“杀鸡用牛刀”部署和运维成本较高。Chroma一个轻量级、嵌入式的向量数据库。它可以直接用Python包安装数据存储在本地SQLite文件中。它的API极其简洁与LangChain集成度非常高。对于我的项目文档数量约1000份文本块约5万个Chroma的性能完全足够而且省去了维护一个独立数据库服务的麻烦。因此我选择了Chroma作为本次项目的向量数据库。它完美契合了个人知识库“轻量、易用、快速启动”的需求。注意关于“上下文理解”和“语境推测”这类意思相近的词在构建向量数据库时强烈建议进行统一。因为嵌入模型如text2vec、bge等会将文本转换为向量相近但不完全相同的词其向量表示可能有细微差异。如果你希望系统将两者视为同类问题最好在知识库构建阶段就进行关键词归一化处理比如都统一为“上下文理解”。这能显著提升检索的准确性和一致性。3. 实战搭建从零构建知识库的完整流水线理论说再多不如动手做一遍。下面是我搭建知识库的完整步骤和核心代码你完全可以跟着复现。3.1 环境准备与依赖安装首先创建一个干净的Python虚拟环境推荐使用conda或venv然后安装核心依赖。# 创建并激活虚拟环境 conda create -n knowledge_base python3.10 conda activate knowledge_base # 安装PyTorch (请根据你的CUDA版本到官网选择对应命令) pip install torch torchvision torchaudio --index-url https://download.pytorch.org/whl/cu118 # 安装LangChain及其相关组件 pip install langchain langchain-community langchain-chroma # 安装文档加载器 (支持txt, pdf, docx, md等) pip install pypdf python-docx markdown unstructured # 安装向量数据库和嵌入模型 pip install chromadb # 选用开源的中文嵌入模型例如BGE pip install sentence-transformers # 安装InternLM的推理库 (这里以transformers为例) pip install transformers3.2 文档加载与预处理知识库的“原料加工”你的知识可能散落在PDF、Word、Markdown文件中。第一步是把它们“读”进来并切成适合处理的“小块”。from langchain_community.document_loaders import DirectoryLoader, TextLoader, PyPDFLoader from langchain.text_splitter import RecursiveCharacterTextSplitter # 1. 加载文档指定你的知识文档目录 loader DirectoryLoader( ./my_docs/, # 你的文档文件夹路径 glob**/*.pdf, # 加载所有pdf文件也可以改成 **/*.md 或 **/*.docx loader_clsPyPDFLoader # 指定PDF加载器 ) documents loader.load() print(f成功加载 {len(documents)} 份文档) # 2. 分割文本这是至关重要的一步 # 块大小chunk_size和重叠区chunk_overlap需要根据你的文档类型调整。 # 技术文档通常结构清晰块可以稍大会议纪要等松散文本块要小一些。 text_splitter RecursiveCharacterTextSplitter( chunk_size500, # 每个文本块约500字符 chunk_overlap50, # 块之间重叠50字符避免上下文断裂 length_functionlen, separators[\n\n, \n, 。, , , , , , ] # 中文优先的分隔符 ) texts text_splitter.split_documents(documents) print(f文档被分割成 {len(texts)} 个文本块)为什么这么分割大模型有上下文长度限制如4096 tokens。如果我们把整本书喂给它它无法处理。分割成小块后检索时只需要找出最相关的几个块大大节省了上下文窗口。重叠区是为了防止一个完整的句子或概念被硬生生切在两块中间导致语义不完整。3.3 向量化与存储构建知识的“记忆宫殿”接下来我们需要一个“嵌入模型”把文本块变成数学向量一组数字然后存进Chroma数据库。from langchain.embeddings import HuggingFaceEmbeddings from langchain.vectorstores import Chroma # 1. 初始化嵌入模型 # 这里使用BGEBAAI/bge-small-zh模型它是一个优秀的中文文本表示模型 embeddings HuggingFaceEmbeddings( model_nameBAAI/bge-small-zh-v1.5, model_kwargs{device: cuda}, # 如果有GPU使用GPU加速 encode_kwargs{normalize_embeddings: True} # 归一化提升检索效果 ) # 2. 创建向量数据库 # persist_directory 指定向量数据持久化存储的路径 vectorstore Chroma.from_documents( documentstexts, embeddingembeddings, persist_directory./chroma_db # 数据将保存在这个目录 ) vectorstore.persist() # 持久化到磁盘 print(向量数据库构建完成已保存至 ./chroma_db)这个过程可能会比较耗时取决于你的文档数量和嵌入模型的速度。BGE-small模型在速度和效果上取得了很好的平衡。完成后你的./chroma_db文件夹里就存储了所有知识的向量索引。3.4 连接大模型为知识库注入“灵魂”现在让我们把本地的InternLM模型接入进来。from transformers import AutoTokenizer, AutoModelForCausalLM import torch from langchain.llms import HuggingFacePipeline from transformers import pipeline # 1. 加载InternLM模型和分词器 model_path internlm/internlm2-chat-7b # 或者你本地下载的模型路径 tokenizer AutoTokenizer.from_pretrained(model_path, trust_remote_codeTrue) model AutoModelForCausalLM.from_pretrained( model_path, torch_dtypetorch.float16, # 半精度加载节省显存 device_mapauto, # 自动分配模型层到GPU/CPU trust_remote_codeTrue ).eval() # 2. 创建文本生成管道 pipe pipeline( text-generation, modelmodel, tokenizertokenizer, max_new_tokens512, # 生成的最大长度 temperature0.1, # 较低的温度使输出更确定、更聚焦 do_sampleTrue, top_p0.8 ) # 3. 将管道包装成LangChain的LLM对象 llm HuggingFacePipeline(pipelinepipe)关键参数解析torch_dtypetorch.float16使用半精度浮点数能在几乎不损失精度的情况下将显存占用减半是消费级显卡运行7B模型的必备操作。temperature0.1在知识问答场景下我们希望答案尽可能准确、确定而不是富有创造性。低温度值可以减少模型的随机性。device_map”auto”让accelerate库自动决定将模型的每一层放在哪个设备上对于模型大于显存的情况它会自动将部分层卸载到CPU内存实现大模型在有限显存上的运行。3.5 组装检索问答链让一切运转起来最后一步用LangChain把向量数据库和大模型“链”起来。from langchain.chains import RetrievalQA from langchain.prompts import PromptTemplate # 1. 从磁盘加载已构建的向量数据库 vectorstore Chroma( persist_directory./chroma_db, embedding_functionembeddings ) # 2. 创建检索器设置返回最相关的3个文本块 retriever vectorstore.as_retriever(search_kwargs{k: 3}) # 3. 定义提示词模板 # 这是提升效果的关键告诉模型如何利用检索到的上下文。 prompt_template 请根据以下上下文信息回答用户的问题。如果上下文信息中没有相关答案请直接说“根据现有资料我无法回答这个问题”不要编造答案。 上下文信息 {context} 问题{question} 请给出专业、准确的回答 PROMPT PromptTemplate( templateprompt_template, input_variables[context, question] ) # 4. 创建检索问答链 qa_chain RetrievalQA.from_chain_type( llmllm, chain_typestuff, # 最简单的方式将所有检索到的上下文塞进提示词 retrieverretriever, chain_type_kwargs{prompt: PROMPT}, return_source_documentsTrue # 返回源文档便于追溯 ) # 5. 进行提问 question LangChain中的工具调用Tool Calling和LLM的原生函数调用Function Call有什么区别 result qa_chain.invoke({query: question}) print(问题, question) print(答案, result[result]) print(\n--- 参考来源 ---) for doc in result[source_documents]: print(f内容片段{doc.page_content[:200]}...) print(f来源文件{doc.metadata.get(source, N/A)}\n)4. 效果优化与深度踩坑实录把流程跑通只是第一步要让知识库真正好用还需要精细调优。下面是我在实战中遇到的几个核心问题和解决方案。4.1 检索质量不佳问题出在文本分割和嵌入模型最初我的回答经常跑偏或者包含无关信息。排查后发现两个主要原因文本分割不合理我一开始用默认的英文分隔符如”\n\n”对中文文档切割得很碎导致一个完整的知识点被拆散。后来调整为上面代码中的中文优先分隔符列表并反复调整chunk_size和chunk_overlap。对于技术文档chunk_size800chunk_overlap100效果更好。嵌入模型不匹配最初尝试了通用的all-MiniLM-L6-v2模型它对中文的语义捕捉能力较弱。切换到专门针对中文优化的BGEBAAI/bge-*系列模型后检索准确率有了质的提升。如果你的知识库是英文为主text-embedding-ada-002OpenAI或all-mpnet-base-v2是更好的选择。4.2 回答冗长或偏离上下文提示词工程是关键即使检索到了正确的文档模型有时也会自顾自地扩展甚至“幻觉”出不存在的内容。这需要通过提示词Prompt进行严格约束。我的提示词模板经历了多次迭代V1简陋版“请回答{question}”—— 结果模型完全无视检索到的上下文。V2基础版“根据以下信息{context} 回答问题{question}”—— 模型开始使用上下文但依然会补充大量通用知识。V3严格版即上面代码中的模板明确指令“如果上下文信息中没有相关答案请直接说‘根据现有资料我无法回答这个问题’不要编造答案。” 这个指令极大地抑制了模型的“幻觉”让答案更紧扣资料。4.3 处理速度慢性能瓶颈分析与优化在本地部署速度是重要体验。我遇到的瓶颈和优化方法嵌入过程慢首次构建向量库时用CPU运行BGE模型处理上万文本块非常慢。解决方案使用支持CUDA的sentence-transformers并将嵌入模型放到GPU上运行model_kwargs{‘device’: ‘cuda’}速度提升超过20倍。模型推理慢InternLM-7B在CPU上生成一个答案需要几十秒。解决方案量化使用bitsandbytes库进行4-bit或8-bit量化能大幅减少显存占用并提升推理速度。推理优化库使用vLLM或llama.cpp等高性能推理库来替代原生transformers进行加载和推理吞吐量能有数倍提升。调整生成参数降低max_new_tokens如设为256对于知识问答通常足够。4.4 LangChain工具调用与LLM原生函数调用的区别这是一个从热词中来的好问题。在调用流程上两者目的相似但抽象层级和灵活性不同。LLM原生函数调用如OpenAI的Function Calling是模型本身具备的一种能力。你定义好函数的名称、描述和参数结构模型在理解用户请求后可以输出一个符合该结构的JSON对象表示它“想要调用”某个函数并传入某些参数。后续需要你写代码去解析这个JSON并真正执行函数。它的速度主要受模型本身推理速度的影响。LangChain工具调用是在LLM原生函数调用之上的一个更高层次的抽象。在LangChain中你定义一个Tool对象它封装了工具的描述、参数和实际的执行函数。当你把Tool绑定到一个支持工具调用的LLM通过bind_tools方法时LangChain会帮你处理底层的提示词格式化、模型输出解析、以及工具执行和结果回传的整个循环。它的速度除了受模型推理速度影响还受工具本身执行时间如调用一个慢速API和LangChain框架开销的影响。简单说LangChain工具调用帮你把“定义-解析-执行-回传”这个流程自动化、标准化了开发更方便但会引入一些框架层面的额外开销。对于简单场景直接使用LLM的原生函数调用可能更轻量、直接。5. 进阶思考知识库的维护与扩展一个知识库不是一劳永逸的新的知识会不断产生。如何更新增量更新ChromaDB支持增量添加文档。你可以定期运行脚本加载新文档分割成块生成向量然后调用vectorstore.add_documents(new_texts)添加到已有数据库中。需要注意的是这不会自动删除旧版本文档如果你的文档有更新最好采用“删除旧索引重建整个库”的方式或者为文档块添加版本元数据。元数据过滤在创建向量库时可以为每个文本块添加元数据如{“source”: “xx.pdf”, “page”: 5, “category”: “backend”}。在检索时可以通过retriever.search_kwargs增加元数据过滤条件实现更精准的检索例如“只在后端分类的文档中搜索”。检索策略优化除了简单的相似性搜索similarity_search可以尝试最大边际相关性MMR在保证相关性的同时增加检索结果的多样性避免返回内容高度重复的文本块。自查询Self-Query让LLM自动将用户问题拆解成“查询语句”和“元数据过滤器”实现更智能的检索。这需要你的文档元数据足够丰富和规范。搭建这个基于InternLM和LangChain的知识库让我深刻体会到当前AI应用落地的核心正从“追求更大参数模型”转向“如何更高效、更可靠地利用现有模型能力”。RAG架构提供了一条清晰可行的路径。这个项目就像一个起点你可以在此基础上集成更多工具如联网搜索、计算器升级为智能体Agent或者尝试更复杂的检索和重排算法。最重要的是你拥有了一个完全受控、不断进化的个人知识大脑这其中的价值远超跟随某个热门API的短期兴奋。
返回列表