mirror of
https://github.com/R0m1k3/Loki.git
synced 2026-10-24 07:28:13 +02:00
Mémoire en notes Markdown + presets de configuration (repris d'AJEAN)
Les deux dernières idées d'AJEAN (github.com/nathaninline/jean). 1. Mémoire en notes Markdown ($DATA_DIR/MEMORY), écrites par l'agent via deux outils (memory_search / memory_save). Trois modes : off, ondemand (défaut) et always. Face au RAG vectoriel : aucun modèle d'embedding requis, notes lisibles et éditables à la main, et rien de mémorisé à l'insu — la recherche par mots-clés ne rappelle que du pertinent, là où le RAG ressortait une demande sans rapport. Le RAG disparaît de l'UI : un seul concept de mémoire. En mode Plan, seule la consultation est proposée, pas l'écriture. 2. Presets : jeux de réglages nommés, enregistrés et rappelés en un clic (sélecteur + « + Preset » dans l'en-tête des Réglages). Au passage, un vrai bug : ConfigPatch ne déclarait ni keep_alive, ni skills_enabled, ni ponytail — Pydantic ignore les champs non déclarés, donc ces trois réglages ne se sauvegardaient JAMAIS. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01SVay7z3y7q2gEe54ByAE6N
This commit is contained in:
7 files changed
+463
-5
No files matched your search
@@ -20,7 +20,9 @@ from fastapi import APIRouter, HTTPException
|
||||
from fastapi.responses import StreamingResponse
|
||||
from pydantic import BaseModel
|
||||
|
||||
from .. import agent_config, coder, db, enhance, memory, rag, skills, tools
|
||||
from .. import (
|
||||
agent_config, coder, db, enhance, memory, memory_notes, rag, skills, tools,
|
||||
)
|
||||
from .. import router as msg_router
|
||||
from ..tools import check_html, _safe_path
|
||||
from ..agent import run_agent
|
||||
@@ -437,6 +439,24 @@ async def chat(req: ChatRequest) -> StreamingResponse:
|
||||
if want_web:
|
||||
convo.insert(1, {"role": "system", "content": skills.WEBAPP_GUIDANCE})
|
||||
|
||||
# Mémoire en notes : en mode « always », on injecte les notes liées à la
|
||||
# demande. Rien n'est deviné — ce sont des notes que l'agent a
|
||||
# lui-même écrites, et le bloc dit explicitement de les ignorer si
|
||||
# elles ne s'appliquent pas.
|
||||
memory_mode = cfg.get("memory_mode", "ondemand")
|
||||
# Mode off : les outils n'existent pas pour le modèle (principe AJEAN).
|
||||
# En mode Plan (lecture seule), la consultation reste permise mais pas
|
||||
# l'écriture d'une note.
|
||||
memory_tools = (
|
||||
[] if memory_mode == "off"
|
||||
else ["memory_search"] if req.mode == "plan"
|
||||
else ["memory_search", "memory_save"]
|
||||
)
|
||||
if memory_mode == "always":
|
||||
block = memory_notes.recall_block(req.content)
|
||||
if block:
|
||||
convo.insert(1, {"role": "system", "content": block})
|
||||
|
||||
if plan:
|
||||
yield _sse("plan", {"steps": plan})
|
||||
|
||||
@@ -504,7 +524,7 @@ async def chat(req: ChatRequest) -> StreamingResponse:
|
||||
model,
|
||||
convo,
|
||||
options=agent_config.ollama_options(cfg),
|
||||
enabled_tools=agent_config.enabled_tool_names(cfg),
|
||||
enabled_tools=agent_config.enabled_tool_names(cfg) + memory_tools,
|
||||
confirm_shell=cfg.get("confirm_shell", True),
|
||||
think=cfg.get("think", True),
|
||||
keep_alive=cfg.get("keep_alive", "30m"),
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
"""Routes de lecture et mise à jour de la configuration de l'agent."""
|
||||
from __future__ import annotations
|
||||
|
||||
from fastapi import APIRouter
|
||||
from fastapi import APIRouter, HTTPException
|
||||
from pydantic import BaseModel
|
||||
|
||||
from .. import agent_config
|
||||
@@ -10,6 +10,13 @@ router = APIRouter(prefix="/api/config", tags=["config"])
|
||||
|
||||
|
||||
class ConfigPatch(BaseModel):
|
||||
"""Champs modifiables. DOIT couvrir agent_config.PROFILE_FIELDS.
|
||||
|
||||
Pydantic ignore silencieusement un champ non déclaré : un oubli ici fait
|
||||
« sauter » le réglage sans la moindre erreur (c'était le cas de
|
||||
keep_alive, skills_enabled et ponytail).
|
||||
"""
|
||||
|
||||
system_prompt: str | None = None
|
||||
temperature: float | None = None
|
||||
top_p: float | None = None
|
||||
@@ -26,6 +33,10 @@ class ConfigPatch(BaseModel):
|
||||
self_review: bool | None = None
|
||||
rag_enabled: bool | None = None
|
||||
embed_model: str | None = None
|
||||
memory_mode: str | None = None
|
||||
skills_enabled: bool | None = None
|
||||
ponytail: bool | None = None
|
||||
keep_alive: str | None = None
|
||||
|
||||
|
||||
@router.get("")
|
||||
@@ -40,3 +51,36 @@ async def get_config(model: str | None = None) -> dict:
|
||||
async def put_config(patch: ConfigPatch, model: str | None = None) -> dict:
|
||||
cfg = agent_config.save_config(patch.model_dump(exclude_none=True), model)
|
||||
return {"config": cfg}
|
||||
|
||||
|
||||
# ── Presets : jeux de réglages nommés, commutables (idée reprise d'AJEAN) ──
|
||||
class PresetBody(BaseModel):
|
||||
name: str
|
||||
|
||||
|
||||
@router.get("/presets")
|
||||
async def get_presets() -> dict:
|
||||
return {"presets": agent_config.list_presets()}
|
||||
|
||||
|
||||
@router.post("/presets")
|
||||
async def post_preset(body: PresetBody, model: str | None = None) -> dict:
|
||||
"""Enregistre la configuration courante sous un nom."""
|
||||
try:
|
||||
return {"presets": agent_config.save_preset(body.name, model)}
|
||||
except ValueError as exc:
|
||||
raise HTTPException(400, str(exc)) from exc
|
||||
|
||||
|
||||
@router.post("/presets/apply")
|
||||
async def apply_preset(body: PresetBody, model: str | None = None) -> dict:
|
||||
"""Applique un preset à la configuration courante."""
|
||||
cfg = agent_config.apply_preset(body.name, model)
|
||||
if cfg is None:
|
||||
raise HTTPException(404, "preset introuvable")
|
||||
return {"config": cfg}
|
||||
|
||||
|
||||
@router.delete("/presets")
|
||||
async def remove_preset(name: str) -> dict:
|
||||
return {"presets": agent_config.delete_preset(name)}
|
||||
Reference in new issue
Block a user