AI Agent白手起家38: 输出解析器的容错机制与自定义实战
纲要
- 容错机制
- 自动重试解析器
RetryOutputParser - 修复解析器
OutputFixingParser
- 自动重试解析器
- 自定义解析器
- 案例一:纳美星语转换解析器(基于正则)
- 案例二:布尔值解析器(继承
BaseOutputParser)
- 完整可运行代码:模拟错误场景,展示容错修复与自定义解析全流程
引言
在前几篇文章中,我们学习了如何用 LangChain 的输出解析器将大模型的自然语言输出转换为结构化的 Python 对象。但在实际应用中,模型输出的随机性(受 temperature 等参数影响)会导致解析失败,比如缺少字段、JSON 格式错误等。LangChain 为此提供了两种容错机制:自动重试和修复。此外,当内置解析器不满足需求时,开发者还可以通过自定义解析器实现任意格式的转换。本文将结合可运行代码,深入介绍这两项实用技能。
输出解析器的容错机制
自动重试:RetryOutputParser
当解析失败时,自动重试解析器会将原始提示词、错误输出以及错误信息一起重新交给大模型,让模型再次生成符合格式的回答。其核心思想是:将解析错误作为反馈信号,引导模型修正输出。
修复解析器:OutputFixingParser
与重试不同,修复解析器会直接调用另一个大模型,尝试将错误的输出修正为目标格式,不依赖原始提示词。它更适用于那些输出已经“离正确格式不远”的情况,例如 JSON 中少了一个花括号。
两种容错方式的对比:
| 特性 | RetryOutputParser | OutputFixingParser |
|---|---|---|
| 输入 | 原始提示词 + 错误输出 + 错误信息 | 错误输出 + 原解析器 |
| 依赖模型 | 需要(可用任意模型) | 需要(通常更轻量) |
| 适用场景 | 格式完全错误,需要重新理解任务 | 小范围格式瑕疵,快速修补 |
自定义解析器实战
纳美星语解析器
我们基于电影《阿凡达》中的纳美星语(Na’vi)构造一个趣味解析器,将英文问候语转换为带有纳美语特征的正则替换结果。该解析器不依赖模型,完全由正则规则驱动,并可直接作为链的一环。
布尔值解析器
通过继承BaseOutputParser实现一个简洁的布尔值解析器,将常见的“yes/no”、“true/false”等自然语言表达统一转换为 Python 的True或False,并支持自定义真值映射。
完整可运行代码
以下代码整合了容错机制与两种自定义解析器的完整示例,使用FakeListChatModel模拟大模型响应,因此无需任何 API Key 即可直接运行。
安装依赖:
pipinstalllangchain langchain-core langchain-community pydantic代码实现:
fromtypingimportAny,Dict,ListfrompydanticimportBaseModel,Fieldfromlangchain_core.promptsimportChatPromptTemplatefromlangchain_core.output_parsersimport(BaseOutputParser,PydanticOutputParser,OutputFixingParser,)fromlangchain_core.language_modelsimportBaseChatModelfromlangchain_community.chat_models.fakeimportFakeListChatModelfromlangchain.output_parsers.retryimportRetryOutputParserfromlangchain_core.messagesimportAIMessage# ===================== 1. 定义数据模型 =====================classAction(BaseModel):action:str=Field(description="下一步行动名称")action_input:str=Field(description="行动所需的输入")# ===================== 2. 模拟错误的模型响应 =====================# 正确的 JSONcorrect_json='{"action": "search", "action_input": "北京天气"}'# 错误响应:缺少 action_input 字段bad_json='{"action": "search"}'# 用于容错机制的假模型,第一次返回错误,第二次返回正确fix_model=FakeListChatModel(responses=[correct_json])# 用于重试的假模型(同样返回正确结果)retry_model=FakeListChatModel(responses=[correct_json])# 用于原始解析的模型(返回错误响应)base_model=FakeListChatModel(responses=[bad_json])# ===================== 3. 构建解析器与提示词 =====================parser=PydanticOutputParser(pydantic_object=Action)format_instructions=parser.get_format_instructions()prompt=ChatPromptTemplate.from_template("根据用户的问题制定下一步计划。\n{format_instructions}\n问题:{question}")prompt=prompt.partial(format_instructions=format_instructions)# ===================== 4. 演示解析失败 =====================print("=== 1. 无容错的原始解析(预期失败) ===")try:bad_chain=prompt|base_model|parser result=bad_chain.invoke({"question":"今天北京天气怎么样?"})print("解析成功:",result)exceptExceptionase:print("解析失败,错误信息:",str(e)[:100],"...")# ===================== 5. 使用 OutputFixingParser 修复 =====================print("\n=== 2. OutputFixingParser 修复示例 ===")fixing_parser=OutputFixingParser.from_llm(parser=parser,llm=fix_model,max_retries=1)# 手动构造一个 AIMessage 模拟错误输出bad_message=AIMessage(content=bad_json)try:fixed_result=fixing_parser.parse(bad_message.content)print("修复后解析成功:",fixed_result)exceptExceptionase:print("修复失败:",e)# ===================== 6. 使用 RetryOutputParser 重试 =====================print("\n=== 3. RetryOutputParser 重示例 ===")retry_parser=RetryOutputParser.from_llm(parser=parser,llm=retry_model,max_retries=2)# 需要提供原始提示词值和错误的输出prompt_value=prompt.invoke({"question":"今天北京天气怎么样?"})try:retry_result=retry_parser.parse_with_prompt(bad_message.content,prompt_value)print("重试后解析成功:",retry_result)exceptExceptionase:print("重试失败:",e)# ===================== 7. 自定义解析器:纳美星语转换 =====================importredefnavi_transformer(text:str)->str:"""将英文转换为带有纳美语特征的文本"""# 常见问候替换text=re.sub(r'\bhello\b','Kaltxì',text,flags=re.IGNORECASE)text=re.sub(r'\bthank you\b','Irayo',text,flags=re.IGNORECASE)# 形容词加前缀 le-text=re.sub(r'\b(beautiful|pretty|great)\b',r'le-\1',text,flags=re.IGNORECASE)# 动词加语气后缀text=re.sub(r'\b(go|come|see|help)\b',r'\1-ti',text,flags=re.IGNORECASE)# 句首添加特征前缀ifnottext.startswith('Tsa\'u'):text='Tsa\'u '+textreturntextclassNaviOutputParser(BaseOutputParser[str]):"""自定义纳美星语解析器"""defparse(self,text:str)->str:# 假设模型输出纯文本returnnavi_transformer(text)defget_format_instructions(self)->str:return"请输出一段简短的英文问候或陈述。"@propertydef_type(self)->str:return"navi_parser"print("\n=== 4. 纳美星语解析器 ===")navi_parser=NaviOutputParser()# 模拟模型的英文输出english_response="Hello, how can I help you? You are beautiful."navi_response=navi_parser.parse(english_response)print("英文输入:",english_response)print("纳美语输出:",navi_response)# ===================== 8. 自定义布尔值解析器 =====================classBooleanOutputParser(BaseOutputParser[bool]):"""将自然语言 yes/no 转换为布尔值"""true_values:List[str]=["yes","true","ok","1"]false_values:List[str]=["no","false","nok","0"]defparse(self,text:str)->bool:clean=text.strip().lower()ifcleaninself.true_values:returnTrueelifcleaninself.false_values:returnFalseelse:raiseValueError(f"无法识别的布尔值:{text},仅支持{self.true_values+self.false_values}")defget_format_instructions(self)->str:returnf"请仅返回以下单词之一:{', '.join(self.true_values+self.false_values)}"@propertydef_type(self)->str:return"boolean_parser"print("\n=== 5. 布尔值解析器 ===")bool_parser=BooleanOutputParser()print("输入 'yes' ->",bool_parser.parse("yes"))print("输入 'No' ->",bool_parser.parse("No"))# 自定义真值映射custom_bool_parser=BooleanOutputParser(true_values=["go"],false_values=["stop"])print("输入 'go' ->",custom_bool_parser.parse("go"))try:custom_bool_parser.parse("maybe")exceptValueErrorase:print("输入 'maybe' 触发错误:",e)运行结果解读
- 无容错解析:直接抛出
ValidationError,因为模型输出缺少action_input字段。 - 修复解析:
OutputFixingParser使用另一个模型(模拟为FakeListChatModel)将{"action": "search"}补全为{"action": "search", "action_input": "北京天气"},成功解析。 - 重试解析:
RetryOutputParser将错误输出和原始提示词一同交给模型,重新生成符合格式的结果。 - 纳美星语解析器:通过正则替换实现趣味转换,完全不依赖外部服务。
- 布尔值解析器:简洁地完成了自然语言到布尔值的映射,并支持自定义参数,错误输入会触发异常。
最佳实践与注意事项
- 容错机制会额外消耗模型调用次数,生产环境中需合理设置
max_retries。 - 修复解析器适合“小修小补”,如果输出与目标格式相差甚远,优先使用重试。
- 自定义解析器时,建议同时实现
get_format_instructions()方法,以便在提示词中注入格式要求。 - 布尔值解析器可通过配置文件动态注入真假映射,方便国际化。
总结
本文介绍了 LangChain 输出解析器的容错方案(重试与修复),并通过两个实战案例展示了如何构建自定义解析器。
这些工具让 LLM 应用在面对不可预测的模型输出时更加强健,也为特殊业务逻辑提供了灵活的解析能力。掌握它们,你就能在构建 AI Agent 时游刃有余地处理各种结构化输出需求。