跳到主要内容

LangChain快速入门与Agent开发实战3

课程说明:

  体验课时间有限,若想深度学习大模型技术,欢迎大家报名由我主讲的《2025大模型Agent智能体开发实战》(夏季班)

d0c81dfe43a1becced8c07db33c3a787_

《2025大模型Agent智能体开发实战》(夏季班) 为【100+小时】体系大课,总共20大模块精讲精析,零基础直达大模型企业级应用!

img

部分项目成果演示

from IPython.display import Video
  • MateGen项目演示
Video("https://ml2022.oss-cn-hangzhou.aliyuncs.com/4.MateGen%20Pro%20%E9%A1%B9%E7%9B%AE%E5%8A%9F%E8%83%BD%E6%BC%94%E7%A4%BA.mp4", width=800, height=400)
  • 智能客服项目演示
Video("https://ml2022.oss-cn-hangzhou.aliyuncs.com/%E6%99%BA%E8%83%BD%E5%AE%A2%E6%9C%8D%E6%A1%88%E4%BE%8B%E6%BC%94%E7%A4%BA.mp4", width=800, height=400)
  • Dify项目演示
Video("https://ml2022.oss-cn-hangzhou.aliyuncs.com/2f1b47f42c65fd59e8d3a83e6cb9f13b_raw.mp4", width=800, height=400)
  • LangChain&LangGraph搭建Multi-Agnet
Video("https://ml2022.oss-cn-hangzhou.aliyuncs.com/%E5%8F%AF%E8%A7%86%E5%8C%96%E6%95%B0%E6%8D%AE%E5%88%86%E6%9E%90Multi-Agent%E6%95%88%E6%9E%9C%E6%BC%94%E7%A4%BA%E6%95%88%E6%9E%9C.mp4", width=800, height=400)

此外,若是对大模型底层原理感兴趣,也欢迎报名由我和菜菜老师共同主讲的《2025大模型原理与实战课程》(夏季班)

da5d51c998df07d747cd223c1ed25f7

两门大模型课程夏季班目前上新特惠+618年中钜惠双惠叠加,合购还有更多优惠哦~详细信息扫码添加助教,回复“大模型”,即可领取课程大纲&查看课程详情👇

6d9391e440ee8df1466cef1bce40705

《大模型Agent智能体开发实战(夏季班)》体验课

LangChain快速入门与Agent开发实战-Part 3

三、LangChain智能体开发实战

1. Function calling流程回顾

  掌握了LangChain的基本使用方法后,我们需要进一步扩展langChain的进阶应用:Funcation Calling。

  我们都知道,能调用外部工具,是大模型进化为智能体Agent的关键,如果不能使用外部工具,大模型就只能是个简单的聊天机器人,甚至连查询天气都做不到。由于底层技术限制啊,大模型本身是无法和外部工具直接通信的,因此Function calling的思路,就是创建一个外部函数(function)作为中介,一边传递大模型的请求,另一边调用外部工具,最终让大模型能够间接的调用外部工具。

image-20250318202017508

  例如,当我们要查询当前天气时,让大模型调用外部工具的function calling的过程就如图所示:

image-20250318202029130

  而完整的一次Function calling执行流程如下:

202412191720637

需要注意的是,对于大模型来说,Function calling的本质,是当模型在特殊情况下的一种特殊响应形式: image-20250327181953725

【选学】外部工具OpenWeather注册及API key获取方法

  OpenWeather是一家提供全球范围内的气象数据服务的公司,该公司的服务包括实时天气信息、天气预报、历史天气数据以及各种气象相关的报告等,并且OpenWeather开放了一定使用限度内完全免费的API,即我们可以在代码环境中通过调用OpenWeather API来进行实时天气查询、天气预报等功能,这意味着开发者可以将OpenWeather的天气预报功能加入到他们自己的应用或网站中。

  为了能够调用OpenWeather服务,和OpenAI的API使用过程类似,我们首先需要先注册OpenWeather账号,并获取OpenWeather API Key。这里需要注意的是,对于大多数在线服务的API来说,都需要通过API key来进行身份验证,尽管OpenWeather相对更加Open,有非常多的免费使用的次数,但身份验证仍然是必要的防止API被滥用的有效手段。OpenWeather API key获取流程如下:

  • Step 1.登录OpenWeather官网并点击Sign—>create account完成注册。该网站无需魔法即可直接登录,可以使用国内邮箱或者QQ邮箱均可进行注册,官网地址为:https://openweathermap.org/
