Python & AI Tutorials Logo
LangChain & LangGraph

8. Stato della Conversazione e Memoria

Fino ad ora, ogni interazione con LLM che abbiamo costruito è stata stateless (senza stato). Stateless significa che ogni richiesta è indipendente—il modello non ha memoria delle conversazioni precedenti. Questo funziona bene per compiti singoli come il riassunto di documenti o domande e risposte singole.

Ma costruire un agente conversazionale è una storia diversa. Hai bisogno che l'agente ricordi ciò che è stato discusso in precedenza, comprenda pronomi come "esso" o "quello", e mantenga il contesto durante tutto il dialogo. Per fare questo, devi gestire esplicitamente lo stato (state).

(Qui, "stato" si riferisce alle informazioni che un programma ricorda. Per gli agenti conversazionali, la cronologia della conversazione precedente è lo stato.)

In questo capitolo, tratteremo:

  • Perché gli LLM non "ricordano" le conversazioni
  • Come implementare la memoria della conversazione usando gli strumenti di cronologia dei messaggi di LangChain
  • Come gestire i budget dei token per prevenire l'overflow del contesto

8.1) Perché gli LLM Dimenticano

Gli LLM Non Hanno Memoria

Gli LLM hanno una caratteristica critica: non ricordano nulla dalle conversazioni precedenti.

Quando chiami un'API LLM, il modello elabora il tuo input e genera una risposta. Ma non memorizza quel record da nessuna parte. Non c'è memoria all'interno del modello che mantiene lo stato, nessuna cronologia della conversazione. Ogni chiamata API è completamente indipendente. È come ricominciare da capo ogni volta.

Questo è per design. Gli LLM funzionano come funzioni stateless: fornisci input, producono output, e nulla viene mantenuto. Il modello in esecuzione sui server di OpenAI in questo momento non ha alcun record di ciò che hai appena chiesto.

Perché gli LLM Sembrano Ricordare

Ma aspetta—quando usi ChatGPT o Claude, sembra che ricordino la tua conversazione. Puoi dire "Parlami di Parigi", poi continuare con "Qual è la popolazione?" e il modello sa che stai ancora parlando di Parigi. Come funziona?

Ecco come: l'applicazione invia la cronologia della conversazione precedente insieme a ogni nuovo messaggio.

Ecco cosa succede realmente:

LLMApplicazioneUtenteLLMApplicazioneUtenteL'app memorizza la cronologia dei messaggi"Parlami di Parigi"[Messaggio 1: "Parlami di Parigi"]"Parigi è la capitale della Francia...""Parigi è la capitale della Francia...""Qual è la popolazione?"[Messaggio 1: "Parlami di Parigi"Messaggio 2: "Parigi è la capitale..."Messaggio 3: "Qual è la popolazione?"]"Parigi ha circa 2,1 milioni...""Parigi ha circa 2,1 milioni..."

L'LLM non "ricorda" che hai chiesto di Parigi prima—lo sa solo perché l'applicazione ha inviato la cronologia della conversazione precedente insieme al nuovo messaggio. In definitiva, è l'applicazione che gestisce lo stato, non il modello.

Perché lo "Stato" Deve Essere Gestito nella Tua Applicazione, Non nel Modello

Pensa a un LLM come a una funzione pura: dato un input, produce un output. Non c'è stato interno che l'LLM gestisce oltre ai messaggi che fornisci. Questo è per design.

Per gli agenti conversazionali, questo significa che lo stato deve essere gestito nella tua applicazione.

Lo stato—la cronologia della conversazione—vive nel codice della tua applicazione, non nel modello. Questo significa che sei responsabile di:

  • Memorizzare la cronologia della conversazione
  • Inviare la cronologia rilevante con ogni nuova richiesta
  • Gestire la dimensione della cronologia (trattato nella sezione 8.3)

Nella sezione 8.2, implementeremo questo usando gli strumenti di cronologia dei messaggi di LangChain.

Cosa Succede Se Non Gestisci lo Stato?

Se non gestisci lo stato, il tuo agente (la tua applicazione) non può mantenere una conversazione coerente. Ecco i fallimenti più comuni:

1. Non Può Ricordare la Conversazione Precedente

python
from langchain_openai import ChatOpenAI
from langchain_core.messages import HumanMessage
 
