Condividi tramite


Cronologia chat

L'oggetto cronologia chat viene usato per mantenere un record di messaggi in una sessione di chat. Viene usato per archiviare messaggi provenienti da autori diversi, ad esempio utenti, assistenti, strumenti o il sistema. Come meccanismo principale per l'invio e la ricezione di messaggi, l'oggetto cronologia chat è essenziale per mantenere il contesto e la continuità in una conversazione.

Creazione di un oggetto cronologia chat

Un oggetto cronologia chat è un elenco sotto le quinte, semplificando la creazione e l'aggiunta di messaggi.

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.");

Aggiunta di messaggi più avanzati a una cronologia delle chat

Il modo più semplice per aggiungere messaggi a un oggetto cronologia chat consiste nell'usare i metodi precedenti. Tuttavia, è anche possibile aggiungere messaggi manualmente creando un nuovo ChatMessage oggetto . In questo modo è possibile fornire informazioni aggiuntive, ad esempio nomi e contenuti di immagini.

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."
);

Simulazione di chiamate di funzione

Oltre ai ruoli utente, assistente e di sistema, è anche possibile aggiungere messaggi dal ruolo dello strumento per simulare le chiamate di funzione. Questo è utile per insegnare all'intelligenza artificiale come usare i plug-in e fornire contesto aggiuntivo alla conversazione.

Ad esempio, per inserire informazioni sull'utente corrente nella cronologia delle chat senza richiedere all'utente di fornire le informazioni o di chiedere il tempo LLM, è possibile usare il ruolo dello strumento per fornire direttamente le informazioni.

Di seguito è riportato un esempio di come siamo in grado di fornire allergie utente all'assistente simulando una chiamata di funzione al plug-in User .

Suggerimento

Le chiamate di funzione simulate sono particolarmente utili per fornire informazioni dettagliate sugli utenti correnti. I moduli APM di oggi sono stati addestrati per essere particolarmente sensibili alle informazioni degli utenti. Anche se si specificano i dettagli dell'utente in un messaggio di sistema, l'LLM può comunque scegliere di ignorarlo. Se viene fornito tramite un messaggio utente o un messaggio dello strumento, è più probabile che venga usato da LLM.

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

Quando si simulano i risultati degli strumenti, è necessario specificare sempre l'oggetto id della chiamata di funzione a cui corrisponde il risultato. Questo è importante per l'intelligenza artificiale per comprendere il contesto del risultato. Alcuni LLMs, ad esempio OpenAI, genereranno un errore se manca id o se id non corrisponde a una chiamata di funzione.

Controllo di un oggetto cronologia chat

Ogni volta che si passa un oggetto cronologia chat a un servizio di completamento della chat con chiamata automatica abilitata, l'oggetto cronologia chat verrà modificato in modo che includa le chiamate di funzione e i risultati. Ciò consente di evitare di dover aggiungere manualmente questi messaggi all'oggetto cronologia chat e consente anche di esaminare l'oggetto cronologia chat per visualizzare le chiamate e i risultati della funzione.

È comunque necessario aggiungere i messaggi finali all'oggetto cronologia chat. Di seguito è riportato un esempio di come esaminare l'oggetto cronologia chat per visualizzare le chiamate di funzione e i risultati.

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

Riduzione della cronologia chat

La gestione della cronologia delle chat è essenziale per mantenere conversazioni con riconoscimento del contesto garantendo prestazioni efficienti. Man mano che una conversazione avanza, l'oggetto cronologia può aumentare oltre i limiti della finestra di contesto di un modello, influenzando la qualità della risposta e rallentando l'elaborazione. Un approccio strutturato per ridurre la cronologia delle chat garantisce che le informazioni più rilevanti rimangano disponibili senza sovraccarichi non necessari.

Perché ridurre la cronologia delle chat?

  • Ottimizzazione delle prestazioni: le cronologie di chat di grandi dimensioni aumentano il tempo di elaborazione. La riduzione delle dimensioni consente di mantenere interazioni veloci ed efficienti.
  • Context Window Management: i modelli linguistici hanno una finestra di contesto fissa. Quando la cronologia supera questo limite, i messaggi meno recenti andranno persi. La gestione della cronologia delle chat garantisce che il contesto più importante rimanga accessibile.
  • Efficienza della memoria: in ambienti con vincoli di risorse, ad esempio applicazioni per dispositivi mobili o sistemi incorporati, la cronologia delle chat non associate può causare un utilizzo eccessivo della memoria e prestazioni lente.
  • Privacy e sicurezza: la conservazione della cronologia delle conversazioni non necessarie aumenta il rischio di esporre informazioni riservate. Un processo di riduzione strutturata riduce al minimo la conservazione dei dati mantenendo il contesto pertinente.

Strategie per ridurre la cronologia delle chat

È possibile usare diversi approcci per mantenere gestibile la cronologia delle chat mantenendo al tempo stesso informazioni essenziali:

  • Troncamento: i messaggi meno recenti vengono rimossi quando la cronologia supera un limite predefinito, assicurando che vengano mantenute solo le interazioni recenti.
  • Riepilogo: i messaggi meno recenti vengono condensati in un riepilogo, mantenendo i dettagli chiave riducendo al contempo il numero di messaggi archiviati.
  • Basata su token: la riduzione basata su token garantisce che la cronologia delle chat rimanga entro il limite di token di un modello misurando il numero totale di token e rimuovendo o riepilogando i messaggi meno recenti quando viene superato il limite.

Un riduttore della cronologia della chat automatizza queste strategie valutando le dimensioni della cronologia e riducendola basandosi su parametri configurabili, come il numero di destinazione (il numero desiderato di messaggi da conservare) e la soglia (il punto in cui viene attivata la riduzione). Integrando queste tecniche di riduzione, le applicazioni di chat possono rimanere reattive e efficienti senza compromettere il contesto conversazionale.

Nella versione .NET del Semantic Kernel, l'astrazione del "Chat History Reducer" è definita dall'interfaccia IChatHistoryReducer.

namespace Microsoft.SemanticKernel.ChatCompletion;

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

Questa interfaccia consente implementazioni personalizzate per la riduzione della cronologia delle chat.

Il kernel semantico offre anche riduttori predefiniti:

  • ChatHistoryTruncationReducer: tronca la cronologia delle chat a una dimensione specificata e rimuove i messaggi rimossi. La riduzione viene attivata quando la lunghezza della cronologia delle chat supera il limite.
  • ChatHistorySummarizationReducer : tronca la cronologia delle chat, riepiloga i messaggi rimossi e aggiunge nuovamente il riepilogo nella cronologia delle chat come singolo messaggio.

Entrambi i riduttori mantengono sempre i messaggi di sistema per mantenere il contesto essenziale per il modello.

L'esempio seguente illustra come conservare solo gli ultimi due messaggi utente mantenendo il flusso di conversazione:

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

Altri esempi sono disponibili nel repository del kernel semantico .

In questa sezione vengono illustrati i dettagli di implementazione della riduzione della cronologia delle chat in Python. L'approccio prevede la creazione di un ChatHistoryReducer che si integra perfettamente con l'oggetto ChatHistory, consentendo di usarlo e passare ovunque sia necessaria una cronologia di chat.

  • Integrazione: in Python, il ChatHistoryReducer è progettato per essere una sottoclasse dell'oggetto ChatHistory. Questa ereditarietà consente al riduttore di essere intercambiabile con le istanze della cronologia di chat standard.
  • Logica di riduzione: gli utenti possono richiamare il metodo reduce nell'oggetto cronologia chat. Il riduttore valuta se il numero di messaggi corrente supera target_count più threshold_count (se impostato). In caso affermativo, la cronologia viene ridotta a target_count tramite troncamento o riepilogo.
  • Configurazione: il comportamento di riduzione è configurabile tramite parametri come target_count (il numero desiderato di messaggi da conservare) e threshold_count (conteggio dei messaggi che attiva il processo di riduzione).

I riduttori di cronologia Python del kernel semantico supportati sono ChatHistorySummarizationReducer e ChatHistoryTruncationReducer. Come parte della configurazione del reducer, auto_reduce può essere abilitata per applicare automaticamente la riduzione della cronologia quando viene usata con add_message_async, assicurando che la cronologia delle chat rimanga entro i limiti configurati.

L'esempio seguente illustra come usare ChatHistoryTruncationReducer per conservare solo gli ultimi due messaggi mantenendo il flusso della conversazione.

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())

La riduzione della cronologia chat non è attualmente disponibile in Java.

Passaggi successivi

Ora che si è appreso come creare e gestire un oggetto cronologia chat, è possibile ottenere altre informazioni sulle chiamate di funzione nell'argomento Chiamata di funzione .