当前位置: 首页 > news >正文

LangChain系列: 使用工具和工具包构建代理实战教程

让我们在LangChain中构建简单代理示例,以帮助我们理解代理的基本概念和构建块。通过保持简单,我们可以更好地掌握这些代理背后的基本思想,使我们能够在未来构建更复杂的代理。

什么是代理

LangChain官方文档有非常好的章节来介绍其代理的高级概念。但本文强调简短易懂,绝对值得在开始之前浏览一下。

如果你查找人工智能代理的定义,你会发现“一个实体能够感知其环境,对其环境采取行动,并就如何达到给定的目标做出明智的决定,以及学习的能力。”

我觉得这很符合LangChain的定义,使这一切在软件中成为可能的是大型语言模型(LLM)的推理能力。LangChain代理的大脑是LLM,LLM用于推断执行用户请求的最佳方式。
在这里插入图片描述

为了执行任务,操作事物和检索信息,代理在LangChain中拥有所谓的工具。正是通过这些工具,它才能够与环境进行互动。

这些工具基本上就是代理可以访问的方法/类,它们可以通过API与Stock Market指数交互、更新办公Calendar事件或对数据库运行查询。我们可以根据需要构建工具,这取决于我们试图与代理一起执行的任务的性质。

LangChain中的工具集合称为Toolkit。在实现方面,这实际上只是代理可用的工具集合。因此,在LangChain中,代理的高级概述看起来是这样的

在这里插入图片描述

因此,在基本层面上,代理需要:

  • 一个LLm充当它的大脑,并赋予它推理能力
  • 工具,以便它可以与周围的环境进行交互,并实现其目标

构建代理

为了使这些概念更加具体,让我们构建一个简单的代理。我们将创建数学代理,它可以执行一些简单的数学运算。

环境设置

首先让我们设置环境和脚本:

mkdir simple-math-agent && cd simple-math-agent
touch math-agent.py
python3 -m venv .venv
. .venv/bin/activatepip install langchain langchain_openai

工具

最简单的开始将是首先为我们的数学代理定义工具。

让我们给它“加”、“乘”和“平方”工具,这样它就可以对我们传递给它的问题执行这些操作。通过保持我们的工具简单,我们可以专注于核心概念,并自己构建工具,而不是依赖于现有的更复杂的工具,如wiki检索,它作为维基百科API的包装器,需要我们从LangChain库中导入它。

同样,我们在这里并没有尝试做任何花哨的事情,只是保持简单,并将代理的主要构建块放在一起,以便我们能够理解它们是如何工作的,并使我们的第一个代理启动并运行。

让我们从“加”工具开始。在LangChain中创建工具的自下而上的方法是扩展BaseTool类,设置类上的名称和描述字段,并实现_run方法。就像这样:

from langchain_core.tools import BaseToolclass AddTool(BaseTool):name = "add"description = "Adds two numbers together"args_schema: Type[BaseModel] = AddInputreturn_direct: bool = Truedef _run(self, a: int, b: int, run_manager: Optional[CallbackManagerForToolRun] = None) -> str:return a + b

注意,我们需要实现_run方法来显示我们的工具如何处理传递给它的参数。还要注意它是如何为args_schema需要一个pydantic模型的。我们在这里定义一下

AddInputa: int = Field(description="first number")b: int = Field(description="second number")

现在,LangChain确实为我们提供了一种更简单的方法来定义工具,然后每次都需要扩展BaseTool类。我们可以在@tool装饰器的帮助下做到这一点。使用@tool装饰器在LangChain中定义“加”工具,代码如下所示:

from langchain.tools import tool@tool
def add(a: int, b: int) -> int:"""Adds two numbers together""" # this docstring gets used as the descriptionreturn a + b # the actions our tool performs

简单多了,对吧?在幕后,装饰器神奇地使用提供的方法来扩展BaseTool类,就像我们前面所做的那样。有些事情需要注意:

  • 方法名也成为工具名
  • 方法参数定义工具的输入参数
  • 文档字符串转换为工具描述

在工具上访问这些属性:

print(add.name) # add
print(add.description) # Adds two numbers together.
print(add.args) # {'a': {'title': 'A', 'type': 'integer'}, 'b': {'title': 'B', 'type': 'integer'}}

请注意,工具的描述非常重要,因为这是LLM用来决定该工具是否适合该工作的工具。错误的描述可能会导致非工具在应该使用的时候被使用,或者在错误的时间被使用。

添加工具完成后,让我们继续定义乘法和平方工具。

@tool
def multiply(a: int, b: int) -> int:"""Multiply two numbers."""return a * b@tool
def square(a) -> int:"""Calculates the square of a number."""a = int(a)return a * a

就是这样,很简单。

因此,我们已经定义了我们自己的三个定制工具。更常见的用例可能是使用LangChain中已经提供的和现有的一些工具,您可以在这里看到。然而,在源代码级别,它们都将使用上面描述的类似方法来构建和定义。

这就是我们所关心的工具。现在是时候将我们的工具组合成一个工具包了。

工具包

工具箱听起来很花哨,但实际上非常简单。它们实际上只是一个工具列表。我们可以将工具箱定义为如下所示的一系列工具

toolkit = [add, multiply, square]

就是这样。真的很简单,没有什么好混淆的。

通常,工具包是一组工具,这些工具组合在一起很有用,对试图执行某些任务的代理很有帮助。例如,SQLToolkit可能包含用于生成SQL查询、验证SQL查询和执行SQL查询的工具。

LangChain文档上的integration Toolkit页面有社区开发的工具箱的大列表,这些工具包可能对你有用。

LLM

如上所述,LLM是代理的大脑。它根据传递给它的问题决定调用哪个工具,根据工具描述采取的最佳下一步是什么。它还决定何时得到最终答案,并准备将答案返回给用户。

from langchain_openai import ChatOpenAIllm = ChatOpenAI(model="gpt-3.5-turbo-1106", temperature=0)

提示词

最后,我们需要一个提示词传递给我们的代理,这样它就有一个关于它是什么类型的代理以及它应该解决什么类型的任务的一般概念。

我们的代理需要一个ChatPromptTemplate才能工作(稍后会详细介绍)。这就是一个基本的ChatPromptTemplate的样子。我们关心的主要部分是系统提示符,其余的只是我们需要传入的默认设置。

在我们的提示中,我们包含了一个示例答案,向代理展示了我们希望它如何只返回答案,而不是随答案一起返回任何描述性文本。

prompt = ChatPromptTemplate.from_messages([("system", """You are a mathematical assistant. Use your tools to answer questions.If you do not have a tool to answer the question, say so. Return only the answers. e.gHuman: What is 1 + 1?AI: 2"""),MessagesPlaceholder("chat_history", optional=True),("human", "{input}"),MessagesPlaceholder("agent_scratchpad"),]
)

就是这样。我们已经设置了我们的Tools和Toolkit,我们的代理将需要它们作为其设置的一部分,因此它知道它可以处理的操作和功能的类型。我们还设置了LLM和系统提示符。

现在到了有趣的部分,建立我们的代理!

代理

LangChain可以创建许多不同类型的代理,具有不同的推理能力和能力。我们将使用目前可用的最强大的代理,OpenAI Tools代理。根据OpenAI工具代理的文档,它也使用更新的OpenAI模型,

更新的OpenAI模型已经进行了微调,可以检测何时应该调用一个或多个函数,并使用应该传递给函数的输入进行响应。在API调用中,你可以描述函数,并让模型智能地选择输出包含参数的JSON对象来调用这些函数。OpenAI工具API的目标是比使用通用文本完成或聊天API更可靠地返回有效和有用的函数调用。这里仅为示例,LangChain可以接入主流大模型,包括DeepSeek R1.

换句话说,这个代理擅长为调用函数生成正确的结构,并且能够理解我们的任务是否还需要多个函数(工具)。这个代理还具有调用具有多个输入参数的函数(工具)的能力,就像我们的一样。有些代理只能处理具有单个输入参数的函数。

如果你熟悉OpenAI的函数调用功能,我们可以使用OpenAI LLM生成正确的参数来调用函数,我们在这里使用的OpenAI Tools代理正在利用一些功能:

agent = create_openai_tools_agent(llm, toolkit, prompt)

最后,为了在LangChain中运行代理,我们不能直接对它们调用“run”类型的方法。它们需要通过AgentExecutor运行。

我在最后才提到代理执行者,因为我不认为它是理解代理如何工作的关键概念,把它和其他东西放在一起只会让整个事情看起来比它需要的更复杂,也会分散对其他更基本概念的理解。

因此,现在我们正在介绍它,AgentExecutor充当LangChain中代理的运行时,并允许代理保持运行,直到它准备好向用户返回其最终响应。在伪代码中,AgentExecutor的操作如下(直接引用自LangChain文档):

next_action = agent.get_action(...)
while next_action != AgentFinish:observation = run(next_action)next_action = agent.get_action(..., next_action, observation)
return next_action

它们基本上是一个while循环,不断调用代理上的下一个操作方法,直到代理返回其最终响应。因此,让我们在代理执行器中设置代理。我们将代理传递给它,还必须将工具包传递给它。我们将verbose设置为True,这样我们就可以了解代理在处理我们的请求时正在做什么:

agent_executor = AgentExecutor(agent=agent, tools=toolkit, verbose=True)

就是这样。现在我们已经准备好向代理传递命令了:

result = agent_executor.invoke({"input": "what is 1 + 1"})

让我们运行脚本,看看代理的输出:

python3 math-agent.py

因为我们已经在AgentExecutor上设置了verbose=True,所以我们可以看到代理所执行的操作行。它确定了我们应该调用“加”工具,调用带有所需参数的“加”工具,并返回我们的结果。

下面是完整源代码:

import osfrom langchain.agents import AgentExecutor, create_openai_tools_agent
from langchain_openai import ChatOpenAIfrom langchain.tools import BaseTool, StructuredTool, tool
from langchain_core.prompts import ChatPromptTemplate, MessagesPlaceholderos.environ["OPENAI_API_KEY"] = "sk-"# setup the tools
@tool
def add(a: int, b: int) -> int:"""Add two numbers."""return a + b@tool
def multiply(a: int, b: int) -> int:"""Multiply two numbers."""return a * b@tool
def square(a) -> int:"""Calculates the square of a number."""a = int(a)return a * aprompt = ChatPromptTemplate.from_messages([("system", """You are a mathematical assistant.Use your tools to answer questions. If you do not have a tool toanswer the question, say so. Return only the answers. e.gHuman: What is 1 + 1?AI: 2"""),MessagesPlaceholder("chat_history", optional=True),("human", "{input}"),MessagesPlaceholder("agent_scratchpad"),]
)# Choose the LLM that will drive the agent
llm = ChatOpenAI(model="gpt-3.5-turbo-1106", temperature=0)# setup the toolkit
toolkit = [add, multiply, square]# Construct the OpenAI Tools agent
agent = create_openai_tools_agent(llm, toolkit, prompt)# Create an agent executor by passing in the agent and tools
agent_executor = AgentExecutor(agent=agent, tools=toolkit, verbose=True)result = agent_executor.invoke({"input": "what is 1 + 1?"})print(result['output'])

测试代码

让我们向代理提出几个问题,看看它的表现如何。

5的平方是多少?

我们再次得到了正确的结果,并看到它确实使用了我们的平方工具。

5的6次方是多少?

这需要一个有趣的推理过程。首先使用平方工具;然后,利用这个结果,尝试使用乘法工具来得到最终的答案。无可否认,最终的答案3125是错误的,需要再乘以5才能得到正确的答案。但是看到代理如何尝试使用不同的工具和多个步骤来尝试获得最终答案是很有趣的。

1 - 3等于多少?

我们没有减号工具。但它足够聪明,可以使用我们的添加工具,但将第二个值设置为-3。有趣的是,有时他们是如此的聪明和有创造力。

64的平方根是多少

作为最后的测试,如果我们要求它执行一个不属于我们工具集的数学运算会怎么样?由于我们没有用于平方根的工具,因此它不会尝试调用工具,而是直接使用LLM计算值。

我们的系统提示词确实告诉它回答“不知道”,如果它没有正确的工具来完成这项工作,它有时在测试期间确实会这样做。改进的初始系统提示符可能有助于解决这个问题,至少在某种程度上是这样。

观察

