共计 3163 个字符,预计需要花费 8 分钟才能阅读完成。
大模型天生只会"吐字符串",但程序需要的是字符串、列表、JSON 对象……
输出解析器负责把模型输出转换成 Python 数据,这是让 LLM 接入业务代码的关键一步。
本节课学到:
- StrOutputParser:只要文本内容(最常用);
- PydanticOutputParser:用 Pydantic 模型约束输出结构(LCEL 经典写法);
- with_structured_output:基于模型原生"函数调用"的结构化输出(更稳更现代)。
"""
运行方式:
.venv\\Scripts\\python examples\\03_output_parser.py
"""
import sys
from pathlib import Path
from typing import Literal
sys.path.insert(0, str(Path(__file__).resolve().parent.parent))
from pydantic import BaseModel, Field # Pydantic:用类型注解定义数据结构
from langchain_core.output_parsers import PydanticOutputParser, StrOutputParser
from langchain_core.prompts import ChatPromptTemplate
from config import get_chat_model, print_sub, print_title
def demo_str_parser() -> None:
"""StrOutputParser:把 AIMessage 转成它的 .content 字符串。"""
print_sub("1. StrOutputParser:只要纯文本")
llm = get_chat_model(temperature=0.3)
raw = llm.invoke("说一个成语")
print(f"不加解析器,返回类型是:{type(raw).__name__}(一个消息对象)")
chain = llm | StrOutputParser() # | 管道:模型输出 → 解析器
text = chain.invoke("说一个成语")
print(f"加了解析器,返回类型是:{type(text).__name__}")
print(f"内容:{text.strip()}")
# ---------------------------------------------------------------------
# 用 Pydantic 定义"我希望模型输出长什么样"
# 继承 BaseModel,每个字段就是一个要抽取的信息,Field 的 description 会告诉模型含义
# ---------------------------------------------------------------------
class DishInfo(BaseModel):
"""从用户对菜品的描述中抽取结构化信息。"""
name: str = Field(description="菜品名称")
flavor: Literal["清淡", "咸鲜", "麻辣", "香甜", "酸甜"] = Field(
description="主要口味,只能从给定选项中选择"
)
is_spicy: bool = Field(description="这道菜是否辣")
ingredients: list[str] = Field(description="主要食材列表")
price_range: str = Field(description="预估价格区间,例如 '20-40 元'")
def demo_pydantic_parser() -> None:
"""PydanticOutputParser:提示词里注入格式约束,链末端自动解析成对象。"""
print_sub("2. PydanticOutputParser:提示词约束 + 自动 JSON 解析")
parser = PydanticOutputParser(pydantic_object=DishInfo)
# 关键:提示词里必须有 {format_instructions} 占位符,
# 解析器会自动生成一段 JSON Schema 说明塞进去(务必保证变量名一致!)
prompt = ChatPromptTemplate.from_messages(
[
("system", "你负责抽取菜品信息。\n{format_instructions}"),
("human", "{description}"),
]
).partial(format_instructions=parser.get_format_instructions()) # partial:提前写死一部分变量
chain = prompt | get_chat_model(temperature=0.0) | parser # 末端是 parser,直接拿到对象
result: DishInfo = chain.invoke({
"description": "昨天吃了一道水煮鱼,又麻又辣,主要有草鱼、豆芽和干辣椒,餐厅卖 68 元。",
})
print(f"解析后的 Python 类型:{type(result).__name__}")
print(f"菜名:{result.name}")
print(f"口味:{result.flavor}")
print(f"是否辣:{result.is_spicy}")
print(f"食材:{result.ingredients}") # 已经是真正的 list,可以直接用于业务逻辑
print(f"价格:{result.price_range}")
def demo_structured_output() -> None:
"""with_structured_output:利用模型原生 function calling,直接返回 Pydantic 对象。"""
print_sub("3. with_structured_output:模型原生结构化输出(推荐)")
llm = get_chat_model(temperature=0.0)
# 一行绑定 schema,模型会通过函数调用机制保证输出结构,比"提示词要求输出 JSON"更可靠
structured_llm = llm.with_structured_output(DishInfo)
result: DishInfo = structured_llm.invoke(
"帮我分析:芒果糯米饭,甜甜的,有糯米、芒果和椰浆,大概 15 到 25 块钱一份。"
)
# 返回的就是 DishInfo 对象,还能调用 .model_dump_json() 序列化成 JSON 存库
print(f"拿到对象:{result}")
print(f"序列化为 JSON:{result.model_dump_json()}")
def main() -> None:
print_title("03 输出解析器:从字符串到结构化 Python 对象")
demo_str_parser()
demo_pydantic_parser()
demo_structured_output()
print_sub("本课小结")
print("只要文本 → StrOutputParser;要 JSON 对象 → with_structured_output(首选)或 PydanticOutputParser。")
print("用 PydanticOutputParser 时:提示词必须包含 {format_instructions},链的最后一步必须是 parser。")
print("字段描述 description 写得越清楚,模型抽取越准确。")
if __name__ == "__main__":
main()
正文完