OpenAI Conversation Memory in Python

In the previous lesson, we learned how to structure messages and provide instructions to the OpenAI Responses API. But consider a user who says, “My favorite language is Python,” and then asks, “What is my favorite language?” If the second API request does not include the first exchange in its context, the model has no reliable way to know the answer. Building a useful conversation means deliberately preserving the information that later requests need. OpenAI conversation memory helps Python applications maintain context across multiple API requests. Without access to earlier messages, a language model cannot reliably answer questions that depend on previous interactions.

This tutorial explains three beginner-friendly ways to maintain conversation context with the OpenAI Python SDK: manually sending history, linking responses with previous_response_id, and using a persistent conversation object.

1. What does conversation memory mean?

For an API application, conversation memory means supplying relevant earlier information when generating the next answer. It does not mean the model permanently remembers every message from every API call. An isolated request only has the context you send or reference through a supported state mechanism.

A conversation might have these turns:

  1. User: “My favorite programming language is Python.”
  2. Assistant: “Python is a popular language for beginners.”
  3. User: “What language did I mention?”

The third turn needs information from the first. The application must preserve that relationship. The three approaches in this lesson differ in where the history is managed, not in whether context matters.

2. Independent requests versus conversation state

Two independent calls to responses.create() do not automatically form a conversation simply because they use the same Python client.

Bash
first = client.responses.create(
    model="gpt-4.1-mini",
    input="My favorite programming language is Python.",
)

second = client.responses.create(
    model="gpt-4.1-mini",
    input="What is my favorite programming language?",
)

In this example, the second request does not reference the first response or repeat its information. The model might guess, but it has not been given the necessary context. Reusing client keeps the API connection configuration available. It does not make the client a memory store.

3. How Does OpenAI Conversation Memory Work?

flowchart LR
    subgraph APP["Python Application"]
        U["New user turn"] --> M{"Context strategy"}
        H["Local message history"] --> M
        R["Previous response ID"] --> M
        V["Conversation ID"] --> M
        M --> SDK["OpenAI SDK client"]
    end
    SDK -->|"Request + context reference"| API["OpenAI API"]
    API -->|"Response"| SDK

The Python SDK runs inside your application. With manual history, your application sends earlier messages again. With response chaining, it supplies an earlier response ID so the API can use the linked context. With the Conversations API, it refers to a persistent conversation object whose items can be used across responses. The approaches have different storage and control trade-offs.

4. Set up the example project

You need a Python virtual environment, an OpenAI API key, and access to a supported text model. If you completed Blogs 02–05, you can reuse that setup.

Install the packages:

Bash
pip install --upgrade openai python-dotenv
pip show openai

In the project root, create .env:

Bash
OPENAI_API_KEY=your_api_key_here

5. Approach one: manage message history yourself

The most explicit technique is to keep a list of messages in Python. For each new turn, include the relevant earlier user and assistant messages in the next request.

Bash
from dotenv import load_dotenv
from openai import OpenAI

load_dotenv()
client = OpenAI()

history = [
    {
        "role": "user",
        "content": "My favorite programming language is Python.",
    }
]

first = client.responses.create(
    model="gpt-4.1-mini",
    input=history,
)

history.append({
    "role": "assistant",
    "content": first.output_text,
})
history.append({
    "role": "user",
    "content": "What is my favorite programming language?",
})

second = client.responses.create(
    model="gpt-4.1-mini",
    input=history,
)

print(second.output_text)

The important step is adding the assistant’s answer and the next user message to history. When the second request is sent, the earlier statement about Python is included in its input. This basic text-only example is intentionally simple. More advanced responses can contain reasoning or tool items, so copying only output_text is not a complete history strategy for every model or workflow.

Advantages: You can inspect, edit, summarize, or remove messages before sending them. You also control how history is saved locally.

Trade-offs: Your application must maintain the list correctly. Longer histories increase input size and may increase token usage and cost. An in-memory list disappears when the program ends unless you save it separately.

6. Approach two: use previous_response_id

The Responses API can link a new request to an earlier response by passing its ID in previous_response_id. This removes the need to manually repeat the previous messages in the simple example.

Bash
from dotenv import load_dotenv
from openai import OpenAI

load_dotenv()
client = OpenAI()

first = client.responses.create(
    model="gpt-4.1-mini",
    input="My favorite programming language is Python.",
)

second = client.responses.create(
    model="gpt-4.1-mini",
    previous_response_id=first.id,
    input="What is my favorite programming language?",
)

print(second.output_text)

Here, first.id identifies the response to continue from. The API can use the earlier response context when producing the second answer. The response must be available under the API’s storage and retention behavior. The examples assume default response storage and do not set store=False.

There are two details worth remembering. First, linking responses does not make all earlier tokens free. Prior context can still contribute to billed input tokens.

Second, instructions from a previous response are not automatically carried into a later call using previous_response_id. Supply the instructions you still need on the new request.

For example:

Bash
second = client.responses.create(
    model="gpt-4.1-mini",
    previous_response_id=first.id,
    instructions="Answer clearly in one short sentence.",
    input="What is my favorite programming language?",
)

Store the latest response ID in your application if you want to continue the same chain in later turns. Do not confuse a response ID with a conversation ID.

7. Approach three: use the Conversations API

The Conversations API provides a durable conversation resource that can hold items across requests. It is useful when your application wants to refer to a conversation by its own ID rather than explicitly managing the complete history or a chain of response IDs.

Bash
from dotenv import load_dotenv
from openai import OpenAI

load_dotenv()
client = OpenAI()

conversation = client.conversations.create()

first = client.responses.create(
    model="gpt-4.1-mini",
    conversation=conversation.id,
    input="My favorite programming language is Python.",
)

