Vai al contenuto principaleVai al footer
PatternscriptIntermedioEseguibileguided-flow

Memoria episodica e semantica: ricordare i turni e ricordare i fatti

Due archivi che rispondono a domande diverse. La memoria episodica conserva ciò che è stato detto e quando; quella semantica conserva fatti estratti e deduplicati sull’utente. Il recupero attinge per impostazione predefinita ai fatti e ricorre alle trascrizioni solo quando la domanda riguarda realmente ciò che è accaduto.

Fatti chiave

Livello
Intermedio
Runtime
Python • API OpenAI
Pattern
Flusso ispezionabile con confini di sistema visibili
Interazione
Sandbox live • Script
Aggiornato
28 luglio 2026

Naviga questo esempio

Vista rapida del flusso

Come questo esempio si muove tra input, esecuzione e risultato rivedibile

Memoria episodica e semantica: ricordare… -> Store reusable memory -> Load environment keys -> Initialize OpenAI client -> Send chat request -> Constrain output schema

Trigger

Memoria episodica e semantica: ricordare…

Runtime

Store reusable memory

Esito

Load environment keys

Perché esiste questa pagina

Questo esempio è mostrato sia come codice sorgente reale che come pattern di interazione orientato al prodotto, così i discenti possono collegare implementazione, UX e doctrine senza lasciare la libreria.

Flusso visivoCodice realeSandbox o walkthroughAccesso MCP

Relazioni con la doctrine

Queste sono forme di ragionamento: come un agente decide, quando agisce, cosa ricorda. Per comporre l'interfaccia intorno a esso, vedi l'AI Pattern Blueprint. Per trigger, pianificazione, caricamento del contesto e ripristino, vedi Agent runtime architecture.

Strutturale

Questo pattern esprime questi principi direttamente nel codice.

  • P7 · Stabilire fiducia attraverso l'ispezionabilità

    La memoria semantica è un archivio chiave-valore leggibile, anziché un blob di embedding opaco, quindi è possibile leggere, verificare e correggere elemento per elemento ciò che il sistema ritiene di sapere su un utente. Un livello di memoria i cui contenuti possono essere ispezionati solo chiedendo al modello che cosa ricorda non è ispezionabile in alcun senso utile.

    episodic_semantic.py:FACTS

Gap predefinito

La forma classica di questo pattern li viola per impostazione predefinita. Ognuno riporta la correzione.

  • P4 · Applicare la divulgazione progressiva all'agenzia del sistema

    La memoria recuperata viene inserita silenziosamente nel prompt, quindi una risposta plasmata da qualcosa detto settimane prima appare identica a una plasmata dal turno corrente. Correzione: indicare quali fatti ricordati hanno contribuito alla risposta, a un livello tale da consentire al lettore di intervenire, così che un fatto errato o obsoleto possa essere corretto nel punto in cui provoca il danno, anziché in un momento successivo.

    episodic_semantic.py:answer

  • P5 · Sostituire la magia implicita con modelli mentali chiari

    All’utente non viene mai comunicato che un fatto su di lui è stato estratto e memorizzato. Dal suo punto di vista, l’assistente semplicemente inizia a sapere delle cose, creando il tipico momento di magia implicita e, quando sono coinvolti dettagli personali, un problema di fiducia più che un motivo di soddisfazione. Correzione: rendere visibile ciò che è stato appreso nel momento in cui viene scritto e fare in modo che l’archivio dei fatti sia consultabile e modificabile, anziché soltanto desumibile dal comportamento.

    episodic_semantic.py:remember

Dipende dall'implementazione

Lo decide la tua implementazione, non il pattern. Nessun verdetto.

  • P8 · Rendere espliciti i passaggi, le approvazioni e i blocchi

    La possibilità per l’utente di rifiutare, modificare o eliminare ciò che è stato ricordato dipende interamente da una decisione del wrapper. Il pattern scrive in un archivio e vi legge; non assume alcuna posizione in materia di consenso, conservazione o cancellazione. Nella maggior parte delle giurisdizioni, questa posizione non è facoltativa, ma non spetta al pattern assumerla.

    episodic_semantic.py:remember

Le relazioni con la doctrine sono analisi di prima parte, non il risultato di una validazione. Esegui il validatore sul tuo codice per ottenere un verdetto con punteggio.

Come dovrebbe essere usato questo esempio nella piattaforma?

