11|代理(下):结构化工具对话、Self-Ask with Search以及 Plan and execute代理

在上一讲中,我们深入LangChain程序内部机制,探索了AgentExecutor究竟是如何思考(Thought)、执行(Execute/Act)和观察(Observe)的,这些步骤之间的紧密联系就是代理在推理(Reasoning)和工具调用过程中的“生死因果”。

什么是结构化工具
2023年初,LangChain 引入了“多操作”代理框架,允许代理计划执行多个操作。在此基础上,LangChain 推出了结构化工具对话代理,允许更复杂、多方面的交互。通过指定AgentType.STRUCTURED_CHAT_ZERO_SHOT_REACT_DESCRIPTION 这个代理类型,代理能够调用包含一系列复杂工具的“结构化工具箱”,组合调用其中的多个工具,完成批次相关的任务集合。

什么是 Playwright

Playwright是一个开源的自动化框架,它可以让你模拟真实用户操作网页,帮助开发者和测试者自动化网页交互和测试。
pip install playwright 安装Playwright工具。
还需要通过 playwright install 命令来安装三种常用的浏览器工具。
通过Playwright浏览器工具来访问一个测试网页

from playwright.sync_api import sync_playwright

def run():
    # 使用Playwright上下文管理器
    with sync_playwright() as p:
        # 使用Chromium,但你也可以选择firefox或webkit
        browser = p.chromium.launch()

        # 创建一个新的页面
        page = browser.new_page()

        # 导航到指定的URL
        page.goto('https://langchain.com/')

        # 获取并打印页面标题
        title = page.title()
        print(f"Page title is: {title}")

        # 关闭浏览器
        browser.close()

if __name__ == "__main__":
    run()

这个脚本展示了Playwright的工作方式,一切都是在命令行里面直接完成。
image.png

使用结构化工具对话代理

在这里,我们要使用的Agent类型是STRUCTURED_CHAT_ZERO_SHOT_REACT_DESCRIPTION。要使用的工具则是PlayWrightBrowserToolkit,这是LangChain中基于PlayWrightBrowser包封装的工具箱,它继承自 BaseToolkit类。

工具名称功能描述应用场景
ClickTool模拟浏览器中的点击操作自动化某个动作,如提交表单或导航到另一个页面
NavigateBrowserTool导航到指定的URL或网页打开一个新的网页或进行网站间跳转
NavigateBackTool允许浏览器回退到上一个页面浏览了一个页面后需要返回到之前的页面
ExtractTextTool从网页上提取文本内容获取和分析网页上的文字信息,例如文章、评论或任何其他文本内容
ExtractHyperlinksTool提取网页上的所有超链接收集或分析一个页面上所有的外部链接或内部链接
GetElementsTool根据给定的条件或选择器获取网页上的元素需要找到并与特定的网页元素交互
CurrentWebPageTool提供当前浏览器所在网页的信息,如 URL、标题等了解当前页面的状态,或者需要记录浏览历史
from langchain.agents.agent_toolkits import PlayWrightBrowserToolkit
from langchain.tools.playwright.utils import create_async_playwright_browser

async_browser = create_async_playwright_browser()
toolkit = PlayWrightBrowserToolkit.from_browser(async_browser=async_browser)
tools = toolkit.get_tools()
print(tools)

from langchain.agents import initialize_agent, AgentType
from langchain.chat_models import ChatAnthropic, ChatOpenAI

# LLM不稳定,对于这个任务,可能要多跑几次才能得到正确结果
llm = ChatOpenAI(temperature=0.5)  

agent_chain = initialize_agent(
    tools,
    llm,
    agent=AgentType.STRUCTURED_CHAT_ZERO_SHOT_REACT_DESCRIPTION,
    verbose=True,
)

async def main():
    response = await agent_chain.arun("What are the headers on python.langchain.com?")
    print(response)

import asyncio
loop = asyncio.get_event_loop()
loop.run_until_complete(main())

在这个示例中,我们询问大模型,网页http://python.langchain.com中有哪些标题目录?
因为大模型的数据只在21年9月,所以不存在他的数据,需要通过PlayWrightBrowser工具来解决问题。

