Skip to main content

What is Chainlit?

Chainlit is an open-source async Python framework which allows developers to build scalable Conversational AI or agentic applications. While providing the base framework, Chainlit gives you full flexibility to implement any external API, logic or local models you want to run. Test assistant In this tutorial we will be implementing RAG (Retrieval Augmented Generation) in two ways:
  • The first will leverage OpenAI assistants with uploaded documents
  • The second will be using llama_index with a local folder of documents

Setting up Chainlit locally

Virtualenv

Let’s start by creating our virtualenv:

Install dependencies

We are now adding our dependencies and freeze them:

Test Chainlit

Let’s start chainlit:
You should now see a placeholder on http://localhost:8000/ Chainlit demo

Let’s deploy it on Upsun

Init the git repository

Don’t forget to add a .gitignore file. Some folders will be used later on.

Create an Upsun project

The Upsun CLI will automatically set the upsun remote on your local git repository.

Let’s add the configuration

Here is an example configuration to run Chainlit:
Nothing out of the ordinary there! We install all dependencies in the build hook and then start the app with chainlit directly and we specify the port it should run on.
💡 You will need to add your OPENAI_API_KEY to either the configuration or your environment variables on the Upsun console or through the CLI. You can get the key by generating it on the OpenAI Platform site.
To add it as an environment variable through the CLI, you can use:

Let’s deploy!

Commit the files and configuration to deploy!

Review the deployment

If everything goes well, you should have Chainlit deployed and working correctly on your main environment: First push

First implementation: OpenAI Assistant & uploaded files

The goal here is to make Chainlit work with an OpenAI assistant. Our content will be loaded directly in the assistant on OpenAI.

Create the assistant

Go to the Platform Assistants page and create a new one. Assistant Set the system instructions and select the model you want to use. Make sure the Response Format is set to text. I like to keep the temperature low, around 0.10 to avoid hallucinations. Copy your assistant ID asst_[xxx] and add it to your environment variables:

Upload your content

Enable the File search toggle and click + Files. Upload your content. While OpenAI is capable of ingesting a lot of different file formats, I like to upload only Markdown as it is faster and easier to parse, removing potential issues with PDF for example. Upload After a few seconds, the content is ingested and transformed into a vector store, ready to be used: Vector store
💡 You can reuse an existing vector store on a different assistant if you want!
Everything is now ready on the OpenAI side and we can implement the logic in Chainlit.

Adding the assistant logic.

Open app.py and replace the content with the code below:
Feel free to review the whole code but let’s focus on the important parts:
on_chat_start is called when a new chat is created. It creates a new thread on OpenAI to handle the conversation and start it with a new welcome message.
on_message is triggered whenever the user is submitting a new message. It sends the content to the OpenAI API on the thread and then launches a stream. You can find more information on how they work on the official documentation. To summarize, instead of getting the answer as part of the HTTP response of the Message request, we have to poll the Threads API to find new messages that would have been created. It’s a bit more cumbersone but allows OpenAI to perform multiple operations asynchronously and add the results into the thread.

Commit the changes and deploy

Let’s state and commit the changes:
And we can now deploy:

Test the Assistant

Go to the deployed Chainlit instance and ask any question related to the content you uploaded: Test assistant You should get an appropriate answer! It might be a bit slow due to the polling process especially on the first message. But it works! OpenAI gives you the indications where in your documents it sourced some of the information

Second implementation: OpenAI + llama_index

So the goal for this version will be to build the knowledge locally and then rely on OpenAI to output the final form.

Create a new branch

Let’s kickstart this by working on a new environment/branch:

Add two new folders and mounts to store our data

Create the folders first on your machine:
And now add the mount to our Upsun configuration:
data will be used for our source documents and storage will handle the cached VectorStore.

Let’s update our app

We will not be using the OpenAI assistant there so the code will be a lot simpler:
Let’s break down the important parts. When the application is starting, we use the text-embedding-3-small (line 39) to embed our documents into our VectorStore.
And whenever the user creates a new chat, we define a query_engine (lines 43-44). It will be passed alongside every message and will contain the result from the k-search of our vector store. You can note that we are using a similarity_top_k param to define how many documents should be matched when searching.
⚠️ I carefully set the Settings.num_output (line 41) so llama does not give me a bigger context window than what OpenAI can take. You can get a lot of context in the query and longer answers by increasing these values but this will obviously consume more tokens and generate higher bills so be mindful!

Deploy the new environment

As usual, commit and push!
Upsun CLI will confirm you want to create a new environment:
Hit Yes and the new app will be deployed in a new isolated environment.

Push some data

In order to have some documents to work with on the Upsun environment, you can automatically upload your data folder:

Test llama_index

Once deployed, head over to your environment (llama-index-sukwicq-[project id].[region].platformsh.site) and test a prompt: llama test As contrary to the OpenAI file_search, the response does not give you the source of the data as it was passed directly from llama_index to OpenAI.
💡 While most of our system relies on local data, we are still generating the final answer through OpenAI. If you wanted to run everything locally, you could rely on Chainlit being capable of querying model like SmolLM running locally.

Bonus: Adding authentification to Chainlit

Now that our Chainlit application is deployed and available, it would be great to add some form of authentication to make sure only you and your folks can access it. While Chainlit has many capabilities for this, we will go for the simpler route of using a sqlite database for this.

Create the database folder

And add the mount in the Upsun configuration:

Add the auth logic to our application

First let’s add a new environment variable:
We can now add the logic into app.py:
When the script is run, it first check that it can open the database or create it if it doesn’t exist. Adding @cl.password_auth_callback will automagically add a login form to our app. The logic in auth_callback is pretty simple right there. Feel free to change it the way you want. We are hashing the form password and looking up for a user that matches both username and password. If so we return the user with the admin privileges.

Create a simple script to generate hashed passwords

You can invoke it and it will output the password and the hash:

Adding users

Now it’s just a matter of creating new records in the users table of our auth.db. Don’t forget you need to put the hashed version of the password in the database! You can either run queries through the CLI or use GUI: Creating users And we should now be ready to go!

Deploy the authentication

As usual, commit and push:
In order for our authentication to work, let’s upload our sqlite database:

Login now!

You now need to input your credentials to login to your Chainlit interface: Login form

Conclusion

In this tutorial, we’ve successfully deployed a Chainlit application on Upsun with two different RAG implementations, each offering unique advantages. The OpenAI Assistant approach provides built-in file handling and clear source attribution, while the llama_index implementation offers more control over the embedding process and local vector store management. We’ve also added a layer of security through SQLite-based authentication, making the application production-ready. By leveraging Upsun’s platform capabilities, particularly its storage mounts and environment management, we’ve created a scalable and secure conversational AI interface that can be adapted for various use cases. Whether you’re building a document-aware chatbot, a knowledge base assistant, or any other RAG-powered application, this setup provides a solid foundation for further development. Remember that while we used OpenAI’s models for generation in both implementations, the architecture we’ve built could be adapted to work with other language models, including local ones, depending on your specific needs and requirements.
Last modified on April 27, 2026