Thread Summarization w LLM: Jak nauczyć AI pamiętać kontekst rozmowy?

Podczas tworzenia aplikacji wykorzystujących modele językowe (LLM) naturalnym jest, że przy zadaniu pytaniu pytania, chcemy mieć możliwość odnoszenia się do uzyskanej odpowiedzi lub informacji zawartej w pytaniu. Chcemy, żeby model “zapamiętał” to co zostało powiedziane wcześniej. Natomiast natura modeli LLM jest odwrotna. Ich bezstanowość powoduje, że każde wywołanie API to rozpoczęcie rozmowy od początku – model nie wie, co zostało wcześniej powiedziane. Z rozwiązaniem przychodzi technika Thread Summarization, czyli podsumowywanie wątku rozmowy.

Bezstanowość modeli LLM

Ważna cecha, która trzeba brać pod uwagę podczas projektowanie aplikacji wykorzystujących LLM jest ich bezstanowość. Oznacza to, że modele nie pamiętają poprzednich interakcji z użytkownikiem. Każde zapytanie jest przetwarzane niezależnie, a kontekst rozmowy musi być przekazywany w całości w każdym kolejnym zapytaniu. Inaczej mówiąc model nie wie, co było wcześniej powiedziane.

Bezstanowość wynika z projektowych założeń stojących za architekturą LLM. Choć modele te mogą analizować długie teksty wejściowe i generować spójne odpowiedzi, to same w sobie nie posiadają mechanizmu pamięci długotrwałej.

W tym przykładzie widać, że model nie ma pojęcia o jakim filmie mówi użytkownik.

Jednym z rozwiązań jest emulowanie pamięci długotrwałej na czas rozmowy z modelem na poziomie aplikacji, gdzie przechowujemy skrót historii rozmowy i dodajemy go każdorazowo do zapytania.

Rozwiązanie: Thread Summarization

Thread Summarization to technika, która pozwala nam emulować „pamięć” w aplikacjach wykorzystujących LLM. Podstawowa idea jest prosta, ale skuteczna:

  1. Zbieranie kontekstu: Po każdej wymianie zdań (pytanie użytkownika i odpowiedź asystenta), generujemy zwięzłe podsumowanie tej interakcji.
  2. Akumulacja wiedzy: Nowe podsumowanie jest łączone z poprzednimi, tworząc skondensowany zapis całej rozmowy.
  3. Dostarczanie kontekstu: Przy każdym nowym zapytaniu, dostarczamy modelowi aktualne podsumowanie jako część promptu systemowego.

Zalety tego podejścia:

  • Oszczędność tokenów: Zamiast przekazywać całą historię rozmowy, używamy jej skróconej wersji, co pozwala na dłuższe konwersacje w ramach limitu kontekstu.
  • Kontrola nad pamięcią: Możemy świadomie decydować, które informacje są najważniejsze i powinny być zachowane.
  • Optymalizacja kosztów: Do generowania podsumowań możemy używać tańszych modeli, zachowując główny model do generowania odpowiedzi.

Implementacja skrótu wypowiedzi

Oto przykład implementacji w Pythonie z użyciem API OpenAI:

async def generate_summarization(user_message, assistant_response):
    global previous_summarization

    summarization_prompt = {
        "role": "system",
        "content": f"""Please summarize the following conversation in a concise manner, incorporating the previous summary if available:
<previous_summary>{previous_summarization or "No previous summary"}</previous_summary>
<current_turn> User: {user_message['content']}\\nAssistant: {assistant_response.content} </current_turn>"""
    }
    response = await openai_service.completion([summarization_prompt, {"role": "user", "content": "Please create/update our conversation summary."}], model="gpt-4o-mini", stream=False)
    return response.choices[0].message.content if response else "No conversation history"

def create_system_prompt(summarization):
    return {
        "role": "system",
        "content": f"""You are Cartman, a helpful assistant who's answering a user questions like Yoda in max 10 words.

        {'Here is a summary of the conversation: <conversation_summary>' if summarization else ''}
          {summarization if summarization else ''}"""
    }
    

@app.route('/api/chat', methods=['POST'])
async def chat():
    global previous_summarization
    try:
        data = request.get_json()
        message = data.get('message')
        system_prompt = create_system_prompt(previous_summarization)
        assistant_response = await openai_service.completion([
            system_prompt, 
            message
        ], model="gpt-4o", stream=False)

        previous_summarization = await generate_summarization(message, assistant_response.choices[0].message)

        return jsonify(assistant_response)
    except Exception as e:
        print('Error in OpenAI completion:', str(e))
        return jsonify({'error': 'An error occurred while processing your request'}), 500

Ten pseudo kod (całość dostępna tutaj) tworzy podsumowanie poprzedniej rozmowy użytkownika z asystentem a następnie dodaje ostania wiadomość użytkownika. Oczywiście przy takim podejściu tracimy część informacji, ale możemy łatwo rozbudować mechanizm o dodatkowe przeszukiwanie poprzednich wątków w razie gdy podsumowanie jest niewystarczające do udzielenia odpowiedzi.

Jak widać po odpytaniu o ulubiona część Gwiezdnych Wojen, model odpowiada prawidłowo, zgodnie z dana mu wcześniej informacją.

Dodatkowe plusy takiego rozwiązania to:

  • Wykorzystanie tańszego modelu gpt-4o-mini, a także dostarczenie mniejszej ilości tokenów, co spowoduje zmniejszenie kosztów.
  • Model otrzymuje mniejsza ilość treści, dzięki czemu model lepiej skupia się na zadaniu.

Co dalej?

  • Rozważ dodanie wektorowej bazy danych do przechowywania historii.
  • Zaimplementuj mechanizm priorytetyzacji informacji w podsumowaniach.
  • Eksperymentuj z różnymi promptami do generowania podsumowań.
Scroll to Top