mirror of
https://github.com/microsoft/agent-framework.git
synced 2026-06-16 21:04:09 +08:00
setup azurestorage proj
This commit is contained in:
@@ -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" />
|
||||
|
||||
@@ -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");
|
||||
|
||||
+158
@@ -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();
|
||||
}
|
||||
}
|
||||
+26
@@ -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; }
|
||||
}
|
||||
+56
@@ -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);
|
||||
});
|
||||
}
|
||||
+23
@@ -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>
|
||||
Reference in New Issue
Block a user