实战:30分钟搭建你的智能文档问答助手——LangChain + OpenAI全攻略
今天我们来聊一个非常实用的话题:如何用AI技术快速搭建一个能理解你公司文档、回答你专业问题的智能助手。
想象一下:公司新员工入职,面对几十个G的培训文档、技术手册、项目文档,如何快速找到需要的信息?或者你是开发者,面对数百篇API文档和教程,如何快速定位解决方案?今天,我们就用LangChain和OpenAI,手把手教你搭建这样一个智能文档问答助手。
在信息爆炸的时代,我们面临的最大问题往往不是信息太少,而是信息太多。传统的搜索方式(如关键词匹配)在理解自然语言查询、把握上下文意图方面存在明显短板。而基于大语言模型(LLM)的问答系统,能够真正理解你的问题,从文档中提取相关信息,并给出精准回答。
今天我们要搭建的系统,专业术语叫做RAG(检索增强生成)系统。简单来说,它的工作流程就像一位聪明的图书管理员:
1. 检索:当你有问题时,它先在文档库中查找相关段落2. 增强:将找到的相关文档片段与你的问题组合3. 生成:让AI基于”问题+相关文档”生成准确答案
这样既保证了回答的专业性(基于你的实际文档),又避免了AI”胡说八道”(幻觉问题)。
让我们把技术原理讲透,但不堆砌术语。RAG系统的核心是三个步骤:
你的文档可能是PDF、Word、Excel、网页等各种格式,AI不能直接”阅读”。我们需要:
– 文本提取:从各种格式中提取纯文本– 文本分割:把长文档切成适合处理的小片段(通常200-500字)– 向量化:把文本转换成AI能理解的”数字向量”
这里的”向量”可以理解为文本的”数字指纹”。相似的文本会有相似的向量。
– 系统把你的问题也转换成向量– 在向量数据库中搜索与问题向量最相似的文档向量– 返回最相关的几个文档片段
这就像在图书馆的目录卡中,快速找到与问题主题最相关的书籍章节。
系统将”你的问题”和”检索到的相关文档”一起喂给大语言模型(如GPT),让它基于这些信息生成回答。这样:
– 回答基于你的实际文档,准确性高– AI不会凭空编造信息– 可以追溯答案来源(知道答案来自哪个文档)
首先确保你的Python环境是3.8+版本。我们创建一个新的虚拟环境:
# 创建并激活虚拟环境(可选但推荐)python -m venv rag_envsource rag_env/bin/activate # Linux/Mac# 或 rag_env\Scripts\activate # Windows# 安装核心依赖pip install langchain openai chromadb tiktoken pypdf python-docx
– langchain:我们的主力框架,简化AI应用开发– openai:调用GPT等模型的接口– chromadb:轻量级向量数据库– tiktoken:计算token数量(控制成本)– pypdf和python-docx:读取PDF和Word文档
我们创建一个Python脚本,假设你的文档放在`documents/`文件夹下:
import osfromlangchain_community.document_loadersimport(PyPDFLoader,DocxLoader,TextLoader,DirectoryLoader)fromlangchain.text_splitterimportRecursiveCharacterTextSplitter# 1. 加载文档defload_documents(directory_path):“””加载指定目录下的所有文档”””loaders = {‘.pdf’: PyPDFLoader,‘.docx’: DocxLoader,‘.txt’: TextLoader}documents = []forfilenameinos.listdir(directory_path):file_path = os.path.join(directory_path, filename)ext = os.path.splitext(filename)[1].lower()ifextinloaders:try:print(f”正在加载: {filename}”)loader = loaders[ext](file_path)loaded_docs = loader.load()documents.extend(loaded_docs)exceptExceptionase:print(f”加载 {filename} 时出错: {e}”)print(f”共加载 {len(documents)} 个文档片段”)returndocuments# 2. 文本分割defsplit_documents(documents):“””将文档分割成适合处理的小片段”””text_splitter = RecursiveCharacterTextSplitter(chunk_size=500,# 每个片段500字符chunk_overlap=50,# 片段间重叠50字符(保持上下文)separators=[“\n\n”,“\n”,“。”,“?”,“!”,” “,“”]# 中文友好的分隔符)split_docs = text_splitter.split_documents(documents)print(f”分割后得到 {len(split_docs)} 个文本片段”)returnsplit_docs# 执行加载和分割documents = load_documents(“documents/”)split_docs = split_documents(documents)
from langchain_openaiimportOpenAIEmbeddingsfromlangchain_community.vectorstoresimportChromafromlangchain_openaiimportChatOpenAIimportos# 设置OpenAI API Key(请替换成你的真实key)os.environ[“OPENAI_API_KEY”] =“你的-openai-api-key”# 1. 初始化嵌入模型(用于生成向量)embeddings = OpenAIEmbeddings(model=“text-embedding-3-small”,# 性价比高的嵌入模型chunk_size=1000)# 2. 创建向量数据库defcreate_vector_store(documents, persist_directory=“./chroma_db”):“””将文档转换为向量并存储到数据库”””# 如果已有数据库,直接加载ifos.path.exists(persist_directory):print(“检测到已有向量数据库,正在加载…”)vector_store = Chroma(persist_directory=persist_directory,embedding_function=embeddings)else:print(“正在创建新的向量数据库…”)vector_store = Chroma.from_documents(documents=documents,embedding=embeddings,persist_directory=persist_directory)vector_store.persist()# 保存到磁盘print(f”向量数据库创建完成,共 {vector_store._collection.count()} 条记录”)returnvector_store# 执行创建vector_store = create_vector_store(split_docs)
from langchain.chainsimportRetrievalQAfromlangchain.promptsimportPromptTemplate# 1. 初始化大语言模型llm = ChatOpenAI(model=“gpt-3.5-turbo”,# 平衡性能与成本temperature=0.3,# 较低的温度,回答更稳定max_tokens=1000#限制回答长度)# 2. 自定义提示模板(让AI回答更符合我们的需求)prompt_template =“””你是一个专业的文档问答助手,请基于以下文档内容回答问题。相关文档内容:{context}问题:{question}要求:1. 基于提供的文档内容回答问题,不要编造文档中没有的信息2. 如果文档中没有相关信息,请如实告知”根据提供的文档,无法找到相关信息”3. 回答要简洁明了,重点突出4. 如果可能,指出答案参考了哪些文档片段请用中文回答:“””PROMPT = PromptTemplate(template=prompt_template,input_variables=[“context”,“question”])# 3. 创建问答链qa_chain = RetrievalQA.from_chain_type(llm=llm,chain_type=“stuff”,# 简单高效的方式retriever=vector_store.as_retriever(search_kwargs={“k”: 3}# 每次检索3个最相关片段),chain_type_kwargs={“prompt”: PROMPT},return_source_documents=True# 返回来源文档)print(“问答系统构建完成!”)
# 测试函数defask_question(question):“””向系统提问并获取回答”””print(f”\n你的问题:{question}”)print(“-” * 40)result = qa_chain({“query”: question})# 打印答案print(f”🤖 AI回答:\n{result[‘result’]}”)# 显示来源文档(可选)print(f”\n📚 参考来源:”)fori, docinenumerate(result[‘source_documents’][:2]):# 只显示前2个来源print(f”来源{i+1}: {doc.page_content[:150]}…”)returnresult# 测试几个问题test_questions = [“我们公司的请假流程是什么?”,“项目的技术架构是怎样的?”,“如何申请服务器资源?”]forqintest_questions:ask_question(q)
系统工作流程:1. 在向量库中搜索与”新员工入职培训”最相关的文档片段2找到《员工手册》中关于入职培训的章节(2个相关片段)3将问题+相关片段发送给GPT4GPT生成基于实际文档的回答
“根据公司《员工手册》第三章规定,新员工入职需完成以下培训:1. 公司文化培训(第1周):了解公司发展历程、价值观2. 岗位技能培训(第2-3周):由直属主管指导的具体工作技能3. 安全规范培训(第1周):办公室安全、信息安全4. 系统使用培训(第1周):OA系统、内部协作工具使用建议在入职后第一个月内完成所有培训,并向HR部门提交培训反馈表。”
# 使用更复杂的检索策略retriever = vector_store.as_retriever(search_type=“mmr”, # 最大边际相关性,避免重复内容search_kwargs={“k”: 5,“fetch_k”: 20,“lambda_mult”: 0.7 # 平衡相关性与多样性})
from langchain.memoryimportConversationBufferMemoryfromlangchain.chainsimportConversationalRetrievalChainmemory = ConversationBufferMemory(memory_key=“chat_history”,return_messages=True)conversational_chain = ConversationalRetrievalChain.from_llm(llm=llm,retriever=vector_store.as_retriever(),memory=memory)
可以添加对Excel、PPT、网页、Markdown等格式的支持。
使用Streamlit或Gradio快速搭建一个Web界面让团队成员都能使用。
使用AI服务时,成本是需要考虑的因素。以下是实用建议:
-
文本嵌入:
text-embedding-3-small(性价比高)
-
对话生成:
gpt-3.5-turbo(日常问答足够)
# 计算每次问答的token数import tiktokendefcount_tokens(text):encoding = tiktoken.get_encoding(“cl100k_base”)return len(encoding.encode(text))
A:即使是10-20篇文档,系统也能明显提升信息查找效率。文档越多,系统越”聪明”。
A:文档内容确实会发送到OpenAI服务器。如果是敏感信息,可以考虑:1. 使用本地模型(如开源LLM)2. 使用Azure OpenAI等企业级服务(具有数据保护协议)3. 对文档进行脱敏处理
A:第一次构建向量库较慢取决于文档量,后续检索和生成回答通常在3-8秒内。
A:可以从三个维度评估:1. 准确性:回答是否基于文档,有无编造2. 相关性:是否回答了问题的核心3. 实用性:回答是否有实际指导价值
今天我们一起完成了一个实用的AI项目。从原理到代码,我希望不仅给了你一个可运行的系统,更重要的是让你理解了背后的技术逻辑。
AI技术正在从”炫技”走向”实用”,像我们今天搭建的这种文档问答系统,在很多场景下已经能实实在在提升效率。特别是对于技术团队、知识密集型公司,这种工具的价值尤为明显。
技术不是目的,解决问题才是。这套系统你可以:
如果你在实施过程中遇到问题,或者有新的想法想要尝试,随时可以讨论。技术之路,我们一起成长。
下期预告:如何用微调(Fine-tuning)让AI更懂你的业务语言?我们将深入探讨大模型个性化定制技术。
相关资源:
注意事项:
希望今天的内容对你有帮助!如果你搭建成功了,或者有任何改进建议,欢迎分享你的经验。🚀