LangChain——LangChain 与 LLM 基础知识

267 阅读24分钟

在一般社交媒体中,我们初步了解了 LLM 提示的强大功能,我们亲眼看到了不同提示技术对 LLM 输出结果的影响,特别是当这些技术巧妙结合时。构建优秀 LLM 应用程序的挑战,实际上在于如何有效地构建发送给模型的提示,并处理模型的预测以返回准确的输出(见图 1-1)。

image.png

如果你能解决这个问题,那么你已经在构建 LLM 应用程序的道路上走得很远,无论是简单的还是复杂的。在本章中,你将了解 LangChain 的构建模块如何映射到 LLM 概念,以及它们如何在有效结合时帮助你构建 LLM 应用程序。但首先,边栏“为什么选择 LangChain?”将简要介绍我们认为使用 LangChain 构建 LLM 应用程序的理由。

为什么选择 LangChain?

当然,你可以在没有 LangChain 的情况下构建 LLM 应用程序。最明显的替代方案是使用 LLM 提供商(例如 OpenAI)首次尝试的 SDK——该包将其 HTTP API 的方法暴露为你选择的编程语言中的函数。我们认为学习 LangChain 在短期和长期内都能带来回报,原因如下:

预构建的常见模式

LangChain 提供了最常见 LLM 应用模式的参考实现(我们在前言中提到过其中一些:思维链、工具调用等)。这是开始使用 LLM 的最快方式,通常这可能就是你所需要的一切。我们建议从这些模式开始构建任何新的应用程序,并检查开箱即用的结果是否足够适合你的用例。如果不够,那么请参阅下一项,了解 LangChain 库的另一半。

可互换的构建块

这些是可以轻松替换为替代方案的组件。每个组件(例如 LLM、聊天模型、输出解析器等——稍后将详细介绍)都遵循共享的规范,这使得你的应用程序具有未来兼容性。随着模型提供商发布新功能,以及你的需求发生变化,你可以在不每次重写应用程序的情况下,逐步演进你的应用程序。

在本书的代码示例中,我们使用了以下主要组件:

  • LLM/聊天模型:OpenAI
  • 嵌入:OpenAI
  • 向量存储:PGVector

你可以将每个组件替换为以下页面列出的任何替代方案:

  • 聊天模型:查看 LangChain 文档。如果你不想使用 OpenAI(商业 API),我们建议 Anthropic 作为商业替代方案,或者 Ollama 作为开源替代方案。
  • 嵌入:查看 LangChain 文档。如果你不想使用 OpenAI(商业 API),我们建议 Cohere 作为商业替代方案,或者 Ollama 作为开源替代方案。
  • 向量存储:查看 LangChain 文档。如果你不想使用 PGVector(流行的 SQL 数据库 Postgres 的开源扩展),我们建议使用 Weaviate(专用向量存储)或 OpenSearch(作为流行搜索数据库一部分的向量搜索功能)。

这种努力不仅仅体现在所有 LLM 都有相同的方法、类似的参数和返回值。例如,来看一下聊天模型和两个流行的 LLM 提供商 OpenAI 和 Anthropic。两者都提供聊天 API,接收聊天消息(通常定义为具有类型字符串和内容字符串的对象)并返回由模型生成的新消息。但如果你尝试在同一对话中使用这两种模型,你会立刻遇到问题,因为它们的聊天消息格式存在细微的不兼容性。LangChain 抽象化了这些差异,使得构建真正独立于特定提供商的应用程序成为可能。例如,使用 LangChain,你可以在聊天机器人对话中同时使用 OpenAI 和 Anthropic 模型。

最后,当你使用这些组件构建 LLM 应用程序时,我们发现 LangChain 的协调功能非常有用:

  • 所有主要组件都通过回调系统进行了监控(在第 8 章中详细介绍)。
  • 所有主要组件实现了相同的接口(本章末尾将进一步说明)。
  • 长时间运行的 LLM 应用程序可以中断、恢复或重试(第 6 章将详细介绍)。

设置 LangChain 环境

为了跟随本章及未来章节的内容,我们建议首先在你的计算机上设置 LangChain。

