Skip to content

Интеграция YandexGPT с семантическим ядром Microsoft

Изменено: at 09:30Предложить изменение

Интеграция YandexGPT с семантическим ядром Microsoft

Аннотация

В данной статье рассматривается процесс интеграции языковой модели YandexGPT с семантическим ядром Microsoft (Microsoft Semantic Kernel). Исследуются особенности взаимодействия с нейросетями через API, анализируются различия между API Yandex и OpenAI, а также представляется пошаговая методология разработки коннектора, позволяющего использовать возможности YandexGPT в экосистеме Microsoft. Особое внимание уделяется интеграции с инструментом Prompty для эффективного управления промптами.

LLM - semantic_kernel_poster.png

Оглавление

1. LLM модели и способы взаимодействия с нейросетями

1.1 Большие языковые модели (LLM)

Большие языковые модели (Large Language Models, LLM) представляют собой класс нейронных сетей, обученных на огромных массивах текстовых данных с целью понимания и генерации человеческого языка. Эти модели используют архитектуру трансформеров, предложенную в 2017 году, которая позволяет эффективно обрабатывать последовательности данных, учитывая контекст и взаимосвязи между элементами.

Современные LLM, такие как GPT (Generative Pre-trained Transformer) от OpenAI, Claude от Anthropic, Llama от Meta и YandexGPT от Яндекса, содержат миллиарды параметров и способны решать широкий спектр задач обработки естественного языка: от генерации текста и перевода до ответов на вопросы и анализа документов.

LLM - basic.png

1.2 Способы интеграции моделей в конечное программное обеспечение

Интеграция языковых моделей в программное обеспечение позволяет расширить функциональность приложений, добавив возможности обработки естественного языка, генерации контента и автоматизации задач. Существует несколько ключевых подходов к такой интеграции:

LLM - llm_intergration.png

1.2.1 Агентные системы (AI Agents)

Агентные системы представляют собой автономные программные сущности, использующие LLM для принятия решений и выполнения действий. Они характеризуются:

Примеры использования: виртуальные ассистенты, автоматизированные системы поддержки клиентов, исследовательские агенты для анализа данных и более сложные системы, интегрируемые в программное обеспечение.

1.2.2 Функциональные вызовы (Function Calling)

Function Calling (или Tool Use) — это механизм, позволяющий языковым моделям вызывать внешние функции для выполнения действий, недоступных самой модели:

Этот подход особенно полезен для:

1.2.3 RAG (Retrieval-Augmented Generation)

LLM - rag.png

RAG объединяет поисковые системы с генеративными моделями, позволяя обогащать ответы релевантной информацией из внешних источников:

Преимущества RAG:

1.2.4 Оркестрация и цепочки (Orchestration & Chains)

LLM - chain.png

Этот подход предполагает создание последовательностей операций, где выход одной операции становится входом для следующей:

1.3 Необходимость интеграции через API

Интеграция с языковыми моделями через API предоставляет ряд существенных преимуществ:

  1. Глубокая интеграция — API-подход позволяет включить ИИ функции напрямую в программное обеспечение без необходимости строить чат модели рядом с пользовательским интерфейсом.

  2. Актуальность моделей — провайдеры API регулярно обновляют модели, предоставляя доступ к последним функциям без необходимости дообучения.

  3. Гибкость — возможность выбора и переключения между различными моделями в зависимости от требований задачи.

  4. Стандартизация — API предоставляют унифицированный интерфейс для взаимодействия с различными моделями.

  5. Мультимодальность — современные API часто предоставляют доступ не только к текстовым, но и к мультимодальным возможностям (обработка изображений, аудио).

Интеграция через API особенно важна для корпоративных приложений, где требуется соблюдение строгих требований к производительности, безопасности и соответствию нормативным требованиям.

1.4 Промпт-инженерия

Ключевым аспектом эффективного взаимодействия с LLM является промпт-инженерия - искусство формулирования запросов к модели таким образом, чтобы получить желаемый результат. Это включает в себя:

Инструменты вроде Prompty помогают стандартизировать и оптимизировать этот процесс, обеспечивая воспроизводимость результатов и упрощая управление промптами.

2. API OpenAI

2.1 Структура API OpenAI

API OpenAI стал де-факто стандартом в индустрии для взаимодействия с языковыми моделями. Его основные компоненты включают:

  1. Аутентификация - использование API-ключей для авторизации запросов.

  2. Endpoints - специализированные конечные точки для различных моделей и функций:

    • /v1/chat/completions - для диалоговых моделей
    • /v1/embeddings - для получения векторных представлений текста
    • /v1/images/generations - для генерации изображений
  3. Параметры запросов:

    • model - идентификатор используемой модели
    • messages - массив сообщений для диалоговых моделей
    • temperature - параметр, контролирующий случайность генерации
    • max_tokens - ограничение длины ответа
    • и другие специфические параметры
  4. Форматы ответов - структурированные JSON-ответы, содержащие сгенерированный контент и метаданные.

2.2 Преимущества API OpenAI

API OpenAI предлагает ряд преимуществ, которые сделали его популярным среди разработчиков:

2.3 Ограничения и проблемы совместимости

Несмотря на популярность, API OpenAI имеет ряд ограничений:

Последний пункт особенно актуален при работе с альтернативными моделями, такими как YandexGPT, что создает необходимость в разработке специализированных коннекторов.

3. Microsoft Semantic Kernel

3.1 Архитектура и концепция Semantic Kernel

LLM - semantic_kernel.png

