Chat and LLM apps

Streamlit · Advanced · 12 min

Chat front ends for LLMs and assistants are one of the most common things people build with Streamlit.

Streamlit has elements made for chat interfaces:

  • st.chat_input("Ask something") renders an input pinned to the bottom of the page and returns the submitted text (or None) for the rerun after the user presses Enter. It can also accept file attachments with accept_file=True.
  • with st.chat_message("user"): (or "assistant", or any name with an avatar=) draws a message bubble; anything written inside appears in it.
  • st.write_stream(generator) writes a stream of text chunks as they arrive, with a typewriter effect, and returns the full concatenated text when done. It accepts plain generators and the streaming responses of OpenAI-style clients.

Because the script reruns on every message, the conversation must live in session state. The standard pattern:

1. Initialize st.session_state.messages = []. 2. Re-display every stored message at the top of each run. 3. When chat_input returns text, display it, call the model, stream the reply, and append both messages to the history.

Cache the model client with @st.cache_resource, and read API keys from st.secrets.

st.status("Thinking...") shows expandable progress for multi-step agents, and st.feedback("thumbs") collects ratings. Long conversations cost tokens and memory: trim or summarize history before sending it to the model.

Example

import time
import streamlit as st

def fake_llm(prompt):                  # stand-in for a streaming LLM call
    for word in f"You said: {prompt}. Nice!".split():
        yield word + " "
        time.sleep(0.05)

if "messages" not in st.session_state:
    st.session_state.messages = [{"role": "assistant", "content": "Hi! Ask me anything."}]

for m in st.session_state.messages:    # redraw history on every rerun
    with st.chat_message(m["role"]):
        st.markdown(m["content"])

if prompt := st.chat_input("Your message"):
    st.session_state.messages.append({"role": "user", "content": prompt})
    with st.chat_message("user"):
        st.markdown(prompt)
    with st.chat_message("assistant"):
        reply = st.write_stream(fake_llm(prompt))   # returns the full text
    st.session_state.messages.append({"role": "assistant", "content": reply})

Remember

  • st.chat_input returns the submitted message; st.chat_message draws bubbles
  • Keep the conversation in session state and redraw it every run
  • st.write_stream displays streamed tokens and returns the full reply

Watch out for

  • Not storing messages in session state, so the conversation disappears on the next message.

Check yourself

  1. Why does a Streamlit chat app loop over `st.session_state.messages` at the top of the script?

Sign in to answer, see the explanations, and get this lesson back as a quick review later.

Official docs: docs.streamlit.io/develop/api-reference/chat