Historia czatów
Obiekt historii czatu służy do obsługi rekordu wiadomości w sesji czatu. Służy do przechowywania komunikatów od różnych autorów, takich jak użytkownicy, asystenci, narzędzia lub system. Podstawowym mechanizmem wysyłania i odbierania wiadomości obiekt historii czatów jest niezbędny do utrzymania kontekstu i ciągłości konwersacji.
Tworzenie obiektu historii czatów
Obiekt historii czatów jest listą pod maską, dzięki czemu można łatwo tworzyć i dodawać wiadomości.
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.");
Dodawanie bogatszych wiadomości do historii czatów
Najprostszym sposobem dodawania wiadomości do obiektu historii czatów jest użycie powyższych metod. Można jednak również ręcznie dodać komunikaty, tworząc nowy ChatMessage
obiekt. Dzięki temu można podać dodatkowe informacje, takie jak nazwy i zawartość obrazów.
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."
);
Symulowanie wywołań funkcji
Oprócz ról użytkownika, asystenta i systemu można również dodawać komunikaty z roli narzędzia w celu symulowania wywołań funkcji. Jest to przydatne w przypadku nauczania sztucznej inteligencji sposobu używania wtyczek i udostępniania dodatkowego kontekstu konwersacji.
Aby na przykład wstrzyknąć informacje o bieżącym użytkowniku w historii czatu bez konieczności podawania informacji przez użytkownika lub proszenia o nieużytkowania czasu w usłudze LLM, możesz użyć roli narzędzia, aby przekazać informacje bezpośrednio.
Poniżej przedstawiono przykład sposobu, w jaki możemy zapewnić alergie użytkownika asystentowi, symulując wywołanie funkcji do User
wtyczki.
Napiwek
Symulowane wywołania funkcji są szczególnie przydatne w przypadku podawania szczegółowych informacji o bieżących użytkownikach. Dzisiejsze moduły LLM zostały przeszkolone, aby były szczególnie wrażliwe na informacje o użytkowniku. Nawet jeśli podasz szczegóły użytkownika w komunikacie systemowym, usługa LLM może nadal go zignorować. Jeśli podasz go za pośrednictwem komunikatu użytkownika lub komunikatu narzędzia, narzędzie LLM będzie bardziej prawdopodobne, aby go użyć.
// 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.
Ważne
Podczas symulowania wyników narzędzia należy zawsze podać id
wywołanie funkcji odpowiadające wynikowi. Jest to ważne, aby sztuczna inteligencja zrozumiała kontekst wyniku. Niektóre moduły LLM, takie jak OpenAI, zgłaszają błąd, jeśli id
brakuje elementu lub jeśli id
nie odpowiada wywołaniu funkcji.
Inspekcja obiektu historii czatów
Za każdym razem, gdy przekażesz obiekt historii czatu do usługi uzupełniania czatów z włączoną funkcją automatyczną, obiekt historii czatu będzie manipulowany tak, aby zawierał wywołania funkcji i wyniki. Pozwala to uniknąć konieczności ręcznego dodawania tych wiadomości do obiektu historii czatów, a także umożliwia sprawdzenie obiektu historii czatów w celu wyświetlenia wywołań funkcji i wyników.
Musisz jednak dodać końcowe wiadomości do obiektu historii czatu. Poniżej przedstawiono przykładowy sposób sprawdzania obiektu historii czatów w celu wyświetlenia wywołań funkcji i wyników.
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);
Redukcja historii czatów
Zarządzanie historią czatów jest niezbędne do utrzymania świadomych kontekstu konwersacji przy jednoczesnym zapewnieniu wydajności. W miarę postępu konwersacji obiekt historii może przekroczyć limity okna kontekstowego modelu, wpływając na jakość odpowiedzi i spowalniając przetwarzanie. Ustrukturyzowane podejście do zmniejszania historii czatów zapewnia, że najbardziej istotne informacje pozostają dostępne bez niepotrzebnych obciążeń.
Dlaczego warto zmniejszyć historię czatów?
- Optymalizacja wydajności: duże historie rozmów zwiększają czas przetwarzania. Zmniejszenie ich rozmiaru pomaga w utrzymaniu szybkich i wydajnych interakcji.
- Zarządzanie oknami kontekstowymi: Modele językowe mają stałe okno kontekstu. Gdy historia przekroczy ten limit, starsze komunikaty zostaną utracone. Zarządzanie historią czatów gwarantuje, że najważniejszy kontekst pozostanie dostępny.
- Wydajność pamięci: w środowiskach ograniczonych zasobami, takich jak aplikacje mobilne lub systemy osadzone, niezwiązana historia czatów może prowadzić do nadmiernego użycia pamięci i niskiej wydajności.
- Prywatność i zabezpieczenia: Zachowanie niepotrzebnej historii konwersacji zwiększa ryzyko ujawnienia poufnych informacji. Proces redukcji strukturalnej minimalizuje przechowywanie danych przy zachowaniu odpowiedniego kontekstu.
Strategie zmniejszania historii czatów
Kilka podejść może służyć do zarządzania historią czatów przy zachowaniu podstawowych informacji:
- Obcięcie: najstarsze komunikaty są usuwane, gdy historia przekracza wstępnie zdefiniowany limit, zapewniając zachowywanie tylko ostatnich interakcji.
- Podsumowanie: Starsze komunikaty są skondensowane w podsumowaniu, zachowując kluczowe szczegóły przy jednoczesnym zmniejszeniu liczby przechowywanych komunikatów.
- Oparte na tokenach: redukcja oparta na tokenach zapewnia, że historia czatów pozostaje w limicie tokenu modelu, mierząc łączną liczbę tokenów i usuwając lub podsumowując starsze wiadomości po przekroczeniu limitu.
Reduktor historii czatów automatyzuje te strategie, oceniając rozmiar historii i zmniejszając go na podstawie konfigurowalnych parametrów, takich jak liczba docelowa (żądana liczba komunikatów do zachowania) i liczba progów (punkt, w którym jest wyzwalana redukcja). Dzięki zintegrowaniu tych technik redukcji aplikacje czatów mogą pozostać dynamiczne i wydajne bez naruszania kontekstu konwersacyjnego.
W wersji semantycznej jądra platformy .NET abstrakcja reduktora historii czatów jest definiowana przez interfejs IChatHistoryReducer
:
namespace Microsoft.SemanticKernel.ChatCompletion;
[Experimental("SKEXP0001")]
public interface IChatHistoryReducer
{
Task<IEnumerable<ChatMessageContent>?> ReduceAsync(IReadOnlyList<ChatMessageContent> chatHistory, CancellationToken cancellationToken = default);
}
Ten interfejs umożliwia niestandardowe implementacje na potrzeby redukcji historii czatów.
Ponadto semantyczne jądro zapewnia wbudowane reduktory:
-
ChatHistoryTruncationReducer
— obcina historię czatów do określonego rozmiaru i odrzuca usunięte wiadomości. Redukcja jest wyzwalana, gdy długość historii czatu przekracza limit. -
ChatHistorySummarizationReducer
— obcina historię czatów, podsumowuje usunięte wiadomości i dodaje podsumowanie z powrotem do historii czatu jako pojedynczą wiadomość.
Oba reduktory zawsze zachowują komunikaty systemowe, aby zachować podstawowy kontekst dla modelu.
W poniższym przykładzie pokazano, jak zachować tylko dwa ostatnie komunikaty użytkowników przy zachowaniu przepływu konwersacji:
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}");
Więcej przykładów można znaleźć w repozytorium Semantic Kernel .
W tej sekcji omówiono szczegóły implementacji redukcji historii czatów w języku Python. Podejście polega na utworzeniu obiektu ChatHistoryReducer, który bezproblemowo integruje się z obiektem ChatHistory, dzięki czemu może być używany i przekazywany wszędzie tam, gdzie jest wymagana historia czatów.
- Integracja: w języku Python
ChatHistoryReducer
jest przeznaczona do podklasy obiektuChatHistory
. To dziedziczenie pozwala reduktorowi na bycie wymiennym z instancjami standardowej historii czatu. - Logika redukcji: użytkownicy mogą wywoływać metodę
reduce
w obiekcie historii czatów. Reduktor ocenia, czy bieżąca liczba komunikatów przekraczatarget_count
plusthreshold_count
(jeśli ustawiono). Jeśli tak się stanie, historia zostanie zredukowana dotarget_count
poprzez obcięcie lub podsumowanie. - Konfiguracja: Zachowanie redukcji można konfigurować za pomocą parametrów, takich jak
target_count
(żądana liczba komunikatów do zachowania) i threshold_count (liczba komunikatów wyzwalających proces redukcji).
Obsługiwane reduktory historii jądra semantycznego w Pythonie to ChatHistorySummarizationReducer
i ChatHistoryTruncationReducer
. W ramach konfiguracji reduktora można włączyć auto_reduce
, aby automatycznie stosować redukcję historii w przypadku użycia z add_message_async
, zapewniając, że historia czatów pozostaje w skonfigurowanych limitach.
W poniższym przykładzie pokazano, jak używać metody ChatHistoryTruncationReducer do przechowywania tylko dwóch ostatnich wiadomości przy zachowaniu przepływu konwersacji.
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())
Redukcja historii czatów jest obecnie niedostępna w języku Java.
Następne kroki
Teraz, gdy wiesz już, jak utworzyć obiekt historii czatów i zarządzać nim, możesz dowiedzieć się więcej na temat wywoływania funkcji w temacie Wywoływanie funkcji.