llm.with_structured_output 是 LangChain 中一个非常强大且便捷的方法,用于强制大模型输出符合特定格式的数据(如 JSON、Pydantic 对象),而不是返回普通的文本字符串。
1. 核心原理
该方法会在底层通过以下两种方式之一工作(取决于模型支持情况):
- Function Calling (工具调用):这是最推荐的方式。模型不会直接输出文本,而是调用一个定义好的函数,参数就是你要的结构化数据。LangChain 会自动解析这个函数调用并返回对象。
- JSON Mode:如果模型不支持工具调用,LangChain 会尝试在 System Prompt 中强制模型输出 JSON 格式,然后用正则或解析器提取。
2. 基础用法:使用 Pydantic 模型 (推荐)
这是最常用、最健壮的方式。利用 Pydantic 进行数据校验,能确保类型正确(例如金额必须是 float,日期必须是 datetime)。
步骤:
- 定义一个继承自
BaseModel的类,并使用Field描述每个字段。 - 调用
llm.with_structured_output(Schema)。
代码示例:
from langchain_core.prompts import ChatPromptTemplate
from langchain_openai import ChatOpenAI # 假设你用的是 Qwen,可以用 ChatOpenAI 兼容接口
from pydantic import BaseModel, Field
# 1. 定义输出的数据结构
class BidderInfo(BaseModel):
"""投标人信息提取"""
company_name: str = Field(description="中标公司的全称")
winning_amount: float = Field(description="中标金额,单位为万元")
contact_person: str = Field(description="联系人姓名", default="未知")
# 2. 初始化模型(这里以 Qwen 为例,通过 vLLM/OpenAI 兼容接口调用)
llm = ChatOpenAI(
base_url="http://localhost:8000/v1", # 你的 vLLM 地址
model="qwen-32b-instruct",
temperature=0
)
# 3. 使用 with_structured_output 包装模型
# 这里的 structured_llm 现在的输出类型就是 BidderInfo 对象,而不是 str
structured_llm = llm.with_structured_output(BidderInfo)
# 4. 构建提示词并调用
prompt = ChatPromptTemplate.from_messages([
("system", "你是一个专业的招投标信息提取助手。请从用户输入的文本中提取关键信息。"),
("user", "{input}")
])
chain = prompt | structured_llm
# 模拟一段标书文本
text = """
根据中标公告显示,XX科技有限公司于今日成功中标该项目。
中标金额为 450.5 万元。
项目负责人为张三。
"""
# 5. 调用链
result = chain.invoke({"input": text})
# 6. 查看结果(此时 result 已经是一个对象,不是字符串)
print(f"类型: {type(result)}")
print(f"公司名: {result.company_name}")
print(f"金额: {result.winning_amount}")
print(f"JSON输出: {result.model_dump_json()}") # 可以直接转成 JSON 字符串存库
3. 进阶用法:处理复杂结构 (嵌套与列表)
在招投标场景中,往往需要提取一个列表(例如:评分表中的多条明细,或多个产品清单)。Pydantic 完美支持嵌套。
代码示例:提取评分细则列表
from typing import List, Literal
from pydantic import BaseModel, Field
# 定义单个评分项的结构
class ScoreItem(BaseModel):
category: Literal["技术分", "商务分", "价格分"] = Field(description="评分大类")
factor: str = Field(description="评审因素,如:实施方案")
standard: str = Field(description="评审标准描述")
max_score: float = Field(description="该项最高分值")
# 定义整个评分表的结构
class EvaluationStandard(BaseModel):
"""评分标准表"""
total_score: float = Field(description="总分")
items: List[ScoreItem] = Field(description="评分细则列表")
# 包装模型
structured_llm = llm.with_structured_output(EvaluationStandard)
# 调用
text = "本项目总分100分。技术分占40分,其中实施方案20分,人员资质20分。商务分30分...(此处省略)"
result = structured_llm.invoke(text)
# 遍历列表
for item in result.items:
print(f"{item.factor}: {item.max_score}分")
4. 常用参数与技巧
llm.with_structured_output 除了传入 Schema 类,还支持一些参数:
-
name: 给这个工具起个名字(主要用于 Agent 场景,让模型知道什么时候调用它)。 -
description: 描述这个工具的用途(主要用于 Agent)。 -
include_raw: 布尔值。如果设为True,返回结果中会包含原始的raw_output(模型的原始响应)和parsed_output(解析后的对象)。这对于调试解析失败的情况非常有用。structured_llm = llm.with_structured_output(BidderInfo, include_raw=True)
response = structured_llm.invoke(...)
print(response['raw']) # 模型生成的原始文本或工具调用参数
print(response['parsed']) # 解析成功的 Pydantic 对象
注意事项
- 模型能力要求 :
with_structured_output依赖于模型 Function Calling (Tool Use) 的能力。- Qwen: Qwen-2.5, Qwen-2, Qwen-1.5 系列均支持 Function Calling。
- 如果使用 vLLM 部署,请确保 vLLM 版本较新,并且开启了对应的支持(通常默认开启 OpenAI 兼容协议即可)。
- 描述非常重要 :在
Field(description="...")中写的描述越清晰,模型提取的准确率越高。特别是对于金额,最好注明单位(如"单位:元")。 - 容错处理 :如果模型完全无法提取(例如文本中根本没相关信息),Pydantic 可能会报错或生成空字段。在代码中要做好
try-except或默认值处理。