dsh-biocore 0.5.1 → 0.5.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/index.js +10 -0
- package/lib/py/bridge.py +98 -0
- package/package.json +1 -1
- package/python/biocore_memory/adapters/dsh_cli.py +56 -2
- package/python/biocore_memory/core/config.py +274 -142
- package/python/biocore_memory/engine.py +3346 -2738
- package/python/biocore_memory/engine_helpers.py +15 -1
- package/python/biocore_memory/event/activation.py +118 -0
- package/python/biocore_memory/event/chains.py +112 -0
- package/python/biocore_memory/event/claims.py +486 -0
- package/python/biocore_memory/event/compaction.py +240 -0
- package/python/biocore_memory/event/consistency.py +10 -99
- package/python/biocore_memory/event/event_graph.py +100 -0
- package/python/biocore_memory/event/events.py +926 -0
- package/python/biocore_memory/event/facts.py +150 -0
- package/python/biocore_memory/event/json_tolerant.py +73 -0
- package/python/biocore_memory/event/keynorm.py +380 -0
- package/python/biocore_memory/event/rolling.py +211 -0
- package/python/biocore_memory/event/segment.py +164 -0
- package/python/biocore_memory/event/segment_adaptive.py +114 -0
- package/python/biocore_memory/event/timerender.py +68 -0
- package/python/biocore_memory/evict/consequence.py +10 -14
- package/python/biocore_memory/index/entities.py +11 -51
- package/python/biocore_memory/recall/activation.py +144 -0
- package/python/biocore_memory/recall/api.py +5 -2
- package/python/biocore_memory/recall/cover.py +134 -0
- package/python/biocore_memory/recall/embedding.py +555 -50
- package/python/biocore_memory/recall/point_expand.py +169 -0
- package/python/biocore_memory/recall/providers.py +442 -0
- package/python/biocore_memory/recall/segments.py +111 -0
- package/python/biocore_memory/space.py +967 -24
- package/python/biocore_memory/space_learning.py +347 -0
- package/python/biocore_memory/store/dedupe.py +5 -42
- package/python/biocore_memory/store/json_store.py +81 -2
- package/python/biocore_memory/store/near_dup.py +250 -0
- package/python/biocore_memory/store/sql_store.py +665 -0
- package/python/biocore_memory/store/versioning.py +291 -0
- package/python/biocore_model/entity_graph.py +13 -7
- package/python/biocore_model/tests/conftest.py +58 -0
- package/python/biocore_model/tests/test_appraisal.py +23 -9
- package/python/biocore_model/tests/test_appraisal_v2.py +140 -0
- package/python/biocore_model/tests/test_batch_b.py +310 -0
- package/python/biocore_model/tests/test_batch_d.py +244 -0
- package/python/biocore_model/tests/test_batch_e.py +210 -0
- package/python/biocore_model/tests/test_batch_registry.py +272 -0
- package/python/biocore_model/tests/test_bio_tick.py +50 -5
- package/python/biocore_model/tests/test_damping_needs_split.py +144 -0
- package/python/biocore_model/tests/test_dimensions.py +249 -0
- package/python/biocore_model/tests/test_dimensions_conflict.py +192 -0
- package/python/biocore_model/tests/test_expression.py +254 -0
- package/python/biocore_model/tests/test_governance.py +180 -181
- package/python/biocore_model/tests/test_learning_eval.py +142 -0
- package/python/biocore_model/tests/test_module_monitor.py +172 -0
- package/python/biocore_model/tests/test_paper_alignment.py +138 -0
- package/python/biocore_model/tests/test_perception_batch.py +371 -0
- package/python/biocore_model/tests/test_perception_batch_a2.py +304 -0
- package/python/biocore_model/tests/test_proposal_split.py +214 -0
- package/python/biocore_model/tests/test_runtime_pipeline.py +3 -6
- package/python/biocore_model/tests/test_source_split.py +170 -0
- package/python/biocore_model/tests/test_space.py +240 -0
- package/python/biocore_model/tests/test_spatial_recall.py +288 -0
- package/python/biocore_model/tests/test_surprise_priority.py +143 -0
- package/python/biocore_model/tests/test_timescales.py +181 -0
- package/python/biocore_runtime/extensions.py +2 -2
- package/python/biocore_runtime/pipeline.py +3996 -183
- package/python/biocore_runtime/rerank.py +51 -84
- package/python/biocore_model/tests/test_autonomy_self.py +0 -153
- package/python/biocore_model/tests/test_memory_inject.py +0 -211
- package/python/data/signals.json +0 -15307
package/lib/index.js
CHANGED
|
@@ -385,7 +385,17 @@ function coreConfigPayload(dataDir) {
|
|
|
385
385
|
try { switches = readSwitches(dataDir).switches } catch { switches = {} }
|
|
386
386
|
let persona = ''
|
|
387
387
|
try { persona = readPersonaText(dataDir) } catch { persona = '' }
|
|
388
|
+
// Keys this host does not own (summarizer, recall, anything the core or a
|
|
389
|
+
// human added) must survive a re-configure. Dropping them made capture raise
|
|
390
|
+
// "no summarizer block" and memory vanished silently behind an adapter
|
|
391
|
+
// fallback, so carry the previous file forward and overwrite only our keys.
|
|
392
|
+
let carried = {}
|
|
393
|
+
try {
|
|
394
|
+
const previous = JSON.parse(readFileSync(join(dataDir, 'core_config.json'), 'utf8'))
|
|
395
|
+
if (previous && typeof previous === 'object' && !Array.isArray(previous)) carried = previous
|
|
396
|
+
} catch { carried = {} }
|
|
388
397
|
return {
|
|
398
|
+
...carried,
|
|
389
399
|
data_dir: dataDir,
|
|
390
400
|
persona_text: persona,
|
|
391
401
|
embedding: config.embedding,
|
package/lib/py/bridge.py
CHANGED
|
@@ -256,12 +256,90 @@ def _json_arg(value):
|
|
|
256
256
|
return {}
|
|
257
257
|
|
|
258
258
|
|
|
259
|
+
_SUMMARIZER_STORE = [""]
|
|
260
|
+
|
|
261
|
+
|
|
262
|
+
def _deepseek_key():
|
|
263
|
+
"""API key for the summariser: env first, then the DSH credential store."""
|
|
264
|
+
key = str(os.environ.get("DEEPSEEK_API_KEY") or "").strip()
|
|
265
|
+
if key:
|
|
266
|
+
return key
|
|
267
|
+
try:
|
|
268
|
+
import re
|
|
269
|
+
text = open(str(_DSH_HOME / ".credentials.yaml"), encoding="utf-8").read()
|
|
270
|
+
hit = re.search(r"DEEPSEEK_API_KEY:\s*(\S+)", text)
|
|
271
|
+
if hit:
|
|
272
|
+
return hit.group(1).strip().strip('"').strip("'")
|
|
273
|
+
except Exception:
|
|
274
|
+
pass
|
|
275
|
+
return ""
|
|
276
|
+
|
|
277
|
+
|
|
278
|
+
def _summarizer_config(store):
|
|
279
|
+
"""The summariser block of the store's core_config.json (no silent default)."""
|
|
280
|
+
for name in ("core_config.json", "config.json"):
|
|
281
|
+
path = os.path.join(str(store or ""), name)
|
|
282
|
+
if not os.path.isfile(path):
|
|
283
|
+
continue
|
|
284
|
+
try:
|
|
285
|
+
data = json.loads(open(path, encoding="utf-8").read())
|
|
286
|
+
except Exception:
|
|
287
|
+
continue
|
|
288
|
+
block = data.get("summarizer") if isinstance(data, dict) else None
|
|
289
|
+
if isinstance(block, dict) and str(block.get("model") or "").strip():
|
|
290
|
+
return {"base_url": str(block.get("base_url") or block.get("baseUrl")
|
|
291
|
+
or "https://api.deepseek.com/v1"),
|
|
292
|
+
"model": str(block["model"]).strip(),
|
|
293
|
+
"key": str(block.get("key") or block.get("api_key") or "").strip()}
|
|
294
|
+
raise RuntimeError("summarizer config missing: core_config.json has no summarizer block "
|
|
295
|
+
"(the event layer cannot close events without it)")
|
|
296
|
+
|
|
297
|
+
|
|
298
|
+
def _llm_summarizer(prompt):
|
|
299
|
+
"""Event-close summariser for the real plugin path.
|
|
300
|
+
|
|
301
|
+
capture() only closes events when it is handed a summariser; without one the
|
|
302
|
+
event layer buffers forever and no event ever exists (the production path had
|
|
303
|
+
exactly that hole). This uses the host's own model over HTTPS.
|
|
304
|
+
"""
|
|
305
|
+
cfg = _summarizer_config(_SUMMARIZER_STORE[0])
|
|
306
|
+
key = cfg["key"] or _deepseek_key()
|
|
307
|
+
if not key:
|
|
308
|
+
raise RuntimeError("summarizer config has no key and DEEPSEEK_API_KEY is unset")
|
|
309
|
+
import urllib.request
|
|
310
|
+
model = cfg["model"]
|
|
311
|
+
base = cfg["base_url"]
|
|
312
|
+
body = json.dumps({
|
|
313
|
+
"model": model,
|
|
314
|
+
"temperature": 0,
|
|
315
|
+
"messages": [{"role": "system", "content": "只输出 JSON 对象,不要解释。"},
|
|
316
|
+
{"role": "user", "content": str(prompt)}],
|
|
317
|
+
}).encode("utf-8")
|
|
318
|
+
req = urllib.request.Request(base.rstrip("/") + "/chat/completions", data=body,
|
|
319
|
+
headers={"Content-Type": "application/json",
|
|
320
|
+
"Authorization": "Bearer " + key})
|
|
321
|
+
try:
|
|
322
|
+
with urllib.request.urlopen(req, timeout=120) as resp:
|
|
323
|
+
data = json.loads(resp.read().decode("utf-8", "replace"))
|
|
324
|
+
return str((data.get("choices") or [{}])[0].get("message", {}).get("content") or "")
|
|
325
|
+
except Exception as exc:
|
|
326
|
+
sys.stderr.write("__bioSummarizer failed: %s\n" % str(exc)[:160])
|
|
327
|
+
return None
|
|
328
|
+
|
|
329
|
+
|
|
259
330
|
def _memory_capture(argv):
|
|
260
331
|
store = argv[1]
|
|
261
332
|
session_id = argv[2]
|
|
262
333
|
role = argv[3] if len(argv) > 3 else "user"
|
|
263
334
|
text = argv[4] if len(argv) > 4 else ""
|
|
264
335
|
config = _json_arg(argv[5] if len(argv) > 5 else "")
|
|
336
|
+
config = dict(config) if isinstance(config, dict) else {}
|
|
337
|
+
_SUMMARIZER_STORE[0] = str(store or "")
|
|
338
|
+
if not config.get("event_summarizer"):
|
|
339
|
+
# the host never supplied one; without it no event is ever generated.
|
|
340
|
+
# _llm_summarizer raises when core_config.json has no summarizer block.
|
|
341
|
+
_summarizer_config(store)
|
|
342
|
+
config["event_summarizer"] = _llm_summarizer
|
|
265
343
|
result = _pipeline().capture(session_id, role, text, store, config)
|
|
266
344
|
decision = result.get("decision") if isinstance(result, dict) else {}
|
|
267
345
|
decision = decision if isinstance(decision, dict) else {}
|
|
@@ -276,6 +354,10 @@ def _memory_capture(argv):
|
|
|
276
354
|
|
|
277
355
|
|
|
278
356
|
def _memory_inject(argv):
|
|
357
|
+
return _memory_inject_impl(argv)
|
|
358
|
+
|
|
359
|
+
|
|
360
|
+
def _memory_inject_impl(argv):
|
|
279
361
|
store = argv[1]
|
|
280
362
|
session_id = argv[2]
|
|
281
363
|
kwargs = {}
|
|
@@ -313,9 +395,25 @@ def _runtime_gate(argv):
|
|
|
313
395
|
return _emit(_pipeline().gate_reply(reply, payload))
|
|
314
396
|
|
|
315
397
|
|
|
398
|
+
_EVENT_ACTIONS = ("memory_capture", "memory_inject")
|
|
399
|
+
|
|
400
|
+
|
|
316
401
|
def _runtime_action(argv):
|
|
317
402
|
action = argv[1] if len(argv) > 1 else ""
|
|
318
403
|
payload = _json_arg(argv[2] if len(argv) > 2 else "")
|
|
404
|
+
if action in _EVENT_ACTIONS and isinstance(payload, dict):
|
|
405
|
+
# the real node path calls dispatch(); it must carry the summariser too,
|
|
406
|
+
# or the event layer stays silently off (capture) / raises (render)
|
|
407
|
+
payload = dict(payload)
|
|
408
|
+
store = str(payload.get("store") or payload.get("root") or "")
|
|
409
|
+
_SUMMARIZER_STORE[0] = store
|
|
410
|
+
if str(os.environ.get("BIOCORE_STRICT_TS") or "") == "1" \
|
|
411
|
+
and action == "memory_capture" and not payload.get("now"):
|
|
412
|
+
raise RuntimeError("capture payload has no logical timestamp (now); "
|
|
413
|
+
"BIOCORE_STRICT_TS forbids falling back to the wall clock")
|
|
414
|
+
if not payload.get("event_summarizer"):
|
|
415
|
+
_summarizer_config(store)
|
|
416
|
+
payload["event_summarizer"] = _llm_summarizer
|
|
319
417
|
return _emit(_pipeline().dispatch(action, payload))
|
|
320
418
|
|
|
321
419
|
|
package/package.json
CHANGED
|
@@ -50,7 +50,15 @@ def dispatch(engine, action, **kwargs):
|
|
|
50
50
|
if action == "recall":
|
|
51
51
|
return engine.recall(kwargs.get("query", ""),
|
|
52
52
|
budget=kwargs.get("budget", 3),
|
|
53
|
-
vector=kwargs.get("vector")
|
|
53
|
+
vector=kwargs.get("vector"),
|
|
54
|
+
mode=kwargs.get("mode", "now"))
|
|
55
|
+
if action in ("spatial_rank", "rank"):
|
|
56
|
+
from ..core.config import SPATIAL_SELECT_DEPTH
|
|
57
|
+
return engine.spatial_rank(
|
|
58
|
+
kwargs.get("query", ""),
|
|
59
|
+
query_ts=kwargs.get("query_ts"),
|
|
60
|
+
limit=kwargs.get("limit", SPATIAL_SELECT_DEPTH),
|
|
61
|
+
scope=kwargs.get("scope", "default"))
|
|
54
62
|
if action == "forget":
|
|
55
63
|
return engine.forget(kwargs.get("entry_id", ""))
|
|
56
64
|
if action == "pin":
|
|
@@ -130,10 +138,27 @@ def cmd_recall(args):
|
|
|
130
138
|
"tier": "expand" if score >= 0.55 else "hint",
|
|
131
139
|
"role": entry.get("role"), "ts": entry.get("ts")})
|
|
132
140
|
return _emit({"ok": True, "command": "recall", "query": args.query,
|
|
141
|
+
"mode": getattr(args, "mode", "now") or "now",
|
|
142
|
+
"spatial_select": getattr(engine, "_last_spatial_select", None),
|
|
133
143
|
"count": len(out), "results": out,
|
|
134
144
|
"hint": "no_cue" if not out else ""})
|
|
135
145
|
|
|
136
146
|
|
|
147
|
+
def cmd_rank(args):
|
|
148
|
+
"""Whole-store spatial selection (engine.spatial_rank), JSON out."""
|
|
149
|
+
engine = _engine_class()(data_dir=args.store)
|
|
150
|
+
try:
|
|
151
|
+
limit = int(args.limit) if args.limit is not None else None
|
|
152
|
+
except Exception:
|
|
153
|
+
limit = None
|
|
154
|
+
scope = str(getattr(args, "scope", "") or "").strip() or None
|
|
155
|
+
payload = engine.spatial_rank(args.query,
|
|
156
|
+
query_ts=getattr(args, "ts", None),
|
|
157
|
+
limit=limit, scope=scope)
|
|
158
|
+
return _emit({"ok": bool(payload.get("ok")), "command": "rank",
|
|
159
|
+
"result": payload})
|
|
160
|
+
|
|
161
|
+
|
|
137
162
|
def cmd_forget(args):
|
|
138
163
|
engine = _engine_class()(data_dir=args.store)
|
|
139
164
|
result = engine.forget(args.entry_id)
|
|
@@ -233,6 +258,20 @@ def cmd_export(args):
|
|
|
233
258
|
return _emit({"ok": True, "command": "export", "payload": payload})
|
|
234
259
|
|
|
235
260
|
|
|
261
|
+
def cmd_reembed(args):
|
|
262
|
+
"""Rebuild every vector in the store with the configured provider.
|
|
263
|
+
|
|
264
|
+
The supported way out of an embedding-space mismatch: the store is read
|
|
265
|
+
through the runtime pipeline (sqlite primary), every entry is re-embedded,
|
|
266
|
+
event centroids are re-derived and the meta is re-stamped.
|
|
267
|
+
"""
|
|
268
|
+
from biocore_runtime import pipeline as P
|
|
269
|
+
session = str(getattr(args, "session", "") or "default")
|
|
270
|
+
result = P.reembed_store(args.store, session,
|
|
271
|
+
batch_size=int(getattr(args, "batch_size", 0) or 32))
|
|
272
|
+
return _emit(dict(result, command="reembed"))
|
|
273
|
+
|
|
274
|
+
|
|
236
275
|
def cmd_stats(args):
|
|
237
276
|
engine = _engine_class()(data_dir=args.store)
|
|
238
277
|
return _emit({"ok": True, "command": "stats", "monitor": engine.monitor()})
|
|
@@ -285,8 +324,17 @@ def build_parser():
|
|
|
285
324
|
r = sp.add_parser("recall", help="query memories")
|
|
286
325
|
r.add_argument("query")
|
|
287
326
|
r.add_argument("--limit", type=int, default=3)
|
|
288
|
-
r.add_argument("--mode", default="now", choices=("now", "then"))
|
|
327
|
+
r.add_argument("--mode", default="now", choices=("now", "then", "spatial"))
|
|
289
328
|
r.set_defaults(func=cmd_recall)
|
|
329
|
+
r = sp.add_parser("rank",
|
|
330
|
+
help="top-N over the whole store by five-axis distance")
|
|
331
|
+
r.add_argument("query")
|
|
332
|
+
r.add_argument("--limit", type=int, default=None)
|
|
333
|
+
r.add_argument("--ts", type=float, default=None,
|
|
334
|
+
help="anchor the time axis at this epoch (default now)")
|
|
335
|
+
r.add_argument("--scope", default="",
|
|
336
|
+
help="scope filter (default: every scope in the store)")
|
|
337
|
+
r.set_defaults(func=cmd_rank)
|
|
290
338
|
r = sp.add_parser("forget", help="delete by id")
|
|
291
339
|
r.add_argument("entry_id")
|
|
292
340
|
r.set_defaults(func=cmd_forget)
|
|
@@ -319,6 +367,12 @@ def build_parser():
|
|
|
319
367
|
r.set_defaults(func=cmd_snapshot)
|
|
320
368
|
r = sp.add_parser("consolidate-run", help="run gated consolidation")
|
|
321
369
|
r.set_defaults(func=cmd_consolidate_run)
|
|
370
|
+
r = sp.add_parser("reembed",
|
|
371
|
+
help="rebuild vectors after changing the embedding model/dim")
|
|
372
|
+
r.add_argument("--store", required=True)
|
|
373
|
+
r.add_argument("--session", default="default")
|
|
374
|
+
r.add_argument("--batch-size", dest="batch_size", type=int, default=32)
|
|
375
|
+
r.set_defaults(func=cmd_reembed)
|
|
322
376
|
return p
|
|
323
377
|
|
|
324
378
|
|