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

在我之前写的两篇博客中:OpenAI的函数调用,LangChain的表达式语言(LCEL)中介绍了如何利用openai的api来实现函数调用功能,以及在langchain中如何实现openai的函数调用功能,在这两篇博客中,我们都需要手动去创建一个结构比较复杂的函数描述变量,如下图所示:

由于我们手动创建这样的函数描述变量会非常的费时,且容易出错, 那么今天我们再介绍一种更加轻松的方式在langchain中实现openai的函数调用功能。在介绍今天的主要内容之前先让我们做一些初始化的工作,如设置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']

一、Pydantic 语法

今天的介绍的内容中,我们会用到Pydantic 语法,Pydantic是一个Python库,用于数据类型验证和解析。它使用类型注释来控制模式验证和序列化。Pydantic的核心验证逻辑是用Rust编写的,因此它是Python中最快的数据验证库之一,Pydantic提供了一种简洁的方法来定义数据结构,同时确保数据遵守指定的类型和约束。下面我们来演示一个例子,在这个例子中我们会创建一个简单的python类:

class User:def __init__(self, name: str, age: int, email: str):self.name = nameself.age = ageself.email = email

在这个User类中有三个成员分别是name,age,email,其中它们的类型分别定义为str,int,str。下面我们创建两个类的实例foo1和foo2:

foo1 = User(name="Joe",age=32, email="joe@gmail.com")
foo2 = User(name="Joe",age="bar", email="joe@gmail.com")print(foo1.age)
print(foo2.age)

这里我们看到User的age定义的类型是int, 然而我们却给User的实例foo2的age赋了str的值“bar”,但是结果任然不受影响,也就是说python中的变量的类型不受定义的约束,这种不严格的类型定义方式有时候会导致程序的崩溃和不可预料的后果,下面我们看看pydantic的是怎么来解决这个问题的:

from typing import List
from pydantic import BaseModel, Field#定义类pUser
class pUser(BaseModel):name: strage: intemail: str#创建类的实例
foo_p = pUser(name="Jane", age=32, email="jane@gmail.com")print(f'name:{foo_p.name}')
print(f'age:{foo_p.age}')
print(f'email:{foo_p.email}')

下面我们创建一个新的pUser实例,并且给age赋一个str值看看会怎么:

foo_p = pUser(name="Jane", age="bar", email="jane@gmail.com")

这里我们看到pUser类是从pyPantic的子类BaseModel继承而来,因此pUser也具备了pyPantic提供的数据类型验证机制,当我们给变量赋了一个错误的类型值时就会发生异常,并告知类型错误。下面我们来创建一个具有List变量的类:

class Class(BaseModel):students: List[pUser]obj = Class(students=[pUser(name="Jane", age=32, email="jane@gmail.com")]
)obj

这里我们创建了一个班级类(Class),并且包含了一个students的List成员 ,List中的元素类型为pUser。

二、使用pydantic创建Openai的函数描述对象

下面我们使用pyPantic创建一个函数描述对象类:

class WeatherSearch(BaseModel):"""Call this with an airport code to get the weather at that airport"""airport_code: str = Field(description="airport code to get weather for")

这里我们创建了一个WeatherSearch类,它继承自pyPantic的BaseModel子类,因此WeatherSearch类的所有成员都被具备了数据类型校验机制,该类有一个str类型的成员airport_code它表示机场代码,并且它有一个描述信息description,用来说明airport_code的作用,在airport_code的上方也有一段文本描述信息:"""Call this with an airport code to get the weather at that airport""" 这段文本信息是对类WeatherSearch的说明,意思是通过机场代码可以查询天气情况,接下来我们要使用langchain将这个WeatherSearch类转换成openai的函数描述对象:

from langchain.utils.openai_functions import convert_pydantic_to_openai_functionweather_function = convert_pydantic_to_openai_function(WeatherSearch)
weather_function

这里我们使用了langchain的convert_pydantic_to_openai_function方法将pydantic类转换成了openai的函数描述对象。需要的注意的是在定义pydantic类时文本描述信息不可缺少,如缺少文本描述信息会导致转换时出错,下面我们定义一个pydantic类WeatherSearch1:

class WeatherSearch1(BaseModel):airport_code: str = Field(description="airport code to get weather for")convert_pydantic_to_openai_function(WeatherSearch1)

这里我们看到,由于我们没有在 WeatherSearch1中加入对WeatherSearch1本身的描述信息,导致在转换时报错,虽然我们加了类成员airport_code的描述信息description,但是缺少对类本身的描述信息,所以最终导致在转换时出错,这说明在定义pydantic类时,类本身的描述信息是必须要有的。下面我们再看一个例子:

