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
52 changes: 52 additions & 0 deletions src/Agents/CompositeAIContextProvider.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
using Microsoft.Agents.AI;
using Microsoft.Extensions.AI;

namespace Devlooped.Agents.AI;

/// <summary>
/// Concatenates multiple <see cref="AIContext"/> instances into a single one.
/// </summary>
class CompositeAIContextProvider : AIContextProvider
{
readonly AIContext context;

public CompositeAIContextProvider(IList<AIContext> contexts)
{
if (contexts.Count == 1)
{
context = contexts[0];
return;
}

// Concatenate instructions from all contexts
context = new();
var instructions = new List<string>();
var messages = new List<ChatMessage>();
var tools = new List<AITool>();

foreach (var ctx in contexts)
{
if (!string.IsNullOrEmpty(ctx.Instructions))
instructions.Add(ctx.Instructions);

if (ctx.Messages != null)
messages.AddRange(ctx.Messages);

if (ctx.Tools != null)
tools.AddRange(ctx.Tools);
}

// Same separator used by M.A.AI for instructions appending from AIContext
if (instructions.Count > 0)
context.Instructions = string.Join('\n', instructions);

if (messages.Count > 0)
context.Messages = messages;

if (tools.Count > 0)
context.Tools = tools;
}

public override ValueTask<AIContext> InvokingAsync(InvokingContext context, CancellationToken cancellationToken = default)
=> ValueTask.FromResult(this.context);
}
39 changes: 39 additions & 0 deletions src/Agents/ConfigurableAIAgent.cs
Original file line number Diff line number Diff line change
Expand Up @@ -113,7 +113,45 @@ public override IAsyncEnumerable<AgentRunResponseUpdate> RunStreamingAsync(IEnum
services.GetService<AIContextProviderFactory>();

if (contextFactory is not null)
{
if (options.Use?.Count > 0)
throw new InvalidOperationException($"Invalid simultaneous use of keyed service {nameof(AIContextProviderFactory)} and '{section}:use' in configuration.");

options.AIContextProviderFactory = contextFactory.CreateProvider;
}
else if (services.GetKeyedService<AIContextProvider>(name) is { } contextProvider)
{
if (options.Use?.Count > 0)
throw new InvalidOperationException($"Invalid simultaneous use of keyed service {nameof(AIContextProvider)} and '{section}:use' in configuration.");

options.AIContextProviderFactory = _ => contextProvider;
}
else if (options.Use?.Count > 0)
{
var contexts = new List<AIContext>();
foreach (var use in options.Use)
{
var context = services.GetKeyedService<AIContext>(use);
if (context is null)
{
var function = services.GetKeyedService<AITool>(use) ??
services.GetKeyedService<AIFunction>(use) ??
throw new InvalidOperationException($"Specified AI context '{use}' for agent '{name}' is not registered as either an {nameof(AIContent)} or an {nameof(AITool)}.");

contexts.Add(new AIContext { Tools = [function] });
}
else
{
contexts.Add(context);
}
}

options.AIContextProviderFactory = _ => new CompositeAIContextProvider(contexts);
}
}
else if (options.Use?.Count > 0)
{
throw new InvalidOperationException($"Invalid simultaneous use of {nameof(ChatClientAgentOptions)}.{nameof(ChatClientAgentOptions.AIContextProviderFactory)} and '{section}:use' in configuration.");
}

if (options.ChatMessageStoreFactory is null)
Expand Down Expand Up @@ -148,6 +186,7 @@ void OnReload(object? state)
internal class AgentClientOptions : ChatClientAgentOptions
{
public string? Client { get; set; }
public IList<string>? Use { get; set; }
}
}

Expand Down
Loading
Loading