自定义 LangChain 组件:打造专属 RAG 应用

news/2024/11/14 5:25:02/文章来源:https://www.cnblogs.com/muzinan110/p/18541360

引言

在构建专业的检索增强生成(RAG)应用时,LangChain 提供了丰富的内置组件。然而,有时我们需要根据特定需求定制自己的组件。本文将深入探讨如何自定义 LangChain 组件,特别是文档加载器、文档分割器和检索器,以打造更加个性化和高效的 RAG 应用。

自定义文档加载器

LangChain 的文档加载器负责从各种源加载文档。虽然内置加载器覆盖了大多数常见格式,但有时我们需要处理特殊格式或来源的文档。

为什么要自定义文档加载器?

  1. 处理特殊文件格式
  2. 集成专有数据源
  3. 实现特定的预处理逻辑

自定义文档加载器的步骤

  1. 继承 BaseLoader
  2. 实现 load() 方法
  3. 返回 Document 对象列表

示例:自定义 CSV 文档加载器

from langchain.document_loaders.base import BaseLoader
from langchain.schema import Document
import csvclass CustomCSVLoader(BaseLoader):def __init__(self, file_path):self.file_path = file_pathdef load(self):documents = []with open(self.file_path, 'r') as csv_file:csv_reader = csv.DictReader(csv_file)for row in csv_reader:content = f"Name: {row['name']}, Age: {row['age']}, City: {row['city']}"metadata = {"source": self.file_path, "row": csv_reader.line_num}documents.append(Document(page_content=content, metadata=metadata))return documents# 使用自定义加载器
loader = CustomCSVLoader("path/to/your/file.csv")
documents = loader.load()

自定义文档分割器

文档分割是 RAG 系统中的一个关键环节。虽然 LangChain 提供了多种内置分割器,但在特定场景下,我们可能需要自定义分割器来满足特殊需求。

为什么需要自定义文档分割器?

  1. 处理特殊格式的文本(如代码、表格、特定领域的专业文档)
  2. 实现特定的分割规则(如按章节、段落或特定标记分割)
  3. 优化分割结果的质量和语义完整性

自定义文档分割器的基本架构

继承 TextSplitter 基类

from langchain.text_splitter import TextSplitter
from typing import Listclass CustomTextSplitter(TextSplitter):def __init__(self, chunk_size: int = 1000, chunk_overlap: int = 200):super().__init__(chunk_size=chunk_size, chunk_overlap=chunk_overlap)def split_text(self, text: str) -> List[str]:"""实现具体的文本分割逻辑"""# 自定义分割规则chunks = []# 处理文本并返回分割后的片段return chunks

实用示例:自定义分割器

1. 基于特定标记的分割器

class MarkerBasedSplitter(TextSplitter):def __init__(self, markers: List[str], **kwargs):super().__init__(**kwargs)self.markers = markersdef split_text(self, text: str) -> List[str]:chunks = []current_chunk = ""for line in text.split('\n'):if any(marker in line for marker in self.markers):if current_chunk.strip():chunks.append(current_chunk.strip())current_chunk = lineelse:current_chunk += '\n' + lineif current_chunk.strip():chunks.append(current_chunk.strip())return chunks# 使用示例
splitter = MarkerBasedSplitter(markers=["## ", "# ", "### "],chunk_size=1000,chunk_overlap=200
)

2. 代码感知分割器

class CodeAwareTextSplitter(TextSplitter):def __init__(self, language: str, **kwargs):super().__init__(**kwargs)self.language = languagedef split_text(self, text: str) -> List[str]:chunks = []current_chunk = ""in_code_block = Falsefor line in text.split('\n'):# 检测代码块开始和结束if line.startswith('```'):in_code_block = not in_code_blockcurrent_chunk += line + '\n'continue# 如果在代码块内,保持完整性if in_code_block:current_chunk += line + '\n'else:if len(current_chunk) + len(line) > self.chunk_size:chunks.append(current_chunk.strip())current_chunk = lineelse:current_chunk += line + '\n'if current_chunk:chunks.append(current_chunk.strip())return chunks

