结构化输出:JSON Mode与Pydantic解析

核心知识点讲解

什么是结构化输出?

结构化输出是指让大语言模型生成符合特定格式的输出,如JSON、XML、CSV等。与自由文本输出不同,结构化输出具有明确的格式要求和数据结构,使得输出结果更加规范、一致和易于处理。

结构化输出的优势

  • 便于程序自动处理和解析
  • 减少格式错误和歧义
  • 提高数据的一致性和可靠性
  • 简化下游应用的开发
  • 便于验证输出的正确性

为什么需要结构化输出?

在构建AI智能体时,结构化输出尤为重要,因为:

  1. 数据交换需求:智能体需要与其他系统或服务交换数据
  2. 自动化处理:输出结果需要被程序自动处理,而不是人工阅读
  3. 多步骤流程:智能体的输出可能作为后续步骤的输入
  4. 数据验证:需要确保输出数据的完整性和正确性
  5. 用户体验:结构化输出可以提供更一致、更可预测的用户体验

JSON Mode 简介

JSON Mode是OpenAI API提供的一种功能,它允许模型生成纯JSON格式的输出。当启用JSON Mode时,模型会确保其输出是有效的JSON格式,避免生成任何额外的文本或解释。

JSON Mode的特点

  • 强制模型生成有效的JSON
  • 不允许生成任何非JSON内容
  • 需要在提示词中明确要求JSON输出
  • 适用于需要严格格式的场景

Pydantic 简介

Pydantic是一个Python库,用于数据验证和设置管理。它使用Python类型提示来定义数据模型,并自动验证输入数据是否符合模型定义。在大语言模型的语境中,Pydantic常用于解析和验证模型的输出。

Pydantic的特点

  • 使用类型提示定义数据模型
  • 自动验证数据类型和约束
  • 提供详细的错误信息
  • 支持嵌套模型和复杂数据结构
  • 与Python生态系统无缝集成

结构化输出的实现方法

实现结构化输出主要有以下几种方法:

  1. 提示词约束:在提示词中明确要求特定格式的输出
  2. JSON Mode:使用OpenAI API的JSON Mode功能
  3. Pydantic解析器:使用Pydantic库解析和验证输出
  4. 模板填充:提供输出模板,让模型填充具体内容
  5. 正则表达式:使用正则表达式提取结构化信息

实用案例分析

案例一:生成产品信息

场景描述
你需要构建一个智能体,用于从产品描述中提取关键信息,并以JSON格式输出。

传统方法
让模型自由生成文本,然后手动解析或使用正则表达式提取信息。

结构化输出方法

# 使用JSON Mode
prompt = """请从以下产品描述中提取关键信息,并以JSON格式输出:

产品描述:
Apple iPhone 15 Pro Max,6.7英寸Super Retina XDR显示屏,A17 Pro芯片,钛金属机身,256GB存储容量,深空黑色。

JSON格式应包含:品牌、型号、屏幕尺寸、芯片、机身材质、存储容量、颜色。"""

# 调用API时启用JSON Mode
response = openai.ChatCompletion.create(
    model="gpt-3.5-turbo",
    messages=[{"role": "user", "content": prompt}],
    response_format={"type": "json_object"}  # 启用JSON Mode
)

应用效果

  • 模型生成的输出是有效的JSON格式
  • 信息提取更加准确和完整
  • 下游应用可以直接解析和使用输出结果

案例二:情感分析与实体识别

场景描述
你需要构建一个智能体,用于分析用户评论的情感倾向,并识别评论中提到的产品实体。

传统方法
分别进行情感分析和实体识别,然后手动整合结果。

结构化输出方法

# 使用Pydantic定义数据模型
from pydantic import BaseModel
from typing import List

class Entity(BaseModel):
    text: str
    type: str

class SentimentAnalysisResult(BaseModel):
    sentiment: str  # positive, negative, neutral
    score: float    # 0-1
    entities: List[Entity]
    summary: str

