用通俗易懂的方式讲解大模型:LangChain Agent 原理解析

news2025/4/21 3:22:31

LangChain 是一个基于 LLM(大型语言模型)的编程框架,旨在帮助开发人员使用 LLM 构建端到端的应用程序。它提供了一套工具、组件和接口,可以简化创建由 LLM 和聊天模型提供支持的应用程序的过程。

LangChain 由几大组件构成,包括 Models,Prompts,Chains,Memory 和 Agent 等,而 Agent 是其中重要的组成部分,如果把 LLM 比做大脑的话,那 Agent 就是给大脑加上手和脚。今天就来带大家重点了解一下 Agent 以及它的工作原理。

什么是 LangChain Agent

在 LangChain 中,Agent 是一个代理,接收用户的输入,采取相应的行动然后返回行动的结果。Agent 可以看作是一个自带路由消费 Chains 的代理,基于 MRKL 和 ReAct 的基本原理,Agent 可以使用工具和自然语言处理问题。官方也提供了对应的 Agent,包括 OpenAI Functions Agent、Plan-and-execute Agent、Self Ask With Search 类 AutoGPT 的 Agent 等。Agent 的作用是代表用户或其他系统完成任务,例如数据收集、数据处理、决策支持等。Agent 可以是自主的,具备一定程度的智能和自适应性,以便在不同的情境中执行任务。我们今天主要了解基于 ReAct 原理来实现的 Agent。

ReAct

ReAct 是一个结合了推理和行动的语言模型。虽然 LLM 在语言理解和交互决策制定方面展现出了令人印象深刻的能力,但它们的推理(例如链式思考提示)和行动(例如行动计划生成)的能力主要被视为两个独立的主题。ReAct 的目标是探索如何使用 LLM 以交错的方式生成推理痕迹和特定任务的行动,从而在两者之间实现更大的协同作用。

想象一下,你有一个智能助手机器人,名叫小明。你给小明一个任务:去厨房为你做一杯咖啡。小明不仅要完成这个任务,还要告诉你他是如何一步步完成的。

没有 ReAct 的小明:

  1. 小明直接跑到厨房。

  2. 你听到了一些声音,但不知道小明在做什么。

  3. 过了一会儿,小明回来给你一杯咖啡。

这样的问题是,你不知道小明是怎么做咖啡的,他是否加了糖或奶,或者他是否在过程中遇到了任何问题。

有 ReAct 的小明:

  1. 小明告诉你:“我现在去厨房。”

  2. 小明再说:“我找到了咖啡粉和咖啡机。”

  3. “我现在开始煮咖啡。”

  4. “咖啡煮好了,我要加点糖和奶。”

  5. “好了,咖啡做好了,我现在给你拿过去。”

这次,你完全知道小明是怎么做咖啡的,知道他的每一个步骤和决策。

ReAct 就是这样的原理。它不仅执行任务(行动),还会告诉你它是如何思考和决策的(推理)。这样,你不仅知道任务完成了,还知道为什么这样做,如果有问题,也更容易找出原因。

更多关于 ReAct 的内容可以查看这篇文章[1]。

自定义 LLM Agent

LangChain 在官方网站上提供了关于如何创建自定义 LLM Agent[2]的例子,在官网的示例中,我们除了看到自定义 LLM Agent 外,还有一个自定义 Agent,这两者的区别就是自定义 LLM Agent 使用了 LLM 来解析用户输入,判断使用何种工具,而自定义 Agent 则是直接自行判断工具的使用,这种方式只能用于简单的场景,而自定义 LLM Agent 可以用于更复杂的场景。

在官方示例中,对其实现原理做了一些简单的描述,介绍了其组成部分和流程等,但如果是刚开始了解 Agent 的同学看起来可能会一知半解,所以我们今天主要结合其中的示例代码来了解自定义 LLM Agent 的实现原理。