优化技巧

1. 保持语义完整性

class SemanticAwareTextSplitter(TextSplitter):def __init__(self, sentence_endings: List[str] = ['.', '!', '?'], **kwargs):super().__init__(**kwargs)self.sentence_endings = sentence_endingsdef split_text(self, text: str) -> List[str]:chunks = []current_chunk = ""for sentence in self._split_into_sentences(text):if len(current_chunk) + len(sentence) > self.chunk_size:if current_chunk:chunks.append(current_chunk.strip())current_chunk = sentenceelse:current_chunk += ' ' + sentenceif current_chunk:chunks.append(current_chunk.strip())return chunksdef _split_into_sentences(self, text: str) -> List[str]:sentences = []current_sentence = ""for char in text:current_sentence += charif char in self.sentence_endings:sentences.append(current_sentence.strip())current_sentence = ""if current_sentence:sentences.append(current_sentence.strip())return sentences

2. 重叠处理优化

def _merge_splits(self, splits: List[str], chunk_overlap: int) -> List[str]:"""优化重叠区域的处理"""if not splits:return splitsmerged = []current_doc = splits[0]for next_doc in splits[1:]:if len(current_doc) + len(next_doc) <= self.chunk_size:current_doc += '\n' + next_docelse:merged.append(current_doc)current_doc = next_docmerged.append(current_doc)return merged

自定义检索器

检索器是 RAG 系统的核心组件,负责从向量存储中检索相关文档。虽然 LangChain 提供了多种内置检索器,但有时我们需要自定义检索器以实现特定的检索逻辑或集成专有的检索算法。

01. 内置检索器与自定义技巧

LangChain 提供了多种内置检索器,如 SimilaritySearch、MMR(最大边际相关性)等。但在某些情况下,我们可能需要自定义检索器以满足特定需求。

为什么要自定义检索器?

  1. 实现特定的相关性计算方法
  2. 集成专有的检索算法
  3. 优化检索结果的多样性和相关性
  4. 实现特定领域的上下文感知检索

自定义检索器的基本架构

from langchain.retrievers import BaseRetriever
from langchain.schema import Document
from typing import Listclass CustomRetriever(BaseRetriever):def __init__(self, vectorstore):self.vectorstore = vectorstoredef get_relevant_documents(self, query: str) -> List[Document]:# 实现自定义检索逻辑results = []# ... 检索过程 ...return resultsasync def aget_relevant_documents(self, query: str) -> List[Document]:# 异步版本的检索逻辑return await asyncio.to_thread(self.get_relevant_documents, query)

实用示例:自定义检索器

1. 混合检索器

结合多种检索方法,如关键词搜索和向量相似度搜索:

from langchain.retrievers import BM25Retriever
from langchain.vectorstores import FAISSclass HybridRetriever(BaseRetriever):def __init__(self, vectorstore, documents):self.vectorstore = vectorstoreself.bm25 = BM25Retriever.from_documents(documents)def get_relevant_documents(self, query: str) -> List[Document]:bm25_results = self.bm25.get_relevant_documents(query)vector_results = self.vectorstore.similarity_search(query)# 合并结果并去重all_results = bm25_results + vector_resultsunique_results = list({doc.page_content: doc for doc in all_results}.values())return unique_results[:5]  # 返回前5个结果

2. 上下文感知检索器

考虑查询的上下文信息进行检索:

class ContextAwareRetriever(BaseRetriever):def __init__(self, vectorstore):self.vectorstore = vectorstoredef get_relevant_documents(self, query: str, context: str = "") -> List[Document]:# 结合查询和上下文enhanced_query = f"{context} {query}".strip()# 使用增强的查询进行检索results = self.vectorstore.similarity_search(enhanced_query, k=5)# 根据上下文对结果进行后处理processed_results = self._post_process(results, context)return processed_resultsdef _post_process(self, results: List[Document], context: str) -> List[Document]:# 实现基于上下文的后处理逻辑# 例如,根据上下文调整文档的相关性得分return results