# 定义提示词
prompt = """请分析以下用户评论的情感倾向,并识别评论中提到的产品实体:

评论:这款iPhone 15 Pro Max的相机表现非常出色,尤其是在低光环境下,但是价格有点贵。

请以JSON格式输出分析结果,包含:情感倾向(positive/negative/neutral)、情感得分(0-1)、识别的实体(文本和类型)、以及简短总结。"""

# 调用API
response = openai.ChatCompletion.create(
    model="gpt-3.5-turbo",
    messages=[{"role": "user", "content": prompt}],
    response_format={"type": "json_object"}
)

# 解析和验证结果
result = SentimentAnalysisResult.parse_raw(response.choices[0].message.content)

应用效果

  • 情感分析和实体识别结果整合在一个结构化输出中
  • 输出结果经过自动验证,确保格式正确
  • 可以直接访问和使用结构化数据的各个字段

案例三:旅行计划生成

场景描述
你需要构建一个智能体,用于根据用户的需求生成旅行计划。

传统方法
让模型生成自由文本形式的旅行计划。

结构化输出方法

# 使用Pydantic定义数据模型
from pydantic import BaseModel
from typing import List, Optional
from datetime import date

class Activity(BaseModel):
    day: int
    title: str
description: str
    duration: str
    location: str
    cost: Optional[str] = None

class TravelPlan(BaseModel):
    destination: str
    start_date: str
    end_date: str
    duration: int
    activities: List[Activity]
    budget: str
    packing_list: List[str]
    tips: List[str]

# 定义提示词
prompt = """请为以下旅行需求生成详细的旅行计划:

目的地:巴黎
出发日期:2024年6月1日
返回日期:2024年6月5日
预算:中等
兴趣:艺术、美食、历史

请以JSON格式输出旅行计划,包含:目的地、出发日期、返回日期、行程天数、每天的活动安排(包括标题、描述、时长、地点、费用)、总预算、 packing清单、以及旅行建议。"""

# 调用API
response = openai.ChatCompletion.create(
    model="gpt-3.5-turbo",
    messages=[{"role": "user", "content": prompt}],
    response_format={"type": "json_object"}
)

# 解析和验证结果
plan = TravelPlan.parse_raw(response.choices[0].message.content)

应用效果

  • 旅行计划以结构化形式呈现,包含所有必要信息
  • 可以轻松访问和处理计划的各个部分
  • 输出结果格式一致,便于与其他系统集成

代码示例

示例1:使用JSON Mode生成结构化输出

import openai
import json

# 设置API密钥
openai.api_key = "YOUR_API_KEY"

# 定义提示词
prompt = """请从以下产品描述中提取关键信息,并以JSON格式输出:

产品描述:
Apple iPhone 15 Pro Max,6.7英寸Super Retina XDR显示屏,A17 Pro芯片,钛金属机身,256GB存储容量,深空黑色。

JSON格式应包含:品牌、型号、屏幕尺寸、芯片、机身材质、存储容量、颜色。"""

# 调用API,启用JSON Mode
try:
    response = openai.ChatCompletion.create(
        model="gpt-3.5-turbo",
        messages=[{"role": "user", "content": prompt}],
        response_format={"type": "json_object"}  # 启用JSON Mode
    )
    
    # 提取和打印结果
    product_info = response.choices[0].message.content
    print("原始输出:")
    print(product_info)
    
    # 解析JSON
    product_data = json.loads(product_info)
    print("\n解析后的数据:")
    print(f"品牌: {product_data.get('品牌')}")
    print(f"型号: {product_data.get('型号')}")
    print(f"屏幕尺寸: {product_data.get('屏幕尺寸')}")
    print(f"芯片: {product_data.get('芯片')}")
    print(f"机身材质: {product_data.get('机身材质')}")
    print(f"存储容量: {product_data.get('存储容量')}")
    print(f"颜色: {product_data.get('颜色')}")
    
except Exception as e:
    print(f"错误: {e}")

