LangChain 学习第 3 课 – 输出解析器(Output Parser)

1次阅读
没有评论

共计 3163 个字符,预计需要花费 8 分钟才能阅读完成。

大模型天生只会"吐字符串",但程序需要的是字符串、列表、JSON 对象……
输出解析器负责把模型输出转换成 Python 数据,这是让 LLM 接入业务代码的关键一步。

本节课学到:

  1. StrOutputParser:只要文本内容(最常用);
  2. PydanticOutputParser:用 Pydantic 模型约束输出结构(LCEL 经典写法);
  3. 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()
正文完
 0
Eric chan
版权声明:本站原创文章,由 Eric chan 于2026-10-10发表,共计3163字。
转载说明:除特殊说明外本站文章皆由CC-4.0协议发布,转载请注明出处。