e9a6286c483ccf21ab96af1972a918b 577e19b46f4c124b50a0455ccc132e9
  • Step 2.获取API-key:注册完成后,即可在API keys页面查看当前账户的API key:
f44ba05a810500a61516caa28a4fcb4 e497a7ebb4f8d50ff3b6e362e4b569a

  一般来说完成注册后,就会有一个已经激活的API-key。和OpenAI一样,OpenWeather的API key也创建多个。

  • Step 3.将其设置为环境变量:和OpenAI API key类似,为了方便后续调用,我们也可以直接将OpenWeather API key设置为环境变量,变量名为OPENWEATHER_API_KEY。具体设置环境变量的方法参考Ch.1中OpenAI APkey设置环境变量流程,此处不再赘述。
aa0f15de151921727c20918fccc8de0

  设置完了环境变量之后,接下来即可按照如下方式创建OpenWeather API key变量:

open_weather_key = "YOUR_KEY"

  接下来我们通过一个简单的示例,来介绍如何通过OpenWeather API获取实时天气信息:

import requests
import json
# Step 1.构建请求
url = "https://api.openweathermap.org/data/2.5/weather"

# Step 2.设置查询参数
params = {
"q": "Beijing", # 查询北京实时天气
"appid": open_weather_key, # 输入API key
"units": "metric", # 使用摄氏度而不是华氏度
"lang":"zh_cn" # 输出语言为简体中文
}

# Step 3.发送GET请求
response = requests.get(url, params=params)

# Step 4.解析响应
data = response.json()

  这里需要注意的是,城市名必须输入英文名,否则无法正确识别。接下来查看返回结果。首先我们先查看response结果:

response
type(response)

  在未解析之前,我们只能查看到基本请求结果状态,这里的200代表成功相应,即本次发送请求获得了对应的响应,且响应内容包含在response中。考虑到默认情况下返回结果是JSON格式,因此后续代码使用了response.json()对其进行解析。解析内容如下:

data
def get_weather(loc):
"""
查询即时天气函数
:param loc: 必要参数,字符串类型,用于表示查询天气的具体城市名称,\
注意,中国的城市需要用对应城市的英文名称代替,例如如果需要查询北京市天气,则loc参数需要输入'Beijing';
:return:OpenWeather API查询即时天气的结果,具体URL请求地址为:https://api.openweathermap.org/data/2.5/weather\
返回结果对象类型为解析之后的JSON格式对象,并用字符串形式进行表示,其中包含了全部重要的天气信息
"""
# Step 1.构建请求
url = "https://api.openweathermap.org/data/2.5/weather"

# Step 2.设置查询参数
params = {
"q": loc,
"appid": open_weather_key, # 输入API key
"units": "metric", # 使用摄氏度而不是华氏度
"lang":"zh_cn" # 输出语言为简体中文
}

# Step 3.发送GET请求
response = requests.get(url, params=params)

# Step 4.解析响应
data = response.json()
return json.dumps(data)

2. LangChain 调用外部工具流程

  大家可以理解到,如果我们手动实现一个Function calling,其实是非常复杂的。但在LangChain中则不需要那么麻烦,只需要几行代码就可以快速接入自定义的外部工具并实现准确的调用。其实现的过程在LangChain中就是一个组件链,由提示模版、大模型、外部工具和输出解析器组成,并利用大模型在循环中反复调用自身以实现复杂的Function calling流程。

  这里我们以实时获取天气数据为例。在langChain中,如果想要把一个普通的函数,变成一个可以被大模型调用的工具,只需要将函数包装成一个Tool对象即可。代码如下:

import os
from dotenv import load_dotenv
load_dotenv(override=True)

OPENWEATHER_API_KEY = os.getenv("OPENWEATHER_API_KEY")
# print(OPENWEATHER_API_KEY) # 可以通过打印查看
import os
import requests
import json
from langchain_core.tools import tool

@tool
def get_weather(loc):
"""
查询即时天气函数
:param loc: 必要参数,字符串类型,用于表示查询天气的具体城市名称,\
注意,中国的城市需要用对应城市的英文名称代替,例如如果需要查询北京市天气,则loc参数需要输入'Beijing';
:return:OpenWeather API查询即时天气的结果,具体URL请求地址为:https://api.openweathermap.org/data/2.5/weather\
返回结果对象类型为解析之后的JSON格式对象,并用字符串形式进行表示,其中包含了全部重要的天气信息
"""
# Step 1.构建请求
url = "https://api.openweathermap.org/data/2.5/weather"

