挂载 ASGI 应用¶
ASGI (Asynchronous Server Gateway Interface) 是 WSGI (Web Server Gateway Interface) 的精神继承者,旨在为支持异步的 Python Web 服务器、框架和应用提供标准接口。ASGI 支持异步请求处理,允许多个请求同时处理,这使其适用于实时 Web 应用,例如 WebSockets、长轮询等。
BentoML 的服务器在 ASGI Web 服务层中运行 Service API,暴露 REST 端点用于推理 API(例如 POST /summarize
)和用于监控的常见基础设施 API(例如 GET /metrics
)。这个 ASGI 原生 Web 服务层允许直接挂载现有的 ASGI 应用,使其能够与 BentoML Services 并行服务。
本文档解释了如何将 ASGI 应用挂载到 BentoML Service 上。
为什么要挂载 ASGI 应用¶
将 ASGI 应用(例如使用 FastAPI 构建的应用)挂载到 BentoML Service 上有多种优势:
功能扩展:它使您能够通过附加的 Web 功能来扩展机器学习服务,例如用于数据处理、用户管理或提供静态文件和 Web 用户界面的自定义 API,这些功能与模型服务没有直接关系。
自定义认证和授权:通过集成 ASGI 应用,您可以实现高级的认证和授权机制,根据应用的特定需求定制安全措施。
API 文档:使用 FastAPI 等工具,您可以自动获得交互式 API 文档,使用户更容易理解和使用 API。
将 BentoML 与 ASGI 框架集成¶
BentoML 提供了与不同 ASGI 框架的无缝集成,使您能够在提供 ML 模型的同时,也提供自定义 Web 应用逻辑,例如异步操作、实时数据处理和复杂的 Web 应用功能。
集成 ASGI 框架时,您可以使用 bentoml.get_current_service()
来检索当前的 BentoML Service 实例。当您需要从 ASGI 应用路由内部访问 BentoML Service 实例,或使用依赖注入模式将 Service 实例注入 ASGI 应用路由时,这非常有用。
请参阅以下示例了解详情。
FastAPI¶
FastAPI 是一个基于 ASGI 的 Web 框架,用于构建 API,可以处理异步请求。要将 FastAPI 应用与 BentoML Service 集成,您可以在 Service 内部或外部定义 FastAPI 路由,如下所示。
注意
确保您已通过运行 pip install fastapi
安装 FastAPI。请参阅 FastAPI 文档 了解更多信息。
from fastapi import FastAPI, Depends
import bentoml
app = FastAPI()
@bentoml.service
@bentoml.asgi_app(app, path="/v1")
class MyService:
name = "MyService"
@app.get('/hello')
def hello(self): # Inside service class, use `self` to access the service
return f"Hello {self.name}"
@app.get("/hello1")
async def hello(service: MyService = Depends(bentoml.get_current_service)):
# Outside service class, use `Depends` to get the service
return f"Hello {service.name}"
具体而言,按照以下步骤挂载 FastAPI
使用
FastAPI()
创建 FastAPI 应用。使用
@bentoml.asgi_app
装饰器将 FastAPI 应用挂载到 BentoML Service,使它们可以一起提供服务。设置path
参数以自定义前缀路径。使用
@app.get("/<route-name>")
在 Service 类内部或外部定义 FastAPI 路由。类内部:使用
self
访问 Service 实例的属性和方法。类外部:使用 FastAPI 的依赖注入系统(
Depends
)将 BentoML Service 实例注入路由函数。在上面的代码中,hello1
路由使用Depends(bentoml.get_current_service)
注入MyService
实例,允许路由访问 Service 的属性和方法。
在 FastAPI 路由内,添加您想要的实现逻辑。本示例使用 Service 的名称返回问候消息。
注意
除了 get
,您还可以使用其他操作,如 post
、put
和 delete
。请参阅 FastAPI 文档 了解更多信息。
设计选择:内部 vs. 外部
在 Service 类内部和外部访问 BentoML Service 实例,在构建 Service 逻辑和依赖项以及与之交互方面提供了灵活性。在这些上下文中访问 BentoML Service 实例的差异主要与作用域和预期用例有关。
Service 类内部
直接访问:在定义 BentoML Service 的类中,您可以直接访问
self
,它代表 Service 的实例。这允许您直接访问其属性和方法,而无需注入任何依赖。这是在 Service 自身定义中使用其功能最直接的方式。上下文使用:在类内部访问 Service 实例通常用于定义 Service 的内部逻辑,例如设置端点、执行模型操作以及处理与 Service 主要功能直接相关的请求。
Service 类外部
依赖注入:在类外部访问 BentoML Service 实例通常需要依赖注入机制,例如 FastAPI 中的
Depends
函数。当您想在项目的其他部分使用 Service 实例时,这种方法是必要的。模块化和解耦设计:这种方法允许 BentoML 项目的不同组件与 Service 交互,而无需紧密集成到其类定义中。例如,您的 ML 逻辑可以封装在 BentoML Service 中,而其他方面,如自定义认证、补充数据处理或附加的 REST 端点,可以在外部进行管理,但仍能按需与 Service 交互。
以下是将 FastAPI 挂载到 你好世界 中 Summarization Service 上的一个更实际的示例。它通过分别从类内部和外部访问 Service,使用 FastAPI 定义了两个额外的端点。
from __future__ import annotations
import bentoml
from transformers import pipeline
from fastapi import FastAPI, Depends
EXAMPLE_INPUT = "Breaking News: In an astonishing turn of events, the small town of Willow Creek has been taken by storm as local resident Jerry Thompson's cat, Whiskers, performed what witnesses are calling a 'miraculous and gravity-defying leap.' Eyewitnesses report that Whiskers, an otherwise unremarkable tabby cat, jumped a record-breaking 20 feet into the air to catch a fly. The event, which took place in Thompson's backyard, is now being investigated by scientists for potential breaches in the laws of physics. Local authorities are considering a town festival to celebrate what is being hailed as 'The Leap of the Century."
# Create a FastAPI app instance
app = FastAPI()
@bentoml.service(
resources={"cpu": "2"},
traffic={"timeout": 10},
)
@bentoml.asgi_app(app, path="/v1")
class Summarization:
def __init__(self) -> None:
self.pipeline = pipeline('summarization')
# Define a name attribute
name = "MyService"
# The original Service API endpoint for text summarization
@bentoml.api
def summarize(self, text: str = EXAMPLE_INPUT) -> str:
result = self.pipeline(text)
return result[0]['summary_text']
# Access the Service instance inside the class
@app.get("/hello-inside")
def hello(self):
# Add other logic here if needed
return f"Hello {self.name}. You can access the Service instance inside the class."
# Access the Service instance outside the class
@app.get("/hello-outside")
async def hello(service: MyService = Depends(bentoml.get_current_service)):
# Add other logic here if needed
return f"Hello {service.name}. You can access the Service instance outside the class."
启动 BentoML Service 后,您可以通过 https://:3000/ 访问它,您将发现暴露了两个额外的端点 hello-inside
和 hello-outside
。

