AI engineering

Migrate from the Azure OpenAI Assistants API to Foundry Agent Service

Move code from the retired Azure OpenAI Assistants API to Foundry Agent Service: assistants to agents, threads to conversations, runs to responses, plus exporting thread history.

10 min read
On this page

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 APIFoundry Agent ServiceNotes
Assistant (asst_...)Agent version (name:version)Created on the project client with agents.create_version()
ThreadConversationHolds items: messages, tool calls, tool outputs and more
Message added to a threadItem added to a conversationconversations.items.create()
Run plus status pollingResponseresponses.create() returns output items; tool call loops are managed explicitly
additional_instructions on a runInput on the responseMicrosoft'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:

ToolClassicNew agents
Code InterpreterGAGA
File SearchGAGA
FunctionGAGA
OpenAPIGAGA
Azure AI SearchGAGA
Grounding with Bing SearchGAGA
Azure FunctionsGANot available
Connected AgentsPreviewNot available; Agent to Agent (A2A) tool recommended
Deep ResearchPreviewNot available; Deep Research model with Web Search tool recommended
MCPPreviewGA
Web SearchNot availableGA

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-projects 2.3.0 or later and azure-identity; sign in with az login or use DefaultAzureCredential.
  • 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-identity

Step 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:

FlagEffect
--listShows what would be migrated, changes nothing
--only-with-tools or --only-without-toolsFilters assistants by whether they use tools
asst_... ID as an argumentMigrates one assistant
--source-resource-id with --resource-idReads from one project and writes to another
--no-migrate-filesDefinitions 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:

  1. Run the updated code and confirm it executes without errors.
  2. Confirm create_version() returns an object with an id and a version.
  3. Create a conversation, send a response and check that output items come back.
  4. 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

SymptomCauseFix
AttributeError: 'AIProjectClient' has no attribute 'conversations'Conversations called on the project clientUse project.get_openai_client() and call openai.conversations.create()
create_agent() not foundRemoved in SDK 2.0.0Use create_version() with a PromptAgentDefinition
Old thread data isn't available in the new APIState isn't migratedStart new conversations; export history through the previous API while it responds
responses.create() raises a model errorWrong model name or model not available in the regionCheck the deployment name and regional availability in the project
Migration tool returns 401 or 403Missing roleAssign Foundry User on the AI Services resource
File search returns nothing after migrationRan with --no-migrate-files, or files rejectedRe-upload files, check migration_failed_files.json
Agent relied on Azure Functions or Connected AgentsTool not available in new agentsMove 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.

References

Questions people ask

Is the Azure OpenAI Assistants API retired?

Yes. Microsoft's Assistants documentation now states that the Assistants API is retired and directs workloads to the generally available Foundry Agent Service, with a migration guide that maps assistants, threads and runs to the new agents, conversations and responses.

What replaces threads and runs in Foundry Agent Service?

Threads become conversations, which store items such as messages, tool calls and tool outputs. Runs become responses: you send input items or reference a conversation and get output items back, without polling a run status. Agent definitions become versioned agents created with create_version.

Does the Foundry migration tool move my old threads and messages?

No. Microsoft states that the migration tool converts agent and assistant definitions and related code constructs but doesn't migrate state data such as past runs, threads or messages. The tool's README describes copying referenced files and recreating file search vector stores. Export any thread history you need to keep separately.

Can I use the Responses API without creating an agent?

Yes. The Azure OpenAI Responses API works directly against a model deployment through the v1 endpoint, and you can chain turns with previous_response_id. Stored response data is retained for 30 days by default and can be deleted by ID.

Azure OpenAIFoundry Agent ServiceResponses APIMicrosoft Foundry
  1. Azure OpenAI Deployment Types: Global Standard vs Data Zone vs Provisioned

    Choose between Global Standard, Data Zone, Standard and Provisioned deployments in Azure OpenAI based on where data is processed, latency variance, throughput guarantees and billing model.

    AI engineering12 min read
  2. Azure OpenAI model retirement runbook: avoid 410 errors, upgrade safely

    Track Azure OpenAI model retirement dates, set versionUpgradeOption, validate the replacement model and migrate provisioned deployments before the cutoff.

    AI engineering11 min read
  3. Fix Azure OpenAI 429 Too Many Requests: TPM, RPM and PTU limits

    Diagnose Azure OpenAI 429 errors on Standard and provisioned deployments and fix them with quota changes, max_tokens tuning, correct retries and spillover.

    AI engineering14 min read