Vai al contenuto principaleVai al footer
PatternscriptintermediateEseguibileguided-flow

Episodic and semantic memory: remember turns, and remember facts

Two stores answering different questions. Episodic keeps what was said and when; semantic keeps extracted, deduplicated facts about the user. Retrieval defaults to facts, and reaches for transcripts only when the question is genuinely about what happened.

Fatti chiave

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

Naviga questo esempio

Vista rapida del flusso

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

Episodic and semantic memory: remember… -> Store reusable memory -> Load environment keys -> Initialize OpenAI client -> Send chat request -> Constrain output schema

Trigger

Episodic and semantic memory: remember…

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à

    Semantic memory is a legible key-value store rather than an opaque embedding blob, so what the system believes about a user can be read, checked, and corrected item by item. A memory layer whose contents can only be inspected by asking the model what it remembers is not inspectable in any useful sense.

    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

    Retrieved memory is injected silently into the prompt, so an answer shaped by something said weeks ago looks identical to one shaped by the current turn. Fix: state which remembered facts informed an answer, at a level the reader can act on, so a wrong or stale fact is correctable at the point it does damage rather than after.

    episodic_semantic.py:answer

  • P5 · Sostituire la magia implicita con modelli mentali chiari

    The user is never told a fact was extracted and stored about them. From their side the assistant simply starts knowing things, which is the archetypal implied-magic moment and, once it involves personal detail, a trust problem rather than a delight. Fix: surface what was learned at the moment it is written, and make the fact store viewable and editable rather than only inferable from behaviour.

    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

    Whether the user can refuse, edit, or delete what was remembered is entirely a wrapper decision. The pattern writes to a store and reads from it; it takes no position on consent, retention, or erasure. In most jurisdictions that position is not optional, but it is not the pattern's to hold.

    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

Two stores answering different questions. Episodic keeps what was said and when; semantic keeps extracted, deduplicated facts about the user. Retrieval defaults to facts, and reaches for transcripts only when the question is genuinely about what happened.

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