LangChain 六大组件(三)| 豆包MarsCode AI 刷题

177 阅读19分钟

image.png

什么是链

简单的来说,就是链接LangChain的各个组件和功能——模型之间彼此链接,或模型与其他组件链接。

这种将多个组件相互链接,组合成一个链的想法简单但很强大。它简化了复杂应用程序的实现,并使之更加模块化,能够创建出单一的、连贯的应用程序,从而使调试、维护和改进应用程序变得容易。

链的实现

  • 首先LangChain通过设计好的接口,实现一个具体的链的功能。例如,LLM链(LLMChain)能够接受用户输入,使用 PromptTemplate 对其进行格式化,然后将格式化的响应传递给 LLM。这就相当于把整个Model I/O的流程封装到链里面。
  • 实现了链的具体功能之后,我们可以通过将多个链组合在一起,或者将链与其他组件组合来构建更复杂的链。

这里是一些LangChain提供的各种类型的预置链

image.png

LLMChain:最简单的链

LLMChain围绕着语言模型推理功能又添加了一些功能,整合了PromptTemplate、语言模型(LLM或聊天模型)和 Output Parser,相当于把Model I/O放在一个链中整体操作。它使用提示模板格式化输入,将格式化的字符串传递给 LLM,并返回 LLM 输出。

示例如下:

这是使用Model I/O实现版本

#----第一步 创建提示 
# 导入LangChain中的提示模板 
from langchain import PromptTemplate 
# 原始字符串模板
template = "{flower}的花语是?" 
# 创建LangChain模板 
prompt_temp = PromptTemplate.from_template(template) 
# 根据模板创建提示 
prompt = prompt_temp.format(flower='玫瑰') 
# 打印提示的内容 
print(prompt) 
#----第二步 创建并调用模型 
# 导入LangChain中的OpenAI模型接口 
from langchain import OpenAI 
# 创建模型实例 
model = OpenAI(temperature=0) 
# 传入提示,调用模型,返回结果 
result = model(prompt) print(result)

输出:

玫瑰的花语是?
爱情、浪漫、美丽、永恒、誓言、坚贞不渝。

使用LLMChain实现:

# 导入所需的库
from langchain import PromptTemplate, OpenAI, LLMChain
# 原始字符串模板
template = "{flower}的花语是?"
# 创建模型实例
llm = OpenAI(temperature=0)
# 创建LLMChain
llm_chain = LLMChain(
    llm=llm,
    prompt=PromptTemplate.from_template(template))
# 调用LLMChain,返回结果
result = llm_chain("玫瑰")
print(result)

输出:

{'flower': '玫瑰', 'text': '\n\n爱情、浪漫、美丽、永恒、誓言、坚贞不渝。'}

可以看到,使用LLMChain的代码更加简洁。

链的调用方式

刚才我们是直接调用的链对象。当我们像函数一样调用一个对象时,它实际上会调用该对象内部实现的__call__方法。

直接调用

prompt = PromptTemplate(
    input_variables=["flower", "season"],
    template="{flower}在{season}的花语是?",
)
llm_chain = LLMChain(llm=llm, prompt=prompt)
print(llm_chain({
    'flower': "玫瑰",
    'season': "夏季" }))

输出:

{'flower': '玫瑰', 'season': '夏季', 'text': '\n\n玫瑰在夏季的花语是爱的誓言,热情,美丽,坚定的爱情。'}

run方法(等价于调用_call_函数)

语句:

llm_chain("玫瑰")

等价于:

llm_chain.run("玫瑰")

predict方法

predict的调用很像run方法,但是predict输入键被指定为参数,而不是Python字典

result = llm_chain.predict(flower="玫瑰")
print(result)

apply方法

可以输入列表作为参数,一次性处理。

# apply允许您针对输入列表运行链
input_list = [
    {"flower": "玫瑰",'season': "夏季"},
    {"flower": "百合",'season': "春季"},
    {"flower": "郁金香",'season': "秋季"}
]
result = llm_chain.apply(input_list)
print(result)

