Partilhar via


Histórico do bate-papo

O objeto de histórico de bate-papo é usado para manter um registro de mensagens em uma sessão de bate-papo. Ele é usado para armazenar mensagens de diferentes autores, como usuários, assistentes, ferramentas ou o sistema. Como o principal mecanismo para enviar e receber mensagens, o objeto de histórico de bate-papo é essencial para manter o contexto e a continuidade em uma conversa.

Criando um objeto de histórico de bate-papo

Um objeto de histórico de bate-papo é uma lista sob o capô, facilitando a criação e adição de mensagens.

using Microsoft.SemanticKernel.ChatCompletion;

// Create a chat history object
ChatHistory chatHistory = [];

chatHistory.AddSystemMessage("You are a helpful assistant.");
chatHistory.AddUserMessage("What's available to order?");
chatHistory.AddAssistantMessage("We have pizza, pasta, and salad available to order. What would you like to order?");
chatHistory.AddUserMessage("I'd like to have the first option, please.");
# Create a chat history object
chat_history = ChatHistory()

chat_history.add_system_message("You are a helpful assistant.")
chat_history.add_user_message("What's available to order?")
chat_history.add_assistant_message("We have pizza, pasta, and salad available to order. What would you like to order?")
chat_history.add_user_message("I'd like to have the first option, please.")
import com.microsoft.semantickernel.services.chatcompletion.ChatHistory;

// Create a chat history object
ChatHistory chatHistory = new ChatHistory();

chatHistory.addSystemMessage("You are a helpful assistant.");
chatHistory.addUserMessage("What's available to order?");
chatHistory.addAssistantMessage("We have pizza, pasta, and salad available to order. What would you like to order?");
chatHistory.addUserMessage("I'd like to have the first option, please.");

Adicionar mensagens mais avançadas a um histórico de chat

A maneira mais fácil de adicionar mensagens a um objeto de histórico de bate-papo é usar os métodos acima. No entanto, você também pode adicionar mensagens manualmente criando um novo ChatMessage objeto. Isso permite que você forneça informações adicionais, como nomes e conteúdo de imagens.

using Microsoft.SemanticKernel.ChatCompletion;

// Add system message
chatHistory.Add(
    new() {
        Role = AuthorRole.System,
        Content = "You are a helpful assistant"
    }
);

// Add user message with an image
chatHistory.Add(
    new() {
        Role = AuthorRole.User,
        AuthorName = "Laimonis Dumins",
        Items = [
            new TextContent { Text = "What available on this menu" },
            new ImageContent { Uri = new Uri("https://example.com/menu.jpg") }
        ]
    }
);

// Add assistant message
chatHistory.Add(
    new() {
        Role = AuthorRole.Assistant,
        AuthorName = "Restaurant Assistant",
        Content = "We have pizza, pasta, and salad available to order. What would you like to order?"
    }
);

// Add additional message from a different user
chatHistory.Add(
    new() {
        Role = AuthorRole.User,
        AuthorName = "Ema Vargova",
        Content = "I'd like to have the first option, please."
    }
);
from semantic_kernel.contents.chat_history import ChatHistory
from semantic_kernel.contents import ChatMessageContent, TextContent, ImageContent
from semantic_kernel.contents.utils.author_role import AuthorRole

# Add system message
chat_history.add_message(
    ChatMessage(
        role=AuthorRole.System,
        content="You are a helpful assistant"
    )
)

# Add user message with an image
chat_history.add_message(
    ChatMessageContent(
        role=AuthorRole.USER,
        name="Laimonis Dumins",
        items=[
            TextContent(text="What available on this menu"),
            ImageContent(uri="https://example.com/menu.jpg")
        ]
    )
)

# Add assistant message
chat_history.add_message(
    ChatMessageContent(
        role=AuthorRole.ASSISTANT,
        name="Restaurant Assistant",
        content="We have pizza, pasta, and salad available to order. What would you like to order?"
    )
)

