第1章 通过LangChain学习LLM 基础知识

人工智能 Alan 10小时前 17次浏览 0个评论 扫描二维码

我们都体验过LLM 提示词的强大之处,也亲眼见证过不同的提示词技巧如何影响 LLM 的输出效果,在合理进行组合时效果更佳。构建优秀的 LLM 应用的挑战在于如何有效地构造发送给模型的提示词,并处理模型的预测结果以返回准确的输出(见图 1-1)。

第1章 通过LangChain学习LLM 基础知识
图 1-1. 让 LLM 成为应用程序得力助手的挑战
如果能解决这个问题,那么无论是开发简单还是复杂的 LLM 应用,你都已经迈出了重要一步。在本章中,我们将进一步了解 LangChain 的构建模块如何与LLM 概念产生关联,并且当这些模块有效组合时,它们又如何帮助我们构建 LLM 应用程序。但在深入学习之前,先进入“为什么选择 LangChain?” ,简要介绍下为什么我们认为 LangChain 是构建 LLM 应用的有力工具。

为什么选择 LangChain?

当然,你大可以不使用 LangChain 来构建 LLM 应用。最直接的替代方案是使用 LLM 提供商的 软件开发工具包(SDK)——它们将 HTTP API 的方法封装为各种编程语言的函数,例如 OpenAI 的 SDK。但我们认为学习 LangChain 在短期和长期都会带来回报,原因如下:

预构建的通用模式

LangChain 提供了最常见 LLM 应用模式的参考实现(如 思维链(Chain-of-Thought)工具调用(Tool Calling)等)。这是最快速开始使用 LLM 的方式,而且通常这可能已经能足够满足你的需求。我们建议从这些模式开始构建新的应用,并先看看默认的输出是否符合你的使用场景。如果不符合,那就可以参考下面介绍的 LangChain 其他功能模块。

可插拔的构建模块

这些组件可以轻松替换为其他选项。每个组件(如 LLM、聊天模型、输出解析器 等,稍后会详细介绍)都遵循统一的规范,使得你的应用具备前瞻性。模型提供商发布新功能,或需求发生变化时,可以在不重写代码的情况下优化和升级应用。

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

  • LLM/聊天模型:OpenAI
  • 嵌入模型(Embeddings):OpenAI
  • 向量存储(Vector Store):PGVector

你可以将这些组件替换为下述的其它替代方案:

聊天模型

参见LangChain 文档。如果你不想使用 OpenAI(商业 API),可以选择 Anthropic 作为商业替代方案,或 Ollama 作为开源替代方案。

嵌入模型

参见LangChain 文档。如果你不想使用 OpenAI(商业 API),可以选择 Cohere 作为商业替代方案,或 Ollama 作为开源替代方案。

向量存储

参见LangChain 文档。如果你不想使用 PGVector(Postgres 数据库的开源扩展),可以选择 Weaviate(专业的向量存储库)或 OpenSearch(流行搜索数据库中的向量搜索功能)。

这样所有 LLM 将共享相同的方法、类似的参数和返回值。以 聊天模型 和两个流行的 LLM 提供商(OpenAI 和 Anthropic)为例:

  • 两者都提供了 聊天 API,接收聊天消息(大致定义为包含类型字符串和内容字符串的对象),然后返回模型生成的新消息。
  • 但如若尝试在同一对话中同时使用这两个模型,则会马上遇到问题,因为它们的 聊天消息格式存在细微的不兼容性
  • LangChain 屏蔽了这些差异,让我们可以构建真正独立于特定提供商的应用。例如,在 LangChain 中,可以在同一对话中同时使用 OpenAI 和 Anthropic 进行对话。

最后,在用多个组件构建 LLM 应用时,我们发现 LangChain 的编排能力 非常有用:

  • 所有主要组件都支持回调系统,用于可观测性(详见第 8 章)。
  • 所有主要组件实现了相同的接口(本章后半部分会详细介绍)。
  • 长时间运行的 LLM 应用可以被中断、恢复或重试(详见第 6 章)。

配置 LangChain 环境

为能够顺利跟进本章及后续章节的内容,我们建议先安装 LangChain。

首先,请完成 OpenAI 账户或相关代理服务的注册(如尚未完成)。如果你更倾向于使用其他 LLM 提供商,请参考 “为什么选择 LangChain?” 小节中的替代方案。

然后,登录 OpenAI 账户,进入 API 密钥(API Keys) 页面,创建一个 API Key 并保存下来——很快就会用到。

