Build an AI Chat Assistant in 60 Lines of Code
You've probably used ChatGPT several times. But have you built your own AI assistant?
In this tutorial, you'll build a simple AI Study Assistant using Python, Streamlit, and the OpenAI API.
What we're building
You'll be able to ask questions, receive streaming responses, and ask follow-up questions using the conversation history.
✓The key idea
Your application manages the conversation and its UI. The model generates the response.
What you need
- Python 3.11+
- An OpenAI API key
- Streamlit
- OpenAI Python SDK
- python-dotenv
Install the libraries:
pip install streamlit openai python-dotenvCreate .env:
OPENAI_API_KEY=your-api-key-here
MODEL_NAME=gpt-5-miniAdd .env to .gitignore:
.envNever commit your API key to GitHub.
Step 1: Connect to the model
Create app.py:
import os
import streamlit as st
from openai import OpenAI
from dotenv import load_dotenv
load_dotenv()
client = OpenAI(api_key=os.getenv("OPENAI_API_KEY"))
MODEL = os.getenv("MODEL_NAME", "gpt-5-mini")load_dotenv() loads your environment variables, and OpenAI() creates the client used to call the model.
The client reads your API key once and reuses it for every request. MODEL falls back to "gpt-5-mini" if you haven't set MODEL_NAME, so the app still runs even with a minimal .env.
Step 2: Give the assistant instructions
SYSTEM_PROMPT = """You are a helpful study assistant.
Help students understand technical concepts in simple language.
Give examples when useful.
Stay within the scope of the assistant.
Do not invent information.
Ask for clarification when necessary.
For topics other than study you can politely refuse to answer"""This is the system prompt. It tells the model how your assistant should behave.
Change it and you can turn the same application into a coding assistant, travel planner, or career coach.
✓Where the intelligence lives
The model provides the intelligence. Your application provides the instructions and context.
Step 3: Create the chat interface
st.title("AI Study Assistant")
if "messages" not in st.session_state:
st.session_state.messages = []
for message in st.session_state.messages:
with st.chat_message(message["role"]):
st.markdown(message["content"])st.session_state stores the conversation for the session while the user is using the application. Without it, the chat history would disappear when Streamlit reruns the script.
Streamlit reruns your entire app.py from top to bottom after every interaction. The if "messages" not in st.session_state check makes sure the list is only created once, on the first run. The for loop below it then redraws every past message so the conversation stays visible across reruns.
Step 4: Get the user's message
if prompt := st.chat_input("Ask me anything..."):
st.session_state.messages.append(
{"role": "user", "content": prompt}
)
with st.chat_message("user"):
st.markdown(prompt)st.chat_input() gives us the chat input box.
When the user submits a message, we add it to the conversation history, then immediately render it on screen so the user sees their own message before the assistant replies.
Step 5: Call the model and stream the response
with st.chat_message("assistant"):
response = ""
try:
stream = client.responses.create(
model=MODEL,
instructions=SYSTEM_PROMPT,
input=st.session_state.messages,
stream=True,
)
for event in stream:
if event.type == "response.output_text.delta":
response += event.delta
st.write(response)
except Exception as e:
st.error("Something went wrong. Please try again.")
print(e)client.responses.create sends the conversation, instructions, and selected model to the OpenAI API and enables streaming so the response can be displayed as it is generated.
The for event in stream loop reads the response piece by piece as the model generates it, rather than waiting for the whole answer. Each response.output_text.delta event carries the next chunk of text, which we append to response and redraw with st.write(). The try/except wraps the call so a network error or API failure shows a friendly message instead of crashing the app.
Three things are happening here:
Conversation history
input=st.session_state.messagesThe application sends the previous messages with the new request. That's how the assistant can follow the conversation. The chat history is maintained for the session and we lose it if we reopen the chat window
Instructions
instructions=SYSTEM_PROMPTThe system prompt controls the assistant's behavior.
Streaming
stream=TrueThe response is displayed incrementally instead of waiting for the complete answer.
Step 6: Save the response
if response:
st.session_state.messages.append(
{"role": "assistant", "content": response}
)Now the assistant's response becomes part of the conversation history.
The next request will include it. The if response: guard matters here — if the API call failed and response is still an empty string, we skip saving it, so a failed turn doesn't leave a blank message in the history.
The complete application
▶ Show the complete app.py
import os
import streamlit as st
from openai import OpenAI
from dotenv import load_dotenv
load_dotenv()
client = OpenAI(api_key=os.getenv("OPENAI_API_KEY"))
MODEL = os.getenv("MODEL_NAME", "gpt-5-mini")
SYSTEM_PROMPT = """You are a helpful study assistant.
Help students understand technical concepts in simple language.
Give examples when useful.
Stay within the scope of the assistant.
Do not invent information.
Ask for clarification when necessary."""
st.title("AI Study Assistant")
if "messages" not in st.session_state:
st.session_state.messages = []
for message in st.session_state.messages:
with st.chat_message(message["role"]):
st.markdown(message["content"])
if prompt := st.chat_input("Ask me anything..."):
st.session_state.messages.append(
{"role": "user", "content": prompt}
)
with st.chat_message("user"):
st.markdown(prompt)
with st.chat_message("assistant"):
response = ""
try:
stream = client.responses.create(
model=MODEL,
instructions=SYSTEM_PROMPT,
input=st.session_state.messages,
stream=True,
)
for event in stream:
if event.type == "response.output_text.delta":
response += event.delta
st.write(response)
except Exception as e:
st.error("Something went wrong. Please try again.")
print(e)
if response:
st.session_state.messages.append(
{"role": "assistant", "content": response}
)Run it
Start the application:
streamlit run app.pyTry asking these questions:
What is an API? Give me a real-world example. How is that different from a database?
The assistant can follow the conversation because your application sends the previous messages with each request.
What did you just build?
You didn't train a model. You didn't build a neural network. You built a software application around an existing LLM. That's AI engineering.
This simple application already introduces several concepts you'll use in larger AI systems:
- LLM API calls
- Prompting
- Conversation state
- Streaming
- Error handling
Where to go next
Once this works, try adding:
- Structured outputs — make responses predictable
- Tool calling — let the assistant take actions
- RAG — let it answer questions from your documents
- Agents — combine models, tools, and workflows
Start by building. 🚀
Learn AI engineering by building
At Auragile, we focus on practical AI engineering for software developers — building real applications with LLMs, APIs, RAG, tools, and agents.
Want to go further than a 60-line assistant?
Explore the course →