🦜️🏓 LangServe
[!警告] 我们建议新项目使用LangGraph平台而不是LangServe。
请参阅LangGraph平台迁移指南以获取更多信息。
我们将继续接受社区对LangServe的错误修复;然而,我们将不接受新的功能贡献。
概述
LangServe 帮助开发者
将 LangChain
可运行程序和链
部署为 REST API。
这个库与FastAPI集成,并使用pydantic进行数据验证。
此外,它还提供了一个客户端,可用于调用部署在服务器上的可运行程序。 在LangChain.js中提供了一个JavaScript客户端。
功能
- 输入和输出模式自动从您的LangChain对象推断,并在每次API调用时强制执行,提供丰富的错误信息
- 带有JSONSchema和Swagger的API文档页面(插入示例链接)
- 高效的
/invoke
、/batch
和/stream
端点,支持在单个服务器上处理多个并发请求 /stream_log
端点用于流式传输来自您的链/代理的所有(或部分)中间步骤- new 从 0.0.40 版本开始,支持
/stream_events
,使得流式处理更加容易,无需解析/stream_log
的输出。 - Playground页面位于
/playground/
,具有流式输出和中间步骤 - 内置(可选)追踪到 LangSmith,只需添加您的 API 密钥(参见 Instructions)
- 全部使用经过实战检验的开源Python库构建,如FastAPI、Pydantic、uvloop和asyncio。
- 使用客户端SDK调用LangServe服务器,就像它在本地运行一样(或直接调用HTTP API)
- LangServe 中心
⚠️ LangGraph 兼容性
LangServe 主要用于部署简单的 Runnables 并与 langchain-core 中的已知原语一起工作。
如果你需要LangGraph的部署选项,你应该考虑使用LangGraph Cloud (beta),它更适合部署LangGraph应用程序。
限制
- 尚不支持源自服务器的事件的客户端回调
- LangServe 版本 <= 0.2.0 在使用 Pydantic V2 时不会正确生成 OpenAPI 文档,因为 Fast API 不支持 混合使用 pydantic v1 和 v2 命名空间。 详情请参阅下面的部分。请升级到 LangServe>=0.3.0 或将 Pydantic 降级到 pydantic 1。
安全
- 版本0.0.13至0.0.15中的漏洞——playground端点允许访问服务器上的任意文件。已在0.0.16中解决。
安装
对于客户端和服务器:
pip install "langserve[all]"
或者 pip install "langserve[client]"
用于客户端代码,
和 pip install "langserve[server]"
用于服务器代码。
LangChain CLI 🛠️
使用 LangChain
CLI 快速启动一个 LangServe
项目。
要使用langchain CLI,请确保您安装了最新版本的langchain-cli
。您可以使用pip install -U langchain-cli
进行安装。
设置
注意: 我们使用 poetry
进行依赖管理。请遵循 poetry 文档 了解更多信息。
1. 使用 langchain cli 命令创建新应用
langchain app new my-app
2. 在add_routes中定义可运行项。转到server.py并进行编辑
add_routes(app. NotImplemented)
3. 使用 poetry
添加第三方包(例如,langchain-openai, langchain-anthropic, langchain-mistral 等)。
poetry add [package-name] // e.g `poetry add langchain-openai`
4. 设置相关的环境变量。例如,
export OPENAI_API_KEY="sk-..."
5. 服务你的应用
poetry run langchain serve --port=8100
示例
通过示例目录快速启动您的LangServe实例。
描述 | 链接 |
---|---|
LLMs 保留OpenAI和Anthropic聊天模型的最小示例。使用异步,支持批处理和流式处理。 | server, client |
Retriever 一个简单的服务器,将检索器暴露为可运行的。 | server, client |
对话检索器 一个通过LangServe暴露的对话检索器 | server, client |
Agent 没有 对话历史 基于 OpenAI 工具 | server, client |
Agent 基于 OpenAI 工具 的 对话历史 | server, client |
RunnableWithMessageHistory 用于实现后端持久化的聊天,基于客户端提供的 session_id 。 | server, client |
RunnableWithMessageHistory 用于实现后端持久化的聊天,基于客户端提供的 conversation_id 和 user_id (参见 Auth 以正确实现 user_id )。 | server, client |
Configurable Runnable 用于创建一个支持运行时配置索引名称的检索器。 | server, client |
Configurable Runnable 显示可配置字段和可配置替代方案。 | server, client |
APIHandler 展示了如何使用 APIHandler 而不是 add_routes 。这为开发者提供了更多的灵活性来定义端点。适用于所有 FastAPI 模式,但需要更多的努力。 | server |
LCEL 示例 使用 LCEL 操作字典输入的示例。 | server, client |
Auth 使用 add_routes :简单的认证,可以应用于与应用程序关联的所有端点。(单独使用时对于实现每个用户的逻辑没有用处。) | server |
Auth 使用 add_routes :基于路径依赖的简单认证机制。(单独使用时无法实现每个用户的逻辑。) | server |
Auth 使用 add_routes :为使用每个请求配置修改器的端点实现每个用户的逻辑和认证。(注意:目前,不与OpenAPI文档集成。) | server, client |
Auth 使用 APIHandler :实现每个用户的逻辑和认证,展示如何仅在用户拥有的文档中进行搜索。 | server, client |
小部件 可以与playground一起使用的不同小部件(文件上传和聊天) | server |
小部件 用于LangServe游乐场的文件上传小部件。 | server, client |
示例应用程序
服务器
这是一个部署了OpenAI聊天模型、Anthropic聊天模型以及一个使用Anthropic模型来讲述关于某个主题的笑话的链的服务器。
#!/usr/bin/env python
from fastapi import FastAPI
from langchain.prompts import ChatPromptTemplate
from langchain.chat_models import ChatAnthropic, ChatOpenAI
from langserve import add_routes
app = FastAPI(
title="LangChain Server",
version="1.0",
description="A simple api server using Langchain's Runnable interfaces",
)
add_routes(
app,
ChatOpenAI(model="gpt-3.5-turbo-0125"),
path="/openai",
)
add_routes(
app,
ChatAnthropic(model="claude-3-haiku-20240307"),
path="/anthropic",
)
model = ChatAnthropic(model="claude-3-haiku-20240307")
prompt = ChatPromptTemplate.from_template("tell me a joke about {topic}")
add_routes(
app,
prompt | model,
path="/joke",
)
if __name__ == "__main__":
import uvicorn
uvicorn.run(app, host="localhost", port=8000)
如果您打算从浏览器调用您的端点,您还需要设置CORS头。 您可以使用FastAPI的内置中间件来实现这一点:
from fastapi.middleware.cors import CORSMiddleware
# Set all CORS enabled origins
app.add_middleware(
CORSMiddleware,
allow_origins=["*"],
allow_credentials=True,
allow_methods=["*"],
allow_headers=["*"],
expose_headers=["*"],
)
文档
如果您已经部署了上述服务器,您可以使用以下方式查看生成的OpenAPI文档:
⚠️ 如果使用 LangServe <= 0.2.0 和 pydantic v2,invoke、batch、stream、stream_log 的文档将不会生成。有关更多详细信息,请参阅下面的 Pydantic 部分。要解决此问题,请升级到 LangServe 0.3.0。
curl localhost:8000/docs
确保添加 /docs
后缀。
⚠️ 索引页面
/
未通过 设计 定义,因此curl localhost:8000
或访问该 URL 将返回 404。如果您希望在/
处显示内容,请定义一个端点@app.get("/")
。
客户端
Python SDK
from langchain.schema import SystemMessage, HumanMessage
from langchain.prompts import ChatPromptTemplate
from langchain.schema.runnable import RunnableMap
from langserve import RemoteRunnable
openai = RemoteRunnable("http://localhost:8000/openai/")
anthropic = RemoteRunnable("http://localhost:8000/anthropic/")
joke_chain = RemoteRunnable("http://localhost:8000/joke/")
joke_chain.invoke({"topic": "parrots"})
# or async
await joke_chain.ainvoke({"topic": "parrots"})
prompt = [
SystemMessage(content='Act like either a cat or a parrot.'),
HumanMessage(content='Hello!')
]
# Supports astream
async for msg in anthropic.astream(prompt):
print(msg, end="", flush=True)
prompt = ChatPromptTemplate.from_messages(
[("system", "Tell me a long story about {topic}")]
)
# Can define custom chains
chain = prompt | RunnableMap({
"openai": openai,
"anthropic": anthropic,
})
chain.batch([{"topic": "parrots"}, {"topic": "cats"}])
在 TypeScript 中(需要 LangChain.js 版本 0.0.166 或更高版本):
import { RemoteRunnable } from "@langchain/core/runnables/remote";
const chain = new RemoteRunnable({
url: `http://localhost:8000/joke/`,
});
const result = await chain.invoke({
topic: "cats",
});
Python 使用 requests
:
import requests
response = requests.post(
"http://localhost:8000/joke/invoke",
json={'input': {'topic': 'cats'}}
)
response.json()
你也可以使用 curl
:
curl --location --request POST 'http://localhost:8000/joke/invoke' \
--header 'Content-Type: application/json' \
--data-raw '{
"input": {
"topic": "cats"
}
}'
端点
以下代码:
...
add_routes(
app,
runnable,
path="/my_runnable",
)
将这些端点添加到服务器:
POST /my_runnable/invoke
- 在单个输入上调用可运行对象POST /my_runnable/batch
- 在一批输入上调用可运行对象POST /my_runnable/stream
- 在单个输入上调用并流式传输输出POST /my_runnable/stream_log
- 在单个输入上调用并流式传输输出,包括生成时的中间步骤输出POST /my_runnable/astream_events
- 在单个输入上调用并流式传输生成的事件,包括来自中间步骤的事件。GET /my_runnable/input_schema
- 可运行对象的输入json模式GET /my_runnable/output_schema
- 可运行对象输出的json模式GET /my_runnable/config_schema
- 可运行配置的json模式
这些端点匹配 LangChain 表达式语言接口 -- 请参考此文档以获取更多详细信息。
游乐场
你可以在 /my_runnable/playground/
找到一个用于你的可运行代码的游乐场页面。这提供了一个简单的用户界面来配置并使用流式输出和中间步骤调用你的可运行代码。
小部件
游乐场支持小部件,并可用于测试您的可运行程序与不同的输入。有关更多详细信息,请参阅下面的小部件部分。
分享
此外,对于可配置的可运行项,playground 将允许您配置可运行项并分享带有配置的链接:
聊天游乐场
LangServe 还支持一个专注于聊天的游乐场,可以在 /my_runnable/playground/
下选择并使用。
与通用游乐场不同,仅支持某些类型的可运行对象 - 可运行对象的输入模式必须是一个 dict
,并且具有以下之一:
- 一个单独的键,该键的值必须是一个聊天消息的列表。
- 两个键,一个键的值是消息列表,另一个代表最新的消息。
我们建议您使用第一种格式。
可运行对象还必须返回一个AIMessage
或一个字符串。
要启用它,您必须在添加路由时设置playground_type="chat",
。以下是一个示例:
# Declare a chain
prompt = ChatPromptTemplate.from_messages(
[
("system", "You are a helpful, professional assistant named Cob."),
MessagesPlaceholder(variable_name="messages"),
]
)
chain = prompt | ChatAnthropic(model="claude-2.1")
class InputChat(BaseModel):
"""Input for the chat endpoint."""
messages: List[Union[HumanMessage, AIMessage, SystemMessage]] = Field(
...,
description="The chat messages representing the current conversation.",
)
add_routes(
app,
chain.with_types(input_type=InputChat),
enable_feedback_endpoint=True,
enable_public_trace_link_endpoint=True,
playground_type="chat",
)
如果您正在使用LangSmith,您还可以在您的路由上设置enable_feedback_endpoint=True
以启用每条消息后的点赞/点踩按钮,并设置enable_public_trace_link_endpoint=True
以添加一个创建运行公共跟踪的按钮。
请注意,您还需要设置以下环境变量:
export LANGCHAIN_TRACING_V2="true"
export LANGCHAIN_PROJECT="YOUR_PROJECT_NAME"
export LANGCHAIN_API_KEY="YOUR_API_KEY"
以下是启用上述两个选项的示例:
注意:如果您启用了公共跟踪链接,您的链的内部将被暴露。我们建议仅在演示或测试时使用此设置。
遗留链
LangServe 可以与 Runnables(通过 LangChain 表达式语言 构建)和传统链(继承自 Chain
)一起工作。然而,一些传统链的输入模式可能不完整/不正确,导致错误。这可以通过在 LangChain 中更新这些链的 input_schema
属性来修复。如果您遇到任何错误,请在此仓库中提出问题,我们将努力解决它。
部署
部署到 AWS
您可以使用AWS Copilot CLI部署到AWS
copilot init --app [application-name] --name [service-name] --type 'Load Balanced Web Service' --dockerfile './Dockerfile' --deploy
点击这里了解更多。
部署到 Azure
您可以使用Azure容器应用(无服务器)部署到Azure:
az containerapp up --name [container-app-name] --source . --resource-group [resource-group-name] --environment [environment-name] --ingress external --target-port 8001 --env-vars=OPENAI_API_KEY=your_key
你可以找到更多信息 这里
部署到GCP
您可以使用以下命令部署到GCP Cloud Run:
gcloud run deploy [your-service-name] --source . --port 8001 --allow-unauthenticated --region us-central1 --set-env-vars=OPENAI_API_KEY=your_key
社区贡献
部署到 Railway
Pydantic
LangServe>=0.3 完全支持 Pydantic 2。
如果您使用的是LangServe的早期版本(<= 0.2),请注意对Pydantic 2的支持有以下限制:
- 使用Pydantic V2时,不会为invoke/batch/stream/stream_log生成OpenAPI文档。Fast API不支持[混合pydantic v1和v2命名空间]。要解决此问题,请使用
pip install pydantic==1.10.17
。 - LangChain 在 Pydantic v2 中使用 v1 命名空间。请阅读 以下指南以确保与 LangChain 的兼容性
除了这些限制之外,我们期望API端点、playground以及任何其他功能都能按预期工作。
高级
处理认证
如果您需要为您的服务器添加身份验证,请阅读Fast API的文档 关于dependencies 和security。
以下示例展示了如何使用FastAPI原语将认证逻辑连接到LangServe端点。
您负责提供实际的认证逻辑、用户表等。
如果你不确定自己在做什么,可以尝试使用现有的解决方案 Auth0。
使用 add_routes
如果您正在使用add_routes
,请参阅
示例 这里。
描述 | 链接 |
---|---|
Auth 使用 add_routes :简单的认证,可以应用于与应用程序关联的所有端点。(单独使用时对于实现每个用户的逻辑没有用处。) | server |
Auth 使用 add_routes :基于路径依赖的简单认证机制。(单独使用时无法实现每个用户的逻辑。) | server |
Auth 使用 add_routes :为使用每个请求配置修改器的端点实现每个用户的逻辑和认证。(注意:目前,不与OpenAPI文档集成。) | server, client |
或者,你可以使用FastAPI的middleware。
使用全局依赖项和路径依赖项的优势在于,OpenAPI文档页面将正确支持身份验证,但这些不足以实现每个用户的逻辑(例如,创建一个只能搜索用户拥有文档的应用程序)。
如果您需要实现每个用户的逻辑,您可以使用per_req_config_modifier
或APIHandler
(如下)来实现此逻辑。
每个用户
如果您需要依赖于用户的授权或逻辑,请在使用add_routes
时指定per_req_config_modifier
。使用一个可调用对象接收原始的Request
对象,并可以从中提取相关信息用于认证和授权目的。
使用 APIHandler
如果你对FastAPI和python感到熟悉,你可以使用LangServe的APIHandler。
描述 | 链接 |
---|---|
Auth 使用 APIHandler :实现每个用户的逻辑和认证,展示如何仅在用户拥有的文档中进行搜索。 | server, client |
APIHandler 展示了如何使用APIHandler 而不是add_routes 。这为开发者提供了更多的灵活性来定义端点。与所有FastAPI模式兼容,但需要更多的努力。 | server, client |
这需要更多的工作,但它让你完全控制端点定义,因此你可以为认证做任何你需要的自定义逻辑。
文件
LLM 应用程序经常处理文件。有不同的架构可以用来实现文件处理;在高层次上:
- 文件可以通过专用端点上传到服务器,并使用单独的端点进行处理
- 文件可以通过值(文件的字节)或引用(例如,s3 url 到文件内容)上传
- 处理端点可能是阻塞的或非阻塞的
- 如果需要大量处理,可以将处理任务卸载到专用的进程池
您应该确定适合您应用程序的架构。
目前,要通过值上传文件到可运行的程序,请使用base64编码文件(尚不支持multipart/form-data
)。
这里有一个示例,展示了如何使用base64编码将文件发送到远程可运行程序。
请记住,您始终可以通过引用(例如,s3 url)上传文件,或者将它们作为multipart/form-data上传到专用端点。
自定义输入和输出类型
所有可运行对象上都定义了输入和输出类型。
您可以通过input_schema
和output_schema
属性访问它们。
LangServe
使用这些类型进行验证和文档编写。
如果你想覆盖默认推断的类型,你可以使用with_types
方法。
这里有一个简单的例子来说明这个想法:
from typing import Any
from fastapi import FastAPI
from langchain.schema.runnable import RunnableLambda
app = FastAPI()
def func(x: Any) -> int:
"""Mistyped function that should accept an int but accepts anything."""
return x + 1
runnable = RunnableLambda(func).with_types(
input_type=int,
)
add_routes(app, runnable)
自定义用户类型
如果你想将数据反序列化为pydantic模型而不是等效的字典表示,请继承CustomUserType
。
目前,这种类型仅在服务器端有效,用于指定所需的解码行为。如果继承此类型,服务器将保持解码后的类型为pydantic模型,而不是将其转换为字典。
from fastapi import FastAPI
from langchain.schema.runnable import RunnableLambda
from langserve import add_routes
from langserve.schema import CustomUserType
app = FastAPI()
class Foo(CustomUserType):
bar: int
def func(foo: Foo) -> int:
"""Sample function that expects a Foo type which is a pydantic model"""
assert isinstance(foo, Foo)
return foo.bar
# Note that the input and output type are automatically inferred!
# You do not need to specify them.
# runnable = RunnableLambda(func).with_types( # <-- Not needed in this case
# input_type=Foo,
# output_type=int,
#
add_routes(app, RunnableLambda(func), path="/foo")
游乐场小部件
游乐场允许您从后端为您的可运行程序定义自定义小部件。
以下是一些示例:
描述 | 链接 |
---|---|
小部件 可以与playground一起使用的不同小部件(文件上传和聊天) | server, client |
小部件 用于LangServe游乐场的文件上传小部件。 | server, client |
架构
- 小部件在字段级别指定,并作为输入类型的JSON模式的一部分提供
- 一个小部件必须包含一个名为
type
的键,其值为已知小部件列表中的一个 - 其他小部件键将与描述JSON对象中路径的值相关联
type JsonPath = number | string | (number | string)[];
type NameSpacedPath = { title: string; path: JsonPath }; // Using title to mimick json schema, but can use namespace
type OneOfPath = { oneOf: JsonPath[] };
type Widget = {
type: string; // Some well known type (e.g., base64file, chat etc.)
[key: string]: JsonPath | NameSpacedPath | OneOfPath;
};
可用的小部件
目前用户只能手动指定两个小部件:
- 文件上传小部件
- 聊天历史小部件
请参阅下面有关这些小部件的更多信息。
游乐场用户界面上的所有其他小部件都是根据Runnable的配置模式由用户界面自动创建和管理的。当你创建可配置的Runnables时,游乐场应该为你创建适当的小部件来控制行为。
文件上传小部件
允许在UI playground中创建一个文件上传输入,用于上传以base64编码字符串形式上传的文件。这里是完整的示例。
代码片段:
try:
from pydantic.v1 import Field
except ImportError:
from pydantic import Field
from langserve import CustomUserType
# ATTENTION: Inherit from CustomUserType instead of BaseModel otherwise
# the server will decode it into a dict instead of a pydantic model.
class FileProcessingRequest(CustomUserType):
"""Request including a base64 encoded file."""
# The extra field is used to specify a widget for the playground UI.
file: str = Field(..., extra={"widget": {"type": "base64file"}})
num_chars: int = 100
示例小部件:
聊天小部件
查看widget示例。
要定义一个聊天小部件,请确保传递 "type": "chat"。
- "input" 是 JSONPath,指向 Request 中包含新输入消息的字段。
- "output" 是 JSONPath 到 Response 中包含新输出消息的字段。
- 如果整个输入或输出应该按原样使用,请不要指定这些字段(例如,如果输出是聊天消息列表)。
这是一个片段:
class ChatHistory(CustomUserType):
chat_history: List[Tuple[str, str]] = Field(
...,
examples=[[("human input", "ai response")]],
extra={"widget": {"type": "chat", "input": "question", "output": "answer"}},
)
question: str
def _format_to_messages(input: ChatHistory) -> List[BaseMessage]:
"""Format the input to a list of messages."""
history = input.chat_history
user_input = input.question
messages = []
for human, ai in history:
messages.append(HumanMessage(content=human))
messages.append(AIMessage(content=ai))
messages.append(HumanMessage(content=user_input))
return messages
model = ChatOpenAI()
chat_model = RunnableParallel({"answer": (RunnableLambda(_format_to_messages) | model)})
add_routes(
app,
chat_model.with_types(input_type=ChatHistory),
config_keys=["configurable"],
path="/chat",
)
示例小部件:
你也可以直接指定一个消息列表作为你的参数,如下面的代码片段所示:
prompt = ChatPromptTemplate.from_messages(
[
("system", "You are a helpful assisstant named Cob."),
MessagesPlaceholder(variable_name="messages"),
]
)
chain = prompt | ChatAnthropic(model="claude-2.1")
class MessageListInput(BaseModel):
"""Input for the chat endpoint."""
messages: List[Union[HumanMessage, AIMessage]] = Field(
...,
description="The chat messages representing the current conversation.",
extra={"widget": {"type": "chat", "input": "messages"}},
)
add_routes(
app,
chain.with_types(input_type=MessageListInput),
path="/chat",
)
查看此示例文件以获取示例。
启用 / 禁用端点 (LangServe >=0.0.33)
您可以在为给定链添加路由时启用/禁用暴露的端点。
如果您想确保在将langserve升级到新版本时永远不会获得新的端点,请使用enabled_endpoints
。
启用:下面的代码将仅启用invoke
、batch
和相应的config_hash
端点变体。
add_routes(app, chain, enabled_endpoints=["invoke", "batch", "config_hashes"], path="/mychain")
禁用:下面的代码将禁用该链的playground
add_routes(app, chain, disabled_endpoints=["playground"], path="/mychain")