**Building a Streamlit UI for Your LangGraph Customer Service Agent**
In a previous article, I walked through how to build a LangGraph-based AI agent to automate a 15-minute customer service booking session. That version ran in a terminal-based CLI, which was great for testing logic but not ideal for a customer-facing experience.
In this article, we evolve that project by building a clean, interactive Streamlit user interface on top of the existing LangGraph agent. This allows us to present a polished UI while keeping the agent logic separate and reusable.
—
### User Interface for the Agent
Streamlit differs from a CLI only in presentation. Both serve as a wrapper around the LangGraph agent. While the CLI collected input and printed responses, Streamlit renders structured information such as:
– Current booking details
– Price quotes
– Acceptance or rejection buttons
– Optimized time slot options
The architecture remains the same: Streamlit handles user interaction and displays state, while the LangGraph agent processes the conversation and manages business logic.
—
### Setting Up the Streamlit Page
Since the project uses Poetry for dependency management, install Streamlit with:
“`bash
poetry add streamlit
“`
Create a new file named `streamlit_app.py`. Start by importing necessary modules:
“`python
from __future__ import annotations
import os
from datetime import datetime
from typing import Any
from uuid import uuid4
import streamlit as st
from dotenv import load_dotenv
from langchain_core.messages import AIMessage, HumanMessage
from langchain_openai import ChatOpenAI
from customer_service_agent.graph import build_graph
from customer_service_agent.models import (
AgentState,
BookingDetails,
TimeOption,
)
from customer_service_agent.observability import (
create_langfuse_handler,
flush_langfuse,
graph_config,
)
“`
The agent graph does not contain Streamlit-specific code, which means it can also run in a CLI, API, or other frontends.
Initialize the agent state:
“`python
INITIAL_STATE: AgentState = {
“messages”: [],
“booking_details”: BookingDetails(),
“calculated_price”: None,
“time_options”: [],
“selected_slot”: None,
“status”: “gathering_info”,
}
“`
Use `session_state` to preserve conversation across reruns, and initialize the graph and handler once per session:
“`python
def initialize_session() -> None:
if “graph” in st.session_state:
return
llm = ChatOpenAI(
model=os.getenv(“OPENAI_MODEL”, “gpt-4o-mini”),
temperature=0,
)
handler = create_langfuse_handler()
st.session_state.graph = build_graph(llm)
st.session_state.handler = handler
st.session_state.config = graph_config(
str(uuid4()),
handler,
)
st.session_state.agent_state = INITIAL_STATE.copy()
st.session_state.started = False
“`
—
### Processing User Input
Define an `_invoke` function to submit customer messages and update the agent state:
“`python
def _invoke(customer_text: str) -> None:
graph_input: dict[str, Any] = {“messages”: [HumanMessage(content=customer_text)]}
if not st.session_state.started:
graph_input.update(INITIAL_STATE)
graph_input[“messages”] = [HumanMessage(content=customer_text)]
st.session_state.started = True
try:
result = st.session_state.graph.invoke(graph_input, config=st.session_state.config)
st.session_state.agent_state = result
flush_langfuse(st.session_state.handler)
except Exception:
st.session_state.started = bool(st.session_state.agent_state.get(“messages”))
st.error(“The assistant could not process that request. Please try again.”)
“`
The function supports both chat messages and button clicks, preserving conversation history via LangGraph’s `add_messages` reducer.
—
### Rendering the Conversation
Define helper functions to render messages, pricing, options, and booking status. For example:
“`python
def _render_messages(state: AgentState) -> None:
if not state.get(“messages”):
with st.chat_message(“assistant”):
st.write(
“Hi! I can help you book house or couch cleaning. ”
“Tell me what you need, including the size and service address.”
)
return
for message in state[“messages”]:
if isinstance(message, HumanMessage):
role = “user”
elif isinstance(message, AIMessage):
role = “assistant”
else:
continue
with st.chat_message(role):
st.write(str(message.content))
“`
This function pulls messages from the latest LangGraph state and displays them in Streamlit chat bubbles.
—
### Running the Streamlit App
Test the app locally with:
“`bash
poetry run streamlit run customer_service_agent/streamlit_app.py
“`
You’ll need an `OPENAI_API_KEY`. The interface supports:
– Natural language understanding
– Dynamic price quoting
– Time slot selection
– Booking confirmation
Example interactions show the agent asking for missing details, presenting options, and confirming reservations — all through a clean UI.
—
### FAQ
**Q: Do I need an OpenAI API key?**
Yes. The agent uses OpenAI’s `gpt-4o-mini` model by default, so you’ll need a valid API key.
**Q: Can I use a different frontend framework?**
Yes. The agent graph is framework-agnostic and can be integrated into Flask, FastAPI, WhatsApp, or custom web apps.
**Q: Is conversation history preserved across refreshes?**
No. Streamlit reruns the script on each interaction. To persist state, integrate a database or external checkpointer.
**Q: How much does testing cost?**
Costs depend on token usage. Basic booking interactions typically cost only a few cents.
**Q: Can I add more features like WhatsApp or email integration?**
Absolutely. The article mentions plans for WhatsApp integration, and the architecture supports adding new channels easily.
—
### Conclusion
By adding a Streamlit UI, we transformed a terminal-based LangGraph agent into a user-friendly customer service booking interface. The separation of UI and logic ensures the agent remains portable across platforms. With minimal changes, this setup can support additional channels and richer interactions. If you’d like to explore integrations, state persistence, or deployment options, this project provides a solid foundation.



