Building an AI Agent in .NET with Inferable

Nadeesha Cabral

In this tutorial, we'll build a simple AI agent that manages inventory operations using .NET and Inferable. Our agent will handle stock checks, process orders, and manage inventory - all through natural language commands.

Prerequisites

Before we begin, make sure you have:

  • .NET 7.0 or later installed
  • Basic familiarity with C#
  • Inferable CLI installed (npm install -g @inferablehq/inferable)

Authenticate with Inferable

inf auth login

Setting Up the Project

First, let's create a new .NET project with the Inferable SDK:

inf bootstrap dotnet

This will guide you through creating a new .NET project with the Inferable SDK installed. Let's remove the example code since we'll be starting fresh:

rm Program.cs

Creating Our Inventory System

Let's start by defining our inventory models and a simple in-memory store:

using System.Collections.Concurrent;

namespace InventoryAgent.Models;

public class Item
{
    public string Id { get; set; } = string.Empty;
    public string Name { get; set; } = string.Empty;
    public decimal Price { get; set; }
    public int Quantity { get; set; }
}

public class Store
{
    private readonly ConcurrentDictionary<string, Item> _items = new();

    public void Initialize()
    {
        var items = new[]
        {
            new Item { Id = "LIGHT-1", Name = "Lightsaber", Price = 199.99m, Quantity = 5 },
            new Item { Id = "DROID-1", Name = "Astromech Droid", Price = 2499.99m, Quantity = 2 },
            new Item { Id = "FORCE-1", Name = "Force Crystal", Price = 99.99m, Quantity = 10 }
        };

        foreach (var item in items)
        {
            _items.TryAdd(item.Id, item);
        }
    }

    public Item? GetItem(string itemId)
    {
        _items.TryGetValue(itemId, out var item);
        return item;
    }

    public bool UpdateItem(string itemId, Func<Item, Item> updateFn)
    {
        return _items.TryUpdate(
            itemId,
            oldValue => updateFn(oldValue),
            item => true);
    }
}

Implementing Inventory Functions

Now, let's create our input models and functions:

namespace InventoryAgent.Models;

public class CheckStockInput
{
    public string ItemId { get; set; } = string.Empty;
}

public class OrderInput
{
    public string ItemId { get; set; } = string.Empty;
    public int Quantity { get; set; }
}

public class InventoryService
{
    private readonly Store _store;

    public InventoryService(Store store)
    {
        _store = store;
    }

    public async Task<Item?> CheckStock(CheckStockInput input)
    {
        var item = _store.GetItem(input.ItemId);
        if (item == null)
        {
            throw new Exception($"Item not found: {input.ItemId}");
        }

        return item;
    }

    public async Task<Item?> ProcessOrder(OrderInput input)
    {
        var item = _store.GetItem(input.ItemId);
        if (item == null)
        {
            throw new Exception($"Item not found: {input.ItemId}");
        }

        if (item.Quantity < input.Quantity)
        {
            throw new Exception(
                $"Insufficient stock: requested {input.Quantity}, available {item.Quantity}");
        }

        var success = _store.UpdateItem(input.ItemId, current =>
        {
            current.Quantity -= input.Quantity;
            return current;
        });

        if (!success)
        {
            throw new Exception("Failed to update inventory");
        }

        return _store.GetItem(input.ItemId);
    }
}

Creating the Inferable Agent

Now let's connect our inventory system to Inferable:

using Inferable;
using InventoryAgent.Models;

var store = new Store();
store.Initialize();

var service = new InventoryService(store);

var options = new InferableOptions
{
    ApiSecret = Environment.GetEnvironmentVariable("INFERABLE_API_SECRET")
};

var client = new InferableClient(options);

// Register our inventory service
var inventoryService = client.Default;

// Register the CheckStock function
await inventoryService.RegisterFunction(new FunctionRegistration<CheckStockInput>
{
    Name = "checkStock",
    Description = "Check the current stock level of an item",
    Function = async (input) => await service.CheckStock(input)
});

// Register the ProcessOrder function
await inventoryService.RegisterFunction(new FunctionRegistration<OrderInput>
{
    Name = "processOrder",
    Description = "Process an order for an item",
    Function = async (input) => await service.ProcessOrder(input)
});

// Start the service
await inventoryService.Start();

// Keep the application running
await Task.Delay(-1);

Using our multi-agent system

To run the agent, use the inf app command from within the project directory:

inf app

With our agent running, we can interact with it through natural language. Here are some example interactions:

  1. Checking stock:

    "How many lightsabers do we have in stock?"
    
  2. Processing an order:

    "Place an order for 2 Astromech Droids"
    

The agent will:

  1. Understand the natural language request
  2. Map it to the appropriate function
  3. Extract required parameters
  4. Execute the function
  5. Return results in a user-friendly format

How It Works

Inferable orchestrates the interaction by:

  1. Using LLMs to parse natural language input
  2. Mapping requests to registered functions
  3. Validating inputs using .NET type information
  4. Executing functions in your .NET service
  5. Formatting responses for users

The agent can handle complex queries and multi-step operations. For example, it will automatically check stock levels before processing orders.

Security and Control

A key benefit of this implementation is that all inventory operations run on your own machine. Inferable never has direct access to your data - it only orchestrates function calls based on natural language input.

C#-Specific Features

The .NET implementation takes advantage of several C#-specific features:

  1. Uses ConcurrentDictionary for thread-safe operations
  2. Leverages C#'s async/await pattern
  3. Uses nullable reference types for better type safety
  4. Takes advantage of .NET's built-in dependency injection patterns

Next Steps

You can extend this basic agent in several ways:

  1. Add Entity Framework for persistent storage
  2. Implement CQRS pattern for operations
  3. Add approval workflows using requiredApproval configuration
  4. Integrate with real inventory systems
  5. Add data masking for sensitive information using the masked() decorator

Conclusion

We've built a simple but powerful AI agent that manages inventory operations through natural language. The Inferable .NET SDK made it easy to connect our C# code to an AI interface while maintaining full control over our business logic and data.