Pydantic(JSON)解析器实战 | 豆包MarsCode AI刷题

120 阅读11分钟

Pydantic(JSON)解析器实战

Pydantic (JSON) 解析器应该是最常用也是最重要的解析器。

Pydantic 是一个 Python 数据验证和设置管理库,主要基于 Python 类型提示。尽管它不是专为 JSON 设计的,但由于 JSON 是现代 Web 应用和 API 交互中的常见数据格式,Pydantic 在处理和验证 JSON 数据时特别有用。

第一步:创建模型实例

先通过环境变量设置OpenAI API密钥,然后使用LangChain库创建了一个OpenAI的模型实例。这里选择了text-davinci-003作为大语言模型。

# ------Part 1
# 设置OpenAI API密钥
import os
os.environ["OPENAI_API_KEY"] = '你的OpenAI API Key'

# 创建模型实例
from langchain import OpenAI
model = OpenAI(model_name='gpt-3.5-turbo-instruct')

第二步:定义输出数据的格式

先创建了一个空的DataFrame,用于存储从模型生成的描述。接下来,通过一个名为FlowerDescription的Pydantic BaseModel类,定义了期望的数据格式(也就是数据的结构)。

# ------Part 2
# 创建一个空的DataFrame用于存储结果
import pandas as pd
df = pd.DataFrame(columns=["flower_type", "price", "description", "reason"])

# 数据准备
flowers = ["玫瑰", "百合", "康乃馨"]
prices = ["50", "30", "20"]

# 定义我们想要接收的数据格式
from pydantic import BaseModel, Field
class FlowerDescription(BaseModel):
    flower_type: str = Field(description="鲜花的种类")
    price: int = Field(description="鲜花的价格")
    description: str = Field(description="鲜花的描述文案")
    reason: str = Field(description="为什么要这样写这个文案")

在这里我们用到了负责数据格式验证的Pydantic库来创建带有类型注解的类FlowerDescription,它可以自动验证输入数据,确保输入数据符合你指定的类型和其他验证条件。

Pydantic有这样几个特点。

  1. 数据验证:当你向Pydantic类赋值时,它会自动进行数据验证。例如,如果你创建了一个字段需要是整数,但试图向它赋予一个字符串,Pydantic会引发异常。
  2. 数据转换:Pydantic不仅进行数据验证,还可以进行数据转换。例如,如果你有一个需要整数的字段,但你提供了一个可以转换为整数的字符串,如 "42",Pydantic会自动将这个字符串转换为整数42。
  3. 易于使用:创建一个Pydantic类就像定义一个普通的Python类一样简单。只需要使用Python的类型注解功能,即可在类定义中指定每个字段的类型。
  4. JSON支持:Pydantic类可以很容易地从JSON数据创建,并可以将类的数据转换为JSON格式。

第三步:创建输出解析器

创建输出解析器并获取输出格式指示。先使用LangChain库中的PydanticOutputParser创建了输出解析器,该解析器将用于解析模型的输出,以确保其符合FlowerDescription的格式。然后,使用解析器的get_format_instructions方法获取了输出格式的指示。

# ------Part 3
# 创建输出解析器
from langchain.output_parsers import PydanticOutputParser
output_parser = PydanticOutputParser(pydantic_object=FlowerDescription)

# 获取输出格式指示
format_instructions = output_parser.get_format_instructions()
# 打印提示
print("输出格式:",format_instructions)

程序输出如下:

输出格式: The output should be formatted as a JSON instance that conforms to the JSON schema below.

As an example, for the schema {"properties": {"foo": {"title": "Foo", "description": "a list of strings", "type": "array", "items": {"type": "string"}}}, "required": ["foo"]}}
the object {"foo": ["bar", "baz"]} is a well-formatted instance of the schema. The object {"properties": {"foo": ["bar", "baz"]}} is not well-formatted.

Here is the output schema:

{"properties": {"flower_type": {"title": "Flower Type", "description": "\u9c9c\u82b1\u7684\u79cd\u7c7b", "type": "string"}, "price": {"title": "Price", "description": "\u9c9c\u82b1\u7684\u4ef7\u683c", "type": "integer"}, "description": {"title": "Description", "description": "\u9c9c\u82b1\u7684\u63cf\u8ff0\u6587\u6848", "type": "string"}, "reason": {"title": "Reason", "description": "\u4e3a\u4ec0\u4e48\u8981\u8fd9\u6837\u5199\u8fd9\u4e2a\u6587\u6848", "type": "string"}}, "required": ["flower_type", "price", "description", "reason"]}