llm = ChatOpenAI(model="gpt-4o-mini")
 
# Prima domanda
response1 = llm.invoke([HumanMessage(content="Mi chiamo Alice")])
print(response1.content)  # Output: Piacere di conoscerti, Alice!
 
# Seconda domanda (nessuna cronologia inviata)
response2 = llm.invoke([HumanMessage(content="Come mi chiamo?")])
print(response2.content)  # Output: Non conosco il tuo nome...

Il modello non ha idea che hai detto che ti chiami Alice perché non abbiamo inviato quell'informazione nella seconda chiamata.

2. Non Può Capire a Cosa Si Riferiscono i Pronomi

python
# L'utente chiede di un argomento
response1 = llm.invoke([HumanMessage(content="Parlami di Python")])
print(response1.content)  
# Output: Python è un linguaggio di programmazione ad alto livello noto per la sua leggibilità...
 
# L'utente continua con un pronome
response2 = llm.invoke([HumanMessage(content="Quali sono le sue caratteristiche principali?")])
print(response2.content)  
# Output: Sarei felice di aiutarti! Potresti specificare di cosa stai chiedendo?

Senza il messaggio precedente, il modello non può capire a cosa si riferisce "sue".

Impatto nel Mondo Reale:

Immagina di costruire un agente di supporto clienti senza gestione dello stato:

Utente: "Ho un problema con il mio ordine #12345"
Agente: "Mi dispiace sentirlo. Qual è il problema?"
Utente: "L'indirizzo di spedizione è sbagliato"
Agente: "Posso aiutarti con questo. Potresti fornire il numero del tuo ordine?"
Utente: "Te l'ho appena detto..."

Questa esperienza frustra gli utenti e perde la loro fiducia. La gestione dello stato non è opzionale per gli agenti conversazionali—è essenziale per creare interazioni coerenti e utili.

Nella sezione 8.2, implementeremo la gestione dello stato usando gli strumenti di cronologia dei messaggi di LangChain e aggiungeremo la memoria della conversazione alla chat CLI.

8.2) Gestione dello Stato della Conversazione

Ora che comprendiamo perché la gestione dello stato è critica, impariamo come implementarla. Useremo gli strumenti di cronologia dei messaggi integrati di LangChain per gestire la cronologia della conversazione. Alla fine, applicheremo ciò che abbiamo imparato per aggiungere la gestione dello stato alla chat CLI del Capitolo 3.

Comprendere i Tipi di Messaggio: HumanMessage, AIMessage e SystemMessage

Prima di imparare come gestire lo stato della conversazione, devi conoscere i tipi di messaggio usati nello stato della conversazione. LangChain usa tre tipi di messaggio per rappresentare le conversazioni: HumanMessage (input utente), AIMessage (risposta del modello) e SystemMessage (istruzioni). Ogni messaggio ha un ruolo (tipo di messaggio) e contenuto (il testo effettivo).

python
from langchain_core.messages import HumanMessage, AIMessage, SystemMessage
 
# SystemMessage: Istruzioni per il comportamento del modello
system_msg = SystemMessage(content="Sei un assistente utile specializzato in programmazione Python.")
 
# HumanMessage: Input utente
user_msg = HumanMessage(content="Come leggo un file in Python?")
 
# AIMessage: Risposta del modello
# (In pratica, LangChain avvolge la risposta del modello in questo oggetto - mostrato qui per illustrazione)
ai_msg = AIMessage(content="Puoi usare la funzione `open()` con un context manager...")

Perché tipi di messaggio separati?

Perché il modello comprenda efficacemente la cronologia della conversazione, deve conoscere lo scopo di ogni messaggio e chi l'ha detto. I tre tipi di messaggio servono scopi distinti:

  • SystemMessage: Istruzioni che definiscono come il modello dovrebbe comportarsi (es., "Sii conciso", "Sei un tutor Python")
  • HumanMessage: Ciò che l'utente ha detto
  • AIMessage: Ciò che il modello ha risposto precedentemente

Questa struttura consente al modello di distinguere tra istruzioni, domande dell'utente e le proprie risposte passate—che è essenziale per mantenere conversazioni multi-turno coerenti.

Costruire Manualmente la Cronologia della Conversazione

