如何创建自定义输出解析器 
在某些情况下,您可能希望实现一个自定义解析器,将模型的输出结构化为自定义格式。 有两种实现自定义解析器的方式:
- 使用 LCEL 中的 RunnableLambda或RunnableGenerator—— 我们强烈推荐大多数情况下使用这种方式
- 通过继承输出解析的基类之一 —— 这是一种较为困难的做法 这两种方法的区别主要是表面的,主要体现在触发哪些回调(例如 on_chain_start与on_parser_start),以及可追踪平台(如 LangSmith)中可视化可运行的 lambda 函数与解析器的方式。
可运行的 Lambda 函数和生成器 
推荐的解析方式是使用 可运行的 lambda 函数 和 可运行的生成器! 在这里,我们将创建一个简单的解析器,将模型的输出大小写反转。 例如,如果模型输出为:"Meow",解析器将产生 "mEOW"。
from typing import Iterable
from langchain_anthropic.chat_models import ChatAnthropic
from langchain_core.messages import AIMessage, AIMessageChunk
model = ChatAnthropic(model_name="claude-2.1")
def parse(ai_message: AIMessage) -> str:
    """解析 AI 消息。"""
    return ai_message.content.swapcase()
chain = model | parse
chain.invoke("hello")'hELLO!'TIP
LCEL 在使用 | 语法组合时,会自动将函数 parse 升级为 RunnableLambda(parse)。 如果您不喜欢这种方式,可以手动导入 RunnableLambda,然后运行 parse = RunnableLambda(parse)。
流式处理是否有效?
for chunk in chain.stream("tell me about yourself in one sentence"):
    print(chunk, end="|", flush=True)i'M cLAUDE, AN ai ASSISTANT CREATED BY aNTHROPIC TO BE HELPFUL, HARMLESS, AND HONEST.|不,因为解析器在解析输出之前会聚合输入。 如果我们想要实现流式解析器,可以让解析器接受输入的可迭代对象,并在结果可用时产生结果。
from langchain_core.runnables import RunnableGenerator
def streaming_parse(chunks: Iterable[AIMessageChunk]) -> Iterable[str]:
    for chunk in chunks:
        yield chunk.content.swapcase()
streaming_parse = RunnableGenerator(streaming_parse):::important 请使用 RunnableGenerator 包装流式解析器,因为我们可能不会自动使用 | 语法升级它。 :::
chain = model | streaming_parse
chain.invoke("hello")'hELLO!'让我们确认一下流式处理是否有效!
for chunk in chain.stream("tell me about yourself in one sentence"):
    print(chunk, end="|", flush=True)i|'M| cLAUDE|,| AN| ai| ASSISTANT| CREATED| BY| aN|THROP|IC| TO| BE| HELPFUL|,| HARMLESS|,| AND| HONEST|.|继承解析基类 
另一种实现解析器的方法是通过继承 BaseOutputParser、BaseGenerationOutputParser 或其他基本解析器中的一个,具体取决于您的需求。 总的来说,我们不建议大多数情况下使用这种方式,因为它会导致编写更多的代码而没有显著的好处。 最简单的输出解析器扩展了 BaseOutputParser 类,并且必须实现以下方法:
- parse:接受模型的字符串输出并对其进行解析
- (可选)_type:标识解析器的名称 当聊天模型或 LLM 的输出格式不正确时,可以抛出OutputParserException来指示由于坏输入而解析失败。使用此异常允许利用解析器的代码以一致的方式处理异常。解析器是可运行的! 🏃 因为 BaseOutputParser实现了Runnable接口,您通过这种方式创建的任何自定义解析器都将成为有效的 LangChain 可运行对象,并且将受益于自动的异步支持、批处理接口、日志支持等。
简单解析器 
下面是一个简单的解析器,可以解析表示布尔值的 字符串(例如 YES 或 NO)并将其转换为相应的 boolean 类型。
from langchain_core.exceptions import OutputParserException
from langchain_core.output_parsers import BaseOutputParser
# [bool] 描述了一个泛型的参数化。
# 它基本上指示了 parse 的返回类型
# 在这种情况下,返回类型要么是 True,要么是 False
class BooleanOutputParser(BaseOutputParser[bool]):
    """自定义布尔解析器。"""
    true_val: str = "YES"
    false_val: str = "NO"
    def parse(self, text: str) -> bool:
        cleaned_text = text.strip().upper()
        if cleaned_text not in (self.true_val.upper(), self.false_val.upper()):
            raise OutputParserException(
                f"BooleanOutputParser 期望输出值为 {self.true_val} 或 {self.false_val}(不区分大小写)。收到 {cleaned_text}。"
            )
        return cleaned_text == self.true_val.upper()
    @property
    def _type(self) -> str:
        return "boolean_output_parser"parser = BooleanOutputParser()
parser.invoke("YES")Truetry:
    parser.invoke("MEOW")
except Exception as e:
    print(f"Triggered an exception of type: {type(e)}")Triggered an exception of type: <class 'langchain_core.exceptions.OutputParserException'>让我们测试更改参数设置
parser = BooleanOutputParser(true_val="OKAY")
parser.invoke("OKAY")True让我们确认其他 LCEL 方法是否存在
parser.batch(["OKAY", "NO"])[True, False]await parser.abatch(["OKAY", "NO"])[True, False]from langchain_anthropic.chat_models import ChatAnthropic
anthropic = ChatAnthropic(model_name="claude-2.1")
anthropic.invoke("say OKAY or NO")AIMessage(content='OKAY')让我们测试我们的解析器是否有效!
chain = anthropic | parser
chain.invoke("say OKAY or NO")True:::note 该解析器将适用于 LLM 的输出(字符串)或聊天模型的输出(AIMessage)! :::
解析原始模型输出 
有时除了原始文本之外,模型输出中还有重要的元数据。一个例子是工具调用,其中用于传递给被调用函数的参数以单独的属性返回。如果您需要这种更精细的控制,可以子类化 BaseGenerationOutputParser 类。 这个类需要一个名为 parse_result 的方法。该方法接受原始模型输出(例如 Generation 或 ChatGeneration 的列表)并返回解析后的输出。 支持 Generation 和 ChatGeneration 使解析器可以同时处理常规 LLM 和聊天模型。
from typing import List
from langchain_core.exceptions import OutputParserException
from langchain_core.messages import AIMessage
from langchain_core.output_parsers import BaseGenerationOutputParser
from langchain_core.outputs import ChatGeneration, Generation
class StrInvertCase(BaseGenerationOutputParser[str]):
    """一个示例解析器,用于反转消息中字符的大小写。
    这只是一个演示示例解析器,目的是保持示例尽可能简单。
    """
    def parse_result(self, result: List[Generation], *, partial: bool = False) -> str:
        """将模型生成的列表解析为特定格式。
        Args:
            result: 要解析的 Generation 列表。假定这些 Generation 是单个模型输入的不同候选输出。
                许多解析器假定只传递了单个生成。
                我们将对此进行断言
            partial: 是否允许部分结果。这用于支持流式传输的解析器
        """
        if len(result) != 1:
            raise NotImplementedError("此输出解析器只能用于单个生成。")
        generation = result[0]
        if not isinstance(generation, ChatGeneration):
            # 表明这个只能用于聊天生成
            raise OutputParserException("此输出解析器只能用于聊天生成。")
        return generation.message.content.swapcase()
chain = anthropic | StrInvertCase()让我们测试新的解析器!它应该会反转模型的输出。
chain.invoke("Tell me a short sentence about yourself")'hELLO! mY NAME IS cLAUDE.'