0Pricing
AI Engineering Academy · Lekcja

Dlaczego bezstanowe LLM-y potrzebują pamięci zewnętrznej

Uczestnicy zrozumieją, dlaczego każde wywołanie API rozpoczyna się od zera, jak naiwne upychanie kontekstu prowadzi do gwałtownego wzrostu liczby tokenów oraz jakie strategie pamięci można stosować — od prostych po złożone.

Dlaczego bezstanowe LLM-y potrzebują pamięci zewnętrznej to bezpłatna lekcja AI Engineering Academy na CoddyKit. To lekcja 1 z 4. Możesz przeczytać całą lekcję poniżej za darmo — a potem ćwiczyć ją interaktywnie w przeglądarce z wbudowanym edytorem kodu i tutorem AI dostępnym 24/7. To część ścieżki edukacyjnej AI Engineering Academy, a Twój postęp synchronizuje się między webem a aplikacją CoddyKit. Kurs AI Engineering Academy zawiera 4 lekcji w sumie.

Modele LLM domyślnie nie mają pamięci

Każde wywołanie API modelu LLM jest całkowicie niezależne. Model otrzymuje wiadomości wysłane w ramach pojedynczego żądania i je przetwarza — nic więcej. Przy następnym wywołaniu model nie pamięta poprzedniej rozmowy. To tak, jakby rozmawiali Państwo z osobą pozbawioną pamięci krótkotrwałej. Ta bezstanowość jest zamierzona: ułatwia skalowanie i wdrażanie modeli, ale przenosi odpowiedzialność za pamięć na aplikację.

Problem amnezji w praktyce

Bez pamięci chatbot nie poradzi sobie z podstawowymi zadaniami wieloturowymi. Jeśli użytkownik powie w pierwszej turze „Mam na imię Alicja”, a w trzeciej zapyta „Jak mam na imię?”, model odpowie, że tego nie wie. Każda tura wygląda jak nowa rozmowa. Jest to dla użytkowników bardzo frustrujące. Rozwiązaniem jest przechowywanie historii rozmowy przez aplikację i dołączanie jej do każdego wywołania API.

# Naive stateless approach — model forgets everything
from openai import OpenAI

client = OpenAI()

def chat_stateless(user_message: str) -> str:
    response = client.chat.completions.create(
        model='gpt-4o-mini',
        messages=[  # Only the new message — no history!
            {'role': 'user', 'content': user_message}
        ]
    )
    return response.choices[0].message.content

chat_stateless('My name is Alice.')   # Model: 'Hello Alice!'
chat_stateless('What is my name?')    # Model: 'I do not know your name.'

Naiwne rozwiązanie: dołączanie całej historii

Najprostsze podejście do pamięci polega na dopisywaniu każdej tury do rosnącej listy i wysyłaniu całej listy przy każdym żądaniu. Działa to, ale ma istotną wadę: okno kontekstu ma ograniczony rozmiar. Okno obejmujące 128 tys. tokenów może wydawać się duże, lecz długa rozmowa z działem obsługi klienta zawierająca fragmenty kodu może je wyczerpać w kilka minut. Wysyłanie całej historii oznacza również wielokrotne płacenie za te same tokeny.

messages = []  # grows with every turn

def chat_with_full_history(user_message: str) -> str:
    messages.append({'role': 'user', 'content': user_message})
    response = client.chat.completions.create(
        model='gpt-4o-mini',
        messages=messages  # all history every time
    )
    reply = response.choices[0].message.content
    messages.append({'role': 'assistant', 'content': reply})
    return reply

# Works at first, but messages list grows unboundedly
# After 100 turns: could be 50,000+ tokens per request

Przestrzeń projektowa pamięci

Istnieje całe spektrum strategii pamięci, z których każda inaczej równoważy wierność kontekstu (czyli zakres zapamiętanej historii) i koszt tokenów (liczbę tokenów używanych w jednym żądaniu). Strategie uporządkowane od najprostszych do najbardziej zaawansowanych to: pamięć pełnego bufora, pamięć z przesuwanym oknem, pamięć podsumowań, pamięć encji oraz epizodyczna pamięć oparta na wektorach. Właściwy wybór zależy od długości rozmów i dostępnego budżetu.

Koszt tokenów historii rozmowy

Każde wywołanie API kosztuje tokeny zależnie od łącznej długości wszystkich wiadomości — zarówno wejścia (tablicy wiadomości), jak i wyjścia (odpowiedzi modelu). Jeśli w każdym żądaniu uwzględniana jest pełna historia, koszt tokenów rośnie kwadratowo wraz z długością rozmowy: tura N wysyła N wcześniejszych wiadomości. Rozmowa obejmująca 50 tur po 200 tokenów wysyła łącznie 200+400+600+...+10 000, czyli ponad 250 000 tokenów wejściowych.

import tiktoken

