我们都体验过LLM 提示词的强大之处,也亲眼见证过不同的提示词技巧如何影响 LLM 的输出效果,在合理进行组合时效果更佳。构建优秀的 LLM 应用的挑战在于如何有效地构造发送给模型的提示词,并处理模型的预测结果以返回准确的输出(见图 1-1)。
图 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 用户的安装指南
- 确保已安装 Python(请根据操作系统查看安装指南)。
也可参考Python自动化脚本-运维人员宝典第一章 Python脚本概述进一步了解Python的安装和使用。
关于Anaconda,venv等虚拟环境的使用这里不再赘述。 - 安装 Jupyter Notebook(如需在 Notebook 环境中运行示例代码,可执行
pip install notebook
) - 安装 LangChain 库,运行以下命令:
12pip install langchain langchain-openai langchain-communitypip install langchain-text-splitters langchain-postgres - 将 OpenAI API Key 设置为环境变量(替换
your-key
为之前生成的 API 密钥):
12export OPENAI_API_KEY=your-key# 使用代理服务时可再配置 export OPENAI_BASE_URL=xxx - 启动 Jupyter Notebook:
1jupyter notebook
此时就可以在 Notebook 环境中运行 Python 代码示例了。
下面是针对使用JavaScript 的读者的:
- 将 OpenAI API Key 设置为环境变量(请替换
your-key
为你的 API 密钥):
12export OPENAI_API_KEY=your-key# 使用代理服务时可再配置 export OPENAI_BASE_URL=xxx - 安装 Node.js(如果尚未安装,请按照官方指南安装)。
- 安装 LangChain 相关库:
12npm install langchain @langchain/openai @langchain/communitynpm install @langchain/core pg - 将示例代码保存为
.js
文件,例如example.js
,然后使用以下命令运行:
1node ./example.js
在 LangChain 中使用 LLM
回顾一下,LLMs(大语言模型)是大多数生成式 AI 应用的核心引擎。LangChain 提供了两种简单的接口与任何 LLM API 提供商交互:
-
- Chat Models(聊天模型)
- LLMs(基础语言模型)
LLM 接口的基本原理是:接收字符串提示(prompt)作为输入,发送到模型提供商,并返回模型预测结果作为输出。
我们来导入 LangChain 的 OpenAI LLM 封装 调用模型,并使用一个简单的 prompt 进行预测:
Python
1 2 3 4 5 |
from langchain_openai.llms import OpenAI model = OpenAI(model="gpt-4o-mini") model.invoke("The sky is") |
JavaScript
1 2 3 4 5 |
import { OpenAI } from "@langchain/openai"; const model = new OpenAI({ model: "gpt-4o-mini" }); await model.invoke("The sky is"); |
输出:
1 |
Blue! |
小贴士
注意传递给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
1 2 3 4 5 6 7 |
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
1 2 3 4 5 6 7 |
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) |
输出:
1 |
AIMessage(content='The capital of France is Paris.') |
在 Chat Model 接口中,我们不会像 LLM 接口那样使用单个字符串作为 prompt,而是使用不同类型的聊天消息接口,每种类型对应之前提到的不同角色:
HumanMessage
(用户消息):
代表用户输入的内容,角色为user
。AIMessage
(AI 消息):
代表 AI 生成的内容,角色为assistant
。SystemMessage
(系统消息):
用于设定 AI 的行为和风格,角色为system
。ChatMessage
(通用消息):
允许自由指定角色,可用于扩展功能。
下面我们在示例中添加SystemMessage
指令:
Python
1 2 3 4 5 6 7 8 9 10 11 |
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
1 2 3 4 5 6 7 8 9 10 11 12 13 |
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); |
输出:
1 |
AIMessage('Paris!!!') |
正如你所见,模型遵循了 SystemMessage
中提供的指令,尽管用户的问题中并没有明确提到这一点。这样我们可以预先配置 AI 应用程序,让其根据用户输入以相对可预测的方式作出响应。
提升LLM 提示词可复用性
前面的部分展示了提示词(Prompt)指令如何显著影响模型的输出。提示词帮助模型理解上下文,并生成与询问相关的答案。
以下是一个详细的提示词示例:
1 2 3 4 5 6 7 8 9 10 11 12 13 |
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: 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? Answer: |
尽管提示词看起来只是一个简单的字符串,但真正的挑战在于如何设计文本内容,以及如何根据用户的输入进行动态调整。在这个示例中,Context
和 Question
的值是硬编码的,但如果我们想要动态传递这些值呢?
没错,LangChain 还提供了提示词模板接口,让我们能够轻松构造带有动态输入的提示词:
Python
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
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
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
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?" }) |
输出:
1 2 3 4 5 6 7 8 9 |
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: ') |
本例将前面静态的提示词转换为动态模板。模板定义了最终提示词的结构,并指定了动态输入将插入的位置。
因此,该模板可以作为“配方”,用于构建多个具体的提示词。在使用指定值(如 context
和 question
)格式化提示词时,就会生成一个静态提示词,可以直接传递给 LLM 进行处理。
如上,在 invoke
函数中,question
参数是动态传递的。默认情况下,LangChain 的提示词使用Python 的 f-string 语法来定义动态参数——任何被 {}
包裹的单词(如 {question}
)都是运行时插入的占位符。在前面的示例中,{question}
被替换成了 "Which model providers offer LLMs?"
接下来,我们来看如何将其传递给 LangChain 中的 OpenAI 模型:
Python
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 |
from langchain_openai.llms import OpenAI from langchain_core.prompts import PromptTemplate # both `template` and `model` can be reused many times 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` and `completion` are the results of using template and model once 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
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 |
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) |
输出:
1 2 |
Hugging Face's `transformers` library, OpenAI using the `openai` library, and Cohere using the `cohere` library offer LLMs. |
如果想构建一个AI 聊天应用,可以使用 ChatPromptTemplate
,它允许根据聊天消息的角色提供动态输入:
Python
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
from langchain_core.prompts import ChatPromptTemplate template = ChatPromptTemplate.from_messages([ ('system', '''Answer the question based on the context below. If the question cannot be answered using the information provided, answer with "I don\'t know".'''), ('human', 'Context: {context}'), ('human', 'Question: {question}'), ]) 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
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
import { ChatPromptTemplate } from '@langchain/core/prompts' const template = ChatPromptTemplate.fromMessages([ ['system', `Answer the question based on the context below. If the question cannot be answered using the information provided, answer with "I don\'t know".`], ['human', 'Context: {context}'], ['human', 'Question: {question}'], ]) 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?" }) |
输出:
1 2 3 4 5 6 7 8 9 10 |
ChatPromptValue(messages=[SystemMessage(content='Answer the question based on the context below. If the question cannot be answered using the information provided, answer with "I don\'t know".'), HumanMessage(content="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."), HumanMessage (content='Question: Which model providers offer LLMs?')]) |
请注意,该提示词包含了 SystemMessage
(系统指令),以及两个 HumanMessage
(用于动态上下文 context
和问题 question
)。可以像之前一样格式化模板,并获得一个静态提示词,然后将其传递给大型语言模型(LLM)生成预测输出:
Python
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 |
from langchain_openai.chat_models import ChatOpenAI from langchain_core.prompts import ChatPromptTemplate # both `template` and `model` can be reused many times template = ChatPromptTemplate.from_messages([ ('system', '''Answer the question based on the context below. If the question cannot be answered using the information provided, answer with "I don\'t know".'''), ('human', 'Context: {context}'), ('human', 'Question: {question}'), ]) model = ChatOpenAI() # `prompt` and `completion` are the results of using template and model once 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?" }) model.invoke(prompt) |
JavaScript
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
import { ChatPromptTemplate } from '@langchain/core/prompts' import { ChatOpenAI } from '@langchain/openai' const model = new ChatOpenAI() const template = ChatPromptTemplate.fromMessages([ ['system', `Answer the question based on the context below. If the question cannot be answered using the information provided, answer with "I don\'t know".`], ['human', 'Context: {context}'], ['human', 'Question: {question}'], ]) 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) |
输出:
1 2 |
AIMessage(content="Hugging Face's `transformers` library, OpenAI using the `openai` library, and Cohere using the `cohere` library offer LLMs.") |
从 LLM 获取指定格式的输出
纯文本输出很有用,但在某些场景下,你可能需要 LLM 生成结构化的输出——即机器可读的格式,如 JSON
、XML
、CSV
,甚至是 Python
或 JavaScript
代码。这在需要将 LLM 生成的内容传递给其他代码时尤其重要,使 LLM 成为更大应用程序的一部分。
JSON 输出
JSON
是 LLM 最常见的结构化输出格式。例如,可以将 JSON 发送给前端代码,或者将其存入数据库。
生成 JSON 的第一步是定义 LLM 需要遵循的模式(schema),然后将该模式放在提示词中,同时提供文本来源。来看一个示例:
Python
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
from langchain_openai import ChatOpenAI from langchain_core.pydantic_v1 import BaseModel class AnswerWithJustification(BaseModel): '''An answer to the user's question along with justification for the answer.''' answer: str '''The answer to the user's question''' justification: str '''Justification for the answer''' 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
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
import { ChatOpenAI } from '@langchain/openai' import { z } from "zod"; const answerSchema = z .object({ answer: z.string().describe("The answer to the user's question"), justification: z.string().describe(`Justification for the answer`), }) .describe(`An answer to the user's question along with justification for the answer.`); 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") |
输出:
1 2 3 4 5 |
{ 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 } |
首先,需要定义一个模式(schema)。在 Python 中,最简单的方法是使用 Pydantic(一个用于数据验证的库),而在 JavaScript 中,最简单的方法是使用 Zod(类似的库)。方法 with_structured_output
主要用于以下两点:
- 模式转换
- 该模式会被转换为
JSONSchema
(一种 JSON 格式,用于描述 JSON 数据的结构,如类型、字段名称、描述等),然后传递给 LLM。 - 对于不同的 LLM,LangChain 会选择最合适的方法来执行此操作,通常是函数调用(function calling)或提示工程(prompting)。
- 该模式会被转换为
- 输出验证:该模式还用于验证 LLM 返回的输出,确保其完全符合你传递的模式。
使用输出解析器生成其他机器可读格式
还可以让 LLM 或聊天模型生成其他格式的输出,如 CSV
或 XML
。这时,输出解析器(Output Parsers)就派上用场了。输出解析器是一些辅助结构化大语言模型响应的类,有两大主要功能:
- 提供格式指令:输出解析器可以在提示词中添加额外的指令,引导 LLM 按指定格式生成文本。
- 验证和解析输出
- 主要作用是将 LLM 生成的文本解析为更结构化的格式(如列表、XML 等)。
- 解析器还可以执行去除冗余信息、修正不完整输出、验证解析值等操作。
来看一个输出解析器的示例:
Python
1 2 3 |
from langchain_core.output_parsers import CommaSeparatedListOutputParser parser = CommaSeparatedListOutputParser() items = parser.invoke("apple, banana, cherry") |
JavaScript
1 2 3 4 5 |
import { CommaSeparatedListOutputParser } from '@langchain/core/output_parsers' const parser = new CommaSeparatedListOutputParser() await parser.invoke("apple, banana, cherry") |
输出:
1 |
['apple', 'banana', 'cherry'] |
LangChain 提供了多种输出解析器(Output Parsers),适用于不同的应用场景,如 CSV
、XML
等。接下来,我们将学习如何将输出解析器、模型和提示词(Prompts) 结合使用。
组装LLM应用的核心组件
我们已经学习了 LangChain 框架的核心构建模块。那么,如何有效地组合这些模块,从而构建出一个完整的 LLM 应用呢?
使用 Runnable 接口
读者可能已经注意到,在前面的示例中,所有代码示例都使用了类似的接口,并通过 invoke()
方法从模型(或提示模板、输出解析器)中生成输出。所有组件都包含:
- 以下通用接口方法:
invoke()
:将单个输入转换为输出batch()
:高效地将多个输入转换为多个输出stream()
:从单个输入流式输出,随着生成过程逐步返回
- 此外,LangChain 还提供了内置工具,支持:
- 重试(Retries):自动处理失败的请求
- 回退(Fallbacks):当一个方法失败时,可以自动使用备用方法
- 模式(Schemas):定义输入和输出的格式
- 运行时配置(Runtime Configurability):允许在运行时修改行为
- 在 Python 中,所有这个方法都有
asyncio
异步版本,方便异步任务调度。
由于这些方法具有一致的调用方式,你可以像操作一个组件一样操作所有组件:
Python
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
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
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
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()
:接受一个输入,以流式方式返回多个部分的输出。
有时底层组件不支持迭代输出,则只会返回一个完整的结果。
可以通过两种方式组合这些组件:
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
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
from langchain_openai.chat_models import ChatOpenAI from langchain_core.prompts import ChatPromptTemplate from langchain_core.runnables import chain # the building blocks template = ChatPromptTemplate.from_messages([ ('system', 'You are a helpful assistant.'), ('human', '{question}'), ]) model = ChatOpenAI() # combine them in a function # @chain decorator adds the same Runnable interface for any function you write @chain def chatbot(values): prompt = template.invoke(values) return model.invoke(prompt) # use it chatbot.invoke({"question": "Which model providers offer LLMs?"}) |
JavaScript
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 |
import {ChatOpenAI} from '@langchain/openai' import {ChatPromptTemplate} from '@langchain/core/prompts' import {RunnableLambda} from '@langchain/core/runnables' // the building blocks const template = ChatPromptTemplate.fromMessages([ ['system', 'You are a helpful assistant.'], ['human', '{question}'], ]) const model = new ChatOpenAI() // combine them in a function // RunnableLambda adds the same Runnable interface for any function you write const chatbot = RunnableLambda.from(async values => { const prompt = await template.invoke(values) return await model.invoke(prompt) }) // use it await chatbot.invoke({ "question": "Which model providers offer LLMs?" }) |
输出:
1 2 |
AIMessage(content="Hugging Face's `transformers` library, OpenAI using the `openai` library, and Cohere using the `cohere` library offer LLMs.") |
上例是一个完整的聊天机器人示例,使用了提示词和聊天模型。可以看到,它使用了我们熟悉的Python语法并支持在函数中添加任意自定义逻辑。
如果希望添加流式和异步支持,则需要对函数进行修改。例如,以下添加了流式响应支持:
Python
1 2 3 4 5 6 7 8 9 10 |
@chain def chatbot(values): prompt = template.invoke(values) for token in model.stream(prompt): yield token for part in chatbot.stream({ "question": "Which model providers offer LLMs?" }): print(part) |
JavaScript
1 2 3 4 5 6 7 8 9 10 11 12 |
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": "Which model providers offer LLMs?" })) { console.log(token) } |
输出:
1 2 3 4 |
AIMessageChunk(content="Hugging") AIMessageChunk(content=" Face's") AIMessageChunk(content=" `transformers`") ... |
所以,不论是JS还是Python,都可以通过在自定义函数中抛出(yield
)值,然后通过stream
调用。
如需异步执行,则应重写函数如下:
Python
1 2 3 4 5 6 7 8 |
@chain async def chatbot(values): prompt = await template.ainvoke(values) return await model.ainvoke(prompt) await chatbot.ainvoke({"question": "Which model providers offer LLMs?"}) # > AIMessage(content="""Hugging Face's `transformers` library, OpenAI using the `openai` library, and Cohere using the `cohere` library offer LLMs.""") |
这个只适用于Python,因为在JavaScript,所有代码默认都是异步的,而 Python 需要显式标记 async
。
声明式(Declarative)组合
LCEL(LangChain Expression Language)是一种声明式语言,用于组合 LangChain 组件。LangChain 会将 LCEL 组合编译为优化的执行计划,并自动支持以下功能:
- 并行化(Parallelization)
- 流式处理(Streaming)
- 跟踪(Tracing)
- 异步支持(Async Support)
这种方式使得开发者可以更高效地构建 LLM 应用,无需手动编写并行执行、流式传输或异步调用的逻辑。
下面将示例修改为使用LCEL:
Python
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
from langchain_openai.chat_models import ChatOpenAI from langchain_core.prompts import ChatPromptTemplate # the building blocks template = ChatPromptTemplate.from_messages([ ('system', 'You are a helpful assistant.'), ('human', '{question}'), ]) model = ChatOpenAI() # combine them with the | operator chatbot = template | model # use it chatbot.invoke({"question": "Which model providers offer LLMs?"}) |
JavaScript
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
import { ChatOpenAI } from '@langchain/openai' import { ChatPromptTemplate } from '@langchain/core/prompts' import { RunnableLambda } from '@langchain/core/runnables' // the building blocks const template = ChatPromptTemplate.fromMessages([ ['system', 'You are a helpful assistant.'], ['human', '{question}'], ]) const model = new ChatOpenAI() // combine them in a function const chatbot = template.pipe(model) // use it await chatbot.invoke({ "question": "Which model providers offer LLMs?" }) |
输出:
1 2 |
AIMessage(content="Hugging Face's `transformers` library, OpenAI using the `openai` library, and Cohere using the `cohere` library offer LLMs.") |
两种示例的最后一行是一致的,即使用函数和LCEL序列都按同样的方式使用invoke/stream/batch
。而在这一版本中,不需要做额外操作即可使用流式响应:
Python
1 2 3 4 5 6 7 8 9 10 |
chatbot = template | model for part in chatbot.stream({ "question": "Which model providers offer LLMs?" }): print(part) # > AIMessageChunk(content="Hugging") # > AIMessageChunk(content=" Face's") # > AIMessageChunk(content=" `transformers`") # ... |
JavaScript
1 2 3 4 5 6 7 |
const chatbot = template.pipe(model) for await (const token of await chatbot.stream({ "question": "Which model providers offer LLMs?" })) { console.log(token) } |
对于Python,使用异步方法的方式也相同:
Python
1 2 3 4 5 |
chatbot = template | model await chatbot.ainvoke({ "question": "Which model providers offer LLMs?" }) |
小结
在本章中,我们学习了构建 LLM 应用所需的核心结构和关键组件,并掌握了如何使用 LangChain 来开发 LLM 应用。
LLM 应用的核心是一个链式结构,主要包括:
- 大语言模型(LLM):用于生成预测结果
- 提示词(Prompt):用于引导模型生成所需的输出
- 输出解析器(Output Parser)(可选):用于转换 LLM 的输出格式
所有 LangChain 组件都具备相同的接口,包括:
invoke()
:处理单个输入stream()
:处理流式输出batch()
:处理多个输入
这些组件可以通过两种方式组合:
- 命令式(Imperative)方式:直接调用组件,适合需要编写大量自定义逻辑的场景
- 声明式(Declarative)方式(LCEL):使用 LangChain表达式语言(LCEL) 进行组件拼装,适合快速搭建现有组件的场景
下一章(第 2 章),我们将学习如何为 AI 聊天机器人提供外部数据作为其上文,从而让 LLM 能够基于特定数据进行交互,实现“与数据对话”的能力。