Utilizar extensões para Retrieval Augmented Generation (RAG)

Muitas vezes, os seus agentes de IA têm de recuperar dados de fontes externas para gerar respostas fundamentadas. Sem este contexto adicional, os seus agentes de IA podem alucinar ou fornecer informações incorretas. Para resolver isto, podes usar plugins para recuperar dados de fontes externas.

Ao considerar plugins para Geração Aumentada de Recuperação (RAG), deve fazer-se duas perguntas:

  1. Como é que você (ou o seu agente de IA) irá "procurar" os dados necessários? Precisas de pesquisa semântica ou pesquisa clássica?
  2. Já sabe os dados que o agente de IA precisa antecipadamente (dados pré-buscados), ou o agente de IA precisa de recuperar os dados dinamicamente?
  3. Como irá manter os seus dados seguros e evitar a partilha excessiva de informações sensíveis?

Ao desenvolver plugins para Geração Aumentada de Recuperação (RAG), pode usar dois tipos de pesquisa: pesquisa semântica e pesquisa clássica.

A pesquisa semântica utiliza bases de dados vetoriais para compreender e recuperar informação com base no significado e contexto da consulta, em vez de apenas corresponder palavras-chave. Este método permite ao motor de busca compreender as nuances da linguagem, como sinónimos, conceitos relacionados e a intenção geral por detrás de uma consulta.

A pesquisa semântica destaca-se em ambientes onde as consultas dos utilizadores são complexas, abertas ou requerem uma compreensão mais profunda do conteúdo. Por exemplo, pesquisar por "melhores smartphones para fotografia" daria resultados que consideram o contexto das funcionalidades fotográficas nos smartphones, em vez de apenas corresponder às palavras "melhor", "smartphones" e "fotografia".

Ao fornecer a um LLM uma função de pesquisa semântica, normalmente só precisa de definir uma função com uma única consulta de pesquisa. O LLM utilizará então esta função para recuperar a informação necessária. Abaixo está um exemplo de uma função de pesquisa semântica que utiliza o Pesquisa de IA do Azure para encontrar documentos semelhantes a uma determinada consulta.

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; }
    }
}

A pesquisa clássica, também conhecida como pesquisa baseada em atributos ou baseada em critérios, baseia-se na filtragem e na correspondência de termos ou valores exatos dentro de um conjunto de dados. É particularmente eficaz para consultas a bases de dados, pesquisas de inventário e qualquer situação em que seja necessário filtrar por atributos específicos.

Por exemplo, se um utilizador quiser encontrar todas as encomendas feitas por um determinado ID de cliente ou recuperar produtos dentro de uma faixa e categoria de preços específicas, a pesquisa clássica fornece resultados precisos e fiáveis. A pesquisa clássica, no entanto, é limitada pela sua incapacidade de compreender o contexto ou por variações linguísticas.

Tip

Na maioria dos casos, os seus serviços existentes já suportam a pesquisa clássica. Antes de implementar uma pesquisa semântica, considere se os seus serviços existentes podem fornecer o contexto necessário para os seus agentes de IA.

Veja-se, por exemplo, um plugin que recupera informações de clientes de um sistema CRM usando a pesquisa clássica. Aqui, a IA simplesmente precisa de chamar a GetCustomerInfoAsync função com um ID de cliente para obter a informação necessária.

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);
    }
}

Alcançar a mesma funcionalidade de pesquisa com a pesquisa semântica provavelmente seria impossível ou impraticável devido à natureza não determinística das consultas semânticas.

Quando usar cada

A escolha entre pesquisa semântica e clássica depende da natureza da consulta. É ideal para ambientes com muitos conteúdos, como bases de conhecimento e apoio ao cliente, onde os utilizadores podem colocar questões ou procurar produtos usando linguagem natural. A pesquisa clássica, por outro lado, deve ser utilizada quando a precisão e as correspondências exatas são importantes.

Em alguns cenários, poderá ser necessário combinar ambas as abordagens para fornecer capacidades de pesquisa abrangentes. Por exemplo, um chatbot que ajuda clientes numa loja de comércio eletrónico pode usar a pesquisa semântica para compreender as dúvidas dos utilizadores e a pesquisa clássica para filtrar produtos com base em atributos específicos como preço, marca ou disponibilidade.

