简介
LangChain是一个开源的python AI应用开发框架,它提供了用于构建AI大模型的所需的模块和工具。通过LangChain,开发者可以轻松与大模型(LLM)集成,完成文本问答、翻译、对话等任务。LangChain降低了AI开发门槛,让任何人都可以基于LLM构建属于自己的创意应用。
LangChain特性
- LLM和提示(Prompt):LangChain对所有LLM大模型进行了API抽象,统一了大模型访问API,同时提供了Prompt提示模板管理机制。
- 链(Chain):langchain对一些常见的场景封装成了一些模块,例如:基于上下文问答系统、自然语言生成SQL查询等。因为实现这些任务的过程就和工作流一样,一步一步执行,所以叫做链。
- LCEL(LangChain Experssion Language):(LCEL),langchain新版本的核心,用于解决工作流编排问题,通过LCEL表达式,我们可以灵活的定义AI任务处理流程,也就是灵活定义Chain。
- 数据增强生成(RAG):因为大模型(LLM)不了解新的信息,无法回答新的问题,所以我们可以将新的信息导入到LLM,用于增强LLM生成内容的质量,这种模式叫做RAG模式。
- 模型记忆:让大模型(LLM)记住之前的对话内容,这种能力称为模型记忆。
LangChain核心组成
- LangSmith:功能是关于用于构建生产级的 LLM 应用程序。它提供了一系列功能,旨在简化 LLM 应用的开发、调试、测试、部署和监控流程。
- LangServer:作用是将开发的AI做部署使用的,对外暴露为请求接口,方便跨语言调用的。
- Templates:用于定义模板的。
- Models I/O:用于对接大模型的。
- Retieval:提供检索相关的功能,比如:对向量数据库的一些检索。
- Agent Tooling:做第三方工具调用的。
- LangChain-Core:为开发基于语言模型的应用提供了基础的工具和抽象,旨在简化与语言模型的交互以及构建复杂的语言处理流程。
任务处理流程
主要包含Format(格式化)、Predict(预测)、Parse(解析)三个关键环节:
- Format(格式化):将变量值
x = "foo", y = "bar"
填充到提示模板 “Does {x} like {y}, and why?” 中,形成具体的问题 “Does foo like bar, and why?” ,为模型预测做准备。 - Predict(预测):生成的问题会被发送到大语言模型(LLM)或聊天模型(Chat Model) 进行处理,模型基于自身训练的知识和模式预测并给出回答,如 “Foo does…”。
- Parse(解析):对模型输出的文本进行解析,提取结构化信息。图中示例将回答解析成键值对形式,即 “likes”: True 表示喜好结果, “reason”: “Because …” 给出喜欢的原因 。
核心概念
LLMs
LangChain封装的基础模型,模型接收一个文本输入,然后返回一个文本结果。
Chat Models
聊天模型,与LLMs不同,这些模型专为对话场景设计。模型可以接收一组对话消息,然后返回对话消息,类似于聊天。
消息(Message)
指的是聊天模型(Chat Models)的消息内容,消息类型包括包括HumanMessage、AIMessage、SystemMessage、FunctionMessage和ToolMessage等多种类型的消息。
提示(prompts)
LangChain封装了一组专门用于提示词(prompts)管理的工具类,方便我们格式化提示词(prompts)内容。
输出解析器
如上图介绍,Langchain接受大模型(LLM)返回的文本内容之后,可以使用专门的输出解析器对文本内容进行格式化,例如解析json、或者将输出的内容转成python对象。
Retrievers
为方便我们将私有数据导入到大模型(LLM),提高模型回答问题的质量,LangChain封装了检索框架(Retrievers),方便我们加载文档数据、切割文档数据、存储和检索文档数据。
向量存储(Vector atores)
为了支持私有数据的语义相似搜索,langchain支持多种向量数据库。
Agents
智能体(Agents),通常指的是大模型(LLM)作为决策引擎,根据用户输入的任务,自动调用外部系统、硬件设备共同完成用户的任务,是一种以大模型为核心的应用设计模式。
快速入门
langchain安装
安装LangCahin,可以使用Pip进行安装,下面是LangChain的安装步骤:
# 在pycharm终端中执行命令安装langchain
pip install langchain
openai集成
首先国内由于网络环境是无法使用openai的我们可以用第三方中转的方式,第三放中专还需要设置中转站的地址。
安装
因为我们这里使用openai的的sdk,所以还需要安装langchain X openai的集成包:
pip install langchain openai
配置
langchain需要集成大模型(LLM)才能正式使用,所以我们需要配置第大模型(LLM)。
因为要调用第三方的服务,所以需要拿到授权认证,也就是
API_KEY
。因为网络环境所以不止需要设置API KEY,还需要设置中转站地址,也就是
OPENAI_BASE_URL
。这些key都是固定的,不能自定义。
配置的方法有如下几种:
windows下配置
直接在高级系统设置
>>环境变量
中设置即可
Linux下配置
linux下执行该命令即可,将值替换即可
# 下面的值需要自行替换
export OPENAI_API_KEY="your key"
export OPENAI_BASE_URL="your url"
直接在python中配置
# 下面的值需要自行替换
import os
os.environ["OPENAI_API_KEY"] = "your_openai_api_key"
os.environ["OPENAI_BASE_URL"] = "your url"
快速上手
下面是一个使用了提示词模板的简单实例,让AI去完成指定的任务。
# 1、引入langchain提示词模板
from langchain_core.prompts import ChatPromptTemplate
# 2、引入langchain的openai的sdk
from langchain_openai import ChatOpenAI
# 3、获取大模型示例
llm = ChatOpenAI()
# 4、根据message定义提示词模板
prompt = ChatPromptTemplate.from_messages([
{"role": "system", "content": "你是诗人"},
{"role": "user", "content": "{input}"}
])
# 5、调用langchain链式调用,生成一个链
chain = prompt | llm
# 6、获取结果(调用 invoke,先生成提示词模板再调用大模型)
try:
result = chain.invoke({"input": "写一篇关于AI的诗,50字以下"})
# 7、打印结果
print(result.content)
except Exception as e:
print(f"请求出错: {e}")
'''
---------------------------------------------------------------------------------------------------------------------------------------------------------
遇到的问题:
控制台报错ModuleNotFoundError: No module named 'langchain_community'
解决方案:
在命令行执行 pip install langchain_community
---------------------------------------------------------------------------------------------------------------------------------------------------------
遇到的问题:
from langchain_openai import ChatOpenAI 爆红
解决方案:
执行更新命令 pip install -U langchain-openai
---------------------------------------------------------------------------------------------------------------------------------------------------------
'''
提示词模板
提示词模板是预设文本框架,用于引导规范语言生成模型输出:
- 明确方向:帮模型锁定内容主题与方向,像写新闻报道,引导围绕关键要素创作。
- 规范风格:借特定词汇、句式等,让模型生成契合场景的风格文本,如创作感染力强的广告语。
- 提升效率:减少输入量与时间,常见任务(邮件、报告总结)填关键信息,模型即可快速生成。
- 把控结果:经精心设计,约束模型输出,避免无关、不合理内容,如知识问答中引导精准作答 。
例如在OpenAI的Chat Completion API中,openai的聊天模型,给不同的消息定义了三种角色,分别是:
- 助手(Assistant):AI回答给我们的内容
- 人类(user):我们给AI发送的消息内容
- 系统(system):给AI进行身份描述
不论我们使用了哪种方式生成提示词模板,最后其实都是生成了一样的message。
简单提示词模板
简单的提示词模板有两种方式可以定义:
- 一种是传递列表,列表中存储的每个元素都是字典,都有role和content两个键需要我们指定内容:
prompt = ChatPromptTemplate.from_messages([
{"role": "system", "content": "你是诗人"},
{"role": "user", "content": "{input}"}
])
- 第二种是以对象引用的方式传递:
prompt = ChatPromptTemplate.from_messages(
[
SystemMessage("你是诗人,可以即兴创作诗文"),
HumanMessagePromptTemplate.from_template("{input}")
]
)
消息占位符(MessagePlaceholder)
我们在上面的例子中使用的是{}
占位符的方式去动态插值到模板中的方式,而实际情况中,使用占位符的话不具有可读性,而且它难以实现在复杂场景下的使用,所以引入了消息占位符(MessagePlaceholder),虽然功能相同,但是更加好用。
例子如下:
from langchain_core.prompts import ChatPromptTemplate, MessagesPlaceholder
from langchain.schema import HumanMessage
# 创建一个聊天提示模板
prompt = ChatPromptTemplate.from_messages([
{"role": "system", "content": "你是一位知识渊博的助手。"},
MessagesPlaceholder(variable_name="user_input")
# 在variable_name处指定我们的key
])
# 渲染提示模板
user_input = "给我介绍一下金字塔。"
# 将用户输入转换为 HumanMessage 对象列表
user_message = [HumanMessage(content=user_input)]
messages = prompt.format_messages(user_input=user_message)
print(messages)
示例提示词
提示词追加示例(Fwe-shot prompt templates)
简单来讲,就是提供一组问答示例,告诉大模型我们要想怎么样的一个回答,而不是大模型自己生成的宽泛回答。
例如:
我们提供示例
问:什么是大模型。
答:大模型,即大型语言模型(Large Language Model,简称 LLM),是一种基于深度学习技术的人工智能模型。
我们再提问
问:什么是agent
答:[AI就会根据我们上面的模式去回答]
代码示例如下:
from langchain.prompts.few_shot import FewShotPromptTemplate
from langchain.prompts import PromptTemplate
from langchain_openai import ChatOpenAI
# 定义示例
examples = [
{
"question": "分析情感:'这部电影太精彩了,我看得目不转睛。'",
"answer": "积极"
},
{
"question": "分析情感:'今天的天气真糟糕,一直下雨。'",
"answer": "消极"
},
{
"question": "分析情感:'我早上喝了一杯咖啡。'",
"answer": "中性"
}
]
# 指定示例应该如何填充
examples_prmpt = PromptTemplate(
input_variables=["question", "answer"],
template="问题:{question}\n回答:{answer}"
)
# 创建带示例的提示模板对象
prompt = FewShotPromptTemplate(
examples=examples,
example_prompt=examples_prmpt,
suffix="问题:{input}",
input_variables=["input"]
)
# 生成提示文本
prompt_text = prompt.invoke({"input": "分析情感:'这家餐厅的服务非常周到,食物也很美味。'"})
# 初始化 OpenAI 语言模型
llm = ChatOpenAI()
# 调用模型获取回答
answer = llm.invoke(prompt_text)
print("生成的提示文本:")
print(prompt_text)
print("模型的回答:")
print(answer.content)
示例选择器(ExampleSelector)
如果我们的示例很多的话,我们不可能全部发送给服务端的大模型,因为比较耗费资源,有些时候我们只需要传递一部分就行了(token有限)。
from langchain.prompts.few_shot import FewShotPromptTemplate
from langchain.prompts import PromptTemplate
from langchain_openai import ChatOpenAI
# 定义示例
examples = [
{
"question": "分析情感:'这部电影太精彩了,我看得目不转睛。'",
"answer": "积极"
},
{
"question": "分析情感:'今天的天气真糟糕,一直下雨。'",
"answer": "消极"
},
{
"question": "分析情感:'我早上喝了一杯咖啡。'",
"answer": "中性"
}
]
from langchain.prompts.example_selector import SemanticSimilarityExampleSelector
from langchain_community.vectorstores import Chroma
from langchain_openai import OpenAIEmbeddings
examples_selector = SemanticSimilarityExampleSelector.from_examples(
# 可供选择的示例表
examples,
# 用去生成嵌入的嵌入类,该嵌入用于衡量语义的相似性
OpenAIEmbeddings(),
# 用于存储嵌入和执行相似搜索的VectorStore类
Chroma,
# 生成的示例数
k=1
)
# 选择与输入最相似的示例
question = "分析情感:'这家餐厅的服务非常周到,食物也很美味。'"
# selected_question = examples_selector.select_examples({"question": question})
# print("最相似的示例")
# print(f"{question}: {selected_question}")
# 指定示例应该如何填充
examples_prompt = PromptTemplate(
input_variables=["question", "answer"],
template="问题:{question}\n回答:{answer}"
)
# 创建带示例的提示模板对象
prompt = FewShotPromptTemplate(
example_selector=examples_selector,
example_prompt=examples_prompt,
suffix="问题:{input}",
input_variables=["input"]
)
prompt_text = prompt.invoke({"input": question})
# 初始化 OpenAI 语言模型
llm = ChatOpenAI()
result = llm.invoke(prompt_text)
print(result.content)
LangChain工作流编排
LCEL介绍
LCEL(langchain experssion language)一种强大的工作流编排工具,可以从基本组件构建复杂任务链条,支持诸如流式处理、并处理和日志等等开箱即用的功能。
特点
- 流式支持:用LCEL搭建链时,能最快看到输出的开头部分。有些链能让大语言模型(LLM)直接把生成的内容小块,以原始速度传给输出解析器,这样你很快就能看到逐步解析出的内容。比如问问题,能马上看到回答的开头陆续出现。
- 异步支持:不管是在开发原型(比如在Jupyter笔记本中测试),还是在实际生产(比如在LangServe服务器上运行),用LCEL搭建的链既可以用同步方式调用,也能用异步方式调用。这样代码在不同阶段都能用,性能不错,还能同时处理很多请求。
- 优化的并行执行:如果LCEL链里有些步骤能同时进行(比如从好几个地方找文档),不管是同步还是异步调用,系统都会自动并行处理,让等待时间最短。就像同时让几个人去不同地方找东西,更快拿到结果。
- 重试和回退:可以给LCEL链的任何部分设置出错时重试或者采取其他措施(回退),让链运行更可靠。目前还在努力让这个功能和流式支持结合,这样出错重试也不会增加等待时间。
- 访问中间结果:对于复杂的链,在得出最终结果前,看到中间步骤的结果很有用。可以把中间结果展示给用户,让他们知道程序在运行,也能用来检查链的问题。而且在每个LangServe服务器上都能这么做。
- 输入和输出模式:每个LCEL链都有根据自身结构得出的输入和输出模式(Pydantic和JSONSchema模式)。这能用来检查输入和输出对不对,也是LangServe的重要部分。
作用
- 加速开发过程:在大语言模型应用开发中,使用 LCEL 能够快速搭建原型和实现复杂的功能。开发者无需从头开始编写大量的底层代码来处理模型交互和数据流转,大大缩短了开发周期。
- 支持多种任务:无论是文本生成、问答系统、摘要提取、情感分析等常见的自然语言处理任务,还是更复杂的多模态任务,LCEL 都可以通过合理的组件组合来实现,具有很强的通用性。
- 提高应用性能:通过精确地配置和组合组件,开发者可以更好地优化应用的性能,例如提高模型的响应速度、减少资源消耗,同时提升输出结果的质量和准确性。
Runable interface
为了尽可能简化创建自定义链的过程,我们实现了一个“Runable”协议。许多LangChain组件都实现了Runable协议,包括聊天模型、LLMs、输出解析器、提示词模板等等。此外,还有一些有用的基本组件可以用于处理可运行对象。
它的接口标准包括:
stream
:返回响应的数据块invoke
:对输出调用链batch
:对输入列表调用链
这些还有响应的异步方法,应该与asyncio一起使用await
语法以实现并发:
astream
:异步返回响应的数据块ainvoke
:异步对输入列表调用链abatch
:异步对输入列表调用链astream_log
:异步返回中间步骤,以及最终响应steam_events
:beta流式传输链中发生的事件
输入类型和输出类型因组件而异:
组件 | 输入类型 | 输出类型 |
---|---|---|
提示 | 字典 | 提示值 |
聊天模型 | 单个字符、聊天消息列表、提示值 | 聊天消息 |
LLM | 单个字符、聊天消息列表、提示值 | 字符串 |
输出解析器 | LLM或聊天模型的输出 | 取决于解析器 |
检索器 | 单个字符 | 文档列表 |
工具 | 单个字符、字典 | 取决于工具 |
所有可运行对象都公开输入和输出模式以检查输入和输出:
input_schema
:从可运行对象结构自动生成的输入pydantic模型output_schema
:从可运行对象结构自动生成的输出pydantic模型
Stream(流)
所有Runnable
对象都实现了一个名为stream
的同步方法和一个名为astream
的异步变体。这些方法旨在以块的形式流式传输最终输出,尽快返回每个块。只有在程序中的所有步骤都知道如何处理输入流时,才能进行流式传输;即,逐个处理输入块,并产生相应的输出块。这种处理的复杂性可以有所不同,从简单的任务,如发出LLM生成的令牌,到更具挑战性的务,如在整个JSON完成之前流式传输JSON结果的部分。开始探索流式传输的最佳方法是从LLM应用程序中最重要的组件开始一LLM本身!
流式调用示例
要使得应用具更高的响应率,我们可以显示中间进度,也就是以令牌为当为进行流式输出。
首先是基于同步的stream
API开始调用,异步的astream
在下面chain部分会提到
from langchain_openai import ChatOpenAI
model = ChatOpenAI(model="gpt-4")
chunks = []
for chunk in model.stream("天空是什么颜色?"):
chunks.append(chunk)
print(chunk.content, end="|", flush=True)
model.steam
中的每个块其实本质是AIMessageChunk,该块表示AIMessage的一部分,消息快是可叠加的,可以理解为直至目前为止得到的响应。
Chain(链)
几乎所有的LLM应用程序都涉及不止一步的操作,而不仅仅是调用语言模型。让我们使用LangChain表达式语言(LCEL)构建一个简单的链,该链结合了一个提示、模型和解析器,并验证流式传输是否正常工作。我们将使用StrOutputParser
来解析模型的输出。这是一个简单的解析器,从AIMessageChunk
中提取content
字段,给出模型返回的token。
LCEL是一种声明式的方式,通过将不同的LangChain原语链接在一起来指定一个“程序”。使用LCEL创建的链可以自动实现stream
和astream
,从而实现对最终输出的流式传输。事实上,使用LCEL创建的链实现了整个标准Runnable
接口。
现在是基于异步的astream
做链式调用:
from langchain_openai import ChatOpenAI
import asyncio # 需要使用asyncio来执行异步的方法
from langchain_core.output_parsers import StrOutputParser
from langchain_core.prompts import ChatPromptTemplate
prompt = ChatPromptTemplate.from_template("给我将一个关于{topic}的笑话")
model = ChatOpenAI(model="gpt-4")
parser = StrOutputParser()
# 这就是chain,链式调用
chain = prompt | model | parser
async def async_stream():
async for chunk in chain.astream({"topic": "学生"}):
print(chunk, end="|", flush=True)
# 运行异步流处理结果
asyncio.run(async_stream())
又比如我们想让它默认输出的markdown
格式的数据变成json数据,我们可以将链式调用中的OutputParser
换成JsonOutputParser
,如下:
from langchain_openai import ChatOpenAI
import asyncio
from langchain_core.output_parsers import JsonOutputParser
model = ChatOpenAI(model="gpt-4")
parser = JsonOutputParser()
chain = model | parser
async def async_stream():
async for chunk in chain.astream("以json格式输出美国和中国及其省市县的人口表"):
print(chunk, flush=True)
'''
这里需要注意:
指定输出为json的时候,其实输出的还是markdown
但是如果不指定输出为json的时候,转换成json可能不会成功
'''
# 运行异步流处理结果
asyncio.run(async_stream())
Stream event(事件流)
上面已经了解了stream
和astream
的工作原理,一般是做监控的时候使用。
为了使astream_events
API能够正常工作:
- 在代码中尽可能使用
async
- 如果自定义函数/可运行项,请传播回调
- 在没有LCEL的情况下使用可运行项时,确保在LLMs上调用
.astream()
而不是.invoke
以强制LLM传输令牌
事件流的事件参考
表示各种可运行对象可能发出的一些事件。
当流式传输正确实现时,对于可运行项的输入直到输出流完全耗尽后才会直到。这意味着inputs
通常仅仅包含end
而不包含start
事件。
事件 | 名称 | 块 | 输入 | 输出 |
---|---|---|---|---|
on_chain_start | 链开始事件 | 无特定块,包含链开始的标识信息 | 传递给链的初始输入数据 | 无(此时还未产生输出) |
on_chain_end | 链结束事件 | 无特定块,包含链结束的相关状态信息 | 同 on_chain_start 时的输入信息 | 链执行完成后的最终输出结果 |
on_chain_stream | 链流事件 | 包含链输出的分块内容 | 同 on_chain_start 时的输入信息 | 链输出的分块内容 |
on_llm_start | 语言模型开始事件 | 无特定块,主要是触发调用相关信息 | 传递给语言模型的输入提示等相关数据 | 无(此时还未产生输出) |
on_llm_stream | 语言模型流事件 | 包含语言模型新生成的一个 Token 信息 | 同 on_llm_start 时的输入信息 | 语言模型新生成的一个 Token 内容 |
on_llm_end | 语言模型结束事件 | 无特定块,包含调用结束的相关状态信息 | 同 on_llm_start 时的输入信息 | 语言模型最终生成的完整输出内容 |
on_chat_model_start | 聊天模型开始事件 | 无特定块,包含聊天模型开始的相关信息 | 通常是一个包含消息列表的字典,如 {"messages": [system_message, human_message]} | 无(此时还未产生输出) |
on_chat_model_stream | 聊天模型流事件 | 包含聊天模型新生成的一个消息块(如 AIMessageChunk ) | 同 on_chat_model_start 时的输入信息 | 聊天模型新生成的消息块内容 |
on_chat_model_end | 聊天模型结束事件 | 无特定块,包含聊天模型结束的相关状态信息 | 同 on_chat_model_start 时的输入信息 | 包含生成的消息列表、模型输出等信息,如 {"generations": [...], "llm_output": None, ...} |
on_tool_start | 工具开始事件 | 无特定块,包含工具开始的标识信息 | 传递给工具的输入数据 | 无(此时工具还未执行完成) |
on_tool_end | 工具结束事件 | 无特定块,包含工具执行结束的相关信息 | 同 on_tool_start 时的输入信息 | 工具执行后的输出结果 |
on_retriever_get_relevant_documents_start | 检索器获取相关文档开始事件 | 无特定块,主要是检索开始的标识信息 | 检索的查询条件等相关输入数据 | 无(此时还未获取到文档) |
on_retriever_get_relevant_documents_end | 检索器获取相关文档结束事件 | 包含获取到的相关文档集合信息 | 同 on_retriever_get_relevant_documents_start 时的输入信息 | 获取到的相关文档列表 |
RawResponsesStreamEvent | 原始响应事件 | 直接从LLM传递的原始事件,采用OpenAI响应API格式,每个事件有类型(如 response.created 、response.output_text.delta 等)和数据 | 无 | 无 |
AgentUpdatedStreamEvent | 智能体更新事件 | 无特定块,在当前智能体更改时(例如作为交接的结果)提供更新信息 | 无 | 无 |
我们可以执行如下代码查看究竟执行了哪些事件:
from langchain_openai import ChatOpenAI
import asyncio
model = ChatOpenAI(model="gpt-4")
async def async_stream():
events = []
async for event in model.astream_events("/hello", version="v2"):
events.append(event)
print(events)
asyncio.run(async_stream())
附(扩展)
tavily(塔莉维)
模型有些时候并不能获取实时的信息,所以我们需要使用tavily(塔莉维)去实现对数据的实时检索,虽然它付费吧。
使用之前我们需要先去获取KEY并配在环境变量中:
tavily官网:tavily直达
# windows下设置环境变量的命令
SETX TAVILY_API_KEY "your key"
# 也可以直接去【高级系统设置】>>【环境变量】设置
然后在代码中就能使用它去实现实时检索了:
from langchain_openai import ChatOpenAI
from langchain.agents import AgentExecutor, create_tool_calling_agent
# pip isntall -U langchain-community tavily-python
from langchain_community.tools.tavily_search import TavilySearchResults
from langchain_core.prompts import ChatPromptTemplate
from langchain.globals import set_verbose
llm = ChatOpenAI(model="gpt-4")
tools = [TavilySearchResults(max_results=1)]
prompt = ChatPromptTemplate.from_messages(
[
(
"system",
"你是一位AI助手"
),
(
"placeholder",
"{chat_history}"
),
(
"human",
"{input}"
),
(
"placeholder",
"{agent_scratchpad}"
)
]
)
# 构建工具代理
agent = create_tool_calling_agent(llm, tools, prompt)
# 打印日志
set_verbose(True)
# 通过传入代理和工具来创建代理执行器
agent_executor = AgentExecutor(agent=agent, tools=tools)
response = agent_executor.invoke({"input": "哪吒2电影目前的票房是多少?"})
print(response)
LangServe服务部署
概述
LangServe是用于部署 LangChain 应用程序的工具,部署为REST API,由此获得良好跨平台性。该库集成了FastAPI并使用pydantic
进行数据验证。
**pydantic**
是一个在python中用于数据验证和解析的第三方库,现在是python中使用最为广泛的数据验证库。
- 它利用声明式的方式定义数据模型和Python类型提示的强大功能来执行数据验证和序列化,使您的代码更可靠、更可读、更简洁且更易于调试。。
- 它还可以从模型生成SON架构,提供了自动生成文档等功能,从而轻松与其他工具集成。
此外还提供了一个客户端,用于调用部署在服务器上的可运行对象,JavaScript客户端可以在LangChain.js中找到。
特性
- 从LangChain对象自动推断输入和输出模式,并在每次API调用中执行,提供吩咐的错误信息
- 带有JSONSchema和Swagger的API文档页面(插入示例链接)
- 高效的
/invoke
、/batch
和/stream
端点,支持单个服务器上的多个并发请求 - 支持
/stream_events
,使流传输更简便,无需解析/stream_kig
的输出 - 使用经过严格测试的开源python库构建,如FastAPI、Pydantic、uvloop和asyncio
- 使用客户端SDK调用LangServe服务器,就像本地运行可运行对象一样(或直接调用HTTP API)
限制
- 目前不支持服务器发起的事件的客户端回调
- 当使用pydantic V2的时候,将不会生成OpenAPI文档。FastAPI支持混合使用Pydantic V1和V2命名空间
安装
安装命令如下:
# 客户端+服务端
pip install --upgrade "langserve[all]"
# 客户端
pip install "langserve[client]"
# 服务端
pip install "langserve[server]"
对于我们开发来讲直接安装all即可。
LangChain CLI
使用langchain cli快速启动langserve项目。
要使用langchain CLI需要确保已经安装最新版本的langchain-cli
。可以使用如下命令安装:
pip install -U langchain-cli
设置
注意:我们使用poetry进行依赖管理(优点类似于java的maven)。
- 使用langchian cli命令创建新应用
langchain app new [应用名称]
- 在add_routes中定义可运行对象,转到server.py并编辑
add_routes(app.NotImplemented)
- 使用
**poetry**
添加第三方包(例如langchain-openai、langchain-anthropic、langchain-mistral等),依次执行以下命令。
# 1、首先安装pipx
pip install pipx
# 2、将pipx加入到环境变量(添加完需要重启pycharm)
pipx ensurepath
# 3、安装poetry
pipx install poetry
# 4、安装langchain-openai库
# 执行完这个命令其实就是在pyproject.toml中的[tool.poety.dependencies]中添加了相关的依赖
poetry add langchain
poetry add langchain-openai
注意:poetry和python版本有依赖关系。
在执行poetry add langchain
还可能会出现以下报错:
'''
控制台报错:
Because no versions of langchain match >0.3.21,<0.4.0
and langchain (0.3.21) depends on pydantic (>=2.7.4,<3.0.0), langchain (>=0.3.21,<0.4.0) requires pydantic (>=2.7.4,<3.0.0).
So, because myapp depends on both pydantic (<2) and langchain (^0.3.21), version solving failed.
'''
解决方案:
- 修改
pyproject.toml
文件:在[tool.poetry.dependencies]
部分,把pydantic
的版本要求改成>=2.7.4,<3.0.0
。以下是修改后的示例:
[tool.poetry.dependencies]
python = "^3.11"
langchain = "^0.3.21"
pydantic = ">=2.7.4,<3.0.0"
- 更新依赖:在命令行中运行以下命令来更新依赖:
poetry update
- 配置相关的API KEY等环境变量
- 启动项目
poetry run langchain serve --port=8000
之后我们可以通过访问[http://127.0.0.1:8000/docs](http://127.0.0.1:8000/docs)
来查看接口文档信息:
注意:如果想要使用浏览器去调用这些接口的话可能还需要设置:
# 设置启用所有的CORS来源
app.add_middleware(
CORSMiddleware,
allow_origins=["*"],
allow_credentials=True,
allow_methods=["*"],
allow_headers=["*"],
expose_headers=["*"]
)
小案例
服务端
我们需要在server.py
中编写一个简单的服务端代码,调用openai:
from fastapi import FastAPI
from langserve import add_routes
from langchain_openai import ChatOpenAI
app = FastAPI(
title="langchain服务端",
version="1.0",
description="使用langchain的runnable接口实现简单的服务端"
)
# Edit this to add the chain you want to add
add_routes(
app,
ChatOpenAI(model="gpt-4"),
path="/openai"
)
if __name__ == "__main__":
import uvicorn
uvicorn.run(app, host="0.0.0.0", port=8000)
客户端
我们可以在python中再自己实现一个客户端实现发送请求给服务端:
from langchain.schema.runnable import RunnableMap
from langchain_core.prompts import ChatPromptTemplate
from langserve import RemoteRunnable
# 简单的客户端请求实现
openai = RemoteRunnable("http://localhost:8000/openai")
prompt = ChatPromptTemplate.from_messages(
[("system", "你是一个喜欢写故事的助手"), ("user", "写一个故事:主题是{topic}")]
)
# 自定义链
chain = prompt | RunnableMap({"openai": openai})
response = chain.invoke({"topic": "猫"})
print(response)
直接请求
我们也可以通过编写request请求或post发起请求的方式请求服务端:
import requests
# 同步调用
print("同步调用...")
response = requests.post(
'http://127.0.0.1:8000/openai/invoke',
json={'input': "{'topic': '猫'}"}
)
print(response.json())
# 流式调用
print("流式调用...")
response_stream = requests.post(
'http://127.0.0.1:8000/openai/stream',
json={'input': "{'topic': '猫'}"}
)
for line in response_stream.iter_lines():
line = line.decode('utf-8')
if line.startswith('data: ') and not line.endswith("[DONE]"):
data = json.loads(line[len('data: '):])
print(data)
LangChain服务监控
与构建任何类型的软件一样,使用LLM构建时,总会有调试的需求。模型调用可能会失败,模型输出可能格式错误,或者可能存在一些嵌套的模型调用,不清楚在哪一步出现了错误的输出。有三种主要的调试方法:
- 详细模式(Verbose):为你的链中的"重要”事件添加打印语句。
- 调试模式(Debug):为你的链中的所有事件添加日志记录语句。
- LangSmithi跟踪:将事件记录到LangSmith,以便在那里进行可视化。
LangSmith
使用LangChain构建的许多应用程序将包含多个步骤,其中包含多次LLM调用。随着这些应用程序变得越来越复杂,能够检查链或代理内部发生了什么变得至关重要。这样做的最佳方式是使用LangSmith。在上面的链接上注册后,请确保设置你的环境变量以开始记录跟踪:
LangSmith官网:smith直达
# windows导入环境变量
# 配置LangSmith监控开关,true开启,false关闭
setx LANGCHAIN_TRACING_V2 "true"
# 配置LangSmith api key
setx LANGCHAIN_API_KEY "your key"
# 也可以直接去【高级系统设置】>>【环境变量】设置
接下来我们只要是调用了用langchain写的调用,他就可以实现一个监控执行。
Debug(调试日志打印)
set_debug(True)
设置全局的debug
标志将导致所有具有回调支持的LangChain组件(链、模型、代理、工具、检索器)打印它们接收的输入和生成的输出。这是最详细的设置,将完全记录原始输入和输出。
from langchain.globals import set_debug
# 直接在代码中插入设置
set_debug(True) #打印调试日志
# 不输出详细日志
set_verbose(False)
消息管理与历史存储
消息存储在内存
会话唯一键
下面我们展示一个简单的示例,其中聊天历史保存在内存中,此外通过全局Python字典实现。
Langchain中提供了一个名为ChatMessageHistory
的类,这个类是用来存储聊天消息历史的。我们可以给这个类传递一些参数,比如:
session_id
:会话ID,也就是说可以实现同时进行多个会话,并对不同的会话进行管理。
例如我们下面的例子:
from langchain_community.chat_models import ChatOpenAI
from langchain_core.chat_history import BaseChatMessageHistory
from langchain_core.prompts import ChatPromptTemplate, MessagesPlaceholder
from langchain_community.chat_message_histories import ChatMessageHistory
from langchain_core.runnables import RunnableWithMessageHistory
# 准备提示词模板
prompt = ChatPromptTemplate.from_messages(
[
(
"system",
"你是一位擅长{ability}的助手,请用不超过20个词回复。"
),
# 历史消息占位符,可以将每次聊天的记录作为参数传递进来
MessagesPlaceholder(variable_name="history"),
("human","{input}")
]
)
# 使用模型
model = ChatOpenAI(model="gpt-4")
# chain式调用
runnable = prompt | model
# 用来存储会话历史记录
store = {}
# 定义一个获取会话历史的函数,参数是sessionid,返回结果是一个会话历史记录
# -> 是继承 这里是继承了BaseChatMessageHistory
def get_session_history(session_id: str) -> BaseChatMessageHistory:
if session_id not in store:
store[session_id] = ChatMessageHistory() # 做初始化
return store[session_id]
# 创建一个带会话历史记录的运行器Runnable
with_message_history = RunnableWithMessageHistory(
runnable,
get_session_history,
input_messages_key="input",
history_messages_key="history",
)
# 调用Runnable(会话1)
response = with_message_history.invoke(
{"ability":"math","input":"余弦是什么意思"}, # 填充模板
config={"configurable":{"session_id":"abc123"}} # 固定写法,可以传递一个字典
)
print(response)
# 调用Runnable(会话1)
response = with_message_history.invoke(
{"ability":"math","input":"什么?"}, # 填充模板
config={"configurable":{"session_id":"abc123"}} # 固定写法,可以传递一个字典
)
print(response)
# 调用Runnable(会话3)
response = with_message_history.invoke(
{"ability":"math","input":"什么?"}, # 填充模板
config={"configurable":{"session_id":"abc124"}} # 固定写法,可以传递一个字典
)
print(response)
# 执行代码后发现,在相同的会话下的提问,他会记住历史消息,我们提问它可以答的上来
用户+会话唯一键
上面的例子中是以session_id为键存储的,但是在正常使用情况下一般是需要通过用户ID+会话ID确定唯一的维度。
我们可以通过向history_factory_config
参数传递一个ConfigurableFieldSpec
对象列表来定义跟踪消息历史的配置参数。下面我们使用了两个参数:user_id
和conversation_id
。
配置user_id和conversation_id作为会话唯一键
from langchain_core.runnables import ConfigurableFieldSpec