cd /news/natural-language-processing/building-a-conversational-ai-agent-w… · home topics natural-language-processing article
[ARTICLE · art-22733] src=dev.to pub= topic=natural-language-processing verified=true sentiment=↑ positive

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.

read5 min publishedJun 5, 2026

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! 👋

**

── more in #natural-language-processing 4 stories · sorted by recency
sponsored brought to you by zahid.host 4,200+ EU-deployed projects
reading about agents? ship yours in a single git push.

Run your AI side-project on zahid.host

EU-based hosting, git-push deploys, automatic HTTPS, no cold starts. Free tier with a custom domain — perfect for shipping the agent you just read about.

$git push zahid main
Live at https://your-agent.zahid.host
Get free account → Pricing
from €0/mo · no card required
LIVE [news/building-a-conversat…] indexed:0 read:5min 2026-06-05 ·