LangChain 实战基础:链、记忆与代理的深度探索 | 青训营X豆包MarsCode AI 刷题

43 阅读14分钟

LangChain 实战基础:链、记忆与代理的深度探索

学习基于LangChain实战课(掘金小册) | 青训营X豆包MarsCode AI 刷题 - AI方向

主题:【第二部分】基础篇 Ⅱ 总结

类型:实践记录/学习体验/学习方法

一、前言

在当今人工智能技术蓬勃发展的时代,LangChain 作为一款强大的开发框架,为构建智能语言模型应用提供了丰富的功能和工具。本文将深入探讨 LangChain 中的三个重要主题:链(Chain)、记忆(Memory)和代理(Agent)。通过对这些主题的详细介绍、原理剖析以及实战案例展示,帮助读者更好地理解和应用 LangChain,从而构建出强大且智能的语言模型应用程序。文中涉及的知识和示例主要参考了 LangChain 官方文档、相关技术博客以及实际的开发经验总结。

二、链(Chain)

(一)什么是链

在 LangChain 中,链是实现复杂应用程序的关键构建块。它通过精心设计的接口,将多个组件(如提示模板、语言模型、输出解析器等)有机地链接在一起,形成一个协同工作的整体。这种组合方式不仅简化了复杂应用的开发流程,使其更具模块化特性,还极大地提升了应用程序的可维护性和可扩展性。例如,在一个智能客服应用中,链可以将用户输入的处理、模型的调用以及回答的生成等环节紧密相连,形成一个连贯的服务流程。正如LangChain 官方文档中所阐述的,链的设计理念旨在为开发者提供一种高效且灵活的方式来构建复杂的语言模型应用。

(二)LLMChain:最简单的链

  1. 功能概述
    LLMChain 是 LangChain 中最基础且常用的链类型之一。它围绕语言模型推理功能进行了扩展,整合了 PromptTemplate(提示模板)、语言模型(LLM 或聊天模型)和 Output Parser(输出解析器)。这意味着它能够将用户输入按照预定义的模板进行格式化,然后传递给语言模型进行处理,并最终对模型的输出进行解析和返回。例如,当我们想要查询某种花的花语时,LLMChain 可以方便地实现这一功能。
  2. 代码示例与调用方式
    以下是一个使用 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)

LLMChain 有多种调用方式,包括直接调用(如llm_chain("玫瑰"),实际上会调用对象内部的__call__方法)、通过run方法(等价于直接调用__call__函数,如llm_chain.run("玫瑰"))、通过predict方法(输入键被指定为关键字参数,如result = llm_chain.predict(flower="玫瑰"))、通过apply方法(允许针对输入列表运行链,一次处理多个输入)和通过generate方法(返回一个 LLMResult 对象,包含模型生成文本过程中的相关信息,如令牌数量、模型名称等)。

(三)Sequential Chain:顺序链

  1. 功能与示例场景
    Sequential Chain 能够将多个 LLMChain 按照特定顺序串联起来,实现更为复杂的任务流程。以一个鲜花运营场景为例,我们可以设定以下三个步骤:首先,假设大模型是一个植物学家,让其给出某种特定鲜花的知识和介绍;接着,假设大模型是一个鲜花评论者,参考植物学家的输出对鲜花进行评论;最后,假设大模型是易速鲜花的社交媒体运营经理,依据前面的介绍和评论撰写一篇鲜花运营文案。
  2. 代码实现与输出结果
    具体代码实现如下:
# 设置OpenAI API密钥
import os
os.environ["OPENAI_API_KEY"] = '你的OpenAI API Key'
from langchain.llms import OpenAI
from langchain.chains import LLMChain
from langchain.prompts import PromptTemplate
from langchain.chains import SequentialChain

# 这是第一个LLMChain,用于生成鲜花的介绍,输入为花的名称和种类
llm = OpenAI(temperature=.7)
template = """
你是一个植物学家。给定花的名称和类型,你需要为这种花写一个200字左右的介绍。
花名: {name}
颜色: {color}
植物学家: 这是关于上述花的介绍:"""
prompt_template = PromptTemplate(input_variables=["name", "color"], template=template)
introduction_chain = LLMChain(llm=llm, prompt=prompt_template, output_key="introduction")

# 这是第二个LLMChain,用于根据鲜花的介绍写出鲜花的评论
llm = OpenAI(temperature=.7)
template = """
你是一位鲜花评论家。给定一种花的介绍,你需要为这种花写一篇200字左右的评论。
鲜花介绍:
{introduction}
花评人对上述花的评论:"""
prompt_template = PromptTemplate(input_variables=["introduction"], template=template)
review_chain = LLMChain(llm=llm, prompt=prompt_template, output_key="review")

# 这是第三个LLMChain,用于根据鲜花的介绍和评论写出一篇自媒体的文案
template = """
你是一家花店的社交媒体经理。给定一种花的介绍和评论,你需要为这种花写一篇社交媒体的帖子,300字左右。
鲜花介绍:
{introduction}
花评人对上述花的评论:
{review}
社交媒体帖子:
"""
prompt_template = PromptTemplate(input_variables=["introduction", "review"], template=template)
social_post_chain = LLMChain(llm=llm, prompt=prompt_template, output_key="social_post_text")