示例2:使用Pydantic解析和验证输出

import openai
from pydantic import BaseModel, Field
from typing import List, Optional

# 设置API密钥
openai.api_key = "YOUR_API_KEY"

# 使用Pydantic定义数据模型
class Item(BaseModel):
    name: str
    quantity: int
    price: float
    category: str

class ShoppingList(BaseModel):
    title: str
    items: List[Item]
    total_items: int
    estimated_total: float
    store_recommendations: List[str]

# 定义提示词
prompt = """请根据以下需求生成购物清单:

我需要为周末的烧烤聚会准备食材,大约有10人参加。我需要购买肉类、蔬菜、饮料和调味料。

请以JSON格式输出购物清单,包含:标题、物品列表(每个物品包含名称、数量、价格、类别)、物品总数、估计总费用、以及推荐的购买地点。"""

# 调用API
try:
    response = openai.ChatCompletion.create(
        model="gpt-3.5-turbo",
        messages=[{"role": "user", "content": prompt}],
        response_format={"type": "json_object"}
    )
    
    # 提取结果
    json_output = response.choices[0].message.content
    print("原始JSON输出:")
    print(json_output)
    
    # 使用Pydantic解析和验证
    shopping_list = ShoppingList.parse_raw(json_output)
    
    print("\n解析和验证后的购物清单:")
    print(f"标题: {shopping_list.title}")
    print(f"物品总数: {shopping_list.total_items}")
    print(f"估计总费用: ${shopping_list.estimated_total:.2f}")
    
    print("\n物品列表:")
    for item in shopping_list.items:
        print(f"- {item.name} (数量: {item.quantity}, 价格: ${item.price:.2f}, 类别: {item.category})")
    
    print("\n推荐购买地点:")
    for store in shopping_list.store_recommendations:
        print(f"- {store}")
        
except Exception as e:
    print(f"错误: {e}")

示例3:使用LangChain的输出解析器

from langchain import LLMChain, PromptTemplate
from langchain_openai import ChatOpenAI
from langchain.output_parsers import PydanticOutputParser
from pydantic import BaseModel, Field
from typing import List

# 设置API密钥
import os
os.environ["OPENAI_API_KEY"] = "YOUR_API_KEY"

# 使用Pydantic定义数据模型
class MovieRecommendation(BaseModel):
    title: str = Field(description="电影标题")
    director: str = Field(description="导演")
    year: int = Field(description="上映年份")
    genre: str = Field(description="电影类型")
    rating: float = Field(description="评分,1-10分")
    summary: str = Field(description="电影简介")
    reasons: List[str] = Field(description="推荐理由")

# 创建输出解析器
parser = PydanticOutputParser(pydantic_object=MovieRecommendation)

# 定义提示词模板
prompt_template = PromptTemplate(
    input_variables=["genre", "actor"],
    template="""请推荐一部{genre}类型的电影,由{actor}主演。

{format_instructions}

请提供电影的标题、导演、上映年份、类型、评分、简介以及推荐理由。""",
    partial_variables={"format_instructions": parser.get_format_instructions()}
)

# 初始化LLM
llm = ChatOpenAI(temperature=0.7, model_name="gpt-3.5-turbo")

# 创建LLMChain
chain = LLMChain(llm=llm, prompt=prompt_template)

# 运行链
try:
    result = chain.run(genre="科幻", actor="汤姆·克鲁斯")
    print("原始输出:")
    print(result)
    
    # 解析输出
    movie = parser.parse(result)
    print("\n解析后的电影推荐:")
    print(f"标题: {movie.title}")
    print(f"导演: {movie.director}")
    print(f"年份: {movie.year}")
    print(f"类型: {movie.genre}")
    print(f"评分: {movie.rating}")
    print(f"简介: {movie.summary}")
    print("推荐理由:")
    for reason in movie.reasons:
        print(f"- {reason}")
        
except Exception as e:
    print(f"错误: {e}")

示例4:处理复杂的嵌套结构

