OpenAI shuts down the Assistants API on August 26, 2026. Code calling client.beta.assistants or client.beta.threads stops working that day.
DeprecationBot migrates that code to the Responses API, verifies the rewrite preserves what your code sends to the model, and refuses any migration it cannot verify.
from openai import OpenAI
client = OpenAI()
- assistant = client.beta.assistants.create(
- name="Math Tutor",
- instructions="You are a personal math tutor. Be concise.",
- model="gpt-4o",
- tools=[{"type": "code_interpreter"}],
- )
-
- thread = client.beta.threads.create()
-
- client.beta.threads.messages.create(
- thread_id=thread.id,
- role="user",
- content="Solve 3x + 11 = 14",
- )
-
- run = client.beta.threads.runs.create_and_poll(
- thread_id=thread.id,
- assistant_id=assistant.id,
- )
-
- messages = client.beta.threads.messages.list(thread_id=thread.id)
- print(messages)
+ conversation = client.conversations.create()
+
+ response = client.responses.create(
+ model="gpt-4o",
+ instructions="You are a personal math tutor. Be concise.",
+ tools=[{"type": "code_interpreter"}],
+ input=[{"role": "user", "content": "Solve 3x + 11 = 14"}],
+ conversation=conversation.id,
+ )
+
+ print(response.output_text)Five calls become two. The assistant, the thread, the message creation, and the polling run collapse into one responses.create().
This is not find-and-replace. assistant_id, thread_id, and run_id create data dependencies across call sites, often across functions. DeprecationBot builds a variable-flow graph so related calls migrate together — migrating any one alone produces code that doesn't run.
This tool rewrites source code. The design assumes you should not have to take its word for anything.
- It refuses rather than guesses. Unrecognized patterns, oversized files, and failed verification all produce a refusal with a stated reason. Never a partial migration.
- It never writes unverified output. Every transformation passes syntax validation and invariant comparison before touching disk.
- Dry run is the default. Writing requires an explicit
--write. There is no setting to change that.
A refusal:
$ deprecationbot migrate src/auth_service.py
DEFERRED src/auth_service.py (340 lines)
Exceeds the 250-line validated limit.
Measured truncation threshold: ~300 lines.
No LLM call made. No files modified.
That limit was measured, not guessed. See Limitations.
Not yet on PyPI. From source:
git clone https://github.com/TODO/deprecationbot
cd deprecationbot
python -m venv .venv
source .venv/bin/activate
pip install -e .Python 3.10+.
Transformation calls an LLM with your own key:
export DEEPSEEK_API_KEY=...scan and verify need no key and make no network calls.
deprecationbot scan ./src # detect, read-only
deprecationbot migrate ./src/assistant.py # preview, writes nothing
deprecationbot migrate ./src/assistant.py --writeA successful dry run:
$ deprecationbot migrate examples/math_tutor.py
Found 5 deprecated API calls in examples/math_tutor.py
Migrating... done
--- examples/math_tutor.py
+++ examples/math_tutor.py (migrated)
[unified diff]
VERIFIED
model preserved gpt-4o
instructions preserved exact match (44 chars)
tool schemas preserved 1 schema, byte-identical
message order preserved 1 message
syntax valid
dry run — no files modified
Run again with --write to apply.
| Command | |
|---|---|
scan <path> |
Detect deprecated usage. Read-only. |
migrate <path> |
Migrate. Dry run unless --write. |
verify <old> <new> |
Compare two files for semantic equivalence. |
pr <owner/repo> |
Fork, migrate, verify, open a pull request. All gates apply. |
Exit codes: 0 success · 1 verification failed · 2 safety gate tripped · 3 nothing detected. Nothing is written on 1 or 2.
Read this before running the tool. These are measured or structural, not a backlog.
The transformer sends a file to an LLM and gets a rewritten file back, so output token limits bound what it can process. The boundary was measured:
| Lines | Result |
|---|---|
| ~100 | Complete output |
| ~200 | Complete output |
| ~300 | Truncated — top-level definitions missing |
| ~400+ | Truncated |
Truncation starts around 300. The tool refuses above 250, leaving margin. Raising the ceiling requires localized CST replacement instead of whole-file generation — see Roadmap.
TypeScript and JavaScript are not supported.
Detected and reported, not migrated, because the correct result isn't mechanically determined:
- Streaming (
stream=True) — event semantics differ between the APIs file_search— vector store wiring differssubmit_tool_outputs— the tool-call loop becomes explicit- Runtime-loaded assistant IDs (
os.getenv("ASSISTANT_ID"), DB lookups) — config is invisible to static analysis - Externally persisted thread IDs — existing
thread_identifiers need backfill
- Indirect clients.
get_client().beta.threads.create()is missed; the client can't be resolved statically. - Cross-file units. Flagged but not migrated. An assistant created in one module and used in another is deferred.
DeprecationBot compares semantic invariants extracted from the syntax tree of the original and migrated code:
| Invariant | Meaning |
|---|---|
| Model | The model identifier is unchanged |
| Instructions | System instruction text is unchanged, character for character |
| Tool schemas | Tool definitions are byte-identical after key normalization |
| Messages | User message content and ordering are preserved |
| Syntax | Both the transformed block and the whole file parse and compile |
Any failure rejects the migration and nothing is written.
Values that aren't literals — variables, f-strings, environment lookups, function calls — can't be compared statically. They are reported as unverified, never as passed, and every report states the split:
VERIFIED (partial)
model unverified — value is a variable (MODEL_NAME)
instructions preserved exact match (44 chars)
tool schemas preserved 1 schema, byte-identical
There is no runtime verification. The tool does not call the OpenAI API, execute your code, or compare live responses. It proves the values reaching the API are preserved. It does not prove your application behaves identically end to end — that would require running your code against a live API, which this tool won't do.
Review every diff before merging it. Verification is evidence, not a guarantee.
No servers, no account, no hosted component.
- To your LLM provider: contents of files being migrated, using your own key under your own agreement. Only during
migrateandpr. - To GitHub: only during
pr, through theghCLI with your existing auth. - To the authors: nothing. No telemetry, no analytics, no crash reporting — enabled or otherwise.
- Locally: a response cache, and a rate-limit state file if you use
pr.
The Assistants shutdown isn't isolated. Checked against vendor deprecation pages on 2026-07-27:
| Date | Change | Provider |
|---|---|---|
| 2026-07-23 | 25+ model IDs shut down (first wave) | OpenAI |
| 2026-08-05 | Claude Opus 4.1 retired | Anthropic |
| 2026-08-26 | Assistants API shutdown | OpenAI |
| 2026-10-23 | Model shutdown, second wave | OpenAI |
| 2026-10-31 | Evals platform read-only | OpenAI |
| 2026-11-30 | Evals, Agent Builder, /v1/prompts shut down |
OpenAI |
| 2026-12-01 | Legacy GPT Image models removed | OpenAI |
Two details matter more than the length of that list. The breakage has outgrown model identifiers: from Claude Opus 4.7 onward, setting temperature, top_p, or top_k to a non-default value returns a 400 error — a code change, not a string swap. And schedules diverge by platform: Claude Sonnet 4 retired on the Anthropic API on 2026-06-15, while the same model on Amazon Bedrock carries an EOL of 2026-10-14.
Verify current dates against OpenAI and Anthropic.
OpenAI's migration guide says to convert each Assistant into a Prompt object. DeprecationBot doesn't, because Prompt objects can only be created in the dashboard — there's no API, so the step can't be automated — and reusable prompt objects were themselves deprecated on 2026-06-03 with shutdown on 2026-11-30.
Following the official guidance moves you onto a surface that closes three months later. DeprecationBot inlines model, instructions, and tools directly into responses.create() instead.
Migrations are declarative YAML mappings.
| Recipe | Provider | Sunset | Status |
|---|---|---|---|
assistants_to_responses |
OpenAI | 2026-08-26 | Implemented, in validation |
Only work that removes a known limitation.
- Localized CST replacement — statement-range rewriting instead of whole-file generation. Removes the 250-line ceiling, produces smaller diffs.
- Additional recipes — Anthropic model retirements; OpenAI's 2026-11-30 shutdowns.
- Deprecation monitoring — ingest vendor announcements so recipes exist before shutdown dates arrive.
Useful reports, in order:
- A migration that verified but was wrong. The most valuable thing you can file. Minimal reproduction, please.
- Deprecated usage the detector misses.
- Patterns that defer but shouldn't.
git clone https://github.com/TODO/deprecationbot
cd deprecationbot
python -m venv .venv
source .venv/bin/activate
pip install -e ".[dev]"
pytestApache-2.0. See LICENSE.