Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 24 additions & 0 deletions src/Agents/ChatMessageStoreFactory.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
using Microsoft.Agents.AI;
using static Microsoft.Agents.AI.ChatClientAgentOptions;

namespace Devlooped.Agents.AI;

/// <summary>
/// An implementation of a <see cref="ChatMessageStore"/> factory as a class that can provide
/// the functionality to <see cref="ChatClientAgentOptions.ChatMessageStoreFactory"/> and integrates
/// more easily into a service collection.
/// </summary>
/// <remarks>
/// The <see cref="ChatMessageStore"/> is a key extensibility point in Microsoft.Agents.AI, allowing
/// storage and retrieval of chat messages.
/// </remarks>
public abstract class ChatMessageStoreFactory
{
/// <summary>
/// Provides the implementation of <see cref="ChatClientAgentOptions.ChatMessageStoreFactory"/>
/// to provide message persistence.
/// </summary>
/// <param name="context">The context to potentially hydrate state from.</param>
/// <returns>The message store that will handle chat messages.</returns>
public abstract ChatMessageStore CreateStore(ChatMessageStoreFactoryContext context);
}
9 changes: 9 additions & 0 deletions src/Agents/ConfigurableAIAgent.cs
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,15 @@ public override IAsyncEnumerable<AgentRunResponseUpdate> RunStreamingAsync(IEnum
options.AIContextProviderFactory = contextFactory.CreateProvider;
}

if (options.ChatMessageStoreFactory is null)
{
var storeFactory = services.GetKeyedService<ChatMessageStoreFactory>(name) ??
services.GetService<ChatMessageStoreFactory>();

if (storeFactory is not null)
options.ChatMessageStoreFactory = storeFactory.CreateStore;
}

LogConfigured(name);

return (new ChatClientAgent(client, options, services.GetRequiredService<ILoggerFactory>(), services), options, client);
Expand Down
57 changes: 56 additions & 1 deletion src/Tests/ConfigurableAgentTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -131,8 +131,63 @@ public void AssignsContextProviderFromService()
var options = agent.GetService<ChatClientAgentOptions>();

Assert.NotNull(options?.AIContextProviderFactory);
Assert.Same(context, options?.AIContextProviderFactory?.Invoke(new ChatClientAgentOptions.AIContextProviderFactoryContext()));
Assert.Same(context, options?.AIContextProviderFactory?.Invoke(new()));
}

[Fact]
public void AssignsMessageStoreFactoryFromKeyedService()
{
var builder = new HostApplicationBuilder();
var context = Mock.Of<ChatMessageStore>();

builder.Services.AddKeyedSingleton<ChatMessageStoreFactory>("bot",
Mock.Of<ChatMessageStoreFactory>(x
=> x.CreateStore(It.IsAny<ChatClientAgentOptions.ChatMessageStoreFactoryContext>()) == context));

builder.Configuration.AddInMemoryCollection(new Dictionary<string, string?>
{
["ai:clients:chat:modelid"] = "gpt-4.1-nano",
["ai:clients:chat:apikey"] = "sk-asdfasdf",
["ai:agents:bot:client"] = "chat",
["ai:agents:bot:options:temperature"] = "0.5",
});

builder.AddAIAgents();

var app = builder.Build();
var agent = app.Services.GetRequiredKeyedService<AIAgent>("bot");
var options = agent.GetService<ChatClientAgentOptions>();

Assert.NotNull(options?.ChatMessageStoreFactory);
Assert.Same(context, options?.ChatMessageStoreFactory?.Invoke(new()));
}

[Fact]
public void AssignsMessageStoreFactoryFromService()
{
var builder = new HostApplicationBuilder();
var context = Mock.Of<ChatMessageStore>();

builder.Services.AddSingleton<ChatMessageStoreFactory>(
Mock.Of<ChatMessageStoreFactory>(x
=> x.CreateStore(It.IsAny<ChatClientAgentOptions.ChatMessageStoreFactoryContext>()) == context));

builder.Configuration.AddInMemoryCollection(new Dictionary<string, string?>
{
["ai:clients:chat:modelid"] = "gpt-4.1-nano",
["ai:clients:chat:apikey"] = "sk-asdfasdf",
["ai:agents:bot:client"] = "chat",
["ai:agents:bot:options:temperature"] = "0.5",
});

builder.AddAIAgents();

var app = builder.Build();
var agent = app.Services.GetRequiredKeyedService<AIAgent>("bot");
var options = agent.GetService<ChatClientAgentOptions>();

Assert.NotNull(options?.ChatMessageStoreFactory);
Assert.Same(context, options?.ChatMessageStoreFactory?.Invoke(new()));
}
}