之前我们说了,在LLM执行中,大模型加上Zero shot的组合能力有多强。这是大模型利用自己的推理能力得到结果的实现方式
但是大模型有时候会因为推理能力而胡说八道。
就比如利用一个不存在的书作为一个prompt,就可以让大模型沿着这个思路一直胡编乱造。
如果不希望大模型进行胡编乱造,而是通过一些外部的资料查询来进行返回。
就需要通过一些方式来集成外部工具从而使用。
代理就可以理解为一套接口,可以接触并且使用一套工具,根据用户的输入,从而决定调用哪些工具。
LangChain中使用代理,需要三个元素
大模型,外部工具,和控制流程的代理
这三个相辅相成。
而在LangChain,将这三个集成的就是ReAct框架。
那么我们看下这个ReAct的使用,以及如何和大模型进行的集成。
ReAct框架全名叫做Reasoning-Acting框架,其中Reasoning包括了对当前环境和状态的观察,并思考生成推理轨迹,Acting则告诉大模型该如何进行下一步操作。
这里我们给出一个示例,如果我希望根据当天的鲜花进货价格来对我销售的鲜花进行定价,那么该如何进行思考行动路径?
1. 先搜索当天进货价格
2. 然后观察价格
3. 思考定价
4. 回复定价
上面的流程中,先是行动,然后是观察,思考,最后给出行动,即报价。
利用这个Reasoning-Acting的框架,可以赋予大模型自主性,从一个自闭的,内部对话的bot,变为一个会使用工具的现代人。
那么LangChain如何使用ReAct ?这就回来我们一开始说的Agent类了。
这里我们利用LangChain 的Agent类,来实现我们上面说的鲜花价格推断链路。我们希望得到比当前市场价格高15%的新价格。
这里我们先获取一个SEARCH API KEY
这里我们使用Google的search工具,https://serpapi.com/
然后安装 Serp API的包
pip install google-search-results
并设置相关环境变量
import os
os.environ[“OPENAI_API_KEY”] = ‘Your OpenAI API Key’
os.environ[“SERPAPI_API_KEY”] = ‘Your SerpAPI API Key’
# 加载所需的库
from langchain.agents import load_tools
from langchain.agents import initialize_agent
from langchain.agents import AgentType
from langchain.llms import OpenAI
其实使用相对简单
# 初始化大模型
llm = OpenAI(temperature=0)
# 设置工具
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%卖出,应该如何定价?”)
这里我们利用verbose为True,声明了打印思考轨迹。
得到的输出如下
上面是思考的整体链路流程。
可以划分为
执行
然后是观察
之后是思考
得到答案输出。
那么上面我们说了下LangChain中的重要组件,ReAct框架,以及如何进行集成为一个Agent。其会进行观察环境,然后思考,最后采取行动。
从而增强了大模型的思考能力。
那么我们继续要往下走的话,就需要看看Agent是如何真正进行自我思考的。
不过在此之前,我们之前说过链在LangChain中的地位,我们可以对比下Chain和Agent之间的区别,Chain是在LangChain之中,以硬编码的方式,将一系列操作串联起来。而Agent,则更加偏向于自己决定采取什么操作来执行对应的任务。
这也更加贴合大模型应用的核心概念,就是利用AI来自主决定程序逻辑
而在上面的Agent代码,我们只是很简单的传入了tools和llm,就获得了一个Agent组件
而在Agent内部,则是可以分为多个内部组件
基本分为了:
代理,这个类决定了下一步操作,由大语言模型实现,也就是实际的大脑
工具,Tools,供Agent在使用过程中调用的工具,LangChain提供了一系列的工具,也可以定义自己的工具
工具包,Toolkits, 工具包,一组用于完成特定目标,彼此相关的工具,每个工具包含多个工具,比如LangChain的Office 365,支持读取邮件到发送邮件等一系列工具
代理执行器,LangChain 的核心工具,主要负责调用代理,并执行代理发出的指令。也是我们将要实现的核心组件。
总体来看,代理就是由LangChain实现的代理执行器驱动代理,代理进行思考后,利用工具,从而根据文本来做出理解和推理,从而自动化的进行任务处理。
那么我们就着重看下AgentExcutor的运行机制,这一部分我们是通过AgentExecutor的内部代码debug来看。
其核心在,agent.py文件中AgentExecutor类中方法_take_next_step
首先通过_take_next_step方法,我们进一步debug,深入self.agent.plan方法,来到第一步Plan,这个Plan方法位于Agent类内,其内部调用了llm_chain,从而获取大模型的返回结果。
在调用的时候,我们可以看下其给出的提示具体内容。
0: StringPromptValue(text=’Answer the following questions as best you can. You have access to the following tools:
Search: A search engine. Useful for when you need to answer questions about current events. Input should be a search query. Calculator: Useful for when you need to answer questions about math. Use the following format: Question: the input question you must answer Thought: you should always think about what to do Action: the action to take, should be one of [Search, Calculator] Action Input: the input to the action Observation: the result of the action… (this Thought/Action/Action Input/Observation can repeat N times) Thought: I now know the final answer Final Answer: the final answer to the original input question Begin! Question: 目前市场上玫瑰花的平均价格是多少?如果我在此基础上加价15%卖出,应该如何定价? Thought: |
这个提示中
首先告诉模型拥有哪些工具You have access to the following tools:
分别是Search和Calculator 搜索和计算器
之后是告诉模型我们的交互框架
Use the following format:
Question: the input question you must answer (问题) Thought: you should always think about what to do (思考) Action: the action to take, should be one of [Search, Calculator] (行动) Action Input: the input to the action (行动输入) Observation: the result of the actionn… (this Thought/Action/Action (返回结果) Input/Observation can repeat N times) Thought: I now know the final answer (再思考) Final Answer: the final answer to the original input question (最终答案) |
之后是一句Begin,说明开始
最后给出用户的输入,也就是问题
告诉了Agent了输入框架,Agent后面的llm大模型就会不断的按照框架思考,行动,直到得到答案。
那么在这个提示之后,从模型得到的答案为:
0: LLMResult(generations=[[Generation(text=’ I need to find the current market price of roses and then calculate the new price with a 15% markup.
Action: Search Action Input: “Average price of roses”‘, generation_info={‘finish_reason’: ‘stop’, ‘logprobs’: None})]], llm_output={‘token_usage’: {‘completion_tokens’: 36, ‘total_tokens’: 294, ‘prompt_tokens’: 258}, ‘model_name’: ‘text-davinci-003’}, run=None) |
发现模型理解了用户意图,同时发现自己处理不了,于是决定使用工具箱中的搜索,调用搜索工具
这样第一次Agent和Agent Executor的交互就结束了,AgentExector在得到了输出之后,利用OutputParse得到了结果。这时候会调用Search工具
调用完成之后,会得到一个对当前工具调用的Observation,让我们将Observation再交给大模型
之后就是下一轮对Agent的调用
再次进入了Plan环节,对于llm的调用,输入为
0: StringPromptValue(text=’Answer the following questions as best you can. You have access to the following tools:
Search: A search engine. Useful for when you need to answer questions about current events. Input should be a search query. Calculator: Useful for when you need to answer questions about math. Use the following format: Question: the input question you must answer Thought: you should always think about what to do Action: the action to take, should be one of [Search, Calculator] Action Input: the input to the action Observation: the result of the action … (this Thought/Action/Action Input/Observation can repeat N times) Thought: I now know the final answer Final Answer: the final answer to the original input question Begin! Question: 目前市场上玫瑰花的平均价格是多少?如果我在此基础上加价15%卖出,应该如何定价? Thought: I need to find the current market price of roses and then calculate the new price with a 15% markup. Action: Search Action Input: “Average price of roses” Observation: The average price for a dozen roses in the U.S. is $80.16. The state where a dozen roses cost the most is Hawaii at $108.33. That’s 35% more expensive than the national average. A dozen roses are most affordable in Pennsylvania, costing $66.15 on average. Thought: |
可以看到,在第二轮的input过程中,我们首先增加了上次大模型要求的Action,以及Action的结果。
解析来,大模型需要进行进一步的推理。因此给出的输出结果为,
Agent:Action(tool=’Calculator’, tool_input=’80.16 * 1.15′, log=’ I need to calculate the new price with a 15% markup.
Action: Calculator
Action Input: 80.16 * 1.15′)
上面是大模型要求使用另一个tools,计算器,计算得到根据当前玫瑰进货价格加15%之后的售价应该为多少。
这个工具是一个内置工具,本质上是还是调用LLM进行数字计算
0: StringPromptValue(text=’Translate a math problem into a expression that can be executed using Python’s numexpr library. Use the output of running this code to answer the question.
最后得到了答案
observation’Answer: 92.18399999999998′
然后我们再将上面新的环境传递给LLM
0: StringPromptValue(text=’Answer the following questions as best you can. You have access to the following tools:
Search: A search engine. Useful for when you need to answer questions about current events. Input should be a search query. Calculator: Useful for when you need to answer questions about math. Use the following format: Question: the input question you must answer Thought: you should always think about what to do Action: the action to take, should be one of [Search, Calculator] Action Input: the input to the action Observation: the result of the action … (this Thought/Action/Action Input/Observation can repeat N times) Thought: I now know the final answer Final Answer: the final answer to the original input question Begin! Question: 目前市场上玫瑰花的平均价格是多少?如果我在此基础上加价15%卖出,应该如何定价? Thought: I need to find the current market price of roses and then calculate the new price with a 15% markup. Action: Search Action Input: “Average price of roses” Observation: The average price for a dozen roses in the U.S. is $80.16. The state where a dozen roses cost the most is Hawaii at $108.33. That’s 35% more expensive than the national average. A dozen roses are most affordable in Pennsylvania, costing $66.15 on average. Thought: I need to calculate the new price with a 15% markup. Action: Calculator Action Input: 80.16 * 1.15nObservation: Answer: 92.18399999999998nThought:’) |
最后模型得到了这个环境,进行观察思考之后
得出了得到最终答案的回答
这个回答会被LangChain理解为AgentFinish实例,判断任务已经完成了,循环跳出。
因此在命令行输出, Thought:I now know the final answer。
得到答案为 92.18美元
那么我们简单总结一下,上面我们深入到了AgentExecutor的内部,深挖其内部机制,了解了AgentExecutor如何将Agent和Tools集成的。
在说完了AgentExecutor之后,我们看下其他类型的预置代理。
Structured Tool Chat 结构化工具对话代理
Self-Ask with Search 自主询问搜索
Plan and execute 计划和执行代理
首先是第一类,结构化工具对话代理
在LangChain中,存在着多操作代理框架,可以让代理计划执行多个操作。
通过制定AgentType.STRUCTURED_CHAT_ZERO_SHOT_REACT_DESCRIPTION 这个代理类型,可以调用一系列复杂工具的结构化工具箱,来以一种组合的方式达成用户的需求。
就比如,有一个文件管理工具集,支持所有文件系统操作,写入 搜索 移动 复制 列目录 查找。
再比如我们提供一个Web浏览器工具集,可以供代理去模拟真正的人操作浏览器。
这里我们就以这个Web浏览器工具集,来让Agent去自己搜索得到答案,其中涉及到的工具集由 PlayWright工具包负责
这里先说下PlayWright 工具包,其可以模仿多种浏览器去操作,比如Chrome,Firefox等,并且可以在这些浏览器中,模拟真正的用户去操作网页
其使用很简单,需要先进行pip install
pip install 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: “) # 关闭浏览器 browser.close() |
我们利用代码的方式,获取到了指定URL的title。
那么我们就利用这个作为工具集,来实现结构化工具对话。
如果希望在LangChain中使用Playwright的话,可以使用STRUCTURED_CHAT_ZERO_SHOT_REACT_DESCRIPTION和PlayWrightBrowserToolkit
PlayWrightBrowserToolkit工具集继承自BaseToolkit类
其内部封装了一系列工具,包括
这些工具提供个Agent,让Agent 进行调用
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() llm = ChatOpenAI(temperature=0.5) agent_chain = initialize_agent( tools, llm, agent=AgentType.STRUCTURED_CHAT_ZERO_SHOT_REACT_DESCRIPTION, verbose=True, ) |
上面我们获得了Toolkit,并且创建了对应的agent
之后我们尝试进行输入
response = await agent_chain.arun(“What are the headers on python.langchain.com?”)
让agent帮我搜索对应URL网站的headers
那么在我们输入这个语句之后,LangChain将进入思考。
第一轮思考,大模型给出的Action为
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.
Action:{“action”: “navigate_browser”, “action_input”: {“url”: “<a href=”https://python.langchain.com”>https://python.langchain.com</a>”}}
之后得到的观察为
Observation: Navigating to https://python.langchain.com returned status code 200
上面大模型考虑使用了
navigate_browser工具
然后是第二轮思考
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.
Action: {“action”: “get_elements”, “action_input”: {“selector”: “h1, h2, h3, h4, h5, h6”, “attributes”: [“innerText”]}}
得到的观察为
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”}]
通过使用get_elements工具,获得的了多个标题的文本结果
最终在第三轮思考中给出了action
Final answer,说明得到了答案。
上面代码中,Agent使用了工具包中的两种不同工具,完成了任务。
其次是self-ask with search 代理
也就是说,在一次搜索中,代理可能拿不到想要的答案,这种时候,我们赋予大模型追问的能力,通过多轮搜索,得到最终的答案。
这里我们直接上代码
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_searh.run(“哪个国家使用玫瑰花作为国花,请你告诉我他的首都“)
上面我提出的问题,是一个多级练跳的问题,需要进行多轮的思考,以及和搜索多轮的交互。
就比如,agent需要先问搜索工具,哪个国家使用玫瑰花作为国花,其次才是对应的国家的首都是哪里
那么我们看下agent是如何运行的
进行了多轮搜索之后,最终得到了伦敦作为答案返回
最后是Plan and execute 代理,这是一种比较新的代理方式
负责将计划拆分为多个子任务,最后再执行者多个子任务
这样的话,制定子任务的大模型和执行子任务的大模型就可以不是同一个大模型
如果是希望使用它,需要先安装langchain_experimental这个包
pip install -U langchain langchain_experimental
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美元能买几束玫瑰?”) |
其在输出中分别进行了多轮任务的拆解,
分别拆分为了获取价格,除以100,取整,返回
最终多轮之后,得到了Final Answer,并返回给了用户。
上面就是关于PlanAndExecuter的基本使用。
那么到此,Agent我们就讲完了
在最后,我们给出一个包含现阶段所有Agent类型的图