Microsoft Semantic Kernel представляет собой открытый фреймворк, который упрощает интеграцию языковых моделей в приложения. Ключевая концепция Semantic Kernel заключается в абстрагировании сложности взаимодействия с различными LLM и предоставлении унифицированного интерфейса для разработчиков.

Основные компоненты архитектуры Semantic Kernel:

  1. Kernel (Ядро) - центральный компонент, координирующий взаимодействие между различными частями системы.

  2. Functions (Функции) - базовые блоки функциональности, которые могут быть:

    • Semantic Functions - функции, использующие LLM для выполнения задач
    • Native Functions - обычные программные функции, написанные на языке программирования
  3. Plugins (Плагины) - коллекции функций, сгруппированные по назначению.

  4. Memory (Память) - компонент для хранения и извлечения информации, включая векторные базы данных.

  5. Connectors (Коннекторы) - адаптеры для различных LLM-сервисов, таких как OpenAI, Azure OpenAI, и в нашем случае - YandexGPT.

3.2 Преимущества использования Semantic Kernel

Semantic Kernel предлагает ряд существенных преимуществ:

3.3 Основные компоненты для интеграции с LLM

Для интеграции новой языковой модели с Semantic Kernel необходимо реализовать несколько ключевых компонентов:

  1. IChatCompletionService - интерфейс для генерации ответов в формате диалога
  2. ITextCompletionService - интерфейс для текстовых завершений
  3. ITextEmbeddingGenerationService - интерфейс для создания векторных представлений текста

Реализация этих интерфейсов позволяет Semantic Kernel взаимодействовать с новой моделью так же, как с уже поддерживаемыми моделями, обеспечивая совместимость и взаимозаменяемость.

3.4 Паттерны проектирования в Semantic Kernel

Microsoft Semantic Kernel использует ряд паттернов проектирования, которые обеспечивают его расширяемость и гибкость при интеграции с различными языковыми моделями. Понимание этих паттернов необходимо для эффективной разработки коннекторов и расширений.

3.4.1 Паттерн “Стратегия” (Strategy Pattern)

Semantic Kernel активно использует паттерн “Стратегия”, позволяющий выбирать конкретную реализацию алгоритма во время выполнения. Это особенно важно для интеграции с различными LLM:

// Интерфейс стратегии
public interface IChatCompletionService
{
    Task<IReadOnlyList<ChatMessageContent>> GetChatMessageContentsAsync(
        ChatHistory chat,
        PromptExecutionSettings? settings = null,
        Kernel? kernel = null,
        CancellationToken cancellationToken = default);
}

// Конкретные реализации стратегии
public class OpenAIChatCompletionService : IChatCompletionService { ... }
public class AzureOpenAIChatCompletionService : IChatCompletionService { ... }
public class YandexAIChatCompletionService : IChatCompletionService { ... }

Этот паттерн позволяет клиентскому коду работать с абстракцией, не заботясь о конкретной реализации, что обеспечивает взаимозаменяемость различных LLM-сервисов.

3.4.2 Паттерн “Фабрика” (Factory Pattern)

Для создания экземпляров сервисов Semantic Kernel использует паттерн “Фабрика”, который инкапсулирует логику создания объектов:

// Пример фабричного метода в KernelBuilder
public static KernelBuilder AddYandexAIChatCompletion(
    this KernelBuilder builder,
    string deployment,
    string apiKey,
    string folderId,
    string? serviceId = null)
{
    builder.Services.AddKeyedSingleton<IChatCompletionService>(
        serviceId ?? deployment,
        (serviceProvider, _) => new YandexAIChatCompletionService(deployment, apiKey, folderId));
    
    return builder;
}

Этот паттерн упрощает создание и конфигурирование сервисов, скрывая сложность их инициализации.

3.4.3 Паттерн “Адаптер” (Adapter Pattern)

Коннекторы в Semantic Kernel по сути являются адаптерами, преобразующими интерфейс одной системы (например, API YandexGPT) в интерфейс, ожидаемый другой системой (Semantic Kernel):

// Адаптер для YandexGPT
public sealed class YandexAIChatCompletionService : IChatCompletionService
{
    // Метод адаптера, преобразующий запрос Semantic Kernel в формат YandexGPT
    public async Task<IReadOnlyList<ChatMessageContent>> GetChatMessageContentsAsync(...)
    {
        // Преобразование ChatHistory в формат сообщений Yandex
        var messages = chat.Select(m => new YandexAIMessage
        {
            Role = ConvertRole(m.Role),
            Text = m.Content
        }).ToList();
        
        // Отправка запроса в формате YandexGPT
        var response = await SendRequestAsync(...);
        
        // Преобразование ответа YandexGPT в формат Semantic Kernel
        return new List<ChatMessageContent>
        {
            new(AuthorRole.Assistant, response.Result.Alternatives[0].Message.Text)
        };
    }
}

Этот паттерн позволяет системам с несовместимыми интерфейсами работать вместе.

3.4.4 Паттерн “Декоратор” (Decorator Pattern)

Semantic Kernel использует декораторы для добавления дополнительной функциональности к сервисам без изменения их основного кода:

// Пример декоратора для логирования
public class LoggingChatCompletionService : IChatCompletionService
{
    private readonly IChatCompletionService _innerService;
    private readonly ILogger _logger;
    
    public LoggingChatCompletionService(IChatCompletionService innerService, ILogger logger)
    {
        _innerService = innerService;
        _logger = logger;
    }
    