第一轮思考

I can use the “navigate_browser” tool to visit the website and then use the “get_elements” tool to retrieve the headers. Let me do that.

这是第一轮思考,大模型知道自己没有相关信息,决定使用PlayWrightBrowserToolkit工具箱中的 navigate_browser 工具。

Action:{"action": "navigate_browser", "action_input": {"url": "[https://python.langchain.com](https://link.zhihu.com/?target=https%3A//python.langchain.com)"}}

行动:通过Playwright浏览器访问这个网站。

Observation: Navigating to https://python.langchain.com returned status code 200

观察:成功得到浏览器访问的返回结果。
在第一轮思考过程中,模型决定使用PlayWrightBrowserToolkit中的navigate_browser工具。

第二轮思考

image.png

Thought:Now that I have successfully navigated to the website, I can use the “get_elements” tool to retrieve the headers. I will specify the CSS selector for the headers and retrieve their text.

第二轮思考:模型决定使用PlayWrightBrowserToolkit工具箱中的另一个工具 get_elements,并且指定CSS selector只拿标题的文字。

Action:{"action": "get_elements", "action_input": {"selector": "h1, h2, h3, h4, h5, h6", "attributes": ["innerText"]}}

行动:用Playwright的 get_elements 工具去拿网页中各级标题的文字。

Observation: [{“innerText”: “Introduction”}, {“innerText”: “Get started”}, {“innerText”: “Modules”}, {“innerText”: “Model I/O”}, {“innerText”: “Data connection”}, {“innerText”: “Chains”}, {“innerText”: “Agents”}, {“innerText”: “Memory”}, {“innerText”: “Callbacks”}, {“innerText”: “Examples, ecosystem, and resources”}, {“innerText”: “Use cases”}, {“innerText”: “Guides”}, {“innerText”: “Ecosystem”}, {“innerText”: “Additional resources”}, {“innerText”: “Support”}, {“innerText”: “API reference”}]

观察:成功地拿到了标题文本。
在第二轮思考过程中,模型决定使用PlayWrightBrowserToolkit中的get_elements工具。

第三轮思考


对上述思考做一个具体说明。第三轮思考:模型已经找到了网页中的所有标题。
行动:给出最终答案。

使用 Self-Ask with Search 代理

Self-Ask with Search 也是 LangChain 中的一个有用的代理类型 (SELF_ASK_WITH_SEARCH)。它利用一种叫做 “Follow-up Question(追问)”加 “Intermediate Answer(中间答案)”的技巧,来辅助大模型寻找事实性问题的过渡性答 案,从而引出最终答案。

from langchain import OpenAI, SerpAPIWrapper
from langchain.agents import initialize_agent, Tool
from langchain.agents import AgentType
llm = OpenAI(temperature=0)
search = SerpAPIWrapper()
tools = [
Tool(
name="Intermediate Answer",
func=search.run,
description="useful for when you need to ask with search",
)
]
self_ask_with_search = initialize_agent(
tools, llm, agent=AgentType.SELF_ASK_WITH_SEARCH, verbose=True
)
self_ask_with_search.run(
"使用玫瑰作为国花的国家的首都是哪里?"
)

image.png

  1. 工具集合:代理包含解决问题所必须的搜索工具,可以用来查询和验证多个信息点。这里我
    们在程序中为代理武装了 SerpAPIWrapper 工具。
  2. 逐步逼近:代理可以根据第一个问题的答案,提出进一步的问题,直到得到最终答案。这种
    逐步逼近的方式可以确保答案的准确性。
  3. 自我提问与搜索:代理可以自己提问并搜索答案。例如,首先确定哪个国家使用玫瑰作为国
    花,然后确定该国家的首都是什么。
  4. 决策链:代理通过一个决策链来执行任务,使其可以跟踪和处理复杂的多跳问题,这对于解 决需要多步推理的问题尤为重要。

使用 Plan and execute 代理