通俗易懂讲解大模型系列

  • 用通俗易懂的方式讲解大模型:HugggingFace 推理 API、推理端点和推理空间使用详解
  • 用通俗易懂的方式讲解大模型:使用 LangChain 封装自定义的 LLM,太棒了
  • 用通俗易懂的方式讲解大模型:使用 FastChat 部署 LLM 的体验太爽了
  • 用通俗易懂的方式讲解大模型:基于 Langchain 和 ChatChat 部署本地知识库问答系统
  • 用通俗易懂的方式讲解大模型:使用 Docker 部署大模型的训练环境
  • 用通俗易懂的方式讲解大模型:在 Ubuntu 22 上安装 CUDA、Nvidia 显卡驱动、PyTorch等大模型基础环境
  • 用通俗易懂的方式讲解大模型:Llama2 部署讲解及试用方式
  • 用通俗易懂的方式讲解大模型:LangChain 知识库检索常见问题及解决方案
  • 用通俗易懂的方式讲解大模型:基于 LangChain 和 ChatGLM2 打造自有知识库问答系统
  • 用通俗易懂的方式讲解大模型:代码大模型盘点及优劣分析
  • 用通俗易懂的方式讲解大模型:Prompt 提示词在开发中的使用

技术交流

建了大模型技术交流群! 想要学习、技术交流、获取如下原版资料的同学,可以直接加微信号:mlc2060。加的时候备注一下:研究方向 +学校/公司+CSDN,即可。然后就可以拉你进群了。

方式①、微信搜索公众号:机器学习社区,后台回复:加群
方式②、添加微信号:mlc2060,备注:来自CSDN + 技术交流

在这里插入图片描述

提示词模板

要实现 Agent,我们需要先定义一套基于 ReAct 的提示词模板,示例中的 Agent 就是基于 ReAct 原理来实现的,为了方便理解,我们将官方的英文提示词模板换成中文和去掉一些没必要的内容,修改后的提示词模板内容如下:

template = """尽你所能回答以下问题,你可以使用以下工具:

{tools}

请按照以下格式:

问题:你必须回答的输入问题
思考:你应该始终考虑该怎么做
行动:要采取的行动,应该是[{tool_names}]中的一个
行动输入:行动的输入
观察:行动的结果
... (这个思考/行动/行动输入/观察可以重复N次)
思考:我现在知道最终答案了
最终答案:对原始输入问题的最终答案

开始吧!

问题:{input}
{agent_scratchpad}"""

简单介绍一下这个提示词模板,首先提示词模板中会引用到一些工具,可以看到模板中有 2 个变量:toolstool_names,tools 变量是一个列表,包含了所有的工具,列表中的每个元素包含了工具的名称和描述,而 tool_names 变量是工具名称的列表,传入具体的工具后,会生成对应的工具列表,比如我们有如下 2 个工具,解析后如下所示:

尽你所能回答以下问题,你可以使用以下工具:

search: 实时联网搜索的工具
math: 数学计算的工具

请按照以下格式:
......
行动:要采取的行动,应该是[search, math]中的一个
......

这样 LLM 在执行任务时就知道要使用哪些工具,以及在提取信息时可以提取到正确的工具名。

模板中下面的思考/行动/行动输入/观察就是标准的 ReAct 流程,思考是指思考如何解决问题,行动是具体的工具,行动输入是工具用到的参数,观察是工具执行完成后得到的结果,这个流程可以重复多次,直到最终得到最终答案。

模板最后还有 2 个变量:inputagent_scratchpad,input 是用户输入的问题,agent_scratchpad 是之前的思考过程(下面解析代码时会讲),包括了思考、行动、行动输入和观察等,这个变量在 Agent 执行过程中会被更新,代入具体的值后,模板会生成如下的提示词:

......
问题:北京的天气怎么样
思考: 我们需要通过 search 工具查找北京天气。
行动: search
行动输入: "北京天气"
观察: 6日(今天). 多云转晴. 32/22. <3级
思考:

从问题下面一句到最后结束就是agent_scratchpad的值。

构造提示词

准备好提示词模板后,我们就可以构造提示词了,构造提示词的官方示例代码如下:

# Set up a prompt template
class CustomPromptTemplate(StringPromptTemplate):
    # The template to use
    template: str
    # The list of tools available
    tools: List[Tool]

    def format(self, **kwargs) -> str:
        # Get the intermediate steps (AgentAction, Observation tuples)
        # Format them in a particular way
        intermediate_steps = kwargs.pop("intermediate_steps")
        thoughts = ""
        for action, observation in intermediate_steps:
            thoughts += action.log
            thoughts += f"\nObservation: {observation}\nThought: "
        # Set the agent_scratchpad variable to that value
        kwargs["agent_scratchpad"] = thoughts
        # Create a tools variable from the list of tools provided
        kwargs["tools"] = "\n".join([f"{tool.name}: {tool.description}" for tool in self.tools])
        # Create a list of tool names for the tools provided
        kwargs["tool_names"] = ", ".join([tool.name for tool in self.tools])
        return self.template.format(**kwargs)

prompt = CustomPromptTemplate(
    template=template,
    tools=tools,
    # This omits the `agent_scratchpad`, `tools`, and `tool_names` variables because those are generated dynamically
    # This includes the `intermediate_steps` variable because that is needed
    input_variables=["input", "intermediate_steps"]
)

首先我们需要定义一个类,继承自StringPromptTemplate,然后实现format方法,这个方法的作用是将提示词模板中的变量代入具体的值,然后返回提示词。其中intermediate_steps是中间 Agent 思考的步骤,每个步骤是一个元组,包含了AgentAction(行为和行为输入)和Observation(观察)的值,这个变量不会直接传递到 LLM,所以它不会在提示词中出现,但提示词模板会将它转换为agent_scratchpad变量,这个变量也就是我们在上面提到的agent_scratchpad,这个变量的值是 Agent 思考的过程,包括了思考、行动、行动输入和观察等。

format 方法最后会设置模板中的toolstool_names变量,这两个变量的值是提示词模板中的工具列表,这个列表是根据tools变量生成的,包含了所有的工具,列表中的每个元素包含了工具的名称和描述,而tool_names变量是工具名称的列表。

最后我们需要创建一个CustomPromptTemplate对象,传入提示词模板和工具列表,这个对象就是我们最终要传入 LLM 的提示词。

工具解析

接下来是输出结果的解析,其中分为 2 个部分,一个是工具的解析,一个是结果的解析,我们来看下官方的示例代码:

class CustomOutputParser(AgentOutputParser):

    def parse(self, llm_output: str) -> Union[AgentAction, AgentFinish]:
        # 暂时不看结果解析的代码

        # Parse out the action and action input
        regex = r"Action\s*\d*\s*:(.*?)\nAction\s*\d*\s*Input\s*\d*\s*:[\s]*(.*)"
        match = re.search(regex, llm_output, re.DOTALL)
        if not match:
            raise OutputParserException(f"Could not parse LLM output: `{llm_output}`")
        action = match.group(1).strip()
        action_input = match.group(2)
        # Return the action and action input
        return AgentAction(tool=action, tool_input=action_input.strip(" ").strip('"'), log=llm_output)

parse 方法前半部分是关于结果解析的,我们待会再讲,我们先看后面的代码,这是对工具的解析。

代码使用了一个正则表达式来解析 LLM 关于思考过程的输出结果,一般思考过程的输出结果是这样的格式:

思考: 我们需要通过 search 工具查找北京天气。
行动: search
行动输入: "北京天气"

根据我们的提示词模板,LLM 会智能地返回我们制定好的格式,有思考、行动、行动输入几项内容,我们主要通过这个正则表达式来获取到行动行动输入这两项的值(也就是工具名称和工具所需参数),这样 Agent 就知道该如何使用工具了。可以看到代码中 action(工具)获取的是正则结果中的第一个分组的值,而 action_input(工具参数)获取的是第二个分组的值。最后将这 2 个值封装成一个 AgentAction 对象返回。

但需要注意的是,虽然我们设置好了提示词模板,但如果 LLM 不够智能的话,返回的结果可能会和我们预期的不一样,比如 LLM 可能返回这样的结果:

思考: 我们需要通过 search 工具查找北京天气。
行动: 我需要使用search工具来查询
行动输入: "北京天气"

可以看到行动的值不是我们预期的 search,而是一句话,这样通过正则表达式获取的工具名就是我需要使用search工具来查询,然后 Agent 会拿这个工具名去匹配工具,然后去调用,发现没有这个工具,就会报错。所以如果我们发现 LLM 有时候返回的结果不符合我们预期时,我们需要通过其他方式来解析出工具的名称,比如去掉一些无用的内容,只保留工具名称,这样才能保证 Agent 能够正常使用工具。