输出:

'''[{'text': '\n\n玫瑰在夏季的花语是“恋爱”、“热情”和“浪漫”。'}, 
{'text': '\n\n百合在春季的花语是“爱情”和“友谊”。'},
 {'text': '\n\n郁金香在秋季的花语表达的是“热情”、“思念”、“爱恋”、“回忆”和“持久的爱”。'}]'''

generate方法

generate方法的返回值是一个LLMResult对象,而不是字符串。

result = llm_chain.generate(input_list)
print(result)

输出:

generations=[[Generation(text='\n\n玫瑰在夏季的花语是“热情”、“爱情”和“幸福”。', 
generation_info={'finish_reason': 'stop', 'logprobs': None})], 
[Generation(text='\n\n春季的花语是爱情、幸福、美满、坚贞不渝。', 
generation_info={'finish_reason': 'stop', 'logprobs': None})], 
[Generation(text='\n\n秋季的花语是“思念”。银色的百合象征着“真爱”,而淡紫色的郁金香则象征着“思念”,因为它们在秋天里绽放的时候,犹如在思念着夏天的温暖。', 
generation_info={'finish_reason': 'stop', 'logprobs': None})]] 
llm_output={'token_usage': {'completion_tokens': 243, 'total_tokens': 301, 'prompt_tokens': 58}, 'model_name': 'gpt-3.5-turbo-instruct'} 
run=[RunInfo(run_id=UUID('13058cca-881d-4b76-b0cf-0f9c831af6c4')), 
RunInfo(run_id=UUID('7f38e33e-bab5-4d03-b77c-f50cd195affb')), 
RunInfo(run_id=UUID('7a1e45fd-77ee-4133-aab0-431147186db8'))]

Sequential Chain:顺序链

SequentialChain,是LangChain框架中用于串联多个步骤或模块的一种链式结构。它允许开发者将一个大任务分解为多个小任务,并依次执行这些小任务,每个任务的输出成为下一个任务的输入。这种链式结构使得任务处理流程更加清晰、模块化,并提高了代码的可读性和可维护性。

RouterChain

  • 定义:RouterChain本身就是一个chain应用,能够根据用户输入进行下游子链(Destination Chain)的选择。
  • 原理:RouterChain接收用户输入后,会将其放入大语言模型(LLM)中,通过Prompt的形式让大语言模型来进行路由。或者,通过向量搜索的方式(如EmbeddingRouterChain)来匹配用户输入与下游子链的相关性。

记忆(Memory)

在学习记忆之前,先看一个简单的示例并打印出ConversationChain中的内置提示模板:

from langchain import OpenAI
from langchain.chains import ConversationChain

# 初始化大语言模型
llm = OpenAI(
    temperature=0.5,
    model_name="gpt-3.5-turbo-instruct"
)

# 初始化对话链
conv_chain = ConversationChain(llm=llm)