注:本书的代码示例将同时提供 Python 和 JavaScript(JS) 版本。LangChain 在这两种语言中的功能基本一致,因此读者可以选择自己最熟悉的语言,并根据相应的代码示例进行学习(两种语言的代码示例是等效的)。

Python 用户的安装指南

  1. 确保已安装 Python(请根据操作系统查看安装指南)。
    也可参考Python自动化脚本-运维人员宝典第一章 Python脚本概述进一步了解Python的安装和使用。
    关于Anaconda,venv等虚拟环境的使用这里不再赘述。
  2. 安装 Jupyter Notebook(如需在 Notebook 环境中运行示例代码,可执行pip install notebook
  3. 安装 LangChain 库,运行以下命令:
  4. 将 OpenAI API Key 设置为环境变量(替换 your-key 为之前生成的 API 密钥):
  5. 启动 Jupyter Notebook

此时就可以在 Notebook 环境中运行 Python 代码示例了。

下面是针对使用JavaScript 的读者的:

  1. 将 OpenAI API Key 设置为环境变量(请替换 your-key 为你的 API 密钥):
  2. 安装 Node.js(如果尚未安装,请按照官方指南安装)。
  3. 安装 LangChain 相关库
  4. 将示例代码保存为 .js 文件,例如 example.js,然后使用以下命令运行:

在 LangChain 中使用 LLM

回顾一下,LLMs(大语言模型)是大多数生成式 AI 应用的核心引擎。LangChain 提供了两种简单的接口与任何 LLM API 提供商交互:

    • Chat Models(聊天模型)
    • LLMs(基础语言模型)

LLM 接口的基本原理是:接收字符串提示(prompt)作为输入,发送到模型提供商,并返回模型预测结果作为输出

我们来导入 LangChain 的 OpenAI LLM 封装 调用模型,并使用一个简单的 prompt 进行预测:

Python

JavaScript

输出:

小贴士

注意传递给OpenAI的参数model。这是在使用 LLM 或 Chat Model 时,最常配置的参数是,即选择要使用的底层模型。大多数 LLM 提供商会提供 多个不同的模型,它们在 能力、成本和速度 方面有所不同(通常 更强大的模型更昂贵但更慢)。请参考 OpenAI 的模型总览 了解更多详情。

除了 model,大部分模型提供商还提供了一些常见的参数可供配置:

  • temperature(温度)
    • 控制生成输出时的采样算法
    • 低温度值(如 0.1) 使输出更加可预测。
    • 高温度值(如 0.9) 使结果更加有创造力或不可预测。
    • 适用场景: 结构化输出(如代码、摘要)通常需要较低的温度,而创意写作等任务则需要较高的温度值。
  • max_tokens(最大 token 数)
    • 限制输出的大小(同时也影响成本)。
    • 设得过低可能会导致 LLM 在输出完整之前就提前停止,从而截断输出

除了这些参数,每个 LLM 提供商还会暴露出不同的可配置参数。建议查阅你所选择提供商的文档,例如 OpenAI 平台的 API 文档

与 LLM 接口不同,Chat Model 接口 允许用户与模型进行多轮对话。许多 LLM 提供商(如 OpenAI)会将发送和接收的消息划分为不同的角色:

  • System(系统角色)
    • 用于设定模型的行为,影响回答方式(例如:“你是一名 AI 编程助手”)。
  • User(用户角色)
    • 代表用户输入的查询或提供的信息。
  • Assistant(助手角色)
    • 代表模型生成的内容。

在构建 AI 聊天机器人 时,Chat Model 接口提供了更便捷的管理方式。以下是一个使用 LangChain 的 ChatOpenAI 模型 的示例:

Python

JavaScript

输出:

在 Chat Model 接口中,我们不会像 LLM 接口那样使用单个字符串作为 prompt,而是使用不同类型的聊天消息接口,每种类型对应之前提到的不同角色:

  • HumanMessage(用户消息):
    代表用户输入的内容,角色为 user
  • AIMessage(AI 消息):
    代表 AI 生成的内容,角色为 assistant
  • SystemMessage(系统消息):
    用于设定 AI 的行为和风格,角色为 system
  • ChatMessage(通用消息):
    允许自由指定角色,可用于扩展功能。

下面我们在示例中添加SystemMessage指令:

Python

JavaScript

输出:

正如你所见,模型遵循了 SystemMessage 中提供的指令,尽管用户的问题中并没有明确提到这一点。这样我们可以预先配置 AI 应用程序,让其根据用户输入以相对可预测的方式作出响应。

提升LLM 提示词可复用性

前面的部分展示了提示词(Prompt)指令如何显著影响模型的输出。提示词帮助模型理解上下文,并生成与询问相关的答案。

以下是一个详细的提示词示例:

尽管提示词看起来只是一个简单的字符串,但真正的挑战在于如何设计文本内容,以及如何根据用户的输入进行动态调整。在这个示例中,ContextQuestion 的值是硬编码的,但如果我们想要动态传递这些值呢?

没错,LangChain 还提供了提示词模板接口,让我们能够轻松构造带有动态输入的提示词:

Python

JavaScript

输出:

本例将前面静态的提示词转换为动态模板。模板定义了最终提示词的结构,并指定了动态输入将插入的位置

因此,该模板可以作为“配方”,用于构建多个具体的提示词。在使用指定值(如 contextquestion)格式化提示词时,就会生成一个静态提示词,可以直接传递给 LLM 进行处理。

如上,在 invoke 函数中,question 参数是动态传递的。默认情况下,LangChain 的提示词使用Python 的 f-string 语法来定义动态参数——任何被 {} 包裹的单词(如 {question})都是运行时插入的占位符。在前面的示例中,{question} 被替换成了 "Which model providers offer LLMs?"

接下来,我们来看如何将其传递给 LangChain 中的 OpenAI 模型:

Python

JavaScript

输出:

如果想构建一个AI 聊天应用,可以使用 ChatPromptTemplate,它允许根据聊天消息的角色提供动态输入

Python

JavaScript

输出:

请注意,该提示词包含了 SystemMessage(系统指令),以及两个 HumanMessage(用于动态上下文 context 和问题 question)。可以像之前一样格式化模板,并获得一个静态提示词,然后将其传递给大型语言模型(LLM)生成预测输出:

Python

JavaScript

输出:

从 LLM 获取指定格式的输出

纯文本输出很有用,但在某些场景下,你可能需要 LLM 生成结构化的输出——即机器可读的格式,如 JSONXMLCSV,甚至是 PythonJavaScript 代码。这在需要将 LLM 生成的内容传递给其他代码时尤其重要,使 LLM 成为更大应用程序的一部分。

JSON 输出

JSON 是 LLM 最常见的结构化输出格式。例如,可以将 JSON 发送给前端代码,或者将其存入数据库

生成 JSON 的第一步是定义 LLM 需要遵循的模式(schema),然后将该模式放在提示词中,同时提供文本来源。来看一个示例:

Python

JavaScript

输出:

首先,需要定义一个模式(schema)。在 Python 中,最简单的方法是使用 Pydantic(一个用于数据验证的库),而在 JavaScript 中,最简单的方法是使用 Zod(类似的库)。方法 with_structured_output 主要用于以下两点:

  1. 模式转换
    • 该模式会被转换为 JSONSchema(一种 JSON 格式,用于描述 JSON 数据的结构,如类型、字段名称、描述等),然后传递给 LLM。
    • 对于不同的 LLM,LangChain 会选择最合适的方法来执行此操作,通常是函数调用(function calling)或提示工程(prompting)
  2. 输出验证:该模式还用于验证 LLM 返回的输出,确保其完全符合你传递的模式

使用输出解析器生成其他机器可读格式

还可以让 LLM 或聊天模型生成其他格式的输出,如 CSVXML。这时,输出解析器(Output Parsers)就派上用场了。输出解析器是一些辅助结构化大语言模型响应的类,有两大主要功能:

  1. 提供格式指令:输出解析器可以在提示词中添加额外的指令,引导 LLM 按指定格式生成文本
  2. 验证和解析输出
    • 主要作用是将 LLM 生成的文本解析为更结构化的格式(如列表、XML 等)。
    • 解析器还可以执行去除冗余信息、修正不完整输出、验证解析值等操作。

来看一个输出解析器的示例

Python

JavaScript

输出:

LangChain 提供了多种输出解析器(Output Parsers),适用于不同的应用场景,如 CSVXML 等。接下来,我们将学习如何将输出解析器、模型和提示词(Prompts) 结合使用。

组装LLM应用的核心组件

我们已经学习了 LangChain 框架的核心构建模块。那么,如何有效地组合这些模块,从而构建出一个完整的 LLM 应用呢?

使用 Runnable 接口

读者可能已经注意到,在前面的示例中,所有代码示例都使用了类似的接口,并通过 invoke() 方法从模型(或提示模板、输出解析器)中生成输出。所有组件都包含:

  • 以下通用接口方法:
    • invoke():将单个输入转换为输出
    • batch():高效地将多个输入转换为多个输出
    • stream():从单个输入流式输出,随着生成过程逐步返回
  • 此外,LangChain 还提供了内置工具,支持:
    • 重试(Retries):自动处理失败的请求
    • 回退(Fallbacks):当一个方法失败时,可以自动使用备用方法
    • 模式(Schemas):定义输入和输出的格式
    • 运行时配置(Runtime Configurability):允许在运行时修改行为
  • 在 Python 中,所有这个方法都有 asyncio 异步版本,方便异步任务调度

由于这些方法具有一致的调用方式,你可以像操作一个组件一样操作所有组件

Python

JavaScript

本例中,我们看到三种主要方法的运行方式

  1. invoke():接受一个输入,返回一个输出
  2. batch():接受一组输入,返回一组输出
  3. stream():接受一个输入,以流式方式返回多个部分的输出。

有时底层组件不支持迭代输出,则只会返回一个完整的结果

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

You can combine these components in two ways:

Imperative
Call your components directly, for example, with model.invoke(...)
Declarative
Use LangChain Expression Language (LCEL), as covered in an upcoming section

1. 命令式(Imperative):直接调用组件,例如:model.invoke(...)

2. 声明式(Declarative):使用 LangChain表达式(LCEL),将在后续章节中介绍。

表 1-1 总结了这两种方式的主要区别,我们将在接下来的内容中详细演示它们的实际应用。

表 1-1:命令式(Imperative) vs. 声明式(Declarative) 组合的主要区别

对比项 命令式(Imperative) 声明式(Declarative)
语法 使用完整的 Python 或 JavaScript 代码 使用 LangChain表达式(LCEL)
并行执行 Python: 使用线程(Threads)或协程(Coroutines) JavaScript: 使用 Promise.all 自动处理并行执行
流式处理 通过 yield 关键字实现 自动支持流式处理
异步执行 通过 async 函数手动实现 自动支持异步执行

命令式(Imperative)组合

命令式组合听起来很高大上,其实就是我们平时编写代码的方式,将组件组织成函数和类。例如,下例中组合了提示模板(Prompt)、模型(LLM)和输出解析器(Output Parser)

Python

JavaScript

输出:

上例是一个完整的聊天机器人示例,使用了提示词和聊天模型。可以看到,它使用了我们熟悉的Python语法并支持在函数中添加任意自定义逻辑。

如果希望添加流式和异步支持,则需要对函数进行修改。例如,以下添加了流式响应支持:

Python

JavaScript

输出:

所以,不论是JS还是Python,都可以通过在自定义函数中抛出(yield)值,然后通过stream调用。

如需异步执行,则应重写函数如下:

Python

这个只适用于Python,因为在JavaScript,所有代码默认都是异步的,而 Python 需要显式标记 async

声明式(Declarative)组合

LCEL(LangChain Expression Language)是一种声明式语言,用于组合 LangChain 组件。LangChain 会将 LCEL 组合编译为优化的执行计划,并自动支持以下功能:

  • 并行化(Parallelization)
  • 流式处理(Streaming)
  • 跟踪(Tracing)
  • 异步支持(Async Support)

这种方式使得开发者可以更高效地构建 LLM 应用,无需手动编写并行执行、流式传输或异步调用的逻辑。

下面将示例修改为使用LCEL:

Python

JavaScript

输出:

两种示例的最后一行是一致的,即使用函数和LCEL序列都按同样的方式使用invoke/stream/batch。而在这一版本中,不需要做额外操作即可使用流式响应:

Python

JavaScript

对于Python,使用异步方法的方式也相同:

Python

小结

在本章中,我们学习了构建 LLM 应用所需的核心结构关键组件,并掌握了如何使用 LangChain 来开发 LLM 应用。

LLM 应用的核心是一个链式结构,主要包括:

  • 大语言模型(LLM):用于生成预测结果
  • 提示词(Prompt):用于引导模型生成所需的输出
  • 输出解析器(Output Parser)(可选):用于转换 LLM 的输出格式

所有 LangChain 组件都具备相同的接口,包括:

  • invoke():处理单个输入
  • stream():处理流式输出
  • batch():处理多个输入

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

  • 命令式(Imperative)方式:直接调用组件,适合需要编写大量自定义逻辑的场景
  • 声明式(Declarative)方式(LCEL):使用 LangChain表达式语言(LCEL) 进行组件拼装,适合快速搭建现有组件的场景

下一章(第 2 章),我们将学习如何为 AI 聊天机器人提供外部数据作为其上文,从而让 LLM 能够基于特定数据进行交互,实现“与数据对话”的能力。

喜欢 (0)
[]
分享 (0)
发表我的评论
取消评论

表情 贴图 加粗 删除线 居中 斜体 签到

Hi,您需要填写昵称和邮箱!

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址