Usa prima la sandbox per comprendere il pattern di esperienza, poi ispeziona il sorgente per vedere come il confine del prodotto, il confine del modello e il confine della doctrine sono effettivamente implementati.

UX pattern: Flusso ispezionabile con confini di sistema visibili
Applicare la divulgazione progressiva all'agenzia del sistema
Sostituire la magia implicita con modelli mentali chiari
Stabilire fiducia attraverso l'ispezionabilità

Riferimenti sorgente

Voce di libreria
pattern-episodic-semantic
Percorso sorgente
content/patterns/memory/episodic-semantic/episodic_semantic.py
Librerie
openai, pydantic, python-dotenv
Requisiti di runtime
OPENAI_API_KEY
Principi correlati
Applicare la divulgazione progressiva all'agenzia del sistema, Sostituire la magia implicita con modelli mentali chiari, Stabilire fiducia attraverso l'ispezionabilità, Rendere espliciti i passaggi, le approvazioni e i blocchi

episodic_semantic.py

python
"""
Episodic and semantic memory: remember turns, and remember facts

Two stores, because they answer different questions.

  Episodic  what was said, when. Searchable transcript of past turns.
  Semantic  what is true. Extracted, deduplicated facts about the user.

    Turn -> store episode -> extract facts -> upsert semantic
    Query -> retrieve facts (+ episodes only if needed) -> answer

The failure this prevents is subtle. Loading whole past conversations into
context to "remember" the user is expensive and actively harmful: old
transcripts pull the model toward whatever was being discussed then, not what
is being asked now. Facts are small, current, and steer nothing.

So the default retrieval here is facts only. Episodes are fetched when the
question is genuinely about what happened rather than about what is true.

Run: python episodic_semantic.py
"""

from typing import Literal

from dotenv import load_dotenv
from openai import OpenAI
from pydantic import BaseModel, Field

load_dotenv()

client = OpenAI()

MODEL = "gpt-5.4-mini"

# --------------------------------------------------------------
# Two stores. In production these are a vector index and a table;
# the shape of the pattern is the same.
# --------------------------------------------------------------

EPISODES: list[dict[str, str]] = []
FACTS: dict[str, str] = {}


class ExtractedFact(BaseModel):
    key: str = Field(
        description="Stable snake_case identifier, e.g. 'preferred_language'."
    )
    value: str = Field(description="The fact itself, as short as possible.")


class Extraction(BaseModel):
    facts: list[ExtractedFact] = Field(
        description=(
            "Durable facts about the user or their situation. Empty when the "
            "turn contains nothing worth remembering. Most turns do not."
        )
    )


class Retrieval(BaseModel):
    needs_episodes: bool = Field(
        description=(
            "True only when the question is about what was previously said or "
            "done, rather than about what is currently true."
        )
    )
    why: str


def remember(user_text: str, assistant_text: str) -> list[ExtractedFact]:
    """Append the episode verbatim, then distil anything durable into facts."""
    EPISODES.append({"user": user_text, "assistant": assistant_text})

    completion = client.beta.chat.completions.parse(
        model=MODEL,
        messages=[
            {
                "role": "system",
                "content": (
                    "Extract durable facts about the user from this exchange. "
                    "A durable fact is still true next month. Preferences, "
                    "constraints, and stable context qualify. Questions asked, "
                    "one-off requests, and anything about the current task do "
                    "not. Return an empty list when nothing qualifies."
                ),
            },
            {"role": "user", "content": f"User: {user_text}\nAssistant: {assistant_text}"},
        ],
        response_format=Extraction,
    )
    extracted = completion.choices[0].message.parsed.facts
    for fact in extracted:
        # Upsert, so a changed preference replaces rather than accumulates.
        # Append-only fact stores end up holding both the old answer and the
        # new one, with nothing to say which is current.
        FACTS[fact.key] = fact.value
    return extracted


def decide_retrieval(question: str) -> Retrieval:
    completion = client.beta.chat.completions.parse(
        model=MODEL,
        messages=[
            {
                "role": "system",
                "content": (
                    "Decide whether answering needs the past transcript, or "
                    "whether the known facts are enough. Transcripts are "
                    "expensive and drag the answer toward old topics, so "
                    "prefer facts unless the question is genuinely about what "
                    "was said or done."
                ),
            },
            {"role": "user", "content": question},
        ],
        response_format=Retrieval,
    )
    return completion.choices[0].message.parsed


