DLAI-大模型格式化输出笔记-全-
DLAI 大模型格式化输出笔记(全)
001:课程介绍


欢迎来到《获取结构化大语言模型输出》课程,本课程是与dot text合作开发的。
在本课程中,我们将学习如何从大语言模型获取格式化的、机器可读的输出,这对于构建软件应用至关重要。
为什么需要结构化输出?
在典型的聊天界面中使用大语言模型时,非结构化的文本输出是可以接受的。
但如果你正在构建软件,依赖自由格式的文本输出会变得非常困难。
这就是结构化输出(例如JSON)变得非常重要的地方。
它们提供了一种清晰的格式,机器可以读取和处理这种格式。
例如,如果你希望你的大语言模型查看产品评论并生成几个字段,比如产品名称和情感是积极还是消极,那么以JSON格式输出这两个字段可以让下游软件轻松读取和处理这些字段。
课程内容概述
在本课程中,你将学习如何使用几种方法获取结构化输出。
基本思路是,你可以告诉大语言模型你希望数据以特定格式输出,并且你需要清晰地提供这种格式,具体方法是使用计算机科学家所称的“正则表达式”。
这将是一种高效的方式,能让大语言模型可靠地以该格式生成输出,因为正则表达式可以用来强制约束下一个要生成的令牌,每次一个令牌。
我很高兴向大家介绍本课程的讲师沃克特和卡伦·普法威尔。沃克特是dot text的创始工程师,卡梅伦是开发者关系工程师,他们将帮助像你一样的开发者将可靠的结构化格式构建并集成到你的大语言模型应用中。谢谢安德鲁。
你将学到什么
在本课程中,你将首先从支持结构化响应的模型中获取结构化输出。
你还会了解到这种方法的局限性。
为了解决其中一些局限性,我们将使用Instructor,这是一个开源库,它会重新提示模型,直到生成有效的JSON结构。
你还将学习约束解码的工作原理。这是本课程中将使用的开源库Outlines背后的核心概念。
在学习本课程所有概念的同时,你将处理几个很酷的示例和用例。
包括一个社交媒体分析智能体。该智能体读取用户帖子,识别情感,并决定是否需要回复。
如果存在问题,它甚至可以生成客户支持工单。所有输出都以JSON格式呈现。
在使用基于重试的方法后,你将学习如何从一开始就使用Outlines获取结构化输出。
Outlines是一个在令牌级别约束模型输出的库。通过拦截逻辑值(即模型分配给下一个令牌的概率),Outlines会阻止任何不符合你定义的模式或格式的令牌。
这保证了有效的输出,无需任何重试。
在最后一课中,你将学习如何生成超出普通JSON的格式。
例如,你将学习如何生成有效的电话号码、电子邮件地址,甚至是ASCII艺术棋盘。
基本上,任何你可以用正则表达式表达的格式都可以。
致谢
许多人为创建本课程付出了努力。我要感谢来自dot text的雷米·卢夫和安德烈·佩索,来自DeepLearning.AI的埃布·加加里也为本课程做出了贡献。
第一课将是结构化输出生成的介绍。这听起来很棒。
让我们进入下一个视频,开始获取结构化输出。

总结

本节课我们一起学习了获取大语言模型结构化输出的重要性、课程的整体目标以及你将掌握的核心技能。从下一节开始,我们将深入实践,探索具体的方法和工具。
002:结构化输出生成简介 🧠
在本节课中,我们将学习什么是结构化输出、它们为何重要,以及生成结构化输出的不同方法。你将了解结构化输出如何让我们能够基于大语言模型进行可扩展的软件开发,并理解从“提示词工程”迈向真正“AI工程”的路径。
什么是结构化输出?
当我们使用大语言模型时,其输出通常是自由形式的文本,没有特定的结构。结构化输出意味着模型的输出遵循某种预定义的结构,例如 JSON。JSON 是一种非常常见的结构化输出格式,但并非唯一。

为什么需要结构化输出?
如果你长期使用大语言模型,可能非常熟悉常见的聊天界面。在这种格式下,模型像人类助手一样与我们互动。我们提出问题,模型给出回答。作为人类,我们很容易解析和理解这些回答。
然而,在编程环境中使用大语言模型时,情况就不同了。我们使用一种称为“指令接口”的类似界面,向模型发送请求并获取响应。虽然我们能在代码中与模型交互,但如何以编程方式解析模型返回的信息呢?作为人类这很容易,但程序需要一种可靠的方法来提取数据。
构建系统与可扩展性需求
为了更好地理解结构化输出的必要性及其如何帮助我们构建可扩展的软件,让我们设想构建一个由大语言模型驱动的社交媒体代理。这个代理将自动处理我们在社交媒体上收到的评论并生成回复。


一个简单的应用流程是:模型接收提示词和用户消息,生成回复,然后通过社交媒体 API 发布。但问题在于:我们如何处理这些原始的、非结构化的文本数据?

一种初级的解决方案是添加一个解析层,编写一些简单的规则来提取答案。不幸的是,这种方法非常耗时且容易出错。手动编写解析器很容易遗漏细节或遇到意料之外的输出格式,导致难以正确实现。更重要的是,它难以扩展。
例如,如果产品经理要求修改代理:当用户消息是投诉时,应将其转给客户支持,而不是自动回复。使用初级解析方法,我们需要同时解析“是否为投诉”和“回复内容”两个信息,代码会变得非常混乱。这显然不是构建可扩展软件的方法。
结构化输出的优势
如果模型的输出是结构化的、可预测的 JSON,实现上述功能就变得非常简单。我们可以定义一个包含 complaint(布尔值)和 response(字符串)字段的 JSON 结构。
{
"complaint": true,
"response": "用户的具体投诉内容..."
}
程序可以像检查任何其他 JSON 对象一样检查 complaint 字段。如果为 true,则发送给客户支持;如果为 false,则将 response 字段的内容传递给社交媒体 API。通过 JSON 提取数据对 API 来说轻而易举。
如何获取结构化输出?


有多种方法可以获取结构化输出。
1. 使用专有API
最直接的方法是使用你已经在使用的主流模型提供商(如 OpenAI、Anthropic)的专有 API。每个提供商都提供了不同的结构化输出解决方案。
- 基于逻辑的消息或约束解码:这种方法会修改模型本身,使其只生成符合结构要求的令牌。
- 函数调用或工具使用:向模型提供一系列它可以调用的函数列表,其响应通常以 JSON 格式返回。
- JSON 模式:对模型进行微调,使其在被提示时返回 JSON。
- 其他“黑箱”方法:由于专有 API 的内部机制不透明,可能还有其他我们不了解的方法。

