память: синк без записи в стор, круги из событий (не из проекции), калибровка порогов nomic
- бэкфилл индекса шёл 13 мин при 4 с CPU: _sync_once звал run_projections() (пишет в стор) и ждал блокировку живой лестницы — теперь только чтение; - круги людей — из событий TrustSet, задачи — из своих событий: отставшая проекция дала бы дефолтный круг 4 — ошибка в сторону утечки; - пороги релевантности по прод-индексу (nomic): релевантное 0.78–0.88, посторонний запрос ≤ 0.75 → MIN_SCORE 0.76 + окно 0.07 от лучшего; «рецепт борща» → пусто, «миграция Gennady» → нужная задача; - прод: индекс 1650 разговоров (личка 1 / правление 2 / агенты 3), 270 задач, 91 дайджест; поиск круга 2 не отдаёт записей круга 1. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
parent
969d57f14a
commit
d06a4e45bc
2 changed files with 34 additions and 4 deletions
|
|
@ -42,7 +42,11 @@ except ImportError:
|
|||
KINDS = ("conv", "out", "task", "digest")
|
||||
BUDGET = {2: (8, 5000), 3: (15, 12000)} # глубина → (записей, символов)
|
||||
SYNC_BATCH = 400
|
||||
MIN_SCORE = float(os.environ.get("SIMBIOT_MEMORY_MIN_SCORE", "0.45")) # ниже — шум
|
||||
# калибровка на прод-индексе (nomic, 2026-10-17): релевантное 0.78–0.88,
|
||||
# посторонний запрос («рецепт борща») — шум ≤ 0.75 → абсолютный порог 0.76 +
|
||||
# окно от лучшего совпадения (берём только близкое к лучшему)
|
||||
MIN_SCORE = float(os.environ.get("SIMBIOT_MEMORY_MIN_SCORE", "0.76"))
|
||||
REL_WINDOW = float(os.environ.get("SIMBIOT_MEMORY_REL_WINDOW", "0.07"))
|
||||
_LOCK = threading.Lock()
|
||||
_LAST = {} # уровень → последнее решение о глубине (для DepthChosen)
|
||||
|
||||
|
|
@ -140,7 +144,15 @@ def _sync_once(limit: int, log=None) -> tuple:
|
|||
(last, limit)).fetchall()
|
||||
if not rows:
|
||||
return 0, 0
|
||||
model = es.run_projections()
|
||||
# круги — прямо из событий TrustSet, только чтение: run_projections()
|
||||
# пишет в стор и ждал блокировку живой лестницы (бэкфилл 13 мин
|
||||
# при 4 с CPU), а отставшая проекция дала бы дефолтный круг 4 —
|
||||
# ошибку в сторону утечки (запись видна шире, чем надо)
|
||||
tmap = {}
|
||||
for (td,) in src.execute("SELECT data FROM events WHERE event_type='TrustSet' ORDER BY id"):
|
||||
td = json.loads(td)
|
||||
tmap[td["subject"]] = {"circle": td["circle"]}
|
||||
model = {"trust": tmap}
|
||||
senders = json.loads((db.execute("SELECT v FROM meta WHERE k='senders'")
|
||||
.fetchone() or ["{}"])[0])
|
||||
senders = {k: set(v) for k, v in senders.items()}
|
||||
|
|
@ -175,7 +187,7 @@ def _sync_once(limit: int, log=None) -> tuple:
|
|||
chat_circle(d.get("chat_id"), model, senders),
|
||||
"Коди", t[:1500]))
|
||||
elif et == "TaskCompleted":
|
||||
t = (model.get("tasks") or {}).get(aid) or {}
|
||||
t = _task_info(src, aid)
|
||||
ans = (d.get("answer") or d.get("result") or "")
|
||||
if t.get("title") or ans:
|
||||
pending.append((f"task:{aid}", "task", ts, "", trust.norm(t.get("circle")),
|
||||
|
|
@ -205,6 +217,20 @@ def _sync_once(limit: int, log=None) -> tuple:
|
|||
db.close()
|
||||
|
||||
|
||||
def _task_info(src, tid: str) -> dict:
|
||||
"""Заголовок и круг задачи — из её событий (по индексу агрегата)."""
|
||||
t = {}
|
||||
for et, d in src.execute("SELECT event_type, data FROM events WHERE aggregate='task' "
|
||||
"AND aggregate_id=? AND event_type IN ('TaskSubmitted','TaskCircleSet') "
|
||||
"ORDER BY id", (tid,)):
|
||||
d = json.loads(d)
|
||||
if et == "TaskSubmitted":
|
||||
t.update(title=d.get("title"), circle=d.get("circle"))
|
||||
else:
|
||||
t["circle"] = d.get("circle")
|
||||
return t
|
||||
|
||||
|
||||
def _conv_item(src, db, aid, d, ts, model, senders, pending=()):
|
||||
row = src.execute("SELECT data FROM events WHERE aggregate='inbox' AND aggregate_id=? "
|
||||
"AND event_type='MessageReceived' LIMIT 1", (aid,)).fetchone()
|
||||
|
|
@ -334,8 +360,9 @@ def context_block(depth: int, level: str, inbox: list, batch: list, circle: int)
|
|||
except Exception as e:
|
||||
return f"ПАМЯТЬ: поиск недоступен ({str(e)[:80]}) — отвечай из контекста тика."
|
||||
lines, used = [], 0
|
||||
top = hits[0]["score"] if hits else 0.0
|
||||
for h in hits:
|
||||
if h["score"] < MIN_SCORE:
|
||||
if h["score"] < MIN_SCORE or h["score"] < top - REL_WINDOW:
|
||||
continue
|
||||
line = f"- [{h['kind']} {h['ts'][:10]}] {h['text'][:900]}"
|
||||
if used + len(line) > budget:
|
||||
|
|
|
|||
|
|
@ -27,6 +27,9 @@ os.environ["SIMBIOT_MEMORY_DIR"] = os.path.join(_tmp, "memory")
|
|||
os.environ["SIMBIOT_CORE_DIR"] = os.path.join(_tmp, "core")
|
||||
for _v in ("SIMBIOT_MEMORY", "SIMBIOT_DECISION"):
|
||||
os.environ.pop(_v, None)
|
||||
# «мешок слов» даёт оценки ниже nomic — пороги под него (калибровка nomic — в коде)
|
||||
os.environ["SIMBIOT_MEMORY_MIN_SCORE"] = "0.2"
|
||||
os.environ["SIMBIOT_MEMORY_REL_WINDOW"] = "1.0"
|
||||
|
||||
import numpy as np
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue