---
name: azure-openai-to-responses
description: >-
Migrate Python apps from Azure OpenAI Chat Completions to the Responses API.
Covers AzureOpenAI/AsyncAzureOpenAI client migration to the v1 endpoint,
streaming, tools, structured output, multi-turn, EntraID auth, and model
compatibility checks. Python-focused, Azure OpenAI-specific.
USE FOR: migrate to responses API, switch from chat completions, openai responses,
upgrade openai SDK, responses API migration, move from completions to responses,
gpt-5 migration, azure openai python migration, chat completions to responses,
AzureOpenAI to OpenAI client, python azure openai upgrade.
DO NOT USE FOR: building new apps from scratch (start with responses directly),
Node/TypeScript/C#/Java/Go migrations (this skill is Python-only),
Azure infrastructure setup (use azure-prepare), deploying models (use microsoft-foundry).
license: MIT
---
# Migrate Python Apps from Azure OpenAI Chat Completions to Responses API
> **AUTHORITATIVE GUIDANCE — FOLLOW EXACTLY**
>
> This skill migrates Python codebases using Azure OpenAI Chat Completions
> to the unified Responses API. Follow these instructions precisely.
> Do not improvise parameter mappings or invent API shapes.
---
## Triggers
Activate this skill when user wants to:
- Migrate a Python app from Azure OpenAI Chat Completions to Responses API
- Upgrade Python OpenAI SDK usage to the latest API shape against Azure OpenAI
- Prepare Python code for GPT-5 or newer models that require Responses on Azure
- Switch from `AzureOpenAI`/`AsyncAzureOpenAI` to standard `OpenAI`/`AsyncOpenAI` client with the v1 endpoint
- Fix deprecation warnings related to `AzureOpenAI` constructors or `api_version`
---
## ⚠️ Model Compatibility — CHECK FIRST
> **Before migrating, verify your Azure OpenAI deployment supports the Responses API.**
### 1. Smoke-test your deployment (fastest)
```python
import os
from openai import OpenAI
client = OpenAI(
api_key=os.environ["AZURE_OPENAI_API_KEY"],
base_url=f"{os.environ['AZURE_OPENAI_ENDPOINT'].rstrip('/')}/openai/v1/",
)
try:
resp = client.responses.create(
model=os.environ["AZURE_OPENAI_DEPLOYMENT"],
input="ping",
max_output_tokens=50,
store=False,
)
print(f"✅ Deployment supports Responses API: {resp.output_text}")
except Exception as e:
print(f"❌ Deployment does NOT support Responses API: {e}")
```
> **Note**: `max_output_tokens` has a **minimum of 16** on Azure OpenAI. Values below 16 return a 400 error. Use 50+ for smoke tests.
If this returns a 404, the deployment's model doesn't support Responses yet — check the reference below or redeploy with a supported model.
### 2. Check available models in your region (recommended)
Run the built-in model compatibility tool to see what's available with Responses API support in your specific region:
```bash
python migrate.py models --subscription YOUR_SUB_ID --location YOUR_REGION
```
This queries Azure ARM live and shows a compatibility matrix — which models support Responses, structured output, tools, etc. Use `--filter gpt-5.1,gpt-5.2` to narrow results or `--json` for scripting.
### 3. Full model support reference
- **Live query**: `python migrate.py models` (see above — region-specific, always up to date)
- **Browse availability**: [Model summary table and region availability](https://learn.microsoft.com/en-us/azure/foundry/foundry-models/concepts/models-sold-directly-by-azure?tabs=global-standard-aoai%2Cglobal-standard&pivots=azure-openai#model-summary-table-and-region-availability)
- **Quickstart & guidance**: **https://aka.ms/openai/start**
### ⚠️ Older model limitations
> **WARNING**: Older models (those predating `gpt-4.1`) may not support all Responses API features fully.
>
> Known limitations with older models:
> - **`reasoning` parameter**: Not supported on many non-reasoning models. Only migrate `reasoning` if it was already present in the original code.
> - **`seed` parameter**: Not supported in Responses API at all — remove from all requests.
> - **Structured output via `text.format`**: Older models may not enforce `strict: true` JSON schemas reliably.
> - **Tool orchestration**: GPT-5+ orchestrates tool calls as part of internal reasoning. Older models on Responses still work but lack this deep integration.
> - **Temperature constraints**: When migrating to `gpt-5`, temperature must be omitted or set to `1`. Older models have no such constraint.
### O-series reasoning models (o1, o3-mini, o3, o4-mini)
O-series models have unique parameter constraints. When migrating apps that target o-series models:
- **`temperature`**: Must be `1` (or omitted). O-series models do not accept other values.
- **`max_completion_tokens` → `max_output_tokens`**: Apps using the Azure-specific `max_completion_tokens` must switch to `max_output_tokens`. Set high values (4096+) because reasoning tokens count against the limit.
- **`reasoning_effort`**: If the app uses `reasoning_effort` (low/medium/high), keep it — the Responses API supports this parameter for o-series models.
- **Streaming behavior**: O-series models may buffer output until reasoning completes before emitting text delta events. Streaming still works, but the first `response.output_text.delta` may arrive after a longer delay than with GPT models.
- **`top_p`**: Not supported on o-series — remove if present.
- **Tool use**: O-series models support tools via the Responses API the same as GPT models, but tool call orchestration quality varies by model.
**Action — proactive model advisory**: During the scan phase, check which model the app targets (deployment names, env vars, config). If the model predates `gpt-4.1` (not gpt-4.1+), proactively tell the user:
- The migration will work for basic text, chat, streaming, and tools on their current model.
- Newer models (`gpt-5.1`, `gpt-5.2`) offer better tool orchestration, structured output enforcement, reasoning, and cross-region availability.
- They should consider upgrading their deployment when ready — it's not blocking the migration.
Do not block or refuse to migrate based on model version. The advisory is informational.
### GitHub Models does NOT support the Responses API
> **GitHub Models (`models.github.ai`, `models.inference.ai.azure.com`) does not support the Responses API.**
If the codebase has a GitHub Models code path (look for `base_url` pointing to `models.github.ai` or `models.inference.ai.azure.com`), **remove it entirely** during migration. The Responses API requires Azure OpenAI, OpenAI, or a compatible local endpoint (e.g., Ollama with Responses support).
Action during scan:
- Flag any GitHub Models code paths for removal.
---
## Framework Migration
Many apps use higher-level frameworks on top of OpenAI. When migrating these, the framework's own API changes — not just the underlying OpenAI calls.
### Microsoft Agent Framework (MAF)
**Check your MAF version first** — the migration depends on whether you are on MAF 1.0.0+ or a pre-1.0.0 beta/rc.
#### MAF 1.0.0+ (agent-framework-openai >= 1.0.0)
`OpenAIChatClient` **already uses the Responses API** — no migration needed. If the codebase uses the legacy `OpenAIChatCompletionClient` (which uses `chat.completions.create`), replace it with `OpenAIChatClient`.
| Before | After |
|--------|-------|
| `from agent_framework.openai import OpenAIChatCompletionClient` | `from agent_framework.openai import OpenAIChatClient` |
| `OpenAIChatCompletionClient(...)` | `OpenAIChatClient(...)` |
To check your version: `python -c "import agent_framework_openai; print(agent_framework_openai.__version__)"`
#### MAF pre-1.0.0 (beta/rc releases)
In pre-1.0.0 MAF, `OpenAIChatClient` used Chat Completions. Upgrade to `agent-framework-openai>=1.0.0` where `OpenAIChatClient` uses the Responses API by default.
No other changes needed — the `Agent` and tool APIs remain the same.
### LangChain (`langchain-openai`)
Add `use_responses_api=True` to `ChatOpenAI()`. Also update response access from `.content` to `.text`.
| Before | After |
|--------|-------|
| `ChatOpenAI(model=..., base_url=..., api_key=...)` | `ChatOpenAI(model=..., base_url=..., api_key=..., use_responses_api=True)` |
| `result['messages'][-1].content` | `result['messages'][-1].text` |
For complete before/after code examples, see [cheat-sheet.md](./references/cheat-sheet.md).
---
## Frontend Migration Guidance
> **The Responses API is a server-side concern.** Migrate your Python backend; the frontend's HTTP contract should stay unchanged unless your backend is a thin pass-through — in that case, consider adopting the Responses request shape to eliminate a translation layer. If the frontend calls OpenAI directly with a client-side key, move those calls to a backend first.
### `@microsoft/ai-chat-protocol` deprecation
The `@microsoft/ai-chat-protocol` npm package is deprecated and should be replaced with [`ndjson-readablestream`](https://www.npmjs.com/package/ndjson-readablestream). If you encounter it in a frontend:
1. Replace the CDN script tag:
```html
```
2. Remove the `AIChatProtocolClient` instantiation (`new ChatProtocol.AIChatProtocolClient("/chat")`).
3. Replace `client.getStreamedCompletion(messages)` with a direct `fetch()` call to the backend streaming endpoint.
4. Replace `for await (const response of result)` with `for await (const chunk of readNDJSONStream(response.body))`.
5. Update property access from `response.delta.content` / `response.error` to `chunk.delta.content` / `chunk.error`.
---
## Goals
- Enumerate all Python call sites using Chat Completions or legacy Completions against Azure OpenAI.
- Propose a migration plan and sequencing for the Python codebase.
- Apply safe, minimal edits to switch to Responses API.
- Update callers to consume the Responses output schema; no backcompat wrappers.
- Run tests/lints; fix trivial breakages introduced by the migration.
- Prepare small, reviewable change sets and provide a final summary with diffs (do not commit).
---
## Guardrails
- Only modify files inside the git workspace. Never write outside.
- Do not preserve backward-compatibility shims; migrate code to the new API shape.
- Do not leave tombstone/transition comments or backup files.
- Preserve streaming semantics if previously used; otherwise use non-streaming.
- Ask for approval before running commands or network calls if in approval mode.
- Do not run `git add`/`git commit`/`git push`; produce working-tree edits only.
---
## Step 0: Azure OpenAI Client Migration (Prerequisite)
If the codebase uses `AzureOpenAI` or `AsyncAzureOpenAI` constructors, migrate to the standard `OpenAI` / `AsyncOpenAI` constructors first. The Azure-specific constructors are deprecated in `openai>=1.108.1`.
### Why the v1 API path?
The new `/openai/v1` endpoint uses the standard `OpenAI()` client instead of `AzureOpenAI()`, requires no `api_version` parameter, and works identically across OpenAI and Azure OpenAI. The same client code is future-proof — no version management needed.
### Key changes
| Before | After |
|--------|-------|
| `AzureOpenAI` | `OpenAI` |
| `AsyncAzureOpenAI` | `AsyncOpenAI` |
| `azure_endpoint` | `base_url` |
| `azure_ad_token_provider` | `api_key` |
| `api_version=...` | Remove entirely |
### Cleanup checklist
- Remove `api_version` argument from client construction.
- Remove `AZURE_OPENAI_VERSION` / `AZURE_OPENAI_API_VERSION` environment variables from `.env`, app settings, and Bicep/infra files.
- Rename `AZURE_OPENAI_CLIENT_ID` → `AZURE_CLIENT_ID` in `.env`, app settings, Bicep/infra, and test fixtures (standard Azure Identity SDK convention).
- Ensure `openai>=1.108.1` in `requirements.txt` or `pyproject.toml`.
### Environment variable migration
| Old env var | Action | Notes |
|-------------|--------|-------|
| `AZURE_OPENAI_VERSION` | **Remove** | No `api_version` needed with v1 endpoint |
| `AZURE_OPENAI_API_VERSION` | **Remove** | Same as above |
| `AZURE_OPENAI_CLIENT_ID` | **Rename** → `AZURE_CLIENT_ID` | Standard Azure Identity SDK convention for `ManagedIdentityCredential(client_id=...)` |
| `AZURE_OPENAI_ENDPOINT` | **Keep** | Still needed for `base_url` construction |
| `AZURE_OPENAI_CHAT_DEPLOYMENT` | **Keep** | Used as `model` param in `responses.create` |
| `AZURE_OPENAI_API_KEY` | **Keep** | Used as `api_key` for key-based auth |
For client setup code examples (sync, async, EntraID, API key, multi-tenant), see [cheat-sheet.md](./references/cheat-sheet.md).
---
## Step 1: Detect Legacy Call Sites
Run the [detect_legacy.py](./scripts/detect_legacy.py) script to find all call sites that need migration:
```bash
python skills/azure-openai-to-responses/scripts/detect_legacy.py .
```
Or run these searches manually — every match is a migration target:
```bash
# Legacy API calls (must rewrite)
rg "chat\.completions\.create"
rg "ChatCompletion\.create"
rg "Completion\.create"
# Deprecated Azure client constructors (must replace)
rg "AzureOpenAI\("
rg "AsyncAzureOpenAI\("
# Response shape access patterns (must update)
rg "choices\[0\]\.message\.content"
rg "choices\[0\]\.delta\.content"
rg "choices\[0\]\.message\.function_call"
rg "choices\[0\]\.message\.tool_calls"
# Tool definitions in old nested format (must flatten)
rg '"function":\s*{\s*"name"'
rg "pydantic_function_tool"
# Tool results in old format (must convert to function_call_output)
rg '"role":\s*"tool"'
rg '"tool_call_id"'
# Deprecated parameters (must remove or rename)
rg "response_format"
rg "max_tokens\b" # rename to max_output_tokens
rg "['\"]seed['\"]" # remove entirely
# Deprecated env vars (clean up)
rg "AZURE_OPENAI_API_VERSION|AZURE_OPENAI_VERSION"
rg "AZURE_OPENAI_CLIENT_ID" # should be AZURE_CLIENT_ID
# GitHub Models endpoints (must remove — Responses API not supported)
rg "models\.github\.ai|models\.inference\.ai\.azure"
# Framework-level legacy patterns (must update)
rg "OpenAIChatCompletionClient" # MAF 1.0.0+: replace with OpenAIChatClient
rg "ChatOpenAI\(" | grep -v "use_responses_api" # LangChain: needs use_responses_api=True
# Test infrastructure (must update)
rg "ChatCompletionChunk|AsyncCompletions\.create" tests/
rg "_azure_ad_token_provider" tests/
rg "prompt_filter_results|content_filter_results" tests/
rg "choices\[0\]" tests/
# Content filter error body access (must update — structure changed)
rg 'innererror.*content_filter_result|error\.body\["innererror"\]'
rg "content_filter_result\[" # old singular form — now content_filter_results (plural) inside content_filters array
# Raw HTTP calls to Chat Completions endpoint (must update URL)
rg "/openai/deployments/.*/chat/completions"
rg "api-version="
```
### Heuristics (detect and rewrite)
- **Chat Completions client**: `client.chat.completions.create` → `client.responses.create(...)`.
- **Azure client constructors**: `AzureOpenAI(...)` → `OpenAI(base_url=..., api_key=...)`.
- **Tools**: convert function-calling tool definitions from nested format (`{"type": "function", "function": {"name": ...}}`) to flat Responses format (`{"type": "function", "name": ...}`); use `tool_choice`; return tool results as `{"type": "function_call_output", "call_id": ..., "output": ...}` items (not `{"role": "tool", ...}`).
- **Tool round-trips**: when the model returns function calls, append `response.output` items to the conversation (not a manual `{"role": "assistant", "tool_calls": [...]}` dict), then append `function_call_output` items for each result.
- **Few-shot tool examples**: if the conversation includes hardcoded tool call examples, convert them to `{"type": "function_call", "id": "fc_...", "call_id": "fc_...", ...}` + `{"type": "function_call_output", ...}` items. IDs must start with `fc_`.
- **`pydantic_function_tool()`**: this helper still generates the old nested format and is **not compatible** with `responses.create()`. Replace with manual tool definitions or a flattening wrapper.
- **Multi-turn**: maintain conversation history in the app; pass prior turns via `input` items.
- **Formatting**: replace Chat's top-level `response_format` with `text.format` in Responses. Canonical shape: `text={"format": {"type": "json_schema", "name": "Output", "strict": True, "schema": {...}}}`.
- **Content items**: replace Chat `content[].type: "text"` with Responses `content[].type: "input_text"` for user/system turns.
- **Image content items**: replace Chat `content[].type: "image_url"` with Responses `content[].type: "input_image"`. The `image_url` field changes from a nested object `{"url": "..."}` to a flat string. See the cheat sheet for before/after examples.
- **Reasoning effort**: **only migrate `reasoning` if it already exists in the original code**.
- **Content filter error handling**: the error body structure changed. Chat Completions used `error.body["innererror"]["content_filter_result"]` (singular); Responses API uses `error.body["content_filters"][0]["content_filter_results"]` (plural, inside an array). Code that accesses `innererror` will raise `KeyError`. Rewrite to use the new path.
- **Raw HTTP calls**: if the app calls the Azure OpenAI REST API directly (via `requests`, `httpx`, etc.) using `/openai/deployments/{name}/chat/completions?api-version=...`, rewrite to `/openai/v1/responses`. The request body changes: `messages` → `input`, add `max_output_tokens` and `store: false`, remove `api-version` query param. The response body changes: `choices[0].message.content` → `output[0].content[0].text` (note: `output_text` is an SDK convenience property not present in raw REST JSON).
---
## Step 2: Apply Migration
### Migration notes (Chat Completions → Responses)
- **Why migrate**: Responses is the unified API for text, tools, and streaming; Chat Completions is legacy. With GPT-5, Responses is required for best performance.
- **HTTP**: Azure endpoint switches from `/openai/deployments/{name}/chat/completions` to `/openai/v1/responses`.
- **Fields**: `messages` → `input`, `max_tokens` → `max_output_tokens`. `temperature` remains.
- **Formatting**: `response_format` → `text.format` with a proper object.
- **Content items**: Replace Chat `content[].type: "text"` with Responses `content[].type: "input_text"` for system/user turns.
- **Image content items**: Replace Chat `content[].type: "image_url"` with Responses `content[].type: "input_image"`. Flatten the `image_url` field from `{"image_url": {"url": "..."}}` to `{"image_url": "..."}` (a plain string — either an HTTPS URL or a `data:image/...;base64,...` data URI).
### Parameter mapping reference
| Chat Completions | Responses API |
|-----------------|---------------|
| `prompt` | `input` |
| `messages` | `input` (array of items) |
| `max_tokens` | `max_output_tokens` |
| `response_format` | `text.format` (object) |
| `temperature` | `temperature` (unchanged) |
| `stop` | `stop` (unchanged) |
| `frequency_penalty` | `frequency_penalty` (unchanged) |
| `presence_penalty` | `presence_penalty` (unchanged) |
| `tools` / function-calling | `tools` (unchanged) |
| `seed` | **Remove** (not supported) |
| `store` | `store` (set to `false`) |
| `content[].type: "text"` | `content[].type: "input_text"` |
| `content[].type: "image_url"` | `content[].type: "input_image"` |
| `"image_url": {"url": "..."}` | `"image_url": "..."` (flat string) |
For complete before/after code examples, see [cheat-sheet.md](./references/cheat-sheet.md).
For test infrastructure migration (mocks, snapshots, assertions), see [test-migration.md](./references/test-migration.md).
For troubleshooting errors and gotchas, see [troubleshooting.md](./references/troubleshooting.md).
---
## Data Retention & State
- Set `store: false` on all Responses requests.
- Do not rely on previous message IDs or server-stored context; keep state client-managed and minimize metadata.
---
## Acceptance Criteria
### Code-level gates (all must pass)
- [ ] Zero matches for `rg "chat\.completions\.create|ChatCompletion\.create|Completion\.create"` in migrated files.
- [ ] Zero matches for `rg "AzureOpenAI\(|AsyncAzureOpenAI\("` — all constructors use `OpenAI`/`AsyncOpenAI` with the v1 endpoint.
- [ ] Zero matches for `rg "models\.github\.ai|models\.inference\.ai\.azure"` — GitHub Models code paths removed.
- [ ] Zero matches for `rg "OpenAIChatCompletionClient"` — MAF 1.0.0+ code uses `OpenAIChatClient` (which uses Responses API). In pre-1.0.0, upgrade to `agent-framework-openai>=1.0.0`.
- [ ] All `ChatOpenAI(...)` calls include `use_responses_api=True`.
- [ ] Zero matches for `rg "choices\[0\]"` — all response access uses `resp.output_text` or the Responses output schema.
- [ ] No `response_format` at top level; all structured output uses `text={"format": {...}}`.
- [ ] `openai>=1.108.1` and `azure-identity` in `requirements.txt` or `pyproject.toml`; dependencies reinstalled.
- [ ] `store=False` set on every `responses.create` call.
- [ ] No `api_version` in client construction; `AZURE_OPENAI_API_VERSION` removed from env files and infra.
### Test infrastructure gates (all must pass)
- [ ] Zero matches for `rg "ChatCompletionChunk|AsyncCompletions\.create|chat\.completions" tests/`.
- [ ] Zero matches for `rg "_azure_ad_token_provider" tests/` — assertions updated to check `isinstance(client, AsyncOpenAI)` or `base_url`.
- [ ] Zero matches for `rg "prompt_filter_results|content_filter_results" tests/` — Azure-specific filter mocks removed.
- [ ] Mock fixtures use `kwargs.get("input")` not `kwargs.get("messages")`.
- [ ] Snapshot / golden files updated to Responses streaming shape (no `choices[0]`, `function_call`, `logprobs`, etc.).
- [ ] `pytest` passes with zero failures after all test updates.
### Behavioral gates (verify manually or via test harness)
- [ ] **Basic completion**: non-streaming `responses.create` returns non-empty `output_text`.
- [ ] **Stream parity**: if the original code used streaming, the migrated code streams and yields `response.output_text.delta` events with non-empty deltas.
- [ ] **Structured output**: if using `text.format` with `json_schema`, `json.loads(resp.output_text)` succeeds and matches the schema.
- [ ] **Tool-call loop**: if tools are used, the model issues tool calls, the app executes them, and the follow-up request returns a final `output_text` (no infinite loop).
- [ ] **Async parity**: if `AsyncAzureOpenAI` was used, `AsyncOpenAI` equivalent works with `await`.
- [ ] **Error rate**: no new 400/401/404 errors compared to the pre-migration baseline.
### Deliverables
- Summary includes edited files, before/after counts of legacy call sites, and next steps.
- Changes are working-tree edits only (no commits).
---
## SDK Version Requirements
| Package | Minimum Version |
|---------|----------------|
| `openai` | `>=1.108.1` |
| `azure-identity` | Latest (for EntraID auth) |
---
## References
- [Cheat Sheet — all code snippets](./references/cheat-sheet.md)
- [Test Migration — mocks, snapshots, assertions](./references/test-migration.md)
- [Troubleshooting — errors, risk table, gotchas](./references/troubleshooting.md)
- [detect_legacy.py — automated scanner](./scripts/detect_legacy.py)
- [Azure OpenAI Starter Kit](https://aka.ms/openai/start)
- [Azure OpenAI Responses API docs](https://learn.microsoft.com/en-us/azure/ai-foundry/openai/how-to/responses)
- [Azure OpenAI API version lifecycle](https://learn.microsoft.com/en-us/azure/ai-foundry/openai/api-version-lifecycle?view=foundry-classic&tabs=python#api-evolution)
- [OpenAI Responses API reference](https://platform.openai.com/docs/api-reference/responses)