InquireAI / inquire

Inquire is a generalized chatbot ready to handle anything you throw at it.
https://inquire.run
MIT License
3 stars 0 forks source link

(bot/web) expose routes for bot to use to save queries and check subscription status #11

Closed ethan-stone closed 1 year ago

Lucas-Kohorst commented 1 year ago

Generally this will be what the tg auth decorator will look like

    def auth(self):
        def decorator(func):
            @wraps(func)
            async def wrapper(self, update, context):
                # check user total queries and limits
                user_limits = nextjs api call /<user-id>/queries
                # check user is paying
                is_premium = nextjs api call /<user-id>/subscription
                if user_limits > 100 && !is_premium:
                    # log the user query into the limits of the database
                    # send telegram message with link to upgrade in a DM
                    await update.message.reply_text("You have reached your inquires limit for the month. Please upgrade your account to continue using the bot.")
                    # if they are in a group chat send a message that they need to upgrade
                    # @ethan unsure if I should just send the main auth message always to DMs or it its a better 
                    # flow to have it send in whatever chat (could be chat, channel, or group chat) that they 
                    # reached their limit in. 
                else: 
                    # log the user query into the limits of the database
                    # answer their query they still have inquires left

basically consists of

So I think for this I only need routes for GET subscription status and GET/PUT on the limits for a user

ethan-stone commented 1 year ago

@Lucas-Kohorst Let me know what you think of this. I know it's a lot lol but kind of important to resolve before I go much further.

So I started working on this and here is where I got. There are two things in play for a user.

  1. The user in our system signed up Inquire. In the database, these are in the User table
  2. The user's information for whatever platform they use Inquire from ie. Telegram. In the database, these are in the Connection table. Each Connection has a connectionType (TELEGRAM for now, but DISCORD, SLACK etc. would be added later) and a connectionUserId, the id for that user on that platform id, as well as an optional userId which references a row in the User table.

Here's a diagram to help https://drawsql.app/teams/personal-419/diagrams/inquire

userId is optional on Connection because a user won't exist until they sign up but we will have their Connection info from interaction with the bot to track their free quota. Once they sign up and formerly add their connection info (ex. sign in with telegram) then the userId will be added to the Connection.

So the basic flow from first using the bot to signing up is as follows.

First, the bot service needs to create the Connection without the userId to track the quota. I don't know too many details about the bot and what events you receive, but off the top of my head, the best place to create the Connection is when the bot is invited to a chat. If you don't receive an event when the bot is invited to a chat then maybe on every message we have to check if the Connection already exists and if it doesn't then create it. This isn't ideal because it wouldn't scale as well since we need to query the database/API on every message.

At some point, the user signs up formerly with Inquire. When they formerly add a connection we check if the Connection already exists and if it does we just update it with the userId, and if not we create it.

I also thought to have a better separation of concerns, rather than having the bot retrieve the quota usage through the API, checking that they haven't hit the limit, then checking the subscription status, and then querying dust.tt, we move all of this to the API. That way the business logic of how to authenticate/authorize an Inquiry is handled where the database lives, and we have better performance for having direct access to the database.

Going down this route, when the bot receives a message, it doesn't have to authenticate/authorize, it just calls an API route to create an inquiry. The possible 200 status code responses might look like this.

If the inquiry is successful.

{
    "success": true,
    "data": {
        "id": "unique id",
        "result": "This is the result from dv3 or whatever model is used",
        "connectionType": "TELEGRAM",
        "connectionUserId": "telegram user id"
    }
}

If the quota is reached

{
    "success": false,
    "code": "QUOTA_REACHED" 
}

and from here a link would be sent to the user.

This might take a good amount of time to refactor but I think it would be worthwhile. This to me is a better architecture and better for scalability. The bot only has to care about communicating with the platform (TELEGRAM, DISCORD, etc.) trying to create an inquiry, and what to do if creating an inquiry fails. All the business logic of how to authenticate/authorize a connection, the status of a user's subscription and payments, and basically anything that pertains to if a user can make an inquiry is abstracted away from the bot. New pricing plans, quota limits etc. can be added without ever having to change the bot.

