azure-ai-agents-persistent-dotnet
Azure AI Agents Persistent SDK for .NET. Low-level SDK for creating and managing AI agents with threads, messages, runs, and tools. Use for agent CRUD, conversation threads, streaming responses, function calling, file search, and code interpreter. Triggers: "PersistentAgentsClien
Install
npx skills add https://github.com/microsoft/skills/tree/main/.github/plugins/azure-sdk-dotnet/skills/azure-ai-agents-persistent-dotnet
claude plugin marketplace add https://llmmart.ai/marketplace.json && claude plugin install microsoft-skills@llmmart
git clone https://github.com/microsoft/skills.git
The skills CLI installs just this skill, for any of its supported agents. Claude Code installs the whole microsoft/skills collection as a plugin from our marketplace. Git is the plain clone.
Skill manifest
Azure.AI.Agents.Persistent (.NET)
Low-level SDK for creating and managing persistent AI agents with threads, messages, runs, and tools.
Installation
dotnet add package Azure.AI.Agents.Persistent --prerelease
dotnet add package Azure.Identity
Current Versions: Stable v1.1.0, Preview v1.2.0-beta.8
Environment Variables
PROJECT_ENDPOINT=https://<resource>.services.ai.azure.com/api/projects/<project> # Required: Azure AI project endpoint
MODEL_DEPLOYMENT_NAME=gpt-4o-mini # Required: model deployment name
AZURE_BING_CONNECTION_ID=<bing-connection-resource-id> # Required: Bing connection resource ID
AZURE_AI_SEARCH_CONNECTION_ID=<search-connection-resource-id> # Required: Azure AI Search connection resource ID
AZURE_TOKEN_CREDENTIALS=prod # Required only if DefaultAzureCredential is used in production
Authentication
using Azure.AI.Agents.Persistent;
using Azure.Identity;
var projectEndpoint = Environment.GetEnvironmentVariable("PROJECT_ENDPOINT");
// Local dev: DefaultAzureCredential. Production: set AZURE_TOKEN_CREDENTIALS=prod or AZURE_TOKEN_CREDENTIALS=<specific_credential>
var credential = new DefaultAzureCredential(
DefaultAzureCredential.DefaultEnvironmentVariableName
);
// Or use a specific credential directly in production:
// See https://learn.microsoft.com/dotnet/api/overview/azure/identity-readme?view=azure-dotnet#credential-classes
// var credential = new ManagedIdentityCredential();
PersistentAgentsClient client = new(projectEndpoint, credential);
Client Hierarchy
PersistentAgentsClient
├── Administration → Agent CRUD operations
├── Threads → Thread management
├── Messages → Message operations
├── Runs → Run execution and streaming
├── Files → File upload/download
└── VectorStores → Vector store management
Core Workflow
1. Create Agent
var modelDeploymentName = Environment.GetEnvironmentVariable("MODEL_DEPLOYMENT_NAME");
PersistentAgent agent = await client.Administration.CreateAgentAsync(
model: modelDeploymentName,
name: "Math Tutor",
instructions: "You are a personal math tutor. Write and run code to answer math questions.",
tools: [new CodeInterpreterToolDefinition()]
);
2. Create Thread and Message
// Create thread
PersistentAgentThread thread = await client.Threads.CreateThreadAsync();
// Create message
await client.Messages.CreateMessageAsync(
thread.Id,
MessageRole.User,
"I need to solve the equation `3x + 11 = 14`. Can you help me?"
);
3. Run Agent (Polling)
// Create run
ThreadRun run = await client.Runs.CreateRunAsync(
thread.Id,
agent.Id,
additionalInstructions: "Please address the user as Jane Doe."
);
// Poll for completion
do
{
await Task.Delay(TimeSpan.FromMilliseconds(500));
run = await client.Runs.GetRunAsync(thread.Id, run.Id);
}
while (run.Status == RunStatus.Queued || run.Status == RunStatus.InProgress);
// Retrieve messages
await foreach (PersistentThreadMessage message in client.Messages.GetMessagesAsync(
threadId: thread.Id,
order: ListSortOrder.Ascending))
{
Console.Write($"{message.Role}: ");
foreach (MessageContent content in message.ContentItems)
{
if (content is MessageTextContent textContent)
Console.WriteLine(textContent.Text);
}
}
4. Streaming Response
AsyncCollectionResult<StreamingUpdate> stream = client.Runs.CreateRunStreamingAsync(
thread.Id,
agent.Id
);
await foreach (StreamingUpdate update in stream)
{
if (update.UpdateKind == StreamingUpdateReason.RunCreated)
{
Console.WriteLine("--- Run started! ---");
}
else if (update is MessageContentUpdate contentUpdate)
{
Console.Write(contentUpdate.Text);
}
else if (update.UpdateKind == StreamingUpdateReason.RunCompleted)
{
Console.WriteLine("\n--- Run completed! ---");
}
}
5. Function Calling
// Define function tool
FunctionToolDefinition weatherTool = new(
name: "getCurrentWeather",
description: "Gets the current weather at a location.",
parameters: BinaryData.FromObjectAsJson(new
{
Type = "object",
Properties = new
{
Location = new { Type = "string", Description = "City and state, e.g. San Francisco, CA" },
Unit = new { Type = "string", Enum = new[] { "c", "f" } }
},
Required = new[] { "location" }
}, new JsonSerializerOptions { PropertyNamingPolicy = JsonNamingPolicy.CamelCase })
);
// Create agent with function
PersistentAgent agent = await client.Administration.CreateAgentAsync(
model: modelDeploymentName,
name: "Weather Bot",
instructions: "You are a weather bot.",
tools: [weatherTool]
);
// Handle function calls during polling
do
{
await Task.Delay(500);
run = await client.Runs.GetRunAsync(thread.Id, run.Id);
if (run.Status == RunStatus.RequiresAction
&& run.RequiredAction is SubmitToolOutputsAction submitAction)
{
List<ToolOutput> outputs = [];
foreach (RequiredToolCall toolCall in submitAction.ToolCalls)
{
if (toolCall is RequiredFunctionToolCall funcCall)
{
// Execute function and get result
string result = ExecuteFunction(funcCall.Name, funcCall.Arguments);
outputs.Add(new ToolOutput(toolCall, result));
}
}
run = await client.Runs.SubmitToolOutputsToRunAsync(run, outputs, toolApprovals: null);
}
}
while (run.Status == RunStatus.Queued || run.Status == RunStatus.InProgress);
6. File Search with Vector Store
// Upload file
PersistentAgentFileInfo file = await client.Files.UploadFileAsync(
filePath: "document.txt",
purpose: PersistentAgentFilePurpose.Agents
);
// Create vector store
PersistentAgentsVectorStore vectorStore = await client.VectorStores.CreateVectorStoreAsync(
fileIds: [file.Id],
name: "my_vector_store"
);
// Create file search resource
FileSearchToolResource fileSearchResource = new();
fileSearchResource.VectorStoreIds.Add(vectorStore.Id);
// Create agent with file search
PersistentAgent agent = await client.Administration.CreateAgentAsync(
model: modelDeploymentName,
name: "Document Assistant",
instructions: "You help users find information in documents.",
tools: [new FileSearchToolDefinition()],
toolResources: new ToolResources { FileSearch = fileSearchResource }
);
7. Bing Grounding
var bingConnectionId = Environment.GetEnvironmentVariable("AZURE_BING_CONNECTION_ID");
BingGroundingToolDefinition bingTool = new(
new BingGroundingSearchToolParameters(
[new BingGroundingSearchConfiguration(bingConnectionId)]
)
);
PersistentAgent agent = await client.Administration.CreateAgentAsync(
model: modelDeploymentName,
name: "Search Agent",
instructions: "Use Bing to answer questions about current events.",
tools: [bingTool]
);
8. Azure AI Search
AzureAISearchToolResource searchResource = new(
connectionId: searchConnectionId,
indexName: "my_index",
topK: 5,
filter: "category eq 'documentation'",
queryType: AzureAISearchQueryType.Simple
);
PersistentAgent agent = await client.Administration.CreateAgentAsync(
model: modelDeploymentName,
name: "Search Agent",
instructions: "Search the documentation index to answer questions.",
tools: [new AzureAISearchToolDefinition()],
toolResources: new ToolResources { AzureAISearch = searchResource }
);
9. Cleanup
await client.Threads.DeleteThreadAsync(thread.Id);
await client.Administration.DeleteAgentAsync(agent.Id);
await client.VectorStores.DeleteVectorStoreAsync(vectorStore.Id);
await client.Files.DeleteFileAsync(file.Id);
Available Tools
| Tool | Class | Purpose |
|---|---|---|
| Code Interpreter | CodeInterpreterToolDefinition |
Execute Python code, generate visualizations |
| File Search | FileSearchToolDefinition |
Search uploaded files via vector stores |
| Function Calling | FunctionToolDefinition |
Call custom functions |
| Bing Grounding | BingGroundingToolDefinition |
Web search via Bing |
| Azure AI Search | AzureAISearchToolDefinition |
Search Azure AI Search indexes |
| OpenAPI | OpenApiToolDefinition |
Call external APIs via OpenAPI spec |
| Azure Functions | AzureFunctionToolDefinition |
Invoke Azure Functions |
| MCP | MCPToolDefinition |
Model Context Protocol tools |
| SharePoint | SharepointToolDefinition |
Access SharePoint content |
| Microsoft Fabric | MicrosoftFabricToolDefinition |
Access Fabric data |
Streaming Update Types
| Update Type | Description |
|---|---|
StreamingUpdateReason.RunCreated |
Run started |
StreamingUpdateReason.RunInProgress |
Run processing |
StreamingUpdateReason.RunCompleted |
Run finished |
StreamingUpdateReason.RunFailed |
Run errored |
MessageContentUpdate |
Text content chunk |
RunStepUpdate |
Step status change |
Key Types Reference
| Type | Purpose |
|---|---|
PersistentAgentsClient |
Main entry point |
PersistentAgent |
Agent with model, instructions, tools |
PersistentAgentThread |
Conversation thread |
PersistentThreadMessage |
Message in thread |
ThreadRun |
Execution of agent against thread |
RunStatus |
Queued, InProgress, RequiresAction, Completed, Failed |
ToolResources |
Combined tool resources |
ToolOutput |
Function call response |
Best Practices
- Always dispose clients — Use
usingstatements or explicit disposal - Poll with appropriate delays — 500ms recommended between status checks
- Clean up resources — Delete threads and agents when done
- Handle all run statuses — Check for
RequiresAction,Failed,Cancelled - Use streaming for real-time UX — Better user experience than polling
- Store IDs not objects — Reference agents/threads by ID
- Use async methods — All operations should be async
Error Handling
using Azure;
try
{
var agent = await client.Administration.CreateAgentAsync(...);
}
catch (RequestFailedException ex) when (ex.Status == 404)
{
Console.WriteLine("Resource not found");
}
catch (RequestFailedException ex)
{
Console.WriteLine($"Error: {ex.Status} - {ex.ErrorCode}: {ex.Message}");
}
Related SDKs
| SDK | Purpose | Install |
|---|---|---|
Azure.AI.Agents.Persistent |
Low-level agents (this SDK) | dotnet add package Azure.AI.Agents.Persistent |
Azure.AI.Projects |
High-level project client | dotnet add package Azure.AI.Projects |
Reference Links
Files (skills)
-
SKILL.md 11.6 KB
--- name: azure-ai-agents-persistent-dotnet description: | Azure AI Agents Persistent SDK for .NET. Low-level SDK for creating and managing AI agents with threads, messages, runs, and tools. Use for agent CRUD, conversation threads, streaming responses, function calling, file search, and code interpreter. Triggers: "PersistentAgentsClient", "persistent agents", "agent threads", "agent runs", "streaming agents", "function calling agents .NET". license: MIT metadata: author: Microsoft version: "1.0.0" package: Azure.AI.Agents.Persistent --- # Azure.AI.Agents.Persistent (.NET) Low-level SDK for creating and managing persistent AI agents with threads, messages, runs, and tools. ## Installation ```bash dotnet add package Azure.AI.Agents.Persistent --prerelease dotnet add package Azure.Identity ``` **Current Versions**: Stable v1.1.0, Preview v1.2.0-beta.8 ## Environment Variables ```bash PROJECT_ENDPOINT=https://<resource>.services.ai.azure.com/api/projects/<project> # Required: Azure AI project endpoint MODEL_DEPLOYMENT_NAME=gpt-4o-mini # Required: model deployment name AZURE_BING_CONNECTION_ID=<bing-connection-resource-id> # Required: Bing connection resource ID AZURE_AI_SEARCH_CONNECTION_ID=<search-connection-resource-id> # Required: Azure AI Search connection resource ID AZURE_TOKEN_CREDENTIALS=prod # Required only if DefaultAzureCredential is used in production ``` ## Authentication ```csharp using Azure.AI.Agents.Persistent; using Azure.Identity; var projectEndpoint = Environment.GetEnvironmentVariable("PROJECT_ENDPOINT"); // Local dev: DefaultAzureCredential. Production: set AZURE_TOKEN_CREDENTIALS=prod or AZURE_TOKEN_CREDENTIALS=<specific_credential> var credential = new DefaultAzureCredential( DefaultAzureCredential.DefaultEnvironmentVariableName ); // Or use a specific credential directly in production: // See https://learn.microsoft.com/dotnet/api/overview/azure/identity-readme?view=azure-dotnet#credential-classes // var credential = new ManagedIdentityCredential(); PersistentAgentsClient client = new(projectEndpoint, credential); ``` ## Client Hierarchy ``` PersistentAgentsClient ├── Administration → Agent CRUD operations ├── Threads → Thread management ├── Messages → Message operations ├── Runs → Run execution and streaming ├── Files → File upload/download └── VectorStores → Vector store management ``` ## Core Workflow ### 1. Create Agent ```csharp var modelDeploymentName = Environment.GetEnvironmentVariable("MODEL_DEPLOYMENT_NAME"); PersistentAgent agent = await client.Administration.CreateAgentAsync( model: modelDeploymentName, name: "Math Tutor", instructions: "You are a personal math tutor. Write and run code to answer math questions.", tools: [new CodeInterpreterToolDefinition()] ); ``` ### 2. Create Thread and Message ```csharp // Create thread PersistentAgentThread thread = await client.Threads.CreateThreadAsync(); // Create message await client.Messages.CreateMessageAsync( thread.Id, MessageRole.User, "I need to solve the equation `3x + 11 = 14`. Can you help me?" ); ``` ### 3. Run Agent (Polling) ```csharp // Create run ThreadRun run = await client.Runs.CreateRunAsync( thread.Id, agent.Id, additionalInstructions: "Please address the user as Jane Doe." ); // Poll for completion do { await Task.Delay(TimeSpan.FromMilliseconds(500)); run = await client.Runs.GetRunAsync(thread.Id, run.Id); } while (run.Status == RunStatus.Queued || run.Status == RunStatus.InProgress); // Retrieve messages await foreach (PersistentThreadMessage message in client.Messages.GetMessagesAsync( threadId: thread.Id, order: ListSortOrder.Ascending)) { Console.Write($"{message.Role}: "); foreach (MessageContent content in message.ContentItems) { if (content is MessageTextContent textContent) Console.WriteLine(textContent.Text); } } ``` ### 4. Streaming Response ```csharp AsyncCollectionResult<StreamingUpdate> stream = client.Runs.CreateRunStreamingAsync( thread.Id, agent.Id ); await foreach (StreamingUpdate update in stream) { if (update.UpdateKind == StreamingUpdateReason.RunCreated) { Console.WriteLine("--- Run started! ---"); } else if (update is MessageContentUpdate contentUpdate) { Console.Write(contentUpdate.Text); } else if (update.UpdateKind == StreamingUpdateReason.RunCompleted) { Console.WriteLine("\n--- Run completed! ---"); } } ``` ### 5. Function Calling ```csharp // Define function tool FunctionToolDefinition weatherTool = new( name: "getCurrentWeather", description: "Gets the current weather at a location.", parameters: BinaryData.FromObjectAsJson(new { Type = "object", Properties = new { Location = new { Type = "string", Description = "City and state, e.g. San Francisco, CA" }, Unit = new { Type = "string", Enum = new[] { "c", "f" } } }, Required = new[] { "location" } }, new JsonSerializerOptions { PropertyNamingPolicy = JsonNamingPolicy.CamelCase }) ); // Create agent with function PersistentAgent agent = await client.Administration.CreateAgentAsync( model: modelDeploymentName, name: "Weather Bot", instructions: "You are a weather bot.", tools: [weatherTool] ); // Handle function calls during polling do { await Task.Delay(500); run = await client.Runs.GetRunAsync(thread.Id, run.Id); if (run.Status == RunStatus.RequiresAction && run.RequiredAction is SubmitToolOutputsAction submitAction) { List<ToolOutput> outputs = []; foreach (RequiredToolCall toolCall in submitAction.ToolCalls) { if (toolCall is RequiredFunctionToolCall funcCall) { // Execute function and get result string result = ExecuteFunction(funcCall.Name, funcCall.Arguments); outputs.Add(new ToolOutput(toolCall, result)); } } run = await client.Runs.SubmitToolOutputsToRunAsync(run, outputs, toolApprovals: null); } } while (run.Status == RunStatus.Queued || run.Status == RunStatus.InProgress); ``` ### 6. File Search with Vector Store ```csharp // Upload file PersistentAgentFileInfo file = await client.Files.UploadFileAsync( filePath: "document.txt", purpose: PersistentAgentFilePurpose.Agents ); // Create vector store PersistentAgentsVectorStore vectorStore = await client.VectorStores.CreateVectorStoreAsync( fileIds: [file.Id], name: "my_vector_store" ); // Create file search resource FileSearchToolResource fileSearchResource = new(); fileSearchResource.VectorStoreIds.Add(vectorStore.Id); // Create agent with file search PersistentAgent agent = await client.Administration.CreateAgentAsync( model: modelDeploymentName, name: "Document Assistant", instructions: "You help users find information in documents.", tools: [new FileSearchToolDefinition()], toolResources: new ToolResources { FileSearch = fileSearchResource } ); ``` ### 7. Bing Grounding ```csharp var bingConnectionId = Environment.GetEnvironmentVariable("AZURE_BING_CONNECTION_ID"); BingGroundingToolDefinition bingTool = new( new BingGroundingSearchToolParameters( [new BingGroundingSearchConfiguration(bingConnectionId)] ) ); PersistentAgent agent = await client.Administration.CreateAgentAsync( model: modelDeploymentName, name: "Search Agent", instructions: "Use Bing to answer questions about current events.", tools: [bingTool] ); ``` ### 8. Azure AI Search ```csharp AzureAISearchToolResource searchResource = new( connectionId: searchConnectionId, indexName: "my_index", topK: 5, filter: "category eq 'documentation'", queryType: AzureAISearchQueryType.Simple ); PersistentAgent agent = await client.Administration.CreateAgentAsync( model: modelDeploymentName, name: "Search Agent", instructions: "Search the documentation index to answer questions.", tools: [new AzureAISearchToolDefinition()], toolResources: new ToolResources { AzureAISearch = searchResource } ); ``` ### 9. Cleanup ```csharp await client.Threads.DeleteThreadAsync(thread.Id); await client.Administration.DeleteAgentAsync(agent.Id); await client.VectorStores.DeleteVectorStoreAsync(vectorStore.Id); await client.Files.DeleteFileAsync(file.Id); ``` ## Available Tools | Tool | Class | Purpose | |------|-------|---------| | Code Interpreter | `CodeInterpreterToolDefinition` | Execute Python code, generate visualizations | | File Search | `FileSearchToolDefinition` | Search uploaded files via vector stores | | Function Calling | `FunctionToolDefinition` | Call custom functions | | Bing Grounding | `BingGroundingToolDefinition` | Web search via Bing | | Azure AI Search | `AzureAISearchToolDefinition` | Search Azure AI Search indexes | | OpenAPI | `OpenApiToolDefinition` | Call external APIs via OpenAPI spec | | Azure Functions | `AzureFunctionToolDefinition` | Invoke Azure Functions | | MCP | `MCPToolDefinition` | Model Context Protocol tools | | SharePoint | `SharepointToolDefinition` | Access SharePoint content | | Microsoft Fabric | `MicrosoftFabricToolDefinition` | Access Fabric data | ## Streaming Update Types | Update Type | Description | |-------------|-------------| | `StreamingUpdateReason.RunCreated` | Run started | | `StreamingUpdateReason.RunInProgress` | Run processing | | `StreamingUpdateReason.RunCompleted` | Run finished | | `StreamingUpdateReason.RunFailed` | Run errored | | `MessageContentUpdate` | Text content chunk | | `RunStepUpdate` | Step status change | ## Key Types Reference | Type | Purpose | |------|---------| | `PersistentAgentsClient` | Main entry point | | `PersistentAgent` | Agent with model, instructions, tools | | `PersistentAgentThread` | Conversation thread | | `PersistentThreadMessage` | Message in thread | | `ThreadRun` | Execution of agent against thread | | `RunStatus` | Queued, InProgress, RequiresAction, Completed, Failed | | `ToolResources` | Combined tool resources | | `ToolOutput` | Function call response | ## Best Practices 1. **Always dispose clients** — Use `using` statements or explicit disposal 2. **Poll with appropriate delays** — 500ms recommended between status checks 3. **Clean up resources** — Delete threads and agents when done 4. **Handle all run statuses** — Check for `RequiresAction`, `Failed`, `Cancelled` 5. **Use streaming for real-time UX** — Better user experience than polling 6. **Store IDs not objects** — Reference agents/threads by ID 7. **Use async methods** — All operations should be async ## Error Handling ```csharp using Azure; try { var agent = await client.Administration.CreateAgentAsync(...); } catch (RequestFailedException ex) when (ex.Status == 404) { Console.WriteLine("Resource not found"); } catch (RequestFailedException ex) { Console.WriteLine($"Error: {ex.Status} - {ex.ErrorCode}: {ex.Message}"); } ``` ## Related SDKs | SDK | Purpose | Install | |-----|---------|---------| | `Azure.AI.Agents.Persistent` | Low-level agents (this SDK) | `dotnet add package Azure.AI.Agents.Persistent` | | `Azure.AI.Projects` | High-level project client | `dotnet add package Azure.AI.Projects` | ## Reference Links | Resource | URL | |----------|-----| | NuGet Package | https://www.nuget.org/packages/Azure.AI.Agents.Persistent | | API Reference | https://learn.microsoft.com/dotnet/api/azure.ai.agents.persistent | | GitHub Source | https://github.com/Azure/azure-sdk-for-net/tree/main/sdk/ai/Azure.AI.Agents.Persistent | | Samples | https://github.com/Azure/azure-sdk-for-net/tree/main/sdk/ai/Azure.AI.Agents.Persistent/samples |
Comments (0)
Sign in to join the conversation.
Reviews (0)
No reviews yet.
No comments yet.