Abaixo está um exemplo de um plugin que combina pesquisa semântica e clássica para recuperar informações de produtos a partir de uma base de dados de comércio eletrónico.

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
    }
}

Recuperação de dados dinâmica vs pré-buscada

Ao desenvolver plugins para Geração Aumentada de Recuperação (RAG), deve também considerar se o processo de recuperação de dados é estático ou dinâmico. Isto permite-lhe otimizar o desempenho dos seus agentes de IA, recuperando dados apenas quando necessário.

Recuperação dinâmica de dados

Na maioria dos casos, a consulta do utilizador determinará os dados que o agente de IA precisa de recuperar. Por exemplo, um utilizador pode pedir a diferença entre dois produtos diferentes. O agente de IA teria então de recuperar dinamicamente a informação do produto de uma base de dados ou API para gerar uma resposta usando a chamada de funções. Seria impraticável pré-buscar toda a informação possível do produto antecipadamente e entregá-la ao agente de IA.

Abaixo está um exemplo de uma conversa de ida e volta entre um utilizador e um agente de IA, onde é necessária uma recuperação dinâmica de dados.

Função Message
🔵 Utilizador Pode falar-me sobre os melhores colchões?
🔴 Assistente (chamada de função) Products.Search("mattresses")
🟢 Ferramenta [{"id": 25323, "name": "Cloud Nine"},{"id": 63633, "name": "Best Sleep"}]
🔴 Assistente Claro! Temos tanto o Cloud Nine como o Best Sleep
🔵 Utilizador Qual é a diferença entre ambas?
🔴 Assistente (chamada de função) Products.GetDetails(25323) Products.GetDetails(63633)
🟢 Ferramenta { "id": 25323, "name": "Cloud Nine", "price": 1000, "material": "Memory foam" }
🟢 Ferramenta { "id": 63633, "name": "Best Sleep", "price": 1200, "material": "Latex" }
🔴 Assistente O Cloud Nine é feito de espuma viscoelástica e custa $1000. O Best Sleep é feito de látex e custa $1200.

Recuperação de dados pré-carregados

A recuperação estática de dados envolve obter dados de fontes externas e fornecê-los sempre ao agente de IA. Isto é útil quando os dados são necessários para cada pedido ou quando os dados são relativamente estáveis e não mudam frequentemente.

Veja-se, por exemplo, um agente que responde sempre a perguntas sobre o tempo local. Assumindo que tens um WeatherPlugin, podes pré-buscar dados meteorológicos de uma API meteorológica e fornecê-los no histórico do chat. Isto permite ao agente gerar respostas sobre o tempo sem perder tempo a pedir os dados à 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);

Manter os dados seguros

Ao recuperar dados de fontes externas, é importante garantir que os dados são seguros e que informações sensíveis não são expostas. Para evitar a partilha excessiva de informações sensíveis, pode usar as seguintes estratégias:

Strategy Description
Usar o token de autenticação do utilizador Evite criar princípios de serviço usados pelo agente de IA para obter informação para os utilizadores. Fazê-lo dificulta a verificação de que um utilizador tem acesso à informação recuperada.
Evite recriar serviços de pesquisa Antes de criar um novo serviço de pesquisa com uma base de dados vetorial, verifique se já existe um para o serviço que tenha os dados necessários. Ao reutilizar serviços existentes, pode evitar duplicar conteúdos sensíveis, utilizar controlos de acesso existentes e utilizar mecanismos de filtragem que só devolvem dados a que o utilizador tem acesso.
Armazenar referência em bases de dados vetoriais em vez de conteúdo Em vez de duplicar conteúdos sensíveis em bases de dados vetoriais, pode armazenar referências aos dados reais. Para que um utilizador aceda a esta informação, o seu token de autenticação deve primeiro ser usado para recuperar os dados reais.

Passos seguintes

Agora que sabe como fundamentar os seus agentes de IA com dados de fontes externas, pode aprender a usar agentes de IA para automatizar processos de negócio. Para saber mais, consulte a utilização de funções de automação de tarefas.