# Add additional message from a different user
chat_history.add_message(
    ChatMessageContent(
        role=AuthorRole.USER,
        name="Ema Vargova",
        content="I'd like to have the first option, please."
    )
)
import com.microsoft.semantickernel.services.chatcompletion.message.ChatMessageImageContent;
import com.microsoft.semantickernel.services.chatcompletion.message.ChatMessageTextContent;

// Add system message
chatHistory.addSystemMessage(
    "You are a helpful assistant"
);

// Add user message with an image
chatHistory.addUserMessage(
    "What available on this menu"
);

chatHistory.addMessage(
    ChatMessageImageContent.builder()
            .withImageUrl("https://example.com/menu.jpg")
            .build()
);

// Add assistant message
chatHistory.addAssistantMessage(
    "We have pizza, pasta, and salad available to order. What would you like to order?"
);

// Add additional message from a different user
chatHistory.addUserMessage(
    "I'd like to have the first option, please."
);

Simulando chamadas de função

Além das funções de usuário, assistente e sistema, você também pode adicionar mensagens da função de ferramenta para simular chamadas de função. Isso é útil para ensinar a IA a usar plugins e fornecer contexto adicional para a conversa.

Por exemplo, para injetar informações sobre o usuário atual no histórico de bate-papo sem exigir que o usuário forneça as informações ou fazer com que o LLM perca tempo pedindo-as, você pode usar a função de ferramenta para fornecer as informações diretamente.

Abaixo está um exemplo de como podemos fornecer alergias do usuário ao assistente simulando uma chamada de função para o User plugin.

Gorjeta

As chamadas de função simuladas são particularmente úteis para fornecer detalhes sobre o(s) usuário(s) atual(is). Os LLMs de hoje foram treinados para serem particularmente sensíveis às informações do usuário. Mesmo que você forneça detalhes do usuário em uma mensagem do sistema, o LLM ainda pode optar por ignorá-lo. Se você fornecê-lo através de uma mensagem de usuário, ou mensagem de ferramenta, o LLM é mais provável de usá-lo.

// Add a simulated function call from the assistant
chatHistory.Add(
    new() {
        Role = AuthorRole.Assistant,
        Items = [
            new FunctionCallContent(
                functionName: "get_user_allergies",
                pluginName: "User",
                id: "0001",
                arguments: new () { {"username", "laimonisdumins"} }
            ),
            new FunctionCallContent(
                functionName: "get_user_allergies",
                pluginName: "User",
                id: "0002",
                arguments: new () { {"username", "emavargova"} }
            )
        ]
    }
);

// Add a simulated function results from the tool role
chatHistory.Add(
    new() {
        Role = AuthorRole.Tool,
        Items = [
            new FunctionResultContent(
                functionName: "get_user_allergies",
                pluginName: "User",
                id: "0001",
                result: "{ \"allergies\": [\"peanuts\", \"gluten\"] }"
            )
        ]
    }
);
chatHistory.Add(
    new() {
        Role = AuthorRole.Tool,
        Items = [
            new FunctionResultContent(
                functionName: "get_user_allergies",
                pluginName: "User",
                id: "0002",
                result: "{ \"allergies\": [\"dairy\", \"soy\"] }"
            )
        ]
    }
);
from semantic_kernel.contents import ChatMessageContent, FunctionCallContent, FunctionResultContent

# Add a simulated function call from the assistant
chat_history.add_message(
    ChatMessageContent(
        role=AuthorRole.ASSISTANT,
        items=[
            FunctionCallContent(
                name="get_user_allergies-User",
                id="0001",
                arguments=str({"username": "laimonisdumins"})
            ),
            FunctionCallContent(
                name="get_user_allergies-User",
                id="0002",
                arguments=str({"username": "emavargova"})
            )
        ]
    )
)

