How to Build Your First AI Application with Python (Part 3: Memory and Context)
Parts 16 and 17 built a real, working, error-handled, streaming request — but it only ever handles one isolated question at a time. This part adds real, multi-turn conversation memory, directly implementing part 9's context-window management as actual, working code.
Real, explicit memory: an actual message history list
class SupportConversation:
def __init__(self, plan_data: str):
self.plan_data = plan_data
self.history: list[dict] = []
def ask(self, user_question: str) -> str:
self.history.append({"role": "user", "content": user_question})
response = client.messages.create(
model="claude-sonnet-5",
max_tokens=1024,
system=(
f"You are a customer support assistant for Bright Leaf "
f"Coffee. Real, current plan data:\n{self.plan_data}\n\n"
f"Only answer using this real data. If you don't have "
f"the information needed, say so directly."
),
messages=self.history,
)
answer = response.content[0].text
self.history.append({"role": "assistant", "content": answer})
return answerThis is exactly part 18's earlier promise, delivered — this is directly part 9's honest point about model "memory" made concrete: the model itself remembers nothing between calls; self.history is the real, actual mechanism creating the appearance of memory, by resending the full real conversation with every new request, exactly as part 9 described.
Watching it work across a real, multi-turn conversation
convo = SupportConversation(plan_data="""
- Gift Subscription: $18/mo, 1 bag, free shipping
- Monthly Subscription: $16/mo, 1 bag, free shipping
""")
print(convo.ask("What subscriptions do you offer?"))
# → "We offer a Gift Subscription ($18/mo) and a Monthly
# Subscription ($16/mo), both with free shipping."
print(convo.ask("Can I switch between them?"))
# → the model correctly understands "them" refers to the two
# plans just mentioned, because the FULL prior exchange is
# included in self.history, exactly per part 11's real
# assistant-role message structureThis directly demonstrates part 11's multi-turn example working in real, actual code — the second question's correct handling of "them" isn't the model genuinely recalling anything; it's the direct, real result of self.history including the full prior exchange in the request.
The real problem this introduces: an unbounded, growing context
Every new real message appends to self.history — and every
subsequent request resends the ENTIRE growing history, exactly
the real problem described conceptually in part 9A long, real support conversation will eventually push self.history's total token count toward the model's context window limit — the exact, concrete version of part 9's abstract warning.
Implementing part 9's sliding-window strategy, in real code
class SupportConversation:
def __init__(self, plan_data: str, max_history_messages: int = 10):
self.plan_data = plan_data
self.history: list[dict] = []
self.max_history_messages = max_history_messages
def ask(self, user_question: str) -> str:
self.history.append({"role": "user", "content": user_question})
# keep only the most recent N messages — part 9's sliding
# window strategy, applied directly
trimmed_history = self.history[-self.max_history_messages:]
response = client.messages.create(
model="claude-sonnet-5",
max_tokens=1024,
system=f"...plan_data included as before...",
messages=trimmed_history,
)
answer = response.content[0].text
self.history.append({"role": "assistant", "content": answer})
return answerThis is exactly part 9's sliding-window technique, the simplest of the three strategies covered there, chosen deliberately for this real project over summarization's added complexity — trimmed_history sends only the most recent real exchanges to the model, while self.history itself keeps the full, real, untrimmed record for logging or display purposes.
This is a real, deliberate, honest trade-off, not a flaw — trimming means a customer referencing something from very early in a long, real conversation might get a less accurate response, since that early context has genuinely fallen out of what's sent to the model. For a real support assistant, this is usually an acceptable trade-off; a genuinely different real application (a long-form document analysis tool) might need part 9's summarization strategy instead, precisely because losing early context there would be more costly.
The real, complete project so far
Part 16: a real, working request, grounded in real plan data
Part 17: real error handling and streaming
Part 18 (this part): real, multi-turn memory with context managementThis is genuinely a complete, real, small production application at this point — not a toy example. Part 22's capstone extends it once more, but everything covered from part 4 through part 18 is now directly reflected in real, working code.
Next: what RAG (Retrieval-Augmented Generation) actually is — a real, more scalable alternative to hardcoding plan data directly, for when a real knowledge base grows too large to fit in every prompt.