:从结构化数据到智能检索的进阶之路)
在上一篇《检索优化一》中我们学习了混合检索如何同时利用稀疏和密集向量的优势和查询构建如何让LLM将自然语言转换为元数据过滤和图查询。但现实世界的RAG系统面对的挑战远不止这些数据不仅仅在向量库里还有大量的结构化数据躺在关系型数据库中用户的原始问题往往不够“完美”需要预处理和重构初步检索到的结果可能包含噪音需要二次精排和压缩本篇将带你逐一攻克这三个难关文本到SQL打通结构化数据、查询重构与分发优化检索输入、检索进阶精排、压缩与校正让你的RAG系统从“能用”进化为“好用”。第一部分文本到SQL —— 用自然语言查询结构化数据在数据世界中除了向量数据库能够处理的非结构化数据关系型数据库如MySQL、PostgreSQL、SQLite同样是存储和管理结构化数据的重镇。文本到SQLText-to-SQL正是为了打破人与结构化数据之间的语言障碍而生。它利用大语言模型LLM将用户的自然语言问题直接翻译成可以在数据库上执行的SQL查询语句。一、业务挑战将自然语言转换为SQL并非易事主要面临三大挑战挑战说明后果“幻觉”问题LLM可能会“想象”出数据库中不存在的表或字段生成的SQL语句无效执行报错对数据库结构理解不足LLM需要准确理解表的结构、字段的含义以及表与表之间的关联关系生成的JOIN和WHERE子句错误用户输入的模糊性用户的提问可能存在拼写错误或不规范的表达如“上个月的销售冠军是谁”模型需要具备容错和推理能力二、优化策略针对上述挑战业界总结出了五条核心优化策略策略核心思想效果提供精确的数据库模式向LLM提供相关表的CREATE TABLE语句就像给了LLM一张数据库“地图”让LLM了解表名、列名、数据类型和外键关系提供少量高质量的示例在提示中加入“问题-SQL”的示例对Few-shot让LLM学习如何根据相似问题构建查询利用RAG增强上下文为数据库构建专门的“知识库”包含DDL、字段描述、同义词映射、复杂查询示例等用户提问时先从知识库检索相关信息再让LLM生成SQL极大降低“幻觉”风险错误修正与反思生成SQL后尝试执行如果报错将错误信息反馈给LLM让它“反思”并修正迭代过程显著提高查询成功率安全执行策略自动为SELECT语句添加LIMIT限制防止大量数据返回保护数据库性能和系统稳定性三、Text2SQL框架实现本节基于RAGFlow的方案实现了一个简单但完整的Text2SQL框架。该框架使用Milvus向量数据库作为知识库BGE-M3模型进行语义检索DeepSeek作为大语言模型专门针对SQLite数据库进行了优化。3.1 框架架构概览整个框架由三个核心模块组成协同完成从自然语言到SQL的转换text 用户查询: 年龄大于30的用户有哪些 ↓ ┌─────────────────────────────────────────────────────────────┐ │ SimpleText2SQLAgent代理协调模块 │ │ ┌─────────────────────────────────────────────────────────┐ │ │ │ ① 知识检索 → ② SQL生成 → ③ 执行与重试 → ④ 返回结果 │ │ │ └─────────────────────────────────────────────────────────┘ │ │ ↓ ↓ ↓ │ │ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │ │ │ KnowledgeBase│ │ SQLGenerator │ │ SQL执行器 │ │ │ │ (知识库模块) │ │ (SQL生成模块) │ │ (带重试) │ │ │ └──────────────┘ └──────────────┘ └──────────────┘ │ └─────────────────────────────────────────────────────────────┘ ↓ 最终输出: {success: true, sql: SELECT * FROM users WHERE age 30, results: [...]}3.2 模块一知识库Knowledge Base知识库是Text2SQL框架的“大脑”负责存储和检索所有与SQL相关的知识。设计思想将三种类型的知识统一存储在一个Milvus集合中通过type字段区分使用BGE-M3进行语义检索。class SimpleKnowledgeBase: Text2SQL知识库 def __init__(self, milvus_uri: str http://localhost:19530): self.milvus_uri milvus_uri self.client MilvusClient(urimilvus_uri) self.embedding_function BGEM3EmbeddingFunction(use_fp16False, devicecpu) self.collection_name text2sql_kb self._setup_collection() def _setup_collection(self): 设置集合定义字段结构 fields [ FieldSchema(namepk, dtypeDataType.VARCHAR, is_primaryTrue, auto_idTrue, max_length100), FieldSchema(namecontent, dtypeDataType.VARCHAR, max_length4096), # 存储知识内容 FieldSchema(nametype, dtypeDataType.VARCHAR, max_length32), # 知识类型ddl, qsql, description FieldSchema(namedense_vector, dtypeDataType.FLOAT_VECTOR, dimself.embedding_function.dim[dense]) ] # ... 创建集合逻辑三种知识类型类型标识内容示例作用DDL知识ddlCREATE TABLE users (id INT, name TEXT, age INT)告诉LLM表的结构和字段定义Q-SQL知识qsql{question: 年龄超过25岁的用户, sql: SELECT * FROM users WHERE age 25}提供“问题-SQL”示例帮助LLM学习模式描述知识descriptionage字段用户年龄INTEGER类型用于存储用户的年龄信息用自然语言解释表和字段的业务含义检索机制当用户提问时系统将问题向量化在知识库中检索最相关的知识默认Top-5并按类型分组返回。def search(self, query: str, top_k: int 5) - List[Dict[str, Any]]: 搜索知识库中与查询最相关的内容 query_embeddings self.embedding_function([query]) search_results self.client.search( collection_nameself.collection_name, dataquery_embeddings[dense], anns_fielddense_vector, search_params{metric_type: IP}, # 内积相似度 limittop_k, output_fields[content, type] ) return search_results3.3 模块二SQL生成器SQL GeneratorSQL生成器负责将自然语言问题转换为SQL查询语句并具备错误修复能力。class SimpleSQLGenerator: SQL生成器生成SQL并支持错误修复 def __init__(self, api_key: str None): self.llm ChatDeepSeek( modeldeepseek-chat, temperature0, # 确保结果的确定性 api_keyapi_key or os.getenv(DEEPSEEK_API_KEY) )核心方法1生成SQLpython def generate_sql(self, user_query: str, knowledge_results: List[Dict[str, Any]]) - str: 根据用户查询和检索到的知识生成SQL context self._build_context(knowledge_results) prompt f你是一个SQL专家。请根据以下信息将用户问题转换为SQL查询语句。 数据库信息 {context} 用户问题{user_query} 要求 1. 只返回SQL语句不要包含任何解释 2. 确保SQL语法正确 3. 使用上下文中提供的表名和字段名 4. 如果需要JOIN请根据表结构进行合理关联 SQL语句 response self.llm.invoke(prompt) return response.content.strip()核心方法2修复SQL错误反思python def fix_sql(self, original_sql: str, error_message: str, knowledge_results: List[Dict[str, Any]]) - str: 根据错误信息修复SQL语句 context self._build_context(knowledge_results) prompt f请修复以下SQL语句的错误。 数据库信息 {context} 原始SQL {original_sql} 错误信息 {error_message} 请返回修复后的SQL语句只返回SQL不要解释 response self.llm.invoke(prompt) return response.content.strip()上下文构建策略将检索到的知识按类型分组分层次组织——先展示表结构DDL再展示字段描述最后展示查询示例。python def _build_context(self, knowledge_results: List[Dict[str, Any]]) - str: 构建结构化的上下文信息 ddl_info [] # 表结构信息 qsql_examples [] # 查询示例 descriptions [] # 表描述信息 for result in knowledge_results: content result[entity][content] doc_type result[entity][type] if doc_type ddl: ddl_info.append(content) elif doc_type qsql: qsql_examples.append(content) elif doc_type description: descriptions.append(content) context if ddl_info: context 表结构信息 \n \n.join(ddl_info) \n\n if descriptions: context 表和字段描述 \n \n.join(descriptions) \n\n if qsql_examples: context 查询示例 \n \n.join(qsql_examples) \n return context3.4 模块三代理协调器Agent代理模块是整个框架的“指挥官”协调知识库检索、SQL生成和执行的完整流程。python class SimpleText2SQLAgent: Text2SQL代理协调知识检索、SQL生成和执行 def __init__(self, milvus_uri: str http://localhost:19530, api_key: str None): self.knowledge_base SimpleKnowledgeBase(milvus_uri) self.sql_generator SimpleSQLGenerator(api_key) # 配置参数 self.max_retry_count 3 # 最大重试次数 self.top_k_retrieval 5 # 检索数量 self.max_result_rows 100 # 结果行数限制主查询流程python def query(self, user_question: str) - Dict[str, Any]: 执行完整的Text2SQL查询流程 # 1. 从知识库检索相关信息 knowledge_results self.knowledge_base.search(user_question, self.top_k_retrieval) # 2. 生成SQL语句 sql self.sql_generator.generate_sql(user_question, knowledge_results) # 3. 执行SQL带重试机制 retry_count 0 while retry_count self.max_retry_count: success, result self._execute_sql(sql) if success: return { success: True, sql: sql, results: result, retry_count: retry_count } else: # 尝试修复SQL sql self.sql_generator.fix_sql(sql, result, knowledge_results) retry_count 1 return {success: False, error: SQL执行失败已超过最大重试次数, sql: sql}安全执行策略python def _execute_sql(self, sql: str) - Tuple[bool, Any]: 安全执行SQL语句 # 自动添加LIMIT限制防止大量数据返回 if sql.strip().upper().startswith(SELECT) and LIMIT not in sql.upper(): sql f{sql.rstrip(;)} LIMIT {self.max_result_rows} # ... 执行SQL并结构化返回结果3.5 完整流程示例从自然语言到SQL让我们以查询“年龄大于30的用户有哪些”为例完整演示框架的运作过程。Step 1知识库检索BGE-M3将查询转换为向量在知识库中检索到最相关的5条知识类型相似度内容DDL0.85CREATE TABLE users (id INT PRIMARY KEY, name TEXT, email TEXT UNIQUE, age INT, city TEXT)Q-SQL0.82{question: 查询年龄超过25岁的用户, sql: SELECT * FROM users WHERE age 25}描述0.78age字段用户年龄INTEGER类型name字段用户姓名TEXT类型Step 2SQL生成DeepSeek分析用户意图识别关键信息字段age、比较操作、阈值30参考示例模式从WHERE age 25学习到WHERE age 数值的模式生成目标SQLSELECT * FROM users WHERE age 30Step 3SQL执行与结果处理假设users表包含以下数据ID姓名邮箱年龄城市1张三zhangsanemail.com25北京2李四lisiemail.com32上海3王五wangwuemail.com28广州4赵六zhaoliuemail.com35深圳5陈七chenqiemail.com29杭州执行SQL后自动添加LIMIT 100安全限制筛选出符合条件的记录json { success: true, sql: SELECT * FROM users WHERE age 30 LIMIT 100, results: { columns: [id, name, email, age, city], rows: [ {id: 2, name: 李四, email: lisiemail.com, age: 32, city: 上海}, {id: 4, name: 赵六, email: zhaoliuemail.com, age: 35, city: 深圳} ], count: 2 }, retry_count: 0 }3.6 为什么不直接使用封装好的框架 因为淋过雨所以想为你撑把伞。市面上确实有很多成熟的Text2SQL框架但这些高度封装的工具往往存在黑盒问题——当查询结果不符合预期时很难定位是检索环节、SQL生成环节还是执行环节出了问题。正如我们在上一章LangChain的SelfQueryRetriever示例中遇到的“时间最短的视频”查询异常很难深入到框架内部进行精确调试和优化。自主实现框架的核心价值优势说明可调试性每个环节检索→生成→执行→重试都可单独调试和日志记录可定制性可根据业务需求灵活调整知识库结构、提示词模板、重试策略可优化性方便针对特定数据库类型如SQLite、MySQL、PostgreSQL进行针对性优化透明可控所有逻辑清晰可见不依赖第三方黑盒便于维护和迭代第二部分查询重构与分发 —— 让问题更“好搜”在RAG系统中用户的原始问题往往不是最优的检索输入。它可能过于复杂、包含歧义或者与文档的实际措辞存在偏差。查询重构与分发就是在检索之前对用户查询进行“预处理”让检索更精准。四、查询翻译查询翻译的目标是弥合用户自然语言提问与文档库中存储信息之间的“语义鸿沟”。4.1 提示工程Prompt Engineering这是最直接的查询重构方法。通过精心设计的提示词引导LLM将用户的原始查询改写得更清晰、更具体。实战案例解决“最值查询”的排序问题在上一章中我们遇到一个问题SelfQueryRetriever无法正确处理“时间最短的视频”这类需要排序的查询。原因在于它不支持ORDER BY操作。解决方案让LLM直接生成结构化的排序指令而不是依赖于SelfQueryRetriever的有限能力。python # 设计提示词要求LLM输出JSON格式的排序指令 prompt f你是一个智能助手请将用户的问题转换成一个用于排序视频的JSON指令。 你需要识别用户想要排序的字段和排序方向。 - 排序字段必须是 view_count (观看次数) 或 length (时长) 之一。 - 排序方向必须是 asc (升序) 或 desc (降序) 之一。 例如: - 时间最短的视频 → {{sort_by: length, order: asc}} - 播放量最高的视频 → {{sort_by: view_count, order: desc}} 请根据以下问题生成JSON指令: 原始问题: {query} JSON指令: # 调用LLM并解析结果 response llm.invoke(prompt) instruction json.loads(response.content) # 根据指令在代码中执行排序 sort_by instruction.get(sort_by) # length 或 view_count order instruction.get(order) # asc 或 desc reverse_order (order desc) sorted_docs sorted(all_documents, keylambda doc: doc.metadata.get(sort_by, 0), reversereverse_order) result sorted_docs[0] # 取第一个通过这种方式我们将LLM从“文本改写员”提升为能够理解复杂意图并生成可执行计划的“智能代理”优雅地解决了“最值”查询的难题。4.2 多查询分解Multi-Query当用户提出一个复杂的问题时直接用整个问题去检索可能效果不佳因为它可能包含多个子主题。核心思想将复杂问题拆分成多个更简单、更具体的子问题分别检索然后合并结果。text 原始问题: 在《流浪地球》中刘慈欣对人工智能和未来社会结构有何看法 ↓ 分解 子问题1: 《流浪地球》中描述的人工智能技术有哪些 子问题2: 《流浪地球》中描绘的未来社会是怎样的 子问题3: 刘慈欣关于人工智能的观点是什么 ↓ 分别检索后合并 最终答案综合三个子问题的检索结果LangChain提供了MultiQueryRetriever来完成这一过程。它在内部利用LLM将原始问题从不同角度分解成多个子问题并行为每个子问题检索相关文档最后合并去重。4.3 退步提示Step-Back Prompting由Google DeepMind团队提出的提示工程技术。当面对一个细节繁多或过于具体的问题时模型直接作答容易出错。核心思路先引导模型“退后一步”生成一个更概括的“退步问题”获取通用原理再基于这个原理推理具体问题。流程对比步骤传统方法直接推理退步提示方法Step 1直接回答问题先生成退步问题“这个问题背后的物理原理是什么”Step 2可能出错回答退步问题“理想气体定律 PVnRT”Step 3-基于定律代入具体数值推出正确答案示例原始问题退步问题推理过程结果“如果理想气体的温度增加2倍体积增加8倍其压力会如何变化”“这个问题背后的物理原理是什么”先得到“理想气体定律PVnRT”代入数值计算压力变为原来的1/4 ✅4.4 假设性文档嵌入HyDEHyDEHypothetical Document Embeddings是一种无需微调即可显著提升向量检索质量的查询改写技术。核心问题用户的查询通常简短、关键词有限而数据库中的文档则内容详实两者在向量空间中可能存在“鸿沟”。解决思路不直接使用原始查询而是先用LLM生成一个“假设性”的、能够完美回答该查询的文档然后将这个假设文档向量化用它去检索真实文档。text 用户查询: 如何快速入门Python ↓ LLM生成假设文档: Python入门需要掌握基础语法、数据类型、控制流、函数定义...一段详尽的回答 ↓ 将假设文档向量化 ↓ 用假设文档向量检索真实文档文档到文档的匹配更准确 ↓ 返回最相关的真实文档为什么有效HyDE将困难的“查询到文档”匹配查询短、文档长语义不对齐转化为相对容易的“文档到文档”匹配假设文档与真实文档长度相近、语义对齐。五、查询路由当RAG系统接入了多个不同的数据源或具备多种处理能力时就需要一个“智能调度中心”来分析用户的查询并动态选择最合适的处理路径。这就是查询路由Query Routing。5.1 应用场景路由类型说明示例数据源路由根据查询意图路由到不同的知识库产品问题→产品文档库订单问题→SQL数据库组件路由根据问题复杂性分配给不同处理组件简单FAQ→直接向量检索复杂操作→Agent执行提示模板路由为不同类型的任务选择最优提示模板数学问题→分步推理模板代码生成→代码优化模板5.2 实现方式一基于LLM的意图识别通过设计包含路由选项的提示词让LLM直接对用户查询进行分类。python from langchain_core.prompts import ChatPromptTemplate from langchain_core.output_parsers import StrOutputParser from langchain_core.runnables import RunnableBranch, RunnableLambda # 第一步定义分类器 classifier_prompt ChatPromptTemplate.from_template( 根据用户问题中提到的菜品将其分类为[川菜, 粤菜, 其他]。 不要解释你的理由只返回一个单词的分类结果。 问题: {question} ) classifier_chain classifier_prompt | llm | StrOutputParser() # 第二步定义各分支的处理链 sichuan_chain PromptTemplate.from_template( 你是一位川菜大厨。请用正宗的川菜做法回答关于「{query}」的问题。 ) | llm | StrOutputParser() cantonese_chain PromptTemplate.from_template( 你是一位粤菜大厨。请用经典的粤菜做法回答关于「{query}」的问题。 ) | llm | StrOutputParser() general_chain PromptTemplate.from_template( 请回答关于「{query}」的问题。 ) | llm | StrOutputParser() # 第三步构建路由分支 router_branch RunnableBranch( (lambda x: 川菜 in x[topic], sichuan_chain), (lambda x: 粤菜 in x[topic], cantonese_chain), general_chain # 默认选项 ) # 第四步组合完整路由链 # 并行执行分类器生成topic同时保留原始question # 然后将结果传递给路由分支 full_router_chain { topic: classifier_chain, question: lambda x: x[question] } | router_branch # 调用示例 result full_router_chain.invoke({question: 麻婆豆腐怎么做}) # 自动路由到川菜大厨处理 ✅5.3 实现方式二嵌入相似性路由这种方法不依赖LLM进行分类延迟更低。通过计算用户查询与预设“路由描述”之间的向量相似度来做决策。python import numpy as np from sklearn.metrics.pairwise import cosine_similarity # 第一步定义路由描述并向量化 sichuan_desc 你是一位处理川菜的专家。用户的问题是关于麻辣、辛香、重口味的菜肴例如水煮鱼、麻婆豆腐、鱼香肉丝等。 cantonese_desc 你是一位处理粤菜的专家。用户的问题是关于清淡、鲜美、原汁原味的菜肴例如白切鸡、老火靓汤、虾饺等。 route_descs [sichuan_desc, cantonese_desc] route_names [川菜, 粤菜] route_embeddings embeddings.embed_documents(route_descs) # 第二步定义路由函数 def semantic_route(info): # 计算用户查询与各路由描述的相似度 query_emb embeddings.embed_query(info[query]) similarities cosine_similarity([query_emb], route_embeddings)[0] # 选择最相似的路由 best_idx np.argmax(similarities) chosen_route route_names[best_idx] # 调用对应的处理链 return route_map[chosen_route].invoke(info) # 第三步组合成链 full_chain RunnableLambda(semantic_route)5.4 LlamaIndex中的路由实现LlamaIndex通过RouterQueryEngine实现查询路由。其思路是将不同的数据源或查询策略包装为“工具Tool”然后通过路由器为用户查询动态选择最合适的工具。python from llama_index.core.query_engine import RouterQueryEngine from llama_index.core.tools import QueryEngineTool # 定义多个工具每个工具包含一个查询引擎和一段描述 tools [ QueryEngineTool.from_defaults( query_engineproduct_engine, description用于回答产品相关问题 ), QueryEngineTool.from_defaults( query_engineorder_engine, description用于回答订单和物流相关问题 ), ] # 创建路由查询引擎 router_engine RouterQueryEngine.from_defaults( toolstools, selectorLLMSingleSelector.from_defaults() # LLM根据语义选择工具 ) # 用户查询自动路由到最合适的工具 response router_engine.query(我上次买的手机什么时候发货)第三部分检索进阶 —— 重排、压缩与校正在基础的RAG流程中我们依赖向量相似度从知识库中检索信息。但这种方法存在一些固有局限最相关的文档不总是在检索结果的顶端语义理解可能有偏差检索结果可能包含噪音。为了构建更强大的生产级RAG应用需要引入更高级的检索技术。六、重排序Re-ranking重排序的目标是对初步检索结果进行二次排序把最相关的内容排到最前面。6.1 RRF倒数排名融合我们在混合检索章节中已详细介绍了RRF。它是一种零样本重排方法不依赖模型训练纯粹基于文档在多个检索器结果列表中的排名计算分数。优点缺点无需训练零样本使用只考虑排名信息忽略原始相似度分数计算成本极低可能丢失部分有用信息融合多路检索优势-6.2 RankLLM基于LLM的重排直接利用大型语言模型本身来进行重排。基本逻辑非常直观既然LLM最终要负责根据上下文生成答案为什么不直接让它判断哪些上下文最相关提示词示例text 以下是一个文档列表每个文档都有一个编号和摘要。同时提供一个问题。请根据问题按相关性顺序列出您认为最需要查阅的文档编号并给出相关性分数1-10分。 文档 1: 文档1的摘要 文档 2: 文档2的摘要 ... 文档 10: 文档10的摘要 问题: 用户的问题 回答: Doc: 9, Relevance: 7 Doc: 3, Relevance: 4 Doc: 7, Relevance: 3优点缺点理解能力最强能处理复杂的相关性判断API调用成本高、延迟大可提供相关性解释受限于LLM的上下文窗口6.3 Cross-Encoder重排Cross-Encoder将查询和每个候选文档拼接成一个输入[CLS] query [SEP] document [SEP]输入到Transformer模型中输出一个0-1之间的相关性分数。[SEP]是BERT等Transformer模型中用于分隔不同文本片段的特殊标记。text 工作流程: ① 初步检索: 从知识库召回Top-50文档 ② 逐一评分: 对每个查询-文档对进行独立的Cross-Encoder推理 ③ 返回重排结果: 根据新分数重新排序优点缺点精度最高同时分析查询和文档的交互需要N次独立的模型推理延迟高能捕捉细粒度的相关性信号计算成本大常用模型ms-marco-MiniLM-L-12-v2、ms-marco-TinyBERT-L-2-v26.4 ColBERT重排ColBERTContextualized Late Interaction over BERT在Cross-Encoder的高精度和双编码器的高效率之间取得了平衡采用“后期交互”机制。text 工作流程: ① 独立编码: 分别为查询和文档中的每个Token生成上下文相关的嵌入向量 ② 后期交互: 查询时计算每个查询Token与文档Token之间的最大相似度MaxSim ③ 分数聚合: 将所有查询Token的最大相似度相加得到最终总分ColBERT vs Cross-Encoder vs 双编码器维度双编码器Bi-EncoderCross-EncoderColBERT编码方式查询和文档独立编码查询和文档联合编码独立编码后期交互精度较低最高高速度最快可预计算文档向量最慢N次联合推理中向量点积计算交互粒度无仅比较[CLS]向量全量交互Token级MaxSim6.5 重排方法对比总结特性RRFRankLLMCross-EncoderColBERT核心机制融合多个排名LLM推理生成排序列表联合编码计算相关分独立编码后期交互计算成本极低中API费用与延迟高N次模型推理中向量点积计算交互粒度无仅排名概念/语义级句子级Token级是否需要训练否否是需微调是需微调适用场景多路召回融合高价值语义理解场景Top-K精排Top-K重排七、压缩Compression“压缩”技术旨在解决一个问题初步检索到的文档块虽然整体与查询相关但可能包含大量无关的“噪音”文本。将这些未处理的冗长上下文直接提供给LLM不仅增加API成本还可能因信息过载降低答案质量。7.1 LangChain的ContextualCompressionRetrieverLangChain提供了ContextualCompressionRetriever组件来实现上下文压缩。它像一个包装器包裹在基础检索器之上对检索结果进行处理后再返回。内置压缩器压缩器工作方式类型LLMChainExtractor利用LLM提取文档中与查询相关的句子内容提取LLMChainFilter利用LLM判断整个文档是否相关不相关的直接丢弃文档过滤EmbeddingsFilter计算查询和文档的嵌入相似度只保留超过阈值的文档文档过滤7.2 自定义重排器与压缩管道以ColBERT为例展示如何集成未被官方支持的功能。实现步骤如下python from langchain.retrievers.document_compressors.base import BaseDocumentCompressor from langchain.retrievers.document_compressors import DocumentCompressorPipeline from langchain.retrievers import ContextualCompressionRetriever class ColBERTReranker(BaseDocumentCompressor): 自定义ColBERT重排器 def __init__(self, model_name: str bert-base-uncased, **kwargs): super().__init__(**kwargs) self.tokenizer AutoTokenizer.from_pretrained(model_name) self.model AutoModel.from_pretrained(model_name) self.model.eval() def compress_documents( self, documents: Sequence[Document], query: str, callbacksNone, ) - Sequence[Document]: 对文档进行ColBERT重排序 # 1. 编码查询和文档独立编码 query_emb self._encode_text([query]) doc_embs self._encode_text([doc.page_content for doc in documents]) # 2. 计算MaxSim相似度后期交互 scores self._calculate_colbert_similarity(query_emb, doc_embs) # 3. 排序并返回Top-5 scored_docs sorted(zip(documents, scores), keylambda x: x[1], reverseTrue) return [doc for doc, _ in scored_docs[:5]] def _encode_text(self, texts): ColBERT文本编码 inputs self.tokenizer(texts, return_tensorspt, paddingTrue, truncationTrue, max_length128) with torch.no_grad(): outputs self.model(**inputs) embeddings outputs.last_hidden_state return F.normalize(embeddings, p2, dim-1) def _calculate_colbert_similarity(self, query_emb, doc_embs): MaxSim操作每个查询Token找最相似的文档Token scores [] for doc_emb in doc_embs: # 计算相似度矩阵 sim_matrix torch.matmul(query_emb, doc_emb.transpose(-2, -1)) # MaxSim: 每个查询Token取最大相似度然后求和 max_sim sim_matrix.max(dim-1)[0].sum().item() scores.append(max_sim) return scores组装完整管道python # 1. 创建基础检索器 base_retriever vectorstore.as_retriever(search_kwargs{k: 20}) # 2. 创建处理管道ColBERT重排 LLM压缩 pipeline_compressor DocumentCompressorPipeline( transformers[ ColBERTReranker(), # 先重排 LLMChainExtractor.from_llm(llm) # 再压缩 ] ) # 3. 创建最终检索器 final_retriever ContextualCompressionRetriever( base_compressorpipeline_compressor, base_retrieverbase_retriever ) # 4. 执行查询 results final_retriever.get_relevant_documents(AI还有哪些缺陷需要克服)7.3 LlamaIndex中的检索压缩LlamaIndex提供了SentenceEmbeddingOptimizer作为后处理器。它检索后把文档分解成句子计算每个句子与查询的嵌入相似度只保留相似度最高的句子去除无关部分。八、校正Corrective-RAG传统RAG流程有一个隐含假设检索到的文档总是与问题相关且包含正确答案。但现实中检索系统可能失败返回不相关或错误的文档。校正检索Corrective-RAGC-RAG正是为解决这一问题而提出的策略。8.1 C-RAG的工作流程C-RAG引入了一个“自我反思”循环分为三个阶段text ┌─────────────────────────────────────────────────────────────┐ │ 阶段一检索Retrieve │ │ 根据用户查询从知识库中检索文档 │ └─────────────────────────────────────────────────────────────┘ ↓ ┌─────────────────────────────────────────────────────────────┐ │ 阶段二评估Assess │ │ 检索评估器判断每个文档的相关性给出标签 │ │ ✅ 正确Correct ❌ 不正确Incorrect ❓ 模糊Ambiguous│ └─────────────────────────────────────────────────────────────┘ ↓ ┌───────────────────┼───────────────────┐ ↓ ↓ ↓ ┌───────────────┐ ┌───────────────┐ ┌───────────────┐ │ ✅ 正确 │ │ ❌ 不正确 │ │ ❓ 模糊 │ │ → 知识精炼 │ │ → 知识搜索 │ │ → 知识搜索 │ │ 分解、过滤、 │ │ 查询重写后 │ │ 直接使用原查询 │ │ 重组上下文 │ │ 进行Web搜索 │ │ 进行Web搜索 │ └───────────────┘ └───────────────┘ └───────────────┘三种行动路径详解评估结果行动名称具体操作✅ 正确知识精炼将文档分解为更细粒度的知识片段过滤无关部分重新组合成精准上下文❌ 不正确知识搜索对原始查询进行重写优化关键词然后进行Web搜索获取外部信息❓ 模糊知识搜索直接使用原始查询进行Web搜索获取额外信息辅助生成通过这种方式C-RAG极大地增强了RAG系统的鲁棒性。它不再盲目信任检索结果而是在检索失败时主动寻求外部帮助有效减少幻觉提升答案的准确性。第四部分总结核心技术一句话概括主要应用Text-to-SQL用自然语言生成SQL查询打通结构化数据MySQL、SQLite等Text2SQL知识库DDLQ-SQL描述三类知识统一存储降低LLM生成SQL的“幻觉”提示工程用Prompt改写和优化查询解决排序、最值等复杂查询多查询分解复杂问题拆分为多个子问题提升多主题问题的召回率退步提示先获取通用原理再推理具体问题复杂推理问答HyDE生成假设文档用文档向量检索解决查询与文档的语义鸿沟查询路由LLMLLM分类意图选择处理路径多数据源、多组件的智能分发查询路由语义嵌入相似度匹配路由低延迟、不依赖LLM的路由RRF重排基于排名融合多路检索结果混合检索的结果融合RankLLMLLM直接判断文档相关性并排序高精度语义重排Cross-Encoder联合编码查询和文档Top-K高精度精排ColBERT独立编码后期交互精度与效率的平衡上下文压缩提取相关句子或过滤不相关文档降低噪音、节省成本Corrective-RAG评估检索质量失败时主动补救增强系统鲁棒性如果本文对你有帮助欢迎点赞、收藏、转发有任何疑问欢迎在评论区交流讨论