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.
Files changed (245) hide show
  1. package/README.md +11 -0
  2. package/lib/core-worker.js +268 -0
  3. package/lib/index.js +160 -70
  4. package/lib/modules/autonomy.js +125 -0
  5. package/lib/modules/cache-stats.js +48 -0
  6. package/lib/modules/core-turn.js +24 -6
  7. package/lib/modules/expression-inject.js +0 -10
  8. package/lib/modules/expression-refresh.js +0 -9
  9. package/lib/modules/memory-capture.js +2 -1
  10. package/lib/modules/memory-inject.js +50 -5
  11. package/lib/modules/memory-tools.js +7 -4
  12. package/lib/modules/python-env.js +46 -0
  13. package/lib/modules/safety-gate.js +0 -3
  14. package/lib/modules/series.js +54 -0
  15. package/lib/modules/switch.js +16 -0
  16. package/lib/py-bridge.js +81 -15
  17. package/package.json +1 -1
  18. package/python/biocore_memory/core/config.py +138 -127
  19. package/python/biocore_memory/engine.py +2653 -2476
  20. package/python/biocore_memory/recall/embedding.py +129 -0
  21. package/python/biocore_memory/recall/feedback.py +202 -0
  22. package/python/biocore_model/preference_drift.py +255 -0
  23. package/python/biocore_model/tests/__init__.py +1 -0
  24. package/python/biocore_model/tests/core_mode_ui_check.mjs +63 -0
  25. package/python/biocore_model/tests/core_status_check.mjs +29 -0
  26. package/python/biocore_model/tests/dom_safe_check.mjs +22 -0
  27. package/python/biocore_model/tests/leak_samples_run.py +18 -0
  28. package/python/biocore_model/tests/orb_ring_check.mjs +20 -0
  29. package/python/biocore_model/tests/panel_html_check.mjs +63 -0
  30. package/python/biocore_model/tests/persona_flow_check.mjs +17 -0
  31. package/python/biocore_model/tests/render_guard_check.mjs +19 -0
  32. package/python/biocore_model/tests/switch_groups_check.mjs +94 -0
  33. package/python/biocore_model/tests/tabs_state_check.mjs +24 -0
  34. package/python/biocore_model/tests/test_affective_labor.py +102 -0
  35. package/python/biocore_model/tests/test_appraisal.py +137 -0
  36. package/python/biocore_model/tests/test_appraisal_action.py +55 -0
  37. package/python/biocore_model/tests/test_arch_convergence.py +35 -0
  38. package/python/biocore_model/tests/test_attachment_behavior.py +95 -0
  39. package/python/biocore_model/tests/test_attachment_regulation.py +63 -0
  40. package/python/biocore_model/tests/test_attachment_state.py +29 -0
  41. package/python/biocore_model/tests/test_attribution.py +56 -0
  42. package/python/biocore_model/tests/test_attributor.py +108 -0
  43. package/python/biocore_model/tests/test_autonomic.py +69 -0
  44. package/python/biocore_model/tests/test_autonomy_accumulator.py +84 -0
  45. package/python/biocore_model/tests/test_autonomy_affect.py +62 -0
  46. package/python/biocore_model/tests/test_autonomy_blame.py +44 -0
  47. package/python/biocore_model/tests/test_autonomy_caps.py +29 -0
  48. package/python/biocore_model/tests/test_autonomy_check.py +40 -0
  49. package/python/biocore_model/tests/test_autonomy_clingy.py +41 -0
  50. package/python/biocore_model/tests/test_autonomy_cooldown_selfcontact.py +138 -0
  51. package/python/biocore_model/tests/test_autonomy_frustration.py +42 -0
  52. package/python/biocore_model/tests/test_autonomy_hourly_limit.py +46 -0
  53. package/python/biocore_model/tests/test_autonomy_loop.py +64 -0
  54. package/python/biocore_model/tests/test_autonomy_mode.py +37 -0
  55. package/python/biocore_model/tests/test_autonomy_model.py +60 -0
  56. package/python/biocore_model/tests/test_autonomy_model_v2.py +68 -0
  57. package/python/biocore_model/tests/test_autonomy_module_text.py +104 -0
  58. package/python/biocore_model/tests/test_autonomy_params.py +163 -0
  59. package/python/biocore_model/tests/test_autonomy_reason.py +34 -0
  60. package/python/biocore_model/tests/test_autonomy_self.py +153 -0
  61. package/python/biocore_model/tests/test_autonomy_send_gate.py +38 -0
  62. package/python/biocore_model/tests/test_autonomy_silence.py +37 -0
  63. package/python/biocore_model/tests/test_autonomy_status.py +35 -0
  64. package/python/biocore_model/tests/test_autonomy_test_trigger.py +24 -0
  65. package/python/biocore_model/tests/test_autonomy_tick.py +148 -0
  66. package/python/biocore_model/tests/test_autonomy_v4.py +64 -0
  67. package/python/biocore_model/tests/test_autonomy_v5.py +43 -0
  68. package/python/biocore_model/tests/test_autonomy_v7.py +48 -0
  69. package/python/biocore_model/tests/test_autonomy_v_theta.py +85 -0
  70. package/python/biocore_model/tests/test_batch1_wiring.py +231 -0
  71. package/python/biocore_model/tests/test_batch2_wiring.py +231 -0
  72. package/python/biocore_model/tests/test_batch3_wiring.py +223 -0
  73. package/python/biocore_model/tests/test_bio_tick.py +202 -0
  74. package/python/biocore_model/tests/test_boundary.py +183 -0
  75. package/python/biocore_model/tests/test_boundary_gate.py +295 -0
  76. package/python/biocore_model/tests/test_brain_split.py +99 -0
  77. package/python/biocore_model/tests/test_co_recall.py +350 -0
  78. package/python/biocore_model/tests/test_commitment_extractor.py +197 -0
  79. package/python/biocore_model/tests/test_compassion_fatigue.py +84 -0
  80. package/python/biocore_model/tests/test_connection_bids.py +69 -0
  81. package/python/biocore_model/tests/test_core_bridge.py +88 -0
  82. package/python/biocore_model/tests/test_core_bridge_episode.py +14 -0
  83. package/python/biocore_model/tests/test_core_bridge_forget.py +17 -0
  84. package/python/biocore_model/tests/test_core_bridge_identity.py +15 -0
  85. package/python/biocore_model/tests/test_core_bridge_media.py +17 -0
  86. package/python/biocore_model/tests/test_core_bridge_relation.py +17 -0
  87. package/python/biocore_model/tests/test_core_bridge_summary.py +17 -0
  88. package/python/biocore_model/tests/test_core_client.py +156 -0
  89. package/python/biocore_model/tests/test_core_commands.py +146 -0
  90. package/python/biocore_model/tests/test_core_mode.py +161 -0
  91. package/python/biocore_model/tests/test_core_state.py +240 -0
  92. package/python/biocore_model/tests/test_criticality.py +61 -0
  93. package/python/biocore_model/tests/test_cross_session_copy.py +67 -0
  94. package/python/biocore_model/tests/test_debug_values.py +169 -0
  95. package/python/biocore_model/tests/test_defense_mechanisms.py +177 -0
  96. package/python/biocore_model/tests/test_delta_budget.py +157 -0
  97. package/python/biocore_model/tests/test_diag_trace.py +201 -0
  98. package/python/biocore_model/tests/test_directives_compat.py +698 -0
  99. package/python/biocore_model/tests/test_dissonance.py +97 -0
  100. package/python/biocore_model/tests/test_drift_sign.py +87 -0
  101. package/python/biocore_model/tests/test_embedding_fallback.py +277 -0
  102. package/python/biocore_model/tests/test_embedding_local.py +279 -0
  103. package/python/biocore_model/tests/test_embedding_mode.py +175 -0
  104. package/python/biocore_model/tests/test_embedding_modes.py +342 -0
  105. package/python/biocore_model/tests/test_emotion_geometry.py +114 -0
  106. package/python/biocore_model/tests/test_emotion_probe.py +214 -0
  107. package/python/biocore_model/tests/test_emotion_probe_concurrency.py +128 -0
  108. package/python/biocore_model/tests/test_entity_graph.py +198 -0
  109. package/python/biocore_model/tests/test_entity_resolve.py +446 -0
  110. package/python/biocore_model/tests/test_envy.py +173 -0
  111. package/python/biocore_model/tests/test_exit_or_silence.py +209 -0
  112. package/python/biocore_model/tests/test_expr_guard.py +1 -0
  113. package/python/biocore_model/tests/test_expr_intent.py +66 -0
  114. package/python/biocore_model/tests/test_expr_state.py +66 -0
  115. package/python/biocore_model/tests/test_expr_template.py +71 -0
  116. package/python/biocore_model/tests/test_expr_tone.py +41 -0
  117. package/python/biocore_model/tests/test_extreme_scenarios.py +147 -0
  118. package/python/biocore_model/tests/test_feedback_flow.py +185 -0
  119. package/python/biocore_model/tests/test_feedback_guard.py +65 -0
  120. package/python/biocore_model/tests/test_feedback_llm.py +59 -0
  121. package/python/biocore_model/tests/test_feedback_llm_inferred.py +140 -0
  122. package/python/biocore_model/tests/test_feedback_memory.py +63 -0
  123. package/python/biocore_model/tests/test_feedback_user.py +61 -0
  124. package/python/biocore_model/tests/test_frustration_curve.py +66 -0
  125. package/python/biocore_model/tests/test_global_bio_state.py +116 -0
  126. package/python/biocore_model/tests/test_governance.py +181 -0
  127. package/python/biocore_model/tests/test_granularity.py +133 -0
  128. package/python/biocore_model/tests/test_graph_extract.py +224 -0
  129. package/python/biocore_model/tests/test_graph_recall.py +226 -0
  130. package/python/biocore_model/tests/test_graph_sqlite.py +298 -0
  131. package/python/biocore_model/tests/test_growth.py +301 -0
  132. package/python/biocore_model/tests/test_intent_render.py +60 -0
  133. package/python/biocore_model/tests/test_intero_regulation.py +224 -0
  134. package/python/biocore_model/tests/test_intero_style.py +146 -0
  135. package/python/biocore_model/tests/test_item3_numeric.py +105 -0
  136. package/python/biocore_model/tests/test_item6_numeric.py +116 -0
  137. package/python/biocore_model/tests/test_layered_memory.py +174 -0
  138. package/python/biocore_model/tests/test_leak_guard.py +75 -0
  139. package/python/biocore_model/tests/test_learning_probe.py +93 -0
  140. package/python/biocore_model/tests/test_life_loop.py +222 -0
  141. package/python/biocore_model/tests/test_life_loop_10s.py +131 -0
  142. package/python/biocore_model/tests/test_local_embedding_health.py +258 -0
  143. package/python/biocore_model/tests/test_memory_affect_encoding.py +79 -0
  144. package/python/biocore_model/tests/test_memory_backend.py +332 -0
  145. package/python/biocore_model/tests/test_memory_dedup.py +316 -0
  146. package/python/biocore_model/tests/test_memory_dual.py +219 -0
  147. package/python/biocore_model/tests/test_memory_dual_wiring.py +219 -0
  148. package/python/biocore_model/tests/test_memory_feedback.py +241 -0
  149. package/python/biocore_model/tests/test_memory_fixes.py +84 -0
  150. package/python/biocore_model/tests/test_memory_forgetting.py +58 -0
  151. package/python/biocore_model/tests/test_memory_inject.py +211 -0
  152. package/python/biocore_model/tests/test_memory_mood_recall.py +118 -0
  153. package/python/biocore_model/tests/test_memory_store.py +372 -0
  154. package/python/biocore_model/tests/test_metacog.py +103 -0
  155. package/python/biocore_model/tests/test_migrate.py +245 -0
  156. package/python/biocore_model/tests/test_migrate_store.py +194 -0
  157. package/python/biocore_model/tests/test_mind_wander.py +65 -0
  158. package/python/biocore_model/tests/test_mood.py +35 -0
  159. package/python/biocore_model/tests/test_narrative.py +114 -0
  160. package/python/biocore_model/tests/test_needs_esteem.py +51 -0
  161. package/python/biocore_model/tests/test_needs_events.py +205 -0
  162. package/python/biocore_model/tests/test_needs_sleep.py +54 -0
  163. package/python/biocore_model/tests/test_noise_bank.py +54 -0
  164. package/python/biocore_model/tests/test_numeric_schema.py +98 -0
  165. package/python/biocore_model/tests/test_output_protocol.py +65 -0
  166. package/python/biocore_model/tests/test_page_debug.py +147 -0
  167. package/python/biocore_model/tests/test_page_status_autonomy.py +215 -0
  168. package/python/biocore_model/tests/test_pipeline.py +345 -0
  169. package/python/biocore_model/tests/test_pipeline_api.py +192 -0
  170. package/python/biocore_model/tests/test_pipeline_mode.py +222 -0
  171. package/python/biocore_model/tests/test_plugin_config.py +85 -0
  172. package/python/biocore_model/tests/test_policy_learning.py +116 -0
  173. package/python/biocore_model/tests/test_possible_selves.py +88 -0
  174. package/python/biocore_model/tests/test_predictor.py +227 -0
  175. package/python/biocore_model/tests/test_preference_drift.py +142 -0
  176. package/python/biocore_model/tests/test_preset.py +142 -0
  177. package/python/biocore_model/tests/test_proactive_context.py +70 -0
  178. package/python/biocore_model/tests/test_proactive_history.py +90 -0
  179. package/python/biocore_model/tests/test_proactive_memory.py +141 -0
  180. package/python/biocore_model/tests/test_proactive_send_record.py +88 -0
  181. package/python/biocore_model/tests/test_probe_contract.py +71 -0
  182. package/python/biocore_model/tests/test_prospective.py +154 -0
  183. package/python/biocore_model/tests/test_provider_fallback.py +89 -0
  184. package/python/biocore_model/tests/test_reconstruction.py +111 -0
  185. package/python/biocore_model/tests/test_regulation_strategies.py +228 -0
  186. package/python/biocore_model/tests/test_relation_stage.py +114 -0
  187. package/python/biocore_model/tests/test_relationship_stage.py +59 -0
  188. package/python/biocore_model/tests/test_release_140.py +55 -0
  189. package/python/biocore_model/tests/test_repair_after_boundary.py +249 -0
  190. package/python/biocore_model/tests/test_reply_limit.py +86 -0
  191. package/python/biocore_model/tests/test_reply_limit_persist.py +48 -0
  192. package/python/biocore_model/tests/test_reply_split.py +52 -0
  193. package/python/biocore_model/tests/test_reply_split_astrbot.py +66 -0
  194. package/python/biocore_model/tests/test_reply_split_inject.py +53 -0
  195. package/python/biocore_model/tests/test_rerank.py +229 -0
  196. package/python/biocore_model/tests/test_rollback.py +175 -0
  197. package/python/biocore_model/tests/test_runtime.py +175 -0
  198. package/python/biocore_model/tests/test_runtime_active.py +205 -0
  199. package/python/biocore_model/tests/test_runtime_pipeline.py +1583 -0
  200. package/python/biocore_model/tests/test_runtime_proactive.py +231 -0
  201. package/python/biocore_model/tests/test_scheduler.py +79 -0
  202. package/python/biocore_model/tests/test_secrecy.py +105 -0
  203. package/python/biocore_model/tests/test_self_esteem.py +45 -0
  204. package/python/biocore_model/tests/test_self_intent.py +347 -0
  205. package/python/biocore_model/tests/test_self_interest.py +183 -0
  206. package/python/biocore_model/tests/test_self_layer_wiring.py +198 -0
  207. package/python/biocore_model/tests/test_self_verification.py +113 -0
  208. package/python/biocore_model/tests/test_situation.py +142 -0
  209. package/python/biocore_model/tests/test_sleep_circadian.py +41 -0
  210. package/python/biocore_model/tests/test_social_pain.py +49 -0
  211. package/python/biocore_model/tests/test_social_repair.py +67 -0
  212. package/python/biocore_model/tests/test_social_theory.py +78 -0
  213. package/python/biocore_model/tests/test_source_monitoring.py +204 -0
  214. package/python/biocore_model/tests/test_speak_like_group.py +36 -0
  215. package/python/biocore_model/tests/test_stages.py +226 -0
  216. package/python/biocore_model/tests/test_state_tag.py +86 -0
  217. package/python/biocore_model/tests/test_template_expr.py +65 -0
  218. package/python/biocore_model/tests/test_text_inference.py +70 -0
  219. package/python/biocore_model/tests/test_theta_params.py +115 -0
  220. package/python/biocore_model/tests/test_topic_control.py +200 -0
  221. package/python/biocore_model/tests/test_turn_memory.py +105 -0
  222. package/python/biocore_model/tests/test_turn_memory_panel.py +174 -0
  223. package/python/biocore_model/tests/test_ui_settings.py +84 -0
  224. package/python/biocore_model/tests/test_ui_settings_authority.py +179 -0
  225. package/python/biocore_model/tests/test_user_appraisal_async.py +155 -0
  226. package/python/biocore_model/tests/test_user_classify.py +122 -0
  227. package/python/biocore_model/tests/test_value_order.py +117 -0
  228. package/python/biocore_model/tests/test_voice_bank.py +105 -0
  229. package/python/biocore_model/tests/test_waiting_sense.py +216 -0
  230. package/python/biocore_model/tests/test_x_directives.py +101 -0
  231. package/python/biocore_model/tests/ui_compat_check.mjs +22 -0
  232. package/python/biocore_model/tests/view_mode_check.mjs +17 -0
  233. package/python/biocore_runtime/autonomy.py +208 -0
  234. package/python/biocore_runtime/core_state.py +302 -0
  235. package/python/biocore_runtime/governance.py +495 -0
  236. package/python/biocore_runtime/growth.py +528 -0
  237. package/python/biocore_runtime/pipeline.py +850 -23
  238. package/python/biocore_runtime/relationship_state.py +214 -0
  239. package/python/biocore_runtime/self_layer.py +103 -0
  240. package/python/biocore_runtime/service.py +162 -0
  241. package/python/biocore_runtime/standby.py +472 -0
  242. package/ui/panel-layout.js +83 -39
  243. package/ui/panel.css +885 -743
  244. package/ui/panel.html +356 -296
  245. 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])