使用专有API的优缺点:
- 优点:
- 从非结构化文本到 JSON 是一个巨大的飞跃,有助于创建可靠的系统。
- 如果你已经在使用某个主流提供商,集成起来相对容易。
- 提供商持续改进其技术,输出质量在提升。
- 缺点:
- 你的代码与特定模型提供商深度绑定,更换提供商可能意味着重大重构。
- 结果可能不一致,某些提供商不一定总能返回预期的结构。
- 可能对输出质量有不明影响(某些评估显示结构化输出可能损害特定任务的性能)。
- 可用的结构类型有限,通常只支持 JSON 子集,难以强制执行复杂约束(如特定日期格式)。
2. 使用重提示库
为了解决专有API的局限性,出现了“重提示”库,如 Instructor 和 LangChain。它们被设计为可与任何主流大语言模型配合工作。
工作原理:
- 向大语言模型发送常规请求(提示词)。
- 同时提供一个“验证器”,描述我们期望的 JSON 结构。
- 如果模型输出符合验证器,则直接返回数据。
- 如果输出解析失败(例如,格式错误),重提示库会自动将失败原因作为额外信息添加到原始提示词中,然后重新尝试请求。

重提示库的优缺点:
- 优点:
- 支持任何大语言模型 API,提高了代码的可复用性和可移植性。
- 在结构定义上比大多数专有提供商更灵活,允许使用正则表达式等对字段施加约束(如验证电子邮件格式)。
- 缺点:
- 重试可能带来额外的成本和延迟,影响用户体验。
- 不保证绝对成功,如果多次重试后仍失败,库会报错。
- 输出结构仍然主要局限于 JSON。
3. 结构化生成(约束解码)
结构化生成,也称为约束解码,是一种直接与模型交互以获得所需输出的方法。我们使用“结构化生成”这个术语,是因为我们实际上是在控制令牌的生成过程,以确保最终输出符合我们的结构。
支持此功能的库包括 Outlines、SGLang、Microsoft Guidance 和 X-Grammar。
工作原理简述:
大语言模型通过迭代预测下一个令牌来生成文本。每一步,模型都会输出一个“逻辑值”分布,表示每个可能令牌的概率。结构化生成会在每一步修改这个逻辑值分布,移除不符合我们预定结构规则的无效令牌,并重新归一化剩余有效令牌的概率,然后从中采样。这个过程持续进行,直到生成一个完全符合结构规则的字符串。
结构化生成的优缺点:
- 优点:
- 适用于任何开源大语言模型(如 Hugging Face 上的模型)。
- 速度极快,推理成本基本为零,甚至有研究显示可以利用结构来跳过某些令牌,从而加速推理。
- 通常能提供更高质量的输出,在多项评估中表现优于非结构化生成。
- 非常适合资源受限的环境(如小型设备),因为它轻量高效。
- 能生成极其广泛的结构,不仅是 JSON,还包括正则表达式、语法正确的代码等。
- 缺点:
- 需要直接访问模型的逻辑值,这意味着你必须使用开源模型,或者自行托管并控制推理过程的专有模型。

从提示词工程到AI工程
我们可以将上述方法视为从“提示词工程”迈向真正“AI工程”的旅程。
- 提示词工程:仅使用基于聊天的界面,通过不断调整发送给模型的信息并手动解析结果来迭代,这不可扩展,也难称得上是真正的软件工程。
- 专有JSON API:开启了新可能,我们可以开始编写真正的软件,获得可预测的响应,并将大语言模型代码集成到软件的其他部分。但代码与单一提供商深度绑定。
- 重提示库:解决了供应商锁定的问题,允许我们创建可复用的软件库,兼容多种大语言模型提供商。这是构建可扩展大语言模型软件的一大步,但代码与模型之间仍存在隔阂,依赖于有限的重提示技巧。
- 结构化生成:代表了真正的AI工程。我们直接与模型协作,精确获取所需结果。编写的代码易于理解、扩展和改进。

总结 🎯
在本节课中,我们一起学习了:
- 结构化输出的基本概念:即模型输出遵循预定义格式(如JSON)。
- 其重要性:它们是构建可扩展、可靠的大语言模型应用的关键,使程序能够可靠地解析和使用模型输出。
- 三种主要实现方法:
- 利用模型提供商的专有API(易用但可能被绑定)。
- 使用重提示库(如Instructor,提高可移植性和灵活性)。
- 采用结构化生成/约束解码(直接控制模型生成过程,高效、灵活且质量高,但需使用开源或自托管模型)。

这些技术共同使我们能够利用大语言模型创建真正强大和可维护的软件。在下一节课中,你将通过使用 OpenAI 的结构化输出 API 构建一个社交媒体代理,来亲眼看看这一切是如何运作的。敬请期待,下节课见!
003:如何使用结构化输出


在本节课中,我们将学习如何使用OpenAI的结构化输出API构建一个简单的社交媒体管理助手。同时,我们还将学习使用Pydantic库来指定期望的输出结构的基础知识。

概述:什么是结构化输出与JSON Schema
结构化输出通常使用一种称为JSON Schema的标准来强制规定输出的结构。JSON Schema是一种描述JSON消息形状的标准。这些Schema提供了结构化输出所需的类型信息。
例如,以下是一个包含Dolly Parton姓名、年龄和邮箱的JSON消息:
{
"name": "Dolly Parton",
"age": 79,
"email": "dolly@parton.com"
}
我们可以看到,左边是键(如name、age、email),右边是它们的值。

