Microsoft Agent Framework through Dependency Injection

After working on Semantic Kernel (SK) for sometime and then gradually shifting focus towards Microsoft Agent Framework (MAF), I’ve personally started to like MAF over SK.
Implementation through SK had a relatively high level of complexity which I felt was unnecessary for simpler use cases and also SK was heavily dependent on the Dependency Injection (DI) pattern that made setup more involved.
If interested you could read my article on SK Dependency Injection pattern here.
In SK everything revolves around the kernel. And not to mention, managing conversation context across multiple sessions was not straightforward as it was up to developers on how to reconstruct chat history due to the stateless nature of LLM's.
Though ChatHistory helped with in-session context handling, multi-session persistence and state management in SK, it still required custom implementation that added unnecessary complexity.
In MAF, the introduction of middleware gives more flexibility and provides a powerful way to intercept and modify agent behavior.
Not to say that there aren't some shortcomings in MAF.
For example I couldn't find a way to create functions through prompts in MAF.
In SK, this was quite straightforward. You could do something like this easily.
kernel.CreateFunctionFromPrompt("My Prompt")
However, there isn’t an equivalent approach for this aspect in MAF.
Another drawback was lack of in built PromptTemplate support.
With SK you could use kernelPromptTemplateFactory object that natively protects prompt injection attacks.
Though using external providers like Azure ShieldPrompt can be an option to prevent injection attacks ,it would have been great if there would had been something equivalent to kernelPromptTemplateFactoryin MAF wrt to prompt templates.
If you would like to know how to use ShieldPrompt to prevent prompt injection attacks, I have an article on that topic here .
Lets get back to the topic of this article.
Earlier, I mentioned that SK was heavily DI dependent but in MAF its optional. You could still use DI in MAF.
In this article I would show how it can be done. I think I will be using this approach extensively for foreseeable future.
I would recommend to go through my an article on integrating C# native functions in SK. You can find that article here and it might help you understand the the intricacies on function calling in SK.
Some important differences between SK and MAF wrt to function calling ,
AIFunctionin MAF is equivalent toKernelFunctionin SKAIFunctionFactoryin MAF is equivalent toKernelFunctionFactoryin SKAIFunctionArgumentsin MAF is equivalent toKernelArgumentsin SK
I will pen down a detailed article shortly on invoking function calls in MAF.
SetUp
Add the following dependencies to your C# console application
dotnet add package Azure.AI.OpenAI
dotnet add package Microsoft.Extensions.AI
dotnet add package Microsoft.Agents.AI
dotnet add package Microsoft.Extensions.Configuration
dotnet add package Microsoft.Extensions.DependencyInjection
Create appsettings.json file with the following details
"AppSettings": {
"Chat_DeploymentName": "Deployment name",
"EndPoint": "Azure AI endpoint",
"ApiKey": "Azure AI API key"
}
Code
Read the config file
var configuration = new ConfigurationBuilder()
.SetBasePath(Directory.GetCurrentDirectory())
.AddJsonFile("appsettings.json", optional: false)
.Build();
Create a class called UserPlugin, that has two methods HelloUser and HelloCity.
[Description("Greets the user")]
public static string HelloUser([Description("Name of the user")] string user)
{
return $"Hello from : {user} ";
}
[Description("Greets the user")]
public static string HelloCity([Description("Name of the city")] string city)
{
return $"Nice city : {city} ";
}
In MAF, you have the option to expose the functions as AITool. It is the base class for all tools that can be provided to agents.
In our example we can do it through through the custom function AsAITools by exposing the two functions as a sequence of type IEnumerable.
public IEnumerable<AITool> AsAITools()
{
yield return AIFunctionFactory.Create(HelloCity);
yield return AIFunctionFactory.Create(HelloUser);
}
The
AIFunctionFactoryin MAF is similar toKernelFunctionFactoryin SK
Our UserPluginclass looks like this
using Microsoft.Extensions.AI;
using System.ComponentModel;
namespace Plugins_Functions
{
internal class UserPlugin
{
[Description("Greets the user")]
public static string HelloUser([Description("Name of the user")] string user)
{
return $"Hello from : {user} ";
}
[Description("Greets the user")]
public static string HelloCity([Description("Name of the city")] string city)
{
return $"Nice city : {city} ";
}
public IEnumerable<AITool> AsAITools()
{
yield return AIFunctionFactory.Create(HelloCity);
yield return AIFunctionFactory.Create(HelloUser);
}
}
}
As we are using DI, we have to inject the class UserPlugin. We can register and resolve through ServiceCollection.
ServiceCollection servicecollection = new();
servicecollection.AddSingleton<UserPlugin>();
We can validate the credentials, through AzureKeyCredential. Though I don't like this approach, for brevity we will use this approach in this article.
My preferred approach would be using SPN based authentication. I had penned a article on that topic that you can read it here.
var credential = new AzureKeyCredential(configuration["AppSettings:ApiKey"]);
Next, we register the ChatClientwith the servicecollection.
servicecollection.AddKeyedChatClient("ChatClient", (sp) => new AzureOpenAIClient(
new Uri(configuration["AppSettings:EndPoint"]), credential)
.GetChatClient(configuration["AppSettings:Chat_DeploymentName"]).AsIChatClient());
We then register an AIAgent with a factory in the ServiceCollection, allowing the DI container to construct it.
servicecollection.AddSingleton<AIAgent>(sp =>
{
return new ChatClientAgent(
chatClient: sp.GetRequiredKeyedService<IChatClient>("ChatClient"),
options: new ChatClientAgentOptions
{
ChatOptions = new ChatOptions
{
Instructions = "You are a helpful AI assistant.You must call the provided function to greet users and return the EXACT output of the function without modifying it.",
Tools = sp.GetRequiredService<UserPlugin>().AsAITools().ToList(),
},
}
);
});
Note the instructions above
"You are a helpful AI assistant. You must call the provided function to greet users and return the EXACT output of the function without modifying it."
This ensures that the agent delegates the greeting to the function and returns its output as exactly stated in the description of the function without adding, removing or altering any content by the agent.
We then build the DI container that can be used to create the AIAgent.
var serviceProvider = servicecollection.BuildServiceProvider();
var agent = serviceProvider.GetRequiredService<AIAgent>();
We then create an AgentSession and send a prompt
AgentSession session = await agent.CreateSessionAsync();
Console.WriteLine(await agent.RunAsync("My name is Sachin and I am from Mumbai ", session));
Screengrab :
An alternate approach for function calling , is that you can directly use the functions created through AIFunctionFactoryin the agent registration by exposing the function as AITool.
Its similar to the SK approach. I have article on that topic here.
MAF Example :
var hellouser = AIFunctionFactory.Create(UserPlugin.HelloUser, name: "HelloUser", description: "Greets the user");
var hellocity = AIFunctionFactory.Create(UserPlugin.HelloCity, name: "HelloCity", description: "Greets the city");
List<AITool> AllFunctions = new List<AITool> { hellouser, hellocity };
Replace the following line of code in the ChatOptionsof the AIAgent service registration
ChatOptions = new ChatOptions
{
Instructions = "You are a helpful AI assistant.You must call the provided function to greet users and return the EXACT output of the function without modifying it.",
Tools = sp.GetRequiredService<UserPlugin> ().AsAITools().ToList()
},
with this
ChatOptions = new ChatOptions
{
Instructions = "You are a helpful AI assistant.You must call the provided function to greet users and return the EXACT output of the function without modifying it.",
Tools = AllFunctions
},
I personally prefer the AIToolapproach as it give more abstraction and is more like a whitelist approach unlike the dump everything approach.
Conclusion
In this article I touch based on an very important and crucial concept in MAF. In upcoming articles, I’ll dive deeper into MAF covering additional patterns and more advanced use cases.
So stay tuned !!!