参见前言中的说明,设置 OpenAI 账户,如果你还没有完成这些步骤,请先完成。如果你更喜欢使用其他 LLM 提供商,请参考“为什么选择 LangChain?”部分中的替代方案。

接下来,登录到 OpenAI 账户后,前往 OpenAI 网站的 API 密钥页面,创建一个 API 密钥并保存——你很快就需要它了。

注意
在本书中,我们将同时展示 Python 和 JavaScript(JS)的代码示例。LangChain 在这两种语言中提供相同的功能,所以你可以选择自己最熟悉的语言,并按照相应的代码片段进行操作(每种语言的代码示例是等效的)。

Python 用户的设置说明:
  1. 确保你已安装 Python。请查看适合你操作系统的安装说明。

  2. 如果你想在 notebook 环境中运行示例,请安装 Jupyter。可以通过在终端中运行 pip install notebook 来安装。

  3. 通过在终端运行以下命令安装 LangChain 库:

    pip install langchain langchain-openai langchain-community
    pip install langchain-text-splitters langchain-postgres
    
  4. 将你在本节开始时生成的 OpenAI API 密钥添加到终端环境中。可以通过运行以下命令:

    export OPENAI_API_KEY=your-key
    

    请确保将 your-key 替换为你之前生成的 API 密钥。

  5. 通过运行以下命令打开 Jupyter notebook:

    jupyter notebook
    

    现在你可以开始跟随 Python 代码示例。

JavaScript 用户的设置说明:
  1. 将你在本节开始时生成的 OpenAI API 密钥添加到终端环境中。可以通过运行以下命令:

    export OPENAI_API_KEY=your-key
    

    请确保将 your-key 替换为你之前生成的 API 密钥。

  2. 如果你想作为 Node.js 脚本运行示例,请按照说明安装 Node。

  3. 通过在终端运行以下命令安装 LangChain 库:

    npm install langchain @langchain/openai @langchain/community
    npm install @langchain/core pg
    
  4. 将每个示例保存为 .js 文件,并使用以下命令运行:

    node ./file.js
    

使用 LangChain 中的 LLM

回顾一下,LLM 是大多数生成性 AI 应用的核心引擎。LangChain 提供了两个简单的接口来与任何 LLM API 提供商进行交互:

  • 聊天模型
  • LLM

LLM 接口简单地接受一个字符串提示作为输入,将输入发送到模型提供商,并返回模型预测的输出。

让我们导入 LangChain 的 OpenAI LLM 封装器,使用一个简单的提示来调用模型预测:

Python:

from langchain_openai.llms import OpenAI

model = OpenAI(model="gpt-3.5-turbo")

model.invoke("The sky is")

JavaScript:

import { OpenAI } from "@langchain/openai";

const model = new OpenAI({ model: "gpt-3.5-turbo" });

await model.invoke("The sky is");

输出:

Blue!

小贴士

注意传递给 OpenAI 的 model 参数。这是使用 LLM 或聊天模型时最常配置的参数,指定要使用的底层模型,因为大多数提供商提供了多个具有不同能力和成本折衷的模型(通常较大的模型更强大,但也更昂贵且速度较慢)。请查看 OpenAI 提供的模型概述。

其他有用的配置参数包括:

  • temperature
    控制用于生成输出的采样算法。较低的值产生更可预测的输出(例如,0.1),而较高的值则生成更具创意或意外的结果(例如,0.9)。不同的任务会需要不同的值。例如,生成结构化输出通常受益于较低的温度,而创意写作任务则适合较高的温度。
  • max_tokens
    限制输出的大小(和成本)。较低的值可能会导致 LLM 在输出自然结束之前停止生成,因此输出看起来可能被截断。

除此之外,每个提供商还会暴露一组不同的参数。我们建议查看你选择的提供商的文档。举例来说,查看 OpenAI 的平台。

聊天模型接口

与 LLM 接口不同,聊天模型接口支持用户与模型之间的交互式对话。之所以有一个单独的接口,是因为像 OpenAI 这样的流行 LLM 提供商将发送到模型的消息分为用户、助手和系统角色(这里角色表示消息的内容类型):

  • 系统角色:用于模型回答用户问题时的指令。
  • 用户角色:用于用户的查询及用户生成的任何其他内容。
  • 助手角色:用于模型生成的内容。

