dsh-biocore 0.2.0 → 0.3.0
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/README.md +11 -0
- package/lib/core-worker.js +268 -0
- package/lib/index.js +160 -70
- package/lib/modules/autonomy.js +125 -0
- package/lib/modules/cache-stats.js +48 -0
- package/lib/modules/core-turn.js +24 -6
- package/lib/modules/expression-inject.js +0 -10
- package/lib/modules/expression-refresh.js +0 -9
- package/lib/modules/memory-capture.js +2 -1
- package/lib/modules/memory-inject.js +50 -5
- package/lib/modules/memory-tools.js +7 -4
- package/lib/modules/python-env.js +46 -0
- package/lib/modules/safety-gate.js +0 -3
- package/lib/modules/series.js +54 -0
- package/lib/modules/switch.js +16 -0
- package/lib/py-bridge.js +81 -15
- package/package.json +1 -1
- package/python/biocore_memory/core/config.py +138 -127
- package/python/biocore_memory/engine.py +2653 -2476
- package/python/biocore_memory/recall/embedding.py +129 -0
- package/python/biocore_memory/recall/feedback.py +202 -0
- package/python/biocore_model/preference_drift.py +255 -0
- package/python/biocore_model/tests/__init__.py +1 -0
- package/python/biocore_model/tests/core_mode_ui_check.mjs +63 -0
- package/python/biocore_model/tests/core_status_check.mjs +29 -0
- package/python/biocore_model/tests/dom_safe_check.mjs +22 -0
- package/python/biocore_model/tests/leak_samples_run.py +18 -0
- package/python/biocore_model/tests/orb_ring_check.mjs +20 -0
- package/python/biocore_model/tests/panel_html_check.mjs +63 -0
- package/python/biocore_model/tests/persona_flow_check.mjs +17 -0
- package/python/biocore_model/tests/render_guard_check.mjs +19 -0
- package/python/biocore_model/tests/switch_groups_check.mjs +94 -0
- package/python/biocore_model/tests/tabs_state_check.mjs +24 -0
- package/python/biocore_model/tests/test_affective_labor.py +102 -0
- package/python/biocore_model/tests/test_appraisal.py +137 -0
- package/python/biocore_model/tests/test_appraisal_action.py +55 -0
- package/python/biocore_model/tests/test_arch_convergence.py +35 -0
- package/python/biocore_model/tests/test_attachment_behavior.py +95 -0
- package/python/biocore_model/tests/test_attachment_regulation.py +63 -0
- package/python/biocore_model/tests/test_attachment_state.py +29 -0
- package/python/biocore_model/tests/test_attribution.py +56 -0
- package/python/biocore_model/tests/test_attributor.py +108 -0
- package/python/biocore_model/tests/test_autonomic.py +69 -0
- package/python/biocore_model/tests/test_autonomy_accumulator.py +84 -0
- package/python/biocore_model/tests/test_autonomy_affect.py +62 -0
- package/python/biocore_model/tests/test_autonomy_blame.py +44 -0
- package/python/biocore_model/tests/test_autonomy_caps.py +29 -0
- package/python/biocore_model/tests/test_autonomy_check.py +40 -0
- package/python/biocore_model/tests/test_autonomy_clingy.py +41 -0
- package/python/biocore_model/tests/test_autonomy_cooldown_selfcontact.py +138 -0
- package/python/biocore_model/tests/test_autonomy_frustration.py +42 -0
- package/python/biocore_model/tests/test_autonomy_hourly_limit.py +46 -0
- package/python/biocore_model/tests/test_autonomy_loop.py +64 -0
- package/python/biocore_model/tests/test_autonomy_mode.py +37 -0
- package/python/biocore_model/tests/test_autonomy_model.py +60 -0
- package/python/biocore_model/tests/test_autonomy_model_v2.py +68 -0
- package/python/biocore_model/tests/test_autonomy_module_text.py +104 -0
- package/python/biocore_model/tests/test_autonomy_params.py +163 -0
- package/python/biocore_model/tests/test_autonomy_reason.py +34 -0
- package/python/biocore_model/tests/test_autonomy_self.py +153 -0
- package/python/biocore_model/tests/test_autonomy_send_gate.py +38 -0
- package/python/biocore_model/tests/test_autonomy_silence.py +37 -0
- package/python/biocore_model/tests/test_autonomy_status.py +35 -0
- package/python/biocore_model/tests/test_autonomy_test_trigger.py +24 -0
- package/python/biocore_model/tests/test_autonomy_tick.py +148 -0
- package/python/biocore_model/tests/test_autonomy_v4.py +64 -0
- package/python/biocore_model/tests/test_autonomy_v5.py +43 -0
- package/python/biocore_model/tests/test_autonomy_v7.py +48 -0
- package/python/biocore_model/tests/test_autonomy_v_theta.py +85 -0
- package/python/biocore_model/tests/test_batch1_wiring.py +231 -0
- package/python/biocore_model/tests/test_batch2_wiring.py +231 -0
- package/python/biocore_model/tests/test_batch3_wiring.py +223 -0
- package/python/biocore_model/tests/test_bio_tick.py +202 -0
- package/python/biocore_model/tests/test_boundary.py +183 -0
- package/python/biocore_model/tests/test_boundary_gate.py +295 -0
- package/python/biocore_model/tests/test_brain_split.py +99 -0
- package/python/biocore_model/tests/test_co_recall.py +350 -0
- package/python/biocore_model/tests/test_commitment_extractor.py +197 -0
- package/python/biocore_model/tests/test_compassion_fatigue.py +84 -0
- package/python/biocore_model/tests/test_connection_bids.py +69 -0
- package/python/biocore_model/tests/test_core_bridge.py +88 -0
- package/python/biocore_model/tests/test_core_bridge_episode.py +14 -0
- package/python/biocore_model/tests/test_core_bridge_forget.py +17 -0
- package/python/biocore_model/tests/test_core_bridge_identity.py +15 -0
- package/python/biocore_model/tests/test_core_bridge_media.py +17 -0
- package/python/biocore_model/tests/test_core_bridge_relation.py +17 -0
- package/python/biocore_model/tests/test_core_bridge_summary.py +17 -0
- package/python/biocore_model/tests/test_core_client.py +156 -0
- package/python/biocore_model/tests/test_core_commands.py +146 -0
- package/python/biocore_model/tests/test_core_mode.py +161 -0
- package/python/biocore_model/tests/test_core_state.py +240 -0
- package/python/biocore_model/tests/test_criticality.py +61 -0
- package/python/biocore_model/tests/test_cross_session_copy.py +67 -0
- package/python/biocore_model/tests/test_debug_values.py +169 -0
- package/python/biocore_model/tests/test_defense_mechanisms.py +177 -0
- package/python/biocore_model/tests/test_delta_budget.py +157 -0
- package/python/biocore_model/tests/test_diag_trace.py +201 -0
- package/python/biocore_model/tests/test_directives_compat.py +698 -0
- package/python/biocore_model/tests/test_dissonance.py +97 -0
- package/python/biocore_model/tests/test_drift_sign.py +87 -0
- package/python/biocore_model/tests/test_embedding_fallback.py +277 -0
- package/python/biocore_model/tests/test_embedding_local.py +279 -0
- package/python/biocore_model/tests/test_embedding_mode.py +175 -0
- package/python/biocore_model/tests/test_embedding_modes.py +342 -0
- package/python/biocore_model/tests/test_emotion_geometry.py +114 -0
- package/python/biocore_model/tests/test_emotion_probe.py +214 -0
- package/python/biocore_model/tests/test_emotion_probe_concurrency.py +128 -0
- package/python/biocore_model/tests/test_entity_graph.py +198 -0
- package/python/biocore_model/tests/test_entity_resolve.py +446 -0
- package/python/biocore_model/tests/test_envy.py +173 -0
- package/python/biocore_model/tests/test_exit_or_silence.py +209 -0
- package/python/biocore_model/tests/test_expr_guard.py +1 -0
- package/python/biocore_model/tests/test_expr_intent.py +66 -0
- package/python/biocore_model/tests/test_expr_state.py +66 -0
- package/python/biocore_model/tests/test_expr_template.py +71 -0
- package/python/biocore_model/tests/test_expr_tone.py +41 -0
- package/python/biocore_model/tests/test_extreme_scenarios.py +147 -0
- package/python/biocore_model/tests/test_feedback_flow.py +185 -0
- package/python/biocore_model/tests/test_feedback_guard.py +65 -0
- package/python/biocore_model/tests/test_feedback_llm.py +59 -0
- package/python/biocore_model/tests/test_feedback_llm_inferred.py +140 -0
- package/python/biocore_model/tests/test_feedback_memory.py +63 -0
- package/python/biocore_model/tests/test_feedback_user.py +61 -0
- package/python/biocore_model/tests/test_frustration_curve.py +66 -0
- package/python/biocore_model/tests/test_global_bio_state.py +116 -0
- package/python/biocore_model/tests/test_governance.py +181 -0
- package/python/biocore_model/tests/test_granularity.py +133 -0
- package/python/biocore_model/tests/test_graph_extract.py +224 -0
- package/python/biocore_model/tests/test_graph_recall.py +226 -0
- package/python/biocore_model/tests/test_graph_sqlite.py +298 -0
- package/python/biocore_model/tests/test_growth.py +301 -0
- package/python/biocore_model/tests/test_intent_render.py +60 -0
- package/python/biocore_model/tests/test_intero_regulation.py +224 -0
- package/python/biocore_model/tests/test_intero_style.py +146 -0
- package/python/biocore_model/tests/test_item3_numeric.py +105 -0
- package/python/biocore_model/tests/test_item6_numeric.py +116 -0
- package/python/biocore_model/tests/test_layered_memory.py +174 -0
- package/python/biocore_model/tests/test_leak_guard.py +75 -0
- package/python/biocore_model/tests/test_learning_probe.py +93 -0
- package/python/biocore_model/tests/test_life_loop.py +222 -0
- package/python/biocore_model/tests/test_life_loop_10s.py +131 -0
- package/python/biocore_model/tests/test_local_embedding_health.py +258 -0
- package/python/biocore_model/tests/test_memory_affect_encoding.py +79 -0
- package/python/biocore_model/tests/test_memory_backend.py +332 -0
- package/python/biocore_model/tests/test_memory_dedup.py +316 -0
- package/python/biocore_model/tests/test_memory_dual.py +219 -0
- package/python/biocore_model/tests/test_memory_dual_wiring.py +219 -0
- package/python/biocore_model/tests/test_memory_feedback.py +241 -0
- package/python/biocore_model/tests/test_memory_fixes.py +84 -0
- package/python/biocore_model/tests/test_memory_forgetting.py +58 -0
- package/python/biocore_model/tests/test_memory_inject.py +211 -0
- package/python/biocore_model/tests/test_memory_mood_recall.py +118 -0
- package/python/biocore_model/tests/test_memory_store.py +372 -0
- package/python/biocore_model/tests/test_metacog.py +103 -0
- package/python/biocore_model/tests/test_migrate.py +245 -0
- package/python/biocore_model/tests/test_migrate_store.py +194 -0
- package/python/biocore_model/tests/test_mind_wander.py +65 -0
- package/python/biocore_model/tests/test_mood.py +35 -0
- package/python/biocore_model/tests/test_narrative.py +114 -0
- package/python/biocore_model/tests/test_needs_esteem.py +51 -0
- package/python/biocore_model/tests/test_needs_events.py +205 -0
- package/python/biocore_model/tests/test_needs_sleep.py +54 -0
- package/python/biocore_model/tests/test_noise_bank.py +54 -0
- package/python/biocore_model/tests/test_numeric_schema.py +98 -0
- package/python/biocore_model/tests/test_output_protocol.py +65 -0
- package/python/biocore_model/tests/test_page_debug.py +147 -0
- package/python/biocore_model/tests/test_page_status_autonomy.py +215 -0
- package/python/biocore_model/tests/test_pipeline.py +345 -0
- package/python/biocore_model/tests/test_pipeline_api.py +192 -0
- package/python/biocore_model/tests/test_pipeline_mode.py +222 -0
- package/python/biocore_model/tests/test_plugin_config.py +85 -0
- package/python/biocore_model/tests/test_policy_learning.py +116 -0
- package/python/biocore_model/tests/test_possible_selves.py +88 -0
- package/python/biocore_model/tests/test_predictor.py +227 -0
- package/python/biocore_model/tests/test_preference_drift.py +142 -0
- package/python/biocore_model/tests/test_preset.py +142 -0
- package/python/biocore_model/tests/test_proactive_context.py +70 -0
- package/python/biocore_model/tests/test_proactive_history.py +90 -0
- package/python/biocore_model/tests/test_proactive_memory.py +141 -0
- package/python/biocore_model/tests/test_proactive_send_record.py +88 -0
- package/python/biocore_model/tests/test_probe_contract.py +71 -0
- package/python/biocore_model/tests/test_prospective.py +154 -0
- package/python/biocore_model/tests/test_provider_fallback.py +89 -0
- package/python/biocore_model/tests/test_reconstruction.py +111 -0
- package/python/biocore_model/tests/test_regulation_strategies.py +228 -0
- package/python/biocore_model/tests/test_relation_stage.py +114 -0
- package/python/biocore_model/tests/test_relationship_stage.py +59 -0
- package/python/biocore_model/tests/test_release_140.py +55 -0
- package/python/biocore_model/tests/test_repair_after_boundary.py +249 -0
- package/python/biocore_model/tests/test_reply_limit.py +86 -0
- package/python/biocore_model/tests/test_reply_limit_persist.py +48 -0
- package/python/biocore_model/tests/test_reply_split.py +52 -0
- package/python/biocore_model/tests/test_reply_split_astrbot.py +66 -0
- package/python/biocore_model/tests/test_reply_split_inject.py +53 -0
- package/python/biocore_model/tests/test_rerank.py +229 -0
- package/python/biocore_model/tests/test_rollback.py +175 -0
- package/python/biocore_model/tests/test_runtime.py +175 -0
- package/python/biocore_model/tests/test_runtime_active.py +205 -0
- package/python/biocore_model/tests/test_runtime_pipeline.py +1583 -0
- package/python/biocore_model/tests/test_runtime_proactive.py +231 -0
- package/python/biocore_model/tests/test_scheduler.py +79 -0
- package/python/biocore_model/tests/test_secrecy.py +105 -0
- package/python/biocore_model/tests/test_self_esteem.py +45 -0
- package/python/biocore_model/tests/test_self_intent.py +347 -0
- package/python/biocore_model/tests/test_self_interest.py +183 -0
- package/python/biocore_model/tests/test_self_layer_wiring.py +198 -0
- package/python/biocore_model/tests/test_self_verification.py +113 -0
- package/python/biocore_model/tests/test_situation.py +142 -0
- package/python/biocore_model/tests/test_sleep_circadian.py +41 -0
- package/python/biocore_model/tests/test_social_pain.py +49 -0
- package/python/biocore_model/tests/test_social_repair.py +67 -0
- package/python/biocore_model/tests/test_social_theory.py +78 -0
- package/python/biocore_model/tests/test_source_monitoring.py +204 -0
- package/python/biocore_model/tests/test_speak_like_group.py +36 -0
- package/python/biocore_model/tests/test_stages.py +226 -0
- package/python/biocore_model/tests/test_state_tag.py +86 -0
- package/python/biocore_model/tests/test_template_expr.py +65 -0
- package/python/biocore_model/tests/test_text_inference.py +70 -0
- package/python/biocore_model/tests/test_theta_params.py +115 -0
- package/python/biocore_model/tests/test_topic_control.py +200 -0
- package/python/biocore_model/tests/test_turn_memory.py +105 -0
- package/python/biocore_model/tests/test_turn_memory_panel.py +174 -0
- package/python/biocore_model/tests/test_ui_settings.py +84 -0
- package/python/biocore_model/tests/test_ui_settings_authority.py +179 -0
- package/python/biocore_model/tests/test_user_appraisal_async.py +155 -0
- package/python/biocore_model/tests/test_user_classify.py +122 -0
- package/python/biocore_model/tests/test_value_order.py +117 -0
- package/python/biocore_model/tests/test_voice_bank.py +105 -0
- package/python/biocore_model/tests/test_waiting_sense.py +216 -0
- package/python/biocore_model/tests/test_x_directives.py +101 -0
- package/python/biocore_model/tests/ui_compat_check.mjs +22 -0
- package/python/biocore_model/tests/view_mode_check.mjs +17 -0
- package/python/biocore_runtime/autonomy.py +208 -0
- package/python/biocore_runtime/core_state.py +302 -0
- package/python/biocore_runtime/governance.py +495 -0
- package/python/biocore_runtime/growth.py +528 -0
- package/python/biocore_runtime/pipeline.py +850 -23
- package/python/biocore_runtime/relationship_state.py +214 -0
- package/python/biocore_runtime/self_layer.py +103 -0
- package/python/biocore_runtime/service.py +162 -0
- package/python/biocore_runtime/standby.py +472 -0
- package/ui/panel-layout.js +83 -39
- package/ui/panel.css +885 -743
- package/ui/panel.html +356 -296
- package/ui/panel.js +739 -865
|
@@ -0,0 +1,211 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
"""task-221 (batch 2): query recall injection + embedding stats persistence."""
|
|
3
|
+
import json
|
|
4
|
+
import os
|
|
5
|
+
import tempfile
|
|
6
|
+
import unittest
|
|
7
|
+
from pathlib import Path
|
|
8
|
+
|
|
9
|
+
from biocore_memory.recall import embedding
|
|
10
|
+
from biocore_runtime import pipeline
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def entry(i, text, ts, importance=0.5):
|
|
14
|
+
return {"id": i, "text": text, "role": "user", "ts": ts,
|
|
15
|
+
"importance": importance, "hits": 1, "scope": "default",
|
|
16
|
+
"memory_confidence": 0.7, "memory_source": "direct"}
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class InjectCase(unittest.TestCase):
|
|
20
|
+
def setUp(self):
|
|
21
|
+
self.tmp = tempfile.TemporaryDirectory()
|
|
22
|
+
self.root = Path(self.tmp.name)
|
|
23
|
+
self.store = self.root / "data" / "memory"
|
|
24
|
+
self.entries = [
|
|
25
|
+
entry("new", "今天买了咖啡", 1700000300.0),
|
|
26
|
+
entry("old", "上次说的苹果很甜", 1700000100.0),
|
|
27
|
+
entry("mid", "明天要去公园", 1700000200.0),
|
|
28
|
+
]
|
|
29
|
+
(self.store / "sessions" / "s1").mkdir(parents=True)
|
|
30
|
+
(self.store / "sessions" / "s1" / "memory.json").write_text(
|
|
31
|
+
json.dumps({"entries": self.entries}), encoding="utf-8")
|
|
32
|
+
|
|
33
|
+
def tearDown(self):
|
|
34
|
+
self.tmp.cleanup()
|
|
35
|
+
|
|
36
|
+
def inject(self, **kwargs):
|
|
37
|
+
return pipeline.inject_block("s1", str(self.store), **kwargs)
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
class QueryModeTest(InjectCase):
|
|
41
|
+
def test_query_recall_orders_by_query(self):
|
|
42
|
+
out = self.inject(query="苹果", mode="query", top_k=3)
|
|
43
|
+
self.assertEqual(out["ids"][0], "old")
|
|
44
|
+
|
|
45
|
+
def test_recent_mode_keeps_newest_first(self):
|
|
46
|
+
out = self.inject(query="苹果", mode="recent", top_k=3)
|
|
47
|
+
self.assertEqual(out["ids"][0], "new")
|
|
48
|
+
|
|
49
|
+
def test_default_mode_is_query(self):
|
|
50
|
+
out = self.inject(query="苹果", top_k=2)
|
|
51
|
+
self.assertEqual(out["ids"][0], "old")
|
|
52
|
+
self.assertEqual(len(out["ids"]), 2)
|
|
53
|
+
|
|
54
|
+
def test_empty_query_falls_back_recent(self):
|
|
55
|
+
out = self.inject(query="", mode="query", top_k=3)
|
|
56
|
+
self.assertEqual(out["ids"][0], "new")
|
|
57
|
+
|
|
58
|
+
def test_engine_failure_falls_back_recent(self):
|
|
59
|
+
saved = pipeline._inject_engine
|
|
60
|
+
pipeline._inject_engine = lambda store, entries: (_ for _ in ()).throw(RuntimeError("boom"))
|
|
61
|
+
try:
|
|
62
|
+
out = self.inject(query="苹果", mode="query", top_k=2)
|
|
63
|
+
finally:
|
|
64
|
+
pipeline._inject_engine = saved
|
|
65
|
+
self.assertEqual(out["ids"][0], "new")
|
|
66
|
+
self.assertEqual(len(out["ids"]), 2)
|
|
67
|
+
|
|
68
|
+
def test_output_format_and_budget(self):
|
|
69
|
+
out = self.inject(query="苹果", mode="query", top_k=3, max_chars=60)
|
|
70
|
+
self.assertTrue(out["text"].startswith("你记得这些事:"))
|
|
71
|
+
self.assertTrue(all(line.startswith("- ") for line in out["text"].split("\n")[1:]))
|
|
72
|
+
self.assertLessEqual(out["chars"], 60)
|
|
73
|
+
self.assertEqual(out["chars"], len(out["text"]))
|
|
74
|
+
self.assertTrue(set(out["ids"]).issubset({"new", "old", "mid"}))
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
class RerankTest(InjectCase):
|
|
78
|
+
def test_rerank_reorders_when_enabled(self):
|
|
79
|
+
class FakeRerank:
|
|
80
|
+
def __init__(self):
|
|
81
|
+
self.calls = 0
|
|
82
|
+
|
|
83
|
+
def rerank_documents(self, query, documents, config):
|
|
84
|
+
self.calls += 1
|
|
85
|
+
return [{"index": 2}, {"index": 0}, {"index": 1}]
|
|
86
|
+
|
|
87
|
+
fake = FakeRerank()
|
|
88
|
+
saved = pipeline._rerank_mod
|
|
89
|
+
pipeline._rerank_mod = fake
|
|
90
|
+
try:
|
|
91
|
+
out = self.inject(query="苹果", mode="query", top_k=8,
|
|
92
|
+
rerank_config={"rerank_enabled": True})
|
|
93
|
+
finally:
|
|
94
|
+
pipeline._rerank_mod = saved
|
|
95
|
+
self.assertEqual(fake.calls, 1)
|
|
96
|
+
# engine put 苹果 (old) first; rerank's order must win instead
|
|
97
|
+
self.assertEqual(out["ids"], ["mid", "old", "new"])
|
|
98
|
+
|
|
99
|
+
def test_rerank_off_does_not_call(self):
|
|
100
|
+
class Exploding:
|
|
101
|
+
def rerank_documents(self, *args, **kwargs):
|
|
102
|
+
raise AssertionError("rerank must not run")
|
|
103
|
+
|
|
104
|
+
saved = pipeline._rerank_mod
|
|
105
|
+
pipeline._rerank_mod = Exploding()
|
|
106
|
+
try:
|
|
107
|
+
out = self.inject(query="苹果", mode="query", top_k=8,
|
|
108
|
+
rerank_config={"rerank_enabled": False})
|
|
109
|
+
finally:
|
|
110
|
+
pipeline._rerank_mod = saved
|
|
111
|
+
self.assertEqual(out["ids"][0], "old")
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
class EmbeddingFallbackTest(InjectCase):
|
|
115
|
+
def test_embedding_provider_failure_degrades_to_lexical(self):
|
|
116
|
+
class BadProvider:
|
|
117
|
+
def embed_text(self, text):
|
|
118
|
+
raise RuntimeError("no network")
|
|
119
|
+
|
|
120
|
+
from biocore_memory.engine import MemoryEngine
|
|
121
|
+
from biocore_memory.store.json_store import MemoryStore
|
|
122
|
+
|
|
123
|
+
def engine_with_bad_embedder(store, entries):
|
|
124
|
+
memory = MemoryStore(doc={"entries": [dict(item) for item in entries]})
|
|
125
|
+
return MemoryEngine(store=memory, embedder=BadProvider(),
|
|
126
|
+
config={"flags": {}})
|
|
127
|
+
|
|
128
|
+
saved = pipeline._inject_engine
|
|
129
|
+
pipeline._inject_engine = engine_with_bad_embedder
|
|
130
|
+
try:
|
|
131
|
+
out = self.inject(query="苹果", mode="query", top_k=3)
|
|
132
|
+
finally:
|
|
133
|
+
pipeline._inject_engine = saved
|
|
134
|
+
self.assertEqual(out["ids"][0], "old")
|
|
135
|
+
|
|
136
|
+
def test_learning_flags_default_on(self):
|
|
137
|
+
captured = {}
|
|
138
|
+
|
|
139
|
+
class CaptureEngine:
|
|
140
|
+
def __init__(self, **kwargs):
|
|
141
|
+
captured.update(kwargs)
|
|
142
|
+
|
|
143
|
+
def recall(self, query, budget=5):
|
|
144
|
+
return []
|
|
145
|
+
|
|
146
|
+
saved = pipeline._inject_engine
|
|
147
|
+
saved_engine = None
|
|
148
|
+
try:
|
|
149
|
+
from biocore_memory.engine import MemoryEngine
|
|
150
|
+
saved_engine = MemoryEngine
|
|
151
|
+
import biocore_memory.engine as engine_module
|
|
152
|
+
engine_module.MemoryEngine = CaptureEngine
|
|
153
|
+
try:
|
|
154
|
+
pipeline.inject_block("s1", str(self.store), query="苹果")
|
|
155
|
+
finally:
|
|
156
|
+
engine_module.MemoryEngine = saved_engine
|
|
157
|
+
finally:
|
|
158
|
+
pipeline._inject_engine = saved
|
|
159
|
+
flags = (captured.get("config") or {}).get("flags") or {}
|
|
160
|
+
for key in ("mood_recall", "affect_encoding",
|
|
161
|
+
"memory_feedback", "preference_drift"):
|
|
162
|
+
self.assertIs(flags.get(key), True)
|
|
163
|
+
|
|
164
|
+
|
|
165
|
+
class EmbedStatsTest(unittest.TestCase):
|
|
166
|
+
def setUp(self):
|
|
167
|
+
self.tmp = tempfile.TemporaryDirectory()
|
|
168
|
+
self.dir = Path(self.tmp.name)
|
|
169
|
+
self.stats_file = self.dir / "embed_stats.json"
|
|
170
|
+
self.cache_dir = self.dir / "cache"
|
|
171
|
+
self._env = {k: os.environ.get(k) for k in
|
|
172
|
+
("BIOCORE_EMBED_STATS_FILE", "BIOCORE_EMBEDDING_CACHE_FILE")}
|
|
173
|
+
os.environ["BIOCORE_EMBED_STATS_FILE"] = str(self.stats_file)
|
|
174
|
+
os.environ["BIOCORE_EMBEDDING_CACHE_FILE"] = str(self.cache_dir)
|
|
175
|
+
embedding.embed_stats(reset=True)
|
|
176
|
+
|
|
177
|
+
def tearDown(self):
|
|
178
|
+
for key, value in self._env.items():
|
|
179
|
+
if value is None:
|
|
180
|
+
os.environ.pop(key, None)
|
|
181
|
+
else:
|
|
182
|
+
os.environ[key] = value
|
|
183
|
+
self.tmp.cleanup()
|
|
184
|
+
|
|
185
|
+
def test_miss_remote_then_disk_hit(self):
|
|
186
|
+
class Provider:
|
|
187
|
+
def embed_text(self, text):
|
|
188
|
+
return [0.1] * 8
|
|
189
|
+
|
|
190
|
+
self.assertIsNotNone(embedding.embed("hello world", Provider()))
|
|
191
|
+
first = json.loads(self.stats_file.read_text(encoding="utf-8"))
|
|
192
|
+
self.assertEqual(first["miss"], 1)
|
|
193
|
+
self.assertEqual(first["remote"], 1)
|
|
194
|
+
self.assertEqual(first["disk_hit"], 0)
|
|
195
|
+
embedding.embed("hello world", Provider())
|
|
196
|
+
second = json.loads(self.stats_file.read_text(encoding="utf-8"))
|
|
197
|
+
self.assertEqual(second["disk_hit"], 1)
|
|
198
|
+
|
|
199
|
+
def test_stats_action_reads_persisted_file(self):
|
|
200
|
+
self.stats_file.write_text(json.dumps(
|
|
201
|
+
{"hit": 2, "disk_hit": 3, "miss": 4, "remote": 5,
|
|
202
|
+
"local": 6, "fail": 7}), encoding="utf-8")
|
|
203
|
+
store = self.dir / "memory"
|
|
204
|
+
out = pipeline.dispatch("stats", {"store": str(store)})
|
|
205
|
+
self.assertEqual(out["embed"]["disk_hit"], 3)
|
|
206
|
+
self.assertEqual(out["embed"]["miss"], 4)
|
|
207
|
+
self.assertEqual(out["embed"]["remote"], 5)
|
|
208
|
+
|
|
209
|
+
|
|
210
|
+
if __name__ == "__main__":
|
|
211
|
+
unittest.main()
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
import json, os, tempfile, unittest
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
from biocore_memory.engine import MemoryEngine
|
|
5
|
+
from biocore_memory.core import config as engine_config
|
|
6
|
+
|
|
7
|
+
def entry(i, text, affect=None, importance=0.5, ts=1700000000.0):
|
|
8
|
+
value = {"id": i, "text": text, "role": "user", "ts": ts, "importance": importance,
|
|
9
|
+
"vector": [], "hits": 1, "self_relevance": 0.0, "pin": False,
|
|
10
|
+
"memory_source": "direct", "memory_confidence": 0.7,
|
|
11
|
+
"previous_versions": [], "module": "biocore_memory", "scope": "default"}
|
|
12
|
+
if affect is not None:
|
|
13
|
+
value["affect"] = affect
|
|
14
|
+
return value
|
|
15
|
+
|
|
16
|
+
class MoodRecallTest(unittest.TestCase):
|
|
17
|
+
def setUp(self):
|
|
18
|
+
self.tmp = tempfile.TemporaryDirectory()
|
|
19
|
+
self.dir = Path(self.tmp.name)
|
|
20
|
+
self._saved = {k: os.environ.get(k) for k in ("BIOCORE_MOOD_RECALL", "BIOCORE_MOOD_LAMBDA")}
|
|
21
|
+
os.environ.pop("BIOCORE_MOOD_RECALL", None)
|
|
22
|
+
os.environ.pop("BIOCORE_MOOD_LAMBDA", None)
|
|
23
|
+
|
|
24
|
+
def tearDown(self):
|
|
25
|
+
self.tmp.cleanup()
|
|
26
|
+
for k, v in self._saved.items():
|
|
27
|
+
if v is None: os.environ.pop(k, None)
|
|
28
|
+
else: os.environ[k] = v
|
|
29
|
+
|
|
30
|
+
def write(self, entries):
|
|
31
|
+
(self.dir / "memory.json").write_text(json.dumps({"entries": entries, "tombstones": [], "schema_version": 1}), encoding="utf-8")
|
|
32
|
+
|
|
33
|
+
def engine(self, affect=None):
|
|
34
|
+
return MemoryEngine(data_dir=str(self.dir), affect=affect)
|
|
35
|
+
|
|
36
|
+
def test_flag_defaults_off(self):
|
|
37
|
+
self.assertIs(engine_config.FLAGS.get("mood_recall"), False)
|
|
38
|
+
|
|
39
|
+
def test_write_stores_affect(self):
|
|
40
|
+
e = self.engine(affect={"v": 0.7, "a": 0.8})
|
|
41
|
+
result = e.remember({"text": "affect write probe"})
|
|
42
|
+
self.assertIsNotNone(result)
|
|
43
|
+
self.assertIn("affect", result)
|
|
44
|
+
self.assertAlmostEqual(result["affect"]["valence"], 0.7, places=3)
|
|
45
|
+
self.assertAlmostEqual(result["affect"]["arousal"], 0.8, places=3)
|
|
46
|
+
|
|
47
|
+
def test_off_is_baseline(self):
|
|
48
|
+
self.write([entry("h", "beach happy", {"valence": 0.9, "arousal": 0.8}),
|
|
49
|
+
entry("s", "beach sad", {"valence": -0.9, "arousal": 0.2})])
|
|
50
|
+
rows = self.engine(affect={"v": 0.9, "a": 0.8}).recall("beach", budget=3)
|
|
51
|
+
self.assertEqual(len(rows), 2)
|
|
52
|
+
happy = next(r for r in rows if r["entry"]["id"] == "h")
|
|
53
|
+
sad = next(r for r in rows if r["entry"]["id"] == "s")
|
|
54
|
+
self.assertAlmostEqual(float(happy["score"]), float(sad["score"]), places=4)
|
|
55
|
+
|
|
56
|
+
def test_mood_on_ranks_matching_affect_higher(self):
|
|
57
|
+
os.environ["BIOCORE_MOOD_RECALL"] = "1"
|
|
58
|
+
self.write([entry("h", "beach happy", {"valence": 0.9, "arousal": 0.8}),
|
|
59
|
+
entry("s", "beach sad", {"valence": -0.9, "arousal": 0.2})])
|
|
60
|
+
rows = self.engine(affect={"v": 0.9, "a": 0.8}).recall("beach", budget=3)
|
|
61
|
+
self.assertEqual(rows[0]["entry"]["id"], "h")
|
|
62
|
+
happy = next(r for r in rows if r["entry"]["id"] == "h")
|
|
63
|
+
sad = next(r for r in rows if r["entry"]["id"] == "s")
|
|
64
|
+
self.assertGreater(float(happy["score"]), float(sad["score"]))
|
|
65
|
+
self.assertGreater(happy.get("mood_cos", 0), sad.get("mood_cos", 0))
|
|
66
|
+
|
|
67
|
+
def test_mood_lambda_zero_neutralizes_affect_term(self):
|
|
68
|
+
os.environ["BIOCORE_MOOD_RECALL"] = "1"
|
|
69
|
+
os.environ["BIOCORE_MOOD_LAMBDA"] = "0"
|
|
70
|
+
self.write([entry("h", "beach happy", {"valence": 0.9, "arousal": 0.8}),
|
|
71
|
+
entry("s", "beach sad", {"valence": -0.9, "arousal": 0.2})])
|
|
72
|
+
rows = self.engine(affect={"v": 0.9, "a": 0.8}).recall("beach", budget=3)
|
|
73
|
+
happy = next(r for r in rows if r["entry"]["id"] == "h")
|
|
74
|
+
sad = next(r for r in rows if r["entry"]["id"] == "s")
|
|
75
|
+
self.assertAlmostEqual(float(happy["score"]), float(sad["score"]), places=4)
|
|
76
|
+
|
|
77
|
+
def test_missing_affect_falls_back_neutral(self):
|
|
78
|
+
os.environ["BIOCORE_MOOD_RECALL"] = "1"
|
|
79
|
+
self.write([entry("n", "beach plain"), entry("h", "beach happy", {"valence": 0.9, "arousal": 0.8})])
|
|
80
|
+
rows = self.engine(affect={"v": 0.9, "a": 0.8}).recall("beach", budget=3)
|
|
81
|
+
neutral = next(r for r in rows if r["entry"]["id"] == "n")
|
|
82
|
+
self.assertIn("mood_cos", neutral)
|
|
83
|
+
self.assertTrue(float(neutral["score"]) >= 0.0)
|
|
84
|
+
self.assertLess(float(neutral["score"]), next(r for r in rows if r["entry"]["id"] == "h")["score"] + 1e-6)
|
|
85
|
+
|
|
86
|
+
def test_scores_finite_and_ordered(self):
|
|
87
|
+
os.environ["BIOCORE_MOOD_RECALL"] = "1"
|
|
88
|
+
self.write([entry("h", "beach happy", {"valence": 0.5, "arousal": 0.5}),
|
|
89
|
+
entry("s", "beach sad", {"valence": -0.5, "arousal": 0.5})])
|
|
90
|
+
rows = self.engine(affect={"v": 0.5, "a": 0.5}).recall("beach", budget=3)
|
|
91
|
+
scores = [float(r["score"]) for r in rows]
|
|
92
|
+
self.assertTrue(all(x == x and x >= 0 for x in scores))
|
|
93
|
+
self.assertEqual(scores, sorted(scores, reverse=True))
|
|
94
|
+
|
|
95
|
+
def test_stability_repeated_recall(self):
|
|
96
|
+
os.environ["BIOCORE_MOOD_RECALL"] = "1"
|
|
97
|
+
self.write([entry("h", "beach happy", {"valence": 0.9, "arousal": 0.8}),
|
|
98
|
+
entry("s", "beach sad", {"valence": -0.9, "arousal": 0.2})])
|
|
99
|
+
e = self.engine(affect={"v": 0.9, "a": 0.8})
|
|
100
|
+
first = [r["entry"]["id"] for r in e.recall("beach", budget=3)]
|
|
101
|
+
second = [r["entry"]["id"] for r in e.recall("beach", budget=3)]
|
|
102
|
+
self.assertEqual(first, second)
|
|
103
|
+
|
|
104
|
+
def test_mood_mismatch_lower(self):
|
|
105
|
+
os.environ["BIOCORE_MOOD_RECALL"] = "1"
|
|
106
|
+
self.write([entry("m", "beach mixed", {"valence": 0.0, "arousal": 0.5}),
|
|
107
|
+
entry("h", "beach happy", {"valence": 0.9, "arousal": 0.8})])
|
|
108
|
+
rows = self.engine(affect={"v": 0.9, "a": 0.8}).recall("beach", budget=3)
|
|
109
|
+
mixed = next(r for r in rows if r["entry"]["id"] == "m")
|
|
110
|
+
happy = next(r for r in rows if r["entry"]["id"] == "h")
|
|
111
|
+
self.assertGreater(float(happy["score"]), float(mixed["score"]))
|
|
112
|
+
|
|
113
|
+
def test_no_affect_field_no_crash(self):
|
|
114
|
+
os.environ["BIOCORE_MOOD_RECALL"] = "1"
|
|
115
|
+
self.write([{"id": "bare", "text": "beach bare", "ts": 1700000000.0, "importance": 0.5, "hits": 1}])
|
|
116
|
+
rows = self.engine(affect=None).recall("beach", budget=3)
|
|
117
|
+
self.assertEqual(len(rows), 1)
|
|
118
|
+
self.assertIn("score", rows[0])
|