class WeatherSearch2(BaseModel):"""Call this with an airport code to get the weather at that airport"""airport_code: strweatherSearch2=convert_pydantic_to_openai_function(WeatherSearch2)
weatherSearch2

这里我们在定义WeatherSearch2时添加了类本身的描述信息,但是对于类成员airport_code我们只定义了类型却没有添加描述信息,但在转换时却没有报错,这可能是因为llm可以从类的描述信息中推断出类成员的含义和作用,因此有时候定义类成员的时候不添加描述信息也是可以的。下面我们是在langchain中的invoke方法增加一个functions参数来绑定函数描述对象看看会得到什么样的结果:

from langchain.chat_models import ChatOpenAI
#创建llm
model = ChatOpenAI()
#执行函数调用
response = model.invoke("what is the weather in SF today?", functions=[weather_function])
response

这里我们看到当我们向llm询问机场天气情况时,llm返回了函数调用参数airport_code,这说明llm认为回答用户的这个问题需要调用外部函数,并将调用外部函数的参数返回给了我们,然后我们就可以拿着函数的参数去实际调用外部函数了。除了在invoke方法中增加一个functions参数来绑定函数描述对象以外我们还可以在执行invoke之前使用bind方法来绑定函数描述对象,这样也会达到同样的效果:

#创建llm
model = ChatOpenAI()
#绑定函数描述对象
model_with_function = model.bind(functions=[weather_function])
#执行函数调用
response = model_with_function.invoke("what is the weather in sf?")
response

下面我们测试一下,当我们只和llm打招呼时,它会返回什么结果:

response = model_with_function.invoke("hi!")
response

这里我们可以看到当我们只和llm打招呼时("hi!"), llm并没有激活函数调用,也就是说llm意识到当前用户只是在做礼节性的打招呼,因此无需激活函数调用,所以它没有返回函数调用的信息。

三、强制执行函数调用 

在之前第一篇博客OpenAI的函数调用中,我们介绍了让llm强制激活函数调用功能,这里我们也同样可以强制llm激活函数调用,只要我们在bind时增加一个function_call参数就可以了,无论用户提什么问题都会返回函数参数信息:

#指定调用的函数名称
model_with_forced_function = model.bind(functions=[weather_function],function_call={"name":"WeatherSearch"})response = model_with_forced_function.invoke("what is the weather in sf?")
response

如果用户的问题和天气无关时,llm也同样会返回调用函数的参数信息:

model_with_forced_function.invoke("hi!")

 这里我们看到,当我们和LLM打招呼时,它也返回了函数调用参数airport_code,只是它的值时随机的。

四、使用chain来实现函数调用

在一般情况下我们会使用chain来实现整个问答的流程,接下来我们通过创建chain来实现函数调用功能:

from langchain.prompts import ChatPromptTemplate
from langchain.chat_models import ChatOpenAI#通过prompt模板创建prompt
prompt = ChatPromptTemplate.from_messages([("system", "You are a helpful assistant"),("user", "{input}")
])#创建llm
model = ChatOpenAI()
#绑定函数描述对象
model_with_function = model.bind(functions=[weather_function])
#创建chain
chain = prompt | model_with_function
#测试函数调用功能
response  = chain.invoke({"input": "what is the weather in sf?"})
response

这里使用了chain的invoke方法来实现对llm的问答,llm根据问题的自行判断是否需要激活函数调用。如何需要函数调用则返回函数调用参数。 

 下面我们来提取arguments参数:

arguments=response.additional_kwargs['function_call']['arguments']
arguments = eval(arguments)
arguments

这里我们使用了eval函数将原先的字符串变量转换成了字典对象,这样便于我们从中提取我们需要的数据。

五、使用多个函数

前面我们只是通过pydantic创建了一个函数描述对象,但在很多应用场景中,我们可能需要传递一组函数,让 LLM 根据问题上下文决定使用哪个函数。下面我们再创建一个函数描述对象ProductSearch,用来查询商品信息,这样再加上之前的天气查询函数,我们就有了两个函数描述对象了,我们可以让llm自己根据用户的问题来自行判断调用哪个函数:

#创建天气查询函数描述对象
class WeatherSearch(BaseModel):"""Call this with an airport code to get the weather at that airport"""airport_code: str = Field(description="airport code to get weather for")#创建商品查询函数描述对象        
class ProductPriceSearch(BaseModel):"""Call this with product name  to get the price of product """product_name: str = Field(description="name of product to look up")#创建函数列表
functions = [convert_pydantic_to_openai_function(WeatherSearch),convert_pydantic_to_openai_function(ProductPriceSearch),
]#绑定函数列表
model_with_functions = model.bind(functions=functions)#用户提问
model_with_functions.invoke("what is the weather in sf?")

 这里我们向llm询问了天气情况,llm正确返回了天气函数的调用参数,下面我们再询问一个商品的问题:

model_with_functions.invoke("what are the price of iphone 14 pro ")

这里我们提出了一个关于手机的问题,llm返回了商品查询函数的参数,下面我们和llm打给招呼,看看会返回什么:

model_with_functions.invoke("hi!")

 

这里我们看到,当我们和llm打招呼时,llm没有返回任何函数的参数, 也就是说llm意识到了用户的问题和预先设定的两个函数没有任何关系,所以无需返回函数调用参数。

六、总结

今天我们学习了pydantic的基础语法,以及如何利用langchain将pydantic定义的类转换成openai的函数描述对象,通过pydantic我们可以轻松定义函数描述对象的类,然后使用langchain的convert_pydantic_to_openai_function方法将其转换成openai所需要的格式,如果不使用pydantic我们必须手动创建openai的函数描述对象,这将是非常低效且繁琐的工作。

七、参考资料

DLAI - Learning Platform Beta

Welcome to Pydantic - Pydantic

Introduction | 🦜️🔗 Langchain

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

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

相关文章

0X04

看到一道有趣的misc题 misc签到题 打开后啥都没有,全选后发现每一行有空格,数了一行发现空格数量转ascil码后是f,猜测都如此, 后面就可以交个脚本了,统计之后转换成ascii from Crypto.Util.number import long_to_b…

YOLOv8-Seg改进:SENetV2,squeeze和excitation全面升级,效果优于SENet | 2023年11月最新成果

🚀🚀🚀本文改进: SENetV2,squeeze和excitation全面升级,作为注意力机制引入到YOLOv8,放入不同网络位置实现涨点 🚀🚀🚀YOLOv8-seg创新专栏:http://t.csdnimg.cn/KLSdv 学姐带你学习YOLOv8,从入门到创新,轻轻松松搞定科研; 1)手把手教你如何训练YOLOv8-s…

《曾国藩传》:崇尚笨拙的人生哲学

哈喽啊,大家好,我是雷工! 以前读书喜欢读小说,喜欢看《我从你的全世界路过》《云间有个小卖铺》这些轻松的小说,读起来很轻松。 随着年龄增长,阅历的增加开始喜欢读历史,读人物传记,…

Ubuntu镜像与K8S冲突,容器持续Terminating

问题 记录一次软件冲突BUG: eclipse-temurin:11-jdk(底层Ubuntu 20.04.3 LTS)镜像创建的容器在K8S-1.25.5上无法正常terminating,造成资源浪费,甚至引发K8S资源CPU insufficient报错。具体表现 某些容器镜像在K8S上无…

uni-app+ts----微信小程序锚点定位 、自动吸顶、滚动自动选择对应的锚点(点击tab跳转对应的元素位置)

uni-app----微信小程序锚点定位 、自动吸顶、滚动自动选择对应的锚点&#xff08;点击tab跳转对应的元素位置&#xff09; html代码部分 重点是给元素加入【 :id“‘item’ item.id”】 <view class"radiusz bg-white pt-[30rpx] z-[999]"><u-tabs:list&q…

INFINI Labs 产品更新 | 修复 Easysearch 跨集群复制索引同步问题,Gateway 内存异常增长等问题

INFINI Labs 产品又更新啦~&#xff0c;本次更新主要对 Easysearch、Gateway、Console、Agent 等产品功能进行优化和相关 Bug 修复&#xff0c;解决了内存异常增长等问题&#xff0c;以下是详细说明。 INFINI Easysearch v1.6.2 INFINI Easysearch 是一个分布式的近实时搜索与…

机器学习---EM算法

1. 极大似然估计与EM算法 极大似然估计是一种常用的参数估计方法&#xff0c;它是以观测值出现的概率最大作为准则。关于极 大似然估计&#xff0c;假设现在已经取到样本值了&#xff0c;这表明取到这一样本的概率L(θ) 比较 大。我们自然不会考虑那些不能使样本出现的θ作为…

计算机基础知识62

模型层回顾&#xff1a;基本使用 # 模型层有orm框架&#xff1a;对象关系映射 数据库中&#xff1a;一个个表 &#xff1a;user表&#xff0c;book表&#xff0c;一条条的记录 程序中&#xff1a;一个个类&#xff0c;一个个对象 数据库中一张表---->程序中一个…

【超详细】vue项目:Tinymce富文本使用教程以及踩坑总结+功能扩展