让我们看一个描述“人”的JSON Schema示例。它同样是JSON格式,包含了所有键及其类型定义。例如,"title": "name"和"type": "string"。
为什么使用Pydantic
手动维护JSON Schema既困难又容易出错。对于任何非简单的结构,处理起来都非常麻烦。因此,AI工程师经常使用Pydantic来描述模型的输出结构。
Pydantic是一个开源的数据验证库,你可以灵活地定义应用程序中使用的数据结构。它功能丰富,建议查阅其文档以了解所有支持的功能。使用Pydantic可以使语言模型的输出变得可编程,我们马上就会看到这一点。
使用Pydantic定义结构
以下是如何在Pydantic中定义一个User类:
from pydantic import BaseModel
from typing import Optional
class User(BaseModel):
name: str
age: int
email: Optional[str] = None
继承BaseModel是使User成为Pydantic类的全部要求。接下来,使用类型注解来定义模型应生成的结构。例如,name可以是任何字符串,age可以是任何整数,email可以是空值或字符串。但请注意,OpenAI不允许你强制邮箱的格式(例如,必须是test@co.com的形式),而Instructor等工具可以做到。
构建社交媒体客户支持助手
假设我们有很多用户在推特上@我们,例如:“@techcorp,你们的应用太棒了,新设计完美!”我们将把这个输入到我们的客户支持助手中,然后输出一些结构化数据,告诉我们这是关于哪个产品、用户的情感、我们是否需要回复,以及如果出现问题需要创建什么支持工单。
让我们开始看代码。
首先进行一些准备工作。以下两行代码用于过滤掉学习过程中可能不想看到的警告:
import warnings
warnings.filterwarnings('ignore')
以下是加载我们将要使用的OpenAI API密钥的方式:
import os
from openai import OpenAI
# 假设你的API密钥已通过环境变量配置
client = OpenAI(api_key=os.environ.get("OPENAI_API_KEY"))
使用Pydantic和OpenAI生成对象
让我们看一个如何使用Pydantic定义结构并与OpenAI交互的例子。
我们将从粘贴之前在幻灯片中定义的User类开始。然后,使用OpenAI生成一个用户对象:
completion = client.beta.chat.completions.parse(
model="gpt-4o-mini",
messages=[
{"role": "system", "content": "You are a helpful assistant."},
{"role": "user", "content": "Make up a user."}
],
response_format=User
)
user = completion.choices[0].message.parsed
print(user)
运行后,OpenAI会返回一个用户对象,例如:name='John Doe', age=30, email='john.doe@example.com'。
定义社交媒体分析的结构
在使用结构化输出时,通常在编写其余代码之前,先定义你希望看到的结构。
在本例中,我们添加了一个Mention类:
from pydantic import BaseModel
from typing import Optional, Literal
class Mention(BaseModel):
product: Literal["app", "website", "not applicable"]
sentiment: Literal["positive", "negative", "neutral"]
needs_response: bool
response: Optional[str] = None
support_ticket: Optional[str] = None
product和sentiment使用了Literal类型,这意味着语言模型必须在给定的选项中选择。needs_response可以是True或False。response和support_ticket是可选字符串。
创建分析函数
接下来,我们创建一个analyze_mention函数来处理用户消息并生成Mention对象:
def analyze_mention(post: str, personality: str = "friendly"):
completion = client.beta.chat.completions.parse(
model="gpt-4o-mini",
messages=[
{
"role": "system",
"content": f"""
You are a social media manager with a {personality} personality.
Analyze the user's mention.
Provide the product mentioned, the sentiment, whether to respond,
a custom response message, and an optional support ticket description.
Do not respond to purely informational messages or bait.
"""
},
{"role": "user", "content": post}
],
response_format=Mention
)
return completion.choices[0].message.parsed
使用分析函数
让我们提供一些示例数据并测试我们的函数:
mentions = [
"@techcorp your app is so amazing. the new design is perfect!",
"@techcorp your website is down. please fix it!",
"@techcorp you're so evil."
]
# 分析第一条提及(正面评价)
processed_mention = analyze_mention(mentions[0])
print(processed_mention)
输出会显示产品是app,情感是positive,needs_response为True,并包含一个回复文本,例如:“We're thrilled to hear that you love the new design!” 由于没有明显问题,support_ticket为None。
调整模型个性
你可以通过改变personality参数来调整模型的回复风格。例如,将其设置为"rude":
rude_response = analyze_mention(mentions[0], personality="rude")
print(rude_response.response)
输出可能是:“Thanks for the praise. We're glad you love the new design, but don't get too comfy, we're constantly improving.”
查看原始JSON输出
出于好奇,我们可以查看语言模型在解析成Pydantic对象之前生成的原始JSON内容:
print(processed_mention.model_dump_json(indent=2))
扩展练习:添加更多字段
你可以尝试扩展这个结构。例如,创建一个包含更多字段的UserPostWithExtras类:
from typing import List
class UserPostWithExtras(BaseModel):
user_mood: Literal["awful", "bad", "evil"]
product: Literal["app", "website", "not applicable"]
internal_monologue: List[str]
message: str
然后,你可以让模型生成这样的帖子,再将其输入回analyze_mention函数进行分析。
编程化处理输出
结构化输出的目的是将语言模型的响应转化为可编程的数据。以下是一个如何编程化处理输出的例子:
rows = []
for user_message in mentions:
# 调用LLM获取可编程的Mention对象
processed = analyze_mention(user_message)
# 基于分析结果编程
if processed.needs_response:
print(f"Responding to user: {user_message}")
print(f"Sentiment: {processed.sentiment}")
print(f"Our response: {processed.response}")
if processed.support_ticket:
print(f"Creating support ticket: {processed.support_ticket}")
# 转换为字典并存储
row_dict = processed.model_dump()
row_dict["original_message"] = user_message
rows.append(row_dict)
print() # 打印空行分隔
转换为DataFrame
使用结构化输出的一个优势是,你知道它遵循确切的格式,这意味着你可以轻松地将其转换为其他数据格式,例如Pandas DataFrame:
import pandas as pd
df = pd.DataFrame(rows)
print(df)
DataFrame将包含product、sentiment、needs_response、response、support_ticket和original_message等列,便于进一步分析和处理。
总结
本节课中,我们一起学习了:
- 如何使用Pydantic定义模型的输出结构。
- 如何使用OpenAI的结构化输出API来获取格式化的响应。
- 如何对语言模型的输出进行基本的编程化处理,例如条件判断和数据转换。
通过将非结构化的文本转换为结构化的数据,我们可以更可靠、更高效地将大语言模型集成到我们的应用程序和工作流中。


004:基于重试的结构化输出


在本节课中,你将学习如何使用开源库 instructor,从那些不支持正式结构化输出的模型提供商那里生成结构化输出。
概述
我们将首先介绍 instructor 库的基本原理和优缺点。然后,我们将通过代码演示如何使用 instructor 从 Together AI 获取结构化输出。最后,我们会探讨 instructor 的一些局限性,特别是关于重试机制和成本的问题。
认识 Instructor 🧠

