06 与外部世界交互的代理功能|豆包MarsCode AI刷题

44 阅读10分钟

仅仅应用思维链推理并不能解决大模型的固有问题:无法主动更新自己的知识,导致出现事实幻觉。也就是说,因为缺乏和外部世界的接触,大模型只拥有训练时见过的知识,以及提示信息中作为上下文提供的附加知识。如果你问的问题超出它的知识范围,要么大模型向你坦白:“我的训练时间截至XXXX年XX月XX日”,要么它就会开始一本正经地胡说。

image.png 上面所说的无论本地知识库还是搜索引擎,都不是封装在大模型内部的知识,我们把它们称为“外部工具”。

代理的作用

每当你遇到这种需要模型做自主判断、自行调用工具、自行决定下一步行动的时候,就要使用Agent。

在LangChain中使用代理,我们只需要理解下面三个元素。

  • 大模型:提供逻辑的引擎,负责生成预测和处理输入。
  • 与之交互的外部工具:可能包括数据清洗工具、搜索引擎、应用程序等。
  • 控制交互的代理:调用适当的外部工具,并管理整个交互过程的流程。

ReAct论文来源于【ICLR 2023】《ReAct: Synergizing Reasoning and Acting in Language Models》 这篇文章的一个关键启发在于:大语言模型可以通过生成推理痕迹和任务特定行动来实现更大的协同作用。 其中,Reasoning包括了对当前环境和状态的观察,并生成推理轨迹。这使模型能够诱导、跟踪和更新操作计划,甚至处理异常情况。Acting在于指导大模型采取下一步的行动,比如与外部源(如知识库或环境)进行交互并且收集信息,或者给出最终答案。

仅仅使用思维链(CoT)提示,LLMs能够执行推理轨迹,以完成算术和常识推理等问题,但这样的模型因为缺乏和外部世界的接触或无法更新自己的知识,会导致幻觉的出现。

而将 ReAct框架和思维链(CoT)结合使用,则能够让大模型在推理过程同时使用内部知识和获取到的外部信息,从而给出更可靠和实际的回应,也提高了 LLMs 的可解释性和可信度。ReAct框架会提示 LLMs 为任务生成推理轨迹和操作,这使得代理能系统地执行动态推理来创建、维护和调整操作计划,同时还支持与外部环境(例如Google搜索、Wikipedia)的交互,以将额外信息合并到推理中。

image.png Agent接到任务之后,自动进行推理,然后自主调用工具完成任务的过程。

通过代理实现ReAct框架

就让我们用LangChain中最为常用的 ZERO_SHOT_REACT_DESCRIPTION ——这种常用代理类型,来剖析一下LLM是如何在ReAct框架的指导之下进行推理的。 案例:给代理一个任务,这个任务是找到玫瑰的当前市场价格,然后计算出加价15%后的新价格。

# 初始化大模型,将用于控制代理的语言模型
llm = ChatOpenAI(model=os.environ["LLM_MODELEND"], temperature=0)

# 设置工具,包括serpapi(这是调用Google搜索引擎的工具)以及llm-math(这是通过LLM进行数学计算的工具)。
tools = load_tools(["serpapi", "llm-math"], llm=llm)

# 初始化Agent,使用工具、语言模型和代理类型来初始化代理
agent = initialize_agent(
    tools, llm, agent=AgentType.ZERO_SHOT_REACT_DESCRIPTION, verbose=True
)

agent.run(
    "目前市场上玫瑰花的平均价格是多少?如果我在此基础上加价15%卖出,应该如何定价?"
)

输出结果:

image.png 对照结果,解读行动链的每一个环节

image.png

思考题一

  1. 在ReAct框架中,推理和行动各自代表什么?其相互之间的关系如何?

    在ReAct框架中,“推理”指的是模型基于当前环境或给定信息进行逻辑分析,以理解问题并形成解决问题的初步想法。而“行动”则是指模型根据推理结果采取的具体步骤,这些步骤可能是查询额外的信息、执行某个操作或是生成下一步的指令等。他们之间的关系是,模型首先会基于已有的信息进行推理,然后根据推理的结果采取行动;接着,模型会根据行动的结果反馈再次进行推理,形成新的行动,这个循环的过程。

  2. 为什么说ReAct框架能改善大模型解决问题时的可解释性和可信度?

    每一步的推理和行动都是明确的,这有助于用户理解模型是如何逐步接近最终解决方案的,从而增强了模型行为的可解释性。此外,由于每个行动都基于具体的推理结果,这种设计减少了模型出错的可能性,增加了用户对模型决策的信任度。

  3. 你能否说一说LangChain中的代理和链的核心差异?

  • 链(Chain) :在LangChain中,链的设计允许开发者将复杂的任务分解成多个简单的子任务,从而简化了整个系统的构建和维护。
  • 代理(Agent) :代理则更加灵活,它不仅包括了链的功能,还具备了自我决策的能力。代理可以基于当前环境状态和目标,自主决定下一步应该执行哪个动作或调用哪个工具。代理通常用于需要较高灵活性和适应性的场景,如自动问答系统、个性化推荐等。代理可以通过与环境的交互不断优化自己的行为策略,实现更高效的问题解决。

