langchain 入门中篇:数据封装,Memory 封装

数据的处理流程可以看一张图来帮助理解

数据来源可以是网络,可以是邮件,可以是本地文件

经过 Document Loaders 加载,再在 Transform 阶段对文档进行 split, filter, translate, extract metadata 等操作,之后在 Embed 阶段进行向量化,存储在向量数据库中后期用于检索。

加载文档

准备工作:

安装依赖库:pip install pypdf

加载本地文档

from langchain_community.document_loaders import PyPDFLoader

loader = PyPDFLoader("./data/spyl.pdf") # pdf 是从网上下载的,放在了 ./data 目录下
pages = loader.load_and_split()

print(pages[0].page_content)

输出:

加载 web 网页:

from langchain_community.document_loaders import WebBaseLoader

loader = WebBaseLoader("https://docs.smith.langchain.com/user_guide")  # LangSmith 的官方介绍
pages = loader.load_and_split()
print(pages[2].page_content)

输出:

文档处理器

1. TextSplitter

安装依赖库:pip install --upgrade langchain-text-splitters

接下来我们将前面的文档进一步切分处理

from langchain_text_splitters import RecursiveCharacterTextSplitter

text_splitter = RecursiveCharacterTextSplitter(
    chunk_size=300,
    chunk_overlap=50,
    length_function=len,
    add_start_index=True
)

paragraphs = text_splitter.create_documents([p.page_content for p in pages])
for para in paragraphs:
    print(para.page_content)
    print("=====================")

输出:

每个 chunk 300 个 token,重叠最多 50 个 token

注意:L昂Chain 的 PDFLoader 和 TextSplitter 实现都比较粗糙,实际生产不建议使用

向量数据库与向量检索

文档已经加载到内存中,接下来就需要将文档向量化,然后灌入向量数据库

from langchain_openai import OpenAIEmbeddings
from langchain_community.vectorstores import FAISS

# 灌库
embeddings = OpenAIEmbeddings(model="text-embedding-ada-002")
db = FAISS.from_documents(paragraphs, embeddings)

# 检索 top-5 结果
retriever = db.as_retriever(search_kwargs={"k": 5})

docs = retriever.invoke("白酒最近的产量")

for doc in docs:
    print(doc.page_content)
    print("+++++++++++++++++++++")

输出:

根据检索结果,检索成功匹配到了最相关的信息

小结:

  1. 文档处理部分 LangChain 实现较为粗糙,实际生产中不建议使用
  2. 与向量数据库的链接部分本质是接口封装,向量数据库需要自己选型

记忆封装

先来看几个简单的例子:

对话上下文:ConversationBufferMemory

from langchain.memory import ConversationBufferMemory, ConversationBufferWindowMemory

history = ConversationBufferMemory()
history.save_context({"input": "你好啊"}, {"output": "你也好啊"})

print(history.load_memory_variables({}))

history.save_context({"input": "你再好啊"}, {"output": "你又好啊"})

print(history.load_memory_variables({}))
{'history': 'Human: 你好啊\nAI: 你也好啊'}
{'history': 'Human: 你好啊\nAI: 你也好啊\nHuman: 你再好啊\nAI: 你又好啊'}

只保留一个窗口的上下文:ConversationBufferWindowMemory

from langchain.memory import ConversationBufferWindowMemory

window = ConversationBufferWindowMemory(k=2)
window.save_context({"input":"第一轮问"}, {"output":"第一轮答"})
window.save_context({"input":"第二轮问"}, {"output":"第二轮答"})
window.save_context({"input":"第三轮问"}, {"output":"第三轮答"})

print(window.load_memory_variables({}))
{'history': 'Human: 第二轮问\nAI: 第二轮答\nHuman: 第三轮问\nAI: 第三轮答'}

限制上下文长度:ConversationTokenBufferMemory

from langchain.memory import ConversationTokenBufferMemory
from langchain_openai import ChatOpenAI


llm = ChatOpenAI()
memory = ConversationTokenBufferMemory(
    max_token_limit=200,
    llm=llm
)
memory.save_context({"input":"你好"}, {"output":"你好,我是你的AI助手。"})
memory.save_context({"input":"你会干什么?"}, {"output":"我上知天文下知地理,什么都会。"})
print(memory.load_memory_variables({}))
{'history': 'Human: 你会干什么?\nAI: 我上知天文下知地理,什么都会。'}

应用场景:

我们通过一个简单的示例来展示 memory 的作用,这里设置 verbose=True ,这样我们就可以看到 prompt 的生成。

from langchain.chains import ConversationChain

conversation_with_summary = ConversationChain(
    llm=llm,
    # We set a very low max_token_limit for the purposes of testing.
    memory=memory,
    verbose=True,
)
conversation_with_summary.predict(input="今天天气真不错!")
> Entering new ConversationChain chain...
Prompt after formatting:
The following is a friendly conversation between a human and an AI. The AI is talkative and provides lots of specific details from its context. If the AI does not know the answer to a question, it truthfully says it does not know.