def answer(question: str) -> str:
    plan = decide_retrieval(question)
    print(f"  retrieval: facts{' + episodes' if plan.needs_episodes else ' only'}")
    print(f"      why  : {plan.why}")

    known = "\n".join(f"- {k}: {v}" for k, v in FACTS.items()) or "(nothing known)"
    context = f"Known facts:\n{known}"
    if plan.needs_episodes:
        transcript = "\n".join(
            f"User: {e['user']}\nAssistant: {e['assistant']}" for e in EPISODES
        )
        context += f"\n\nPast turns:\n{transcript}"

    completion = client.chat.completions.create(
        model=MODEL,
        messages=[
            {
                "role": "system",
                "content": (
                    "Answer using the context provided. If it does not contain "
                    "the answer, say you do not know rather than guessing."
                ),
            },
            {"role": "user", "content": f"{context}\n\nQuestion: {question}"},
        ],
    )
    return completion.choices[0].message.content or ""


if __name__ == "__main__":
    print("\n> seeding two turns")
    for user_text, assistant_text in [
        ("I run a small ceramics shop on Shopify.", "Noted, thanks."),
        ("Keep any tooling under 200 a month, that is my ceiling.", "Understood."),
    ]:
        new_facts = remember(user_text, assistant_text)
        print(f"  learned: {[(f.key, f.value) for f in new_facts] or 'nothing durable'}")

    for question in [
        "What platform am I on, and what is my budget?",
        "What exactly did I say when I mentioned the budget?",
    ]:
        print(f"\n> {question}")
        print(f"  answer: {answer(question)}")

Cosa dovrebbe ispezionare il discente nel codice?

Cerca il punto esatto in cui lo scope del sistema è delimitato: definizioni di schema, impostazione del prompt, configurazione di runtime e il punto di chiamata che trasforma l'intenzione dell'utente in un'azione concreta del modello o del workflow.

Cerca i contratti di output e la validazione
Cerca la chiamata di esecuzione esatta
Cerca cosa il prodotto potrebbe esporre all'utente

Come si relaziona la sandbox al sorgente?

La sandbox dovrebbe rendere leggibile l'UX: cosa vede l'utente, cosa sta decidendo il sistema e come il risultato diventa revisionabile. Il sorgente mostra poi come quel comportamento è effettivamente implementato.

Leggi il riepilogo dell'implementazione.
Esplora gli stati utente e di sistema.
Ispeziona il codice sorgente tenendo a mente le decisioni di doctrine evidenziate.
SandboxFlusso ispezionabile con confini di sistema visibili

Guida all'interazione

Usa la sandbox per esplorare l'esperienza visibile all'utente, il lavoro del sistema e la scelta di doctrine che l'esempio sta facendo.

Spiegazione UX

La sandbox spiega cosa dovrebbe vedere l'utente, cosa sta facendo il sistema e dove il controllo o l'ispezionabilità devono rimanere espliciti.

Spiegazione AI Design

La pagina trasforma il codice sorgente in un pattern orientato al prodotto: cosa può decidere il modello, cosa dovrebbe esporre il prodotto e dove il codice deterministico o la revisione devono subentrare.

Guida all'interazione

  1. 1Leggi il riepilogo dell'implementazione.
  2. 2Esplora gli stati utente e di sistema.
  3. 3Ispeziona il codice sorgente tenendo a mente le decisioni di doctrine evidenziate.

Visibile all'utente

Due archivi che rispondono a domande diverse. La memoria episodica conserva ciò che è stato detto e quando; quella semantica conserva fatti estratti e deduplicati sull’utente. Il recupero attinge per impostazione predefinita ai fatti e ricorre alle trascrizioni solo quando la domanda riguarda realmente ciò che è accaduto.

Lavoro del sistema

Il prodotto prepara un task delimitato per il modello o il workflow.

Perché è importante

L'interfaccia dovrebbe rendere il task delegato leggibile prima che avvenga l'automazione.

Usato in corsi e percorsi

Questo esempio attualmente è indipendente nella libreria, ma si connette comunque al sistema dei principi e alla famiglia di esempi più ampia.

Principi correlati

Runtime architecture

Usa questo esempio nei tuoi agenti

Questo esempio è disponibile anche tramite il layer agent-ready del blueprint. Usa la pagina Per agenti per recuperare MCP pubblico, export deterministici e setup per Claude o Cursor.

Definisci trigger, contesto e confini prima di aumentare l'autonomia
Rendi espliciti controllo, osservabilita e recovery nel runtime
Scegli i pattern operativi giusti prima di delegare ai workflow