import openai
from pydantic import BaseModel
from typing import List, Optional

# 设置API密钥
openai.api_key = "YOUR_API_KEY"

# 使用Pydantic定义嵌套数据模型
class Question(BaseModel):
    text: str
    options: List[str]
    correct_answer: str
    explanation: Optional[str] = None

class Quiz(BaseModel):
    title: str
    topic: str
    difficulty: str  # easy, medium, hard
    questions: List[Question]
    total_questions: int

# 定义提示词
prompt = """请生成一个关于人工智能基础知识的测验,包含5道多选题。

请以JSON格式输出测验,包含:标题、主题、难度、问题列表(每个问题包含题目、选项、正确答案、解释)、以及总问题数。"""

# 调用API
try:
    response = openai.ChatCompletion.create(
        model="gpt-3.5-turbo",
        messages=[{"role": "user", "content": prompt}],
        response_format={"type": "json_object"}
    )
    
    # 解析和验证结果
    quiz_data = response.choices[0].message.content
    quiz = Quiz.parse_raw(quiz_data)
    
    print(f"测验标题: {quiz.title}")
    print(f"主题: {quiz.topic}")
    print(f"难度: {quiz.difficulty}")
    print(f"总问题数: {quiz.total_questions}\n")
    
    # 打印问题
    for i, question in enumerate(quiz.questions, 1):
        print(f"问题 {i}: {question.text}")
        print("选项:")
        for j, option in enumerate(question.options, 1):
            print(f"{j}. {option}")
        print(f"正确答案: {question.correct_answer}")
        if question.explanation:
            print(f"解释: {question.explanation}")
        print()
        
except Exception as e:
    print(f"错误: {e}")

总结与思考

关键要点回顾

  1. 结构化输出的概念:让大语言模型生成符合特定格式的输出,如JSON、XML等。

  2. 结构化输出的优势:便于程序自动处理,减少格式错误,提高数据一致性,简化下游应用开发。

  3. JSON Mode:OpenAI API提供的功能,强制模型生成有效的JSON格式输出。

  4. Pydantic:Python库,用于数据验证和设置管理,可用于解析和验证模型的输出。

  5. 实现方法:提示词约束、JSON Mode、Pydantic解析器、模板填充、正则表达式。

实践建议

  • 明确格式要求:在提示词中明确指定所需的输出格式和结构。

  • 使用JSON Mode:对于需要JSON输出的场景,启用OpenAI API的JSON Mode功能。

  • 定义数据模型:使用Pydantic等库定义清晰的数据模型,确保输出数据的结构正确。

  • 添加格式指令:在提示词中添加格式指令,帮助模型理解输出要求。

  • 处理错误情况:实现错误处理机制,应对模型输出不符合预期格式的情况。

  • 验证输出结果:使用Pydantic等库验证输出结果的正确性和完整性。

  • 逐步复杂:从简单的结构开始,逐步过渡到更复杂的嵌套结构。

未来学习方向

  • 多模态结构化输出:探索如何在多模态场景中实现结构化输出。

  • 动态结构生成:研究如何根据不同场景动态生成输出结构。

  • 输出质量评估:开发评估结构化输出质量的方法和指标。

  • 自动修复机制:实现自动检测和修复输出格式错误的机制。

  • 与工具调用集成:探索如何将结构化输出与工具调用相结合,实现更复杂的智能体功能。

  • 性能优化:研究如何优化结构化输出的生成速度和准确性。

结构化输出是构建可靠、高效AI智能体的重要技术。通过掌握JSON Mode和Pydantic解析等技术,你将能够让智能体生成更加规范、一致和易于处理的输出,从而构建更加健壮的AI应用。在接下来的课程中,我们将继续探索更多高级技术,帮助你进一步提升智能体的能力。

« 上一篇 少样本学习(Few-Shot)与上下文学习(In-Context Learning) 下一篇 » 复杂任务分解:思维树(Tree of Thoughts)入门