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

news2024/10/7 12:22:02

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

由于我们手动创建这样的函数描述变量会非常的费时,且容易出错, 那么今天我们再介绍一种更加轻松的方式在langchain中实现openai的函数调用功能。在介绍今天的主要内容之前先让我们做一些初始化的工作,如设置opai的api_key,这里我们需要说明一下,在我们项目的文件夹里会存放一个 .env的配置文件,我们将api_key放置在该文件中,我们在程序中会使用dotenv包来读取api_key,这样可以避免将api_key直接暴露在程序中:

#pip install -U python-dotenv
 
import os
import openai
 
from 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 = name
        self.age = age
        self.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: str
    age: int
    email: 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_function

weather_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: str

weatherSearch2=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.coloradmin.cn/o/1276557.html

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

相关文章

【前端】-【electron】

文章目录 介绍electron工作流程环境搭建 electron生命周期(app的生命周期)窗口尺寸窗口标题自定义窗口的实现阻止窗口关闭父子及模态窗口自定义菜单 介绍 electron技术架构:chromium、node.js、native.apis electron工作流程 桌面应用就是…

SQL Server 2016(为数据表Porducts添加数据)

1、实验环境。 某公司有一台已经安装了SQL Server 2016的服务器,并已经创建了数据库PM。 2、需求描述。 在数据库PM中创建表products,"编号"列的值自动增长并为主键。然后使用T-SQL语句为表格插入如下数据。 3、实验步骤。 1、使用SSMS管理工…

2022年高校大数据挑战赛B题图像信息隐藏求解全过程论文及程序

2022年高校大数据挑战赛 B题 图像信息隐藏 原题再现: 互联网的快速发展,给图像、视频的传播方式带来巨大变化。图像作为媒体的重要载体,每天有大量的原创图像公开在互联网上,如何保护图像版权的同时不破坏原始的图像一直是图像处…

反转链表的实现

题目描述: 给出一个链表的头节点,将其反转,并返回新的头节点 思路1:反转地址 将每个节点里的地址由指向下一个节点变为指向前一个节点 定义三个结构体指针n1,n2,n3,n1表示改后指针的地址,n2表示要修改结构体里next的…

手机上的记事本怎么打开?安卓手机通用的记事本APP

有不少上班族发现,自己想要在电脑上随手记录一些工作文字内容,直接使用电脑上的记事本工具来编辑文字是比较便捷的。但是如果想要在手机上记录文字内容,就找不到手机上的记事本了。那么手机上的记事本怎么打开?安卓手机通用的记事…

AWS Remote Control ( Wi-Fi ) on i.MX RT1060 EVK - 1 “建立开发环境”

这个系列的文章将叙述如何借由 NXP 的“evkmimxrt1060_aws_remote_control_wifi_nxp”这支 Sample Code,达到 NXP RT1060EVK 经由 U-Blox EVK-JODY-W263 将资讯传到 AWS 上,并可借由手机对 RT1060 EVK 的 LED 进行远端控制。 整体架构如下图所示&#x…

VUE语法-(readonly的用法)将数据设置成只读模式

1、功能概述 在Vue中定义一个变量,这个变量的值不允许被修改,核心是通过readonly设置成只读。 如果不会使用ref和reactive响应式数据参考如下博客: https://blog.csdn.net/tangshiyilang/article/details/134701103 2、具体实现 如下案例…

41 - 如何使用缓存优化系统性能?

缓存是我们提高系统性能的一项必不可少的技术,无论是前端、还是后端,都应用到了缓存技术。前端使用缓存,可以降低多次请求服务的压力;后端使用缓存,可以降低数据库操作的压力,提升读取数据的性能。 今天我…

LeetCode | 965. 单值二叉树