def estimate_conversation_cost(
    turns: int,
    tokens_per_turn: int,
    price_per_1k_input: float = 0.00015  # gpt-4o-mini
) -> float:
    total_input_tokens = sum(
        (i + 1) * tokens_per_turn  # each turn sends all previous turns
        for i in range(turns)
    )
    total_output_tokens = turns * tokens_per_turn
    cost = (total_input_tokens / 1000) * price_per_1k_input
    print(f'{turns} turns: {total_input_tokens:,} input tokens, ${cost:.4f}')
    return cost

estimate_conversation_cost(50, 200)

Gdzie przechowywać historię rozmowy

Historię rozmowy należy przechowywać poza procesem Pythona, aby przetrwała ponowne uruchomienie serwera i mogła być współdzielona przez wiele instancji. Typowe backendy przechowywania to: Redis zapewniający szybki dostęp do danych w pamięci z wygasaniem TTL (świetny do aktywnych sesji), PostgreSQL do trwałego przechowywania długoterminowego i analityki oraz DynamoDB do automatycznego skalowania bezserwerowego. LangChain od razu udostępnia konektory do wszystkich tych rozwiązań.

# Three storage options for conversation history

# 1. In-process dict (development only — lost on restart)
from langchain_core.chat_history import InMemoryChatMessageHistory

# 2. Redis (production — fast, TTL-based expiry)
from langchain_community.chat_message_histories import RedisChatMessageHistory
history = RedisChatMessageHistory(session_id='user-123', url='redis://localhost:6379')

# 3. PostgreSQL (production — durable, queryable)
from langchain_community.chat_message_histories import PostgresChatMessageHistory
history = PostgresChatMessageHistory(
    session_id='user-123',
    connection_string='postgresql://user:pass@localhost/db'
)

Identyfikatory sesji: rozdzielanie rozmów użytkowników

Gdy aplikacja obsługuje wielu użytkowników, należy utrzymywać osobne historie dla każdej rozmowy. session_id (zwykle UUID albo połączenie identyfikatora użytkownika i identyfikatora rozmowy) określa, którą historię należy wczytać dla danego żądania. RunnableWithMessageHistory w LangChain przyjmuje funkcję get_session_history, która otrzymuje identyfikator sesji i zwraca właściwy obiekt historii.

import uuid
from langchain_core.runnables.history import RunnableWithMessageHistory
from langchain_core.chat_history import InMemoryChatMessageHistory

store = {}  # session_id -> history (use Redis in production)

def get_session_history(session_id: str) -> InMemoryChatMessageHistory:
    if session_id not in store:
        store[session_id] = InMemoryChatMessageHistory()
    return store[session_id]

# Create a new session for each user conversation
def new_session() -> str:
    return str(uuid.uuid4())

alice_session = new_session()
bob_session = new_session()
# Alice and Bob's histories are completely independent

Wrapper RunnableWithMessageHistory

RunnableWithMessageHistory to natywny dla LCEL sposób LangChain na dodawanie pamięci do dowolnego łańcucha. Opakowuje łańcuch, automatycznie wczytuje historię przed każdym wywołaniem, dopisuje nową wiadomość użytkownika i odpowiedź AI, a następnie zapisuje całość z powrotem w magazynie. Należy określić, który klucz wejściowy zawiera wiadomość użytkownika oraz która zmienna promptu ma otrzymać historię.

from langchain_openai import ChatOpenAI
from langchain_core.prompts import ChatPromptTemplate, MessagesPlaceholder
from langchain_core.runnables.history import RunnableWithMessageHistory
from langchain_core.output_parsers import StrOutputParser

prompt = ChatPromptTemplate.from_messages([
    ('system', 'You are a helpful assistant.'),
    MessagesPlaceholder(variable_name='history'),  # history injected here
    ('human', '{input}'),
])

chain = prompt | ChatOpenAI(model='gpt-4o-mini') | StrOutputParser()

chain_with_memory = RunnableWithMessageHistory(
    chain,
    get_session_history,
    input_messages_key='input',
    history_messages_key='history'
)

Wywoływanie łańcucha z obsługą pamięci

Podczas wywoływania łańcucha opakowanego w RunnableWithMessageHistory należy przekazać słownik config z wartością configurable: {session_id: ...}. Informuje to opakowanie, z którego magazynu historii ma wczytać dane. Łańcuch zajmuje się resztą: wczytaniem historii przed wywołaniem, wstrzyknięciem jej do promptu oraz zapisaniem nowej wymiany po otrzymaniu odpowiedzi.

session_id = 'user-alice-session-1'

# First message
response1 = chain_with_memory.invoke(
    {'input': 'My name is Alice and I love Python.'},
    config={'configurable': {'session_id': session_id}}
)
print(response1)  # 'Hello Alice! Nice to meet you.'

# Second message — model now knows Alice's name and interest
response2 = chain_with_memory.invoke(
    {'input': 'What is my name and what do I love?'},
    config={'configurable': {'session_id': session_id}}
)
print(response2)  # 'Your name is Alice and you love Python!'

Tryby awarii pamięci, których należy unikać

