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
相关文章:

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

WiFi概念介绍
WiFi概念介绍 1. 什么是WLAN2. 什么是Wi-Fi3. Wi-Fi联盟4. WLAN定义范围5. WiFi协议体系6. 协议架构7. WiFi技术的发展7.1 IEEE802.117.2 802.11标准和补充 8. 术语 1. 什么是WLAN Wireless Local Area Network,采用802.11无线技术进行互连的一组计算机和相关设备。…...

如何优雅的进行业务分层
1.什么是应用分层 说起应用分层,大部分人都会认为这个不是很简单嘛 就controller,service, mapper三层。 看起来简单,很多人其实并没有把他们职责划分开,在很多代码中,controller做的逻辑比service还多,service往往当…...
C++的std命名空间
总以为自己懂了,可是仔细想想,多问自己几个问题,发现好像又不是很清楚 命名空间(Namespace)是C中一种用于解决命名冲突问题的机制,它能够将全局作用域划分为若干个不同的区域,每个区域内可以有…...

unity学习笔记
一、射线检测 如何让鼠标点击某个位置,游戏角色就能移动到该位置? 实现的原理分析:我们能看见游戏的东西就是摄像机拍摄到的东西,所以摄像机的镜平面就是当前能看到的了。 那接下来我们可以让摄像机发射一条射线,鼠标…...

使用SpringBoot和ZXing实现二维码生成与解析
一、ZXing简介 ZXing是一个开源的,用Java实现的多种格式的1D/2D条码图像处理库。它包含了用于解析多种格式的1D/2D条形码的工具类,目标是能够对QR编码,Data Matrix, UPC的1D条形码进行解码。在二维码编制上,ZXing巧妙地利用构成计…...