【【超详细】vue项目&#xff1a;Tinymce富文本使用教程以及踩坑总结功能扩展 引言&#xff1a;一、 开始二、快速开始1、安装Tinymce 三、封装成Vue组件1、文件结构2、index.vue3、dynamicLoadScript.js4、plugin.js5、toolbar.js 四、使用Tinymce组件五、业务逻辑实现1、添加…

对外汉语教师简历(精选12篇)

以对外汉语老师招聘需求为背景&#xff0c;我们制作了1份全面、专业且具有参考价值的简历案例&#xff0c;大家可以灵活借鉴&#xff0c;希望能帮助大家在众多候选人中脱颖而出。 对外汉语教师简历下载&#xff08;在线制作&#xff09;&#xff1a;百度幻主简历或huanzhucv.c…

Promise的resolve和reject方法(手写题)

1.resolve 2.reject 3.手写 1.resolve //构造函数上添加 resolve 方法 Promise.resolve function (value) {return new Promise((resolve, reject) > {if (value instanceof Promise) {value.then((val) > {resolve(val)},(err) > {reject(err)})} else {resolve(v…

【Python表白系列】这个情人节送她一个漂浮的爱心吧(完整代码)

文章目录 漂浮的爱心环境需求完整代码详细分析系列文章 漂浮的爱心 环境需求 python3.11.4PyCharm Community Edition 2023.2.5pyinstaller6.2.0&#xff08;可选&#xff0c;这个库用于打包&#xff0c;使程序没有python环境也可以运行&#xff0c;如果想发给好朋友的话需要这…

21.Python 操作文件

目录 1. 认识文件和I/O2. 打开文件在异常处理语句中打开在上下文管理中打开 3.读取文件3. 写入文件4. 删除文件5. 复制文件6. 重命名文件7. 文件查找和替换 1. 认识文件和I/O 文件是存储在设备上的一组字符或字节序列&#xff0c;可以包含任何内容&#xff0c;它是数据的集合和…

SQL中left join、right join、inner join等的区别

一张图可以简洁明了的理解出left join、right join、join、inner join的区别&#xff1a; 1、left join 就是“左连接”&#xff0c;表1左连接表2&#xff0c;以左为主&#xff0c;表示以表1为主&#xff0c;关联上表2的数据&#xff0c;查出来的结果显示左边的所有数据&#…

【自动化测试】Selenium IDE脚本编辑与操作(了解)

之前&#xff0c;我们录制脚本时是录制鼠标和键盘的所有在浏览器的操作&#xff0c;那么脚本会出现多余的步骤&#xff0c;有时候我们需要手动填写脚本或修改脚本&#xff0c;所以我们有必要对selenium IDE脚本编辑与操作有所了解&#xff1b;&#xff08;采用录制的方式很容易…

Java+SSM+MySQL基于微信小程序的商城购物小程序(附源码 调试 文档)

基于微信小程序的商城购物小程序 一、引言二、国内外研究现状三、系统设计四、系统实现五、测试与评估六、结论七、界面展示八、源码获取 摘要&#xff1a; 本文介绍了一种基于微信小程序的商城购物小程序&#xff0c;该系统分为管理员和用户两种用户角色。管理员可以通过系统进…

流量内存cpu使用率使用工具

类似360工具球的工具 我提供了夸克下载喜欢的朋友可以直接下载使用 我用夸克网盘分享了「TrafficMonitor」&#xff0c;点击链接即可保存。打开「夸克APP」&#xff0c;无需下载在线播放视频&#xff0c;畅享原画5倍速&#xff0c;支持电视投屏。 链接&#xff1a;https://pan…

(详细教程)笔记本电脑安装Ubuntu系统

1.前言 老的小米笔记本淘汰了&#xff0c;装一下linux系统玩一下。 使用工具如下&#xff1a;一台小米笔记本pro15.6一个惠普32G U盘一个台式机用于下载镜像等资源 2.下载Ubuntu桌面版 cn.ubuntu.com/download/de… 这里我下载的是 22.04.3 LTS 3.下载烧录工具&#xff0c…

前端面试高频考点—TCP vs UDP

目录 简介&#xff1a; 区别&#xff1a; 应用选择&#xff1a; tcp为什么需要三次握手&#xff1f; 简介&#xff1a; TCP(传输控制协议)和UDP&#xff08;用户数据报协议&#xff09; TCP是一种面向连接的、可靠的、基于字节流的传输层通信协议&#xff0c;是专门为了在不…

MySQL之性能分析和系统调优

MySQL之性能分析和系统调优 性能分析 查看执行计划 EXPLAIN EXPLAIN作为MySQL的性能分析神器&#xff0c;可以用来分析SQL执行计划&#xff0c;需要理解分析结果可以帮助我们优化SQL explain select … from … [where ...]TABLE 表名 查询的每一行记录都对于着一张表 id 该…