GLM-4-9B-Chat-1M与LangChain集成:构建智能问答系统
1. 引言
想象一下,你手里有一份几百页的技术文档,或者一整本产品手册,你想快速找到某个特定功能的说明,或者让AI帮你总结一下核心要点。传统的大模型可能处理不了这么长的内容,要么直接拒绝,要么只能分段处理,结果往往支离破碎,上下文信息丢失严重。
这就是长文本处理面临的现实挑战。很多企业在构建内部知识库或者智能客服系统时,最头疼的就是如何让AI理解那些动辄几十万甚至上百万字的文档资料。分段处理吧,上下文不连贯;一次性输入吧,模型又吃不消。
好在现在有了GLM-4-9B-Chat-1M这个“大胃王”模型。它能一口气处理约200万中文字符的上下文,相当于两本《红楼梦》的长度。这意味着你可以把整个公司的技术文档、产品手册、历史案例都喂给它,它都能消化得了。
但光有能“吃”长文本的模型还不够,怎么让它“消化”得好、“回答”得准,才是关键。这就是我们今天要聊的——如何把GLM-4-9B-Chat-1M和LangChain框架结合起来,搭建一个真正能用的智能问答系统。
这个系统能做什么呢?简单来说,你可以上传任意长度的文档,然后像跟专家聊天一样,问它任何相关问题。比如“我们产品的技术架构是什么?”、“去年第三季度的销售数据怎么样?”、“帮我找出所有关于安全漏洞的修复记录”。系统会从海量文档中精准找到相关信息,然后生成准确、连贯的回答。
接下来,我就带你一步步搭建这个系统,从环境准备到代码实现,再到实际应用,让你也能拥有一个处理长文本的智能助手。
2. 为什么选择GLM-4-9B-Chat-1M和LangChain
在开始动手之前,我们先聊聊为什么选这两个技术组合。这就像盖房子,选对材料和工具,后面的事情就顺了。
2.1 GLM-4-9B-Chat-1M:长文本处理的“专业选手”
GLM-4-9B-Chat-1M最近在技术圈里挺火的,主要因为它有几个硬核优势:
首先是超长的上下文窗口。1M的tokens,换算成中文字符大概是200万字。这是什么概念呢?一本《三国演义》大概64万字,它能同时处理三本。对于企业文档、法律合同、学术论文这类长文本场景,这个容量完全够用。
然后是精准的信息定位能力。光能“装得下”还不够,关键是要“找得准”。GLM-4-9B-Chat-1M在“大海捞针”测试中表现很出色,就是在超长文本里随机插入一些关键信息,看模型能不能准确找出来。测试结果显示,即使在100万tokens的长度下,它的定位准确率还能保持在95%以上。这意味着你问它一个具体问题,它不太会“看走眼”。
还有多语言支持。除了中文和英文,它还支持日语、韩语、德语等26种语言。如果你的业务涉及多语言文档,比如跨境电商的产品描述、国际公司的合同文件,这个特性就很有用了。
最后是开源和可本地部署。作为开源模型,你可以把它部署在自己的服务器上,数据不用上传到云端,安全性有保障。而且90亿的参数规模,对硬件要求相对友好,一张RTX 4090显卡就能跑起来。
2.2 LangChain:AI应用的“脚手架”
如果说GLM-4-9B-Chat-1M是核心引擎,那LangChain就是让这个引擎发挥作用的整车框架。
LangChain本质上是一个开发框架,专门用来构建基于大语言模型的应用。它把很多复杂的功能都封装好了,你不需要从头造轮子。
最核心的是它的RAG(检索增强生成)能力。RAG是什么?简单说就是“先检索,再生成”。当用户提问时,系统不是让模型凭空想象答案,而是先从你的文档库里找到相关的信息片段,把这些片段和问题一起交给模型,让模型基于这些真实信息来生成答案。这样既保证了答案的准确性,又充分利用了模型的推理能力。
LangChain还提供了完整的工具链。比如文档加载器,能读取PDF、Word、TXT、网页等各种格式;文本分割器,能把长文档切成适合处理的小块;向量数据库集成,能快速检索相似内容;还有对话记忆、工具调用等各种组件。
另一个好处是标准化。LangChain定义了一套标准的接口和流程,让你的代码结构清晰,维护起来也方便。而且它有活跃的社区,遇到问题容易找到解决方案。
2.3 强强联合的价值
把这两个技术结合起来,效果是1+1>2的。
GLM-4-9B-Chat-1M提供了处理长文本的基础能力,而LangChain提供了构建智能应用的工程框架。你可以理解为:GLM是“大脑”,负责理解和生成;LangChain是“神经系统”,负责信息的输入、处理和输出。
这种组合特别适合企业级的应用场景。比如:
- 内部知识库问答:员工可以快速查询公司制度、技术文档、产品资料
- 客户支持系统:基于产品手册和常见问题,自动回答客户咨询
- 法律文档分析:快速检索合同条款、法律条文
- 学术研究助手:处理长篇论文,提取关键信息
而且整个系统可以部署在本地,数据安全可控,成本也比调用商业API低得多。
3. 环境准备与快速上手
好了,理论说完了,咱们开始动手。首先得把环境搭起来,这个过程其实不复杂,跟着步骤走就行。
3.1 硬件和软件要求
先看看你的电脑能不能跑得动。GLM-4-9B-Chat-1M虽然只有90亿参数,但对硬件还是有些要求的。
硬件方面,建议至少:
- 内存:32GB以上(模型加载需要约18GB,还要留一些给系统和其他程序)
- 显卡:显存16GB以上的NVIDIA GPU,比如RTX 4090、RTX 3090
- 存储:至少50GB的可用空间(模型文件大概18GB,还要留空间给文档和向量数据库)
如果你没有这么强的显卡,也可以考虑用CPU推理,就是速度会慢很多。或者用云服务器,现在很多云服务商都提供带GPU的实例,按小时计费,用完了就关掉,成本可控。
软件环境,我们需要准备:
- Python 3.10或更高版本
- CUDA工具包(如果用GPU的话)
- 必要的Python包
3.2 一步步安装依赖
打开你的终端或命令行,咱们开始安装。建议先创建一个虚拟环境,这样不会和你系统里其他的Python项目冲突。
# 创建虚拟环境(如果你用conda) conda create -n glm-langchain python=3.10 conda activate glm-langchain # 或者用venv(如果你用原生的Python) python -m venv glm-langchain # Windows glm-langchain\Scripts\activate # Linux/Mac source glm-langchain/bin/activate环境激活后,安装必要的包:
# 安装PyTorch(根据你的CUDA版本选择,这里以CUDA 11.8为例) pip install torch torchvision torchaudio --index-url https://download.pytorch.org/whl/cu118 # 安装transformers和模型相关依赖 pip install transformers accelerate sentencepiece protobuf # 安装LangChain和相关组件 pip install langchain langchain-community langchain-text-splitters # 安装向量数据库(这里用Chroma,轻量级,适合本地开发) pip install chromadb # 安装文档加载器(支持多种格式) pip install pypdf python-docx beautifulsoup4这些包的作用我简单说一下:
torch:PyTorch深度学习框架,模型运行的基础transformers:Hugging Face的模型库,用来加载和运行GLM模型langchain:核心框架,提供RAG的各种组件chromadb:向量数据库,用来存储和检索文档的向量表示pypdf和python-docx:用来读取PDF和Word文档
3.3 下载GLM-4-9B-Chat-1M模型
模型文件比较大,大概18GB,下载需要一些时间。你可以从Hugging Face或者ModelScope下载。
from transformers import AutoModelForCausalLM, AutoTokenizer import torch # 设置模型路径(如果已经下载到本地) model_path = "THUDM/glm-4-9b-chat-1m" # 从Hugging Face下载 # 或者本地路径 # model_path = "/path/to/your/local/model" print("开始加载模型和分词器...") tokenizer = AutoTokenizer.from_pretrained( model_path, trust_remote_code=True # GLM需要这个参数 ) model = AutoModelForCausalLM.from_pretrained( model_path, torch_dtype=torch.bfloat16, # 用bfloat16节省显存 device_map="auto", # 自动分配到可用的设备 trust_remote_code=True ) print("模型加载完成!")第一次运行时会自动下载模型,如果网络不好可能会中断。你也可以用git lfs先下载到本地:
# 安装git-lfs git lfs install # 克隆模型仓库 git clone https://huggingface.co/THUDM/glm-4-9b-chat-1m下载完成后,把上面的model_path改成你的本地路径就行。
3.4 测试模型是否能正常运行
先写个简单的测试脚本,看看模型能不能正常工作:
def test_model(): # 准备对话 messages = [ {"role": "user", "content": "你好,请介绍一下你自己。"} ] # 应用聊天模板 inputs = tokenizer.apply_chat_template( messages, add_generation_prompt=True, tokenize=True, return_tensors="pt", return_dict=True ) # 移动到GPU(如果有的话) if torch.cuda.is_available(): inputs = {k: v.cuda() for k, v in inputs.items()} # 生成回复 with torch.no_grad(): outputs = model.generate( **inputs, max_new_tokens=500, temperature=0.7, do_sample=True ) # 解码输出 response = tokenizer.decode(outputs[0], skip_special_tokens=True) print("模型回复:", response) # 运行测试 test_model()如果一切正常,你应该能看到模型自我介绍的内容。第一次运行可能会慢一些,因为要加载模型到内存。后面再运行就会快很多。
到这里,基础环境就准备好了。你可能觉得步骤有点多,但大部分都是一次性的设置。搭好之后,后面的开发就顺畅了。
4. 构建智能问答系统的核心步骤
环境准备好了,现在开始构建系统的核心部分。我会带你一步步实现一个完整的智能问答系统,从文档处理到问答生成,每个环节都配上代码和解释。
4.1 第一步:文档加载与处理
智能问答系统的第一步是要“喂”文档。你的文档可能是各种格式的——PDF、Word、TXT、网页,甚至数据库里的数据。LangChain提供了丰富的文档加载器,能处理大部分常见格式。
from langchain_community.document_loaders import ( PyPDFLoader, # PDF文档 TextLoader, # 纯文本 Docx2txtLoader, # Word文档 WebBaseLoader, # 网页 DirectoryLoader # 整个目录 ) from langchain.text_splitter import RecursiveCharacterTextSplitter def load_documents(doc_path): """ 加载文档,支持多种格式 """ documents = [] if doc_path.endswith('.pdf'): loader = PyPDFLoader(doc_path) elif doc_path.endswith('.txt'): loader = TextLoader(doc_path, encoding='utf-8') elif doc_path.endswith('.docx'): loader = Docx2txtLoader(doc_path) elif doc_path.startswith('http'): loader = WebBaseLoader(doc_path) else: # 如果是目录,加载所有支持的文件 loader = DirectoryLoader( doc_path, glob="**/*.pdf", loader_cls=PyPDFLoader ) documents = loader.load() print(f"加载了 {len(documents)} 个文档片段") return documents # 使用示例 documents = load_documents("your_document.pdf")加载文档后,你会发现一个问题:有些文档太长了,比如几百页的PDF。直接扔给模型处理是不行的,一方面可能超出上下文限制,另一方面检索效率也低。所以我们需要把长文档切成小块,专业术语叫“文本分割”。
def split_documents(documents, chunk_size=1000, chunk_overlap=200): """ 分割文档为小块,方便后续处理 chunk_size: 每个块的大小(字符数) chunk_overlap: 块之间的重叠部分,避免信息被切断 """ text_splitter = RecursiveCharacterTextSplitter( chunk_size=chunk_size, chunk_overlap=chunk_overlap, length_function=len, separators=["\n\n", "\n", "。", "!", "?", ";", ",", " ", ""] ) chunks = text_splitter.split_documents(documents) print(f"文档被分割成 {len(chunks)} 个文本块") # 看看前几个块的内容 for i, chunk in enumerate(chunks[:3]): print(f"\n--- 块 {i+1} ---") print(chunk.page_content[:200] + "...") return chunks # 使用示例 text_chunks = split_documents(documents)这里有几个参数需要注意:
chunk_size:每个文本块的大小。太小了信息不完整,太大了检索不精准。一般1000-2000字符比较合适。chunk_overlap:块之间的重叠。比如第一个块是1-1000字符,第二个块是800-1800字符,这样能保证信息连续性。separators:分割符列表。按优先级尝试分割,先尝试双换行,再单换行,再句号等等。
4.2 第二步:创建向量数据库
文本分割好后,我们需要把它们转换成向量(就是一组数字),然后存到向量数据库里。这样当用户提问时,系统能快速找到最相关的文本块。
为什么用向量?因为文字本身计算机不好直接比较相似度,但转换成向量后,就可以用数学方法计算距离,距离越近表示内容越相似。
from langchain.embeddings import HuggingFaceEmbeddings from langchain.vectorstores import Chroma import os def create_vector_store(chunks, persist_directory="./chroma_db"): """ 创建向量数据库 """ # 使用中文嵌入模型(如果你主要处理中文文档) embeddings = HuggingFaceEmbeddings( model_name="BAAI/bge-large-zh-v1.5", # 中文嵌入模型,效果不错 model_kwargs={'device': 'cuda' if torch.cuda.is_available() else 'cpu'}, encode_kwargs={'normalize_embeddings': True} ) # 创建向量数据库 vectorstore = Chroma.from_documents( documents=chunks, embedding=embeddings, persist_directory=persist_directory ) # 保存到磁盘 vectorstore.persist() print(f"向量数据库已创建并保存到 {persist_directory}") return vectorstore # 使用示例 vectorstore = create_vector_store(text_chunks)这里我用了BAAI/bge-large-zh-v1.5这个中文嵌入模型,它在中文文本上的表现很好。如果你主要处理英文文档,可以换成all-MiniLM-L6-v2之类的英文模型。
向量数据库创建好后,数据就持久化保存了。下次启动系统时,可以直接加载,不用重新处理文档:
def load_vector_store(persist_directory="./chroma_db"): """ 加载已有的向量数据库 """ embeddings = HuggingFaceEmbeddings( model_name="BAAI/bge-large-zh-v1.5", model_kwargs={'device': 'cuda' if torch.cuda.is_available() else 'cpu'} ) vectorstore = Chroma( persist_directory=persist_directory, embedding_function=embeddings ) print(f"从 {persist_directory} 加载了向量数据库") return vectorstore4.3 第三步:搭建RAG检索链
现在到了核心部分——检索增强生成(RAG)。当用户提问时,系统要做三件事:
- 从向量数据库检索相关文档
- 把检索到的文档和问题一起交给模型
- 让模型基于这些文档生成答案
from langchain.chains import RetrievalQA from langchain.prompts import PromptTemplate from langchain.llms import HuggingFacePipeline from transformers import pipeline def create_rag_chain(vectorstore): """ 创建RAG问答链 """ # 首先,把GLM模型包装成LangChain能用的格式 print("正在初始化GLM模型...") # 创建文本生成管道 text_generation_pipeline = pipeline( "text-generation", model=model, tokenizer=tokenizer, max_new_tokens=1024, temperature=0.7, do_sample=True, top_p=0.9, repetition_penalty=1.1, device=0 if torch.cuda.is_available() else -1 ) # 包装成LangChain的LLM llm = HuggingFacePipeline(pipeline=text_generation_pipeline) # 创建提示模板 prompt_template = """基于以下上下文信息,请回答问题。如果你不知道答案,就说不知道,不要编造信息。 上下文: {context} 问题:{question} 请根据上下文提供准确、完整的回答:""" PROMPT = PromptTemplate( template=prompt_template, input_variables=["context", "question"] ) # 创建检索器 retriever = vectorstore.as_retriever( search_type="similarity", # 相似度检索 search_kwargs={"k": 4} # 返回最相关的4个文档块 ) # 创建RAG链 qa_chain = RetrievalQA.from_chain_type( llm=llm, chain_type="stuff", # 把所有检索到的文档拼接到一起 retriever=retriever, chain_type_kwargs={"prompt": PROMPT}, return_source_documents=True # 返回源文档,方便查看参考了哪些内容 ) print("RAG问答链创建完成!") return qa_chain # 使用示例 qa_chain = create_rag_chain(vectorstore)这个提示模板很关键,它告诉模型:“你要基于我给的上下文来回答问题,不知道就说不知道”。这样可以减少模型“胡编乱造”的情况。
search_kwargs={"k": 4}表示每次检索返回4个最相关的文档块。这个数字可以根据需要调整,太多可能引入噪声,太少可能信息不全。
4.4 第四步:问答交互实现
系统搭好了,现在来试试效果:
def ask_question(qa_chain, question): """ 提问并获取答案 """ print(f"\n问题:{question}") print("正在检索和生成答案...") start_time = time.time() try: result = qa_chain({"query": question}) # 计算耗时 elapsed_time = time.time() - start_time print(f"\n答案:{result['result']}") print(f"\n耗时:{elapsed_time:.2f}秒") # 显示参考了哪些文档 print("\n参考文档:") for i, doc in enumerate(result['source_documents'][:2]): # 显示前2个 print(f"\n--- 参考文档 {i+1} ---") print(doc.page_content[:300] + "...") except Exception as e: print(f"出错:{str(e)}") # 测试几个问题 questions = [ "文档中提到了哪些关键技术?", "这个产品的核心功能是什么?", "实施这个方案需要哪些步骤?" ] for q in questions: ask_question(qa_chain, q) print("\n" + "="*50 + "\n")运行这个代码,你会看到系统先检索相关文档,然后生成答案,还会显示参考了哪些文档片段。这样你就能知道答案是从哪里来的,增加了可信度。
5. 实际应用案例与效果展示
理论讲完了,代码也写了,现在来看看实际用起来怎么样。我准备了一个具体的案例,让你感受一下这个系统的能力。
5.1 案例背景:技术文档问答
假设你是一家科技公司的技术文档工程师,公司有几百页的产品技术文档,包括API参考、架构说明、部署指南等等。新员工来了要熟悉产品,老员工遇到问题要查资料,客户咨询技术细节……每天都有大量的文档查询需求。
传统做法是让人去文档里翻找,效率低还容易漏掉信息。现在用我们刚搭建的系统,情况就不一样了。
5.2 文档准备
我准备了一份模拟的技术文档,内容涵盖:
- 产品概述和架构设计
- API接口说明
- 部署和配置指南
- 故障排查方法
- 性能优化建议
文档总长度大约5万字,包含文字、代码示例、配置说明等各种内容。
5.3 系统搭建与测试
按照前面的步骤,我加载文档、创建向量数据库、搭建RAG链。整个过程大概花了10分钟(主要时间是文档处理和向量化)。
然后我开始提问测试:
第一个问题:“我们的产品支持哪些数据库?”
系统检索了文档中关于数据库支持的部分,然后生成回答:
根据技术文档,我们的产品支持以下数据库: 1. MySQL 5.7及以上版本 2. PostgreSQL 12及以上版本 3. MongoDB 4.4及以上版本 4. Redis 6.0及以上版本(用于缓存) 文档中还提供了每种数据库的配置示例和性能调优建议。第二个问题:“如何部署到Kubernetes集群?”
系统找到了部署指南章节,给出了步骤:
Kubernetes部署步骤如下: 1. 准备Docker镜像:使用提供的Dockerfile构建镜像 2. 创建命名空间:kubectl create namespace your-app 3. 部署配置映射:包含环境变量和配置文件 4. 部署服务:定义Service和Ingress 5. 部署工作负载:使用Deployment或StatefulSet 文档中提供了完整的yaml配置示例,并建议在生产环境使用Helm Chart进行管理。第三个问题:“遇到性能瓶颈时应该怎么排查?”
这个问题涉及多个章节,系统从故障排查和性能优化部分找到了相关信息:
性能排查建议: 1. 监控指标检查:查看CPU、内存、磁盘IO、网络流量 2. 日志分析:检查应用日志中的警告和错误信息 3. 数据库优化:检查慢查询,优化索引 4. 缓存策略:评估缓存命中率,调整缓存策略 5. 代码分析:使用性能分析工具定位热点代码 文档中提供了具体的命令和工具使用方法,并建议定期进行性能压测。5.4 效果分析
从测试结果看,系统的表现有几个亮点:
首先是准确性高。答案都来自文档原文,没有编造信息。即使问题比较宽泛(比如“性能瓶颈排查”),系统也能从多个相关章节提取信息,组织成连贯的回答。
其次是响应速度快。从提问到得到答案,平均在3-5秒内完成。这包括了向量检索、文档拼接、模型生成多个步骤。对于日常使用来说,这个速度完全可以接受。
然后是处理长文档的能力。5万字的文档一次性处理,不需要人工分段。系统能理解文档的整体结构,在不同章节间建立关联。
最后是易用性。用户不需要知道文档的具体结构,用自然语言提问就行。系统会自动找到相关信息,比手动搜索方便多了。
5.5 与传统方法的对比
为了更直观地展示效果,我做了个简单对比:
| 对比项 | 传统搜索 | 我们的RAG系统 |
|---|---|---|
| 查询方式 | 关键词搜索 | 自然语言提问 |
| 结果呈现 | 文档列表 | 直接答案 |
| 信息整合 | 需要人工阅读多个文档 | 自动整合相关信息 |
| 处理速度 | 几分钟到几十分钟 | 几秒钟 |
| 准确性 | 依赖关键词匹配 | 基于语义理解 |
| 适用场景 | 明确知道要找什么 | 探索性、复杂性问题 |
从实际使用反馈来看,技术团队对这个系统很满意。新员工用它来熟悉产品,能快速找到需要的信息;开发人员遇到问题,不用在文档里大海捞针;客户支持团队也能更准确地回答技术咨询。
6. 优化技巧与进阶功能
基础功能跑通了,但要让系统真正好用,还需要一些优化和进阶功能。这部分我分享几个实用的技巧。
6.1 优化检索效果
检索是RAG系统的关键,检索不准,后面的生成再好也没用。有几个方法可以提升检索质量:
调整文本分割策略。不同的文档类型适合不同的分割方式:
# 对于技术文档,可以按章节分割 from langchain.text_splitter import MarkdownHeaderTextSplitter headers_to_split_on = [ ("#", "Header 1"), ("##", "Header 2"), ("###", "Header 3"), ] markdown_splitter = MarkdownHeaderTextSplitter( headers_to_split_on=headers_to_split_on ) # 对于代码文档,可以按函数/类分割 from langchain.text_splitter import Language, RecursiveCharacterTextSplitter python_splitter = RecursiveCharacterTextSplitter.from_language( language=Language.PYTHON, chunk_size=1000, chunk_overlap=100 )使用混合检索。除了向量检索,还可以结合关键词检索:
from langchain.retrievers import BM25Retriever, EnsembleRetriever from langchain.vectorstores import Chroma # 创建BM25检索器(基于关键词) from langchain.retrievers import BM25Retriever bm25_retriever = BM25Retriever.from_documents( documents=text_chunks, k=4 ) # 创建向量检索器 vector_retriever = vectorstore.as_retriever(search_kwargs={"k": 4}) # 组合两个检索器 ensemble_retriever = EnsembleRetriever( retrievers=[bm25_retriever, vector_retriever], weights=[0.4, 0.6] # 可以调整权重 )重排序(Re-ranking)。检索到的文档按相关性重新排序:
from langchain.retrievers import ContextualCompressionRetriever from langchain.retrievers.document_compressors import LLMChainExtractor # 使用LLM提取最相关的部分 compressor = LLMChainExtractor.from_llm(llm) compression_retriever = ContextualCompressionRetriever( base_compressor=compressor, base_retriever=ensemble_retriever )6.2 提升生成质量
检索到的文档质量很重要,但怎么用这些文档生成好答案同样关键。
优化提示词。好的提示词能让模型更好地理解任务:
# 更详细的提示词模板 detailed_prompt_template = """你是一个专业的技术支持助手,请基于提供的上下文信息回答问题。 要求: 1. 答案必须基于上下文,不要添加上下文之外的信息 2. 如果上下文没有相关信息,直接回答"根据提供的文档,没有找到相关信息" 3. 答案要结构清晰,重要信息可以加粗 4. 如果涉及步骤,请用列表形式呈现 5. 如果涉及代码,请确保语法正确 上下文信息: {context} 用户问题:{question} 请按照要求生成专业、准确的回答:""" PROMPT = PromptTemplate( template=detailed_prompt_template, input_variables=["context", "question"] )控制生成参数。调整生成参数可以影响答案的质量:
# 更精细的生成参数 generation_config = { "max_new_tokens": 1500, # 最大生成长度 "temperature": 0.3, # 温度越低,答案越确定 "top_p": 0.85, # 核采样,控制多样性 "do_sample": True, "repetition_penalty": 1.2, # 重复惩罚,避免重复内容 "length_penalty": 1.0, # 长度惩罚,避免过长 }添加引用来源。让答案更有说服力:
def format_answer_with_sources(result): """ 格式化答案,包含引用来源 """ answer = result['result'] sources = result['source_documents'] formatted_answer = f"{answer}\n\n**参考来源:**\n" for i, doc in enumerate(sources): # 提取文档片段的前100字符作为摘要 snippet = doc.page_content[:100].replace('\n', ' ') formatted_answer += f"{i+1}. {snippet}...\n" return formatted_answer6.3 添加对话记忆
现在的系统每次问答都是独立的,没有上下文记忆。对于多轮对话场景,可以添加对话历史:
from langchain.memory import ConversationBufferMemory from langchain.chains import ConversationalRetrievalChain # 创建对话记忆 memory = ConversationBufferMemory( memory_key="chat_history", return_messages=True, output_key='answer' ) # 创建对话式RAG链 conversational_qa = ConversationalRetrievalChain.from_llm( llm=llm, retriever=vectorstore.as_retriever(), memory=memory, combine_docs_chain_kwargs={"prompt": PROMPT}, verbose=True ) # 使用示例 def chat_with_memory(question): result = conversational_qa({"question": question}) print(f"Q: {question}") print(f"A: {result['answer']}") print(f"对话历史:{memory.load_memory_variables({})}") # 连续提问 chat_with_memory("我们的产品支持哪些数据库?") chat_with_memory("其中哪个性能最好?") # 这个问题能利用上一个问题的上下文6.4 支持多格式输出
有时候用户可能需要不同格式的答案,比如列表、表格、JSON等:
def ask_with_format(qa_chain, question, output_format="text"): """ 指定输出格式 """ format_prompts = { "text": "请用段落形式回答", "list": "请用列表形式回答,每个要点用-开头", "table": "请用Markdown表格形式回答", "json": "请用JSON格式回答" } formatted_question = f"{question}\n\n请用{output_format}格式回答。" if output_format in format_prompts: formatted_question = f"{question}\n\n{format_prompts[output_format]}" result = qa_chain({"query": formatted_question}) return result['result'] # 使用示例 question = "列出产品的主要功能" print("文本格式:", ask_with_format(qa_chain, question, "text")) print("\n列表格式:", ask_with_format(qa_chain, question, "list"))6.5 性能监控与优化
系统上线后,需要监控性能,及时发现问题:
import time from collections import defaultdict class QAMonitor: def __init__(self): self.stats = defaultdict(list) def record_query(self, question, answer, retrieval_time, generation_time, sources_count): self.stats['questions'].append(question) self.stats['answers'].append(answer) self.stats['retrieval_times'].append(retrieval_time) self.stats['generation_times'].append(generation_time) self.stats['sources_counts'].append(sources_count) def get_stats(self): return { 'total_queries': len(self.stats['questions']), 'avg_retrieval_time': sum(self.stats['retrieval_times']) / len(self.stats['retrieval_times']), 'avg_generation_time': sum(self.stats['generation_times']) / len(self.stats['generation_times']), 'avg_sources_per_query': sum(self.stats['sources_counts']) / len(self.stats['sources_counts']) } # 在问答函数中添加监控 monitor = QAMonitor() def ask_with_monitoring(qa_chain, question): start_time = time.time() # 检索阶段 retrieval_start = time.time() relevant_docs = vectorstore.similarity_search(question, k=4) retrieval_time = time.time() - retrieval_start # 生成阶段 result = qa_chain({"query": question}) generation_time = time.time() - start_time - retrieval_time # 记录数据 monitor.record_query( question=question, answer=result['result'], retrieval_time=retrieval_time, generation_time=generation_time, sources_count=len(result['source_documents']) ) return result这些优化技巧可以根据实际需求选择使用。刚开始可以先用基础版本,随着使用深入,再逐步添加高级功能。
7. 总结
把这个系统搭起来用了一段时间,感觉确实挺实用的。GLM-4-9B-Chat-1M的长文本处理能力确实强,配合LangChain的RAG框架,能很好地解决企业文档问答的需求。
从技术角度看,这个方案有几个明显的优势。首先是成本可控,全部开源,可以本地部署,数据安全有保障。其次是灵活性高,可以根据自己的需求调整各个环节,比如换不同的嵌入模型、调整检索策略、优化提示词等等。最后是效果不错,基于文档的问答准确率比直接问模型要高很多。
实际用下来,我觉得有几个地方特别值得注意。文档预处理很重要,分割策略直接影响检索效果。提示词设计也很关键,好的提示词能让模型更好地理解任务。还有监控和优化,系统上线后要持续观察效果,根据反馈调整参数。
当然,这个系统也不是万能的。对于特别专业或者需要深度推理的问题,可能还是需要人工介入。而且模型的生成速度受硬件影响比较大,如果文档量特别大,检索时间也会增加。
不过总的来说,对于大多数企业的知识库问答需求,这个方案已经足够用了。特别是那些文档量大、查询频繁的场景,能节省很多时间和人力。
如果你也想搭建类似的系统,建议先从小的文档集开始,跑通整个流程,然后再逐步扩大规模。遇到问题多查查LangChain的文档和社区,很多常见问题都有现成的解决方案。
技术总是在进步的,现在GLM-4-9B-Chat-1M能处理200万字,说不定明年就有能处理2000万字的模型了。但不管模型怎么变,RAG的基本思路不会变——让模型基于准确的信息来生成答案,而不是凭空想象。这个方向是对的,也是未来AI应用发展的趋势。
获取更多AI镜像
想探索更多AI镜像和应用场景?访问 CSDN星图镜像广场,提供丰富的预置镜像,覆盖大模型推理、图像生成、视频生成、模型微调等多个领域,支持一键部署。