# 这是总的链,我们按顺序运行这三个链
overall_chain = SequentialChain(
    chains=[introduction_chain, review_chain, social_post_chain],
    input_variables=["name", "color"],
    output_variables=["introduction","review","social_post_text"],
    verbose=True)
# 运行链,并打印结果
result = overall_chain({"name":"玫瑰", "color": "黑色"})
print(result)

最终输出为一篇包含黑色玫瑰介绍、评论和运营文案的完整内容,展示了 Sequential Chain 如何将多个简单链组合成一个复杂且有意义的任务流程。

三、记忆(Memory)

(一)使用 ConversationChain

  1. 对话格式与记忆机制
    ConversationChain 提供了一种特殊的对话格式,包含 AI 前缀和人类前缀的对话摘要。这种格式与记忆机制紧密结合,其内置提示模板为人类和人工智能之间的对话设定了基本框架。例如,模板中会明确说明这是一个友好对话,AI 应提供详细信息,若不知答案应如实告知。同时,模板中的{history}参数用于存储会话记忆,即对话历史信息,{input}参数则表示新输入内容。当进行对话时,历史信息会通过提示模板传递给 LLM,从而使模型能够基于之前的对话进行回应,实现记忆功能。
  2. 示例代码与输出分析
    以下是一个简单的 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)

输出的模板内容清晰展示了对话的结构和记忆机制的实现方式,让我们能够理解如何通过这种方式实现对话历史的记录和利用。

(二)使用 ConversationBufferMemory

  1. 简单记忆实现原理
    ConversationBufferMemory 是 LangChain 中实现最简单记忆机制的方式。它直接存储所有的对话历史内容,在每次对话时将这些内容作为提示的一部分传递给模型。例如,在一个关于购买生日花束的对话场景中,它会完整记录每一轮的对话信息。
  2. 代码示例与对话过程展示
from langchain import OpenAI
from langchain.chains import ConversationChain
from langchain.chains.conversation.memory import ConversationBufferMemory

# 初始化大语言模型
llm = OpenAI(
    temperature=0.5,
    model_name="gpt-3.5-turbo-instruct")
# 初始化对话链
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)

在这个示例中,我们可以看到随着对话的进行,记忆不断累积,模型能够基于完整的对话历史进行回应,但这种方式也可能导致新输入中包含过多 Token,影响响应时间和成本,并且当达到 LLM 的令牌数限制时,过长的对话可能无法被完整记住。

(三)使用 ConversationBufferWindowMemory

  1. 窗口记忆的概念与优势
    ConversationBufferWindowMemory 基于缓冲记忆的思想,引入了窗口值 k,只保留最新的 k 次人类和 AI 的互动。这种方式能够有效限制使用的 Token 数量,尤其适用于只需要关注近期互动的场景。例如,在上述购买花束的对话中,如果设置 k = 1,模型只会记住最近一次的对话内容,从而减少了记忆负担。
  2. 代码示例与不同回合的输出分析
from langchain import OpenAI
from langchain.chains import ConversationChain
from langchain.chains.conversation.memory import ConversationBufferWindowMemory

# 创建大语言模型实例
llm = OpenAI(
    temperature=0.5,
    model_name="gpt-3.5-turbo-instruct")
# 初始化对话链
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)

通过分析不同回合的输出,我们可以看到窗口记忆如何影响模型对对话历史的记忆和回应,以及在特定场景下的优势和局限性。

(四)使用 ConversationSummaryMemory

  1. 对话汇总记忆的特点
    ConversationSummaryMemory 采用对话历史汇总的方式来传递给{history}参数。它由另一个 LLM 驱动,在每次新互动发生时对对话进行汇总,并添加到之前所有互动的 “运行汇总” 中。这种方法对于长对话优势明显,虽然初始使用 Token 数量较多,但随着对话进展,汇总方法的增长速度会减慢,相比常规的缓冲内存模型更节省 Token。然而,对于较短对话,可能会导致更高的 Token 使用,且对话历史的记忆依赖于中间汇总 LLM 的能力,还会增加成本,并且不限制对话长度。
  2. 代码示例与多回合对话中的表现
from langchain.chains.conversation.memory import ConversationSummaryMemory

# 初始化对话链
conversation = ConversationChain(
    llm=llm,
    memory=ConversationSummaryMemory(llm=llm)
)
# 第一天的对话
# 回合1
result = conversation("我姐姐明天要过生日,我需要一束生日花束。")
print(result)
# 回合2
result = conversation("她喜欢粉色玫瑰,颜色是粉色的。")
print(result)
# 回合3
result = conversation("我又来了,还记得我昨天为什么要来买花吗?")
print(result)

在这个示例中,我们可以观察到随着对话轮次的增加,{history}参数中的内容是经过汇总后的信息,而非简单的对话复制粘贴,展示了这种记忆机制在长对话中的 Token 优化效果。