    public async Task<IReadOnlyList<ChatMessageContent>> GetChatMessageContentsAsync(...)
    {
        _logger.LogInformation("Sending chat completion request");
        var result = await _innerService.GetChatMessageContentsAsync(...);
        _logger.LogInformation("Received chat completion response");
        return result;
    }
}

Декораторы позволяют добавлять такую функциональность, как логирование, кэширование, повторные попытки и т.д., не изменяя основные реализации сервисов.

3.4.5 Паттерн “Цепочка ответственности” (Chain of Responsibility)

Для обработки последовательностей операций Semantic Kernel использует паттерн “Цепочка ответственности”, особенно в контексте планировщиков и оркестрации:

// Пример цепочки функций
var result = await kernel.RunAsync(async context =>
{
    // Первый обработчик в цепочке
    context = await summarizeFunction.InvokeAsync(kernel, context);
    
    // Второй обработчик в цепочке
    context = await translateFunction.InvokeAsync(kernel, context);
    
    // Третий обработчик в цепочке
    context = await formatFunction.InvokeAsync(kernel, context);
    
    return context;
});

Этот паттерн позволяет создавать гибкие цепочки обработки, где каждый элемент может модифицировать контекст и передать его следующему.

3.4.6 Паттерн “Внедрение зависимостей” (Dependency Injection)

Semantic Kernel активно использует внедрение зависимостей для управления сервисами и их жизненным циклом:

// Регистрация сервисов
var kernel = Kernel.CreateBuilder()
    .AddYandexAIChatCompletion(deployment, apiKey, folderId)
    .Build();

// Использование сервисов через внедрение зависимостей
var chatCompletion = kernel.GetRequiredService<IChatCompletionService>();

Этот паттерн упрощает тестирование, обеспечивает гибкость конфигурации и уменьшает связанность компонентов.

3.4.7 Паттерн “Команда” (Command Pattern)

Функции в Semantic Kernel реализуют паттерн “Команда”, инкапсулируя запрос как объект:

// Создание команды (функции)
var function = kernel.CreateFunctionFromPrompt("Summarize this text: {{$input}}");

// Выполнение команды
var result = await function.InvokeAsync(kernel, new KernelArguments { ["input"] = text });

Этот паттерн позволяет параметризовать клиентов с запросами, ставить запросы в очередь и поддерживать отмену операций.

Понимание и применение этих паттернов проектирования при разработке коннекторов для Semantic Kernel обеспечивает согласованность с архитектурой фреймворка и максимальную совместимость с существующими компонентами.

4. Пошаговая разработка коннектора для Yandex API

4.1 Анализ различий между API Yandex и OpenAI

Прежде чем приступить к разработке коннектора, необходимо проанализировать различия между API Yandex и OpenAI:

  1. Структура аутентификации:

    • OpenAI использует API-ключ в заголовке Authorization
    • Yandex требует API-ключ в заголовке Authorization и дополнительный параметр folderId для идентификации проекта
  2. Формат запросов:

    • OpenAI:
      {
        "model": "gpt-3.5-turbo",
        "messages": [{"role": "user", "content": "Hello"}],
        "temperature": 0.7
      }
      
    • Yandex:
      {
        "modelUri": "gpt://b1gvmob95yysaplct532/yandexgpt/latest",
        "messages": [{"role": "user", "text": "Hello"}],
        "temperature": 0.7
      }
      
  3. URL эндпоинтов:

    • OpenAI: https://api.openai.com/v1/chat/completions
    • Yandex: https://llm.api.cloud.yandex.net/foundationModels/v1/completion
  4. Форматы ответов:

    • OpenAI возвращает структуру с полем choices[0].message.content
    • Yandex возвращает структуру с полем result.alternatives[0].message.text

4.2 Создание базовой структуры коннектора

Начнем с создания основной структуры проекта:

namespace Microsoft.SemanticKernel.Connectors.YandexAI
{
    public sealed class YandexAIChatCompletionService : IChatCompletionService
    {
        private readonly string _apiKey;
        private readonly string _folderId;
        private readonly string _deployment;
        private readonly HttpClient _httpClient;
        
        public YandexAIChatCompletionService(string deployment, string apiKey, string folderId, HttpClient? httpClient = null)
        {
            _deployment = deployment;
            _apiKey = apiKey;
            _folderId = folderId;
            _httpClient = httpClient ?? new HttpClient();
        }
        
        // Реализация методов интерфейса
    }
}

4.3 Реализация метода GetChatMessageContentsAsync

Ключевой метод для интеграции с Semantic Kernel - GetChatMessageContentsAsync, который преобразует запросы Semantic Kernel в формат Yandex API:

public async Task<IReadOnlyList<ChatMessageContent>> GetChatMessageContentsAsync(
    ChatHistory chat,
    PromptExecutionSettings? settings = null,
    Kernel? kernel = null,
    CancellationToken cancellationToken = default)
{
    // Преобразование ChatHistory в формат сообщений Yandex
    var messages = chat.Select(m => new YandexAIMessage
    {
        Role = ConvertRole(m.Role),
        Text = m.Content
    }).ToList();
    
    // Создание запроса
    var request = new YandexAIChatCompletionRequest
    {
        ModelUri = $"gpt://{_folderId}/{_deployment}",
        Messages = messages,
        Temperature = settings?.ExtensionData?.GetValueOrDefault("temperature", 0.7f) ?? 0.7f,
        MaxTokens = settings?.ExtensionData?.GetValueOrDefault("max_tokens", 1024) ?? 1024
    };
    
    // Отправка запроса
    var response = await SendRequestAsync(request, cancellationToken);
    
    // Преобразование ответа в формат Semantic Kernel
    return new List<ChatMessageContent>
    {
        new(AuthorRole.Assistant, response.Result.Alternatives[0].Message.Text)
    };
}

