Azure AI Persistent Agents for .NET: Setup, Usage & Best Practices
The Azure AI Persistent Agents SDK for .NET provides low-level control over persistent AI agents with explicit thread management, message handling, and run execution. This SDK gives you granular access to the agent lifecycle—creating agents with specific tools, managing conversation threads, polling or streaming run execution, and handling function calls manually.
What This Skill Does
This SDK operates at a lower abstraction level than high-level agent frameworks. You explicitly create agents via PersistentAgentsClient.Administration, manage conversation threads through the Threads API, add messages to threads, create runs to execute agents against threads, and poll or stream for completion. The client hierarchy separates concerns: Administration for agent CRUD, Threads for conversation management, Messages for thread content, Runs for execution, Files for uploads, and VectorStores for semantic search.
Agents persist on Azure AI Foundry and can be reused across multiple threads and sessions. You define an agent once with model, instructions, and tools, then reference it by ID whenever you need it. Threads represent conversations, maintaining message history and context. When you create a run, the agent processes the thread's messages using its configured tools—code interpreter, file search, function calling, Bing search, Azure AI Search, or external APIs via OpenAPI specs.
The execution model supports both polling and streaming. Polling checks run status periodically until completion, useful for batch processing or background jobs. Streaming delivers updates in real-time as tokens arrive, ideal for interactive chat interfaces where users expect immediate feedback. Function calling requires manual handling: when a run enters RequiresAction status, you execute the requested functions, submit outputs, and resume the run.
Getting Started
Install the SDK via NuGet:
dotnet add package Azure.AI.Agents.Persistent --prerelease
dotnet add package Azure.Identity
Configure environment variables:
export PROJECT_ENDPOINT=https://<resource>.services.ai.azure.com/api/projects/<project>
export MODEL_DEPLOYMENT_NAME=gpt-4o-mini
export AZURE_BING_CONNECTION_ID=<bing-connection-resource-id>
Authenticate using Azure's identity libraries:
using Azure.AI.Agents.Persistent;
using Azure.Identity;
var client = new PersistentAgentsClient(
Environment.GetEnvironmentVariable("PROJECT_ENDPOINT"),
new DefaultAzureCredential()
);
In development, DefaultAzureCredential uses az login. In production, it supports managed identities, service principals, and other Azure authentication methods.
Key Features
Low-Level Control: Explicitly manage every step of the agent lifecycle. Create agents, threads, messages, and runs individually. This granularity enables custom workflows, advanced error handling, and integration with complex business logic.
Multi-Tool Support: Equip agents with code interpreters for Python execution, file search via vector stores, custom function calling, Bing web search, Azure AI Search indexes, OpenAPI endpoints, Azure Functions, MCP tools, SharePoint connectors, and Microsoft Fabric data access.
Streaming and Polling: Choose your execution model. Polling with CreateRunAsync and status checks suits batch operations. Streaming with CreateRunStreamingAsync delivers token-level updates for real-time interfaces.
Function Calling: Define custom functions as FunctionToolDefinition with JSON schemas for parameters. When runs enter RequiresAction status, extract tool calls, execute functions, create ToolOutput objects, and submit via SubmitToolOutputsToRunAsync.
Vector Store Integration: Upload files, create vector stores, and attach them to agents with file search tools. Query documents semantically during agent runs without manual embedding management.
Conversation Persistence: Threads maintain message history across runs. Create a thread once, run multiple agents against it, and the conversation context persists. Store thread IDs in databases to resume conversations later.
Usage Examples
Basic Agent with Code Interpreter: Create an agent that executes Python code:
var agent = await client.Administration.CreateAgentAsync(
model: Environment.GetEnvironmentVariable("MODEL_DEPLOYMENT_NAME"),
name: "Math Tutor",
instructions: "You are a math tutor. Use code to solve equations.",
tools: [new CodeInterpreterToolDefinition()]
);
var thread = await client.Threads.CreateThreadAsync();
await client.Messages.CreateMessageAsync(
thread.Id,
MessageRole.User,
"Solve: 3x + 11 = 14"
);
var run = await client.Runs.CreateRunAsync(thread.Id, agent.Id);
// Poll for completion
while (run.Status == RunStatus.Queued || run.Status == RunStatus.InProgress)
{
await Task.Delay(500);
run = await client.Runs.GetRunAsync(thread.Id, run.Id);
}
// Retrieve response
await foreach (var message in client.Messages.GetMessagesAsync(thread.Id))
{
Console.WriteLine($"{message.Role}: {message.ContentItems[0]}");
}
Streaming Response: Deliver tokens in real-time:
var stream = client.Runs.CreateRunStreamingAsync(thread.Id, agent.Id);
await foreach (var update in stream)
{
if (update is MessageContentUpdate contentUpdate)
Console.Write(contentUpdate.Text);
else if (update.UpdateKind == StreamingUpdateReason.RunCompleted)
Console.WriteLine("\n--- Complete ---");
}
Function Calling: Define and handle custom function tools:
var weatherTool = new FunctionToolDefinition(
name: "getCurrentWeather",
description: "Gets weather for a location.",
parameters: BinaryData.FromObjectAsJson(new {
Type = "object",
Properties = new {
Location = new { Type = "string", Description = "City and state" }
},
Required = new[] { "location" }
})
);
var agent = await client.Administration.CreateAgentAsync(
model: modelDeploymentName,
tools: [weatherTool]
);
// During run execution
if (run.Status == RunStatus.RequiresAction
&& run.RequiredAction is SubmitToolOutputsAction submitAction)
{
var outputs = new List<ToolOutput>();
foreach (var toolCall in submitAction.ToolCalls)
{
if (toolCall is RequiredFunctionToolCall funcCall)
{
var result = GetWeather(funcCall.Arguments); // Your function
outputs.Add(new ToolOutput(toolCall, result));
}
}
run = await client.Runs.SubmitToolOutputsToRunAsync(run, outputs);
}
File Search with Vector Store: Enable document querying:
var file = await client.Files.UploadFileAsync("document.txt", PersistentAgentFilePurpose.Agents);
var vectorStore = await client.VectorStores.CreateVectorStoreAsync(
fileIds: [file.Id],
name: "my_docs"
);
var fileSearchResource = new FileSearchToolResource();
fileSearchResource.VectorStoreIds.Add(vectorStore.Id);
var agent = await client.Administration.CreateAgentAsync(
model: modelDeploymentName,
tools: [new FileSearchToolDefinition()],
toolResources: new ToolResources { FileSearch = fileSearchResource }
);
Best Practices
Always Dispose Clients: Use using statements or explicit disposal to clean up HTTP connections and resources properly.
Poll with Appropriate Delays: Check run status every 500ms. Faster polling wastes API calls; slower polling delays response delivery.
Handle All Run Statuses: Check for Queued, InProgress, RequiresAction, Completed, Failed, and Cancelled. Don't assume success.
Clean Up Resources: Delete threads, agents, vector stores, and files when finished. Orphaned resources consume quota and clutter listings.
Store IDs Not Objects: Reference agents and threads by ID. Serialize IDs to databases or configuration, not entire objects.
Use Async Methods: All SDK operations are async. Blocking on async methods (.Result, .Wait()) causes deadlocks in ASP.NET and UI frameworks.
Stream for UX: Streaming delivers better user experience in chat interfaces. Users perceive faster response times when seeing incremental output.
When to Use / When NOT to Use
Use this SDK when:
- You need low-level control over agent execution flow
- You're integrating agents into complex business logic
- You need custom error handling or retry logic
- You're building .NET applications targeting Azure AI
- You want to manage threads and runs explicitly
- You need detailed visibility into agent execution steps
- You're implementing custom function calling workflows
Avoid this SDK when:
- You want high-level abstractions (use Agent Framework instead)
- You're building simple chatbots without custom tools
- You don't need conversation persistence (use Azure OpenAI directly)
- You're working in Python or Java (use language-specific SDKs)
- You prefer declarative over imperative agent configuration
- You need minimal code for standard agent patterns
Related Skills
- agent-framework-azure-ai-py: High-level Python agent framework
- azure-ai-agents-persistent-java: Java version of this SDK
- azure-ai-projects-dotnet: .NET SDK for Azure AI project management
Source
Maintained by Microsoft. View on GitHub