LangChain的基本输出解析器可以处理简单的字符串格式或基本JSON,但生产级应用经常会遇到更复杂、更难预测或偶有畸形的大语言模型(LLM)输出。有效提取结构化信息、处理变体以及从错误中恢复,需要更高级的解析方法。分析处理LLM响应的技术,以确保为后续任务可靠地获取数据。尽管进行了提示工程,LLM并不总是完全遵循请求的输出格式。它们可能会添加解释性文本、遗漏必需字段,或者生成语法不正确的结构(如畸形JSON)。仅依赖基本的字符串分割或标准JSON解析,可能导致应用脆弱且意外失败。运用Pydantic进行结构化校验一种有效方法是使用Pydantic模型定义期望的输出结构。Pydantic通过Python类型注解提供数据校验和设置管理。LangChain通过PydanticOutputParser与Pydantic集成。您定义一个Pydantic模型,表示您期望LLM返回的模式。解析器随后自动生成格式指令,将其包含在提示中,引导LLM生成正确的结构。更重要的是,它使用Pydantic模型解析LLM的字符串输出,校验数据类型,检查必需字段,并将原始文本转换为结构化Python对象。from pydantic import BaseModel, Field from typing import List, Optional from langchain.output_parsers import PydanticOutputParser from langchain_core.prompts import PromptTemplate from langchain_openai import ChatOpenAI # 或任何其他兼容的LLM # 定义你的目标数据结构 class ProductReview(BaseModel): product_name: str = Field(description="所评价产品的名称。") rating: int = Field(description="1到5的评分。", ge=1, le=5) summary: str = Field(description="评价的简要总结。") pros: Optional[List[str]] = Field(description="可选的优点列表。") cons: Optional[List[str]] = Field(description="可选的缺点列表。") # 设置一个解析器 parser = PydanticOutputParser(pydantic_object=ProductReview) # 定义包含格式指令的提示模板 prompt_template = """ 分析以下产品评价并提取重要信息。 {format_instructions} 评价文本: {review_text} """ prompt = PromptTemplate( template=prompt_template, input_variables=["review_text"], partial_variables={"format_instructions": parser.get_format_instructions()} ) # 示例用法 # llm = ChatOpenAI(model="gpt-3.5-turbo", temperature=0) # chain = prompt | llm | parser # review = "This laptop is amazing! Super fast (5/5), great screen. Only downside is the battery life could be better." # structured_output = chain.invoke({"review_text": review}) # print(structured_output) # print(f"Rating: {structured_output.rating}")使用PydanticOutputParser不仅提供解析,还基于您的模型定义进行自动校验(例如,确保rating在1到5之间)。如果输出不符合要求,Pydantic会抛出校验错误,这通常比一般解析失败更具信息量。实现解析失败的重试逻辑有时,LLM生成的输出几乎正确,但初始解析失败(例如,JSON中缺少逗号,额外评论)。此时,您可以实现重试逻辑,而不是立即失败。LangChain提供RetryOutputParser以及更复杂的RetryWithErrorOutputParser。这些解析器包装一个主解析器(如PydanticOutputParser或SimpleJsonOutputParser)。如果主解析器失败,重试解析器会捕获异常。然后,它格式化一个新的提示,包含原始提示、有问题的输出以及错误信息。这个新提示指示LLM根据错误纠正其之前的输出。这种重试循环可以显著提高从不太可靠的LLM响应中提取结构化数据的成功率。from langchain.output_parsers import PydanticOutputParser, RetryWithErrorOutputParser from langchain_core.prompts import PromptTemplate from langchain_openai import ChatOpenAI # 或任何其他兼容的LLM # 假定ProductReview Pydantic模型来自之前的示例 llm = ChatOpenAI(model="gpt-3.5-turbo", temperature=0) main_parser = PydanticOutputParser(pydantic_object=ProductReview) # 使用重试机制包装主解析器 retry_parser = RetryWithErrorOutputParser.from_llm( parser=main_parser, llm=llm, # 用于尝试纠正的LLM max_retries=2 # 重试次数 ) # 提示模板最初保持不变 prompt_template = """ 分析以下产品评价并提取重要信息。 {format_instructions} 评价文本: {review_text} """ prompt = PromptTemplate( template=prompt_template, input_variables=["review_text"], partial_variables={"format_instructions": main_parser.get_format_instructions()} # 仍使用主解析器的指令 ) # 链现在使用重试解析器 # chain = prompt | llm | retry_parser # 链构建 # review = "Decent phone (4 stars). Good camera. Wish it had more storage." # try: # structured_output = chain.invoke({"review_text": review}) # print("成功解析:") # print(structured_output) # except Exception as e: # print(f"重试后解析失败:{e}")RetryWithErrorOutputParser智能地使用来自失败解析尝试的错误信息来引导LLM进行纠正,使其比仅仅要求LLM“再试一次”更有效。运用结构化输出能力(函数/工具调用)现代LLM(如OpenAI的GPT系列、Anthropic的Claude 3、Google的Gemini)越来越多地直接通过通常被称为“函数调用”或“工具调用”的机制支持结构化输出生成。不再要求LLM将其文本输出格式化为字符串内的JSON,而是您随同提示提供期望输出结构的模式(通常是JSON Schema)。LLM随后返回一个遵循该模式的专用结构化对象(通常是JSON),与文本响应分离。LangChain通过JsonOutputFunctionsParser、PydanticOutputFunctionsParser等解析器,或在调用兼容LLM时直接指定工具/函数来集成这些能力。这种方法通常比解析格式化文本更可靠,因为LLM专门设计用于生成符合所提供模式的输出。from langchain_openai import ChatOpenAI from langchain_core.pydantic_v1 import BaseModel, Field from langchain_core.utils.function_calling import convert_to_openai_function # 定义Pydantic模型(在某些LangChain版本中使用pydantic_v1的BaseModel以保持兼容性) class WeatherInfo(BaseModel): """关于特定地点天气的信息。""" location: str = Field(description="城市和州,例如旧金山,加利福尼亚州") temperature: int = Field(description="当前华氏温度") forecast: str = Field(description="简要天气预报(例如晴朗、多云)") # 将Pydantic模型转换为OpenAI函数/工具格式 openai_functions = [convert_to_openai_function(WeatherInfo)] # 使用函数定义初始化LLM llm = ChatOpenAI(model="gpt-4o", temperature=0) llm_with_functions = llm.bind_functions(functions=openai_functions, function_call={"name": "WeatherInfo"}) # 调用示例 # prompt = "What's the weather like in Boston today?" # ai_message = llm_with_functions.invoke(prompt) # 从“additional_kwargs”或“tool_calls”属性中提取结构化数据 # function_call_args = ai_message.additional_kwargs.get("function_call", {}).get("arguments") # if function_call_args: # # LangChain也提供解析器来自动处理此问题 # # 用于手动检查: # import json # weather_data = json.loads(function_call_args) # print(weather_data) # else: # print("LLM未调用该函数。") 当可用时,使用LLM的原生结构化输出能力通常是最有效的方法。它减少了解析试图遵循格式指令的自由格式文本固有的歧义性。组合解析器与自定义逻辑对于极复杂的情况,您可能需要链接多个解析器,甚至通过继承LangChain的BaseOutputParser实现自定义解析逻辑。例如,LLM可能会生成一个同时包含文本摘要和JSON块的响应。您可以使用自定义解析器首先使用正则表达式或字符串操作提取JSON块,然后将该块输入到PydanticOutputParser或JsonOutputParser中。开发自定义解析器需要实现parse方法(以及可能的get_format_instructions)。这提供最大灵活性,但需要仔细设计和测试。from langchain_core.output_parsers import BaseOutputParser from typing import Any import re import json class CustomJsonExtractorParser(BaseOutputParser): """ 一个自定义解析器,用于提取```json ... ```中包含的JSON块并解析它。 """ def parse(self, text: str) -> Any: match = re.search(r"```json\s*(.*?)\s*```", text, re.DOTALL) if not match: raise ValueError(f"未能在文本中找到JSON块:{text}") json_str = match.group(1) try: return json.loads(json_str) except json.JSONDecodeError as e: raise ValueError(f"解析JSON失败:{e}\nJSON字符串:{json_str}") def get_format_instructions(self) -> str: return "请将JSON输出包含在```json\n ... \n```标签内。" # 示例用法 # custom_parser = CustomJsonExtractorParser() # llm_output = "分析如下:\n```json\n{\n \"key\": \"value\",\n \"number\": 123\n}\n```\n如果需要更多详细信息,请告诉我。" # parsed_data = custom_parser.parse(llm_output) # print(parsed_data)处理不可恢复的错误即使有重试和可靠的解析器,某些LLM输出仍可能无法解析或校验失败。生产系统必须优雅地处理这些情况。策略包括:日志记录: 记录有问题的输出和解析错误,以供后续分析以及可能的微调或提示调整。默认值/回退: 返回默认对象或None,以允许应用程序流程继续,可能会将结果标记为不确定。错误传播: 将错误沿着调用栈上传,由更高级别的应用程序逻辑处理。人工干预: 对于关键应用,将失败的解析尝试路由到人工审核队列。选择正确的策略取决于应用程序的错误容忍度和提取数据的重要性。通过运用这些高级解析策略,借助Pydantic进行结构化和校验,实现重试逻辑,使用原生结构化输出功能,以及为不可恢复的错误做准备,您可以构建更具弹性和可靠的LangChain应用,能够处理生产环境中LLM输出固有的可变性。这些技术是本课程中讨论的复杂链和代理的基本构成要素。