结果解析

我们再说下结果的解析,就是上面代码中 parse方法的前半部分:

class CustomOutputParser(AgentOutputParser):

    def parse(self, llm_output: str) -> Union[AgentAction, AgentFinish]:
        # Check if Agent should finish
        if "Final Answer:" in llm_output:
            return AgentFinish(
                # Return values is generally always a dictionary with a single `output` key
                # It is not recommended to try anything else at the moment :)
                return_values={"output": llm_output.split("Final Answer:")[-1].strip()},
                log=llm_output,
            )

示例代码中判断 LLM 的输出是否有包含最终答案:,如果有的话,就说明 LLM 已经得到了最终的答案,这时就可以返回最终的答案了,这个格式也是我们在提示词模板中定义的:

"""
思考:我现在知道最终答案了
最终答案:对原始输入问题的最终答案
"""

这个最终的答案是 LLM 输出结果中的最后一句话,我们可以通过split方法来获取到最后一句话,然后返回一个ActionFinish对象,这个对象包含了最终的答案,解析出来的结果如下:

# 思考过程
思考:我现在知道最终答案了
最终答案: 北京的天气情况如下:6日(今天)多云转晴,温度在32/22℃,风力小于3# 最终结果
北京的天气情况如下:6日(今天)多云转晴,温度在32/22℃,风力小于3

跟工具解析一样,我们在结果解析时也需要注意 LLM 返回的结果是否符合我们预期,比如有时候 LLM 会输出这样的结果:

思考:我现在知道最终答案了
北京的天气情况如下:6日(今天)多云转晴,温度在32/22℃,风力小于3

可以看到 LLM 的输出没有包含最终结果:关键字,这样示例代码中的 if 逻辑就不生效了,会导致解析逻辑进到工具解析那一部分去,然后引发错误。所以我们在解析结果时,要提高我们解析程序的健壮性,以满足不同的 LLM 输出结果,或者调整我们的提示词模板,让 LLM 返回的结果更加准确。

中断提示

最后一步是创建 Agent,示例代码如下:

llm = OpenAI(temperature=0)
# LLM chain consisting of the LLM and a prompt
llm_chain = LLMChain(llm=llm, prompt=prompt)

tool_names = [tool.name for tool in tools]
agent = LLMSingleActionAgent(
    llm_chain=llm_chain,
    output_parser=output_parser,
    stop=["\nObservation:"],
    allowed_tools=tool_names
)

这里重点关注的是 Agent 中的stop参数,我们知道一般 LLM 都会长篇大论,说一大堆废话,我们希望 LLM 在返回了我们需要的信息后就停止输出,这里就需要用到stop参数,这个参数是一个列表,列表中的每个元素都是一个字符串,代表了 LLM 输出中的某一句话,当 LLM 输出中包含了这句话时,LLM 就会停止输出,这样我们就可以只获取到我们需要的信息了,这里我们使用观察关键字来停止 LLM 的输出。

总结

自定义 LLM Agent 的示例代码我们已经介绍完了,最后我们再讲下来 Agent 中使用的 LLM。在官方示例中,LLM 用的是 OpenAI,也就是gpt-3.5这个模型,但如果想达到更好的效果的话,推荐使用 OpenAI 的gpt-4模型,它是目前最好的 LLM,如果使用的 LLM 比较差,就容易出现刚才我们提到 LLM 返回的结果不符合我们预期的情况。

有人希望通过一些开源的 LLM 来实现 ReAct Agent,但实际开发过程中会发现开源低参数(比如一些 6B、7B 的 LLM)的 LLM 对于提示词的理解会非常差,根本不会按照提示词模板的格式来输出,这样就会导致我们的 Agent 无法正常工作,所以如果想要实现一个好的 Agent,还是需要使用好的 LLM,目前看来使用gpt-3.5模型是最低要求。

关注我,一起学习各种人工智能和 AIGC 新技术,欢迎交流,如果你有什么想问想说的,欢迎在评论区留言。

参考:

[1]这篇文章: https://react-lm.github.io/

[2]创建自定义 LLM Agent: https://python.langchain.com/docs/modules/agents/how_to/custom_llm_agent

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1346446.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

