setup azurestorage proj

This commit is contained in:
Korolev Dmitry
2025-11-03 19:19:14 +01:00
Unverified
parent b467ddf92a
commit 6c969418ac
8 changed files with 274 additions and 0 deletions
+1
View File
@@ -21,6 +21,7 @@
<PackageVersion Include="Azure.AI.OpenAI" Version="2.5.0-beta.1" />
<PackageVersion Include="Azure.Identity" Version="1.17.0" />
<PackageVersion Include="Azure.Monitor.OpenTelemetry.Exporter" Version="1.4.0" />
<PackageVersion Include="Azure.Storage.Blobs" Version="12.26.0" />
<!-- System.* -->
<PackageVersion Include="Microsoft.Bcl.AsyncInterfaces" Version="9.0.10" />
<PackageVersion Include="Microsoft.Bcl.HashCode" Version="6.0.0" />
+1
View File
@@ -265,6 +265,7 @@
<Project Path="src/Microsoft.Agents.AI.CopilotStudio/Microsoft.Agents.AI.CopilotStudio.csproj" />
<Project Path="src/Microsoft.Agents.AI.Hosting.A2A.AspNetCore/Microsoft.Agents.AI.Hosting.A2A.AspNetCore.csproj" />
<Project Path="src/Microsoft.Agents.AI.Hosting.A2A/Microsoft.Agents.AI.Hosting.A2A.csproj" />
<Project Path="src/Microsoft.Agents.AI.Hosting.AzureStorage/Microsoft.Agents.AI.Hosting.AzureStorage.csproj" Id="ad9b8214-0408-475d-928a-10741e8088e7" />
<Project Path="src/Microsoft.Agents.AI.Hosting.OpenAI/Microsoft.Agents.AI.Hosting.OpenAI.csproj" />
<Project Path="src/Microsoft.Agents.AI.Hosting/Microsoft.Agents.AI.Hosting.csproj" />
<Project Path="src/Microsoft.Agents.AI.Mem0/Microsoft.Agents.AI.Mem0.csproj" />
@@ -8,6 +8,7 @@
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\..\..\src\Microsoft.Agents.AI.Hosting.AzureStorage\Microsoft.Agents.AI.Hosting.AzureStorage.csproj" />
<ProjectReference Include="..\..\..\src\Microsoft.Agents.AI.Workflows\Microsoft.Agents.AI.Workflows.csproj" />
<ProjectReference Include="..\..\..\src\Microsoft.Agents.AI.Abstractions\Microsoft.Agents.AI.Abstractions.csproj" />
<ProjectReference Include="..\..\..\src\Microsoft.Agents.AI.Hosting\Microsoft.Agents.AI.Hosting.csproj" />
@@ -3,6 +3,7 @@
using A2A.AspNetCore;
using AgentWebChat.AgentHost;
using AgentWebChat.AgentHost.Utilities;
using Azure.Storage.Blobs;
using Microsoft.Agents.AI;
using Microsoft.Agents.AI.Hosting;
using Microsoft.Agents.AI.Workflows;
@@ -27,6 +28,13 @@ builder.AddAIAgent(
chatClientServiceKey: "chat-model")
.WithInMemoryThreadStore();
builder.AddAIAgent(
"gambler",
instructions: "You are a gambler. Talk like a gambler.",
description: "An agent which gambles",
chatClientServiceKey: "chat-model")
.WithAzureBlobThreadStore(sp => new BlobContainerClient(connectionString: "UseDevelopmentStorage=true", "agent-threads"));
builder.AddAIAgent("knights-and-knaves", (sp, key) =>
{
var chatClient = sp.GetRequiredKeyedService<IChatClient>("chat-model");
@@ -0,0 +1,158 @@
// Copyright (c) Microsoft. All rights reserved.
using System;
using System.IO;
using System.Text;
using System.Text.Json;
using System.Threading;
using System.Threading.Tasks;
using Azure;
using Azure.Storage.Blobs;
using Azure.Storage.Blobs.Models;
using Microsoft.Shared.Diagnostics;
namespace Microsoft.Agents.AI.Hosting.AzureStorage.Blob;
internal sealed class AzureBlobAgentThreadStore : AgentThreadStore
{
private static readonly BlobOpenWriteOptions s_uploadJsonOptions = new()
{
HttpHeaders = new()
{
ContentType = "application/json"
}
};
private readonly BlobContainerClient _containerClient;
private readonly AzureBlobAgentThreadStoreOptions _options;
private bool _containerInitialized;
/// <summary>
/// Initializes a new instance of the <see cref="AzureBlobAgentThreadStore"/> class using a <see cref="BlobContainerClient"/>.
/// </summary>
/// <param name="containerClient">The blob container client to use for storage operations.</param>
/// <param name="options">Optional configuration options. If <see langword="null"/>, default options will be used.</param>
/// <exception cref="ArgumentNullException"><paramref name="containerClient"/> is <see langword="null"/>.</exception>
public AzureBlobAgentThreadStore(BlobContainerClient containerClient, AzureBlobAgentThreadStoreOptions? options = null)
{
this._containerClient = containerClient ?? throw new ArgumentNullException(nameof(containerClient));
this._options = options ?? new AzureBlobAgentThreadStoreOptions();
}
/// <inheritdoc/>
public override async ValueTask SaveThreadAsync(
AIAgent agent,
string conversationId,
AgentThread thread,
CancellationToken cancellationToken = default)
{
Throw.IfNull(agent);
Throw.IfNull(conversationId);
Throw.IfNull(thread);
await this.EnsureContainerExistsAsync(cancellationToken).ConfigureAwait(false);
var blobName = this.GetBlobName(agent.Id, conversationId);
var blobClient = this._containerClient.GetBlobClient(blobName);
JsonElement serializedThread = thread.Serialize();
#pragma warning disable CA2007 // Consider calling ConfigureAwait on the awaited task
await using Stream stream = await blobClient.OpenWriteAsync(overwrite: true, s_uploadJsonOptions, cancellationToken).ConfigureAwait(false);
await using Utf8JsonWriter writer = new(stream);
#pragma warning restore CA2007 // Consider calling ConfigureAwait on the awaited task
serializedThread.WriteTo(writer);
await writer.FlushAsync(cancellationToken).ConfigureAwait(false);
}
/// <inheritdoc/>
public override async ValueTask<AgentThread> GetThreadAsync(
AIAgent agent,
string conversationId,
CancellationToken cancellationToken = default)
{
Throw.IfNull(agent);
Throw.IfNull(conversationId);
await this.EnsureContainerExistsAsync(cancellationToken).ConfigureAwait(false);
var blobName = this.GetBlobName(agent.Id, conversationId);
var blobClient = this._containerClient.GetBlobClient(blobName);
try
{
var stream = await blobClient.OpenReadAsync(cancellationToken: cancellationToken).ConfigureAwait(false);
var jsonDoc = await JsonDocument.ParseAsync(stream, cancellationToken: cancellationToken).ConfigureAwait(false);
var serializedThread = jsonDoc.RootElement;
return agent.DeserializeThread(serializedThread);
}
catch (RequestFailedException ex) when (ex.Status == 404)
{
// Blob doesn't exist, return a new thread
return agent.GetNewThread();
}
}
/// <summary>
/// Ensures that the blob container exists, creating it if necessary.
/// </summary>
private async Task EnsureContainerExistsAsync(CancellationToken cancellationToken)
{
if (this._containerInitialized)
{
return;
}
if (!this._containerInitialized)
{
if (this._options.CreateContainerIfNotExists)
{
await this._containerClient.CreateIfNotExistsAsync(cancellationToken: cancellationToken).ConfigureAwait(false);
}
this._containerInitialized = true;
}
}
/// <summary>
/// Generates the blob name for a given agent and conversation.
/// </summary>
private string GetBlobName(string agentId, string conversationId)
{
string sanitizedAgentId = this.SanitizeBlobNameSegment(agentId);
string sanitizedConversationId = this.SanitizeBlobNameSegment(conversationId);
string baseName = $"{sanitizedAgentId}/{sanitizedConversationId}.json";
return string.IsNullOrEmpty(this._options.BlobNamePrefix)
? baseName
: $"{this._options.BlobNamePrefix.TrimEnd('/')}/{baseName}";
}
/// <summary>
/// Sanitizes a string to be safe for use in blob names.
/// </summary>
private string SanitizeBlobNameSegment(string input)
{
if (string.IsNullOrWhiteSpace(input))
{
return "default";
}
// Replace invalid characters with underscore
StringBuilder builder = new(input.Length);
foreach (char c in input)
{
if (char.IsLetterOrDigit(c) || c == '-' || c == '_' || c == '.')
{
builder.Append(c);
}
else
{
builder.Append('_');
}
}
return builder.ToString();
}
}
@@ -0,0 +1,26 @@
// Copyright (c) Microsoft. All rights reserved.
namespace Microsoft.Agents.AI.Hosting.AzureStorage.Blob;
/// <summary>
/// Configuration options for <see cref="AzureBlobAgentThreadStore"/>.
/// </summary>
public sealed class AzureBlobAgentThreadStoreOptions
{
/// <summary>
/// Gets or sets a value indicating whether to automatically create the container if it doesn't exist.
/// </summary>
/// <remarks>
/// Defaults to <see langword="true"/>.
/// </remarks>
public bool CreateContainerIfNotExists { get; set; } = true;
/// <summary>
/// Gets or sets the blob name prefix to use for organizing threads.
/// </summary>
/// <remarks>
/// This can be used to namespace threads within a container.
/// For example, setting this to "prod/" will store all blobs under a "prod/" prefix.
/// </remarks>
public string? BlobNamePrefix { get; set; }
}
@@ -0,0 +1,56 @@
// Copyright (c) Microsoft. All rights reserved.
using System;
using Azure.Storage.Blobs;
using Microsoft.Agents.AI.Hosting.AzureStorage.Blob;
using Microsoft.Extensions.DependencyInjection;
namespace Microsoft.Agents.AI.Hosting;
/// <summary>
/// Provides extension methods for configuring <see cref="AIAgent"/>.
/// </summary>
public static partial class HostedAgentBuilderExtensions
{
/// <summary>
/// Configures the host agent builder to use an Azure Blob thread store with dependency injection.
/// Resolves <see cref="BlobContainerClient"/> from the service provider.
/// </summary>
/// <param name="builder">The host agent builder to configure with the Azure blob thread store.</param>
/// <param name="options">Optional configuration options for the blob thread store.</param>
/// <returns>The same <paramref name="builder"/> instance, configured to use Azure blob thread store.</returns>
/// <remarks>
/// This overload requires a <see cref="BlobContainerClient"/> to be registered in the service collection.
/// Use Azure.Extensions.AspNetCore.Configuration.Secrets or similar to register the client.
/// </remarks>
public static IHostedAgentBuilder WithAzureBlobThreadStore(this IHostedAgentBuilder builder, AzureBlobAgentThreadStoreOptions? options = null)
=> WithAzureBlobThreadStore(builder, sp => sp.GetRequiredKeyedService<BlobContainerClient>(builder.Name), options);
/// <summary>
/// Configures the host agent builder to use an Azure Blob thread store for agent thread management.
/// </summary>
/// <param name="builder">The host agent builder to configure with the Azure blob thread store.</param>
/// <param name="containerClient">The blob container client to use for storage operations.</param>
/// <param name="options">Optional configuration options for the blob thread store.</param>
/// <returns>The same <paramref name="builder"/> instance, configured to use Azure blob thread store.</returns>
public static IHostedAgentBuilder WithAzureBlobThreadStore(this IHostedAgentBuilder builder, BlobContainerClient containerClient, AzureBlobAgentThreadStoreOptions? options = null)
=> WithAzureBlobThreadStore(builder, sp => containerClient, options);
/// <summary>
/// Configures the agent builder to use Azure Blob Storage as the thread store for agent state persistence.
/// </summary>
/// <param name="builder">The agent builder to configure with Azure Blob thread store support.</param>
/// <param name="createBlobContainer">A factory function that provides a configured BlobContainerClient instance for accessing the Azure Blob
/// container used to store thread data.</param>
/// <param name="options">Optional settings for customizing the Azure Blob thread store behavior. If null, default options are used.</param>
/// <returns>The same agent builder instance, configured to use Azure Blob Storage for thread persistence.</returns>
public static IHostedAgentBuilder WithAzureBlobThreadStore(
this IHostedAgentBuilder builder,
Func<IServiceProvider, BlobContainerClient> createBlobContainer,
AzureBlobAgentThreadStoreOptions? options = null)
=> builder.WithThreadStore((sp, key) =>
{
var blobContainer = createBlobContainer(sp);
return new AzureBlobAgentThreadStore(blobContainer, options);
});
}
@@ -0,0 +1,23 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFrameworks>$(ProjectsTargetFrameworks)</TargetFrameworks>
<TargetFrameworks Condition="'$(Configuration)' == 'Debug'">net9.0</TargetFrameworks>
<VersionSuffix>preview</VersionSuffix>
<InjectSharedThrow>true</InjectSharedThrow>
<!-- NuGet Package Settings -->
<Title>Microsoft Agent Framework AzureStorage</Title>
<Description>Provides AzureStorage integration with Microsoft Agent Framework.</Description>
</PropertyGroup>
<Import Project="$(RepoRoot)/dotnet/nuget/nuget-package.props" />
<ItemGroup>
<PackageReference Include="Azure.Storage.Blobs" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\Microsoft.Agents.AI.Hosting\Microsoft.Agents.AI.Hosting.csproj" />
</ItemGroup>
</Project>