# KernelService Internal Architecture: Dynamic AI Model Provider Loading in AntSK

> Explore AntSK's KernelService internal architecture for dynamic AI model provider loading. Discover how it efficiently binds OpenAI Azure OpenAI SparkDesk and Ollama at runtime via a factory pattern.

- Repository: [AIDotNet/antsk](https://github.com/aidotnet/antsk)
- Tags: internals
- Published: 2026-02-24

---

**The KernelService in AntSK dynamically instantiates fresh Semantic Kernel instances per application, binding AI providers like OpenAI, Azure OpenAI, SparkDesk, and Ollama at runtime through a three-layer factory architecture that isolates provider configuration from plugin management.**

The AntSK open-source project (aidotnet/antsk) implements a sophisticated kernel management system that enables multi-tenant AI applications to switch between diverse language model providers without code changes. The KernelService acts as the central orchestrator, resolving model configurations, registering provider-specific services, and injecting application-specific plugins. This design ensures complete isolation between applications while supporting dynamic provider selection based on database-driven `AIModel` configurations.

## Three-Layer Architecture Overview

The KernelService implementation in [`src/AntSK.Domain/Domain/Service/KernelService.cs`](https://github.com/aidotnet/antsk/blob/main/src/AntSK.Domain/Domain/Service/KernelService.cs) organizes its responsibilities into three distinct layers:

- **Kernel Factory**: Builds fresh `Kernel` instances on demand, ensuring application isolation
- **Provider Selector**: Maps `AIType` enum values to concrete Semantic Kernel service registrations  
- **Plugin Injector**: Registers default system plugins and application-specific API or native functions

This separation allows the system to support eight distinct AI providers—from standard OpenAI to custom implementations like SparkDesk—while maintaining clean dependency injection patterns.

## Kernel Factory: Creating Isolated Instances per Application

The factory layer ensures that each application receives an independent kernel instance, preventing plugin cross-contamination between tenants. The entry points are `GetKernelByApp()` and `GetKernelByAIModelID()`.

### GetKernelByApp Implementation

Located at lines 50-64 in KernelService.cs, this method resolves the application's configured model and constructs a new kernel:

```csharp
public Kernel GetKernelByApp(Apps app)
{
    // Resolve the model linked to the app
    var chatModel = _aIModels_Repositories.GetFirst(p => p.Id == app.ChatModelID);
    var chatHttpClient = OpenAIHttpClientHandlerUtil.GetHttpClient(chatModel.EndPoint);

    // Build a new kernel and plug the correct provider
    var builder = Kernel.CreateBuilder();
    WithTextGenerationByAIType(builder, chatModel, chatHttpClient);
    _kernel = builder.Build();

    // Register default plugins (conversation summary, KMS)
    RegisterPluginsWithKernel(_kernel);
    return _kernel;
}

```

The method always instantiates a new `KernelBuilder` rather than reusing instances, ensuring that plugin registrations from one application cannot leak into another. The companion method `GetKernelByAIModelID()` (lines 71-80) provides the same functionality when only the model identifier is available.

## Provider Selector: Dynamic AI Model Binding

The `WithTextGenerationByAIType()` method (lines 82-151) serves as the provider selection engine, using a switch statement on the `AIType` enum to register the appropriate Semantic Kernel services.

### Standard Provider Registration

For OpenAI and Azure OpenAI, the method calls the standard Semantic Kernel extension methods:

```csharp
case AIType.OpenAI:
    builder.AddOpenAIChatCompletion(
        modelId: chatModel.ModelName,
        apiKey: chatModel.ModelKey,
        httpClient: chatHttpClient);
    break;

case AIType.AzureOpenAI:
    builder.AddAzureOpenAIChatCompletion(
        deploymentName: chatModel.ModelName,
        apiKey: chatModel.ModelKey,
        endpoint: chatModel.EndPoint);
    break;

```

### Custom Provider Registration via Keyed DI

For non-standard providers like SparkDesk, DashScope, and Mock implementations, the system uses keyed dependency injection to register custom service implementations:

```csharp
case AIType.SparkDesk:
    var settings = chatModel.ModelKey.Split("|");
    var modelVersion = ResolveSparkDeskVersion(chatModel.ModelName);
    var options = new SparkDeskOptions
    {
        AppId = settings[0],
        ApiSecret = settings[1],
        ApiKey = settings[2],
        ModelVersion = modelVersion
    };
    builder.Services.AddKeyedSingleton<ITextGenerationService>(
        "spark-desk", new SparkDeskTextCompletion(options, chatModel.Id));
    builder.Services.AddKeyedSingleton<IChatCompletionService>(
        "spark-desk-chat", new SparkDeskChatCompletion(options, chatModel.Id));
    break;

case AIType.DashScope:
    builder.Services.AddDashScopeChatCompletion(chatModel.ModelKey, chatModel.ModelName);
    break;

```

The `AIType.LLamaFactory` and `AIType.Ollama` cases reuse the OpenAI chat completion method but with a dummy API key, leveraging compatibility layers provided by those services.

## Plugin and Function Injection

After kernel construction, the system registers two categories of plugins: default system plugins and application-specific functions.

### Default System Plugins

The `RegisterPluginsWithKernel()` method (lines 303-309) ensures every kernel includes baseline capabilities:

```csharp
private void RegisterPluginsWithKernel(Kernel kernel)
{
    kernel.ImportPluginFromObject(new ConversationSummaryPlugin(), "ConversationSummaryPlugin");
    kernel.ImportPluginFromPromptDirectory(
        Path.Combine(RepoFiles.SamplePluginsPath(), "KMSPlugin"));
}

```

These provide conversation summarization and knowledge management system (KMS) capabilities regardless of the specific application configuration.

### Application-Specific Functions

The `ImportFunctionsByApp()` method (lines 59-74) dynamically imports functions defined in the application's configuration:

```csharp
public void ImportFunctionsByApp(Apps app, Kernel _kernel)
{
    if (_kernel.Plugins.Any(p => p.Name == "AntSKFunctions"))
        return;

    var functions = new List<KernelFunction>();
    ImportApiFunction(app, functions);      // HTTP-based plugins
    ImportNativeFunction(app, functions);   // Local method plugins
    _kernel.ImportPluginFromFunctions("AntSKFunctions", functions);
}

```

**API Functions**: `ImportApiFunction()` creates `KernelFunction` instances that execute HTTP requests using RestSharp, allowing external APIs to be exposed as native kernel functions.

**Native Functions**: `ImportNativeFunction()` discovers methods marked with the `FunctionService` attribute in the DI container, converting them to `KernelFunction` objects via `CreateFunctionFromMethod`.

## End-to-End Execution Flow

When a request arrives for a specific application:

1. **Resolution**: `GetKernelByApp()` queries the `AIModels` repository using the app's `ChatModelID`
2. **Provider Binding**: `WithTextGenerationByAIType()` registers the appropriate `ITextGenerationService` and `IChatCompletionService` based on the `AIType`
3. **Default Plugins**: `RegisterPluginsWithKernel()` adds conversation summarization and KMS capabilities
4. **Custom Functions**: `ImportFunctionsByApp()` injects application-specific API and native functions
5. **Execution**: The fully configured `Kernel` instance returns to the caller for chat completion or text generation

This flow ensures that each application operates within an isolated kernel environment with precisely the capabilities and AI provider specified in its database configuration.

## Practical Implementation Examples

### Instantiating a Kernel for an Application

```csharp
// Resolve the application entity
var app = _appsRepository.GetById(appId);

// Create the kernel with dynamic provider loading
var kernel = _kernelService.GetKernelByApp(app);

// Import application-specific functions
_kernelService.ImportFunctionsByApp(app, kernel);

```

### Invoking Dynamically Loaded Functions

```csharp
// Retrieve a function registered via ImportApiFunction
var apiFunc = kernel.Plugins.GetFunction("AntSKFunctions", "MyExternalApi");

// Execute with JSON payload
var result = await kernel.InvokeAsync(apiFunc,
    new KernelArguments { ["jsonbody"] = "{\"query\":\"Hello\"}" });

Console.WriteLine(result.GetValue<string>());

```

### Using SparkDesk Provider

```csharp
// Application configured with SparkDesk model
var app = _appsRepository.GetById(appId);
var kernel = _kernelService.GetKernelByApp(app);

// The kernel automatically uses SparkDeskTextCompletion and SparkDeskChatCompletion
// registered via keyed DI in WithTextGenerationByAIType

```

## Summary

- **KernelService** acts as a factory creating fresh, isolated Semantic Kernel instances per application to prevent cross-tenant contamination
- **Dynamic provider selection** occurs in `WithTextGenerationByAIType()`, which maps the `AIType` enum to specific service registrations for OpenAI, Azure OpenAI, SparkDesk, DashScope, LLaMA Factory, Ollama, and Mock providers
- **Keyed dependency injection** enables custom provider implementations like SparkDesk to coexist with standard Semantic Kernel services
- **Plugin architecture** separates default system capabilities (conversation summary, KMS) from application-specific API and native functions imported at runtime
- **Database-driven configuration** allows AI model selection to change without code deployment, with all provider parameters stored in the `AIModels` entity

## Frequently Asked Questions

### How does KernelService handle different AI providers without hardcoding credentials?

The service retrieves provider configuration from the `AIModels` repository, which stores the `AIType`, `ModelName`, `ModelKey`, and `EndPoint` for each model. The `WithTextGenerationByAIType()` method uses this data to call the appropriate Semantic Kernel extension methods or register keyed services, ensuring credentials remain in the database while the code remains provider-agnostic.

### What is the purpose of keyed singleton registration for providers like SparkDesk?

Keyed dependency injection (`AddKeyedSingleton`) allows multiple implementations of `ITextGenerationService` and `IChatCompletionService` to coexist in the same dependency injection container. When the kernel requests a service for a specific provider, the keyed registration ensures the correct implementation resolves without conflicts between different AI types.

### How does the architecture prevent plugin conflicts between applications?

By calling `Kernel.CreateBuilder()` and `builder.Build()` for every request in `GetKernelByApp()`, the service creates completely isolated kernel instances. Since plugins are registered on the specific kernel instance rather than a shared static container, one application's plugin configuration cannot interfere with another's execution environment.

### Can applications mix multiple AI providers simultaneously?

While `GetKernelByApp()` creates a kernel configured for a single chat model, the `GetKernelByAIModelID()` method allows creating additional kernels with different providers. Applications could instantiate multiple kernels—each with different AI types—and orchestrate them manually, though each individual kernel instance binds to one specific provider configuration.