基于对代理的使用,我注意到以下几点:

  • 当直接问它有工具可以回答的问题时,它会非常一致地使用正确的工具来完成任务,并返回正确的答案。所以,从这个意义上说,它非常可靠。
  • 如果问题有点复杂,例如我们的“5的6次方”问题,它并不总是返回正确的结果。
  • 它有时可以只使用LLM的纯粹力量来回答我们的问题,而不调用我们的工具。
  • 建议你对照示例,测试不同的大模型,尤其国内主流大模型,如智普、DeepSeek等。

总结

希望本文介绍内容能帮助你开始在LangChain中构建代理。记住,代理本质上只是一个大脑(LLM)和一堆工具,它们可以用来帮助我们完成一些特定任务。

相关文章:

LangChain系列: 使用工具和工具包构建代理实战教程

让我们在LangChain中构建简单代理示例,以帮助我们理解代理的基本概念和构建块。通过保持简单,我们可以更好地掌握这些代理背后的基本思想,使我们能够在未来构建更复杂的代理。 什么是代理 LangChain官方文档有非常好的章节来介绍其代理的高级…...

布隆过滤器(简单介绍)

布隆过滤器(Bloom Filter) 是一种高效的概率型数据结构,用于快速判断一个元素是否可能存在于某个集合中。它的核心特点是空间效率极高,但存在一定的误判率(可能误报存在,但不会漏报)。 核心原理…...

C++ 利器:inline 与 nullptr

探秘 C 利器:inline 与 nullptr 引言 在 C 的浩瀚海洋中,有着许多实用且强大的特性,它们如同夜空中闪烁的繁星,照亮了开发者前行的道路。今天,我们要深入探索其中两颗耀眼的星星:inline 关键字和 nullptr …...

给一个单体项目加装Feign

1.导入pom坐标 <dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-starter-openfeign</artifactId><version>4.1.2</version> </dependency> 2.主函数注解 EnableFeignClients public cl…...

可以使用Deepseek R1模型的平台集锦

最近Deepseek掀起了AI浪潮&#xff0c;就在今天百度文心一言和ChatGPT宣布要在近期实施免费开放&#xff0c;日渐减少的用户。Deepseek这么火爆&#xff0c;其官网却一直遭受攻击&#xff0c;访问速度很慢。自己本地部署&#xff0c;又负担不起硬件费用&#xff0c;相比之下&am…...

“探索1688平台:高效获取店铺商品信息的实用指南“

在电商领域&#xff0c;获取店铺所有商品信息对于商家进行数据分析、库存管理、竞品分析等方面具有重要意义。1688平台作为中国领先的B2B电商平台&#xff0c;提供了丰富的API接口供开发者使用&#xff0c;其中就包括获取店铺所有商品信息的接口。本文将详细介绍如何使用该接口…...

在fedora41中安装钉钉dingtalk_7.6.25.4122001_amd64

在Fedora-Workstation-Live-x86_64-41-1.4中安装钉钉dingtalk_7.6.25.4122001_amd64.deb 到官网下载钉钉Linux客户端com.alibabainc.dingtalk_7.6.25.4122001_amd64.deb https://page.dingtalk.com/wow/z/dingtalk/simple/ddhomedownload#/ 一、直接使用dpkg命令安装deb包报错…...

数据结构:图论入门

图论起源于欧拉对哥尼斯堡七桥问题的解决. 他构建的图模型将陆地用点来表示, 桥梁则用线表示, 如此一来, 该问题便转化为在图中能否不重复地遍历每条边的问题. 图论的应用 地图着色 在地图着色问题中, 我们用顶点代表国家, 将相邻国家之间用边相连. 这样, 问题就转化为用最少…...

有限状态系统的抽象定义及CEGAR分析解析理论篇

文章目录 一、有限状态系统的抽象定义及相关阐述1、有限状态系统定义2、 有限状态系统间的抽象关系&#xff08;Abstract&#xff09;2.1 基于函数的抽象定义2.2 基于等价关系的抽象定义 二、 基于上面的定义出发&#xff0c;提出的思考1. 为什么我们想要/需要进行抽象2. 抽象是…...

Apache Hive用PySpark统计指定表中各字段的空值、空字符串或零值比例

from pyspark.sql import SparkSession from pyspark.sql.functions import col, coalesce, trim, when, lit, sum from pyspark.sql.types import StringType, NumericType# 初始化SparkSession spark SparkSession.builder \.appName("Hive Data Quality Analysis"…...

