Learnly

Из чего состоит ИИ-помощник на Semantic Kernel

Архитектура 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);

Под капотом за один такой вызов:

  1. SK соберёт описание всех зарегистрированных функций в формат tools OpenAI.
  2. Отправит запрос с историей и tools в Azure OpenAI.
  3. Если модель вернёт tool_calls, SK выполнит каждую через филь­тры → вернёт результат модели.
  4. Цикл может повториться несколько раз (multi-turn function calling).
  5. Каждый шаг попадёт в OpenTelemetry.

Главное

Архитектура SK, набор простых хорошо состыкованных абстракций: Kernel-DI, KernelFunction (метод/промпт/OpenAPI/MCP), KernelPlugin как namespace, Connector-абстракция над AI-сервисами, function calling как primary orchestration, filters как middleware.

Эти 5–6 концепций покрывают 90% сценариев. Остальные 10%, память, multi-agent и бизнес-процессы, разберём в следующих уроках.

AI-тест
1 / 11

Какой компонент системы Semantic Kernel выполняет интеллектуальные функции, такие как понимание запросов и принятие решений?