private async Task<YandexAIChatCompletionResponse> SendRequestAsync(
    YandexAIChatCompletionRequest request,
    CancellationToken cancellationToken)
{
    using var httpRequest = new HttpRequestMessage(HttpMethod.Post, "https://llm.api.cloud.yandex.net/foundationModels/v1/completion")
    {
        Content = new StringContent(JsonSerializer.Serialize(request), Encoding.UTF8, "application/json")
    };
    
    httpRequest.Headers.Add("Authorization", $"Api-Key {_apiKey}");
    
    using var response = await _httpClient.SendAsync(httpRequest, cancellationToken);
    response.EnsureSuccessStatusCode();
    
    var content = await response.Content.ReadAsStringAsync(cancellationToken);
    return JsonSerializer.Deserialize<YandexAIChatCompletionResponse>(content)!;
}

private string ConvertRole(AuthorRole role)
{
    return role switch
    {
        AuthorRole.User => "user",
        AuthorRole.Assistant => "assistant",
        AuthorRole.System => "system",
        _ => throw new ArgumentException($"Unsupported role: {role}")
    };
}

4.4 Реализация вспомогательных классов

Для работы с API Yandex необходимо создать несколько вспомогательных классов:

public class YandexAIMessage
{
    [JsonPropertyName("role")]
    public string Role { get; set; } = string.Empty;
    
    [JsonPropertyName("text")]
    public string Text { get; set; } = string.Empty;
}

public class YandexAIChatCompletionRequest
{
    [JsonPropertyName("modelUri")]
    public string ModelUri { get; set; } = string.Empty;
    
    [JsonPropertyName("messages")]
    public List<YandexAIMessage> Messages { get; set; } = new();
    
    [JsonPropertyName("temperature")]
    public float Temperature { get; set; } = 0.7f;
    
    [JsonPropertyName("maxTokens")]
    public int MaxTokens { get; set; } = 1024;
}

public class YandexAIChatCompletionResponse
{
    [JsonPropertyName("result")]
    public YandexAIResult Result { get; set; } = new();
}

public class YandexAIResult
{
    [JsonPropertyName("alternatives")]
    public List<YandexAIAlternative> Alternatives { get; set; } = new();
}

public class YandexAIAlternative
{
    [JsonPropertyName("message")]
    public YandexAIMessage Message { get; set; } = new();
}

Интеграция YandexGPT с семантическим ядром Microsoft

4.5 Реализация метода расширения для Kernel Builder

Для упрощения интеграции с Semantic Kernel создадим метод расширения для KernelBuilder:

public static class YandexAIKernelBuilderExtensions
{
    public static KernelBuilder AddYandexAIChatCompletion(
        this KernelBuilder builder,
        string deployment,
        string apiKey,
        string folderId,
        string? serviceId = null,
        HttpClient? httpClient = null)
    {
        builder.Services.AddKeyedSingleton<IChatCompletionService>(
            serviceId ?? deployment,
            (serviceProvider, _) => new YandexAIChatCompletionService(deployment, apiKey, folderId, httpClient));
        
        return builder;
    }
}

Этот метод расширения позволяет разработчикам легко добавлять поддержку YandexGPT в свои приложения с помощью цепочки методов:

var kernel = Kernel.CreateBuilder()
    .AddYandexAIChatCompletion("yandexgpt", apiKey, folderId)
    .Build();

4.6 Обработка ошибок и исключений

Важной частью разработки коннектора является корректная обработка ошибок:

private async Task<YandexAIChatCompletionResponse> SendRequestAsync(
    YandexAIChatCompletionRequest request,
    CancellationToken cancellationToken)
{
    try
    {
        using var httpRequest = new HttpRequestMessage(HttpMethod.Post, "https://llm.api.cloud.yandex.net/foundationModels/v1/completion")
        {
            Content = new StringContent(JsonSerializer.Serialize(request), Encoding.UTF8, "application/json")
        };
        
        httpRequest.Headers.Add("Authorization", $"Api-Key {_apiKey}");
        
        using var response = await _httpClient.SendAsync(httpRequest, cancellationToken);
        
        if (!response.IsSuccessStatusCode)
        {
            var errorContent = await response.Content.ReadAsStringAsync(cancellationToken);
            throw new YandexAIException($"Error calling YandexAI API: {response.StatusCode} - {errorContent}");
        }
        
        var content = await response.Content.ReadAsStringAsync(cancellationToken);
        var result = JsonSerializer.Deserialize<YandexAIChatCompletionResponse>(content);
        
        if (result == null || result.Result.Alternatives.Count == 0)
        {
            throw new YandexAIException("YandexAI returned an empty response");
        }
        
        return result;
    }
    catch (Exception ex) when (ex is not YandexAIException)
    {
        throw new YandexAIException("Error processing YandexAI request", ex);
    }
}

public class YandexAIException : Exception
{
    public YandexAIException(string message) : base(message) { }
    public YandexAIException(string message, Exception innerException) : base(message, innerException) { }
}

Наш подход к обработке ошибок включает:

  1. Специализированные исключения — создание класса YandexAIException для представления ошибок, связанных с YandexGPT
  2. Детальные сообщения об ошибках — включение информации о статусе HTTP и содержимом ответа
  3. Проверки валидности — проверка наличия и корректности ответа
  4. Обертывание исключений — преобразование общих исключений в специализированные для единообразной обработки

