LangChain解锁LLM大语言模型的结构化输出能力:调用 with_structured_output() 方法

embedded/2025/3/3 5:00:19/

什么是LLM的结构化输出能力?

在一些工业级LLM应用或比较复杂的LLM应用编排环节,我们需要用LLM的输出作为下一环节的输入,而这个过程往往对LLM输出的格式有一定要求,比如JSON、XML、YAML、CSV、Markdown 表格和HTML 等比较常见的格式。因此我们需要通过各种方式手段让LLM具备符合我们期望的结构化输出能力,即模型能够按照用户指定的格式或规则生成内容(而不仅仅是自由形式的文本)的能力。这种能力使LLM的输出结果更易于被程序解析、集成到自动化流程中,以一种更为可控的方式满足特定落地场景的需求。

如何让LLM具备该能力?

以下内容参考自 langchain官方文档教程:How to return structured data from a model (内容&代码解释由Deepseek-R1部分帮忙完成)

调用 with_structured_output() 函数是一种最为简单且可靠的结构化LLM输出的方式。它专为支持原生APIs对输出进行结构化的模型设计(例如工具 / 函数调用或 JSON 模式),并在底层直接调用LLM原生具备的这些能力。

该方法通过接收一个 模式(schema) 来指定输出属性的名称、类型和描述,返回一个类似模型的 Runnable 对象。与普通模型不同,此对象输出的不是字符串或消息,而是符合指定模式的结构化对象。模式可通过以下三种方式定义:

  1. TypedDict 类(类型化字典):Runnable 返回字典
  2. JSON Schema(JSON 模式):Runnable 返回字典
  3. Pydantic 类:返回 Pydantic 对象

接下来我们用实际的示例代码具体看以上三种方式的落地实现。

目标:希望LLM根据用户输入的信息生成相关的笑话并将该笑话的铺垫(set up)和笑点(punchline)分离出来,并同时给这个笑话的好笑程度以十分制进行打分(rating)。

1. Pydantic 类

代码如下:

# 导入所需库
from langchain_ollama import ChatOllama  # Ollama聊天模型接口
from typing import Optional             # 用于定义可选类型
from pydantic import BaseModel, Field   # 数据验证和字段定义工具# 初始化Ollama聊天模型
llm = ChatOllama(model="llama3.1:latest",   # 指定使用的模型版本temperature=0.8            # 控制生成随机性(0-1,越高越有创意)
)# 使用Pydantic定义结构化输出格式
class Joke(BaseModel):"""Joke to tell user."""setup: str = Field(description="The setup of the joke")  # 必填字段,字符串类型punchline: str = Field(description="The punchline to the joke") rating: Optional[int] = Field(  # 可选字段,整型(可为None)default=None, description="How funny the joke is, from 1 to 10")# 将模型包装为结构化输出生成器
structured_llm = llm.with_structured_output(Joke)  # 绑定Pydantic模型到LLM# 调用结构化模型生成内容
result = structured_llm.invoke("Tell me a joke about cats")  # 输入提示词

让LLM讲一个关于猫猫的笑话,结构化输出如下:

print(result)
# result变量是Joke()这个Pydantic类的对象
setup='Why did the cat join a band?' punchline='Because it wanted to be a purr-cussionist' rating=8# 将对象的三个属性打印出来
print(result.setup)
Why did the cat join a band?
print(result.punchline)
Because it wanted to be a purr-cussionist
print(result.rating)
8

