《LangChain简明教程》系列文章目录
- LangChain简明教程(1)
- LangChain简明教程(2)
- LangChain简明教程(3)
- LangChain简明教程(4)
- LangChain简明教程(5)
- LangChain简明教程(6)
- LangChain简明教程(7)
- LangChain简明教程(8)
- LangChain简明教程(9)
- LangChain简明教程(10)
使用 LangServe 部署
LangServe 帮助开发者将 LangChain 的可运行对象(runnables)和链(chains)部署为 REST API。这个库与 FastAPI 集成,并使用 pydantic 进行数据验证。此外,它还提供了一个客户端,可用于调用部署在服务器上的 runnables,同时在 LangChainJS 中也提供了 JavaScript 客户端。
功能特性
- 自动推断输入输出 Schema:从 LangChain 对象中自动推断输入和输出的格式定义,并在每次 API 调用时进行校验,提供详细的错误信息。
- API 文档页面:提供基于 JSONSchema 和 Swagger 的 API 接口文档页面。
- 高效的
/invoke
、/batch
和/stream
接口:支持单台服务器上处理大量并发请求。 /stream_log
接口:用于流式传输链(chain)或代理(agent)中的所有(或部分)中间步骤。- 交互式测试页面:位于
/playground
,支持流式输出和查看中间步骤。 - 内置(可选)追踪功能:集成到 LangSmith;只需添加 API 密钥即可开启追踪功能(见后续说明)。
- 基于成熟的开源 Python 库构建:如 FastAPI、Pydantic、uvloop 和 asyncio。
限制
- 不支持客户端回调机制:对于由服务器端触发的事件,目前还不支持客户端回调。
- OpenAPI 文档生成限制:当使用 Pydantic V2 时,不会生成 OpenAPI 文档。FastAPI 不支持混合使用 Pydantic V1 和 V2 的命名空间。详见后续相关章节。
启动和调用 API
使用 LangChain CLI 快速启动一个 LangServe 项目。要使用 langchain CLI,请确保已经安装了最新版本的 langchain-cli
。可以通过以下命令安装:
pip install -U langchain-cli
使用 LangChain 模板快速启动 LangServe 实例。
langchain app new ../path/to/directory
以下是一个服务器示例,它部署了一个 OpenAI 的聊天模型、一个 Anthropic 的聊天模型,以及一个使用 Anthropic 模型来讲某个主题相关笑话的链(chain)。
#!/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(),
path="/openai",
)
add_routes(
app,
ChatAnthropic(),
path="/anthropic",
)
model = ChatAnthropic()
prompt = ChatPromptTemplate.from_template("tell me a joke about {topic}")
add_routes(
app,
prompt | model,
path="/chain",
)
if __name__ == "__main__":
import uvicorn
uvicorn.run(app, host="localhost", port=8000)
一旦部署了上面的服务器,可以使用以下命令查看生成的 OpenAPI 文档:
curl localhost:8000/docs
注意,要有 /docs
。
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/chain/")
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/runnables/remote";
const chain = new RemoteRunnable({
url: `http://localhost:8000/chain/invoke/`,
});
const result = await chain.invoke({
topic: "cats",
});
用 Python 的 requests
调用:
import requests
response = requests.post(
"http://localhost:8000/chain/invoke/",
json={'input': {'topic': 'cats'}}
)
response.json()
也可以用 curl:
curl --location --request POST 'http://localhost:8000/chain/invoke/' \
--header 'Content-Type: application/json' \
--data-raw '{
"input": {
"topic": "cats"
}
}'
如下代码将一个 LangChain 的 Runnable 对象通过 FastAPI 应用(app)暴露为一组 REST API 接口,并指定访问路径为 /my_runnable。
...
add_routes(
app,
runnable,
path="/my_runnable",
)
使用 add_routes(...)
函数(来自 langserve),自动为给定的 runnable
添加多个标准的 API 路由。
参数说明:
- app: 一个 FastAPI 实例,表示你的 Web 服务应用。
- runnable: 一个 LangChain 的 Runnable 对象,例如一个模型、chain 或 agent。
- path=“/my_runnable”: 指定该 runnable 访问路径的基础 URL。
自动生成的路由包括:
路由 | 方法 | 功能 |
---|---|---|
/my_runnable/invoke | POST | 单个输入调用 runnable |
/my_runnable/batch | POST | 批量输入调用 runnable |
/my_runnable/stream | POST | 流式输出调用结果 |
/my_runnable/stream_log | POST | 流式输出结果和中间步骤 |
/my_runnable/input_schema | GET | 获取 runnable 的输入格式定义(JSON Schema) |
/my_runnable/output_schema | GET | 获取 runnable 的输出格式定义 |
/my_runnable/config_schema | GET | 获取 runnable 的配置格式定义 |
/my_runnable/playground | GET | 提供一个交互式 UI 页面用于测试 runnable |
你可以在 /my_runnable/playground
找到一个交互式测试页面。这个页面提供了一个简单的 UI 界面,用于配置并调用你的 runnable,支持流式输出和查看中间步骤。
适用于客户端与服务器的安装 langserve 的方法:
pip install "langserve[all]"
或者使用以下命令安装依赖:
- 用于客户端:
pip install "langserve[client]"
- 用于服务器端:
pip install "langserve[server]"
如果需要为服务器添加身份验证功能,请参考 FastAPI 的安全文档 和 中间件文档。
使用以下命令部署到 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
GCP Cloud Run 是 Google Cloud Platform (GCP) 提供的一项完全托管的无服务器计算服务,它允许运行由 HTTP 请求触发的容器化应用程序。使用 Cloud Run,可以轻松地部署、管理和扩展应用,而无需担心底层基础设施的管理。这意味着开发者可以专注于编写代码和业务逻辑,而不必操心服务器配置、维护以及自动伸缩等运维问题。
GCP Cloud Run 是 Google Cloud Platform 提供的一项特定服务,它依赖于 Google 的基础架构和服务体系。不能在国内其他云服务商提供的云上部署。如果需要,可以选择该云服务商提供的类似服务。
LangServe 对 Pydantic 2 提供了有限的支持。在使用 Pydantic V2 时,不会为 invoke / batch / stream / stream_log 等接口生成 OpenAPI 文档。FastAPI 目前不支持混合使用 Pydantic V1 和 V2 的命名空间。而 LangChain 在 Pydantic V2 中使用的是 V1 兼容的命名空间。所以,要确保与 LangChain 的兼容性。
除了这些限制之外,API 接口、Playground 页面以及其他功能都能如你所愿。
处理文件
LLM 应用程序通常会处理文件。实现文件处理可以采用不同的架构方式,从高层次来看包括:
- 文件可以通过一个专用端点(一个具体的 URL 或接口路径)上传,并通过另一个端点进行处理。
- 文件既可以以值的形式上传(即文件的字节数据),也可以以引用形式上传(例如指向 S3 的 URL)。
- 处理端点可以是阻塞式的,也可以是非阻塞式的。
- 如果需要大量处理,可以将任务卸载到专门的进程池中。
应当根据应用场景选择合适的架构。假设通过值的方式将文件上传给某个 runnable,应使用 base64 编码来传输文件(当前尚不支持 multipart/form-data 格式)。
以下是一个示例,展示如何使用 base64 编码将文件发送给远程的 runnable。请注意,你也可以通过引用方式上传文件(如 S3 URL),或将文件以 multipart/form-data 形式上传到一个专用端点进行处理。
所有 runnable 都定义了输入和输出类型。可以通过 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_schema=int,
)
add_routes(app, runnable)
如果将数据反序列化为 Pydantic 模型,而不是等效的字典(dict)表示形式,可以继承 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
add_routes(app, RunnableLambda(func), path="/foo")
Playground 允许从后端为 runnable 定义自定义控件(widget)。一个控件是在字段级别上指定的,并作为输入类型 JSON Schema 的一部分进行传输。一个控件必须包含一个名为 type
的键,其值必须是已知控件列表中的一个。其他控件的键将对应一些值,这些值描述了 JSON 对象中的路径。
通用 Schema:
type JsonPath = number | string | (number | string)[];
type NameSpacedPath = { title: string; path: JsonPath }; // Using title to mimic 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;
};
允许在 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