Module 2: OpenAI API - Introduction
Conversations with Context
Capsule overview
So far, each request is independent: GPT doesn't remember previous messages. To create a real chatbot, you need conversational context.
In this capsule:
- You'll implement message history
- GPT will remember the whole conversation
- You'll create an interactive chatbot (CLI)
Time: 30 minutes
Difficulty: Medium
🎯 Objectives
- ✅ Understand the message format (system, user, assistant)
- ✅ Maintain conversation history
- ✅ Create a chatbot with memory
- ✅ Manage the context window
📚 Concepts: Message Roles
Format of the messages array:
messages = [
{"role": "system", "content": "..."}, # Initial instructions
{"role": "user", "content": "..."}, # User
{"role": "assistant", "content": "..."}, # GPT
{"role": "user", "content": "..."}, # User replies
{"role": "assistant", "content": "..."}, # GPT replies
]
Role 1: system (Instructions)
Defines GPT's behavior and personality:
{"role": "system", "content": "You are a technical assistant specialized in Python"}
Features:
- Optional (but highly recommended)
- Goes at the start of the array
- GPT will always follow these instructions
Examples:
# Technical support
{"role": "system", "content": "You are a support agent. Answer concisely and in a friendly way."}
# Educational tutor
{"role": "system", "content": "You are a programming tutor. Explain concepts step by step."}
# Formal assistant
{"role": "system", "content": "You are a corporate assistant. Use formal language."}
Role 2: user (User)
Messages from the human user:
{"role": "user", "content": "What is a list in Python?"}
Role 3: assistant (GPT)
Responses generated by GPT:
{"role": "assistant", "content": "A list in Python is an ordered collection..."}
Important: You must include GPT's previous responses in the history.
💻 Implementation: Chatbot with Memory
Complete code (chatbot_context.py):
from dotenv import load_dotenv
import os
from openai import OpenAI
load_dotenv()
client = OpenAI(api_key=os.getenv("OPENAI_API_KEY"))
# Conversation history
messages = [
{"role": "system", "content": "You are a helpful and friendly assistant."}
]
def chat(user_message: str) -> str:
"""Send a message and return the response, keeping context."""
# 1. Add the user's message to the history
messages.append({"role": "user", "content": user_message})
# 2. Send the ENTIRE history to GPT
response = client.chat.completions.create(
model="gpt-3.5-turbo",
messages=messages # Includes system + all previous messages
)
# 3. Extract the response
assistant_message = response.choices[0].message.content
# 4. Add GPT's response to the history
messages.append({"role": "assistant", "content": assistant_message})
return assistant_message
# Interactive CLI
print("Chatbot with context (type 'exit' to finish)\n")
while True:
user_input = input("You: ")
if user_input.lower() in ["exit", "quit"]:
print("Goodbye!")
break
response = chat(user_input)
print(f"Bot: {response}\n")
Execution:
python chatbot_context.py
Example conversation:
Chatbot with context (type 'exit' to finish)
You: Hi, my name is Juan
Bot: Hi Juan! How can I help you today?
You: What's my name?
Bot: Your name is Juan.
You: What is Python?
Bot: Python is a programming language...
You: Give me an example
Bot: Sure, here's an example in Python:
print("Hello world")
You: exit
Goodbye!
Notice: GPT remembers your name and the context of the previous questions.
🔍 Breakdown: How It Works
Initial state:
messages = [
{"role": "system", "content": "You are a helpful and friendly assistant."}
]
First interaction:
User: "Hi, my name is Juan"
After messages.append():
messages = [
{"role": "system", "content": "You are a helpful and friendly assistant."},
{"role": "user", "content": "Hi, my name is Juan"}
]
GPT receives: System + User message → Responds
Response: "Hi Juan! How can I help you today?"
After adding the response:
messages = [
{"role": "system", "content": "You are a helpful and friendly assistant."},
{"role": "user", "content": "Hi, my name is Juan"},
{"role": "assistant", "content": "Hi Juan! How can I help you today?"}
]
Second interaction:
User: "What's my name?"
After append:
messages = [
{"role": "system", "content": "You are a helpful and friendly assistant."},
{"role": "user", "content": "Hi, my name is Juan"},
{"role": "assistant", "content": "Hi Juan! How can I help you today?"},
{"role": "user", "content": "What's my name?"}
]
GPT sees the ENTIRE history: It knows your name is Juan → Responds correctly
📊 History Visualization
Add debugging to see the history:
def chat(user_message: str) -> str:
messages.append({"role": "user", "content": user_message})
# DEBUG: Show the history
print("\n--- HISTORY SENT TO GPT ---")
for i, msg in enumerate(messages):
print(f"{i+1}. [{msg['role']}]: {msg['content'][:50]}...")
print("--- END OF HISTORY ---\n")
response = client.chat.completions.create(
model="gpt-3.5-turbo",
messages=messages
)
assistant_message = response.choices[0].message.content
messages.append({"role": "assistant", "content": assistant_message})
return assistant_message
Output:
--- HISTORY SENT TO GPT ---
1. [system]: You are a helpful and friendly assistant.
2. [user]: Hi, my name is Juan
3. [assistant]: Hi Juan! How can I help you today?
4. [user]: What's my name?
--- END OF HISTORY ---
⚙️ Parameters: Advanced System Message
Specific personality:
{"role": "system", "content": """
You are a technical support assistant for an e-commerce app.
Rules:
1. Answer in English
2. Maximum 3 sentences per response
3. If you don't know something, say "Let me escalate this to a human agent"
4. Never make up data (order numbers, prices, etc.)
"""}
Result: GPT will follow these rules consistently.
Context injection (advanced):
{"role": "system", "content": f"""
You are an assistant for {user_name}.
User information:
- Name: {user_name}
- Plan: Premium
- Last purchase: {last_purchase_date}
Use this info when relevant.
"""}
Useful for: Personalization with data from a database.
🧪 Experiments
Experiment 1: Without a system message
Comment out the line:
messages = [
# {"role": "system", "content": "You are a helpful and friendly assistant."}
]
Notice: GPT keeps working, but the behavior is less predictable.
Experiment 2: A specific system message
messages = [
{"role": "system", "content": "You are a pirate. Always talk like a pirate."}
]
Output:
You: Hi
Bot: Ahoy, sailor! What can I do for ye today?
Experiment 3: Token count with history
def chat(user_message: str) -> str:
messages.append({"role": "user", "content": user_message})
response = client.chat.completions.create(
model="gpt-3.5-turbo",
messages=messages
)
assistant_message = response.choices[0].message.content
messages.append({"role": "assistant", "content": assistant_message})
# Show tokens
print(f"[Tokens used: {response.usage.total_tokens}]")
return assistant_message
Notice: Tokens increase with each message (the history grows).
⚠️ Problem: Context Window Overflow
Context limits:
- GPT-3.5-turbo: 16,385 tokens max
- GPT-4-turbo: 128,000 tokens max
Problem: If the conversation is very long, the history exceeds the limit → Error
Solution 1: Sliding window (simple)
Only keep the last N messages:
MAX_HISTORY = 10 # Last 10 messages (5 exchanges)
def chat(user_message: str) -> str:
messages.append({"role": "user", "content": user_message})
# Keep only the last MAX_HISTORY (+ the system message always)
if len(messages) > MAX_HISTORY + 1: # +1 for the system
# Keep system + the last MAX_HISTORY
messages[:] = [messages[0]] + messages[-(MAX_HISTORY):]
response = client.chat.completions.create(
model="gpt-3.5-turbo",
messages=messages
)
assistant_message = response.choices[0].message.content
messages.append({"role": "assistant", "content": assistant_message})
return assistant_message
Advantage: You never exceed the context limit
Disadvantage: GPT forgets old messages
Solution 2: Summarization (advanced)
Every 20 messages, summarize the history:
def summarize_history():
"""Summarize the old history and replace it with a summary."""
if len(messages) > 20:
# Create a summary prompt
summary_prompt = "Summarize this conversation in 3 sentences:\n\n"
for msg in messages[1:-5]: # Excludes system and the last 5
summary_prompt += f"{msg['role']}: {msg['content']}\n"
# Ask GPT for a summary
summary_response = client.chat.completions.create(
model="gpt-3.5-turbo",
messages=[{"role": "user", "content": summary_prompt}]
)
summary = summary_response.choices[0].message.content
# Replace the history: system + summary + the last 5
messages[:] = [
messages[0], # system
{"role": "assistant", "content": f"[Previous summary: {summary}]"},
*messages[-5:] # The last 5 messages
]
Advantage: Keeps important context
Disadvantage: Extra cost (the summary request)
📊 Summary
Key concepts:
-
Three message roles:
system: Instructions/behavioruser: User messagesassistant: GPT responses
-
Conversation history:
messages = [] messages.append({"role": "user", "content": "..."}) response = client.chat.completions.create(messages=messages) messages.append({"role": "assistant", "content": response...}) -
Context window management:
- Sliding window (last N messages)
- Summarization (summarize the old history)
Checklist:
- Chatbot with context working
- GPT remembers previous messages
- Custom system message
- Sliding window implemented
- Experimented with different personalities
🔗 Additional resources
- Chat Completions Guide - Official
- Best Practices for Prompting - System messages
- Token Limits - Per model
➡️ Next step
Next capsule: 05-advanced-parameters.md
You'll learn to control GPT's responses with:
temperature(creativity vs determinism)max_tokens(length)top_p,frequency_penalty, etc.
Time: 25 minutes
Estimated time: 30 minutes
Next: 05-advanced-parameters.md