chainlit project

This is a minimal Chainlit app that listens for incoming messages and echoes them back:

import chainlit as cl

@cl.on_message
async def on_message(msg: cl.Message):
    print("The user sent:", msg.content)
    await cl.Message(content=f"You said: {msg.content}").send()

What it does

@cl.on_message
  • Registers the function as a handler for every user message.
async def on_message(msg: cl.Message):
  • Defines an asynchronous callback.
  • msg contains information about the user's message.
print("The user sent:", msg.content)
  • Prints the message content to the server console/logs.
await cl.Message(content=f"You said: {msg.content}").send()
  • Creates a new message.
  • Sends it back to the user, effectively creating an echo bot.
    Example

User sends:

Hello Chainlit!

Server console:

The user sent: Hello Chainlit!

Chat response:

You said: Hello Chainlit!

Running the app

Save the code in a file such as app.py, then run:

chainlit run app.py

Open the local URL displayed in the terminal (typically http://localhost:8000) to interact with the bot.

One thing to consider: print() is fine for quick debugging, but for production applications it's usually better to use Python's logging module so logs can be filtered, stored, and monitored more effectively.


There are actually two different uses of cl.Message in your code, and it's easy to mix them up.

Let's look at this line:

async def on_message(msg: cl.Message):

and this line:

await cl.Message(content=f"You said: {msg.content}").send()

They use the same class (cl.Message) in different ways.

  1. msg: cl.Message — Type Hint

Here:

async def on_message(msg: cl.Message):

the part after the colon is a type annotation (type hint).

It tells Python (and developers):

"The variable msg is expected to be an object of type cl.Message."

Think of it like:

name: str
age: int

or

def add(a: int, b: int):

Similarly:

msg: cl.Message

means:

msg is a Message object provided by Chainlit

This does not create a message object.

It only describes what type msg will be.

2. Where does msg come from?

When a user sends:

Hello

Chainlit automatically creates a Message object behind the scenes.

Something conceptually like:

msg = cl.Message(
    content="Hello"
)

Then Chainlit calls your function:

await on_message(msg)

So you don't create msg.

Chainlit creates it and passes it into your handler.

3. What is inside msg?

msg is not just a string.

It is an object that contains information about the message.

Example:

print(msg.content)

might output:

Hello

You can think of it as:

msg = {
    "content": "Hello",
    "author": "user",
    "timestamp": "..."
}

(not the real implementation, but conceptually similar)

So:

msg.content

extracts the text the user typed.

  1. cl.Message(...) — Creating a New Message

Now look at:

await cl.Message(
    content=f"You said: {msg.content}"
).send()

This is different.

Here you are actually creating a new Message object.

Like:

new_msg = cl.Message(
    content="You said: Hello"
)

Then:

await new_msg.send()

sends it to the chat UI.

Simple Analogy

Imagine a post office.

Incoming letter
async def on_message(msg: cl.Message):

msg is the letter that arrived.

Chainlit delivers it to you.

Outgoing letter

cl.Message(content="Thanks!")

You are writing a new letter.

Then:

.send()

puts it in the mailbox.

Visual Flow

User types:
    "Hello"


Chainlit creates:

    msg = Message(
        content="Hello"
    )


on_message(msg)


msg.content
    = "Hello"


Create new Message:

    Message(
        content="You said: Hello"
    )


send()


Displayed in chat:
    "You said: Hello"

So the key distinction is:

msg: cl.Message

→ "This variable will contain a Message object." (type hint)

while

cl.Message(...)

→ "Create a new Message object."

Those two uses look similar because they reference the same class, but one is describing a type and the other is instantiating an object from that type.

Comments (0)