Failed to find input channel class error

I’m facing this issue for quite some time already and I don’t know what I should do here. I’ve read doc and similar issues although I cannot find the source of my problem. Any help would be very much appreciated.

So, I implemented custom input and output channels cause I need to make a connection with a custom chat-widget we’re having.

My project structure looks like this:

├── app
│   ├── requirements.txt
│   ├── Dockerfile
│   ├── __init__.py
│   ├── models
│   ├── entrypoint-actions.sh
│   ├── endpoints.yml
│   ├── integrations
│   │   ├── __init__.py
│   │   └── suphelp
│   │       ├── suphelp-input.py
│   │       ├── suphelp-output.py
│   │       ├── __init__.py
│   ├── credentials.yml
│   ├── actions
│   ├── config.yml
│   ├── domain.yml
│   └── data
│       ├── nlu
│       ├── replies
│       ├── stories
│       └── rules

The problem I’ having is with suphelp channel implementation.

The content of suphelp-input.py

import inspect
import json
import logging
from typing import Any, Awaitable, Callable, Dict, List, Text
from flask import Blueprint
from rasa.core.channels.channel import (CollectingOutputChannel, InputChannel,
                                        OutputChannel, UserMessage, QueueOutputChannel)
from sanic import Blueprint, response
from sanic.request import Request
from sanic.response import HTTPResponse

log = logging.getLogger("suphelp_input")
logging.basicConfig(level=logging.DEBUG, format='%(asctime)s %(levelname)s: %(message)s')


class SuphelperInput(InputChannel):
    def name(self) -> Text:
        """Name of your custom channel."""
        return "suphelper_input"
    
    def __init__(self, url: Text = "http://localhost:8080/") -> None:
        self.url = url

    def format_bot_replies(self, output_messages: List[Dict[str, Any]]):
        new_output_messages = []
        for message in output_messages:
            if message.get("buttons") == None:
                new_output_messages.extend(
                    {
                        "type": "TextResponse",
                        "text": message.get("text")
                    }
                )
            else:
                buttons = []
                for button in message.get("buttons"):
                    buttons.extend(
                        {
                            "type": "ButtonVariant",
                            "id": "button-id",
                            "text": button.get("title")
                        }
                    )
                new_output_messages.extend(
                    {
                        "type": "MarkupResponse",
                        "title": message.get("text"),
                        "replyMarkup": {
                            "buttons": buttons
                        }
                    }
                )
        return new_output_messages


    def blueprint(
        self, on_new_message: Callable[[UserMessage], Awaitable[None]]
    ) -> Blueprint:

        custom_webhook = Blueprint(
            "custom_webhook_{}".format(type(self).__name__),
            inspect.getmodule(self).__name__,
        )

        @custom_webhook.route("/", methods=["GET"])
        async def health(request: Request) -> HTTPResponse:
            return response.json({"status": "ok"})

        @custom_webhook.route("/webhook", methods=["POST"])
        async def receive(request: Request) -> HTTPResponse:
            try:
                user_id = request.json.get("user").get("id") # method to get sender_id 
                text = request.json.get("text") # method to fetch text
                input_channel = self.name() # method to fetch input channel
                metadata = self.get_metadata(request) # method to get metadata

                collector = CollectingOutputChannel()
                
                # include exception handling

                await on_new_message(
                    UserMessage(
                        text,
                        collector,
                        sender_id=user_id,
                        input_channel=input_channel,
                        metadata=metadata,
                    )
                )

                return response.json(
                    body=json.dumps(self.format_bot_replies(collector.messages)),
                    status=200,
                    headers={"Content-type": "application/json"}
                )
            except Exception as error:
                log.error(f"Suphelper error when trying to handle message.{error}")
                return response.json(
                    {"status": "failed", "error": f"{error}"}, status=500
                )

        return custom_webhook

The content of suphelp-output.py:

import json
import logging
import requests
from rasa.core.channels import OutputChannel
from typing import List, Dict, Any, Text
from rasa.core.channels.channel import UserMessage
from sanic.response import HTTPResponse

log = logging.getLogger("suphelp_output")
logging.basicConfig(level=logging.DEBUG, format='%(asctime)s %(levelname)s: %(message)s')

class SuphelperBot(OutputChannel):
    @classmethod
    def name(cls) -> Text:
        return "suphelper_output"
    
    def __init__(self, url: str = "http://localhost:8080/") -> None: #SUPHELP_URL
        super().__init__()
        self.url = url

    async def send_text_message(self, recipient_id: Text, text: Text, buttons: List[Dict[Text, Any]] = None, **kwargs: Any):
        try:
            if buttons == None:
                response = {"type": "TextResponse", "text": text}
            else:
                response = {"type": "MarkupResponse", "title": text, "replyMarkup": {"buttons": buttons}}

            post_request = requests.post(
                url=self.url,
                headers = {"Content-Type": "application/json"},
                data=json.dumps(response),
            )
            post_request.close()
            if not post_request.status_code == 200:
                log.error(f"Failed to send message to SupHelp channel.\nStatus: {post_request.status_code}.\nBot Response: {response}")
            return response
        except Exception as error:
            log.error(f"Failed to send message to SupHelp channel.")
            return HTTPResponse(json.dumps({"error": f"Exception happened while sending messages between assistance and SupHelp widget.\nError: {error}"}), status=500)

    async def send_response(self, recipient_id: Text, message: Dict[Text, Any]) -> None:
        # Implement the logic to send the response to the recipient using the custom channel
        pass

in credentials.yml I’m having it like this

integrations.suphelp.suphelp_input.SuphelperInput:
integrations.suphelp.suphelp_output.SuphelperBot:
  url: "http://localhost:8080/"

But I tried different ways of specifying the channel in credentials.yml. None of which work. Every time I run I get this error

RasaException: Failed to find input channel class for 'integrations.suphelp.suphelp_input.SuphelperInput'. Unknown input channel. Check your credentials configuration to make sure the mentioned channel is not misspelled. If you are creating your own channel, make sure it is a proper name of a class in a module.

What am I doing wrong here?

Make sure you have read the docs page on this. It looks like the python file is called suphelp-input but you refer to it as suphelp_input in the credentials.yml.