聊天历史记录

聊天历史记录对象用于维护聊天会话中消息的记录。 它用于存储来自不同作者的消息,例如用户、助理、工具或系统。 作为发送和接收消息的主要机制,聊天历史记录对象对于维护对话中的上下文和连续性至关重要。

创建聊天历史记录对象

聊天历史记录对象是后台列表,便于创建和添加消息。

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

将更丰富的消息添加到聊天历史记录

将消息添加到聊天历史记录对象的最简单方法是使用上述方法。 但是,还可以通过创建新 ChatMessage 对象手动添加消息。 这样,你可以提供其他信息,如名称和图像内容。

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

模拟函数调用

除了用户、助理和系统角色外,还可以从工具角色添加消息来模拟函数调用。 这可用于教授 AI 如何使用插件并提供对话的其他上下文。

例如,若要在聊天历史记录中注入有关当前用户的信息,而无需用户提供信息或让 LLM 浪费时间请求它,可以使用工具角色直接提供信息。

下面是一个示例,演示如何通过模拟对插件的函数调用 User 向助手提供用户过敏。

提示

模拟函数调用特别有助于提供有关当前用户的详细信息。 今天的 LLM 经过培训,对用户信息特别敏感。 即使你在系统消息中提供用户详细信息,LLM 仍可能选择忽略它。 如果通过用户消息或工具消息提供它,则 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. 

重要

模拟工具结果时,必须始终提供 id 结果所对应的函数调用。 这对于 AI 了解结果上下文非常重要。 某些 LLM(如 OpenAI)会在缺少id或与函数调用不对应时id引发错误。

检查聊天历史记录对象

只要将聊天历史记录对象传递到启用了自动调用功能的聊天完成服务,就会操作聊天历史记录对象,以便它包括函数调用和结果。 这样,就无需手动将这些消息添加到聊天历史记录对象,还可以检查聊天历史记录对象以查看函数调用和结果。

但是,仍必须将最终消息添加到聊天历史记录对象。 下面是如何检查聊天历史记录对象以查看函数调用和结果的示例。

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

聊天历史记录减少

管理聊天历史记录对于维护上下文感知对话至关重要,同时确保高效性能。 随着对话的进行,历史记录对象可能会超出模型上下文窗口的限制,从而影响响应质量并降低处理速度。 减少聊天历史记录的结构化方法可确保最相关的信息仍然可用,而无需不必要的开销。

为什么减少聊天历史记录?

  • 性能优化:大型聊天历史记录会增加处理时间。 减小其大小有助于保持快速高效的交互。
  • 上下文窗口管理:语言模型具有固定的上下文窗口。 当历史记录超过此限制时,旧消息将丢失。 管理聊天历史记录可确保最重要的上下文保持可访问性。
  • 内存效率:在受资源约束的环境(如移动应用程序或嵌入式系统)中,无限制的聊天历史记录可能会导致内存使用率过高和性能降低。
  • 隐私和安全:保留不必要的对话历史记录会增加公开敏感信息的风险。 结构化缩减过程可最大程度地减少数据保留期,同时维护相关上下文。

减少聊天历史记录的策略

可以使用多种方法来保持聊天历史记录可管理,同时保留基本信息:

  • 截断:当历史记录超过预定义限制时,将删除最早的消息,确保仅保留最近的交互。
  • 摘要:较旧的消息压缩为摘要,同时保留关键详细信息,同时减少存储的消息数。
  • 基于令牌的减少:基于令牌的减少通过测量令牌总数并在超出限制时删除或汇总旧消息,确保聊天历史记录保持在模型的令牌限制范围内。

聊天历史记录化简器通过评估历史记录的大小并根据可配置参数(要保留的消息数)和阈值计数(触发减少点)来自动执行这些策略。 通过集成这些减少技术,聊天应用程序可以保持响应性和性能,而不会损害聊天上下文。

在语义内核的 .NET 版本中,聊天历史记录化简器抽象由 IChatHistoryReducer 接口定义:

namespace Microsoft.SemanticKernel.ChatCompletion;

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

此接口允许自定义实现来减少聊天历史记录。

此外,语义内核还提供内置化简器:

  • ChatHistoryTruncationReducer - 将聊天历史记录截断为指定大小并丢弃已删除的消息。 当聊天历史记录长度超过限制时,将触发减少。
  • ChatHistorySummarizationReducer - 截断聊天历史记录,汇总已删除的消息,并将摘要添加回聊天历史记录作为单个消息。

这两个化简器始终保留系统消息,以保留模型的基本上下文。

以下示例演示如何在维护聊天流时仅保留最后两条用户消息:

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

可以在语义内核 存储库中找到更多示例。

在本部分中,我们将介绍 Python 中聊天历史记录减少的实现详细信息。 该方法涉及创建与 ChatHistory 对象无缝集成的 ChatHistoryReducer,从而允许在需要聊天历史记录的位置使用和传递聊天历史记录。

  • 集成:在 Python 中,ChatHistoryReducer 设计为 ChatHistory 对象的子类。 此继承允许化简器与标准聊天历史记录实例互换。
  • 减少逻辑:用户可以在聊天历史记录对象上调用 reduce 方法。 化简器评估当前消息计数是否超过 target_count 加上 threshold_count(如果已设置)。 如果这样做,则通过截断或汇总将历史记录减少到 target_count
  • 配置:可通过 target_count(需要保留的消息数)和threshold_count(触发缩减过程的消息计数)等参数配置缩减行为。

支持的语义内核 Python 历史记录化简器 ChatHistorySummarizationReducerChatHistoryTruncationReducer。 作为化简器配置的一部分,可以启用 auto_reduce,以便在与 add_message_async一起使用时自动应用历史记录减少,确保聊天历史记录保持在配置的限制范围内。

以下示例演示如何使用 ChatHistoryTruncationReducer 在维护聊天流的同时仅保留最后两条消息。

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

聊天记录削减目前在 Java 中不可用。

后续步骤

了解如何创建和管理聊天历史记录对象后,可以在函数调用主题中 了解有关函数调用 的详细信息。