优化技巧

  1. 动态权重调整:根据查询类型或领域动态调整不同检索方法的权重。

  2. 结果多样性:实现类似 MMR 的算法,确保检索结果的多样性。

  3. 性能优化:对于大规模数据集,考虑使用近似最近邻(ANN)算法。

  4. 缓存机制:实现智能缓存,存储常见查询的结果。

  5. 反馈学习:根据用户反馈或系统性能指标不断优化检索策略。

class AdaptiveRetriever(BaseRetriever):def __init__(self, vectorstore):self.vectorstore = vectorstoreself.cache = {}self.feedback_data = []def get_relevant_documents(self, query: str) -> List[Document]:if query in self.cache:return self.cache[query]results = self.vectorstore.similarity_search(query, k=10)diverse_results = self._apply_mmr(results, query)self.cache[query] = diverse_results[:5]return self.cache[query]def _apply_mmr(self, results, query, lambda_param=0.5):# 实现 MMR 算法# ...def add_feedback(self, query: str, doc_id: str, relevant: bool):self.feedback_data.append((query, doc_id, relevant))if len(self.feedback_data) > 1000:self._update_retrieval_strategy()def _update_retrieval_strategy(self):# 基于反馈数据更新检索策略# ...

测试和验证

在实际应用自定义组件时,建议进行以下测试:

def test_loader():loader = CustomCSVLoader("path/to/test.csv")documents = loader.load()assert len(documents) > 0assert all(isinstance(doc, Document) for doc in documents)def test_splitter():text = """长文本内容..."""splitter = CustomTextSplitter(chunk_size=1000, chunk_overlap=200)chunks = splitter.split_text(text)# 验证分割结果assert all(len(chunk) <= splitter.chunk_size for chunk in chunks)# 检查重叠if len(chunks) > 1:for i in range(len(chunks)-1):overlap = splitter._get_overlap(chunks[i], chunks[i+1])assert overlap <= splitter.chunk_overlapdef test_retriever():vectorstore = FAISS(...)  # 初始化向量存储retriever = CustomRetriever(vectorstore)query = "测试查询"results = retriever.get_relevant_documents(query)assert len(results) > 0assert all(isinstance(doc, Document) for doc in results)

自定义组件的最佳实践

  1. 模块化设计:将自定义组件设计为可重用和可组合的模块。
  2. 性能优化:注意大规模数据处理的性能,使用异步方法和批处理。
  3. 错误处理:实现健壮的错误处理机制,确保组件在各种情况下都能正常工作。
  4. 可配置性:提供灵活的配置选项,使组件易于适应不同的使用场景。
  5. 文档和注释:为自定义组件提供详细的文档和代码注释,方便团队协作和维护。
  6. 测试覆盖:编写全面的单元测试和集成测试,确保组件的可靠性。
  7. 版本控制:使用版本控制系统管理自定义组件的代码,便于追踪变更和回滚。

结论

通过自定义 LangChain 组件,我们可以构建更加灵活和高效的 RAG 应用。无论是文档加载器、分割器还是检索器,定制化都能帮助我们更好地满足特定领域或场景的需求。在实践中,要注意平衡自定义的灵活性和系统的复杂性,确保所开发的组件不仅功能强大,而且易于维护和扩展。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/832172.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

《Django 5 By Example》阅读笔记:p17-p53

《Django 5 By Example》学习第2天,p17-p53总结,总计37页。 一、技术总结 1.数据库迁移 python manage.py makemigrations blog python manage.py sqlmigrate blog 0001 python manage.py migrate 2.ORM Django自带ORM。 3.view (1)定义 p42, A Django view is just a Python…

CentOS虚拟机无法查看ipv4地址

CentOS默认没有开启ens33 vi /etc/sysconfig/network-scripts/ifcfg-ens33将最后一行的ONBOOT=no修改为ONBOOT=yes 重启网卡服务 systemctl restart network然后 ip addr 查看ip目前这个是动态ip 如果要静态ip 继续编辑网卡配置文件ifcfg-ens33,将BOOTPROTO=dhcp修改为BOOTPRO…

