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,97 @@
1
+ # -*- coding: utf-8 -*-
2
+ """Cognitive dissonance: strength and relief choice."""
3
+ import unittest
4
+
5
+ from biocore_model import dissonance as ds
6
+
7
+ STANCES = [{"text": "不熬夜", "weight": 0.8, "age_days": 1},
8
+ {"text": "不翻旧账", "weight": 0.5, "age_days": 90}]
9
+
10
+ class StrengthTest(unittest.TestCase):
11
+ def test_no_stance_is_zero(self):
12
+ out = ds.strength([], {"kind": "熬夜"})
13
+ self.assertEqual(out["dissonance"], 0.0)
14
+
15
+ def test_no_behaviour_is_zero(self):
16
+ out = ds.strength(STANCES, {})
17
+ self.assertEqual(out["dissonance"], 0.0)
18
+
19
+ def test_matching_stance_creates_tension(self):
20
+ out = ds.strength(STANCES, {"kind": "熬夜", "contradicts": ["不熬夜"]})
21
+ self.assertGreater(out["dissonance"], 0.2)
22
+ self.assertIn("不熬夜", out["matched"])
23
+
24
+ def test_salience_scales(self):
25
+ low = ds.strength(STANCES, {"kind": "熬夜", "contradicts": ["不熬夜"],
26
+ "salience": 0.2})
27
+ high = ds.strength(STANCES, {"kind": "熬夜", "contradicts": ["不熬夜"],
28
+ "salience": 1.0})
29
+ self.assertGreater(high["dissonance"], low["dissonance"])
30
+
31
+ def test_justification_lowers(self):
32
+ raw = ds.strength(STANCES, {"kind": "熬夜", "contradicts": ["不熬夜"]})
33
+ justified = ds.strength(STANCES, {"kind": "熬夜", "contradicts": ["不熬夜"],
34
+ "justification": 0.9})
35
+ self.assertLess(justified["dissonance"], raw["dissonance"])
36
+
37
+ def test_old_stance_binds_less(self):
38
+ fresh = ds.strength([{"text": "不熬夜", "weight": 0.8, "age_days": 1}],
39
+ {"kind": "熬夜", "contradicts": ["不熬夜"]})
40
+ old = ds.strength([{"text": "不熬夜", "weight": 0.8, "age_days": 365}],
41
+ {"kind": "熬夜", "contradicts": ["不熬夜"]})
42
+ self.assertGreater(fresh["dissonance"], old["dissonance"])
43
+
44
+ class ReliefTest(unittest.TestCase):
45
+ def test_low_is_silence(self):
46
+ self.assertEqual(ds.choose_relief(0.1), "沉默")
47
+
48
+ def test_high_fresh_is_admission(self):
49
+ self.assertEqual(ds.choose_relief(0.8, freshness=0.9), "承认")
50
+
51
+ def test_high_stale_is_backtrack(self):
52
+ self.assertEqual(ds.choose_relief(0.8, freshness=0.2), "改口")
53
+
54
+ def test_mid_with_justification_is_rationalisation(self):
55
+ self.assertEqual(ds.choose_relief(0.4, justification=0.5), "合理化")
56
+
57
+ def test_mid_fresh_is_rationalisation(self):
58
+ self.assertEqual(ds.choose_relief(0.4, freshness=0.8), "合理化")
59
+
60
+ def test_all_reliefs_are_known(self):
61
+ for value in (0.0, 0.3, 0.5, 0.7, 1.0):
62
+ self.assertIn(ds.choose_relief(value), ds.RELIEFS)
63
+
64
+ class BoundsTest(unittest.TestCase):
65
+ def test_never_exceeds_one(self):
66
+ out = ds.strength([{"text": "a", "weight": 1.0}],
67
+ {"kind": "x", "contradicts": ["a"],
68
+ "salience": 9, "justification": -3})
69
+ self.assertLessEqual(out["dissonance"], 1.0)
70
+ self.assertGreaterEqual(out["dissonance"], 0.0)
71
+
72
+ class FallbackTest(unittest.TestCase):
73
+ def test_disabled(self):
74
+ out = ds.strength(STANCES, {"kind": "熬夜", "contradicts": ["不熬夜"]},
75
+ flags={"enabled": False})
76
+ self.assertEqual(out["dissonance"], 0.0)
77
+ self.assertEqual(out["relief"], "沉默")
78
+
79
+ def test_malformed(self):
80
+ out = ds.strength("x", 3)
81
+ self.assertEqual(out["dissonance"], 0.0)
82
+ out = ds.strength([None, 5, {"stance": "不熬夜"}], {"kind": "熬夜"})
83
+ self.assertIn(out["dissonance"], (0.0, out["dissonance"]))
84
+
85
+ class DirectiveTest(unittest.TestCase):
86
+ def test_each_relief_has_hint(self):
87
+ for relief in ds.RELIEFS:
88
+ out = ds.directives({"enabled": True, "relief": relief})
89
+ self.assertEqual(out["relief"], relief)
90
+ self.assertGreaterEqual(out["length_scale"], 0.5)
91
+
92
+ def test_disabled_directive(self):
93
+ out = ds.directives({"enabled": False})
94
+ self.assertEqual(out["tone"], "")
95
+
96
+ if __name__ == "__main__":
97
+ unittest.main()
@@ -0,0 +1,87 @@
1
+ # -*- coding: utf-8 -*-
2
+ """Drift sign/monotonicity: silence must eventually drive proactivity."""
3
+ import math
4
+ import unittest
5
+
6
+ from biocore_model import autonomy_model as am
7
+
8
+ LEAK = 0.15
9
+ THETA = 0.55
10
+ NOW = 1_000_000.0
11
+
12
+
13
+ class CalmCore:
14
+ """Calm, close, no open loops: only silence can move the drive."""
15
+ v = 0.0
16
+ a = 0.45
17
+ d = 0.55
18
+ social = 0.6
19
+ stress = 0.1
20
+ energy = 0.5
21
+ allostatic_load = 0.0
22
+ boredom = 0.0
23
+ homeostasis = {"L_int": 0.05}
24
+ labels = {"emotion": "neutral", "intensity": 0.0}
25
+ open_loops = []
26
+ open_loops_due = 0
27
+
28
+ def snapshot(self):
29
+ return {"v": self.v, "a": self.a, "d": self.d, "social": self.social,
30
+ "stress": self.stress, "energy": self.energy,
31
+ "allostatic_load": self.allostatic_load, "boredom": self.boredom,
32
+ "homeostasis": dict(self.homeostasis),
33
+ "open_loops": [], "open_loops_due": 0}
34
+
35
+
36
+ REL = {"state": {"closeness": 0.75, "conflict": 0.2, "repair": 0.2},
37
+ "attachment": {"safe": 0.6}}
38
+
39
+
40
+ def drift_at(hours, core=None):
41
+ parts = am.parts_from_state(core or CalmCore(), REL, now=NOW,
42
+ acc={"last_user_msg_ts": NOW - hours * 3600.0})
43
+ return am.drift_value(parts), parts
44
+
45
+
46
+ def ticks_to_theta(drift, leak=LEAK, theta=THETA):
47
+ """Autonomy ticks (60s) from V=0 to theta under constant drift."""
48
+ if drift <= theta * leak:
49
+ return None
50
+ return math.log(1.0 - theta * leak / drift) / math.log(1.0 - leak)
51
+
52
+
53
+ class DriftMonotonicTest(unittest.TestCase):
54
+ def test_two_six_twentyfour_increasing(self):
55
+ d2, _ = drift_at(2)
56
+ d6, _ = drift_at(6)
57
+ d24, _ = drift_at(24)
58
+ self.assertLess(d2, d6)
59
+ self.assertLess(d6, d24)
60
+ self.assertGreater(d2, 0.0)
61
+
62
+ def test_silence_crosses_threshold_by_two_hours(self):
63
+ d2, parts = drift_at(2)
64
+ self.assertGreater(d2, THETA * LEAK)
65
+ self.assertGreater(parts["lonely"], 0.5)
66
+ self.assertGreater(parts["social_gap"], 0.0)
67
+
68
+ def test_zero_silence_stays_below_threshold(self):
69
+ d0, _ = drift_at(0)
70
+ self.assertLess(d0, THETA * LEAK)
71
+
72
+ def test_time_to_threshold_is_finite(self):
73
+ d2, _ = drift_at(2)
74
+ ticks = ticks_to_theta(d2)
75
+ self.assertIsNotNone(ticks)
76
+ self.assertGreater(ticks, 0.0)
77
+ self.assertLess(ticks, 60.0) # under one hour at 60s ticks
78
+
79
+ def test_drift_never_negative(self):
80
+ for hours in (0, 1, 2, 6, 12, 24, 48):
81
+ drift, parts = drift_at(hours)
82
+ self.assertGreaterEqual(drift, 0.0)
83
+ self.assertLessEqual(drift, 1.0)
84
+
85
+
86
+ if __name__ == "__main__":
87
+ unittest.main()
@@ -0,0 +1,277 @@
1
+ # -*- coding: utf-8 -*-
2
+ """task-192 远程优先 + 本地 int8 降级单测。
3
+
4
+ 远程成功/抛错/超时、本地成功/缺失/维度错误、缓存命中、关闭、健康状态、
5
+ 启动预检与一次性 warning;用 stub 取代真实模型与网络,不需要 onnxruntime。
6
+ """
7
+ import tempfile
8
+ import time
9
+ import unittest
10
+ from pathlib import Path
11
+ from unittest import mock
12
+
13
+ from biocore_memory.recall import embedding as emb
14
+
15
+
16
+ class StubLocal:
17
+ dim = 4
18
+ model_path = "stub.onnx"
19
+ tokenizer_path = ""
20
+ threads = 2
21
+
22
+ def __init__(self, vector=None, fail=False):
23
+ self.vector = list(vector or [1.0, 0.0, 0.0, 0.0])
24
+ self.fail = fail
25
+ self.calls = 0
26
+
27
+ def encode(self, texts):
28
+ self.calls += 1
29
+ if self.fail:
30
+ raise RuntimeError("local boom")
31
+ return [list(self.vector) for _ in texts]
32
+
33
+
34
+ class Remote:
35
+ def __init__(self, vector=None, fail=False, delay=0.0):
36
+ self.vector = list(vector or [0.0, 1.0, 0.0, 0.0])
37
+ self.fail = fail
38
+ self.delay = delay
39
+ self.calls = 0
40
+ self.timeout = 5.0
41
+ self.fallback_config = {"enabled": True, "model_path": "stub.onnx",
42
+ "tokenizer_path": "", "threads": 2,
43
+ "timeout_s": 8.0}
44
+ self.local_embedder = None
45
+ self.local_status = "missing"
46
+
47
+ def embed_text(self, text):
48
+ self.calls += 1
49
+ if self.delay:
50
+ time.sleep(self.delay)
51
+ if self.fail:
52
+ raise RuntimeError("remote down")
53
+ return list(self.vector)
54
+
55
+
56
+ class FallbackBehaviorTest(unittest.TestCase):
57
+ def test_remote_success_skips_local(self):
58
+ provider = Remote()
59
+ local = StubLocal()
60
+ provider.local_embedder = local
61
+ cache = {}
62
+ vector = emb.embed("你好", provider, cache)
63
+ self.assertEqual(vector, [0.0, 1.0, 0.0, 0.0])
64
+ self.assertEqual(provider.calls, 1)
65
+ self.assertEqual(local.calls, 0)
66
+ self.assertIn(emb.cache_key("你好"), cache)
67
+
68
+ def test_remote_failure_uses_local_and_caches(self):
69
+ provider = Remote(fail=True)
70
+ local = StubLocal()
71
+ provider.local_embedder = local
72
+ cache = {}
73
+ vector = emb.embed("你好", provider, cache)
74
+ self.assertEqual(vector, [1.0, 0.0, 0.0, 0.0])
75
+ self.assertEqual(local.calls, 1)
76
+ self.assertEqual(provider.local_status, "missing")
77
+ self.assertEqual(emb.embed("你好", provider, cache),
78
+ [1.0, 0.0, 0.0, 0.0])
79
+ self.assertEqual(provider.calls, 1)
80
+ self.assertEqual(local.calls, 1)
81
+
82
+ def test_remote_timeout_uses_local(self):
83
+ provider = Remote(delay=0.3)
84
+ local = StubLocal()
85
+ provider.local_embedder = local
86
+ started = time.perf_counter()
87
+ vector = emb.embed("你好", provider, {}, timeout=0.1)
88
+ elapsed = time.perf_counter() - started
89
+ self.assertEqual(vector, [1.0, 0.0, 0.0, 0.0])
90
+ self.assertEqual(local.calls, 1)
91
+ self.assertLess(elapsed, 1.0)
92
+
93
+ def test_fallback_disabled_stays_remote_only(self):
94
+ provider = Remote(fail=True)
95
+ provider.fallback_config["enabled"] = False
96
+ local = StubLocal()
97
+ provider.local_embedder = local
98
+ self.assertIsNone(emb.embed("你好", provider))
99
+ self.assertEqual(local.calls, 0)
100
+
101
+ def test_local_sentinel_means_no_retry(self):
102
+ provider = Remote(fail=True)
103
+ provider.local_embedder = False
104
+ self.assertIsNone(emb.embed("你好", provider))
105
+
106
+ def test_local_failure_returns_none_and_disables(self):
107
+ provider = Remote(fail=True)
108
+ provider.local_embedder = StubLocal(fail=True)
109
+ self.assertIsNone(emb.embed("你好", provider))
110
+ self.assertIs(provider.local_embedder, False)
111
+ self.assertEqual(provider.local_status, "failed")
112
+
113
+ def test_local_wrong_dimension_rejected(self):
114
+ provider = Remote(fail=True)
115
+ local = StubLocal(vector=[1.0, 0.0, 0.0])
116
+ local.dim = 4
117
+ provider.local_embedder = local
118
+ self.assertIsNone(emb.embed("你好", provider))
119
+
120
+ def test_local_non_finite_rejected(self):
121
+ provider = Remote(fail=True)
122
+ local = StubLocal(vector=[float("nan"), 0.0, 0.0, 0.0])
123
+ provider.local_embedder = local
124
+ self.assertIsNone(emb.embed("你好", provider))
125
+
126
+ def test_empty_and_malformed_texts(self):
127
+ provider = Remote()
128
+ self.assertIsNone(emb.embed("", provider))
129
+ self.assertIsNone(emb.embed(None, provider))
130
+ self.assertIsNone(emb.embed(" ", provider))
131
+ self.assertEqual(provider.calls, 0)
132
+ self.assertIsNone(emb.embed("你好", None))
133
+
134
+ def test_cache_hit_skips_everything(self):
135
+ provider = Remote()
136
+ cache = {emb.cache_key("你好"): [0.0, 0.0, 1.0, 0.0]}
137
+ vector = emb.embed("你好", provider, cache)
138
+ self.assertEqual(vector, [0.0, 0.0, 1.0, 0.0])
139
+ self.assertEqual(provider.calls, 0)
140
+
141
+
142
+ class ConfigTest(unittest.TestCase):
143
+ def test_local_config_defaults(self):
144
+ cfg = emb.local_config({})
145
+ self.assertFalse(cfg["enabled"]) # task-198: opt-in, default off
146
+ self.assertEqual(cfg["model_path"], "")
147
+ self.assertEqual(cfg["threads"], 2)
148
+ self.assertEqual(cfg["timeout_s"], 8.0)
149
+
150
+ def test_local_config_reads_nested_keys(self):
151
+ cfg = emb.local_config({"embedding": {
152
+ "embedding_local_fallback_enabled": False,
153
+ "embedding_local_model_path": "x.onnx",
154
+ "embedding_local_threads": 4,
155
+ "embedding_timeout_s": 3,
156
+ }})
157
+ self.assertFalse(cfg["enabled"])
158
+ self.assertEqual(cfg["model_path"], "x.onnx")
159
+ self.assertEqual(cfg["threads"], 4)
160
+ self.assertEqual(cfg["timeout_s"], 3.0)
161
+
162
+ def test_load_provider_attaches_fallback_config(self):
163
+ provider = emb.load_provider({"base_url": "http://127.0.0.1:9/v1",
164
+ "model": "missing-model",
165
+ "embedding_local_fallback_enabled": True})
166
+ self.assertIsNotNone(provider)
167
+ self.assertEqual(provider.fallback_config["enabled"], True)
168
+ self.assertEqual(provider.local_status, "missing")
169
+ self.assertEqual(provider.timeout, 8.0)
170
+
171
+ def test_load_provider_disabled_fallback(self):
172
+ provider = emb.load_provider({
173
+ "base_url": "http://127.0.0.1:9/v1", "model": "m",
174
+ "embedding_local_fallback_enabled": False})
175
+ self.assertFalse(provider.fallback_config["enabled"])
176
+ self.assertEqual(provider.timeout, emb.DEFAULT_TIMEOUT)
177
+
178
+ def test_load_provider_nothing_configured(self):
179
+ self.assertIsNone(emb.load_provider({}))
180
+
181
+ def test_default_off_does_not_touch_local_model(self):
182
+ with mock.patch("biocore_memory.embedding_local.LocalEmbedder",
183
+ side_effect=AssertionError("must not load")):
184
+ provider = emb.load_provider({
185
+ "base_url": "http://127.0.0.1:9/v1", "model": "m",
186
+ "embedding_local_model_path": "nope.onnx"})
187
+ self.assertFalse(provider.fallback_config["enabled"])
188
+ self.assertEqual(provider.local_status, "missing")
189
+ self.assertIsNone(provider.local_embedder)
190
+ self.assertIsNone(emb.embed("你好", provider))
191
+
192
+ def test_enabled_without_path_is_missing_pure_remote(self):
193
+ provider = emb.load_provider({
194
+ "base_url": "http://127.0.0.1:9/v1", "model": "m",
195
+ "embedding_local_fallback_enabled": True})
196
+ self.assertTrue(provider.fallback_config["enabled"])
197
+ self.assertEqual(provider.local_status, "missing")
198
+ with self.assertNoLogs("biocore_memory.embedding", level="WARNING"):
199
+ self.assertIsNone(emb.embed("你好", provider))
200
+
201
+ def test_load_provider_local_only(self):
202
+ with mock.patch.object(emb, "_load_local_embedder",
203
+ return_value=StubLocal()):
204
+ provider = emb.load_provider({
205
+ "embedding_local_fallback_enabled": True,
206
+ "embedding_local_model_path": "stub.onnx"})
207
+ self.assertIsNotNone(provider)
208
+ self.assertTrue(provider.local_only)
209
+ self.assertEqual(emb.local_embedding_status(provider), "ok")
210
+ self.assertEqual(provider.embed_text("你好"), [1.0, 0.0, 0.0, 0.0])
211
+ self.assertEqual(emb.embed("你好", provider), [1.0, 0.0, 0.0, 0.0])
212
+
213
+
214
+ class StartupAndHealthTest(unittest.TestCase):
215
+ def setUp(self):
216
+ self._tmp = tempfile.TemporaryDirectory()
217
+ emb._LOCAL_WARNED.clear()
218
+
219
+ def tearDown(self):
220
+ self._tmp.cleanup()
221
+ emb._LOCAL_WARNED.clear()
222
+
223
+ def test_configured_missing_model_warns_once(self):
224
+ path = str(Path(self._tmp.name) / "nope.onnx")
225
+ with self.assertLogs("biocore_memory.embedding", level="WARNING") as logs:
226
+ emb.load_provider({"base_url": "http://127.0.0.1:9/v1",
227
+ "model": "m",
228
+ "embedding_local_fallback_enabled": True,
229
+ "embedding_local_model_path": path})
230
+ emb.load_provider({"base_url": "http://127.0.0.1:9/v1",
231
+ "model": "m",
232
+ "embedding_local_fallback_enabled": True,
233
+ "embedding_local_model_path": path})
234
+ self.assertEqual(len(logs.output), 1)
235
+
236
+ def test_failed_preload_never_retries(self):
237
+ path = str(Path(self._tmp.name) / "nope.onnx")
238
+ provider = emb.load_provider({"base_url": "http://127.0.0.1:9/v1",
239
+ "model": "m",
240
+ "embedding_local_fallback_enabled": True,
241
+ "embedding_local_model_path": path})
242
+ self.assertEqual(provider.local_status, "failed")
243
+ self.assertIs(provider.local_embedder, False)
244
+ with mock.patch.object(emb, "_load_local_embedder",
245
+ side_effect=AssertionError("must not retry")):
246
+ self.assertIsNone(emb._fallback_embed(provider, "你好"))
247
+
248
+ def test_health_statuses(self):
249
+ self.assertEqual(emb.local_embedding_status(None), "missing")
250
+ self.assertEqual(emb.embedding_health(None)["local_embedding"],
251
+ "missing")
252
+ remote = Remote()
253
+ remote.local_status = "failed"
254
+ self.assertEqual(emb.local_embedding_status(remote), "failed")
255
+ remote.local_status = "ok"
256
+ self.assertEqual(emb.local_embedding_status(remote), "ok")
257
+ local_only = emb.LocalOnlyEmbeddingProvider(StubLocal())
258
+ self.assertEqual(emb.local_embedding_status(local_only), "ok")
259
+ health = emb.embedding_health(remote)
260
+ for key in ("local_embedding", "remote_configured", "local_only",
261
+ "local_model_path", "local_threads", "timeout_s"):
262
+ self.assertIn(key, health)
263
+
264
+ def test_engine_health_exposes_local_embedding(self):
265
+ from biocore_memory.engine import MemoryEngine
266
+
267
+ engine = MemoryEngine()
268
+ self.assertEqual(engine.health()["local_embedding"], "missing")
269
+ self.assertEqual(engine.monitor()["local_embedding"], "missing")
270
+ failed = Remote()
271
+ failed.local_status = "failed"
272
+ engine.embedder = failed
273
+ self.assertEqual(engine.health()["local_embedding"], "failed")
274
+
275
+
276
+ if __name__ == "__main__":
277
+ unittest.main()