Skip to content

Commit ca96fe3

Browse files
committed
Add service-driven ChatMessageStoreFactory for agents
In order to extend the configured agents, in addition to providing the configureOptions delegate when adding agents, the user can now also rely on auto-wiring of services exported as ChatMessageStoreFactory to customize message persistence. The export can be keyed to a specific agent (takes priority) or without a key (fallback). This allows granular wiring without resorting to more code.
1 parent 3228844 commit ca96fe3

File tree

3 files changed

+89
-1
lines changed

3 files changed

+89
-1
lines changed
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
using Microsoft.Agents.AI;
2+
using static Microsoft.Agents.AI.ChatClientAgentOptions;
3+
4+
namespace Devlooped.Agents.AI;
5+
6+
/// <summary>
7+
/// An implementation of a <see cref="ChatMessageStore"/> factory as a class that can provide
8+
/// the functionality to <see cref="ChatClientAgentOptions.ChatMessageStoreFactory"/> and integrates
9+
/// more easily into a service collection.
10+
/// </summary>
11+
/// <remarks>
12+
/// The <see cref="ChatMessageStore"/> is a key extensibility point in Microsoft.Agents.AI, allowing
13+
/// storage and retrieval of chat messages.
14+
/// </remarks>
15+
public abstract class ChatMessageStoreFactory
16+
{
17+
/// <summary>
18+
/// Provides the implementation of <see cref="ChatClientAgentOptions.ChatMessageStoreFactory"/>
19+
/// to provide message persistence.
20+
/// </summary>
21+
/// <param name="context">The context to potentially hydrate state from.</param>
22+
/// <returns>The message store that will handle chat messages.</returns>
23+
public abstract ChatMessageStore CreateStore(ChatMessageStoreFactoryContext context);
24+
}

src/Agents/ConfigurableAIAgent.cs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,15 @@ public override IAsyncEnumerable<AgentRunResponseUpdate> RunStreamingAsync(IEnum
9898
options.AIContextProviderFactory = contextFactory.CreateProvider;
9999
}
100100

101+
if (options.ChatMessageStoreFactory is null)
102+
{
103+
var storeFactory = services.GetKeyedService<ChatMessageStoreFactory>(name) ??
104+
services.GetService<ChatMessageStoreFactory>();
105+
106+
if (storeFactory is not null)
107+
options.ChatMessageStoreFactory = storeFactory.CreateStore;
108+
}
109+
101110
LogConfigured(name);
102111

103112
return (new ChatClientAgent(client, options, services.GetRequiredService<ILoggerFactory>(), services), options, client);

src/Tests/ConfigurableAgentTests.cs

Lines changed: 56 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -131,8 +131,63 @@ public void AssignsContextProviderFromService()
131131
var options = agent.GetService<ChatClientAgentOptions>();
132132

133133
Assert.NotNull(options?.AIContextProviderFactory);
134-
Assert.Same(context, options?.AIContextProviderFactory?.Invoke(new ChatClientAgentOptions.AIContextProviderFactoryContext()));
134+
Assert.Same(context, options?.AIContextProviderFactory?.Invoke(new()));
135+
}
136+
137+
[Fact]
138+
public void AssignsMessageStoreFactoryFromKeyedService()
139+
{
140+
var builder = new HostApplicationBuilder();
141+
var context = Mock.Of<ChatMessageStore>();
142+
143+
builder.Services.AddKeyedSingleton<ChatMessageStoreFactory>("bot",
144+
Mock.Of<ChatMessageStoreFactory>(x
145+
=> x.CreateStore(It.IsAny<ChatClientAgentOptions.ChatMessageStoreFactoryContext>()) == context));
146+
147+
builder.Configuration.AddInMemoryCollection(new Dictionary<string, string?>
148+
{
149+
["ai:clients:chat:modelid"] = "gpt-4.1-nano",
150+
["ai:clients:chat:apikey"] = "sk-asdfasdf",
151+
["ai:agents:bot:client"] = "chat",
152+
["ai:agents:bot:options:temperature"] = "0.5",
153+
});
154+
155+
builder.AddAIAgents();
156+
157+
var app = builder.Build();
158+
var agent = app.Services.GetRequiredKeyedService<AIAgent>("bot");
159+
var options = agent.GetService<ChatClientAgentOptions>();
160+
161+
Assert.NotNull(options?.ChatMessageStoreFactory);
162+
Assert.Same(context, options?.ChatMessageStoreFactory?.Invoke(new()));
135163
}
136164

165+
[Fact]
166+
public void AssignsMessageStoreFactoryFromService()
167+
{
168+
var builder = new HostApplicationBuilder();
169+
var context = Mock.Of<ChatMessageStore>();
170+
171+
builder.Services.AddSingleton<ChatMessageStoreFactory>(
172+
Mock.Of<ChatMessageStoreFactory>(x
173+
=> x.CreateStore(It.IsAny<ChatClientAgentOptions.ChatMessageStoreFactoryContext>()) == context));
174+
175+
builder.Configuration.AddInMemoryCollection(new Dictionary<string, string?>
176+
{
177+
["ai:clients:chat:modelid"] = "gpt-4.1-nano",
178+
["ai:clients:chat:apikey"] = "sk-asdfasdf",
179+
["ai:agents:bot:client"] = "chat",
180+
["ai:agents:bot:options:temperature"] = "0.5",
181+
});
182+
183+
builder.AddAIAgents();
184+
185+
var app = builder.Build();
186+
var agent = app.Services.GetRequiredKeyedService<AIAgent>("bot");
187+
var options = agent.GetService<ChatClientAgentOptions>();
188+
189+
Assert.NotNull(options?.ChatMessageStoreFactory);
190+
Assert.Same(context, options?.ChatMessageStoreFactory?.Invoke(new()));
191+
}
137192
}
138193

0 commit comments

Comments
 (0)