Agent的关键组件

  1. 代理(Agent):这个类决定下一步执行什么操作。它由一个语言模型和一个提示(prompt)驱动。提示可能包含代理的性格(也就是给它分配角色,让它以特定方式进行响应)、任务的背景(用于给它提供更多任务类型的上下文)以及用于激发更好推理能力的提示策略(例如ReAct)。LangChain中包含很多种不同类型的代理。
  2. 工具(Tools):工具是代理调用的函数。提供正确的工具和这些工具的描述,以完成这些任务。LangChain提供了一系列的工具,同时你也可以定义自己的工具。
  3. 工具包(Toolkits):工具包是一组用于完成特定目标的彼此相关的工具,每个工具包中包含多个工具。
  4. 代理执行器(AgentExecutor):代理执行器是代理的运行环境,它调用代理并执行代理选择的操作。执行器也负责处理多种复杂情况,包括处理代理选择了不存在的工具的情况、处理工具出错的情况、处理代理产生的无法解析成工具调用的输出的情况,以及在代理决策和工具调用进行观察和日志记录。

AgentExecutor这个类是作为链(Chain)而存在,最重要的步骤处理方法,_take_next_step方法。它用于在思考-行动-观察的循环中采取单步行动。先调用代理的计划,查找代理选择的工具,然后使用选定的工具执行该计划(此时把输入传给工具),从而获得观察结果,然后继续思考,直到输出是 AgentFinish 类型,循环才会结束。

更复杂的代理

结构化工具对话代理Structured Tool Chat

AgentExecutor引导模型经过推理调用工具时,仅仅能够生成两部分内容:一是工具的名称,二是输入工具的内容。而且,在每一轮中,代理只被允许使用一个工具,并且输入内容只能是一个简单的字符串。

但是随着推理能力的增强,代理提供了更高的稳定性和可行性。,LangChain 引入了“多操作”代理框架,允许代理计划执行多个操作。在此基础上,LangChain 推出了结构化工具对话代理,允许更复杂、多方面的交互。通过指定AgentType.STRUCTURED_CHAT_ZERO_SHOT_REACT_DESCRIPTION 这个代理类型,代理能够调用包含一系列复杂工具的“结构化工具箱”,组合调用其中的多个工具,完成批次相关的任务集合。例如,文件管理工具及,web浏览器工具及,接下来以web浏览器工具集PlayWright为例,实现一个结构化工具对话代理。

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()

自主询问搜索代理Self-Ask with Search

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使用玫瑰作为国花的国家的首都是哪里? ”这个问题不是一个简单的问题,它其实是一个多跳问题——在问题和最终答案之间,存在中间过程。多跳问题(Multi-hop question)是指为了得到最终答案,需要进行多步推理或多次查询。

为什么 Self-Ask with Search 代理适合解决多跳问题呢?有下面几个原因。

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

在上面的例子中,通过大模型的两次follow-up追问,搜索工具给出了两个中间答案,最后给出了问题的最终答案

计划与执行代理Plan and execute

Plan and execute 代理。

计划和执行代理通过首先计划要做什么,然后执行子任务来实现目标。

这种代理的独特之处在于,它的计划和执行不再是由同一个代理所完成,而是:

  • 计划由一个大语言模型代理(负责推理)完成。
  • 执行由另一个大语言模型代理(负责调用工具)完成。
from langchain.chat_models import ChatOpenAI
from langchain_experimental.plan_and_execute import PlanAndExecute, load_agent_executor, load_chat_planner
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 events"
    ),
    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 image.png 先分成了以下几步:找到玫瑰花在纽约的平均价格;用100除以这个可被购买的价格;向下取整结果;鉴于以上步骤回答问题;