高校元宇宙实训室解决方案:以技术驱动教育,用数字人链接未来

在AIGC技术的浪潮下&#xff0c;AI数字人正成为数字营销、文化传播等领域的核心工具。为助力高校培养适应未来需求的新型人才&#xff0c;广州虚拟动力推出高校元宇宙实训室解决方案&#xff0c;通过动作捕捉设备与虚拟数字人技术&#xff0c;构建沉浸式教学场景&#xff0c;赋…...

提升编程效率,体验智能编程助手—豆包MarsCode一键Apply功能测评

提升编程效率&#xff0c;体验智能编程助手—豆包MarsCode一键Apply功能测评 &#x1f31f; 嗨&#xff0c;我是LucianaiB&#xff01; &#x1f30d; 总有人间一两风&#xff0c;填我十万八千梦。 &#x1f680; 路漫漫其修远兮&#xff0c;吾将上下而求索。 目录 引言豆包…...

【前端开发】query参数和params参数的区别

在Web开发中&#xff0c;query参数&#xff08;URL查询参数&#xff09;和params参数&#xff08;路由参数&#xff09;是两种不同的URL传参方式&#xff0c;它们的核心区别如下&#xff1a; 一、 位置不同 query参数params参数位置URL中?之后&#xff0c;用&连接多个参数…...

推荐系统召回算法

推荐系统召回算法 召回算法UserCFItemCFSwing矩阵分解 召回算法 基于协同过滤的召回算法主要是应用在推荐环节的早期阶段&#xff0c;大致可以分为基于用户、基于物品的。两者各有优劣&#xff0c;优点是具有较好的可解释性&#xff0c;缺点是对于稀疏的交互矩阵&#xff0c;效…...

Python基础(上)

1. 基础语法 1.1 环境安装 Python版本: 推荐使用Python 3.6.6及以上开发工具: PyCharm 1.2 基本语法 输出: print("Hello World")​ 注释: 单行注释: # 注释内容​&#xff08;快捷键 Ctrl/​&#xff09; 多行注释: 使用三引号 注释内容​ 注意&#xff1a;不推…...

【DuodooBMS】给PDF附件加“受控”水印的完整Python实现

给PDF附件加“受控”水印的完整Python实现 功能需求 在实际工作中&#xff0c;许多文件需要添加水印以标识其状态&#xff0c;例如“受控”“机密”等。对于PDF文件&#xff0c;添加水印不仅可以增强文件的可识别性&#xff0c;还可以防止未经授权的使用。本代码的功能需求是…...

【虚幻引擎UE】UE4.23到UE5.5的核心功能变化

简单总结从UE4.23到UE5.5&#xff0c;虚幻引擎的重大变化&#xff1a; 1. WebGL/HTML5 平台支持和像素流 UE4.23-UE4.25&#xff1a;移除官方HTML5支持&#xff0c;改为社区插件维护。 但通过第三方插件&#xff08;如WebAssemblyWebGPU&#xff09;可在浏览器运行部分项目。U…...

阿里云《AI 剧本生成与动画创作》解决方案技术评测

引言 随着人工智能技术的发展&#xff0c;越来越多的工具和服务被应用于内容创作领域。阿里云推出的《AI 剧本生成与动画创作》解决方案&#xff0c;利用函数计算 FC 构建 Web 服务&#xff0c;结合百炼模型服务和 ComfyUI 工具&#xff0c;实现了从故事剧本撰写、插图设计、声…...

commons-io 包 IOUtils、FileUtils、FilenameUtils

1. IOUtils void IOUtils.closeQuietly(Closeable... closeables) 无条件关闭流。int IOUtils.copy(InputStream inputStream, OutputStream outputStream) 将字节从InputStream复制到OutputStream&#xff0c;返回复制的长度&#xff0c;流最大不能超过2G&#xff0c;默认缓冲…...

JavaScript 加密技术全面指南

一、加密技术概述 在现代 Web 开发中&#xff0c;加密技术在保护用户数据和确保信息安全方面发挥着至关重要的作用。本文将带您了解 JavaScript 加密技术的基本概念、分类及其在实际应用中的场景。 加密的基本概念 加密是一种将明文数据转换为密文的技术&#xff0c;以保护数…...