03 HAL库下UART的使用

引言&#xff1a; 需要使用到的uart调试工具在文章最后的资料里面 题外话&#xff1a;uart和usart的区别 UART&#xff08;Universal Asynchronous Receiver/Transmitter&#xff09;和USART&#xff08;Universal Synchronous/Asynchronous Receiver/Transmitter&#xff09;…

Halcon腐蚀erosion_circle/erosion_rectanglel

Halcon腐蚀 文章目录 Halcon腐蚀 腐蚀操作是对所选区域进行“收缩”的一种操作&#xff0c;可以用于消除边缘和杂点。腐蚀区域的大小与结构元素的大小和形状相关。其原理是使用&#xff0c;个自定义的结构元素&#xff0c;如矩形、圆形等&#xff0c;在二值图像上进行类似于“滤…

最简单的基于 SDL2 的音频播放器

最简单的基于 SDL2 的音频播放器 最简单的基于 SDL2 的音频播放器正文工程文件下载 参考雷霄骅博士的文章&#xff0c;链接&#xff1a;最简单的基于FFMPEGSDL的音频播放器&#xff1a;拆分-解码器和播放器 最简单的基于 SDL2 的音频播放器 正文 SDL2 音频播放器实现了播放 …

【零基础入门VUE】在 Vue 中构建复杂表单

✍面向读者&#xff1a;所有人 ✍所属专栏&#xff1a;零基础入门VUE专栏https://blog.csdn.net/arthas777/category_12537076.html 目录 v-modelVue 中的 指令 Vue 中的组件 没有构建步骤 随着构建步骤 注册 VUE 组件 Vue 道具 VUE 中的道具声明 在 VUE 中传递 PROP…

unknown variable ‘authentication_policy=mysql_native_password‘

unknown variable authentication_policymysql_native_password 背景解决尝试一尝试二(解决) 总结 背景 mac上安装多个版本数据库。我是通过dmg安装的&#xff0c;先装的5.7&#xff0c;再装的5.8&#xff0c;然后5.8的能正常用&#xff0c;5.7的启动不起来。报错信息为如下 …

docker安装mysql看这一篇就够了

docker安装mysql 一、 安装docker二、docker安装mysql三、设置MySQL远程访问 一、 安装docker 1、安装依赖环境&#xff0c;yum-utils yum -y install yum-utils device-mapper-persistent-data lvm2 备注&#xff1a;使用yum工具下载 yum是软件包管理工具 通过 执行 man yum…

nodejs+vue网上书城图书销售商城系统io69w

功能介绍 该系统将采用B/S结构模式&#xff0c;使用Vue和ElementUI框架搭建前端页面&#xff0c;后端使用Nodejs来搭建服务器&#xff0c;并使用MySQL&#xff0c;通过axios完成前后端的交互 系统的主要功能包括首页、个人中心、用户管理、图书类型管理、图书分类管理、图书信…

【Redis技术专区】「原理分析」探讨Redis 6.0为何需要启用多线程?

探讨Redis 6.0为何需要启用多线程 背景介绍开启多线程多线程的CPU核心配置IO多线程模式单线程处理方式多线程处理方式 为什么要开启多线程&#xff1f;充分利用多核CPU提高网络I/O效率响应现代应用需求 多线程实现启用多线程 最后总结 背景介绍 在Redis 6.0版本中&#xff0c;…

【ONE·MySQL || 数据类型 表的约束】

总言 主要内容&#xff1a;介绍MySQL中的常见数据类型&#xff08;数值类型、文本二进制类型、时间日期、字符串类型&#xff09;&#xff0c;以及对表的约束&#xff08;非空约束、默认约束、列描述、零填充约束、自增长约束、主键约束、唯一键约束、外键约束&#xff09;。  …

【Vue】使用Axios请求下载后端返回的文件流,并能够提示后端报错信息

【需求】使用Axios请求下载后端返回的文件流&#xff0c;下载失败时提示信息不写死&#xff0c;按照后端返回的信息进行提示。 一、需求分析 看到这个需求的时候&#xff0c;有人可能会很疑惑&#xff0c;这不是直接就能获取到吗&#xff0c;直接message.error()弹框就完事了&…

【webpack】高级篇