Ora che comprendiamo i tre tipi di messaggio, vediamo come costruire manualmente una cronologia della conversazione:

python
from langchain_openai import ChatOpenAI
from langchain_core.messages import HumanMessage, AIMessage, SystemMessage
 
llm = ChatOpenAI(model="gpt-4o-mini")
 
# Costruisci la cronologia della conversazione
# SystemMessage imposta le istruzioni (una volta all'inizio)
# Poi: Input utente → Risposta AI → Input utente (flusso di conversazione naturale)
messages = [
    SystemMessage(content="Sei un tutor Python conciso."),
    HumanMessage(content="Cos'è una list comprehension?"),
    AIMessage(content="Una list comprehension è un modo conciso per creare liste: [x*2 for x in range(5)]"),
    HumanMessage(content="Puoi mostrarmi un esempio più complesso?")
]
 
# Invia l'intera cronologia con la nuova domanda
response = llm.invoke(messages)
print(response.content)

Output:

Certo! Ecco una list comprehension che filtra e trasforma:
[x**2 for x in range(10) if x % 2 == 0]
Questo produce:
[0, 4, 16, 36, 64]

Il modello comprende che "un esempio più complesso" si riferisce a un esempio di list comprehension più complesso perché abbiamo inviato l'intera cronologia della conversazione.

Usare InMemoryChatMessageHistory

Costruire manualmente liste di messaggi diventa scomodo man mano che le conversazioni crescono. InMemoryChatMessageHistory di LangChain semplifica questo fornendo metodi per aggiungere messaggi e recuperare la cronologia completa.

Metodi chiave:

  • add_message(message): Aggiunge un singolo messaggio (HumanMessage, AIMessage, SystemMessage)
  • add_messages(messages): Aggiunge più messaggi contemporaneamente
  • messages: Proprietà che restituisce la lista completa dei messaggi
  • clear(): Rimuove tutti i messaggi (utile per ricominciare da capo)

Esempio:

python
from langchain_core.chat_history import InMemoryChatMessageHistory
from langchain_core.messages import HumanMessage, AIMessage, SystemMessage
 
# Crea un archivio di cronologia dei messaggi
history = InMemoryChatMessageHistory()
 
# Aggiungi più messaggi contemporaneamente
history.add_messages([
    SystemMessage(content="Sei un tutor Python utile."),
    HumanMessage(content="Cos'è un decorator in Python?")
])
 
# Aggiungi messaggi uno alla volta
history.add_message(AIMessage(content="Un decorator è una funzione che modifica il comportamento di un'altra funzione..."))
history.add_message(HumanMessage(content="Puoi mostrare un esempio?"))
 
# Recupera tutti i messaggi
messages = history.messages

Esempio Pratico: Gestione dello Stato con InMemoryChatMessageHistory:

python
from langchain_openai import ChatOpenAI
from langchain_core.chat_history import InMemoryChatMessageHistory
from langchain_core.messages import SystemMessage, HumanMessage
 
llm = ChatOpenAI(model="gpt-4o-mini")
history = InMemoryChatMessageHistory()
 
# Imposta il messaggio di sistema e aggiungilo alla cronologia (fatto una volta all'inizio)
system_msg = SystemMessage(content="Sei un tutor Python utile.")
history.add_message(system_msg)
 
# Funzione di chat
def chat(user_input):
    """Elabora l'input utente, invia all'LLM e gestisce automaticamente la cronologia della conversazione"""
    # Aggiungi l'input utente alla cronologia
    history.add_message(HumanMessage(content=user_input))
    
    # Invia l'input utente insieme alla cronologia della conversazione precedente
    response = llm.invoke(history.messages)
    
    # Aggiungi la risposta del modello alla cronologia
    history.add_message(response)
    
    return response.content
 
# Simula conversazione
print(chat("Cos'è una funzione lambda?"))
print(chat("Mostrami un esempio"))  # Il modello ricorda il contesto
print(chat("Qual è la differenza da una funzione regolare?"))  # Ricorda ancora

Output:

Una funzione lambda è una funzione anonima definita con la parola chiave lambda...
 
Ecco un esempio: square = lambda x: x**2
Puoi usarla così: square(5) # Restituisce 25
 