Lucas-Kohorst commented 1 year ago

Sweet this is awesome and I am highly aligned with it. A few clarifying this and questions

Database Schema

Telegram Responses

This will likely be way more than you want to know. But I figured I would lay it out

All Telegram responses for literally any action are all done through an update object. Here are some example responses to events. The bot is invited into a group chat This basically logs info on the group chat, who updated the chat (this is in the ChatMemberUpdated), who was added (new_chat_member), removed / left (old_chat_member).

Update(
    my_chat_member=ChatMemberUpdated(
        chat=Chat(
            api_kwargs={'all_members_are_administrators': True}, 
            id=-<Group ID ex. 677136673>,
            title=<Group Name>, 
            type=<ChatType.GROUP>), 
            date=datetime.datetime(2022, 12, 19, 1, 33, 40, tzinfo=<UTC>), 
            from_user=User(
            first_name='Lucas', 
            id=779540407
            is_bot=False, 
            language_code='en', 
            username='lucaskohorst'
        ), 
        new_chat_member=ChatMemberMember(
            status=<ChatMemberStatus.MEMBER>, 
            user=User(
                first_name='Inquire', 
                id=5856151524, 
                is_bot=True, 
                username='inquire_ai_bot'
            )
        ), 
        old_chat_member=ChatMemberLeft(
            status=<ChatMemberStatus.LEFT>,
            user=User(
                first_name='Inquire', 
                id=5856151524, 
                is_bot=True,
                username='inquire_ai_bot'
            )
        )
    ), 
    update_id=10526409
)

On message received This is any message sent to the bot in a direct message or a group from a user. This update includes the chat type and who's it is, in this case its a private chat b/t me and the bot.

Update(
    my_chat_member=ChatMemberUpdated(
        chat=Chat(first_name='Lucas', id=779540407, type=<ChatType.PRIVATE>, username='lucaskohorst'), 
        date=datetime.datetime(2022, 12, 19, 1, 35, 1, tzinfo=<UTC>), 
        from_user=User(first_name='Lucas', id=779540407, is_bot=False, language_code='en', username='lucaskohorst'), 
        new_chat_member=ChatMemberMember(status=<ChatMemberStatus.MEMBER>, user=User(first_name='Inquire', id=5856151524, is_bot=True, username='inquire_ai_bot')), 
        old_chat_member=ChatMemberBanned(status=<ChatMemberStatus.BANNED>, until_date=datetime.datetime(1970, 1, 1, 0, 0, tzinfo=<UTC>), 
        user=User(first_name='Inquire', id=5856151524, is_bot=True, username='inquire_ai_bot'))), 
        update_id=10526411
)

On message sent This one is quite long and there is tons of repeated information (the User object is in there like 4 times) but generally gives

With all of that. Yes it is totally possible to update Connection on a message to the bot no matter where the message is coming from.

The API

As mentioned moving everything up to the API level will benefit us in a few ways

I am totally in favor of what you suggested. On my side I was going to focus on integrating dust.tt apps into the python telegram client. But now it seems like I should simultaneously work on

  1. Refactoring the telegram bot to handle query the API for just about everything
  2. Familiarize myself with the current API and work on routes for dust.tt.
    • The way I am thinking about the dust.tt routes is essentially any app that is created on my dust.tt account will be a chatbot. This allows for new bots to constantly be added w/ out updating and eventually as it becomes openish access to submit bots allow for easier scalability.
    • This would essentially look like a general dust.tt API route to list apps available and get a specific app
Lucas-Kohorst commented 1 year ago

ah also just thought of with this, the axiom logging at the bot level isn't really needed and should just be incorporated to the API level

Lucas-Kohorst commented 1 year ago

@ethan-stone this issue can be closed right?

ethan-stone commented 1 year ago

Yup this can be closed