Skip to content

Commit

Permalink
merge
Browse files Browse the repository at this point in the history
  • Loading branch information
paradous committed Feb 7, 2021
2 parents 49f2e43 + 89cfa81 commit 32e92bb
Show file tree
Hide file tree
Showing 35 changed files with 1,117 additions and 0 deletions.
29 changes: 29 additions & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@

FROM python:3.8-slim-buster

# Install the security updates.
RUN apt-get update
RUN apt-get -y upgrade

# Dependencies to build requires packages
RUN apt-get -y install gcc

# Remove all cached file. Get a smaller image.
RUN apt-get clean
RUN rm -rf /var/lib/apt/lists/*

EXPOSE 3978

# Copy the application.
COPY . /opt/app
WORKDIR /opt/app

# Install the app librairies.
RUN pip install -r requirements.txt

# Install SpaCy small model
RUN python -m spacy download en_core_web_sm

# Start the app.
ENTRYPOINT [ "python" ]
CMD [ "main.py" ]
21 changes: 21 additions & 0 deletions LICENSE
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
MIT License

Copyright (c) 2021 Joffrey Bienvenu

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
80 changes: 80 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
# chatbot





## Cross-plateforme implementation


## fonctionnalités du Bot :

### Traitement des inputs "utilisateurs"

- Bert ?

### Possible réponses

- Accueillire
- Décrire les fonctionnalités du bot
- Répondre
- Donner les heures d'ouvertures
- Afficher une liste d'objet, de produit

### Nice to have

- Réserver un service / un produit
- Gérer un agenda
- vérifier la disponibilité
-
-

"**In English**, the bot should be able to :\n",
"\n",
"- Understand phrases related to a room reservation.\n",
"Example that the bot will have to understand: \n",
"\n",
"\t\t> I wish to reserve a room for 2 people.\n",
"\t\t> I wish to reserve a room for 4 days\n",
"\t\t> Do you have rooms available from July 23rd?\n",
"\t\t> I would like to reserve a room for two days and for two people\n",
"\n",
"- Understand phrases related to a table reservation for the restaurant. \n",
"\n",
"\t\t> I would like to make a reservation for tonight.\n",
"\t\t> I'd like to reserve a table for four people.\n",
"\n",
"- Must ensure a continuous and ongoing conversation. Example of a complete conversation : \n",
"\n",
"\t\t> Customer : Hello !\n",
"\t\t> Bot : Hello, how can I help you? \n",
"\t\t> Customer: I would like to reserve a table for 4 people ? \n",
"\t\t> Bot : For which date would you like to reserve your table?\n",
"\t\t> Customer : Today at 7:00 pm\n",
"\t\t> Bot : What name should I make the reservation under?\n",
"\t\t> Customer : My name is Mr. Dupont! \n",
"\t\t> Bot : Very well Mr Dupont, I confirm you the reservation of a table for 4 people tonight at 7:00 pm. \n",
"\t\t> Bot : Can I help you with something else?\n",
"\t\t> Customer : No thanks\n",
"\t\t> Bot: Have a nice day. \n",
"\n",
"- Understand when the client is angry. In this case, the bot will indicate that it is transmitting the conversation to a human. \n",
"\n",
"\t\t> You're incompetent!\n",
"\t\t> My room is dirty! This is outrageous!\n",
"\t\t> I want to talk to a human. \n",
"\n",
"### Nice-to-have features\n",
"- Create an API of your bot to make it cross-platform \n",
"- Use Docker\n",




## Hébergement du Bot

Timeline:
- Etablir l'objectif (déployer bot cross-plateforme + créer propre modele)
- Trouver un framework >> MSBotFramework
- Créer un dataset
- Deployer dummy bot
3 changes: 3 additions & 0 deletions assets/conversation_simple.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added assets/images/profile_large.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added assets/images/profile_small.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Empty file added assets/model/.empty
Empty file.
32 changes: 32 additions & 0 deletions config.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@

from os import environ


class Config:
"""Bot configuration class."""

# Deployment
PORT = int(environ.get("PORT", 3978))

# Azure deployment
APP_ID = environ.get("MS_APP_ID", "")
APP_PASSWORD = environ.get("MS_APP_PASSWORD", "")

# Models
MODEL_PREPROCESS = "en_core_web_sm" # SpaCy smallest model - For preprocess
MODEL_MATCHING = "TF-IDF" # PolyFuzz lightest model - Optimized for matching
MODEL_CLASSIFIER = "bert-base-uncased" # HuggingFace smallest BERT model - For tokenization and classifying

# Remote files
s3_base_url = environ.get("S3_BASE_URL", "")

weight_file = "resa_BERT_model.pt"
MODEL_WEIGHT_URL = f"{s3_base_url}/{weight_file}" # Fine-tuned weights for BERT model
MODEL_WEIGHT_LOCAL_COPY = f"./assets/model/{weight_file}"

classes_file = "labels.pickle"
MODEL_CLASSES_URL = f"{s3_base_url}/{classes_file}"
MODEL_CLASSES_LOCAL_COPY = f"./assets/model/{classes_file}"

# Filters
FILTERS_TOML = "./filters.toml"
15 changes: 15 additions & 0 deletions filters.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
# TOML document to store filters

[longtalk_make_reservation]

# Size of the room: How many people ?
[longtalk_make_reservation.people]
words = ["pearson", "people"]
regex = '''(?P<people>\d)\W%s'''
threshold = 0.85

# Duration of the book: How long ?
[longtalk_make_reservation.duration]
words = ["day", "night"]
regex = '''(?P<duration>\d)\W%s'''
threshold = 0.85
114 changes: 114 additions & 0 deletions main.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@

import sys
import traceback
from datetime import datetime
from http import HTTPStatus

from aiohttp import web
from aiohttp.web import Request, Response, json_response
from botbuilder.core import BotFrameworkAdapterSettings, TurnContext, BotFrameworkAdapter, ConversationState, MemoryStorage, UserState
from botbuilder.core.integration import aiohttp_error_middleware
from botbuilder.schema import Activity, ActivityTypes

from src.dialogs import MainDialog, BookingRoomDialog
from src.nlu import NLU
from src import Bot
from config import Config

# Load the config and create the bot
config = Config()

# Init a Bot adapter https://aka.ms/about-bot-adapter
settings = BotFrameworkAdapterSettings(config.APP_ID, config.APP_PASSWORD)
ADAPTER = BotFrameworkAdapter(settings)


# Catch-all for errors
async def on_error(context: TurnContext, error_: Exception):
"""
Catch-all functions to write out errors on console log.
NOTE: In production environment, logging should be done
to Azure application insights.
"""

# Print the error into the logs
print(f"\n [on_turn_error] unhandled error: {error_}", file=sys.stderr)
traceback.print_exc()

# Send a message to the user
await context.send_activity("The bot encountered an error or bug.")

# If the bot is run from the Bot Framework Emulator (dev environment),
# print a more complete error log.
if context.activity.channel_id == "emulator":

trace_activity = Activity(
label="TurnError",
name="on_turn_error Trace",
timestamp=datetime.utcnow(),
type=ActivityTypes.trace,
value=f"{error_}",
value_type="https://www.botframework.com/schemas/error",
)
await context.send_activity(trace_activity)

# Clear out state
await CONVERSATION_STATE.delete(context)


# Set the error handler on the Adapter.
ADAPTER.on_turn_error = on_error

# Create MemoryStorage, UserState and ConversationState
MEMORY = MemoryStorage()
CONVERSATION_STATE = ConversationState(MEMORY)
USER_STATE = UserState(MEMORY)

# Load the NLU recognizer
nlu = NLU()

# Create the dialogs
dialog_room_reservation = BookingRoomDialog(nlu, USER_STATE)
dialog_main = MainDialog(nlu, USER_STATE, dialog_room_reservation)

# Create the bot
bot = Bot(CONVERSATION_STATE, USER_STATE, dialog_main)


# Direct message API
async def messages(req: Request) -> Response:
"""
Main bot function: Listen for incoming API request.
Route: '/api/messages'.
"""

# Filter only JSON requests
if "application/json" in req.headers["Content-Type"]:
body = await req.json()
else:
return Response(status=HTTPStatus.UNSUPPORTED_MEDIA_TYPE)

# Deserialize the JSON
activity = Activity().deserialize(body)

# Retrieve the authorization code if sent
auth_header = ""
if "Authorization" in req.headers:
auth_header = req.headers["Authorization"]

# Call the bot and send back its response
response = await ADAPTER.process_activity(activity, auth_header, bot.on_turn)
if response:
return json_response(data=response.body, status=response.status)

# Return HTTP-200 if no response is send back
return Response(status=HTTPStatus.OK)

# Init and open routes for direct API call
app = web.Application(middlewares=[aiohttp_error_middleware])
app.router.add_post("/api/messages", messages)


if __name__ == "__main__":

web.run_app(app, host="0.0.0.0", port=config.PORT)
26 changes: 26 additions & 0 deletions requirements.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@

# MS Bot Framework
botbuilder-core==4.11.0
botbuilder-integration-aiohttp==4.11.0
botbuilder-schema==4.11.0
botframework-connector==4.11.0
botbuilder-dialogs==4.11.0
aiohttp==3.6.2

# Preprocessing
beautifulsoup4==4.9.3
spacy==3.0.1
Unidecode==1.1.2
word2number==1.1
contractions==0.0.45

# Classification
transformers==4.2.2
torch==1.7.1
requests==2.23.0

# Matching
polyfuzz==0.2.2
toml==0.10.2
pandas==1.2.1

4 changes: 4 additions & 0 deletions src/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@

from .bot import Bot

__all__ = ["Bot"]
39 changes: 39 additions & 0 deletions src/bot.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@

from botbuilder.schema import ChannelAccount
from botbuilder.core import ActivityHandler, TurnContext, ConversationState, UserState
from botbuilder.dialogs import Dialog

from .dialogs.utils import Emoji
from .dialogs.helpers import DialogHelper


class Bot(ActivityHandler):

def __init__(self, conversation_state: ConversationState, user_state: UserState, dialog: Dialog):

self.conversation_state = conversation_state
self.user_state = user_state
self.dialog = dialog

async def on_members_added_activity(self, members_added: [ChannelAccount], turn_context: TurnContext):

# Send an "Hello" to any new user connected to the bot
for member in members_added:
if member.id != turn_context.activity.recipient.id:
await turn_context.send_activity(f"Hello {Emoji.WAVING_HAND.value}")

async def on_turn(self, turn_context: TurnContext):

await super().on_turn(turn_context)

# Save any state changes that might have occurred during the turn.
await self.conversation_state.save_changes(turn_context)
await self.user_state.save_changes(turn_context)

async def on_message_activity(self, turn_context: TurnContext):

await DialogHelper.run_dialog(
self.dialog,
turn_context,
self.conversation_state.create_property("DialogState"),
)
Loading

0 comments on commit 32e92bb

Please sign in to comment.