人工智能是这样理解“情绪”的

前一篇:《人工智能模型训练:从不同格式文件中读取训练数据集》 前言:在前面的内容中,我们经常提到“特征”,那么如何表示特征呢?举个例子,在日常生活中,我们描述一个快递包装盒时可能会提到它的高度、宽度和深度(这三个值就是盒子的特征,当然也可以用颜色、重量、材料…

利用卷积神经网络(CNN)进行花朵分类任务

一、卷积神经网络 卷积神经网络(Convolutional Neural Netword,CNN)是一种深度学习模型,它在图像识别、视频分析、自然语言处理等领域表现出色。CNN 的核心思想是利用卷积运算来提取输入数据的特征,并且能够保持空间层次结构。 卷积神经网络的架构如下:我们今天的重点是利…

南沙C++信奥赛老师解一本通题 1385:团伙(group)

​ 【题目描述】在某城市里住着n个人,任何两个认识的人不是朋友就是敌人,而且满足: 1、我朋友的朋友是我的朋友; 2、我敌人的敌人是我的朋友; 所有是朋友的人组成一个团伙。告诉你关于这n个人的m条信息,即某两个人是朋友,或者某两个人是敌人,请你编写一个程序,计算出这…

Docker:部署kkFileView所有格式文档在线预览服务

前言 kkFileView是一个文档在线预览服务,基本支持主流文档格式预览,目前支持的文件类型如下:支持 doc, docx, xls, xlsx, xlsm, ppt, pptx, csv, tsv, dotm, xlt, xltm, dot, dotx,xlam, xla 等 Office 办公文档 支持 wps, dps, et, ett, wpt 等国产 WPS Office 办公文档 支…

学校厕所防欺凌检测系统

学校厕所防欺凌检测系统通过在关键区域安装的音频和视频监控设备,学校厕所防欺凌检测系统实时捕捉现场的声音和画面。AI音频分析技术能够对前端音频进行实时处理,当系统识别到“救命”、“打架”、“老师快来”等敏感词汇时,会自动触发预警机制,联动值班老师或校园安全中心…

物流园区烟火烟雾检测系统

物流园区烟火烟雾检测系统通过在园区关键位置安装的高清摄像头,物流园区烟火烟雾检测系统实现对监控区域的无人值守和不间断工作。系统利用先进的AI视觉算法,能够主动发现监控区域内的烟雾和火灾苗头,并进行实时分析报警。与传统的火灾监测系统相比,该系统不需要依赖其他传…

错误代码的个人见解以及逻辑分析题

一、代码错误分析代码中的错误: 1.src 指针指向字符串字面值,不可修改: 字符串 "hello,world" 是存储在只读区域的常量字符串,不能通过指针直接修改。 如果需要倒序操作,需要把字符串复制到一个可修改的内存中。2.dest 未正确分配内存: 在 malloc(len) 时,没有…

docx 生成word报告

# -*- coding: utf-8 -*- import base64 import os from io import BytesIO from docx import Document from docx.shared import Inches, Pt from bs4 import BeautifulSoup from matplotlib import pyplot as plt from wordcloud import WordCloud # 设置全局字体 plt.rcPara…

leetcode算法题-有效的括号(简单)

有效的括号(简单) leetcode:https://leetcode.cn/problems/valid-parentheses/description/ 前言 防止脑袋生锈,做一下leetcode的简单算法题,难得也做不来哈哈。 大佬绕道,小白可看。 题目描述 给定一个只包括 (,),{,},[,] 的字符串 s ,判断字符串是否有效。 有效字符…

30+企业高管齐聚!医疗器械企业渠道优化与健康增长主题沙龙成功举办

10月29日,深圳医疗器械行业协会携手纷享销客,共同举办了一场以“渠道优化与健康增长”为主题,探索医疗器械企业在新形势下渠道管理及落地实践的沙龙活动。此次活动吸引了33位医疗器械企业的管理层,共同探寻医疗器械企业营销增长的新思路、新渠道与新路径。<活动照片>…