Le funzioni lambda sono limitate a una singola espressione, mentre le funzioni regolari...

La funzione chat() gestisce automaticamente la gestione dello stato: invia ogni richiesta utente insieme alla cronologia della conversazione precedente all'LLM, e aggiunge sia la richiesta che la risposta alla cronologia. Semplicemente chiamando chat() si mantiene lo stato della conversazione senza gestione aggiuntiva.

Refactoring Capitolo 3: Aggiungere Memoria alla Tua Chat CLI

Prendiamo la chat CLI in streaming del Capitolo 3 e aggiungiamo la memoria della conversazione. Ecco la versione originale senza stato:

python
# chapter3_cli.py (originale - senza stato)
from langchain_openai import ChatOpenAI
from langchain_core.messages import HumanMessage
 
llm = ChatOpenAI(model="gpt-4o-mini")
 
def chat_loop():
    print("Chat avviata. Digita 'quit' per uscire.\n")
    while True:
        user_input = input("Tu: ")
        if user_input.lower() == "quit":
            break
        
        # Senza stato - nessuna cronologia
        response = llm.stream([HumanMessage(content=user_input)])
        print("AI: ", end="", flush=True)
        for chunk in response:
            print(chunk.content, end="", flush=True)
        print("\n")
 
if __name__ == "__main__":
    chat_loop()

Versione refactorizzata con memoria:

python
# chapter8_cli.py (con memoria)
from langchain_openai import ChatOpenAI
from langchain_core.chat_history import InMemoryChatMessageHistory
from langchain_core.messages import SystemMessage, HumanMessage, AIMessage
 
llm = ChatOpenAI(model="gpt-4o-mini")
 
def chat_loop():
    print("Chat avviata. Digita 'quit' per uscire.\n")
    
    # Crea la cronologia e aggiungi il messaggio di sistema
    history = InMemoryChatMessageHistory()
    history.add_message(SystemMessage(content="Sei un assistente utile."))
    
    while True:
        user_input = input("Tu: ")
        if user_input.lower() == "quit":
            break
        
        # Aggiungi il messaggio utente alla cronologia
        history.add_message(HumanMessage(content=user_input))
        
        # Streaming della risposta
        print("AI: ", end="", flush=True)
        full_response = ""
        for chunk in llm.stream(history.messages):
            print(chunk.content, end="", flush=True)
            full_response += chunk.content
        print("\n")
        
        # Aggiungi la risposta AI alla cronologia
        history.add_message(AIMessage(content=full_response))
 
if __name__ == "__main__":
    chat_loop()

Cosa è cambiato:

  1. Aggiunto archivio cronologia: Creata istanza InMemoryChatMessageHistory() dentro chat_loop()
  2. Messaggio di sistema: Aggiunto alla cronologia una volta all'inizio
  3. Invia input utente con cronologia: llm.stream(history.messages) include la conversazione precedente
  4. Traccia conversazione: Aggiungi sia l'input utente che la risposta LLM alla cronologia

Test della chat refactorizzata:

Chat avviata. Digita 'quit' per uscire.
 
Tu: Mi chiamo Alice
AI: Piacere di conoscerti, Alice! Come posso aiutarti oggi?
 
Tu: Come mi chiamo?
AI: Il tuo nome è Alice.
 
Tu: Cosa ti ho appena chiesto?
AI: Mi hai chiesto come ti chiami.
 
Tu: quit

Il modello ora mantiene il contesto durante l'intera conversazione. Ricorda il tuo nome, le domande precedenti e può fare riferimento a parti precedenti del dialogo.

Archiviazione Persistente: Oltre le Opzioni In-Memory

InMemoryChatMessageHistory è conveniente per lo sviluppo locale, ma passare alla produzione richiede di sostituirlo con una soluzione di archiviazione che garantisce la persistenza.

Limitazioni Tecniche di InMemory:

  • RAM Volatile: Quando il processo del server termina o si riavvia, tutta la cronologia della conversazione memorizzata in memoria viene immediatamente eliminata. Aggiornamenti o recupero da errori risultano in perdita completa del contesto utente.
  • Nessuno scaling orizzontale: Man mano che il tuo servizio scala a più istanze server, ogni server mantiene la propria memoria isolata. Gli utenti che si connettono a server diversi non possono condividere la cronologia della conversazione.
  • Inefficienza delle Risorse: Memorizzare tutta la cronologia della conversazione in RAM è intensivo in termini di memoria e minaccia la stabilità del sistema man mano che gli utenti concorrenti aumentano.