计划和执行代理通过首先计划要做什么,然后执行子任务来实现目标。这个想法是受到 Plan-and-Solve 论文的启发。论文中提出了计划与解决(Plan-and-Solve)提示。它由两 部分组成:首先,制定一个计划,并将整个任务划分为更小的子任务;然后按照该计划执行子 任务。
这种代理的独特之处在于,它的计划和执行不再是由同一个代理所完成,而是:

  • 计划由一个大语言模型代理(负责推理)完成。
  • 执行由另一个大语言模型代理(负责调用工具)完成
pip install -U langchain langchain_experimental
from langchain.chat_models import ChatOpenAI
from langchain_experimental.plan_and_execute import PlanAndExecute, load_agent_ex
from langchain.llms import OpenAI
from langchain import SerpAPIWrapper
from langchain.agents.tools import Tool
from langchain import LLMMathChain
search = SerpAPIWrapper()
llm = OpenAI(temperature=0)
llm_math_chain = LLMMathChain.from_llm(llm=llm, verbose=True)
tools = [
Tool(
name = "Search",
func=search.run,
description="useful for when you need to answer questions about current e
),
Tool(
name="Calculator",
func=llm_math_chain.run,
description="useful for when you need to answer questions about math"
),
]
model = ChatOpenAI(temperature=0)
planner = load_chat_planner(model)
executor = load_agent_executor(model, tools, verbose=True)
agent = PlanAndExecute(planner=planner, executor=executor, verbose=True)
agent.run("在纽约,100美元能买几束玫瑰?")

image.png

14|工具和工具箱:LangChain中的Tool和Toolkits一览

未来的 AI Agent,应该就是以 LLM 为核心控制器的代理系统。而工具,则是代理身上延展出的三头六臂,是代理的武器, 代理通过工具来与世界进行交互,控制并改造世界

工具是代理的武器

当代理接收到一个任务时,它会根据任务的类型和需求,通过大模型的推理,来选择合适的工 具处理这个任务。这个选择过程可以基于各种策略,例如基于工具的性能,或者基于工具处理 特定类型任务的能力。
一旦选择了合适的工具,LangChain 就会将任务的输入传递给这个工具,然后工具会处理这 些输入并生成输出。这个输出又经过大模型的推理,可以被用作其他工具的输入,或者作为最 终结果,被返回给用户。
image.png

如何加载工具

