Text size
Dark mode
RecallRunGuides & benchmarks
Learn to build real systemsGuides•Benchmarks•Experiments•Community
Search the site/Free checklist

Guides/OpenAI SDK

OpenAI SDK: Conversation state & memory

The model is stateless (part 01), so a chat must supply earlier turns on every call.

In this post

What this post covers
  1. 1. The three approaches
  2. 2. Manual history
  3. 3. previous_response_id
  4. 4. Conversations
  5. 5. Keeping long conversations affordable
  6. Recap

The model is stateless (part 01), so a chat must supply earlier turns on every call. There are three ways to do that: resend the history yourself, chain responses with previous_response_id, or use a Conversation object that OpenAI stores. All three bill the earlier turns as input tokens on every call. Long chats need a plan for trimming or summarising.

1. The three approaches

Where history livesYou send each turnGood for
Manual historyyour databasethe full list of itemsfull control, any provider, store=False privacy
previous_response_idOpenAI (stored responses)only the new input + the idsimple chains, tool loops (part 07)
Conversations APIOpenAI (a conversation object)new input + conversation idlong-lived threads you resume later

2. Manual history

from fake_openai import fake_client

client = fake_client(script=["Hi Asha! How can I help?", "Your name is Asha.", "You live in Kochi."])
history = []                                          # stored in YOUR database per chat session

def chat(user_text):
    history.append({"role": "user", "content": user_text})
    r = client.responses.create(model="gpt-5.6-luna", instructions="Be brief.",
                                input=history, store=False)
    history.append({"role": "assistant", "content": r.output_text})
    return r

for text in ["Hi, I'm Asha from Kochi.", "What's my name?", "Where do I live?"]:
    r = chat(text)
    print(f"{r.output_text:28} | items sent this turn: {len(client.fake.requests[-1]['body']['input'])}")

Output

Hi Asha! How can I help?     | items sent this turn: 1
Your name is Asha.           | items sent this turn: 3
You live in Kochi.           | items sent this turn: 5

The input grows every turn: 1, 3, then 5 items. Turn 50 resends 99 items, and every one of them is billed. That growth is the core cost problem of chat applications.

3. previous_response_id

from fake_openai import fake_client

client = fake_client(script=["Noted: Asha, Kochi.", "Your name is Asha."])
r1 = client.responses.create(model="gpt-5.6-luna", input="I'm Asha from Kochi.")
r2 = client.responses.create(model="gpt-5.6-luna", input="What's my name?",
                             previous_response_id=r1.id)          # continue from r1
print(r2.output_text)
print("second request body:", client.fake.requests[1]["body"])

Output

Your name is Asha.
second request body: {'input': "What's my name?", 'model': 'gpt-5.6-luna', 'previous_response_id': 'resp_2'}

Your request is small, but the API still processes and bills the earlier turns as input, and it requires the previous response to have been stored (store=True, the default for the Responses API). Remember that instructions are not inherited, so send them with every call.

4. Conversations

real API

conv = client.conversations.create(metadata={"user": "u_123", "channel": "web"})

client.responses.create(model="gpt-5.6-luna", conversation=conv.id, input="I'm Asha from Kochi.")
r = client.responses.create(model="gpt-5.6-luna", conversation=conv.id, input="What's my name?")
print(r.output_text)

# days later: continue the same thread, or list its items to show the history in your UI
items = client.conversations.items.list(conv.id)

5. Keeping long conversations affordable

Window keep the last N turns only simple; forgets early facts Summarise older turns → a short summary (cheap model) keeps the gist; lossy Facts / profile extract durable facts (name, preferences) into your DB; inject as needed precise; more engineering truncation="auto" API drops the oldest items if the context window would overflow a safety net, not a plan

window + running summary

from fake_openai import fake_client

client = fake_client(script=["Asha from Kochi ordered a desk lamp (ORD-7731) that arrived cracked; wants a refund."])
history = [{"role": "user", "content": f"message {i}"} for i in range(1, 21)]   # 20 old turns

KEEP = 6
old, recent = history[:-KEEP], history[-KEEP:]
summary = client.responses.create(
    model="gpt-5.6-luna",
    instructions="Summarise the conversation so far in 2 sentences, keeping names, ids and requests.",
    input=old, max_output_tokens=120,
).output_text

compact = [{"role": "developer", "content": f"Earlier in this chat: {summary}"}] + recent
print(len(history), "items →", len(compact), "items")
print(compact[0]["content"])

Output

20 items → 7 items
Earlier in this chat: Asha from Kochi ordered a desk lamp (ORD-7731) that arrived cracked; wants a refund.

Recap

  • Memory is always resent input, whichever approach you use, and it is billed each turn.
  • Manual history gives full control (and works with store=False); previous_response_id is the simplest chain; Conversations suit long-lived threads.
  • Resend instructions every call; they are not inherited.
  • Window, summarise, extract facts to keep long chats fast and cheap.

Get the free RAG production checklist

25 checks to run before real users see your RAG feature: exact-match misses, invented citations, prompt injection through documents, quality that slips after a prompt change. Confirm your email and the PDF comes with the welcome email, plus each new experiment.

One email to confirm. Unsubscribe any time. What's in it · Privacy

Want the production version of this? See ShipRAG: A production RAG + MCP starter kit for FastAPI: hybrid search, validated citations, evals in CI, Docker, 32 tests.

Written by

Alby Tomy

Python/FastAPI Engineer | Data Engineer | AI Engineer building RAG and MCP systems.

X · GitHub · Contact & enquiries