Memoria e sessioni
Questa guida tratta del modello di trascrizione di Goa-AI, della persistenza della memoria e di come modellare conversazioni a più turni e flussi di lavoro di lunga durata.
Perché le trascrizioni sono importanti
Goa-AI considera la trascrizione come l’unica fonte di verità per un’esecuzione: una sequenza ordinata di messaggi e interazioni con gli strumenti sufficiente per:
- Ricostruire i payload del provider (Bedrock/OpenAI) per ogni chiamata al modello
- Guidare i pianificatori (compresi i tentativi e la riparazione degli strumenti)
- Alimentare le interfacce utente con uno storico accurato
Poiché la trascrizione è autorevole, non è necessario gestirla a mano:
- Elenchi separati di chiamate precedenti allo strumento e di risultati dello strumento
- Strutture di “stato di conversazione” ad hoc
- Copie per turno dei messaggi precedenti dell’utente/assistente
Si persiste e si passa solo la trascrizione; Goa-AI e i suoi adattatori di provider ricostruiscono tutto ciò di cui hanno bisogno.
Messaggi e parti
Al confine del modello, Goa-AI utilizza i valori model.Message per rappresentare la trascrizione. Ogni messaggio ha un ruolo (user, assistant) e un elenco ordinato di parti:
| Tipo di parte | Descrizione |
|---|---|
ThinkingPart | Contenuto del ragionamento del fornitore (testo in chiaro + firma o byte redatti). Non è rivolto all’utente; è usato per la verifica/riproduzione e per le interfacce utente opzionali di “ragionamento”. |
TextPart | Testo visibile all’utente (domande, risposte, spiegazioni). |
ToolUsePart | Chiamata allo strumento avviata dall’assistente con ID, Name (ID strumento canonico) e Input (carico utile JSON). |
ToolResultPart | Risultato dell’utente/strumento correlato a un uso precedente dello strumento tramite ToolUseID e Content (payload JSON). |
**L’ordine è sacro
- Il messaggio di un assistente che utilizza uno strumento è tipicamente simile a:
ThinkingPart, poi uno o piùToolUsePart, poiTextPartopzionale - Un messaggio di risultato utente/strumento contiene tipicamente uno o più
ToolResultPartche fanno riferimento a precedenti ID di utilizzo dello strumento, oltre a un testo opzionale dell’utente
Gli adattatori dei provider di Goa-AI (ad esempio, Bedrock Converse) ricodificano queste parti in blocchi specifici del provider senza riordino.
Il contratto di trascrizione
Il contratto di trascrizione di alto livello in Goa-AI è:
- L’applicazione (o il runtime) presenta ogni evento per un’esecuzione in ordine: pensiero dell’assistente, testo, tool_use (ID + args), tool_result dell’utente (tool_use_id + content), messaggi successivi dell’assistente e così via
- Prima di ogni chiamata al modello, il chiamante fornisce l’intera trascrizione** di quella sessione come
[]*model.Message, con l’ultimo elemento che è il nuovo delta (testo dell’utente o risultato dello strumento) - Goa-AI ricodifica la trascrizione nel formato di chat del provider nello stesso ordine
Non esiste un’API separata per la “cronologia degli strumenti”; la trascrizione è la cronologia.
Come questo semplifica i pianificatori e le interfacce utente
- Pianificatori: Ricevono la trascrizione corrente in
planner.PlanInput.Messageseplanner.PlanResumeInput.Messages. Possono decidere cosa fare basandosi esclusivamente sui messaggi, senza dover ricorrere a uno stato aggiuntivo. - UI: Possono rendere la cronologia della chat, i nastri degli strumenti e le schede degli agenti dalla stessa trascrizione sottostante che persiste per il modello. Non sono necessarie strutture separate di “log degli strumenti”.
- adattatori Provider: Non indovinano mai quali strumenti sono stati chiamati o quali risultati appartengono a un determinato punto; mappano semplicemente le parti della trascrizione → i blocchi dei provider.
Registro di trascrizione
Il transcript ledger è un record preciso del fornitore che mantiene la cronologia delle conversazioni nel formato esatto richiesto dai fornitori di modelli. Assicura un replay deterministico e la fedeltà del fornitore senza far trapelare i tipi di SDK del fornitore nello stato del flusso di lavoro.
Fedeltà del fornitore
I diversi fornitori di modelli (Bedrock, OpenAI, ecc.) hanno requisiti rigorosi per quanto riguarda l’ordine e la struttura dei messaggi. Il libro mastro fa rispettare questi vincoli:
| Requisiti del fornitore | Garanzia del libro mastro |
|---|---|
| Il pensiero deve precedere l’uso dello strumento nei messaggi degli assistenti | Il ledger ordina le parti: pensiero → testo → uso dello strumento |
| I risultati dell’utensile devono seguire il corrispondente utilizzo dell’utensile | Il libro mastro correla il risultato dell’utensile tramite ToolUseID |
| Alternanza di messaggi (assistente → utente → assistente) | Ledger lava l’assistente prima di aggiungere i risultati dell’utente |
Per Bedrock in particolare, quando il pensiero è abilitato:
- I messaggi dell’assistente contenenti tool_use devono iniziare con un blocco di riflessione
- I messaggi utente con tool_result devono seguire immediatamente il messaggio assistente che dichiara il tool_use
- Il numero di risultati dello strumento non può superare il numero di utilizzi dello strumento precedente
Requisiti di ordinamento
Il libro mastro memorizza i pezzi nell’ordine canonico richiesto dai fornitori:
Assistant Message:
1. ThinkingPart(s) - provider reasoning (text + signature or redacted bytes)
2. TextPart(s) - visible assistant text
3. ToolUsePart(s) - tool invocations (ID, name, args)
User Message:
1. ToolResultPart(s) - tool results correlated via ToolUseID
Questo ordine è sacro: il libro mastro non riordina mai le parti e gli adattatori dei provider le ricodificano in blocchi specifici del provider nella stessa sequenza.
Manutenzione automatica del libro mastro
Il runtime mantiene automaticamente il registro delle trascrizioni. Non è necessario gestirlo manualmente:
Cattura eventi: Durante l’avanzamento della corsa, il runtime conserva gli eventi di memoria (
EventThinking,EventAssistantMessage,EventToolCall,EventToolResult) in modo daRicostruzione del registro: La funzione
BuildMessagesFromEventsricostruisce i messaggi pronti per il provider a partire dagli eventi memorizzati:
// Reconstruct messages from persisted events
events := loadEventsFromStore(agentID, runID)
messages := transcript.BuildMessagesFromEvents(events)
// Messages are now in canonical provider order
// Ready to pass to model.Client.Complete() or Stream()
- Validazione: Prima dell’invio ai provider, il runtime può convalidare la struttura del messaggio:
// Validate Bedrock constraints when thinking is enabled
if err := transcript.ValidateBedrock(messages, thinkingEnabled); err != nil {
// Handle constraint violation
}
API del libro mastro
Per casi d’uso avanzati, è possibile interagire direttamente con il libro mastro. Il libro mastro fornisce questi metodi chiave:
| Metodo | Descrizione |
|---|---|
NewLedger() | Crea un nuovo libro mastro vuoto |
AppendThinking(part) | Aggiunge una parte pensante al messaggio dell’assistente corrente |
AppendText(text) | Aggiunge un testo visibile al messaggio dell’assistente corrente |
DeclareToolUse(id, name, args) | Dichiara l’invocazione di uno strumento nel messaggio assistente corrente |
FlushAssistant() | Finalizza il messaggio assistente corrente e si prepara per l’input dell’utente |
AppendUserToolResults(results) | Applica i risultati dello strumento come messaggio utente |
BuildMessages() | Restituisce la trascrizione completa come []*model.Message |
Esempio di utilizzo:
import "goa.design/goa-ai/runtime/agent/transcript"
// Create a new ledger
l := transcript.NewLedger()
// Record assistant turn
l.AppendThinking(transcript.ThinkingPart{
Text: "Let me search for that...",
Signature: "provider-sig",
Index: 0,
Final: true,
})
l.AppendText("I'll search the database.")
l.DeclareToolUse("tu-1", "search_db", map[string]any{"query": "status"})
l.FlushAssistant()
// Record user tool results
l.AppendUserToolResults([]transcript.ToolResultSpec{{
ToolUseID: "tu-1",
Content: map[string]any{"results": []string{"item1", "item2"}},
IsError: false,
}})
// Build provider-ready messages
messages := l.BuildMessages()
Nota: La maggior parte degli utenti non ha bisogno di interagire direttamente con il libro mastro. Il runtime mantiene automaticamente il libro mastro attraverso la cattura e la ricostruzione degli eventi. Utilizzare l’API del libro mastro solo per scenari avanzati, come pianificatori personalizzati o strumenti di debug.
Perché è importante
- Riproduzione deterministica: Gli eventi memorizzati possono ricostruire l’esatta trascrizione per il debugging, l’auditing o la ripetizione di turni falliti
- Magazzino agnostico dei fornitori: Il libro mastro memorizza parti JSON-friendly senza dipendenze dall’SDK del provider
- Piani semplificati: I pianificatori ricevono messaggi ordinati correttamente senza gestire i vincoli dei provider
- Validazione: Cattura le violazioni dell’ordine prima che raggiungano il provider e causino errori criptici
Sessioni, corse e trascrizioni
Goa-AI separa lo stato della conversazione in tre livelli:
Sessione (
SessionID) - una conversazione o un flusso di lavoro nel tempo:- ad esempio, una sessione di chat, un ticket di riparazione, un’attività di ricerca
- Più esecuzioni possono appartenere alla stessa sessione
Esecuzione (
RunID) - un’esecuzione di un agente:- Ogni chiamata a un client agente (
Run/Start) crea un’esecuzione - Le esecuzioni hanno stato, fasi ed etichette
- Ogni chiamata a un client agente (
Transcript - la cronologia completa dei messaggi e delle interazioni con gli strumenti per un’esecuzione:
- Rappresentata come
[]*model.Message - Persistito tramite
memory.Storecome eventi ordinati in memoria
- Rappresentata come
SessionID e TurnID in pratica
Quando si chiama un agente:
client := chat.NewClient(rt)
out, err := client.Run(ctx, "chat-session-123", messages,
runtime.WithTurnID("turn-1"), // optional but recommended for chat
)
SessionID: Raggruppa tutte le corse per una conversazione; spesso viene utilizzato come chiave di ricerca negli archivi delle corse e nei dashboardTurnID: Raggruppa gli eventi per un singolo utente → interazione con l’assistente; opzionale ma utile per le interfacce utente e i log
Memorizzazione della memoria vs. memorizzazione dell’esecuzione
I moduli funzionali di Goa-AI forniscono memorie complementari:
Memory Store (memory.Store)
Conserva la cronologia degli eventi per ogni esecuzione:
- Messaggi dell’utente/assistente
- Chiamate allo strumento e risultati
- Note e pensieri del pianificatore
type Store interface {
LoadRun(ctx context.Context, agentID, runID string) (memory.Snapshot, error)
AppendEvents(ctx context.Context, agentID, runID string, events ...memory.Event) error
}
Tipi chiave:
memory.Snapshot- vista immutabile della cronologia memorizzata di una corsa (AgentID,RunID,Events []memory.Event)memory.Event- singola voce persistente conType(user_message,assistant_message,tool_call,tool_result,planner_note),thinking,Timestamp,DataeLabels
Esegui negozio (run.Store)
Conserva i metadati dell’esecuzione a grana grossa:
RunID,AgentID,SessionID,TurnID- Stato, timestamp, etichette
type Store interface {
Upsert(ctx context.Context, record run.Record) error
Load(ctx context.Context, runID string) (run.Record, error)
}
run.Record catture:
AgentID,RunID,SessionID,TurnIDStatus(pending,running,completed,failed,canceled,paused)StartedAt,UpdatedAtLabels(inquilino, priorità, ecc.)
Cablaggio Negozi
Con le implementazioni supportate da MongoDB:
import (
memorymongo "goa.design/goa-ai/features/memory/mongo"
runmongo "goa.design/goa-ai/features/run/mongo"
"goa.design/goa-ai/runtime/agent/runtime"
)
mongoClient := newMongoClient()
memStore, err := memorymongo.NewStore(memorymongo.Options{Client: mongoClient})
if err != nil {
log.Fatal(err)
}
runStore, err := runmongo.NewStore(runmongo.Options{Client: mongoClient})
if err != nil {
log.Fatal(err)
}
rt := runtime.New(
runtime.WithMemoryStore(memStore),
runtime.WithRunStore(runStore),
)
Una volta configurati:
- I sottoscrittori predefiniti persistono in memoria ed eseguono automaticamente i metadati
- È possibile ricostruire le trascrizioni da
memory.Storein qualsiasi momento per richiamare i modelli, alimentare le UI o eseguire analisi offline
Archivi personalizzati
Implementare le interfacce memory.Store e run.Store per i backend personalizzati:
// Memory store
type Store interface {
LoadRun(ctx context.Context, agentID, runID string) (memory.Snapshot, error)
AppendEvents(ctx context.Context, agentID, runID string, events ...memory.Event) error
}
// Run store
type Store interface {
Upsert(ctx context.Context, record run.Record) error
Load(ctx context.Context, runID string) (run.Record, error)
}
Modelli comuni
Sessioni di chat
- Utilizzare un
SessionIDper sessione di chat - Avviare una nuova sessione per turno dell’utente o per “attività”
- Persistere le trascrizioni per ogni sessione; utilizzare i metadati della sessione per ricucire la conversazione
Flussi di lavoro di lunga durata
- Utilizzare una singola sessione per flusso di lavoro logico (potenzialmente con pausa/ripresa)
- Usare
SessionIDper raggruppare flussi di lavoro correlati (ad esempio, per ticket o incidente) - Affidarsi agli eventi
run.PhaseeRunCompletedper il monitoraggio dello stato
Ricerca e cruscotti
- Interrogazione di
run.StoreperSessionID, etichette, stato - Caricamento delle trascrizioni da
memory.Storesu richiesta per le corse selezionate
Migliori pratiche
Correlare sempre i risultati degli strumenti: Assicurarsi che le implementazioni degli strumenti e i pianificatori conservino gli ID tool_use e mappino i risultati degli strumenti al corretto
ToolUseParttramiteToolResultPart.ToolUseIDUtilizzare schemi forti e descrittivi: Tipi, descrizioni ed esempi ricchi di
Args/Returnnella progettazione di Goa producono carichi utili/risultati più chiari nella trascrizioneLasciare che sia il runtime a gestire lo stato: Evitare di mantenere array paralleli di “cronologia degli strumenti” o fette di “messaggi precedenti” nel pianificatore. Leggere da
PlanInput.Messages/PlanResumeInput.Messagese affidarsi al runtime per aggiungere nuove partiPersistere le trascrizioni una volta, riutilizzarle ovunque: Qualunque sia lo store scelto, trattate la trascrizione come un’infrastruttura riutilizzabile: la stessa trascrizione supporta le chiamate al modello, l’interfaccia della chat, l’interfaccia di debug e l’analisi offline
Indicizzare i campi interrogati di frequente: ID sessione, ID esecuzione, stato per query efficienti
Archiviare le vecchie trascrizioni: Ridurre i costi di archiviazione archiviando le sessioni completate
Prossimi passi
- Produzione - Distribuzione con Temporal, UI in streaming e integrazione del modello
- Runtime - Comprendere il ciclo piano/esecuzione
- Composizione di agenti - Costruire grafi di agenti complessi