# Step 2.设置查询参数
params = {
"q": loc,
"appid": OPENWEATHER_API_KEY, # 输入API key
"units": "metric", # 使用摄氏度而不是华氏度
"lang":"zh_cn" # 输出语言为简体中文
}

# Step 3.发送GET请求
response = requests.get(url, params=params)

# Step 4.解析响应
data = response.json()
return json.dumps(data)

  依然使用DeepSeek模型,如下代码所示:

from langchain.chat_models import init_chat_model

# 初始化模型
model = init_chat_model("deepseek-chat", model_provider="deepseek")

  接下来,如果想让大模型调用某一个外部工具,需要使用bind_tools方法,将工具绑定到模型上。代码如下:

# 定义 天气查询 工具函数
tools = [get_weather]

# 将工具绑定到模型
llm_with_tools = model.bind_tools(tools)

  接下来,便可以通过新的llm_with_tools模型通过invoke方法来调用模型。代码如下:

response = llm_with_tools.invoke("你好, 请问北京的天气怎么样?")

print(response)

  这会产生一个包含tool_calls 的模型响应,打印如下:

response.additional_kwargs

  我们需要调用ToolsAgentOutputParser输出解析器来处理模型响应。

from langchain.agents.output_parsers.tools import ToolsAgentOutputParser

# 解析模型响应
agentAction = ToolsAgentOutputParser().invoke(response)

print(agentAction)

  从结果上我们看到它返回一个 ToolAgentAction,这些操作由AgentExecuter执行(LangChain构建Agent的底层逻辑就是AgentExecutor),并且列表中的每个操作都会返回一个字符串输出。

  我们可以从ToolAgentAction中获取工具调用,并手动执行工具调用以获取工具调用的结果。代码如下:

# 获取工具调用
for tool_call in response.tool_calls:
selected_tool = {"get_weather": get_weather}[tool_call["name"].lower()]
tool_output = selected_tool.invoke(tool_call["args"])
print(tool_output)

  最后,使用LangChain中的format_to_tool_messages函数,将工具调用转换为工具消息。代码如下:

from langchain.agents.format_scratchpad.tools import format_to_tool_messages

format_to_tool_messages(intermediate_steps = [(agentAction[0], tool_output)])

  这个过程会返回一个ToolMessage以添加到提示中并用于调用模型生成最终的回复。其完整流程其实是这样的:

FENCE0

  代理是链(Runnable 序列),它循环运行并调用自身,直到达到最终的目标,或者出现异常才会终止。

  • llm_with_tools:当接收到用户的输入时,大模型可以决定使用工具并返回tool_call
  • ToolsAgentOutputParser:将这些返回解析为要执行的toolAgentAction;
  • 该工具执行后,输出(agent_scratch_pad), 使用format_to_tool_messages进行处理,以产生一条可再次用于提示chat_history的消息;
  • 当大模型不再执行任何工具调用并返回最终输出时,循环结束。

  代理是链(Runnable 序列),它循环运行并调用自身,直到达到最终的目标,或者出现异常才会终止。

  当然,上述流程是为了帮助大家理解LangChain中代理的实现方式,在实际使用中,我们其实可以直接使用create_tool_calling_agent来快速构建工具调用代理。代码如下:

from langchain.agents import create_tool_calling_agent, tool
from langchain_core.prompts import ChatPromptTemplate

#定义工具
tools = [get_weather]

# 构建提示模版
prompt = ChatPromptTemplate.from_messages(
[
("system", "你是天气助手,请根据用户的问题,给出相应的天气信息"),
("human", "{input}"),
("placeholder", "{agent_scratchpad}"),
]
)

# 初始化模型
model = init_chat_model("deepseek-chat", model_provider="deepseek")

# 直接使用`create_tool_calling_agent`创建代理
agent = create_tool_calling_agent(model, tools, prompt)

  使用AgentExecutor来执行代理。代码如下:

from langchain.agents import AgentExecutor
agent_executor = AgentExecutor(agent=agent, tools=tools, verbose=True)


response = agent_executor.invoke({"input": "请问今天北京的天气怎么样?"})
print(response)
print(response["output"])

