Taxi Example is the simplest service to order a taxi. The service allows the customer to get to the right place quickly, and the drivers provide a constant flow of orders.
The customer orders a taxi through CustomerBot, specifying:
- pick up location,
- destination point.
The driver provides through the DriverBot:
- phone number/name on the first visit (registration),
- location,
- radius in which the taxi service is provided.
TaxiService service based on these data finds the best driver on the customer's order.
The service includes three roles:
The customer, CustomerBot:
- creates the order,
- searches for a driver,
- manages the state of the trip.
The driver, DriverBot:
- starts working (informs the system that he works and determines the geography of orders),
- stops working (tells the system that he has finished working),
- accepts the order,
- rejects the order,
- informs about the waiting for the customer,
- informs about the beginning of the trip,
- informs about the end of the trip,
- reports readiness for new trips.
The system, TaxiService:
- registers the customer,
- registers the driver,
- calculates the trip distance and cost,
- connects customer and driver,
- registers the start/end of the driver's work,
- registers and monitors the state of the trip,
- cancels the trip,
- looking for suitable drivers,
- sends offers to drivers for the trip,
- registers the agreement/disagreement to make a trip by the driver.
The customer's role is implemented as a chat-bot for two channels Viber and Telegram.
The driver's role is implemented as a separate chat-bot in Telegram.
The system's role is implemented as a separate REST API application.
The general scheme of the service is shown below:
Before you start, you should make sure that the system has:
- Python 3.9 - 3.11,
- Git,
- Poetry,
- Pagekite.
We recommend to just download pagekite.py and use it as a separate scenario.
curl -O https://pagekite.net/pk/pagekite.py
It's also required:
- To register and receive a token for the Routing Service,
- To create two bots in
Telegram: driver’s chat and customer's chat: see BotFather and instruction, - To create a
Viberbot for customer: see instruction.
Note You'll need a smartphone with Telegram and Viber apps to work properly. Desktop versions of Telegram and Viber do not fit, as they don't allow you to send the location.
Clone this repository and change current directory
git clone https://github.com/maxbot-ai/taxi_bot
cd taxi_botInstall the package with dependencies
poetry shell
poetry installThe service implements the entire business logic of the project:
- registers the driver and customers,
- calculates the trip distance and cost,
- builds the route,
- connects customer and driver,
- monitors the state of the trip.
Below is a scheme of the service with some details that will be used to customize the example.
-
To handle incoming requests from
DriverBotandCustomerBotTaxiServiceprovidesHTTP REST API(it is a server). Thebind_portparameter sets the port whichTaxiService REST APIwill use. The address and port must be set in the startup parameters ofDriverBotandCustomerBot. The file in whichbind_portis set and the commands to start the bots are described below. -
REST APIis also used to transfer data toDriverBotand toCustomerBot. In this caseTaxiServiceis the client. Thedriver_bot_urlandcustomer_bot_urlparameters intaxi_bot.conffile are responsible for this. These URLs can be either local (localhost) or external when using tunnels. -
The external service openrouteservice.org is used to generate a map and calculate travel distances. The service supports access via
HTTP REST API.TaxiServiceis a client. To work with the service you need to register for free and get the access key. See instructions. Theopenrouteservice_tokenparameter defines a key for using the route building API. This setting is set in the filetaxi_bot.conf.
To configure the service, save the file taxi_bot.conf.template with the name taxi_bot.conf.
cp taxi_bot.conf.template taxi_bot.confNext, open the taxi_bot.conf file in your favorite editor and set the actual values for all parameters:
bind_port— port on whichTaxiServicewill wait to incomingHTTPrequests. For example,6010. Requests fromCustomerBotandDriverBotwill be received on this port.openrouteservice_token— token to access the openrouteservice.org service. Free registration is required to obtain the key. See instructions.driver_bot_url— address for interaction withDriverBot. For example,http://localhost:6011.customer_bot_url— address for interaction withCustomerBot. For example,http://localhost:6012.image_storage_url— external address whereTaxiServiceshould be available. The external address is required in order to send pictures toTelegramandViber. You can use pagekite or ngrok to get the external address and forward it to the local port ofTaxiService. See examples of launches below.upload_file_path— full path to the directory where the maps of the built trips will be saved. For example,/tmp.
-
We will use pagekite to get an external address and create a tunnel to the
TaxiService. You can use any other similar utility, such as ngrok or setup communication throughreverse-proxy. -
If you use pagekite, you need to register and specify the subdomain to be used to create the tunnel. For example,
taxiexample.pagekite.me. -
Run
pagekite. Setbind_port, for example6010and specify the desired external address (it shouldn't be occupied by another person, if there is an error, then come up with a different name). For example,python3 pagekite.py 6010 taxiexample.pagekite.meThen follow the instructions at the
pagekitecommand line. As a result,pagekiteshould create a tunnel. You should see the following line in console:<> Flying localhost:6010 as https://taxiexample.pagekite.me/ -
Create a directory to store routing files. For example,
/home/user_name/tmp/or use/tmpif access rights allow. And don't forget to write the full path in thetaxi_bot.conffile,upload_file_pathparameter. -
Make sure that you are in the
taxi_botdirectory. -
Run the service with the command
taxi_bot --config taxi_bot.conf
The bot implements the interaction between the driver and TaxiService. It provides the following features:
- driver registration,
- confirmation and cancellation of the trip,
- trip execution.
- The bot supports the
Telegramchannel. Interaction with theTelegramservice is realized through theWebHooksmechanism. You should have an external address to registerWebHook. See the Launch section. DriverBottransfers data toTaxiServiceviaREST API. TheDriverBotis a client. The address to access is set in the bot settings. See the Setup section,TAXI_BOT_APIparameter.DriverBotreceives data fromTaxiServiceviaREST APItoo. TheDriverBotis the server. See theportparameter in theDriverBotstart command below.
To configure the bot, save the file env.template with the name .env in the taxi_bot directory. Оpen file .env in your favorite editor and set the actual values for all parameters:
TELEGRAM_DRIVER_API_KEY— the token ofTelegrambot, that will becomeDriverBot.TELEGRAM_CUSTOMER_API_KEY— the token ofTelegrambot, that will becomeCustomerBotinTelegram.VIBER_CUSTOMER_API_KEY— the token ofViberbot, that will becomeCustomerBotinViber.TAXI_BOT_API— address of theTaxiServiceservice. Specifyhttp://127.0.0.1:6010, if you used this value when configuringTaxiService.
All fields are required in the current version of the bot.
- In the pagekite control panel, add the
kiteto create another tunnel. For example,driver-taxiexample.pagekite.me. To do this, click theadd kitebutton. - Make sure you are in the
taxi_botdirectory. - Select the port for
DriverBot, for example,6011. - Run the bot with the command
In our particular example:
maxbot run --bot taxi_bot.bot:driver --updater=webhooks --host=localhost --port=<BIND_PORT> --public-url=<DRIVER_BOT_PUBLIC_URL>maxbot run --bot taxi_bot.bot:driver --updater=webhooks --host=localhost --port=6011 --public-url=https://driver-taxiexample.pagekite.me - Run
pagekite. Set the port and the external address. For example,python3 pagekite.py 6011 driver-taxiexample.pagekite.me - Make sure that
pagekitehas started successfully and created a tunnel.
The bot implements the interaction between the customer and TaxiService. It provides the following features:
- customer registration,
- ordering a taxi,
- change the status of the trip.
- The bot supports the
Telegramand theViberchannels. Interaction with these services is realized through theWebHooksmechanism. You should have an external address to registerWebHook. See the Launch section. CustomerBottransfers data toTaxiServiceviaREST APIas well as theDriverBot. See the appropriate section.CustomerBotreceives data fromTaxiServiceviaREST APIas well as theDriverBot. See the appropriate section.
See the appropriate section for the DriverBot.
- In the pagekite control panel, add the
kiteto create another tunnel. For example,customer-taxiexample.pagekite.me. To do this, click the `add kite' button. - Select the port for
CustomerBot, for example,6012. - For
CustomerBotit is important to create the tunnel first, and then run the bot (due to the features of Viber and pagekite). Run pagekite. Set the port and the external address, for example,python3 pagekite.py 6012 customer-taxiexample.pagekite.me - Make sure that
pagekitehas started successfully and created a tunnel onbind_port. - Make sure you are in the
taxi_botdirectory. - Run the bot with the command
In our particular example:
maxbot run --bot taxi_bot.bot:customer --updater=webhooks --host=localhost --port=<BIND_PORT>\ --public-url=<CUSTOMER_BOT_PUBLIC_URL>maxbot run --bot taxi_bot.bot:customer --updater=webhooks --host=localhost --port=6012 --public-url=https://customer-taxiexample.pagekite.me
After successfully launching TaxiService, DriverBot and CustomerBot you can test the example. Text DriverBot and CustomerBot via Telegram and follow the hint.
This example shows the details of the implementation:
- using an external
HTTPservice, - interacting with an external
HTTPservice through theRESTcommand, - database schema,
- customer settings in the channel,
dialogvariable, - adding new commands to channels using
mixin, - using of
RPCrequests to notify bots from externalHTTPservice, - working with several channels (
TelegramandViber) inCustomerBot, - running tests,
- adding dependencies.
- An external
HTTPservice in python is used to implement business logic:TaxiService. - The
taxi_bot/api_servicefolder and the file are used for startingtaxi_bot/cli.py. - It is accessed from bots using the REST external
HTTPcalls function (see below). - There is also a feature for external services to notify bots via
HTTPqueries:RPC(see below).
The REST command is used to interact with an external service; it performs HTTP requests.
It is necessary to add the external service setting to the bot's scenario.
Example:
extensions:
rest:
services:
- name: taxi_service
base_url: http://taxi.service.net
timeout: 10
...Or you can specify the address via the environment settings:
...
base_url: !ENV ${TAXI_BOT_API}
...The TAXI_BOT_API variable must be specified in the file taxi_bot/.env
You can specify more than one external service; each service must have a unique value of name.
Example of using an external service with the REST command:
response: |-
{% POST "taxi_service://{}/{}".format(dialog.channel_name, dialog.user_id) body {
"phone": slots.phone
} %}This command sends a HTTP request (POST) http://taxi.service.net/{channel_name}/{user_id} with the JSON body.
See the dialog variable below.
You can get the information about the customer from the bot scenario using dialog variable.
The following parameters are available:
- customer channel name:
dialog.channel_nameit isTelegramorViberin this example - customer ID in the channel:
dialog.user_idis a unique string value within the channel
Note
- The pair (
dialog.user_id,dialog.channel_name) is unique for each customer, it is used to store data about customers in this example - Only one channel (
Telegram) was created for drivers, so it is enough to usedialog.user_idto work with them
Sometimes you need the bot to respond to events, taking place in an external HTTP service, not as a reaction to messages from customers.
The RPC feature is made to receive and process such events. This is the ability of the bot scenario
to receive and process HTTP requests. Each request must be described in the RPC block.
Example of configuration:
rpc:
- method: arrival
params:
- name: order
required: trueExample of processing:
- condition: rpc.driver_arrived and slots.order_id == params.order_id
response: |-
<jump_to node="waiting_start_ride" transition="response" />It is used to notify that a driver has arrived at the customer in CustomerBot.
The event that happened in DriverBot via an external HTTP service and the RPC feature comes to CustomerBot.
mixinis used to add features to communicate withTelegramandVibermessengers- the
channelsfolder contains the implementation of additional features of channels: work with buttons, locations and contacts
TelegramdocumentationViberdocumentation
Example of usage in scenario:
- condition: message.contact
response: |-
phone={{message.contact.phone_number}}
first_name={{message.contact.first_name}}
last_name={{message.contact.last_name|default('')}}Note The Viber does not have the first_name and the last_name parameters, but has the name parameter.
It comes in the message.contact.first_name variable, while message.contact.last_name is always empty, equal to none.
Note The last_name parameter can also be empty in Telegram.
Telegramdocumentation- The
request_contactparameter is used in implementation forTelegram Viberdocumentation- The
ActionType=share-phoneparameter is used in implementation forViber
Example of using in bot scenario:
response: |-
<keyboard_button_contact title="Send your phone" text="Please, send me your contact." />TelegramdocumentationViberdocumentation- The
horizontal_accuracyparameter determines the map scale when displaying the map in messenger, its value is chosen empirically.
Example of using in bot scenario:
- condition: message.location
response: |-
latitude={{message.location.latitude}}
longitude={{message.location.longitude}}
<location latitude="{{ message.location.latitude }}"
longitude="{{ message.location.longitude }}"
horizontal_accuracy="5"/>Telegramdocumentation- The
request_locationparameter is used in implementation forTelegram Viberdocumentation- The
ActionType=location-pickerparameter is used in implementation forViber
Example of using in bot scenario:
response: |-
<keyboard_button_location text="Submit your location." title="My location" />Used to select an option from the list or to prevent the customer from writing text.
TelegramdocumentationViberdocumentation
Example of using in bot scenario:
response: |-
<keyboard_button_list text="Click start or change route:" >
<buttons>start route</buttons>
<buttons>change route</buttons>
</keyboard_button_list>Telegramdocumentation- The
InputFieldState=hiddenparameter is used in implementation forTelegram Viberdocumentation- The
ActionType=noneparameters is used in implementation forViber
Note If you want to remove the chat buttons, you need to use the KeyboardButtonRemoveMixin, otherwise
buttons will not disappear after sending a new message.
Note The message with the button removal command should contain text: it will be sent as a text message.
Example of using in bot scenario:
response: |-
<keyboard_button_remove text="Ride started" />CustomerBot uses two channels: Viber and Telegram. The example demonstrates
the ability to work with multiple messengers in the same scenario.
poetry shell
poetry install (if not previously installed)
poetry run pytest tests/The external python service contains a number of dependencies that need to be installed.
To do this, add these dependencies to the pyproject.toml file:
[tool.poetry.dependencies]
python = ">=3.9, <3.12"
maxbot = "^0.2.0b2"
click-config-file = {version = "^0.6.0"}
Flask-SQLAlchemy = {version = "^3.0.2"}
...
The maxbot dependency should be in all examples.
The tool.poetry.group.dev.dependencies section lists dependencies to run unit tests ,
in this example it is:
[tool.poetry.group.dev.dependencies]
pytest = "^7.2.0" (for pytest based unit tests)
httpretty = "^1.1.4" (mock library for `HTTP` modules)