4.7 Реализация поддержки потоковой генерации

Для поддержки потоковой генерации (streaming) необходимо реализовать дополнительный метод:

public async IAsyncEnumerable<StreamingChatMessageContent> GetStreamingChatMessageContentsAsync(
    ChatHistory chat,
    PromptExecutionSettings? settings = null,
    Kernel? kernel = null,
    [EnumeratorCancellation] CancellationToken cancellationToken = default)
{
    // Преобразование ChatHistory в формат сообщений Yandex
    var messages = chat.Select(m => new YandexAIMessage
    {
        Role = ConvertRole(m.Role),
        Text = m.Content
    }).ToList();
    
    // Создание запроса
    var request = new YandexAIChatCompletionRequest
    {
        ModelUri = $"gpt://{_folderId}/{_deployment}",
        Messages = messages,
        Temperature = settings?.ExtensionData?.GetValueOrDefault("temperature", 0.7f) ?? 0.7f,
        MaxTokens = settings?.ExtensionData?.GetValueOrDefault("max_tokens", 1024) ?? 1024,
        Stream = true
    };
    
    // Отправка запроса и обработка потокового ответа
    await foreach (var chunk in SendStreamingRequestAsync(request, cancellationToken))
    {
        yield return new StreamingChatMessageContent(
            AuthorRole.Assistant,
            chunk.Result.Alternatives[0].Message.Text,
            chunk.IsLastChunk);
    }
}

private async IAsyncEnumerable<YandexAIStreamingResponse> SendStreamingRequestAsync(
    YandexAIChatCompletionRequest request,
    [EnumeratorCancellation] CancellationToken cancellationToken)
{
    using var httpRequest = new HttpRequestMessage(HttpMethod.Post, "https://llm.api.cloud.yandex.net/foundationModels/v1/completion")
    {
        Content = new StringContent(JsonSerializer.Serialize(request), Encoding.UTF8, "application/json")
    };
    
    httpRequest.Headers.Add("Authorization", $"Api-Key {_apiKey}");
    
    using var response = await _httpClient.SendAsync(httpRequest, HttpCompletionOption.ResponseHeadersRead, cancellationToken);
    response.EnsureSuccessStatusCode();
    
    using var stream = await response.Content.ReadAsStreamAsync(cancellationToken);
    using var reader = new StreamReader(stream);
    
    while (!reader.EndOfStream)
    {
        var line = await reader.ReadLineAsync();
        if (string.IsNullOrEmpty(line)) continue;
        
        if (line.StartsWith("data: "))
        {
            var json = line.Substring(6);
            if (json == "[DONE]") break;
            
            var chunk = JsonSerializer.Deserialize<YandexAIStreamingResponse>(json);
            if (chunk != null)
            {
                yield return chunk;
            }
        }
    }
}

Для поддержки потоковой генерации необходимо добавить соответствующие классы:

public class YandexAIStreamingResponse
{
    [JsonPropertyName("result")]
    public YandexAIResult Result { get; set; } = new();
    
    [JsonPropertyName("isLastChunk")]
    public bool IsLastChunk { get; set; }
}

4.8 Реализация поддержки векторных эмбеддингов

Для полноценной интеграции с Semantic Kernel полезно также реализовать поддержку векторных эмбеддингов:

public sealed class YandexAITextEmbeddingGenerationService : ITextEmbeddingGenerationService
{
    private readonly string _apiKey;
    private readonly string _folderId;
    private readonly string _deployment;
    private readonly HttpClient _httpClient;
    
    public YandexAITextEmbeddingGenerationService(string deployment, string apiKey, string folderId, HttpClient? httpClient = null)
    {
        _deployment = deployment;
        _apiKey = apiKey;
        _folderId = folderId;
        _httpClient = httpClient ?? new HttpClient();
    }
    
    public async Task<IList<ReadOnlyMemory<float>>> GenerateEmbeddingsAsync(
        IList<string> texts,
        Kernel? kernel = null,
        CancellationToken cancellationToken = default)
    {
        var results = new List<ReadOnlyMemory<float>>();
        
        foreach (var text in texts)
        {
            var request = new YandexAIEmbeddingRequest
            {
                ModelUri = $"emb://{_folderId}/{_deployment}",
                Text = text
            };
            
            var response = await SendEmbeddingRequestAsync(request, cancellationToken);
            var embedding = response.Embedding.Select(f => (float)f).ToArray();
            results.Add(new ReadOnlyMemory<float>(embedding));
        }
        
        return results;
    }
    
    private async Task<YandexAIEmbeddingResponse> SendEmbeddingRequestAsync(
        YandexAIEmbeddingRequest request,
        CancellationToken cancellationToken)
    {
        using var httpRequest = new HttpRequestMessage(HttpMethod.Post, "https://llm.api.cloud.yandex.net/foundationModels/v1/embedding")
        {
            Content = new StringContent(JsonSerializer.Serialize(request), Encoding.UTF8, "application/json")
        };
        
        httpRequest.Headers.Add("Authorization", $"Api-Key {_apiKey}");
        
        using var response = await _httpClient.SendAsync(httpRequest, cancellationToken);
        response.EnsureSuccessStatusCode();
        
        var content = await response.Content.ReadAsStringAsync(cancellationToken);
        return JsonSerializer.Deserialize<YandexAIEmbeddingResponse>(content)!;
    }
}

public class YandexAIEmbeddingRequest
{
    [JsonPropertyName("modelUri")]
    public string ModelUri { get; set; } = string.Empty;
    