3. LangChain Agents 运行流程

  LangChainAgents模块的整体架构设计。如下所示:

  在Agents的内部结构。每个Agent组件一般会由语言模型 + 提示 + 输出解析器构成,它会作为Agents的大脑去处理用户的输入。Agent能够处理的输入主要来源于三个方面:input代表用户的原始输入,Model Response指的是模型对某一个子任务的响应输出,而History则能携带上下文的信息。其输出部分,则链接到实际的工具库,需要调用哪些工具,将由经过Agent模块后拆分的子任务来决定。

  而我们知道,大模型调用外部函数会分为两个过程:识别工具和实际执行。在Message -> Agent -> Toolkits 这个流程中,负责的是将子任务拆解,然后根据这些子任务在工具库中找到相应的工具,提取工具名称及所需参数,这个过程可以视作一种“静态”的执行流程。而将这些决策转化为实际行动的工作,则会交给AgentExecutor

  所以综上需要理解的是:在LangChain的Agents实际架构中,Agent的角色是接收输入并决定采取的操作,但它本身并不直接执行这些操作。这一任务是由AgentExecutor来完成的。将Agent(决策大脑)与AgentExecutor(执行操作的Runtime)结合使用,才构成了完整的Agents(智能体),其中AgentExecutor负责调用代理并执行指定的工具,以此来实现整个智能体的功能。

  这也就是为什么create_tool_calling_agent需要通过AgentExecutor才能够实际运行的原因。当然,在这种模式下,AgentExecutor的内部已经自动处理好了关于我们工具调用的所有逻辑,其中包含串行和并行工具调用的两种常用模式。

image-20250401155527229

3.1 多工具并联调用

  在大模型中,并行工具调用指的是在大模型调用外部工具时,可以在单次交互过程中可以同时调用多个工具,并行执行以解决用户的问题。如下图所示:

image-20250320184519636

  而在create_tool_calling_agent中,已经自动处理了并行工具调用的处理逻辑,并不需要我们在手动处理,比如接下来测试一些复杂的问题:

from langchain.agents import create_tool_calling_agent, tool
from langchain_core.prompts import ChatPromptTemplate

#定义工具
tools = [get_weather]

# 构建提示模版
prompt = ChatPromptTemplate.from_messages(
[
("system", "你是天气助手,请根据用户的问题,给出相应的天气信息"),
("human", "{input}"),
("placeholder", "{agent_scratchpad}"),
]
)

# 初始化模型
model = init_chat_model("deepseek-chat", model_provider="deepseek")

# 直接使用`create_tool_calling_agent`创建代理
agent = create_tool_calling_agent(model, tools, prompt)

  这里我们在提出的问题中,尝试让大模型同时查询北京和上海两个城市的天气并汇总结果。

from langchain.agents import AgentExecutor
agent_executor = AgentExecutor(agent=agent, tools=tools, verbose=True)


response = agent_executor.invoke({"input": "请问今天北京和杭州的天气怎么样,哪个城市更热?"})
print(response)

  从这个过程中可以明显的看出,一次性发起了同一个外部函数的两次调用请求,并依次获得了北京和杭州两个城市的天气。这就是一次标准的parallel_function_call

image-20250401161846923

3.2 多工具串联调用

  接下来继续尝试进行多工具串联调用测试:

image-20250320184527687

  此时我们再定义一个write_file函数,用于将“文本写入本地”:

@tool
def write_file(content):
"""
将指定内容写入本地文件。
:param content: 必要参数,字符串类型,用于表示需要写入文档的具体内容。
:return:是否成功写入
"""

return "已成功写入本地文件。"

  然后在tools列表中直接添加write_file工具,并修改提示模版,添加write_file工具的使用场景。代码如下所示:

from langchain.agents import AgentExecutor, create_tool_calling_agent, tool
from langchain_core.prompts import ChatPromptTemplate


tools = [get_weather, write_file]

prompt = ChatPromptTemplate.from_messages(
[
("system", "你是天气助手,请根据用户的问题,给出相应的天气信息,如果用户需要将查询结果写入文件,请使用write_file工具"),
("human", "{input}"),
("placeholder", "{agent_scratchpad}"),
]
)

# 初始化模型
model = init_chat_model("deepseek-chat", model_provider="deepseek")

agent = create_tool_calling_agent(model, tools, prompt)

  接下来尝试运行:

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


agent_executor.invoke({"input": "查一下北京和杭州现在的温度,并将结果写入本地的文件中。"})

  通过中间过程信息的打印,我们能够看到在一次交互过程中依次调用的get_weather查询到北京和杭州的天气,然后又将结果写入到本地的文件中。这就是一个非常典型的串行工具调用的流程,如下图所示:

image-20250401162048526

4. 实战:多智能体协作实现浏览器自动化

  正如上述我们使用的create_tool_calling_agent方法,它其实在langChain中是一个通用的用来构建工具代理的方法,除此以外,langChain还封装了非常多种不同的Agent实现形式,大家可以在这个链接中查看到所有LangChain中已经集成的Agent实现形式:

image-20250401162048526

  每一个Agent的实现都对应着不同的应用场景,而Agent的实现方式也多种多样,比较常用的Agent类型如下表所示:

推荐的Agent创建函数

函数名功能描述适用场景
create_tool_calling_agent创建使用工具的Agent通用工具调用
create_openai_tools_agent创建OpenAI工具AgentOpenAI模型专用
create_openai_functions_agent创建OpenAI函数AgentOpenAI函数调用
create_react_agent创建ReAct推理Agent推理+行动模式
create_structured_chat_agent创建结构化聊天Agent多输入工具支持
create_conversational_retrieval_agent创建对话检索Agent检索增强对话
create_json_chat_agent创建JSON聊天AgentJSON格式交互
create_xml_agent创建XML格式AgentXML逻辑格式
create_self_ask_with_search_agent创建自问自答搜索Agent自主搜索推理

  其中比较通用场景的就是我们刚刚使用的create_tool_calling_agent,而对于一些符合OpenAI API RESTFUL API的模型,则同样可以使用create_openai_tools_agent,另外像create_react_agent可以用于一些推理任务,create_conversational_retrieval_agent则可以用于一些对话系统,具体还是需要根据实际需求来选择。

  目前来说,在大模型应用开发领域有非常多的需求场景,其中一个比较热门的就是浏览器自动化,通过自动化提取网页内容,然后进行分析,最后生成报告。这样的流程提升效率和收集信息的有效途径。因此接下来,我们就尝试使用尝试使用create_openai_tools_agent来实际开发一个浏览器自动化代理。

  首先,执行浏览器自动化代理需要安装一系列的第三方依赖包,如下所示:

! pip install playwright lxml langchain_community beautifulsoup4 reportlab

  此外,还需要安装 Playwright 浏览器,需要在当前虚拟环境中执行如下命令:

! playwright install

  这个安装过程它会下载并安装 Playwright 支持的浏览器内核(注意:这里不是用我们本机已有的浏览器),包括Chromium(类似 Chrome)、FirefoxWebKit(类似 Safari),并将这些浏览器下载到本地的 .cache/ms-playwright 目录或项目的 ~/.playwright 目录中,以便 Playwright 使用稳定一致的运行环境。

  这个案例的核心代码首先是需要用代理工具初始化同步 Playwright 浏览器:

FENCE0

  然后再通过create_openai_tools_agent接收初始化的大模型和Playwright工具构建共同构建OpenAI Tools 代理,最后通过AgentExecutor执行代理。

FENCE0

  完整的代码因为langChian的模块化封装非常简洁,如下所示:

FENCE0

  但需要注意的是:Playwright 工具的初始化过程需要同步执行,在Jupyter Notebook中无法直接使用,需要将代码保存为Python文件运行。这里完整的代码脚本为auto_playwright.py,已经上传到了百度网盘中,大家可以扫码进行领取。

image-20250610203719731 6d9391e440ee8df1466cef1bce40705

  运行效果如下所示:

Video("https://ml2022.oss-cn-hangzhou.aliyuncs.com/%E6%B5%8F%E8%A7%88%E5%99%A8%E6%8A%93%E5%8F%96%E5%AE%9E%E6%97%B6%E6%95%B0%E6%8D%AE%E6%BC%94%E7%A4%BA.mp4", width=800, height=400)

  更进一步地,我们还可以将Playwright Agent封装成工具函数,并结合LangChainLCEL串行链,实现一个更加复杂的浏览器自动化代理。这里定义的工具如下所示:

FENCE0

  然后我们可以自定义不同的链路,比如简单的串行链由Playwright Agentgenerate_pdf Agent组成,即先爬取网页的内容,然后将网页中的内容写入到本地的PDF文件中。

FENCE0

  除此以外,我们还可以再定一个摘要工具,在使用Playwright工具访问网页后,根据爬取到的网页内容先使用大模型进行摘要总结,再调用generate_pdf工具将总结内容写入到本地的PDF文件中。代码如下所示:

FENCE0

  完整的代码如下所示:

FENCE0

  上述完整的代码我们已经上传到百度网盘中playwright_pdf_agent.py文件中,大家可以扫描下方的二维码免费领取。

image-20250610203728333 6d9391e440ee8df1466cef1bce40705

  运行效果如下所示:

Video("https://ml2022.oss-cn-hangzhou.aliyuncs.com/%E6%B5%8F%E8%A7%88%E5%99%A8%E5%A4%9A%E6%99%BA%E8%83%BD%E4%BD%93%E5%8D%8F%E4%BD%9C.mp4", width=800, height=400)