Guida · Sophia KB Console

Indice ← Torna alla console

La console è la porta umano→KB di Sophia. La guida è in due parti — le trovi come voci del menù a sinistra (apri una voce per vederne le sezioni): ① Interfaccia, come si usa ogni vista; e ② Sotto il cofano, cosa succede tecnicamente quando scrivi nella KB (la pipeline dell’add e i prompt che Graphiti passa all’LLM).

① Interfaccia come si usa la console

Cos’è la console #

Uno strumento di validazione della knowledge base: non nasconde cosa succede, lo mostra. Ogni risposta della chat porta in testa la traccia di cosa ha capito l’interprete e cosa è stato passato al motore del grafo (graphiti).

La console ha tre schede in alto, più questa guida:

SchedaA cosa serve
Chat defaultFai domande, esplora, gestisci i workspace — tutto in linguaggio naturale. È il cuore della console.
AddIncolli un testo, vedi in anteprima cosa verrebbe estratto, e solo allora confermi la scrittura.
OntologiaVista strutturale ad albero (Ambito → Tipo → Attributo) per i workspace-schema.
Nota La console è statica: un solo file HTML, zero dipendenze. Parla al GraphQL della KB nella stessa origine (/graphql), quindi niente CORS né tunnel.

Accesso e token #

L’accesso è a token. Alla prima apertura incolli il tuo KB token e premi Entra: viene validato subito (interrogando i workspace autorizzati, operazione veloce che non impegna l’LLM).

  • Il token resta nel localStorage del browser: le volte successive entri diretto, il campo sparisce.
  • Esci (in alto a destra) rimuove il token e torna alla schermata di login.
  • Se il token scade o è errato, l’app torna al login segnalando l’errore.
Come funziona l’auth L’autorizzazione è verificata dai resolver dell’API: gli errori di token/ruolo tornano nel corpo della risposta (HTTP 200), non come 401. La console li mostra in chiaro.

Workspace e ruoli #

Il workspace è il grafo su cui stai lavorando. Lo scegli dal selettore in alto: sono elencati solo quelli su cui sei autorizzato, ciascuno col tuo ruolo (es. bottega_common · admin).

Cambiando workspace riparte tutto: la conversazione, il Feed e l’eventuale vista Ontologia si ricaricano, perché sono per-workspace.

RuoloPuoi
readerLeggere: interrogare, esplorare, ispezionare.
writerLeggere + scrivere e curare (i pulsanti di cura compaiono).
adminTutto, inclusa la gestione dei workspace (crea / elimina / azzera).

I workspace col prefisso _ (e default_db) sono di sistema: protetti, non eliminabili né azzerabili.

Stato della KB (il badge) #

In alto a destra un badge riassume lo stato dei servizi:

  • KB ok · LLM ✓ — grafo (FalkorDB) e gateway LLM raggiungibili.
  • FalkorDB ✓ · LLM ✗ — parziale: un componente non risponde (le domande che usano l’LLM falliranno).
  • KB: errore — la KB non risponde; passa il mouse per il dettaglio.

Il controllo è asincrono e non blocca il login.

Concetti della KB #

Per usare bene la console conviene tenere a mente pochi concetti del grafo:

TermineSignificato
EpisodioUn pezzo di testo immesso nella KB. È la fonte: da esso graphiti estrae entità e fatti.
EntitàUna «cosa» del mondo (persona, organizzazione, concetto…), con nome, tipo (label) e un summary.
FattoUna relazione fra due entità, valida in un periodo di tempo. È l’unità che la chat cita.
Fatto corrente vs superatoUn fatto ha un valid_at; se viene invalid_at è superato — resta nella Storia (in grigio) ma esce dalle viste correnti.
ProvenanceIl legame fatto → episodio(i) che lo hanno prodotto. In console è il link → episodio.
Principio La KB non giudica la semantica: registra fatti ed evidenze. Il senso lo dà chi legge e cura — per questo la console espone provenienza, storia e strumenti di cura.

La chat e l’interprete #

La scheda Chat è a tre colonne:

  • Feed Attività (sinistra) — recenti e ricerca del workspace.
  • Thread (centro) — la conversazione; i dettagli (schede, episodi) compaiono qui come messaggi.
  • Entità coinvolte (destra) — le entità dei fatti citati nell’ultima risposta.