通过发送 GET
请求,您可以从这两个端点接收相应的输出。
Service 类内部的 FastAPI 路由

Service 类外部的 FastAPI 路由

Quart¶
Quart 是一个用于 Python 的异步 Web 框架,使您能够在 Web 应用中使用 async/await 特性来处理大量的并发连接。
以下是集成 Quart 与 BentoML 的示例。
注意
确保您已通过运行 pip install quart
安装 Quart。请参阅 Quart 文档 了解更多信息。
from quart import Quart
app = Quart(__name__)
@app.get("/hello")
async def hello_world():
service = bentoml.get_current_service()
return f"Hello, {service.name}"
@bentoml.service
@bentoml.asgi_app(app, path="/v1")
class MyService:
name = "MyService"
具体而言,按照以下步骤挂载 Quart
使用
Quart()
创建 Quart 应用。使用
@bentoml.asgi_app
装饰器将 Quart 应用挂载到 BentoML Service,使它们可以一起提供服务。设置path
参数以自定义前缀路径。使用
@app.get(/"<route-name>")
在 Service 类外部定义 Quart 路由。使用bentoml.get_current_service()
注入MyService
实例,允许路由访问 Service 的属性和方法。在 Quart 路由内,添加您想要的实现逻辑。本示例使用 Service 的名称返回问候消息。
注意
除了 get
,您还可以使用其他操作,如 post
、put
和 delete
。请参阅 Quart 文档 了解更多信息。
以下是将 Quart 挂载到 你好世界 中 Summarization Service 上的一个更实际的示例。它定义了一个额外的端点 hello
。
from __future__ import annotations
import bentoml
from transformers import pipeline
from quart import Quart
EXAMPLE_INPUT = "Breaking News: In an astonishing turn of events, the small town of Willow Creek has been taken by storm as local resident Jerry Thompson's cat, Whiskers, performed what witnesses are calling a 'miraculous and gravity-defying leap.' Eyewitnesses report that Whiskers, an otherwise unremarkable tabby cat, jumped a record-breaking 20 feet into the air to catch a fly. The event, which took place in Thompson's backyard, is now being investigated by scientists for potential breaches in the laws of physics. Local authorities are considering a town festival to celebrate what is being hailed as 'The Leap of the Century."
# Create a Quart app instance
app = Quart(__name__)
@app.get("/hello")
async def hello_world():
service = bentoml.get_current_service()
# Add other logic here if needed
return f"Hello, {service.name}"
@bentoml.service(
resources={"cpu": "2"},
traffic={"timeout": 10},
)
@bentoml.asgi_app(app, path="/v1")
class Summarization:
def __init__(self) -> None:
self.pipeline = pipeline('summarization')
# Define a name attribute
name = "MyService"
# The original Service API endpoint for text summarization
@bentoml.api
def summarize(self, text: str = EXAMPLE_INPUT) -> str:
result = self.pipeline(text)
return result[0]['summary_text']
启动 BentoML Service 后,您可以通过 https://:3000/ 访问它,您可以与暴露的端点 hello
进行交互。例如:
$ curl https://:3000/v1/hello
Hello, MyService
注意
与 FastAPI 不同,Quart 不原生支持 OpenAPI 规范,因此端点不会显示在 Swagger UI 上。您可以使用其他方式与之通信,例如 curl
。
自定义前缀路径¶
将 ASGI 工具挂载到 BentoML Service 上时,可以通过设置前缀来自定义路由路径。这对于组织 API 端点以及简化路由和命名空间管理非常有用。
要设置前缀路径,只需在装饰器 @bentoml.asgi_app
中设置 path
参数即可。这是一个 FastAPI 示例:
from fastapi import FastAPI, Depends
import bentoml
app = FastAPI()
@bentoml.service
@bentoml.asgi_app(app, path="/fastapi") # Add the prefix here
class MyService:
name = "MyService"
@app.get('/hello') # This endpoint should be requested via "/fastapi/hello"
def hello(self):
return f"Hello {self.name}"
通过指定 path="/fastapi"
,整个 FastAPI 应用将在此前缀下提供服务。这意味着在 FastAPI 应用中定义的所有路由都将通过 /fastapi
访问。在此示例中,启动此 BentoML Service 后,您应该与 /fastapi/hello
端点进行交互。
添加自定义 ASGI 中间件¶
add_asgi_middleware
是 BentoML 提供的一个 API,用于应用自定义 ASGI 中间件。中间件作为一个层来处理请求和响应,允许您基于特定条件操纵它们或执行附加操作。它通常用于实现安全措施、自定义头部、管理 CORS、压缩响应等。
示例用法
from __future__ import annotations
import bentoml
from transformers import pipeline
from starlette.middleware.trustedhost import TrustedHostMiddleware
@bentoml.service(
resources={"cpu": "2"},
traffic={"timeout": 10},
)
class Summarization:
def __init__(self) -> None:
self.pipeline = pipeline('summarization')
@bentoml.api
def summarize(self, text: str) -> str:
result = self.pipeline(text)
return result[0]['summary_text']
# Add TrustedHostMiddleware to ensure the Service only accepts requests from certain hosts
Summarization.add_asgi_middleware(TrustedHostMiddleware, allowed_hosts=['example.com', '*.example.com'])
此示例确保 Summarization
Service 只接受来自指定主机的请求,并防止主机头攻击。然后,您可以通过在请求中手动指定 Host
头部与 Service 交互:
curl -H "Host: example.com" https://:3000
注意
或者,您可以编辑 hosts
文件,将 example.com
映射到 127.0.0.1
(localhost),然后访问 http://example.com:3000/
。
虽然 add_asgi_middleware
用于向 BentoML 用于提供 API 的 ASGI 应用添加中间件,但 @bentoml.asgi_app
用于将整个 ASGI 应用集成到 BentoML Service 中。这适用于将具有自身路由逻辑的完整 Web 应用,如 FastAPI 或 Quart 应用,直接添加到您的 BentoML Service 旁边。
通过 add_asgi_middleware
添加的中间件适用于整个 ASGI 应用,包括 BentoML Service 和任何已挂载的 ASGI 应用。这确保了整个应用中所有请求的一致处理,无论它们是针对 BentoML Services 还是其他组件。