Alternative Professionali:

  • PostgresChatMessageHistory (Consigliato): La scelta più robusta e ampiamente adottata. Usa PostgreSQL per l'archiviazione permanente ed eccelle in query complesse e analisi dei dati.
  • SQLChatMessageHistory: Sfrutta database SQL esistenti come MySQL. Ti consente di usare la tua infrastruttura attuale senza modifiche.
  • RedisChatMessageHistory: Ideale per servizi dove la velocità di risposta è critica. Basato su memoria con opzioni di persistenza, specializzato per la gestione di traffico elevato.

"L'archiviazione cambia, il codice rimane lo stesso"

LangChain fornisce un'interfaccia unificata tra tutti i backend di archiviazione. Gli stessi metodi che hai usato con InMemoryChatMessageHistory—come add_message() e add_messages()—funzionano in modo identico con altre opzioni di archiviazione. Questo significa che la tua logica di business (codice di gestione della conversazione) non richiede modifiche quando si cambia archiviazione.

python
# [Sviluppo] In-memory locale
# from langchain_core.chat_history import InMemoryChatMessageHistory
# history = InMemoryChatMessageHistory()
 
# [Produzione] Archiviazione persistente PostgreSQL
import psycopg
from langchain_postgres import PostgresChatMessageHistory
 
# Crea connessione PostgreSQL
sync_connection = psycopg.connect(
    "postgresql://user:password@10.1.1.100:5432/agent_db",
    autocommit=True,
)
 
# Specifica connessione DB e ID sessione
# session_id: Chiave unica per identificare le conversazioni
#   - Gestione per utente: session_id = user_id (una conversazione per utente)
#   - Gestione per sessione: session_id = uuid (nuovo ID per ogni conversazione)
history = PostgresChatMessageHistory(
    table_name="chat_history",
    session_id="user_123",
    sync_connection=sync_connection
)
 
# --- Stessa interfaccia indipendentemente dal tipo di archiviazione ---
history.add_message(HumanMessage(content="Mostrami il mio ordine precedente."))
print(history.messages)

Importante: Questa guida usa InMemory per una progressione rapida, ma i deployment in produzione devono passare ad archiviazione persistente come PostgresChatMessageHistory.

8.3) Gestione della Lunghezza della Conversazione

La funzionalità di memoria della conversazione che abbiamo implementato nella sezione precedente ha un problema importante: aggiunge solo messaggi alla cronologia. Questo significa che la cronologia continua a crescere, il che crea due problemi principali:

  1. Costo: L'intera cronologia viene inviata con ogni richiesta, quindi il costo per richiesta continua ad aumentare man mano che la cronologia cresce
  2. Limiti della finestra di contesto: I modelli hanno una dimensione massima di input che possono elaborare in una singola richiesta (es., 400K token per GPT-5). Quando la cronologia della conversazione supera questo limite, il modello non può elaborare correttamente la richiesta.

Una delle soluzioni più semplici è il pattern della finestra scorrevole (sliding window).

Pattern della Finestra Scorrevole (Mantieni Solo gli Ultimi N Messaggi)

Il pattern della finestra scorrevole risolve i due problemi sopra mantenendo solo gli ultimi N messaggi più recenti nella cronologia della conversazione. Gestisce la dimensione della cronologia scartando i messaggi vecchi, fornendo i seguenti benefici:

  1. Controllo dei costi: Mantenendo la cronologia sotto una certa dimensione indipendentemente dalla lunghezza della conversazione, previene che i costi per richiesta crescano infinitamente
  2. Nessun overflow: Mantiene la dimensione dell'input entro il limite massimo del modello

Diagramma concettuale:

Messaggio 1

Messaggio 2

Messaggio 3

Messaggio 4

Messaggio 5

Messaggio 6

Dimensione Finestra = 4

Con una dimensione della finestra di 4, manteniamo solo i 4 messaggi più recenti (3, 4, 5, 6) e scartiamo i messaggi più vecchi (1, 2). Quando arriva un nuovo messaggio (7), la finestra si sposta verso il messaggio più recente, eliminando il messaggio più vecchio (3) e mantenendo i messaggi 4, 5, 6, 7.