# Add a simulated function results from the tool role
chat_history.add_message(
    ChatMessageContent(
        role=AuthorRole.TOOL,
        items=[
            FunctionResultContent(
                name="get_user_allergies-User",
                id="0001",
                result="{ \"allergies\": [\"peanuts\", \"gluten\"] }"
            )
        ]
    )
)
chat_history.add_message(
    ChatMessageContent(
        role=AuthorRole.TOOL,
        items=[
            FunctionResultContent(
                name="get_user_allergies-User",
                id="0002",
                result="{ \"allergies\": [\"dairy\", \"gluten\"] }"
            )
        ]
    )
)
This functionality is not supported in the current version of Semantic Kernel for Java. 

Importante

Ao simular os resultados da ferramenta, você deve sempre fornecer a id chamada de função à qual o resultado corresponde. Isso é importante para que a IA entenda o contexto do resultado. Alguns LLMs, como OpenAI, lançarão um erro se o id estiver faltando ou se o id não corresponder a uma chamada de função.

Inspecionando um objeto de histórico de bate-papo

Sempre que você passar um objeto de histórico de bate-papo para um serviço de conclusão de bate-papo com a chamada de função automática habilitada, o objeto de histórico de bate-papo será manipulado para incluir as chamadas de função e os resultados. Isso permite que você evite ter que adicionar manualmente essas mensagens ao objeto de histórico de bate-papo e também permite que você inspecione o objeto de histórico de bate-papo para ver as chamadas de função e os resultados.

No entanto, você ainda deve adicionar as mensagens finais ao objeto de histórico de bate-papo. Abaixo está um exemplo de como você pode inspecionar o objeto de histórico de bate-papo para ver as chamadas de função e os resultados.

using Microsoft.SemanticKernel.ChatCompletion;

ChatHistory chatHistory = [
    new() {
        Role = AuthorRole.User,
        Content = "Please order me a pizza"
    }
];

// Get the current length of the chat history object
int currentChatHistoryLength = chatHistory.Count;

// Get the chat message content
ChatMessageContent results = await chatCompletionService.GetChatMessageContentAsync(
    chatHistory,
    kernel: kernel
);

// Get the new messages added to the chat history object
for (int i = currentChatHistoryLength; i < chatHistory.Count; i++)
{
    Console.WriteLine(chatHistory[i]);
}

// Print the final message
Console.WriteLine(results);

// Add the final message to the chat history object
chatHistory.Add(results);
from semantic_kernel.contents import ChatMessageContent

chat_history = ChatHistory([
    ChatMessageContent(
        role=AuthorRole.USER,
        content="Please order me a pizza"
    )
])

# Get the current length of the chat history object
current_chat_history_length = len(chat_history)

# Get the chat message content
results = await chat_completion.get_chat_message_content(
    chat_history=history,
    settings=execution_settings,
    kernel=kernel,
)

# Get the new messages added to the chat history object
for i in range(current_chat_history_length, len(chat_history)):
    print(chat_history[i])

# Print the final message
print(results)

# Add the final message to the chat history object
chat_history.add_message(results)
import com.microsoft.semantickernel.services.chatcompletion.ChatHistory;

ChatHistory chatHistory = new ChatHistory();
chatHistory.addUserMessage("Please order me a pizza");

// Get the chat message content
List<ChatMessageContent> results = chatCompletionService.getChatMessageContentsAsync(
    chatHistory,
    kernel,
    null
).block();