C++模板—函数模板、类模板
目录 一、函数模板 1、概念 2、格式 3、实例化 4、模板参数的匹配 二、类模板 1、定义格式 2、实例化 交换两个变量的值,针对不同类型,我们可以使用函数重载实现。 void Swap(double& left, double& right) {double tmp left;left ri…...

Monkey
一、Monkey的概念 “猴子测试”是指没有测试经验的人甚至对计算机根本不了解的人(就像猴子一样)不需要知道程序的任何用户交互方面的知识,如果给他一个程序,他就会针对他看到的界面进行操作,其操作是无目的的、乱点乱按…...

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

算法学习—排序
排序算法 一、选择排序 1.算法简介 选择排序是一个简单直观的排序方法,它的工作原理很简单,首先从未排序序列中找到最大的元素,放到已排序序列的末尾,重复上述步骤,直到所有元素排序完毕。 2.算法描述 1ÿ…...

在Pycharm中创建项目新环境,安装Pytorch
在python项目中,很多项目使用的各类包的版本是不一致的。所以我们可以对每个项目有专属于它的环境。所以这个文章就是教你如何创建新环境。 一、创建新环境 首先我们需要去官网下载conda。然后在Pycharm下面添加conda的可执行文件。 用conda创建新环境。 二、…...
linux里source、sh、bash、./有什么区别
1、source source a.sh 在当前shell内去读取、执行a.sh,而a.sh不需要有"执行权限" source命令可以简写为"." . a.sh 注意:中间是有空格的。 2、sh/bash sh a.sh bash a.sh 都是打开一个subshell去读取、执行a.sh,而a.…...

IDEA编译器技巧-提示词忽略大小写
IDEA编译器技巧-提示词忽略大小写 写代码时,每次创建对象都要按住 Shift 字母 做大写开头, 废手, 下面通过编译器配置解放Shift 键 setting -> Editor -> General -> Code Completion -> Match case 把这个√去掉, 创建对象就不需要再按住 Shift 键 示例: 1.…...

【MySQL】MySQL安装 环境初始化
MySQL安装 MYSQL官网 安装完成后,傻瓜下一步即可 配置一下环境变量即可 (1) 初始化MySQL, 管理员身份运行 mysqld --initialize-insecure(2) 注册 mysqld mysqld -install# 如果记录以前的版本执行下面指令 mysqld -remove(3) 启动MySQL服务 // 启动mysql服务 net start …...
C# IList 与List区别二叉树的层序遍历
IList 接口: IList 是一个接口,定义了一种有序集合的通用 API。继承自 ICollection 接口和IEnumerable<T>,是所有泛型列表的基接,口它提供了对列表中元素的基本操作,如添加、删除、索引访问等。IList 不是一个具…...

助力android面试2024【面试题合集】
转眼间,2023年快过完了。今年作为口罩开放的第一年大家的日子都过的十分艰难,那么想必找工作也不好找,在我们android开发这一行业非常的卷,在各行各业中尤为突出。android虽然不好过,但不能不吃饭吧。卷归卷但是还得干…...

【动态规划】LeetCode-62.不同路径
🎈算法那些事专栏说明:这是一个记录刷题日常的专栏,每个文章标题前都会写明这道题使用的算法。专栏每日计划至少更新1道题目,在这立下Flag🚩 🏠个人主页:Jammingpro 📕专栏链接&…...

对 Vision Transformers 及其基于 CNN-Transformer 的变体的综述
A survey of the Vision Transformers and its CNN-Transformer based Variants 摘要1、介绍2、vit的基本概念2.1 patch嵌入2.2 位置嵌入2.2.1 绝对位置嵌入(APE)2.2.2 相对位置嵌入(RPE)2.2.3卷积位置嵌入(CPE) 2.3 注意力机制2.3.1多头自我注意(MSA) 2.4 Transformer层2.4.1 …...

MongoDB简介
数据库,顾名思义,是保存数据的地方。中华文化博大精深,短短3个文字,就定义了一个强大的数据管理和读写方式出来。数据库,管理的对象是数据。称为库,表示数据在库中有组织,相互之间有微妙的关系。…...
尚硅谷hadoop3.x课程部分资料文件下载,jdk,hadoopjar包
jdk文件百度云下载: 链接:https://pan.baidu.com/s/1MCiGRzOZY8rAFpRJwA3tdw 提取码:kphl hadoop的jar包: 最新版官网链接: Index of /dist/hadoop/core/stable (apache.org) 百度云下载,3.3.3版…...
设计模式和设计原则回顾
设计模式和设计原则回顾 23种设计模式是设计原则的完美体现,设计原则设计原则是设计模式的理论基石, 设计模式 在经典的设计模式分类中(如《设计模式:可复用面向对象软件的基础》一书中),总共有23种设计模式,分为三大类: 一、创建型模式(5种) 1. 单例模式(Sing…...

深入剖析AI大模型:大模型时代的 Prompt 工程全解析
今天聊的内容,我认为是AI开发里面非常重要的内容。它在AI开发里无处不在,当你对 AI 助手说 "用李白的风格写一首关于人工智能的诗",或者让翻译模型 "将这段合同翻译成商务日语" 时,输入的这句话就是 Prompt。…...

.Net框架,除了EF还有很多很多......
文章目录 1. 引言2. Dapper2.1 概述与设计原理2.2 核心功能与代码示例基本查询多映射查询存储过程调用 2.3 性能优化原理2.4 适用场景 3. NHibernate3.1 概述与架构设计3.2 映射配置示例Fluent映射XML映射 3.3 查询示例HQL查询Criteria APILINQ提供程序 3.4 高级特性3.5 适用场…...

为什么需要建设工程项目管理?工程项目管理有哪些亮点功能?
在建筑行业,项目管理的重要性不言而喻。随着工程规模的扩大、技术复杂度的提升,传统的管理模式已经难以满足现代工程的需求。过去,许多企业依赖手工记录、口头沟通和分散的信息管理,导致效率低下、成本失控、风险频发。例如&#…...

NLP学习路线图(二十三):长短期记忆网络(LSTM)
在自然语言处理(NLP)领域,我们时刻面临着处理序列数据的核心挑战。无论是理解句子的结构、分析文本的情感,还是实现语言的翻译,都需要模型能够捕捉词语之间依时序产生的复杂依赖关系。传统的神经网络结构在处理这种序列依赖时显得力不从心,而循环神经网络(RNN) 曾被视为…...

C++ Visual Studio 2017厂商给的源码没有.sln文件 易兆微芯片下载工具加开机动画下载。
1.先用Visual Studio 2017打开Yichip YC31xx loader.vcxproj,再用Visual Studio 2022打开。再保侟就有.sln文件了。 易兆微芯片下载工具加开机动画下载 ExtraDownloadFile1Info.\logo.bin|0|0|10D2000|0 MFC应用兼容CMD 在BOOL CYichipYC31xxloaderDlg::OnIni…...
Linux离线(zip方式)安装docker
目录 基础信息操作系统信息docker信息 安装实例安装步骤示例 遇到的问题问题1:修改默认工作路径启动失败问题2 找不到对应组 基础信息 操作系统信息 OS版本:CentOS 7 64位 内核版本:3.10.0 相关命令: uname -rcat /etc/os-rele…...

浪潮交换机配置track检测实现高速公路收费网络主备切换NQA
浪潮交换机track配置 项目背景高速网络拓扑网络情况分析通信线路收费网络路由 收费汇聚交换机相应配置收费汇聚track配置 项目背景 在实施省内一条高速公路时遇到的需求,本次涉及的主要是收费汇聚交换机的配置,浪潮网络设备在高速项目很少,通…...

Vue ③-生命周期 || 脚手架
生命周期 思考:什么时候可以发送初始化渲染请求?(越早越好) 什么时候可以开始操作dom?(至少dom得渲染出来) Vue生命周期: 一个Vue实例从 创建 到 销毁 的整个过程。 生命周期四个…...
Oracle11g安装包
Oracle 11g安装包 适用于windows系统,64位 下载路径 oracle 11g 安装包...