使用上述这种基于Pydantic类对LLM输出进行格式化方法时,除了类的字段结构定义,以下元素对结构化输出的生成也是至关重要:

  1. 类名称(如Joke
    作为模型理解任务类型的提示(例如生成笑话)。
  2. 类的文档字符串(如"""Joke to tell user."""
    描述类的整体目的,为模型提供上下文。
  3. 字段名称与描述(如description="笑话的铺垫部分"
    明确每个字段的具体含义和预期内容。

底层实现原理

大多数情况下,with_structured_output() 方法利用模型的 函数/工具调用接口(Function/Tool Calling API)。从开发者视角可以理解为:上述所有元数据信息(类名、文档字符串、字段描述)实际上都会被整合到模型的输入提示中,指导其生成符合规范的结构化输出。

设计建议

  • 语义清晰的类名:使用WeatherReport而非Data等泛化名称
  • 详细的文档字符串:明确说明生成任务的目标和范围
  • 精准的字段描述:避免模糊定义(如用"用户年龄(1-100岁)"替代"年龄"

这些元数据共同构成模型的“结构化生成指南”,将直接影响输出的准确性和可控性。

2. TypedDict 类

若有以下需求,可通过 TypedDict 定义模式(Schema)而非基于Pydantic 类的方式:

  1. 不需要参数验证:希望跳过 Pydantic 的数据类型检查。
  2. 需要流式输出:支持逐步生成结构化内容而非一次性返回。
  3. 避免依赖 Pydantic:简化项目依赖或适配其他数据模型框架。

示例代码如下:

# 导入所需库
from typing import Optional  # 支持可选类型
from typing_extensions import Annotated, TypedDict  # 类型扩展工具
from langchain_ollama import ChatOllama  # Ollama聊天模型接口# 初始化Ollama聊天模型
llm = ChatOllama(model="llama3.1:latest",  # 指定模型版本temperature=0.8  # 控制生成随机性(0-1,越高越有创意)
)# 使用TypedDict定义结构化输出格式(不包含数据验证)
class Joke(TypedDict):"""Joke to tell user."""# Annotated语法结构:[类型, 默认值(可选), 描述]# setup字段:字符串类型,必须生成,无默认值,带描述setup: Annotated[str, ..., "The setup of the joke"]  # 等同于 Field(description="...")# 以下是替代写法示例(注释掉的代码):# setup: str                    # 最简单写法:只有类型,无默认值和描述# setup: Annotated[str, ...]    # 等效于上方的简单写法# setup: Annotated[str, "foo"]  # 带默认值但无描述的写法(默认值仅用于提示构造)# punchline字段:字符串类型,必须生成,无默认值,带描述punchline: Annotated[str, ..., "The punchline of the joke"]# rating字段:可选整型,默认值为None,带描述rating: Annotated[Optional[int], None, "How funny the joke is, from 1 to 10"]# 将模型包装为结构化输出生成器(使用TypedDict模式)
structured_llm = llm.with_structured_output(Joke)

结构化输出如下(result是一个dict类型的变量):

# 调用模型生成结构化内容
# 注意:即使定义默认值(如rating的None),若模型未生成该字段,值仍会缺失
# 需要通过代码处理可能的缺失字段(例如 result.get("rating", None))
result = structured_llm.invoke("Tell me a joke about cats")
print(result)  # 示例输出:{'setup': '...', 'punchline': '...', 'rating': 7}
{'punchline': 'Why did the cat join a band? Because it wanted to be the purr-cussionist!', 'rating': 8, 'setup': 'A cat walks into a music store and says...'}
3. JSON Schema

和通过 TypedDict 定义模式(Schema)的实现效果一致,只不过是通过定义JSON Schema格式的方式而非定义TypedDict 子类来创建结构化输出模板(和让LLM支持function calling的方法大同小异)。

示例代码如下:

from langchain_ollama import ChatOllama  # 导入Ollama聊天模型接口# 初始化Ollama聊天模型
llm = ChatOllama(model="llama3.1:latest",  # 指定使用的模型版本(需与本地/服务器部署的模型一致)temperature=0.8            # 控制生成随机性(0-1,越高越有创意)
)# 定义JSON Schema格式的结构化输出模板
json_schema = {"title": "joke",          # 模式标题(用于模型理解生成内容的类型)"description": "Joke to tell user.",  # 模式描述(整体任务说明)"type": "object",         # 指定输出为JSON对象"properties": {           # 定义对象的各个字段"setup": {            # 必填字段:笑话的铺垫部分"type": "string", # 字段类型为字符串"description": "The setup of the joke",},"punchline": {        # 必填字段:笑话的笑点部分"type": "string","description": "The punchline to the joke",},"rating": {           # 可选字段:笑话评分(1-10)"type": "integer","description": "How funny the joke is, from 1 to 10","default": None,  # 默认值为None(若模型未生成则保留空值)},},"required": ["setup", "punchline"],  # 必须包含的字段(rating为可选)
}# 将模型包装为结构化输出生成器(基于JSON Schema)
structured_llm = llm.with_structured_output(json_schema)

结构化输出如下(result是一个dict类型的变量):

# 调用模型生成结构化内容
# 注意:
# 1. 即使定义了rating的默认值None,若模型未生成该字段,结果中可能不存在此键
# 2. 输出为标准的Python字典,可通过result["setup"]访问字段值
# 3. JSON Schema不执行自动数据验证(与Pydantic不同),需自行处理类型错误
result = structured_llm.invoke("Tell me a joke about cats")
print(result) 
{'punchline': 'Why did the cat join a band? Because it wanted to be the purr-cussionist!', 'rating': 8, 'setup': 'A cat walked into a music store and asked the owner if he could join his band.'}

小结

在本篇博客中,我们学习了如何通过最简单高效的 with_structured_output() 方法调用的方式让LLM具备结构化输出的能力。在具体实现时,有三种可选方式可对输出格式进行规范,分别是基于 TypedDict 类(类型化字典)、JSON Schema(JSON 模式)和 Pydantic 类。当然,除了本篇博客中提到的实现方式,还存在多种其他技术方案可对模型输出进行结构化控制。例如:

  • 小样本示例引导(Few-shot prompting):通过提供输入 - 输出样例指导模型生成格式;
  • 结构化方法指定(Specifying the method for structuring outputs):显式定义 JSON、XML 等数据格式规则;
  • 直接解析模型输出(Direct prompting and parsing):结合自然语言指令与后处理解析逻辑。

关于这些技术的具体实现与应用场景,我们后续博客再见,感兴趣的朋友可以持续关注。


http://www.ppmy.cn/embedded/169525.html

相关文章

JavaScript知识点4

1.解释一下这段JavaScript代码 var fruits ["Apple", "Orange", "Apple", "Mango"]; var a fruits.indexOf("Apple",-1); console.log("index"a); 输出的a值为-1,indexOf的第二个参数是-1&#xf…

Git与GitHub:它们是什么,有什么区别与联系?

1.Git是什么? Git 是一个开源的、分布式版本控制系统(Version Control System, VCS),由 Linus Torvalds 于 2005 年开发,最初用于管理 Linux 内核的开发。它的核心功能是跟踪文件的变更历史,帮助开发者高效…

「Selenium+Python自动化从0到1①|2025最新环境搭建+浏览器驱动避坑指南(附验证代码)」

Selenium Python 自动化 1 - 环境搭建 一、Selenium 简介 Selenium 是一个广泛使用的自动化测试工具,主要用于 Web 应用程序的自动化测试。它支持多种编程语言(如 Java、Python、C#、Ruby、JavaScript 等),并允许用户控制浏览器…

linux vim 撤销 回退操作

在Linux的vim编辑器中,撤销和回退操作是非常基本的,但它们可以通过不同的方式实现,具体取决于你想要的精确效果。下面是一些常用的方法: 1. 撤销(Undo) 单个撤销: 你可以通过按下u键来撤销上一…

如何获取Mac OS 安装盘

发现虚拟机VirtualBox支持Mac虚拟,就想尝试一下。但是发现Mac的安装盘特别难拿到,因此留档。发现有几种方法,最简单的方法,是在有Mac 机器的情况下,直接到App Store里,根据Mac版本的名字查找并下载。另外还…

UE5切换关卡函数OpenLevel,输入模式结构体,UI界面

1.输入模式结构体 FInputModeGameOnly:玩家只能与游戏世界交互,UI 不可交互。FInputModeGameAndUI:玩家可以与游戏世界和 UI 同时交互。FInputModeUIOnly:玩家只能与 UI 交互,无法与游戏世界进行互动。 FInputModeGam…

给wordpress仪表盘添加自定义图标

wordpress后台仪表盘默认的图标是wordpress自带的&#xff0c;如果要将图片修改为自己的&#xff0c;只需要在function.php文件中加入以下代码。 function wdp_custom_logo() { echo <style type"text/css"> #wpadminbar #wp-admin-bar-wp-logo > .ab-ite…

安全模块设计:token服务、校验注解(开启token校验、开启签名校验、允许处理API日志)、获取当前用户信息的辅助类

文章目录 引言pom.xmlI 校验注解ApiValidationII token服务TokenService获取当前用户信息的辅助类III 域登录接口响应数据登陆用户信息引言 pom.xml <?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/PO…