Архитектура Semantic Kernel: Kernel, Plugins, Functions, Connectors
В этом уроке, детальный разбор того, из чего на уровне кода состоит SK-приложение, и какие у каждого компонента есть варианты реализации. Примеры на C# и Python; концепции одинаковые.
Kernel, центральный объект runtime
Kernel, это dependency injection-контейнер, специализированный под AI-сценарии. Он держит:
- AI-сервисы (chat completion, embeddings, text-to-image, audio).
- Зарегистрированные плагины и их функции.
- Цепочки фильтров (middleware).
- Service provider (через
IServiceCollection). - Конфиг (model id, deployment name, temperature по умолчанию).
Создание через KernelBuilder (паттерн как в ASP.NET Host):
// C#
var builder = Kernel.CreateBuilder()
.AddAzureOpenAIChatCompletion(
deploymentName: "gpt-4o",
endpoint: "https://my-aoai.openai.azure.com",
apiKey: Environment.GetEnvironmentVariable("AOAI_KEY")!)
.AddAzureOpenAITextEmbeddingGeneration(
deploymentName: "text-embedding-3-large",
endpoint: "https://my-aoai.openai.azure.com",
apiKey: Environment.GetEnvironmentVariable("AOAI_KEY")!);
builder.Plugins.AddFromType<EmailPlugin>();
builder.Plugins.AddFromType<CalendarPlugin>();
builder.Services.AddLogging(c => c.AddConsole().SetMinimumLevel(LogLevel.Trace));
var kernel = builder.Build();
# Python
from semantic_kernel import Kernel
from semantic_kernel.connectors.ai.open_ai import AzureChatCompletion, AzureTextEmbedding
kernel = Kernel()
kernel.add_service(AzureChatCompletion(
service_id="chat",
deployment_name="gpt-4o",
endpoint="https://my-aoai.openai.azure.com",
api_key=os.getenv("AOAI_KEY"),
))
kernel.add_plugin(EmailPlugin(), plugin_name="email")
Один Kernel, на запрос пользователя или на сессию. Создавать новый Kernel дёшево; не делитесь одним Kernel между несвязанными сценариями (filters/plugins будут общими).
AI-сервисы (Connectors)
Connector, это реализация интерфейса (IChatCompletionService, IEmbeddingGenerationService, ITextToImageService, IAudioToTextService) поверх конкретного провайдера.
Поддерживаются (через Microsoft.SemanticKernel.Connectors.*):
| Connector | Chat | Embed | Image | Audio |
|---|---|---|---|---|
OpenAI |
✅ | ✅ | ✅ DALL-E | ✅ Whisper, TTS |
AzureOpenAI |
✅ | ✅ | ✅ | ✅ |
MistralAI |
✅ | ✅ | , | , |
Google (Gemini, PaLM, Vertex) |
✅ | ✅ | , | , |
HuggingFace |
✅ | ✅ | , | , |
Ollama (local) |
✅ | ✅ | , | , |
ONNX (local) |
✅ | ✅ | , | , |
Amazon (Bedrock) |
✅ (community) | ✅ | , | , |
Anthropic |
✅ (community) | , | , | , |
Можно регистрировать несколько сервисов с разными serviceId:
builder.AddOpenAIChatCompletion("gpt-4o", apiKey, serviceId: "smart");
builder.AddOpenAIChatCompletion("gpt-4o-mini", apiKey, serviceId: "fast");
// в вызове выбрать через PromptExecutionSettings.ServiceId
В корне SK, Microsoft.Extensions.AI (IChatClient, IEmbeddingGenerator<TInput, TEmbedding>), отдельный пакет, который SK использует под капотом. Это значит, что любой IChatClient (например, из Microsoft.Extensions.AI.OpenAI) можно подключить к SK без отдельного коннектора.
KernelFunction, единица вызова
KernelFunction, это типизированная функция, которую LLM (или ваш код) может вызвать. Source может быть разный.
1. FromMethod, обычный C#/Python метод
public class WeatherPlugin
{
[KernelFunction("get_weather")]
[Description("Получить текущую погоду в городе")]
public async Task<WeatherInfo> GetWeather(
[Description("Название города")] string city,
[Description("Единицы: celsius или fahrenheit")] string units = "celsius")
{
var json = await httpClient.GetStringAsync($"https://api.weather.com/v1/{city}");
return JsonSerializer.Deserialize<WeatherInfo>(json)!;
}
}
builder.Plugins.AddFromType<WeatherPlugin>();
SK автоматически:
- Извлекает имя, описание, параметры из атрибутов и сигнатуры.
- Генерирует JSON Schema (для function calling провайдера).
- Валидирует аргументы при вызове.
- Десериализует возврат в строку для модели.
# Python, через декоратор
from semantic_kernel.functions import kernel_function
from typing import Annotated
class WeatherPlugin:
@kernel_function(name="get_weather", description="Получить текущую погоду в городе")
async def get_weather(
self,
city: Annotated[str, "Название города"],
units: Annotated[str, "celsius или fahrenheit"] = "celsius",
) -> str:
...
2. FromPrompt, функция-промпт
«Семантическая функция», это параметризованный промпт, который сам по себе вызов LLM:
var summarize = kernel.CreateFunctionFromPrompt(
promptTemplate: """
Кратко (≤3 предложения) суммируй следующий текст:
{{$input}}
""",
executionSettings: new OpenAIPromptExecutionSettings { Temperature = 0.2 },
functionName: "summarize");
var summary = await kernel.InvokeAsync(summarize, new() { ["input"] = longText });
Шаблоны бывают трёх форматов: semantic-kernel (default, {{...}}), handlebars, liquid. Handlebars даёт условия, циклы, helpers, нужен для нетривиальных промптов:
var fn = kernel.CreateFunctionFromPrompt(
"{{#if user.vip}}Уважаемый клиент,{{/if}} ваш заказ {{order.id}}...",
templateFormat: "handlebars",
promptTemplateFactory: new HandlebarsPromptTemplateFactory());
Промпт-функцию можно загрузить из папки с YAML/файлами (стандарт SK):
plugins/
WriterPlugin/
Summarize/
skprompt.txt
config.json
Translate/
skprompt.txt
config.json
builder.Plugins.AddFromPromptDirectory("plugins/WriterPlugin");
3. FromOpenApi, целый REST API как набор функций
await kernel.ImportPluginFromOpenApiAsync(
pluginName: "GitHub",
uri: new Uri("https://raw.githubusercontent.com/github/rest-api-description/main/descriptions/api.github.com/api.github.com.yaml"),
executionParameters: new OpenApiFunctionExecutionParameters {
AuthCallback = (request, _) => {
request.Headers.Authorization = new("Bearer", token);
return Task.CompletedTask;
}
});
Все endpoint'ы из OpenAPI становятся доступны как kernel.Plugins["GitHub"]["createIssue"] и т.д. Полезно, когда ваш сервис уже описан в OpenAPI/Swagger.
4. FromMcp, Model Context Protocol сервер
В 2025 SK добавил поддержку MCP (Anthropic-стандарт для подключения LLM к инструментам):
await kernel.Plugins.AddMcpFunctionsFromSseServerAsync(
pluginName: "Filesystem",
serverUri: new Uri("http://localhost:3000/sse"));
Это даёт мгновенный доступ к экосистеме MCP-серверов (filesystem, GitHub, Postgres, Slack, и тд) без оборачивания вручную.
5. FromAIPlugin, формат ChatGPT plugins
Поддержка legacy ChatGPT-plugins формата (ai-plugin.json).
Plugins, группы функций
KernelPlugin, это namespace для функций. Один plugin = одна логически связанная область (Email, Calendar, CRM).
builder.Plugins.AddFromType<EmailPlugin>();
builder.Plugins.AddFromObject(new CalendarPlugin(calendarService));
builder.Plugins.AddFromFunctions("Math", [
KernelFunctionFactory.CreateFromMethod((double a, double b) => a + b, "add"),
KernelFunctionFactory.CreateFromMethod((double a, double b) => a * b, "multiply"),
]);
Plugin-name + function-name составляют уникальный идентификатор для LLM: Email.send_message.
Function Calling, как LLM вызывает функции
В современном SK основной способ оркестрации, native function calling провайдера (OpenAI tools, Anthropic tool use, Mistral function calling).
var settings = new OpenAIPromptExecutionSettings {
FunctionChoiceBehavior = FunctionChoiceBehavior.Auto()
};
var response = await kernel.InvokePromptAsync(
"Отправь Лизе письмо с подтверждением встречи в пятницу в 15:00",
new(settings));
FunctionChoiceBehavior имеет три режима:
.Auto(), модель сама решает, вызывать ли функцию (default)..Required(), модель обязана вызвать одну из функций (для structured output)..None(), функции описаны, но вызов запрещён (для оценки intent).
Можно ограничить набор:
FunctionChoiceBehavior.Auto(functions: [emailPlugin["send"]])
И выключить autoInvoke, чтобы получить намерение модели и вызвать функцию вручную (для approval flow):
FunctionChoiceBehavior.Auto(autoInvoke: false)
Filters, middleware для AI-вызовов
SK даёт три типа фильтров, это AI-эквивалент middleware ASP.NET, оборачивающий вызовы:
IFunctionInvocationFilter, вокруг вызова любой функции
class LoggingFilter : IFunctionInvocationFilter
{
public async Task OnFunctionInvocationAsync(
FunctionInvocationContext context,
Func<FunctionInvocationContext, Task> next)
{
var sw = Stopwatch.StartNew();
await next(context);
logger.LogInformation("{Plugin}.{Func} took {Ms}ms",
context.Function.PluginName, context.Function.Name, sw.ElapsedMilliseconds);
}
}
builder.Services.AddSingleton<IFunctionInvocationFilter, LoggingFilter>();
IPromptRenderFilter, перед/после рендеринга промпта
Полезно для PII-маскирования, prompt injection санитайза, prompt caching.
IAutoFunctionInvocationFilter, вокруг auto-invoked функций (function calling)
Для approval-flow, rate limiting, аудита перед опасными вызовами.
class ApprovalFilter : IAutoFunctionInvocationFilter
{
public async Task OnAutoFunctionInvocationAsync(
AutoFunctionInvocationContext context,
Func<AutoFunctionInvocationContext, Task> next)
{
if (context.Function.Name.StartsWith("delete_") || context.Function.Name == "send_email")
{
if (!await approvalService.AskUserAsync(context)) {
context.Terminate = true; // прерываем цепочку
return;
}
}
await next(context);
}
}
Полный пример end-to-end (C#)
var builder = Kernel.CreateBuilder()
.AddAzureOpenAIChatCompletion("gpt-4o", endpoint, key);
builder.Plugins.AddFromType<EmailPlugin>();
builder.Plugins.AddFromType<CalendarPlugin>();
await builder.Plugins.AddMcpFunctionsFromSseServerAsync("Slack", new Uri("http://mcp/sse"));
builder.Services.AddSingleton<IAutoFunctionInvocationFilter, ApprovalFilter>();
builder.Services.AddOpenTelemetry().WithTracing(t => t.AddSource("Microsoft.SemanticKernel*"));
var kernel = builder.Build();
var settings = new OpenAIPromptExecutionSettings {
FunctionChoiceBehavior = FunctionChoiceBehavior.Auto(),
Temperature = 0.2,
MaxTokens = 1500,
};
var history = new ChatHistory(systemMessage: "Ты ассистент CRM команды. Действуй уважительно. Запрашивай подтверждение для операций отправки.");
history.AddUserMessage("Найди в Slack всех, кто упоминал клиента Acme на этой неделе, и отправь их сводку Алисе на email.");
var chat = kernel.GetRequiredService<IChatCompletionService>();
var response = await chat.GetChatMessageContentAsync(history, settings, kernel);
Console.WriteLine(response.Content);
Под капотом за один такой вызов:
- SK соберёт описание всех зарегистрированных функций в формат tools OpenAI.
- Отправит запрос с историей и tools в Azure OpenAI.
- Если модель вернёт tool_calls, SK выполнит каждую через фильтры → вернёт результат модели.
- Цикл может повториться несколько раз (multi-turn function calling).
- Каждый шаг попадёт в OpenTelemetry.
Главное
Архитектура SK, набор простых хорошо состыкованных абстракций: Kernel-DI, KernelFunction (метод/промпт/OpenAPI/MCP), KernelPlugin как namespace, Connector-абстракция над AI-сервисами, function calling как primary orchestration, filters как middleware.
Эти 5–6 концепций покрывают 90% сценариев. Остальные 10%, память, multi-agent и бизнес-процессы, разберём в следующих уроках.