dsh-biocore 0.2.0 → 0.3.1

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,229 @@
1
+ # -*- coding: utf-8 -*-
2
+ """Rerank core tests: config, protocols, sorting, truncation, silent fallback."""
3
+ import json
4
+ import unittest
5
+
6
+ from biocore_runtime import rerank
7
+
8
+
9
+ class FakeTransport:
10
+ def __init__(self, responses):
11
+ self.responses = list(responses)
12
+ self.calls = []
13
+
14
+ def __call__(self, url, payload, timeout, headers):
15
+ self.calls.append({"url": url, "payload": payload,
16
+ "timeout": timeout, "headers": dict(headers)})
17
+ response = self.responses.pop(0)
18
+ if isinstance(response, Exception):
19
+ raise response
20
+ return response
21
+
22
+
23
+ def ok(results):
24
+ return (200, json.dumps({"results": results}).encode("utf-8"))
25
+
26
+
27
+ def cfg(**overrides):
28
+ data = {
29
+ "rerank_enabled": True,
30
+ "rerank_base_url": "https://api.example.com",
31
+ "rerank_model": "bge-reranker",
32
+ }
33
+ data.update(overrides)
34
+ return data
35
+
36
+
37
+ class ConfigTest(unittest.TestCase):
38
+ def test_defaults(self):
39
+ data = rerank.normalize_config({})
40
+ self.assertFalse(data["rerank_enabled"])
41
+ self.assertEqual(data["rerank_base_url"], "")
42
+ self.assertEqual(data["rerank_model"], "")
43
+ self.assertEqual(data["rerank_api_key"], "")
44
+ self.assertEqual(data["rerank_timeout"], 8.0)
45
+ self.assertEqual(data["rerank_top_n"], 20)
46
+
47
+ def test_clamps_timeout_and_top_n(self):
48
+ data = rerank.normalize_config(cfg(rerank_timeout=999, rerank_top_n=0))
49
+ self.assertEqual(data["rerank_timeout"], 60.0)
50
+ self.assertEqual(data["rerank_top_n"], 1)
51
+ data = rerank.normalize_config(cfg(rerank_timeout=-1, rerank_top_n=9999))
52
+ self.assertEqual(data["rerank_timeout"], 0.1)
53
+ self.assertEqual(data["rerank_top_n"], 1000)
54
+
55
+
56
+ class DegradeTest(unittest.TestCase):
57
+ def test_disabled_never_calls_transport(self):
58
+ transport = FakeTransport([AssertionError("should not call")])
59
+ reasons = []
60
+ out = rerank.rerank_documents(
61
+ "q", ["a", "b"], {"rerank_enabled": False},
62
+ transport=transport, on_reason=reasons.append)
63
+ self.assertIsNone(out)
64
+ self.assertEqual(transport.calls, [])
65
+ self.assertEqual(reasons, ["disabled"])
66
+
67
+ def test_missing_config_degrades(self):
68
+ transport = FakeTransport([AssertionError("should not call")])
69
+ reasons = []
70
+ out = rerank.rerank_documents(
71
+ "q", ["a"], {"rerank_enabled": True, "rerank_model": "m"},
72
+ transport=transport, on_reason=reasons.append)
73
+ self.assertIsNone(out)
74
+ self.assertEqual(transport.calls, [])
75
+ self.assertEqual(reasons, ["missing_config"])
76
+
77
+ def test_non_2xx_degrades(self):
78
+ transport = FakeTransport([(500, b"oops"), (503, b"nope")])
79
+ reasons = []
80
+ out = rerank.rerank_documents("q", ["a", "b"], cfg(),
81
+ transport=transport,
82
+ on_reason=reasons.append)
83
+ self.assertIsNone(out)
84
+ self.assertEqual(len(transport.calls), 2)
85
+ self.assertEqual(reasons, ["http_503"])
86
+
87
+ def test_timeout_degrades_one_attempt(self):
88
+ transport = FakeTransport([TimeoutError("slow")])
89
+ reasons = []
90
+ out = rerank.rerank_documents("q", ["a"], cfg(),
91
+ transport=transport,
92
+ on_reason=reasons.append)
93
+ self.assertIsNone(out)
94
+ self.assertEqual(len(transport.calls), 1)
95
+ self.assertEqual(reasons, ["timeout"])
96
+
97
+ def test_bad_json_degrades(self):
98
+ transport = FakeTransport([(200, b"not json"), (200, b"not json")])
99
+ reasons = []
100
+ out = rerank.rerank_documents("q", ["a"], cfg(),
101
+ transport=transport,
102
+ on_reason=reasons.append)
103
+ self.assertIsNone(out)
104
+ self.assertEqual(reasons, ["bad_json"])
105
+
106
+
107
+ class ProtocolTest(unittest.TestCase):
108
+ def test_rerank_endpoint_first_and_payload(self):
109
+ transport = FakeTransport([ok([{"index": 0, "relevance_score": 0.3}])])
110
+ out = rerank.rerank_documents("query", ["doc-a", "doc-b"], cfg(),
111
+ transport=transport)
112
+ self.assertEqual(len(out), 1)
113
+ self.assertEqual(transport.calls[0]["url"],
114
+ "https://api.example.com/rerank")
115
+ payload = transport.calls[0]["payload"]
116
+ self.assertEqual(payload["model"], "bge-reranker")
117
+ self.assertEqual(payload["query"], "query")
118
+ self.assertEqual(payload["documents"], ["doc-a", "doc-b"])
119
+ self.assertEqual(payload["top_n"], 2)
120
+ self.assertEqual(transport.calls[0]["timeout"], 8.0)
121
+
122
+ def test_v1_rerank_fallback(self):
123
+ transport = FakeTransport([(404, b"no"), ok([{"index": 1, "relevance_score": 0.9}])])
124
+ out = rerank.rerank_documents("q", ["a", "b"], cfg(),
125
+ transport=transport)
126
+ self.assertIsNotNone(out)
127
+ self.assertEqual([call["url"] for call in transport.calls], [
128
+ "https://api.example.com/rerank",
129
+ "https://api.example.com/v1/rerank",
130
+ ])
131
+ self.assertEqual(out[0]["index"], 1)
132
+
133
+ def test_v1_base_does_not_double_v1(self):
134
+ transport = FakeTransport([ok([{"index": 0, "relevance_score": 0.5}])])
135
+ rerank.rerank_documents("q", ["a"], cfg(
136
+ rerank_base_url="https://api.example.com/v1"),
137
+ transport=transport)
138
+ self.assertEqual(transport.calls[0]["url"],
139
+ "https://api.example.com/v1/rerank")
140
+
141
+ def test_auth_header_and_timeout_forwarded(self):
142
+ transport = FakeTransport([ok([{"index": 0, "relevance_score": 0.5}])])
143
+ rerank.rerank_documents("q", ["a"], cfg(
144
+ rerank_api_key="secret", rerank_timeout=3.5),
145
+ transport=transport)
146
+ call = transport.calls[0]
147
+ self.assertEqual(call["headers"].get("Authorization"),
148
+ "Bearer secret")
149
+ self.assertEqual(call["timeout"], 3.5)
150
+
151
+
152
+ class RankingTest(unittest.TestCase):
153
+ def test_sorted_by_relevance(self):
154
+ transport = FakeTransport([ok([
155
+ {"index": 0, "relevance_score": 0.2},
156
+ {"index": 1, "relevance_score": 0.9},
157
+ {"index": 2, "relevance_score": 0.5},
158
+ ])])
159
+ out = rerank.rerank_documents("q", ["a", "b", "c"], cfg(),
160
+ transport=transport)
161
+ self.assertEqual([row["index"] for row in out], [1, 2, 0])
162
+ self.assertEqual([row["document"] for row in out], ["b", "c", "a"])
163
+
164
+ def test_top_n_truncates(self):
165
+ transport = FakeTransport([ok([
166
+ {"index": index, "relevance_score": index / 10.0}
167
+ for index in range(5)
168
+ ])])
169
+ out = rerank.rerank_documents("q", ["a", "b", "c", "d", "e"],
170
+ cfg(rerank_top_n=2),
171
+ transport=transport)
172
+ self.assertEqual(len(out), 2)
173
+ self.assertEqual([row["index"] for row in out], [4, 3])
174
+ self.assertEqual(transport.calls[0]["payload"]["top_n"], 2)
175
+
176
+ def test_invalid_results_skipped(self):
177
+ transport = FakeTransport([ok([
178
+ {"index": 0, "relevance_score": 0.4},
179
+ {"index": 99, "relevance_score": 0.9},
180
+ {"index": 1, "relevance_score": "bad"},
181
+ "garbage",
182
+ ])])
183
+ out = rerank.rerank_documents("q", ["a", "b"], cfg(),
184
+ transport=transport)
185
+ self.assertEqual([row["index"] for row in out], [0])
186
+
187
+ def test_ties_keep_index_order(self):
188
+ transport = FakeTransport([ok([
189
+ {"index": 2, "relevance_score": 0.5},
190
+ {"index": 0, "relevance_score": 0.5},
191
+ {"index": 1, "relevance_score": 0.5},
192
+ ])])
193
+ out = rerank.rerank_documents("q", ["a", "b", "c"], cfg(),
194
+ transport=transport)
195
+ self.assertEqual([row["index"] for row in out], [0, 1, 2])
196
+
197
+
198
+ class PipelineHookTest(unittest.TestCase):
199
+ def test_tool_recall_uses_rerank_hook(self):
200
+ import tempfile
201
+ from unittest.mock import patch
202
+ from biocore_runtime import pipeline as pl
203
+ root = tempfile.mkdtemp(prefix="biocore-rerank-hook-")
204
+ pl.tool_call("remember", {"text": "第一条记忆"}, root, session_id="s")
205
+ pl.tool_call("remember", {"text": "第二条记忆"}, root, session_id="s")
206
+ fake = lambda query, docs, config=None, **kwargs: [
207
+ {"index": 0, "document": docs[0], "relevance_score": 0.9},
208
+ {"index": 1, "document": docs[1], "relevance_score": 0.1},
209
+ ]
210
+ with patch.object(pl._rerank_mod, "rerank_documents", fake):
211
+ out = pl.tool_call("recall", {
212
+ "query": "记忆", "limit": 2,
213
+ "rerank_config": {"rerank_enabled": True},
214
+ }, root, session_id="s")
215
+ self.assertEqual([row["text"] for row in out["results"]],
216
+ ["第二条记忆", "第一条记忆"])
217
+
218
+ def test_recall_action_falls_back_without_config(self):
219
+ from biocore_runtime import pipeline
220
+ out = pipeline.dispatch("recall", {
221
+ "query": "q", "documents": ["a", "b"],
222
+ "config": {"rerank_enabled": False}})
223
+ self.assertTrue(out["ok"])
224
+ self.assertFalse(out["reranked"])
225
+ self.assertEqual([row["index"] for row in out["results"]], [0, 1])
226
+
227
+
228
+ if __name__ == "__main__":
229
+ unittest.main()
@@ -0,0 +1,175 @@
1
+ # -*- coding: utf-8 -*-
2
+ """task-126: rollback snapshot/restore + bounded ring tests."""
3
+ import unittest
4
+ from pathlib import Path
5
+
6
+ from biocore_model import rollback
7
+
8
+ REPO = Path(__file__).resolve().parents[2]
9
+
10
+
11
+ def _doc():
12
+ return {
13
+ "state": {"v": 0.2, "a": 0.6, "d": 0.5, "social": 0.4,
14
+ "stress": 0.3, "energy": 0.7, "trust": 0.55,
15
+ "echo": 0.1, "echo_trace": 0.1, "attention": 0.5,
16
+ "boredom": 0.1, "novelty_need": 0.4,
17
+ "boundary_pressure": 0.0, "refusal_readiness": 0.0,
18
+ "conflict_ema": 0.0, "rumination_ema": 0.0, "ticks": 7,
19
+ "unknown": 99, "label": "x", "flag": True},
20
+ "needs": {"belonging": 0.6, "self_esteem": 0.5, "control": 0.4,
21
+ "meaningful_existence": 0.55, "other": 0.1},
22
+ "relationship": {"trust": 0.55, "state": {"closeness": 0.6},
23
+ "repair_state": {"phase": "stable"}},
24
+ "recent_turns": [{"role": "user", "text": "a"},
25
+ {"role": "assistant", "text": "b"}],
26
+ "memory_marker": {"entries": ["id1"], "turn": {"count": 1}},
27
+ }
28
+
29
+
30
+ class SnapshotTest(unittest.TestCase):
31
+ def test_state_whitelist_only(self):
32
+ snap = rollback.snapshot(_doc())
33
+ self.assertNotIn("unknown", snap["state"])
34
+ self.assertNotIn("label", snap["state"])
35
+ self.assertNotIn("flag", snap["state"])
36
+ self.assertEqual(snap["state"]["v"], 0.2)
37
+ self.assertIn("ticks", snap["state"])
38
+
39
+ def test_needs_whitelist(self):
40
+ snap = rollback.snapshot(_doc())
41
+ self.assertNotIn("other", snap["needs"])
42
+ self.assertEqual(snap["needs"]["belonging"], 0.6)
43
+
44
+ def test_relationship_and_recent_copied(self):
45
+ doc = _doc()
46
+ snap = rollback.snapshot(doc)
47
+ self.assertEqual(snap["relationship"]["trust"], 0.55)
48
+ self.assertEqual(len(snap["recent_turns"]), 2)
49
+ self.assertEqual(snap["memory_marker"]["entries"], ["id1"])
50
+ doc["relationship"]["trust"] = 0.9
51
+ doc["recent_turns"].append({"role": "user", "text": "c"})
52
+ self.assertEqual(snap["relationship"]["trust"], 0.55)
53
+ self.assertEqual(len(snap["recent_turns"]), 2)
54
+
55
+ def test_recent_turns_bounded(self):
56
+ doc = _doc()
57
+ doc["recent_turns"] = [{"role": "user", "text": str(i)}
58
+ for i in range(20)]
59
+ snap = rollback.snapshot(doc)
60
+ self.assertEqual(len(snap["recent_turns"]), rollback.RECENT_LIMIT)
61
+ self.assertEqual(snap["recent_turns"][-1]["text"], "19")
62
+ snap2 = rollback.snapshot(doc, recent_limit=3)
63
+ self.assertEqual(len(snap2["recent_turns"]), 3)
64
+
65
+ def test_empty_and_none_are_safe(self):
66
+ for value in (None, {}, [], "x"):
67
+ snap = rollback.snapshot(value)
68
+ self.assertIsInstance(snap, dict)
69
+ self.assertEqual(snap["state"], {})
70
+ self.assertEqual(snap["recent_turns"], [])
71
+
72
+
73
+ class RestoreTest(unittest.TestCase):
74
+ def test_restore_applies_all_fields(self):
75
+ snap = rollback.snapshot(_doc())
76
+ target = {"state": {"v": -0.5, "stress": 0.9}, "needs": {},
77
+ "relationship": {}, "recent_turns": []}
78
+ out = rollback.restore(target, snap)
79
+ self.assertAlmostEqual(out["state"]["v"], 0.2)
80
+ self.assertAlmostEqual(out["state"]["stress"], 0.3)
81
+ self.assertEqual(out["needs"]["control"], 0.4)
82
+ self.assertEqual(len(out["recent_turns"]), 2)
83
+ self.assertEqual(out["memory_marker"]["entries"], ["id1"])
84
+
85
+ def test_restore_empty_snapshot_keeps_doc(self):
86
+ target = _doc()
87
+ out = rollback.restore(target, {})
88
+ self.assertEqual(out["state"]["v"], 0.2)
89
+ out2 = rollback.restore(target, None)
90
+ self.assertEqual(out2["state"]["stress"], 0.3)
91
+
92
+ def test_restore_malformed_snapshot_is_safe(self):
93
+ target = {"state": {"v": 0.1}}
94
+ for snap in ([], "x", {"state": "nope"}, {"needs": 5}):
95
+ out = rollback.restore(target, snap)
96
+ self.assertEqual(out["state"]["v"], 0.1)
97
+
98
+ def test_restore_does_not_mutate_inputs(self):
99
+ doc = _doc()
100
+ snap = rollback.snapshot(doc)
101
+ before_doc = repr(doc)
102
+ before_snap = repr(snap)
103
+ rollback.restore(doc, snap)
104
+ self.assertEqual(repr(doc), before_doc)
105
+ self.assertEqual(repr(snap), before_snap)
106
+
107
+ def test_roundtrip(self):
108
+ doc = _doc()
109
+ snap = rollback.snapshot(doc)
110
+ doc["state"]["v"] = -0.9
111
+ doc["recent_turns"] = []
112
+ doc["relationship"]["trust"] = 0.1
113
+ restored = rollback.restore(doc, snap)
114
+ self.assertAlmostEqual(restored["state"]["v"], 0.2)
115
+ self.assertEqual(len(restored["recent_turns"]), 2)
116
+ self.assertEqual(restored["relationship"]["trust"], 0.55)
117
+
118
+
119
+ class RingTest(unittest.TestCase):
120
+ def _snap(self, value):
121
+ return {"state": {"v": value}}
122
+
123
+ def test_push_bounded_to_limit(self):
124
+ ring = []
125
+ for index in range(15):
126
+ ring = rollback.push(ring, self._snap(index / 10.0))
127
+ self.assertEqual(len(ring), rollback.RING_LIMIT)
128
+ self.assertAlmostEqual(ring[-1]["state"]["v"], 1.4)
129
+ self.assertAlmostEqual(ring[0]["state"]["v"], 0.5)
130
+
131
+ def test_push_copies_snapshot(self):
132
+ snap = self._snap(0.3)
133
+ ring = rollback.push([], snap)
134
+ snap["state"]["v"] = -1.0
135
+ self.assertAlmostEqual(ring[0]["state"]["v"], 0.3)
136
+
137
+ def test_pop_newest_first(self):
138
+ ring = rollback.push(rollback.push([], self._snap(0.1)),
139
+ self._snap(0.2))
140
+ left, latest = rollback.pop(ring)
141
+ self.assertAlmostEqual(latest["state"]["v"], 0.2)
142
+ self.assertEqual(len(left), 1)
143
+
144
+ def test_pop_empty(self):
145
+ self.assertEqual(rollback.pop([]), ([], None))
146
+ self.assertEqual(rollback.pop(None), ([], None))
147
+
148
+ def test_consecutive_two_rollbacks(self):
149
+ ring = []
150
+ for value in (0.1, 0.2, 0.3):
151
+ ring = rollback.push(ring, self._snap(value))
152
+ ring, first = rollback.pop(ring)
153
+ ring, second = rollback.pop(ring)
154
+ self.assertAlmostEqual(first["state"]["v"], 0.3)
155
+ self.assertAlmostEqual(second["state"]["v"], 0.2)
156
+ self.assertEqual(len(ring), 1)
157
+
158
+ def test_can_rollback_and_describe(self):
159
+ self.assertFalse(rollback.can_rollback([]))
160
+ self.assertTrue(rollback.can_rollback([{"state": {}}]))
161
+ snap = rollback.snapshot(_doc())
162
+ self.assertIn("v=0.20", rollback.describe(snap))
163
+ self.assertEqual(rollback.describe(None), "没有可回退的轮次")
164
+
165
+
166
+ class WiringStaticTest(unittest.TestCase):
167
+ def test_main_has_rollback_command_and_ring(self):
168
+ source = (REPO / "main.py").read_text(encoding="utf-8")
169
+ self.assertIn("sub == \"rollback\"", source)
170
+ self.assertIn("_rollback_ring", source)
171
+ self.assertIn("_rollback_doc", source)
172
+
173
+
174
+ if __name__ == "__main__":
175
+ unittest.main()
@@ -0,0 +1,175 @@
1
+ # -*- coding: utf-8 -*-
2
+ """PIPE-2 runtime tests: default pipeline assembly, switches, run_reply, fallback."""
3
+ import unittest
4
+
5
+ from biocore_model import pipeline as pl
6
+ from biocore_model import runtime as rt
7
+
8
+
9
+ class FakeEvent:
10
+ def __init__(self, text="", umo="umo-9", sender="u-9"):
11
+ self.message_str = text
12
+ self.unified_msg_origin = umo
13
+ self._sender = sender
14
+
15
+ def get_sender_id(self):
16
+ return self._sender
17
+
18
+
19
+ class FakeCore:
20
+ def __init__(self):
21
+ self._snapshot = {"emotion": "calm", "intensity": 0.5, "v": 0.1,
22
+ "a": 0.2, "stress": 0.1, "self_esteem": 0.7}
23
+ self.observed = []
24
+ self.loaded = []
25
+
26
+ def snapshot(self):
27
+ return dict(self._snapshot)
28
+
29
+ def observe(self, text, embedding=None):
30
+ self.observed.append(text)
31
+
32
+ def load_state(self, state):
33
+ self.loaded.append(dict(state))
34
+
35
+
36
+ class BoomPipeline(pl.Pipeline):
37
+ def run(self, ctx=None, start=None):
38
+ raise RuntimeError("runtime boom")
39
+
40
+
41
+ def request_ctx(text="你好", llm=None, core=None, memory=None):
42
+ meta = {"now": 1000.0}
43
+ if llm is not None:
44
+ meta["llm"] = llm
45
+ return rt.make_request_ctx(plugin=object(), event=FakeEvent(text),
46
+ core=core or FakeCore(), text=text,
47
+ memory=memory or [], now=1000.0, meta=meta)
48
+
49
+
50
+ class BuildTest(unittest.TestCase):
51
+ def test_default_pipeline_order(self):
52
+ p = rt.build_default_pipeline()
53
+ self.assertEqual(p.names(),
54
+ ["perceive", "memory", "bio", "decide", "expr_state",
55
+ "expr_tone", "expr_intent", "think", "parse",
56
+ "feedback", "present"])
57
+
58
+ def test_default_switches_reflect_in_status(self):
59
+ p = rt.build_default_pipeline()
60
+ rows = {row["name"]: row for row in p.get_status()["stages"]}
61
+ self.assertTrue(rows["memory"]["enabled"])
62
+ self.assertTrue(rows["think"]["enabled"])
63
+ off = rt.build_default_pipeline(switches={"memory": False, "bio": False})
64
+ rows = {row["name"]: row for row in off.get_status()["stages"]}
65
+ self.assertFalse(rows["memory"]["enabled"])
66
+ self.assertFalse(rows["bio"]["enabled"])
67
+ # think is in PIPE-1 REQUIRED_STAGES, so it stays registered; a missing
68
+ # llm already makes it a clean no-op instead of aborting the mainline.
69
+ locked = rt.build_default_pipeline(switches={"think": False})
70
+ rows = {row["name"]: row for row in locked.get_status()["stages"]}
71
+ self.assertTrue(rows["think"]["enabled"])
72
+
73
+ def test_singleton_identity_and_reset(self):
74
+ rt.reset_pipeline()
75
+ first = rt.get_pipeline()
76
+ self.assertIs(first, rt.get_pipeline())
77
+ rt.reset_pipeline()
78
+ self.assertIsNot(first, rt.get_pipeline())
79
+
80
+ def test_required_stages_cannot_be_disabled(self):
81
+ rt.reset_pipeline()
82
+ for name in rt.REQUIRED_STAGES:
83
+ self.assertRaises(ValueError, rt.set_enabled, name, False)
84
+
85
+
86
+ class RunReplyTest(unittest.TestCase):
87
+ def test_end_to_end_with_llm(self):
88
+ ctx = request_ctx(llm=lambda prompt: "我在。")
89
+ out = rt.run_reply(ctx)
90
+ self.assertEqual(rt.reply_text(out), "我在。")
91
+ self.assertNotIn("pipeline_error", out["meta"])
92
+ names = [row["name"] for row in out["trace"]["stages"]]
93
+ self.assertEqual(names, list(rt.get_pipeline().names()))
94
+
95
+ def test_without_llm_has_no_reply_and_no_raise(self):
96
+ out = rt.run_reply(request_ctx())
97
+ self.assertEqual(rt.reply_text(out), "")
98
+ self.assertNotIn("pipeline_error", out["meta"])
99
+ self.assertIn("think", [row["stage"] for row in out["meta"]["skipped"]])
100
+
101
+ def test_abort_is_contained(self):
102
+ p = pl.Pipeline()
103
+ p.register(pl.Stage("perceive", lambda c: 1 / 0))
104
+ out = rt.run_reply(request_ctx(), pipeline=p)
105
+ self.assertEqual(out["meta"]["pipeline_error"]["kind"], "abort")
106
+ self.assertEqual(out["meta"]["pipeline_error"]["stage"], "perceive")
107
+
108
+ def test_exception_is_contained(self):
109
+ out = rt.run_reply(request_ctx(), pipeline=BoomPipeline())
110
+ self.assertEqual(out["meta"]["pipeline_error"]["kind"], "exception")
111
+
112
+ def test_legal_text_has_no_protocol_leak(self):
113
+ fence = chr(96) * 3
114
+ raw = ('好。<bc>{"e":"joy","i":0.6}</bc>' + fence + 'json\n'
115
+ + '{"e":"joy"}\n' + fence)
116
+ out = rt.run_reply(request_ctx(llm=lambda prompt: raw))
117
+ text = rt.reply_text(out)
118
+ self.assertTrue(text)
119
+ self.assertNotIn("<bc>", text)
120
+ self.assertNotIn("json", text)
121
+
122
+ def test_disabled_memory_stage_still_valid(self):
123
+ p = rt.build_default_pipeline(switches={"memory": False})
124
+ out = rt.run_reply(request_ctx(llm=lambda prompt: "嗯。"), pipeline=p)
125
+ rows = {row["name"]: row for row in out["trace"]["stages"]}
126
+ self.assertEqual(rows["memory"]["status"], "skipped")
127
+ self.assertEqual(rt.reply_text(out), "嗯。")
128
+
129
+ def test_think_error_does_not_abort(self):
130
+ def boom(prompt):
131
+ raise RuntimeError("provider down")
132
+ out = rt.run_reply(request_ctx(llm=boom))
133
+ self.assertNotIn("pipeline_error", out["meta"])
134
+ self.assertEqual(rt.reply_text(out), "")
135
+ self.assertIn("think", [row["stage"] for row in out["meta"]["skipped"]])
136
+
137
+
138
+ class RequestCtxTest(unittest.TestCase):
139
+ def test_fields_and_side_effect_flags(self):
140
+ core = FakeCore()
141
+ ctx = request_ctx(core=core)
142
+ self.assertEqual(ctx["user"], "u-9")
143
+ self.assertEqual(ctx["umo"], "umo-9")
144
+ self.assertEqual(ctx["state"]["emotion"], "calm")
145
+ self.assertTrue(ctx["meta"]["shadow"])
146
+ self.assertFalse(ctx["meta"]["write_core"])
147
+ self.assertFalse(ctx["meta"]["observe"])
148
+
149
+ def test_shadow_run_does_not_touch_core(self):
150
+ core = FakeCore()
151
+ out = rt.run_reply(request_ctx(llm=lambda prompt: "ok", core=core))
152
+ self.assertEqual(core.observed, [])
153
+ self.assertEqual(core.loaded, [])
154
+ self.assertEqual(out["core"], core)
155
+
156
+ def test_trace_ring_recorded(self):
157
+ out = rt.run_reply(request_ctx(llm=lambda prompt: "hi"))
158
+ self.assertEqual(len(out["trace"]["stages"]), 11)
159
+ self.assertIn("ring", out["trace"])
160
+ self.assertGreaterEqual(len(out["trace"]["ring"]), 11)
161
+
162
+
163
+ class StatusTest(unittest.TestCase):
164
+ def test_get_status_and_set_enabled(self):
165
+ rt.reset_pipeline()
166
+ rt.set_enabled("expr_intent", False)
167
+ rows = {row["name"]: row for row in rt.get_status()["stages"]}
168
+ self.assertFalse(rows["expr_intent"]["enabled"])
169
+ rt.set_enabled("expr_intent", True)
170
+ rows = {row["name"]: row for row in rt.get_status()["stages"]}
171
+ self.assertTrue(rows["expr_intent"]["enabled"])
172
+
173
+
174
+ if __name__ == "__main__":
175
+ unittest.main()