文章目录
- 一、提示词模版(prompts)
- 二、输出解析器(Output Parsers)
一、提示词模版(prompts)
1.1 什么是提示词模版
在大模型应用开发中,我们通常需要根据不同的用户输入来构造不同的Prompt。如果每次都手动拼接字符串,不仅代码冗余,还容易出错、无法统一管控、不支持流式/异步LCEL链路。
提示词模板(Prompt Template)核心思想:固定指令与动态变量解耦,预定义模板骨架,运行时注入变量自动生成合规PromptValue,无缝对接LLM/ChatModel。
核心价值
| 优势 | 说明 |
|---|---|
| 复用性 | 一次定义模板,多业务/多会话复用,无需重复写指令 |
| 参数化 | 支持静态变量、动态函数变量、批量插值 |
| 标准化 | 统一托管所有提示词,支持文件加载、版本管理、A/B测试 |
| LCEL原生兼容 | 全部模板实现Runnable协议,可直接用` |
| 自动校验 | 自动检测缺失变量、变量名不匹配,提前抛错 |
1.2 提示词模板类型完整概览
1)基础底层模板
PromptTemplate:纯字符串模板,适配老式文本LLM(不推荐现代对话场景)ChatPromptTemplate:聊天消息模板,适配GPT/Qwen/Llama等ChatModel,企业开发首选MessagesPlaceholder:消息列表插槽,专门承载对话历史List[BaseMessage]
2)进阶少样本模板
FewShotPromptTemplate:纯文本少样本,仅兼容老式LLMFewShotChatMessagePromptTemplate:聊天场景专用少样本模板,兼容ChatPromptTemplate,生产对话机器人必用
1.3 提示词模板统一工作原理
所有模板统一处理链路:
变量字典 → Template.invoke() → PromptValue → LLM/ChatModel
⚠️ 废弃旧API:.format() 仅返回字符串,不兼容LCEL;新标准统一使用.invoke(),输出PromptValue中间对象,自动兼容文本模型/聊天模型,支持流式、异步
标准工作流程
- 输入:传入变量字典
{key: value};partial预填充变量可省略传参 - 格式化:模板自动匹配占位符注入变量,自动校验变量完整性
- 输出:生成
PromptValue(统一中间格式,分StringPromptValue/ChatPromptValue) - 调用模型:PromptValue可直接丢给LLM/ChatModel,无需手动转换
核心优势:一套模板同时兼容文本生成、多轮对话,无缝接入LCEL管道。
1.4 PromptTemplate 纯字符串模板(老式LLM专用)
仅适用于非对话文本生成(摘要、翻译、简单文案),现代聊天业务优先使用ChatPromptTemplate。
1. 基础两种创建方式
from langchain_core.prompts import PromptTemplate
# 方式1:from_template(自动解析占位符,无需手动写input_variables,推荐)
prompt = PromptTemplate.from_template(
"讲一个关于{topic}的{adjective}小故事,控制在50字内"
)
# 方式2:构造函数手动声明变量(模板复杂、需要严格校验时使用)
prompt = PromptTemplate(
template="讲一个关于{topic}的{adjective}小故事,控制在50字内",
input_variables=["topic", "adjective"] # 必须和模板占位符完全一致,缺省抛错
)
# LCEL标准调用 invoke(返回PromptValue)
formatted_val = prompt.invoke({"topic": "人工智能", "adjective": "治愈"})
print(formatted_val.text)
# 输出:讲一个关于人工智能的治愈小故事,控制在50字内
2. partial 预填充变量
两类预填充方案,生产场景清晰区分:
partial_variables:定义模板时静态固定常量,写入模板定义,全局统一默认值.partial():运行时动态绑定,支持延迟函数(获取当前时间、请求ID、配置动态值),支持链式多次partial
from datetime import datetime
from langchain_core.prompts import PromptTemplate
# 方案1:构造函数 partial_variables 静态固定
prompt_static = PromptTemplate(
template="请用{style}解释{concept},当前日期:{now}",
input_variables=["concept"],
partial_variables={
"style": "通俗大白话",
"now": "2026-7-13",
}
)
print(prompt_static.invoke({"concept": "递归"}).text)
# 方案2:.partial() 动态绑定,支持字符串 + 无参函数(高阶生产用法)
prompt_base = PromptTemplate.from_template("请用{style}解释{concept},当前日期:{now}")
# 延迟函数:每次调用模板实时计算,而非固定死值
prompt_dynamic = prompt_base.partial(
style="专业技术术语",
now=lambda: datetime.now().strftime("%Y-%m-%d")
)
print(prompt_dynamic.invoke({"concept": "LCEL"}).text)
# 链式多次partial(分步绑定变量,接口分层场景常用)
step1 = prompt_base.partial(style="极简短句")
step2 = step1.partial(now=lambda: datetime.now().strftime("%m月%d日"))
print(step2.invoke({"concept": "RAG"}).text)
partial 与 partial_variables 完整决策表
| 场景 | 选用方案 | 优势 |
|---|---|---|
| 固定值写死、全局统一规范(如统一输出风格) | partial_variables | 模板自描述,一眼看清默认常量,适合沉淀通用模板 |
| 运行时才确定值(接口入参、配置文件读取) | .partial() | 灵活派生多个模板实例,不污染原模板 |
| 需要动态实时值(当前时间、请求ID、随机参数) | .partial(变量=lambda函数) | 每次invoke动态刷新,静态partial_variables无法实现 |
| 分层分步绑定变量(多层业务链路逐步填充) | .partial() | 支持链式调用,拆分参数填充逻辑 |
1.5 ChatPromptTemplate 聊天模板(现代业务首选)
适配所有对话大模型,承载system/human/ai/assistant多角色消息列表,是对话机器人、RAG、Agent的基础。
1. 基础from_messages创建(推荐元组写法)
from langchain_core.prompts import ChatPromptTemplate
# 元组创建:(角色标识, 模板字符串),支持变量插值
chat_prompt = ChatPromptTemplate.from_messages([
("system", "你是资深{role},回答简洁不超过三句话"),
("human", "请解答{topic}相关问题"),
("ai", "没问题,请说出你的具体疑问"),
("human", "{question}")
])
# LCEL标准invoke,返回ChatPromptValue(内置messages消息列表)
msg_val = chat_prompt.invoke({
"role": "Python全栈讲师",
"topic": "装饰器",
"question": "装饰器底层原理是什么?"
})
# 打印结构化消息列表(调试必备)
print(msg_val.messages)
完整合法角色标识
| 角色标识 | 对应消息类 | 适用场景 |
|---|---|---|
"system" | SystemMessage | 设定AI角色、全局规则、约束条件 |
"human" | HumanMessage | 用户提问、用户输入 |
"ai" / "assistant" | AIMessage | AI历史回复,两种标识全模型兼容 |
2. Message对象混合写法(固定无变量消息专用)
纯静态消息用对象更直观,可和变量元组混合使用:
from langchain_core.messages import SystemMessage, HumanMessage, AIMessage
chat_prompt = ChatPromptTemplate.from_messages([
SystemMessage(content="你是严谨的LangChain技术讲师,禁止编造知识点"),
HumanMessage(content="什么是Runnable协议?"),
AIMessage(content="Runnable是LangChain所有组件统一执行标准"),
("human", "详细解释{component}底层执行流程")
])
res = chat_prompt.invoke({"component": "MessagesPlaceholder"})
print(res.messages)
选择建议:无变量固定消息 → Message对象;含{变量}动态插值 → 元组写法。
1.6 MessagesPlaceholder 对话历史插槽(修正致命生产坑)
核心作用:预留插槽,批量注入完整历史消息列表List[BaseMessage],是带记忆对话系统核心组件。
关键强制规则(线上高频报错点)
- 传入
variable_name对应的值必须是消息对象列表,不能传字符串;传字符串会直接打印[HumanMessage(...)]垃圾文本 - 搭配记忆组件时,记忆必须开启
return_messages=True - 等价简写
("placeholder", "{history}")仅v0.1+支持,旧版本不兼容,生产优先显式MessagesPlaceholder保证兼容性
标准完整示例(对接记忆的规范写法)
from langchain_core.prompts import ChatPromptTemplate, MessagesPlaceholder
from langchain_core.messages import AIMessage, HumanMessage
# 显式创建占位符(生产推荐,兼容性拉满)
prompt = ChatPromptTemplate.from_messages([
("system", "你是AI对话助手,结合上下文连贯回答"),
MessagesPlaceholder(variable_name="chat_history"), # 历史消息插槽
("human", "{user_input}")
])
# 注入标准消息列表
msg_result = prompt.invoke({
"chat_history": [
HumanMessage(content="什么是LCEL?"),
AIMessage(content="LCEL是LangChain表达式语言,用|串联组件"),
],
"user_input": "LCEL相比旧LLMChain优势是什么?"
})
print(msg_result.messages)
# 兼容简写写法(开发调试可用,大型项目不推荐)
prompt_short = ChatPromptTemplate.from_messages([
("system", "你是AI对话助手"),
("placeholder", "{chat_history}"),
("human", "{user_input}")
])
高频报错坑
ValueError: Unsupported chat history format: <class ‘str’>
原因:把拼接后的字符串传给占位符,解决方案:统一使用return_messages=True输出消息列表。
1.7 FewShot 少样本提示模板
1. FewShotPromptTemplate(仅老式文本LLM使用,不用于对话)
from langchain_core.prompts import FewShotPromptTemplate, PromptTemplate
from langchain_openai import OpenAI # 老式文本模型,非ChatOpenAI
# 1. 定义示例数据
examples = [
{"input": "高兴", "output": "开心"},
{"input": "难过", "output": "悲伤"},
{"input": "生气", "output": "愤怒"}
]
# 2. 单条示例格式化模板
example_formatter = PromptTemplate(
template="输入: {input}\n输出: {output}",
input_variables=["input", "output"]
)
# 3. 组装少样本模板
few_shot_prompt = FewShotPromptTemplate(
examples=examples,
example_prompt=example_formatter,
prefix="以下是同义词转换示例,严格模仿格式输出,不要额外文字",
suffix="\n输入: {input}\n输出:",
input_variables=["input"],
example_separator="\n\n" # 示例之间分隔符,默认两行换行
)
# 调用模板 + 老式文本LLM
prompt_val = few_shot_prompt.invoke({"input": "兴奋"})
llm = OpenAI(model="gpt-3.5-turbo-instruct")
res = llm.invoke(prompt_val)
print(res)
2. 企业对话场景专用:FewShotChatMessagePromptTemplate
用于ChatModel,示例是多轮对话消息,可直接嵌入ChatPromptTemplate,客服、意图识别高频使用:
import os
from dotenv import load_dotenv
from langchain_openai import ChatOpenAI
from langchain_core.output_parsers import StrOutputParser
from langchain_core.prompts import ChatPromptTemplate, FewShotChatMessagePromptTemplate
load_dotenv()
API_KEY = os.getenv('DEEPSEEK_API_KEY')
BASE_URL = os.getenv('DEEPSEEK_BASE_URL')
# 1. 对话式示例(human+ai成对)
chat_examples = [
{"input": "怎么退款", "output": "您可以进入订单页申请售后退款,1-3个工作日原路返回"},
{"input": "快递多久到", "output": "普通快递3-5天,顺丰次日达"},
]
# 2. 单条对话示例模板
example_prompt = ChatPromptTemplate.from_messages([
("human", "{input}"),
("ai", "{output}"),
])
# 3. 聊天少样本模板
few_shot_chat = FewShotChatMessagePromptTemplate(
examples=chat_examples,
example_prompt=example_prompt
)
# 4. 嵌入主对话模板(标准生产写法)
final_prompt = ChatPromptTemplate.from_messages([
("system", "你是电商客服,根据示例规范回答用户售后问题"),
few_shot_chat, # 插入批量对话示例
("human", "{user_question}")
])
# 创建模型实例
llm = ChatOpenAI(
model='deepseek-v4-flash',
api_key=API_KEY,
base_url=BASE_URL
)
# 完整LCEL链路串联示例
# LCEL管道:模板 | 聊天模型 | 字符串输出解析器
chain = final_prompt | llm | StrOutputParser()
# 直接调用链路,一键生成结果
result = chain.invoke({"user_question": "商品破损怎么处理?"})
print(result)
1.8 提示词模板生产级最佳实践
| 实践规范 | 落地说明 |
|---|---|
| 系统指令前置 | system消息放在模板最顶部,模型优先读取规则约束 |
| 示例数量控制 | 少样本2-5个最佳,过多提升token成本、稀释核心指令 |
| 变量名统一规范 | 历史对话统一chat_history,用户输入统一user_input,避免冲突 |
| 区分静态/动态模板 | 全局固定规则用partial_variables,接口动态参数用.partial() |
| 聊天业务禁用PromptTemplate | 全部使用ChatPromptTemplate,兼容多角色、历史消息插槽 |
| 模板外部文件托管 | 超长提示词存入txt文件,PromptTemplate.from_file("./prompt.txt")加载,便于运营修改 |
| 链路统一LCEL | 模板不单独调用format,全部接入 |
二、输出解析器(Output Parsers)
2.1 为什么需要输出解析器
在对话场景中,大模型直接返回自然语言文本即可。但在实际生产环境,我们通常需要将大模型用于非对话场景(如数据提取、内容生成流水线等),此时需要模型以结构化的格式(如JSON、列表)输出结果,以便程序进一步处理。
LangChain 在 langchain_core.output_parsers 包中提供了一系列输出解析器,全部实现 Runnable 协议,原生支持 LCEL | 管道串联,专门解决如何将模型的自然语言输出转换为结构化数据这一问题。
要让大模型输出结构化数据,有两种基本策略:
| 策略 | 方式 | 可靠性 | 适用场景 |
|---|---|---|---|
| Prompt约束 | 在提示词中注入自动生成的格式指令,引导模型输出指定格式文本,代码手动解析 | 依赖模型能力,小模型极易出现格式错误 | 通用,兼容所有开源/闭源模型,无厂商API限制 |
| 厂商原生能力 | 调用模型API内置结构化参数,底层强制输出规范结构 | 极高,API层校验拦截非法格式 | OpenAI、Gemini、通义千问等新版商用模型,企业生产首选 |
下面分别介绍这两种策略的具体实现。
2.2 策略一:通过Prompt约束(JsonOutputParser)
JsonOutputParser 的工作原理是:将你定义的JSON Schema转化为一段标准化格式说明文字,插入到Prompt中,引导模型输出纯净JSON字符串;解析器负责清洗文本、提取JSON、转为Python字典。
使用标准步骤
- 通过Pydantic BaseModel定义目标JSON数据结构
- 实例化
JsonOutputParser,绑定Pydantic模型 - 调用
get_format_instructions()获取格式约束文本,注入System提示词 - 使用LCEL串联
prompt | llm | parser,一键完成调用+解析
完整可运行代码
import os
from langchain_core.prompts import ChatPromptTemplate
from langchain_openai import ChatOpenAI
from langchain_core.output_parsers import JsonOutputParser
from pydantic import BaseModel, Field
from dotenv import load_dotenv
load_dotenv()
API_KEY = os.getenv('DEEPSEEK_API_KEY')
BASE_URL = os.getenv('DEEPSEEK_BASE_URL')
# 创建模型实例
llm = ChatOpenAI(
model='deepseek-v4-flash',
api_key=API_KEY,
base_url=BASE_URL
)
# 1. 定义目标JSON结构,优化字段描述,避免模型数据错位
class Prime(BaseModel):
prime: list[int] = Field(description="素数数字列表,5个1000-100000之间的素数")
count: list[int] = Field(description="对应素数以内所有素数的总个数,与prime列表一一对应")
# 2. 构造JSON解析器
json_parser = JsonOutputParser(pydantic_object=Prime)
# 获取给模型的强制格式说明
format_rules = json_parser.get_format_instructions()
# 3. 构建聊天提示词模板,注入格式约束
prompt = ChatPromptTemplate.from_messages([
("system", "严格遵守以下输出规范,禁止输出任何解释、换行、markdown代码块:\n{format_instr}"),
("human", "任意生成5个1000-100000之间的素数,并标出小于该素数的素数个数")
])
# 4. LCEL标准链式调用:模板→模型→解析器,一步完成
chain = prompt | llm | json_parser
parsed_res = chain.invoke({"format_instr": format_rules})
print(parsed_res)
print(type(parsed_res)) # <class 'dict'>,普通字典
关键参数 partial 完整释义
构造解析器支持入参 partial: bool,默认 False,专门适配流式输出场景:
partial=True:流式增量解析
-
- 适用场景:
stream()实时打字流式输出,模型分段吐出不完整JSON - 逻辑:自动用
parse_json_markdown提取文本中可用JSON片段,能解析的字段直接返回,缺失字段填充空值,不会抛出异常,等待后续token补全
- 适用场景:
partial=False(默认):完整结果强校验
-
- 适用场景:
invoke()一次性完整输出 - 逻辑:要求模型返回闭合、无缺失、语法正确的完整JSON;格式错误、字段缺失直接抛出
OutputParserException,快速定位模型输出问题
- 适用场景:
一句话区分:partial=True 适配边生成边流式解析;partial=False 适配生成完成后一次性严格校验。
注意事项
- Prompt约束依赖模型的理解能力。7B/13B参数量较小的开源模型极易输出带注释、markdown代码块、多余文字的不规范JSON,导致解析失败。对于生产环境,建议优先使用策略二厂商原生结构化方案。
- 捕获解析异常兜底(生产必备):
from langchain_core.exceptions import OutputParserException
try:
parsed_res = chain.invoke({"format_instr": format_rules})
except OutputParserException as e:
print(f"JSON格式解析失败:{e}")
# 业务可实现自动重试、文本清洗、人工兜底逻辑
2.3 策略二:通过厂商原生能力
主流大模型厂商的API已经提供了专门的结构化输出参数,在API层面强制模型输出符合指定Schema的结构化数据,相比Prompt约束稳定性大幅提升。
OpenAI原生parse接口示例
from openai import OpenAI
from pydantic import BaseModel
client = OpenAI()
class CalendarEvent(BaseModel):
name: str
date: str
participants: list[str]
# 重要限制:仅 gpt-4o / gpt-4o-mini 新版接口支持,gpt-3.5-turbo不兼容该写法
response = client.chat.completions.parse(
model="gpt-4o-mini",
messages=[
{"role": "user", "content": "Alice and Bob are going to a science fair on Friday."}
],
response_format=CalendarEvent
)
# 直接返回校验完成的Pydantic实体对象,无需手动解析
event = response.choices[0].message.parsed
print(event)
# CalendarEvent(name='Science Fair', date='Friday', participants=['Alice', 'Bob'])
注意:response_format 不是普通输入参数,作用分为两层:
- 约束层:告诉API强制模型按照Pydantic结构生成纯JSON文本,禁止多余描述;
- 解析层:API内部自动完成JSON解析+Pydantic类型校验,结果存入
message.parsed;
输入仅由messages控制,response_format只改变输出格式逻辑。
Google Gemini示例
from google import genai
from pydantic import BaseModel
class CalendarEvent(BaseModel):
name: str
date: str
participants: list[str]
client = genai.Client(
api_key="sk-xxxx",
http_options={
"base_url": "https://api.openai-proxy.org/google"
},
)
response = client.models.generate_content(
model="gemini-2.5-flash-lite",
contents="Alice and Bob are going to a science fair on Friday.",
config={
"response_mime_type": "application/json",
# 将Pydantic模型转为标准JSON Schema,下发给模型做输出约束
"response_json_schema": CalendarEvent.model_json_schema(),
},
)
# 手动将返回JSON字符串校验转为Pydantic对象
event = CalendarEvent.model_validate_json(response.text)
print(event)
说明:
CalendarEvent.model_json_schema():将Pydantic类转换标准JSON Schema,传递给模型接口,约束输出字段、类型、必填项;CalendarEvent.model_validate_json(response.text):对模型返回的JSON字符串做类型、必填项校验,不符合规则抛出ValidationError。
厂商原生方案痛点
OpenAI、Gemini、文心一言各自结构化入参、返回格式完全不统一,如果业务需要切换模型,必须重写整套调用逻辑,耦合度极高;这正是LangChain封装 with_structured_output 的核心动机。
2.4 LangChain统一封装:with_structured_output
LangChain 在 ChatModel 上提供 with_structured_output() 方法,屏蔽所有厂商API差异,一套代码兼容OpenAI、Gemini、通义千问、本地Qwen等支持结构化输出的模型,调用方式完全统一,是企业生产标准方案。
补充核心参数 method
with_structured_output(schema, method="xxx") 两种底层执行模式:
method="json_mode":开启模型原生JSON输出模式,强制返回纯JSON字符串,后序自动校验匹配Schema;兼容更多型号模型method="function_calling":底层调用Function Calling工具机制,约束力度最强,支持多层嵌套复杂Schema,推荐业务数据抽取场景
完整标准代码示例
import os
from langchain_openai import ChatOpenAI
from pydantic import BaseModel
# 1. 初始化LLM
llm = ChatOpenAI(
model="gpt-4o-mini",
temperature=0.0,
base_url=os.getenv("OPENAI_BASE_URL"),
api_key=os.getenv("OPENAI_API_KEY")
)
# 2. 定义Pydantic数据结构
class CalendarEvent(BaseModel):
name: str = Field(description="活动全称")
date: str = Field(description="活动举办日期")
participants: list[str] = Field(description="参与人员名单")
# 3. 封装结构化Runnable,指定底层调用方式
structured_llm = llm.with_structured_output(
schema=CalendarEvent,
method="function_calling"
)
# 4. 直接调用,无需额外解析器,原生返回Pydantic对象
result = structured_llm.invoke("Alice and Bob are going to a science fair on Friday.")
print(result)
# CalendarEvent(name='Science Fair', date='Friday', participants=['Alice', 'Bob'])
print(type(result)) # <class 'CalendarEvent'>
核心优势
- 跨模型兼容:切换OpenAI/Anthropic/Gemini仅需修改LLM初始化代码,结构化调用逻辑完全不动;
- 内置校验:底层API强制规范输出,极少出现解析报错,减少异常兜底代码;
- 原生支持LCEL、流式stream、异步ainvoke,适配高并发业务。
2.5 其他常用输出解析器
除JSON结构化解析外,LangChain内置多种适配不同业务格式的解析器,全部位于 langchain_core.output_parsers
官方文档地址:https://reference.langchain.com/python/langchain-core/output-parsers
1. StrOutputParser 基础文本解析器
最简单的解析器,唯一作用:提取 AIMessage.content 模型原始文本,去除消息包装对象,返回纯字符串;基础对话、文案生成链路必备。
from langchain_core.output_parsers import StrOutputParser
from langchain_core.prompts import ChatPromptTemplate
parser = StrOutputParser()
# LCEL标准串联写法,日常开发高频使用
prompt = ChatPromptTemplate.from_messages([("human", "详细讲解LCEL语法")])
chain = prompt | llm | StrOutputParser()
text_result = chain.invoke({})
print(type(text_result)) # str
2. PydanticOutputParser 强校验解析器
在介绍这个解析器之前,我们需要先了解Pydantic是什么。
Pydantic简介
Pydantic 是 Python 主流数据校验库,依托类型注解自动完成数据类型校验、自动类型转换。简单来说,自定义数据模型类后,Pydantic会强制校验输入数据的结构、字段类型、自定义业务规则。
最小使用案例
from pydantic import BaseModel, Field
# 定义一个数据模型
class User(BaseModel):
name: str # 名字必须是字符串
age: int # 年龄必须是整数
email: str = Field(description="用户的邮箱地址")
# 创建实例 - 数据会自动校验和转换
user = User(name="张三", age=25, email="[email protected]")
print(user.name) # 张三
print(user.age) # 25
# 类型不匹配时,Pydantic会自动尝试转换
user2 = User(name="李四", age="30") # age传入字符串"30",自动转为整数30
print(user2.age) # 30(已自动转换为int)
# 数据不合法时会抛出ValidationError
# User(name="王五", age="abc") # 报错:无法将"abc"转为整数
Pydantic的核心价值
- 类型安全:自动校验数据类型,拦截非法数据
- 自动转换:兼容数字字符串、布尔字符串等可转换格式
- 清晰的错误提示:校验失败输出精准字段错误信息
- JSON双向转换:支持模型转JSON、JSON反向解析为模型实例
PydanticOutputParser 作用
它依靠Prompt引导模型输出合规JSON,最终返回完整Pydantic实体对象,区别于JsonOutputParser仅返回字典;支持字段内置约束、自定义校验器,适合对数据精度有严格要求的场景。
PydanticOutputParser完整示例
from langchain_core.output_parsers import PydanticOutputParser
from langchain_core.prompts import ChatPromptTemplate
from pydantic import BaseModel, Field, field_validator
from langchain_openai import ChatOpenAI
class MovieReview(BaseModel):
"""电影评论结构化输出模型"""
title: str = Field(description="电影完整标题")
rating: int = Field(description="评分,1-10分", ge=1, le=10)
summary: str = Field(description="简短剧情简介,50字以内")
recommended: bool = Field(description="是否推荐观看")
# 自定义字段校验器
@field_validator('rating')
@classmethod
def rating_must_be_valid(cls, v):
if v < 1 or v > 10:
raise ValueError('评分必须在1-10区间内')
return v
llm = ChatOpenAI(model="gpt-4o-mini", temperature=0)
# 实例化Pydantic解析器
parser = PydanticOutputParser(pydantic_object=MovieReview)
format_instr = parser.get_format_instructions()
# 组装提示词
prompt = ChatPromptTemplate.from_messages([
("system", "{format_instr}"),
("human", "客观评价电影《盗梦空间》")
])
# LCEL链路
chain = prompt | llm | parser
result = chain.invoke({"format_instr": format_instr})
print(f"电影: {result.title}, 评分: {result.rating}/10")
执行流程说明
chain = prompt | llm | parser管道顺序执行;- 解析器接收模型文本输出,先提取JSON字符串转为Python字典;
- 执行
MovieReview.model_validate(字典)做全量校验:
-
- 先校验内置约束
ge=1, le=10; - 再执行自定义
@field_validator校验逻辑;
- 先校验内置约束
- 校验失败,LangChain包装抛出
OutputParserException,捕获后可重试。
_PYDANTIC_FORMAT_INSTRUCTIONS VS JSON_FORMAT_INSTRUCTIONS 区别
两者都是注入到System提示词、引导模型输出JSON的文本约束,核心差异:
_PYDANTIC_FORMAT_INSTRUCTIONS:附带所有字段描述、数值约束、自定义校验说明;允许少量辅助文字,只要求主体为JSON;JSON_FORMAT_INSTRUCTIONS:强硬性约束,三条强制规则:
-
- 只能输出JSON,禁止任何自然语言解释;
- 禁止使用 ```json markdown代码块包裹;
- 顶层只能是单一对象/数组,不能分段输出。
核心关键点:二者都只是提示词文本,仅作用于引导模型;最终模型输出依然是字符串,必须经过解析器转换为Python数据。
2.6 自定义输出解析器
当内置解析器无法满足业务特殊格式(逗号列表、表格、自定义标记文本)时,继承 BaseOutputParser 实现自定义解析逻辑。
父类强制要求实现两个抽象方法,缺少任意一个会直接报错:
parse(text: str):核心解析逻辑,入参为模型纯文本,返回自定义数据结构;get_format_instructions():返回给模型的格式约束提示词。
完整代码
from typing import List
from langchain_core.output_parsers import BaseOutputParser
class CommaListParser(BaseOutputParser[List[str]]):
"""自定义解析器:将英文逗号分割的文本清洗为字符串列表"""
def parse(self, text: str) -> List[str]:
# 清洗换行、首尾空格、空元素
clean_text = text.strip().replace("\n", "")
item_list = [item.strip() for item in clean_text.split(",")]
# 过滤空字符串
return [item for item in item_list if item]
# 必须实现:告诉模型输出格式规范
def get_format_instructions(self) -> str:
return "仅输出结果,多个元素使用英文逗号分隔,不要换行、注释、额外文字、代码块"
# 使用示例
parser = CommaListParser()
result = parser.parse("苹果, 香蕉, 橘子, ")
print(result) # ['苹果', '香蕉', '橘子']
# LCEL管道串联使用
chain = prompt | llm | CommaListParser()
2.7 输出解析器选型指南
| 场景 | 推荐方案 | 核心原因 |
|---|---|---|
| 企业生产结构化数据抽取、表单生成 | llm.with_structured_output() | 厂商底层强制约束,解析失败率极低,跨模型兼容 |
| 本地开源模型、无原生结构化API,需要字段强校验 | PydanticOutputParser | 完整Pydantic类型+自定义校验,全模型通用 |
| 简单JSON字典提取,无复杂字段校验逻辑 | JsonOutputParser | 轻量简洁,仅输出普通字典 |
| 普通对话、文案生成、摘要,仅需要纯文本 | StrOutputParser | 最轻量,无额外性能开销 |
| 自定义特殊分割格式、行业专属文本规范 | 继承 BaseOutputParser 自定义解析器 | 解析逻辑完全自主可控,适配个性化业务 |
转载自 CSDN-专业IT技术社区
原文链接:https://blog.csdn.net/2301_79964758/article/details/162848433




