随着大模型技术的飞速发展,如何高效地利用它们构建实际应用成为了关键。本文将深入探讨利用 LangChain 框架构建生成式 AI 应用的核心概念:链(Chains)与输出解析器(Output Parsers)。我们将解析如何使用 LangChain 处理结构化输出,并探讨不同类型的输出解析器,以及如何在 LangChain 中构建链,从而打造更强大的 AI 应用。本文将基于英文文章 “Generative AI with LangChain: Chains & Output Parsers (Part 3)”,对其核心思想进行提炼和扩展,旨在帮助读者更好地理解和应用 LangChain。

1. 结构化输出与输出解析器 (Structured Output & Output Parsers)

1.1 结构化输出 (Structured Output)

在生成式 AI 应用中,仅仅获得自然语言文本的回答是不够的。许多应用场景需要模型以特定的、结构化的格式返回结果,例如 JSON、Python 字典或其他数据结构。结构化输出能够方便后续的数据处理、存储和进一步分析,极大地提高了 AI 应用的实用性。例如,一个电商应用可能需要大模型根据用户评论生成产品属性的汇总,并以 JSON 格式输出,方便系统自动更新产品信息。

1.2 使用结构化输出的场景与优势

使用结构化输出的优势在于:

  • 机器可读性: 结构化数据易于被机器解析和处理,避免了从非结构化文本中提取信息的复杂性和不确定性。
  • 数据一致性: 确保输出格式的一致性,避免了不同格式带来的兼容性问题。
  • 简化下游任务: 结构化数据可以直接用于数据库存储、API 调用或其他数据处理任务,简化了后续开发流程。

举例来说,一个旅游推荐应用需要根据用户的旅行偏好(如预算、目的地类型、出行时间等)推荐酒店。如果大模型输出的酒店信息是自由文本格式,那么应用需要进行复杂的自然语言处理才能提取出酒店名称、价格、评分等关键信息。而如果大模型直接输出 JSON 格式的酒店信息,则应用可以轻松地获取并展示这些信息。

[
  {
    "hotel_name": "豪华海景酒店",
    "price_per_night": 200,
    "rating": 4.5,
    "location": "海滨",
    "amenities": ["游泳池", "健身房", "免费 Wi-Fi"]
  },
  {
    "hotel_name": "经济型商务酒店",
    "price_per_night": 80,
    "rating": 4.0,
    "location": "市中心",
    "amenities": ["免费 Wi-Fi", "早餐"]
  }
]

1.3 输出解析器的类型:TypedDict, Pydantic, JSON Schema

LangChain 提供了多种类型的输出解析器,用于将大模型的输出转换为结构化数据。

  • TypedDict: Python 的 typing 模块提供的一种类型提示,可以定义字典的键和对应的值类型。LangChain 可以利用 TypedDict 来指定输出字典的结构。

  • Pydantic: 一个强大的数据验证和解析库,可以定义数据模型,并自动验证输入数据是否符合模型定义。LangChain 可以使用 Pydantic 模型来定义输出数据的结构,并自动进行数据验证。

  • JSON Schema: 一种描述 JSON 数据结构的标准化格式。LangChain 可以使用 JSON Schema 来定义输出数据的结构,并进行验证。

案例:使用 Pydantic 定义输出结构

from langchain.output_parsers import PydanticOutputParser
from pydantic import BaseModel, Field

class Hotel(BaseModel):
    hotel_name: str = Field(description="酒店名称")
    price_per_night: int = Field(description="每晚价格")
    rating: float = Field(description="评分")

parser = PydanticOutputParser(pydantic_object=Hotel)

prompt = """
请根据以下信息生成酒店信息:
酒店名称:阳光海滩度假村
每晚价格:150
评分:4.8

{format_instructions}
"""

prompt_formatted = prompt.format(format_instructions=parser.get_format_instructions())

# 假设 llm 是一个大模型实例
output = llm(prompt_formatted)

hotel = parser.parse(output)

print(hotel) # 输出:hotel_name='阳光海滩度假村' price_per_night=150 rating=4.8

在这个例子中,我们使用 Pydantic 定义了一个 Hotel 类,指定了酒店名称、每晚价格和评分的类型和描述。然后,我们使用 PydanticOutputParser 将大模型的输出解析为 Hotel 对象。parser.get_format_instructions() 方法可以生成用于提示大模型的格式说明,帮助大模型按照指定的格式输出。

1.4 输出解析器 (Output Parsers)

输出解析器负责将大模型的原始输出转换为预定义的结构化格式。它接收大模型的输出字符串,并根据预定义的规则进行解析,最终返回一个结构化的数据对象。

LangChain 提供了多种内置的输出解析器,如 PydanticOutputParser, CommaSeparatedListOutputParser, JSONOutputParser 等。此外,用户还可以自定义输出解析器,以满足特定的需求。

2. 链 (Chains)

2.1 什么是链 (What are Chains?)

链 (Chains) 是 LangChain 中一个核心的概念,它代表一系列按特定顺序连接在一起的组件,用于处理数据并完成特定任务。链可以由多个模型、提示模板、输出解析器或其他链组成,形成一个复杂的工作流程。

链的设计思想是将复杂的任务分解为多个简单的步骤,每个步骤由一个组件负责,然后将这些组件连接起来,形成一个完整的流程。这使得开发人员可以更加灵活地构建 AI 应用,并更好地控制每个步骤的行为。

