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

> Build production-grade chatbot UIs with Streamlit for AI agents. Learn to use st.chat_message and st.session_state with OpenAI's API in this guide from the Agents Towards Production repository.

- Repository: [NirDiamant/agents-towards-production](https://github.com/nirdiamant/agents-towards-production)
- Tags: how-to-guide
- Published: 2026-05-18

---

**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`](https://github.com/NirDiamant/agents-towards-production/blob/main/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`](https://github.com/NirDiamant/agents-towards-production/blob/main/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`](https://github.com/NirDiamant/agents-towards-production/blob/main/tutorials/agent-with-streamlit-ui/app.py). Below is the essential structure you can adapt for your own projects:

```python
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`](https://github.com/NirDiamant/agents-towards-production/blob/main/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:

```bash
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`](https://github.com/NirDiamant/agents-towards-production/blob/main/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`](https://github.com/NirDiamant/agents-towards-production/blob/main/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.