1. 知识回顾
在 LangChain 的代理中,有以下几个关键组件:代理(Agent)、工具(Tools)、工具包(Toolkits)和 代理执行器(AgentExecutor)。代理用于决定下一步做什么;工具是代理调用的函数;工具包是一组用于完成特定目标的彼此相关的工具;而代理执行器则是代理的运行环境,它调用代理并执行代理选择的操作。
总的来说,代理就是一种用语言模型做出决策、调用工具来执行具体操作的系统。通过设定代理的性格、背景以及工具的描述,我们可以定制代理的行为,使其能够根据输入的文本做出理解和推理,从而实现自动化的任务处理。不同于链将一系列操作硬编码到代码中的做法,在代理中,语言模型被用作推理引擎来确定要采取哪些操作以及按什么顺序执行这些操作,这就是大模型应用开发的核心理念。下面这张图展示了 Agent 接到任务之后,自动进行推理,然后自主调用工具完成任务的过程:
在第13节课中,我们了解了 AgentExecutor 的运行机制——通过设计合适的 Prompt 驱动大模型不断进行 “思考 -> 行动 -> 观察行动结果 -> 再思考 -> 再行动 -> 再观察” 这一过程,直到模型判断问题已经解决,可以得到最终答案才跳出循环。而在这一过程中,模型可能会遇到自己无法解决的问题(比如一些时效性问题),这时模型就会根据任务的类型和需求,通过大模型的推理,来选择合适的工具处理这个任务。目前 LangChain 已经支持了很多类型的工具以简化开发者的工作:
2. LangChain 中的自定义工具
上图中展示的 LangChain 中集成的原生工具说到底只能解决预设好的问题,而现实场景中存在海量任务,很多时候仅靠框架 工具包 内的原生工具可能无法满足我们的需求,这时就需要我们根据自己的需求设计特定于任务的自定义工具。工具的自定义极大地拓展了 LangChain 的功能边界,也让开发者使用工具变得更加灵活。
在 LangChain 中开发者可以使用 Tool.from_function() 方法或者 子类化 BaseTool 类 来自定义工具。使用 Tool.from_function() 方法能够迅速利用简单的函数创造出工具(只需要使用 @tool 装饰器就可以将简单的函数变成自定义工具)。
而子类化 BaseTool 类方法可以让我们更精细地控制工具的行为,并定义自定义的实例变量或者传递回调。对于一个工具类,其应该包含以下组成部分:
- name(字符串),必需项,而且必须在一个代理所提供的工具集合中保持唯一性
- description(字符串),可选项但推荐使用,以便代理用其来判断工具的用途
- return_direct(布尔型,默认值为False)
- args_schema(Pydantic BaseModel),可选项但推荐使用,它可以用来提供更多信息(如少量示例)或者预期参数的校验。
3. 使用 LangChain 创建自定义天气查询工具
在第15课中,我们体验了文献检索工具 Arixiv 和 Gmail 邮件管理工具。下面我将尝试通过调用和风天气的 API 来设计一个实时天气查询的工具。
3.1 获取天气查询 API
首先我们需要去 和风天气 API 文档 界面,依次点击右上角 控制台→ 项目管理 → 创建项目,项目名称随便填,订阅方式选择免费订阅,并在 “订阅方案” 中选择 “API KEY”。创建完成后点击创建凭据,找到你的 API KEY 并复制保存,这里 API KEY 忘了保存也没事,你还可以在自己项目管理中查看自己的 API KEY:
获取完 API KEY 之后,先尝试创建一个天气查询的工具类。先在 14_工具 文件夹下创建一个 WeatherQuery.py 文件用于存放我们的工具类。
3.2 自定义天气查询工具的设计
这里我通过继承 BaseTool 类来定义自己的天气查询工具类,具体代码如下:
from langchain.agents import AgentType, initialize_agent
from langchain.chat_models import ChatOpenAI
from langchain.tools import BaseTool
import requests
import os
# 初始化模型和工具
llm = ChatOpenAI(
temperature=0.0,
model=os.environ.get("LLM_MODELEND"),
)
class WeatherQueryTool(BaseTool):
name = "天气查询工具"
description = "当你需要回答有关天气的问题时,你应该使用这个工具。输入应该是一个城市或地区的名"
def _run(self, location: str) -> dict:
api_key = os.getenv("weather_token")
try:
# 如果 location 不是坐标格式(例如 "116.41,39.92"),则调用 GeoAPI 获取 LocationID
if not ("," in location and location.replace(",", "").replace(".", "").isdigit()):
# 使用 GeoAPI 获取 LocationID
geo_url = f"https://geoapi.qweather.com/v2/city/lookup?location={location}&key={api_key}"
geo_response = requests.get(geo_url)
geo_data = geo_response.json()
if geo_data.get("code") != "200" or not geo_data.get("location"):
raise Exception(f"GeoAPI 返回错误码:{geo_data.get('code')} 或未找到位置")
location = geo_data["location"][0]["id"]
# 构建天气查询的 API 请求 URL
weather_url = f"https://devapi.qweather.com/v7/weather/now?location={location}&key={api_key}"
response = requests.get(weather_url)
data = response.json()
# 检查 API 响应码
if data.get("code") != "200":
raise Exception(f"Weather API 返回错误码:{data.get('code')}")
# 解析和组织天气信息
weather_info = {
"location": location,
"weather": data["now"]["text"],
"temperature": data["now"]["temp"] + "°C",
"wind_direction": data["now"]["windDir"],
"wind_speed": data["now"]["windSpeed"] + " km/h",
"humidity": data["now"]["humidity"] + "%",
"report_time": data["updateTime"]
}
return {"result": weather_info}
except Exception as exc:
return {"error": str(exc)}
tools = [WeatherQueryTool()]
# 初始化链
agent_chain = initialize_agent(
tools,
llm,
agent=AgentType.ZERO_SHOT_REACT_DESCRIPTION,
verbose=True,
)
# 运行链
res = agent_chain.run("帮我查询一下北京市海淀区的天气")
print("\n", res)
这里我定义了一个继承自 BaseTool 类的自定义工具类 WeatherQueryTool,这个类的作用是通过调用和风天气的 API 来解析 IP 并返回你想查询地区的实时天气状况。
在具体函数实现上,先利用 GeoAPI 获取 LocationID,当用户输入的 location 不是经纬度坐标格式时(如 116.41,39.92),则使用和风天气的 GeoAPI 将位置名转换为 LocationID,并通过 Weather API 获取目标位置的实时天气数据。最后,解析返回的 JSON 数据,并将其格式化为结构化字典:
工具类构建完成后将其塞进一个工具列表 Tools 并用这个工具列表初始化一个智能代理 agent_chain 用于执行天气查询任务,这里我们运行一下看看结果:
可以看到,代理首先根据我询问的地区解析出了对应的 IP,然后再调用实时天气查询 API 得到了我所询问地区的天气状况,最终输出的结果跟 中国气象局官网 查到的基本一致。
4. Web 部署
既然工具类能成功运行,那么不妨把我们自定义的工具用 Streamlit 部署一下。我的部署结果如下:
可以看到整体效果还不错~
完整部署代码这里就不贴出来了,大家有兴趣的话去我网盘取吧:
链接: pan.baidu.com/s/14z-15SC1… 提取码: pbdz
下载下来之后先设置一下环境变量:
export weather_token="你的和风天气 API KEY"
再运行如下命令:
streamlit run WeatherQuery_streamlit.py
点击进入本地部署网址即可看到 Web 页面。
5. 总结
这份代码只是一个demo,还不完善,比如还可以添加不同模型和插件的选择功能。而在链章节的课程中提到了“路由”的概念:模型可以揣摩用户意图并选择合适的链来执行任务,类似的概念在工具章节同样提到了:“模型会根据任务的类型和需求,通过大模型的推理,来选择合适的工具处理这个任务”。所以我们还可以尝试在这份 demo 中设置多个工具同时运行来测试模型的路由功能。