Ogni messaggio che scrivi passa prima dall’interprete: una singola passata LLM che decide l’atto (cosa vuoi fare) e l’eventuale entità bersaglio. In base all’atto la chat risponde direttamente oppure evoca una vista.

Default sicuro Se l’interprete è incerto o non disponibile, la chat ricade su ask (una risposta fondata sui fatti): non ti lascia mai a bocca asciutta.

I sei atti #

L’interprete classifica ogni frase in uno di questi atti:

AttoEsempioCosa fa la console
Interrogare ask«Chi ha fondato X?»Risposta fondata sui fatti, con citazioni [n] → Fonti.
Esplorare · cerca«cerca X»Cerca fra le entità e apre/propone la scheda.
Esplorare · vicinato«cosa c’è intorno a X?»Apre la scheda entità sulla sezione Vicinato.
Esplorare · storia«la storia di X»Apre la scheda entità sulla sezione Storia.
Gestire workspace ws_*«elenca i workspace», «crea il workspace demo»Lista / seleziona / crea / elimina / azzera un workspace.
Curare(azioni dirette)La cura avviene coi pulsanti dove vivono gli oggetti — vedi Curare la KB.

Interrogare (ask) #

È il livello «indiretto»: fai una domanda in linguaggio naturale e ricevi una risposta scritta dall’LLM ma fondata sui fatti del workspace, ciascuno citato.

  1. La console cerca i fatti pertinenti nel grafo (ricerca ibrida bm25 + semantica).
  2. Passa domanda + fatti numerati all’LLM, che risponde citando [n].
  3. Sotto la risposta trovi le Fonti: ogni fatto con la sua validità e il link → episodio alla provenienza.

A destra, Entità coinvolte elenca le entità di quei fatti: cliccabili per aprirne la scheda.

Onestà Solo fatti correnti possono essere citati (i superati appartengono alla Storia). Se non ci sono fatti a sostegno, la risposta lo dice.

Esplorare #

L’atto esplorare evoca la scheda Entità. La console prima cerca l’entità bersaglio fra i nodi del grafo (ricerca ibrida, niente LLM):

  • 1 risultato → apre la scheda direttamente (sulla sezione giusta: vicinato o storia).
  • più risultati → li elenca in chat e ti fa scegliere quale.
  • 0 risultati → risposta onesta: «non trovo X fra le entità di questo workspace».

Gestire i workspace dalla chat #

Alcuni atti ws_* ti fanno amministrare i workspace scrivendo in chat:

IntentoEffetto
«elenca i workspace»Lista divisa in Sistema · protetti e Contenuto; ogni nome è cliccabile per selezionarlo.
«seleziona X»Passa al workspace X (lo aggiunge al selettore se assente).
«crea il workspace X»Crea e seleziona X. Compare in lista al primo contenuto scritto. Il prefisso _ è vietato (riservato al sistema).
«elimina X» / «azzera X»Chiede conferma in un riquadro (azione distruttiva). I workspace di sistema sono protetti.
Distruttivo Elimina rimuove il grafo del workspace; Azzera lo svuota (rinasce vuoto al primo contenuto). Entrambe passano dalla modale di conferma.

La traccia del giro #

In testa a ogni risposta compare una traccia in monospazio: mostra cosa ha capito l’interprete (atto, entità, confidenza) e cosa è stato chiamato sul grafo, con quanti risultati sono tornati. È il modo in cui la console resta uno strumento di validazione trasparente.

interprete → act=ask · entity_query=null · confidence 0.82
ask(question:"…", workspace:"…") → graphiti: search ibrida sui FATTI → LLM: DOMANDA + fatti [n]
graphiti → 5 fatti (le Fonti qui sotto)

Feed Attività #

La colonna sinistra della chat mostra le scritture recenti del workspace, più recenti in cima. Non solo episodi: anche entità e relazioni, così un workspace-schema (fatto di soli nodi) non appare vuoto.

TipoBadgeUn click apre…
episodioepisodiol’estrazione dell’episodio nel thread.
entitàentitàla scheda dell’entità.
relazionerelazionela scheda dell’entità sorgente.
  • ricarica i recenti.
  • La ricerca («cerca entità…») filtra la KB per entità; svuotando il campo si torna ai recenti.
  • I bottoni tutto / episodi / entità / relazioni filtrano per tipo la lista mostrata.