second = client.responses.create(
    model="gpt-4.1-mini",
    conversation=conversation.id,
    input="What is my favorite programming language?",
)

print(second.output_text)

The same conversation.id is supplied in both requests. The conversation resource keeps the relevant items associated with that conversation.

Your application must still store the conversation ID if it wants to find the same conversation later. Avoid mixing conversation and previous_response_id in a single Responses API request because these parameters cannot be used together.

A durable conversation resource does not mean unlimited context. Models still have context-window limits, and applications must plan for long histories, privacy, retention, and costs.

8. Compare the approaches

ApproachWho manages the history?Best for
Manual message historyYour Python applicationLearning, explicit control, custom trimming
previous_response_idAPI-linked response chain, referenced by your applicationSimple follow-up requests
Conversations APIPersistent conversation resource, referenced by your applicationReusing a conversation across requests or sessions

9. Complete two-turn example with response chaining

This example keeps the setup minimal and shows how the response ID changes as the conversation progresses.

Bash
from dotenv import load_dotenv
from openai import OpenAI

load_dotenv()
client = OpenAI()

first_response = client.responses.create(
    model="gpt-4.1-mini",
    instructions="Answer in simple, beginner-friendly language.",
    input="My favorite programming language is Python.",
)

print("First response:")
print(first_response.output_text)

second_response = client.responses.create(
    model="gpt-4.1-mini",
    previous_response_id=first_response.id,
    instructions="Answer in simple, beginner-friendly language.",
    input="Which programming language did I mention?",
)

print("Second response:")
print(second_response.output_text)

Save the file as main.py and run python main.py in the active virtual environment. Both requests use the same model, and the second explicitly references the first. We repeat the instruction because a previous request’s instructions are not implicitly carried forward. The actual generated wording and response IDs vary by execution.

10. Common mistakes and troubleshooting

ProblemWhat to check
Follow-up answer ignores earlier informationVerify that you sent history, a valid previous response ID, or a conversation ID
Reusing the same Python client does not preserve contextThe client is not a conversation store
previous_response_id failsCheck the ID, access, and whether the referenced response is available
Assistant changes style unexpectedlyRepeat required instructions on subsequent chained requests
Token usage increases over timePrevious context can still count toward billed input tokens
History becomes too longPlan trimming or summarization without removing essential facts
A conversation appears lost after restarting PythonSave the appropriate history or conversation identifier securely
API rejects context parametersDo not pass conversation and previous_response_id together

Do not store private conversation content unnecessarily. If you persist histories or conversation IDs, apply appropriate access controls and retention practices.

11. Does Conversation Memory Work with Local LLMs?

Yes. Conversation memory works with local language models, including Qwen3-Coder, because the underlying idea is the same: the model needs the relevant earlier messages in its current input context. Memory management is primarily an application or model-serving feature, not a special capability reserved for frontier models.

The important difference is where conversation state lives. With OpenAI’s hosted API, previous_response_id and the Conversations API can refer to state managed by OpenAI’s backend. The model does not independently fetch a database record. The API service resolves the reference and prepares the context for inference.

For a local model, support depends on the serving software. An Ollama, LM Studio, vLLM, or custom server may expose different APIs and different state-management capabilities. Do not assume an OpenAI-compatible endpoint automatically implements OpenAI’s stateful Responses API parameters. Manual history is the most portable technique because your application sends the earlier messages explicitly.

Example: manual history with a local Qwen3-Coder server

The following example assumes a locally running Ollama server that exposes an OpenAI-compatible Chat Completions endpoint. Replace the model name with the exact identifier installed on your computer. The example uses a placeholder local API key because the OpenAI Python SDK expects a value.

Bash
from openai import OpenAI

client = OpenAI(
    base_url="<http://localhost:11434/v1>",
    api_key="ollama",
)

history = [
    {
        "role": "user",
        "content": "My favorite programming language is Python.",
    }
]

first = client.chat.completions.create(
    model="YOUR_LOCAL_QWEN_MODEL_NAME",
    messages=history,
)

history.append({
    "role": "assistant",
    "content": first.choices[0].message.content,
})
history.append({
    "role": "user",
    "content": "What is my favorite programming language?",
})

second = client.chat.completions.create(
    model="YOUR_LOCAL_QWEN_MODEL_NAME",
    messages=history,
)

print(second.choices[0].message.content)

Notice that this local example uses client.chat.completions.create() rather than the hosted Responses API. This is a compatibility example, not a recommendation to change the main course from the Responses API. No request is sent to OpenAI when the client is configured to use the local server address shown above.

QuestionOpenAI hosted APILocal model server
Can the model use earlier messages?Yes, when context is suppliedYes, when context is supplied
Can the application resend history manually?YesYes
Can a response ID refer to stored context?Supported through the Responses APIOnly if the chosen server implements it
Can a conversation ID refer to persistent state?Supported through the Conversations APIOnly if the chosen server implements it
Does the model permanently learn the conversation?NoNo

Key takeaway: The same model-context principle applies to hosted and local LLMs. What changes is whether your application must resend history or can ask the serving backend to retrieve it. In either case, the model processes context available for the current generation, subject to context-window limits. Local-server API support should be verified against the version and serving stack you actually use.

12. Summary and next lesson

Conversation memory is about providing relevant context for each new response. You can manage messages yourself, link responses using previous_response_id, or associate requests with a persistent conversation. Each technique helps the model respond to follow-up questions, but none should be confused with unlimited or automatic personal memory.

In Blog 07 — Streaming OpenAI Responses in Python, we’ll learn how to display generated text incrementally instead of waiting for a complete response.

Leave a Reply

Your email address will not be published. Required fields are marked *