Building a Conversational AI Agent with Python and Rasa: A Step‑by‑Step Guide A developer built a conversational AI travel assistant using Python and Rasa Open Source, creating a chatbot that can greet users, answer FAQs, and fetch dynamic flight status data from an external API. The project walks through environment setup, NLU training, story-driven dialogue management, custom actions, and deployment, with the final bot capable of handling intents like flight status inquiries and user information collection. The implementation uses Rasa's domain file as a single source of truth for intents, entities, slots, and actions, while training the NLU model on annotated examples to enable entity extraction and conversation flow management. 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 Expected output: Rasa Open Source 3.x.x 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 Output: Model trained successfully. Model path: models/... 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: python actions/actions.py 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 Clear the slot so the bot can handle a new query cleanly return SlotSet "flight number", None Tip– If you don’t have a real API, spin up a simple Flask mock: python mock flight api.py from flask import Flask, jsonify app = Flask name @app.route "/status/