Backend Step 5: OpenAI Integration
Replace the hardcoded bot reply with a real AI response using OpenAI.
What You'll Learn
- Loading environment variables with
python-dotenv - Calling the OpenAI Chat Completions API
- Handling API errors
- Streaming responses (optional)
Step 1: Get an OpenAI API Key
- Go to https://platform.openai.com/api-keys
- Sign up or log in
- Click "Create new secret key"
- Copy the key (starts with
sk-...)
Step 2: Set Up the .env File
cd aiproject/backend
cp app/.env.example app/.env
Edit app/.env and replace with your actual key:
OPENAI_API_KEY=sk-proj-your-actual-key-here
If using Docker: The docker-compose.yml automatically loads this .env file into the container. No extra setup needed.
Step 3: Add .env to .gitignore
echo ".env" >> .gitignore
This ensures you never accidentally commit your secret key.
Step 4: Load Environment Variables
Update backend/app/__init__.py:
from flask import Flask
from flask_sqlalchemy import SQLAlchemy
from dotenv import load_dotenv
load_dotenv()
db = SQLAlchemy()
def create_app():
app = Flask(__name__)
app.config["SQLALCHEMY_DATABASE_URI"] = "sqlite:///chat.db"
app.config["SQLALCHEMY_TRACK_MODIFICATIONS"] = False
db.init_app(app)
from .routes import api
app.register_blueprint(api, url_prefix="/api")
with app.app_context():
db.create_all()
return app
if __name__ == "__main__":
app = create_app()
app.run(debug=True, port=5000)
The load_dotenv() call reads the .env file and makes its contents available via os.getenv().
Step 5: Update routes.py with OpenAI
from flask import Blueprint, request, jsonify
from . import db
from .models import Message
from openai import OpenAI
import os
api = Blueprint("api", __name__)
client = OpenAI(api_key=os.getenv("OPENAI_API_KEY"))
@api.route("/status")
def status():
return jsonify({"status": "ok"})
@api.route("/messages", methods=["GET"])
def get_messages():
messages = Message.query.order_by(Message.timestamp.asc()).all()
return jsonify([msg.to_dict() for msg in messages])
@api.route("/chat", methods=["POST"])
def chat():
data = request.get_json()
user_message = data.get("message", "")
if not user_message:
return jsonify({"error": "Message is required"}), 400
user_msg = Message(content=user_message, sender="user")
db.session.add(user_msg)
db.session.commit()
try:
response = client.chat.completions.create(
model="gpt-4o-mini",
messages=[
{
"role": "system",
"content": "You are a helpful assistant.",
},
{"role": "user", "content": user_message},
],
)
ai_reply = response.choices[0].message.content
ai_msg = Message(content=ai_reply, sender="ai")
db.session.add(ai_msg)
db.session.commit()
return jsonify({
"user_message": user_msg.to_dict(),
"ai_message": ai_msg.to_dict(),
})
except Exception as e:
return jsonify({"error": str(e)}), 500
Step 6: Test It
cd aiproject/backend
source venv/bin/activate
python app/__init__.py
In another terminal:
curl -X POST http://127.0.0.1:5000/api/chat \
-H "Content-Type: application/json" \
-d '{"message": "What is the capital of France?"}'
Expected response (actual AI reply):
{
"user_message": {"id": 1, "content": "What is the capital of France?", "sender": "user", ...},
"ai_message": {"id": 2, "content": "The capital of France is Paris.", "sender": "ai", ...}
}
How the OpenAI Call Works
response = client.chat.completions.create(
model="gpt-4o-mini", # Which AI model to use
messages=[ # The conversation history
{"role": "system", "content": "You are a helpful assistant."},
{"role": "user", "content": user_message},
],
)
model: Which AI model to use.gpt-4o-miniis fast and cheap.messages: The conversation context. Thesystemmessage sets the AI's personality. Theusermessage is the question.response.choices[0].message.content: Extracts the AI's reply text.
Error Handling
The try/except block catches any API errors:
- Invalid API key → authentication error
- Rate limit → too many requests
- Network error → can't reach OpenAI
If an error occurs, the API returns {"error": "..."} with status 500 instead of crashing.
Step 7: Mocking OpenAI in Tests
Your tests currently call the real OpenAI API. That wastes money and is slow. Instead, mock the API — replace it with a fake that returns a canned response.
This is a critical skill: never let tests depend on external services.
Update your test to mock OpenAI:
from unittest.mock import patch
@patch("app.routes.client.chat.completions.create")
def test_chat_with_mock_openai(mock_openai, client):
mock_openai.return_value.choices = [
MockChoice(MockMessage("Mock AI reply"))
]
response = client.post("/api/chat", json={"message": "Hello"})
assert response.status_code == 200
data = response.get_json()
assert data["ai_message"]["content"] == "Mock AI reply"
Add the helper classes at the top of your test file:
class MockMessage:
def __init__(self, content):
self.content = content
class MockChoice:
def __init__(self, message):
self.message = message
Now your tests run instantly and free — no API key needed, no network calls.
Verification
- Send a question and get a real AI response
- Send an empty message and get a 400 error
- Check
instance/chat.dbto confirm messages are saved - Run
pytest tests/ -v— tests should pass without a real API key