    [JsonPropertyName("text")]
    public string Text { get; set; } = string.Empty;
}

public class YandexAIEmbeddingResponse
{
    [JsonPropertyName("embedding")]
    public List<double> Embedding { get; set; } = new();
}

И соответствующий метод расширения для KernelBuilder:

public static KernelBuilder AddYandexAITextEmbeddingGeneration(
    this KernelBuilder builder,
    string deployment,
    string apiKey,
    string folderId,
    string? serviceId = null,
    HttpClient? httpClient = null)
{
    builder.Services.AddKeyedSingleton<ITextEmbeddingGenerationService>(
        serviceId ?? deployment,
        (serviceProvider, _) => new YandexAITextEmbeddingGenerationService(deployment, apiKey, folderId, httpClient));
    
    return builder;
}

5. Интеграция Microsoft Semantic Kernel и инструмента Prompty

5.1 Что такое Prompty и его преимущества

LLM - prompty.png

Prompty - это новый инструмент в экосистеме LLM, предназначенный для управления промптами. Он предлагает следующие преимущества:

5.2 Создание и использование промптов с Prompty

Промпты в Prompty создаются в виде файлов с расширением .prompty. Пример базового промпта:

// promties/basic.prompty
system:
Ты - ассистент для магазина туристического снаряжения. Ты помогаешь клиентам выбрать подходящее снаряжение для их походов и путешествий.

user: {{$question}}

Этот промпт можно использовать в коде следующим образом:

KernelArguments kernelArguments = new()
{
    { "question", "What can you tell me about your tents?" }
};

var prompty = kernel.CreateFunctionFromPromptyFile("promties/basic.prompty");
var result = await prompty.InvokeAsync<string>(kernel, kernelArguments);

5.3 Интеграция YandexGPT с Prompty

Для использования YandexGPT с Prompty необходимо настроить Semantic Kernel с нашим коннектором:

var kernel = Kernel.CreateBuilder()
    .AddYandexAIChatCompletion(deployment, apiKey, folderId)
    .Build();

После этого можно создавать и использовать промпты с Prompty, которые будут выполняться через YandexGPT:

// Пример более сложного промпта для генерации тегов
var tagsPrompt = kernel.CreateFunctionFromPromptyFile("promties/space-tags-suggestion.prompty");
var tags = await tagsPrompt.InvokeAsync<string>(kernel);

5.4 Примеры использования

Рассмотрим несколько примеров использования YandexGPT с Prompty:

  1. Базовый диалог:
var basicPrompt = kernel.CreateFunctionFromPromptyFile("promties/basic.prompty");
var response = await basicPrompt.InvokeAsync<string>(kernel, new KernelArguments
{
    { "question", "Какие палатки вы рекомендуете для зимнего похода?" }
});
  1. Генерация описания города:
var townPrompt = kernel.CreateFunctionFromPromptyFile("promties/town.prompty");
var description = await townPrompt.InvokeAsync<string>(kernel);
  1. Предложение тегов для пространства:
var tagsPrompt = kernel.CreateFunctionFromPromptyFile("promties/space-tags-suggestion.prompty");
var tags = await tagsPrompt.InvokeAsync<string>(kernel);

5.5 Оптимизация производительности

Для оптимизации производительности при работе с YandexGPT и Prompty можно использовать следующие подходы:

  1. Кэширование HTTP-клиента:
var httpClient = new HttpClient();
var kernel = Kernel.CreateBuilder()
    .AddYandexAIChatCompletion(deployment, apiKey, folderId, httpClient: httpClient)
    .Build();
  1. Измерение времени выполнения:
async Task WithStopWatch(Func<Task> action)
{
    var stopWatch = new Stopwatch();
    stopWatch.Start();

    await action();

    stopWatch.Stop();
    var ts = stopWatch.Elapsed;
    var elapsedTime = $"{ts.Hours:00}:{ts.Minutes:00}:{ts.Seconds:00}.{ts.Milliseconds / 10:00}";

    logger.LogInformation("RunTime {ElapsedTime}", elapsedTime);
}
  1. Параллельное выполнение запросов:
var tasks = new List<Task<string>>();
for (int i = 0; i < 5; i++)
{
    tasks.Add(prompty.InvokeAsync<string>(kernel, new KernelArguments { { "index", i } }));
}
var results = await Task.WhenAll(tasks);

6. Практические сценарии использования

6.1 Интеграция с RAG (Retrieval-Augmented Generation)

Одним из наиболее мощных сценариев использования Semantic Kernel с YandexGPT является создание RAG-систем:

// Создание и настройка ядра
var kernel = Kernel.CreateBuilder()
    .AddYandexAIChatCompletion(deployment, apiKey, folderId)
    .AddYandexAITextEmbeddingGeneration(embeddingDeployment, apiKey, folderId)
    .Build();

// Создание хранилища векторных эмбеддингов
var memoryStore = new VolatileMemoryStore();
var embeddingGenerator = kernel.GetRequiredService<ITextEmbeddingGenerationService>();
var memory = new SemanticTextMemory(memoryStore, embeddingGenerator);

// Добавление документов в память
await memory.SaveInformationAsync(
    collection: "documents",
    id: "doc1",
    text: "Палатка Tramp Rock 2 подходит для зимних походов благодаря усиленному каркасу и двойному тенту.",
    kernel: kernel);

