Compartilhar via


Histórico de chat

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.

Criação de um objeto de histórico de bate-papo

Um objeto de histórico de bate-papo é uma lista oculta, facilitando a criação e a 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.");

Adição de mensagens mais avançadas a um histórico de bate-papo

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 plug-ins e fornecer contexto adicional à 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 solicitando-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 plug-in.

Dica

As chamadas de função simuladas são particularmente úteis para fornecer detalhes sobre os usuários atuais. 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á-la. Se você fornecê-lo por meio de uma mensagem do usuário ou mensagem da ferramenta, é mais provável que o LLM o use.

// 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 resultados de ferramentas, você deve sempre fornecer o id da chamada de função à qual o resultado corresponde. Isso é importante para que a IA entenda o contexto do resultado. Alguns LLMs, como o OpenAI, lançarão um erro se o id estiver ausente ou se não id 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 chat para um serviço de conclusão de chat com chamada automática de função habilitada, o objeto de histórico de chat 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 do histórico de bate-papo. Abaixo está um exemplo de como você pode inspecionar o objeto de histórico de chat para ver as chamadas e os resultados da função.

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 chat

O gerenciamento do histórico de chat é essencial para manter conversas com reconhecimento de contexto, garantindo um desempenho eficiente. À medida que uma conversa progride, o objeto histórico pode crescer além dos limites da janela de contexto de um modelo, afetando a qualidade da resposta e diminuindo o processamento. Uma abordagem estruturada para reduzir o histórico de chat garante que as informações mais relevantes permaneçam disponíveis sem sobrecarga desnecessária.

Por que reduzir o histórico de chat?

  • Otimização de Desempenho: grandes históricos de chat 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, as mensagens mais antigas são perdidas. O gerenciamento do histórico de chat garante que o contexto mais importante permaneça acessível.
  • Eficiência de memória: em ambientes restritos a recursos, como aplicativos móveis ou sistemas inseridos, o histórico de chat não associado pode levar ao uso excessivo de memória e ao desempenho lento.
  • Privacidade e segurança: manter o histórico de conversas desnecessário 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 chat

Várias abordagens podem ser usadas para manter o histórico de chat 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 interações recentes sejam retidas.
  • Resumo: as mensagens mais antigas são condensadas em um resumo, preservando os detalhes da chave e reduzindo o número de mensagens armazenadas.
  • Baseado em token: a redução baseada em token garante que o histórico de chat permaneça dentro do limite de token de um modelo medindo a contagem total de tokens e removendo ou resumindo mensagens mais antigas quando o limite for 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 destino (o número desejado de mensagens a serem retidas) e contagem de limites (o ponto no qual a redução é disparada). Ao integrar essas técnicas de redução, os aplicativos de chat podem permanecer responsivos e com desempenho sem comprometer o contexto de conversa.

Na versão .NET do Kernel Semântico, a abstração do Redutor de Histórico de Chat é definida pela interface IChatHistoryReducer.

namespace Microsoft.SemanticKernel.ChatCompletion;

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

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

Além disso, o Kernel Semântico fornece redutores internos:

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

Ambos os redutores sempre preservam mensagens do sistema para manter o contexto essencial para o modelo.

O exemplo a seguir demonstra como reter apenas as duas últimas mensagens de 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 do Kernel Semântico.

Nesta seção, abordaremos os detalhes de implementação da redução do histórico de chat no Python. A abordagem envolve a criação de um ChatHistoryReducer que se integra perfeitamente ao objeto ChatHistory, permitindo que ele seja usado e passado onde quer que um histórico de chat seja necessário.

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

Os redutores de histórico semântico com suporte no Kernel Semântico para Python são ChatHistorySummarizationReducer e ChatHistoryTruncationReducer. Como parte da configuração do redutor, auto_reduce pode ser habilitado para aplicar automaticamente a redução de histórico quando usado com add_message_async, garantindo que o histórico de chat 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 está indisponível no momento em Java.

Próximas etapas

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