mirror of
https://github.com/R0m1k3/Loki.git
synced 2026-10-20 21:48:47 +02:00
Niveau supérieur : mémoire compressée, outils chirurgicaux, modèle code auto
Faire d'un petit modèle un bon agent : - memory.py : contexte = invite + résumé des anciens tours + 10 derniers messages ; résumé régénéré en arrière-plan après chaque réponse (colonne summary sur sessions, migration douce). Contexte court = modèle concentré et qui tient sur le GPU. - tools.py : edit_file (recherche/remplacement exact, erreurs pédagogiques, unicité exigée), grep_search (regex bornée, dossiers ignorés), et vérification syntaxique auto (.py/.json) après chaque écriture — l'erreur revient au modèle qui se corrige dans le même tour. - coder.pick_code_model : les tâches de code vont au meilleur modèle code installé (qwen-coder, deepseek-coder…) via config code_model=auto. - Branché dans la route chat (mémoire + résolution du modèle code) et la boucle agent (code_task) ; UI : descriptions et glyphes des nouveaux outils. Tests : edit/grep/vérification, compression mémoire (19 msgs -> 12 dont résumé), pick auto/explicite, chat HTTP de bout en bout, build front. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01SVay7z3y7q2gEe54ByAE6N
This commit is contained in:
11 files changed
+356
-11
No files matched your search
@@ -0,0 +1,90 @@
|
||||
"""Mémoire de conversation compressée — le vrai levier des petits modèles.
|
||||
|
||||
Un petit modèle se noie dans un long historique : il oublie la consigne, part
|
||||
en boucle, et un grand num_ctx le fait déborder du GPU. On garde donc :
|
||||
[invite système] + [résumé compact des anciens tours] + [N derniers messages]
|
||||
|
||||
Le résumé est régénéré en arrière-plan (après la réponse, sans latence pour
|
||||
l'utilisateur) dès que l'historique dépasse le seuil.
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import logging
|
||||
|
||||
import httpx
|
||||
|
||||
from . import db
|
||||
from .ollama_client import ollama
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
# Nombre de messages récents passés tels quels au modèle.
|
||||
KEEP_RECENT = 10
|
||||
# Au-delà de ce total, les anciens tours sont compressés dans le résumé.
|
||||
SUMMARIZE_AFTER = KEEP_RECENT + 6
|
||||
|
||||
_SUMMARY_PROMPT = (
|
||||
"Résume la conversation ci-dessous en français, en 10 lignes maximum. "
|
||||
"Conserve impérativement : l'objectif de l'utilisateur, les décisions "
|
||||
"prises, les fichiers créés/modifiés et leur rôle, et les points encore "
|
||||
"ouverts. Réponds UNIQUEMENT par le résumé."
|
||||
)
|
||||
|
||||
|
||||
def build_convo(sid: str, system_prompt: str) -> list[dict]:
|
||||
"""Construit le contexte : système + résumé éventuel + messages récents."""
|
||||
convo: list[dict] = [{"role": "system", "content": system_prompt}]
|
||||
|
||||
session = db.get_session(sid) or {}
|
||||
summary = (session.get("summary") or "").strip()
|
||||
messages = db.list_messages_for_model(sid)
|
||||
|
||||
if summary and len(messages) > KEEP_RECENT:
|
||||
convo.append(
|
||||
{
|
||||
"role": "system",
|
||||
"content": f"Résumé des échanges précédents :\n{summary}",
|
||||
}
|
||||
)
|
||||
convo += messages[-KEEP_RECENT:]
|
||||
else:
|
||||
convo += messages
|
||||
return convo
|
||||
|
||||
|
||||
async def maybe_summarize(sid: str, model: str) -> None:
|
||||
"""Compresse les anciens tours dans le résumé (tâche d'arrière-plan)."""
|
||||
try:
|
||||
messages = db.list_messages_for_model(sid)
|
||||
if len(messages) <= SUMMARIZE_AFTER:
|
||||
return
|
||||
|
||||
session = db.get_session(sid) or {}
|
||||
previous = (session.get("summary") or "").strip()
|
||||
old = messages[:-KEEP_RECENT]
|
||||
|
||||
transcript = "\n".join(
|
||||
f"[{m['role']}] {m['content'][:600]}" for m in old
|
||||
)[-8000:]
|
||||
if previous:
|
||||
transcript = f"[résumé existant] {previous}\n{transcript}"
|
||||
|
||||
text = ""
|
||||
async for chunk in ollama.chat(
|
||||
model,
|
||||
[
|
||||
{"role": "system", "content": _SUMMARY_PROMPT},
|
||||
{"role": "user", "content": transcript},
|
||||
],
|
||||
options={"temperature": 0.2, "num_predict": 350},
|
||||
stream=True,
|
||||
):
|
||||
text += chunk.get("message", {}).get("content", "")
|
||||
if chunk.get("done"):
|
||||
break
|
||||
|
||||
if text.strip():
|
||||
db.set_session_summary(sid, text.strip())
|
||||
except (httpx.HTTPError, OSError) as exc:
|
||||
# Best-effort : un échec de résumé ne doit jamais gêner le chat.
|
||||
logger.warning("Résumé de session %s impossible : %s", sid, exc)
|
||||
Reference in new issue
Block a user