~/TechPurAI
~/tutorials/ai-fundamentals/building-your-first-ai-application-adding-memory-and-context
intermediate·part 18 of 22·4 min read

How to Build Your First AI Application with Python (Part 3: Memory and Context)

Updated Aug 16, 2026AI

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

python
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 answer

This 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

python
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 structure

This 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

text
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 9

A 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

python
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 answer

This 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.

Why it matters

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

text
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 management

This 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.

VK

Vijay Kumar

Founder of TechPurAI — writing hands-on tutorials and honest tool breakdowns.

LinkedIn ↗
← previous17. How to Build Your First AI Application with Python (Part 2: Error Handling and Streaming)next →19. What Is RAG (Retrieval-Augmented Generation)? A Practical Explanation