Scheda Entità #

La scheda di un’entità compare come messaggio nel thread e ha due sezioni:

Vicinato

I fatti correnti a un salto (1 hop) dall’entità. Su ogni fatto l’altra estremità è cliccabile: si naviga di scheda in scheda. C’è anche → episodio per la provenienza.

Storia

Tutti i fatti dell’entità, inclusi i superati (in grigio, con «fino al…»). È qui che si legge l’evoluzione temporale della conoscenza su quell’entità.

Se puoi curare il workspace, in alto compaiono ✎ Modifica e Unisci con…, e su ogni fatto le azioni invalida / ripristina.

Episodio / estrazione #

Aprendo un episodio (dal Feed, da una Fonte o dopo una scrittura) ne vedi l’estrazione nel thread: il testo originale, le entità estratte (con tipo, summary, attributi) e i fatti (con validità e numero di episodi di provenance). Le entità sono cliccabili.

Con i permessi di cura, il pulsante Elimina episodio è in cima alla scheda.

Aggiungere contenuto #

La scrittura è consapevole: prima l’anteprima, poi la conferma. Non si scrive mai «alla cieca».

  1. Incolli il testo e dai un nome all’episodio, poi premi Anteprima →.
  2. Graphiti estrae in dry-run (senza scrivere) e la console mostra le entità e i fatti che nascerebbero.
  3. Se la scrittura contraddice fatti esistenti, li elenca sotto ⚠ Verrebbero invalidati.
  4. Con Conferma scrittura viene scritto esattamente quel piano; con Annulla non resta nulla.

A scrittura fatta puoi saltare all’Ispeziona → per vedere l’estrazione reale.

Vuoi il dettaglio tecnico? Cosa fa esattamente Graphiti fra «Anteprima» e «Conferma» è spiegato nella voce ② Sotto il cofano — la pipeline dell’add.

Ontologia #

Vista strutturale pensata per i workspace-schema (es. ontology): invece degli episodi, si naviga lo schema ad albero.

ambitotipoattr (con l’eventuale TipoDato).

  • Ogni nodo è cliccabile → apre la scheda entità, dove lo si modifica.
  • I tipi senza ambito e il vocabolario TipoDato sono raccolti a parte.
  • Se il workspace non ha tipi d’ontologia, la vista lo dice: si popolano creando Ambiti/Tipi con scrittura diretta.

Curare la KB #

La cura sono azioni dirette con conferma, dove vivono gli oggetti: pulsanti sulla scheda del fatto/entità/episodio. Compaiono solo se il tuo ruolo (writer o admin) ti permette di scrivere in quel workspace. Ogni azione passa da una modale che spiega l’effetto e la reversibilità.

AzioneDoveEffetto
Invalida / ripristinasu un fatto (Vicinato/Storia)segna «non più vero da ora» (reversibile) / toglie l’invalidazione.
Unisci entitàscheda entitàfonde un doppione nel sopravvissuto (fatti e menzioni migrano). Irreversibile.
✎ Modifica entitàscheda entitàscrittura diretta di nome/tipo/summary (niente LLM). Per curare l’ontologia.
Elimina episodioscheda episodiorimuove i fatti dell’episodio e le entità rimaste orfane. Irreversibile.

Glossario #

TermineIn breve
AttoLa classe d’intento che l’interprete assegna alla tua frase (interrogare, esplorare, gestire workspace…).
askDomanda fondata sui fatti, con citazioni. Anche il default sicuro quando l’interprete è incerto.
ProvenanceIl legame fatto → episodio che lo ha generato (link → episodio).
VicinatoFatti correnti a 1 hop da un’entità.
StoriaTutti i fatti di un’entità nel tempo, superati inclusi.
Piano (plan)L’anteprima dry-run di una scrittura: cosa nascerebbe, senza scrivere.
CuraLe azioni che correggono il grafo: invalida/ripristina, unisci, modifica, elimina.
Workspace-schemaUn workspace fatto di soli nodi/tipi (es. l’ontologia), che si naviga ad albero.

