厦门网站建设价,制作网页时要综合考虑哪些因素,wordpress 管理中心,四视图网站在我之前写的两篇博客中:OpenAI的函数调用,LangChain的表达式语言(LCEL)中介绍了如何利用openai的api来实现函数调用功能#xff0c;以及在langchain中如何实现openai的函数调用功能#xff0c;在这两篇博客中#xff0c;我们都需要手动去创建一个结构比较复杂的函数描述变量…
在我之前写的两篇博客中: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(nameJoe,age32, emailjoegmail.com)
foo2 User(nameJoe,agebar, emailjoegmail.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(nameJane, age32, emailjanegmail.com)print(fname:{foo_p.name})
print(fage:{foo_p.age})
print(femail:{foo_p.email}) 下面我们创建一个新的pUser实例并且给age赋一个str值看看会怎么
foo_p pUser(nameJane, agebar, emailjanegmail.com) 这里我们看到pUser类是从pyPantic的子类BaseModel继承而来因此pUser也具备了pyPantic提供的数据类型验证机制当我们给变量赋了一个错误的类型值时就会发生异常并告知类型错误。下面我们来创建一个具有List变量的类
class Class(BaseModel):students: List[pUser]obj Class(students[pUser(nameJane, age32, emailjanegmail.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 airportairport_code: str Field(descriptionairport 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(descriptionairport 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 airportairport_code: strweatherSearch2convert_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参数
argumentsresponse.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 airportairport_code: str Field(descriptionairport code to get weather for)#创建商品查询函数描述对象
class ProductPriceSearch(BaseModel):Call this with product name to get the price of product product_name: str Field(descriptionname of product to look up)#创建函数列表
functions [convert_pydantic_to_openai_function(WeatherSearch),convert_pydantic_to_openai_function(ProductPriceSearch),
]#绑定函数列表
model_with_functions model.bind(functionsfunctions)#用户提问
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