Current conversation:
Human: 你好
AI: 你好,我是你的AI助手。
Human: 你会干什么?
AI: 我上知天文下知地理,什么都会。
Human: 今天天气真不错!
AI: 是的,今天天气晴朗,气温适宜,适合出门活动。您有什么计划吗?
Human: 今天天气真不错!
AI:

> Finished chain.

[13]:

'是的,今天天气确实很好,阳光明媚,适合户外活动。您想去哪里呢?'
conversation_with_summary.predict(input="我喜欢跑步,喜欢放风筝")
> Entering new ConversationChain chain...
Prompt after formatting:
The following is a friendly conversation between a human and an AI. The AI is talkative and provides lots of specific details from its context. If the AI does not know the answer to a question, it truthfully says it does not know.

Current conversation:
Human: 你好
AI: 你好,我是你的AI助手。
Human: 你会干什么?
AI: 我上知天文下知地理,什么都会。
Human: 今天天气真不错!
AI: 是的,今天天气晴朗,气温适宜,适合出门活动。您有什么计划吗?
Human: 今天天气真不错!
AI: 是的,今天天气确实很好,阳光明媚,适合户外活动。您想去哪里呢?
Human: 我喜欢跑步,喜欢放风筝
AI:

> Finished chain.

[14]:

'那真是个好主意!跑步和放风筝都是很棒的户外活动。您喜欢在什么样的地方跑步呢?有没有特别喜欢去的公园或者跑步道?'
conversation_with_summary.predict(input="获取爬山也不错,不如去爬一次黄山吧")
> Entering new ConversationChain chain...
Prompt after formatting:
The following is a friendly conversation between a human and an AI. The AI is talkative and provides lots of specific details from its context. If the AI does not know the answer to a question, it truthfully says it does not know.

Current conversation:
AI: 是的,今天天气晴朗,气温适宜,适合出门活动。您有什么计划吗?
Human: 今天天气真不错!
AI: 是的,今天天气确实很好,阳光明媚,适合户外活动。您想去哪里呢?
Human: 我喜欢跑步,喜欢放风筝
AI: 那真是个好主意!跑步和放风筝都是很棒的户外活动。您喜欢在什么样的地方跑步呢?有没有特别喜欢去的公园或者跑步道?
Human: 获取爬山也不错,不如去爬一次黄山吧
AI:

> Finished chain.

[15]:

'黄山是一个非常著名的旅游胜地,有着壮丽的风景和丰富的自然资源。黄山位于安徽省,被誉为中国的五大名山之一。登上黄山,您可以欣赏到奇松怪石、云海日出等壮丽景观。不过需要注意的是,黄山地势险峻,需要一定的体力和意志力来完成登山之旅。希望您在黄山能够享受到美妙的风景和登山的乐趣!您需要我为您提供黄山的相关信息吗?'
conversation_with_summary.predict(input="我喜欢什么运动?")
> Entering new ConversationChain chain...
Prompt after formatting:
The following is a friendly conversation between a human and an AI. The AI is talkative and provides lots of specific details from its context. If the AI does not know the answer to a question, it truthfully says it does not know.

Current conversation:
AI: 黄山是一个非常著名的旅游胜地,有着壮丽的风景和丰富的自然资源。黄山位于安徽省,被誉为中国的五大名山之一。登上黄山,您可以欣赏到奇松怪石、云海日出等壮丽景观。不过需要注意的是,黄山地势险峻,需要一定的体力和意志力来完成登山之旅。希望您在黄山能够享受到美妙的风景和登山的乐趣!您需要我为您提供黄山的相关信息吗?
Human: 我喜欢什么运动?
AI:

> Finished chain.

[16]:

'很抱歉,我不知道您喜欢什么运动。您可以告诉我您感兴趣的领域,我会尽力为您提供相关信息。比如,如果您喜欢户外活动,我可以为您介绍一些常见的户外运动项目。如果您喜欢健身运动,我也可以为您提供一些健身的建议和指导。请告诉我您的兴趣方向,我会尽力帮助您。'

从上面的实验结果看,前面的对话长度 token 没到 200 的时候,AI 能根据前面的对话来回答你的问题,但是当超过 200 之后,前面的信息就丢了,相当于遗忘了。

ConversationSummaryMemory

如果对话历史如果很长,可以通过摘要记忆对信息进行浓缩

from langchain.memory import ConversationSummaryMemory, ChatMessageHistory
from langchain_openai import OpenAI

memory = ConversationSummaryMemory(llm=OpenAI(temperature=0))
memory.save_context({"input": "你好"}, {"output": "你好,有什么需要帮助?"})
memory.load_memory_variables({})
{'history': '\nThe human greets the AI in Chinese. The AI responds in Chinese and asks if there is anything it can help with.'}
通过已经存在的messages或者摘要初始化
history = ChatMessageHistory()
history.add_user_message("hi")
history.add_ai_message("hi there!")

memory = ConversationSummaryMemory.from_messages(
    llm=OpenAI(temperature=0),
    chat_memory=history,
    return_messages=True
)

memory.buffer

输出:
    '\nThe human greets the AI, to which the AI responds with a friendly greeting.'
