Building an AI Chatbot for Order Status with React.js, Rasa, and Flask

This article details the development of an 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 () and dialogue management framework powered by , and a simple backend using (Flask) to serve order information.

I. Core Technologies:

  1. React.js (Frontend): A JavaScript library for building dynamic and interactive user interfaces.
  2. Rasa (NLU and Dialogue Management): An open-source framework for building conversational AI assistants, handling intent recognition, entity extraction, and conversation flow.
  3. Flask (Backend): A lightweight Python web framework for creating the 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.

  1. 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
  2. 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
  3. 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
  4. 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 []
  5. 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):

  1. Order Service (backend/order_service.py): Simulates an order .
    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)
  2. 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):

  1. 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;
  2. src/App.css: Basic styling for the chat interface.
    CSS
    /* … (CSS styles as in the previous detailed implementation) … */

VI. Running the Application:

  1. Start Rasa: Navigate to the rasa directory and run rasa run –enable-api -p 5005 –cors “*”.
  2. Start Flask Backend: Navigate to the backend directory and run python app.py.
  3. Start React Frontend: Navigate to the order-chatbot-react directory and run npm start or yarn start.

VII. Interaction Flow:

  1. The user interacts with the React.js chat interface in their web browser.
  2. 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.
  3. Rasa processes the message using its NLU model to understand the user’s intent and extract entities like the order_id.
  4. Based on the intent and the dialogue rules, Rasa may trigger a custom action defined in actions.py.
  5. The custom action makes an HTTP GET request to the Flask backend at /orders/<order_id> to retrieve order information.
  6. The Flask backend fetches the order details from its simulated database (or a real database in a production environment) and returns them as JSON.
  7. The custom action in Rasa receives the order information and uses it to formulate a response based on the templates defined in domain.yml.
  8. Rasa sends the response back to the React frontend as a JSON array of chatbot messages.
  9. 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.

Agentic AI AI AI Agent API Automation AWS Azure Chatbot database Databricks ELK Kafka LLM monitoring Monolith NLU python RAG rasa ReactJS redis Spark time series Vertex AI

Leave a Reply

Your email address will not be published. Required fields are marked *