Использование подключаемых модулей для извлечения дополненного поколения (RAG)

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

Рассматривая плагины для генерации с дополнением извлечёнными данными (RAG), вы должны задать себе два вопроса:

  1. Как вы (или ваш агент ИИ) будете искать необходимые данные? Вам нужен семантический поиск или классический поиск?
  2. Вы уже знаете, что данные, необходимые агенту ИИ, требуются заранее (предварительно полученные данные) или агент ИИ должен динамически извлекать данные?
  3. Как вы будете обеспечивать защиту своих данных и предотвращать чрезмерное раскрытие конфиденциальной информации?

При разработке плагинов для генерации с дополненным поиском (RAG) можно использовать два типа поиска: семантический поиск и классический поиск.

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

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

Когда вы предоставляете LLM функцию семантического поиска, обычно достаточно определить функцию с одним параметром — поисковым запросом. Затем LLM будет использовать эту функцию для получения необходимых сведений. Ниже приведен пример семантической функции поиска, которая использует Поиск с использованием ИИ Azure для поиска документов, аналогичных заданному запросу.

using System.ComponentModel;
using System.Text.Json.Serialization;
using Azure;
using Azure.Search.Documents;
using Azure.Search.Documents.Indexes;
using Azure.Search.Documents.Models;
using Microsoft.SemanticKernel;
using Microsoft.SemanticKernel.Embeddings;

public class InternalDocumentsPlugin
{
    private readonly ITextEmbeddingGenerationService _textEmbeddingGenerationService;
    private readonly SearchIndexClient _indexClient;

    public AzureAISearchPlugin(ITextEmbeddingGenerationService textEmbeddingGenerationService, SearchIndexClient indexClient)
    {
        _textEmbeddingGenerationService = textEmbeddingGenerationService;
        _indexClient = indexClient;
    }

    [KernelFunction("Search")]
    [Description("Search for a document similar to the given query.")]
    public async Task<string> SearchAsync(string query)
    {
        // Convert string query to vector
        ReadOnlyMemory<float> embedding = await _textEmbeddingGenerationService.GenerateEmbeddingAsync(query);

        // Get client for search operations
        SearchClient searchClient = _indexClient.GetSearchClient("default-collection");

        // Configure request parameters
        VectorizedQuery vectorQuery = new(embedding);
        vectorQuery.Fields.Add("vector");

        SearchOptions searchOptions = new() { VectorSearch = new() { Queries = { vectorQuery } } };

        // Perform search request
        Response<SearchResults<IndexSchema>> response = await searchClient.SearchAsync<IndexSchema>(searchOptions);

        // Collect search results
        await foreach (SearchResult<IndexSchema> result in response.Value.GetResultsAsync())
        {
            return result.Document.Chunk; // Return text from first result
        }

        return string.Empty;
    }

    private sealed class IndexSchema
    {
        [JsonPropertyName("chunk")]
        public string Chunk { get; set; }

        [JsonPropertyName("vector")]
        public ReadOnlyMemory<float> Vector { get; set; }
    }
}

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

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

Tip

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

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

using System.ComponentModel;
using Microsoft.SemanticKernel;

public class CRMPlugin
{
    private readonly CRMService _crmService;

    public CRMPlugin(CRMService crmService)
    {
        _crmService = crmService;
    }

    [KernelFunction("GetCustomerInfo")]
    [Description("Retrieve customer information based on the given customer ID.")]
    public async Task<Customer> GetCustomerInfoAsync(string customerId)
    {
        return await _crmService.GetCustomerInfoAsync(customerId);
    }
}

Достижение того же функциональных возможностей поиска семантического поиска, скорее всего, было бы невозможным или непрактичным из-за недетерминированного характера семантических запросов.

Когда следует использовать каждый

Выбор между семантической и классической поиск зависит от характера запроса. Это идеально подходит для сред с большим количеством содержимого, таких как базы знаний и поддержка клиентов, где пользователи могут задавать вопросы или искать продукты с использованием естественного языка. Классический поиск, с другой стороны, следует использовать, когда точность и точные совпадения важны.

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

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

