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.

8 min read • View on GitHub • More from azure-samples

A human hand and a mechanical hand jointly turning a large lock that sits between an old chat bubble and a new response stream. Small code fragments orbit the lock, including choices[0].delta, input, output_text, api_version, and responses.create. It explains that the migration is really about changing the contract between app code and the model, not just swapping a client class.
The repo frames migration as a contract shift, not a rename exercise.
Key Takeaways

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.

Azure-Samples, Project Maintainer (GitHub Organization) · Azure-Samples/azure-openai-to-responses (README.md)

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.

Azure-Samples, Project Maintainer (GitHub Organization) · Azure-Samples/azure-openai-to-responses (README.md)

Why the API break is bigger than a client swap

Legacy chat completionsResponses API
`AzureOpenAI()` with `chat.completions.create(messages=...)``OpenAI(...).responses.create(input=...)`
Output usually read from `choices[0].message.content` or streaming deltasOutput 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 siteMigration 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 migration pipeline works like a decision engine: scan, classify, validate, then choose the safest route.

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.

A close-up scanner beam passes over a codebase and reveals hidden fossils inside it. The beam lights up legacy fragments such as choices[0].delta.content, temperature=0.7, .env variables, Bicep settings, and api-version, then shows them being rewritten into a cleaner Responses API shape on the other side. It explains that the repo finds migration debt by pattern, not by brute-force renaming.
The scanner exposes the assumptions buried inside the code, config, and tests.

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.

ModeStrengthWeakness
Manual migrationFast to startEasy to miss hidden dependencies
Agent-guided migration with `SKILL.md`Captures repeatable migration knowledgeDepends on the quality of the prompt and scanner
Bulk org migrationScales across many reposRequires 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.

Azure-Samples, Project Maintainer (GitHub Organization) · Chat Completions to Responses API (2026): Practical Migration Guide + Gotchas

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.