Интеграция YandexGPT с семантическим ядром Microsoft
Аннотация
В данной статье рассматривается процесс интеграции языковой модели YandexGPT с семантическим ядром Microsoft (Microsoft Semantic Kernel). Исследуются особенности взаимодействия с нейросетями через API, анализируются различия между API Yandex и OpenAI, а также представляется пошаговая методология разработки коннектора, позволяющего использовать возможности YandexGPT в экосистеме Microsoft. Особое внимание уделяется интеграции с инструментом Prompty для эффективного управления промптами.
Оглавление
- 1. LLM модели и способы взаимодействия с нейросетями
- 2. API OpenAI
- 3. Microsoft Semantic Kernel
- 3.1 Архитектура и концепция Semantic Kernel
- 3.2 Преимущества использования Semantic Kernel
- 3.3 Основные компоненты для интеграции с LLM
- 3.4 Паттерны проектирования в Semantic Kernel
- 3.4.1 Паттерн “Стратегия” (Strategy Pattern)
- 3.4.2 Паттерн “Фабрика” (Factory Pattern)
- 3.4.3 Паттерн “Адаптер” (Adapter Pattern)
- 3.4.4 Паттерн “Декоратор” (Decorator Pattern)
- 3.4.5 Паттерн “Цепочка ответственности” (Chain of Responsibility)
- 3.4.6 Паттерн “Внедрение зависимостей” (Dependency Injection)
- 3.4.7 Паттерн “Команда” (Command Pattern)
- 4. Пошаговая разработка коннектора для Yandex API
- 4.1 Анализ различий между API Yandex и OpenAI
- 4.2 Создание базовой структуры коннектора
- 4.3 Реализация метода GetChatMessageContentsAsync
- 4.4 Реализация вспомогательных классов
- 4.5 Реализация метода расширения для Kernel Builder
- 4.6 Обработка ошибок и исключений
- 4.7 Реализация поддержки потоковой генерации
- 4.8 Реализация поддержки векторных эмбеддингов
- 5. Интеграция Microsoft Semantic Kernel и инструмента Prompty
- 6. Практические сценарии использования
- 7. Проблемы и решения при интеграции
1. LLM модели и способы взаимодействия с нейросетями
1.1 Большие языковые модели (LLM)
Большие языковые модели (Large Language Models, LLM) представляют собой класс нейронных сетей, обученных на огромных массивах текстовых данных с целью понимания и генерации человеческого языка. Эти модели используют архитектуру трансформеров, предложенную в 2017 году, которая позволяет эффективно обрабатывать последовательности данных, учитывая контекст и взаимосвязи между элементами.
Современные LLM, такие как GPT (Generative Pre-trained Transformer) от OpenAI, Claude от Anthropic, Llama от Meta и YandexGPT от Яндекса, содержат миллиарды параметров и способны решать широкий спектр задач обработки естественного языка: от генерации текста и перевода до ответов на вопросы и анализа документов.
1.2 Способы интеграции моделей в конечное программное обеспечение
Интеграция языковых моделей в программное обеспечение позволяет расширить функциональность приложений, добавив возможности обработки естественного языка, генерации контента и автоматизации задач. Существует несколько ключевых подходов к такой интеграции:
1.2.1 Агентные системы (AI Agents)
Агентные системы представляют собой автономные программные сущности, использующие LLM для принятия решений и выполнения действий. Они характеризуются:
- Автономностью — способностью самостоятельно планировать и выполнять последовательность действий
- Целенаправленностью — ориентацией на достижение конкретных целей
- Персистентностью — поддержанием состояния между взаимодействиями
- Адаптивностью — способностью учиться на основе опыта и обратной связи
Примеры использования: виртуальные ассистенты, автоматизированные системы поддержки клиентов, исследовательские агенты для анализа данных и более сложные системы, интегрируемые в программное обеспечение.
1.2.2 Функциональные вызовы (Function Calling)
Function Calling (или Tool Use) — это механизм, позволяющий языковым моделям вызывать внешние функции для выполнения действий, недоступных самой модели:
- Распознавание необходимости — модель определяет, когда требуется вызвать внешнюю функцию
- Структурированный вывод — генерация параметров в формате, понятном для API, например в json
- Интеграция результатов — включение результатов выполнения функции в дальнейшую генерацию
Этот подход особенно полезен для:
- Получения актуальной информации (погода, новости, курсы валют, каталог товаров)
- Взаимодействия с внешними системами (базы данных, CRM, ERP)
- Выполнения вычислений и обработки данных
1.2.3 RAG (Retrieval-Augmented Generation)
RAG объединяет поисковые системы с генеративными моделями, позволяя обогащать ответы релевантной информацией из внешних источников:
- Индексация — создание векторных представлений документов и их хранение
- Поиск — нахождение релевантных документов на основе запроса пользователя
- Аугментация — включение найденной информации в контекст запроса к LLM
- Генерация — создание ответа на основе обогащенного контекста
Преимущества RAG:
- Актуальность информации (не ограничена данными обучения модели)
- Проверяемость (возможность указать источники информации)
- Снижение галлюцинаций (модель опирается на фактические данные)
- Персонализация (возможность использовать корпоративные или личные данные)
1.2.4 Оркестрация и цепочки (Orchestration & Chains)
Этот подход предполагает создание последовательностей операций, где выход одной операции становится входом для следующей:
- Цепочки промптов — последовательное использование результатов одного запроса как части следующего
- Ветвление — принятие решений о дальнейших действиях на основе промежуточных результатов
- Параллельное выполнение — одновременное выполнение нескольких операций
- Обработка ошибок — механизмы восстановления при сбоях в цепочке
1.3 Необходимость интеграции через API
Интеграция с языковыми моделями через API предоставляет ряд существенных преимуществ:
-
Глубокая интеграция — API-подход позволяет включить ИИ функции напрямую в программное обеспечение без необходимости строить чат модели рядом с пользовательским интерфейсом.
-
Актуальность моделей — провайдеры API регулярно обновляют модели, предоставляя доступ к последним функциям без необходимости дообучения.
-
Гибкость — возможность выбора и переключения между различными моделями в зависимости от требований задачи.
-
Стандартизация — API предоставляют унифицированный интерфейс для взаимодействия с различными моделями.
-
Мультимодальность — современные API часто предоставляют доступ не только к текстовым, но и к мультимодальным возможностям (обработка изображений, аудио).
Интеграция через API особенно важна для корпоративных приложений, где требуется соблюдение строгих требований к производительности, безопасности и соответствию нормативным требованиям.
1.4 Промпт-инженерия
Ключевым аспектом эффективного взаимодействия с LLM является промпт-инженерия - искусство формулирования запросов к модели таким образом, чтобы получить желаемый результат. Это включает в себя:
- Структурирование запросов
- Предоставление примеров (few-shot learning)
- Разбиение сложных задач на подзадачи
- Использование специальных шаблонов и техник
Инструменты вроде Prompty помогают стандартизировать и оптимизировать этот процесс, обеспечивая воспроизводимость результатов и упрощая управление промптами.
2. API OpenAI
2.1 Структура API OpenAI
API OpenAI стал де-факто стандартом в индустрии для взаимодействия с языковыми моделями. Его основные компоненты включают:
-
Аутентификация - использование API-ключей для авторизации запросов.
-
Endpoints - специализированные конечные точки для различных моделей и функций:
/v1/chat/completions
- для диалоговых моделей/v1/embeddings
- для получения векторных представлений текста/v1/images/generations
- для генерации изображений
-
Параметры запросов:
model
- идентификатор используемой моделиmessages
- массив сообщений для диалоговых моделейtemperature
- параметр, контролирующий случайность генерацииmax_tokens
- ограничение длины ответа- и другие специфические параметры
-
Форматы ответов - структурированные JSON-ответы, содержащие сгенерированный контент и метаданные.
2.2 Преимущества API OpenAI
API OpenAI предлагает ряд преимуществ, которые сделали его популярным среди разработчиков:
- Простота использования - понятная документация и интуитивный дизайн API
- Гибкость - широкий спектр параметров для настройки поведения моделей
- Масштабируемость - возможность обработки большого количества запросов
- Версионность - поддержка различных версий моделей
- Экосистема инструментов - множество библиотек и фреймворков, совместимых с API
2.3 Ограничения и проблемы совместимости
Несмотря на популярность, API OpenAI имеет ряд ограничений:
- Закрытость - отсутствие доступа к исходному коду моделей
- Зависимость от внешнего сервиса - необходимость постоянного подключения к серверам OpenAI
- Стоимость - платная модель использования
- Проблемы совместимости - другие провайдеры языковых моделей часто используют несовместимые API
Последний пункт особенно актуален при работе с альтернативными моделями, такими как YandexGPT, что создает необходимость в разработке специализированных коннекторов.
3. Microsoft Semantic Kernel
3.1 Архитектура и концепция Semantic Kernel
Microsoft Semantic Kernel представляет собой открытый фреймворк, который упрощает интеграцию языковых моделей в приложения. Ключевая концепция Semantic Kernel заключается в абстрагировании сложности взаимодействия с различными LLM и предоставлении унифицированного интерфейса для разработчиков.
Основные компоненты архитектуры Semantic Kernel:
-
Kernel (Ядро) - центральный компонент, координирующий взаимодействие между различными частями системы.
-
Functions (Функции) - базовые блоки функциональности, которые могут быть:
- Semantic Functions - функции, использующие LLM для выполнения задач
- Native Functions - обычные программные функции, написанные на языке программирования
-
Plugins (Плагины) - коллекции функций, сгруппированные по назначению.
-
Memory (Память) - компонент для хранения и извлечения информации, включая векторные базы данных.
-
Connectors (Коннекторы) - адаптеры для различных LLM-сервисов, таких как OpenAI, Azure OpenAI, и в нашем случае - YandexGPT.
3.2 Преимущества использования Semantic Kernel
Semantic Kernel предлагает ряд существенных преимуществ:
- Унификация - единый интерфейс для работы с различными языковыми моделями
- Композиция - возможность комбинировать функции для решения сложных задач
- Расширяемость - легкая интеграция новых моделей и функциональности
- Переносимость - минимальные изменения в коде при переключении между моделями
- Оркестрация - управление последовательностью выполнения задач
- Интеграция с экосистемой .NET - нативная поддержка для разработчиков на C#
3.3 Основные компоненты для интеграции с LLM
Для интеграции новой языковой модели с Semantic Kernel необходимо реализовать несколько ключевых компонентов:
- IChatCompletionService - интерфейс для генерации ответов в формате диалога
- ITextCompletionService - интерфейс для текстовых завершений
- 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:
-
Структура аутентификации:
- OpenAI использует API-ключ в заголовке
Authorization
- Yandex требует API-ключ в заголовке
Authorization
и дополнительный параметрfolderId
для идентификации проекта
- OpenAI использует API-ключ в заголовке
-
Формат запросов:
- 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 }
- OpenAI:
-
URL эндпоинтов:
- OpenAI:
https://api.openai.com/v1/chat/completions
- Yandex:
https://llm.api.cloud.yandex.net/foundationModels/v1/completion
- OpenAI:
-
Форматы ответов:
- OpenAI возвращает структуру с полем
choices[0].message.content
- Yandex возвращает структуру с полем
result.alternatives[0].message.text
- OpenAI возвращает структуру с полем
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) { }
}
Наш подход к обработке ошибок включает:
- Специализированные исключения — создание класса
YandexAIException
для представления ошибок, связанных с YandexGPT - Детальные сообщения об ошибках — включение информации о статусе HTTP и содержимом ответа
- Проверки валидности — проверка наличия и корректности ответа
- Обертывание исключений — преобразование общих исключений в специализированные для единообразной обработки
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 и его преимущества
Prompty - это новый инструмент в экосистеме LLM, предназначенный для управления промптами. Он предлагает следующие преимущества:
- Структурированное хранение промптов - промпты хранятся в файлах с расширением
.prompty
- Шаблонизация - поддержка переменных и условной логики в промптах
- Повторное использование - возможность создания библиотек промптов
- Версионирование - отслеживание изменений промптов через системы контроля версий
- Тестирование - возможность автоматизированного тестирования промптов
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:
- Базовый диалог:
var basicPrompt = kernel.CreateFunctionFromPromptyFile("promties/basic.prompty");
var response = await basicPrompt.InvokeAsync<string>(kernel, new KernelArguments
{
{ "question", "Какие палатки вы рекомендуете для зимнего похода?" }
});
- Генерация описания города:
var townPrompt = kernel.CreateFunctionFromPromptyFile("promties/town.prompty");
var description = await townPrompt.InvokeAsync<string>(kernel);
- Предложение тегов для пространства:
var tagsPrompt = kernel.CreateFunctionFromPromptyFile("promties/space-tags-suggestion.prompty");
var tags = await tagsPrompt.InvokeAsync<string>(kernel);
5.5 Оптимизация производительности
Для оптимизации производительности при работе с YandexGPT и Prompty можно использовать следующие подходы:
- Кэширование HTTP-клиента:
var httpClient = new HttpClient();
var kernel = Kernel.CreateBuilder()
.AddYandexAIChatCompletion(deployment, apiKey, folderId, httpClient: httpClient)
.Build();
- Измерение времени выполнения:
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);
}
- Параллельное выполнение запросов:
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 для языковых моделей, имеет ограничения на количество запросов и токенов. Для эффективного управления лимитами можно реализовать:
- Отслеживание использования:
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++;
}
}
}
- Очередь запросов:
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, такими как векторные базы данных и инструменты оркестрации.
Ссылки, документы и исходный код
- Microsoft. (2024). Semantic Kernel Documentation. https://learn.microsoft.com/en-us/semantic-kernel/overview/
- Yandex. (2024). YandexGPT Documentation. https://yandex.cloud/ru/docs/foundation-models/quickstart/yandexgpt
- OpenAI. (2024). OpenAI API Reference. https://platform.openai.com/docs/api-reference
- Microsoft. (2024). Prompty Documentation. https://prompty.ai/
- Kroniak. (2024). Microsoft SemanticKernel Connectors YandexAI. https://github.com/kroniak/Microsoft.SemanticKernel.Connectors.YandexAI