Can someone please explain example chatbot code?

I tried the example chatbot code on the streamlit website [link].(Streamlit • A faster way to build and share data apps)

I have 2 questions.

  1. Why do I need to add if "messages" not in st.session_state: here? When the chatbot is first run, it’s obvious that messages won’t exist, so why doesn’t it work if I just write a code with st.session_state without the if statement?"
if "messages" not in st.session_state:
    st.session_state["messages"] = [{"role": "assistant", "content": "How can I help you?"}]
  1. Why is the code for generating chatbot responses and adding them to the chat window all placed under if prompt := st.chat_input():? This if statement runs when there is user input, but why does it behave like a for loop, running continuously instead of just once?
if prompt := st.chat_input():
    if not openai_api_key:
        st.info("Please add your OpenAI API key to continue.")
        st.stop()

    client = OpenAI(api_key=openai_api_key)
    st.session_state.messages.append({"role": "user", "content": prompt})
    st.chat_message("user").write(prompt)
    response = client.chat.completions.create(model="gpt-3.5-turbo", messages=st.session_state.messages)
    msg = response.choices[0].message.content
    st.session_state.messages.append({"role": "assistant", "content": msg})
    st.chat_message("assistant").write(msg)```

Hi @Dohun,

Question 1:

This pattern:

if "thing" not in st.session_state:
    st.session_state["thing"] = 123

Is a common pattern streamlit. The reason it’s necessary is that every interaction with a streamlit app → the app is rerun from top to bottom.

So, if you just did something like this:

messages = [{"role": "assistant", "content": "How can I help you?"}]
...

That would mean every time the app reran, the message variable would be recreated and end up just being that same starting message, and nothing you did with the app would make it ever make a permanent change to messages.

One tool Streamlit provides to help if you actually WANT to keep track of a variable that lasts for more than 1 run of the app is st.session_state – this is basically a special dictionary that lasts for as long as the session (e.g. from when the user opens the streamlit app to when they close it).

BUT, if you just did this:

st.session_state["messages"] = [{"role": "assistant", "content": "How can I help you?"}]

that would STILL mean that the list of messages would be replaced every time the app runs.

if "messages" not in st.session_state:
    st.session_state["messages"] = [{"role": "assistant", "content": "How can I help you?"}]

This pattern is very useful for when you want to make something in session stage have some starting value.

It’s essentially:

if this is the first time (in this session) that you ran the app:
    populate messages with the default starting value

Question 2:

Again, if you think of a streamlit app as “a script that runs from top to bottom”, then you can think of this app as doing the following:

  1. Add a sidebar to collect some API keys (with st.sidebar:...)
  2. If this is the first run of the app (in this session), create the messages list and populate it with the first default message (if "messages" not in st.session_state:...)
  3. Loop through all the messages currently in the messages list and show them in the app (for msg in st.session_state.messages:...)
  4. If the user types in a new message:
    a. Add it to the messages list
    b. Get the response from OpenAI and also add that to the messages list
    c. Show what the user typed as a chat message immediately (st.chat_message("user").write(prompt)
    d. Show the OpenAI response as a chat message immediately st.chat_message("assistant").write(msg)

The reason those 4c and 4d are helpful is that it means you don’t need to rerun step 3 before you see the result from OpenAI. Alternatively, you could skip 4c and 4d, and just do st.rerun().

3 Likes

Here’s a simplified example that also shows how to you can make a “clear history” button that also reset the messages list to be the default starting list. It also uses st.rerun to just rerun the whole app once the prompt and responses have been added to the messages list.

import streamlit as st
import random

clear_history = st.button("🗑️ Clear history")
# Initialize chat history
if "messages" not in st.session_state or clear_history:
    st.session_state.messages = [{"role": "assistant", "content": "Let's start chatting! 👇"}]

# Display chat messages from history on app rerun
for message in st.session_state.messages:
    with st.chat_message(message["role"]):
        st.markdown(message["content"])

# Accept user input
if prompt := st.chat_input("What is up?"):
    # Add user message to chat history
    st.session_state.messages.append({"role": "user", "content": prompt})
    
    assistant_response = random.choice(
        [
            "Hello there! How can I assist you today?",
            "Hi, human! Is there anything I can help you with?",
            "Do you need help?",
        ]
    )
    st.session_state.messages.append({"role": "assistant", "content": assistant_response})
    st.rerun()

You can see it running here

2 Likes

@blackary Thank you very much for the kind explanation! It really helped me a lot. Based on your explanation, it seems like understanding Streamlit’s syntax is more important than Python syntax in this case. Is there a well-organized article or resource that covers such Streamlit-specific syntax?

I think Basic concepts of Streamlit - Streamlit Docs is a great place to start

Thank you. I read the docs. Then would it be correct to think that in Streamlit, since the code restarts from the top every time there’s an interaction, an if statement functions similarly to a for loop?

I really think it’s better to just think “every time I interact, the script reruns from the top”. In one sense, that just means “the whole script is sort of in a while loop”. There’s nothing particular about an if statement being more like a while loop than anything else.

You can maybe think of it like this:

while True:
    run_the_whole_page()
    pause_till_theres_an_interaction()

Thank you very much for kind explanation.

1 Like

This topic was automatically closed 2 days after the last reply. New replies are no longer allowed.