Overall Application Architecture - JamestheCog/chatbot_for_sics GitHub Wiki
If you recall from the [Setting Up the Application] page of this wiki, this project has numerous dependencies in and outside of Ruby's gem ecosystem. However, the one thing I did not discuss over there (that I will here) is how the different components of the application work with one another to make the application what it currently is as of the time of writing - hence the above diagram! Long story short:
- Application: this is a web application built with Sinatra (i.e., Ruby) and hosted on Render. It's also the only outward-facing component in the diagram - the only part caregivers and the rest of the internet will see should they chance across the application!
- Database: this component serves to log any sent messages - either by the chatbot or the user.
- Gemini: this is the underlying AI model (i.e., Google's Gemini-2.5-Flash) that the entire chatbot depends on to be able to communicate with users.
- MailerSend: this is the mail sending service that will be used to send conversations to the specified email address (in the
.envfile) for a particular day. - Cronjob: the only component of the application not previously mentioned in the wiki's other pages; this Cronjob prevents the chatbot from experiencing cold starts (i.e., Render spins down applications that have been inactive for 15 minutes), the database from becoming inactive (i.e., free SQLitecloud databases take a nap after 12 hours' worth of inactivity), and also ensures that that MailerSend point above is executed.
Each and every time that a user accesses the webpage and enters a message, the following happens:
- The message is logged in the database via a helper function in the source code responsible for handling all message uploads.
- The message is then sent off to Gemini's servers through a hyper-specific JSON payload. In doing so, the prompt (i.e.,
base_bot_prompt.txt) is also decoded and sent as part of the said payload to Gemini's servers. - The response from Gemini is then acquired before being extracted into a more appropriate (and simpler) JSON object for downstream
- The said response is then logged in the database via the same helper function again
- The response is then displayed to the user on the application's front-end.
Otherwise, do note that the application is capable of internally tracking users' message history (via a user-defined class MessageLogger) - however, this does not include small image uploads (i.e., less than four kilobytes); mostly text for now! I'll also talk more about each individual component below:
Individual Components
Gemini
| Relevant Files in Codebase | Purpose |
|---|---|
./utils/chat.rb |
Contains utilities for decrypting ./resources/base_bot_prompt.txt and fetching model responses from Gemini's servers |
./utils/classes/message_logger.rb |
Contains the user-defined MessageLogger class mentioned above - this object is responsible for storing user conversations |
Like I mentioned earlier, Gemini (i.e., Gemini-2.5-Flash) is the underlying LLM behind the entire chatbot. However, there are a couple of things to note when editing the items in the above table:
get_model_response
This function accepts two arguments:
conversation_history: an array containing the user's conversation with the chatbot. This is obtained from themessagesattribute of the currentMessageLoggerinstance in the user's session.num_attempts: the number of attempts the function should try to fetch a response from Gemini.
The thing about Gemini is that depending on when an API call is made, its servers can be hammered - during which you might end up with error 503s that might cause the application to malfunction. Hence, the function will attempt to fetch a response from the model num_attempts amount of times (during which an exponential delay with a random jitter is utilized) - after which it automatically fails. Unfortunately, there's no way of knowing when this might happen, so one can only hope for the best and try to avoid this problem with multiple calls at different time points!
Database
Like previously mentioned, we're using SQLitecloud as our database service. However, because this database service of ours' is currently on SQLitecloud's free-tier services, any project on its platform will be immediately spun down after 12 hours of inactivity. So, I've attempted a workaround this with one of the application's (hidden) routes: /wake_database! The route itself accepts a POST request and expects a JSON payload with the following schema:
{
"authorization" : {"password" : "<password here>"}
}
After which (i.e., following successful authentication) a simple SQL query will be sent to SQLitecloud to be run! Fortunately for us, I don't think SQLitecloud has imposed any hard limits for long a database or a project can be active (unlike Render), so we should be good for now!