Поделиться через


Журнал чата

Объект журнала чата используется для хранения записи сообщений в сеансе чата. Он используется для хранения сообщений от разных авторов, таких как пользователи, помощники, инструменты или система. В качестве основного механизма отправки и получения сообщений объект журнала чата является важным для поддержания контекста и непрерывности беседы.

Создание объекта журнала чата

Объект журнала чата — это список под капотом, что упрощает создание и добавление сообщений.

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

Имитация вызовов функций

Помимо пользовательских, помощников и системных ролей, можно также добавлять сообщения из роли средства для имитации вызовов функций. Это полезно для обучения ИИ, как использовать подключаемые модули и предоставить дополнительный контекст для беседы.

Например, чтобы ввести сведения о текущем пользователе в журнале чата, не требуя от пользователя предоставления сведений или запроса времени 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 вызов функции, соответствующий результату. Это важно для ИИ, чтобы понять контекст результата. Некоторые 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. Этот подход предполагает создание ChatHistoryReducer, который легко интегрируется с объектом ChatHistory, что позволяет использовать его и передавать везде, где требуется журнал чата.

  • Интеграция. В Python ChatHistoryReducer предназначено для подкласса объекта ChatHistory. Это наследование позволяет редуктору быть взаимозаменяемым со стандартными экземплярами истории чата.
  • Логика сокращения: пользователи могут вызывать метод reduce в объекте журнала чата. Редуктор оценивает, превышает ли текущее число сообщений target_count плюс threshold_count (если задано). Если это так, история сокращается до target_count с помощью усечения или суммирования.
  • Конфигурация. Поведение сокращения можно настроить с помощью таких параметров, как target_count (требуемое количество сообщений для хранения) и threshold_count (число сообщений, которое активирует процесс сокращения).

Поддерживаемые сокращения журнала семантического ядра Python — это ChatHistorySummarizationReducer и ChatHistoryTruncationReducer. В рамках конфигурации средства сокращения 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.

Следующие шаги

Теперь, когда вы знаете, как создать объект журнала чата и управлять ими, вы можете узнать больше о вызове функций в разделе вызовов функции.