Compromessi della Finestra Scorrevole:

  • Pro: Limita la dimensione della cronologia per mantenere i costi costanti e previene l'overflow della finestra di contesto
  • Contro: I messaggi oltre la dimensione della finestra vengono scartati, quindi il modello non può farvi riferimento

Questo compromesso può essere problematico. La soluzione è usare una finestra scorrevole per la conversazione recente mentre si recuperano le informazioni passate necessarie da un'archiviazione separata quando necessario. Questo può essere implementato usando RAG (Retrieval-Augmented Generation), che tratteremo nel Capitolo 9.

Unità di Dimensione della Finestra: Conteggio Messaggi vs Conteggio Token

Il diagramma sopra mostra un esempio di impostazione della dimensione della finestra per conteggio messaggi. Tuttavia, negli ambienti di produzione, il dimensionamento della finestra basato su token è più comunemente usato perché le dimensioni dei messaggi variano:

Trimming Basato su Conteggio Messaggi:

Limita la dimensione della finestra per conteggio messaggi (es., mantieni solo gli ultimi 20 messaggi).

  • Caratteristiche: Conteggio messaggi fisso, ma il conteggio totale dei token può ancora variare
  • Usa quando: Le dimensioni dei messaggi sono controllate (SMS, chat con limite di caratteri)
  • Rischio: Un messaggio lungo può ancora superare la finestra di contesto

Trimming Basato su Conteggio Token (Consigliato per la Produzione):

Limita la dimensione della finestra per conteggio token, l'unità di input elaborata dagli LLM (es., mantieni solo gli ultimi 5.000 token).

  • Caratteristiche: Non supera mai la finestra di contesto indipendentemente dalla lunghezza del messaggio
  • Usa quando: Le dimensioni dei messaggi variano

Implementare il Trimming Basato su Token: trim_messages()

LangChain fornisce un'utilità trim_messages() che implementa il pattern della finestra scorrevole basato su token.

Come funziona trim_messages():

Questa funzione prende la lista completa dei messaggi e il conteggio massimo dei token, restituendo solo i messaggi più recenti che rientrano nel limite di token.

Parametri chiave:

  • messages: Lista di messaggi da ridurre
  • max_tokens: Conteggio massimo di token da mantenere
  • token_counter: Funzione per calcolare il conteggio dei token per ogni messaggio (usa il tokenizer del modello per restituire il conteggio dei token del messaggio)
  • include_system: Se mantenere sempre SystemMessage (di solito True)

Configurazione del contatore di token:

python
import tiktoken
from langchain_core.messages import BaseMessage, HumanMessage, AIMessage, SystemMessage
from langchain_core.messages.utils import trim_messages
 
# Ottieni il tokenizer per il tuo modello (diverse famiglie di modelli usano tokenizer diversi)
# Fornisci il nome del modello per ottenere il tokenizer appropriato
# Modelli Claude: Usa il tokenizer di Anthropic (tiktoken è specifico per OpenAI)
enc = tiktoken.encoding_for_model("gpt-4o")
 
def token_counter(msg: BaseMessage) -> int:
    """Conta i token in un singolo messaggio."""
    return len(enc.encode(msg.content or ""))
 
# Crea cronologia della conversazione
messages = [
    SystemMessage(content="Sei un assistente utile."),
    HumanMessage(content="Ciao!"),
    AIMessage(content="Ciao! Come posso aiutarti?"),
    HumanMessage(content="Quanto fa 2+2?"),
    AIMessage(content="2+2 fa 4."),
    HumanMessage(content="Quanto fa 3+3?"),
    AIMessage(content="3+3 fa 6."),
    HumanMessage(content="Quanto fa 4+4?"),
]
 
# Mantieni solo i messaggi entro il conteggio massimo di token
trimmed = trim_messages(
    messages,
    max_tokens=30,
    token_counter=token_counter,
    include_system=True,
)
 
print(f"Originale: {len(messages)} messaggi")
print(f"Ridotto: {len(trimmed)} messaggi")
for m in trimmed:
    print(f"{type(m).__name__}: {m.content}")