例如,一个问答链可能包含以下步骤:

  1. 检索 (Retrieval): 从知识库中检索与问题相关的文档。
  2. 生成 (Generation): 使用大模型根据检索到的文档生成答案。
  3. 输出 (Output): 将答案格式化并输出。

每个步骤都可以使用不同的组件来实现,例如可以使用 FAISS 作为知识库,使用 GPT-3 作为大模型,使用 StrOutputParser 作为输出解析器。

2.2 旧 vs 新 (Old vs New)

在早期的 LangChain 版本中,链的构建方式相对复杂,需要手动连接各个组件。而新版本的 LangChain 提供了更加简洁和灵活的链构建方式,例如可以使用 LangChain Expression Language (LCEL) 来定义链。

旧版本构建链的方式:

from langchain.llms import OpenAI
from langchain.prompts import PromptTemplate
from langchain.chains import LLMChain

llm = OpenAI(temperature=0.9)
prompt = PromptTemplate(
    input_variables=["product"],
    template="What is a good name for a company that makes {product}?",
)

chain = LLMChain(llm=llm, prompt=prompt)

print(chain.run("colorful socks"))

新版本使用 LCEL 构建链的方式:

from langchain.chat_models import ChatOpenAI
from langchain.prompts import PromptTemplate
from langchain.schema.output_parser import StrOutputParser

prompt_template = """你是一个命名专家,擅长为新公司起名字。
请为以下产品起一个好听的名字:{product}"""
prompt = PromptTemplate.from_template(prompt_template)
model = ChatOpenAI(temperature=0.8)
output_parser = StrOutputParser()

chain = prompt | model | output_parser

print(chain.invoke({"product": "智能家居设备"}))

新版本的 LCEL 使用管道符号 (|) 将各个组件连接起来,更加简洁和直观。这使得开发人员可以更加方便地构建复杂的链,并快速地进行实验和迭代。

2.3 链的类型

LangChain 提供了多种内置的链类型,用于处理不同的任务。常见的链类型包括:

  • LLMChain: 将提示模板和语言模型连接在一起,用于生成文本。
  • SequentialChain: 将多个链按顺序连接在一起,用于执行一系列任务。
  • RetrievalQAChain: 用于问答任务,结合了信息检索和生成模型。
  • RouterChain: 根据输入选择不同的链来处理。

2.4 链的应用案例

案例 1:构建一个简单的问答链

from langchain.chat_models import ChatOpenAI
from langchain.embeddings import OpenAIEmbeddings
from langchain.vectorstores import FAISS
from langchain.text_splitter import CharacterTextSplitter
from langchain.chains import RetrievalQA

# 1. 加载文档
with open("state_of_the_union.txt") as f:
    state_of_the_union = f.read()

# 2. 将文档分割成小块
text_splitter = CharacterTextSplitter(chunk_size=1000, chunk_overlap=0)
texts = text_splitter.split_text(state_of_the_union)

# 3. 创建嵌入
embeddings = OpenAIEmbeddings()
docsearch = FAISS.from_texts(texts, embeddings)

# 4. 创建问答链
qa = RetrievalQA.from_chain_type(llm=ChatOpenAI(temperature=0), chain_type="stuff", retriever=docsearch.as_retriever())

query = "What did the president say about Ketanji Brown Jackson"
print(qa.run(query))

这个例子展示了如何使用 LangChain 构建一个简单的问答链。首先,我们加载文档并将其分割成小块。然后,我们使用 OpenAIEmbeddings 创建文本嵌入,并将其存储在 FAISS 向量数据库中。最后,我们使用 RetrievalQA.from_chain_type 创建问答链,该链使用 stuff 类型的链来处理检索到的文档。

案例 2:使用 SequentialChain 构建一个故事生成器

from langchain.llms import OpenAI
from langchain.chains import LLMChain, SimpleSequentialChain
from langchain.prompts import PromptTemplate

# 1. 定义第一个链:生成故事概要
prompt_synopsis = PromptTemplate(
    input_variables=["topic"],
    template="Write a synopsis for a story about {topic}.",
)
chain_synopsis = LLMChain(llm=OpenAI(temperature=0.7), prompt=prompt_synopsis)

# 2. 定义第二个链:根据概要生成故事
prompt_story = PromptTemplate(
    input_variables=["synopsis"],
    template="Write a story based on the following synopsis: {synopsis}.",
)
chain_story = LLMChain(llm=OpenAI(temperature=0.9), prompt=prompt_story)

# 3. 将两个链连接在一起
overall_chain = SimpleSequentialChain(chains=[chain_synopsis, chain_story], verbose=True)

# 4. 运行链
topic = "a magical cat"
print(overall_chain.run(topic))

这个例子展示了如何使用 SimpleSequentialChain 构建一个故事生成器。首先,我们定义两个链:第一个链用于生成故事概要,第二个链用于根据概要生成故事。然后,我们将两个链连接在一起,形成一个完整的故事生成流程。

结论

本文深入探讨了 LangChain 中的两个核心概念:链(Chains)与输出解析器(Output Parsers)。通过合理地使用结构化输出和输出解析器,我们可以更加方便地处理大模型的输出,并将其用于各种下游任务。通过构建链,我们可以将多个组件连接在一起,形成一个复杂的工作流程,从而解决更加复杂的 AI 问题。掌握这些技术,将有助于开发者更好地利用 LangChain 构建强大的生成式 AI 应用,并在实际应用中发挥大模型技术的潜力。随着 LangChain 的不断发展,相信未来会出现更多强大而灵活的链和输出解析器,进一步简化 AI 应用的开发流程,并推动大模型技术的普及应用。