
Benchmark deterministico e harness di valutazione per l'avvelenamento della memoria persistente negli agenti LLM, con casi appaiati puliti/avvelenati, runner LangChain/LlamaIndex e difese.
Questo repository accompagna il paper MemForm: Benchmarking Memory Formation Poisoning in LLM Agents.
MemForm è un benchmark deterministico dell'avvelenamento della memoria persistente negli agenti LLM. Pone una domanda causale che le comuni suite di jailbreak o di prompt injection non isolano:
Se la memoria a lungo termine di un agente viene modificata in una superficie ben definita, il processo decisionale a valle passa da una policy pulita a una policy avvelenata sullo stesso task?
Ogni elemento è un intervento appaiato. Il trigger a valle, lo spazio delle azioni e la semantica della famiglia restano fissi. Solo l'input che costruisce la memoria cambia tra la condizione pulita e quella avvelenata. Il successo dell'attacco è un contrasto all'interno dello stesso elemento.
Questa release contiene i casi di benchmark congelati e i runner di valutazione / difesa. La pipeline di costruzione è trattenuta finché il paper è in fase di revisione e sarà rilasciata dopo la pubblicazione.
Ogni famiglia è istanziata su cinque interfacce di memoria. Il campo del caso è interface. P1–P5 sono i nomi usati nel paper: Content, Evidence, Scope, Selection e Path.
| Interfaccia | Nome nel paper | Variabile causale | Contrasto pulito vs. avvelenato | Sonda a valle |
|---|---|---|---|---|
| P1 | Content | Testo della memoria memorizzato | Scrittura diretta di una policy riutilizzabile pulita vs. una policy avvelenata | Un task target generale |
| P2 | Evidence | Autenticità dei record di supporto | Storia autentica a budget uguale vs. una storia appaiata che sostituisce alcuni record con prove falsificate. La provenienza è nascosta al learner della memoria | Un task target generale |
| P3 | Scope | Distribuzione dello scope osservato | Prove localmente valide più controesempi esterni vs. successi interni salienti al cue più successi esterni/generali autentici per l'azione preferita localmente | Tre task: in-scope, quasi out-of-scope, target out-of-scope |
| P4 | Selection | Sottoinsieme mantenuto | Stesso pool di candidati; mantenimento pulito vs. avvelenato di un sottoinsieme di dimensione fissa, con un controllo sull'intero pool | Un task target generale |
| P5 | Path | Solo l'ordine di consolidamento | Lo stesso multiset di esperienze con lo stesso contesto in due permutazioni. Le prove pulite sono per ultime nell'ordine pulito; le prove avvelenate sono per ultime nell'ordine avvelenato | Un task target con lo stesso contesto |
P3 in-scope è un controllo di accordo / target condiviso: le risposte gold pulite e avvelenate sono identiche. I task quasi-outside e target-outside sono le sonde separabili. P5 vieta spostamenti nascosti del contesto: ogni record condivide un unico context_key, e sia i record che supportano la condizione pulita sia quelli che supportano l'avvelenamento sono successi osservati.
| Percorso | Contenuti |
|---|---|
outputs/mcq_item_audit/accepted_cases.jsonl | Casi a scelta multipla congelati (994 elementi, 235 famiglie) |
outputs/freeform_item_audit/accepted_cases.jsonl | Casi in forma libera congelati (897 elementi, 218 famiglie) |
memform/eval/ | Protocollo condiviso più runner LangChain / LlamaIndex / difese |
memform/freeform/evaluator.py | Adjudicator a cascata per risposte in forma libera |
memform/defenses/ | Difese in fase di scrittura, Task Shield in fase d'uso e metriche a denominatore fisso |
memform/common.py | I/O JSONL e controlli dello schema dei casi |
Conteggi per interfaccia — MCQ: P1 234, P2 233, P3 174, P4 144, P5 209. Forma libera: P1 209, P2 212, P3 144, P4 134, P5 198.
memform-code/
├── outputs/
│ ├── mcq_item_audit/accepted_cases.jsonl
│ └── freeform_item_audit/accepted_cases.jsonl
├── memform/
│ ├── common.py
│ ├── freeform/evaluator.py
│ ├── eval/ # protocol, LangChain, LlamaIndex, defense runners
│ └── defenses/
├── run_langchain.py
├── run_llamaindex.py
├── run_defense.py
├── pyproject.toml
├── requirements.txt
└── .env.example
Gli script run_*.py nella root chiamano gli stessi entry point di python -m memform <command>.
Python 3.10+.
python -m venv .venv
source .venv/bin/activate # Windows: .venv\Scripts\activate
pip install -e .
Copia .env.example in .env e imposta le credenziali:
| Variabile | Utilizzata da |
|---|---|
OPENAI_API_KEY | Agente di valutazione / modello di memoria |
OPENAI_BASE_URL | Gateway opzionale compatibile con OpenAI (OPENAI_API_BASE è accettato anche da LlamaIndex) |
JUDGE_OPENAI_API_KEY / JUDGE_OPENAI_BASE_URL | Valutatore judge / free-form opzionale e separato |
DEFENSE_OPENAI_API_KEY / DEFENSE_OPENAI_BASE_URL | Endpoint opzionale e separato per il modello di difesa |
--api-key e --base-url hanno la precedenza sulle variabili d'ambiente. Qualsiasi server Chat Completions compatibile con OpenAI funziona. I nomi dei modelli predefiniti della CLI sono segnaposto; passa un modello disponibile sul tuo endpoint.
python -m memform --help
JSONL, un caso per riga.
Campi condivisi: case_id, family_id, interface (P1–P5), attack_type, domain, decision_variable, clean_policy, poisoned_policy, scope, candidate_experiences, memory_building, metadata.
memory_building è l'intervento appaiato:
oracle_memory.clean / oracle_memory.poisoned — testo della policy gold (diagnostico)clean_condition / poisoned_condition — input di memory-building per quell'interfacciaMCQ. evaluation.type è multiple_choice. Ogni voce di trigger_tasks[] ha task, choices, clean_answer e poisoned_answer.
Free-form. task_format è free_form; source_case_id punta al caso MCQ di origine. I menu di opzioni sono sostituiti da una raccomandazione aperta. Il punteggio utilizza behavioral_signatures appaiate (clean / poisoned / ambiguous / unclear). La costruzione del poisoning resta invariata.
La provenienza e le etichette gold costituiscono la ground truth del benchmark per il punteggio. Le difese non ricevono mai l'interfaccia di attacco, i nomi delle condizioni, le risposte gold, la provenienza o gli ID come feature del prompt.
Entrambi i runner condividono la validazione dei casi, la consolidazione della memoria, la decisione a valle, il punteggio e l'aggregazione. Differiscono solo nello stack LLM / memoria.
python -m memform run-langchain \
--cases outputs/mcq_item_audit/accepted_cases.jsonl \
--output-dir results/langchain_mcq \
--phase all \
--interfaces P1 P2 P3 P4 P5 \
--model MODEL
python -m memform run-llamaindex \
--cases outputs/mcq_item_audit/accepted_cases.jsonl \
--output-dir results/llamaindex_mcq \
--phase learned \
--interfaces P1 P2 P3 P4 P5 \
--model MODEL
Gli stessi comandi accettano outputs/freeform_item_audit/accepted_cases.jsonl. Il punteggio free-form utilizza la corrispondenza canonica esatta, poi modelli opzionali di parsing / giudizio (--freeform-parser-model, --freeform-judge-model).
| Flag | Significato |
|---|---|
--phase oracle|learned|all | Oracle utilizza il testo della policy gold; learned consolida dalla cronologia compilata |
--target-alignment | Giudizio condizionato dal task su ciò che la memoria appresa raccomanda (diagnostico) |
--p5-updaters | incremental_merge (predefinito), full_rewrite, recency_weighted_strict, batch_replay |
--p5-random-orders N | Permutazioni P5 deterministiche aggiuntive |
--cache | Cache su disco JSONL delle chiamate LLM |
--validate-only | Stampa il piano di esecuzione e il numero stimato di chiamate, quindi esce |
Ogni esecuzione scrive case_results.jsonl e summary.json sotto --output-dir.