Backend
Migrasi OpenAI Assistants API yang Dimatikan ke Responses API
Oktober 202611 menit baca

OpenAI melakukan sunset Assistants API pada 26 Agustus 2026, dan API itu tidak lagi tersedia. Halaman deprecations menyebut Responses API dan Conversations API sebagai pengganti yang direkomendasikan. Panggilan ke endpoint assistants, threads, dan runs sudah tidak berfungsi.
Migration guide OpenAI memetakan Assistants ke prompts, Threads ke Conversations, Runs ke Responses, dan Run steps ke Items. Karena reusable prompt object sendiri dijadwalkan dimatikan pada 30 November 2026, pengganti yang lebih aman untuk sebuah assistant adalah model, instructions, dan tools-nya yang disimpan sebagai konfigurasi berversi di kode Anda sendiri.
Tidak. OpenAI tidak menyediakan konverter otomatis, dan panggilan untuk membaca daftar pesan sebuah thread berhenti berfungsi saat sunset. Anda harus membangun ulang riwayat dari pesan yang disimpan aplikasi Anda sendiri, mengubah teks pengguna menjadi input_text dan teks assistant menjadi output_text, dengan maksimal 20 item per panggilan Conversations API.
Vector store adalah resource terpisah dari Assistants API, dan tool file_search di Responses API menerima vector_store_ids secara langsung. Alih-alih ditempelkan lewat tool_resources di assistant atau thread, Anda mereferensikannya di definisi tool pada setiap request. Tampilkan dulu daftar store Anda dan pastikan setiap id yang dipakai kode masih valid.
Response object disimpan selama 30 hari secara default, dan mengatur store ke false menghindarinya. Conversation object, item di dalamnya, dan response apa pun yang terhubung ke conversation tidak terkena TTL 30 hari, sehingga tersimpan sampai Anda menghapusnya. Siapkan kebijakan penghapusan sebelum memigrasikan data pelanggan.

