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浪潮,就在今天百度文心一言和ChatGPT宣布要在近期实施免费开放,日渐减少的用户。Deepseek这么火爆,其官网却一直遭受攻击,访问速度很慢。自己本地部署,又负担不起硬件费用,相比之下&am…...
“探索1688平台:高效获取店铺商品信息的实用指南“
在电商领域,获取店铺所有商品信息对于商家进行数据分析、库存管理、竞品分析等方面具有重要意义。1688平台作为中国领先的B2B电商平台,提供了丰富的API接口供开发者使用,其中就包括获取店铺所有商品信息的接口。本文将详细介绍如何使用该接口…...
在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、 有限状态系统间的抽象关系(Abstract)2.1 基于函数的抽象定义2.2 基于等价关系的抽象定义 二、 基于上面的定义出发,提出的思考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技术的浪潮下,AI数字人正成为数字营销、文化传播等领域的核心工具。为助力高校培养适应未来需求的新型人才,广州虚拟动力推出高校元宇宙实训室解决方案,通过动作捕捉设备与虚拟数字人技术,构建沉浸式教学场景,赋…...
提升编程效率,体验智能编程助手—豆包MarsCode一键Apply功能测评
提升编程效率,体验智能编程助手—豆包MarsCode一键Apply功能测评 🌟 嗨,我是LucianaiB! 🌍 总有人间一两风,填我十万八千梦。 🚀 路漫漫其修远兮,吾将上下而求索。 目录 引言豆包…...
【前端开发】query参数和params参数的区别
在Web开发中,query参数(URL查询参数)和params参数(路由参数)是两种不同的URL传参方式,它们的核心区别如下: 一、 位置不同 query参数params参数位置URL中?之后,用&连接多个参数…...
推荐系统召回算法
推荐系统召回算法 召回算法UserCFItemCFSwing矩阵分解 召回算法 基于协同过滤的召回算法主要是应用在推荐环节的早期阶段,大致可以分为基于用户、基于物品的。两者各有优劣,优点是具有较好的可解释性,缺点是对于稀疏的交互矩阵,效…...
Python基础(上)
1. 基础语法 1.1 环境安装 Python版本: 推荐使用Python 3.6.6及以上开发工具: PyCharm 1.2 基本语法 输出: print("Hello World") 注释: 单行注释: # 注释内容(快捷键 Ctrl/) 多行注释: 使用三引号 注释内容 注意:不推…...
【DuodooBMS】给PDF附件加“受控”水印的完整Python实现
给PDF附件加“受控”水印的完整Python实现 功能需求 在实际工作中,许多文件需要添加水印以标识其状态,例如“受控”“机密”等。对于PDF文件,添加水印不仅可以增强文件的可识别性,还可以防止未经授权的使用。本代码的功能需求是…...
【虚幻引擎UE】UE4.23到UE5.5的核心功能变化
简单总结从UE4.23到UE5.5,虚幻引擎的重大变化: 1. WebGL/HTML5 平台支持和像素流 UE4.23-UE4.25:移除官方HTML5支持,改为社区插件维护。 但通过第三方插件(如WebAssemblyWebGPU)可在浏览器运行部分项目。U…...
阿里云《AI 剧本生成与动画创作》解决方案技术评测
引言 随着人工智能技术的发展,越来越多的工具和服务被应用于内容创作领域。阿里云推出的《AI 剧本生成与动画创作》解决方案,利用函数计算 FC 构建 Web 服务,结合百炼模型服务和 ComfyUI 工具,实现了从故事剧本撰写、插图设计、声…...
commons-io 包 IOUtils、FileUtils、FilenameUtils
1. IOUtils void IOUtils.closeQuietly(Closeable... closeables) 无条件关闭流。int IOUtils.copy(InputStream inputStream, OutputStream outputStream) 将字节从InputStream复制到OutputStream,返回复制的长度,流最大不能超过2G,默认缓冲…...
JavaScript 加密技术全面指南
一、加密技术概述 在现代 Web 开发中,加密技术在保护用户数据和确保信息安全方面发挥着至关重要的作用。本文将带您了解 JavaScript 加密技术的基本概念、分类及其在实际应用中的场景。 加密的基本概念 加密是一种将明文数据转换为密文的技术,以保护数…...
Vue2 第一节_Vue2上手_插值表达式{{}}_访问数据和修改数据_Vue开发者工具
文章目录 1.Vue2上手-如何创建一个Vue实例,进行初始化渲染2. 插值表达式{{}}3. 访问数据和修改数据4. vue响应式5. Vue开发者工具--方便调试 1.Vue2上手-如何创建一个Vue实例,进行初始化渲染 准备容器引包创建Vue实例 new Vue()指定配置项 ->渲染数据 准备一个容器,例如: …...
将对透视变换后的图像使用Otsu进行阈值化,来分离黑色和白色像素。这句话中的Otsu是什么意思?
Otsu 是一种自动阈值化方法,用于将图像分割为前景和背景。它通过最小化图像的类内方差或等价地最大化类间方差来选择最佳阈值。这种方法特别适用于图像的二值化处理,能够自动确定一个阈值,将图像中的像素分为黑色和白色两类。 Otsu 方法的原…...
Nuxt.js 中的路由配置详解
Nuxt.js 通过其内置的路由系统简化了应用的路由配置,使得开发者可以轻松地管理页面导航和 URL 结构。路由配置主要涉及页面组件的组织、动态路由的设置以及路由元信息的配置。 自动路由生成 Nuxt.js 会根据 pages 目录下的文件结构自动生成路由配置。每个文件都会对…...
DBAPI如何优雅的获取单条数据
API如何优雅的获取单条数据 案例一 对于查询类API,查询的是单条数据,比如根据主键ID查询用户信息,sql如下: select id, name, age from user where id #{id}API默认返回的数据格式是多条的,如下: {&qu…...
MySQL用户和授权
开放MySQL白名单 可以通过iptables-save命令确认对应客户端ip是否可以访问MySQL服务: test: # iptables-save | grep 3306 -A mp_srv_whitelist -s 172.16.14.102/32 -p tcp -m tcp --dport 3306 -j ACCEPT -A mp_srv_whitelist -s 172.16.4.16/32 -p tcp -m tcp -…...
Golang——7、包与接口详解
包与接口详解 1、Golang包详解1.1、Golang中包的定义和介绍1.2、Golang包管理工具go mod1.3、Golang中自定义包1.4、Golang中使用第三包1.5、init函数 2、接口详解2.1、接口的定义2.2、空接口2.3、类型断言2.4、结构体值接收者和指针接收者实现接口的区别2.5、一个结构体实现多…...
【LeetCode】算法详解#6 ---除自身以外数组的乘积
1.题目介绍 给定一个整数数组 nums,返回 数组 answer ,其中 answer[i] 等于 nums 中除 nums[i] 之外其余各元素的乘积 。 题目数据 保证 数组 nums之中任意元素的全部前缀元素和后缀的乘积都在 32 位 整数范围内。 请 不要使用除法,且在 O…...
32单片机——基本定时器
STM32F103有众多的定时器,其中包括2个基本定时器(TIM6和TIM7)、4个通用定时器(TIM2~TIM5)、2个高级控制定时器(TIM1和TIM8),这些定时器彼此完全独立,不共享任何资源 1、定…...
02.运算符
目录 什么是运算符 算术运算符 1.基本四则运算符 2.增量运算符 3.自增/自减运算符 关系运算符 逻辑运算符 &&:逻辑与 ||:逻辑或 !:逻辑非 短路求值 位运算符 按位与&: 按位或 | 按位取反~ …...
【阅读笔记】MemOS: 大语言模型内存增强生成操作系统
核心速览 研究背景 研究问题:这篇文章要解决的问题是当前大型语言模型(LLMs)在处理内存方面的局限性。LLMs虽然在语言感知和生成方面表现出色,但缺乏统一的、结构化的内存架构。现有的方法如检索增强生成(RA…...