② Sotto il cofano la pipeline dell’add, a livello tecnico

La pipeline dell’add #

Quando aggiungi contenuto (scheda Add, oppure via MCP), Graphiti non «salva il testo»: lo legge, estrae entità e fatti, li riconcilia con ciò che è già nel grafo e infine scrive. Questa voce descrive quel percorso passo per passo, ancorato al codice reale di Sophia (graph.plan_episode / graph.persist_episode) e a graphiti-core 0.29.1.

Nel codice di Sophia la pipeline è spezzata in due funzioni, ed è questo che rende possibile l’Anteprima:

FunzioneCosa faScrive sul grafo?
plan_episodePassi 0→4: estrae e risolve tutto in RAM.NO
persist_episodePasso 5: l’unica scrittura (il bulk).
Perché conta L’Anteprima esegue solo plan_episode: puoi guardare entità, fatti e contraddizioni prima che qualcosa tocchi il grafo. La Conferma esegue solo persist_episode su quello stesso piano — nessuna ri-estrazione.

I sei passi (0 → 5) #

Legenda: LETTURA tocca il grafo in sola lettura · LLM passo probabilistico (chiamata al modello) · DET deterministico, zero LLM.

0

Contesto LETTURA

Prima di tutto Graphiti recupera gli ultimi episodi del workspace (retrieve_episodes, last_n = RELEVANT_SCHEMA_LIMIT). Non legge il tuo testo da solo: lo legge insieme a cosa è entrato di recente — serve a sciogliere riferimenti come «l’azienda di cui sopra». Questi diventano i PREVIOUS MESSAGES dei prompt.

1

Estrai le entità LLM

L’LLM legge il testo e tira fuori i candidati (es. «Dusoft srl», «Zironda srl»): solo nomi, per ora (extract_nodes → prompt extract_text, perché la sorgente è testo).

Aggancio ontologia (1° presa): se passassimo entity_types, l’LLM proverebbe anche a classificarli (Azienda? Persona?). Oggi Sophia passa None → tutto generico.

2

Risolvi le entità — dedup LETTURA LLM

Per ogni candidato, cerca nel grafo entità simili (ricerca ibrida: embedding + testo) e un LLM decide «è la stessa cosa?» (resolve_extracted_nodes → prompt dedupe_nodes). Se sì → riusa l’entità esistente (stesso uuid); se no → nodo nuovo.

È il dedup: per questo l’anteprima marca «già presente» le entità di cui esiste già l’uuid (nel codice: _existing_entity_uuids → campo existing).

3

Estrai i fatti LLM

L’LLM estrae le relazioni fra i candidati: un arco Dusoft srl → Zironda srl con un nome (È_CLIENTE_DI) e la frase leggibile («Dusoft srl è cliente di Zironda srl») (extract_edges → prompt edge). Gli endpoint vengono rimappati sugli uuid risolti al passo 2 (resolve_edge_pointers).

Aggancio ontologia (2° presa): con edge_types l’LLM sceglierebbe fra relazioni tipizzate ammesse per quella coppia di tipi. Oggi None → nome libero in SCREAMING_SNAKE_CASE.

4

Risolvi i fatti — dedup + contraddizioni LETTURA LLM

Come il passo 2 ma sugli archi (resolve_extracted_edges → prompt dedupe_edges): il fatto esiste già? E soprattutto: contraddice un fatto corrente? Il prompt restituisce due liste, duplicate_facts e contradicted_facts. I contraddetti diventano gli invalidated_edges del piano → il tuo would_invalidate (è il famoso §13: la contraddizione la giudica l’LLM).

Infine un ultimo giro LLM idrata le entità coinvolte: attributi (extract_attributes) e summary (extract_summaries_batch).

5

Scrivi DET

Tutto ciò che è stato deciso in RAM viene scritto in un colpo solo: episodio + entità + archi + embedding, un’unica scrittura bulk (add_nodes_and_edges_bulk, MERGE per-uuid → ri-applicare lo stesso piano è idempotente). Zero LLM.

add vs anteprima / conferma #

Stessa pipeline, tagliata in punti diversi:

OperazionePassi eseguitiLLMScrive
add (diretto)0 → 5 di fila, in una chiamata1–4sì (passo 5)
Anteprima add_plan0 → 4, poi stop1–4no
Conferma applysolo 5nessuno

