Rollen-Ausbau: kritiker (GLM-4.7-Flash) + Gedaechtnis-Reranker (Qwen3-Reranker-0.6B)

Ergebnis des Rollen-Audits 07.07. (User: 'das beste, aber sinnvollste'):

(1) KRITIKER: fremdblick-Prosa + Chef-Gutachter-Vertretung liefen auf der
9B-Vision-GLM, obwohl es reine Text-Jobs sind. Neu: GLM-4.7-Flash
(30B-A3B, MIT, Unsloth UD-Q4_K_XL, 17,5 GB, on-demand ttl 600, Alias
'kritiker'). Gleicher Fremd-Vendor wie bisher -> die 'andere Brille'
des Kritiker-Konzepts bleibt. 4.6V-Flash bleibt scout (Bild-Jobs).

(2) RERANKER (die eine echte Rollen-Luecke): Mem0-Suche war reine
Vektor-Aehnlichkeit. Neu: Qwen3-Reranker-0.6B (q8_0, Mungert-GGUF —
Community-Konvertierungen liefern bekannt Nullscores) via llama-swap
/v1/rerank ordnet die Top-20 Kandidaten nach echter Relevanz um.
NUR die Reihenfolge aendert sich: score bleibt Vektor-Score (Hermes-
Plugin-Filter RECALL_MIN_SCORE bleibt kalibriert), rerank_score kommt
als neues Feld dazu; jeder Fehler -> lautlos Vektor-Reihenfolge.
Env: MC_RERANK_ENABLED/_URL/_MODEL/_TIMEOUT/_CANDIDATES. In brains
(verdraengungssicher, ~0,7 GB).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Hitonabi
2026-07-07 22:21:57 +02:00
parent f88ed4e5df
commit 88ec8b9121
4 changed files with 71 additions and 4 deletions
+41
View File
@@ -46,6 +46,17 @@ API_KEY = os.environ.get("MEM0_API_KEY", "sk-local") # llama.cpp ignoriert den
LLM_MAX_TOKENS = int(os.environ.get("MEM0_LLM_MAX_TOKENS", "2048"))
SEARCH_TOP_K = int(os.environ.get("MEM0_SEARCH_TOP_K", "50"))
# --- Rerank-Zweitstufe (07.07.2026): Die Vektor-Suche findet ÄHNLICHES, der Reranker ordnet
# nach ECHTER Relevanz (Qwen3-Reranker via llama-swap /v1/rerank). Es ändert sich NUR die
# Reihenfolge: `score` bleibt der Vektor-Score (RECALL_MIN_SCORE im Hermes-Plugin bleibt
# kalibriert), Konsumenten, die vorne abschneiden (RECALL_LIMIT), bekommen automatisch die
# relevantesten Fakten. Jeder Fehler → lautlos Vektor-Reihenfolge (Gedächtnis bricht NIE).
RERANK_ENABLED = os.environ.get("MC_RERANK_ENABLED", "1") != "0"
RERANK_URL = os.environ.get("MC_RERANK_URL", "http://127.0.0.1:8080/v1/rerank")
RERANK_MODEL = os.environ.get("MC_RERANK_MODEL", "Qwen3-Reranker-0.6B")
RERANK_TIMEOUT = float(os.environ.get("MC_RERANK_TIMEOUT", "8"))
RERANK_CANDIDATES = int(os.environ.get("MC_RERANK_CANDIDATES", "20")) # nur die Top-N neu ordnen (Latenz)
# Kategorien = fundierte Memory-Taxonomie (semantisch/prozedural/episodisch), bewusst knapp (4).
# Mem0-OSS kann nicht nativ klassifizieren (Cloud-Feature) → wir lassen das Hirn beim Lernen
# einordnen (classify_facts). Die Beschreibungen steuern die Treffsicherheit der Auto-Zuordnung.
@@ -195,12 +206,42 @@ def health() -> dict:
raise HTTPException(503, f"mem0 nicht bereit: {exc}")
def _rerank(query: str, items: list[dict]) -> list[dict]:
"""Suchtreffer nach echter Relevanz umordnen; bei jedem Fehler unverändert zurück."""
if not (RERANK_ENABLED and len(items) > 1):
return items
try:
import httpx
r = httpx.post(RERANK_URL, json={
"model": RERANK_MODEL,
"query": query,
"documents": [i.get("content", "") for i in items],
"top_n": len(items),
}, timeout=RERANK_TIMEOUT)
r.raise_for_status()
results = r.json().get("results") or []
ranked = []
for s in sorted(results, key=lambda x: x.get("relevance_score", 0.0), reverse=True):
idx = s.get("index")
if isinstance(idx, int) and 0 <= idx < len(items):
item = dict(items[idx])
item["rerank_score"] = round(float(s.get("relevance_score", 0.0)), 4)
ranked.append(item)
# Nur übernehmen, wenn der Reranker ALLE Kandidaten bewertet hat (sonst Verlustgefahr).
return ranked if len(ranked) == len(items) else items
except Exception as exc: # noqa: BLE001 — Gedächtnis darf am Reranker nie scheitern
log.debug("rerank übersprungen: %s", exc)
return items
@app.get("/memory")
def list_memory(q: str = "", category: str = "") -> list[dict]:
"""q gesetzt → semantische Suche (mit Relevanz-Score). Sonst → alle Fakten."""
if q:
res = mem().search(q, filters={"user_id": USER_ID}, top_k=SEARCH_TOP_K)
items = [_to_item(r, r.get("score")) for r in res.get("results", [])]
# Zweitstufe: die vorderen Kandidaten nach echter Relevanz ordnen (Rest bleibt hinten dran).
items = _rerank(q, items[:RERANK_CANDIDATES]) + items[RERANK_CANDIDATES:]
else:
res = mem().get_all(filters={"user_id": USER_ID}, top_k=1000)
items = [_to_item(r) for r in res.get("results", [])]