Introduction
Artificial Intelligence is changing how we build modern applications. Traditionally, an application follows predefined business rules to process requests, retrieve data, and return responses. With AI-powered applications, we can go a step further by allowing a Large Language Model (LLM) to understand user intent and choose appropriate actions through controlled application tools.
For example, imagine an e-commerce customer asking:
"Can you tell me where my order is and when it will arrive?"
A traditional application might require the customer to navigate to an order-tracking page, enter an order number, and interpret the result.
An AI-powered customer support agent can understand the question, identify the required information, invoke a C# method to retrieve the order status, and explain the result in natural language.
In this article, we'll explore how to design such an AI agent in .NET using the Microsoft Agent Framework, Clean Architecture, Ollama, and ASP.NET Core Web API.
We'll also discuss why separating AI integration from business logic is important and how this architecture can be extended to other applications.
1. What Is an AI Agent?
An AI agent is an AI-powered system that can interpret a request, decide which available action may help, invoke permitted tools, and use their results to complete a task.
A standard LLM integration commonly follows this flow:
- Receive a user prompt.
- Send the prompt to the model.
- Receive a generated response.
- Return the response to the user.
An agent can follow a more interactive workflow:
- Receive the user's request.
- Understand the user's intent.
- Select an appropriate tool when necessary.
- Invoke the tool with structured arguments.
- Use the tool's result to formulate a response.
For example, a customer asks:
"What's the delivery status of order 1052?"
The AI model may decide that it needs order information and select a tool named GetOrderStatus.
The application executes the corresponding C# method, retrieves the authorized order information, and returns the result to the agent. The agent then generates a user-friendly answer.
The important distinction is that the LLM does not automatically gain access to your database. It can use only the capabilities that your application exposes and permits.
2. Understanding the Microsoft Agent Framework
The Microsoft Agent Framework provides building blocks for developing AI agents and workflows in .NET and other supported environments.
Instead of manually implementing every part of an agent interaction, developers can use framework capabilities to configure agents, connect models, expose functions as tools, and coordinate interactions.
A typical integration involves three components:
- Chat client: Communicates with the underlying language model.
- Agent: Defines the AI assistant's behavior and available capabilities.
- Tools: Expose application functions that the agent can request the model to use.
For a customer support application, the chat client connects to a model, the agent receives instructions about its role, and the tools provide access to approved business operations.
For example, the agent may be allowed to:
- Retrieve order status.
- Check product availability.
- Look up shipping information.
- Retrieve an order's delivery estimate.
It should not receive unrestricted access to every database table or business operation.
Important: Framework APIs and package names can change between versions. The implementation should follow the documentation for the specific Microsoft Agent Framework version being used.
3. Why Use Clean Architecture?
Clean Architecture organizes an application into layers with clearly defined responsibilities. Its primary goal is to keep core business rules independent of infrastructure and external frameworks.
This becomes particularly useful when integrating AI because AI providers, model APIs, and agent frameworks can change over time.
For example, you might begin with a local model running through Ollama. Later, you may decide to use a hosted model provider.
If the application is designed properly, changing the AI integration should not require rewriting your order-management business logic.
A typical solution can be organized into four projects.
3.1 Domain Layer
The Domain Layer contains the core business concepts and rules.
For an e-commerce application, it may include:
- Order
- Product
- Customer
- Order status
- Business rules governing orders
Example:
namespace MyApp.Domain.Entities;
public class Order
{
public int Id { get; private set; }
public string Status { get; private set; }
public Order(int id, string status)
{
Id = id;
Status = status;
}
}
This is a simplified example of an order entity.
The Domain Layer should not depend on the Microsoft Agent Framework, Ollama SDKs, HTTP clients, or database-specific implementations.
Why?
Because business rules should remain usable even if the application changes its AI provider or persistence technology.
3.2 Application Layer
The Application Layer contains use cases and coordinates business operations.
For example, retrieving an order's status is an application use case. It should not need to know how an AI agent is configured.
We can define a repository abstraction:
using MyApp.Domain.Entities;
namespace MyApp.Application.Abstractions;
public interface IOrderRepository
{
Task<Order?> GetByIdAsync(
int orderId,
CancellationToken cancellationToken);
}
Next, create an application service:
using MyApp.Application.Abstractions;
namespace MyApp.Application.Orders;
public sealed class GetOrderStatusService
{
private readonly IOrderRepository _repository;
public GetOrderStatusService(IOrderRepository repository)
{
_repository = repository;
}
public async Task<string?> ExecuteAsync(
int orderId,
CancellationToken cancellationToken)
{
var order = await _repository.GetByIdAsync(
orderId,
cancellationToken);
return order?.Status;
}
}
This service coordinates the use case without knowing whether the request originated from a web controller, a background process, or an AI agent.
In a production application, you would also validate the order ID and enforce the appropriate customer authorization rules.
3.3 Infrastructure Layer
The Infrastructure Layer contains implementations that depend on external technologies.
Examples include:
- Database repositories.
- AI model clients.
- Microsoft Agent Framework integration.
- External APIs.
- Email services.
The infrastructure repository implements the application abstraction:
using MyApp.Application.Abstractions;
using MyApp.Domain.Entities;
namespace MyApp.Infrastructure.Persistence;
public sealed class OrderRepository : IOrderRepository
{
public Task<Order?> GetByIdAsync(
int orderId,
CancellationToken cancellationToken)
{
// Query the actual database here.
throw new NotImplementedException();
}
}
The implementation is intentionally incomplete because the actual database technology and schema determine how the query should be written.
The AI agent configuration also belongs in this layer, keeping framework-specific details outside the Domain Layer.
3.4 API Layer
The API Layer exposes HTTP endpoints to clients.
It receives requests, validates their transport-level requirements, invokes the appropriate application component, and returns HTTP responses.
For example:
public sealed record CustomerQuestion(string Message);
An ASP.NET Core controller can expose an endpoint that delegates the question to an application-level agent abstraction.
[ApiController]
[Route("api/support")]
public class SupportController : ControllerBase
{
private readonly ICustomerSupportAgent _agent;
public SupportController(ICustomerSupportAgent agent)
{
_agent = agent;
}
[HttpPost("ask")]
public async Task<IActionResult> Ask(
[FromBody] CustomerQuestion request,
CancellationToken cancellationToken)
{
var response = await _agent.AskAsync(
request.Message,
cancellationToken);
return Ok(new { response });
}
}
Here, ICustomerSupportAgent represents an application-owned abstraction. It must be defined and implemented as part of the solution.
This example illustrates the architectural boundary rather than a complete, directly runnable project.
The controller does not need to know which LLM provider is being used or how tools are registered.
4. Understanding the Complete Request Flow
Let's combine the layers into a single workflow.
Suppose the customer asks:
"What's the status of my order 1052?"
The request travels through the following steps:
- The API receives the customer's question.
- The application invokes the customer-support agent abstraction.
- The Infrastructure Layer sends the relevant conversation and instructions to the configured model.
- The model determines that order information is required and requests the appropriate tool.
- The framework invokes the registered C# tool.
- The tool calls the Application Layer.
- The application service retrieves data through the repository abstraction.
- The result is returned to the agent.
- The model generates a natural-language answer.
- The API returns the answer to the client.
For example, if the database reports that an order has shipped, the final answer might be:
"Your order has been shipped and is currently in transit."
This answer is based on retrieved application data rather than the model guessing the order's status.
5. What Are AI Tools in .NET?
One of the most important concepts in agent development is tool calling, often called function calling.
A tool is a function that an AI model can request the application to execute.
The function itself runs within your application. The model generally determines which tool to request and what arguments to provide; your framework and application execute the actual code.
Consider a simplified C# method:
public async Task<string> GetOrderStatusAsync(
int orderId,
CancellationToken cancellationToken)
{
var status = await _orderStatusService.ExecuteAsync(
orderId,
cancellationToken);
return status ?? "Order not found.";
}
This method could serve as the basis for an agent tool.
However, defining a normal C# method does not automatically make it available to the LLM. The method must be registered or exposed using the framework's supported tool mechanism.
The tool should also have a clear name, a meaningful description, well-defined parameters, and a controlled return value.
For example:
- Tool name:
GetOrderStatus - Description: Retrieves the status of an order the current customer is authorized to access.
- Parameter:
orderId - Result: The order status or a safe not-found response.
Good descriptions help the model understand when a tool is appropriate. They do not replace authorization or input validation.
6. Why Tool Descriptions and Annotations Matter
When registering a tool, descriptive metadata helps the model understand its purpose.
Compare these two descriptions:
Poor description:
"Gets data."
Better description:
"Retrieves the current status of an order using its ID. Use this tool when a customer asks about order progress."
The second description provides clearer guidance about the tool's intended use.
Depending on the framework version, C# methods can be exposed using function factories, method annotations, or other supported registration mechanisms.
Conceptually, the registration process looks like this:
// Illustrative pseudocode, not a complete framework API call.
var tools = new[]
{
CreateTool(
method: GetOrderStatusAsync,
description: "Retrieve the current status of an order.")
};
// Register the tools with the configured agent.
The exact implementation depends on the Microsoft Agent Framework version and the specific tool-registration API.
The principle remains the same: expose a small set of well-defined operations rather than allowing the model to execute arbitrary code.
7. Configuring Ollama for Local AI Execution
Ollama allows developers to run supported language models locally.
This can be useful during development because you can experiment with agent behavior without necessarily sending every prompt to a hosted model provider.
A typical setup involves:
- Installing Ollama.
- Downloading a model supported by your environment.
- Starting the local model service.
- Configuring the .NET application to use the local endpoint.
- Creating the chat client and agent using compatible framework integrations.
The application configuration might contain settings such as:
{
"AI": {
"Provider": "Ollama",
"Model": "your-local-model",
"Endpoint": "http://localhost:11434"
}
}
The model name is a placeholder. Use a model that is actually installed and compatible with the selected integration.
In a real application, environment-specific settings should be managed appropriately. Credentials, if required, should be stored in environment variables or a secrets manager rather than committed to source control.
Also remember that local execution has hardware requirements. Model size, available memory, inference speed, and tool-calling support all affect the experience.
8. The Agent Factory Pattern
Creating an agent may require several configuration steps:
- Creating a chat client.
- Selecting the model.
- Setting system instructions.
- Registering tools.
- Configuring execution options.
- Creating the agent instance.
Repeating these steps in controllers or application services makes the code difficult to maintain.
An agent factory centralizes the creation process.
Conceptually:
public interface ICustomerSupportAgentFactory
{
ICustomerSupportAgent Create();
}
The infrastructure project can implement this factory using the chosen AI framework.
The API or application layer depends on an application-owned abstraction instead of directly constructing framework-specific agents.
This provides several benefits:
- Centralized agent configuration.
- Easier testing.
- Reduced duplication.
- Clear separation of responsibilities.
- Simpler changes to provider-specific implementation.
Not every application needs to create a new agent instance for every request. Depending on the framework's lifecycle and thread-safety requirements, a configured agent may be reused. Conversation-specific state should be handled separately and safely.
9. Dependency Injection in ASP.NET Core
ASP.NET Core provides built-in dependency injection.
Instead of creating repositories, application services, or AI clients manually inside controllers, you register them with the service container.
For example:
builder.Services.AddScoped<
GetOrderStatusService>();
builder.Services.AddScoped<
IOrderRepository,
OrderRepository>();
The agent abstraction and its factory would also be registered according to their implementations and lifecycle requirements.
For example, an infrastructure extension method might look like this:
public static class DependencyInjection
{
public static IServiceCollection AddAiInfrastructure(
this IServiceCollection services,
IConfiguration configuration)
{
// Register the AI client, tools, agent factory,
// and agent abstraction here.
return services;
}
}
The method is a structural example; the actual registrations depend on the chosen framework APIs.
In Program.cs, the API project can then call:
builder.Services.AddAiInfrastructure(
builder.Configuration);
This keeps the application startup file from becoming crowded with model configuration and tool-registration details.
10. Keeping AI Dependencies Out of the Domain Layer
This is one of the most important Clean Architecture principles in an AI application.
Suppose your domain entity directly references a class from an AI framework.
// Avoid this architectural approach.
public class Order
{
// Domain properties should not depend on
// AI framework-specific classes.
}
The problem is not that a domain entity cannot contain business logic. The problem is introducing an unnecessary dependency on an external AI technology.
If you later replace the AI framework, your domain model should not require changes simply because an infrastructure dependency changed.
A cleaner dependency direction is:
- The Domain Layer contains core business concepts.
- The Application Layer contains use cases and abstractions.
- The Infrastructure Layer implements external integrations.
- The API Layer exposes endpoints and composes the application.
Infrastructure depends on application abstractions, not the other way around.
This makes it easier to unit-test business logic without starting an LLM, connecting to a real database, or making network requests.
11. Security Considerations for AI Agents
An AI agent introduces additional security considerations because it can select from application capabilities based on natural-language input.
The model should never be treated as an authorization mechanism.
For example, a customer may ask:
"Show me the details of order 2000."
Even if the model correctly identifies the order-status tool, the application must verify that the current customer is allowed to access that order.
Important safeguards include:
Authentication: Identify the current user through your normal authentication mechanism.
Authorization: Check access permissions inside the application service or another trusted enforcement layer.
Input validation: Validate order IDs and other arguments supplied to tools.
Least privilege: Expose only the operations required by the agent.
Sensitive-data protection: Return only the fields needed to answer the question.
Controlled side effects: Require appropriate authorization and confirmation before performing actions such as cancelling orders, issuing refunds, or changing account information.
Logging and monitoring: Record useful operational details without unnecessarily logging passwords, tokens, personal information, or sensitive prompts.
A tool description may tell the model not to access another customer's order, but the application must enforce that restriction in code.
12. Testing an AI Agent Application
Testing should cover the ordinary .NET application as well as the AI integration.
Unit tests
Test application services independently.
For example, verify that GetOrderStatusService returns the correct status when a repository provides an order and handles missing orders appropriately.
Use a mock or fake repository to avoid requiring a real database.
Integration tests
Verify that the infrastructure repository communicates correctly with the database and that dependency injection is configured as expected.
Tool tests
Test tool argument validation, authorization, successful results, error handling, and unexpected inputs.
Agent behavior tests
Evaluate whether the model selects the correct tool for representative user questions and produces responses consistent with the tool results.
Because model outputs may vary, avoid relying exclusively on exact-string comparisons. Test important behavioral properties and security boundaries.
End-to-end tests
Test the entire request path, from the HTTP endpoint through tool execution to the final response.
This combination helps identify problems in business logic, infrastructure configuration, tool selection, and model behavior.
13. Applying This Architecture to Other .NET Applications
The same design can be used in applications beyond e-commerce.
For example, a blogging platform could expose controlled tools for retrieving articles, finding related content, or retrieving authorized engagement statistics.
A developer-support application could retrieve documentation and code examples.
An internal business application could help employees find information from authorized company systems.
The key design principle is to keep the AI agent as an orchestration component rather than making it the owner of business rules or data access.
The model interprets requests, the application enforces rules, and the infrastructure connects to external systems.
Conclusion
Building an AI agent in .NET involves more than connecting a language model to an API endpoint. A maintainable solution needs clear architectural boundaries, controlled tools, dependency injection, provider configuration, security controls, and appropriate testing.
The Microsoft Agent Framework can help with agent and tool orchestration. Ollama can provide a local model execution option. ASP.NET Core exposes the application over HTTP, while Clean Architecture keeps the domain and application logic independent of AI-specific implementation details.
When these components are designed together, developers can add AI capabilities without tightly coupling their core business logic to a particular model provider or framework.
The key takeaway: Let the AI model decide which permitted tool may help, but let your .NET application remain responsible for business rules, authorization, data integrity, and execution.
Further Learning
- Microsoft Agent Framework documentation: https://learn.microsoft.com/en-us/agent-framework/
- Ollama documentation: https://docs.ollama.com/
- ASP.NET Core documentation: https://learn.microsoft.com/en-us/aspnet/core/
- Clean Architecture principles: https://learn.microsoft.com/en-us/dotnet/architecture/
Note: The code examples in this article illustrate the architecture and responsibilities of each layer. The agent registration and Ollama integration should be completed using the APIs available in your selected Microsoft Agent Framework version.
Top comments (0)