前言
官方文档:代理(Agents)
前面有说到“链”,那么在链的组装过程中,我们还可能遇到几个问题,比如:
- 过程中我需要使用其他功能而不是AI来作为链中的一环(例如数据库操作、计算器)怎么办
- 如果我的链是需要判断逻辑,依据当前情况来决定下一步的话怎么办?
看看示例
工具的加载
from langchain.agents import load_tools
from langchain.llms import OpenAI
llm = OpenAI(temperature=0)
tools = load_tools(["serpapi", "llm-math"], llm=llm)
这个示例代码里,是通过函数load_tools加载了一些langchain包里内置的工具类,其中llm-math这个工具类用到了AI,所以需要将llm参数传递进去。
然后进去这个load_tools函数里看看,发现其实是用名字映射到了各个工具。
_LLM_TOOLS: Dict[str, Callable[[BaseLanguageModel], BaseTool]] = {
"llm-math": _get_llm_math,
"open-meteo-api": _get_open_meteo_api,
}
def _get_llm_math(llm: BaseLanguageModel) -> BaseTool:
return Tool(
name="Calculator",
description="Useful for when you need to answer questions about math.",
func=LLMMathChain.from_llm(llm=llm).run,
coroutine=LLMMathChain.from_llm(llm=llm).arun,
)
到这里,就可以看出工具类的定义和使用方式了:自定义工具
- 名称(str),是必需的,并且必须在提供给代理的一组工具中是唯一的
- 描述(str),是可选的,但建议使用,因为代理用于确定工具使用
- return_direct(bool),默认为False
- args_schema(Pydantic BaseModel), 是可选的,但建议使用,可以用于提供更多信息或验证预期参数。
上面还有指定一个coroutine参数,使用的是工具类的异步处理方法,我这边没有试过所以先暂时略过。
代理的使用
代理也分为很多种类,这里使用最简单的一种:
ZERO_SHOT_REACT_DESCRIPTION 仅基于工具的描述来确定要使用的工具
文档:代理类型
- zero-shot-react-description
此代理使用ReAct框架,仅基于工具的描述来确定要使用的工具。可以提供任意数量的工具,需要为每个工具提供描述。- react-docstore
这个代理使用ReAct框架与文档存储进行交互。必须提供两个工具:一个Search工具和一个Lookup工具(它们必须被命名为这样)。Search工具应该搜索文档,而Lookup工具应该查找最近找到的文档中的一个术语。- self-ask-with-search
这个代理使用一个被命名为Intermediate Answer的工具。这个工具应该能够查找问题的事实性答案。- conversational-react-description
这个代理程序旨在用于对话环境中。提示设计旨在使代理程序有助于对话。 它使用ReAct框架来决定使用哪个工具,并使用内存来记忆先前的对话交互。
先用示例的内置计算器工具来实践
import include
from langchain.agents import initialize_agent, AgentType, load_tools
from langchain.chat_models import ChatOpenAI
llm = ChatOpenAI(
model="gpt-35-turbo",
model_kwargs={"deployment_id": "gpt-35-turbo"},
temperature=0,
)
# 加载默认工具
tools = load_tools(["llm-math"], llm=llm)
# 创建代理,传入工具、模型、代理类型,开启调试
agent = initialize_agent(tools, llm, agent=AgentType.ZERO_SHOT_REACT_DESCRIPTION, verbose=True)
# 提问
rs = agent.run("Assume you are 12 years old now, what's your age raised to the 0.43 power?")
print(rs)
> Entering new AgentExecutor chain...
I need to use a calculator to raise my age to the 0.43 power.
Action: Calculator
Action Input: 12 ^ 0.43
Observation: Answer: 2.911038409866027
Thought:I now know my age raised to the 0.43 power.
Final Answer: 2.911038409866027
> Finished chain.
2.911038409866027
Process finished with exit code 0
从结果来看,由于这里只定义了一个计算器工具,因此agent也只会使用这一个工具。
自定义工具并使用
为了尝试一下代理Agent的选择功能,这里自定义一个新工具,看是否会自动选择
import include
from langchain import LLMMathChain
from langchain.agents import initialize_agent, AgentType
from langchain.chat_models import ChatOpenAI
from langchain.tools import Tool
llm = ChatOpenAI(
model="gpt-35-turbo",
model_kwargs={"deployment_id": "gpt-35-turbo"},
temperature=0,
)
# 简单定义函数作为一个工具
def personal_info(name: str):
info_list = {
"Artorias": {
"name": "Artorias",
"age": 18,
"sex": "Male",
},
"Furina": {
"name": "Furina",
"age": 16,
"sex": "Female",
},
}
if name not in info_list:
return None
return info_list[name]
# 自定义工具字典
tools = (
# 这个就是上面的llm-math工具
Tool(
name="Calculator",
description="Useful for when you need to answer questions about math.",
func=LLMMathChain.from_llm(llm=llm).run,
coroutine=LLMMathChain.from_llm(llm=llm).arun,
),
# 自定义的信息查询工具,声明要接收用户名字,并会给出用户信息
Tool(
name="Personal Assistant",
description="Useful for when you need to answer questions about somebody, input person name then you will get name and age info.",
func=personal_info,
)
)
agent = initialize_agent(tools, llm, agent=AgentType.ZERO_SHOT_REACT_DESCRIPTION, verbose=True)
# 提问,询问Furina用户的年龄的0.43次方
rs = agent.run("What's the person Furina's age raised to the 0.43 power?")
print(rs)
可以看到代理会先思考说,首先需要获取Furina的年龄,因此要先使用能够查询用户信息的工具。
获取了用户信息后,代理发现已经获得了年龄信息,这时候需要使用计算器进行计算,于是调用了Calculator,得出最终结果。
> Entering new AgentExecutor chain...
I need to use the Personal Assistant tool to get Furina's age first.
Action: Personal Assistant
Action Input: Furina
Observation: {'name': 'Furina', 'age': 16, 'sex': 'Female'}
Thought:Now that I have Furina's age, I can use the calculator to raise it to the 0.43 power.
Action: Calculator
Action Input: 16^0.43
Observation: Answer: 3.2943640690702924
Thought:I now know the final answer.
Final Answer: 3.2943640690702924
> Finished chain.
3.2943640690702924
Process finished with exit code 0
到这里可以基本反映出来,非AI功能操作和AI功能操作可以通过工具+代理抉择的方式进行组装,从而形成一个以LLM为驱动核心的模块化处理应用,其效果的好坏完全取决于提示工程,也就是Prompts的好坏。
bad case
例如,如果我在工具描述里描述得模糊一些,不说明接收的参数,我们看看会发生什么
# 这是上面示例的自定义工具,现在我们去掉后面关于输入的描述,看看会发生什么
Tool(
name="Personal Assistant",
description="Useful for when you need to answer questions about somebody, input person name then you will get name and age info.",
func=personal_info,
)
# 改成这样
Tool(
name="Personal Assistant",
description="Useful for when you need to answer questions about somebody.",
func=personal_info,
)
最终发生了报错,这是因为AI不知道工具输入的情况下,它直接对工具进行了提问“What is Furina’s age?”,而我们简易的自定义工具无法处理这种提问,因此没有给出回复,因此进一步的,代理不知道计算器该用什么数值进行计算。
> Entering new AgentExecutor chain...
I need to use the Personal Assistant tool to find out Furina's age.
Action: Personal Assistant
Action Input: "What is Furina's age?"
Observation: None
Thought:I need to use the Calculator tool to raise Furina's age to the 0.43 power.
Action: Calculator
Action Input: Furina's age ^ 0.43
Traceback (most recent call last):
……………………………………
……………………………………
各种报错
……………………………………
……………………………………
raise ValueError(f"unknown format from LLM: {llm_output}")
ValueError: unknown format from LLM: I'm sorry, but the question is incomplete. Please provide the age of Furina or any other necessary information to form a complete mathematical expression.
Process finished with exit code 1