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

人工智能 Alan 1个月前 (03-14) 577次浏览 1个评论 扫描二维码

我们都体验过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 相关库(如安装速度不理想可使用--registry https://registry.npm.taobao.org或添加全局配置)
  4. 将示例代码保存为 .js 文件,例如 example.js,然后使用以下命令运行:

在 LangChain 中使用 LLM

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

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

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

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

Python

JavaScript

示例输出:

小贴士

注意传递给ChatOpenAI的参数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,您需要填写昵称和邮箱!

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址
(1)个小伙伴在吐槽
  1. 追更 :mrgreen:
    向往2025-03-26 11:30 回复