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.hqwc.cn/news/237989.html

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

相关文章

存储虚拟化的写入过程

存储虚拟化的场景下,整个写入的过程。 在虚拟机里面,应用层调用 write 系统调用写入文件。write 系统调用进入虚拟机里面的内核,经过 VFS,通用块设备层,I/O 调度层,到达块设备驱动。虚拟机里面的块设备驱动…

分享89个清新唯美PPT,总有一款适合您

分享89个清新唯美PPT,总有一款适合您 89个清新唯美PPT下载链接:https://pan.baidu.com/s/14DAA9jvVmlQZ_FJ4DNy9Rw?pwd8888 提取码:8888 Python采集代码下载链接:采集代码.zip - 蓝奏云 学习知识费力气,收集整…

LeetCode Hot100 438.找到字符串中所有字母异位词

题目: 给定两个字符串 s 和 p,找到 s 中所有 p 的 异位词 的子串,返回这些子串的起始索引。不考虑答案输出的顺序。 异位词 指由相同字母重排列形成的字符串(包括相同的字符串)。 代码: class Solution …

伙伴关系亲密无间,泄密无从滋生:上海迅软DSE帮您提升供应商机密保护力!

如今企业与第三方供应商的合作越来越频繁,为了达到合作伙伴的信息安全要求,防止机密被有意无意泄露,不少企业除了提高员工安全意识,还会主动部署安全产品,强化企业的信息保护措施。 迅软DSE企业机密防泄密解决方案 1、…

centos8 在线安装、离线安装cmake

在线安装 yum install -y cmake make 离线安装 通过finalshell 上传离线安装包 离线安装 进入到程序所在路径下执行命令进行安装 rpm -Uvh --force --nodeps *.rpm

汽车行驶不同工况数据

1、内容简介 略 28-可以交流、咨询、答疑 2、内容说明 汽车行驶不同工况数据 汽车行驶不同工况数据 ECE、EUDC、FTP75、NEDC、自定义 3、仿真分析 4、参考论文 略 链接:https://pan.baidu.com/s/1AAJ_SlHseYpa5HAwMJlk1w 提取码:rvol

2021年11月10日 Go生态洞察:Twelve Years of Go

🌷🍁 博主猫头虎(🐅🐾)带您 Go to New World✨🍁 🦄 博客首页——🐅🐾猫头虎的博客🎐 🐳 《面试题大全专栏》 🦕 文章图文…

模糊C均值(Fuzzy C-means,FCM)聚类的可运行的python程序代码,复制即可用!!切记需要安装库 scikit-fuzzy

文章目录 前言一、安装库 scikit-fuzzy二、具体程序代码(复制可运行)三、结果展示总结 前言 模糊C均值(Fuzzy C-means,FCM)聚类是一种软聚类方法,它允许数据点属于多个聚类,每个数据点对所有聚…

leecode 【二】

相交链表 给你两个单链表的头节点 headA 和 headB ,请你找出并返回两个单链表相交的起始节点。如果两个链表不存在相交节点,返回 null 。 图示两个链表在节点 c1 开始相交: 题目数据 保证 整个链式结构中不存在环。 注意,函数返…

朴素贝叶斯算法

一、贝叶斯算法 朴素贝叶斯算法是一种基于贝叶斯定理的分类算法。在理解朴素贝叶斯算法之前,需要先了解贝叶斯定理。最早出现在英国数学家托马斯贝叶斯(Thomas Bayes)在 1763 年发表的一篇论文中,该定理实际上是一种用于计算在给定某些前提条件下某个事…

使用Xshell启动远程服务器上的tensorboard:本地浏览器打开

在远程服务器上启动的tensorboard产生的localhost网址用本地浏览器一般不能直接打开,我们需要建立本地PC与远程服务器的通信,将tensorboard的映射端口与本地端口连接起来(参考解决方案)。 一、连接远程服务器设置 二、添加SSH隧道…

C++-详解智能指针

目录 ​编辑 一.什么是智能指针 1.RAII 2.智能智能指针 二.为什么需要智能指针 1.内存泄漏 a. 什么是内存泄漏,内存泄漏的危害 b.内存泄漏分类 c.如何检测内存泄漏 d.如何避免内存泄漏 总结一下: 2.为什么需要智能指针以及智能指针的原理 三.智能指针的使用 1.C…