Il risultato dei passi 0–4 (entità con «già presente», fatti, would_invalidate) è il piano, parcheggiato server-side con una scadenza di 1 ora e consumabile una volta sola. La Conferma scrive esattamente quel piano: quello che hai visto in anteprima è quello che va nel grafo, byte per byte — nessuna ri-estrazione, nessun secondo giro LLM.

I due punti che contano

Dove sta il probabilistico: i passi 1–4 sono LLM (estrazione, dedup, fatti, contraddizioni) — è lì che nasce la varianza (i «2 fatti» una volta sì e una no). Il passo 5 è deterministico. L’anteprima esiste proprio per farti guardare 1–4 prima di committare.

Dove si aggancia l’ontologia: vedi la sezione seguente.

Dove si aggancia l’ontologia #

Nei passi 1 e 3 ci sono due «prese» già cablate nel codice (entity_types e edge_types) in cui oggi Sophia passa None (service.py chiama plan_episode senza tipi; l’edge_type_map di default è {("Entity","Entity"): []}). Il traduttore dell’ontologia farà esattamente questo: leggere _ontology e riempire quelle prese.

Il dettaglio chiave per il bottom-up Quando i tipi ci sono, l’LLM classifica se ci riesce — se nessun tipo calza, l’entità resta generica (il prompt lo prevede: tipo = None). Le generiche ricorrenti sono quindi il segnale naturale di «manca un tipo»: è l’aggancio da cui il bottom-up può nascere.

I prompt d’ingest #

Ecco i prompt che Graphiti passa all’LLM durante l’ingest, nell’ordine della pipeline. Per ognuno: cosa chiede, e — nel blocco a fisarmonica — il testo reale (graphiti-core 0.29.1). I segnaposto tipo {episode_content} sono i punti dove Graphiti inietta i dati.

1 · Estrazione entità — extract_nodes.extract_text

Estrae solo entità concrete e nominabili; vieta pronomi, concetti astratti, nomi generici; usa la forma più specifica; classifica con entity_type_id (o nessun tipo). In coda viene iniettata la nostra direttiva di lingua (custom_extraction_instructions).

Vedi il prompt reale — extract_text
system + user message
SYSTEM:
You are an entity extraction specialist for unstructured text. NEVER extract abstract concepts, feelings, or generic words.

USER:
NEVER extract:
- Pronouns (you, me, he, she, they, it, them, him, her, we, us, this, that, those)
- Abstract concepts (joy, balance, growth, resilience, passion, motivation)
- Generic common nouns or bare object words (day, life, people, work, stuff, things, food, time,
  tickets, supplies, clothes, keys, gear)
- Generic media/content nouns unless uniquely identified in the node name itself (photo, pic, picture,
  image, video, post, story)
- Generic event/activity nouns unless uniquely identified in the node name itself (event, game, meeting,
  class, workshop, competition)
- Broad institutional nouns unless explicitly named or uniquely qualified (government, school, company,
  team, office)
- Ambiguous bare nouns whose meaning depends on sentence context rather than the node name itself
- Sentence fragments or clauses as entity names
- Bare relational or kinship terms (dad, mom, sister, brother, spouse, friend, boss, pet, dog,
  cat) unless qualified with a possessor (e.g., "Nisha's dad" is acceptable, "dad" alone is not)
- Bare generic objects that cannot be meaningfully qualified with a possessor, brand, or
  distinguishing detail (e.g., NEVER extract "supplies" from "I picked up some supplies")

Extract entities from the TEXT that are **explicitly mentioned**.
For each entity, classify it using the ENTITY TYPES above.
Only extract entities specific enough to be uniquely identifiable — ask: "Could this have its own Wikipedia article or database entry?"

<ENTITY TYPES>
{entity_types}
</ENTITY TYPES>

<TEXT>
{episode_content}
</TEXT>

Guidelines:
1. Extract named entities and specific, concrete things.
2. Do not create nodes for relationships or actions.
3. Do not create nodes for temporal information like dates, times or years.
4. Be explicit in node names, using full names and avoiding abbreviations.
5. Always use the most specific form from the text (e.g., "road cycling" not "cycling",
   "wool coat" not "coat"). Include qualifying context when it's clear from the text.
