This article details the development of an AI Chatbot that enables users to inquire about the status of their orders. The implementation utilizes a modern frontend built with React.js, a robust Natural Language Understanding (NLU) and dialogue management framework powered by Rasa, and a simple backend using Python (Flask) to serve order information.
I. Core Technologies:
- React.js (Frontend): A JavaScript library for building dynamic and interactive user interfaces.
- Rasa (NLU and Dialogue Management): An open-source framework for building conversational AI assistants, handling intent recognition, entity extraction, and conversation flow.
- Flask (Backend): A lightweight Python web framework for creating the API to serve order data.
II. Project Structure:
The project will be organized into three main parts:
- Frontend (order-chatbot-react): Contains the React.js application.
- Backend (backend): Houses the Flask API.
- Rasa (rasa): Includes the Rasa NLU model, dialogue policies, and custom actions.
III. Rasa Implementation:
The Rasa component is responsible for understanding user input and managing the conversation.
- NLU Model (rasa/nlu.yml): Defines intents (user goals) and entities (key information).
YAML
version: “3.1”
nlu:
– intent: check_order_status
examples: |
– what is the status of my order [12345](order_id)
– track order [ABC-67890](order_id)
– where is order [XYZ11223](order_id)
– status for order [PENDING99](order_id)
– intent: request_order_details
examples: |
– tell me more about order [12345](order_id)
– what are the items in order [ABC-67890](order_id)
– intent: goodbye
examples: |
– bye
– goodbye
– intent: greet
examples: |
– hi
– hello - Dialogue Domain (rasa/domain.yml): Defines intents, entities, slots (memory), responses, and actions.
YAML
version: “3.1”
intents:
– check_order_status
– request_order_details
– goodbye
– greet
entities:
– order_id
slots:
order_id:
type: text
influence_conversation: true
mappings:
– type: from_entity
entity: order_id
responses:
utter_greet:
– text: “Hello! How can I help you with your order today?”
utter_ask_order_id:
– text: “Could you please provide your order number?”
utter_order_status:
– text: “Order #{order_id} is currently: {status}.”
utter_order_status_tracking:
– text: “Order #{order_id} is currently: {status}. Tracking number: {tracking}.”
utter_order_details:
– text: “Order #{order_id} contains the following items: {items}.”
utter_order_not_found:
– text: “Sorry, order #{order_id} was not found.”
utter_goodbye:
– text: “Thank you for contacting us. Have a great day!”
utter_default:
– text: “Sorry, I didn’t understand that. Could you please rephrase?”
actions:
– action_get_order_status
– action_get_order_details
session_config:
session_expiration_time: 60
carry_over_slots_to_new_session: true - Dialogue Rules (rasa/rules.yml): Defines the flow of the conversation based on intents and actions.
YAML
version: “3.1”
rules:
– rule: Greet user
steps:
– intent: greet
– action: utter_greet
– rule: Ask for order ID if not provided for status check
steps:
– intent: check_order_status
– action: utter_ask_order_id
– wait_for_user_input:
intent: check_order_status
entities:
– order_id
– action: action_get_order_status
– rule: Provide order status if order ID is given
steps:
– intent: check_order_status
– entities:
– order_id
– action: action_get_order_status
– rule: Ask for order ID if not provided for details request
steps:
– intent: request_order_details
– action: utter_ask_order_id
– wait_for_user_input:
intent: request_order_details
entities:
– order_id
– action: action_get_order_details
– rule: Provide order details if order ID is given
steps:
– intent: request_order_details
– entities:
– order_id
– action: action_get_order_details
– rule: Handle goodbye
steps:
– intent: goodbye
– action: utter_goodbye
– rule: Handle fallback
steps:
– action: utter_default - Custom Actions (rasa/actions.py): Python code to integrate with the backend.
Python
from typing import Any, Text, Dict, List
from rasa_sdk import Action, Tracker
from rasa_sdk.executor import CollectingDispatcher
from rasa_sdk.types import DomainDict
import requests
import json
class ActionGetOrderStatus(Action):
def name(self) -> Text:
return “action_get_order_status”
async def run(
self,
dispatcher: CollectingDispatcher,
tracker: Tracker,
domain: DomainDict,
) -> List[Dict[Text, Any]]:
order_id = tracker.get_slot(“order_id”)
if order_id:
try:
response = requests.get(f”http://127.0.0.1:5000/orders/{order_id}”)
response.raise_for_status()
order_data = response.json()
status = order_data.get(“status”)
tracking = order_data.get(“tracking”)
if status:
if tracking:
dispatcher.utter(text=f”Order #{order_id} is currently: {status}. Tracking number: {tracking}.”)
else:
dispatcher.utter(text=f”Order #{order_id} is currently: {status}.”)
else:
dispatcher.utter(text=f”Sorry, could not retrieve the status for order #{order_id}.”)
except requests.exceptions.RequestException:
dispatcher.utter(text=f”Sorry, there was an error communicating with the order service.”)
except json.JSONDecodeError:
dispatcher.utter(text=”Sorry, the order service returned an invalid response.”)
else:
dispatcher.utter(text=”Sorry, I couldn’t find the order ID.”)
return []
class ActionGetOrderDetails(Action):
def name(self) -> Text:
return “action_get_order_details”
async def run(
self,
dispatcher: CollectingDispatcher,
tracker: Tracker,
domain: DomainDict,
) -> List[Dict[Text, Any]]:
order_id = tracker.get_slot(“order_id”)
if order_id:
try:
response = requests.get(f”http://127.0.0.1:5000/orders/{order_id}”)
response.raise_for_status()
order_data = response.json()
items = “, “.join(order_data.get(“items”, []))
if items:
dispatcher.utter(text=f”Order #{order_id} contains the following items: {items}.”)
else:
dispatcher.utter(text=f”Order #{order_id} does not have any items listed.”)
except requests.exceptions.RequestException:
dispatcher.utter(text=f”Sorry, there was an error communicating with the order service.”)
except json.JSONDecodeError:
dispatcher.utter(text=”Sorry, the order service returned an invalid response.”)
else:
dispatcher.utter(text=”Sorry, I couldn’t find the order ID.”)
return [] - Rasa Configuration (rasa/config.yml): Defines the NLU pipeline and dialogue policies.
YAML
version: “3.1”
pipeline:
– name: WhitespaceTokenizer
– name: RegexFeaturizer
– name: LexicalSyntacticFeaturizer
– name: CountVectorsFeaturizer
– name: CountVectorsFeaturizer
analyzer: char_wb
min_ngram: 1
max_ngram: 4
– name: DIETClassifier
epochs: 100
– name: EntityExtractorDIET
– name: ResponseSelector
epochs: 100
– name: FallbackClassifier
threshold: 0.6
policies:
– name: MemoizationPolicy
– name: RulePolicy
– name: TEDPolicy
max_history: 5
epochs: 100
– name: FallbackPolicy
nlu_threshold: 0.6
core_threshold: 0.3
fallback_action_name: “utter_default”
IV. Backend Implementation (Flask – backend/app.py and backend/order_service.py):
- Order Service (backend/order_service.py): Simulates an order database.
Python
orders_db = {
“12345”: {“status”: “Processing”, “items”: [“Laptop”, “Mouse”], “tracking”: None},
“ABC-67890”: {“status”: “Shipped”, “items”: [“Keyboard”], “tracking”: “TRACK-ABC-67890”},
“XYZ11223”: {“status”: “Delivered”, “items”: [“Monitor”], “tracking”: “DELIVERED-XYZ”},
“PENDING99”: {“status”: “Pending Payment”, “items”: [“Webcam”], “tracking”: None},
“54321”: {“status”: “Processing”, “items”: [“Tablet”, “Charger”], “tracking”: None},
“9876”: {“status”: “Shipped”, “items”: [“Headphones”], “tracking”: “SHIP-9876”},
}
def get_order_details(order_id):
return orders_db.get(order_id) - Flask Application (backend/app.py): Provides the API endpoint for order data.
Python
from flask import Flask, jsonify
from flask_cors import CORS
from order_service import get_order_details
app = Flask(__name__)
CORS(app)
@app.route(‘/orders/<order_id>’, methods=[‘GET’])
def get_order(order_id):
order = get_order_details(order_id)
if order:
return jsonify(order)
return jsonify({“error”: “Order not found”}), 404
if __name__ == ‘__main__’:
app.run(debug=True, port=5000)
V. Frontend Implementation (React.js – order-chatbot-react directory):
- src/App.js: The main React component.
JavaScript
import React, { useState, useRef, useEffect } from ‘react’;
import axios from ‘axios’;
import ‘./App.css’;
function App() {
const [messages, setMessages] = useState([{ sender: ‘bot’, text: ‘Hello! How can I help you with your order today?’ }]);
const [inputValue, setInputValue] = useState(”);
const chatContainerRef = useRef(null);
const [isLoading, setIsLoading] = useState(false);
useEffect(() => {
if (chatContainerRef.current) {
chatContainerRef.current.scrollTop = chatContainerRef.current.scrollHeight;
}
}, [messages]);
const sendMessage = async () => {
const message = inputValue.trim();
if (message) {
appendMessage(‘user’, message);
setInputValue(”);
setIsLoading(true);
try {
const response = await axios.post(‘http://localhost:5005/webhooks/rest/webhook’, {
sender: “react-user”,
message: message
});
if (response.data && response.data.length > 0) {
response.data.forEach(item => {
if (item.hasOwnProperty(‘text’)) {
appendMessage(‘bot’, item.text);
}
});
} else {
appendMessage(‘bot’, ‘Sorry, I didn\’t receive a response.’);
}
} catch (error) {
appendMessage(‘bot’, ‘Sorry, there was an error communicating with the chatbot.’);
console.error(“Error sending message to Rasa:”, error);
} finally {
setIsLoading(false);
}
}
};
const appendMessage = (sender, text) => {
setMessages(prevMessages => […prevMessages, { sender, text }]);
};
const handleInputChange = (event) => {
setInputValue(event.target.value);
};
const handleKeyPress = (event) => {
if (event.key === ‘Enter’) {
sendMessage();
}
};
return (
<div className=”chat-container”>
<h1>Order Status Chatbot</h1>
<div className=”message-list” ref={chatContainerRef}>
{messages.map((msg, index) => (
<div key={index} className={`message ${msg.sender}`}>
{msg.text}
</div>
))}
{isLoading && <div className=”message bot”>Thinking…</div>}
</div>
<div className=”input-area”>
<input
type=”text”
value={inputValue}
onChange={handleInputChange}
onKeyPress={handleKeyPress}
placeholder=”Enter your message (e.g., What’s the status of order 12345?)”
/>
<button onClick={sendMessage} disabled={isLoading}>Send</button>
</div>
</div>
);
}
export default App; - src/App.css: Basic styling for the chat interface.
CSS
/* … (CSS styles as in the previous detailed implementation) … */
VI. Running the Application:
- Start Rasa: Navigate to the rasa directory and run rasa run –enable-api -p 5005 –cors “*”.
- Start Flask Backend: Navigate to the backend directory and run python app.py.
- Start React Frontend: Navigate to the order-chatbot-react directory and run npm start or yarn start.
VII. Interaction Flow:
- The user interacts with the React.js chat interface in their web browser.
- When the user sends a message, the React app sends it to the Rasa server via a POST request to the /webhooks/rest/webhook endpoint.
- Rasa processes the message using its NLU model to understand the user’s intent and extract entities like the order_id.
- Based on the intent and the dialogue rules, Rasa may trigger a custom action defined in actions.py.
- The custom action makes an HTTP GET request to the Flask backend at /orders/<order_id> to retrieve order information.
- The Flask backend fetches the order details from its simulated database (or a real database in a production environment) and returns them as JSON.
- The custom action in Rasa receives the order information and uses it to formulate a response based on the templates defined in domain.yml.
- Rasa sends the response back to the React frontend as a JSON array of chatbot messages.
- The React app updates its state with the new messages, and they are displayed in the chat window.
This architecture provides a more robust and scalable foundation for an AI Chatbot compared to a simple keyword-matching approach. Rasa handles the complexities of natural language understanding and dialogue flow, while React.js offers a dynamic and user-friendly frontend experience. The Flask backend acts as a dedicated service for providing order data. For a production-ready application, the simulated order database would need to be replaced with integration to a real order management system, and more comprehensive error handling and user authentication would be necessary.
Leave a Reply