The Azure OpenAI Assistants API is retired, and Microsoft's replacement is Foundry Agent Service, which is built on the Responses API. Migrating means recreating each assistant as a versioned agent with create_version, replacing threads with conversations and runs with responses, checking that every tool you used still exists, and exporting any thread history you need yourself, because the migration tool converts definitions and files but not past threads, messages or runs. If you used assistants only for stateful chat, the Responses API on its own, with previous_response_id, is often enough.
Who this is for and what you will have
This guide is for developers who built on client.beta.assistants, client.beta.threads and runs in the OpenAI Python library against Azure OpenAI, or on classic Foundry agents with create_agent, and now need a supported path. At the end you will have:
- A mapping from every Assistants concept you use to its Foundry Agent Service equivalent.
- An export of thread history that the migration does not carry over.
- Assistant definitions recreated as agents, by the migration tool or by hand.
- Application code that uses conversations and responses, verified end to end.
What changes
| Assistants API | Foundry Agent Service | Notes |
|---|---|---|
Assistant (asst_...) | Agent version (name:version) | Created on the project client with agents.create_version() |
| Thread | Conversation | Holds items: messages, tool calls, tool outputs and more |
| Message added to a thread | Item added to a conversation | conversations.items.create() |
| Run plus status polling | Response | responses.create() returns output items; tool call loops are managed explicitly |
additional_instructions on a run | Input on the response | Microsoft's example passes it in the response input |
New agents add capabilities the Assistants API never had: web search, MCP tool calling, image generation, reasoning summaries, background mode for long-running work, and single-tenant storage with the option to bring your own Azure Cosmos DB account for state. Microsoft states that new features and model support are only added to the new agents.
Not every tool carries over. Check yours against this subset of Microsoft's availability table:
| Tool | Classic | New agents |
|---|---|---|
| Code Interpreter | GA | GA |
| File Search | GA | GA |
| Function | GA | GA |
| OpenAPI | GA | GA |
| Azure AI Search | GA | GA |
| Grounding with Bing Search | GA | GA |
| Azure Functions | GA | Not available |
| Connected Agents | Preview | Not available; Agent to Agent (A2A) tool recommended |
| Deep Research | Preview | Not available; Deep Research model with Web Search tool recommended |
| MCP | Preview | GA |
| Web Search | Not available | GA |
Prerequisites
- An Azure subscription and a Microsoft Foundry project. The project endpoint has the form
https://resource_name.services.ai.azure.com/api/projects/project_name. - Python package
azure-ai-projects2.3.0 or later andazure-identity; sign in withaz loginor useDefaultAzureCredential. - For the migration tool: Docker Desktop and Azure CLI, or Python 3.10 or later and Azure CLI, and the Foundry User role on the AI Services resource for the signed-in user. Without that role the tool fails with 401 or 403; granting it requires Owner or User Access Administrator on the resource.
- Your application's own records of thread IDs, if you need to export history.
pip install "azure-ai-projects>=2.3.0" azure-identityStep 1: Export thread history you must keep
The migration tool doesn't migrate state data such as past runs, threads or messages; new conversations start empty. Microsoft's guide says historical data stays accessible through the previous API until it is deprecated, so export what you need while those calls still succeed, and test them against your resource first.
Exports start from the thread IDs your application stored. For each thread, list its messages and write them to JSONL, and download files that tools generated. The example uses the client.beta.threads surface of the OpenAI Python library, so run it with the library version your existing Assistants code already uses:
import json
import os
from openai import AzureOpenAI
client = AzureOpenAI(
api_key=os.getenv("AZURE_OPENAI_API_KEY"),
api_version="2024-08-01-preview",
azure_endpoint=os.getenv("AZURE_OPENAI_ENDPOINT"),
)
thread_ids = ["thread_6bunpoBRZwNhovwzYo7fhNVd"] # from your application database
with open("assistants-export.jsonl", "w", encoding="utf-8") as out:
for thread_id in thread_ids:
for message in client.beta.threads.messages.list(thread_id=thread_id):
out.write(json.dumps(message.model_dump()) + "\n")
for block in message.content:
if block.type == "image_file":
file_id = block.image_file.file_id
client.files.content(file_id).write_to_file(f"{file_id}.png")Store the export with the same retention and access controls as the rest of your conversation data. If a user needs to continue an old conversation, you can seed a new conversation with selected messages as items rather than replaying the entire history.
Step 2: Run the migration tool for definitions
Microsoft publishes a migration tool in the foundry-samples GitHub repository (linked from the migration guide as aka.ms/agent/migrate/tool). It reads classic assistants (asst_... items) and creates named agents. By default it also downloads referenced files, re-uploads them to the target Foundry endpoint, recreates file_search vector stores and rewrites the IDs in the new agent definitions.
Get the project's resource ID from Project settings in the Foundry portal; it has the form /subscriptions/{sub}/resourceGroups/{rg}/providers/Microsoft.CognitiveServices/accounts/{resource}/projects/{project}. Then run a read-only listing before migrating anything:
cd migration
.\migrate-docker.ps1 --resource-id "<RESOURCE_ID>" --list
.\migrate-docker.ps1 --resource-id "<RESOURCE_ID>"Without Docker, install the requirements and use migrate.ps1, or migrate.sh on Linux and macOS, with the same flags:
pip install -r requirements.txt
./migrate.sh --resource-id "<RESOURCE_ID>" --list
./migrate.sh --resource-id "<RESOURCE_ID>"Useful flags from the tool's README:
| Flag | Effect |
|---|---|
--list | Shows what would be migrated, changes nothing |
--only-with-tools or --only-without-tools | Filters assistants by whether they use tools |
asst_... ID as an argument | Migrates one assistant |
--source-resource-id with --resource-id | Reads from one project and writes to another |
--no-migrate-files | Definitions only; file_search and code_interpreter stay broken until you re-upload files |
The tool doesn't filter files by type or size. The target API enforces limits such as a 512 MB maximum file size and 5,000,000 tokens per file, and rejected files are logged in migration_failed_files.json without retry. Files that no assistant references are left alone.
Step 3: Recreate agent definitions in code
Even if the tool migrated your assistants, your deployment pipeline should create agents from code. Initialise both clients once: the project client for agents, and the OpenAI client it returns for conversations and responses.
from azure.identity import DefaultAzureCredential
from azure.ai.projects import AIProjectClient
from azure.ai.projects.models import CodeInterpreterTool, PromptAgentDefinition
project = AIProjectClient(
endpoint="https://contoso-ai.services.ai.azure.com/api/projects/support-bot",
credential=DefaultAzureCredential(),
)
openai = project.get_openai_client()
agent = project.agents.create_version(
agent_name="data-viz",
definition=PromptAgentDefinition(
model="gpt-4.1",
instructions="You create clear visualizations from the data provided. "
"Use the Code Interpreter tool to run and test your code.",
tools=[CodeInterpreterTool()],
),
)
print(agent.id, agent.version)This replaces client.beta.assistants.create(...). If you used classic Foundry agents, create_agent() was removed in version 2.0.0 of the SDK and create_version() is its replacement. Each call creates a new version, so changes to instructions or tools become traceable versions instead of in-place edits.
Step 4: Replace threads with conversations
Where your code created a thread and added messages, create a conversation with initial items, then add follow-up items:
conversation = openai.conversations.create(
items=[{"type": "message", "role": "user",
"content": "Plot monthly ticket volume for the last year."}],
metadata={"agent": "data-viz"},
)
openai.conversations.items.create(
conversation_id=conversation.id,
items=[{"type": "message", "role": "user",
"content": "Use a dark background."}],
)Conversation IDs start with conv_ instead of thread_. Update database columns, logs and any code that parses IDs.
Step 5: Replace runs with responses
The old pattern created a run and polled until the status left queued or in_progress. A response call replaces both, referencing the agent by name:
response = openai.responses.create(
conversation=conversation.id,
input="Address the user formally. The user has a premium account.",
extra_body={"agent_reference": {"name": agent.name, "type": "agent_reference"}},
)
for item in response.output:
if item.type == "message":
for block in item.content:
print(block.text)Read results from response.output items instead of listing thread messages after a run. With function tools, function calls come back as output items and your code executes them and returns the results in a follow-up request; Microsoft describes this as tool call loops being explicitly managed. Remove the polling loop, the requires_action handling and run-expiry logic that the Assistants API needed.
When you don't need an agent
If your assistant had no tools and existed only to keep conversation state, call the Responses API directly against a deployment through the v1 endpoint and chain turns with previous_response_id:
import os
from openai import OpenAI
client = OpenAI(
api_key=os.getenv("AZURE_OPENAI_API_KEY"),
base_url="https://contoso-aoai.openai.azure.com/openai/v1/",
)
first = client.responses.create(model="gpt-4.1-prod", input="Define catastrophic forgetting.")
second = client.responses.create(
model="gpt-4.1-prod",
previous_response_id=first.id,
input="Explain it for a college freshman.",
)
print(second.output_text)Response data is retained for 30 days by default. Delete a stored response with client.responses.delete(response_id) when your retention policy requires it.
Verify the migration
Microsoft's verification steps, in order:
- Run the updated code and confirm it executes without errors.
- Confirm
create_version()returns an object with anidand aversion. - Create a conversation, send a response and check that output items come back.
- Send several responses to the same conversation and confirm the agent remembers earlier messages.
Add your own checks for tool calls, file outputs and error handling, and compare answers for a fixed set of prompts between the old and new implementation. If the migration also moves you to a newer model, use the evaluation approach in the Azure OpenAI model retirement runbook so you can separate API changes from model changes. For the wider operating model around agents and RAG, see production LLMOps for enterprise RAG.
Troubleshooting
| Symptom | Cause | Fix |
|---|---|---|
AttributeError: 'AIProjectClient' has no attribute 'conversations' | Conversations called on the project client | Use project.get_openai_client() and call openai.conversations.create() |
create_agent() not found | Removed in SDK 2.0.0 | Use create_version() with a PromptAgentDefinition |
| Old thread data isn't available in the new API | State isn't migrated | Start new conversations; export history through the previous API while it responds |
responses.create() raises a model error | Wrong model name or model not available in the region | Check the deployment name and regional availability in the project |
| Migration tool returns 401 or 403 | Missing role | Assign Foundry User on the AI Services resource |
| File search returns nothing after migration | Ran with --no-migrate-files, or files rejected | Re-upload files, check migration_failed_files.json |
| Agent relied on Azure Functions or Connected Agents | Tool not available in new agents | Move to function or OpenAPI tools, or the A2A tool |
Checklist
- List every assistant, its model, tools and files.
- Export thread history you are required to keep, starting from stored thread IDs.
- Run the migration tool with
--list, then migrate. - Recreate agents from code with
create_version()and version them. - Replace threads with conversations and runs with responses; delete polling code.
- Replace tools that are not available in new agents.
- Verify state retention, tool calls and outputs before switching production traffic.