6. When the text refers to a person's relative, pet, or associate by a bare term, qualify the
   entity with the possessor's name (e.g., "Dr. Osei's colleague" not "colleague").
7. If a phrase would not be meaningful and distinguishable when read alone later, do NOT extract it.
8. When in doubt, do NOT extract.

{custom_extraction_instructions}

<EXAMPLE>
Text: "Dr. Amara Osei presented her migraine study results at the AAN conference. The study tracked 340 patients using a new CGRP combination protocol."
Good extractions: "Dr. Amara Osei" (Person), "AAN" (Organization), "migraine study" (Topic), "CGRP combination protocol" (Object)
Do NOT extract: "results" (generic noun), "340" (number), "patients" (generic noun), "conference" (generic without a specific name)
</EXAMPLE>

<EXAMPLE>
Text: "Alex shared a pic after the event and said scoring the last basket felt incredible."
Good extractions: "Alex" (Person)
Do NOT extract: "pic" (generic media noun), "event" (generic event noun), "basket" (ambiguous bare noun)
</EXAMPLE>

2 · Dedup entità — dedupe_nodes.nodes

Per ogni candidato estratto, confronta con le entità simili già nel grafo e decide se è la stessa cosa del mondo reale: restituisce il candidate_id del match, o -1 se nuovo/incerto.

Vedi il prompt reale — dedupe_nodes.nodes
system + user message
SYSTEM:
You are an entity deduplication assistant. NEVER fabricate entity names or mark distinct entities as duplicates.

USER:
<PREVIOUS MESSAGES>
{previous_episodes}
</PREVIOUS MESSAGES>

<CURRENT MESSAGE>
{episode_content}
</CURRENT MESSAGE>

<ENTITIES>
{extracted_nodes}
</ENTITIES>

<EXISTING ENTITIES>
{existing_nodes}
</EXISTING ENTITIES>

Each of the above ENTITIES was extracted from the CURRENT MESSAGE.
For each entity, determine if it is a duplicate of any EXISTING ENTITY.
Entities should only be considered duplicates if they refer to the *same real-world object or concept*.

NEVER mark entities as duplicates if:
- They are related but distinct.
- They have similar names or purposes but refer to separate instances or concepts.

Task:
ENTITIES contains N entities with IDs 0 through N-1.
Your response MUST include EXACTLY N resolutions with IDs 0 through N-1. Do not skip or add IDs.

For every entity, provide:
- `id`: integer id from ENTITIES
- `name`: the best full name for the entity (preserve the original name unless a duplicate has a more complete name)
- `duplicate_candidate_id`: the `candidate_id` of the EXISTING ENTITY that is the best duplicate match, or -1 if there is no duplicate

<EXAMPLE>
ENTITY: "NYC"
EXISTING ENTITIES: [{"candidate_id": 0, "name": "New York City", "entity_types": ["Location"]}, {"candidate_id": 1, "name": "New York Knicks", "entity_types": ["Organization"]}]
Result: duplicate_candidate_id = 0 (same location, abbreviated name)

ENTITY: "Java" (programming language)
EXISTING ENTITIES: [{"candidate_id": 0, "name": "Java", "entity_types": ["Location"], "summary": "An island in Indonesia"}]
Result: duplicate_candidate_id = -1 (same name but distinct real-world things)
</EXAMPLE>

3 · Estrazione fatti — extract_edges.edge

Estrae triple source → RELATION_TYPE → target fra due entità distinte della lista, con una fact in prosa che preserva ogni dettaglio concreto (mai generalizzare) e le date valid_at/invalid_at. Se ci sono FACT_TYPES (ontologia) sceglie fra quelli; altrimenti conia il nome in SCREAMING_SNAKE_CASE.

Vedi il prompt reale — extract_edges.edge
system + user message
SYSTEM:
You are an expert fact extractor that extracts fact triples from text.
1. Extracted fact triples should also be extracted with relevant date information.
2. The CURRENT_MESSAGE may contain multiple episodes, each with its own timestamp.
Use each episode's timestamp to resolve temporal references within that episode.
REFERENCE_TIME is a fallback for when no per-episode timestamp is available.