// Создание функции для поиска и генерации ответа
var ragFunction = kernel.CreateFunctionFromPrompt(@"
system:
Ты - ассистент для магазина туристического снаряжения. Используй предоставленную информацию для ответа на вопрос.

Информация: {{$information}}

user: {{$question}}
");

// Использование RAG
var question = "Какие палатки подходят для зимы?";
var searchResults = await memory.SearchAsync(
    collection: "documents",
    query: question,
    limit: 3,
    kernel: kernel);

var information = string.Join("\n", searchResults.Select(r => r.Metadata.Text));
var result = await ragFunction.InvokeAsync(kernel, new KernelArguments
{
    { "information", information },
    { "question", question }
});

6.2 Использование Function Calling

Semantic Kernel также позволяет интегрировать YandexGPT с функциональными вызовами:

// Определение нативной функции
[KernelFunction]
public static string GetWeather(string city)
{
    // В реальном сценарии здесь был бы запрос к API погоды
    return $"В городе {city} сейчас солнечно, температура +20°C";
}

// Регистрация функции в ядре
var kernel = Kernel.CreateBuilder()
    .AddYandexAIChatCompletion(deployment, apiKey, folderId)
    .Build();

kernel.Plugins.AddFromObject(new { GetWeather });

// Создание промпта с использованием функции
var functionCallingPrompt = kernel.CreateFunctionFromPrompt(@"
system:
Ты - туристический ассистент. Если пользователь спрашивает о погоде, используй функцию GetWeather для получения актуальной информации.

user: {{$question}}
");

// Использование функции
var result = await functionCallingPrompt.InvokeAsync(kernel, new KernelArguments
{
    { "question", "Какая сейчас погода в Москве?" }
});

6.3 Цепочки функций и оркестрация

Semantic Kernel позволяет создавать сложные цепочки функций для решения комплексных задач:

// Создание отдельных функций
var summarizeFunction = kernel.CreateFunctionFromPrompt("Summarize the following text: {{$input}}");
var translateFunction = kernel.CreateFunctionFromPrompt("Translate the following text to Russian: {{$input}}");
var formatFunction = kernel.CreateFunctionFromPrompt("Format the following text as a bulleted list: {{$input}}");

// Создание цепочки функций
var pipeline = kernel.CreateFunctionFromPrompt(async (KernelArguments args, Kernel kernel) =>
{
    // Шаг 1: Суммаризация
    var summary = await summarizeFunction.InvokeAsync(kernel, new KernelArguments
    {
        { "input", args["text"] }
    });
    
    // Шаг 2: Перевод
    var translated = await translateFunction.InvokeAsync(kernel, new KernelArguments
    {
        { "input", summary }
    });
    
    // Шаг 3: Форматирование
    var formatted = await formatFunction.InvokeAsync(kernel, new KernelArguments
    {
        { "input", translated }
    });
    
    return formatted;
});

// Использование цепочки
var result = await pipeline.InvokeAsync(kernel, new KernelArguments
{
    { "text", "Long text about hiking equipment..." }
});

6.4 Интеграция с пользовательским интерфейсом

Для создания полноценного приложения можно интегрировать Semantic Kernel с YandexGPT в пользовательский интерфейс:

// ASP.NET Core контроллер
[ApiController]
[Route("api/[controller]")]
public class ChatController : ControllerBase
{
    private readonly Kernel _kernel;
    
    public ChatController(Kernel kernel)
    {
        _kernel = kernel;
    }
    
    [HttpPost]
    public async Task<IActionResult> Chat([FromBody] ChatRequest request)
    {
        var chatHistory = new ChatHistory();
        
        // Добавление предыдущих сообщений
        foreach (var message in request.History)
        {
            chatHistory.AddMessage(
                message.Role == "user" ? AuthorRole.User : AuthorRole.Assistant,
                message.Content);
        }
        
        // Добавление нового сообщения
        chatHistory.AddUserMessage(request.Message);
        
        // Получение ответа от YandexGPT
        var chatCompletionService = _kernel.GetRequiredService<IChatCompletionService>();
        var response = await chatCompletionService.GetChatMessageContentsAsync(chatHistory);
        
        return Ok(new ChatResponse
        {
            Message = response[0].Content
        });
    }
}

// Модели данных
public class ChatRequest
{
    public List<ChatMessage> History { get; set; } = new();
    public string Message { get; set; } = string.Empty;
}

public class ChatMessage
{
    public string Role { get; set; } = string.Empty;
    public string Content { get; set; } = string.Empty;
}

public class ChatResponse
{
    public string Message { get; set; } = string.Empty;
}

7. Проблемы и решения при интеграции

7.2 Обработка ошибок и повторные попытки

При работе с внешними API важно корректно обрабатывать ошибки и реализовывать механизмы повторных попыток:

private async Task<YandexAIChatCompletionResponse> SendRequestWithRetriesAsync(
    YandexAIChatCompletionRequest request,
    CancellationToken cancellationToken)
{
    int maxRetries = 3;
    int retryDelay = 1000; // ms
    
    for (int attempt = 1; attempt <= maxRetries; attempt++)
    {
        try
        {
            return await SendRequestAsync(request, cancellationToken);
        }
        catch (YandexAIException ex) when (IsTransientError(ex) && attempt < maxRetries)
        {
            await Task.Delay(retryDelay * attempt, cancellationToken);
        }
    }
    
    // Последняя попытка без перехвата исключения
    return await SendRequestAsync(request, cancellationToken);
}

private bool IsTransientError(YandexAIException ex)
{
    // Определение, является ли ошибка временной (например, 429 Too Many Requests)
    return ex.Message.Contains("429") || ex.Message.Contains("503");
}

7.3 Управление лимитами и квотами

API YandexGPT, как и другие API для языковых моделей, имеет ограничения на количество запросов и токенов. Для эффективного управления лимитами можно реализовать:

  1. Отслеживание использования:
public class ApiUsageTracker
{
    private readonly object _lock = new();
    private int _requestsCount;
    private DateTime _resetTime;
    private readonly int _maxRequestsPerMinute;
    
    public ApiUsageTracker(int maxRequestsPerMinute = 60)
    {
        _maxRequestsPerMinute = maxRequestsPerMinute;
        _resetTime = DateTime.UtcNow.AddMinutes(1);
    }
    
    public bool CanMakeRequest()
    {
        lock (_lock)
        {
            var now = DateTime.UtcNow;
            if (now > _resetTime)
            {
                _requestsCount = 0;
                _resetTime = now.AddMinutes(1);
            }
            
            return _requestsCount < _maxRequestsPerMinute;
        }
    }
    
    public void TrackRequest()
    {
        lock (_lock)
        {
            _requestsCount++;
        }
    }
}
  1. Очередь запросов:
public class RequestQueue
{
    private readonly SemaphoreSlim _semaphore;
    private readonly ApiUsageTracker _tracker;
    
    public RequestQueue(int concurrentRequests, ApiUsageTracker tracker)
    {
        _semaphore = new SemaphoreSlim(concurrentRequests);
        _tracker = tracker;
    }
    
    public async Task<T> EnqueueAsync<T>(Func<Task<T>> requestFunc)
    {
        await _semaphore.WaitAsync();
        try
        {
            while (!_tracker.CanMakeRequest())
            {
                await Task.Delay(100);
            }
            
            _tracker.TrackRequest();
            return await requestFunc();
        }
        finally
        {
            _semaphore.Release();
        }
    }
}

7.4 Тестирование коннектора

Для обеспечения надежности коннектора необходимо разработать комплексные тесты:

public class YandexAIChatCompletionServiceTests
{
    [Fact]
    public async Task GetChatMessageContentsAsync_ShouldReturnValidResponse()
    {
        // Arrange
        var mockHttpMessageHandler = new MockHttpMessageHandler();
        var httpClient = new HttpClient(mockHttpMessageHandler);
        
        mockHttpMessageHandler.When("https://llm.api.cloud.yandex.net/foundationModels/v1/completion")
            .Respond("application/json", "{\"result\":{\"alternatives\":[{\"message\":{\"role\":\"assistant\",\"text\":\"Test response\"}}]}}");
        
        var service = new YandexAIChatCompletionService("test", "test-key", "test-folder", httpClient);
        var chatHistory = new ChatHistory();
        chatHistory.AddUserMessage("Test message");
        
        // Act
        var result = await service.GetChatMessageContentsAsync(chatHistory);
        
        // Assert
        Assert.Single(result);
        Assert.Equal("Test response", result[0].Content);
        Assert.Equal(AuthorRole.Assistant, result[0].Role);
    }
    
    [Fact]
    public async Task GetChatMessageContentsAsync_ShouldHandleErrors()
    {
        // Arrange
        var mockHttpMessageHandler = new MockHttpMessageHandler();
        var httpClient = new HttpClient(mockHttpMessageHandler);
        
        mockHttpMessageHandler.When("https://llm.api.cloud.yandex.net/foundationModels/v1/completion")
            .Respond(HttpStatusCode.BadRequest, "application/json", "{\"error\":\"Invalid request\"}");
        
        var service = new YandexAIChatCompletionService("test", "test-key", "test-folder", httpClient);
        var chatHistory = new ChatHistory();
        chatHistory.AddUserMessage("Test message");
        
        // Act & Assert
        await Assert.ThrowsAsync<YandexAIException>(() => service.GetChatMessageContentsAsync(chatHistory));
    }
}

Заключение

Интеграция YandexGPT с Microsoft Semantic Kernel открывает новые возможности для разработчиков, позволяя использовать российскую языковую модель в экосистеме Microsoft. Разработанный коннектор обеспечивает совместимость между различными API и упрощает процесс взаимодействия с YandexGPT.

Комбинация Semantic Kernel и инструмента Prompty предоставляет мощный и гибкий инструментарий для создания интеллектуальных приложений, использующих возможности языковых моделей. Структурированный подход к управлению промптами повышает надежность и воспроизводимость результатов, а унифицированный интерфейс Semantic Kernel обеспечивает переносимость решений между различными моделями.

В будущем можно ожидать дальнейшего развития коннектора с добавлением поддержки новых функций YandexGPT, а также интеграции с другими компонентами экосистемы Microsoft, такими как векторные базы данных и инструменты оркестрации.

LLM - conclusion.png

Ссылки, документы и исходный код

  1. Microsoft. (2024). Semantic Kernel Documentation. https://learn.microsoft.com/en-us/semantic-kernel/overview/
  2. Yandex. (2024). YandexGPT Documentation. https://yandex.cloud/ru/docs/foundation-models/quickstart/yandexgpt
  3. OpenAI. (2024). OpenAI API Reference. https://platform.openai.com/docs/api-reference
  4. Microsoft. (2024). Prompty Documentation. https://prompty.ai/
  5. Kroniak. (2024). Microsoft SemanticKernel Connectors YandexAI. https://github.com/kroniak/Microsoft.SemanticKernel.Connectors.YandexAI

Предыдущая статья
InBetween 2025: Наша новая конференция на стыке тактики и стратегии
Следующая статья
Старт цикла статей по интеграции LLM в программное обеспечение