(五)使用 ConversationSummaryBufferMemory

  1. 混合记忆模型的原理与优势
    ConversationSummaryBufferMemory 是一种混合记忆模型,结合了 ConversationSummaryMemory 和 ConversationBufferWindowMemory 的特点。它通过max_token_limit参数来平衡记忆内容,当最新对话文字长度在该参数范围内时,记忆原始对话内容;超出时则对早期对话进行总结以节省 Token 数量。这种模型既能回忆起较早的互动,又能完整存储最近的互动,为对话记忆提供了较大的灵活性,在节省 Token 数量方面也具有竞争力。
  2. 代码示例与实际效果验证
from langchain.chains.conversation.memory import ConversationSummaryBufferMemory

# 初始化对话链
conversation = ConversationChain(
    llm=llm,
    memory=ConversationSummaryBufferMemory(
        llm=llm,
        max_token_limit=300))
# 第一天的对话
# 回合1
result = conversation("我姐姐明天要过生日,我需要一束生日花束。")
print(result)
# 回合2
result = conversation("她喜欢粉色玫瑰,颜色是粉色的。")
print(result)
# 回合3
result = conversation("我又来了,还记得我昨天为什么要来买花吗?")
print(result)

从代码运行结果可以看出,在第二回合记忆完整记录了第一回合对话,而在第三回合,当对话超出限制时,早期对话被总结,体现了该记忆模型的工作原理和优势。

四、代理(Agent)

(一)代理的作用

  1. 解决模型局限性的关键
    在大语言模型应用中,尽管思维链(CoT)能使模型执行推理轨迹,但模型无法主动更新知识,容易出现事实幻觉。代理的出现解决了这一问题,它就像一座桥梁,连接了大模型与外部工具(如本地知识库、搜索引擎等)。根据用户输入,代理能够自主判断并调用合适的工具,获取外部信息,然后将这些信息整合到模型的推理过程中,从而给出更可靠和实际的回应。例如,当询问一个超出模型训练知识范围的问题时,代理可以通过搜索引擎查找相关信息,再结合模型的推理能力进行回答。
  2. 与外部工具的协同工作方式
    代理与外部工具的协同工作涉及多个环节。它需要准确判断何时调用工具、如何确定工具的检索是否完成、选择合适的外部搜索工具、判断工具返回的内容是否满足需求以及如何整合这些信息进行下一步推理。这一系列复杂的操作都在代理的掌控之下,使得模型能够突破自身知识的局限,与外部世界进行交互,从而实现更强大的功能。

(二)ReAct 框架

  1. 框架原理与灵感来源
    ReAct 框架的灵感来源于人类在执行任务时的 “行动” 和 “推理” 协同模式。在日常生活中,人们会根据观察到的环境信息进行思考,然后采取相应行动,这种模式同样适用于大语言模型。该框架引导模型生成任务解决轨迹,包括观察环境、进行思考和采取行动三个主要步骤,简化后为推理 - 行动(Reasoning - Acting)框架。在推理阶段,模型对当前环境和状态进行观察并生成推理轨迹,以便诱导、跟踪和更新操作计划,甚至处理异常情况;在行动阶段,模型与外部源(如知识库或环境)交互收集信息或给出最终答案。例如,在一个寻找物品并放置的虚拟任务中,模型通过 ReAct 框架能够逐步规划行动,先观察环境找到物品,再思考如何放置,最后执行放置动作。
  2. 在 LangChain 中的实现与意义
    LangChain 通过 Agent 类对 ReAct 框架进行了完美封装,赋予了大模型极大的自主性。这使得大模型从单纯的对话聊天工具转变为能够自主使用工具的智能代理。ReAct 框架的应用提高了大模型解决问题的可解释性和可信度,因为其推理过程被详细记录。同时,随着技术发展,该框架有望处理更多复杂任务,如在具身智能领域,使智能代理能够在虚拟或实际环境中进行更复杂的交互,极大地扩展了 AI 的应用范围。

(三)Agent 的关键组件

  1. 代理(Agent)
    代理类决定下一步执行的操作,由语言模型和提示驱动。提示包含代理的性格(设定角色以特定方式响应)、任务背景(提供更多上下文)和提示策略(如 ReAct 框架激发推理能力)。LangChain 提供多种类型的代理,以满足不同场景需求。例如,在一个客户服务场景中,代理可以被设定为专业、耐心的客服角色,根据用户问题提供准确解答。
  2. 工具(Tools)
    工具是代理调用的函数,确保代理能访问正确的工具并正确描述它们至关重要。如果工具提供不足或描述不当,代理将无法完成任务。LangChain 提供了一系列内置工具,同时开发者也可根据需求自定义工具。比如,在查询信息场景中,可使用搜索引擎工具获取外部信息,或使用数学计算工具进行数值运算。
  3. 工具包(Toolkits)
    工具包是一组相关工具的集合,用于完成特定目标。例如,LangChain 的 Office365 工具包包含连接 Outlook、读取邮件列表、发送邮件等工具,方便在办公场景中实现多种功能。不同的工具包针对不同的应用领域