《Qt C++ 与 OpenCV:解锁视频播放程序设计的奥秘》

引言:探索视频播放程序设计之旅 在当今数字化时代,多媒体应用已渗透到我们生活的方方面面,从日常的视频娱乐到专业的视频监控、视频会议系统,视频播放程序作为多媒体应用的核心组成部分,扮演着至关重要的角色。无论是在个人电脑、移动设备还是智能电视等平台上,用户都期望…...

linux arm系统烧录

1、打开瑞芯微程序 2、按住linux arm 的 recover按键 插入电源 3、当瑞芯微检测到有设备 4、松开recover按键 5、选择升级固件 6、点击固件选择本地刷机的linux arm 镜像 7、点击升级 &#xff08;忘了有没有这步了 估计有&#xff09; 刷机程序 和 镜像 就不提供了。要刷的时…...

postgresql|数据库|只读用户的创建和删除(备忘)

CREATE USER read_only WITH PASSWORD 密码 -- 连接到xxx数据库 \c xxx -- 授予对xxx数据库的只读权限 GRANT CONNECT ON DATABASE xxx TO read_only; GRANT USAGE ON SCHEMA public TO read_only; GRANT SELECT ON ALL TABLES IN SCHEMA public TO read_only; GRANT EXECUTE O…...

数据链路层的主要功能是什么

数据链路层&#xff08;OSI模型第2层&#xff09;的核心功能是在相邻网络节点&#xff08;如交换机、主机&#xff09;间提供可靠的数据帧传输服务&#xff0c;主要职责包括&#xff1a; &#x1f511; 核心功能详解&#xff1a; 帧封装与解封装 封装&#xff1a; 将网络层下发…...

【Java_EE】Spring MVC

目录 Spring Web MVC ​编辑注解 RestController RequestMapping RequestParam RequestParam RequestBody PathVariable RequestPart 参数传递 注意事项 ​编辑参数重命名 RequestParam ​编辑​编辑传递集合 RequestParam 传递JSON数据 ​编辑RequestBody ​…...

大学生职业发展与就业创业指导教学评价

这里是引用 作为软工2203/2204班的学生&#xff0c;我们非常感谢您在《大学生职业发展与就业创业指导》课程中的悉心教导。这门课程对我们即将面临实习和就业的工科学生来说至关重要&#xff0c;而您认真负责的教学态度&#xff0c;让课程的每一部分都充满了实用价值。 尤其让我…...

uniapp 字符包含的相关方法

在uniapp中&#xff0c;如果你想检查一个字符串是否包含另一个子字符串&#xff0c;你可以使用JavaScript中的includes()方法或者indexOf()方法。这两种方法都可以达到目的&#xff0c;但它们在处理方式和返回值上有所不同。 使用includes()方法 includes()方法用于判断一个字…...

【Elasticsearch】Elasticsearch 在大数据生态圈的地位 实践经验

Elasticsearch 在大数据生态圈的地位 & 实践经验 1.Elasticsearch 的优势1.1 Elasticsearch 解决的核心问题1.1.1 传统方案的短板1.1.2 Elasticsearch 的解决方案 1.2 与大数据组件的对比优势1.3 关键优势技术支撑1.4 Elasticsearch 的竞品1.4.1 全文搜索领域1.4.2 日志分析…...

uni-app学习笔记三十五--扩展组件的安装和使用

由于内置组件不能满足日常开发需要&#xff0c;uniapp官方也提供了众多的扩展组件供我们使用。由于不是内置组件&#xff0c;需要安装才能使用。 一、安装扩展插件 安装方法&#xff1a; 1.访问uniapp官方文档组件部分&#xff1a;组件使用的入门教程 | uni-app官网 点击左侧…...

linux设备重启后时间与网络时间不同步怎么解决?

linux设备重启后时间与网络时间不同步怎么解决&#xff1f; 设备只要一重启&#xff0c;时间又错了/偏了&#xff0c;明明刚刚对时还是对的&#xff01; 这在物联网、嵌入式开发环境特别常见&#xff0c;尤其是开发板、树莓派、rk3588 这类设备。 解决方法&#xff1a; 加硬件…...