Home / AI & GenAI

AI & GenAIRunbookIntermediate

Transformation: migrating from the Assistants API to the Responses API

OpenAI has deprecated the Assistants API in favour of the Responses API with Conversations. Map assistants, threads, runs and vector stores to the new model, rewrite the call path, move conversation state, re-test tool use and file search, and remove the old polling code.

Deepak Bhardwaj3 min read

Applies toOpenAI API and Azure OpenAI apps using Assistants (v2) with threads, runs, file_search or code_interpreter

Warning

Check the current sunset date for the Assistants API on OpenAI and on Azure OpenAI (they publish separately). After it, Assistants calls fail. Treat this as a planned migration, not a someday task.

Concept mapping

Assistants API Responses API
Assistant (model, instructions, tools) instructions + tools in each request, or a stored prompt object
Thread Conversation (or chain with previous_response_id)
Run (create, poll status) One response call (sync or streamed); no polling loop
Run steps Output items (messages, tool calls, tool results)
Messages on a thread Items in a conversation
file_search with vector stores Same vector stores, file_search tool on the response
code_interpreter code_interpreter tool with a container
Function calling (requires_action) Function call items returned; send function_call_output items back

1. Inventory

For each app: assistants (export their instructions, model, tools, vector store ids), how threads are stored and mapped to users, whether runs are polled or streamed, and which function tools exist.

for a in client.beta.assistants.list(limit=100):
    print(a.id, a.name, a.model, [t.type for t in a.tools])

2. Rewrite the call path

Before (simplified):

thread = client.beta.threads.create()
client.beta.threads.messages.create(thread.id, role="user", content=q)
run = client.beta.threads.runs.create_and_poll(thread_id=thread.id, assistant_id=ASSISTANT_ID)
msgs = client.beta.threads.messages.list(thread.id, order="desc", limit=1)

After:

conv = client.conversations.create()          # once per user session; store conv.id
resp = client.responses.create(
    model="gpt-4.1",                          # Azure: your deployment name
    instructions=ASSISTANT_INSTRUCTIONS,
    conversation=conv.id,
    input=q,
    tools=[{"type": "file_search", "vector_store_ids": [VECTOR_STORE_ID]}],
)
print(resp.output_text)

Alternative without a conversation object: pass previous_response_id=resp.id on the next turn (requires stored responses).

3. Function tools

resp = client.responses.create(model=M, input=q, tools=[{"type": "function", "name": "get_ticket",
        "description": "Get a ticket by id", "parameters": {"type": "object",
        "properties": {"id": {"type": "string"}}, "required": ["id"], "additionalProperties": False}}])

calls = [i for i in resp.output if i.type == "function_call"]
outputs = [{"type": "function_call_output", "call_id": c.call_id,
            "output": json.dumps(get_ticket(**json.loads(c.arguments)))} for c in calls]
resp = client.responses.create(model=M, previous_response_id=resp.id, input=outputs, tools=TOOLS)

No more requires_action / submit_tool_outputs polling.

4. Existing threads

Decide per app:

  • Start fresh: acceptable for most support chats; keep old threads read-only until sunset for reference.
  • Carry history: read thread messages and add them as items to a new conversation (text only; check how attachments should be handled).

Map your user ↔ thread table to user ↔ conversation.

5. Test

  • Evaluation set through old and new paths; compare answers and file_search citations.
  • Tool calls: arguments, multiple parallel calls, error outputs.
  • Streaming events changed names and shape. Update the UI's event handling.
  • Data retention settings for stored responses/conversations agree with your policy (store parameter).

6. Clean up

Remove polling loops, run-status handling and assistant-creation scripts; move instructions into source control (or managed prompts with versions).

Tip

Putting instructions in code (or a versioned prompt) instead of a mutable assistant object is a quiet improvement: prompt changes now go through review and your evaluation gate.

Checklist

  • Sunset date known for your platform
  • Assistants, tools, vector stores inventoried
  • Call path rewritten (conversations or previous_response_id)
  • Function tool loop and streaming UI updated
  • Thread migration decision made
  • Evaluation compared, old code removed