LeetCode | 965. 单值二叉树 OJ链接 首先判断树为不为空,为空直接true然后判断左子树的val,和根的val相不相同再判断右子树的val,和根的val相不相同最后递归左子树和右子树 bool isUnivalTree(struct TreeNode* root) {if(root NULL)retur…

windows下如何搭建属于自己的git服务器?

windows下如何搭建属于自己的git服务器? 工具准备(此章节为网上摘要,忘记出自哪里了,大家自行参考)实操步骤 工具准备(此章节为网上摘要,忘记出自哪里了,大家自行参考) …

c语言详解牛顿迭代法以及求解倒数和平方根

Newtons iteration method 是在实数域和复数域利用切线不断逼近方程根的一种求高次曲线方程的方法,区别于梯度下降法,它是二阶导,收敛速度比较快,对于非凸函数,牛顿法容易受到鞍点或者最大值点的吸引。由于牛顿迭代法是…

[英语学习][3][Word Power Made Easy]的精读与翻译优化

[序言] 这次翻译校验, 难度有点大, 原版中英翻译已出现了严重地偏差. 昨晚11点开始阅读如下段落, 花费了1个小时也没有理解原作者的核心表达, 索性睡觉了. 今早学习完朗文单词之后, 9点半开始继续揣摩. 竟然弄到了中午11点30, 终于明白原作者要表达的意思了. 废话不多说&#x…

笔记----单纯剖分----1

笔记----单纯剖分 定义 线性组合仿射组合: 线性组合的系数为1凸组合: 仿射组合所有的系数都是正数 凸集 R^m 的 任意有限个点的凸组合仍在其中的子集仿射子空间 R^m 的 任意有限个点的仿射组合仍在其中的子集凸包 conv(A) A是R^m的一个子集 A的所有有限凸…

【小布_ORACLE笔记】Part11-6 RMAN Backups

【小布_ORACLE笔记】Part11-6 RMAN Backups 1.track文件的作用 当做差异性备份时,server process对应的RMAN客户端的server process就不用去每个块每个块的检查,只要到trackfile 里面去读一下,看哪个块改变了就直接把哪个块备份下来&#x…

轻量封装WebGPU渲染系统示例<40>- 多层材质的Mask混合(源码)

当前示例源码github地址: https://github.com/vilyLei/voxwebgpu/blob/feature/rendering/src/voxgpu/sample/MaskTextureEffect.ts 当前示例运行效果: 两层材质效果: 三层材质效果: 此示例基于此渲染系统实现,当前示例TypeScript源码如下: export c…

【Go】protobuf介绍及安装

目录 一、Protobuf介绍 1.Protobuf用来做什么 2. Protobuf的序列化与反序列化 3. Protobuf的优点和缺点 4. RPC介绍 <1>文档规范 <2>消息编码 <3>传输协议 <4>传输性能 <5>传输形式 <6>浏览器的支持度 <7>消息的可读性和…

【鸿蒙应用ArkTS开发系列】-自定义底部菜单列表弹窗

文章目录 前言创建Demo工程创建dialog 文件夹创建ListMenu 接口创建自定义弹窗 ListMenuDialog使用自定义弹窗 打包测试效果演示默认效果菜单带图标效果设置文本颜色效果不同文本颜色效果无标题效果 前言 上一篇文章中我们实现了选择图片、选择文件、拍照的功能 。 链接在这里…

11.28 C++作业

提示并输入一个字符串&#xff0c;统计该字符中大写、小写字母个数、数字个数、空格个数以及其他字符个数 要求使用C风格字符串完成 #include <iostream>using namespace std;int main() {string str;cout << "请输入一个字符串&#xff1a;" <<…

92基于matlab的引力搜索算法优化支持向量机(GSA-SVM)分类模型

基于matlab的引力搜索算法优化支持向量机&#xff08;GSA-SVM&#xff09;分类模型&#xff0c;以分类精度为优化目标优化SVM算法的参数c和g&#xff0c;输出分类可视化结果及适应度变化曲线。数据可更换自己的&#xff0c;程序已调通&#xff0c;可直接运行。 92 引力搜索算法…

无人机助力电力设备螺母缺销智能检测识别,python基于YOLOv5开发构建电力设备螺母缺销小目标检测识别系统

传统作业场景下电力设备的运维和维护都是人工来完成的&#xff0c;随着现代技术科技手段的不断发展&#xff0c;基于无人机航拍飞行的自动智能化电力设备问题检测成为了一种可行的手段&#xff0c;本文的核心内容就是基于YOLOv7来开发构建电力设备螺母缺销检测识别系统&#xf…