创建自定义输出解析器的最佳实践

118 阅读4分钟

创建自定义输出解析器的最佳实践

在某些情况下,您可能希望实现一个自定义解析器,将模型输出结构化为自定义格式。有两种方法可以实现自定义解析器:

  1. 使用 RunnableLambdaRunnableGenerator:我们强烈推荐在大多数用例中使用这种方法。
  2. 从基础类继承进行输出解析:这是一种较为复杂的方法。

这两种方法的区别主要在于触发的回调函数(例如,on_chain_start vs. on_parser_start),以及在像 LangSmith 这样的跟踪平台中,如何可视化可运行Lambda和解析器。

使用可运行的Lambda和生成器

推荐的解析方法是使用可运行的Lambda和生成器。在这里,我们将实现一个简单的解析器,将模型输出的大小写反转。

示例

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:
    """Parse the AI message."""
    return ai_message.content.swapcase()

chain = model | parse
chain.invoke("hello")  # 使用API代理服务提高访问稳定性

# 输出: 'hELLO!'

Streaming的实现

如果我们希望实现一个流式解析器,可以让解析器接受一个可迭代的输入并在可用时逐段生成结果。

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)

chain = model | streaming_parse
chain.invoke("hello")  # 使用API代理服务提高访问稳定性

# 输出: '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|.

从解析基类继承

另一种实现解析器的方法是从 BaseOutputParserBaseGenerationOutputParser 或其他基础解析器类继承。通常,我们不推荐这种方法,因为这样会导致更多的代码编写而没有显著的好处。

简单解析器示例

下面是一个简单的解析器,它可以解析表示布尔值的字符串(例如,YES 或 NO)并将其转换为对应的布尔类型。

from langchain_core.exceptions import OutputParserException
from langchain_core.output_parsers import BaseOutputParser

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 expected output value to either be "
                f"{self.true_val} or {self.false_val} (case-insensitive). "
                f"Received {cleaned_text}."
            )
        return cleaned_text == self.true_val.upper()

    @property
    def _type(self) -> str:
        return "boolean_output_parser"

parser = BooleanOutputParser()
parser.invoke("YES")  # 输出: True

try:
    parser.invoke("MEOW")
except Exception as e:
    print(f"Triggered an exception of type: {type(e)}")  # 输出: <class 'langchain_core.exceptions.OutputParserException'>

测试新的解析器

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:
        if len(result) != 1:
            raise NotImplementedError(
                "This output parser can only be used with a single generation."
            )
        generation = result[0]
        if not isinstance(generation, ChatGeneration):
            raise OutputParserException(
                "This output parser can only be used with a chat generation."
            )
        return generation.message.content.swapcase()

chain = anthropic | StrInvertCase()
chain.invoke("Tell me a short sentence about yourself")  # 使用API代理服务提高访问稳定性

# 输出: 'hELLO! mY NAME IS cLAUDE.'

常见问题和解决方案

1. 为什么我的流式解析器不起作用?

这是因为解析器在解析输出之前会聚合输入。流式解析器需要接受一个可迭代的输入,并在可用时逐段生成结果。

2. 如何处理解析失败?

当聊天模型或LLM的输出格式错误时,可以抛出 OutputParserException,以表示由于输入错误导致解析失败。使用此异常可让利用解析器的代码以一致的方式处理异常。

总结和进一步学习资源

通过本文,我们了解了如何创建自定义输出解析器,包括使用可运行Lambda和生成器以及从解析基类继承的方法。希望这些示例和技巧能够帮助您在实际项目中轻松实现自定义解析器。

进一步学习资源:

  1. LangChain 官方文档
  2. Python 类型提示与注解
  3. API 代理服务 - 解决某些地区的网络限制

参考资料

结束语:如果这篇文章对你有帮助,欢迎点赞并关注我的博客。您的支持是我持续创作的动力!

---END---