Skip to content

Commit

Permalink
examples: update io-descriptor
Browse files Browse the repository at this point in the history
  • Loading branch information
lycheel1 committed Apr 26, 2024
1 parent 6f79391 commit 421942e
Show file tree
Hide file tree
Showing 3 changed files with 47 additions and 36 deletions.
51 changes: 30 additions & 21 deletions examples/io-descriptors/README.md
@@ -1,19 +1,21 @@
# BentoML Input/Output Types Tutorial

A BentoML service supports a wide range of data types when creating a Service API. The data types can be catagorized as follows:
BentoML supports a wide range of data types when creating a Service API. The data types can be catagorized as follows:
- Python Standards: `str`, `int`, `float`, `list`, `dict` etc.
- Pydantic field types: see [Pydantic types documentation](https://field-idempotency--pydantic-docs.netlify.app/usage/types/).
- ML specific types: `nummpy.ndarray`, `torch.Tensor` , `tf.Tensor` for tensor data, `pd.DataFrame` for tabular data, `PIL.Image.Image` for
Image data, and `pathlib.Path` for files such as audios, images, and pdfs.

When creating a Bentoml service, you should use Python's type annotations to define the expected input and output types for each API endpoint. This
step can not only helps validate the data against the specified schema, but also enhances the clarity and readability of your code. Type annotations play
an important role in generating the BentoML API, client, and service UI components, ensuring a consitent and predictable interaction with the Service.
When creating a Bentoml Service, you should use Python's type annotations to define the expected input and output types for each API endpoint. This
step can not only help validate the data against the specified schema, but also enhances the clarity and readability of your code. Type annotations play
an important role in generating the BentoML API, client, and Service UI components, ensuring a consitent and predictable interaction with the Service.

You can also use `pydantic.Field` to set additional information about servicee parameters, such as default values and descriptions. This improves the API's
usability and provides basic documentation. See the following examples for details.
You can also use `pydantic.Field` to set additional information about service parameters, such as default values and descriptions. This improves the API's
usability and provides basic documentation.

## Running this example
In this tutorial, you will learn how to set different input and output types for BentoML Services.

## Installing Dependencies

Let's start with the environment. We recommend using virtual environment for better package handling.

Expand All @@ -23,7 +25,8 @@ source io-descriptors-example/bin/activate
pip install -r requirements.txt
```

7 different API services are implemented within this example, with diversed input/output types. When running, you should specified the class name of the service
## Running a Service
7 different API Services are implemented in `service.py`, with diversed input/output types. When running, you should specified the class name of the Service
you'd like to run inside `bentofile.yaml`.

```yaml
Expand All @@ -32,25 +35,25 @@ include:
- "service.py"
```

In the above configuration through `bentofile.yaml`, we're running the `AudioSpeedUp` service, which you can find on line 62 of `service.py`. When running a different
service, simply replace `AudioSpeedUp` with the class name of the service.
In the above configuration through `bentofile.yaml`, we're running the `AudioSpeedUp` Service, which you can find on line 62 of `service.py`. When running a different
Service, simply replace `AudioSpeedUp` with the class name of the Service.

For example, if you want to run the first service `ImageResize`, you can configure the `bentofile.yaml` as follows:
For example, if you want to run the first Service `ImageResize`, you can configure the `bentofile.yaml` as follows:

```yaml
service: "service.py:ImageResize"
include:
- "service.py"
```

After you finished configuring `bentofile.yaml`, run `bentoml serve .` to deploy the service locally. You can then interact with the auto-generated swagger UI to play
After you finished configuring `bentofile.yaml`, run `bentoml serve .` to deploy the Service locally. You can then interact with the auto-generated swagger UI to play
around with each different API endpoints.

## Different data types

### Standard Python types

The following demonstrates a simple addtion service, with both inputs and output as float parameters. You can
The following demonstrates a simple addtion Service, with both inputs and output as float parameters. You can
obviously change the type annotation to `int`, `str` etc. to get familiar with the interaction between type
annotaions and the auto-generated Swagger UI when deploying locally.\

Expand Down Expand Up @@ -84,19 +87,20 @@ class AppendStringToFile:
return txt_file
```

Within `service.py`, example API services with 4 different file types are implemented (audio, image, text file, and pdf file). The functionality of each service
is quite simple and self-explainatory.
Within `service.py`, example API Services with 4 different file types are implemented (audio, image, text file, and pdf file). The functionality of each Service
is quite simple and self-explanatory.

Notice that for class `ImageResize`, two different API endpoints are implemented. This is because BentoML can support images parameters directly through
`PIL.Image.Image`, which means that image objects can be directly passed through clients, instead of a file object.

The last two services are examples of having `numpy.ndarray` or `pandas.DataFrame` as input parameters. Since they all work quite similarly with the above examples,
we will not specifically explain them in this tutorial. You can try to write revise the service with `torch.Tensor` as input to check your understanding.
The last two Services are examples of having `numpy.ndarray` or `pandas.DataFrame` as input parameters. Since they all work quite similarly with the above examples,
we will not specifically explain them in this tutorial. You can try to write revise the Service with `torch.Tensor` as input to check your understanding.

To serve the these examples locally, run `bentoml serve .`

```bash
bentoml serve .
$ bentoml serve .

2024-03-22T19:25:24+0000 [INFO] [cli] Starting production HTTP BentoServer from "service:ImageResize" listening on http://localhost:3000 (Press CTRL+C to quit)
```

Expand All @@ -106,10 +110,15 @@ You may also send request with `curl` command or any HTTP client, e.g.:

```bash
curl -X 'POST' \
'http://0.0.0.0:3000/classify_generated_texts' \
'http://localhost:3000/transpose' \
-H 'accept: application/json' \
-H 'Content-Type: text/plain' \
-d 'I have an idea!'
-H 'Content-Type: application/json' \
-d '{
"tensor": [
[0, 1, 2, 3],
[4, 5, 6, 7]
]
}'
```

## Deploy to BentoCloud
Expand Down
1 change: 1 addition & 0 deletions examples/io-descriptors/requirements.txt
Expand Up @@ -5,3 +5,4 @@ torch
accelerate
pydub
pdf2img
pandas
31 changes: 16 additions & 15 deletions examples/io-descriptors/service.py
Expand Up @@ -12,26 +12,24 @@
from bentoml.validators import DataframeSchema
from bentoml.validators import DType

# PROMPT_TEMPLATE = """<image>\nUSER: What's the content of the image?\nASSISTANT:"""


@bentoml.service()
class ImageResize:
@bentoml.api()
def generate(self, image: Image, height: str = "64", width: str = "64") -> Image:
size = int(height), int(width)
return image.resize(size, im.ANTIALIAS)
def generate(self, image: Image, height: int = 64, width: int = 64) -> Image:
size = height, width
return image.resize(size, im.LANCZOS)

@bentoml.api()
def generate_with_path(
self,
image: t.Annotated[Path, bentoml.validators.ContentType("image/jpeg")],
height: str = "64",
width: str = "64",
height: int = 64,
width: int = 64,
) -> Image:
size = int(height), int(width)
size = height, width
image = im.open(image)
return image.resize(size, im.ANTIALIAS)
return image.resize(size, im.LANCZOS)


@bentoml.service()
Expand All @@ -46,12 +44,13 @@ class AppendStringToFile:
@bentoml.api()
def append_string_to_eof(
self,
context: bentoml.Context,
txt_file: t.Annotated[Path, bentoml.validators.ContentType("text/plain")],
input_string: str,
) -> t.Annotated[Path, bentoml.validators.ContentType("text/plain")]:
with open(txt_file, "a") as file:
with open(output_path, "a") as file:
file.write(input_string)
return txt_file
return output_path


@bentoml.service()
Expand All @@ -72,15 +71,17 @@ class AudioSpeedUp:
@bentoml.api()
def speed_up_audio(
self,
context: bentoml.Context,
audio: t.Annotated[Path, bentoml.validators.ContentType("audio/mpeg")],
velocity: float,
) -> t.Annotated[Path, bentoml.validators.ContentType("audio/mp3")]:
from pydub import AudioSegment

sound = AudioSegment.from_file(audio) # type:
import os
output_path = os.path.join(context.temp_dir, "output.mp3")
sound = AudioSegment.from_file(audio)
sound = sound.speedup(velocity)
sound.export("output.mp3", format="mp3")
return Path("output.mp3")
sound.export(output_path, format="mp3")
return Path(output_path)


@bentoml.service()
Expand Down

0 comments on commit 421942e

Please sign in to comment.