USER:
<PREVIOUS_MESSAGES>
{previous_episodes}
</PREVIOUS_MESSAGES>

<CURRENT_MESSAGE>
{episode_content}
</CURRENT_MESSAGE>

<ENTITIES>
{nodes}
</ENTITIES>

<REFERENCE_TIME>
{reference_time}   # ISO 8601 (UTC); used to resolve relative time mentions
</REFERENCE_TIME>

{edge_types_section}   # <FACT_TYPES> … </FACT_TYPES> se l'ontologia li fornisce (oggi assente)

# TASK
Extract all factual relationships between the given ENTITIES based on the CURRENT MESSAGE.
Only extract facts that:
- involve two DISTINCT ENTITIES from the ENTITIES list,
- are clearly stated or unambiguously implied in the CURRENT MESSAGE,
    and can be represented as edges in a knowledge graph.
- Facts should include entity names rather than pronouns whenever possible.

You may use information from the PREVIOUS MESSAGES only to disambiguate references or support continuity.

{custom_extraction_instructions}

# EXTRACTION RULES
1. **Entity Name Validation**: `source_entity_name` and `target_entity_name` must use only the `name` values from the ENTITIES list provided above.
   - **CRITICAL**: Using names not in the list will cause the edge to be rejected
2. Each fact must involve two **distinct** entities.
3. Prefer facts that involve two distinct entities from the ENTITIES list. When a sentence describes a specific, concrete detail about a single entity, look for a second entity in the list that the detail relates to and form a proper triple (e.g., Entity -> OWNS -> item, Entity -> LIVES_IN -> place, Entity -> HAS_ATTRIBUTE -> detail). Only skip when no second entity can anchor the detail.
4. Do not emit semantically redundant facts. If a later episode adds specific details to a previously stated fact, extract the more detailed version as a NEW fact.
5. The `fact` MUST preserve all specific details from the source text: proper nouns, brand names, product/model numbers, quantities, counts, colors, materials, physical descriptions, named locations and activities. Paraphrase the structure but NEVER generalize ("Gamecube" not "gaming console", "wool coat" not "coat").
6. Use REFERENCE_TIME to resolve vague/relative temporal expressions.
7. Do not hallucinate or infer temporal bounds from unrelated events.

# RELATION TYPE RULES
- If FACT_TYPES are provided and the relationship matches one (considering the entity type signature), use that fact_type_name as the `relation_type`.
- Otherwise, derive a `relation_type` from the predicate in SCREAMING_SNAKE_CASE (e.g., WORKS_AT, LIVES_IN).

# DATETIME RULES
- ISO 8601 with "Z" suffix (UTC). If ongoing (present tense), valid_at = episode timestamp (or REFERENCE_TIME).
- If a change/termination is expressed, set invalid_at. Leave null if no explicit/resolvable time.
- Date only → assume 00:00:00. Year only → January 1st 00:00:00.

4 · Dedup + contraddizioni fatti — dedupe_edges.resolve_edge §13

Confronta il nuovo fatto con i fatti esistenti e coi «candidati all’invalidazione»: restituisce duplicate_facts (identici, solo dagli esistenti) e contradicted_facts (che il nuovo fatto supera/contraddice). Questi ultimi sono ciò che finisce in would_invalidate.

Vedi il prompt reale — dedupe_edges.resolve_edge
system + user message
SYSTEM:
You are a fact deduplication assistant. NEVER mark facts with key differences as duplicates.

USER:
NEVER mark facts as duplicates if they have key differences, particularly around numeric values, dates, or key qualifiers.

IMPORTANT constraints:
- duplicate_facts: ONLY idx values from EXISTING FACTS (NEVER include FACT INVALIDATION CANDIDATES)
- contradicted_facts: idx values from EITHER list (EXISTING FACTS or FACT INVALIDATION CANDIDATES)
- The idx values are continuous across both lists (INVALIDATION CANDIDATES start where EXISTING FACTS end)

<EXISTING FACTS>
{existing_edges}
</EXISTING FACTS>

<FACT INVALIDATION CANDIDATES>
{edge_invalidation_candidates}
</FACT INVALIDATION CANDIDATES>

<NEW FACT>
{new_edge}
</NEW FACT>

