You can have a conversation context with a chain of previous response IDs when using “store”:true on the Responses API, even if the output is a JSON object specified by a text.format for structured output, even strict.
Some caveats:
-
If using the “playground”, it does not use response ID for chat persistence. Only the seen messages are run like a self-managed conversation, (along with unpresented encrypted reasoning that is sent back in also).
-
It is unclear how you are asking the AI about PDFs. It can either be a vector store, requiring search and returning partial chunks, or part of the user message content and fully-extracted and placed.
-
and here’s the big one: A single response ID does NOT contain all of the previous turns of a chat! This is contrary to expectations and documentation, and even retrieving the “input” from the API for a past Responses ID and call. It is dependent on all IDs remaining on the server and not being deleted.
Here’s a conversation using structured output, where there are several fields for the AI to complete in JSON, being then extracted and displayed.
Prompt (or ‘exit’ to quit): What is a babboon’s closest primate cousin or ancestor?
#----------------------------------------
:topic: Baboon’s Closest Relatives
:chat_title: Baboon Primate Relations
:output_intro: The question concerns the evolutionary relatives of baboons.
:complete_answer: Baboons, belonging to the genus Papio, share close evolutionary ties with other Old World monkeys, particularly those within the subfamily Cercopithecinae, such as macaques (Macaca) and mangabeys (Cercocebus and related genera). Their most recent common ancestors precede the divergence of these groups in the evolutionary timeline of primates. Comparative genetic studies and fossil records assist scientists in elucidating these relationships more thoroughly.
Prompt (or ‘exit’ to quit): Where does that genus name come from?
#----------------------------------------
:topic: Origin of the Genus Name Papio
:chat_title: Genus Name “Papio”
:output_intro: You seek the etymology of the name “Papio.”
:complete_answer: The genus name Papio originates from the Latin term “papio,” which translates to baboon, reflecting the common application of this designation in taxonomy. This Latin root is likely derived from earlier etymological sources in classical languages referring to these primates. Linnaean scientific nomenclature often incorporates such historical or descriptive origins when categorizing species.
Prompt (or ‘exit’ to quit): Can domestic dogs, dingos, wolves, coyotes all interbreed? What is the closest relative that cannot? Foxes?
#----------------------------------------
:topic: Canid Interbreeding
:chat_title: Canids and Interbreeding
:output_intro: Exploring interbreeding capabilities and relations among canids.
:complete_answer: Domestic dogs (Canis lupus familiaris), dingoes (Canis lupus dingo), wolves (Canis lupus), and coyotes (Canis latrans) belong to the genus Canis and are capable of interbreeding, producing fertile hybrids due to their close genetic relationships. Foxes, belonging to the genus Vulpes, are more distantly related and cannot interbreed with members of the genus Canis. The evolutionary divergence between Canis species and foxes reflects significant genetic and behavioral differences precluding hybridization.
Prompt (or ‘exit’ to quit): Look back at my very first chat question. Can you re-write that without using scientific names, please?
#----------------------------------------
:topic: Rewriting a Question on Interbreeding
:chat_title: Animal Interbreeding Query Rewritten
:output_intro: Rewriting the initial query to exclude scientific names.
:complete_answer: Certainly! Here is your originally asked question rephrased: “Can domestic dogs, dingos, wolves, and coyotes interbreed? Also, what is the closest relative within the animal kingdom that cannot successfully interbreed with them? Are foxes among them?”
Prompt (or ‘exit’ to quit): The question even before that one!
#----------------------------------------
:topic: Primate Relations Inquiry
:chat_title: Primate Cousins Question
:output_intro: Rephrasing the initial inquiry without scientific names.
:complete_answer: Certainly! Here’s a restatement of your first question: “Which primates are the closest relatives or ancestors to baboons?”
Prompt (or ‘exit’ to quit): I meant: answer it again, but without the scientific names.
#----------------------------------------
:topic: Baboon Closest Relatives
:chat_title: Baboon Relation Exploration
:output_intro: Providing an answer without scientific terminologies.
:complete_answer: Baboons are closely related to other types of monkeys that are found in Africa and Asia, such as macaques and mangabeys. Their shared traits and origins trace back to a common ancestor from which they diverged in evolution. This information helps us understand the relationships among these species.
You can see that exploration of the chat history above was aggravated by AI model stupidity (gpt-4o in this case), where I say “Look back at my very first chat question”, and it answers the most recent instead. With persistence I can see it has knowledge of the first still, though.
Unexpected
Let’s say that I would never, ever, use a previous response ID in any application I would ever write, nor the SDK and its methods, but do so just for you. And I hate server junk left behind from testing.
So I immediately delete a previous response from the server after it has been utilized by “previous_response_id”.
Then, the result. I now have an AI that cannot remember more than one turn, given the same inputs:
Prompt (or ‘exit’ to quit): What is a babboon’s closest primate cousin or ancestor?
#----------------------------------------
:topic: Primate Evolution
:chat_title: Baboon’s Cousins
:output_intro: Here’s information on baboon’s close relatives.
:complete_answer: Baboons belong to the genus Papio, within the family Cercopithecidae, which is part of the Old World monkeys. Their closest relatives are other genera within this family, such as macaques (Macaca), with a shared evolutionary lineage that split into distinct species millions of years ago. Fossil records and genetic studies indicate that baboons and their relatives diverged from a common ancestor approximately 5–8 million years ago, contributing to our understanding of primate evolution.
Prompt (or ‘exit’ to quit): Where does that genus name come from?
#----------------------------------------
:topic: Etymology of Genus Papio
:chat_title: Baboon’s Genus Origin
:output_intro: The genus name Papio has an interesting origin.
:complete_answer: The genus name Papio, assigned to baboons, has its roots in Latin. It is derived from the word “papio,” which was historically used in classical texts to refer to a baboon. This term itself likely originates from an even older linguistic source, emphasizing the longstanding recognition of these primates in human history and nomenclature.
Prompt (or ‘exit’ to quit): Can domestic dogs, dingos, wolves, coyotes all interbreed? What is the closest relative that cannot? Foxes?
#----------------------------------------
:topic: Interbreeding in Canines
:chat_title: Canine Interbreeding Relatives
:output_intro: Exploring interbreeding capabilities within canids.
:complete_answer: Yes, domestic dogs (Canis lupus familiaris), dingoes (Canis lupus dingo), wolves (Canis lupus), and coyotes (Canis latrans) can interbreed and produce viable offspring, as these species share close genetic affiliations within the genus Canis. The ability to interbreed is a testament to their shared evolutionary lineage as members of the same extended species complex. However, foxes, which belong to different genera such as Vulpes for red foxes (Vulpes vulpes), are more distantly related to the Canis genus, rendering interbreeding biologically impossible. This genetic distance makes genus Vulpes a likely point where interbreeding is no longer feasible, despite belonging to the broader family Canidae. This distinction helps outline the phylogenetic relationships within the family.
Prompt (or ‘exit’ to quit): Look back at my very first chat question. Can you re-write that answer without using scientific names, please?
#----------------------------------------
:topic: Interbreeding in Canines
:chat_title: Canine Interbreeding Relatives
:output_intro: Exploring interbreeding capabilities within canids.
:complete_answer: Certainly! Domestic dogs, dingoes, wolves, and coyotes are genetically similar enough that they can mate and produce healthy and fertile offspring. This is because they are closely related species within the same group of animals. However, foxes, which belong to a different branch of the canine family, are too distantly related to dogs, wolves, and coyotes to interbreed. Understanding this relationship sheds light on the intriguing diversity in the dog family.
Prompt (or ‘exit’ to quit): The question even before that one!
#----------------------------------------
:topic: Historical Query Resolved
:chat_title: Past Query Assistance
:output_intro: Revisiting and reshaping an earlier response.
:complete_answer: Certainly! Please provide the details of the question or indicate where I should look to retrieve the topic for accurate and restructured assistance.
By the last question, the AI is lost for what I am even talking about.
This shows that indeed there is a “chain” of previous calls that, if missing, will fail silently.
Conclusion
So, yes, for either response ID as a “chat history” or “conversations” as a response history, you will get memory, even with structured output.
However, with OpenAI’s “solution”, you will get completely unbudgeted and unmanaged billing up to the model’s maximum per turn, and with internal tools, that can be the near maximum context of a model multipled many times per chat. There is no working “truncation” that will limit the length of growing conversation. That is in addition to OpenAI’s documentation about response ID life of 30 days default being false and data persisted forever if you don’t delete the IDs (which you can’t list).
Here’s Python code for you to run and chat with structured outputs as just shown. I give a Pydantic model as the structured output format along with the parse() method, simply for ease of communication (do not infer recommendation). The printing and parsing at the end is not dependent on particular keys; it will print top-level strings of a JSON if you want to change the response format class a bit.
If you type “exit” after your chat instead of aborting, it will clean up the server. It also has a global IMMEDIATE_CLEANUP that if set True will exhibit the latter symptom of short memory on Responses by immediate deleting of IDs.
from concurrent.futures import ThreadPoolExecutor
from pydantic import BaseModel, Field
from openai import OpenAI
# Structured output as Pydantic input to SDK 'text_format' instead of 'text'
class ExpertResponse(BaseModel):
topic: str = Field(description="Concise topic title. Max 12 words.")
chat_title: str = Field(description="Short chat title. Use 3-5 words.")
output_intro: str = Field(description="Brief summary of request.")
complete_answer: str = Field(description="Complete answer to request.")
def delete_response_id(resp_id: str | None) -> None:
if resp_id:
try:
client.responses.delete(resp_id)
except Exception:
pass
def delete_response_ids(ids: list[str | None]) -> None:
if not ids:
return
# Dispatch in parallel; context manager waits for completion
with ThreadPoolExecutor(max_workers=min(16, len(ids))) as ex:
for rid in ids:
ex.submit(delete_response_id, rid)
instructions = """You are a conversational expert.
- Your JSON output has fields that populate the UI, then the actual response."""
user_input = "*automated*: Produce a one-sentence user welcome message to the chatbot"
client = OpenAI()
previous_id = None
previous_response_ids: list[str | None] = [] # collects all created response ids
IMMEDIATE_CLEANUP = False
while user_input != "exit":
response = client.responses.parse(
model="gpt-4.1-mini", # or gpt-5-mini shows we skip reasoning output items
input=user_input,
text_format=ExpertResponse,
max_output_tokens=6000,
previous_response_id=previous_id,
# reasoning={"effort":"low"}, # reasoning models only
store=True,
stream=False, # parse() is incompatible with streaming
) # will raise if unparsable
previous_response_ids.append(response.id)
if IMMEDIATE_CLEANUP:
delete_response_id(previous_id) # bye, server junk
else:
previous_response_ids.append(response.id)
previous_id = response.id # retain newest id for the next turn
# non-parsed iteration to find content objects (can also be printed)
for index, out_item in enumerate(response.output):
if getattr(out_item, "type", None) != "message":
continue
for element in getattr(out_item, "content", []):
text = getattr(element, "text", None)
if text:
#print(f"\n[Output item {index}]:\n{text}")
pass
print("-" * 40)
parsed = response.output_parsed # gives direct key access, like `parsed.topic`
for key, value in parsed.model_dump().items():
print(f":{key}: {value}")
user_input = input("\nPrompt (or 'exit' to quit): ").strip()
delete_response_ids(previous_response_ids) # clean up all recorded ids with "exit"
Note that the code runs an initial prompt so that you can see you are “connected” without typing.
Hope you can find out where you went wrong!