Conversational AI is no longer a niche hobby; enterprises use it for customer support, lead qualification, and internal tooling. Rasa Open Source gives you a production‑ready stack that runs on‑premise, lets you keep data private, and offers full Python extensibility. In this article we’ll walk through a complete Rasa project from scratch, covering environment setup, NLU training, story‑driven dialogue management, custom actions, testing, and deployment. By the end you’ll have a working chatbot that can greet users, answer FAQs, and fetch dynamic data from an external API.
Actionable Insight– Start every Rasa project in its own virtual environment. It isolates dependencies and makes CI/CD pipelines deterministic.
| Requirement | Version |
|---|---|
| Python | 3.9‑3.11 |
| Rasa | 3.6+ |
| pip | latest |
| git | any |
You’ll also need a basic familiarity with YAML and Python. If you haven’t installed Rasa yet, run:
python -m venv rasa-env
source rasa-env/bin/activate # Windows: .\rasa-env\Scripts\activate
pip install --upgrade pip
pip install rasa
Verify the installation:
rasa --version
Create a fresh directory and initialise a Rasa project:
mkdir travel-bot && cd travel-bot
rasa init --no-prompt
The command scaffolds the following structure:
travel-bot/
├─ actions/
│ └─ actions.py
├─ data/
│ ├─ nlu.yml
│ └─ stories.yml
├─ config.yml
├─ domain.yml
└─ credentials.yml
We’ll replace the auto‑generated files with our own definitions.
domain.yml
is the single source of truth for intents, entities, slots, actions, and responses. For a travel‑assistant bot we need:
version: "3.0"
intents:
- greet
- goodbye
- ask_flight_status
- inform
entities:
- flight_number
slots:
flight_number:
type: text
influence_conversation: false
responses:
utter_greet:
- text: "Hey there! I’m your travel assistant. How can I help you today?"
utter_goodbye:
- text: "Safe travels! 👋"
utter_ask_flight:
- text: "Sure, could you share the flight number?"
utter_flight_status:
- text: "Fetching status for flight {flight_number}..."
utter_flight_not_found:
- text: "I couldn’t locate that flight. Please double‑check the number."
actions:
- action_flight_status
Why this matters – Slots are lightweight containers for user‑provided data (e.g., a flight number). By declaring influence_conversation: false
we tell the dialogue policy not to treat the slot as a decision factor, which keeps the conversation flow deterministic.
Rasa’s NLU component learns from annotated examples. Replace data/nlu.yml
with:
version: "3.0"
nlu:
- intent: greet
examples: |
- hi
- hello
- hey there
- good morning
- intent: goodbye
examples: |
- bye
- see you later
- goodbye
- catch you later
- intent: ask_flight_status
examples: |
- what's the status of flight AA123?
- can you check flight BA456?
- flight status for DL789
- I need the status of flight LH321
entities:
- flight_number: AA123
- flight_number: BA456
- flight_number: DL789
- flight_number: LH321
- intent: inform
examples: |
- it's AA123
- flight number is BA456
- the flight is DL789
Actionable Insight – Use the entities:
block inside each example to seed the entity extractor. This speeds up convergence and reduces the need for a large dataset.
Train the model:
rasa train
Stories encode the expected path of a conversation. Create data/stories.yml
:
version: "3.0"
stories:
- story: greet and ask flight status
steps:
- intent: greet
- action: utter_greet
- intent: ask_flight_status
- action: utter_ask_flight
- intent: inform
entities:
flight_number: "AA123"
- slot_was_set:
- flight_number: "AA123"
- action: action_flight_status
- action: utter_flight_status
- story: goodbye
steps:
- intent: goodbye
- action: utter_goodbye
Rasa’s default policy (TED Policy) will learn a finite‑state machine from these stories. For more complex flows you can add rules.yml
or use form
actions, but the above suffices for a minimal demo.
The action_flight_status
action contacts a mock API to retrieve real‑time flight data. Replace actions/actions.py
with:
import requests
from typing import Any, Text, Dict, List
from rasa_sdk import Action, Tracker
from rasa_sdk.executor import CollectingDispatcher
from rasa_sdk.events import SlotSet
API_ENDPOINT = "https://api.mockflight.com/status"
class ActionFlightStatus(Action):
"""Calls an external flight‑status API and returns a friendly message."""
def name(self) -> Text:
return "action_flight_status"
def run(
self,
dispatcher: CollectingDispatcher,
tracker: Tracker,
domain: Dict[Text, Any],
) -> List[Dict[Text, Any]]:
flight_number = tracker.get_slot("flight_number")
if not flight_number:
dispatcher.utter_message(text="I need a flight number first.")
return []
try:
response = requests.get(f"{API_ENDPOINT}/{flight_number}", timeout=5)
response.raise_for_status()
data = response.json()
status = data.get("status", "unknown")
message = f"✈️ Flight {flight_number} is currently *{status}*."
except requests.RequestException:
message = "❗️ I couldn’t reach the flight service. Please try again later."
except KeyError:
message = "❓ I didn’t understand the response from the service."
dispatcher.utter_message(text=message)
return [SlotSet("flight_number", None)]
Tip– If you don’t have a real API, spin up a simple Flask mock:
from flask import Flask, jsonify
app = Flask(__name__)
@app.route("/status/<flight>")
def status(flight):
return jsonify({"status": "on time" if flight.endswith("1") else "delayed"})
if __name__ == "__main__":
app.run(port=5001)
Run it in the background and change API_ENDPOINT
to http://localhost:5001/status
.
rasa run actions
Leave this terminal open; Rasa Core will call the endpoint whenever action_flight_status
is triggered.
Launch the chatbot in the shell:
rasa shell
Sample interaction:
User: hi
Bot: Hey there! I’m your travel assistant. How can I help you today?
User: what's the status of flight AA123?
Bot: Sure, could you share the flight number?
User: it's AA123
Bot: Fetching status for flight AA123...
✈️ Flight AA123 is currently *on time*.
User: bye
Bot: Safe travels! 👋
**