Building Chatbot UIs with Streamlit for Agent Interaction: A Production-Grade Implementation

You can build a production-ready chatbot UI for AI agents using Streamlit by combining st.chat_message for the interface, st.session_state for persistence, and OpenAI's API for inference, as demonstrated in the Agents Towards Production repository.

This guide walks through a complete implementation of building chatbot UIs with Streamlit for agent interaction found in NirDiamant/agents-towards-production. The tutorial provides a minimal yet production-ready architecture that supports real-time file uploads and contextual conversations with GPT-4o.

Architectural Overview

The implementation in tutorials/agent-with-streamlit-ui/app.py follows a clean separation between the presentation layer and the AI backend. Four core components work together to create a responsive chat experience.

Streamlit Frontend Components

The UI layer leverages Streamlit's native chat elements to create a familiar messaging interface. st.set_page_config and st.title establish the page layout, while st.chat_message renders conversation bubbles for both the user and assistant roles.

Session persistence is handled through st.session_state.messages, which stores the entire conversation history. This ensures that messages survive reruns and maintain context across user interactions. The chat loop spans lines 76-117 of app.py, handling message display, user input capture, and response rendering.

File Upload and Processing

A sidebar interface built with st.sidebar.file_uploader accepts TXT and PDF files. When a user uploads a document, the process_uploaded_file function dispatches the content based on MIME type.

For PDF documents, the code calls extract_text_from_pdf (lines 20-25) using PyPDF2 to extract text from each page. Plain text files are decoded directly from the uploaded bytes. This extracted content becomes available as supplemental context for the LLM.

OpenAI LLM Integration

The backend initializes an openai.OpenAI client (lines 15-18) using an API key loaded from a .env file via load_dotenv. The generate_response function constructs the message payload, optionally prepending a system message containing the uploaded file's content when present.

The function calls client.chat.completions.create with model gpt-4o (lines 53-71), passing the assembled message history. This design allows the agent to reference uploaded documents without requiring users to manually paste content into the chat.

Core Implementation in app.py

The complete logic resides in tutorials/agent-with-streamlit-ui/app.py. Below is the essential structure you can adapt for your own projects:

import os, openai, streamlit as st, io
from dotenv import load_dotenv
import PyPDF2

load_dotenv()
client = openai.OpenAI(api_key=os.getenv("OPENAI_API_KEY"))

def extract_text_from_pdf(pdf_file):
    reader = PyPDF2.PdfReader(pdf_file)
    return "\n".join(page.extract_text() for page in reader.pages)

def process_uploaded_file(uploaded):
    if uploaded.type == "application/pdf":
        return extract_text_from_pdf(uploaded)
    if uploaded.type == "text/plain":
        return uploaded.getvalue().decode()
    return "Unsupported file type."

def generate_response(prompt, extra=None):
    messages = []
    if extra:
        messages.append({
            "role": "system",
            "content": f"The user uploaded:\n\n{extra}\n\nUse this when answering."
        })
    messages.append({"role": "user", "content": prompt})
    resp = client.chat.completions.create(model="gpt-4o", messages=messages)
    return resp.choices[0].message.content

st.title("🤖 AI Chatbot Assistant")
uploaded = st.sidebar.file_uploader("Upload .txt or .pdf", type=["txt", "pdf"])
file_content = process_uploaded_file(uploaded) if uploaded else None

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

for msg in st.session_state.history:
    with st.chat_message(msg["role"]):
        st.write(msg["content"])

if user_msg := st.chat_input("Your message"):
    st.session_state.history.append({"role": "user", "content": user_msg})
    with st.spinner("Thinking…"):
        reply = generate_response(user_msg, file_content)
    st.session_state.history.append({"role": "assistant", "content": reply})
    with st.chat_message("assistant"):
        st.write(reply)

Real-Time Feedback Mechanisms

The implementation uses with st.spinner("Processing file...") and with st.spinner("Thinking...") to provide visual feedback during blocking operations. This improves perceived performance while PyPDF2 parses documents or while waiting for the OpenAI API response.

Dependencies and Environment Setup

The requirements.txt file in the tutorial directory pins specific versions to ensure reproducibility:

  • streamlit>=1.28.0
  • openai>=1.0.0
  • PyPDF2 (for PDF text extraction)
  • python-dotenv>=1.0.0
  • langchain>=0.1.0

Install dependencies and launch the application:

pip install -r requirements.txt
streamlit run app.py

The tutorial also includes building-chatbot-notebook.ipynb for step-by-step exploration in Jupyter before deploying the Streamlit version.

Summary

  • Session state management using st.session_state.history persists conversations across Streamlit reruns without external databases.
  • File processing pipeline supports both PDF (via PyPDF2) and plain text uploads, injecting content as system prompts for contextual answers.
  • OpenAI integration uses the modern openai>=1.0.0 client library with GPT-4o, configured via environment variables.
  • UX enhancements include native chat elements and spinner states to indicate processing activity.
  • Production patterns mirror larger deployment architectures found elsewhere in the repository, making this an ideal prototyping foundation.

Frequently Asked Questions

How does the chatbot maintain conversation history across interactions?

The implementation stores messages in st.session_state.history, a Streamlit session state variable that persists data for the duration of the browser session. When a user submits a message, the code appends both the user input and the assistant response to this list, then rerenders the full history using st.chat_message for each entry.

Can the UI handle multiple file types simultaneously?

The current process_uploaded_file function in app.py processes a single uploaded file, prioritizing PDFs and TXT files. It uses the type attribute from the uploaded file object to dispatch to PyPDF2 for PDFs or decode bytes for plain text. To support multiple files, you would modify the file uploader to accept multiple uploads and iterate through them before calling generate_response.

What is the advantage of using a system prompt for uploaded file content?

Injecting file content as a system-role message rather than appending it to the user's query ensures the LLM treats the information as background context rather than part of the specific question. According to the source code, this approach uses the message: "The user has uploaded a file with the following content... Please consider this information when responding," which typically results in more natural integration of the document context into answers.

Is this architecture suitable for production deployment beyond local prototyping?

Yes, the code structure follows patterns used for scalable agent deployments. While the tutorial uses Streamlit's built-in server for simplicity, the separation of UI logic (app.py), processing functions (extract_text_from_pdf, process_uploaded_file), and LLM calls (generate_response) makes it straightforward to migrate the backend logic to FastAPI containers or add authentication layers as demonstrated in other tutorials within the Agents Towards Production repository.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →