调用大模型做结构化输出时,最让人头疼的问题之一就是输出格式不受控。你明明在提示词里写好了"请输出JSON",模型却偏偏在前后加一段说明文字,或者漏掉某个字段,甚至把引号写成中文的。一旦Pydantic解析器拿到这种输出,立刻抛出OutputParserException,整个应用链路直接崩掉。RetryWithErrorOutputParser就是LangChain针对这类问题给出的官方容错方案,它的核心思路很朴素:解析失败不可怕,把报错信息和原始输出一起发回给模型,让模型自己看着改。

一、为什么普通OutputParser会失败
先看一个典型的失败场景。假设我们定义了一个简单的结构体,要求模型返回一个包含笑话和提问的内容:
from langchain.output_parsers import PydanticOutputParser
from pydantic import BaseModel
class Joke(BaseModel):
setup: str
punchline: str
parser = PydanticOutputParser(pydantic_object=Joke)这个解析器内部会调用Pydantic做字段校验。如果模型返回的字符串是"好的,这是一个笑话:{"setup": "...", "punchline": "..."} 希望你喜欢",JSON提取逻辑可能还能勉强找到中间的JSON片段。但如果模型返回的JSON里punchline字段写成了punch_line,或者干脆漏掉了,Pydantic校验会直接失败,抛出类似"field required"的异常。
失败的原因通常分几类:第一类是格式性污染,模型在JSON外面加了自然语言包装;第二类是字段不匹配,模型自己发挥改了字段名;第三类是JSON语法错误,比如用了单引号、末尾多了逗号。这三类问题在温度参数较高或者提示词不够明确时出现频率明显上升。
二、RetryWithErrorOutputParser的工作机制
RetryWithErrorOutputParser的精妙之处在于它不自己解析,而是包装一个基础解析器,在失败时触发重试逻辑。它调用parse_with_prompt方法时,会先尝试用基础解析器解析模型的输出,如果解析失败,就构造一段修复提示词,包含三个关键信息:模型上一次的原始输出、解析失败的具体错误信息、以及最初要求的格式说明。
这三段内容组合成一条新的消息发回给模型。模型看到自己上次的输出和报错原因后,通常能准确定位问题并给出符合格式的修正版本。相当于给了模型一次自我纠错的机会,而不是让应用直接崩溃。
使用时需要特别注意一点:因为重试时要用到原始提示词中的格式指令,所以必须使用parse_with_prompt方法并传入完整的PromptValue,而不能只调用parse方法,后者拿不到原始提示词上下文,无法构造修复请求。
三、完整代码实战
下面是一个完整可运行的例子,展示如何把PromptTemplate、模型和RetryWithErrorOutputParser串联起来:
from langchain.output_parsers import RetryWithErrorOutputParser
from langchain.output_parsers import PydanticOutputParser
from langchain.prompts import PromptTemplate
from langchain_openai import OpenAI
from pydantic import BaseModel
class Joke(BaseModel):
setup: str
punchline: str
# 基础解析器,负责格式说明和实际解析
base_parser = PydanticOutputParser(pydantic_object=Joke)
# 容错解析器,包装基础解析器
retry_parser = RetryWithErrorOutputParser.from_llm(
parser=base_parser,
llm=OpenAI(temperature=0)
)
template = """根据用户的提问生成一个笑话。
{format_instructions}
提问: {query}"""
prompt = PromptTemplate(
template=template,
input_variables=["query"],
partial_variables={
"format_instructions": base_parser.get_format_instructions()
}
)
query = "讲一个关于程序员的笑话"
# 关键点:必须用 parse_with_prompt 并传入 prompt_value
response = "这是一个笑话:{'setup': '程序员', 'punchline': '没有对象'}"
result = retry_parser.parse_with_prompt(response, prompt.format_prompt(query=query))
print(result)上面代码中故意传了一个带污染文字且使用单引号的模拟输出。如果用基础解析器处理这个字符串,会直接抛异常。而parse_with_prompt会捕获错误,构造修复提示发给模型,拿到正确的JSON后再次解析,最终返回一个Joke实例。
在实际链路中,把最后两步换成正常流程即可:先用chain = prompt | llm拿到模型输出,再调用retry_parser.parse_with_prompt(output, prompt_value)。这样第一次输出格式正确时零开销,只有失败时才多一次模型调用。
四、容错方案对比与选择建议
除了Retry方案,还有几种常见做法。一种是OutputFixingParser,它同样在解析失败后把错误发回模型,但它只需要输出内容本身,不需要原始提示词,实现更简单;缺点是修复提示中缺少格式指令的完整上下文,对于字段漏缺类的错误修复成功率略低。另一种是Function Calling或结构化输出模式,直接从模型层面约束输出格式,稳定性最高,但依赖特定模型能力,不是所有场景都能用。
我的建议是按场景分层:模型支持工具调用时优先用结构化输出模式;需要兼容多种模型时,用RetryWithErrorOutputParser做兜底;对延迟敏感的场景可以先用正则清洗再做普通解析,失败后再触发重试。另外重试次数默认只有一次,如果业务对成功率要求极高,可以自己在外层再包一层循环,但要设置好最大重试次数,防止模型陷入反复输出错误格式的死循环。
最后提醒一点,格式指令的质量直接影响重试成功率。提示词里除了格式说明,最好明确写出"不要输出任何JSON以外的内容"这类约束,把问题在源头就压到最低,重试机制只作为最后一道防线。这样组合下来,结构化输出的稳定性基本能满足生产环境的要求。
RetryWithErrorOutputParser输出解析器大模型结构化输出修改时间:2026-09-03 20:52:54