webpack高级应用 安装流程提高开发效率与完善团队开发规范devtool属性 devServer属性下compress属性port属性headers请求头proxy开启代理https配置http2配置historyApiFallback配置开发服务器主机 host模块热替换与热加载 eslint代码规范取消黑色背景弹窗提示 git-hooks与husky…

Vue中methods,watch与computed之间的差异

Methods methods 选项定义了组件中可以调用的方法。这些方法可以在模板中通过事件绑定或者在其他方法中调用。methods 中的函数可以接受参数&#xff0c;并且可以执行任意的 JavaScript 代码。因此&#xff0c;methods 主要用于处理组件中的用户交互、事件响应和自定义的操作。…

【FileZilla的安装与使用以及主动与被动模式详解

目录 一. FileZilla是什么&#xff1f; 二. FileZilla的安装与使用 2.1 FileZilla服务端安装与配置 2.1.2 新建组 2.1.3 新建用户 2.1.4 新建目录 2.1.5 权限分配 &#xff08;1&#xff09;用户fu权限分配 2.2 FileZilla客户端安装与使用 2.2.1 权限分配测试 权限演…

【ARMv8M Cortex-M33 系列 2.1 -- Cortex-M33 使用 .hex /.srec 文件介绍】

请阅读【嵌入式开发学习必备专栏 之Cortex-M33 专栏】 文章目录 HEX 文件介绍英特尔十六进制文件格式记录类型hex 示例Cortex-M 系列hex 文件的使用 hex 文件和srec 文件生成Motorola S-Record (srec) 格式 HEX 文件介绍 .hex 文件通常用于微控制器编程&#xff0c;包括 ARM C…

JUC Lock 锁入门

文章目录 死锁&#xff08;Deadlock&#xff09;通过 Visualvm 等工具排查死锁 活锁park & unpark与 wait & notify 的区别park & unpark 实现&#xff1a;点外卖 Lock 对象ReentrantLock 可重入锁可重入lockInterruptibly 方法上锁&#xff08;可打断&#xff09;…

C#,入门教程(04)——Visual Studio 2022 数据编程实例:随机数与组合

上一篇&#xff1a; C#&#xff0c;入门教程(03)——Visual Studio 2022编写彩色Hello World与动画效果https://blog.csdn.net/beijinghorn/article/details/123478581 C#&#xff0c;入门教程(01)—— Visual Studio 2022 免费安装的详细图文与动画教程https://blog.csdn.net…

Field II 仿真软件——安装

1. 去官网下载文件压缩包 Field II Ultrasound Simulation Program (field-ii.dk) 在Download页面下载符合自己系统的压缩包。 2. 解压压缩文件&#xff0c;然后将这个文件夹添加到matlab的路径中&#xff0c;如下图所示&#xff1a; 3. 在matlab命令行输入&#xff1a;field…

05 HAL库驱动蜂鸣器唱出一首小歌

目录 一、蜂鸣器的基本知识 1、有源蜂鸣器 2、无源蜂鸣器 二、PWM的相关知识 1. PWM概念 2. PWM常见参数 3.PWM基本结构 三、蜂鸣器发出音调的原理 四、频率计算 五、实验开始 一、蜂鸣器的基本知识 蜂鸣器是一种能够发出持续而连续的声音的电子设备&#xff0c;它被…

【十一】【C++\动态规划】1218. 最长定差子序列、873. 最长的斐波那契子序列的长度、1027. 最长等差数列,三道题目深度解析

动态规划 动态规划就像是解决问题的一种策略&#xff0c;它可以帮助我们更高效地找到问题的解决方案。这个策略的核心思想就是将问题分解为一系列的小问题&#xff0c;并将每个小问题的解保存起来。这样&#xff0c;当我们需要解决原始问题的时候&#xff0c;我们就可以直接利…

.NET进阶篇06-async异步、thread多线程2

知识须要不断积累、总结和沉淀&#xff0c;思考和写做是成长的催化剂web 内容目录 1、线程Thread 一、生命周期 二、后台线程 三、静态方法 1.线程本地存储 2.内存栅栏 四、返回值 2、线程池ThreadPool 一、工做队列 二、工做线程和IO线程 三、和Thread区别 四、定时器 1、线…