首页 LangChain 🦜️🔗 与 知识库问答实践
文章
取消

LangChain 🦜️🔗 与 知识库问答实践

查看 jupyter notebook

复习与准备

1
2
3
4
5
6
import os
from openai import OpenAI

os.environ['HTTP_PROXY'] = 'http://127.0.0.1:xxxxxx'
os.environ['HTTPS_PROXY'] = 'http://127.0.0.1:xxxxxx'
os.environ["OPENAI_API_KEY"] = "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"

Document loaders(文档加载)旨在从源中加载数据构建 Document。LangChain 中 Document是包含文本和与其关联的元数据。LangChain 中包含加载简单 txt 文件的文档加载器,用于加载任何网页的文本内容的加载器,甚至还包含用于加载 YouTube 视频的转录稿的加载器。以下是一个最简单的从文件中读取文本加载数据的 Document 的示例:

1
2
3
4
from langchain.document_loaders import TextLoader

loader = TextLoader("./txt/dutai.txt", encoding="utf-8")
loader.load()
1
[Document(page_content='《人工智能基础》是......

Document transformers(文档转换)旨在处理文档,以完成各种转换任务,如将文档格式化为Q&A 形式,去除文档中的冗余内容等,从而更好地满足不同应用程序的需求。一个简单的文档转换示例是将长文档分割成较小的部分,以适应不同模型的上下文窗口大小。LangChain 中有许多内置的文档转换器,使拆分、合并、过滤和其他操作文档变得很容易。以下是对长文档进行拆分的代码示例:

1
2
3
4
5
6
7
8
9
10
11
from langchain.text_splitter import RecursiveCharacterTextSplitter

with open("./txt/dutai.txt", "r", encoding="utf-8") as f:
    state_of_the_union = f.read()
    text_splitter = RecursiveCharacterTextSplitter(
        chunk_size=500,  # 每个块的长度最大为 500 个字符
    )
    texts = text_splitter.create_documents([state_of_the_union])

for text in texts:
    print(text)
1
page_content='《人工智能基础》是大......

Text embedding models(文本嵌入模型)旨在将非结构化文本转换为嵌入表示。基于文本的嵌入表示,可以进行语义搜索,查找最相似的文本片段。Embeddings 类则是用于与文本嵌入模型进行交互,并为不同的嵌入模型提供统一标准接口,包括 OpenAI、Cohere 等。LangChain 中的 Embeddings类公开了两个方法:一个用于文档嵌入表示,另一个用于查询嵌入表示。前者输入多个文本,后者输入单个文本。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
from langchain_openai import OpenAIEmbeddings

embeddings_model = OpenAIEmbeddings()
embeddings = embeddings_model.embed_documents(
    [
        "Hi there!",
        "Oh, hello!",
        "What's your name?",
        "My friends call me World",
        "Hello World!"
    ]
)
print((len(embeddings)), len(embeddings[0]))
embedded_query = embeddings_model.embed_query("What was the name mentioned in this session?")
print(embedded_query[:5])
1
2
5 1536
[0.0071710232714532396, 0.00038795966115911845, 0.02249002941018872, 0.0038081521876218607, -0.007490030552940845]

Vector Stores(向量存储)是存储和检索非结构化数据的主要方式之一。它首先将数据转化为嵌入表示,然后存储这些生成的嵌入向量。在查询阶段,系统会利用这些嵌入向量来检索与查询内容“最相似”的文档。向量存储的主要任务是保存这些嵌入数据并执行基于向量的搜索。LangChain能够与多种向量数据库集成,如 Chroma、FAISS 和 Lance 等。以下给出了使用 FAISS 向量数据库的代码示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
from langchain.document_loaders import TextLoader
from langchain_openai import OpenAIEmbeddings
from langchain.text_splitter import CharacterTextSplitter
from langchain.vectorstores import FAISS

# Load the document, split it into chunks, embed each chunk and load it into the vector store.
raw_documents = TextLoader("./txt/dutai.txt", encoding="utf-8").load()
text_splitter = CharacterTextSplitter(chunk_size=500, chunk_overlap=0)
documents = text_splitter.split_documents(raw_documents)
db = FAISS.from_documents(documents, OpenAIEmbeddings())
# Do Simiarity Search
query = "因为这门课是去年新开的,所以参考会很少。"
docs = db.similarity_search(query)
print(docs[0].page_content)

1
当然,这门课也有亮点。最后复习在看ppt的时候,我发现......

Retrievers(检索器)是一个接口,其功能是基于非结构化查询返回相应的文档。检索器不需要存储文档,只需要能根据查询返回结果即可。可以通过get_relevant_documents方法或者通过异步调用aget_relevant_documents方法获得与查询最相关的文档。

基于向量存储的检索器(Vector store-backed retriever)是使用向量存储检索文档的检索器。它是向量存储类的轻量级包装器,使其符合 Retriever 接口。使用向量存储实现的搜索方法,如相似性搜索和 MMR,来查询使用向量存储的文本。接下来是一个基于向量存储的检索器的代码示例:

1
2
3
retriever = db.as_retriever()
docs = retriever.get_relevant_documents("因为这门课是去年新开的,所以参考会很少。")
print(docs[0].page_content)
1
当然,这门课也有亮点。最后复习在看ppt的时候,我发现....

知识库问答实践

大语言模型虽然可以很好的回答很多领域的 各种问题,但是由于其知识是通过语言模型训练以及指令微调等方式注入到模型参数中,因此针 对本地知识库中的内容,大语言模型很难通过此前的方式有效的进行学习。通过 LangChain 框架, 可以有效的融合本地知识库内容与大语言模型的知识问答能力。

基于 LangChain 的知识问答系统框架如图所示。知识库问答系统主要包含以下几个主要步骤:

  1. 收集领域知识数据构造知识库,这些数据应当能够尽可能的全面覆盖问答需求;
  2. 将知识库中的对非结构数据进行文本提取和文本拆分,得到文本块;
  3. 利用嵌入向量表示模型给出文本块嵌入表示,并利用向量数据库进行保存
  4. 根据用户输入信息的嵌入表示,通过向量数据库检索得到最相关文本片段,利用提示词模板与用户输入以及历史消息合并输入大语言模型;
  5. 将大语言模型结果返回用户。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
from langchain.document_loaders import DirectoryLoader
from langchain_openai import OpenAIEmbeddings
from langchain.text_splitter import CharacterTextSplitter
from langchain.vectorstores import Chroma
from langchain.chains import ChatVectorDBChain, ConversationalRetrievalChain
from langchain_openai import ChatOpenAI
from langchain.chains import RetrievalQA
from langchain.prompts.chat import (
    ChatPromptTemplate,
    SystemMessagePromptTemplate,
    AIMessagePromptTemplate,
    HumanMessagePromptTemplate,
)

# 从本地读取相关数据
loader = DirectoryLoader(
    './txt/', glob='*.txt', show_progress=True
)
docs = loader.load()

# 将文件进行切分
text_splitter = CharacterTextSplitter(
    chunk_size=300,  # 或者 1000
    chunk_overlap=0
)
docs_split = text_splitter.split_documents(docs)
print("len of docs_split:", len(docs_split))

# 初始化 OpenAI Embeddings
embeddings = OpenAIEmbeddings()

# 将数据存入 Chroma 向量存储
vector_store = Chroma.from_documents(docs, embeddings)
# 初始化检索器,使用向量存储
retriever = vector_store.as_retriever()


system_template = """
Use the following pieces of context to answer the users question.
If you don't know the answer, just say that you don't know, don't try to make up an answer.
Answering these questions in Chinese.
-----------
{question}
-----------
{chat_history}
"""

# 构建初始 Messages 列表
messages = [
    SystemMessagePromptTemplate.from_template(system_template),
    HumanMessagePromptTemplate.from_template('{question}')
]

# 初始化 Prompt 对象
prompt = ChatPromptTemplate.from_messages(messages)

# 初始化大语言模型,使用 OpenAI API
llm=ChatOpenAI(temperature=0.1, max_tokens=2048)

# 初始化问答链
qa = ConversationalRetrievalChain.from_llm(llm,retriever,condense_question_prompt=prompt)

chat_history = []
question = "今年考了什么题目?"
result = qa.invoke({'question': question, 'chat_history': chat_history})
chat_history.append((question, result['answer']))
print(result['answer'])
1
2
3
4
5
6
7
8
9
10
11
12
13
14
100%|██████████| 1/1 [00:00<00:00, 125.42it/s]


len of docs_split: 6
今年考试的大题包括以下内容:

1. 聊聊为什么用卷积?好处?
2. 列举一些人工智能研究方向、热点。
3. αβ过程和剪枝。
4. 卷积计算。
5. A*算法求路径。
6. 归约法证明结论。

这些是今年考试的大题内容。

如果要连续提问,使用以下代码:

1
2
3
4
5
6
while True:
    question = input('问题:')
    # 开始发送问题 chat_history 为必须参数, 用于存储对话历史
    result = qa({'question': question, 'chat_history': chat_history})
    chat_history.append((question, result['answer']))
    print(result['answer'])
本文由作者按照 CC BY 4.0 进行授权

LangChain 🦜️🔗 与知识增强生成

Improving Language Understanding by Generative Pre-Training