results.forEach(result -> System.out.println(result.getContent());

// Get the new messages added to the chat history object. By default, 
// the ChatCompletionService returns new messages only. 
chatHistory.addAll(results);

Redução do histórico de bate-papo

Gerenciar o histórico de bate-papo é essencial para manter conversas sensíveis ao contexto e, ao mesmo tempo, garantir um desempenho eficiente. À medida que uma conversa progride, o objeto de histórico pode crescer além dos limites da janela de contexto de um modelo, afetando a qualidade da resposta e retardando o processamento. Uma abordagem estruturada para reduzir o histórico de bate-papo garante que as informações mais relevantes permaneçam disponíveis sem sobrecarga desnecessária.

Por que reduzir o histórico de bate-papo?

  • Otimização de desempenho: grandes históricos de bate-papo aumentam o tempo de processamento. Reduzir seu tamanho ajuda a manter interações rápidas e eficientes.
  • Gerenciamento de janela de contexto: Os modelos de linguagem têm uma janela de contexto fixa. Quando o histórico excede esse limite, mensagens mais antigas são perdidas. Gerenciar o histórico de bate-papo garante que o contexto mais importante permaneça acessível.
  • Eficiência de memória: Em ambientes com recursos limitados, como aplicativos móveis ou sistemas incorporados, o histórico de bate-papo ilimitado pode levar ao uso excessivo de memória e desempenho lento.
  • Privacidade e Segurança: Reter o histórico de conversas desnecessárias aumenta o risco de expor informações confidenciais. Um processo de redução estruturado minimiza a retenção de dados, mantendo o contexto relevante.

Estratégias para reduzir o histórico de bate-papo

Várias abordagens podem ser usadas para manter o histórico de bate-papo gerenciável, preservando informações essenciais:

  • Truncamento: as mensagens mais antigas são removidas quando o histórico excede um limite predefinido, garantindo que apenas as interações recentes sejam mantidas.
  • Resumo: As mensagens mais antigas são condensadas em um resumo, preservando os principais detalhes e reduzindo o número de mensagens armazenadas.
  • Baseado em token: a redução baseada em tokens garante que o histórico de bate-papo permaneça dentro do limite de token de um modelo, medindo a contagem total de tokens e removendo ou resumindo mensagens antigas quando o limite é excedido.

Um Redutor de Histórico de Chat automatiza essas estratégias avaliando o tamanho do histórico e reduzindo-o com base em parâmetros configuráveis, como contagem de alvos (o número desejado de mensagens a reter) e contagem de limites (o ponto em que a redução é acionada). Ao integrar essas técnicas de redução, os aplicativos de bate-papo podem permanecer responsivos e com desempenho sem comprometer o contexto conversacional.

Na versão .NET do Kernel Semântico, a abstração do Chat History Reducer é definida pela interface IChatHistoryReducer:

namespace Microsoft.SemanticKernel.ChatCompletion;

[Experimental("SKEXP0001")]
public interface IChatHistoryReducer
{
    Task<IEnumerable<ChatMessageContent>?> ReduceAsync(IReadOnlyList<ChatMessageContent> chatHistory, CancellationToken cancellationToken = default);
}

Esta interface permite implementações personalizadas para redução do histórico de chat.

Além disso, o Semantic Kernel fornece redutores integrados:

  • ChatHistoryTruncationReducer - trunca o histórico de bate-papo para um tamanho especificado e descarta as mensagens removidas. A redução é acionada quando a duração do histórico de bate-papo excede o limite.
  • ChatHistorySummarizationReducer - trunca o histórico de bate-papo, resume as mensagens removidas e adiciona o resumo de volta ao histórico de bate-papo como uma única mensagem.

Ambos os redutores sempre preservam as mensagens do sistema de modo a conservar o contexto essencial para o modelo.

O exemplo a seguir demonstra como reter apenas as duas últimas mensagens do usuário enquanto mantém o fluxo de conversa:

using Microsoft.SemanticKernel.ChatCompletion;
using Microsoft.SemanticKernel.Connectors.OpenAI;

var chatService = new OpenAIChatCompletionService(
    modelId: "<model-id>",
    apiKey: "<api-key>");

var reducer = new ChatHistoryTruncationReducer(targetCount: 2); // Keep system message and last user message

var chatHistory = new ChatHistory("You are a librarian and expert on books about cities");

string[] userMessages = [
    "Recommend a list of books about Seattle",
    "Recommend a list of books about Dublin",
    "Recommend a list of books about Amsterdam",
    "Recommend a list of books about Paris",
    "Recommend a list of books about London"
];

int totalTokenCount = 0;

foreach (var userMessage in userMessages)
{
    chatHistory.AddUserMessage(userMessage);

    Console.WriteLine($"\n>>> User:\n{userMessage}");

    var reducedMessages = await reducer.ReduceAsync(chatHistory);

    if (reducedMessages is not null)
    {
        chatHistory = new ChatHistory(reducedMessages);
    }

    var response = await chatService.GetChatMessageContentAsync(chatHistory);

    chatHistory.AddAssistantMessage(response.Content!);

    Console.WriteLine($"\n>>> Assistant:\n{response.Content!}");

    if (response.InnerContent is OpenAI.Chat.ChatCompletion chatCompletion)
    {
        totalTokenCount += chatCompletion.Usage?.TotalTokenCount ?? 0;
    }
}

Console.WriteLine($"Total Token Count: {totalTokenCount}");

Mais exemplos podem ser encontrados no repositório Semantic Kernel .

Nesta seção, abordamos os detalhes da implementação da redução do histórico de bate-papo em Python. A abordagem envolve a criação de um ChatHistoryReducer que se integra perfeitamente com o objeto ChatHistory, permitindo que ele seja usado e passado sempre que um histórico de bate-papo for necessário.

  • Integração: Em Python, o ChatHistoryReducer é projetado para ser uma subclasse do objeto ChatHistory. Essa herança permite que o redutor seja intercambiável com instâncias padrão do histórico de bate-papo.
  • Lógica de redução: os usuários podem invocar o método reduce no objeto de histórico de bate-papo. O redutor avalia se a contagem de mensagens atual excede target_count mais threshold_count (se definido). Se isso acontecer, a história é reduzida a target_count por truncamento ou sumarização.
  • Configuração: O comportamento de redução é configurável através de parâmetros como target_count (o número desejado de mensagens a reter) e threshold_count (a contagem de mensagens que desencadeia o processo de redução).

Os redutores de histórico do Kernel Semântico Python suportados são ChatHistorySummarizationReducer e ChatHistoryTruncationReducer. Como parte da configuração do redutor, auto_reduce pode ser ativado para aplicar automaticamente a redução de histórico quando usado com add_message_async, garantindo que o histórico de bate-papo permaneça dentro dos limites configurados.

O exemplo a seguir demonstra como usar ChatHistoryTruncationReducer para reter apenas as duas últimas mensagens enquanto mantém o fluxo de conversa.

import asyncio

from semantic_kernel.connectors.ai.open_ai import AzureChatCompletion
from semantic_kernel.contents import ChatHistoryTruncationReducer
from semantic_kernel.kernel import Kernel


async def main():
    kernel = Kernel()
    kernel.add_service(AzureChatCompletion())

    # Keep the last two messages
    truncation_reducer = ChatHistoryTruncationReducer(
        target_count=2,
    )
    truncation_reducer.add_system_message("You are a helpful chatbot.")

    is_reduced = False

    while True:
        user_input = input("User:> ")

        if user_input.lower() == "exit":
            print("\n\nExiting chat...")
            break

        is_reduced = await truncation_reducer.reduce()
        if is_reduced:
            print(f"@ History reduced to {len(truncation_reducer.messages)} messages.")

        response = await kernel.invoke_prompt(
            prompt="{{$chat_history}}{{$user_input}}", user_input=user_input, chat_history=truncation_reducer
        )

        if response:
            print(f"Assistant:> {response}")
            truncation_reducer.add_user_message(str(user_input))
            truncation_reducer.add_message(response.value[0])

    if is_reduced:
        for msg in truncation_reducer.messages:
            print(f"{msg.role} - {msg.content}\n")
        print("\n")


if __name__ == "__main__":
    asyncio.run(main())

A Redução do Histórico de Chat não está disponível no momento em Java.

Próximos passos

Agora que você sabe como criar e gerenciar um objeto de histórico de bate-papo, você pode aprender mais sobre chamada de função no tópico Chamada de função.