Nota: Il numero di messaggi mantenuti dipende dal valore max_tokens e dal conteggio effettivo dei token di ogni messaggio. Nell'esempio sopra, max_tokens=30 è un valore molto piccolo scelto per scopi di test. In produzione, dovresti impostare un valore appropriato considerando la dimensione media dei messaggi e l'intervallo di conversazione desiderato.

Esempio: Applicare il Trimming alla Funzione di Chat

python
import tiktoken
from langchain_openai import ChatOpenAI
from langchain_core.chat_history import InMemoryChatMessageHistory
from langchain_core.messages import SystemMessage, HumanMessage, BaseMessage
from langchain_core.messages.utils import trim_messages
 
llm = ChatOpenAI(model="gpt-4o-mini")
history = InMemoryChatMessageHistory()
enc = tiktoken.encoding_for_model("gpt-4o-mini")
 
def token_counter(msg: BaseMessage) -> int:
    """Conta i token in un singolo messaggio."""
    return len(enc.encode(msg.content or ""))
 
def chat_with_trimming(user_input: str, max_tokens: int = 1000) -> str:
    """Chat con trimming automatico della cronologia."""
    history.add_message(HumanMessage(content=user_input))
    
    system_msg = SystemMessage(content="Sei un assistente utile.")
    all_messages = [system_msg] + history.messages
    
    # Riduci al conteggio massimo di token
    trimmed_messages = trim_messages(
        all_messages,
        max_tokens=max_tokens,
        token_counter=token_counter,
        include_system=True,
    )
    
    response = llm.invoke(trimmed_messages)
    history.add_message(response)
    
    return response.content
 
# Esempio di utilizzo
print(chat_with_trimming("Sto pianificando un viaggio in Giappone"))
print(chat_with_trimming("Cosa dovrei visitare a Tokyo?"))
print(chat_with_trimming("Quanti giorni dovrei passarci?"))
# Anche se la cronologia cresce, solo la conversazione recente entro il conteggio massimo di token viene inviata all'LLM

Prossimo Passo: Limitazioni della Gestione dello Stato della Conversazione e Soluzioni (Capitolo 9: RAG)

Fornire la cronologia della conversazione al modello aiuta a mantenere il contesto della conversazione. Tuttavia, questo da solo non è sufficiente in alcuni casi. Per esempio:

  • Quando devi trovare informazioni in documenti aziendali o manuali
  • Quando devi fare riferimento a vecchie cronologie di conversazione che sono state spinte fuori dalla finestra scorrevole

Qui è dove è necessario RAG (Retrieval-Augmented Generation). RAG funziona come segue:

  1. Archiviazione: Memorizza informazioni in un database vettoriale per la ricerca semantica
  2. Recupero: Interroga informazioni con significato simile a ciò che stai cercando

Se usi RAG per complementare le limitazioni del pattern della finestra scorrevole:

  • Finestra scorrevole: Mantieni gli ultimi 20 messaggi (contesto recente)
  • RAG: Cerca e recupera contenuto rilevante dai messaggi spinti fuori dalla finestra

Piuttosto che ricordare solo la conversazione recente, RAG ti consente di creare un sistema di memoria a lungo termine.

RAG consente agli agenti di sfruttare conoscenze più ampie e contesto più lungo attraverso l'utilizzo di conoscenze esterne e il recupero di conversazioni passate.

Il Capitolo 9 tratterà come implementare RAG in dettaglio.


Riepilogo del Capitolo:

In questo capitolo, hai imparato:

  1. Perché gli LLM dimenticano: I modelli sono stateless—la memoria è un'illusione creata re-inviando la cronologia della conversazione
  2. Tipi di messaggio: SystemMessage (istruzioni), HumanMessage (input utente), AIMessage (risposte del modello)
  3. Gestione dello stato: Gestire la cronologia della conversazione con InMemoryChatMessageHistory
  4. Gestione della lunghezza della conversazione: Perché la cronologia illimitata causa problemi di costo e finestra di contesto
  5. Finestre scorrevoli: Un pattern che mantiene solo i messaggi recenti per prevenire che la cronologia cresca infinitamente
  6. Trimming basato su token: Implementare finestre scorrevoli usando trim_messages() e conteggio dei token