using System.ComponentModel;
using Microsoft.SemanticKernel;

public class ECommercePlugin
{
    [KernelFunction("search_products")]
    [Description("Search for products based on the given query.")]
    public async Task<IEnumerable<Product>> SearchProductsAsync(string query, ProductCategories category = null, decimal? minPrice = null, decimal? maxPrice = null)
    {
        // Perform semantic and classic search with the given parameters
    }
}

Динамическое и предварительное получение данных

При разработке плагинов для генерации с дополнением поиском (RAG) необходимо также учитывать, является ли процесс извлечения данных статическим или динамическим. Это позволяет оптимизировать производительность агентов ИИ, извлекая данные только при необходимости.

Динамическое извлечение данных

В большинстве случаев запрос пользователя определяет данные, необходимые агенту ИИ. Например, пользователь может запросить разницу между двумя различными продуктами. Затем агент ИИ должен динамически извлекать сведения о продукте из базы данных или API, чтобы создать ответ с помощью вызова функции. Было бы непрактично заранее собрать всю возможную информацию о продукте и передать её ИИ-агенту.

Ниже приведён пример диалога между пользователем и ИИ-агентом, в котором необходимо динамическое получение данных.

Роль Message
🔵 Пользователь Можете ли вы рассказать мне о лучших матрасах?
🔴 Помощник (вызов функции) Products.Search("mattresses")
🟢 Инструмент [{"id": 25323, "name": "Cloud Nine"},{"id": 63633, "name": "Best Sleep"}]
🔴 Помощник Уверен! У нас есть и «Клауд Найн», и «Бест Слип»
🔵 Пользователь В чем разница между ними?
🔴 Ассистент (вызов функции) Products.GetDetails(25323) Products.GetDetails(63633)
🟢 Инструмент { "id": 25323, "name": "Cloud Nine", "price": 1000, "material": "Memory foam" }
🟢 Инструмент { "id": 63633, "name": "Best Sleep", "price": 1200, "material": "Latex" }
🔴 Помощник Cloud Nine состоит из пены памяти и стоит $ 1000. Лучший сон сделан из латекса и стоит $ 1200.

Получение предварительно загруженных данных

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

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

using System.Text.Json;
using Microsoft.SemanticKernel;
using Microsoft.SemanticKernel.ChatCompletion;

IKernelBuilder builder = Kernel.CreateBuilder();
builder.AddAzureOpenAIChatCompletion(deploymentName, endpoint, apiKey);
builder.Plugins.AddFromType<WeatherPlugin>();
Kernel kernel = builder.Build();

// Get the weather
var weather = await kernel.Plugins.GetFunction("WeatherPlugin", "get_weather").InvokeAsync(kernel);

// Initialize the chat history with the weather
ChatHistory chatHistory = new ChatHistory("The weather is:\n" + JsonSerializer.Serialize(weather));

// Simulate a user message
chatHistory.AddUserMessage("What is the weather like today?");

// Get the answer from the AI agent
IChatCompletionService chatCompletionService = kernel.GetRequiredService<IChatCompletionService>();
var result = await chatCompletionService.GetChatMessageContentAsync(chatHistory);

Обеспечение безопасности данных

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

Strategy Description
Используйте токен аутентификации пользователя Избегайте создания субъектов-служб, используемых агентом ИИ для получения сведений для пользователей. Это затрудняет проверку наличия у пользователя доступа к полученной информации.
Избегайте повторного восстановления служб поиска Прежде чем создавать новую службу поиска с помощью векторной базы данных, проверьте, существует ли служба с необходимыми данными. Повторно используя существующие службы, можно избежать дедупликации конфиденциального содержимого, использовать существующие элементы управления доступом и использовать существующие механизмы фильтрации, которые возвращают только данные, к которым пользователь имеет доступ.
Хранение ссылок в векторных DBS вместо содержимого Вместо дублирования конфиденциальных данных в векторных базах данных можно хранить ссылки на сами данные. Для доступа к этой информации пользователю сначала необходимо использовать маркер проверки подлинности для получения реальных данных.

Дальнейшие действия

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