上面这个输出,这部分是通过output_parser.get_format_instructions()方法生成的,这是Pydantic (JSON) 解析器的核心价值。同时它也算得上是一个很清晰的提示模板,能够为模型提供良好的指导,描述了模型输出应该符合的格式。(其中description中的中文被转成了UTF-8编码。)

它指示模型输出JSON Schema的形式,定义了一个有效的输出应该包含哪些字段,以及这些字段的数据类型。例如,它指定了 "flower_type" 字段应该是字符串类型,"price" 字段应该是整数类型。这个指示中还提供了一个例子,说明了什么是一个格式良好的输出。

第四步:创建提示模板

定义了一个提示模板,该模板将用于为模型生成输入提示。模板中包含了需要模型填充的变量(如价格和花的种类),以及之前获取的输出格式指示。

# ------Part 4
# 创建提示模板
from langchain import PromptTemplate
prompt_template = """您是一位专业的鲜花店文案撰写员。
对于售价为 {price} 元的 {flower} ,您能提供一个吸引人的简短中文描述吗?
{format_instructions}"""

# 根据模板创建提示,同时在提示中加入输出解析器的说明
prompt = PromptTemplate.from_template(prompt_template, 
       partial_variables={"format_instructions": format_instructions}) 

# 打印提示
print("提示:", prompt)

输出:

提示: 
input_variables=['flower', 'price'] 

output_parser=None 

partial_variables={'format_instructions': 'The output should be formatted as a JSON instance that conforms to the JSON schema below.\n\n
As an example, for the schema {
"properties": {"foo": {"title": "Foo", "description": "a list of strings", "type": "array", "items": {"type": "string"}}}, 
"required": ["foo"]}}\n
the object {"foo": ["bar", "baz"]} is a well-formatted instance of the schema. 
The object {"properties": {"foo": ["bar", "baz"]}} is not well-formatted.\n\n
Here is the output schema:\n```\n
{"properties": {
"flower_type": {"title": "Flower Type", "description": "\u9c9c\u82b1\u7684\u79cd\u7c7b", "type": "string"}, 
"price": {"title": "Price", "description": "\u9c9c\u82b1\u7684\u4ef7\u683c", "type": "integer"}, 
"description": {"title": "Description", "description": "\u9c9c\u82b1\u7684\u63cf\u8ff0\u6587\u6848", "type": "string"}, 
"reason": {"title": "Reason", "description": "\u4e3a\u4ec0\u4e48\u8981\u8fd9\u6837\u5199\u8fd9\u4e2a\u6587\u6848", "type": "string"}}, 
"required": ["flower_type", "price", "description", "reason"]}\n```'} 

template='您是一位专业的鲜花店文案撰写员。
\n对于售价为 {price} 元的 {flower} ,您能提供一个吸引人的简短中文描述吗?\n
{format_instructions}' 

template_format='f-string' 

validate_template=True

这就是包含了format_instructions信息的提示模板。

  1. input_variables=['flower', 'price']:这是一个包含你想要在模板中使用的输入变量的列表。我们在模板中使用了 'flower' 和 'price' 两个变量,后面我们会用具体的值(如玫瑰、20元)来替换这两个变量。
  2. output_parser=None:这是你可以选择在模板中使用的一个输出解析器。在此例中,我们并没有选择在模板中使用输出解析器,而是在模型外部进行输出解析,所以这里是 None
  3. partial_variables:包含了你想要在模板中使用,但在生成模板时无法立即提供的变量。在这里,我们通过 'format_instructions' 传入输出格式的详细说明。
  4. template:这是模板字符串本身。它包含了你想要模型生成的文本的结构。在此例中,模板字符串是你询问鲜花描述的问题,以及关于输出格式的说明。
  5. template_format='f-string':这是一个表示模板字符串格式的选项。此处是f-string格式。
  6. validate_template=True:表示是否在创建模板时检查模板的有效性。这里选择了在创建模板时进行检查,以确保模板是有效的。

总的来说,这个提示模板是一个用于生成模型输入的工具。你可以在模板中定义需要的输入变量,以及模板字符串的格式和结构,然后使用这个模板来为每种鲜花生成一个描述。

第五步:生成提示,传入模型并解析输出

