azure-openai-to-responses Turns API Migration Into a Skill, Not a Rewrite
A migration toolkit for Python teams moving from Azure OpenAI chat completions to the Responses API, with scanners, compatibility checks, agent instructions, and rollout tooling.
- This repo treats Azure OpenAI migration as a contract problem, so the scanner, smoke test, and bulk tooling all work together instead of acting like a one-off script.
- The `SKILL.md` file is the real product move, because it packages migration knowledge for both humans and coding agents.
- The hardest part is not swapping `AzureOpenAI` for `OpenAI`, but tracking response shapes, streaming behavior, model rules, and deployment settings that break silently.
- The demo and org-level tooling show the migration is meant to scale from a single codebase to a repeatable operational process.
The weirdest thing about azure-openai-to-responses is that it does not read like a utility repo. It reads like an instruction set for an assistant that knows how to migrate your app, check your deployment, and keep you from stepping on model-specific landmines.
That is the right frame, because the hard part of this migration is not the client swap. It is the hidden contract around response shape, streaming, parameters, environment variables, and the Azure deployment you are pointing at.
GPT-5 and newer models require the Responses API. Migrating now future-proofs your apps and unlocks deep tool integration, structured output, and a stable /openai/v1/ endpoint with no api_version management.
The migration repo that teaches your agent
The central trick here is the SKILL.md layer. Instead of only telling you what to change, the repo teaches an AI coding agent how to recognize legacy patterns, classify them, and apply the migration safely.
That matters because migrations fail in the seams. A human sees AzureOpenAI(). The repo also sees choices[0].delta.content, api_version, temperature assumptions, and the test fixtures that quietly depend on the old shape.
The Responses API is a server-side concern. In most cases, you should only migrate your Python backend — the HTTP contract between your frontend and backend should stay unchanged.
Why the API break is bigger than a client swap
| Legacy chat completions | Responses API |
|---|---|
| `AzureOpenAI()` with `chat.completions.create(messages=...)` | `OpenAI(...).responses.create(input=...)` |
| Output usually read from `choices[0].message.content` or streaming deltas | Output read from `resp.output_text` and Responses events |
| Azure versioning centered on `api_version` | Stable `/openai/v1/` endpoint reduces version churn |
| Migration risk concentrated in a single call site | Migration risk spreads across tests, config, and model rules |
The repo is opinionated about this distinction. It treats the Responses API as a new contract, not a new method name, which is why the tooling keeps checking for response-shape dependencies instead of stopping at import changes.
The scanner catches the hidden migration debt
The repo’s scanner, detect_legacy.py, is the most revealing piece of code in the project. It does not merely flag AzureOpenAI. It hunts for the deeper dependencies that make a migration brittle, especially response-shape access patterns and parameter assumptions.
# Legacy patterns the scanner is designed to catch
client = AzureOpenAI(api_version="2024-xx-xx")
resp = client.chat.completions.create(
model="gpt-4o",
messages=messages,
temperature=0.7,
)
text = resp.choices[0].message.content
stream_chunk = delta.choices[0].delta.content
That is a better migration model than search and replace. Once the scanner can label code as response-shape or env-var debt, the refactor stops being guesswork and starts looking like a checklist.
The smoke test prevents a bad migration from starting
The operational piece is migrate.py, especially its smoke test flow. Before anyone starts refactoring, the repo checks whether the target Azure deployment can actually serve Responses API calls.
That is a small move with a big payoff. It keeps the team from converting code to a contract the cloud side cannot honor yet, which is the kind of failure that wastes an afternoon and produces a false sense of progress.
def _run_smoke_test(client):
response = client.responses.create(
model=model,
input="Say hello in one sentence."
)
return response.output_text
Bulk migration is where this becomes an enterprise tool
The bulk tooling pushes the project past a single-repo helper. It can search an organization, clone repositories, open branches, and prepare pull requests with a standard migration template.
That changes the economics. Manual migration is a task. Bulk migration is a program, and this repo is built to help a platform team run it repeatedly without rediscovering the same edge cases in every codebase.
| Mode | Strength | Weakness |
|---|---|---|
| Manual migration | Fast to start | Easy to miss hidden dependencies |
| Agent-guided migration with `SKILL.md` | Captures repeatable migration knowledge | Depends on the quality of the prompt and scanner |
| Bulk org migration | Scales across many repos | Requires strong validation and rollout discipline |
The demo repo proves the migration is holistic
The demo folder matters because it proves this is not only an application-layer change. The reference implementation updates Bicep templates, environment variables, async behavior, and tests so the new API contract is reflected everywhere it needs to be reflected.
That is the hidden lesson of the whole repository. A model migration is never only about code that calls the model. It reaches into infra, config, and test fixtures, which is why a skill-driven migration makes more sense than a one-off rewrite.
A practical reason to migrate is not “the endpoint will disappear tomorrow” — it’s that Responses is where the platform’s agentic primitives (tools, state, richer outputs) are designed to feel native.
Why the Responses API changes the shape of AI apps
This repo is really about a change in mental model. Legacy chat completions treated the model like a text endpoint. Responses treats it more like an agent substrate, where tools, richer outputs, and compatibility rules become part of the default workflow.
That is why the repository leans so hard on checks, prompts, and rollout tooling. The actual migration is not syntax. It is organizational memory, captured well enough that a person or an agent can repeat it without improvising.