# 打印对话的模板
print(conv_chain.prompt.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.

Current conversation:
{history}
Human: {input}
AI:

这里有两个参数 {history}{input}

  • {history}  是存储会话记忆的地方,也就是人类和人工智能之间对话历史的信息。
  • {input}  是新输入的地方,你可以把它看成是和ChatGPT对话时,文本框中的输入。

image.png

那么当有了  {history}  参数,以及 Human  AI 这两个前缀,我们就能够把历史对话信息存储在提示模板中,并作为新的提示内容在新一轮的对话过程中传递给模型。—— 这就是记忆机制的原理

四种记忆模式:

image.png

ConversationBufferMemory

在LangChain中,通过ConversationBufferMemory(缓冲记忆)可以实现最简单的记忆机制。

# 设置OpenAI API密钥
import os

# 导入所需的库
from langchain_openai import ChatOpenAI
from langchain.chains import ConversationChain
from langchain.chains.conversation.memory import ConversationBufferMemory

# 初始化大语言模型
llm = ChatOpenAI(
    model=os.environ.get("LLM_MODELEND"),
    temperature=0.5,
)

# 初始化对话链
conversation = ConversationChain(llm=llm, memory=ConversationBufferMemory())

# 第一天的对话
# 回合1
conversation("我姐姐明天要过生日,我需要一束生日花束。")
print("第一次对话后的记忆:", conversation.memory.buffer)

# 回合2
conversation("她喜欢粉色玫瑰,颜色是粉色的。")
print("第二次对话后的记忆:", conversation.memory.buffer)

# 回合3 (第二天的对话)
conversation("我又来了,还记得我昨天为什么要来买花吗?")
print("/n第三次对话后时提示:/n", conversation.prompt.template)
print("/n第三次对话后的记忆:/n", conversation.memory.buffer)

输出:

第一次对话后的记忆: Human: 我姐姐明天要过生日,我需要一束生日花束。
AI: 那你可以考虑去当地的花店或者在线花店订购一束生日花束。如果你想要特别一点的,你可以告诉花店你姐姐喜欢的花或者颜色,让他们为你定制一束独特的花束。常见的适合生日的花有玫瑰、康乃馨、百合、郁金香等等。你也可以在花束中加入一些绿叶或者其他装饰,让花束更加美观。另外,你还可以选择不同的包装方式,比如用彩色纸张或者丝带包装,让花束更加精美。希望这些建议对你有所帮助,祝你姐姐生日快乐!
第二次对话后的记忆: Human: 我姐姐明天要过生日,我需要一束生日花束。
AI: 那你可以考虑去当地的花店或者在线花店订购一束生日花束。如果你想要特别一点的,你可以告诉花店你姐姐喜欢的花或者颜色,让他们为你定制一束独特的花束。常见的适合生日的花有玫瑰、康乃馨、百合、郁金香等等。你也可以在花束中加入一些绿叶或者其他装饰,让花束更加美观。另外,你还可以选择不同的包装方式,比如用彩色纸张或者丝带包装,让花束更加精美。希望这些建议对你有所帮助,祝你姐姐生日快乐!
Human: 她喜欢粉色玫瑰,颜色是粉色的。
AI: 那你可以去花店跟店员说你想要一束以粉色玫瑰为主的生日花束呀。你可以让他们挑选新鲜的粉色玫瑰,然后搭配一些白色的满天星或者绿色的尤加利叶来增加层次感和清新感。包装的话,可以选择粉色的包装纸,再系上一个粉色的丝带,这样整个花束就会非常协调和美观,很符合你姐姐喜欢粉色的喜好呢。
/n第三次对话后时提示:/n 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:
{history}
Human: {input}
AI:
/n第三次对话后的记忆:/n Human: 我姐姐明天要过生日,我需要一束生日花束。
AI: 那你可以考虑去当地的花店或者在线花店订购一束生日花束。如果你想要特别一点的,你可以告诉花店你姐姐喜欢的花或者颜色,让他们为你定制一束独特的花束。常见的适合生日的花有玫瑰、康乃馨、百合、郁金香等等。你也可以在花束中加入一些绿叶或者其他装饰,让花束更加美观。另外,你还可以选择不同的包装方式,比如用彩色纸张或者丝带包装,让花束更加精美。希望这些建议对你有所帮助,祝你姐姐生日快乐!
Human: 她喜欢粉色玫瑰,颜色是粉色的。
AI: 那你可以去花店跟店员说你想要一束以粉色玫瑰为主的生日花束呀。你可以让他们挑选新鲜的粉色玫瑰,然后搭配一些白色的满天星或者绿色的尤加利叶来增加层次感和清新感。包装的话,可以选择粉色的包装纸,再系上一个粉色的丝带,这样整个花束就会非常协调和美观,很符合你姐姐喜欢粉色的喜好呢。
Human: 我又来了,还记得我昨天为什么要来买花吗?
AI: 当然记得呀,你昨天说是因为你姐姐明天要过生日,所以你需要一束生日花束呀。

弊端

虽然这样简单直接地存储所有内容为LLM提供了最大量的信息,但是新输入中也包含了更多的Token(所有的聊天历史记录),这意味着响应时间变慢和更高的成本。而且,当达到LLM的令牌数(上下文窗口)限制时,太长的对话无法被记住。

ConversationBufferWindowMemory

ConversationBufferWindowMemory 是缓冲窗口记忆,它的思路就是只保存最新最近的几次人类和AI的互动。因此,它在之前的“缓冲记忆”基础上增加了一个窗口值 k。这意味着我们只保留一定数量的过去互动,然后“忘记”之前的互动。

# 设置OpenAI API密钥
import os

# 导入所需的库
from langchain_openai import ChatOpenAI
from langchain.chains import ConversationChain
from langchain.chains.conversation.memory import ConversationBufferWindowMemory

# 初始化大语言模型
llm = ChatOpenAI(
    temperature=0.5,
    model=os.environ.get("LLM_MODELEND"),
)

# 初始化对话链
conversation = ConversationChain(llm=llm, memory=ConversationBufferWindowMemory(k=1))

# 第一天的对话
# 回合1
result = conversation("我姐姐明天要过生日,我需要一束生日花束。")
print(result)
# 回合2
result = conversation("她喜欢粉色玫瑰,颜色是粉色的。")
# print("\n第二次对话后的记忆:\n", conversation.memory.buffer)
print(result)

# 第二天的对话
# 回合3
result = conversation("我又来了,还记得我昨天为什么要来买花吗?")
print(result)

输出:

{'input': '我姐姐明天要过生日,我需要一束生日花束。', 
'history': '', 
'response': '那你可以考虑去当地的花店或者在线花店订购一束生日花束哦。你姐姐有没有特别喜欢的花呢?如果有的话,你可以根据她的喜好来选择花束的种类。比如,如果她喜欢玫瑰,你可以选择一束红玫瑰或者粉玫瑰;如果她喜欢百合,你可以选择一束白色的百合花束。\n\n另外,你还可以根据你姐姐的年龄和性格来选择花束的风格。如果她比较年轻,你可以选择一束色彩鲜艳、造型活泼的花束;如果她比较成熟稳重,你可以选择一束颜色淡雅、造型简洁的花束。\n\n在选择花束的时候,你还可以考虑加上一些绿叶和小花来增加花束的层次感和美观度。同时,你也可以选择一个漂亮的花瓶来搭配花束,让整个礼物更加完美。\n\n希望这些建议对你有所帮助,祝你姐姐生日快乐!'}

{'input': '她喜欢粉色玫瑰,颜色是粉色的。', 
'history': 'Human: 我姐姐明天要过生日,我需要一束生日花束。\nAI: 那你可以考虑去当地的花店或者在线花店订购一束生日花束哦。你姐姐有没有特别喜欢的花呢?如果有的话,你可以根据她的喜好来选择花束的种类。比如,如果她喜欢玫瑰,你可以选择一束红玫瑰或者粉玫瑰;如果她喜欢百合,你可以选择一束白色的百合花束。\n\n另外,你还可以根据你姐姐的年龄和性格来选择花束的风格。如果她比较年轻,你可以选择一束色彩鲜艳、造型活泼的花束;如果她比较成熟稳重,你可以选择一束颜色淡雅、造型简洁的花束。\n\n在选择花束的时候,你还可以考虑加上一些绿叶和小花来增加花束的层次感和美观度。同时,你也可以选择一个漂亮的花瓶来搭配花束,让整个礼物更加完美。\n\n希望这些建议对你有所帮助,祝你姐姐生日快乐!', 
'response': '那你可以选择一束以粉色玫瑰为主的花束呀。可以用大量的粉色玫瑰作为主花,再搭配一些白色的满天星来增加浪漫的氛围,还可以点缀一些绿色的尤加利叶来增添清新感。这样的花束会非常漂亮且符合你姐姐的喜好。你可以告诉花店的工作人员你的具体需求,他们会帮你精心制作的。'}

{'input': '我又来了,还记得我昨天为什么要来买花吗?', 
'history': 'Human: 她喜欢粉色玫瑰,颜色是粉色的。\nAI: 那你可以选择一束以粉色玫瑰为主的花束呀。可以用大量的粉色玫瑰作为主花,再搭配一些白色的满天星来增加浪漫的氛围,还可以点缀一些绿色的尤加利叶来增添清新感。这样的花束会非常漂亮且符合你姐姐的喜好。你可以告诉花店的工作人员你的具体需求,他们会帮你精心制作的。', 
'response': '我不太清楚你昨天为什么要来买花呢,你可以再给我讲讲呀。'}

尽管这种方法不适合记住遥远的互动,但它非常擅长限制使用的Token数量。如果只需要记住最近的互动,缓冲窗口记忆是一个很好的选择。但是,如果需要混合远期和近期的互动信息,则还有其他选择。

ConversationSummaryMemory

ConversationSummaryMemory(对话总结记忆)的思路就是将对话历史进行汇总,然后再传递给 {history} 参数。这种方法旨在通过对之前的对话进行汇总来避免过度使用 Token。

# 设置OpenAI API密钥
import os

# 导入所需的库
from langchain_openai import ChatOpenAI
from langchain.chains import ConversationChain
from langchain.chains.conversation.memory import ConversationSummaryBufferMemory

# 初始化大语言模型
llm = ChatOpenAI(
    temperature=0.5,
    model=os.environ.get("LLM_MODELEND"),
)

# 初始化对话链
conversation = ConversationChain(
    llm=llm, memory=ConversationSummaryBufferMemory(llm=llm, max_token_limit=300)
)

# 第一天的对话
# 回合1
result = conversation("我姐姐明天要过生日,我需要一束生日花束。")
print(result)
# 回合2
result = conversation("她喜欢粉色玫瑰,颜色是粉色的。")
# print("\n第二次对话后的记忆:\n", conversation.memory.buffer)
print(result)

# 第二天的对话
# 回合3
result = conversation("我又来了,还记得我昨天为什么要来买花吗?")
print(result)

输出:

{'input': '我姐姐明天要过生日,我需要一束生日花束。', 
'history': '', 
'response': ' 我明白,你需要一束生日花束。我可以为你提供一些建议吗?我可以推荐一些花束给你,比如玫瑰,康乃馨,百合,仙客来,郁金香,满天星等等。挑选一束最适合你姐姐的生日花束吧!'}

{'input': '她喜欢粉色玫瑰,颜色是粉色的。', 
'history': "\nThe human asked 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. The human then asked the AI for advice on what type of flower bouquet to get for their sister's birthday, to which the AI provided a variety of suggestions.", 
'response': ' 为了为你的姐姐的生日准备一束花,我建议你搭配粉色玫瑰和白色康乃馨。你可以在玫瑰花束中添加一些紫色的满天星,或者添加一些绿叶以增加颜色对比。这将是一束可爱的花束,让你姐姐的生日更加特别。'}

{'input': '我又来了,还记得我昨天为什么要来买花吗?', 
'history': "\n\nThe human asked 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. The human then asked the AI for advice on what type of flower bouquet to get for their sister's birthday, to which the AI suggested pink roses and white carnations with the addition of purple aster flowers and green leaves for contrast. This would make a lovely bouquet to make the sister's birthday extra special.",
'response': ' 确实,我记得你昨天想买一束花给你的姐姐作为生日礼物。我建议你买粉红色的玫瑰花和白色的康乃馨花,再加上紫色的雏菊花和绿叶,这样可以让你的姐姐的生日更加特别。'}

可以看到,这里的 'history',不再是之前人类和AI对话的简单复制粘贴,而是经过了总结和整理之后的一个综述信息。

但是,对于比较短的对话内容,可能还会导致更高的Token花费。而且,对话历史的记忆依赖于中间大模型的汇总能力,这也会导致Token的花费,增加了成本。

ConversationSummaryBufferMemory

ConversationSummaryBufferMemory,即对话总结缓冲记忆,它是一种混合记忆模型,结合了上述各种记忆机制,包括ConversationSummaryMemory 和 ConversationBufferWindowMemory的特点。这种模型旨在在对话中总结早期的互动,同时尽量保留最近互动中的原始内容。

# 设置OpenAI API密钥
import os

# 导入所需的库
from langchain_openai import ChatOpenAI
from langchain.chains import ConversationChain
from langchain.chains.conversation.memory import ConversationSummaryBufferMemory

# 初始化大语言模型
llm = ChatOpenAI(
    temperature=0.5,
    model=os.environ.get("LLM_MODELEND"),
)

# 初始化对话链
conversation = ConversationChain(
    llm=llm, memory=ConversationSummaryBufferMemory(llm=llm, max_token_limit=300)
)

输出:

{'input': '我姐姐明天要过生日,我需要一束生日花束。', 
'history': '', 
'response': ' 哇,你姐姐要过生日啊!那太棒了!我建议你去买一束色彩鲜艳的花束,因为这样可以代表你给她的祝福和祝愿。你可以去你家附近的花店,或者也可以从网上订购,你可以看看有没有特别的花束,比如彩色玫瑰或者百合花,它们会更有特色。'}

{'input': '她喜欢粉色玫瑰,颜色是粉色的。', 
'history': 'Human: 我姐姐明天要过生日,我需要一束生日花束。\nAI:  哇,你姐姐要过生日啊!那太棒了!我建议你去买一束色彩鲜艳的花束,因为这样可以代表你给她的祝福和祝愿。你可以去你家附近的花店,或者也可以从网上订购,你可以看看有没有特别的花束,比如彩色玫瑰或者百合花,它们会更有特色。', 
'response': ' 好的,那粉色玫瑰就是一个很好的选择!你可以买一束粉色玫瑰花束,这样你姐姐会很开心的!你可以在花店里找到粉色玫瑰,也可以从网上订购,你可以根据你的预算,选择合适的数量。另外,你可以考虑添加一些装饰,比如细绳、彩带或者小礼品'}

{'input': '我又来了,还记得我昨天为什么要来买花吗?', 
'history': "System: \nThe human asked the AI for advice on buying a bouquet for their sister's birthday. The AI suggested buying a vibrant bouquet as a representation of their wishes and blessings, and recommended looking for special bouquets like colorful roses or lilies for something more unique.\nHuman: 她喜欢粉色玫瑰,颜色是粉色的。\nAI:  好的,那粉色玫瑰就是一个很好的选择!你可以买一束粉色玫瑰花束,这样你姐姐会很开心的!你可以在花店里找到粉色玫瑰,也可以从网上订购,你可以根据你的预算,选择合适的数量。另外,你可以考虑添加一些装饰,比如细绳、彩带或者小礼品", 
'response': ' 是的,我记得你昨天来买花是为了给你姐姐的生日。你想买一束粉色玫瑰花束来表达你的祝福和祝愿,你可以在花店里找到粉色玫瑰,也可以从网上订购,你可以根据你的预算,选择合适的数量。另外,你可以考虑添加一些装饰,比如细绳、彩带或者小礼品}

故,ConversationSummaryBufferMemory有以下几个方面的好处

  • 优化内存使用:ConversationSummaryBufferMemory通过对对话内容的摘要来减少对内存的使用,避免长对话时的内存溢出。
  • 提高模型效率:通过对存储对话的摘要,缩短了传给大模型的数据长度,提高模型处理的速率和效率。
  • 灵活度高:ConversationSummaryBufferMemory适用于不同需求和长度的对话场景。