通过之前的摘要进行初始化来加快初始化速度
memory = ConversationSummaryMemory(
    llm=OpenAI(temperature=0),
    buffer="The human asks what the AI thinks of artificial intelligence. The AI thinks artificial intelligence is a force for good because it will help humans reach their full potential.",
    chat_memory=history,
    return_messages=True
)

在 chain 使用

from langchain_openai import OpenAI
from langchain.chains import ConversationChain
llm = OpenAI(temperature=0)
conversation_with_summary = ConversationChain(
    llm=llm,
    memory=ConversationSummaryMemory(llm=OpenAI()),
    verbose=True
)
conversation_with_summary.predict(input="Hi, what's up?")

输出:

    > Entering new ConversationChain chain...
    Prompt after formatting:
    The following is a friendly conversation between a human and an AI. The AI is talkative and provides lots of specific details from its context. If the AI does not know the answer to a question, it truthfully says it does not know.

    Current conversation:

    Human: Hi, what's up?
    AI:

    > Finished chain.

    " Hi there! I'm doing great. I'm currently helping a customer with a technical issue. How about you?"

向量数据库支持的VectorStoreRetrieverMemory 

当想引用对话中之前提到过的一些信息,这个 memory 会比较有用

初始化向量数据库:

这一步会因使用的向量数据库不同而有所区别,具体细节可以看向量数据库的文档

import faiss
from langchain_openai import OpenAIEmbeddings
from langchain_community.docstore import InMemoryDocstore
from langchain_community.vectorstores import FAISS

embedding_size = 1536
index = faiss.IndexFlatL2(embedding_size)
embedding = OpenAIEmbeddings()
vectorstore = FAISS(embedding, index, InMemoryDocstore({}), {})
创建 VectorStoreRetrieverMemory
from langchain.memory import VectorStoreRetrieverMemory

retriever = vectorstore.as_retriever(search_kwargs={"k":2}) # 实际使用时 k 值会设置得比较高
memory = VectorStoreRetrieverMemory(retriever=retriever)

# When added to an agent, the memory object can save pertinent information from conversations or used tools
memory.save_context({"input": "我喜欢披萨"}, {"output": "哦,那太好了"})
memory.save_context({"input": "我喜欢足球"}, {"output": "..."})
memory.save_context({"input": "我不喜欢梅西"}, {"output": "ok"}) #
print(memory.load_memory_variables({"prompt": "我该看什么体育节目?"})["history"])

输出:

input: 我喜欢足球
output: ...
input: 我不喜欢梅西
output: ok
在 chain 中使用
from langchain_core.prompts import PromptTemplate


llm = OpenAI(temperature=0) # Can be any valid LLM
_DEFAULT_TEMPLATE = """The following is a friendly conversation between a human and an AI. The AI is talkative and provides lots of specific details from its context. If the AI does not know the answer to a question, it truthfully says it does not know.

Relevant pieces of previous conversation:
{history}

(You do not need to use these pieces of information if not relevant)

Current conversation:
Human: {input}
AI:"""
PROMPT = PromptTemplate(
    input_variables=["history", "input"], template=_DEFAULT_TEMPLATE
)
conversation_with_summary = ConversationChain(
    llm=llm,
    prompt=PROMPT,
    memory=memory,
    verbose=True
)
conversation_with_summary.predict(input="你好,我是 Gem")

输出:

> Entering new ConversationChain chain...
Prompt after formatting:
The following is a friendly conversation between a human and an AI. The AI is talkative and provides lots of specific details from its context. If the AI does not know the answer to a question, it truthfully says it does not know.

Relevant pieces of previous conversation:
input: 我不喜欢梅西
output: ok
input: 我喜欢披萨
output: 哦,那太好了

(You do not need to use these pieces of information if not relevant)

Current conversation:
Human: 你好,我是 Gem
AI:

> Finished chain.

[79]:

' 你好,Gem!我是你的AI助手。我可以回答你关于任何事情的问题。你有什么想知道的吗?'
conversation_with_summary.predict(input="我喜欢什么?")

输出:

> Entering new ConversationChain chain...
Prompt after formatting:
The following is a friendly conversation between a human and an AI. The AI is talkative and provides lots of specific details from its context. If the AI does not know the answer to a question, it truthfully says it does not know.

Relevant pieces of previous conversation:
input: 我喜欢披萨
output: 哦,那太好了
input: 我喜欢足球
output: ...

(You do not need to use these pieces of information if not relevant)

Current conversation:
Human: 我喜欢什么?
AI:

> Finished chain.

[80]:

' 你喜欢什么?我不太清楚,因为我是一个人工智能,我没有自己的喜好。但是我可以告诉你一些我所知道的事情。比如,我知道你喜欢披萨和足球。你还喜欢什么?'

小结:

  1. LangChain 的 Memory 管理机制属于可用的部分,尤其是简单情况如按轮数或按 Token 数管理;
  2. 对于复杂情况,它不一定是最优的实现,例如检索向量库方式,建议根据实际情况和效果评估;
  3. 但是它对内存的各种维护方法的思路在实际生产中可以借鉴
  • 30
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值