Ringkasan Utama
OpenAI Assistants API dimatikan pada 26 Agustus 2026 dan digantikan oleh Responses API serta Conversations API. Pindahkan assistant ke konfigurasi berversi di dalam kode, bukan ke prompt object yang juga dimatikan pada 30 November 2026, bangun ulang riwayat thread dari pesan yang Anda simpan sendiri per batch 20 item, lalu pindahkan tool loop ke kode aplikasi.
Kegagalan setelah sebuah API dimatikan biasanya sunyi. Widget support yang sudah berjalan setahun mulai mengembalikan error generik, log penuh dengan panggilan gagal ke /v1/threads dan /v1/threads/runs, dan orang yang dulu menulis integrasinya sudah tidak bekerja di sana. Banyak tim berada di posisi itu bulan ini: Assistants API berhenti menjawab pada 26 Agustus 2026, sementara kode yang memanggilnya masih ter-deploy.
Ini playbook yang akan saya ikuti untuk memperbaiki integrasi Assistants yang masih live di Oktober 2026. Isinya disusun dari migration guide resmi OpenAI, halaman deprecations, dan referensi Conversations API, dan mencakup empat hal yang benar-benar perlu ditulis ulang: tempat konfigurasi assistant disimpan, cara membangun ulang riwayat thread, pengganti run loop, serta perpindahan file_search dan code_interpreter. Tulisan ini juga menandai dua jebakan di guide resmi yang akan memaksa Anda migrasi dua kali jika diikuti mentah-mentah.
Migration guide OpenAI menyatakannya dengan lugas: Assistants API resmi di-sunset pada 26 Agustus 2026 dan tidak lagi tersedia. Halaman deprecations menyebut Responses API dan Conversations API sebagai pengganti yang direkomendasikan. Seluruh permukaan beta untuk assistants, threads, runs, dan run steps ikut hilang, termasuk panggilan untuk membaca daftar pesan di sebuah thread. Poin terakhir itu yang paling penting, karena artinya riwayat yang tersimpan di thread milik OpenAI tidak bisa lagi dibaca lewat API.
Yang tidak ikut hilang adalah semua hal yang memang bukan bagian dari Assistants. File dan vector store adalah resource tersendiri, dan tool file_search di Responses API menerima vector store id secara langsung, sehingga knowledge base yang sudah Anda index adalah satu-satunya aset yang ikut pindah cukup lewat referensi. Jadi migrasi ini timpang: konfigurasi dan state percakapan harus dibangun ulang, data retrieval sebagian besar cukup diarahkan ulang, dan orkestrasi yang dulu terjadi di dalam run sekarang harus Anda tulis sendiri.
Guide OpenAI meringkas perubahannya menjadi empat penggantian nama. Tabel di bawah menambahkan artinya dalam praktik, plus kolom tentang apa yang benar-benar akan saya pakai hari ini, karena di dua baris jawaban resminya sudah bergeser.
| Konsep Assistants | Pengganti resmi | Yang saya pakai di Oktober 2026 | Jebakannya |
|---|---|---|---|
| Assistant | Prompt object, dibuat di dashboard | Model, instructions, dan tools sebagai modul berversi di repo Anda | Reusable prompt object dimatikan pada 30 November 2026 |
| Thread | Conversation | Conversation, dengan thread id lama disimpan di metadata | Tidak ada konverter otomatis; riwayat diambil dari penyimpanan Anda sendiri |
| Run | Response | Satu panggilan responses.create per giliran model | Instructions dan tools dikirim di setiap request |
| Run step | Item | Output item bertipe: message, function_call, file_search_call | Kode yang mem-parsing run steps untuk sitasi harus membaca annotations |
| requires_action dan submit_tool_outputs | Item function_call dan function_call_output | Tool loop eksplisit dengan batas jumlah giliran | Loop kini milik Anda, termasuk pengaman dari infinite loop |
| file_search lewat tool_resources | Tool file_search dengan vector_store_ids | Vector store yang sama, direferensikan di setiap request | Vector store tingkat thread tidak punya thread lagi untuk ditempeli |
| code_interpreter lewat tool_resources | Tool code_interpreter dengan container | Container bertipe auto dengan file_ids | File hasil generate kembali sebagai annotation container_file_citation |
Arah setiap baris sama: state dan orkestrasi yang dulu dipegang OpenAI di dalam assistant dan run kini berpindah ke aplikasi Anda. Guide menyebutnya separation of concerns, dengan kode Anda menangani pemangkasan riwayat, tool loop, dan retry. Untuk tim backend ini pertukaran yang adil, karena semuanya jadi bisa dites, tetapi ini juga alasan migrasinya berupa penulisan ulang call site, bukan sekadar find-and-replace nama endpoint.
Langkah pertama di guide resmi adalah membuka setiap assistant di dashboard dan menekan Create prompt, yang mengubahnya menjadi reusable prompt object yang dipanggil lewat id. Halaman yang sama kini memuat catatan bahwa reusable prompt object juga sedang di-deprecate, dan halaman deprecations memberi tanggalnya: pembuatan prompt mulai tidak ditonjolkan sejak 3 Juni 2026, dan API v1/prompts beserta reusable prompt object dijadwalkan dimatikan pada 30 November 2026. Mengikuti langkah pertama secara harfiah hanya memberi Anda sekitar dua bulan.
Jika Anda sudah memindahkan assistant ke prompt object di dashboard, Anda punya tenggat kedua pada 30 November 2026. Panduan OpenAI sendiri untuk migrasi itu adalah memindahkan isi prompt keluar dari managed object dan masuk ke kode aplikasi, yang memang seharusnya menjadi tempatnya sejak awal.
// Wrong: the dashboard "Create prompt" path. Each assistant becomes a
// reusable prompt object (pmpt_...), and v1/prompts is scheduled to shut
// down on 30 November 2026 — you would migrate the same config twice.
await client.responses.create({
prompt: { id: "pmpt_123", version: "3" },
conversation: conversationId,
input,
});
// Right: the assistant's instructions + tools live in a module you review,
// diff and roll back like any other code. src/agents/invoice-helper.ts
export const INVOICE_HELPER = {
version: "2026-10-01",
model: "gpt-6-astra",
instructions: [
"You answer questions about purchase invoices in the ERP.",
"Quote invoice numbers exactly. Never invent a due date.",
].join("\n"),
tools: [
{
type: "file_search",
vector_store_ids: [process.env.POLICY_VECTOR_STORE_ID!],
},
{
type: "function",
name: "get_invoice",
description: "Fetch one purchase invoice by its number.",
parameters: {
type: "object",
properties: { number: { type: "string" } },
required: ["number"],
additionalProperties: false,
},
strict: true,
},
],
};Modul di atas adalah pengganti utuh untuk sebuah assistant object. Modul itu memberi apa yang dijanjikan prompt object, yaitu definisi yang bisa di-review, di-diff, dan berversi, lewat alat yang sudah Anda pakai untuk hal itu: git. Tambahkan field version dan log versi itu di setiap response, supaya saat pelanggan melaporkan jawaban yang buruk Anda tahu instructions mana yang menghasilkannya. Jika butuh varian per tenant, susun di kode dari satu definisi dasar, jangan menyimpan salinan.
OpenAI tidak mengonversi thread menjadi conversation. Contoh di guide menunjukkan cara menyalin riwayat sebelum sunset dengan mem-paging threads.messages.list, lalu menyatakan bahwa panggilan itu tidak lagi berfungsi dan Anda harus memakai pesan yang Anda simpan sendiri. Jika aplikasi Anda menyimpan salinan setiap pesan, semua thread aktif bisa dibangun ulang. Jika aplikasi hanya menyimpan thread id dan mengandalkan OpenAI untuk isinya, riwayat itu sudah hilang, dan solusi yang jujur adalah memulai conversation baru untuk pengguna tersebut.
Konversinya sendiri kecil, dengan satu detail yang dilewatkan contoh di guide. Contohnya mengirim semua pesan hasil konversi ke satu panggilan conversations.create, padahal referensi Conversations API menyebut Anda hanya boleh menambahkan hingga 20 item sekaligus, baik saat create maupun items.create. Thread demo berisi lima pesan akan lolos; thread pelanggan berisi enam puluh pesan tidak. Pecah menjadi batch:
# Rebuild one legacy thread as a Conversation from messages YOU stored.
# OpenAI's example reads threads.messages.list — that call stopped working
# at the sunset, so the source is your own table, oldest message first.
from openai import OpenAI
client = OpenAI()
BATCH = 20 # conversations.create and items.create take up to 20 items per call
def to_item(row):
# User text is input_text; assistant text must be output_text, or the
# rebuilt history reads as if the user said the assistant's lines.
part = "input_text" if row["role"] == "user" else "output_text"
return {"role": row["role"], "content": [{"type": part, "text": row["text"]}]}
def migrate_thread(legacy_thread_id, rows):
items = [to_item(r) for r in rows if r["text"]]
conversation = client.conversations.create(
items=items[:BATCH],
# Keep the old id, so support can trace a conversation to its thread.
metadata={"legacy_thread_id": legacy_thread_id},
)
# Wrong: create(items=items) for a 60-message thread. It works in a demo
# with five messages and fails on the first real customer history.
for start in range(BATCH, len(items), BATCH):
client.conversations.items.create(
conversation.id, items=items[start:start + BATCH]
)
return conversation.idDua aturan kecil lain berasal dari contoh yang sama. Teks pengguna menjadi part input_text dan teks assistant menjadi part output_text, sedangkan gambar menjadi part input_image beserta URL dan detail-nya. Petakan setiap thread id lama ke conversation id baru di database Anda selama migrasi, supaya request berikutnya dari pengguna lama mendarat di riwayat yang sudah dibangun ulang, bukan membuat conversation baru.
Migrasikan hanya thread yang akan dibuka lagi. Thread yang tidak tersentuh selama enam bulan lebih murah diarsipkan di database Anda daripada dibangun ulang, dan conversation yang Anda buat untuknya akan tersimpan selamanya, karena conversation tidak tercakup retensi response 30 hari.
Run adalah job asinkron: buat, polling statusnya, tangani requires_action dengan mengirim tool output, lalu polling lagi. Response adalah request yang mengembalikan output item. Tool loop yang dulu disembunyikan requires_action kini eksplisit: Anda membaca item function_call dari output, menjalankan fungsi Anda, lalu mengirim balik item function_call_output yang merujuk setiap panggilan lewat call_id.
// Before (Assistants, openai v4 SDK): start a run, then poll it.
let run = await openai.beta.threads.runs.create(threadId, {
assistant_id: assistantId,
});
while (["queued", "in_progress"].includes(run.status)) {
await sleep(1000);
run = await openai.beta.threads.runs.retrieve(threadId, run.id);
}
// ...then requires_action -> submit_tool_outputs -> poll again.
// After (Responses + Conversations): one request per model turn.
import OpenAI from "openai";
import { INVOICE_HELPER } from "./agents/invoice-helper";
const client = new OpenAI();
const MAX_TOOL_TURNS = 5;
export async function ask(conversationId: string, text: string) {
const base = {
model: INVOICE_HELPER.model,
// Sent on every call: the conversation stores items, not your config.
instructions: INVOICE_HELPER.instructions,
tools: INVOICE_HELPER.tools,
conversation: conversationId,
};
let response = await client.responses.create({
...base,
input: [{ role: "user", content: text }],
});
// requires_action used to hide this loop. Now it is yours: run each
// function_call, answer it by call_id, and send the outputs back.
for (let turn = 0; turn < MAX_TOOL_TURNS; turn++) {
const calls = response.output.filter((item) => item.type === "function_call");
if (calls.length === 0) return response.output_text;
const outputs = await Promise.all(
calls.map(async (call) => ({
type: "function_call_output" as const,
call_id: call.call_id,
output: JSON.stringify(await runTool(call.name, JSON.parse(call.arguments))),
})),
);
response = await client.responses.create({ ...base, input: outputs });
}
// A cap the old run never needed you to write: fail loudly, don't spin.
throw new Error(`Tool loop did not settle in ${MAX_TOOL_TURNS} turns`);
}Dua pilihan di kode itu disengaja. Instructions dan tools dikirim di setiap panggilan, bukan dipercaya akan tersimpan, karena conversation menyimpan item, bukan konfigurasi Anda, dan mengirimnya setiap kali juga berarti perubahan konfigurasi langsung berlaku di giliran berikutnya. Lalu loop-nya punya batas keras. Sebuah run pada akhirnya akan gagal atau expire sendiri; loop Anda akan dengan senang hati memanggil tool yang bermasalah selamanya, jadi beri batas atas dan error yang akan terlihat di log.
Di Assistants, hosted tool dideklarasikan di assistant dan datanya ditempelkan lewat tool_resources, baik di assistant maupun di thread tertentu. Di Responses tidak ada assistant object dan tidak ada thread untuk ditempeli apa pun, jadi setiap tool membawa resource-nya di definisinya sendiri, di setiap request.
// Before: tools declared on the assistant, files bound via tool_resources
// (on the assistant or on the thread).
await openai.beta.assistants.create({
model: "gpt-4o",
tools: [{ type: "file_search" }, { type: "code_interpreter" }],
tool_resources: {
file_search: { vector_store_ids: ["vs_policies"] },
code_interpreter: { file_ids: ["file-ledger-q3"] },
},
});
// After: each tool brings its own resources, on every request.
tools: [
{
type: "file_search",
vector_store_ids: ["vs_policies"], // the same vector store, referenced by id
max_num_results: 8,
},
{
type: "code_interpreter",
// "auto" creates a container, or reuses one already in the context.
container: { type: "auto", file_ids: ["file-ledger-q3"] },
},
]
// Read citations from the output message, not from run steps:
// annotations of type file_citation (file_search) and
// container_file_citation (files code_interpreter wrote).Untuk file_search perubahannya kebanyakan mekanis, karena vector store-nya objek yang sama dan direferensikan lewat id. Kasus yang perlu dipikirkan adalah vector store yang ditempelkan ke satu thread, biasanya file yang diunggah pengguna dalam satu sesi chat. Simpan id store itu di samping conversation di database Anda dan tambahkan ke definisi tool untuk request pada conversation tersebut. Untuk code_interpreter, container auto dibuatkan untuk Anda atau dipakai ulang dari konteks sebelumnya, dan file yang dihasilkan model kembali sebagai annotation container_file_citation di output message, yang kini menjadi sumber link download di UI Anda.
Model state berubah dengan cara yang penting bagi siapa pun yang memegang data pelanggan atau data ERP. Guide conversation state dari OpenAI menyebut tiga aturan yang layak ditulis di catatan migrasi Anda:
Jadi conversation bersifat permanen sampai Anda menghapusnya. Itu yang Anda inginkan untuk chat support yang akan dibuka lagi oleh pengguna, dan itu menjadi beban untuk invoice assistant yang melihat nama supplier, nominal, dan detail rekening bank. Tentukan masa retensi sebelum migrasi, simpan conversation id terhadap user atau tenant pemiliknya, dan hapus conversation ketika hubungan itu berakhir. Di bawah UU Pelindungan Data Pribadi, menyimpan riwayat chat tanpa batas waktu dan tanpa tujuan yang jelas bukan posisi yang ingin Anda pertahankan.
Jangan membuat satu conversation bersama per tenant demi menghemat usaha. Setiap pengguna di tenant itu akan membaca riwayat yang sama, termasuk pertanyaan pengguna lain. Satu conversation per end user per sesi chat adalah default yang aman.
Ini urutan kerja yang akan saya pakai, supaya setiap langkah bisa diverifikasi sebelum langkah berikutnya bergantung padanya:
Shutdown Assistants API bukan sekadar ganti nama, melainkan perpindahan kepemilikan. Konfigurasi pindah ke repository Anda, riwayat percakapan ke database Anda dulu baru kemudian ke Conversation, dan tool loop ke kode Anda. Pindahkan masing-masing ke tempat yang Anda kendalikan, bukan ke managed object berikutnya di daftar deprecations, dan ini akan menjadi migrasi terakhir yang dibutuhkan integrasi tersebut.