from langchain.agents import load_tools
tool_names = [...]
tools = load_tools(tool_names
                   
某些工具(例如链、代理)可能需要 LLM 来初始化它们。
from langchain.agents import load_tools
tool_names = [...]
llm = ...
tools = load_tools(tool_names, llm=llm)

LangChain 支持的工具一览

image.png

使用 arXiv 工具开发科研助理

arXiv 是一个提供免费访问的预印本库,供研究者在正式出版前上传和分享其研究工作。

# 设置OpenAI API的密钥
import os
os.environ["OPENAI_API_KEY"] = 'Your Key'

# 导入库
from langchain.chat_models import ChatOpenAI
from langchain.agents import load_tools, initialize_agent, AgentType

# 初始化模型和工具
llm = ChatOpenAI(temperature=0.0)
tools = load_tools(
    ["arxiv"],
)

# 初始化链
agent_chain = initialize_agent(
    tools,
    llm,
    agent=AgentType.ZERO_SHOT_REACT_DESCRIPTION,
    verbose=True,
)

# 运行链
agent_chain.run("介绍一下2005.14165这篇论文的创新点?")

研究一下 ZERO_SHOT_REACT_DESCRIPTION 这个 Agent 是怎么通过提 示来引导模型调用工具的

“prompts”: [ "Answer the following questions as best you can. You have access to the following tools:\n\n

首先告诉模型,要尽力回答问题,但是可以访问下面的工具。

arxiv: A wrapper around Arxiv.org Useful for when you need to answer questions about 。。。。

arxiv 工具:一个围绕 Arxiv.org 的封装工具。

Use the following format:\n\n

指导模型输出下面的内容。

Question: the input question you must answer\n (问题:需要回答的问题)
Thought: you should always think about what to do\n (思考:应该总是思考下一步 做什么)
Action: the action to take, should be one of [arxiv]\n (行动:从具体工具列表中选择 行动——这里只有 arxiv 一个工具)
Action Input: the input to the action\n (行动的输入:输入工具的内容)
Observation: the result of the action\n… (观察:工具返回的结果)
(this Thought/Action/Action Input/Observation can repeat N times)\n (上面 Thought/Action/Action Input/Observation 的过程将重复 N 次)
Thought: I now know the final answer\n (现在我知道最终答案了)
Final Answer: the final answer to the original input question\n\n (原始问题的最终答案)

Begin!\n\n

现在开始!

Question: 'Chain-of-Thought Prompting Elicits Reasoning in Large Language Models 这篇论文的创新点\n

真正的问题在此。

Thought:"

开始思考吧!
然后,我们来看看 Chain 的运行过程
image.png

“text”: " I need to read the paper to understand the innovation\n (思考:我需要 阅读文章才能理解创新点)
Action: arxiv\n (行动:arxiv 工具)
Action Input: ‘Chain-of-Thought Prompting Elicits Reasoning in Large Language Models’", (行动的输入:论文的标题)

因为在之前的提示中,LangChain 告诉大模型,对于 Arxiv 工具的输入总是以搜索的形式出 现,因此尽管我指明了论文的 ID,Arxiv 还是根据这篇论文的关键词搜索到了 3 篇相关论文 的信息。 模型对这些信息进行了总结,认为信息已经完善,并给出了最终答案。

模型对这些信息进行了总结,认为信息已经完善,并给出了最终答案。

Thought: I now know the final answer

想法:我现在知道了最终答案。

Final Answer: The innovation of the paper ‘Chain-of-Thought Prompting Elicits Reasoning in Large Language Models’ is the introduction of a simple method called chain of thought prompting, where a few chain of thought demonstrations are provided as exemplars in prompting, which significantly improves the ability of large language models to perform complex reasoning."

最终答案:这篇名为《链式思考提示促使大型语言模型进行推理》的论文的创新之处在于,引 入了一种简单的方法,即链式思考提示,在提示中提供了一些链式思考的示例,这大大提高了 大型语言模型执行复杂推理的能力。

LangChain 中的工具箱一览

image.png

使用 Gmail 工具箱开发个人助理

  • 通过 Gmail 工具箱,你可以通过 LangChain 应用检查邮件、删除垃圾邮件,甚至让它帮你 撰写邮件草稿。
  • 通过 Office365 工具箱,你可以让 LangChain 应用帮你读写文档、总结文档,甚至做 PPT。
  • 通过 GitHub 工具箱,你可以指示 LangChain 应用来检查最新的代码,Commit Changes、Merge Branches,甚至尝试让大模型自动回答 Issues 中的问题——反正大模 型解决代码问题的能力本来就更强。

目标:我要让 AI 应用来访问我的 Gmail 邮件,让他每天早晨检查一次我的邮箱,看看“易 速鲜花”的客服有没有给我发信息

第一步:在 Google Cloud 中设置你的应用程序接口

Gmail API 的官方配置链接完成 ,然后就获取一个开发秘钥。
。。。。。。。。

第二步:根据密钥生成开发 Token

pip install --upgrade google-api-python-client
pip install --upgrade google-auth-oauthlib
pip install --upgrade google-auth-httplib2

from __future__ import print_function

import os.path

from google.auth.transport.requests import Request
from google.oauth2.credentials import Credentials
from google_auth_oauthlib.flow import InstalledAppFlow
from googleapiclient.discovery import build
from googleapiclient.errors import HttpError

# If modifying these scopes, delete the file token.json.
SCOPES = ['https://www.googleapis.com/auth/gmail.readonly']

def main():
    """Shows basic usage of the Gmail API.
    Lists the user's Gmail labels.
    """
    creds = None
    # The file token.json stores the user's access and refresh tokens, and is
    # created automatically when the authorization flow completes for the first
    # time.
    if os.path.exists('token.json'):
        creds = Credentials.from_authorized_user_file('token.json', SCOPES)
    # If there are no (valid) credentials available, let the user log in.
    if not creds or not creds.valid:
        if creds and creds.expired and creds.refresh_token:
            creds.refresh(Request())
        else:
            flow = InstalledAppFlow.from_client_secrets_file(
                'credentials.json', SCOPES)
            creds = flow.run_local_server(port=8088)
        # Save the credentials for the next run
        with open('token.json', 'w') as token:
            token.write(creds.to_json())

    try:
        # Call the Gmail API
        service = build('gmail', 'v1', credentials=creds)
        results = service.users().labels().list(userId='me').execute()
        labels = results.get('labels', [])

        if not labels:
            print('No labels found.')
            return
        print('Labels:')
        for label in labels:
            print(label['name'])

    except HttpError as error:
        # TODO(developer) - Handle errors from gmail API.
        print(f'An error occurred: {error}')

if __name__ == '__main__':
    main()

这是 Google API 网站提供的标准示例代码,里面给了读取权限(gmail.readonly)的 Token,如果你要编写邮件,甚至发送邮件,需要根据需求来调整权限。更多细节可以参阅 Google API 的文档。
这个程序会生成一个 token.json 文件,是有相关权限的开发令牌。这个文件在 LangChain 应 用中需要和密钥一起使用。
image.png

第三步:用 LangChain 框架开发 Gmail App

这段代码的核心目的是连接到 Gmail API,查询用户的邮件,并通过 LangChain 的 Agent 框 架智能化地调用 API(用语言而不是具体 API),与邮件进行互动。

# 设置OpenAI API的密钥
import os
os.environ["OPENAI_API_KEY"] = 'Your Key'

# 导入与Gmail交互所需的工具包
from langchain.agents.agent_toolkits import GmailToolkit

# 初始化Gmail工具包
toolkit = GmailToolkit()

# 从gmail工具中导入一些有用的功能
from langchain.tools.gmail.utils import build_resource_service, get_gmail_credentials

# 获取Gmail API的凭证,并指定相关的权限范围
credentials = get_gmail_credentials(
    token_file="token.json",  # Token文件路径
    scopes=["https://mail.google.com/"],  # 具有完全的邮件访问权限
    client_secrets_file="credentials.json",  # 客户端的秘密文件路径
)
# 使用凭证构建API资源服务
api_resource = build_resource_service(credentials=credentials)
toolkit = GmailToolkit(api_resource=api_resource)

# 获取工具
tools = toolkit.get_tools()
print(tools)

# 导入与聊天模型相关的包
from langchain.chat_models import ChatOpenAI
from langchain.agents import initialize_agent, AgentType

# 初始化聊天模型
llm = ChatOpenAI(temperature=0, model='gpt-4')

# 通过指定的工具和聊天模型初始化agent
agent = initialize_agent(
    tools=toolkit.get_tools(),
    llm=llm,
    agent=AgentType.STRUCTURED_CHAT_ZERO_SHOT_REACT_DESCRIPTION,
)

# 使用agent运行一些查询或指令
result = agent.run(
    "今天易速鲜花客服给我发邮件了么?最新的邮件是谁发给我的?"
)

# 打印结果
print(result)

你的请求是查询今天是否收到了来自“易速鲜花客服”的邮件,以及最新邮件的发送者是谁。 这个请求是模糊的,是自然语言格式,具体调用什么 API,由 Agent、Tool 也就是 Gmail API 它俩商量着来
image.png

总结

**集成多模型和多策略:**LangChain 提供了一种方法,使得多个模型或策略能够在一个统一 的框架下工作。例如,arXiv 是一个单独的工具,它负责处理特定的任务。
**更易于交互和维护:**通过 LangChain,你可以更方便地管理和维护你的工具和模型。
适应性:LangChain 提供的架构允许你轻松地添加新的工具或模型,或者替换现有的工具 或模型。
可解释性:LangChain 还提供了对模型决策的可解释性。

  • 17
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值