1. DUPLICATE DETECTION:
   - If the NEW FACT represents identical factual information as any fact in EXISTING FACTS, return those idx values in duplicate_facts.
   - If no duplicates, return an empty list.

2. CONTRADICTION DETECTION:
   - Determine which facts the NEW FACT contradicts from either list.
   - A fact from EXISTING FACTS can be both a duplicate AND contradicted (e.g., semantically the same but the new fact updates/supersedes it).
   - Return all contradicted idx values in contradicted_facts.

<EXAMPLE>
EXISTING FACT: idx=1, "Alice works at Acme Corp as a software engineer"
NEW FACT: "Alice works at Acme Corp as a senior engineer"
Result: duplicate_facts=[], contradicted_facts=[1] (same relationship but updated title — contradiction, NOT a duplicate)

EXISTING FACT: idx=2, "Bob ran 5 miles on Tuesday"
NEW FACT: "Bob ran 3 miles on Wednesday"
Result: duplicate_facts=[], contradicted_facts=[] (different events on different days — neither duplicate nor contradiction)
</EXAMPLE>

5 · Idratazione entità — extract_summaries_batch + extract_attributes

Ultimo giro LLM prima della scrittura: genera/aggiorna il summary denso di ogni entità coinvolta (usando solo i fatti supportati) e ne estrae gli attributi (solo valori esplicitamente presenti nel testo).

Vedi il prompt reale — extract_summaries_batch
system + user message
SYSTEM:
You are a helpful assistant that generates concise entity summaries from provided context.

USER:
Given the MESSAGES and a list of ENTITIES, generate an updated summary for each entity that needs one.
Each summary must be under {MAX_SUMMARY_CHARS} characters.

{summary_instructions}

<MESSAGES>
{previous_episodes}
{episode_content}
</MESSAGES>
{entity_type_descriptions_section}
<ENTITIES>
{entities}
</ENTITIES>

For each entity, combine relevant information from the MESSAGES with any existing summary content.
Only return summaries for entities that have meaningful information to summarize.
If an entity has no relevant information in the messages and no existing summary, you may skip it.
Vedi il prompt reale — extract_attributes (estratto)
system + user message · HARD RULES anti-allucinazione
SYSTEM:
You are an entity attribute extraction specialist. You ONLY emit attribute values that are explicitly
stated in MESSAGES or already present on the ENTITY. You output strictly the JSON specified by the
response schema — no reasoning, no explanation, no commentary in any field.

USER (HARD RULES — violating any of these is a failure):
1. Each attribute value MUST be one of: (a) a clean value copied/normalized from MESSAGES,
   (b) the existing value already on the ENTITY, or (c) null / omitted.
2. NEVER write reasoning, justification, or commentary into any field
   (no "(implied by ...)", no "I should...", no listing alternatives, no explaining why a value is null).
3. Schema description text is NEVER itself a value.
4. "null", "N/A", "unknown", "not provided" (or any sentence describing absence) are NOT valid values.
5. Each value must be a short, well-formed instance of the type the field describes.
6. NEVER infer from the entity's name, related entities, or world knowledge — only verbatim/normalized text from MESSAGES.
7. If MESSAGES say nothing about an attribute, leave the existing value unchanged; else null.

<MESSAGES> {previous_episodes} {episode_content} </MESSAGES>
<ENTITY> {node} </ENTITY>

Nota Sophia · lingua d’estrazione

Sophia inietta una direttiva di lingua (per-istanza, es. italiano) nella coda dei prompt di estrazione via custom_extraction_instructions — così summary, fatti e label escono in italiano, ma i nomi propri restano nella forma originale. La direttiva raggiunge l’estrazione di nodi e archi (passi 1 e 3) ma non la fase di summary (gap upstream di graphiti): per questo, dopo la scrittura, Sophia fa una post-pass dedicata (_canonicalize_summaries, un giro LLM) che riscrive i summary in lingua canonica. È fail-soft: se fallisce, l’episodio è già scritto.

Vedi la direttiva di lingua iniettata
# ADDITIONAL EXTRACTION INSTRUCTIONS
Perform all extraction in {language}. Write every entity summary, fact description and relationship
label in {language}, even when the source text is in another language. Keep proper names (people,
organizations, brands, places, products) in their original form — do not translate them. Do not mix
languages within a single field.