- Le moteur n'envoie jamais l'historique (system + objectif courant seulement) : chaque run repartait de zéro. Nouveau service app/services/agent_memory.py — une ligne résumé par conversation (table agent_memory, migration 31), écrite en fin de run et ré-injectée au contexte du run suivant. - context_for() : bloc « ## Mémoire de la conversation », budget 4000 car. (troncature gauche + marqueur). remember() : 1 note/échange (180 → 700 car.), 20 notes max, upsert, erreurs journalisées — un run ne peut pas échouer à cause de la mémoire. Toggle OFF = ni lecture ni écriture. - Toggle « Mémoire » dans le menu + : entrée racine à l'état réel (🧠/💭 + libellé), PATCH /api/agent/conversations/{id} avec memory_enabled ajouté au whitelist, état lu à l'ouverture et à la création, défaut AGENT_MEMORY_DEFAULT (settings.agent_memory_default) + memory_enabled dans la réponse de création. - Décision : mémoire d'espace (lignes workspace_id NULL) remise — YAGNI, rien ne l'écrivait ; noté ponytail: dans le service avec le chemin de montée. - Tests : tests/test_v754_memory.py (8) dont 2 runs réels du moteur (FakeLLM capture le prompt : mémoire présente ON / absente OFF) ; test_v751 et test_v753 adaptés (2 sections « bientôt » restantes). Suite complète 1293 verts (-n auto, 2e run ; 1 échec intermittent sur test_v56_import URL au 1er run, vert isolé + vert au run suivant = flake préexistant), ruff 0, eslint 0 problème. OpenAPI 516 chemins / 7.54.0.
90 lines
3.4 KiB
Python
90 lines
3.4 KiB
Python
"""Mémoire de l'agent (v7.54.0).
|
|
|
|
Une ligne résumé **par conversation**, mise à jour à chaque run et ré-injectée
|
|
au contexte du run suivant — le moteur n'envoie jamais l'historique des messages
|
|
(`AgentEngine.run()` ne construit que system + objectif courant), donc sans
|
|
mémoire chaque run repart de zéro.
|
|
|
|
Toggle : colonne `agent_conversations.memory_enabled` — OFF = aucune lecture ni
|
|
écriture (contexte strict du run courant).
|
|
|
|
ponytail : mémoire par conversation seulement. Si besoin de notes partagées
|
|
entre conversations, ajouter des lignes avec ``conversation_id NULL`` +
|
|
``workspace_id`` et élargir la lecture dans ``context_for()``.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import logging
|
|
import sqlite3
|
|
|
|
from app.db import get_conn
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
INJECT_BUDGET = 4000 # caractères de mémoire max injectés dans un prompt
|
|
NOTE_LINES = 20 # échanges max conservés
|
|
NOTE_OBJECTIVE = 180 # troncature de l'objectif
|
|
NOTE_ANSWER = 700 # troncature de la réponse
|
|
|
|
|
|
def _enabled(conn: sqlite3.Connection, conversation_id: int) -> bool:
|
|
row = conn.execute(
|
|
"SELECT memory_enabled FROM agent_conversations WHERE id=?",
|
|
(conversation_id,),
|
|
).fetchone()
|
|
if row is None:
|
|
return False
|
|
return bool(row["memory_enabled"])
|
|
|
|
|
|
def _one_line(text: str, limit: int) -> str:
|
|
return " ".join((text or "").split())[:limit]
|
|
|
|
|
|
def context_for(conversation_id: int) -> str:
|
|
"""Bloc « mémoire » à préfixer au contexte, ou ``''`` (toggle OFF / vide)."""
|
|
with get_conn() as conn:
|
|
if not _enabled(conn, conversation_id):
|
|
return ""
|
|
row = conn.execute(
|
|
"SELECT content FROM agent_memory WHERE conversation_id=?",
|
|
(conversation_id,),
|
|
).fetchone()
|
|
content = ((row["content"] if row else "") or "").strip()
|
|
if not content:
|
|
return ""
|
|
if len(content) > INJECT_BUDGET:
|
|
content = "…(début de mémoire tronqué)\n" + content[-INJECT_BUDGET:]
|
|
return "## Mémoire de la conversation (échanges précédents)\n" + content
|
|
|
|
|
|
def remember(conversation_id: int, objective: str, final_text: str) -> None:
|
|
"""Ajoute un échange à la mémoire — no-op si le toggle est OFF.
|
|
|
|
Ne doit **jamais** faire échouer un run : toute erreur est journalisée.
|
|
"""
|
|
note = f"- **{_one_line(objective, NOTE_OBJECTIVE)}** → {_one_line(final_text, NOTE_ANSWER)}"
|
|
try:
|
|
with get_conn() as conn:
|
|
if not _enabled(conn, conversation_id):
|
|
return
|
|
row = conn.execute(
|
|
"SELECT content FROM agent_memory WHERE conversation_id=?",
|
|
(conversation_id,),
|
|
).fetchone()
|
|
lines = [ln for ln in ((row["content"] if row else "") or "").split("\n") if ln.strip()]
|
|
lines.append(note)
|
|
if len(lines) > NOTE_LINES:
|
|
lines = lines[-NOTE_LINES:]
|
|
conn.execute(
|
|
"INSERT INTO agent_memory (conversation_id, content, updated_at) "
|
|
"VALUES (?, ?, CURRENT_TIMESTAMP) "
|
|
"ON CONFLICT(conversation_id) DO UPDATE SET "
|
|
"content=excluded.content, updated_at=CURRENT_TIMESTAMP",
|
|
(conversation_id, "\n".join(lines)),
|
|
)
|
|
conn.commit()
|
|
except Exception: # noqa: BLE001 — la mémoire ne casse jamais un run
|
|
logger.exception("Agent memory save failed for conversation #%s", conversation_id)
|