# Function Calls through Dependency Injection in Microsoft Agent Framework

My previous [article](https://www.azureguru.net/microsoft-agent-framework-through-dependency-injection) on Microsoft Agent Framework (MAF) was focused on Dependency Injection (DI) in MAF.

In that article I demonstrated how `AITools`can be registered in the service container through DI and use them in the `Tools` property of `ChatOptions`.

The following code implements this behavior:

```csharp
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()
                       },
                   }
               );
          });
```

The `UserPlugin`class above, is registered in the DI container (`serviceCollection`) and exposes its functions through custom`AsAITools()`method and returns an `IEnumerable<AITool>`.

```csharp
public IEnumerable<AITool> AsAITools()
{
    yield return AIFunctionFactory.Create(Function_1);
    yield return AIFunctionFactory.Create(Function_2);  
    .
    .
    .
    .
    yield return AIFunctionFactory.Create(Function_N);    
}
```

The function execution is indirectly driven by the user prompt and relies on the model’s interpretation rather than explicit control.

> LLM's are designed to understand intent driven through user input and infer from natural language.

Instead of relying on plugins we could centralize function calling through DI and register a AI-callable function.

***Lets assume a use case.***

Consider a chatbot integrated with an ERP system. Once a user logs in the user should be made aware of access level to the underlying ERP modules and the data they are allowed to query.

In this case the bot cannot rely on user inputs. Instead it should understand it from the logged in user context and then through the LLM layer be able to return the details of the user access levels.

Below we have is a function that returns access level of a logged in user. Of course this is highly simplified and the details are hardcoded for brevity. Ideally, in a production set up the following details should come from databases or API's.

### Code

```csharp
public static Func<string, string[]> AccessDetails = (args) =>
    {
        switch (args)
        {
            case "superadmin@azureguru.net":

                return
            [
               "Admin Module >> Full Access",
               "Sales Module >> Full Access",
               "Marketing Module >> Full Access",
               "Accounts Module >> Full Access"
            ];
                break;

            case "superuser@azureguru.net":

                return
            [
               "Admin Module >> No Access",
                "Sales Module >> Full Access",
                "Marketing Module >> Full Access",
                "Accounts Module >> Full Access"
            ];
                break;

            case "sales@azureguru.net":

                return
            [
               "Admin Module >> No Access",
                "Sales Module >> Full Access",
                "Marketing Module >> Partial Access",
                "Accounts Module >> No Access"
            ];
                break;

            case "marketing@azureguru.net":

                return
            [
               "Admin Module >> No Access",
                "Sales Module >> Partial Access",
                "Marketing Module >> Full Access",
                "Accounts Module >> No Access"
            ];
                break;
            
                default:

                return
            [
              "",
              "Unknown User >> Please get in touch with Admin  for module access"
            ];

        }
    };
```

In my previous [article](https://www.azureguru.net/microsoft-agent-framework-through-dependency-injection), we registered a plugin that exposes functions as tools in a DI container. In this one, we will register a AI-callable function(`AIFunction`) in the DI container.

Let’s see how it works.

First we use `ConfigurationBuilder`to get the `appsettings.json` file

```csharp
  var configuration = new ConfigurationBuilder()
 .SetBasePath(Directory.GetCurrentDirectory())
 .AddJsonFile("appsettings.json", optional: false)
 .Build();
```

and read the `AzureOpenAI` API key to fetch the API keys and set the credentials.

```csharp
var credential = new AzureKeyCredential(configuration["AppSettings:ApiKey"]);
```

Next, we register a named keyed chat client in DI that sets the required values from `appsettings.json` with credentials.

```csharp
servicecollection.AddKeyedChatClient("ChatClient", (sp) => new AzureOpenAIClient(
new Uri(configuration["AppSettings:EndPoint"]), credential)                   .GetChatClient(configuration["AppSettings:Chat_DeploymentName"]).AsIChatClient());
```

In the next step, we configure an `AIAgent` as a `ChatClientAgent` and register it with DI container. Also we set up the necessary `ChatOptions`.

```csharp
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."                                 
                               },
                           }
                       );
                  });
```

> Note : In the above code unlike the plugin-based approach that was used in the previous article we are not registering or resolving plugins as `Tools` through DI.

Now comes the most important part. Registering the AI callable function in DI.

```csharp
servicecollection.AddSingleton<AIFunction>(sp =>
   {
       return AIFunctionFactory.Create(
           (Func<AIFunctionArguments, string[]>)((args) =>
           {
               string? userlogin = args.TryGetValue("userlogin", out var c)? c?.ToString(): "Unknown";
               return AccessDetails(userlogin!);
           })
       );
   });
```

Above, we register a `AIFunction` through factory and create the function at runtime and wrapping it.

Now you might ask what advantages would this approach have ?

The biggest advantage is that the we have centralized the routing logic. This is very useful when you don't want prompt driven function execution. Recall our use case, we don't want the LLM to control the function execution to return the access details of the logged in user.

Lets take a step back and allow me to explain it through a simple example .

Assume we have two functions that take completely different set of arguments and return a collection of `string[]` and`int[]` with arguments being `string` and `int` respectively.

Lets call them `FuncA`and `FuncB`.

**FuncA**

```csharp
public static Func<string, string[]> FuncA= (args) =>
        {
            return
            [
               "a",
               "b",
               "c"
            ];
        };
```

**FuncB**

```csharp
public static Func<int ,int[]> FuncB= (args) =>
        {
            return
            [
               1,
               2,
               3
            ];
        };
```

The conventional approach i.e. direct method call, the caller should aware of the function and its execution path.

But with this approach of function style routing, the caller has to only pass the arguments and the function decides which underlying method to execute based on passed arguments.

The following code registers functions `FuncA` and `FuncB` in the DI container.

```csharp
  servicecollection.AddSingleton(sp =>
  {
      return
          (Func<Dictionary<string, object>, object>)((args) =>
          {
              string? FuncA_arg = args.TryGetValue("FuncA_arg", out var c) ? c?.ToString() : "Unknown";

if (FuncA_arg != "Unknown") return FuncA(c!.ToString()!);

              int FuncB_arg = args.TryGetValue("FuncB_arg", out var c1) ? (int)c1! : 0;
              
if (FuncB_arg != 0) return FuncB((int)c1!);

       return null;
          });

  });
```

The function expects `Dictionary<string, object>` as input of type and returns an object. Instead of object for values you can also use a `CustomType<T>` .

Example :`Dictionary<string, MyClass>`

Call to the above is as follows

```csharp
var arg = new Dictionary<string, object>
{
    ["FuncA_arg"] = "hello" 
};

var fn = serviceProvider.GetRequiredService<Func<Dictionary<string, object>, object>>();

 var results= fn.Invoke(arg) as string[];

 foreach (var value in results!)
 {
     Console.WriteLine(value);
 }
```

Based on the arguments passed, the DI container decides the function execution.

Our argument above is of type `Dictionary<string, object>` with value `"hello"`.

```csharp
var arg = new Dictionary<string, object>
{
    ["FuncA_arg"] = "hello" 
};
```

We then ask the DI container for a function that matches a specific signature.

```csharp
var fn = serviceProvider.GetRequiredService<Func<Dictionary<string, object>, object>>();
```

Based on the arguments passed the function determines which logic to execute

```csharp
string? FuncA_arg = args.TryGetValue("FuncA_arg", out var c) ? c?.ToString() : "Unknown";

if (FuncA_arg != "Unknown") return FuncA(c!.ToString()!);
```

It prints the collection from `Func_A`

![](https://cdn.hashnode.com/uploads/covers/6693c62c166ee9c594cffda0/850aed0d-2cfb-4deb-b45d-646b82c4deca.png align="left")

Similarly we invoke function `FuncB` with arguments

```csharp
var arg = new Dictionary<string, object>
{
    ["FuncB_arg"] = 1 
};
```

```csharp
var arg = new Dictionary<string, object>
{
    ["FuncB_arg"] = 1 
};

var results= fn.Invoke(arg) as int[];

 foreach (var value in results!)
 {
     Console.WriteLine(value);
 }
```

and we get the following output

![](https://cdn.hashnode.com/uploads/covers/6693c62c166ee9c594cffda0/ee0602e9-6f57-49f9-817b-a1f699c68f60.png align="left")

To sum up we have centralized the entry point and routing logic .I hope it makes sense.

Now lets get back to our example of AI callable function in DI for MAF.

After we register the `AIFunction` through `AIFunctionfactory`, we build the service provider.

```csharp

var serviceProvider = servicecollection.BuildServiceProvider();
```

We save the logged user value in a variable that acts as an argument to the callable function.

```csharp
  string loggedinuser = "superadmin@azureguru.net";
```

We create an agent instance through the DI container and also create an agent session.

```csharp
var agent = serviceProvider.GetRequiredService<AIAgent>();
AgentSession session = await agent.CreateSessionAsync();
```

Next, we pass the required arguments as type `AIFunctionArguments`

```csharp
  var arg = new AIFunctionArguments
  {
      ["userlogin"] = loggedinuser
  };
```

We then resolve and invoke the function and display the output to the screen

```csharp
var fn = serviceProvider.GetRequiredService<AIFunction>().InvokeAsync(arg);
```

We add the output to the agent context through `agent.RunAsync` and the output is stored in the session `Statebag`through `InMemoryChatHistoryProvider`

```csharp
Console.WriteLine("");
Console.WriteLine("You have the following module access");
Console.WriteLine(await agent.RunAsync($"Access Details: {fn.Result}: Explain in markdown format", session));
```

> Soon I will be posting an article on Statebag and ChatHistoryProviders in MAF

![](https://cdn.hashnode.com/uploads/covers/6693c62c166ee9c594cffda0/fc3142f7-8edb-405f-8af2-e7c9fffb0d7c.gif align="center")

If you want the output be displayed in its raw format you can just use

```csharp
 Console.WriteLine(fn.Result);
```

**Output >>**

![](https://cdn.hashnode.com/uploads/covers/6693c62c166ee9c594cffda0/94d71c3a-56c7-45a3-bc87-5dd781640532.png align="left")

### Conclusion

In this article, I tried to demonstrated how you could use DI to register your `AIFunctions`. While it may add some overhead for simple scenarios, it becomes increasingly valuable as applications grow in complexity and manage dependencies through modular and centralized approach.

Thanks for reading !!!