instructor 是一个基于重新提示(reprompting)的开源结构化输出库。它的核心工作原理是:如果模型的输出不符合我们请求的格式,我们就尝试再次生成。
我们可以这样理解其流程:我们的提示词输入到语言模型中,模型产生某种形式的输出。然后,我们检查该输出是否是有效的语法。如果是,就将其返回给用户;如果不是,我们将输出和错误信息反馈给语言模型,并尝试再次获取输出,直到获得有效的输出为止。
Instructor 的优缺点 ⚖️
上一节我们介绍了 instructor 的基本原理,本节中我们来看看它的主要优缺点。
以下是 instructor 的一些优点:
- 简单易用。
- 支持广泛的提供商,如 Anthropic、OpenAI、Together AI、Fireworks AI 等。
- 跨提供商提供一致的 API,有助于避免供应商锁定。
- 支持 Pydantic 的所有功能。
以下是 instructor 的一些缺点:
- 无法完全强制执行结构,因此解析失败的情况仍会发生。
- 重试可能会意外增加成本和耗时。每次解析失败,都需要将整个提示发送回模型。
- 重试过程不够透明。需要深入其内部机制才能理解具体发生了什么。
- 仅适用于经过指令微调的模型,这些模型需要支持函数调用、工具使用或 JSON 模式。而其他方法(如
outlines)可以使用任何模型,无论是否经过指令微调。
实践:使用 Instructor 获取结构化输出 💻
在本实验中,我们将演示 instructor 的简单用法,从 Together AI(一个不支持正式结构化生成的提供商)获取结构化输出。然后,我们将重点介绍 instructor 的一些局限性,以便你分析它是否适合你的应用。
设置环境与获取非结构化输出
让我们开始编写代码。首先获取我们的 Together API 密钥。如果你在 DeepLearning.AI 平台上,这个密钥将可用;如果你在自己的平台上工作,则需要使用自己的 API 密钥。
以下是连接到 Together AI 服务器的方法。我们将导入 instructor 和 openai。请注意,我们导入的是 openai 而不是其他 Together AI 的 SDK,原因是大多数推理提供商都使用 OpenAI 标准进行语言模型推理。这意味着你只需将基础 URL 更改为指向你的语言模型提供商,就可以使用 OpenAI SDK。
import instructor
from openai import OpenAI
import os
# 设置 API 密钥和基础 URL
client = OpenAI(
api_key=os.environ.get("TOGETHER_API_KEY"),
base_url="https://api.together.xyz/v1",
)
要从 Together AI 生成非结构化文本,你可以使用标准的 OpenAI 工具 chat.completions.create。你需要提供 Together AI 服务的任何模型。这里我们将使用 Llama 3.1 的 80 亿参数指令微调模型。请注意,模型名称中的 “instruct” 与 instructor 库无关,它仅表示该模型已经过指令微调,具有类似聊天机器人的功能。
# 生成非结构化输出
response = client.chat.completions.create(
model="meta-llama/Meta-Llama-3.1-8B-Instruct-Turbo",
messages=[{"role": "user", "content": "Hey there!"}]
)
print(response.choices[0].message.content)
运行上述代码,语言模型可能会回复:“嘿!你今天过得怎么样?我过得还不错,机器人。”
使用 Instructor 获取简单结构化输出
要使用 instructor,你只需用 instructor 的 from_* 方法包装你的客户端。例如,这里我们使用 instructor.from_openai,并传入我们的 Together 客户端。这将创建一个名为 instructor_client 的新客户端,它将使用 instructor 的工具来获取结构化输出。
# 包装客户端以启用 instructor 功能
instructor_client = instructor.from_openai(client)
为了试用 instructor,让我们定义一个非常简单的结构。这里,我们将定义一个 Greeting 类,它只有一个字符串类型的字段 hello。
from pydantic import BaseModel
class Greeting(BaseModel):
hello: str
要使用这个结构,我们只需像之前一样调用 instructor_client.chat.completions.create。我们将传递相同的模型名称和消息,与之前的非结构化版本相比没有任何变化。我们唯一添加的是 response_model=Greeting 参数,这是我们期望接收的类。如果你使用 OpenAI 的结构化输出,请注意它们使用 response_format 作为关键字参数,而不是 instructor 的 response_model,请务必注意这一点。
# 使用 instructor 获取结构化输出
structured_response = instructor_client.chat.completions.create(
model="meta-llama/Meta-Llama-3.1-8B-Instruct-Turbo",
messages=[{"role": "user", "content": "Hey there!"}],
response_model=Greeting
)
print(structured_response)
运行此代码,我们将看到输出为 Greeting 对象,其 hello 字段中包含字符串。
构建日历事件提取器
让我们编写一个简单的日历事件提取器来测试 instructor。我们的目标是向模型提供一个事件的粗略描述,然后返回一个结构化的输出,包含事件名称、发生日期、参与者列表、事件地点以及一些额外的地址信息(如州代码和邮政编码)。
以下是事件数据模型的定义:
from pydantic import BaseModel, Field
from datetime import date
import re
class Person(BaseModel):
name: str
class CalendarEvent(BaseModel):
name: str
date: date # 使用 Python 标准库的 date 类型
participants: list[Person]
address_number: int
street_name: str
city_name: str
state_code: str = Field(pattern=r"^[A-Z]{2}$") # 必须是两个大写字母
zip_code: str = Field(pattern=r"^\d{5}$") # 必须是五位数字
核心概念解析:
date: date:instructor支持 Python 标准类型,比 OpenAI 的结构化输出工具更通用。participants: list[Person]:使用Person类而不仅仅是字符串列表,便于未来扩展(如添加电子邮件字段)。Field(pattern=...):这是 Pydantic 的一个工具,用于指定字段值必须满足的正则表达式模式。例如,state_code必须是两个大写字母(如 “OR” 代表俄勒冈州,“NY” 代表纽约州)。
接下来,我们写一个示例事件描述:
event_description = """
Alice and Bob are going to science fair on Friday.
Science fair is hosted at the gymnasium of the Hazeldale Elementary school.
"""
然后,我们编写一个方便的 generate 函数来调用 instructor 客户端:
def generate(response_model, user_prompt, system_prompt="You are a helpful assistant.", model="meta-llama/Meta-Llama-3.1-8B-Instruct-Turbo", max_retries=3):
return instructor_client.chat.completions.create(
model=model,
messages=[
{"role": "system", "content": system_prompt},
{"role": "user", "content": user_prompt}
],
response_model=response_model,
max_retries=max_retries
)
现在,让我们实际生成一个日历事件:
system_prompt = "Make a calendar event. Respond in JSON with the event name, date, list of participants, and the address."
user_prompt = event_description
event = generate(CalendarEvent, user_prompt, system_prompt)
print(event)
查看我们返回的事件,可以看到它包含了事件名称、日期、参与者列表、地址信息等。值得注意的是,邮政编码被推断为 “97005”,尽管在提示词中并未指定。这是一个通过提供结构从模型中提取额外信息的例子。模型能够猜测出大致正确的邮政编码。如果你切换到 700 亿参数模型,它可能会更准确地推断出正确的邮政编码 “97007”。
你可以通过修改 event_description 来尝试让模型输出其他内容。
理解重试机制与成本 ⚠️
instructor 可能比其他形式的结构化输出使用更多的令牌。当输出验证失败时,instructor 会向模型提供反馈,请求新的输出,直到响应被正确解析为止。
instructor 本身并不容易跟踪重试次数和跨重试使用的令牌数。为了演示这一点,我们可以使用一些工具函数来跟踪重试和令牌使用情况。
# 假设有工具函数 clear_tracker(), get_retries_used(), get_tokens_used()
clear_tracker()
event = generate(CalendarEvent, user_prompt, system_prompt)
print(f"Retries used: {get_retries_used()}")
print(f"Tokens used: {get_tokens_used()}")
对于简单的模式,instructor 可能一次就成功,无需重试。
然而,在模式复杂或提示不充分的情况下,模型输出可能需要多次重试,甚至可能永远无法生成正确的输出。为了看到这一点,让我们定义一个复杂的类,并尝试“破坏” instructor 以演示重试。
class ComplicatedClass(BaseModel):
a: str # 必须是 "cat", "dog" 或 "hat"
b: int
c: bool
# 一个会引发问题的提示组合
bad_user_prompt = "Please write me a short essay about Dolly Parton."
bad_system_prompt = "Don‘t give me what I want."
try:
result = generate(ComplicatedClass, bad_user_prompt, bad_system_prompt, max_retries=3)
except Exception as e:
print(f"Failed to parse after retries: {e}")
print(f"Total retries attempted: 3")
# 检查令牌使用情况会显示消耗了数倍的令牌
在这个例子中,我们既没有向模型传达 ComplicatedClass 的结构,又在系统提示中故意要求它不合作。我们将 instructor 的最大重试次数限制为 3 次。结果很可能是解析失败,我们消耗了大约三倍的令牌,却没有得到正确的输出,还增加了等待时间。
你可以尝试修改用户提示和系统提示来修复这个问题,例如明确要求结构并给出合作的指令。
总结
在本节课中,我们一起学习了如何使用 instructor 库从任何模型提供商那里获取结构化输出。我们了解了它的工作流程、优点,以及需要重点考虑的限制,特别是重试机制可能带来的成本和可靠性问题。
在下一节课中,你将深入了解 outlines 库,看看结构化生成是如何能够快速、可靠地生成结构化输出而无需任何重试的。


