🇬🇧 English
You can write a Python function. But only your own program can use it, and only while that program is running. A web service is different. It is a program that stays on and waits. Other programs send it a question over the network, and it sends back an answer. A phone app, a web page or a friend's computer can all ask it.
BentoML is a Python library that turns a class into a web service. People use it mostly to put AI models online. The model is loaded inside the class, and every question from outside is handed to the model. BentoML is free.
The eight steps use no AI model. A model is big and slow, and it would hide the part you need to learn. We use a small noodle shop instead. Its model
is a menu with three prices. What BentoML does with this menu, it does in the same way with a real model, and after step 7 two short tutorials put real models in that place.
The Python on this page is basic: a dictionary, a function, and a class with methods. Each new thing is explained where it first appears.
On this page you will:
- write one small function;
- put it inside a class, so that BentoML can serve it;
- start the service, and ask it from another program;
- send it a wrong question, and read the error;
- load data one time, when the service starts;
- pack the service into one box, called a Bento.
🇹🇭 ไทย
คุณเขียนฟังก์ชัน Python ได้ แต่มีแค่โปรแกรมของคุณเองที่ใช้มันได้ และใช้ได้เฉพาะตอนที่โปรแกรมนั้นกำลังรันอยู่ เว็บเซอร์วิสไม่เหมือนกัน มันคือโปรแกรมที่เปิดค้างไว้และรอ โปรแกรมอื่นส่งคำถามมาหามันผ่านเครือข่าย แล้วมันก็ส่งคำตอบกลับไป แอปบนมือถือ หน้าเว็บ หรือคอมพิวเตอร์ของเพื่อน ล้วนถามมันได้
BentoML คือไลบรารี Python ที่เปลี่ยนคลาสให้เป็นเว็บเซอร์วิส คนส่วนใหญ่ใช้มันเพื่อนำโมเดล AI ขึ้นออนไลน์ โมเดลถูกโหลดไว้ในคลาส และคำถามทุกข้อจากข้างนอกจะถูกส่งต่อให้โมเดล BentoML ใช้ได้ฟรี
แปดขั้นของหน้านี้ไม่ใช้โมเดล AI โมเดลมีขนาดใหญ่และทำงานช้า และจะบังส่วนที่คุณต้องเรียนรู้ เราจึงใช้ร้านก๋วยเตี๋ยวเล็ก ๆ แทน โมเดล
ของร้านคือเมนูที่มีราคาสามรายการ สิ่งที่ BentoML ทำกับเมนูนี้ มันก็ทำแบบเดียวกันกับโมเดลจริง และหลังขั้นที่ 7 มีบทเสริมสั้น ๆ สองบทที่ใส่โมเดลจริงลงในที่นั้น
Python ในหน้านี้เป็นระดับพื้นฐาน คือดิกชันนารี ฟังก์ชัน และคลาสที่มีเมธอด ของใหม่แต่ละอย่างมีคำอธิบายตรงที่มันปรากฏครั้งแรก
ในหน้านี้คุณจะ:
- เขียนฟังก์ชันเล็ก ๆ หนึ่งตัว
- ย้ายมันเข้าไปในคลาส เพื่อให้ BentoML เปิดเป็นเซอร์วิสได้
- เปิดเซอร์วิส แล้วถามมันจากอีกโปรแกรมหนึ่ง
- ส่งคำถามที่ผิดไปให้ แล้วอ่านข้อผิดพลาด
- โหลดข้อมูลครั้งเดียว ตอนที่เซอร์วิสเริ่มทำงาน
- แพ็กเซอร์วิสลงในกล่องใบเดียว ที่เรียกว่า Bento
🇨🇳 中文
你会写 Python 函数。但是只有你自己的程序能用它,而且只在那个程序运行的时候才能用。网络服务不一样。它是一个一直开着、一直在等的程序。别的程序通过网络把问题发给它,它再把答案发回去。手机应用、网页、朋友的电脑,都可以向它提问。
BentoML 是一个 Python 库,它能把一个类变成网络服务。人们主要用它把 AI 模型放到网上:模型在类里面加载,外面发来的每个问题都交给模型。BentoML 是免费的。
这八个步骤不用 AI 模型。模型又大又慢,会挡住你要学的那一部分。我们用一家小面馆来代替。它的模型
就是一张有三个价格的菜单。BentoML 怎样处理这张菜单,也就怎样处理真正的模型;第 7 步之后有两个简短的教程,把真正的模型放到那个位置。
这一页的 Python 都很基础:一个字典、一个函数、一个带方法的类。每样新东西第一次出现的地方都有解释。
在这一页里,你会:
- 写一个小函数;
- 把它放进一个类,让 BentoML 能把它变成服务;
- 启动服务,再从另一个程序向它提问;
- 给它发一个错误的问题,读一读报错;
- 在服务启动时,只加载一次数据;
- 把服务装进一个盒子,这个盒子叫 Bento。
| Fileไฟล์文件 | What it isคืออะไร它是什么 | Where you use itใช้ตรงไหน用在哪里 |
|---|---|---|
| price.py | One function that works out a price.ฟังก์ชันหนึ่งตัวที่คำนวณราคา一个算价格的函数。 | Step 2ขั้นที่ 2第 2 步 |
| service.py | The same function as a service.ฟังก์ชันเดิม ในรูปของเซอร์วิส同一个函数,变成了服务。 | Steps 3 to 6ขั้นที่ 3 ถึง 6第 3 步到第 6 步 |
| ask.py, ask_http.py | Two small programs that ask the service.โปรแกรมเล็ก ๆ สองตัวที่ถามเซอร์วิส两个向服务提问的小程序。 | Step 5ขั้นที่ 5第 5 步 |
| wrong.py | A program that asks two wrong questions.โปรแกรมที่ถามคำถามผิดสองข้อ一个问了两个错误问题的程序。 | Step 6ขั้นที่ 6第 6 步 |
| menu.json | The menu, as a file of its own.เมนู ที่แยกออกมาเป็นไฟล์ของตัวเอง菜单,单独放在一个文件里。 | Steps 7 and 8ขั้นที่ 7 และ 8第 7 步和第 8 步 |
| shop.py | A better service. It reads the menu when it starts.เซอร์วิสที่ดีขึ้น มันอ่านเมนูตอนเริ่มทำงาน更好的服务。它在启动时读取菜单。 | Steps 7 and 8ขั้นที่ 7 และ 8第 7 步和第 8 步 |
| ask_shop.py | A program that asks the better service.โปรแกรมที่ถามเซอร์วิสที่ดีขึ้น一个向更好的服务提问的程序。 | Step 7ขั้นที่ 7第 7 步 |
| judge.py, ask_judge.py | A service with a real model in it, and a program that asks it.เซอร์วิสที่มีโมเดลจริงอยู่ข้างใน และโปรแกรมที่ถามมัน一个里面有真正模型的服务,和一个向它提问的程序。 | Tutorial Aบทเสริม A教程 A |
| talker.py, ask_talker.py | A service that talks to a local LLM, and a program that asks it.เซอร์วิสที่คุยกับ LLM บนเครื่อง และโปรแกรมที่ถามมัน一个和本地 LLM 对话的服务,和一个向它提问的程序。 | Tutorial Bบทเสริม B教程 B |
| webshop.py, ask_webshop.py | A service that sends a web page and reads its form, and a program that asks it.เซอร์วิสที่ส่งหน้าเว็บและอ่านฟอร์มของหน้านั้น และโปรแกรมที่ถามมัน一个发送网页并读取表单的服务,和一个向它提问的程序。 | Tutorial Cบทเสริม C教程 C |
| bentofile.yaml | One line that tells BentoML what to pack.บรรทัดเดียวที่บอก BentoML ว่าจะแพ็กอะไร只有一行,告诉 BentoML 要装什么。 | Step 8ขั้นที่ 8第 8 步 |
| Wordคำ词 | What it means on this pageความหมายในหน้านี้在这一页里的意思 |
|---|---|
| service | A program that stays on, waits for questions and sends back answers.เซอร์วิส: โปรแกรมที่เปิดค้างไว้ รอคำถาม และส่งคำตอบกลับไป服务:一个一直开着、等待问题并发回答案的程序。 |
| to serve | To run a service, so that others can ask it.รันเซอร์วิส เพื่อให้คนอื่นถามมันได้运行一个服务,让别人可以向它提问。 |
| request, reply | A request is one question sent to a service. The reply is what the service sends back.request (คำขอ) คือคำถามหนึ่งข้อที่ส่งไปยังเซอร์วิส reply (คำตอบ) คือสิ่งที่เซอร์วิสส่งกลับมาrequest(请求)是发给服务的一个问题。reply(回复)是服务发回来的东西。 |
| API | One thing that a service can be asked to do. Our first service has one API, named price.สิ่งหนึ่งอย่างที่ขอให้เซอร์วิสทำได้ เซอร์วิสตัวแรกของเรามี API เดียว ชื่อ price可以请服务做的一件事。我们的第一个服务只有一个 API,名叫 price。 |
| class | A group of functions, and the data they share, under one name.คลาส: กลุ่มของฟังก์ชัน พร้อมข้อมูลที่ฟังก์ชันเหล่านั้นใช้ร่วมกัน รวมอยู่ใต้ชื่อเดียว类:一组函数,加上它们共用的数据,放在同一个名字下面。 |
| method | A function that is written inside a class.เมธอด: ฟังก์ชันที่เขียนอยู่ในคลาส方法:写在类里面的函数。 |
| decorator | A line that begins with @, on top of a class or a function. It hands that class or function to some other code. Here that other code is BentoML.เดคอเรเตอร์: บรรทัดที่ขึ้นต้นด้วย @ อยู่เหนือคลาสหรือฟังก์ชัน มันส่งคลาสหรือฟังก์ชันนั้นให้โค้ดอื่น ในหน้านี้โค้ดอื่นนั้นคือ BentoML装饰器:以 @ 开头的一行,写在类或函数的上面。它把这个类或函数交给另一段代码。在这里,那段代码就是 BentoML。 |
| type hint | A note in the code that says what kind of value something is. bowls: int says that bowls is a whole number.ไทป์ฮินต์: โน้ตในโค้ดที่บอกว่าค่าหนึ่งเป็นค่าชนิดไหน bowls: int บอกว่า bowls เป็นจำนวนเต็ม类型提示:代码里的一个标注,说明某个值是哪一种值。bowls: int 说明 bowls 是一个整数。 |
| localhost | The name every computer has for itself. http://localhost:3000 is an address on your own computer.ชื่อที่คอมพิวเตอร์ทุกเครื่องใช้เรียกตัวเอง http://localhost:3000 คือที่อยู่บนคอมพิวเตอร์ของคุณเอง每台电脑对自己的称呼。http://localhost:3000 是你自己电脑上的一个地址。 |
| port | The number after the colon in an address, such as 3000. One computer can run many services, and each one waits at its own port, like the numbered doors of one building.พอร์ต: ตัวเลขหลังเครื่องหมายโคลอนในที่อยู่ เช่น 3000 คอมพิวเตอร์เครื่องเดียวรันเซอร์วิสได้หลายตัว และแต่ละตัวรออยู่ที่พอร์ตของตัวเอง เหมือนประตูที่มีหมายเลขของตึกหลังเดียว端口:地址里冒号后面的数字,比如 3000。一台电脑可以运行很多服务,每个服务在自己的端口上等待,就像一栋楼里编了号的门。 |
| JSON | A way to write data as text. It looks much like a Python dictionary: {"dish": "dumplings", "bowls": 6}.วิธีเขียนข้อมูลเป็นข้อความ หน้าตาคล้ายดิกชันนารีของ Python มาก: {"dish": "dumplings", "bowls": 6}一种把数据写成文字的方式。它看起来很像 Python 的字典:{"dish": "dumplings", "bowls": 6}。 |
| status code | A number at the start of every reply. It says how the request went. 200 means it went well.รหัสสถานะ: ตัวเลขที่อยู่ต้นคำตอบทุกครั้ง มันบอกว่าคำขอเป็นอย่างไร 200 แปลว่าเรียบร้อยดี状态码:每个回复开头的一个数字,说明这次请求的结果。200 表示一切顺利。 |
| Bento | One box that holds a service and the list of everything it needs. A bento is a Japanese lunch box, with a full meal packed inside.กล่องใบเดียวที่บรรจุเซอร์วิสและรายการของทุกอย่างที่มันต้องใช้ เบนโตะคือกล่องข้าวแบบญี่ปุ่น ที่มีอาหารครบมื้ออยู่ข้างใน一个盒子,里面装着一个服务,还有它需要的所有东西的清单。bento(便当)是日本的饭盒,里面装着完整的一餐。 |
🧮 Part 1 — A function · ส่วนที่ 1 — ฟังก์ชัน · 第 1 部分 —— 一个函数
🇬🇧 English
A service starts as ordinary Python. So first you install BentoML, and then you write the function that the service will offer. This part does not use BentoML yet.
🇹🇭 ไทย
เซอร์วิสเริ่มต้นจาก Python ธรรมดา ดังนั้นก่อนอื่นคุณติดตั้ง BentoML แล้วจึงเขียนฟังก์ชันที่เซอร์วิสจะให้บริการ ส่วนนี้ยังไม่ใช้ BentoML
🇨🇳 中文
服务是从普通的 Python 开始的。所以你先安装 BentoML,再写出服务要提供的那个函数。这一部分还用不到 BentoML。
1 Install BentoML · ติดตั้ง BentoML · 安装 BentoML
🇬🇧 English
Make a new folder for this page and open a terminal in it. The first command below makes a virtual environment. That is a separate copy of Python with its own libraries, kept in a folder named venv. It keeps this project apart from your other projects.
The second command installs BentoML into it. BentoML brings other libraries with it. One of them is httpx, which step 5 uses.
On a Mac, write venv/bin/ wherever this page writes venv\Scripts\.
🇹🇭 ไทย
สร้างโฟลเดอร์ใหม่สำหรับหน้านี้ แล้วเปิดเทอร์มินัลในโฟลเดอร์นั้น คำสั่งแรกข้างล่างสร้างสภาพแวดล้อมเสมือน ซึ่งก็คือ Python อีกชุดที่แยกออกมา มีไลบรารีเป็นของตัวเอง เก็บอยู่ในโฟลเดอร์ชื่อ venv มันทำให้โปรเจกต์นี้แยกจากโปรเจกต์อื่นของคุณ
คำสั่งที่สองติดตั้ง BentoML ลงไปในนั้น BentoML พาไลบรารีอื่นมาด้วย หนึ่งในนั้นคือ httpx ซึ่งขั้นที่ 5 ใช้
บนแมค ให้พิมพ์ venv/bin/ ในทุกที่ที่หน้านี้เขียนว่า venv\Scripts\
🇨🇳 中文
为这一页新建一个文件夹,并在里面打开终端。下面第一条命令创建一个虚拟环境,也就是一份单独的 Python,有自己的一套库,放在名叫 venv 的文件夹里。它让这个项目和你别的项目互不影响。
第二条命令把 BentoML 装进去。BentoML 会带来别的库,其中一个是 httpx,第 5 步会用到它。
在 Mac 上,凡是这一页写 venv\Scripts\ 的地方,都要写成 venv/bin/。
🪟 WINDOWS
python -m venv venv
venv\Scripts\python -m pip install bentoml
🍎 MAC · not run by us · · เราไม่ได้รันเอง · · 我们没有运行过
python3 -m venv venv
venv/bin/python -m pip install bentoml
2 price.py: one small function · price.py: ฟังก์ชันเล็ก ๆ หนึ่งตัว · price.py:一个小函数
🇬🇧 English
Save this file in your folder as price.py. It has three parts.
MENU is a dictionary. A dictionary holds pairs: a key, and the value that belongs to it. Here each key is the name of a dish, and its value is the price of one bowl. MENU["dumplings"] gives 40.
price is a function. It takes two values, dish and bowls. It looks up the price of the dish, multiplies it by the number of bowls, and gives the result back with return.
The last two lines call the function and print what it returns.
🇹🇭 ไทย
บันทึกไฟล์นี้ลงในโฟลเดอร์ของคุณในชื่อ price.py มันมีสามส่วน
MENU คือดิกชันนารี ดิกชันนารีเก็บข้อมูลเป็นคู่ คือคีย์ กับค่าที่เป็นของคีย์นั้น ในที่นี้คีย์แต่ละตัวคือชื่ออาหาร และค่าของมันคือราคาหนึ่งชาม MENU["dumplings"] ให้ค่า 40
price คือฟังก์ชัน มันรับค่าสองค่า คือ dish และ bowls มันหาราคาของอาหาร คูณด้วยจำนวนชาม แล้วส่งผลลัพธ์กลับด้วย return
สองบรรทัดสุดท้ายเรียกฟังก์ชัน แล้วพิมพ์สิ่งที่มันส่งกลับมา
🇨🇳 中文
把这个文件存进你的文件夹,取名 price.py。它有三个部分。
MENU 是一个字典。字典里存的是一对一对的东西:一个键,和属于它的值。这里每个键是一道菜的名字,值是一碗的价格。MENU["dumplings"] 得到 40。
price 是一个函数。它接收两个值:dish 和 bowls。它查出这道菜的价格,乘以碗数,再用 return 把结果交回去。
最后两行调用这个函数,并打印它返回的结果。
price.py
# What one bowl of each dish costs.
MENU = {"beef noodles": 60, "fried rice": 50, "dumplings": 40}
def price(dish, bowls):
return MENU[dish] * bowls
print(price("beef noodles", 2))
print(price("fried rice", 3))
🇬🇧 English
Run it:
🇹🇭 ไทย
รันมัน:
🇨🇳 中文
运行它:
venv\Scripts\python price.py
what it printedสิ่งที่มันพิมพ์ออกมา它打印的内容
120
150
🇬🇧 English
Two bowls of beef noodles cost 120, and three bowls of fried rice cost 150. The function works.
But look at who can use it. Only the lines inside price.py can call price. When the file has finished, the function is gone. A phone app cannot ask it for a price. Part 2 changes that.
🇹🇭 ไทย
ก๋วยเตี๋ยวเนื้อสองชามราคา 120 และข้าวผัดสามชามราคา 150 ฟังก์ชันทำงานได้
แต่ลองดูว่าใครใช้มันได้บ้าง มีแค่บรรทัดที่อยู่ใน price.py เท่านั้นที่เรียก price ได้ เมื่อไฟล์รันจบ ฟังก์ชันก็หายไป แอปบนมือถือถามราคาจากมันไม่ได้ ส่วนที่ 2 จะเปลี่ยนเรื่องนี้
🇨🇳 中文
两碗牛肉面 120,三碗炒饭 150。函数没问题。
但是看看谁能用它。只有 price.py 里面的代码能调用 price。文件一运行完,函数就没了。手机应用没法向它问价格。第 2 部分会改变这一点。
🛎️ Part 2 — A service · ส่วนที่ 2 — เซอร์วิส · 第 2 部分 —— 一个服务
🇬🇧 English
Now the same function becomes a service. You change very little code. Then you start the service, ask it from a second program, and see what happens when the question is wrong.
🇹🇭 ไทย
ตอนนี้ฟังก์ชันเดิมจะกลายเป็นเซอร์วิส คุณแก้โค้ดน้อยมาก จากนั้นคุณเปิดเซอร์วิส ถามมันจากโปรแกรมที่สอง และดูว่าเกิดอะไรขึ้นเมื่อคำถามผิด
🇨🇳 中文
现在,同一个函数要变成服务。你只需要改很少的代码。然后你启动服务,从第二个程序向它提问,再看看问题出错时会发生什么。
3 service.py: the function goes into a class · service.py: ย้ายฟังก์ชันเข้าไปอยู่ในคลาส · service.py:把函数放进一个类
🇬🇧 English
Save this file as service.py. Look at the last line first. It is the same line as in price.py. The work has not changed. Everything new is around it, and the table under the file explains each new line.
🇹🇭 ไทย
บันทึกไฟล์นี้ในชื่อ service.py ดูบรรทัดสุดท้ายก่อน มันคือบรรทัดเดียวกับใน price.py งานที่ทำไม่ได้เปลี่ยน ของใหม่ทั้งหมดอยู่รอบ ๆ บรรทัดนั้น และตารางใต้ไฟล์อธิบายบรรทัดใหม่ทีละบรรทัด
🇨🇳 中文
把这个文件存为 service.py。先看最后一行。它和 price.py 里的那一行一模一样。要做的事没有变。新东西都在它的周围,文件下面的表格会逐行解释。
service.py
import bentoml
# What one bowl of each dish costs.
MENU = {"beef noodles": 60, "fried rice": 50, "dumplings": 40}
@bentoml.service
class Shop:
@bentoml.api
def price(self, dish: str, bowls: int) -> int:
return MENU[dish] * bowls
| Lineบรรทัด代码 | What it doesทำอะไร它做什么 |
|---|---|
import bentoml | Brings in the BentoML library, so that this file can use it.นำไลบรารี BentoML เข้ามา เพื่อให้ไฟล์นี้ใช้มันได้把 BentoML 这个库引进来,这个文件才能用它。 |
class Shop: | Starts a class named Shop. A class is a group of functions under one name. BentoML wants a class, because a service can offer more than one function. Step 7 adds a second one.เริ่มคลาสชื่อ Shop คลาสคือกลุ่มของฟังก์ชันที่อยู่ใต้ชื่อเดียว BentoML ต้องการคลาส เพราะเซอร์วิสหนึ่งตัวให้บริการได้มากกว่าหนึ่งฟังก์ชัน ขั้นที่ 7 จะเพิ่มตัวที่สอง开始一个名叫 Shop 的类。类就是放在同一个名字下面的一组函数。BentoML 需要一个类,因为一个服务可以提供不止一个函数。第 7 步会加上第二个。 |
@bentoml.service | A decorator. It sits on top of the class and tells BentoML: this class is a service.เดคอเรเตอร์ มันอยู่เหนือคลาส และบอก BentoML ว่าคลาสนี้คือเซอร์วิส一个装饰器。它写在类的上面,告诉 BentoML:这个类是一个服务。 |
@bentoml.api | Another decorator. It sits on top of a method and tells BentoML: programs outside may call this one. A method without it stays private.เดคอเรเตอร์อีกตัว มันอยู่เหนือเมธอด และบอก BentoML ว่าโปรแกรมข้างนอกเรียกเมธอดนี้ได้ เมธอดที่ไม่มีมันจะใช้ได้แค่ข้างใน另一个装饰器。它写在方法的上面,告诉 BentoML:外面的程序可以调用这个方法。没有它的方法,外面用不了。 |
self | Every method of a class takes self as its first value. It means this shop. You never give it yourself. Python does that for you.เมธอดทุกตัวของคลาสรับ self เป็นค่าแรก มันหมายถึงร้านนี้คุณไม่ต้องส่งมันเอง Python ทำให้คุณ类的每个方法都把 self 当作第一个值。它的意思是这家店。你永远不用自己传它,Python 会替你传。 |
dish: str | A type hint. It says that dish is a str, which is a piece of text.ไทป์ฮินต์ มันบอกว่า dish เป็น str ซึ่งก็คือข้อความ一个类型提示。它说明 dish 是 str,也就是一段文字。 |
bowls: int | A type hint. It says that bowls is an int, which is a whole number.ไทป์ฮินต์ มันบอกว่า bowls เป็น int ซึ่งก็คือจำนวนเต็ม一个类型提示。它说明 bowls 是 int,也就是一个整数。 |
-> int | A type hint for the result. It says that the method returns a whole number.ไทป์ฮินต์ของผลลัพธ์ มันบอกว่าเมธอดส่งจำนวนเต็มกลับมา给结果用的类型提示。它说明这个方法返回一个整数。 |
price.py had no hints and service.py has them.ในโปรแกรม Python ส่วนใหญ่ ไทป์ฮินต์เป็นแค่โน้ตสำหรับคนที่อ่านโค้ด ตัว Python เองไม่ได้ตรวจมัน แต่ BentoML ไม่เหมือนกัน มันอ่านฮินต์เพื่อรู้ว่าคำขอต้องมีหน้าตาอย่างไร และตรวจคำขอทุกครั้งเทียบกับฮินต์ ก่อนที่เมธอดของคุณจะรัน คุณจะได้เห็นมันทำแบบนั้นในขั้นที่ 6เพราะอย่างนี้ price.py จึงไม่มีฮินต์ แต่ service.py มี在大多数 Python 程序里,类型提示只是写给读代码的人看的标注,Python 自己并不检查它。BentoML 不一样。它读这些提示,来知道一个请求应该是什么样子;在你的方法运行之前,它会拿每个请求和提示对照检查。第 6 步你会亲眼看到。所以 price.py 没有提示,而 service.py 有。
🇬🇧 English
There is nothing to run yet. This file has no print and it calls nothing. It only describes a service. The next step starts it.
🇹🇭 ไทย
ยังไม่มีอะไรให้รัน ไฟล์นี้ไม่มี print และไม่ได้เรียกอะไรเลย มันแค่บรรยายเซอร์วิสไว้ ขั้นถัดไปจะเปิดมัน
🇨🇳 中文
现在还没有东西可以运行。这个文件里没有 print,也没有调用任何东西。它只是描述了一个服务。下一步才启动它。
4 Start the service · เปิดเซอร์วิส · 启动服务
🇬🇧 English
Type this command in your terminal. The last word has two halves with a colon between them. service is the file service.py, without the .py. Shop is the class inside it.
🇹🇭 ไทย
พิมพ์คำสั่งนี้ในเทอร์มินัลของคุณ คำสุดท้ายมีสองครึ่ง คั่นด้วยเครื่องหมายโคลอน service คือไฟล์ service.py โดยไม่มี .py ส่วน Shop คือคลาสที่อยู่ในไฟล์นั้น
🇨🇳 中文
在终端里输入这条命令。最后一个词分成两半,中间是一个冒号。service 就是文件 service.py,去掉了 .py。Shop 是文件里的那个类。
venv\Scripts\bentoml serve service:Shop
what it printedสิ่งที่มันพิมพ์ออกมา它打印的内容
2026-10-09T21:22:13+0700 [INFO] [cli] Starting production HTTP BentoServer from "service:Shop" listening on http://localhost:3000 (Press CTRL+C to quit)
2026-10-09T21:22:14+0700 [INFO] [entry_service:Shop:1] Service Shop initialized
🇬🇧 English
The first line gives the address of your service: http://localhost:3000. localhost means this computer, and 3000 is the port. The second line says that the class Shop is ready.
Then nothing more happens, and the terminal does not give you a new line to type on. That is correct. The service is running, and it is waiting for questions. Leave this terminal open. To stop the service later, click in this terminal and press Ctrl+C.
Now open http://localhost:3000 in your web browser. BentoML made this page for you. It lists what your service can do. Find the line POST /price and click it. Click Try it out. You see a box with "dish": "string" and "bowls": 0 in it. Change string to dumplings and 0 to 2, and click Execute. The answer, 80, appears lower down.
Look at the first terminal again. It has a new line for the request you just sent. The service writes one line for every request.
🇹🇭 ไทย
บรรทัดแรกบอกที่อยู่ของเซอร์วิสของคุณ: http://localhost:3000 คำว่า localhost หมายถึงคอมพิวเตอร์เครื่องนี้ และ 3000 คือพอร์ต บรรทัดที่สองบอกว่าคลาส Shop พร้อมแล้ว
จากนั้นไม่มีอะไรเกิดขึ้นอีก และเทอร์มินัลก็ไม่ขึ้นบรรทัดใหม่ให้พิมพ์ แบบนี้ถูกแล้ว เซอร์วิสกำลังรันอยู่ และกำลังรอคำถาม เปิดเทอร์มินัลนี้ทิ้งไว้ เมื่อจะหยุดเซอร์วิสทีหลัง ให้คลิกในเทอร์มินัลนี้แล้วกด Ctrl+C
ตอนนี้เปิด http://localhost:3000 ในเว็บเบราว์เซอร์ BentoML สร้างหน้านี้ให้คุณ มันแสดงรายการสิ่งที่เซอร์วิสของคุณทำได้ หาบรรทัด POST /price แล้วคลิก จากนั้นคลิก Try it out คุณจะเห็นกล่องที่มี "dish": "string" และ "bowls": 0 อยู่ข้างใน เปลี่ยน string เป็น dumplings และเปลี่ยน 0 เป็น 2 แล้วคลิก Execute คำตอบคือ 80 จะปรากฏอยู่ข้างล่าง
กลับไปดูเทอร์มินัลแรกอีกครั้ง มันมีบรรทัดใหม่สำหรับคำขอที่คุณเพิ่งส่ง เซอร์วิสเขียนหนึ่งบรรทัดต่อหนึ่งคำขอ
🇨🇳 中文
第一行给出了你的服务的地址:http://localhost:3000。localhost 指的是这台电脑,3000 是端口。第二行说,Shop 这个类已经准备好了。
然后就没有动静了,终端也不给你新的一行来输入。这是对的。服务正在运行,正在等问题。不要关这个终端。以后想停掉服务,就点一下这个终端,再按 Ctrl+C。
现在用浏览器打开 http://localhost:3000。这个页面是 BentoML 替你做的,上面列出了你的服务能做的事。找到 POST /price 这一行,点开它。再点 Try it out。你会看到一个框,里面有 "dish": "string" 和 "bowls": 0。把 string 改成 dumplings,把 0 改成 2,然后点 Execute。答案 80 会出现在下方。
再看看第一个终端。它多了一行,记的就是你刚才发的那个请求。每来一个请求,服务就写一行。
5 ask.py: ask it from another program · ask.py: ถามเซอร์วิสจากอีกโปรแกรมหนึ่ง · ask.py:从另一个程序向它提问
🇬🇧 English
A person can use the browser page. A program needs code. Open a second terminal in the same folder, and save this file as ask.py.
bentoml.SyncHTTPClient makes a client. A client is an object that knows the address of a service and can send requests to it. We keep it in the name shop.
shop.price(dish="beef noodles", bowls=2) looks like a call to a method, and you write it the same way. But this time the question leaves your program. It travels to the service in the first terminal, the method price runs there, and the answer travels back.
🇹🇭 ไทย
คนใช้หน้าในเบราว์เซอร์ได้ แต่โปรแกรมต้องใช้โค้ด เปิดเทอร์มินัลที่สองในโฟลเดอร์เดียวกัน แล้วบันทึกไฟล์นี้ในชื่อ ask.py
bentoml.SyncHTTPClient สร้างไคลเอนต์ ไคลเอนต์คืออ็อบเจกต์ที่รู้ที่อยู่ของเซอร์วิส และส่งคำขอไปหามันได้ เราเก็บมันไว้ในชื่อ shop
shop.price(dish="beef noodles", bowls=2) ดูเหมือนการเรียกเมธอด และคุณก็เขียนแบบเดียวกัน แต่ครั้งนี้คำถามออกไปจากโปรแกรมของคุณ มันเดินทางไปยังเซอร์วิสในเทอร์มินัลแรก เมธอด price รันที่นั่น แล้วคำตอบก็เดินทางกลับมา
🇨🇳 中文
人可以用浏览器里的那个页面。程序就得用代码了。在同一个文件夹里打开第二个终端,把这个文件存为 ask.py。
bentoml.SyncHTTPClient 创建一个客户端。客户端是一个对象,它知道服务的地址,能向服务发请求。我们把它存在 shop 这个名字里。
shop.price(dish="beef noodles", bowls=2) 看起来像是在调用一个方法,写法也一样。但这一次,问题离开了你的程序。它传到第一个终端里的服务,price 方法在那边运行,答案再传回来。
ask.py
import bentoml
# The address of the service. localhost means "this computer".
shop = bentoml.SyncHTTPClient("http://localhost:3000")
print(shop.price(dish="beef noodles", bowls=2))
print(shop.price(dish="fried rice", bowls=3))
venv\Scripts\python ask.py
what it printedสิ่งที่มันพิมพ์ออกมา它打印的内容
120
150
🇬🇧 English
These are the same two answers as in step 2. But ask.py has no menu and no function price in it. Both are in the other program.
The program that asks does not need BentoML at all. A request to a service is a small piece of JSON, sent to an address. The next file sends one with httpx, a library for web requests. The address ends in /price: the name of your method became part of the address. The JSON holds the two values, under the names dish and bowls.
🇹🇭 ไทย
นี่คือคำตอบสองข้อเดียวกับในขั้นที่ 2 แต่ใน ask.py ไม่มีเมนู และไม่มีฟังก์ชัน price ทั้งสองอย่างอยู่ในอีกโปรแกรมหนึ่ง
โปรแกรมที่ถามไม่จำเป็นต้องมี BentoML เลย คำขอที่ส่งไปยังเซอร์วิสคือ JSON ชิ้นเล็ก ๆ ที่ส่งไปยังที่อยู่หนึ่ง ไฟล์ถัดไปส่งคำขอด้วย httpx ซึ่งเป็นไลบรารีสำหรับส่งคำขอทางเว็บ ที่อยู่ลงท้ายด้วย /price คือชื่อเมธอดของคุณกลายเป็นส่วนหนึ่งของที่อยู่ ส่วน JSON เก็บค่าสองค่าไว้ใต้ชื่อ dish และ bowls
🇨🇳 中文
这两个答案和第 2 步的一样。但是 ask.py 里既没有菜单,也没有 price 函数。这两样都在另一个程序里。
提问的程序根本不需要 BentoML。发给服务的请求只是一小段 JSON,发到一个地址上。下一个文件用 httpx 来发,它是一个发网络请求的库。地址以 /price 结尾:你的方法名成了地址的一部分。JSON 里装着那两个值,名字分别是 dish 和 bowls。
ask_http.py
import httpx
order = {"dish": "dumplings", "bowls": 6}
reply = httpx.post("http://localhost:3000/price", json=order)
print(reply.status_code)
print(reply.text)
venv\Scripts\python ask_http.py
what it printedสิ่งที่มันพิมพ์ออกมา它打印的内容
200
240
🇬🇧 English
The first line is the status code. 200 means that the request went well. The second line is the answer: six bowls of dumplings cost 240.
This is why a service is useful. Any program that can send a web request can ask your Python function for a price. It can be a web page written in JavaScript or an app on a phone.
🇹🇭 ไทย
บรรทัดแรกคือรหัสสถานะ 200 แปลว่าคำขอเรียบร้อยดี บรรทัดที่สองคือคำตอบ: เกี๊ยวหกชามราคา 240
นี่คือเหตุผลที่เซอร์วิสมีประโยชน์ โปรแกรมใดก็ตามที่ส่งคำขอทางเว็บได้ ก็ถามราคาจากฟังก์ชัน Python ของคุณได้ จะเป็นหน้าเว็บที่เขียนด้วย JavaScript หรือแอปบนมือถือก็ได้
🇨🇳 中文
第一行是状态码。200 表示请求一切顺利。第二行是答案:六碗饺子 240。
这就是服务有用的原因。任何能发网络请求的程序,都能向你的 Python 函数问价格。它可以是用 JavaScript 写的网页,也可以是手机上的应用。
- Change
ask.pyso that it asks for the price of four bowls of dumplings. Run it. - Add a dish to
MENUinservice.py. Then stop the service withCtrl+C, start it again, and ask for your new dish. A running service does not see a change in its file until you start it again. - Stop the service and run
ask.py. It waits for 30 seconds and then stops with an error. Read the last line of the error. Then start the service again.
- แก้
ask.pyให้ถามราคาเกี๊ยวสี่ชาม แล้วรัน - เพิ่มอาหารหนึ่งอย่างลงใน
MENUในservice.pyจากนั้นหยุดเซอร์วิสด้วยCtrl+Cเปิดใหม่ แล้วถามราคาอาหารใหม่ของคุณ เซอร์วิสที่กำลังรันอยู่จะไม่เห็นการแก้ไขในไฟล์ จนกว่าคุณจะเปิดมันใหม่ - หยุดเซอร์วิส แล้วรัน
ask.pyมันจะรอ 30 วินาที แล้วหยุดพร้อมข้อผิดพลาด อ่านบรรทัดสุดท้ายของข้อผิดพลาด จากนั้นเปิดเซอร์วิสใหม่
- 改一下
ask.py,让它问四碗饺子的价格。运行它。 - 在
service.py的MENU里加一道菜。然后用Ctrl+C停掉服务,重新启动,再问你新加的那道菜。正在运行的服务看不到文件里的改动,要重新启动才行。 - 停掉服务,再运行
ask.py。它会等 30 秒,然后报错停下。读一读报错的最后一行。然后重新启动服务。
6 wrong.py: send it a wrong question · wrong.py: ส่งคำถามที่ผิดไปให้ · wrong.py:给它发一个错误的问题
🇬🇧 English
A service is asked by programs that other people wrote, and some of them will ask badly. This file sends two wrong requests. In the first, the number of bowls is the word "five". In the second, the dish is "pizza", which is not on the menu.
🇹🇭 ไทย
เซอร์วิสถูกถามโดยโปรแกรมที่คนอื่นเขียน และบางโปรแกรมก็จะถามผิด ไฟล์นี้ส่งคำขอที่ผิดสองครั้ง ครั้งแรก จำนวนชามเป็นคำว่า "five" ครั้งที่สอง อาหารคือ "pizza" ซึ่งไม่มีในเมนู
🇨🇳 中文
向服务提问的,是别人写的程序,其中总有一些会问错。这个文件发了两个错误的请求。第一个里,碗数是一个单词 "five"。第二个里,菜名是 "pizza",菜单上没有。
wrong.py
import httpx
ADDRESS = "http://localhost:3000/price"
# The number of bowls is a word here, and the service wants an int.
reply = httpx.post(ADDRESS, json={"dish": "dumplings", "bowls": "five"})
print(reply.status_code)
print(reply.text)
# The types are right here, but the menu has no pizza.
reply = httpx.post(ADDRESS, json={"dish": "pizza", "bowls": 2})
print(reply.status_code)
print(reply.text)
venv\Scripts\python wrong.py
what it printedสิ่งที่มันพิมพ์ออกมา它打印的内容
400
{"error":"1 validation error for Input","detail":[{"type":"int_parsing","loc":["bowls"],"msg":"Input should be a valid integer, unable to parse string as an integer","url":"https://errors.pydantic.dev/2.14/v/int_parsing"}]}
500
{"error":"An unexpected error has occurred, please check the server log."}
🇬🇧 English
The first reply has the status code 400. A code that begins with 4 means that the request was wrong. BentoML read the hint bowls: int, saw that "five" is not a whole number, and sent the request back. The reply says where the mistake is, "loc":["bowls"], and what it is: Input should be a valid integer
. Your method did not run at all. You wrote no code for this check. The type hint did it.
The second reply has the status code 500. A code that begins with 5 means that the service itself failed. This time both types were right, so BentoML let the request in and your method ran. Then MENU["pizza"] failed, because the dictionary has no such key. The program that asked is told very little: An unexpected error has occurred
.
The full story is in the first terminal, where the service runs. It printed a long error there. These are its last three lines:
🇹🇭 ไทย
คำตอบแรกมีรหัสสถานะ 400 รหัสที่ขึ้นต้นด้วย 4 แปลว่าคำขอผิด BentoML อ่านฮินต์ bowls: int เห็นว่า "five" ไม่ใช่จำนวนเต็ม จึงส่งคำขอกลับไป คำตอบบอกว่าผิดตรงไหน คือ "loc":["bowls"] และผิดอย่างไร: Input should be a valid integer
(ค่าที่ส่งมาต้องเป็นจำนวนเต็ม) เมธอดของคุณไม่ได้รันเลย คุณไม่ได้เขียนโค้ดสำหรับการตรวจนี้ ไทป์ฮินต์ทำให้
คำตอบที่สองมีรหัสสถานะ 500 รหัสที่ขึ้นต้นด้วย 5 แปลว่าตัวเซอร์วิสเองล้มเหลว ครั้งนี้ชนิดของค่าถูกทั้งคู่ BentoML จึงปล่อยคำขอเข้ามา และเมธอดของคุณก็รัน แล้ว MENU["pizza"] ก็ล้มเหลว เพราะดิกชันนารีไม่มีคีย์นี้ โปรแกรมที่ถามได้รับข้อมูลน้อยมาก: An unexpected error has occurred
(เกิดข้อผิดพลาดที่ไม่คาดคิด)
เรื่องทั้งหมดอยู่ในเทอร์มินัลแรก ที่เซอร์วิสรันอยู่ มันพิมพ์ข้อผิดพลาดยาว ๆ ไว้ที่นั่น นี่คือสามบรรทัดสุดท้าย:
🇨🇳 中文
第一个回复的状态码是 400。以 4 开头的状态码表示请求有错。BentoML 读了 bowls: int 这个提示,发现 "five" 不是整数,就把请求退了回去。回复里说了错在哪里,"loc":["bowls"],也说了错的是什么:Input should be a valid integer
(输入应该是一个整数)。你的方法根本没有运行。这个检查你一行代码都没写,是类型提示做的。
第二个回复的状态码是 500。以 5 开头的状态码表示服务自己出了问题。这一次两个值的类型都对,所以 BentoML 放行了,你的方法运行了。接着 MENU["pizza"] 出错,因为字典里没有这个键。提问的程序得到的信息很少:An unexpected error has occurred
(发生了意外的错误)。
完整的经过在第一个终端里,也就是服务运行的地方。它在那里打印了一长段报错。下面是最后三行:
the last three lines of the error, in the service's terminalสามบรรทัดสุดท้ายของข้อผิดพลาด ในเทอร์มินัลของเซอร์วิส服务所在的终端里,报错的最后三行
return MENU[dish] * bowls
~~~~^^^^^^
KeyError: 'pizza'
🇬🇧 English
A KeyError is what Python raises when a dictionary does not have the key you asked for. The service did not stop. It is still running, and it will answer the next request.
So a type hint can check the kind of a value. It cannot know that the shop sells no pizza. That check is your job, and step 7 adds it.
🇹🇭 ไทย
KeyError คือสิ่งที่ Python แจ้งเมื่อดิกชันนารีไม่มีคีย์ที่คุณขอ เซอร์วิสไม่ได้หยุด มันยังรันอยู่ และจะตอบคำขอถัดไป
ดังนั้นไทป์ฮินต์ตรวจชนิดของค่าได้ แต่มันรู้ไม่ได้ว่าร้านไม่ขายพิซซ่า การตรวจแบบนั้นเป็นหน้าที่ของคุณ และขั้นที่ 7 จะเพิ่มเข้าไป
🇨🇳 中文
字典里没有你要的键时,Python 就会报 KeyError。服务并没有停。它还在运行,下一个请求来了照样回答。
所以,类型提示能检查一个值的种类。它没法知道这家店不卖披萨。那种检查是你的事,第 7 步会把它加上。
| Status codeรหัสสถานะ状态码 | What it meansความหมาย意思 | Where you saw itเห็นที่ไหน在哪里见过 |
|---|---|---|
| 200 | The request went well.คำขอเรียบร้อยดี请求一切顺利。 | Step 5ขั้นที่ 5第 5 步 |
| 400 | The request was wrong. Here a value had the wrong type.คำขอผิด ในที่นี้คือค่าหนึ่งมีชนิดผิด请求有错。在这里,是一个值的类型不对。 | Step 6ขั้นที่ 6第 6 步 |
| 404 | The thing that was asked for is not there.ไม่มีสิ่งที่ถูกขอ要的东西不存在。 | Step 7ขั้นที่ 7第 7 步 |
| 500 | The service has a mistake in its code.โค้ดของเซอร์วิสมีข้อผิดพลาด服务的代码里有错误。 | Step 6ขั้นที่ 6第 6 步 |
🍱 Part 3 — A better service, and the box · ส่วนที่ 3 — เซอร์วิสที่ดีขึ้น และกล่อง · 第 3 部分 —— 更好的服务,还有盒子
🇬🇧 English
The first service works, but it has two weak points. The menu is written inside the code, and a wrong dish makes the service fail. This part repairs both, and it shows why a service is a class. Then you pack the service into a Bento.
🇹🇭 ไทย
เซอร์วิสตัวแรกใช้งานได้ แต่มีจุดอ่อนสองจุด เมนูเขียนอยู่ในโค้ด และอาหารที่ผิดทำให้เซอร์วิสล้มเหลว ส่วนนี้แก้ทั้งสองจุด และแสดงให้เห็นว่าทำไมเซอร์วิสจึงเป็นคลาส จากนั้นคุณจะแพ็กเซอร์วิสลงใน Bento
🇨🇳 中文
第一个服务能用,但有两个弱点:菜单写死在代码里,菜名一错服务就出故障。这一部分把两个都修好,也让你看到服务为什么是一个类。然后你把服务装进一个 Bento。
7 shop.py: load once, answer many times · shop.py: โหลดครั้งเดียว ตอบได้หลายครั้ง · shop.py:加载一次,回答很多次
🇬🇧 English
First stop the old service: click in the first terminal and press Ctrl+C. Then save the next two files. menu.json is the menu as a file of its own, so that the shop's owner can change a price and never touch the code.
🇹🇭 ไทย
ก่อนอื่นหยุดเซอร์วิสตัวเก่า: คลิกในเทอร์มินัลแรกแล้วกด Ctrl+C จากนั้นบันทึกสองไฟล์ถัดไป menu.json คือเมนูที่แยกออกมาเป็นไฟล์ของตัวเอง เจ้าของร้านจึงเปลี่ยนราคาได้โดยไม่ต้องแตะโค้ดเลย
🇨🇳 中文
先停掉旧的服务:点一下第一个终端,按 Ctrl+C。然后保存下面两个文件。menu.json 是单独放在一个文件里的菜单,这样店主改价格的时候,完全不用碰代码。
menu.json
{"beef noodles": 60, "fried rice": 50, "dumplings": 40}
shop.py
import json
import bentoml
@bentoml.service
class Shop:
def __init__(self):
# This part runs one time, when the service starts.
with open("menu.json", encoding="utf-8") as file:
self.menu = json.load(file)
print("The menu has", len(self.menu), "dishes.")
@bentoml.api
def dishes(self) -> list[str]:
return list(self.menu)
@bentoml.api
def price(self, dish: str, bowls: int) -> int:
if dish not in self.menu:
raise bentoml.exceptions.NotFound("We do not sell " + dish + ".")
return self.menu[dish] * bowls
🇬🇧 English
This class has three methods, and three things in it are new.
__init__ is a special method. Its name has two underscores on each side. Python runs it one time, when the Shop is made, and BentoML makes the Shop when the service starts. Here it opens menu.json, and json.load turns the text of the file into a dictionary. The line self.menu = ... keeps that dictionary inside the shop. Any other method can then read it as self.menu. This is what self is for: it is how the methods of one class share data.
dishes is a second API. It takes no values, and it returns the names of the dishes. list(self.menu) makes a list of the dictionary's keys. The hint -> list[str] says that the result is a list of pieces of text. Two methods with @bentoml.api make two addresses: /dishes and /price.
raise stops a method and reports an error. In price, the if checks that the dish is on the menu. If it is not, raise sends back an error that BentoML has ready, NotFound, with a message that we wrote ourselves. The last line of the method then does not run.
Start the new service. This time the word before the colon is shop, because the file is shop.py.
🇹🇭 ไทย
คลาสนี้มีสามเมธอด และมีของใหม่สามอย่าง
__init__ คือเมธอดพิเศษ ชื่อของมันมีขีดล่างสองตัวทั้งสองข้าง Python รันมันครั้งเดียว ตอนที่ Shop ถูกสร้าง และ BentoML สร้าง Shop ตอนที่เซอร์วิสเริ่มทำงาน ในที่นี้มันเปิด menu.json และ json.load เปลี่ยนข้อความในไฟล์ให้เป็นดิกชันนารี บรรทัด self.menu = ... เก็บดิกชันนารีนั้นไว้ในร้าน เมธอดอื่นจึงอ่านมันได้ในชื่อ self.menu นี่คือหน้าที่ของ self: มันคือวิธีที่เมธอดของคลาสเดียวกันใช้ข้อมูลร่วมกัน
dishes คือ API ตัวที่สอง มันไม่รับค่าอะไร และส่งชื่ออาหารกลับมา list(self.menu) สร้างลิสต์จากคีย์ของดิกชันนารี ฮินต์ -> list[str] บอกว่าผลลัพธ์คือลิสต์ของข้อความ เมธอดสองตัวที่มี @bentoml.api ทำให้เกิดที่อยู่สองแห่ง: /dishes และ /price
raise หยุดเมธอดและแจ้งข้อผิดพลาด ใน price คำสั่ง if ตรวจว่าอาหารอยู่ในเมนู ถ้าไม่อยู่ raise จะส่งข้อผิดพลาดที่ BentoML เตรียมไว้ให้ คือ NotFound กลับไป พร้อมข้อความที่เราเขียนเอง บรรทัดสุดท้ายของเมธอดจึงไม่รัน
เปิดเซอร์วิสตัวใหม่ ครั้งนี้คำที่อยู่หน้าโคลอนคือ shop เพราะไฟล์ชื่อ shop.py
🇨🇳 中文
这个类有三个方法,其中有三样新东西。
__init__ 是一个特殊的方法,名字两边各有两条下划线。Python 只运行它一次,就是在创建 Shop 的时候;而 BentoML 在服务启动时创建 Shop。在这里,它打开 menu.json,json.load 把文件里的文字变成一个字典。self.menu = ... 这一行把字典存在这家店里面。之后别的方法都可以用 self.menu 读到它。self 的用处就在这里:同一个类的方法靠它共用数据。
dishes 是第二个 API。它不接收任何值,返回所有菜名。list(self.menu) 把字典的键做成一个列表。-> list[str] 这个提示说明,结果是由文字组成的列表。两个带 @bentoml.api 的方法,就有两个地址:/dishes 和 /price。
raise 让方法停下来,并报告一个错误。在 price 里,if 检查这道菜在不在菜单上。如果不在,raise 就发回 BentoML 准备好的一种错误 NotFound,带上我们自己写的一句话。这样,方法的最后一行就不会运行。
启动新的服务。这一次冒号前面的词是 shop,因为文件叫 shop.py。
venv\Scripts\bentoml serve shop:Shop
what it printedสิ่งที่มันพิมพ์ออกมา它打印的内容
2026-10-09T21:22:55+0700 [INFO] [cli] Starting production HTTP BentoServer from "shop:Shop" listening on http://localhost:3000 (Press CTRL+C to quit)
The menu has 3 dishes.
2026-10-09T21:22:56+0700 [INFO] [entry_service:Shop:1] Service Shop initialized
🇬🇧 English
The middle line is the print inside __init__. It appears one time, when the service starts. It will not appear again, however many requests come in.
In the second terminal, save ask_shop.py and run it.
🇹🇭 ไทย
บรรทัดกลางคือ print ที่อยู่ใน __init__ มันปรากฏครั้งเดียว ตอนที่เซอร์วิสเริ่มทำงาน และจะไม่ปรากฏอีก ไม่ว่าจะมีคำขอเข้ามากี่ครั้ง
ในเทอร์มินัลที่สอง บันทึก ask_shop.py แล้วรัน
🇨🇳 中文
中间那一行是 __init__ 里的 print 打印的。它只在服务启动时出现一次。之后不管来多少个请求,它都不会再出现。
在第二个终端里,保存 ask_shop.py 并运行它。
ask_shop.py
import bentoml
import httpx
shop = bentoml.SyncHTTPClient("http://localhost:3000")
print(shop.dishes())
print(shop.price(dish="dumplings", bowls=4))
# The menu has no pizza. httpx shows the reply as the service sent it.
order = {"dish": "pizza", "bowls": 2}
reply = httpx.post("http://localhost:3000/price", json=order)
print(reply.status_code)
print(reply.text)
venv\Scripts\python ask_shop.py
what it printedสิ่งที่มันพิมพ์ออกมา它打印的内容
['beef noodles', 'fried rice', 'dumplings']
160
404
[{"error":"We do not sell pizza."}]
🇬🇧 English
The first line is the reply of dishes, and the second is the price of four bowls of dumplings. The last two lines are the request for pizza. The status code is now 404 and not 500, and the reply holds our own message. A program that asks for pizza can now tell its user what went wrong.
🇹🇭 ไทย
บรรทัดแรกคือคำตอบของ dishes และบรรทัดที่สองคือราคาเกี๊ยวสี่ชาม สองบรรทัดสุดท้ายคือคำขอพิซซ่า ตอนนี้รหัสสถานะเป็น 404 ไม่ใช่ 500 และคำตอบมีข้อความของเราเอง โปรแกรมที่ขอพิซซ่าจึงบอกผู้ใช้ของมันได้แล้วว่าผิดตรงไหน
🇨🇳 中文
第一行是 dishes 的回复,第二行是四碗饺子的价格。最后两行是要披萨的那个请求。状态码现在是 404,不再是 500,回复里是我们自己写的那句话。这样,来要披萨的程序就能告诉它的用户哪里出了错。
- Add a dish to
menu.json. Start the service again, and runask_shop.py. You changed no Python. - Open
http://localhost:3000in your browser. How many lines begin withPOSTnow? - Write a third API named
cheapest. It takes no values and returns the name of the dish with the lowest price.min(self.menu, key=self.menu.get)finds it.
- เพิ่มอาหารหนึ่งอย่างลงใน
menu.jsonเปิดเซอร์วิสใหม่ แล้วรันask_shop.pyคุณไม่ได้แก้ Python เลย - เปิด
http://localhost:3000ในเบราว์เซอร์ ตอนนี้มีกี่บรรทัดที่ขึ้นต้นด้วยPOST - เขียน API ตัวที่สาม ชื่อ
cheapestมันไม่รับค่าอะไร และส่งชื่ออาหารที่ราคาถูกที่สุดกลับมาmin(self.menu, key=self.menu.get)หามันได้
- 在
menu.json里加一道菜。重新启动服务,再运行ask_shop.py。你一行 Python 都没改。 - 用浏览器打开
http://localhost:3000。现在有几行是以POST开头的? - 写第三个 API,名叫
cheapest。它不接收任何值,返回最便宜的那道菜的名字。min(self.menu, key=self.menu.get)可以找到它。
A Tutorial A — judge.py: a real model in the service · บทเสริม A — judge.py: โมเดลจริงในเซอร์วิส · 教程 A —— judge.py:服务里放一个真正的模型
venv folder grew from about 140 MB to about 950 MB. The first run also downloads the model, and that is 3.8 GB.แปดขั้นหลักไม่ต้องใช้สิ่งเหล่านี้เลย ถ้าคอมพิวเตอร์ของคุณเล็ก ให้ข้ามไปบทเสริม B หรือขั้นที่ 8ไลบรารีของบทเสริมนี้พาไลบรารีขนาดใหญ่ชื่อ PyTorch มาด้วย โฟลเดอร์ venv ของเราโตจากราว 140 MB เป็นราว 950 MB การรันครั้งแรกยังดาวน์โหลดโมเดลด้วย ซึ่งมีขนาด 3.8 GB那八个步骤都不需要这些。如果你的电脑比较小,可以直接跳到教程 B 或第 8 步。这个教程用的库会带来一个很大的库,叫 PyTorch。我们的 venv 文件夹从大约 140 MB 变成了大约 950 MB。第一次运行还要下载模型,有 3.8 GB。
🇬🇧 English
In step 7 the thing that was loaded one time was a menu. Now you put a real AI model in the same place. The model is named decider-2b. It does not write text. You give it a text and a question, and it gives back a number that says how sure it is. The page Decider explains the model. Here you only say hello to it.
Install its library into the same venv:
🇹🇭 ไทย
ในขั้นที่ 7 สิ่งที่ถูกโหลดครั้งเดียวคือเมนู ตอนนี้คุณจะใส่โมเดล AI ของจริงลงในที่เดียวกัน โมเดลนี้ชื่อ decider-2b มันไม่เขียนข้อความ คุณให้ข้อความกับคำถามหนึ่งข้อ แล้วมันส่งตัวเลขกลับมา ซึ่งบอกว่ามันมั่นใจแค่ไหน หน้า Decider อธิบายโมเดลนี้ ในที่นี้คุณแค่ทักทายมัน
ติดตั้งไลบรารีของมันลงใน venv เดิม:
🇨🇳 中文
在第 7 步里,只加载一次的东西是一张菜单。现在你要在同一个位置放一个真正的 AI 模型。这个模型叫 decider-2b。它不写文字。你给它一段文字和一个问题,它还给你一个数字,表示它有多确定。Decider 那一页讲解了这个模型。在这里,你只是跟它打个招呼。
把它的库装进同一个 venv:
🪟 WINDOWS
venv\Scripts\python -m pip install decider-ai
🍎 MAC · not run by us · เราไม่ได้รันเอง · 我们没有运行过
venv/bin/python -m pip install decider-ai
judge.py
import bentoml
from decider.infer import Decider
# BentoML gives up on a request after 60 seconds. A model's first answer can be slower.
@bentoml.service(traffic={"timeout": 300})
class Judge:
def __init__(self):
# Loading the model is slow, so it is done one time, here.
self.model = Decider("Mapika/decider-2b")
@bentoml.api
def yes_or_no(self, text: str, question: str) -> float:
questions = {"answer": {"type": "noul", "instructions": question}}
result = self.model.system_one(text, questions)
return result["answers"]["answer"]["noul"]
🇬🇧 English
The shape is the one you wrote in step 7: load in __init__, keep in self, answer in a method.
__init__ loads the model one time and keeps it in self.model. If the model were loaded inside yes_or_no, every request would have to wait for the loading.
yes_or_no takes two pieces of text: the text to read, and a question about it. The hint -> float says that it returns a number with a decimal point.
The method makes a dictionary with one question in it, named "answer". "noul" is the model's own name for a yes-or-no question. Then self.model.system_one asks the model. The result is a dictionary, and the last line takes the number out of it. A number near 1 means yes. A number near 0 means no.
traffic={"timeout": 300} is new. BentoML gives up on a request after 60 seconds. A model's first answer can take longer than that, so this line lets a request take 300 seconds.
Stop the old service with Ctrl+C and start this one.
🇹🇭 ไทย
รูปแบบคือแบบที่คุณเขียนในขั้นที่ 7: โหลดใน __init__ เก็บไว้ใน self และตอบในเมธอด
__init__ โหลดโมเดลครั้งเดียว และเก็บไว้ใน self.model ถ้าโหลดโมเดลใน yes_or_no คำขอทุกครั้งจะต้องรอการโหลด
yes_or_no รับข้อความสองชิ้น คือข้อความที่จะอ่าน กับคำถามเกี่ยวกับข้อความนั้น ฮินต์ -> float บอกว่ามันส่งตัวเลขที่มีจุดทศนิยมกลับมา
เมธอดสร้างดิกชันนารีที่มีคำถามหนึ่งข้อ ชื่อ "answer" ส่วน "noul" คือชื่อที่โมเดลใช้เรียกคำถามแบบใช่หรือไม่ใช่ จากนั้น self.model.system_one ถามโมเดล ผลลัพธ์เป็นดิกชันนารี และบรรทัดสุดท้ายหยิบตัวเลขออกมา ตัวเลขใกล้ 1 แปลว่าใช่ ตัวเลขใกล้ 0 แปลว่าไม่ใช่
traffic={"timeout": 300} เป็นของใหม่ BentoML เลิกรอคำขอหลังจาก 60 วินาที คำตอบแรกของโมเดลอาจใช้เวลานานกว่านั้น บรรทัดนี้จึงให้คำขอใช้เวลาได้ 300 วินาที
หยุดเซอร์วิสตัวเก่าด้วย Ctrl+C แล้วเปิดตัวนี้
🇨🇳 中文
结构就是你在第 7 步写的那样:在 __init__ 里加载,存在 self 里,在方法里回答。
__init__ 只加载一次模型,并把它存在 self.model 里。如果把模型放在 yes_or_no 里加载,每个请求都得等它加载完。
yes_or_no 接收两段文字:要读的那段文字,和关于它的一个问题。-> float 这个提示说明,它返回一个带小数点的数。
这个方法先做一个字典,里面有一个问题,名字叫 "answer"。"noul" 是这个模型对“是或否”问题的叫法。然后 self.model.system_one 去问模型。结果是一个字典,最后一行把那个数字取出来。接近 1 表示“是”,接近 0 表示“否”。
traffic={"timeout": 300} 是新东西。一个请求超过 60 秒,BentoML 就不再等了。模型的第一次回答可能比这更久,所以这一行允许一个请求用 300 秒。
用 Ctrl+C 停掉旧的服务,再启动这一个。
venv\Scripts\bentoml serve judge:Judge
what it printedสิ่งที่มันพิมพ์ออกมา它打印的内容
2026-10-10T07:18:43+0700 [INFO] [cli] Starting production HTTP BentoServer from "judge:Judge" listening on http://localhost:3000 (Press CTRL+C to quit)
Warning: You are sending unauthenticated requests to the HF Hub. Please set a HF_TOKEN to enable higher rate limits and faster downloads.
2026-10-10T07:18:52+0700 [WARNING] [entry_service:Judge:1] Warning: You are sending unauthenticated requests to the HF Hub. Please set a HF_TOKEN to enable higher rate limits and faster downloads.
2026-10-10T07:19:10+0700 [INFO] [entry_service:Judge:1] Service Judge initialized
🇬🇧 English
Wait for the last line, Service Judge initialized. With the menu that line came after 1 second. With the model it came after 27 seconds, and after 84 seconds the first time we started it. Your terminal also shows a moving bar while the model loads. The bar is left out here.
The two lines with Warning come from the library that downloads the model. You can ignore them. You need no account.
In the second terminal, save ask_judge.py and run it.
🇹🇭 ไทย
รอบรรทัดสุดท้าย คือ Service Judge initialized ตอนใช้เมนู บรรทัดนั้นมาหลังจาก 1 วินาที ตอนใช้โมเดล มันมาหลังจาก 27 วินาที และหลังจาก 84 วินาทีในครั้งแรกที่เราเปิด เทอร์มินัลของคุณยังแสดงแถบที่เคลื่อนไหวขณะโหลดโมเดลด้วย แถบนั้นถูกตัดออกจากที่นี่
สองบรรทัดที่มีคำว่า Warning มาจากไลบรารีที่ดาวน์โหลดโมเดล ไม่ต้องสนใจก็ได้ คุณไม่ต้องมีบัญชี
ในเทอร์มินัลที่สอง บันทึก ask_judge.py แล้วรัน
🇨🇳 中文
等最后一行 Service Judge initialized 出现。用菜单的时候,这一行 1 秒后就出来了。用模型的时候,它过了 27 秒才出来;我们第一次启动时等了 84 秒。模型加载的时候,你的终端还会显示一个会动的进度条。这里把它省掉了。
带 Warning 的那两行,来自下载模型的那个库。可以不管它们。你不需要账号。
在第二个终端里,保存 ask_judge.py 并运行它。
ask_judge.py
import bentoml
# Wait up to 300 seconds for an answer. Without this the client waits 30.
judge = bentoml.SyncHTTPClient("http://localhost:3000", timeout=300)
order = "Two bowls of beef noodles, please. No chilli."
print(judge.yes_or_no(text=order, question="Does the customer order food?"))
print(judge.yes_or_no(text=order, question="Does the customer want it spicy?"))
venv\Scripts\python ask_judge.py
what it printedสิ่งที่มันพิมพ์ออกมา它打印的内容
0.908
0.0085
🇬🇧 English
The order says Two bowls of beef noodles, please. No chilli.
Does the customer order food? The model says 0.908, which is near 1: yes. Does the customer want it spicy? The model says 0.0085, which is near 0: no. The model read No chilli
and understood it.
timeout=300 in ask_judge.py is there for a reason. A client waits 30 seconds for an answer and then stops with an error. Our very first request to this service took 59 seconds, so our first try failed. After that first one, each answer took about 3 seconds.
🇹🇭 ไทย
ออร์เดอร์เขียนว่า Two bowls of beef noodles, please. No chilli.
ลูกค้าสั่งอาหารไหม โมเดลตอบ 0.908 ซึ่งใกล้ 1 คือใช่ ลูกค้าอยากได้เผ็ดไหม โมเดลตอบ 0.0085 ซึ่งใกล้ 0 คือไม่ใช่ โมเดลอ่าน No chilli
แล้วเข้าใจ
timeout=300 ใน ask_judge.py มีเหตุผลของมัน ไคลเอนต์รอคำตอบ 30 วินาที แล้วหยุดพร้อมข้อผิดพลาด คำขอแรกสุดของเราที่ส่งไปยังเซอร์วิสนี้ใช้เวลา 59 วินาที ครั้งแรกที่เราลองจึงล้มเหลว หลังจากคำขอแรกนั้น คำตอบแต่ละครั้งใช้เวลาราว 3 วินาที
🇨🇳 中文
订单写的是 Two bowls of beef noodles, please. No chilli.
顾客是在点餐吗?模型说 0.908,接近 1:是。顾客想要辣的吗?模型说 0.0085,接近 0:否。模型读到了 No chilli
,也读懂了。
ask_judge.py 里的 timeout=300 不是随便写的。客户端等答案只等 30 秒,然后就报错停下。我们发给这个服务的第一个请求用了 59 秒,所以我们第一次尝试失败了。第一个请求之后,每个回答大约用 3 秒。
- In
ask_judge.py, changeNo chilli
toExtra chilli
and run it again. Which number changes? - Ask a question of your own about the order, such as
Does the customer say please?
- ใน
ask_judge.pyเปลี่ยนNo chilli
เป็นExtra chilli
แล้วรันอีกครั้ง ตัวเลขตัวไหนเปลี่ยน - ถามคำถามของคุณเองเกี่ยวกับออร์เดอร์ เช่น
Does the customer say please?
- 在
ask_judge.py里,把No chilli
改成Extra chilli
,再运行一次。哪个数字变了? - 就这份订单问一个你自己的问题,比如
Does the customer say please?
B Tutorial B — talker.py: talk to a local LLM · บทเสริม B — talker.py: คุยกับ LLM บนเครื่องของคุณ · 教程 B —— talker.py:和本地的 LLM 对话
not run by usเราไม่ได้รันเอง我们没有运行过
ollama pull llama3.2
🇬🇧 English
A large language model, or LLM, is a model that writes text. llama3.2 is a small one. Local means that it runs on your own computer, and your words do not leave it.
Ollama is a service too. It stays on in the background and waits at port 11434. So this time your service loads no model. It passes each question on to another service, and passes the answer back. That is very common: services ask other services.
🇹🇭 ไทย
โมเดลภาษาขนาดใหญ่ หรือ LLM คือโมเดลที่เขียนข้อความ llama3.2 เป็นตัวเล็ก คำว่า local หมายความว่ามันรันบนคอมพิวเตอร์ของคุณเอง และคำพูดของคุณไม่ออกไปจากเครื่อง
Ollama ก็เป็นเซอร์วิสเหมือนกัน มันเปิดค้างอยู่เบื้องหลัง และรออยู่ที่พอร์ต 11434 ครั้งนี้เซอร์วิสของคุณจึงไม่โหลดโมเดล มันส่งคำถามแต่ละข้อต่อไปให้อีกเซอร์วิสหนึ่ง แล้วส่งคำตอบกลับมา เรื่องนี้พบได้บ่อยมาก: เซอร์วิสถามเซอร์วิสอื่น
🇨🇳 中文
大语言模型,简称 LLM,是会写文字的模型。llama3.2 是其中一个小的。“本地”的意思是它在你自己的电脑上运行,你说的话不会离开这台电脑。
Ollama 自己也是一个服务。它一直在后台开着,在 11434 端口等着。所以这一次,你的服务不加载模型。它把每个问题转交给另一个服务,再把答案传回来。这很常见:服务去问别的服务。
talker.py
import bentoml
import httpx
# Ollama is a service too. This is its address on this computer.
OLLAMA = "http://localhost:11434/api/generate"
MODEL = "llama3.2"
@bentoml.service
class Talker:
@bentoml.api
def say(self, prompt: str) -> str:
request = {"model": MODEL, "prompt": prompt, "stream": False}
reply = httpx.post(OLLAMA, json=request, timeout=120)
return reply.json()["response"]
🇬🇧 English
This class has no __init__, because it has nothing to load. Ollama keeps the model.
say takes a prompt, which is the text you give to an LLM. It builds a dictionary named request with three things in it: which model to use, the prompt, and "stream": False. That last one asks Ollama to send the whole answer at once, and not word by word.
httpx.post sends the dictionary to Ollama as JSON. You used the same call in step 5, from the other side. timeout=120 waits up to 120 seconds, because an LLM is slow. reply.json() turns Ollama's reply back into a dictionary, and the text of the answer is under the key "response".
Stop the old service and start this one. Then save ask_talker.py and run it in the second terminal.
🇹🇭 ไทย
คลาสนี้ไม่มี __init__ เพราะไม่มีอะไรต้องโหลด Ollama เป็นผู้เก็บโมเดลไว้
say รับพรอมต์ ซึ่งก็คือข้อความที่คุณส่งให้ LLM มันสร้างดิกชันนารีชื่อ request ที่มีสามอย่าง คือจะใช้โมเดลตัวไหน พรอมต์ และ "stream": False อย่างสุดท้ายนี้ขอให้ Ollama ส่งคำตอบทั้งหมดมาในครั้งเดียว ไม่ใช่ทีละคำ
httpx.post ส่งดิกชันนารีไปให้ Ollama เป็น JSON คุณใช้คำสั่งเดียวกันนี้ในขั้นที่ 5 จากอีกฝั่งหนึ่ง timeout=120 รอได้ถึง 120 วินาที เพราะ LLM ทำงานช้า reply.json() เปลี่ยนคำตอบของ Ollama กลับเป็นดิกชันนารี และข้อความของคำตอบอยู่ใต้คีย์ "response"
หยุดเซอร์วิสตัวเก่าแล้วเปิดตัวนี้ จากนั้นบันทึก ask_talker.py แล้วรันในเทอร์มินัลที่สอง
🇨🇳 中文
这个类没有 __init__,因为它没有东西要加载。模型由 Ollama 保管。
say 接收一个提示词,也就是你交给 LLM 的那段文字。它做一个叫 request 的字典,里面有三样东西:用哪个模型、提示词,还有 "stream": False。最后这一项是让 Ollama 把整个回答一次发过来,而不是一个词一个词地发。
httpx.post 把这个字典当作 JSON 发给 Ollama。第 5 步你用过同样的调用,只是站在另一边。timeout=120 表示最多等 120 秒,因为 LLM 很慢。reply.json() 把 Ollama 的回复变回字典,回答的文字放在 "response" 这个键下面。
停掉旧的服务,启动这一个。然后保存 ask_talker.py,在第二个终端里运行它。
venv\Scripts\bentoml serve talker:Talker
ask_talker.py
import bentoml
# Wait up to 300 seconds for an answer. Without this the client waits 30.
talker = bentoml.SyncHTTPClient("http://localhost:3000", timeout=300)
print(talker.say(prompt="Say hello to a new student in one short sentence."))
print(talker.say(prompt="What is a noodle? Answer in one short sentence."))
venv\Scripts\python ask_talker.py
what it printedสิ่งที่มันพิมพ์ออกมา它打印的内容
"Hello, welcome to our class! I'm so glad you're here."
A noodle is a type of starchy food made from flour, water, and sometimes eggs, typically long and thin in shape.
🇬🇧 English
Your answers will be different from ours. An LLM does not write the same sentence every time. We ran this file twice, and the two greetings were not the same.
Three programs worked together here. ask_talker.py asked your service. Your service asked Ollama. Ollama asked the model. Each one knows only the address of the next.
Now compare the two tutorials. The model in tutorial A gives a number, and a program can use a number at once: if answer > 0.5. The model in tutorial B gives a sentence, which is good for a person to read. BentoML serves both in the same way.
🇹🇭 ไทย
คำตอบของคุณจะต่างจากของเรา LLM ไม่ได้เขียนประโยคเดิมทุกครั้ง เรารันไฟล์นี้สองครั้ง และคำทักทายสองครั้งก็ไม่เหมือนกัน
ที่นี่มีสามโปรแกรมทำงานร่วมกัน ask_talker.py ถามเซอร์วิสของคุณ เซอร์วิสของคุณถาม Ollama แล้ว Ollama ถามโมเดล แต่ละตัวรู้แค่ที่อยู่ของตัวถัดไป
ทีนี้ลองเทียบบทเสริมทั้งสอง โมเดลในบทเสริม A ให้ตัวเลข และโปรแกรมใช้ตัวเลขได้ทันที: if answer > 0.5 ส่วนโมเดลในบทเสริม B ให้ประโยค ซึ่งเหมาะให้คนอ่าน BentoML เปิดทั้งสองแบบเป็นเซอร์วิสด้วยวิธีเดียวกัน
🇨🇳 中文
你得到的回答会和我们的不一样。LLM 不会每次都写出同一句话。这个文件我们运行了两次,两次的问候语就不一样。
这里有三个程序在一起工作。ask_talker.py 问你的服务。你的服务问 Ollama。Ollama 问模型。每一个都只知道下一个的地址。
现在比较一下这两个教程。教程 A 的模型给出一个数字,程序马上就能用数字:if answer > 0.5。教程 B 的模型给出一句话,适合给人读。BentoML 用同样的方式把它们变成服务。
- Change the two prompts in
ask_talker.pyand run it again. - Add a second API to
talker.py, namedtranslate. It takes a piece of text, and asks the model to translate it into Chinese. Build the prompt with"Translate into Chinese: " + text.
- เปลี่ยนพรอมต์สองข้อใน
ask_talker.pyแล้วรันอีกครั้ง - เพิ่ม API ตัวที่สองใน
talker.pyชื่อtranslateมันรับข้อความหนึ่งชิ้น แล้วขอให้โมเดลแปลเป็นภาษาจีน สร้างพรอมต์ด้วย"Translate into Chinese: " + text
- 改一改
ask_talker.py里的两个提示词,再运行一次。 - 给
talker.py加第二个 API,名叫translate。它接收一段文字,请模型把它翻译成中文。用"Translate into Chinese: " + text拼出提示词。
C Tutorial C — webshop.py: a web page with a form · บทเสริม C — webshop.py: หน้าเว็บที่มีฟอร์ม · 教程 C —— webshop.py:一个带表单的网页
🇬🇧 English
So far a program asked your service, and the answer was JSON. A person wants a web page. A BentoML service can send one, and it can read what the person types into a form.
This tutorial needs nothing new: no model, and nothing to install. It uses Starlette, a small web library that came with BentoML in step 1. You need to know a little HTML: a form, an input and a button.
🇹🇭 ไทย
ที่ผ่านมา โปรแกรมเป็นฝ่ายถามเซอร์วิสของคุณ และคำตอบเป็น JSON แต่คนต้องการหน้าเว็บ เซอร์วิส BentoML ส่งหน้าเว็บได้ และอ่านสิ่งที่คนพิมพ์ลงในฟอร์มได้
บทเสริมนี้ไม่ต้องใช้อะไรใหม่ ไม่มีโมเดล และไม่ต้องติดตั้งอะไร มันใช้ Starlette ซึ่งเป็นไลบรารีเว็บตัวเล็กที่มากับ BentoML ในขั้นที่ 1 คุณควรรู้ HTML นิดหน่อย คือฟอร์ม ช่องกรอก และปุ่ม
🇨🇳 中文
到现在为止,都是程序向你的服务提问,答案是 JSON。人想要的是网页。BentoML 服务可以发一个网页,也可以读取人在表单里填的内容。
这个教程不需要任何新东西:没有模型,也不用安装什么。它用的是 Starlette,一个小型的网页库,第 1 步里已经随 BentoML 一起装好了。你需要懂一点 HTML:表单、输入框和按钮。
webshop.py
from typing import Annotated
import bentoml
from bentoml.validators import ContentType
from starlette.applications import Starlette
from starlette.responses import HTMLResponse
from starlette.routing import Route
# What one bowl of each dish costs.
MENU = {"beef noodles": 60, "fried rice": 50, "dumplings": 40}
# A str that the browser should show as a web page.
HTML = Annotated[str, ContentType("text/html")]
FORM = """<!doctype html>
<meta charset="utf-8">
<title>Noodle shop</title>
<h1>Noodle shop</h1>
<form action="/order" method="post">
<p>Dish:
<select name="dish">
<option>beef noodles</option>
<option>fried rice</option>
<option>dumplings</option>
</select>
</p>
<p>Bowls: <input name="bowls" value="2"></p>
<p><button>Ask the price</button></p>
</form>
"""
def form_page(request):
return HTMLResponse(FORM)
@bentoml.service
@bentoml.asgi_app(Starlette(routes=[Route("/", form_page)]), path="/shop")
class Shop:
@bentoml.api
def order(self, dish: str, bowls: int) -> HTML:
total = MENU[dish] * bowls
line = str(bowls) + " bowls of " + dish + " cost " + str(total) + "."
return "<h1>" + line + "</h1><p><a href='/shop/'>Ask again</a></p>"
🇬🇧 English
The file is longer than the others, but most of it is the web page itself.
FORM is one long piece of text. Three quotation marks, """, let a piece of text run over many lines. It holds an HTML page with one form. Three things in the form matter. action="/order" is the address that the form is sent to. method="post" is the way it is sent. And each field has a name: name="dish" and name="bowls".
order is an ordinary API, like price in step 3. Look at the values it takes: dish and bowls. They are the names of the fields in the form. That is all the wiring there is. BentoML reads the form and hands each field to the value with the same name.
A form sends everything as text, so the browser sends the text "3". The hint bowls: int turns it into the number 3. If it is not a number, BentoML sends the request back, as in step 6.
HTML is a type hint with a note fixed to it. It says: this is a str, and the browser should show it as a web page. The method builds a small page from pieces of text and returns it.
form_page is there for one reason. A browser opens a page with a request named GET, and an API of BentoML answers only requests named POST. So the page with the form comes from a plain function, and the line @bentoml.asgi_app fixes that function to the service at the address /shop. Starlette and Route are the tools that do it.
Stop the old service and start this one.
🇹🇭 ไทย
ไฟล์นี้ยาวกว่าไฟล์อื่น แต่ส่วนใหญ่คือตัวหน้าเว็บเอง
FORM คือข้อความยาวหนึ่งชิ้น เครื่องหมายคำพูดสามตัว """ ทำให้ข้อความยาวได้หลายบรรทัด ข้างในเป็นหน้า HTML ที่มีฟอร์มหนึ่งอัน ในฟอร์มมีสามอย่างที่สำคัญ action="/order" คือที่อยู่ที่ฟอร์มถูกส่งไป method="post" คือวิธีส่ง และแต่ละช่องมีชื่อ: name="dish" และ name="bowls"
order เป็น API ธรรมดา เหมือน price ในขั้นที่ 3 ดูค่าที่มันรับ: dish และ bowls ซึ่งก็คือชื่อของช่องในฟอร์ม การเชื่อมต่อมีแค่นี้เอง BentoML อ่านฟอร์ม แล้วส่งแต่ละช่องให้ค่าที่มีชื่อเดียวกัน
ฟอร์มส่งทุกอย่างเป็นข้อความ เบราว์เซอร์จึงส่งข้อความ "3" ฮินต์ bowls: int เปลี่ยนมันเป็นตัวเลข 3 ถ้ามันไม่ใช่ตัวเลข BentoML จะส่งคำขอกลับไป เหมือนในขั้นที่ 6
HTML คือไทป์ฮินต์ที่มีโน้ตติดอยู่ มันบอกว่า นี่คือ str และเบราว์เซอร์ควรแสดงมันเป็นหน้าเว็บ เมธอดประกอบหน้าเล็ก ๆ จากข้อความหลายชิ้นแล้วส่งกลับ
form_page มีไว้ด้วยเหตุผลเดียว เบราว์เซอร์เปิดหน้าเว็บด้วยคำขอที่ชื่อ GET แต่ API ของ BentoML ตอบเฉพาะคำขอที่ชื่อ POST หน้าที่มีฟอร์มจึงมาจากฟังก์ชันธรรมดา และบรรทัด @bentoml.asgi_app ติดฟังก์ชันนั้นเข้ากับเซอร์วิสที่ที่อยู่ /shop ส่วน Starlette และ Route คือเครื่องมือที่ใช้ทำเรื่องนี้
หยุดเซอร์วิสตัวเก่าแล้วเปิดตัวนี้
🇨🇳 中文
这个文件比别的长,但其中大部分就是网页本身。
FORM 是一长段文字。三个引号 """ 可以让一段文字写成很多行。里面是一个 HTML 页面,带一个表单。表单里有三样东西要紧。action="/order" 是表单要发去的地址。method="post" 是发送的方式。每个输入项还有一个名字:name="dish" 和 name="bowls"。
order 是一个普通的 API,和第 3 步的 price 一样。看看它接收的值:dish 和 bowls。它们就是表单里输入项的名字。全部的连接就是这样。BentoML 读取表单,把每一项交给同名的那个值。
表单把所有东西都当作文字发送,所以浏览器发来的是文字 "3"。bowls: int 这个提示把它变成数字 3。如果它不是数字,BentoML 就把请求退回去,和第 6 步一样。
HTML 是一个附带说明的类型提示。它的意思是:这是一个 str,浏览器应该把它当作网页来显示。这个方法用几段文字拼出一个小页面,然后返回。
form_page 存在只有一个原因。浏览器打开网页用的是一种叫 GET 的请求,而 BentoML 的 API 只回答叫 POST 的请求。所以带表单的那个页面由一个普通函数给出,@bentoml.asgi_app 这一行把这个函数装到服务的 /shop 这个地址上。Starlette 和 Route 就是做这件事的工具。
停掉旧的服务,启动这一个。
venv\Scripts\bentoml serve webshop:Shop
🇬🇧 English
Open http://localhost:3000/shop/ in your browser, with the slash at the end. You see the form. Choose a dish, type a number of bowls, and press the button. The browser goes to /order and shows a page with the price. The link Ask again
takes you back to the form.
Now type the word two in the box and press the button. The browser shows the same message as in step 6, with the status code 400. The type hint checks a form in the same way as it checks JSON.
The next file does from Python what your browser just did. Save it and run it in the second terminal.
🇹🇭 ไทย
เปิด http://localhost:3000/shop/ ในเบราว์เซอร์ โดยมีเครื่องหมายทับปิดท้าย คุณจะเห็นฟอร์ม เลือกอาหาร พิมพ์จำนวนชาม แล้วกดปุ่ม เบราว์เซอร์จะไปที่ /order และแสดงหน้าที่มีราคา ลิงก์ Ask again
พาคุณกลับไปที่ฟอร์ม
ทีนี้พิมพ์คำว่า two ลงในช่องแล้วกดปุ่ม เบราว์เซอร์แสดงข้อความเดียวกับในขั้นที่ 6 พร้อมรหัสสถานะ 400 ไทป์ฮินต์ตรวจฟอร์มด้วยวิธีเดียวกับที่ตรวจ JSON
ไฟล์ถัดไปทำจาก Python ในสิ่งที่เบราว์เซอร์ของคุณเพิ่งทำ บันทึกแล้วรันในเทอร์มินัลที่สอง
🇨🇳 中文
用浏览器打开 http://localhost:3000/shop/,末尾要有斜杠。你会看到表单。选一道菜,填上碗数,按下按钮。浏览器会转到 /order,显示一个写着价格的页面。Ask again
这个链接带你回到表单。
现在在框里输入单词 two,再按按钮。浏览器显示的信息和第 6 步的一样,状态码是 400。类型提示检查表单的方式,和检查 JSON 的方式一样。
下一个文件用 Python 做你的浏览器刚才做的事。保存它,在第二个终端里运行。
ask_webshop.py
import httpx
# What a browser does when you open the page.
page = httpx.get("http://localhost:3000/shop/")
print(page.status_code, page.headers["content-type"])
# What a browser does when you press the button. data= sends a form.
form = {"dish": "dumplings", "bowls": "3"}
reply = httpx.post("http://localhost:3000/order", data=form)
print(reply.status_code, reply.headers["content-type"])
print(reply.text)
venv\Scripts\python ask_webshop.py
what it printedสิ่งที่มันพิมพ์ออกมา它打印的内容
200 text/html; charset=utf-8
200 text/html; charset=utf-8
<h1>3 bowls of dumplings cost 120.</h1><p><a href='/shop/'>Ask again</a></p>
🇬🇧 English
The first line is the page with the form: the status code 200, and text/html, which is how a reply says that it is a web page. The second line is the reply to the form, and the third is the page that order built.
One word is different from step 5. There the file sent JSON with json=. Here it sends a form with data=. The API takes both.
🇹🇭 ไทย
บรรทัดแรกคือหน้าที่มีฟอร์ม: รหัสสถานะ 200 และ text/html ซึ่งเป็นวิธีที่คำตอบบอกว่ามันเป็นหน้าเว็บ บรรทัดที่สองคือคำตอบของฟอร์ม และบรรทัดที่สามคือหน้าที่ order สร้างขึ้น
มีคำหนึ่งที่ต่างจากขั้นที่ 5 ที่นั่นไฟล์ส่ง JSON ด้วย json= ส่วนที่นี่ส่งฟอร์มด้วย data= API รับได้ทั้งสองแบบ
🇨🇳 中文
第一行是带表单的那个页面:状态码 200,还有 text/html,回复就是用它来表明自己是一个网页。第二行是对表单的回复,第三行是 order 拼出来的页面。
有一个词和第 5 步不同。那里文件用 json= 发送 JSON。这里用 data= 发送表单。这个 API 两种都接收。
- Add a field to the form:
<p>Your name: <input name="customer"></p>. Addcustomer: strto the values thatordertakes, and put the name into the page it returns. - Add a fourth dish. It needs a line in
MENUand an<option>in the form.
- เพิ่มช่องในฟอร์ม:
<p>Your name: <input name="customer"></p>เพิ่มcustomer: strลงในค่าที่orderรับ แล้วใส่ชื่อลงในหน้าที่มันส่งกลับ - เพิ่มอาหารอย่างที่สี่ ต้องมีหนึ่งบรรทัดใน
MENUและ<option>หนึ่งอันในฟอร์ม
- 给表单加一项:
<p>Your name: <input name="customer"></p>。在order接收的值里加上customer: str,再把名字放进它返回的页面里。 - 加第四道菜。
MENU里要加一行,表单里要加一个<option>。
D Tutorial D — the service on your local network · บทเสริม D — เซอร์วิสบนเครือข่ายในบ้านของคุณ · 教程 D —— 让局域网里的设备用上你的服务
🇬🇧 English
localhost is an address that only your own computer can use. A phone in the same room cannot open http://localhost:3000. On the phone, localhost means the phone.
To reach your service, another device needs your computer's address on the local network. The local network is all the devices on the same Wi-Fi. Find your address with this command, and look for the line IPv4 Address under your Wi-Fi. It looks like 192.168.1.20.
🇹🇭 ไทย
localhost คือที่อยู่ที่ใช้ได้เฉพาะคอมพิวเตอร์ของคุณเอง โทรศัพท์ที่อยู่ในห้องเดียวกันเปิด http://localhost:3000 ไม่ได้ บนโทรศัพท์ localhost หมายถึงตัวโทรศัพท์เอง
อุปกรณ์อื่นจะเข้าถึงเซอร์วิสของคุณได้ ต้องใช้ที่อยู่ของคอมพิวเตอร์คุณบนเครือข่ายท้องถิ่น เครือข่ายท้องถิ่นคืออุปกรณ์ทั้งหมดที่ใช้ Wi-Fi เดียวกัน หาที่อยู่ของคุณด้วยคำสั่งนี้ แล้วมองหาบรรทัด IPv4 Address ใต้ Wi-Fi ของคุณ หน้าตาของมันประมาณ 192.168.1.20
🇨🇳 中文
localhost 这个地址只有你自己的电脑能用。同一个房间里的手机打不开 http://localhost:3000。在手机上,localhost 指的是手机自己。
别的设备要用上你的服务,就需要你的电脑在局域网里的地址。局域网就是连着同一个 Wi-Fi 的所有设备。用这条命令查出你的地址,在你的 Wi-Fi 下面找 IPv4 Address 那一行。它的样子像 192.168.1.20。
🪟 WINDOWS
ipconfig
🍎 MAC · not run by us · เราไม่ได้รันเอง · 我们没有运行过
ipconfig getifaddr en0
🇬🇧 English
Start the service of tutorial C in the usual way, with bentoml serve webshop:Shop. Then, on another device on the same Wi-Fi, open http://192.168.1.20:3000/shop/, with your own address in place of this one.
We tested it from a second computer. It runs Linux and has no screen, so we did not use a browser. We used curl, a program that sends web requests from a terminal. -d sends a form.
🇹🇭 ไทย
เปิดเซอร์วิสของบทเสริม C ตามปกติ ด้วย bentoml serve webshop:Shop จากนั้นบนอุปกรณ์อื่นที่ใช้ Wi-Fi เดียวกัน เปิด http://192.168.1.20:3000/shop/ โดยใส่ที่อยู่ของคุณเองแทนที่อยู่นี้
เราทดสอบจากคอมพิวเตอร์เครื่องที่สอง มันรัน Linux และไม่มีจอ เราจึงไม่ได้ใช้เบราว์เซอร์ เราใช้ curl ซึ่งเป็นโปรแกรมที่ส่งคำขอทางเว็บจากเทอร์มินัล ส่วน -d ใช้ส่งฟอร์ม
🇨🇳 中文
像平常一样启动教程 C 的服务:bentoml serve webshop:Shop。然后在连着同一个 Wi-Fi 的另一台设备上,打开 http://192.168.1.20:3000/shop/,把这个地址换成你自己的。
我们是在第二台电脑上测试的。它运行 Linux,没有屏幕,所以我们没有用浏览器。我们用的是 curl,一个从终端发送网络请求的程序。-d 用来发送表单。
on the second computerบนคอมพิวเตอร์เครื่องที่สอง在第二台电脑上
curl -d "dish=dumplings&bowls=3" http://192.168.1.20:3000/order
what the second computer gotสิ่งที่คอมพิวเตอร์เครื่องที่สองได้รับ第二台电脑得到的内容
<h1>3 bowls of dumplings cost 120.</h1><p><a href='/shop/'>Ask again</a></p>
bentoml serve prints says listening on http://localhost:3000, but BentoML listens on every network that your computer is on. We checked it with the plain command of step 4, and the second computer got its answer. So everybody on the same Wi-Fi can use your service while it runs. At home that is fine. On the Wi-Fi of a school or a café, think first.The first time, Windows may ask whether other devices may reach Python. We were not asked on this laptop, so we cannot show you that question. If another device cannot connect, the firewall of your computer is the first thing to look at.คุณไม่ได้ทำอะไรใหม่เลยเพื่อให้สิ่งนี้ใช้ได้ บรรทัดแรกที่ bentoml serve พิมพ์ออกมาบอกว่า listening on http://localhost:3000 แต่ BentoML รับคำขอจากทุกเครือข่ายที่คอมพิวเตอร์ของคุณเชื่อมอยู่ เราตรวจสอบด้วยคำสั่งธรรมดาของขั้นที่ 4 และคอมพิวเตอร์เครื่องที่สองก็ได้คำตอบ ดังนั้นทุกคนที่ใช้ Wi-Fi เดียวกันใช้เซอร์วิสของคุณได้ตลอดเวลาที่มันรันอยู่ ที่บ้านไม่เป็นไร แต่บน Wi-Fi ของโรงเรียนหรือร้านกาแฟ ให้คิดก่อนครั้งแรก Windows อาจถามว่าจะให้อุปกรณ์อื่นเข้าถึง Python ได้หรือไม่ บนแล็ปท็อปเครื่องนี้เราไม่ถูกถาม จึงแสดงคำถามนั้นให้ดูไม่ได้ ถ้าอุปกรณ์อื่นเชื่อมต่อไม่ได้ ไฟร์วอลล์ของคอมพิวเตอร์คุณคือสิ่งแรกที่ควรดู为了让它能用,你并没有做任何新的事。bentoml serve 打印的第一行写着 listening on http://localhost:3000,但 BentoML 其实在你的电脑连着的每一个网络上都在接收请求。我们用第 4 步那条普通的命令验证过,第二台电脑照样得到了答案。所以只要服务在运行,连着同一个 Wi-Fi 的每个人都能用它。在家里这没问题。在学校或咖啡店的 Wi-Fi 上,先想一想。第一次运行时,Windows 可能会问是否允许别的设备访问 Python。在这台笔记本上我们没有被问到,所以没法把那个提问给你看。如果别的设备连不上,首先要检查的就是你电脑的防火墙。
🇬🇧 English
To keep the service to your own computer, add --host 127.0.0.1 to the command. 127.0.0.1 is localhost written as a number.
🇹🇭 ไทย
ถ้าจะให้เซอร์วิสใช้ได้เฉพาะคอมพิวเตอร์ของคุณเอง ให้เพิ่ม --host 127.0.0.1 ลงในคำสั่ง 127.0.0.1 ก็คือ localhost ที่เขียนเป็นตัวเลข
🇨🇳 中文
想让服务只给你自己的电脑用,就在命令里加上 --host 127.0.0.1。127.0.0.1 就是写成数字的 localhost。
venv\Scripts\bentoml serve webshop:Shop --host 127.0.0.1
🇬🇧 English
Now the first line says listening on http://127.0.0.1:3000. Your own computer still gets its answers. The second computer sent the same command as before, and this time it waited for eight seconds and gave up:
🇹🇭 ไทย
ตอนนี้บรรทัดแรกบอกว่า listening on http://127.0.0.1:3000 คอมพิวเตอร์ของคุณเองยังได้คำตอบอยู่ คอมพิวเตอร์เครื่องที่สองส่งคำสั่งเดิม และครั้งนี้มันรอแปดวินาทีแล้วเลิกรอ:
🇨🇳 中文
现在第一行写的是 listening on http://127.0.0.1:3000。你自己的电脑照样能得到答案。第二台电脑发了和刚才一样的命令,这一次它等了八秒钟就放弃了:
what the second computer gotสิ่งที่คอมพิวเตอร์เครื่องที่สองได้รับ第二台电脑得到的内容
curl: (28) Connection timed out after 8002 milliseconds
🇬🇧 English
A local address stops at your router, the box that gives you Wi-Fi. A friend in another house cannot use 192.168.1.20. Tutorial E is about that.
🇹🇭 ไทย
ที่อยู่ท้องถิ่นไปได้ไกลแค่เราเตอร์ของคุณ ซึ่งก็คือกล่องที่ปล่อย Wi-Fi ให้คุณ เพื่อนที่อยู่อีกบ้านหนึ่งใช้ 192.168.1.20 ไม่ได้ บทเสริม E พูดถึงเรื่องนี้
🇨🇳 中文
局域网地址到你的路由器为止,路由器就是给你提供 Wi-Fi 的那个盒子。住在另一栋房子里的朋友用不了 192.168.1.20。教程 E 讲的就是这件事。
E Tutorial E — Tailscale: from anywhere, and then the internet · บทเสริม E — Tailscale: จากที่ไหนก็ได้ แล้วจึงถึงอินเทอร์เน็ต · 教程 E —— Tailscale:从任何地方访问,再到整个互联网
🇬🇧 English
First, your own devices. Tailscale gives each device an address that begins with 100. Ask your computer for its own:
🇹🇭 ไทย
เริ่มจากอุปกรณ์ของคุณเองก่อน Tailscale ให้ที่อยู่ที่ขึ้นต้นด้วย 100 แก่อุปกรณ์แต่ละเครื่อง ถามคอมพิวเตอร์ของคุณว่าของมันคืออะไร:
🇨🇳 中文
先说你自己的设备。Tailscale 给每台设备一个以 100 开头的地址。问问你的电脑,它的地址是什么:
tailscale ip -4
🇬🇧 English
Start the service in the usual way, without --host. On your other device, use the address that begins with 100, as in http://100.x.y.z:3000/shop/. This is meant to work when the two devices are on different networks, such as a laptop at home and a phone outside. Only devices that are signed in to your account can do it.
We sent the form from a second device in our Tailscale network to the laptop's address that begins with 100. Both devices were in the same building, so we did not test it from far away.
🇹🇭 ไทย
เปิดเซอร์วิสตามปกติ โดยไม่ใส่ --host บนอุปกรณ์อีกเครื่องของคุณ ใช้ที่อยู่ที่ขึ้นต้นด้วย 100 เช่น http://100.x.y.z:3000/shop/ วิธีนี้ออกแบบมาให้ใช้ได้เมื่ออุปกรณ์สองเครื่องอยู่คนละเครือข่าย เช่นแล็ปท็อปอยู่ที่บ้านและโทรศัพท์อยู่ข้างนอก มีแต่อุปกรณ์ที่ลงชื่อเข้าใช้บัญชีของคุณเท่านั้นที่ทำได้
เราส่งฟอร์มจากอุปกรณ์เครื่องที่สองในเครือข่าย Tailscale ของเรา ไปยังที่อยู่ที่ขึ้นต้นด้วย 100 ของแล็ปท็อป อุปกรณ์ทั้งสองอยู่ในอาคารเดียวกัน เราจึงไม่ได้ทดสอบจากที่ไกล ๆ
🇨🇳 中文
像平常一样启动服务,不加 --host。在你的另一台设备上,用那个以 100 开头的地址,比如 http://100.x.y.z:3000/shop/。它的设计就是让两台设备在不同的网络里也能用,比如笔记本在家里,手机在外面。只有登录了你的账号的设备才能这样做。
我们从 Tailscale 网络里的第二台设备,把表单发到了笔记本那个以 100 开头的地址。两台设备在同一栋楼里,所以我们没有从很远的地方测试。
what the second device gotสิ่งที่อุปกรณ์เครื่องที่สองได้รับ第二台设备得到的内容
<h1>2 bowls of fried rice cost 100.</h1><p><a href='/shop/'>Ask again</a></p>
🇬🇧 English
Then, the whole internet. Tailscale has a feature named Funnel for this. One command gives your service a public address:
🇹🇭 ไทย
จากนั้นคืออินเทอร์เน็ตทั้งหมด Tailscale มีฟีเจอร์ชื่อ Funnel สำหรับเรื่องนี้ คำสั่งเดียวก็ให้ที่อยู่สาธารณะแก่เซอร์วิสของคุณ:
🇨🇳 中文
然后是整个互联网。Tailscale 有一个叫 Funnel 的功能,就是做这件事的。一条命令就给你的服务一个公开的地址:
not run by usเราไม่ได้รันเอง我们没有运行过
tailscale funnel 3000
🇬🇧 English
3000 is the port of your service. Tailscale prints an address that looks like https://your-computer.your-tailnet.ts.net. Anyone in the world who has that address can open it, and they do not need Tailscale. Put /shop/ at the end to get the form.
Leave the command running. Ctrl+C stops it and closes the address. The first time, Tailscale gives you a link to a web page where you switch Funnel on for your account.
We did not run this command for this page. What we say about it is from Tailscale's own documentation and from the help that the command prints.
🇹🇭 ไทย
3000 คือพอร์ตของเซอร์วิสของคุณ Tailscale จะพิมพ์ที่อยู่ที่หน้าตาแบบ https://your-computer.your-tailnet.ts.net ใครก็ตามในโลกที่มีที่อยู่นั้นเปิดได้ และไม่ต้องมี Tailscale ใส่ /shop/ ต่อท้ายเพื่อไปที่ฟอร์ม
ปล่อยให้คำสั่งรันต่อไป Ctrl+C หยุดมันและปิดที่อยู่นั้น ครั้งแรก Tailscale จะให้ลิงก์ไปยังหน้าเว็บที่คุณเปิดใช้ Funnel สำหรับบัญชีของคุณ
เราไม่ได้รันคำสั่งนี้สำหรับหน้านี้ สิ่งที่เราพูดถึงมันมาจากเอกสารของ Tailscale เอง และจากข้อความช่วยเหลือที่คำสั่งพิมพ์ออกมา
🇨🇳 中文
3000 是你的服务的端口。Tailscale 会打印一个地址,样子像 https://your-computer.your-tailnet.ts.net。世界上任何拿到这个地址的人都能打开它,而且不需要 Tailscale。在末尾加上 /shop/ 就是那个表单。
让这条命令一直运行。Ctrl+C 会停掉它,同时关闭那个地址。第一次用的时候,Tailscale 会给你一个链接,打开那个网页,为你的账号开启 Funnel。
这一页我们没有运行这条命令。关于它的说法,来自 Tailscale 自己的文档,以及这条命令打印的帮助信息。
8 Pack it into a Bento · แพ็กลงใน Bento · 把它装进一个 Bento
🇬🇧 English
Your service runs on your computer, in your folder, with your venv. To run it on another computer, that computer needs the same files and the same libraries. BentoML can pack all of that into one box. It calls the box a Bento, after the Japanese lunch box that holds a full meal.
Save this file as bentofile.yaml. It has one line, and it says which service to pack: the class Shop in the file shop.py.
🇹🇭 ไทย
เซอร์วิสของคุณรันบนคอมพิวเตอร์ของคุณ ในโฟลเดอร์ของคุณ ด้วย venv ของคุณ ถ้าจะรันบนคอมพิวเตอร์เครื่องอื่น เครื่องนั้นต้องมีไฟล์เดียวกันและไลบรารีเดียวกัน BentoML แพ็กทั้งหมดนั้นลงในกล่องใบเดียวได้ มันเรียกกล่องนี้ว่า Bento ตามชื่อกล่องข้าวแบบญี่ปุ่นที่ใส่อาหารครบมื้อ
บันทึกไฟล์นี้ในชื่อ bentofile.yaml มันมีบรรทัดเดียว และบอกว่าจะแพ็กเซอร์วิสตัวไหน: คลาส Shop ในไฟล์ shop.py
🇨🇳 中文
你的服务在你的电脑上运行,在你的文件夹里,用的是你的 venv。要在另一台电脑上运行它,那台电脑就得有同样的文件和同样的库。BentoML 可以把这些全部装进一个盒子。它把这个盒子叫作 Bento,名字来自日本的便当盒,里面装着完整的一餐。
把这个文件存为 bentofile.yaml。它只有一行,说明要装哪个服务:文件 shop.py 里的 Shop 类。
bentofile.yaml
service: "shop:Shop"
🇬🇧 English
Then build the Bento. You can do this in the second terminal. The service may go on running in the first one.
🇹🇭 ไทย
จากนั้นสร้าง Bento คุณทำในเทอร์มินัลที่สองได้ เซอร์วิสรันต่อในเทอร์มินัลแรกได้
🇨🇳 中文
然后构建 Bento。你可以在第二个终端里做。第一个终端里的服务可以继续运行。
venv\Scripts\bentoml build
what it printedสิ่งที่มันพิมพ์ออกมา它打印的内容
INFO: Adding BentoML requirement to the image: bentoml==1.4.39.
INFO: Locking PyPI package versions.
INFO: Locking packages for x86_64-manylinux_2_36. Pass `--platform` option to specify the platform.
██████╗ ███████╗███╗ ██╗████████╗ ██████╗ ███╗ ███╗██╗
██╔══██╗██╔════╝████╗ ██║╚══██╔══╝██╔═══██╗████╗ ████║██║
██████╔╝█████╗ ██╔██╗ ██║ ██║ ██║ ██║██╔████╔██║██║
██╔══██╗██╔══╝ ██║╚██╗██║ ██║ ██║ ██║██║╚██╔╝██║██║
██████╔╝███████╗██║ ╚████║ ██║ ╚██████╔╝██║ ╚═╝ ██║███████╗
╚═════╝ ╚══════╝╚═╝ ╚═══╝ ╚═╝ ╚═════╝ ╚═╝ ╚═╝╚══════╝
Successfully built Bento(tag="shop:6l4xfrwd5sf334di").
Next steps:
* Deploy to BentoCloud:
$ bentoml deploy shop:6l4xfrwd5sf334di -n ${DEPLOYMENT_NAME}
* Update an existing deployment on BentoCloud:
$ bentoml deployment update --bento shop:6l4xfrwd5sf334di
${DEPLOYMENT_NAME}
* Containerize your Bento with `bentoml containerize`:
$ bentoml containerize shop:6l4xfrwd5sf334di
* Push to BentoCloud with `bentoml push`:
$ bentoml push shop:6l4xfrwd5sf334di
🇬🇧 English
The line that matters is Successfully built Bento. After it comes the tag of your Bento. A tag has two halves with a colon between them: the name, shop, and a version that BentoML made up. Every build gets a new version, so your letters will be different from ours.
The three lines at the top say that BentoML wrote down which libraries the service needs, with their exact versions. It writes that list for Linux, because most services run on Linux computers. The lines under Next steps
are about putting the Bento online. You do not need them for this page.
The next command lists the Bentos on your computer.
🇹🇭 ไทย
บรรทัดที่สำคัญคือ Successfully built Bento ต่อจากนั้นคือแท็กของ Bento ของคุณ แท็กมีสองครึ่ง คั่นด้วยเครื่องหมายโคลอน: ชื่อ คือ shop และเวอร์ชันที่ BentoML ตั้งขึ้นเอง การสร้างแต่ละครั้งได้เวอร์ชันใหม่ ตัวอักษรของคุณจึงจะต่างจากของเรา
สามบรรทัดบนสุดบอกว่า BentoML จดไว้ว่าเซอร์วิสต้องใช้ไลบรารีอะไรบ้าง พร้อมเวอร์ชันที่แน่นอน มันเขียนรายการนั้นสำหรับ Linux เพราะเซอร์วิสส่วนใหญ่รันบนคอมพิวเตอร์ Linux ส่วนบรรทัดใต้ Next steps
เป็นเรื่องการนำ Bento ขึ้นออนไลน์ หน้านี้ไม่ต้องใช้
คำสั่งถัดไปแสดงรายการ Bento บนคอมพิวเตอร์ของคุณ
🇨🇳 中文
要紧的是 Successfully built Bento 这一行。它后面是你的 Bento 的标签。标签分成两半,中间是一个冒号:前面是名字 shop,后面是 BentoML 自己起的版本号。每次构建都会得到一个新版本,所以你那串字母会和我们的不一样。
最上面三行说的是,BentoML 记下了服务需要哪些库,以及它们的确切版本。这份清单是为 Linux 写的,因为大多数服务都运行在 Linux 电脑上。Next steps
下面的那些行,讲的是怎样把 Bento 放到网上。这一页用不到。
下一条命令列出你电脑上的 Bento。
venv\Scripts\bentoml list
what it printedสิ่งที่มันพิมพ์ออกมา它打印的内容
Tag Size Model Size Creation Time
shop:6l4xfrwd5sf334di 23.19 KiB 0.00 B 2026-10-09 21:23:08
🇬🇧 English
BentoML keeps its Bentos in a folder named bentoml in your home folder. Our Bento held three things: a copy of the files in our folder, without the venv folder; the list of libraries; and a description of the two APIs.
A Bento can be served by its tag. Stop the service in the first terminal, and start it again from the box:
🇹🇭 ไทย
BentoML เก็บ Bento ไว้ในโฟลเดอร์ชื่อ bentoml ในโฮมโฟลเดอร์ของคุณ Bento ของเรามีสามอย่าง: สำเนาของไฟล์ในโฟลเดอร์ของเรา โดยไม่มีโฟลเดอร์ venv รายการไลบรารี และคำอธิบายของ API ทั้งสองตัว
Bento เปิดเป็นเซอร์วิสได้ด้วยแท็กของมัน หยุดเซอร์วิสในเทอร์มินัลแรก แล้วเปิดใหม่จากกล่อง:
🇨🇳 中文
BentoML 把 Bento 放在你的主文件夹里一个名叫 bentoml 的文件夹中。我们的 Bento 里有三样东西:我们文件夹里那些文件的副本,不包括 venv 文件夹;库的清单;还有对两个 API 的描述。
可以用标签来运行一个 Bento。停掉第一个终端里的服务,再从盒子里把它启动:
venv\Scripts\bentoml serve shop:latest
🇬🇧 English
latest means the newest version. This service does not read shop.py from your folder. It reads the copy inside the box. We tested that by starting it from a different folder, and it answered.
The usual next step is bentoml containerize. It turns a Bento into a container image, which is a package that a server can run by itself. That command needs a program named Docker, and we did not run it for this page.
🇹🇭 ไทย
latest หมายถึงเวอร์ชันใหม่ที่สุด เซอร์วิสนี้ไม่ได้อ่าน shop.py จากโฟลเดอร์ของคุณ มันอ่านสำเนาที่อยู่ในกล่อง เราทดสอบเรื่องนี้ด้วยการเปิดมันจากโฟลเดอร์อื่น และมันก็ตอบ
ขั้นต่อไปตามปกติคือ bentoml containerize มันเปลี่ยน Bento ให้เป็นคอนเทนเนอร์อิมเมจ ซึ่งเป็นแพ็กเกจที่เซิร์ฟเวอร์รันได้เลยโดยไม่ต้องมีอะไรเพิ่ม คำสั่งนั้นต้องใช้โปรแกรมชื่อ Docker และเราไม่ได้รันมันสำหรับหน้านี้
🇨🇳 中文
latest 的意思是最新的版本。这个服务读的不是你文件夹里的 shop.py,而是盒子里的那份副本。我们换了一个文件夹来启动它,它照样回答了,这就验证了这一点。
通常的下一步是 bentoml containerize。它把 Bento 变成一个容器镜像,也就是服务器不需要别的东西就能直接运行的一个包。这条命令需要一个叫 Docker 的程序,这一页我们没有运行它。
🧭 What to do next · ทำอะไรต่อดี · 接下来做什么
🇬🇧 English
You have done the three things that BentoML is for. You wrote a service as a class. You served it and asked it from another program. You packed it into a Bento. A service with an AI model in it is built in the same three steps.
Now make a service of your own. Here are three ideas, from easy to hard:
- A service with an API
grade. It takes a score from 0 to 100 and returns a letter from A to F. - A service that reads a file of English words with their Chinese meanings in
__init__, and has an APImeaningthat takes a word and returns its meaning. - A service that keeps a count in
self. Each request adds one to it, and the reply says how many requests have come in.
If classes are still new to you, read Python classes. The other AI lessons are on this page.
🇹🇭 ไทย
คุณได้ทำสามอย่างที่ BentoML มีไว้ให้ทำแล้ว คุณเขียนเซอร์วิสเป็นคลาส คุณเปิดมันและถามมันจากอีกโปรแกรมหนึ่ง คุณแพ็กมันลงใน Bento เซอร์วิสที่มีโมเดล AI อยู่ข้างในก็สร้างด้วยสามขั้นเดียวกันนี้
ตอนนี้ลองสร้างเซอร์วิสของคุณเอง นี่คือสามไอเดีย จากง่ายไปยาก:
- เซอร์วิสที่มี API ชื่อ
gradeมันรับคะแนนตั้งแต่ 0 ถึง 100 และส่งเกรดเป็นตัวอักษร A ถึง F กลับมา - เซอร์วิสที่อ่านไฟล์คำศัพท์ภาษาอังกฤษพร้อมความหมายภาษาจีนใน
__init__และมี API ชื่อmeaningที่รับคำหนึ่งคำแล้วส่งความหมายกลับมา - เซอร์วิสที่เก็บตัวนับไว้ใน
selfคำขอแต่ละครั้งบวกหนึ่งให้ตัวนับ และคำตอบบอกว่ามีคำขอเข้ามาแล้วกี่ครั้ง
ถ้าคลาสยังเป็นเรื่องใหม่สำหรับคุณ อ่าน คลาสใน Python ส่วนบทเรียน AI อื่น ๆ อยู่ในหน้านี้
🇨🇳 中文
BentoML 的三件事,你都做过了。你把服务写成了一个类。你启动了它,并从另一个程序向它提问。你把它装进了一个 Bento。带 AI 模型的服务,也是用这三步做出来的。
现在做一个你自己的服务吧。这里有三个点子,从易到难:
- 一个带
grade这个 API 的服务。它接收 0 到 100 的分数,返回 A 到 F 的一个字母。 - 一个在
__init__里读取文件的服务,文件里是英语单词和它们的中文意思;它有一个叫meaning的 API,接收一个单词,返回它的意思。 - 一个在
self里记数的服务。每来一个请求就加一,回复里说一共来过多少个请求。