聊天模型的接口使得在 AI 聊天应用程序中配置和管理对话变得更加简单。以下是使用 LangChain 的 ChatOpenAI 模型的示例:

Python:

from langchain_openai.chat_models import ChatOpenAI
from langchain_core.messages import HumanMessage

model = ChatOpenAI()
prompt = [HumanMessage("What is the capital of France?")]

model.invoke(prompt)

JavaScript:

import { ChatOpenAI } from '@langchain/openai'
import { HumanMessage } from '@langchain/core/messages'

const model = new ChatOpenAI()
const prompt = [new HumanMessage('What is the capital of France?')]

await model.invoke(prompt)

输出:

AIMessage(content='The capital of France is Paris.')

与单一提示字符串不同,聊天模型使用不同类型的聊天消息接口,每个接口与先前提到的角色相关联。包括:

  • HumanMessage:从人类角度发送的消息,属于用户角色。
  • AIMessage:从 AI 角度发送的消息,属于助手角色。
  • SystemMessage:设定 AI 应遵循的指令,属于系统角色。
  • ChatMessage:允许任意设置角色的消息。

让我们在示例中加入一个 SystemMessage 指令:

Python:

from langchain_core.messages import HumanMessage, SystemMessage
from langchain_openai.chat_models import ChatOpenAI

model = ChatOpenAI()
system_msg = SystemMessage(
    '''You are a helpful assistant that responds to questions with three 
        exclamation marks.'''
)
human_msg = HumanMessage('What is the capital of France?')

model.invoke([system_msg, human_msg])

JavaScript:

import { ChatOpenAI } from "@langchain/openai";
import { HumanMessage, SystemMessage } from "@langchain/core/messages";

const model = new ChatOpenAI();
const prompt = [
  new SystemMessage(
    `You are a helpful assistant that responds to questions with three 
      exclamation marks.`,
  ),
  new HumanMessage("What is the capital of France?"),
];

await model.invoke(prompt);

输出:

AIMessage('Paris!!!')

如你所见,模型遵循了 SystemMessage 中提供的指令,即使这些指令并不包含在用户的问题中。这使得你可以预配置 AI 应用程序,使其根据用户的输入以相对可预测的方式做出响应。

使 LLM 提示可重用

上一节展示了提示指令如何显著影响模型的输出。提示帮助模型理解上下文,并生成与查询相关的答案。

这是一个详细的提示示例:

根据下面的上下文回答问题。如果无法使用提供的信息回答问题,请回答 "I don't know"。

上下文:最近的自然语言处理(NLP)进展是由大规模语言模型(LLM)驱动的。这些模型超过了它们较小的对手,已经成为开发者创建具有 NLP 功能的应用程序时不可或缺的工具。开发者可以通过 Hugging Face 的 `transformers` 库,或者分别通过 OpenAI 和 Cohere 的 `openai``cohere` 库来使用这些模型。

问题:哪些模型提供商提供 LLM?

答案:

虽然提示看起来像一个简单的字符串,但挑战在于弄清楚文本应包含什么内容,并根据用户的输入如何变化。在这个示例中,ContextQuestion 的值是硬编码的,但如果我们想要动态地传递这些值怎么办?

幸运的是,LangChain 提供了提示模板接口,使得构建动态输入的提示变得简单:

Python:

from langchain_core.prompts import PromptTemplate

template = PromptTemplate.from_template("""Answer the question based on the
    context below. If the question cannot be answered using the information 
    provided, answer with "I don't know".

Context: {context}

Question: {question}

Answer: """)

template.invoke({
    "context": """The most recent advancements in NLP are being driven by Large 
        Language Models (LLMs). These models outperform their smaller 
        counterparts and have become invaluable for developers who are creating 
        applications with NLP capabilities. Developers can tap into these 
        models through Hugging Face's `transformers` library, or by utilizing 
        OpenAI and Cohere's offerings through the `openai` and `cohere` 
        libraries, respectively.""",
    "question": "Which model providers offer LLMs?"
})

JavaScript:

import { PromptTemplate } from '@langchain/core/prompts'

const template = PromptTemplate.fromTemplate(`Answer the question based on the 
  context below. If the question cannot be answered using the information 
  provided, answer with "I don't know".

Context: {context}

Question: {question}

Answer: `)

