LangChain的函数,工具和代理(五):Tools Routing

关于langchain的函数、工具、代理系列的博客我之前已经写了四篇,还没有看过的朋友请先看一下,这样便于对后续博客内容的理解:

LangChain的函数,工具和代理(一):OpenAI的函数调用

LangChain的函数,工具和代理(二):LangChain的表达式语言(LCEL)

LangChain的函数,工具和代理(三):LangChain中轻松实现OpenAI函数调用 

LangChain的函数,工具和代理(四):使用 OpenAI 函数进行标记(Tagging) & 提取(Extraction)

 今天我们来学习Langchain中非常有用的工具“tools”,以及用来选择tools的方法“routing”,在之前的几篇博客中我们介绍了如何在langchain中实现openai的函数调用的功能,这里需要强调的是我们之前介绍的langchain的函数调用并非真正意义上的函数调用,而是让llm根据用户信息的上下文来返回被调用函数的参数,真正的函数调用还是需要手动编写函数调用的代码,那么今天我们来介绍一种让langchain实现真正意义上函数调用功能.

一、Tools

在langchain中,tools是代理(agent)用来与外界交互的接口。这里所谓的“与外界交换”是指让llm可以访问外部世界的功能,如执行互联网搜索,调用外部api等的能力。之前我们介绍了如何让llm来调用外部函数,也就是我们需要先定义好外部函数,然后生成一个openai能理解的该函数的描述对象,最后通过解析llm的返回值来获取调用参数,整个过程比较繁琐。但是有了langchain的tools以后,就可以大大简化整个函数调用的流程。

 接下来在正式“抠腚”😀之前,先让我们做一些初始化的工作,如设置opai的api_key,这里我们需要说明一下,在我们项目的文件夹里会存放一个 .env的配置文件,我们将api_key放置在该文件中,我们在程序中会使用dotenv包来读取api_key,这样可以避免将api_key直接暴露在程序中:

#pip install -U python-dotenvimport os
import openaifrom dotenv import load_dotenv, find_dotenv
_ = load_dotenv(find_dotenv()) # read local .env file
openai.api_key = os.environ['OPENAI_API_KEY']

下面我们来定义一个函数用来模拟查询天气的函数search,它的逻辑简单只返回一个温度值:

#导入langchain的tool
from langchain.agents import tool#添加tool装饰器
@tool
def search(query: str) -> str:"""Search for weather online"""return "42f"print(f"search.name:{search.name}")
print(f"search.description:{search.description}")
print(f"search.args:{search.args}")

这里我们在定义search函数时添加了一个@tool的装饰器,它表示search函数为一个tool,当search函数成为一个tool以后,它就具备了"run"的能力:

#执行函数调用
search.run("sf")

 下面我们需要对search函数的输入参数做一个类型限制,所以我们需要创建一个pydantic类:

from pydantic import BaseModel, Field
class SearchInput(BaseModel):query: str = Field(description="Thing to search for")@tool(args_schema=SearchInput)
def search(query: str) -> str:"""Search for the weather online."""return "42f"print(f"search.args:{search.args}")

 这里我们给装饰器@tool附加了args_schema参数,它的值为SearchInput,这样就可以限制search函数的输入参数的类型了。

接下来我们需要定义一个真实的调用外部api获取天气温度的函数:

import requests
from pydantic import BaseModel, Field
import datetime# Define the input schema
class OpenMeteoInput(BaseModel):latitude: float = Field(..., description="Latitude of the location to fetch weather data for")longitude: float = Field(..., description="Longitude of the location to fetch weather data for")@tool(args_schema=OpenMeteoInput)
def get_current_temperature(latitude: float, longitude: float) -> str:"""Fetch current temperature for given coordinates."""BASE_URL = "https://api.open-meteo.com/v1/forecast"# Parameters for the requestparams = {'latitude': latitude,'longitude': longitude,'hourly': 'temperature_2m','forecast_days': 1,}# Make the requestresponse = requests.get(BASE_URL, params=params)if response.status_code == 200:results = response.json()else:raise Exception(f"API Request failed with status code: {response.status_code}")current_utc_time = datetime.datetime.utcnow()time_list = [datetime.datetime.fromisoformat(time_str.replace('Z', '+00:00')) for time_str in results['hourly']['time']]temperature_list = results['hourly']['temperature_2m']closest_time_index = min(range(len(time_list)), key=lambda i: abs(time_list[i] - current_utc_time))current_temperature = temperature_list[closest_time_index]return f'The current temperature is {current_temperature}°C'

