In Chapter 1, you learned the fundamentals of Generative AI and sketched out the idea of an AI Learning Assistant. In this chapter, we go one level deeper: how does your Python application actually talk to an OpenAI model? Understanding this communication layer is what turns “I know what AI can do” into “I can build with it.”
What You Will Build
AI Learning Assistant
↓
Python Application
↓
OpenAI API
↓
OpenAI Model
↓
AI Response
What you’ll learn:
- API fundamentals
- API keys
- Environment variables
- The OpenAI Python SDK
- Requests and responses
- Model selection
- Conversation input
- Error handling
- Streaming responses
- Token and cost awareness
- Building a complete command-line chatbot
2.1 What Is an API?
API stands for Application Programming Interface — a defined way for two pieces of software to communicate without needing to know each other’s internal implementation.
Real-world analogy: think of a restaurant.
You
↓
Waiter
↓
Kitchen
↓
Waiter
↓
Your Food
You never walk into the kitchen yourself — the waiter is the interface between you and the kitchen. An API works the same way:
Your Python Application
↓
API Request
↓
OpenAI Platform
↓
AI Model
↓
API Response
↓
Your Python Application
Your Python application never directly touches the AI model itself. It sends a request through the API and receives a response back — the model’s internal workings stay hidden, which is exactly the point: you get a stable, predictable interface to build against.
2.2 API Request and API Response
Every API interaction has two halves.
Request — your application sends information:
Request:
- Model
- User input
- Instructions
Response — the API sends information back:
Response:
- Generated answer
- Usage information
- Other response data
Conceptually:
REQUEST
{
"model": "...",
"input": "What is Python?"
}
↓
RESPONSE
{
"output": "Python is a programming language..."
}
2.3 What Is an API Key?
An API key is a secret credential that authenticates your application to OpenAI — functionally, a password for your app.
Your Application
+
API Key
↓
OpenAI API
Never do this:
api_key = "secret-key-here"
Why not? Because a hardcoded key can easily end up:
- Uploaded to GitHub
- Shared with someone by accident
- Included in a screenshot
- Exposed in a public application
Instead, load it from an environment variable:
Environment Variable
↓
Python Application
↓
OpenAI API
2.4 Setting Up Your Project
Create a new project:
openai-api-fundamentals/
│
├── app.py
├── .env
├── .gitignore
└── requirements.txt
Install the packages:
pip install openai python-dotenv
requirements.txt:
openai
python-dotenv
2.5 Store Your API Key Securely
Create a file called .env:
OPENAI_API_KEY=your_api_key_here
Then create .gitignore:
.env
__pycache__/
This ensures your secret file never gets accidentally committed to Git — a mistake that’s extremely common and extremely costly if a real key leaks publicly.
2.6 Create Your First API Client
Create app.py:
import os
from dotenv import load_dotenv
from openai import OpenAI
load_dotenv()
client = OpenAI(
api_key=os.getenv("OPENAI_API_KEY")
)
Let’s walk through this line by line.
Step 1
from dotenv import load_dotenv
This lets Python read variables out of your .env file.
Step 2
load_dotenv()
This loads OPENAI_API_KEY=... into your environment so Python can access it.
Step 3
os.getenv("OPENAI_API_KEY")
This retrieves the key’s value from the environment.
Step 4
client = OpenAI(...)
This creates the client object your code will use for every future request. Once this is set up, your application is ready to talk to the API.
2.7 Your First API Request
Add:
response = client.responses.create(
model="YOUR_MODEL_NAME",
input="What is Generative AI?"
)
print(response.output_text)
The flow:
Your Python Code
↓
client.responses.create()
↓
OpenAI API
↓
Model
↓
Response
↓
response.output_text
The Responses API is built specifically to accept model input and hand back generated output in a form that’s easy to use in application code.
2.8 Understanding the Request
This snippet:
response = client.responses.create(
model="YOUR_MODEL_NAME",
input="What is Generative AI?"
)
has two important pieces.
Model — model="YOUR_MODEL_NAME" tells the API which AI should handle the request.
Input — input="What is Generative AI?" is what you’re asking the AI to process.
MODEL: Which AI should process the request?
INPUT: What should the AI process?
2.9 Understanding the Response
The full response object contains more than just the visible answer text.
Response
│
├── Output
├── Usage
├── Status
└── Other metadata
For a simple text answer:
print(response.output_text)
you get:
Generative AI is a type of artificial intelligence
that can generate new content.
The rest of the response (usage stats, status, etc.) becomes important later when you need to track cost or debug failures.
2.10 Practical Demo 1: Simple Question-Answer Application
Create app.py:
import os
from dotenv import load_dotenv
from openai import OpenAI
load_dotenv()
client = OpenAI(
api_key=os.getenv("OPENAI_API_KEY")
)
question = input("Ask a question: ")
response = client.responses.create(
model="YOUR_MODEL_NAME",
input=question
)
print("\nAI Response:")
print(response.output_text)
Run it:
python app.py
Example:
Ask a question: What is an API?
Output:
AI Response:
An API is a way for software applications
to communicate with each other.
2.11 Practical Demo 2: Adding Instructions
Instead of sending only input=question, you can layer in additional instructions that shape the model’s behavior:
Role:
You are a programming teacher.
User Question:
What is an API?
In code:
response = client.responses.create(
model="YOUR_MODEL_NAME",
instructions="You are a friendly programming teacher.",
input=question
)
Now the model has explicit behavioral guidance layered on top of the raw question — this is the seed of the next chapter, Prompt Engineering.
2.12 Instructions vs User Input
A useful mental model:
Instructions
+
User Input
↓
AI Model
↓
Response
Instructions tell the model how to behave:
You are a Python teacher.
Explain concepts simply.
User Input is the actual question:
What is a list in Python?
Keeping these conceptually separate — even before we get to system/user roles formally — sets you up well for everything that follows.
2.13 Practical Demo 3: Build a Reusable Function
Rather than repeating API-calling code everywhere, wrap it in a function:
def ask_ai(question):
response = client.responses.create(
model="YOUR_MODEL_NAME",
instructions="You are a helpful programming teacher.",
input=question
)
return response.output_text
Then:
answer = ask_ai("What is Python?")
print(answer)
Why this is better:
ask_ai()
↓
API Request
↓
AI Response
You can now reuse ask_ai() anywhere in your application instead of duplicating request logic.
2.14 Practical Demo 4: Build a Continuous Chatbot
Now let’s combine everything into a real chatbot loop:
import os
from dotenv import load_dotenv
from openai import OpenAI
load_dotenv()
client = OpenAI(
api_key=os.getenv("OPENAI_API_KEY")
)
print("AI Learning Assistant")
print("Type 'exit' to quit.\n")
while True:
question = input("You: ")
if question.lower() == "exit":
print("Goodbye!")
break
response = client.responses.create(
model="YOUR_MODEL_NAME",
instructions="You are a helpful programming teacher.",
input=question
)
print("\nAI:", response.output_text)
print()
Execution flow:
Start
↓
Ask user for input
↓
Is input "exit"?
├── Yes → Stop
└── No
↓
Send API request
↓
Receive response
↓
Display answer
↓
Repeat
This is your first functional command-line AI chatbot.
2.15 What Is Streaming?
Without streaming:
User Question
↓
Wait
↓
Complete Response
↓
Display Everything
With streaming:
User Question
↓
Response starts arriving
↓
First part appears
↓
More content appears
↓
Complete response
For long responses, streaming makes the application feel far more responsive, even though the total generation time is similar.
Normal: [Wait..................][Complete answer]
Streaming: [The][answer][appears][piece][by][piece]
The OpenAI API supports streaming so applications can process and display output as it’s generated, rather than waiting for the whole thing.
2.16 Practical Demo 5: Streaming Output
Conceptually, streaming code looks like:
stream = client.responses.create(
model="YOUR_MODEL_NAME",
input="Explain artificial intelligence in detail.",
stream=True
)
for event in stream:
print(event)
In a real application, you’d inspect event types and print just the text deltas representing newly generated content. The key idea:
Response
↓
Event 1
↓
Event 2
↓
Event 3
↓
Event 4
instead of waiting for one large final blob.
2.17 Error Handling
API calls can fail for many reasons:
- Invalid API key
- No internet connection
- Rate limit
- Invalid request
- Service issue
- Incorrect model configuration
Your application should never crash unexpectedly because of this. Basic structure:
try:
response = client.responses.create(
model="YOUR_MODEL_NAME",
input=question
)
print(response.output_text)
except Exception as error:
print("Something went wrong.")
print(error)
A more user-friendly version:
try:
response = client.responses.create(
model="YOUR_MODEL_NAME",
input=question
)
print(response.output_text)
except Exception:
print("Unable to get a response right now.")
print("Please check your API configuration and try again.")
2.18 Better Error Handling
A production application should distinguish between different failure types rather than treating every error identically.
API Request
↓
Did it succeed?
├── Yes → Show response
│
└── No
↓
Identify error
├── Authentication
├── Rate limit
├── Invalid request
└── Network issue
Example structure:
try:
response = client.responses.create(
model="YOUR_MODEL_NAME",
input=question
)
except Exception as error:
print(f"API Error: {error}")
Later, in production-focused chapters, we’ll layer in logging, retries, and monitoring on top of this.
2.19 Conversation History
A chatbot becomes genuinely useful once it remembers prior turns in the conversation.
User: My name is Alex.
AI: Nice to meet you, Alex.
User: What is my name?
AI: Your name is Alex.
For this to work, the application must preserve and resend relevant context.
Conversation History
↓
Current Question
↓
API Request
↓
AI Response
A simple local representation:
conversation = [
{
"role": "user",
"content": "My name is Alex."
},
{
"role": "assistant",
"content": "Nice to meet you, Alex."
}
]
The current question then gets added to this list before the next request. (The exact API structure for passing conversation history should always follow the current SDK/API documentation, since these details evolve.)
2.20 Practical Demo 6: Simple Conversation Memory
Basic design:
conversation = []
Flow when the user asks a question:
User Question
↓
Add to conversation
↓
Send relevant conversation
↓
Receive AI answer
↓
Store AI answer
Conceptually:
conversation.append({
"role": "user",
"content": question
})
Then:
conversation.append({
"role": "assistant",
"content": answer
})
The conversation list grows as:
[
User Message 1,
AI Response 1,
User Message 2,
AI Response 2
]
This pattern becomes foundational in:
- AI assistants
- Agents
- RAG systems
- Customer support applications
2.21 Understanding API Costs
Every API request consumes resources, and most providers bill by token.
Input + Output = Total Processing
Longer input:
Large Document
↓
More Input
↓
More Processing
Longer output requests ("Write a 10,000-word detailed report") cost more than short ones ("Explain this in 10 words").
As a developer, keep in mind:
- Input length
- Output length
- Conversation history size
- Repeated requests
- Model selection
This is why production AI applications need ongoing cost monitoring and optimization — costs can grow silently as usage scales.
2.22 Model Selection
Different models vary in capability, speed, and cost. When choosing one, ask:
1. What is the task? Simple classification needs a different strategy than complex reasoning.
2. How fast should the response be? Real-time applications may need to prioritize speed.
3. How much does each request cost? At 1,000,000 requests, even small per-request cost differences add up fast.
4. Does the application need multimodal capabilities? Text + image tasks need capabilities beyond plain text generation.
Always check current OpenAI model documentation before finalizing a model choice for a production project — model lineups and pricing change over time.
2.23 Complete Chapter Project
Project: Command-Line AI Learning Assistant
Requirements — the application should:
- Load the API key securely.
- Create an OpenAI client.
- Ask the user for questions.
- Send questions to the model.
- Display responses.
- Continue until the user types
exit. - Handle API errors.
- Provide teacher-style instructions.
Architecture:
┌──────────────────────┐
│ User │
└──────────┬───────────┘
│
▼
┌──────────────────────┐
│ Python Application │
└──────────┬───────────┘
│
▼
┌──────────────────────┐
│ OpenAI API │
└──────────┬───────────┘
│
▼
┌──────────────────────┐
│ AI Model │
└──────────┬───────────┘
│
▼
┌──────────────────────┐
│ AI Response │
└──────────────────────┘
2.24 Suggested Project Structure
openai-learning-assistant/
│
├── app.py
├── .env
├── .gitignore
└── requirements.txt
Later, this can evolve into:
openai-learning-assistant/
│
├── app.py
├── config.py
├── ai_service.py
├── conversation.py
├── .env
├── .gitignore
└── requirements.txt
This is exactly how a small experiment gradually becomes a maintainable application — a pattern you’ll see repeated throughout this course.
2.25 Chapter 2 Practice Challenges
Challenge 1: Ask Questions — build a working Ask a question: prompt that returns an AI response.
Challenge 2: Add a Teacher Persona — make the AI respond as “a friendly programming teacher.”
Challenge 3: Add Exit Logic — typing exit should stop the program.
Challenge 4: Add Error Handling — a failed API request should show "Something went wrong. Please try again." instead of crashing.
Challenge 5: Add Conversation Memory — get the assistant to remember "My name is Alex." and correctly answer "What is my name?" afterward.
Chapter 2 Summary
You learned the complete API communication flow:
1. User enters a question
↓
2. Python application creates a request
↓
3. API authenticates the request
↓
4. OpenAI model processes the input
↓
5. API returns a response
↓
6. Python displays the result
You also learned:
- What an API is
- What an API key is
- How to protect API keys
- How to use the OpenAI Python SDK
- How to create a client
- How to send input
- How to read output
- How to use instructions
- How to build a chatbot loop
- What streaming means
- Why error handling matters
- How conversation history works
- Why model and cost selection matter
Next chapter: Chapter 3 — Prompt Engineering, where we’ll learn how to write effective instructions, design system behavior, use few-shot examples, request structured outputs, and build a powerful prompt-based AI application.
