← All posts

Build an AI Chat Assistant in 60 Lines of Code

·6 min read

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.

Browser
Streamlit Chat UI
Python app + history
OpenAI API (LLM)

✓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:

bash
pip install streamlit openai python-dotenv

Create .env:

.env
OPENAI_API_KEY=your-api-key-here
MODEL_NAME=gpt-5-mini

Add .env to .gitignore:

.gitignore
.env

Never commit your API key to GitHub.


Step 1: Connect to the model

Create app.py:

app.py
python
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

python
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

python
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

python
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

python
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

python
input=st.session_state.messages

The 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

python
instructions=SYSTEM_PROMPT

The system prompt controls the assistant's behavior.

Streaming

python
stream=True

The response is displayed incrementally instead of waiting for the complete answer.


Step 6: Save the response

python
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
app.py
python
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:

bash
streamlit run app.py

Try 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 →