LangChain教程(Python版本)
一、LangChain入门
1.LangChain 简介
1`简介
LangChain是一个开源的Python AI应用开发框架,它提供了构建基于大模型的AI应用所需的模块和工具。通过LangChain,开发者可以轻松地与大型语言模型(LLM)集成,完成文本生成、问答、翻译、对话等任务。LangChain降低了AI应用开发的门槛,让任何人都可以基于LLM构建属于自己的创意应用。
LangChain特性:
- LLM 和提示(Prompt):LangChain 对所有LLM大模型进行了API抽象,统一了大模型访问API,同时提供了Prompt提示模板管理机制。
- 链(Chain):Langchain对一些常见的场景封装了一些现成的模块,例如:基于上下文信息的问答系统,自然语言生成SQL查询等等,因为实现这些任务的过程就像工作流一样,一步一步的执行,所以叫链(chain)。
- LCEL:LangChain Expression Language (LCEL), langchain新版本的核心特性,用于解决工作流编排问题,通过LCEL表达式,我们可以灵活的自定义AI任务处理流程,也就是灵活自定义链(Chain)。
- 数据增强生成(RAG):因为大模型(LLM)不了解新的信息,无法回答新的问题,所以我们可以将新的信息导入到LLM,用于增强LLM生成内容的质量,这种模式叫做RAG模式(Retrieval Augmented Generation)。
- Agents:是一种基于大模型(LLM)的应用设计模式,利用LLM的自然语言理解和推理能力(LLM作为大脑)),根据用户的需求自动调用外部系统、设备共同去完成任务,例如:用户输入“明天请假一天”, 大模型(LLM)自动调用请假系统,发起一个请假申请。
- 模型记忆(memory):让大模型(llm)记住之前的对话内容,这种能力成为模型记忆(memory)。
2.LangChain框架组成
LangChain框架由几个部分组成,包括:
- LangChain库:Python和JavaScript库。包含接口和集成多种组件的运行时基础,以及现成的链和代理的实现。
- LangChain模板:Langchain官方提供的一些AI任务模板。
- LangServe:基于FastAPI可以将Langchain定义的链(Chain),发布成为REST API。
- LangSmith:开发平台,是个云服务,支持Langchain debug、任务监控。
3.LangChain库(Libraries)
LangChain库本身由几个不同的包组成。
langchain-core
:基础抽象和LangChain表达语言。langchain-community
:第三方集成,主要包括langchain集成的第三方组件。langchain
:主要包括链(chain)、代理(agent)和检索策略。
4.langchain任务处理流程
如上图,langChain提供一套提示词模板(prompt template)管理工具,负责处理提示词,然后传递给大模型处理,最后处理大模型返回的结果,
LangChain对大模型的封装主要包括LLM和Chat Model两种类型。
- LLM - 问答模型,模型接收一个文本输入,然后返回一个文本结果。
- Chat Model - 对话模型,接收一组对话消息,然后返回对话消息,类似聊天消息一样。
5.核心概念
1`. LLMs
LangChain封装的基础模型,模型接收一个文本输入,然后返回一个文本结果。
2`. Chat Models
聊天模型(或者成为对话模型),与 LLMs 不同,这些模型专为对话场景而设计。模型可以接收一组对话消息,然后返回对话消息,类似聊天消息一样。
3`. 消息(Message)
指的是聊天模型(Chat Models)的消息内容,消息类型包括包括 HumanMessage、AIMessage、SystemMessage、FunctionMessage 和 ToolMessage等多种类型的消息。
4`. 提示(prompts)
LangChain封装了一组专门用于提示词(prompts)管理的工具类,方便我们格式化提示词(prompts)内容。
5`. 输出解析器(Output Parsers)
如上图介绍,Langchain接受大模型(llm)返回的文本内容之后,可以使用专门的输出解析器对文本内容进行格式化,例如解析json、或者将llm输出的内容转成python对象。
6`. Retrievers
为方便我们将私有数据导入到大模型(LLM), 提高模型回答问题的质量,LangChain封装了检索框架(Retrievers),方便我们加载文档数据、切割文档数据、存储和检索文档数据。
7`. 向量存储(Vector stores)
为支持私有数据的语义相似搜索,langchain支持多种向量数据库。
8`. Agents
智能体(Agents),通常指的是以大模型(LLM)作为决策引擎,根据用户输入的任务,自动调用外部系统、硬件设备共同完成用户的任务,是一种以大模型(LLM)为核心的应用设计模式。
9`应用场景
- 对话机器人:构建智能的对话助手、客服机器人、聊天机器人等。
- 知识库问答:结合知识图谱,进行开放域问题的问答服务。
- 智能写作:如文章写作、创意写作、文本摘要等。
2.LangChain 快速入门
1. 快速入门
下面介绍langchain的简单例子,如何通过langchain框架调用模型完成任务。
2. Langchain例子
2.1. 安装LangChain
要安装LangChain,可以使用Pip和Conda进行安装。以下是安装LangChain的步骤:
使用Pip:
pip install langchain
使用Conda:
conda install langchain -c conda-forge
2.2. 初始化模型
在使用LangChain之前,需要导入LangChain x OpenAI集成包,并设置API密钥作为环境变量或直接传递给OpenAI LLM类。
首先,获取OpenAI的API密钥,可以通过创建账户并访问此链接来获取。然后,可以将API密钥设置为环境变量,方法如下:
export OPENAI_API_KEY="YOUR_API_KEY"
接下来,初始化模型:
from langchain_openai import ChatOpenAI
llm = ChatOpenAI()
如果不希望设置环境变量,可以在初始化OpenAI LLM类时直接传递API密钥:
from langchain_openai import ChatOpenAI
llm = ChatOpenAI(openai_api_key="YOUR_API_KEY")
2.3. 使用LLM
使用LLM来回答问题非常简单。可以直接调用LLM的invoke
方法,并传入问题作为参数。此外,还可以通过提示模板(prompt template)生成提示词,用于向模型(LLM)发送指令。
下面演示了如何构建一个简单的LLM链(chains):
from langchain_core.prompts import ChatPromptTemplate
# 创建一个提示模板(prompt template)
# 这里以对话模型的消息格式为例子,不熟悉openai对话模型消息格式,建议先学习OpenAI的API教程
# 下面消息模板,定义两条消息,system消息告诉模型扮演什么角色,user消息代表用户输入的问题,这里用了一个占位符{input} 代表接受一个模版参数input。
prompt = ChatPromptTemplate.from_messages([
("system", "You are world class technical documentation writer."),
("user", "{input}")
])
# 基于LCEL 表达式构建LLM链,lcel语法类似linux的pipeline语法,从左到右按顺序执行
# 下面编排了一个简单的工作流,首先执行prompt完成提示词模板(prompt template)格式化处理, 然后将格式化后的prompt传递给llm模型执行,最终返回llm执行结果。
chain = prompt | llm
# 调用LLM链并设置模板参数input, invoke会把调用参数传递给prompt提示模板,开始chain定义的步骤开始逐步执行。
chain.invoke({"input": "how can langsmith help with testing?"})
2.4. 输出转换
LLM的输出通常是一条消息,为了更方便处理结果,可以将消息转换为字符串。下面展示如何将LLM的输出消息转换为字符串:
from langchain_core.output_parsers import StrOutputParser
# 创建一个字符串输出解析器
output_parser = StrOutputParser()
# 将输出解析器添加到LLM链中,跟前面的例子,区别就是工作流编排,最后一步将llm模型输出的结果传递给output_parser进行格式转换
chain = prompt | llm | output_parser
# 调用LLM链并提出问题
chain.invoke({"input": "how can langsmith help with testing?"})
以上是关于LLM链的介绍,希望能帮助您更好地理解如何安装LangChain并构建不同类型的链。
3.LangChain 安装
LangChain安装方式汇总
2.1. 使用Pip安装
使用Pip进行LangChain的安装是一种常见的方式。您可以通过以下命令来安装LangChain:
pip install langchain
确保您已经设置好Pip环境,并且具有足够的权限来安装软件包。
2.2. 使用Conda安装
另一种安装LangChain的方法是使用Conda。您可以通过以下命令来在Conda环境中安装LangChain:
conda install langchain -c conda-forge
Conda的安装方式也是非常简单直接的。
3. 安装额外包
安装LangChain之后,您可能需要安装一些额外的包来扩展其功能或访问特定的功能模块。以下是一些常用的LangChain额外包及其安装方法:
3.1. LangChain Community
LangChain Community包提供了与第三方集成所需的工具。您可以使用以下命令来安装:
pip install langchain-community
3.2. LangChain Core
如果您需要LangChain的基本抽象和LangChain表达语言,可以安装LangChain Core包:
pip install langchain-core
3.3. LangChain Experimental
对于研究目的的实验性代码,您可以安装LangChain Experimental包:
pip install langchain-experimental
安装这些额外包可以帮助您更好地利用LangChain的功能并探索更多可能性。
4. LangServe设置
LangServe可帮助您部署LangChain,将链(chains)作为REST API。如果您不使用LangChain CLI,可以按照以下方式安装LangServe:
安装客户端和服务器依赖:
pip install "langserve[all]"
仅安装客户端代码:
pip install "langserve[client]"
仅安装服务器代码:
pip install "langserve[server]"
根据您的需求选择相应的安装方式,以便成功部署LangServe。
5. LangChain CLI安装
LangChain CLI是一个管理LangChain模板和项目的重要工具。您可以使用以下命令来安装LangChain CLI:
pip install langchain-cli
安装LangChain CLI后,您可以更便捷地管理您的LangChain项目并进行各种操作。
二、提示词管理
1.LangChain Prompt templates(提示词模板)
语言模型以文本作为输入 - 这个文本通常被称为提示词(prompt)。在开发过程中,对于提示词通常不能直接硬编码,不利于提示词管理,而是通过提示词模板进行维护,类似开发过程中遇到的短信模板、邮件模板等等。
1.什么是提示词模板?
提示词模板本质上跟平时大家使用的邮件模板、短信模板没什么区别,就是一个字符串模板,模板可以包含一组模板参数,通过模板参数值可以替换模板对应的参数。
一个提示词模板可以包含下面内容:
- 发给大语言模型(LLM)的指令。
- 一组问答示例,以提醒AI以什么格式返回请求。
- 发给语言模型的问题。
2.创建一个提示词模板(prompt template)
可以使用 PromptTemplate
类创建简单的提示词。提示词模板可以内嵌任意数量的模板参数,然后通过参数值格式化模板内容。
from langchain.prompts import PromptTemplate
# 定义一个提示模板,包含adjective和content两个模板变量,模板变量使用{}包括起来
prompt_template = PromptTemplate.from_template(
"Tell me a {adjective} joke about {content}."
)
# 通过模板参数格式化提示模板
prompt_template.format(adjective="funny", content="chickens")
模板输出结果:
'Tell me a funny joke about chickens.'
3.聊天消息提示词模板(chat prompt template)
聊天模型(Chat Model)以聊天消息列表作为输入,这个聊天消息列表的消息内容也可以通过提示词模板进行管理。这些聊天消息与原始字符串不同,因为每个消息都与“角色(role)”相关联。
例如,在OpenAI的Chat Completion API中,Openai的聊天模型,给不同的聊天消息定义了三种角色类型分别是助手(assistant)、人类(human)或系统(system)角色:
- 助手(Assistant) 消息指的是当前消息是AI回答的内容
- 人类(user)消息指的是你发给AI的内容
- 系统(system)消息通常是用来给AI身份进行描述。
创建聊天消息模板例子
from langchain_core.prompts import ChatPromptTemplate
# 通过一个消息数组创建聊天消息模板
# 数组每一个元素代表一条消息,每个消息元组,第一个元素代表消息角色(也成为消息类型),第二个元素代表消息内容。
# 消息角色:system代表系统消息、human代表人类消息,ai代表LLM返回的消息内容
# 下面消息定义了2个模板参数name和user_input
chat_template = ChatPromptTemplate.from_messages(
[
("system", "You are a helpful AI bot. Your name is {name}."),
("human", "Hello, how are you doing?"),
("ai", "I'm doing well, thanks!"),
("human", "{user_input}"),
]
)
# 通过模板参数格式化模板内容
messages = chat_template.format_messages(name="Bob", user_input="What is your name?")
另外一种消息格式例子:
from langchain.prompts import HumanMessagePromptTemplate
from langchain_core.messages import SystemMessage
from langchain_openai import ChatOpenAI
# 使用langchain定义的SystemMessage、HumanMessagePromptTemplate等工具类定义消息,跟前面的例子类似,下面定义了两条消息
chat_template = ChatPromptTemplate.from_messages(
[
SystemMessage(
content=(
"You are a helpful assistant that re-writes the user's text to "
"sound more upbeat."
)
),
HumanMessagePromptTemplate.from_template("{text}"),
]
)
# 使用模板参数格式化模板
messages = chat_template.format_messages(text="I don't like eating tasty things")
print(messages)
通常我们不会直接使用format_messages函数格式化提示模板(prompt templae)内容, 而是交给Langchain框架自动处理。
2.LangChain 提示词追加示例
提示词中包含交互样本的作用是为了帮助模型更好地理解用户的意图,从而更好地回答问题或执行任务。小样本提示模板是指使用一组少量的示例来指导模型处理新的输入。这些示例可以用来训练模型,以便模型可以更好地理解和回答类似的问题。
例子:
Q: 什么是蝙蝠侠?
A: 蝙蝠侠是一个虚构的漫画人物。
Q: 什么是torsalplexity?
A: 未知。
Q: 什么是语言模型?
A:
告诉模型根据,Q是问题,A是答案,按这种格式进行问答交互。
下面讲解的就是Lanchain针对在提示词中插入少量交互样本提供的工具类。
使用示例集
创建示例集
下面定义一个examples示例数组,里面包含一组问答样例。
from langchain.prompts.few_shot import FewShotPromptTemplate
from langchain.prompts.prompt import PromptTemplate
examples = [
{
"question": "谁的寿命更长,穆罕默德·阿里还是艾伦·图灵?",
"answer":
"""
这里需要跟进问题吗:是的。
跟进:穆罕默德·阿里去世时多大?
中间答案:穆罕默德·阿里去世时74岁。
跟进:艾伦·图灵去世时多大?
中间答案:艾伦·图灵去世时41岁。
所以最终答案是:穆罕默德·阿里
"""
},
{
"question": "craigslist的创始人是什么时候出生的?",
"answer":
"""
这里需要跟进问题吗:是的。
跟进:craigslist的创始人是谁?
中间答案:craigslist由Craig Newmark创立。
跟进:Craig Newmark是什么时候出生的?
中间答案:Craig Newmark于1952年12月6日出生。
所以最终答案是:1952年12月6日
"""
},
{
"question": "乔治·华盛顿的祖父母中的母亲是谁?",
"answer":
"""
这里需要跟进问题吗:是的。
跟进:乔治·华盛顿的母亲是谁?
中间答案:乔治·华盛顿的母亲是Mary Ball Washington。
跟进:Mary Ball Washington的父亲是谁?
中间答案:Mary Ball Washington的父亲是Joseph Ball。
所以最终答案是:Joseph Ball
"""
},
{
"question": "《大白鲨》和《皇家赌场》的导演都来自同一个国家吗?",
"answer":
"""
这里需要跟进问题吗:是的。
跟进:《大白鲨》的导演是谁?
中间答案:《大白鲨》的导演是Steven Spielberg。
跟进:Steven Spielberg来自哪里?
中间答案:美国。
跟进:《皇家赌场》的导演是谁?
中间答案:《皇家赌场》的导演是Martin Campbell。
跟进:Martin Campbell来自哪里?
中间答案:新西兰。
所以最终答案是:不是
"""
}
]
创建小样本示例的格式化程序
通过PromptTemplate
对象,简单的在提示词模板中插入样例。
example_prompt = PromptTemplate(input_variables=["question", "answer"], template="问题:{question}\\n{answer}")
# 提取examples示例集合的一个示例的内容,用于格式化模板内容
print(example_prompt.format(**examples[0]))
返回:
问题:谁的寿命更长,穆罕默德·阿里还是艾伦·图灵?
这里需要跟进问题吗:是的。
跟进:穆罕默德·阿里去世时多大?
中间答案:穆罕默德·阿里去世时74岁。
跟进:艾伦·图灵去世时多大?
中间答案:艾伦·图灵去世时41岁。
所以最终答案是:穆罕默德·阿里
将示例和格式化程序提供给FewShotPromptTemplate
通过FewShotPromptTemplate
对象,批量插入示例内容。
# 接收examples示例数组参数,通过example_prompt提示词模板批量渲染示例内容
# suffix和input_variables参数用于在提示词模板最后追加内容, input_variables用于定义suffix中包含的模板参数
prompt = FewShotPromptTemplate(
examples=examples,
example_prompt=example_prompt,
suffix="问题:{input}",
input_variables=["input"]
)
print(prompt.format(input="乔治·华盛顿的父亲是谁?"))
返回:
问题:谁的寿命更长,穆罕默德·阿里还是艾伦·图灵?
这里需要跟进问题吗:是的。
跟进:穆罕默德·阿里去世时多大?
中间答案:穆罕默德·阿里去世时74岁。
跟进:艾伦·图灵去世时多大?
中间答案:艾伦·图灵去世时41岁。
所以最终答案是:穆罕默德·阿里
问题:craigslist的创始人是什么时候出生的?
这里需要跟进问题吗:是的。
跟进:craigslist的创始人是谁?
中间答案:craigslist由Craig Newmark创立。
跟进:Craig Newmark是什么时候出生的?
中间答案:Craig Newmark于1952年12月6日出生。
所以最终答案是:1952年12月6日
问题:乔治·华盛顿的祖父母中的母亲是谁?
这里需要跟进问题吗:是的。
跟进:乔治·华盛顿的母亲是谁?
中间答案:乔治·华盛顿的母亲是Mary Ball Washington。
跟进:Mary Ball Washington的父亲是谁?
中间答案:Mary Ball Washington的父亲是Joseph Ball。
所以最终答案是:Joseph Ball
问题:《大白鲨》和《皇家赌场》的导演都来自同一个国家吗?
这里需要跟进问题吗:是的。
跟进:《大白鲨》的导演是谁?
中间答案:《大白鲨》的导演是Steven Spielberg。
跟进:Steven Spielberg来自哪里?
中间答案:美国。
跟进:《皇家赌场》的导演是谁?
中间答案:《皇家赌场》的导演是Martin Campbell。
跟进:Martin Campbell来自哪里?
中间答案:新西兰。
所以最终答案是:不是
问题:乔治·华盛顿的父亲是谁?
使用示例选择器
将示例提供给ExampleSelector
这里重用前一部分中的示例集和提示词模板(prompt template)。但是,不会将示例直接提供给FewShotPromptTemplate
对象,把全部示例插入到提示词中,而是将它们提供给一个ExampleSelector
对象,插入部分示例。
这里我们使用SemanticSimilarityExampleSelector
类。该类根据与输入的相似性选择小样本示例。它使用嵌入模型计算输入和小样本示例之间的相似性,然后使用向量数据库执行相似搜索,获取跟输入相似的示例。
- 提示:这里涉及向量计算、向量数据库,在AI领域这两个主要用于数据相似度搜索,例如:查询相似文章内容、相似的图片、视频等等,这里先简单了解下就行。
from langchain.prompts.example_selector import SemanticSimilarityExampleSelector
from langchain_community.vectorstores import Chroma
from langchain_openai import OpenAIEmbeddings
example_selector = SemanticSimilarityExampleSelector.from_examples(
# 这是可供选择的示例列表。
examples,
# 这是用于生成嵌入的嵌入类,该嵌入用于衡量语义相似性。
OpenAIEmbeddings(),
# 这是用于存储嵌入和执行相似性搜索的VectorStore类。
Chroma,
# 这是要生成的示例数。
k=1
)
# 选择与输入最相似的示例。
question = "乔治·华盛顿的父亲是谁?"
selected_examples = example_selector.select_examples({"question": question})
print(f"最相似的示例:{question}")
for example in selected_examples:
print("\\n")
for k, v in example.items():
print(f"{k}:{v}")
这里匹配了跟问题相似的例子,下面是返回:
使用本地API直接运行Chroma。
使用DuckDB内存中的数据库。数据将是短暂的。
最相似的示例:乔治·华盛顿的父亲是谁?
question:乔治·华盛顿的祖父母中的母亲是谁?
answer:
这里需要跟进问题吗:是的。
跟进:乔治·华盛顿的母亲是谁?
中间答案:乔治·华盛顿的母亲是Mary Ball Washington。
跟进:Mary Ball Washington的父亲是谁?
中间答案:Mary Ball Washington的父亲是Joseph Ball。
所以最终答案是:Joseph Ball
将示例选择器提供给FewShotPromptTemplate
最后,创建一个FewShotPromptTemplate
对象。根据前面的example_selector示例选择器,选择一个跟问题相似的例子。
prompt = FewShotPromptTemplate(
example_selector=example_selector,
example_prompt=example_prompt,
suffix="问题:{input}",
input_variables=["input"]
)
print(prompt.format(input="乔治·华盛顿的父亲是谁?"))
返回:
问题:乔治·华盛顿的祖父母中的母亲是谁?
这里需要跟进问题吗:是的。
跟进:乔治·华盛顿的母亲是谁?
中间答案:乔治·华盛顿的母亲是Mary Ball Washington。
跟进:Mary Ball Washington的父亲是谁?
中间答案:Mary Ball Washington的父亲是Joseph Ball。
所以最终答案是:Joseph Ball
问题:乔治·华盛顿的父亲是谁?
三、LangChain表达式语句
1.LangChain Expression Language (LCEL)介绍
LangChain Expression Language (简称LCEL)是一种声明式的AI工作流编排方式,可以轻松地将多个处理步骤,像链条一样组合在一起,所以LCEL定义的工作流,在LangChain中称为链条(Chain)。从最简单的“提示(prompt)+LLM”链到最复杂的链条(例如由数百个步骤组成的链(Chain))
LangChain Expression Language特性如下:
- 流式处理支持:使用LCEL构建链条时,为了尽快获取LLM模型返回的内容,LCEL也支持流式处理(Streaming),实现类似ChatGPT一个个字符逐步返回内容的效果。
- 异步支持:使用LCEL构建的任何链条都可以使用同步API和使用异步API。使用异步API目的是为了改善性能,支持更高的并发请求。
- 支持并行执行:每当您的LCEL链条具有可以并行执行的步骤时(例如,如果您从多个检索器获取文档),我们都会自动执行它,用于同步和异步接口,以实现尽可能小的延迟。
- 重试和回退:为LCEL链条的任何部分配置重试和回退。
- 支持访问中间结果:对于复杂的工作流链条,在debug的时候,通常希望可以浏览不同任务步骤之间的中间结果。
- 输入和输出模式:输入和输出模式根据链条结构推断,为每个LCEL链条提供了Pydantic和JSONSchema模式。这可用于验证输入和输出,是LangServe的一个组成部分。
- 支持跟LangSmith监控服务集成:随着链条变得越来越复杂,准确了解每个步骤发生了什么变得越来越重要。使用LCEL,所有步骤都会自动记录到LangSmith,以实现最大的可观察性和调试性。
- 支持LangServe部署:使用LCEL创建的任何链条都可以轻松使用LangServe进行部署。
2.LCEL 入门
LCEL(LangChain Expression Language) 是一种强大的工作流编排工具,可以从基本组件构建复杂任务链条(chain),并支持诸如流式处理、并行处理和日志记录等开箱即用的功能。
基本示例:提示(prompt) + 模型 + 输出解析器
在这个示例中,我们将展示如何通过LCEL(LangChain Expression Language) ,将提示模板(prompt)、模型和输出解析器三个组件链接在一起形成一个完整的工作流,用于实现”讲笑话”的任务。通过代码演示了如何创建链条(chain)、使用管道符号 |
连接不同组件,并介绍了每个组件的作用以及输出结果。
首先,让我们看一下如何将提示模板(prompt template)和模型连接在一起,完成生成一个关于特定主题的笑话:
安装依赖库
%pip install --upgrade --quiet langchain-core langchain-community langchain-openai
from langchain_core.output_parsers import StrOutputParser
from langchain_core.prompts import ChatPromptTemplate
from langchain_openai import ChatOpenAI
# 定义一个提示模板,包含topic模板参数,用于设置"笑话"的主题
prompt = ChatPromptTemplate.from_template("告诉我一个关于{topic}的小笑话")
# 定义对话模型实例,选择gpt-4模型
model = ChatOpenAI(model="gpt-4")
# 定义字符串输出解析器,这个解析器只是简单的将模型返回的内容转成字符串
output_parser = StrOutputParser()
# 注意这里,这里通过LCEL表达式,定义一个工作流,生成一个chain(任务链条)
chain = prompt | model | output_parser
# 通过prompt模板参数调用工作流,又或者叫chain
chain.invoke({"topic": "冰淇淋"})
返回结果
“为什么派对从不邀请冰淇淋?”因为东西一热,就会滴水!”
在这段代码中,我们使用 LCEL 将不同组件连接成一个链条(chain):
chain = prompt | model | output_parser
这里的 |
符号类似于 unix 管道操作符,它将不同组件连接在一起,将一个组件的输出作为下一个组件的输入。
在这个链条中,用户输入被传递到提示模板,然后提示模板的输出被传递到模型,最后模型的输出被传递到输出解析器。让我们分别看一下每个组件,以更好地理解发生了什么。
1. 提示(prompt)
prompt
是一个 BasePromptTemplate
,它接受一个模板变量字典并生成一个 PromptValue
。PromptValue
是一个包装完成提示的对象,可以传递给 LLM
(以字符串作为输入)或 ChatModel
(以消息序列作为输入)。它可以与任何一种语言模型类型配合使用,因为它定义了生成 BaseMessage
和生成字符串的逻辑。
# 下面我们手动调用prompt传入模板参数,格式化提示词模板(prompt template)
prompt_value = prompt.invoke({"topic": "冰淇淋"})
prompt_value
输出结果
ChatPromptValue(messages=[HumanMessage(content='告诉我一个关于冰淇淋的小笑话')])
下面将prompt格式结果转成对话模型(chat models)使用的消息格式
prompt_value.to_messages()
输出结果
[HumanMessage(content='告诉我一个关于冰淇淋的小笑话')]
也可以直接转成字符串
prompt_value.to_string()
输出结果
'Human: 告诉我一个关于冰淇淋的小笑话。'
2. 模型(model)
然后将 PromptValue
传递给 model
。在本例中,我们的 model
是一个 ChatModel
,这意味着它将输出一个 BaseMessage
。
下面尝试直接调用model
message = model.invoke(prompt_value)
message
返回
AIMessage(content="为什么冰淇淋从不被邀请参加派对?\n\n因为它们总是在事情变热时滴!")
如果我们的 model
定义的是一个 LLM
类型,它将输出一个字符串。
from langchain_openai.llms import OpenAI
# 定义一个OpenAI模型实例,使用gpt-3.5-turbo-instruct模型
llm = OpenAI(model="gpt-3.5-turbo-instruct")
# 通过前面定义的提示词(prompt)调用模型
llm.invoke(prompt_value)
模型返回结果
'\n\n机器人: 为什么冰淇淋车坏了?因为它发生了熔毁!'
3. 输出解析器
最后,将我们的 model
输出传递给 output_parser
,它是一个 BaseOutputParser
,意味着它接受一个字符串或 BaseMessage
作为输入。StrOutputParser
具体地将任何输入简单转换为一个字符串。
output_parser.invoke(message)
为什么冰淇淋从不被邀请参加派对?\n\n因为它们总是在事情变热时滴!
4. 整个流程
执行过程如下:
- 调用
chain.invoke({"topic": "冰淇淋"})
,相当于启动我们定义的工作流,传入参数{"topic": "冰淇淋"}
,要求生成一个主题关于”冰淇淋”的笑话 - 将调用参数
{"topic": "冰淇淋"}
传给chain的第一个组件prompt,prompt通过参数格式化提示模板(prompt template),得到告诉我一个关于冰淇淋的小笑话
提示词(prompt) - 将
告诉我一个关于冰淇淋的小笑话
提示词(prompt)传给model
(gpt4模型) - 将
model
返回的结果,传给output_parser
输出解析器, 输出解析器格式化模型结果,并返回最终的内容。
如果你对任何组件的输出感兴趣,可以随时测试链条的较小版本,如 prompt
或 prompt | model
,以查看中间结果:
input = {"topic": "冰淇淋"}
# 直接调试prompt组件
prompt.invoke(input)
# > ChatPromptValue(messages=[HumanMessage(content='tell me a short joke about ice cream')])
# 定义一个由prompt和model组成的任务链条,然后通过invoke进行调用测试
(prompt | model).invoke(input)
# > AIMessage(content="为什么冰淇淋要去心理治疗?\n因为它有太多的配料,找不到锥-控自己!")
RAG 搜索示例
接下来,讲解一个稍微复杂点的LCEL例子,我们将运行一个检索增强生成链条(chain)的示例,以在回答问题时添加一些背景信息。
# 需要安装:
# pip install langchain docarray tiktoken
from langchain_community.vectorstores import DocArrayInMemorySearch
from langchain_core.output_parsers import StrOutputParser
from langchain_core.prompts import ChatPromptTemplate
from langchain_core.runnables import RunnableParallel, RunnablePassthrough
from langchain_openai.chat_models import ChatOpenAI
from langchain_openai.embeddings import OpenAIEmbeddings
# 定义一个向量存储用于测试相似度搜索,后续的章节会单独讲解向量存储,这里简单了解即可
# 下面导入两条文本信息作为测试数据
vectorstore = DocArrayInMemorySearch.from_texts(
["harrison worked at kensho", "bears like to eat honey"],
embedding=OpenAIEmbeddings(),
)
# 通过向量存储获取检索对象,用于支持根据问题查询相似的文本数据,作为背景信息
retriever = vectorstore.as_retriever()
# 定义prompt模板
template = """仅基于以下背景回答问题:
{context}
问题:{question}
"""
prompt = ChatPromptTemplate.from_template(template)
# 定义对话模型
model = ChatOpenAI()
# 定义输出解析器
output_parser = StrOutputParser()
# 自定义一个任务步骤,用于通过`retriever`查询向量数据库中的相似数据,然后赋值给context字段
setup_and_retrieval = RunnableParallel(
{"context": retriever, "question": RunnablePassthrough()}
)
# 通过lcel表达式,定义工作流,生成一个链条(chain)
chain = setup_and_retrieval | prompt | model | output_parser
# 调用工作流
chain.invoke("harrison 在哪工作?")
在这种情况下,组成的链条是:
chain = setup_and_retrieval | prompt | model | output_parser
简单解释一下,上面的提示模板接受 context
和 question
作为要替换在提示(prompt)中的值。在构建提示模板之前,我们希望检索相关文档以用作上下文的一部分。
作为测试,我们使用DocArrayInMemorySearch
模拟一个基于内存的向量数据库,定义了一个检索器,它可以根据查询检索相似文档。这也是一个可链式连接的可运行组件,但你也可以尝试单独运行它:
retriever.invoke("harrison 在哪工作?")
然后,我们使用 RunnableParallel
准备提示(prompt)所需的输入,使用检索器进行文档搜索,并使用 RunnablePassthrough
传递用户的问题:
setup_and_retrieval = RunnableParallel(
{"context": retriever, "question": RunnablePassthrough()}
)
综上所述,完整的链条是:
setup_and_retrieval = RunnableParallel(
{"context": retriever, "question": RunnablePassthrough()}
)
chain = setup_and_retrieval | prompt | model | output_parser
流程为:
- 首先,创建一个
RunnableParallel
对象,其中包含两个条目。第一个条目context
将包括检索器提取的文档结果。第二个条目question
将包含用户原始问题。为传递问题,我们使用RunnablePassthrough
复制这个条目。 - 将上一步的字典传递给
prompt
组件。它接受用户输入(即question
)以及检索到的文档(即context
),构建一个提示并输出一个PromptValue
。 model
组件接受生成的提示,并传递给 OpenAI 的 LLM 模型进行评估。模型生成的输出是一个ChatMessage
对象。- 最后,
output_parser
组件接受一个ChatMessage
,将其转换为 Python 字符串,并从invoke
方法返回。
3.为什么使用LCEL(LangChain Expression Language)
1. LCEL简介
LCEL(LangChain Expression Language)是一个简单易用的框架,用于构建复杂链条。它提供了统一的接口和组合原语,使得构建链条变得更加容易。每个LCEL对象都实现了Runnable
接口,定义了一组常用的调用方法(如invoke
、batch
、stream
、ainvoke
等)。因此,LCEL对象的链条也可以自动支持这些调用方法,使得每个LCEL对象的链条本身也是一个LCEL对象。
2. 调用(Invoke)
2.1. 没有使用LCEL
在没有使用LCEL的情况下,可以通过以下代码片段传入一个主题字符串并获取一个笑话字符串。
from typing import List
import openai
prompt_template = "Tell me a short joke about {topic}"
client = openai.OpenAI()
def call_chat_model(messages: List[dict]) -> str:
response = client.chat.completions.create(
model="gpt-3.5-turbo",
messages=messages,
)
return response.choices[0].message.content
def invoke_chain(topic: str) -> str:
prompt_value = prompt_template.format(topic=topic)
messages = [{"role": "user", "content": prompt_value}]
return call_chat_model(messages)
invoke_chain("ice cream")
2.2. 使用LCEL
相比之下,使用LCEL可以更加简洁地实现相同的功能,下面的代码片段展示了如何使用LCEL对象轻松构建链条。
from langchain_openai import ChatOpenAI
from langchain_core.prompts import ChatPromptTemplate
from langchain_core.output_parsers import StrOutputParser
from langchain_core.runnables import RunnablePassthrough
prompt = ChatPromptTemplate.from_template(
"Tell me a short joke about {topic}"
)
output_parser = StrOutputParser()
model = ChatOpenAI(model="gpt-3.5-turbo")
chain = (
{"topic": RunnablePassthrough()}
| prompt
| model
| output_parser
)
chain.invoke("ice cream")
3. 流式处理(Stream)
3.1. 没有使用LCEL
下面的代码片段演示了在不使用LCEL的情况下如何流式处理结果。
from typing import Iterator
def stream_chat_model(messages: List[dict]) -> Iterator[str]:
stream = client.chat.completions.create(
model="gpt-3.5-turbo",
messages=messages,
stream=True,
)
for response in stream:
content = response.choices[0].delta.content
if content is not None:
yield content
def stream_chain(topic: str) -> Iterator[str]:
prompt_value = prompt.format(topic=topic)
stream = stream_chat_model([{"role": "user", "content": prompt_value}])
for chunk in stream:
print(chunk, end="", flush=True)
stream_chain("ice cream")
3.2. 使用LCEL
使用LCEL来流式处理结果更加简便,下面的代码片段展示了如何使用LCEL流式处理结果。
for chunk in chain.stream("ice cream"):
print(chunk, end="", flush=True)
4. 批处理(Batch)
4.1. 没有使用LCEL
下面的代码片段展示了如何在不使用LCEL的情况下并行处理一批输入。
from concurrent.futures import ThreadPoolExecutor
def batch_chain(topics: list) -> list:
with ThreadPoolExecutor(max_workers=5) as executor:
return list(executor.map(invoke_chain, topics))
batch_chain(["ice cream", "spaghetti", "dumplings"])
4.2. 使用LCEL
使用LCEL执行批处理操作非常简便,下面的代码片段展示了如何使用LCEL执行批处理操作。
chain.batch(["ice cream", "spaghetti", "dumplings"])
`## 四、语言模型
### 1.LangChain LLM基础模型
#### 1. LLMs 介绍
大语言模型(LLMs)是LangChain的核心组件,LangChain本身不提供大语言模型能力,LangChain封装了各种常见的大语言模型,提供一套操作大语言模型的标准接口,方便开发者。
目前LangChain封装不少大语言模型,也支持自定义模型,开发者可以自己封装新的模型接口。
- 提示:目前开源大模型和商业大模型有很多,考虑到成本&模型能力的差异,大家会有不同的选择,同时也可能会经常换模型,但是换模型又不想改动太多业务代码,这个时候LangCain的价值就体现出来了。
#### 2. 设置
##### 2.1 安装
要在LangChain中使用OpenAI LLM,用户需要通过运行以下命令来安装OpenAI Python包:
```bash
pip install openai
2.2 API密钥设置
访问OpenAI API需要一个API密钥,可以通过在OpenAI平台上创建账户来获取。一旦获得API密钥,可以通过以下命令将其设置为环境变量:
export OPENAI_API_KEY="your-api-key"
另外,如果不希望设置环境变量,也可以在初始化OpenAI LLM类时直接传递API密钥,示例如下:
from langchain_openai import OpenAI
llm = OpenAI(openai_api_key="your-api-key")
3. LCEL 实现
在LangChain中,LLMs实现了LangChain表达式语言(LCEL)的可运行接口。这意味着它们支持各种函数调用,如invoke
、stream
、batch
等。
LLMs接受字符串输入或可以强制转换为字符串提示的对象,包括List[BaseMessage]
和PromptValue
。
4. LLM使用示例
4.1 调用
要使用特定提示词(prompt)调用LLM,可以使用invoke
函数,示例如下:
llm.invoke("在这里为LLM提供提示(prompt)")
4.2 流式处理
从LLMs中实时获取并处理文本输出的方法称为流式处理。以下是使用LLM进行流式处理的示例:
for chunk in llm.stream("在这里为LLM流处理提供提示(prompt)"):
print(chunk, end="", flush=True)
4.3 批处理
批处理能够实现对LLMs的多个输入进行并行处理。您可以像这样处理一批提示(prompt):
llm.batch(["prompt 1", "prompt 2", "prompt 3"])
4.4 异步操作
为了提高效率,可以使用异步操作与LLMs一起工作。异步操作如ainvoke
、astream
、abatch
和astream_log
可以实现异步执行。以下是使用LLM进行异步流处理的示例:
async for chunk in llm.astream("在这里提供异步LLM流处理的提示(prompt)"):
print(chunk, end="", flush=True)
这些示例展示了在LangChain框架中利用LLMs进行自然语言处理任务的多样化方式。
2.LangChain 聊天模型
聊天模型是语言模型的一种变体。虽然聊天模型在底层使用的也是语言模型(LLM),但它们所公开的接口有些不同。它们不是通过 “输入文本,输出文本” API 公开接口,而是通过 “聊天消息” 作为输入和输出的接口,聊天模型的整个交互过程类似互相发送聊天消息的过程。
聊天模型入门
本章节基于OpenAI模型进行讲解。
配置
langChain默认没有安装模型依赖,这里首先安装 OpenAI 的 Python 包:
pip install langchain-openai
访问 API 需要一个 API 密钥,可以通过环境变量配置,下面是linux环境变量设置方式
export OPENAI_API_KEY="..."
如果您不想设置环境变量,可以通过在初始化 OpenAI LLM 类时直接通过 openai_api_key
命名参数传递密钥:
from langchain_openai import ChatOpenAI
chat = ChatOpenAI(openai_api_key="...")
否则,可以不带任何参数初始化:
from langchain_openai import ChatOpenAI
chat = ChatOpenAI()
消息
聊天模型接口基于消息而不是原始文本。LangChain 当前支持的消息类型包括 AIMessage
、HumanMessage
、SystemMessage
、FunctionMessage
和 ChatMessage
,其中 ChatMessage
需要一个任意的角色参数。大多数情况下,你只会处理 HumanMessage
、AIMessage
和 SystemMessage
。
LCEL
聊天模型(Chat model)实现了Runnable interface
接口,支持支持invoke
, ainvoke
, stream
, astream
, batch
, abatch
, astream_log
等方法调用,因此聊天模型(Chat model)也可以用于LangChain Expression Language (LCEL)表达式。
聊天模型接受List[BaseMessage]
作为输入,或接受可以强制为消息的对象,包括str
(转换为HumanMessage
)和PromptValue
。
from langchain_core.messages import HumanMessage, SystemMessage
# 定义消息数组,这里包含两条消息,一个system消息告诉ai扮演什么角色,一个humen消息,代表用户的问题。
messages = [
SystemMessage(content="你是个乐于助人的助手"),
HumanMessage(content="模型正则化的目的是什么?"),
]
通过消息数组调用模型
chat.invoke(messages)
返回示例
AIMessage(content="AI返回内容...详情忽略..")
流式输出AI响应结果
for chunk in chat.stream(messages):
print(chunk.content, end="", flush=True)
3.LangChain 自定义模型
目前AI模型领域百家争鸣,LangChain官方也没有对接好所有模型,有时候你需要自定义模型,接入LangChain框架。
本章将介绍如何创建自定义 LLM 包装器,方便你使用自己的模型或者LangChain官方不支持的模型。
在LangChain中,如果想要使用自己的LLM或与LangChain支持的不同包装器不同的包装器,可以创建一个自定义LLM包装器。自定义LLM只需要实现两个必需的方法:
- 一个
_call
方法,接受一个字符串作为输入,一些可选的停用词,并返回一个字符串,在_call
方法实现模型调用。 - 一个
_llm_type
属性,返回一个代表模型名称的字符串,仅用于日志记录目的。
除了必需的方法之外,自定义LLM还可以实现一个可选的方法:
- 一个
_identifying_params
属性,用于帮助打印该类。应返回一个字典。
实现一个简单的自定义LLM
让我们实现一个非常简单的自定义LLM,它只返回输入的前n个字符。
from typing import Any, List, Mapping, Optional
from langchain_core.callbacks.manager import CallbackManagerForLLMRun
from langchain_core.language_models.llms import LLM
# 集成`LLM`
class CustomLLM(LLM):
n: int
@property
def _llm_type(self) -> str:
# 返回我们自定义的模型标记
return "custom"
def _call(
self,
prompt: str,
stop: Optional[List[str]] = None,
run_manager: Optional[CallbackManagerForLLMRun] = None,
**kwargs: Any,
) -> str:
# 在这里实现模型api调用
if stop is not None:
raise ValueError("stop kwargs are not permitted.")
return prompt[: self.n]
@property
def _identifying_params(self) -> Mapping[str, Any]:
# 可选debug信息
"""Get the identifying parameters."""
return {"n": self.n}
现在我们可以像使用其他LLM一样使用这个自定义LLM。
使用自定义LLM
我们可以实例化并使用自定义LLM对象,演示如何调用自定义LLM以及如何自定义打印输出。
llm = CustomLLM(n=10)
llm.invoke("This is a foobar thing")
'This is a '
我们还可以打印LLM并查看其自定义打印输出。
print(llm)
CustomLLM
Params: {'n': 10}
自定义Chat model(聊天模型)
这里讲解如何自定义LangChain的Chat model(聊天模型)。
消息输入和输出
在聊天模型中,消息是输入和输出的重点。消息(message)是指用户输入的内容以及模型生成的回复。
消息
聊天模型将消息作为输入,然后生成一个或多个消息作为输出。在LangChain中,有几种内置的消息类型,包括:
SystemMessage
:用于初始化AI行为,通常作为一系列输入消息中的第一个消息。HumanMessage
:表示用户与聊天模型进行交互的消息。AIMessage
:表示来自聊天模型的消息,可以是文本或请求调用工具。FunctionMessage
/ToolMessage
:用于将工具调用的结果传递回模型。
这些消息类型的使用可以根据具体需求进行扩展和定制,例如按照OpenAI的function
和tool
参数进行调整。
from langchain_core.messages import (
AIMessage,
BaseMessage,
FunctionMessage,
HumanMessage,
SystemMessage,
)
流式变体
所有聊天消息都有一个流式变体,名称中包含Chunk
。
from langchain_core.messages import (
AIMessageChunk,
FunctionMessageChunk,
HumanMessageChunk,
SystemMessageChunk,
ToolMessageChunk,
)
这些Chunk
在从聊天模型流式输出时使用,并且它们都定义了一个可累加的属性!
例子
AIMessageChunk(content="你好") + AIMessageChunk(content=" 世界!")
返回结果
AIMessageChunk(content='你好 世界!')
简单聊天模型
从SimpleChatModel
继承可以快速实现一个简单的聊天模型(chat model)
虽然它不能实现聊天模型可能需要的所有功能,但是它快速实现,并且如果需要更多功能,可以过渡到下面介绍的BaseChatModel
。
继承SimpleChatModel
需要实现以下接口:
_call
方法 - 实现外部模型API调用。
此外,还可以指定以下内容:
_identifying_params
属性 - 用于记录模型参数化信息。
可选的:
_stream
方法 - 用于实现流式输出。
基本聊天模型
从BaseChatModel
继承,实现_generate
方法以及_llm_type
属性。可选择实现_stream
、_agenerate
、_astream
以及_identifying_params
。
自定义聊天模型的例子
在这一部分,我们将展示一个名为CustomChatModelAdvanced
的自定义聊天模型的代码实现,包括生成聊天结果、流式输出、异步流实现等。
from typing import Any, AsyncIterator, Dict, Iterator, List, Optional
from langchain_core.callbacks import (
AsyncCallbackManagerForLLMRun,
CallbackManagerForLLMRun,
)
from langchain_core.language_models import BaseChatModel, SimpleChatModel
from langchain_core.messages import AIMessageChunk, BaseMessage, HumanMessage
from langchain_core.outputs import ChatGeneration, ChatGenerationChunk, ChatResult
from langchain_core.runnables import run_in_executor
class CustomChatModelAdvanced(BaseChatModel):
"""实现一个自定义聊天模型,返回最后一条消息的前`n`个字符
"""
n: int
"""自定义模型参数"""
def _generate(
self,
messages: List[BaseMessage],
stop: Optional[List[str]] = None,
run_manager: Optional[CallbackManagerForLLMRun] = None,
**kwargs: Any,
) -> ChatResult:
"""这里实现模型调用逻辑,实际情况一般是调用第三方模型的api,然后把api返回的结果封装成langchain可以识别的格式
关键参数说明:
messages: 提示词(prompt)组成的消息列表
"""
last_message = messages[-1]
tokens = last_message.content[: self.n]
# 提取最后一条消息的前面`n`个字符,模拟模型生成结果,把结果封装到AIMessage中
message = AIMessage(content=tokens)
# 进一步包装模型结果
generation = ChatGeneration(message=message)
# 最后使用ChatResult包装模型输出结果
return ChatResult(generations=[generation])
def _stream(
self,
messages: List[BaseMessage],
stop: Optional[List[str]] = None,
run_manager: Optional[CallbackManagerForLLMRun] = None,
**kwargs: Any,
) -> Iterator[ChatGenerationChunk]:
"""模型流式输出实现,跟_generate方法类似,区别是要处理流式输出
"""
last_message = messages[-1]
tokens = last_message.content[: self.n]
for token in tokens:
# 使用chunk版本的消息对象封装model返回结果,分段返回模型结果
chunk = ChatGenerationChunk(message=AIMessageChunk(content=token))
if run_manager:
run_manager.on_llm_new_token(token, chunk=chunk)
yield chunk
async def _astream(
self,
messages: List[BaseMessage],
stop: Optional[List[str]] = None,
run_manager: Optional[AsyncCallbackManagerForLLMRun] = None,
**kwargs: Any,
) -> AsyncIterator[ChatGenerationChunk]:
"""异步处理版本的`stream`方法实现
"""
result = await run_in_executor(
None,
self._stream,
messages,
stop=stop,
run_manager=run_manager.get_sync() if run_manager else None,
**kwargs,
)
for chunk in result:
yield chunk
@property
def _llm_type(self) -> str:
"""返回自定义模型的标记"""
return "echoing-chat-model-advanced"
@property
def _identifying_params(self) -> Dict[str, Any]:
"""返回自定义的debug信息"""
return {"n": self.n}
测试自定义聊天模型
让我们来测试聊天模型,包括使用invoke
、batch
、stream
方法以及异步流实现。
model = CustomChatModelAdvanced(n=3)
model.invoke([HumanMessage(content="hello!")])
# 输出: AIMessage(content='Meo')
model.invoke("hello")
# 输出: AIMessage(content='hel')
model.batch(["hello", "goodbye"])
# 输出: [AIMessage(content='hel'), AIMessage(content='goo')]
for chunk in model.stream("cat"):
print(chunk.content, end="|")
# 输出: c|a|t|
async for chunk in model.astream("cat"):
print(chunk.content, end="|")
# 输出: c|a|t|
async for event in model.astream_events("cat", version="v1"):
print(event)
4.LangChain 输出解析器
LLM语言模型输出内容是文本格式,但是开发AI应用的时候,我们希望能拿到的是格式化的内容,例如结果转成目标对象,数组等,方便程序处理。这就需要LangChain提供的输出解析器(Output parser)格式化模型返回的内容。
输出解析器作用是用于是格式化语言模型返回的结果。一个输出解析器必须实现两种必要的方法:
- “get_format_instructions”: 返回一个字符串,其中包含要求语言模型应该返回什么格式内容的提示词。
- “parse”: 将模型返回的内容,解析为目标格式。
下面我们看看LangChain内置的输出解析器。
Pydantic 解析器
下面是LangChain封装的核心输出解析器PydanticOutputParser,
该解析器是基于python的pydantic库,用于实现将模型的输出结果转成Python对象。
# 导入必要的模块
from langchain.output_parsers import PydanticOutputParser
from langchain.prompts import PromptTemplate
from langchain_core.pydantic_v1 import BaseModel, Field, validator
from langchain_openai import OpenAI
# 初始化语言模型
model = OpenAI(model_name="gpt-3.5-turbo-instruct", temperature=0.0)
# 继承`BaseModel`定义需要的数据结构
class Joke(BaseModel):
# 通过Field告诉模型,使用什么信息填充当前字段
setup: str = Field(description="question to set up a joke")
punchline: str = Field(description="answer to resolve the joke")
# 使用Pydantic进行自定义验证逻辑
@validator("setup")
def question_ends_with_question_mark(cls, field):
if field[-1] != "?":
raise ValueError("Badly formed question!")
return field
# 定义解析器,设置我们希望返回的python对象
parser = PydanticOutputParser(pydantic_object=Joke)
# 将输出解析器的格式指令注入到提示词模板(prompt template)中, 通过get_format_instructions函数获取格式指令
prompt = PromptTemplate(
# 观察提示词模板(prompt template),里面包含format_instructions和query两个模板参数,format_instructions参数用于注入输出解析器的格式指令,query参数用于注入用户的问题,
template="Answer the user query.\n{format_instructions}\n{query}\n",
input_variables=["query"],
partial_variables={"format_instructions": parser.get_format_instructions()},
)
# 使用lcel表达式,定义一个工作流
prompt_and_model = prompt | model
# 调用前面定义的工作流
output = prompt_and_model.invoke({"query": "Tell me a joke."})
parser.invoke(output)
返回结果示例
Joke(setup='Why did the chicken cross the road?', punchline='To get to the other side!')
LCEL接口
Runnable接口
输出解析器实现了Runnable接口,是LangChain表达语言(LCEL)的基本构建块之一。它支持invoke
、ainvoke
、stream
、astream
、batch
、abatch
、astream_log
等调用方法。
输出解析器在LCEL中的应用
输出解析器可以接受字符串或BaseMessage
作为输入,并返回任意类型的结构化数据。我们可以通过将解析器添加到Runnable序列中来构建并调用解析器链。
# 将输出解析器拼接到Lcel表达式中
chain = prompt | model | parser
chain.invoke({"query": "Tell me a joke."})
返回
Joke(setup='Why did the chicken cross the road?', punchline='To get to the other side!')
一些解析器可以流式传输部分解析对象,例如SimpleJsonOutputParser
,而有些则不支持。最终输出取决于解析器是否能够构建部分解析对象。
from langchain.output_parsers.json import SimpleJsonOutputParser
json_prompt = PromptTemplate.from_template(
"Return a JSON object with an `answer` key that answers the following question: {question}"
)
json_parser = SimpleJsonOutputParser()
json_chain = json_prompt | model | json_parser
list(json_chain.stream({"question": "Who invented the microscope?"}))
[{},
{'answer': ''},
{'answer': 'Ant'},
{'answer': 'Anton'},
{'answer': 'Antonie'},
{'answer': 'Antonie van'},
{'answer': 'Antonie van Lee'},
{'answer': 'Antonie van Leeu'},
{'answer': 'Antonie van Leeuwen'},
{'answer': 'Antonie van Leeuwenho'},
{'answer': 'Antonie van Leeuwenhoek'}]
在LCEL中,我们可以通过组合不同的解析器构建复杂的数据处理流程,以满足各种需求。``