005:基于Logits的结构化生成原理与实践

概述
在本节课中,我们将深入探讨基于Logits的结构化生成技术的工作原理。你将学习如何通过操作语言模型的Logits来实现结构化输出,并通过代码示例直观地理解模型如何选择下一个token。
结构化生成的核心优势
上一节我们介绍了结构化生成的基本概念,本节中我们来看看其核心优势。

基于Logits的结构化生成是一种更高效、更灵活的输出结构化方法。它也被称为“约束解码”或“基于Logits的方法”。
以下是其主要优势:
- 直接修改模型输出:这意味着你总能获得定义好的结构。相比之下,使用专有模型的JSON模式或重新提示技术有时会失败,导致无法获得预期的结构。
- 推理时间成本极低:在推理过程中使用结构化生成的时间成本基本为零,非常轻量,你几乎感觉不到它的存在。
- 支持更广泛的结构:你不仅限于使用JSON,可以定义更多样化的输出格式。
然而,使用结构化生成有一个前提需要注意:因为我们需要操作Logits,所以必须能访问到模型的Logits层。这意味着你需要使用开源权重模型,或者你本身就是专有模型提供商。
语言模型如何生成文本
在深入原理之前,我们先快速回顾一下语言模型生成文本的基本流程。
- 你向LLM提供一个提示,该提示被分解为一系列tokens。
- 这些tokens被一次性输入到LLM中。
- LLM产生一组Logits,代表每个可能的下一个token的相对概率。
- 根据这些概率,通过一个采样器选择一个token,并将其附加到提示后面。
- 这个过程不断重复,直到遇到表示生成结束的特殊token(如
<EOS>),或者达到预设的token数量上限。
结构化生成如何工作:一个实例
让我们通过一个具体任务,看看结构化生成如何通过修改Logits来工作。
假设我们使用一个视觉语言模型,并提供一张图片和文本提示:“请仅使用‘hot dog’或‘not hot dog’这两个标签来描述图片”。
模型初始的Logits分布可能包含多种可能的token,例如:H, ham, hamburger, hot, hot dog, not。即使我们要求模型只使用特定标签,它仍可能根据图片内容(比如一个汉堡)倾向于生成“hamburger”。
采样器负责从Logits中选择token。常见的采样策略有:
- 贪婪采样:总是选择概率最高的token。在上述例子中,可能会选择“hamburger”。
- 多项式采样:根据Logits的概率分布随机选择token。可能会选择“ham”。
但这都不是我们想要的结构(“hot dog”或“not hot dog”)。
结构化生成的做法是直接修改Logits。我们将不允许的token(如ham, hamburger)的Logits值大幅降低或置为无效。修改后,有效的token集合变为:H, hot, hot dog, not(H是“hot dog”的开头,所以有效)。
然后,采样器(贪婪或多项式)会在这个被约束过的概率分布中选择token,从而确保输出符合我们定义的结构。
代码实践:生成结构化JSON对象
现在,让我们通过代码看看这是如何实现的。
首先,我们导入必要的库并加载一个本地小模型。这里我们使用Hugging Face上的一个小型模型(lm2-135M),以便在演示中快速运行。
import outlines
from outlines import models
model = models.transformers("lm2-135M")
接下来,我们使用Pydantic定义一个希望模型生成的数据结构。这里定义一个Person类,包含name(字符串)和age(整数)字段。
from pydantic import BaseModel
class Person(BaseModel):
name: str
age: int
然后,我们创建一个生成器函数。在Outlines中,生成器是一个接受任何字符串提示并返回指定结构对象的函数。
import outlines.generate as gen
generator = gen.json(model, Person, max_tokens=100)
为了让生成过程确定以便教学,我们设置采样方式为greedy(贪婪采样)。同时,使用track_logits工具函数来观察每一步的token概率。
from outlines.generate.text import track_logits
track_logits()
在调用生成器之前,需要注意提示模板。大多数框架会自动添加对话所需的特殊token(如系统、用户标记),但Outlines为了给予用户更低层级的控制,不会自动处理。我们可以使用提供的template工具函数。
from outlines import prompt
system_prompt = "You are a helpful assistant."
user_prompt = "Generate a random person."
formatted_prompt = prompt.template(model, user_prompt, system_prompt)
现在,我们可以生成一个随机人物了。
person = generator(formatted_prompt)
print(person)
# 示例输出: Person(name='John', age=30)
我们成功获得了一个结构正确的Person对象。那么,模型是如何一步步生成符合JSON格式的文本的呢?让我们通过观察Logits来解密。
逐步解析生成过程
首先,我们知道目标JSON字符串的开头必须是:{"name": "。
第一步:生成开头的花括号 {
当我们查看第一步(position 0)的token概率时,会发现:
- 约束后(橙色):
{的概率极高(例如90.8%),{(带空格的左花括号)也有一定概率。 - 无约束(蓝色):模型原本倾向于生成自然语言,如“Here”, “I”, “Meet”等。
结构化生成在此处关闭了模型生成自然语言响应的倾向,强制其以JSON格式开始。
第二步:生成字段名 "name"
在生成了{之后,模型知道接下来应该是字段名。在对应的步骤(position 4),我们看到:
- 约束后:
"name"这个token的概率接近100%。 - 无约束:模型可能想生成
"username","Person","user"等,但这些都被约束关闭了。
第三步:生成字段值(如 "John")
在生成了 "name": " 之后,模型进入字符串值区域。此时,约束主要保证生成的是一个有效的字符串(被引号包围),而对字符串内容本身限制较少。因此,约束与无约束的Logits分布比较接近。模型根据训练数据,以高概率生成了 "John"。
第四步:生成 "age" 字段及其整数值
同理,模型在约束下生成 "age": ,然后生成一个整数(如 30)。对于整数,约束确保生成的是数字字符。
第五步:生成结束符 }
在生成完所有字段后,模型知道必须用 } 结束JSON对象。在最后一步,} 的概率在约束下占主导地位,然后是表示生成结束的特殊token <EOS>。
通过全程跟踪Logits,你可以清晰地看到结构化生成如何像一位“引导员”,在每个决策点修正模型的输出方向,确保其走在符合我们定义格式的道路上。
动手实验:修改约束条件
为了加深理解,请尝试以下实验:
- 修改
Person类,增加一个job字段,并使用Literal限制其取值(例如,只能为"doctor","basketball player","welder"之一)。from typing import Literal from pydantic import BaseModel class EmployedPerson(BaseModel): name: str age: int job: Literal["doctor", "basketball player", "welder"] - 使用新的类创建生成器,并生成一个
EmployedPerson对象。 - 观察在生成
job字段时,Logits如何将不允许的选项(如"software engineer")的概率抑制,并提高允许选项的概率。
通过这个实验,你将直观感受到约束如何精确地控制模型在有限选项内的选择。
总结
本节课中,我们一起深入探索了基于Logits的结构化生成技术的内核。你学到了:
- 结构化生成通过直接修改语言模型输出的Logits来确保格式正确。
- 与API的JSON模式相比,这种方法更可靠、高效且灵活。
- 我们通过代码示例,逐步跟踪了Logits的变化,亲眼见证了模型如何在约束下生成一个结构化的JSON对象。
- 核心在于,约束在每个token生成步骤充当过滤器,屏蔽不符合格式要求的路径,引导模型走向有效输出。

在下一节课中,我们将把这项技术扩展到更多样的结构化输出场景,例如生成电话号码、电子邮件地址甚至井字棋棋盘状态。
006:超越JSON的结构化生成 🚀

在本节课中,我们将学习outlines库如何高效地生成结构化输出,并探索其如何支持远不止JSON的多种结构。我们将理解正则表达式如何转换为有限状态机以实现高效计算,并最终探索JSON之外的各种结构化输出示例。
概述
上一节我们介绍了outlines如何通过修改逻辑来保证模型输出的结构。本节我们将深入探讨outlines如何精确地跟踪结构。outlines在底层使用正则表达式来建模我们想要的结构,这允许我们定义比JSON更广泛的结构范围。正则表达式与有限状态机之间存在有趣的关系,我们利用这种关系,在模型生成过程中轻松高效地处理结构。


正则表达式与有限状态机
首先,简要讨论正则表达式及其工作原理。您可能熟悉它们,但以防万一,我们快速回顾一下。
以下是一个非常简单的正则表达式,它描述了我们在第一课中讨论的示例:
A*B*C*$
这是一个必须按顺序出现的A、B、C序列。现在,可以有任意数量的A(包括0个)、任意数量的B、任意数量的C,然后是字符串的结尾。因此,有效的字符串包括空字符串(不违反约束)、ABC(按顺序)、AABCC(允许重复),当然,单独的C也是允许的,因为它前面的字符没有违反规则。
无效的字符串包括BAA、ABBCCB(只要有一次顺序不对,无论其他部分如何,都是无效的),以及CBA。
接下来,讨论正则表达式与有限状态机之间的关系。我们可以将刚刚描述的正则表达式转换为右侧所示的有限状态机。您可能认为这增加了问题的复杂性,但这个有限状态机实际上使程序能够轻松跟踪正则表达式。
让我们通过一个模式匹配示例来理解这个过程。给定代表我们之前描述的正则表达式的有限状态机,我们有一个字符串“AABC”,我们想看看它是否匹配。
我们从起始位置开始,观察到一个A,这使我们进入A状态。现在我们处于A状态,观察到另一个A,我们循环回到A状态,仍然保持在A状态。接下来,我们观察到一个B,这使我们从A状态移动到B状态。现在我们观察到一个C,并讨论匹配字符串的含义。当我们匹配一个字符串时,意味着我们成功地结束于图中用两个圆圈标记的目标状态,并且没有更多的字符串需要处理。因此,我们成功地匹配了这个字符串。
现在,让我们看一个匹配失败的例子。这个字符串根据我们的正则表达式是无效的。让我们看看有限状态机如何证明这一点。同样,我们从起始状态开始,观察到一个A,移动到A状态。接下来,我们观察到一个C,移动到C状态。到目前为止一切顺利,我们还没有违反正则表达式的任何规则。现在,我们有一个A。但是,您可以看到,从C状态出发的所有路径都没有A,而且我们不在结束状态,因此匹配失败。
结构化生成的应用
您可能会问,这个用于匹配正则表达式的有限状态机如何对生成结构有用?我们可以简单地反转这个过程。当我们在outlines中使用FSM时,幕后就是这样工作的:我们跟踪所处的状态,并查看从该状态出发的所有路径。
例如,我们处于起始状态,所有可能的路径都是允许的。右侧是我们的逻辑,可以看到结构不需要对这些逻辑进行任何更改,因为我们拥有的所有标记都是可接受的。但我们允许一个B,并移动到B状态。现在,您可以看到我们并非拥有所有路径。因此,尽管模型为所有选项生成了逻辑权重,但我们知道只有路径B、C和结束是允许的。我们在生成过程中继续这样做。
通过这种方式,我们能够将匹配过程反转为生成过程。这是一种非常强大的表示结构的方法。
超越JSON的结构
结构远不止JSON。简单的结构通常足以满足我们的大多数任务。
以下是几种常见的结构化输出示例:
- 类别标签:如果您正在构建一个零样本或单样本分类器,您可能希望将模型的输出限制在您拥有的类别标签内,不需要额外的JSON包装。
- 电子邮件地址:如果您从文档中解析电子邮件地址,如果LLM能够一致地输出电子邮件地址格式,那将非常有用。
- 电话号码:确保LLM的输出匹配电话号码格式的正则表达式非常有用。这一点很重要,因为人们可以用不同的方式书写电话号码,因此您可能希望在解析电话号码时应用一致的格式。
- 常见文档类型:我们已经讨论了很多关于JSON的内容,但当然,我们仍然可以生成JSON。我们也可以生成CSV文件或YAML。
- 上下文无关文法:对于观看本课程的形式语言爱好者,您可能会感到惊讶。通常,我们认为上下文无关文法比正则表达式更强大。然而,只要限制允许的递归深度,我们就可以将任何任意的上下文无关文法表示为正则表达式。有了上下文无关文法,我们就可以输出语法正确的编程语言,这是非常令人印象深刻的事情。基本上,您可以建模任何您能想象的文档类型。世界上有各种各样的结构,有了可用的上下文无关文法,您可以表示的内容几乎没有限制。
实践示例
现在,让我们看一些例子。我们将从添加这两行代码开始,以避免看到任何不必要的警告。
接下来,我们将添加我们将要使用的库。我们有一个实用程序模板,可以帮助我们轻松编写提示。我们还将使用outlines,并且在某些情况下使用outlines的贪婪采样,以确保我们始终确切知道模型将输出什么。
然后,我们将加载我们的语言模型。我们将使用Hugging Face的small LM2的1.35亿参数指令版本。即使在资源受限的硬件上,它也能运行得很好。因此,所有这些示例都将在具有约8GB RAM的CPU上实际运行,这是一个非常令人印象深刻的成就。
示例一:选择结构
我们将看到的第一个结构是“选择”。当我们想用有限数量的可能标签来标记某物时,就会使用选择。
在这种情况下,我们有一个简单的请求,我们希望将餐厅评论分类为正面或负面。这是任何时候我们使用LM进行单样本或零样本分类时非常常见的模式。因此,我们只希望输出一个类别标签。
值得指出的是,这可以用正则表达式解决。以下是代表这一点的正则表达式示例:
(positive|negative)
只有两个可能的字符串被允许:“positive”或“negative”。但这并不太糟糕,但对于更复杂的模型来说,编写正则表达式有点繁琐。因此,我们将实际使用称为“choice”的其他东西。
以下是使用outlines.choice创建生成器的代码,该生成器将只在“positive”和“negative”之间选择。在这段代码中,您可以看到,我们不需要传入完整的正则表达式,只需传入我们希望从模型中输出的值列表。在这种情况下,是“positive”和“negative”。您可以添加任意多个这样的值,这当然比运行正则表达式更容易。它还允许我们从另一个源获取标签并直接输入到模型中,这可能非常有用。
最后,我们将运行我们的新模型,看看我们得到了什么。结果是评论是正面的。当然,评论者说披萨很美味,我们得到了我们想要的结果。
示例二:电话号码正则表达式
在这个下一个例子中,我们将看一个用于此任务的电话号码正则表达式。我们想从提示中提取一个电话号码。现在,如果您看这个例子,我们实际上希望以与提供的格式不同的格式提取电话号码。我们希望区号周围有括号,一个空格,前三位本地号码,然后通过破折号分隔最后四位。
以下是提供的号码示例,它不匹配这个格式。这是一个非常、非常有用的应用程序,一个用于数据提取问题的非常简单的结构。
以下是我们将用于此的正则表达式。如您所见,它编码了我们刚刚描述的电话号码的所有属性。现在,我们所要做的就是获取我们的正则表达式,并使用outlines.generate.regex创建一个电话号码生成器。这将为我们提取电话号码,并且完全按照我们希望的格式。当我们运行这个时,我们可以看到它成功地提取了我们需要的号码,并应用了我们想要的格式。比较一下提示中我们拥有的不同格式。区号之间有一个破折号,没有括号,而我们的生成器成功地提取了正确的格式。
示例三:电子邮件地址

接下来,我们将对电子邮件地址做同样的事情。在这种情况下,我们肯定需要一个正则表达式,因为电子邮件地址可能非常复杂。现在,如果您了解如何验证电子邮件地址,这实际上不是完全完美的电子邮件地址,但它适用于我们的情况。对于这个例子,我们只是要求我们的模型为亚马逊的某人生成一个随机电子邮件地址。再次,我们只需使用outlines定义一个简单的正则表达式生成器并生成电子邮件地址。

示例四:HTML图像标签
接下来,我们将做一些更复杂的事情。我们将使用正则表达式根据我们提供的文件名生成HTML图像标签。由于我们正在处理一个更复杂的正则表达式,我想介绍一种处理结构化生成的技术,这可能非常有帮助。这里有一个我们希望从模型中得到的结构的例子。这是确保您定义的结构正确的非常有用的技术。由于这是一个不平凡的情况,我们真的想确保我们的正则表达式有效。
让我们看看我们将要使用的正则表达式。这是我们的图像标签正则表达式。这对我来说看起来不错,但我想在运行模型之前验证这实际上是否匹配我们的示例。我们将导入Python的正则表达式库re,并将在我们拥有的示例中搜索我们的图像标签。如您所见,它成功地找到了匹配项。这确实有力地证明我们为任务定义了正确的正则表达式,并节省了我们在实际运行LLM时查找错误的大量时间,因为我们可以相信我们的结构看起来不错。
现在是构建生成器的时候了。现在,我们使用生成器创建图像标签。我们给了它一个提示,说“为文件big_fish.jpg生成一个基本的HTML图像标签。确保包含alt标签。”让我们检查这个结果,看看我们是否喜欢我们得到的东西。好的,这是一个图像标签,源文件是big_fish.jpg,alt标签是“data about fish”。如果我们在创建博客文章,并希望确保我们的图像是可查找的,这可能很有用。我们不需要通过查看它来怀疑,我们实际上可以在页面上渲染HTML。正如我们所看到的,我们的图像标签有效,渲染了我们的大鱼图片。
示例五:井字棋棋盘
结构无处不在。我们通常认为结构严格是JSON或简单的正则表达式,以及像Markdown这样的东西。但在这个例子中,我们将生成一个井字棋棋盘。这是我们井字棋棋盘的正则表达式。它有点复杂,但这定义了一个ASCII井字棋棋盘的结构。让我们继续生成一个例子。我们将再次创建生成器,并生成井字棋棋盘。注意,在提示中,我向模型展示了我认为井字棋棋盘应该是什么样子的示例。即使我们保证能得到我们想要的结构,在提示中提供您希望得到的输出示例总是有帮助的。最后,我们可以打印出井字棋棋盘。正如您所看到的,我们有一个由LLM生成的正在进行的井字棋游戏。
示例六:CSV内容
我们已经详细讨论了JSON,但如果您是数据科学家或从事机器学习工作,您可能更熟悉CSV作为首选文件格式。在这个例子中,我们将直接从模型生成CSV内容,并将其输入到pandas数据框中。
以下是我们将用于生成此内容的正则表达式。您可以看到我们有三个列:code、amount和cost,它们代表库存物品的物品代码、我们拥有的该物品的数量以及每单位物品的成本。然后,我们指定了希望此CSV文件具有的一些属性。例如,我们可以看到code必须是一个三个字符的物品代码。然后,amount最多有两位数字。我们使用更多数字和一个小数点来表示cost,以确保我们始终有一个正确的美元值。
这是我们的简单CSV生成器,然后我们可以用它来创建CSV输出。注意,我们在这里只是简要描述了我们的CSV文件,我们将看到模型仅凭提示中的描述能做得有多好。与其打印出这个结果的字符串表示,我们实际上可以使用Python的StringIO将其直接传输到pandas。如您所见,它成功地创建了一个CSV文件,我们可以直接从LLM发送到pandas。如果您正在进行任何类型的数据处理,并且这将成为数据科学工作流的一部分,这将非常有用。
示例七:GSM8K问题的结构化实现
在下一部分,我们将讨论为GSM8K实现结构,这是一个常见的LM评估基准,使用小学问题来查看LM是否能正确回答它们。我们还将讨论使正则表达式更容易的方法。
以下是常见的GSM8K类型问题的示例:“Tom has three cucumbers. Jo gives two more. How many does Tom have?” 这是提示中提供给模型的内容。然后它接着进行推理步骤。这是模型对问题的思考:“Tom started with three cucumbers, then received two more, this means he has five cucumbers.” 最后,模型被要求回答:“So the answer is 5.” 注意,即使这是纯文本,这里仍然有一个非常清晰的结构。我们将用问题提示模型,并希望它遵循正确的推理,最重要的是,正确的答案。所有这些都是我们可以强制模型遵循的格式。
当然,为这个问题编写正则表达式将非常棘手。幸运的是,在outlines中,我们有一个领域特定语言,允许我们以一种易于理解的语言非常容易地表示正则表达式。我们可以看到我们通过从outlines.types导入digits并使用dsl2regex函数来使用它。
推理部分以短语“reasoning”开头,就像在这个例子中一样。然后我们将添加一个重复一到两次的句子。这意味着语句必须以“reasoning”开头,并且我们允许推理继续一到两句话,这些句子已经被预定义。因此,您不需要正则表达式。然后答案需要以“So the answer is: ”开头。然后我们需要有一个长度在一到四位数之间的数字。看,我们在这里使用digits并重复它一到四次。非常直接。最后,我们将把所有这些转换为正则表达式,看看它是什么样子。我当然很高兴我不必手动编写那个。
接下来,我们将通过传入这个新正则表达式来构建我们的正则表达式生成器,就像我们之前所做的那样。现在,我们准备测试我们的模型到底有多聪明。我们将给它这个问题:“Sally has five apples, then receives two more. How many does Sally have?” 现在,我们可以看看我们将用来向模型发送这个问题的提示。在提示中,我们解释了问题的结构。我们讨论了这将如何工作并提供了一个示例。然后我们只是将我们的问题放入模板中,保持与此提示的格式一致。因此,我们期望下一个自然步骤将是输出推理,然后是解决方案。最后,我们可以看到模型的表现如何。正如我们所看到的,模型正确地推理出Sally有五个苹果,她收到了两个更多,这意味着她有5+2=7个苹果。所以答案是7。所有这些都在一个我们可以轻松理解和解析的结构中。当然,我们本可以用JSON来做这件事,但尝试使用我们日常生活中发现的更自然的结构形式总是值得的。
最终挑战:热狗分类器
最后,我们将留给您一个非常有趣的项目。您将构建您自己的“热狗”与“非热狗”分类器。我们将从一些基本的样板代码开始。这将加载一个视觉模型,这是一个多模态模型,允许我们实际在提示中使用图像,因为您的任务是添加结构。我们将包括outlines.text方法的非常简单使用,它只是从模型返回非结构化结果,允许您完成其余的工作。
接下来,我们将查看我们的提示,这将有助于理解手头的任务。我们将指示我们的模型,它将获得一张热狗或非热狗的图像,并且必须正确标记,仅用“hot dog”或“not a hot dog”响应。注意,两者都是小写,并且是我们希望从模型中得到的唯一选项。为了帮助您入门,我们还包括了一些代码,这些代码将遍历文件中的图像并在它们上运行我们的模型,看看它的表现如何。
让我们看看这个非结构化模型的表现如何。现在,看看模型的表现如何。如您所见,它正确地将第一张图像标记为“hot dog.”,但它没有完全遵循我们的标记标准。我们希望它输出小写的“hot dog”,没有句点。现在,这可能看起来有点挑剔,但如果您正在为热狗或非热狗构建生产分类系统,您绝对希望这些标签是一致的。我们可以快速滚动查看其他结果,并发现我们在所有方面都有类似的问题。正确标记为“not a hot dog.”但没有遵守我们的准则。但当我们看到最后一张图像时,我们可以看到模型真的偏离了轨道。它只是将其描述为“This airplane is flying in the sky.”。虽然是对图像的正确描述,但这不是我们想要的。您的任务是运用我们在本课中学到的知识,并将其应用于此问题,看看是否能为所有这些输出获得一致的标签。
总结



在本节课中,我们学习了outlines如何真正由正则表达式驱动。我们用它来创建一系列不同的工程结构。最后,我们以一个非常有趣的练习结束,供您在家尝试。
007:总结与展望 🎉

在本节课中,我们将对结构化输出课程的全部内容进行回顾与总结,并展望未来的应用方向。
课程回顾
上一节我们深入探讨了结构化生成的内部原理,现在让我们对整个课程的核心收获进行梳理。
在本课程中,你学习了如何利用大语言模型的结构化输出来编写可靠的软件。具体内容包括:
- 使用OpenAI结构化输出:你构建了一个社交媒体代理,实践了如何利用特定API获取格式化的模型响应。
- 掌握重新提示方法:你学会了如何通过精心设计的提示词,从任何推理服务提供商处获取结构化的输出,而不依赖于特定API。
- 理解内部工作原理:你“打开”了大语言模型的黑箱,窥见了Outline等工具执行结构化生成的内在机制。
- 认识结构化生成的潜力:你了解到结构化生成的能力远不止于产生JSON格式的数据,其应用场景更为广泛。
总结与展望
本节课中我们一起学习了构建可靠AI应用的关键——结构化输出技术。从具体的API调用到通用的提示工程方法,再到对底层原理的理解,你已掌握了确保大语言模型输出稳定、可预测、易于程序处理的一系列核心技能。
结构化输出是将大语言模型的强大能力与严谨的软件工程结合起来的桥梁。我们期待看到你将运用这些知识构建出怎样的创新应用。🚀


浙公网安备 33010602011771号