使用LangChain实现自动写作

使用LangChain制作文档检索器和生成器的教程

LangChain 是 Python 和 JavaScript 中最常用的 RAG 库之一。检索增强生成是一种用更多文档增强大型语言模型的技术,而无需经历针对特定任务进行微调的麻烦。

当我尝试使用大模型来帮助我写作时,我提出了这篇文章的概念,我发现简单的提示通常无法生成可靠的输出 - 因此 RAG 应用程序是最佳选择。

提取文档
LangChain 有一个名为“文档”的专有对象,允许用户加载文本以与其包一起使用。这些文档可以使用文本拆分器轻松拆分,并可以添加到链、输出解析器和 LLM 中。

我将使用我所有的中等博客文章进行培训,我已经将它们下载为 html 文件并将它们放置在项目目录中。

from os import listdir 
from os.path import isfile, join 

mypath = 'project_directory'
 onlyfiles = [f for f in listdir(mypath) if isfile(join(mypath, f))] 

过滤html格式的文件
onlyfiles = [x for x in onlyfiles if  'htm'  in x]

下一步是使用 langChain 将这些解析为可在 RAG 管道中使用的文档。

# 有很多 HTMLLoader 可供选择 from 
langchain_community.document_loaders import UnstructuredHTMLLoader #

数据字典将包含文档
data = {} 

i = 0 
for file in onlyfiles: 
    loader = UnstructuralHTMLLoader(file) 

    data[i] = loader.load( ) 

    i+= 1

这些文件的大小超过了许多 LLM 的消化能力。ChatGPT 3.5 的令牌标记窗口只有 4096 个,仅一篇文章就有大约 2000 个标记。为了扩大上下文窗口,我必须建立一个管道,让 LLM 可以从管道中检索这些文档。将文档分割成块也是一个不错的策略,因为这将有助于 LLM 生成这些内容,我还将删除每个文档中的一些不必要文本。

导入特定的文本分割器,在本例中我们将使用 TokenTextSplitter 
from langchain.text_splitter import TokenTextSplitter 


text_splitter = TokenTextSplitter(chunk_size= 1000 , chunk_overlap= 25 ) 
包含所有分割后文档的字典
 texts = {} 
for i in  range ( len (data)): 
texts字典的键是每个文件的标题
    texts[data[i][ 0 ].metadata[ 'source' ]] = text_splitter.split_documents(data [我])

文档已被提取,下一步是创建嵌入。我将使用 ChromaDB(本地)和 OpenAI 嵌入,但也可以使用其他向量存储和嵌入模型。

创建嵌入
简单来说,嵌入是一个接受字符串输入并输出数字向量的函数。嵌入函数本身就是一个广泛的主题,超出了本文的范围。

如果您有兴趣了解有关嵌入的更多信息,请访问OpenAI 嵌入文档。

from langchain_community.vectorstores import Chroma 
from langchain_openai.embeddings import OpenAIEmbeddings 

for i in  range ( len (texts)): 
    vectordb = Chroma.from_documents( 
获取文档列表
      texts[texts_keys[i]], 
Embedding 函数,我们是using OpenAI default
       embedding=OpenAIEmbeddings(api_key= 'your_open_ai_key' ), 
指定您想要这些内容的目录
      persist_directory= './LLM_train_embedding/Doc'
     ) 
将它们推入目录
    vectordb.persist()

检索retriever 
对于检索器的最简单实现,我使用了 ChromaDB 中的内置方法,它允许您搜索文档。有一些可用的搜索算法,您可以从此处了解。

用于基于向量存储的检索的内置 ChromaDb LangChain 方法
需要两个参数,即要使用的算法和搜索 kwargs,其中指定
每个算法的参数。 k 告诉要返回的文档数量等


定义检索器检索Defines the retriever
retriever = vectordb.as_retriever(search_type='mmr', search_kwargs ={'k':1})

获取检索器的文档Gets the document for the retriever
retriever.get_relevant_documents('What is a Sankey?')

检索是一个很大的主题,我鼓励您更多地了解它。出于本文的目的,我还将使用父文档检索器。

父文档检索:将大文档分解为较小块的检索方法,并允许检索器“了解”有关文档特定部分的更多信息。根据 LangChain 的实现,父文档检索需要向量存储和文档存储。

from langchain.retrievers import ParentDocumentRetriever 
from langchain.storage import InMemoryStore 

告诉如何分割子进程
child_splitter = TokenTextSplitter(chunk_size= 250 , chunk_overlap= 10 ) 
告诉如何分割父进程
parent_splitter = TokenTextSplitter(chunk_size= 1000 , chunk_overlap= 50 ) 

vectorstore = Chroma( 
    collection_name= "full_documents" , embedding_function=OpenAIEmbeddings(api_key= 'your_api_key' ) 

在内存中创建文档存储
 store = InMemoryStore()
此检索器采用向量存储和文档存储
Retriever = ParentDocumentRetriever( 
    vectorstore=vectorstore, 
    docstore=store, 
    child_splitter=child_splitter, 
   Parent_splitter = Parent_splitter 


# 添加子文档并拆分它们。
for i in  range ( len (data)): 
    retrieve.add_documents(data[i]) 

搜索文档
 retrieve.get_relevant_documents( 'What is a Sankey?' )

生成
现在,我将指示法律硕士使用检索文档进行生成。这将为 LLM 提供有关我写作风格的更多背景信息。

我们将使用 RetrievalQA 链

from langchain_core.output_parsers import StrOutputParser
from langchain_core.prompts import ChatPromptTemplate
from langchain_core.runnables import RunnablePassthrough
from langchain_openai import ChatOpenAI

# # 这是我使用的提示

# 它以 {context} 的形式接收文件,并由用户提供 {topic} 。
template = """Mimic the writing style in the context:
{context} and produce a blog on the topic

Topic: {topic}


"""

prompt = ChatPromptTemplate.from_template(template)

model = ChatOpenAI(api_key =api_key)

# 使用 LangCHain LCEL 提供提示并生成输出
chain = (
    {
        "context":itemgetter("topic") | retriever,
        "topic": itemgetter("topic"),

    }
    | prompt
    | model
    | StrOutputParser()
)
running the Chain
chain.invoke({"topic": "Pakistan "})

结论
这就是如何创建一个 LLM 机器人,模仿并尝试再现你自己的写作。我将对此进行实验,并尝试获得一些评估,看看 LLM 模仿我的写作有多 "好"。