память: синк без записи в стор, круги из событий (не из проекции), калибровка порогов 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")
|
KINDS = ("conv", "out", "task", "digest")
|
||||||
BUDGET = {2: (8, 5000), 3: (15, 12000)} # глубина → (записей, символов)
|
BUDGET = {2: (8, 5000), 3: (15, 12000)} # глубина → (записей, символов)
|
||||||
SYNC_BATCH = 400
|
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()
|
_LOCK = threading.Lock()
|
||||||
_LAST = {} # уровень → последнее решение о глубине (для DepthChosen)
|
_LAST = {} # уровень → последнее решение о глубине (для DepthChosen)
|
||||||
|
|
||||||
|
|
@ -140,7 +144,15 @@ def _sync_once(limit: int, log=None) -> tuple:
|
||||||
(last, limit)).fetchall()
|
(last, limit)).fetchall()
|
||||||
if not rows:
|
if not rows:
|
||||||
return 0, 0
|
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'")
|
senders = json.loads((db.execute("SELECT v FROM meta WHERE k='senders'")
|
||||||
.fetchone() or ["{}"])[0])
|
.fetchone() or ["{}"])[0])
|
||||||
senders = {k: set(v) for k, v in senders.items()}
|
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),
|
chat_circle(d.get("chat_id"), model, senders),
|
||||||
"Коди", t[:1500]))
|
"Коди", t[:1500]))
|
||||||
elif et == "TaskCompleted":
|
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 "")
|
ans = (d.get("answer") or d.get("result") or "")
|
||||||
if t.get("title") or ans:
|
if t.get("title") or ans:
|
||||||
pending.append((f"task:{aid}", "task", ts, "", trust.norm(t.get("circle")),
|
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()
|
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=()):
|
def _conv_item(src, db, aid, d, ts, model, senders, pending=()):
|
||||||
row = src.execute("SELECT data FROM events WHERE aggregate='inbox' AND aggregate_id=? "
|
row = src.execute("SELECT data FROM events WHERE aggregate='inbox' AND aggregate_id=? "
|
||||||
"AND event_type='MessageReceived' LIMIT 1", (aid,)).fetchone()
|
"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:
|
except Exception as e:
|
||||||
return f"ПАМЯТЬ: поиск недоступен ({str(e)[:80]}) — отвечай из контекста тика."
|
return f"ПАМЯТЬ: поиск недоступен ({str(e)[:80]}) — отвечай из контекста тика."
|
||||||
lines, used = [], 0
|
lines, used = [], 0
|
||||||
|
top = hits[0]["score"] if hits else 0.0
|
||||||
for h in hits:
|
for h in hits:
|
||||||
if h["score"] < MIN_SCORE:
|
if h["score"] < MIN_SCORE or h["score"] < top - REL_WINDOW:
|
||||||
continue
|
continue
|
||||||
line = f"- [{h['kind']} {h['ts'][:10]}] {h['text'][:900]}"
|
line = f"- [{h['kind']} {h['ts'][:10]}] {h['text'][:900]}"
|
||||||
if used + len(line) > budget:
|
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")
|
os.environ["SIMBIOT_CORE_DIR"] = os.path.join(_tmp, "core")
|
||||||
for _v in ("SIMBIOT_MEMORY", "SIMBIOT_DECISION"):
|
for _v in ("SIMBIOT_MEMORY", "SIMBIOT_DECISION"):
|
||||||
os.environ.pop(_v, None)
|
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
|
import numpy as np
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue