ARTICLE DETAIL

建站实战干货

来自一线的建站与推广经验沉淀,每一条都经过真实交付验证。

正本清源:原生RAG入门案例拆解(企业规章制度问答)+ 技术栈全景

2026/9/3 9:07:43 拓冰建站 浏览量
正本清源:原生RAG入门案例拆解(企业规章制度问答)+ 技术栈全景 最近知识星球中新加入的一些新手盆友又集中问到了如何快速入门 RAG 的问题。我早在今年三月初就在星球中做过相关答疑回复。简单来说从个人 24 年年初开始一路的实践踩坑经验来看通过原生开发的方式进行入门无疑是种更加务实的学习路线。这篇结合最近写的几篇书稿的章节内容试图说清楚RAG 技术栈生态、框架与平台选型策略、企业规章制度问答场景拆解、原生 RAG 问答系统的完整技术实现以及工程经验和架构演进梳理。1、RAG 技术栈全景RAG 的技术生态并非一个扁平的技术集合而是一个可以自下而上理解的多层次结构。每一层提供了不同程度的抽象和封装以满足开发者在不同场景下对开发效率、灵活性和系统性能的权衡。需要说明的是下述划分中的层级间并非严格的单向依赖关系跨层组合使用很常见层级更多是功能分工。此外某些工具具有跨层特性可能同时承担多个层级的功能。不同层次的工具和平台相辅相成共同构成了从开发到应用的完整技术体系。理解这个分层架构是做出明智技术选型的第一步。1.1、基础层 ((Foundation Layer)提供关键的编程环境和科学计算支持可为所有上层应用的开发、训练和推理提供最基础的计算能力。代表技术有 Python主流开发语言、NumPy数值计算库、PyTorch深度学习框架等。1.2、组件层 (Components Layer)由实现单一具体功能的底层库组成是构建 RAG 工作流程的基础组件。代表技术PyPDF2PDF 内容提取、Transformers模型库、FAISS向量检索等。可直接调用这些库以最大灵活度手动实现 RAG 流程的每个步骤。1.3、工具层 (Tools Layer)该层级提供针对 RAG 特定环节的专业化、生产级解决方案产品化程度更高。代表技术Pinecone生产级向量数据库、MinerU复杂文档解析、RagasRAG 应用评估等。1.4、框架层 (Framework Layer)该层级是高度封装的集成框架提供标准化接口来串联和管理底层组件与工具。代表技术LangChain、LlamaIndex、Haystack 等。可以像组合积木一样快速搭建和切换不同的 RAG 配置在开发效率和灵活性间取得平衡。1.5、应用层 (Application Layer)该层级抽象层次最高以可视化界面和开箱即用平台的形式存在。代表技术RAGFlow、Dify、Flowise 等。将技术实现细节完全封装通过拖拽和配置即可快速构建完整的 RAG 应用极大降低技术门槛。但同样不好之处在于对新手是个黑箱实际调试过程会相对麻烦。2、框架与平台选型策略技术选型的本质在于在多重约束条件下寻找最适合的平衡点。从组件层理解原理到框架层快速迭代再到工具层优化关键环节的渐进式演进路径已经是RAG过去大半年一线从业者逐步形成的共识。无论选择哪种技术栈关键在于明确当前阶段的核心目标并为未来的技术演进预留足够的灵活性。3、制度问答场景拆解企业规章制度类文档以下简称“制度文档”天然具有格式异构性存储格式通常是由制度文档的性质和用途决定。例如为了保证格式的统一性和内容不可篡改正式发布的《员工手册》通常是以 PDF 格式归档而对于需要内部流转与频繁修订的《差旅报销细则》则一般是多以 DOCX 格式存在。一言以蔽之这种也算是个典型的多源异构的文档问答场景。是 RAG 问答比较适合先去落地解决的问题。在正式开始介绍技术实现前先给各位大致看下后续问答测试所使用的两份文档。这两份文档也是今年 2 月底我最早一批做的项目中的原始材料已脱敏。员工手册.pdf 文档中包括了人力资源管理的各个方面包括双通道职级体系专业序列 P1-P6管理序列 M1-M5、工作时间考勤、假期管理、薪酬福利和绩效评估等制度。差旅报销细则.docx 文档中包括基于职级和城市等级一、二、三线的差旅费用标准体系包括交通工具选择权限、住宿费用上限和餐饮通讯补贴标准。后续技术实现部分的两个问题测试也试图从实际场景出发分别检验系统在单文档信息定位和跨文档信息整合场景方面的表现。4、技术实现4.1、核心架构以下按照四个核心模块展开全局配置与参数管理、文档解析与结构化分块、向量化与索引存储系统、检索与生成核心引擎。4.2、全局配置与参数管理config.py 作为系统的配置中心采用集中化管理策略实现配置与业务逻辑的解耦确保系统的可维护性和灵活性。系统支持本地 Ollama 和在线 SiliconFlow 两种部署模式通过环境变量和模型标识符进行区分其核心配置代码如下。# API密钥配置 SILICONFLOW_API_KEY os.getenv(SILICONFLOW_API_KEY) # 本地模式配置 LOCAL_EMBEDDING_MODEL BAAI/bge-small-zh-v1.5 LOCAL_LLM_MODEL qwen3:30b # 根据配置进行调整 OLLAMA_BASE_URL http://localhost:11434 # 在线模式配置 ONLINE_EMBEDDING_MODEL Qwen/Qwen3-Embedding-8B ONLINE_LLM_MODEL deepseek-ai/DeepSeek-R1 SILICONFLOW_BASE_URL https://api.siliconflow.cn/v1注Ollma 上的问答模型中在中等尺寸模型中 Qwen3:30B 效果较为出色有条件的可以切换成这个模型进行测试。配置有限也可换成 8b 尺寸。直接影响检索效果和问答质量的关键参数在本系统中的具体设定如下。文本分块配置CHUNK_SIZE 600 # 文本块目标大小 CHUNK_OVERLAP 120 # 相邻块重叠长度检索调优配置DEFAULT_RETRIEVAL_K 5 # 检索返回数量 DEFAULT_RETRIEVAL_THRESHOLD 0.4 # 相似度过滤阈值这些参数的设定遵循以下原则CHUNK_SIZE 设为 600 字符既保证了语义的完整性又控制了向量化的计算开销CHUNK_OVERLAP 设为 120 字符约 20%重叠率有效防止重要信息在块边界处被切断检索参数 K5 和阈值 0.4 的组合在保证召回质量的同时控制了上下文长度。通过这种集中化配置策略后续各模块只需引入相应参数即可大幅提升了系统的可配置性和维护效率。4.3、文档解析与结构化分块这个模块采用混合分块方案在语义分块的基础上结合递归字符分块形成“先语义预分块后递归精细切分”的两阶段处理策略。具体实现由 document_parser.py 和 text_chunker.py 两个脚本协同完成。前者负责基于文档结构特征的语义预分块后者则采用递归字符分块算法进行精细化处理。语义预分块PDF 文档处理采用 PyMuPDF 库进行文本提取通过正则表达式识别章节标题模式实现按章节的结构化分割。该策略在 _load_pdf_document 函数中的核心代码实现如下。def _load_pdf_document(file_path: str) - list[LangchainDocument]: doc fitz.open(file_path) # 提取全文并按章节分割 chapter_pattern r第[一二三四五六七八九十\d]章[:]. chapter_matches list(re.finditer(chapter_pattern, full_text)) # ... 按章节位置精确分割逻辑DOCX 文档处理通过 pypandoc 转换为 Markdown 格式保留结构信息并按一级标题进行语义分割。该处理逻辑在 _load_docx_document 函数中核心代码实现如下。def _load_docx_document(file_path: str) - list[LangchainDocument]: # 转换为Markdown保留结构 md_content pypandoc.convert_file(file_path, gfm, formatdocx) # 按一级标题分割 chunks re.split(r(^#\s[^#].*), md_content, flagsre.MULTILINE)系统通过统一的入口函数根据文件类型自动选择解析策略。这个调度逻辑由 load_document 函数实现其核心代码实现如下。def load_document(file_path: str) - list[LangchainDocument]: file_extension os.path.splitext(file_path)[1].lower() if file_extension .docx: return _load_docx_document(file_path) elif file_extension .pdf:递归精细切分递归精细切分阶段由 text_chunker.py 执行使用 RecursiveCharacterTextSplitter 对预分块结果进行精细化处理。该分割器按照双换行符、单换行符、空格的优先级递归切分有效保护句子完整性。该功能的核心实现封装在 split_documents 函数中其核心代码实现如下。def split_documents(documents: List[Document]) - List[Document]: text_splitter RecursiveCharacterTextSplitter( chunk_sizeCHUNK_SIZE, # 400字符目标大小 chunk_overlapCHUNK_OVERLAP, # 80字符重叠防止切断 add_start_indexTrue # 记录原文位置索引 ) return text_splitter.split_documents(documents)两阶段分块策略的核心优势在于结合了语义保持和检索优化。通过这种两阶段处理策略系统将异构的制度文档高效转换为标准化的 Document 对象列表为后续向量化流程提供高质量的结构化输入。4.4、向量化与索引存储系统核心实现位于 vector_indexer.py 脚本中主要包含双模式文本向量化、FAISS 索引构建与优化、以及索引持久化机制三个功能模块。文本向量化部分不做赘述了着重介绍后两部分。FAISS 索引构建系统选用 Meta AI原 Facebook AI Research开源的 FAISS 库构建向量索引VectorIndexer 类的 build_index 方法实现索引构建的完整流程。该方法集成了批量向量化、索引创建和 L2 归一化等关键步骤其核心代码实现如下。# 批量向量化与索引构建 embeddings self.embedding_model.encode(texts) embedding_dim embeddings.shape[1] # 创建内积索引并执行L2归一化 self.index faiss.IndexFlatIP(embedding_dim) faiss.normalize_L2(embeddings) # 归一化后内积等价于余弦相似度 self.index.add(embeddings.astype(np.float32))注IndexFlatIP 是 FAISS 中的内积索引类型IP 即 Inner Product通过计算向量间的内积来衡量相似性。L2 归一化是将向量长度标准化为 1 的数学操作经过 L2 归一化后两个向量的内积运算在数学上等价于余弦相似度计算。余弦相似度专门用于衡量向量方向的一致性忽略向量长度差异因此更适合度量文本语义相似性索引持久化为避免重复执行耗时的向量化和索引构建系统实现了索引持久化机制。save_index 方法将构建完成的索引和元数据分别存储。该方法将 FAISS 索引与文档数据分离存储以确保高效读写其核心代码实现如下。# 分离存储FAISS索引和文档数据 faiss.write_index(self.index, f{self.index_path}.faiss) with open(f{self.index_path}_docs.pkl, wb) as f: pickle.dump({ documents: [doc.page_content for doc in self.documents], metadata: self.document_metadata }, f)注FAISS 索引以二进制格式即计算机直接读取的 0 和 1 编码格式相比文本格式具有存储紧凑、读取速度快的优势保存为.faiss 文件。由于 FAISS 专门针对向量数据优化仅存储数值向量而不包含原始文本因此系统需要单独保存文本内容和元数据。pickle 是 Python 的对象序列化工具能将复杂的数据结构如包含文本和字典的列表转换为字节流并保存为.pkl 文件实现完整数据结构的持久化存储。对应的 load_index 方法在系统启动时从磁盘快速恢复索引器的完整状态。4.5、检索与生成模块检索与生成核心模块负责处理员工查询的完整流程从向量检索到答案生成。由 retriever.py 和 generator.py 两个脚本构成分别承担检索上下文和生成答案的职责协同完成 RAG 系统的核心问答流程。文档检索器retriever.py 脚本中的 DocumentRetriever 类负责从 FAISS 索引中高效检索与员工问题语义最相关的文档块。核心的 retrieve 方法实现了完整的检索流程它首先调用与索引构建时相同的嵌入模型将查询文本向量化并进行 L2 归一化以匹配索引格式随后在 FAISS 索引中执行相似度搜索获取 Top-K 个最相似的文档块最后应用配置的相似度阈值对结果进行过滤并将最终结果封装为 RetrievalResult 对象返回。该方法封装了这一完整的检索逻辑其核心代码实现如下。# retrieve方法核心逻辑 def retrieve(self, query: str, k: int, score_threshold: float): # 调用向量索引器执行语义搜索 raw_results self.indexer.search(query, kk) # 应用相似度阈值过滤低质量结果 if score_threshold is not None: raw_results [r for r in raw_results if r[score] score_threshold] # 封装检索结果并返回检索指标 return results, metrics答案生成器generator.py 脚本中的 AnswerGenerator 类负责整合检索信息并调用大模型生成最终答案。PromptTemplate 类负责构建结构化的大模型输入。build_prompt 方法将检索到的文档块格式化为清晰的上下文与员工问题一同填入以下预定义模板。# 员工指令模板示例 DEFAULT_USER_TEMPLATE 基于以下文档内容请回答员工的问题。 **员工问题** {query} **相关文档内容** {context} **请提供准确、有条理的回答**generate_answer 方法实现完整的生成过程。首先调用 PromptTemplate 构建完整提示词然后传递给对应模式的大模型客户端获取生成结果。# generate_answer方法核心逻辑 def generate_answer(self, query: str, retrieval_results: List[RetrievalResult]): # 构建结构化提示词 system, user_prompt PromptTemplate.build_prompt(query, retrieval_results) # 调用大模型生成答案 answer, prompt_tokens, completion_tokens self.llm_client.generate( system, user_prompt ) # 封装生成结果并返回完整指标 return GenerationResult(...)5、交互界面问题测试我选择了 Gradio 库快速构建一个 Web 界面整体界面采用双栏布局主要目的是方便对分块效果召回结果以及思维链和最终回答的对比展示。先初始化界面上传“差旅报销细节.docx”和“员工手册.pdf”两份制度文档系统自动完成解析、分块和索引构建。以下通过两个测试问题分别检验系统在不同层面的能力。5.1、差旅餐费补贴查询在问题输入框中输入第一个测试问题“部门主管可以选择什么舱位的机票一天餐费是多少钱”这个测试问题需要系统同时获取部门主管的交通工具权限标准和餐饮补贴费用信息两个维度的数据。提交问题后系统在右侧召回片段详情区域展示了三个高度相关的文档块片段 1相似度 0.613精准召回了餐饮补贴标准表格清晰显示各城市等级的费用标准片段 2相似度 0.604准确定位到交通工具标准条款明确了部门主管的舱位选择权限片段 3相似度 0.549提供了任职标准的补充信息。左侧答案显示区域呈现了系统的完整回答展现了清晰的思维链推理过程首先明确“部门主管”的职级定义然后分别针对机票舱位和餐费标准给出精确答案。系统准确识别了部门主管可选择经济舱或在特定条件下预订公务舱并根据出差城市等级提供了 150 元、120 元、100 元的差异化餐费标准充分验证了单文档信息整合和精准回答的能力。5.2、跨文档职级住宿查询接下来测试更具挑战性的问题“M3 级别员工去上海出差住宿标准”。这个问题看似更简单但实际需要系统进行跨文档信息整合从不同文档中获取职级定义和住宿标准信息。系统展现了出色的跨文档检索能力召回了 5 个高度相关的文档片段相似度分数分布在 0.543 至 0.620 区间。系统的跨文档推理过程清晰可见首先从片段 5“员工手册.pdf”相似度 0.543中准确识别出“M1-M2项目经理/部门主管M3部门总监”的职级对应关系确定 M3 级别对应部门总监然后从片段 2“差旅报销细则.docx”相似度 0.620中获取城市分级信息明确上海属于一线城市最后从片段 1 的住宿标准表格中查找到部门总监在一线城市的住宿标准为 1000 元/晚。左侧答案显示区域完整展现了这三步推理过程系统准确执行了“职级映射→城市分级→标准匹配”的逻辑链条最终给出了精确的住宿费用标准。6、工程经验与架构演进6.1、工程经验config.py 的集中化配置策略在实践中展现出显著优势支持本地 Ollama 与云端 SiliconFlow 的无缝切换使相同业务逻辑能够适配不同技术栈。双模式架构设计体现了重要的工程价值。本地模式提供完全自主可控的解决方案适合数据安全要求严格的环境云端模式获得更强的模型能力和服务稳定性。这种设计为技术验证提供了渐进式路径开发者可先用云端 API 验证业务逻辑成熟后再切换到本地部署。原生实现使每个技术环节完全可见、可控制从 PyMuPDF 的文档解析到 FAISS 的向量检索所有核心逻辑都以最直接方式呈现。当系统出现问题时透明的实现方式使问题定位变得简单每个模块的输入输出都是标准 Python 对象便于添加调试信息和修改处理逻辑。这种可控性在原型开发和算法调优阶段具有不可替代的价值。6.2、架构演进当前原生实现在处理复杂业务逻辑时暴露出开发成本高、功能扩展困难等问题。每增加一个新功能都需要从底层开始实现如添加多轮对话支持需要自建会话管理、实现混合检索需要整合多种算法。成熟的 RAG 框架通过标准化的组件和接口能够显著降低这些高级功能的实现复杂度让开发者将更多精力集中在业务逻辑优化上。LangChain 作为当前最主流的 RAG 开发框架提供了完整的组件生态系统。迁移路径可以采用渐进式策略首先用 LangChain 的 Document Loaders 替代自建的文档解析器利用其丰富的格式支持然后采用 VectorStores 统一向量存储接口获得更好的扩展性最后通过 Chains 将检索和生成逻辑串联实现更复杂的 RAG 工作流。LangChain 的最大优势在于其丰富的集成能力和活跃的社区生态能够快速适配各种模型和向量数据库。LlamaIndex 专门针对文档理解和知识问答场景进行了深度优化在处理结构化文档和复杂查询方面表现突出。其提供的 Index 抽象层能够更好地处理文档间的层次关系Query Engine 支持多种高级检索策略。对于需要深度文档理解的应用场景LlamaIndex 往往能提供更精准的检索和生成效果。实际迁移过程中建议采用混合策略保留原生实现中验证有效的核心逻辑逐步引入框架组件替代复杂度高的部分。这种方式既能享受框架带来的开发效率提升又能保持对关键逻辑的控制力。7、写在最后在 Agent 和 Context Engineering 概念满天飞的时候回过头来重温下原生 RAG 似乎有些逆潮流。但正本清源或许是更加务实的做法。anyway下周进一步介绍利用 LangChain、LlamaIndex 等框架结合简历筛选分析场景构建更加一个复杂 RAG 应用感兴趣的可以蹲一蹲。然后9月第一周更新鸽了很久的京东joyagent二开案例演示。项目源码及文档已上传至知识星球欢迎加入和200位一线从业者交流实践