这里我们定义了一个调用外部api(https://api.open-meteo.com/v1/forecast)来获取指定经纬度坐标d地区的天气温度的函数get_current_temperature,该函数的输入参数时经纬度坐标,它们被pydantic类限制了数据类型。下面我们查看一下该函数的输入参数:

print(get_current_temperature.args)

 下面我们实际调用一下该函数:

get_current_temperature.run({"latitude": 13, "longitude": 14})

 从上面的返回结果中我们看到外部api返回了温度值为18.2度,这里需要说明的是,我们在定义该函数时输入参数为pydantic 类,因此在执行函数调用的时候,输入参数必须是字典的格式,否则会报错。下面我们再定义一个维基百科的查询函数:

#pip install wikipediaimport wikipedia
@tool
def search_wikipedia(query: str) -> str:"""Run Wikipedia search and get page summaries."""page_titles = wikipedia.search(query)summaries = []for page_title in page_titles[: 3]:try:wiki_page =  wikipedia.page(title=page_title, auto_suggest=False)summaries.append(f"Page: {page_title}\nSummary: {wiki_page.summary}")except (self.wiki_client.exceptions.PageError,self.wiki_client.exceptions.DisambiguationError,):passif not summaries:return "No good Wikipedia Search Result was found"return "\n\n".join(summaries)

这里我们定义了一个维基百科的查询函数search_wikipedia,接下来我们可以直接调用该函数

search_wikipedia.run({"query": "langchain"})

这里我们看到 search_wikipedia函数通过维基百科查询到了关于langchain的相关信息。

二、Routing

前面我们已经有了两个实用的外部函数“get_current_temperature”和“search_wikipedia”,它们可以根据要求来返回天气温度和维基百科的相关信息,接下来我们要让llm在和用户对话的过程中自主判该调用哪一个函数,不过由于llm只能理解函数的描述信息,因此我们需要将这两个函数转换成openai能理解的函数描述变量,我们可以使用langchain的format_tool_to_openai_function方法将函数转换成描述变量比如像这样:

from langchain.tools.render import format_tool_to_openai_functionformat_tool_to_openai_function(get_current_temperature)

format_tool_to_openai_function(search_wikipedia)

 接下来我们来定义一个可以查询天气温度和维基百科的chain:

from langchain.chat_models import ChatOpenAI
from langchain.prompts import ChatPromptTemplate#创建函数描述变量
functions = [format_tool_to_openai_function(f) for f in [search_wikipedia, get_current_temperature]
]#定义llm
model = ChatOpenAI(temperature=0).bind(functions=functions)#创建prompt
prompt = ChatPromptTemplate.from_messages([("system", "You are helpful but sassy assistant"),("user", "{input}"),
])#定义chain
chain = prompt | model#调用chain
chain.invoke({"input": "上海现在的天气怎么样?"})

 这里我们看到llm并没有实际去调用函数,而是返回了函数调用的参数,并且llm通过上下文准确识别出需要调用哪个函数,并给出了函数的参数即经纬度坐标。我们再测试一下维基百科查询:

chain.invoke({"input": "langchain 是什么?"})

 这里我们看到当llm需要调用函数时返回的消息AIMessage中content都是空的,这个content表示llm返回给用户的文本内容,这就意味着当llm觉得需要调用外部函数时llm不会回答用户任何文本信息,因此返回消息中content都是空的。下面我们给llm打个招呼,看看会返回什么信息:

chain.invoke({"input": "你好"})

这里我们看到当我们和llm打招呼时,llm返回的AIMessage消息中只包含了content内容,除此之外没有其他任何的内容,这说明此时llm意识到不需要调用任何外部函数。 接下来我们给chain加上一个解析器组件:

from langchain.agents.output_parsers import OpenAIFunctionsAgentOutputParserchain = prompt | model | OpenAIFunctionsAgentOutputParser()result = chain.invoke({"input": "上海现在的天气怎么样?"})
result

当我们给chain添加了一个解析器组件OpenAIFunctionsAgentOutputParser以后,llm返回了一个AgentActionMessageLog结构的消息体,通过该消息体我们可以轻松获取被调用的函数名和参数:

result.tool

result.tool_input

有了该消息体,我们还可以轻松调用外部函数:

get_current_temperature(result.tool_input)

下面我们测试一下和llm打招呼:

result = chain.invoke({"input": "你好"})
result

这里我们看到当我们和llm打招呼时候,llm返回的并不是之前的 AgentActionMessageLog结构的消息体,而是AgentFinish结构的消息体,这说明当llm需要或者不需要调用外部函数时它们返回的消息体类型是不一样的。

type(result)

当不需要调用函数时,我们可以使用return_values方法来获取llm返回的文本消息:

result.return_values

前面我们介绍的让llm调用外部函数的方法和之前博客中介绍的类似,都是让llm返回被调用函数的参数,并没有真正实现函数调用,要实现真正的函数调用还必须要手工去执行该函数,下面我们来介绍真正的让llm自动化调用函数的功能,不过我们先要创建一个route函数,我们要借助这个rute函数来让llm实现真正的函数调用:

from langchain.schema.agent import AgentFinishdef route(result):if isinstance(result, AgentFinish):return result.return_values['output']else:tools = {"search_wikipedia": search_wikipedia, "get_current_temperature": get_current_temperature,}return tools[result.tool].run(result.tool_input)

这里需要说明的是route函数通过识别llm返回消息的类型,来判断是否需要调用函数,如何需要调用函数,则执行之前介绍的函数的run功能来实现真正的函数调用,接下来我们需要给chain添加一个route组件,让它可以实现真正的函数调用:

chain = prompt | model | OpenAIFunctionsAgentOutputParser() | routeresult = chain.invoke({"input": "上海的天气现在怎么样?"})
result

 这里我们看到当我们向llm询问上海的天气时候,llm调用了天气查询函数,并通过该函数返回了一个真实的温度值。

result = chain.invoke({"input": "langchain是什么?"})
result

 

 从上面的结果中我们看到,当我们询问“langchain是什么”的时候,llm调用了维基百科搜索函数,并将搜索结果返回给了用户。下面我们测试一下不需要调用函数的场景:

result = chain.invoke({"input": "请问老鼠生病了可以吃老鼠药吗?"})
result

 这里我们看到,当我们询问llm“老鼠生病了可以吃老鼠药吗”这个问题时,llm给出的结果是它自己判断的结果,并没有调用我们定义好的外部函数。

三、OpenAPI Specification

OpenAPI 规范 (OAS) 定义了一个与语言无关的标准 HTTP API 接口,允许人类和计算机发现和理解服务的功能,而无需访问源代码、文档或通过网络流量检查。下面我们再介绍一下langchain中如何将符合OpenAPI规范文本转换成openai的函数描述变量。下面我们有一个符合openAPI规范的函数描述文本,我们要将它转换成openai的函数描述变量:

text = """
{"openapi": "3.0.0","info": {"version": "1.0.0","title": "Swagger Petstore","license": {"name": "MIT"}},"servers": [{"url": "http://petstore.swagger.io/v1"}],"paths": {"/pets": {"get": {"summary": "List all pets","operationId": "listPets","tags": ["pets"],"parameters": [{"name": "limit","in": "query","description": "How many items to return at one time (max 100)","required": false,"schema": {"type": "integer","maximum": 100,"format": "int32"}}],"responses": {"200": {"description": "A paged array of pets","headers": {"x-next": {"description": "A link to the next page of responses","schema": {"type": "string"}}},"content": {"application/json": {"schema": {"$ref": "#/components/schemas/Pets"}}}},"default": {"description": "unexpected error","content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}}},"post": {"summary": "Create a pet","operationId": "createPets","tags": ["pets"],"responses": {"201": {"description": "Null response"},"default": {"description": "unexpected error","content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}}}},"/pets/{petId}": {"get": {"summary": "Info for a specific pet","operationId": "showPetById","tags": ["pets"],"parameters": [{"name": "petId","in": "path","required": true,"description": "The id of the pet to retrieve","schema": {"type": "string"}}],"responses": {"200": {"description": "Expected response to a valid request","content": {"application/json": {"schema": {"$ref": "#/components/schemas/Pet"}}}},"default": {"description": "unexpected error","content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}}}}},"components": {"schemas": {"Pet": {"type": "object","required": ["id","name"],"properties": {"id": {"type": "integer","format": "int64"},"name": {"type": "string"},"tag": {"type": "string"}}},"Pets": {"type": "array","maxItems": 100,"items": {"$ref": "#/components/schemas/Pet"}},"Error": {"type": "object","required": ["code","message"],"properties": {"code": {"type": "integer","format": "int32"},"message": {"type": "string"}}}}}
}
"""

该变量text 包含了三个函数的描述内容,接下来我们要将它转换成openai的函数描述变量:

from langchain.chains.openai_functions.openapi import openapi_spec_to_openai_fn
from langchain.utilities.openapi import OpenAPISpecspec = OpenAPISpec.from_text(text)pet_openai_functions, pet_callables = openapi_spec_to_openai_fn(spec)
pet_openai_functions

这里我们使用了openapi_spec_to_openai_fn方法轻松的将openapi的变换成了openai的函数描述变量其中包含了3个函数 listPets,createPets,showPetById,下面我们还实际使用一下该函数描述变量:

from langchain.chat_models import ChatOpenAImodel = ChatOpenAI(temperature=0).bind(functions=pet_openai_functions)model.invoke("what are three pets names")

 

model.invoke("tell me about pet with id 42")

从上面的结果中我们看到llm能根据问题返回调用函数的参数,这说明符合openapi规范的文本也可以被正确转换成openai能识别的函数描述变量。

四、总结

今天我们学习了langchain的tools,routing组件的原理,并借助tool和routing让llm实现了真正意思上的函数调用,最后我们还介绍了openapi格式的函数说明文本如何通过langchain来转换成openai的函数描述变量,希望今天的内容对大家有所帮助。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/201992.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

2023最全的Web自动化测试介绍(建议收藏)

做测试的同学们都了解,做Web自动化,我们主要用Selenium或者是QTP。 有的人可能就会说,我没这个Java基础,没有Selenium基础,能行吗?测试虽然属于计算机行业,但其实并不需要太深入的编程知识&…

C++模板初阶

文章目录 泛型编程函数模板格式模板调用的是同一个函数吗?模板的实现原理T不明确模板实例化的函数和普通函数 类模板类模板写法类模板用法 注意事项 泛型编程 假如我们要写一个两数交换的函数,按我们之前学的知识,我们会这样。 void Swap(i…

微信小程序基础bug

1.苹果11手机小程序请求数据不显示 设置-》隐私-》分析与改进-》开启 ”与开发者共享“ 2.<navigator>组件回退delta不成功 tabBar 页面是不能实现后退的效果的. 因为, 当我们跳转到 tabBar 页面&#xff0c;会关闭其他所有非tabBar 页面,所以当处于 tabBar 页面时, 无…

SAP ABAP 通过右键菜单完成Tree Control 节点的增删改功能

通过右键菜单完成Tree Control 节点的增删改功能 Tree 节点的增删改是很重要的功能&#xff0c;包括&#xff1a;增加本级节点&#xff0c;增加子节点&#xff0c;修改节点&#xff0c;删 除节点。完成后效果如下&#xff1a; 选择根节点&#xff0c;单击右键&#xff0c;弹…

peertalk Usbmux 资料收集与整理

Usbmux - The iPhone Wiki Usbmux During normal operations, iTunes communicates with the iPhone using something called “usbmux” – this is a system for multiplexing several “connections” over one USB pipe. Conceptually, it provides a TCP-like system –…

高压功率放大器产品参数及优势有哪些

高压功率放大器是一种关键性能器件&#xff0c;常用于不同领域的应用&#xff0c;包括声音放大、通信系统、电力传输等。以下是关于高压功率放大器产品参数和优势的详细介绍。 一、高压功率放大器的产品参数 输入/输出电压范围&#xff1a;高压功率放大器通常能够承受较高的输入…

每日汇评:由于美国ADP就业数据疲弱,黄金的反弹可能会延续

在美元全面回落的背景下&#xff0c;金价在2020美元附近跃跃欲试。&#xff1b; 黄金价格的上涨似乎受到美债正收益率的限制&#xff1b; 黄金价格等待美国ADP就业数据获得新的提振&#xff0c;因为技术支撑仍然存在&#xff1b; 周三早盘&#xff0c;金价在2020美元附近小幅反…

完美解决:wget命令下载时遇到“错误 308:Permanent Redirect。”

目录 1 问题 2 解决方法 1 问题 使用wget命令下载时候遇到&#xff1a; --2023-12-02 20:36:08-- http://mirrors.jenkins.io/war-stable/latest/jenkins.war 正在解析主机 mirrors.jenkins.io (mirrors.jenkins.io)... 20.7.178.24, 2603:1030:408:5::15a 正在连接 mirror…

电子版简历模板精选5篇

电子版简历模板模板下载&#xff08;可在线编辑制作&#xff09;&#xff1a;做好简历&#xff0c;来幻主简历。 电子版简历1&#xff1a; 求职意向 求职类型&#xff1a;全职 意向岗位&#xff1a;ERP咨询顾问 意向城市&#xff1a;北京市 薪资要求&#xff1a;…

波长和陡度 现货黄金强弱分析中yyds

按照强势的那一方做交易&#xff0c;不和弱势的一方为伍&#xff0c;这是我们做现货黄金投资的哲学&#xff0c;顺势交易也是建立在这样的基础之上的。要判断现货黄金是强势还是弱势&#xff0c;除了借助技术指标以外&#xff0c;通过看走势本身也可以实现&#xff0c;下面我们…

【Docker】从零开始:13.Docker安装tomcat

Docker】从零开始&#xff1a;13.Docker安装Tomcat 下载Tomcat镜像启动Tomcat镜像新版本Tomcat修改访问Tomact首页 下载Tomcat镜像 [rootdocker ~]# docker pull tomcat Using default tag: latest latest: Pulling from library/tomcat 0e29546d541c: Pull complete 9b829c7…

uniapp横向滚动示例

目录 插件市场案例最后 插件市场 地址 案例 地址 最后 感觉文章好的话记得点个心心和关注和收藏&#xff0c;有错的地方麻烦指正一下&#xff0c;如果需要转载,请标明出处&#xff0c;多谢&#xff01;&#xff01;&#xff01;

11月榜单丨飞瓜数据B站UP主排行榜(哔哩哔哩平台)发布!

飞瓜轻数发布2023年11月飞瓜数据UP主排行榜&#xff08;B站平台&#xff09;&#xff0c;通过充电数、涨粉数、成长指数、带货数据等维度来体现UP主账号成长的情况&#xff0c;为用户提供B站号综合价值的数据参考&#xff0c;根据UP主成长情况用户能够快速找到运营能力强的B站U…

java--泛型方法、通配符、上下限

1.泛型方法 2.通配符 就是"?"&#xff0c;可以在"使用泛型"的时候代表一切类型&#xff1b;E T K V是在定义泛型的时候使用。 3.泛型的上下限 ①泛型上限&#xff1a;? extends Car&#xff1a;? 能接收的必须是Car或者其子类 ②泛型下限&#xff1…

ubuntu安装MySQL8

1.下载mysql8 MySQL :: Download MySQL Installer (Archived Versions) 选择对应的mysql版本和对应的ubuntu版本图即可 2.下载后上传到sftp文件夹中&#xff0c;然后通过以下命令解压 tar -xvf mysql-server_8.0.29-1ubuntu20.04_amd64.deb-bundle.tar 3.依次安装即可 &#…

Sql Server 2017主从配置之:AlwaysOn高可用

AlwaysOn高可用功能&#xff0c;真正实现了数据库的灾备切换、高可用。 AlwaysOn通过Windows Server故障转移群集&#xff0c;部署高可用数据库组。 在故障转移群集基础上完成部署读写分离&#xff0c;只读负载平衡最多3个写入节点实现故障转移最多3个数据实时同步节点 环境…

西门子SMART精彩触摸屏如何在进入某个画面时置位某个BOOL变量?

西门子SMART精彩触摸屏如何在进入某个画面时置位某个BOOL变量&#xff1f; 以下举例进行说明具体的操作&#xff1a; 如下图所示&#xff0c;新建一个项目后&#xff0c;在变量表中添加好自己需要的变量&#xff1b; 添加一个画面&#xff0c;这里以“画面_1”进行举例说明&…

【链表Linked List】力扣-24 两两交换链表中的节点

目录 题目描述 解题过程 题目描述 给你一个链表&#xff0c;两两交换其中相邻的节点&#xff0c;并返回交换后链表的头节点。你必须在不修改节点内部的值的情况下完成本题&#xff08;即&#xff0c;只能进行节点交换&#xff09;。 示例 1&#xff1a; 输入&#xff1a;he…

安科瑞智慧型动态无功补偿的工业应用—— 以江苏某陶瓷生产企业配电房改造为例

安科瑞电气股份有限公司 摘 要&#xff1a;低压配电系统的无功补偿是电能质量治理的重要环节。在传统无功补偿中&#xff0c;响应速度较慢&#xff0c;补偿电流呈阶梯式&#xff0c;存在过补或欠补的现象&#xff0c;有时未必能到达理想的效果。为了解决这一问题&#xff0c;…

二极管:ESD静电保护二极管

一、什么是ESD二极管 ESD二极管与 TVS二极管原理是一样的&#xff0c;也是为了保护电&#xff0c;但ESD二极管的主要功能是防止静电。 静电防护的前提条件就要求其电容值要足够地低&#xff0c;一般在1PF-3.5PF之间最好&#xff0c;主要应用于板级保护。 二、什么是静电 静…