await template.invoke({
  context: `The most recent advancements in NLP are being driven by Large 
    Language Models (LLMs). These models outperform their smaller 
    counterparts and have become invaluable for developers who are creating 
    applications with NLP capabilities. Developers can tap into these models 
    through Hugging Face's `transformers` library, or by utilizing OpenAI 
    and Cohere's offerings through the `openai` and `cohere` libraries, 
    respectively.`,
  question: "Which model providers offer LLMs?"
})

输出:

StringPromptValue(text='Answer the question based on the context below. If the 
    question cannot be answered using the information provided, answer with "I
    don't know".\n\nContext: The most recent advancements in NLP are being 
    driven by Large Language Models (LLMs). These models outperform their 
    smaller counterparts and have become invaluable for developers who are 
    creating applications with NLP capabilities. Developers can tap into these 
    models through Hugging Face's `transformers` library, or by utilizing 
    OpenAI and Cohere's offerings through the `openai` and `cohere` libraries, 
    respectively.\n\nQuestion: Which model providers offer LLMs?\n\nAnswer: ')

这个示例将之前的静态提示转换为动态提示。模板包含最终提示的结构,以及动态输入将插入的位置定义。

因此,模板可以作为构建多个静态、具体提示的“食谱”。当你为提示格式化一些具体的值——在这个例子中是 contextquestion——你就得到了一个准备传递给 LLM 的静态提示。

如你所见,question 参数是通过 invoke 函数动态传递的。默认情况下,LangChain 提示遵循 Python 的 f-string 语法来定义动态参数——任何被大括号括起来的词,如 {question},都是运行时传递的值的占位符。在前面的示例中,{question} 被替换为“Which model providers offer LLMs?”。

让我们看看如何将这个输入传递给 LLM OpenAI 模型:

Python:

from langchain_openai.llms import OpenAI
from langchain_core.prompts import PromptTemplate

# `template` 和 `model` 可以被多次重用

template = PromptTemplate.from_template("""Answer the question based on the 
    context below. If the question cannot be answered using the information 
    provided, answer with "I don't know".

Context: {context}

Question: {question}

Answer: """)

model = OpenAI()

# `prompt` 和 `completion` 是使用模板和模型一次的结果

prompt = template.invoke({
    "context": """The most recent advancements in NLP are being driven by Large
        Language Models (LLMs). These models outperform their smaller 
        counterparts and have become invaluable for developers who are creating 
        applications with NLP capabilities. Developers can tap into these 
        models through Hugging Face's `transformers` library, or by utilizing 
        OpenAI and Cohere's offerings through the `openai` and `cohere` 
        libraries, respectively.""",
    "question": "Which model providers offer LLMs?"
})

completion = model.invoke(prompt)

JavaScript:

import { PromptTemplate } from '@langchain/core/prompts'
import { OpenAI } from '@langchain/openai'

const model = new OpenAI()
const template = PromptTemplate.fromTemplate(`Answer the question based on the  
  context below. If the question cannot be answered using the information 
  provided, answer with "I don't know".

Context: {context}

Question: {question}

Answer: `)

const prompt = await template.invoke({
  context: `The most recent advancements in NLP are being driven by Large 
    Language Models (LLMs). These models outperform their smaller 
    counterparts and have become invaluable for developers who are creating 
    applications with NLP capabilities. Developers can tap into these models 
    through Hugging Face's `transformers` library, or by utilizing OpenAI 
    and Cohere's offerings through the `openai` and `cohere` libraries, 
    respectively.`,
  question: "Which model providers offer LLMs?"
})

await model.invoke(prompt)

输出:

Hugging Face's `transformers` library, OpenAI using the `openai` library, and 
Cohere using the `cohere` library offer LLMs.

如果您想构建一个AI聊天应用程序,可以使用ChatPromptTemplate来根据聊天消息的角色提供动态输入:

Python 代码:

from langchain_core.prompts import ChatPromptTemplate

template = ChatPromptTemplate.from_messages([
    ('system', '''根据以下上下文回答问题。如果无法根据提供的信息回答问题,请回答 "I don't know"。'''),
    ('human', '上下文: {context}'),
    ('human', '问题: {question}'),
])

template.invoke({
    "context": """NLP(自然语言处理)的最新进展是由大语言模型(LLM)推动的。这些模型超过了它们较小的同类,并且对开发者在创建具有NLP能力的应用程序时变得不可或缺。开发者可以通过Hugging Face的`transformers`库,或通过分别使用OpenAI和Cohere的`openai`和`cohere`库来利用这些模型。""",
    "question": "哪些模型提供商提供LLM?"
})

JavaScript 代码:

import { ChatPromptTemplate } from '@langchain/core/prompts'

const template = ChatPromptTemplate.fromMessages([
  ['system', `根据以下上下文回答问题。如果无法根据提供的信息回答问题,请回答 "I don't know"。`],
  ['human', '上下文: {context}'],
  ['human', '问题: {question}'],
])

await template.invoke({
  context: `NLP(自然语言处理)的最新进展是由大语言模型(LLM)推动的。这些模型超过了它们较小的同类,并且对开发者在创建具有NLP能力的应用程序时变得不可或缺。开发者可以通过Hugging Face的`transformers`库,或通过分别使用OpenAI和Cohere的`openai`和`cohere`库来利用这些模型。`,
  question: "哪些模型提供商提供LLM?"
})

输出:

ChatPromptValue(messages=[SystemMessage(content='根据以下上下文回答问题。如果无法根据提供的信息回答问题,请回答 "I don't know"。'), HumanMessage(content="上下文: NLP(自然语言处理)的最新进展是由大语言模型(LLM)推动的。这些模型超过了它们较小的同类,并且对开发者在创建具有NLP能力的应用程序时变得不可或缺。开发者可以通过Hugging Face的`transformers`库,或通过分别使用OpenAI和Cohere的`openai``cohere`库来利用这些模型。"), HumanMessage(content='问题: 哪些模型提供商提供LLM?')])

可以看到,提示包含了一个SystemMessage的指令和两个包含动态上下文与问题变量的HumanMessage实例。你仍然可以像之前一样格式化模板,并返回一个静态的提示,将其传递给大语言模型以获取预测输出。

Python 代码:

from langchain_openai.chat_models import ChatOpenAI
from langchain_core.prompts import ChatPromptTemplate

template = ChatPromptTemplate.from_messages([
    ('system', '''根据以下上下文回答问题。如果无法根据提供的信息回答问题,请回答 "I don't know"。'''),
    ('human', '上下文: {context}'),
    ('human', '问题: {question}'),
])

model = ChatOpenAI()

prompt = template.invoke({
    "context": """NLP(自然语言处理)的最新进展是由大语言模型(LLM)推动的。这些模型超过了它们较小的同类,并且对开发者在创建具有NLP能力的应用程序时变得不可或缺。开发者可以通过Hugging Face的`transformers`库,或通过分别使用OpenAI和Cohere的`openai`和`cohere`库来利用这些模型。""",
    "question": "哪些模型提供商提供LLM?"
})

model.invoke(prompt)

JavaScript 代码:

import { ChatPromptTemplate } from '@langchain/core/prompts'
import { ChatOpenAI } from '@langchain/openai'

const model = new ChatOpenAI()
const template = ChatPromptTemplate.fromMessages([
  ['system', `根据以下上下文回答问题。如果无法根据提供的信息回答问题,请回答 "I don't know"。`],
  ['human', '上下文: {context}'],
  ['human', '问题: {question}'],
])

const prompt = await template.invoke({
  context: `NLP(自然语言处理)的最新进展是由大语言模型(LLM)推动的。这些模型超过了它们较小的同类,并且对开发者在创建具有NLP能力的应用程序时变得不可或缺。开发者可以通过Hugging Face的`transformers`库,或通过分别使用OpenAI和Cohere的`openai`和`cohere`库来利用这些模型。`,
  question: "哪些模型提供商提供LLM?"
})

await model.invoke(prompt)

输出:

AIMessage(content="Hugging Face的`transformers`库、OpenAI使用`openai`库以及Cohere使用`cohere`库提供LLM。")

从LLM获取特定格式的输出

纯文本输出很有用,但在某些情况下,您可能需要LLM生成结构化输出——也就是机器可读的格式,如JSON、XML、CSV,甚至编程语言格式,如Python或JavaScript。当您打算将输出交给其他代码处理时,这非常有用,LLM就成了您更大应用程序的一部分。

JSON 输出

生成LLM最常见的格式是JSON。JSON输出可以(例如)通过网络发送到前端代码,或保存到数据库中。 在生成JSON时,首要任务是定义LLM在生成输出时需要遵循的模式(schema)。然后,您应将该模式包含在提示中,以及您希望作为源的文本。下面是一个示例:

Python 代码:

from langchain_openai import ChatOpenAI
from langchain_core.pydantic_v1 import BaseModel

class AnswerWithJustification(BaseModel):
    '''用户问题的答案及其理由。'''
    answer: str
    '''用户问题的答案'''
    justification: str
    '''答案的理由'''

llm = ChatOpenAI(model="gpt-3.5-turbo", temperature=0)
structured_llm = llm.with_structured_output(AnswerWithJustification)

structured_llm.invoke("""What weighs more, a pound of bricks or a pound 
    of feathers""")

JavaScript 代码:

import { ChatOpenAI } from '@langchain/openai'
import { z } from "zod";

const answerSchema = z
  .object({
    answer: z.string().describe("用户问题的答案"),
    justification: z.string().describe(`答案的理由`),
  })
  .describe(`用户问题的答案及其理由`);

const model = new ChatOpenAI({
  model: "gpt-3.5-turbo",
  temperature: 0,
}).withStructuredOutput(answerSchema)

await model.invoke("What weighs more, a pound of bricks or a pound of feathers")

输出:

{
  answer: "They weigh the same",
  justification: "Both a pound of bricks and a pound of feathers weigh one pound. The weight is the same, but the volu"... 42 more characters
}

首先,定义一个模式。在Python中,使用Pydantic(一个用于验证数据是否符合模式的库)是最方便的。在JavaScript中,使用Zod(一个等效的库)是最方便的。with_structured_output方法会将该模式用于以下两个方面:

  1. 模式将被转换为JSONSchema对象(JSON格式,用于描述JSON数据的形状[类型、名称、描述]),并将其发送给LLM。对于每个LLM,LangChain选择最合适的方法来执行此操作,通常是函数调用或提示。
  2. 模式还将用于验证LLM返回的输出,确保生成的输出严格遵循您提供的模式。

其他机器可读格式与输出解析器

您还可以使用LLM或聊天模型生成其他格式的输出,如CSV或XML。此时,输出解析器(Output Parsers)派上用场。输出解析器是帮助您结构化大语言模型响应的类,它们有两个作用:

  • 提供格式指令
    输出解析器可以用来在提示中注入额外的指令,帮助引导LLM输出它知道如何解析的格式。
  • 验证和解析输出
    主要功能是将LLM或聊天模型的文本输出转换为更结构化的格式,如列表、XML或其他格式。这包括删除多余的信息、修正不完整的输出,以及验证解析后的值。

下面是一个输出解析器的示例:

Python 代码:

from langchain_core.output_parsers import CommaSeparatedListOutputParser
parser = CommaSeparatedListOutputParser()
items = parser.invoke("apple, banana, cherry")

JavaScript 代码:

import { CommaSeparatedListOutputParser } from '@langchain/core/output_parsers'

const parser = new CommaSeparatedListOutputParser()

await parser.invoke("apple, banana, cherry")

输出:

['apple', 'banana', 'cherry']

LangChain提供了多种输出解析器,用于各种用例,包括CSV、XML等。我们将在下一节中看到如何将输出解析器与模型和提示结合使用。

组装LLM应用程序的各个组成部分

到目前为止,您所学习的关键组件是LangChain框架的基本构建块。这就引出了一个关键问题:如何有效地将它们结合起来构建您的LLM应用程序?

使用Runnable接口

如您所见,迄今为止所有的代码示例都使用了一个类似的接口和invoke()方法来从模型(或提示模板、或输出解析器)生成输出。所有组件都有以下特性:

  • 具有共同的接口,包含这些方法:

    • invoke:将单个输入转换为输出
    • batch:高效地将多个输入转换为多个输出
    • stream:在生成过程中从单个输入流式输出
  • 内置的工具支持重试、回退、模式和运行时配置。

  • 在Python中,这三种方法都有相应的asyncio版本。

因此,所有组件的行为相同,对其中一个组件学习到的接口可以应用于所有组件。

Python 示例:
from langchain_openai.llms import ChatOpenAI

model = ChatOpenAI()

completion = model.invoke('Hi there!') 
# Hi!

completions = model.batch(['Hi there!', 'Bye!'])
# ['Hi!', 'See you!']

for token in model.stream('Bye!'):
    print(token)
    # Good
    # bye
    # !
JavaScript 示例:
import { ChatOpenAI } from '@langchain/openai'

const model = new ChatOpenAI()

const completion = await model.invoke('Hi there!') 
// Hi!

const completions = await model.batch(['Hi there!', 'Bye!'])
// ['Hi!', 'See you!']

for await (const token of await model.stream('Bye!')) {
  console.log(token)
  // Good
  // bye
  // !
}

在这个示例中,您可以看到三个主要方法的工作方式:

  • invoke() 接受单个输入并返回单个输出。
  • batch() 接受多个输入并返回多个输出。
  • stream() 接受单个输入并返回一个输出的迭代器,随着输出的生成逐步提供。

在某些情况下,如果底层组件不支持迭代输出,可能会返回一个包含所有输出的单一部分。

组件组合方式

您可以通过两种方式来组合这些组件:

  • 命令式(Imperative)
    直接调用组件,例如使用model.invoke(...)
  • 声明式(Declarative)
    使用LangChain表达式语言(LCEL),将在下一节中介绍。

下表总结了命令式和声明式组合的区别,接下来我们将展示每种方式的应用。

表1-1. 命令式与声明式组合的主要区别

 命令式 (Imperative)  声明式 (Declarative)  
语法完整的Python或JavaScriptLCEL
并行执行Python:使用线程或协程;JavaScript:使用Promise.all自动
流式输出使用yield关键字自动
异步执行使用异步函数自动

命令式组合

命令式组合只是一个花哨的名称,指的是编写您习惯编写的代码,将这些组件组合成函数和类。以下是一个结合提示、模型和输出解析器的示例:

Python 示例:
from langchain_openai.chat_models import ChatOpenAI
from langchain_core.prompts import ChatPromptTemplate
from langchain_core.runnables import chain

# 构建块

template = ChatPromptTemplate.from_messages([
    ('system', '你是一个有帮助的助手。'),
    ('human', '{question}'),
])

model = ChatOpenAI()

# 将它们组合成一个函数
# @chain装饰器为您编写的任何函数添加相同的Runnable接口

@chain
def chatbot(values):
    prompt = template.invoke(values)
    return model.invoke(prompt)

# 使用它

chatbot.invoke({"question": "哪些模型提供商提供LLM?"})
JavaScript 示例:
import {ChatOpenAI} from '@langchain/openai'
import {ChatPromptTemplate} from '@langchain/core/prompts'
import {RunnableLambda} from '@langchain/core/runnables'

// 构建块

const template = ChatPromptTemplate.fromMessages([
  ['system', '你是一个有帮助的助手。'],
  ['human', '{question}'],
])

const model = new ChatOpenAI()

// 将它们组合成一个函数
// RunnableLambda为您编写的任何函数添加相同的Runnable接口

const chatbot = RunnableLambda.from(async values => {
  const prompt = await template.invoke(values)
  return await model.invoke(prompt)
})

// 使用它

await chatbot.invoke({
  "question": "哪些模型提供商提供LLM?"
})

输出:

AIMessage(content="Hugging Face的`transformers`库,OpenAI使用`openai`库,以及Cohere使用`cohere`库提供LLM。")

上面是一个完整的聊天机器人的示例,使用了提示和聊天模型。正如您所见,它使用了熟悉的Python语法,并支持您可能希望在该函数中添加的任何自定义逻辑。

另一方面,如果您想启用流式输出或异步支持,您需要修改您的函数来支持它。例如,可以按如下方式添加流式支持:

Python 示例:
@chain
def chatbot(values):
    prompt = template.invoke(values)
    for token in model.stream(prompt):
        yield token

for part in chatbot.stream({
    "question": "哪些模型提供商提供LLM?"
}):
    print(part)
JavaScript 示例:
const chatbot = RunnableLambda.from(async function* (values) {
  const prompt = await template.invoke(values)
  for await (const token of await model.stream(prompt)) {
    yield token
  }
})

for await (const token of await chatbot.stream({
  "question": "哪些模型提供商提供LLM?"
})) {
  console.log(token)
}

输出:

AIMessageChunk(content="Hugging")
AIMessageChunk(content=" Face's")
AIMessageChunk(content=" `transformers`")
...

因此,在JS或Python中,您可以通过产出您想要流式传输的值,并使用stream调用它,从而启用流式输出。

对于异步执行,您可以这样重写您的函数:

Python 示例:
@chain
async def chatbot(values):
    prompt = await template.ainvoke(values)
    return await model.ainvoke(prompt)

await chatbot.ainvoke({"question": "哪些模型提供商提供LLM?"})
# > AIMessage(content="""Hugging Face的`transformers`库,OpenAI使用
# `openai`库,以及Cohere使用`cohere`库提供LLM。""")

这个示例仅适用于Python,因为在JavaScript中,异步执行是唯一的选择。

声明式组合

LCEL(LangChain表达式语言)是用于组合LangChain组件的声明式语言。LangChain将LCEL组合编译为优化的执行计划,具有自动并行化、流式输出、追踪和异步支持。

让我们通过LCEL来看一下相同的示例:

Python 示例:
from langchain_openai.chat_models import ChatOpenAI
from langchain_core.prompts import ChatPromptTemplate

# 构建块

template = ChatPromptTemplate.from_messages([
    ('system', '你是一个有帮助的助手。'),
    ('human', '{question}'),
])

model = ChatOpenAI()

# 使用 | 运算符将它们组合

chatbot = template | model

# 使用它

chatbot.invoke({"question": "哪些模型提供商提供LLM?"})
JavaScript 示例:
import { ChatOpenAI } from '@langchain/openai'
import { ChatPromptTemplate } from '@langchain/core/prompts'
import { RunnableLambda } from '@langchain/core/runnables'

// 构建块

const template = ChatPromptTemplate.fromMessages([
  ['system', '你是一个有帮助的助手。'],
  ['human', '{question}'],
])

const model = new ChatOpenAI()

// 将它们组合成一个函数

const chatbot = template.pipe(model)

// 使用它

await chatbot.invoke({
  "question": "哪些模型提供商提供LLM?"
})

输出:

AIMessage(content="Hugging Face的`transformers`库,OpenAI使用`openai`库,以及Cohere使用`cohere`库提供LLM。")

关键是,两个示例中的最后一行是相同的——也就是说,您使用函数和LCEL序列的方式是一样的,通过invokestreambatch。在这个版本中,您不需要做其他任何操作来启用流式输出:

Python 示例:
chatbot = template | model

for part in chatbot.stream({
    "question": "哪些模型提供商提供LLM?"
}):
    print(part)
    # > AIMessageChunk(content="Hugging")
    # > AIMessageChunk(content=" Face's")
    # > AIMessageChunk(content=" `transformers`")
    # ...
JavaScript 示例:
const chatbot = template.pipe(model)

for await (const token of await chatbot.stream({
  "question": "哪些模型提供商提供LLM?"
})) {
  console.log(token)
}

对于仅Python而言,使用异步方法也是一样的:

Python 示例:
chatbot = template | model

await chatbot.ainvoke({
    "question": "哪些模型提供商提供LLM?"
})

小结

在本章中,您了解了使用LangChain构建LLM应用程序所需的构建块和关键组件。LLM应用程序本质上是一个链条,由大型语言模型进行预测,提示指令引导模型朝向预期输出,和一个可选的输出解析器将模型输出格式化。

所有LangChain组件共享相同的接口,具有invokestreambatch方法来处理各种输入和输出。它们可以通过直接调用来命令式地组合和执行,或者使用LCEL声明式地组合。

命令式方法在您打算编写大量自定义逻辑时非常有用,而声明式方法则适用于仅仅将现有组件组装起来并进行有限自定义的场景。

在第2章中,您将学习如何为您的AI聊天机器人提供外部数据作为上下文,这样您就可以构建一个能够与数据“对话”的LLM应用程序。