Trzy częste błędy w implementacji pamięci: brak izolacji sesji — ponowne użycie jednego obiektu historii dla wszystkich użytkowników ujawnia prywatne dane między rozmowami. Ignorowanie TTL — przechowywanie historii bezterminowo zapełnia bazę danych; należy ustawić czas wygaśnięcia dla nieaktywnych sesji. Nadmierne zaufanie do historii — użytkownicy mogą wstrzyknąć fałszywe wspomnienia („Mówiłem, że jestem administratorem”), dlatego należy weryfikować twierdzenia względem wiarygodnego źródła, a nie polegać wyłącznie na historii rozmowy.

# Mistake 1: Shared history for all users
global_history = InMemoryChatMessageHistory()  # BAD!

# Fix: per-session history
store = {}  # keyed by session_id

# Mistake 2: No TTL on Redis history
# BAD: history = RedisChatMessageHistory(session_id=sid, url=url)
# Good: set TTL to 24 hours
history = RedisChatMessageHistory(
    session_id=session_id,
    url='redis://localhost',
    ttl=86400  # 24 hours in seconds
)

Wybór strategii pamięci

Pamięć full buffer należy stosować tylko w przypadku krótkich rozmów, gdy wiadomo, że cały kontekst zmieści się w limitach. Sliding window sprawdza się w przypadku ogólnych chatbotów — zachowuje ostatnie N wymian. Summary memory należy wybrać, gdy rozmowy są otwarte i mogą być bardzo długie. Vector memory jest przydatna, gdy użytkownicy muszą przywoływać konkretne fakty z dużo wcześniejszej części długiej rozmowy. Każdą z tych strategii omówimy w kolejnych lekcjach.

Szybki test

Sprawdź swoją wiedzę na temat tego, dlaczego bezstanowe modele LLM potrzebują pamięci zewnętrznej.

Podsumowanie lekcji

W tej lekcji nauczyłeś się, że: modele LLM są z założenia bezstanowe — każde wywołanie API widzi tylko to, co zostanie wysłane w tym żądaniu; naiwne przekazywanie całej historii zwiększa koszty tokenów kwadratowo i ostatecznie przekracza limity kontekstu; a RunnableWithMessageHistory to przejrzysty sposób LangChain na dodanie pamięci zewnętrznej z dowolnym backendem (Redis, PostgreSQL), indeksowanej identyfikatorem sesji. W następnej części omówimy konkretne strategie pamięci: pamięć buforową i okienkową.

Często zadawane pytania

Czy lekcja „Dlaczego bezstanowe LLM-y potrzebują pamięci zewnętrznej” jest bezpłatna?

Tak — pełny tekst „Dlaczego bezstanowe LLM-y potrzebują pamięci zewnętrznej” jest dostępny za darmo tutaj w sieci. Aby ćwiczyć ją interaktywnie (wbudowany edytor kodu i tutor AI dostępny 24/7) i odblokować resztę kursu AI Engineering Academy, przejdź na CoddyKit PRO. Kurs AI Engineering Academy zawiera 4 lekcji w sumie.

Co nauczysz się w „Dlaczego bezstanowe LLM-y potrzebują pamięci zewnętrznej”?

Uczestnicy zrozumieją, dlaczego każde wywołanie API rozpoczyna się od zera, jak naiwne upychanie kontekstu prowadzi do gwałtownego wzrostu liczby tokenów oraz jakie strategie pamięci można stosować —… Ćwiczysz AI Engineering Academy z praktycznym kodem, który uruchamiasz bezpośrednio w przeglądarce, a tutor AI dostępny 24/7 odpowiada na Twoje pytania podczas pracy nad lekcją.

Czy potrzebuję doświadczenia, aby zacząć AI Engineering Academy?

Nie wymagamy żadnego doświadczenia. AI Engineering Academy w CoddyKit jest strukturyzowany dla początkujących i zaawansowanych użytkowników, więc możesz zacząć tutaj lub od początku i uczyć się w swoim tempie. To lekcja 1 z 4.

Ile czasu zajmuje lekcja „Dlaczego bezstanowe LLM-y potrzebują pamięci zewnętrznej”?

Większość lekcji CoddyKit trwa około 5–10 minut. Każda lekcja to mały, interaktywny krok, dzięki czemu robisz systematyczne postępy i zawsze wracasz dokładnie do tego samego miejsca — na webie i w aplikacji.

Czy mogę pisać i uruchamiać kod w tej lekcji AI Engineering Academy?

Tak. Każda lekcja AI Engineering Academy zawiera wbudowany edytor kodu, więc piszesz i uruchamiasz prawdziwy kod bezpośrednio w przeglądarce i od razu otrzymujesz sprzężenie zwrotne od AI — bez konfiguracji na komputerze.

Wszystkie lekcje w tym kursie

  1. Dlaczego bezstanowe LLM-y potrzebują pamięci zewnętrznej
  2. Pamięć buforowa i okno kontekstu
  3. Pamięć podsumowań i skracanie uwzględniające tokeny
  4. Trwałe przechowywanie historii czatu w Redis i PostgreSQL
← Powrót do AI Engineering Academy