这部分是程序的主体,循环来处理所有的花和它们的价格。对于每种花,都根据提示模板创建了输入,然后获取模型的输出。然后使用之前创建的解析器来解析这个输出,并将解析后的输出添加到DataFrame中。最后,打印出了所有的结果,并且可以选择将其保存到CSV文件中。

# ------Part 5
for flower, price in zip(flowers, prices):
    # 根据提示准备模型的输入
    input = prompt.format(flower=flower, price=price)
    # 打印提示
    print("提示:", input)

    # 获取模型的输出
    output = model(input)

    # 解析模型的输出
    parsed_output = output_parser.parse(output)
    parsed_output_dict = parsed_output.dict()  # 将Pydantic格式转换为字典

    # 将解析后的输出添加到DataFrame中
    df.loc[len(df)] = parsed_output.dict()

# 打印字典
print("输出的数据:", df.to_dict(orient='records'))

这一步中,使用模型和输入提示(由鲜花种类和价格组成)生成了一个具体鲜花的文案需求(同时带有格式描述),然后传递给大模型,也就是说,提示模板中的 flower 和 price,此时都被具体的花取代了,而且模板中的 {format_instructions},也被替换成了 JSON Schema 中指明的格式信息。

具体来说,输出的一个提示是这样的:

提示: 您是一位专业的鲜花店文案撰写员。 对于售价为 20 元的 康乃馨 ,您能提供一个吸引人的简短中文描述吗?

The output should be formatted as a JSON instance that conforms to the JSON schema below.

As an example, for the schema {"properties": {"foo": {"title": "Foo", "description": "a list of strings", "type": "array", "items": {"type": "string"}}}, "required": ["foo"]}}

the object {"foo": ["bar", "baz"]} is a well-formatted instance of the schema. The object {"properties": {"foo": ["bar", "baz"]}} is not well-formatted.

Here is the output schema:

json
复制代码
AI练中学
{"properties": {"flower_type": {"title": "Flower Type", "description": "\u9c9c\u82b1\u7684\u79cd\u7c7b", "type": "string"}, "price": {"title": "Price", "description": "\u9c9c\u82b1\u7684\u4ef7\u683c", "type": "integer"}, "description": {"title": "Description", "description": "\u9c9c\u82b1\u7684\u63cf\u8ff0\u6587\u6848", "type": "string"}, "reason": {"title": "Reason", "description": "\u4e3a\u4ec0\u4e48\u8981\u8fd9\u6837\u5199\u8fd9\u4e2a\u6587\u6848", "type": "string"}}, "required": ["flower_type", "price", "description", "reason"]}

下面,程序解析模型的输出。在这一步中,使用之前定义的输出解析器(output_parser)将模型的输出解析成了一个FlowerDescription的实例。FlowerDescription是之前定义的一个Pydantic类,它包含了鲜花的类型、价格、描述以及描述的理由。

然后,将解析后的输出添加到DataFrame中。在这一步中,将解析后的输出(即FlowerDescription实例)转换为一个字典,并将这个字典添加到你的DataFrame中。这个DataFrame是用来存储所有鲜花描述的。

模型的最后输出如下:

输出的数据: 
[{'flower_type': 'Rose', 'price': 50, 'description': '玫瑰是最浪漫的花,它具有柔和的粉红色,有着浓浓的爱意,价格实惠,50元就可以拥有一束玫瑰。', 'reason': '玫瑰代表着爱情,是最浪漫的礼物,以实惠的价格,可以让您尽情体验爱的浪漫。'}, {'flower_type': '百合', 'price': 30, 'description': '这支百合,柔美的花蕾,在你的手中摇曳,仿佛在与你深情的交谈', 'reason': '营造浪漫氛围'}, {'flower_type': 'Carnation', 'price': 20, 'description': '艳丽缤纷的康乃馨,带给你温馨、浪漫的气氛,是最佳的礼物选择!', 'reason': '康乃馨是一种颜色鲜艳、芬芳淡雅、具有浪漫寓意的鲜花,非常适合作为礼物,而且20元的价格比较实惠。'}]

因此,Pydantic的优点就是容易解析,而解析之后的字典格式的列表在进行数据分析、处理和存储时非常方便。每个字典代表一条记录,它的键( 即 "flower_type""price""description" 和 "reason")是字段名称,对应的值是这个字段的内容。这样一来,每个字段都对应一列,每个字典就是一行,适合以DataFrame的形式来表示和处理。