ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

基于RAG与Milvus构建《天龙八部》AI知识库:从文本处理到智能问答实战

基于RAG与Milvus构建《天龙八部》AI知识库:从文本处理到智能问答实战 1. 项目概述当武侠经典遇上AI知识库最近在折腾AI应用开发总想找点有趣又有挑战性的场景来练手。那天晚上重温《天龙八部》看到段誉在少室山大战中凌波微步、六脉神剑齐出的名场面一个念头突然冒出来如果我把整部《天龙八部》的文本“喂”给一个大语言模型然后像问一个资深金庸迷一样去问它“段誉到底会哪些武功”AI能给出一个准确、完整甚至超出预期的答案吗这个想法让我立刻来了精神。这不仅仅是一个简单的问答它背后涉及的是当前AI应用开发的一个核心范式——RAG检索增强生成以及如何让AI真正“理解”并“记住”一本几十万字的小说中所有零散、交织的细节。传统的AI对话模型依赖的是其训练时学到的通用知识。你问它“段誉会什么武功”它或许能基于常见的金庸知识给出“六脉神剑、凌波微步、北冥神功”这几个答案。但这远远不够。段誉的武功习得过程充满了偶然和细节他是在哪里无意中学会“凌波微步”和“北冥神功”的他的“六脉神剑”时灵时不灵的原因是什么他后期还从鸠摩智那里被动吸收了小无相功的内力吗这些深度的、上下文相关的信息是通用大模型难以精确掌握的。我的目标就是构建一个专属于《天龙八部》的“AI书童”。它不仅能回答关于人物、武功、情节的基础问题更能进行深度推理比如“段誉的武功在小说后期为什么突然稳定了”或者“比较一下段誉和虚竹的武功成长路径”。要实现这个关键就在于RAG首先让AI拥有一个关于这部小说的、可精确检索的“记忆库”向量数据库然后在回答问题时先从记忆库中找到最相关的原文片段作为依据再生成答案。这样答案就不再是模型凭模糊印象“编造”的而是有原文支撑的、可靠的。接下来我就详细拆解这个从“一个念头”到“一个能惊呆我的答案”的全过程。2. 技术架构与核心组件选型构建一个能“读懂”整本小说的RAG系统不是简单地把文本丢给模型就完事了。它需要一个清晰、健壮的架构将数据处理、存储、检索和生成等多个环节串联起来。我的核心架构可以概括为文档处理 - 向量化与索引 - 智能检索 - 增强生成。每一个环节的组件选型都直接决定了最终问答的准确性和效率。2.1 文档接入与预处理从EPUB到纯净文本我手头的《天龙八部》是EPUB格式这是一种常见的电子书格式本质是一个压缩包里面包含了HTML文件、样式表和图片。第一步就是把它“拆开”提取出我们需要的纯文本内容。工具选型我选择了Python的ebooklib和BeautifulSoup库。ebooklib专门用于解析EPUB文件的结构能轻松地遍历书籍中的所有章节即HTML文件。BeautifulSoup则是HTML解析的神器能帮我把HTML标签如p,div,h1中的文本内容干净地提取出来过滤掉无关的样式代码和脚本。预处理的核心——文本切片Chunking这是RAG系统中至关重要且容易被忽视的一步。你不能把整本上百万字的小说作为一个整体丢给模型去检索那样效率极低且检索出的上下文可能过于庞大和杂乱。必须进行切片将长文本切割成大小适中、语义相对完整的片段。切片策略的考量我尝试了多种策略。按固定字符数如512个字符切割最简单但很容易在一句话中间或者一个关键情节描述中断开破坏语义。最终我选择了基于语义的滑动窗口切片法。具体操作我使用langchain的RecursiveCharacterTextSplitter并进行了参数调优。设置chunk_size500每个切片大约500字符chunk_overlap100相邻切片有100字符的重叠。这个重叠是关键它能防止一个完整的句子或一个关键信息比如一个武功的名字刚好被切在两个片段的边界而丢失。同时我强制以句号、换行等自然语言边界作为优先切割点尽可能保证每个切片在语义上的完整性。例如关于“段誉在琅嬛福地学会北冥神功和凌波微步”这一整段描述会被尽量保持在一个切片内。清洗工作提取的文本常包含多余的空格、换行符、以及电子书中常见的“上一章”、“下一章”等导航文字。我用正则表达式进行了清洗确保喂给后续步骤的是干净、连贯的小说正文。2.2 向量数据库选型为什么是Milvus文本切片后我们需要一种方式让计算机能快速地从海量片段中找到与问题最相关的那些。这就是向量数据库的用武之地。它将每一段文本通过嵌入模型Embedding Model转换成一个高维度的向量一组数字这个向量在某种程度上代表了这段文本的“语义”。相似语义的文本其向量在空间中的距离也更近。选型对比市面上常见的向量数据库有Chroma轻量、易用、FAISSFacebook出品性能强劲的库、PGvectorPostgreSQL的扩展与关系数据库结合紧密以及Milvus专为向量搜索设计的云原生数据库。Chroma非常适合快速原型验证内存模式一键启动。但对于百万级甚至更高维度的向量以及未来可能扩展的多模态检索其功能和性能上限可能成为瓶颈。FAISS它是一个优秀的库但本身不是数据库。你需要自己处理向量数据的持久化、版本管理和服务化部署增加了工程复杂度。PGvector最大优势是与现有PostgreSQL生态无缝集成如果你的业务数据本就存在PG中加入向量检索非常方便。但其纯向量搜索的性能优化相比专业向量数据库仍有差距。Milvus我最终选择了它。原因在于1.专业性能专为大规模向量检索设计支持多种索引类型如IVF_FLAT, HNSW能轻松应对千万级向量的毫秒级查询。2.生产级特性支持数据持久化、高可用、分布式部署满足了从实验到生产的需求。3.丰富的生态与LangChain、LlamaIndex等主流AI框架集成良好API调用方便。4.活跃社区遇到问题容易找到解决方案。Milvus在Windows下的部署很多人认为Milvus只能在Linux下运行其实不然。最简便的方式是使用Docker。在Windows上安装Docker Desktop后一条命令即可拉取并启动Milvus单机版容器它包含了所需的Etcd和MinIO服务。这避免了复杂的本地编译和环境配置让我在十分钟内就搭建好了测试环境。2.3 嵌入模型与检索策略让AI理解“武功”和“人物”选择了存储的“仓库”Milvus下一步是决定如何把文本变成向量嵌入模型以及如何设计检索逻辑。嵌入模型选择我使用了text-embedding-ada-002通过OpenAI API作为生产级选择它在语义表示的质量和效率上取得了很好的平衡。对于完全本地的方案BAAI/bge-small-zh或m3e-base这类开源的中文嵌入模型也是极佳的选择它们对中文语义的理解尤其是对古风小说中的词汇如“内力”、“穴道”、“招式”有不错的表征能力。索引构建在Milvus中创建集合Collection时我根据嵌入向量的维度如1536定义了字段并选择了HNSWHierarchical Navigable Small World索引。HNSW图索引在查询速度和召回率之间提供了一个很好的权衡特别适合我们的交互式问答场景需要快速返回结果。混合检索策略这是提升答案相关性的“秘密武器”。单纯的向量检索语义搜索有时会漏掉一些关键词完全匹配但表述不同的重要片段。因此我实现了“混合检索”。向量检索语义相似性将用户问题“段誉会什么武功”也转化为向量在Milvus中查找与之余弦相似度最高的前k个文本片段。关键词检索稀疏检索同时使用如BM25等传统算法在文本片段中直接搜索“段誉”、“武功”、“六脉神剑”等关键词。重排序Rerank将两种检索方式得到的结果池合并然后使用一个更精细但计算量也更大的重排序模型如BAAI/bge-reranker-base对结果进行再次打分和排序选出最相关、最可靠的几个片段作为最终提供给大模型的上下文。这个策略确保了即使问题表述比较模糊例如“那个大理段氏公子的招牌功夫有哪些”系统也能通过语义搜索找到相关段落同时对于明确的关键词也能通过关键词检索精准定位。3. 核心实现步骤与代码解析理论架构清晰后我们进入动手实操环节。我将以Python为核心结合LangChain框架来串联整个流程因为它提供了大量现成的组件能极大提升开发效率。3.1 环境准备与依赖安装首先创建一个干净的Python虚拟环境是好习惯。核心的依赖库如下pip install langchain langchain-openai langchain-community ebooklib beautifulsoup4 pymilvuslangchain: 核心框架用于组织RAG流水线。langchain-openai: 用于调用OpenAI的嵌入模型和生成模型。langchain-community: 包含许多社区贡献的组件如文档加载器。ebooklibbeautifulsoup4: 用于解析EPUB文件。pymilvus: Milvus的Python客户端。如果你使用本地嵌入模型可能还需要安装sentence-transformers或flag-embedding库。3.2 文档加载与文本切片实现我编写了一个自定义的EPUB加载器因为LangChain自带的可能无法完美处理某些中文EPUB的格式。from ebooklib import epub from bs4 import BeautifulSoup from langchain.schema import Document from typing import List class CustomEPUBLoader: def __init__(self, file_path: str): self.file_path file_path def load(self) - List[Document]: book epub.read_epub(self.file_path) documents [] for item in book.get_items(): # 通常章节内容类型是9 (XHTML) if item.get_type() 9: # epub.ITEM_DOCUMENT soup BeautifulSoup(item.get_content(), html.parser) text soup.get_text() # 简单的清洗 text .join(text.split()) if len(text) 100: # 过滤掉过短的页面可能是封面、目录 # 将每个章节或大片段作为一个Document对象 # 这里可以更精细地按章节标题分割本例简化处理 doc Document(page_contenttext, metadata{source: item.get_name()}) documents.append(doc) return documents # 使用加载器 loader CustomEPUBLoader(tianlongbabu.epub) raw_documents loader.load() print(f加载了 {len(raw_documents)} 个文档片段)接下来使用LangChain的文本分割器进行切片from langchain.text_splitter import RecursiveCharacterTextSplitter text_splitter RecursiveCharacterTextSplitter( chunk_size500, chunk_overlap100, length_functionlen, separators[\n\n, \n, 。, , , , , 、, ] ) split_documents text_splitter.split_documents(raw_documents) print(f切片后得到 {len(split_documents)} 个文本块)3.3 向量化与存入Milvus这里我使用OpenAI的嵌入模型你需要设置自己的OPENAI_API_KEY。from langchain.embeddings import OpenAIEmbeddings from langchain.vectorstores import Milvus # 1. 初始化嵌入模型 embeddings OpenAIEmbeddings(modeltext-embedding-ada-002, openai_api_keyyour-api-key) # 2. 连接Milvus并创建向量库 # 确保你的Milvus服务已经运行例如通过Docker vector_store Milvus.from_documents( documentssplit_documents, embeddingembeddings, connection_args{host: localhost, port: 19530}, # Milvus默认端口 collection_nametianlong_rag, # 集合名称 drop_oldTrue # 如果集合已存在先删除仅用于实验 ) print(文档向量化并成功存入Milvus)这个过程会将每个文本切片通过text-embedding-ada-002模型转化为向量并连同原文和元数据一起存储到名为tianlong_rag的Milvus集合中。3.4 构建检索问答链这是系统的“大脑”它将检索器和大语言模型组合起来。from langchain.chains import RetrievalQA from langchain_openai import ChatOpenAI # 1. 从已存在的集合创建检索器 vector_store Milvus( embedding_functionembeddings, connection_args{host: localhost, port: 19530}, collection_nametianlong_rag ) retriever vector_store.as_retriever(search_kwargs{k: 5}) # 检索最相关的5个片段 # 2. 初始化大语言模型这里用GPT-3.5-turbo也可用其他或本地模型 llm ChatOpenAI(model_namegpt-3.5-turbo, temperature0.1, openai_api_keyyour-api-key) # temperature调低让答案更确定、更基于上下文 # 3. 创建检索问答链 qa_chain RetrievalQA.from_chain_type( llmllm, chain_typestuff, # 将检索到的所有上下文“塞”进提示词 retrieverretriever, return_source_documentsTrue, # 非常重要返回源文档用于验证 chain_type_kwargs{prompt: PROMPT} # 可以使用自定义提示词模板 ) # 一个简单的自定义提示词模板用于指导模型基于上下文回答 from langchain.prompts import PromptTemplate template 请严格根据以下提供的上下文信息来回答问题。如果上下文没有提供足够的信息请直接说“根据已知信息无法回答此问题”不要编造答案。 上下文 {context} 问题{question} 基于上下文的答案 PROMPT PromptTemplate(templatetemplate, input_variables[context, question])3.5 发起查询与解析结果现在激动人心的时刻到了让我们向这个专属的“天龙AI”提问。question 段誉在《天龙八部》中都学会了哪些武功请详细列出并说明他是如何学会的。 result qa_chain.invoke({query: question}) print(问题, question) print(\n AI生成的答案 ) print(result[result]) print(\n 支持答案的原文来源检索到的片段) for i, doc in enumerate(result[source_documents]): print(f\n--- 片段 {i1} ---) print(doc.page_content[:300] ...) # 打印前300字符 print(f来源: {doc.metadata.get(source, N/A)})4. 效果评估、问题排查与优化心得当我第一次运行上述代码并提出“段誉会什么武功”这个问题时屏幕上滚动的答案确实让我有点“惊呆”了。它不仅仅是罗列了“北冥神功、凌波微步、六脉神剑”这三个名字。4.1 令人惊喜的答案深度AI生成的答案大致如下经过整理 “根据《天龙八部》原文段誉掌握的武功主要包括北冥神功他在无量山琅嬛福地的洞中无意间叩首千遍后从蒲团中得到帛卷学习了其中‘手太阴肺经’的吸纳法门。此功可吸取他人内力为己用但段誉并未学全。凌波微步与北冥神功同载于帛卷之上是一门极上乘的轻功步法按《周易》六十四卦方位行走。段誉借此多次在险境中脱身。六脉神剑大理段氏至高无上的绝学以浑厚内力化于指端发出无形剑气。段誉在天龙寺因身具深厚内力部分来自北冥神功所吸而被枯荣大师等人选中观看剑谱后学会。但其运用时灵时不灵直至小说后期内力收发自如后才得心应手。 此外段誉还因意外吸收了众多高手的内力如黄眉僧、段延庆、鸠摩智部分内力等使其内力震古烁今为施展六脉神剑奠定了基础。他并未系统学习其他拳脚招式对敌主要依赖凌波微步闪避和六脉神剑远程攻击。”这个答案的深度在于它不仅列出了武功名称还准确指出了习得地点、方式、特点甚至缺陷并且理清了武功之间的关联内力是六脉神剑的基础。这完全超越了一个普通读者凭记忆的概括更像是一个做了详细读书笔记的学者给出的回答。这正是RAG的价值体现——答案严格基于我“喂”给它的文本没有幻觉Hallucination细节丰满。4.2 实操中遇到的典型问题与解决方案在实现过程中我踩过不少坑这里分享出来希望能帮你避开。问题1检索结果不相关答案胡言乱语。现象问“段誉的武功”却返回了关于“乔峰降龙十八掌”或“少林寺七十二绝技”的片段导致AI生成的答案混淆了人物。排查与解决检查切片大小chunk_size可能太大比如2000一个片段里包含了多个人物的信息。调小到500-800并确保chunk_overlap设置合理。优化检索策略启用前面提到的混合检索。单纯向量搜索可能在某些语义关联上出现偏差。加入关键词检索BM25能强制包含问题中的实体如“段誉”。调整检索数量ksearch_kwargs{“k”: 5}中的k值。太小可能遗漏关键信息太大会引入噪声。通常4-8是一个不错的起点。可以观察source_documents如果前两个就不相关说明检索有问题如果第三个之后才开始不相关可以适当调小k。检查嵌入模型对于中文小说尝试更换更适合中文的嵌入模型如BAAI/bge-small-zh可能比通用英文模型对中文人名、武功名有更好的向量表示。问题2答案过于简略缺乏细节。现象答案只说了“会北冥神功、凌波微步和六脉神剑”没有过程描述。排查与解决优化提示词Prompt这是最关键的一步。默认的提示词可能只是让模型“回答问题”。你需要更明确地指令它。例如在我的自定义PROMPT中我加入了“请详细列出并说明他是如何学会的”这样的要求。可以进一步强化“请根据上下文详细描述每一项武功的习得过程、关键特点、在书中的主要应用场景。”检查上下文是否充足查看source_documents看检索到的片段是否本身就包含了细节。如果没有可能需要重新审视切片策略确保描述性段落没有被切碎。问题3Milvus连接失败或插入数据慢。现象Connection refused或插入上万条向量时耗时极长。排查确认服务状态运行docker ps确保Milvus的容器以及其依赖的etcd、minio都在运行。检查端口确认连接参数中的端口默认19530是否正确。批量插入LangChain的from_documents方法内部通常是批量插入的。如果自己实现确保使用Milvus的批量插入接口而不是一条条插入。索引创建时机对于大规模数据可以先插入数据再创建索引速度会更快。from_documents方法通常会自动处理。4.3 性能与效果优化技巧分阶段测试不要一次性处理整本书。先用一个章节如“第一章 青衫磊落险峰行”进行全流程测试从加载、切片、向量化、检索到问答确保每个环节都工作正常答案符合预期。这能极大节省调试时间。重视源文档查看始终开启return_source_documentsTrue。这是调试RAG系统的黄金法则。任何离谱的答案首先去检查它到底“看”了哪些原文。你能立刻判断是检索错了还是模型理解错了。尝试不同的Chain Type在RetrievalQA.from_chain_type中我用了“stuff”它把所有检索到的上下文拼接起来传给模型。对于非常长的上下文可能会超出模型令牌限制。可以尝试“map_reduce”或“refine”它们以不同的方式处理长上下文可能对复杂推理问题有奇效。后处理与引用为了让答案更可信可以在生成答案后让模型顺便标出答案中每一句话来源于哪个源文档片段甚至页码。这需要更复杂的提示工程但能做出类似“AI读书笔记”的效果。本地化部署考量如果希望完全私有化可以用ChatGLM3、Qwen或Llama系列模型作为LLM用BGE或M3E作为嵌入模型配合Milvus搭建一个完全离线的RAG系统。这需要更强的本地算力GPU但数据安全和定制化程度最高。5. 项目总结与扩展思考通过这个把《天龙八部》“喂”给AI的项目我深刻体会到RAG技术如何将大语言模型的“泛化脑”和向量数据库的“专业记忆”完美结合。它解决的正是大模型“知其然不知其所以然”缺乏特定领域细节和“胡编乱造”幻觉的核心痛点。这个项目不仅仅是一个有趣的实验它提供了一个可复现的范式适用于任何需要基于特定、复杂、长文本进行智能问答的场景。个人实操中的核心体会是RAG系统的效果三分靠模型七分靠工程。文档预处理特别是切片策略、检索策略混合检索重排序和提示词工程往往比单纯换一个更强大的LLM更能提升最终答案的质量。你需要像一个侦探一样仔细检查检索系统提供的“证据”源文档不断调整你的“侦查手段”切片、检索参数才能让AI这个“推理专家”做出最准确的判断。这个项目还有很多可以扩展的方向。例如多模态RAG除了文本能否把电视剧版《天龙八部》的经典画面或人物定妆照也作为知识库的一部分当问及“段誉的凌波微步是什么样子的”系统能否同时返回文字描述和相关剧照这需要多模态嵌入模型和向量数据库的支持。角色扮演与对话基于这个知识库可以构建一个“段誉AI”或者“王语嫣AI”的聊天机器人让其回答不仅基于事实还能模仿人物的语言风格和性格。复杂推理与关系查询通过优化检索和提示让系统能够回答更复杂的问题比如“如果段誉和乔峰在少林寺时期比武谁会赢请基于书中两人的武功描述进行分析。”这需要系统能检索并综合比较多个分散的段落。最终当我看到AI条理清晰地复述出段誉在琅嬛福地的奇遇、六脉神剑的时灵时不灵、以及其内力积累的完整过程时那种感觉就像亲手赋予了一堆代码“深度阅读”和理解一部文学经典的能力。技术不再是冰冷的它成为了我们与人类文化遗产进行新颖对话的桥梁。这或许就是开发者最快乐的时刻。
返回列表